diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1ff0c423 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..b06e864a --- /dev/null +++ b/.gitignore @@ -0,0 +1,212 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml diff --git a/.project b/.project new file mode 100644 index 00000000..6486c564 --- /dev/null +++ b/.project @@ -0,0 +1,11 @@ + + + eMule + + + + + + + + diff --git a/3DPreviewControl.cpp b/3DPreviewControl.cpp new file mode 100644 index 00000000..8c4d54cb --- /dev/null +++ b/3DPreviewControl.cpp @@ -0,0 +1,61 @@ +#include "stdafx.h" +#include "eMule.h" +#include "3DPreviewControl.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +CBarShader C3DPreviewControl::s_preview(16,32); + +// C3DPreviewControl +IMPLEMENT_DYNAMIC(C3DPreviewControl, CStatic) + +BEGIN_MESSAGE_MAP(C3DPreviewControl, CStatic) + ON_WM_PAINT() +END_MESSAGE_MAP() + +C3DPreviewControl::C3DPreviewControl() +: m_iSliderPos(0) // use flat +{ +} + +C3DPreviewControl::~C3DPreviewControl() +{ +} + +// Sets "slider" position for type of preview + + +void C3DPreviewControl::SetSliderPos(int iPos) +{ + if ( iPos <= 5 && iPos >= -5) + { + m_iSliderPos = iPos; + } + if ( GetSafeHwnd() ) + { + Invalidate(); + UpdateWindow(); + } +} + +void C3DPreviewControl::OnPaint() +{ + CPaintDC dc(this); // device context for painting + RECT outline_rec; + outline_rec.top=0; + outline_rec.bottom=18; + outline_rec.left=0; + outline_rec.right=34; + CBrush gdiBrush(RGB(104,104,104)); + CBrush* pOldBrush = dc.SelectObject(&gdiBrush); //eklmn: select a new brush + dc.FrameRect(&outline_rec, &gdiBrush); + dc.SelectObject(pOldBrush); //eklmn: recover an old brush + s_preview.SetFileSize((uint64)32); + s_preview.Fill(RGB(192,192,255)); + s_preview.DrawPreview(&dc, 1, 1, m_iSliderPos); +} diff --git a/3DPreviewControl.h b/3DPreviewControl.h new file mode 100644 index 00000000..8128b718 --- /dev/null +++ b/3DPreviewControl.h @@ -0,0 +1,25 @@ +#pragma once + +#include "barshader.h" + +// C3DPreviewControl + +class C3DPreviewControl : public CStatic +{ + DECLARE_DYNAMIC(C3DPreviewControl) + +public: + C3DPreviewControl(); + virtual ~C3DPreviewControl(); + +protected: + DECLARE_MESSAGE_MAP() + int m_iSliderPos; + static CBarShader s_preview; +public: + // Sets "slider" position for type of preview + void SetSliderPos(int iPos); + afx_msg void OnPaint(); +}; + + diff --git a/AICHSyncThread.cpp b/AICHSyncThread.cpp new file mode 100644 index 00000000..8001784b --- /dev/null +++ b/AICHSyncThread.cpp @@ -0,0 +1,438 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "StdAfx.h" +#include "aichsyncthread.h" +#include "shahashset.h" +#include "safefile.h" +#include "knownfile.h" +#include "sha.h" +#include "emule.h" +#include "emuledlg.h" +#include "sharedfilelist.h" +#include "knownfilelist.h" +#include "preferences.h" +#include "sharedfileswnd.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +///////////////////////////////////////////////////////////////////////////////////////// +///CAICHSyncThread +IMPLEMENT_DYNCREATE(CAICHSyncThread, CWinThread) + +CAICHSyncThread::CAICHSyncThread() +{ + +} + +BOOL CAICHSyncThread::InitInstance() +{ + DbgSetThreadName("AICHSyncThread"); + InitThreadLocale(); + return TRUE; +} + +int CAICHSyncThread::Run() +{ + if ( !theApp.emuledlg->IsRunning() ) + return 0; + // we need to keep a lock on this file while the thread is running + CSingleLock lockKnown2Met(&CAICHRecoveryHashSet::m_mutKnown2File); + lockKnown2Met.Lock(); + + CSafeFile file; + bool bJustCreated = ConvertToKnown2ToKnown264(&file); + + // we collect all masterhashs which we find in the known2.met and store them in a list + CArray aKnown2Hashs; + CArray aKnown2HashsFilePos; + CString fullpath = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR); + fullpath.Append(KNOWN2_MET_FILENAME); + + CFileException fexp; + uint32 nLastVerifiedPos = 0; + + if (!bJustCreated && !file.Open(fullpath,CFile::modeCreate|CFile::modeReadWrite|CFile::modeNoTruncate|CFile::osSequentialScan|CFile::typeBinary|CFile::shareDenyNone, &fexp)){ + if (fexp.m_cause != CFileException::fileNotFound){ + CString strError(_T("Failed to load ") KNOWN2_MET_FILENAME _T(" file")); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + } + return false; + } + try { + if (file.GetLength() >= 1){ + uint8 header = file.ReadUInt8(); + if (header != KNOWN2_MET_VERSION){ + AfxThrowFileException(CFileException::endOfFile, 0, file.GetFileName()); + } + //setvbuf(file.m_pStream, NULL, _IOFBF, 16384); + uint32 nExistingSize = (UINT)file.GetLength(); + uint32 nHashCount; + while (file.GetPosition() < nExistingSize){ + aKnown2HashsFilePos.Add(file.GetPosition()); + aKnown2Hashs.Add(CAICHHash(&file)); + nHashCount = file.ReadUInt32(); + if (file.GetPosition() + nHashCount*CAICHHash::GetHashSize() > nExistingSize){ + AfxThrowFileException(CFileException::endOfFile, 0, file.GetFileName()); + } + // skip the rest of this hashset + file.Seek(nHashCount*CAICHHash::GetHashSize(), CFile::current); + nLastVerifiedPos = (UINT)file.GetPosition(); + } + } + else + file.WriteUInt8(KNOWN2_MET_VERSION); + } + catch(CFileException* error){ + if (error->m_cause == CFileException::endOfFile){ + LogError(LOG_STATUSBAR,GetResString(IDS_ERR_MET_BAD), KNOWN2_MET_FILENAME); + // truncate the file to the size to the last verified valid pos + try{ + file.SetLength(nLastVerifiedPos); + if (file.GetLength() == 0){ + file.SeekToBegin(); + file.WriteUInt8(KNOWN2_MET_VERSION); + } + } + catch(CFileException* error2){ + error2->Delete(); + } + } + else{ + TCHAR buffer[MAX_CFEXP_ERRORMSG]; + error->GetErrorMessage(buffer, ARRSIZE(buffer)); + LogError(LOG_STATUSBAR,GetResString(IDS_ERR_SERVERMET_UNKNOWN),buffer); + } + error->Delete(); + return false; + } + + // now we check that all files which are in the sharedfilelist have a corresponding hash in out list + // those who don'T are added to the hashinglist + CList liUsedHashs; + CSingleLock sharelock(&theApp.sharedfiles->m_mutWriteList); + sharelock.Lock(); + + bool bDbgMsgCreatingPartHashs = true; + for (int i = 0; i < theApp.sharedfiles->GetCount(); i++){ + CKnownFile* pCurFile = theApp.sharedfiles->GetFileByIndex(i); + if (pCurFile != NULL && !pCurFile->IsPartFile() ) + { + if (theApp.emuledlg==NULL || !theApp.emuledlg->IsRunning()) // in case of shutdown while still hashing + return 0; + if (pCurFile->GetFileIdentifier().HasAICHHash()){ + bool bFound = false; + for (int i = 0; i < aKnown2Hashs.GetCount(); i++) + { + CAICHHash current_hash = aKnown2Hashs[i]; + if (current_hash == pCurFile->GetFileIdentifier().GetAICHHash()){ + bFound = true; + liUsedHashs.AddTail(current_hash); + pCurFile->SetAICHRecoverHashSetAvailable(true); + // Has the file the proper AICH Parthashset? If not probably upgrading, create it + if (!pCurFile->GetFileIdentifier().HasExpectedAICHHashCount()) + { + if (bDbgMsgCreatingPartHashs) + { + bDbgMsgCreatingPartHashs = false; + DebugLogWarning(_T("Missing AICH Part Hashsets for known files - maybe upgrading from earlier version. Creating them out of full AICH Recovery Hashsets, shouldn't take too long")); + } + CAICHRecoveryHashSet tempHashSet(pCurFile, pCurFile->GetFileSize()); + tempHashSet.SetMasterHash(pCurFile->GetFileIdentifier().GetAICHHash(), AICH_HASHSETCOMPLETE); + if (!tempHashSet.LoadHashSet()) + { + ASSERT( false ); + DebugLogError(_T("Failed to load full AICH Recovery Hashset - known2.met might be corrupt. Unable to create AICH Part Hashset - %s"), pCurFile->GetFileName()); + } + else + { + if (!pCurFile->GetFileIdentifier().SetAICHHashSet(tempHashSet)) + { + DebugLogError(_T("Failed to create AICH Part Hashset out of full AICH Recovery Hashset - %s"), pCurFile->GetFileName()); + ASSERT( false ); + } + ASSERT(pCurFile->GetFileIdentifier().HasExpectedAICHHashCount()); + } + } + //theApp.QueueDebugLogLine(false, _T("%s - %s"), current_hash.GetString(), pCurFile->GetFileName()); + break; + } + } + if (bFound) // hashset is available, everything fine with this file + continue; + } + pCurFile->SetAICHRecoverHashSetAvailable(false); + m_liToHash.AddTail(pCurFile); + } + } + sharelock.Unlock(); + + // removed all unused AICH hashsets from known2.met + if (liUsedHashs.GetCount() != aKnown2Hashs.GetCount() && + (!thePrefs.IsRememberingDownloadedFiles() || thePrefs.DoPartiallyPurgeOldKnownFiles())) + { + file.SeekToBegin(); + try { + uint8 header = file.ReadUInt8(); + if (header != KNOWN2_MET_VERSION){ + AfxThrowFileException(CFileException::endOfFile, 0, file.GetFileName()); + } + + uint32 nExistingSize = (UINT)file.GetLength(); + uint32 nHashCount; + ULONGLONG posWritePos = file.GetPosition(); + ULONGLONG posReadPos = file.GetPosition(); + uint32 nPurgeCount = 0; + uint32 nPurgeBecauseOld = 0; + while (file.GetPosition() < nExistingSize){ + ULONGLONG nCurrentHashsetPos = file.GetPosition(); + CAICHHash aichHash(&file); + nHashCount = file.ReadUInt32(); + if (file.GetPosition() + nHashCount*CAICHHash::GetHashSize() > nExistingSize){ + AfxThrowFileException(CFileException::endOfFile, 0, file.GetFileName()); + } + if (!thePrefs.IsRememberingDownloadedFiles() && liUsedHashs.Find(aichHash) == NULL) + { + // unused hashset skip the rest of this hashset + file.Seek(nHashCount*CAICHHash::GetHashSize(), CFile::current); + nPurgeCount++; + } + else if (thePrefs.IsRememberingDownloadedFiles() && theApp.knownfiles->ShouldPurgeAICHHashset(aichHash)) + { + ASSERT( thePrefs.DoPartiallyPurgeOldKnownFiles() ); + // also unused (purged) hashset skip the rest of this hashset + file.Seek(nHashCount*CAICHHash::GetHashSize(), CFile::current); + nPurgeCount++; + nPurgeBecauseOld++; + } + else if(nPurgeCount == 0){ + // used Hashset, but it does not need to be moved as nothing changed yet + file.Seek(nHashCount*CAICHHash::GetHashSize(), CFile::current); + posWritePos = file.GetPosition(); + CAICHRecoveryHashSet::AddStoredAICHHash(aichHash, nCurrentHashsetPos); + } + else{ + // used Hashset, move position in file + BYTE* buffer = new BYTE[nHashCount*CAICHHash::GetHashSize()]; + file.Read(buffer, nHashCount*CAICHHash::GetHashSize()); + posReadPos = file.GetPosition(); + file.Seek(posWritePos, CFile::begin); + file.Write(aichHash.GetRawHash(), CAICHHash::GetHashSize()); + file.WriteUInt32(nHashCount); + file.Write(buffer, nHashCount*CAICHHash::GetHashSize()); + delete[] buffer; + CAICHRecoveryHashSet::AddStoredAICHHash(aichHash, posWritePos); + + posWritePos = file.GetPosition(); + file.Seek(posReadPos, CFile::begin); + } + } + posReadPos = file.GetPosition(); + file.SetLength(posWritePos); + theApp.QueueDebugLogLine(false, _T("Cleaned up known2.met, removed %u hashsets and purged %u hashsets of old known files (%s)") + , nPurgeCount - nPurgeBecauseOld, nPurgeBecauseOld, CastItoXBytes(posReadPos-posWritePos)); + + file.Flush(); + file.Close(); + } + catch(CFileException* error){ + if (error->m_cause == CFileException::endOfFile){ + // we just parsed this files some ms ago, should never happen here + ASSERT( false ); + } + else{ + TCHAR buffer[MAX_CFEXP_ERRORMSG]; + error->GetErrorMessage(buffer, ARRSIZE(buffer)); + LogError(LOG_STATUSBAR,GetResString(IDS_ERR_SERVERMET_UNKNOWN),buffer); + } + error->Delete(); + return false; + } + } + else + { + // remember (/index) all hashs which are stored in the file for faster checking lateron + for (int i = 0; i < aKnown2Hashs.GetCount(); i++) + { + CAICHRecoveryHashSet::AddStoredAICHHash(aKnown2Hashs[i], aKnown2HashsFilePos[i]); + } + } + +#ifdef _DEBUG + for (POSITION pos = liUsedHashs.GetHeadPosition();pos != 0;) + { + CAICHHash current_hash = liUsedHashs.GetNext(pos); + CKnownFile* pCurFile = theApp.sharedfiles->GetFileByAICH(current_hash); + if (pCurFile == NULL) + { + ASSERT( false ); + continue; + } + CAICHRecoveryHashSet* pTempHashSet = new CAICHRecoveryHashSet(pCurFile); + pTempHashSet->SetFileSize(pCurFile->GetFileSize()); + pTempHashSet->SetMasterHash(pCurFile->GetFileIdentifier().GetAICHHash(), AICH_HASHSETCOMPLETE); + ASSERT( pTempHashSet->LoadHashSet() ); + delete pTempHashSet; + } +#endif + + lockKnown2Met.Unlock(); + // warn the user if he just upgraded + if (thePrefs.IsFirstStart() && !m_liToHash.IsEmpty() && !bJustCreated){ + LogWarning(GetResString(IDS_AICH_WARNUSER)); + } + if (!m_liToHash.IsEmpty()){ + theApp.QueueLogLine(true, GetResString(IDS_AICH_SYNCTOTAL), m_liToHash.GetCount() ); + theApp.emuledlg->sharedfileswnd->sharedfilesctrl.SetAICHHashing(m_liToHash.GetCount()); + // let first all normal hashing be done before starting out synchashing + CSingleLock sLock1(&theApp.hashing_mut); // only one filehash at a time + while (theApp.sharedfiles->GetHashingCount() != 0){ + Sleep(100); + if (!CemuleDlg::IsRunning()) + return 0; + } + sLock1.Lock(); + uint32 cDone = 0; + for (POSITION pos = m_liToHash.GetHeadPosition();pos != 0; cDone++) + { + if (!CemuleDlg::IsRunning()){ // in case of shutdown while still hashing + return 0; + } + theApp.emuledlg->sharedfileswnd->sharedfilesctrl.SetAICHHashing(m_liToHash.GetCount()-cDone); + if (theApp.emuledlg->sharedfileswnd->sharedfilesctrl.m_hWnd != NULL) + theApp.emuledlg->sharedfileswnd->sharedfilesctrl.ShowFilesCount(); + CKnownFile* pCurFile = m_liToHash.GetNext(pos); + // just to be sure that the file hasnt been deleted lately + if (!(theApp.knownfiles->IsKnownFile(pCurFile) && theApp.sharedfiles->GetFileByID(pCurFile->GetFileHash())) ) + continue; + theApp.QueueLogLine(false, GetResString(IDS_AICH_CALCFILE), pCurFile->GetFileName()); + if(!pCurFile->CreateAICHHashSetOnly()) + theApp.QueueDebugLogLine(false, _T("Failed to create AICH Hashset while sync. for file %s"), pCurFile->GetFileName()); + } + + theApp.emuledlg->sharedfileswnd->sharedfilesctrl.SetAICHHashing(0); + if (theApp.emuledlg->sharedfileswnd->sharedfilesctrl.m_hWnd != NULL) + theApp.emuledlg->sharedfileswnd->sharedfilesctrl.ShowFilesCount(); + sLock1.Unlock(); + } + + theApp.QueueDebugLogLine(false, _T("AICHSyncThread finished")); + return 0; +} + +bool CAICHSyncThread::ConvertToKnown2ToKnown264(CSafeFile* pTargetFile){ + // converting known2.met to known2_64.met to support large files + // changing hashcount from uint16 to uint32 + + // there still exists a lock on known2_64.met and it should be not opened at this point + CString oldfullpath = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR); + oldfullpath.Append(OLD_KNOWN2_MET_FILENAME); + CString newfullpath = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR); + newfullpath.Append(KNOWN2_MET_FILENAME); + + if (PathFileExists(newfullpath) || !PathFileExists(oldfullpath)){ + // only continue if the old file doe and the new file does not exists + return false; + } + + CSafeFile oldfile; + CFileException fexp; + + if (!oldfile.Open(oldfullpath,CFile::modeRead|CFile::osSequentialScan|CFile::typeBinary|CFile::shareDenyNone, &fexp)){ + if (fexp.m_cause != CFileException::fileNotFound){ + CString strError(_T("Failed to load ") OLD_KNOWN2_MET_FILENAME _T(" file")); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + } + // else -> known2.met also doesn't exists, so nothing to convert + return false; + } + + + if (!pTargetFile->Open(newfullpath,CFile::modeCreate|CFile::modeReadWrite|CFile::osSequentialScan|CFile::typeBinary|CFile::shareDenyNone, &fexp)){ + if (fexp.m_cause != CFileException::fileNotFound){ + CString strError(_T("Failed to load ") KNOWN2_MET_FILENAME _T(" file")); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + } + return false; + } + + theApp.QueueLogLine(false, GetResString(IDS_CONVERTINGKNOWN2MET), OLD_KNOWN2_MET_FILENAME, KNOWN2_MET_FILENAME); + + try { + pTargetFile->WriteUInt8(KNOWN2_MET_VERSION); + uint32 nHashCount; + while (oldfile.GetPosition() < oldfile.GetLength()){ + CAICHHash aichHash(&oldfile); + nHashCount = oldfile.ReadUInt16(); + if (oldfile.GetPosition() + nHashCount*CAICHHash::GetHashSize() > oldfile.GetLength()){ + AfxThrowFileException(CFileException::endOfFile, 0, oldfile.GetFileName()); + } + BYTE* buffer = new BYTE[nHashCount*CAICHHash::GetHashSize()]; + oldfile.Read(buffer, nHashCount*CAICHHash::GetHashSize()); + pTargetFile->Write(aichHash.GetRawHash(), CAICHHash::GetHashSize()); + pTargetFile->WriteUInt32(nHashCount); + pTargetFile->Write(buffer, nHashCount*CAICHHash::GetHashSize()); + delete[] buffer; + } + pTargetFile->Flush(); + oldfile.Close(); + } + catch(CFileException* error){ + if (error->m_cause == CFileException::endOfFile){ + LogError(LOG_STATUSBAR,GetResString(IDS_ERR_MET_BAD), OLD_KNOWN2_MET_FILENAME); + ASSERT( false ); + } + else{ + TCHAR buffer[MAX_CFEXP_ERRORMSG]; + error->GetErrorMessage(buffer, ARRSIZE(buffer)); + LogError(LOG_STATUSBAR,GetResString(IDS_ERR_SERVERMET_UNKNOWN),buffer); + } + error->Delete(); + theApp.QueueLogLine(false, GetResString(IDS_CONVERTINGKNOWN2FAILED)); + pTargetFile->Close(); + return false; + } + theApp.QueueLogLine(false, GetResString(IDS_CONVERTINGKNOWN2DONE)); + + // FIXME LARGE FILES (uncomment) + //DeleteFile(oldfullpath); + pTargetFile->SeekToBegin(); + return true; + + +} + diff --git a/AICHSyncThread.h b/AICHSyncThread.h new file mode 100644 index 00000000..f1873bdc --- /dev/null +++ b/AICHSyncThread.h @@ -0,0 +1,38 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once + +class CKnownFile; +class CSafeFile; +///////////////////////////////////////////////////////////////////////////////////////// +///CAICHSyncThread +class CAICHSyncThread : public CWinThread +{ + DECLARE_DYNCREATE(CAICHSyncThread) +protected: + CAICHSyncThread(); +public: + virtual BOOL InitInstance(); + virtual int Run(); + +protected: + bool ConvertToKnown2ToKnown264(CSafeFile* pTargetFile); + +private: + CTypedPtrList m_liToHash; +}; diff --git a/AbstractFile.cpp b/AbstractFile.cpp new file mode 100644 index 00000000..59722d33 --- /dev/null +++ b/AbstractFile.cpp @@ -0,0 +1,491 @@ +// parts of this file are based on work from pan One (http://home-3.tiscali.nl/~meost/pms/) +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "AbstractFile.h" +#include "OtherFunctions.h" +#include "Kademlia/Kademlia/Entry.h" +#include "ini2.h" +#include "Preferences.h" +#include "opcodes.h" +#include "Packets.h" +#include "StringConversion.h" +#ifdef _DEBUG +#include "DebugHelpers.h" +#endif + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +IMPLEMENT_DYNAMIC(CAbstractFile, CObject) + +CAbstractFile::CAbstractFile() + : m_FileIdentifier(m_nFileSize) +{ + m_nFileSize = (uint64)0; + m_uRating = 0; + m_bCommentLoaded = false; + m_uUserRating = 0; + m_bHasComment = false; + m_bKadCommentSearchRunning = false; +} + +CAbstractFile::CAbstractFile(const CAbstractFile* pAbstractFile) + : m_FileIdentifier(pAbstractFile->m_FileIdentifier, m_nFileSize) +{ + m_strFileName = pAbstractFile->m_strFileName; + m_nFileSize = pAbstractFile->m_nFileSize; + m_strComment = pAbstractFile->m_strComment; + m_uRating = pAbstractFile->m_uRating; + m_bCommentLoaded = pAbstractFile->m_bCommentLoaded; + m_uUserRating = pAbstractFile->m_uUserRating; + m_bHasComment = pAbstractFile->m_bHasComment; + m_strFileType = pAbstractFile->m_strFileType; + m_bKadCommentSearchRunning = pAbstractFile->m_bKadCommentSearchRunning; + + const CTypedPtrList& list = pAbstractFile->getNotes(); + for(POSITION pos = list.GetHeadPosition(); pos != NULL; ) + { + Kademlia::CEntry* entry = list.GetNext(pos); + m_kadNotes.AddTail(entry->Copy()); + } + + CopyTags(pAbstractFile->GetTags()); +} + +CAbstractFile::~CAbstractFile() +{ + ClearTags(); + for(POSITION pos = m_kadNotes.GetHeadPosition(); pos != NULL; ) + { + Kademlia::CEntry* entry = m_kadNotes.GetNext(pos); + delete entry; + } +} + +#ifdef _DEBUG +void CAbstractFile::AssertValid() const +{ + CObject::AssertValid(); + (void)m_strFileName; + (void)m_FileIdentifier; + (void)m_nFileSize; + (void)m_strComment; + (void)m_uRating; + (void)m_strFileType; + (void)m_uUserRating; + CHECK_BOOL(m_bHasComment); + CHECK_BOOL(m_bCommentLoaded); + taglist.AssertValid(); +} + +void CAbstractFile::Dump(CDumpContext& dc) const +{ + CObject::Dump(dc); +} +#endif + +bool CAbstractFile::AddNote(Kademlia::CEntry* pEntry) +{ + for(POSITION pos = m_kadNotes.GetHeadPosition(); pos != NULL; ) + { + Kademlia::CEntry* entry = m_kadNotes.GetNext(pos); + if(entry->m_uSourceID == pEntry->m_uSourceID) + { + ASSERT(entry != pEntry); + return false; + } + } + m_kadNotes.AddHead(pEntry); + UpdateFileRatingCommentAvail(); + return true; +} + +UINT CAbstractFile::GetFileRating() /*const*/ +{ + if (!m_bCommentLoaded) + LoadComment(); + return m_uRating; +} + +const CString& CAbstractFile::GetFileComment() /*const*/ +{ + if (!m_bCommentLoaded) + LoadComment(); + return m_strComment; +} + +void CAbstractFile::LoadComment() +{ + CIni ini(thePrefs.GetFileCommentsFilePath(), md4str(GetFileHash())); + m_strComment = ini.GetStringUTF8(_T("Comment")).Left(MAXFILECOMMENTLEN); + m_uRating = ini.GetInt(_T("Rate"), 0); + m_bCommentLoaded = true; +} + +void CAbstractFile::CopyTags(const CArray& tags) +{ + for (int i = 0; i < tags.GetSize(); i++) + taglist.Add(new CTag(*tags.GetAt(i))); +} + +void CAbstractFile::ClearTags() +{ + for (int i = 0; i < taglist.GetSize(); i++) + delete taglist[i]; + taglist.RemoveAll(); +} + +void CAbstractFile::AddTagUnique(CTag* pTag) +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pCurTag = taglist[i]; + if ( ( (pCurTag->GetNameID()!=0 && pCurTag->GetNameID()==pTag->GetNameID()) + || (pCurTag->GetName()!=NULL && pTag->GetName()!=NULL && CmpED2KTagName(pCurTag->GetName(), pTag->GetName())==0) + ) + && pCurTag->GetType() == pTag->GetType()){ + delete pCurTag; + taglist.SetAt(i, pTag); + return; + } + } + taglist.Add(pTag); +} + +void CAbstractFile::SetFileName(LPCTSTR pszFileName, bool bReplaceInvalidFileSystemChars, bool bAutoSetFileType, bool bRemoveControlChars) +{ + m_strFileName = pszFileName; + if (bReplaceInvalidFileSystemChars){ + m_strFileName.Replace(_T('/'), _T('-')); + m_strFileName.Replace(_T('>'), _T('-')); + m_strFileName.Replace(_T('<'), _T('-')); + m_strFileName.Replace(_T('*'), _T('-')); + m_strFileName.Replace(_T(':'), _T('-')); + m_strFileName.Replace(_T('?'), _T('-')); + m_strFileName.Replace(_T('\"'), _T('-')); + m_strFileName.Replace(_T('\\'), _T('-')); + m_strFileName.Replace(_T('|'), _T('-')); + } + if (bAutoSetFileType) + SetFileType(GetFileTypeByName(m_strFileName)); + + if (bRemoveControlChars){ + for (int i = 0; i < m_strFileName.GetLength(); ) + if (m_strFileName.GetAt(i) <= '\x1F') + m_strFileName.Delete(i); + else + i++; + } +} + +void CAbstractFile::SetFileType(LPCTSTR pszFileType) +{ + m_strFileType = pszFileType; +} + +CString CAbstractFile::GetFileTypeDisplayStr() const +{ + CString strFileTypeDisplayStr(GetFileTypeDisplayStrFromED2KFileType(GetFileType())); + if (strFileTypeDisplayStr.IsEmpty()) + strFileTypeDisplayStr = GetFileType(); + return strFileTypeDisplayStr; +} + +bool CAbstractFile::HasNullHash() const +{ + return isnulmd4(m_FileIdentifier.GetMD4Hash()); +} + +uint32 CAbstractFile::GetIntTagValue(uint8 tagname) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->IsInt()) + return pTag->GetInt(); + } + return 0; +} + +bool CAbstractFile::GetIntTagValue(uint8 tagname, uint32& ruValue) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->IsInt()){ + ruValue = pTag->GetInt(); + return true; + } + } + return false; +} + +uint64 CAbstractFile::GetInt64TagValue(LPCSTR tagname) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pTag = taglist[i]; + if (pTag->GetNameID()==0 && pTag->IsInt64(true) && CmpED2KTagName(pTag->GetName(), tagname)==0) + return pTag->GetInt64(); + } + return 0; +} + +uint64 CAbstractFile::GetInt64TagValue(uint8 tagname) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->IsInt64(true)) + return pTag->GetInt64(); + } + return 0; +} + +bool CAbstractFile::GetInt64TagValue(uint8 tagname, uint64& ruValue) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->IsInt64(true)){ + ruValue = pTag->GetInt64(); + return true; + } + } + return false; +} + +uint32 CAbstractFile::GetIntTagValue(LPCSTR tagname) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pTag = taglist[i]; + if (pTag->GetNameID()==0 && pTag->IsInt() && CmpED2KTagName(pTag->GetName(), tagname)==0) + return pTag->GetInt(); + } + return 0; +} + +void CAbstractFile::SetIntTagValue(uint8 tagname, uint32 uValue) +{ + for (int i = 0; i < taglist.GetSize(); i++){ + CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->IsInt()){ + pTag->SetInt(uValue); + return; + } + } + CTag* pTag = new CTag(tagname, uValue); + taglist.Add(pTag); +} + +void CAbstractFile::SetInt64TagValue(uint8 tagname, uint64 uValue) +{ + for (int i = 0; i < taglist.GetSize(); i++){ + CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->IsInt64(true)){ + pTag->SetInt64(uValue); + return; + } + } + CTag* pTag = new CTag(tagname, uValue); + taglist.Add(pTag); +} + +const CString& CAbstractFile::GetStrTagValue(uint8 tagname) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->IsStr()) + return pTag->GetStr(); + } + static const CString s_strEmpty; + return s_strEmpty; +} + +const CString& CAbstractFile::GetStrTagValue(LPCSTR tagname) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + const CTag* pTag = taglist[i]; + if (pTag->GetNameID()==0 && pTag->IsStr() && CmpED2KTagName(pTag->GetName(), tagname)==0) + return pTag->GetStr(); + } + static const CString s_strEmpty; + return s_strEmpty; +} + +void CAbstractFile::SetStrTagValue(uint8 tagname, LPCTSTR pszValue) +{ + for (int i = 0; i < taglist.GetSize(); i++){ + CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->IsStr()){ + pTag->SetStr(pszValue); + return; + } + } + CTag* pTag = new CTag(tagname, pszValue); + taglist.Add(pTag); +} + +CTag* CAbstractFile::GetTag(uint8 tagname, uint8 tagtype) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname && pTag->GetType()==tagtype) + return pTag; + } + return NULL; +} + +CTag* CAbstractFile::GetTag(LPCSTR tagname, uint8 tagtype) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + CTag* pTag = taglist[i]; + if (pTag->GetNameID()==0 && pTag->GetType()==tagtype && CmpED2KTagName(pTag->GetName(), tagname)==0) + return pTag; + } + return NULL; +} + +CTag* CAbstractFile::GetTag(uint8 tagname) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname) + return pTag; + } + return NULL; +} + +CTag* CAbstractFile::GetTag(LPCSTR tagname) const +{ + for (int i = 0; i < taglist.GetSize(); i++){ + CTag* pTag = taglist[i]; + if (pTag->GetNameID()==0 && CmpED2KTagName(pTag->GetName(), tagname)==0) + return pTag; + } + return NULL; +} + +void CAbstractFile::DeleteTag(CTag* pTag) +{ + for (int i = 0; i < taglist.GetSize(); i++){ + if (taglist[i] == pTag){ + taglist.RemoveAt(i); + delete pTag; + return; + } + } +} + +void CAbstractFile::DeleteTag(uint8 tagname) +{ + for (int i = 0; i < taglist.GetSize(); i++){ + CTag* pTag = taglist[i]; + if (pTag->GetNameID()==tagname){ + taglist.RemoveAt(i); + delete pTag; + return; + } + } +} + +void CAbstractFile::SetKadCommentSearchRunning(bool bVal){ + if (bVal != m_bKadCommentSearchRunning){ + m_bKadCommentSearchRunning = bVal; + UpdateFileRatingCommentAvail(true); + } +} + +void CAbstractFile::RefilterKadNotes(bool bUpdate){ + // check all availabe comments against our filter again + if (thePrefs.GetCommentFilter().IsEmpty()) + return; + POSITION pos1, pos2; + for (pos1 = m_kadNotes.GetHeadPosition();( pos2 = pos1 ) != NULL;) + { + m_kadNotes.GetNext(pos1); + Kademlia::CEntry* entry = m_kadNotes.GetAt(pos2); + if (!entry->GetStrTagValue(TAG_DESCRIPTION).IsEmpty()){ + CString strCommentLower(entry->GetStrTagValue(TAG_DESCRIPTION)); + // Verified Locale Dependency: Locale dependent string conversion (OK) + strCommentLower.MakeLower(); + + int iPos = 0; + CString strFilter(thePrefs.GetCommentFilter().Tokenize(_T("|"), iPos)); + while (!strFilter.IsEmpty()) + { + // comment filters are already in lowercase, compare with temp. lowercased received comment + if (strCommentLower.Find(strFilter) >= 0) + { + m_kadNotes.RemoveAt(pos2); + delete entry; + break; + } + strFilter = thePrefs.GetCommentFilter().Tokenize(_T("|"), iPos); + } + } + } + if (bUpdate) // untill updated rating and m_bHasComment might be wrong + UpdateFileRatingCommentAvail(); +} + +CString CAbstractFile::GetED2kLink(bool bHashset, bool bHTML, bool bHostname, bool bSource, uint32 dwSourceIP) const +{ + if (this == NULL) + { + ASSERT( false ); + return _T(""); + } + CString strLink, strBuffer; + strLink.Format(_T("ed2k://|file|%s|%I64u|%s|"), + EncodeUrlUtf8(StripInvalidFilenameChars(GetFileName())), + GetFileSize(), + EncodeBase16(GetFileHash(),16)); + + if (bHTML) + strLink = _T(" 0 && GetFileIdentifierC().HasExpectedMD4HashCount()){ + strLink += _T("p="); + for (UINT j = 0; j < GetFileIdentifierC().GetAvailableMD4PartHashCount(); j++) + { + if (j > 0) + strLink += _T(':'); + strLink += EncodeBase16(GetFileIdentifierC().GetMD4PartHash(j), 16); + } + strLink += _T('|'); + } + + if (GetFileIdentifierC().HasAICHHash()) + { + strBuffer.Format(_T("h=%s|"), GetFileIdentifierC().GetAICHHash().GetString() ); + strLink += strBuffer; + } + + strLink += _T('/'); + if (bHostname && !thePrefs.GetYourHostname().IsEmpty() && thePrefs.GetYourHostname().Find(_T('.')) != -1) + { + strBuffer.Format(_T("|sources,%s:%i|/"), thePrefs.GetYourHostname(), thePrefs.GetPort() ); + strLink += strBuffer; + } + else if(bSource && dwSourceIP != 0) + { + strBuffer.Format(_T("|sources,%i.%i.%i.%i:%i|/"),(uint8)dwSourceIP,(uint8)(dwSourceIP>>8),(uint8)(dwSourceIP>>16),(uint8)(dwSourceIP>>24), thePrefs.GetPort() ); + strLink += strBuffer; + } + if (bHTML) + strLink += _T("\">") + StripInvalidFilenameChars(GetFileName()) + _T(""); + + return strLink; +} \ No newline at end of file diff --git a/AbstractFile.h b/AbstractFile.h new file mode 100644 index 00000000..bb85991e --- /dev/null +++ b/AbstractFile.h @@ -0,0 +1,137 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include +#include "opcodes.h" +#include "FileIdentifier.h" + +/* + CPartFile + / + CKnownFile + / + CShareableFile + / +CAbstractFile - CCollectionFile + \ + CSearchFile +*/ + +namespace Kademlia +{ + class CUInt128; + class CEntry; + class CKadTagValueString; + typedef std::list WordList; +}; + +class CTag; + +typedef CTypedPtrList CKadEntryPtrList; + +class CAbstractFile: public CObject +{ + DECLARE_DYNAMIC(CAbstractFile) + +public: + CAbstractFile(); + CAbstractFile(const CAbstractFile* pAbstractFile); + virtual ~CAbstractFile(); + + const CString& GetFileName() const { return m_strFileName; } + virtual void SetFileName(LPCTSTR pszFileName, bool bReplaceInvalidFileSystemChars = false, bool bAutoSetFileType = true, bool bRemoveControlChars = false); // 'bReplaceInvalidFileSystemChars' is set to 'false' for backward compatibility! + + // returns the ED2K file type (an ASCII string) + const CString& GetFileType() const { return m_strFileType; } + virtual void SetFileType(LPCTSTR pszFileType); + + // returns the file type which is used to be shown in the GUI + CString GetFileTypeDisplayStr() const; + + CFileIdentifier& GetFileIdentifier() { return m_FileIdentifier; } + const CFileIdentifier& GetFileIdentifierC() const { return m_FileIdentifier; } + const uchar* GetFileHash() const { return m_FileIdentifier.GetMD4Hash(); } + void SetFileHash(const uchar* pucFileHash) { m_FileIdentifier.SetMD4Hash(pucFileHash); } + bool HasNullHash() const; + CString GetED2kLink(bool bHashset = false, bool bHTML = false, bool bHostname = false, bool bSource = false, uint32 dwSourceIP = 0) const; + + + EMFileSize GetFileSize() const { return m_nFileSize; } + virtual void SetFileSize(EMFileSize nFileSize) { m_nFileSize = nFileSize; } + bool IsLargeFile() const { return m_nFileSize > (uint64)OLD_MAX_EMULE_FILE_SIZE; } + + uint32 GetIntTagValue(uint8 tagname) const; + uint32 GetIntTagValue(LPCSTR tagname) const; + bool GetIntTagValue(uint8 tagname, uint32& ruValue) const; + uint64 GetInt64TagValue(uint8 tagname) const; + uint64 GetInt64TagValue(LPCSTR tagname) const; + bool GetInt64TagValue(uint8 tagname, uint64& ruValue) const; + void SetIntTagValue(uint8 tagname, uint32 uValue); + void SetInt64TagValue(uint8 tagname, uint64 uValue); + const CString& GetStrTagValue(uint8 tagname) const; + const CString& GetStrTagValue(LPCSTR tagname) const; + void SetStrTagValue(uint8 tagname, LPCTSTR); + CTag* GetTag(uint8 tagname, uint8 tagtype) const; + CTag* GetTag(LPCSTR tagname, uint8 tagtype) const; + CTag* GetTag(uint8 tagname) const; + CTag* GetTag(LPCSTR tagname) const; + const CArray& GetTags() const { return taglist; } + void AddTagUnique(CTag* pTag); + void DeleteTag(uint8 tagname); + void DeleteTag(CTag* pTag); + void ClearTags(); + void CopyTags(const CArray& tags); + virtual bool IsPartFile() const { return false; } + + bool HasComment() const { return m_bHasComment; } + void SetHasComment(bool in) { m_bHasComment = in; } + UINT UserRating(bool bKadSearchIndicator = false) const { return (bKadSearchIndicator && m_bKadCommentSearchRunning) ? 6 : m_uUserRating; } + bool HasRating() const { return m_uUserRating > 0; } + bool HasBadRating() const { return ( HasRating() && (m_uUserRating < 2)); } + void SetUserRating(UINT in) { m_uUserRating = in; } + const CString& GetFileComment() /*const*/; + UINT GetFileRating() /*const*/; + void LoadComment(); + virtual void UpdateFileRatingCommentAvail(bool bForceUpdate = true) = 0; + + bool AddNote(Kademlia::CEntry* pEntry); + void RefilterKadNotes(bool bUpdate = true); + const CKadEntryPtrList& getNotes() const { return m_kadNotes; } + + bool IsKadCommentSearchRunning() const { return m_bKadCommentSearchRunning; } + void SetKadCommentSearchRunning(bool bVal); + +#ifdef _DEBUG + // Diagnostic Support + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +protected: + CFileIdentifier m_FileIdentifier; + CString m_strFileName; + EMFileSize m_nFileSize; + CString m_strComment; + UINT m_uRating; + bool m_bCommentLoaded; + UINT m_uUserRating; + bool m_bHasComment; + bool m_bKadCommentSearchRunning; + CString m_strFileType; + CArray taglist; + CKadEntryPtrList m_kadNotes; +}; diff --git a/AddFriend.cpp b/AddFriend.cpp new file mode 100644 index 00000000..c9c550df --- /dev/null +++ b/AddFriend.cpp @@ -0,0 +1,170 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "AddFriend.h" +#include "Friend.h" +#include "otherfunctions.h" +#include "FriendList.h" +#include "Preferences.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// CAddFriend dialog + +IMPLEMENT_DYNAMIC(CAddFriend, CDialog) + +BEGIN_MESSAGE_MAP(CAddFriend, CDialog) + ON_BN_CLICKED(IDC_ADD, OnAddBtn) +END_MESSAGE_MAP() + +CAddFriend::CAddFriend() + : CDialog(CAddFriend::IDD) +{ + m_pShowFriend = NULL; + m_icnWnd = NULL; +} + +CAddFriend::~CAddFriend() +{ + if (m_icnWnd) + VERIFY( DestroyIcon(m_icnWnd) ); +} + +void CAddFriend::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); +} + +BOOL CAddFriend::OnInitDialog() +{ + CDialog::OnInitDialog(); + InitWindowStyles(this); + Localize(); + if (m_pShowFriend) + { + SetIcon(m_icnWnd = theApp.LoadIcon(_T("ClientDetails")), FALSE); + SendDlgItemMessage(IDC_IP, EM_SETREADONLY, TRUE); + SendDlgItemMessage(IDC_PORT, EM_SETREADONLY, TRUE); + SendDlgItemMessage(IDC_USERNAME, EM_SETREADONLY, TRUE); + + SetDlgItemInt(IDC_IP, m_pShowFriend->m_dwLastUsedIP, FALSE); + SetDlgItemInt(IDC_PORT, m_pShowFriend->m_nLastUsedPort, FALSE); + SetDlgItemText(IDC_USERNAME, m_pShowFriend->m_strName); + if (m_pShowFriend->HasUserhash()) + SetDlgItemText(IDC_USERHASH, md4str(m_pShowFriend->m_abyUserhash)); + else + SetDlgItemText(IDC_USERHASH, _T("")); + + if (m_pShowFriend->m_dwLastSeen){ + CTime t((time_t)m_pShowFriend->m_dwLastSeen); + SetDlgItemText(IDC_EDIT2, t.Format(thePrefs.GetDateTimeFormat())); + } + SetDlgItemText(IDC_AFKADID, m_pShowFriend->HasKadID() ? GetResString(IDS_KNOWN) : GetResString(IDS_UNKNOWN)); + /*if (m_pShowFriend->m_dwLastChatted){ + CTime t((time_t)m_pShowFriend->m_dwLastChatted); + SetDlgItemText(IDC_LAST_CHATTED, t.Format(thePrefs.GetDateTimeFormat())); + }*/ + + GetDlgItem(IDC_ADD)->ShowWindow(SW_HIDE); + } + else + { + SetIcon(m_icnWnd = theApp.LoadIcon(_T("AddFriend")), FALSE); + ((CEdit*)GetDlgItem(IDC_USERNAME))->SetLimitText(thePrefs.GetMaxUserNickLength()); + SetDlgItemText(IDC_USERHASH, _T("")); + } + return TRUE; +} + +void CAddFriend::Localize() +{ + SetWindowText(m_pShowFriend ? GetResString(IDS_DETAILS) : GetResString(IDS_ADDAFRIEND)); + GetDlgItem(IDC_INFO1)->SetWindowText(GetResString(IDS_PAF_REQINFO)); + GetDlgItem(IDC_INFO2)->SetWindowText(GetResString(IDS_PAF_MOREINFO)); + + GetDlgItem(IDC_ADD)->SetWindowText(GetResString(IDS_ADD)); + GetDlgItem(IDCANCEL)->SetWindowText(m_pShowFriend ? GetResString(IDS_FD_CLOSE) : GetResString(IDS_CANCEL)); + + GetDlgItem(IDC_STATIC31)->SetWindowText(GetResString(IDS_CD_UNAME)); + GetDlgItem(IDC_STATIC32)->SetWindowText(GetResString(IDS_CD_UHASH)); + GetDlgItem(IDC_STATIC34)->SetWindowText(m_pShowFriend ? GetResString(IDS_USERID)+_T(":") : GetResString(IDS_CD_UIP)); + GetDlgItem(IDC_STATIC35)->SetWindowText(GetResString(IDS_PORT)+_T(":")); + SetDlgItemText(IDC_LAST_SEEN_LABEL, GetResString(IDS_LASTSEEN)+_T(":")); + SetDlgItemText(IDC_AFKADIDLABEL, GetResString(IDS_KADID)+_T(":")); + //SetDlgItemText(IDC_LAST_CHATTED_LABEL, GetResString(IDS_LASTCHATTED)+_T(":")); +} + +void CAddFriend::OnAddBtn() +{ + if (!m_pShowFriend) + { + CString strBuff; + uint32 ip; + GetDlgItemText(IDC_IP, strBuff); + UINT u1, u2, u3, u4, uPort = 0; + if (_stscanf(strBuff, _T("%u.%u.%u.%u:%u"), &u1, &u2, &u3, &u4, &uPort) != 5 || u1 > 255 || u2 > 255 || u3 > 255 || u4 > 255 || uPort > 65535){ + uPort = 0; + if (_stscanf(strBuff, _T("%u.%u.%u.%u"), &u1, &u2, &u3, &u4) != 4 || u1 > 255 || u2 > 255 || u3 > 255 || u4 > 255){ + AfxMessageBox(GetResString(IDS_ERR_NOVALIDFRIENDINFO)); + GetDlgItem(IDC_IP)->SetFocus(); + return; + } + } + in_addr iaFriend; + iaFriend.S_un.S_un_b.s_b1 = (BYTE)u1; + iaFriend.S_un.S_un_b.s_b2 = (BYTE)u2; + iaFriend.S_un.S_un_b.s_b3 = (BYTE)u3; + iaFriend.S_un.S_un_b.s_b4 = (BYTE)u4; + ip = iaFriend.S_un.S_addr; + + if (uPort == 0) + { + GetDlgItemText(IDC_PORT, strBuff); + if (_stscanf(strBuff, _T("%u"), &uPort) != 1){ + AfxMessageBox(GetResString(IDS_ERR_NOVALIDFRIENDINFO)); + GetDlgItem(IDC_PORT)->SetFocus(); + return; + } + } + + CString strUserName; + GetDlgItemText(IDC_USERNAME, strUserName); + strUserName.Trim(); + strUserName = strUserName.Left(thePrefs.GetMaxUserNickLength()); + + // why did we offer an edit control for entering the userhash but did not store it? + ; + + if (!theApp.friendlist->AddFriend(NULL, 0, ip, (uint16)uPort, 0, strUserName, 0)){ + AfxMessageBox(GetResString(IDS_WRN_FRIENDDUPLIPPORT)); + GetDlgItem(IDC_IP)->SetFocus(); + return; + } + } + else{ + // No "update" friend's data for now -- too much work to synchronize/update all + // possible available related data in the client list... + } + + OnCancel(); +} diff --git a/AddFriend.h b/AddFriend.h new file mode 100644 index 00000000..4bd478c6 --- /dev/null +++ b/AddFriend.h @@ -0,0 +1,43 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "Friend.h" + +// CAddFriend dialog + +class CAddFriend : public CDialog +{ + DECLARE_DYNAMIC(CAddFriend) + +public: + CAddFriend(); // standard constructor + virtual ~CAddFriend(); + + enum { IDD = IDD_ADDFRIEND }; + CFriend* m_pShowFriend; + + void Localize(); + +protected: + HICON m_icnWnd; + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + + DECLARE_MESSAGE_MAP() + afx_msg void OnAddBtn(); +}; diff --git a/AddSourceDlg.cpp b/AddSourceDlg.cpp new file mode 100644 index 00000000..37f42a63 --- /dev/null +++ b/AddSourceDlg.cpp @@ -0,0 +1,198 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "AddSourceDlg.h" +#include "PartFile.h" +#include "OtherFunctions.h" +#include "UpDownClient.h" +#include "DownloadQueue.h" +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// CAddSourceDlg dialog + +IMPLEMENT_DYNAMIC(CAddSourceDlg, CDialog) + +BEGIN_MESSAGE_MAP(CAddSourceDlg, CResizableDialog) + ON_BN_CLICKED(IDC_RSRC, OnBnClickedRadio1) + ON_BN_CLICKED(IDC_RURL, OnBnClickedRadio4) + ON_BN_CLICKED(IDC_BUTTON1, OnBnClickedButton1) + ON_BN_CLICKED(IDOK, OnBnClickedOk) +END_MESSAGE_MAP() + +CAddSourceDlg::CAddSourceDlg(CWnd* pParent /*=NULL*/) + : CResizableDialog(CAddSourceDlg::IDD, pParent) + , m_nSourceType(0) +{ + m_pFile = NULL; +} + +CAddSourceDlg::~CAddSourceDlg() +{ +} + +void CAddSourceDlg::DoDataExchange(CDataExchange* pDX) +{ + CResizableDialog::DoDataExchange(pDX); + DDX_Radio(pDX, IDC_RSRC, m_nSourceType); +} + +void CAddSourceDlg::SetFile(CPartFile *pFile) +{ + m_pFile = pFile; +} + +BOOL CAddSourceDlg::OnInitDialog() +{ + CResizableDialog::OnInitDialog(); + InitWindowStyles(this); + + AddAnchor(IDC_SOURCE_TYPE, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_EDIT10, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDOK, BOTTOM_RIGHT); + AddAnchor(IDC_BUTTON1, BOTTOM_RIGHT); + AddAnchor(IDCANCEL, BOTTOM_RIGHT); + + if (m_pFile) + SetWindowText(m_pFile->GetFileName()); + + // localize + SetDlgItemText(IDC_BUTTON1,GetResString(IDS_ADD)); + SetDlgItemText(IDCANCEL,GetResString(IDS_CANCEL)); + SetDlgItemText(IDC_RSRC,GetResString(IDS_SOURCECLIENT)); + SetDlgItemText(IDC_SOURCE_TYPE,GetResString(IDS_META_SRCTYPE)); + SetDlgItemText(IDC_RURL,GetResString(IDS_SV_URL)); + SetDlgItemText(IDC_UIP,GetResString(IDS_USERSIP)); + SetDlgItemText(IDC_PORT,GetResString(IDS_PORT)); + GetDlgItem(IDOK)->SetWindowText(GetResString(IDS_TREEOPTIONS_OK)); + + + EnableSaveRestore(_T("AddSourceDlg")); + + OnBnClickedRadio1(); + return FALSE; // return FALSE, we changed the focus! +} + +void CAddSourceDlg::OnBnClickedRadio1() +{ + m_nSourceType = 0; + GetDlgItem(IDC_EDIT2)->EnableWindow(true); + GetDlgItem(IDC_EDIT3)->EnableWindow(true); + GetDlgItem(IDC_EDIT10)->EnableWindow(false); + GetDlgItem(IDC_EDIT2)->SetFocus(); +} + +void CAddSourceDlg::OnBnClickedRadio4() +{ + m_nSourceType = 1; + GetDlgItem(IDC_EDIT2)->EnableWindow(false); + GetDlgItem(IDC_EDIT3)->EnableWindow(false); + GetDlgItem(IDC_EDIT10)->EnableWindow(true); + GetDlgItem(IDC_EDIT10)->SetFocus(); +} + +void CAddSourceDlg::OnBnClickedButton1() +{ + if (!m_pFile) + return; + + switch (m_nSourceType) + { + case 0: + { + CString sip; + GetDlgItem(IDC_EDIT2)->GetWindowText(sip); + if (sip.IsEmpty()) + return; + + // if the port is specified with the IP, ignore any possible specified port in the port control + uint16 port; + int iColon = sip.Find(_T(':')); + if (iColon != -1) { + port = (uint16)_tstoi(sip.Mid(iColon + 1)); + sip = sip.Left(iColon); + } + else { + BOOL bTranslated = FALSE; + port = (uint16)GetDlgItemInt(IDC_EDIT3, &bTranslated, FALSE); + if (!bTranslated) + return; + } + + uint32 ip; + if ((ip = inet_addr(CT2CA(sip))) == INADDR_NONE && _tcscmp(sip, _T("255.255.255.255")) != 0) + ip = 0; + if (IsGoodIPPort(ip, port)) + { + CUpDownClient* toadd = new CUpDownClient(m_pFile, port, ntohl(ip), 0, 0); + toadd->SetSourceFrom(SF_PASSIVE); + theApp.downloadqueue->CheckAndAddSource(m_pFile, toadd); + } + break; + } + case 1: + { + CString strURL; + GetDlgItem(IDC_EDIT10)->GetWindowText(strURL); + if (!strURL.IsEmpty()) + { + TCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH]; + TCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH]; + TCHAR szUrlPath[INTERNET_MAX_PATH_LENGTH]; + TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; + TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH]; + TCHAR szExtraInfo[INTERNET_MAX_URL_LENGTH]; + URL_COMPONENTS Url = {0}; + Url.dwStructSize = sizeof(Url); + Url.lpszScheme = szScheme; + Url.dwSchemeLength = ARRSIZE(szScheme); + Url.lpszHostName = szHostName; + Url.dwHostNameLength = ARRSIZE(szHostName); + Url.lpszUserName = szUserName; + Url.dwUserNameLength = ARRSIZE(szUserName); + Url.lpszPassword = szPassword; + Url.dwPasswordLength = ARRSIZE(szPassword); + Url.lpszUrlPath = szUrlPath; + Url.dwUrlPathLength = ARRSIZE(szUrlPath); + Url.lpszExtraInfo = szExtraInfo; + Url.dwExtraInfoLength = ARRSIZE(szExtraInfo); + if (InternetCrackUrl(strURL, 0, 0, &Url) && Url.dwHostNameLength > 0 && Url.dwHostNameLength < INTERNET_MAX_HOST_NAME_LENGTH) + { + SUnresolvedHostname* hostname = new SUnresolvedHostname; + hostname->strURL = strURL; + hostname->strHostname = szHostName; + theApp.downloadqueue->AddToResolved(m_pFile, hostname); + delete hostname; + } + } + break; + } + } +} + +void CAddSourceDlg::OnBnClickedOk() +{ + OnBnClickedButton1(); + OnOK(); +} diff --git a/AddSourceDlg.h b/AddSourceDlg.h new file mode 100644 index 00000000..6ab2d7c3 --- /dev/null +++ b/AddSourceDlg.h @@ -0,0 +1,66 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "ResizableLib/ResizableDialog.h" + +class CPartFile; + +struct SUnresolvedHostname +{ + SUnresolvedHostname() + { + nPort = 0; + } + CStringA strHostname; + uint16 nPort; + CString strURL; +}; + +// CAddSourceDlg dialog + +class CAddSourceDlg : public CResizableDialog +{ + DECLARE_DYNAMIC(CAddSourceDlg) + +public: + CAddSourceDlg(CWnd* pParent = NULL); // standard constructor + virtual ~CAddSourceDlg(); + + void SetFile( CPartFile* pFile ); + +// Dialog Data + enum { IDD = IDD_ADDSOURCE }; + +protected: + CPartFile* m_pFile; + int m_nSourceType; + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnBnClickedRadio1(); + afx_msg void OnBnClickedRadio2(); + afx_msg void OnBnClickedRadio3(); + afx_msg void OnBnClickedRadio4(); + afx_msg void OnBnClickedRadio5(); + afx_msg void OnBnClickedRadio6(); + afx_msg void OnBnClickedRadio7(); + afx_msg void OnBnClickedButton1(); + afx_msg void OnBnClickedOk(); +}; diff --git a/AfxBeginMsgMapTemplate.h b/AfxBeginMsgMapTemplate.h new file mode 100644 index 00000000..f4a88c48 --- /dev/null +++ b/AfxBeginMsgMapTemplate.h @@ -0,0 +1,89 @@ +// ------------------------------------------------------------ +// BEGIN_MESSAGE_MAP_TEMPLATE +// MFC (VisualC++ 6/7) BEGIN_MESSAGE_MAP template compatibility +// ------------------------------------------------------------ +// AfxBeginMsgMapTemplate.hpp +// zegzav - 1/10/2002 - eMule project (http://www.emule-project.net) +// ------------------------------------------------------------ +#pragma once + +#if _MFC_VER >= 0x0600 && _MFC_VER < 0x0700 + +// MFC for Visual C++ 6.0 (MFC 4.x) + +#ifdef _AFXDLL +#define BEGIN_MESSAGE_MAP_TEMPLATE(templateList, templateClass, theClass, baseClass) \ + templateList const AFX_MSGMAP* PASCAL templateClass::_GetBaseMessageMap() \ + { return &baseClass::messageMap; } \ + templateList const AFX_MSGMAP* templateClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + templateList AFX_COMDAT AFX_DATADEF const AFX_MSGMAP templateClass::messageMap = \ + { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \ + templateList AFX_COMDAT const AFX_MSGMAP_ENTRY templateClass::_messageEntries[] = \ + { +#else +#define BEGIN_MESSAGE_MAP_TEMPLATE(templateList, templateClass, theClass, baseClass) \ + templateList const AFX_MSGMAP* templateClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + templateList AFX_COMDAT AFX_DATADEF const AFX_MSGMAP templateClass::messageMap = \ + { &baseClass::messageMap, &theClass::_messageEntries[0] }; \ + templateList AFX_COMDAT const AFX_MSGMAP_ENTRY templateClass::_messageEntries[] = \ + { +#endif + +#elif _MFC_VER >= 0x0700 && _MFC_VER < 0x0800 + +// MFC for Visual C++ 7.0 (MFC 7.x) + +#ifdef _AFXDLL +#define BEGIN_MESSAGE_MAP_TEMPLATE(templateList, templateClass, theClass, baseClass) \ + templateList const AFX_MSGMAP* PASCAL templateClass::GetThisMessageMap() \ + { return &theClass::messageMap; } \ + templateList const AFX_MSGMAP* templateClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + templateList /*AFX_COMDAT*/ const AFX_MSGMAP templateClass::messageMap = \ + { &baseClass::GetThisMessageMap, &theClass::_messageEntries[0] }; \ + templateList /*AFX_COMDAT*/ const AFX_MSGMAP_ENTRY templateClass::_messageEntries[] = \ + { +#else +#define BEGIN_MESSAGE_MAP_TEMPLATE(templateList, templateClass, theClass, baseClass) \ + templateList const AFX_MSGMAP* templateClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + templateList /*AFX_COMDAT*/ const AFX_MSGMAP templateClass::messageMap = \ + { &baseClass::messageMap, &theClass::_messageEntries[0] }; \ + templateList /*AFX_COMDAT*/ const AFX_MSGMAP_ENTRY templateClass::_messageEntries[] = \ + { +#endif + +#elif _MFC_VER>=0x0800 + +// MFC for Visual C++ 7.0 (MFC 7.x) + +#ifdef _AFXDLL +#define BEGIN_MESSAGE_MAP_TEMPLATE(templateList, templateClass, theClass, baseClass) \ + templateList const AFX_MSGMAP* PASCAL templateClass::GetThisMessageMap() \ + { return &theClass::messageMap; } \ + templateList const AFX_MSGMAP* templateClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + templateList /*AFX_COMDAT*/ const AFX_MSGMAP templateClass::messageMap = \ + { &baseClass::GetThisMessageMap, &theClass::_messageEntries[0] }; \ + templateList /*AFX_COMDAT*/ const AFX_MSGMAP_ENTRY templateClass::_messageEntries[] = \ + { +#else +#define BEGIN_MESSAGE_MAP_TEMPLATE(templateList, templateClass, theClass, baseClass) \ + PTM_WARNING_DISABLE \ + templateList const AFX_MSGMAP* templateClass::GetMessageMap() const \ + { return GetThisMessageMap(); } \ + templateList const AFX_MSGMAP* PASCAL templateClass::GetThisMessageMap() \ + { \ + typedef theClass ThisClass; \ + typedef baseClass TheBaseClass; \ + static const AFX_MSGMAP_ENTRY _messageEntries[] = \ + { +#endif + +#else + +#error "MFC version not supported" + +#endif diff --git a/ArchivePreviewDlg.cpp b/ArchivePreviewDlg.cpp new file mode 100644 index 00000000..ca46241d --- /dev/null +++ b/ArchivePreviewDlg.cpp @@ -0,0 +1,1236 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ArchivePreviewDlg.h" +#include "KnownFile.h" +#include "partfile.h" +#include "preferences.h" +#include "UserMsgs.h" +#include "SplitterControl.h" +#include "MenuCmds.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// from free unRAR, Alexander L. Roshal +class EncodeFileName +{ + private: + byte *EncName; + byte Flags; + int FlagBits; + int FlagsPos; + int DestSize; + public: + EncodeFileName(); + int Encode(char *Name,wchar_t *NameW,byte *EncName); + void Decode(char *Name,byte *EncName,int EncSize,wchar_t *NameW,int MaxDecSize); +}; + +EncodeFileName::EncodeFileName() +{ + Flags=0; + FlagBits=0; + FlagsPos=0; + DestSize=0; +} + +void EncodeFileName::Decode(char *Name,byte *EncName,int EncSize,wchar_t *NameW, + int MaxDecSize) +{ + int EncPos=0,DecPos=0; + byte HighByte=EncName[EncPos++]; + while (EncPos>6) + { + case 0: + NameW[DecPos++]=EncName[EncPos++]; + break; + case 1: + NameW[DecPos++]=EncName[EncPos++]+(HighByte<<8); + break; + case 2: + NameW[DecPos++]=EncName[EncPos]+(EncName[EncPos+1]<<8); + EncPos+=2; + break; + case 3: + { + int Length=EncName[EncPos++]; + if (Length & 0x80) + { + byte Correction=EncName[EncPos++]; + for (Length=(Length&0x7f)+2;Length>0 && DecPos0 && DecPosGetWindowRect(rc); + nDelta1 += rc.Height(); + GetDlgItem(IDC_RESTOREARCH)->GetWindowRect(rc); + nDelta2 += rc.Height(); + CSplitterControl::ChangePos(GetDlgItem(IDC_FILELIST), 0, -nDelta1); + CSplitterControl::ChangeHeight(GetDlgItem(IDC_FILELIST), nDelta1 + nDelta2); + CSplitterControl::ChangePos(GetDlgItem(IDC_ARCHPROGRESS), 0, nDelta2); + CSplitterControl::ChangePos(GetDlgItem(IDC_INFO_FILECOUNT), 0, nDelta2); + GetDlgItem(IDC_READARCH)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_RESTOREARCH)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_APV_FILEINFO)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_ARCP_ATTRIBS)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_INFO_ATTR)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_AP_EXPLAIN)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_INFO_STATUS)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_INFO_TYPE)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_ARCP_TYPE)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_ARCP_STATUS)->ShowWindow(SW_HIDE); + } + AddAnchor(IDC_INFO_FILECOUNT, BOTTOM_RIGHT); + AddAnchor(IDC_ARCHPROGRESS, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT); + + // Win98: Explicitly set to Unicode to receive Unicode notifications. + m_ContentList.SendMessage(CCM_SETUNICODEFORMAT, TRUE); + // To support full sorting of the archive entries list we'd need a seperate list which + // is holding the unified archive entries for all different supported archive formats so + // that the ListView's sortproc can get valid 'lParam' values which are pointing to those + // entries. This could be done, but for now we just let the default ListView sort + // functionality sort the archive entries by filename (the content of the first column). + ASSERT( m_ContentList.GetStyle() & LVS_SORTASCENDING ); + ASSERT( m_ContentList.GetStyle() & LVS_SHAREIMAGELISTS ); + m_ContentList.SendMessage(LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)theApp.GetSystemImageList()); + m_ContentList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_GRIDLINES); + m_ContentList.EnableHdrCtrlSortBitmaps(); + + m_ContentList.ReadColumnStats(_countof(s_aColumns), s_aColumns); + m_ContentList.CreateColumns(_countof(s_aColumns), s_aColumns); + m_ContentList.InitColumnOrders(_countof(s_aColumns), s_aColumns); + m_ContentList.UpdateSortColumn(_countof(s_aColumns), s_aColumns); + + CResizablePage::UpdateData(FALSE); + Localize(); + + m_progressbar.SetRange(0,1000); + m_progressbar.SetPos(0); + + return TRUE; +} + +void CArchivePreviewDlg::OnDestroy() +{ + if (m_ContentList.GetColumnWidth(2)==0) + m_ContentList.SetColumnWidth(2, m_StoredColWidth2); + if (m_ContentList.GetColumnWidth(5)==0) + m_ContentList.SetColumnWidth(5, m_StoredColWidth5); + + m_ContentList.WriteColumnStats(_countof(s_aColumns), s_aColumns); + + // stop running scanner-thread if present + if (m_activeTParams != NULL) { + m_activeTParams->m_bIsValid = false; + m_activeTParams = NULL; + } + + // This property sheet's window may get destroyed and re-created several times although + // the corresponding C++ class is kept -> explicitly reset ResizeableLib state + RemoveAllAnchors(); + + CResizablePage::OnDestroy(); +} + +BOOL CArchivePreviewDlg::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + + if (m_bDataChanged) + { + UpdateArchiveDisplay(thePrefs.m_bAutomaticArcPreviewStart); + m_bDataChanged = false; + } + return TRUE; +} + +LRESULT CArchivePreviewDlg::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +void CArchivePreviewDlg::Localize(void) +{ + if (!m_bReducedDlg) + { + SetDlgItemText(IDC_READARCH, GetResString(IDS_SV_UPDATE) ); + SetDlgItemText(IDC_RESTOREARCH, GetResString(IDS_AP_CREATEPREVCOPY) ); + SetDlgItemText(IDC_ARCP_TYPE , GetResString(IDS_ARCHTYPE)+_T(":") ); + SetDlgItemText(IDC_ARCP_STATUS, GetResString(IDS_STATUS)+_T(":") ); + SetDlgItemText(IDC_ARCP_ATTRIBS,GetResString(IDS_INFO)+_T(":") ); + } +} + +void CArchivePreviewDlg::OnLvnDeleteAllItemsArchiveEntries(NMHDR *, LRESULT *pResult) +{ + // To suppress subsequent LVN_DELETEITEM notification messages, return TRUE. + *pResult = TRUE; +} + +void CArchivePreviewDlg::OnNMCustomDrawArchiveEntries(NMHDR *pNMHDR, LRESULT *plResult) +{ + LPNMLVCUSTOMDRAW pnmlvcd = (LPNMLVCUSTOMDRAW)pNMHDR; + + if (pnmlvcd->nmcd.dwDrawStage == CDDS_PREPAINT) { + *plResult = CDRF_NOTIFYITEMDRAW; + return; + } + + if (pnmlvcd->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) + { + if (pnmlvcd->nmcd.lItemlParam == 1) { + // Show not available or incomplete entries in gray. + pnmlvcd->clrText = RGB(128,128,128); + } + } + + *plResult = CDRF_DODEFAULT; +} + +void CArchivePreviewDlg::OnBnExplain() { + AfxMessageBox(GetResString(IDS_ARCHPREV_ATTRIBINFO), MB_OK | MB_ICONINFORMATION); +} + +void CArchivePreviewDlg::OnBnClickedRead() +{ + UpdateArchiveDisplay(true); +} + +void CArchivePreviewDlg::OnBnClickedCreateRestored() { + if ( ((CShareableFile*)STATIC_DOWNCAST(CShareableFile, (*m_paFiles)[0])->IsPartFile()==false)) + return; + + CPartFile* file=STATIC_DOWNCAST(CPartFile, (*m_paFiles)[0]); + + if (!file->m_bRecoveringArchive && !file->m_bPreviewing) + CArchiveRecovery::recover((CPartFile*)file, true, thePrefs.GetPreviewCopiedArchives()); +} + +// ########################################################################### + +/***************************************** + A C E + *****************************************/ +int CArchivePreviewDlg::ShowAceResults(int succ, archiveScannerThreadParams_s* tp) { + + if (succ==0) { + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPREV_INSUFFDATA)); + return -1; + } + + // file content into list + bool statusEncrypted=false; + CString temp; + UINT uArchiveFileEntries = 0; + + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPARSED) + _T(" ") + + (tp->ai->ACEdir->IsEmpty()?GetResString(IDS_ARCPREV_INSUFFDATA): + (tp->file->IsPartFile()?GetResString(IDS_ARCPREV_LISTMAYBEINCOMPL):_T(""))) ); + + if (!tp->ai->ACEdir->IsEmpty()) + { + CTime lm; + int iItem; + int uSubId=1; + ACE_BlockFile *block; + + m_ContentList.SetRedraw(FALSE); + POSITION pos = tp->ai->ACEdir->GetHeadPosition(); + while (pos != NULL) + { + block = tp->ai->ACEdir->GetNext(pos); + uSubId = 1; + + bool bCompleteEntry = !tp->file->IsPartFile() || STATIC_DOWNCAST(CPartFile, tp->file)->IsComplete(block->data_offset, block->data_offset + block->PACK_SIZE, true); + bool bIsDirectory = ((block->FILE_ATTRIBS & FILE_ATTRIBUTE_DIRECTORY) != 0); + if (!bIsDirectory) + uArchiveFileEntries++; + + // file/folder name + temp = CString(block->FNAME); + int iSystemImage = bIsDirectory ? theApp.GetFileTypeSystemImageIdx(_T("\\"), 1) : theApp.GetFileTypeSystemImageIdx(temp, temp.GetLength()); + iItem = m_ContentList.InsertItem(LVIF_TEXT | LVIF_PARAM | (iSystemImage > 0 ? LVIF_IMAGE : 0), + INT_MAX, temp, 0, 0, iSystemImage, + !bCompleteEntry ? 0x00000001 : 0x00000000); + + if (bIsDirectory) + uSubId += 3; + else { + // size (uncompressed) + UINT64 size=block->ORIG_SIZE; + temp.Format(_T("%s"), CastItoXBytes(size)); + m_ContentList.SetItemText(iItem,uSubId++,temp); + + // crc + temp.Format(_T("%08X"),block->CRC32); + m_ContentList.SetItemText(iItem,uSubId++,temp); + } + + // attribs + temp.Empty(); + + if (block->HEAD_FLAGS & 0x4000) { + statusEncrypted=true; + temp+=_T('P'); + } + + if (bIsDirectory) { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('D'); + } + + if (block->HEAD_FLAGS & 0x2000) { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('>'); + } + if (block->HEAD_FLAGS & 0x1000) { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('<'); + } + if (block->HEAD_FLAGS & 0x02) { + // file comment - theoretically + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('C'); + } + + if (!bCompleteEntry) { + // packed data not yet downloaded + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('M'); + } + + if (!bIsDirectory) { + // compression level + if (!temp.IsEmpty()) temp+=_T(", "); + if ((BYTE)block->TECHINFO==0) + temp.Append(_T("L0")); + else + temp.AppendFormat(_T("L%i"), (BYTE)(block->TECHINFO>>8) ); + m_ContentList.SetItemText(iItem,uSubId++,temp); + } + + // date time + UINT16 fdate,ftime; + ftime=(UINT16)block->FTIME; + fdate=(UINT16)(block->FTIME>>16); + lm=CTime((WORD)(((fdate>>9) & 0x7f) + 1980), + (WORD)((fdate>>5) & 0xf), + (WORD)(fdate & 0x1f), + (WORD)((ftime >> 11) & 0x1f), + (WORD)((ftime >> 5) & 0x3f), + (WORD)((ftime & 0x1f) * 2)); + m_ContentList.SetItemText(iItem,uSubId++,lm.Format(thePrefs.GetDateTimeFormat4Log())); + + // cleanup + delete block; + } + m_ContentList.SetRedraw(TRUE); + } + + temp.Format(_T("%s: %u"), GetResString(IDS_FILES), uArchiveFileEntries); + SetDlgItemText( IDC_INFO_FILECOUNT, temp); + + // general info / archive attribs + CString status; + if (statusEncrypted) + status+= GetResString(IDS_PASSWPROT); + + if (tp->ai->ACEhdr) { + if (tp->ai->ACEhdr->HEAD_FLAGS & 0x8000){ + if (!status.IsEmpty()) status+=_T(','); + status+= _T("Solid"); + } + if (tp->ai->ACEhdr->HEAD_FLAGS & 0x4000){ + if (!status.IsEmpty()) status+=_T(','); + status+= _T("Locked"); + } + if (tp->ai->ACEhdr->HEAD_FLAGS & 0x2000){ + if (!status.IsEmpty()) status+=_T(','); + status+= _T("RecoveryRec"); + } + if (tp->ai->ACEhdr->COMMENT_SIZE > 0){ + if (!status.IsEmpty()) status+=_T(','); + status+= GetResString(IDS_COMMENT); + } + } + + SetDlgItemText(IDC_INFO_ATTR, status); + //... any other info? + + delete tp->ai->ACEdir; + tp->ai->ACEdir=NULL; + if (tp->ai->ACEhdr) { + delete tp->ai->ACEhdr; + tp->ai->ACEhdr=NULL; + } + + return 0; +} + +/***************************************** + I S O + *****************************************/ +int CArchivePreviewDlg::ShowISOResults(int succ, archiveScannerThreadParams_s* tp) { + + if (succ==0) { + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_UNSUPPORTEDIMAGE)); + delete tp->ai->ISOdir; + return -1; + } + + // file content into list + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPARSED) + _T(" ") + + (tp->ai->ISOdir->IsEmpty()?GetResString(IDS_ARCPREV_INSUFFDATA): + (tp->file->IsPartFile()?GetResString(IDS_ARCPREV_LISTMAYBEINCOMPL):_T(""))) ); + + DWORD filecount = 0; + + CString temp; + + m_ContentList.SetRedraw(FALSE); + + ISO_FileFolderEntry* file = NULL; + POSITION pos = tp->ai->ISOdir->GetHeadPosition(); + while (pos != NULL) + { + file = tp->ai->ISOdir->GetNext(pos); + + bool bCompleteEntry = !tp->file->IsPartFile() || STATIC_DOWNCAST(CPartFile, tp->file)->IsComplete( + LODWORD(file->sector1OfExtension) * tp->ai->isoInfos.secSize, + (LODWORD(file->sector1OfExtension) * tp->ai->isoInfos.secSize) + file->dataSize, + true); + + temp = CString(file->name); + // remove seperator extentions + int sep = temp.ReverseFind(';'); + if (sep>=0) + temp.Delete(sep, temp.GetLength()-sep); + + int iSystemImage = theApp.GetFileTypeSystemImageIdx(temp, temp.GetLength()); + int iItem = m_ContentList.InsertItem(LVIF_TEXT | LVIF_PARAM | (iSystemImage > 0 ? LVIF_IMAGE : 0), + INT_MAX, temp, 0, 0, iSystemImage, + !bCompleteEntry ? 0x00000001 : 0x00000000); + + + // attribs + temp.Empty(); + + //is directory ? + if (file->fileFlags & ISO_DIRECTORY) { + temp+=_T('D'); + } else { + filecount++; + + // size + UINT32 size= LODWORD(file->dataSize); + temp.Format(_T("%s"), CastItoXBytes(size)); + m_ContentList.SetItemText(iItem,1,temp); + temp.Empty(); + } + + // file attribs + if (file->fileFlags & ISO_HIDDEN) + { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('H'); + } + if (file->fileFlags & ISO_READONLY) + { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('R'); + } + + if (!bCompleteEntry) + { + // packed data not yet downloaded + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('M'); + } + + m_ContentList.SetItemText(iItem, 3, temp); + + // date time + CTime lm=CTime( + 1900+ file->dateTime.year, + file->dateTime.month, + file->dateTime.day, + file->dateTime.hour, + file->dateTime.minute, + file->dateTime.second + ); + m_ContentList.SetItemText(iItem, 4, lm.Format(thePrefs.GetDateTimeFormat4Log())+ _T(" GMT") ); + + // cleanup + delete file; + } + m_ContentList.SetRedraw(TRUE); + temp.Format(_T("%s: %u"), GetResString(IDS_FILES), filecount ); + SetDlgItemText( IDC_INFO_FILECOUNT, temp); + + temp.Empty(); + if (tp->ai->isoInfos.bBootable) + temp = GetResString(IDS_BOOTABLE); + + if (tp->ai->isoInfos.type & ISOtype_9660) + { + if (!temp.IsEmpty()) + temp.Append(_T(",")); + temp.Append(_T("ISO9660")); + } + if (tp->ai->isoInfos.type & ISOtype_joliet) + { + if (!temp.IsEmpty()) + temp.Append(_T(",")); + temp.Append(_T("Joliet")); + } + if (tp->ai->isoInfos.type & ISOtype_UDF_nsr02 || tp->ai->isoInfos.type & ISOtype_UDF_nsr03) + { + if (!temp.IsEmpty()) + temp.Append(_T(",")); + temp.Append(_T("UDF (") + GetResString(IDS_UNSUPPORTEDIMAGE) + _T(")")); + } + + + SetDlgItemText(IDC_INFO_ATTR, temp); + + delete tp->ai->ISOdir; + + return 0; +} + +/***************************************** + R A R + *****************************************/ +int CArchivePreviewDlg::ShowRarResults(int succ, archiveScannerThreadParams_s* tp) { + + if (succ==0) { + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPREV_INSUFFDATA)); + return -1; + } + + // file content into list + bool statusEncrypted=false; + CString temp; + UINT uArchiveFileEntries = 0; + + if (tp->ai->rarFlags & 0x0080){ + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_RARFULLENCR)); + statusEncrypted=true; + } + else + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPARSED) + _T(" ") + + (tp->ai->RARdir->IsEmpty()?GetResString(IDS_ARCPREV_INSUFFDATA): + (tp->file->IsPartFile()?GetResString(IDS_ARCPREV_LISTMAYBEINCOMPL):_T(""))) ); + + if (!tp->ai->RARdir->IsEmpty()) + { + char buf[MAX_PATH + MAX_PATH*2]; + CStringA strBuffA; + CTime lm; + int iItem; + int uSubId = 1; + RAR_BlockFile *block; + + // This file could be a self extracting RAR archive. Now that we eventually have read something + // RAR-like from that file, we can set the 'verified' file type. + tp->file->SetVerifiedFileType(ARCHIVE_RAR); + + m_ContentList.SetRedraw(FALSE); + POSITION pos = tp->ai->RARdir->GetHeadPosition(); + while (pos != NULL) + { + block = tp->ai->RARdir->GetNext(pos); + uSubId=1; + + bool bCompleteEntry = !tp->file->IsPartFile() || STATIC_DOWNCAST(CPartFile, tp->file)->IsComplete(block->offsetData, block->offsetData + block->dataLength, true); + bool bIsDirectory = (block->HEAD_FLAGS & 0xE0) == 0xE0; + if (!bIsDirectory) + uArchiveFileEntries++; + + // file/folder name + memcpy(buf, block->FILE_NAME, block->NAME_SIZE); + buf[block->NAME_SIZE] = 0; + + // read unicode name from namebuffer and decode it + if (block->HEAD_FLAGS & 0x0200) { + unsigned int asciilen = strlen(buf) + 1; + wchar_t nameuc[MAX_PATH]; + EncodeFileName enc; + enc.Decode(buf, (byte*)buf + asciilen, block->NAME_SIZE - asciilen, nameuc, _countof(nameuc)); + temp = nameuc; + } else + temp = buf; + + int iSystemImage = bIsDirectory ? theApp.GetFileTypeSystemImageIdx(_T("\\"), 1) : theApp.GetFileTypeSystemImageIdx(temp, temp.GetLength()); + iItem = m_ContentList.InsertItem(LVIF_TEXT | LVIF_PARAM | (iSystemImage > 0 ? LVIF_IMAGE : 0), + INT_MAX, temp, 0, 0, iSystemImage, + !bCompleteEntry ? 0x00000001 : 0x00000000); + + if ( (block->HEAD_FLAGS & 0xE0) != 0xE0) { + + // size (uncompressed) + UINT64 size=block->UNP_SIZE; + if (block->ATTR & 0x100) + size+=block->HIGH_UNP_SIZE*0x100000000; + temp.Format(_T("%s"), CastItoXBytes(size)); + m_ContentList.SetItemText(iItem,uSubId++,temp); + + // crc + temp.Format(_T("%08X"),block->FILE_CRC); + m_ContentList.SetItemText(iItem,uSubId++,temp); + + } else + uSubId+=2; + + // attribs + temp.Empty(); + + if (block->HEAD_FLAGS & 0x04) + { + statusEncrypted=true; + temp+=_T('P'); + } + + //is directory ? + if (bIsDirectory){ + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('D'); + } + + if (block->HEAD_FLAGS & 0x01) { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('<'); + } + if (block->HEAD_FLAGS & 0x02) { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('>'); + } + if (block->HEAD_FLAGS & 0x08) { + // file comment - theoretically + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('C'); + } + + if (!bCompleteEntry) { + // packed data not yet downloaded + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('M'); + } + + if (!temp.IsEmpty()) temp+=_T(", "); + temp.AppendFormat(_T("L%i"),block->METHOD-0x30); + + m_ContentList.SetItemText(iItem,uSubId++,temp); + + // date time + UINT16 fdate,ftime; + ftime=(UINT16)block->FTIME; + fdate=(UINT16)(block->FTIME>>16); + lm=CTime((WORD)(((fdate>>9) & 0x7f) + 1980), + (WORD)((fdate>>5) & 0xf), + (WORD)(fdate & 0x1f), + (WORD)((ftime >> 11) & 0x1f), + (WORD)((ftime >> 5) & 0x3f), + (WORD)((ftime & 0x1f) * 2)); + m_ContentList.SetItemText(iItem,uSubId++,lm.Format(thePrefs.GetDateTimeFormat4Log())); + + // cleanup + delete [] block->FILE_NAME; + delete block; + } + m_ContentList.SetRedraw(TRUE); + } + + temp.Format(_T("%s: %u"), GetResString(IDS_FILES), uArchiveFileEntries); + SetDlgItemText(IDC_INFO_FILECOUNT, temp); + + // general info / archive attribs + CString status; + if (statusEncrypted) + status+= GetResString(IDS_PASSWPROT); + + if (tp->ai->rarFlags & 0x0008){ + if (!status.IsEmpty()) status+=_T(','); + status+= _T("Solid"); + } + if (tp->ai->rarFlags & 0x0004){ + if (!status.IsEmpty()) status+=_T(','); + status+= _T("Locked"); + } + if (tp->ai->rarFlags & 0x0040){ + if (!status.IsEmpty()) status+=_T(','); + status+= _T("RecoveryRec"); + } + if (tp->ai->rarFlags & 0x0002){ + if (!status.IsEmpty()) status+=_T(','); + status+= GetResString(IDS_COMMENT); + } + + SetDlgItemText(IDC_INFO_ATTR, status); + //... any other info? + + delete tp->ai->RARdir; + tp->ai->RARdir=NULL; + + return 0; +} + +/***************************************** + Z I P + *****************************************/ +int CArchivePreviewDlg::ShowZipResults(int succ, archiveScannerThreadParams_s* tp) { + + if (succ==0) { + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPREV_INSUFFDATA)); + return 1; + } + + // file content into list + CString temp; + UINT uArchiveFileEntries = 0; + + if (tp->ai->bZipCentralDir) + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPREV_DIRSUCCREAD) ); + else + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPREV_DIRNOTFOUND) + _T(" ") + + GetResString(IDS_ARCPARSED) + _T(" ") + + (tp->ai->centralDirectoryEntries->IsEmpty()?GetResString(IDS_ARCPREV_INSUFFDATA): + GetResString(IDS_ARCPREV_LISTMAYBEINCOMPL)) ); + + bool statusEncrypted=false; + if (!tp->ai->centralDirectoryEntries->IsEmpty()) + { + CStringA strBuffA; + CTime lm; + int iItem; + int uSubId=1; + ZIP_CentralDirectory *cdEntry; + m_ContentList.SetRedraw(FALSE); + POSITION pos = tp->ai->centralDirectoryEntries->GetHeadPosition(); + while (pos != NULL) + { + cdEntry = tp->ai->centralDirectoryEntries->GetNext(pos); + uSubId = 1; + + // compressed data not available yet? + bool bCompleteEntry = true; + if (tp->file->IsPartFile()) { + const CPartFile* pf = STATIC_DOWNCAST(CPartFile, tp->file); + UINT64 dataoffset = cdEntry->relativeOffsetOfLocalHeader + + sizeof(ZIP_Entry) - (3 * sizeof(BYTE*)) + + cdEntry->lenComment + + cdEntry->lenFilename + + cdEntry->lenExtraField; + bCompleteEntry = pf->IsComplete(dataoffset, dataoffset + cdEntry->lenCompressed, true); + } + + // Is directory? + bool bIsDirectory = (cdEntry->externalFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + || (cdEntry->crc32 == 0 && cdEntry->lenCompressed == 0 && cdEntry->lenUncompressed == 0 && cdEntry->lenFilename != 0); + if (!bIsDirectory) + uArchiveFileEntries++; + + // file/folder name + strBuffA.SetString((char *)cdEntry->filename, cdEntry->lenFilename); + temp = CString(strBuffA); + int iSystemImage = bIsDirectory ? theApp.GetFileTypeSystemImageIdx(_T("\\"), 1) : theApp.GetFileTypeSystemImageIdx(temp, temp.GetLength()); + iItem = m_ContentList.InsertItem(LVIF_TEXT | LVIF_PARAM | (iSystemImage > 0 ? LVIF_IMAGE : 0), + INT_MAX, temp, 0, 0, iSystemImage, + !bCompleteEntry ? 0x00000001 : 0x00000000); + + // size (uncompressed) + if (!bIsDirectory) { + temp.Format(_T("%s"), CastItoXBytes(cdEntry->lenUncompressed)); + m_ContentList.SetItemText(iItem,uSubId++,temp); + + // crc + temp.Format(_T("%08X"),cdEntry->crc32); + m_ContentList.SetItemText(iItem,uSubId++,temp); + } else + uSubId+=2; + + // attribs + temp.Empty(); + if (cdEntry->generalPurposeFlag & 0x01 || cdEntry->generalPurposeFlag & 0x40) { + statusEncrypted=true; + temp+=_T('P'); + } + + if (bIsDirectory) { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('D'); + } + + // compressed data not available yet? + if (!bCompleteEntry) { + if (!temp.IsEmpty()) temp+=_T(','); + temp+=_T('M'); + } + + m_ContentList.SetItemText(iItem,uSubId++,temp); + + // date time + lm=CTime((WORD)(((cdEntry->lastModFileDate>>9) & 0x7f) + 1980), + (WORD)((cdEntry->lastModFileDate>>5) & 0xf), + (WORD)(cdEntry->lastModFileDate & 0x1f), + (WORD)((cdEntry->lastModFileTime >> 11) & 0x1f), + (WORD)((cdEntry->lastModFileTime >> 5) & 0x3f), + (WORD)((cdEntry->lastModFileTime & 0x1f) * 2)); + m_ContentList.SetItemText(iItem,uSubId++,lm.Format(thePrefs.GetDateTimeFormat4Log())); + + // comment + temp.Empty(); + if (cdEntry->lenComment) { + strBuffA.SetString((char *)cdEntry->comment, cdEntry->lenComment); + temp = CString(strBuffA); + } + m_ContentList.SetItemText(iItem, uSubId++, temp); + + + // cleanup + delete [] cdEntry->filename; + if (cdEntry->lenExtraField > 0) + delete [] cdEntry->extraField; + if (cdEntry->lenComment > 0) + delete [] cdEntry->comment; + delete cdEntry; + } + m_ContentList.SetRedraw(TRUE); + } + + temp.Format(_T("%s: %u"), GetResString(IDS_FILES), uArchiveFileEntries); + SetDlgItemText(IDC_INFO_FILECOUNT, temp); + + // general info / archive attribs + CString status; + if (statusEncrypted) + status+= GetResString(IDS_PASSWPROT); + + SetDlgItemText(IDC_INFO_ATTR, status); + //... any other info? + + delete tp->ai->centralDirectoryEntries; + tp->ai->centralDirectoryEntries=NULL; + + return 0; +} + + +// ################################################################## +static void FreeMemory(void* arg) { + + archiveScannerThreadParams_s* tp=(archiveScannerThreadParams_s*)arg; + + POSITION pos = tp->filled->GetHeadPosition(); + while (pos != NULL) + delete tp->filled->GetNext(pos); + tp->filled->RemoveAll(); + delete tp->filled; + tp->filled=NULL; + + + if (tp->ai->RARdir) { + RAR_BlockFile *block; + POSITION pos = tp->ai->RARdir->GetHeadPosition(); + while (pos != NULL) + { + block = tp->ai->RARdir->GetNext(pos); + delete [] block->FILE_NAME; + delete block; + } + delete tp->ai->RARdir; + tp->ai->RARdir=NULL; + } + + if (tp->ai->centralDirectoryEntries) { + pos = tp->ai->centralDirectoryEntries->GetHeadPosition(); + while (pos != NULL) + { + ZIP_CentralDirectory *cdEntry = tp->ai->centralDirectoryEntries->GetNext(pos); + // cleanup + delete [] cdEntry->filename; + if (cdEntry->lenExtraField > 0) + delete [] cdEntry->extraField; + if (cdEntry->lenComment > 0) + delete [] cdEntry->comment; + delete cdEntry; + } + delete tp->ai->centralDirectoryEntries; + tp->ai->centralDirectoryEntries=NULL; + } + + if (tp->ai->ACEhdr){ + delete tp->ai->ACEhdr; + tp->ai->ACEhdr=NULL; + } + if (tp->ai->ACEdir){ + pos = tp->ai->ACEdir->GetHeadPosition(); + while (pos != NULL) + { + ACE_BlockFile *block= tp->ai->ACEdir->GetNext(pos); + delete block; + } + delete tp->ai->ACEdir; + tp->ai->ACEdir=NULL; + } + + delete tp->ai; + tp->ai=NULL; + + delete tp; + tp=NULL; +} + +void CArchivePreviewDlg::UpdateArchiveDisplay(bool doscan) { + + // invalidate previous scanning thread if still running + if (m_activeTParams != NULL){ + m_activeTParams->m_bIsValid = false; + m_activeTParams = NULL; // thread may still run but is not our active one anymore + } + m_progressbar.SetPos(0); + + m_ContentList.DeleteAllItems(); + m_ContentList.UpdateWindow(); + + // set infos + SetDlgItemText(IDC_APV_FILEINFO, _T("") ); + SetDlgItemText(IDC_INFO_ATTR, _T("")); + SetDlgItemText(IDC_INFO_STATUS, _T("")); + SetDlgItemText(IDC_INFO_FILECOUNT, _T("")); + + if (m_paFiles == NULL || m_paFiles->GetSize() == 0) + return; + + CShareableFile* file=STATIC_DOWNCAST(CShareableFile, (*m_paFiles)[0]); + + GetDlgItem(IDC_RESTOREARCH)->EnableWindow( file->IsPartFile() && + (((CPartFile*)file)->IsArchive(true)) && + (((CPartFile*)file)->IsReadyForPreview() ) ); + + + + EFileType type=GetFileTypeEx(file); + switch(type) { + case ARCHIVE_ZIP: + SetDlgItemText(IDC_INFO_TYPE, _T("ZIP")); + break; + case ARCHIVE_RAR: + SetDlgItemText(IDC_INFO_TYPE, _T("RAR")); + break; + case ARCHIVE_ACE: + SetDlgItemText(IDC_INFO_TYPE, _T("ACE")); + break; + case IMAGE_ISO: + SetDlgItemText(IDC_INFO_TYPE, _T("ISO")); + break; + default: + SetDlgItemText(IDC_INFO_TYPE, GetResString(IDS_ARCPREV_UNKNOWNFORMAT)); + return; + break; + } + + if (!doscan) + return; + + // preparing archive processing + + archiveinfo_s* ai=new archiveinfo_s; + + // get filled area list + CTypedPtrList *filled = new CTypedPtrList; + if (file->IsPartFile()) { + ((CPartFile*)(file))->GetFilledList(filled); + if (filled->GetCount()==0) { + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPREV_INSUFFDATA)); + delete filled; + delete ai; + return; + } + } + else { + Gap_Struct* gap = new Gap_Struct; + gap->start=0; + gap->end=file->GetFileSize(); + filled->AddTail(gap); + } + + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ARCPREV_PLEASEWAIT) ); + + switch(type) { + case ARCHIVE_ZIP: + ai->centralDirectoryEntries= new CTypedPtrList; + break; + case ARCHIVE_RAR: + ai->RARdir = new CTypedPtrList; + break; + case ARCHIVE_ACE: + ai->ACEdir = new CTypedPtrList; + break; + case IMAGE_ISO: + ai->ISOdir = new CTypedPtrList; + break; + } + + // prepare threadparams + archiveScannerThreadParams_s *tp= new archiveScannerThreadParams_s; + m_activeTParams=tp; + tp->ai=ai; + tp->file=file; + tp->filled=filled; + tp->type=type; + tp->ownerHwnd=m_hWnd; + tp->progressHwnd= GetDlgItem(IDC_ARCHPROGRESS)->m_hWnd; + tp->m_bIsValid=true; + + // start scanning thread + if (AfxBeginThread(RunArchiveScanner, (LPVOID)tp, THREAD_PRIORITY_BELOW_NORMAL) == NULL) { + FreeMemory(tp); + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_ERROR) ); + } +} + +UINT AFX_CDECL CArchivePreviewDlg::RunArchiveScanner(LPVOID pParam) { + DbgSetThreadName("ArchiveScanner"); + //InitThreadLocale(); + + archiveScannerThreadParams_s *tp=(archiveScannerThreadParams_s *)pParam; + + int ret=0; + CFile inFile; + + if (!inFile.Open(tp->file->GetFilePath(), CFile::modeRead | CFile::shareDenyNone)){ + ret=-1; + } else { + switch (tp->type) { + case ARCHIVE_ZIP: + ret=CArchiveRecovery::recoverZip(&inFile, NULL, tp, tp->filled, (inFile.GetLength() == tp->file->GetFileSize())); + break; + case ARCHIVE_RAR: + ret=CArchiveRecovery::recoverRar(&inFile, NULL, tp, tp->filled); + break; + case ARCHIVE_ACE: + ret=CArchiveRecovery::recoverAce(&inFile, NULL, tp, tp->filled); + break; + case IMAGE_ISO: + ret=CArchiveRecovery::recoverISO(&inFile, NULL, tp, tp->filled); + break; + } + + inFile.Close(); + } + + if (!tp->m_bIsValid || !IsWindow(tp->ownerHwnd) || !::SendMessage(tp->ownerHwnd, UM_ARCHIVESCANDONE, ret, (LPARAM)tp)) + FreeMemory(tp); + return 0; +} + +LRESULT CArchivePreviewDlg::ShowScanResults(WPARAM wParam, LPARAM lParam) +{ + CWaitCursor curHourglass; + + int ret = wParam; + archiveScannerThreadParams_s* tp = (archiveScannerThreadParams_s*)lParam; + ASSERT( tp->ownerHwnd == m_hWnd ); + + // We may receive 'stopped' archive thread results here, just ignore them (but free the memory) + if (tp->m_bIsValid) + { + m_progressbar.SetPos(0); + if (ret == -1) { + SetDlgItemText(IDC_INFO_STATUS, GetResString(IDS_IMP_ERR_IO)); + } + else if (tp->m_bIsValid) { + + // hide two unused columns for ISO and unhide for other types + if (tp->type!=IMAGE_ISO) + { + if (m_ContentList.GetColumnWidth(2)==0) + m_ContentList.SetColumnWidth(2, m_StoredColWidth2); + if (m_ContentList.GetColumnWidth(5)==0) + m_ContentList.SetColumnWidth(5, m_StoredColWidth5); + } + else + { + int w = m_ContentList.GetColumnWidth(2); + if (w>0) + m_StoredColWidth2 = w; + w = m_ContentList.GetColumnWidth(5); + if (w>0) + m_StoredColWidth5 = w; + + m_ContentList.SetColumnWidth(2, 0); + m_ContentList.SetColumnWidth(5, 0); + } + + + switch (tp->type) { + case ARCHIVE_ZIP: + ShowZipResults(ret, tp); + break; + case ARCHIVE_RAR: + ShowRarResults(ret, tp); + break; + case ARCHIVE_ACE: + ShowAceResults(ret, tp); + break; + case IMAGE_ISO: + ShowISOResults(ret,tp); + break; + } + } + ASSERT( tp == m_activeTParams ); + m_activeTParams = NULL; + } + + FreeMemory(tp); + return 1; +} + +void CArchivePreviewDlg::OnContextMenu(CWnd* pWnd, CPoint point) +{ + if (m_bReducedDlg) + { + CMenu menu; + menu.CreatePopupMenu(); + menu.AppendMenu(MF_STRING, MP_UPDATE, GetResString(IDS_SV_UPDATE)); + menu.AppendMenu(MF_STRING, MP_HM_HELP, GetResString(IDS_EM_HELP)); + menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); + } + else + __super::OnContextMenu(pWnd, point); +} diff --git a/ArchivePreviewDlg.h b/ArchivePreviewDlg.h new file mode 100644 index 00000000..ab9141a7 --- /dev/null +++ b/ArchivePreviewDlg.h @@ -0,0 +1,78 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "ResizableLib/ResizablePage.h" +#include "ListCtrlX.h" +#include "ArchiveRecovery.h" + +class CKnownFile; + +static void FreeMemory(void *arg); + +/////////////////////////////////////////////////////////////////////////////// +// CArchivePreviewDlg + +class CArchivePreviewDlg : public CResizablePage +{ + DECLARE_DYNAMIC(CArchivePreviewDlg) + +public: + CArchivePreviewDlg(); + virtual ~CArchivePreviewDlg(); + + void SetFiles(const CSimpleArray* paFiles) { m_paFiles = paFiles; m_bDataChanged = true; } + void SetReducedDialog() { m_bReducedDlg = true; } + +// Dialog Data + enum { IDD = IDD_ARCHPREV }; + +protected: + const CSimpleArray* m_paFiles; + archiveScannerThreadParams_s* m_activeTParams; + + CListCtrlX m_ContentList; + bool m_bDataChanged; + int m_StoredColWidth2, m_StoredColWidth5; + bool m_bReducedDlg; + + void Localize(); + void UpdateArchiveDisplay(bool doscan); + int ShowISOResults(int ret, archiveScannerThreadParams_s* tp); + int ShowZipResults(int ret, archiveScannerThreadParams_s* tp); + int ShowRarResults(int ret, archiveScannerThreadParams_s* tp); + int ShowAceResults(int ret, archiveScannerThreadParams_s* tp); + LRESULT ShowScanResults(WPARAM, LPARAM lParam); + + static UINT RunArchiveScanner(LPVOID pParam); + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + virtual BOOL OnSetActive(); + + CProgressCtrl m_progressbar; + + DECLARE_MESSAGE_MAP() + afx_msg void OnBnClickedRead(); + afx_msg void OnBnClickedCreateRestored(); + afx_msg void OnBnExplain(); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); + afx_msg void OnDestroy(); + afx_msg void OnLvnDeleteAllItemsArchiveEntries(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNMCustomDrawArchiveEntries(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); +}; diff --git a/ArchiveRecovery.cpp b/ArchiveRecovery.cpp new file mode 100644 index 00000000..28ce0adf --- /dev/null +++ b/ArchiveRecovery.cpp @@ -0,0 +1,1924 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ArchiveRecovery.h" +#include "OtherFunctions.h" +#include "Preferences.h" +#include "PartFile.h" +#include +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#pragma pack(push,1) +typedef struct { + BYTE type; + WORD flags; + WORD size; +} RARMAINHDR; +#pragma pack(pop) + +#pragma pack(push,1) +typedef struct { + BYTE type; + WORD flags; + WORD size; + DWORD packetSize; + DWORD unpacketSize; + BYTE hostOS; + DWORD fileCRC; + DWORD fileTime; + BYTE unpVer; + BYTE method; + WORD nameSize; + DWORD fileAttr; +} RARFILEHDR; +#pragma pack(pop) + + +void CArchiveRecovery::recover(CPartFile *partFile, bool preview, bool bCreatePartFileCopy) +{ + if (partFile->m_bPreviewing || partFile->m_bRecoveringArchive) + return; + partFile->m_bRecoveringArchive = true; + + AddLogLine(true, _T("%s \"%s\""), GetResString(IDS_ATTEMPTING_RECOVERY), partFile->GetFileName()); + + // Get the current filled list for this file + CTypedPtrList *filled = new CTypedPtrList; + partFile->GetFilledList(filled); +#ifdef _DEBUG + { + int i = 0; + TRACE("%s: filled\n", __FUNCTION__); + POSITION pos = filled->GetHeadPosition(); + while (pos){ + Gap_Struct* gap = filled->GetNext(pos); + TRACE("%3u: %10u %10u (%u)\n", i++, gap->start, gap->end, gap->end - gap->start + 1); + } + } +#endif + + // The rest of the work can be safely done in a new thread + ThreadParam *tp = new ThreadParam; + tp->partFile = partFile; + tp->filled = filled; + tp->preview = preview; + tp->bCreatePartFileCopy = bCreatePartFileCopy; + // - do NOT use Windows API 'CreateThread' to create a thread which uses MFC/CRT -> lot of mem leaks! + if (!AfxBeginThread(run, (LPVOID)tp)){ + partFile->m_bRecoveringArchive = false; + LogError(LOG_STATUSBAR, _T("%s \"%s\""), GetResString(IDS_RECOVERY_FAILED), partFile->GetFileName()); + // Need to delete the memory here as won't be done in thread + DeleteMemory(tp); + } +} + +UINT AFX_CDECL CArchiveRecovery::run(LPVOID lpParam) +{ + ThreadParam *tp = (ThreadParam *)lpParam; + DbgSetThreadName("ArchiveRecovery"); + InitThreadLocale(); + + if (!performRecovery(tp->partFile, tp->filled, tp->preview, tp->bCreatePartFileCopy)) + theApp.QueueLogLine(true, _T("%s \"%s\""), GetResString(IDS_RECOVERY_FAILED), tp->partFile->GetFileName()); + + tp->partFile->m_bRecoveringArchive = false; + + // Delete memory used by copied gap list + DeleteMemory(tp); + + return 0; +} + +bool CArchiveRecovery::performRecovery(CPartFile *partFile, CTypedPtrList *filled, + bool preview, bool bCreatePartFileCopy) +{ + if (filled->GetCount()==0) + return false; + + bool success = false; + try + { + CFile temp; + CString tempFileName; + if (bCreatePartFileCopy) + { + // Copy the file + tempFileName = partFile->GetTempPath() + partFile->GetFileName().Mid(0, 5) + _T("-rec.tmp"); + if (!CopyFile(partFile, filled, tempFileName)) + return false; + + // Open temp file for reading + if (!temp.Open(tempFileName, CFile::modeRead|CFile::shareDenyWrite)) + return false; + } + else + { + if (!temp.Open(partFile->GetFilePath(), CFile::modeRead | CFile::shareDenyNone)) + return false; + } + + // Open the output file + EFileType myAtype=GetFileTypeEx(partFile); + CString ext; + if (myAtype==ARCHIVE_ZIP) + ext=_T(".zip"); + else if (myAtype==ARCHIVE_RAR) + ext=_T(".rar"); + else if (myAtype==ARCHIVE_ACE) + ext=_T(".ace"); + + CString outputFileName = partFile->GetTempPath() + partFile->GetFileName().Mid(0, 5) + _T("-rec") + ext; + CFile output; + ULONGLONG ulTempFileSize = 0; + if (output.Open(outputFileName, CFile::modeWrite | CFile::shareDenyWrite | CFile::modeCreate)) + { + // Process the output file + if ( myAtype==ARCHIVE_ZIP) + success = recoverZip(&temp, &output, NULL, filled, (temp.GetLength() == partFile->GetFileSize())); + else if(myAtype==ARCHIVE_RAR) + success = recoverRar(&temp, &output, NULL, filled); + else if(myAtype==ARCHIVE_ACE) + success = recoverAce(&temp, &output, NULL, filled); + + ulTempFileSize = output.GetLength(); + + // Close output + output.Close(); + } + // Close temp file + temp.Close(); + + // Remove temp file + if (!tempFileName.IsEmpty() ) + CFile::Remove(tempFileName); + + // Report success + if (success) + { + theApp.QueueLogLine(true, _T("%s \"%s\""), GetResString(IDS_RECOVERY_SUCCESSFUL), partFile->GetFileName()); + theApp.QueueDebugLogLine(false, _T("Archive recovery: Part file size: %s, temp. archive file size: %s (%.1f%%)"), CastItoXBytes(partFile->GetFileSize()), CastItoXBytes(ulTempFileSize), partFile->GetFileSize() > (uint64)0 ? (ulTempFileSize * 100.0 / (uint64)partFile->GetFileSize()) : 0.0); + + // Preview file if required + if (preview) + { + SHELLEXECUTEINFO SE; + memset(&SE,0,sizeof(SE)); + SE.fMask = SEE_MASK_NOCLOSEPROCESS ; + SE.lpVerb = _T("open"); + SE.lpFile = outputFileName; + SE.nShow = SW_SHOW; + SE.cbSize = sizeof(SE); + ShellExecuteEx(&SE); + if (SE.hProcess) + { + WaitForSingleObject(SE.hProcess, INFINITE); + CloseHandle(SE.hProcess); + } + CFile::Remove(outputFileName); + } + } else + CFile::Remove(outputFileName); + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + return success; +} + +bool CArchiveRecovery::recoverZip(CFile *zipInput, CFile *zipOutput, archiveScannerThreadParams_s* aitp , + CTypedPtrList *filled, bool fullSize) +{ + bool retVal = false; + long fileCount = 0; + try + { + CTypedPtrList* centralDirectoryEntries; + if (aitp==NULL) { + centralDirectoryEntries= new CTypedPtrList; + } else { + centralDirectoryEntries=aitp->ai->centralDirectoryEntries; + } + + Gap_Struct *fill; + + // If the central directory is intact this is simple + if (fullSize && readZipCentralDirectory(zipInput, centralDirectoryEntries, filled)) + { + if (centralDirectoryEntries->GetCount() == 0) + { + if (aitp == NULL) + delete centralDirectoryEntries; + return false; + } + + // if only read directory, return now + if (zipOutput==NULL) { + if (aitp == NULL) { + ASSERT(0); // FIXME + delete centralDirectoryEntries; + return false; + } + aitp->ai->bZipCentralDir = true; + return true; + } + + ZIP_CentralDirectory *cdEntry; + POSITION pos = centralDirectoryEntries->GetHeadPosition(); + bool deleteCD; + for (int i=centralDirectoryEntries->GetCount(); i>0; i--) + { + deleteCD = false; + cdEntry = centralDirectoryEntries->GetAt(pos); + uint32 lenEntry = sizeof(ZIP_Entry) + cdEntry->lenFilename + cdEntry->lenExtraField + cdEntry->lenCompressed; + if (IsFilled(cdEntry->relativeOffsetOfLocalHeader, cdEntry->relativeOffsetOfLocalHeader + lenEntry, filled)) + { + zipInput->Seek(cdEntry->relativeOffsetOfLocalHeader, CFile::begin); + // Update offset + cdEntry->relativeOffsetOfLocalHeader = (UINT)zipOutput->GetPosition(); + if (!processZipEntry(zipInput, zipOutput, lenEntry, NULL)) + deleteCD = true; + } + else + deleteCD = true; + + if (deleteCD) + { + delete [] cdEntry->filename; + if (cdEntry->lenExtraField > 0) + delete [] cdEntry->extraField; + if (cdEntry->lenComment > 0) + delete [] cdEntry->comment; + delete cdEntry; + POSITION del = pos; + centralDirectoryEntries->GetNext(pos); + centralDirectoryEntries->RemoveAt(del); + } + else + centralDirectoryEntries->GetNext(pos); + } + } + else // Have to scan the file the hard way + { + // Loop through filled areas of the file looking for entries + POSITION pos = filled->GetHeadPosition(); + while (pos != NULL) + { + fill = filled->GetNext(pos); + uint32 filePos = (UINT)zipInput->GetPosition(); + // The file may have been positioned to the next entry in ScanForMarker() or processZipEntry() + if (filePos > fill->end) + continue; + if (filePos < fill->start) + zipInput->Seek(fill->start, CFile::begin); + + // If there is any problem, then don't bother checking the rest of this part + for (;;) + { + if (aitp && !aitp->m_bIsValid) + return 0; + // Scan for entry marker within this filled area + if (!scanForZipMarker(zipInput, aitp, (uint32)ZIP_LOCAL_HEADER_MAGIC, (UINT)(fill->end - zipInput->GetPosition() + 1))) + break; + if (zipInput->GetPosition() > fill->end) + break; + if (!processZipEntry(zipInput, zipOutput, (UINT)(fill->end - zipInput->GetPosition() + 1), centralDirectoryEntries)) + break; + } + } + if (!zipOutput) + { + retVal = (centralDirectoryEntries->GetCount()>0 ); + if (aitp == NULL) + delete centralDirectoryEntries; + return retVal; + } + } + + // Remember offset before CD entries + uint32 startOffset = (UINT)zipOutput->GetPosition(); + + // Write all central directory entries + fileCount = centralDirectoryEntries->GetCount(); + if (fileCount > 0) + { + ZIP_CentralDirectory *cdEntry; + POSITION pos = centralDirectoryEntries->GetHeadPosition(); + while (pos != NULL) + { + cdEntry = centralDirectoryEntries->GetNext(pos); + + writeUInt32(zipOutput, ZIP_CD_MAGIC); + writeUInt16(zipOutput, cdEntry->versionMadeBy); + writeUInt16(zipOutput, cdEntry->versionToExtract); + writeUInt16(zipOutput, cdEntry->generalPurposeFlag); + writeUInt16(zipOutput, cdEntry->compressionMethod); + writeUInt16(zipOutput, cdEntry->lastModFileTime); + writeUInt16(zipOutput, cdEntry->lastModFileDate); + writeUInt32(zipOutput, cdEntry->crc32); + writeUInt32(zipOutput, cdEntry->lenCompressed); + writeUInt32(zipOutput, cdEntry->lenUncompressed); + writeUInt16(zipOutput, cdEntry->lenFilename); + writeUInt16(zipOutput, cdEntry->lenExtraField); + writeUInt16(zipOutput, cdEntry->lenComment); + writeUInt16(zipOutput, 0); // Disk number start + writeUInt16(zipOutput, cdEntry->internalFileAttributes); + writeUInt32(zipOutput, cdEntry->externalFileAttributes); + writeUInt32(zipOutput, cdEntry->relativeOffsetOfLocalHeader); + zipOutput->Write(cdEntry->filename, cdEntry->lenFilename); + if (cdEntry->lenExtraField > 0) + zipOutput->Write(cdEntry->extraField, cdEntry->lenExtraField); + if (cdEntry->lenComment > 0) + zipOutput->Write(cdEntry->comment, cdEntry->lenComment); + + delete [] cdEntry->filename; + if (cdEntry->lenExtraField > 0) + delete [] cdEntry->extraField; + if (cdEntry->lenComment > 0) + delete [] cdEntry->comment; + delete cdEntry; + } + + // Remember offset before CD entries + uint32 endOffset = (UINT)zipOutput->GetPosition(); + + // Write end of central directory + writeUInt32(zipOutput, ZIP_END_CD_MAGIC); + writeUInt16(zipOutput, 0); // Number of this disk + writeUInt16(zipOutput, 0); // Number of the disk with the start of the central directory + writeUInt16(zipOutput, (uint16)fileCount); + writeUInt16(zipOutput, (uint16)fileCount); + writeUInt32(zipOutput, endOffset - startOffset); + writeUInt32(zipOutput, startOffset); + writeUInt16(zipOutput, (uint16)strlen(ZIP_COMMENT)); + zipOutput->Write(ZIP_COMMENT, strlen(ZIP_COMMENT)); + + centralDirectoryEntries->RemoveAll(); + } + if (aitp == NULL) + delete centralDirectoryEntries; + retVal = true; + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + + // Tell the user how many files were recovered + CString msg; + if (fileCount == 1) + msg = GetResString(IDS_RECOVER_SINGLE); + else + msg.Format(GetResString(IDS_RECOVER_MULTIPLE), fileCount); + theApp.QueueLogLine(true, _T("%s"), msg); + + return retVal; +} + +bool CArchiveRecovery::readZipCentralDirectory(CFile *zipInput, CTypedPtrList *centralDirectoryEntries, CTypedPtrList *filled) +{ + bool retVal = false; + try + { + // Ideally this zip file will not have a comment and the End-CD will be easy to find + zipInput->Seek(-22, CFile::end); + if (!(readUInt32(zipInput) == ZIP_END_CD_MAGIC)) + { + // Have to look for it, comment could be up to 65535 chars but only try with less than 1k + zipInput->Seek(-1046, CFile::end); + if (!scanForZipMarker(zipInput, NULL, (uint32)ZIP_END_CD_MAGIC, 1046)) + return false; + // Skip it again + readUInt32(zipInput); + } + + // Found End-CD + // Only interested in offset of first CD + zipInput->Seek(12, CFile::current); + uint32 startOffset = readUInt32(zipInput); + if (!IsFilled(startOffset, (UINT)zipInput->GetLength(), filled)) + return false; + + // Goto first CD and start reading + zipInput->Seek(startOffset, CFile::begin); + ZIP_CentralDirectory *cdEntry; + while (readUInt32(zipInput) == ZIP_CD_MAGIC) + { + cdEntry = new ZIP_CentralDirectory; + cdEntry->versionMadeBy = readUInt16(zipInput); + cdEntry->versionToExtract = readUInt16(zipInput); + cdEntry->generalPurposeFlag = readUInt16(zipInput); + cdEntry->compressionMethod = readUInt16(zipInput); + cdEntry->lastModFileTime = readUInt16(zipInput); + cdEntry->lastModFileDate = readUInt16(zipInput); + cdEntry->crc32 = readUInt32(zipInput); + cdEntry->lenCompressed = readUInt32(zipInput); + cdEntry->lenUncompressed = readUInt32(zipInput); + cdEntry->lenFilename = readUInt16(zipInput); + cdEntry->lenExtraField = readUInt16(zipInput); + cdEntry->lenComment = readUInt16(zipInput); + cdEntry->diskNumberStart = readUInt16(zipInput); + cdEntry->internalFileAttributes = readUInt16(zipInput); + cdEntry->externalFileAttributes = readUInt32(zipInput); + cdEntry->relativeOffsetOfLocalHeader= readUInt32(zipInput); + + if (cdEntry->lenFilename > 0) + { + cdEntry->filename = new BYTE[cdEntry->lenFilename]; + zipInput->Read(cdEntry->filename, cdEntry->lenFilename); + } + if (cdEntry->lenExtraField > 0) + { + cdEntry->extraField = new BYTE[cdEntry->lenExtraField]; + zipInput->Read(cdEntry->extraField, cdEntry->lenExtraField); + } + if (cdEntry->lenComment > 0) + { + cdEntry->comment = new BYTE[cdEntry->lenComment]; + zipInput->Read(cdEntry->comment, cdEntry->lenComment); + } + + centralDirectoryEntries->AddTail(cdEntry); + } + + retVal = true; + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + return retVal; +} + +bool CArchiveRecovery::processZipEntry(CFile *zipInput, CFile *zipOutput, uint32 available, CTypedPtrList *centralDirectoryEntries) +{ + if (available < 26) + return false; + + bool retVal = false; + long startOffset=0; + + try + { + // Need to know where it started + if (zipOutput) + startOffset = (long)zipOutput->GetPosition(); + else + startOffset = (long)zipInput->GetPosition(); + + // Entry format : + // 4 2 bytes Version needed to extract + // 6 2 bytes General purpose bit flag + // 8 2 bytes Compression method + // 10 2 bytes Last mod file time + // 12 2 bytes Last mod file date + // 14 4 bytes CRC-32 + // 18 4 bytes Compressed size (n) + // 22 4 bytes Uncompressed size + // 26 2 bytes Filename length (f) + // 28 2 bytes Extra field length (e) + // (f)bytes Filename + // (e)bytes Extra field + // (n)bytes Compressed data + + // Read header + if (readUInt32(zipInput) != ZIP_LOCAL_HEADER_MAGIC) + return false; + + ZIP_Entry entry = {0}; + entry.versionToExtract = readUInt16(zipInput); + entry.generalPurposeFlag = readUInt16(zipInput); + entry.compressionMethod = readUInt16(zipInput); + entry.lastModFileTime = readUInt16(zipInput); + entry.lastModFileDate = readUInt16(zipInput); + entry.crc32 = readUInt32(zipInput); + entry.lenCompressed = readUInt32(zipInput); + entry.lenUncompressed = readUInt32(zipInput); + entry.lenFilename = readUInt16(zipInput); + entry.lenExtraField = readUInt16(zipInput); + + // Do some quick checks at this stage that data is looking ok + if ((entry.crc32 == 0) && (entry.lenCompressed == 0) && (entry.lenUncompressed == 0) && (entry.lenFilename != 0)) + ; // this is a directory entry + else if ((entry.crc32 == 0) || (entry.lenCompressed == 0) || (entry.lenUncompressed == 0) || (entry.lenFilename == 0)) + return false; + + // Is this entry complete + if ((entry.lenFilename + entry.lenExtraField + + ((zipOutput!=NULL)?entry.lenCompressed:0) + ) > (available - 26)) + { + // Move the file pointer to the start of the next entry + zipInput->Seek((entry.lenFilename + entry.lenExtraField + entry.lenCompressed), CFile::current); + return false; + } + + // Filename + if (entry.lenFilename > MAX_PATH) + return false; // Possibly corrupt, don't allocate lots of memory + entry.filename = new BYTE[entry.lenFilename]; + if (zipInput->Read(entry.filename, entry.lenFilename) != entry.lenFilename) + { + delete [] entry.filename; + return false; + } + + // Extra data + if (entry.lenExtraField > 0) + { + entry.extraField = new BYTE[entry.lenExtraField]; + zipInput->Read(entry.extraField, entry.lenExtraField); + } + + if (zipOutput) { + // Output + writeUInt32(zipOutput, ZIP_LOCAL_HEADER_MAGIC); + writeUInt16(zipOutput, entry.versionToExtract); + writeUInt16(zipOutput, entry.generalPurposeFlag); + writeUInt16(zipOutput, entry.compressionMethod); + writeUInt16(zipOutput, entry.lastModFileTime); + writeUInt16(zipOutput, entry.lastModFileDate); + writeUInt32(zipOutput, entry.crc32); + writeUInt32(zipOutput, entry.lenCompressed); + writeUInt32(zipOutput, entry.lenUncompressed); + writeUInt16(zipOutput, entry.lenFilename); + writeUInt16(zipOutput, entry.lenExtraField); + if (entry.lenFilename > 0) + zipOutput->Write(entry.filename, entry.lenFilename); + if (entry.lenExtraField > 0) + zipOutput->Write(entry.extraField, entry.lenExtraField); + + // Read and write compressed data to avoid reading all into memory + uint32 written = 0; + BYTE buf[4096]; + uint32 lenChunk = 4096; + while (written < entry.lenCompressed) + { + lenChunk = (entry.lenCompressed - written); + if (lenChunk > 4096) + lenChunk = 4096; + lenChunk = zipInput->Read(buf, lenChunk); + if (lenChunk == 0) + break; + written += lenChunk; + zipOutput->Write(buf, lenChunk); + } + zipOutput->Flush(); + } + + //Central directory: + if (centralDirectoryEntries != NULL) + { + ZIP_CentralDirectory *cdEntry = new ZIP_CentralDirectory; + cdEntry->header = ZIP_CD_MAGIC; + cdEntry->versionMadeBy = entry.versionToExtract; + cdEntry->versionToExtract = entry.versionToExtract; + cdEntry->generalPurposeFlag = entry.generalPurposeFlag; + cdEntry->compressionMethod = entry.compressionMethod; + cdEntry->lastModFileTime = entry.lastModFileTime; + cdEntry->lastModFileDate = entry.lastModFileDate; + cdEntry->crc32 = entry.crc32; + cdEntry->lenCompressed = entry.lenCompressed; + cdEntry->lenUncompressed = entry.lenUncompressed; + cdEntry->lenFilename = entry.lenFilename; + cdEntry->lenExtraField = entry.lenExtraField; + cdEntry->diskNumberStart = 0; + cdEntry->internalFileAttributes = 1; + cdEntry->externalFileAttributes = 0x81B60020; + cdEntry->relativeOffsetOfLocalHeader = startOffset; + cdEntry->filename = entry.filename; + + if (entry.lenExtraField > 0) + cdEntry->extraField = entry.extraField; + + cdEntry->lenComment = 0; + if (zipOutput!=NULL) { + cdEntry->lenComment = (uint16)strlen(ZIP_COMMENT); + cdEntry->comment = new BYTE[cdEntry->lenComment]; + memcpy(cdEntry->comment, ZIP_COMMENT, cdEntry->lenComment); + } + + centralDirectoryEntries->AddTail(cdEntry); + } + else + { + delete [] entry.filename; + if (entry.lenExtraField > 0) + delete [] entry.extraField; + } + retVal = true; + + // skip data when reading directory only + if (zipOutput==NULL) { + + // out of available data? + if ((entry.lenFilename + entry.lenExtraField + entry.lenCompressed ) > (available - 26)) + return false; + + zipInput->Seek( entry.lenCompressed, CFile::current); + } + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + return retVal; +} + +void CArchiveRecovery::DeleteMemory(ThreadParam *tp) +{ + POSITION pos = tp->filled->GetHeadPosition(); + while (pos != NULL) + delete tp->filled->GetNext(pos); + tp->filled->RemoveAll(); + delete tp->filled; + delete tp; +} + +bool CArchiveRecovery::CopyFile(CPartFile *partFile, CTypedPtrList *filled, CString tempFileName) +{ + bool retVal = false; + try + { + CFile srcFile; + if (!srcFile.Open(partFile->GetFilePath(), CFile::modeRead | CFile::shareDenyNone)) + return false; + + // Open destination file and set length to last filled end position + CFile destFile; + destFile.Open(tempFileName, CFile::modeWrite | CFile::shareDenyWrite | CFile::modeCreate); + Gap_Struct *fill = filled->GetTail(); + destFile.SetLength(fill->end); + + BYTE buffer[4096]; + uint32 read; + uint32 copied; + + // Loop through filled areas and copy data + partFile->m_bPreviewing = true; + POSITION pos = filled->GetHeadPosition(); + while (pos != NULL) + { + fill = filled->GetNext(pos); + copied = 0; + srcFile.Seek(fill->start, CFile::begin); + destFile.Seek(fill->start, CFile::begin); + while ((read = srcFile.Read(buffer, 4096)) > 0) + { + destFile.Write(buffer, read); + copied += read; + // Stop when finished fill (don't worry about extra) + if (fill->start + copied >= fill->end) + break; + } + } + destFile.Close(); + srcFile.Close(); + partFile->m_bPreviewing = false; + + retVal = true; + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + + return retVal; +} + +bool CArchiveRecovery::recoverRar(CFile *rarInput, CFile *rarOutput, archiveScannerThreadParams_s* aitp, + CTypedPtrList *filled) +{ + bool retVal = false; + long fileCount = 0; + try + { + // Try to get file header and main header + // + bool bValidFileHeader = false; + bool bOldFormat = false; + bool bValidMainHeader = false; + BYTE fileHeader[7] = {0}; + RARMAINHDR mainHeader = {0}; + if (rarInput->Read(fileHeader, sizeof fileHeader) == sizeof fileHeader) + { + if (fileHeader[0] == 0x52) { + if (fileHeader[1] == 0x45 && fileHeader[2] ==0x7e && fileHeader[3] == 0x5e) { + bOldFormat = true; + bValidFileHeader = true; + } + else if (fileHeader[1] == 0x61 && fileHeader[2] == 0x72 && fileHeader[3] == 0x21 && fileHeader[4] == 0x1a && fileHeader[5] == 0x07 && fileHeader[6] == 0x00) { + bValidFileHeader = true; + } + } + + if (bValidFileHeader && !bOldFormat) + { + WORD checkCRC; + if (rarInput->Read(&checkCRC, sizeof checkCRC) == sizeof checkCRC) + { + if (rarInput->Read(&mainHeader, sizeof mainHeader) == sizeof mainHeader) + { + if (mainHeader.type == 0x73) + { + DWORD crc = crc32(0, (Bytef*)&mainHeader, sizeof mainHeader); + for (UINT i = 0; i < sizeof(WORD) + sizeof(DWORD); i++) + { + BYTE ch; + if (rarInput->Read(&ch, sizeof ch) != sizeof ch) + break; + crc = crc32(crc, &ch, 1); + } + if (checkCRC == (WORD)crc) + bValidMainHeader = true; + } + } + } + } + rarInput->SeekToBegin(); + } + + static const BYTE start[] = { + // RAR file header + 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00, + + // main header + 0x08, 0x1A, // CRC + 0x73, // type + 0x02, 0x00, // flags + 0x3B, 0x00, // size + 0x00, 0x00, // AV + 0x00, 0x00, // AV + 0x00, 0x00, // AV + + // main comment + 0xCA, 0x44, // CRC + 0x75, // type + 0x00, 0x00, // flags + 0x2E, 0x00, // size + + 0x12, 0x00, 0x14, 0x34, 0x2B, + 0x4A, 0x08, 0x15, 0x48, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x2B, 0xF9, 0x0E, 0xE2, 0xC1, + 0x32, 0xFB, 0x9E, 0x04, 0x10, 0x50, 0xD7, 0xFE, 0xCD, 0x75, 0x87, 0x9C, 0x28, 0x85, 0xDF, 0xA3, + 0x97, 0xE0 }; + + // If this is a 'solid' archive the chance to successfully decompress any entries gets higher, + // when we pass the 'solid' main header bit to the temp. archive. + BYTE start1[sizeof start]; + memcpy(start1, start, sizeof start); + if (bValidFileHeader && bValidMainHeader && (mainHeader.flags & 0x0008/*MHD_SOLID*/)) { + start1[10] |= 8; /*MHD_SOLID*/ + *((short*)&start1[7]) = (short)crc32(0, &start1[9], 11); + } + + if (aitp) + aitp->ai->rarFlags=mainHeader.flags; + + if (rarOutput) + rarOutput->Write(start1, sizeof(start1)); + + RAR_BlockFile *block; + + // loop filled blocks + POSITION pos = filled->GetHeadPosition(); + Gap_Struct *fill; + while (pos != NULL) + { + fill = filled->GetNext(pos); + + rarInput->Seek(fill->start , CFile::begin); + + while ((block = scanForRarFileHeader(rarInput, aitp, (fill->end - rarInput->GetPosition() ))) != NULL) + { + if (aitp){ + aitp->ai->RARdir->AddTail(block); + if (!aitp->m_bIsValid) + return false; + } + + if (rarOutput!=NULL && IsFilled((UINT)block->offsetData, (UINT)(block->offsetData + block->dataLength), filled)) + { + // Don't include directories in file count + if ((block->HEAD_FLAGS & 0xE0) != 0xE0) + fileCount++; + if (rarOutput) + writeRarBlock(rarInput, rarOutput, block); + } + else + { + rarInput->Seek(block->offsetData + block->dataLength, CFile::begin); + } + if (aitp==NULL) { + delete [] block->FILE_NAME; + delete block; + } + if (rarInput->GetPosition() >=fill->end) + break; + } + } + retVal = true; + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + + // Tell the user how many files were recovered + if (aitp==NULL) { + CString msg; + if (fileCount == 1) + msg = GetResString(IDS_RECOVER_SINGLE); + else + msg.Format(GetResString(IDS_RECOVER_MULTIPLE), fileCount); + theApp.QueueLogLine(true, _T("%s"), msg); + } + + return retVal; +} + +bool CArchiveRecovery::IsFilled(uint32 start, uint32 end, CTypedPtrList *filled) +{ + POSITION pos = filled->GetHeadPosition(); + Gap_Struct *fill; + while (pos != NULL) + { + fill = filled->GetNext(pos); + if (fill->start > start) + return false; + if (fill->end >= end) + return true; + } + return false; +} + +// This will find the marker in the file and leave it positioned at the position to read the marker again +bool CArchiveRecovery::scanForZipMarker(CFile *input, archiveScannerThreadParams_s* aitp, uint32 marker, uint32 available) +{ + try + { + //uint32 originalOffset = input->GetPosition(); + int lenChunk = 51200; // 50k buffer + BYTE chunk[51200]; + BYTE *foundPos = NULL; + int pos = 0; + + while ((available > 0) && ((lenChunk = input->Read(chunk, lenChunk)) > 0)) + { + available -= lenChunk; + foundPos = &chunk[0]; + // Move back one, will be incremented in loop + foundPos--; + while (foundPos != NULL) + { + if (aitp && !aitp->m_bIsValid) + return false; + + // Find first matching byte + foundPos = (BYTE*)memchr( foundPos+1, (marker & 0xFF), (lenChunk - (foundPos+1 - (&chunk[0]))) ); + if (foundPos == NULL) + break; + + // Test for end of buffer + pos = foundPos - (&chunk[0]); + if ((pos + 3) > lenChunk) + { + // Re-read buffer starting from found first byte position + input->Seek(pos - lenChunk, CFile::current); + break; + } + + if (aitp) { + ProcessProgress(aitp, input->GetPosition() ); + } + + + // Check for other bytes + if (chunk[pos + 1] == ((marker >> 8) & 0xFF)) + { + if (chunk[pos + 2] == ((marker >> 16) & 0xFF)) + { + if (chunk[pos + 3] == ((marker >> 24) & 0xFF)) + { + // Found it + input->Seek(pos - lenChunk, CFile::current); + return true; + } + } + } + } + } + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + return false; +} + +#define TESTCHUNKSIZE 51200 // 50k buffer +// This will find a file block in the file and leave it positioned at the end of the filename +RAR_BlockFile *CArchiveRecovery::scanForRarFileHeader(CFile *input, archiveScannerThreadParams_s* aitp, UINT64 available) +{ + RAR_BlockFile *retVal = NULL; + try + { + UINT lenChunk; + BYTE chunk[TESTCHUNKSIZE]; + BYTE *foundPos = NULL; + ULONGLONG foundOffset; + ULONGLONG chunkOffset; + UINT64 chunkstart; + + uint16 headCRC; + BYTE checkCRC[sizeof(RARFILEHDR) + 8 + sizeof(DWORD)*2 + 512]; + unsigned checkCRCsize = 0; + uint16 lenFileName; + BYTE *fileName; + uint32 crc; + + while (available > 0) + { + chunkstart=input->GetPosition(); + lenChunk = input->Read(chunk, (UINT)(min(available, TESTCHUNKSIZE)) ); + if (lenChunk==0) + break; + + available-=lenChunk; + foundPos = &chunk[0]; + + // Move back one, will be incremented in loop + foundPos--; + while (foundPos != NULL) + { + if (aitp && !aitp->m_bIsValid) + return false; + + // Find rar head block marker + foundPos = (BYTE*)memchr( foundPos+1, RAR_HEAD_FILE, (lenChunk - (foundPos+1 - (&chunk[0])) ) ); + if (foundPos == NULL) + break; + + chunkOffset = foundPos - (&chunk[0]); + foundOffset = chunkstart + chunkOffset; + + if (aitp) { + ProcessProgress(aitp,foundOffset); + } + + // Move back 2 bytes to get crc and read block + input->Seek(foundOffset-2 , CFile::begin); + + // CRC of fields from HEAD_TYPE to ATTR + filename + ext. stuff + headCRC = readUInt16(input); + + RARFILEHDR* hdr = (RARFILEHDR*)checkCRC; + input->Read(checkCRC, sizeof(*hdr)); + + // this bit always is set + if (!(hdr->flags & 0x8000)) + continue; + + checkCRCsize = sizeof(*hdr); + + // get high parts of 64-bit file size fields + if (hdr->flags & 0x0100/*LHD_LARGE*/) { + input->Read(&checkCRC[checkCRCsize], sizeof(DWORD) * 2); + checkCRCsize += sizeof(DWORD) * 2; + } + + // get filename + lenFileName = hdr->nameSize; + fileName = new BYTE[lenFileName]; + input->Read(fileName, lenFileName); + + // get encryption params + unsigned saltPos = 0; + if (hdr->flags & 0x0400/*LHD_SALT*/) { + saltPos = checkCRCsize; + input->Read(&checkCRC[checkCRCsize], 8); + checkCRCsize += 8; + } + + // get ext. file date/time + unsigned extTimePos = 0; + unsigned extTimeSize = 0; + if (hdr->flags & 0x1000/*LHD_EXTTIME*/) + { + try { + extTimePos = checkCRCsize; + if (checkCRCsize + sizeof(WORD) > sizeof(checkCRC)) + throw -1; + input->Read(&checkCRC[checkCRCsize], sizeof(WORD)); + unsigned short Flags = *((WORD*)&checkCRC[checkCRCsize]); + checkCRCsize += sizeof(WORD); + for (int i = 0; i < 4; i++) + { + unsigned int rmode = Flags >> (3 - i) * 4; + if ((rmode & 8) == 0) + continue; + if (i != 0) { + if (checkCRCsize + sizeof(DWORD) > sizeof(checkCRC)) + throw -1; + input->Read(&checkCRC[checkCRCsize], sizeof(DWORD)); + checkCRCsize += sizeof(DWORD); + } + int count = rmode & 3; + for (int j = 0; j < count; j++) { + if (checkCRCsize + sizeof(BYTE) > sizeof(checkCRC)) + throw -1; + input->Read(&checkCRC[checkCRCsize], sizeof(BYTE)); + checkCRCsize += sizeof(BYTE); + } + } + extTimeSize = checkCRCsize - extTimePos; + } + catch (int ex) { + (void)ex; + extTimePos = 0; + extTimeSize = 0; + } + } + + crc = crc32(0, checkCRC, sizeof(*hdr)); + crc = crc32(crc, fileName, lenFileName); + if (checkCRCsize > sizeof(*hdr)) + crc = crc32(crc, &checkCRC[sizeof(*hdr)], checkCRCsize - sizeof(*hdr)); + if ((crc & 0xFFFF) == headCRC) + { + // Found valid crc, build block and return + // Note that it may still be invalid data, so more checks should be performed + retVal = new RAR_BlockFile; + retVal->HEAD_CRC = headCRC; + retVal->HEAD_TYPE = 0x74; + retVal->HEAD_FLAGS = calcUInt16(&checkCRC[ 1]); + retVal->HEAD_SIZE = calcUInt16(&checkCRC[ 3]); + retVal->PACK_SIZE = calcUInt32(&checkCRC[ 5]); + retVal->UNP_SIZE = calcUInt32(&checkCRC[ 9]); + retVal->HOST_OS = checkCRC[13]; + retVal->FILE_CRC = calcUInt32(&checkCRC[14]); + retVal->FTIME = calcUInt32(&checkCRC[18]); + retVal->UNP_VER = checkCRC[22]; + retVal->METHOD = checkCRC[23]; + retVal->NAME_SIZE = lenFileName; + retVal->ATTR = calcUInt32(&checkCRC[26]); + // Optional values, present only if bit 0x100 in HEAD_FLAGS is set. + if ((retVal->HEAD_FLAGS & 0x100) == 0x100) { + retVal->HIGH_PACK_SIZE = calcUInt32(&checkCRC[30]); + retVal->HIGH_UNP_SIZE = calcUInt32(&checkCRC[34]); + }else { + retVal->HIGH_PACK_SIZE=0; + retVal->HIGH_UNP_SIZE=0; + } + retVal->FILE_NAME = fileName; + if (saltPos != 0) + memcpy(retVal->SALT, &checkCRC[saltPos], sizeof retVal->SALT); + if (extTimePos != 0 && extTimeSize != 0) { + retVal->EXT_DATE = new BYTE[extTimeSize]; + memcpy(retVal->EXT_DATE, &checkCRC[extTimePos], extTimeSize); + retVal->EXT_DATE_SIZE = extTimeSize; + } + + // Run some quick checks + if (validateRarFileBlock(retVal)) + { + // Set some useful markers in the block + retVal->offsetData = input->GetPosition(); + uint32 dataLength = retVal->PACK_SIZE; + // If comment present find length + if ((retVal->HEAD_FLAGS & 0x08) == 0x08) + { + // Skip start of comment block + input->Seek(5, CFile::current); + // Read comment length + dataLength += readUInt16(input); + } + retVal->dataLength = dataLength; + + return retVal; + } + } + // If not valid, continue searching where we left of + delete [] fileName; + delete retVal; + retVal = NULL; + } + } + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + return false; +} + +// This assumes that head crc has already been checked +bool CArchiveRecovery::validateRarFileBlock(RAR_BlockFile *block) +{ + if (block->HEAD_TYPE != 0x74) + return false; + + // The following condition basically makes sense, but it though triggers false errors + // in some cases (e.g. when there are very small files compressed it's not that unlikely + // that the compressed size is a little larger than the uncompressed size due to RAR + // overhead. + //if ((block->HEAD_FLAGS & 0x0400/*LHD_SALT*/) == 0 && block->UNP_SIZE < block->PACK_SIZE) + // return false; + + if (block->HOST_OS > 5) + return false; + + switch (block->METHOD) + { + case 0x30: // storing + case 0x31: // fastest compression + case 0x32: // fast compression + case 0x33: // normal compression + case 0x34: // good compression + case 0x35: // best compression + break; + default: + return false; + } + + if (block->HEAD_FLAGS & 0x0200) { + // ANSI+Unicode name + if (block->NAME_SIZE > MAX_PATH + MAX_PATH*2) + return false; + } + else { + // ANSI+Unicode name + if (block->NAME_SIZE > MAX_PATH) + return false; + } + + // Check directory entry has no size + if (((block->HEAD_FLAGS & 0xE0) == 0xE0) && ((block->PACK_SIZE + block->UNP_SIZE + block->FILE_CRC) > 0)) + return false; + + return true; +} + +void CArchiveRecovery::writeRarBlock(CFile *input, CFile *output, RAR_BlockFile *block) +{ + ULONGLONG offsetStart = output->GetPosition(); + try + { + writeUInt16(output, block->HEAD_CRC); + output->Write(&block->HEAD_TYPE, 1); + writeUInt16(output, block->HEAD_FLAGS); + writeUInt16(output, block->HEAD_SIZE); + writeUInt32(output, block->PACK_SIZE); + writeUInt32(output, block->UNP_SIZE); + output->Write(&block->HOST_OS, 1); + writeUInt32(output, block->FILE_CRC); + writeUInt32(output, block->FTIME); + output->Write(&block->UNP_VER, 1); + output->Write(&block->METHOD, 1); + writeUInt16(output, block->NAME_SIZE); + writeUInt32(output, block->ATTR); + // Optional values, present only if bit 0x100 in HEAD_FLAGS is set. + if ((block->HEAD_FLAGS & 0x100) == 0x100) { + writeUInt32(output, block->HIGH_PACK_SIZE); + writeUInt32(output, block->HIGH_UNP_SIZE); + } + output->Write(block->FILE_NAME, block->NAME_SIZE); + if (block->HEAD_FLAGS & 0x0400/*LHD_SALT*/) + output->Write(block->SALT, sizeof block->SALT); + output->Write(block->EXT_DATE, block->EXT_DATE_SIZE); + + // Now copy compressed data from input file + uint32 lenToCopy = block->dataLength; + if (lenToCopy > 0) + { + input->Seek(block->offsetData, CFile::begin); + uint32 written = 0; + BYTE chunk[4096]; + uint32 lenChunk = 4096; + while (written < lenToCopy) + { + lenChunk = (lenToCopy - written); + if (lenChunk > 4096) + lenChunk = 4096; + lenChunk = input->Read(chunk, lenChunk); + if (lenChunk == 0) + break; + written += lenChunk; + output->Write(chunk, lenChunk); + } + } + output->Flush(); + } + catch (CFileException* error){ + error->Delete(); + try { output->SetLength(offsetStart); } catch (...) {ASSERT(0);} + } + catch (...){ + ASSERT(0); + try { output->SetLength(offsetStart); } catch (...) {ASSERT(0);} + } +} + + +//################################ ACE #################################### +#define CRC_MASK 0xFFFFFFFFL +#define CRCPOLY 0xEDB88320L +static ULONG crctable[256]; +void make_crctable(void) // initializes CRC table +{ + ULONG r,i,j; + + for (i = 0; i <= 255; i++) + { + for (r = i, j = 8; j; j--) + r = (r & 1) ? (r >> 1) ^ CRCPOLY : (r >> 1); + crctable[i] = r; + } +} + +// Updates crc from addr till addr+len-1 +// +ULONG getcrc(ULONG crc, UCHAR * addr, INT len) +{ + while (len--) + crc = crctable[(unsigned char) crc ^ (*addr++)] ^ (crc >> 8); + return (crc); +} + +bool CArchiveRecovery::recoverAce(CFile *aceInput, CFile *aceOutput, archiveScannerThreadParams_s* aitp, + CTypedPtrList *filled) +{ + UINT64 filesearchstart=0; + bool retVal=false; + ACE_ARCHIVEHEADER* acehdr=NULL; + + make_crctable(); + + try + { + // Try to get file header and main header + if (IsFilled(0,32,filled)) { + static const char ACE_ID[] = { 0x2A, 0x2A, 0x41, 0x43, 0x45, 0x2A, 0x2A }; + acehdr=new ACE_ARCHIVEHEADER; + + LONG headcrc; + UINT hdrread=0; + + hdrread+=aceInput->Read((void*)acehdr, sizeof(ACE_ARCHIVEHEADER) - (3*sizeof(char*)) - sizeof(uint16) ); + + if (memcmp(acehdr->HEAD_SIGN , ACE_ID, sizeof(ACE_ID) )!=0 || acehdr->HEAD_TYPE!=0 || + IsFilled(0,acehdr->HEAD_SIZE,filled)==false ) { + delete acehdr; + acehdr=NULL; + if (aceOutput) + return false; + + }else { + + hdrread-= 2*sizeof(uint16); // care for the size that is specified with HEADER_SIZE + headcrc = getcrc(CRC_MASK, (BYTE*)&acehdr->HEAD_TYPE, hdrread ); + + if (acehdr->AVSIZE) { + acehdr->AV=(char*)calloc(acehdr->AVSIZE+1,1); + hdrread+=aceInput->Read(acehdr->AV, acehdr->AVSIZE); + headcrc = getcrc(headcrc, (UCHAR*)acehdr->AV, acehdr->AVSIZE); + } + + if (hdrreadHEAD_SIZE) { + + hdrread+=aceInput->Read(&acehdr->COMMENT_SIZE, sizeof(acehdr->COMMENT_SIZE)); + headcrc = getcrc(headcrc, (UCHAR*)(&acehdr->COMMENT_SIZE), sizeof(acehdr->COMMENT_SIZE) ); + if (acehdr->COMMENT_SIZE){ + acehdr->COMMENT=(char*)calloc(acehdr->COMMENT_SIZE+1,1); + hdrread+=aceInput->Read(acehdr->COMMENT, acehdr->COMMENT_SIZE); + headcrc = getcrc(headcrc, (UCHAR*)acehdr->COMMENT, acehdr->COMMENT_SIZE ); + } + } + + // some unknown data left to read + if (hdrreadHEAD_SIZE) { + UINT dumpsize=acehdr->HEAD_SIZE-hdrread; + acehdr->DUMP=(char*)malloc(dumpsize); + hdrread+=aceInput->Read(acehdr->DUMP , dumpsize); + headcrc = getcrc(headcrc, (UCHAR*)acehdr->DUMP, dumpsize ); + } + + if (acehdr->HEAD_CRC == (headcrc & 0xffff)) { + if (aitp) + aitp->ai->ACEhdr=acehdr; + filesearchstart=acehdr->HEAD_SIZE + 2*sizeof(uint16); + if (aceOutput) + writeAceHeader(aceOutput,acehdr); + + } else { + aceInput->SeekToBegin(); + filesearchstart=0; + delete acehdr; + acehdr=NULL; + } + } + } + + if (aceOutput && !acehdr) + return false; + if(aitp == NULL && acehdr != NULL) + { + delete acehdr; + acehdr = NULL; + } + + + ACE_BlockFile *block; + + // loop filled blocks + POSITION pos = filled->GetHeadPosition(); + Gap_Struct *fill; + while (pos != NULL) + { + fill = filled->GetNext(pos); + + filesearchstart = (filesearchstartstart)?fill->start:filesearchstart; + aceInput->Seek( filesearchstart, CFile::begin); + + while ((block = scanForAceFileHeader(aceInput, aitp, (fill->end - filesearchstart ))) != NULL) + { + if (aitp){ + if (!aitp->m_bIsValid) + return false; + + aitp->ai->ACEdir->AddTail(block); + } + + + if (aceOutput!=NULL && IsFilled((UINT)block->data_offset, (UINT)(block->data_offset + block->PACK_SIZE), filled)) + { + // Don't include directories in file count + //if ((block->HEAD_FLAGS & 0xE0) != 0xE0) fileCount++; + writeAceBlock(aceInput, aceOutput, block); + } + else + { + aceInput->Seek(block->PACK_SIZE, CFile::current); + } + if (aitp==NULL) { + delete block; + } + if (aceInput->GetPosition() >=fill->end) + break; + } + } + retVal = true; + + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + + return retVal; +} + +#define MAXACEHEADERSIZE 10240 +ACE_BlockFile *CArchiveRecovery::scanForAceFileHeader(CFile *input, archiveScannerThreadParams_s* aitp, UINT64 available) +{ + static char blockmem[MAXACEHEADERSIZE]; + + BYTE chunk[TESTCHUNKSIZE]; + UINT lenChunk; + BYTE *foundPos = NULL; + ULONGLONG foundOffset; + ULONGLONG chunkOffset; + UINT64 chunkstart; + uint16 headCRC,headSize; + uint32 crc; + + + try + { + while (available > 0) + { + chunkstart=input->GetPosition(); + lenChunk = input->Read(chunk, (UINT)(min(available, TESTCHUNKSIZE)) ); + if (lenChunk==0) + break; + + available-=lenChunk; + foundPos = &chunk[0]; + + // Move back one, will be incremented in loop + foundPos--; + while (foundPos != NULL) + { + if (aitp && !aitp->m_bIsValid) + return false; + + // Find rar head block marker + foundPos = (BYTE*)memchr( foundPos+1, 0x01, (lenChunk - (foundPos+1 - (&chunk[0])) ) ); + if (foundPos == NULL) + break; + + chunkOffset = foundPos - (&chunk[0]); + foundOffset = chunkstart + chunkOffset; + + if (chunkOffset<4) + continue; + + if (aitp) { + ProcessProgress(aitp,foundOffset); + } + + + // Move back 4 bytes to get crc,size and read block + input->Seek(foundOffset-4 , CFile::begin); + + input->Read(&headCRC, 2); + input->Read(&headSize,2); + if (headSize>MAXACEHEADERSIZE || // header limit + (lenChunk-chunkOffset)+available < headSize // header too big for my filled part + ) + continue; + + input->Read(&blockmem[0],headSize); + crc = getcrc(CRC_MASK, (BYTE*)blockmem,headSize); + + if ( (crc & 0xFFFF) != headCRC ) + continue; + + char* mempos= &blockmem[0]; + + ACE_BlockFile *newblock = new ACE_BlockFile; + + newblock->HEAD_CRC=headCRC; + newblock->HEAD_SIZE=headSize; + + memcpy(&newblock->HEAD_TYPE, + mempos, + sizeof(ACE_BlockFile)- 4- (2*sizeof(newblock->COMMENT)) - sizeof(newblock->COMM_SIZE) - sizeof(newblock->data_offset) ); + + mempos+=sizeof(ACE_BlockFile)-sizeof(newblock->HEAD_CRC)-sizeof(newblock->HEAD_SIZE) - (2*sizeof(newblock->COMMENT)) - sizeof(newblock->COMM_SIZE) - sizeof(newblock->data_offset); + + // basic checks + if (newblock->HEAD_SIZE < newblock->FNAME_SIZE) { + delete newblock; + continue; + } + + if (newblock->FNAME_SIZE>0) { + newblock->FNAME = (char*)calloc(newblock->FNAME_SIZE+1,1); + memcpy(newblock->FNAME, mempos,newblock->FNAME_SIZE); + mempos+=newblock->FNAME_SIZE; + } + +// int blockleft=newblock->HEAD_SIZE - newblock->FNAME_SIZE - (sizeof(ACE_BlockFile)- (3*sizeof(uint16)) - sizeof(newblock->data_offset) - 2* sizeof(char*) ) ; + + if (mempos < &blockmem[0] + newblock->HEAD_SIZE) { + memcpy(&newblock->COMM_SIZE, mempos, sizeof(newblock->COMM_SIZE)); + mempos+=sizeof(newblock->COMM_SIZE); + + if (newblock->COMM_SIZE) { + newblock->COMMENT = (char*)calloc(newblock->COMM_SIZE,1); + memcpy(newblock->COMMENT, mempos,newblock->COMM_SIZE); + mempos+=newblock->COMM_SIZE; + } + } + + //if (mempos-blockmem[4] > 0) input->Seek(blockleft, CFile::current) ; + + + newblock->data_offset= input->GetPosition(); + return newblock; + + } // while foundpos + } // while available>0 + } + catch (CFileException* error){ + error->Delete(); + } + catch (...){ + ASSERT(0); + } + + return NULL; +} + +void CArchiveRecovery::writeAceHeader(CFile *output, ACE_ARCHIVEHEADER* hdr) { + + writeUInt16(output, hdr->HEAD_CRC); + writeUInt16(output, hdr->HEAD_SIZE); + output->Write(&hdr->HEAD_TYPE, 1); + writeUInt16(output, hdr->HEAD_FLAGS); + output->Write(&hdr->HEAD_SIGN, sizeof(hdr->HEAD_SIGN)); + output->Write(&hdr->VER_EXTRACT, 1); + output->Write(&hdr->VER_CREATED, 1); + output->Write(&hdr->HOST_CREATED, 1); + output->Write(&hdr->VOLUME_NUM, 1); + writeUInt32(output, hdr->FTIME); + output->Write(&hdr->RESERVED, sizeof(hdr->RESERVED)); + output->Write(&hdr->AVSIZE, 1); + + int rest= hdr->HEAD_SIZE - (sizeof(ACE_ARCHIVEHEADER)- (3*sizeof(uint16)) - (3*sizeof(char*))); + + if (hdr->AVSIZE>0){ + output->Write(hdr->AV, hdr->AVSIZE); + rest-=hdr->AVSIZE; + } + + if (hdr->COMMENT_SIZE) { + writeUInt16(output, hdr->COMMENT_SIZE); + rest-=sizeof(hdr->COMMENT_SIZE); + + output->Write(hdr->COMMENT, hdr->COMMENT_SIZE); + rest-=hdr->COMMENT_SIZE; + } + + if ( rest && hdr->DUMP ) { + output->Write(hdr->DUMP,rest); + rest=0; + } + ASSERT(rest==0); +} + +void CArchiveRecovery::writeAceBlock(CFile *input, CFile *output, ACE_BlockFile *block) +{ + ULONGLONG offsetStart = output->GetPosition(); + try + { +// block->data_offset = offsetStart; + + + writeUInt16(output, block->HEAD_CRC); + writeUInt16(output, block->HEAD_SIZE); + output->Write(&block->HEAD_TYPE, 1); + writeUInt16(output, block->HEAD_FLAGS); + writeUInt32(output, block->PACK_SIZE); + writeUInt32(output, block->ORIG_SIZE); + writeUInt32(output, block->FTIME); + writeUInt32(output, block->FILE_ATTRIBS); + writeUInt32(output, block->CRC32); + writeUInt32(output, block->TECHINFO); + writeUInt16(output, block->RESERVED); + writeUInt16(output, block->FNAME_SIZE); + output->Write(block->FNAME, block->FNAME_SIZE); + + if (block->COMM_SIZE) { + writeUInt16(output, block->COMM_SIZE); + output->Write(block->COMMENT, block->COMM_SIZE); + } + + // skip unknown data between header and compressed data - if ever existed... + + // Now copy compressed data from input file + uint32 lenToCopy = block->PACK_SIZE; + if (lenToCopy > 0) + { + input->Seek(block->data_offset, CFile::begin); + uint32 written = 0; + BYTE chunk[4096]; + uint32 lenChunk = 4096; + while (written < lenToCopy) + { + lenChunk = (lenToCopy - written); + if (lenChunk > 4096) + lenChunk = 4096; + lenChunk = input->Read(chunk, lenChunk); + if (lenChunk == 0) + break; + written += lenChunk; + output->Write(chunk, lenChunk); + } + } + + output->Flush(); + } + catch (CFileException* error){ + error->Delete(); + try { output->SetLength(offsetStart); } catch (...) {ASSERT(0);} + } + catch (...){ + ASSERT(0); + try { output->SetLength(offsetStart); } catch (...) {ASSERT(0);} + } +} + +// ############### ISO handling ############# +// ISO, reads a directory entries of a directory at the given sector (startSec) + +void CArchiveRecovery::ISOReadDirectory(archiveScannerThreadParams_s* aitp, UINT32 startSec, CFile* isoInput, CString currentDirName) +{ + // read directory entries + int iSecsOfDirectoy = -1; + UINT32 blocksize; + + if (!IsFilled( startSec * aitp->ai->isoInfos.secSize, (startSec * aitp->ai->isoInfos.secSize) + aitp->ai->isoInfos.secSize, + aitp->filled)) + return; + + isoInput->Seek(startSec * aitp->ai->isoInfos.secSize, FILE_BEGIN); + + while (aitp->m_bIsValid) + { + ISO_FileFolderEntry *file = new ISO_FileFolderEntry; + + blocksize = isoInput->Read(file, sizeof(ISO_FileFolderEntry)-sizeof(file->name) ); + + if (file->lenRecord==0) { + delete file; + + // do we continue at next sector? + if (iSecsOfDirectoy-- > 1) { + startSec++; + if (!IsFilled( startSec * aitp->ai->isoInfos.secSize, (startSec * aitp->ai->isoInfos.secSize) + aitp->ai->isoInfos.secSize, + aitp->filled)) + break; + + isoInput->Seek(startSec * aitp->ai->isoInfos.secSize, FILE_BEGIN); + continue; + } else + break; // folder end + } + + file->name = (TCHAR*)calloc( file->nameLen+2, sizeof(TCHAR*)); + + blocksize += isoInput->Read(file->name, file->nameLen ); + + if (file->nameLen % 2 ==0 ) + blocksize++; + + UINT32 skip=LODWORD(file->lenRecord) - blocksize; + UINT64 pos2 = isoInput->Seek(skip, FILE_CURRENT); // skip padding + if (pos2 % 2 ){ + isoInput->Seek(1, FILE_CURRENT); // skip padding + pos2++; + } + + // set progressbar + if (aitp) + ProcessProgress(aitp, pos2); + + // selfdir, parentdir ( "." && ".." ) handling + if ((file->fileFlags & ISO_DIRECTORY) && file->nameLen==1 && (file->name[0]==0x00 || file->name[0]==0x01 ) ) + { + // get size of directory and calculate how many sectors are spanned + if (file->name[0]==0x00) + iSecsOfDirectoy = (int)(file->dataSize / aitp->ai->isoInfos.secSize); + delete file; + continue; + } + + if (aitp->ai->isoInfos.iJolietUnicode){ + for (unsigned int i = 0; i < file->nameLen/sizeof(uint16); i++) + file->name[i] = _byteswap_ushort(file->name[i]); + } + + // make filename Cstring from ascii or unicode + CString filename; + if (aitp->ai->isoInfos.iJolietUnicode==0) + filename = CString((char*)file->name); + else + filename = CString(file->name); + + if (file->fileFlags & ISO_DIRECTORY) + { + // store dir entry + CString pathNew; + pathNew = currentDirName + filename + _T("\\"); + free(file->name); + file->name = _tcsdup(pathNew); + aitp->ai->ISOdir->AddTail(file); + + // read subdirectory recursively + LONGLONG curpos = isoInput->GetPosition(); + ISOReadDirectory(aitp, LODWORD(file->sector1OfExtension), isoInput, pathNew); + isoInput->Seek(curpos,FILE_BEGIN); + } + else + { + // store file entry + CString fullpath; + fullpath = currentDirName + filename; + free(file->name); + file->name = _tcsdup(fullpath); + aitp->ai->ISOdir->AddTail(file); + } + } +} + +bool CArchiveRecovery::recoverISO(CFile *isoInput, CFile *isoOutput, archiveScannerThreadParams_s* aitp, + CTypedPtrList *filled) +{ + if (isoOutput) + return false; + + aitp->ai->isoInfos.secSize = sizeof(ISO_PVD_s); + + // do we have the primary volume descriptor ? + if (!IsFilled(16*aitp->ai->isoInfos.secSize, 17*aitp->ai->isoInfos.secSize, filled)) + return false; + + ISO_PVD_s pvd, svd, tempSec; + + // skip to PVD + UINT32 nextstart = 16 * aitp->ai->isoInfos.secSize; + isoInput->Seek(nextstart, FILE_BEGIN); + + pvd.descr_type=0xff; + svd.descr_type=0xff; + aitp->ai->isoInfos.type = ISOtype_unknown; + + int iUdfDetectState=0; + // read PVD + do + { + isoInput->Read(&tempSec, aitp->ai->isoInfos.secSize); + nextstart+=aitp->ai->isoInfos.secSize; + + if (tempSec.descr_type==0xff || (tempSec.descr_type==0 && tempSec.descr_ver==0)) // Volume Descriptor Set Terminator (VDST) + break; + + if (tempSec.descr_type==0x01 && pvd.descr_type==0xff) { + memcpy(&pvd, &tempSec, aitp->ai->isoInfos.secSize); + aitp->ai->isoInfos.type |= ISOtype_9660; + } + + if (tempSec.descr_type==0x02) { + memcpy(&svd, &tempSec, aitp->ai->isoInfos.secSize); + if (svd.escSeq[0]==0x25 && svd.escSeq[1]==0x2f) + { + aitp->ai->isoInfos.type |= ISOtype_joliet; + + if (svd.escSeq[2]==0x40) + aitp->ai->isoInfos.iJolietUnicode = 1; + else if (svd.escSeq[2]==0x43) + aitp->ai->isoInfos.iJolietUnicode = 2; + else if (svd.escSeq[2]==0x45) + aitp->ai->isoInfos.iJolietUnicode = 3; + } + } + + if (tempSec.descr_type==0x00) { + BootDescr* bDesc = (BootDescr*)&tempSec; + if ( memcmp((const char*)bDesc->sysid, sElToritoID, strlen(sElToritoID))==0) + aitp->ai->isoInfos.bBootable = true; // anything else? + } + + // check for udf + if (tempSec.descr_type==0x00 && tempSec.descr_ver==0x01 ) { + + if (memcmp(&tempSec.magic, sig_udf_bea, 5)==0 && iUdfDetectState==0) + iUdfDetectState=1;// detected Beginning Extended Area Descriptor (BEA) + + else if (memcmp(&tempSec.magic, sig_udf_nsr2, 5)==0 && iUdfDetectState==1) // Volume Sequence Descriptor (VSD) 2 + iUdfDetectState=2; + + else if (memcmp(&tempSec.magic, sig_udf_nsr3, 5)==0 && iUdfDetectState==1) // Volume Sequence Descriptor (VSD) 3 + iUdfDetectState=3; + + else if (memcmp(&tempSec.magic, sig_tea, 5)==0 && (iUdfDetectState==2 || iUdfDetectState==3)) + iUdfDetectState+=2; // remember Terminating Extended Area Descriptor (TEA) received + } + + } while ( IsFilled(nextstart, nextstart + aitp->ai->isoInfos.secSize, filled )); + + if (iUdfDetectState==4) + aitp->ai->isoInfos.type |= ISOtype_UDF_nsr02; + else if (iUdfDetectState==5) + aitp->ai->isoInfos.type |= ISOtype_UDF_nsr03; + + if (aitp->ai->isoInfos.type == 0) + return false; + + if (iUdfDetectState==4 || iUdfDetectState==5) + { + // TODO: UDF handling (http://www.osta.org/specs/) + return false; // no known udf version + } + else + { + // ISO9660/Joliet handling + + // read root directory of iso and recursive + ISO_FileFolderEntry rootdir; + memcpy(&rootdir, svd.descr_type!=0xff?svd.rootdir:pvd.rootdir, 33); + + ISOReadDirectory(aitp, LODWORD(rootdir.sector1OfExtension), isoInput, _T("") ); + } + + return true; +} + +// ########## end of ISO handling ############# + + +uint16 CArchiveRecovery::readUInt16(CFile *input) +{ + uint16 retVal = 0; + BYTE b[2]; + if (input->Read(b, 2) > 0) + retVal = (b[1] << 8) + b[0]; + return retVal; +} + +uint32 CArchiveRecovery::readUInt32(CFile *input) +{ + uint32 retVal = 0; + BYTE b[4]; + if (input->Read(b, 4) > 0) + retVal = (b[3] << 24) + (b[2] << 16) + (b[1] << 8) + b[0]; + return retVal; +} + +uint16 CArchiveRecovery::calcUInt16(BYTE *input) +{ + return (((uint16)input[1]) << 8) + ((uint16)input[0]); +} + +uint32 CArchiveRecovery::calcUInt32(BYTE *input) +{ + return (((uint32)input[3]) << 24) + (((uint32)input[2]) << 16) + (((uint32)input[1]) << 8) + ((uint32)input[0]); +} + +void CArchiveRecovery::writeUInt16(CFile *output, uint16 val) +{ + BYTE b[2]; + b[0] = (BYTE)(val & 0x000000ff); + b[1] = (BYTE)((val & 0x0000ff00) >> 8); + output->Write(b, 2); +} + +void CArchiveRecovery::writeUInt32(CFile *output, uint32 val) +{ + BYTE b[4]; + b[0] = (BYTE)(val & 0x000000ff); + b[1] = (BYTE)((val & 0x0000ff00) >> 8); + b[2] = (BYTE)((val & 0x00ff0000) >> 16); + b[3] = (BYTE)((val & 0xff000000) >> 24); + output->Write(b, 4); +} + +void CArchiveRecovery::ProcessProgress(archiveScannerThreadParams_s* aitp, UINT64 pos) { + + if (!aitp->m_bIsValid) + return; + +/* + if (aitp->maxProgress==0) { + + if (aitp->file->IsPartFile()) + aitp->maxProgress=((CPartFile*)aitp->file)->GetCompletedSize(); + else + aitp->maxProgress=aitp->file->GetFileSize(); + aitp->maxProgress=aitp->file->GetFileSize(); + } +*/ + + int nNewProgress = 0; + if ((uint64)aitp->file->GetFileSize() > 0) + nNewProgress = (int)(uint64)((pos*1000) / aitp->file->GetFileSize()); + + if (nNewProgress <= aitp->curProgress+1) + return; + + aitp->curProgress=nNewProgress; + + SendMessage(aitp->progressHwnd, PBM_SETPOS, nNewProgress, 0); +} + diff --git a/ArchiveRecovery.h b/ArchiveRecovery.h new file mode 100644 index 00000000..78dbaa60 --- /dev/null +++ b/ArchiveRecovery.h @@ -0,0 +1,432 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +class CPartFile; +class CShareableFile; +struct Gap_Struct; + +#define ZIP_LOCAL_HEADER_MAGIC 0x04034b50 +#define ZIP_LOCAL_HEADER_EXT_MAGIC 0x08074b50 +#define ZIP_CD_MAGIC 0x02014b50 +#define ZIP_END_CD_MAGIC 0x06054b50 +#define ZIP_COMMENT "Recovered by eMule" + +#define RAR_HEAD_FILE 0x74 + +#pragma pack(1) +struct ZIP_Entry +{ + uint32 header; + uint16 versionToExtract; + uint16 generalPurposeFlag; + uint16 compressionMethod; + uint16 lastModFileTime; + uint16 lastModFileDate; + uint32 crc32; + uint32 lenCompressed; + uint32 lenUncompressed; + uint16 lenFilename; + uint16 lenExtraField; + BYTE *filename; + BYTE *extraField; + BYTE *compressedData; +}; +#pragma pack() + +#pragma pack(1) +struct ZIP_CentralDirectory +{ + ZIP_CentralDirectory() { + lenFilename = 0; + filename = NULL; + lenExtraField = 0; + extraField = NULL; + lenComment = 0; + comment = NULL; + } + uint32 header; + uint16 versionMadeBy; + uint16 versionToExtract; + uint16 generalPurposeFlag; + uint16 compressionMethod; + uint16 lastModFileTime; + uint16 lastModFileDate; + uint32 crc32; + uint32 lenCompressed; + uint32 lenUncompressed; + uint16 lenFilename; + uint16 lenExtraField; + uint16 lenComment; + uint16 diskNumberStart; + uint16 internalFileAttributes; + uint32 externalFileAttributes; + uint32 relativeOffsetOfLocalHeader; + BYTE *filename; + BYTE *extraField; + BYTE *comment; +}; +#pragma pack() + +#pragma pack(1) +struct RAR_BlockFile +{ + RAR_BlockFile() + { + EXT_DATE = NULL; + EXT_DATE_SIZE = 0; + } + ~RAR_BlockFile() + { + delete[] EXT_DATE; + } + + // This indicates the position in the input file just after the filename + ULONGLONG offsetData; + // This indicates how much of the block is after this offset + uint32 dataLength; + + uint16 HEAD_CRC; + BYTE HEAD_TYPE; + uint16 HEAD_FLAGS; + uint16 HEAD_SIZE; + uint32 PACK_SIZE; + uint32 UNP_SIZE; + BYTE HOST_OS; + uint32 FILE_CRC; + uint32 FTIME; + BYTE UNP_VER; + BYTE METHOD; + uint16 NAME_SIZE; + uint32 ATTR; + uint32 HIGH_PACK_SIZE; + uint32 HIGH_UNP_SIZE; + BYTE *FILE_NAME; + BYTE *EXT_DATE; + uint32 EXT_DATE_SIZE; + BYTE SALT[8]; +}; +#pragma pack() +#pragma pack(1) +struct ACE_ARCHIVEHEADER +{ + uint16 HEAD_CRC; + uint16 HEAD_SIZE; + BYTE HEAD_TYPE; + uint16 HEAD_FLAGS; + BYTE HEAD_SIGN[7]; + BYTE VER_EXTRACT; + BYTE VER_CREATED; + BYTE HOST_CREATED; + BYTE VOLUME_NUM; + uint32 FTIME; + BYTE RESERVED[8]; + BYTE AVSIZE; + //**AV + uint16 COMMENT_SIZE; + + char* AV; + char* COMMENT; + char* DUMP; + + ACE_ARCHIVEHEADER() { + AV=NULL; + COMMENT=NULL; + DUMP=NULL; + COMMENT_SIZE=0; + } + ~ACE_ARCHIVEHEADER() { + if (AV) { free(AV); AV=NULL;} + if (COMMENT){ free(COMMENT);COMMENT=NULL;} + if (DUMP) { free(DUMP); DUMP=NULL;} + } +}; +#pragma pack() +#pragma pack(1) +struct ACE_BlockFile +{ + uint16 HEAD_CRC; + uint16 HEAD_SIZE; + BYTE HEAD_TYPE; + uint16 HEAD_FLAGS; + uint32 PACK_SIZE; + uint32 ORIG_SIZE; + uint32 FTIME; + uint32 FILE_ATTRIBS; + uint32 CRC32; + uint32 TECHINFO; + uint16 RESERVED; + uint16 FNAME_SIZE; + // fname + uint16 COMM_SIZE; + // comment + + char* FNAME; + char* COMMENT; + uint64 data_offset; + ACE_BlockFile() { + FNAME=NULL; + COMMENT=NULL; + COMM_SIZE=0; + } + ~ACE_BlockFile() { + free(FNAME); + free(COMMENT); + } +}; +#pragma pack() + +// ################################ +// ISO related +static unsigned char sig_udf_bea[5] = { 0x42, 0x45, 0x41, 0x30, 0x31 }; // "BEA01" +static unsigned char sig_udf_nsr2[5] = { 0x4e, 0x53, 0x52, 0x30, 0x32 }; // "NSR02" +static unsigned char sig_udf_nsr3[5] = { 0x4e, 0x53, 0x52, 0x30, 0x33 }; // "NSR03" +static unsigned char sig_tea[5] = { 0x54, 0x45, 0x41, 0x30, 0x31 }; // "TEA01" +static const char sElToritoID[] = "EL TORITO SPECIFICATION"; + +enum ISO_ImageType +{ + ISOtype_unknown = 0, + ISOtype_9660 = 1, + ISOtype_joliet = 2, + ISOtype_UDF_nsr02 = 4, + ISOtype_UDF_nsr03 = 8, +}; +enum ISO_FileFlags +{ + ISO_HIDDEN = 1, + ISO_DIRECTORY = 2, + ISO_FILE = 4, + ISO_RECORD = 8, + ISO_READONLY = 16 +}; +#pragma pack(1) +struct ISO_DateTimePVD_s +{ + unsigned char year[4]; + unsigned char month[2]; + unsigned char day[2]; + unsigned char hour[2]; + unsigned char minute[2]; + unsigned char second[2]; + unsigned char sechundr[2]; + unsigned char offsetGreenwich; +}; +#pragma pack() + +#pragma pack(1) +struct ISO_DateTimeFileFolder_s +{ + unsigned char year; + unsigned char month; + unsigned char day; + unsigned char hour; + unsigned char minute; + unsigned char second; + unsigned char offsetGreenwich; +}; +#pragma pack() + +#pragma pack(1) +struct ISO_PVD_s { + unsigned char descr_type; + unsigned char magic[5]; + unsigned char descr_ver; + unsigned char unused; + unsigned char sysid[32]; + unsigned char volid[32]; + unsigned char zeros1[8]; + unsigned char seknum[8]; + unsigned char escSeq[32]; + + UINT32 volsetsize; + UINT32 volseqnum; + UINT32 seksize; + UINT64 pathtablen; + + UINT32 firstSek_LEpathTab1_LE; + UINT32 firstsek_LEpathtab2_LE; + UINT32 firstsek_BEpathtab1_BE; + UINT32 firstsek_BEpathtab2_BE; + + unsigned char rootdir[34]; + unsigned char volsetid[128]; + unsigned char pubid[128]; + unsigned char dataprepid[128]; + unsigned char appid[128]; + unsigned char copyr[37]; + unsigned char abstractfileid[37]; + unsigned char bibliofileid[37]; + + ISO_DateTimePVD_s creationdate; + ISO_DateTimePVD_s modifydate; + ISO_DateTimePVD_s expiredate; + + unsigned char effective[17]; + unsigned char filestruc_ver; + unsigned char zero; + unsigned char app_use[512]; + unsigned char res[653]; +}; +#pragma pack() + +#pragma pack(1) +struct BootDescr +{ + unsigned char descr_type; + unsigned char magic[5]; + unsigned char descr_ver; + unsigned char sysid[32]; + unsigned char bootid[32]; + unsigned char system_use[1977]; +}; +#pragma pack() + +#pragma pack(1) +struct ISO_BootDescr_s +{ + unsigned char descr_type; + unsigned char magic[5]; + unsigned char descr_ver; + unsigned char sysid[32]; + unsigned char bootid[32]; + unsigned char system_use[1977]; +}; +#pragma pack() + +#pragma pack(1) +struct ISO_PathtableEntry +{ + BYTE len; + BYTE lenExt; + unsigned int sectorOfExtension; + UINT16 sectorOfParent; + char* name; +}; +#pragma pack() +#pragma pack(1) +struct ISO_FileFolderEntry +{ + BYTE lenRecord; + BYTE nrOfSecInExt; + UINT64 sector1OfExtension; + UINT64 dataSize; + ISO_DateTimeFileFolder_s dateTime; + BYTE fileFlags; + BYTE fileUnitSize; + BYTE interleaveGapSize; + unsigned int volSeqNr; + BYTE nameLen; + TCHAR* name; + ISO_FileFolderEntry() { name=NULL;}; + ~ISO_FileFolderEntry() { if (name) free(name);}; +}; +#pragma pack() +struct ISOInfos_s +{ + bool bBootable; + DWORD secSize; + bool bUDF; + int iJolietUnicode; + DWORD type; +}; + +struct ThreadParam +{ + CPartFile *partFile; + CTypedPtrList *filled; + bool preview; + bool bCreatePartFileCopy; +}; + + +struct archiveinfo_s { + CTypedPtrList *centralDirectoryEntries; + CTypedPtrList *RARdir; + CTypedPtrList *ACEdir; + CTypedPtrList *ISOdir; + + bool bZipCentralDir; + WORD rarFlags; + ISOInfos_s isoInfos; + ACE_ARCHIVEHEADER *ACEhdr; + archiveinfo_s() { + centralDirectoryEntries=NULL; + RARdir=NULL; + ACEdir=NULL; + rarFlags=0; + bZipCentralDir=false; + ACEhdr=NULL; + isoInfos.bBootable=false; + isoInfos.secSize=false; + isoInfos.iJolietUnicode=0; + } +}; +struct archiveScannerThreadParams_s { + CShareableFile* file; + archiveinfo_s* ai; + CTypedPtrList *filled; + int type; + HWND ownerHwnd; + HWND progressHwnd; + int curProgress; + bool m_bIsValid; +}; + +class CArchiveRecovery +{ +public: + static void recover(CPartFile *partFile, bool preview = false, bool bCreatePartFileCopy = true); + static bool recoverZip(CFile *zipInput, CFile *zipOutput, archiveScannerThreadParams_s* ai, CTypedPtrList *filled, bool fullSize); + static bool recoverRar(CFile *rarInput, CFile *rarOutput, archiveScannerThreadParams_s* ai, CTypedPtrList *filled); + static bool recoverAce(CFile *aceInput, CFile *aceOutput, archiveScannerThreadParams_s* ai, CTypedPtrList *filled); + static bool recoverISO(CFile *aceInput, CFile *aceOutput, archiveScannerThreadParams_s* ai, CTypedPtrList *filled); + +private: + CArchiveRecovery(void); // Just use static recover method + + static UINT AFX_CDECL run(LPVOID lpParam); + static bool performRecovery(CPartFile *partFile, CTypedPtrList *filled, bool preview, bool bCreatePartFileCopy = true); + + static bool scanForZipMarker(CFile *input, archiveScannerThreadParams_s* aitp, uint32 marker, uint32 available); + static bool processZipEntry(CFile *zipInput, CFile *zipOutput, uint32 available, CTypedPtrList *centralDirectoryEntries); + static bool readZipCentralDirectory(CFile *zipInput, CTypedPtrList *centralDirectoryEntries, CTypedPtrList *filled); + + static RAR_BlockFile *scanForRarFileHeader(CFile *input, archiveScannerThreadParams_s* aitp, UINT64 available); + static bool validateRarFileBlock(RAR_BlockFile *block); + static void writeRarBlock(CFile *input, CFile *output, RAR_BlockFile *block); + + static ACE_BlockFile *scanForAceFileHeader(CFile *input, archiveScannerThreadParams_s* aitp, UINT64 available); + static void writeAceBlock(CFile *input, CFile *output, ACE_BlockFile *block); + static void CArchiveRecovery::writeAceHeader(CFile *output, ACE_ARCHIVEHEADER* hdr); + + static bool CopyFile(CPartFile *partFile, CTypedPtrList *filled, CString tempFileName); + static void DeleteMemory(ThreadParam *tp); + static bool IsFilled(uint32 start, uint32 end, CTypedPtrList *filled); + + static void ISOReadDirectory(archiveScannerThreadParams_s* aitp, UINT32 startSec, CFile* isoInput, CString currentDirName); + + static void ProcessProgress(archiveScannerThreadParams_s* aitp, UINT64 pos); + + static uint16 readUInt16(CFile *input); + static uint32 readUInt32(CFile *input); + static uint16 calcUInt16(BYTE *input); + static uint32 calcUInt32(BYTE *input); + static void writeUInt16(CFile *output, uint16 val); + static void writeUInt32(CFile *output, uint32 val); + +}; // class diff --git a/AsyncProxySocketLayer.cpp b/AsyncProxySocketLayer.cpp new file mode 100644 index 00000000..293c129d --- /dev/null +++ b/AsyncProxySocketLayer.cpp @@ -0,0 +1,1241 @@ +/*CAsyncProxySocketLayer by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.6 (2003-03-26) +-------------------------------------------------------- + +Introduction: +------------- + +This class is layer class for CAsyncSocketEx. With this class you +can connect through SOCKS4/5 and HTTP 1.1 proxies. This class works +as semi-transparent layer between CAsyncSocketEx and the actual socket. +This class is used in FileZilla, a powerful open-source FTP client. +It can be found under http://sourceforge.net/projects/filezilla +For more information about SOCKS4/5 goto +http://www.socks.nec.com/socksprot.html +For more information about HTTP 1.1 goto http://www.rfc-editor.org +and search for RFC2616 + +How to use? +----------- + +You don't have to change much in you already existing code to use +CAsyncProxySocketLayer. +To use it, create an instance of CAsyncProxySocketLayer, call SetProxy +and attach it to a CAsyncSocketEx instance. +You have to process OnLayerCallback in you CAsyncSocketEx instance as it will +receive all layer nofications. +The following notifications are sent: + +//Error codes +PROXYERROR_NOERROR 0 +PROXYERROR_NOCONN 1 //Can't connect to proxy server, use GetLastError for more information +PROXYERROR_REQUESTFAILED 2 //Request failed, can't send data +PROXYERROR_AUTHREQUIRED 3 //Authentication required +PROXYERROR_AUTHTYPEUNKNOWN 4 //Authtype unknown or not supported +PROXYERROR_AUTHFAILED 5 //Authentication failed +PROXYERROR_AUTHNOLOGON 6 +PROXYERROR_CANTRESOLVEHOST 7 + +//Status messages +PROXYSTATUS_LISTENSOCKETCREATED 8 //Called when a listen socket was created successfully. Unlike the normal listen function, + //a socksified socket has to connect to the proxy to negotiate the details with the server + //on which the listen socket will be created + //The two parameters will contain the ip and port of the listen socket on the server. + +If you want to use CAsyncProxySocketLayer to create a listen socket, you +have to use this overloaded function: +BOOL PrepareListen(unsigned long serverIp); +serverIP is the IP of the server you are already connected +through the SOCKS proxy. You can't use listen sockets over a +SOCKS proxy without a primary connection. Listen sockets are only +supported by SOCKS proxies, this won't work with HTTP proxies. +When the listen socket is created successfully, the PROXYSTATUS_LISTENSOCKETCREATED +notification is sent. The parameters will tell you the ip and the port of the listen socket. +After it you have to handle the OnAccept message and accept the +connection. +Be carful when calling Accept: rConnected socket will NOT be filled! Instead use the instance which created the +listen socket, it will handle the data connection. +If you want to accept more than one connection, you have to create a listing socket for each of them! + +Description of important functions and their parameters: +-------------------------------------------------------- + +void SetProxy(int nProxyType); +void SetProxy(int nProxyType, const char * pProxyHost, int nProxyPort); +void SetProxy(int nProxyType, const char * pProxyHost, int nProxyPort, const char *pProxyUser, const char * pProxyPass); + +Call one of this functions to set the proxy type. +Parametes: +- nProxyType specifies the Proxy Type. +- ProxyHost and nProxyPort specify the address of the proxy +- ProxyUser and ProxyPass are only available for SOCKS5 proxies. + +supported proxy types: +PROXYTYPE_NOPROXY +PROXYTYPE_SOCKS4 +PROXYTYPE_SOCKS4A +PROXYTYPE_SOCKS5 +PROXYTYPE_HTTP11 + +There are also some other functions: + +GetProxyPeerName +Like GetPeerName of CAsyncSocket, but returns the address of the +server connected through the proxy. If using proxies, GetPeerName +only returns the address of the proxy. + +int GetProxyType(); +Returns the used proxy + +const int GetLastProxyError() const; +Returns the last proxy error + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de + +Version history +--------------- + +- 1.6 got rid of MFC +- 1.5 released CAsyncSocketExLayer version +- 1.4 added Unicode support +- 1.3 added basic HTTP1.1 authentication + fixed memory leak in SOCKS5 code + OnSocksOperationFailed will be called after Socket has been closed + fixed some minor bugs +- 1.2 renamed into CAsyncProxySocketLayer + added HTTP1.1 proxy support +- 1.1 fixes all known bugs, mostly with SOCKS5 authentication +- 1.0 initial release +*/ + +#include "stdafx.h" +#include "AsyncProxySocketLayer.h" +#include "CBase64coding.hpp" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +extern CStringA ipstrA(uint32 nIP); + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +CAsyncProxySocketLayer::CAsyncProxySocketLayer() +{ + m_nProxyOpID = 0; + m_nProxyOpState = 0; + m_pRecvBuffer = 0; + m_nRecvBufferPos = 0; + m_nProxyPeerIP = 0; + m_nProxyPeerPort = 0; + m_pProxyPeerHost = NULL; + m_pStrBuffer = NULL; + m_iStrBuffSize = 0; + m_ProxyData.nProxyType = PROXYTYPE_NOPROXY; +} + +CAsyncProxySocketLayer::~CAsyncProxySocketLayer() +{ + delete[] m_pProxyPeerHost; + ClearBuffer(); +} + +///////////////////////////////////////////////////////////////////////////// +// Member-Funktion CAsyncProxySocketLayer + +void CAsyncProxySocketLayer::SetProxy(int nProxyType) +{ + //Validate the parameters + ASSERT( nProxyType == PROXYTYPE_NOPROXY ); + + m_ProxyData.nProxyType = nProxyType; +} + +void CAsyncProxySocketLayer::SetProxy(int nProxyType, const CStringA& strProxyHost, int ProxyPort) +{ + //Validate the parameters + ASSERT( nProxyType == PROXYTYPE_SOCKS4 || + nProxyType == PROXYTYPE_SOCKS4A || + nProxyType == PROXYTYPE_SOCKS5 || + nProxyType == PROXYTYPE_HTTP10 || + nProxyType == PROXYTYPE_HTTP11 ); + ASSERT( !m_nProxyOpID ); + ASSERT( !strProxyHost.IsEmpty() ); + ASSERT( ProxyPort > 0); + ASSERT( ProxyPort <= 65535 ); + + m_ProxyData.nProxyType = nProxyType; + m_ProxyData.strProxyHost = strProxyHost; + m_ProxyData.nProxyPort = ProxyPort; + m_ProxyData.bUseLogon = FALSE; +} + +void CAsyncProxySocketLayer::SetProxy(int nProxyType, const CStringA& strProxyHost, int ProxyPort, + const CStringA& strProxyUser, const CStringA& strProxyPass) +{ + //Validate the parameters + ASSERT( nProxyType == PROXYTYPE_SOCKS5 || nProxyType == PROXYTYPE_HTTP10 || nProxyType == PROXYTYPE_HTTP11 ); + ASSERT( !m_nProxyOpID ); + ASSERT( !strProxyHost.IsEmpty() ); + ASSERT( ProxyPort > 0 ); + ASSERT( ProxyPort <= 65535 ); + + m_ProxyData.nProxyType = nProxyType; + m_ProxyData.strProxyHost = strProxyHost; + m_ProxyData.nProxyPort = ProxyPort; + m_ProxyData.bUseLogon = TRUE; + m_ProxyData.strProxyUser = strProxyUser; + m_ProxyData.strProxyPass = strProxyPass; +} + +CStringA GetSocks4Error(UINT ver, UINT cd) +{ + if (ver != 0) { + CStringA strError; + strError.Format("Unknown protocol version: %u", ver); + return strError; + } + + switch (cd) + { + case 90: return ""; + case 91: return "Request rejected or failed"; + case 92: return "Failed to connect to 'identd'"; + case 93: return "'identd' user-id error"; + default:{ + CStringA strError; + strError.Format("Unknown command: %u", cd); + return strError; + } + } +} + +CStringA GetSocks5Error(UINT rep) +{ + switch (rep) + { + case 0x00: return ""; + case 0x01: return "General SOCKS server failure"; + case 0x02: return "Connection not allowed by ruleset"; + case 0x03: return "Network unreachable"; + case 0x04: return "Host unreachable"; + case 0x05: return "Connection refused"; + case 0x06: return "TTL expired"; + case 0x07: return "Command not supported"; + case 0x08: return "Address type not supported"; + default:{ + CStringA strError; + strError.Format("Unknown reply: %u", rep); + return strError; + } + } +} + +void CAsyncProxySocketLayer::OnClose(int nErrorCode) +{ + // We must route that event with the same functionality (PostMessage) which is used by + // the 'OnReceive' and 'OnConnect' event handlers. Otherwise the socket event queue of + // the underlying 'CAsyncSocketEx' may get out of sync. + TriggerEvent(FD_CLOSE, nErrorCode, TRUE); +} + +void CAsyncProxySocketLayer::OnAccept(int nErrorCode) +{ + // We must route that event with the same functionality (PostMessage) which is used by + // the 'OnReceive' and 'OnConnect' event handlers. Otherwise the socket event queue of + // the underlying 'CAsyncSocketEx' may get out of sync. + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); +} + +void CAsyncProxySocketLayer::OnSend(int nErrorCode) +{ + // We must route that event with the same functionality (PostMessage) which is used by + // the 'OnReceive' and 'OnConnect' event handlers. Otherwise the socket event queue of + // the underlying 'CAsyncSocketEx' may get out of sync. + TriggerEvent(FD_WRITE, nErrorCode, TRUE); +} + +void CAsyncProxySocketLayer::OnReceive(int nErrorCode) +{ + if (m_nProxyOpID == 0) { + TriggerEvent(FD_READ, nErrorCode, TRUE); + return; + } + + if (nErrorCode) + TriggerEvent(FD_READ, nErrorCode, TRUE); + + if (m_nProxyOpState == 0) //We should not receive a response yet! + return; + + if (m_ProxyData.nProxyType == PROXYTYPE_SOCKS4 || m_ProxyData.nProxyType == PROXYTYPE_SOCKS4A) + { + if ( m_nProxyOpState == 1 // Response to initial connect or bind request + || m_nProxyOpState == 2)// Response (2nd) to bind request + { + if (!m_pRecvBuffer) + m_pRecvBuffer = new char[8]; + int numread = ReceiveNext(m_pRecvBuffer + m_nRecvBufferPos, 8 - m_nRecvBufferPos); + if (numread == SOCKET_ERROR) { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + ClearBuffer(); + } + return; + } + m_nRecvBufferPos += numread; + + // +----+----+----+----+----+----+----+----+ + // | VN | CD | DSTPORT | DSTIP | + // +----+----+----+----+----+----+----+----+ + // # of bytes: 1 1 2 4 + // + // VN is the version of the reply code and should be 0. CD is the result + // code with one of the following values: + // + // 90: request granted + // 91: request rejected or failed + // 92: request rejected becasue SOCKS server cannot connect to + // identd on the client + // 93: request rejected because the client program and identd + // report different user-ids + + if (m_nRecvBufferPos == 8) + { + TRACE("SOCKS4 response: VN=%u CD=%u DSTPORT=%u DSTIP=%s\n", (BYTE)m_pRecvBuffer[0], (BYTE)m_pRecvBuffer[1], ntohs(*(u_short*)&m_pRecvBuffer[2]), ipstrA(*(u_long*)&m_pRecvBuffer[4])); + if (m_pRecvBuffer[0] != 0 || m_pRecvBuffer[1] != 90) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0, (LPARAM)(LPCSTR)GetSocks4Error(m_pRecvBuffer[0], m_pRecvBuffer[1])); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + // Proxy SHOULD answer with DSTPORT and DSTIP. Most proxies do, but some answer with 0.0.0.0:0 + //ASSERT( m_nProxyPeerPort == *(u_short*)&m_pRecvBuffer[2] ); + //ASSERT( m_nProxyPeerIP == 0 || m_nProxyPeerIP == *(u_long*)&m_pRecvBuffer[4] ); + + if ( (m_nProxyOpID == PROXYOP_CONNECT && m_nProxyOpState == 1) || + (m_nProxyOpID == PROXYOP_BIND && m_nProxyOpState == 2) ) + { + int nOpIDEvent = m_nProxyOpID == PROXYOP_CONNECT ? FD_CONNECT : FD_ACCEPT; + ClearBuffer(); + Reset(); + TriggerEvent(nOpIDEvent, 0, TRUE); + TriggerEvent(FD_READ, 0, TRUE); + TriggerEvent(FD_WRITE, 0, TRUE); + return; + } + else if (m_nProxyOpID == PROXYOP_BIND && m_nProxyOpState == 1) + { + // Listen socket created + m_nProxyOpState = 2; + u_long ip = *(u_long*)&m_pRecvBuffer[4]; + if (ip == 0) + { + // No IP returned, use the IP of the proxy server + SOCKADDR SockAddr = {0}; + int SockAddrLen = sizeof(SockAddr); + if (GetPeerName(&SockAddr, &SockAddrLen)) { + ip = ((LPSOCKADDR_IN)&SockAddr)->sin_addr.S_un.S_addr; + } + else { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + } + t_ListenSocketCreatedStruct data; + data.ip = ip; + data.nPort = *(u_short*)&m_pRecvBuffer[2]; + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYSTATUS_LISTENSOCKETCREATED, 0, (LPARAM)&data); + // Wait for 2nd response to bind request + } + ClearBuffer(); + } + } + } + else if (m_ProxyData.nProxyType == PROXYTYPE_SOCKS5) + { + if ( m_nProxyOpState == 1 // Response to initialization request + || m_nProxyOpState == 2)// Response to authentication request + { + if (!m_pRecvBuffer) + m_pRecvBuffer = new char[2]; + int numread = ReceiveNext(m_pRecvBuffer + m_nRecvBufferPos, 2 - m_nRecvBufferPos); + if (numread == SOCKET_ERROR) { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + } + return; + } + m_nRecvBufferPos += numread; + + if (m_nRecvBufferPos == 2) + { + TRACE("SOCKS5 response: VER=%u METHOD=%u\n", (BYTE)m_pRecvBuffer[0], (BYTE)m_pRecvBuffer[1]); + bool bIniReqFailed = m_nProxyOpState == 1 && m_pRecvBuffer[0] != 5; // Response to initialization request + bool bAuthReqFailed = m_nProxyOpState == 2 && m_pRecvBuffer[1] != 0; // Response to authentication request + if (bIniReqFailed || bAuthReqFailed) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, bAuthReqFailed ? PROXYERROR_AUTHFAILED : PROXYERROR_REQUESTFAILED, 0); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + + if (m_nProxyOpState == 1 && m_pRecvBuffer[1] != 0) // Authentication needed + { + if (m_pRecvBuffer[1] != 2) { // Unknown authentication type + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_AUTHTYPEUNKNOWN, 0); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + + if (!m_ProxyData.bUseLogon) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_AUTHNOLOGON, 0); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + + // Send authentication + // + // RFC 1929 - Username/Password Authentication for SOCKS V5 + // + // +----+------+----------+------+----------+ + // |VER | ULEN | UNAME | PLEN | PASSWD | + // +----+------+----------+------+----------+ + // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + // +----+------+----------+------+----------+ + // + // The VER field contains the current version of the subnegotiation, + // which is X'01'. The ULEN field contains the length of the UNAME field + // that follows. The UNAME field contains the username as known to the + // source operating system. The PLEN field contains the length of the + // PASSWD field that follows. The PASSWD field contains the password + // association with the given UNAME. + + char cBuff[1 + 1 + 255 + 1 + 255]; + int iLen = _snprintf(cBuff, _countof(cBuff), "\x01%c%s%c%s", + m_ProxyData.strProxyUser.GetLength(), m_ProxyData.strProxyUser, + m_ProxyData.strProxyPass.GetLength(), m_ProxyData.strProxyPass); + + int res = SendNext(cBuff, iLen); + if (res == SOCKET_ERROR || res < iLen) + { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK || res < iLen) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + return; + } + } + ClearBuffer(); + m_nProxyOpState = 2; + return; + } + + // Send connection request + const char* pszAsciiProxyPeerHost; + int iLenAsciiProxyPeerHost; + if (m_nProxyPeerIP == 0) { + pszAsciiProxyPeerHost = m_pProxyPeerHost; + iLenAsciiProxyPeerHost = strlen(pszAsciiProxyPeerHost); + } + else { + pszAsciiProxyPeerHost = 0; + iLenAsciiProxyPeerHost = 0; + } + char* pcReq = (char*)_alloca(10 + 1 + iLenAsciiProxyPeerHost); + pcReq[0] = 5; + pcReq[1] = (m_nProxyOpID == PROXYOP_CONNECT) ? 1 : 2; + pcReq[2] = 0; + int iReqLen = 3; + if (m_nProxyPeerIP) { + pcReq[iReqLen++] = 1; + *(u_long*)&pcReq[iReqLen] = m_nProxyPeerIP; + iReqLen += 4; + } + else { + pcReq[iReqLen++] = 3; + pcReq[iReqLen++] = (char)iLenAsciiProxyPeerHost; + memcpy(&pcReq[iReqLen], pszAsciiProxyPeerHost, iLenAsciiProxyPeerHost); + iReqLen += iLenAsciiProxyPeerHost; + } + *(u_short*)&pcReq[iReqLen] = m_nProxyPeerPort; + iReqLen += 2; + + int res = SendNext(pcReq, iReqLen); + if (res == SOCKET_ERROR || res < iReqLen) + { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK || res < iReqLen) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + return; + } + } + m_nProxyOpState = 3; + ClearBuffer(); + } + } + else if ( m_nProxyOpState == 3 // Response to connection or bind request + || m_nProxyOpState == 4) // Response (2nd) to bind request + { + if (!m_pRecvBuffer) + m_pRecvBuffer = new char[10]; + int numread = ReceiveNext(m_pRecvBuffer + m_nRecvBufferPos, 10 - m_nRecvBufferPos); + if (numread == SOCKET_ERROR) { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + } + return; + } + m_nRecvBufferPos += numread; + + if (m_nRecvBufferPos == 10) + { + TRACE("SOCKS5 response: VER=%u REP=%u RSV=%u ATYP=%u BND.ADDR=%s BND.PORT=%u\n", (BYTE)m_pRecvBuffer[0], (BYTE)m_pRecvBuffer[1], (BYTE)m_pRecvBuffer[2], (BYTE)m_pRecvBuffer[3], ipstrA(*(u_long*)&m_pRecvBuffer[4]), ntohs(*(u_short*)&m_pRecvBuffer[8])); + if (m_pRecvBuffer[0] != 5 || m_pRecvBuffer[1] != 0) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0, (LPARAM)(LPCSTR)GetSocks5Error(m_pRecvBuffer[1])); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + + if ( (m_nProxyOpID == PROXYOP_CONNECT && m_nProxyOpState == 3) || + (m_nProxyOpID == PROXYOP_BIND && m_nProxyOpState == 4) ) + { + int nOpIDEvent = m_nProxyOpID == PROXYOP_CONNECT ? FD_CONNECT : FD_ACCEPT; + Reset(); + ClearBuffer(); + TriggerEvent(nOpIDEvent, 0, TRUE); + TriggerEvent(FD_READ, 0, TRUE); + TriggerEvent(FD_WRITE, 0, TRUE); + return; + } + else if (m_nProxyOpID == PROXYOP_BIND && m_nProxyOpState == 3) + { + // Listen socket created + if (m_pRecvBuffer[3] != 1) { // Check which kind of address the response contains + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0, (LPARAM)"Unexpected ATYP received"); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + + m_nProxyOpState = 4; + t_ListenSocketCreatedStruct data; + data.ip = *(u_long*)&m_pRecvBuffer[4]; + data.nPort = *(u_short*)&m_pRecvBuffer[8]; + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYSTATUS_LISTENSOCKETCREATED, 0, (LPARAM)&data); + // Wait for 2nd response to bind request + } + ClearBuffer(); + } + } + } + else if (m_ProxyData.nProxyType == PROXYTYPE_HTTP10 || m_ProxyData.nProxyType == PROXYTYPE_HTTP11) + { + ASSERT( m_nProxyOpID == PROXYOP_CONNECT ); + + // Read everything which is currently available at the socket + // + bool bFoundEOH = false; + while (!bFoundEOH) + { + char cBuff[4096]; + int iRead = ReceiveNext(cBuff, sizeof cBuff); + if (iRead == SOCKET_ERROR) { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + Reset(); + ClearBuffer(); + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + } + return; + } + else if (iRead == 0) + return; + + // Safety check: Don't allow buffer to grow too large + char *pNewStrBuffer; + if ( m_iStrBuffSize + iRead > 4096 + || (pNewStrBuffer = (char*)realloc(m_pStrBuffer, m_iStrBuffSize + iRead)) == NULL) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0, (LPARAM)"Invalid HTTP response - Header size exceeds limit"); + Reset(); + ClearBuffer(); + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + return; + } + m_pStrBuffer = pNewStrBuffer; + + // Append read chunk to buffer + memcpy(m_pStrBuffer + m_iStrBuffSize, cBuff, iRead); + m_iStrBuffSize += iRead; + + // Search for EOH (CRLFCRLF) + const char* pc = m_pStrBuffer; + int iMaxOff = m_iStrBuffSize - sizeof(DWORD); + for (int i = 0; i <= iMaxOff; i++) { + if (*(DWORD*)(pc++) == 0x0A0D0A0D) { // VC-BUG?: '\r\n\r\n' results in 0x0A0D0A0D too, although it should not! + bFoundEOH = true; + break; + } + } + } + ASSERT( bFoundEOH ); + + // Evaluate HTTP status + // + // We already know that we have a (CR)NL-character which can be safely used + // as a NUL-character in the context of 'sscanf'. + // + UINT uHttpStatus; + if (sscanf(m_pStrBuffer, "HTTP/%*u.%*u %u", &uHttpStatus) != 1 || uHttpStatus != 200) + { + if (*(DWORD*)m_pStrBuffer == 'PTTH') + { + TRACE("%hs\n", CStringA(m_pStrBuffer, m_iStrBuffSize).TrimRight("\r\n")); + char* pcNl = (char*)memchr(m_pStrBuffer, '\n', m_iStrBuffSize); + if (pcNl) { + *pcNl = '\0'; + if (pcNl[-1] == '\r') + pcNl[-1] = '\0'; + } + if (uHttpStatus == 407) + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_AUTHREQUIRED, 0); + else + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0, (LPARAM)m_pStrBuffer); + } + else { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0, (LPARAM)"Invalid HTTP response"); + } + Reset(); + ClearBuffer(); + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + return; + } + + TRACE("%hs\n", CStringA(m_pStrBuffer, m_iStrBuffSize).TrimRight("\r\n")); + Reset(); + ClearBuffer(); + TriggerEvent(FD_CONNECT, 0, TRUE); + TriggerEvent(FD_READ, 0, TRUE); + TriggerEvent(FD_WRITE, 0, TRUE); + } +} + +BOOL CAsyncProxySocketLayer::Connect(LPCSTR lpszHostAddress, UINT nHostPort) +{ + ASSERT( lpszHostAddress != NULL ); + + if (!m_ProxyData.nProxyType) + //Connect normally because there is no proxy + return ConnectNext(lpszHostAddress, nHostPort); + + //Translate the host address + SOCKADDR_IN sockAddr = {0}; + sockAddr.sin_addr.s_addr = inet_addr(lpszHostAddress); + if (sockAddr.sin_addr.s_addr == INADDR_NONE) + { + LPHOSTENT lphost = gethostbyname(lpszHostAddress); + if (lphost != NULL) + sockAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; + else + { + // Can't resolve hostname + if (m_ProxyData.nProxyType == PROXYTYPE_SOCKS4A || + m_ProxyData.nProxyType == PROXYTYPE_SOCKS5 || + m_ProxyData.nProxyType == PROXYTYPE_HTTP10 || + m_ProxyData.nProxyType == PROXYTYPE_HTTP11) + { //Can send domain names to proxy + + //Conect to proxy server + BOOL res = ConnectNext(m_ProxyData.strProxyHost, m_ProxyData.nProxyPort); + if (!res) { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOCONN, nErrorCode); + return FALSE; + } + } + m_nProxyPeerPort = htons((u_short)nHostPort); + m_nProxyPeerIP = 0; + delete[] m_pProxyPeerHost; + m_pProxyPeerHost = NULL; // 'new' may throw an exception + m_pProxyPeerHost = new CHAR[strlen(lpszHostAddress) + 1]; + strcpy(m_pProxyPeerHost, lpszHostAddress); + m_nProxyOpID = PROXYOP_CONNECT; + return TRUE; + } + else { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_CANTRESOLVEHOST, 0); + WSASetLastError(WSAEINVAL); + return FALSE; + } + } + } + sockAddr.sin_family = AF_INET; + sockAddr.sin_port = htons((u_short)nHostPort); + + BOOL res = CAsyncProxySocketLayer::Connect((SOCKADDR*)&sockAddr, sizeof(sockAddr)); + if (res || WSAGetLastError() == WSAEWOULDBLOCK) + { + delete[] m_pProxyPeerHost; + m_pProxyPeerHost = NULL; // 'new' may throw an exception + m_pProxyPeerHost = new CHAR[strlen(lpszHostAddress) + 1]; + strcpy(m_pProxyPeerHost, lpszHostAddress); + } + return res; +} + +BOOL CAsyncProxySocketLayer::Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen) +{ + if (!m_ProxyData.nProxyType) + //Connect normally because there is no proxy + return ConnectNext(lpSockAddr, nSockAddrLen); + + LPSOCKADDR_IN sockAddr = (LPSOCKADDR_IN)lpSockAddr; + + //Save server details + m_nProxyPeerIP = sockAddr->sin_addr.S_un.S_addr; + m_nProxyPeerPort = sockAddr->sin_port; + delete[] m_pProxyPeerHost; + m_pProxyPeerHost = NULL; + + m_nProxyOpID = PROXYOP_CONNECT; + + BOOL res = ConnectNext(m_ProxyData.strProxyHost, m_ProxyData.nProxyPort); + if (!res) { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOCONN, nErrorCode); + return FALSE; + } + } + + return res; +} + +void CAsyncProxySocketLayer::OnConnect(int nErrorCode) +{ + if (m_ProxyData.nProxyType == PROXYTYPE_NOPROXY) { + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + return; + } + + if (m_nProxyOpID == 0) { + ASSERT(0); + return; + } + + if (nErrorCode) { + // Can't connect to proxy + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOCONN, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + ClearBuffer(); + return; + } + + if (m_nProxyOpID == PROXYOP_CONNECT || m_nProxyOpID == PROXYOP_BIND) + { + if (m_nProxyOpState) + return; // Somehow OnConnect has been called more than once + + ASSERT( m_ProxyData.nProxyType != PROXYTYPE_NOPROXY ); + ClearBuffer(); + + if (m_ProxyData.nProxyType == PROXYTYPE_SOCKS4 || m_ProxyData.nProxyType == PROXYTYPE_SOCKS4A) + { + const char* pszAsciiProxyPeerHost; + int iSizeAsciiProxyPeerHost; + if (m_nProxyPeerIP == 0) { + pszAsciiProxyPeerHost = m_pProxyPeerHost; + iSizeAsciiProxyPeerHost = strlen(pszAsciiProxyPeerHost) + 1; + } + else { + pszAsciiProxyPeerHost = 0; + iSizeAsciiProxyPeerHost = 0; + } + + // SOCKS 4 + // --------------------------------------------------------------------------- + // +----+----+----+----+----+----+----+----+----+----+....+----+ + // | VN | CD | DSTPORT | DSTIP | USERID |NULL| + // +----+----+----+----+----+----+----+----+----+----+....+----+ + //# of bytes: 1 1 2 4 variable 1 + + char* pcReq = (char*)_alloca(9 + iSizeAsciiProxyPeerHost); + pcReq[0] = 4; // VN: 4 + pcReq[1] = (m_nProxyOpID == PROXYOP_CONNECT) ? 1 : 2; // CD: 1=CONNECT, 2=BIND + *(u_short*)&pcReq[2] = m_nProxyPeerPort; // DSTPORT + + int iReqLen = 4 + 4 + 1; + if (m_nProxyPeerIP == 0) + { + ASSERT( m_ProxyData.nProxyType == PROXYTYPE_SOCKS4A ); + ASSERT( strcmp(pszAsciiProxyPeerHost, "") != 0 ); + ASSERT( iSizeAsciiProxyPeerHost > 0 ); + + // For version 4A, if the client cannot resolve the destination host's + // domain name to find its IP address, it should set the first three bytes + // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds + // to IP address 0.0.0.x, with x nonzero.) + + // DSTIP: Set the IP to 0.0.0.x (x is nonzero) + pcReq[4] = 0; + pcReq[5] = 0; + pcReq[6] = 0; + pcReq[7] = 1; + + pcReq[8] = 0; // Terminating NUL-byte for USERID + + // Following the NULL byte terminating USERID, the client must send the + // destination domain name and termiantes it with another NULL byte. + + // Add hostname (including terminating NUL-byte) + memcpy(&pcReq[9], pszAsciiProxyPeerHost, iSizeAsciiProxyPeerHost); + iReqLen += iSizeAsciiProxyPeerHost; + } + else { + *(u_long*)&pcReq[4] = m_nProxyPeerIP; // DSTIP + pcReq[8] = 0; // Terminating NUL-byte for USERID + } + + int res = SendNext(pcReq, iReqLen); + if (res == SOCKET_ERROR) { + int nErrorCode = WSAGetLastError(); + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, (nErrorCode == WSAEWOULDBLOCK) ? WSAECONNABORTED : nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + ClearBuffer(); + return; + } + else if (res < iReqLen) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + } + else if (m_ProxyData.nProxyType == PROXYTYPE_SOCKS5) + { + // SOCKS 5 + // ------------------------------------------------------------------------------------------- + // The client connects to the server, and sends a version identifier/method selection message: + // +----+----------+----------+ + // |VER | NMETHODS | METHODS | + // +----+----------+----------+ + // | 1 | 1 | 1 to 255 | + // +----+----------+----------+ + // + // The values currently defined for METHOD are: + // + // o X'00' NO AUTHENTICATION REQUIRED + // o X'01' GSSAPI + // o X'02' USERNAME/PASSWORD + // o X'03' to X'7F' IANA ASSIGNED + // o X'80' to X'FE' RESERVED FOR PRIVATE METHODS + // o X'FF' NO ACCEPTABLE METHODS + + int iReqLen; + char acReq[4]; + acReq[0] = 5; // VER: 5 + if (m_ProxyData.bUseLogon) { + acReq[1] = 2; // NMETHODS: 2 + acReq[2] = 2; // METHOD #1: 2 (USERNAME/PASSWORD) + acReq[3] = 0; // METHOD #2: 0 (NO AUTHENTICATION) + iReqLen = 4; + } + else { + acReq[1] = 1; // NMETHODS: 1 + acReq[2] = 0; // METHOD #1: 0 (NO AUTHENTICATION) + iReqLen = 3; + } + + int res = SendNext(acReq, iReqLen); + if (res == SOCKET_ERROR) { + int nErrorCode = WSAGetLastError(); + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, (nErrorCode == WSAEWOULDBLOCK) ? WSAECONNABORTED : nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + ClearBuffer(); + return; + } + else if (res < iReqLen) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + } + else if (m_ProxyData.nProxyType == PROXYTYPE_HTTP10 || m_ProxyData.nProxyType == PROXYTYPE_HTTP11) + { + const char* pszHost; + if (m_pProxyPeerHost && m_pProxyPeerHost[0] != '\0') + pszHost = m_pProxyPeerHost; + else + pszHost = inet_ntoa(*(in_addr*)&m_nProxyPeerIP); + + UINT nProxyPeerPort = ntohs((u_short)m_nProxyPeerPort); + char szHttpReq[4096]; + int iHttpReqLen; + if (!m_ProxyData.bUseLogon) + { + if (m_ProxyData.nProxyType == PROXYTYPE_HTTP10) { + // The reason why we offer HTTP/1.0 support is just because it + // allows us to *not *send the "Host" field, thus saving overhead. + iHttpReqLen = _snprintf(szHttpReq, _countof(szHttpReq), + "CONNECT %s:%u HTTP/1.0\r\n" + "\r\n", + pszHost, nProxyPeerPort); + } + else { + // "Host" field is a MUST for HTTP/1.1 according RFC 2161 + iHttpReqLen = _snprintf(szHttpReq, _countof(szHttpReq), + "CONNECT %s:%u HTTP/1.1\r\n" + "Host: %s:%u\r\n" + "\r\n", + pszHost, nProxyPeerPort, pszHost, nProxyPeerPort); + } + } + else + { + char szUserPass[512]; + int iUserPassLen = _snprintf(szUserPass, _countof(szUserPass), "%s:%s", m_ProxyData.strProxyUser, m_ProxyData.strProxyPass); + + char szUserPassBase64[2048]; + CBase64Coding base64coding; + if (!base64coding.Encode(szUserPass, iUserPassLen, szUserPassBase64)) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + + if (m_ProxyData.nProxyType == PROXYTYPE_HTTP10) { + // The reason why we offer HTTP/1.0 support is just because it + // allows us to *not *send the "Host" field, thus saving overhead. + iHttpReqLen = _snprintf(szHttpReq, _countof(szHttpReq), + "CONNECT %s:%u HTTP/1.0\r\n" + "Authorization: Basic %s\r\n" + "Proxy-Authorization: Basic %s\r\n" + "\r\n", + pszHost, nProxyPeerPort, szUserPassBase64, szUserPassBase64); + } + else { + // "Host" field is a MUST for HTTP/1.1 according RFC 2161 + iHttpReqLen = _snprintf(szHttpReq, _countof(szHttpReq), + "CONNECT %s:%u HTTP/1.1\r\n" + "Host: %s:%u\r\n" + "Authorization: Basic %s\r\n" + "Proxy-Authorization: Basic %s\r\n" + "\r\n", + pszHost, nProxyPeerPort, pszHost, nProxyPeerPort, szUserPassBase64, szUserPassBase64); + } + } + + int iSent = SendNext(szHttpReq, iHttpReqLen); + if (iSent == SOCKET_ERROR) { + int nErrorCode = WSAGetLastError(); + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, nErrorCode); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, (nErrorCode == WSAEWOULDBLOCK) ? WSAECONNABORTED : nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + ClearBuffer(); + return; + } + else if (iSent < iHttpReqLen) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0); + if (m_nProxyOpID == PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + m_nProxyOpState++; + return; + } + else + ASSERT(0); + + //Now we'll wait for the response, handled in OnReceive + m_nProxyOpState++; + } +} + +void CAsyncProxySocketLayer::ClearBuffer() +{ + free(m_pStrBuffer); + m_pStrBuffer = NULL; + m_iStrBuffSize = 0; + delete[] m_pRecvBuffer; + m_pRecvBuffer = 0; + m_nRecvBufferPos = 0; +} + +BOOL CAsyncProxySocketLayer::Listen(int nConnectionBacklog) +{ + if (GetProxyType() == PROXYTYPE_NOPROXY) + return ListenNext(nConnectionBacklog); + + //Connect to proxy server + BOOL res = ConnectNext(m_ProxyData.strProxyHost, m_ProxyData.nProxyPort); + if (!res) { + int nErrorCode = WSAGetLastError(); + if (nErrorCode != WSAEWOULDBLOCK) { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOCONN, nErrorCode); + return FALSE; + } + } + m_nProxyPeerPort = 0; + m_nProxyPeerIP = (unsigned int)nConnectionBacklog; // ??????????????????? + m_nProxyOpID = PROXYOP_BIND; + return TRUE; +} + +#ifdef _AFX +BOOL CAsyncProxySocketLayer::GetPeerName(CString &rPeerAddress, UINT &rPeerPort) +{ + if (m_ProxyData.nProxyType == PROXYTYPE_NOPROXY) + return GetPeerNameNext(rPeerAddress, rPeerPort); + + if (GetLayerState() == notsock) + { + WSASetLastError(WSAENOTSOCK); + return FALSE; + } + else if (GetLayerState() != connected) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + else if (!m_nProxyPeerIP || !m_nProxyPeerPort) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + + ASSERT( m_ProxyData.nProxyType ); + BOOL res = GetPeerNameNext(rPeerAddress, rPeerPort); + if (res) + { + rPeerPort = ntohs((u_short)m_nProxyPeerPort); + rPeerAddress.Format(_T("%u.%u.%u.%u"), m_nProxyPeerIP&0xff, (m_nProxyPeerIP>>8)&0xff, (m_nProxyPeerIP>>16)&0xff, m_nProxyPeerIP>>24); + } + return res; +} +#endif + +BOOL CAsyncProxySocketLayer::GetPeerName(SOCKADDR* lpSockAddr, int* lpSockAddrLen) +{ + if (m_ProxyData.nProxyType == PROXYTYPE_NOPROXY) + return GetPeerNameNext(lpSockAddr, lpSockAddrLen); + + if (GetLayerState() == notsock) + { + WSASetLastError(WSAENOTSOCK); + return FALSE; + } + else if (GetLayerState() != connected) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + else if (!m_nProxyPeerIP || !m_nProxyPeerPort) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + + ASSERT( m_ProxyData.nProxyType ); + BOOL res = GetPeerNameNext(lpSockAddr, lpSockAddrLen); + if (res) + { + LPSOCKADDR_IN addr = (LPSOCKADDR_IN)lpSockAddr; + addr->sin_port = (u_short)m_nProxyPeerPort; + addr->sin_addr.S_un.S_addr = m_nProxyPeerIP; + } + return res; +} + +int CAsyncProxySocketLayer::GetProxyType() const +{ + return m_ProxyData.nProxyType; +} + +void CAsyncProxySocketLayer::Close() +{ + m_ProxyData.strProxyHost.Empty(); + m_ProxyData.strProxyUser.Empty(); + m_ProxyData.strProxyPass.Empty(); + delete[] m_pProxyPeerHost; + m_pProxyPeerHost = NULL; + ClearBuffer(); + Reset(); + CloseNext(); +} + +void CAsyncProxySocketLayer::Reset() +{ + m_nProxyOpState = 0; + m_nProxyOpID = 0; +} + +int CAsyncProxySocketLayer::Send(const void* lpBuf, int nBufLen, int nFlags) +{ + if (m_nProxyOpID) + { + WSASetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + + return SendNext(lpBuf, nBufLen, nFlags); +} + +int CAsyncProxySocketLayer::Receive(void* lpBuf, int nBufLen, int nFlags) +{ + if (m_nProxyOpID) + { + WSASetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + + return ReceiveNext(lpBuf, nBufLen, nFlags); +} + +BOOL CAsyncProxySocketLayer::PrepareListen(unsigned long ip) +{ + if (GetLayerState()!=notsock && GetLayerState()!=unconnected) + return FALSE; + m_nProxyPeerIP = ip; + return TRUE; +} + +BOOL CAsyncProxySocketLayer::Accept( CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/ ) +{ + if (!m_ProxyData.nProxyType) + return AcceptNext(rConnectedSocket, lpSockAddr, lpSockAddrLen); + + GetPeerName(lpSockAddr, lpSockAddrLen); + return TRUE; +} + +CString GetProxyError(UINT nError) +{ + if (nError == PROXYERROR_NOERROR) + return _T("No proxy error"); + else if (nError == PROXYERROR_NOCONN) + return _T("Proxy connection failed"); + else if (nError == PROXYERROR_REQUESTFAILED) + return _T("Proxy request failed"); + else if (nError == PROXYERROR_AUTHREQUIRED) + return _T("Proxy authentication required"); + else if (nError == PROXYERROR_AUTHTYPEUNKNOWN) + return _T("Proxy authentication not supported"); + else if (nError == PROXYERROR_AUTHFAILED) + return _T("Proxy authentication failed"); + else if (nError == PROXYERROR_AUTHNOLOGON) + return _T("Proxy authentication required"); + else if (nError == PROXYERROR_CANTRESOLVEHOST) + return _T("Proxy hostname not resolved"); + else if (nError == PROXYSTATUS_LISTENSOCKETCREATED) + return _T("Proxy listen socket created"); + else{ + CString strError; + strError.Format(_T("Proxy-Error: %u"), nError); + return strError; + } +} diff --git a/AsyncProxySocketLayer.h b/AsyncProxySocketLayer.h new file mode 100644 index 00000000..af05ac9b --- /dev/null +++ b/AsyncProxySocketLayer.h @@ -0,0 +1,236 @@ +/*CAsyncProxySocketLayer by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.6 (2003-03-26) +-------------------------------------------------------- + +Introduction: +------------- + +This class is layer class for CAsyncSocketEx. With this class you +can connect through SOCKS4/5 and HTTP 1.1 proxies. This class works +as semi-transparent layer between CAsyncSocketEx and the actual socket. +This class is used in FileZilla, a powerful open-source FTP client. +It can be found under http://sourceforge.net/projects/filezilla +For more information about SOCKS4/5 goto +http://www.socks.nec.com/socksprot.html +For more information about HTTP 1.1 goto http://www.rfc-editor.org +and search for RFC2616 + +How to use? +----------- + +You don't have to change much in you already existing code to use +CAsyncProxySocketLayer. + +To use it, create an instance of CAsyncProxySocketLayer, call SetProxy +and attach it to a CAsyncSocketEx instance. + +You have to process OnLayerCallback in you CAsyncSocketEx instance as it will +receive all layer nofications. + +The following notifications are sent: + +//Error codes +PROXYERROR_NOERROR 0 +PROXYERROR_NOCONN 1 //Can't connect to proxy server, use GetLastError for more information +PROXYERROR_REQUESTFAILED 2 //Request failed, can't send data +PROXYERROR_AUTHREQUIRED 3 //Authentication required +PROXYERROR_AUTHTYPEUNKNOWN 4 //Authtype unknown or not supported +PROXYERROR_AUTHFAILED 5 //Authentication failed +PROXYERROR_AUTHNOLOGON 6 +PROXYERROR_CANTRESOLVEHOST 7 + +//Status messages +PROXYSTATUS_LISTENSOCKETCREATED 8 //Called when a listen socket was created successfully. Unlike the normal listen function, + //a socksified socket has to connect to the proxy to negotiate the details with the server + //on which the listen socket will be created + //The two parameters will contain the ip and port of the listen socket on the server. + +If you want to use CAsyncProxySocketLayer to create a listen socket, you +have to use this overloaded function: +BOOL PrepareListen(unsigned long serverIp); +serverIP is the IP of the server you are already connected +through the SOCKS proxy. You can't use listen sockets over a +SOCKS proxy without a primary connection. Listen sockets are only +supported by SOCKS proxies, this won't work with HTTP proxies. +When the listen socket is created successfully, the PROXYSTATUS_LISTENSOCKETCREATED +notification is sent. The parameters will tell you the ip and the port of the listen socket. +After it you have to handle the OnAccept message and accept the +connection. +Be carful when calling Accept: rConnected socket will NOT be filled! Instead use the instance which created the +listen socket, it will handle the data connection. +If you want to accept more than one connection, you have to create a listing socket for each of them! + +Description of important functions and their parameters: +-------------------------------------------------------- + +void SetProxy(int nProxyType); +void SetProxy(int nProxyType, const char * ProxyHost, int nProxyPort); +void SetProxy(int nProxyType, const char *, int nProxyPort, const char * ProxyUser, const char * ProxyPass); + +Call one of this functions to set the proxy type. +Parametes: +- nProxyType specifies the Proxy Type. +- ProxyHost and nProxyPort specify the address of the proxy +- ProxyUser and ProxyPass are only available for SOCKS5 proxies. + +supported proxy types: +PROXYTYPE_NOPROXY +PROXYTYPE_SOCKS4 +PROXYTYPE_SOCKS4A +PROXYTYPE_SOCKS5 +PROXYTYPE_HTTP11 + +There are also some other functions: + +GetProxyPeerName +Like GetPeerName of CAsyncSocket, but returns the address of the +server connected through the proxy. If using proxies, GetPeerName +only returns the address of the proxy. + +int GetProxyType(); +Returns the used proxy + +const int GetLastProxyError() const; +Returns the last proxy error + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de + + +Version history +--------------- + +- 1.6 got rid of MFC +- 1.5 released CAsyncSocketExLayer version +- 1.4 added Unicode support +- 1.3 added basic HTTP1.1 authentication + fixed memory leak in SOCKS5 code + OnSocksOperationFailed will be called after Socket has been closed + fixed some minor bugs +- 1.2 renamed into CAsyncProxySocketLayer + added HTTP1.1 proxy support +- 1.1 fixes all known bugs, mostly with SOCKS5 authentication +- 1.0 initial release +*/ +#pragma once +#include "AsyncSocketExLayer.h" + +class CAsyncProxySocketLayer : public CAsyncSocketExLayer +{ +// Attribute +public: + +// Operationen +public: + CAsyncProxySocketLayer(); + virtual ~CAsyncProxySocketLayer(); + +// Überschreibungen +public: + virtual void Close(); + virtual BOOL Connect(LPCSTR lpHostAddress, UINT nHostPort); + virtual BOOL Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen); + virtual BOOL Listen(int nConnectionBacklog); + + void SetProxy(int nProxyType); //Only PROXYTYPE_NOPROXY + void SetProxy(int nProxyType, const CStringA& strProxyHost, int ProxyPort); //May not be PROXYTYPE_NOPROXY + void SetProxy(int nProxyType, const CStringA& strProxyHost, int ProxyPort, const CStringA& strProxyUser, const CStringA& strProxyPass); //Only SOCKS5 and HTTP1.1 proxies + //Sets the proxy details. + //nProxyType - Type of the proxy. May be PROXYTYPE_NONE, PROXYTYPE_SOCKS4, PROXYTYPE_SOCKS5 or PROXYTYPE_HTTP11 + //ProxyHost - The address of the proxy. Can be either IP or URL + //ProxyPort - The port of the proxy + //ProxyUser - the username for SOCKS5 proxies + //ProxyPass - the password for SOCKS5 proxies + + //Prepare listen + BOOL PrepareListen(ULONG ip); + + //Returns the type of the proxy + int GetProxyType() const; + +#ifdef _AFX + virtual BOOL GetPeerName(CString& rPeerAddress, UINT& rPeerPort); +#endif + virtual BOOL GetPeerName(SOCKADDR* lpSockAddr, int* lpSockAddrLen); + +// Implementierung +protected: + virtual BOOL Accept(CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL); + virtual int Send(const void* lpBuf, int nBufLen, int nFlags = 0); + virtual int Receive(void* lpBuf, int nBufLen, int nFlags = 0); + + //Notification event handlers + virtual void OnAccept(int nErrorCode); + virtual void OnClose(int nErrorCode); + virtual void OnConnect(int nErrorCode); + virtual void OnReceive(int nErrorCode); + virtual void OnSend(int nErrorCode); + +private: + void Reset(); + void ClearBuffer(); //Clears the receive buffer + + char *m_pRecvBuffer; //The receive buffer + int m_nRecvBufferLen; //Length of the RecvBuffer + int m_nRecvBufferPos; //Position within the receive buffer + + char *m_pStrBuffer; //Recvbuffer needed by HTTP1.1 proxy + int m_iStrBuffSize; + + int m_nProxyOpState; //State of an operation + int m_nProxyOpID; //Currently active operation (0 if none) + + u_short m_nProxyPeerPort;//Port of the server you are connected to, retrieve via GetPeerName + u_long m_nProxyPeerIP; //IP of the server you are connected to, retrieve via GetPeerName + typedef struct + { + int nProxyType; + CStringA strProxyHost; + int nProxyPort; + CStringA strProxyUser; + CStringA strProxyPass; + BOOL bUseLogon; + } t_proxydata; //This structure will be used to hold the proxy details + t_proxydata m_ProxyData;//Structure to hold the data set by SetProxy + LPSTR m_pProxyPeerHost;//The host connected to +}; + +//Errorcodes +#define PROXYERROR_NOERROR 0 +#define PROXYERROR_NOCONN 1 //Can't connect to proxy server, use GetLastError for more information +#define PROXYERROR_REQUESTFAILED 2 //Request failed, can't send data +#define PROXYERROR_AUTHREQUIRED 3 //Authentication required +#define PROXYERROR_AUTHTYPEUNKNOWN 4 //Authtype unknown or not supported +#define PROXYERROR_AUTHFAILED 5 //Authentication failed +#define PROXYERROR_AUTHNOLOGON 6 +#define PROXYERROR_CANTRESOLVEHOST 7 + +//Status messages +#define PROXYSTATUS_LISTENSOCKETCREATED 8 //Called when a listen socket was created successfully. Unlike the normal listen function, + //a socksified socket has to connect to the proxy to negotiate the details with the server + //on which the listen socket will be created + //The two parameters will contain the ip and port of the listen socket on the server. + +CString GetProxyError(UINT nError); + +struct t_ListenSocketCreatedStruct +{ + unsigned long ip; + UINT nPort; +}; + +//Proxytypes +#define PROXYTYPE_NOPROXY 0 +#define PROXYTYPE_SOCKS4 1 +#define PROXYTYPE_SOCKS4A 2 +#define PROXYTYPE_SOCKS5 3 +#define PROXYTYPE_HTTP10 4 +#define PROXYTYPE_HTTP11 5 + +#define PROXYOP_CONNECT 1 +#define PROXYOP_BIND 2 diff --git a/AsyncSocketEx.cpp b/AsyncSocketEx.cpp new file mode 100644 index 00000000..f7c802e3 --- /dev/null +++ b/AsyncSocketEx.cpp @@ -0,0 +1,1097 @@ +/*CAsyncSocketEx by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.2 (2003-03-28) +-------------------------------------------------------- + +Introduction: +------------- + +CAsyncSocketEx is a replacement for the MFC class CAsyncSocket. +This class was written because CAsyncSocket is not the fastest WinSock +wrapper and it's very hard to add new functionality to CAsyncSocket +derived classes. This class offers the same functionality as CAsyncSocket. +Also, CAsyncSocketEx offers some enhancements which were not possible with +CAsyncSocket without some tricks. + +How do I use it? +---------------- +Basically exactly like CAsyncSocket. +To use CAsyncSocketEx, just replace all occurrences of CAsyncSocket in your +code with CAsyncSocketEx, if you did not enhance CAsyncSocket yourself in +any way, you won't have to change anything else in your code. + +Why is CAsyncSocketEx faster? +----------------------------- + +CAsyncSocketEx is slightly faster when dispatching notification event messages. +First have a look at the way CAsyncSocket works. For each thread that uses +CAsyncSocket, a window is created. CAsyncSocket calls WSAAsyncSelect with +the handle of that window. Until here, CAsyncSocketEx works the same way. +But CAsyncSocket uses only one window message (WM_SOCKET_NOTIFY) for all +sockets within one thread. When the window receive WM_SOCKET_NOTIFY, wParam +contains the socket handle and the window looks up an CAsyncSocket instance +using a map. CAsyncSocketEx works differently. It's helper window uses a +wide range of different window messages (WM_USER through 0xBFFF) and passes +a different message to WSAAsyncSelect for each socket. When a message in +the specified range is received, CAsyncSocketEx looks up the pointer to a +CAsyncSocketEx instance in an Array using the index of message - WM_USER. +As you can see, CAsyncSocketEx uses the helper window in a more efficient +way, as it don't have to use the slow maps to lookup it's own instance. +Still, speed increase is not very much, but it may be noticeable when using +a lot of sockets at the same time. +Please note that the changes do not affect the raw data throughput rate, +CAsyncSocketEx only dispatches the notification messages faster. + +What else does CAsyncSocketEx offer? +------------------------------------ + +CAsyncSocketEx offers a flexible layer system. One example is the proxy layer. +Just create an instance of the proxy layer, configure it and add it to the layer +chain of your CAsyncSocketEx instance. After that, you can connect through +proxies. +Benefit: You don't have to change much to use the layer system. +Another layer that is currently in development is the SSL layer to establish +SSL encrypted connections. + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de +*/ + +#include "stdafx.h" +#include "DebugHelpers.h" +#include "AsyncSocketEx.h" + +#ifndef NOLAYERS +#include "AsyncSocketExLayer.h" +#endif //NOLAYERS + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#ifndef CCRITICALSECTIONWRAPPERINCLUDED +class CCriticalSectionWrapper +{ +public: + CCriticalSectionWrapper() + { + InitializeCriticalSection(&m_criticalSection); + } + + ~CCriticalSectionWrapper() + { + DeleteCriticalSection(&m_criticalSection); + } + + void Lock() + { + EnterCriticalSection(&m_criticalSection); + } + void Unlock() + { + LeaveCriticalSection(&m_criticalSection); + } +protected: + CRITICAL_SECTION m_criticalSection; +}; +#define CCRITICALSECTIONWRAPPERINCLUDED +#endif + +CCriticalSectionWrapper CAsyncSocketEx::m_sGlobalCriticalSection; +CAsyncSocketEx::t_AsyncSocketExThreadDataList *CAsyncSocketEx::m_spAsyncSocketExThreadDataList = 0; + +#ifndef _AFX +#ifndef VERIFY +#define VERIFY(x) (void(x)) +#endif //VERIFY +#ifndef ASSERT +#define ASSERT(x) +#endif //ASSERT +#endif //_AFX + +///////////////////////////// +//Helper Window class + +class CAsyncSocketExHelperWindow +{ +public: + CAsyncSocketExHelperWindow() + { + //Initialize data + m_pAsyncSocketExWindowData = new t_AsyncSocketExWindowData[512]; //Reserve space for 512 active sockets + memset(m_pAsyncSocketExWindowData, 0, 512*sizeof(t_AsyncSocketExWindowData)); + m_nWindowDataSize = 512; + m_nSocketCount = 0; + m_nWindowDataPos = 0; + + //Create window + WNDCLASSEX wndclass; + wndclass.cbSize = sizeof wndclass; + wndclass.style = 0; + wndclass.lpfnWndProc = WindowProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = GetModuleHandle(0); + wndclass.hIcon = 0; + wndclass.hCursor = 0; + wndclass.hbrBackground = 0; + wndclass.lpszMenuName = 0; + wndclass.lpszClassName = _T("CAsyncSocketEx Helper Window"); + wndclass.hIconSm = 0; + RegisterClassEx(&wndclass); + + m_hWnd = CreateWindow(_T("CAsyncSocketEx Helper Window"), _T("CAsyncSocketEx Helper Window"), 0, 0, 0, 0, 0, 0, 0, 0, GetModuleHandle(0)); + ASSERT( m_hWnd ); + SetWindowLong(m_hWnd, GWL_USERDATA, (LONG)this); + }; + + virtual ~CAsyncSocketExHelperWindow() + { + //Clean up socket storage + delete[] m_pAsyncSocketExWindowData; + m_pAsyncSocketExWindowData = 0; + m_nWindowDataSize = 0; + m_nSocketCount = 0; + + //Destroy window + if (m_hWnd) + { + DestroyWindow(m_hWnd); + m_hWnd = 0; + } + } + + //Adds a socket to the list of attached sockets + BOOL AddSocket(CAsyncSocketEx *pSocket, int &nSocketIndex) + { + ASSERT( pSocket ); + if (!m_nWindowDataSize) + { + ASSERT( !m_nSocketCount ); + m_nWindowDataSize = 512; + m_pAsyncSocketExWindowData = new t_AsyncSocketExWindowData[512]; //Reserve space for 512 active sockets + memset(m_pAsyncSocketExWindowData, 0, 512 * sizeof(t_AsyncSocketExWindowData)); + } + + if (nSocketIndex != -1) + { + ASSERT( m_pAsyncSocketExWindowData ); + ASSERT( m_nWindowDataSize>nSocketIndex ); + ASSERT( m_pAsyncSocketExWindowData[nSocketIndex].m_pSocket == pSocket ); + ASSERT( m_nSocketCount ); + return TRUE; + } + + //Increase socket storage if too small + if (m_nSocketCount >= m_nWindowDataSize - 10) + { + int nOldWindowDataSize = m_nWindowDataSize; + ASSERT( m_nWindowDataSize < MAX_SOCKETS ); + m_nWindowDataSize += 512; + if (m_nWindowDataSize > MAX_SOCKETS) + m_nWindowDataSize = MAX_SOCKETS; + + t_AsyncSocketExWindowData* tmp = m_pAsyncSocketExWindowData; + m_pAsyncSocketExWindowData = new t_AsyncSocketExWindowData[m_nWindowDataSize]; + memcpy(m_pAsyncSocketExWindowData, tmp, nOldWindowDataSize * sizeof(t_AsyncSocketExWindowData)); + memset(m_pAsyncSocketExWindowData + nOldWindowDataSize, 0, (m_nWindowDataSize - nOldWindowDataSize) * sizeof(t_AsyncSocketExWindowData)); + delete[] tmp; + } + + //Search for free slot + for (int i = m_nWindowDataPos; i < m_nWindowDataSize + m_nWindowDataPos; i++) + { + if (m_pAsyncSocketExWindowData[i % m_nWindowDataSize].m_pSocket == NULL) + { + m_pAsyncSocketExWindowData[i % m_nWindowDataSize].m_pSocket = pSocket; + nSocketIndex = i % m_nWindowDataSize; + m_nWindowDataPos = (i + 1) % m_nWindowDataSize; + m_nSocketCount++; + return TRUE; + } + } + + //No slot found, maybe there are too much sockets! + return FALSE; + } + + //Removes a socket from the socket storage + BOOL RemoveSocket(CAsyncSocketEx *pSocket, int &nSocketIndex) + { + UNREFERENCED_PARAMETER(pSocket); + ASSERT( pSocket ); + + if (nSocketIndex == -1) + return TRUE; + + ASSERT( m_pAsyncSocketExWindowData ); + ASSERT( m_nWindowDataSize > 0 ); + ASSERT( m_nSocketCount > 0 ); + ASSERT( m_pAsyncSocketExWindowData[nSocketIndex].m_pSocket == pSocket ); + m_pAsyncSocketExWindowData[nSocketIndex].m_pSocket = 0; + nSocketIndex = -1; + m_nSocketCount--; + + return TRUE; + } + + //Processes event notifications sent by the sockets or the layers + static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { +#if !NO_USE_CLIENT_TCP_CATCH_ALL_HANDLER + try + { +#endif//!NO_USE_CLIENT_TCP_CATCH_ALL_HANDLER + if (message >= WM_SOCKETEX_NOTIFY) + { + //Verify parameters + ASSERT( hWnd ); + CAsyncSocketExHelperWindow *pWnd = (CAsyncSocketExHelperWindow *)GetWindowLong(hWnd, GWL_USERDATA); + ASSERT( pWnd ); + + if (message < (UINT)(WM_SOCKETEX_NOTIFY + pWnd->m_nWindowDataSize)) //Index is within socket storage + { + //Lookup socket and verify if it's valid + CAsyncSocketEx *pSocket = pWnd->m_pAsyncSocketExWindowData[message - WM_SOCKETEX_NOTIFY].m_pSocket; + SOCKET hSocket = wParam; + if (!pSocket) + return 0; + if (hSocket == INVALID_SOCKET) + return 0; + if (pSocket->m_SocketData.hSocket != hSocket) + return 0; + + int nEvent = lParam & 0xFFFF; + int nErrorCode = lParam >> 16; + + //Dispatch notification + #ifndef NOLAYERS + if (!pSocket->m_pFirstLayer) + { + #endif //NOLAYERS + //Dispatch to CAsyncSocketEx instance + switch (nEvent) + { + case FD_READ: + { + DWORD nBytes; + if (!pSocket->IOCtl(FIONREAD, &nBytes)) + nErrorCode = WSAGetLastError(); + if (nBytes != 0 || nErrorCode != 0) + pSocket->OnReceive(nErrorCode); + break; + } + case FD_FORCEREAD: //Forceread does not check if there's data waiting + pSocket->OnReceive(nErrorCode); + break; + case FD_WRITE: + pSocket->OnSend(nErrorCode); + break; + case FD_CONNECT: + pSocket->OnConnect(nErrorCode); + break; + case FD_ACCEPT: + pSocket->OnAccept(nErrorCode); + break; + case FD_CLOSE: + pSocket->OnClose(nErrorCode); + break; + } + } + #ifndef NOLAYERS + else //Dispatch notification to the lowest layer + { + if (nEvent == FD_READ) + { + DWORD nBytes; + if (!pSocket->IOCtl(FIONREAD, &nBytes)) + nErrorCode = WSAGetLastError(); + if (nBytes != 0 || nErrorCode != 0) + pSocket->m_pLastLayer->CallEvent(nEvent, nErrorCode); + } + else + pSocket->m_pLastLayer->CallEvent(nEvent, nErrorCode); + } + } + #endif //NOLAYERS + return 0; + } + #ifndef NOLAYERS + else if (message == WM_SOCKETEX_TRIGGER) //Notification event sent by a layer + { + //Verify parameters, lookup socket and notification message + if (!wParam) + return 0; + CAsyncSocketEx *pSocket = (CAsyncSocketEx *)wParam; + CAsyncSocketExLayer::t_LayerNotifyMsg *pMsg = (CAsyncSocketExLayer::t_LayerNotifyMsg *)lParam; + if (pSocket->m_SocketData.hSocket == INVALID_SOCKET) { + delete pMsg; + return 0; + } + + int nEvent = pMsg->lEvent & 0xFFFF; + int nErrorCode = pMsg->lEvent >> 16; + + //Dispatch to layer + if (pMsg->pLayer) + pMsg->pLayer->CallEvent(nEvent, nErrorCode); + else + { + //Dispatch to socket class + switch (nEvent) + { + case FD_READ: + if (pSocket->m_lEvent & FD_READ) + pSocket->OnReceive(nErrorCode); + break; + case FD_FORCEREAD: + if (pSocket->m_lEvent & FD_FORCEREAD) + pSocket->OnReceive(nErrorCode); + break; + case FD_WRITE: + if (pSocket->m_lEvent & FD_WRITE) + pSocket->OnSend(nErrorCode); + break; + case FD_CONNECT: + if (pSocket->m_lEvent & FD_CONNECT) + pSocket->OnConnect(nErrorCode); + break; + case FD_ACCEPT: + if (pSocket->m_lEvent & FD_ACCEPT) + pSocket->OnAccept(nErrorCode); + break; + case FD_CLOSE: + if (pSocket->m_lEvent & FD_CLOSE) + pSocket->OnClose(nErrorCode); + break; + } + } + delete pMsg; + return 0; + } + #endif //NOLAYERS + else if (message == WM_SOCKETEX_GETHOST) + { + //WSAAsyncGetHostByName reply + + //Verify parameters + ASSERT( hWnd ); + CAsyncSocketExHelperWindow *pWnd = (CAsyncSocketExHelperWindow *)GetWindowLong(hWnd, GWL_USERDATA); + ASSERT( pWnd ); + + CAsyncSocketEx *pSocket = NULL; + int i; + for (i = 0; i < pWnd->m_nWindowDataSize; i++) + { + pSocket = pWnd->m_pAsyncSocketExWindowData[i].m_pSocket; + if (pSocket && pSocket->m_hAsyncGetHostByNameHandle && + pSocket->m_hAsyncGetHostByNameHandle == (HANDLE)wParam) + break; + } + if (i == pWnd->m_nWindowDataSize) + return 0; + + int nErrorCode = lParam >> 16; + if (nErrorCode) { + pSocket->OnConnect(nErrorCode); + // Do *NOT* access 'pSocket', it may already have been deleted (CServerConnect) + //delete[] pSocket->m_pAsyncGetHostByNameBuffer; + //pSocket->m_pAsyncGetHostByNameBuffer = 0; + //pSocket->m_hAsyncGetHostByNameHandle = 0; + return 0; + } + + SOCKADDR_IN sockAddr = {0}; + sockAddr.sin_family = AF_INET; + sockAddr.sin_addr.s_addr = ((LPIN_ADDR)((LPHOSTENT)pSocket->m_pAsyncGetHostByNameBuffer)->h_addr)->s_addr; + sockAddr.sin_port = htons((u_short)pSocket->m_nAsyncGetHostByNamePort); + + if (!pSocket->OnHostNameResolved(&sockAddr)) { + // Do *NOT* access 'pSocket', it may already have been deleted (CServerConnect) + //delete[] pSocket->m_pAsyncGetHostByNameBuffer; + //pSocket->m_pAsyncGetHostByNameBuffer = 0; + //pSocket->m_hAsyncGetHostByNameHandle = 0; + return 0; + } + + BOOL res = pSocket->Connect((SOCKADDR*)&sockAddr, sizeof(sockAddr)); + delete[] pSocket->m_pAsyncGetHostByNameBuffer; + pSocket->m_pAsyncGetHostByNameBuffer = 0; + pSocket->m_hAsyncGetHostByNameHandle = 0; + + if (!res && GetLastError() != WSAEWOULDBLOCK) + pSocket->OnConnect(GetLastError()); + + return 0; + } + return DefWindowProc(hWnd, message, wParam, lParam); +#if !NO_USE_CLIENT_TCP_CATCH_ALL_HANDLER + } + catch(CException* e){ + TCHAR szError[1024]; + e->GetErrorMessage(szError, ARRSIZE(szError)); + const CRuntimeClass* pRuntimeClass = e->GetRuntimeClass(); + LPCSTR pszClassName = (pRuntimeClass) ? pRuntimeClass->m_lpszClassName : NULL; + if (!pszClassName) + pszClassName = "CException"; + TRACE(_T("*** Unknown %hs exception in CAsyncSocketExHelperWindow::WindowProc - %s\n"), pszClassName, szError); + e->Delete(); + } + catch (...) { + // TODO: This exception handler should definitively *not* be here. Though we seem to need it to + // catch some very strange crashs which deal with socket deletion problems in the client's TCP socket. + TRACE("*** Unknown exception in CAsyncSocketExHelperWindow::WindowProc\n"); + ASSERT(0); + } + return 0; +#endif//!NO_USE_CLIENT_TCP_CATCH_ALL_HANDLER + } + + HWND CAsyncSocketExHelperWindow::GetHwnd() + { + return m_hWnd; + } + +private: + HWND m_hWnd; + struct t_AsyncSocketExWindowData + { + CAsyncSocketEx *m_pSocket; + } *m_pAsyncSocketExWindowData; + int m_nWindowDataSize; + int m_nWindowDataPos; + int m_nSocketCount; +}; + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +IMPLEMENT_DYNAMIC(CAsyncSocketEx, CObject) + +CAsyncSocketEx::CAsyncSocketEx() +{ + m_SocketData.hSocket = INVALID_SOCKET; + m_SocketData.nSocketIndex = -1; + m_pLocalAsyncSocketExThreadData = 0; +#ifndef NOLAYERS + m_pFirstLayer = 0; + m_pLastLayer = 0; +#endif //NOLAYERS + m_pAsyncGetHostByNameBuffer = NULL; + m_hAsyncGetHostByNameHandle = NULL; +} + +CAsyncSocketEx::~CAsyncSocketEx() +{ + Close(); + FreeAsyncSocketExInstance(); +} + +BOOL CAsyncSocketEx::Create(UINT nSocketPort /*=0*/, int nSocketType /*=SOCK_STREAM*/, + long lEvent /*=FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE*/, + LPCSTR lpszSocketAddress /*=NULL*/, BOOL bReuseAddr /*=FALSE*/ ) +{ + //Close the socket, although this should not happen + if (GetSocketHandle() != INVALID_SOCKET) { + ASSERT(0); + WSASetLastError(WSAEALREADY); + return FALSE; + } + + if (!InitAsyncSocketExInstance()) { + ASSERT(0); + WSASetLastError(WSANOTINITIALISED); + return FALSE; + } + +#ifndef NOLAYERS + if (m_pFirstLayer) + return m_pFirstLayer->Create(nSocketPort, nSocketType, lEvent, lpszSocketAddress); + else +#endif //NOLAYERS + { + SOCKET hSocket = socket(AF_INET, nSocketType, 0); + if (hSocket == INVALID_SOCKET) + return FALSE; + m_SocketData.hSocket = hSocket; + + AttachHandle(hSocket); + if (!AsyncSelect(lEvent)) { + Close(); + return FALSE; + } +#ifndef NOLAYERS + if (m_pFirstLayer) + { + if (WSAAsyncSelect(m_SocketData.hSocket, GetHelperWindowHandle(), m_SocketData.nSocketIndex + WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE)) { + Close(); + return FALSE; + } + } +#endif //NOLAYERS + + if (bReuseAddr) + { + // Has to be done right before binding the socket! + int iOptVal = 1; + VERIFY( SetSockOpt(SO_REUSEADDR, &iOptVal, sizeof iOptVal) ); + } + + if (!Bind(nSocketPort, lpszSocketAddress)) { + Close(); + return FALSE; + } + } + return TRUE; +} + +void CAsyncSocketEx::OnReceive(int /*nErrorCode*/) +{ +} + +void CAsyncSocketEx::OnSend(int /*nErrorCode*/) +{ +} + +void CAsyncSocketEx::OnConnect(int /*nErrorCode*/) +{ +} + +void CAsyncSocketEx::OnAccept(int /*nErrorCode*/) +{ +} + +void CAsyncSocketEx::OnClose(int /*nErrorCode*/) +{ +} + +BOOL CAsyncSocketEx::OnHostNameResolved(const SOCKADDR_IN * /*pSockAddr*/) +{ + return TRUE; +} + +BOOL CAsyncSocketEx::Bind(UINT nSocketPort, LPCSTR lpszSocketAddress) +{ + SOCKADDR_IN sockAddr = {0}; + if (lpszSocketAddress == NULL) + sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); + else + { + sockAddr.sin_addr.s_addr = inet_addr(lpszSocketAddress); + if (sockAddr.sin_addr.s_addr == INADDR_NONE) { + WSASetLastError(WSAEINVAL); + return FALSE; + } + } + sockAddr.sin_family = AF_INET; + sockAddr.sin_port = htons((u_short)nSocketPort); + return Bind((SOCKADDR*)&sockAddr, sizeof(sockAddr)); +} + +BOOL CAsyncSocketEx::Bind(const SOCKADDR* lpSockAddr, int nSockAddrLen) +{ + if (!bind(m_SocketData.hSocket, lpSockAddr, nSockAddrLen)) + return TRUE; + else + return FALSE; +} + +void CAsyncSocketEx::AttachHandle(SOCKET /*hSocket*/) +{ + ASSERT( m_pLocalAsyncSocketExThreadData ); + VERIFY( m_pLocalAsyncSocketExThreadData->m_pHelperWindow->AddSocket(this, m_SocketData.nSocketIndex) ); +} + +void CAsyncSocketEx::DetachHandle(SOCKET /*hSocket*/) +{ + ASSERT( m_pLocalAsyncSocketExThreadData ); + if (!m_pLocalAsyncSocketExThreadData) + return; + + ASSERT( m_pLocalAsyncSocketExThreadData->m_pHelperWindow ); + if (!m_pLocalAsyncSocketExThreadData->m_pHelperWindow) + return; + + VERIFY( m_pLocalAsyncSocketExThreadData->m_pHelperWindow->RemoveSocket(this, m_SocketData.nSocketIndex) ); +} + +void CAsyncSocketEx::Close() +{ +#ifndef NOLAYERS + if (m_pFirstLayer) + m_pFirstLayer->Close(); +#endif //NOLAYERS + if (m_SocketData.hSocket != INVALID_SOCKET) + { + VERIFY( closesocket(m_SocketData.hSocket) != SOCKET_ERROR ); + DetachHandle(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + } +#ifndef NOLAYERS + RemoveAllLayers(); +#endif //NOLAYERS + delete[] m_pAsyncGetHostByNameBuffer; + m_pAsyncGetHostByNameBuffer = NULL; + if (m_hAsyncGetHostByNameHandle) { + WSACancelAsyncRequest(m_hAsyncGetHostByNameHandle); + m_hAsyncGetHostByNameHandle = NULL; + } +} + +BOOL CAsyncSocketEx::InitAsyncSocketExInstance() +{ + //Check if already initialized + if (m_pLocalAsyncSocketExThreadData) + return TRUE; + + DWORD id = GetCurrentThreadId(); + BOOL bResult = TRUE; + m_sGlobalCriticalSection.Lock(); + + try{ + //Get thread specific data + if (m_spAsyncSocketExThreadDataList) + { + t_AsyncSocketExThreadDataList *pList = m_spAsyncSocketExThreadDataList; + while (pList) + { + ASSERT( pList->pThreadData ); + ASSERT( pList->pThreadData->nInstanceCount > 0 ); + + if (pList->pThreadData->nThreadId == id) + { + m_pLocalAsyncSocketExThreadData = pList->pThreadData; + m_pLocalAsyncSocketExThreadData->nInstanceCount++; + break; + } + pList = pList->pNext; + } + //Current thread yet has no sockets + if (!pList) + { + //Initialize data for current thread + pList = new t_AsyncSocketExThreadDataList; + pList->pNext = m_spAsyncSocketExThreadDataList; + m_spAsyncSocketExThreadDataList = pList; + m_pLocalAsyncSocketExThreadData = new t_AsyncSocketExThreadData; + m_pLocalAsyncSocketExThreadData->nInstanceCount = 1; + m_pLocalAsyncSocketExThreadData->nThreadId = id; + m_pLocalAsyncSocketExThreadData->m_pHelperWindow = new CAsyncSocketExHelperWindow; + m_spAsyncSocketExThreadDataList->pThreadData = m_pLocalAsyncSocketExThreadData; + } + } + else + { //No thread has instances of CAsyncSocketEx; Initialize data + m_spAsyncSocketExThreadDataList = new t_AsyncSocketExThreadDataList; + m_spAsyncSocketExThreadDataList->pNext = 0; + m_pLocalAsyncSocketExThreadData = new t_AsyncSocketExThreadData; + m_pLocalAsyncSocketExThreadData->nInstanceCount = 1; + m_pLocalAsyncSocketExThreadData->nThreadId = id; + m_pLocalAsyncSocketExThreadData->m_pHelperWindow = new CAsyncSocketExHelperWindow; + m_spAsyncSocketExThreadDataList->pThreadData = m_pLocalAsyncSocketExThreadData; + } + } + catch(...){ + TRACE("Unknown exception in CAsyncSocketEx::InitAsyncSocketExInstance()\n"); + ASSERT(0); + bResult = FALSE; + } + + m_sGlobalCriticalSection.Unlock(); + + return bResult; +} + +void CAsyncSocketEx::FreeAsyncSocketExInstance() +{ + //Check if already freed + if (!m_pLocalAsyncSocketExThreadData) + return; + + DWORD id = m_pLocalAsyncSocketExThreadData->nThreadId; + m_sGlobalCriticalSection.Lock(); + + try{ + ASSERT( m_spAsyncSocketExThreadDataList ); + t_AsyncSocketExThreadDataList *pList = m_spAsyncSocketExThreadDataList; + t_AsyncSocketExThreadDataList *pPrev = 0; + + //Search for data for current thread and decrease instance count + while (pList) + { + ASSERT( pList->pThreadData ); + ASSERT( pList->pThreadData->nInstanceCount > 0 ); + + if (pList->pThreadData->nThreadId == id) + { + ASSERT( m_pLocalAsyncSocketExThreadData == pList->pThreadData ); + m_pLocalAsyncSocketExThreadData->nInstanceCount--; + + //Freeing last instance? + //If so, destroy helper window + if (!m_pLocalAsyncSocketExThreadData->nInstanceCount) + { + delete m_pLocalAsyncSocketExThreadData->m_pHelperWindow; + delete m_pLocalAsyncSocketExThreadData; + if (pPrev) + pPrev->pNext = pList->pNext; + else + m_spAsyncSocketExThreadDataList = pList->pNext; + delete pList; + break; + } + break; + } + pPrev = pList; + pList = pList->pNext; + ASSERT( pList ); + } + } + catch(...){ + TRACE("Unknown exception in CAsyncSocketEx::FreeAsyncSocketExInstance()\n"); + ASSERT(0); + } + + m_sGlobalCriticalSection.Unlock(); +} + +int CAsyncSocketEx::Receive(void* lpBuf, int nBufLen, int nFlags /*=0*/) +{ +#ifndef NOLAYERS + if (m_pFirstLayer) + return m_pFirstLayer->Receive(lpBuf, nBufLen, nFlags); + else +#endif //NOLAYERS + return recv(m_SocketData.hSocket, (LPSTR)lpBuf, nBufLen, nFlags); +} + +int CAsyncSocketEx::Send(const void* lpBuf, int nBufLen, int nFlags /*=0*/) +{ +#ifndef NOLAYERS + if (m_pFirstLayer) + return m_pFirstLayer->Send(lpBuf, nBufLen, nFlags); + else +#endif //NOLAYERS + return send(m_SocketData.hSocket, (LPSTR)lpBuf, nBufLen, nFlags); +} + +BOOL CAsyncSocketEx::Connect(LPCSTR lpszHostAddress, UINT nHostPort) +{ + ASSERT( lpszHostAddress != NULL ); + +#ifndef NOLAYERS + if (m_pFirstLayer) + return m_pFirstLayer->Connect(lpszHostAddress, nHostPort); + else +#endif //NOLAYERS + { + SOCKADDR_IN sockAddr = {0}; + sockAddr.sin_addr.s_addr = inet_addr(lpszHostAddress); + if (sockAddr.sin_addr.s_addr == INADDR_NONE) + { + m_pAsyncGetHostByNameBuffer = new char[MAXGETHOSTSTRUCT]; + m_nAsyncGetHostByNamePort = nHostPort; + m_hAsyncGetHostByNameHandle = WSAAsyncGetHostByName(GetHelperWindowHandle(), WM_SOCKETEX_GETHOST, lpszHostAddress, m_pAsyncGetHostByNameBuffer, MAXGETHOSTSTRUCT); + if (!m_hAsyncGetHostByNameHandle) + return FALSE; + WSASetLastError(WSAEWOULDBLOCK); + return TRUE; + } + sockAddr.sin_family = AF_INET; + sockAddr.sin_port = htons((u_short)nHostPort); + return CAsyncSocketEx::Connect((SOCKADDR*)&sockAddr, sizeof(sockAddr)); + } +} + +BOOL CAsyncSocketEx::Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen) +{ +#ifndef NOLAYERS + if (m_pFirstLayer) + return m_pFirstLayer->Connect(lpSockAddr, nSockAddrLen); + else +#endif //NOLAYERS + return connect(m_SocketData.hSocket, lpSockAddr, nSockAddrLen) != SOCKET_ERROR; +} + +#ifdef _AFX +BOOL CAsyncSocketEx::GetPeerName(CString& rPeerAddress, UINT& rPeerPort) +{ +#ifndef NOLAYERS + if (m_pFirstLayer) + return m_pFirstLayer->GetPeerName(rPeerAddress, rPeerPort); +#endif NOLAYERS + + SOCKADDR_IN sockAddr = {0}; + int nSockAddrLen = sizeof(sockAddr); + BOOL bResult = GetPeerName((SOCKADDR*)&sockAddr, &nSockAddrLen); + if (bResult) + { + rPeerPort = ntohs(sockAddr.sin_port); + rPeerAddress = inet_ntoa(sockAddr.sin_addr); + } + return bResult; +} +#endif //AFX + +BOOL CAsyncSocketEx::GetPeerName(SOCKADDR* lpSockAddr, int* lpSockAddrLen) +{ +#ifndef NOLAYERS + if (m_pFirstLayer) + return m_pFirstLayer->GetPeerName(lpSockAddr, lpSockAddrLen); +#endif //NOLAYERS + + if (!getpeername(m_SocketData.hSocket, lpSockAddr, lpSockAddrLen)) + return TRUE; + else + return FALSE; +} + +#ifdef _AFX +BOOL CAsyncSocketEx::GetSockName(CString& rSocketAddress, UINT& rSocketPort) +{ + SOCKADDR_IN sockAddr = {0}; + int nSockAddrLen = sizeof(sockAddr); + BOOL bResult = GetSockName((SOCKADDR*)&sockAddr, &nSockAddrLen); + if (bResult) + { + rSocketPort = ntohs(sockAddr.sin_port); + rSocketAddress = inet_ntoa(sockAddr.sin_addr); + } + return bResult; +} +#endif + +BOOL CAsyncSocketEx::GetSockName(SOCKADDR* lpSockAddr, int* lpSockAddrLen) +{ + if (!getsockname(m_SocketData.hSocket, lpSockAddr, lpSockAddrLen)) + return TRUE; + else + return FALSE; +} + +BOOL CAsyncSocketEx::ShutDown(int nHow /*=sends*/) +{ +#ifndef NOLAYERS + if (m_pFirstLayer) + { + return m_pFirstLayer->ShutDown(); + } + else +#endif + { + if (!shutdown(m_SocketData.hSocket, nHow)) + return TRUE; + else + return FALSE; + } +} + +SOCKET CAsyncSocketEx::Detach() +{ + SOCKET socket = m_SocketData.hSocket; + DetachHandle(socket); + m_SocketData.hSocket = INVALID_SOCKET; + return socket; +} + +BOOL CAsyncSocketEx::Attach(SOCKET hSocket, long lEvent /*= FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE*/) +{ + if (hSocket == INVALID_SOCKET || !hSocket) + return FALSE; + VERIFY( InitAsyncSocketExInstance() ); + m_SocketData.hSocket = hSocket; + AttachHandle(hSocket); + return AsyncSelect(lEvent); +} + +BOOL CAsyncSocketEx::AsyncSelect(long lEvent /*= FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE*/) +{ + ASSERT( m_pLocalAsyncSocketExThreadData ); + m_lEvent = lEvent; +#ifndef NOLAYERS + if (!m_pFirstLayer) +#endif //NOLAYERS + { + if (!WSAAsyncSelect(m_SocketData.hSocket, GetHelperWindowHandle(), m_SocketData.nSocketIndex + WM_SOCKETEX_NOTIFY, lEvent)) + return TRUE; + else + return FALSE; + } + return TRUE; +} + +BOOL CAsyncSocketEx::Listen(int nConnectionBacklog /*=5*/) +{ +#ifndef NOLAYERS + if (m_pFirstLayer) + return m_pFirstLayer->Listen(nConnectionBacklog); +#endif //NOLAYERS + + if (!listen(m_SocketData.hSocket, nConnectionBacklog)) + return TRUE; + else + return FALSE; +} + +BOOL CAsyncSocketEx::Accept(CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/) +{ + ASSERT( rConnectedSocket.m_SocketData.hSocket == INVALID_SOCKET ); +#ifndef NOLAYERS + if (m_pFirstLayer) + { + return m_pFirstLayer->Accept(rConnectedSocket, lpSockAddr, lpSockAddrLen); + } + else +#endif //NOLAYERS + { + SOCKET hTemp = accept(m_SocketData.hSocket, lpSockAddr, lpSockAddrLen); + if (hTemp == INVALID_SOCKET) + return FALSE; + VERIFY(rConnectedSocket.InitAsyncSocketExInstance()); + rConnectedSocket.m_SocketData.hSocket = hTemp; + rConnectedSocket.AttachHandle(hTemp); + } + return TRUE; +} + +BOOL CAsyncSocketEx::IOCtl(long lCommand, DWORD* lpArgument) +{ + return ioctlsocket(m_SocketData.hSocket, lCommand, lpArgument) != SOCKET_ERROR; +} + +int CAsyncSocketEx::GetLastError() +{ + return WSAGetLastError(); +} + +BOOL CAsyncSocketEx::TriggerEvent(long lEvent) +{ + if (m_SocketData.hSocket == INVALID_SOCKET) + return FALSE; + + ASSERT( m_pLocalAsyncSocketExThreadData ); + ASSERT( m_pLocalAsyncSocketExThreadData->m_pHelperWindow ); + ASSERT( m_SocketData.nSocketIndex != -1 ); + +#ifndef NOLAYERS + if (m_pFirstLayer) + { + CAsyncSocketExLayer::t_LayerNotifyMsg *pMsg = new CAsyncSocketExLayer::t_LayerNotifyMsg; + pMsg->lEvent = lEvent & 0xFFFF; + pMsg->pLayer = 0; + BOOL res = PostMessage(GetHelperWindowHandle(), WM_SOCKETEX_TRIGGER, (WPARAM)this, (LPARAM)pMsg); + if (!res) + delete pMsg; + return res; + } + else +#endif //NOLAYERS + return PostMessage(GetHelperWindowHandle(), m_SocketData.nSocketIndex + WM_SOCKETEX_NOTIFY, m_SocketData.hSocket, lEvent & 0xFFFF); +} + +SOCKET CAsyncSocketEx::GetSocketHandle() +{ + return m_SocketData.hSocket; +} + +HWND CAsyncSocketEx::GetHelperWindowHandle() +{ + if (!m_pLocalAsyncSocketExThreadData) + return 0; + if (!m_pLocalAsyncSocketExThreadData->m_pHelperWindow) + return 0; + return m_pLocalAsyncSocketExThreadData->m_pHelperWindow->GetHwnd(); +} + +#ifndef NOLAYERS +BOOL CAsyncSocketEx::AddLayer(CAsyncSocketExLayer *pLayer) +{ + ASSERT( pLayer ); + if (m_SocketData.hSocket != INVALID_SOCKET) + return FALSE; + if (m_pFirstLayer) + { + ASSERT( m_pLastLayer ); + m_pLastLayer = m_pLastLayer->AddLayer(pLayer, this); + return m_pLastLayer ? TRUE : FALSE; + } + else + { + ASSERT( !m_pLastLayer ); + pLayer->Init(0, this); + m_pFirstLayer = pLayer; + m_pLastLayer = m_pFirstLayer; + } + return TRUE; +} + +void CAsyncSocketEx::RemoveAllLayers() +{ + m_pFirstLayer = 0; + m_pLastLayer = 0; +} + +int CAsyncSocketEx::OnLayerCallback(const CAsyncSocketExLayer *pLayer, int /*nType*/, int /*nCode*/, WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + UNREFERENCED_PARAMETER(pLayer); + ASSERT(pLayer); + return 1; +} +#endif //NOLAYERS + +BOOL CAsyncSocketEx::GetSockOpt(int nOptionName, void* lpOptionValue, int* lpOptionLen, int nLevel /*=SOL_SOCKET*/) +{ + return getsockopt(m_SocketData.hSocket, nLevel, nOptionName, (LPSTR)lpOptionValue, lpOptionLen) != SOCKET_ERROR; +} + +BOOL CAsyncSocketEx::SetSockOpt(int nOptionName, const void* lpOptionValue, int nOptionLen, int nLevel /*=SOL_SOCKET*/) +{ + return setsockopt(m_SocketData.hSocket, nLevel, nOptionName, (LPSTR)lpOptionValue, nOptionLen) != SOCKET_ERROR; +} + +#ifdef _DEBUG +void CAsyncSocketEx::AssertValid() const +{ + CObject::AssertValid(); + + (void)m_SocketData; + (void)m_lEvent; + (void)m_pAsyncGetHostByNameBuffer; + (void)m_hAsyncGetHostByNameHandle; + (void)m_nAsyncGetHostByNamePort; + + //Pointer to the data of the local thread +// struct t_AsyncSocketExThreadData +// { +// CAsyncSocketExHelperWindow *m_pHelperWindow; +// int nInstanceCount; +// DWORD nThreadId; +// } *m_pLocalAsyncSocketExThreadData; + + //List of the data structures for all threads +// static struct t_AsyncSocketExThreadDataList +// { +// t_AsyncSocketExThreadDataList *pNext; +// t_AsyncSocketExThreadData *pThreadData; +// } *m_spAsyncSocketExThreadDataList; + +#ifndef NOLAYERS + CHECK_PTR(m_pFirstLayer); + CHECK_PTR(m_pLastLayer); +#endif //NOLAYERS +} +#endif + +#ifdef _DEBUG +void CAsyncSocketEx::Dump(CDumpContext& dc) const +{ + CObject::Dump(dc); +} +#endif diff --git a/AsyncSocketEx.h b/AsyncSocketEx.h new file mode 100644 index 00000000..8b13245e --- /dev/null +++ b/AsyncSocketEx.h @@ -0,0 +1,287 @@ +/*CAsyncSocketEx by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.2 (2003-03-28) +-------------------------------------------------------- + +Introduction: +------------- + +CAsyncSocketEx is a replacement for the MFC class CAsyncSocket. +This class was written because CAsyncSocket is not the fastest WinSock +wrapper and it's very hard to add new functionality to CAsyncSocket +derived classes. This class offers the same functionality as CAsyncSocket. +Also, CAsyncSocketEx offers some enhancements which were not possible with +CAsyncSocket without some tricks. + +How do I use it? +---------------- +Basically exactly like CAsyncSocket. +To use CAsyncSocketEx, just replace all occurrences of CAsyncSocket in your +code with CAsyncSocketEx. If you did not enhance CAsyncSocket yourself in +any way, you won't have to change anything else in your code. + +Why is CAsyncSocketEx faster? +----------------------------- + +CAsyncSocketEx is slightly faster when dispatching notification event messages. +First have a look at the way CAsyncSocket works. For each thread that uses +CAsyncSocket, a window is created. CAsyncSocket calls WSAAsyncSelect with +the handle of that window. Until here, CAsyncSocketEx works the same way. +But CAsyncSocket uses only one window message (WM_SOCKET_NOTIFY) for all +sockets within one thread. When the window receive WM_SOCKET_NOTIFY, wParam +contains the socket handle and the window looks up an CAsyncSocket instance +using a map. CAsyncSocketEx works differently. It's helper window uses a +wide range of different window messages (WM_USER through 0xBFFF) and passes +a different message to WSAAsyncSelect for each socket. When a message in +the specified range is received, CAsyncSocketEx looks up the pointer to a +CAsyncSocketEx instance in an Array using the index of message - WM_USER. +As you can see, CAsyncSocketEx uses the helper window in a more efficient +way, as it don't have to use the slow maps to lookup it's own instance. +Still, speed increase is not very much, but it may be noticeable when using +a lot of sockets at the same time. +Please note that the changes do not affect the raw data throughput rate, +CAsyncSocketEx only dispatches the notification messages faster. + +What else does CAsyncSocketEx offer? +------------------------------------ + +CAsyncSocketEx offers a flexible layer system. One example is the proxy layer. +Just create an instance of the proxy layer, configure it and add it to the layer +chain of your CAsyncSocketEx instance. After that, you can connect through +proxies. +Benefit: You don't have to change much to use the layer system. +Another layer that is currently in development is the SSL layer to establish +SSL encrypted connections. + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de +*/ +#pragma once + +#define FD_FORCEREAD (1 << 15) + +#if defined(_AFXDLL) && (_MFC_VER==0x0700) +// See also: KB article Q316312 - BUG: Mfc70.lib Does Not Export AfxGetModuleThreadState +#define _afxSockThreadState AfxGetModuleState()->m_thread.GetDataNA() +#else +#define _afxSockThreadState AfxGetModuleThreadState() +#endif +#define _AFX_SOCK_THREAD_STATE AFX_MODULE_THREAD_STATE + + +class CAsyncSocketExHelperWindow; + +#define WM_SOCKETEX_TRIGGER (WM_USER + 0x101 + 0) // 0x0501 +#define WM_SOCKETEX_GETHOST (WM_USER + 0x101 + 1) // 0x0502 +#define WM_SOCKETEX_NOTIFY (WM_USER + 0x101 + 2) // 0x0503 +#define MAX_SOCKETS (0xBFFF - WM_SOCKETEX_NOTIFY + 1) // 0xBAFD 47869d + + +#ifndef NOLAYERS +class CAsyncSocketExLayer; +#endif //NOLAYERS +class CCriticalSectionWrapper; +class CAsyncSocketEx : public CObject +{ + DECLARE_DYNAMIC(CAsyncSocketEx) +public: + /////////////////////////////////////// + //Functions that imitate CAsyncSocket// + /////////////////////////////////////// + + //Construction + //------------ + + //Constructs a CAsyncSocketEx object. + CAsyncSocketEx(); + virtual ~CAsyncSocketEx(); + + //Creates a socket. + BOOL Create(UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, + long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, + LPCSTR lpszSocketAddress = NULL, BOOL bReuseAddr = FALSE); + + + //Attributes + //---------- + + //Attaches a socket handle to a CAsyncSocketEx object. + BOOL Attach(SOCKET hSocket, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE); + + //Detaches a socket handle from a CAsyncSocketEx object. + SOCKET Detach(); + + //Gets the error status for the last operation that failed. + static int GetLastError(); + + //Gets the address of the peer socket to which the socket is connected. +#ifdef _AFX + BOOL GetPeerName(CString& rPeerAddress, UINT& rPeerPort); +#endif + BOOL GetPeerName(SOCKADDR* lpSockAddr, int* lpSockAddrLen); + + //Gets the local name for a socket. +#ifdef _AFX + BOOL GetSockName(CString& rSocketAddress, UINT& rSocketPort); +#endif + BOOL GetSockName(SOCKADDR* lpSockAddr, int* lpSockAddrLen); + + //Retrieves a socket option. + BOOL GetSockOpt(int nOptionName, void* lpOptionValue, int* lpOptionLen, int nLevel = SOL_SOCKET); + + //Sets a socket option. + BOOL SetSockOpt(int nOptionName, const void* lpOptionValue, int nOptionLen, int nLevel = SOL_SOCKET); + + + //Operations + //---------- + + //Accepts a connection on the socket. + virtual BOOL Accept(CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL); + + //Requests event notification for the socket. + BOOL AsyncSelect(long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE); + + //Associates a local address with the socket. + BOOL Bind(UINT nSocketPort, LPCSTR lpszSocketAddress); + BOOL Bind(const SOCKADDR* lpSockAddr, int nSockAddrLen); + + //Closes the socket. + virtual void Close(); + + //Establishes a connection to a peer socket. + virtual BOOL Connect(LPCSTR lpszHostAddress, UINT nHostPort); + virtual BOOL Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen); + + //Controls the mode of the socket. + BOOL IOCtl(long lCommand, DWORD* lpArgument); + + //Establishes a socket to listen for incoming connection requests. + BOOL Listen(int nConnectionBacklog = 5); + + //Receives data from the socket. + virtual int Receive(void* lpBuf, int nBufLen, int nFlags = 0); + + //Sends data to a connected socket. + virtual int Send(const void* lpBuf, int nBufLen, int nFlags = 0); + + //Disables Send and/or Receive calls on the socket. + BOOL ShutDown(int nHow = sends); + enum { receives = 0, sends = 1, both = 2 }; + + //Overridable Notification Functions + //---------------------------------- + + //Notifies a listening socket that it can accept pending connection requests by calling Accept. + virtual void OnAccept(int nErrorCode); + + //Notifies a socket that the socket connected to it has closed. + virtual void OnClose(int nErrorCode); + + //Notifies a connecting socket that the connection attempt is complete, whether successfully or in error. + virtual void OnConnect(int nErrorCode); + + //Notifies a listening socket that there is data to be retrieved by calling Receive. + virtual void OnReceive(int nErrorCode); + + //Notifies a socket that it can send data by calling Send. + virtual void OnSend(int nErrorCode); + + virtual BOOL OnHostNameResolved(const SOCKADDR_IN *pSockAddr); + + //////////////////////// + //Additional functions// + //////////////////////// + +#ifndef NOLAYERS + //Resets layer chain. + virtual void RemoveAllLayers(); + + //Attaches a new layer to the socket. + BOOL AddLayer(CAsyncSocketExLayer *pLayer); +#endif //NOLAYERS + + //Returns the handle of the socket. + SOCKET GetSocketHandle(); + + //Trigers an event on the socket + // Any combination of FD_READ, FD_WRITE, FD_CLOSE, FD_ACCEPT, FD_CONNECT and FD_FORCEREAD is valid for lEvent. + BOOL TriggerEvent(long lEvent); + +#ifdef _DEBUG + // Diagnostic Support + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +protected: + //Strucure to hold the socket data + struct t_AsyncSocketExData + { + SOCKET hSocket; //Socket handle + int nSocketIndex; //Index of socket, required by CAsyncSocketExHelperWindow + } m_SocketData; + + //If using layers, only the events specified with m_lEvent will send to the event handlers. + long m_lEvent; + + //AsyncGetHostByName + char *m_pAsyncGetHostByNameBuffer; //Buffer for hostend structure + HANDLE m_hAsyncGetHostByNameHandle; //TaskHandle + int m_nAsyncGetHostByNamePort; //Port to connect to + + //Returns the handle of the helper window + HWND GetHelperWindowHandle(); + + //Attaches socket handle to helper window + void AttachHandle(SOCKET hSocket); + + //Detaches socket handle to helper window + void DetachHandle(SOCKET hSocket); + + //Critical section for thread synchronization + static CCriticalSectionWrapper m_sGlobalCriticalSection; + + //Pointer to the data of the local thread + struct t_AsyncSocketExThreadData + { + CAsyncSocketExHelperWindow *m_pHelperWindow; + int nInstanceCount; + DWORD nThreadId; + } *m_pLocalAsyncSocketExThreadData; + + //List of the data structures for all threads + static struct t_AsyncSocketExThreadDataList + { + t_AsyncSocketExThreadDataList *pNext; + t_AsyncSocketExThreadData *pThreadData; + } *m_spAsyncSocketExThreadDataList; + + //Initializes Thread data and helper window, fills m_pLocalAsyncSocketExThreadData + BOOL InitAsyncSocketExInstance(); + + //Destroys helper window after last instance of CAsyncSocketEx in current thread has been closed + void FreeAsyncSocketExInstance(); + +#ifndef NOLAYERS + //Layer chain + CAsyncSocketExLayer *m_pFirstLayer; + CAsyncSocketExLayer *m_pLastLayer; + + friend CAsyncSocketExLayer; + + //Called by the layers to notify application of some events + virtual int OnLayerCallback(const CAsyncSocketExLayer *pLayer, int nType, int nCode, WPARAM wParam, LPARAM lParam); +#endif //NOLAYERS + + friend CAsyncSocketExHelperWindow; +}; + +#ifndef NOLAYERS +#define LAYERCALLBACK_STATECHANGE 0 +#define LAYERCALLBACK_LAYERSPECIFIC 1 +#endif //NOLAYERS diff --git a/AsyncSocketExLayer.cpp b/AsyncSocketExLayer.cpp new file mode 100644 index 00000000..bb3d9438 --- /dev/null +++ b/AsyncSocketExLayer.cpp @@ -0,0 +1,584 @@ +/*CAsyncSocketEx by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.1 (2002-11-01) +-------------------------------------------------------- + +Introduction: +------------- + +CAsyncSocketEx is a replacement for the MFC class CAsyncSocket. +This class was written because CAsyncSocket is not the fastest WinSock +wrapper and it's very hard to add new functionality to CAsyncSocket +derived classes. This class offers the same functionality as CAsyncSocket. +Also, CAsyncSocketEx offers some enhancements which were not possible with +CAsyncSocket without some tricks. + +How do I use it? +---------------- +Basically exactly like CAsyncSocket. +To use CAsyncSocketEx, just replace all occurrences of CAsyncSocket in your +code with CAsyncSocketEx, if you did not enhance CAsyncSocket yourself in +any way, you won't have to change anything else in your code. + +Why is CAsyncSocketEx faster? +----------------------------- + +CAsyncSocketEx is slightly faster when dispatching notification event messages. +First have a look at the way CAsyncSocket works. For each thread that uses +CAsyncSocket, a window is created. CAsyncSocket calls WSAAsyncSelect with +the handle of that window. Until here, CAsyncSocketEx works the same way. +But CAsyncSocket uses only one window message (WM_SOCKET_NOTIFY) for all +sockets within one thread. When the window receive WM_SOCKET_NOTIFY, wParam +contains the socket handle and the window looks up an CAsyncSocket instance +using a map. CAsyncSocketEx works differently. It's helper window uses a +wide range of different window messages (WM_USER through 0xBFFF) and passes +a different message to WSAAsyncSelect for each socket. When a message in +the specified range is received, CAsyncSocketEx looks up the pointer to a +CAsyncSocketEx instance in an Array using the index of message - WM_USER. +As you can see, CAsyncSocketEx uses the helper window in a more efficient +way, as it don't have to use the slow maps to lookup it's own instance. +Still, speed increase is not very much, but it may be noticeable when using +a lot of sockets at the same time. +Please note that the changes do not affect the raw data throughput rate, +CAsyncSocketEx only dispatches the notification messages faster. + +What else does CAsyncSocketEx offer? +------------------------------------ + +CAsyncSocketEx offers a flexible layer system. One example is the proxy layer. +Just create an instance of the proxy layer, configure it and add it to the layer +chain of your CAsyncSocketEx instance. After that, you can connect through +proxies. +Benefit: You don't have to change much to use the layer system. +Another layer that is currently in development is the SSL layer to establish +SSL encrypted connections. + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de +*/ +#include "stdafx.h" +#include "AsyncSocketExLayer.h" + +#include "AsyncSocketEx.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +CAsyncSocketExLayer::CAsyncSocketExLayer() +{ + m_pOwnerSocket=0; + m_pNextLayer=0; + + m_nLayerState=notsock; + m_nCriticalError=0; +} + +CAsyncSocketExLayer::~CAsyncSocketExLayer() +{ +} + +CAsyncSocketExLayer *CAsyncSocketExLayer::AddLayer(CAsyncSocketExLayer *pLayer, CAsyncSocketEx *pOwnerSocket) +{ + ASSERT(pLayer); + ASSERT(pOwnerSocket); + if (m_pNextLayer) + { + return m_pNextLayer->AddLayer(pLayer, pOwnerSocket); + } + else + { + ASSERT(m_pOwnerSocket==pOwnerSocket); + pLayer->Init(this, m_pOwnerSocket); + m_pNextLayer=pLayer; + } + return m_pNextLayer; +} + +int CAsyncSocketExLayer::Receive(void* lpBuf, int nBufLen, int nFlags /*=0*/) +{ + return ReceiveNext(lpBuf, nBufLen, nFlags); +} + +int CAsyncSocketExLayer::Send(const void* lpBuf, int nBufLen, int nFlags /*=0*/) +{ + return SendNext(lpBuf, nBufLen, nFlags); +} + +void CAsyncSocketExLayer::OnReceive(int nErrorCode) +{ + if (m_pPrevLayer) + m_pPrevLayer->OnReceive(nErrorCode); + else + if (m_pOwnerSocket->m_lEvent&FD_READ) + m_pOwnerSocket->OnReceive(nErrorCode); +} + +void CAsyncSocketExLayer::OnSend(int nErrorCode) +{ + if (m_pPrevLayer) + m_pPrevLayer->OnSend(nErrorCode); + else + if (m_pOwnerSocket->m_lEvent&FD_WRITE) + m_pOwnerSocket->OnSend(nErrorCode); +} + +void CAsyncSocketExLayer::OnConnect(int nErrorCode) +{ + if (m_pPrevLayer) + m_pPrevLayer->OnConnect(nErrorCode); + else + if (m_pOwnerSocket->m_lEvent&FD_CONNECT) + m_pOwnerSocket->OnConnect(nErrorCode); +} + +void CAsyncSocketExLayer::OnAccept(int nErrorCode) +{ + if (m_pPrevLayer) + m_pPrevLayer->OnAccept(nErrorCode); + else + if (m_pOwnerSocket->m_lEvent&FD_ACCEPT) + m_pOwnerSocket->OnAccept(nErrorCode); +} + +void CAsyncSocketExLayer::OnClose(int nErrorCode) +{ + if (m_pPrevLayer) + m_pPrevLayer->OnClose(nErrorCode); + else + if (m_pOwnerSocket->m_lEvent&FD_CLOSE) + m_pOwnerSocket->OnClose(nErrorCode); +} + +BOOL CAsyncSocketExLayer::TriggerEvent(long lEvent, int nErrorCode, BOOL bPassThrough /*=FALSE*/ ) +{ + ASSERT( m_pOwnerSocket ); + if (m_pOwnerSocket->m_SocketData.hSocket == INVALID_SOCKET) + return FALSE; + + if (lEvent & FD_CONNECT) + { + ASSERT( bPassThrough ); + if (nErrorCode == 0) + ASSERT( bPassThrough && GetLayerState() == connected); + else + { + SetLayerState(aborted); + m_nCriticalError = nErrorCode; + } + } + else if (lEvent & FD_CLOSE && nErrorCode == 0) + { + SetLayerState(closed); + } + else if (lEvent & FD_CLOSE && nErrorCode != 0) + { + SetLayerState(aborted); + m_nCriticalError = nErrorCode; + } + ASSERT( m_pOwnerSocket->m_pLocalAsyncSocketExThreadData ); + ASSERT( m_pOwnerSocket->m_pLocalAsyncSocketExThreadData->m_pHelperWindow ); + ASSERT( m_pOwnerSocket->m_SocketData.nSocketIndex != -1); + t_LayerNotifyMsg *pMsg = new t_LayerNotifyMsg; + pMsg->lEvent = (lEvent & 0xffff) + (nErrorCode << 16); + pMsg->pLayer = bPassThrough ? m_pPrevLayer : this; + BOOL res = PostMessage(m_pOwnerSocket->GetHelperWindowHandle(), WM_SOCKETEX_TRIGGER, (WPARAM)m_pOwnerSocket, (LPARAM)pMsg); + if (!res) + delete pMsg; + return res; +} + +void CAsyncSocketExLayer::Close() +{ + CloseNext(); +} + +void CAsyncSocketExLayer::CloseNext() +{ + SetLayerState(notsock); + if (m_pNextLayer) + m_pNextLayer->Close(); +} + +BOOL CAsyncSocketExLayer::Connect(LPCSTR lpszHostAddress, UINT nHostPort) +{ + return ConnectNext(lpszHostAddress, nHostPort); +} + +BOOL CAsyncSocketExLayer::Connect( const SOCKADDR* lpSockAddr, int nSockAddrLen ) +{ + return ConnectNext(lpSockAddr, nSockAddrLen); +} + +int CAsyncSocketExLayer::SendNext(const void *lpBuf, int nBufLen, int nFlags /*=0*/) +{ + if (m_nCriticalError) + { + WSASetLastError(m_nCriticalError); + return SOCKET_ERROR; + } + else if (GetLayerState()==notsock) + { + WSASetLastError(WSAENOTSOCK); + return SOCKET_ERROR; + } + else if (GetLayerState()==unconnected || GetLayerState()==connecting || GetLayerState()==listening) + { + WSASetLastError(WSAENOTCONN); + return SOCKET_ERROR; + } + + if (!m_pNextLayer) + { + ASSERT(m_pOwnerSocket); + return send(m_pOwnerSocket->GetSocketHandle(), (LPSTR)lpBuf, nBufLen, nFlags); + } + else + return m_pNextLayer->Send(lpBuf, nBufLen, nFlags); +} + +int CAsyncSocketExLayer::ReceiveNext(void *lpBuf, int nBufLen, int nFlags /*=0*/) +{ + if (m_nCriticalError) + { + WSASetLastError(m_nCriticalError); + return SOCKET_ERROR; + } + else if (GetLayerState()==notsock) + { + WSASetLastError(WSAENOTSOCK); + return SOCKET_ERROR; + } + else if (GetLayerState()==unconnected || GetLayerState()==connecting || GetLayerState()==listening) + { + WSASetLastError(WSAENOTCONN); + return SOCKET_ERROR; + } + + if (!m_pNextLayer) + { + ASSERT(m_pOwnerSocket); + return recv(m_pOwnerSocket->GetSocketHandle(), (LPSTR)lpBuf, nBufLen, nFlags); + } + else + return m_pNextLayer->Receive(lpBuf, nBufLen, nFlags); +} + +BOOL CAsyncSocketExLayer::ConnectNext(LPCSTR lpszHostAddress, UINT nHostPort) +{ + ASSERT( lpszHostAddress != NULL ); + ASSERT( GetLayerState() == unconnected ); + ASSERT( m_pOwnerSocket ); + + BOOL res; + if (m_pNextLayer) + res = m_pNextLayer->Connect(lpszHostAddress, nHostPort); + else + { + SOCKADDR_IN sockAddr = {0}; + sockAddr.sin_addr.s_addr = inet_addr(lpszHostAddress); + if (sockAddr.sin_addr.s_addr == INADDR_NONE) + { + LPHOSTENT lphost = gethostbyname(lpszHostAddress); + if (lphost == NULL) { + WSASetLastError(WSAEINVAL); + return FALSE; + } + sockAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; + } + sockAddr.sin_family = AF_INET; + sockAddr.sin_port = htons((u_short)nHostPort); + res = (connect(m_pOwnerSocket->GetSocketHandle(), (SOCKADDR*)&sockAddr, sizeof(sockAddr)) != SOCKET_ERROR); + } + + if (res || WSAGetLastError() == WSAEWOULDBLOCK) + SetLayerState(connecting); + + return res; +} + +BOOL CAsyncSocketExLayer::ConnectNext(const SOCKADDR* lpSockAddr, int nSockAddrLen) +{ + ASSERT( GetLayerState() == unconnected ); + ASSERT( m_pOwnerSocket ); + + BOOL res; + if (m_pNextLayer) + res = m_pNextLayer->Connect(lpSockAddr, nSockAddrLen); + else + res = (connect(m_pOwnerSocket->GetSocketHandle(), lpSockAddr, nSockAddrLen) != SOCKET_ERROR); + + if (res || WSAGetLastError() == WSAEWOULDBLOCK) + SetLayerState(connecting); + + return res; +} + +//Gets the address of the peer socket to which the socket is connected +#ifdef _AFX + +BOOL CAsyncSocketExLayer::GetPeerName( CString& rPeerAddress, UINT& rPeerPort ) +{ + return GetPeerNameNext(rPeerAddress, rPeerPort); +} + +BOOL CAsyncSocketExLayer::GetPeerNameNext( CString& rPeerAddress, UINT& rPeerPort ) +{ + if (m_pNextLayer) + return m_pNextLayer->GetPeerName(rPeerAddress, rPeerPort); + else + { + ASSERT(m_pOwnerSocket); + SOCKADDR_IN sockAddr = {0}; + int nSockAddrLen = sizeof(sockAddr); + if (!getpeername(m_pOwnerSocket->GetSocketHandle(), (SOCKADDR*)&sockAddr, &nSockAddrLen)) + { + rPeerPort = ntohs(sockAddr.sin_port); + rPeerAddress = inet_ntoa(sockAddr.sin_addr); + return TRUE; + } + else + return FALSE; + } +} + +#endif //_AFX + +BOOL CAsyncSocketExLayer::GetPeerName( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + return GetPeerNameNext(lpSockAddr, lpSockAddrLen); +} + +BOOL CAsyncSocketExLayer::GetPeerNameNext( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + if (m_pNextLayer) + return m_pNextLayer->GetPeerName(lpSockAddr, lpSockAddrLen); + else + { + ASSERT(m_pOwnerSocket); + if ( !getpeername(m_pOwnerSocket->GetSocketHandle(), lpSockAddr, lpSockAddrLen) ) + return TRUE; + else + return FALSE; + } +} + +void CAsyncSocketExLayer::Init(CAsyncSocketExLayer *pPrevLayer, CAsyncSocketEx *pOwnerSocket) +{ + ASSERT(pOwnerSocket); + m_pPrevLayer=pPrevLayer; + m_pOwnerSocket=pOwnerSocket; + m_pNextLayer=0; +} + +int CAsyncSocketExLayer::GetLayerState() +{ + return m_nLayerState; +} + +void CAsyncSocketExLayer::CallEvent(int nEvent, int nErrorCode) +{ + if (m_nCriticalError) + return; + m_nCriticalError=nErrorCode; + switch (nEvent) + { + case FD_READ: + case FD_FORCEREAD: + if (GetLayerState()==connected) + { + if (nErrorCode) + SetLayerState(aborted); + OnReceive(nErrorCode); + } + break; + case FD_WRITE: + if (GetLayerState()==connected) + { + if (nErrorCode) + SetLayerState(aborted); + OnSend(nErrorCode); + } + break; + case FD_CONNECT: + if (GetLayerState()==connecting) + { + if (!nErrorCode) + SetLayerState(connected); + else + SetLayerState(aborted); + OnConnect(nErrorCode); + } + break; + case FD_ACCEPT: + if (GetLayerState()==listening) + { + if (!nErrorCode) + SetLayerState(connected); + else + SetLayerState(aborted); + OnAccept(nErrorCode); + } + break; + case FD_CLOSE: + if (GetLayerState()==connected) + { + if (nErrorCode) + SetLayerState(aborted); + else + SetLayerState(closed); + OnClose(nErrorCode); + } + break; + } +} + +BOOL CAsyncSocketExLayer::Create(UINT nSocketPort, int nSocketType, long lEvent, LPCSTR lpszSocketAddress) +{ + return CreateNext(nSocketPort, nSocketType, lEvent, lpszSocketAddress); +} + +BOOL CAsyncSocketExLayer::CreateNext(UINT nSocketPort, int nSocketType, long lEvent, LPCSTR lpszSocketAddress) +{ + ASSERT(GetLayerState()==notsock); + BOOL res=FALSE; + if (m_pNextLayer) + res=m_pNextLayer->Create(nSocketPort, nSocketType, lEvent, lpszSocketAddress); + else + { + SOCKET hSocket=socket(AF_INET, nSocketType, 0); + if (hSocket==INVALID_SOCKET) + res=FALSE; + m_pOwnerSocket->m_SocketData.hSocket=hSocket; + m_pOwnerSocket->AttachHandle(hSocket); + if (!m_pOwnerSocket->AsyncSelect(lEvent)) + { + m_pOwnerSocket->Close(); + res=FALSE; + } + if (m_pOwnerSocket->m_pFirstLayer) + { + if (WSAAsyncSelect(m_pOwnerSocket->m_SocketData.hSocket, m_pOwnerSocket->GetHelperWindowHandle(), m_pOwnerSocket->m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE) ) + { + m_pOwnerSocket->Close(); + res=FALSE; + } + } + if (!m_pOwnerSocket->Bind(nSocketPort, lpszSocketAddress)) + { + m_pOwnerSocket->Close(); + res=FALSE; + } + res=TRUE; + } + if (res) + SetLayerState(unconnected); + return res; +} + +void CAsyncSocketExLayer::SetLayerState(int nLayerState) +{ + ASSERT( m_pOwnerSocket ); + int nOldLayerState = GetLayerState(); + m_nLayerState = nLayerState; + if (nOldLayerState != nLayerState) + DoLayerCallback(LAYERCALLBACK_STATECHANGE, GetLayerState(), nOldLayerState); +} + +int CAsyncSocketExLayer::DoLayerCallback(int nType, int nCode, WPARAM wParam, LPARAM lParam) +{ + ASSERT(m_pOwnerSocket); + + int nError = WSAGetLastError(); + int res = m_pOwnerSocket->OnLayerCallback(this, nType, nCode, wParam, lParam); + WSASetLastError(nError); + + return res; +} + +BOOL CAsyncSocketExLayer::Listen( int nConnectionBacklog) +{ + return ListenNext( nConnectionBacklog); +} + +BOOL CAsyncSocketExLayer::ListenNext( int nConnectionBacklog) +{ + ASSERT(GetLayerState()==unconnected); + BOOL res; + if (m_pNextLayer) + res=m_pNextLayer->Listen(nConnectionBacklog); + else + res=listen(m_pOwnerSocket->GetSocketHandle(), nConnectionBacklog); + if (res) + { + SetLayerState(listening); + } + return res; +} + +BOOL CAsyncSocketExLayer::Accept( CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/ ) +{ + return AcceptNext(rConnectedSocket, lpSockAddr, lpSockAddrLen); +} + +BOOL CAsyncSocketExLayer::AcceptNext( CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/ ) +{ + ASSERT(GetLayerState()==listening); + BOOL res; + if (m_pNextLayer) + res=m_pNextLayer->Accept(rConnectedSocket, lpSockAddr, lpSockAddrLen); + else + { + SOCKET hTemp = accept(m_pOwnerSocket->m_SocketData.hSocket, lpSockAddr, lpSockAddrLen); + + if (hTemp == INVALID_SOCKET) + return FALSE; + VERIFY(rConnectedSocket.InitAsyncSocketExInstance()); + rConnectedSocket.m_SocketData.hSocket=hTemp; + rConnectedSocket.AttachHandle(hTemp); + } + return TRUE; +} + +BOOL CAsyncSocketExLayer::ShutDown(int nHow /*=sends*/) +{ + return ShutDownNext(nHow); +} + +BOOL CAsyncSocketExLayer::ShutDownNext(int nHow /*=sends*/) +{ + if (m_nCriticalError) + { + WSASetLastError(m_nCriticalError); + return FALSE; + } + else if (GetLayerState()==notsock) + { + WSASetLastError(WSAENOTSOCK); + return FALSE; + } + else if (GetLayerState()==unconnected || GetLayerState()==connecting || GetLayerState()==listening) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + + if (!m_pNextLayer) + { + ASSERT(m_pOwnerSocket); + return shutdown(m_pOwnerSocket->GetSocketHandle(), nHow); + } + else + return m_pNextLayer->ShutDownNext(nHow); +} \ No newline at end of file diff --git a/AsyncSocketExLayer.h b/AsyncSocketExLayer.h new file mode 100644 index 00000000..0a9af3a2 --- /dev/null +++ b/AsyncSocketExLayer.h @@ -0,0 +1,157 @@ +/*CAsyncSocketEx by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.1 (2002-11-01) +-------------------------------------------------------- + +Introduction: +------------- + +CAsyncSocketEx is a replacement for the MFC class CAsyncSocket. +This class was written because CAsyncSocket is not the fastest WinSock +wrapper and it's very hard to add new functionality to CAsyncSocket +derived classes. This class offers the same functionality as CAsyncSocket. +Also, CAsyncSocketEx offers some enhancements which were not possible with +CAsyncSocket without some tricks. + +How do I use it? +---------------- +Basically exactly like CAsyncSocket. +To use CAsyncSocketEx, just replace all occurrences of CAsyncSocket in your +code with CAsyncSocketEx, if you did not enhance CAsyncSocket yourself in +any way, you won't have to change anything else in your code. + +Why is CAsyncSocketEx faster? +----------------------------- + +CAsyncSocketEx is slightly faster when dispatching notification event messages. +First have a look at the way CAsyncSocket works. For each thread that uses +CAsyncSocket, a window is created. CAsyncSocket calls WSAAsyncSelect with +the handle of that window. Until here, CAsyncSocketEx works the same way. +But CAsyncSocket uses only one window message (WM_SOCKET_NOTIFY) for all +sockets within one thread. When the window receive WM_SOCKET_NOTIFY, wParam +contains the socket handle and the window looks up an CAsyncSocket instance +using a map. CAsyncSocketEx works differently. It's helper window uses a +wide range of different window messages (WM_USER through 0xBFFF) and passes +a different message to WSAAsyncSelect for each socket. When a message in +the specified range is received, CAsyncSocketEx looks up the pointer to a +CAsyncSocketEx instance in an Array using the index of message - WM_USER. +As you can see, CAsyncSocketEx uses the helper window in a more efficient +way, as it don't have to use the slow maps to lookup it's own instance. +Still, speed increase is not very much, but it may be noticeable when using +a lot of sockets at the same time. +Please note that the changes do not affect the raw data throughput rate, +CAsyncSocketEx only dispatches the notification messages faster. + +What else does CAsyncSocketEx offer? +------------------------------------ + +CAsyncSocketEx offers a flexible layer system. One example is the proxy layer. +Just create an instance of the proxy layer, configure it and add it to the layer +chain of your CAsyncSocketEx instance. After that, you can connect through +proxies. +Benefit: You don't have to change much to use the layer system. +Another layer that is currently in development is the SSL layer to establish +SSL encrypted connections. + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de + +*/ +#pragma once +#include "AsyncSocketEx.h" // Hinzugefügt von der Klassenansicht + +class CAsyncSocketEx; +class CAsyncSocketExLayer +{ + friend CAsyncSocketEx; + friend CAsyncSocketExHelperWindow; +protected: + //Protected constructor so that CAsyncSocketExLayer can't be instantiated + CAsyncSocketExLayer(); + virtual ~CAsyncSocketExLayer(); + + //Notification event handlers + virtual void OnAccept(int nErrorCode); + virtual void OnClose(int nErrorCode); + virtual void OnConnect(int nErrorCode); + virtual void OnReceive(int nErrorCode); + virtual void OnSend(int nErrorCode); + + //Operations + virtual BOOL Accept(CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL); + virtual void Close(); + virtual BOOL Connect(LPCSTR lpszHostAddress, UINT nHostPort); + virtual BOOL Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen); + virtual BOOL Create(UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, + long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, + LPCSTR lpszSocketAddress = NULL ); + virtual BOOL GetPeerName(SOCKADDR* lpSockAddr, int* lpSockAddrLen); +#ifdef _AFX + virtual BOOL GetPeerName(CString& rPeerAddress, UINT& rPeerPort); +#endif + virtual BOOL Listen(int nConnectionBacklog); + virtual int Receive(void* lpBuf, int nBufLen, int nFlags = 0); + virtual int Send(const void* lpBuf, int nBufLen, int nFlags = 0); + virtual BOOL ShutDown(int nHow = sends); + enum { receives = 0, sends = 1, both = 2 }; + + //Functions that will call next layer + BOOL ShutDownNext(int nHow = sends); + BOOL AcceptNext(CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL); + void CloseNext(); + BOOL ConnectNext(LPCSTR lpszHostAddress, UINT nHostPort); + BOOL ConnectNext(const SOCKADDR* lpSockAddr, int nSockAddrLen); + BOOL CreateNext(UINT nSocketPort, int nSocketType, long lEvent, LPCSTR lpszSocketAddress); +#ifdef _AFX + BOOL GetPeerNameNext(CString& rPeerAddress, UINT& rPeerPort); +#endif + BOOL GetPeerNameNext(SOCKADDR* lpSockAddr, int* lpSockAddrLen); + BOOL ListenNext( int nConnectionBacklog); + int ReceiveNext(void *lpBuf, int nBufLen, int nFlags = 0); + int SendNext(const void *lpBuf, int nBufLen, int nFlags = 0); + + CAsyncSocketEx *m_pOwnerSocket; + + //Calls OnLayerCallback on owner socket + int DoLayerCallback(int nType, int nCode, WPARAM wParam = 0, LPARAM lParam = 0); + + int GetLayerState(); + BOOL TriggerEvent(long lEvent, int nErrorCode, BOOL bPassThrough = FALSE); + + enum LayerState + { + notsock, + unconnected, + connecting, + listening, + connected, + closed, + aborted + }; + +private: + //Layer state can't be set directly from derived classes + void SetLayerState(int nLayerState); + int m_nLayerState; + + //Called by helper window, dispatches event notification and updated layer state + void CallEvent(int nEvent, int nErrorCode); + + int m_nCriticalError; + + void Init(CAsyncSocketExLayer *pPrevLayer, CAsyncSocketEx *pOwnerSocket); + CAsyncSocketExLayer *AddLayer(CAsyncSocketExLayer *pLayer, CAsyncSocketEx *pOwnerSocket); + + CAsyncSocketExLayer *m_pNextLayer; + CAsyncSocketExLayer *m_pPrevLayer; + + struct t_LayerNotifyMsg + { + CAsyncSocketExLayer *pLayer; + long lEvent; + }; +}; diff --git a/BarShader.cpp b/BarShader.cpp new file mode 100644 index 00000000..ea96a32b --- /dev/null +++ b/BarShader.cpp @@ -0,0 +1,254 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include +#include "emule.h" +#include "barshader.h" +#include "Preferences.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// Why does _USE_MATH_DEFINES work in debug builds, but not in release builds?? +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define HALF(X) (((X) + 1) / 2) + +CBarShader::CBarShader(uint32 height, uint32 width) { + m_iWidth = width; + m_iHeight = height; + m_uFileSize = (uint64)1; + m_Spans.SetAt(0, 0); // SLUGFILLER: speedBarShader + m_Modifiers = NULL; + m_bIsPreview=false; +} + +CBarShader::~CBarShader(void) { + delete[] m_Modifiers; +} + +void CBarShader::Reset() { + Fill(0); +} + +void CBarShader::BuildModifiers() { + delete[] m_Modifiers; + m_Modifiers = NULL; // 'new' may throw an exception + + if (!m_bIsPreview) + m_used3dlevel=thePrefs.Get3DDepth(); + + // Barry - New property page slider to control depth of gradient + + // Depth must be at least 2 + // 2 gives greatest depth, the higher the value, the flatter the appearance + // m_Modifiers[count-1] will always be 1, m_Modifiers[0] depends on the value of depth + + int depth = (7-m_used3dlevel); + int count = HALF(m_iHeight); + double piOverDepth = M_PI/depth; + double base = piOverDepth * ((depth / 2.0) - 1); + double increment = piOverDepth / (count - 1); + + m_Modifiers = new float[count]; + for (int i = 0; i < count; i++) + m_Modifiers[i] = (float)(sin(base + i * increment)); +} + +void CBarShader::SetWidth(int width) { + if(m_iWidth != width) { + m_iWidth = width; + if (m_uFileSize > (uint64)0) + m_dPixelsPerByte = (double)m_iWidth / (uint64)m_uFileSize; + else + m_dPixelsPerByte = 0.0; + if (m_iWidth) + m_dBytesPerPixel = (double)m_uFileSize / m_iWidth; + else + m_dBytesPerPixel = 0.0; + } +} + +void CBarShader::SetFileSize(EMFileSize fileSize) { + if(m_uFileSize != fileSize) { + m_uFileSize = fileSize; + + if (m_uFileSize > (uint64)0) + m_dPixelsPerByte = (double)m_iWidth / (uint64)m_uFileSize; + else + m_dPixelsPerByte = 0.0; + + if (m_iWidth) + m_dBytesPerPixel = (double)m_uFileSize / m_iWidth; + else + m_dBytesPerPixel = 0.0; + } +} + +void CBarShader::SetHeight(int height) { + if(m_iHeight != height) { + m_iHeight = height; + + BuildModifiers(); + } +} + +void CBarShader::FillRange(uint64 start, uint64 end, COLORREF color) { + if(end > m_uFileSize) + end = m_uFileSize; + + if(start >= end) + return; + + // SLUGFILLER: speedBarShader + POSITION endpos = m_Spans.FindFirstKeyAfter(end+1); + + if (endpos) + m_Spans.GetPrev(endpos); + else + endpos = m_Spans.GetTailPosition(); + + ASSERT(endpos != NULL); + + COLORREF endcolor = m_Spans.GetValueAt(endpos); + endpos = m_Spans.SetAt(end, endcolor); + + for (POSITION pos = m_Spans.FindFirstKeyAfter(start+1); pos != NULL && pos != endpos; ) { + POSITION pos1 = pos; + m_Spans.GetNext(pos); + m_Spans.RemoveAt(pos1); + } + + m_Spans.GetPrev(endpos); + + if (m_Spans.GetValueAt(endpos) != color) + m_Spans.SetAt(start, color); + // SLUGFILLER: speedBarShader +} + +void CBarShader::Fill(COLORREF color) { + // SLUGFILLER: speedBarShader + m_Spans.RemoveAll(); + m_Spans.SetAt(0, color); + m_Spans.SetAt(m_uFileSize, 0); + // SLUGFILLER: speedBarShader +} + +void CBarShader::Draw(CDC* dc, int iLeft, int iTop, bool bFlat) { + POSITION pos = m_Spans.GetHeadPosition(); // SLUGFILLER: speedBarShader + RECT rectSpan; + rectSpan.top = iTop; + rectSpan.bottom = iTop + m_iHeight; + rectSpan.right = iLeft; + + uint64 uBytesInOnePixel = (uint64)(m_dBytesPerPixel + 0.5f); + uint64 start = 0;//bsCurrent->start; + // SLUGFILLER: speedBarShader + COLORREF color = m_Spans.GetValueAt(pos); + m_Spans.GetNext(pos); + // SLUGFILLER: speedBarShader + while(pos != NULL && rectSpan.right < (iLeft + m_iWidth)) { // SLUGFILLER: speedBarShader + uint64 uSpan = m_Spans.GetKeyAt(pos) - start; // SLUGFILLER: speedBarShader + uint64 uPixels = (uint64)(uSpan * m_dPixelsPerByte + 0.5f); + if (uPixels > 0) { + rectSpan.left = rectSpan.right; + rectSpan.right += (int)uPixels; + FillRect(dc, &rectSpan, color, bFlat); // SLUGFILLER: speedBarShader + start += (uint64)(uPixels * m_dBytesPerPixel + 0.5f); + } else { + float fRed = 0; + float fGreen = 0; + float fBlue = 0; + uint64 iEnd = start + uBytesInOnePixel; + uint64 iLast = start; + // SLUGFILLER: speedBarShader + do { + float fWeight = (float)((min(m_Spans.GetKeyAt(pos), iEnd) - iLast) * m_dPixelsPerByte); + fRed += GetRValue(color) * fWeight; + fGreen += GetGValue(color) * fWeight; + fBlue += GetBValue(color) * fWeight; + if(m_Spans.GetKeyAt(pos) > iEnd) + break; + iLast = m_Spans.GetKeyAt(pos); + color = m_Spans.GetValueAt(pos); + m_Spans.GetNext(pos); + } while(pos != NULL); + // SLUGFILLER: speedBarShader + rectSpan.left = rectSpan.right; + rectSpan.right++; + if (g_bLowColorDesktop) + FillRect(dc, &rectSpan, color, bFlat); + else + FillRect(dc, &rectSpan, fRed, fGreen, fBlue, bFlat); + start += uBytesInOnePixel; + } + // SLUGFILLER: speedBarShader + while(pos != NULL && m_Spans.GetKeyAt(pos) < start) { + color = m_Spans.GetValueAt(pos); + m_Spans.GetNext(pos); + } + // SLUGFILLER: speedBarShader + } +} + +void CBarShader::FillRect(CDC *dc, LPRECT rectSpan, COLORREF color, bool bFlat) { + if(!color || bFlat) + dc->FillRect(rectSpan, &CBrush(color)); + else + FillRect(dc, rectSpan, GetRValue(color), GetGValue(color), GetBValue(color), false); +} + +void CBarShader::FillRect(CDC *dc, LPRECT rectSpan, float fRed, float fGreen, + float fBlue, bool bFlat) { + if(bFlat) { + COLORREF color = RGB((int)(fRed + .5f), (int)(fGreen + .5f), (int)(fBlue + .5f)); + dc->FillRect(rectSpan, &CBrush(color)); + } else { + if (m_Modifiers == NULL || (m_used3dlevel!=thePrefs.Get3DDepth() && !m_bIsPreview) ) + BuildModifiers(); + RECT rect = *rectSpan; + int iTop = rect.top; + int iBot = rect.bottom; + int iMax = HALF(m_iHeight); + for(int i = 0; i < iMax; i++) { + CBrush cbNew(RGB((int)(fRed * m_Modifiers[i] + .5f), (int)(fGreen * m_Modifiers[i] + .5f), (int)(fBlue * m_Modifiers[i] + .5f))); + + rect.top = iTop + i; + rect.bottom = iTop + i + 1; + dc->FillRect(&rect, &cbNew); + + rect.top = iBot - i - 1; + rect.bottom = iBot - i; + dc->FillRect(&rect, &cbNew); + } + } +} + +void CBarShader::DrawPreview(CDC* dc, int iLeft, int iTop, UINT previewLevel) //Cax2 aqua bar +{ + m_bIsPreview=true; + m_used3dlevel = previewLevel; + BuildModifiers(); + Draw( dc, iLeft, iTop, (previewLevel==0)); + m_bIsPreview=false; +} diff --git a/BarShader.h b/BarShader.h new file mode 100644 index 00000000..7265cb19 --- /dev/null +++ b/BarShader.h @@ -0,0 +1,57 @@ +#pragma once + +class CBarShader +{ +public: + CBarShader(uint32 height = 1, uint32 width = 1); + ~CBarShader(void); + + //set the width of the bar + void SetWidth(int width); + + //set the height of the bar + void SetHeight(int height); + + //returns the width of the bar + int GetWidth() { + return m_iWidth; + } + + //returns the height of the bar + int GetHeight() { + return m_iHeight; + } + + //call this to blank the shaderwithout changing file size + void Reset(); + + //sets new file size and resets the shader + void SetFileSize(EMFileSize fileSize); + + //fills in a range with a certain color, new ranges overwrite old + void FillRange(uint64 start, uint64 end, COLORREF color); + + //fills in entire range with a certain color + void Fill(COLORREF color); + + //draws the bar + void Draw(CDC* dc, int iLeft, int iTop, bool bFlat); + void DrawPreview(CDC* dc, int iLeft, int iTop, UINT previewLevel); //Cax2 aqua bar + +protected: + void BuildModifiers(); + void FillRect(CDC *dc, LPRECT rectSpan, float fRed, float fGreen, float fBlue, bool bFlat); + void FillRect(CDC *dc, LPRECT rectSpan, COLORREF color, bool bFlat); + + int m_iWidth; + int m_iHeight; + double m_dPixelsPerByte; + double m_dBytesPerPixel; + EMFileSize m_uFileSize; + bool m_bIsPreview; + +private: + CRBMap m_Spans; // SLUGFILLER: speedBarShader + float *m_Modifiers; + UINT m_used3dlevel; +}; diff --git a/BaseClient.cpp b/BaseClient.cpp new file mode 100644 index 00000000..7dbd7dec --- /dev/null +++ b/BaseClient.cpp @@ -0,0 +1,3217 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#ifdef _DEBUG +#include "DebugHelpers.h" +#endif +#include "emule.h" +#include "UpDownClient.h" +#include "FriendList.h" +#include "Clientlist.h" +#include "OtherFunctions.h" +#include "PartFile.h" +#include "ListenSocket.h" +#include "PeerCacheSocket.h" +#include "Friend.h" +#include +#include "Packets.h" +#include "Opcodes.h" +#include "SafeFile.h" +#include "Preferences.h" +#include "Server.h" +#include "ClientCredits.h" +#include "IPFilter.h" +#include "Statistics.h" +#include "Sockets.h" +#include "DownloadQueue.h" +#include "UploadQueue.h" +#include "SearchFile.h" +#include "SearchList.h" +#include "SharedFileList.h" +#include "Kademlia/Kademlia/Kademlia.h" +#include "Kademlia/Kademlia/Search.h" +#include "Kademlia/Kademlia/SearchManager.h" +#include "Kademlia/Kademlia/UDPFirewallTester.h" +#include "Kademlia/routing/RoutingZone.h" +#include "Kademlia/Utils/UInt128.h" +#include "Kademlia/Net/KademliaUDPListener.h" +#include "Kademlia/Kademlia/Prefs.h" +#include "emuledlg.h" +#include "ServerWnd.h" +#include "TransferDlg.h" +#include "ChatWnd.h" +#include "CxImage/xImage.h" +#include "PreviewDlg.h" +#include "Exceptions.h" +#include "Peercachefinder.h" +#include "ClientUDPSocket.h" +#include "shahashset.h" +#include "Log.h" +#include "CaptchaGenerator.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define URLINDICATOR _T("http:|www.|.de |.net |.com |.org |.to |.tk |.cc |.fr |ftp:|ed2k:|https:|ftp.|.info|.biz|.uk|.eu|.es|.tv|.cn|.tw|.ws|.nu|.jp") + +IMPLEMENT_DYNAMIC(CClientException, CException) +IMPLEMENT_DYNAMIC(CUpDownClient, CObject) + +CUpDownClient::CUpDownClient(CClientReqSocket* sender) +{ + socket = sender; + reqfile = NULL; + Init(); +} + +CUpDownClient::CUpDownClient(CPartFile* in_reqfile, uint16 in_port, uint32 in_userid,uint32 in_serverip, uint16 in_serverport, bool ed2kID) +{ + //Converting to the HybridID system.. The ED2K system didn't take into account of IP address ending in 0.. + //All IP addresses ending in 0 were assumed to be a lowID because of the calculations. + socket = NULL; + reqfile = in_reqfile; + Init(); + m_nUserPort = in_port; + //If this is a ED2K source, check if it's a lowID.. If not, convert it to a HyrbidID. + //Else, it's already in hybrid form. + if(ed2kID && !IsLowID(in_userid)) + m_nUserIDHybrid = ntohl(in_userid); + else + m_nUserIDHybrid = in_userid; + + //If highID and ED2K source, incoming ID and IP are equal.. + //If highID and Kad source, incoming IP needs ntohl for the IP + if (!HasLowID() && ed2kID) + m_nConnectIP = in_userid; + else if(!HasLowID()) + m_nConnectIP = ntohl(in_userid); + m_dwServerIP = in_serverip; + m_nServerPort = in_serverport; +} + +void CUpDownClient::Init() +{ + m_nChatstate = MS_NONE; + m_nKadState = KS_NONE; + m_nChatCaptchaState = CA_NONE; + m_nUploadState = US_NONE; + m_nDownloadState = DS_NONE; + m_SecureIdentState = IS_UNAVAILABLE; + m_nConnectingState = CCS_NONE; + m_ePeerCacheDownState = PCDS_NONE; + m_ePeerCacheUpState = PCUS_NONE; + + credits = NULL; + m_nSumForAvgUpDataRate = 0; + m_bAddNextConnect = false; + m_cShowDR = 0; + m_nUDPPort = 0; + m_nKadPort = 0; + m_nTransferredUp = 0; + m_cAsked = 0; + m_cDownAsked = 0; + m_nUpDatarate = 0; + m_pszUsername = 0; + m_nUserIDHybrid = 0; + m_dwServerIP = 0; + m_nServerPort = 0; + m_iFileListRequested = 0; + m_dwLastUpRequest = 0; + m_bEmuleProtocol = false; + m_bCompleteSource = false; + m_bFriendSlot = false; + m_bCommentDirty = false; + m_bReaskPending = false; + m_bUDPPending = false; + m_byEmuleVersion = 0; + m_nUserPort = 0; + m_nPartCount = 0; + m_nUpPartCount = 0; + m_abyPartStatus = 0; + m_abyUpPartStatus = 0; + m_dwUploadTime = 0; + m_nTransferredDown = 0; + m_nDownDatarate = 0; + m_nDownDataRateMS = 0; + m_dwLastBlockReceived = 0; + m_byDataCompVer = 0; + m_byUDPVer = 0; + m_bySourceExchange1Ver = 0; + m_byAcceptCommentVer = 0; + m_byExtendedRequestsVer = 0; + m_nRemoteQueueRank = 0; + m_dwLastSourceRequest = 0; + m_dwLastSourceAnswer = 0; + m_dwLastAskedForSources = 0; + m_byCompatibleClient = 0; + m_nSourceFrom = SF_SERVER; + m_bIsHybrid = false; + m_bIsML=false; + m_Friend = NULL; + m_uFileRating=0; + (void)m_strFileComment; + m_fMessageFiltered = 0; + m_fIsSpammer = 0; + m_cMessagesReceived = 0; + m_cMessagesSent = 0; + m_nCurSessionUp = 0; + m_nCurSessionDown = 0; + m_nCurSessionPayloadDown = 0; + m_nSumForAvgDownDataRate = 0; + m_clientSoft=SO_UNKNOWN; + m_bRemoteQueueFull = false; + md4clr(m_achUserHash); + SetBuddyID(NULL); + m_nBuddyIP = 0; + m_nBuddyPort = 0; + if (socket){ + SOCKADDR_IN sockAddr = {0}; + int nSockAddrLen = sizeof(sockAddr); + socket->GetPeerName((SOCKADDR*)&sockAddr, &nSockAddrLen); + SetIP(sockAddr.sin_addr.S_un.S_addr); + } + else{ + SetIP(0); + } + m_fHashsetRequestingAICH = 0; + m_fHashsetRequestingMD4 = 0; + m_fSharedDirectories = 0; + m_fSentCancelTransfer = 0; + m_nClientVersion = 0; + m_lastRefreshedDLDisplay = 0; + m_dwDownStartTime = 0; + m_nLastBlockOffset = (uint64)-1; + m_bUnicodeSupport = false; + m_dwLastSignatureIP = 0; + m_bySupportSecIdent = 0; + m_byInfopacketsReceived = IP_NONE; + m_lastPartAsked = (uint16)-1; + m_nUpCompleteSourcesCount= 0; + m_fSupportsPreview = 0; + m_fPreviewReqPending = 0; + m_fPreviewAnsPending = 0; + m_bTransferredDownMini = false; + m_addedPayloadQueueSession = 0; + m_nCurQueueSessionPayloadUp = 0; // PENDING: Is this necessary? ResetSessionUp()... + m_lastRefreshedULDisplay = ::GetTickCount(); + m_bGPLEvildoer = false; + m_bHelloAnswerPending = false; + m_fNoViewSharedFiles = 0; + m_bMultiPacket = 0; + md4clr(requpfileid); + m_nTotalUDPPackets = 0; + m_nFailedUDPPackets = 0; + m_nUrlStartPos = (uint64)-1; + m_iHttpSendState = 0; + m_fPeerCache = 0; + m_uPeerCacheDownloadPushId = 0; + m_uPeerCacheUploadPushId = 0; + m_pPCDownSocket = NULL; + m_pPCUpSocket = NULL; + m_uPeerCacheRemoteIP = 0; + m_bPeerCacheDownHit = false; + m_bPeerCacheUpHit = false; + m_fNeedOurPublicIP = 0; + m_random_update_wait = (uint32)(rand()/(RAND_MAX/1000)); + m_bSourceExchangeSwapped = false; // ZZ:DownloadManager + m_dwLastTriedToConnect = ::GetTickCount()-20*60*1000; // ZZ:DownloadManager + m_fQueueRankPending = 0; + m_fUnaskQueueRankRecv = 0; + m_fFailedFileIdReqs = 0; + m_slotNumber = 0; + lastSwapForSourceExchangeTick = 0; + m_pReqFileAICHHash = NULL; + m_fSupportsAICH = 0; + m_fAICHRequested = 0; + m_byKadVersion = 0; + SetLastBuddyPingPongTime(); + m_fSentOutOfPartReqs = 0; + m_bCollectionUploadSlot = false; + m_fSupportsLargeFiles = 0; + m_fExtMultiPacket = 0; + m_fRequestsCryptLayer = 0; + m_fSupportsCryptLayer = 0; + m_fRequiresCryptLayer = 0; + m_fSupportsSourceEx2 = 0; + m_fSupportsCaptcha = 0; + m_fDirectUDPCallback = 0; + m_cCaptchasSent = 0; + m_fSupportsFileIdent = 0; +} + +CUpDownClient::~CUpDownClient(){ + if (IsAICHReqPending()){ + m_fAICHRequested = FALSE; + CAICHRecoveryHashSet::ClientAICHRequestFailed(this); + } + + if (GetFriend() != NULL) + { + if (GetFriend()->IsTryingToConnect()) + GetFriend()->UpdateFriendConnectionState(FCR_DELETED); + m_Friend->SetLinkedClient(NULL); + } + ASSERT( m_nConnectingState == CCS_NONE || !theApp.emuledlg->IsRunning() ); + theApp.clientlist->RemoveClient(this, _T("Destructing client object")); + + if (socket){ + socket->client = 0; + socket->Safe_Delete(); + } + if (m_pPCDownSocket){ + m_pPCDownSocket->client = NULL; + m_pPCDownSocket->Safe_Delete(); + } + if (m_pPCUpSocket){ + m_pPCUpSocket->client = NULL; + m_pPCUpSocket->Safe_Delete(); + } + + free(m_pszUsername); + + delete[] m_abyPartStatus; + m_abyPartStatus = NULL; + + delete[] m_abyUpPartStatus; + m_abyUpPartStatus = NULL; + + FlushSendBlocks(); + + for (POSITION pos = m_RequestedFiles_list.GetHeadPosition();pos != 0;) + delete m_RequestedFiles_list.GetNext(pos); + + for (POSITION pos = m_PendingBlocks_list.GetHeadPosition();pos != 0;){ + Pending_Block_Struct *pending = m_PendingBlocks_list.GetNext(pos); + delete pending->block; + // Not always allocated + if (pending->zStream){ + inflateEnd(pending->zStream); + delete pending->zStream; + } + delete pending; + } + + for (POSITION pos = m_WaitingPackets_list.GetHeadPosition();pos != 0;) + delete m_WaitingPackets_list.GetNext(pos); + + DEBUG_ONLY (theApp.listensocket->Debug_ClientDeleted(this)); + SetUploadFileID(NULL); + + m_fileReaskTimes.RemoveAll(); // ZZ:DownloadManager (one resk timestamp for each file) + + delete m_pReqFileAICHHash; +} + +void CUpDownClient::ClearHelloProperties() +{ + m_nUDPPort = 0; + m_byUDPVer = 0; + m_byDataCompVer = 0; + m_byEmuleVersion = 0; + m_bySourceExchange1Ver = 0; + m_byAcceptCommentVer = 0; + m_byExtendedRequestsVer = 0; + m_byCompatibleClient = 0; + m_nKadPort = 0; + m_bySupportSecIdent = 0; + m_fSupportsPreview = 0; + m_nClientVersion = 0; + m_fSharedDirectories = 0; + m_bMultiPacket = 0; + m_fPeerCache = 0; + m_uPeerCacheDownloadPushId = 0; + m_uPeerCacheUploadPushId = 0; + m_byKadVersion = 0; + m_fSupportsLargeFiles = 0; + m_fExtMultiPacket = 0; + m_fRequestsCryptLayer = 0; + m_fSupportsCryptLayer = 0; + m_fRequiresCryptLayer = 0; + m_fSupportsSourceEx2 = 0; + m_fSupportsCaptcha = 0; + m_fDirectUDPCallback = 0; + m_fSupportsFileIdent = 0; +} + +bool CUpDownClient::ProcessHelloPacket(const uchar* pachPacket, uint32 nSize) +{ + CSafeMemFile data(pachPacket, nSize); + data.ReadUInt8(); // read size of userhash + // reset all client properties; a client may not send a particular emule tag any longer + ClearHelloProperties(); + return ProcessHelloTypePacket(&data); +} + +bool CUpDownClient::ProcessHelloAnswer(const uchar* pachPacket, uint32 nSize) +{ + CSafeMemFile data(pachPacket, nSize); + bool bIsMule = ProcessHelloTypePacket(&data); + m_bHelloAnswerPending = false; + return bIsMule; +} + +bool CUpDownClient::ProcessHelloTypePacket(CSafeMemFile* data) +{ + bool bDbgInfo = thePrefs.GetUseDebugDevice(); + m_strHelloInfo.Empty(); + // clear hello properties which can be changed _only_ on receiving OP_Hello/OP_HelloAnswer + m_bIsHybrid = false; + m_bIsML = false; + m_fNoViewSharedFiles = 0; + m_bUnicodeSupport = false; + + data->ReadHash16(m_achUserHash); + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("Hash=%s (%s)"), md4str(m_achUserHash), DbgGetHashTypeString(m_achUserHash)); + m_nUserIDHybrid = data->ReadUInt32(); + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T(" UserID=%u (%s)"), m_nUserIDHybrid, ipstr(m_nUserIDHybrid)); + uint16 nUserPort = data->ReadUInt16(); // hmm clientport is sent twice - why? + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T(" Port=%u"), nUserPort); + + DWORD dwEmuleTags = 0; + bool bPrTag = false; + uint32 tagcount = data->ReadUInt32(); + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T(" Tags=%u"), tagcount); + for (uint32 i = 0; i < tagcount; i++) + { + CTag temptag(data, true); + switch (temptag.GetNameID()) + { + case CT_NAME: + if (temptag.IsStr()) { + free(m_pszUsername); + m_pszUsername = _tcsdup(temptag.GetStr()); + if (bDbgInfo) { + if (m_pszUsername) {//filter username for bad chars + TCHAR* psz = m_pszUsername; + while (*psz != _T('\0')) { + if (*psz == _T('\n') || *psz == _T('\r')) + *psz = _T(' '); + psz++; + } + } + m_strHelloInfo.AppendFormat(_T("\n Name='%s'"), m_pszUsername); + } + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case CT_VERSION: + if (temptag.IsInt()) { + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n Version=%u"), temptag.GetInt()); + m_nClientVersion = temptag.GetInt(); + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case CT_PORT: + if (temptag.IsInt()) { + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n Port=%u"), temptag.GetInt()); + nUserPort = (uint16)temptag.GetInt(); + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case CT_MOD_VERSION: + if (temptag.IsStr()) + m_strModVersion = temptag.GetStr(); + else if (temptag.IsInt()) + m_strModVersion.Format(_T("ModID=%u"), temptag.GetInt()); + else + m_strModVersion = _T("ModID="); + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ModID=%s"), m_strModVersion); + CheckForGPLEvilDoer(); + break; + + case CT_EMULE_UDPPORTS: + // 16 KAD Port + // 16 UDP Port + if (temptag.IsInt()) { + m_nKadPort = (uint16)(temptag.GetInt() >> 16); + m_nUDPPort = (uint16)temptag.GetInt(); + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n KadPort=%u UDPPort=%u"), m_nKadPort, m_nUDPPort); + dwEmuleTags |= 1; + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case CT_EMULE_BUDDYUDP: + // 16 --Reserved for future use-- + // 16 BUDDY Port + if (temptag.IsInt()) { + m_nBuddyPort = (uint16)temptag.GetInt(); + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n BuddyPort=%u"), m_nBuddyPort); + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case CT_EMULE_BUDDYIP: + // 32 BUDDY IP + if (temptag.IsInt()) { + m_nBuddyIP = temptag.GetInt(); + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n BuddyIP=%s"), ipstr(m_nBuddyIP)); + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case CT_EMULE_MISCOPTIONS1: + // 3 AICH Version (0 = not supported) + // 1 Unicode + // 4 UDP version + // 4 Data compression version + // 4 Secure Ident + // 4 Source Exchange - deprecated + // 4 Ext. Requests + // 4 Comments + // 1 PeerChache supported + // 1 No 'View Shared Files' supported + // 1 MultiPacket - deprecated with FileIdentifiers/MultipacketExt2 + // 1 Preview + if (temptag.IsInt()) { + m_fSupportsAICH = (temptag.GetInt() >> 29) & 0x07; + m_bUnicodeSupport = (temptag.GetInt() >> 28) & 0x01; + m_byUDPVer = (uint8)((temptag.GetInt() >> 24) & 0x0f); + m_byDataCompVer = (uint8)((temptag.GetInt() >> 20) & 0x0f); + m_bySupportSecIdent = (uint8)((temptag.GetInt() >> 16) & 0x0f); + m_bySourceExchange1Ver = (uint8)((temptag.GetInt() >> 12) & 0x0f); + m_byExtendedRequestsVer = (uint8)((temptag.GetInt() >> 8) & 0x0f); + m_byAcceptCommentVer = (uint8)((temptag.GetInt() >> 4) & 0x0f); + m_fPeerCache = (temptag.GetInt() >> 3) & 0x01; + m_fNoViewSharedFiles = (temptag.GetInt() >> 2) & 0x01; + m_bMultiPacket = (temptag.GetInt() >> 1) & 0x01; + m_fSupportsPreview = (temptag.GetInt() >> 0) & 0x01; + dwEmuleTags |= 2; + if (bDbgInfo) { + m_strHelloInfo.AppendFormat(_T("\n PeerCache=%u UDPVer=%u DataComp=%u SecIdent=%u SrcExchg=%u") + _T(" ExtReq=%u Commnt=%u Preview=%u NoViewFiles=%u Unicode=%u"), + m_fPeerCache, m_byUDPVer, m_byDataCompVer, m_bySupportSecIdent, m_bySourceExchange1Ver, + m_byExtendedRequestsVer, m_byAcceptCommentVer, m_fSupportsPreview, m_fNoViewSharedFiles, m_bUnicodeSupport); + } + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case CT_EMULE_MISCOPTIONS2: + // 18 Reserved + // 1 Supports new FileIdentifiers/MultipacketExt2 + // 1 Direct UDP Callback supported and available + // 1 Supports ChatCaptchas + // 1 Supports SourceExachnge2 Packets, ignores SX1 Packet Version + // 1 Requires CryptLayer + // 1 Requests CryptLayer + // 1 Supports CryptLayer + // 1 Reserved (ModBit) + // 1 Ext Multipacket (Hash+Size instead of Hash) - deprecated with FileIdentifiers/MultipacketExt2 + // 1 Large Files (includes support for 64bit tags) + // 4 Kad Version - will go up to version 15 only (may need to add another field at some point in the future) + if (temptag.IsInt()) { + m_fSupportsFileIdent = (temptag.GetInt() >> 13) & 0x01; + m_fDirectUDPCallback = (temptag.GetInt() >> 12) & 0x01; + m_fSupportsCaptcha = (temptag.GetInt() >> 11) & 0x01; + m_fSupportsSourceEx2 = (temptag.GetInt() >> 10) & 0x01; + m_fRequiresCryptLayer = (temptag.GetInt() >> 9) & 0x01; + m_fRequestsCryptLayer = (temptag.GetInt() >> 8) & 0x01; + m_fSupportsCryptLayer = (temptag.GetInt() >> 7) & 0x01; + // reserved 1 + m_fExtMultiPacket = (temptag.GetInt() >> 5) & 0x01; + m_fSupportsLargeFiles = (temptag.GetInt() >> 4) & 0x01; + m_byKadVersion = (uint8)((temptag.GetInt() >> 0) & 0x0f); + dwEmuleTags |= 8; + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n KadVersion=%u, LargeFiles=%u ExtMultiPacket=%u CryptLayerSupport=%u CryptLayerRequest=%u CryptLayerRequires=%u SupportsSourceEx2=%u SupportsCaptcha=%u DirectUDPCallback=%u"), m_byKadVersion, m_fSupportsLargeFiles, m_fExtMultiPacket, m_fSupportsCryptLayer, m_fRequestsCryptLayer, m_fRequiresCryptLayer, m_fSupportsSourceEx2, m_fSupportsCaptcha, m_fDirectUDPCallback); + m_fRequestsCryptLayer &= m_fSupportsCryptLayer; + m_fRequiresCryptLayer &= m_fRequestsCryptLayer; + + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case CT_EMULE_VERSION: + // 8 Compatible Client ID + // 7 Mjr Version (Doesn't really matter..) + // 7 Min Version (Only need 0-99) + // 3 Upd Version (Only need 0-5) + // 7 Bld Version (Only need 0-99) -- currently not used + if (temptag.IsInt()) { + m_byCompatibleClient = (uint8)((temptag.GetInt() >> 24)); + m_nClientVersion = temptag.GetInt() & 0x00ffffff; + m_byEmuleVersion = 0x99; + m_fSharedDirectories = 1; + dwEmuleTags |= 4; + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ClientVer=%u.%u.%u.%u Comptbl=%u"), (m_nClientVersion >> 17) & 0x7f, (m_nClientVersion >> 10) & 0x7f, (m_nClientVersion >> 7) & 0x07, m_nClientVersion & 0x7f, m_byCompatibleClient); + } + else if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + default: + // Since eDonkeyHybrid 1.3 is no longer sending the additional Int32 at the end of the Hello packet, + // we use the "pr=1" tag to determine them. + if (temptag.GetName() && temptag.GetName()[0]=='p' && temptag.GetName()[1]=='r') { + bPrTag = true; + } + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***UnkTag=%s"), temptag.GetFullInfo()); + } + } + m_nUserPort = nUserPort; + m_dwServerIP = data->ReadUInt32(); + m_nServerPort = data->ReadUInt16(); + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n Server=%s:%u"), ipstr(m_dwServerIP), m_nServerPort); + + // Check for additional data in Hello packet to determine client's software version. + // + // *) eDonkeyHybrid 0.40 - 1.2 sends an additional Int32. (Since 1.3 they don't send it any longer.) + // *) MLdonkey sends an additional Int32 + // + if (data->GetLength() - data->GetPosition() == sizeof(uint32)){ + uint32 test = data->ReadUInt32(); + if (test == 'KDLM'){ + m_bIsML = true; + if (bDbgInfo) + m_strHelloInfo += _T("\n ***AddData: \"MLDK\""); + } + else{ + m_bIsHybrid = true; + if (bDbgInfo) + m_strHelloInfo.AppendFormat(_T("\n ***AddData: uint32=%u (0x%08x)"), test, test); + } + } + else if (bDbgInfo && data->GetPosition() < data->GetLength()){ + UINT uAddHelloDataSize = (UINT)(data->GetLength() - data->GetPosition()); + if (uAddHelloDataSize == sizeof(uint32)){ + DWORD dwAddHelloInt32 = data->ReadUInt32(); + m_strHelloInfo.AppendFormat(_T("\n ***AddData: uint32=%u (0x%08x)"), dwAddHelloInt32, dwAddHelloInt32); + } + else if (uAddHelloDataSize == sizeof(uint32)+sizeof(uint16)){ + DWORD dwAddHelloInt32 = data->ReadUInt32(); + WORD w = data->ReadUInt16(); + m_strHelloInfo.AppendFormat(_T("\n ***AddData: uint32=%u (0x%08x), uint16=%u (0x%04x)"), dwAddHelloInt32, dwAddHelloInt32, w, w); + } + else + m_strHelloInfo.AppendFormat(_T("\n ***AddData: %u bytes"), uAddHelloDataSize); + } + + SOCKADDR_IN sockAddr = {0}; + int nSockAddrLen = sizeof(sockAddr); + socket->GetPeerName((SOCKADDR*)&sockAddr, &nSockAddrLen); + SetIP(sockAddr.sin_addr.S_un.S_addr); + + if (thePrefs.GetAddServersFromClients() && m_dwServerIP && m_nServerPort){ + CServer* addsrv = new CServer(m_nServerPort, ipstr(m_dwServerIP)); + addsrv->SetListName(addsrv->GetAddress()); + addsrv->SetPreference(SRV_PR_LOW); + if (!theApp.emuledlg->serverwnd->serverlistctrl.AddServer(addsrv, true)) + delete addsrv; + } + + //(a)If this is a highID user, store the ID in the Hybrid format. + //(b)Some older clients will not send a ID, these client are HighID users that are not connected to a server. + //(c)Kad users with a *.*.*.0 IPs will look like a lowID user they are actually a highID user.. They can be detected easily + //because they will send a ID that is the same as their IP.. + if(!HasLowID() || m_nUserIDHybrid == 0 || m_nUserIDHybrid == m_dwUserIP ) + m_nUserIDHybrid = ntohl(m_dwUserIP); + + CClientCredits* pFoundCredits = theApp.clientcredits->GetCredit(m_achUserHash); + if (credits == NULL){ + credits = pFoundCredits; + if (!theApp.clientlist->ComparePriorUserhash(m_dwUserIP, m_nUserPort, pFoundCredits)){ + if (thePrefs.GetLogBannedClients()) + AddDebugLogLine(false, _T("Clients: %s (%s), Banreason: Userhash changed (Found in TrackedClientsList)"), GetUserName(), ipstr(GetConnectIP())); + Ban(); + } + } + else if (credits != pFoundCredits){ + // userhash change ok, however two hours "waittime" before it can be used + credits = pFoundCredits; + if (thePrefs.GetLogBannedClients()) + AddDebugLogLine(false, _T("Clients: %s (%s), Banreason: Userhash changed"), GetUserName(), ipstr(GetConnectIP())); + Ban(); + } + + + if (GetFriend() != NULL && GetFriend()->HasUserhash() && md4cmp(GetFriend()->m_abyUserhash, m_achUserHash) != 0) + { + // this isnt our friend anymore and it will be removed/replaced, tell our friendobject about it + if (GetFriend()->IsTryingToConnect()) + GetFriend()->UpdateFriendConnectionState(FCR_USERHASHFAILED); // this will remove our linked friend + else + GetFriend()->SetLinkedClient(NULL); + } + // do not replace friendobjects which have no userhash, but the fitting ip with another friend object with the + // fitting userhash (both objects would fit to this instance), as this could lead to unwanted results + if (GetFriend() == NULL || GetFriend()->HasUserhash() || GetFriend()->m_dwLastUsedIP != GetConnectIP() + || GetFriend()->m_nLastUsedPort != GetUserPort()) + { + if ((m_Friend = theApp.friendlist->SearchFriend(m_achUserHash, m_dwUserIP, m_nUserPort)) != NULL){ + // Link the friend to that client + m_Friend->SetLinkedClient(this); + } + else{ + // avoid that an unwanted client instance keeps a friend slot + SetFriendSlot(false); + } + } + else{ + // however, copy over our userhash in this case + md4cpy(GetFriend()->m_abyUserhash, m_achUserHash); + } + + + // check for known major gpl breaker + CString strBuffer = m_pszUsername; + strBuffer.MakeUpper(); + strBuffer.Remove(_T(' ')); + if (strBuffer.Find(_T("EMULE-CLIENT")) != -1 || strBuffer.Find(_T("POWERMULE")) != -1 ){ + m_bGPLEvildoer = true; + } + + m_byInfopacketsReceived |= IP_EDONKEYPROTPACK; + // check if at least CT_EMULEVERSION was received, all other tags are optional + bool bIsMule = (dwEmuleTags & 0x04) == 0x04; + if (bIsMule){ + m_bEmuleProtocol = true; + m_byInfopacketsReceived |= IP_EMULEPROTPACK; + } + else if (bPrTag){ + m_bIsHybrid = true; + } + + InitClientSoftwareVersion(); + + if (m_bIsHybrid) + m_fSharedDirectories = 1; + + if (thePrefs.GetVerbose() && GetServerIP() == INADDR_NONE) + AddDebugLogLine(false, _T("Received invalid server IP %s from %s"), ipstr(GetServerIP()), DbgGetClientInfo()); + + return bIsMule; +} + +void CUpDownClient::SendHelloPacket(){ + if (socket == NULL){ + ASSERT(0); + return; + } + + CSafeMemFile data(128); + data.WriteUInt8(16); // size of userhash + SendHelloTypePacket(&data); + Packet* packet = new Packet(&data); + packet->opcode = OP_HELLO; + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__Hello", this); + theStats.AddUpDataOverheadOther(packet->size); + SendPacket(packet, true); + + m_bHelloAnswerPending = true; + return; +} + +void CUpDownClient::SendMuleInfoPacket(bool bAnswer){ + if (socket == NULL){ + ASSERT(0); + return; + } + + CSafeMemFile data(128); + data.WriteUInt8((uint8)theApp.m_uCurVersionShort); + data.WriteUInt8(EMULE_PROTOCOL); + data.WriteUInt32(7); // nr. of tags + CTag tag(ET_COMPRESSION,1); + tag.WriteTagToFile(&data); + CTag tag2(ET_UDPVER,4); + tag2.WriteTagToFile(&data); + CTag tag3(ET_UDPPORT,thePrefs.GetUDPPort()); + tag3.WriteTagToFile(&data); + CTag tag4(ET_SOURCEEXCHANGE,3); + tag4.WriteTagToFile(&data); + CTag tag5(ET_COMMENTS,1); + tag5.WriteTagToFile(&data); + CTag tag6(ET_EXTENDEDREQUEST,2); + tag6.WriteTagToFile(&data); + + uint32 dwTagValue = (theApp.clientcredits->CryptoAvailable() ? 3 : 0); + if (thePrefs.CanSeeShares() != vsfaNobody) // set 'Preview supported' only if 'View Shared Files' allowed + dwTagValue |= 128; + CTag tag7(ET_FEATURES, dwTagValue); + tag7.WriteTagToFile(&data); + + Packet* packet = new Packet(&data,OP_EMULEPROT); + if (!bAnswer) + packet->opcode = OP_EMULEINFO; + else + packet->opcode = OP_EMULEINFOANSWER; + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend(!bAnswer ? "OP__EmuleInfo" : "OP__EmuleInfoAnswer", this); + theStats.AddUpDataOverheadOther(packet->size); + SendPacket(packet, true); +} + +void CUpDownClient::ProcessMuleInfoPacket(const uchar* pachPacket, uint32 nSize) +{ + bool bDbgInfo = thePrefs.GetUseDebugDevice(); + m_strMuleInfo.Empty(); + CSafeMemFile data(pachPacket, nSize); + m_byCompatibleClient = 0; + m_byEmuleVersion = data.ReadUInt8(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("EmuleVer=0x%x"), (UINT)m_byEmuleVersion); + if (m_byEmuleVersion == 0x2B) + m_byEmuleVersion = 0x22; + uint8 protversion = data.ReadUInt8(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T(" ProtVer=%u"), (UINT)protversion); + + //implicitly supported options by older clients + if (protversion == EMULE_PROTOCOL) { + //in the future do not use version to guess about new features + + if (m_byEmuleVersion < 0x25 && m_byEmuleVersion > 0x22) + m_byUDPVer = 1; + + if (m_byEmuleVersion < 0x25 && m_byEmuleVersion > 0x21) + m_bySourceExchange1Ver = 1; + + if (m_byEmuleVersion == 0x24) + m_byAcceptCommentVer = 1; + + // Shared directories are requested from eMule 0.28+ because eMule 0.27 has a bug in + // the OP_ASKSHAREDFILESDIR handler, which does not return the shared files for a + // directory which has a trailing backslash. + if (m_byEmuleVersion >= 0x28 && !m_bIsML) // MLdonkey currently does not support shared directories + m_fSharedDirectories = 1; + + } else { + return; + } + + uint32 tagcount = data.ReadUInt32(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T(" Tags=%u"), (UINT)tagcount); + for (uint32 i = 0; i < tagcount; i++) + { + CTag temptag(&data, false); + switch (temptag.GetNameID()) + { + case ET_COMPRESSION: + // Bits 31- 8: 0 - reserved + // Bits 7- 0: data compression version + if (temptag.IsInt()) { + m_byDataCompVer = (uint8)temptag.GetInt(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n Compr=%u"), (UINT)temptag.GetInt()); + } + else if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case ET_UDPPORT: + // Bits 31-16: 0 - reserved + // Bits 15- 0: UDP port + if (temptag.IsInt()) { + m_nUDPPort = (uint16)temptag.GetInt(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n UDPPort=%u"), (UINT)temptag.GetInt()); + } + else if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case ET_UDPVER: + // Bits 31- 8: 0 - reserved + // Bits 7- 0: UDP protocol version + if (temptag.IsInt()) { + m_byUDPVer = (uint8)temptag.GetInt(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n UDPVer=%u"), (UINT)temptag.GetInt()); + } + else if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case ET_SOURCEEXCHANGE: + // Bits 31- 8: 0 - reserved + // Bits 7- 0: source exchange protocol version + if (temptag.IsInt()) { + m_bySourceExchange1Ver = (uint8)temptag.GetInt(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n SrcExch=%u"), (UINT)temptag.GetInt()); + } + else if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case ET_COMMENTS: + // Bits 31- 8: 0 - reserved + // Bits 7- 0: comments version + if (temptag.IsInt()) { + m_byAcceptCommentVer = (uint8)temptag.GetInt(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n Commnts=%u"), (UINT)temptag.GetInt()); + } + else if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case ET_EXTENDEDREQUEST: + // Bits 31- 8: 0 - reserved + // Bits 7- 0: extended requests version + if (temptag.IsInt()) { + m_byExtendedRequestsVer = (uint8)temptag.GetInt(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ExtReq=%u"), (UINT)temptag.GetInt()); + } + else if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case ET_COMPATIBLECLIENT: + // Bits 31- 8: 0 - reserved + // Bits 7- 0: compatible client ID + if (temptag.IsInt()) { + m_byCompatibleClient = (uint8)temptag.GetInt(); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n Comptbl=%u"), (UINT)temptag.GetInt()); + } + else if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case ET_FEATURES: + // Bits 31- 8: 0 - reserved + // Bit 7: Preview + // Bit 6- 0: secure identification + if (temptag.IsInt()) { + m_bySupportSecIdent = (uint8)((temptag.GetInt()) & 3); + m_fSupportsPreview = (temptag.GetInt() >> 7) & 1; + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n SecIdent=%u Preview=%u"), m_bySupportSecIdent, m_fSupportsPreview); + } + else if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkType=%s"), temptag.GetFullInfo()); + break; + + case ET_MOD_VERSION: + if (temptag.IsStr()) + m_strModVersion = temptag.GetStr(); + else if (temptag.IsInt()) + m_strModVersion.Format(_T("ModID=%u"), temptag.GetInt()); + else + m_strModVersion = _T("ModID="); + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ModID=%s"), m_strModVersion); + CheckForGPLEvilDoer(); + break; + + default: + if (bDbgInfo) + m_strMuleInfo.AppendFormat(_T("\n ***UnkTag=%s"), temptag.GetFullInfo()); + } + } + if (m_byDataCompVer == 0) { + m_bySourceExchange1Ver = 0; + m_byExtendedRequestsVer = 0; + m_byAcceptCommentVer = 0; + m_nUDPPort = 0; + } + if (bDbgInfo && data.GetPosition() < data.GetLength()) { + m_strMuleInfo.AppendFormat(_T("\n ***AddData: %u bytes"), data.GetLength() - data.GetPosition()); + } + + m_bEmuleProtocol = true; + m_byInfopacketsReceived |= IP_EMULEPROTPACK; + InitClientSoftwareVersion(); + + if (thePrefs.GetVerbose() && GetServerIP() == INADDR_NONE) + AddDebugLogLine(false, _T("Received invalid server IP %s from %s"), ipstr(GetServerIP()), DbgGetClientInfo()); +} + +void CUpDownClient::SendHelloAnswer(){ + if (socket == NULL){ + ASSERT(0); + return; + } + + CSafeMemFile data(128); + SendHelloTypePacket(&data); + Packet* packet = new Packet(&data); + packet->opcode = OP_HELLOANSWER; + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__HelloAnswer", this); + theStats.AddUpDataOverheadOther(packet->size); + + // Servers send a FIN right in the data packet on check connection, so we need to force the response immediate + bool bForceSend = theApp.serverconnect->AwaitingTestFromIP(GetConnectIP()); + socket->SendPacket(packet, true, true, 0, bForceSend); + + m_bHelloAnswerPending = false; +} + +void CUpDownClient::SendHelloTypePacket(CSafeMemFile* data) +{ + data->WriteHash16(thePrefs.GetUserHash()); + uint32 clientid; + clientid = theApp.GetID(); + + data->WriteUInt32(clientid); + data->WriteUInt16(thePrefs.GetPort()); + + uint32 tagcount = 6; + + if( theApp.clientlist->GetBuddy() && theApp.IsFirewalled() ) + tagcount += 2; + + data->WriteUInt32(tagcount); + + // eD2K Name + + // TODO implement multi language website which informs users of the effects of bad mods + CTag tagName(CT_NAME, (!m_bGPLEvildoer) ? thePrefs.GetUserNick() : _T("Please use a GPL-conform version of eMule") ); + tagName.WriteTagToFile(data, utf8strRaw); + + // eD2K Version + CTag tagVersion(CT_VERSION,EDONKEYVERSION); + tagVersion.WriteTagToFile(data); + + // eMule UDP Ports + uint32 kadUDPPort = 0; + if(Kademlia::CKademlia::IsConnected()) + { + if (Kademlia::CKademlia::GetPrefs()->GetExternalKadPort() != 0 + && Kademlia::CKademlia::GetPrefs()->GetUseExternKadPort() + && Kademlia::CUDPFirewallTester::IsVerified()) + { + kadUDPPort = Kademlia::CKademlia::GetPrefs()->GetExternalKadPort(); + } + else + kadUDPPort = Kademlia::CKademlia::GetPrefs()->GetInternKadPort(); + } + CTag tagUdpPorts(CT_EMULE_UDPPORTS, + ((uint32)kadUDPPort << 16) | + ((uint32)thePrefs.GetUDPPort() << 0) + ); + tagUdpPorts.WriteTagToFile(data); + + if( theApp.clientlist->GetBuddy() && theApp.IsFirewalled() ) + { + CTag tagBuddyIP(CT_EMULE_BUDDYIP, theApp.clientlist->GetBuddy()->GetIP() ); + tagBuddyIP.WriteTagToFile(data); + + CTag tagBuddyPort(CT_EMULE_BUDDYUDP, +// ( RESERVED ) + ((uint32)theApp.clientlist->GetBuddy()->GetUDPPort() ) + ); + tagBuddyPort.WriteTagToFile(data); + } + + // eMule Misc. Options #1 + const UINT uUdpVer = 4; + const UINT uDataCompVer = 1; + const UINT uSupportSecIdent = theApp.clientcredits->CryptoAvailable() ? 3 : 0; + // *** + // deprecated - will be set back to 3 with the next release (to allow the new version to spread first), + // due to a bug in earlier eMule version. Use SupportsSourceEx2 and new opcodes instead + const UINT uSourceExchange1Ver = 4; + // *** + const UINT uExtendedRequestsVer = 2; + const UINT uAcceptCommentVer = 1; + const UINT uNoViewSharedFiles = (thePrefs.CanSeeShares() == vsfaNobody) ? 1 : 0; // for backward compatibility this has to be a 'negative' flag + const UINT uMultiPacket = 1; + const UINT uSupportPreview = (thePrefs.CanSeeShares() != vsfaNobody) ? 1 : 0; // set 'Preview supported' only if 'View Shared Files' allowed + const UINT uPeerCache = 1; + const UINT uUnicodeSupport = 1; + const UINT nAICHVer = 1; + CTag tagMisOptions1(CT_EMULE_MISCOPTIONS1, + (nAICHVer << 29) | + (uUnicodeSupport << 28) | + (uUdpVer << 24) | + (uDataCompVer << 20) | + (uSupportSecIdent << 16) | + (uSourceExchange1Ver << 12) | + (uExtendedRequestsVer << 8) | + (uAcceptCommentVer << 4) | + (uPeerCache << 3) | + (uNoViewSharedFiles << 2) | + (uMultiPacket << 1) | + (uSupportPreview << 0) + ); + tagMisOptions1.WriteTagToFile(data); + + // eMule Misc. Options #2 + const UINT uKadVersion = KADEMLIA_VERSION; + const UINT uSupportLargeFiles = 1; + const UINT uExtMultiPacket = 1; + const UINT uReserved = 0; // mod bit + const UINT uSupportsCryptLayer = thePrefs.IsClientCryptLayerSupported() ? 1 : 0; + const UINT uRequestsCryptLayer = thePrefs.IsClientCryptLayerRequested() ? 1 : 0; + const UINT uRequiresCryptLayer = thePrefs.IsClientCryptLayerRequired() ? 1 : 0; + const UINT uSupportsSourceEx2 = 1; + const UINT uSupportsCaptcha = 1; + // direct callback is only possible if connected to kad, tcp firewalled and verified UDP open (for example on a full cone NAT) + const UINT uDirectUDPCallback = (Kademlia::CKademlia::IsRunning() && Kademlia::CKademlia::IsFirewalled() + && !Kademlia::CUDPFirewallTester::IsFirewalledUDP(true) && Kademlia::CUDPFirewallTester::IsVerified()) ? 1 : 0; + const UINT uFileIdentifiers = 1; + + CTag tagMisOptions2(CT_EMULE_MISCOPTIONS2, +// (RESERVED ) + (uFileIdentifiers << 13) | + (uDirectUDPCallback << 12) | + (uSupportsCaptcha << 11) | + (uSupportsSourceEx2 << 10) | + (uRequiresCryptLayer << 9) | + (uRequestsCryptLayer << 8) | + (uSupportsCryptLayer << 7) | + (uReserved << 6) | + (uExtMultiPacket << 5) | + (uSupportLargeFiles << 4) | + (uKadVersion << 0) + ); + tagMisOptions2.WriteTagToFile(data); + + // eMule Version + CTag tagMuleVersion(CT_EMULE_VERSION, + //(uCompatibleClientID << 24) | + (CemuleApp::m_nVersionMjr << 17) | + (CemuleApp::m_nVersionMin << 10) | + (CemuleApp::m_nVersionUpd << 7) +// (RESERVED ) + ); + tagMuleVersion.WriteTagToFile(data); + + uint32 dwIP; + uint16 nPort; + if (theApp.serverconnect->IsConnected()){ + dwIP = theApp.serverconnect->GetCurrentServer()->GetIP(); + nPort = theApp.serverconnect->GetCurrentServer()->GetPort(); +#ifdef _DEBUG + if (dwIP == theApp.serverconnect->GetLocalIP()){ + dwIP = 0; + nPort = 0; + } +#endif + } + else{ + nPort = 0; + dwIP = 0; + } + data->WriteUInt32(dwIP); + data->WriteUInt16(nPort); +// data->WriteUInt32(dwIP); //The Hybrid added some bits here, what ARE THEY FOR? +} + +void CUpDownClient::ProcessMuleCommentPacket(const uchar* pachPacket, uint32 nSize) +{ + if (reqfile && reqfile->IsPartFile()) + { + CSafeMemFile data(pachPacket, nSize); + uint8 uRating = data.ReadUInt8(); + if (thePrefs.GetLogRatingDescReceived() && uRating > 0) + AddDebugLogLine(false, GetResString(IDS_RATINGRECV), m_strClientFilename, uRating); + CString strComment; + UINT uLength = data.ReadUInt32(); + if (uLength > 0) + { + // we have to increase the raw max. allowed file comment len because of possible UTF8 encoding. + if (uLength > MAXFILECOMMENTLEN*4) + uLength = MAXFILECOMMENTLEN*4; + strComment = data.ReadString(GetUnicodeSupport()!=utf8strNone, uLength); + + if (strComment.GetLength() > MAXFILECOMMENTLEN) // enforce the max len on the comment + strComment = strComment.Left(MAXFILECOMMENTLEN); + + if (thePrefs.GetLogRatingDescReceived() && !strComment.IsEmpty()) + AddDebugLogLine(false, GetResString(IDS_DESCRIPTIONRECV), m_strClientFilename, strComment); + + // test if comment is filtered + if (!thePrefs.GetCommentFilter().IsEmpty()) + { + CString strCommentLower(strComment); + strCommentLower.MakeLower(); + + int iPos = 0; + CString strFilter(thePrefs.GetCommentFilter().Tokenize(_T("|"), iPos)); + while (!strFilter.IsEmpty()) + { + // comment filters are already in lowercase, compare with temp. lowercased received comment + if (strCommentLower.Find(strFilter) >= 0) + { + strComment.Empty(); + uRating = 0; + SetSpammer(true); + break; + } + strFilter = thePrefs.GetCommentFilter().Tokenize(_T("|"), iPos); + } + } + } + if (!strComment.IsEmpty() || uRating > 0) + { + m_strFileComment = strComment; + m_uFileRating = uRating; + reqfile->UpdateFileRatingCommentAvail(); + } + } +} + +bool CUpDownClient::Disconnected(LPCTSTR pszReason, bool bFromSocket) +{ + ASSERT( theApp.clientlist->IsValidClient(this) ); + + // TODO LOGREMOVE + if (m_nConnectingState == CCS_DIRECTCALLBACK) + DebugLog(_T("Direct Callback failed - %s"), DbgGetClientInfo()); + + if (GetKadState() == KS_QUEUED_FWCHECK_UDP || GetKadState() == KS_CONNECTING_FWCHECK_UDP) + Kademlia::CUDPFirewallTester::SetUDPFWCheckResult(false, true, ntohl(GetConnectIP()), 0); // inform the tester that this test was cancelled + else if (GetKadState() == KS_FWCHECK_UDP) + Kademlia::CUDPFirewallTester::SetUDPFWCheckResult(false, false, ntohl(GetConnectIP()), 0); // inform the tester that this test has failed + else if (GetKadState() == KS_CONNECTED_BUDDY) + DebugLogWarning(_T("Buddy client disconnected - %s, %s"), pszReason, DbgGetClientInfo()); + //If this is a KAD client object, just delete it! + SetKadState(KS_NONE); + + if (GetUploadState() == US_UPLOADING || GetUploadState() == US_CONNECTING) + { + // sets US_NONE + theApp.uploadqueue->RemoveFromUploadQueue(this, CString(_T("CUpDownClient::Disconnected: ")) + pszReason); + } + + if (GetDownloadState() == DS_DOWNLOADING){ + ASSERT( m_nConnectingState == CCS_NONE ); + if (m_ePeerCacheDownState == PCDS_WAIT_CACHE_REPLY || m_ePeerCacheDownState == PCDS_DOWNLOADING) + theApp.m_pPeerCache->DownloadAttemptFailed(); + SetDownloadState(DS_ONQUEUE, CString(_T("Disconnected: ")) + pszReason); + } + else{ + // ensure that all possible block requests are removed from the partfile + ClearDownloadBlockRequests(); + if (GetDownloadState() == DS_CONNECTED){ // successfully connected, but probably didn't responsed to our filerequest + theApp.clientlist->m_globDeadSourceList.AddDeadSource(this); + theApp.downloadqueue->RemoveSource(this); + } + } + + // we had still an AICH request pending, handle it + if (IsAICHReqPending()){ + m_fAICHRequested = FALSE; + CAICHRecoveryHashSet::ClientAICHRequestFailed(this); + } + + // The remote client does not have to answer with OP_HASHSETANSWER *immediatly* + // after we've sent OP_HASHSETREQUEST. It may occure that a (buggy) remote client + // is sending use another OP_FILESTATUS which would let us change to DL-state to DS_ONQUEUE. + if (m_fHashsetRequestingMD4 && (reqfile != NULL)) + reqfile->m_bMD4HashsetNeeded = true; + if (m_fHashsetRequestingAICH && (reqfile != NULL)) + reqfile->SetAICHHashSetNeeded(true); + + if (m_iFileListRequested){ + LogWarning(LOG_STATUSBAR, GetResString(IDS_SHAREDFILES_FAILED), GetUserName()); + m_iFileListRequested = 0; + } + + if (m_Friend) + theApp.friendlist->RefreshFriend(m_Friend); + + ASSERT( theApp.clientlist->IsValidClient(this) ); + + //check if this client is needed in any way, if not delete it + bool bDelete = true; + switch(m_nUploadState){ + case US_ONUPLOADQUEUE: + bDelete = false; + break; + } + switch(m_nDownloadState){ + case DS_ONQUEUE: + case DS_TOOMANYCONNS: + case DS_NONEEDEDPARTS: + case DS_LOWTOLOWIP: + bDelete = false; + } + + // Dead Soure Handling + // + // If we failed to connect to that client, it is supposed to be 'dead'. Add the IP + // to the 'dead sources' lists so we don't waste resources and bandwidth to connect + // to that client again within the next hour. + // + // But, if we were just connecting to a proxy and failed to do so, that client IP + // is supposed to be valid until the proxy itself tells us that the IP can not be + // connected to (e.g. 504 Bad Gateway) + // + if ( (m_nConnectingState != CCS_NONE && !(socket && socket->GetProxyConnectFailed())) + || m_nDownloadState == DS_ERROR) + { + if (m_nDownloadState != DS_NONE) // Unable to connect = Remove any downloadstate + theApp.downloadqueue->RemoveSource(this); + theApp.clientlist->m_globDeadSourceList.AddDeadSource(this); + bDelete = true; + } + + // We keep chat partners in any case + if (GetChatState() != MS_NONE){ + bDelete = false; + if (GetFriend() != NULL && GetFriend()->IsTryingToConnect()) + GetFriend()->UpdateFriendConnectionState(FCR_DISCONNECTED); // for friends any connectionupdate is handled in the friend class + else + theApp.emuledlg->chatwnd->chatselector.ConnectingResult(this,false); // other clients update directly + } + + // Delete Socket + if (!bFromSocket && socket){ + ASSERT( theApp.listensocket->IsValidSocket(socket) ); + socket->Safe_Delete(); + } + socket = NULL; + + theApp.emuledlg->transferwnd->GetClientList()->RefreshClient(this); + + // finally, remove the client from the timeouttimer and reset the connecting state + m_nConnectingState = CCS_NONE; + theApp.clientlist->RemoveConnectingClient(this); + + if (bDelete) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + Debug(_T("--- Deleted client %s; Reason=%s\n"), DbgGetClientInfo(true), pszReason); + return true; + } + else + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + Debug(_T("--- Disconnected client %s; Reason=%s\n"), DbgGetClientInfo(true), pszReason); + m_fHashsetRequestingMD4 = 0; + m_fHashsetRequestingAICH = 0; + SetSentCancelTransfer(0); + m_bHelloAnswerPending = false; + m_fQueueRankPending = 0; + m_fFailedFileIdReqs = 0; + m_fUnaskQueueRankRecv = 0; + m_uPeerCacheDownloadPushId = 0; + m_uPeerCacheUploadPushId = 0; + m_uPeerCacheRemoteIP = 0; + SetPeerCacheDownState(PCDS_NONE); + SetPeerCacheUpState(PCUS_NONE); + if (m_pPCDownSocket){ + m_pPCDownSocket->client = NULL; + m_pPCDownSocket->Safe_Delete(); + } + if (m_pPCUpSocket){ + m_pPCUpSocket->client = NULL; + m_pPCUpSocket->Safe_Delete(); + } + m_fSentOutOfPartReqs = 0; + return false; + } +} + +//Returned bool is not if the TryToConnect is successful or not.. +//false means the client was deleted! +//true means the client was not deleted! +bool CUpDownClient::TryToConnect(bool bIgnoreMaxCon, bool bNoCallbacks, CRuntimeClass* pClassSocket) +{ + // There are 7 possible ways how we are going to connect in this function, sorted by priority: + // 1) Already Connected/Connecting + // We are already connected or try to connect right now. Abort, no additional Disconnect() call will be done + // 2) Immediate Fail + // Some precheck or precondition failed, or no other way is available, so we do not try to connect at all + // but fail right away, possibly deleting the client as it becomes useless + // 3) Normal Outgoing TCP Connection + // Applies to all HighIDs/Open clients: We do a straight forward connection try to the TCP port of the client + // 4) Direct Callback Connections + // Applies to TCP firewalled - UDP open clients: We sent a UDP packet to the client, requesting him to connect + // to us. This is pretty easy too and ressourcewise nearly on the same level as 3) + // (* 5) Waiting/Abort + // This check is done outside this function. + // We want to connect for some download related thing (for example reasking), but the client has a LowID and + // is on our uploadqueue. So we are smart and safing ressources by just waiting untill he reasks us, so we don't + // have to do the ressource intensive options 6 or 7. *) + // 6) Server Callback + // This client is firewalled, but connected to our server. We sent the server a callback request to forward to + // the client and hope for the best + // 7) Kad Callback + // This client is firewalled, but has a Kad buddy. We sent the buddy a callback request to forward to the client + // and hope for the best + + if( GetKadState() == KS_QUEUED_FWCHECK ) + SetKadState(KS_CONNECTING_FWCHECK); + else if (GetKadState() == KS_QUEUED_FWCHECK_UDP) + SetKadState(KS_CONNECTING_FWCHECK_UDP); + + //////////////////////////////////////////////////////////// + // Check for 1) Already Connected/Connecting + if (m_nConnectingState != CCS_NONE) { + DebugLog(_T("TryToConnect: Already Connecting (%s)"), DbgGetClientInfo());// TODO LogRemove + return true; + } + else if (socket != NULL){ + if (socket->IsConnected()) + { + if (CheckHandshakeFinished()){ + DEBUG_ONLY( DebugLog(_T("TryToConnect: Already Connected (%s)"), DbgGetClientInfo()) );// TODO LogRemove + ConnectionEstablished(); + } + else + DebugLogWarning( _T("TryToConnect found connected socket, but without Handshake finished - %s"), DbgGetClientInfo()); + return true; + } + else + socket->Safe_Delete(); + } + m_nConnectingState = CCS_PRECONDITIONS; // We now officially try to connect :) + + //////////////////////////////////////////////////////////// + // Check for 2) Immediate Fail + + if (theApp.listensocket->TooManySockets() && !bIgnoreMaxCon) + { + // This is a sanitize check and counts as a "hard failure", so this check should be also done before calling + // TryToConnect if a special handling, like waiting till there are enough connection avaiable should be fone + DebugLogWarning(_T("TryToConnect: Too many connections sanitize check (%s)"), DbgGetClientInfo()); + if(Disconnected(_T("Too many connections"))) + { + delete this; + return false; + } + return true; + } + // do not try to connect to source which are incompatible with our encryption setting (one requires it, and the other one doesn't supports it) + if ( (RequiresCryptLayer() && !thePrefs.IsClientCryptLayerSupported()) || (thePrefs.IsClientCryptLayerRequired() && !SupportsCryptLayer()) ) + { + DEBUG_ONLY( AddDebugLogLine(DLP_DEFAULT, false, _T("Rejected outgoing connection because CryptLayer-Setting (Obfuscation) was incompatible %s"), DbgGetClientInfo()) ); + if(Disconnected(_T("CryptLayer-Settings (Obfuscation) incompatible"))){ + delete this; + return false; + } + else + return true; + } + + uint32 uClientIP = (GetIP() != 0) ? GetIP() : GetConnectIP(); + if (uClientIP == 0 && !HasLowID()) + uClientIP = ntohl(m_nUserIDHybrid); + if (uClientIP) + { + // although we filter all received IPs (server sources, source exchange) and all incomming connection attempts, + // we do have to filter outgoing connection attempts here too, because we may have updated the ip filter list + if (theApp.ipfilter->IsFiltered(uClientIP)) + { + theStats.filteredclients++; + if (thePrefs.GetLogFilteredIPs()) + AddDebugLogLine(true, GetResString(IDS_IPFILTERED), ipstr(uClientIP), theApp.ipfilter->GetLastHit()); + if (Disconnected(_T("IPFilter"))) + { + delete this; + return false; + } + return true; + } + + // for safety: check again whether that IP is banned + if (theApp.clientlist->IsBannedClient(uClientIP)) + { + if (thePrefs.GetLogBannedClients()) + AddDebugLogLine(false, _T("Refused to connect to banned client %s"), DbgGetClientInfo()); + if (Disconnected(_T("Banned IP"))) + { + delete this; + return false; + } + return true; + } + } + + if ( HasLowID() ) + { + ASSERT( pClassSocket == NULL ); + if(!theApp.CanDoCallback(this)) // lowid2lowid check used for the whole function, don't remove + { + // We cannot reach this client, so we hard fail to connect, if this client should be kept, + // for example because we might want to wait a bit and hope we get a highid, this check has + // to be done before calling this function + if(Disconnected(_T("LowID->LowID"))) + { + delete this; + return false; + } + return true; + } + + // are callbacks disallowed? + if (bNoCallbacks){ + DebugLogError(_T("TryToConnect: Would like to do callback on a no-callback client, %s"), DbgGetClientInfo()); + if(Disconnected(_T("LowID: No Callback Option allowed"))) + { + delete this; + return false; + } + return true; + } + + // Is any callback available? + if (!( (SupportsDirectUDPCallback() && thePrefs.GetUDPPort() != 0 && GetConnectIP() != 0) // Direct Callback + || (HasValidBuddyID() && Kademlia::CKademlia::IsConnected() && ((GetBuddyIP() && GetBuddyPort()) || reqfile != NULL)) // Kad Callback + || theApp.serverconnect->IsLocalServer(GetServerIP(), GetServerPort()) )) // Server Callback + { + // Nope + if(Disconnected(_T("LowID: No Callback Option available"))) + { + delete this; + return false; + } + return true; + } + } + + // Prechecks finished, now for the real connecting + //////////////////////////////////////////////////// + + theApp.clientlist->AddConnectingClient(this); // Starts and checks for the timeout, ensures following Disconnect() or ConnectionEstablished() call + + //////////////////////////////////////////////////////////// + // 3) Normal Outgoing TCP Connection + if (!HasLowID()) + { + m_nConnectingState = CCS_DIRECTTCP; + if (pClassSocket == NULL) + pClassSocket = RUNTIME_CLASS(CClientReqSocket); + socket = static_cast(pClassSocket->CreateObject()); + socket->SetClient(this); + if (!socket->Create()) + { + socket->Safe_Delete(); + // we let the timeout handle the cleanup in this case + DebugLogError(_T("TryToConnect: Failed to create socket for outgoing connection, %s"), DbgGetClientInfo()); + } + else + Connect(); + return true; + } + //////////////////////////////////////////////////////////// + // 4) Direct Callback Connections + else if (SupportsDirectUDPCallback() && thePrefs.GetUDPPort() != 0 && GetConnectIP() != 0) + { + m_nConnectingState = CCS_DIRECTCALLBACK; + // TODO LOGREMOVE + DebugLog(_T("Direct Callback on port %u to client %s (%s) "), GetKadPort(), DbgGetClientInfo(), md4str(GetUserHash())); + CSafeMemFile data; + data.WriteUInt16(thePrefs.GetPort()); // needs to know our port + data.WriteHash16(thePrefs.GetUserHash()); // and userhash + // our connection settings + data.WriteUInt8(GetMyConnectOptions(true, false)); + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugSend("OP_DIRECTCALLBACKREQ", this); + Packet* packet = new Packet(&data, OP_EMULEPROT); + packet->opcode = OP_DIRECTCALLBACKREQ; + theStats.AddUpDataOverheadOther(packet->size); + theApp.clientudp->SendPacket(packet, GetConnectIP(), GetKadPort(), ShouldReceiveCryptUDPPackets(), GetUserHash(), false, 0); + return true; + } + //////////////////////////////////////////////////////////// + // 6) Server Callback + 7) Kad Callback + if (GetDownloadState() == DS_CONNECTING) + SetDownloadState(DS_WAITCALLBACK); + + if (GetUploadState() == US_CONNECTING){ + ASSERT( false ); // we should never try to connect in this case, but wait for the LowID to connect to us + DebugLogError( _T("LowID and US_CONNECTING (%s)"), DbgGetClientInfo()); + } + + if (theApp.serverconnect->IsLocalServer(m_dwServerIP, m_nServerPort)) + { + m_nConnectingState = CCS_SERVERCALLBACK; + Packet* packet = new Packet(OP_CALLBACKREQUEST,4); + PokeUInt32(packet->pBuffer, m_nUserIDHybrid); + if (thePrefs.GetDebugServerTCPLevel() > 0 || thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__CallbackRequest", this); + theStats.AddUpDataOverheadServer(packet->size); + theApp.serverconnect->SendPacket(packet); + return true; + } + else if (HasValidBuddyID() && Kademlia::CKademlia::IsConnected() && ((GetBuddyIP() && GetBuddyPort()) || reqfile != NULL)) + { + m_nConnectingState = CCS_KADCALLBACK; + if( GetBuddyIP() && GetBuddyPort()) + { + CSafeMemFile bio(34); + bio.WriteUInt128(&Kademlia::CUInt128(GetBuddyID())); + bio.WriteUInt128(&Kademlia::CUInt128(reqfile->GetFileHash())); + bio.WriteUInt16(thePrefs.GetPort()); + if (thePrefs.GetDebugClientKadUDPLevel() > 0 || thePrefs.GetDebugClientUDPLevel() > 0) + DebugSend("KadCallbackReq", this); + Packet* packet = new Packet(&bio, OP_KADEMLIAHEADER); + packet->opcode = KADEMLIA_CALLBACK_REQ; + theStats.AddUpDataOverheadKad(packet->size); + // FIXME: We dont know which kadversion the buddy has, so we need to send unencrypted + theApp.clientudp->SendPacket(packet, GetBuddyIP(), GetBuddyPort(), false, NULL, true, 0); + SetDownloadState(DS_WAITCALLBACKKAD); + } + else if (reqfile != NULL) + { + // I don't think we should ever have a buddy without its IP (anymore), but nevertheless let the functionality in + //Create search to find buddy. + Kademlia::CSearch *findSource = new Kademlia::CSearch; + findSource->SetSearchTypes(Kademlia::CSearch::FINDSOURCE); + findSource->SetTargetID(Kademlia::CUInt128(GetBuddyID())); + findSource->AddFileID(Kademlia::CUInt128(reqfile->GetFileHash())); + if( Kademlia::CKademlia::GetPrefs()->GetTotalSource() > 0 || Kademlia::CSearchManager::AlreadySearchingFor(Kademlia::CUInt128(GetBuddyID()))) + { + //There are too many source lookups already or we are already searching this key. + // bad luck, as lookups aren't supposed to hapen anyway, we just let it fail, if we want + // to actually really use lookups (so buddies without known IPs), this should be reworked + // for example by adding a queuesystem for queries + DebugLogWarning(_T("TryToConnect: Buddy without knonw IP, Lookup crrently impossible")); + return true; + } + if(Kademlia::CSearchManager::StartSearch(findSource)) + { + //Started lookup.. + SetDownloadState(DS_WAITCALLBACKKAD); + } + else + { + //This should never happen.. + ASSERT(0); + } + } + return true; + } + else { + ASSERT( false ); + DebugLogError(_T("TryToConnect: Bug: No Callback available despite prechecks")); + return true; + } +} + +void CUpDownClient::Connect() +{ + // enable or disable crypting based on our and the remote clients preference + if (HasValidHash() && SupportsCryptLayer() && thePrefs.IsClientCryptLayerSupported() && (RequestsCryptLayer() || thePrefs.IsClientCryptLayerRequested())){ + //DebugLog(_T("Enabling CryptLayer on outgoing connection to client %s"), DbgGetClientInfo()); // to be removed later + socket->SetConnectionEncryption(true, GetUserHash(), false); + } + else + socket->SetConnectionEncryption(false, NULL, false); + + //Try to always tell the socket to WaitForOnConnect before you call Connect. + socket->WaitForOnConnect(); + SOCKADDR_IN sockAddr = {0}; + sockAddr.sin_family = AF_INET; + sockAddr.sin_port = htons(GetUserPort()); + sockAddr.sin_addr.S_un.S_addr = GetConnectIP(); + socket->Connect((SOCKADDR*)&sockAddr, sizeof sockAddr); + SendHelloPacket(); +} + +void CUpDownClient::ConnectionEstablished() +{ + // ok we have a connection, lets see if we want anything from this client + + // was this a direct callback? + if (m_nConnectingState == CCS_DIRECTCALLBACK) // TODO LOGREMOVE + DebugLog(_T("Direct Callback succeeded, connection established - %s"), DbgGetClientInfo()); + + // remove the connecting timer and state + //if (m_nConnectingState == CCS_NONE) // TODO LOGREMOVE + // DEBUG_ONLY( DebugLog(_T("ConnectionEstablished with CCS_NONE (incoming, thats fine)")) ); + m_nConnectingState = CCS_NONE; + theApp.clientlist->RemoveConnectingClient(this); + + // check if we should use this client to retrieve our public IP + if (theApp.GetPublicIP() == 0 && theApp.IsConnected() && m_fPeerCache) + SendPublicIPRequest(); + + switch(GetKadState()) + { + case KS_CONNECTING_FWCHECK: + SetKadState(KS_CONNECTED_FWCHECK); + break; + case KS_CONNECTING_BUDDY: + case KS_INCOMING_BUDDY: + DEBUG_ONLY( DebugLog(_T("Set KS_CONNECTED_BUDDY for client %s"), DbgGetClientInfo()) ); + SetKadState(KS_CONNECTED_BUDDY); + break; + case KS_CONNECTING_FWCHECK_UDP: + SetKadState(KS_FWCHECK_UDP); + DEBUG_ONLY( DebugLog(_T("Set KS_FWCHECK_UDP for client %s"), DbgGetClientInfo()) ); + SendFirewallCheckUDPRequest(); + break; + } + + if (GetChatState() == MS_CONNECTING || GetChatState() == MS_CHATTING) + { + if (GetFriend() != NULL && GetFriend()->IsTryingToConnect()){ + GetFriend()->UpdateFriendConnectionState(FCR_ESTABLISHED); // for friends any connectionupdate is handled in the friend class + if (credits != NULL && credits->GetCurrentIdentState(GetConnectIP()) == IS_IDFAILED) + GetFriend()->UpdateFriendConnectionState(FCR_SECUREIDENTFAILED); + } + else + theApp.emuledlg->chatwnd->chatselector.ConnectingResult(this, true); // other clients update directly + } + + switch(GetDownloadState()) + { + case DS_CONNECTING: + case DS_WAITCALLBACK: + case DS_WAITCALLBACKKAD: + m_bReaskPending = false; + SetDownloadState(DS_CONNECTED); + SendFileRequest(); + break; + } + + if (m_bReaskPending) + { + m_bReaskPending = false; + if (GetDownloadState() != DS_NONE && GetDownloadState() != DS_DOWNLOADING) + { + SetDownloadState(DS_CONNECTED); + SendFileRequest(); + } + } + + switch(GetUploadState()) + { + case US_CONNECTING: + if (theApp.uploadqueue->IsDownloading(this)) + { + SetUploadState(US_UPLOADING); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__AcceptUploadReq", this); + Packet* packet = new Packet(OP_ACCEPTUPLOADREQ,0); + theStats.AddUpDataOverheadFileRequest(packet->size); + SendPacket(packet,true); + } + } + + if (m_iFileListRequested == 1) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend(m_fSharedDirectories ? "OP__AskSharedDirs" : "OP__AskSharedFiles", this); + Packet* packet = new Packet(m_fSharedDirectories ? OP_ASKSHAREDDIRS : OP_ASKSHAREDFILES,0); + theStats.AddUpDataOverheadOther(packet->size); + SendPacket(packet,true); + } + + while (!m_WaitingPackets_list.IsEmpty()) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("Buffered Packet", this); + SendPacket(m_WaitingPackets_list.RemoveHead(), true); + } + +} + +void CUpDownClient::InitClientSoftwareVersion() +{ + if (m_pszUsername == NULL){ + m_clientSoft = SO_UNKNOWN; + return; + } + + int iHashType = GetHashType(); + if (m_bEmuleProtocol || iHashType == SO_EMULE){ + LPCTSTR pszSoftware; + switch(m_byCompatibleClient){ + case SO_CDONKEY: + m_clientSoft = SO_CDONKEY; + pszSoftware = _T("cDonkey"); + break; + case SO_XMULE: + m_clientSoft = SO_XMULE; + pszSoftware = _T("xMule"); + break; + case SO_AMULE: + m_clientSoft = SO_AMULE; + pszSoftware = _T("aMule"); + break; + case SO_SHAREAZA: + case 40: + m_clientSoft = SO_SHAREAZA; + pszSoftware = _T("Shareaza"); + break; + case SO_LPHANT: + m_clientSoft = SO_LPHANT; + pszSoftware = _T("lphant"); + break; + default: + if (m_bIsML || m_byCompatibleClient == SO_MLDONKEY){ + m_clientSoft = SO_MLDONKEY; + pszSoftware = _T("MLdonkey"); + } + else if (m_bIsHybrid){ + m_clientSoft = SO_EDONKEYHYBRID; + pszSoftware = _T("eDonkeyHybrid"); + } + else if (m_byCompatibleClient != 0){ + m_clientSoft = SO_XMULE; // means: 'eMule Compatible' + pszSoftware = _T("eMule Compat"); + } + else{ + m_clientSoft = SO_EMULE; + pszSoftware = _T("eMule"); + } + } + + int iLen; + TCHAR szSoftware[128]; + if (m_byEmuleVersion == 0){ + m_nClientVersion = MAKE_CLIENT_VERSION(0, 0, 0); + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("%s"), pszSoftware); + } + else if (m_byEmuleVersion != 0x99){ + UINT nClientMinVersion = (m_byEmuleVersion >> 4)*10 + (m_byEmuleVersion & 0x0f); + m_nClientVersion = MAKE_CLIENT_VERSION(0, nClientMinVersion, 0); + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("%s v0.%u"), pszSoftware, nClientMinVersion); + } + else{ + UINT nClientMajVersion = (m_nClientVersion >> 17) & 0x7f; + UINT nClientMinVersion = (m_nClientVersion >> 10) & 0x7f; + UINT nClientUpVersion = (m_nClientVersion >> 7) & 0x07; + m_nClientVersion = MAKE_CLIENT_VERSION(nClientMajVersion, nClientMinVersion, nClientUpVersion); + if (m_clientSoft == SO_EMULE) + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("%s v%u.%u%c"), pszSoftware, nClientMajVersion, nClientMinVersion, _T('a') + nClientUpVersion); + else if (m_clientSoft == SO_AMULE || nClientUpVersion != 0) + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("%s v%u.%u.%u"), pszSoftware, nClientMajVersion, nClientMinVersion, nClientUpVersion); + else if (m_clientSoft == SO_LPHANT) + { + if (nClientMinVersion < 10) + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("%s v%u.0%u"), pszSoftware, (nClientMajVersion-1), nClientMinVersion); + else + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("%s v%u.%u"), pszSoftware, (nClientMajVersion-1), nClientMinVersion); + } + else + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("%s v%u.%u"), pszSoftware, nClientMajVersion, nClientMinVersion); + } + if (iLen > 0){ + memcpy(m_strClientSoftware.GetBuffer(iLen), szSoftware, iLen*sizeof(TCHAR)); + m_strClientSoftware.ReleaseBuffer(iLen); + } + return; + } + + if (m_bIsHybrid){ + m_clientSoft = SO_EDONKEYHYBRID; + // seen: + // 105010 0.50.10 + // 10501 0.50.1 + // 10300 1.3.0 + // 10201 1.2.1 + // 10103 1.1.3 + // 10102 1.1.2 + // 10100 1.1 + // 1051 0.51.0 + // 1002 1.0.2 + // 1000 1.0 + // 501 0.50.1 + + UINT nClientMajVersion; + UINT nClientMinVersion; + UINT nClientUpVersion; + if (m_nClientVersion > 100000){ + UINT uMaj = m_nClientVersion/100000; + nClientMajVersion = uMaj - 1; + nClientMinVersion = (m_nClientVersion - uMaj*100000) / 100; + nClientUpVersion = m_nClientVersion % 100; + } + else if (m_nClientVersion >= 10100 && m_nClientVersion <= 10309){ + UINT uMaj = m_nClientVersion/10000; + nClientMajVersion = uMaj; + nClientMinVersion = (m_nClientVersion - uMaj*10000) / 100; + nClientUpVersion = m_nClientVersion % 10; + } + else if (m_nClientVersion > 10000){ + UINT uMaj = m_nClientVersion/10000; + nClientMajVersion = uMaj - 1; + nClientMinVersion = (m_nClientVersion - uMaj*10000) / 10; + nClientUpVersion = m_nClientVersion % 10; + } + else if (m_nClientVersion >= 1000 && m_nClientVersion < 1020){ + UINT uMaj = m_nClientVersion/1000; + nClientMajVersion = uMaj; + nClientMinVersion = (m_nClientVersion - uMaj*1000) / 10; + nClientUpVersion = m_nClientVersion % 10; + } + else if (m_nClientVersion > 1000){ + UINT uMaj = m_nClientVersion/1000; + nClientMajVersion = uMaj - 1; + nClientMinVersion = m_nClientVersion - uMaj*1000; + nClientUpVersion = 0; + } + else if (m_nClientVersion > 100){ + UINT uMin = m_nClientVersion/10; + nClientMajVersion = 0; + nClientMinVersion = uMin; + nClientUpVersion = m_nClientVersion - uMin*10; + } + else{ + nClientMajVersion = 0; + nClientMinVersion = m_nClientVersion; + nClientUpVersion = 0; + } + m_nClientVersion = MAKE_CLIENT_VERSION(nClientMajVersion, nClientMinVersion, nClientUpVersion); + + int iLen; + TCHAR szSoftware[128]; + if (nClientUpVersion) + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("eDonkeyHybrid v%u.%u.%u"), nClientMajVersion, nClientMinVersion, nClientUpVersion); + else + iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("eDonkeyHybrid v%u.%u"), nClientMajVersion, nClientMinVersion); + if (iLen > 0){ + memcpy(m_strClientSoftware.GetBuffer(iLen), szSoftware, iLen*sizeof(TCHAR)); + m_strClientSoftware.ReleaseBuffer(iLen); + } + return; + } + + if (m_bIsML || iHashType == SO_MLDONKEY){ + m_clientSoft = SO_MLDONKEY; + UINT nClientMinVersion = m_nClientVersion; + m_nClientVersion = MAKE_CLIENT_VERSION(0, nClientMinVersion, 0); + TCHAR szSoftware[128]; + int iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("MLdonkey v0.%u"), nClientMinVersion); + if (iLen > 0){ + memcpy(m_strClientSoftware.GetBuffer(iLen), szSoftware, iLen*sizeof(TCHAR)); + m_strClientSoftware.ReleaseBuffer(iLen); + } + return; + } + + if (iHashType == SO_OLDEMULE){ + m_clientSoft = SO_OLDEMULE; + UINT nClientMinVersion = m_nClientVersion; + m_nClientVersion = MAKE_CLIENT_VERSION(0, nClientMinVersion, 0); + TCHAR szSoftware[128]; + int iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("Old eMule v0.%u"), nClientMinVersion); + if (iLen > 0){ + memcpy(m_strClientSoftware.GetBuffer(iLen), szSoftware, iLen*sizeof(TCHAR)); + m_strClientSoftware.ReleaseBuffer(iLen); + } + return; + } + + m_clientSoft = SO_EDONKEY; + UINT nClientMinVersion = m_nClientVersion; + m_nClientVersion = MAKE_CLIENT_VERSION(0, nClientMinVersion, 0); + TCHAR szSoftware[128]; + int iLen = _sntprintf(szSoftware, ARRSIZE(szSoftware), _T("eDonkey v0.%u"), nClientMinVersion); + if (iLen > 0){ + memcpy(m_strClientSoftware.GetBuffer(iLen), szSoftware, iLen*sizeof(TCHAR)); + m_strClientSoftware.ReleaseBuffer(iLen); + } +} + +int CUpDownClient::GetHashType() const +{ + if (m_achUserHash[5] == 13 && m_achUserHash[14] == 110) + return SO_OLDEMULE; + else if (m_achUserHash[5] == 14 && m_achUserHash[14] == 111) + return SO_EMULE; + else if (m_achUserHash[5] == 'M' && m_achUserHash[14] == 'L') + return SO_MLDONKEY; + else + return SO_UNKNOWN; +} + +void CUpDownClient::SetUserName(LPCTSTR pszNewName) +{ + free(m_pszUsername); + if (pszNewName) + m_pszUsername = _tcsdup(pszNewName); + else + m_pszUsername = NULL; +} + +void CUpDownClient::RequestSharedFileList() +{ + if (m_iFileListRequested == 0){ + AddLogLine(true, GetResString(IDS_SHAREDFILES_REQUEST), GetUserName()); + m_iFileListRequested = 1; + TryToConnect(true); + } + else{ + LogWarning(LOG_STATUSBAR, _T("Requesting shared files from user %s (%u) is already in progress"), GetUserName(), GetUserIDHybrid()); + } +} + +void CUpDownClient::ProcessSharedFileList(const uchar* pachPacket, uint32 nSize, LPCTSTR pszDirectory) +{ + if (m_iFileListRequested > 0) + { + m_iFileListRequested--; + theApp.searchlist->ProcessSearchAnswer(pachPacket,nSize,this,NULL,pszDirectory); + } +} + +void CUpDownClient::SetUserHash(const uchar* pucUserHash) +{ + if( pucUserHash == NULL ){ + md4clr(m_achUserHash); + return; + } + md4cpy(m_achUserHash, pucUserHash); +} + +void CUpDownClient::SetBuddyID(const uchar* pucBuddyID) +{ + if( pucBuddyID == NULL ){ + md4clr(m_achBuddyID); + m_bBuddyIDValid = false; + return; + } + m_bBuddyIDValid = true; + md4cpy(m_achBuddyID, pucBuddyID); +} + +void CUpDownClient::SendPublicKeyPacket() +{ + // send our public key to the client who requested it + if (socket == NULL || credits == NULL || m_SecureIdentState != IS_KEYANDSIGNEEDED){ + ASSERT ( false ); + return; + } + if (!theApp.clientcredits->CryptoAvailable()) + return; + + Packet* packet = new Packet(OP_PUBLICKEY,theApp.clientcredits->GetPubKeyLen() + 1,OP_EMULEPROT); + theStats.AddUpDataOverheadOther(packet->size); + memcpy(packet->pBuffer+1,theApp.clientcredits->GetPublicKey(), theApp.clientcredits->GetPubKeyLen()); + packet->pBuffer[0] = theApp.clientcredits->GetPubKeyLen(); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__PublicKey", this); + SendPacket(packet, true); + m_SecureIdentState = IS_SIGNATURENEEDED; +} + +void CUpDownClient::SendSignaturePacket() +{ + // signate the public key of this client and send it + if (socket == NULL || credits == NULL || m_SecureIdentState == 0){ + ASSERT ( false ); + return; + } + + if (!theApp.clientcredits->CryptoAvailable()) + return; + if (credits->GetSecIDKeyLen() == 0) + return; // We don't have his public key yet, will be back here later + // do we have a challenge value received (actually we should if we are in this function) + if (credits->m_dwCryptRndChallengeFrom == 0){ + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("Want to send signature but challenge value is invalid ('%s')"), GetUserName()); + return; + } + // v2 + // we will use v1 as default, except if only v2 is supported + bool bUseV2; + if ( (m_bySupportSecIdent&1) == 1 ) + bUseV2 = false; + else + bUseV2 = true; + + uint8 byChaIPKind = 0; + uint32 ChallengeIP = 0; + if (bUseV2){ + if (theApp.serverconnect->GetClientID() == 0 || theApp.serverconnect->IsLowID()){ + // we cannot do not know for sure our public ip, so use the remote clients one + ChallengeIP = GetIP(); + byChaIPKind = CRYPT_CIP_REMOTECLIENT; + } + else{ + ChallengeIP = theApp.serverconnect->GetClientID(); + byChaIPKind = CRYPT_CIP_LOCALCLIENT; + } + } + //end v2 + uchar achBuffer[250]; + uint8 siglen = theApp.clientcredits->CreateSignature(credits, achBuffer, 250, ChallengeIP, byChaIPKind ); + if (siglen == 0){ + ASSERT ( false ); + return; + } + Packet* packet = new Packet(OP_SIGNATURE,siglen + 1+ ( (bUseV2)? 1:0 ),OP_EMULEPROT); + theStats.AddUpDataOverheadOther(packet->size); + memcpy(packet->pBuffer+1,achBuffer, siglen); + packet->pBuffer[0] = siglen; + if (bUseV2) + packet->pBuffer[1+siglen] = byChaIPKind; + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__Signature", this); + SendPacket(packet, true); + m_SecureIdentState = IS_ALLREQUESTSSEND; +} + +void CUpDownClient::ProcessPublicKeyPacket(const uchar* pachPacket, uint32 nSize) +{ + theApp.clientlist->AddTrackClient(this); + + if (socket == NULL || credits == NULL || pachPacket[0] != nSize-1 + || nSize < 10 || nSize > 250){ + ASSERT ( false ); + return; + } + if (!theApp.clientcredits->CryptoAvailable()) + return; + // the function will handle everything (mulitple key etc) + if (credits->SetSecureIdent(pachPacket+1, pachPacket[0])){ + // if this client wants a signature, now we can send him one + if (m_SecureIdentState == IS_SIGNATURENEEDED){ + SendSignaturePacket(); + } + else if(m_SecureIdentState == IS_KEYANDSIGNEEDED) + { + // something is wrong + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("Invalid State error: IS_KEYANDSIGNEEDED in ProcessPublicKeyPacket")); + } + } + else + { + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("Failed to use new received public key")); + } +} + +void CUpDownClient::ProcessSignaturePacket(const uchar* pachPacket, uint32 nSize) +{ + // here we spread the good guys from the bad ones ;) + + if (socket == NULL || credits == NULL || nSize > 250 || nSize < 10){ + ASSERT ( false ); + return; + } + + uint8 byChaIPKind; + if (pachPacket[0] == nSize-1) + byChaIPKind = 0; + else if (pachPacket[0] == nSize-2 && (m_bySupportSecIdent & 2) > 0) //v2 + byChaIPKind = pachPacket[nSize-1]; + else{ + ASSERT ( false ); + return; + } + + if (!theApp.clientcredits->CryptoAvailable()) + return; + + // we accept only one signature per IP, to avoid floods which need a lot cpu time for cryptfunctions + if (m_dwLastSignatureIP == GetIP()) + { + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("received multiple signatures from one client")); + return; + } + + // also make sure this client has a public key + if (credits->GetSecIDKeyLen() == 0) + { + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("received signature for client without public key")); + return; + } + + // and one more check: did we ask for a signature and sent a challange packet? + if (credits->m_dwCryptRndChallengeFor == 0) + { + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("received signature for client with invalid challenge value ('%s')"), GetUserName()); + return; + } + + if (theApp.clientcredits->VerifyIdent(credits, pachPacket+1, pachPacket[0], GetIP(), byChaIPKind ) ){ + // result is saved in function abouve + //if (thePrefs.GetLogSecureIdent()) + // AddDebugLogLine(false, _T("'%s' has passed the secure identification, V2 State: %i"), GetUserName(), byChaIPKind); + + // inform our friendobject if needed + if (GetFriend() != NULL && GetFriend()->IsTryingToConnect()) + GetFriend()->UpdateFriendConnectionState(FCR_USERHASHVERIFIED); + } + else + { + if (GetFriend() != NULL && GetFriend()->IsTryingToConnect()) + GetFriend()->UpdateFriendConnectionState(FCR_SECUREIDENTFAILED); + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("'%s' has failed the secure identification, V2 State: %i"), GetUserName(), byChaIPKind); + } + m_dwLastSignatureIP = GetIP(); +} + +void CUpDownClient::SendSecIdentStatePacket() +{ + // check if we need public key and signature + uint8 nValue = 0; + if (credits){ + if (theApp.clientcredits->CryptoAvailable()){ + if (credits->GetSecIDKeyLen() == 0) + nValue = IS_KEYANDSIGNEEDED; + else if (m_dwLastSignatureIP != GetIP()) + nValue = IS_SIGNATURENEEDED; + } + if (nValue == 0){ + //if (thePrefs.GetLogSecureIdent()) + // AddDebugLogLine(false, _T("Not sending SecIdentState Packet, because State is Zero")); + return; + } + // crypt: send random data to sign + uint32 dwRandom = rand()+1; + credits->m_dwCryptRndChallengeFor = dwRandom; + Packet* packet = new Packet(OP_SECIDENTSTATE,5,OP_EMULEPROT); + theStats.AddUpDataOverheadOther(packet->size); + packet->pBuffer[0] = nValue; + PokeUInt32(packet->pBuffer+1, dwRandom); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__SecIdentState", this); + SendPacket(packet, true); + } + else + ASSERT ( false ); +} + +void CUpDownClient::ProcessSecIdentStatePacket(const uchar* pachPacket, uint32 nSize) +{ + if (nSize != 5) + return; + if (!credits){ + ASSERT ( false ); + return; + } + switch(pachPacket[0]){ + case 0: + m_SecureIdentState = IS_UNAVAILABLE; + break; + case 1: + m_SecureIdentState = IS_SIGNATURENEEDED; + break; + case 2: + m_SecureIdentState = IS_KEYANDSIGNEEDED; + break; + } + credits->m_dwCryptRndChallengeFrom = PeekUInt32(pachPacket+1); +} + +void CUpDownClient::InfoPacketsReceived() +{ + // indicates that both Information Packets has been received + // needed for actions, which process data from both packets + ASSERT ( m_byInfopacketsReceived == IP_BOTH ); + m_byInfopacketsReceived = IP_NONE; + + if (m_bySupportSecIdent){ + SendSecIdentStatePacket(); + } +} + +void CUpDownClient::ResetFileStatusInfo() +{ + delete[] m_abyPartStatus; + m_abyPartStatus = NULL; + m_nRemoteQueueRank = 0; + m_nPartCount = 0; + m_strClientFilename.Empty(); + m_bCompleteSource = false; + m_uFileRating = 0; + m_strFileComment.Empty(); + delete m_pReqFileAICHHash; + m_pReqFileAICHHash = NULL; +} + +bool CUpDownClient::IsBanned() const +{ + return theApp.clientlist->IsBannedClient(GetIP()); +} + +void CUpDownClient::SendPreviewRequest(const CAbstractFile* pForFile) +{ + if (m_fPreviewReqPending == 0){ + m_fPreviewReqPending = 1; + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__RequestPreview", this, pForFile->GetFileHash()); + Packet* packet = new Packet(OP_REQUESTPREVIEW,16,OP_EMULEPROT); + md4cpy(packet->pBuffer,pForFile->GetFileHash()); + theStats.AddUpDataOverheadOther(packet->size); + SafeConnectAndSendPacket(packet); + } + else{ + LogWarning(LOG_STATUSBAR, GetResString(IDS_ERR_PREVIEWALREADY)); + } +} + +void CUpDownClient::SendPreviewAnswer(const CKnownFile* pForFile, CxImage** imgFrames, uint8 nCount) +{ + m_fPreviewAnsPending = 0; + CSafeMemFile data(1024); + if (pForFile){ + data.WriteHash16(pForFile->GetFileHash()); + } + else{ + static const uchar _aucZeroHash[16] = {0}; + data.WriteHash16(_aucZeroHash); + } + data.WriteUInt8(nCount); + for (int i = 0; i != nCount; i++){ + if (imgFrames == NULL){ + ASSERT ( false ); + return; + } + CxImage* cur_frame = imgFrames[i]; + if (cur_frame == NULL){ + ASSERT ( false ); + return; + } + BYTE* abyResultBuffer = NULL; + long nResultSize = 0; + if (!cur_frame->Encode(abyResultBuffer, nResultSize, CXIMAGE_FORMAT_PNG)){ + ASSERT ( false ); + return; + } + data.WriteUInt32(nResultSize); + data.Write(abyResultBuffer, nResultSize); + free(abyResultBuffer); + } + Packet* packet = new Packet(&data, OP_EMULEPROT); + packet->opcode = OP_PREVIEWANSWER; + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__PreviewAnswer", this, (uchar*)packet->pBuffer); + theStats.AddUpDataOverheadOther(packet->size); + SafeConnectAndSendPacket(packet); +} + +void CUpDownClient::ProcessPreviewReq(const uchar* pachPacket, uint32 nSize) +{ + if (nSize < 16) + throw GetResString(IDS_ERR_WRONGPACKAGESIZE); + + if (m_fPreviewAnsPending || thePrefs.CanSeeShares()==vsfaNobody || (thePrefs.CanSeeShares()==vsfaFriends && !IsFriend())) + return; + + m_fPreviewAnsPending = 1; + CKnownFile* previewFile = theApp.sharedfiles->GetFileByID(pachPacket); + if (previewFile == NULL){ + SendPreviewAnswer(NULL, NULL, 0); + } + else{ + previewFile->GrabImage(4,0,true,450,this); + } +} + +void CUpDownClient::ProcessPreviewAnswer(const uchar* pachPacket, uint32 nSize) +{ + if (m_fPreviewReqPending == 0) + return; + m_fPreviewReqPending = 0; + CSafeMemFile data(pachPacket, nSize); + uchar Hash[16]; + data.ReadHash16(Hash); + uint8 nCount = data.ReadUInt8(); + if (nCount == 0){ + LogError(LOG_STATUSBAR, GetResString(IDS_ERR_PREVIEWFAILED), GetUserName()); + return; + } + CSearchFile* sfile = theApp.searchlist->GetSearchFileByHash(Hash); + if (sfile == NULL){ + //already deleted + return; + } + + BYTE* pBuffer = NULL; + try{ + for (int i = 0; i != nCount; i++){ + uint32 nImgSize = data.ReadUInt32(); + if (nImgSize > nSize) + throw CString(_T("CUpDownClient::ProcessPreviewAnswer - Provided image size exceeds limit")); + pBuffer = new BYTE[nImgSize]; + data.Read(pBuffer, nImgSize); + CxImage* image = new CxImage(pBuffer, nImgSize, CXIMAGE_FORMAT_PNG); + delete[] pBuffer; + pBuffer = NULL; + if (image->IsValid()) + sfile->AddPreviewImg(image); + else + delete image; + } + } + catch(...){ + delete[] pBuffer; + throw; + } + (new PreviewDlg())->SetFile(sfile); +} + +// sends a packet, if needed it will establish a connection before +// options used: ignore max connections, control packet, delete packet +// !if the functions returns false that client object was deleted because the connection try failed and the object wasn't needed anymore. +bool CUpDownClient::SafeConnectAndSendPacket(Packet* packet) +{ + if (socket != NULL && socket->IsConnected()) + { + socket->SendPacket(packet, true, true); + return true; + } + else + { + m_WaitingPackets_list.AddTail(packet); + return TryToConnect(true); + } +} + +bool CUpDownClient::SendPacket(Packet* packet, bool bDeletePacket, bool bVerifyConnection) +{ + if (socket != NULL && (!bVerifyConnection || socket->IsConnected())) + { + socket->SendPacket(packet, bDeletePacket, true); + return true; + } + else + { + DebugLogError(_T("Outgoing packet (0x%X) discarded because expected socket or connection does not exists %s"), packet->opcode, DbgGetClientInfo()); + if (bDeletePacket) + delete packet; + return false; + } +} + +#ifdef _DEBUG +void CUpDownClient::AssertValid() const +{ + CObject::AssertValid(); + + CHECK_OBJ(socket); + CHECK_PTR(credits); + CHECK_PTR(m_Friend); + CHECK_OBJ(reqfile); + (void)m_abyUpPartStatus; + m_OtherRequests_list.AssertValid(); + m_OtherNoNeeded_list.AssertValid(); + (void)m_lastPartAsked; + (void)m_cMessagesReceived; + (void)m_cMessagesSent; + (void)m_dwUserIP; + (void)m_dwServerIP; + (void)m_nUserIDHybrid; + (void)m_nUserPort; + (void)m_nServerPort; + (void)m_nClientVersion; + (void)m_nUpDatarate; + (void)m_byEmuleVersion; + (void)m_byDataCompVer; + CHECK_BOOL(m_bEmuleProtocol); + CHECK_BOOL(m_bIsHybrid); + (void)m_pszUsername; + (void)m_achUserHash; + (void)m_achBuddyID; + (void)m_nBuddyIP; + (void)m_nBuddyPort; + (void)m_nUDPPort; + (void)m_nKadPort; + (void)m_byUDPVer; + (void)m_bySourceExchange1Ver; + (void)m_byAcceptCommentVer; + (void)m_byExtendedRequestsVer; + CHECK_BOOL(m_bFriendSlot); + CHECK_BOOL(m_bCommentDirty); + CHECK_BOOL(m_bIsML); + //ASSERT( m_clientSoft >= SO_EMULE && m_clientSoft <= SO_SHAREAZA || m_clientSoft == SO_MLDONKEY || m_clientSoft >= SO_EDONKEYHYBRID && m_clientSoft <= SO_UNKNOWN ); + (void)m_strClientSoftware; + (void)m_dwLastSourceRequest; + (void)m_dwLastSourceAnswer; + (void)m_dwLastAskedForSources; + (void)m_iFileListRequested; + (void)m_byCompatibleClient; + m_WaitingPackets_list.AssertValid(); + m_DontSwap_list.AssertValid(); + (void)m_lastRefreshedDLDisplay; + ASSERT( m_SecureIdentState >= IS_UNAVAILABLE && m_SecureIdentState <= IS_KEYANDSIGNEEDED ); + (void)m_dwLastSignatureIP; + ASSERT( (m_byInfopacketsReceived & ~IP_BOTH) == 0 ); + (void)m_bySupportSecIdent; + (void)m_nTransferredUp; + ASSERT( m_nUploadState >= US_UPLOADING && m_nUploadState <= US_NONE ); + (void)m_dwUploadTime; + (void)m_cAsked; + (void)m_dwLastUpRequest; + (void)m_nCurSessionUp; + (void)m_nCurQueueSessionPayloadUp; + (void)m_addedPayloadQueueSession; + (void)m_nUpPartCount; + (void)m_nUpCompleteSourcesCount; + (void)s_UpStatusBar; + (void)requpfileid; + (void)m_lastRefreshedULDisplay; + m_AvarageUDR_list.AssertValid(); + m_RequestedFiles_list.AssertValid(); + ASSERT( m_nDownloadState >= DS_DOWNLOADING && m_nDownloadState <= DS_NONE ); + (void)m_cDownAsked; + (void)m_abyPartStatus; + (void)m_strClientFilename; + (void)m_nTransferredDown; + (void)m_nCurSessionPayloadDown; + (void)m_dwDownStartTime; + (void)m_nLastBlockOffset; + (void)m_nDownDatarate; + (void)m_nDownDataRateMS; + (void)m_nSumForAvgDownDataRate; + (void)m_cShowDR; + (void)m_nRemoteQueueRank; + (void)m_dwLastBlockReceived; + (void)m_nPartCount; + ASSERT( m_nSourceFrom >= SF_SERVER && m_nSourceFrom <= SF_LINK ); + CHECK_BOOL(m_bRemoteQueueFull); + CHECK_BOOL(m_bCompleteSource); + CHECK_BOOL(m_bReaskPending); + CHECK_BOOL(m_bUDPPending); + CHECK_BOOL(m_bTransferredDownMini); + CHECK_BOOL(m_bUnicodeSupport); + ASSERT( m_nKadState >= KS_NONE && m_nKadState <= KS_CONNECTING_FWCHECK_UDP); + m_AvarageDDR_list.AssertValid(); + (void)m_nSumForAvgUpDataRate; + m_PendingBlocks_list.AssertValid(); + (void)s_StatusBar; + ASSERT( m_nChatstate >= MS_NONE && m_nChatstate <= MS_UNABLETOCONNECT ); + (void)m_strFileComment; + (void)m_uFileRating; + CHECK_BOOL(m_bCollectionUploadSlot); +#undef CHECK_PTR +#undef CHECK_BOOL +} +#endif + +#ifdef _DEBUG +void CUpDownClient::Dump(CDumpContext& dc) const +{ + CObject::Dump(dc); +} +#endif + +LPCTSTR CUpDownClient::DbgGetDownloadState() const +{ + const static LPCTSTR apszState[] = + { + _T("Downloading"), + _T("OnQueue"), + _T("Connected"), + _T("Connecting"), + _T("WaitCallback"), + _T("WaitCallbackKad"), + _T("ReqHashSet"), + _T("NoNeededParts"), + _T("TooManyConns"), + _T("TooManyConnsKad"), + _T("LowToLowIp"), + _T("Banned"), + _T("Error"), + _T("None"), + _T("RemoteQueueFull") + }; + if (GetDownloadState() >= ARRSIZE(apszState)) + return _T("*Unknown*"); + return apszState[GetDownloadState()]; +} + +LPCTSTR CUpDownClient::DbgGetUploadState() const +{ + const static LPCTSTR apszState[] = + { + _T("Uploading"), + _T("OnUploadQueue"), + _T("Connecting"), + _T("Banned"), + _T("None") + }; + if (GetUploadState() >= ARRSIZE(apszState)) + return _T("*Unknown*"); + return apszState[GetUploadState()]; +} + +LPCTSTR CUpDownClient::DbgGetKadState() const +{ + const static LPCTSTR apszState[] = + { + _T("None"), + _T("FwCheckQueued"), + _T("FwCheckConnecting"), + _T("FwCheckConnected"), + _T("BuddyQueued"), + _T("BuddyIncoming"), + _T("BuddyConnecting"), + _T("BuddyConnected"), + _T("QueuedFWCheckUDP"), + _T("FWCheckUDP"), + _T("FwCheckConnectingUDP") + }; + if (GetKadState() >= ARRSIZE(apszState)) + return _T("*Unknown*"); + return apszState[GetKadState()]; +} + +CString CUpDownClient::DbgGetFullClientSoftVer() const +{ + if (GetClientModVer().IsEmpty()) + return GetClientSoftVer(); + return GetClientSoftVer() + _T(" [") + GetClientModVer() + _T(']'); +} + +CString CUpDownClient::DbgGetClientInfo(bool bFormatIP) const +{ + CString str; + if (this != NULL) + { + try{ + if (HasLowID()) + { + if (GetConnectIP()) + { + str.Format(_T("%u@%s (%s) '%s' (%s,%s/%s/%s)"), + GetUserIDHybrid(), ipstr(GetServerIP()), + ipstr(GetConnectIP()), + GetUserName(), + DbgGetFullClientSoftVer(), + DbgGetDownloadState(), DbgGetUploadState(), DbgGetKadState()); + } + else + { + str.Format(_T("%u@%s '%s' (%s,%s/%s/%s)"), + GetUserIDHybrid(), ipstr(GetServerIP()), + GetUserName(), + DbgGetFullClientSoftVer(), + DbgGetDownloadState(), DbgGetUploadState(), DbgGetKadState()); + } + } + else + { + str.Format(bFormatIP ? _T("%-15s '%s' (%s,%s/%s/%s)") : _T("%s '%s' (%s,%s/%s/%s)"), + ipstr(GetConnectIP()), + GetUserName(), + DbgGetFullClientSoftVer(), + DbgGetDownloadState(), DbgGetUploadState(), DbgGetKadState()); + } + } + catch(...){ + str.Format(_T("%p - Invalid client instance"), this); + } + } + return str; +} + +bool CUpDownClient::CheckHandshakeFinished() const +{ + if (m_bHelloAnswerPending) + { + // 24-Nov-2004 [bc]: The reason for this is that 2 clients are connecting to each other at the same.. + //if (thePrefs.GetVerbose()) + // AddDebugLogLine(DLP_VERYLOW, false, _T("Handshake not finished - while processing packet: %s; %s"), DbgGetClientTCPOpcode(protocol, opcode), DbgGetClientInfo()); + return false; + } + + return true; +} + +void CUpDownClient::CheckForGPLEvilDoer() +{ + if (!m_strModVersion.IsEmpty()){ + LPCTSTR pszModVersion = (LPCTSTR)m_strModVersion; + + // skip leading spaces + while (*pszModVersion == _T(' ')) + pszModVersion++; + + // check for known major gpl breaker + if (_tcsnicmp(pszModVersion, _T("LH"), 2)==0 || _tcsnicmp(pszModVersion, _T("LIO"), 3)==0 || _tcsnicmp(pszModVersion, _T("PLUS PLUS"), 9)==0) + m_bGPLEvildoer = true; + } +} + +void CUpDownClient::OnSocketConnected(int /*nErrorCode*/) +{ +} + +CString CUpDownClient::GetDownloadStateDisplayString() const +{ + CString strState; + switch (GetDownloadState()) + { + case DS_CONNECTING: + strState = GetResString(IDS_CONNECTING); + break; + case DS_CONNECTED: + strState = GetResString(IDS_ASKING); + break; + case DS_WAITCALLBACK: + strState = GetResString(IDS_CONNVIASERVER); + break; + case DS_ONQUEUE: + if (IsRemoteQueueFull()) + strState = GetResString(IDS_QUEUEFULL); + else + strState = GetResString(IDS_ONQUEUE); + break; + case DS_DOWNLOADING: + strState = GetResString(IDS_TRANSFERRING); + break; + case DS_REQHASHSET: + strState = GetResString(IDS_RECHASHSET); + break; + case DS_NONEEDEDPARTS: + strState = GetResString(IDS_NONEEDEDPARTS); + break; + case DS_LOWTOLOWIP: + strState = GetResString(IDS_NOCONNECTLOW2LOW); + break; + case DS_TOOMANYCONNS: + strState = GetResString(IDS_TOOMANYCONNS); + break; + case DS_ERROR: + strState = GetResString(IDS_ERROR); + break; + case DS_WAITCALLBACKKAD: + strState = GetResString(IDS_KAD_WAITCBK); + break; + case DS_TOOMANYCONNSKAD: + strState = GetResString(IDS_KAD_TOOMANDYKADLKPS); + break; + } + + if (thePrefs.GetPeerCacheShow()) + { + switch (m_ePeerCacheDownState) + { + case PCDS_WAIT_CLIENT_REPLY: + strState += _T(" ")+GetResString(IDS_PCDS_CLIENTWAIT); + break; + case PCDS_WAIT_CACHE_REPLY: + strState += _T(" ")+GetResString(IDS_PCDS_CACHEWAIT); + break; + case PCDS_DOWNLOADING: + strState += _T(" ")+GetResString(IDS_CACHE); + break; + } + if (m_ePeerCacheDownState != PCDS_NONE && m_bPeerCacheDownHit) + strState += _T(" Hit"); + } + + return strState; +} + +CString CUpDownClient::GetUploadStateDisplayString() const +{ + CString strState; + switch (GetUploadState()){ + case US_ONUPLOADQUEUE: + strState = GetResString(IDS_ONQUEUE); + break; + case US_BANNED: + strState = GetResString(IDS_BANNED); + break; + case US_CONNECTING: + strState = GetResString(IDS_CONNECTING); + break; + case US_UPLOADING: + // GetNumberOfRequestedBlocksInQueue is no longer available and retrieving it would cause quite some extra load + // (either due to thread syncing or due to adding redunant extra varts just for this function), so given that + // "stalled, waiting for disk" should happen like never, it is removed for now + if(GetPayloadInBuffer() == 0 && /*GetNumberOfRequestedBlocksInQueue() == 0 &&*/ thePrefs.IsExtControlsEnabled()) { + strState = GetResString(IDS_US_STALLEDW4BR); + /*} else if(GetPayloadInBuffer() == 0 && thePrefs.IsExtControlsEnabled()) { + strState = GetResString(IDS_US_STALLEDREADINGFDISK);*/ + } else if(GetSlotNumber() <= theApp.uploadqueue->GetActiveUploadsCount()) { + strState = GetResString(IDS_TRANSFERRING); + } else { + strState = GetResString(IDS_TRICKLING); + } + break; + } + + if (thePrefs.GetPeerCacheShow()) + { + switch (m_ePeerCacheUpState) + { + case PCUS_WAIT_CACHE_REPLY: + strState += _T(" CacheWait"); + break; + case PCUS_UPLOADING: + strState += _T(" Cache"); + break; + } + if (m_ePeerCacheUpState != PCUS_NONE && m_bPeerCacheUpHit) + strState += _T(" Hit"); + } + + return strState; +} + +void CUpDownClient::SendPublicIPRequest(){ + if (socket && socket->IsConnected()){ + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__PublicIPReq", this); + Packet* packet = new Packet(OP_PUBLICIP_REQ,0,OP_EMULEPROT); + theStats.AddUpDataOverheadOther(packet->size); + SendPacket(packet, true); + m_fNeedOurPublicIP = 1; + } +} + +void CUpDownClient::ProcessPublicIPAnswer(const BYTE* pbyData, UINT uSize){ + if (uSize != 4) + throw GetResString(IDS_ERR_WRONGPACKAGESIZE); + uint32 dwIP = PeekUInt32(pbyData); + if (m_fNeedOurPublicIP == 1){ // did we? + m_fNeedOurPublicIP = 0; + if (theApp.GetPublicIP() == 0 && !::IsLowID(dwIP) ) + theApp.SetPublicIP(dwIP); + } +} + +void CUpDownClient::CheckFailedFileIdReqs(const uchar* aucFileHash) +{ + if ( aucFileHash != NULL && (theApp.sharedfiles->IsUnsharedFile(aucFileHash) || theApp.downloadqueue->GetFileByID(aucFileHash)) ) + return; + //if (GetDownloadState() != DS_DOWNLOADING) // filereq floods are never allowed! + { + if (m_fFailedFileIdReqs < 6)// NOTE: Do not increase this nr. without increasing the bits for 'm_fFailedFileIdReqs' + m_fFailedFileIdReqs++; + if (m_fFailedFileIdReqs == 6) + { + if (theApp.clientlist->GetBadRequests(this) < 2) + theApp.clientlist->TrackBadRequest(this, 1); + if (theApp.clientlist->GetBadRequests(this) == 2){ + theApp.clientlist->TrackBadRequest(this, -2); // reset so the client will not be rebanned right after the ban is lifted + Ban(_T("FileReq flood")); + } + throw CString(thePrefs.GetLogBannedClients() ? _T("FileReq flood") : _T("")); + } + } +} + +EUtf8Str CUpDownClient::GetUnicodeSupport() const +{ + if (m_bUnicodeSupport) + return utf8strRaw; + return utf8strNone; +} + +void CUpDownClient::SetSpammer(bool bVal){ + if (bVal) + Ban(_T("Identified as Spammer")); + else if (IsBanned() && m_fIsSpammer) + UnBan(); + m_fIsSpammer = bVal ? 1 : 0; +} + +void CUpDownClient::SetMessageFiltered(bool bVal) { + m_fMessageFiltered = bVal ? 1 : 0; +} + +bool CUpDownClient::IsObfuscatedConnectionEstablished() const { + if (socket != NULL && socket->IsConnected()) + return socket->IsObfusicating(); + else + return false; +} + +bool CUpDownClient::ShouldReceiveCryptUDPPackets() const { + return (thePrefs.IsClientCryptLayerSupported() && SupportsCryptLayer() && theApp.GetPublicIP() != 0 + && HasValidHash() && (thePrefs.IsClientCryptLayerRequested() || RequestsCryptLayer()) ); +} + +void CUpDownClient::ProcessChatMessage(CSafeMemFile* data, uint32 nLength) +{ + //filter me? + if ( (thePrefs.MsgOnlyFriends() && !IsFriend()) || (thePrefs.MsgOnlySecure() && GetUserName()==NULL) ) + { + if (!GetMessageFiltered()){ + if (thePrefs.GetVerbose()) + AddDebugLogLine(false,_T("Filtered Message from '%s' (IP:%s)"), GetUserName(), ipstr(GetConnectIP())); + } + SetMessageFiltered(true); + return; + } + + CString strMessage(data->ReadString(GetUnicodeSupport()!=utf8strNone, nLength)); + if (thePrefs.GetDebugClientTCPLevel() > 0) + Debug(_T(" %s\n"), strMessage); + + // default filtering + CString strMessageCheck(strMessage); + strMessageCheck.MakeLower(); + CString resToken; + int curPos = 0; + resToken = thePrefs.GetMessageFilter().Tokenize(_T("|"), curPos); + while (!resToken.IsEmpty()) + { + resToken.Trim(); + if (strMessageCheck.Find(resToken.MakeLower()) > -1){ + if ( thePrefs.IsAdvSpamfilterEnabled() && !IsFriend() && GetMessagesSent() == 0 ){ + SetSpammer(true); + theApp.emuledlg->chatwnd->chatselector.EndSession(this); + } + return; + } + resToken = thePrefs.GetMessageFilter().Tokenize(_T("|"), curPos); + } + + // advanced spamfilter check + if (thePrefs.IsChatCaptchaEnabled() && !IsFriend()) { + // captcha checks outrank any further checks - if the captcha has been solved, we assume its no spam + // first check if we need to sent a captcha request to this client + if (GetMessagesSent() == 0 && GetMessagesReceived() == 0 && GetChatCaptchaState() != CA_CAPTCHASOLVED) + { + // we have never sent a message to this client, and no message from him has ever passed our filters + if (GetChatCaptchaState() != CA_CHALLENGESENT) + { + // we also aren't currently expecting a cpatcha response + if (m_fSupportsCaptcha != NULL) + { + // and he supports captcha, so send him on and store the message (without showing for now) + if (m_cCaptchasSent < 3) // no more than 3 tries + { + m_strCaptchaPendingMsg = strMessage; + CSafeMemFile fileAnswer(1024); + fileAnswer.WriteUInt8(0); // no tags, for future use + CCaptchaGenerator captcha(4); + if (captcha.WriteCaptchaImage(fileAnswer)){ + m_strCaptchaChallenge = captcha.GetCaptchaText(); + m_nChatCaptchaState = CA_CHALLENGESENT; + m_cCaptchasSent++; + Packet* packet = new Packet(&fileAnswer, OP_EMULEPROT, OP_CHATCAPTCHAREQ); + theStats.AddUpDataOverheadOther(packet->size); + if (!SafeConnectAndSendPacket(packet)) + return; // deleted client while connecting + } + else{ + ASSERT( false ); + DebugLogError(_T("Failed to create Captcha for client %s"), DbgGetClientInfo()); + } + } + } + else + { + // client doesn't supports captchas, but we require them, tell him that its not going to work out + // with an answer message (will not be shown and doesn't counts as sent message) + if (m_cCaptchasSent < 1) // dont sent this notifier more than once + { + m_cCaptchasSent++; + // always sent in english + CString rstrMessage = _T("In order to avoid spam messages, this user requires you to solve a captcha before you can send a message to him. However your client does not support captchas, so you will not be able to chat with this user."); + DebugLog(_T("Received message from client not supporting captchs, filtered and sent notifier (%s)"), DbgGetClientInfo()); + SendChatMessage(rstrMessage); // could delete client + } + else + DebugLog(_T("Received message from client not supporting captchs, filtered, didn't sent notifier (%s)"), DbgGetClientInfo()); + } + return; + } + else //(GetChatCaptchaState() == CA_CHALLENGESENT) + { + // this message must be the answer to the captcha request we send him, lets verify + ASSERT( !m_strCaptchaChallenge.IsEmpty() ); + if (m_strCaptchaChallenge.CompareNoCase(strMessage.Trim().Right(min(strMessage.GetLength(), m_strCaptchaChallenge.GetLength()))) == 0){ + // allright + DebugLog(_T("Captcha solved, showing withheld message (%s)"), DbgGetClientInfo()); + m_nChatCaptchaState = CA_CAPTCHASOLVED; // this state isn't persitent, but the messagecounter will be used to determine later if the captcha has been solved + // replace captchaanswer with withheld message and show it + strMessage = m_strCaptchaPendingMsg; + m_cCaptchasSent = 0; + m_strCaptchaChallenge = _T(""); + Packet* packet = new Packet(OP_CHATCAPTCHARES, 1, OP_EMULEPROT, false); + packet->pBuffer[0] = 0; // status response + theStats.AddUpDataOverheadOther(packet->size); + if (!SafeConnectAndSendPacket(packet)) { + ASSERT( false ); // deleted client while connecting + return; + } + } + else{ // wrong, cleanup and ignore + DebugLogWarning(_T("Captcha answer failed (%s)"), DbgGetClientInfo()); + m_nChatCaptchaState = CA_NONE; + m_strCaptchaChallenge = _T(""); + m_strCaptchaPendingMsg = _T(""); + Packet* packet = new Packet(OP_CHATCAPTCHARES, 1, OP_EMULEPROT, false); + packet->pBuffer[0] = (m_cCaptchasSent < 3)? 1 : 2; // status response + theStats.AddUpDataOverheadOther(packet->size); + SafeConnectAndSendPacket(packet); + return; // nothing more todo + } + } + } + else + DEBUG_ONLY( DebugLog(_T("Message passed CaptchaFilter - already solved or not needed (%s)"), DbgGetClientInfo()) ); + + } + if (thePrefs.IsAdvSpamfilterEnabled() && !IsFriend()) // friends are never spammer... (but what if two spammers are friends :P ) + { + bool bIsSpam = false; + if (IsSpammer()) + bIsSpam = true; + else + { + + // first fixed criteria: If a client sends me an URL in his first message before I response to him + // there is a 99,9% chance that it is some poor guy advising his leech mod, or selling you .. well you know :P + if (GetMessagesSent() == 0){ + int curPos=0; + CString resToken = CString(URLINDICATOR).Tokenize(_T("|"), curPos); + while (resToken != _T("")){ + if (strMessage.Find(resToken) > (-1) ) + bIsSpam = true; + resToken= CString(URLINDICATOR).Tokenize(_T("|"),curPos); + } + // second fixed criteria: he sent me 4 or more messages and I didn't answered him once + if (GetMessagesReceived() > 3) + bIsSpam = true; + } + } + if (bIsSpam) + { + if (IsSpammer()){ + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("'%s' has been marked as spammer"), GetUserName()); + } + SetSpammer(true); + theApp.emuledlg->chatwnd->chatselector.EndSession(this); + return; + + } + } + + theApp.emuledlg->chatwnd->chatselector.ProcessMessage(this, strMessage); +} + +void CUpDownClient::ProcessCaptchaRequest(CSafeMemFile* data){ + // received a captcha request, check if we actually accept it (only after sending a message ourself to this client) + if (GetChatCaptchaState() == CA_ACCEPTING && GetChatState() != MS_NONE + && theApp.emuledlg->chatwnd->chatselector.GetItemByClient(this) != NULL) + { + // read tags (for future use) + uint8 nTagCount = data->ReadUInt8(); + for (uint32 i = 0; i < nTagCount; i++) + CTag tag(data, true); + // sanitize checks - we want a small captcha not a wallpaper + uint32 nSize = (uint32)(data->GetLength() - data->GetPosition()); + if ( nSize > 128 && nSize < 4096) + { + ULONGLONG pos = data->GetPosition(); + BYTE* byBuffer = data->Detach(); + CxImage imgCaptcha(&byBuffer[pos], nSize, CXIMAGE_FORMAT_BMP); + //free(byBuffer); + if (imgCaptcha.IsValid() && imgCaptcha.GetHeight() > 10 && imgCaptcha.GetHeight() < 50 + && imgCaptcha.GetWidth() > 10 && imgCaptcha.GetWidth() < 150 ) + { + HBITMAP hbmp = imgCaptcha.MakeBitmap(); + if (hbmp != NULL){ + m_nChatCaptchaState = CA_CAPTCHARECV; + theApp.emuledlg->chatwnd->chatselector.ShowCaptchaRequest(this, hbmp); + DeleteObject(hbmp); + } + else + DebugLogWarning(_T("Received captcha request from client, Creating bitmap failed (%s)"), DbgGetClientInfo()); + } + else + DebugLogWarning(_T("Received captcha request from client, processing image failed or invalid pixel size (%s)"), DbgGetClientInfo()); + } + else + DebugLogWarning(_T("Received captcha request from client, size sanitize check failed (%u) (%s)"), nSize, DbgGetClientInfo()); + } + else + DebugLogWarning(_T("Received captcha request from client, but don't accepting it at this time (%s)"), DbgGetClientInfo()); +} + +void CUpDownClient::ProcessCaptchaReqRes(uint8 nStatus) +{ + if (GetChatCaptchaState() == CA_SOLUTIONSENT && GetChatState() != MS_NONE + && theApp.emuledlg->chatwnd->chatselector.GetItemByClient(this) != NULL) + { + ASSERT( nStatus < 3 ); + m_nChatCaptchaState = CA_NONE; + theApp.emuledlg->chatwnd->chatselector.ShowCaptchaResult(this, GetResString((nStatus == 0) ? IDS_CAPTCHASOLVED : IDS_CAPTCHAFAILED)); + } + else { + m_nChatCaptchaState = CA_NONE; + DebugLogWarning(_T("Received captcha result from client, but don't accepting it at this time (%s)"), DbgGetClientInfo()); + } +} + +CFriend* CUpDownClient::GetFriend() const +{ + if (m_Friend != NULL && theApp.friendlist->IsValid(m_Friend)) + return m_Friend; + else if (m_Friend != NULL) + ASSERT( FALSE ); + return NULL; +} + +void CUpDownClient::SendChatMessage(CString strMessage) +{ + CSafeMemFile data; + data.WriteString(strMessage, GetUnicodeSupport()); + Packet* packet = new Packet(&data, OP_EDONKEYPROT, OP_MESSAGE); + theStats.AddUpDataOverheadOther(packet->size); + SafeConnectAndSendPacket(packet); +} + +bool CUpDownClient::HasPassedSecureIdent(bool bPassIfUnavailable) const +{ + if (credits != NULL) + { + if (credits->GetCurrentIdentState(GetConnectIP()) == IS_IDENTIFIED + || (credits->GetCurrentIdentState(GetConnectIP()) == IS_NOTAVAILABLE && bPassIfUnavailable)) + { + return true; + } + } + return false; +} + +void CUpDownClient::SendFirewallCheckUDPRequest() +{ + ASSERT( GetKadState() == KS_FWCHECK_UDP ); + if (!Kademlia::CKademlia::IsRunning()){ + SetKadState(KS_NONE); + return; + } + else if (GetUploadState() != US_NONE || GetDownloadState() != DS_NONE || GetChatState() != MS_NONE + || GetKadVersion() <= KADEMLIA_VERSION5_48a || GetKadPort() == 0) + { + Kademlia::CUDPFirewallTester::SetUDPFWCheckResult(false, true, ntohl(GetIP()), 0); // inform the tester that this test was cancelled + SetKadState(KS_NONE); + return; + } + CSafeMemFile data; + data.WriteUInt16(Kademlia::CKademlia::GetPrefs()->GetInternKadPort()); + data.WriteUInt16(Kademlia::CKademlia::GetPrefs()->GetExternalKadPort()); + data.WriteUInt32(Kademlia::CKademlia::GetPrefs()->GetUDPVerifyKey(GetConnectIP())); + Packet* packet = new Packet(&data, OP_EMULEPROT, OP_FWCHECKUDPREQ); + theStats.AddUpDataOverheadKad(packet->size); + SafeConnectAndSendPacket(packet); +} + +void CUpDownClient::ProcessFirewallCheckUDPRequest(CSafeMemFile* data){ + if (!Kademlia::CKademlia::IsRunning() || Kademlia::CKademlia::GetUDPListener() == NULL){ + DebugLogWarning(_T("Ignored Kad Firewallrequest UDP because Kad is not running (%s)"), DbgGetClientInfo()); + return; + } + // first search if we know this IP already, if so the result might be biased and we need tell the requester + bool bErrorAlreadyKnown = false; + if (GetUploadState() != US_NONE || GetDownloadState() != DS_NONE || GetChatState() != MS_NONE) + bErrorAlreadyKnown = true; + else if (Kademlia::CKademlia::GetRoutingZone()->GetContact(ntohl(GetConnectIP()), 0, false) != NULL) + bErrorAlreadyKnown = true; + + uint16 nRemoteInternPort = data->ReadUInt16(); + uint16 nRemoteExternPort = data->ReadUInt16(); + uint32 dwSenderKey = data->ReadUInt32(); + if (nRemoteInternPort == 0){ + DebugLogError(_T("UDP Firewallcheck requested with Intern Port == 0 (%s)"), DbgGetClientInfo()); + return; + } + if (dwSenderKey == 0) + DebugLogWarning(_T("UDP Firewallcheck requested with SenderKey == 0 (%s)"), DbgGetClientInfo()); + + CSafeMemFile fileTestPacket1; + fileTestPacket1.WriteUInt8(bErrorAlreadyKnown ? 1 : 0); + fileTestPacket1.WriteUInt16(nRemoteInternPort); + if (thePrefs.GetDebugClientKadUDPLevel() > 0) + DebugSend("KADEMLIA2_FIREWALLUDP", ntohl(GetConnectIP()), nRemoteInternPort); + Kademlia::CKademlia::GetUDPListener()->SendPacket(&fileTestPacket1, KADEMLIA2_FIREWALLUDP, ntohl(GetConnectIP()) + , nRemoteInternPort, Kademlia::CKadUDPKey(dwSenderKey, theApp.GetPublicIP(false)), NULL); + + // if the client has a router with PAT (and therefore a different extern port than intern), test this port too + if (nRemoteExternPort != 0 && nRemoteExternPort != nRemoteInternPort){ + CSafeMemFile fileTestPacket2; + fileTestPacket2.WriteUInt8(bErrorAlreadyKnown ? 1 : 0); + fileTestPacket2.WriteUInt16(nRemoteExternPort); + if (thePrefs.GetDebugClientKadUDPLevel() > 0) + DebugSend("KADEMLIA2_FIREWALLUDP", ntohl(GetConnectIP()), nRemoteExternPort); + Kademlia::CKademlia::GetUDPListener()->SendPacket(&fileTestPacket2, KADEMLIA2_FIREWALLUDP, ntohl(GetConnectIP()) + , nRemoteExternPort, Kademlia::CKadUDPKey(dwSenderKey, theApp.GetPublicIP(false)), NULL); + } + DebugLog(_T("Answered UDP Firewallcheck request (%s)"), DbgGetClientInfo()); +} + +void CUpDownClient::SetConnectOptions(uint8 byOptions, bool bEncryption, bool bCallback) +{ + SetCryptLayerSupport((byOptions & 0x01) != 0 && bEncryption); + SetCryptLayerRequest((byOptions & 0x02) != 0 && bEncryption); + SetCryptLayerRequires((byOptions & 0x04) != 0 && bEncryption); + SetDirectUDPCallbackSupport((byOptions & 0x08) != 0 && bCallback); +} + +void CUpDownClient::SendSharedDirectories() +{ + //TODO: Don't send shared directories which do not contain any files + // add shared directories + CString strDir; + CStringArray arFolders; + POSITION pos = thePrefs.shareddir_list.GetHeadPosition(); + while (pos) + { + strDir = theApp.sharedfiles->GetPseudoDirName(thePrefs.shareddir_list.GetNext(pos)); + if (!strDir.IsEmpty()) + arFolders.Add(strDir); + } + + // add incoming folders + for (int iCat = 0; iCat < thePrefs.GetCatCount(); iCat++) + { + strDir = theApp.sharedfiles->GetPseudoDirName(thePrefs.GetCategory(iCat)->strIncomingPath); + if (!strDir.IsEmpty()) + arFolders.Add(strDir); + } + + // add temporary folder if there are any temp files + if (theApp.downloadqueue->GetFileCount() > 0) + arFolders.Add(CString(OP_INCOMPLETE_SHARED_FILES)); + // add "Other" folder (for single shared files) if there are any single shared files + if (theApp.sharedfiles->ProbablyHaveSingleSharedFiles()) + arFolders.Add(CString(OP_OTHER_SHARED_FILES)); + + // build packet + CSafeMemFile tempfile(80); + tempfile.WriteUInt32(arFolders.GetCount()); + for (int i = 0; i < arFolders.GetCount(); i++) + tempfile.WriteString(arFolders.GetAt(i), GetUnicodeSupport()); + + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__AskSharedDirsAnswer", this); + Packet* replypacket = new Packet(&tempfile); + replypacket->opcode = OP_ASKSHAREDDIRSANS; + theStats.AddUpDataOverheadOther(replypacket->size); + VERIFY( SendPacket(replypacket, true, true) ); +} diff --git a/BtnST.cpp b/BtnST.cpp new file mode 100644 index 00000000..ba385308 --- /dev/null +++ b/BtnST.cpp @@ -0,0 +1,2115 @@ +#include "stdafx.h" +#include "BtnST.h" +#include "emule.h" +#ifdef BTNST_USE_SOUND +#define MMNODRV // mmsystem: Installable driver support +//#define MMNOSOUND // mmsystem: Sound support +#define MMNOWAVE // mmsystem: Waveform support +#define MMNOMIDI // mmsystem: MIDI support +#define MMNOAUX // mmsystem: Auxiliary audio support +#define MMNOMIXER // mmsystem: Mixer support +#define MMNOTIMER // mmsystem: Timer support +#define MMNOJOY // mmsystem: Joystick support +#define MMNOMCI // mmsystem: MCI support +#define MMNOMMIO // mmsystem: Multimedia file I/O support +#define MMNOMMSYSTEM // mmsystem: General MMSYSTEM functions +#include +#endif + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +///////////////////////////////////////////////////////////////////////////// +// CButtonST + +// Mask for control's type +#ifdef BS_TYPEMASK +#undef BS_TYPEMASK +#endif +#define BS_TYPEMASK SS_TYPEMASK + +CButtonST::CButtonST() +{ + m_bIsPressed = FALSE; + m_bIsFocused = FALSE; + m_bIsDisabled = FALSE; + m_bMouseOnButton = FALSE; + leftAligned = false; + marked = false; + + FreeResources(FALSE); + + // Default type is "flat" button + m_bIsFlat = TRUE; + // Button will be tracked also if when the window is inactive (like Internet Explorer) + m_bAlwaysTrack = TRUE; + + // By default draw border in "flat" button + m_bDrawBorder = TRUE; + + // By default icon is aligned horizontally + m_byAlign = ST_ALIGN_HORIZ; + + // By default use usual pressed style + SetPressedStyle(BTNST_PRESSED_LEFTRIGHT, FALSE); + + // By default, for "flat" button, don't draw the focus rect + m_bDrawFlatFocus = FALSE; + + // By default the button is not the default button + m_bIsDefault = FALSE; + // Invalid value, since type still unknown + m_nTypeStyle = BS_TYPEMASK; + + // By default the button is not a checkbox + m_bIsCheckBox = FALSE; + m_nCheck = 0; + + // Set default colors + SetDefaultColors(FALSE); + + // No tooltip created + m_ToolTip.m_hWnd = NULL; + + // Do not draw as a transparent button + m_bDrawTransparent = FALSE; + m_pbmpOldBk = NULL; + + // No URL defined + SetURL(NULL); + + // No cursor defined + m_hCursor = NULL; + + // No associated menu +#ifndef BTNST_USE_BCMENU + m_hMenu = NULL; +#endif + m_hParentWndMenu = NULL; + m_bMenuDisplayed = FALSE; + + m_bShowDisabledBitmap = TRUE; + + m_ptImageOrg.x = 3; + m_ptImageOrg.y = 3; + + // No defined callbacks + ::ZeroMemory(&m_csCallbacks, sizeof(m_csCallbacks)); + +#ifdef BTNST_USE_SOUND + // No defined sounds + ::ZeroMemory(&m_csSounds, sizeof(m_csSounds)); +#endif +} // End of CButtonST + +CButtonST::~CButtonST() +{ + // Restore old bitmap (if any) + if (m_dcBk.m_hDC && m_pbmpOldBk) + { + m_dcBk.SelectObject(m_pbmpOldBk); + } // if + + FreeResources(); + + // Destroy the cursor (if any) + if (m_hCursor) ::DestroyCursor(m_hCursor); + + // Destroy the menu (if any) +#ifdef BTNST_USE_BCMENU + if (m_menuPopup.m_hMenu) VERIFY( m_menuPopup.DestroyMenu() ); +#else + if (m_hMenu) VERIFY( ::DestroyMenu(m_hMenu) ); +#endif +} // End of ~CButtonST + +BEGIN_MESSAGE_MAP(CButtonST, CButton) + //{{AFX_MSG_MAP(CButtonST) + ON_WM_SETCURSOR() + ON_WM_KILLFOCUS() + ON_WM_MOUSEMOVE() + ON_WM_SYSCOLORCHANGE() + ON_CONTROL_REFLECT_EX(BN_CLICKED, OnClicked) + ON_WM_ACTIVATE() + ON_WM_ENABLE() + ON_WM_CANCELMODE() + ON_WM_GETDLGCODE() + ON_WM_CTLCOLOR_REFLECT() + //}}AFX_MSG_MAP +#ifdef BTNST_USE_BCMENU + ON_WM_MENUCHAR() + ON_WM_MEASUREITEM() +#endif + + ON_MESSAGE(BM_SETSTYLE, OnSetStyle) + ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave) + ON_MESSAGE(BM_SETCHECK, OnSetCheck) + ON_MESSAGE(BM_GETCHECK, OnGetCheck) +END_MESSAGE_MAP() + +void CButtonST::FreeResources(BOOL bCheckForNULL) +{ + if (bCheckForNULL) + { + // Destroy icons + // Note: the following two lines MUST be here! even if + // BoundChecker says they are unnecessary! + if (m_csIcons[0].hIcon) + VERIFY( ::DestroyIcon(m_csIcons[0].hIcon) ); + if (m_csIcons[1].hIcon) + VERIFY( ::DestroyIcon(m_csIcons[1].hIcon) ); + + // Destroy bitmaps + if (m_csBitmaps[0].hBitmap) + VERIFY( ::DeleteObject(m_csBitmaps[0].hBitmap) ); + if (m_csBitmaps[1].hBitmap) + VERIFY( ::DeleteObject(m_csBitmaps[1].hBitmap) ); + + // Destroy mask bitmaps + if (m_csBitmaps[0].hMask) + VERIFY( ::DeleteObject(m_csBitmaps[0].hMask) ); + if (m_csBitmaps[1].hMask) + VERIFY( ::DeleteObject(m_csBitmaps[1].hMask) ); + } // if + + ::ZeroMemory(&m_csIcons, sizeof(m_csIcons)); + ::ZeroMemory(&m_csBitmaps, sizeof(m_csBitmaps)); +} // End of FreeResources + +void CButtonST::PreSubclassWindow() +{ + UINT nBS; + + nBS = GetButtonStyle(); + + // Set initial control type + m_nTypeStyle = nBS & BS_TYPEMASK; + + // Check if this is a checkbox + if (nBS & BS_CHECKBOX) m_bIsCheckBox = TRUE; + + // Set initial default state flag + if (m_nTypeStyle == BS_DEFPUSHBUTTON) + { + // Set default state for a default button + m_bIsDefault = TRUE; + + // Adjust style for default button + m_nTypeStyle = BS_PUSHBUTTON; + } // If + + // You should not set the Owner Draw before this call + // (don't use the resource editor "Owner Draw" or + // ModifyStyle(0, BS_OWNERDRAW) before calling PreSubclassWindow() ) + ASSERT(m_nTypeStyle != BS_OWNERDRAW); + + // Switch to owner-draw + ModifyStyle(BS_TYPEMASK, BS_OWNERDRAW, SWP_FRAMECHANGED); + + CButton::PreSubclassWindow(); +} // End of PreSubclassWindow + +UINT CButtonST::OnGetDlgCode() +{ + UINT nCode = CButton::OnGetDlgCode(); + + // Tell the system if we want default state handling + // (losing default state always allowed) + nCode |= (m_bIsDefault ? DLGC_DEFPUSHBUTTON : DLGC_UNDEFPUSHBUTTON); + + return nCode; +} // End of OnGetDlgCode + +BOOL CButtonST::PreTranslateMessage(MSG* pMsg) +{ + InitToolTip(); + m_ToolTip.RelayEvent(pMsg); + + if (pMsg->message == WM_LBUTTONDBLCLK) + pMsg->message = WM_LBUTTONDOWN; + + return CButton::PreTranslateMessage(pMsg); +} // End of PreTranslateMessage + +HBRUSH CButtonST::CtlColor(CDC* /*pDC*/, UINT /*nCtlColor*/) +{ + return (HBRUSH)::GetStockObject(NULL_BRUSH); +} // End of CtlColor + +void CButtonST::OnSysColorChange() +{ + CButton::OnSysColorChange(); + + m_dcBk.DeleteDC(); + m_bmpBk.DeleteObject(); + SetDefaultColors(); +} // End of OnSysColorChange + +LRESULT CButtonST::OnSetStyle(WPARAM wParam, LPARAM lParam) +{ + UINT nNewType = (wParam & BS_TYPEMASK); + + // Update default state flag + if (nNewType == BS_DEFPUSHBUTTON) + { + m_bIsDefault = TRUE; + } // if + else if (nNewType == BS_PUSHBUTTON) + { + // Losing default state always allowed + m_bIsDefault = FALSE; + } // if + + // Can't change control type after owner-draw is set. + // Let the system process changes to other style bits + // and redrawing, while keeping owner-draw style + return DefWindowProc(BM_SETSTYLE, + (wParam & ~BS_TYPEMASK) | BS_OWNERDRAW, lParam); +} // End of OnSetStyle + +LRESULT CButtonST::OnSetCheck(WPARAM wParam, LPARAM /*lParam*/) +{ + ASSERT(m_bIsCheckBox); + + switch (wParam) + { + case BST_CHECKED: + case BST_INDETERMINATE: // Indeterminate state is handled like checked state + SetCheck(1); + break; + default: + SetCheck(0); + break; + } // switch + + return 0; +} // End of OnSetCheck + +LRESULT CButtonST::OnGetCheck(WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + ASSERT(m_bIsCheckBox); + return GetCheck(); +} // End of OnGetCheck + +#ifdef BTNST_USE_BCMENU +LRESULT CButtonST::OnMenuChar(UINT nChar, UINT nFlags, CMenu* pMenu) +{ + LRESULT lResult; + if (BCMenu::IsMenu(pMenu)) + lResult = BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu); + else + lResult = CButton::OnMenuChar(nChar, nFlags, pMenu); + return lResult; +} // End of OnMenuChar +#endif + +#ifdef BTNST_USE_BCMENU +void CButtonST::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) +{ + BOOL bSetFlag = FALSE; + if (lpMeasureItemStruct->CtlType == ODT_MENU) + { + if (IsMenu((HMENU)lpMeasureItemStruct->itemID) && BCMenu::IsMenu((HMENU)lpMeasureItemStruct->itemID)) + { + m_menuPopup.MeasureItem(lpMeasureItemStruct); + bSetFlag = TRUE; + } // if + } // if + if (!bSetFlag) CButton::OnMeasureItem(nIDCtl, lpMeasureItemStruct); +} // End of OnMeasureItem +#endif + +void CButtonST::OnEnable(BOOL bEnable) +{ + CButton::OnEnable(bEnable); + + if (bEnable == FALSE) + { + CWnd* pWnd = GetParent()->GetNextDlgTabItem(this); + if (pWnd) + pWnd->SetFocus(); + else + GetParent()->SetFocus(); + + CancelHover(); + } // if +} // End of OnEnable + +void CButtonST::OnKillFocus(CWnd * pNewWnd) +{ + CButton::OnKillFocus(pNewWnd); + CancelHover(); +} // End of OnKillFocus + +void CButtonST::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) +{ + CButton::OnActivate(nState, pWndOther, bMinimized); + if (nState == WA_INACTIVE && !marked) CancelHover(); +} // End of OnActivate + +void CButtonST::OnCancelMode() +{ + CButton::OnCancelMode(); + CancelHover(); +} // End of OnCancelMode + +BOOL CButtonST::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) +{ + // If a cursor was specified then use it! + if (m_hCursor != NULL) + { + ::SetCursor(m_hCursor); + return TRUE; + } // if + + return CButton::OnSetCursor(pWnd, nHitTest, message); +} // End of OnSetCursor + +void CButtonST::CancelHover() +{ + // Only for flat buttons + if (m_bIsFlat) + { + if (m_bMouseOnButton) + { + m_bMouseOnButton = FALSE; + Invalidate(); + } // if + } // if +} // End of CancelHover + +void CButtonST::OnMouseMove(UINT nFlags, CPoint point) +{ + CWnd* wndUnderMouse = NULL; + CWnd* wndActive = this; + TRACKMOUSEEVENT csTME; + + CButton::OnMouseMove(nFlags, point); + + ClientToScreen(&point); + wndUnderMouse = WindowFromPoint(point); + + // If the mouse enter the button with the left button pressed then do nothing + if (nFlags & MK_LBUTTON && m_bMouseOnButton == FALSE) return; + + // If our button is not flat then do nothing + if (m_bIsFlat == FALSE) return; + + if (m_bAlwaysTrack == FALSE) wndActive = GetActiveWindow(); + + if (wndUnderMouse && wndUnderMouse->m_hWnd == m_hWnd && wndActive) + { + if (!m_bMouseOnButton) + { + m_bMouseOnButton = TRUE; + + Invalidate(); + +#ifdef BTNST_USE_SOUND + // Play sound ? + if (m_csSounds[0].lpszSound) + ::PlaySound(m_csSounds[0].lpszSound, m_csSounds[0].hMod, m_csSounds[0].dwFlags); +#endif + + csTME.cbSize = sizeof(csTME); + csTME.dwFlags = TME_LEAVE; + csTME.hwndTrack = m_hWnd; + ::_TrackMouseEvent(&csTME); + } // if + } else CancelHover(); +} // End of OnMouseMove + +// Handler for WM_MOUSELEAVE +LRESULT CButtonST::OnMouseLeave(WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + CancelHover(); + return 0; +} // End of OnMouseLeave + +BOOL CButtonST::OnClicked() +{ + SetFocus(); + +#ifdef BTNST_USE_SOUND + // Play sound ? + if (m_csSounds[1].lpszSound) + ::PlaySound(m_csSounds[1].lpszSound, m_csSounds[1].hMod, m_csSounds[1].dwFlags); +#endif + + if (m_bIsCheckBox) + { + m_nCheck = !m_nCheck; + Invalidate(); + } // if + else + { + // Handle the menu (if any) +#ifdef BTNST_USE_BCMENU + if (m_menuPopup.m_hMenu) +#else + if (m_hMenu) +#endif + { + CRect rWnd; + GetWindowRect(rWnd); + + m_bMenuDisplayed = TRUE; + Invalidate(); + +#ifdef BTNST_USE_BCMENU + BCMenu* psub = (BCMenu*)m_menuPopup.GetSubMenu(0); + if (m_csCallbacks.hWnd) ::SendMessage(m_csCallbacks.hWnd, m_csCallbacks.nMessage, (WPARAM)psub, m_csCallbacks.lParam); + DWORD dwRetValue = psub->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, rWnd.left, rWnd.bottom, this, NULL); +#else + HMENU hSubMenu = ::GetSubMenu(m_hMenu, 0); + if (m_csCallbacks.hWnd) ::SendMessage(m_csCallbacks.hWnd, m_csCallbacks.nMessage, (WPARAM)hSubMenu, m_csCallbacks.lParam); + DWORD dwRetValue = ::TrackPopupMenuEx(hSubMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, rWnd.left, rWnd.bottom, m_hParentWndMenu, NULL); +#endif + + m_bMenuDisplayed = FALSE; + Invalidate(); + + if (dwRetValue) + ::PostMessage(m_hParentWndMenu, WM_COMMAND, MAKEWPARAM(dwRetValue, 0), (LPARAM)NULL); + } // if + else + { + // Handle the URL (if any) + if (_tcslen(m_szURL) > 0) + { + SHELLEXECUTEINFO csSEI; + + memset(&csSEI, 0, sizeof(csSEI)); + csSEI.cbSize = sizeof(SHELLEXECUTEINFO); + csSEI.fMask = SEE_MASK_FLAG_NO_UI; + csSEI.lpVerb = _T("open"); + csSEI.lpFile = m_szURL; + csSEI.nShow = SW_SHOWMAXIMIZED; + ::ShellExecuteEx(&csSEI); + } // if + } // else + } // else + + return FALSE; +} // End of OnClicked + +void CButtonST::DrawItem(LPDRAWITEMSTRUCT lpDIS) +{ + CDC* pDC = CDC::FromHandle(lpDIS->hDC); + CPen* pOldPen; + + // Checkbox? + if (m_bIsCheckBox) + { + m_bIsPressed = ((lpDIS->itemState & ODS_SELECTED) || (m_nCheck != 0) || marked ); + } // if + else // Normal button OR other button style ... + { + m_bIsPressed = ((lpDIS->itemState & ODS_SELECTED) || marked); + + // If there is a menu and it's displayed, draw the button as pressed + if ( +#ifdef BTNST_USE_BCMENU + m_menuPopup.m_hMenu +#else + m_hMenu +#endif + && m_bMenuDisplayed) m_bIsPressed = TRUE; + } // else + + m_bIsFocused = (lpDIS->itemState & ODS_FOCUS); + m_bIsDisabled = (lpDIS->itemState & ODS_DISABLED); + + CRect itemRect = lpDIS->rcItem; + + pDC->SetBkMode(TRANSPARENT); + + if (m_bIsFlat == FALSE) + { + if (m_bIsFocused || m_bIsDefault) + { + CBrush br(RGB(0,0,0)); + pDC->FrameRect(&itemRect, &br); + itemRect.DeflateRect(1, 1); + } // if + } // if + + // Prepare draw... paint button background + + // Draw transparent? + if (m_bDrawTransparent) + PaintBk(pDC); + else + OnDrawBackground(pDC, &itemRect); + + // Draw pressed button + if (m_bIsPressed) + { + if (m_bIsFlat) + { + if (m_bDrawBorder) + OnDrawBorder(pDC, &itemRect); + } + else + { + CBrush brBtnShadow(GetSysColor(COLOR_BTNSHADOW)); + pDC->FrameRect(&itemRect, &brBtnShadow); + } + } + else // ...else draw non pressed button + { + CPen penBtnHiLight(PS_SOLID, 0, GetSysColor(COLOR_BTNHILIGHT)); // White + CPen pen3DLight(PS_SOLID, 0, GetSysColor(COLOR_3DLIGHT)); // Light gray + CPen penBtnShadow(PS_SOLID, 0, GetSysColor(COLOR_BTNSHADOW)); // Dark gray + CPen pen3DDKShadow(PS_SOLID, 0, GetSysColor(COLOR_3DDKSHADOW)); // Black + + if (m_bIsFlat) + { + if (m_bMouseOnButton && m_bDrawBorder) + OnDrawBorder(pDC, &itemRect); + } + else + { + // Draw top-left borders + // White line + pOldPen = pDC->SelectObject(&penBtnHiLight); + pDC->MoveTo(itemRect.left, itemRect.bottom-1); + pDC->LineTo(itemRect.left, itemRect.top); + pDC->LineTo(itemRect.right, itemRect.top); + // Light gray line + pDC->SelectObject(pen3DLight); + pDC->MoveTo(itemRect.left+1, itemRect.bottom-1); + pDC->LineTo(itemRect.left+1, itemRect.top+1); + pDC->LineTo(itemRect.right, itemRect.top+1); + // Draw bottom-right borders + // Black line + pDC->SelectObject(pen3DDKShadow); + pDC->MoveTo(itemRect.left, itemRect.bottom-1); + pDC->LineTo(itemRect.right-1, itemRect.bottom-1); + pDC->LineTo(itemRect.right-1, itemRect.top-1); + // Dark gray line + pDC->SelectObject(penBtnShadow); + pDC->MoveTo(itemRect.left+1, itemRect.bottom-2); + pDC->LineTo(itemRect.right-2, itemRect.bottom-2); + pDC->LineTo(itemRect.right-2, itemRect.top); + // + pDC->SelectObject(pOldPen); + } // else + } // else + + // Read the button's title + CString sTitle; + GetWindowText(sTitle); + + CRect captionRect = lpDIS->rcItem; + + // Draw the icon + if (m_csIcons[0].hIcon) + { + DrawTheIcon(pDC, !sTitle.IsEmpty(), &lpDIS->rcItem, &captionRect, m_bIsPressed, m_bIsDisabled); + } // if + + if (m_csBitmaps[0].hBitmap) + { + pDC->SetBkColor(RGB(255,255,255)); + DrawTheBitmap(pDC, !sTitle.IsEmpty(), &lpDIS->rcItem, &captionRect, m_bIsPressed, m_bIsDisabled); + } // if + + // Write the button title (if any) + if (sTitle.IsEmpty() == FALSE) + { + DrawTheText(pDC, (LPCTSTR)sTitle, &lpDIS->rcItem, &captionRect, m_bIsPressed, m_bIsDisabled); + } // if + + if (m_bIsFlat == FALSE || (m_bIsFlat && m_bDrawFlatFocus)) + { + // Draw the focus rect + if (m_bIsFocused) + { + CRect focusRect = itemRect; + if (!m_bIsFlat) + focusRect.DeflateRect(3, 3); + pDC->DrawFocusRect(&focusRect); + } // if + } // if +} // End of DrawItem + +void CButtonST::PaintBk(CDC* pDC) +{ + CClientDC clDC(GetParent()); + CRect rect; + CRect rect1; + + GetClientRect(rect); + + GetWindowRect(rect1); + GetParent()->ScreenToClient(rect1); + + if (m_dcBk.m_hDC == NULL) + { + m_dcBk.CreateCompatibleDC(&clDC); + m_bmpBk.CreateCompatibleBitmap(&clDC, rect.Width(), rect.Height()); + m_pbmpOldBk = m_dcBk.SelectObject(&m_bmpBk); + m_dcBk.BitBlt(0, 0, rect.Width(), rect.Height(), &clDC, rect1.left, rect1.top, SRCCOPY); + } // if + + pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &m_dcBk, 0, 0, SRCCOPY); +} // End of PaintBk + +HBITMAP CButtonST::CreateBitmapMask(HBITMAP hSourceBitmap, DWORD dwWidth, DWORD dwHeight, COLORREF crTransColor) +{ + HBITMAP hMask = NULL; + HDC hdcSrc = NULL; + HDC hdcDest = NULL; + HBITMAP hbmSrcT = NULL; + HBITMAP hbmDestT = NULL; + COLORREF crSaveBk; + COLORREF crSaveDestText; + + hMask = ::CreateBitmap(dwWidth, dwHeight, 1, 1, NULL); + if (hMask == NULL) return NULL; + + hdcSrc = ::CreateCompatibleDC(NULL); + hdcDest = ::CreateCompatibleDC(NULL); + + hbmSrcT = (HBITMAP)::SelectObject(hdcSrc, hSourceBitmap); + hbmDestT = (HBITMAP)::SelectObject(hdcDest, hMask); + + crSaveBk = ::SetBkColor(hdcSrc, crTransColor); + + ::BitBlt(hdcDest, 0, 0, dwWidth, dwHeight, hdcSrc, 0, 0, SRCCOPY); + + crSaveDestText = ::SetTextColor(hdcSrc, RGB(255, 255, 255)); + ::SetBkColor(hdcSrc,RGB(0, 0, 0)); + + ::BitBlt(hdcSrc, 0, 0, dwWidth, dwHeight, hdcDest, 0, 0, SRCAND); + + SetTextColor(hdcDest, crSaveDestText); + + ::SetBkColor(hdcSrc, crSaveBk); + ::SelectObject(hdcSrc, hbmSrcT); + ::SelectObject(hdcDest, hbmDestT); + + ::DeleteDC(hdcSrc); + ::DeleteDC(hdcDest); + + return hMask; +} // End of CreateBitmapMask + +// +// Parameters: +// [IN] bHasTitle +// TRUE if the button has a text +// [IN] rpItem +// A pointer to a RECT structure indicating the allowed paint area +// [IN/OUT]rpTitle +// A pointer to a CRect object indicating the paint area reserved for the +// text. This structure will be modified if necessary. +// [IN] bIsPressed +// TRUE if the button is currently pressed +// [IN] dwWidth +// Width of the image (icon or bitmap) +// [IN] dwHeight +// Height of the image (icon or bitmap) +// [OUT] rpImage +// A pointer to a CRect object that will receive the area available to the image +// +void CButtonST::PrepareImageRect(BOOL bHasTitle, RECT* rpItem, CRect* rpTitle, BOOL bIsPressed, DWORD dwWidth, DWORD dwHeight, CRect* rpImage) +{ + CRect rBtn; + + rpImage->CopyRect(rpItem); + + switch (m_byAlign) + { + case ST_ALIGN_HORIZ: + if (bHasTitle == FALSE) + { + // Center image horizontally + rpImage->left += ((rpImage->Width() - (long)dwWidth)/2); + } + else + { + // Image must be placed just inside the focus rect + rpImage->left += m_ptImageOrg.x; + rpTitle->left += dwWidth + m_ptImageOrg.x; + } + // Center image vertically + rpImage->top += ((rpImage->Height() - (long)dwHeight)/2); + break; + + case ST_ALIGN_HORIZ_RIGHT: + GetClientRect(&rBtn); + if (bHasTitle == FALSE) + { + // Center image horizontally + rpImage->left += ((rpImage->Width() - (long)dwWidth)/2); + } + else + { + // Image must be placed just inside the focus rect + rpTitle->right = rpTitle->Width() - dwWidth - m_ptImageOrg.x; + rpTitle->left = m_ptImageOrg.x; + rpImage->left = rBtn.right - dwWidth - m_ptImageOrg.x; + // Center image vertically + rpImage->top += ((rpImage->Height() - (long)dwHeight)/2); + } + break; + + case ST_ALIGN_VERT: + // Center image horizontally + rpImage->left += ((rpImage->Width() - (long)dwWidth)/2); + if (bHasTitle == FALSE) + { + // Center image vertically + rpImage->top += ((rpImage->Height() - (long)dwHeight)/2); + } + else + { + rpImage->top = m_ptImageOrg.y; + rpTitle->top += dwHeight; + } + break; + + case ST_ALIGN_OVERLAP: + break; + } // switch + + // If button is pressed then press image also + if (bIsPressed && m_bIsCheckBox == FALSE) + rpImage->OffsetRect(m_ptPressedOffset.x, m_ptPressedOffset.y); +} // End of PrepareImageRect + +void CButtonST::DrawTheIcon(CDC* pDC, BOOL bHasTitle, RECT* rpItem, CRect* rpCaption, BOOL bIsPressed, BOOL bIsDisabled) +{ + BYTE byIndex = 0; + + // Select the icon to use + if ((m_bIsCheckBox && bIsPressed) || (!m_bIsCheckBox && (bIsPressed || m_bMouseOnButton))) + byIndex = 0; + else + byIndex = (m_csIcons[1].hIcon == NULL ? 0 : 1); + + CRect rImage; + PrepareImageRect(bHasTitle, rpItem, rpCaption, bIsPressed, m_csIcons[byIndex].dwWidth, m_csIcons[byIndex].dwHeight, &rImage); + + // Ole'! + pDC->DrawState( rImage.TopLeft(), + rImage.Size(), + m_csIcons[byIndex].hIcon, + (bIsDisabled ? DSS_DISABLED : DSS_NORMAL), + (CBrush*)NULL); +} // End of DrawTheIcon + +void CButtonST::DrawTheBitmap(CDC* pDC, BOOL bHasTitle, RECT* rpItem, CRect* rpCaption, BOOL bIsPressed, BOOL bIsDisabled) +{ + HDC hdcBmpMem = NULL; + HBITMAP hbmOldBmp = NULL; + HDC hdcMem = NULL; + HBITMAP hbmT = NULL; + + BYTE byIndex = 0; + + // Select the bitmap to use + if ((m_bIsCheckBox && bIsPressed) || (!m_bIsCheckBox && (bIsPressed || m_bMouseOnButton))) + byIndex = 0; + else + byIndex = (m_csBitmaps[1].hBitmap == NULL ? 0 : 1); + + CRect rImage; + PrepareImageRect(bHasTitle, rpItem, rpCaption, bIsPressed, m_csBitmaps[byIndex].dwWidth, m_csBitmaps[byIndex].dwHeight, &rImage); + + hdcBmpMem = ::CreateCompatibleDC(pDC->m_hDC); + + hbmOldBmp = (HBITMAP)::SelectObject(hdcBmpMem, m_csBitmaps[byIndex].hBitmap); + + hdcMem = ::CreateCompatibleDC(NULL); + + hbmT = (HBITMAP)::SelectObject(hdcMem, m_csBitmaps[byIndex].hMask); + + if (bIsDisabled && m_bShowDisabledBitmap) + { + HDC hDC = NULL; + HBITMAP hBitmap = NULL; + + hDC = ::CreateCompatibleDC(pDC->m_hDC); + hBitmap = ::CreateCompatibleBitmap(pDC->m_hDC, m_csBitmaps[byIndex].dwWidth, m_csBitmaps[byIndex].dwHeight); + HBITMAP hOldBmp2 = (HBITMAP)::SelectObject(hDC, hBitmap); + + RECT rRect; + rRect.left = 0; + rRect.top = 0; + rRect.right = rImage.right + 1; + rRect.bottom = rImage.bottom + 1; + ::FillRect(hDC, &rRect, (HBRUSH)RGB(255, 255, 255)); + + COLORREF crOldColor = ::SetBkColor(hDC, RGB(255,255,255)); + + ::BitBlt(hDC, 0, 0, m_csBitmaps[byIndex].dwWidth, m_csBitmaps[byIndex].dwHeight, hdcMem, 0, 0, SRCAND); + ::BitBlt(hDC, 0, 0, m_csBitmaps[byIndex].dwWidth, m_csBitmaps[byIndex].dwHeight, hdcBmpMem, 0, 0, SRCPAINT); + + ::SetBkColor(hDC, crOldColor); + ::SelectObject(hDC, hOldBmp2); + ::DeleteDC(hDC); + + pDC->DrawState( CPoint(rImage.left/*+1*/, rImage.top), + CSize(m_csBitmaps[byIndex].dwWidth, m_csBitmaps[byIndex].dwHeight), + hBitmap, DST_BITMAP | DSS_DISABLED); + + VERIFY( ::DeleteObject(hBitmap) ); + } // if + else + { + ::BitBlt(pDC->m_hDC, rImage.left, rImage.top, m_csBitmaps[byIndex].dwWidth, m_csBitmaps[byIndex].dwHeight, hdcMem, 0, 0, SRCAND); + + ::BitBlt(pDC->m_hDC, rImage.left, rImage.top, m_csBitmaps[byIndex].dwWidth, m_csBitmaps[byIndex].dwHeight, hdcBmpMem, 0, 0, SRCPAINT); + } // else + + ::SelectObject(hdcMem, hbmT); + ::DeleteDC(hdcMem); + + ::SelectObject(hdcBmpMem, hbmOldBmp); + ::DeleteDC(hdcBmpMem); +} // End of DrawTheBitmap + +void CButtonST::DrawTheText(CDC* pDC, LPCTSTR lpszText, RECT* /*rpItem*/, CRect* rpCaption, BOOL /*bIsPressed*/, BOOL /*bIsDisabled*/) +{ + // Draw the button's title + // If button is pressed then "press" title also + if (m_bIsPressed && m_bIsCheckBox == FALSE) + rpCaption->OffsetRect(m_ptPressedOffset.x, m_ptPressedOffset.y); + + // ONLY FOR DEBUG + //CBrush brBtnShadow(RGB(255, 0, 0)); + //pDC->FrameRect(rCaption, &brBtnShadow); + + // Center text + CRect centerRect = rpCaption; + pDC->DrawText(lpszText, -1, rpCaption, DT_WORDBREAK | DT_CENTER | DT_CALCRECT); + + if (!leftAligned) rpCaption->OffsetRect((centerRect.Width() - rpCaption->Width())/2, (centerRect.Height() - rpCaption->Height())/2); + else rpCaption->OffsetRect( 8, (centerRect.Height() - rpCaption->Height())/2); + + /* RFU + rpCaption->OffsetRect(0, (centerRect.Height() - rpCaption->Height())/2); + rpCaption->OffsetRect((centerRect.Width() - rpCaption->Width())-4, (centerRect.Height() - rpCaption->Height())/2); + */ + + pDC->SetBkMode(TRANSPARENT); + /* + pDC->DrawState(rCaption->TopLeft(), rCaption->Size(), (LPCTSTR)sTitle, (bIsDisabled ? DSS_DISABLED : DSS_NORMAL), + TRUE, 0, (CBrush*)NULL); + */ + if (m_bIsDisabled) + { + rpCaption->OffsetRect(1, 1); + pDC->SetTextColor(::GetSysColor(COLOR_3DHILIGHT)); + pDC->DrawText(lpszText, -1, rpCaption, DT_WORDBREAK | DT_CENTER); + rpCaption->OffsetRect(-1, -1); + pDC->SetTextColor(::GetSysColor(COLOR_3DSHADOW)); + pDC->DrawText(lpszText, -1, rpCaption, DT_WORDBREAK | DT_CENTER); + } // if + else + { + if (m_bMouseOnButton || m_bIsPressed) + { + pDC->SetTextColor(m_crColors[BTNST_COLOR_FG_IN]); + pDC->SetBkColor(m_crColors[BTNST_COLOR_BK_IN]); + } // if + else + { + pDC->SetTextColor(m_crColors[BTNST_COLOR_FG_OUT]); + pDC->SetBkColor(m_crColors[BTNST_COLOR_BK_OUT]); + } // else + pDC->DrawText(lpszText, -1, rpCaption, DT_WORDBREAK | DT_CENTER); + } // if +} // End of DrawTheText + +// This function creates a grayscale icon starting from a given icon. +// The resulting icon will have the same size of the original one. +// +// Parameters: +// [IN] hIcon +// Handle to the original icon. +// +// Return value: +// If the function succeeds, the return value is the handle to the newly created +// grayscale icon. +// If the function fails, the return value is NULL. +// +// Updates: +// 03/May/2002 Removed dependancy from m_hWnd +// Removed 1 BitBlt operation +// +HICON CButtonST::CreateGrayscaleIcon(HICON hIcon) +{ + HICON hGrayIcon = NULL; + HDC hMainDC = NULL, hMemDC1 = NULL, hMemDC2 = NULL; + BITMAP bmp; + HBITMAP hOldBmp1 = NULL, hOldBmp2 = NULL; + ICONINFO csII, csGrayII; + BOOL bRetValue = FALSE; + + bRetValue = ::GetIconInfo(hIcon, &csII); + if (bRetValue == FALSE) return NULL; + + hMainDC = ::GetDC(HWND_DESKTOP); + hMemDC1 = ::CreateCompatibleDC(hMainDC); + hMemDC2 = ::CreateCompatibleDC(hMainDC); + if (hMainDC == NULL || hMemDC1 == NULL || hMemDC2 == NULL) return NULL; + + if (::GetObject(csII.hbmColor, sizeof(BITMAP), &bmp)) + { + DWORD dwWidth = csII.xHotspot*2; + DWORD dwHeight = csII.yHotspot*2; + + csGrayII.hbmColor = ::CreateBitmap(dwWidth, dwHeight, bmp.bmPlanes, bmp.bmBitsPixel, NULL); + if (csGrayII.hbmColor) + { + hOldBmp1 = (HBITMAP)::SelectObject(hMemDC1, csII.hbmColor); + hOldBmp2 = (HBITMAP)::SelectObject(hMemDC2, csGrayII.hbmColor); + + //::BitBlt(hMemDC2, 0, 0, dwWidth, dwHeight, hMemDC1, 0, 0, SRCCOPY); + + DWORD dwLoopY = 0, dwLoopX = 0; + COLORREF crPixel = 0; + BYTE byNewPixel = 0; + + for (dwLoopY = 0; dwLoopY < dwHeight; dwLoopY++) + { + for (dwLoopX = 0; dwLoopX < dwWidth; dwLoopX++) + { + crPixel = ::GetPixel(hMemDC1, dwLoopX, dwLoopY); + + byNewPixel = (BYTE)((GetRValue(crPixel) * 0.299) + (GetGValue(crPixel) * 0.587) + (GetBValue(crPixel) * 0.114)); + if (crPixel) ::SetPixel(hMemDC2, dwLoopX, dwLoopY, RGB(byNewPixel, byNewPixel, byNewPixel)); + } // for + } // for + + ::SelectObject(hMemDC1, hOldBmp1); + ::SelectObject(hMemDC2, hOldBmp2); + + csGrayII.hbmMask = csII.hbmMask; + + csGrayII.fIcon = TRUE; + hGrayIcon = ::CreateIconIndirect(&csGrayII); + } // if + + VERIFY( ::DeleteObject(csGrayII.hbmColor) ); + //::DeleteObject(csGrayII.hbmMask); + } // if + + VERIFY( ::DeleteObject(csII.hbmColor) ); + VERIFY( ::DeleteObject(csII.hbmMask) ); + VERIFY( ::DeleteDC(hMemDC1) ); + VERIFY( ::DeleteDC(hMemDC2) ); + ::ReleaseDC(NULL, hMainDC); + + return hGrayIcon; +} // End of CreateGrayscaleIcon + +// This function assigns icons to the button. +// Any previous icon or bitmap will be removed. +// +// Parameters: +// [IN] nIconIn +// ID number of the icon resource to show when the mouse is over the button. +// Pass NULL to remove any icon from the button. +// [IN] nIconOut +// ID number of the icon resource to show when the mouse is outside the button. +// Can be NULL. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDRESOURCE +// Failed loading the specified resource. +// +DWORD CButtonST::SetIcon(LPCTSTR pszIconIn, LPCTSTR pszIconOut) +{ + HICON hIconIn = NULL; + HICON hIconOut = NULL; + + // Set icon when the mouse is IN the button + hIconIn = theApp.LoadIcon(pszIconIn, 16, 16); + + // Set icon when the mouse is OUT the button + if (pszIconOut) + { + if ((UINT)pszIconOut == (UINT)BTNST_AUTO_GRAY) + hIconOut = BTNST_AUTO_GRAY; + else + hIconOut = theApp.LoadIcon(pszIconOut, 16, 16); + } // if + + return SetIcon(hIconIn, hIconOut); +} // End of SetIcon + +// This function assigns icons to the button. +// Any previous icon or bitmap will be removed. +// +// Parameters: +// [IN] hIconIn +// Handle fo the icon to show when the mouse is over the button. +// Pass NULL to remove any icon from the button. +// [IN] hIconOut +// Handle to the icon to show when the mouse is outside the button. +// Can be NULL. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDRESOURCE +// Failed loading the specified resource. +// +DWORD CButtonST::SetIcon(HICON hIconIn, HICON hIconOut) +{ + BOOL bRetValue; + ICONINFO ii; + + // Free any loaded resource + FreeResources(); + + if (hIconIn) + { + // Icon when mouse over button? + m_csIcons[0].hIcon = hIconIn; + // Get icon dimension + ::ZeroMemory(&ii, sizeof(ICONINFO)); + bRetValue = ::GetIconInfo(hIconIn, &ii); + if (bRetValue == FALSE) + { + FreeResources(); + return BTNST_INVALIDRESOURCE; + } // if + + m_csIcons[0].dwWidth = (DWORD)(ii.xHotspot * 2); + m_csIcons[0].dwHeight = (DWORD)(ii.yHotspot * 2); + VERIFY( ::DeleteObject(ii.hbmMask) ); + VERIFY( ::DeleteObject(ii.hbmColor) ); + + // Icon when mouse outside button? + if (hIconOut) + { + if (hIconOut == BTNST_AUTO_GRAY) + { + hIconOut = CreateGrayscaleIcon(hIconIn); + } // if + + m_csIcons[1].hIcon = hIconOut; + // Get icon dimension + ::ZeroMemory(&ii, sizeof(ICONINFO)); + bRetValue = ::GetIconInfo(hIconOut, &ii); + if (bRetValue == FALSE) + { + FreeResources(); + return BTNST_INVALIDRESOURCE; + } // if + + m_csIcons[1].dwWidth = (DWORD)(ii.xHotspot * 2); + m_csIcons[1].dwHeight = (DWORD)(ii.yHotspot * 2); + VERIFY( ::DeleteObject(ii.hbmMask) ); + VERIFY( ::DeleteObject(ii.hbmColor) ); + } // if + } // if + + Invalidate(); + + return BTNST_OK; +} // End of SetIcon + +// This function assigns bitmaps to the button. +// Any previous icon or bitmap will be removed. +// +// Parameters: +// [IN] nBitmapIn +// ID number of the bitmap resource to show when the mouse is over the button. +// Pass NULL to remove any bitmap from the button. +// [IN] crTransColorIn +// Color (inside nBitmapIn) to be used as transparent color. +// [IN] nBitmapOut +// ID number of the bitmap resource to show when the mouse is outside the button. +// Can be NULL. +// [IN] crTransColorOut +// Color (inside nBitmapOut) to be used as transparent color. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDRESOURCE +// Failed loading the specified resource. +// BTNST_FAILEDMASK +// Failed creating mask bitmap. +// +DWORD CButtonST::SetBitmaps(int nBitmapIn, COLORREF crTransColorIn, int nBitmapOut, COLORREF crTransColorOut) +{ + HBITMAP hBitmapIn = NULL; + HBITMAP hBitmapOut = NULL; + HINSTANCE hInstResource = NULL; + + // Find correct resource handle + hInstResource = AfxFindResourceHandle(MAKEINTRESOURCE(nBitmapIn), RT_BITMAP); + + // Load bitmap In + hBitmapIn = (HBITMAP)::LoadImage(hInstResource, MAKEINTRESOURCE(nBitmapIn), IMAGE_BITMAP, 0, 0, 0); + + // Load bitmap Out + if (nBitmapOut) + hBitmapOut = (HBITMAP)::LoadImage(hInstResource, MAKEINTRESOURCE(nBitmapOut), IMAGE_BITMAP, 0, 0, 0); + + return SetBitmaps(hBitmapIn, crTransColorIn, hBitmapOut, crTransColorOut); +} // End of SetBitmaps + +// This function assigns bitmaps to the button. +// Any previous icon or bitmap will be removed. +// +// Parameters: +// [IN] hBitmapIn +// Handle fo the bitmap to show when the mouse is over the button. +// Pass NULL to remove any bitmap from the button. +// [IN] crTransColorIn +// Color (inside hBitmapIn) to be used as transparent color. +// [IN] hBitmapOut +// Handle to the bitmap to show when the mouse is outside the button. +// Can be NULL. +// [IN] crTransColorOut +// Color (inside hBitmapOut) to be used as transparent color. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDRESOURCE +// Failed loading the specified resource. +// BTNST_FAILEDMASK +// Failed creating mask bitmap. +// +DWORD CButtonST::SetBitmaps(HBITMAP hBitmapIn, COLORREF crTransColorIn, HBITMAP hBitmapOut, COLORREF crTransColorOut) +{ + int nRetValue; + BITMAP csBitmapSize; + + // Free any loaded resource + FreeResources(); + + if (hBitmapIn) + { + m_csBitmaps[0].hBitmap = hBitmapIn; + m_csBitmaps[0].crTransparent = crTransColorIn; + // Get bitmap size + nRetValue = ::GetObject(hBitmapIn, sizeof(csBitmapSize), &csBitmapSize); + if (nRetValue == 0) + { + FreeResources(); + return BTNST_INVALIDRESOURCE; + } // if + m_csBitmaps[0].dwWidth = (DWORD)csBitmapSize.bmWidth; + m_csBitmaps[0].dwHeight = (DWORD)csBitmapSize.bmHeight; + + // Create mask for bitmap In + m_csBitmaps[0].hMask = CreateBitmapMask(hBitmapIn, m_csBitmaps[0].dwWidth, m_csBitmaps[0].dwHeight, crTransColorIn); + if (m_csBitmaps[0].hMask == NULL) + { + FreeResources(); + return BTNST_FAILEDMASK; + } // if + + if (hBitmapOut) + { + m_csBitmaps[1].hBitmap = hBitmapOut; + m_csBitmaps[1].crTransparent = crTransColorOut; + // Get bitmap size + nRetValue = ::GetObject(hBitmapOut, sizeof(csBitmapSize), &csBitmapSize); + if (nRetValue == 0) + { + FreeResources(); + return BTNST_INVALIDRESOURCE; + } // if + m_csBitmaps[1].dwWidth = (DWORD)csBitmapSize.bmWidth; + m_csBitmaps[1].dwHeight = (DWORD)csBitmapSize.bmHeight; + + // Create mask for bitmap Out + m_csBitmaps[1].hMask = CreateBitmapMask(hBitmapOut, m_csBitmaps[1].dwWidth, m_csBitmaps[1].dwHeight, crTransColorOut); + if (m_csBitmaps[1].hMask == NULL) + { + FreeResources(); + return BTNST_FAILEDMASK; + } // if + } // if + } // if + + Invalidate(); + + return BTNST_OK; +} // End of SetBitmaps + +// This functions sets the button to have a standard or flat style. +// +// Parameters: +// [IN] bFlat +// If TRUE the button will have a flat style, else +// will have a standard style. +// By default, CButtonST buttons are flat. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::SetFlat(BOOL bFlat, BOOL bRepaint) +{ + m_bIsFlat = bFlat; + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of SetFlat + +// This function sets the alignment type between icon/bitmap and text. +// +// Parameters: +// [IN] byAlign +// Alignment type. Can be one of the following values: +// ST_ALIGN_HORIZ Icon/bitmap on the left, text on the right +// ST_ALIGN_VERT Icon/bitmap on the top, text on the bottom +// ST_ALIGN_HORIZ_RIGHT Icon/bitmap on the right, text on the left +// ST_ALIGN_OVERLAP Icon/bitmap on the same space as text +// By default, CButtonST buttons have ST_ALIGN_HORIZ alignment. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDALIGN +// Alignment type not supported. +// +DWORD CButtonST::SetAlign(BYTE byAlign, BOOL bRepaint) +{ + switch (byAlign) + { + case ST_ALIGN_HORIZ: + case ST_ALIGN_HORIZ_RIGHT: + case ST_ALIGN_VERT: + case ST_ALIGN_OVERLAP: + m_byAlign = byAlign; + if (bRepaint) Invalidate(); + return BTNST_OK; + break; + } // switch + + return BTNST_INVALIDALIGN; +} // End of SetAlign + +// This function sets the pressed style. +// +// Parameters: +// [IN] byStyle +// Pressed style. Can be one of the following values: +// BTNST_PRESSED_LEFTRIGHT Pressed style from left to right (as usual) +// BTNST_PRESSED_TOPBOTTOM Pressed style from top to bottom +// By default, CButtonST buttons have BTNST_PRESSED_LEFTRIGHT style. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDPRESSEDSTYLE +// Pressed style not supported. +// +DWORD CButtonST::SetPressedStyle(BYTE byStyle, BOOL bRepaint) +{ + switch (byStyle) + { + case BTNST_PRESSED_LEFTRIGHT: + m_ptPressedOffset.x = 1; + m_ptPressedOffset.y = 1; + break; + case BTNST_PRESSED_TOPBOTTOM: + m_ptPressedOffset.x = 0; + m_ptPressedOffset.y = 2; + break; + default: + return BTNST_INVALIDPRESSEDSTYLE; + } // switch + + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of SetPressedStyle + +// This function sets the state of the checkbox. +// If the button is not a checkbox, this function has no meaning. +// +// Parameters: +// [IN] nCheck +// 1 to check the checkbox. +// 0 to un-check the checkbox. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::SetCheck(int nCheck, BOOL bRepaint) +{ + if (m_bIsCheckBox) + { + if (nCheck == 0) m_nCheck = 0; + else m_nCheck = 1; + + if (bRepaint) Invalidate(); + } // if + + return BTNST_OK; +} // End of SetCheck + +// This function returns the current state of the checkbox. +// If the button is not a checkbox, this function has no meaning. +// +// Return value: +// The current state of the checkbox. +// 1 if checked. +// 0 if not checked or the button is not a checkbox. +// +int CButtonST::GetCheck() +{ + return m_nCheck; +} // End of GetCheck + +// This function sets all colors to a default value. +// +// Parameters: +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::SetDefaultColors(BOOL bRepaint) +{ + m_crColors[BTNST_COLOR_BK_IN] = ::GetSysColor(COLOR_BTNFACE); + m_crColors[BTNST_COLOR_FG_IN] = ::GetSysColor(COLOR_BTNTEXT); + m_crColors[BTNST_COLOR_BK_OUT] = ::GetSysColor(COLOR_BTNFACE); + m_crColors[BTNST_COLOR_FG_OUT] = ::GetSysColor(COLOR_BTNTEXT); + m_crColors[BTNST_COLOR_BK_FOCUS] = ::GetSysColor(COLOR_BTNFACE); + m_crColors[BTNST_COLOR_FG_FOCUS] = ::GetSysColor(COLOR_BTNTEXT); + + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of SetDefaultColors + +// This function sets the color to use for a particular state. +// +// Parameters: +// [IN] byColorIndex +// Index of the color to set. Can be one of the following values: +// BTNST_COLOR_BK_IN Background color when mouse is over the button +// BTNST_COLOR_FG_IN Text color when mouse is over the button +// BTNST_COLOR_BK_OUT Background color when mouse is outside the button +// BTNST_COLOR_FG_OUT Text color when mouse is outside the button +// BTNST_COLOR_BK_FOCUS Background color when the button is focused +// BTNST_COLOR_FG_FOCUS Text color when the button is focused +// [IN] crColor +// New color. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDINDEX +// Invalid color index. +// +DWORD CButtonST::SetColor(BYTE byColorIndex, COLORREF crColor, BOOL bRepaint) +{ + if (byColorIndex >= BTNST_MAX_COLORS) return BTNST_INVALIDINDEX; + + // Set new color + m_crColors[byColorIndex] = crColor; + + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of SetColor + +// This functions returns the color used for a particular state. +// +// Parameters: +// [IN] byColorIndex +// Index of the color to get. +// See SetColor for the list of available colors. +// [OUT] crpColor +// A pointer to a COLORREF that will receive the color. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDINDEX +// Invalid color index. +// +DWORD CButtonST::GetColor(BYTE byColorIndex, COLORREF* crpColor) +{ + if (byColorIndex >= BTNST_MAX_COLORS) return BTNST_INVALIDINDEX; + + // Get color + *crpColor = m_crColors[byColorIndex]; + + return BTNST_OK; +} // End of GetColor + +// This function applies an offset to the RGB components of the specified color. +// This function can be seen as an easy way to make a color darker or lighter than +// its default value. +// +// Parameters: +// [IN] byColorIndex +// Index of the color to set. +// See SetColor for the list of available colors. +// [IN] shOffsetColor +// A short value indicating the offset to apply to the color. +// This value must be between -255 and 255. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDINDEX +// Invalid color index. +// BTNST_BADPARAM +// The specified offset is out of range. +// +DWORD CButtonST::OffsetColor(BYTE byColorIndex, short shOffset, BOOL bRepaint) +{ + BYTE byRed = 0; + BYTE byGreen = 0; + BYTE byBlue = 0; + short shOffsetR = shOffset; + short shOffsetG = shOffset; + short shOffsetB = shOffset; + + if (byColorIndex >= BTNST_MAX_COLORS) return BTNST_INVALIDINDEX; + if (shOffset < -255 || shOffset > 255) return BTNST_BADPARAM; + + // Get RGB components of specified color + byRed = GetRValue(m_crColors[byColorIndex]); + byGreen = GetGValue(m_crColors[byColorIndex]); + byBlue = GetBValue(m_crColors[byColorIndex]); + + // Calculate max. allowed real offset + if (shOffset > 0) + { + if (byRed + shOffset > 255) shOffsetR = 255 - byRed; + if (byGreen + shOffset > 255) shOffsetG = 255 - byGreen; + if (byBlue + shOffset > 255) shOffsetB = 255 - byBlue; + + shOffset = min(min(shOffsetR, shOffsetG), shOffsetB); + } // if + else + { + if (byRed + shOffset < 0) shOffsetR = -byRed; + if (byGreen + shOffset < 0) shOffsetG = -byGreen; + if (byBlue + shOffset < 0) shOffsetB = -byBlue; + + shOffset = max(max(shOffsetR, shOffsetG), shOffsetB); + } // else + + // Set new color + m_crColors[byColorIndex] = RGB(byRed + shOffset, byGreen + shOffset, byBlue + shOffset); + + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of OffsetColor + +// This function sets the hilight logic for the button. +// Applies only to flat buttons. +// +// Parameters: +// [IN] bAlwaysTrack +// If TRUE the button will be hilighted even if the window that owns it, is +// not the active window. +// If FALSE the button will be hilighted only if the window that owns it, +// is the active window. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::SetAlwaysTrack(BOOL bAlwaysTrack) +{ + m_bAlwaysTrack = bAlwaysTrack; + return BTNST_OK; +} // End of SetAlwaysTrack + +// This function sets the cursor to be used when the mouse is over the button. +// +// Parameters: +// [IN] nCursorId +// ID number of the cursor resource. +// Pass NULL to remove a previously loaded cursor. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDRESOURCE +// Failed loading the specified resource. +// +DWORD CButtonST::SetBtnCursor(int nCursorId, BOOL bRepaint) +{ + HINSTANCE hInstResource = NULL; + // Destroy any previous cursor + if (m_hCursor) + { + ::DestroyCursor(m_hCursor); + m_hCursor = NULL; + } // if + + // Load cursor + if (nCursorId) + { + hInstResource = AfxFindResourceHandle(MAKEINTRESOURCE(nCursorId), RT_GROUP_CURSOR); + // Load cursor resource + m_hCursor = (HCURSOR)::LoadImage(hInstResource, MAKEINTRESOURCE(nCursorId), IMAGE_CURSOR, 0, 0, 0); + // Repaint the button + if (bRepaint) Invalidate(); + // If something wrong + if (m_hCursor == NULL) return BTNST_INVALIDRESOURCE; + } // if + + return BTNST_OK; +} // End of SetBtnCursor + +// This function sets if the button border must be drawn. +// Applies only to flat buttons. +// +// Parameters: +// [IN] bDrawBorder +// If TRUE the border will be drawn. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::DrawBorder(BOOL bDrawBorder, BOOL bRepaint) +{ + m_bDrawBorder = bDrawBorder; + // Repaint the button + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of DrawBorder + +// This function sets if the focus rectangle must be drawn for flat buttons. +// +// Parameters: +// [IN] bDrawFlatFocus +// If TRUE the focus rectangle will be drawn also for flat buttons. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::DrawFlatFocus(BOOL bDrawFlatFocus, BOOL bRepaint) +{ + m_bDrawFlatFocus = bDrawFlatFocus; + // Repaint the button + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of DrawFlatFocus + +void CButtonST::InitToolTip() +{ + if (m_ToolTip.m_hWnd == NULL) + { + // Create ToolTip control + m_ToolTip.Create(this); + // Create inactive + m_ToolTip.Activate(FALSE); + // Enable multiline + m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, 400); + } // if +} // End of InitToolTip + +// This function sets the text to show in the button tooltip. +// +// Parameters: +// [IN] nText +// ID number of the string resource containing the text to show. +// [IN] bActivate +// If TRUE the tooltip will be created active. +// +void CButtonST::SetTooltipText(int nText, BOOL bActivate) +{ + CString sText; + + // Load string resource + sText.LoadString(nText); + // If string resource is not empty + if (sText.IsEmpty() == FALSE) SetTooltipText((LPCTSTR)sText, bActivate); +} // End of SetTooltipText + +// This function sets the text to show in the button tooltip. +// +// Parameters: +// [IN] lpszText +// Pointer to a null-terminated string containing the text to show. +// [IN] bActivate +// If TRUE the tooltip will be created active. +// +void CButtonST::SetTooltipText(LPCTSTR lpszText, BOOL bActivate) +{ + // We cannot accept NULL pointer + if (lpszText == NULL) return; + + // Initialize ToolTip + InitToolTip(); + + // If there is no tooltip defined then add it + if (m_ToolTip.GetToolCount() == 0) + { + CRect rectBtn; + GetClientRect(rectBtn); + m_ToolTip.AddTool(this, lpszText, rectBtn, 1); + } // if + + // Set text for tooltip + m_ToolTip.UpdateTipText(lpszText, this, 1); + m_ToolTip.Activate(bActivate); +} // End of SetTooltipText + +// This function enables or disables the button tooltip. +// +// Parameters: +// [IN] bActivate +// If TRUE the tooltip will be activated. +// +void CButtonST::ActivateTooltip(BOOL bActivate) +{ + // If there is no tooltip then do nothing + if (m_ToolTip.GetToolCount() == 0) return; + + // Activate tooltip + m_ToolTip.Activate(bActivate); +} // End of EnableTooltip + +// This function returns if the button is the default button. +// +// Return value: +// TRUE +// The button is the default button. +// FALSE +// The button is not the default button. +// +BOOL CButtonST::GetDefault() +{ + return m_bIsDefault; +} // End of GetDefault + +// This function enables the transparent mode. +// Note: this operation is not reversible. +// DrawTransparent should be called just after the button is created. +// Do not use trasparent buttons until you really need it (you have a bitmapped +// background) since each transparent button makes a copy in memory of its background. +// This may bring unnecessary memory use and execution overload. +// +// Parameters: +// [IN] bRepaint +// If TRUE the control will be repainted. +// +void CButtonST::DrawTransparent(BOOL bRepaint) +{ + m_bDrawTransparent = TRUE; + + // Restore old bitmap (if any) + if (m_dcBk.m_hDC != NULL && m_pbmpOldBk != NULL) + { + m_dcBk.SelectObject(m_pbmpOldBk); + } // if + + m_bmpBk.DeleteObject(); + m_dcBk.DeleteDC(); + + // Repaint the button + if (bRepaint) Invalidate(); +} // End of DrawTransparent + +DWORD CButtonST::SetBk(CDC* pDC) +{ + if (m_bDrawTransparent && pDC) + { + // Restore old bitmap (if any) + if (m_dcBk.m_hDC != NULL && m_pbmpOldBk != NULL) + { + m_dcBk.SelectObject(m_pbmpOldBk); + } // if + + m_bmpBk.DeleteObject(); + m_dcBk.DeleteDC(); + + CRect rect; + CRect rect1; + + GetClientRect(rect); + + GetWindowRect(rect1); + GetParent()->ScreenToClient(rect1); + + m_dcBk.CreateCompatibleDC(pDC); + m_bmpBk.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); + m_pbmpOldBk = m_dcBk.SelectObject(&m_bmpBk); + m_dcBk.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, rect1.left, rect1.top, SRCCOPY); + + return BTNST_OK; + } // if + + return BTNST_BADPARAM; +} // End of SetBk + +// This function sets the URL that will be opened when the button is clicked. +// +// Parameters: +// [IN] lpszURL +// Pointer to a null-terminated string that contains the URL. +// Pass NULL to removed any previously specified URL. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::SetURL(LPCTSTR lpszURL) +{ + // Remove any existing URL + memset(m_szURL, 0, sizeof(m_szURL)); + + if (lpszURL) + { + // Store the URL + _tcsncpy(m_szURL, lpszURL, _MAX_PATH); + } // if + + return BTNST_OK; +} // End of SetURL + +// This function associates a menu to the button. +// The menu will be displayed clicking the button. +// +// Parameters: +// [IN] nMenu +// ID number of the menu resource. +// Pass NULL to remove any menu from the button. +// [IN] hParentWnd +// Handle to the window that owns the menu. +// This window receives all messages from the menu. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDRESOURCE +// Failed loading the specified resource. +// +#ifndef BTNST_USE_BCMENU +DWORD CButtonST::SetMenu(UINT nMenu, HWND hParentWnd, BOOL bRepaint) +{ + HINSTANCE hInstResource = NULL; + + // Destroy any previous menu + if (m_hMenu) + { + VERIFY( ::DestroyMenu(m_hMenu) ); + m_hMenu = NULL; + m_hParentWndMenu = NULL; + m_bMenuDisplayed = FALSE; + } // if + + // Load menu + if (nMenu) + { + // Find correct resource handle + hInstResource = AfxFindResourceHandle(MAKEINTRESOURCE(nMenu), RT_MENU); + // Load menu resource + m_hMenu = ::LoadMenu(hInstResource, MAKEINTRESOURCE(nMenu)); + m_hParentWndMenu = hParentWnd; + // If something wrong + if (m_hMenu == NULL) return BTNST_INVALIDRESOURCE; + } // if + + // Repaint the button + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of SetMenu +#endif + +// This function associates a menu to the button. +// The menu will be displayed clicking the button. +// The menu will be handled by the BCMenu class. +// +// Parameters: +// [IN] nMenu +// ID number of the menu resource. +// Pass NULL to remove any menu from the button. +// [IN] hParentWnd +// Handle to the window that owns the menu. +// This window receives all messages from the menu. +// [IN] bWinXPStyle +// If TRUE the menu will be displayed using the new Windows XP style. +// If FALSE the menu will be displayed using the standard style. +// [IN] nToolbarID +// Resource ID of the toolbar to be associated to the menu. +// [IN] sizeToolbarIcon +// A CSize object indicating the size (in pixels) of each icon into the toolbar. +// All icons into the toolbar must have the same size. +// [IN] crToolbarBk +// A COLORREF value indicating the color to use as background for the icons into the toolbar. +// This color will be used as the "transparent" color. +// [IN] bRepaint +// If TRUE the control will be repainted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// BTNST_INVALIDRESOURCE +// Failed loading the specified resource. +// +#ifdef BTNST_USE_BCMENU +DWORD CButtonST::SetMenu(UINT nMenu, HWND hParentWnd, BOOL bWinXPStyle, UINT nToolbarID, CSize sizeToolbarIcon, COLORREF crToolbarBk, BOOL bRepaint) +{ + BOOL bRetValue = FALSE; + + // Destroy any previous menu + if (m_menuPopup.m_hMenu) + { + VERIFY( m_menuPopup.DestroyMenu() ); + m_hParentWndMenu = NULL; + m_bMenuDisplayed = FALSE; + } // if + + // Load menu + if (nMenu) + { + m_menuPopup.SetMenuDrawMode(bWinXPStyle); + // Load menu + bRetValue = m_menuPopup.LoadMenu(nMenu); + // If something wrong + if (bRetValue == FALSE) return BTNST_INVALIDRESOURCE; + + // Load toolbar + if (nToolbarID) + { + m_menuPopup.SetBitmapBackground(crToolbarBk); + m_menuPopup.SetIconSize(sizeToolbarIcon.cx, sizeToolbarIcon.cy); + + bRetValue = m_menuPopup.LoadToolbar(nToolbarID); + // If something wrong + if (bRetValue == FALSE) + { + VERIFY( m_menuPopup.DestroyMenu() ); + return BTNST_INVALIDRESOURCE; + } // if + } // if + + m_hParentWndMenu = hParentWnd; + } // if + + // Repaint the button + if (bRepaint) Invalidate(); + + return BTNST_OK; +} // End of SetMenu +#endif + +// This function sets the callback message that will be sent to the +// specified window just before the menu associated to the button is displayed. +// +// Parameters: +// [IN] hWnd +// Handle of the window that will receive the callback message. +// Pass NULL to remove any previously specified callback message. +// [IN] nMessage +// Callback message to send to window. +// [IN] lParam +// A 32 bits user specified value that will be passed to the callback function. +// +// Remarks: +// the callback function must be in the form: +// LRESULT On_MenuCallback(WPARAM wParam, LPARAM lParam) +// Where: +// [IN] wParam +// If support for BCMenu is enabled: a pointer to BCMenu +// else a HMENU handle to the menu that is being to be displayed. +// [IN] lParam +// The 32 bits user specified value. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::SetMenuCallback(HWND hWnd, UINT nMessage, LPARAM lParam) +{ + m_csCallbacks.hWnd = hWnd; + m_csCallbacks.nMessage = nMessage; + m_csCallbacks.lParam = lParam; + + return BTNST_OK; +} // End of SetMenuCallback + +// This function resizes the button to the same size of the image. +// To get good results both the IN and OUT images should have the same size. +// +void CButtonST::SizeToContent() +{ + if (m_csIcons[0].hIcon) + { + m_ptImageOrg.x = 0; + m_ptImageOrg.y = 0; + SetWindowPos( NULL, -1, -1, m_csIcons[0].dwWidth, m_csIcons[0].dwHeight, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + } // if + else + if (m_csBitmaps[0].hBitmap) + { + m_ptImageOrg.x = 0; + m_ptImageOrg.y = 0; + SetWindowPos( NULL, -1, -1, m_csBitmaps[0].dwWidth, m_csBitmaps[0].dwHeight, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + } // if +} // End of SizeToContent + +// This function sets the sound that must be played on particular button states. +// +// Parameters: +// [IN] lpszSound +// A string that specifies the sound to play. +// If hMod is NULL this string is interpreted as a filename, else it +// is interpreted as a resource identifier. +// Pass NULL to remove any previously specified sound. +// [IN] hMod +// Handle to the executable file that contains the resource to be loaded. +// This parameter must be NULL unless lpszSound specifies a resource identifier. +// [IN] bPlayOnClick +// TRUE if the sound must be played when the button is clicked. +// FALSE if the sound must be played when the mouse is moved over the button. +// [IN] bPlayAsync +// TRUE if the sound must be played asynchronously. +// FALSE if the sound must be played synchronously. The application takes control +// when the sound is completely played. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +#ifdef BTNST_USE_SOUND +DWORD CButtonST::SetSound(LPCTSTR lpszSound, HMODULE hMod, BOOL bPlayOnClick, BOOL bPlayAsync) +{ + BYTE byIndex = bPlayOnClick ? 1 : 0; + + // Store new sound + if (lpszSound) + { + if (hMod) // From resource identifier ? + { + m_csSounds[byIndex].lpszSound = lpszSound; + } // if + else + { + _tcscpy(m_csSounds[byIndex].szSound, lpszSound); + m_csSounds[byIndex].lpszSound = m_csSounds[byIndex].szSound; + } // else + + m_csSounds[byIndex].hMod = hMod; + m_csSounds[byIndex].dwFlags = SND_NODEFAULT | SND_NOWAIT; + m_csSounds[byIndex].dwFlags |= hMod ? SND_RESOURCE : SND_FILENAME; + m_csSounds[byIndex].dwFlags |= bPlayAsync ? SND_ASYNC : SND_SYNC; + } // if + else + { + // Or remove any existing + ::ZeroMemory(&m_csSounds[byIndex], sizeof(STRUCT_SOUND)); + } // else + + return BTNST_OK; +} // End of SetSound +#endif + +// This function is called every time the button background needs to be painted. +// If the button is in transparent mode this function will NOT be called. +// This is a virtual function that can be rewritten in CButtonST-derived classes +// to produce a whole range of buttons not available by default. +// +// Parameters: +// [IN] pDC +// Pointer to a CDC object that indicates the device context. +// [IN] pRect +// Pointer to a CRect object that indicates the bounds of the +// area to be painted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::OnDrawBackground(CDC* pDC, LPCRECT pRect) +{ + COLORREF crColor; + + if (m_bMouseOnButton || m_bIsPressed) + crColor = m_crColors[BTNST_COLOR_BK_IN]; + else + { + if (m_bIsFocused) + crColor = m_crColors[BTNST_COLOR_BK_FOCUS]; + else + crColor = m_crColors[BTNST_COLOR_BK_OUT]; + } // else + + CBrush brBackground(crColor); + + pDC->FillRect(pRect, &brBackground); + + return BTNST_OK; +} // End of OnDrawBackground + +// This function is called every time the button border needs to be painted. +// If the button is in standard (not flat) mode this function will NOT be called. +// This is a virtual function that can be rewritten in CButtonST-derived classes +// to produce a whole range of buttons not available by default. +// +// Parameters: +// [IN] pDC +// Pointer to a CDC object that indicates the device context. +// [IN] pRect +// Pointer to a CRect object that indicates the bounds of the +// area to be painted. +// +// Return value: +// BTNST_OK +// Function executed successfully. +// +DWORD CButtonST::OnDrawBorder(CDC* pDC, LPCRECT pRect) +{ + if (m_bIsPressed) + pDC->Draw3dRect(pRect, ::GetSysColor(COLOR_BTNSHADOW), ::GetSysColor(COLOR_BTNHILIGHT)); + else + pDC->Draw3dRect(pRect, ::GetSysColor(COLOR_BTNHILIGHT), ::GetSysColor(COLOR_BTNSHADOW)); + + return BTNST_OK; +} // End of OnDrawBorder + +#undef BS_TYPEMASK diff --git a/BtnST.h b/BtnST.h new file mode 100644 index 00000000..1dcd8a03 --- /dev/null +++ b/BtnST.h @@ -0,0 +1,303 @@ +// +// Class: CButtonST +// +// Compiler: Visual C++ +// Tested on: Visual C++ 5.0 +// Visual C++ 6.0 +// +// Version: See GetVersionC() or GetVersionI() +// +// Created: xx/xxxx/1998 +// Updated: 22/July/2002 +// +// Author: Davide Calabro' davide_calabro@yahoo.com +// http://www.softechsoftware.it +// +// Note: Code for the PreSubclassWindow and OnSetStyle functions +// has been taken from the COddButton class +// published by Paolo Messina and Jerzy Kaczorowski +// +// Disclaimer +// ---------- +// THIS SOFTWARE AND THE ACCOMPANYING FILES ARE DISTRIBUTED "AS IS" AND WITHOUT +// ANY WARRANTIES WHETHER EXPRESSED OR IMPLIED. NO REPONSIBILITIES FOR POSSIBLE +// DAMAGES OR EVEN FUNCTIONALITY CAN BE TAKEN. THE USER MUST ASSUME THE ENTIRE +// RISK OF USING THIS SOFTWARE. +// +// Terms of use +// ------------ +// THIS SOFTWARE IS FREE FOR PERSONAL USE OR FREEWARE APPLICATIONS. +// IF YOU USE THIS SOFTWARE IN COMMERCIAL OR SHAREWARE APPLICATIONS YOU +// ARE GENTLY ASKED TO DONATE 1$ (ONE U.S. DOLLAR) TO THE AUTHOR: +// +// Davide Calabro' +// P.O. Box 65 +// 21019 Somma Lombardo (VA) +// Italy +// +#ifndef _BTNST_H +#define _BTNST_H + +// Uncomment the following 2 lines to enable support for BCMenu class +//#define BTNST_USE_BCMENU +//#include "BCMenu.h" + +// Uncomment the following line to enable support for sound effects +//#define BTNST_USE_SOUND + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +// Return values +#ifndef BTNST_OK +#define BTNST_OK 0 +#endif +#ifndef BTNST_INVALIDRESOURCE +#define BTNST_INVALIDRESOURCE 1 +#endif +#ifndef BTNST_FAILEDMASK +#define BTNST_FAILEDMASK 2 +#endif +#ifndef BTNST_INVALIDINDEX +#define BTNST_INVALIDINDEX 3 +#endif +#ifndef BTNST_INVALIDALIGN +#define BTNST_INVALIDALIGN 4 +#endif +#ifndef BTNST_BADPARAM +#define BTNST_BADPARAM 5 +#endif +#ifndef BTNST_INVALIDPRESSEDSTYLE +#define BTNST_INVALIDPRESSEDSTYLE 6 +#endif + +// Dummy identifier for grayscale icon +#ifndef BTNST_AUTO_GRAY +#define BTNST_AUTO_GRAY (HICON)(0xffffffff - 1L) +#endif + +class CButtonST : public CButton +{ +public: + CButtonST(); + ~CButtonST(); + + enum { ST_ALIGN_HORIZ = 0, // Icon/bitmap on the left, text on the right + ST_ALIGN_VERT, // Icon/bitmap on the top, text on the bottom + ST_ALIGN_HORIZ_RIGHT, // Icon/bitmap on the right, text on the left + ST_ALIGN_OVERLAP // Icon/bitmap on the same space as text + }; + + enum { BTNST_COLOR_BK_IN = 0, // Background color when mouse is INside + BTNST_COLOR_FG_IN, // Text color when mouse is INside + BTNST_COLOR_BK_OUT, // Background color when mouse is OUTside + BTNST_COLOR_FG_OUT, // Text color when mouse is OUTside + BTNST_COLOR_BK_FOCUS, // Background color when the button is focused + BTNST_COLOR_FG_FOCUS, // Text color when the button is focused + BTNST_MAX_COLORS + }; + + enum { BTNST_PRESSED_LEFTRIGHT = 0, // Pressed style from left to right (as usual) + BTNST_PRESSED_TOPBOTTOM // Pressed style from top to bottom + }; + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CButtonST) + public: + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + virtual BOOL PreTranslateMessage(MSG* pMsg); + protected: + virtual void PreSubclassWindow(); + //}}AFX_VIRTUAL + +public: + DWORD SetDefaultColors(BOOL bRepaint = TRUE); + DWORD SetColor(BYTE byColorIndex, COLORREF crColor, BOOL bRepaint = TRUE); + DWORD GetColor(BYTE byColorIndex, COLORREF* crpColor); + DWORD OffsetColor(BYTE byColorIndex, short shOffset, BOOL bRepaint = TRUE); + void SetLeftAlign(bool in) {leftAligned=in;} + bool GetLeftAlign() {return leftAligned;} + void SetMarked(BOOL in) {marked=in;} + DWORD SetCheck(int nCheck, BOOL bRepaint = TRUE); + int GetCheck(); + + DWORD SetURL(LPCTSTR lpszURL = NULL); + void DrawTransparent(BOOL bRepaint = FALSE); + DWORD SetBk(CDC* pDC); + + BOOL GetDefault(); + DWORD SetAlwaysTrack(BOOL bAlwaysTrack = TRUE); + + void SetTooltipText(int nText, BOOL bActivate = TRUE); + void SetTooltipText(LPCTSTR lpszText, BOOL bActivate = TRUE); + void ActivateTooltip(BOOL bEnable = TRUE); + void Repaint() {Invalidate();} + + DWORD SetBtnCursor(int nCursorId = NULL, BOOL bRepaint = TRUE); + + DWORD SetFlat(BOOL bFlat = TRUE, BOOL bRepaint = TRUE); + DWORD SetAlign(BYTE byAlign, BOOL bRepaint = TRUE); + DWORD SetPressedStyle(BYTE byStyle, BOOL bRepaint = TRUE); + + DWORD DrawBorder(BOOL bDrawBorder = TRUE, BOOL bRepaint = TRUE); + DWORD DrawFlatFocus(BOOL bDrawFlatFocus, BOOL bRepaint = TRUE); + + DWORD SetIcon(HICON hIconIn, HICON hIconOut = NULL); + DWORD SetIcon(LPCTSTR hIconIn, LPCTSTR hIconOut = NULL); + + DWORD SetBitmaps(int nBitmapIn, COLORREF crTransColorIn, int nBitmapOut = NULL, COLORREF crTransColorOut = 0); + DWORD SetBitmaps(HBITMAP hBitmapIn, COLORREF crTransColorIn, HBITMAP hBitmapOut = NULL, COLORREF crTransColorOut = 0); + + void SizeToContent(); + +#ifdef BTNST_USE_BCMENU + DWORD SetMenu(UINT nMenu, HWND hParentWnd, BOOL bWinXPStyle = TRUE, UINT nToolbarID = NULL, CSize sizeToolbarIcon = CSize(16, 16), COLORREF crToolbarBk = RGB(255, 0, 255), BOOL bRepaint = TRUE); +#else + DWORD SetMenu(UINT nMenu, HWND hParentWnd, BOOL bRepaint = TRUE); +#endif + DWORD SetMenuCallback(HWND hWnd, UINT nMessage, LPARAM lParam = 0); + +#ifdef BTNST_USE_SOUND + DWORD SetSound(LPCTSTR lpszSound, HMODULE hMod = NULL, BOOL bPlayOnClick = FALSE, BOOL bPlayAsync = TRUE); +#endif + + static short GetVersionI() {return 37;} + static LPCTSTR GetVersionC() {return (LPCTSTR)_T("3.7");} + + BOOL m_bShowDisabledBitmap; + POINT m_ptImageOrg; + POINT m_ptPressedOffset; + +protected: + //{{AFX_MSG(CButtonST) + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + afx_msg void OnKillFocus(CWnd* pNewWnd); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnSysColorChange(); + afx_msg BOOL OnClicked(); + afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); + afx_msg void OnEnable(BOOL bEnable); + afx_msg void OnCancelMode(); + afx_msg UINT OnGetDlgCode(); + //}}AFX_MSG + +#ifdef BTNST_USE_BCMENU + afx_msg LRESULT OnMenuChar(UINT nChar, UINT nFlags, CMenu* pMenu); + afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct); +#endif + + afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor); + HICON CreateGrayscaleIcon(HICON hIcon); + virtual DWORD OnDrawBackground(CDC* pDC, LPCRECT pRect); + virtual DWORD OnDrawBorder(CDC* pDC, LPCRECT pRect); + + BOOL m_bIsFlat; // Is a flat button? + BOOL m_bMouseOnButton; // Is mouse over the button? + BOOL m_bDrawTransparent; // Draw transparent? + BOOL m_bIsPressed; // Is button pressed? + BOOL m_bIsFocused; // Is button focused? + BOOL m_bIsDisabled; // Is button disabled? + BOOL m_bIsDefault; // Is default button? + BOOL m_bIsCheckBox; // Is the button a checkbox? + BYTE m_byAlign; // Align mode + BOOL m_bDrawBorder; // Draw border? + BOOL m_bDrawFlatFocus; // Draw focus rectangle for flat button? + COLORREF m_crColors[BTNST_MAX_COLORS]; // Colors to be used + HWND m_hParentWndMenu; // Handle to window for menu selection + BOOL m_bMenuDisplayed; // Is menu displayed ? + +#ifdef BTNST_USE_BCMENU + BCMenu m_menuPopup; // BCMenu class instance +#else + HMENU m_hMenu; // Handle to associated menu +#endif + +private: + LRESULT OnSetCheck(WPARAM wParam, LPARAM lParam); + LRESULT OnGetCheck(WPARAM wParam, LPARAM lParam); + LRESULT OnSetStyle(WPARAM wParam, LPARAM lParam); + LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam); + + void FreeResources(BOOL bCheckForNULL = TRUE); + void PrepareImageRect(BOOL bHasTitle, RECT* rpItem, CRect* rpTitle, BOOL bIsPressed, DWORD dwWidth, DWORD dwHeight, CRect* rpImage); + HBITMAP CreateBitmapMask(HBITMAP hSourceBitmap, DWORD dwWidth, DWORD dwHeight, COLORREF crTransColor); + virtual void DrawTheIcon(CDC* pDC, BOOL bHasTitle, RECT* rpItem, CRect* rpCaption, BOOL bIsPressed, BOOL bIsDisabled); + virtual void DrawTheBitmap(CDC* pDC, BOOL bHasTitle, RECT* rpItem, CRect* rpCaption, BOOL bIsPressed, BOOL bIsDisabled); + virtual void DrawTheText(CDC* pDC, LPCTSTR lpszText, RECT* rpItem, CRect* rpCaption, BOOL bIsPressed, BOOL bIsDisabled); + void PaintBk(CDC* pDC); + + void InitToolTip(); + + HCURSOR m_hCursor; // Handle to cursor + CToolTipCtrl m_ToolTip; // Tooltip + + CDC m_dcBk; + CBitmap m_bmpBk; + CBitmap* m_pbmpOldBk; + + void CancelHover(); + BOOL m_bAlwaysTrack; // Always hilight button? + int m_nCheck; // Current value for checkbox + bool leftAligned; + BOOL marked; + UINT m_nTypeStyle; // Button style + + TCHAR m_szURL[_MAX_PATH]; // URL to open when clicked + +#pragma pack(1) + typedef struct _STRUCT_ICONS + { + HICON hIcon; // Handle to icon + DWORD dwWidth; // Width of icon + DWORD dwHeight; // Height of icon + } STRUCT_ICONS; +#pragma pack() + +#pragma pack(1) + typedef struct _STRUCT_BITMAPS + { + HBITMAP hBitmap; // Handle to bitmap + DWORD dwWidth; // Width of bitmap + DWORD dwHeight; // Height of bitmap + HBITMAP hMask; // Handle to mask bitmap + COLORREF crTransparent; // Transparent color + } STRUCT_BITMAPS; +#pragma pack() + +#pragma pack(1) + typedef struct _STRUCT_CALLBACK + { + HWND hWnd; // Handle to window + UINT nMessage; // Message identifier + WPARAM wParam; + LPARAM lParam; + } STRUCT_CALLBACK; +#pragma pack() + + STRUCT_ICONS m_csIcons[2]; + STRUCT_BITMAPS m_csBitmaps[2]; + + STRUCT_CALLBACK m_csCallbacks; + +#ifdef BTNST_USE_SOUND +#pragma pack(1) + typedef struct _STRUCT_SOUND + { + TCHAR szSound[_MAX_PATH]; + LPCTSTR lpszSound; + HMODULE hMod; + DWORD dwFlags; + } STRUCT_SOUND; +#pragma pack() + + STRUCT_SOUND m_csSounds[2]; // Index 0 = Over 1 = Clicked +#endif + + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Developer Studio will insert additional declarations immediately before the previous line. + +#endif diff --git a/BuddyButton.cpp b/BuddyButton.cpp new file mode 100644 index 00000000..5d5b04df --- /dev/null +++ b/BuddyButton.cpp @@ -0,0 +1,123 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +// +// Based on idea from http://www.gipsysoft.com/articles/BuddyButton/ +#include "stdafx.h" +#include "emule.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +static LPCTSTR s_szPropOldWndProc = _T("PropBuddyButtonOldWndProc"); +static LPCTSTR s_szPropBuddyData = _T("PropBuddyButtonData"); + +struct SBuddyData +{ + UINT m_uButtonWidth; + HWND m_hwndButton; +}; + +static LRESULT CALLBACK BuddyButtonSubClassedProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam) +{ + WNDPROC pfnOldWndProc = (WNDPROC)GetProp(hWnd, s_szPropOldWndProc); + ASSERT( pfnOldWndProc != NULL ); + + SBuddyData *pBuddyData = (SBuddyData *)GetProp(hWnd, s_szPropBuddyData); + ASSERT( pBuddyData != NULL ); + + switch (uMessage) + { + case WM_NCDESTROY: + SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pfnOldWndProc); + VERIFY( RemoveProp(hWnd, s_szPropOldWndProc) != NULL ); + VERIFY( RemoveProp(hWnd, s_szPropBuddyData) != NULL ); + delete pBuddyData; + break; + + case WM_NCHITTEST: { + LRESULT lResult = CallWindowProc(pfnOldWndProc, hWnd, uMessage, wParam, lParam); + if (lResult == HTNOWHERE) + lResult = HTTRANSPARENT; + return lResult; + } + + case WM_NCCALCSIZE: { + LRESULT lResult = CallWindowProc(pfnOldWndProc, hWnd, uMessage, wParam, lParam); + LPNCCALCSIZE_PARAMS lpNCCS = (LPNCCALCSIZE_PARAMS)lParam; + lpNCCS->rgrc[0].right -= pBuddyData->m_uButtonWidth; + return lResult; + } + + case WM_SIZE: { + CRect rc; + GetClientRect(hWnd, rc); + rc.left = rc.right; + rc.right = rc.left + pBuddyData->m_uButtonWidth; + MapWindowPoints(hWnd, GetParent(hWnd), (LPPOINT)&rc, 2); + SetWindowPos(pBuddyData->m_hwndButton, NULL, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER); + break; + } + } + return CallWindowProc(pfnOldWndProc, hWnd, uMessage, wParam, lParam); +} + +void AddBuddyButton(HWND hwndEdit, HWND hwndButton) +{ + FARPROC lpfnOldWndProc = (FARPROC)SetWindowLong(hwndEdit, GWL_WNDPROC, (LONG)BuddyButtonSubClassedProc); + ASSERT( lpfnOldWndProc != NULL ); + VERIFY( SetProp(hwndEdit, s_szPropOldWndProc, (HANDLE)lpfnOldWndProc) ); + + // Remove the 'flat' style which my have been set by 'InitWindowStyles' + DWORD dwButtonStyle = (DWORD)GetWindowLong(hwndButton, GWL_STYLE); + if (dwButtonStyle & BS_FLAT) + SetWindowLong(hwndButton, GWL_STYLE, dwButtonStyle & ~BS_FLAT); + + CRect rcButton; + GetWindowRect(hwndButton, rcButton); + + SBuddyData *pBuddyData = new SBuddyData; + pBuddyData->m_uButtonWidth = rcButton.Width(); + pBuddyData->m_hwndButton = hwndButton; + VERIFY( SetProp(hwndEdit, s_szPropBuddyData, (HANDLE)pBuddyData) ); + + SetWindowPos(hwndEdit, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); +} + +bool InitAttachedBrowseButton(HWND hwndButton, HICON &ricoBrowse) +{ + // Showing an icon button works for all Windows versions *except* Windows XP w/ active styles + if (theApp.IsXPThemeActive()) + return false; + // However, do not stress system resources for non-NT systems. + if (afxIsWin95()) + return false; + + if (ricoBrowse == NULL) + { + ricoBrowse = theApp.LoadIcon(_T("BrowseFolderSmall")); + if (ricoBrowse == NULL) + return false; + } + + DWORD dwStyle = (DWORD)GetWindowLong(hwndButton, GWL_STYLE); + SetWindowLong(hwndButton, GWL_STYLE, dwStyle | BS_ICON); + SendMessage(hwndButton, BM_SETIMAGE, IMAGE_ICON, (LPARAM)ricoBrowse); + return true; +} diff --git a/ButtonsTabCtrl.cpp b/ButtonsTabCtrl.cpp new file mode 100644 index 00000000..0a06a3f1 --- /dev/null +++ b/ButtonsTabCtrl.cpp @@ -0,0 +1,205 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ButtonsTabCtrl.h" +#include "OtherFunctions.h" +#include "MenuCmds.h" +#include "UserMsgs.h" +#include "VisualStylesXP.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// _WIN32_WINNT >= 0x0501 (XP only) +#define _WM_THEMECHANGED 0x031A +#define _ON_WM_THEMECHANGED() \ + { _WM_THEMECHANGED, 0, 0, 0, AfxSig_l, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(void) > (_OnThemeChanged)) \ + }, + +/////////////////////////////////////////////////////////////////////////////// +// CButtonsTabCtrl + +IMPLEMENT_DYNAMIC(CButtonsTabCtrl, CTabCtrl) + +BEGIN_MESSAGE_MAP(CButtonsTabCtrl, CTabCtrl) + ON_WM_CREATE() + _ON_WM_THEMECHANGED() +END_MESSAGE_MAP() + +CButtonsTabCtrl::CButtonsTabCtrl() +{ +} + +CButtonsTabCtrl::~CButtonsTabCtrl() +{ +} + +void CButtonsTabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDIS) +{ + CRect rect(lpDIS->rcItem); + int nTabIndex = lpDIS->itemID; + if (nTabIndex < 0) + return; + + TCHAR szLabel[256]; + TC_ITEM tci; + tci.mask = TCIF_TEXT; + tci.pszText = szLabel; + tci.cchTextMax = _countof(szLabel); + if (!GetItem(nTabIndex, &tci)) + return; + szLabel[_countof(szLabel) - 1] = _T('\0'); + + CDC* pDC = CDC::FromHandle(lpDIS->hDC); + if (!pDC) + return; + + CRect rcFullItem(lpDIS->rcItem); + bool bSelected = (lpDIS->itemState & ODS_SELECTED) != 0; + + /////////////////////////////////////////////////////////////////////////////////////// + // Adding support for XP Styles (Vista Themes) for owner drawn tab controls simply + // does *not* work under Vista. Maybe it works under XP (did not try), but that is + // meaningless because under XP a owner drawn tab control is already rendered *with* + // the proper XP Styles. So, for XP there is no need to care about the theme API at all. + // + // However, under Vista, a tab control which has the TCS_OWNERDRAWFIXED + // style gets additional 3D-borders which are applied by Vista *after* WM_DRAWITEM + // was processed. Thus, there is no known workaround available to prevent Vista from + // adding those old fashioned 3D-borders. We can render the tab control items within + // the WM_DRAWITEM handler in whatever style we want, but Vista will in each case + // overwrite the borders of each tab control item with old fashioned 3D-borders... + // + // To complete this experience, tab controls also do not support NMCUSTOMDRAW. So, the + // only known way to customize a tab control is by using TCS_OWNERDRAWFIXED which does + // however not work properly under Vista. + // + // The "solution" which is currently implemented to prevent Vista from drawing those + // 3D-borders is by using "ExcludeClipRect" to reduce the drawing area which is used + // by Windows after WM_DRAWITEM was processed. This "solution" is very sensitive to + // the used rectangles and offsets in general. Incrementing/Decrementing one of the + // "rcItem", "rcFullItem", etc. rectangles makes the entire "solution" flawed again + // because some borders would become visible again. + // + HTHEME hTheme = NULL; + int iPartId = BP_PUSHBUTTON; + int iStateId = PBS_NORMAL; + bool bVistaHotTracked = false; + bool bVistaThemeActive = theApp.IsVistaThemeActive(); + if (bVistaThemeActive) + { + // To determine if the current item is in 'hot tracking' mode, we need to evaluate + // the current foreground color - there is no flag which would indicate this state + // more safely. This applies only for Vista and for tab controls which have the + // TCS_OWNERDRAWFIXED style. + bVistaHotTracked = pDC->GetTextColor() == GetSysColor(COLOR_HOTLIGHT); + + hTheme = g_xpStyle.OpenThemeData(m_hWnd, L"BUTTON"); + if (hTheme) + { + rcFullItem.InflateRect(2, 2); // get the real tab item rect + + if (bSelected) + iStateId = PBS_PRESSED; + else + iStateId = bVistaHotTracked ? PBS_HOT : PBS_NORMAL; + + // Not very smart, but this is needed (in addition to the DrawThemeBackground and the clipping) + // to fix a minor glitch in both of the bottom side corners. + CRect rcTopBorder(rcFullItem.left, rcFullItem.top, rcFullItem.right, rcFullItem.top + 2); + pDC->FillSolidRect(&rcTopBorder, GetSysColor(COLOR_BTNFACE)); + + if (g_xpStyle.IsThemeBackgroundPartiallyTransparent(hTheme, iPartId, iStateId)) + g_xpStyle.DrawThemeParentBackground(m_hWnd, *pDC, &rcFullItem); + g_xpStyle.DrawThemeBackground(hTheme, *pDC, iPartId, iStateId, &rcFullItem, NULL); + } + } + + // Following background clearing is needed for: + // WinXP/Vista (when used without an application theme) + // Vista (when used with an application theme but without a theme for the tab control) + if ( (!g_xpStyle.IsThemeActive() || !g_xpStyle.IsAppThemed()) + || (hTheme == NULL && bVistaThemeActive) ) + pDC->FillSolidRect(&lpDIS->rcItem, GetSysColor(COLOR_BTNFACE)); + + int iOldBkMode = pDC->SetBkMode(TRANSPARENT); + + COLORREF crOldColor = CLR_NONE; + if (bVistaHotTracked) + crOldColor = pDC->SetTextColor(GetSysColor(COLOR_BTNTEXT)); + + rect.top += 2; + // Vista: Tab control has troubles with determining the width of a tab if the + // label contains one '&' character. To get around this, we use the old code which + // replaces one '&' character with two '&' characters and we do not specify DT_NOPREFIX + // here when drawing the text. + // + // Vista: "DrawThemeText" can not be used in case we need a certain foreground color. Thus we always us + // "DrawText" to always get the same font and metrics (just for safety). + pDC->DrawText(szLabel, rect, DT_SINGLELINE | DT_TOP | DT_CENTER /*| DT_NOPREFIX*/); + + if (crOldColor != CLR_NONE) + pDC->SetTextColor(crOldColor); + pDC->SetBkMode(iOldBkMode); + + if (hTheme) + { + pDC->ExcludeClipRect(&rcFullItem); + g_xpStyle.CloseThemeData(hTheme); + } +} + +void CButtonsTabCtrl::PreSubclassWindow() +{ + CTabCtrl::PreSubclassWindow(); + InternalInit(); +} + +int CButtonsTabCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CTabCtrl::OnCreate(lpCreateStruct) == -1) + return -1; + InternalInit(); + return 0; +} + +void CButtonsTabCtrl::InternalInit() +{ + if (theApp.IsVistaThemeActive()) { + ModifyStyle(0, TCS_OWNERDRAWFIXED); + ModifyStyle(0, TCS_HOTTRACK); + } +} + +LRESULT CButtonsTabCtrl::_OnThemeChanged() +{ + // Owner drawn tab control seems to have troubles with updating itself due to an XP theme change.. + bool bIsOwnerDrawn = (GetStyle() & TCS_OWNERDRAWFIXED) != 0; + if (bIsOwnerDrawn) + ModifyStyle(TCS_OWNERDRAWFIXED, 0); // Reset control style to not-owner drawn + Default(); // Process original WM_THEMECHANGED message + if (bIsOwnerDrawn) + ModifyStyle(0, TCS_OWNERDRAWFIXED); // Apply owner drawn style again + return 0; +} diff --git a/ButtonsTabCtrl.h b/ButtonsTabCtrl.h new file mode 100644 index 00000000..8421b022 --- /dev/null +++ b/ButtonsTabCtrl.h @@ -0,0 +1,20 @@ +#pragma once + +class CButtonsTabCtrl : public CTabCtrl +{ + DECLARE_DYNAMIC(CButtonsTabCtrl) + +public: + CButtonsTabCtrl(); + virtual ~CButtonsTabCtrl(); + +protected: + void InternalInit(); + + virtual void PreSubclassWindow(); + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + + DECLARE_MESSAGE_MAP() + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg LRESULT _OnThemeChanged(); +}; diff --git a/CBase64Coding.cpp b/CBase64Coding.cpp new file mode 100644 index 00000000..bd73873a --- /dev/null +++ b/CBase64Coding.cpp @@ -0,0 +1,287 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "CBase64Coding.hpp" +#pragma hdrstop + +#define CARRIAGE_RETURN (13) +#define LINE_FEED (10) + +/* +** Author: Samuel R. Blackburn +** Internet: wfc@pobox.com +** +** You can use it any way you like as long as you don't try to sell it. +** +** Any attempt to sell WFC in source code form must have the permission +** of the original author. You can produce commercial executables with +** WFC but you can't sell WFC. +** +** Copyright, 2000, Samuel R. Blackburn +** +** $Workfile: CBase64Coding.cpp $ +** $Revision: 14 $ +** $Modtime: 5/12/00 3:39p $ +** $Reuse Tracing Code: 1 $ +*/ + +//Modified for use with CAsyncProxySocket, removed tracing code + +#if defined( _DEBUG ) && ! defined( WFC_STL ) +#undef THIS_FILE +static char THIS_FILE[]=__FILE__; +#define new DEBUG_NEW +#endif + +#define END_OF_BASE64_ENCODED_DATA ('=') +#define BASE64_END_OF_BUFFER (0xFD) +#define BASE64_IGNORABLE_CHARACTER (0xFE) +#define BASE64_UNKNOWN_VALUE (0xFF) +#define BASE64_NUMBER_OF_CHARACTERS_PER_LINE (72) + +static inline BYTE __get_character( const BYTE * buffer, const BYTE * decoder_table, int& index, int size_of_buffer ) +{ + BYTE return_value = 0; + + do + { + if ( index >= size_of_buffer ) + { + return( BASE64_END_OF_BUFFER ); + } + + return_value = buffer[ index ]; + index++; + } + while( return_value != END_OF_BASE64_ENCODED_DATA && + decoder_table[ return_value ] == BASE64_IGNORABLE_CHARACTER ); + + return( return_value ); +} + +CBase64Coding::CBase64Coding() +{ +} + +CBase64Coding::~CBase64Coding() +{ +} + +BOOL CBase64Coding::Encode( const char * source, int len, char * destination_string ) +{ + + const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + int loop_index = 0; + int number_of_bytes_to_encode = len; + + BYTE byte_to_add = 0; + BYTE byte_1 = 0; + BYTE byte_2 = 0; + BYTE byte_3 = 0; + + DWORD number_of_bytes_encoded = (DWORD) ( (double) number_of_bytes_to_encode / (double) 0.75 ) + 1; + + // Now add in the CR/LF pairs, each line is truncated at 72 characters + + // 2000-05-12 + // Thanks go to Ilia Golubev (ilia@varicom.co.il) for finding a bug here. + // I was using number_of_bytes_to_encode rather than number_of_bytes_encoded. + + number_of_bytes_encoded += (DWORD)( ( ( number_of_bytes_encoded / BASE64_NUMBER_OF_CHARACTERS_PER_LINE ) + 1 ) * 2 ); + + char * destination = destination_string; + + number_of_bytes_encoded = 0; + + while( loop_index < number_of_bytes_to_encode ) + { + // Output the first byte + + byte_1 = source[ loop_index ]; + byte_to_add = alphabet[ ( byte_1 >> 2 ) ]; + + destination[ number_of_bytes_encoded ] = static_cast< char >( byte_to_add ); + number_of_bytes_encoded++; + + loop_index++; + + if ( loop_index >= number_of_bytes_to_encode ) + { + // We're at the end of the data to encode + + byte_2 = 0; + byte_to_add = alphabet[ ( ( ( byte_1 & 0x03 ) << 4 ) | ( ( byte_2 & 0xF0 ) >> 4 ) ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + destination[ number_of_bytes_encoded ] = END_OF_BASE64_ENCODED_DATA; + number_of_bytes_encoded++; + + destination[ number_of_bytes_encoded ] = END_OF_BASE64_ENCODED_DATA; + + // 1999-09-01 + // Thanks go to Yurong Lin (ylin@dial.pipex.com) for finding a bug here. + // We must NULL terminate the string before letting CString have the buffer back. + + destination[ number_of_bytes_encoded + 1 ] = 0; + + return( TRUE ); + } + else + { + byte_2 = source[ loop_index ]; + } + + byte_to_add = alphabet[ ( ( ( byte_1 & 0x03 ) << 4 ) | ( ( byte_2 & 0xF0 ) >> 4 ) ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + loop_index++; + + if ( loop_index >= number_of_bytes_to_encode ) + { + // We ran out of bytes, we need to add the last half of byte_2 and pad + byte_3 = 0; + + byte_to_add = alphabet[ ( ( ( byte_2 & 0x0F ) << 2 ) | ( ( byte_3 & 0xC0 ) >> 6 ) ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + destination[ number_of_bytes_encoded ] = END_OF_BASE64_ENCODED_DATA; + + // 1999-09-01 + // Thanks go to Yurong Lin (ylin@dial.pipex.com) for finding a bug here. + // We must NULL terminate the string before letting CString have the buffer back. + + destination[ number_of_bytes_encoded + 1 ] = 0; + + return( TRUE ); + } + else + { + byte_3 = source[ loop_index ]; + } + + loop_index++; + + byte_to_add = alphabet[ ( ( ( byte_2 & 0x0F ) << 2 ) | ( ( byte_3 & 0xC0 ) >> 6 ) ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + byte_to_add = alphabet[ ( byte_3 & 0x3F ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + if ( ( number_of_bytes_encoded % BASE64_NUMBER_OF_CHARACTERS_PER_LINE ) == 0 ) + { + destination[ number_of_bytes_encoded ] = CARRIAGE_RETURN; + number_of_bytes_encoded++; + + destination[ number_of_bytes_encoded ] = LINE_FEED; + number_of_bytes_encoded++; + } + } + + destination[ number_of_bytes_encoded ] = END_OF_BASE64_ENCODED_DATA; + + // 1999-09-01 + // Thanks go to Yurong Lin (ylin@dial.pipex.com) for finding a bug here. + // We must NULL terminate the string before letting CString have the buffer back. + + destination[ number_of_bytes_encoded + 1 ] = 0; + + return( TRUE ); +} + +// End of source + +#if 0 + + + +WFC - CBase64Coding + + + + + + +

CBase64Coding

+ +$Revision: 14 $

+ +

Description

+ +This class gives you the ability to encode/decode data using base64. + +

Constructors

+ +
+ +
CBase64Coding()
+Constructs this object. + +
+ +

Methods

+ +
+ +
BOOL Decode( const CByteArray& source, CByteArray& destination )
+BOOL Decode( const CString&    source, CByteArray& destination )
+This method takes base64 encoded text and produces the bytes. It decodes +the base64 encoding. + +
BOOL Encode( const CByteArray& source, CByteArray& destination )
+BOOL Encode( const CByteArray& source, CString&    destination )
+This method takes bytes and turns them into base64 text. + +
+ +

Example

+
#include <wfc.h>
+
+int _tmain( int number_of_command_line_arguments, LPCTSTR command_line_arguments[] )
+{
+   WFCTRACEINIT( TEXT( "_tmain()" ) );
+
+   CByteArray bytes;
+
+   get_file_contents( command_line_arguments[ 0 ], bytes );
+
+   CBase64Coding encoder;
+
+   CString encoded_data;
+
+   if ( encoder.Encode( bytes, encoded_data ) != FALSE )
+   {
+      _tprintf( TEXT( "%s\n", (LPCTSTR) encoded_data );
+   }
+}
+
Copyright, 2000, Samuel R. Blackburn
+$Workfile: CBase64Coding.cpp $
+$Modtime: 5/12/00 3:39p $ + + + +#endif diff --git a/CBase64Coding.hpp b/CBase64Coding.hpp new file mode 100644 index 00000000..4c4d2ce6 --- /dev/null +++ b/CBase64Coding.hpp @@ -0,0 +1,50 @@ +#if ! defined( BASE_64_CODING_CLASS_HEADER ) + +#pragma once + +/* +** Author: Samuel R. Blackburn +** Internet: wfc@pobox.com +** +** You can use it any way you like as long as you don't try to sell it. +** +** Any attempt to sell WFC in source code form must have the permission +** of the original author. You can produce commercial executables with +** WFC but you can't sell WFC. +** +** Copyright, 2000, Samuel R. Blackburn +** +** $Workfile: CBase64Coding.hpp $ +** $Revision: 3 $ +** $Modtime: 1/04/00 4:39a $ +*/ + +#define BASE_64_CODING_CLASS_HEADER + +class CBase64Coding +{ + private: + + // Don't allow canonical behavior (i.e. don't allow this class + // to be passed by value) + + CBase64Coding( const CBase64Coding& ) {}; + CBase64Coding& operator=( const CBase64Coding& ) { return( *this ); }; + + public: + + // Construction + + CBase64Coding(); + + /* + ** Destructor should be virtual according to MSJ article in Sept 1992 + ** "Do More with Less Code:..." + */ + + virtual ~CBase64Coding(); + + virtual BOOL Encode( const char * source, int len, char * destination ); +}; + +#endif // BASE_64_CODING_CLASS_HEADER diff --git a/CaptchaGenerator.cpp b/CaptchaGenerator.cpp new file mode 100644 index 00000000..09cb06cc --- /dev/null +++ b/CaptchaGenerator.cpp @@ -0,0 +1,104 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#include "StdAfx.h" +#include "CaptchaGenerator.h" +#include "CxImage/xImage.h" +#include "OtherFunctions.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define LETTERSIZE 32 +#define CROWDEDSIZE 18 + +// fairly simply captcha generator, might be improved is spammers think its really worth it solving captchas on eMule + +static const char schCaptchaContent[34] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L' +, 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + +CCaptchaGenerator::CCaptchaGenerator(uint32 nLetterCount) +{ + m_pimgCaptcha = NULL; + ReGenerateCaptcha(nLetterCount); +} + +CCaptchaGenerator::~CCaptchaGenerator(void) +{ + Clear(); +} + +void CCaptchaGenerator::ReGenerateCaptcha(uint32 nLetterCount) +{ + Clear(); + CxImage* pimgResult = new CxImage(nLetterCount > 1 ? (LETTERSIZE + (nLetterCount-1)*CROWDEDSIZE) : LETTERSIZE, 32, 1, CXIMAGE_FORMAT_BMP); + pimgResult->SetPaletteColor(0, 255, 255, 255); + pimgResult->SetPaletteColor(1, 0, 0, 0, 0); + pimgResult->Clear(); + CxImage imgBlank(LETTERSIZE, LETTERSIZE, 1, CXIMAGE_FORMAT_BMP); + imgBlank.SetPaletteColor(0, 255, 255, 255); + imgBlank.SetPaletteColor(1, 0, 0, 0, 0); + imgBlank.Clear(); + for (uint32 i = 0; i < nLetterCount; i++) { + CxImage imgLetter(imgBlank); + + CString strLetter(schCaptchaContent[GetRandomUInt16() % ARRSIZE(schCaptchaContent)]); + m_strCaptchaText += strLetter; + + uint16 nRandomSize = GetRandomUInt16() % 10; + uint16 nRandomOffset = 3 + GetRandomUInt16() % 11; + imgLetter.DrawString(NULL, nRandomOffset, 32, strLetter, imgLetter.RGBtoRGBQUAD(RGB(0, 0, 0)), _T("Arial"), 40 - nRandomSize, 1000); + //imgLetter.DrawTextA(NULL, nRandomOffset, 32, strLetter, imgLetter.RGBtoRGBQUAD(RGB(0, 0, 0)), "Arial", 40 - nRandomSize, 1000); + float fRotate = (float)(35 - (GetRandomUInt16() % 70)); + imgLetter.Rotate2(fRotate, NULL, CxImage::IM_BILINEAR, CxImage::OM_BACKGROUND, 0, false, true); + uint32 nOffset = i * CROWDEDSIZE; + ASSERT( imgLetter.GetHeight() == pimgResult->GetHeight() && pimgResult->GetWidth() >= nOffset + imgLetter.GetWidth() ); + for (uint32 j = 0; j < imgLetter.GetHeight(); j++) + for (uint32 k = 0; k < imgLetter.GetWidth(); k++) + if (pimgResult->GetPixelIndex(nOffset + k, j) != 1) + pimgResult->SetPixelIndex(nOffset + k, j, imgLetter.GetPixelIndex(k, j)); + } + pimgResult->Jitter(1); + //pimgResult->Save("D:\\CaptchaTest.bmp", CXIMAGE_FORMAT_BMP); + m_pimgCaptcha = pimgResult; +} + +void CCaptchaGenerator::Clear(){ + delete m_pimgCaptcha; + m_pimgCaptcha = NULL; + m_strCaptchaText = _T(""); +} + +bool CCaptchaGenerator::WriteCaptchaImage(CFileDataIO& file) +{ + if (m_pimgCaptcha == NULL) + return false; + BYTE* pbyBuffer = NULL; + long ulSize = 0; + if (m_pimgCaptcha->Encode(pbyBuffer, ulSize, CXIMAGE_FORMAT_BMP)){ + file.Write(pbyBuffer, ulSize); + ASSERT( ulSize > 100 && ulSize < 1000 ); + free(pbyBuffer); + return true; + } + else + return false; +} \ No newline at end of file diff --git a/CaptchaGenerator.h b/CaptchaGenerator.h new file mode 100644 index 00000000..10003e2d --- /dev/null +++ b/CaptchaGenerator.h @@ -0,0 +1,38 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "SafeFile.h" + +class CxImage; + +class CCaptchaGenerator +{ +public: + CCaptchaGenerator(uint32 nLetterCount = 4); + ~CCaptchaGenerator(void); + + void ReGenerateCaptcha(uint32 nLetterCount = 4); + void Clear(); + CString GetCaptchaText() const {return m_strCaptchaText;} + bool WriteCaptchaImage(CFileDataIO& file); + + +private: + CxImage* m_pimgCaptcha; + CString m_strCaptchaText; +}; diff --git a/CatDialog.cpp b/CatDialog.cpp new file mode 100644 index 00000000..8efed901 --- /dev/null +++ b/CatDialog.cpp @@ -0,0 +1,219 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "CustomAutoComplete.h" +#include "Preferences.h" +#include "otherfunctions.h" +#include "SharedFileList.h" +#include "emuledlg.h" +#include "TransferDlg.h" +#include "CatDialog.h" +#include "UserMsgs.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define REGULAREXPRESSIONS_STRINGS_PROFILE _T("AC_VF_RegExpr.dat") + +// CCatDialog dialog + +IMPLEMENT_DYNAMIC(CCatDialog, CDialog) + +BEGIN_MESSAGE_MAP(CCatDialog, CDialog) + ON_BN_CLICKED(IDC_BROWSE, OnBnClickedBrowse) + ON_BN_CLICKED(IDOK, OnBnClickedOk) + ON_BN_CLICKED(IDC_REB, OnDDBnClicked) + ON_MESSAGE(UM_CPN_SELENDOK, OnSelChange) //UM_CPN_SELCHANGE +END_MESSAGE_MAP() + +CCatDialog::CCatDialog(int index) + : CDialog(CCatDialog::IDD) +{ + m_myCat = thePrefs.GetCategory(index); + if (m_myCat == NULL) + return; + m_pacRegExp=NULL; + newcolor = (DWORD)-1; +} + +CCatDialog::~CCatDialog() +{ + if (m_pacRegExp){ + m_pacRegExp->SaveList(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + REGULAREXPRESSIONS_STRINGS_PROFILE); + m_pacRegExp->Unbind(); + m_pacRegExp->Release(); + } +} + +BOOL CCatDialog::OnInitDialog() +{ + CDialog::OnInitDialog(); + InitWindowStyles(this); + Localize(); + m_ctlColor.SetDefaultColor(GetSysColor(COLOR_BTNTEXT)); + UpdateData(); + + if (!thePrefs.IsExtControlsEnabled()) { + GetDlgItem(IDC_REGEXPR)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_STATIC_REGEXP)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_REGEXP)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_REB)->ShowWindow(SW_HIDE); + } + + m_pacRegExp = new CCustomAutoComplete(); + m_pacRegExp->AddRef(); + if (m_pacRegExp->Bind(::GetDlgItem(m_hWnd, IDC_REGEXP), ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST)) { + m_pacRegExp->LoadList(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + REGULAREXPRESSIONS_STRINGS_PROFILE); + } + if (theApp.m_fontSymbol.m_hObject){ + GetDlgItem(IDC_REB)->SetFont(&theApp.m_fontSymbol); + GetDlgItem(IDC_REB)->SetWindowText(_T("6")); // show a down-arrow + } + + return TRUE; +} + +void CCatDialog::UpdateData() +{ + GetDlgItem(IDC_TITLE)->SetWindowText(m_myCat->strTitle); + GetDlgItem(IDC_INCOMING)->SetWindowText(m_myCat->strIncomingPath); + GetDlgItem(IDC_COMMENT)->SetWindowText(m_myCat->strComment); + + if (m_myCat->filter==18) + SetDlgItemText(IDC_REGEXP,m_myCat->regexp); + + CheckDlgButton(IDC_REGEXPR,m_myCat->ac_regexpeval); + + newcolor = m_myCat->color; + m_ctlColor.SetColor(m_myCat->color == -1 ? m_ctlColor.GetDefaultColor() : m_myCat->color); + + GetDlgItem(IDC_AUTOCATEXT)->SetWindowText(m_myCat->autocat); + + m_prio.SetCurSel(m_myCat->prio); +} + +void CCatDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_CATCOLOR, m_ctlColor); + DDX_Control(pDX, IDC_PRIOCOMBO, m_prio); +} + +void CCatDialog::Localize() +{ + GetDlgItem(IDC_STATIC_TITLE)->SetWindowText(GetResString(IDS_TITLE)); + GetDlgItem(IDC_STATIC_INCOMING)->SetWindowText(GetResString(IDS_PW_INCOMING) + _T(" ") + GetResString(IDS_SHAREWARNING) ); + GetDlgItem(IDC_STATIC_COMMENT)->SetWindowText(GetResString(IDS_COMMENT)); + GetDlgItem(IDCANCEL)->SetWindowText(GetResString(IDS_CANCEL)); + GetDlgItem(IDC_STATIC_COLOR)->SetWindowText(GetResString(IDS_COLOR)); + GetDlgItem(IDC_STATIC_PRIO)->SetWindowText(GetResString(IDS_STARTPRIO)); + GetDlgItem(IDC_STATIC_AUTOCAT)->SetWindowText(GetResString(IDS_AUTOCAT_LABEL)); + GetDlgItem(IDC_REGEXPR)->SetWindowText(GetResString(IDS_ASREGEXPR)); + GetDlgItem(IDOK)->SetWindowText(GetResString(IDS_TREEOPTIONS_OK)); + + m_ctlColor.CustomText = GetResString(IDS_COL_MORECOLORS); + m_ctlColor.DefaultText = GetResString(IDS_DEFAULT); + + SetWindowText(GetResString(IDS_EDITCAT)); + + SetDlgItemText(IDC_STATIC_REGEXP,GetResString(IDS_STATIC_REGEXP)); + + m_prio.ResetContent(); + m_prio.AddString(GetResString(IDS_PRIOLOW)); + m_prio.AddString(GetResString(IDS_PRIONORMAL)); + m_prio.AddString(GetResString(IDS_PRIOHIGH)); + m_prio.SetCurSel(m_myCat->prio); +} + +void CCatDialog::OnBnClickedBrowse() +{ + TCHAR buffer[MAX_PATH] = {0}; + GetDlgItemText(IDC_INCOMING, buffer, _countof(buffer)); + if (SelectDir(GetSafeHwnd(), buffer,GetResString(IDS_SELECT_INCOMINGDIR))) + GetDlgItem(IDC_INCOMING)->SetWindowText(buffer); +} + +void CCatDialog::OnBnClickedOk() +{ + CString oldpath = m_myCat->strIncomingPath; + if (GetDlgItem(IDC_TITLE)->GetWindowTextLength()>0) + GetDlgItem(IDC_TITLE)->GetWindowText(m_myCat->strTitle); + + if (GetDlgItem(IDC_INCOMING)->GetWindowTextLength()>2) + GetDlgItem(IDC_INCOMING)->GetWindowText(m_myCat->strIncomingPath); + + GetDlgItem(IDC_COMMENT)->GetWindowText(m_myCat->strComment); + + m_myCat->ac_regexpeval= IsDlgButtonChecked(IDC_REGEXPR)>0; + + MakeFoldername(m_myCat->strIncomingPath); + if (!thePrefs.IsShareableDirectory(m_myCat->strIncomingPath)){ + m_myCat->strIncomingPath = thePrefs.GetMuleDirectory(EMULE_INCOMINGDIR); + MakeFoldername(m_myCat->strIncomingPath); + } + + if (!PathFileExists(m_myCat->strIncomingPath)){ + if (!::CreateDirectory(m_myCat->strIncomingPath, 0)){ + AfxMessageBox(GetResString(IDS_ERR_BADFOLDER)); + m_myCat->strIncomingPath = oldpath; + return; + } + } + + if (m_myCat->strIncomingPath.CompareNoCase(oldpath)!=0) + theApp.sharedfiles->Reload(); + + m_myCat->color=newcolor; + m_myCat->prio=m_prio.GetCurSel(); + GetDlgItem(IDC_AUTOCATEXT)->GetWindowText(m_myCat->autocat); + + GetDlgItemText(IDC_REGEXP,m_myCat->regexp); + if (m_myCat->regexp.GetLength()>0) { + if (m_pacRegExp && m_pacRegExp->IsBound()){ + m_pacRegExp->AddItem(m_myCat->regexp,0); + m_myCat->filter=18; + } + } else if (m_myCat->filter==18) { + // deactivate regexp + m_myCat->filter=0; + } + + theApp.emuledlg->transferwnd->GetDownloadList()->Invalidate(); + + OnOK(); +} + +LONG CCatDialog::OnSelChange(UINT lParam, LONG /*wParam*/) +{ + if (lParam == CLR_DEFAULT) + newcolor = (DWORD)-1; + else + newcolor = m_ctlColor.GetColor(); + return TRUE; +} + +void CCatDialog::OnDDBnClicked() +{ + CWnd* box = GetDlgItem(IDC_REGEXP); + box->SetFocus(); + box->SetWindowText(_T("")); + box->SendMessage(WM_KEYDOWN, VK_DOWN, 0x00510001); +} diff --git a/CatDialog.h b/CatDialog.h new file mode 100644 index 00000000..1998a6d0 --- /dev/null +++ b/CatDialog.h @@ -0,0 +1,50 @@ +//this file is part of eMule +// added by quekky +// +//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. +#pragma once +#include "ColorButton.h" + +struct Category_Struct; +class CCustomAutoComplete; + +class CCatDialog : public CDialog +{ + DECLARE_DYNAMIC(CCatDialog) +public: + CCatDialog(int catindex); // standard constructor + virtual ~CCatDialog(); + + enum { IDD = IDD_CAT }; + +protected: + Category_Struct* m_myCat; + DWORD newcolor; + CColorButton m_ctlColor; + CComboBox m_prio; + CCustomAutoComplete* m_pacRegExp; + + void Localize(); + void UpdateData(); + + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + + DECLARE_MESSAGE_MAP() + afx_msg LONG OnSelChange(UINT lParam, LONG wParam); + afx_msg void OnBnClickedBrowse(); + afx_msg void OnBnClickedOk(); + afx_msg void OnDDBnClicked(); +}; diff --git a/ChatSelector.cpp b/ChatSelector.cpp new file mode 100644 index 00000000..3938e2fd --- /dev/null +++ b/ChatSelector.cpp @@ -0,0 +1,678 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ChatSelector.h" +#include "packets.h" +#include "HTRichEditCtrl.h" +#include "emuledlg.h" +#include "Statistics.h" +#include "OtherFunctions.h" +#include "UpDownClient.h" +#include "Preferences.h" +#include "TaskbarNotifier.h" +#include "ListenSocket.h" +#include "ChatWnd.h" +#include "SafeFile.h" +#include "Log.h" +#include "MenuCmds.h" +#include "ClientDetailDialog.h" +#include "FriendList.h" +#include "ClientList.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define STATUS_MSG_COLOR RGB(0,128,0) // dark green +#define SENT_TARGET_MSG_COLOR RGB(0,192,0) // bright green +#define RECV_SOURCE_MSG_COLOR RGB(0,128,255) // bright cyan/blue + +#define TIME_STAMP_FORMAT _T("[%H:%M] ") + +/////////////////////////////////////////////////////////////////////////////// +// CChatItem + +CChatItem::CChatItem() +{ + client = NULL; + log = NULL; + notify = false; + history_pos = 0; +} + +CChatItem::~CChatItem() +{ + delete log; +} + +/////////////////////////////////////////////////////////////////////////////// +// CChatSelector + +IMPLEMENT_DYNAMIC(CChatSelector, CClosableTabCtrl) + +BEGIN_MESSAGE_MAP(CChatSelector, CClosableTabCtrl) + ON_WM_SIZE() + ON_WM_DESTROY() + ON_WM_TIMER() + ON_WM_SYSCOLORCHANGE() + ON_NOTIFY_REFLECT(TCN_SELCHANGE, OnTcnSelChangeChatSel) + ON_WM_CONTEXTMENU() +END_MESSAGE_MAP() + +CChatSelector::CChatSelector() +{ + m_lastemptyicon = false; + m_blinkstate = false; + m_Timer = 0; + m_bCloseable = true; +} + +CChatSelector::~CChatSelector() +{ +} + +void CChatSelector::Init(CChatWnd *pParent) +{ + m_pParent = pParent; + + ModifyStyle(0, WS_CLIPCHILDREN); + SetAllIcons(); + + VERIFY( (m_Timer = SetTimer(20, 1500, 0)) != NULL ); +} + +void CChatSelector::OnSysColorChange() +{ + CClosableTabCtrl::OnSysColorChange(); + SetAllIcons(); +} + +void CChatSelector::SetAllIcons() +{ + CImageList iml; + iml.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + iml.Add(CTempIconLoader(_T("Chat"))); + iml.Add(CTempIconLoader(_T("Message"))); + iml.Add(CTempIconLoader(_T("MessagePending"))); + SetImageList(&iml); + m_imlChat.DeleteImageList(); + m_imlChat.Attach(iml.Detach()); + SetPadding(CSize(12, 3)); +} + +void CChatSelector::UpdateFonts(CFont* pFont) +{ + TCITEM item; + item.mask = TCIF_PARAM; + int i = 0; + while (GetItem(i++, &item)){ + CChatItem* ci = (CChatItem*)item.lParam; + ci->log->SetFont(pFont); + } +} + +CChatItem* CChatSelector::StartSession(CUpDownClient* client, bool show) +{ + if (show) + m_pParent->m_wndMessage.SetFocus(); + if (GetTabByClient(client) != -1){ + if (show){ + SetCurSel(GetTabByClient(client)); + ShowChat(); + } + return NULL; + } + + CChatItem* chatitem = new CChatItem(); + chatitem->client = client; + chatitem->log = new CHTRichEditCtrl; + + CRect rcChat; + GetChatSize(rcChat); + if (GetItemCount() == 0) + rcChat.top += 19; // add the height of the tab which is not yet there + // using ES_NOHIDESEL is actually not needed, but it helps to get around a tricky window update problem! + // If that style is not specified there are troubles with right clicking into the control for the very first time!? + chatitem->log->Create(WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VSCROLL | ES_MULTILINE | ES_READONLY | ES_NOHIDESEL, rcChat, this, (UINT)-1); + chatitem->log->ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_FRAMECHANGED); + chatitem->log->SendMessage(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(3, 3)); + chatitem->log->SetEventMask(chatitem->log->GetEventMask() | ENM_LINK); + chatitem->log->SetFont(&theApp.m_fontHyperText); + chatitem->log->SetProfileSkinKey(_T("Chat")); + chatitem->log->ApplySkin(); + chatitem->log->EnableSmileys(thePrefs.GetMessageEnableSmileys()); + + PARAFORMAT pf = {0}; + pf.cbSize = sizeof pf; + pf.dwMask = PFM_OFFSET; + pf.dxOffset = 150; + chatitem->log->SetParaFormat(pf); + + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(chatitem); + chatitem->log->AppendKeyWord(GetResString(IDS_CHAT_START) + client->GetUserName() + _T("\n"), STATUS_MSG_COLOR); + client->SetChatState(MS_CHATTING); + + CString name; + if (client->GetUserName() != NULL) + name = client->GetUserName(); + else + name.Format(_T("(%s)"), GetResString(IDS_UNKNOWN)); + chatitem->log->SetTitle(name); + + TCITEM newitem; + newitem.mask = TCIF_PARAM | TCIF_TEXT | TCIF_IMAGE; + newitem.lParam = (LPARAM)chatitem; + name.Replace(_T("&"), _T("&&")); + newitem.pszText = const_cast((LPCTSTR)name); + newitem.iImage = 0; + int iItemNr = InsertItem(GetItemCount(), &newitem); + if (show || IsWindowVisible()){ + SetCurSel(iItemNr); + ShowChat(); + } + return chatitem; +} + +int CChatSelector::GetTabByClient(CUpDownClient* client) +{ + for (int i = 0; i < GetItemCount(); i++){ + TCITEM cur_item; + cur_item.mask = TCIF_PARAM; + if (GetItem(i, &cur_item) && ((CChatItem*)cur_item.lParam)->client == client) + return i; + } + return -1; +} + +CChatItem* CChatSelector::GetItemByIndex(int index) +{ + TCITEM item; + item.mask = TCIF_PARAM; + if (GetItem(index, &item)==FALSE) + return NULL; + + return (CChatItem*)item.lParam; +} + +CChatItem* CChatSelector::GetItemByClient(CUpDownClient* client) +{ + for (int i = 0; i < GetItemCount(); i++){ + TCITEM cur_item; + cur_item.mask = TCIF_PARAM; + if (GetItem(i, &cur_item) && ((CChatItem*)cur_item.lParam)->client == client) + return (CChatItem*)cur_item.lParam; + } + return NULL; +} + +void CChatSelector::ProcessMessage(CUpDownClient* sender, const CString& message) +{ + sender->IncMessagesReceived(); + CChatItem* ci = GetItemByClient(sender); + + AddLogLine(true, GetResString(IDS_NEWMSG), sender->GetUserName(), ipstr(sender->GetConnectIP())); + + bool isNewChatWindow = false; + if (!ci) + { + if ((UINT)GetItemCount() >= thePrefs.GetMsgSessionsMax()) + return; + ci = StartSession(sender, false); + isNewChatWindow = true; + } + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(ci); + ci->log->AppendKeyWord(sender->GetUserName(), RECV_SOURCE_MSG_COLOR); + ci->log->AppendText(_T(": ")); + ci->log->AppendText(message + _T("\n")); + int iTabItem = GetTabByClient(sender); + if (GetCurSel() == iTabItem && GetParent()->IsWindowVisible()) + { + // chat window is already visible + ; + } + else if (GetCurSel() != iTabItem) + { + // chat window is already visible, but tab is not selected + ci->notify = true; + } + else + { + ci->notify = true; + if (isNewChatWindow || thePrefs.GetNotifierOnEveryChatMsg()) + theApp.emuledlg->ShowNotifier(GetResString(IDS_TBN_NEWCHATMSG) + _T(" ") + CString(sender->GetUserName()) + _T(":'") + message + _T("'\n"), TBN_CHAT); + isNewChatWindow = false; + } +} + +void CChatSelector::ShowCaptchaRequest(CUpDownClient* sender, HBITMAP bmpCaptcha) +{ + CChatItem* ci = GetItemByClient(sender); + if (ci != NULL) + { + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(ci); + ci->log->AppendKeyWord(_T("*** ") + GetResString(IDS_CAPTCHAREQUEST), STATUS_MSG_COLOR); + ci->log->AddCaptcha(bmpCaptcha); + ci->log->AddLine(_T("\n")); + } +} + +void CChatSelector::ShowCaptchaResult(CUpDownClient* sender, CString strResult) +{ + CChatItem* ci = GetItemByClient(sender); + if (ci != NULL) + { + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(ci); + ci->log->AppendKeyWord(_T("*** ") + strResult + _T("\n"), STATUS_MSG_COLOR); + } +} + +bool CChatSelector::SendMessage(const CString& rstrMessage) +{ + CChatItem* ci = GetCurrentChatItem(); + if (!ci) + return false; + + if ((UINT)ci->history.GetCount() == thePrefs.GetMaxChatHistoryLines()) + ci->history.RemoveAt(0); + ci->history.Add(rstrMessage); + ci->history_pos = ci->history.GetCount(); + + // advance spamfilter stuff + ci->client->IncMessagesSent(); + ci->client->SetSpammer(false); + if (ci->client->GetChatState() == MS_CONNECTING) + return false; + + if (ci->client->GetChatCaptchaState() == CA_CAPTCHARECV) + ci->client->SetChatCaptchaState(CA_SOLUTIONSENT); + else if (ci->client->GetChatCaptchaState() == CA_SOLUTIONSENT) + ASSERT( false ); // we responsed to a captcha but didn't heard from the client afterwards - hopefully its just lag and this message will get through + else + ci->client->SetChatCaptchaState(CA_ACCEPTING); + + + + + // there are three cases on connectiing/sending the message: + if (ci->client->socket && ci->client->socket->IsConnected()) + { + // 1.) the client is connected already - this is simple, jsut send it + ci->client->SendChatMessage(rstrMessage); + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(ci); + ci->log->AppendKeyWord(thePrefs.GetUserNick(), SENT_TARGET_MSG_COLOR); + ci->log->AppendText(_T(": ")); + ci->log->AppendText(rstrMessage + _T("\n")); + } + else if (ci->client->GetFriend() != NULL) + { + // We are not connected and this client is a friend - friends have additional ways to connect and additional checks + // to make sure they are really friends, let the friend class is handling it + ci->strMessagePending = rstrMessage; + ci->client->SetChatState(MS_CONNECTING); + ci->client->GetFriend()->TryToConnect(this); + } + else + { + // this is a normal client, who is not connected right now. just try to connect to the given IP, without any + // additional checks or searchings. + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(ci); + ci->log->AppendKeyWord(_T("*** ") + GetResString(IDS_CONNECTING), STATUS_MSG_COLOR); + ci->strMessagePending = rstrMessage; + ci->client->SetChatState(MS_CONNECTING); + ci->client->TryToConnect(true); + } + return true; +} + +void CChatSelector::ConnectingResult(CUpDownClient* sender, bool success) +{ + CChatItem* ci = GetItemByClient(sender); + if (!ci) + return; + + ci->client->SetChatState(MS_CHATTING); + if (!success){ + if (!ci->strMessagePending.IsEmpty()){ + ci->log->AppendKeyWord(_T(" ...") + GetResString(IDS_FAILED) + _T("\n"), STATUS_MSG_COLOR); + ci->strMessagePending.Empty(); + } + else{ + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(ci); + ci->log->AppendKeyWord(GetResString(IDS_CHATDISCONNECTED) + _T("\n"), STATUS_MSG_COLOR); + } + } + else if (!ci->strMessagePending.IsEmpty()){ + ci->log->AppendKeyWord(_T(" ...") + GetResString(IDS_TREEOPTIONS_OK) + _T("\n"), STATUS_MSG_COLOR); + ci->client->SendChatMessage(ci->strMessagePending); + + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(ci); + ci->log->AppendKeyWord(thePrefs.GetUserNick(), SENT_TARGET_MSG_COLOR); + ci->log->AppendText(_T(": ")); + ci->log->AppendText(ci->strMessagePending + _T("\n")); + + ci->strMessagePending.Empty(); + } + else{ + if (thePrefs.GetIRCAddTimeStamp()) + AddTimeStamp(ci); + ci->log->AppendKeyWord(_T("*** Connected\n"), STATUS_MSG_COLOR); + } +} + +void CChatSelector::DeleteAllItems() +{ + for (int i = 0; i < GetItemCount(); i++){ + TCITEM cur_item; + cur_item.mask = TCIF_PARAM; + if (GetItem(i, &cur_item)) + delete (CChatItem*)cur_item.lParam; + } +} + +void CChatSelector::OnTimer(UINT_PTR /*nIDEvent*/) +{ + m_blinkstate = !m_blinkstate; + bool globalnotify = false; + for (int i = 0; i < GetItemCount();i++) + { + TCITEM cur_item; + cur_item.mask = TCIF_PARAM | TCIF_IMAGE; + if (!GetItem(i, &cur_item)) + break; + + cur_item.mask = TCIF_IMAGE; + if (((CChatItem*)cur_item.lParam)->notify){ + cur_item.iImage = (m_blinkstate) ? 1 : 2; + SetItem(i, &cur_item); + HighlightItem(i, TRUE); + globalnotify = true; + } + else if (cur_item.iImage != 0){ + cur_item.iImage = 0; + SetItem(i, &cur_item); + HighlightItem(i, FALSE); + } + } + + if (globalnotify) { + theApp.emuledlg->ShowMessageState(m_blinkstate ? 1 : 2); + m_lastemptyicon = false; + } + else if (!m_lastemptyicon) { + theApp.emuledlg->ShowMessageState(0); + m_lastemptyicon = true; + } +} + +CChatItem* CChatSelector::GetCurrentChatItem() +{ + int iCurSel = GetCurSel(); + if (iCurSel == -1) + return NULL; + + TCITEM cur_item; + cur_item.mask = TCIF_PARAM; + if (!GetItem(iCurSel, &cur_item)) + return NULL; + + return (CChatItem*)cur_item.lParam; +} + +void CChatSelector::ShowChat() +{ + CChatItem* ci = GetCurrentChatItem(); + if (!ci) + return; + + // show current chat window + ci->log->ShowWindow(SW_SHOW); + m_pParent->m_wndMessage.SetFocus(); + + TCITEM item; + item.mask = TCIF_IMAGE; + item.iImage = 0; + SetItem(GetCurSel(), &item); + HighlightItem(GetCurSel(), FALSE); + + // hide all other chat windows + item.mask = TCIF_PARAM; + int i = 0; + while (GetItem(i++, &item)){ + CChatItem* ci2 = (CChatItem*)item.lParam; + if (ci2 != ci) + ci2->log->ShowWindow(SW_HIDE); + } + + ci->notify = false; +} + +void CChatSelector::OnTcnSelChangeChatSel(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + ShowChat(); + *pResult = 0; +} + +int CChatSelector::InsertItem(int nItem, TCITEM* pTabCtrlItem) +{ + int iResult = CClosableTabCtrl::InsertItem(nItem, pTabCtrlItem); + RedrawWindow(); + return iResult; +} + +BOOL CChatSelector::DeleteItem(int nItem) +{ + CClosableTabCtrl::DeleteItem(nItem); + RedrawWindow(); + return TRUE; +} + +void CChatSelector::EndSession(CUpDownClient* client) +{ + int iCurSel; + if (client) + iCurSel = GetTabByClient(client); + else + iCurSel = GetCurSel(); + if (iCurSel == -1) + return; + + TCITEM item; + item.mask = TCIF_PARAM; + if (!GetItem(iCurSel, &item) || item.lParam == 0) + return; + CChatItem* ci = (CChatItem*)item.lParam; + ci->client->SetChatState(MS_NONE); + ci->client->SetChatCaptchaState(CA_NONE); + + DeleteItem(iCurSel); + delete ci; + + int iTabItems = GetItemCount(); + if (iTabItems > 0){ + // select next tab + if (iCurSel == CB_ERR) + iCurSel = 0; + else if (iCurSel >= iTabItems) + iCurSel = iTabItems - 1; + (void)SetCurSel(iCurSel); // returns CB_ERR if error or no prev. selection(!) + iCurSel = GetCurSel(); // get the real current selection + if (iCurSel == CB_ERR) // if still error + iCurSel = SetCurSel(0); + ShowChat(); + } +} + +void CChatSelector::GetChatSize(CRect& rcChat) +{ + CRect rcClient; + GetClientRect(&rcClient); + AdjustRect(FALSE, rcClient); + rcChat.left = rcClient.left + 4; + rcChat.top = rcClient.top + 4; + rcChat.right = rcClient.right - 4; + rcChat.bottom = rcClient.bottom - 4; +} + +void CChatSelector::OnSize(UINT nType, int cx, int cy) +{ + CClosableTabCtrl::OnSize(nType, cx, cy); + + CRect rcChat; + GetChatSize(rcChat); + + TCITEM item; + item.mask = TCIF_PARAM; + int i = 0; + while (GetItem(i++, &item)){ + CChatItem* ci = (CChatItem*)item.lParam; + ci->log->SetWindowPos(NULL, rcChat.left, rcChat.top, rcChat.Width(), rcChat.Height(), SWP_NOZORDER); + } +} + +void CChatSelector::AddTimeStamp(CChatItem* ci) +{ + ci->log->AppendText(CTime::GetCurrentTime().Format(TIME_STAMP_FORMAT)); +} + +void CChatSelector::OnDestroy() +{ + if (m_Timer){ + KillTimer(m_Timer); + m_Timer = NULL; + } + CClosableTabCtrl::OnDestroy(); +} + +BOOL CChatSelector::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (wParam) { + case MP_DETAIL:{ + const CChatItem* ci = GetItemByIndex(m_iContextIndex); + if (ci) { + CClientDetailDialog dialog(ci->client); + dialog.DoModal(); + } + return TRUE; + } + case MP_ADDFRIEND:{ + const CChatItem* ci = GetItemByIndex(m_iContextIndex); + if (ci) { + CFriend* fr = theApp.friendlist->SearchFriend(ci->client->GetUserHash(), 0, 0); + if (!fr) + theApp.friendlist->AddFriend(ci->client); + } + return TRUE; + } + case MP_REMOVEFRIEND:{ + const CChatItem* ci = GetItemByIndex(m_iContextIndex); + if (ci) { + CFriend* fr = theApp.friendlist->SearchFriend(ci->client->GetUserHash(), 0, 0); + if (fr) + theApp.friendlist->RemoveFriend(fr); + } + return TRUE; + } + case MP_REMOVE:{ + const CChatItem* ci = GetItemByIndex(m_iContextIndex); + if (ci) + EndSession(ci->client); + return TRUE; + } + } + return CClosableTabCtrl::OnCommand(wParam, lParam); +} + +void CChatSelector::OnContextMenu(CWnd*, CPoint point) +{ + TCHITTESTINFO hti = {0}; + ::GetCursorPos(&hti.pt); + ScreenToClient(&hti.pt); + + + m_iContextIndex=this->HitTest(&hti); + if (m_iContextIndex==-1) + return; + + TCITEM item; + item.mask = TCIF_PARAM; + GetItem(m_iContextIndex, &item); + + const CChatItem* ci = (CChatItem*)item.lParam; + if (ci == NULL) + return; + + CFriend* pFriend = theApp.friendlist->SearchFriend(ci->client->GetUserHash(), 0, 0); + + CTitleMenu menu; + menu.CreatePopupMenu(); + menu.AddMenuTitle(GetResString(IDS_CLIENT), true); + + menu.AppendMenu(MF_STRING, MP_DETAIL, GetResString(IDS_SHOWDETAILS), _T("CLIENTDETAILS")); + + GetCurrentChatItem(); + if (pFriend == NULL) + menu.AppendMenu(MF_STRING, MP_ADDFRIEND, GetResString(IDS_IRC_ADDTOFRIENDLIST), _T("ADDFRIEND")); + else + menu.AppendMenu(MF_STRING, MP_REMOVEFRIEND, GetResString(IDS_REMOVEFRIEND), _T("DELETEFRIEND")); + + menu.AppendMenu(MF_STRING, MP_REMOVE, GetResString(IDS_FD_CLOSE)); + + m_ptCtxMenu = point; + ScreenToClient(&m_ptCtxMenu); + menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); +} + +void CChatSelector::EnableSmileys(bool bEnable) +{ + for (int i = 0; i < GetItemCount(); i++){ + TCITEM cur_item; + cur_item.mask = TCIF_PARAM; + if (GetItem(i, &cur_item) && ((CChatItem*)cur_item.lParam)->log) + ((CChatItem*)cur_item.lParam)->log->EnableSmileys(bEnable); + } +} + +void CChatSelector::ReportConnectionProgress(CUpDownClient* pClient, CString strProgressDesc, bool bNoTimeStamp) +{ + CChatItem* ci = GetItemByClient(pClient); + if (!ci) + return; + if (thePrefs.GetIRCAddTimeStamp() && !bNoTimeStamp) + AddTimeStamp(ci); + ci->log->AppendKeyWord(strProgressDesc, STATUS_MSG_COLOR); +} + +void CChatSelector::ClientObjectChanged(CUpDownClient* pOldClient, CUpDownClient* pNewClient){ + // the friend has deceided to change the clients objects (because the old doesnt seems to be our friend) during a connectiontry + // in order to not close and reopen a new session and loose the prior chat, switch the objects on a existing tab + // nothing else changes since the tab is supposed to be still connected to the same friend + CChatItem* ci = GetItemByClient(pOldClient); + if (!ci) + return; + ci->client = pNewClient; +} \ No newline at end of file diff --git a/ChatSelector.h b/ChatSelector.h new file mode 100644 index 00000000..b1ab730f --- /dev/null +++ b/ChatSelector.h @@ -0,0 +1,102 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "ClosableTabCtrl.h" +#include "Friend.h" + +class CChatWnd; +class CHTRichEditCtrl; +class CUpDownClient; + + +/////////////////////////////////////////////////////////////////////////////// +// CChatItem + +class CChatItem +{ +public: + CChatItem(); + ~CChatItem(); + + CUpDownClient* client; + CHTRichEditCtrl* log; + CString strMessagePending; + bool notify; + CStringArray history; + int history_pos; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// CChatSelector + +class CChatSelector : public CClosableTabCtrl, CFriendConnectionListener +{ + DECLARE_DYNAMIC(CChatSelector) + +public: + CChatSelector(); + virtual ~CChatSelector(); + + void Init(CChatWnd *pParent); + CChatItem* StartSession(CUpDownClient* client, bool show = true); + void EndSession(CUpDownClient* client = 0); + int GetTabByClient(CUpDownClient* client); + CChatItem* GetItemByClient(CUpDownClient* client); + CChatItem* GetItemByIndex(int index); + void ProcessMessage(CUpDownClient* sender, const CString& message); + void ShowCaptchaRequest(CUpDownClient* sender, HBITMAP bmpCaptcha); + void ShowCaptchaResult(CUpDownClient* sender, CString strResult); + bool SendMessage(const CString& rstrMessage); + void DeleteAllItems(); + void ShowChat(); + void ConnectingResult(CUpDownClient* sender,bool success); + void Send(); + void UpdateFonts(CFont* pFont); + CChatItem* GetCurrentChatItem(); + BOOL RemoveItem(int nItem) { return DeleteItem(nItem); } + void EnableSmileys(bool bEnable); + void ReportConnectionProgress(CUpDownClient* pClient, CString strProgressDesc, bool bNoTimeStamp); + void ClientObjectChanged(CUpDownClient* pOldClient, CUpDownClient* pNewClient); + +protected: + CChatWnd *m_pParent; + UINT_PTR m_Timer; + bool m_blinkstate; + bool m_lastemptyicon; + CImageList m_imlChat; + int m_iContextIndex; + + void AddTimeStamp(CChatItem*); + void SetAllIcons(); + void GetChatSize(CRect& rcChat); + + virtual int InsertItem(int nItem, TCITEM* pTabCtrlItem); + virtual BOOL DeleteItem(int nItem); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + + DECLARE_MESSAGE_MAP() + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnDestroy(); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg void OnTcnSelChangeChatSel(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnBnClickedCsend(); + afx_msg void OnBnClickedCclose(); + afx_msg void OnBnClickedSmiley(); + afx_msg void OnSysColorChange(); + afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); +}; diff --git a/ChatWnd.cpp b/ChatWnd.cpp new file mode 100644 index 00000000..70a8bf16 --- /dev/null +++ b/ChatWnd.cpp @@ -0,0 +1,572 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ChatWnd.h" +#include "HTRichEditCtrl.h" +#include "FriendList.h" +#include "emuledlg.h" +#include "UpDownClient.h" +#include "OtherFunctions.h" +#include "HelpIDs.h" +#include "Opcodes.h" +#include "friend.h" +#include "ClientCredits.h" +#include "IconStatic.h" +#include "UserMsgs.h" +#include "SmileySelector.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define SPLITTER_HORZ_MARGIN 0 +#define SPLITTER_HORZ_WIDTH 4 +#define SPLITTER_HORZ_RANGE_MIN 170 +#define SPLITTER_HORZ_RANGE_MAX 400 + + +// CChatWnd dialog + +IMPLEMENT_DYNAMIC(CChatWnd, CDialog) + +BEGIN_MESSAGE_MAP(CChatWnd, CResizableDialog) + ON_WM_KEYDOWN() + ON_WM_SHOWWINDOW() + ON_MESSAGE(UM_CLOSETAB, OnCloseTab) + ON_WM_SYSCOLORCHANGE() + ON_WM_CTLCOLOR() + ON_WM_CONTEXTMENU() + ON_WM_HELPINFO() + ON_NOTIFY(LVN_ITEMACTIVATE, IDC_FRIENDS_LIST, OnLvnItemActivateFriendList) + ON_NOTIFY(NM_CLICK, IDC_FRIENDS_LIST, OnNmClickFriendList) + ON_STN_DBLCLK(IDC_FRIENDSICON, OnStnDblClickFriendIcon) + ON_BN_CLICKED(IDC_CSEND, OnBnClickedSend) + ON_BN_CLICKED(IDC_CCLOSE, OnBnClickedClose) +END_MESSAGE_MAP() + +CChatWnd::CChatWnd(CWnd* pParent /*=NULL*/) + : CResizableDialog(CChatWnd::IDD, pParent) +{ + icon_friend = NULL; + icon_msg = NULL; + m_pwndSmileySel = NULL; +} + +CChatWnd::~CChatWnd() +{ + if (m_pwndSmileySel != NULL){ + m_pwndSmileySel->DestroyWindow(); + delete m_pwndSmileySel; + } + if (icon_friend) + VERIFY( DestroyIcon(icon_friend) ); + if (icon_msg) + VERIFY( DestroyIcon(icon_msg) ); +} + +void CChatWnd::DoDataExchange(CDataExchange* pDX) +{ + CResizableDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_CHATSEL, chatselector); + DDX_Control(pDX, IDC_FRIENDS_LIST, m_FriendListCtrl); + DDX_Control(pDX, IDC_FRIENDS_MSG, m_cUserInfo); + DDX_Control(pDX, IDC_TEXT_FORMAT, m_wndFormat); + DDX_Control(pDX, IDC_CMESSAGE, m_wndMessage); + DDX_Control(pDX, IDC_CSEND, m_wndSend); + DDX_Control(pDX, IDC_CCLOSE, m_wndClose); +} + +void CChatWnd::OnLvnItemActivateFriendList(NMHDR* /*pNMHDR*/, LRESULT* /*pResult*/) +{ + UpdateSelectedFriendMsgDetails(); +} + +void CChatWnd::UpdateSelectedFriendMsgDetails() +{ + int iSel = m_FriendListCtrl.GetSelectionMark(); + if (iSel != -1) { + CFriend* pFriend = (CFriend*)m_FriendListCtrl.GetItemData(iSel); + ShowFriendMsgDetails(pFriend); + } + else + ShowFriendMsgDetails(NULL); +} + +void CChatWnd::ShowFriendMsgDetails(CFriend* pFriend) +{ + if (pFriend) + { + CString buffer; + + // Name + if (pFriend->GetLinkedClient()) + GetDlgItem(IDC_FRIENDS_NAME_EDIT)->SetWindowText(pFriend->GetLinkedClient()->GetUserName()); + else if (pFriend->m_strName != _T("")) + GetDlgItem(IDC_FRIENDS_NAME_EDIT)->SetWindowText(pFriend->m_strName); + else + GetDlgItem(IDC_FRIENDS_NAME_EDIT)->SetWindowText(_T("?")); + + // Hash + if (pFriend->GetLinkedClient()) + GetDlgItem(IDC_FRIENDS_USERHASH_EDIT)->SetWindowText(md4str(pFriend->GetLinkedClient()->GetUserHash())); + else if (pFriend->HasUserhash()) + GetDlgItem(IDC_FRIENDS_USERHASH_EDIT)->SetWindowText(md4str(pFriend->m_abyUserhash)); + else + GetDlgItem(IDC_FRIENDS_USERHASH_EDIT)->SetWindowText(_T("?")); + + // Client + if (pFriend->GetLinkedClient()) + GetDlgItem(IDC_FRIENDS_CLIENTE_EDIT)->SetWindowText(pFriend->GetLinkedClient()->GetClientSoftVer()); + else + GetDlgItem(IDC_FRIENDS_CLIENTE_EDIT)->SetWindowText(_T("?")); + + // Identification + if (pFriend->GetLinkedClient() && pFriend->GetLinkedClient()->Credits()) + { + if (theApp.clientcredits->CryptoAvailable()) + { + switch (pFriend->GetLinkedClient()->Credits()->GetCurrentIdentState(pFriend->GetLinkedClient()->GetIP())) + { + case IS_NOTAVAILABLE: + GetDlgItem(IDC_FRIENDS_IDENTIFICACION_EDIT)->SetWindowText(GetResString(IDS_IDENTNOSUPPORT)); + break; + case IS_IDFAILED: + case IS_IDNEEDED: + case IS_IDBADGUY: + GetDlgItem(IDC_FRIENDS_IDENTIFICACION_EDIT)->SetWindowText(GetResString(IDS_IDENTFAILED)); + break; + case IS_IDENTIFIED: + GetDlgItem(IDC_FRIENDS_IDENTIFICACION_EDIT)->SetWindowText(GetResString(IDS_IDENTOK)); + break; + } + } + else + GetDlgItem(IDC_FRIENDS_IDENTIFICACION_EDIT)->SetWindowText(GetResString(IDS_IDENTNOSUPPORT)); + } + else + GetDlgItem(IDC_FRIENDS_IDENTIFICACION_EDIT)->SetWindowText(_T("?")); + + // Upload and downloaded + if (pFriend->GetLinkedClient() && pFriend->GetLinkedClient()->Credits()) + GetDlgItem(IDC_FRIENDS_DESCARGADO_EDIT)->SetWindowText(CastItoXBytes(pFriend->GetLinkedClient()->Credits()->GetDownloadedTotal(), false, false)); + else + GetDlgItem(IDC_FRIENDS_DESCARGADO_EDIT)->SetWindowText(_T("?")); + + if (pFriend->GetLinkedClient() && pFriend->GetLinkedClient()->Credits()) + GetDlgItem(IDC_FRIENDS_SUBIDO_EDIT)->SetWindowText(CastItoXBytes(pFriend->GetLinkedClient()->Credits()->GetUploadedTotal(), false, false)); + else + GetDlgItem(IDC_FRIENDS_SUBIDO_EDIT)->SetWindowText(_T("?")); + } + else + { + GetDlgItem(IDC_FRIENDS_NAME_EDIT)->SetWindowText(_T("-")); + GetDlgItem(IDC_FRIENDS_USERHASH_EDIT)->SetWindowText(_T("-")); + GetDlgItem(IDC_FRIENDS_CLIENTE_EDIT)->SetWindowText(_T("-")); + GetDlgItem(IDC_FRIENDS_IDENTIFICACION_EDIT)->SetWindowText(_T("-")); + GetDlgItem(IDC_FRIENDS_DESCARGADO_EDIT)->SetWindowText(_T("-")); + GetDlgItem(IDC_FRIENDS_SUBIDO_EDIT)->SetWindowText(_T("-")); + } +} + +BOOL CChatWnd::OnInitDialog() +{ + CResizableDialog::OnInitDialog(); + InitWindowStyles(this); + SetAllIcons(); + + m_wndMessage.SetLimitText(MAX_CLIENT_MSG_LEN); + if (theApp.m_fontChatEdit.m_hObject) + { + m_wndMessage.SendMessage(WM_SETFONT, (WPARAM)theApp.m_fontChatEdit.m_hObject, FALSE); + CRect rcEdit; + m_wndMessage.GetWindowRect(&rcEdit); + ScreenToClient(&rcEdit); + rcEdit.top -= 2; + rcEdit.bottom += 2; + m_wndMessage.MoveWindow(&rcEdit, FALSE); + } + + chatselector.Init(this); + m_FriendListCtrl.Init(); + + CRect rcSpl; + m_FriendListCtrl.GetWindowRect(rcSpl); + ScreenToClient(rcSpl); + rcSpl.left = rcSpl.right + SPLITTER_HORZ_MARGIN; + rcSpl.right = rcSpl.left + SPLITTER_HORZ_WIDTH; + m_wndSplitterHorz.Create(WS_CHILD | WS_VISIBLE, rcSpl, this, IDC_SPLITTER_FRIEND); + + // Vista: Remove the TBSTYLE_TRANSPARENT to avoid flickering (can be done only after the toolbar was initially created with TBSTYLE_TRANSPARENT !?) + m_wndFormat.ModifyStyle((theApp.m_ullComCtrlVer >= MAKEDLLVERULL(6, 16, 0, 0)) ? TBSTYLE_TRANSPARENT : 0, TBSTYLE_TOOLTIPS); + m_wndFormat.SetExtendedStyle(m_wndFormat.GetExtendedStyle() | TBSTYLE_EX_MIXEDBUTTONS); + TBBUTTON atb[1] = {0}; + atb[0].iBitmap = 0; + atb[0].idCommand = IDC_SMILEY; + atb[0].fsState = TBSTATE_ENABLED; + atb[0].fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE; + atb[0].iString = -1; + m_wndFormat.AddButtons(_countof(atb), atb); + + CSize size; + m_wndFormat.GetMaxSize(&size); + if (size.cx < 24) // avoid glitch with COMCTL32 v5.81 and Win2000 + size.cx = 24; + if (size.cy < 22) // avoid glitch with COMCTL32 v5.81 and Win2000 + size.cy = 22; + ::SetWindowPos(m_wndFormat, NULL, 0, 0, size.cx, size.cy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + + AddAnchor(IDC_FRIENDSICON, TOP_LEFT); + AddAnchor(IDC_FRIENDS_LBL, TOP_LEFT); + AddAnchor(IDC_FRIENDS_NAME, BOTTOM_LEFT); + AddAnchor(IDC_FRIENDS_USERHASH, BOTTOM_LEFT); + AddAnchor(IDC_FRIENDS_CLIENT, BOTTOM_LEFT); + AddAnchor(IDC_FRIENDS_IDENT, BOTTOM_LEFT); + AddAnchor(IDC_FRIENDS_UPLOADED, BOTTOM_LEFT); + AddAnchor(IDC_FRIENDS_DOWNLOADED, BOTTOM_LEFT); + AddAnchor(m_wndSplitterHorz, TOP_LEFT, BOTTOM_LEFT); + AddAnchor(m_wndFormat, BOTTOM_LEFT); + AddAnchor(m_wndMessage, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(m_wndSend, BOTTOM_RIGHT); + AddAnchor(m_wndClose, BOTTOM_RIGHT); + + int iPosStatInit = rcSpl.left; + int iPosStatNew = thePrefs.GetSplitterbarPositionFriend(); + if (iPosStatNew > SPLITTER_HORZ_RANGE_MAX) + iPosStatNew = SPLITTER_HORZ_RANGE_MAX; + else if (iPosStatNew < SPLITTER_HORZ_RANGE_MIN) + iPosStatNew = SPLITTER_HORZ_RANGE_MIN; + rcSpl.left = iPosStatNew; + rcSpl.right = iPosStatNew + SPLITTER_HORZ_WIDTH; + if (iPosStatNew != iPosStatInit) + { + m_wndSplitterHorz.MoveWindow(rcSpl); + DoResize(iPosStatNew - iPosStatInit); + } + + Localize(); + theApp.friendlist->ShowFriends(); + + return TRUE; +} + +void CChatWnd::DoResize(int iDelta) +{ + CSplitterControl::ChangeWidth(&m_FriendListCtrl, iDelta); + m_FriendListCtrl.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); + CSplitterControl::ChangeWidth(&m_cUserInfo, iDelta); + CSplitterControl::ChangeWidth(GetDlgItem(IDC_FRIENDS_NAME_EDIT), iDelta); + CSplitterControl::ChangeWidth(GetDlgItem(IDC_FRIENDS_USERHASH_EDIT), iDelta); + CSplitterControl::ChangeWidth(GetDlgItem(IDC_FRIENDS_CLIENTE_EDIT), iDelta); + CSplitterControl::ChangeWidth(GetDlgItem(IDC_FRIENDS_IDENTIFICACION_EDIT), iDelta); + CSplitterControl::ChangeWidth(GetDlgItem(IDC_FRIENDS_SUBIDO_EDIT), iDelta); + CSplitterControl::ChangeWidth(GetDlgItem(IDC_FRIENDS_DESCARGADO_EDIT), iDelta); + CSplitterControl::ChangeWidth(&chatselector, -iDelta, CW_RIGHTALIGN); + CSplitterControl::ChangePos(GetDlgItem(IDC_MESSAGES_LBL), -iDelta, 0); + CSplitterControl::ChangePos(GetDlgItem(IDC_MESSAGEICON), -iDelta, 0); + CSplitterControl::ChangePos(&m_wndFormat, -iDelta, 0); + CSplitterControl::ChangePos(&m_wndMessage, -iDelta, 0); + CSplitterControl::ChangeWidth(&m_wndMessage, -iDelta); + + CRect rcSpl; + m_wndSplitterHorz.GetWindowRect(rcSpl); + ScreenToClient(rcSpl); + thePrefs.SetSplitterbarPositionFriend(rcSpl.left); + + RemoveAnchor(m_FriendListCtrl); + AddAnchor(m_FriendListCtrl, TOP_LEFT, BOTTOM_LEFT); + RemoveAnchor(m_cUserInfo); + AddAnchor(m_cUserInfo, BOTTOM_LEFT, BOTTOM_LEFT); + RemoveAnchor(chatselector); + AddAnchor(chatselector, TOP_LEFT, BOTTOM_RIGHT); + RemoveAnchor(IDC_MESSAGES_LBL); + AddAnchor(IDC_MESSAGES_LBL, TOP_LEFT); + RemoveAnchor(IDC_MESSAGEICON); + AddAnchor(IDC_MESSAGEICON, TOP_LEFT); + RemoveAnchor(IDC_FRIENDS_NAME_EDIT); + AddAnchor(IDC_FRIENDS_NAME_EDIT, BOTTOM_LEFT); + RemoveAnchor(IDC_FRIENDS_USERHASH_EDIT); + AddAnchor(IDC_FRIENDS_USERHASH_EDIT, BOTTOM_LEFT); + RemoveAnchor(IDC_FRIENDS_CLIENTE_EDIT); + AddAnchor(IDC_FRIENDS_CLIENTE_EDIT, BOTTOM_LEFT); + RemoveAnchor(IDC_FRIENDS_IDENTIFICACION_EDIT); + AddAnchor(IDC_FRIENDS_IDENTIFICACION_EDIT, BOTTOM_LEFT); + RemoveAnchor(IDC_FRIENDS_SUBIDO_EDIT); + AddAnchor(IDC_FRIENDS_SUBIDO_EDIT, BOTTOM_LEFT); + RemoveAnchor(IDC_FRIENDS_DESCARGADO_EDIT); + AddAnchor(IDC_FRIENDS_DESCARGADO_EDIT, BOTTOM_LEFT); + RemoveAnchor(m_wndSplitterHorz); + AddAnchor(m_wndSplitterHorz, TOP_LEFT, BOTTOM_LEFT); + RemoveAnchor(m_wndFormat); + AddAnchor(m_wndFormat, BOTTOM_LEFT); + RemoveAnchor(m_wndMessage); + AddAnchor(m_wndMessage, BOTTOM_LEFT, BOTTOM_RIGHT); + RemoveAnchor(m_wndSend); + AddAnchor(m_wndSend, BOTTOM_RIGHT); + RemoveAnchor(m_wndClose); + AddAnchor(m_wndClose, BOTTOM_RIGHT); + + CRect rcWnd; + GetWindowRect(rcWnd); + ScreenToClient(rcWnd); + m_wndSplitterHorz.SetRange(rcWnd.left + SPLITTER_HORZ_RANGE_MIN + SPLITTER_HORZ_WIDTH/2, + rcWnd.left + SPLITTER_HORZ_RANGE_MAX - SPLITTER_HORZ_WIDTH/2); + + Invalidate(); + UpdateWindow(); +} + +LRESULT CChatWnd::DefWindowProc(UINT uMessage, WPARAM wParam, LPARAM lParam) +{ + switch (uMessage) + { + case WM_PAINT: + if (m_wndSplitterHorz) + { + CRect rcWnd; + GetWindowRect(rcWnd); + if (rcWnd.Width() > 0) + { + CRect rcSpl; + m_FriendListCtrl.GetWindowRect(rcSpl); + ScreenToClient(rcSpl); + rcSpl.left = rcSpl.right + SPLITTER_HORZ_MARGIN; + rcSpl.right = rcSpl.left + SPLITTER_HORZ_WIDTH; + ScreenToClient(rcWnd); + rcSpl.bottom = rcWnd.bottom - 6; + m_wndSplitterHorz.MoveWindow(rcSpl, TRUE); + } + } + break; + + case WM_NOTIFY: + if (wParam == IDC_SPLITTER_FRIEND) + { + SPC_NMHDR* pHdr = (SPC_NMHDR*)lParam; + DoResize(pHdr->delta); + } + break; + + case WM_SIZE: + if (m_wndSplitterHorz) + { + CRect rcWnd; + GetWindowRect(rcWnd); + ScreenToClient(rcWnd); + m_wndSplitterHorz.SetRange(rcWnd.left + SPLITTER_HORZ_RANGE_MIN + SPLITTER_HORZ_WIDTH/2, + rcWnd.left + SPLITTER_HORZ_RANGE_MAX - SPLITTER_HORZ_WIDTH/2); + } + break; + } + return CResizableDialog::DefWindowProc(uMessage, wParam, lParam); +} + +void CChatWnd::StartSession(CUpDownClient* client) +{ + if (!client->GetUserName()) + return; + theApp.emuledlg->SetActiveDialog(this); + chatselector.StartSession(client, true); +} + +void CChatWnd::OnShowWindow(BOOL bShow, UINT /*nStatus*/) +{ + if (bShow) + chatselector.ShowChat(); +} + +BOOL CChatWnd::PreTranslateMessage(MSG* pMsg) +{ + if (pMsg->message == WM_KEYDOWN) + { + // Don't handle Ctrl+Tab in this window. It will be handled by main window. + if (pMsg->wParam == VK_TAB && GetAsyncKeyState(VK_CONTROL) < 0) + return FALSE; + + if (pMsg->wParam == VK_RETURN){ + if (pMsg->hwnd == m_wndMessage) + OnBnClickedSend(); + } + + if (pMsg->hwnd == m_wndMessage && (pMsg->wParam == VK_UP || pMsg->wParam == VK_DOWN)){ + ScrollHistory(pMsg->wParam == VK_DOWN); + return TRUE; + } + } + else if (pMsg->message == WM_KEYUP) + { + if (pMsg->hwnd == m_FriendListCtrl.m_hWnd) + OnLvnItemActivateFriendList(0, 0); + } + + return CResizableDialog::PreTranslateMessage(pMsg); +} + +void CChatWnd::OnNmClickFriendList(NMHDR *pNMHDR, LRESULT *pResult) +{ + OnLvnItemActivateFriendList(pNMHDR, pResult); + *pResult = 0; +} + +void CChatWnd::SetAllIcons() +{ + if (icon_friend) + VERIFY( DestroyIcon(icon_friend) ); + if (icon_msg) + VERIFY( DestroyIcon(icon_msg) ); + icon_friend = theApp.LoadIcon(_T("Friend"), 16, 16); + icon_msg = theApp.LoadIcon(_T("Message"), 16, 16); + ((CStatic*)GetDlgItem(IDC_MESSAGEICON))->SetIcon(icon_msg); + ((CStatic*)GetDlgItem(IDC_FRIENDSICON))->SetIcon(icon_friend); + m_cUserInfo.SetIcon(_T("Info")); + + CImageList iml; + iml.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + iml.Add(CTempIconLoader(_T("Smiley_Smile"))); + CImageList *pimlOld = m_wndFormat.SetImageList(&iml); + iml.Detach(); + if (pimlOld) + pimlOld->DeleteImageList(); +} + +void CChatWnd::Localize() +{ + GetDlgItem(IDC_FRIENDS_LBL)->SetWindowText(GetResString(IDS_CW_FRIENDS)); + GetDlgItem(IDC_MESSAGES_LBL)->SetWindowText(GetResString(IDS_CW_MESSAGES)); + m_cUserInfo.SetWindowText(GetResString(IDS_INFO)); + GetDlgItem(IDC_FRIENDS_DOWNLOADED)->SetWindowText(GetResString(IDS_CHAT_DOWNLOADED)); + GetDlgItem(IDC_FRIENDS_UPLOADED)->SetWindowText(GetResString(IDS_CHAT_UPLOADED)); + GetDlgItem(IDC_FRIENDS_IDENT)->SetWindowText(GetResString(IDS_CHAT_IDENT)); + GetDlgItem(IDC_FRIENDS_CLIENT)->SetWindowText(GetResString(IDS_CD_CSOFT)); + GetDlgItem(IDC_FRIENDS_NAME)->SetWindowText(GetResString(IDS_CD_UNAME)); + GetDlgItem(IDC_FRIENDS_USERHASH)->SetWindowText(GetResString(IDS_CD_UHASH)); + m_wndSend.SetWindowText(GetResString(IDS_CW_SEND)); + m_wndClose.SetWindowText(GetResString(IDS_CW_CLOSE)); + m_wndFormat.SetBtnText(IDC_SMILEY, _T("Smileys")); + m_FriendListCtrl.Localize(); +} + +LRESULT CChatWnd::OnCloseTab(WPARAM wParam, LPARAM /*lParam*/) +{ + TCITEM item = {0}; + item.mask = TCIF_PARAM; + if (chatselector.GetItem((int)wParam, &item)) + chatselector.EndSession(((CChatItem*)item.lParam)->client); + return TRUE; +} + +void CChatWnd::ScrollHistory(bool down) +{ + CChatItem* ci = chatselector.GetCurrentChatItem(); + if (ci == NULL) + return; + + if ((ci->history_pos == 0 && !down) || (ci->history_pos == ci->history.GetCount() && down)) + return; + if (down) + ++ci->history_pos; + else + --ci->history_pos; + + CString strBuffer = (ci->history_pos == ci->history.GetCount()) ? _T("") : ci->history.GetAt(ci->history_pos); + m_wndMessage.SetWindowText(strBuffer); + m_wndMessage.SetSel(strBuffer.GetLength(), strBuffer.GetLength()); +} + +void CChatWnd::OnSysColorChange() +{ + CResizableDialog::OnSysColorChange(); + SetAllIcons(); +} + +void CChatWnd::UpdateFriendlistCount(UINT count) +{ + CString strTemp; + strTemp.Format(_T(" (%i)"), count); + GetDlgItem(IDC_FRIENDS_LBL)->SetWindowText(GetResString(IDS_CW_FRIENDS) + strTemp); +} + +BOOL CChatWnd::OnHelpInfo(HELPINFO* /*pHelpInfo*/) +{ + theApp.ShowHelp(eMule_FAQ_GUI_Messages); + return TRUE; +} + +void CChatWnd::OnStnDblClickFriendIcon() +{ + theApp.emuledlg->ShowPreferences(IDD_PPG_MESSAGES); +} + +BOOL CChatWnd::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (wParam) { + case IDC_SMILEY: + OnBnClickedSmiley(); + break; + } + return CResizableDialog::OnCommand(wParam, lParam); +} + +void CChatWnd::OnBnClickedSmiley() +{ + if (m_pwndSmileySel) { + m_pwndSmileySel->DestroyWindow(); + delete m_pwndSmileySel; + m_pwndSmileySel = NULL; + } + m_pwndSmileySel = new CSmileySelector; + + CRect rcBtn; + m_wndFormat.GetWindowRect(&rcBtn); + rcBtn.top -= 2; + + if (!m_pwndSmileySel->Create(this, &rcBtn, &m_wndMessage)) + { + delete m_pwndSmileySel; + m_pwndSmileySel = NULL; + } +} + +void CChatWnd::OnBnClickedClose() +{ + chatselector.EndSession(); +} + +void CChatWnd::OnBnClickedSend() +{ + CString strMessage; + m_wndMessage.GetWindowText(strMessage); + strMessage.Trim(); + if (!strMessage.IsEmpty()) + { + if (chatselector.SendMessage(strMessage)) + m_wndMessage.SetWindowText(_T("")); + } + + m_wndMessage.SetFocus(); +} + +HBRUSH CChatWnd::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) +{ + HBRUSH hbr = theApp.emuledlg->GetCtlColor(pDC, pWnd, nCtlColor); + if (hbr) + return hbr; + return __super::OnCtlColor(pDC, pWnd, nCtlColor); +} diff --git a/ChatWnd.h b/ChatWnd.h new file mode 100644 index 00000000..0d443d75 --- /dev/null +++ b/ChatWnd.h @@ -0,0 +1,82 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "ResizableLib\ResizableDialog.h" +#include "ChatSelector.h" +#include "FriendListCtrl.h" +#include "SplitterControl.h" +#include "IconStatic.h" +#include "ToolBarCtrlX.h" + +class CSmileySelector; + +class CChatWnd : public CResizableDialog +{ + DECLARE_DYNAMIC(CChatWnd) + +public: + CChatWnd(CWnd* pParent = NULL); // standard constructor + virtual ~CChatWnd(); + +// Dialog Data + enum { IDD = IDD_CHAT }; + + void StartSession(CUpDownClient* client); + void Localize(); + void UpdateFriendlistCount(UINT count); + void UpdateSelectedFriendMsgDetails(); + void ScrollHistory(bool down); + void EnableSmileys(bool bEnable) { chatselector.EnableSmileys(bEnable); } + + CFriendListCtrl m_FriendListCtrl; + CChatSelector chatselector; + +protected: + friend class CChatSelector; + HICON icon_friend; + HICON icon_msg; + CSplitterControl m_wndSplitterHorz; + CIconStatic m_cUserInfo; + CToolBarCtrlX m_wndFormat; + CEdit m_wndMessage; + CButton m_wndSend; + CButton m_wndClose; + CSmileySelector *m_pwndSmileySel; + + void SetAllIcons(); + void DoResize(int delta); + void ShowFriendMsgDetails(CFriend* pFriend); + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + virtual LRESULT DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam); + + DECLARE_MESSAGE_MAP() + afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo); + afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); + afx_msg LRESULT OnCloseTab(WPARAM wparam, LPARAM lparam); + afx_msg void OnBnClickedClose(); + afx_msg void OnBnClickedSend(); + afx_msg void OnBnClickedSmiley(); + afx_msg void OnLvnItemActivateFriendList(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmClickFriendList(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnShowWindow(BOOL bShow, UINT nStatus); + afx_msg void OnStnDblClickFriendIcon(); + afx_msg void OnSysColorChange(); +}; diff --git a/ClientCredits.cpp b/ClientCredits.cpp new file mode 100644 index 00000000..4c75b0f8 --- /dev/null +++ b/ClientCredits.cpp @@ -0,0 +1,701 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include +#include "emule.h" +#include "ClientCredits.h" +#include "OtherFunctions.h" +#include "Preferences.h" +#include "SafeFile.h" +#include "Opcodes.h" +#include "Sockets.h" +#pragma warning(disable:4516) // access-declarations are deprecated; member using-declarations provide a better alternative +#pragma warning(disable:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4702) // unreachable code +#include +#include +#include +#include +#pragma warning(default:4702) // unreachable code +#pragma warning(default:4100) // unreferenced formal parameter +#pragma warning(default:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(default:4516) // access-declarations are deprecated; member using-declarations provide a better alternative +#include "emuledlg.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define CLIENTS_MET_FILENAME _T("clients.met") + +CClientCredits::CClientCredits(CreditStruct* in_credits) +{ + m_pCredits = in_credits; + InitalizeIdent(); + m_dwUnSecureWaitTime = 0; + m_dwSecureWaitTime = 0; + m_dwWaitTimeIP = 0; +} + +CClientCredits::CClientCredits(const uchar* key) +{ + m_pCredits = new CreditStruct; + memset(m_pCredits, 0, sizeof(CreditStruct)); + md4cpy(m_pCredits->abyKey, key); + InitalizeIdent(); + m_dwUnSecureWaitTime = ::GetTickCount(); + m_dwSecureWaitTime = ::GetTickCount(); + m_dwWaitTimeIP = 0; +} + +CClientCredits::~CClientCredits() +{ + delete m_pCredits; +} + +void CClientCredits::AddDownloaded(uint32 bytes, uint32 dwForIP) { + if ((GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable()) { + return; + } + + //encode + uint64 current = (((uint64)m_pCredits->nDownloadedHi << 32) | m_pCredits->nDownloadedLo) + bytes; + + //recode + m_pCredits->nDownloadedLo = (uint32)current; + m_pCredits->nDownloadedHi = (uint32)(current >> 32); +} + +void CClientCredits::AddUploaded(uint32 bytes, uint32 dwForIP) { + if ((GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable()) { + return; + } + + //encode + uint64 current = (((uint64)m_pCredits->nUploadedHi << 32) | m_pCredits->nUploadedLo) + bytes; + + //recode + m_pCredits->nUploadedLo = (uint32)current; + m_pCredits->nUploadedHi = (uint32)(current >> 32); +} + +uint64 CClientCredits::GetUploadedTotal() const { + return ((uint64)m_pCredits->nUploadedHi << 32) | m_pCredits->nUploadedLo; +} + +uint64 CClientCredits::GetDownloadedTotal() const { + return ((uint64)m_pCredits->nDownloadedHi << 32) | m_pCredits->nDownloadedLo; +} + +float CClientCredits::GetScoreRatio(uint32 dwForIP) const +{ + // check the client ident status + if ( ( GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable() ){ + // bad guy - no credits for you + return 1.0F; + } + + if (GetDownloadedTotal() < 1048576) + return 1.0F; + float result = 0.0F; + if (!GetUploadedTotal()) + result = 10.0F; + else + result = (float)(((double)GetDownloadedTotal()*2.0)/(double)GetUploadedTotal()); + + // exponential calcualtion of the max multiplicator based on uploaded data (9.2MB = 3.34, 100MB = 10.0) + float result2 = 0.0F; + result2 = (float)(GetDownloadedTotal()/1048576.0); + result2 += 2.0F; + result2 = (float)sqrt(result2); + + // linear calcualtion of the max multiplicator based on uploaded data for the first chunk (1MB = 1.01, 9.2MB = 3.34) + float result3 = 10.0F; + if (GetDownloadedTotal() < 9646899){ + result3 = (((float)(GetDownloadedTotal() - 1048576) / 8598323.0F) * 2.34F) + 1.0F; + } + + // take the smallest result + result = min(result, min(result2, result3)); + + if (result < 1.0F) + return 1.0F; + else if (result > 10.0F) + return 10.0F; + return result; +} + + +CClientCreditsList::CClientCreditsList() +{ + m_nLastSaved = ::GetTickCount(); + LoadList(); + + InitalizeCrypting(); +} + +CClientCreditsList::~CClientCreditsList() +{ + SaveList(); + CClientCredits* cur_credit; + CCKey tmpkey(0); + POSITION pos = m_mapClients.GetStartPosition(); + while (pos){ + m_mapClients.GetNextAssoc(pos, tmpkey, cur_credit); + delete cur_credit; + } + delete m_pSignkey; +} + +void CClientCreditsList::LoadList() +{ + CString strFileName = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + CLIENTS_MET_FILENAME; + const int iOpenFlags = CFile::modeRead|CFile::osSequentialScan|CFile::typeBinary|CFile::shareDenyWrite; + CSafeBufferedFile file; + CFileException fexp; + if (!file.Open(strFileName, iOpenFlags, &fexp)){ + if (fexp.m_cause != CFileException::fileNotFound){ + CString strError(GetResString(IDS_ERR_LOADCREDITFILE)); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + } + return; + } + setvbuf(file.m_pStream, NULL, _IOFBF, 16384); + + try{ + uint8 version = file.ReadUInt8(); + if (version != CREDITFILE_VERSION && version != CREDITFILE_VERSION_29){ + LogWarning(GetResString(IDS_ERR_CREDITFILEOLD)); + file.Close(); + return; + } + + // everything is ok, lets see if the backup exist... + CString strBakFileName; + strBakFileName.Format(_T("%s") CLIENTS_MET_FILENAME _T(".bak"), thePrefs.GetMuleDirectory(EMULE_CONFIGDIR)); + + 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 exist, get the size + dwBakFileSize = ::GetFileSize(hBakFile, NULL); //debug + if (dwBakFileSize > (DWORD)file.GetLength()) + { + // the size of the backup was larger then the org. file, something is wrong here, don't overwrite old backup.. + bCreateBackup = FALSE; + } + //else: backup is smaller or the same size as org. 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(GetResString(IDS_ERR_MAKEBAKCREDITFILE)); + + // reopen file + CFileException fexp; + if (!file.Open(strFileName, iOpenFlags, &fexp)){ + CString strError(GetResString(IDS_ERR_LOADCREDITFILE)); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + return; + } + setvbuf(file.m_pStream, NULL, _IOFBF, 16384); + file.Seek(1, CFile::begin); //set filepointer behind file version byte + } + + UINT count = file.ReadUInt32(); + m_mapClients.InitHashTable(count+5000); // TODO: should be prime number... and 20% larger + + const uint32 dwExpired = time(NULL) - 12960000; // today - 150 day + uint32 cDeleted = 0; + for (UINT i = 0; i < count; i++){ + CreditStruct* newcstruct = new CreditStruct; + memset(newcstruct, 0, sizeof(CreditStruct)); + if (version == CREDITFILE_VERSION_29) + file.Read(newcstruct, sizeof(CreditStruct_29a)); + else + file.Read(newcstruct, sizeof(CreditStruct)); + + if (newcstruct->nLastSeen < dwExpired){ + cDeleted++; + delete newcstruct; + continue; + } + + CClientCredits* newcredits = new CClientCredits(newcstruct); + m_mapClients.SetAt(CCKey(newcredits->GetKey()), newcredits); + } + file.Close(); + + if (cDeleted>0) + AddLogLine(false, GetResString(IDS_CREDITFILELOADED) + GetResString(IDS_CREDITSEXPIRED), count-cDeleted,cDeleted); + else + AddLogLine(false, GetResString(IDS_CREDITFILELOADED), count); + } + catch(CFileException* error){ + if (error->m_cause == CFileException::endOfFile) + LogError(LOG_STATUSBAR, GetResString(IDS_CREDITFILECORRUPT)); + else{ + TCHAR buffer[MAX_CFEXP_ERRORMSG]; + error->GetErrorMessage(buffer, ARRSIZE(buffer)); + LogError(LOG_STATUSBAR, GetResString(IDS_ERR_CREDITFILEREAD), buffer); + } + error->Delete(); + } +} + +void CClientCreditsList::SaveList() +{ + if (thePrefs.GetLogFileSaving()) + AddDebugLogLine(false, _T("Saving clients credit list file \"%s\""), CLIENTS_MET_FILENAME); + m_nLastSaved = ::GetTickCount(); + + CString name = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + CLIENTS_MET_FILENAME; + CFile file;// no buffering needed here since we swap out the entire array + CFileException fexp; + if (!file.Open(name, CFile::modeWrite|CFile::modeCreate|CFile::typeBinary|CFile::shareDenyWrite, &fexp)){ + CString strError(GetResString(IDS_ERR_FAILED_CREDITSAVE)); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + return; + } + + uint32 count = m_mapClients.GetCount(); + BYTE* pBuffer = new BYTE[count*sizeof(CreditStruct)]; + CClientCredits* cur_credit; + CCKey tempkey(0); + POSITION pos = m_mapClients.GetStartPosition(); + count = 0; + while (pos) + { + m_mapClients.GetNextAssoc(pos, tempkey, cur_credit); + if (cur_credit->GetUploadedTotal() || cur_credit->GetDownloadedTotal()) + { + memcpy(pBuffer+(count*sizeof(CreditStruct)), cur_credit->GetDataStruct(), sizeof(CreditStruct)); + count++; + } + } + + try{ + uint8 version = CREDITFILE_VERSION; + file.Write(&version, 1); + file.Write(&count, 4); + file.Write(pBuffer, count*sizeof(CreditStruct)); + if (thePrefs.GetCommitFiles() >= 2 || (thePrefs.GetCommitFiles() >= 1 && !theApp.emuledlg->IsRunning())) + file.Flush(); + file.Close(); + } + catch(CFileException* error){ + CString strError(GetResString(IDS_ERR_FAILED_CREDITSAVE)); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (error->GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + error->Delete(); + } + + delete[] pBuffer; +} + +CClientCredits* CClientCreditsList::GetCredit(const uchar* key) +{ + CClientCredits* result; + CCKey tkey(key); + if (!m_mapClients.Lookup(tkey, result)){ + result = new CClientCredits(key); + m_mapClients.SetAt(CCKey(result->GetKey()), result); + } + result->SetLastSeen(); + return result; +} + +void CClientCreditsList::Process() +{ + if (::GetTickCount() - m_nLastSaved > MIN2MS(13)) + SaveList(); +} + +void CClientCredits::InitalizeIdent() +{ + if (m_pCredits->nKeySize == 0 ){ + memset(m_abyPublicKey,0,80); // for debugging + m_nPublicKeyLen = 0; + IdentState = IS_NOTAVAILABLE; + } + else{ + m_nPublicKeyLen = m_pCredits->nKeySize; + memcpy(m_abyPublicKey, m_pCredits->abySecureIdent, m_nPublicKeyLen); + IdentState = IS_IDNEEDED; + } + m_dwCryptRndChallengeFor = 0; + m_dwCryptRndChallengeFrom = 0; + m_dwIdentIP = 0; +} + +void CClientCredits::Verified(uint32 dwForIP) +{ + m_dwIdentIP = dwForIP; + // client was verified, copy the keyto store him if not done already + if (m_pCredits->nKeySize == 0){ + m_pCredits->nKeySize = m_nPublicKeyLen; + memcpy(m_pCredits->abySecureIdent, m_abyPublicKey, m_nPublicKeyLen); + if (GetDownloadedTotal() > 0){ + // for security reason, we have to delete all prior credits here + m_pCredits->nDownloadedHi = 0; + m_pCredits->nDownloadedLo = 1; + m_pCredits->nUploadedHi = 0; + m_pCredits->nUploadedLo = 1; // in order to safe this client, set 1 byte + if (thePrefs.GetVerbose()) + DEBUG_ONLY(AddDebugLogLine(false, _T("Credits deleted due to new SecureIdent"))); + } + } + IdentState = IS_IDENTIFIED; +} + +bool CClientCredits::SetSecureIdent(const uchar* pachIdent, uint8 nIdentLen) // verified Public key cannot change, use only if there is not public key yet +{ + if (MAXPUBKEYSIZE < nIdentLen || m_pCredits->nKeySize != 0 ) + return false; + memcpy(m_abyPublicKey,pachIdent, nIdentLen); + m_nPublicKeyLen = nIdentLen; + IdentState = IS_IDNEEDED; + return true; +} + +EIdentState CClientCredits::GetCurrentIdentState(uint32 dwForIP) const +{ + if (IdentState != IS_IDENTIFIED) + return IdentState; + else{ + if (dwForIP == m_dwIdentIP) + return IS_IDENTIFIED; + else + return IS_IDBADGUY; + // mod note: clients which just reconnected after an IP change and have to ident yet will also have this state for 1-2 seconds + // so don't try to spam such clients with "bad guy" messages (besides: spam messages are always bad) + } +} + +using namespace CryptoPP; + +void CClientCreditsList::InitalizeCrypting() +{ + m_nMyPublicKeyLen = 0; + memset(m_abyMyPublicKey,0,80); // not really needed; better for debugging tho + m_pSignkey = NULL; + if (!thePrefs.IsSecureIdentEnabled()) + return; + // check if keyfile is there + bool bCreateNewKey = false; + HANDLE hKeyFile = ::CreateFile(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + _T("cryptkey.dat"), GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hKeyFile != INVALID_HANDLE_VALUE) + { + if (::GetFileSize(hKeyFile, NULL) == 0) + bCreateNewKey = true; + ::CloseHandle(hKeyFile); + } + else + bCreateNewKey = true; + if (bCreateNewKey) + CreateKeyPair(); + + // load key + try{ + // load private key + FileSource filesource(CStringA(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + _T("cryptkey.dat")), true,new Base64Decoder); + m_pSignkey = new RSASSA_PKCS1v15_SHA_Signer(filesource); + // calculate and store public key + RSASSA_PKCS1v15_SHA_Verifier pubkey(*m_pSignkey); + ArraySink asink(m_abyMyPublicKey, 80); + pubkey.DEREncode(asink); + m_nMyPublicKeyLen = (uint8)asink.TotalPutLength(); + asink.MessageEnd(); + } + catch(...) + { + delete m_pSignkey; + m_pSignkey = NULL; + LogError(LOG_STATUSBAR, GetResString(IDS_CRYPT_INITFAILED)); + ASSERT(0); + } + ASSERT( Debug_CheckCrypting() ); +} + +bool CClientCreditsList::CreateKeyPair() +{ + try{ + AutoSeededRandomPool rng; + InvertibleRSAFunction privkey; + privkey.Initialize(rng,RSAKEYSIZE); + + Base64Encoder privkeysink(new FileSink(CStringA(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + _T("cryptkey.dat")))); + privkey.DEREncode(privkeysink); + privkeysink.MessageEnd(); + + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("Created new RSA keypair")); + } + catch(...) + { + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("Failed to create new RSA keypair")); + ASSERT ( false ); + return false; + } + return true; +} + +uint8 CClientCreditsList::CreateSignature(CClientCredits* pTarget, uchar* pachOutput, uint8 nMaxSize, + uint32 ChallengeIP, uint8 byChaIPKind, + CryptoPP::RSASSA_PKCS1v15_SHA_Signer* sigkey) +{ + // sigkey param is used for debug only + if (sigkey == NULL) + sigkey = m_pSignkey; + + // create a signature of the public key from pTarget + ASSERT( pTarget ); + ASSERT( pachOutput ); + uint8 nResult; + if ( !CryptoAvailable() ) + return 0; + try{ + + SecByteBlock sbbSignature(sigkey->SignatureLength()); + AutoSeededRandomPool rng; + byte abyBuffer[MAXPUBKEYSIZE+9]; + uint32 keylen = pTarget->GetSecIDKeyLen(); + memcpy(abyBuffer,pTarget->GetSecureIdent(),keylen); + // 4 additional bytes random data send from this client + uint32 challenge = pTarget->m_dwCryptRndChallengeFrom; + ASSERT ( challenge != 0 ); + PokeUInt32(abyBuffer+keylen, challenge); + uint16 ChIpLen = 0; + if ( byChaIPKind != 0){ + ChIpLen = 5; + PokeUInt32(abyBuffer+keylen+4, ChallengeIP); + PokeUInt8(abyBuffer+keylen+4+4, byChaIPKind); + } + sigkey->SignMessage(rng, abyBuffer ,keylen+4+ChIpLen , sbbSignature.begin()); + ArraySink asink(pachOutput, nMaxSize); + asink.Put(sbbSignature.begin(), sbbSignature.size()); + nResult = (uint8)asink.TotalPutLength(); + } + catch(...) + { + ASSERT ( false ); + nResult = 0; + } + return nResult; +} + +bool CClientCreditsList::VerifyIdent(CClientCredits* pTarget, const uchar* pachSignature, uint8 nInputSize, + uint32 dwForIP, uint8 byChaIPKind) +{ + ASSERT( pTarget ); + ASSERT( pachSignature ); + if ( !CryptoAvailable() ){ + pTarget->IdentState = IS_NOTAVAILABLE; + return false; + } + bool bResult; + try{ + StringSource ss_Pubkey((byte*)pTarget->GetSecureIdent(),pTarget->GetSecIDKeyLen(),true,0); + RSASSA_PKCS1v15_SHA_Verifier pubkey(ss_Pubkey); + // 4 additional bytes random data send from this client +5 bytes v2 + byte abyBuffer[MAXPUBKEYSIZE+9]; + memcpy(abyBuffer,m_abyMyPublicKey,m_nMyPublicKeyLen); + uint32 challenge = pTarget->m_dwCryptRndChallengeFor; + ASSERT ( challenge != 0 ); + PokeUInt32(abyBuffer+m_nMyPublicKeyLen, challenge); + + // v2 security improvments (not supported by 29b, not used as default by 29c) + uint8 nChIpSize = 0; + if (byChaIPKind != 0){ + nChIpSize = 5; + uint32 ChallengeIP = 0; + switch (byChaIPKind){ + case CRYPT_CIP_LOCALCLIENT: + ChallengeIP = dwForIP; + break; + case CRYPT_CIP_REMOTECLIENT: + if (theApp.serverconnect->GetClientID() == 0 || theApp.serverconnect->IsLowID()){ + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false, _T("Warning: Maybe SecureHash Ident fails because LocalIP is unknown")); + ChallengeIP = theApp.serverconnect->GetLocalIP(); + } + else + ChallengeIP = theApp.serverconnect->GetClientID(); + break; + case CRYPT_CIP_NONECLIENT: // maybe not supported in future versions + ChallengeIP = 0; + break; + } + PokeUInt32(abyBuffer+m_nMyPublicKeyLen+4, ChallengeIP); + PokeUInt8(abyBuffer+m_nMyPublicKeyLen+4+4, byChaIPKind); + } + //v2 end + + bResult = pubkey.VerifyMessage(abyBuffer, m_nMyPublicKeyLen+4+nChIpSize, pachSignature, nInputSize); + } + catch(...) + { + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("Error: Unknown exception in %hs"), __FUNCTION__); + //ASSERT(0); + bResult = false; + } + if (!bResult){ + if (pTarget->IdentState == IS_IDNEEDED) + pTarget->IdentState = IS_IDFAILED; + } + else{ + pTarget->Verified(dwForIP); + } + return bResult; +} + +bool CClientCreditsList::CryptoAvailable() +{ + return (m_nMyPublicKeyLen > 0 && m_pSignkey != 0 && thePrefs.IsSecureIdentEnabled() ); +} + + +#ifdef _DEBUG +bool CClientCreditsList::Debug_CheckCrypting() +{ + // create random key + AutoSeededRandomPool rng; + + RSASSA_PKCS1v15_SHA_Signer priv(rng, 384); + RSASSA_PKCS1v15_SHA_Verifier pub(priv); + + byte abyPublicKey[80]; + ArraySink asink(abyPublicKey, 80); + pub.DEREncode(asink); + uint8 PublicKeyLen = (uint8)asink.TotalPutLength(); + asink.MessageEnd(); + uint32 challenge = rand(); + // create fake client which pretends to be this emule + CreditStruct* newcstruct = new CreditStruct; + memset(newcstruct, 0, sizeof(CreditStruct)); + CClientCredits* newcredits = new CClientCredits(newcstruct); + newcredits->SetSecureIdent(m_abyMyPublicKey,m_nMyPublicKeyLen); + newcredits->m_dwCryptRndChallengeFrom = challenge; + // create signature with fake priv key + uchar pachSignature[200]; + memset(pachSignature,0,200); + uint8 sigsize = CreateSignature(newcredits,pachSignature,200,0,false, &priv); + + + // next fake client uses the random created public key + CreditStruct* newcstruct2 = new CreditStruct; + memset(newcstruct2, 0, sizeof(CreditStruct)); + CClientCredits* newcredits2 = new CClientCredits(newcstruct2); + newcredits2->m_dwCryptRndChallengeFor = challenge; + + // if you uncomment one of the following lines the check has to fail + //abyPublicKey[5] = 34; + //m_abyMyPublicKey[5] = 22; + //pachSignature[5] = 232; + + newcredits2->SetSecureIdent(abyPublicKey,PublicKeyLen); + + //now verify this signature - if it's true everything is fine + bool bResult = VerifyIdent(newcredits2,pachSignature,sigsize,0,0); + + delete newcredits; + delete newcredits2; + + return bResult; +} +#endif +uint32 CClientCredits::GetSecureWaitStartTime(uint32 dwForIP) +{ + if (m_dwUnSecureWaitTime == 0 || m_dwSecureWaitTime == 0) + SetSecWaitStartTime(dwForIP); + + if (m_pCredits->nKeySize != 0){ // this client is a SecureHash Client + if (GetCurrentIdentState(dwForIP) == IS_IDENTIFIED){ // good boy + return m_dwSecureWaitTime; + } + else{ // not so good boy + if (dwForIP == m_dwWaitTimeIP){ + return m_dwUnSecureWaitTime; + } + else{ // bad boy + // this can also happen if the client has not identified himself yet, but will do later - so maybe he is not a bad boy :) . + CString buffer2, buffer; + /*for (uint16 i = 0;i != 16;i++){ + buffer2.Format("%02X",this->m_pCredits->abyKey[i]); + buffer+=buffer2; + } + if (thePrefs.GetLogSecureIdent()) + AddDebugLogLine(false,"Warning: WaitTime resetted due to Invalid Ident for Userhash %s", buffer);*/ + + m_dwUnSecureWaitTime = ::GetTickCount(); + m_dwWaitTimeIP = dwForIP; + return m_dwUnSecureWaitTime; + } + } + } + else{ // not a SecureHash Client - handle it like before for now (no security checks) + return m_dwUnSecureWaitTime; + } +} + +void CClientCredits::SetSecWaitStartTime(uint32 dwForIP) +{ + m_dwUnSecureWaitTime = ::GetTickCount()-1; + m_dwSecureWaitTime = ::GetTickCount()-1; + m_dwWaitTimeIP = dwForIP; +} + +void CClientCredits::ClearWaitStartTime() +{ + m_dwUnSecureWaitTime = 0; + m_dwSecureWaitTime = 0; +} diff --git a/ClientCredits.h b/ClientCredits.h new file mode 100644 index 00000000..8447e931 --- /dev/null +++ b/ClientCredits.h @@ -0,0 +1,134 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "MapKey.h" +#pragma warning(disable:4516) // access-declarations are deprecated; member using-declarations provide a better alternative +#pragma warning(disable:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4702) // unreachable code +#include +#pragma warning(default:4702) // unreachable code +#pragma warning(default:4100) // unreferenced formal parameter +#pragma warning(default:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(default:4516) // access-declarations are deprecated; member using-declarations provide a better alternative + +#define MAXPUBKEYSIZE 80 + +#define CRYPT_CIP_REMOTECLIENT 10 +#define CRYPT_CIP_LOCALCLIENT 20 +#define CRYPT_CIP_NONECLIENT 30 + +#pragma pack(1) +struct CreditStruct_29a{ + uchar abyKey[16]; + uint32 nUploadedLo; // uploaded TO him + uint32 nDownloadedLo; // downloaded from him + uint32 nLastSeen; + uint32 nUploadedHi; // upload high 32 + uint32 nDownloadedHi; // download high 32 + uint16 nReserved3; +}; +struct CreditStruct{ + uchar abyKey[16]; + uint32 nUploadedLo; // uploaded TO him + uint32 nDownloadedLo; // downloaded from him + uint32 nLastSeen; + uint32 nUploadedHi; // upload high 32 + uint32 nDownloadedHi; // download high 32 + uint16 nReserved3; + uint8 nKeySize; + uchar abySecureIdent[MAXPUBKEYSIZE]; +}; +#pragma pack() + +enum EIdentState{ + IS_NOTAVAILABLE, + IS_IDNEEDED, + IS_IDENTIFIED, + IS_IDFAILED, + IS_IDBADGUY, +}; + +class CClientCredits +{ + friend class CClientCreditsList; +public: + CClientCredits(CreditStruct* in_credits); + CClientCredits(const uchar* key); + ~CClientCredits(); + + const uchar* GetKey() const {return m_pCredits->abyKey;} + uchar* GetSecureIdent() {return m_abyPublicKey;} + uint8 GetSecIDKeyLen() const {return m_nPublicKeyLen;} + CreditStruct* GetDataStruct() const {return m_pCredits;} + void ClearWaitStartTime(); + void AddDownloaded(uint32 bytes, uint32 dwForIP); + void AddUploaded(uint32 bytes, uint32 dwForIP); + uint64 GetUploadedTotal() const; + uint64 GetDownloadedTotal() const; + float GetScoreRatio(uint32 dwForIP) const; + void SetLastSeen() {m_pCredits->nLastSeen = time(NULL);} + bool SetSecureIdent(const uchar* pachIdent, uint8 nIdentLen); // Public key cannot change, use only if there is not public key yet + uint32 m_dwCryptRndChallengeFor; + uint32 m_dwCryptRndChallengeFrom; + EIdentState GetCurrentIdentState(uint32 dwForIP) const; // can be != IdentState + uint32 GetSecureWaitStartTime(uint32 dwForIP); + void SetSecWaitStartTime(uint32 dwForIP); +protected: + void Verified(uint32 dwForIP); + EIdentState IdentState; +private: + void InitalizeIdent(); + CreditStruct* m_pCredits; + byte m_abyPublicKey[80]; // even keys which are not verified will be stored here, and - if verified - copied into the struct + uint8 m_nPublicKeyLen; + uint32 m_dwIdentIP; + uint32 m_dwSecureWaitTime; + uint32 m_dwUnSecureWaitTime; + uint32 m_dwWaitTimeIP; // client IP assigned to the waittime +}; + +class CClientCreditsList +{ +public: + CClientCreditsList(); + ~CClientCreditsList(); + + // return signature size, 0 = Failed | use sigkey param for debug only + uint8 CreateSignature(CClientCredits* pTarget, uchar* pachOutput, uint8 nMaxSize, uint32 ChallengeIP, uint8 byChaIPKind, CryptoPP::RSASSA_PKCS1v15_SHA_Signer* sigkey = NULL); + bool VerifyIdent(CClientCredits* pTarget, const uchar* pachSignature, uint8 nInputSize, uint32 dwForIP, uint8 byChaIPKind); + + CClientCredits* GetCredit(const uchar* key) ; + void Process(); + uint8 GetPubKeyLen() const {return m_nMyPublicKeyLen;} + byte* GetPublicKey() {return m_abyMyPublicKey;} + bool CryptoAvailable(); +protected: + void LoadList(); + void SaveList(); + void InitalizeCrypting(); + bool CreateKeyPair(); +#ifdef _DEBUG + bool Debug_CheckCrypting(); +#endif +private: + CMap m_mapClients; + uint32 m_nLastSaved; + CryptoPP::RSASSA_PKCS1v15_SHA_Signer* m_pSignkey; + byte m_abyMyPublicKey[80]; + uint8 m_nMyPublicKeyLen; +}; diff --git a/ClientDetailDialog.cpp b/ClientDetailDialog.cpp new file mode 100644 index 00000000..8a46a131 --- /dev/null +++ b/ClientDetailDialog.cpp @@ -0,0 +1,308 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ClientDetailDialog.h" +#include "UpDownClient.h" +#include "PartFile.h" +#include "ClientCredits.h" +#include "otherfunctions.h" +#include "Server.h" +#include "ServerList.h" +#include "SharedFileList.h" +#include "HighColorTab.hpp" +#include "UserMsgs.h" +#include "ListenSocket.h" +#include "preferences.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// CClientDetailPage + +IMPLEMENT_DYNAMIC(CClientDetailPage, CResizablePage) + +BEGIN_MESSAGE_MAP(CClientDetailPage, CResizablePage) + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) +END_MESSAGE_MAP() + +CClientDetailPage::CClientDetailPage() + : CResizablePage(CClientDetailPage::IDD, 0 ) +{ + m_paClients = NULL; + m_bDataChanged = false; + m_strCaption = GetResString(IDS_CD_TITLE); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; +} + +CClientDetailPage::~CClientDetailPage() +{ +} + +void CClientDetailPage::DoDataExchange(CDataExchange* pDX) +{ + CResizablePage::DoDataExchange(pDX); +} + +BOOL CClientDetailPage::OnInitDialog() +{ + CResizablePage::OnInitDialog(); + InitWindowStyles(this); + + AddAnchor(IDC_STATIC30, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_STATIC40, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_STATIC50, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_DNAME, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_DSNAME, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_DDOWNLOADING, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_UPLOADING, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_OBFUSCATION_STAT, TOP_LEFT, TOP_RIGHT); + + Localize(); + return TRUE; +} + +BOOL CClientDetailPage::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + + if (m_bDataChanged) + { + CUpDownClient* client = STATIC_DOWNCAST(CUpDownClient, (*m_paClients)[0]); + + CString buffer; + if (client->GetUserName()) + GetDlgItem(IDC_DNAME)->SetWindowText(client->GetUserName()); + else + GetDlgItem(IDC_DNAME)->SetWindowText(_T("?")); + + if (client->HasValidHash()) + GetDlgItem(IDC_DHASH)->SetWindowText(md4str(client->GetUserHash())); + else + GetDlgItem(IDC_DHASH)->SetWindowText(_T("?")); + + GetDlgItem(IDC_DSOFT)->SetWindowText(client->GetClientSoftVer()); + + if (client->SupportsCryptLayer() && thePrefs.IsClientCryptLayerSupported() && (client->RequestsCryptLayer() || thePrefs.IsClientCryptLayerRequested()) + && (client->IsObfuscatedConnectionEstablished() || !(client->socket != NULL && client->socket->IsConnected()))) + { + buffer = GetResString(IDS_ENABLED); + } + else if (client->SupportsCryptLayer()) + buffer = GetResString(IDS_SUPPORTED); + else + buffer = GetResString(IDS_IDENTNOSUPPORT); +#if defined(_DEBUG) + if (client->IsObfuscatedConnectionEstablished()) + buffer += _T("(In Use)"); +#endif + GetDlgItem(IDC_OBFUSCATION_STAT)->SetWindowText(buffer); + + buffer.Format(_T("%s"),(client->HasLowID() ? GetResString(IDS_IDLOW):GetResString(IDS_IDHIGH))); + GetDlgItem(IDC_DID)->SetWindowText(buffer); + + if (client->GetServerIP()){ + GetDlgItem(IDC_DSIP)->SetWindowText(ipstr(client->GetServerIP())); + CServer* cserver = theApp.serverlist->GetServerByIPTCP(client->GetServerIP(), client->GetServerPort()); + if (cserver) + GetDlgItem(IDC_DSNAME)->SetWindowText(cserver->GetListName()); + else + GetDlgItem(IDC_DSNAME)->SetWindowText(_T("?")); + } + else{ + GetDlgItem(IDC_DSIP)->SetWindowText(_T("?")); + GetDlgItem(IDC_DSNAME)->SetWindowText(_T("?")); + } + + CKnownFile* file = theApp.sharedfiles->GetFileByID(client->GetUploadFileID()); + if (file) + GetDlgItem(IDC_DDOWNLOADING)->SetWindowText(file->GetFileName()); + else + GetDlgItem(IDC_DDOWNLOADING)->SetWindowText(_T("-")); + + if (client->GetRequestFile()) + GetDlgItem(IDC_UPLOADING)->SetWindowText( client->GetRequestFile()->GetFileName() ); + else + GetDlgItem(IDC_UPLOADING)->SetWindowText(_T("-")); + + GetDlgItem(IDC_DDUP)->SetWindowText(CastItoXBytes(client->GetTransferredDown(), false, false)); + + GetDlgItem(IDC_DDOWN)->SetWindowText(CastItoXBytes(client->GetTransferredUp(), false, false)); + + buffer.Format(_T("%s"), CastItoXBytes(client->GetDownloadDatarate(), false, true)); + GetDlgItem(IDC_DAVUR)->SetWindowText(buffer); + + buffer.Format(_T("%s"),CastItoXBytes(client->GetDatarate(), false, true)); + GetDlgItem(IDC_DAVDR)->SetWindowText(buffer); + + if (client->Credits()){ + GetDlgItem(IDC_DUPTOTAL)->SetWindowText(CastItoXBytes(client->Credits()->GetDownloadedTotal(), false, false)); + GetDlgItem(IDC_DDOWNTOTAL)->SetWindowText(CastItoXBytes(client->Credits()->GetUploadedTotal(), false, false)); + buffer.Format(_T("%.1f"),(float)client->Credits()->GetScoreRatio(client->GetIP())); + GetDlgItem(IDC_DRATIO)->SetWindowText(buffer); + + if (theApp.clientcredits->CryptoAvailable()){ + switch(client->Credits()->GetCurrentIdentState(client->GetIP())){ + case IS_NOTAVAILABLE: + GetDlgItem(IDC_CDIDENT)->SetWindowText(GetResString(IDS_IDENTNOSUPPORT)); + break; + case IS_IDFAILED: + case IS_IDNEEDED: + case IS_IDBADGUY: + GetDlgItem(IDC_CDIDENT)->SetWindowText(GetResString(IDS_IDENTFAILED)); + break; + case IS_IDENTIFIED: + GetDlgItem(IDC_CDIDENT)->SetWindowText(GetResString(IDS_IDENTOK)); + break; + } + } + else + GetDlgItem(IDC_CDIDENT)->SetWindowText(GetResString(IDS_IDENTNOSUPPORT)); + } + else{ + GetDlgItem(IDC_DDOWNTOTAL)->SetWindowText(_T("?")); + GetDlgItem(IDC_DUPTOTAL)->SetWindowText(_T("?")); + GetDlgItem(IDC_DRATIO)->SetWindowText(_T("?")); + GetDlgItem(IDC_CDIDENT)->SetWindowText(_T("?")); + } + + if (client->GetUserName() && client->Credits()!=NULL){ + buffer.Format(_T("%.1f"),(float)client->GetScore(false,client->IsDownloading(),true)); + GetDlgItem(IDC_DRATING)->SetWindowText(buffer); + } + else + GetDlgItem(IDC_DRATING)->SetWindowText(_T("?")); + + if (client->GetUploadState() != US_NONE && client->Credits()!=NULL){ + if (!client->GetFriendSlot()){ + buffer.Format(_T("%u"),client->GetScore(false,client->IsDownloading(),false)); + GetDlgItem(IDC_DSCORE)->SetWindowText(buffer); + } + else + GetDlgItem(IDC_DSCORE)->SetWindowText(GetResString(IDS_FRIENDDETAIL)); + } + else + GetDlgItem(IDC_DSCORE)->SetWindowText(_T("-")); + + if (client->GetKadPort() ) + buffer.Format( _T("%s"), GetResString(IDS_CONNECTED)); + else + buffer.Format( _T("%s"), GetResString(IDS_DISCONNECTED)); + GetDlgItem(IDC_CLIENTDETAIL_KADCON)->SetWindowText(buffer); + + m_bDataChanged = false; + } + return TRUE; +} + +LRESULT CClientDetailPage::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +void CClientDetailPage::Localize() +{ + GetDlgItem(IDC_STATIC30)->SetWindowText(GetResString(IDS_CD_GENERAL)); + GetDlgItem(IDC_STATIC31)->SetWindowText(GetResString(IDS_CD_UNAME)); + GetDlgItem(IDC_STATIC32)->SetWindowText(GetResString(IDS_CD_UHASH)); + GetDlgItem(IDC_STATIC33)->SetWindowText(GetResString(IDS_CD_CSOFT) + _T(':')); + GetDlgItem(IDC_STATIC35)->SetWindowText(GetResString(IDS_CD_SIP)); + GetDlgItem(IDC_STATIC38)->SetWindowText(GetResString(IDS_CD_SNAME)); + GetDlgItem(IDC_STATIC_OBF_LABEL)->SetWindowText(GetResString(IDS_OBFUSCATION) + _T(':')); + + GetDlgItem(IDC_STATIC40)->SetWindowText(GetResString(IDS_CD_TRANS)); + GetDlgItem(IDC_STATIC41)->SetWindowText(GetResString(IDS_CD_CDOWN)); + GetDlgItem(IDC_STATIC42)->SetWindowText(GetResString(IDS_CD_DOWN)); + GetDlgItem(IDC_STATIC43)->SetWindowText(GetResString(IDS_CD_ADOWN)); + GetDlgItem(IDC_STATIC44)->SetWindowText(GetResString(IDS_CD_TDOWN)); + GetDlgItem(IDC_STATIC45)->SetWindowText(GetResString(IDS_CD_UP)); + GetDlgItem(IDC_STATIC46)->SetWindowText(GetResString(IDS_CD_AUP)); + GetDlgItem(IDC_STATIC47)->SetWindowText(GetResString(IDS_CD_TUP)); + GetDlgItem(IDC_STATIC48)->SetWindowText(GetResString(IDS_CD_UPLOADREQ)); + + GetDlgItem(IDC_STATIC50)->SetWindowText(GetResString(IDS_CD_SCORES)); + GetDlgItem(IDC_STATIC51)->SetWindowText(GetResString(IDS_CD_MOD)); + GetDlgItem(IDC_STATIC52)->SetWindowText(GetResString(IDS_CD_RATING)); + GetDlgItem(IDC_STATIC53)->SetWindowText(GetResString(IDS_CD_USCORE)); + GetDlgItem(IDC_STATIC133x)->SetWindowText(GetResString(IDS_CD_IDENT)); + GetDlgItem(IDC_CLIENTDETAIL_KAD)->SetWindowText(GetResString(IDS_KADEMLIA) + _T(":")); +} + + +/////////////////////////////////////////////////////////////////////////////// +// CClientDetailDialog + +IMPLEMENT_DYNAMIC(CClientDetailDialog, CListViewWalkerPropertySheet) + +BEGIN_MESSAGE_MAP(CClientDetailDialog, CListViewWalkerPropertySheet) + ON_WM_DESTROY() +END_MESSAGE_MAP() + +CClientDetailDialog::CClientDetailDialog(CUpDownClient* pClient, CListCtrlItemWalk* pListCtrl) + : CListViewWalkerPropertySheet(pListCtrl) +{ + m_aItems.Add(pClient); + Construct(); +} + +CClientDetailDialog::CClientDetailDialog(const CSimpleArray* paClients, CListCtrlItemWalk* pListCtrl) + : CListViewWalkerPropertySheet(pListCtrl) +{ + for (int i = 0; i < paClients->GetSize(); i++) + m_aItems.Add((*paClients)[i]); + Construct(); +} + +void CClientDetailDialog::Construct() +{ + m_psh.dwFlags &= ~PSH_HASHELP; + m_psh.dwFlags |= PSH_NOAPPLYNOW; + + m_wndClient.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndClient.m_psp.dwFlags |= PSP_USEICONID; + m_wndClient.m_psp.pszIcon = _T("CLIENTDETAILS"); + m_wndClient.SetClients(&m_aItems); + AddPage(&m_wndClient); +} + +CClientDetailDialog::~CClientDetailDialog() +{ +} + +void CClientDetailDialog::OnDestroy() +{ + CListViewWalkerPropertySheet::OnDestroy(); +} + +BOOL CClientDetailDialog::OnInitDialog() +{ + EnableStackedTabs(FALSE); + BOOL bResult = CListViewWalkerPropertySheet::OnInitDialog(); + HighColorTab::UpdateImageList(*this); + InitWindowStyles(this); + EnableSaveRestore(_T("ClientDetailDialog")); // call this after(!) OnInitDialog + SetWindowText(GetResString(IDS_CD_TITLE)); + return bResult; +} diff --git a/ClientDetailDialog.h b/ClientDetailDialog.h new file mode 100644 index 00000000..135ea23c --- /dev/null +++ b/ClientDetailDialog.h @@ -0,0 +1,77 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +#include "ResizableLib/ResizablePage.h" +#include "ResizableLib/ResizableSheet.h" +#include "ListViewWalkerPropertySheet.h" + +class CUpDownClient; + +/////////////////////////////////////////////////////////////////////////////// +// CClientDetailPage + +class CClientDetailPage : public CResizablePage +{ + DECLARE_DYNAMIC(CClientDetailPage) + +public: + CClientDetailPage(); // standard constructor + virtual ~CClientDetailPage(); + + void SetClients(const CSimpleArray* paClients) { m_paClients = paClients; m_bDataChanged = true; } + + enum { IDD = IDD_SOURCEDETAILWND }; + +protected: + const CSimpleArray* m_paClients; + bool m_bDataChanged; + + void Localize(); + void RefreshData(); + + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnSetActive(); + + DECLARE_MESSAGE_MAP() + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); +}; + + +/////////////////////////////////////////////////////////////////////////////// +// CClientDetailDialog + +class CClientDetailDialog : public CListViewWalkerPropertySheet +{ + DECLARE_DYNAMIC(CClientDetailDialog) + +public: + CClientDetailDialog(CUpDownClient* pClient, CListCtrlItemWalk* pListCtrl = NULL); + CClientDetailDialog(const CSimpleArray* paClients, CListCtrlItemWalk* pListCtrl = NULL); + virtual ~CClientDetailDialog(); + +protected: + CClientDetailPage m_wndClient; + + void Construct(); + + virtual BOOL OnInitDialog(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnDestroy(); +}; diff --git a/ClientList.cpp b/ClientList.cpp new file mode 100644 index 00000000..94b167f4 --- /dev/null +++ b/ClientList.cpp @@ -0,0 +1,1019 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ClientList.h" +#include "otherfunctions.h" +#include "Kademlia/Kademlia/kademlia.h" +#include "Kademlia/Kademlia/prefs.h" +#include "Kademlia/Kademlia/search.h" +#include "Kademlia/Kademlia/searchmanager.h" +#include "Kademlia/routing/contact.h" +#include "Kademlia/net/kademliaudplistener.h" +#include "kademlia/kademlia/UDPFirewallTester.h" +#include "kademlia/utils/UInt128.h" +#include "LastCommonRouteFinder.h" +#include "UploadQueue.h" +#include "DownloadQueue.h" +#include "UpDownClient.h" +#include "ClientCredits.h" +#include "ListenSocket.h" +#include "Opcodes.h" +#include "Sockets.h" +#include "emuledlg.h" +#include "TransferDlg.h" +#include "serverwnd.h" +#include "Log.h" +#include "packets.h" +#include "Statistics.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +CClientList::CClientList(){ + m_dwLastBannCleanUp = 0; + m_dwLastTrackedCleanUp = 0; + m_dwLastClientCleanUp = 0; + m_nBuddyStatus = Disconnected; + m_bannedList.InitHashTable(331); + m_trackedClientsList.InitHashTable(2011); + m_globDeadSourceList.Init(true); + m_pBuddy = NULL; +} + +CClientList::~CClientList(){ + RemoveAllTrackedClients(); +} + +void CClientList::GetStatistics(uint32 &ruTotalClients, int stats[NUM_CLIENTLIST_STATS], + CMap& clientVersionEDonkey, + CMap& clientVersionEDonkeyHybrid, + CMap& clientVersionEMule, + CMap& clientVersionAMule) +{ + ruTotalClients = list.GetCount(); + memset(stats, 0, sizeof(stats[0]) * NUM_CLIENTLIST_STATS); + + for (POSITION pos = list.GetHeadPosition(); pos != NULL; ) + { + const CUpDownClient* cur_client = list.GetNext(pos); + + if (cur_client->HasLowID()) + stats[14]++; + + switch (cur_client->GetClientSoft()) + { + case SO_EMULE: + case SO_OLDEMULE: + stats[2]++; + clientVersionEMule[cur_client->GetVersion()]++; + break; + + case SO_EDONKEYHYBRID : + stats[4]++; + clientVersionEDonkeyHybrid[cur_client->GetVersion()]++; + break; + + case SO_AMULE: + stats[10]++; + clientVersionAMule[cur_client->GetVersion()]++; + break; + + case SO_EDONKEY: + stats[1]++; + clientVersionEDonkey[cur_client->GetVersion()]++; + break; + + case SO_MLDONKEY: + stats[3]++; + break; + + case SO_SHAREAZA: + stats[11]++; + break; + + // all remaining 'eMule Compatible' clients + case SO_CDONKEY: + case SO_XMULE: + case SO_LPHANT: + stats[5]++; + break; + + default: + stats[0]++; + break; + } + + if (cur_client->Credits() != NULL) + { + switch (cur_client->Credits()->GetCurrentIdentState(cur_client->GetIP())) + { + case IS_IDENTIFIED: + stats[12]++; + break; + case IS_IDFAILED: + case IS_IDNEEDED: + case IS_IDBADGUY: + stats[13]++; + break; + } + } + + if (cur_client->GetDownloadState()==DS_ERROR) + stats[6]++; // Error + + switch (cur_client->GetUserPort()) + { + case 4662: + stats[8]++; // Default Port + break; + default: + stats[9]++; // Other Port + } + + // Network client stats + if (cur_client->GetServerIP() && cur_client->GetServerPort()) + { + stats[15]++; // eD2K + if(cur_client->GetKadPort()) + { + stats[17]++; // eD2K/Kad + stats[16]++; // Kad + } + } + else if (cur_client->GetKadPort()) + stats[16]++; // Kad + else + stats[18]++; // Unknown + } +} + +void CClientList::AddClient(CUpDownClient* toadd, bool bSkipDupTest) +{ + // skipping the check for duplicate list entries is only to be done for optimization purposes, if the calling + // function has ensured that this client instance is not already within the list -> there are never duplicate + // client instances in this list. + if (!bSkipDupTest){ + if(list.Find(toadd)) + return; + } + theApp.emuledlg->transferwnd->GetClientList()->AddClient(toadd); + list.AddTail(toadd); +} + +// ZZ:UploadSpeedSense --> +bool CClientList::GiveClientsForTraceRoute() { + // this is a host that lastCommonRouteFinder can use to traceroute + return theApp.lastCommonRouteFinder->AddHostsToCheck(list); +} +// ZZ:UploadSpeedSense <-- + +void CClientList::RemoveClient(CUpDownClient* toremove, LPCTSTR pszReason){ + POSITION pos = list.Find(toremove); + if (pos){ + theApp.uploadqueue->RemoveFromUploadQueue(toremove, CString(_T("CClientList::RemoveClient: ")) + pszReason); + theApp.uploadqueue->RemoveFromWaitingQueue(toremove); + theApp.downloadqueue->RemoveSource(toremove); + theApp.emuledlg->transferwnd->GetClientList()->RemoveClient(toremove); + list.RemoveAt(pos); + } + RemoveFromKadList(toremove); + RemoveConnectingClient(toremove); +} + +void CClientList::DeleteAll(){ + theApp.uploadqueue->DeleteAll(); + theApp.downloadqueue->DeleteAll(); + POSITION pos1, pos2; + for (pos1 = list.GetHeadPosition();( pos2 = pos1 ) != NULL;){ + list.GetNext(pos1); + CUpDownClient* cur_client = list.GetAt(pos2); + list.RemoveAt(pos2); + delete cur_client; // recursiv: this will call RemoveClient + } +} + +bool CClientList::AttachToAlreadyKnown(CUpDownClient** client, CClientReqSocket* sender){ + POSITION pos1, pos2; + CUpDownClient* tocheck = (*client); + CUpDownClient* found_client = NULL; + CUpDownClient* found_client2 = NULL; + for (pos1 = list.GetHeadPosition(); (pos2 = pos1) != NULL; ){ + list.GetNext(pos1); + CUpDownClient* cur_client = list.GetAt(pos2); + if (tocheck->Compare(cur_client,false)){ //matching userhash + found_client2 = cur_client; + } + if (tocheck->Compare(cur_client,true)){ //matching IP + found_client = cur_client; + break; + } + } + if (found_client == NULL) + found_client = found_client2; + + if (found_client != NULL){ + if (tocheck == found_client){ + //we found the same client instance (client may have sent more than one OP_HELLO). do not delete that client! + return true; + } + if (sender){ + if (found_client->socket){ + if (found_client->socket->IsConnected() + && (found_client->GetIP() != tocheck->GetIP() || found_client->GetUserPort() != tocheck->GetUserPort() ) ) + { + // if found_client is connected and has the IS_IDENTIFIED, it's safe to say that the other one is a bad guy + if (found_client->Credits() && found_client->Credits()->GetCurrentIdentState(found_client->GetIP()) == IS_IDENTIFIED){ + if (thePrefs.GetLogBannedClients()) + AddDebugLogLine(false, _T("Clients: %s (%s), Banreason: Userhash invalid"), tocheck->GetUserName(), ipstr(tocheck->GetConnectIP())); + tocheck->Ban(); + return false; + } + + //IDS_CLIENTCOL Warning: Found matching client, to a currently connected client: %s (%s) and %s (%s) + if (thePrefs.GetLogBannedClients()) + AddDebugLogLine(true,GetResString(IDS_CLIENTCOL), tocheck->GetUserName(), ipstr(tocheck->GetConnectIP()), found_client->GetUserName(), ipstr(found_client->GetConnectIP())); + return false; + } + found_client->socket->client = 0; + found_client->socket->Safe_Delete(); + } + found_client->socket = sender; + tocheck->socket = 0; + } + *client = 0; + delete tocheck; + *client = found_client; + return true; + } + return false; +} + +CUpDownClient* CClientList::FindClientByIP(uint32 clientip, UINT port) const +{ + for (POSITION pos = list.GetHeadPosition(); pos != NULL;) + { + CUpDownClient* cur_client = list.GetNext(pos); + if (cur_client->GetIP() == clientip && cur_client->GetUserPort() == port) + return cur_client; + } + return 0; +} + +CUpDownClient* CClientList::FindClientByUserHash(const uchar* clienthash, uint32 dwIP, uint16 nTCPPort) const +{ + CUpDownClient* pFound = NULL; + for (POSITION pos = list.GetHeadPosition(); pos != NULL;) + { + CUpDownClient* cur_client = list.GetNext(pos); + if (!md4cmp(cur_client->GetUserHash() ,clienthash)){ + if ((dwIP == 0 || dwIP == cur_client->GetIP()) && (nTCPPort == 0 || nTCPPort == cur_client->GetUserPort())) + return cur_client; + else + pFound = pFound != NULL ? pFound : cur_client; + } + } + return pFound; +} + +CUpDownClient* CClientList::FindClientByIP(uint32 clientip) const +{ + for (POSITION pos = list.GetHeadPosition(); pos != NULL;) + { + CUpDownClient* cur_client = list.GetNext(pos); + if (cur_client->GetIP() == clientip) + return cur_client; + } + return 0; +} + +CUpDownClient* CClientList::FindClientByIP_UDP(uint32 clientip, UINT nUDPport) const +{ + for (POSITION pos = list.GetHeadPosition(); pos != NULL;) + { + CUpDownClient* cur_client = list.GetNext(pos); + if (cur_client->GetIP() == clientip && cur_client->GetUDPPort() == nUDPport) + return cur_client; + } + return 0; +} + +CUpDownClient* CClientList::FindClientByUserID_KadPort(uint32 clientID, uint16 kadPort) const +{ + for (POSITION pos = list.GetHeadPosition(); pos != NULL;) + { + CUpDownClient* cur_client = list.GetNext(pos); + if (cur_client->GetUserIDHybrid() == clientID && cur_client->GetKadPort() == kadPort) + return cur_client; + } + return 0; +} + +CUpDownClient* CClientList::FindClientByIP_KadPort(uint32 ip, uint16 port) const +{ + for (POSITION pos = list.GetHeadPosition(); pos != NULL;) + { + CUpDownClient* cur_client = list.GetNext(pos); + if (cur_client->GetIP() == ip && cur_client->GetKadPort() == port) + return cur_client; + } + return 0; +} + +CUpDownClient* CClientList::FindClientByServerID(uint32 uServerIP, uint32 uED2KUserID) const +{ + uint32 uHybridUserID = ntohl(uED2KUserID); + for (POSITION pos = list.GetHeadPosition(); pos != NULL;) + { + CUpDownClient* cur_client = list.GetNext(pos); + if (cur_client->GetServerIP() == uServerIP && cur_client->GetUserIDHybrid() == uHybridUserID) + return cur_client; + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// Banned clients + +void CClientList::AddBannedClient(uint32 dwIP){ + m_bannedList.SetAt(dwIP, ::GetTickCount()); +} + +bool CClientList::IsBannedClient(uint32 dwIP) const +{ + uint32 dwBantime; + if (m_bannedList.Lookup(dwIP, dwBantime)){ + if (dwBantime + CLIENTBANTIME > ::GetTickCount()) + return true; + } + return false; +} + +void CClientList::RemoveBannedClient(uint32 dwIP){ + m_bannedList.RemoveKey(dwIP); +} + +void CClientList::RemoveAllBannedClients(){ + m_bannedList.RemoveAll(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// Tracked clients + +void CClientList::AddTrackClient(CUpDownClient* toadd){ + CDeletedClient* pResult = 0; + if (m_trackedClientsList.Lookup(toadd->GetIP(), pResult)){ + pResult->m_dwInserted = ::GetTickCount(); + for (int i = 0; i != pResult->m_ItemsList.GetCount(); i++){ + if (pResult->m_ItemsList[i].nPort == toadd->GetUserPort()){ + // already tracked, update + pResult->m_ItemsList[i].pHash = toadd->Credits(); + return; + } + } + PORTANDHASH porthash = { toadd->GetUserPort(), toadd->Credits()}; + pResult->m_ItemsList.Add(porthash); + } + else{ + m_trackedClientsList.SetAt(toadd->GetIP(), new CDeletedClient(toadd)); + } +} + +// true = everything ok, hash didn't changed +// false = hash changed +bool CClientList::ComparePriorUserhash(uint32 dwIP, uint16 nPort, void* pNewHash){ + CDeletedClient* pResult = 0; + if (m_trackedClientsList.Lookup(dwIP, pResult)){ + for (int i = 0; i != pResult->m_ItemsList.GetCount(); i++){ + if (pResult->m_ItemsList[i].nPort == nPort){ + if (pResult->m_ItemsList[i].pHash != pNewHash) + return false; + else + break; + } + } + } + return true; +} + +UINT CClientList::GetClientsFromIP(uint32 dwIP) const +{ + CDeletedClient* pResult; + if (m_trackedClientsList.Lookup(dwIP, pResult)) + return pResult->m_ItemsList.GetCount(); + return 0; +} + +void CClientList::TrackBadRequest(const CUpDownClient* upcClient, int nIncreaseCounter){ + CDeletedClient* pResult = NULL; + if (upcClient->GetIP() == 0){ + ASSERT( false ); + return; + } + if (m_trackedClientsList.Lookup(upcClient->GetIP(), pResult)){ + pResult->m_dwInserted = ::GetTickCount(); + pResult->m_cBadRequest += nIncreaseCounter; + } + else{ + CDeletedClient* ccToAdd = new CDeletedClient(upcClient); + ccToAdd->m_cBadRequest = nIncreaseCounter; + m_trackedClientsList.SetAt(upcClient->GetIP(), ccToAdd); + } +} + +uint32 CClientList::GetBadRequests(const CUpDownClient* upcClient) const{ + CDeletedClient* pResult = NULL; + if (upcClient->GetIP() == 0){ + ASSERT( false ); + return 0; + } + if (m_trackedClientsList.Lookup(upcClient->GetIP(), pResult)){ + return pResult->m_cBadRequest; + } + else + return 0; +} + +void CClientList::RemoveAllTrackedClients(){ + POSITION pos = m_trackedClientsList.GetStartPosition(); + uint32 nKey; + CDeletedClient* pResult; + while (pos != NULL){ + m_trackedClientsList.GetNextAssoc(pos, nKey, pResult); + m_trackedClientsList.RemoveKey(nKey); + delete pResult; + } +} + +void CClientList::Process() +{ + /////////////////////////////////////////////////////////////////////////// + // Cleanup banned client list + // + const uint32 cur_tick = ::GetTickCount(); + if (m_dwLastBannCleanUp + BAN_CLEANUP_TIME < cur_tick) + { + m_dwLastBannCleanUp = cur_tick; + + POSITION pos = m_bannedList.GetStartPosition(); + uint32 nKey; + uint32 dwBantime; + while (pos != NULL) + { + m_bannedList.GetNextAssoc( pos, nKey, dwBantime ); + if (dwBantime + CLIENTBANTIME < cur_tick ) + RemoveBannedClient(nKey); + } + } + + /////////////////////////////////////////////////////////////////////////// + // Cleanup tracked client list + // + if (m_dwLastTrackedCleanUp + TRACKED_CLEANUP_TIME < cur_tick) + { + m_dwLastTrackedCleanUp = cur_tick; + if (thePrefs.GetLogBannedClients()) + AddDebugLogLine(false, _T("Cleaning up TrackedClientList, %i clients on List..."), m_trackedClientsList.GetCount()); + POSITION pos = m_trackedClientsList.GetStartPosition(); + uint32 nKey; + CDeletedClient* pResult; + while (pos != NULL) + { + m_trackedClientsList.GetNextAssoc( pos, nKey, pResult ); + if (pResult->m_dwInserted + KEEPTRACK_TIME < cur_tick ){ + m_trackedClientsList.RemoveKey(nKey); + delete pResult; + } + } + if (thePrefs.GetLogBannedClients()) + AddDebugLogLine(false, _T("...done, %i clients left on list"), m_trackedClientsList.GetCount()); + } + + /////////////////////////////////////////////////////////////////////////// + // Process Kad client list + // + //We need to try to connect to the clients in m_KadList + //If connected, remove them from the list and send a message back to Kad so we can send a ACK. + //If we don't connect, we need to remove the client.. + //The sockets timeout should delete this object. + POSITION pos1, pos2; + + // buddy is just a flag that is used to make sure we are still connected or connecting to a buddy. + buddyState buddy = Disconnected; + + for (pos1 = m_KadList.GetHeadPosition(); (pos2 = pos1) != NULL; ) + { + m_KadList.GetNext(pos1); + CUpDownClient* cur_client = m_KadList.GetAt(pos2); + if( !Kademlia::CKademlia::IsRunning() ) + { + //Clear out this list if we stop running Kad. + //Setting the Kad state to KS_NONE causes it to be removed in the switch below. + cur_client->SetKadState(KS_NONE); + } + switch(cur_client->GetKadState()) + { + case KS_QUEUED_FWCHECK: + case KS_QUEUED_FWCHECK_UDP: + //Another client asked us to try to connect to them to check their firewalled status. + cur_client->TryToConnect(true, true); + break; + case KS_CONNECTING_FWCHECK: + //Ignore this state as we are just waiting for results. + break; + case KS_FWCHECK_UDP: + case KS_CONNECTING_FWCHECK_UDP: + // we want a UDP firewallcheck from this client and are just waiting to get connected to send the request + break; + case KS_CONNECTED_FWCHECK: + //We successfully connected to the client. + //We now send a ack to let them know. + if (cur_client->GetKadVersion() >= KADEMLIA_VERSION7_49a){ + // the result is now sent per TCP instead of UDP, because this will fail if our intern UDP port is unreachable. + // But we want the TCP testresult regardless if UDP is firewalled, the new UDP state and test takes care of the rest + ASSERT( cur_client->socket != NULL && cur_client->socket->IsConnected() ); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP_KAD_FWTCPCHECK_ACK", cur_client); + Packet* pPacket = new Packet(OP_KAD_FWTCPCHECK_ACK, 0, OP_EMULEPROT); + if (!cur_client->SafeConnectAndSendPacket(pPacket)) + cur_client = NULL; + } + else { + if (thePrefs.GetDebugClientKadUDPLevel() > 0) + DebugSend("KADEMLIA_FIREWALLED_ACK_RES", cur_client->GetIP(), cur_client->GetKadPort()); + Kademlia::CKademlia::GetUDPListener()->SendNullPacket(KADEMLIA_FIREWALLED_ACK_RES, ntohl(cur_client->GetIP()), cur_client->GetKadPort(), 0, NULL); + } + //We are done with this client. Set Kad status to KS_NONE and it will be removed in the next cycle. + if (cur_client != NULL) + cur_client->SetKadState(KS_NONE); + break; + + case KS_INCOMING_BUDDY: + //A firewalled client wants us to be his buddy. + //If we already have a buddy, we set Kad state to KS_NONE and it's removed in the next cycle. + //If not, this client will change to KS_CONNECTED_BUDDY when it connects. + if( m_nBuddyStatus == Connected ) + cur_client->SetKadState(KS_NONE); + break; + + case KS_QUEUED_BUDDY: + //We are firewalled and want to request this client to be a buddy. + //But first we check to make sure we are not already trying another client. + //If we are not already trying. We try to connect to this client. + //If we are already connected to a buddy, we set this client to KS_NONE and it's removed next cycle. + //If we are trying to connect to a buddy, we just ignore as the one we are trying may fail and we can then try this one. + if( m_nBuddyStatus == Disconnected ) + { + buddy = Connecting; + m_nBuddyStatus = Connecting; + cur_client->SetKadState(KS_CONNECTING_BUDDY); + cur_client->TryToConnect(true, true); + theApp.emuledlg->serverwnd->UpdateMyInfo(); + } + else if( m_nBuddyStatus == Connected ) + cur_client->SetKadState(KS_NONE); + break; + + case KS_CONNECTING_BUDDY: + //We are trying to connect to this client. + //Although it should NOT happen, we make sure we are not already connected to a buddy. + //If we are we set to KS_NONE and it's removed next cycle. + //But if we are not already connected, make sure we set the flag to connecting so we know + //things are working correctly. + if( m_nBuddyStatus == Connected ) + cur_client->SetKadState(KS_NONE); + else + { + ASSERT( m_nBuddyStatus == Connecting ); + buddy = Connecting; + } + break; + + case KS_CONNECTED_BUDDY: + //A potential connected buddy client wanting to me in the Kad network + //We set our flag to connected to make sure things are still working correctly. + buddy = Connected; + + //If m_nBuddyStatus is not connected already, we set this client as our buddy! + if( m_nBuddyStatus != Connected ) + { + m_pBuddy = cur_client; + m_nBuddyStatus = Connected; + theApp.emuledlg->serverwnd->UpdateMyInfo(); + } + if( m_pBuddy == cur_client && theApp.IsFirewalled() && cur_client->SendBuddyPingPong() ) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__BuddyPing", cur_client); + Packet* buddyPing = new Packet(OP_BUDDYPING, 0, OP_EMULEPROT); + theStats.AddUpDataOverheadOther(buddyPing->size); + VERIFY( cur_client->SendPacket(buddyPing, true, true) ); + cur_client->SetLastBuddyPingPongTime(); + } + break; + + default: + RemoveFromKadList(cur_client); + } + } + + //We either never had a buddy, or lost our buddy.. + if( buddy == Disconnected ) + { + if( m_nBuddyStatus != Disconnected || m_pBuddy ) + { + if( Kademlia::CKademlia::IsRunning() && theApp.IsFirewalled() && Kademlia::CUDPFirewallTester::IsFirewalledUDP(true)) + { + //We are a lowID client and we just lost our buddy. + //Go ahead and instantly try to find a new buddy. + Kademlia::CKademlia::GetPrefs()->SetFindBuddy(); + } + m_pBuddy = NULL; + m_nBuddyStatus = Disconnected; + theApp.emuledlg->serverwnd->UpdateMyInfo(); + } + } + + if ( Kademlia::CKademlia::IsConnected() ) + { + //we only need a buddy if direct callback is not available + if( Kademlia::CKademlia::IsFirewalled() && Kademlia::CUDPFirewallTester::IsFirewalledUDP(true)) + { + //TODO 0.49b: Kad buddies won'T work with RequireCrypt, so it is disabled for now but should (and will) + //be fixed in later version + // Update: Buddy connections itself support obfuscation properly since 0.49a (this makes it work fine if our buddy uses require crypt) + // ,however callback requests don't support it yet so we wouldn't be able to answer callback requests with RequireCrypt, protocolchange intended for the next version + if( m_nBuddyStatus == Disconnected && Kademlia::CKademlia::GetPrefs()->GetFindBuddy() && !thePrefs.IsClientCryptLayerRequired()) + { + DEBUG_ONLY( DebugLog(_T("Starting Buddysearch")) ); + //We are a firewalled client with no buddy. We have also waited a set time + //to try to avoid a false firewalled status.. So lets look for a buddy.. + if( !Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::FINDBUDDY, true, Kademlia::CUInt128(true).Xor(Kademlia::CKademlia::GetPrefs()->GetKadID())) ) + { + //This search ID was already going. Most likely reason is that + //we found and lost our buddy very quickly and the last search hadn't + //had time to be removed yet. Go ahead and set this to happen again + //next time around. + Kademlia::CKademlia::GetPrefs()->SetFindBuddy(); + } + } + } + else + { + if( m_pBuddy ) + { + //Lets make sure that if we have a buddy, they are firewalled! + //If they are also not firewalled, then someone must have fixed their firewall or stopped saturating their line.. + //We just set the state of this buddy to KS_NONE and things will be cleared up with the next cycle. + if( !m_pBuddy->HasLowID() ) + m_pBuddy->SetKadState(KS_NONE); + } + } + } + else + { + if( m_pBuddy ) + { + //We are not connected anymore. Just set this buddy to KS_NONE and things will be cleared out on next cycle. + m_pBuddy->SetKadState(KS_NONE); + } + } + + /////////////////////////////////////////////////////////////////////////// + // Cleanup client list + // + CleanUpClientList(); + + /////////////////////////////////////////////////////////////////////////// + // Process Direct Callbacks for Timeouts + // + ProcessConnectingClientsList(); +} + +#ifdef _DEBUG +void CClientList::Debug_SocketDeleted(CClientReqSocket* deleted) const +{ + for (POSITION pos = list.GetHeadPosition(); pos != NULL;){ + CUpDownClient* cur_client = list.GetNext(pos); + if (!AfxIsValidAddress(cur_client, sizeof(CUpDownClient))) { + AfxDebugBreak(); + } + if (thePrefs.m_iDbgHeap >= 2) + ASSERT_VALID(cur_client); + if (cur_client->socket == deleted){ + AfxDebugBreak(); + } + } +} +#endif + +bool CClientList::IsValidClient(CUpDownClient* tocheck) const +{ + if (thePrefs.m_iDbgHeap >= 2) + ASSERT_VALID(tocheck); + return list.Find(tocheck)!=NULL; +} + + +/////////////////////////////////////////////////////////////////////////////// +// Kad client list + +bool CClientList::RequestTCP(Kademlia::CContact* contact, uint8 byConnectOptions) +{ + uint32 nContactIP = ntohl(contact->GetIPAddress()); + // don't connect ourself + if (theApp.serverconnect->GetLocalIP() == nContactIP && thePrefs.GetPort() == contact->GetTCPPort()) + return false; + + CUpDownClient* pNewClient = FindClientByIP(nContactIP, contact->GetTCPPort()); + + if (!pNewClient) + pNewClient = new CUpDownClient(0, contact->GetTCPPort(), contact->GetIPAddress(), 0, 0, false ); + else if (pNewClient->GetKadState() != KS_NONE) + return false; // already busy with this client in some way (probably buddy stuff), don't mess with it + + //Add client to the lists to be processed. + pNewClient->SetKadPort(contact->GetUDPPort()); + pNewClient->SetKadState(KS_QUEUED_FWCHECK); + if (contact->GetClientID() != 0){ + byte ID[16]; + contact->GetClientID().ToByteArray(ID); + pNewClient->SetUserHash(ID); + pNewClient->SetConnectOptions(byConnectOptions, true, false); + } + m_KadList.AddTail(pNewClient); + //This method checks if this is a dup already. + AddClient(pNewClient); + return true; +} + +void CClientList::RequestBuddy(Kademlia::CContact* contact, uint8 byConnectOptions) +{ + uint32 nContactIP = ntohl(contact->GetIPAddress()); + // don't connect ourself + if (theApp.serverconnect->GetLocalIP() == nContactIP && thePrefs.GetPort() == contact->GetTCPPort()) + return; + CUpDownClient* pNewClient = FindClientByIP(nContactIP, contact->GetTCPPort()); + if (!pNewClient) + pNewClient = new CUpDownClient(0, contact->GetTCPPort(), contact->GetIPAddress(), 0, 0, false ); + else if (pNewClient->GetKadState() != KS_NONE) + return; // already busy with this client in some way (probably fw stuff), don't mess with it + else if (IsKadFirewallCheckIP(nContactIP)){ // doing a kad firewall check with this IP, abort + DEBUG_ONLY( DebugLogWarning(_T("KAD tcp Firewallcheck / Buddy request collosion for IP %s"), ipstr(nContactIP)) ); + return; + } + //Add client to the lists to be processed. + pNewClient->SetKadPort(contact->GetUDPPort()); + pNewClient->SetKadState(KS_QUEUED_BUDDY); + byte ID[16]; + contact->GetClientID().ToByteArray(ID); + pNewClient->SetUserHash(ID); + pNewClient->SetConnectOptions(byConnectOptions, true, false); + AddToKadList(pNewClient); + //This method checks if this is a dup already. + AddClient(pNewClient); +} + +bool CClientList::IncomingBuddy(Kademlia::CContact* contact, Kademlia::CUInt128* buddyID ) +{ + uint32 nContactIP = ntohl(contact->GetIPAddress()); + //If eMule already knows this client, abort this.. It could cause conflicts. + //Although the odds of this happening is very small, it could still happen. + if (FindClientByIP(nContactIP, contact->GetTCPPort())) + return false; + else if (IsKadFirewallCheckIP(nContactIP)){ // doing a kad firewall check with this IP, abort + DEBUG_ONLY( DebugLogWarning(_T("KAD tcp Firewallcheck / Buddy request collosion for IP %s"), ipstr(nContactIP)) ); + return false; + } + else if (theApp.serverconnect->GetLocalIP() == nContactIP && thePrefs.GetPort() == contact->GetTCPPort()) + return false; // don't connect ourself + + //Add client to the lists to be processed. + CUpDownClient* pNewClient = new CUpDownClient(0, contact->GetTCPPort(), contact->GetIPAddress(), 0, 0, false ); + pNewClient->SetKadPort(contact->GetUDPPort()); + pNewClient->SetKadState(KS_INCOMING_BUDDY); + byte ID[16]; + contact->GetClientID().ToByteArray(ID); + pNewClient->SetUserHash(ID); //?? + buddyID->ToByteArray(ID); + pNewClient->SetBuddyID(ID); + AddToKadList(pNewClient); + AddClient(pNewClient); + return true; +} + +void CClientList::RemoveFromKadList(CUpDownClient* torem){ + POSITION pos = m_KadList.Find(torem); + if(pos) + { + if(torem == m_pBuddy) + { + m_pBuddy = NULL; + theApp.emuledlg->serverwnd->UpdateMyInfo(); + } + m_KadList.RemoveAt(pos); + } +} + +void CClientList::AddToKadList(CUpDownClient* toadd){ + if(!toadd) + return; + POSITION pos = m_KadList.Find(toadd); + if(pos) + { + return; + } + m_KadList.AddTail(toadd); +} + +bool CClientList::DoRequestFirewallCheckUDP(const Kademlia::CContact& contact){ + // first make sure we don't know this IP already from somewhere + if (FindClientByIP(ntohl(contact.GetIPAddress())) != NULL) + return false; + // fine, justcreate the client object, set the state and wait + // TODO: We don't know the clients usershash, this means we cannot build an obfuscated connection, which + // again mean that the whole check won't work on "Require Obfuscation" setting, which is not a huge problem, + // but certainly not nice. Only somewhat acceptable way to solve this is to use the KadID instead. + CUpDownClient* pNewClient = new CUpDownClient(0, contact.GetTCPPort(), contact.GetIPAddress(), 0, 0, false ); + pNewClient->SetKadState(KS_QUEUED_FWCHECK_UDP); + DebugLog(_T("Selected client for UDP Firewallcheck: %s"), ipstr(ntohl(contact.GetIPAddress()))); + AddToKadList(pNewClient); + AddClient(pNewClient); + ASSERT( !pNewClient->SupportsDirectUDPCallback() ); + return true; +} + +/*bool CClientList::DebugDoRequestFirewallCheckUDP(uint32 ip, uint16 port){ + // first make sure we don't know this IP already from somewhere + // fine, justcreate the client object, set the state and wait + // TODO: We don't know the clients usershash, this means we cannot build an obfuscated connection, which + // again mean that the whole check won't work on "Require Obfuscation" setting, which is not a huge problem, + // but certainly not nice. Only somewhat acceptable way to solve this is to use the KadID instead. + CUpDownClient* pNewClient = new CUpDownClient(0, port, ip, 0, 0, false ); + pNewClient->SetKadState(KS_QUEUED_FWCHECK_UDP); + DebugLog(_T("Selected client for UDP Firewallcheck: %s"), ipstr(ntohl(ip))); + AddToKadList(pNewClient); + AddClient(pNewClient); + ASSERT( !pNewClient->SupportsDirectUDPCallback() ); + return true; +}*/ + + + +void CClientList::CleanUpClientList(){ + // we remove clients which are not needed any more by time + // this check is also done on CUpDownClient::Disconnected, however it will not catch all + // cases (if a client changes the state without beeing connected + // + // Adding this check directly to every point where any state changes would be more effective, + // is however not compatible with the current code, because there are points where a client has + // no state for some code lines and the code is also not prepared that a client object gets + // invalid while working with it (aka setting a new state) + // so this way is just the easy and safe one to go (as long as emule is basically single threaded) + const uint32 cur_tick = ::GetTickCount(); + if (m_dwLastClientCleanUp + CLIENTLIST_CLEANUP_TIME < cur_tick ){ + m_dwLastClientCleanUp = cur_tick; + POSITION pos1, pos2; + uint32 cDeleted = 0; + for (pos1 = list.GetHeadPosition();( pos2 = pos1 ) != NULL;){ + list.GetNext(pos1); + CUpDownClient* pCurClient = list.GetAt(pos2); + if ((pCurClient->GetUploadState() == US_NONE || pCurClient->GetUploadState() == US_BANNED && !pCurClient->IsBanned()) + && pCurClient->GetDownloadState() == DS_NONE + && pCurClient->GetChatState() == MS_NONE + && pCurClient->GetKadState() == KS_NONE + && pCurClient->socket == NULL) + { + cDeleted++; + delete pCurClient; + } + } + DEBUG_ONLY(AddDebugLogLine(false,_T("Cleaned ClientList, removed %i not used known clients"), cDeleted)); + } +} + + +CDeletedClient::CDeletedClient(const CUpDownClient* pClient) +{ + m_cBadRequest = 0; + m_dwInserted = ::GetTickCount(); + PORTANDHASH porthash = { pClient->GetUserPort(), pClient->Credits()}; + m_ItemsList.Add(porthash); +} + +// ZZ:DownloadManager --> +void CClientList::ProcessA4AFClients() const { + //if(thePrefs.GetLogA4AF()) AddDebugLogLine(false, _T(">>> Starting A4AF check")); + POSITION pos1, pos2; + for (pos1 = list.GetHeadPosition();( pos2 = pos1 ) != NULL;){ + list.GetNext(pos1); + CUpDownClient* cur_client = list.GetAt(pos2); + + if(cur_client->GetDownloadState() != DS_DOWNLOADING && + cur_client->GetDownloadState() != DS_CONNECTED && + (!cur_client->m_OtherRequests_list.IsEmpty() || !cur_client->m_OtherNoNeeded_list.IsEmpty())) { + //AddDebugLogLine(false, _T("+++ ZZ:DownloadManager: Trying for better file for source: %s '%s'"), cur_client->GetUserName(), cur_client->reqfile->GetFileName()); + cur_client->SwapToAnotherFile(_T("Periodic A4AF check CClientList::ProcessA4AFClients()"), false, false, false, NULL, true, false); + } + } + //if(thePrefs.GetLogA4AF()) AddDebugLogLine(false, _T(">>> Done with A4AF check")); +} +// <-- ZZ:DownloadManager + +void CClientList::AddKadFirewallRequest(uint32 dwIP){ + IPANDTICS add = {dwIP, ::GetTickCount()}; + listFirewallCheckRequests.AddHead(add); + while (!listFirewallCheckRequests.IsEmpty()){ + if (::GetTickCount() - listFirewallCheckRequests.GetTail().dwInserted > SEC2MS(180)) + listFirewallCheckRequests.RemoveTail(); + else + break; + } +} + +bool CClientList::IsKadFirewallCheckIP(uint32 dwIP) const{ + for (POSITION pos = listFirewallCheckRequests.GetHeadPosition(); pos != NULL; listFirewallCheckRequests.GetNext(pos)){ + if (listFirewallCheckRequests.GetAt(pos).dwIP == dwIP && ::GetTickCount() - listFirewallCheckRequests.GetAt(pos).dwInserted < SEC2MS(180)) + return true; + } + return false; +} + +void CClientList::AddConnectingClient(CUpDownClient* pToAdd){ + for (POSITION pos = m_liConnectingClients.GetHeadPosition(); pos != NULL; m_liConnectingClients.GetNext(pos)){ + if (m_liConnectingClients.GetAt(pos).pClient == pToAdd){ + ASSERT( false ); + return; + } + } + ASSERT( pToAdd->GetConnectingState() != CCS_NONE ); + CONNECTINGCLIENT cc = {pToAdd, ::GetTickCount()}; + m_liConnectingClients.AddTail(cc); +} + +void CClientList::ProcessConnectingClientsList(){ + // we do check if any connects have timed out by now + const uint32 cur_tick = ::GetTickCount(); + POSITION pos1, pos2; + for (pos1 = m_liConnectingClients.GetHeadPosition();( pos2 = pos1 ) != NULL;){ + m_liConnectingClients.GetNext(pos1); + CONNECTINGCLIENT cc = m_liConnectingClients.GetAt(pos2); + if (cc.dwInserted + SEC2MS(45) < cur_tick) + { + ASSERT( cc.pClient->GetConnectingState() != CCS_NONE ); + m_liConnectingClients.RemoveAt(pos2); + CString dbgInfo; + if (cc.pClient->Disconnected(_T("Connectiontry Timeout"))) + delete cc.pClient; + } + } +} + +void CClientList::RemoveConnectingClient(CUpDownClient* pToRemove){ + for (POSITION pos = m_liConnectingClients.GetHeadPosition(); pos != NULL; m_liConnectingClients.GetNext(pos)){ + if (m_liConnectingClients.GetAt(pos).pClient == pToRemove){ + m_liConnectingClients.RemoveAt(pos); + return; + } + } +} + +void CClientList::AddTrackCallbackRequests(uint32 dwIP){ + IPANDTICS add = {dwIP, ::GetTickCount()}; + listDirectCallbackRequests.AddHead(add); + while (!listDirectCallbackRequests.IsEmpty()){ + if (::GetTickCount() - listDirectCallbackRequests.GetTail().dwInserted > MIN2MS(3)) + listDirectCallbackRequests.RemoveTail(); + else + break; + } +} + +bool CClientList::AllowCalbackRequest(uint32 dwIP) const +{ + for (POSITION pos = listDirectCallbackRequests.GetHeadPosition(); pos != NULL; listDirectCallbackRequests.GetNext(pos)){ + if (listDirectCallbackRequests.GetAt(pos).dwIP == dwIP && ::GetTickCount() - listDirectCallbackRequests.GetAt(pos).dwInserted < MIN2MS(3)) + return false; + } + return true; +} diff --git a/ClientList.h b/ClientList.h new file mode 100644 index 00000000..efa8cc78 --- /dev/null +++ b/ClientList.h @@ -0,0 +1,159 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "DeadSourceList.h" + +class CClientReqSocket; +class CUpDownClient; +namespace Kademlia{ + class CContact; + class CUInt128; +}; +typedef CTypedPtrList CUpDownClientPtrList; + +#define NUM_CLIENTLIST_STATS 19 +#define BAN_CLEANUP_TIME 1200000 // 20 min + +//------------CDeletedClient Class---------------------- +// this class / list is a bit overkill, but currently needed to avoid any exploit possibtility +// it will keep track of certain clients attributes for 2 hours, while the CUpDownClient object might be deleted already +// currently: IP, Port, UserHash +struct PORTANDHASH{ + uint16 nPort; + void* pHash; +}; + +struct IPANDTICS{ + uint32 dwIP; + uint32 dwInserted; +}; +struct CONNECTINGCLIENT{ + CUpDownClient* pClient; + uint32 dwInserted; +}; + + +class CDeletedClient{ +public: + CDeletedClient(const CUpDownClient* pClient); + CArray m_ItemsList; + uint32 m_dwInserted; + uint32 m_cBadRequest; +}; + +enum buddyState +{ + Disconnected, + Connecting, + Connected +}; + +// ----------------------CClientList Class--------------- +class CClientList +{ + friend class CClientListCtrl; + +public: + CClientList(); + ~CClientList(); + + // Clients + void AddClient(CUpDownClient* toadd,bool bSkipDupTest = false); + void RemoveClient(CUpDownClient* toremove, LPCTSTR pszReason = NULL); + void GetStatistics(uint32& totalclient, int stats[NUM_CLIENTLIST_STATS], + CMap& clientVersionEDonkey, + CMap& clientVersionEDonkeyHybrid, + CMap& clientVersionEMule, + CMap& clientVersionAMule); + uint32 GetClientCount() { return list.GetCount();} + void DeleteAll(); + bool AttachToAlreadyKnown(CUpDownClient** client, CClientReqSocket* sender); + CUpDownClient* FindClientByIP(uint32 clientip, UINT port) const; + CUpDownClient* FindClientByUserHash(const uchar* clienthash, uint32 dwIP = 0, uint16 nTCPPort = 0) const; + CUpDownClient* FindClientByIP(uint32 clientip) const; + CUpDownClient* FindClientByIP_UDP(uint32 clientip, UINT nUDPport) const; + CUpDownClient* FindClientByServerID(uint32 uServerIP, uint32 uUserID) const; + CUpDownClient* FindClientByUserID_KadPort(uint32 clientID,uint16 kadPort) const; + CUpDownClient* FindClientByIP_KadPort(uint32 ip, uint16 port) const; + + // Banned clients + void AddBannedClient(uint32 dwIP); + bool IsBannedClient(uint32 dwIP) const; + void RemoveBannedClient(uint32 dwIP); + UINT GetBannedCount() const { return m_bannedList.GetCount(); } + void RemoveAllBannedClients(); + + // Tracked clients + void AddTrackClient(CUpDownClient* toadd); + bool ComparePriorUserhash(uint32 dwIP, uint16 nPort, void* pNewHash); + UINT GetClientsFromIP(uint32 dwIP) const; + void TrackBadRequest(const CUpDownClient* upcClient, int nIncreaseCounter); + uint32 GetBadRequests(const CUpDownClient* upcClient) const; + UINT GetTrackedCount() const { return m_trackedClientsList.GetCount(); } + void RemoveAllTrackedClients(); + + // Kad client list, buddy handling + bool RequestTCP(Kademlia::CContact* contact, uint8 byConnectOptions); + void RequestBuddy(Kademlia::CContact* contact, uint8 byConnectOptions); + bool IncomingBuddy(Kademlia::CContact* contact, Kademlia::CUInt128* buddyID); + void RemoveFromKadList(CUpDownClient* torem); + void AddToKadList(CUpDownClient* toadd); + bool DoRequestFirewallCheckUDP(const Kademlia::CContact& contact); + //bool DebugDoRequestFirewallCheckUDP(uint32 ip, uint16 port); + uint8 GetBuddyStatus() { return m_nBuddyStatus; } + CUpDownClient* GetBuddy() { return m_pBuddy; } + + void AddKadFirewallRequest(uint32 dwIP); + bool IsKadFirewallCheckIP(uint32 dwIP) const; + + // Direct Callback List + void AddTrackCallbackRequests(uint32 dwIP); + bool AllowCalbackRequest(uint32 dwIP) const; + + // Connecting Clients + void AddConnectingClient(CUpDownClient* pToAdd); + void RemoveConnectingClient(CUpDownClient* pToRemove); + + void Process(); + bool IsValidClient(CUpDownClient* tocheck) const; + void Debug_SocketDeleted(CClientReqSocket* deleted) const; + + // ZZ:UploadSpeedSense --> + bool GiveClientsForTraceRoute(); + // ZZ:UploadSpeedSense <-- + + void ProcessA4AFClients() const; // ZZ:DownloadManager + CDeadSourceList m_globDeadSourceList; + +protected: + void CleanUpClientList(); + void ProcessConnectingClientsList(); + +private: + CUpDownClientPtrList list; + CUpDownClientPtrList m_KadList; + CMap m_bannedList; + CMap m_trackedClientsList; + uint32 m_dwLastBannCleanUp; + uint32 m_dwLastTrackedCleanUp; + uint32 m_dwLastClientCleanUp; + CUpDownClient* m_pBuddy; + uint8 m_nBuddyStatus; + CList listFirewallCheckRequests; + CList listDirectCallbackRequests; + CList m_liConnectingClients; +}; diff --git a/ClientListCtrl.cpp b/ClientListCtrl.cpp new file mode 100644 index 00000000..9130b0db --- /dev/null +++ b/ClientListCtrl.cpp @@ -0,0 +1,581 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ClientListCtrl.h" +#include "otherfunctions.h" +#include "MenuCmds.h" +#include "ClientDetailDialog.h" +#include "KademliaWnd.h" +#include "ClientList.h" +#include "emuledlg.h" +#include "FriendList.h" +#include "TransferDlg.h" +#include "MemDC.h" +#include "UpDownClient.h" +#include "ClientCredits.h" +#include "ListenSocket.h" +#include "ChatWnd.h" +#include "Kademlia/Kademlia/Kademlia.h" +#include "Kademlia/net/KademliaUDPListener.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +IMPLEMENT_DYNAMIC(CClientListCtrl, CMuleListCtrl) + +BEGIN_MESSAGE_MAP(CClientListCtrl, CMuleListCtrl) + ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnLvnColumnClick) + ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetDispInfo) + ON_NOTIFY_REFLECT(NM_DBLCLK, OnNmDblClk) + ON_WM_CONTEXTMENU() + ON_WM_SYSCOLORCHANGE() +END_MESSAGE_MAP() + +CClientListCtrl::CClientListCtrl() + : CListCtrlItemWalk(this) +{ + SetGeneralPurposeFind(true); + SetSkinKey(L"ClientsLv"); +} + +void CClientListCtrl::Init() +{ + SetPrefsKey(_T("ClientListCtrl")); + SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + + InsertColumn(0, GetResString(IDS_QL_USERNAME), LVCFMT_LEFT, DFLT_CLIENTNAME_COL_WIDTH); + InsertColumn(1, GetResString(IDS_CL_UPLOADSTATUS), LVCFMT_LEFT, 100); + InsertColumn(2, GetResString(IDS_CL_TRANSFUP), LVCFMT_RIGHT, DFLT_SIZE_COL_WIDTH); + InsertColumn(3, GetResString(IDS_CL_DOWNLSTATUS), LVCFMT_LEFT, 100); + InsertColumn(4, GetResString(IDS_CL_TRANSFDOWN), LVCFMT_RIGHT, DFLT_SIZE_COL_WIDTH); + InsertColumn(5, GetResString(IDS_CD_CSOFT), LVCFMT_LEFT, DFLT_CLIENTSOFT_COL_WIDTH); + InsertColumn(6, GetResString(IDS_CONNECTED), LVCFMT_LEFT, 50); + CString coltemp; + coltemp = GetResString(IDS_CD_UHASH); + coltemp.Remove(_T(':')); + InsertColumn(7, coltemp, LVCFMT_LEFT, DFLT_HASH_COL_WIDTH); + + SetAllIcons(); + Localize(); + LoadSettings(); + SetSortArrow(); + SortItems(SortProc, GetSortItem() + (GetSortAscending() ? 0 : 100)); +} + +void CClientListCtrl::Localize() +{ + CHeaderCtrl *pHeaderCtrl = GetHeaderCtrl(); + HDITEM hdi; + hdi.mask = HDI_TEXT; + + CString strRes; + strRes = GetResString(IDS_QL_USERNAME); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(0, &hdi); + + strRes = GetResString(IDS_CL_UPLOADSTATUS); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(1, &hdi); + + strRes = GetResString(IDS_CL_TRANSFUP); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(2, &hdi); + + strRes = GetResString(IDS_CL_DOWNLSTATUS); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(3, &hdi); + + strRes = GetResString(IDS_CL_TRANSFDOWN); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(4, &hdi); + + strRes = GetResString(IDS_CD_CSOFT); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(5, &hdi); + + strRes = GetResString(IDS_CONNECTED); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(6, &hdi); + + strRes = GetResString(IDS_CD_UHASH); + strRes.Remove(_T(':')); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(7, &hdi); +} + +void CClientListCtrl::OnSysColorChange() +{ + CMuleListCtrl::OnSysColorChange(); + SetAllIcons(); +} + +void CClientListCtrl::SetAllIcons() +{ + ApplyImageList(NULL); + m_ImageList.DeleteImageList(); + m_ImageList.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + m_ImageList.Add(CTempIconLoader(_T("ClientEDonkey"))); + m_ImageList.Add(CTempIconLoader(_T("ClientCompatible"))); + m_ImageList.Add(CTempIconLoader(_T("Friend"))); + m_ImageList.Add(CTempIconLoader(_T("ClientMLDonkey"))); + m_ImageList.Add(CTempIconLoader(_T("ClientEDonkeyHybrid"))); + m_ImageList.Add(CTempIconLoader(_T("ClientShareaza"))); + m_ImageList.Add(CTempIconLoader(_T("Server"))); + m_ImageList.Add(CTempIconLoader(_T("ClientAMule"))); + m_ImageList.Add(CTempIconLoader(_T("ClientLPhant"))); + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("ClientSecureOvl"))), 1); + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("OverlayObfu"))), 2); + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("OverlaySecureObfu"))), 3); + // Apply the image list also to the listview control, even if we use our own 'DrawItem'. + // This is needed to give the listview control a chance to initialize the row height. + ASSERT( (GetStyle() & LVS_SHAREIMAGELISTS) != 0 ); + VERIFY( ApplyImageList(m_ImageList) == NULL ); +} + +void CClientListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (!theApp.emuledlg->IsRunning()) + return; + if (!lpDrawItemStruct->itemData) + return; + + CMemDC dc(CDC::FromHandle(lpDrawItemStruct->hDC), &lpDrawItemStruct->rcItem); + BOOL bCtrlFocused; + InitItemMemDC(dc, lpDrawItemStruct, bCtrlFocused); + CRect cur_rec(lpDrawItemStruct->rcItem); + CRect rcClient; + GetClientRect(&rcClient); + const CUpDownClient *client = (CUpDownClient *)lpDrawItemStruct->itemData; + + CHeaderCtrl *pHeaderCtrl = GetHeaderCtrl(); + int iCount = pHeaderCtrl->GetItemCount(); + cur_rec.right = cur_rec.left - sm_iLabelOffset; + cur_rec.left += sm_iIconOffset; + for (int iCurrent = 0; iCurrent < iCount; iCurrent++) + { + int iColumn = pHeaderCtrl->OrderToIndex(iCurrent); + if (!IsColumnHidden(iColumn)) + { + UINT uDrawTextAlignment; + int iColumnWidth = GetColumnWidth(iColumn, uDrawTextAlignment); + cur_rec.right += iColumnWidth; + if (cur_rec.left < cur_rec.right && HaveIntersection(rcClient, cur_rec)) + { + TCHAR szItem[1024]; + GetItemDisplayText(client, iColumn, szItem, _countof(szItem)); + switch (iColumn) + { + case 0:{ + int iImage; + if (client->IsFriend()) + iImage = 2; + else if (client->GetClientSoft() == SO_EDONKEYHYBRID) + iImage = 4; + else if (client->GetClientSoft() == SO_MLDONKEY) + iImage = 3; + else if (client->GetClientSoft() == SO_SHAREAZA) + iImage = 5; + else if (client->GetClientSoft() == SO_URL) + iImage = 6; + else if (client->GetClientSoft() == SO_AMULE) + iImage = 7; + else if (client->GetClientSoft() == SO_LPHANT) + iImage = 8; + else if (client->ExtProtocolAvailable()) + iImage = 1; + else + iImage = 0; + + UINT nOverlayImage = 0; + if ((client->Credits() && client->Credits()->GetCurrentIdentState(client->GetIP()) == IS_IDENTIFIED)) + nOverlayImage |= 1; + if (client->IsObfuscatedConnectionEstablished()) + nOverlayImage |= 2; + int iIconPosY = (cur_rec.Height() > 16) ? ((cur_rec.Height() - 16) / 2) : 1; + POINT point = { cur_rec.left, cur_rec.top + iIconPosY }; + m_ImageList.Draw(dc, iImage, point, ILD_NORMAL | INDEXTOOVERLAYMASK(nOverlayImage)); + + cur_rec.left += 16 + sm_iLabelOffset; + dc.DrawText(szItem, -1, &cur_rec, MLC_DT_TEXT | uDrawTextAlignment); + cur_rec.left -= 16; + cur_rec.right -= sm_iSubItemInset; + break; + } + + default: + dc.DrawText(szItem, -1, &cur_rec, MLC_DT_TEXT | uDrawTextAlignment); + break; + } + } + cur_rec.left += iColumnWidth; + } + } + + DrawFocusRect(dc, lpDrawItemStruct->rcItem, lpDrawItemStruct->itemState & ODS_FOCUS, bCtrlFocused, lpDrawItemStruct->itemState & ODS_SELECTED); +} + +void CClientListCtrl::GetItemDisplayText(const CUpDownClient *client, int iSubItem, LPTSTR pszText, int cchTextMax) +{ + if (pszText == NULL || cchTextMax <= 0) { + ASSERT(0); + return; + } + pszText[0] = _T('\0'); + switch (iSubItem) + { + case 0: + if (client->GetUserName() == NULL) + _sntprintf(pszText, cchTextMax, _T("(%s)"), GetResString(IDS_UNKNOWN)); + else + _tcsncpy(pszText, client->GetUserName(), cchTextMax); + break; + + case 1: + _tcsncpy(pszText, client->GetUploadStateDisplayString(), cchTextMax); + break; + + case 2: + _tcsncpy(pszText, client->credits != NULL ? CastItoXBytes(client->credits->GetUploadedTotal(), false, false) : _T(""), cchTextMax); + break; + + case 3: + _tcsncpy(pszText, client->GetDownloadStateDisplayString(), cchTextMax); + break; + + case 4: + _tcsncpy(pszText, client->credits != NULL ? CastItoXBytes(client->credits->GetDownloadedTotal(), false, false) : _T(""), cchTextMax); + break; + + case 5: + _tcsncpy(pszText, client->GetClientSoftVer(), cchTextMax); + if (pszText[0] == _T('\0')) + _tcsncpy(pszText, GetResString(IDS_UNKNOWN), cchTextMax); + break; + + case 6: + _tcsncpy(pszText, GetResString((client->socket && client->socket->IsConnected()) ? IDS_YES : IDS_NO), cchTextMax); + break; + + case 7: + _tcsncpy(pszText, md4str(client->GetUserHash()), cchTextMax); + break; + } + pszText[cchTextMax - 1] = _T('\0'); +} + +void CClientListCtrl::OnLvnGetDispInfo(NMHDR *pNMHDR, LRESULT *pResult) +{ + if (theApp.emuledlg->IsRunning()) { + // Although we have an owner drawn listview control we store the text for the primary item in the listview, to be + // capable of quick searching those items via the keyboard. Because our listview items may change their contents, + // we do this via a text callback function. The listview control will send us the LVN_DISPINFO notification if + // it needs to know the contents of the primary item. + // + // But, the listview control sends this notification all the time, even if we do not search for an item. At least + // this notification is only sent for the visible items and not for all items in the list. Though, because this + // function is invoked *very* often, do *NOT* put any time consuming code in here. + // + // Vista: That callback is used to get the strings for the label tips for the sub(!) items. + // + NMLVDISPINFO *pDispInfo = reinterpret_cast(pNMHDR); + if (pDispInfo->item.mask & LVIF_TEXT) { + const CUpDownClient* pClient = reinterpret_cast(pDispInfo->item.lParam); + if (pClient != NULL) + GetItemDisplayText(pClient, pDispInfo->item.iSubItem, pDispInfo->item.pszText, pDispInfo->item.cchTextMax); + } + } + *pResult = 0; +} + +void CClientListCtrl::OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult) +{ + NMLISTVIEW *pNMListView = (NMLISTVIEW *)pNMHDR; + bool sortAscending; + if (GetSortItem() != pNMListView->iSubItem) + { + switch (pNMListView->iSubItem) + { + case 1: // Upload State + case 2: // Uploaded Total + case 4: // Downloaded Total + case 5: // Client Software + case 6: // Connected + sortAscending = false; + break; + default: + sortAscending = true; + break; + } + } + else + sortAscending = !GetSortAscending(); + + // Sort table + UpdateSortHistory(pNMListView->iSubItem + (sortAscending ? 0 : 100)); + SetSortArrow(pNMListView->iSubItem, sortAscending); + SortItems(SortProc, pNMListView->iSubItem + (sortAscending ? 0 : 100)); + + *pResult = 0; +} + +int CClientListCtrl::SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + const CUpDownClient *item1 = (CUpDownClient *)lParam1; + const CUpDownClient *item2 = (CUpDownClient *)lParam2; + int iColumn = (lParamSort >= 100) ? lParamSort - 100 : lParamSort; + int iResult = 0; + switch (iColumn) + { + case 0: + if (item1->GetUserName() && item2->GetUserName()) + iResult = CompareLocaleStringNoCase(item1->GetUserName(), item2->GetUserName()); + else if (item1->GetUserName() == NULL) + iResult = 1; // place clients with no usernames at bottom + else if (item2->GetUserName() == NULL) + iResult = -1; // place clients with no usernames at bottom + break; + + case 1: + iResult = item1->GetUploadState() - item2->GetUploadState(); + break; + + case 2: + if (item1->credits && item2->credits) + iResult = CompareUnsigned64(item1->credits->GetUploadedTotal(), item2->credits->GetUploadedTotal()); + else if (item1->credits) + iResult = 1; + else + iResult = -1; + break; + + case 3: + if (item1->GetDownloadState() == item2->GetDownloadState()) + { + if (item1->IsRemoteQueueFull() && item2->IsRemoteQueueFull()) + iResult = 0; + else if (item1->IsRemoteQueueFull()) + iResult = 1; + else if (item2->IsRemoteQueueFull()) + iResult = -1; + } + else + iResult = item1->GetDownloadState() - item2->GetDownloadState(); + break; + + case 4: + if (item1->credits && item2->credits) + iResult = CompareUnsigned64(item1->credits->GetDownloadedTotal(), item2->credits->GetDownloadedTotal()); + else if (item1->credits) + iResult = 1; + else + iResult = -1; + break; + + case 5: + if (item1->GetClientSoft() == item2->GetClientSoft()) + iResult = item1->GetVersion() - item2->GetVersion(); + else + iResult = -(item1->GetClientSoft() - item2->GetClientSoft()); // invert result to place eMule's at top + break; + + case 6: + if (item1->socket && item2->socket) + iResult = item1->socket->IsConnected() - item2->socket->IsConnected(); + else if (item1->socket) + iResult = 1; + else + iResult = -1; + break; + + case 7: + iResult = memcmp(item1->GetUserHash(), item2->GetUserHash(), 16); + break; + } + + if (lParamSort >= 100) + iResult = -iResult; + + //call secondary sortorder, if this one results in equal + int dwNextSort; + if (iResult == 0 && (dwNextSort = theApp.emuledlg->transferwnd->GetClientList()->GetNextSortOrder(lParamSort)) != -1) + iResult = SortProc(lParam1, lParam2, dwNextSort); + + return iResult; +} + +void CClientListCtrl::OnNmDblClk(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1) { + CUpDownClient* client = (CUpDownClient*)GetItemData(iSel); + if (client){ + CClientDetailDialog dialog(client, this); + dialog.DoModal(); + } + } + *pResult = 0; +} + +void CClientListCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + const CUpDownClient* client = (iSel != -1) ? (CUpDownClient*)GetItemData(iSel) : NULL; + + CTitleMenu ClientMenu; + ClientMenu.CreatePopupMenu(); + ClientMenu.AddMenuTitle(GetResString(IDS_CLIENTS), true); + ClientMenu.AppendMenu(MF_STRING | (client ? MF_ENABLED : MF_GRAYED), MP_DETAIL, GetResString(IDS_SHOWDETAILS), _T("CLIENTDETAILS")); + ClientMenu.SetDefaultItem(MP_DETAIL); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && !client->IsFriend()) ? MF_ENABLED : MF_GRAYED), MP_ADDFRIEND, GetResString(IDS_ADDFRIEND), _T("ADDFRIEND")); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient()) ? MF_ENABLED : MF_GRAYED), MP_MESSAGE, GetResString(IDS_SEND_MSG), _T("SENDMESSAGE")); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && client->GetViewSharedFilesSupport()) ? MF_ENABLED : MF_GRAYED), MP_SHOWLIST, GetResString(IDS_VIEWFILES), _T("VIEWFILES")); + if (Kademlia::CKademlia::IsRunning() && !Kademlia::CKademlia::IsConnected()) + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && client->GetKadPort()!=0 && client->GetKadVersion() > 1) ? MF_ENABLED : MF_GRAYED), MP_BOOT, GetResString(IDS_BOOTSTRAP)); + ClientMenu.AppendMenu(MF_STRING | (GetItemCount() > 0 ? MF_ENABLED : MF_GRAYED), MP_FIND, GetResString(IDS_FIND), _T("Search")); + GetPopupMenuPos(*this, point); + ClientMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); +} + +BOOL CClientListCtrl::OnCommand(WPARAM wParam, LPARAM /*lParam*/) +{ + wParam = LOWORD(wParam); + + switch (wParam) + { + case MP_FIND: + OnFindStart(); + return TRUE; + } + + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1){ + CUpDownClient* client = (CUpDownClient*)GetItemData(iSel); + switch (wParam){ + case MP_SHOWLIST: + client->RequestSharedFileList(); + break; + case MP_MESSAGE: + theApp.emuledlg->chatwnd->StartSession(client); + break; + case MP_ADDFRIEND: + if (theApp.friendlist->AddFriend(client)) + Update(iSel); + break; + case MP_UNBAN: + if (client->IsBanned()){ + client->UnBan(); + Update(iSel); + } + break; + case MP_DETAIL: + case MPG_ALTENTER: + case IDA_ENTER: + { + CClientDetailDialog dialog(client, this); + dialog.DoModal(); + break; + } + case MP_BOOT: + if (client->GetKadPort() && client->GetKadVersion() > 1) + Kademlia::CKademlia::Bootstrap(ntohl(client->GetIP()), client->GetKadPort()); + break; + } + } + return true; +} + +void CClientListCtrl::AddClient(const CUpDownClient *client) +{ + if (!theApp.emuledlg->IsRunning()) + return; + if (thePrefs.IsKnownClientListDisabled()) + return; + + int iItemCount = GetItemCount(); + InsertItem(LVIF_TEXT | LVIF_PARAM, iItemCount, LPSTR_TEXTCALLBACK, 0, 0, 0, (LPARAM)client); + theApp.emuledlg->transferwnd->UpdateListCount(CTransferDlg::wnd2Clients, iItemCount + 1); +} + +void CClientListCtrl::RemoveClient(const CUpDownClient *client) +{ + if (!theApp.emuledlg->IsRunning()) + return; + + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)client; + int result = FindItem(&find); + if (result != -1) { + DeleteItem(result); + theApp.emuledlg->transferwnd->UpdateListCount(CTransferDlg::wnd2Clients); + } +} + +void CClientListCtrl::RefreshClient(const CUpDownClient *client) +{ + if (!theApp.emuledlg->IsRunning()) + return; + + if (theApp.emuledlg->activewnd != theApp.emuledlg->transferwnd || !theApp.emuledlg->transferwnd->GetClientList()->IsWindowVisible()) + return; + + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)client; + int result = FindItem(&find); + if (result != -1) + Update(result); +} + +void CClientListCtrl::ShowSelectedUserDetails() +{ + POINT point; + ::GetCursorPos(&point); + CPoint p = point; + ScreenToClient(&p); + int it = HitTest(p); + if (it == -1) + return; + + SetItemState(-1, 0, LVIS_SELECTED); + SetItemState(it, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + SetSelectionMark(it); // display selection mark correctly! + + CUpDownClient* client = (CUpDownClient*)GetItemData(GetSelectionMark()); + if (client){ + CClientDetailDialog dialog(client, this); + dialog.DoModal(); + } +} + +void CClientListCtrl::ShowKnownClients() +{ + DeleteAllItems(); + int iItemCount = 0; + for (POSITION pos = theApp.clientlist->list.GetHeadPosition(); pos != NULL; ) { + const CUpDownClient *cur_client = theApp.clientlist->list.GetNext(pos); + int iItem = InsertItem(LVIF_TEXT | LVIF_PARAM, iItemCount, LPSTR_TEXTCALLBACK, 0, 0, 0, (LPARAM)cur_client); + Update(iItem); + iItemCount++; + } + theApp.emuledlg->transferwnd->UpdateListCount(CTransferDlg::wnd2Clients, iItemCount); +} diff --git a/ClientListCtrl.h b/ClientListCtrl.h new file mode 100644 index 00000000..9d67ec2e --- /dev/null +++ b/ClientListCtrl.h @@ -0,0 +1,56 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "MuleListCtrl.h" +#include "ListCtrlItemWalk.h" + +class CUpDownClient; + +class CClientListCtrl : public CMuleListCtrl, public CListCtrlItemWalk +{ + DECLARE_DYNAMIC(CClientListCtrl) + +public: + CClientListCtrl(); + + void Init(); + void AddClient(const CUpDownClient *client); + void RemoveClient(const CUpDownClient *client); + void RefreshClient(const CUpDownClient *client); + void Hide() { ShowWindow(SW_HIDE); } + void Show() { ShowWindow(SW_SHOW); } + void Localize(); + void ShowSelectedUserDetails(); + void ShowKnownClients(); + +protected: + CImageList m_ImageList; + + void SetAllIcons(); + void GetItemDisplayText(const CUpDownClient *pClient, int iSubItem, LPTSTR pszText, int cchTextMax); + static int CALLBACK SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + + DECLARE_MESSAGE_MAP() + afx_msg void OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnGetDispInfo(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); + afx_msg void OnNmDblClk(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnSysColorChange(); +}; diff --git a/ClientStateDefs.h b/ClientStateDefs.h new file mode 100644 index 00000000..b87d3d83 --- /dev/null +++ b/ClientStateDefs.h @@ -0,0 +1,156 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +enum EUploadState{ + US_UPLOADING, + US_ONUPLOADQUEUE, + US_CONNECTING, + US_BANNED, + US_NONE +}; + +enum EDownloadState{ + DS_DOWNLOADING, + DS_ONQUEUE, + DS_CONNECTED, + DS_CONNECTING, + DS_WAITCALLBACK, + DS_WAITCALLBACKKAD, + DS_REQHASHSET, + DS_NONEEDEDPARTS, + DS_TOOMANYCONNS, + DS_TOOMANYCONNSKAD, + DS_LOWTOLOWIP, + DS_BANNED, + DS_ERROR, + DS_NONE, + DS_REMOTEQUEUEFULL // not used yet, except in statistics +}; + +enum EPeerCacheDownState{ + PCDS_NONE = 0, + PCDS_WAIT_CLIENT_REPLY, + PCDS_WAIT_CACHE_REPLY, + PCDS_DOWNLOADING +}; + +enum EPeerCacheUpState{ + PCUS_NONE = 0, + PCUS_WAIT_CACHE_REPLY, + PCUS_UPLOADING +}; + +enum EChatState{ + MS_NONE, + MS_CHATTING, + MS_CONNECTING, + MS_UNABLETOCONNECT +}; + +enum EKadState{ + KS_NONE, + KS_QUEUED_FWCHECK, + KS_CONNECTING_FWCHECK, + KS_CONNECTED_FWCHECK, + KS_QUEUED_BUDDY, + KS_INCOMING_BUDDY, + KS_CONNECTING_BUDDY, + KS_CONNECTED_BUDDY, + KS_QUEUED_FWCHECK_UDP, + KS_FWCHECK_UDP, + KS_CONNECTING_FWCHECK_UDP +}; + +enum EClientSoftware{ + SO_EMULE = 0, // default + SO_CDONKEY = 1, // ET_COMPATIBLECLIENT + SO_XMULE = 2, // ET_COMPATIBLECLIENT + SO_AMULE = 3, // ET_COMPATIBLECLIENT + SO_SHAREAZA = 4, // ET_COMPATIBLECLIENT + SO_MLDONKEY = 10, // ET_COMPATIBLECLIENT + SO_LPHANT = 20, // ET_COMPATIBLECLIENT + // other client types which are not identified with ET_COMPATIBLECLIENT + SO_EDONKEYHYBRID = 50, + SO_EDONKEY, + SO_OLDEMULE, + SO_URL, + SO_UNKNOWN +}; + +enum ESecureIdentState{ + IS_UNAVAILABLE = 0, + IS_ALLREQUESTSSEND = 0, + IS_SIGNATURENEEDED = 1, + IS_KEYANDSIGNEEDED = 2, +}; + +enum EInfoPacketState{ + IP_NONE = 0, + IP_EDONKEYPROTPACK = 1, + IP_EMULEPROTPACK = 2, + IP_BOTH = 3, +}; + +enum ESourceFrom{ + SF_SERVER = 0, + SF_KADEMLIA = 1, + SF_SOURCE_EXCHANGE = 2, + SF_PASSIVE = 3, + SF_LINK = 4 +}; + +enum EChatCaptchaState{ + CA_NONE = 0, + CA_CHALLENGESENT, + CA_CAPTCHASOLVED, + CA_ACCEPTING, + CA_CAPTCHARECV, + CA_SOLUTIONSENT +}; + +enum EConnectingState{ + CCS_NONE = 0, + CCS_DIRECTTCP, + CCS_DIRECTCALLBACK, + CCS_KADCALLBACK, + CCS_SERVERCALLBACK, + CCS_PRECONDITIONS +}; + +#ifdef _DEBUG + // use the 'Enums' only for debug builds, each enum costs 4 bytes (3 unused) +#define _EClientSoftware EClientSoftware +#define _EChatState EChatState +#define _EKadState EKadState +#define _ESecureIdentState ESecureIdentState +#define _EUploadState EUploadState +#define _EDownloadState EDownloadState +#define _ESourceFrom ESourceFrom +#define _EChatCaptchaState EChatCaptchaState +#define _EConnectingState EConnectingState +#else +#define _EClientSoftware uint8 +#define _EChatState uint8 +#define _EKadState uint8 +#define _ESecureIdentState uint8 +#define _EUploadState uint8 +#define _EDownloadState uint8 +#define _ESourceFrom uint8 +#define _EChatCaptchaState uint8 +#define _EConnectingState uint8 +#endif \ No newline at end of file diff --git a/ClientUDPSocket.cpp b/ClientUDPSocket.cpp new file mode 100644 index 00000000..51effa82 --- /dev/null +++ b/ClientUDPSocket.cpp @@ -0,0 +1,624 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ClientUDPSocket.h" +#include "Packets.h" +#include "DownloadQueue.h" +#include "Statistics.h" +#include "PartFile.h" +#include "SharedFileList.h" +#include "UploadQueue.h" +#include "UpDownClient.h" +#include "Preferences.h" +#include "OtherFunctions.h" +#include "SafeFile.h" +#include "ClientList.h" +#include "Listensocket.h" +#include +#include "kademlia/kademlia/Kademlia.h" +#include "kademlia/kademlia/UDPFirewallTester.h" +#include "kademlia/net/KademliaUDPListener.h" +#include "kademlia/io/IOException.h" +#include "IPFilter.h" +#include "Log.h" +#include "EncryptedDatagramSocket.h" +#include "./kademlia/kademlia/prefs.h" +#include "./kademlia/utils/KadUDPKey.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// CClientUDPSocket + +CClientUDPSocket::CClientUDPSocket() +{ + m_bWouldBlock = false; + m_port=0; +} + +CClientUDPSocket::~CClientUDPSocket() +{ + theApp.uploadBandwidthThrottler->RemoveFromAllQueues(this); // ZZ:UploadBandWithThrottler (UDP) + + POSITION pos = controlpacket_queue.GetHeadPosition(); + while (pos){ + UDPPack* p = controlpacket_queue.GetNext(pos); + delete p->packet; + delete p; + } +} + +void CClientUDPSocket::OnReceive(int nErrorCode) +{ + if (nErrorCode) + { + if (thePrefs.GetVerbose()) + DebugLogError(_T("Error: Client UDP socket, error on receive event: %s"), GetErrorMessage(nErrorCode, 1)); + } + + BYTE buffer[5000]; + SOCKADDR_IN sockAddr = {0}; + int iSockAddrLen = sizeof sockAddr; + int nRealLen = ReceiveFrom(buffer, sizeof buffer, (SOCKADDR*)&sockAddr, &iSockAddrLen); + if (!(theApp.ipfilter->IsFiltered(sockAddr.sin_addr.S_un.S_addr) || theApp.clientlist->IsBannedClient(sockAddr.sin_addr.S_un.S_addr))) + { + BYTE* pBuffer; + uint32 nReceiverVerifyKey; + uint32 nSenderVerifyKey; + int nPacketLen = DecryptReceivedClient(buffer, nRealLen, &pBuffer, sockAddr.sin_addr.S_un.S_addr, &nReceiverVerifyKey, &nSenderVerifyKey); + if (nPacketLen >= 1) + { + CString strError; + try + { + switch (pBuffer[0]) + { + case OP_EMULEPROT: + { + if (nPacketLen >= 2) + ProcessPacket(pBuffer+2, nPacketLen-2, pBuffer[1], sockAddr.sin_addr.S_un.S_addr, ntohs(sockAddr.sin_port)); + else + throw CString(_T("eMule packet too short")); + break; + } + case OP_KADEMLIAPACKEDPROT: + { + theStats.AddDownDataOverheadKad(nPacketLen); + if (nPacketLen >= 2) + { + uint32 nNewSize = nPacketLen*10+300; + BYTE* unpack = NULL; + uLongf unpackedsize = 0; + int iZLibResult = 0; + do { + delete[] unpack; + unpack = new BYTE[nNewSize]; + unpackedsize = nNewSize-2; + iZLibResult = uncompress(unpack+2, &unpackedsize, pBuffer+2, nPacketLen-2); + nNewSize *= 2; // size for the next try if needed + } while (iZLibResult == Z_BUF_ERROR && nNewSize < 250000); + + if (iZLibResult == Z_OK) + { + unpack[0] = OP_KADEMLIAHEADER; + unpack[1] = pBuffer[1]; + try + { + Kademlia::CKademlia::ProcessPacket(unpack, unpackedsize+2, ntohl(sockAddr.sin_addr.S_un.S_addr), ntohs(sockAddr.sin_port) + , (Kademlia::CPrefs::GetUDPVerifyKey(sockAddr.sin_addr.S_un.S_addr) == nReceiverVerifyKey) + , Kademlia::CKadUDPKey(nSenderVerifyKey, theApp.GetPublicIP(false)) ); + } + catch(...) + { + delete[] unpack; + throw; + } + } + else + { + delete[] unpack; + CString strError; + strError.Format(_T("Failed to uncompress Kad packet: zip error: %d (%hs)"), iZLibResult, zError(iZLibResult)); + throw strError; + } + delete[] unpack; + } + else + throw CString(_T("Kad packet (compressed) too short")); + break; + } + case OP_KADEMLIAHEADER: + { + theStats.AddDownDataOverheadKad(nPacketLen); + if (nPacketLen >= 2) + Kademlia::CKademlia::ProcessPacket(pBuffer, nPacketLen, ntohl(sockAddr.sin_addr.S_un.S_addr), ntohs(sockAddr.sin_port) + , (Kademlia::CPrefs::GetUDPVerifyKey(sockAddr.sin_addr.S_un.S_addr) == nReceiverVerifyKey) + , Kademlia::CKadUDPKey(nSenderVerifyKey, theApp.GetPublicIP(false)) ); + else + throw CString(_T("Kad packet too short")); + break; + } + default: + { + CString strError; + strError.Format(_T("Unknown protocol 0x%02x"), pBuffer[0]); + throw strError; + } + } + } + catch(CFileException* error) + { + error->Delete(); + strError = _T("Invalid packet received"); + } + catch(CMemoryException* error) + { + error->Delete(); + strError = _T("Memory exception"); + } + catch(CString error) + { + strError = error; + } + catch(Kademlia::CIOException* error) + { + error->Delete(); + strError = _T("Invalid packet received"); + } + catch(CException* error) + { + error->Delete(); + strError = _T("General packet error"); + } + #ifndef _DEBUG + catch(...) + { + strError = _T("Unknown exception"); + ASSERT(0); + } + #endif + if (thePrefs.GetVerbose() && !strError.IsEmpty()) + { + CString strClientInfo; + CUpDownClient* client; + if (pBuffer[0] == OP_EMULEPROT) + client = theApp.clientlist->FindClientByIP_UDP(sockAddr.sin_addr.S_un.S_addr, ntohs(sockAddr.sin_port)); + else + client = theApp.clientlist->FindClientByIP_KadPort(sockAddr.sin_addr.S_un.S_addr, ntohs(sockAddr.sin_port)); + if (client) + strClientInfo = client->DbgGetClientInfo(); + else + strClientInfo.Format(_T("%s:%u"), ipstr(sockAddr.sin_addr), ntohs(sockAddr.sin_port)); + + DebugLogWarning(_T("Client UDP socket: prot=0x%02x opcode=0x%02x sizeaftercrypt=%u realsize=%u %s: %s"), pBuffer[0], pBuffer[1], nPacketLen, nRealLen, strError, strClientInfo); + } + } + else if (nPacketLen == SOCKET_ERROR) + { + DWORD dwError = WSAGetLastError(); + if (dwError == WSAECONNRESET) + { + // Depending on local and remote OS and depending on used local (remote?) router we may receive + // WSAECONNRESET errors. According some KB articles, this is a special way of winsock to report + // that a sent UDP packet was not received by the remote host because it was not listening on + // the specified port -> no eMule running there. + // + // TODO: So, actually we should do something with this information and drop the related Kad node + // or eMule client... + ; + } + if (thePrefs.GetVerbose() && dwError != WSAECONNRESET) + { + CString strClientInfo; + if (iSockAddrLen > 0 && sockAddr.sin_addr.S_un.S_addr != 0 && sockAddr.sin_addr.S_un.S_addr != INADDR_NONE) + strClientInfo.Format(_T(" from %s:%u"), ipstr(sockAddr.sin_addr), ntohs(sockAddr.sin_port)); + DebugLogError(_T("Error: Client UDP socket, failed to receive data%s: %s"), strClientInfo, GetErrorMessage(dwError, 1)); + } + } + } +} + +bool CClientUDPSocket::ProcessPacket(const BYTE* packet, UINT size, uint8 opcode, uint32 ip, uint16 port) +{ + switch(opcode) + { + case OP_REASKCALLBACKUDP: + { + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugRecv("OP_ReaskCallbackUDP", NULL, NULL, ip); + theStats.AddDownDataOverheadOther(size); + CUpDownClient* buddy = theApp.clientlist->GetBuddy(); + if( buddy ) + { + if( size < 17 || buddy->socket == NULL ) + break; + if (!md4cmp(packet, buddy->GetBuddyID())) + { + PokeUInt32(const_cast(packet)+10, ip); + PokeUInt16(const_cast(packet)+14, port); + Packet* response = new Packet(OP_EMULEPROT); + response->opcode = OP_REASKCALLBACKTCP; + response->pBuffer = new char[size]; + memcpy(response->pBuffer, packet+10, size-10); + response->size = size-10; + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__ReaskCallbackTCP", buddy); + theStats.AddUpDataOverheadFileRequest(response->size); + buddy->SendPacket(response, true); + } + } + break; + } + case OP_REASKFILEPING: + { + theStats.AddDownDataOverheadFileRequest(size); + CSafeMemFile data_in(packet, size); + uchar reqfilehash[16]; + data_in.ReadHash16(reqfilehash); + CKnownFile* reqfile = theApp.sharedfiles->GetFileByID(reqfilehash); + + bool bSenderMultipleIpUnknown = false; + CUpDownClient* sender = theApp.uploadqueue->GetWaitingClientByIP_UDP(ip, port, true, &bSenderMultipleIpUnknown); + if (!reqfile) + { + if (thePrefs.GetDebugClientUDPLevel() > 0) { + DebugRecv("OP_ReaskFilePing", NULL, reqfilehash, ip); + DebugSend("OP__FileNotFound", NULL); + } + + Packet* response = new Packet(OP_FILENOTFOUND,0,OP_EMULEPROT); + theStats.AddUpDataOverheadFileRequest(response->size); + if (sender != NULL) + SendPacket(response, ip, port, sender->ShouldReceiveCryptUDPPackets(), sender->GetUserHash(), false, 0); + else + SendPacket(response, ip, port, false, NULL, false, 0); + break; + } + if (sender) + { + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugRecv("OP_ReaskFilePing", sender, reqfilehash); + + //Make sure we are still thinking about the same file + if (md4cmp(reqfilehash, sender->GetUploadFileID()) == 0) + { + sender->AddAskedCount(); + sender->SetLastUpRequest(); + //I messed up when I first added extended info to UDP + //I should have originally used the entire ProcessExtenedInfo the first time. + //So now I am forced to check UDPVersion to see if we are sending all the extended info. + //For now on, we should not have to change anything here if we change + //anything to the extended info data as this will be taken care of in ProcessExtendedInfo() + //Update extended info. + if (sender->GetUDPVersion() > 3) + { + sender->ProcessExtendedInfo(&data_in, reqfile); + } + //Update our complete source counts. + else if (sender->GetUDPVersion() > 2) + { + uint16 nCompleteCountLast= sender->GetUpCompleteSourcesCount(); + uint16 nCompleteCountNew = data_in.ReadUInt16(); + sender->SetUpCompleteSourcesCount(nCompleteCountNew); + if (nCompleteCountLast != nCompleteCountNew) + { + reqfile->UpdatePartsInfo(); + } + } + CSafeMemFile data_out(128); + if(sender->GetUDPVersion() > 3) + { + if (reqfile->IsPartFile()) + ((CPartFile*)reqfile)->WritePartStatus(&data_out); + else + data_out.WriteUInt16(0); + } + data_out.WriteUInt16((uint16)(theApp.uploadqueue->GetWaitingPosition(sender))); + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugSend("OP__ReaskAck", sender); + Packet* response = new Packet(&data_out, OP_EMULEPROT); + response->opcode = OP_REASKACK; + theStats.AddUpDataOverheadFileRequest(response->size); + SendPacket(response, ip, port, sender->ShouldReceiveCryptUDPPackets(), sender->GetUserHash(), false, 0); + } + else + { + DebugLogError(_T("Client UDP socket; ReaskFilePing; reqfile does not match")); + TRACE(_T("reqfile: %s\n"), DbgGetFileInfo(reqfile->GetFileHash())); + TRACE(_T("sender->GetRequestFile(): %s\n"), sender->GetRequestFile() ? DbgGetFileInfo(sender->GetRequestFile()->GetFileHash()) : _T("(null)")); + } + } + else + { + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugRecv("OP_ReaskFilePing", NULL, reqfilehash, ip); + // Don't answer him. We probably have him on our queue already, but can't locate him. Force him to establish a TCP connection + if (!bSenderMultipleIpUnknown){ + if (((uint32)theApp.uploadqueue->GetWaitingUserCount() + 50) > thePrefs.GetQueueSize()) + { + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugSend("OP__QueueFull", NULL); + Packet* response = new Packet(OP_QUEUEFULL,0,OP_EMULEPROT); + theStats.AddUpDataOverheadFileRequest(response->size); + SendPacket(response, ip, port, false, NULL, false, 0); // we cannot answer this one encrypted since we dont know this client + } + } + else{ + DebugLogWarning(_T("UDP Packet received - multiple clients with the same IP but different UDP port found. Possible UDP Portmapping problem, enforcing TCP connection. IP: %s, Port: %u"), ipstr(ip), port); + } + } + break; + } + case OP_QUEUEFULL: + { + theStats.AddDownDataOverheadFileRequest(size); + CUpDownClient* sender = theApp.downloadqueue->GetDownloadClientByIP_UDP(ip, port, true); + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugRecv("OP_QueueFull", sender, NULL, ip); + if (sender && sender->UDPPacketPending()){ + sender->SetRemoteQueueFull(true); + sender->UDPReaskACK(0); + } + else if (sender != NULL) + DebugLogError(_T("Received UDP Packet (OP_QUEUEFULL) which was not requested (pendingflag == false); Ignored packet - %s"), sender->DbgGetClientInfo()); + break; + } + case OP_REASKACK: + { + theStats.AddDownDataOverheadFileRequest(size); + CUpDownClient* sender = theApp.downloadqueue->GetDownloadClientByIP_UDP(ip, port, true); + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugRecv("OP_ReaskAck", sender, NULL, ip); + if (sender && sender->UDPPacketPending()){ + CSafeMemFile data_in(packet, size); + if ( sender->GetUDPVersion() > 3 ) + { + sender->ProcessFileStatus(true, &data_in, sender->GetRequestFile()); + } + uint16 nRank = data_in.ReadUInt16(); + sender->SetRemoteQueueFull(false); + sender->UDPReaskACK(nRank); + sender->AddAskedCountDown(); + } + else if (sender != NULL) + DebugLogError(_T("Received UDP Packet (OP_REASKACK) which was not requested (pendingflag == false); Ignored packet - %s"), sender->DbgGetClientInfo()); + + break; + } + case OP_FILENOTFOUND: + { + theStats.AddDownDataOverheadFileRequest(size); + CUpDownClient* sender = theApp.downloadqueue->GetDownloadClientByIP_UDP(ip, port, true); + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugRecv("OP_FileNotFound", sender, NULL, ip); + if (sender && sender->UDPPacketPending()){ + sender->UDPReaskFNF(); // may delete 'sender'! + sender = NULL; + } + else if (sender != NULL) + DebugLogError(_T("Received UDP Packet (OP_FILENOTFOUND) which was not requested (pendingflag == false); Ignored packet - %s"), sender->DbgGetClientInfo()); + + break; + } + case OP_PORTTEST: + { + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugRecv("OP_PortTest", NULL, NULL, ip); + theStats.AddDownDataOverheadOther(size); + if (size == 1){ + if (packet[0] == 0x12){ + bool ret = theApp.listensocket->SendPortTestReply('1', true); + AddDebugLogLine(true, _T("UDP Portcheck packet arrived - ACK sent back (status=%i)"), ret); + } + } + break; + } + case OP_DIRECTCALLBACKREQ: + { + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugRecv("OP_DIRECTCALLBACKREQ", NULL, NULL, ip); + if (!theApp.clientlist->AllowCalbackRequest(ip)){ + DebugLogWarning(_T("Ignored DirectCallback Request because this IP (%s) has sent too many request within a short time"), ipstr(ip)); + break; + } + // do we accept callbackrequests at all? + if (Kademlia::CKademlia::IsRunning() && Kademlia::CKademlia::IsFirewalled()) + { + theApp.clientlist->AddTrackCallbackRequests(ip); + CSafeMemFile data(packet, size); + uint16 nRemoteTCPPort = data.ReadUInt16(); + uchar uchUserHash[16]; + data.ReadHash16(uchUserHash); + uint8 byConnectOptions = data.ReadUInt8(); + CUpDownClient* pRequester = theApp.clientlist->FindClientByUserHash(uchUserHash, ip, nRemoteTCPPort); + if (pRequester == NULL) { + pRequester = new CUpDownClient(NULL, nRemoteTCPPort, ip, 0, 0, true); + pRequester->SetUserHash(uchUserHash); + theApp.clientlist->AddClient(pRequester); + } + pRequester->SetConnectOptions(byConnectOptions, true, false); + pRequester->SetDirectUDPCallbackSupport(false); + pRequester->SetIP(ip); + pRequester->SetUserPort(nRemoteTCPPort); + DEBUG_ONLY( DebugLog(_T("Accepting incoming DirectCallbackRequest from %s"), pRequester->DbgGetClientInfo()) ); + pRequester->TryToConnect(); + } + else + DebugLogWarning(_T("Ignored DirectCallback Request because we do not accept DirectCall backs at all (%s)"), ipstr(ip)); + + break; + } + default: + theStats.AddDownDataOverheadOther(size); + if (thePrefs.GetDebugClientUDPLevel() > 0) + { + CUpDownClient* sender = theApp.downloadqueue->GetDownloadClientByIP_UDP(ip, port, true); + Debug(_T("Unknown client UDP packet: host=%s:%u (%s) opcode=0x%02x size=%u\n"), ipstr(ip), port, sender ? sender->DbgGetClientInfo() : _T(""), opcode, size); + } + return false; + } + return true; +} + +void CClientUDPSocket::OnSend(int nErrorCode){ + if (nErrorCode){ + if (thePrefs.GetVerbose()) + DebugLogError(_T("Error: Client UDP socket, error on send event: %s"), GetErrorMessage(nErrorCode, 1)); + return; + } + +// ZZ:UploadBandWithThrottler (UDP) --> + sendLocker.Lock(); + m_bWouldBlock = false; + + if(!controlpacket_queue.IsEmpty()) { + theApp.uploadBandwidthThrottler->QueueForSendingControlPacket(this); + } + sendLocker.Unlock(); +// <-- ZZ:UploadBandWithThrottler (UDP) +} + +SocketSentBytes CClientUDPSocket::SendControlData(uint32 maxNumberOfBytesToSend, uint32 /*minFragSize*/){ // ZZ:UploadBandWithThrottler (UDP) +// ZZ:UploadBandWithThrottler (UDP) --> + // NOTE: *** This function is invoked from a *different* thread! + sendLocker.Lock(); + + uint32 sentBytes = 0; +// <-- ZZ:UploadBandWithThrottler (UDP) + + while (!controlpacket_queue.IsEmpty() && !IsBusy() && sentBytes < maxNumberOfBytesToSend){ // ZZ:UploadBandWithThrottler (UDP) + UDPPack* cur_packet = controlpacket_queue.GetHead(); + if( GetTickCount() - cur_packet->dwTime < UDPMAXQUEUETIME ) + { + uint32 nLen = cur_packet->packet->size+2; + uchar* sendbuffer = new uchar[nLen]; + memcpy(sendbuffer,cur_packet->packet->GetUDPHeader(),2); + memcpy(sendbuffer+2,cur_packet->packet->pBuffer,cur_packet->packet->size); + + if (cur_packet->bEncrypt && (theApp.GetPublicIP() > 0 || cur_packet->bKad)){ + nLen = EncryptSendClient(&sendbuffer, nLen, cur_packet->pachTargetClientHashORKadID, cur_packet->bKad, cur_packet->nReceiverVerifyKey, (cur_packet->bKad ? Kademlia::CPrefs::GetUDPVerifyKey(cur_packet->dwIP) : (uint16)0)); + //DEBUG_ONLY( AddDebugLogLine(DLP_VERYLOW, false, _T("Sent obfuscated UDP packet to clientIP: %s, Kad: %s, ReceiverKey: %u"), ipstr(cur_packet->dwIP), cur_packet->bKad ? _T("Yes") : _T("No"), cur_packet->nReceiverVerifyKey) ); + } + + if (!SendTo((char*)sendbuffer, nLen, cur_packet->dwIP, cur_packet->nPort)){ + sentBytes += nLen; // ZZ:UploadBandWithThrottler (UDP) + + controlpacket_queue.RemoveHead(); + delete cur_packet->packet; + delete cur_packet; + } + delete[] sendbuffer; + } + else + { + controlpacket_queue.RemoveHead(); + delete cur_packet->packet; + delete cur_packet; + } + } + +// ZZ:UploadBandWithThrottler (UDP) --> + if(!IsBusy() && !controlpacket_queue.IsEmpty()) { + theApp.uploadBandwidthThrottler->QueueForSendingControlPacket(this); + } + sendLocker.Unlock(); + + SocketSentBytes returnVal = { true, 0, sentBytes }; + return returnVal; +// <-- ZZ:UploadBandWithThrottler (UDP) +} + +int CClientUDPSocket::SendTo(char* lpBuf,int nBufLen,uint32 dwIP, uint16 nPort){ + // NOTE: *** This function is invoked from a *different* thread! + uint32 result = CAsyncSocket::SendTo(lpBuf,nBufLen,nPort,ipstr(dwIP)); + if (result == (uint32)SOCKET_ERROR){ + uint32 error = GetLastError(); + if (error == WSAEWOULDBLOCK){ + m_bWouldBlock = true; + return -1; + } + if (thePrefs.GetVerbose()) + DebugLogError(_T("Error: Client UDP socket, failed to send data to %s:%u: %s"), ipstr(dwIP), nPort, GetErrorMessage(error, 1)); + } + return 0; +} + +bool CClientUDPSocket::SendPacket(Packet* packet, uint32 dwIP, uint16 nPort, bool bEncrypt, const uchar* pachTargetClientHashORKadID, bool bKad, uint32 nReceiverVerifyKey){ + UDPPack* newpending = new UDPPack; + newpending->dwIP = dwIP; + newpending->nPort = nPort; + newpending->packet = packet; + newpending->dwTime = GetTickCount(); + newpending->bEncrypt = bEncrypt && (pachTargetClientHashORKadID != NULL || (bKad && nReceiverVerifyKey != 0)); + newpending->bKad = bKad; + newpending->nReceiverVerifyKey = nReceiverVerifyKey; + +#ifdef _DEBUG + if (newpending->packet->size > UDP_KAD_MAXFRAGMENT) + DebugLogWarning(_T("Sending UDP packet > UDP_KAD_MAXFRAGMENT, opcode: %X, size: %u"), packet->opcode, packet->size); +#endif + + if (newpending->bEncrypt && pachTargetClientHashORKadID != NULL) + md4cpy(newpending->pachTargetClientHashORKadID, pachTargetClientHashORKadID); + else + md4clr(newpending->pachTargetClientHashORKadID); +// ZZ:UploadBandWithThrottler (UDP) --> + sendLocker.Lock(); + controlpacket_queue.AddTail(newpending); + sendLocker.Unlock(); + + theApp.uploadBandwidthThrottler->QueueForSendingControlPacket(this); + return true; +// <-- ZZ:UploadBandWithThrottler (UDP) +} + +bool CClientUDPSocket::Create() +{ + bool ret = true; + + if (thePrefs.GetUDPPort()) + { + ret = CAsyncSocket::Create(thePrefs.GetUDPPort(), SOCK_DGRAM, FD_READ | FD_WRITE, thePrefs.GetBindAddrW()) != FALSE; + if (ret) + { + m_port = thePrefs.GetUDPPort(); + // the default socket size seems to be not enough for this UDP socket + // because we tend to drop packets if several flow in at the same time + int val = 64 * 1024; + if (!SetSockOpt(SO_RCVBUF, &val, sizeof(val))) + DebugLogError(_T("Failed to increase socket size on UDP socket")); + } + } + + if (ret) + m_port = thePrefs.GetUDPPort(); + + return ret; +} + +bool CClientUDPSocket::Rebind() +{ + if (thePrefs.GetUDPPort() == m_port) + return false; + Close(); + return Create(); +} diff --git a/ClientUDPSocket.h b/ClientUDPSocket.h new file mode 100644 index 00000000..f04d3995 --- /dev/null +++ b/ClientUDPSocket.h @@ -0,0 +1,65 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "UploadBandwidthThrottler.h" // ZZ:UploadBandWithThrottler (UDP) +#include "EncryptedDatagramSocket.h" + +class Packet; + +#pragma pack(1) +struct UDPPack +{ + Packet* packet; + uint32 dwIP; + uint16 nPort; + uint32 dwTime; + bool bEncrypt; + bool bKad; + uint32 nReceiverVerifyKey; + uchar pachTargetClientHashORKadID[16]; + //uint16 nPriority; We could add a priority system here to force some packets. +}; +#pragma pack() + +class CClientUDPSocket : public CAsyncSocket, public CEncryptedDatagramSocket, public ThrottledControlSocket // ZZ:UploadBandWithThrottler (UDP) +{ +public: + CClientUDPSocket(); + virtual ~CClientUDPSocket(); + + bool Create(); + bool Rebind(); + uint16 GetConnectedPort() { return m_port; } + bool SendPacket(Packet* packet, uint32 dwIP, uint16 nPort, bool bEncrypt, const uchar* pachTargetClientHashORKadID, bool bKad, uint32 nReceiverVerifyKey); + SocketSentBytes SendControlData(uint32 maxNumberOfBytesToSend, uint32 minFragSize); // ZZ:UploadBandWithThrottler (UDP) + +protected: + bool ProcessPacket(const BYTE* packet, UINT size, uint8 opcode, uint32 ip, uint16 port); + + virtual void OnSend(int nErrorCode); + virtual void OnReceive(int nErrorCode); + +private: + int SendTo(char* lpBuf,int nBufLen,uint32 dwIP, uint16 nPort); + bool IsBusy() const { return m_bWouldBlock; } + bool m_bWouldBlock; + uint16 m_port; + + CTypedPtrList controlpacket_queue; + + CCriticalSection sendLocker; // ZZ:UploadBandWithThrottler (UDP) +}; diff --git a/ClientVersionInfo.h b/ClientVersionInfo.h new file mode 100644 index 00000000..62c860cb --- /dev/null +++ b/ClientVersionInfo.h @@ -0,0 +1,177 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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 + + +#pragma once +#include "emule.h" +#include "updownclient.h" +// class to convert peercache Client versions to comparable values. + +#define CVI_IGNORED -1 +// == means same client type, same or ignored version (for example eMule/0.4.* == eMule/0.4.2 ) +// != means different client or different defined version (for example eMule/0.4.2 != SomeClient/0.4.2 ) +// > mean _same client type_ and higher version, which therefor cannot be completely undefined ( for example eMule/1.* > eMule/0.4.2 ) +// >= same as > but here the version can be undefined ( for example eMule/* >= eMule/0.4.2 ) +class CClientVersionInfo{ +public: + + CClientVersionInfo(CString strPCEncodedVersion) + { + m_nVerMajor = (UINT)CVI_IGNORED; + m_nVerMinor = (UINT)CVI_IGNORED; + m_nVerUpdate = (UINT)CVI_IGNORED; + m_nVerBuild = (UINT)CVI_IGNORED; + m_ClientTypeMajor = SO_UNKNOWN; + m_ClientTypeMinor = SO_UNKNOWN; + + int posSeperator = strPCEncodedVersion.Find('/',1); + if (posSeperator == (-1) || strPCEncodedVersion.GetLength() - posSeperator < 2){ + theApp.QueueDebugLogLine( false, _T("PeerCache Error: Bad Version info in PeerCache Descriptor found: %s"), strPCEncodedVersion); + return; + } + CString strClientType = strPCEncodedVersion.Left(posSeperator).Trim(); + CString strVersionNumber = strPCEncodedVersion.Mid(posSeperator+1).Trim(); + + if (strClientType.CompareNoCase(_T("eMule")) == 0) + m_ClientTypeMajor = SO_EMULE; + else if (strClientType.CompareNoCase(_T("eDonkey")) == 0) + m_ClientTypeMajor = SO_EDONKEYHYBRID; + // can add more types here + else{ + theApp.QueueDebugLogLine(false, _T("PeerCache Warning: Unknown Clienttype in descriptor file found")); + m_ClientTypeMajor = SO_UNKNOWN; + } + + int curPos2= 0; + CString strNumber = strVersionNumber.Tokenize(_T("."),curPos2); + if (strNumber.IsEmpty()) + return; + else if (strNumber == _T("*")) + m_nVerMajor = (UINT)-1; + else + m_nVerMajor = _tstoi(strNumber); + strNumber = strVersionNumber.Tokenize(_T("."),curPos2); + if (strNumber.IsEmpty()) + return; + else if (strNumber == _T("*")) + m_nVerMinor = (UINT)-1; + else + m_nVerMinor = _tstoi(strNumber); + strNumber = strVersionNumber.Tokenize(_T("."),curPos2); + if (strNumber.IsEmpty()) + return; + else if (strNumber == _T("*")) + m_nVerUpdate = (UINT)-1; + else + m_nVerUpdate = _tstoi(strNumber); + strNumber = strVersionNumber.Tokenize(_T("."),curPos2); + if (strNumber.IsEmpty()) + return; + else if (strNumber == _T("*")) + m_nVerBuild = (UINT)-1; + else + m_nVerBuild = _tstoi(strNumber); + } + + CClientVersionInfo(uint32 dwTagVersionInfo, UINT nClientMajor) + { + UINT nClientMajVersion = (dwTagVersionInfo >> 17) & 0x7f; + UINT nClientMinVersion = (dwTagVersionInfo>> 10) & 0x7f; + UINT nClientUpVersion = (dwTagVersionInfo >> 7) & 0x07; + CClientVersionInfo(nClientMajVersion, nClientMinVersion, nClientUpVersion, (UINT)CVI_IGNORED, nClientMajor, SO_UNKNOWN); + } + + CClientVersionInfo(uint32 nVerMajor, uint32 nVerMinor, uint32 nVerUpdate, uint32 nVerBuild, uint32 ClientTypeMajor, uint32 ClientTypeMinor = SO_UNKNOWN) + { + m_nVerMajor = nVerMajor; + m_nVerMinor = nVerMinor; + m_nVerUpdate = nVerUpdate; + m_nVerBuild = nVerBuild; + m_ClientTypeMajor = ClientTypeMajor; + m_ClientTypeMinor = ClientTypeMinor; + } + + CClientVersionInfo(){ + CClientVersionInfo((UINT)CVI_IGNORED, (UINT)CVI_IGNORED, (UINT)CVI_IGNORED, (UINT)CVI_IGNORED, SO_UNKNOWN, SO_UNKNOWN); + } + + CClientVersionInfo(const CClientVersionInfo& cv) {*this = cv;} + + CClientVersionInfo& operator=(const CClientVersionInfo& cv) + { + m_nVerMajor = cv.m_nVerMajor; + m_nVerMinor = cv.m_nVerMinor; + m_nVerUpdate = cv.m_nVerUpdate; + m_nVerBuild = cv.m_nVerBuild; + m_ClientTypeMajor = cv.m_ClientTypeMajor; + m_ClientTypeMinor = cv.m_ClientTypeMinor; + return *this; + } + + friend bool operator==(const CClientVersionInfo& c1, const CClientVersionInfo& c2) + { + return ( (c1.m_nVerMajor == (-1) || c2.m_nVerMajor == (-1) || c1.m_nVerMajor == c2.m_nVerMajor) + && (c1.m_nVerMinor == (-1) || c2.m_nVerMinor == (-1) || c1.m_nVerMinor == c2.m_nVerMinor) + && (c1.m_nVerUpdate == (-1) || c2.m_nVerUpdate == (-1) || c1.m_nVerUpdate == c2.m_nVerUpdate) + && (c1.m_nVerBuild == (-1) || c2.m_nVerBuild == (-1) || c1.m_nVerBuild == c2.m_nVerBuild) + && (c1.m_ClientTypeMajor == (-1) || c2.m_ClientTypeMajor == (-1) || c1.m_ClientTypeMajor == c2.m_ClientTypeMajor) + && (c1.m_ClientTypeMinor == (-1) || c2.m_ClientTypeMinor == (-1) || c1.m_ClientTypeMinor == c2.m_ClientTypeMinor) + ); + } + + friend bool operator !=(const CClientVersionInfo& c1, const CClientVersionInfo& c2) + { + return !(c1 == c2); + } + + friend bool operator >(const CClientVersionInfo& c1, const CClientVersionInfo& c2) + { + if ( (c1.m_ClientTypeMajor == (-1) || c2.m_ClientTypeMajor == (-1) || c1.m_ClientTypeMajor != c2.m_ClientTypeMajor) + || (c1.m_ClientTypeMinor != c2.m_ClientTypeMinor)) + return false; + if (c1.m_nVerMajor != (-1) && c2.m_nVerMajor != (-1) && c1.m_nVerMajor > c2.m_nVerMajor) + return true; + if (c1.m_nVerMinor != (-1) && c2.m_nVerMinor != (-1) && c1.m_nVerMinor > c2.m_nVerMinor) + return true; + if (c1.m_nVerUpdate != (-1) && c2.m_nVerUpdate != (-1) && c1.m_nVerUpdate > c2.m_nVerUpdate) + return true; + if (c1.m_nVerBuild != (-1) && c2.m_nVerBuild != (-1) && c1.m_nVerBuild > c2.m_nVerBuild) + return true; + return false; + } + + friend bool operator <(const CClientVersionInfo& c1, const CClientVersionInfo& c2) + { + return c2 > c1; + } + + friend bool operator <=(const CClientVersionInfo& c1, const CClientVersionInfo& c2) + { + return c2 > c1 || c1 == c2; + } + + friend bool operator >=(const CClientVersionInfo& c1, const CClientVersionInfo& c2) + { + return c1 > c2 || c1 == c2; + } + + UINT m_nVerMajor; + UINT m_nVerMinor; + UINT m_nVerUpdate; + UINT m_nVerBuild; + UINT m_ClientTypeMajor; + UINT m_ClientTypeMinor; //unused atm +}; \ No newline at end of file diff --git a/ClosableTabCtrl.cpp b/ClosableTabCtrl.cpp new file mode 100644 index 00000000..f86f23c9 --- /dev/null +++ b/ClosableTabCtrl.cpp @@ -0,0 +1,561 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ClosableTabCtrl.h" +#include "OtherFunctions.h" +#include "MenuCmds.h" +#include "UserMsgs.h" +#include "VisualStylesXP.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// _WIN32_WINNT >= 0x0501 (XP only) +#define _WM_THEMECHANGED 0x031A +#define _ON_WM_THEMECHANGED() \ + { _WM_THEMECHANGED, 0, 0, 0, AfxSig_l, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(void) > (_OnThemeChanged)) \ + }, + +/////////////////////////////////////////////////////////////////////////////// +// CClosableTabCtrl + +IMPLEMENT_DYNAMIC(CClosableTabCtrl, CTabCtrl) + +BEGIN_MESSAGE_MAP(CClosableTabCtrl, CTabCtrl) + ON_WM_LBUTTONUP() + ON_WM_LBUTTONDBLCLK() + ON_WM_MBUTTONUP() + ON_WM_CREATE() + ON_WM_SYSCOLORCHANGE() + ON_WM_CONTEXTMENU() + _ON_WM_THEMECHANGED() + ON_WM_CTLCOLOR_REFLECT() + ON_WM_CTLCOLOR() + ON_WM_ERASEBKGND() + ON_WM_MEASUREITEM() + ON_WM_MEASUREITEM_REFLECT() +END_MESSAGE_MAP() + +CClosableTabCtrl::CClosableTabCtrl() +{ + m_bCloseable = true; + memset(&m_iiCloseButton, 0, sizeof m_iiCloseButton); + m_ptCtxMenu.SetPoint(-1, -1); +} + +CClosableTabCtrl::~CClosableTabCtrl() +{ +} + +void CClosableTabCtrl::GetCloseButtonRect(int iItem, const CRect& rcItem, CRect& rcCloseButton, bool bItemSelected, bool bVistaThemeActive) +{ + rcCloseButton.top = rcItem.top + 2; + rcCloseButton.bottom = rcCloseButton.top + (m_iiCloseButton.rcImage.bottom - m_iiCloseButton.rcImage.top); + rcCloseButton.right = rcItem.right - 2; + rcCloseButton.left = rcCloseButton.right - (m_iiCloseButton.rcImage.right - m_iiCloseButton.rcImage.left); + if (bVistaThemeActive) + rcCloseButton.left -= 1; // the close button does not look 'symetric' with a width of 16, give it 17 + if (bItemSelected) { + rcCloseButton.OffsetRect(-1, 0); + if (bVistaThemeActive) { + int iItems = GetItemCount(); + if (iItems > 1 && iItem == iItems - 1) + rcCloseButton.OffsetRect(-2, 0); + } + } + else { + if (bVistaThemeActive) { + int iItems = GetItemCount(); + if (iItems > 1 && iItem < iItems - 1) + rcCloseButton.OffsetRect(2, 0); + } + } +} + +int CClosableTabCtrl::GetTabUnderPoint(CPoint point) const +{ + int iTabs = GetItemCount(); + for (int i = 0; i < iTabs; i++) + { + CRect rcItem; + GetItemRect(i, rcItem); + rcItem.InflateRect(2, 2); // get the real tab item rect + if (rcItem.PtInRect(point)) + return i; + } + return -1; +} + +int CClosableTabCtrl::GetTabUnderContextMenu() const +{ + if (m_ptCtxMenu.x == -1 || m_ptCtxMenu.y == -1) + return -1; + return GetTabUnderPoint(m_ptCtxMenu); +} + +bool CClosableTabCtrl::SetDefaultContextMenuPos() +{ + int iTab = GetCurSel(); + if (iTab != -1) + { + CRect rcItem; + if (GetItemRect(iTab, &rcItem)) + { + rcItem.InflateRect(2, 2); // get the real tab item rect + m_ptCtxMenu.x = rcItem.left + rcItem.Width()/2; + m_ptCtxMenu.y = rcItem.top + rcItem.Height()/2; + return true; + } + } + return false; +} + +void CClosableTabCtrl::OnMButtonUp(UINT nFlags, CPoint point) +{ + if (m_bCloseable) + { + int iTab = GetTabUnderPoint(point); + if (iTab != -1) { + GetParent()->SendMessage(UM_CLOSETAB, (WPARAM)iTab); + return; + } + } + + CTabCtrl::OnMButtonUp(nFlags, point); +} + +void CClosableTabCtrl::OnLButtonUp(UINT nFlags, CPoint point) +{ + if (m_bCloseable) + { + int iTab = GetTabUnderPoint(point); + if (iTab != -1) + { + CRect rcItem; + GetItemRect(iTab, rcItem); + rcItem.InflateRect(2, 2); // get the real tab item rect + + bool bVistaThemeActive = theApp.IsVistaThemeActive(); + CRect rcCloseButton; + GetCloseButtonRect(iTab, rcItem, rcCloseButton, iTab == GetCurSel(), bVistaThemeActive); + + // The visible part of our close icon is one pixel less on each side + if (!bVistaThemeActive) { + rcCloseButton.top += 1; + rcCloseButton.left += 1; + rcCloseButton.right -= 1; + rcCloseButton.bottom -= 1; + } + + if (rcCloseButton.PtInRect(point)) { + GetParent()->SendMessage(UM_CLOSETAB, (WPARAM)iTab); + return; + } + } + } + + CTabCtrl::OnLButtonUp(nFlags, point); +} + +void CClosableTabCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) +{ + int iTab = GetTabUnderPoint(point); + if (iTab != -1) { + GetParent()->SendMessage(UM_DBLCLICKTAB, (WPARAM)iTab); + return; + } + CTabCtrl::OnLButtonDblClk(nFlags, point); +} + +// It would be nice if there would the option to restrict the maximum width of a tab control. +// We would need that feature actually for almost all our tab controls. Especially for the +// search results list - those tab control labels can get quite large. But I did not yet a +// find a way to limit the width of tabs. Although MSDN says that an owner drawn +// tab control receives a WM_MEASUREITEM, I never got one. + +// Vista: This gets never called for an owner drawn tab control +void CClosableTabCtrl::OnMeasureItem(int iCtlId, LPMEASUREITEMSTRUCT lpMeasureItemStruct) +{ + TRACE("CClosableTabCtrl::OnMeasureItem\n"); + __super::OnMeasureItem(iCtlId, lpMeasureItemStruct); +} + +// Vista: This gets never called for an owner drawn tab control +void CClosableTabCtrl::MeasureItem(LPMEASUREITEMSTRUCT) +{ + TRACE("CClosableTabCtrl::MeasureItem\n"); +} + +void CClosableTabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDIS) +{ + CRect rect(lpDIS->rcItem); + int nTabIndex = lpDIS->itemID; + if (nTabIndex < 0) + return; + + TCHAR szLabel[256]; + TC_ITEM tci; + tci.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_STATE; + tci.pszText = szLabel; + tci.cchTextMax = _countof(szLabel); + tci.dwStateMask = TCIS_HIGHLIGHTED; + if (!GetItem(nTabIndex, &tci)) + return; + szLabel[_countof(szLabel) - 1] = _T('\0'); + //TRACE("CClosableTabCtrl::DrawItem: item=%u, state=%08x, color=%08x, rc=%3d,%3d,%3dx%3d\n", nTabIndex, tci.dwState, GetTextColor(lpDIS->hDC), lpDIS->rcItem.left, lpDIS->rcItem.top, lpDIS->rcItem.right - lpDIS->rcItem.left, lpDIS->rcItem.bottom - lpDIS->rcItem.top); + + CDC* pDC = CDC::FromHandle(lpDIS->hDC); + if (!pDC) + return; + + CRect rcFullItem(lpDIS->rcItem); + bool bSelected = (lpDIS->itemState & ODS_SELECTED) != 0; + + /////////////////////////////////////////////////////////////////////////////////////// + // Adding support for XP Styles (Vista Themes) for owner drawn tab controls simply + // does *not* work under Vista. Maybe it works under XP (did not try), but that is + // meaningless because under XP a owner drawn tab control is already rendered *with* + // the proper XP Styles. So, for XP there is no need to care about the theme API at all. + // + // However, under Vista, a tab control which has the TCS_OWNERDRAWFIXED + // style gets additional 3D-borders which are applied by Vista *after* WM_DRAWITEM + // was processed. Thus, there is no known workaround available to prevent Vista from + // adding those old fashioned 3D-borders. We can render the tab control items within + // the WM_DRAWITEM handler in whatever style we want, but Vista will in each case + // overwrite the borders of each tab control item with old fashioned 3D-borders... + // + // To complete this experience, tab controls also do not support NMCUSTOMDRAW. So, the + // only known way to customize a tab control is by using TCS_OWNERDRAWFIXED which does + // however not work properly under Vista. + // + // The "solution" which is currently implemented to prevent Vista from drawing those + // 3D-borders is by using "ExcludeClipRect" to reduce the drawing area which is used + // by Windows after WM_DRAWITEM was processed. This "solution" is very sensitive to + // the used rectangles and offsets in general. Incrementing/Decrementing one of the + // "rcItem", "rcFullItem", etc. rectangles makes the entire "solution" flawed again + // because some borders would become visible again. + // + HTHEME hTheme = NULL; + int iPartId = TABP_TABITEM; + int iStateId = TIS_NORMAL; + bool bVistaHotTracked = false; + bool bVistaThemeActive = theApp.IsVistaThemeActive(); + if (bVistaThemeActive) + { + // To determine if the current item is in 'hot tracking' mode, we need to evaluate + // the current foreground color - there is no flag which would indicate this state + // more safely. This applies only for Vista and for tab controls which have the + // TCS_OWNERDRAWFIXED style. + bVistaHotTracked = pDC->GetTextColor() == GetSysColor(COLOR_HOTLIGHT); + + hTheme = g_xpStyle.OpenThemeData(m_hWnd, L"TAB"); + if (hTheme) + { + if (bSelected) { + // get the real tab item rect + rcFullItem.left += 1; + rcFullItem.right -= 1; + rcFullItem.bottom -= 1; + } + else + rcFullItem.InflateRect(2, 2); // get the real tab item rect + + CRect rcBk(rcFullItem); + if (bSelected) + { + iStateId = TTIS_SELECTED; + if (nTabIndex == 0) { + // First item + if (nTabIndex == GetItemCount() - 1) + iPartId = TABP_TOPTABITEMBOTHEDGE; // First & Last item + else + iPartId = TABP_TOPTABITEMLEFTEDGE; + } + else if (nTabIndex == GetItemCount() - 1) { + // Last item + iPartId = TABP_TOPTABITEMRIGHTEDGE; + } + else { + iPartId = TABP_TOPTABITEM; + } + } + else + { + rcBk.top += 2; + iStateId = bVistaHotTracked ? TIS_HOT : TIS_NORMAL; + if (nTabIndex == 0) { + // First item + if (nTabIndex == GetItemCount() - 1) + iPartId = TABP_TABITEMBOTHEDGE; // First & Last item + else + iPartId = TABP_TABITEMLEFTEDGE; + } + else if (nTabIndex == GetItemCount() - 1) { + // Last item + iPartId = TABP_TABITEMRIGHTEDGE; + } + else { + iPartId = TABP_TABITEM; + } + } + if (g_xpStyle.IsThemeBackgroundPartiallyTransparent(hTheme, iPartId, iStateId)) + g_xpStyle.DrawThemeParentBackground(m_hWnd, *pDC, &rcFullItem); + g_xpStyle.DrawThemeBackground(hTheme, *pDC, iPartId, iStateId, &rcBk, NULL); + } + } + + // Following background clearing is needed for: + // WinXP/Vista (when used without an application theme) + // Vista (when used with an application theme but without a theme for the tab control) + if ( (!g_xpStyle.IsThemeActive() || !g_xpStyle.IsAppThemed()) + || (hTheme == NULL && bVistaThemeActive) ) + pDC->FillSolidRect(&lpDIS->rcItem, GetSysColor(COLOR_BTNFACE)); + + int iOldBkMode = pDC->SetBkMode(TRANSPARENT); + + // Draw image on left side + CImageList *piml = GetImageList(); + if (tci.iImage >= 0 && piml && piml->m_hImageList) + { + IMAGEINFO ii; + piml->GetImageInfo(0, &ii); + rect.left += bSelected ? 8 : 4; + piml->Draw(pDC, tci.iImage, CPoint(rect.left, rect.top + 2), ILD_TRANSPARENT); + rect.left += (ii.rcImage.right - ii.rcImage.left); + if (!bSelected) + rect.left += 4; + } + + bool bCloseable = m_bCloseable; + if (bCloseable && GetParent()->SendMessage(UM_QUERYTAB, nTabIndex)) + bCloseable = false; + + // Draw 'Close button' at right side + if (bCloseable && m_ImgLstCloseButton.m_hImageList) + { + CRect rcCloseButton; + GetCloseButtonRect(nTabIndex, rect, rcCloseButton, bSelected, bVistaThemeActive); + + HTHEME hThemeNC = bVistaThemeActive ? g_xpStyle.OpenThemeData(m_hWnd, _T("WINDOW")) : NULL; + if (hThemeNC) { + // Possible "Close" parts: WP_CLOSEBUTTON, WP_SMALLCLOSEBUTTON, WP_MDICLOSEBUTTON + int iPartId = WP_SMALLCLOSEBUTTON; + int iStateId = (bSelected || bVistaHotTracked) ? CBS_NORMAL : CBS_DISABLED; + if (g_xpStyle.IsThemeBackgroundPartiallyTransparent(hTheme, iPartId, iStateId)) + g_xpStyle.DrawThemeParentBackground(m_hWnd, *pDC, &rcCloseButton); + g_xpStyle.DrawThemeBackground(hThemeNC, *pDC, iPartId, iStateId, rcCloseButton, NULL); + g_xpStyle.CloseThemeData(hThemeNC); + } + else { + m_ImgLstCloseButton.Draw(pDC, (bSelected || bVistaHotTracked) ? 0 : 1, rcCloseButton.TopLeft(), ILD_TRANSPARENT); + } + + rect.right = rcCloseButton.left - 2; + if (bSelected) + rect.left += hTheme ? 4 : 2; + } + + COLORREF crOldColor = CLR_NONE; + if (tci.dwState & TCIS_HIGHLIGHTED) + crOldColor = pDC->SetTextColor(RGB(192, 0, 0)); + else if (bVistaHotTracked) + crOldColor = pDC->SetTextColor(GetSysColor(COLOR_BTNTEXT)); + + rect.top += bSelected ? 4 : 3; + // Vista: Tab control has troubles with determining the width of a tab if the + // label contains one '&' character. To get around this, we use the old code which + // replaces one '&' character with two '&' characters and we do not specify DT_NOPREFIX + // here when drawing the text. + // + // Vista: "DrawThemeText" can not be used in case we need a certain foreground color. Thus we always us + // "DrawText" to always get the same font and metrics (just for safety). + pDC->DrawText(szLabel, rect, DT_SINGLELINE | DT_TOP | DT_CENTER /*| DT_NOPREFIX*/); + + if (crOldColor != CLR_NONE) + pDC->SetTextColor(crOldColor); + pDC->SetBkMode(iOldBkMode); + + if (hTheme) + { + CRect rcClip(rcFullItem); + if (bSelected) { + rcClip.left -= 2 + 1; + rcClip.right += 2 + 1; + } + else { + rcClip.top += 2; + } + pDC->ExcludeClipRect(&rcClip); + g_xpStyle.CloseThemeData(hTheme); + } +} + +void CClosableTabCtrl::PreSubclassWindow() +{ + CTabCtrl::PreSubclassWindow(); + InternalInit(); +} + +int CClosableTabCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CTabCtrl::OnCreate(lpCreateStruct) == -1) + return -1; + InternalInit(); + return 0; +} + +void CClosableTabCtrl::InternalInit() +{ + ModifyStyle(0, TCS_OWNERDRAWFIXED); + +#if 1 + // Under Vista Aero, all tab controls get by default the TCS_HOTTRACK + // style even if it was not specified within the resource file. Though, to 'see' + // the hot tracking effect the control also need to get initialized explicitly with + // the WS_CLIPCHILDREN style within a *seperate* function call. Yes, there is no + // logic to all this, not at all. It simply is that way. + // + // So, do *not* "optimize" that code by using only one "ModifyStyle" function call. + // The 2nd function call to "ModifyStyle" is very much by intention! + // + // However, the hot tracking effect which is achived this way does not survive a + // theme change. After the theme is changed (regardless whether we switch between + // Vista themes or from/to a non-Vista theme), the hot tracking effect is gone even + // if we try to modify the styles again within OnThemeChanged... + if (theApp.IsVistaThemeActive()) + ModifyStyle(0, WS_CLIPCHILDREN); +#else + // Remove the automatically applied hot tracking effect to avoid that the tab control + // may use it when it also sets the WS_CLIPCHILDREN (for other reasons) later. + ModifyStyle(TCS_HOTTRACK, 0); +#endif + + SetAllIcons(); +} + +void CClosableTabCtrl::OnSysColorChange() +{ + CTabCtrl::OnSysColorChange(); + SetAllIcons(); +} + +void CClosableTabCtrl::SetAllIcons() +{ + if (m_bCloseable) + { + const int iIconWidth = 16; + const int iIconHeight = 16; + m_ImgLstCloseButton.DeleteImageList(); + m_ImgLstCloseButton.Create(iIconWidth, iIconHeight, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + m_ImgLstCloseButton.Add(CTempIconLoader(_T("CloseTabSelected"), iIconWidth, iIconHeight)); + m_ImgLstCloseButton.Add(CTempIconLoader(_T("CloseTab"), iIconWidth, iIconHeight)); + m_ImgLstCloseButton.GetImageInfo(0, &m_iiCloseButton); + Invalidate(); + } +} + +void CClosableTabCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + if (m_bCloseable) + { + if (point.x == -1 || point.y == -1) { + if (!SetDefaultContextMenuPos()) + return; + point = m_ptCtxMenu; + ClientToScreen(&point); + } + else { + m_ptCtxMenu = point; + ScreenToClient(&m_ptCtxMenu); + } + + int iTab = GetTabUnderPoint(m_ptCtxMenu); + if (iTab != -1) + { + if (GetParent()->SendMessage(UM_QUERYTAB, (WPARAM)iTab) == 0) + { + CMenu menu; + menu.CreatePopupMenu(); + menu.AppendMenu(MF_STRING, MP_REMOVE, GetResString(IDS_FD_CLOSE)); + menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); + } + } + } +} + +BOOL CClosableTabCtrl::OnCommand(WPARAM wParam, LPARAM lParam) +{ + if (wParam == MP_REMOVE) + { + if (m_ptCtxMenu.x != -1 && m_ptCtxMenu.y != -1) + { + int iTab = GetTabUnderPoint(m_ptCtxMenu); + if (iTab != -1) { + GetParent()->SendMessage(UM_CLOSETAB, (WPARAM)iTab); + return TRUE; + } + } + } + return CTabCtrl::OnCommand(wParam, lParam); +} + +LRESULT CClosableTabCtrl::_OnThemeChanged() +{ + // Owner drawn tab control seems to have troubles with updating itself due to an XP theme change.. + ModifyStyle(TCS_OWNERDRAWFIXED, 0); // Reset control style to not-owner drawn + Default(); // Process original WM_THEMECHANGED message + ModifyStyle(0, TCS_OWNERDRAWFIXED); // Apply owner drawn style again + return 0; +} + +// Vista: This gets never called for an owner drawn tab control +HBRUSH CClosableTabCtrl::CtlColor(CDC* /*pDC*/, UINT /*nCtlColor*/) +{ + // Change any attributes of the DC here + // Return a non-NULL brush if the parent's handler should not be called + return NULL; +} + +// Vista: This gets never called for an owner drawn tab control +HBRUSH CClosableTabCtrl::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) +{ + HBRUSH hbr = CTabCtrl::OnCtlColor(pDC, pWnd, nCtlColor); + // Change any attributes of the DC here + // Return a different brush if the default is not desired + return hbr; +} + +// Vista: Can not be used to workaround the problems with owner drawn tab control +BOOL CClosableTabCtrl::OnEraseBkgnd(CDC* pDC) +{ + return CTabCtrl::OnEraseBkgnd(pDC); +} + +BOOL CClosableTabCtrl::DeleteItem(int nItem) +{ + // if we remove a tab which would lead to scrolling back to other tabs, all those become hidden for... whatever reasons + // its easy enough wo work arround by scrolling to the first visible tab _before_ we delete the other one + SetCurSel(0); + return __super::DeleteItem(nItem); +} diff --git a/ClosableTabCtrl.h b/ClosableTabCtrl.h new file mode 100644 index 00000000..2b21eecb --- /dev/null +++ b/ClosableTabCtrl.h @@ -0,0 +1,43 @@ +#pragma once + +class CClosableTabCtrl : public CTabCtrl +{ + DECLARE_DYNAMIC(CClosableTabCtrl) + +public: + CClosableTabCtrl(); + virtual ~CClosableTabCtrl(); + BOOL DeleteItem(int nItem); + + bool m_bCloseable; + +protected: + CImageList m_ImgLstCloseButton; + IMAGEINFO m_iiCloseButton; + CPoint m_ptCtxMenu; + + void InternalInit(); + void SetAllIcons(); + void GetCloseButtonRect(int iItem, const CRect& rcItem, CRect& rcCloseButton, bool bItemSelected, bool bVistaThemeActive); + int GetTabUnderContextMenu() const; + int GetTabUnderPoint(CPoint point) const; + bool SetDefaultContextMenuPos(); + + virtual void PreSubclassWindow(); + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + + DECLARE_MESSAGE_MAP() + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg void OnMButtonUp(UINT nFlags, CPoint point); + afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); + afx_msg void OnSysColorChange(); + afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); + afx_msg LRESULT _OnThemeChanged(); + afx_msg HBRUSH CtlColor(CDC* /*pDC*/, UINT /*nCtlColor*/); + afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnMeasureItem(int, LPMEASUREITEMSTRUCT); + afx_msg void MeasureItem(LPMEASUREITEMSTRUCT); +}; diff --git a/Collection.cpp b/Collection.cpp new file mode 100644 index 00000000..7cf7515b --- /dev/null +++ b/Collection.cpp @@ -0,0 +1,446 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. + +#include "StdAfx.h" +#include "collection.h" +#include "KnownFile.h" +#include "CollectionFile.h" +#include "SafeFile.h" +#include "Packets.h" +#include "Preferences.h" +#include "SharedFilelist.h" +#include "emule.h" +#include "Log.h" +#include "md5sum.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define COLLECTION_FILE_VERSION1_INITIAL 0x01 +#define COLLECTION_FILE_VERSION2_LARGEFILES 0x02 + +CCollection::CCollection(void) +: m_sCollectionName(_T("")) +, m_sCollectionAuthorName(_T("")) +, m_bTextFormat(false) +{ + m_CollectionFilesMap.InitHashTable(1031); + m_sCollectionName.Format(_T("New Collection-%u"), ::GetTickCount()); + m_pabyCollectionAuthorKey = NULL; + m_nKeySize = 0; +} + +CCollection::CCollection(const CCollection* pCollection) +{ + m_sCollectionName = pCollection->m_sCollectionName; + if (pCollection->m_pabyCollectionAuthorKey != NULL){ + m_nKeySize = pCollection->m_nKeySize; + m_pabyCollectionAuthorKey = new BYTE[m_nKeySize]; + memcpy(m_pabyCollectionAuthorKey, pCollection->m_pabyCollectionAuthorKey, m_nKeySize); + m_sCollectionAuthorName = pCollection->m_sCollectionAuthorName; + } + else{ + m_nKeySize = 0; + m_pabyCollectionAuthorKey = NULL; + } + + m_bTextFormat = pCollection->m_bTextFormat; + + m_CollectionFilesMap.InitHashTable(1031); + POSITION pos = pCollection->m_CollectionFilesMap.GetStartPosition(); + CCollectionFile* pCollectionFile; + CSKey key; + while( pos != NULL ) + { + pCollection->m_CollectionFilesMap.GetNextAssoc( pos, key, pCollectionFile ); + AddFileToCollection(pCollectionFile, true); + } +} + +CCollection::~CCollection(void) +{ + delete[] m_pabyCollectionAuthorKey; + POSITION pos = m_CollectionFilesMap.GetStartPosition(); + CCollectionFile* pCollectionFile; + CSKey key; + while( pos != NULL ) + { + m_CollectionFilesMap.GetNextAssoc( pos, key, pCollectionFile ); + delete pCollectionFile; + } + m_CollectionFilesMap.RemoveAll(); +} + +CCollectionFile* CCollection::AddFileToCollection(CAbstractFile* pAbstractFile, bool bCreateClone) +{ + CSKey key(pAbstractFile->GetFileHash()); + CCollectionFile* pCollectionFile; + if (m_CollectionFilesMap.Lookup(key, pCollectionFile)) + { + ASSERT(0); + return pCollectionFile; + } + + pCollectionFile = NULL; + + if(bCreateClone) + pCollectionFile = new CCollectionFile(pAbstractFile); + else if(pAbstractFile->IsKindOf(RUNTIME_CLASS(CCollectionFile))) + pCollectionFile = (CCollectionFile*)pAbstractFile; + + if(pCollectionFile) + m_CollectionFilesMap.SetAt(key, pCollectionFile); + + return pCollectionFile; +} + +void CCollection::RemoveFileFromCollection(CAbstractFile* pAbstractFile) +{ + CSKey key(pAbstractFile->GetFileHash()); + CCollectionFile* pCollectionFile; + if (m_CollectionFilesMap.Lookup(key, pCollectionFile)) + { + m_CollectionFilesMap.RemoveKey(key); + delete pCollectionFile; + } + else + ASSERT(0); +} + +void CCollection::SetCollectionAuthorKey(const byte* abyCollectionAuthorKey, uint32 nSize) +{ + delete[] m_pabyCollectionAuthorKey; + m_pabyCollectionAuthorKey = NULL; + m_nKeySize = 0; + if (abyCollectionAuthorKey != NULL){ + m_pabyCollectionAuthorKey = new BYTE[nSize]; + memcpy(m_pabyCollectionAuthorKey, abyCollectionAuthorKey, nSize); + m_nKeySize = nSize; + } +} + +bool CCollection::InitCollectionFromFile(const CString& sFilePath, CString sFileName) +{ + DEBUG_ONLY( sFileName.Replace(COLLECTION_FILEEXTENSION, _T("")) ); + + bool bCollectionLoaded = false; + + CSafeFile data; + if(data.Open(sFilePath, CFile::modeRead | CFile::shareDenyWrite | CFile::typeBinary)) + { + try + { + uint32 nVersion = data.ReadUInt32(); + if(nVersion == COLLECTION_FILE_VERSION1_INITIAL || nVersion == COLLECTION_FILE_VERSION2_LARGEFILES) + { + uint32 headerTagCount = data.ReadUInt32(); + while(headerTagCount) + { + CTag tag(&data, true); + switch(tag.GetNameID()) + { + case FT_FILENAME: + { + if(tag.IsStr()) + m_sCollectionName = tag.GetStr(); + break; + } + case FT_COLLECTIONAUTHOR: + { + if(tag.IsStr()) + m_sCollectionAuthorName = tag.GetStr(); + break; + } + case FT_COLLECTIONAUTHORKEY: + { + if(tag.IsBlob()) + { + SetCollectionAuthorKey(tag.GetBlob(), tag.GetBlobSize()); + } + break; + } + } + headerTagCount--; + } + uint32 fileCount = data.ReadUInt32(); + while(fileCount) + { + CCollectionFile* pCollectionFile = new CCollectionFile(&data); + if(pCollectionFile) + AddFileToCollection(pCollectionFile, false); + fileCount--; + } + bCollectionLoaded = true; + } + if (m_pabyCollectionAuthorKey != NULL){ + bool bResult = false; + if (data.GetLength() > data.GetPosition()){ + using namespace CryptoPP; + + uint32 nPos = (uint32)data.GetPosition(); + data.SeekToBegin(); + BYTE* pMessage = new BYTE[nPos]; + VERIFY( data.Read(pMessage, nPos) == nPos); + + StringSource ss_Pubkey(m_pabyCollectionAuthorKey, m_nKeySize, true, 0); + RSASSA_PKCS1v15_SHA_Verifier pubkey(ss_Pubkey); + + int nSignLen = (int)(data.GetLength() - data.GetPosition()); + BYTE* pSignature = new BYTE[nSignLen ]; + VERIFY( data.Read(pSignature, nSignLen) == (UINT)nSignLen); + + bResult = pubkey.VerifyMessage(pMessage, nPos, pSignature, nSignLen); + + delete[] pMessage; + delete[] pSignature; + } + if (!bResult){ + DebugLogWarning(_T("Collection %s: Verifying of public key failed!"), m_sCollectionName); + delete[] m_pabyCollectionAuthorKey; + m_pabyCollectionAuthorKey = NULL; + m_nKeySize = 0; + m_sCollectionAuthorName = _T(""); + } + else + DebugLog(_T("Collection %s: Public key verified"), m_sCollectionName); + + } + else + m_sCollectionAuthorName = _T(""); + data.Close(); + } + catch(CFileException* error) + { + error->Delete(); + return false; + } + catch(...) + { + ASSERT( false ); + data.Close(); + return false; + } + } + else + return false; + + if(!bCollectionLoaded) + { + CStdioFile data; + if(data.Open(sFilePath, CFile::modeRead | CFile::shareDenyWrite | CFile::typeText)) + { + try + { + CString sLink; + while(data.ReadString(sLink)) + { + //Ignore all lines that start with #. + //These lines can be used for future features.. + if(sLink.Find(_T("#")) != 0) + { + try + { + CCollectionFile* pCollectionFile = new CCollectionFile(); + if (pCollectionFile->InitFromLink(sLink)) + AddFileToCollection(pCollectionFile, false); + else + delete pCollectionFile; + } + catch(...) + { + ASSERT( false ); + data.Close(); + return false; + } + } + } + data.Close(); + m_sCollectionName = sFileName; + bCollectionLoaded = true; + m_bTextFormat = true; + } + catch(CFileException* error) + { + error->Delete(); + return false; + } + catch(...) + { + ASSERT( false ); + data.Close(); + return false; + } + } + } + + return bCollectionLoaded; +} + +void CCollection::WriteToFileAddShared(CryptoPP::RSASSA_PKCS1v15_SHA_Signer* pSignKey) +{ + using namespace CryptoPP; + CString sFileName; + sFileName.Format(_T("%s%s"), m_sCollectionName, COLLECTION_FILEEXTENSION); + + CString sFilePath; + sFilePath.Format(_T("%s\\%s"), thePrefs.GetMuleDirectory(EMULE_INCOMINGDIR), sFileName); + + if(m_bTextFormat) + { + CStdioFile data; + if(data.Open(sFilePath, CFile::modeCreate | CFile::modeWrite | CFile::shareDenyWrite | CFile::typeText)) + { + try + { + POSITION pos = m_CollectionFilesMap.GetStartPosition(); + CCollectionFile* pCollectionFile; + CSKey key; + while( pos != NULL ) + { + m_CollectionFilesMap.GetNextAssoc( pos, key, pCollectionFile ); + CString sLink; + sLink.Format(_T("%s\n"), pCollectionFile->GetED2kLink()); + data.WriteString(sLink); + } + data.Close(); + } + catch(CFileException* error) + { + error->Delete(); + return; + } + catch(...) + { + ASSERT( false ); + data.Close(); + return; + } + } + } + else + { + CSafeFile data; + if(data.Open(sFilePath, CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyWrite | CFile::typeBinary)) + { + try + { + //Version + // check first if we have any large files in the map - write use lowest version possible + uint32 dwVersion = COLLECTION_FILE_VERSION1_INITIAL; + POSITION pos = m_CollectionFilesMap.GetStartPosition(); + CCollectionFile* pCollectionFile; + CSKey key; + while( pos != NULL ) { + m_CollectionFilesMap.GetNextAssoc( pos, key, pCollectionFile ); + if (pCollectionFile->IsLargeFile()){ + dwVersion = COLLECTION_FILE_VERSION2_LARGEFILES; + break; + } + } + data.WriteUInt32(dwVersion); + + uint32 uTagCount = 1; + + //NumberHeaderTags + if(m_pabyCollectionAuthorKey != NULL) + uTagCount += 2; + + + data.WriteUInt32(uTagCount); + + CTag collectionName(FT_FILENAME, m_sCollectionName); + collectionName.WriteTagToFile(&data, utf8strRaw); + + if(m_pabyCollectionAuthorKey != NULL){ + CTag collectionAuthor(FT_COLLECTIONAUTHOR, m_sCollectionAuthorName); + collectionAuthor.WriteTagToFile(&data, utf8strRaw); + + CTag collectionAuthorKey(FT_COLLECTIONAUTHORKEY, m_nKeySize, m_pabyCollectionAuthorKey); + collectionAuthorKey.WriteTagToFile(&data, utf8strRaw); + } + + //Total Files + data.WriteUInt32(m_CollectionFilesMap.GetSize()); + + pos = m_CollectionFilesMap.GetStartPosition(); + while( pos != NULL ) { + m_CollectionFilesMap.GetNextAssoc( pos, key, pCollectionFile ); + pCollectionFile->WriteCollectionInfo(&data); + } + + if (pSignKey != NULL){ + uint32 nPos = (uint32)data.GetPosition(); + data.SeekToBegin(); + BYTE* pBuffer = new BYTE[nPos]; + VERIFY( data.Read(pBuffer, nPos) == nPos); + + SecByteBlock sbbSignature(pSignKey->SignatureLength()); + AutoSeededRandomPool rng; + pSignKey->SignMessage(rng, pBuffer ,nPos , sbbSignature.begin()); + BYTE abyBuffer2[500]; + ArraySink asink(abyBuffer2, 500); + asink.Put(sbbSignature.begin(), sbbSignature.size()); + int nResult = (uint8)asink.TotalPutLength(); + data.Write(abyBuffer2, nResult); + + delete[] pBuffer; + } + data.Close(); + } + catch(CFileException* error) + { + error->Delete(); + return; + } + catch(...) + { + ASSERT( false ); + data.Close(); + return; + } + } + } + + theApp.sharedfiles->AddFileFromNewlyCreatedCollection(sFilePath); +} + +bool CCollection::HasCollectionExtention(const CString& sFileName) +{ + if(sFileName.Find(COLLECTION_FILEEXTENSION) == -1) + return false; + return true; +} + +CString CCollection::GetCollectionAuthorKeyString(){ + if (m_pabyCollectionAuthorKey != NULL) + return EncodeBase16(m_pabyCollectionAuthorKey, m_nKeySize); + else + return CString(_T("")); +} + +CString CCollection::GetAuthorKeyHashString(){ + if (m_pabyCollectionAuthorKey != NULL){ + MD5Sum md5(m_pabyCollectionAuthorKey, m_nKeySize); + CString strResult = md5.GetHash(); + strResult.MakeUpper(); + return strResult; + } + return CString(_T("")); +} diff --git a/Collection.h b/Collection.h new file mode 100644 index 00000000..cb01d69d --- /dev/null +++ b/Collection.h @@ -0,0 +1,66 @@ +//this file is part of eMule +//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net ) +// +//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. +#pragma once +#include "MapKey.h" +#pragma warning(disable:4516) // access-declarations are deprecated; member using-declarations provide a better alternative +#pragma warning(disable:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4702) // unreachable code +#include +#include +#include +#include +#include +#pragma warning(default:4702) // unreachable code +#pragma warning(default:4100) // unreferenced formal parameter +#pragma warning(default:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(default:4516) // access-declarations are deprecated; member using-declarations provide a better alternative + +#define COLLECTION_FILEEXTENSION _T(".emulecollection") + +class CAbstractFile; +class CCollectionFile; + +typedef CMap CCollectionFilesMap; + +class CCollection +{ + friend class CCollectionCreateDialog; + friend class CCollectionViewDialog; +public: + CCollection(void); + CCollection(const CCollection* pCollection); + ~CCollection(void); + bool InitCollectionFromFile(const CString& sFilePath, CString sFileName); + CCollectionFile* AddFileToCollection(CAbstractFile* pAbstractFile, bool bCreateClone); + void RemoveFileFromCollection(CAbstractFile* pAbstractFile); + void WriteToFileAddShared(CryptoPP::RSASSA_PKCS1v15_SHA_Signer* pSignkey = NULL); + void SetCollectionAuthorKey(const byte* abyCollectionAuthorKey, uint32 nSize); + CString GetCollectionAuthorKeyString(); + static bool HasCollectionExtention(const CString& sFileName); + CString GetAuthorKeyHashString(); + + CString m_sCollectionName; + CString m_sCollectionAuthorName; + + bool m_bTextFormat; + +private: + CCollectionFilesMap m_CollectionFilesMap; + byte* m_pabyCollectionAuthorKey; + uint32 m_nKeySize; +}; diff --git a/CollectionCreateDialog.cpp b/CollectionCreateDialog.cpp new file mode 100644 index 00000000..c8fc6b63 --- /dev/null +++ b/CollectionCreateDialog.cpp @@ -0,0 +1,421 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "emuledlg.h" +#include "CollectionCreateDialog.h" +#include "OtherFunctions.h" +#include "Collection.h" +#include "Sharedfilelist.h" +#include "CollectionFile.h" +#include "KnownFile.h" +#include "KnownFileList.h" +#include "PartFile.h" +#include "TransferDlg.h" +#include "DownloadListCtrl.h" +#pragma warning(disable:4516) // access-declarations are deprecated; member using-declarations provide a better alternative +#pragma warning(disable:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4702) // unreachable code +#include +#include +#include +#include +#include +#pragma warning(default:4702) // unreachable code +#pragma warning(default:4100) // unreferenced formal parameter +#pragma warning(default:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(default:4516) // access-declarations are deprecated; member using-declarations provide a better alternative +#include "Preferences.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define PREF_INI_SECTION _T("CollectionCreateDlg") + +enum ECols +{ + colName = 0, + colSize, + colHash +}; + +IMPLEMENT_DYNAMIC(CCollectionCreateDialog, CDialog) + +BEGIN_MESSAGE_MAP(CCollectionCreateDialog, CResizableDialog) + ON_BN_CLICKED(IDC_CCOLL_CANCEL, OnCancel) + ON_BN_CLICKED(IDC_CCOLL_SAVE, OnBnClickedOk) + ON_BN_CLICKED(IDC_COLLECTIONADD, OnBnClickedCollectionAdd) + ON_BN_CLICKED(IDC_COLLECTIONCREATEFORMAT, OnBnClickedCollectionFormat) + ON_BN_CLICKED(IDC_COLLECTIONREMOVE, OnBnClickedCollectionRemove) + ON_BN_CLICKED(IDC_COLLECTIONVIEWSHAREBUTTON, OnBnClickedCollectionViewShared) + ON_EN_KILLFOCUS(IDC_COLLECTIONNAMEEDIT, OnEnKillFocusCollectionName) + ON_NOTIFY(NM_DBLCLK, IDC_COLLECTIONAVAILLIST, OnNmDblClkCollectionAvailList) + ON_NOTIFY(NM_DBLCLK, IDC_COLLECTIONLISTCTRL, OnNmDblClkCollectionList) +END_MESSAGE_MAP() + +CCollectionCreateDialog::CCollectionCreateDialog(CWnd* pParent /*=NULL*/) + : CResizableDialog(CCollectionCreateDialog::IDD, pParent) + , m_pCollection(NULL) + , m_bSharedFiles(false) +{ + m_icoWnd = NULL; + m_icoForward = NULL; + m_icoBack = NULL; + m_icoColl = NULL; + m_icoFiles = NULL; + m_bCreatemode = false; +} + +CCollectionCreateDialog::~CCollectionCreateDialog() +{ + if (m_icoWnd) + VERIFY( DestroyIcon(m_icoWnd) ); + if (m_icoForward) + VERIFY( DestroyIcon(m_icoForward) ); + if (m_icoBack) + VERIFY( DestroyIcon(m_icoBack) ); + if (m_icoColl) + VERIFY( DestroyIcon(m_icoColl) ); + if (m_icoFiles) + VERIFY( DestroyIcon(m_icoFiles) ); +} + +void CCollectionCreateDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_COLLECTIONLISTCTRL, m_CollectionListCtrl); + DDX_Control(pDX, IDC_COLLECTIONAVAILLIST, m_CollectionAvailListCtrl); + DDX_Control(pDX, IDC_COLLECTIONNAMEEDIT, m_CollectionNameEdit); + DDX_Control(pDX, IDC_COLLECTIONVIEWSHAREBUTTON, m_CollectionViewShareButton); + DDX_Control(pDX, IDC_COLLECTIONADD, m_AddCollectionButton); + DDX_Control(pDX, IDC_COLLECTIONREMOVE, m_RemoveCollectionButton); + DDX_Control(pDX, IDC_COLLECTIONLISTLABEL, m_CollectionListLabel); + DDX_Control(pDX, IDC_CCOLL_SAVE, m_SaveButton); + DDX_Control(pDX, IDC_CCOLL_CANCEL, m_CancelButton); + DDX_Control(pDX, IDC_COLLECTIONLISTICON, m_CollectionListIcon); + DDX_Control(pDX, IDC_COLLECTIONSOURCELISTICON, m_CollectionSourceListIcon); + DDX_Control(pDX, IDC_COLLECTIONCREATESIGNCHECK, m_CollectionCreateSignNameKeyCheck); + DDX_Control(pDX, IDC_COLLECTIONCREATEFORMAT, m_CollectionCreateFormatCheck); +} + +void CCollectionCreateDialog::SetCollection(CCollection* pCollection, bool create) +{ + if (!pCollection) { + ASSERT(0); + return; + } + m_pCollection = pCollection; + m_bCreatemode = create; +} + +BOOL CCollectionCreateDialog::OnInitDialog(void) +{ + CDialog::OnInitDialog(); + InitWindowStyles(this); + + if (!m_pCollection) { + ASSERT(0); + return TRUE; + } + SetIcon(m_icoWnd = theApp.LoadIcon(_T("AABCollectionFileType")), FALSE); + if (m_bCreatemode) + SetWindowText(GetResString(IDS_CREATECOLLECTION)); + else + SetWindowText(GetResString(IDS_MODIFYCOLLECTION) + _T(": ") + m_pCollection->m_sCollectionName); + + m_CollectionListCtrl.Init(_T("CollectionCreateR")); + m_CollectionAvailListCtrl.Init(_T("CollectionCreateL")); + + m_AddCollectionButton.SetIcon(m_icoForward = theApp.LoadIcon(_T("FORWARD"))); + m_RemoveCollectionButton.SetIcon(m_icoBack = theApp.LoadIcon(_T("BACK"))); + m_CollectionListIcon.SetIcon(m_icoColl = theApp.LoadIcon(_T("AABCollectionFileType"))); + m_CollectionSourceListIcon.SetIcon(m_icoFiles = theApp.LoadIcon(_T("SharedFilesList"))); + + m_SaveButton.SetWindowText(GetResString(IDS_SAVE)); + m_CancelButton.SetWindowText(GetResString(IDS_CANCEL)); + m_CollectionCreateSignNameKeyCheck.SetWindowText(GetResString(IDS_COLL_SIGN)); + m_CollectionCreateFormatCheck.SetWindowText(GetResString(IDS_COLL_TEXTFORMAT)); + SetDlgItemText(IDC_CCOLL_STATIC_NAME, GetResString(IDS_SW_NAME) + _T(":")); + SetDlgItemText(IDC_CCOLL_BASICOPTIONS, GetResString(IDS_LD_BASICOPT)); + SetDlgItemText(IDC_CCOLL_ADVANCEDOPTIONS, GetResString(IDS_LD_ADVANCEDOPT)); + + AddAnchor(IDC_COLLECTIONAVAILLIST, TOP_LEFT, BOTTOM_CENTER); + AddAnchor(IDC_COLLECTIONLISTCTRL, TOP_CENTER, BOTTOM_RIGHT); + AddAnchor(IDC_COLLECTIONLISTLABEL, TOP_CENTER); + AddAnchor(IDC_COLLECTIONLISTICON, TOP_CENTER); + AddAnchor(IDC_COLLECTIONADD, MIDDLE_CENTER); + AddAnchor(IDC_COLLECTIONREMOVE, MIDDLE_CENTER); + AddAnchor(IDC_CCOLL_SAVE, BOTTOM_RIGHT); + AddAnchor(IDC_CCOLL_CANCEL, BOTTOM_RIGHT); + AddAnchor(IDC_CCOLL_BASICOPTIONS, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_CCOLL_ADVANCEDOPTIONS, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_CCOLL_STATIC_NAME, BOTTOM_LEFT); + AddAnchor(IDC_COLLECTIONNAMEEDIT, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_COLLECTIONCREATEFORMAT, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_COLLECTIONCREATESIGNCHECK, BOTTOM_LEFT, BOTTOM_RIGHT); + EnableSaveRestore(PREF_INI_SECTION); + + POSITION pos = m_pCollection->m_CollectionFilesMap.GetStartPosition(); + while (pos != NULL) + { + CSKey key; + CCollectionFile* pCollectionFile; + m_pCollection->m_CollectionFilesMap.GetNextAssoc(pos, key, pCollectionFile); + m_CollectionListCtrl.AddFileToList(pCollectionFile); + } + CString strTitle; + strTitle.Format(GetResString(IDS_COLLECTIONLIST) + _T(" (%u)"), m_CollectionListCtrl.GetItemCount()); + m_CollectionListLabel.SetWindowText(strTitle); + + OnBnClickedCollectionViewShared(); + + m_CollectionNameEdit.SetWindowText(::CleanupFilename(m_pCollection->m_sCollectionName)); + m_CollectionCreateFormatCheck.SetCheck(m_pCollection->m_bTextFormat); + OnBnClickedCollectionFormat(); + GetDlgItem(IDC_CCOLL_SAVE)->EnableWindow(m_CollectionListCtrl.GetItemCount() > 0); + + return TRUE; +} + +void CCollectionCreateDialog::AddSelectedFiles(void) +{ + CTypedPtrList knownFileList; + POSITION pos = m_CollectionAvailListCtrl.GetFirstSelectedItemPosition(); + while (pos != NULL) + { + int index = m_CollectionAvailListCtrl.GetNextSelectedItem(pos); + if (index >= 0) + knownFileList.AddTail((CKnownFile*)m_CollectionAvailListCtrl.GetItemData(index)); + } + + while (knownFileList.GetCount() > 0) + { + CAbstractFile* pAbstractFile = knownFileList.RemoveHead(); + CCollectionFile* pCollectionFile = m_pCollection->AddFileToCollection(pAbstractFile, true); + if (pCollectionFile) + m_CollectionListCtrl.AddFileToList(pCollectionFile); + } + + CString strTitle; + strTitle.Format(GetResString(IDS_COLLECTIONLIST) + _T(" (%u)"), m_CollectionListCtrl.GetItemCount()); + m_CollectionListLabel.SetWindowText(strTitle); + + GetDlgItem(IDC_CCOLL_SAVE)->EnableWindow(m_CollectionListCtrl.GetItemCount() > 0); +} + +void CCollectionCreateDialog::RemoveSelectedFiles(void) +{ + CTypedPtrList collectionFileList; + POSITION pos = m_CollectionListCtrl.GetFirstSelectedItemPosition(); + while (pos != NULL) + { + int index = m_CollectionListCtrl.GetNextSelectedItem(pos); + if (index >= 0) + collectionFileList.AddTail((CCollectionFile*)m_CollectionListCtrl.GetItemData(index)); + } + + while (collectionFileList.GetCount() > 0) + { + CCollectionFile* pCollectionFile = collectionFileList.RemoveHead(); + m_CollectionListCtrl.RemoveFileFromList(pCollectionFile); + m_pCollection->RemoveFileFromCollection(pCollectionFile); + } + + CString strTitle; + strTitle.Format(GetResString(IDS_COLLECTIONLIST) + _T(" (%u)"), m_CollectionListCtrl.GetItemCount()); + m_CollectionListLabel.SetWindowText(strTitle); + GetDlgItem(IDC_CCOLL_SAVE)->EnableWindow(m_CollectionListCtrl.GetItemCount() > 0); +} + +void CCollectionCreateDialog::OnBnClickedCollectionRemove() +{ + RemoveSelectedFiles(); +} + +void CCollectionCreateDialog::OnBnClickedCollectionAdd() +{ + AddSelectedFiles(); +} + +void CCollectionCreateDialog::OnBnClickedOk() +{ + //Some users have noted that the collection can at times + //save a collection with a invalid name... + OnEnKillFocusCollectionName(); + + CString sFileName; + m_CollectionNameEdit.GetWindowText(sFileName); + if (!sFileName.IsEmpty()) + { + m_pCollection->m_sCollectionAuthorName.Empty(); + m_pCollection->SetCollectionAuthorKey(NULL, 0); + m_pCollection->m_sCollectionName = sFileName; + m_pCollection->m_bTextFormat = (m_CollectionCreateFormatCheck.GetCheck() == BST_CHECKED); + + CString sFilePath; + sFilePath.Format(_T("%s\\%s.emulecollection"), thePrefs.GetMuleDirectory(EMULE_INCOMINGDIR), m_pCollection->m_sCollectionName); + + using namespace CryptoPP; + RSASSA_PKCS1v15_SHA_Signer* pSignkey = NULL; + if (m_CollectionCreateSignNameKeyCheck.GetCheck()) + { + bool bCreateNewKey = false; + HANDLE hKeyFile = ::CreateFile(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + _T("collectioncryptkey.dat"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hKeyFile != INVALID_HANDLE_VALUE) + { + if (::GetFileSize(hKeyFile, NULL) == 0) + bCreateNewKey = true; + ::CloseHandle(hKeyFile); + } + else + bCreateNewKey = true; + + if (bCreateNewKey) + { + try + { + AutoSeededRandomPool rng; + InvertibleRSAFunction privkey; + privkey.Initialize(rng, 1024); + Base64Encoder privkeysink(new FileSink(CStringA(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + _T("collectioncryptkey.dat")))); + privkey.DEREncode(privkeysink); + privkeysink.MessageEnd(); + } + catch(...) + { + ASSERT(0); + } + } + + try + { + FileSource filesource(CStringA(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + _T("collectioncryptkey.dat")), true,new Base64Decoder); + pSignkey = new RSASSA_PKCS1v15_SHA_Signer(filesource); + RSASSA_PKCS1v15_SHA_Verifier pubkey(*pSignkey); + byte abyMyPublicKey[1000]; + ArraySink asink(abyMyPublicKey, 1000); + pubkey.DEREncode(asink); + int nLen = asink.TotalPutLength(); + asink.MessageEnd(); + m_pCollection->SetCollectionAuthorKey(abyMyPublicKey, nLen); + } + catch(...) + { + ASSERT(0); + } + + m_pCollection->m_sCollectionAuthorName = thePrefs.GetUserNick(); + } + + if (!PathFileExists(sFilePath)) + { + m_pCollection->WriteToFileAddShared(pSignkey); + } + else + { + if (AfxMessageBox(GetResString(IDS_COLL_REPLACEEXISTING), MB_ICONWARNING | MB_ICONQUESTION | MB_DEFBUTTON2 | MB_YESNO) == IDNO) + return; + + bool bDeleteSuccessful = ShellDeleteFile(sFilePath); + if (bDeleteSuccessful) + { + CKnownFile* pKnownFile = theApp.knownfiles->FindKnownFileByPath(sFilePath); + if (pKnownFile) + { + theApp.sharedfiles->RemoveFile(pKnownFile, true); + if (pKnownFile->IsKindOf(RUNTIME_CLASS(CPartFile))) + theApp.emuledlg->transferwnd->GetDownloadList()->ClearCompleted(static_cast(pKnownFile)); + } + m_pCollection->WriteToFileAddShared(pSignkey); + } + else + { + AfxMessageBox(GetResString(IDS_COLL_ERR_DELETING),MB_ICONWARNING | MB_ICONQUESTION | MB_DEFBUTTON2 | MB_YESNO); + } + } + + delete pSignkey; + pSignkey = NULL; + + OnOK(); + } +} + +void CCollectionCreateDialog::UpdateAvailFiles(void) +{ + m_CollectionAvailListCtrl.DeleteAllItems(); + + CMap Files_Map; + if (m_bSharedFiles) + theApp.sharedfiles->CopySharedFileMap(Files_Map); + else + theApp.knownfiles->CopyKnownFileMap(Files_Map); + + POSITION pos = Files_Map.GetStartPosition(); + while (pos != NULL) + { + CCKey key; + CKnownFile* pKnownFile; + Files_Map.GetNextAssoc(pos, key, pKnownFile); + m_CollectionAvailListCtrl.AddFileToList(pKnownFile); + } +} + +void CCollectionCreateDialog::OnBnClickedCollectionViewShared() +{ + m_bSharedFiles = !m_bSharedFiles; + UpdateAvailFiles(); + CString strTitle; + strTitle.Format(_T(" ") + (m_bSharedFiles ? GetResString(IDS_SHARED) : GetResString(IDS_KNOWN)) + _T(" (%u)"), m_CollectionAvailListCtrl.GetItemCount()); + m_CollectionViewShareButton.SetWindowText(strTitle); +} + +void CCollectionCreateDialog::OnNmDblClkCollectionAvailList(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + AddSelectedFiles(); + *pResult = 0; +} + +void CCollectionCreateDialog::OnNmDblClkCollectionList(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + RemoveSelectedFiles(); + *pResult = 0; +} + +void CCollectionCreateDialog::OnEnKillFocusCollectionName() +{ + CString sFileName; + CString sNewFileName; + m_CollectionNameEdit.GetWindowText(sFileName); + sNewFileName = ValidFilename(sFileName); + if (sNewFileName.Compare(sFileName)) + m_CollectionNameEdit.SetWindowText(sNewFileName); +} + +void CCollectionCreateDialog::OnBnClickedCollectionFormat() +{ + if (m_CollectionCreateFormatCheck.GetCheck()) { + m_CollectionCreateSignNameKeyCheck.SetCheck(BST_UNCHECKED); + m_CollectionCreateSignNameKeyCheck.EnableWindow(FALSE); + } + else + m_CollectionCreateSignNameKeyCheck.EnableWindow(TRUE); +} diff --git a/CollectionCreateDialog.h b/CollectionCreateDialog.h new file mode 100644 index 00000000..9b1c0343 --- /dev/null +++ b/CollectionCreateDialog.h @@ -0,0 +1,76 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. +#pragma once +#include "CollectionListCtrl.h" +#include "ResizableLib\ResizableDialog.h" + +class CCollection; +class CCollectionFile; + +class CCollectionCreateDialog : public CResizableDialog +{ + DECLARE_DYNAMIC(CCollectionCreateDialog) + +public: + CCollectionCreateDialog(CWnd* pParent = NULL); // standard constructor + virtual ~CCollectionCreateDialog(); + +// Dialog Data + enum { IDD = IDD_COLLECTIONCREATEDIALOG }; + + void SetCollection(CCollection* pCollection, bool create); + +protected: + CCollection* m_pCollection; + CEdit m_CollectionNameEdit; + CCollectionListCtrl m_CollectionListCtrl; + CCollectionListCtrl m_CollectionAvailListCtrl; + bool m_bSharedFiles; + CButton m_CollectionViewShareButton; + CButton m_CollectionCreateFormatCheck; + HICON m_icoWnd; + HICON m_icoForward; + HICON m_icoBack; + HICON m_icoColl; + HICON m_icoFiles; + bool m_bCreatemode; + CButton m_AddCollectionButton; + CButton m_RemoveCollectionButton; + CStatic m_CollectionListLabel; + CButton m_SaveButton; + CButton m_CancelButton; + CStatic m_CollectionListIcon; + CStatic m_CollectionSourceListIcon; + CButton m_CollectionCreateSignNameKeyCheck; + + void AddSelectedFiles(void); + void RemoveSelectedFiles(void); + void UpdateAvailFiles(void); + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(void); + + DECLARE_MESSAGE_MAP() + afx_msg void OnBnClickedCollectionAdd(); + afx_msg void OnBnClickedCollectionFormat(); + afx_msg void OnBnClickedCollectionRemove(); + afx_msg void OnBnClickedCollectionViewShared(); + afx_msg void OnBnClickedOk(); + afx_msg void OnEnKillFocusCollectionName(); + afx_msg void OnNmDblClkCollectionAvailList(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmDblClkCollectionList(NMHDR *pNMHDR, LRESULT *pResult); +}; diff --git a/CollectionFile.cpp b/CollectionFile.cpp new file mode 100644 index 00000000..c33f1886 --- /dev/null +++ b/CollectionFile.cpp @@ -0,0 +1,196 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. + +#include "StdAfx.h" +#include "collectionfile.h" +#include "OtherFunctions.h" +#include "Packets.h" +#include "Ed2kLink.h" +#include "resource.h" +#include "Log.h" +#include "Kademlia/Kademlia/Entry.h" +#include "Kademlia/Kademlia/Tag.h" + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +IMPLEMENT_DYNAMIC(CCollectionFile, CAbstractFile) + +CCollectionFile::CCollectionFile(void) +{ +} + +CCollectionFile::CCollectionFile(CFileDataIO* in_data) +{ + UINT tagcount = in_data->ReadUInt32(); + + for (UINT i = 0; i < tagcount; i++) + { + CTag* toadd = new CTag(in_data, true); + if (toadd) + taglist.Add(toadd); + } + + CTag* pTagHash = GetTag(FT_FILEHASH); + if(pTagHash) + SetFileHash(pTagHash->GetHash()); + else + ASSERT(0); + + pTagHash = GetTag(FT_AICH_HASH); + if (pTagHash != NULL && pTagHash->IsStr()) + { + CAICHHash hash; + if (DecodeBase32(pTagHash->GetStr(), hash) == (UINT)CAICHHash::GetHashSize()) + m_FileIdentifier.SetAICHHash(hash); + else + ASSERT( false ); + } + + // here we have two choices + // - if the server/client sent us a filetype, we could use it (though it could be wrong) + // - we always trust our filetype list and determine the filetype by the extension of the file + // + // if we received a filetype from server, we use it. + // if we did not receive a filetype, we determine it by examining the file's extension. + // + // but, in no case, we will use the receive file type when adding this search result to the download queue, to avoid + // that we are using 'wrong' file types in part files. (this has to be handled when creating the part files) + const CString& rstrFileType = GetStrTagValue(FT_FILETYPE); + SetFileName(GetStrTagValue(FT_FILENAME), false, rstrFileType.IsEmpty()); + SetFileSize(GetInt64TagValue(FT_FILESIZE)); + if (!rstrFileType.IsEmpty()) + { + if (_tcscmp(rstrFileType, _T(ED2KFTSTR_PROGRAM))==0) + { + CString strDetailFileType = GetFileTypeByName(GetFileName()); + if (!strDetailFileType.IsEmpty()) + SetFileType(strDetailFileType); + else + SetFileType(rstrFileType); + } + else + SetFileType(rstrFileType); + } + + if(GetFileSize() == (uint64)0 || !GetFileName().Compare(_T(""))) + ASSERT(0); +} + +CCollectionFile::CCollectionFile(CAbstractFile* pAbstractFile) : CAbstractFile(pAbstractFile) +{ + ClearTags(); + + taglist.Add(new CTag(FT_FILEHASH, pAbstractFile->GetFileHash())); + taglist.Add(new CTag(FT_FILESIZE, pAbstractFile->GetFileSize(), true)); + taglist.Add(new CTag(FT_FILENAME, pAbstractFile->GetFileName())); + + if (m_FileIdentifier.HasAICHHash()) + taglist.Add(new CTag(FT_AICH_HASH, m_FileIdentifier.GetAICHHash().GetString())); + + if(!pAbstractFile->GetFileComment().IsEmpty()) + taglist.Add(new CTag(FT_FILECOMMENT, pAbstractFile->GetFileComment())); + + if(pAbstractFile->GetFileRating()) + taglist.Add(new CTag(FT_FILERATING, pAbstractFile->GetFileRating())); + + UpdateFileRatingCommentAvail(); +} + +bool CCollectionFile::InitFromLink(CString sLink) +{ + CED2KLink* pLink = NULL; + CED2KFileLink* pFileLink = NULL; + try + { + pLink = CED2KLink::CreateLinkFromUrl(sLink); + if(!pLink) + throw GetResString(IDS_ERR_NOTAFILELINK); + pFileLink = pLink->GetFileLink(); + if (!pFileLink) + throw GetResString(IDS_ERR_NOTAFILELINK); + } + catch (CString error) + { + CString strBuffer; + strBuffer.Format(GetResString(IDS_ERR_INVALIDLINK),error); + LogError(LOG_STATUSBAR, GetResString(IDS_ERR_LINKERROR), strBuffer); + return false; + } + + taglist.Add(new CTag(FT_FILEHASH, pFileLink->GetHashKey())); + m_FileIdentifier.SetMD4Hash(pFileLink->GetHashKey()); + + taglist.Add(new CTag(FT_FILESIZE, pFileLink->GetSize(), true)); + SetFileSize(pFileLink->GetSize()); + + taglist.Add(new CTag(FT_FILENAME, pFileLink->GetName())); + SetFileName(pFileLink->GetName(), false, false); + + if (pFileLink->HasValidAICHHash()) + { + taglist.Add(new CTag(FT_AICH_HASH, pFileLink->GetAICHHash().GetString())); + m_FileIdentifier.SetAICHHash(pFileLink->GetAICHHash()); + } + + delete pLink; + return true; +} + +CCollectionFile::~CCollectionFile(void) +{ +} + +void CCollectionFile::WriteCollectionInfo(CFileDataIO *out_data) +{ + out_data->WriteUInt32(taglist.GetSize()); + + for (int i = 0; i < taglist.GetSize(); i++) + { + CTag tempTag(*taglist.GetAt(i)); + tempTag.WriteNewEd2kTag(out_data, utf8strRaw); + } +} + +void CCollectionFile::UpdateFileRatingCommentAvail(bool /*bForceUpdate*/) +{ + m_bHasComment = false; + UINT uRatings = 0; + UINT uUserRatings = 0; + + for(POSITION pos = m_kadNotes.GetHeadPosition(); pos != NULL; ) + { + Kademlia::CEntry* entry = m_kadNotes.GetNext(pos); + if (!m_bHasComment && !entry->GetStrTagValue(TAG_DESCRIPTION).IsEmpty()) + m_bHasComment = true; + UINT rating = (UINT)entry->GetIntTagValue(TAG_FILERATING); + if (rating != 0) + { + uRatings++; + uUserRatings += rating; + } + } + + if (uRatings) + m_uUserRating = uUserRatings / uRatings; + else + m_uUserRating = 0; +} diff --git a/CollectionFile.h b/CollectionFile.h new file mode 100644 index 00000000..64152b84 --- /dev/null +++ b/CollectionFile.h @@ -0,0 +1,36 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "abstractfile.h" + +class CFileDataIO; + +class CCollectionFile : public CAbstractFile +{ + DECLARE_DYNAMIC(CCollectionFile) + +public: + CCollectionFile(void); + CCollectionFile(CFileDataIO* in_data); + CCollectionFile(CAbstractFile* copyfrom); + virtual ~CCollectionFile(void); + + bool InitFromLink(CString sLink); + void WriteCollectionInfo(CFileDataIO* out_data); + virtual void UpdateFileRatingCommentAvail(bool bForceUpdate = false); +}; diff --git a/CollectionListCtrl.cpp b/CollectionListCtrl.cpp new file mode 100644 index 00000000..5b81b70d --- /dev/null +++ b/CollectionListCtrl.cpp @@ -0,0 +1,284 @@ +//this file is part of eMule +//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "CollectionListCtrl.h" +#include "OtherFunctions.h" +#include "AbstractFile.h" +#include "MetaDataDlg.h" +#include "HighColorTab.hpp" +#include "ListViewWalkerPropertySheet.h" +#include "UserMsgs.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[]=__FILE__; +#endif + + +////////////////////////////////////////////////////////////////////////////// +// CCollectionFileDetailsSheet + +class CCollectionFileDetailsSheet : public CListViewWalkerPropertySheet +{ + DECLARE_DYNAMIC(CCollectionFileDetailsSheet) + +public: + CCollectionFileDetailsSheet(CTypedPtrList& aFiles, UINT uPshInvokePage = 0, CListCtrlItemWalk* pListCtrl = NULL); + virtual ~CCollectionFileDetailsSheet(); + +protected: + CMetaDataDlg m_wndMetaData; + + UINT m_uPshInvokePage; + static LPCTSTR m_pPshStartPage; + + void UpdateTitle(); + + virtual BOOL OnInitDialog(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnDestroy(); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); +}; + +LPCTSTR CCollectionFileDetailsSheet::m_pPshStartPage; + +IMPLEMENT_DYNAMIC(CCollectionFileDetailsSheet, CListViewWalkerPropertySheet) + +BEGIN_MESSAGE_MAP(CCollectionFileDetailsSheet, CListViewWalkerPropertySheet) + ON_WM_DESTROY() + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) +END_MESSAGE_MAP() + +CCollectionFileDetailsSheet::CCollectionFileDetailsSheet(CTypedPtrList& aFiles, UINT uPshInvokePage, CListCtrlItemWalk* pListCtrl) + : CListViewWalkerPropertySheet(pListCtrl) +{ + m_uPshInvokePage = uPshInvokePage; + POSITION pos = aFiles.GetHeadPosition(); + while (pos) + m_aItems.Add(aFiles.GetNext(pos)); + m_psh.dwFlags &= ~PSH_HASHELP; + + m_wndMetaData.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndMetaData.m_psp.dwFlags |= PSP_USEICONID; + m_wndMetaData.m_psp.pszIcon = _T("METADATA"); + if (m_aItems.GetSize() == 1 && thePrefs.IsExtControlsEnabled()) { + m_wndMetaData.SetFiles(&m_aItems); + AddPage(&m_wndMetaData); + } + + LPCTSTR pPshStartPage = m_pPshStartPage; + if (m_uPshInvokePage != 0) + pPshStartPage = MAKEINTRESOURCE(m_uPshInvokePage); + for (int i = 0; i < m_pages.GetSize(); i++) + { + CPropertyPage* pPage = GetPage(i); + if (pPage->m_psp.pszTemplate == pPshStartPage) + { + m_psh.nStartPage = i; + break; + } + } +} + +CCollectionFileDetailsSheet::~CCollectionFileDetailsSheet() +{ +} + +void CCollectionFileDetailsSheet::OnDestroy() +{ + if (m_uPshInvokePage == 0) + m_pPshStartPage = GetPage(GetActiveIndex())->m_psp.pszTemplate; + CListViewWalkerPropertySheet::OnDestroy(); +} + +BOOL CCollectionFileDetailsSheet::OnInitDialog() +{ + EnableStackedTabs(FALSE); + BOOL bResult = CListViewWalkerPropertySheet::OnInitDialog(); + HighColorTab::UpdateImageList(*this); + InitWindowStyles(this); + EnableSaveRestore(_T("CollectionFileDetailsSheet")); // call this after(!) OnInitDialog + UpdateTitle(); + return bResult; +} + +LRESULT CCollectionFileDetailsSheet::OnDataChanged(WPARAM, LPARAM) +{ + UpdateTitle(); + return 1; +} + +void CCollectionFileDetailsSheet::UpdateTitle() +{ + if (m_aItems.GetSize() == 1) + SetWindowText(GetResString(IDS_DETAILS) + _T(": ") + STATIC_DOWNCAST(CAbstractFile, m_aItems[0])->GetFileName()); + else + SetWindowText(GetResString(IDS_DETAILS)); +} + + + +////////////////////////////////////////////////////////////////////////////// +// CCollectionListCtrl + +enum ECols +{ + colName = 0, + colSize, + colHash +}; + +IMPLEMENT_DYNAMIC(CCollectionListCtrl, CMuleListCtrl) + +BEGIN_MESSAGE_MAP(CCollectionListCtrl, CMuleListCtrl) + ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnLvnColumnClick) + ON_NOTIFY_REFLECT(NM_RCLICK, OnNmRClick) +END_MESSAGE_MAP() + +CCollectionListCtrl::CCollectionListCtrl() + : CListCtrlItemWalk(this) +{ +} + +CCollectionListCtrl::~CCollectionListCtrl() +{ +} + +void CCollectionListCtrl::Init(CString strNameAdd) +{ + SetPrefsKey(_T("CollectionListCtrl") + strNameAdd); + + ASSERT( GetStyle() & LVS_SHAREIMAGELISTS ); + SendMessage(LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)theApp.GetSystemImageList()); + + ASSERT( (GetStyle() & LVS_SINGLESEL) == 0 ); + SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + + InsertColumn(colName, GetResString(IDS_DL_FILENAME), LVCFMT_LEFT, DFLT_FILENAME_COL_WIDTH); + InsertColumn(colSize, GetResString(IDS_DL_SIZE), LVCFMT_RIGHT, DFLT_SIZE_COL_WIDTH); + InsertColumn(colHash, GetResString(IDS_FILEHASH), LVCFMT_LEFT, DFLT_HASH_COL_WIDTH); + + LoadSettings(); + SetSortArrow(); + SortItems(SortProc, MAKELONG(GetSortItem(), (GetSortAscending() ? 0 : 1))); +} + +void CCollectionListCtrl::OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult) +{ + NMLISTVIEW *pNMListView = (NMLISTVIEW *)pNMHDR; + + // Determine ascending based on whether already sorted on this column + int iSortItem = GetSortItem(); + bool bOldSortAscending = GetSortAscending(); + bool bSortAscending = (iSortItem != pNMListView->iSubItem) ? true : !bOldSortAscending; + + // Item is column clicked + iSortItem = pNMListView->iSubItem; + + // Sort table + UpdateSortHistory(MAKELONG(iSortItem, (bSortAscending ? 0 : 0x0001))); + SetSortArrow(iSortItem, bSortAscending); + SortItems(SortProc, MAKELONG(iSortItem, (bSortAscending ? 0 : 0x0001))); + + *pResult = 0; +} + +int CCollectionListCtrl::SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + const CAbstractFile *item1 = (CAbstractFile *)lParam1; + const CAbstractFile *item2 = (CAbstractFile *)lParam2; + if (item1 == NULL || item2 == NULL) + return 0; + + int iResult; + switch (LOWORD(lParamSort)) + { + case colName: + iResult = CompareLocaleStringNoCase(item1->GetFileName(),item2->GetFileName()); + break; + + case colSize: + iResult = CompareUnsigned64(item1->GetFileSize(), item2->GetFileSize()); + break; + + case colHash: + iResult = memcmp(item1->GetFileHash(), item2->GetFileHash(), 16); + break; + + default: + return 0; + } + if (HIWORD(lParamSort)) + iResult = -iResult; + return iResult; +} + +void CCollectionListCtrl::OnNmRClick(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + CTypedPtrList abstractFileList; + POSITION pos = GetFirstSelectedItemPosition(); + while (pos != NULL) + { + int index = GetNextSelectedItem(pos); + if (index >= 0) + abstractFileList.AddTail((CAbstractFile*)GetItemData(index)); + } + + if(abstractFileList.GetCount() > 0) + { + CCollectionFileDetailsSheet dialog(abstractFileList, 0, this); + dialog.DoModal(); + } + *pResult = 0; +} + +void CCollectionListCtrl::AddFileToList(CAbstractFile* pAbstractFile) +{ + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)pAbstractFile; + int iItem = FindItem(&find); + if (iItem != -1) + { + ASSERT(0); + return; + } + + int iImage = theApp.GetFileTypeSystemImageIdx(pAbstractFile->GetFileName()); + iItem = InsertItem(LVIF_TEXT | LVIF_PARAM | (iImage > 0 ? LVIF_IMAGE : 0), GetItemCount(), NULL, 0, 0, iImage, (LPARAM)pAbstractFile); + if (iItem != -1) + { + SetItemText(iItem,colName,pAbstractFile->GetFileName()); + SetItemText(iItem,colSize,CastItoXBytes(pAbstractFile->GetFileSize())); + SetItemText(iItem,colHash,::md4str(pAbstractFile->GetFileHash())); + } +} + +void CCollectionListCtrl::RemoveFileFromList(CAbstractFile* pAbstractFile) +{ + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)pAbstractFile; + int iItem = FindItem(&find); + if (iItem != -1) + DeleteItem(iItem); + else + ASSERT(0); +} diff --git a/CollectionListCtrl.h b/CollectionListCtrl.h new file mode 100644 index 00000000..2636723c --- /dev/null +++ b/CollectionListCtrl.h @@ -0,0 +1,46 @@ +//this file is part of eMule +//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "MuleListCtrl.h" +#include "ListCtrlItemWalk.h" + +class CAbstractFile; + +class CCollectionListCtrl : public CMuleListCtrl, public CListCtrlItemWalk +{ + DECLARE_DYNAMIC(CCollectionListCtrl) + +public: + CCollectionListCtrl(); + virtual ~CCollectionListCtrl(); + + void Init(CString strNameAdd); + void Localize(); + + void AddFileToList(CAbstractFile* pAbstractFile); + void RemoveFileFromList(CAbstractFile* pAbstractFile); + +protected: + static int CALLBACK SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + + void SetAllIcons(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmRClick(NMHDR *pNMHDR, LRESULT *pResult); +}; diff --git a/CollectionViewDialog.cpp b/CollectionViewDialog.cpp new file mode 100644 index 00000000..f8a6ccf8 --- /dev/null +++ b/CollectionViewDialog.cpp @@ -0,0 +1,210 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "emuledlg.h" +#include "CollectionViewDialog.h" +#include "OtherFunctions.h" +#include "Collection.h" +#include "CollectionFile.h" +#include "DownloadQueue.h" +#include "TransferDlg.h" +#include "CatDialog.h" +#include "SearchDlg.h" +#include "Partfile.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define PREF_INI_SECTION _T("CollectionViewDlg") + +enum ECols +{ + colName = 0, + colSize, + colHash +}; + +IMPLEMENT_DYNAMIC(CCollectionViewDialog, CDialog) + +BEGIN_MESSAGE_MAP(CCollectionViewDialog, CResizableDialog) + ON_BN_CLICKED(IDC_VCOLL_CLOSE, OnBnClickedOk) + ON_BN_CLICKED(IDC_VIEWCOLLECTIONDL, OnBnClickedViewCollection) + ON_NOTIFY(NM_DBLCLK, IDC_COLLECTIONVEWLIST, OnNmDblClkCollectionList) +END_MESSAGE_MAP() + +CCollectionViewDialog::CCollectionViewDialog(CWnd* pParent /*=NULL*/) + : CResizableDialog(CCollectionViewDialog::IDD, pParent) + , m_pCollection(NULL) +{ + m_icoWnd = NULL; + m_icoColl = NULL; +} + +CCollectionViewDialog::~CCollectionViewDialog() +{ + if (m_icoWnd) + VERIFY( DestroyIcon(m_icoWnd) ); + if (m_icoColl) + VERIFY( DestroyIcon(m_icoColl) ); +} + +void CCollectionViewDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_COLLECTIONVEWLIST, m_CollectionViewList); + DDX_Control(pDX, IDC_COLLECTIONVIEWCATEGORYCHECK, m_AddNewCatagory); + DDX_Control(pDX, IDC_COLLECTIONVIEWLISTLABEL, m_CollectionViewListLabel); + DDX_Control(pDX, IDC_COLLECTIONVIEWLISTICON, m_CollectionViewListIcon); + DDX_Control(pDX, IDC_VIEWCOLLECTIONDL, m_CollectionDownload); + DDX_Control(pDX, IDC_VCOLL_CLOSE, m_CollectionExit); + DDX_Control(pDX, IDC_COLLECTIONVIEWAUTHOR, m_CollectionViewAuthor); + DDX_Control(pDX, IDC_COLLECTIONVIEWAUTHORKEY, m_CollectionViewAuthorKey); +} + +void CCollectionViewDialog::SetCollection(CCollection* pCollection) +{ + if (!pCollection) { + ASSERT(0); + return; + } + m_pCollection = pCollection; +} + +BOOL CCollectionViewDialog::OnInitDialog(void) +{ + CDialog::OnInitDialog(); + InitWindowStyles(this); + + if (!m_pCollection) { + ASSERT(0); + return TRUE; + } + + m_CollectionViewList.Init(_T("CollectionView")); + SetIcon(m_icoWnd = theApp.LoadIcon(_T("Collection_View")), FALSE); + + m_AddNewCatagory.SetCheck(false); + + SetWindowText(GetResString(IDS_VIEWCOLLECTION) + _T(": ") + m_pCollection->m_sCollectionName); + + m_CollectionViewListIcon.SetIcon(m_icoColl = theApp.LoadIcon(_T("AABCollectionFileType"))); + m_CollectionDownload.SetWindowText(GetResString(IDS_DOWNLOAD)); + m_CollectionExit.SetWindowText(GetResString(IDS_CW_CLOSE)); + SetDlgItemText(IDC_COLLECTIONVIEWAUTHORLABEL, GetResString(IDS_AUTHOR) + _T(":")); + SetDlgItemText(IDC_COLLECTIONVIEWAUTHORKEYLABEL, GetResString(IDS_AUTHORKEY) + _T(":")); + SetDlgItemText(IDC_COLLECTIONVIEWCATEGORYCHECK, GetResString(IDS_COLL_ADDINCAT)); + SetDlgItemText(IDC_VCOLL_DETAILS, GetResString(IDS_DETAILS)); + SetDlgItemText(IDC_VCOLL_OPTIONS, GetResString(IDS_OPTIONS)); + + m_CollectionViewAuthor.SetWindowText(m_pCollection->m_sCollectionAuthorName); + m_CollectionViewAuthorKey.SetWindowText(m_pCollection->GetAuthorKeyHashString()); + + AddAnchor(IDC_COLLECTIONVEWLIST, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_VCOLL_DETAILS, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_VCOLL_OPTIONS, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_COLLECTIONVIEWAUTHORLABEL, BOTTOM_LEFT); + AddAnchor(IDC_COLLECTIONVIEWAUTHORKEYLABEL, BOTTOM_LEFT); + AddAnchor(IDC_COLLECTIONVIEWCATEGORYCHECK, BOTTOM_LEFT); + AddAnchor(IDC_COLLECTIONVIEWAUTHOR, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_COLLECTIONVIEWAUTHORKEY, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_VCOLL_CLOSE, BOTTOM_RIGHT); + AddAnchor(IDC_VIEWCOLLECTIONDL, BOTTOM_RIGHT); + EnableSaveRestore(PREF_INI_SECTION); + + POSITION pos = m_pCollection->m_CollectionFilesMap.GetStartPosition(); + while (pos != NULL) + { + CCollectionFile* pCollectionFile; + CSKey key; + m_pCollection->m_CollectionFilesMap.GetNextAssoc(pos, key, pCollectionFile); + + int iImage = theApp.GetFileTypeSystemImageIdx(pCollectionFile->GetFileName()); + int iItem = m_CollectionViewList.InsertItem(LVIF_TEXT | LVIF_PARAM | (iImage > 0 ? LVIF_IMAGE : 0), m_CollectionViewList.GetItemCount(), NULL, 0, 0, iImage, (LPARAM)pCollectionFile); + if (iItem != -1) + { + m_CollectionViewList.SetItemText(iItem, colName, pCollectionFile->GetFileName()); + m_CollectionViewList.SetItemText(iItem, colSize, CastItoXBytes(pCollectionFile->GetFileSize())); + m_CollectionViewList.SetItemText(iItem, colHash, md4str(pCollectionFile->GetFileHash())); + } + } + + int iItem = m_CollectionViewList.GetItemCount(); + while (iItem) + m_CollectionViewList.SetItemState(--iItem, LVIS_SELECTED, LVIS_SELECTED); + + CString strTitle; + strTitle.Format(GetResString(IDS_COLLECTIONLIST) + _T(" (%u)"), m_CollectionViewList.GetItemCount()); + m_CollectionViewListLabel.SetWindowText(strTitle); + + return TRUE; +} + +void CCollectionViewDialog::OnNmDblClkCollectionList(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + DownloadSelected(); + *pResult = 0; +} + +void CCollectionViewDialog::DownloadSelected(void) +{ + int iNewIndex = 0; + for (int iIndex = 1; iIndex < thePrefs.GetCatCount(); iIndex++) + { + if (!m_pCollection->m_sCollectionName.CompareNoCase(thePrefs.GetCategory(iIndex)->strTitle)) + { + iNewIndex = iIndex; + break; + } + } + + if (m_AddNewCatagory.GetCheck() && !iNewIndex) + { + iNewIndex = theApp.emuledlg->transferwnd->AddCategory(m_pCollection->m_sCollectionName, thePrefs.GetMuleDirectory(EMULE_INCOMINGDIR), _T(""), _T(""), true); + theApp.emuledlg->searchwnd->UpdateCatTabs(); + } + + CTypedPtrList collectionFileList; + POSITION pos = m_CollectionViewList.GetFirstSelectedItemPosition(); + while (pos != NULL) + { + int index = m_CollectionViewList.GetNextSelectedItem(pos); + if (index >= 0) + collectionFileList.AddTail((CCollectionFile*)m_CollectionViewList.GetItemData(index)); + } + + while (collectionFileList.GetCount() > 0) + { + CCollectionFile* pCollectionFile = collectionFileList.RemoveHead(); + if (pCollectionFile) + theApp.downloadqueue->AddSearchToDownload(pCollectionFile->GetED2kLink(), thePrefs.AddNewFilesPaused(), iNewIndex); + } +} + +void CCollectionViewDialog::OnBnClickedViewCollection() +{ + DownloadSelected(); + OnBnClickedOk(); +} + +void CCollectionViewDialog::OnBnClickedOk() +{ + OnOK(); +} diff --git a/CollectionViewDialog.h b/CollectionViewDialog.h new file mode 100644 index 00000000..a9adc074 --- /dev/null +++ b/CollectionViewDialog.h @@ -0,0 +1,58 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. +#pragma once +#include "CollectionListCtrl.h" +#include "ResizableLib\ResizableDialog.h" + +class CCollection; + +class CCollectionViewDialog : public CResizableDialog +{ + DECLARE_DYNAMIC(CCollectionViewDialog) + +public: + CCollectionViewDialog(CWnd* pParent = NULL); // standard constructor + virtual ~CCollectionViewDialog(); + + // Dialog Data + enum { IDD = IDD_COLLECTIONVIEWDIALOG }; + + void SetCollection(CCollection* pCollection); + +protected: + CButton m_AddNewCatagory; + CStatic m_CollectionViewListLabel; + CStatic m_CollectionViewListIcon; + CButton m_CollectionDownload; + CButton m_CollectionExit; + CEdit m_CollectionViewAuthor; + CEdit m_CollectionViewAuthorKey; + CCollectionListCtrl m_CollectionViewList; + CCollection* m_pCollection; + HICON m_icoWnd; + HICON m_icoColl; + + void DownloadSelected(void); + + virtual BOOL OnInitDialog(void); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + + DECLARE_MESSAGE_MAP() + afx_msg void OnBnClickedOk(); + afx_msg void OnBnClickedViewCollection(); + afx_msg void OnNmDblClkCollectionList(NMHDR *pNMHDR, LRESULT *pResult); +}; diff --git a/ColorButton.cpp b/ColorButton.cpp new file mode 100644 index 00000000..483ea39f --- /dev/null +++ b/ColorButton.cpp @@ -0,0 +1,448 @@ +//*************************************************************************** +// +// AUTHOR: James White (feel free to remove or otherwise mangle any part) +// +//*************************************************************************** +#include "stdafx.h" +#include "ColorButton.h" +#include "UserMsgs.h" + +//*********************************************************************** +//** MFC Debug Symbols ** +//*********************************************************************** +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +//*********************************************************************** +//** DDX Method ** +//*********************************************************************** + +void AFXAPI DDX_ColorButton(CDataExchange *pDX, int nIDC, COLORREF& crColour) +{ + HWND hWndCtrl = pDX->PrepareCtrl(nIDC); + ASSERT (hWndCtrl != NULL); + + CColorButton* pColourButton = (CColorButton*) CWnd::FromHandle(hWndCtrl); + if (pDX->m_bSaveAndValidate) + { + crColour = pColourButton->Color; + } + else // initializing + { + pColourButton->Color = crColour; + } +} + +//*********************************************************************** +//** Constants ** +//*********************************************************************** +const int g_ciArrowSizeX = 4 ; +const int g_ciArrowSizeY = 2 ; + +//*********************************************************************** +//** MFC Macros ** +//*********************************************************************** +IMPLEMENT_DYNCREATE(CColorButton, CButton) + +//*********************************************************************** +// Method: CColorButton::CColorButton(void) +// Notes: Default Constructor. +//*********************************************************************** +CColorButton::CColorButton(void): + _Inherited(), + m_Color(CLR_DEFAULT), + m_DefaultColor(::GetSysColor(COLOR_APPWORKSPACE)), + m_strDefaultText(_T("Automatic")), + m_strCustomText(_T("More Colors...")), + m_bPopupActive(FALSE), + m_bTrackSelection(FALSE) +{ +} + +//*********************************************************************** +// Method: CColorButton::~CColorButton(void) +// Notes: Destructor. +//*********************************************************************** +CColorButton::~CColorButton(void) +{ +} + +//*********************************************************************** +// Method: CColorButton::GetColor() +// Notes: None. +//*********************************************************************** +COLORREF CColorButton::GetColor(void) const +{ + return m_Color; +} + + +//*********************************************************************** +// Method: CColorButton::SetColor() +// Notes: None. +//*********************************************************************** +void CColorButton::SetColor(COLORREF Color) +{ + m_Color = Color; + + if (::IsWindow(m_hWnd)) + RedrawWindow(); +} + + +//*********************************************************************** +// Method: CColorButton::GetDefaultColor() +// Notes: None. +//*********************************************************************** +COLORREF CColorButton::GetDefaultColor(void) const +{ + return m_DefaultColor; +} + +//*********************************************************************** +// Method: CColorButton::SetDefaultColor() +// Notes: None. +//*********************************************************************** +void CColorButton::SetDefaultColor(COLORREF Color) +{ + m_DefaultColor = Color; +} + +//*********************************************************************** +// Method: CColorButton::SetCustomText() +// Notes: None. +//*********************************************************************** +void CColorButton::SetCustomText(LPCTSTR tszText) +{ + m_strCustomText = tszText; +} + +//*********************************************************************** +// Method: CColorButton::SetDefaultText() +// Notes: None. +//*********************************************************************** +void CColorButton::SetDefaultText(LPCTSTR tszText) +{ + m_strDefaultText = tszText; +} + + +//*********************************************************************** +// Method: CColorButton::SetTrackSelection() +// Notes: None. +//*********************************************************************** +void CColorButton::SetTrackSelection(BOOL bTrack) +{ + m_bTrackSelection = bTrack; +} + +//*********************************************************************** +// Method: CColorButton::GetTrackSelection() +// Notes: None. +//*********************************************************************** +BOOL CColorButton::GetTrackSelection(void) const +{ + return m_bTrackSelection; +} + +//*********************************************************************** +//** CButton Overrides ** +//*********************************************************************** +void CColorButton::PreSubclassWindow() +{ + ModifyStyle(0, BS_OWNERDRAW); + + _Inherited::PreSubclassWindow(); +} + +//*********************************************************************** +//** Message Handlers ** +//*********************************************************************** +BEGIN_MESSAGE_MAP(CColorButton, CButton) + //{{AFX_MSG_MAP(CColorButton) + ON_CONTROL_REFLECT_EX(BN_CLICKED, OnClicked) + ON_WM_CREATE() + //}}AFX_MSG_MAP + ON_MESSAGE(UM_CPN_SELENDOK, OnSelEndOK) + ON_MESSAGE(UM_CPN_SELENDCANCEL, OnSelEndCancel) + ON_MESSAGE(UM_CPN_SELCHANGE, OnSelChange) +END_MESSAGE_MAP() + + +//*********************************************************************** +// Method: CColorButton::OnSelEndOK() +// Notes: None. +//*********************************************************************** +LONG CColorButton::OnSelEndOK(UINT lParam, LONG /*wParam*/) +{ + m_bPopupActive = FALSE; + + COLORREF OldColor = m_Color; + + Color = (COLORREF)lParam; + + CWnd *pParent = GetParent(); + + if (pParent) + { + pParent->SendMessage(UM_CPN_CLOSEUP, lParam, (WPARAM) GetDlgCtrlID()); + pParent->SendMessage(UM_CPN_SELENDOK, lParam, (WPARAM) GetDlgCtrlID()); + } + + if (OldColor != m_Color) + if (pParent) pParent->SendMessage(UM_CPN_SELCHANGE, (m_Color!=CLR_DEFAULT)? m_Color:m_DefaultColor, (WPARAM) GetDlgCtrlID()); + + return TRUE; +} + + +//*********************************************************************** +// Method: CColorButton::OnSelEndCancel() +// Notes: None. +//*********************************************************************** +LONG CColorButton::OnSelEndCancel(UINT lParam, LONG /*wParam*/) +{ + m_bPopupActive = FALSE; + + Color = (COLORREF)lParam; + + CWnd *pParent = GetParent(); + + if (pParent) + { + pParent->SendMessage(UM_CPN_CLOSEUP, lParam, (WPARAM) GetDlgCtrlID()); + pParent->SendMessage(UM_CPN_SELENDCANCEL, lParam, (WPARAM) GetDlgCtrlID()); + } + + return TRUE; +} + + +//*********************************************************************** +// Method: CColorButton::OnSelChange() +// Notes: None. +//*********************************************************************** +LONG CColorButton::OnSelChange(UINT lParam, LONG /*wParam*/) +{ + if (m_bTrackSelection) + Color = (COLORREF)lParam; + + CWnd *pParent = GetParent(); + + if (pParent) pParent->SendMessage(UM_CPN_SELCHANGE, (m_Color!=CLR_DEFAULT)? m_Color:m_DefaultColor, (WPARAM) GetDlgCtrlID()); //Cax2 defaultcol fix + + return TRUE; +} + +//*********************************************************************** +// Method: CColorButton::OnCreate() +// Notes: None. +//*********************************************************************** +int CColorButton::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CButton::OnCreate(lpCreateStruct) == -1) + return -1; + + return 0; +} + +//*********************************************************************** +// Method: CColorButton::OnClicked() +// Notes: None. +//*********************************************************************** +BOOL CColorButton::OnClicked() +{ + m_bPopupActive = TRUE; + + CRect rDraw; + GetWindowRect(rDraw); + + new CColourPopup(CPoint(rDraw.left, rDraw.bottom), // Point to display popup + m_Color, // Selected colour + this, // parent + m_strDefaultText, // "Default" text area + m_strCustomText); // Custom Text + + CWnd *pParent = GetParent(); + + if (pParent) + pParent->SendMessage(UM_CPN_DROPDOWN, (LPARAM)m_Color, (WPARAM) GetDlgCtrlID()); + + return TRUE; +} + + + +//*********************************************************************** +// Method: CColorButton::DrawItem() +// Notes: None. +//*********************************************************************** +void CColorButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + ASSERT(lpDrawItemStruct); + + CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); + UINT state = lpDrawItemStruct->itemState; + CRect rDraw = lpDrawItemStruct->rcItem; + CRect rArrow; + + if (m_bPopupActive) + state |= ODS_SELECTED|ODS_FOCUS; + + //****************************************************** + //** Draw Outer Edge + //****************************************************** + UINT uFrameState = DFCS_BUTTONPUSH|DFCS_ADJUSTRECT; + + if (state & ODS_SELECTED) + uFrameState |= DFCS_PUSHED; + + if (state & ODS_DISABLED) + uFrameState |= DFCS_INACTIVE; + + pDC->DrawFrameControl(&rDraw, + DFC_BUTTON, + uFrameState); + + + if (state & ODS_SELECTED) + rDraw.OffsetRect(1,1); + + //****************************************************** + //** Draw Focus + //****************************************************** + if (state & ODS_FOCUS) + { + RECT rFocus = {rDraw.left, + rDraw.top, + rDraw.right - 1, + rDraw.bottom}; + + pDC->DrawFocusRect(&rFocus); + } + + rDraw.DeflateRect(::GetSystemMetrics(SM_CXEDGE), + ::GetSystemMetrics(SM_CYEDGE)); + + //****************************************************** + //** Draw Arrow + //****************************************************** + rArrow.left = rDraw.right - g_ciArrowSizeX - ::GetSystemMetrics(SM_CXEDGE) /2; + rArrow.right = rArrow.left + g_ciArrowSizeX; + rArrow.top = (rDraw.bottom + rDraw.top)/2 - g_ciArrowSizeY / 2; + rArrow.bottom = (rDraw.bottom + rDraw.top)/2 + g_ciArrowSizeY / 2; + + DrawArrow(pDC, + &rArrow, + 0, + (state & ODS_DISABLED) + ? ::GetSysColor(COLOR_GRAYTEXT) + : RGB(0,0,0)); + + + rDraw.right = rArrow.left - ::GetSystemMetrics(SM_CXEDGE)/2; + + //****************************************************** + //** Draw Separator + //****************************************************** + pDC->DrawEdge(&rDraw, + EDGE_ETCHED, + BF_RIGHT); + + rDraw.right -= (::GetSystemMetrics(SM_CXEDGE) * 2) + 1 ; + + //****************************************************** + //** Draw Color + //****************************************************** + if ((state & ODS_DISABLED) == 0) + { + pDC->FillSolidRect(&rDraw, + (m_Color == CLR_DEFAULT) + ? m_DefaultColor + : m_Color); + + ::FrameRect(pDC->m_hDC, + &rDraw, + (HBRUSH)::GetStockObject(BLACK_BRUSH)); + } +} + + +//*********************************************************************** +//** Static Methods ** +//*********************************************************************** + +//*********************************************************************** +// Method: CColorButton::DrawArrow() +// Notes: None. +//*********************************************************************** +void CColorButton::DrawArrow(CDC* pDC, + RECT* pRect, + int iDirection, + COLORREF clrArrow /*= RGB(0,0,0)*/) +{ + POINT ptsArrow[3]; + + switch (iDirection) + { + case 0 : // Down + { + ptsArrow[0].x = pRect->left; + ptsArrow[0].y = pRect->top; + ptsArrow[1].x = pRect->right; + ptsArrow[1].y = pRect->top; + ptsArrow[2].x = (pRect->left + pRect->right)/2; + ptsArrow[2].y = pRect->bottom; + break; + } + + case 1 : // Up + { + ptsArrow[0].x = pRect->left; + ptsArrow[0].y = pRect->bottom; + ptsArrow[1].x = pRect->right; + ptsArrow[1].y = pRect->bottom; + ptsArrow[2].x = (pRect->left + pRect->right)/2; + ptsArrow[2].y = pRect->top; + break; + } + + case 2 : // Left + { + ptsArrow[0].x = pRect->right; + ptsArrow[0].y = pRect->top; + ptsArrow[1].x = pRect->right; + ptsArrow[1].y = pRect->bottom; + ptsArrow[2].x = pRect->left; + ptsArrow[2].y = (pRect->top + pRect->bottom)/2; + break; + } + + case 3 : // Right + { + ptsArrow[0].x = pRect->left; + ptsArrow[0].y = pRect->top; + ptsArrow[1].x = pRect->left; + ptsArrow[1].y = pRect->bottom; + ptsArrow[2].x = pRect->right; + ptsArrow[2].y = (pRect->top + pRect->bottom)/2; + break; + } + } + + CBrush brsArrow(clrArrow); + CPen penArrow(PS_SOLID, 1 , clrArrow); + + CBrush* pOldBrush = pDC->SelectObject(&brsArrow); + CPen* pOldPen = pDC->SelectObject(&penArrow); + + pDC->SetPolyFillMode(WINDING); + pDC->Polygon(ptsArrow, 3); + + pDC->SelectObject(pOldBrush); + pDC->SelectObject(pOldPen); +} \ No newline at end of file diff --git a/ColorButton.h b/ColorButton.h new file mode 100644 index 00000000..997a0c37 --- /dev/null +++ b/ColorButton.h @@ -0,0 +1,178 @@ +//*************************************************************************** +// +// AUTHOR: James White (feel free to remove or otherwise mangle any part) +// +// DESCRIPTION: This class is alarmingly similar to the CColourPicker control +// created by Chris Maunder of www.codeproject.com. It is so as it was blatantly +// copied from that class and is entirely dependant on his other great work +// in CColourPopup. I was hoping for (cough.. gag..) a more Microsoft look +// and I think this is pretty close. Hope you like it. +// +// ORIGINAL: http://www.codeproject.com/miscctrl/colour_picker.asp +// +//*************************************************************************** +#pragma once +#include "ColourPopup.h" + +void AFXAPI DDX_ColorButton(CDataExchange *pDX, int nIDC, COLORREF& crColour); + +class CColorButton : public CButton +{ +public: + DECLARE_DYNCREATE(CColorButton); + + //*********************************************************************** + // Name: CColorButton + // Description: Default constructor. + // Parameters: None. + // Return: None. + // Notes: None. + //*********************************************************************** + CColorButton(void); + + //*********************************************************************** + // Name: CColorButton + // Description: Destructor. + // Parameters: None. + // Return: None. + // Notes: None. + //*********************************************************************** + virtual ~CColorButton(void); + + //*********************************************************************** + //** Property Accessors ** + //*********************************************************************** + __declspec(property(get=GetColor,put=SetColor)) COLORREF Color; + __declspec(property(get=GetDefaultColor,put=SetDefaultColor)) COLORREF DefaultColor; + __declspec(property(get=GetTrackSelection,put=SetTrackSelection)) BOOL TrackSelection; + __declspec(property(put=SetCustomText)) LPCTSTR CustomText; + __declspec(property(put=SetDefaultText)) LPCTSTR DefaultText; + + //*********************************************************************** + // Name: GetColor + // Description: Returns the current color selected in the control. + // Parameters: void + // Return: COLORREF + // Notes: None. + //*********************************************************************** + COLORREF GetColor(void) const; + + //*********************************************************************** + // Name: SetColor + // Description: Sets the current color selected in the control. + // Parameters: COLORREF Color + // Return: None. + // Notes: None. + //*********************************************************************** + void SetColor(COLORREF Color); + + + //*********************************************************************** + // Name: GetDefaultColor + // Description: Returns the color associated with the 'default' selection. + // Parameters: void + // Return: COLORREF + // Notes: None. + //*********************************************************************** + COLORREF GetDefaultColor(void) const; + + //*********************************************************************** + // Name: SetDefaultColor + // Description: Sets the color associated with the 'default' selection. + // The default value is COLOR_APPWORKSPACE. + // Parameters: COLORREF Color + // Return: None. + // Notes: None. + //*********************************************************************** + void SetDefaultColor(COLORREF Color); + + //*********************************************************************** + // Name: SetCustomText + // Description: Sets the text to display in the 'Custom' selection of the + // CColourPicker control, the default text is "More Colors...". + // Parameters: LPCTSTR tszText + // Return: None. + // Notes: None. + //*********************************************************************** + void SetCustomText(LPCTSTR tszText); + + //*********************************************************************** + // Name: SetDefaultText + // Description: Sets the text to display in the 'Default' selection of the + // CColourPicker control, the default text is "Automatic". If + // this value is set to "", the 'Default' selection will not + // be shown. + // Parameters: LPCTSTR tszText + // Return: None. + // Notes: None. + //*********************************************************************** + void SetDefaultText(LPCTSTR tszText); + + //*********************************************************************** + // Name: SetTrackSelection + // Description: Turns on/off the 'Track Selection' option of the control + // which shows the colors during the process of selection. + // Parameters: BOOL bTrack + // Return: None. + // Notes: None. + //*********************************************************************** + void SetTrackSelection(BOOL bTrack); + + //*********************************************************************** + // Name: GetTrackSelection + // Description: Returns the state of the 'Track Selection' option. + // Parameters: void + // Return: BOOL + // Notes: None. + //*********************************************************************** + BOOL GetTrackSelection(void) const; + + //{{AFX_VIRTUAL(CColorButton) + public: + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + protected: + virtual void PreSubclassWindow(); + //}}AFX_VIRTUAL + +protected: + //{{AFX_MSG(CColorButton) + afx_msg BOOL OnClicked(); + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + //}}AFX_MSG + afx_msg LONG OnSelEndOK(UINT lParam, LONG wParam); + afx_msg LONG OnSelEndCancel(UINT lParam, LONG wParam); + afx_msg LONG OnSelChange(UINT lParam, LONG wParam); + + //*********************************************************************** + // Name: DrawArrow + // Description: None. + // Parameters: CDC* pDC + // RECT* pRect + // int iDirection + // 0 - Down + // 1 - Up + // 2 - Left + // 3 - Right + // Return: static None. + // Notes: None. + //*********************************************************************** + static void DrawArrow(CDC* pDC, + RECT* pRect, + int iDirection = 0, + COLORREF clrArrow = RGB(0,0,0)); + + + DECLARE_MESSAGE_MAP() + + COLORREF m_Color; + COLORREF m_DefaultColor; + CString m_strDefaultText; + CString m_strCustomText; + BOOL m_bPopupActive; + BOOL m_bTrackSelection; + +private: + + typedef CButton _Inherited; + +}; diff --git a/ColorFrameCtrl.cpp b/ColorFrameCtrl.cpp new file mode 100644 index 00000000..807da1c5 --- /dev/null +++ b/ColorFrameCtrl.cpp @@ -0,0 +1,82 @@ +#include "stdafx.h" +#include "ColorFrameCtrl.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +///////////////////////////////////////////////////////////////////////////// +// CColorFrameCtrl + +BEGIN_MESSAGE_MAP(CColorFrameCtrl, CWnd) + ON_WM_PAINT() + ON_WM_SIZE() +END_MESSAGE_MAP() + +CColorFrameCtrl::CColorFrameCtrl() +{ + m_crBackColor = RGB(0, 0, 0); // see also SetBackgroundColor + m_crFrameColor = RGB(0, 255, 255); // see also SetFrameColor + + m_brushBack.CreateSolidBrush(m_crBackColor); + m_brushFrame.CreateSolidBrush(m_crFrameColor); +} + +CColorFrameCtrl::~CColorFrameCtrl() +{ + m_brushFrame.DeleteObject(); + m_brushBack.DeleteObject(); +} + +BOOL CColorFrameCtrl::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID) +{ + BOOL result; + static CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW, AfxGetApp()->LoadStandardCursor(IDC_ARROW)); + + result = CWnd::CreateEx( WS_EX_STATICEDGE, + className, NULL, dwStyle, + rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, + pParentWnd->GetSafeHwnd(), (HMENU)nID); + if (result != 0) + Invalidate(); + return result; +} + +void CColorFrameCtrl::SetFrameColor( COLORREF color ) +{ + m_crFrameColor = color; + m_brushFrame.DeleteObject(); + m_brushFrame.CreateSolidBrush(m_crFrameColor); + + // clear out the existing garbage, re-start with a clean plot + Invalidate(); +} + +void CColorFrameCtrl::SetBackgroundColor(COLORREF color) +{ + m_crBackColor = color; + + m_brushBack.DeleteObject(); + m_brushBack.CreateSolidBrush(m_crBackColor); + + // clear out the existing garbage, re-start with a clean plot + Invalidate(); +} + +void CColorFrameCtrl::OnPaint() +{ + CPaintDC dc(this); // device context for painting + + dc.FillRect(m_rectClient, &m_brushBack); + dc.FrameRect(m_rectClient, &m_brushFrame); +} + +void CColorFrameCtrl::OnSize(UINT nType, int cx, int cy) +{ + // NOTE: OnSize automatically gets called during the setup of the control + CWnd::OnSize(nType, cx, cy); + GetClientRect(m_rectClient); +} diff --git a/ColorFrameCtrl.h b/ColorFrameCtrl.h new file mode 100644 index 00000000..51fe5dea --- /dev/null +++ b/ColorFrameCtrl.h @@ -0,0 +1,28 @@ +#pragma once + +///////////////////////////////////////////////////////////////////////////// +// CColorFrameCtrl window + +class CColorFrameCtrl : public CWnd +{ +public: + CColorFrameCtrl(); + virtual ~CColorFrameCtrl(); + + virtual BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0); + + void SetFrameColor(COLORREF color); + void SetBackgroundColor(COLORREF color); + + COLORREF m_crBackColor; // background color + COLORREF m_crFrameColor; // frame color + +protected: + CRect m_rectClient; + CBrush m_brushBack; + CBrush m_brushFrame; + + DECLARE_MESSAGE_MAP() + afx_msg void OnPaint(); + afx_msg void OnSize(UINT nType, int cx, int cy); +}; diff --git a/ColourPopup.cpp b/ColourPopup.cpp new file mode 100644 index 00000000..65fa4a86 --- /dev/null +++ b/ColourPopup.cpp @@ -0,0 +1,952 @@ +// ColourPopup.cpp : implementation file +// +// Written by Chris Maunder (chrismaunder@codeguru.com) +// Extended by Alexander Bischofberger (bischofb@informatik.tu-muenchen.de) +// Copyright (c) 1998. +// +// Updated 30 May 1998 to allow any number of colours, and to +// make the appearance closer to Office 97. +// Also added "Default" text area. (CJM) +// +// 13 June 1998 Fixed change of focus bug (CJM) +// 30 June 1998 Fixed bug caused by focus bug fix (D'oh!!) +// Solution suggested by Paul Wilkerson. +// +// ColourPopup is a helper class for the colour picker control +// CColourPicker. Check out the header file or the accompanying +// HTML doc file for details. +// +// This code may be used in compiled form in any way you desire. This +// file may be redistributed unmodified by any means PROVIDING it is +// not sold for profit without the authors written consent, and +// providing that this notice and the authors name is included. +// +// This file is provided "as is" with no expressed or implied warranty. +// The author accepts no liability if it causes any damage to you or your +// computer whatsoever. It's free, so don't hassle me about it. +// +// Expect bugs. +// +// Please use and enjoy. Please let me know of any bugs/mods/improvements +// that you have found/implemented and I will fix/incorporate them into this +// file. +#include "stdafx.h" +#include +#include "ColourPopup.h" +#include "UserMsgs.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define DEFAULT_BOX_VALUE -3 +#define CUSTOM_BOX_VALUE -2 +#define INVALID_COLOUR -1 + +#define MAX_COLOURS 100 + + +ColourTableEntry CColourPopup::m_crColours[] = + { + { RGB(0x00, 0x00, 0x00), _T("Black") }, + { RGB(0xA5, 0x2A, 0x00), _T("Brown") }, + { RGB(0x00, 0x40, 0x40), _T("Dark Olive Green") }, + { RGB(0x00, 0x55, 0x00), _T("Dark Green") }, + { RGB(0x00, 0x00, 0x5E), _T("Dark Teal") }, + { RGB(0x00, 0x00, 0x8B), _T("Dark blue") }, + { RGB(0x4B, 0x00, 0x82), _T("Indigo") }, + { RGB(0x28, 0x28, 0x28), _T("Dark grey") }, + + { RGB(0x8B, 0x00, 0x00), _T("Dark red") }, + { RGB(0xFF, 0x68, 0x20), _T("Orange") }, + { RGB(0x8B, 0x8B, 0x00), _T("Dark yellow") }, + { RGB(0x00, 0x93, 0x00), _T("Green") }, + { RGB(0x38, 0x8E, 0x8E), _T("Teal") }, + { RGB(0x00, 0x00, 0xFF), _T("Blue") }, + { RGB(0x7B, 0x7B, 0xC0), _T("Blue-grey") }, + { RGB(0x66, 0x66, 0x66), _T("Grey - 40") }, + + { RGB(0xFF, 0x00, 0x00), _T("Red") }, + { RGB(0xFF, 0xAD, 0x5B), _T("Light orange") }, + { RGB(0x32, 0xCD, 0x32), _T("Lime") }, + { RGB(0x3C, 0xB3, 0x71), _T("Sea green") }, + { RGB(0x7F, 0xFF, 0xD4), _T("Aqua") }, + { RGB(0x7D, 0x9E, 0xC0), _T("Light blue") }, + { RGB(0x80, 0x00, 0x80), _T("Violet") }, + { RGB(0x7F, 0x7F, 0x7F), _T("Grey - 50") }, + + { RGB(0xFF, 0xC0, 0xCB), _T("Pink") }, + { RGB(0xFF, 0xD7, 0x00), _T("Gold") }, + { RGB(0xFF, 0xFF, 0x00), _T("Yellow") }, + { RGB(0x00, 0xFF, 0x00), _T("Bright green") }, + { RGB(0x40, 0xE0, 0xD0), _T("Turquoise") }, + { RGB(0xC0, 0xFF, 0xFF), _T("Skyblue") }, + { RGB(0x48, 0x00, 0x48), _T("Plum") }, + { RGB(0xC0, 0xC0, 0xC0), _T("Light grey") }, + + { RGB(0xFF, 0xE4, 0xE1), _T("Rose") }, + { RGB(0xD2, 0xB4, 0x8C), _T("Tan") }, + { RGB(0xFF, 0xFF, 0xE0), _T("Light yellow") }, + { RGB(0x98, 0xFB, 0x98), _T("Pale green ") }, + { RGB(0xAF, 0xEE, 0xEE), _T("Pale turquoise") }, + { RGB(0x68, 0x83, 0x8B), _T("Pale blue") }, + { RGB(0xE6, 0xE6, 0xFA), _T("Lavender") }, + { RGB(0xFF, 0xFF, 0xFF), _T("White") } + }; + +///////////////////////////////////////////////////////////////////////////// +// CColourPopup + +CColourPopup::CColourPopup() +{ + Initialise(); +} + +CColourPopup::CColourPopup(CPoint p, COLORREF crColour, CWnd* pParentWnd, + LPCTSTR szDefaultText /* = NULL */, + LPCTSTR szCustomText /* = NULL */, + COLORREF* colourArray /* = NULL*/, + int NumberOfColours /* = 0*/ ) +{ + colourArrayPassed = colourArray;//copy the pointer to the array of colours we will be using + if(colourArray && NumberOfColours) + m_nNumColours = NumberOfColours; + else + colourArrayPassed = NULL; //if an array is passed without a size parameter ignore it and use the defaults + + Initialise();//If not a custom palette intialise as NORMAL! + + m_crColour = m_crInitialColour = crColour; + m_pParent = pParentWnd; + m_strDefaultText = (szDefaultText)? szDefaultText : _T(""); + m_strCustomText = (szCustomText)? szCustomText : _T(""); + + CColourPopup::Create(p, crColour, pParentWnd, szDefaultText, szCustomText); +} + +void CColourPopup::Initialise() +{ + //set size if it has not been set already + if(colourArrayPassed==NULL) + m_nNumColours = sizeof(m_crColours)/sizeof(ColourTableEntry); + + ASSERT(m_nNumColours <= MAX_COLOURS); + if (m_nNumColours > MAX_COLOURS) + m_nNumColours = MAX_COLOURS; + + m_nNumColumns = 0; + m_nNumRows = 0; + m_nBoxSize = 18; + m_nMargin = ::GetSystemMetrics(SM_CXEDGE); + m_nCurrentSel = INVALID_COLOUR; + m_nChosenColourSel = INVALID_COLOUR; + m_pParent = NULL; + m_crColour = m_crInitialColour = RGB(0,0,0); + + m_bChildWindowVisible = FALSE; + + // Idiot check: Make sure the colour square is at least 5 x 5; + if (m_nBoxSize - 2*m_nMargin - 2 < 5) + m_nBoxSize = 5 + 2*m_nMargin + 2; + + // Create the font + NONCLIENTMETRICS ncm; + ncm.cbSize = sizeof(NONCLIENTMETRICS); + VERIFY(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0)); + m_Font.CreateFontIndirect(&(ncm.lfMessageFont)); + + // Create the palette + struct + { + LOGPALETTE LogPalette; + PALETTEENTRY PalEntry[MAX_COLOURS]; + } + pal; + + LOGPALETTE* pLogPalette = (LOGPALETTE*) &pal; + pLogPalette->palVersion = 0x300; + pLogPalette->palNumEntries = (WORD) m_nNumColours; + + if(colourArrayPassed==NULL) + {//use default array + for (int i = 0; i < m_nNumColours; i++) + { + pLogPalette->palPalEntry[i].peRed = GetRValue(m_crColours[i].crColour); + pLogPalette->palPalEntry[i].peGreen = GetGValue(m_crColours[i].crColour); + pLogPalette->palPalEntry[i].peBlue = GetBValue(m_crColours[i].crColour); + pLogPalette->palPalEntry[i].peFlags = 0; + } + } + else + {//if an array has been passed use it + for(int i=0;ipalPalEntry[i].peRed = GetRValue(colourArrayPassed[i]); + pLogPalette->palPalEntry[i].peGreen = GetGValue(colourArrayPassed[i]); + pLogPalette->palPalEntry[i].peBlue = GetBValue(colourArrayPassed[i]); + pLogPalette->palPalEntry[i].peFlags = 0; + } + } + + m_Palette.CreatePalette(pLogPalette); +} + +CColourPopup::~CColourPopup() +{ + m_Font.DeleteObject(); + m_Palette.DeleteObject(); +} + +BOOL CColourPopup::Create(CPoint p, COLORREF crColour, CWnd* pParentWnd, + LPCTSTR szDefaultText /* = NULL */, + LPCTSTR szCustomText /* = NULL */) +{ + ASSERT(pParentWnd && ::IsWindow(pParentWnd->GetSafeHwnd())); + + m_pParent = pParentWnd; + m_crColour = m_crInitialColour = crColour; + + // Get the class name and create the window + CString szClassName = AfxRegisterWndClass(CS_CLASSDC|CS_SAVEBITS|CS_HREDRAW|CS_VREDRAW, + AfxGetApp()->LoadStandardCursor(IDC_ARROW), + (HBRUSH) (COLOR_BTNFACE+1), + 0); + + if (!CWnd::CreateEx(0, szClassName, _T(""), WS_VISIBLE|WS_POPUP, + p.x, p.y, 100, 100, // size updated soon + pParentWnd->GetSafeHwnd(), 0, NULL)) + return FALSE; + + // Store the Custom text + if (szCustomText != NULL) + m_strCustomText = szCustomText; + + // Store the Default Area text + if (szDefaultText != NULL) + m_strDefaultText = szDefaultText; + + // Set the window size + SetWindowSize(); + + // Create the tooltips + CreateToolTips(); + + // Find which cell (if any) corresponds to the initial colour + FindCellFromColour(crColour); + + // Capture all mouse events for the life of this window + SetCapture(); + + return TRUE; +} + +BEGIN_MESSAGE_MAP(CColourPopup, CWnd) +//{{AFX_MSG_MAP(CColourPopup) +ON_WM_NCDESTROY() +ON_WM_LBUTTONUP() +ON_WM_PAINT() +ON_WM_MOUSEMOVE() +ON_WM_KEYDOWN() +ON_WM_QUERYNEWPALETTE() +ON_WM_PALETTECHANGED() +ON_WM_KILLFOCUS() +ON_WM_ACTIVATEAPP() +//}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CColourPopup message handlers + +// For tooltips +BOOL CColourPopup::PreTranslateMessage(MSG* pMsg) +{ + m_ToolTip.RelayEvent(pMsg); + + // Fix (Adrian Roman): Sometimes if the picker loses focus it is never destroyed + if (GetCapture()->GetSafeHwnd() != m_hWnd) + SetCapture(); + + return CWnd::PreTranslateMessage(pMsg); +} + +// If an arrow key is pressed, then move the selection +void CColourPopup::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + int row = GetRow(m_nCurrentSel), + col = GetColumn(m_nCurrentSel); + + if (nChar == VK_DOWN) + { + if (row == DEFAULT_BOX_VALUE) + row = col = 0; + else if (row == CUSTOM_BOX_VALUE) + { + if (m_strDefaultText.GetLength()) + row = col = DEFAULT_BOX_VALUE; + else + row = col = 0; + } + else + { + row++; + if (GetIndex(row,col) < 0) + { + if (m_strCustomText.GetLength()) + row = col = CUSTOM_BOX_VALUE; + else if (m_strDefaultText.GetLength()) + row = col = DEFAULT_BOX_VALUE; + else + row = col = 0; + } + } + ChangeSelection(GetIndex(row, col)); + } + + if (nChar == VK_UP) + { + if (row == DEFAULT_BOX_VALUE) + { + if (m_strCustomText.GetLength()) + row = col = CUSTOM_BOX_VALUE; + else + { + row = GetRow(m_nNumColours-1); + col = GetColumn(m_nNumColours-1); + } + } + else if (row == CUSTOM_BOX_VALUE) + { + row = GetRow(m_nNumColours-1); + col = GetColumn(m_nNumColours-1); + } + else if (row > 0) + row--; + else /* row == 0 */ + { + if (m_strDefaultText.GetLength()) + row = col = DEFAULT_BOX_VALUE; + else if (m_strCustomText.GetLength()) + row = col = CUSTOM_BOX_VALUE; + else + { + row = GetRow(m_nNumColours-1); + col = GetColumn(m_nNumColours-1); + } + } + ChangeSelection(GetIndex(row, col)); + } + + if (nChar == VK_RIGHT) + { + if (row == DEFAULT_BOX_VALUE) + row = col = 0; + else if (row == CUSTOM_BOX_VALUE) + { + if (m_strDefaultText.GetLength()) + row = col = DEFAULT_BOX_VALUE; + else + row = col = 0; + } + else if (col < m_nNumColumns-1) + col++; + else + { + col = 0; + row++; + } + + if (GetIndex(row,col) == INVALID_COLOUR) + { + if (m_strCustomText.GetLength()) + row = col = CUSTOM_BOX_VALUE; + else if (m_strDefaultText.GetLength()) + row = col = DEFAULT_BOX_VALUE; + else + row = col = 0; + } + + ChangeSelection(GetIndex(row, col)); + } + + if (nChar == VK_LEFT) + { + if (row == DEFAULT_BOX_VALUE) + { + if (m_strCustomText.GetLength()) + row = col = CUSTOM_BOX_VALUE; + else + { + row = GetRow(m_nNumColours-1); + col = GetColumn(m_nNumColours-1); + } + } + else if (row == CUSTOM_BOX_VALUE) + { + row = GetRow(m_nNumColours-1); + col = GetColumn(m_nNumColours-1); + } + else if (col > 0) + col--; + else /* col == 0 */ + { + if (row > 0) + { + row--; + col = m_nNumColumns-1; + } + else + { + if (m_strDefaultText.GetLength()) + row = col = DEFAULT_BOX_VALUE; + else if (m_strCustomText.GetLength()) + row = col = CUSTOM_BOX_VALUE; + else + { + row = GetRow(m_nNumColours-1); + col = GetColumn(m_nNumColours-1); + } + } + } + ChangeSelection(GetIndex(row, col)); + } + + if (nChar == VK_ESCAPE) + { + m_crColour = m_crInitialColour; + EndSelection(UM_CPN_SELENDCANCEL); + return; + } + + if (nChar == VK_RETURN || nChar == VK_SPACE) + { + EndSelection(UM_CPN_SELENDOK); + return; + } + + CWnd::OnKeyDown(nChar, nRepCnt, nFlags); +} + +// auto-deletion +void CColourPopup::OnNcDestroy() +{ + CWnd::OnNcDestroy(); + delete this; +} + +void CColourPopup::OnPaint() +{ + CPaintDC dc(this); // device context for painting + + // Draw the Default Area text + if (m_strDefaultText.GetLength()) + DrawCell(&dc, DEFAULT_BOX_VALUE); + + // Draw colour cells + for (int i = 0; i < m_nNumColours; i++) + DrawCell(&dc, i); + + // Draw custom text + if (m_strCustomText.GetLength()) + DrawCell(&dc, CUSTOM_BOX_VALUE); + + // Draw raised window edge (ex-window style WS_EX_WINDOWEDGE is sposed to do this, + // but for some reason isn't + CRect rect; + GetClientRect(rect); + dc.DrawEdge(rect, EDGE_RAISED, BF_RECT); +} + +void CColourPopup::OnMouseMove(UINT nFlags, CPoint point) +{ + int nNewSelection = INVALID_COLOUR; + + // Translate points to be relative raised window edge + point.x -= m_nMargin; + point.y -= m_nMargin; + + // First check we aren't in text box + if (m_strCustomText.GetLength() && m_CustomTextRect.PtInRect(point)) + nNewSelection = CUSTOM_BOX_VALUE; + else if (m_strDefaultText.GetLength() && m_DefaultTextRect.PtInRect(point)) + nNewSelection = DEFAULT_BOX_VALUE; + else + { + // Take into account text box + if (m_strDefaultText.GetLength()) + point.y -= m_DefaultTextRect.Height(); + + // Get the row and column + nNewSelection = GetIndex(point.y / m_nBoxSize, point.x / m_nBoxSize); + + // In range? If not, default and exit + if (nNewSelection < 0 || nNewSelection >= m_nNumColours) + { + CWnd::OnMouseMove(nFlags, point); + return; + } + } + + // OK - we have the row and column of the current selection (may be CUSTOM_BOX_VALUE) + // Has the row/col selection changed? If yes, then redraw old and new cells. + if (nNewSelection != m_nCurrentSel) + ChangeSelection(nNewSelection); + + CWnd::OnMouseMove(nFlags, point); +} + +// End selection on LButtonUp +void CColourPopup::OnLButtonUp(UINT nFlags, CPoint point) +{ + CWnd::OnLButtonUp(nFlags, point); + + DWORD pos = GetMessagePos(); + point = CPoint(LOWORD(pos), HIWORD(pos)); + + if (m_WindowRect.PtInRect(point)) + EndSelection(UM_CPN_SELENDOK); + else + EndSelection(UM_CPN_SELENDCANCEL); +} + +///////////////////////////////////////////////////////////////////////////// +// CColourPopup implementation + +int CColourPopup::GetIndex(int row, int col) const +{ + if ((row == CUSTOM_BOX_VALUE || col == CUSTOM_BOX_VALUE) && m_strCustomText.GetLength()) + return CUSTOM_BOX_VALUE; + else if ((row == DEFAULT_BOX_VALUE || col == DEFAULT_BOX_VALUE) && m_strDefaultText.GetLength()) + return DEFAULT_BOX_VALUE; + else if (row < 0 || col < 0 || row >= m_nNumRows || col >= m_nNumColumns) + return INVALID_COLOUR; + else + { + if (row*m_nNumColumns + col >= m_nNumColours) + return INVALID_COLOUR; + else + return row*m_nNumColumns + col; + } +} + +int CColourPopup::GetRow(int nIndex) const +{ + if (nIndex == CUSTOM_BOX_VALUE && m_strCustomText.GetLength()) + return CUSTOM_BOX_VALUE; + else if (nIndex == DEFAULT_BOX_VALUE && m_strDefaultText.GetLength()) + return DEFAULT_BOX_VALUE; + else if (nIndex < 0 || nIndex >= m_nNumColours) + return INVALID_COLOUR; + else + return nIndex / m_nNumColumns; +} + +int CColourPopup::GetColumn(int nIndex) const +{ + if (nIndex == CUSTOM_BOX_VALUE && m_strCustomText.GetLength()) + return CUSTOM_BOX_VALUE; + else if (nIndex == DEFAULT_BOX_VALUE && m_strDefaultText.GetLength()) + return DEFAULT_BOX_VALUE; + else if (nIndex < 0 || nIndex >= m_nNumColours) + return INVALID_COLOUR; + else + return nIndex % m_nNumColumns; +} + +void CColourPopup::FindCellFromColour(COLORREF crColour) +{ + if (crColour == CLR_DEFAULT && m_strDefaultText.GetLength()) + { + m_nChosenColourSel = DEFAULT_BOX_VALUE; + return; + } + + for (int i = 0; i < m_nNumColours; i++) + { + if (GetColour(i) == crColour) + { + m_nChosenColourSel = i; + return; + } + } + + if (m_strCustomText.GetLength()) + m_nChosenColourSel = CUSTOM_BOX_VALUE; + else + m_nChosenColourSel = INVALID_COLOUR; +} + +// Gets the dimensions of the colour cell given by (row,col) +BOOL CColourPopup::GetCellRect(int nIndex, const LPRECT& rect) +{ + if (nIndex == CUSTOM_BOX_VALUE) + { + ::SetRect(rect, + m_CustomTextRect.left, m_CustomTextRect.top, + m_CustomTextRect.right, m_CustomTextRect.bottom); + return TRUE; + } + else if (nIndex == DEFAULT_BOX_VALUE) + { + ::SetRect(rect, + m_DefaultTextRect.left, m_DefaultTextRect.top, + m_DefaultTextRect.right, m_DefaultTextRect.bottom); + return TRUE; + } + + if (nIndex < 0 || nIndex >= m_nNumColours) + return FALSE; + + rect->left = GetColumn(nIndex) * m_nBoxSize + m_nMargin; + rect->top = GetRow(nIndex) * m_nBoxSize + m_nMargin; + + // Move everything down if we are displaying a default text area + if (m_strDefaultText.GetLength()) + rect->top += (m_nMargin + m_DefaultTextRect.Height()); + + rect->right = rect->left + m_nBoxSize; + rect->bottom = rect->top + m_nBoxSize; + + return TRUE; +} + +// Works out an appropriate size and position of this window +void CColourPopup::SetWindowSize() +{ + CSize TextSize; + + // If we are showing a custom or default text area, get the font and text size. + if (m_strCustomText.GetLength() || m_strDefaultText.GetLength()) + { + CClientDC dc(this); + CFont* pOldFont = (CFont*) dc.SelectObject(&m_Font); + + // Get the size of the custom text (if there IS custom text) + TextSize = CSize(0,0); + if (m_strCustomText.GetLength()) + TextSize = dc.GetTextExtent(m_strCustomText); + + // Get the size of the default text (if there IS default text) + if (m_strDefaultText.GetLength()) + { + CSize DefaultSize = dc.GetTextExtent(m_strDefaultText); + if (DefaultSize.cx > TextSize.cx) + TextSize.cx = DefaultSize.cx; + if (DefaultSize.cy > TextSize.cy) + TextSize.cy = DefaultSize.cy; + } + + dc.SelectObject(pOldFont); + TextSize += CSize(2*m_nMargin,2*m_nMargin); + + // Add even more space to draw the horizontal line + TextSize.cy += 2*m_nMargin + 2; + } + + // Get the number of columns and rows + //m_nNumColumns = (int) sqrt((double)m_nNumColours); // for a square window (yuk) + m_nNumColumns = 8; + m_nNumRows = m_nNumColours / m_nNumColumns; + if (m_nNumColours % m_nNumColumns) + m_nNumRows++; + + // Get the current window position, and set the new size + CRect rect; + GetWindowRect(rect); + + m_WindowRect.SetRect(rect.left, rect.top, + rect.left + m_nNumColumns*m_nBoxSize + 2*m_nMargin, + rect.top + m_nNumRows*m_nBoxSize + 2*m_nMargin); + + // if custom text, then expand window if necessary, and set text width as + // window width + if (m_strDefaultText.GetLength()) + { + if (TextSize.cx > m_WindowRect.Width()) + m_WindowRect.right = m_WindowRect.left + TextSize.cx; + TextSize.cx = m_WindowRect.Width()-2*m_nMargin; + + // Work out the text area + m_DefaultTextRect.SetRect(m_nMargin, m_nMargin, + m_nMargin+TextSize.cx, 2*m_nMargin+TextSize.cy); + m_WindowRect.bottom += m_DefaultTextRect.Height() + 2*m_nMargin; + } + + // if custom text, then expand window if necessary, and set text width as + // window width + if (m_strCustomText.GetLength()) + { + if (TextSize.cx > m_WindowRect.Width()) + m_WindowRect.right = m_WindowRect.left + TextSize.cx; + TextSize.cx = m_WindowRect.Width()-2*m_nMargin; + + // Work out the text area + m_CustomTextRect.SetRect(m_nMargin, m_WindowRect.Height(), + m_nMargin+TextSize.cx, + m_WindowRect.Height()+m_nMargin+TextSize.cy); + m_WindowRect.bottom += m_CustomTextRect.Height() + 2*m_nMargin; + } + + // Need to check it'll fit on screen: Too far right? + CSize ScreenSize(::GetSystemMetrics(SM_CXSCREEN), ::GetSystemMetrics(SM_CYSCREEN)); + if (m_WindowRect.right > ScreenSize.cx) + m_WindowRect.OffsetRect(-(m_WindowRect.right - ScreenSize.cx), 0); + + // Too far left? + if (m_WindowRect.left < 0) + m_WindowRect.OffsetRect( -m_WindowRect.left, 0); + + // Bottom falling out of screen? + if (m_WindowRect.bottom > ScreenSize.cy) + { + CRect ParentRect; + m_pParent->GetWindowRect(ParentRect); + m_WindowRect.OffsetRect(0, -(ParentRect.Height() + m_WindowRect.Height())); + } + + // Set the window size and position + MoveWindow(m_WindowRect, TRUE); +} + +void CColourPopup::CreateToolTips() +{ + // Create the tool tip + if (!m_ToolTip.Create(this)) + return; + + // Add a tool for each cell + for (int i = 0; i < m_nNumColours; i++) + { + CRect rect; + if (!GetCellRect(i, rect)) + continue; + m_ToolTip.AddTool(this,_T(""), rect, 1); // GetColourName(i) + } +} + +void CColourPopup::ChangeSelection(int nIndex) +{ + CClientDC dc(this); // device context for drawing + + if (nIndex > m_nNumColours) + nIndex = CUSTOM_BOX_VALUE; + + if ((m_nCurrentSel >= 0 && m_nCurrentSel < m_nNumColours) || + m_nCurrentSel == CUSTOM_BOX_VALUE || m_nCurrentSel == DEFAULT_BOX_VALUE) + { + // Set Current selection as invalid and redraw old selection (this way + // the old selection will be drawn unselected) + int OldSel = m_nCurrentSel; + m_nCurrentSel = INVALID_COLOUR; + DrawCell(&dc, OldSel); + } + + // Set the current selection as row/col and draw (it will be drawn selected) + m_nCurrentSel = nIndex; + DrawCell(&dc, m_nCurrentSel); + + // Store the current colour + if (m_nCurrentSel == CUSTOM_BOX_VALUE) + m_pParent->SendMessage(UM_CPN_SELCHANGE, (WPARAM) m_crInitialColour, 0); + else if (m_nCurrentSel == DEFAULT_BOX_VALUE) + { + m_crColour = CLR_DEFAULT; + m_pParent->SendMessage(UM_CPN_SELCHANGE, (WPARAM) CLR_DEFAULT, 0); + } + else + { + m_crColour = GetColour(m_nCurrentSel); + m_pParent->SendMessage(UM_CPN_SELCHANGE, (WPARAM) m_crColour, 0); + } +} + +void CColourPopup::EndSelection(int nMessage) +{ + ReleaseCapture(); + + // If custom text selected, perform a custom colour selection + if (nMessage != UM_CPN_SELENDCANCEL && m_nCurrentSel == CUSTOM_BOX_VALUE) + { + m_bChildWindowVisible = TRUE; + + CColorDialog dlg(m_crInitialColour, CC_FULLOPEN | CC_ANYCOLOR, this); + + if (dlg.DoModal() == IDOK) + m_crColour = dlg.GetColor(); + else + nMessage = UM_CPN_SELENDCANCEL; + + m_bChildWindowVisible = FALSE; + } + + if (nMessage == UM_CPN_SELENDCANCEL) + m_crColour = m_crInitialColour; + + m_pParent->SendMessage(nMessage, (WPARAM) m_crColour, 0); + + // Kill focus bug fixed by Martin Wawrusch + if (!m_bChildWindowVisible) + DestroyWindow(); +} + +void CColourPopup::DrawCell(CDC* pDC, int nIndex) +{ + // For the Custom Text area + if (m_strCustomText.GetLength() && nIndex == CUSTOM_BOX_VALUE) + { + // The extent of the actual text button + CRect TextButtonRect = m_CustomTextRect; + TextButtonRect.top += 2*m_nMargin; + + // Fill background + pDC->FillSolidRect(TextButtonRect, ::GetSysColor(COLOR_3DFACE)); + + // Draw horizontal line + pDC->FillSolidRect(m_CustomTextRect.left+2*m_nMargin, m_CustomTextRect.top, + m_CustomTextRect.Width()-4*m_nMargin, 1, ::GetSysColor(COLOR_3DSHADOW)); + pDC->FillSolidRect(m_CustomTextRect.left+2*m_nMargin, m_CustomTextRect.top+1, + m_CustomTextRect.Width()-4*m_nMargin, 1, ::GetSysColor(COLOR_3DHILIGHT)); + + TextButtonRect.DeflateRect(1,1); + + // fill background + if (m_nChosenColourSel == nIndex && m_nCurrentSel != nIndex) + pDC->FillSolidRect(TextButtonRect, ::GetSysColor(COLOR_3DLIGHT)); + else + pDC->FillSolidRect(TextButtonRect, ::GetSysColor(COLOR_3DFACE)); + + // Draw button + if (m_nCurrentSel == nIndex) + pDC->DrawEdge(TextButtonRect, BDR_RAISEDINNER, BF_RECT); + else if (m_nChosenColourSel == nIndex) + pDC->DrawEdge(TextButtonRect, BDR_SUNKENOUTER, BF_RECT); + + // Draw custom text + CFont *pOldFont = (CFont*) pDC->SelectObject(&m_Font); + pDC->SetBkMode(TRANSPARENT); + pDC->DrawText(m_strCustomText, TextButtonRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + pDC->SelectObject(pOldFont); + + return; + } + + // For the Default Text area + if (m_strDefaultText.GetLength() && nIndex == DEFAULT_BOX_VALUE) + { + // Fill background + pDC->FillSolidRect(m_DefaultTextRect, ::GetSysColor(COLOR_3DFACE)); + + // The extent of the actual text button + CRect TextButtonRect = m_DefaultTextRect; + TextButtonRect.DeflateRect(1,1); + + // fill background + if (m_nChosenColourSel == nIndex && m_nCurrentSel != nIndex) + pDC->FillSolidRect(TextButtonRect, ::GetSysColor(COLOR_3DLIGHT)); + else + pDC->FillSolidRect(TextButtonRect, ::GetSysColor(COLOR_3DFACE)); + + // Draw thin line around text + CRect LineRect = TextButtonRect; + LineRect.DeflateRect(2*m_nMargin,2*m_nMargin); + CPen pen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW)); + CPen* pOldPen = pDC->SelectObject(&pen); + pDC->SelectStockObject(NULL_BRUSH); + pDC->Rectangle(LineRect); + pDC->SelectObject(pOldPen); + + // Draw button + if (m_nCurrentSel == nIndex) + pDC->DrawEdge(TextButtonRect, BDR_RAISEDINNER, BF_RECT); + else if (m_nChosenColourSel == nIndex) + pDC->DrawEdge(TextButtonRect, BDR_SUNKENOUTER, BF_RECT); + + // Draw custom text + CFont *pOldFont = (CFont*) pDC->SelectObject(&m_Font); + pDC->SetBkMode(TRANSPARENT); + pDC->DrawText(m_strDefaultText, TextButtonRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + pDC->SelectObject(pOldFont); + + return; + } + + CRect rect; + if (!GetCellRect(nIndex, rect)) + return; + + // Select and realize the palette + CPalette* pOldPalette = NULL; + if (pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + pOldPalette = pDC->SelectPalette(&m_Palette, FALSE); + pDC->RealizePalette(); + } + + // fill background + if (m_nChosenColourSel == nIndex && m_nCurrentSel != nIndex) + pDC->FillSolidRect(rect, ::GetSysColor(COLOR_3DHILIGHT)); + else + pDC->FillSolidRect(rect, ::GetSysColor(COLOR_3DFACE)); + + // Draw button + if (m_nCurrentSel == nIndex) + pDC->DrawEdge(rect, BDR_RAISEDINNER, BF_RECT); + else if (m_nChosenColourSel == nIndex) + pDC->DrawEdge(rect, BDR_SUNKENOUTER, BF_RECT); + + CBrush brush(PALETTERGB(GetRValue(GetColour(nIndex)), + GetGValue(GetColour(nIndex)), + GetBValue(GetColour(nIndex)) )); + CPen pen; + pen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW)); + + CBrush* pOldBrush = (CBrush*) pDC->SelectObject(&brush); + CPen* pOldPen = (CPen*) pDC->SelectObject(&pen); + + // Draw the cell colour + rect.DeflateRect(m_nMargin+1, m_nMargin+1); + pDC->Rectangle(rect); + + // restore DC and cleanup + pDC->SelectObject(pOldBrush); + pDC->SelectObject(pOldPen); + brush.DeleteObject(); + pen.DeleteObject(); + + if (pOldPalette && pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + pDC->SelectPalette(pOldPalette, FALSE); +} + +BOOL CColourPopup::OnQueryNewPalette() +{ + Invalidate(); + return CWnd::OnQueryNewPalette(); +} + +void CColourPopup::OnPaletteChanged(CWnd* pFocusWnd) +{ + CWnd::OnPaletteChanged(pFocusWnd); + + if (pFocusWnd->GetSafeHwnd() != GetSafeHwnd()) + Invalidate(); +} + +void CColourPopup::OnKillFocus(CWnd* pNewWnd) +{ + CWnd::OnKillFocus(pNewWnd); + + ReleaseCapture(); + //DestroyWindow(); - causes crash when Custom colour dialog appears. +} + +// KillFocus problem fix suggested by Paul Wilkerson. +void CColourPopup::OnActivateApp(BOOL bActive, DWORD hTask) +{ + CWnd::OnActivateApp(bActive, hTask); + + // If Deactivating App, cancel this selection + if (!bActive) + EndSelection(UM_CPN_SELENDCANCEL); +} diff --git a/ColourPopup.h b/ColourPopup.h new file mode 100644 index 00000000..f56f0b48 --- /dev/null +++ b/ColourPopup.h @@ -0,0 +1,120 @@ +#pragma once + +// ColourPopup.h : header file +// +// Written by Chris Maunder (chrismaunder@codeguru.com) +// Extended by Alexander Bischofberger (bischofb@informatik.tu-muenchen.de) +// Copyright (c) 1998. +// +// This code may be used in compiled form in any way you desire. This +// file may be redistributed unmodified by any means PROVIDING it is +// not sold for profit without the authors written consent, and +// providing that this notice and the authors name is included. If +// the source code in this file is used in any commercial application +// then a simple email would be nice. +// +// This file is provided "as is" with no expressed or implied warranty. +// The author accepts no liability if it causes any damage whatsoever. +// It's free - so you get what you pay for. + + +// forward declaration +class CColourPicker; + +// To hold the colours and their names +typedef struct +{ + COLORREF crColour; + TCHAR *szName; +} +ColourTableEntry; + + +///////////////////////////////////////////////////////////////////////////// +// CColourPopup window + +class CColourPopup : public CWnd +{ +// Construction +public: + CColourPopup(); + CColourPopup(CPoint p, COLORREF crColour, CWnd* pParentWnd, + LPCTSTR szDefaultText = NULL, LPCTSTR szCustomText = NULL, + COLORREF* colourArray = NULL,int NumberOfColours = 0); + void Initialise(); + +// Attributes +public: + +// Operations +public: + BOOL Create(CPoint p, COLORREF crColour, CWnd* pParentWnd, LPCTSTR szDefaultText = NULL, + LPCTSTR szCustomText = NULL); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CColourPopup) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CColourPopup(); + +protected: + COLORREF* colourArrayPassed; + BOOL GetCellRect(int nIndex, const LPRECT& rect); + void FindCellFromColour(COLORREF crColour); + void SetWindowSize(); + void CreateToolTips(); + void ChangeSelection(int nIndex); + void EndSelection(int nMessage); + void DrawCell(CDC* pDC, int nIndex); + + COLORREF GetColour(int nIndex) + { + if(colourArrayPassed==NULL) + return m_crColours[nIndex].crColour; + else + return colourArrayPassed[nIndex]; + } + LPCTSTR GetColourName(int nIndex) { return m_crColours[nIndex].szName; } + int GetIndex(int row, int col) const; + int GetRow(int nIndex) const; + int GetColumn(int nIndex) const; + +// protected attributes +protected: + static ColourTableEntry m_crColours[]; + int m_nNumColours; + int m_nNumColumns, m_nNumRows; + int m_nBoxSize, m_nMargin; + int m_nCurrentSel; + int m_nChosenColourSel; + CString m_strDefaultText; + CString m_strCustomText; + CRect m_CustomTextRect, m_DefaultTextRect, m_WindowRect; + CFont m_Font; + CPalette m_Palette; + COLORREF m_crInitialColour, m_crColour; + CToolTipCtrl m_ToolTip; + CWnd* m_pParent; + BOOL m_bChildWindowVisible; + + // Generated message map functions +protected: + //{{AFX_MSG(CColourPopup) + afx_msg void OnNcDestroy(); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg void OnPaint(); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg BOOL OnQueryNewPalette(); + afx_msg void OnPaletteChanged(CWnd* pFocusWnd); + afx_msg void OnKillFocus(CWnd* pNewWnd); + afx_msg void OnActivateApp(BOOL bActive, DWORD hTask); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + diff --git a/ComboBoxEx2.cpp b/ComboBoxEx2.cpp new file mode 100644 index 00000000..105a3440 --- /dev/null +++ b/ComboBoxEx2.cpp @@ -0,0 +1,193 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "ComboBoxEx2.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// CComboBoxEx2 + +IMPLEMENT_DYNAMIC(CComboBoxEx2, CComboBoxEx) + +BEGIN_MESSAGE_MAP(CComboBoxEx2, CComboBoxEx) +END_MESSAGE_MAP() + +CComboBoxEx2::CComboBoxEx2() +{ +} + +CComboBoxEx2::~CComboBoxEx2() +{ +} + +int CComboBoxEx2::AddItem(LPCTSTR pszText, int iImage) +{ + COMBOBOXEXITEM cbi = {0}; + cbi.mask = CBEIF_TEXT; + cbi.iItem = -1; + cbi.pszText = (LPTSTR)pszText; + if (iImage != -1) + { + cbi.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE; + cbi.iImage = iImage; + cbi.iSelectedImage = iImage; + } + return InsertItem(&cbi); +} + +BOOL CComboBoxEx2::PreTranslateMessage(MSG* pMsg) +{ + // there seems to be no way that we get the WM_CHARTOITEM for this control + ASSERT( pMsg->message != WM_CHARTOITEM ); + + if (pMsg->message == WM_KEYDOWN) + { + UINT uChar = MapVirtualKey(pMsg->wParam, 2); + if (uChar != 0) + { + // CComboBox::SelectString seems also not to work + CComboBox* pctrlCB = GetComboBoxCtrl(); + if (pctrlCB != NULL) + { + int iCount = pctrlCB->GetCount(); + for (int i = 0; i < iCount; i++) + { + CString strItem; + pctrlCB->GetLBText(i, strItem); + if (strItem.IsEmpty()) + continue; + + //those casts are indeed all(!) needed to get that thing (at least!) running correctly for ANSI code pages, + //if that will also work for MBCS code pages has to be tested.. + UINT uFirstChar = (UINT)(_TUCHAR)strItem[0]; + UINT uFirstCharLower = (UINT)(_TUCHAR)_totlower((_TINT)(uFirstChar)); + UINT uTheChar = (UINT)(_TUCHAR)_totlower((_TINT)((UINT)uChar)); + if (uFirstCharLower == uTheChar){ + SetCurSel(i); + GetParent()->SendMessage(WM_COMMAND, MAKELONG((WORD)GetWindowLong(m_hWnd, GWL_ID), CBN_SELCHANGE), (LPARAM)m_hWnd); + return TRUE; + } + } + } + } + } + return CComboBoxEx::PreTranslateMessage(pMsg); +} + +// Win98: This function does not work under Win98 ? +BOOL CComboBoxEx2::SelectString(LPCTSTR pszText) +{ + // CComboBox::SelectString seems also not to work + CComboBox* pctrlCB = GetComboBoxCtrl(); + if (pctrlCB != NULL) + { + int iCount = pctrlCB->GetCount(); + for (int i = 0; i < iCount; i++) + { + CString strItem; + pctrlCB->GetLBText(i, strItem); + if (strItem == pszText) + { + SetCurSel(i); + GetParent()->SendMessage(WM_COMMAND, MAKELONG((WORD)GetWindowLong(m_hWnd, GWL_ID), CBN_SELCHANGE), (LPARAM)m_hWnd); + return TRUE; + } + } + } + return FALSE; +} + +BOOL CComboBoxEx2::SelectItemDataStringA(LPCSTR pszText) +{ + CComboBox* pctrlCB = GetComboBoxCtrl(); + if (pctrlCB != NULL) + { + int iCount = pctrlCB->GetCount(); + for (int i = 0; i < iCount; i++) + { + void* pvItemData = GetItemDataPtr(i); + if (pvItemData && strcmp((LPCSTR)pvItemData, pszText) == 0) + { + SetCurSel(i); + GetParent()->SendMessage(WM_COMMAND, MAKELONG((WORD)GetWindowLong(m_hWnd, GWL_ID), CBN_SELCHANGE), (LPARAM)m_hWnd); + return TRUE; + } + } + } + return FALSE; +} + +void UpdateHorzExtent(CComboBox &rctlComboBox, int iIconWidth) +{ + int iItemCount = rctlComboBox.GetCount(); + if (iItemCount > 0) + { + CDC *pDC = rctlComboBox.GetDC(); + if (pDC != NULL) + { + // *** To get *ACCURATE* results from 'GetOutputTextExtent' one *MUST* + // *** explicitly set the font! + CFont *pOldFont = pDC->SelectObject(rctlComboBox.GetFont()); + + CString strItem; + int iMaxWidth = 0; + for (int i = 0; i < iItemCount; i++) + { + rctlComboBox.GetLBText(i, strItem); + int iItemWidth = pDC->GetOutputTextExtent(strItem, strItem.GetLength()).cx; + if (iItemWidth > iMaxWidth) + iMaxWidth = iItemWidth; + } + + pDC->SelectObject(pOldFont); + rctlComboBox.ReleaseDC(pDC); + + // Depending on the string (lot of "M" or lot of "i") sometime the + // width is just a few pixels too small! + iMaxWidth += 4; + if (iIconWidth) + iMaxWidth += 2 + iIconWidth + 2; + rctlComboBox.SetHorizontalExtent(iMaxWidth); + if (rctlComboBox.GetDroppedWidth() < iMaxWidth) + rctlComboBox.SetDroppedWidth(iMaxWidth); + } + } + else + rctlComboBox.SetHorizontalExtent(0); +} + +HWND GetComboBoxEditCtrl(CComboBox& cb) +{ + CWnd* pWnd = cb.GetWindow(GW_CHILD); + while (pWnd) + { + CHAR szClassName[MAX_PATH]; + if (::GetClassNameA(*pWnd, szClassName, ARRSIZE(szClassName))) + { + if (__ascii_stricmp(szClassName, "EDIT") == 0) + return pWnd->m_hWnd; + } + pWnd = pWnd->GetNextWindow(); + } + return NULL; +} diff --git a/ComboBoxEx2.h b/ComboBoxEx2.h new file mode 100644 index 00000000..f460a288 --- /dev/null +++ b/ComboBoxEx2.h @@ -0,0 +1,20 @@ +#pragma once + +class CComboBoxEx2 : public CComboBoxEx +{ + DECLARE_DYNAMIC(CComboBoxEx2) +public: + CComboBoxEx2(); + virtual ~CComboBoxEx2(); + + int AddItem(LPCTSTR pszText, int iImage); + BOOL SelectString(LPCTSTR pszText); + BOOL SelectItemDataStringA(LPCSTR pszText); + + virtual BOOL PreTranslateMessage(MSG* pMsg); + +protected: + DECLARE_MESSAGE_MAP() +}; + +void UpdateHorzExtent(CComboBox &rctlComboBox, int iIconWidth); diff --git a/CommentDialog.cpp b/CommentDialog.cpp new file mode 100644 index 00000000..6cb167c4 --- /dev/null +++ b/CommentDialog.cpp @@ -0,0 +1,348 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "CommentDialog.h" +#include "KnownFile.h" +#include "PartFile.h" +#include "OtherFunctions.h" +#include "Opcodes.h" +#include "StringConversion.h" +#include "UpDownClient.h" +#include "kademlia/kademlia/kademlia.h" +#include "kademlia/kademlia/SearchManager.h" +#include "kademlia/kademlia/Search.h" +#include "UserMsgs.h" +#include "searchlist.h" +#include "sharedfilelist.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// CommentDialog dialog + +IMPLEMENT_DYNAMIC(CCommentDialog, CResizablePage) + +BEGIN_MESSAGE_MAP(CCommentDialog, CResizablePage) + ON_BN_CLICKED(IDC_RESET, OnBnClickedReset) + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) + ON_EN_CHANGE(IDC_CMT_TEXT, OnEnChangeCmtText) + ON_CBN_SELENDOK(IDC_RATELIST, OnCbnSelendokRatelist) + ON_CBN_SELCHANGE(IDC_RATELIST, OnCbnSelchangeRatelist) + ON_BN_CLICKED(IDC_SEARCHKAD, OnBnClickedSearchKad) + ON_WM_TIMER() + ON_WM_DESTROY() +END_MESSAGE_MAP() + +CCommentDialog::CCommentDialog() + : CResizablePage(CCommentDialog::IDD, 0) +{ + m_paFiles = NULL; + m_bDataChanged = false; + m_strCaption = GetResString(IDS_COMMENT); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; + m_bMergedComment = false; + m_bSelf = false; + m_timer = 0; + m_bEnabled = true; +} + +CCommentDialog::~CCommentDialog() +{ +} + +void CCommentDialog::DoDataExchange(CDataExchange* pDX) +{ + CResizablePage::DoDataExchange(pDX); + DDX_Control(pDX, IDC_RATELIST, m_ratebox); + DDX_Control(pDX, IDC_LST, m_lstComments); +} + +void CCommentDialog::OnTimer(UINT /*nIDEvent*/) +{ + RefreshData(false); +} + +BOOL CCommentDialog::OnInitDialog() +{ + CResizablePage::OnInitDialog(); + InitWindowStyles(this); + + AddAnchor(IDC_LST, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_CMT_LQUEST, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_CMT_LAIDE, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_CMT_TEXT, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_RATEQUEST, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_RATEHELP, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_USERCOMMENTS, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_RESET, TOP_RIGHT); + AddAnchor(IDC_SEARCHKAD, BOTTOM_RIGHT); + + m_lstComments.Init(); + Localize(); + + // start time for calling 'RefreshData' + VERIFY( (m_timer = SetTimer(301, 5000, 0)) != NULL ); + + return TRUE; +} + +BOOL CCommentDialog::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + if (m_bDataChanged) + { + bool bContainsSharedKnownFile = false;; + int iRating = -1; + m_bMergedComment = false; + CString strComment; + for (int i = 0; i < m_paFiles->GetSize(); i++) + { + if (!(*m_paFiles)[i]->IsKindOf(RUNTIME_CLASS(CKnownFile))) + continue; + CKnownFile* file = STATIC_DOWNCAST(CKnownFile, (*m_paFiles)[i]); + // we actually could show, add and even search for comments on kad for known but not shared files, + // but we don't publish coments entered by the user if the file is not shared (which might be changed at some point) + // so make sure we don't let him think he can comment and disable the dialog for such files + if (theApp.sharedfiles->GetFileByID(file->GetFileHash()) == NULL) + continue; + bContainsSharedKnownFile = true; + if (i == 0) + { + strComment = file->GetFileComment(); + iRating = file->GetFileRating(); + } + else + { + if (!m_bMergedComment && strComment.Compare(file->GetFileComment()) != 0) + { + strComment.Empty(); + m_bMergedComment = true; + } + if (iRating != -1 && (UINT)iRating != file->GetFileRating()) + iRating = -1; + } + } + m_bSelf = true; + SetDlgItemText(IDC_CMT_TEXT, strComment); + ((CEdit*)GetDlgItem(IDC_CMT_TEXT))->SetLimitText(MAXFILECOMMENTLEN); + m_ratebox.SetCurSel(iRating); + m_bSelf = false; + EnableDialog(bContainsSharedKnownFile); + + m_bDataChanged = false; + + RefreshData(); + } + + return TRUE; +} + +LRESULT CCommentDialog::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +void CCommentDialog::OnBnClickedReset() +{ + SetDlgItemText(IDC_CMT_TEXT, _T("")); + m_bMergedComment = false; + m_ratebox.SetCurSel(0); +} + +BOOL CCommentDialog::OnApply() +{ + if (m_bEnabled && !m_bDataChanged) + { + CString strComment; + GetDlgItem(IDC_CMT_TEXT)->GetWindowText(strComment); + int iRating = m_ratebox.GetCurSel(); + for (int i = 0; i < m_paFiles->GetSize(); i++) + { + if (!(*m_paFiles)[i]->IsKindOf(RUNTIME_CLASS(CKnownFile))) + continue; + CKnownFile* file = STATIC_DOWNCAST(CKnownFile, (*m_paFiles)[i]); + if (theApp.sharedfiles->GetFileByID(file->GetFileHash()) == NULL) + continue; + if (!strComment.IsEmpty() || !m_bMergedComment) + file->SetFileComment(strComment); + if (iRating != -1) + file->SetFileRating(iRating); + } + } + return CResizablePage::OnApply(); +} + +void CCommentDialog::Localize(void) +{ + GetDlgItem(IDC_RESET)->SetWindowText(GetResString(IDS_PW_RESET)); + + GetDlgItem(IDC_CMT_LQUEST)->SetWindowText(GetResString(IDS_CMT_QUEST)); + GetDlgItem(IDC_CMT_LAIDE)->SetWindowText(GetResString(IDS_CMT_AIDE)); + + GetDlgItem(IDC_RATEQUEST)->SetWindowText(GetResString(IDS_CMT_RATEQUEST)); + GetDlgItem(IDC_RATEHELP)->SetWindowText(GetResString(IDS_CMT_RATEHELP)); + + GetDlgItem(IDC_USERCOMMENTS)->SetWindowText(GetResString(IDS_COMMENT)); + GetDlgItem(IDC_SEARCHKAD)->SetWindowText(GetResString(IDS_SEARCHKAD)); + + CImageList iml; + iml.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + iml.Add(CTempIconLoader(_T("Rating_NotRated"))); + iml.Add(CTempIconLoader(_T("Rating_Fake"))); + iml.Add(CTempIconLoader(_T("Rating_Poor"))); + iml.Add(CTempIconLoader(_T("Rating_Fair"))); + iml.Add(CTempIconLoader(_T("Rating_Good"))); + iml.Add(CTempIconLoader(_T("Rating_Excellent"))); + m_ratebox.SetImageList(&iml); + m_imlRating.DeleteImageList(); + m_imlRating.Attach(iml.Detach()); + + m_ratebox.ResetContent(); + m_ratebox.AddItem(GetResString(IDS_CMT_NOTRATED), 0); + m_ratebox.AddItem(GetResString(IDS_CMT_FAKE), 1); + m_ratebox.AddItem(GetResString(IDS_CMT_POOR), 2); + m_ratebox.AddItem(GetResString(IDS_CMT_FAIR), 3); + m_ratebox.AddItem(GetResString(IDS_CMT_GOOD), 4); + m_ratebox.AddItem(GetResString(IDS_CMT_EXCELLENT), 5); + UpdateHorzExtent(m_ratebox, 16); // adjust dropped width to ensure all strings are fully visible + + RefreshData(); +} + +void CCommentDialog::OnDestroy() +{ + m_imlRating.DeleteImageList(); + CResizablePage::OnDestroy(); + if (m_timer){ + KillTimer(m_timer); + m_timer = 0; + } +} + +void CCommentDialog::OnEnChangeCmtText() +{ + if (!m_bSelf) + SetModified(); +} + +void CCommentDialog::OnCbnSelendokRatelist() +{ + if (!m_bSelf) + SetModified(); +} + +void CCommentDialog::OnCbnSelchangeRatelist() +{ + if (!m_bSelf) + SetModified(); +} + +void CCommentDialog::RefreshData(bool deleteOld) +{ + if (deleteOld) + m_lstComments.DeleteAllItems(); + + if (!m_bEnabled) + return; + + bool kadsearchable = true; + for (int i = 0; i < m_paFiles->GetSize(); i++) + { + CAbstractFile* file = STATIC_DOWNCAST(CAbstractFile, (*m_paFiles)[i]); + if (file->IsPartFile()) + { + for (POSITION pos = ((CPartFile*)file)->srclist.GetHeadPosition(); pos != NULL; ) + { + CUpDownClient* cur_src = ((CPartFile*)file)->srclist.GetNext(pos); + if (cur_src->HasFileRating() || !cur_src->GetFileComment().IsEmpty()) + m_lstComments.AddItem(cur_src); + } + } + else if (!file->IsKindOf(RUNTIME_CLASS(CKnownFile))) + continue; + else if (theApp.sharedfiles->GetFileByID(file->GetFileHash()) == NULL) + continue; + + const CTypedPtrList& list = file->getNotes(); + for (POSITION pos = list.GetHeadPosition(); pos != NULL; ) + { + Kademlia::CEntry* entry = list.GetNext(pos); + m_lstComments.AddItem(entry); + } + + // check if note searches are running for this file(s) + if (Kademlia::CSearchManager::AlreadySearchingFor(Kademlia::CUInt128(file->GetFileHash()))) + kadsearchable = false; + } + + CWnd* pWndFocus = GetFocus(); + if (Kademlia::CKademlia::IsConnected()) { + SetDlgItemText(IDC_SEARCHKAD, kadsearchable ? GetResString(IDS_SEARCHKAD) : GetResString(IDS_KADSEARCHACTIVE)); + GetDlgItem(IDC_SEARCHKAD)->EnableWindow(kadsearchable); + } + else { + SetDlgItemText(IDC_SEARCHKAD, GetResString(IDS_SEARCHKAD)); + GetDlgItem(IDC_SEARCHKAD)->EnableWindow(FALSE); + } + if (pWndFocus && pWndFocus->m_hWnd == GetDlgItem(IDC_SEARCHKAD)->m_hWnd) + m_lstComments.SetFocus(); +} + +void CCommentDialog::OnBnClickedSearchKad() +{ + if (m_bEnabled && Kademlia::CKademlia::IsConnected()) + { + bool bSkipped = false; + int iMaxSearches = min(m_paFiles->GetSize(), KADEMLIATOTALFILE); + for (int i = 0; i < iMaxSearches; i++) + { + CAbstractFile* file = STATIC_DOWNCAST(CAbstractFile, (*m_paFiles)[i]); + if (file && file->IsKindOf(RUNTIME_CLASS(CKnownFile)) && theApp.sharedfiles->GetFileByID(file->GetFileHash()) != NULL) + { + if (!Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::NOTES, true, Kademlia::CUInt128(file->GetFileHash()))) + bSkipped = true; + else{ + theApp.searchlist->SetNotesSearchStatus(file->GetFileHash(), true); + file->SetKadCommentSearchRunning(true); + } + } + } + if (bSkipped) + AfxMessageBox(GetResString(IDS_KADSEARCHALREADY), MB_OK | MB_ICONINFORMATION); + } + RefreshData(); +} + +void CCommentDialog::EnableDialog(bool bEnabled) +{ + if (m_bEnabled == bEnabled) + return; + m_bEnabled = bEnabled; + GetDlgItem(IDC_LST)->EnableWindow(m_bEnabled ? TRUE : FALSE); + GetDlgItem(IDC_CMT_TEXT)->EnableWindow(m_bEnabled ? TRUE : FALSE); + GetDlgItem(IDC_RATELIST)->EnableWindow(m_bEnabled ? TRUE : FALSE); + GetDlgItem(IDC_RESET)->EnableWindow(m_bEnabled ? TRUE : FALSE); + GetDlgItem(IDC_SEARCHKAD)->EnableWindow(m_bEnabled ? TRUE : FALSE); +} \ No newline at end of file diff --git a/CommentDialog.h b/CommentDialog.h new file mode 100644 index 00000000..e221c819 --- /dev/null +++ b/CommentDialog.h @@ -0,0 +1,53 @@ +#pragma once +#include "ResizableLib/ResizablePage.h" +#include "ComboBoxEx2.h" +#include "CommentListCtrl.h" + +class CKnownFile; +namespace Kademlia { + class CEntry; +}; + +class CCommentDialog : public CResizablePage +{ + DECLARE_DYNAMIC(CCommentDialog) + +public: + CCommentDialog(); // standard constructor + virtual ~CCommentDialog(); + + void SetFiles(const CSimpleArray* paFiles) { m_paFiles = paFiles; m_bDataChanged = true; } + + // Dialog Data + enum { IDD = IDD_COMMENT }; + + void Localize(); + +protected: + const CSimpleArray* m_paFiles; + bool m_bDataChanged; + CComboBoxEx2 m_ratebox; + CImageList m_imlRating; + CCommentListCtrl m_lstComments; + bool m_bMergedComment; + bool m_bSelf; + uint32 m_timer; + bool m_bEnabled; + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + virtual BOOL OnSetActive(); + virtual BOOL OnApply(); + void RefreshData(bool deleteOld = true); + void EnableDialog(bool bEnabled); + + DECLARE_MESSAGE_MAP() + afx_msg void OnBnClickedSearchKad(); + afx_msg void OnBnClickedReset(); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); + afx_msg void OnEnChangeCmtText(); + afx_msg void OnCbnSelendokRatelist(); + afx_msg void OnCbnSelchangeRatelist(); + afx_msg void OnDestroy(); + afx_msg void OnTimer(UINT nIDEvent); +}; diff --git a/CommentDialogLst.cpp b/CommentDialogLst.cpp new file mode 100644 index 00000000..cc15cb22 --- /dev/null +++ b/CommentDialogLst.cpp @@ -0,0 +1,228 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "CommentDialogLst.h" +#include "PartFile.h" +#include "UpDownClient.h" +#include "UserMsgs.h" +#include "kademlia/kademlia/kademlia.h" +#include "kademlia/kademlia/SearchManager.h" +#include "kademlia/kademlia/Search.h" +#include "searchlist.h" +#include "InputBox.h" +#include "DownloadQueue.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +IMPLEMENT_DYNAMIC(CCommentDialogLst, CResizablePage) + +BEGIN_MESSAGE_MAP(CCommentDialogLst, CResizablePage) + ON_BN_CLICKED(IDOK, OnBnClickedApply) + ON_BN_CLICKED(IDC_SEARCHKAD, OnBnClickedSearchKad) + ON_BN_CLICKED(IDC_EDITCOMMENTFILTER, OnBnClickedFilter) + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) + ON_WM_TIMER() + ON_WM_DESTROY() +END_MESSAGE_MAP() + +CCommentDialogLst::CCommentDialogLst() + : CResizablePage(CCommentDialogLst::IDD, IDS_CMT_READALL) +{ + m_paFiles = NULL; + m_bDataChanged = false; + m_strCaption = GetResString(IDS_CMT_READALL); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; + m_paFiles = NULL; + m_timer = 0; +} + +CCommentDialogLst::~CCommentDialogLst() +{ +} + +void CCommentDialogLst::DoDataExchange(CDataExchange* pDX) +{ + CResizablePage::DoDataExchange(pDX); + DDX_Control(pDX, IDC_LST, m_lstComments); +} + +void CCommentDialogLst::OnBnClickedApply() +{ + CResizablePage::OnOK(); +} + +void CCommentDialogLst::OnTimer(UINT /*nIDEvent*/) +{ + RefreshData(false); +} + +BOOL CCommentDialogLst::OnInitDialog() +{ + CResizablePage::OnInitDialog(); + InitWindowStyles(this); + + AddAnchor(IDC_LST,TOP_LEFT,BOTTOM_RIGHT); + AddAnchor(IDC_SEARCHKAD,BOTTOM_RIGHT); + AddAnchor(IDC_EDITCOMMENTFILTER,BOTTOM_LEFT); + + m_lstComments.Init(); + Localize(); + + // start time for calling 'RefreshData' + VERIFY( (m_timer = SetTimer(301, 5000, 0)) != NULL ); + + return TRUE; +} + +BOOL CCommentDialogLst::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + if (m_bDataChanged) + { + RefreshData(); + m_bDataChanged = false; + } + return TRUE; +} + +LRESULT CCommentDialogLst::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +void CCommentDialogLst::OnDestroy() +{ + if (m_timer){ + KillTimer(m_timer); + m_timer = 0; + } +} + +void CCommentDialogLst::Localize(void) +{ + GetDlgItem(IDC_SEARCHKAD)->SetWindowText(GetResString(IDS_SEARCHKAD)); + GetDlgItem(IDC_EDITCOMMENTFILTER)->SetWindowText(GetResString(IDS_EDITSPAMFILTER)); +} + +void CCommentDialogLst::RefreshData(bool deleteOld) +{ + if (deleteOld) + m_lstComments.DeleteAllItems(); + + bool kadsearchable = true; + for (int i = 0; i < m_paFiles->GetSize(); i++) + { + CAbstractFile* file = STATIC_DOWNCAST(CAbstractFile, (*m_paFiles)[i]); + if (file->IsPartFile()) + { + for (POSITION pos = ((CPartFile*)file)->srclist.GetHeadPosition(); pos != NULL; ) + { + CUpDownClient* cur_src = ((CPartFile*)file)->srclist.GetNext(pos); + if (cur_src->HasFileRating() || !cur_src->GetFileComment().IsEmpty()) + m_lstComments.AddItem(cur_src); + } + } + + const CTypedPtrList& list = file->getNotes(); + for (POSITION pos = list.GetHeadPosition(); pos != NULL; ) + { + Kademlia::CEntry* entry = list.GetNext(pos); + m_lstComments.AddItem(entry); + } + if (file->IsPartFile()) + ((CPartFile*)file)->UpdateFileRatingCommentAvail(); + + // check if note searches are running for this file(s) + if (Kademlia::CSearchManager::AlreadySearchingFor(Kademlia::CUInt128(file->GetFileHash()))) + kadsearchable = false; + } + + CWnd* pWndFocus = GetFocus(); + if (Kademlia::CKademlia::IsConnected()) { + SetDlgItemText(IDC_SEARCHKAD, kadsearchable ? GetResString(IDS_SEARCHKAD) : GetResString(IDS_KADSEARCHACTIVE)); + GetDlgItem(IDC_SEARCHKAD)->EnableWindow(kadsearchable); + } + else { + SetDlgItemText(IDC_SEARCHKAD, GetResString(IDS_SEARCHKAD)); + GetDlgItem(IDC_SEARCHKAD)->EnableWindow(FALSE); + } + if (pWndFocus && pWndFocus->m_hWnd == GetDlgItem(IDC_SEARCHKAD)->m_hWnd) + m_lstComments.SetFocus(); +} + +void CCommentDialogLst::OnBnClickedSearchKad() +{ + if (Kademlia::CKademlia::IsConnected()) + { + bool bSkipped = false; + int iMaxSearches = min(m_paFiles->GetSize(), KADEMLIATOTALFILE); + for (int i = 0; i < iMaxSearches; i++) + { + CAbstractFile* file = STATIC_DOWNCAST(CAbstractFile, (*m_paFiles)[i]); + if (file) + { + if (!Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::NOTES, true, Kademlia::CUInt128(file->GetFileHash()))) + bSkipped = true; + else{ + theApp.searchlist->SetNotesSearchStatus(file->GetFileHash(), true); + file->SetKadCommentSearchRunning(true); + } + } + } + if (bSkipped) + AfxMessageBox(GetResString(IDS_KADSEARCHALREADY), MB_OK | MB_ICONINFORMATION); + } + RefreshData(); +} + +void CCommentDialogLst::OnBnClickedFilter() +{ + InputBox inputbox; + inputbox.SetLabels(GetResString(IDS_EDITSPAMFILTERCOMMENTS), GetResString(IDS_FILTERCOMMENTSLABEL), thePrefs.GetCommentFilter()); + inputbox.DoModal(); + if (!inputbox.WasCancelled()){ + CString strCommentFilters = inputbox.GetInput(); + strCommentFilters.MakeLower(); + CString strNewCommentFilters; + int curPos = 0; + CString strFilter(strCommentFilters.Tokenize(_T("|"), curPos)); + while (!strFilter.IsEmpty()) + { + strFilter.Trim(); + if (!strNewCommentFilters.IsEmpty()) + strNewCommentFilters += _T('|'); + strNewCommentFilters += strFilter; + strFilter = strCommentFilters.Tokenize(_T("|"), curPos); + } + if (thePrefs.GetCommentFilter() != strNewCommentFilters){ + thePrefs.SetCommentFilter(strNewCommentFilters); + theApp.downloadqueue->RefilterAllComments(); + RefreshData(); + } + } +} + + diff --git a/CommentDialogLst.h b/CommentDialogLst.h new file mode 100644 index 00000000..4e69b328 --- /dev/null +++ b/CommentDialogLst.h @@ -0,0 +1,45 @@ +#pragma once +#include "ResizableLib/ResizablePage.h" +#include "CommentListCtrl.h" + +class CPartFile; + + +/////////////////////////////////////////////////////////////////////////////// +// CCommentDialogLst + +class CCommentDialogLst : public CResizablePage +{ + DECLARE_DYNAMIC(CCommentDialogLst) + +public: + CCommentDialogLst(); + virtual ~CCommentDialogLst(); + + void SetFiles(const CSimpleArray* paFiles) { m_paFiles = paFiles; m_bDataChanged = true; } + +// Dialog Data + enum { IDD = IDD_COMMENTLST }; + +protected: + CString m_strCaption; + CCommentListCtrl m_lstComments; + const CSimpleArray* m_paFiles; + bool m_bDataChanged; + uint32 m_timer; + + void Localize(); + void RefreshData(bool deleteOld = true); + + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnSetActive(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnBnClickedApply(); + afx_msg void OnBnClickedSearchKad(); + afx_msg void OnBnClickedFilter(); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); + afx_msg void OnDestroy(); + afx_msg void OnTimer(UINT nIDEvent); +}; diff --git a/CommentListCtrl.cpp b/CommentListCtrl.cpp new file mode 100644 index 00000000..7214099e --- /dev/null +++ b/CommentListCtrl.cpp @@ -0,0 +1,246 @@ +//this file is part of eMule +//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "CommentListCtrl.h" +#include "OtherFunctions.h" +#include "MenuCmds.h" +#include "TitleMenu.h" +#include "emule.h" +#include "UpDownClient.h" +#include "kademlia/kademlia/Entry.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[]=__FILE__; +#endif + +enum ECols +{ + colRating = 0, + colComment, + colFileName, + colUserName, + colOrigin +}; + +IMPLEMENT_DYNAMIC(CCommentListCtrl, CMuleListCtrl) + +BEGIN_MESSAGE_MAP(CCommentListCtrl, CMuleListCtrl) + ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnLvnColumnClick) + ON_NOTIFY_REFLECT(LVN_DELETEITEM, OnLvnDeleteItem) + ON_WM_CONTEXTMENU() +END_MESSAGE_MAP() + +CCommentListCtrl::CCommentListCtrl() +{ +} + +CCommentListCtrl::~CCommentListCtrl() +{ +} + +void CCommentListCtrl::Init(void) +{ + SetPrefsKey(_T("CommentListCtrl")); + ASSERT( (GetStyle() & LVS_SINGLESEL) == 0 ); + SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + + InsertColumn(colRating, GetResString(IDS_QL_RATING), LVCFMT_LEFT, 80); + InsertColumn(colComment, GetResString(IDS_COMMENT), LVCFMT_LEFT, 340); + InsertColumn(colFileName, GetResString(IDS_DL_FILENAME), LVCFMT_LEFT, DFLT_FILENAME_COL_WIDTH); + InsertColumn(colUserName, GetResString(IDS_QL_USERNAME), LVCFMT_LEFT, DFLT_CLIENTNAME_COL_WIDTH); + InsertColumn(colOrigin, GetResString(IDS_NETWORK), LVCFMT_LEFT, 80); + + CImageList iml; + iml.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + iml.Add(CTempIconLoader(_T("Rating_NotRated"))); + iml.Add(CTempIconLoader(_T("Rating_Fake"))); + iml.Add(CTempIconLoader(_T("Rating_Poor"))); + iml.Add(CTempIconLoader(_T("Rating_Fair"))); + iml.Add(CTempIconLoader(_T("Rating_Good"))); + iml.Add(CTempIconLoader(_T("Rating_Excellent"))); + CImageList* pimlOld = SetImageList(&iml, LVSIL_SMALL); + iml.Detach(); + if (pimlOld) + pimlOld->DeleteImageList(); + + LoadSettings(); + SetSortArrow(); + SortItems(SortProc, MAKELONG(GetSortItem(), (GetSortAscending() ? 0 : 1))); +} + +int CCommentListCtrl::SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + const SComment *item1 = (SComment *)lParam1; + const SComment *item2 = (SComment *)lParam2; + if (item1 == NULL || item2 == NULL) + return 0; + + int iResult; + switch (LOWORD(lParamSort)) + { + case colRating: + if (item1->m_iRating < item2->m_iRating) + iResult = -1; + else if (item1->m_iRating > item2->m_iRating) + iResult = 1; + else + iResult = 0; + break; + + case colComment: + iResult = CompareLocaleStringNoCase(item1->m_strComment, item2->m_strComment); + break; + + case colFileName: + iResult = CompareLocaleStringNoCase(item1->m_strFileName, item2->m_strFileName); + break; + + case colUserName: + iResult = CompareLocaleStringNoCase(item1->m_strUserName, item2->m_strUserName); + break; + + case colOrigin: + if (item1->m_iOrigin < item2->m_iOrigin) + iResult = -1; + else if (item1->m_iOrigin > item2->m_iOrigin) + iResult = 1; + else + iResult = 0; + break; + + default: + ASSERT(0); + return 0; + } + if (HIWORD(lParamSort)) + iResult = -iResult; + return iResult; +} + +void CCommentListCtrl::OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMLISTVIEW pNMLV = reinterpret_cast(pNMHDR); + + // Determine ascending based on whether already sorted on this column + int iSortItem = GetSortItem(); + bool bOldSortAscending = GetSortAscending(); + bool bSortAscending = (iSortItem != pNMLV->iSubItem) ? true : !bOldSortAscending; + + // Item is column clicked + iSortItem = pNMLV->iSubItem; + + // Sort table + UpdateSortHistory(MAKELONG(iSortItem, (bSortAscending ? 0 : 0x0001))); + SetSortArrow(iSortItem, bSortAscending); + SortItems(SortProc, MAKELONG(iSortItem, (bSortAscending ? 0 : 0x0001))); + + *pResult = 0; +} + +void CCommentListCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + UINT flag = MF_STRING; + if (GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED) == -1) + flag = MF_GRAYED; + + CTitleMenu popupMenu; + popupMenu.CreatePopupMenu(); + popupMenu.AppendMenu(MF_STRING | flag, MP_COPYSELECTED, GetResString(IDS_COPY)); + + GetPopupMenuPos(*this, point); + popupMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); + VERIFY( popupMenu.DestroyMenu() ); +} + +BOOL CCommentListCtrl::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (wParam) + { + case MP_COPYSELECTED: { + CString strText; + POSITION posItem = GetFirstSelectedItemPosition(); + while (posItem) { + int iItem = GetNextSelectedItem(posItem); + if (iItem >= 0) { + CString strComment = GetItemText(iItem, colComment); + if (!strComment.IsEmpty()) { + if (!strText.IsEmpty()) + strText += _T("\r\n"); + strText += strComment; + } + } + } + theApp.CopyTextToClipboard(strText); + break; + } + } + return CMuleListCtrl::OnCommand(wParam, lParam); +} + +int CCommentListCtrl::FindClientComment(const void* pClientCookie) +{ + int iItems = GetItemCount(); + for (int i = 0; i < iItems; i++) + { + const SComment* pComment = (SComment*)GetItemData(i); + if (pComment && pComment->m_pClientCookie == pClientCookie) + return i; + } + return -1; +} + +void CCommentListCtrl::AddComment(const SComment* pComment) +{ + int iItem = InsertItem(LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM, + 0, GetRateString(pComment->m_iRating), + 0, 0, pComment->m_iRating, (LPARAM)pComment); + SetItemText(iItem, colComment, pComment->m_strComment); + SetItemText(iItem, colFileName, pComment->m_strFileName); + SetItemText(iItem, colUserName, pComment->m_strUserName); + SetItemText(iItem, colOrigin, pComment->m_iOrigin == 0 ? _T("eD2K") : _T("Kad")); +} + +void CCommentListCtrl::AddItem(const CUpDownClient* client) +{ + const void* pClientCookie = client; + if (FindClientComment(pClientCookie) != -1) + return; + int iRating = client->GetFileRating(); + SComment* pComment = new SComment(pClientCookie, iRating, client->GetFileComment(), + client->GetClientFilename(), client->GetUserName(), 0/*eD2K*/); + AddComment(pComment); +} + +void CCommentListCtrl::AddItem(const Kademlia::CEntry* entry) +{ + const void* pClientCookie = entry; + if (FindClientComment(pClientCookie) != -1) + return; + int iRating = (int)entry->GetIntTagValue(TAG_FILERATING); + SComment* pComment = new SComment(pClientCookie, iRating, entry->GetStrTagValue(TAG_DESCRIPTION), + entry->GetCommonFileName(), _T(""), 1/*Kad*/); + AddComment(pComment); +} + +void CCommentListCtrl::OnLvnDeleteItem(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMLISTVIEW pNMLV = reinterpret_cast(pNMHDR); + delete (SComment*)pNMLV->lParam; + *pResult = 0; +} diff --git a/CommentListCtrl.h b/CommentListCtrl.h new file mode 100644 index 00000000..226ff4b4 --- /dev/null +++ b/CommentListCtrl.h @@ -0,0 +1,65 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. +#pragma once +#include "MuleListCtrl.h" + +class CUpDownClient; +namespace Kademlia +{ + class CEntry; +}; + +class CCommentListCtrl : public CMuleListCtrl +{ + DECLARE_DYNAMIC(CCommentListCtrl) + +public: + CCommentListCtrl(); + virtual ~CCommentListCtrl(); + + void Init(); + void AddItem(const CUpDownClient* client); + void AddItem(const Kademlia::CEntry* entry); + +protected: + struct SComment + { + SComment(const void* pClientCookie, int iRating, const CString& strComment, + const CString& strFileName, const CString& strUserName, int iOrigin) + : m_pClientCookie(pClientCookie), m_iRating(iRating), + m_strComment(strComment), m_strFileName(strFileName), + m_strUserName(strUserName), m_iOrigin(iOrigin) + { } + + const void* m_pClientCookie; + int m_iRating; + CString m_strComment; + CString m_strFileName; + CString m_strUserName; + int m_iOrigin; // 0=eD2K, 1=Kad + }; + void AddComment(const SComment* pComment); + int FindClientComment(const void* pCookie); + + static int CALLBACK SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + + DECLARE_MESSAGE_MAP() + afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); + afx_msg void OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnDeleteItem(NMHDR *pNMHDR, LRESULT *pResult); +}; diff --git a/CorruptionBlackBox.cpp b/CorruptionBlackBox.cpp new file mode 100644 index 00000000..c8b22013 --- /dev/null +++ b/CorruptionBlackBox.cpp @@ -0,0 +1,395 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "StdAfx.h" +#include "corruptionblackbox.h" +#include "knownfile.h" +#include "updownclient.h" +#include "log.h" +#include "emule.h" +#include "clientlist.h" +#include "opcodes.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define CBB_BANTHRESHOLD 32 //% max corrupted data + +CCBBRecord::CCBBRecord(uint64 nStartPos, uint64 nEndPos, uint32 dwIP, EBBRStatus BBRStatus){ + if (nStartPos > nEndPos){ + ASSERT( false ); + return; + } + m_nStartPos = nStartPos; + m_nEndPos = nEndPos; + m_dwIP = dwIP; + m_BBRStatus = BBRStatus; +} + +CCBBRecord& CCBBRecord::operator=(const CCBBRecord& cv) +{ + m_nStartPos = cv.m_nStartPos; + m_nEndPos = cv.m_nEndPos; + m_dwIP = cv.m_dwIP; + m_BBRStatus = cv.m_BBRStatus; + return *this; +} + +bool CCBBRecord::Merge(uint64 nStartPos, uint64 nEndPos, uint32 dwIP, EBBRStatus BBRStatus){ + + if (m_dwIP == dwIP && m_BBRStatus == BBRStatus && (nStartPos == m_nEndPos + 1 || nEndPos + 1 == m_nStartPos)){ + if (nStartPos == m_nEndPos + 1) + m_nEndPos = nEndPos; + else if (nEndPos + 1 == m_nStartPos) + m_nStartPos = nStartPos; + else + ASSERT( false ); + + return true; + } + else + return false; +} + +bool CCBBRecord::CanMerge(uint64 nStartPos, uint64 nEndPos, uint32 dwIP, EBBRStatus BBRStatus){ + + if (m_dwIP == dwIP && m_BBRStatus == BBRStatus && (nStartPos == m_nEndPos + 1 || nEndPos + 1 == m_nStartPos)){ + return true; + } + else + return false; +} + +void CCorruptionBlackBox::Init(EMFileSize nFileSize) { + m_aaRecords.SetSize((INT_PTR)((uint64)(nFileSize + (uint64)(PARTSIZE - 1)) / (PARTSIZE))); +} + +void CCorruptionBlackBox::Free() { + m_aaRecords.RemoveAll(); + m_aaRecords.FreeExtra(); +} + +void CCorruptionBlackBox::TransferredData(uint64 nStartPos, uint64 nEndPos, const CUpDownClient* pSender){ + if (nEndPos - nStartPos >= PARTSIZE){ + ASSERT( false ); + return; + } + if (nStartPos > nEndPos){ + ASSERT( false ); + return; + } + uint32 dwSenderIP = pSender->GetIP(); + // we store records seperated for each part, so we don't have to search all entries everytime + + // convert pos to relative block pos + UINT nPart = (UINT)(nStartPos / PARTSIZE); + uint64 nRelStartPos = nStartPos - (uint64)nPart*PARTSIZE; + uint64 nRelEndPos = nEndPos - (uint64)nPart*PARTSIZE; + if (nRelEndPos >= PARTSIZE){ + // data crosses the partborder, split it + nRelEndPos = PARTSIZE-1; + uint64 nTmpStartPos = (uint64)nPart*PARTSIZE + nRelEndPos + 1; + ASSERT( nTmpStartPos % PARTSIZE == 0); // remove later + TransferredData(nTmpStartPos, nEndPos, pSender); + } + if (nPart >= (UINT)m_aaRecords.GetCount()){ + //ASSERT( false ); + m_aaRecords.SetSize(nPart+1); + } + int posMerge = -1; + uint64 ndbgRewritten = 0; + for (int i= 0; i < m_aaRecords[nPart].GetCount(); i++){ + if (m_aaRecords[nPart][i].CanMerge(nRelStartPos, nRelEndPos, dwSenderIP, BBR_NONE)){ + posMerge = i; + } + // check if there is already an pending entry and overwrite it + else if (m_aaRecords[nPart][i].m_BBRStatus == BBR_NONE){ + if (m_aaRecords[nPart][i].m_nStartPos >= nRelStartPos && m_aaRecords[nPart][i].m_nEndPos <= nRelEndPos){ + // old one is included in new one -> delete + ndbgRewritten += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart].RemoveAt(i); + i--; + } + else if (m_aaRecords[nPart][i].m_nStartPos < nRelStartPos && m_aaRecords[nPart][i].m_nEndPos > nRelEndPos){ + // old one includes new one + // check if the old one and new one have the same ip + if (dwSenderIP != m_aaRecords[nPart][i].m_dwIP){ + // different IP, means we have to split it 2 times + uint64 nTmpEndPos1 = m_aaRecords[nPart][i].m_nEndPos; + uint64 nTmpStartPos1 = nRelEndPos + 1; + uint64 nTmpStartPos2 = m_aaRecords[nPart][i].m_nStartPos; + uint64 nTmpEndPos2 = nRelStartPos - 1; + m_aaRecords[nPart][i].m_nEndPos = nRelEndPos; + m_aaRecords[nPart][i].m_nStartPos = nRelStartPos; + uint32 dwOldIP = m_aaRecords[nPart][i].m_dwIP; + m_aaRecords[nPart][i].m_dwIP = dwSenderIP; + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos1,nTmpEndPos1, dwOldIP)); + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos2,nTmpEndPos2, dwOldIP)); + // and are done then + } + DEBUG_ONLY( AddDebugLogLine(DLP_DEFAULT, false, _T("CorruptionBlackBox: Debug: %i bytes were rewritten and records replaced with new stats (1)"), (nRelEndPos - nRelStartPos)+1) ); + return; + } + else if (m_aaRecords[nPart][i].m_nStartPos >= nRelStartPos && m_aaRecords[nPart][i].m_nStartPos <= nRelEndPos){ + // old one laps over new one on the right site + ASSERT( nRelEndPos - m_aaRecords[nPart][i].m_nStartPos > 0 ); + ndbgRewritten += nRelEndPos - m_aaRecords[nPart][i].m_nStartPos; + m_aaRecords[nPart][i].m_nStartPos = nRelEndPos + 1; + } + else if (m_aaRecords[nPart][i].m_nEndPos >= nRelStartPos && m_aaRecords[nPart][i].m_nEndPos <= nRelEndPos){ + // old one laps over new one on the left site + ASSERT( m_aaRecords[nPart][i].m_nEndPos - nRelStartPos > 0 ); + ndbgRewritten += m_aaRecords[nPart][i].m_nEndPos - nRelStartPos; + m_aaRecords[nPart][i].m_nEndPos = nRelStartPos - 1; + } + } + } + if (posMerge != (-1) ){ + VERIFY( m_aaRecords[nPart][posMerge].Merge(nRelStartPos, nRelEndPos, dwSenderIP, BBR_NONE) ); + } + else + m_aaRecords[nPart].Add(CCBBRecord(nRelStartPos, nRelEndPos, dwSenderIP, BBR_NONE)); + + if (ndbgRewritten > 0){ + DEBUG_ONLY( AddDebugLogLine(DLP_DEFAULT, false, _T("CorruptionBlackBox: Debug: %i bytes were rewritten and records replaced with new stats (2)"), ndbgRewritten) ); + } +} + +void CCorruptionBlackBox::VerifiedData(uint64 nStartPos, uint64 nEndPos){ + if (nEndPos - nStartPos >= PARTSIZE){ + ASSERT( false ); + return; + } + // convert pos to relative block pos + UINT nPart = (UINT)(nStartPos / PARTSIZE); + uint64 nRelStartPos = nStartPos - (uint64)nPart*PARTSIZE; + uint64 nRelEndPos = nEndPos - (uint64)nPart*PARTSIZE; + if (nRelEndPos >= PARTSIZE){ + ASSERT( false ); + return; + } + if (nPart >= (UINT)m_aaRecords.GetCount()){ + //ASSERT( false ); + m_aaRecords.SetSize(nPart+1); + } + uint64 nDbgVerifiedBytes = 0; + //uint32 nDbgOldEntries = m_aaRecords[nPart].GetCount(); +#ifdef _DEBUG + CMap mapDebug; +#endif + for (int i= 0; i < m_aaRecords[nPart].GetCount(); i++){ + if (m_aaRecords[nPart][i].m_BBRStatus == BBR_NONE || m_aaRecords[nPart][i].m_BBRStatus == BBR_VERIFIED){ + if (m_aaRecords[nPart][i].m_nStartPos >= nRelStartPos && m_aaRecords[nPart][i].m_nEndPos <= nRelEndPos){ + nDbgVerifiedBytes += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart][i].m_BBRStatus = BBR_VERIFIED; + DEBUG_ONLY(mapDebug.SetAt(m_aaRecords[nPart][i].m_dwIP, 1)); + } + else if (m_aaRecords[nPart][i].m_nStartPos < nRelStartPos && m_aaRecords[nPart][i].m_nEndPos > nRelEndPos){ + // need to split it 2* + uint64 nTmpEndPos1 = m_aaRecords[nPart][i].m_nEndPos; + uint64 nTmpStartPos1 = nRelEndPos + 1; + uint64 nTmpStartPos2 = m_aaRecords[nPart][i].m_nStartPos; + uint64 nTmpEndPos2 = nRelStartPos - 1; + m_aaRecords[nPart][i].m_nEndPos = nRelEndPos; + m_aaRecords[nPart][i].m_nStartPos = nRelStartPos; + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos1, nTmpEndPos1, m_aaRecords[nPart][i].m_dwIP, m_aaRecords[nPart][i].m_BBRStatus)); + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos2, nTmpEndPos2, m_aaRecords[nPart][i].m_dwIP, m_aaRecords[nPart][i].m_BBRStatus)); + nDbgVerifiedBytes += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart][i].m_BBRStatus = BBR_VERIFIED; + DEBUG_ONLY(mapDebug.SetAt(m_aaRecords[nPart][i].m_dwIP, 1)); + } + else if (m_aaRecords[nPart][i].m_nStartPos >= nRelStartPos && m_aaRecords[nPart][i].m_nStartPos <= nRelEndPos){ + // need to split it + uint64 nTmpEndPos = m_aaRecords[nPart][i].m_nEndPos; + uint64 nTmpStartPos = nRelEndPos + 1; + m_aaRecords[nPart][i].m_nEndPos = nRelEndPos; + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos, nTmpEndPos, m_aaRecords[nPart][i].m_dwIP, m_aaRecords[nPart][i].m_BBRStatus)); + nDbgVerifiedBytes += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart][i].m_BBRStatus = BBR_VERIFIED; + DEBUG_ONLY(mapDebug.SetAt(m_aaRecords[nPart][i].m_dwIP, 1)); + } + else if (m_aaRecords[nPart][i].m_nEndPos >= nRelStartPos && m_aaRecords[nPart][i].m_nEndPos <= nRelEndPos){ + // need to split it + uint64 nTmpStartPos = m_aaRecords[nPart][i].m_nStartPos; + uint64 nTmpEndPos = nRelStartPos - 1; + m_aaRecords[nPart][i].m_nStartPos = nRelStartPos; + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos, nTmpEndPos, m_aaRecords[nPart][i].m_dwIP, m_aaRecords[nPart][i].m_BBRStatus)); + nDbgVerifiedBytes += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart][i].m_BBRStatus = BBR_VERIFIED; + DEBUG_ONLY(mapDebug.SetAt(m_aaRecords[nPart][i].m_dwIP, 1)); + } + } + } +/*#ifdef _DEBUG + uint32 nClients = mapDebug.GetCount(); +#else + uint32 nClients = 0; +#endif + AddDebugLogLine(DLP_DEFAULT, false, _T("Found and marked %u recorded bytes of %u as verified in the CorruptionBlackBox records, %u(%u) records found, %u different clients"), nDbgVerifiedBytes, (nEndPos-nStartPos)+1, m_aaRecords[nPart].GetCount(), nDbgOldEntries, nClients);*/ +} + + + +void CCorruptionBlackBox::CorruptedData(uint64 nStartPos, uint64 nEndPos){ + if (nEndPos - nStartPos >= EMBLOCKSIZE){ + ASSERT( false ); + return; + } + // convert pos to relative block pos + UINT nPart = (UINT)(nStartPos / PARTSIZE); + uint64 nRelStartPos = nStartPos - (uint64)nPart*PARTSIZE; + uint64 nRelEndPos = nEndPos - (uint64)nPart*PARTSIZE; + if (nRelEndPos >= PARTSIZE){ + ASSERT( false ); + return; + } + if (nPart >= (UINT)m_aaRecords.GetCount()){ + //ASSERT( false ); + m_aaRecords.SetSize(nPart+1); + } + uint64 nDbgVerifiedBytes = 0; + for (int i= 0; i < m_aaRecords[nPart].GetCount(); i++){ + if (m_aaRecords[nPart][i].m_BBRStatus == BBR_NONE){ + if (m_aaRecords[nPart][i].m_nStartPos >= nRelStartPos && m_aaRecords[nPart][i].m_nEndPos <= nRelEndPos){ + nDbgVerifiedBytes += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart][i].m_BBRStatus = BBR_CORRUPTED; + } + else if (m_aaRecords[nPart][i].m_nStartPos < nRelStartPos && m_aaRecords[nPart][i].m_nEndPos > nRelEndPos){ + // need to split it 2* + uint64 nTmpEndPos1 = m_aaRecords[nPart][i].m_nEndPos; + uint64 nTmpStartPos1 = nRelEndPos + 1; + uint64 nTmpStartPos2 = m_aaRecords[nPart][i].m_nStartPos; + uint64 nTmpEndPos2 = nRelStartPos - 1; + m_aaRecords[nPart][i].m_nEndPos = nRelEndPos; + m_aaRecords[nPart][i].m_nStartPos = nRelStartPos; + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos1, nTmpEndPos1, m_aaRecords[nPart][i].m_dwIP, m_aaRecords[nPart][i].m_BBRStatus)); + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos2, nTmpEndPos2, m_aaRecords[nPart][i].m_dwIP, m_aaRecords[nPart][i].m_BBRStatus)); + nDbgVerifiedBytes += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart][i].m_BBRStatus = BBR_CORRUPTED; + } + else if (m_aaRecords[nPart][i].m_nStartPos >= nRelStartPos && m_aaRecords[nPart][i].m_nStartPos <= nRelEndPos){ + // need to split it + uint64 nTmpEndPos = m_aaRecords[nPart][i].m_nEndPos; + uint64 nTmpStartPos = nRelEndPos + 1; + m_aaRecords[nPart][i].m_nEndPos = nRelEndPos; + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos, nTmpEndPos, m_aaRecords[nPart][i].m_dwIP, m_aaRecords[nPart][i].m_BBRStatus)); + nDbgVerifiedBytes += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart][i].m_BBRStatus = BBR_CORRUPTED; + } + else if (m_aaRecords[nPart][i].m_nEndPos >= nRelStartPos && m_aaRecords[nPart][i].m_nEndPos <= nRelEndPos){ + // need to split it + uint64 nTmpStartPos = m_aaRecords[nPart][i].m_nStartPos; + uint64 nTmpEndPos = nRelStartPos - 1; + m_aaRecords[nPart][i].m_nStartPos = nRelStartPos; + m_aaRecords[nPart].Add(CCBBRecord(nTmpStartPos, nTmpEndPos, m_aaRecords[nPart][i].m_dwIP, m_aaRecords[nPart][i].m_BBRStatus)); + nDbgVerifiedBytes += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + m_aaRecords[nPart][i].m_BBRStatus = BBR_CORRUPTED; + } + } + } + AddDebugLogLine(DLP_HIGH, false, _T("Found and marked %I64u recorded bytes of %I64u as corrupted in the CorruptionBlackBox records"), nDbgVerifiedBytes, (nEndPos-nStartPos)+1); +} + +void CCorruptionBlackBox::EvaluateData(uint16 nPart) +{ + CArray aGuiltyClients; + for (int i= 0; i < m_aaRecords[nPart].GetCount(); i++) + if (m_aaRecords[nPart][i].m_BBRStatus == BBR_CORRUPTED) + aGuiltyClients.Add(m_aaRecords[nPart][i].m_dwIP); + + // check if any IPs are already banned, so we can skip the test for those + for(int k = 0; k < aGuiltyClients.GetCount();){ + // remove doubles + for(int y = k+1; y < aGuiltyClients.GetCount();){ + if (aGuiltyClients[k] == aGuiltyClients[y]) + aGuiltyClients.RemoveAt(y); + else + y++; + } + if (theApp.clientlist->IsBannedClient(aGuiltyClients[k])){ + AddDebugLogLine(DLP_DEFAULT, false, _T("CorruptionBlackBox: Suspicous IP (%s) is already banned, skipping recheck"), ipstr(aGuiltyClients[k])); + aGuiltyClients.RemoveAt(k); + } + else + k++; + } + if (aGuiltyClients.GetCount() > 0){ + // parse all recorded data for this file to produce a statistic for the involved clients + + // first init arrays for the statistic + CArray aDataCorrupt; + CArray aDataVerified; + aDataCorrupt.SetSize(aGuiltyClients.GetCount()); + aDataVerified.SetSize(aGuiltyClients.GetCount()); + for (int j = 0; j < aGuiltyClients.GetCount(); j++) + aDataCorrupt[j] = aDataVerified[j] = 0; + + // now the parsing + for (int nPart = 0; nPart < m_aaRecords.GetCount(); nPart++){ + for (int i = 0; i < m_aaRecords[nPart].GetCount(); i++){ + for(int k = 0; k < aGuiltyClients.GetCount(); k++){ + if (m_aaRecords[nPart][i].m_dwIP == aGuiltyClients[k]){ + if (m_aaRecords[nPart][i].m_BBRStatus == BBR_CORRUPTED){ + // corrupted data records are always counted as at least blocksize or bigger + aDataCorrupt[k] += max((m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1, EMBLOCKSIZE); + } + else if(m_aaRecords[nPart][i].m_BBRStatus == BBR_VERIFIED){ + aDataVerified[k] += (m_aaRecords[nPart][i].m_nEndPos-m_aaRecords[nPart][i].m_nStartPos)+1; + } + } + } + } + } + for(int k = 0; k < aGuiltyClients.GetCount(); k++){ + // calculate the percentage of corrupted data for each client and ban + // him if the limit is reached + int nCorruptPercentage; + if ((aDataVerified[k] + aDataCorrupt[k]) > 0) + nCorruptPercentage = (int)(((uint64)aDataCorrupt[k]*100)/(aDataVerified[k] + aDataCorrupt[k])); + else { + AddDebugLogLine(DLP_HIGH, false, _T("CorruptionBlackBox: Programm Error: No records for guilty client found!")); + ASSERT( false ); + nCorruptPercentage = 0; + } + if ( nCorruptPercentage > CBB_BANTHRESHOLD){ + + CUpDownClient* pEvilClient = theApp.clientlist->FindClientByIP(aGuiltyClients[k]); + if (pEvilClient != NULL){ + AddDebugLogLine(DLP_HIGH, false, _T("CorruptionBlackBox: Banning: Found client which send %s of %s corrupted data, %s"), CastItoXBytes(aDataCorrupt[k]), CastItoXBytes((aDataVerified[k] + aDataCorrupt[k])), pEvilClient->DbgGetClientInfo()); + theApp.clientlist->AddTrackClient(pEvilClient); + pEvilClient->Ban(_T("Identified as sender of corrupt data")); + } + else{ + AddDebugLogLine(DLP_HIGH, false, _T("CorruptionBlackBox: Banning: Found client which send %s of %s corrupted data, %s"), CastItoXBytes(aDataCorrupt[k]), CastItoXBytes((aDataVerified[k] + aDataCorrupt[k])), ipstr(aGuiltyClients[k])); + theApp.clientlist->AddBannedClient(aGuiltyClients[k]); + } + } + else{ + CUpDownClient* pSuspectClient = theApp.clientlist->FindClientByIP(aGuiltyClients[k]); + if (pSuspectClient != NULL){ + AddDebugLogLine(DLP_DEFAULT, false, _T("CorruptionBlackBox: Reporting: Found client which probably send %s of %s corrupted data, but it is within the acceptable limit, %s"), CastItoXBytes(aDataCorrupt[k]), CastItoXBytes((aDataVerified[k] + aDataCorrupt[k])), pSuspectClient->DbgGetClientInfo()); + theApp.clientlist->AddTrackClient(pSuspectClient); + } + else + AddDebugLogLine(DLP_DEFAULT, false, _T("CorruptionBlackBox: Reporting: Found client which probably send %s of %s corrupted data, but it is within the acceptable limit, %s"), CastItoXBytes(aDataCorrupt[k]), CastItoXBytes((aDataVerified[k] + aDataCorrupt[k])), ipstr(aGuiltyClients[k])); + } + } + } +} diff --git a/CorruptionBlackBox.h b/CorruptionBlackBox.h new file mode 100644 index 00000000..984cc8b7 --- /dev/null +++ b/CorruptionBlackBox.h @@ -0,0 +1,64 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once + +class CUpDownClient; + + +enum EBBRStatus{ + BBR_NONE = 0, + BBR_VERIFIED, + BBR_CORRUPTED +}; + + +class CCBBRecord +{ +public: + CCBBRecord(uint64 nStartPos = 0, uint64 nEndPos = 0, uint32 dwIP = 0, EBBRStatus BBRStatus = BBR_NONE); + CCBBRecord(const CCBBRecord& cv) { *this = cv; } + CCBBRecord& operator=(const CCBBRecord& cv); + + bool Merge(uint64 nStartPos, uint64 nEndPos, uint32 dwIP, EBBRStatus BBRStatus = BBR_NONE); + bool CanMerge(uint64 nStartPos, uint64 nEndPos, uint32 dwIP, EBBRStatus BBRStatus = BBR_NONE); + + uint64 m_nStartPos; + uint64 m_nEndPos; + uint32 m_dwIP; + EBBRStatus m_BBRStatus; +}; + +typedef CArray CRecordArray; + + +class CCorruptionBlackBox +{ +public: + CCorruptionBlackBox() {} + ~CCorruptionBlackBox() {} + void Init(EMFileSize nFileSize); + void Free(); + void TransferredData(uint64 nStartPos, uint64 nEndPos, const CUpDownClient* pSender); + void VerifiedData(uint64 nStartPos, uint64 nEndPos); + void CorruptedData(uint64 nStartPos, uint64 nEndPos); + void EvaluateData(uint16 nPart); + + +private: + CArray m_aaRecords; +}; diff --git a/CreditsDlg.cpp b/CreditsDlg.cpp new file mode 100644 index 00000000..9edf6841 --- /dev/null +++ b/CreditsDlg.cpp @@ -0,0 +1,177 @@ +/* + You may NOT modify this copyright message. You may add your name, if you + changed or improved this code, but you mot not delete any part of this message or + make it invisible etc. +*/ +#include "stdafx.h" +#include "emule.h" +#include "CreditsDlg.h" +#include "CreditsThread.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// drawable area of the dialog +#define SCREEN_LEFT 6 +#define SCREEN_TOP 175 +#define SCREEN_RIGHT 345 +#define SCREEN_BOTTOM 296 + +// button to dismiss dialog +#define BUTTON_TOP_Y 0 +#define BUTTON_BOTTOM_Y 300 +#define BUTTON_LEFT_X 0 +#define BUTTON_RIGHT_X 350 + +///////////////////////////////////////////////////////////////////////////// +// CCreditsDlg dialog + + +CCreditsDlg::CCreditsDlg(CWnd* pParent /*=NULL*/) + : CDialog(CCreditsDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CCreditsDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT + + m_pDC = NULL; +} +CCreditsDlg::~CCreditsDlg(){ + m_imgSplash.DeleteObject(); +} + +void CCreditsDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CCreditsDlg) + // NOTE: the ClassWizard will add DDX and DDV calls here + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CCreditsDlg, CDialog) + ON_WM_LBUTTONDOWN() + ON_WM_DESTROY() + ON_WM_CREATE() + ON_WM_PAINT() +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CCreditsDlg message handlers + +void CCreditsDlg::OnLButtonDown(UINT nFlags, CPoint point) +{ + CDialog::OnLButtonDown(nFlags, point); + + // see if they clicked on our button to dismiss the dialog + if((point.x >= BUTTON_LEFT_X) && (point.x <= BUTTON_RIGHT_X)) + { + if((point.y >= BUTTON_TOP_Y) && (point.y <= BUTTON_BOTTOM_Y)) + { + CDialog::OnOK(); + return; + } + } + + PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x, point.y)); +} + +BOOL CCreditsDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + VERIFY( m_imgSplash.Attach(theApp.LoadImage(_T("ABOUT"), _T("JPG"))) ); + m_rectScreen.SetRect(SCREEN_LEFT, SCREEN_TOP, SCREEN_RIGHT, SCREEN_BOTTOM); + StartThread(); + + return TRUE; +} + +void CCreditsDlg::OnDestroy() +{ + KillThread(); + + delete m_pDC; + m_pDC = NULL; + + CDialog::OnDestroy(); +} + +void CCreditsDlg::StartThread() +{ + m_pThread = new CCreditsThread(this, m_pDC->GetSafeHdc(), m_rectScreen); + + if (m_pThread == NULL) + return; + + ASSERT_VALID(m_pThread); + m_pThread->m_pThreadParams = NULL; + + // Create Thread in a suspended state so we can set the Priority + // before it starts getting away from us + if (!m_pThread->CreateThread(CREATE_SUSPENDED)) + { + delete m_pThread; + m_pThread = NULL; + return; + } + + // thread priority has been set at idle priority to keep from bogging + // down other apps that may also be running. + VERIFY(m_pThread->SetThreadPriority(THREAD_PRIORITY_IDLE)); + // Now the thread can run wild + m_pThread->ResumeThread(); +} + +void CCreditsDlg::KillThread() +{ + // tell thread to shutdown + VERIFY(SetEvent(m_pThread->m_hEventKill)); + + // wait for thread to finish shutdown + VERIFY(WaitForSingleObject(m_pThread->m_hThread, INFINITE) == WAIT_OBJECT_0); + + delete m_pThread; + m_pThread = NULL; +} + +int CCreditsDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CDialog::OnCreate(lpCreateStruct) == -1) + return -1; + + // m_pDC must be initialized here instead of the constructor + // because the HWND isn't created until Create is called. + m_pDC = new CClientDC(this); + + return 0; +} + +void CCreditsDlg::OnPaint() +{ + CPaintDC dc(this); // device context for painting + + if (m_imgSplash.GetSafeHandle()) + { + CDC dcMem; + + if (dcMem.CreateCompatibleDC(&dc)) + { + CBitmap* pOldBM = dcMem.SelectObject(&m_imgSplash); + BITMAP BM; + m_imgSplash.GetBitmap(&BM); + + WINDOWPLACEMENT wp; + this->GetWindowPlacement(&wp); + wp.rcNormalPosition.right= wp.rcNormalPosition.left+BM.bmWidth; + wp.rcNormalPosition.bottom= wp.rcNormalPosition.top+BM.bmHeight; + this->SetWindowPlacement(&wp); + + dc.BitBlt(0, 0, BM.bmWidth, BM.bmHeight, &dcMem, 0, 0, SRCCOPY); + dcMem.SelectObject(pOldBM); + } + } +} \ No newline at end of file diff --git a/CreditsDlg.h b/CreditsDlg.h new file mode 100644 index 00000000..4428150f --- /dev/null +++ b/CreditsDlg.h @@ -0,0 +1,51 @@ +#pragma once +#include "GDIThread.h" +#include "resource.h" +#include "enbitmap.h" + +///////////////////////////////////////////////////////////////////////////// +// CCreditsDlg dialog + +class CCreditsDlg : public CDialog +{ +// Construction +public: + void KillThread(); + void StartThread(); + CCreditsDlg(CWnd* pParent = NULL); // standard constructor + CCreditsDlg::~CCreditsDlg(); + + CClientDC* m_pDC; + CRect m_rectScreen; + + CGDIThread* m_pThread; + +// Dialog Data + //{{AFX_DATA(CCreditsDlg) + enum { IDD = IDD_ABOUTBOX }; + // NOTE: the ClassWizard will add data members here + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CCreditsDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CCreditsDlg) + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + virtual BOOL OnInitDialog(); + virtual void OnPaint(); + afx_msg void OnDestroy(); + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +private: + CBitmap m_imgSplash; +}; \ No newline at end of file diff --git a/CreditsThread.cpp b/CreditsThread.cpp new file mode 100644 index 00000000..72eac27f --- /dev/null +++ b/CreditsThread.cpp @@ -0,0 +1,644 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "CreditsThread.h" +#include "OtherFunctions.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// define mask color +#define MASK_RGB (COLORREF)0xFFFFFF + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +IMPLEMENT_DYNAMIC(CCreditsThread, CGDIThread) + +BEGIN_MESSAGE_MAP(CCreditsThread, CGDIThread) + //{{AFX_MSG_MAP(CCreditsThread) + // NOTE - the ClassWizard will add and remove mapping macros here. + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +CCreditsThread::CCreditsThread(CWnd* pWnd, HDC hDC, CRect rectScreen) + : CGDIThread(pWnd,hDC) +{ + m_rectScreen = rectScreen; + m_rgnScreen.CreateRectRgnIndirect(m_rectScreen); + m_nScrollPos = 0; + m_pbmpOldBk = NULL; + m_pbmpOldCredits = NULL; + m_pbmpOldScreen = NULL; + m_pbmpOldMask = NULL; + m_nCreditsBmpWidth = 0; + m_nCreditsBmpHeight = 0; +} + +CCreditsThread::~CCreditsThread() +{ +} + +BOOL CCreditsThread::InitInstance() +{ + InitThreadLocale(); + BOOL bResult = CGDIThread::InitInstance(); + + // NOTE: Because this is a separate thread, we have to delete our GDI objects here (while + // the handle maps are still available.) + if(m_dcBk.m_hDC != NULL && m_pbmpOldBk != NULL) + { + m_dcBk.SelectObject(m_pbmpOldBk); + m_pbmpOldBk = NULL; + m_bmpBk.DeleteObject(); + } + + if(m_dcScreen.m_hDC != NULL && m_pbmpOldScreen != NULL) + { + m_dcScreen.SelectObject(m_pbmpOldScreen); + m_pbmpOldScreen = NULL; + m_bmpScreen.DeleteObject(); + } + + if(m_dcCredits.m_hDC != NULL && m_pbmpOldCredits != NULL) + { + m_dcCredits.SelectObject(m_pbmpOldCredits); + m_pbmpOldCredits = NULL; + m_bmpCredits.DeleteObject(); + } + + if(m_dcMask.m_hDC != NULL && m_pbmpOldMask != NULL) + { + m_dcMask.SelectObject(m_pbmpOldMask); + m_pbmpOldMask = NULL; + m_bmpMask.DeleteObject(); + } + + // clean up the fonts we created + for(int n = 0; n < m_arFonts.GetSize(); n++) + { + m_arFonts.GetAt(n)->DeleteObject(); + delete m_arFonts.GetAt(n); + } + m_arFonts.RemoveAll(); + + return bResult; +} + +// wait for vertical retrace +// makes scrolling smoother, especially at fast speeds +// NT does not like this at all +void waitvrt(void) +{ + __asm { + mov dx,3dah + VRT: + in al,dx + test al,8 + jnz VRT + NoVRT: + in al,dx + test al,8 + jz NoVRT + } +} + +void CCreditsThread::SingleStep() +{ + // if this is our first time, initialize the credits + if(m_dcCredits.m_hDC == NULL) + { + CreateCredits(); + } + + // track scroll position + static int nScrollY = 0; + + // timer variables + LARGE_INTEGER nFrequency; + LARGE_INTEGER nStart; + LARGE_INTEGER nEnd; + int nTimeInMilliseconds; + BOOL bTimerValid; + + nStart.QuadPart = 0; + + if(!QueryPerformanceFrequency(&nFrequency)) + { + bTimerValid = FALSE; + } + else + { + bTimerValid = TRUE; + + // get start time + QueryPerformanceCounter(&nStart); + } + + CGDIThread::m_csGDILock.Lock(); + { + PaintBk(&m_dcScreen); + + m_dcScreen.BitBlt(0, 0, m_nCreditsBmpWidth, m_nCreditsBmpHeight, &m_dcCredits, 0, nScrollY, SRCINVERT); + m_dcScreen.BitBlt(0, 0, m_nCreditsBmpWidth, m_nCreditsBmpHeight, &m_dcMask, 0, nScrollY, SRCAND); + m_dcScreen.BitBlt(0, 0, m_nCreditsBmpWidth, m_nCreditsBmpHeight, &m_dcCredits, 0, nScrollY, SRCINVERT); + + // wait for vertical retrace + if(m_bWaitVRT) waitvrt(); + + m_dc.BitBlt(m_rectScreen.left, m_rectScreen.top, m_rectScreen.Width(), m_rectScreen.Height(), &m_dcScreen, 0, 0, SRCCOPY); + + GdiFlush(); + } + CGDIThread::m_csGDILock.Unlock(); + + // continue scrolling + nScrollY += m_nScrollInc; + if(nScrollY >= m_nCreditsBmpHeight) nScrollY = 0; // scrolling up + if(nScrollY < 0) nScrollY = m_nCreditsBmpHeight; // scrolling down + + // delay scrolling by the specified time + if(bTimerValid) + { + QueryPerformanceCounter(&nEnd); + nTimeInMilliseconds = (int)((nEnd.QuadPart - nStart.QuadPart) * 1000 / nFrequency.QuadPart); + + if(nTimeInMilliseconds < m_nDelay) + { + Sleep(m_nDelay - nTimeInMilliseconds); + } + } + else + { + Sleep(m_nDelay); + } +} + +void CCreditsThread::PaintBk(CDC* pDC) +{ + //save background the first time + if (m_dcBk.m_hDC == NULL) + { + m_dcBk.CreateCompatibleDC(&m_dc); + m_bmpBk.CreateCompatibleBitmap(&m_dc, m_rectScreen.Width(), m_rectScreen.Height()); + m_pbmpOldBk = m_dcBk.SelectObject(&m_bmpBk); + m_dcBk.BitBlt(0, 0, m_rectScreen.Width(), m_rectScreen.Height(), &m_dc, m_rectScreen.left, m_rectScreen.top, SRCCOPY); + } + + pDC->BitBlt(0, 0, m_rectScreen.Width(), m_rectScreen.Height(), &m_dcBk, 0, 0, SRCCOPY); +} + +void CCreditsThread::CreateCredits() +{ + InitFonts(); + InitColors(); + InitText(); + + m_dc.SelectClipRgn(&m_rgnScreen); + + m_dcScreen.CreateCompatibleDC(&m_dc); + m_bmpScreen.CreateCompatibleBitmap(&m_dc, m_rectScreen.Width(), m_rectScreen.Height()); + m_pbmpOldScreen = m_dcScreen.SelectObject(&m_bmpScreen); + + m_nCreditsBmpWidth = m_rectScreen.Width(); + m_nCreditsBmpHeight = CalcCreditsHeight(); + + m_dcCredits.CreateCompatibleDC(&m_dc); + m_bmpCredits.CreateCompatibleBitmap(&m_dc, m_nCreditsBmpWidth, m_nCreditsBmpHeight); + m_pbmpOldCredits = m_dcCredits.SelectObject(&m_bmpCredits); + + m_dcCredits.FillSolidRect(0, 0, m_nCreditsBmpWidth, m_nCreditsBmpHeight, MASK_RGB); + + CFont* pOldFont; + + pOldFont = m_dcCredits.SelectObject(m_arFonts.GetAt(0)); + + m_dcCredits.SetBkMode(TRANSPARENT); + + int y = 0; + + int nFont; + int nColor; + + int nLastFont = -1; + int nLastColor = -1; + + int nTextHeight = m_dcCredits.GetTextExtent(_T("Wy")).cy; + + for(int n = 0; n < m_arCredits.GetSize(); n++) + { + CString sType = m_arCredits.GetAt(n).Left(1); + + if(sType == 'B') + { + // it's a bitmap + + CBitmap bmp; + if(! bmp.LoadBitmap(m_arCredits.GetAt(n).Mid(2))) + { + CString str; + str.Format(_T("Could not find bitmap resource \"%s\". Be sure to assign the bitmap a QUOTED resource name"), m_arCredits.GetAt(n).Mid(2)); + AfxMessageBox(str); + return; + } + + BITMAP bmInfo; + bmp.GetBitmap(&bmInfo); + + CDC dc; + dc.CreateCompatibleDC(&m_dcCredits); + CBitmap* pOldBmp = dc.SelectObject(&bmp); + + // draw the bitmap + m_dcCredits.BitBlt((m_rectScreen.Width() - bmInfo.bmWidth) / 2, y, bmInfo.bmWidth, bmInfo.bmHeight, &dc, 0, 0, SRCCOPY); + + dc.SelectObject(pOldBmp); + bmp.DeleteObject(); + + y += bmInfo.bmHeight; + } + else if(sType == 'S') + { + // it's a vertical space + + y += _ttoi(m_arCredits.GetAt(n).Mid(2)); + } + else + { + // it's a text string + + nFont = _ttoi(m_arCredits.GetAt(n).Left(2)); + nColor = _ttoi(m_arCredits.GetAt(n).Mid(3,2)); + + if(nFont != nLastFont) + { + m_dcCredits.SelectObject(m_arFonts.GetAt(nFont)); + nTextHeight = m_arFontHeights.GetAt(nFont); + } + + if(nColor != nLastColor) + { + m_dcCredits.SetTextColor(m_arColors.GetAt(nColor)); + } + + CRect rect(0, y, m_rectScreen.Width(), y + nTextHeight); + + m_dcCredits.DrawText(m_arCredits.GetAt(n).Mid(6), &rect, DT_CENTER); + + y += nTextHeight; + } + } + + m_dcCredits.SetBkColor(MASK_RGB); + m_dcCredits.SelectObject(pOldFont); + + // create the mask bitmap + m_dcMask.CreateCompatibleDC(&m_dcScreen); + m_bmpMask.CreateBitmap(m_nCreditsBmpWidth, m_nCreditsBmpHeight, 1, 1, NULL); + + // select the mask bitmap into the appropriate dc + m_pbmpOldMask = m_dcMask.SelectObject(&m_bmpMask); + + // build mask based on transparent color + m_dcMask.BitBlt(0, 0, m_nCreditsBmpWidth, m_nCreditsBmpHeight, &m_dcCredits, 0, 0, SRCCOPY); +} + +void CCreditsThread::InitFonts() +{ + // create each font we'll need and add it to the fonts array + + CDC dcMem; + dcMem.CreateCompatibleDC(&m_dc); + CFont* pOldFont; + int nTextHeight; + + LOGFONT lf; + + // font 0 + // SMALL ARIAL + CFont* font0 = new CFont; + memset((void*)&lf, 0, sizeof(lf)); + lf.lfHeight = 12; + lf.lfWeight = 500; + lf.lfQuality = NONANTIALIASED_QUALITY; + _tcscpy(lf.lfFaceName, _T("Arial")); + font0->CreateFontIndirect(&lf); + m_arFonts.Add(font0); + + pOldFont = dcMem.SelectObject(font0); + nTextHeight = dcMem.GetTextExtent(_T("Wy")).cy; + m_arFontHeights.Add(nTextHeight); + + // font 1 + // MEDIUM BOLD ARIAL + CFont* font1 = new CFont; + memset((void*)&lf, 0, sizeof(lf)); + lf.lfHeight = 14; + lf.lfWeight = 600; + lf.lfQuality = NONANTIALIASED_QUALITY; + _tcscpy(lf.lfFaceName, _T("Arial")); + font1->CreateFontIndirect(&lf); + m_arFonts.Add(font1); + + dcMem.SelectObject(font1); + nTextHeight = dcMem.GetTextExtent(_T("Wy")).cy; + m_arFontHeights.Add(nTextHeight); + + // font 2 + // LARGE ITALIC HEAVY BOLD TIMES ROMAN + CFont* font2 = new CFont; + memset((void*)&lf, 0, sizeof(lf)); + lf.lfHeight = 16; + lf.lfWeight = 700; + //lf.lfItalic = TRUE; + lf.lfQuality = afxIsWin95() ? NONANTIALIASED_QUALITY : ANTIALIASED_QUALITY; + _tcscpy(lf.lfFaceName, _T("Arial")); + font2->CreateFontIndirect(&lf); + m_arFonts.Add(font2); + + dcMem.SelectObject(font2); + nTextHeight = dcMem.GetTextExtent(_T("Wy")).cy; + m_arFontHeights.Add(nTextHeight); + + // font 3 + CFont* font3 = new CFont; + memset((void*)&lf, 0, sizeof(lf)); + lf.lfHeight = 25; + lf.lfWeight = 900; + lf.lfQuality = afxIsWin95() ? NONANTIALIASED_QUALITY : ANTIALIASED_QUALITY; + _tcscpy(lf.lfFaceName, _T("Arial")); + font3->CreateFontIndirect(&lf); + m_arFonts.Add(font3); + + dcMem.SelectObject(font3); + nTextHeight = dcMem.GetTextExtent(_T("Wy")).cy; + m_arFontHeights.Add(nTextHeight); + + dcMem.SelectObject(pOldFont); +} + +void CCreditsThread::InitColors() +{ + // define each color we'll be using + + m_arColors.Add(PALETTERGB(0, 0, 0)); // 0 = BLACK + m_arColors.Add(PALETTERGB(90, 90, 90)); // 1 = very dark gray + m_arColors.Add(PALETTERGB(128, 128, 128)); // 2 = DARK GRAY + m_arColors.Add(PALETTERGB(192, 192, 192)); // 3 = LIGHT GRAY + m_arColors.Add(PALETTERGB(200, 50, 50)); // 4 = very light gray + m_arColors.Add(PALETTERGB(255, 255, 128)); // 5 white + m_arColors.Add(PALETTERGB(0, 0, 128)); // 6 dark blue + m_arColors.Add(PALETTERGB(128, 128, 255)); // 7 light blue + m_arColors.Add(PALETTERGB(0, 106, 0)); // 8 dark green +} + +void CCreditsThread::InitText() +{ + // 1st pair of digits identifies the font to use + // 2nd pair of digits identifies the color to use + // B = Bitmap + // S = Space (moves down the specified number of pixels) + + CString sTmp; + + /* + You may NOT modify this copyright message. You may add your name, if you + changed or improved this code, but you mot not delete any part of this message, + make it invisible etc. + */ + + // start at the bottom of the screen + sTmp.Format(_T("S:%d"), m_rectScreen.Height()); + m_arCredits.Add(sTmp); + + m_arCredits.Add(_T("03:00:eMule")); + sTmp.Format(_T("02:01:Version %s"),theApp.m_strCurVersionLong); + m_arCredits.Add(sTmp); + m_arCredits.Add(_T("01:06:Copyright (C) 2002-2015 Merkur")); + m_arCredits.Add(_T("S:50")); + m_arCredits.Add(_T("02:04:Developers")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Ornis")); + + m_arCredits.Add(_T("S:50")); + + m_arCredits.Add(_T("02:04:Tester")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Monk")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Daan")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Elandal")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Frozen_North")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:kayfam")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Khandurian")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Masta2002")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:mrLabr")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Nesi-San")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:SeveredCross")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Skynetman")); + + + m_arCredits.Add(_T("S:50")); + m_arCredits.Add(_T("02:04:Retired Members")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Merkur (the Founder)")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:tecxx")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Pach2")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Juanjo")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Barry")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Dirus")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Unknown1")); + + + m_arCredits.Add(_T("S:50")); + m_arCredits.Add(_T("02:04:Thanks to these programmers")); + m_arCredits.Add(_T("02:04:for publishing useful codeparts")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Paolo Messina (ResizableDialog class)")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:6:PJ Naughter (HttpDownload Dialog)")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Jim Connor (Scrolling Credits)")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Yury Goltsman (extended Progressbar)")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Magomed G. Abdurakhmanov (Hyperlink ctrl)")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Arthur Westerman (Titled menu)")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Tim Kosse (AsyncSocket-Proxysupport)")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:Keith Rule (Memory DC)")); + m_arCredits.Add(_T("S:50")); + + m_arCredits.Add(_T("02:07:And thanks to the following")); + m_arCredits.Add(_T("02:07:people for translating eMule")); + m_arCredits.Add(_T("02:07:into different languages:")); + m_arCredits.Add(_T("S:20")); + + + m_arCredits.Add(_T("01:06:Arabic: Dody")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Albanian: Besmir")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Basque: TXiKi")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Breton: KAD-Korvigelloù an Drouizig")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Bulgarian: DapKo, Dumper")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Catalan: LeChuck")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Chinese simplyfied: Tim Chen, Qilu T.")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Chinese Traditional: CML, Donlong, Ryan")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Czech: Patejl")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Danish: Tiede, Cirrus, Itchy")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Estonian: Symbio")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Dutch: Mr.Bean")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Finnish: Nikerabbit")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:French: Motte, Emzc, Lalrobin")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Galician: Juan, Emilio R.")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Greek: Michael Papadakis")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Italian: Trevi, FrankyFive")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Japanese: DukeDog, Shinro T.")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Hebrew: Avi-3k")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Hungarian: r0ll3r")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Korean: pooz")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Latvian: Zivs")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Lithuanian: Daan")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Maltese: Reuben")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Norwegian (Bokmal): Iznogood")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Norwegian (Nynorsk): Hallvor")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Polish: Tomasz \"TMouse\" Broniarek")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Portugese: Filipe, Luís Claro")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Portugese Brasilian: DarthMaul,Brasco,Ducho")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Romanian: Dragos")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Russian: T-Mac, BRMAIL")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Slowenian: Rok Kralj")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Spanish Castellano: Azuredraco, Javier L., |_Hell_|")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Swedish: Andre")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Turkish: Burak Y.")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Ukrainian: Kex")); + m_arCredits.Add(_T("S:05")); + m_arCredits.Add(_T("01:06:Vietnamese: Paul Tran HQ Loc")); + + m_arCredits.Add(_T("S:50")); + m_arCredits.Add(_T("02:04:Part of eMule is based on Kademlia:")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("02:04:Peer-to-peer routing based on the XOR metric.")); + m_arCredits.Add(_T("S:10")); + m_arCredits.Add(_T("01:06:Copyright (C) 2002 Petar Maymounkov")); + m_arCredits.Add(_T("S:5")); + m_arCredits.Add(_T("01:06:http://kademlia.scs.cs.nyu.edu")); + + // pause before repeating + m_arCredits.Add(_T("S:100")); +} + +int CCreditsThread::CalcCreditsHeight() +{ + int nHeight = 0; + + for(int n = 0; n < m_arCredits.GetSize(); n++) + { + CString sType = m_arCredits.GetAt(n).Left(1); + + if(sType == 'B') + { + // it's a bitmap + + CBitmap bmp; + if (! bmp.LoadBitmap(m_arCredits.GetAt(n).Mid(2))) + { + CString str; + str.Format(_T("Could not find bitmap resource \"%s\". Be sure to assign the bitmap a QUOTED resource name"), m_arCredits.GetAt(n).Mid(2)); + AfxMessageBox(str); + return -1; + } + + BITMAP bmInfo; + bmp.GetBitmap(&bmInfo); + + nHeight += bmInfo.bmHeight; + } + else if(sType == 'S') + { + // it's a vertical space + + nHeight += _ttoi(m_arCredits.GetAt(n).Mid(2)); + } + else + { + // it's a text string + + int nFont = _ttoi(m_arCredits.GetAt(n).Left(2)); + nHeight += m_arFontHeights.GetAt(nFont); + } + } + + return nHeight; +} diff --git a/CreditsThread.h b/CreditsThread.h new file mode 100644 index 00000000..5648325d --- /dev/null +++ b/CreditsThread.h @@ -0,0 +1,71 @@ +#pragma once +#include "GDIThread.h" + +class CCreditsThread : public CGDIThread +{ +public: + DECLARE_DYNAMIC(CCreditsThread) + CCreditsThread(CWnd* pWnd, HDC hDC, CRect rectScreen); + +// Attributes +public: + CRect m_rectScreen; + CRgn m_rgnScreen; + + int m_nScrollPos; + + // background bitmap + CDC m_dcBk; + CBitmap m_bmpBk; + CBitmap* m_pbmpOldBk; + + // credits bitmap + CDC m_dcCredits; + CBitmap m_bmpCredits; + CBitmap* m_pbmpOldCredits; + + // screen bitmap + CDC m_dcScreen; + CBitmap m_bmpScreen; + CBitmap* m_pbmpOldScreen; + + // mask bitmap + CDC m_dcMask; + CBitmap m_bmpMask; + CBitmap* m_pbmpOldMask; + + int m_nCreditsBmpWidth; + int m_nCreditsBmpHeight; + + CArray m_arCredits; + CArray m_arColors; + CArray m_arFonts; + CArray m_arFontHeights; + +// Operations +public: + int CalcCreditsHeight(); + void InitText(); + void InitColors(); + void InitFonts(); + void CreateCredits(); + virtual BOOL InitInstance(); + virtual void SingleStep(); + void PaintBk(CDC* pDC); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CCreditsThread) + //}}AFX_VIRTUAL + +// Implementation +protected: + virtual ~CCreditsThread(); + + // Generated message map functions + //{{AFX_MSG(CCreditsThread) + // NOTE - the ClassWizard will add and remove member functions here. + //}}AFX_MSG + + DECLARE_MESSAGE_MAP() +}; diff --git a/CustomAutoComplete.cpp b/CustomAutoComplete.cpp new file mode 100644 index 00000000..b8771235 --- /dev/null +++ b/CustomAutoComplete.cpp @@ -0,0 +1,373 @@ +//-------------------------------------------------------------------------------------------- +// Name: CCustomAutoComplete (CCUSTOMAUTOCOMPLETE.H) +// Type: Wrapper class +// Description: Matches IAutoComplete, IEnumString and the registry (optional) to provide +// custom auto-complete functionality for EDIT controls - including those in +// combo boxes - in WTL projects. +// +// Author: Klaus H. Probst [kprobst@vbbox.com] +// URL: http://www.vbbox.com/ +// Copyright: This work is copyright © 2002, Klaus H. Probst +// Usage: You may use this code as you see fit, provided that you assume all +// responsibilities for doing so. +// Distribution: Distribute freely as long as you maintain this notice as part of the +// file header. +// +// +// Updates: 09-Mai-2003 [bluecow]: +// - changed original string list code to deal with a LRU list +// and auto cleanup of list entries according 'iMaxItemCount'. +// - splitted original code into cpp/h file +// - removed registry stuff +// - added file stuff +// 15-Jan-2004 [Ornis]: +// - changed adding strings to replace existing ones on a new position +// +// +// Notes: +// +// +// Dependencies: +// +// The usual ATL/WTL headers for a normal EXE, plus +// +//-------------------------------------------------------------------------------------------- +#include "stdafx.h" +#include +#include "CustomAutoComplete.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +CCustomAutoComplete::CCustomAutoComplete() +{ + InternalInit(); +} + +CCustomAutoComplete::CCustomAutoComplete(const CStringArray& p_sItemList) +{ + InternalInit(); + SetList(p_sItemList); +} + +CCustomAutoComplete::~CCustomAutoComplete() +{ + if (m_pac) + m_pac.Release(); +} + +BOOL CCustomAutoComplete::Bind(HWND p_hWndEdit, DWORD p_dwOptions, LPCTSTR p_lpszFormatString) +{ + ATLASSERT(::IsWindow(p_hWndEdit)); + if ((m_fBound) || (m_pac)) + return FALSE; + + HRESULT hr = m_pac.CoCreateInstance(CLSID_AutoComplete); + if (SUCCEEDED(hr)) + { + if (p_dwOptions){ + CComQIPtr pAC2(m_pac); + if (pAC2){ + pAC2->SetOptions(p_dwOptions); + pAC2.Release(); + } + } + + if (SUCCEEDED(hr = m_pac->Init(p_hWndEdit, this, NULL, p_lpszFormatString))) + { + m_fBound = TRUE; + return TRUE; + } + } + return FALSE; +} + +VOID CCustomAutoComplete::Unbind() +{ + if (!m_fBound) + return; + if (m_pac){ + m_pac.Release(); + m_fBound = FALSE; + } +} + +BOOL CCustomAutoComplete::SetList(const CStringArray& p_sItemList) +{ + ATLASSERT(p_sItemList.GetSize() != 0); + Clear(); + m_asList.Append(p_sItemList); + return TRUE; +} + +int CCustomAutoComplete::FindItem(const CString& rstr) +{ + for (int i = 0; i < m_asList.GetCount(); i++) + if (m_asList[i].Compare(rstr) == 0) + return i; + return -1; +} + +BOOL CCustomAutoComplete::AddItem(const CString& p_sItem, int iPos) +{ + if (p_sItem.GetLength() != 0) + { + int oldpos=FindItem(p_sItem); + if (oldpos == -1) + { + // use a LRU list + if (iPos == -1) + m_asList.Add(p_sItem); + else + m_asList.InsertAt(iPos, p_sItem); + + while (m_asList.GetSize() > m_iMaxItemCount) + m_asList.RemoveAt(m_asList.GetSize() - 1); + return TRUE; + } else if (iPos!=-1) { + m_asList.RemoveAt(oldpos); + if (oldpos m_iMaxItemCount) + m_asList.RemoveAt(m_asList.GetSize() - 1); + return TRUE; + } + } + return FALSE; +} + +int CCustomAutoComplete::GetItemCount() +{ + return (int)m_asList.GetCount(); +} + +BOOL CCustomAutoComplete::RemoveItem(const CString& p_sItem) +{ + if (p_sItem.GetLength() != 0) + { + int iPos = FindItem(p_sItem); + if (iPos != -1) + { + m_asList.RemoveAt(iPos); + return TRUE; + } + } + return FALSE; +} + +BOOL CCustomAutoComplete::RemoveSelectedItem() +{ + if (m_pac == NULL || !IsBound()) + return FALSE; + CComQIPtr pIAutoCompleteDropDown = m_pac; + if (!pIAutoCompleteDropDown) + return FALSE; + + DWORD dwFlags; + LPWSTR pwszItem; + if (FAILED(pIAutoCompleteDropDown->GetDropDownStatus(&dwFlags, &pwszItem))) + return FALSE; + if (dwFlags != ACDD_VISIBLE) + return FALSE; + if (pwszItem == NULL) + return FALSE; + CString strItem(pwszItem); + CoTaskMemFree(pwszItem); + + return RemoveItem(strItem); +} + +BOOL CCustomAutoComplete::Clear() +{ + if (m_asList.GetSize() != 0) + { + m_asList.RemoveAll(); + return TRUE; + } + return FALSE; +} + +BOOL CCustomAutoComplete::Disable() +{ + if ((!m_pac) || (!m_fBound)) + return FALSE; + return SUCCEEDED(EnDisable(FALSE)); +} + +BOOL CCustomAutoComplete::Enable(VOID) +{ + if ((!m_pac) || (m_fBound)) + return FALSE; + return SUCCEEDED(EnDisable(TRUE)); +} + +const CStringArray& CCustomAutoComplete::GetList() const +{ + return m_asList; +} + +// +// IUnknown implementation +// +STDMETHODIMP_(ULONG) CCustomAutoComplete::AddRef() +{ + ULONG nCount = ::InterlockedIncrement(reinterpret_cast(&m_nRefCount)); + return nCount; +} + +STDMETHODIMP_(ULONG) CCustomAutoComplete::Release() +{ + ULONG nCount = 0; + nCount = (ULONG) ::InterlockedDecrement(reinterpret_cast(&m_nRefCount)); + if (nCount == 0) + delete this; + return nCount; +} + +STDMETHODIMP CCustomAutoComplete::QueryInterface(REFIID riid, void** ppvObject) +{ + HRESULT hr = E_NOINTERFACE; + if (ppvObject != NULL) + { + *ppvObject = NULL; + + if (IID_IUnknown == riid) + *ppvObject = static_cast(this); + else if (IID_IEnumString == riid) + *ppvObject = static_cast(this); + if (*ppvObject != NULL) + { + hr = S_OK; + ((LPUNKNOWN)*ppvObject)->AddRef(); + } + } + else + { + hr = E_POINTER; + } + return hr; +} + +// +// IEnumString implementation +// +STDMETHODIMP CCustomAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) +{ + HRESULT hr = S_FALSE; + + if (!celt) + celt = 1; + if (pceltFetched) + *pceltFetched = 0; + ULONG i; + for (i = 0; i < celt; i++) + { + if (m_nCurrentElement == (ULONG)m_asList.GetSize()) + break; + + rgelt[i] = (LPWSTR)::CoTaskMemAlloc((ULONG) sizeof(WCHAR) * (m_asList[m_nCurrentElement].GetLength() + 1)); + wcscpy(rgelt[i], m_asList[m_nCurrentElement]); + + if (pceltFetched) + (*pceltFetched)++; + + m_nCurrentElement++; + } + + if (i == celt) + hr = S_OK; + + return hr; +} + +STDMETHODIMP CCustomAutoComplete::Skip(ULONG celt) +{ + m_nCurrentElement += celt; + if (m_nCurrentElement > (ULONG)m_asList.GetSize()) + m_nCurrentElement = 0; + + return S_OK; +} + +STDMETHODIMP CCustomAutoComplete::Reset(void) +{ + m_nCurrentElement = 0; + return S_OK; +} + +STDMETHODIMP CCustomAutoComplete::Clone(IEnumString** ppenum) +{ + if (!ppenum) + return E_POINTER; + + CCustomAutoComplete* pnew = new CCustomAutoComplete(); + pnew->AddRef(); + *ppenum = pnew; + return S_OK; +} + +void CCustomAutoComplete::InternalInit() +{ + m_nCurrentElement = 0; + m_nRefCount = 0; + m_fBound = FALSE; + m_iMaxItemCount = 30; +} + +HRESULT CCustomAutoComplete::EnDisable(BOOL p_fEnable) +{ + ATLASSERT(m_pac); + + HRESULT hr = m_pac->Enable(p_fEnable); + if (SUCCEEDED(hr)) + m_fBound = p_fEnable; + return hr; +} + +BOOL CCustomAutoComplete::LoadList(LPCTSTR pszFileName) +{ + FILE* fp = _tfsopen(pszFileName, _T("rb"), _SH_DENYWR); + if (fp == NULL) + return FALSE; + + // verify Unicode byte-order mark 0xFEFF + WORD wBOM = fgetwc(fp); + if (wBOM != 0xFEFF){ + fclose(fp); + return FALSE; + } + + TCHAR szItem[256]; + while (_fgetts(szItem, ARRSIZE(szItem), fp) != NULL){ + CString strItem(szItem); + strItem.Trim(_T(" \r\n")); + AddItem(strItem, -1); + } + fclose(fp); + return TRUE; +} + +BOOL CCustomAutoComplete::SaveList(LPCTSTR pszFileName) +{ + FILE* fp = _tfsopen(pszFileName, _T("wb"), _SH_DENYWR); + if (fp == NULL) + return FALSE; + + // write Unicode byte-order mark 0xFEFF + fputwc(0xFEFF, fp); + + for (int i = 0; i < m_asList.GetCount(); i++) + _ftprintf(fp, _T("%s\r\n"), m_asList[i]); + fclose(fp); + return !ferror(fp); +} + +CString CCustomAutoComplete::GetItem(int pos){ + if (pos>=m_asList.GetCount()) return NULL; + else return m_asList.GetAt(pos); +} diff --git a/CustomAutoComplete.h b/CustomAutoComplete.h new file mode 100644 index 00000000..9d7a85c8 --- /dev/null +++ b/CustomAutoComplete.h @@ -0,0 +1,66 @@ +//-------------------------------------------------------------------------------------------- +// Author: Klaus H. Probst [kprobst@vbbox.com] +// +//-------------------------------------------------------------------------------------------- +#pragma once +#include +#include +#include + +class CCustomAutoComplete : + public IEnumString +{ +private: + CStringArray m_asList; + CComPtr m_pac; + + ULONG m_nCurrentElement; + ULONG m_nRefCount; + BOOL m_fBound; + int m_iMaxItemCount; + + // Constructors/destructors +public: + CCustomAutoComplete(); + CCustomAutoComplete(const CStringArray& p_sItemList); + ~CCustomAutoComplete(); + + // Implementation +public: + BOOL Bind(HWND p_hWndEdit, DWORD p_dwOptions = 0, LPCTSTR p_lpszFormatString = NULL); + VOID Unbind(); + BOOL IsBound() const { return m_fBound; } + + BOOL SetList(const CStringArray& p_sItemList); + const CStringArray& GetList() const; + int GetItemCount(); + + BOOL AddItem(const CString& p_sItem, int iPos); + BOOL RemoveItem(const CString& p_sItem); + BOOL RemoveSelectedItem(); + CString GetItem(int pos); + + BOOL Clear(); + BOOL Disable(); + BOOL Enable(VOID); + + BOOL LoadList(LPCTSTR pszFileName); + BOOL SaveList(LPCTSTR pszFileName); + +public: + STDMETHOD_(ULONG,AddRef)(); + STDMETHOD_(ULONG,Release)(); + STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject); + +public: + STDMETHOD(Next)(ULONG celt, LPOLESTR* rgelt, ULONG* pceltFetched); + STDMETHOD(Skip)(ULONG celt); + STDMETHOD(Reset)(void); + STDMETHOD(Clone)(IEnumString** ppenum); + + // Internal implementation +private: + void InternalInit(); + HRESULT EnDisable(BOOL p_fEnable); + int FindItem(const CString& rstr); +}; diff --git a/CxImage/cximage_vc71.sln b/CxImage/cximage_vc71.sln new file mode 100644 index 00000000..2fb5f6cd --- /dev/null +++ b/CxImage/cximage_vc71.sln @@ -0,0 +1,23 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CxImage", "cximage_vc71.vcproj", "{B437B84A-B4B0-4389-BF5B-7E25FD9DCF3D}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {B437B84A-B4B0-4389-BF5B-7E25FD9DCF3D}.Debug.ActiveCfg = Debug|Win32 + {B437B84A-B4B0-4389-BF5B-7E25FD9DCF3D}.Debug.Build.0 = Debug|Win32 + {B437B84A-B4B0-4389-BF5B-7E25FD9DCF3D}.Release.ActiveCfg = Release|Win32 + {B437B84A-B4B0-4389-BF5B-7E25FD9DCF3D}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/CxImage/cximage_vc71.vcproj b/CxImage/cximage_vc71.vcproj new file mode 100644 index 00000000..9db363c1 --- /dev/null +++ b/CxImage/cximage_vc71.vcproj @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CxImage/cximage_vc8.vcproj b/CxImage/cximage_vc8.vcproj new file mode 100644 index 00000000..9dd99256 --- /dev/null +++ b/CxImage/cximage_vc8.vcproj @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CxImage/cximage_vc9.vcproj b/CxImage/cximage_vc9.vcproj new file mode 100644 index 00000000..bc3d3b6b --- /dev/null +++ b/CxImage/cximage_vc9.vcproj @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CxImage/license.txt b/CxImage/license.txt new file mode 100644 index 00000000..8cc0197b --- /dev/null +++ b/CxImage/license.txt @@ -0,0 +1,48 @@ +This copy of the CxImage notices is provided for your convenience. In case of +any discrepancy between this copy and the notices in the file ximage.h that is +included in the CxImage distribution, the latter shall prevail. + +If you modify CxImage you may insert additional notices immediately following +this sentence. + +-------------------------------------------------------------------------------- + +COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: + +CxImage version 6.0.0 02/Feb/2008 + +CxImage : Copyright (C) 2001 - 2008, Davide Pizzolato + +Original CImage and CImageIterator implementation are: +Copyright (C) 1995, Alejandro Aguilar Sierra (asierra(at)servidor(dot)unam(dot)mx) + +Covered code is provided under this license on an "as is" basis, without warranty +of any kind, either expressed or implied, including, without limitation, warranties +that the covered code is free of defects, merchantable, fit for a particular purpose +or non-infringing. The entire risk as to the quality and performance of the covered +code is with you. Should any covered code prove defective in any respect, you (not +the initial developer or any other contributor) assume the cost of any necessary +servicing, repair or correction. This disclaimer of warranty constitutes an essential +part of this license. No use of any covered code is authorized hereunder except under +this disclaimer. + +Permission is hereby granted to use, copy, modify, and distribute this +source code, or portions hereof, for any purpose, including commercial applications, +freely and without fee, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +-------------------------------------------------------------------------------- + +Other information: about CxImage, and the latest version, can be found at the +CxImage home page: http://www.xdp.it + +-------------------------------------------------------------------------------- diff --git a/CxImage/xfile.h b/CxImage/xfile.h new file mode 100644 index 00000000..b64594ee --- /dev/null +++ b/CxImage/xfile.h @@ -0,0 +1,79 @@ +/* + * File: xfile.h + * Purpose: General Purpose File Class + */ +/* + -------------------------------------------------------------------------------- + + COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: + + CxFile (c) 11/May/2002 Davide Pizzolato - www.xdp.it + CxFile version 2.00 23/Aug/2002 + CxFile version 2.10 16/Dec/2007 + + Special thanks to Chris Shearer Cooper for new features, enhancements and bugfixes + + Covered code is provided under this license on an "as is" basis, without warranty + of any kind, either expressed or implied, including, without limitation, warranties + that the covered code is free of defects, merchantable, fit for a particular purpose + or non-infringing. The entire risk as to the quality and performance of the covered + code is with you. Should any covered code prove defective in any respect, you (not + the initial developer or any other contributor) assume the cost of any necessary + servicing, repair or correction. This disclaimer of warranty constitutes an essential + part of this license. No use of any covered code is authorized hereunder except under + this disclaimer. + + Permission is hereby granted to use, copy, modify, and distribute this + source code, or portions hereof, for any purpose, including commercial applications, + freely and without fee, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + -------------------------------------------------------------------------------- + */ +#if !defined(__xfile_h) +#define __xfile_h + +#if defined (WIN32) || defined (_WIN32_WCE) + #include +#endif + +#include +#include + +#include "ximadef.h" + +class DLL_EXP CxFile +{ +public: + CxFile(void) { }; + virtual ~CxFile() { }; + + virtual bool Close() = 0; + virtual size_t Read(void *buffer, size_t size, size_t count) = 0; + virtual size_t Write(const void *buffer, size_t size, size_t count) = 0; + virtual bool Seek(long offset, int origin) = 0; + virtual long Tell() = 0; + virtual long Size() = 0; + virtual bool Flush() = 0; + virtual bool Eof() = 0; + virtual long Error() = 0; + virtual bool PutC(unsigned char c) + { + // Default implementation + size_t nWrote = Write(&c, 1, 1); + return (bool)(nWrote == 1); + } + virtual long GetC() = 0; + virtual char * GetS(char *string, int n) = 0; + virtual long Scanf(const char *format, void* output) = 0; +}; + +#endif //__xfile_h diff --git a/CxImage/ximabmp.cpp b/CxImage/ximabmp.cpp new file mode 100644 index 00000000..a621ba55 --- /dev/null +++ b/CxImage/ximabmp.cpp @@ -0,0 +1,444 @@ +/* + * File: ximabmp.cpp + * Purpose: Platform Independent BMP Image Class Loader and Writer + * 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 6.0.0 02/Feb/2008 + */ + +#include "ximabmp.h" + +#if CXIMAGE_SUPPORT_BMP + +#include "ximaiter.h" + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageBMP::Encode(CxFile * hFile) +{ + + if (EncodeSafeCheck(hFile)) return false; + + BITMAPFILEHEADER hdr; + + hdr.bfType = 0x4d42; // 'BM' WINDOWS_BITMAP_SIGNATURE + hdr.bfSize = GetSize() + 14 /*sizeof(BITMAPFILEHEADER)*/; + hdr.bfReserved1 = hdr.bfReserved2 = 0; + hdr.bfOffBits = 14 /*sizeof(BITMAPFILEHEADER)*/ + head.biSize + GetPaletteSize(); + + hdr.bfType = ntohs(hdr.bfType); + hdr.bfSize = ntohl(hdr.bfSize); + hdr.bfOffBits = ntohl(hdr.bfOffBits); + +#if CXIMAGE_SUPPORT_ALPHA + if (GetNumColors()==0 && AlphaIsValid()){ + + BITMAPINFOHEADER infohdr; + memcpy(&infohdr,&head,sizeof(BITMAPINFOHEADER)); + infohdr.biCompression = BI_RGB; + infohdr.biBitCount = 32; + DWORD dwEffWidth = ((((infohdr.biBitCount * infohdr.biWidth) + 31) / 32) * 4); + infohdr.biSizeImage = dwEffWidth * infohdr.biHeight; + + hdr.bfSize = infohdr.biSize + infohdr.biSizeImage + 14 /*sizeof(BITMAPFILEHEADER)*/; + + hdr.bfSize = ntohl(hdr.bfSize); + bihtoh(&infohdr); + + // Write the file header + hFile->Write(&hdr,min(14,sizeof(BITMAPFILEHEADER)),1); + hFile->Write(&infohdr,sizeof(BITMAPINFOHEADER),1); + //and DIB+ALPHA interlaced + BYTE *srcalpha = AlphaGetPointer(); + for(long y = 0; y < infohdr.biHeight; ++y){ + BYTE *srcdib = GetBits(y); + for(long x = 0; x < infohdr.biWidth; ++x){ + hFile->Write(srcdib,3,1); + hFile->Write(srcalpha,1,1); + srcdib += 3; + ++srcalpha; + } + } + + } else +#endif //CXIMAGE_SUPPORT_ALPHA + { + // Write the file header + hFile->Write(&hdr,min(14,sizeof(BITMAPFILEHEADER)),1); + //copy attributes + memcpy(pDib,&head,sizeof(BITMAPINFOHEADER)); + bihtoh((BITMAPINFOHEADER*)pDib); + // Write the DIB header and the pixels + hFile->Write(pDib,GetSize(),1); + bihtoh((BITMAPINFOHEADER*)pDib); + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageBMP::Decode(CxFile * hFile) +{ + if (hFile == NULL) return false; + + BITMAPFILEHEADER bf; + DWORD off = hFile->Tell(); // + cx_try { + if (hFile->Read(&bf,min(14,sizeof(bf)),1)==0) cx_throw("Not a BMP"); + + bf.bfSize = ntohl(bf.bfSize); + bf.bfOffBits = ntohl(bf.bfOffBits); + + if (bf.bfType != BFT_BITMAP) { //do we have a RC HEADER? + bf.bfOffBits = 0L; + hFile->Seek(off,SEEK_SET); + } + + BITMAPINFOHEADER bmpHeader; + if (!DibReadBitmapInfo(hFile,&bmpHeader)) cx_throw("Error reading BMP info"); + DWORD dwCompression=bmpHeader.biCompression; + DWORD dwBitCount=bmpHeader.biBitCount; //preserve for BI_BITFIELDS compression + bool bIsOldBmp = bmpHeader.biSize == sizeof(BITMAPCOREHEADER); + + bool bTopDownDib = bmpHeader.biHeight<0; // check if it's a top-down bitmap + if (bTopDownDib) bmpHeader.biHeight=-bmpHeader.biHeight; + + if (info.nEscape == -1) { + // Return output dimensions only + head.biWidth = bmpHeader.biWidth; + head.biHeight = bmpHeader.biHeight; + info.dwType = CXIMAGE_FORMAT_BMP; + cx_throw("output dimensions returned"); + } + + if (!Create(bmpHeader.biWidth,bmpHeader.biHeight,bmpHeader.biBitCount,CXIMAGE_FORMAT_BMP)) + cx_throw(""); + + SetXDPI((long) floor(bmpHeader.biXPelsPerMeter * 254.0 / 10000.0 + 0.5)); + SetYDPI((long) floor(bmpHeader.biYPelsPerMeter * 254.0 / 10000.0 + 0.5)); + + if (info.nEscape) cx_throw("Cancelled"); // - cancel decoding + + RGBQUAD *pRgb = GetPalette(); + if (pRgb){ + if (bIsOldBmp){ + // convert a old color table (3 byte entries) to a new + // color table (4 byte entries) + hFile->Read((void*)pRgb,DibNumColors(&bmpHeader) * sizeof(RGBTRIPLE),1); + for (int i=DibNumColors(&head)-1; i>=0; i--){ + pRgb[i].rgbRed = ((RGBTRIPLE *)pRgb)[i].rgbtRed; + pRgb[i].rgbBlue = ((RGBTRIPLE *)pRgb)[i].rgbtBlue; + pRgb[i].rgbGreen = ((RGBTRIPLE *)pRgb)[i].rgbtGreen; + pRgb[i].rgbReserved = (BYTE)0; + } + } else { + hFile->Read((void*)pRgb,DibNumColors(&bmpHeader) * sizeof(RGBQUAD),1); + //force rgbReserved=0, to avoid problems with some WinXp bitmaps + for (unsigned int i=0; i - cancel decoding + + switch (dwBitCount) { + case 32 : + DWORD bfmask[3]; + if (dwCompression == BI_BITFIELDS) + { + hFile->Read(bfmask, 12, 1); + } else { + bfmask[0]=0x00FF0000; + bfmask[1]=0x0000FF00; + bfmask[2]=0x000000FF; + } + if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET); + if (dwCompression == BI_BITFIELDS || dwCompression == BI_RGB){ + long imagesize=4*head.biHeight*head.biWidth; + BYTE* buff32=(BYTE*)malloc(imagesize); + if (buff32){ + hFile->Read(buff32, imagesize,1); // read in the pixels + +#if CXIMAGE_SUPPORT_ALPHA + if (dwCompression == BI_RGB){ + AlphaCreate(); + if (AlphaIsValid()){ + bool bAlphaOk = false; + BYTE* p; + for (long y=0; ySeek(off + bf.bfOffBits,SEEK_SET); + if (dwCompression == BI_RGB){ + hFile->Read(info.pImage, head.biSizeImage,1); // read in the pixels + } else cx_throw("unknown compression"); + break; + case 16 : + { + DWORD bfmask[3]; + if (dwCompression == BI_BITFIELDS) + { + hFile->Read(bfmask, 12, 1); + } else { + bfmask[0]=0x7C00; bfmask[1]=0x3E0; bfmask[2]=0x1F; //RGB555 + } + // bf.bfOffBits required after the bitfield mask + if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET); + // read in the pixels + hFile->Read(info.pImage, head.biHeight*((head.biWidth+1)/2)*4,1); + // transform into RGB + Bitfield2RGB(info.pImage,bfmask[0],bfmask[1],bfmask[2],16); + break; + } + case 8 : + case 4 : + case 1 : + if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET); + switch (dwCompression) { + case BI_RGB : + hFile->Read(info.pImage, head.biSizeImage,1); // read in the pixels + break; + case BI_RLE4 : + { + BYTE status_byte = 0; + BYTE second_byte = 0; + int scanline = 0; + int bits = 0; + BOOL low_nibble = FALSE; + CImageIterator iter(this); + + for (BOOL bContinue = TRUE; bContinue && hFile->Read(&status_byte, sizeof(BYTE), 1);) { + + switch (status_byte) { + case RLE_COMMAND : + hFile->Read(&status_byte, sizeof(BYTE), 1); + switch (status_byte) { + case RLE_ENDOFLINE : + bits = 0; + scanline++; + low_nibble = FALSE; + break; + case RLE_ENDOFBITMAP : + bContinue=FALSE; + break; + case RLE_DELTA : + { + // read the delta values + BYTE delta_x; + BYTE delta_y; + hFile->Read(&delta_x, sizeof(BYTE), 1); + hFile->Read(&delta_y, sizeof(BYTE), 1); + // apply them + bits += delta_x / 2; + scanline += delta_y; + break; + } + default : + hFile->Read(&second_byte, sizeof(BYTE), 1); + BYTE *sline = iter.GetRow(scanline); + for (int i = 0; i < status_byte; i++) { + if ((BYTE*)(sline+bits) < (BYTE*)(info.pImage+head.biSizeImage)){ + if (low_nibble) { + if (i&1) + *(sline + bits) |= (second_byte & 0x0f); + else + *(sline + bits) |= (second_byte & 0xf0)>>4; + bits++; + } else { + if (i&1) + *(sline + bits) = (BYTE)(second_byte & 0x0f)<<4; + else + *(sline + bits) = (BYTE)(second_byte & 0xf0); + } + } + + if ((i & 1) && (i != (status_byte - 1))) + hFile->Read(&second_byte, sizeof(BYTE), 1); + + low_nibble = !low_nibble; + } + if ((((status_byte+1) >> 1) & 1 ) == 1) + hFile->Read(&second_byte, sizeof(BYTE), 1); + break; + }; + break; + default : + { + BYTE *sline = iter.GetRow(scanline); + hFile->Read(&second_byte, sizeof(BYTE), 1); + for (unsigned i = 0; i < status_byte; i++) { + if ((BYTE*)(sline+bits) < (BYTE*)(info.pImage+head.biSizeImage)){ + if (low_nibble) { + if (i&1) + *(sline + bits) |= (second_byte & 0x0f); + else + *(sline + bits) |= (second_byte & 0xf0)>>4; + bits++; + } else { + if (i&1) + *(sline + bits) = (BYTE)(second_byte & 0x0f)<<4; + else + *(sline + bits) = (BYTE)(second_byte & 0xf0); + } + } + low_nibble = !low_nibble; + } + } + break; + }; + } + break; + } + case BI_RLE8 : + { + BYTE status_byte = 0; + BYTE second_byte = 0; + int scanline = 0; + int bits = 0; + CImageIterator iter(this); + + for (BOOL bContinue = TRUE; bContinue && hFile->Read(&status_byte, sizeof(BYTE), 1);) { + switch (status_byte) { + case RLE_COMMAND : + hFile->Read(&status_byte, sizeof(BYTE), 1); + switch (status_byte) { + case RLE_ENDOFLINE : + bits = 0; + scanline++; + break; + case RLE_ENDOFBITMAP : + bContinue=FALSE; + break; + case RLE_DELTA : + { + // read the delta values + BYTE delta_x; + BYTE delta_y; + hFile->Read(&delta_x, sizeof(BYTE), 1); + hFile->Read(&delta_y, sizeof(BYTE), 1); + // apply them + bits += delta_x; + scanline += delta_y; + break; + } + default : + hFile->Read((void *)(iter.GetRow(scanline) + bits), sizeof(BYTE) * status_byte, 1); + // align run length to even number of bytes + if ((status_byte & 1) == 1) + hFile->Read(&second_byte, sizeof(BYTE), 1); + bits += status_byte; + break; + }; + break; + default : + BYTE *sline = iter.GetRow(scanline); + hFile->Read(&second_byte, sizeof(BYTE), 1); + for (unsigned i = 0; i < status_byte; i++) { + if ((DWORD)bits + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (info.nEscape == -1 && info.dwType == CXIMAGE_FORMAT_BMP) return true; + return false; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/* ReadDibBitmapInfo() + * + * Will read a file in DIB format and return a global HANDLE to its + * BITMAPINFO. This function will work with both "old" and "new" + * bitmap formats, but will always return a "new" BITMAPINFO. + */ +bool CxImageBMP::DibReadBitmapInfo(CxFile* fh, BITMAPINFOHEADER *pdib) +{ + if ((fh==NULL)||(pdib==NULL)) return false; + + if (fh->Read(pdib,sizeof(BITMAPINFOHEADER),1)==0) return false; + + bihtoh(pdib); + + switch (pdib->biSize) // what type of bitmap info is this? + { + case sizeof(BITMAPINFOHEADER): + break; + + case 64: //sizeof(OS2_BMP_HEADER): + fh->Seek((long)(64 - sizeof(BITMAPINFOHEADER)),SEEK_CUR); + break; + + case sizeof(BITMAPCOREHEADER): + { + BITMAPCOREHEADER bc = *(BITMAPCOREHEADER*)pdib; + pdib->biSize = bc.bcSize; + pdib->biWidth = (DWORD)bc.bcWidth; + pdib->biHeight = (DWORD)bc.bcHeight; + pdib->biPlanes = bc.bcPlanes; + pdib->biBitCount = bc.bcBitCount; + pdib->biCompression = BI_RGB; + pdib->biSizeImage = 0; + pdib->biXPelsPerMeter = 0; + pdib->biYPelsPerMeter = 0; + pdib->biClrUsed = 0; + pdib->biClrImportant = 0; + + fh->Seek((long)(sizeof(BITMAPCOREHEADER)-sizeof(BITMAPINFOHEADER)), SEEK_CUR); + } + break; + default: + //give a last chance + if (pdib->biSize>(sizeof(BITMAPINFOHEADER))&& + (pdib->biSizeImage>=(unsigned long)(pdib->biHeight*((((pdib->biBitCount*pdib->biWidth)+31)/32)*4)))&& + (pdib->biPlanes==1)&&(pdib->biClrUsed==0)) + { + if (pdib->biCompression==BI_RGB) + fh->Seek((long)(pdib->biSize - sizeof(BITMAPINFOHEADER)),SEEK_CUR); + break; + } + return false; + } + + FixBitmapInfo(pdib); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_BMP +//////////////////////////////////////////////////////////////////////////////// diff --git a/CxImage/ximabmp.h b/CxImage/ximabmp.h new file mode 100644 index 00000000..2bda4feb --- /dev/null +++ b/CxImage/ximabmp.h @@ -0,0 +1,79 @@ +/* + * File: ximabmp.h + * Purpose: BMP Image Class Loader and Writer + */ +/* ========================================================== + * CxImageBMP (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Special thanks to Troels Knakkergaard for new features, enhancements and bugfixes + * + * original CImageBMP and CImageIterator implementation are: + * Copyright: (c) 1995, Alejandro Aguilar Sierra + * + * ========================================================== + */ + +#if !defined(__ximaBMP_h) +#define __ximaBMP_h + +#include "ximage.h" + +const int RLE_COMMAND = 0; +const int RLE_ENDOFLINE = 0; +const int RLE_ENDOFBITMAP = 1; +const int RLE_DELTA = 2; + +#if !defined(BI_RLE8) + #define BI_RLE8 1L +#endif +#if !defined(BI_RLE4) + #define BI_RLE4 2L +#endif + +#if CXIMAGE_SUPPORT_BMP + +class CxImageBMP: public CxImage +{ +public: + CxImageBMP(): CxImage(CXIMAGE_FORMAT_BMP) {}; + + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE + +protected: + bool DibReadBitmapInfo(CxFile* fh, BITMAPINFOHEADER *pdib); +}; + +#define BFT_ICON 0x4349 /* 'IC' */ +#define BFT_BITMAP 0x4d42 /* 'BM' */ +#define BFT_CURSOR 0x5450 /* 'PT' */ + +#ifndef WIDTHBYTES +#define WIDTHBYTES(i) ((unsigned)((i+31)&(~31))/8) /* ULONG aligned ! */ +#endif + +#endif + +#define DibWidthBytesN(lpbi, n) (UINT)WIDTHBYTES((UINT)(lpbi)->biWidth * (UINT)(n)) +#define DibWidthBytes(lpbi) DibWidthBytesN(lpbi, (lpbi)->biBitCount) + +#define DibSizeImage(lpbi) ((lpbi)->biSizeImage == 0 \ + ? ((DWORD)(UINT)DibWidthBytes(lpbi) * (DWORD)(UINT)(lpbi)->biHeight) \ + : (lpbi)->biSizeImage) + +#define DibNumColors(lpbi) ((lpbi)->biClrUsed == 0 && (lpbi)->biBitCount <= 8 \ + ? (int)(1 << (int)(lpbi)->biBitCount) \ + : (int)(lpbi)->biClrUsed) + +#define FixBitmapInfo(lpbi) if ((lpbi)->biSizeImage == 0) \ + (lpbi)->biSizeImage = DibSizeImage(lpbi); \ + if ((lpbi)->biClrUsed == 0) \ + (lpbi)->biClrUsed = DibNumColors(lpbi); \ + +#endif diff --git a/CxImage/ximacfg.h b/CxImage/ximacfg.h new file mode 100644 index 00000000..0b1ece3e --- /dev/null +++ b/CxImage/ximacfg.h @@ -0,0 +1,57 @@ +#if !defined(__ximaCFG_h) +#define __ximaCFG_h + +///////////////////////////////////////////////////////////////////////////// +// CxImage supported features +#define CXIMAGE_SUPPORT_ALPHA 1 +#define CXIMAGE_SUPPORT_SELECTION 0 +#define CXIMAGE_SUPPORT_TRANSFORMATION 1 +#define CXIMAGE_SUPPORT_DSP 0 +#define CXIMAGE_SUPPORT_LAYERS 0 +#define CXIMAGE_SUPPORT_INTERPOLATION 0 + +#define CXIMAGE_SUPPORT_DECODE 1 +#define CXIMAGE_SUPPORT_ENCODE 1 // +#define CXIMAGE_SUPPORT_WINDOWS 1 + +///////////////////////////////////////////////////////////////////////////// +// CxImage supported formats +#define CXIMAGE_SUPPORT_BMP 1 +#define CXIMAGE_SUPPORT_GIF 0 +#define CXIMAGE_SUPPORT_JPG 0 +#define CXIMAGE_SUPPORT_PNG 1 +#define CXIMAGE_SUPPORT_ICO 0 +#define CXIMAGE_SUPPORT_TIF 0 +#define CXIMAGE_SUPPORT_TGA 0 +#define CXIMAGE_SUPPORT_PCX 0 +#define CXIMAGE_SUPPORT_WBMP 0 +#define CXIMAGE_SUPPORT_WMF 0 + +#define CXIMAGE_SUPPORT_JP2 0 +#define CXIMAGE_SUPPORT_JPC 0 +#define CXIMAGE_SUPPORT_PGX 0 +#define CXIMAGE_SUPPORT_PNM 0 +#define CXIMAGE_SUPPORT_RAS 0 + +#define CXIMAGE_SUPPORT_JBG 0 // GPL'd see ../jbig/copying.txt & ../jbig/patents.htm + +#define CXIMAGE_SUPPORT_MNG 0 +#define CXIMAGE_SUPPORT_SKA 0 +#define CXIMAGE_SUPPORT_RAW 0 + +///////////////////////////////////////////////////////////////////////////// +#define CXIMAGE_MAX_MEMORY 268435456 + +#define CXIMAGE_DEFAULT_DPI 96 + +#define CXIMAGE_ERR_NOFILE "null file handler" +#define CXIMAGE_ERR_NOIMAGE "null image!!!" + +#define CXIMAGE_SUPPORT_EXCEPTION_HANDLING 1 + +///////////////////////////////////////////////////////////////////////////// +//color to grey mapping +//#define RGB2GRAY(r,g,b) (((b)*114 + (g)*587 + (r)*299)/1000) +#define RGB2GRAY(r,g,b) (((b)*117 + (g)*601 + (r)*306) >> 10) + +#endif diff --git a/CxImage/ximadef.h b/CxImage/ximadef.h new file mode 100644 index 00000000..b388b2b8 --- /dev/null +++ b/CxImage/ximadef.h @@ -0,0 +1,206 @@ +#if !defined(__ximadefs_h) +#define __ximadefs_h + +#include "ximacfg.h" + +#if defined(_AFXDLL)||defined(_USRDLL) + #define DLL_EXP __declspec(dllexport) +#elif defined(_MSC_VER)&&(_MSC_VER<1200) + #define DLL_EXP __declspec(dllimport) +#else + #define DLL_EXP +#endif + + +#if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + #define cx_try try + #define cx_throw(message) throw(message) + #define cx_catch catch (const char *message) +#else + #define cx_try bool cx_error=false; + #define cx_throw(message) {cx_error=true; if(strcmp(message,"")) strncpy(info.szLastError,message,255); goto cx_error_catch;} + #define cx_catch cx_error_catch: char message[]=""; if(cx_error) +#endif + + +#if CXIMAGE_SUPPORT_JP2 || CXIMAGE_SUPPORT_JPC || CXIMAGE_SUPPORT_PGX || CXIMAGE_SUPPORT_PNM || CXIMAGE_SUPPORT_RAS + #define CXIMAGE_SUPPORT_JASPER 1 +#else + #define CXIMAGE_SUPPORT_JASPER 0 +#endif + +#if CXIMAGE_SUPPORT_DSP +#undef CXIMAGE_SUPPORT_TRANSFORMATION + #define CXIMAGE_SUPPORT_TRANSFORMATION 1 +#endif + +#if CXIMAGE_SUPPORT_TRANSFORMATION || CXIMAGE_SUPPORT_TIF || CXIMAGE_SUPPORT_TGA || CXIMAGE_SUPPORT_BMP || CXIMAGE_SUPPORT_WINDOWS + #define CXIMAGE_SUPPORT_BASICTRANSFORMATIONS 1 +#endif + +#if CXIMAGE_SUPPORT_DSP || CXIMAGE_SUPPORT_TRANSFORMATION +#undef CXIMAGE_SUPPORT_INTERPOLATION + #define CXIMAGE_SUPPORT_INTERPOLATION 1 +#endif + +#if defined (_WIN32_WCE) + #undef CXIMAGE_SUPPORT_WMF + #define CXIMAGE_SUPPORT_WMF 0 +#endif + +#if !defined(WIN32) && !defined(_WIN32_WCE) + #undef CXIMAGE_SUPPORT_WINDOWS + #define CXIMAGE_SUPPORT_WINDOWS 0 +#endif + +#ifndef min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif +#ifndef max +#define max(a,b) (((a)>(b))?(a):(b)) +#endif + +#ifndef PI + #define PI 3.141592653589793f +#endif + + +#if defined(WIN32) || defined(_WIN32_WCE) +#include +#include +#endif + +#include +#include + +#ifdef __BORLANDC__ + +#ifndef _COMPLEX_DEFINED + +typedef struct tagcomplex { + double x,y; +} _complex; + +#endif + +#define _cabs(c) sqrt(c.x*c.x+c.y*c.y) + +#endif + + +#if !defined(WIN32) && !defined(_WIN32_WCE) + +#include +#include +#include + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; +typedef unsigned int UINT; + +typedef DWORD COLORREF; +typedef unsigned int HANDLE; +typedef void* HRGN; + +#ifndef BOOL +#define BOOL bool +#endif + +#ifndef TRUE +#define TRUE true +#endif + +#ifndef FALSE +#define FALSE false +#endif + +#ifndef TCHAR +#define TCHAR char +#define _T +#endif + +typedef struct tagRECT +{ + long left; + long top; + long right; + long bottom; +} RECT; + +typedef struct tagPOINT +{ + long x; + long y; +} POINT; + +typedef struct tagRGBQUAD { + BYTE rgbBlue; + BYTE rgbGreen; + BYTE rgbRed; + BYTE rgbReserved; +} RGBQUAD; + +#pragma pack(1) + +typedef struct tagBITMAPINFOHEADER{ + DWORD biSize; + long biWidth; + long biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + long biXPelsPerMeter; + long biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER; + +typedef struct tagBITMAPFILEHEADER { + WORD bfType; + DWORD bfSize; + WORD bfReserved1; + WORD bfReserved2; + DWORD bfOffBits; +} BITMAPFILEHEADER; + +typedef struct tagBITMAPCOREHEADER { + DWORD bcSize; + WORD bcWidth; + WORD bcHeight; + WORD bcPlanes; + WORD bcBitCount; +} BITMAPCOREHEADER; + +typedef struct tagRGBTRIPLE { + BYTE rgbtBlue; + BYTE rgbtGreen; + BYTE rgbtRed; +} RGBTRIPLE; + +#pragma pack() + +#define BI_RGB 0L +#define BI_RLE8 1L +#define BI_RLE4 2L +#define BI_BITFIELDS 3L + +#define GetRValue(rgb) ((BYTE)(rgb)) +#define GetGValue(rgb) ((BYTE)(((WORD)(rgb)) >> 8)) +#define GetBValue(rgb) ((BYTE)((rgb)>>16)) +#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16))) + +#ifndef _COMPLEX_DEFINED + +typedef struct tagcomplex { + double x,y; +} _complex; + +#endif + +#define _cabs(c) sqrt(c.x*c.x+c.y*c.y) + +#endif + +#endif //__ximadefs diff --git a/CxImage/ximaenc.cpp b/CxImage/ximaenc.cpp new file mode 100644 index 00000000..8edfbfcd --- /dev/null +++ b/CxImage/ximaenc.cpp @@ -0,0 +1,1008 @@ +// xImaCodec.cpp : Encode Decode functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 6.0.0 02/Feb/2008 + */ + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_JPG +#include "ximajpg.h" +#endif + +#if CXIMAGE_SUPPORT_GIF +#include "ximagif.h" +#endif + +#if CXIMAGE_SUPPORT_PNG +#include "ximapng.h" +#endif + +#if CXIMAGE_SUPPORT_MNG +#include "ximamng.h" +#endif + +#if CXIMAGE_SUPPORT_BMP +#include "ximabmp.h" +#endif + +#if CXIMAGE_SUPPORT_ICO +#include "ximaico.h" +#endif + +#if CXIMAGE_SUPPORT_TIF +#include "ximatif.h" +#endif + +#if CXIMAGE_SUPPORT_TGA +#include "ximatga.h" +#endif + +#if CXIMAGE_SUPPORT_PCX +#include "ximapcx.h" +#endif + +#if CXIMAGE_SUPPORT_WBMP +#include "ximawbmp.h" +#endif + +#if CXIMAGE_SUPPORT_WMF +#include "ximawmf.h" // - WMF/EMF support +#endif + +#if CXIMAGE_SUPPORT_JBG +#include "ximajbg.h" +#endif + +#if CXIMAGE_SUPPORT_JASPER +#include "ximajas.h" +#endif + +#if CXIMAGE_SUPPORT_SKA +#include "ximaska.h" +#endif + +#if CXIMAGE_SUPPORT_RAW +#include "ximaraw.h" +#endif + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::EncodeSafeCheck(CxFile *hFile) +{ + if (hFile==NULL) { + strcpy(info.szLastError,CXIMAGE_ERR_NOFILE); + return true; + } + + if (pDib==NULL){ + strcpy(info.szLastError,CXIMAGE_ERR_NOIMAGE); + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +//#ifdef WIN32 +//bool CxImage::Save(LPCWSTR filename, DWORD imagetype) +//{ +// FILE* hFile; //file handle to write the image +// if ((hFile=_wfopen(filename,L"wb"))==NULL) return false; +// bool bOK = Encode(hFile,imagetype); +// fclose(hFile); +// return bOK; +//} +//#endif //WIN32 +//////////////////////////////////////////////////////////////////////////////// +// For UNICODE support: char -> TCHAR +/** + * Saves to disk the image in a specific format. + * \param filename: file name + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Save(const TCHAR * filename, DWORD imagetype) +{ + FILE* hFile; //file handle to write the image + +#ifdef WIN32 + if ((hFile=_tfopen(filename,_T("wb")))==NULL) return false; // For UNICODE support +#else + if ((hFile=fopen(filename,"wb"))==NULL) return false; +#endif + + bool bOK = Encode(hFile,imagetype); + fclose(hFile); + return bOK; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to disk the image in a specific format. + * \param hFile: file handle, open and enabled for writing. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Encode(FILE *hFile, DWORD imagetype) +{ + CxIOFile file(hFile); + return Encode(&file,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to memory buffer the image in a specific format. + * \param buffer: output memory buffer pointer. Must be NULL, + * the function allocates and fill the memory, + * the application must free the buffer, see also FreeMemory(). + * \param size: output memory buffer size. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Encode(BYTE * &buffer, long &size, DWORD imagetype) +{ + if (buffer!=NULL){ + strcpy(info.szLastError,"the buffer must be empty"); + return false; + } + CxMemFile file; + file.Open(); + if(Encode(&file,imagetype)){ + buffer=file.GetBuffer(); + size=file.Size(); + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to disk the image in a specific format. + * \param hFile: file handle (CxMemFile or CxIOFile), with write access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + * \sa ENUM_CXIMAGE_FORMATS + */ +bool CxImage::Encode(CxFile *hFile, DWORD imagetype) +{ + +#if CXIMAGE_SUPPORT_BMP + + if (imagetype==CXIMAGE_FORMAT_BMP){ + CxImageBMP newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_ICO + if (imagetype==CXIMAGE_FORMAT_ICO){ + CxImageICO newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_TIF + if (imagetype==CXIMAGE_FORMAT_TIF){ + CxImageTIF newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JPG + if (imagetype==CXIMAGE_FORMAT_JPG){ + CxImageJPG newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_GIF + if (imagetype==CXIMAGE_FORMAT_GIF){ + CxImageGIF newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PNG + if (imagetype==CXIMAGE_FORMAT_PNG){ + CxImagePNG newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_MNG + if (imagetype==CXIMAGE_FORMAT_MNG){ + CxImageMNG newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_TGA + if (imagetype==CXIMAGE_FORMAT_TGA){ + CxImageTGA newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PCX + if (imagetype==CXIMAGE_FORMAT_PCX){ + CxImagePCX newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_WBMP + if (imagetype==CXIMAGE_FORMAT_WBMP){ + CxImageWBMP newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_WMF && CXIMAGE_SUPPORT_WINDOWS // - WMF/EMF support + if (imagetype==CXIMAGE_FORMAT_WMF){ + CxImageWMF newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JBG + if (imagetype==CXIMAGE_FORMAT_JBG){ + CxImageJBG newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JASPER + if ( + #if CXIMAGE_SUPPORT_JP2 + imagetype==CXIMAGE_FORMAT_JP2 || + #endif + #if CXIMAGE_SUPPORT_JPC + imagetype==CXIMAGE_FORMAT_JPC || + #endif + #if CXIMAGE_SUPPORT_PGX + imagetype==CXIMAGE_FORMAT_PGX || + #endif + #if CXIMAGE_SUPPORT_PNM + imagetype==CXIMAGE_FORMAT_PNM || + #endif + #if CXIMAGE_SUPPORT_RAS + imagetype==CXIMAGE_FORMAT_RAS || + #endif + false ){ + CxImageJAS newima; + newima.Ghost(this); + if (newima.Encode(hFile,imagetype)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif + +#if CXIMAGE_SUPPORT_SKA + if (imagetype==CXIMAGE_FORMAT_SKA){ + CxImageSKA newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif + +#if CXIMAGE_SUPPORT_RAW + if (imagetype==CXIMAGE_FORMAT_RAW){ + CxImageRAW newima; + newima.Ghost(this); + if (newima.Encode(hFile)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif + + strcpy(info.szLastError,"Encode: Unknown format"); + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to disk or memory pagecount images, referenced by an array of CxImage pointers. + * \param hFile: file handle. + * \param pImages: array of CxImage pointers. + * \param pagecount: number of images. + * \param imagetype: can be CXIMAGE_FORMAT_TIF or CXIMAGE_FORMAT_GIF. + * \return true if everything is ok + */ +bool CxImage::Encode(FILE * hFile, CxImage ** pImages, int pagecount, DWORD imagetype) +{ + CxIOFile file(hFile); + return Encode(&file, pImages, pagecount,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to disk or memory pagecount images, referenced by an array of CxImage pointers. + * \param hFile: file handle (CxMemFile or CxIOFile), with write access. + * \param pImages: array of CxImage pointers. + * \param pagecount: number of images. + * \param imagetype: can be CXIMAGE_FORMAT_TIF, CXIMAGE_FORMAT_GIF or CXIMAGE_FORMAT_ICO. + * \return true if everything is ok + */ +bool CxImage::Encode(CxFile * hFile, CxImage ** pImages, int pagecount, DWORD imagetype) +{ +#if CXIMAGE_SUPPORT_TIF + if (imagetype==CXIMAGE_FORMAT_TIF){ + CxImageTIF newima; + newima.Ghost(this); + if (newima.Encode(hFile,pImages,pagecount)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_GIF + if (imagetype==CXIMAGE_FORMAT_GIF){ + CxImageGIF newima; + newima.Ghost(this); + if (newima.Encode(hFile,pImages,pagecount)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_ICO + if (imagetype==CXIMAGE_FORMAT_ICO){ + CxImageICO newima; + newima.Ghost(this); + if (newima.Encode(hFile,pImages,pagecount)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif + strcpy(info.szLastError,"Multipage Encode, Unsupported operation for this format"); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * exports the image into a RGBA buffer, Useful for OpenGL applications. + * \param buffer: output memory buffer pointer. Must be NULL, + * the function allocates and fill the memory, + * the application must free the buffer, see also FreeMemory(). + * \param size: output memory buffer size. + * \param bFlipY: direction of Y axis. default = false. + * \return true if everything is ok + */ +bool CxImage::Encode2RGBA(BYTE * &buffer, long &size, bool bFlipY) +{ + if (buffer!=NULL){ + strcpy(info.szLastError,"the buffer must be empty"); + return false; + } + CxMemFile file; + file.Open(); + if(Encode2RGBA(&file,bFlipY)){ + buffer=file.GetBuffer(); + size=file.Size(); + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * exports the image into a RGBA buffer, Useful for OpenGL applications. + * \param hFile: file handle (CxMemFile or CxIOFile), with write access. + * \param bFlipY: direction of Y axis. default = false. + * \return true if everything is ok + */ +bool CxImage::Encode2RGBA(CxFile *hFile, bool bFlipY) +{ + if (EncodeSafeCheck(hFile)) return false; + + for (long y1 = 0; y1 < head.biHeight; y1++) { + long y = bFlipY ? head.biHeight - 1 - y1 : y1; + for(long x = 0; x < head.biWidth; x++) { + RGBQUAD color = BlindGetPixelColor(x,y); + hFile->PutC(color.rgbRed); + hFile->PutC(color.rgbGreen); + hFile->PutC(color.rgbBlue); + hFile->PutC(color.rgbReserved); + } + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +// For UNICODE support: char -> TCHAR +/** + * Reads from disk the image in a specific format. + * - If decoding fails using the specified image format, + * the function will try the automatic file format recognition. + * + * \param filename: file name + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Load(const TCHAR * filename, DWORD imagetype) +//bool CxImage::Load(const char * filename, DWORD imagetype) +{ + /*FILE* hFile; //file handle to read the image + if ((hFile=fopen(filename,"rb"))==NULL) return false; + bool bOK = Decode(hFile,imagetype); + fclose(hFile);*/ + + /* automatic file type recognition */ + bool bOK = false; + if ( GetTypeIndexFromId(imagetype) ){ + FILE* hFile; //file handle to read the image + +#ifdef WIN32 + if ((hFile=_tfopen(filename,_T("rb")))==NULL) return false; // For UNICODE support +#else + if ((hFile=fopen(filename,"rb"))==NULL) return false; +#endif + + bOK = Decode(hFile,imagetype); + fclose(hFile); + if (bOK) return bOK; + } + + char szError[256]; + strcpy(szError,info.szLastError); //save the first error + + // if failed, try automatic recognition of the file... + FILE* hFile; + +#ifdef WIN32 + if ((hFile=_tfopen(filename,_T("rb")))==NULL) return false; // For UNICODE support +#else + if ((hFile=fopen(filename,"rb"))==NULL) return false; +#endif + + bOK = Decode(hFile,CXIMAGE_FORMAT_UNKNOWN); + fclose(hFile); + + if (!bOK && imagetype > 0) strcpy(info.szLastError,szError); //restore the first error + + return bOK; +} +//////////////////////////////////////////////////////////////////////////////// +#ifdef WIN32 +//bool CxImage::Load(LPCWSTR filename, DWORD imagetype) +//{ +// /*FILE* hFile; //file handle to read the image +// if ((hFile=_wfopen(filename, L"rb"))==NULL) return false; +// bool bOK = Decode(hFile,imagetype); +// fclose(hFile);*/ +// +// /* automatic file type recognition */ +// bool bOK = false; +// if ( GetTypeIndexFromId(imagetype) ){ +// FILE* hFile; //file handle to read the image +// if ((hFile=_wfopen(filename,L"rb"))==NULL) return false; +// bOK = Decode(hFile,imagetype); +// fclose(hFile); +// if (bOK) return bOK; +// } +// +// char szError[256]; +// strcpy(szError,info.szLastError); //save the first error +// +// // if failed, try automatic recognition of the file... +// FILE* hFile; //file handle to read the image +// if ((hFile=_wfopen(filename,L"rb"))==NULL) return false; +// bOK = Decode(hFile,CXIMAGE_FORMAT_UNKNOWN); +// fclose(hFile); +// +// if (!bOK && imagetype > 0) strcpy(info.szLastError,szError); //restore the first error +// +// return bOK; +//} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from the application resources. + * \param hRes: the resource handle returned by FindResource(). + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS. + * \param hModule: NULL for internal resource, or external application/DLL hinstance returned by LoadLibray. + * \return true if everything is ok + */ +bool CxImage::LoadResource(HRSRC hRes, DWORD imagetype, HMODULE hModule) +{ + DWORD rsize=SizeofResource(hModule,hRes); + HGLOBAL hMem=::LoadResource(hModule,hRes); + if (hMem){ + char* lpVoid=(char*)LockResource(hMem); + if (lpVoid){ + // FILE* fTmp=tmpfile(); doesn't work with network + /*char tmpPath[MAX_PATH] = {0}; + char tmpFile[MAX_PATH] = {0}; + GetTempPath(MAX_PATH,tmpPath); + GetTempFileName(tmpPath,"IMG",0,tmpFile); + FILE* fTmp=fopen(tmpFile,"w+b"); + if (fTmp){ + fwrite(lpVoid,rsize,1,fTmp); + fseek(fTmp,0,SEEK_SET); + bool bOK = Decode(fTmp,imagetype); + fclose(fTmp); + DeleteFile(tmpFile); + return bOK; + }*/ + + CxMemFile fTmp((BYTE*)lpVoid,rsize); + return Decode(&fTmp,imagetype); + } + } else strcpy(info.szLastError,"Unable to load resource!"); + return false; +} +#endif //WIN32 +//////////////////////////////////////////////////////////////////////////////// +/** + * Constructor from file name, see Load() + * \param filename: file name + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + */ +// +// > filename: file name +// > imagetype: specify the image format (CXIMAGE_FORMAT_BMP,...) +// For UNICODE support: char -> TCHAR +CxImage::CxImage(const TCHAR * filename, DWORD imagetype) +//CxImage::CxImage(const char * filename, DWORD imagetype) +{ + Startup(imagetype); + Load(filename,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Constructor from file handle, see Decode() + * \param stream: file handle, with read access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + */ +CxImage::CxImage(FILE * stream, DWORD imagetype) +{ + Startup(imagetype); + Decode(stream,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Constructor from CxFile object, see Decode() + * \param stream: file handle (CxMemFile or CxIOFile), with read access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + */ +CxImage::CxImage(CxFile * stream, DWORD imagetype) +{ + Startup(imagetype); + Decode(stream,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Constructor from memory buffer, see Decode() + * \param buffer: memory buffer + * \param size: size of buffer + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + */ +CxImage::CxImage(BYTE * buffer, DWORD size, DWORD imagetype) +{ + Startup(imagetype); + CxMemFile stream(buffer,size); + Decode(&stream,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from memory buffer + * \param buffer: memory buffer + * \param size: size of buffer + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Decode(BYTE * buffer, DWORD size, DWORD imagetype) +{ + CxMemFile file(buffer,size); + return Decode(&file,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from file handle. + * \param hFile: file handle, with read access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Decode(FILE *hFile, DWORD imagetype) +{ + CxIOFile file(hFile); + return Decode(&file,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from CxFile object + * \param hFile: file handle (CxMemFile or CxIOFile), with read access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + * \sa ENUM_CXIMAGE_FORMATS + */ +bool CxImage::Decode(CxFile *hFile, DWORD imagetype) +{ + if (hFile == NULL){ + strcpy(info.szLastError,CXIMAGE_ERR_NOFILE); + return false; + } + + if (imagetype==CXIMAGE_FORMAT_UNKNOWN){ + DWORD pos = hFile->Tell(); +#if CXIMAGE_SUPPORT_BMP + { CxImageBMP newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_JPG + { CxImageJPG newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_ICO + { CxImageICO newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_GIF + { CxImageGIF newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_PNG + { CxImagePNG newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_TIF + { CxImageTIF newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_MNG + { CxImageMNG newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_TGA + { CxImageTGA newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_PCX + { CxImagePCX newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_WBMP + { CxImageWBMP newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_WMF && CXIMAGE_SUPPORT_WINDOWS + { CxImageWMF newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_JBG + { CxImageJBG newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_JASPER + { CxImageJAS newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_SKA + { CxImageSKA newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif +#if CXIMAGE_SUPPORT_RAW + { CxImageRAW newima; newima.CopyInfo(*this); if (newima.Decode(hFile)) { Transfer(newima); return true; } else hFile->Seek(pos,SEEK_SET); } +#endif + } + +#if CXIMAGE_SUPPORT_BMP + if (imagetype==CXIMAGE_FORMAT_BMP){ + CxImageBMP newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JPG + if (imagetype==CXIMAGE_FORMAT_JPG){ + CxImageJPG newima; + newima.CopyInfo(*this); // + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_ICO + if (imagetype==CXIMAGE_FORMAT_ICO){ + CxImageICO newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + info.nNumFrames = newima.info.nNumFrames; + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_GIF + if (imagetype==CXIMAGE_FORMAT_GIF){ + CxImageGIF newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + info.nNumFrames = newima.info.nNumFrames; + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PNG + if (imagetype==CXIMAGE_FORMAT_PNG){ + CxImagePNG newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_TIF + if (imagetype==CXIMAGE_FORMAT_TIF){ + CxImageTIF newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + info.nNumFrames = newima.info.nNumFrames; + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_MNG + if (imagetype==CXIMAGE_FORMAT_MNG){ + CxImageMNG newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + info.nNumFrames = newima.info.nNumFrames; + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_TGA + if (imagetype==CXIMAGE_FORMAT_TGA){ + CxImageTGA newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PCX + if (imagetype==CXIMAGE_FORMAT_PCX){ + CxImagePCX newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_WBMP + if (imagetype==CXIMAGE_FORMAT_WBMP){ + CxImageWBMP newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_WMF && CXIMAGE_SUPPORT_WINDOWS // vho - WMF support + if (imagetype == CXIMAGE_FORMAT_WMF){ + CxImageWMF newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JBG + if (imagetype==CXIMAGE_FORMAT_JBG){ + CxImageJBG newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JASPER + if ( + #if CXIMAGE_SUPPORT_JP2 + imagetype==CXIMAGE_FORMAT_JP2 || + #endif + #if CXIMAGE_SUPPORT_JPC + imagetype==CXIMAGE_FORMAT_JPC || + #endif + #if CXIMAGE_SUPPORT_PGX + imagetype==CXIMAGE_FORMAT_PGX || + #endif + #if CXIMAGE_SUPPORT_PNM + imagetype==CXIMAGE_FORMAT_PNM || + #endif + #if CXIMAGE_SUPPORT_RAS + imagetype==CXIMAGE_FORMAT_RAS || + #endif + false ){ + CxImageJAS newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile,imagetype)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_SKA + if (imagetype==CXIMAGE_FORMAT_SKA){ + CxImageSKA newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif + +#if CXIMAGE_SUPPORT_RAW + if (imagetype==CXIMAGE_FORMAT_RAW){ + CxImageRAW newima; + newima.CopyInfo(*this); + if (newima.Decode(hFile)){ + Transfer(newima); + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif + + strcpy(info.szLastError,"Decode: Unknown or wrong format"); + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from CxFile object + * \param hFile: file handle (CxMemFile or CxIOFile), with read access. + * \param imagetype: file format, default = 0 (CXIMAGE_FORMAT_UNKNOWN) + * \return : if imagetype is not 0, the function returns true when imagetype + * matches the file image format. If imagetype is 0, the function returns true + * when the file image format is recognized as a supported format. + * If the returned value is true, use GetHeight(), GetWidth() or GetType() + * to retrieve the basic image information. + * \sa ENUM_CXIMAGE_FORMATS + */ +bool CxImage::CheckFormat(CxFile * hFile, DWORD imagetype) +{ + SetType(CXIMAGE_FORMAT_UNKNOWN); + SetEscape(-1); + + if (!Decode(hFile,imagetype)) + return false; + + if (GetType() == CXIMAGE_FORMAT_UNKNOWN || GetType() != imagetype) + return false; + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::CheckFormat(BYTE * buffer, DWORD size, DWORD imagetype) +{ + if (buffer==NULL || size==NULL){ + strcpy(info.szLastError,"invalid or empty buffer"); + return false; + } + CxMemFile file(buffer,size); + return CheckFormat(&file,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// diff --git a/CxImage/ximage.cpp b/CxImage/ximage.cpp new file mode 100644 index 00000000..cdf3cc78 --- /dev/null +++ b/CxImage/ximage.cpp @@ -0,0 +1,594 @@ +// ximage.cpp : main implementation file +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 6.0.0 02/Feb/2008 + */ + +#include "ximage.h" + +//////////////////////////////////////////////////////////////////////////////// +// CxImage +//////////////////////////////////////////////////////////////////////////////// +/** + * Initialize the internal structures + */ +void CxImage::Startup(DWORD imagetype) +{ + //init pointers + pDib = pSelection = pAlpha = NULL; + ppLayers = ppFrames = NULL; + //init structures + memset(&head,0,sizeof(BITMAPINFOHEADER)); + memset(&info,0,sizeof(CXIMAGEINFO)); + //init default attributes + info.dwType = imagetype; + info.fQuality = 90.0f; + info.nAlphaMax = 255; + info.nBkgndIndex = -1; + info.bEnabled = true; + SetXDPI(CXIMAGE_DEFAULT_DPI); + SetYDPI(CXIMAGE_DEFAULT_DPI); + + short test = 1; + info.bLittleEndianHost = (*((char *) &test) == 1); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Empty image constructor + * \param imagetype: (optional) set the image format, see ENUM_CXIMAGE_FORMATS + */ +CxImage::CxImage(DWORD imagetype) +{ + Startup(imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Call this function to destroy image pixels, alpha channel, selection and sub layers. + * - Attributes are not erased, but IsValid returns false. + * + * \return true if everything is freed, false if the image is a Ghost + */ +bool CxImage::Destroy() +{ + //free this only if it's valid and it's not a ghost + if (info.pGhost==NULL){ + if (ppLayers) { + for(long n=0; n Use it before Create() + */ +void CxImage::CopyInfo(const CxImage &src) +{ + if (pDib==NULL) memcpy(&info,&src.info,sizeof(CXIMAGEINFO)); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa Copy + */ +CxImage& CxImage::operator = (const CxImage& isrc) +{ + if (this != &isrc) Copy(isrc); + return *this; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Initializes or rebuilds the image. + * \param dwWidth: width + * \param dwHeight: height + * \param wBpp: bit per pixel, can be 1, 4, 8, 24 + * \param imagetype: (optional) set the image format, see ENUM_CXIMAGE_FORMATS + * \return pointer to the internal pDib object; NULL if an error occurs. + */ +void* CxImage::Create(DWORD dwWidth, DWORD dwHeight, DWORD wBpp, DWORD imagetype) +{ + // destroy the existing image (if any) + if (!Destroy()) + return NULL; + + // prevent further actions if width or height are not vaild + if ((dwWidth == 0) || (dwHeight == 0)){ + strcpy(info.szLastError,"CxImage::Create : width and height must be greater than zero"); + return NULL; + } + + // Make sure bits per pixel is valid + if (wBpp <= 1) wBpp = 1; + else if (wBpp <= 4) wBpp = 4; + else if (wBpp <= 8) wBpp = 8; + else wBpp = 24; + + // limit memory requirements (and also a check for bad parameters) + if (((dwWidth*dwHeight*wBpp)>>3) > CXIMAGE_MAX_MEMORY || + ((dwWidth*dwHeight*wBpp)/wBpp) != (dwWidth*dwHeight)) + { + strcpy(info.szLastError,"CXIMAGE_MAX_MEMORY exceeded"); + return NULL; + } + + // set the correct bpp value + switch (wBpp){ + case 1: + head.biClrUsed = 2; break; + case 4: + head.biClrUsed = 16; break; + case 8: + head.biClrUsed = 256; break; + default: + head.biClrUsed = 0; + } + + //set the common image informations + info.dwEffWidth = ((((wBpp * dwWidth) + 31) / 32) * 4); + info.dwType = imagetype; + + // initialize BITMAPINFOHEADER + head.biSize = sizeof(BITMAPINFOHEADER); // + head.biWidth = dwWidth; // fill in width from parameter + head.biHeight = dwHeight; // fill in height from parameter + head.biPlanes = 1; // must be 1 + head.biBitCount = (WORD)wBpp; // from parameter + head.biCompression = BI_RGB; + head.biSizeImage = info.dwEffWidth * dwHeight; +// head.biXPelsPerMeter = 0; See SetXDPI +// head.biYPelsPerMeter = 0; See SetYDPI +// head.biClrImportant = 0; See SetClrImportant + + pDib = malloc(GetSize()); // alloc memory block to store our bitmap + if (!pDib){ + strcpy(info.szLastError,"CxImage::Create can't allocate memory"); + return NULL; + } + + //clear the palette + RGBQUAD* pal=GetPalette(); + if (pal) memset(pal,0,GetPaletteSize()); + //Destroy the existing selection +#if CXIMAGE_SUPPORT_SELECTION + if (pSelection) SelectionDelete(); +#endif //CXIMAGE_SUPPORT_SELECTION + //Destroy the existing alpha channel +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha) AlphaDelete(); +#endif //CXIMAGE_SUPPORT_ALPHA + + // use our bitmap info structure to fill in first part of + // our DIB with the BITMAPINFOHEADER + BITMAPINFOHEADER* lpbi; + lpbi = (BITMAPINFOHEADER*)(pDib); + *lpbi = head; + + info.pImage=GetBits(); + + return pDib; //return handle to the DIB +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return pointer to the image pixels. USE CAREFULLY + */ +BYTE* CxImage::GetBits(DWORD row) +{ + if (pDib){ + if (row) { + if (row<(DWORD)head.biHeight){ + return ((BYTE*)pDib + *(DWORD*)pDib + GetPaletteSize() + (info.dwEffWidth * row)); + } else { + return NULL; + } + } else { + return ((BYTE*)pDib + *(DWORD*)pDib + GetPaletteSize()); + } + } + return NULL; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return the size in bytes of the internal pDib object + */ +long CxImage::GetSize() +{ + return head.biSize + head.biSizeImage + GetPaletteSize(); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if the coordinates are inside the image + * \return true if x and y are both inside the image + */ +bool CxImage::IsInside(long x, long y) +{ + return (0<=y && y 0) bval = 255; + } + if (GetBpp() == 4){ + bval = (BYTE)(17*(0x0F & bval)); + } + + memset(info.pImage,bval,head.biSizeImage); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Transfers the image from an existing source image. The source becomes empty. + * \return true if everything is ok + */ +bool CxImage::Transfer(CxImage &from, bool bTransferFrames /*=true*/) +{ + if (!Destroy()) + return false; + + memcpy(&head,&from.head,sizeof(BITMAPINFOHEADER)); + memcpy(&info,&from.info,sizeof(CXIMAGEINFO)); + + pDib = from.pDib; + pSelection = from.pSelection; + pAlpha = from.pAlpha; + ppLayers = from.ppLayers; + + memset(&from.head,0,sizeof(BITMAPINFOHEADER)); + memset(&from.info,0,sizeof(CXIMAGEINFO)); + from.pDib = from.pSelection = from.pAlpha = NULL; + from.ppLayers = NULL; + + if (bTransferFrames){ + DestroyFrames(); + ppFrames = from.ppFrames; + from.ppFrames = NULL; + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * (this) points to the same pDib owned by (*from), the image remains in (*from) + * but (this) has the access to the pixels. Use carefully !!! + */ +void CxImage::Ghost(const CxImage *from) +{ + if (from){ + memcpy(&head,&from->head,sizeof(BITMAPINFOHEADER)); + memcpy(&info,&from->info,sizeof(CXIMAGEINFO)); + pDib = from->pDib; + pSelection = from->pSelection; + pAlpha = from->pAlpha; + ppLayers = from->ppLayers; + ppFrames = from->ppFrames; + info.pGhost=(CxImage *)from; + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * turns a 16 or 32 bit bitfield image into a RGB image + */ +void CxImage::Bitfield2RGB(BYTE *src, DWORD redmask, DWORD greenmask, DWORD bluemask, BYTE bpp) +{ + switch (bpp){ + case 16: + { + DWORD ns[3]={0,0,0}; + // compute the number of shift for each mask + for (int i=0;i<16;i++){ + if ((redmask>>i)&0x01) ns[0]++; + if ((greenmask>>i)&0x01) ns[1]++; + if ((bluemask>>i)&0x01) ns[2]++; + } + ns[1]+=ns[0]; ns[2]+=ns[1]; ns[0]=8-ns[0]; ns[1]-=8; ns[2]-=8; + // dword aligned width for 16 bit image + long effwidth2=(((head.biWidth + 1) / 2) * 4); + WORD w; + long y2,y3,x2,x3; + BYTE *p=info.pImage; + // scan the buffer in reverse direction to avoid reallocations + for (long y=head.biHeight-1; y>=0; y--){ + y2=effwidth2*y; + y3=info.dwEffWidth*y; + for (long x=head.biWidth-1; x>=0; x--){ + x2 = 2*x+y2; + x3 = 3*x+y3; + w = (WORD)(src[x2]+256*src[1+x2]); + p[ x3]=(BYTE)((w & bluemask)<>ns[1]); + p[2+x3]=(BYTE)((w & redmask)>>ns[2]); + } + } + break; + } + case 32: + { + DWORD ns[3]={0,0,0}; + // compute the number of shift for each mask + for (int i=8;i<32;i+=8){ + if (redmask>>i) ns[0]++; + if (greenmask>>i) ns[1]++; + if (bluemask>>i) ns[2]++; + } + // dword aligned width for 32 bit image + long effwidth4 = head.biWidth * 4; + long y4,y3,x4,x3; + BYTE *p=info.pImage; + // scan the buffer in reverse direction to avoid reallocations + for (long y=head.biHeight-1; y>=0; y--){ + y4=effwidth4*y; + y3=info.dwEffWidth*y; + for (long x=head.biWidth-1; x>=0; x--){ + x4 = 4*x+y4; + x3 = 3*x+y3; + p[ x3]=src[ns[2]+x4]; + p[1+x3]=src[ns[1]+x4]; + p[2+x3]=src[ns[0]+x4]; + } + } + } + + } + return; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Creates an image from a generic buffer + * \param pArray: source memory buffer + * \param dwWidth: image width + * \param dwHeight: image height + * \param dwBitsperpixel: can be 1,4,8,24,32 + * \param dwBytesperline: line alignment, in bytes, for a single row stored in pArray + * \param bFlipImage: tune this parameter if the image is upsidedown + * \return true if everything is ok + */ +bool CxImage::CreateFromArray(BYTE* pArray,DWORD dwWidth,DWORD dwHeight,DWORD dwBitsperpixel, DWORD dwBytesperline, bool bFlipImage) +{ + if (pArray==NULL) return false; + if (!((dwBitsperpixel==1)||(dwBitsperpixel==4)||(dwBitsperpixel==8)|| + (dwBitsperpixel==24)||(dwBitsperpixel==32))) return false; + + if (!Create(dwWidth,dwHeight,dwBitsperpixel)) return false; + + if (dwBitsperpixel<24) SetGrayPalette(); + +#if CXIMAGE_SUPPORT_ALPHA + if (dwBitsperpixel==32) AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + + BYTE *dst,*src; + + for (DWORD y = 0; yrgbRed,c1->rgbGreen,c1->rgbBlue); + int g2 = (int)RGB2GRAY(c2->rgbRed,c2->rgbGreen,c2->rgbBlue); + + return (g1-g2); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * simply calls "if (memblock) free(memblock);". + * Useful when calling Encode for a memory buffer, + * from a DLL compiled with different memory management options. + * CxImage::FreeMemory will use the same memory environment used by Encode. + * \author [livecn] + */ +void CxImage::FreeMemory(void* memblock) +{ + if (memblock) + free(memblock); +} + +// eMule 05/09/2008: Moved over from DSP functions to not keep the whole file but only this one function +//////////////////////////////////////////////////////////////////////////////// +/** + * Adds a random offset to each pixel in the image + * \param radius: maximum pixel displacement + * \return true if everything is ok + */ +bool CxImage::Jitter(long radius) +{ + if (!pDib) return false; + + long nx,ny; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + long xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(long y=ymin; y 1000 +#pragma once +#endif + +///////////////////////////////////////////////////////////////////////////// +#include "xfile.h" +#include "xiofile.h" +#include "xmemfile.h" +#include "ximadef.h" // adjust some #define + +/* see "ximacfg.h" for CxImage configuration options */ + +///////////////////////////////////////////////////////////////////////////// +// CxImage formats enumerator +enum ENUM_CXIMAGE_FORMATS{ +CXIMAGE_FORMAT_UNKNOWN = 0, +#if CXIMAGE_SUPPORT_BMP +CXIMAGE_FORMAT_BMP = 1, +#endif +#if CXIMAGE_SUPPORT_GIF +CXIMAGE_FORMAT_GIF = 2, +#endif +#if CXIMAGE_SUPPORT_JPG +CXIMAGE_FORMAT_JPG = 3, +#endif +#if CXIMAGE_SUPPORT_PNG +CXIMAGE_FORMAT_PNG = 4, +#endif +#if CXIMAGE_SUPPORT_ICO +CXIMAGE_FORMAT_ICO = 5, +#endif +#if CXIMAGE_SUPPORT_TIF +CXIMAGE_FORMAT_TIF = 6, +#endif +#if CXIMAGE_SUPPORT_TGA +CXIMAGE_FORMAT_TGA = 7, +#endif +#if CXIMAGE_SUPPORT_PCX +CXIMAGE_FORMAT_PCX = 8, +#endif +#if CXIMAGE_SUPPORT_WBMP +CXIMAGE_FORMAT_WBMP = 9, +#endif +#if CXIMAGE_SUPPORT_WMF +CXIMAGE_FORMAT_WMF = 10, +#endif +#if CXIMAGE_SUPPORT_JP2 +CXIMAGE_FORMAT_JP2 = 11, +#endif +#if CXIMAGE_SUPPORT_JPC +CXIMAGE_FORMAT_JPC = 12, +#endif +#if CXIMAGE_SUPPORT_PGX +CXIMAGE_FORMAT_PGX = 13, +#endif +#if CXIMAGE_SUPPORT_PNM +CXIMAGE_FORMAT_PNM = 14, +#endif +#if CXIMAGE_SUPPORT_RAS +CXIMAGE_FORMAT_RAS = 15, +#endif +#if CXIMAGE_SUPPORT_JBG +CXIMAGE_FORMAT_JBG = 16, +#endif +#if CXIMAGE_SUPPORT_MNG +CXIMAGE_FORMAT_MNG = 17, +#endif +#if CXIMAGE_SUPPORT_SKA +CXIMAGE_FORMAT_SKA = 18, +#endif +#if CXIMAGE_SUPPORT_RAW +CXIMAGE_FORMAT_RAW = 19, +#endif +CMAX_IMAGE_FORMATS = CXIMAGE_SUPPORT_BMP + CXIMAGE_SUPPORT_GIF + CXIMAGE_SUPPORT_JPG + + CXIMAGE_SUPPORT_PNG + CXIMAGE_SUPPORT_MNG + CXIMAGE_SUPPORT_ICO + + CXIMAGE_SUPPORT_TIF + CXIMAGE_SUPPORT_TGA + CXIMAGE_SUPPORT_PCX + + CXIMAGE_SUPPORT_WBMP+ CXIMAGE_SUPPORT_WMF + + CXIMAGE_SUPPORT_JBG + CXIMAGE_SUPPORT_JP2 + CXIMAGE_SUPPORT_JPC + + CXIMAGE_SUPPORT_PGX + CXIMAGE_SUPPORT_PNM + CXIMAGE_SUPPORT_RAS + + CXIMAGE_SUPPORT_SKA + CXIMAGE_SUPPORT_RAW + 1 +}; + +///////////////////////////////////////////////////////////////////////////// +// CxImage class +///////////////////////////////////////////////////////////////////////////// +class DLL_EXP CxImage +{ +//extensible information collector +typedef struct tagCxImageInfo { + DWORD dwEffWidth; ///< DWORD aligned scan line width + BYTE* pImage; ///< THE IMAGE BITS + CxImage* pGhost; ///< if this is a ghost, pGhost points to the body + CxImage* pParent; ///< if this is a layer, pParent points to the body + DWORD dwType; ///< original image format + char szLastError[256]; ///< debugging + long nProgress; ///< monitor + long nEscape; ///< escape + long nBkgndIndex; ///< used for GIF, PNG, MNG + RGBQUAD nBkgndColor; ///< used for RGB transparency + float fQuality; ///< used for JPEG, JPEG2000 (0.0f ... 100.0f) + BYTE nJpegScale; ///< used for JPEG [ignacio] + long nFrame; ///< used for TIF, GIF, MNG : actual frame + long nNumFrames; ///< used for TIF, GIF, MNG : total number of frames + DWORD dwFrameDelay; ///< used for GIF, MNG + long xDPI; ///< horizontal resolution + long yDPI; ///< vertical resolution + RECT rSelectionBox; ///< bounding rectangle + BYTE nAlphaMax; ///< max opacity (fade) + bool bAlphaPaletteEnabled; ///< true if alpha values in the palette are enabled. + bool bEnabled; ///< enables the painting functions + long xOffset; + long yOffset; + DWORD dwCodecOpt[CMAX_IMAGE_FORMATS]; ///< for GIF, TIF : 0=def.1=unc,2=fax3,3=fax4,4=pack,5=jpg + RGBQUAD last_c; ///< for GetNearestIndex optimization + BYTE last_c_index; + bool last_c_isvalid; + long nNumLayers; + DWORD dwFlags; ///< 0x??00000 = reserved, 0x00??0000 = blend mode, 0x0000???? = layer id - user flags + BYTE dispmeth; + bool bGetAllFrames; + bool bLittleEndianHost; + +} CXIMAGEINFO; + +public: + //public structures +struct rgb_color { BYTE r,g,b; }; + +#if CXIMAGE_SUPPORT_WINDOWS +// text placement data +// members must be initialized with the InitTextInfo(&this) function. +typedef struct tagCxTextInfo +{ +#if defined (_WIN32_WCE) + TCHAR text[256]; ///< text for windows CE +#else + TCHAR text[4096]; ///< text (char -> TCHAR for UNICODE [Cesar M]) +#endif + LOGFONT lfont; ///< font and codepage data + COLORREF fcolor; ///< foreground color + long align; ///< DT_CENTER, DT_RIGHT, DT_LEFT aligment for multiline text + BYTE smooth; ///< text smoothing option. Default is false. + BYTE opaque; ///< text has background or hasn't. Default is true. + ///< data for background (ignored if .opaque==FALSE) + COLORREF bcolor; ///< background color + float b_opacity; ///< opacity value for background between 0.0-1.0 Default is 0. (opaque) + BYTE b_outline; ///< outline width for background (zero: no outline) + BYTE b_round; ///< rounding radius for background rectangle. % of the height, between 0-50. Default is 10. + ///< (backgr. always has a frame: width = 3 pixel + 10% of height by default.) +} CXTEXTINFO; +#endif + +public: +/** \addtogroup Constructors */ //@{ + CxImage(DWORD imagetype = 0); + CxImage(DWORD dwWidth, DWORD dwHeight, DWORD wBpp, DWORD imagetype = 0); + CxImage(const CxImage &src, bool copypixels = true, bool copyselection = true, bool copyalpha = true); + CxImage(const TCHAR * filename, DWORD imagetype); // For UNICODE support: char -> TCHAR + CxImage(FILE * stream, DWORD imagetype); + CxImage(CxFile * stream, DWORD imagetype); + CxImage(BYTE * buffer, DWORD size, DWORD imagetype); + virtual ~CxImage() { DestroyFrames(); Destroy(); }; + CxImage& operator = (const CxImage&); +//@} + +/** \addtogroup Initialization */ //@{ + void* Create(DWORD dwWidth, DWORD dwHeight, DWORD wBpp, DWORD imagetype = 0); + bool Destroy(); + bool DestroyFrames(); + void Clear(BYTE bval=0); + void Copy(const CxImage &src, bool copypixels = true, bool copyselection = true, bool copyalpha = true); + bool Transfer(CxImage &from, bool bTransferFrames = true); + bool CreateFromArray(BYTE* pArray,DWORD dwWidth,DWORD dwHeight,DWORD dwBitsperpixel, DWORD dwBytesperline, bool bFlipImage); + bool CreateFromMatrix(BYTE** ppMatrix,DWORD dwWidth,DWORD dwHeight,DWORD dwBitsperpixel, DWORD dwBytesperline, bool bFlipImage); + void FreeMemory(void* memblock); + + DWORD Dump(BYTE * dst); + DWORD UnDump(const BYTE * src); + DWORD DumpSize(); + +//@} + +/** \addtogroup Attributes */ //@{ + long GetSize(); + BYTE* GetBits(DWORD row = 0); + BYTE GetColorType(); + void* GetDIB() const; + DWORD GetHeight() const; + DWORD GetWidth() const; + DWORD GetEffWidth() const; + DWORD GetNumColors() const; + WORD GetBpp() const; + DWORD GetType() const; + const char* GetLastError(); + static const TCHAR* GetVersion(); + static const float GetVersionNumber(); + + DWORD GetFrameDelay() const; + void SetFrameDelay(DWORD d); + + void GetOffset(long *x,long *y); + void SetOffset(long x,long y); + + BYTE GetJpegQuality() const; + void SetJpegQuality(BYTE q); + float GetJpegQualityF() const; + void SetJpegQualityF(float q); + + BYTE GetJpegScale() const; + void SetJpegScale(BYTE q); + + long GetXDPI() const; + long GetYDPI() const; + void SetXDPI(long dpi); + void SetYDPI(long dpi); + + DWORD GetClrImportant() const; + void SetClrImportant(DWORD ncolors = 0); + + long GetProgress() const; + long GetEscape() const; + void SetProgress(long p); + void SetEscape(long i); + + long GetTransIndex() const; + RGBQUAD GetTransColor(); + void SetTransIndex(long idx); + void SetTransColor(RGBQUAD rgb); + bool IsTransparent() const; + + DWORD GetCodecOption(DWORD imagetype = 0); + bool SetCodecOption(DWORD opt, DWORD imagetype = 0); + + DWORD GetFlags() const; + void SetFlags(DWORD flags, bool bLockReservedFlags = true); + + BYTE GetDisposalMethod() const; + void SetDisposalMethod(BYTE dm); + + bool SetType(DWORD type); + + static DWORD GetNumTypes(); + static DWORD GetTypeIdFromName(const TCHAR* ext); + static DWORD GetTypeIdFromIndex(const DWORD index); + static DWORD GetTypeIndexFromId(const DWORD id); + + bool GetRetreiveAllFrames() const; + void SetRetreiveAllFrames(bool flag); + CxImage * GetFrame(long nFrame) const; + + //void* GetUserData() const {return info.pUserData;} + //void SetUserData(void* pUserData) {info.pUserData = pUserData;} +//@} + +/** \addtogroup Palette + * These functions have no effects on RGB images and in this case the returned value is always 0. + * @{ */ + bool IsGrayScale(); + bool IsIndexed() const; + bool IsSamePalette(CxImage &img, bool bCheckAlpha = true); + DWORD GetPaletteSize(); + RGBQUAD* GetPalette() const; + RGBQUAD GetPaletteColor(BYTE idx); + bool GetPaletteColor(BYTE i, BYTE* r, BYTE* g, BYTE* b); + BYTE GetNearestIndex(RGBQUAD c); + void BlendPalette(COLORREF cr,long perc); + void SetGrayPalette(); + void SetPalette(DWORD n, BYTE *r, BYTE *g, BYTE *b); + void SetPalette(RGBQUAD* pPal,DWORD nColors=256); + void SetPalette(rgb_color *rgb,DWORD nColors=256); + void SetPaletteColor(BYTE idx, BYTE r, BYTE g, BYTE b, BYTE alpha=0); + void SetPaletteColor(BYTE idx, RGBQUAD c); + void SetPaletteColor(BYTE idx, COLORREF cr); + void SwapIndex(BYTE idx1, BYTE idx2); + void SwapRGB2BGR(); + void SetStdPalette(); +//@} + +/** \addtogroup Pixel */ //@{ + bool IsInside(long x, long y); + bool IsTransparent(long x,long y); + bool GetTransparentMask(CxImage* iDst = 0); + RGBQUAD GetPixelColor(long x,long y, bool bGetAlpha = true); + BYTE GetPixelIndex(long x,long y); + BYTE GetPixelGray(long x, long y); + void SetPixelColor(long x,long y,RGBQUAD c, bool bSetAlpha = false); + void SetPixelColor(long x,long y,COLORREF cr); + void SetPixelIndex(long x,long y,BYTE i); + void DrawLine(int StartX, int EndX, int StartY, int EndY, RGBQUAD color, bool bSetAlpha=false); + void DrawLine(int StartX, int EndX, int StartY, int EndY, COLORREF cr); + void BlendPixelColor(long x,long y,RGBQUAD c, float blend, bool bSetAlpha = false); + bool Jitter(long radius=2); // eMule: Moved from DSP functions +//@} + +protected: +/** \addtogroup Protected */ //@{ + BYTE BlindGetPixelIndex(const long x,const long y); + RGBQUAD BlindGetPixelColor(const long x,const long y, bool bGetAlpha = true); + void *BlindGetPixelPointer(const long x,const long y); + void BlindSetPixelColor(long x,long y,RGBQUAD c, bool bSetAlpha = false); + void BlindSetPixelIndex(long x,long y,BYTE i); +//@} + +public: + +#if CXIMAGE_SUPPORT_INTERPOLATION +/** \addtogroup Interpolation */ //@{ + //overflow methods: + enum OverflowMethod { + OM_COLOR=1, + OM_BACKGROUND=2, + OM_TRANSPARENT=3, + OM_WRAP=4, + OM_REPEAT=5, + OM_MIRROR=6 + }; + void OverflowCoordinates(float &x, float &y, OverflowMethod const ofMethod); + void OverflowCoordinates(long &x, long &y, OverflowMethod const ofMethod); + RGBQUAD GetPixelColorWithOverflow(long x, long y, OverflowMethod const ofMethod=OM_BACKGROUND, RGBQUAD* const rplColor=0); + //interpolation methods: + enum InterpolationMethod { + IM_NEAREST_NEIGHBOUR=1, + IM_BILINEAR =2, + IM_BSPLINE =3, + IM_BICUBIC =4, + IM_BICUBIC2 =5, + IM_LANCZOS =6, + IM_BOX =7, + IM_HERMITE =8, + IM_HAMMING =9, + IM_SINC =10, + IM_BLACKMAN =11, + IM_BESSEL =12, + IM_GAUSSIAN =13, + IM_QUADRATIC =14, + IM_MITCHELL =15, + IM_CATROM =16, + IM_HANNING =17, + IM_POWER =18 + }; + RGBQUAD GetPixelColorInterpolated(float x,float y, InterpolationMethod const inMethod=IM_BILINEAR, OverflowMethod const ofMethod=OM_BACKGROUND, RGBQUAD* const rplColor=0); + RGBQUAD GetAreaColorInterpolated(float const xc, float const yc, float const w, float const h, InterpolationMethod const inMethod, OverflowMethod const ofMethod=OM_BACKGROUND, RGBQUAD* const rplColor=0); +//@} + +protected: +/** \addtogroup Protected */ //@{ + void AddAveragingCont(RGBQUAD const &color, float const surf, float &rr, float &gg, float &bb, float &aa); +//@} + +/** \addtogroup Kernels */ //@{ +public: + static float KernelBSpline(const float x); + static float KernelLinear(const float t); + static float KernelCubic(const float t); + static float KernelGeneralizedCubic(const float t, const float a=-1); + static float KernelLanczosSinc(const float t, const float r = 3); + static float KernelBox(const float x); + static float KernelHermite(const float x); + static float KernelHamming(const float x); + static float KernelSinc(const float x); + static float KernelBlackman(const float x); + static float KernelBessel_J1(const float x); + static float KernelBessel_P1(const float x); + static float KernelBessel_Q1(const float x); + static float KernelBessel_Order1(float x); + static float KernelBessel(const float x); + static float KernelGaussian(const float x); + static float KernelQuadratic(const float x); + static float KernelMitchell(const float x); + static float KernelCatrom(const float x); + static float KernelHanning(const float x); + static float KernelPower(const float x, const float a = 2); +//@} +#endif //CXIMAGE_SUPPORT_INTERPOLATION + +/** \addtogroup Painting */ //@{ +#if CXIMAGE_SUPPORT_WINDOWS + long Blt(HDC pDC, long x=0, long y=0); + HBITMAP MakeBitmap(HDC hdc = NULL, bool bTransparency = false); + HICON MakeIcon(HDC hdc = NULL); + HANDLE CopyToHandle(); + bool CreateFromHANDLE(HANDLE hMem); //Windows objects (clipboard) + bool CreateFromHBITMAP(HBITMAP hbmp, HPALETTE hpal=0); //Windows resource + bool CreateFromHICON(HICON hico); + long Draw(HDC hdc, long x=0, long y=0, long cx = -1, long cy = -1, RECT* pClipRect = 0, bool bSmooth = false); + long Draw(HDC hdc, const RECT& rect, RECT* pClipRect=NULL, bool bSmooth = false); + long Stretch(HDC hdc, long xoffset, long yoffset, long xsize, long ysize, DWORD dwRop = SRCCOPY); + long Stretch(HDC hdc, const RECT& rect, DWORD dwRop = SRCCOPY); + long Tile(HDC hdc, RECT *rc); + long Draw2(HDC hdc, long x=0, long y=0, long cx = -1, long cy = -1); + long Draw2(HDC hdc, const RECT& rect); + //long DrawString(HDC hdc, long x, long y, const char* text, RGBQUAD color, const char* font, long lSize=0, long lWeight=400, BYTE bItalic=0, BYTE bUnderline=0, bool bSetAlpha=false); + long DrawString(HDC hdc, long x, long y, const TCHAR* text, RGBQUAD color, const TCHAR* font, long lSize=0, long lWeight=400, BYTE bItalic=0, BYTE bUnderline=0, bool bSetAlpha=false); + // extensions + long DrawStringEx(HDC hdc, long x, long y, CXTEXTINFO *pTextType, bool bSetAlpha=false ); + void InitTextInfo( CXTEXTINFO *txt ); +#endif //CXIMAGE_SUPPORT_WINDOWS +//@} + + // file operations +#if CXIMAGE_SUPPORT_DECODE +/** \addtogroup Decode */ //@{ +#ifdef WIN32 + //bool Load(LPCWSTR filename, DWORD imagetype=0); + bool LoadResource(HRSRC hRes, DWORD imagetype, HMODULE hModule=NULL); +#endif + // For UNICODE support: char -> TCHAR + bool Load(const TCHAR* filename, DWORD imagetype=0); + //bool Load(const char * filename, DWORD imagetype=0); + bool Decode(FILE * hFile, DWORD imagetype); + bool Decode(CxFile * hFile, DWORD imagetype); + bool Decode(BYTE * buffer, DWORD size, DWORD imagetype); + + bool CheckFormat(CxFile * hFile, DWORD imagetype = 0); + bool CheckFormat(BYTE * buffer, DWORD size, DWORD imagetype = 0); +//@} +#endif //CXIMAGE_SUPPORT_DECODE + +#if CXIMAGE_SUPPORT_ENCODE +protected: +/** \addtogroup Protected */ //@{ + bool EncodeSafeCheck(CxFile *hFile); +//@} + +public: +/** \addtogroup Encode */ //@{ +#ifdef WIN32 + //bool Save(LPCWSTR filename, DWORD imagetype=0); +#endif + // For UNICODE support: char -> TCHAR + bool Save(const TCHAR* filename, DWORD imagetype); + //bool Save(const char * filename, DWORD imagetype=0); + bool Encode(FILE * hFile, DWORD imagetype); + bool Encode(CxFile * hFile, DWORD imagetype); + bool Encode(CxFile * hFile, CxImage ** pImages, int pagecount, DWORD imagetype); + bool Encode(FILE *hFile, CxImage ** pImages, int pagecount, DWORD imagetype); + bool Encode(BYTE * &buffer, long &size, DWORD imagetype); + + bool Encode2RGBA(CxFile *hFile, bool bFlipY = false); + bool Encode2RGBA(BYTE * &buffer, long &size, bool bFlipY = false); +//@} +#endif //CXIMAGE_SUPPORT_ENCODE + +/** \addtogroup Attributes */ //@{ + //misc. + bool IsValid() const; + bool IsEnabled() const; + void Enable(bool enable=true); + + // frame operations + long GetNumFrames() const; + long GetFrame() const; + void SetFrame(long nFrame); +//@} + +#if CXIMAGE_SUPPORT_BASICTRANSFORMATIONS +/** \addtogroup BasicTransformations */ //@{ + bool GrayScale(); + bool Flip(bool bFlipSelection = false, bool bFlipAlpha = true); + bool Mirror(bool bMirrorSelection = false, bool bMirrorAlpha = true); + bool Negative(); + bool RotateLeft(CxImage* iDst = NULL); + bool RotateRight(CxImage* iDst = NULL); +//@} +#endif //CXIMAGE_SUPPORT_BASICTRANSFORMATIONS + +#if CXIMAGE_SUPPORT_TRANSFORMATION +/** \addtogroup Transformations */ //@{ + // image operations + bool Rotate(float angle, CxImage* iDst = NULL); + bool Rotate2(float angle, CxImage *iDst = NULL, InterpolationMethod inMethod=IM_BILINEAR, + OverflowMethod ofMethod=OM_BACKGROUND, RGBQUAD *replColor=0, + bool const optimizeRightAngles=true, bool const bKeepOriginalSize=false); + bool Rotate180(CxImage* iDst = NULL); + bool Resample(long newx, long newy, int mode = 1, CxImage* iDst = NULL); + bool Resample2(long newx, long newy, InterpolationMethod const inMethod=IM_BICUBIC2, + OverflowMethod const ofMethod=OM_REPEAT, CxImage* const iDst = NULL, + bool const disableAveraging=false); + bool DecreaseBpp(DWORD nbit, bool errordiffusion, RGBQUAD* ppal = 0, DWORD clrimportant = 0); + bool IncreaseBpp(DWORD nbit); + bool Dither(long method = 0); + bool Crop(long left, long top, long right, long bottom, CxImage* iDst = NULL); + bool Crop(const RECT& rect, CxImage* iDst = NULL); + bool CropRotatedRectangle( long topx, long topy, long width, long height, float angle, CxImage* iDst = NULL); + bool Skew(float xgain, float ygain, long xpivot=0, long ypivot=0, bool bEnableInterpolation = false); + bool Expand(long left, long top, long right, long bottom, RGBQUAD canvascolor, CxImage* iDst = 0); + bool Expand(long newx, long newy, RGBQUAD canvascolor, CxImage* iDst = 0); + bool Thumbnail(long newx, long newy, RGBQUAD canvascolor, CxImage* iDst = 0); + bool CircleTransform(int type,long rmax=0,float Koeff=1.0f); + bool RedEyeRemove(float strength = 0.8f); + bool QIShrink(long newx, long newy, CxImage* const iDst = NULL, bool bChangeBpp = false); + +//@} +#endif //CXIMAGE_SUPPORT_TRANSFORMATION + +#if CXIMAGE_SUPPORT_DSP +/** \addtogroup DSP */ //@{ + bool Contour(); + bool HistogramStretch(long method = 0, double threshold = 0); + bool HistogramEqualize(); + bool HistogramNormalize(); + bool HistogramRoot(); + bool HistogramLog(); + long Histogram(long* red, long* green = 0, long* blue = 0, long* gray = 0, long colorspace = 0); + bool Repair(float radius = 0.25f, long niterations = 1, long colorspace = 0); + bool Combine(CxImage* r,CxImage* g,CxImage* b,CxImage* a, long colorspace = 0); + bool FFT2(CxImage* srcReal, CxImage* srcImag, CxImage* dstReal, CxImage* dstImag, long direction = 1, bool bForceFFT = true, bool bMagnitude = true); + bool Noise(long level); + bool Median(long Ksize=3); + bool Gamma(float gamma); + bool GammaRGB(float gammaR, float gammaG, float gammaB); + bool ShiftRGB(long r, long g, long b); + bool Threshold(BYTE level); + bool Threshold(CxImage* pThresholdMask); + bool Threshold2(BYTE level, bool bDirection, RGBQUAD nBkgndColor, bool bSetAlpha = false); + bool Colorize(BYTE hue, BYTE sat, float blend = 1.0f); + bool Light(long brightness, long contrast = 0); + float Mean(); + bool Filter(long* kernel, long Ksize, long Kfactor, long Koffset); + bool Erode(long Ksize=2); + bool Dilate(long Ksize=2); + bool Edge(long Ksize=2); + void HuePalette(float correction=1); + enum ImageOpType { OpAdd, OpAnd, OpXor, OpOr, OpMask, OpSrcCopy, OpDstCopy, OpSub, OpSrcBlend, OpScreen, OpAvg }; + void Mix(CxImage & imgsrc2, ImageOpType op, long lXOffset = 0, long lYOffset = 0, bool bMixAlpha = false); + void MixFrom(CxImage & imagesrc2, long lXOffset, long lYOffset); + bool UnsharpMask(float radius = 5.0f, float amount = 0.5f, int threshold = 0); + bool Lut(BYTE* pLut); + bool Lut(BYTE* pLutR, BYTE* pLutG, BYTE* pLutB, BYTE* pLutA = 0); + bool GaussianBlur(float radius = 1.0f, CxImage* iDst = 0); + bool TextBlur(BYTE threshold = 100, BYTE decay = 2, BYTE max_depth = 5, bool bBlurHorizontal = true, bool bBlurVertical = true, CxImage* iDst = 0); + bool SelectiveBlur(float radius = 1.0f, BYTE threshold = 25, CxImage* iDst = 0); + bool Solarize(BYTE level = 128, bool bLinkedChannels = true); + bool FloodFill(const long xStart, const long yStart, const RGBQUAD cFillColor, const BYTE tolerance = 0, + BYTE nOpacity = 255, const bool bSelectFilledArea = false, const BYTE nSelectionLevel = 255); + bool Saturate(const long saturation, const long colorspace = 1); + bool ConvertColorSpace(const long dstColorSpace, const long srcColorSpace); + int OptimalThreshold(long method = 0, RECT * pBox = 0, CxImage* pContrastMask = 0); + bool AdaptiveThreshold(long method = 0, long nBoxSize = 64, CxImage* pContrastMask = 0, long nBias = 0, float fGlobalLocalBalance = 0.5f); + +//@} + +protected: +/** \addtogroup Protected */ //@{ + bool IsPowerof2(long x); + bool FFT(int dir,int m,double *x,double *y); + bool DFT(int dir,long m,double *x1,double *y1,double *x2,double *y2); + bool RepairChannel(CxImage *ch, float radius); + // + int gen_convolve_matrix (float radius, float **cmatrix_p); + float* gen_lookup_table (float *cmatrix, int cmatrix_length); + void blur_line (float *ctable, float *cmatrix, int cmatrix_length, BYTE* cur_col, BYTE* dest_col, int y, long bytes); + void blur_text (BYTE threshold, BYTE decay, BYTE max_depth, CxImage* iSrc, CxImage* iDst, BYTE bytes); +//@} + +public: +/** \addtogroup ColorSpace */ //@{ + bool SplitRGB(CxImage* r,CxImage* g,CxImage* b); + bool SplitYUV(CxImage* y,CxImage* u,CxImage* v); + bool SplitHSL(CxImage* h,CxImage* s,CxImage* l); + bool SplitYIQ(CxImage* y,CxImage* i,CxImage* q); + bool SplitXYZ(CxImage* x,CxImage* y,CxImage* z); + bool SplitCMYK(CxImage* c,CxImage* m,CxImage* y,CxImage* k); + static RGBQUAD HSLtoRGB(COLORREF cHSLColor); + static RGBQUAD RGBtoHSL(RGBQUAD lRGBColor); + static RGBQUAD HSLtoRGB(RGBQUAD lHSLColor); + static RGBQUAD YUVtoRGB(RGBQUAD lYUVColor); + static RGBQUAD RGBtoYUV(RGBQUAD lRGBColor); + static RGBQUAD YIQtoRGB(RGBQUAD lYIQColor); + static RGBQUAD RGBtoYIQ(RGBQUAD lRGBColor); + static RGBQUAD XYZtoRGB(RGBQUAD lXYZColor); + static RGBQUAD RGBtoXYZ(RGBQUAD lRGBColor); +#endif //CXIMAGE_SUPPORT_DSP + static RGBQUAD RGBtoRGBQUAD(COLORREF cr); + static COLORREF RGBQUADtoRGB (RGBQUAD c); +//@} + +#if CXIMAGE_SUPPORT_SELECTION +/** \addtogroup Selection */ //@{ + bool SelectionClear(BYTE level = 0); + bool SelectionCreate(); + bool SelectionDelete(); + bool SelectionInvert(); + bool SelectionMirror(); + bool SelectionFlip(); + bool SelectionAddRect(RECT r, BYTE level = 255); + bool SelectionAddEllipse(RECT r, BYTE level = 255); + bool SelectionAddPolygon(POINT *points, long npoints, BYTE level = 255); + bool SelectionAddColor(RGBQUAD c, BYTE level = 255); + bool SelectionAddPixel(long x, long y, BYTE level = 255); + bool SelectionCopy(CxImage &from); + bool SelectionIsInside(long x, long y); + bool SelectionIsValid(); + void SelectionGetBox(RECT& r); + bool SelectionToHRGN(HRGN& region); + bool SelectionSplit(CxImage *dest); + BYTE SelectionGet(const long x,const long y); + bool SelectionSet(CxImage &from); + void SelectionRebuildBox(); + BYTE* SelectionGetPointer(const long x = 0,const long y = 0); +//@} + +protected: +/** \addtogroup Protected */ //@{ + bool BlindSelectionIsInside(long x, long y); + BYTE BlindSelectionGet(const long x,const long y); + void SelectionSet(const long x,const long y,const BYTE level); +//@} + +public: + +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA +/** \addtogroup Alpha */ //@{ + void AlphaClear(); + bool AlphaCreate(); + void AlphaDelete(); + void AlphaInvert(); + bool AlphaMirror(); + bool AlphaFlip(); + bool AlphaCopy(CxImage &from); + bool AlphaSplit(CxImage *dest); + void AlphaStrip(); + void AlphaSet(BYTE level); + bool AlphaSet(CxImage &from); + void AlphaSet(const long x,const long y,const BYTE level); + BYTE AlphaGet(const long x,const long y); + BYTE AlphaGetMax() const; + void AlphaSetMax(BYTE nAlphaMax); + bool AlphaIsValid(); + BYTE* AlphaGetPointer(const long x = 0,const long y = 0); + bool AlphaFromTransparency(); + + void AlphaPaletteClear(); + void AlphaPaletteEnable(bool enable=true); + bool AlphaPaletteIsEnabled(); + bool AlphaPaletteIsValid(); + bool AlphaPaletteSplit(CxImage *dest); +//@} + +protected: +/** \addtogroup Protected */ //@{ + BYTE BlindAlphaGet(const long x,const long y); +//@} +#endif //CXIMAGE_SUPPORT_ALPHA + +public: +#if CXIMAGE_SUPPORT_LAYERS +/** \addtogroup Layers */ //@{ + bool LayerCreate(long position = -1); + bool LayerDelete(long position = -1); + void LayerDeleteAll(); + CxImage* GetLayer(long position); + CxImage* GetParent() const; + long GetNumLayers() const; + long LayerDrawAll(HDC hdc, long x=0, long y=0, long cx = -1, long cy = -1, RECT* pClipRect = 0, bool bSmooth = false); + long LayerDrawAll(HDC hdc, const RECT& rect, RECT* pClipRect=NULL, bool bSmooth = false); +//@} +#endif //CXIMAGE_SUPPORT_LAYERS + +protected: +/** \addtogroup Protected */ //@{ + void Startup(DWORD imagetype = 0); + void CopyInfo(const CxImage &src); + void Ghost(const CxImage *src); + void RGBtoBGR(BYTE *buffer, int length); + static float HueToRGB(float n1,float n2, float hue); + void Bitfield2RGB(BYTE *src, DWORD redmask, DWORD greenmask, DWORD bluemask, BYTE bpp); + static int CompareColors(const void *elem1, const void *elem2); + short ntohs(const short word); + long ntohl(const long dword); + void bihtoh(BITMAPINFOHEADER* bih); + + void* pDib; //contains the header, the palette, the pixels + BITMAPINFOHEADER head; //standard header + CXIMAGEINFO info; //extended information + BYTE* pSelection; //selected region + BYTE* pAlpha; //alpha channel + CxImage** ppLayers; //generic layers + CxImage** ppFrames; +//@} +}; + +//////////////////////////////////////////////////////////////////////////// +#endif // !defined(__CXIMAGE_H) diff --git a/CxImage/ximainfo.cpp b/CxImage/ximainfo.cpp new file mode 100644 index 00000000..73a1453a --- /dev/null +++ b/CxImage/ximainfo.cpp @@ -0,0 +1,919 @@ +// ximainfo.cpp : main attributes +/* 03/10/2004 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 6.0.0 02/Feb/2008 + */ + +#include "ximage.h" + +//////////////////////////////////////////////////////////////////////////////// +/** + * \return the color used for transparency, and/or for background color + */ +RGBQUAD CxImage::GetTransColor() +{ + if (head.biBitCount<24 && info.nBkgndIndex>=0) return GetPaletteColor((BYTE)info.nBkgndIndex); + return info.nBkgndColor; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Gets the index used for transparency. Returns -1 for no transparancy. + */ +long CxImage::GetTransIndex() const +{ + return info.nBkgndIndex; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the index used for transparency with 1, 4 and 8 bpp images. Set to -1 to remove the effect. + */ +void CxImage::SetTransIndex(long idx) +{ + if (idx<(long)head.biClrUsed) + info.nBkgndIndex = idx; + else + info.nBkgndIndex = 0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the color used for transparency with 24 bpp images. + * You must call SetTransIndex(0) to enable the effect, SetTransIndex(-1) to disable it. + */ +void CxImage::SetTransColor(RGBQUAD rgb) +{ + rgb.rgbReserved=0; + info.nBkgndColor = rgb; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::IsTransparent() const +{ + return info.nBkgndIndex>=0; // +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns true if the image has 256 colors or less. + */ +bool CxImage::IsIndexed() const +{ + return head.biClrUsed!=0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return 1 = indexed, 2 = RGB, 4 = RGBA + */ +BYTE CxImage::GetColorType() +{ + BYTE b = (BYTE)((head.biBitCount>8) ? 2 /*COLORTYPE_COLOR*/ : 1 /*COLORTYPE_PALETTE*/); +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) b = 4 /*COLORTYPE_ALPHA*/; +#endif //CXIMAGE_SUPPORT_ALPHA + return b; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return Resolution for TIFF, JPEG, PNG and BMP formats. + */ +long CxImage::GetXDPI() const +{ + return info.xDPI; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return Resolution for TIFF, JPEG, PNG and BMP formats. + */ +long CxImage::GetYDPI() const +{ + return info.yDPI; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Set resolution for TIFF, JPEG, PNG and BMP formats. + */ +void CxImage::SetXDPI(long dpi) +{ + if (dpi<=0) dpi = CXIMAGE_DEFAULT_DPI; + info.xDPI = dpi; + head.biXPelsPerMeter = (long) floor(dpi * 10000.0 / 254.0 + 0.5); + if (pDib) ((BITMAPINFOHEADER*)pDib)->biXPelsPerMeter = head.biXPelsPerMeter; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Set resolution for TIFF, JPEG, PNG and BMP formats. + */ +void CxImage::SetYDPI(long dpi) +{ + if (dpi<=0) dpi = CXIMAGE_DEFAULT_DPI; + info.yDPI = dpi; + head.biYPelsPerMeter = (long) floor(dpi * 10000.0 / 254.0 + 0.5); + if (pDib) ((BITMAPINFOHEADER*)pDib)->biYPelsPerMeter = head.biYPelsPerMeter; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetFlags + */ +DWORD CxImage::GetFlags() const +{ + return info.dwFlags; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Image flags, for future use + * \param flags + * - 0x??00000 = reserved for 16 bit, CMYK, multilayer + * - 0x00??0000 = blend modes + * - 0x0000???? = layer id or user flags + * + * \param bLockReservedFlags protects the "reserved" and "blend modes" flags + */ +void CxImage::SetFlags(DWORD flags, bool bLockReservedFlags) +{ + if (bLockReservedFlags) info.dwFlags = flags & 0x0000ffff; + else info.dwFlags = flags; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetCodecOption + */ +DWORD CxImage::GetCodecOption(DWORD imagetype) +{ + imagetype = GetTypeIndexFromId(imagetype); + if (imagetype==0){ + imagetype = GetTypeIndexFromId(GetType()); + } + return info.dwCodecOpt[imagetype]; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Encode option for GIF, TIF and JPG. + * - GIF : 0 = LZW (default), 1 = none, 2 = RLE. + * - TIF : 0 = automatic (default), or a valid compression code as defined in "tiff.h" (COMPRESSION_NONE = 1, COMPRESSION_CCITTRLE = 2, ...) + * - JPG : valid values stored in enum CODEC_OPTION ( ENCODE_BASELINE = 0x01, ENCODE_PROGRESSIVE = 0x10, ...) + * - RAW : valid values stored in enum CODEC_OPTION ( DECODE_QUALITY_LIN = 0x00, DECODE_QUALITY_VNG = 0x01, ...) + * + * \return true if everything is ok + */ +bool CxImage::SetCodecOption(DWORD opt, DWORD imagetype) +{ + imagetype = GetTypeIndexFromId(imagetype); + if (imagetype==0){ + imagetype = GetTypeIndexFromId(GetType()); + } + info.dwCodecOpt[imagetype] = opt; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return internal hDib object.. + */ +void* CxImage::GetDIB() const +{ + return pDib; +} +//////////////////////////////////////////////////////////////////////////////// +DWORD CxImage::GetHeight() const +{ + return head.biHeight; +} +//////////////////////////////////////////////////////////////////////////////// +DWORD CxImage::GetWidth() const +{ + return head.biWidth; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return DWORD aligned width of the image. + */ +DWORD CxImage::GetEffWidth() const +{ + return info.dwEffWidth; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return 2, 16, 256; 0 for RGB images. + */ +DWORD CxImage::GetNumColors() const +{ + return head.biClrUsed; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return: 1, 4, 8, 24. + */ +WORD CxImage::GetBpp() const +{ + return head.biBitCount; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return original image format + * \sa ENUM_CXIMAGE_FORMATS. + */ +DWORD CxImage::GetType() const +{ + return info.dwType; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * change image format identifier + * \sa ENUM_CXIMAGE_FORMATS. + */ +bool CxImage::SetType(DWORD type) +{ + switch (type){ +#if CXIMAGE_SUPPORT_BMP + case CXIMAGE_FORMAT_BMP: +#endif +#if CXIMAGE_SUPPORT_GIF + case CXIMAGE_FORMAT_GIF: +#endif +#if CXIMAGE_SUPPORT_JPG + case CXIMAGE_FORMAT_JPG: +#endif +#if CXIMAGE_SUPPORT_PNG + case CXIMAGE_FORMAT_PNG: +#endif +#if CXIMAGE_SUPPORT_MNG + case CXIMAGE_FORMAT_MNG: +#endif +#if CXIMAGE_SUPPORT_ICO + case CXIMAGE_FORMAT_ICO: +#endif +#if CXIMAGE_SUPPORT_TIF + case CXIMAGE_FORMAT_TIF: +#endif +#if CXIMAGE_SUPPORT_TGA + case CXIMAGE_FORMAT_TGA: +#endif +#if CXIMAGE_SUPPORT_PCX + case CXIMAGE_FORMAT_PCX: +#endif +#if CXIMAGE_SUPPORT_WBMP + case CXIMAGE_FORMAT_WBMP: +#endif +#if CXIMAGE_SUPPORT_WMF + case CXIMAGE_FORMAT_WMF: +#endif +#if CXIMAGE_SUPPORT_JBG + case CXIMAGE_FORMAT_JBG: +#endif +#if CXIMAGE_SUPPORT_JP2 + case CXIMAGE_FORMAT_JP2: +#endif +#if CXIMAGE_SUPPORT_JPC + case CXIMAGE_FORMAT_JPC: +#endif +#if CXIMAGE_SUPPORT_PGX + case CXIMAGE_FORMAT_PGX: +#endif +#if CXIMAGE_SUPPORT_PNM + case CXIMAGE_FORMAT_PNM: +#endif +#if CXIMAGE_SUPPORT_RAS + case CXIMAGE_FORMAT_RAS: +#endif +#if CXIMAGE_SUPPORT_SKA + case CXIMAGE_FORMAT_SKA: +#endif +#if CXIMAGE_SUPPORT_RAW + case CXIMAGE_FORMAT_RAW: +#endif + info.dwType = type; + return true; + } + info.dwType = CXIMAGE_FORMAT_UNKNOWN; + return false; +} +//////////////////////////////////////////////////////////////////////////////// +DWORD CxImage::GetNumTypes() +{ + return CMAX_IMAGE_FORMATS-1; +} +//////////////////////////////////////////////////////////////////////////////// +DWORD CxImage::GetTypeIdFromName(const TCHAR* ext) +{ +#if CXIMAGE_SUPPORT_BMP + if (_tcsnicmp(ext,_T("bmp"),3)==0 ) return CXIMAGE_FORMAT_BMP; +#endif +#if CXIMAGE_SUPPORT_JPG + if (_tcsnicmp(ext,_T("jpg"),3)==0 || + _tcsnicmp(ext,_T("jpe"),3)==0 || + _tcsnicmp(ext,_T("jfi"),3)==0 ) return CXIMAGE_FORMAT_JPG; +#endif +#if CXIMAGE_SUPPORT_GIF + if (_tcsnicmp(ext,_T("gif"),3)==0 ) return CXIMAGE_FORMAT_GIF; +#endif +#if CXIMAGE_SUPPORT_PNG + if (_tcsnicmp(ext,_T("png"),3)==0 ) return CXIMAGE_FORMAT_PNG; +#endif +#if CXIMAGE_SUPPORT_ICO + if (_tcsnicmp(ext,_T("ico"),3)==0 || + _tcsnicmp(ext,_T("cur"),3)==0 ) return CXIMAGE_FORMAT_ICO; +#endif +#if CXIMAGE_SUPPORT_TIF + if (_tcsnicmp(ext,_T("tif"),3)==0 ) return CXIMAGE_FORMAT_TIF; +#endif +#if CXIMAGE_SUPPORT_TGA + if (_tcsnicmp(ext,_T("tga"),3)==0 ) return CXIMAGE_FORMAT_TGA; +#endif +#if CXIMAGE_SUPPORT_PCX + if (_tcsnicmp(ext,_T("pcx"),3)==0 ) return CXIMAGE_FORMAT_PCX; +#endif +#if CXIMAGE_SUPPORT_WBMP + if (_tcsnicmp(ext,_T("wbm"),3)==0 ) return CXIMAGE_FORMAT_WBMP; +#endif +#if CXIMAGE_SUPPORT_WMF + if (_tcsnicmp(ext,_T("wmf"),3)==0 || + _tcsnicmp(ext,_T("emf"),3)==0 ) return CXIMAGE_FORMAT_WMF; +#endif +#if CXIMAGE_SUPPORT_JP2 + if (_tcsnicmp(ext,_T("jp2"),3)==0 || + _tcsnicmp(ext,_T("j2k"),3)==0 ) return CXIMAGE_FORMAT_JP2; +#endif +#if CXIMAGE_SUPPORT_JPC + if (_tcsnicmp(ext,_T("jpc"),3)==0 || + _tcsnicmp(ext,_T("j2c"),3)==0 ) return CXIMAGE_FORMAT_JPC; +#endif +#if CXIMAGE_SUPPORT_PGX + if (_tcsnicmp(ext,_T("pgx"),3)==0 ) return CXIMAGE_FORMAT_PGX; +#endif +#if CXIMAGE_SUPPORT_RAS + if (_tcsnicmp(ext,_T("ras"),3)==0 ) return CXIMAGE_FORMAT_RAS; +#endif +#if CXIMAGE_SUPPORT_PNM + if (_tcsnicmp(ext,_T("pnm"),3)==0 || + _tcsnicmp(ext,_T("pgm"),3)==0 || + _tcsnicmp(ext,_T("ppm"),3)==0 ) return CXIMAGE_FORMAT_PNM; +#endif +#if CXIMAGE_SUPPORT_JBG + if (_tcsnicmp(ext,_T("jbg"),3)==0 ) return CXIMAGE_FORMAT_JBG; +#endif +#if CXIMAGE_SUPPORT_MNG + if (_tcsnicmp(ext,_T("mng"),3)==0 || + _tcsnicmp(ext,_T("jng"),3)==0 ) return CXIMAGE_FORMAT_MNG; +#endif +#if CXIMAGE_SUPPORT_SKA + if (_tcsnicmp(ext,_T("ska"),3)==0 ) return CXIMAGE_FORMAT_SKA; +#endif +#if CXIMAGE_SUPPORT_RAW + if (_tcsnicmp(ext,_T("nef"),3)==0 || + _tcsnicmp(ext,_T("crw"),3)==0 || + _tcsnicmp(ext,_T("cr2"),3)==0 || + _tcsnicmp(ext,_T("dng"),3)==0 || + _tcsnicmp(ext,_T("arw"),3)==0 || + _tcsnicmp(ext,_T("erf"),3)==0 || + _tcsnicmp(ext,_T("3fr"),3)==0 || + _tcsnicmp(ext,_T("dcr"),3)==0 || + _tcsnicmp(ext,_T("raw"),3)==0 || + _tcsnicmp(ext,_T("x3f"),3)==0 || + _tcsnicmp(ext,_T("mef"),3)==0 || + _tcsnicmp(ext,_T("raf"),3)==0 || + _tcsnicmp(ext,_T("mrw"),3)==0 || + _tcsnicmp(ext,_T("pef"),3)==0 || + _tcsnicmp(ext,_T("sr2"),3)==0 || + _tcsnicmp(ext,_T("orf"),3)==0 ) return CXIMAGE_FORMAT_RAW; +#endif + + return CXIMAGE_FORMAT_UNKNOWN; +} +//////////////////////////////////////////////////////////////////////////////// +DWORD CxImage::GetTypeIdFromIndex(const DWORD index) +{ + DWORD n; + + n=0; if (index == n) return CXIMAGE_FORMAT_UNKNOWN; +#if CXIMAGE_SUPPORT_BMP + n++; if (index == n) return CXIMAGE_FORMAT_BMP; +#endif +#if CXIMAGE_SUPPORT_GIF + n++; if (index == n) return CXIMAGE_FORMAT_GIF; +#endif +#if CXIMAGE_SUPPORT_JPG + n++; if (index == n) return CXIMAGE_FORMAT_JPG; +#endif +#if CXIMAGE_SUPPORT_PNG + n++; if (index == n) return CXIMAGE_FORMAT_PNG; +#endif +#if CXIMAGE_SUPPORT_ICO + n++; if (index == n) return CXIMAGE_FORMAT_ICO; +#endif +#if CXIMAGE_SUPPORT_TIF + n++; if (index == n) return CXIMAGE_FORMAT_TIF; +#endif +#if CXIMAGE_SUPPORT_TGA + n++; if (index == n) return CXIMAGE_FORMAT_TGA; +#endif +#if CXIMAGE_SUPPORT_PCX + n++; if (index == n) return CXIMAGE_FORMAT_PCX; +#endif +#if CXIMAGE_SUPPORT_WBMP + n++; if (index == n) return CXIMAGE_FORMAT_WBMP; +#endif +#if CXIMAGE_SUPPORT_WMF + n++; if (index == n) return CXIMAGE_FORMAT_WMF; +#endif +#if CXIMAGE_SUPPORT_JP2 + n++; if (index == n) return CXIMAGE_FORMAT_JP2; +#endif +#if CXIMAGE_SUPPORT_JPC + n++; if (index == n) return CXIMAGE_FORMAT_JPC; +#endif +#if CXIMAGE_SUPPORT_PGX + n++; if (index == n) return CXIMAGE_FORMAT_PGX; +#endif +#if CXIMAGE_SUPPORT_PNM + n++; if (index == n) return CXIMAGE_FORMAT_PNM; +#endif +#if CXIMAGE_SUPPORT_RAS + n++; if (index == n) return CXIMAGE_FORMAT_RAS; +#endif +#if CXIMAGE_SUPPORT_JBG + n++; if (index == n) return CXIMAGE_FORMAT_JBG; +#endif +#if CXIMAGE_SUPPORT_MNG + n++; if (index == n) return CXIMAGE_FORMAT_MNG; +#endif +#if CXIMAGE_SUPPORT_SKA + n++; if (index == n) return CXIMAGE_FORMAT_SKA; +#endif +#if CXIMAGE_SUPPORT_RAW + n++; if (index == n) return CXIMAGE_FORMAT_RAW; +#endif + + return CXIMAGE_FORMAT_UNKNOWN; +} +//////////////////////////////////////////////////////////////////////////////// +DWORD CxImage::GetTypeIndexFromId(const DWORD id) +{ + DWORD n; + + n=0; if (id == CXIMAGE_FORMAT_UNKNOWN) return n; +#if CXIMAGE_SUPPORT_BMP + n++; if (id == CXIMAGE_FORMAT_BMP) return n; +#endif +#if CXIMAGE_SUPPORT_GIF + n++; if (id == CXIMAGE_FORMAT_GIF) return n; +#endif +#if CXIMAGE_SUPPORT_JPG + n++; if (id == CXIMAGE_FORMAT_JPG) return n; +#endif +#if CXIMAGE_SUPPORT_PNG + n++; if (id == CXIMAGE_FORMAT_PNG) return n; +#endif +#if CXIMAGE_SUPPORT_ICO + n++; if (id == CXIMAGE_FORMAT_ICO) return n; +#endif +#if CXIMAGE_SUPPORT_TIF + n++; if (id == CXIMAGE_FORMAT_TIF) return n; +#endif +#if CXIMAGE_SUPPORT_TGA + n++; if (id == CXIMAGE_FORMAT_TGA) return n; +#endif +#if CXIMAGE_SUPPORT_PCX + n++; if (id == CXIMAGE_FORMAT_PCX) return n; +#endif +#if CXIMAGE_SUPPORT_WBMP + n++; if (id == CXIMAGE_FORMAT_WBMP) return n; +#endif +#if CXIMAGE_SUPPORT_WMF + n++; if (id == CXIMAGE_FORMAT_WMF) return n; +#endif +#if CXIMAGE_SUPPORT_JP2 + n++; if (id == CXIMAGE_FORMAT_JP2) return n; +#endif +#if CXIMAGE_SUPPORT_JPC + n++; if (id == CXIMAGE_FORMAT_JPC) return n; +#endif +#if CXIMAGE_SUPPORT_PGX + n++; if (id == CXIMAGE_FORMAT_PGX) return n; +#endif +#if CXIMAGE_SUPPORT_PNM + n++; if (id == CXIMAGE_FORMAT_PNM) return n; +#endif +#if CXIMAGE_SUPPORT_RAS + n++; if (id == CXIMAGE_FORMAT_RAS) return n; +#endif +#if CXIMAGE_SUPPORT_JBG + n++; if (id == CXIMAGE_FORMAT_JBG) return n; +#endif +#if CXIMAGE_SUPPORT_MNG + n++; if (id == CXIMAGE_FORMAT_MNG) return n; +#endif +#if CXIMAGE_SUPPORT_SKA + n++; if (id == CXIMAGE_FORMAT_SKA) return n; +#endif +#if CXIMAGE_SUPPORT_RAW + n++; if (id == CXIMAGE_FORMAT_RAW) return n; +#endif + + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return current frame delay in milliseconds. Only for GIF and MNG formats. + */ +DWORD CxImage::GetFrameDelay() const +{ + return info.dwFrameDelay; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets current frame delay. Only for GIF format. + * \param d = delay in milliseconds + */ +void CxImage::SetFrameDelay(DWORD d) +{ + info.dwFrameDelay=d; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::GetOffset(long *x,long *y) +{ + *x=info.xOffset; + *y=info.yOffset; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::SetOffset(long x,long y) +{ + info.xOffset=x; + info.yOffset=y; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetJpegQuality, GetJpegQualityF + * \author [DP]; changes [Stefan Schürmans] + */ +BYTE CxImage::GetJpegQuality() const +{ + return (BYTE)(info.fQuality + 0.5f); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetJpegQuality, GetJpegQuality + * \author [Stefan Schürmans] + */ +float CxImage::GetJpegQualityF() const +{ + return info.fQuality; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * quality level for JPEG and JPEG2000 + * \param q: can be from 0 to 100 + * \author [DP]; changes [Stefan Schürmans] + */ +void CxImage::SetJpegQuality(BYTE q){ + info.fQuality = (float)q; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * quality level for JPEG and JPEG2000 + * necessary for JPEG2000 when quality is between 0.0 and 1.0 + * \param q: can be from 0.0 to 100.0 + * \author [Stefan Schürmans] + */ +void CxImage::SetJpegQualityF(float q){ + if (q>0) info.fQuality = q; + else info.fQuality = 0.0f; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetJpegScale + */ +BYTE CxImage::GetJpegScale() const +{ + return info.nJpegScale; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * scaling down during JPEG decoding valid numbers are 1, 2, 4, 8 + * \author [ignacio] + */ +void CxImage::SetJpegScale(BYTE q){ + info.nJpegScale = q; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Used to monitor the slow loops. + * \return value is from 0 to 100. + * \sa SetProgress + */ +long CxImage::GetProgress() const +{ + return info.nProgress; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return the escape code. + * \sa SetEscape + */ +long CxImage::GetEscape() const +{ + return info.nEscape; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Forces the value of the internal progress variable. + * \param p should be from 0 to 100. + * \sa GetProgress + */ +void CxImage::SetProgress(long p) +{ + info.nProgress = p; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Used to quit the slow loops or the codecs. + * - SetEscape(-1) before Decode forces the function to exit, right after + * the image width and height are available ( for bmp, jpg, gif, tif ) + */ +void CxImage::SetEscape(long i) +{ + info.nEscape = i; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if the image is correctly initializated. + */ +bool CxImage::IsValid() const +{ + return pDib!=0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * True if the image is enabled for painting. + */ +bool CxImage::IsEnabled() const +{ + return info.bEnabled; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Enables/disables the image. + */ +void CxImage::Enable(bool enable) +{ + info.bEnabled=enable; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * This function must be used after a Decode() / Load() call. + * Use the sequence SetFrame(-1); Load(...); GetNumFrames(); + * to get the number of images without loading the first image. + * \return the number of images in the file. + */ +long CxImage::GetNumFrames() const +{ + return info.nNumFrames; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return the current selected image (zero-based index). + */ +long CxImage::GetFrame() const +{ + return info.nFrame; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the image number that the next Decode() / Load() call will load + */ +void CxImage::SetFrame(long nFrame){ + info.nFrame=nFrame; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the method for drawing the frame related to others + * \sa GetDisposalMethod + */ +void CxImage::SetDisposalMethod(BYTE dm) +{ info.dispmeth=dm; } +//////////////////////////////////////////////////////////////////////////////// +/** + * Gets the method for drawing the frame related to others + * Values : 0 - No disposal specified. The decoder is + * not required to take any action. + * 1 - Do not dispose. The graphic is to be left + * in place. + * 2 - Restore to background color. The area used by the + * graphic must be restored to the background color. + * 3 - Restore to previous. The decoder is required to + * restore the area overwritten by the graphic with + * what was there prior to rendering the graphic. + * 4-7 - To be defined. + */ +BYTE CxImage::GetDisposalMethod() const +{ return info.dispmeth; } +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::GetRetreiveAllFrames() const +{ return info.bGetAllFrames; } +//////////////////////////////////////////////////////////////////////////////// +void CxImage::SetRetreiveAllFrames(bool flag) +{ info.bGetAllFrames = flag; } +//////////////////////////////////////////////////////////////////////////////// +CxImage * CxImage::GetFrame(long nFrame) const +{ + if ( ppFrames == NULL) return NULL; + if ( info.nNumFrames == 0) return NULL; + if ( nFrame >= info.nNumFrames ) return NULL; + if ( nFrame < 0) nFrame = info.nNumFrames - 1; + return ppFrames[nFrame]; +} +//////////////////////////////////////////////////////////////////////////////// +short CxImage::ntohs(const short word) +{ + if (info.bLittleEndianHost) return word; + return ( (word & 0xff) << 8 ) | ( (word >> 8) & 0xff ); +} +//////////////////////////////////////////////////////////////////////////////// +long CxImage::ntohl(const long dword) +{ + if (info.bLittleEndianHost) return dword; + return ((dword & 0xff) << 24 ) | ((dword & 0xff00) << 8 ) | + ((dword >> 8) & 0xff00) | ((dword >> 24) & 0xff); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::bihtoh(BITMAPINFOHEADER* bih) +{ + bih->biSize = ntohl(bih->biSize); + bih->biWidth = ntohl(bih->biWidth); + bih->biHeight = ntohl(bih->biHeight); + bih->biPlanes = ntohs(bih->biPlanes); + bih->biBitCount = ntohs(bih->biBitCount); + bih->biCompression = ntohl(bih->biCompression); + bih->biSizeImage = ntohl(bih->biSizeImage); + bih->biXPelsPerMeter = ntohl(bih->biXPelsPerMeter); + bih->biYPelsPerMeter = ntohl(bih->biYPelsPerMeter); + bih->biClrUsed = ntohl(bih->biClrUsed); + bih->biClrImportant = ntohl(bih->biClrImportant); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns the last reported error. + */ +const char* CxImage::GetLastError() +{ + return info.szLastError; +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_LAYERS +DWORD CxImage::DumpSize() +{ + DWORD n; + n = sizeof(BITMAPINFOHEADER) + sizeof(CXIMAGEINFO) + GetSize(); + + if (pAlpha){ + n += 1 + head.biWidth * head.biHeight; + } else n++; + + if (pSelection){ + n += 1 + head.biWidth * head.biHeight; + } else n++; + + if (ppLayers){ + for (long m=0; mDumpSize(); + } + } + } else n++; + + if (ppFrames){ + for (long m=0; mDumpSize(); + } + } + } else n++; + + return n; +} +//////////////////////////////////////////////////////////////////////////////// +DWORD CxImage::Dump(BYTE * dst) +{ + if (!dst) return 0; + + memcpy(dst,&head,sizeof(BITMAPINFOHEADER)); + dst += sizeof(BITMAPINFOHEADER); + + memcpy(dst,&info,sizeof(CXIMAGEINFO)); + dst += sizeof(CXIMAGEINFO); + + memcpy(dst,pDib,GetSize()); + dst += GetSize(); + + if (pAlpha){ + memset(dst++, 1, 1); + memcpy(dst,pAlpha,head.biWidth * head.biHeight); + dst += head.biWidth * head.biHeight; + } else { + memset(dst++, 0, 1); + } + + if (pSelection){ + memset(dst++, 1, 1); + memcpy(dst,pSelection,head.biWidth * head.biHeight); + dst += head.biWidth * head.biHeight; + } else { + memset(dst++, 0, 1); + } + + if (ppLayers){ + memset(dst++, 1, 1); + for (long m=0; mDump(dst); + } + } + } else { + memset(dst++, 0, 1); + } + + if (ppFrames){ + memset(dst++, 1, 1); + for (long m=0; mDump(dst); + } + } + } else { + memset(dst++, 0, 1); + } + + return DumpSize(); +} +//////////////////////////////////////////////////////////////////////////////// +DWORD CxImage::UnDump(const BYTE * src) +{ + if (!src) + return 0; + if (!Destroy()) + return 0; + if (!DestroyFrames()) + return 0; + + DWORD n = 0; + + memcpy(&head,src,sizeof(BITMAPINFOHEADER)); + n += sizeof(BITMAPINFOHEADER); + + memcpy(&info,&src[n],sizeof(CXIMAGEINFO)); + n += sizeof(CXIMAGEINFO); + + if (!Create(head.biWidth, head.biHeight, head.biBitCount, info.dwType)) + return 0; + + memcpy(pDib,&src[n],GetSize()); + n += GetSize(); + + if (src[n++]){ + if (AlphaCreate()){ + memcpy(pAlpha, &src[n], head.biWidth * head.biHeight); + } + n += head.biWidth * head.biHeight; + } + + if (src[n++]){ + RECT box = info.rSelectionBox; + if (SelectionCreate()){ + info.rSelectionBox = box; + memcpy(pSelection, &src[n], head.biWidth * head.biHeight); + } + n += head.biWidth * head.biHeight; + } + + if (src[n++]){ + ppLayers = new CxImage*[info.nNumLayers]; + for (long m=0; mUnDump(&src[n]); + } + } + + if (src[n++]){ + ppFrames = new CxImage*[info.nNumFrames]; + for (long m=0; mUnDump(&src[n]); + } + } + + return n; +} +#endif +//////////////////////////////////////////////////////////////////////////////// +/** + * \return A.BBCCCDDDD + * - A = main version + * - BB = main revision + * - CCC = minor revision (letter) + * - DDDD = experimental revision + */ +const float CxImage::GetVersionNumber() +{ + return 6.000000015f; +} +//////////////////////////////////////////////////////////////////////////////// +const TCHAR* CxImage::GetVersion() +{ + static const TCHAR CxImageVersion[] = _T("CxImage 6.0.0"); + return (CxImageVersion); +} +//////////////////////////////////////////////////////////////////////////////// diff --git a/CxImage/ximaint.cpp b/CxImage/ximaint.cpp new file mode 100644 index 00000000..989d76cf --- /dev/null +++ b/CxImage/ximaint.cpp @@ -0,0 +1,1056 @@ +// xImaInt.cpp : interpolation functions +/* 02/2004 - Branko Brevensek + * CxImage version 6.0.0 02/Feb/2008 - Davide Pizzolato - www.xdp.it + */ + +#include "ximage.h" +#include "ximath.h" + +#if CXIMAGE_SUPPORT_INTERPOLATION + +//////////////////////////////////////////////////////////////////////////////// +/** + * Recalculates coordinates according to specified overflow method. + * If pixel (x,y) lies within image, nothing changes. + * + * \param x, y - coordinates of pixel + * \param ofMethod - overflow method + * + * \return x, y - new coordinates (pixel (x,y) now lies inside image) + * + * \author ***bd*** 2.2004 + */ +void CxImage::OverflowCoordinates(long &x, long &y, OverflowMethod const ofMethod) +{ + if (IsInside(x,y)) return; //if pixel is within bounds, no change + switch (ofMethod) { + case OM_REPEAT: + //clip coordinates + x=max(x,0); x=min(x, head.biWidth-1); + y=max(y,0); y=min(y, head.biHeight-1); + break; + case OM_WRAP: + //wrap coordinates + x = x % head.biWidth; + y = y % head.biHeight; + if (x<0) x = head.biWidth + x; + if (y<0) y = head.biHeight + y; + break; + case OM_MIRROR: + //mirror pixels near border + if (x<0) x=((-x) % head.biWidth); + else if (x>=head.biWidth) x=head.biWidth-(x % head.biWidth + 1); + if (y<0) y=((-y) % head.biHeight); + else if (y>=head.biHeight) y=head.biHeight-(y % head.biHeight + 1); + break; + default: + return; + }//switch +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * See OverflowCoordinates for integer version + * \author ***bd*** 2.2004 + */ +void CxImage::OverflowCoordinates(float &x, float &y, OverflowMethod const ofMethod) +{ + if (x>=0 && x=0 && y=head.biWidth) x=head.biWidth-((float)fmod(x, (float) head.biWidth) + 1); + if (y<0) y=(float)fmod(-y, (float) head.biHeight); + else if (y>=head.biHeight) y=head.biHeight-((float)fmod(y, (float) head.biHeight) + 1); + break; + default: + return; + }//switch +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Method return pixel color. Different methods are implemented for out of bounds pixels. + * If an image has alpha channel, alpha value is returned in .RGBReserved. + * + * \param x,y : pixel coordinates + * \param ofMethod : out-of-bounds method: + * - OF_WRAP - wrap over to pixels on other side of the image + * - OF_REPEAT - repeat last pixel on the edge + * - OF_COLOR - return input value of color + * - OF_BACKGROUND - return background color (if not set, return input color) + * - OF_TRANSPARENT - return transparent pixel + * + * \param rplColor : input color (returned for out-of-bound coordinates in OF_COLOR mode and if other mode is not applicable) + * + * \return color : color of pixel + * \author ***bd*** 2.2004 + */ +RGBQUAD CxImage::GetPixelColorWithOverflow(long x, long y, OverflowMethod const ofMethod, RGBQUAD* const rplColor) +{ + RGBQUAD color; //color to return + if ((!IsInside(x,y)) || pDib==NULL) { //is pixel within bouns?: + //pixel is out of bounds or no DIB + if (rplColor!=NULL) + color=*rplColor; + else { + color.rgbRed=color.rgbGreen=color.rgbBlue=255; color.rgbReserved=0; //default replacement colour: white transparent + }//if + if (pDib==NULL) return color; + //pixel is out of bounds: + switch (ofMethod) { + case OM_TRANSPARENT: +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + //alpha transparency is supported and image has alpha layer + color.rgbReserved=0; + } else { +#endif //CXIMAGE_SUPPORT_ALPHA + //no alpha transparency + if (GetTransIndex()>=0) { + color=GetTransColor(); //single color transparency enabled (return transparent color) + }//if +#if CXIMAGE_SUPPORT_ALPHA + }//if +#endif //CXIMAGE_SUPPORT_ALPHA + return color; + case OM_BACKGROUND: + //return background color (if it exists, otherwise input value) + if (info.nBkgndIndex >= 0) { + if (head.biBitCount<24) color = GetPaletteColor((BYTE)info.nBkgndIndex); + else color = info.nBkgndColor; + }//if + return color; + case OM_REPEAT: + case OM_WRAP: + case OM_MIRROR: + OverflowCoordinates(x,y,ofMethod); + break; + default: + //simply return replacement color (OM_COLOR and others) + return color; + }//switch + }//if + //just return specified pixel (it's within bounds) + return BlindGetPixelColor(x,y); +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * This method reconstructs image according to chosen interpolation method and then returns pixel (x,y). + * (x,y) can lie between actual image pixels. If (x,y) lies outside of image, method returns value + * according to overflow method. + * This method is very useful for geometrical image transformations, where destination pixel + * can often assume color value lying between source pixels. + * + * \param (x,y) - coordinates of pixel to return + * GPCI method recreates "analogue" image back from digital data, so x and y + * are float values and color value of point (1.1,1) will generally not be same + * as (1,1). Center of first pixel is at (0,0) and center of pixel right to it is (1,0). + * (0.5,0) is half way between these two pixels. + * \param inMethod - interpolation (reconstruction) method (kernel) to use: + * - IM_NEAREST_NEIGHBOUR - returns colour of nearest lying pixel (causes stairy look of + * processed images) + * - IM_BILINEAR - interpolates colour from four neighbouring pixels (softens image a bit) + * - IM_BICUBIC - interpolates from 16 neighbouring pixels (can produce "halo" artifacts) + * - IM_BICUBIC2 - interpolates from 16 neighbouring pixels (perhaps a bit less halo artifacts + than IM_BICUBIC) + * - IM_BSPLINE - interpolates from 16 neighbouring pixels (softens image, washes colours) + * (As far as I know, image should be prefiltered for this method to give + * good results... some other time :) ) + * This method uses bicubic interpolation kernel from CXImage 5.99a and older + * versions. + * - IM_LANCZOS - interpolates from 12*12 pixels (slow, ringing artifacts) + * + * \param ofMethod - overflow method (see comments at GetPixelColorWithOverflow) + * \param rplColor - pointer to color used for out of borders pixels in OM_COLOR mode + * (and other modes if colour can't calculated in a specified way) + * + * \return interpolated color value (including interpolated alpha value, if image has alpha layer) + * + * \author ***bd*** 2.2004 + */ +RGBQUAD CxImage::GetPixelColorInterpolated( + float x,float y, + InterpolationMethod const inMethod, + OverflowMethod const ofMethod, + RGBQUAD* const rplColor) +{ + //calculate nearest pixel + int xi=(int)(x); if (x<0) xi--; //these replace (incredibly slow) floor (Visual c++ 2003, AMD Athlon) + int yi=(int)(y); if (y<0) yi--; + RGBQUAD color; //calculated colour + + switch (inMethod) { + case IM_NEAREST_NEIGHBOUR: + return GetPixelColorWithOverflow((long)(x+0.5f), (long)(y+0.5f), ofMethod, rplColor); + default: { + //IM_BILINEAR: bilinear interpolation + if (xi<-1 || xi>=head.biWidth || yi<-1 || yi>=head.biHeight) { //all 4 points are outside bounds?: + switch (ofMethod) { + case OM_COLOR: case OM_TRANSPARENT: case OM_BACKGROUND: + //we don't need to interpolate anything with all points outside in this case + return GetPixelColorWithOverflow(-999, -999, ofMethod, rplColor); + default: + //recalculate coordinates and use faster method later on + OverflowCoordinates(x,y,ofMethod); + xi=(int)(x); if (x<0) xi--; //x and/or y have changed ... recalculate xi and yi + yi=(int)(y); if (y<0) yi--; + }//switch + }//if + //get four neighbouring pixels + if ((xi+1)=0 && (yi+1)=0 && head.biClrUsed==0) { + //all pixels are inside RGB24 image... optimize reading (and use fixed point arithmetic) + WORD wt1=(WORD)((x-xi)*256.0f), wt2=(WORD)((y-yi)*256.0f); + WORD wd=wt1*wt2>>8; + WORD wb=wt1-wd; + WORD wc=wt2-wd; + WORD wa=256-wt1-wc; + WORD wrr,wgg,wbb; + BYTE *pxptr=(BYTE*)info.pImage+yi*info.dwEffWidth+xi*3; + wbb=wa*(*pxptr++); wgg=wa*(*pxptr++); wrr=wa*(*pxptr++); + wbb+=wb*(*pxptr++); wgg+=wb*(*pxptr++); wrr+=wb*(*pxptr); + pxptr+=(info.dwEffWidth-5); //move to next row + wbb+=wc*(*pxptr++); wgg+=wc*(*pxptr++); wrr+=wc*(*pxptr++); + wbb+=wd*(*pxptr++); wgg+=wd*(*pxptr++); wrr+=wd*(*pxptr); + color.rgbRed=(BYTE) (wrr>>8); color.rgbGreen=(BYTE) (wgg>>8); color.rgbBlue=(BYTE) (wbb>>8); +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha) { + WORD waa; + //image has alpha layer... we have to do the same for alpha data + pxptr=AlphaGetPointer(xi,yi); //pointer to first byte + waa=wa*(*pxptr++); waa+=wb*(*pxptr); //first two pixels + pxptr+=(head.biWidth-1); //move to next row + waa+=wc*(*pxptr++); waa+=wd*(*pxptr); //and second row pixels + color.rgbReserved=(BYTE) (waa>>8); + } else +#endif + { //Alpha not supported or no alpha at all + color.rgbReserved = 0; + } + return color; + } else { + //default (slower) way to get pixels (not RGB24 or some pixels out of borders) + float t1=x-xi, t2=y-yi; + float d=t1*t2; + float b=t1-d; + float c=t2-d; + float a=1-t1-c; + RGBQUAD rgb11,rgb21,rgb12,rgb22; + rgb11=GetPixelColorWithOverflow(xi, yi, ofMethod, rplColor); + rgb21=GetPixelColorWithOverflow(xi+1, yi, ofMethod, rplColor); + rgb12=GetPixelColorWithOverflow(xi, yi+1, ofMethod, rplColor); + rgb22=GetPixelColorWithOverflow(xi+1, yi+1, ofMethod, rplColor); + //calculate linear interpolation + color.rgbRed=(BYTE) (a*rgb11.rgbRed+b*rgb21.rgbRed+c*rgb12.rgbRed+d*rgb22.rgbRed); + color.rgbGreen=(BYTE) (a*rgb11.rgbGreen+b*rgb21.rgbGreen+c*rgb12.rgbGreen+d*rgb22.rgbGreen); + color.rgbBlue=(BYTE) (a*rgb11.rgbBlue+b*rgb21.rgbBlue+c*rgb12.rgbBlue+d*rgb22.rgbBlue); +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) + color.rgbReserved=(BYTE) (a*rgb11.rgbReserved+b*rgb21.rgbReserved+c*rgb12.rgbReserved+d*rgb22.rgbReserved); + else +#endif + { //Alpha not supported or no alpha at all + color.rgbReserved = 0; + } + return color; + }//if + }//default + case IM_BICUBIC: + case IM_BICUBIC2: + case IM_BSPLINE: + case IM_BOX: + case IM_HERMITE: + case IM_HAMMING: + case IM_SINC: + case IM_BLACKMAN: + case IM_BESSEL: + case IM_GAUSSIAN: + case IM_QUADRATIC: + case IM_MITCHELL: + case IM_CATROM: + case IM_HANNING: + case IM_POWER: + //bicubic interpolation(s) + if (((xi+2)<0) || ((xi-1)>=head.biWidth) || ((yi+2)<0) || ((yi-1)>=head.biHeight)) { //all points are outside bounds?: + switch (ofMethod) { + case OM_COLOR: case OM_TRANSPARENT: case OM_BACKGROUND: + //we don't need to interpolate anything with all points outside in this case + return GetPixelColorWithOverflow(-999, -999, ofMethod, rplColor); + break; + default: + //recalculate coordinates and use faster method later on + OverflowCoordinates(x,y,ofMethod); + xi=(int)(x); if (x<0) xi--; //x and/or y have changed ... recalculate xi and yi + yi=(int)(y); if (y<0) yi--; + }//switch + }//if + + //some variables needed from here on + int xii,yii; //x any y integer indexes for loops + float kernel, kernelyc; //kernel cache + float kernelx[12], kernely[4]; //precalculated kernel values + float rr,gg,bb,aa; //accumulated color values + //calculate multiplication factors for all pixels + int i; + switch (inMethod) { + case IM_BICUBIC: + for (i=0; i<4; i++) { + kernelx[i]=KernelCubic((float)(xi+i-1-x)); + kernely[i]=KernelCubic((float)(yi+i-1-y)); + }//for i + break; + case IM_BICUBIC2: + for (i=0; i<4; i++) { + kernelx[i]=KernelGeneralizedCubic((float)(xi+i-1-x), -0.5); + kernely[i]=KernelGeneralizedCubic((float)(yi+i-1-y), -0.5); + }//for i + break; + case IM_BSPLINE: + for (i=0; i<4; i++) { + kernelx[i]=KernelBSpline((float)(xi+i-1-x)); + kernely[i]=KernelBSpline((float)(yi+i-1-y)); + }//for i + break; + case IM_BOX: + for (i=0; i<4; i++) { + kernelx[i]=KernelBox((float)(xi+i-1-x)); + kernely[i]=KernelBox((float)(yi+i-1-y)); + }//for i + break; + case IM_HERMITE: + for (i=0; i<4; i++) { + kernelx[i]=KernelHermite((float)(xi+i-1-x)); + kernely[i]=KernelHermite((float)(yi+i-1-y)); + }//for i + break; + case IM_HAMMING: + for (i=0; i<4; i++) { + kernelx[i]=KernelHamming((float)(xi+i-1-x)); + kernely[i]=KernelHamming((float)(yi+i-1-y)); + }//for i + break; + case IM_SINC: + for (i=0; i<4; i++) { + kernelx[i]=KernelSinc((float)(xi+i-1-x)); + kernely[i]=KernelSinc((float)(yi+i-1-y)); + }//for i + break; + case IM_BLACKMAN: + for (i=0; i<4; i++) { + kernelx[i]=KernelBlackman((float)(xi+i-1-x)); + kernely[i]=KernelBlackman((float)(yi+i-1-y)); + }//for i + break; + case IM_BESSEL: + for (i=0; i<4; i++) { + kernelx[i]=KernelBessel((float)(xi+i-1-x)); + kernely[i]=KernelBessel((float)(yi+i-1-y)); + }//for i + break; + case IM_GAUSSIAN: + for (i=0; i<4; i++) { + kernelx[i]=KernelGaussian((float)(xi+i-1-x)); + kernely[i]=KernelGaussian((float)(yi+i-1-y)); + }//for i + break; + case IM_QUADRATIC: + for (i=0; i<4; i++) { + kernelx[i]=KernelQuadratic((float)(xi+i-1-x)); + kernely[i]=KernelQuadratic((float)(yi+i-1-y)); + }//for i + break; + case IM_MITCHELL: + for (i=0; i<4; i++) { + kernelx[i]=KernelMitchell((float)(xi+i-1-x)); + kernely[i]=KernelMitchell((float)(yi+i-1-y)); + }//for i + break; + case IM_CATROM: + for (i=0; i<4; i++) { + kernelx[i]=KernelCatrom((float)(xi+i-1-x)); + kernely[i]=KernelCatrom((float)(yi+i-1-y)); + }//for i + break; + case IM_HANNING: + for (i=0; i<4; i++) { + kernelx[i]=KernelHanning((float)(xi+i-1-x)); + kernely[i]=KernelHanning((float)(yi+i-1-y)); + }//for i + break; + case IM_POWER: + for (i=0; i<4; i++) { + kernelx[i]=KernelPower((float)(xi+i-1-x)); + kernely[i]=KernelPower((float)(yi+i-1-y)); + }//for i + break; + }//switch + rr=gg=bb=aa=0; + if (((xi+2)=1 && ((yi+2)=1) && !IsIndexed()) { + //optimized interpolation (faster pixel reads) for RGB24 images with all pixels inside bounds + BYTE *pxptr, *pxptra; + for (yii=yi-1; yii255) rr=255; if (rr<0) rr=0; color.rgbRed=(BYTE) rr; + if (gg>255) gg=255; if (gg<0) gg=0; color.rgbGreen=(BYTE) gg; + if (bb>255) bb=255; if (bb<0) bb=0; color.rgbBlue=(BYTE) bb; +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + if (aa>255) aa=255; if (aa<0) aa=0; color.rgbReserved=(BYTE) aa; + } else +#endif + { //Alpha not supported or no alpha at all + color.rgbReserved = 0; + } + return color; + case IM_LANCZOS: + //lanczos window (16*16) sinc interpolation + if (((xi+6)<0) || ((xi-5)>=head.biWidth) || ((yi+6)<0) || ((yi-5)>=head.biHeight)) { + //all points are outside bounds + switch (ofMethod) { + case OM_COLOR: case OM_TRANSPARENT: case OM_BACKGROUND: + //we don't need to interpolate anything with all points outside in this case + return GetPixelColorWithOverflow(-999, -999, ofMethod, rplColor); + break; + default: + //recalculate coordinates and use faster method later on + OverflowCoordinates(x,y,ofMethod); + xi=(int)(x); if (x<0) xi--; //x and/or y have changed ... recalculate xi and yi + yi=(int)(y); if (y<0) yi--; + }//switch + }//if + + for (xii=xi-5; xii=0) && ((yi+6)=0) && !IsIndexed()) { + //optimized interpolation (faster pixel reads) for RGB24 images with all pixels inside bounds + BYTE *pxptr, *pxptra; + for (yii=yi-5; yii255) rr=255; if (rr<0) rr=0; color.rgbRed=(BYTE) rr; + if (gg>255) gg=255; if (gg<0) gg=0; color.rgbGreen=(BYTE) gg; + if (bb>255) bb=255; if (bb<0) bb=0; color.rgbBlue=(BYTE) bb; +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + if (aa>255) aa=255; if (aa<0) aa=0; color.rgbReserved=(BYTE) aa; + } else +#endif + { //Alpha not supported or no alpha at all + color.rgbReserved = 0; + } + return color; + }//switch +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Helper function for GetAreaColorInterpolated. + * Adds 'surf' portion of image pixel with color 'color' to (rr,gg,bb,aa). + */ +void CxImage::AddAveragingCont(RGBQUAD const &color, float const surf, float &rr, float &gg, float &bb, float &aa) +{ + rr+=color.rgbRed*surf; + gg+=color.rgbGreen*surf; + bb+=color.rgbBlue*surf; +#if CXIMAGE_SUPPORT_ALPHA + aa+=color.rgbReserved*surf; +#endif +} +//////////////////////////////////////////////////////////////////////////////// +/** + * This method is similar to GetPixelColorInterpolated, but this method also properly handles + * subsampling. + * If you need to sample original image with interval of more than 1 pixel (as when shrinking an image), + * you should use this method instead of GetPixelColorInterpolated or aliasing will occur. + * When area width and height are both less than pixel, this method gets pixel color by interpolating + * color of frame center with selected (inMethod) interpolation by calling GetPixelColorInterpolated. + * If width and height are more than 1, method calculates color by averaging color of pixels within area. + * Interpolation method is not used in this case. Pixel color is interpolated by averaging instead. + * If only one of both is more than 1, method uses combination of interpolation and averaging. + * Chosen interpolation method is used, but since it is averaged later on, there is little difference + * between IM_BILINEAR (perhaps best for this case) and better methods. IM_NEAREST_NEIGHBOUR again + * leads to aliasing artifacts. + * This method is a bit slower than GetPixelColorInterpolated and when aliasing is not a problem, you should + * simply use the later. + * + * \param xc, yc - center of (rectangular) area + * \param w, h - width and height of area + * \param inMethod - interpolation method that is used, when interpolation is used (see above) + * \param ofMethod - overflow method used when retrieving individual pixel colors + * \param rplColor - replacement colour to use, in OM_COLOR + * + * \author ***bd*** 2.2004 + */ +RGBQUAD CxImage::GetAreaColorInterpolated( + float const xc, float const yc, float const w, float const h, + InterpolationMethod const inMethod, + OverflowMethod const ofMethod, + RGBQUAD* const rplColor) +{ + RGBQUAD color; //calculated colour + + if (h<=1 && w<=1) { + //both width and height are less than one... we will use interpolation of center point + return GetPixelColorInterpolated(xc, yc, inMethod, ofMethod, rplColor); + } else { + //area is wider and/or taller than one pixel: + CxRect2 area(xc-w/2.0f, yc-h/2.0f, xc+w/2.0f, yc+h/2.0f); //area + int xi1=(int)(area.botLeft.x+0.49999999f); //low x + int yi1=(int)(area.botLeft.y+0.49999999f); //low y + + + int xi2=(int)(area.topRight.x+0.5f); //top x + int yi2=(int)(area.topRight.y+0.5f); //top y (for loops) + + float rr,gg,bb,aa; //red, green, blue and alpha components + rr=gg=bb=aa=0; + int x,y; //loop counters + float s=0; //surface of all pixels + float cps; //surface of current crosssection + if (h>1 && w>1) { + //width and height of area are greater than one pixel, so we can employ "ordinary" averaging + CxRect2 intBL, intTR; //bottom left and top right intersection + intBL=area.CrossSection(CxRect2(((float)xi1)-0.5f, ((float)yi1)-0.5f, ((float)xi1)+0.5f, ((float)yi1)+0.5f)); + intTR=area.CrossSection(CxRect2(((float)xi2)-0.5f, ((float)yi2)-0.5f, ((float)xi2)+0.5f, ((float)yi2)+0.5f)); + float wBL, wTR, hBL, hTR; + wBL=intBL.Width(); //width of bottom left pixel-area intersection + hBL=intBL.Height(); //height of bottom left... + wTR=intTR.Width(); //width of top right... + hTR=intTR.Height(); //height of top right... + + AddAveragingCont(GetPixelColorWithOverflow(xi1,yi1,ofMethod,rplColor), wBL*hBL, rr, gg, bb, aa); //bottom left pixel + AddAveragingCont(GetPixelColorWithOverflow(xi2,yi1,ofMethod,rplColor), wTR*hBL, rr, gg, bb, aa); //bottom right pixel + AddAveragingCont(GetPixelColorWithOverflow(xi1,yi2,ofMethod,rplColor), wBL*hTR, rr, gg, bb, aa); //top left pixel + AddAveragingCont(GetPixelColorWithOverflow(xi2,yi2,ofMethod,rplColor), wTR*hTR, rr, gg, bb, aa); //top right pixel + //bottom and top row + for (x=xi1+1; x255) rr=255; if (rr<0) rr=0; color.rgbRed=(BYTE) rr; + if (gg>255) gg=255; if (gg<0) gg=0; color.rgbGreen=(BYTE) gg; + if (bb>255) bb=255; if (bb<0) bb=0; color.rgbBlue=(BYTE) bb; +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + if (aa>255) aa=255; if (aa<0) aa=0; color.rgbReserved=(BYTE) aa; + }//if +#endif + }//if + return color; +} + +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBSpline(const float x) +{ + if (x>2.0f) return 0.0f; + // thanks to Kristian Kratzenstein + float a, b, c, d; + float xm1 = x - 1.0f; // Was calculatet anyway cause the "if((x-1.0f) < 0)" + float xp1 = x + 1.0f; + float xp2 = x + 2.0f; + + if ((xp2) <= 0.0f) a = 0.0f; else a = xp2*xp2*xp2; // Only float, not float -> double -> float + if ((xp1) <= 0.0f) b = 0.0f; else b = xp1*xp1*xp1; + if (x <= 0) c = 0.0f; else c = x*x*x; + if ((xm1) <= 0.0f) d = 0.0f; else d = xm1*xm1*xm1; + + return (0.16666666666666666667f * (a - (4.0f * b) + (6.0f * c) - (4.0f * d))); + + /* equivalent + if (x < -2.0) + return(0.0f); + if (x < -1.0) + return((2.0f+x)*(2.0f+x)*(2.0f+x)*0.16666666666666666667f); + if (x < 0.0) + return((4.0f+x*x*(-6.0f-3.0f*x))*0.16666666666666666667f); + if (x < 1.0) + return((4.0f+x*x*(-6.0f+3.0f*x))*0.16666666666666666667f); + if (x < 2.0) + return((2.0f-x)*(2.0f-x)*(2.0f-x)*0.16666666666666666667f); + return(0.0f); + */ +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Bilinear interpolation kernel: + \verbatim + / + | 1-t , if 0 <= t <= 1 + h(t) = | t+1 , if -1 <= t < 0 + | 0 , otherwise + \ + \endverbatim + * ***bd*** 2.2004 + */ +float CxImage::KernelLinear(const float t) +{ +// if (0<=t && t<=1) return 1-t; +// if (-1<=t && t<0) return 1+t; +// return 0; + + // + if (t < -1.0f) + return 0.0f; + if (t < 0.0f) + return 1.0f+t; + if (t < 1.0f) + return 1.0f-t; + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Bicubic interpolation kernel (a=-1): + \verbatim + / + | 1-2|t|**2+|t|**3 , if |t| < 1 + h(t) = | 4-8|t|+5|t|**2-|t|**3 , if 1<=|t|<2 + | 0 , otherwise + \ + \endverbatim + * ***bd*** 2.2004 + */ +float CxImage::KernelCubic(const float t) +{ + float abs_t = (float)fabs(t); + float abs_t_sq = abs_t * abs_t; + if (abs_t<1) return 1-2*abs_t_sq+abs_t_sq*abs_t; + if (abs_t<2) return 4 - 8*abs_t +5*abs_t_sq - abs_t_sq*abs_t; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Bicubic kernel (for a=-1 it is the same as BicubicKernel): + \verbatim + / + | (a+2)|t|**3 - (a+3)|t|**2 + 1 , |t| <= 1 + h(t) = | a|t|**3 - 5a|t|**2 + 8a|t| - 4a , 1 < |t| <= 2 + | 0 , otherwise + \ + \endverbatim + * Often used values for a are -1 and -1/2. + */ +float CxImage::KernelGeneralizedCubic(const float t, const float a) +{ + float abs_t = (float)fabs(t); + float abs_t_sq = abs_t * abs_t; + if (abs_t<1) return (a+2)*abs_t_sq*abs_t - (a+3)*abs_t_sq + 1; + if (abs_t<2) return a*abs_t_sq*abs_t - 5*a*abs_t_sq + 8*a*abs_t - 4*a; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Lanczos windowed sinc interpolation kernel with radius r. + \verbatim + / + h(t) = | sinc(t)*sinc(t/r) , if |t| r) return 0; + if (t==0) return 1; + float pit=PI*t; + float pitd=pit/r; + return (float)((sin(pit)/pit) * (sin(pitd)/pitd)); +} + +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBox(const float x) +{ + if (x < -0.5f) + return 0.0f; + if (x < 0.5f) + return 1.0f; + return 0.0f; +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelHermite(const float x) +{ + if (x < -1.0f) + return 0.0f; + if (x < 0.0f) + return (-2.0f*x-3.0f)*x*x+1.0f; + if (x < 1.0f) + return (2.0f*x-3.0f)*x*x+1.0f; + return 0.0f; +// if (fabs(x)>1) return 0.0f; +// return(0.5f+0.5f*(float)cos(PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelHanning(const float x) +{ + if (fabs(x)>1) return 0.0f; + return (0.5f+0.5f*(float)cos(PI*x))*((float)sin(PI*x)/(PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelHamming(const float x) +{ + if (x < -1.0f) + return 0.0f; + if (x < 0.0f) + return 0.92f*(-2.0f*x-3.0f)*x*x+1.0f; + if (x < 1.0f) + return 0.92f*(2.0f*x-3.0f)*x*x+1.0f; + return 0.0f; +// if (fabs(x)>1) return 0.0f; +// return(0.54f+0.46f*(float)cos(PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelSinc(const float x) +{ + if (x == 0.0) + return(1.0); + return((float)sin(PI*x)/(PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBlackman(const float x) +{ + //if (fabs(x)>1) return 0.0f; + return (0.42f+0.5f*(float)cos(PI*x)+0.08f*(float)cos(2.0f*PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel_J1(const float x) +{ + double p, q; + + register long i; + + static const double + Pone[] = + { + 0.581199354001606143928050809e+21, + -0.6672106568924916298020941484e+20, + 0.2316433580634002297931815435e+19, + -0.3588817569910106050743641413e+17, + 0.2908795263834775409737601689e+15, + -0.1322983480332126453125473247e+13, + 0.3413234182301700539091292655e+10, + -0.4695753530642995859767162166e+7, + 0.270112271089232341485679099e+4 + }, + Qone[] = + { + 0.11623987080032122878585294e+22, + 0.1185770712190320999837113348e+20, + 0.6092061398917521746105196863e+17, + 0.2081661221307607351240184229e+15, + 0.5243710262167649715406728642e+12, + 0.1013863514358673989967045588e+10, + 0.1501793594998585505921097578e+7, + 0.1606931573481487801970916749e+4, + 0.1e+1 + }; + + p = Pone[8]; + q = Qone[8]; + for (i=7; i >= 0; i--) + { + p = p*x*x+Pone[i]; + q = q*x*x+Qone[i]; + } + return (float)(p/q); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel_P1(const float x) +{ + double p, q; + + register long i; + + static const double + Pone[] = + { + 0.352246649133679798341724373e+5, + 0.62758845247161281269005675e+5, + 0.313539631109159574238669888e+5, + 0.49854832060594338434500455e+4, + 0.2111529182853962382105718e+3, + 0.12571716929145341558495e+1 + }, + Qone[] = + { + 0.352246649133679798068390431e+5, + 0.626943469593560511888833731e+5, + 0.312404063819041039923015703e+5, + 0.4930396490181088979386097e+4, + 0.2030775189134759322293574e+3, + 0.1e+1 + }; + + p = Pone[5]; + q = Qone[5]; + for (i=4; i >= 0; i--) + { + p = p*(8.0/x)*(8.0/x)+Pone[i]; + q = q*(8.0/x)*(8.0/x)+Qone[i]; + } + return (float)(p/q); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel_Q1(const float x) +{ + double p, q; + + register long i; + + static const double + Pone[] = + { + 0.3511751914303552822533318e+3, + 0.7210391804904475039280863e+3, + 0.4259873011654442389886993e+3, + 0.831898957673850827325226e+2, + 0.45681716295512267064405e+1, + 0.3532840052740123642735e-1 + }, + Qone[] = + { + 0.74917374171809127714519505e+4, + 0.154141773392650970499848051e+5, + 0.91522317015169922705904727e+4, + 0.18111867005523513506724158e+4, + 0.1038187585462133728776636e+3, + 0.1e+1 + }; + + p = Pone[5]; + q = Qone[5]; + for (i=4; i >= 0; i--) + { + p = p*(8.0/x)*(8.0/x)+Pone[i]; + q = q*(8.0/x)*(8.0/x)+Qone[i]; + } + return (float)(p/q); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel_Order1(float x) +{ + float p, q; + + if (x == 0.0) + return (0.0f); + p = x; + if (x < 0.0) + x=(-x); + if (x < 8.0) + return(p*KernelBessel_J1(x)); + q = (float)sqrt(2.0f/(PI*x))*(float)(KernelBessel_P1(x)*(1.0f/sqrt(2.0f)*(sin(x)-cos(x)))-8.0f/x*KernelBessel_Q1(x)* + (-1.0f/sqrt(2.0f)*(sin(x)+cos(x)))); + if (p < 0.0f) + q = (-q); + return (q); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel(const float x) +{ + if (x == 0.0f) + return(PI/4.0f); + return(KernelBessel_Order1(PI*x)/(2.0f*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelGaussian(const float x) +{ + return (float)(exp(-2.0f*x*x)*0.79788456080287f/*sqrt(2.0f/PI)*/); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelQuadratic(const float x) +{ + if (x < -1.5f) + return(0.0f); + if (x < -0.5f) + return(0.5f*(x+1.5f)*(x+1.5f)); + if (x < 0.5f) + return(0.75f-x*x); + if (x < 1.5f) + return(0.5f*(x-1.5f)*(x-1.5f)); + return(0.0f); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelMitchell(const float x) +{ +#define KM_B (1.0f/3.0f) +#define KM_C (1.0f/3.0f) +#define KM_P0 (( 6.0f - 2.0f * KM_B ) / 6.0f) +#define KM_P2 ((-18.0f + 12.0f * KM_B + 6.0f * KM_C) / 6.0f) +#define KM_P3 (( 12.0f - 9.0f * KM_B - 6.0f * KM_C) / 6.0f) +#define KM_Q0 (( 8.0f * KM_B + 24.0f * KM_C) / 6.0f) +#define KM_Q1 ((-12.0f * KM_B - 48.0f * KM_C) / 6.0f) +#define KM_Q2 (( 6.0f * KM_B + 30.0f * KM_C) / 6.0f) +#define KM_Q3 (( -1.0f * KM_B - 6.0f * KM_C) / 6.0f) + + if (x < -2.0) + return(0.0f); + if (x < -1.0) + return(KM_Q0-x*(KM_Q1-x*(KM_Q2-x*KM_Q3))); + if (x < 0.0f) + return(KM_P0+x*x*(KM_P2-x*KM_P3)); + if (x < 1.0f) + return(KM_P0+x*x*(KM_P2+x*KM_P3)); + if (x < 2.0f) + return(KM_Q0+x*(KM_Q1+x*(KM_Q2+x*KM_Q3))); + return(0.0f); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelCatrom(const float x) +{ + if (x < -2.0) + return(0.0f); + if (x < -1.0) + return(0.5f*(4.0f+x*(8.0f+x*(5.0f+x)))); + if (x < 0.0) + return(0.5f*(2.0f+x*x*(-5.0f-3.0f*x))); + if (x < 1.0) + return(0.5f*(2.0f+x*x*(-5.0f+3.0f*x))); + if (x < 2.0) + return(0.5f*(4.0f+x*(-8.0f+x*(5.0f-x)))); + return(0.0f); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelPower(const float x, const float a) +{ + if (fabs(x)>1) return 0.0f; + return (1.0f - (float)fabs(pow(x,a))); +} +//////////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/CxImage/ximaiter.h b/CxImage/ximaiter.h new file mode 100644 index 00000000..9788919b --- /dev/null +++ b/CxImage/ximaiter.h @@ -0,0 +1,253 @@ +/* + * File: ImaIter.h + * Purpose: Declaration of the Platform Independent Image Base Class + * Author: Alejandro Aguilar Sierra + * Created: 1995 + * Copyright: (c) 1995, Alejandro Aguilar Sierra + * + * 07/08/2001 Davide Pizzolato - www.xdp.it + * - removed slow loops + * - added safe checks + * + * Permission is given by the author to freely redistribute and include + * this code in any program as long as this credit is given where due. + * + * COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY + * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES + * THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE + * OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED + * CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT + * THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY + * SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL + * PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER + * THIS DISCLAIMER. + * + * Use at your own risk! + * ========================================================== + */ + +#if !defined(__ImaIter_h) +#define __ImaIter_h + +#include "ximage.h" +#include "ximadef.h" + +class CImageIterator +{ +friend class CxImage; +protected: + int Itx, Ity; // Counters + int Stepx, Stepy; + BYTE* IterImage; // Image pointer + CxImage *ima; +public: + // Constructors + CImageIterator ( void ); + CImageIterator ( CxImage *image ); + operator CxImage* (); + + // Iterators + BOOL ItOK (); + void Reset (); + void Upset (); + void SetRow(BYTE *buf, int n); + void GetRow(BYTE *buf, int n); + BYTE GetByte( ) { return IterImage[Itx]; } + void SetByte(BYTE b) { IterImage[Itx] = b; } + BYTE* GetRow(void); + BYTE* GetRow(int n); + BOOL NextRow(); + BOOL PrevRow(); + BOOL NextByte(); + BOOL PrevByte(); + + void SetSteps(int x, int y=0) { Stepx = x; Stepy = y; } + void GetSteps(int *x, int *y) { *x = Stepx; *y = Stepy; } + BOOL NextStep(); + BOOL PrevStep(); + + void SetY(int y); /* AD - for interlace */ + int GetY() {return Ity;} + BOOL GetCol(BYTE* pCol, DWORD x); + BOOL SetCol(BYTE* pCol, DWORD x); +}; + +///////////////////////////////////////////////////////////////////// +inline +CImageIterator::CImageIterator(void) +{ + ima = 0; + IterImage = 0; + Itx = Ity = 0; + Stepx = Stepy = 0; +} +///////////////////////////////////////////////////////////////////// +inline +CImageIterator::CImageIterator(CxImage *imageImpl): ima(imageImpl) +{ + if (ima) IterImage = ima->GetBits(); + Itx = Ity = 0; + Stepx = Stepy = 0; +} +///////////////////////////////////////////////////////////////////// +inline +CImageIterator::operator CxImage* () +{ + return ima; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::ItOK () +{ + if (ima) return ima->IsInside(Itx, Ity); + else return FALSE; +} +///////////////////////////////////////////////////////////////////// +inline void CImageIterator::Reset() +{ + if (ima) IterImage = ima->GetBits(); + else IterImage=0; + Itx = Ity = 0; +} +///////////////////////////////////////////////////////////////////// +inline void CImageIterator::Upset() +{ + Itx = 0; + Ity = ima->GetHeight()-1; + IterImage = ima->GetBits() + ima->GetEffWidth()*(ima->GetHeight()-1); +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::NextRow() +{ + if (++Ity >= (int)ima->GetHeight()) return 0; + IterImage += ima->GetEffWidth(); + return 1; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::PrevRow() +{ + if (--Ity < 0) return 0; + IterImage -= ima->GetEffWidth(); + return 1; +} +/* AD - for interlace */ +inline void CImageIterator::SetY(int y) +{ + if ((y < 0) || (y > (int)ima->GetHeight())) return; + Ity = y; + IterImage = ima->GetBits() + ima->GetEffWidth()*y; +} +///////////////////////////////////////////////////////////////////// +inline void CImageIterator::SetRow(BYTE *buf, int n) +{ + if (n<0) n = (int)ima->GetEffWidth(); + else n = min(n,(int)ima->GetEffWidth()); + + if ((IterImage!=NULL)&&(buf!=NULL)&&(n>0)) memcpy(IterImage,buf,n); +} +///////////////////////////////////////////////////////////////////// +inline void CImageIterator::GetRow(BYTE *buf, int n) +{ + if ((IterImage!=NULL)&&(buf!=NULL)&&(n>0)) + memcpy(buf,IterImage,min(n,(int)ima->GetEffWidth())); +} +///////////////////////////////////////////////////////////////////// +inline BYTE* CImageIterator::GetRow() +{ + return IterImage; +} +///////////////////////////////////////////////////////////////////// +inline BYTE* CImageIterator::GetRow(int n) +{ + SetY(n); + return IterImage; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::NextByte() +{ + if (++Itx < (int)ima->GetEffWidth()) return 1; + else + if (++Ity < (int)ima->GetHeight()){ + IterImage += ima->GetEffWidth(); + Itx = 0; + return 1; + } else + return 0; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::PrevByte() +{ + if (--Itx >= 0) return 1; + else + if (--Ity >= 0){ + IterImage -= ima->GetEffWidth(); + Itx = 0; + return 1; + } else + return 0; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::NextStep() +{ + Itx += Stepx; + if (Itx < (int)ima->GetEffWidth()) return 1; + else { + Ity += Stepy; + if (Ity < (int)ima->GetHeight()){ + IterImage += ima->GetEffWidth(); + Itx = 0; + return 1; + } else + return 0; + } +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::PrevStep() +{ + Itx -= Stepx; + if (Itx >= 0) return 1; + else { + Ity -= Stepy; + if (Ity >= 0 && Ity < (int)ima->GetHeight()) { + IterImage -= ima->GetEffWidth(); + Itx = 0; + return 1; + } else + return 0; + } +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::GetCol(BYTE* pCol, DWORD x) +{ + if ((pCol==0)||(ima->GetBpp()<8)||(x>=ima->GetWidth())) + return 0; + DWORD h = ima->GetHeight(); + //DWORD line = ima->GetEffWidth(); + BYTE bytes = (BYTE)(ima->GetBpp()>>3); + BYTE* pSrc; + for (DWORD y=0;yGetBits(y) + x*bytes; + for (BYTE w=0;wGetBpp()<8)||(x>=ima->GetWidth())) + return 0; + DWORD h = ima->GetHeight(); + //DWORD line = ima->GetEffWidth(); + BYTE bytes = (BYTE)(ima->GetBpp()>>3); + BYTE* pSrc; + for (DWORD y=0;yGetBits(y) + x*bytes; + for (BYTE w=0;w>8); + c.rgbGreen = (BYTE)((c.rgbGreen * a + a1 * info.nBkgndColor.rgbGreen)>>8); + c.rgbRed = (BYTE)((c.rgbRed * a + a1 * info.nBkgndColor.rgbRed)>>8); + BlindSetPixelColor(x,y,c); + } + } + AlphaDelete(); + } else { + CxImage tmp(head.biWidth,head.biHeight,24); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return; + } + + for(long y=0; y>8); + c.rgbGreen = (BYTE)((c.rgbGreen * a + a1 * info.nBkgndColor.rgbGreen)>>8); + c.rgbRed = (BYTE)((c.rgbRed * a + a1 * info.nBkgndColor.rgbRed)>>8); + tmp.BlindSetPixelColor(x,y,c); + } + } + Transfer(tmp); + } + return; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::AlphaFlip() +{ + if (!pAlpha) return false; + + BYTE *buff = (BYTE*)malloc(head.biWidth); + if (!buff) return false; + + BYTE *iSrc,*iDst; + iSrc = pAlpha + (head.biHeight-1)*head.biWidth; + iDst = pAlpha; + for (long i=0; i<(head.biHeight/2); ++i) + { + memcpy(buff, iSrc, head.biWidth); + memcpy(iSrc, iDst, head.biWidth); + memcpy(iDst, buff, head.biWidth); + iSrc-=head.biWidth; + iDst+=head.biWidth; + } + + free(buff); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::AlphaMirror() +{ + if (!pAlpha) return false; + BYTE* pAlpha2 = (BYTE*)malloc(head.biWidth * head.biHeight); + if (!pAlpha2) return false; + BYTE *iSrc,*iDst; + long wdt=head.biWidth-1; + iSrc=pAlpha + wdt; + iDst=pAlpha2; + for(long y=0; y < head.biHeight; y++){ + for(long x=0; x <= wdt; x++) + *(iDst+x)=*(iSrc-x); + iSrc+=head.biWidth; + iDst+=head.biWidth; + } + free(pAlpha); + pAlpha=pAlpha2; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Exports the alpha channel in a 8bpp grayscale image. + */ +bool CxImage::AlphaSplit(CxImage *dest) +{ + if (!pAlpha || !dest) return false; + + CxImage tmp(head.biWidth,head.biHeight,8); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + for(long y=0; yTransfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Exports the alpha palette channel in a 8bpp grayscale image. + */ +bool CxImage::AlphaPaletteSplit(CxImage *dest) +{ + if (!AlphaPaletteIsValid() || !dest) return false; + + CxImage tmp(head.biWidth,head.biHeight,8); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + for(long y=0; yTransfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Merge in the alpha layer the transparent color mask + * (previously set with SetTransColor or SetTransIndex) + */ +bool CxImage::AlphaFromTransparency() +{ + if (!IsValid() || !IsTransparent()) + return false; + + AlphaCreate(); + + for(long y=0; y=head.biWidth)||(y>=head.biHeight)) { + if (info.nBkgndIndex >= 0) return (BYTE)info.nBkgndIndex; + else return *info.pImage; + } + if (head.biBitCount==8){ + return info.pImage[y*info.dwEffWidth + x]; + } else { + BYTE pos; + BYTE iDst= info.pImage[y*info.dwEffWidth + (x*head.biBitCount >> 3)]; + if (head.biBitCount==4){ + pos = (BYTE)(4*(1-x%2)); + iDst &= (0x0F<> pos); + } else if (head.biBitCount==1){ + pos = (BYTE)(7-x%8); + iDst &= (0x01<> pos); + } + } + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +BYTE CxImage::BlindGetPixelIndex(const long x,const long y) +{ +#ifdef _DEBUG + if ((pDib==NULL) || (head.biClrUsed==0) || !IsInside(x,y)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return 0; + #endif +#endif + + if (head.biBitCount==8){ + return info.pImage[y*info.dwEffWidth + x]; + } else { + BYTE pos; + BYTE iDst= info.pImage[y*info.dwEffWidth + (x*head.biBitCount >> 3)]; + if (head.biBitCount==4){ + pos = (BYTE)(4*(1-x%2)); + iDst &= (0x0F<> pos); + } else if (head.biBitCount==1){ + pos = (BYTE)(7-x%8); + iDst &= (0x01<> pos); + } + } + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::GetPixelColor(long x,long y, bool bGetAlpha) +{ +// RGBQUAD rgb={0,0,0,0}; + RGBQUAD rgb=info.nBkgndColor; // + if ((pDib==NULL)||(x<0)||(y<0)|| + (x>=head.biWidth)||(y>=head.biHeight)){ + if (info.nBkgndIndex >= 0){ + if (head.biBitCount<24) return GetPaletteColor((BYTE)info.nBkgndIndex); + else return info.nBkgndColor; + } else if (pDib) return GetPixelColor(0,0); + return rgb; + } + + if (head.biClrUsed){ + rgb = GetPaletteColor(BlindGetPixelIndex(x,y)); + } else { + BYTE* iDst = info.pImage + y*info.dwEffWidth + x*3; + rgb.rgbBlue = *iDst++; + rgb.rgbGreen= *iDst++; + rgb.rgbRed = *iDst; + } +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha && bGetAlpha) rgb.rgbReserved = BlindAlphaGet(x,y); +#else + rgb.rgbReserved = 0; +#endif //CXIMAGE_SUPPORT_ALPHA + return rgb; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * This is (a bit) faster version of GetPixelColor. + * It tests bounds only in debug mode (_DEBUG defined). + * + * It is an error to request out-of-borders pixel with this method. + * In DEBUG mode an exception will be thrown, and data will be violated in non-DEBUG mode. + * \author ***bd*** 2.2004 + */ +RGBQUAD CxImage::BlindGetPixelColor(const long x,const long y, bool bGetAlpha) +{ + RGBQUAD rgb; +#ifdef _DEBUG + if ((pDib==NULL) || !IsInside(x,y)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + {rgb.rgbReserved = 0; return rgb;} + #endif +#endif + + if (head.biClrUsed){ + rgb = GetPaletteColor(BlindGetPixelIndex(x,y)); + } else { + BYTE* iDst = info.pImage + y*info.dwEffWidth + x*3; + rgb.rgbBlue = *iDst++; + rgb.rgbGreen= *iDst++; + rgb.rgbRed = *iDst; + rgb.rgbReserved = 0; //needed for images without alpha layer + } +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha && bGetAlpha) rgb.rgbReserved = BlindAlphaGet(x,y); +#else + rgb.rgbReserved = 0; +#endif //CXIMAGE_SUPPORT_ALPHA + return rgb; +} +//////////////////////////////////////////////////////////////////////////////// +BYTE CxImage::GetPixelGray(long x, long y) +{ + RGBQUAD color = GetPixelColor(x,y); + return (BYTE)RGB2GRAY(color.rgbRed,color.rgbGreen,color.rgbBlue); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::BlindSetPixelIndex(long x,long y,BYTE i) +{ +#ifdef _DEBUG + if ((pDib==NULL)||(head.biClrUsed==0)|| + (x<0)||(y<0)||(x>=head.biWidth)||(y>=head.biHeight)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return; + #endif +#endif + + if (head.biBitCount==8){ + info.pImage[y*info.dwEffWidth + x]=i; + return; + } else { + BYTE pos; + BYTE* iDst= info.pImage + y*info.dwEffWidth + (x*head.biBitCount >> 3); + if (head.biBitCount==4){ + pos = (BYTE)(4*(1-x%2)); + *iDst &= ~(0x0F<=head.biWidth)||(y>=head.biHeight)) return ; + + if (head.biBitCount==8){ + info.pImage[y*info.dwEffWidth + x]=i; + return; + } else { + BYTE pos; + BYTE* iDst= info.pImage + y*info.dwEffWidth + (x*head.biBitCount >> 3); + if (head.biBitCount==4){ + pos = (BYTE)(4*(1-x%2)); + *iDst &= ~(0x0F<=head.biWidth)||(y>=head.biHeight)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return; + #endif +#endif + if (head.biClrUsed) + BlindSetPixelIndex(x,y,GetNearestIndex(c)); + else { + BYTE* iDst = info.pImage + y*info.dwEffWidth + x*3; + *iDst++ = c.rgbBlue; + *iDst++ = c.rgbGreen; + *iDst = c.rgbRed; + } +#if CXIMAGE_SUPPORT_ALPHA + if (bSetAlpha) AlphaSet(x,y,c.rgbReserved); +#endif //CXIMAGE_SUPPORT_ALPHA +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::SetPixelColor(long x,long y,RGBQUAD c, bool bSetAlpha) +{ + if ((pDib==NULL)||(x<0)||(y<0)|| + (x>=head.biWidth)||(y>=head.biHeight)) return; + if (head.biClrUsed) + BlindSetPixelIndex(x,y,GetNearestIndex(c)); + else { + BYTE* iDst = info.pImage + y*info.dwEffWidth + x*3; + *iDst++ = c.rgbBlue; + *iDst++ = c.rgbGreen; + *iDst = c.rgbRed; + } +#if CXIMAGE_SUPPORT_ALPHA + if (bSetAlpha) AlphaSet(x,y,c.rgbReserved); +#endif //CXIMAGE_SUPPORT_ALPHA +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Blends the current pixel color with a new color. + * \param x,y = pixel + * \param c = new color + * \param blend = can be from 0 (no effect) to 1 (full effect). + * \param bSetAlpha = if true, blends also the alpha component stored in c.rgbReserved + */ +void CxImage::BlendPixelColor(long x,long y,RGBQUAD c, float blend, bool bSetAlpha) +{ + if ((pDib==NULL)||(x<0)||(y<0)|| + (x>=head.biWidth)||(y>=head.biHeight)) return; + + int a0 = (int)(256*blend); + int a1 = 256 - a0; + + RGBQUAD c0 = BlindGetPixelColor(x,y); + c.rgbRed = (BYTE)((c.rgbRed * a0 + c0.rgbRed * a1)>>8); + c.rgbBlue = (BYTE)((c.rgbBlue * a0 + c0.rgbBlue * a1)>>8); + c.rgbGreen = (BYTE)((c.rgbGreen * a0 + c0.rgbGreen * a1)>>8); + + if (head.biClrUsed) + BlindSetPixelIndex(x,y,GetNearestIndex(c)); + else { + BYTE* iDst = info.pImage + y*info.dwEffWidth + x*3; + *iDst++ = c.rgbBlue; + *iDst++ = c.rgbGreen; + *iDst = c.rgbRed; +#if CXIMAGE_SUPPORT_ALPHA + if (bSetAlpha) AlphaSet(x,y,c.rgbReserved); +#endif //CXIMAGE_SUPPORT_ALPHA + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns the best palette index that matches a specified color. + */ +BYTE CxImage::GetNearestIndex(RGBQUAD c) +{ + if ((pDib==NULL)||(head.biClrUsed==0)) return 0; + + // check matching with the previous result + if (info.last_c_isvalid && (*(long*)&info.last_c == *(long*)&c)) return info.last_c_index; + info.last_c = c; + info.last_c_isvalid = true; + + BYTE* iDst = (BYTE*)(pDib) + sizeof(BITMAPINFOHEADER); + long distance=200000; + int i,j = 0; + long k,l; + int m = (int)(head.biClrImportant==0 ? head.biClrUsed : head.biClrImportant); + for(i=0,l=0;i100) perc=100; + for(i=0;i=0){ + if (head.biClrUsed){ + if (GetPixelIndex(x,y) == info.nBkgndIndex) return true; + } else { + RGBQUAD ct = info.nBkgndColor; + RGBQUAD c = GetPixelColor(x,y,false); + if (*(long*)&c==*(long*)&ct) return true; + } + } + +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha) return AlphaGet(x,y)==0; +#endif + + return false; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::GetTransparentMask(CxImage* iDst) +{ + if (!pDib) return false; + + CxImage tmp; + tmp.Create(head.biWidth, head.biHeight, 1, GetType()); + tmp.SetStdPalette(); + tmp.Clear(0); + + for(long y=0; yTransfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if image has the same palette, if any. + * \param img = image to compare. + * \param bCheckAlpha = check also the rgbReserved field. + */ +bool CxImage::IsSamePalette(CxImage &img, bool bCheckAlpha) +{ + if (head.biClrUsed != img.head.biClrUsed) + return false; + if (head.biClrUsed == 0) + return false; + + RGBQUAD c1,c2; + for (DWORD n=0; n256) { + head.biClrImportant = 0; + return; + } + + switch(head.biBitCount){ + case 1: + head.biClrImportant = min(ncolors,2); + break; + case 4: + head.biClrImportant = min(ncolors,16); + break; + case 8: + head.biClrImportant = ncolors; + break; + } + return; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns pointer to pixel. Currently implemented only for truecolor images. + * + * \param x,y - coordinates + * + * \return pointer to first byte of pixel data + * + * \author ***bd*** 2.2004 + */ +void* CxImage::BlindGetPixelPointer(const long x, const long y) +{ +#ifdef _DEBUG + if ((pDib==NULL) || !IsInside(x,y)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return 0; + #endif +#endif + if (!IsIndexed()) + return info.pImage + y*info.dwEffWidth + x*3; + else + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::DrawLine(int StartX, int EndX, int StartY, int EndY, COLORREF cr) +{ + DrawLine(StartX, EndX, StartY, EndY, RGBtoRGBQUAD(cr)); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::DrawLine(int StartX, int EndX, int StartY, int EndY, RGBQUAD color, bool bSetAlpha) +{ + if (!pDib) return; + ////////////////////////////////////////////////////// + // Draws a line using the Bresenham line algorithm + // Thanks to Jordan DeLozier + ////////////////////////////////////////////////////// + int x1 = StartX; + int y1 = StartY; + int x = x1; // Start x off at the first pixel + int y = y1; // Start y off at the first pixel + int x2 = EndX; + int y2 = EndY; + + int xinc1,xinc2,yinc1,yinc2; // Increasing values + int den, num, numadd,numpixels; + int deltax = abs(x2 - x1); // The difference between the x's + int deltay = abs(y2 - y1); // The difference between the y's + + // Get Increasing Values + if (x2 >= x1) { // The x-values are increasing + xinc1 = 1; + xinc2 = 1; + } else { // The x-values are decreasing + xinc1 = -1; + xinc2 = -1; + } + + if (y2 >= y1) { // The y-values are increasing + yinc1 = 1; + yinc2 = 1; + } else { // The y-values are decreasing + yinc1 = -1; + yinc2 = -1; + } + + // Actually draw the line + if (deltax >= deltay) // There is at least one x-value for every y-value + { + xinc1 = 0; // Don't change the x when numerator >= denominator + yinc2 = 0; // Don't change the y for every iteration + den = deltax; + num = deltax / 2; + numadd = deltay; + numpixels = deltax; // There are more x-values than y-values + } + else // There is at least one y-value for every x-value + { + xinc2 = 0; // Don't change the x for every iteration + yinc1 = 0; // Don't change the y when numerator >= denominator + den = deltay; + num = deltay / 2; + numadd = deltax; + numpixels = deltay; // There are more y-values than x-values + } + + for (int curpixel = 0; curpixel <= numpixels; curpixel++) + { + // Draw the current pixel + SetPixelColor(x,y,color,bSetAlpha); + + num += numadd; // Increase the numerator by the top of the fraction + if (num >= den) // Check if numerator >= denominator + { + num -= den; // Calculate the new numerator value + x += xinc1; // Change the x as appropriate + y += yinc1; // Change the y as appropriate + } + x += xinc2; // Change the x as appropriate + y += yinc2; // Change the y as appropriate + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets a palette with standard colors for 1, 4 and 8 bpp images. + */ +void CxImage::SetStdPalette() +{ + if (!pDib) return; + switch (head.biBitCount){ + case 8: + { + const BYTE pal256[1024] = {0,0,0,0,0,0,128,0,0,128,0,0,0,128,128,0,128,0,0,0,128,0,128,0,128,128,0,0,192,192,192,0, + 192,220,192,0,240,202,166,0,212,240,255,0,177,226,255,0,142,212,255,0,107,198,255,0, + 72,184,255,0,37,170,255,0,0,170,255,0,0,146,220,0,0,122,185,0,0,98,150,0,0,74,115,0,0, + 50,80,0,212,227,255,0,177,199,255,0,142,171,255,0,107,143,255,0,72,115,255,0,37,87,255,0,0, + 85,255,0,0,73,220,0,0,61,185,0,0,49,150,0,0,37,115,0,0,25,80,0,212,212,255,0,177,177,255,0, + 142,142,255,0,107,107,255,0,72,72,255,0,37,37,255,0,0,0,254,0,0,0,220,0,0,0,185,0,0,0,150,0, + 0,0,115,0,0,0,80,0,227,212,255,0,199,177,255,0,171,142,255,0,143,107,255,0,115,72,255,0, + 87,37,255,0,85,0,255,0,73,0,220,0,61,0,185,0,49,0,150,0,37,0,115,0,25,0,80,0,240,212,255,0, + 226,177,255,0,212,142,255,0,198,107,255,0,184,72,255,0,170,37,255,0,170,0,255,0,146,0,220,0, + 122,0,185,0,98,0,150,0,74,0,115,0,50,0,80,0,255,212,255,0,255,177,255,0,255,142,255,0,255,107,255,0, + 255,72,255,0,255,37,255,0,254,0,254,0,220,0,220,0,185,0,185,0,150,0,150,0,115,0,115,0,80,0,80,0, + 255,212,240,0,255,177,226,0,255,142,212,0,255,107,198,0,255,72,184,0,255,37,170,0,255,0,170,0, + 220,0,146,0,185,0,122,0,150,0,98,0,115,0,74,0,80,0,50,0,255,212,227,0,255,177,199,0,255,142,171,0, + 255,107,143,0,255,72,115,0,255,37,87,0,255,0,85,0,220,0,73,0,185,0,61,0,150,0,49,0,115,0,37,0, + 80,0,25,0,255,212,212,0,255,177,177,0,255,142,142,0,255,107,107,0,255,72,72,0,255,37,37,0,254,0, + 0,0,220,0,0,0,185,0,0,0,150,0,0,0,115,0,0,0,80,0,0,0,255,227,212,0,255,199,177,0,255,171,142,0, + 255,143,107,0,255,115,72,0,255,87,37,0,255,85,0,0,220,73,0,0,185,61,0,0,150,49,0,0,115,37,0, + 0,80,25,0,0,255,240,212,0,255,226,177,0,255,212,142,0,255,198,107,0,255,184,72,0,255,170,37,0, + 255,170,0,0,220,146,0,0,185,122,0,0,150,98,0,0,115,74,0,0,80,50,0,0,255,255,212,0,255,255,177,0, + 255,255,142,0,255,255,107,0,255,255,72,0,255,255,37,0,254,254,0,0,220,220,0,0,185,185,0,0,150,150,0, + 0,115,115,0,0,80,80,0,0,240,255,212,0,226,255,177,0,212,255,142,0,198,255,107,0,184,255,72,0, + 170,255,37,0,170,255,0,0,146,220,0,0,122,185,0,0,98,150,0,0,74,115,0,0,50,80,0,0,227,255,212,0, + 199,255,177,0,171,255,142,0,143,255,107,0,115,255,72,0,87,255,37,0,85,255,0,0,73,220,0,0,61,185,0, + 0,49,150,0,0,37,115,0,0,25,80,0,0,212,255,212,0,177,255,177,0,142,255,142,0,107,255,107,0,72,255,72,0, + 37,255,37,0,0,254,0,0,0,220,0,0,0,185,0,0,0,150,0,0,0,115,0,0,0,80,0,0,212,255,227,0,177,255,199,0, + 142,255,171,0,107,255,143,0,72,255,115,0,37,255,87,0,0,255,85,0,0,220,73,0,0,185,61,0,0,150,49,0,0, + 115,37,0,0,80,25,0,212,255,240,0,177,255,226,0,142,255,212,0,107,255,198,0,72,255,184,0,37,255,170,0, + 0,255,170,0,0,220,146,0,0,185,122,0,0,150,98,0,0,115,74,0,0,80,50,0,212,255,255,0,177,255,255,0, + 142,255,255,0,107,255,255,0,72,255,255,0,37,255,255,0,0,254,254,0,0,220,220,0,0,185,185,0,0, + 150,150,0,0,115,115,0,0,80,80,0,242,242,242,0,230,230,230,0,218,218,218,0,206,206,206,0,194,194,194,0, + 182,182,182,0,170,170,170,0,158,158,158,0,146,146,146,0,134,134,134,0,122,122,122,0,110,110,110,0, + 98,98,98,0,86,86,86,0,74,74,74,0,62,62,62,0,50,50,50,0,38,38,38,0,26,26,26,0,14,14,14,0,240,251,255,0, + 164,160,160,0,128,128,128,0,0,0,255,0,0,255,0,0,0,255,255,0,255,0,0,0,255,0,255,0,255,255,0,0,255,255,255,0}; + memcpy(GetPalette(),pal256,1024); + break; + } + case 4: + { + const BYTE pal16[64]={0,0,0,0,0,0,128,0,0,128,0,0,0,128,128,0,128,0,0,0,128,0,128,0,128,128,0,0,192,192,192,0, + 128,128,128,0,0,0,255,0,0,255,0,0,0,255,255,0,255,0,0,0,255,0,255,0,255,255,0,0,255,255,255,0}; + memcpy(GetPalette(),pal16,64); + break; + } + case 1: + { + const BYTE pal2[8]={0,0,0,0,255,255,255,0}; + memcpy(GetPalette(),pal2,8); + break; + } + } + info.last_c_isvalid = false; + return; +} +//////////////////////////////////////////////////////////////////////////////// diff --git a/CxImage/ximapng.cpp b/CxImage/ximapng.cpp new file mode 100644 index 00000000..b90a1aea --- /dev/null +++ b/CxImage/ximapng.cpp @@ -0,0 +1,536 @@ +/* + * File: ximapng.cpp + * Purpose: Platform Independent PNG Image Class Loader and Writer + * 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 6.0.0 02/Feb/2008 + */ + +#include "ximapng.h" + +#if CXIMAGE_SUPPORT_PNG + +#include "ximaiter.h" + +//////////////////////////////////////////////////////////////////////////////// +void CxImagePNG::ima_png_error(png_struct *png_ptr, char *message) +{ + strcpy(info.szLastError,message); + longjmp(png_ptr->jmpbuf, 1); +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +void CxImagePNG::expand2to4bpp(BYTE* prow) +{ + BYTE *psrc,*pdst; + BYTE pos,idx; + for(long x=head.biWidth-1;x>=0;x--){ + psrc = prow + ((2*x)>>3); + pdst = prow + ((4*x)>>3); + pos = (BYTE)(2*(3-x%4)); + idx = (BYTE)((*psrc & (0x03<>pos); + pos = (BYTE)(4*(1-x%2)); + *pdst &= ~(0x0F<jmpbuf)) { + /* Free all of the memory associated with the png_ptr and info_ptr */ + delete [] row_pointers; + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + cx_throw(""); + } + + // use custom I/O functions + png_set_read_fn(png_ptr, hFile, /*(png_rw_ptr)*/user_read_data); + png_set_error_fn(png_ptr,info.szLastError,/*(png_error_ptr)*/user_error_fn,NULL); + + /* read the file information */ + png_read_info(png_ptr, info_ptr); + + if (info.nEscape == -1){ + head.biWidth = info_ptr->width; + head.biHeight= info_ptr->height; + info.dwType = CXIMAGE_FORMAT_PNG; + longjmp(png_ptr->jmpbuf, 1); + } + + /* calculate new number of channels */ + int channels=0; + switch(info_ptr->color_type){ + case PNG_COLOR_TYPE_GRAY: + case PNG_COLOR_TYPE_PALETTE: + channels = 1; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + channels = 2; + break; + case PNG_COLOR_TYPE_RGB: + channels = 3; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + channels = 4; + break; + default: + strcpy(info.szLastError,"unknown PNG color type"); + longjmp(png_ptr->jmpbuf, 1); + } + + //find the right pixel depth used for cximage + int pixel_depth = info_ptr->pixel_depth; + if (channels == 1 && pixel_depth>8) pixel_depth=8; + if (channels == 2) pixel_depth=8; + if (channels >= 3) pixel_depth=24; + + if (!Create(info_ptr->width, info_ptr->height, pixel_depth, CXIMAGE_FORMAT_PNG)){ + longjmp(png_ptr->jmpbuf, 1); + } + + /* get metrics */ + switch (info_ptr->phys_unit_type) + { + case PNG_RESOLUTION_UNKNOWN: + SetXDPI(info_ptr->x_pixels_per_unit); + SetYDPI(info_ptr->y_pixels_per_unit); + break; + case PNG_RESOLUTION_METER: + SetXDPI((long)floor(info_ptr->x_pixels_per_unit * 254.0 / 10000.0 + 0.5)); + SetYDPI((long)floor(info_ptr->y_pixels_per_unit * 254.0 / 10000.0 + 0.5)); + break; + } + + if (info_ptr->num_palette>0){ + SetPalette((rgb_color*)info_ptr->palette,info_ptr->num_palette); + SetClrImportant(info_ptr->num_palette); + } else if (info_ptr->bit_depth ==2) { // needed for 2 bpp grayscale PNGs + SetPaletteColor(0,0,0,0); + SetPaletteColor(1,85,85,85); + SetPaletteColor(2,170,170,170); + SetPaletteColor(3,255,255,255); + } else SetGrayPalette(); // needed for grayscale PNGs + + int nshift = max(0,(info_ptr->bit_depth>>3)-1)<<3; + + if (info_ptr->num_trans!=0){ //palette transparency + if (info_ptr->num_trans==1){ + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE){ + info.nBkgndIndex = info_ptr->trans_color.index; + } else{ + info.nBkgndIndex = info_ptr->trans_color.gray>>nshift; + } + } + if (info_ptr->num_trans>1){ + RGBQUAD* pal=GetPalette(); + if (pal){ + DWORD ip; + for (ip=0;ipnum_trans);ip++) + pal[ip].rgbReserved=info_ptr->trans_alpha[ip]; + for (ip=info_ptr->num_trans;iptrans_color.red>>nshift); + info.nBkgndColor.rgbGreen = (BYTE)(info_ptr->trans_color.green>>nshift); + info.nBkgndColor.rgbBlue = (BYTE)(info_ptr->trans_color.blue>>nshift); + info.nBkgndColor.rgbReserved = 0; + info.nBkgndIndex = 0; + } + } + + int alpha_present = (channels - 1) % 2; + if (alpha_present){ +#if CXIMAGE_SUPPORT_ALPHA // + AlphaCreate(); +#else + png_set_strip_alpha(png_ptr); +#endif //CXIMAGE_SUPPORT_ALPHA + } + + // - flip the RGB pixels to BGR (or RGBA to BGRA) + if (info_ptr->color_type & PNG_COLOR_MASK_COLOR){ + png_set_bgr(png_ptr); + } + + // - handle cancel + if (info.nEscape) longjmp(png_ptr->jmpbuf, 1); + + // row_bytes is the width x number of channels x (bit-depth / 8) + row_pointers = new BYTE[info_ptr->rowbytes + 8]; + + // turn on interlace handling + int number_passes = png_set_interlace_handling(png_ptr); + + if (number_passes>1){ + SetCodecOption(1); + } else { + SetCodecOption(0); + } + + int chan_offset = info_ptr->bit_depth >> 3; + int pixel_offset = info_ptr->pixel_depth >> 3; + + for (int pass=0; pass < number_passes; pass++) { + iter.Upset(); + int y=0; + do { + + // - handle cancel + if (info.nEscape) longjmp(png_ptr->jmpbuf, 1); + +#if CXIMAGE_SUPPORT_ALPHA // + if (AlphaIsValid()) { + + //compute the correct position of the line + long ax,ay; + ay = head.biHeight-1-y; + BYTE* prow= iter.GetRow(ay); + + //recover data from previous scan + if (info_ptr->interlace_type && pass>0 && pass!=7){ + for(ax=0;ax RGB + A + for(ax=0;axinterlace_type && pass>0){ + iter.GetRow(row_pointers, info_ptr->rowbytes); + //re-expand buffer for images with bit depth > 8 + if (info_ptr->bit_depth > 8){ + for(long ax=(head.biWidth*channels-1);ax>=0;ax--) + row_pointers[ax*chan_offset] = row_pointers[ax]; + } + } + + //read next row + png_read_row(png_ptr, row_pointers, NULL); + + //shrink 16 bit depth images down to 8 bits + if (info_ptr->bit_depth > 8){ + for(long ax=0;ax<(head.biWidth*channels);ax++) + row_pointers[ax] = row_pointers[ax*chan_offset]; + } + + //copy the pixels + iter.SetRow(row_pointers, info_ptr->rowbytes); + // expand 2 bpp images only in the last pass + if (info_ptr->bit_depth==2 && pass==(number_passes-1)) + expand2to4bpp(iter.GetRow()); + + //go on + iter.PrevRow(); + } + + y++; + } while(yjmpbuf)){ + /* If we get here, we had a problem reading the file */ + if (info_ptr->palette) free(info_ptr->palette); + png_destroy_write_struct(&png_ptr, (png_infopp)&info_ptr); + cx_throw("Error saving PNG file"); + } + + /* set up the output control */ + //png_init_io(png_ptr, hFile); + + // use custom I/O functions + png_set_write_fn(png_ptr,hFile,/*(png_rw_ptr)*/user_write_data,/*(png_flush_ptr)*/user_flush_data); + + /* set the file information here */ + info_ptr->width = GetWidth(); + info_ptr->height = GetHeight(); + info_ptr->pixel_depth = (BYTE)GetBpp(); + info_ptr->channels = (GetBpp()>8) ? (BYTE)3: (BYTE)1; + info_ptr->bit_depth = (BYTE)(GetBpp()/info_ptr->channels); + info_ptr->compression_type = info_ptr->filter_type = 0; + info_ptr->valid = 0; + + switch(GetCodecOption(CXIMAGE_FORMAT_PNG)){ + case 1: + info_ptr->interlace_type = PNG_INTERLACE_ADAM7; + break; + default: + info_ptr->interlace_type = PNG_INTERLACE_NONE; + } + + /* set compression level */ + //png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); + + bool bGrayScale = IsGrayScale(); + + if (GetNumColors()){ + if (bGrayScale){ + info_ptr->color_type = PNG_COLOR_TYPE_GRAY; + } else { + info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; + } + } else { + info_ptr->color_type = PNG_COLOR_TYPE_RGB; + } +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + info_ptr->color_type |= PNG_COLOR_MASK_ALPHA; + info_ptr->channels++; + info_ptr->bit_depth = 8; + info_ptr->pixel_depth += 8; + } +#endif + + /* set background */ + png_color_16 image_background={ 0, 255, 255, 255, 0 }; + RGBQUAD tc = GetTransColor(); + if (info.nBkgndIndex>=0) { + image_background.blue = tc.rgbBlue; + image_background.green = tc.rgbGreen; + image_background.red = tc.rgbRed; + } + png_set_bKGD(png_ptr, info_ptr, &image_background); + + /* set metrics */ + png_set_pHYs(png_ptr, info_ptr, head.biXPelsPerMeter, head.biYPelsPerMeter, PNG_RESOLUTION_METER); + + png_set_IHDR(png_ptr, info_ptr, info_ptr->width, info_ptr->height, info_ptr->bit_depth, + info_ptr->color_type, info_ptr->interlace_type, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + // simple transparency + if (info.nBkgndIndex >= 0){ + info_ptr->num_trans = 1; + info_ptr->valid |= PNG_INFO_tRNS; + info_ptr->trans_alpha = trans; + info_ptr->trans_color.index = (BYTE)info.nBkgndIndex; + info_ptr->trans_color.red = tc.rgbRed; + info_ptr->trans_color.green = tc.rgbGreen; + info_ptr->trans_color.blue = tc.rgbBlue; + info_ptr->trans_color.gray = info_ptr->trans_color.index; + + // the transparency indexes start from 0 for non grayscale palette + if (!bGrayScale && head.biClrUsed && info.nBkgndIndex) + SwapIndex(0,(BYTE)info.nBkgndIndex); + } + + /* set the palette if there is one */ + if (GetPalette()){ + if (!bGrayScale){ + info_ptr->valid |= PNG_INFO_PLTE; + } + + int nc = GetClrImportant(); + if (nc==0) nc = GetNumColors(); + + if (info.bAlphaPaletteEnabled){ + for(WORD ip=0; ipnum_trans = (WORD)nc; + info_ptr->valid |= PNG_INFO_tRNS; + info_ptr->trans_alpha = trans; + } + + // copy the palette colors + info_ptr->palette = new png_color[nc]; + info_ptr->num_palette = (png_uint_16) nc; + for (int i=0; ipalette[i].red, &info_ptr->palette[i].green, &info_ptr->palette[i].blue); + } + +#if CXIMAGE_SUPPORT_ALPHA // + //Merge the transparent color with the alpha channel + if (AlphaIsValid() && head.biBitCount==24 && info.nBkgndIndex>=0){ + for(long y=0; y < head.biHeight; y++){ + for(long x=0; x < head.biWidth ; x++){ + RGBQUAD c=GetPixelColor(x,y,false); + if (*(long*)&c==*(long*)&tc) + AlphaSet(x,y,0); + } } } +#endif // CXIMAGE_SUPPORT_ALPHA // + + int row_size = max(info.dwEffWidth, info_ptr->width*info_ptr->channels*(info_ptr->bit_depth/8)); + info_ptr->rowbytes = row_size; + BYTE *row_pointers = new BYTE[row_size]; + + /* write the file information */ + png_write_info(png_ptr, info_ptr); + + //interlace handling + int num_pass = png_set_interlace_handling(png_ptr); + for (int pass = 0; pass < num_pass; pass++){ + //write image + iter.Upset(); + long ay=head.biHeight-1; + RGBQUAD c; + do { +#if CXIMAGE_SUPPORT_ALPHA // + if (AlphaIsValid()){ + for (long ax=head.biWidth-1; ax>=0;ax--){ + c = BlindGetPixelColor(ax,ay); + int px = ax * info_ptr->channels; + if (!bGrayScale){ + row_pointers[px++]=c.rgbRed; + row_pointers[px++]=c.rgbGreen; + } + row_pointers[px++]=c.rgbBlue; + row_pointers[px] = AlphaGet(ax,ay); + } + png_write_row(png_ptr, row_pointers); + ay--; + } + else +#endif //CXIMAGE_SUPPORT_ALPHA // + { + iter.GetRow(row_pointers, row_size); + if (info_ptr->color_type == PNG_COLOR_TYPE_RGB) //HACK BY OP + RGBtoBGR(row_pointers, row_size); + png_write_row(png_ptr, row_pointers); + } + } while(iter.PrevRow()); + } + + delete [] row_pointers; + + //if necessary, restore the original palette + if (!bGrayScale && head.biClrUsed && info.nBkgndIndex>0) + SwapIndex((BYTE)info.nBkgndIndex,0); + + /* It is REQUIRED to call this to finish writing the rest of the file */ + png_write_end(png_ptr, info_ptr); + + /* if you malloced the palette, free it here */ + if (info_ptr->palette){ + delete [] (info_ptr->palette); + info_ptr->palette = NULL; + } + + /* clean up after the write, and free any memory allocated */ + png_destroy_write_struct(&png_ptr, (png_infopp)&info_ptr); + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + return FALSE; + } + /* that's it */ + return TRUE; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_PNG diff --git a/CxImage/ximapng.h b/CxImage/ximapng.h new file mode 100644 index 00000000..7db82546 --- /dev/null +++ b/CxImage/ximapng.h @@ -0,0 +1,74 @@ +/* + * File: ximapng.h + * Purpose: PNG Image Class Loader and Writer + */ +/* ========================================================== + * CxImagePNG (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Special thanks to Troels Knakkergaard for new features, enhancements and bugfixes + * + * original CImagePNG and CImageIterator implementation are: + * Copyright: (c) 1995, Alejandro Aguilar Sierra + * + * libpng Copyright (c) 1998-2003 Glenn Randers-Pehrson + * ========================================================== + */ +#if !defined(__ximaPNG_h) +#define __ximaPNG_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_PNG + +extern "C" { +#include "../libpng/png.h" +} + +class CxImagePNG: public CxImage +{ +public: + CxImagePNG(): CxImage(CXIMAGE_FORMAT_PNG) {} + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_PNG);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_PNG);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE + +protected: + void ima_png_error(png_struct *png_ptr, char *message); + void expand2to4bpp(BYTE* prow); + + static void PNGAPI user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) + { + CxFile* hFile = (CxFile*)png_get_io_ptr(png_ptr); + if (hFile == NULL || hFile->Read(data,1,length) != length) png_error(png_ptr, "Read Error"); + } + + static void PNGAPI user_write_data(png_structp png_ptr, png_bytep data, png_size_t length) + { + CxFile* hFile = (CxFile*)png_get_io_ptr(png_ptr); + if (hFile == NULL || hFile->Write(data,1,length) != length) png_error(png_ptr, "Write Error"); + } + + static void PNGAPI user_flush_data(png_structp png_ptr) + { + CxFile* hFile = (CxFile*)png_get_io_ptr(png_ptr); + if (hFile == NULL || !hFile->Flush()) png_error(png_ptr, "Flush Error"); + } + + static void PNGAPI user_error_fn(png_structp png_ptr,png_const_charp error_msg) + { + strncpy((char*)png_ptr->error_ptr,error_msg,255); + longjmp(png_ptr->jmpbuf, 1); + } +}; + +#endif + +#endif diff --git a/CxImage/ximath.cpp b/CxImage/ximath.cpp new file mode 100644 index 00000000..37533e22 --- /dev/null +++ b/CxImage/ximath.cpp @@ -0,0 +1,97 @@ +#include "ximage.h" +#include "ximath.h" +#include + +//this module should contain some classes for geometrical transformations +//usable with selections, etc... once it's done, that is. :) + +CxPoint2::CxPoint2() +{ + x=y=0.0f; +} + +CxPoint2::CxPoint2(float const x_, float const y_) +{ + x=x_; + y=y_; +} + +CxPoint2::CxPoint2(CxPoint2 const &p) +{ + x=p.x; + y=p.y; +} + +float CxPoint2::Distance(CxPoint2 const p2) +{ + return (float)sqrt((x-p2.x)*(x-p2.x)+(y-p2.y)*(y-p2.y)); +} + +float CxPoint2::Distance(float const x_, float const y_) +{ + return (float)sqrt((x-x_)*(x-x_)+(y-y_)*(y-y_)); +} + +CxRect2::CxRect2() +{ +} + +CxRect2::CxRect2(float const x1_, float const y1_, float const x2_, float const y2_) +{ + botLeft.x=x1_; + botLeft.y=y1_; + topRight.x=x2_; + topRight.y=y2_; +} + +CxRect2::CxRect2(CxRect2 const &p) +{ + botLeft=p.botLeft; + topRight=p.topRight; +} + +float CxRect2::Surface() const +/* + * Returns the surface of rectangle. + */ +{ + return (topRight.x-botLeft.x)*(topRight.y-botLeft.y); +} + +CxRect2 CxRect2::CrossSection(CxRect2 const &r2) const +/* + * Returns crossection with another rectangle. + */ +{ + CxRect2 cs; + cs.botLeft.x=max(botLeft.x, r2.botLeft.x); + cs.botLeft.y=max(botLeft.y, r2.botLeft.y); + cs.topRight.x=min(topRight.x, r2.topRight.x); + cs.topRight.y=min(topRight.y, r2.topRight.y); + if (cs.botLeft.x<=cs.topRight.x && cs.botLeft.y<=cs.topRight.y) { + return cs; + } else { + return CxRect2(0,0,0,0); + }//if +} + +CxPoint2 CxRect2::Center() const +/* + * Returns the center point of rectangle. + */ +{ + return CxPoint2((topRight.x+botLeft.x)/2.0f, (topRight.y+botLeft.y)/2.0f); +} + +float CxRect2::Width() const +//returns rectangle width +{ + return topRight.x-botLeft.x; +} + +float CxRect2::Height() const +//returns rectangle height +{ + return topRight.y-botLeft.y; +} + diff --git a/CxImage/ximath.h b/CxImage/ximath.h new file mode 100644 index 00000000..10b98984 --- /dev/null +++ b/CxImage/ximath.h @@ -0,0 +1,39 @@ +#if !defined(__ximath_h) +#define __ximath_h + +#include "ximadef.h" + +//***bd*** simple floating point point +class DLL_EXP CxPoint2 +{ +public: + CxPoint2(); + CxPoint2(float const x_, float const y_); + CxPoint2(CxPoint2 const &p); + + float Distance(CxPoint2 const p2); + float Distance(float const x_, float const y_); + + float x,y; +}; + +//and simple rectangle +class DLL_EXP CxRect2 +{ +public: + CxRect2(); + CxRect2(float const x1_, float const y1_, float const x2_, float const y2_); + CxRect2(CxPoint2 const &bl, CxPoint2 const &tr); + CxRect2(CxRect2 const &p); + + float Surface() const; + CxRect2 CrossSection(CxRect2 const &r2) const; + CxPoint2 Center() const; + float Width() const; + float Height() const; + + CxPoint2 botLeft; + CxPoint2 topRight; +}; + +#endif diff --git a/CxImage/ximatran.cpp b/CxImage/ximatran.cpp new file mode 100644 index 00000000..d3aac018 --- /dev/null +++ b/CxImage/ximatran.cpp @@ -0,0 +1,2624 @@ +// xImaTran.cpp : Transformation functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 6.0.0 02/Feb/2008 + */ + +#include "ximage.h" +#include "ximath.h" + +#if CXIMAGE_SUPPORT_BASICTRANSFORMATIONS +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::GrayScale() +{ + if (!pDib) return false; + if (head.biBitCount<=8){ + RGBQUAD* ppal=GetPalette(); + int gray; + //converts the colors to gray, use the blue channel only + for(DWORD i=0;i= 0) info.nBkgndIndex = ppal[info.nBkgndIndex].rgbBlue; + //create a "real" 8 bit gray scale image + if (head.biBitCount==8){ + BYTE *img=info.pImage; + for(DWORD i=0;i> 1]&((BYTE)0x0F<> pos)].rgbBlue; + } else { + BYTE pos = (BYTE)(7-x%8); + iDst[x]= ppal[(BYTE)((iSrc[x >> 3]&((BYTE)0x01<> pos)].rgbBlue; + } + } + } + Transfer(ima); + } + } else { //from RGB to 8 bit gray scale + BYTE *iSrc=info.pImage; + CxImage ima; + ima.CopyInfo(*this); + if (!ima.Create(head.biWidth,head.biHeight,8,info.dwType)) return false; + ima.SetGrayPalette(); +#if CXIMAGE_SUPPORT_SELECTION + ima.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION +#if CXIMAGE_SUPPORT_ALPHA + ima.AlphaCopy(*this); +#endif //CXIMAGE_SUPPORT_ALPHA + BYTE *img=ima.GetBits(); + long l8=ima.GetEffWidth(); + long l=head.biWidth * 3; + for(long y=0; y < head.biHeight; y++) { + for(long x=0,x8=0; x < l; x+=3,x8++) { + img[x8+y*l8]=(BYTE)RGB2GRAY(*(iSrc+x+2),*(iSrc+x+1),*(iSrc+x+0)); + } + iSrc+=info.dwEffWidth; + } + Transfer(ima); + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa Mirror + * \author [qhbo] + */ +bool CxImage::Flip(bool bFlipSelection, bool bFlipAlpha) +{ + if (!pDib) return false; + + BYTE *buff = (BYTE*)malloc(info.dwEffWidth); + if (!buff) return false; + + BYTE *iSrc,*iDst; + iSrc = GetBits(head.biHeight-1); + iDst = GetBits(0); + for (long i=0; i<(head.biHeight/2); ++i) + { + memcpy(buff, iSrc, info.dwEffWidth); + memcpy(iSrc, iDst, info.dwEffWidth); + memcpy(iDst, buff, info.dwEffWidth); + iSrc-=info.dwEffWidth; + iDst+=info.dwEffWidth; + } + + free(buff); + + if (bFlipSelection){ +#if CXIMAGE_SUPPORT_SELECTION + SelectionFlip(); +#endif //CXIMAGE_SUPPORT_SELECTION + } + + if (bFlipAlpha){ +#if CXIMAGE_SUPPORT_ALPHA + AlphaFlip(); +#endif //CXIMAGE_SUPPORT_ALPHA + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa Flip + */ +bool CxImage::Mirror(bool bMirrorSelection, bool bMirrorAlpha) +{ + if (!pDib) return false; + + CxImage* imatmp = new CxImage(*this,false,true,true); + if (!imatmp) return false; + if (!imatmp->IsValid()){ + delete imatmp; + return false; + } + + BYTE *iSrc,*iDst; + long wdt=(head.biWidth-1) * (head.biBitCount==24 ? 3:1); + iSrc=info.pImage + wdt; + iDst=imatmp->info.pImage; + long x,y; + switch (head.biBitCount){ + case 24: + for(y=0; y < head.biHeight; y++){ + for(x=0; x <= wdt; x+=3){ + *(iDst+x)=*(iSrc-x); + *(iDst+x+1)=*(iSrc-x+1); + *(iDst+x+2)=*(iSrc-x+2); + } + iSrc+=info.dwEffWidth; + iDst+=info.dwEffWidth; + } + break; + case 8: + for(y=0; y < head.biHeight; y++){ + for(x=0; x <= wdt; x++) + *(iDst+x)=*(iSrc-x); + iSrc+=info.dwEffWidth; + iDst+=info.dwEffWidth; + } + break; + default: + for(y=0; y < head.biHeight; y++){ + for(x=0; x <= wdt; x++) + imatmp->SetPixelIndex(x,y,GetPixelIndex(wdt-x,y)); + } + } + + if (bMirrorSelection){ +#if CXIMAGE_SUPPORT_SELECTION + imatmp->SelectionMirror(); +#endif //CXIMAGE_SUPPORT_SELECTION + } + + if (bMirrorAlpha){ +#if CXIMAGE_SUPPORT_ALPHA + imatmp->AlphaMirror(); +#endif //CXIMAGE_SUPPORT_ALPHA + } + + Transfer(*imatmp); + delete imatmp; + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +#define RBLOCK 64 + +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::RotateLeft(CxImage* iDst) +{ + if (!pDib) return false; + + long newWidth = GetHeight(); + long newHeight = GetWidth(); + + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(newWidth,newHeight,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) imgDest.AlphaCreate(); +#endif + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()) imgDest.SelectionCreate(); +#endif + + long x,x2,y,dlineup; + + // Speedy rotate for BW images + if (head.biBitCount == 1) { + + BYTE *sbits, *dbits, *dbitsmax, bitpos, *nrow,*srcdisp; + ldiv_t div_r; + + BYTE *bsrc = GetBits(), *bdest = imgDest.GetBits(); + dbitsmax = bdest + imgDest.head.biSizeImage - 1; + dlineup = 8 * imgDest.info.dwEffWidth - imgDest.head.biWidth; + + imgDest.Clear(0); + for (y = 0; y < head.biHeight; y++) { + // Figure out the Column we are going to be copying to + div_r = ldiv(y + dlineup, (long)8); + // set bit pos of src column byte + bitpos = (BYTE)(1 << div_r.rem); + srcdisp = bsrc + y * info.dwEffWidth; + for (x = 0; x < (long)info.dwEffWidth; x++) { + // Get Source Bits + sbits = srcdisp + x; + // Get destination column + nrow = bdest + (x * 8) * imgDest.info.dwEffWidth + imgDest.info.dwEffWidth - 1 - div_r.quot; + for (long z = 0; z < 8; z++) { + // Get Destination Byte + dbits = nrow + z * imgDest.info.dwEffWidth; + if ((dbits < bdest) || (dbits > dbitsmax)) break; + if (*sbits & (128 >> z)) *dbits |= bitpos; + } + } + }//for y + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + for (x = 0; x < newWidth; x++){ + x2=newWidth-x-1; + for (y = 0; y < newHeight; y++){ + imgDest.AlphaSet(x,y,BlindAlphaGet(y, x2)); + }//for y + }//for x + } +#endif //CXIMAGE_SUPPORT_ALPHA + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()) { + imgDest.info.rSelectionBox.left = newWidth-info.rSelectionBox.top; + imgDest.info.rSelectionBox.right = newWidth-info.rSelectionBox.bottom; + imgDest.info.rSelectionBox.bottom = info.rSelectionBox.left; + imgDest.info.rSelectionBox.top = info.rSelectionBox.right; + for (x = 0; x < newWidth; x++){ + x2=newWidth-x-1; + for (y = 0; y < newHeight; y++){ + imgDest.SelectionSet(x,y,BlindSelectionGet(y, x2)); + }//for y + }//for x + } +#endif //CXIMAGE_SUPPORT_SELECTION + + } else { + //anything other than BW: + //bd, 10. 2004: This optimized version of rotation rotates image by smaller blocks. It is quite + //a bit faster than obvious algorithm, because it produces much less CPU cache misses. + //This optimization can be tuned by changing block size (RBLOCK). 96 is good value for current + //CPUs (tested on Athlon XP and Celeron D). Larger value (if CPU has enough cache) will increase + //speed somehow, but once you drop out of CPU's cache, things will slow down drastically. + //For older CPUs with less cache, lower value would yield better results. + + BYTE *srcPtr, *dstPtr; //source and destionation for 24-bit version + int xs, ys; //x-segment and y-segment + for (xs = 0; xs < newWidth; xs+=RBLOCK) { //for all image blocks of RBLOCK*RBLOCK pixels + for (ys = 0; ys < newHeight; ys+=RBLOCK) { + if (head.biBitCount==24) { + //RGB24 optimized pixel access: + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ //do rotation + info.nProgress = (long)(100*x/newWidth); + x2=newWidth-x-1; + dstPtr = (BYTE*) imgDest.BlindGetPixelPointer(x,ys); + srcPtr = (BYTE*) BlindGetPixelPointer(ys, x2); + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + //imgDest.SetPixelColor(x, y, GetPixelColor(y, x2)); + *(dstPtr) = *(srcPtr); + *(dstPtr+1) = *(srcPtr+1); + *(dstPtr+2) = *(srcPtr+2); + srcPtr += 3; + dstPtr += imgDest.info.dwEffWidth; + }//for y + }//for x + } else { + //anything else than 24bpp (and 1bpp): palette + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + info.nProgress = (long)(100*x/newWidth); // + x2=newWidth-x-1; + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + imgDest.SetPixelIndex(x, y, BlindGetPixelIndex(y, x2)); + }//for y + }//for x + }//if (version selection) +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + x2=newWidth-x-1; + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + imgDest.AlphaSet(x,y,BlindAlphaGet(y, x2)); + }//for y + }//for x + }//if (alpha channel) +#endif //CXIMAGE_SUPPORT_ALPHA + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()) { + imgDest.info.rSelectionBox.left = newWidth-info.rSelectionBox.top; + imgDest.info.rSelectionBox.right = newWidth-info.rSelectionBox.bottom; + imgDest.info.rSelectionBox.bottom = info.rSelectionBox.left; + imgDest.info.rSelectionBox.top = info.rSelectionBox.right; + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + x2=newWidth-x-1; + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + imgDest.SelectionSet(x,y,BlindSelectionGet(y, x2)); + }//for y + }//for x + }//if (selection) +#endif //CXIMAGE_SUPPORT_SELECTION + }//for ys + }//for xs + }//if + + //select the destination + if (iDst) iDst->Transfer(imgDest); + else Transfer(imgDest); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::RotateRight(CxImage* iDst) +{ + if (!pDib) return false; + + long newWidth = GetHeight(); + long newHeight = GetWidth(); + + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(newWidth,newHeight,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) imgDest.AlphaCreate(); +#endif + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()) imgDest.SelectionCreate(); +#endif + + long x,y,y2; + // Speedy rotate for BW images + if (head.biBitCount == 1) { + + BYTE *sbits, *dbits, *dbitsmax, bitpos, *nrow,*srcdisp; + ldiv_t div_r; + + BYTE *bsrc = GetBits(), *bdest = imgDest.GetBits(); + dbitsmax = bdest + imgDest.head.biSizeImage - 1; + + imgDest.Clear(0); + for (y = 0; y < head.biHeight; y++) { + // Figure out the Column we are going to be copying to + div_r = ldiv(y, (long)8); + // set bit pos of src column byte + bitpos = (BYTE)(128 >> div_r.rem); + srcdisp = bsrc + y * info.dwEffWidth; + for (x = 0; x < (long)info.dwEffWidth; x++) { + // Get Source Bits + sbits = srcdisp + x; + // Get destination column + nrow = bdest + (imgDest.head.biHeight-1-(x*8)) * imgDest.info.dwEffWidth + div_r.quot; + for (long z = 0; z < 8; z++) { + // Get Destination Byte + dbits = nrow - z * imgDest.info.dwEffWidth; + if ((dbits < bdest) || (dbits > dbitsmax)) break; + if (*sbits & (128 >> z)) *dbits |= bitpos; + } + } + } + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + for (y = 0; y < newHeight; y++){ + y2=newHeight-y-1; + for (x = 0; x < newWidth; x++){ + imgDest.AlphaSet(x,y,BlindAlphaGet(y2, x)); + } + } + } +#endif //CXIMAGE_SUPPORT_ALPHA + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()){ + imgDest.info.rSelectionBox.left = info.rSelectionBox.bottom; + imgDest.info.rSelectionBox.right = info.rSelectionBox.top; + imgDest.info.rSelectionBox.bottom = newHeight-info.rSelectionBox.right; + imgDest.info.rSelectionBox.top = newHeight-info.rSelectionBox.left; + for (y = 0; y < newHeight; y++){ + y2=newHeight-y-1; + for (x = 0; x < newWidth; x++){ + imgDest.SelectionSet(x,y,BlindSelectionGet(y2, x)); + } + } + } +#endif //CXIMAGE_SUPPORT_SELECTION + + } else { + //anything else but BW + BYTE *srcPtr, *dstPtr; //source and destionation for 24-bit version + int xs, ys; //x-segment and y-segment + for (xs = 0; xs < newWidth; xs+=RBLOCK) { + for (ys = 0; ys < newHeight; ys+=RBLOCK) { + if (head.biBitCount==24) { + //RGB24 optimized pixel access: + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + info.nProgress = (long)(100*y/newHeight); // + y2=newHeight-y-1; + dstPtr = (BYTE*) imgDest.BlindGetPixelPointer(xs,y); + srcPtr = (BYTE*) BlindGetPixelPointer(y2, xs); + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + //imgDest.SetPixelColor(x, y, GetPixelColor(y2, x)); + *(dstPtr) = *(srcPtr); + *(dstPtr+1) = *(srcPtr+1); + *(dstPtr+2) = *(srcPtr+2); + dstPtr += 3; + srcPtr += info.dwEffWidth; + }//for x + }//for y + } else { + //anything else than BW & RGB24: palette + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + info.nProgress = (long)(100*y/newHeight); // + y2=newHeight-y-1; + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + imgDest.SetPixelIndex(x, y, BlindGetPixelIndex(y2, x)); + }//for x + }//for y + }//if +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + y2=newHeight-y-1; + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + imgDest.AlphaSet(x,y,BlindAlphaGet(y2, x)); + }//for x + }//for y + }//if (has alpha) +#endif //CXIMAGE_SUPPORT_ALPHA + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()){ + imgDest.info.rSelectionBox.left = info.rSelectionBox.bottom; + imgDest.info.rSelectionBox.right = info.rSelectionBox.top; + imgDest.info.rSelectionBox.bottom = newHeight-info.rSelectionBox.right; + imgDest.info.rSelectionBox.top = newHeight-info.rSelectionBox.left; + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + y2=newHeight-y-1; + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + imgDest.SelectionSet(x,y,BlindSelectionGet(y2, x)); + }//for x + }//for y + }//if (has alpha) +#endif //CXIMAGE_SUPPORT_SELECTION + }//for ys + }//for xs + }//if + + //select the destination + if (iDst) iDst->Transfer(imgDest); + else Transfer(imgDest); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Negative() +{ + if (!pDib) return false; + + if (head.biBitCount<=8){ + if (IsGrayScale()){ //GRAYSCALE, selection + if (pSelection){ + for(long y=info.rSelectionBox.bottom; y invert transparent color too + info.nBkgndColor.rgbBlue = (BYTE)(255-info.nBkgndColor.rgbBlue); + info.nBkgndColor.rgbGreen = (BYTE)(255-info.nBkgndColor.rgbGreen); + info.nBkgndColor.rgbRed = (BYTE)(255-info.nBkgndColor.rgbRed); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_BASICTRANSFORMATIONS +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_TRANSFORMATION +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Rotate(float angle, CxImage* iDst) +{ + if (!pDib) return false; + + // Copyright (c) 1996-1998 Ulrich von Zadow + + // Negative the angle, because the y-axis is negative. + double ang = -angle*acos((float)0)/90; + int newWidth, newHeight; + int nWidth = GetWidth(); + int nHeight= GetHeight(); + double cos_angle = cos(ang); + double sin_angle = sin(ang); + + // Calculate the size of the new bitmap + POINT p1={0,0}; + POINT p2={nWidth,0}; + POINT p3={0,nHeight}; + POINT p4={nWidth,nHeight}; + CxPoint2 newP1,newP2,newP3,newP4, leftTop, rightTop, leftBottom, rightBottom; + + newP1.x = (float)p1.x; + newP1.y = (float)p1.y; + newP2.x = (float)(p2.x*cos_angle - p2.y*sin_angle); + newP2.y = (float)(p2.x*sin_angle + p2.y*cos_angle); + newP3.x = (float)(p3.x*cos_angle - p3.y*sin_angle); + newP3.y = (float)(p3.x*sin_angle + p3.y*cos_angle); + newP4.x = (float)(p4.x*cos_angle - p4.y*sin_angle); + newP4.y = (float)(p4.x*sin_angle + p4.y*cos_angle); + + leftTop.x = min(min(newP1.x,newP2.x),min(newP3.x,newP4.x)); + leftTop.y = min(min(newP1.y,newP2.y),min(newP3.y,newP4.y)); + rightBottom.x = max(max(newP1.x,newP2.x),max(newP3.x,newP4.x)); + rightBottom.y = max(max(newP1.y,newP2.y),max(newP3.y,newP4.y)); + leftBottom.x = leftTop.x; + leftBottom.y = rightBottom.y; + rightTop.x = rightBottom.x; + rightTop.y = leftTop.y; + + newWidth = (int) floor(0.5f + rightTop.x - leftTop.x); + newHeight= (int) floor(0.5f + leftBottom.y - leftTop.y); + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(newWidth,newHeight,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); + +#if CXIMAGE_SUPPORT_ALPHA + if(AlphaIsValid()) //MTA: Fix for rotation problem when the image has an alpha channel + { + imgDest.AlphaCreate(); + imgDest.AlphaClear(); + } +#endif //CXIMAGE_SUPPORT_ALPHA + + int x,y,newX,newY,oldX,oldY; + + if (head.biClrUsed==0){ //RGB + for (y = (int)leftTop.y, newY = 0; y<=(int)leftBottom.y; y++,newY++){ + info.nProgress = (long)(100*newY/newHeight); + if (info.nEscape) break; + for (x = (int)leftTop.x, newX = 0; x<=(int)rightTop.x; x++,newX++){ + oldX = (long)(x*cos_angle + y*sin_angle + 0.5); + oldY = (long)(y*cos_angle - x*sin_angle + 0.5); + imgDest.SetPixelColor(newX,newY,GetPixelColor(oldX,oldY)); +#if CXIMAGE_SUPPORT_ALPHA + imgDest.AlphaSet(newX,newY,AlphaGet(oldX,oldY)); //MTA: copy the alpha value +#endif //CXIMAGE_SUPPORT_ALPHA + } + } + } else { //PALETTE + for (y = (int)leftTop.y, newY = 0; y<=(int)leftBottom.y; y++,newY++){ + info.nProgress = (long)(100*newY/newHeight); + if (info.nEscape) break; + for (x = (int)leftTop.x, newX = 0; x<=(int)rightTop.x; x++,newX++){ + oldX = (long)(x*cos_angle + y*sin_angle + 0.5); + oldY = (long)(y*cos_angle - x*sin_angle + 0.5); + imgDest.SetPixelIndex(newX,newY,GetPixelIndex(oldX,oldY)); +#if CXIMAGE_SUPPORT_ALPHA + imgDest.AlphaSet(newX,newY,AlphaGet(oldX,oldY)); //MTA: copy the alpha value +#endif //CXIMAGE_SUPPORT_ALPHA + } + } + } + //select the destination + if (iDst) iDst->Transfer(imgDest); + else Transfer(imgDest); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Rotates image around it's center. + * Method can use interpolation with paletted images, but does not change pallete, so results vary. + * (If you have only four colours in a palette, there's not much room for interpolation.) + * + * \param angle - angle in degrees (positive values rotate clockwise) + * \param *iDst - destination image (if null, this image is changed) + * \param inMethod - interpolation method used + * (IM_NEAREST_NEIGHBOUR produces aliasing (fast), IM_BILINEAR softens picture a bit (slower) + * IM_SHARPBICUBIC is slower and produces some halos...) + * \param ofMethod - overflow method (how to choose colour of pixels that have no source) + * \param replColor - replacement colour to use (OM_COLOR, OM_BACKGROUND with no background colour...) + * \param optimizeRightAngles - call faster methods for 90, 180, and 270 degree rotations. Faster methods + * are called for angles, where error (in location of corner pixels) is less + * than 0.25 pixels. + * \param bKeepOriginalSize - rotates the image without resizing. + * + * \author ***bd*** 2.2004 + */ +bool CxImage::Rotate2(float angle, + CxImage *iDst, + InterpolationMethod inMethod, + OverflowMethod ofMethod, + RGBQUAD *replColor, + bool const optimizeRightAngles, + bool const bKeepOriginalSize) +{ + if (!pDib) return false; //no dib no go + + double ang = -angle*acos(0.0f)/90.0f; //convert angle to radians and invert (positive angle performs clockwise rotation) + float cos_angle = (float) cos(ang); //these two are needed later (to rotate) + float sin_angle = (float) sin(ang); + + //Calculate the size of the new bitmap (rotate corners of image) + CxPoint2 p[4]; //original corners of the image + p[0]=CxPoint2(-0.5f,-0.5f); + p[1]=CxPoint2(GetWidth()-0.5f,-0.5f); + p[2]=CxPoint2(-0.5f,GetHeight()-0.5f); + p[3]=CxPoint2(GetWidth()-0.5f,GetHeight()-0.5f); + CxPoint2 newp[4]; //rotated positions of corners + //(rotate corners) + if (bKeepOriginalSize){ + for (int i=0; i<4; i++) { + newp[i].x = p[i].x; + newp[i].y = p[i].y; + }//for + } else { + for (int i=0; i<4; i++) { + newp[i].x = (p[i].x*cos_angle - p[i].y*sin_angle); + newp[i].y = (p[i].x*sin_angle + p[i].y*cos_angle); + }//for i + + if (optimizeRightAngles) { + //For rotations of 90, -90 or 180 or 0 degrees, call faster routines + if (newp[3].Distance(CxPoint2(GetHeight()-0.5f, 0.5f-GetWidth())) < 0.25) + //rotation right for circa 90 degrees (diagonal pixels less than 0.25 pixel away from 90 degree rotation destination) + return RotateRight(iDst); + if (newp[3].Distance(CxPoint2(0.5f-GetHeight(), -0.5f+GetWidth())) < 0.25) + //rotation left for ~90 degrees + return RotateLeft(iDst); + if (newp[3].Distance(CxPoint2(0.5f-GetWidth(), 0.5f-GetHeight())) < 0.25) + //rotation left for ~180 degrees + return Rotate180(iDst); + if (newp[3].Distance(p[3]) < 0.25) { + //rotation not significant + if (iDst) iDst->Copy(*this); //copy image to iDst, if required + return true; //and we're done + }//if + }//if + }//if + + //(read new dimensions from location of corners) + float minx = (float) min(min(newp[0].x,newp[1].x),min(newp[2].x,newp[3].x)); + float miny = (float) min(min(newp[0].y,newp[1].y),min(newp[2].y,newp[3].y)); + float maxx = (float) max(max(newp[0].x,newp[1].x),max(newp[2].x,newp[3].x)); + float maxy = (float) max(max(newp[0].y,newp[1].y),max(newp[2].y,newp[3].y)); + int newWidth = (int) floor(maxx-minx+0.5f); + int newHeight= (int) floor(maxy-miny+0.5f); + float ssx=((maxx+minx)- ((float) newWidth-1))/2.0f; //start for x + float ssy=((maxy+miny)- ((float) newHeight-1))/2.0f; //start for y + + float newxcenteroffset = 0.5f * newWidth; + float newycenteroffset = 0.5f * newHeight; + if (bKeepOriginalSize){ + ssx -= 0.5f * GetWidth(); + ssy -= 0.5f * GetHeight(); + } + + //create destination image + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(newWidth,newHeight,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); +#if CXIMAGE_SUPPORT_ALPHA + if(AlphaIsValid()) imgDest.AlphaCreate(); //MTA: Fix for rotation problem when the image has an alpha channel +#endif //CXIMAGE_SUPPORT_ALPHA + + RGBQUAD rgb; //pixel colour + RGBQUAD rc; + if (replColor!=0) + rc=*replColor; + else { + rc.rgbRed=255; rc.rgbGreen=255; rc.rgbBlue=255; rc.rgbReserved=0; + }//if + float x,y; //destination location (float, with proper offset) + float origx, origy; //origin location + int destx, desty; //destination location + + y=ssy; //initialize y + if (!IsIndexed()){ //RGB24 + //optimized RGB24 implementation (direct write to destination): + BYTE *pxptr; +#if CXIMAGE_SUPPORT_ALPHA + BYTE *pxptra=0; +#endif //CXIMAGE_SUPPORT_ALPHA + for (desty=0; destyTransfer(imgDest); + else Transfer(imgDest); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Rotate180(CxImage* iDst) +{ + if (!pDib) return false; + + long wid = GetWidth(); + long ht = GetHeight(); + + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(wid,ht,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) imgDest.AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + + long x,y,y2; + for (y = 0; y < ht; y++){ + info.nProgress = (long)(100*y/ht); // + y2=ht-y-1; + for (x = 0; x < wid; x++){ + if(head.biClrUsed==0)//RGB + imgDest.SetPixelColor(wid-x-1, y2, BlindGetPixelColor(x, y)); + else //PALETTE + imgDest.SetPixelIndex(wid-x-1, y2, BlindGetPixelIndex(x, y)); + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) imgDest.AlphaSet(wid-x-1, y2,BlindAlphaGet(x, y)); +#endif //CXIMAGE_SUPPORT_ALPHA + + } + } + + //select the destination + if (iDst) iDst->Transfer(imgDest); + else Transfer(imgDest); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Resizes the image. mode can be 0 for slow (bilinear) method , + * 1 for fast (nearest pixel) method, or 2 for accurate (bicubic spline interpolation) method. + * The function is faster with 24 and 1 bpp images, slow for 4 bpp images and slowest for 8 bpp images. + */ +bool CxImage::Resample(long newx, long newy, int mode, CxImage* iDst) +{ + if (newx==0 || newy==0) return false; + + if (head.biWidth==newx && head.biHeight==newy){ + if (iDst) iDst->Copy(*this); + return true; + } + + float xScale, yScale, fX, fY; + xScale = (float)head.biWidth / (float)newx; + yScale = (float)head.biHeight / (float)newy; + + CxImage newImage; + newImage.CopyInfo(*this); + newImage.Create(newx,newy,head.biBitCount,GetType()); + newImage.SetPalette(GetPalette()); + if (!newImage.IsValid()){ + strcpy(info.szLastError,newImage.GetLastError()); + return false; + } + + switch (mode) { + case 1: // nearest pixel + { + for(long y=0; y=head.biHeight) yy = head.biHeight-1; + for(int n=-1; n<3; n++) { + r2 = r1 * KernelBSpline(b - (float)n); + xx = i_x+n; + if (xx<0) xx=0; + if (xx>=head.biWidth) xx=head.biWidth-1; + + if (head.biClrUsed){ + rgb = GetPixelColor(xx,yy); + } else { + iDst = info.pImage + yy*info.dwEffWidth + xx*3; + rgb.rgbBlue = *iDst++; + rgb.rgbGreen= *iDst++; + rgb.rgbRed = *iDst; + } + + rr += rgb.rgbRed * r2; + gg += rgb.rgbGreen * r2; + bb += rgb.rgbBlue * r2; + } + } + + if (head.biClrUsed) + newImage.SetPixelColor(x,y,RGB(rr,gg,bb)); + else { + iDst = newImage.info.pImage + y*newImage.info.dwEffWidth + x*3; + *iDst++ = (BYTE)bb; + *iDst++ = (BYTE)gg; + *iDst = (BYTE)rr; + } + + } + } + break; + } + default: // bilinear interpolation + if (!(head.biWidth>newx && head.biHeight>newy && head.biBitCount==24)) { + // (c) 1999 Steve McMahon (steve@dogma.demon.co.uk) + long ifX, ifY, ifX1, ifY1, xmax, ymax; + float ir1, ir2, ig1, ig2, ib1, ib2, dx, dy; + BYTE r,g,b; + RGBQUAD rgb1, rgb2, rgb3, rgb4; + xmax = head.biWidth-1; + ymax = head.biHeight-1; + for(long y=0; y + const long ACCURACY = 1000; + long i,j; // index for faValue + long x,y; // coordinates in source image + BYTE* pSource; + BYTE* pDest = newImage.info.pImage; + long* naAccu = new long[3 * newx + 3]; + long* naCarry = new long[3 * newx + 3]; + long* naTemp; + long nWeightX,nWeightY; + float fEndX; + long nScale = (long)(ACCURACY * xScale * yScale); + + memset(naAccu, 0, sizeof(long) * 3 * newx); + memset(naCarry, 0, sizeof(long) * 3 * newx); + + int u, v = 0; // coordinates in dest image + float fEndY = yScale - 1.0f; + for (y = 0; y < head.biHeight; y++){ + info.nProgress = (long)(100*y/head.biHeight); // + if (info.nEscape) break; + pSource = info.pImage + y * info.dwEffWidth; + u = i = 0; + fEndX = xScale - 1.0f; + if ((float)y < fEndY) { // complete source row goes into dest row + for (x = 0; x < head.biWidth; x++){ + if ((float)x < fEndX){ // complete source pixel goes into dest pixel + for (j = 0; j < 3; j++) naAccu[i + j] += (*pSource++) * ACCURACY; + } else { // source pixel is splitted for 2 dest pixels + nWeightX = (long)(((float)x - fEndX) * ACCURACY); + for (j = 0; j < 3; j++){ + naAccu[i] += (ACCURACY - nWeightX) * (*pSource); + naAccu[3 + i++] += nWeightX * (*pSource++); + } + fEndX += xScale; + u++; + } + } + } else { // source row is splitted for 2 dest rows + nWeightY = (long)(((float)y - fEndY) * ACCURACY); + for (x = 0; x < head.biWidth; x++){ + if ((float)x < fEndX){ // complete source pixel goes into 2 pixel + for (j = 0; j < 3; j++){ + naAccu[i + j] += ((ACCURACY - nWeightY) * (*pSource)); + naCarry[i + j] += nWeightY * (*pSource++); + } + } else { // source pixel is splitted for 4 dest pixels + nWeightX = (int)(((float)x - fEndX) * ACCURACY); + for (j = 0; j < 3; j++) { + naAccu[i] += ((ACCURACY - nWeightY) * (ACCURACY - nWeightX)) * (*pSource) / ACCURACY; + *pDest++ = (BYTE)(naAccu[i] / nScale); + naCarry[i] += (nWeightY * (ACCURACY - nWeightX) * (*pSource)) / ACCURACY; + naAccu[i + 3] += ((ACCURACY - nWeightY) * nWeightX * (*pSource)) / ACCURACY; + naCarry[i + 3] = (nWeightY * nWeightX * (*pSource)) / ACCURACY; + i++; + pSource++; + } + fEndX += xScale; + u++; + } + } + if (u < newx){ // possibly not completed due to rounding errors + for (j = 0; j < 3; j++) *pDest++ = (BYTE)(naAccu[i++] / nScale); + } + naTemp = naCarry; + naCarry = naAccu; + naAccu = naTemp; + memset(naCarry, 0, sizeof(int) * 3); // need only to set first pixel zero + pDest = newImage.info.pImage + (++v * newImage.info.dwEffWidth); + fEndY += yScale; + } + } + if (v < newy){ // possibly not completed due to rounding errors + for (i = 0; i < 3 * newx; i++) *pDest++ = (BYTE)(naAccu[i] / nScale); + } + delete [] naAccu; + delete [] naCarry; + } + } + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + newImage.AlphaCreate(); + for(long y=0; yTransfer(newImage); + else Transfer(newImage); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * New simpler resample. Adds new interpolation methods and simplifies code (using GetPixelColorInterpolated + * and GetAreaColorInterpolated). It also (unlike old method) interpolates alpha layer. + * + * \param newx, newy - size of resampled image + * \param inMethod - interpolation method to use (see comments at GetPixelColorInterpolated) + * If image size is being reduced, averaging is used instead (or simultaneously with) inMethod. + * \param ofMethod - what to replace outside pixels by (only significant for bordering pixels of enlarged image) + * \param iDst - pointer to destination CxImage or NULL. + * \param disableAveraging - force no averaging when shrinking images (Produces aliasing. + * You probably just want to leave this off...) + * + * \author ***bd*** 2.2004 + */ +bool CxImage::Resample2( + long newx, long newy, + InterpolationMethod const inMethod, + OverflowMethod const ofMethod, + CxImage* const iDst, + bool const disableAveraging) +{ + if (newx<=0 || newy<=0 || !pDib) return false; + + if (head.biWidth==newx && head.biHeight==newy) { + //image already correct size (just copy and return) + if (iDst) iDst->Copy(*this); + return true; + }//if + + //calculate scale of new image (less than 1 for enlarge) + float xScale, yScale; + xScale = (float)head.biWidth / (float)newx; + yScale = (float)head.biHeight / (float)newy; + + //create temporary destination image + CxImage newImage; + newImage.CopyInfo(*this); + newImage.Create(newx,newy,head.biBitCount,GetType()); + newImage.SetPalette(GetPalette()); + if (!newImage.IsValid()){ + strcpy(info.szLastError,newImage.GetLastError()); + return false; + } + + //and alpha channel if required +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) newImage.AlphaCreate(); + BYTE *pxptra = 0; // destination alpha data +#endif + + float sX, sY; //source location + long dX,dY; //destination pixel (int value) + if ((xScale<=1 && yScale<=1) || disableAveraging) { + //image is being enlarged (or interpolation on demand) + if (!IsIndexed()) { + //RGB24 image (optimized version with direct writes) + RGBQUAD q; //pixel colour + BYTE *pxptr; //pointer to destination pixel + for(dY=0; dYTransfer(newImage); + else + Transfer(newImage); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Reduces the number of bits per pixel to nbit (1, 4 or 8). + * ppal points to a valid palette for the final image; if not supplied the function will use a standard palette. + * ppal is not necessary for reduction to 1 bpp. + */ +bool CxImage::DecreaseBpp(DWORD nbit, bool errordiffusion, RGBQUAD* ppal, DWORD clrimportant) +{ + if (!pDib) return false; + if (head.biBitCount < nbit){ + strcpy(info.szLastError,"DecreaseBpp: target BPP greater than source BPP"); + return false; + } + if (head.biBitCount == nbit){ + if (clrimportant==0) return true; + if (head.biClrImportant && (head.biClrImportant4) return false; + + CxImage tmp; + tmp.CopyInfo(*this); + tmp.Create(head.biWidth,head.biHeight,4,info.dwType); + tmp.SetPalette(GetPalette(),GetNumColors()); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + +#if CXIMAGE_SUPPORT_SELECTION + tmp.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA + tmp.AlphaCopy(*this); +#endif //CXIMAGE_SUPPORT_ALPHA + + for (long y=0;y8) return false; + + CxImage tmp; + tmp.CopyInfo(*this); + tmp.Create(head.biWidth,head.biHeight,8,info.dwType); + tmp.SetPalette(GetPalette(),GetNumColors()); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + +#if CXIMAGE_SUPPORT_SELECTION + tmp.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA + tmp.AlphaCopy(*this); +#endif //CXIMAGE_SUPPORT_ALPHA + + for (long y=0;y24) return false; + + CxImage tmp; + tmp.CopyInfo(*this); + tmp.Create(head.biWidth,head.biHeight,24,info.dwType); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + if (info.nBkgndIndex>=0) //translate transparency + tmp.info.nBkgndColor=GetPaletteColor((BYTE)info.nBkgndIndex); + +#if CXIMAGE_SUPPORT_SELECTION + tmp.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA + tmp.AlphaCopy(*this); + if (AlphaPaletteIsValid() && !AlphaIsValid()) tmp.AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + + for (long y=0;y 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + nlevel = GetPixelIndex(x + 1, y) + (error * 8) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + 1, y, level); + nlevel = GetPixelIndex(x + 2, y) + (error * 4) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + 2, y, level); + int i; + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 2; + break; + case -1: + coeff = 4; + break; + case 0: + coeff = 8; + break; + case 1: + coeff = 4; + break; + case 2: + coeff = 2; + break; + } + nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + i, y + 1, level); + } + } + } + break; + } + case 3: + { + //Stucki error diffusion (Thanks to Franco Gerevini) + int TotalCoeffSum = 42; + long error, nlevel, coeff=1; + BYTE level; + + for (long y = 0; y < head.biHeight; y++) { + info.nProgress = (long)(100 * y / head.biHeight); + if (info.nEscape) + break; + for (long x = 0; x < head.biWidth; x++) { + level = BlindGetPixelIndex(x, y); + if (level > 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + nlevel = GetPixelIndex(x + 1, y) + (error * 8) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + 1, y, level); + nlevel = GetPixelIndex(x + 2, y) + (error * 4) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + 2, y, level); + int i; + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 2; + break; + case -1: + coeff = 4; + break; + case 0: + coeff = 8; + break; + case 1: + coeff = 4; + break; + case 2: + coeff = 2; + break; + } + nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + i, y + 1, level); + } + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 1; + break; + case -1: + coeff = 2; + break; + case 0: + coeff = 4; + break; + case 1: + coeff = 2; + break; + case 2: + coeff = 1; + break; + } + nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + i, y + 2, level); + } + } + } + break; + } + case 4: + { + //Jarvis, Judice and Ninke error diffusion (Thanks to Franco Gerevini) + int TotalCoeffSum = 48; + long error, nlevel, coeff=1; + BYTE level; + + for (long y = 0; y < head.biHeight; y++) { + info.nProgress = (long)(100 * y / head.biHeight); + if (info.nEscape) + break; + for (long x = 0; x < head.biWidth; x++) { + level = BlindGetPixelIndex(x, y); + if (level > 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + nlevel = GetPixelIndex(x + 1, y) + (error * 7) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + 1, y, level); + nlevel = GetPixelIndex(x + 2, y) + (error * 5) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + 2, y, level); + int i; + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 3; + break; + case -1: + coeff = 5; + break; + case 0: + coeff = 7; + break; + case 1: + coeff = 5; + break; + case 2: + coeff = 3; + break; + } + nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + i, y + 1, level); + } + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 1; + break; + case -1: + coeff = 3; + break; + case 0: + coeff = 5; + break; + case 1: + coeff = 3; + break; + case 2: + coeff = 1; + break; + } + nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + i, y + 2, level); + } + } + } + break; + } + case 5: + { + //Sierra error diffusion (Thanks to Franco Gerevini) + int TotalCoeffSum = 32; + long error, nlevel, coeff=1; + BYTE level; + + for (long y = 0; y < head.biHeight; y++) { + info.nProgress = (long)(100 * y / head.biHeight); + if (info.nEscape) + break; + for (long x = 0; x < head.biWidth; x++) { + level = BlindGetPixelIndex(x, y); + if (level > 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + nlevel = GetPixelIndex(x + 1, y) + (error * 5) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + 1, y, level); + nlevel = GetPixelIndex(x + 2, y) + (error * 3) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + 2, y, level); + int i; + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 2; + break; + case -1: + coeff = 4; + break; + case 0: + coeff = 5; + break; + case 1: + coeff = 4; + break; + case 2: + coeff = 2; + break; + } + nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + i, y + 1, level); + } + for (i = -1; i < 2; i++) { + switch (i) { + case -1: + coeff = 2; + break; + case 0: + coeff = 3; + break; + case 1: + coeff = 2; + break; + } + nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(x + i, y + 2, level); + } + } + } + break; + } + case 6: + { + //Stevenson and Arce error diffusion (Thanks to Franco Gerevini) + int TotalCoeffSum = 200; + long error, nlevel; + BYTE level; + + for (long y = 0; y < head.biHeight; y++) { + info.nProgress = (long)(100 * y / head.biHeight); + if (info.nEscape) + break; + for (long x = 0; x < head.biWidth; x++) { + level = BlindGetPixelIndex(x, y); + if (level > 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + int tmp_index_x = x + 2; + int tmp_index_y = y; + int tmp_coeff = 32; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 3; + tmp_index_y = y + 1; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 1; + tmp_coeff = 26; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 1; + tmp_coeff = 30; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 3; + tmp_coeff = 16; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 2; + tmp_index_y = y + 2; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x; + tmp_coeff = 26; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 2; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 3; + tmp_index_y = y + 3; + tmp_coeff = 5; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 1; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 1; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 3; + tmp_coeff = 5; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (BYTE)min(255, max(0, (int)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + } + } + break; + } + case 7: + { + // Bayer ordered dither + int order = 4; + //create Bayer matrix + if (order>4) order = 4; + int size = (1 << (2*order)); + BYTE* Bmatrix = (BYTE*) malloc(size * sizeof(BYTE)); + for(int i = 0; i < size; i++) { + int n = order; + int x = i / n; + int y = i % n; + int dither = 0; + while (n-- > 0){ + dither = (((dither<<1)|((x&1) ^ (y&1)))<<1) | (y&1); + x >>= 1; + y >>= 1; + } + Bmatrix[i] = (BYTE)(dither); + } + + int scale = max(0,(8-2*order)); + int level; + for (long y=0;y> scale; + if(level > Bmatrix[ (x % order) + order * (y % order) ]){ + tmp.SetPixelIndex(x,y,1); + } else { + tmp.SetPixelIndex(x,y,0); + } + } + } + + free(Bmatrix); + + break; + } + default: + { + // Floyd-Steinberg error diffusion (Thanks to Steve McMahon) + long error,nlevel,coeff=1; + BYTE level; + + for (long y=0;y 128){ + tmp.SetPixelIndex(x,y,1); + error = level-255; + } else { + tmp.SetPixelIndex(x,y,0); + error = level; + } + + nlevel = GetPixelIndex(x+1,y) + (error * 7)/16; + level = (BYTE)min(255,max(0,(int)nlevel)); + SetPixelIndex(x+1,y,level); + for(int i=-1; i<2; i++){ + switch(i){ + case -1: + coeff=3; break; + case 0: + coeff=5; break; + case 1: + coeff=1; break; + } + nlevel = GetPixelIndex(x+i,y+1) + (error * coeff)/16; + level = (BYTE)min(255,max(0,(int)nlevel)); + SetPixelIndex(x+i,y+1,level); + } + } + } + } + } + + tmp.SetPaletteColor(0,0,0,0); + tmp.SetPaletteColor(1,255,255,255); + Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * CropRotatedRectangle + * \param topx,topy : topmost and leftmost point of the rectangle + (topmost, and if there are 2 topmost points, the left one) + * \param width : size of the right hand side of rect, from (topx,topy) roundwalking clockwise + * \param height : size of the left hand side of rect, from (topx,topy) roundwalking clockwise + * \param angle : angle of the right hand side of rect, from (topx,topy) + * \param iDst : pointer to destination image (if 0, this image is modified) + * \author [VATI] + */ +bool CxImage::CropRotatedRectangle( long topx, long topy, long width, long height, float angle, CxImage* iDst) +{ + if (!pDib) return false; + + + long startx,starty,endx,endy; + double cos_angle = cos(angle/*/57.295779513082320877*/); + double sin_angle = sin(angle/*/57.295779513082320877*/); + + // if there is nothing special, call the original Crop(): + if ( fabs(angle)<0.0002 ) + return Crop( topx, topy, topx+width, topy+height, iDst); + + startx = min(topx, topx - (long)(sin_angle*(double)height)); + endx = topx + (long)(cos_angle*(double)width); + endy = topy + (long)(cos_angle*(double)height + sin_angle*(double)width); + // check: corners of the rectangle must be inside + if ( IsInside( startx, topy )==false || + IsInside( endx, endy ) == false ) + return false; + + // first crop to bounding rectangle + CxImage tmp(*this, true, false, true); + // tmp.Copy(*this, true, false, true); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + if (!tmp.Crop( startx, topy, endx, endy)){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + // the midpoint of the image now became the same as the midpoint of the rectangle + // rotate new image with minus angle amount + if ( false == tmp.Rotate( (float)(-angle*57.295779513082320877) ) ) // Rotate expects angle in degrees + return false; + + // crop rotated image to the original selection rectangle + endx = (tmp.head.biWidth+width)/2; + startx = (tmp.head.biWidth-width)/2; + starty = (tmp.head.biHeight+height)/2; + endy = (tmp.head.biHeight-height)/2; + if ( false == tmp.Crop( startx, starty, endx, endy ) ) + return false; + + if (iDst) iDst->Transfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Crop(const RECT& rect, CxImage* iDst) +{ + return Crop(rect.left, rect.top, rect.right, rect.bottom, iDst); +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Crop(long left, long top, long right, long bottom, CxImage* iDst) +{ + if (!pDib) return false; + + long startx = max(0L,min(left,head.biWidth)); + long endx = max(0L,min(right,head.biWidth)); + long starty = head.biHeight - max(0L,min(top,head.biHeight)); + long endy = head.biHeight - max(0L,min(bottom,head.biHeight)); + + if (startx==endx || starty==endy) return false; + + if (startx>endx) {long tmp=startx; startx=endx; endx=tmp;} + if (starty>endy) {long tmp=starty; starty=endy; endy=tmp;} + + CxImage tmp(endx-startx,endy-starty,head.biBitCount,info.dwType); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + tmp.SetPalette(GetPalette(),head.biClrUsed); + tmp.info.nBkgndIndex = info.nBkgndIndex; + tmp.info.nBkgndColor = info.nBkgndColor; + + switch (head.biBitCount) { + case 1: + case 4: + { + for(long y=starty, yd=0; y + for(long x=startx, xd=0; x> 3; + BYTE* pDest = tmp.info.pImage; + BYTE* pSrc = info.pImage + starty * info.dwEffWidth + (startx*head.biBitCount >> 3); + for(long y=starty; y + memcpy(pDest,pSrc,linelen); + pDest+=tmp.info.dwEffWidth; + pSrc+=info.dwEffWidth; + } + } + } + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ // + tmp.AlphaCreate(); + if (!tmp.AlphaIsValid()) return false; + BYTE* pDest = tmp.pAlpha; + BYTE* pSrc = pAlpha + startx + starty*head.biWidth; + for (long y=starty; yTransfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \param xgain, ygain : can be from 0 to 1. + * \param xpivot, ypivot : is the center of the transformation. + * \param bEnableInterpolation : if true, enables bilinear interpolation. + * \return true if everything is ok + */ +bool CxImage::Skew(float xgain, float ygain, long xpivot, long ypivot, bool bEnableInterpolation) +{ + if (!pDib) return false; + float nx,ny; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + long xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + for(long y=ymin; y top) || (x < left) || (x > right)) { + tmp.SetPixelIndex(x,y, pixel); + } else { + tmp.SetPixelIndex(x,y,GetPixelIndex(x-left,y-bottom)); + } + } + } + break; + } + case 8: + case 24: + { + if (head.biBitCount == 8) { + BYTE pixel = tmp.GetNearestIndex( canvascolor); + memset(tmp.info.pImage, pixel, + (tmp.info.dwEffWidth * newHeight)); + } else { + for (long y = 0; y < newHeight; ++y) { + BYTE *pDest = tmp.info.pImage + (y * tmp.info.dwEffWidth); + for (long x = 0; x < newWidth; ++x) { + *pDest++ = canvascolor.rgbBlue; + *pDest++ = canvascolor.rgbGreen; + *pDest++ = canvascolor.rgbRed; + } + } + } + + BYTE* pDest = tmp.info.pImage + (tmp.info.dwEffWidth * bottom) + (left*(head.biBitCount >> 3)); + BYTE* pSrc = info.pImage; + for(long y=bottom; y <= top; y++){ + info.nProgress = (long)(100*y/(1 + top - bottom)); + memcpy(pDest,pSrc,(head.biBitCount >> 3) * (right - left + 1)); + pDest+=tmp.info.dwEffWidth; + pSrc+=info.dwEffWidth; + } + } + } + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()){ + if (!tmp.SelectionCreate()) + return false; + BYTE* pSrc = SelectionGetPointer(); + BYTE* pDst = tmp.SelectionGetPointer(left,bottom); + for(long y=bottom; y <= top; y++){ + memcpy(pDst,pSrc, (right - left + 1)); + pSrc+=head.biWidth; + pDst+=tmp.head.biWidth; + } + tmp.info.rSelectionBox.left = info.rSelectionBox.left + left; + tmp.info.rSelectionBox.right = info.rSelectionBox.right + left; + tmp.info.rSelectionBox.top = info.rSelectionBox.top + bottom; + tmp.info.rSelectionBox.bottom = info.rSelectionBox.bottom + bottom; + } +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + if (!tmp.AlphaCreate()) + return false; + tmp.AlphaSet(canvascolor.rgbReserved); + BYTE* pSrc = AlphaGetPointer(); + BYTE* pDst = tmp.AlphaGetPointer(left,bottom); + for(long y=bottom; y <= top; y++){ + memcpy(pDst,pSrc, (right - left + 1)); + pSrc+=head.biWidth; + pDst+=tmp.head.biWidth; + } + } +#endif //CXIMAGE_SUPPORT_ALPHA + + //select the destination + if (iDst) iDst->Transfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Expand(long newx, long newy, RGBQUAD canvascolor, CxImage* iDst) +{ + //thanks to + + if (!pDib) return false; + + if ((newx < head.biWidth) || (newy < head.biHeight)) return false; + + int nAddLeft = (newx - head.biWidth) / 2; + int nAddTop = (newy - head.biHeight) / 2; + + return Expand(nAddLeft, nAddTop, newx - (head.biWidth + nAddLeft), newy - (head.biHeight + nAddTop), canvascolor, iDst); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Resamples the image with the correct aspect ratio, and fills the borders. + * \param newx, newy = thumbnail size. + * \param canvascolor = border color. + * \param iDst = pointer to destination image (if it's 0, this image is modified). + * \return true if everything is ok. + * \author [Colin Urquhart] + */ +bool CxImage::Thumbnail(long newx, long newy, RGBQUAD canvascolor, CxImage* iDst) +{ + if (!pDib) return false; + + if ((newx <= 0) || (newy <= 0)) return false; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + // determine whether we need to shrink the image + if ((head.biWidth > newx) || (head.biHeight > newy)) { + float fScale; + float fAspect = (float) newx / (float) newy; + if (fAspect * head.biHeight > head.biWidth) { + fScale = (float) newy / head.biHeight; + } else { + fScale = (float) newx / head.biWidth; + } + tmp.Resample((long) (fScale * head.biWidth), (long) (fScale * head.biHeight), 0); + } + + // expand the frame + tmp.Expand(newx, newy, canvascolor, iDst); + + //select the destination + if (iDst) iDst->Transfer(tmp); + else Transfer(tmp); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Perform circle_based transformations. + * \param type - for different transformations + * - 0 for normal (proturberant) FishEye + * - 1 for reverse (concave) FishEye + * - 2 for Swirle + * - 3 for Cilinder mirror + * - 4 for bathroom + * + * \param rmax - effect radius. If 0, the whole image is processed + * \param Koeff - only for swirle + * \author Arkadiy Olovyannikov ark(at)msun(dot)ru + */ +bool CxImage::CircleTransform(int type,long rmax,float Koeff) +{ + if (!pDib) return false; + + long nx,ny; + double angle,radius,rnew; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + long xmin,xmax,ymin,ymax,xmid,ymid; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + xmid = (long) (tmp.GetWidth()/2); + ymid = (long) (tmp.GetHeight()/2); + + if (!rmax) rmax=(long)sqrt((float)((xmid-xmin)*(xmid-xmin)+(ymid-ymin)*(ymid-ymin))); + if (Koeff==0.0f) Koeff=1.0f; + + for(long y=ymin; yhead.biWidth || newy>head.biHeight) { + //let me repeat... this method can't enlarge image + strcpy(info.szLastError,"QIShrink can't enlarge image"); + return false; + } + + if (newx==head.biWidth && newy==head.biHeight) { + //image already correct size (just copy and return) + if (iDst) iDst->Copy(*this); + return true; + }//if + + //create temporary destination image + CxImage newImage; + newImage.CopyInfo(*this); + newImage.Create(newx,newy,(bChangeBpp)?24:head.biBitCount,GetType()); + newImage.SetPalette(GetPalette()); + if (!newImage.IsValid()){ + strcpy(info.szLastError,newImage.GetLastError()); + return false; + } + + //and alpha channel if required +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) newImage.AlphaCreate(); +#endif + + const int oldx = head.biWidth; + const int oldy = head.biHeight; + + int accuCellSize = 4; +#if CXIMAGE_SUPPORT_ALPHA + BYTE *alphaPtr; + if (AlphaIsValid()) accuCellSize=5; +#endif + + unsigned int *accu = new unsigned int[newx*accuCellSize]; //array for suming pixels... one pixel for every destination column + unsigned int *accuPtr; //pointer for walking through accu + //each cell consists of blue, red, green component and count of pixels summed in this cell + memset(accu, 0, newx * accuCellSize * sizeof(unsigned int)); //clear accu + + if (!IsIndexed()) { + //RGB24 version with pointers + BYTE *destPtr, *srcPtr, *destPtrS, *srcPtrS; //destination and source pixel, and beginnings of current row + srcPtrS=(BYTE*)BlindGetPixelPointer(0,0); + destPtrS=(BYTE*)newImage.BlindGetPixelPointer(0,0); + int ex=0, ey=0; //ex and ey replace division... + int dy=0; + //(we just add pixels, until by adding newx or newy we get a number greater than old size... then + // it's time to move to next pixel) + + for(int y=0; yoldx) { //when we reach oldx, it's time to move to new slot + accuPtr += accuCellSize; + ex -= oldx; //(substract oldx from ex and resume from there on) + }//if (ex overflow) + }//for x + + if (ey>=oldy) { //now when this happens + ey -= oldy; //it's time to move to new destination row + destPtr = destPtrS; //reset pointers to proper initial values + accuPtr = accu; +#if CXIMAGE_SUPPORT_ALPHA + alphaPtr = newImage.AlphaGetPointer(0, dy++); +#endif + for (int k=0; koldx) { //when we reach oldx, it's time to move to new slot + accuPtr += accuCellSize; + ex -= oldx; //(substract oldx from ex and resume from there on) + }//if (ex overflow) + }//for x + + if (ey>=oldy) { //now when this happens + ey -= oldy; //it's time to move to new destination row + accuPtr = accu; + for (int dx=0; dxTransfer(newImage); + else + Transfer(newImage); + return true; + +} + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_TRANSFORMATION diff --git a/CxImage/ximawnd.cpp b/CxImage/ximawnd.cpp new file mode 100644 index 00000000..8e804b47 --- /dev/null +++ b/CxImage/ximawnd.cpp @@ -0,0 +1,1510 @@ +// xImaWnd.cpp : Windows functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 6.0.0 02/Feb/2008 + */ + +#include "ximage.h" + +#include "ximaiter.h" +#include "ximabmp.h" + +//////////////////////////////////////////////////////////////////////////////// +#if defined (_WIN32_WCE) + +#ifndef DEFAULT_GUI_FONT +#define DEFAULT_GUI_FONT 17 +#endif + +#ifndef PROOF_QUALITY +#define PROOF_QUALITY 2 +#endif + +struct DIBINFO : public BITMAPINFO +{ + RGBQUAD arColors[255]; // Color table info - adds an extra 255 entries to palette + operator LPBITMAPINFO() { return (LPBITMAPINFO) this; } + operator LPBITMAPINFOHEADER() { return &bmiHeader; } + RGBQUAD* ColorTable() { return bmiColors; } +}; + +int BytesPerLine(int nWidth, int nBitsPerPixel) +{ + return ( (nWidth * nBitsPerPixel + 31) & (~31) ) / 8; +} + +int NumColorEntries(int nBitsPerPixel, int nCompression, DWORD biClrUsed) +{ + int nColors = 0; + switch (nBitsPerPixel) + { + case 1: + nColors = 2; break; + case 2: + nColors = 4; break; // winCE only + case 4: + nColors = 16; break; + case 8: + nColors =256; break; + case 24: + nColors = 0; break; + case 16: + case 32: + nColors = 3; break; // I've found that PocketPCs need this regardless of BI_RGB or BI_BITFIELDS + default: + ASSERT(FALSE); + } + // If biClrUsed is provided, and it is a legal value, use it + if (biClrUsed > 0 && biClrUsed <= (DWORD)nColors) + return biClrUsed; + + return nColors; +} + +int GetDIBits( + HDC hdc, // handle to DC + HBITMAP hbmp, // handle to bitmap + UINT uStartScan, // first scan line to set + UINT cScanLines, // number of scan lines to copy + LPVOID lpvBits, // array for bitmap bits + LPBITMAPINFO lpbi, // bitmap data buffer + UINT uUsage // RGB or palette index +) +{ + UINT iColorTableSize = 0; + + if (!hbmp) + return 0; + + // Get dimensions of bitmap + BITMAP bm; + if (!::GetObject(hbmp, sizeof(bm),(LPVOID)&bm)) + return 0; + + //3. Creating new bitmap and receive pointer to it's bits. + HBITMAP hTargetBitmap; + void *pBuffer; + + //3.1 Initilize DIBINFO structure + DIBINFO dibInfo; + dibInfo.bmiHeader.biBitCount = 24; + dibInfo.bmiHeader.biClrImportant = 0; + dibInfo.bmiHeader.biClrUsed = 0; + dibInfo.bmiHeader.biCompression = 0; + dibInfo.bmiHeader.biHeight = bm.bmHeight; + dibInfo.bmiHeader.biPlanes = 1; + dibInfo.bmiHeader.biSize = 40; + dibInfo.bmiHeader.biSizeImage = bm.bmHeight*BytesPerLine(bm.bmWidth,24); + dibInfo.bmiHeader.biWidth = bm.bmWidth; + dibInfo.bmiHeader.biXPelsPerMeter = 3780; + dibInfo.bmiHeader.biYPelsPerMeter = 3780; + dibInfo.bmiColors[0].rgbBlue = 0; + dibInfo.bmiColors[0].rgbGreen = 0; + dibInfo.bmiColors[0].rgbRed = 0; + dibInfo.bmiColors[0].rgbReserved = 0; + + //3.2 Create bitmap and receive pointer to points into pBuffer + HDC hDC = ::GetDC(NULL); + ASSERT(hDC); + hTargetBitmap = CreateDIBSection( + hDC, + (const BITMAPINFO*)dibInfo, + DIB_RGB_COLORS, + (void**)&pBuffer, + NULL, + 0); + + ::ReleaseDC(NULL, hDC); + + //4. Copy source bitmap into the target bitmap. + + //4.1 Create 2 device contexts + HDC memDc = CreateCompatibleDC(NULL); + if (!memDc) { + ASSERT(FALSE); + } + + HDC targetDc = CreateCompatibleDC(NULL); + if (!targetDc) { + ASSERT(FALSE); + } + + //4.2 Select source bitmap into one DC, target into another + HBITMAP hOldBitmap1 = (HBITMAP)::SelectObject(memDc, hbmp); + HBITMAP hOldBitmap2 = (HBITMAP)::SelectObject(targetDc, hTargetBitmap); + + //4.3 Copy source bitmap into the target one + BitBlt(targetDc, 0, 0, bm.bmWidth, bm.bmHeight, memDc, 0, 0, SRCCOPY); + + //4.4 Restore device contexts + ::SelectObject(memDc, hOldBitmap1); + ::SelectObject(targetDc, hOldBitmap2); + DeleteDC(memDc); + DeleteDC(targetDc); + + //Here we can bitmap bits: pBuffer. Note: + // 1. pBuffer contains 3 bytes per point + // 2. Lines ane from the bottom to the top! + // 3. Points in the line are from the left to the right + // 4. Bytes in one point are BGR (blue, green, red) not RGB + // 5. Don't delete pBuffer, it will be automatically deleted + // when delete hTargetBitmap + lpvBits = pBuffer; + + DeleteObject(hbmp); + //DeleteObject(hTargetBitmap); + + return 1; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_WINDOWS +//////////////////////////////////////////////////////////////////////////////// +long CxImage::Blt(HDC pDC, long x, long y) +{ + if((pDib==0)||(pDC==0)||(!info.bEnabled)) return 0; + + HBRUSH brImage = CreateDIBPatternBrushPt(pDib, DIB_RGB_COLORS); + POINT pt; + SetBrushOrgEx(pDC,x,y,&pt); // + HBRUSH brOld = (HBRUSH) SelectObject(pDC, brImage); + PatBlt(pDC, x, y, head.biWidth, head.biHeight, PATCOPY); + SelectObject(pDC, brOld); + SetBrushOrgEx(pDC,pt.x,pt.y,NULL); + DeleteObject(brImage); + return 1; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Transfer the image in a global bitmap handle (clipboard copy) + */ +HANDLE CxImage::CopyToHandle() +{ + HANDLE hMem=NULL; + if (pDib){ + hMem= GlobalAlloc(GHND, GetSize()); + if (hMem){ + BYTE* pDst=(BYTE*)GlobalLock(hMem); + if (pDst){ + memcpy(pDst,pDib,GetSize()); + } + GlobalUnlock(hMem); + } + } + return hMem; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Global object (clipboard paste) constructor + * \param hMem: source bitmap object, the clipboard format must be CF_DIB + * \return true if everything is ok + */ +bool CxImage::CreateFromHANDLE(HANDLE hMem) +{ + if (!Destroy()) + return false; + + DWORD dwSize = GlobalSize(hMem); + if (!dwSize) return false; + + BYTE *lpVoid; //pointer to the bitmap + lpVoid = (BYTE *)GlobalLock(hMem); + BITMAPINFOHEADER *pHead; //pointer to the bitmap header + pHead = (BITMAPINFOHEADER *)lpVoid; + if (lpVoid){ + + //CxMemFile hFile(lpVoid,dwSize); + + //copy the bitmap header + memcpy(&head,pHead,sizeof(BITMAPINFOHEADER)); + //check if it's a top-down bitmap + bool bTopDownDib = head.biHeight<0; + if (bTopDownDib) head.biHeight=-head.biHeight; + //create the image + if(!Create(head.biWidth,head.biHeight,head.biBitCount)){ + GlobalUnlock(lpVoid); + return false; + } + //preserve DPI + SetXDPI((long)floor(head.biXPelsPerMeter * 254.0 / 10000.0 + 0.5)); + SetYDPI((long)floor(head.biYPelsPerMeter * 254.0 / 10000.0 + 0.5)); + + /*//copy the pixels (old way) + if((pHead->biCompression != BI_RGB) || (pHead->biBitCount == 32)){ // + // BITFIELD case + // set the internal header in the dib + memcpy(pDib,&head,sizeof(head)); + // get the bitfield masks + DWORD bf[3]; + memcpy(bf,lpVoid+pHead->biSize,12); + // transform into RGB + Bitfield2RGB(lpVoid+pHead->biSize+12,bf[0],bf[1],bf[2],(BYTE)pHead->biBitCount); + } else { //normal bitmap + memcpy(pDib,lpVoid,GetSize()); + }*/ + + // + // fill in color map + bool bIsOldBmp = (head.biSize == sizeof(BITMAPCOREHEADER)); + RGBQUAD *pRgb = GetPalette(); + if (pRgb) { + // number of colors to fill in + int nColors = DibNumColors(pHead); + if (bIsOldBmp) { + /* get pointer to BITMAPCOREINFO (old style 1.x) */ + LPBITMAPCOREINFO lpbmc = (LPBITMAPCOREINFO)lpVoid; + for (int i = nColors - 1; i >= 0; i--) { + pRgb[i].rgbRed = lpbmc->bmciColors[i].rgbtRed; + pRgb[i].rgbGreen = lpbmc->bmciColors[i].rgbtGreen; + pRgb[i].rgbBlue = lpbmc->bmciColors[i].rgbtBlue; + pRgb[i].rgbReserved = (BYTE)0; + } + } else { + /* get pointer to BITMAPINFO (new style 3.x) */ + LPBITMAPINFO lpbmi = (LPBITMAPINFO)lpVoid; + for (int i = nColors - 1; i >= 0; i--) { + pRgb[i].rgbRed = lpbmi->bmiColors[i].rgbRed; + pRgb[i].rgbGreen = lpbmi->bmiColors[i].rgbGreen; + pRgb[i].rgbBlue = lpbmi->bmiColors[i].rgbBlue; + pRgb[i].rgbReserved = (BYTE)0; + } + } + } + + // + DWORD dwCompression = pHead->biCompression; + // compressed bitmap ? + if(dwCompression!=BI_RGB || pHead->biBitCount==32 || pHead->biBitCount ==16) { + // get the bitmap bits + LPSTR lpDIBBits = (LPSTR)((BYTE*)pHead + *(DWORD*)pHead + (WORD)(GetNumColors() * sizeof(RGBQUAD))); + // decode and copy them to our image + switch (pHead->biBitCount) { + case 32 : + { + // BITFIELD case + if (dwCompression == BI_BITFIELDS || dwCompression == BI_RGB) { + // get the bitfield masks + DWORD bf[3]; + memcpy(bf,lpVoid+pHead->biSize,12); + // transform into RGB + Bitfield2RGB(lpVoid+pHead->biSize+12,bf[0],bf[1],bf[2],(BYTE)pHead->biBitCount); + } else { + // "unknown compression"; + GlobalUnlock(lpVoid); + return false; + } + } + break; + case 16 : + { + // get the bitfield masks + long offset=0; + DWORD bf[3]; + if (dwCompression == BI_BITFIELDS) { + memcpy(bf,lpVoid+pHead->biSize,12); + offset= 12; + } else { + bf[0] = 0x7C00; + bf[1] = 0x3E0; + bf[2] = 0x1F; // RGB555 + } + // copy the pixels + memcpy(info.pImage, lpDIBBits + offset, head.biHeight*((head.biWidth+1)/2)*4); + // transform into RGB + Bitfield2RGB(info.pImage, bf[0], bf[1], bf[2], 16); + } + break; + case 8 : + case 4 : + case 1 : + { + switch (dwCompression) { + case BI_RLE4: + { + BYTE status_byte = 0; + BYTE second_byte = 0; + int scanline = 0; + int bits = 0; + BOOL low_nibble = FALSE; + CImageIterator iter(this); + + for (BOOL bContinue = TRUE; bContinue; ) { + status_byte = *(lpDIBBits++); + switch (status_byte) { + case RLE_COMMAND : + status_byte = *(lpDIBBits++); + switch (status_byte) { + case RLE_ENDOFLINE : + bits = 0; + scanline++; + low_nibble = FALSE; + break; + case RLE_ENDOFBITMAP : + bContinue = FALSE; + break; + case RLE_DELTA : + { + // read the delta values + BYTE delta_x; + BYTE delta_y; + delta_x = *(lpDIBBits++); + delta_y = *(lpDIBBits++); + // apply them + bits += delta_x / 2; + scanline += delta_y; + break; + } + default : + second_byte = *(lpDIBBits++); + BYTE* sline = iter.GetRow(scanline); + for (int i = 0; i < status_byte; i++) { + if ((BYTE*)(sline+bits) < (BYTE*)(info.pImage+head.biSizeImage)){ + if (low_nibble) { + if (i&1) + *(sline + bits) |= (second_byte & 0x0f); + else + *(sline + bits) |= (second_byte & 0xf0)>>4; + bits++; + } else { + if (i&1) + *(sline + bits) = (BYTE)(second_byte & 0x0f)<<4; + else + *(sline + bits) = (BYTE)(second_byte & 0xf0); + } + } + + if ((i & 1) && (i != (status_byte - 1))) + second_byte = *(lpDIBBits++); + + low_nibble = !low_nibble; + } + if ((((status_byte+1) >> 1) & 1 ) == 1) + second_byte = *(lpDIBBits++); + break; + }; + break; + default : + { + BYTE* sline = iter.GetRow(scanline); + second_byte = *(lpDIBBits++); + for (unsigned i = 0; i < status_byte; i++) { + if ((BYTE*)(sline+bits) < (BYTE*)(info.pImage+head.biSizeImage)){ + if (low_nibble) { + if (i&1) + *(sline + bits) |= (second_byte & 0x0f); + else + *(sline + bits) |= (second_byte & 0xf0)>>4; + bits++; + } else { + if (i&1) + *(sline + bits) = (BYTE)(second_byte & 0x0f)<<4; + else + *(sline + bits) = (BYTE)(second_byte & 0xf0); + } + } + low_nibble = !low_nibble; + } + } + break; + }; + } + } + break; + case BI_RLE8 : + { + BYTE status_byte = 0; + BYTE second_byte = 0; + int scanline = 0; + int bits = 0; + CImageIterator iter(this); + + for (BOOL bContinue = TRUE; bContinue; ) { + status_byte = *(lpDIBBits++); + if (status_byte==RLE_COMMAND) { + status_byte = *(lpDIBBits++); + switch (status_byte) { + case RLE_ENDOFLINE : + bits = 0; + scanline++; + break; + case RLE_ENDOFBITMAP : + bContinue = FALSE; + break; + case RLE_DELTA : + { + // read the delta values + BYTE delta_x; + BYTE delta_y; + delta_x = *(lpDIBBits++); + delta_y = *(lpDIBBits++); + // apply them + bits += delta_x; + scanline += delta_y; + } + break; + default : + int nNumBytes = sizeof(BYTE) * status_byte; + memcpy((void *)(iter.GetRow(scanline) + bits), lpDIBBits, nNumBytes); + lpDIBBits += nNumBytes; + // align run length to even number of bytes + if ((status_byte & 1) == 1) + second_byte = *(lpDIBBits++); + bits += status_byte; + break; + }; + } else { + BYTE *sline = iter.GetRow(scanline); + second_byte = *(lpDIBBits++); + for (unsigned i = 0; i < status_byte; i++) { + if ((DWORD)bits + // // Create a device-independent bitmap + // return CreateBitmap(head.biWidth,head.biHeight, 1, head.biBitCount, GetBits()); + // use instead this code + HDC hMemDC = CreateCompatibleDC(NULL); + LPVOID pBit32; + HBITMAP bmp = CreateDIBSection(hMemDC,(LPBITMAPINFO)pDib,DIB_RGB_COLORS, &pBit32, NULL, 0); + if (pBit32) memcpy(pBit32, GetBits(), head.biSizeImage); + DeleteDC(hMemDC); + return bmp; + } + + // this single line seems to work very well + //HBITMAP bmp = CreateDIBitmap(hdc, (LPBITMAPINFOHEADER)pDib, CBM_INIT, + // GetBits(), (LPBITMAPINFO)pDib, DIB_RGB_COLORS); + // this alternative works also with _WIN32_WCE + LPVOID pBit32; + HBITMAP bmp = CreateDIBSection(hdc, (LPBITMAPINFO)pDib, DIB_RGB_COLORS, &pBit32, NULL, 0); + if (pBit32) memcpy(pBit32, GetBits(), head.biSizeImage); + + return bmp; +} + + +//////////////////////////////////////////////////////////////////////////////// +/** +* Transfer the image in a icon handle, with transparency. +* \param hdc: target device context (the screen, usually) +* \return icon handle, or NULL if an error occurs. +* \author [brunom] +*/ +HICON CxImage::MakeIcon(HDC hdc /* = NULL */) +{ + HICON hDestIcon = 0; + + ICONINFO csDest; + + csDest.fIcon = TRUE; + csDest.xHotspot = 0; + csDest.yHotspot = 0; + + // Assign HBITMAP with Transparency to ICON Info structure + csDest.hbmColor = MakeBitmap( hdc, true ); + + // Create Mask just in case we need a Mask for the Icons + CxImage a_Mask; + GetTransparentMask(&a_Mask); + + // Assign Mask + csDest.hbmMask = a_Mask.MakeBitmap(); + + // Create Icon + hDestIcon = ::CreateIconIndirect(&csDest); + + return hDestIcon; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Bitmap resource constructor + * \param hbmp : bitmap resource handle + * \param hpal : (optional) palette, useful for 8bpp DC + * \return true if everything is ok + */ +// extened version posted by brunom on cximage forum +bool CxImage::CreateFromHBITMAP(HBITMAP hbmp, HPALETTE hpal) +{ + if (!Destroy()) + return false; + + if (hbmp) { + BITMAP bm; + // get informations about the bitmap + GetObject(hbmp, sizeof(BITMAP), (LPSTR) &bm); + + // Transparency in HBITMAP + if(bm.bmBitsPixel == 32) + { + bool l_bResult = true; + + BITMAPINFO l_BitmapInfo; + l_BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + l_BitmapInfo.bmiHeader.biWidth = bm.bmWidth; + l_BitmapInfo.bmiHeader.biHeight = bm.bmHeight; + l_BitmapInfo.bmiHeader.biPlanes = bm.bmPlanes; + l_BitmapInfo.bmiHeader.biBitCount = bm.bmBitsPixel; + l_BitmapInfo.bmiHeader.biCompression = BI_RGB; + + RGBQUAD *l_pRawBytes = new RGBQUAD[bm.bmWidth * bm.bmHeight]; + + HDC dc = ::GetDC(NULL); + + if(dc) + { + if(GetDIBits(dc, hbmp, 0, bm.bmHeight, l_pRawBytes, &l_BitmapInfo, DIB_RGB_COLORS)) + l_bResult = CreateFromArray((BYTE*)l_pRawBytes, bm.bmWidth, bm.bmHeight, bm.bmBitsPixel, bm.bmWidthBytes, false); + else + l_bResult = false; + + ::ReleaseDC(NULL, dc); + } + else + l_bResult = false; + + delete [] l_pRawBytes; + + return l_bResult; + } + else + { + // create the image + if (!Create(bm.bmWidth, bm.bmHeight, bm.bmBitsPixel, 0)) + return false; + // create a device context for the bitmap + HDC dc = ::GetDC(NULL); + if (!dc) + return false; + + if (hpal){ + SelectObject(dc,hpal); //the palette you should get from the user or have a stock one + RealizePalette(dc); + } + + // copy the pixels + if (GetDIBits(dc, hbmp, 0, head.biHeight, info.pImage, + (LPBITMAPINFO)pDib, DIB_RGB_COLORS) == 0){ //replace &head with pDib + strcpy(info.szLastError,"GetDIBits failed"); + ::ReleaseDC(NULL, dc); + return false; + } + ::ReleaseDC(NULL, dc); + return true; + + } + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * icon resource constructor + * \param hico : icon resource handle + * \return true if everything is ok + * \author []; changes [Arlen Albert Keshabian] + */ +#if !defined (_WIN32_WCE) +bool CxImage::CreateFromHICON(HICON hico) +{ + if (!Destroy() || !hico) + return false; + + bool l_bResult = true; + + ICONINFO iinfo; + GetIconInfo(hico,&iinfo); + + BITMAP l_Bitmap; + GetObject(iinfo.hbmColor, sizeof(BITMAP), &l_Bitmap); + + l_bResult = CreateFromHBITMAP( iinfo.hbmColor ); + +#if CXIMAGE_SUPPORT_ALPHA + if(l_bResult && (l_Bitmap.bmBitsPixel != 32) ) + { + CxImage mask; + mask.CreateFromHBITMAP(iinfo.hbmMask); + mask.GrayScale(); + mask.Negative(); + AlphaSet(mask); + } +#endif + + DeleteObject(iinfo.hbmColor); // + DeleteObject(iinfo.hbmMask); // + + return l_bResult; +} +#endif //_WIN32_WCE +//////////////////////////////////////////////////////////////////////////////// +long CxImage::Draw(HDC hdc, const RECT& rect, RECT* pClipRect, bool bSmooth) +{ + return Draw(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pClipRect,bSmooth); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Draws the image in the specified device context, with support for alpha channel, alpha palette, transparency, opacity. + * \param hdc : destination device context + * \param x,y : (optional) offset + * \param cx,cy : (optional) size. + * - If cx or cy are not specified (or less than 0), the normal width or height will be used + * - If cx or cy are different than width or height, the image will be stretched + * + * \param pClipRect : limit the drawing operations inside a given rectangle in the output device context. + * \param bSmooth : activates a bilinear filter that will enhance the appearence for zommed pictures. + * Quite slow. Needs CXIMAGE_SUPPORT_INTERPOLATION. + * \return true if everything is ok + */ +long CxImage::Draw(HDC hdc, long x, long y, long cx, long cy, RECT* pClipRect, bool bSmooth) +{ + if((pDib==0)||(hdc==0)||(cx==0)||(cy==0)||(!info.bEnabled)) return 0; + + if (cx < 0) cx = head.biWidth; + if (cy < 0) cy = head.biHeight; + bool bTransparent = info.nBkgndIndex >= 0; + bool bAlpha = pAlpha != 0; + + //required for MM_ANISOTROPIC, MM_HIENGLISH, and similar modes [Greg Peatfield] + int hdc_Restore = ::SaveDC(hdc); + if (!hdc_Restore) + return 0; + +#if !defined (_WIN32_WCE) + RECT mainbox; // (experimental) + if (pClipRect){ + GetClipBox(hdc,&mainbox); + HRGN rgn = CreateRectRgnIndirect(pClipRect); + ExtSelectClipRgn(hdc,rgn,RGN_AND); + DeleteObject(rgn); + } +#endif + + //find the smallest area to paint + RECT clipbox,paintbox; + GetClipBox(hdc,&clipbox); + + paintbox.top = min(clipbox.bottom,max(clipbox.top,y)); + paintbox.left = min(clipbox.right,max(clipbox.left,x)); + paintbox.right = max(clipbox.left,min(clipbox.right,x+cx)); + paintbox.bottom = max(clipbox.top,min(clipbox.bottom,y+cy)); + + long destw = paintbox.right - paintbox.left; + long desth = paintbox.bottom - paintbox.top; + + if (!(bTransparent || bAlpha || info.bAlphaPaletteEnabled)){ + if (cx==head.biWidth && cy==head.biHeight){ //NORMAL +#if !defined (_WIN32_WCE) + SetStretchBltMode(hdc,COLORONCOLOR); +#endif + SetDIBitsToDevice(hdc, x, y, cx, cy, 0, 0, 0, cy, + info.pImage,(BITMAPINFO*)pDib,DIB_RGB_COLORS); + } else { //STRETCH + //pixel informations + RGBQUAD c={0,0,0,0}; + //Preparing Bitmap Info + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=destw; + bmInfo.bmiHeader.biHeight=desth; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + BYTE *pbase; //points to the final dib + BYTE *pdst; //current pixel from pbase + BYTE *ppix; //current pixel from image + //get the background + HDC TmpDC=CreateCompatibleDC(hdc); + HBITMAP TmpBmp=CreateDIBSection(hdc,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + + if (pbase){ + long xx,yy; + long sx,sy; + float dx,dy; + BYTE *psrc; + + long ew = ((((24 * destw) + 31) / 32) * 4); + long ymax = paintbox.bottom; + long xmin = paintbox.left; + float fx=(float)head.biWidth/(float)cx; + float fy=(float)head.biHeight/(float)cy; + + for(yy=0;yy 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + if (head.biClrUsed){ + c=GetPaletteColor(GetPixelIndex(sx,sy)); + } else { + ppix = psrc + sx*3; + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix; + } + } + *pdst++=c.rgbBlue; + *pdst++=c.rgbGreen; + *pdst++=c.rgbRed; + } + } + } + //paint the image & cleanup + SetDIBitsToDevice(hdc,paintbox.left,paintbox.top,destw,desth,0,0,0,desth,pbase,&bmInfo,0); + DeleteObject(SelectObject(TmpDC,TmpObj)); + DeleteDC(TmpDC); + } + } else { // draw image with transparent/alpha blending + ////////////////////////////////////////////////////////////////// + //Alpha blend - Thanks to Florian Egel + + //pixel informations + RGBQUAD c={0,0,0,0}; + RGBQUAD ct = GetTransColor(); + long* pc = (long*)&c; + long* pct= (long*)&ct; + long cit = GetTransIndex(); + long ci = 0; + + //Preparing Bitmap Info + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=destw; + bmInfo.bmiHeader.biHeight=desth; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + + BYTE *pbase; //points to the final dib + BYTE *pdst; //current pixel from pbase + BYTE *ppix; //current pixel from image + + //get the background + HDC TmpDC=CreateCompatibleDC(hdc); + HBITMAP TmpBmp=CreateDIBSection(hdc,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + BitBlt(TmpDC,0,0,destw,desth,hdc,paintbox.left,paintbox.top,SRCCOPY); + + if (pbase){ + long xx,yy,alphaoffset,ix,iy; + BYTE a,a1,*psrc; + long ew = ((((24 * destw) + 31) / 32) * 4); + long ymax = paintbox.bottom; + long xmin = paintbox.left; + + if (cx!=head.biWidth || cy!=head.biHeight){ + //STRETCH + float fx=(float)head.biWidth/(float)cx; + float fy=(float)head.biHeight/(float)cy; + float dx,dy; + long sx,sy; + + for(yy=0;yy>8); + + if (head.biClrUsed){ + ci = GetPixelIndex(sx,sy); +#if CXIMAGE_SUPPORT_INTERPOLATION + if (bSmooth){ + if (fx > 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + c = GetPaletteColor(GetPixelIndex(sx,sy)); + } + if (info.bAlphaPaletteEnabled){ + a = (BYTE)((a*(1+c.rgbReserved))>>8); + } + } else { +#if CXIMAGE_SUPPORT_INTERPOLATION + if (bSmooth){ + if (fx > 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + ppix = psrc + sx*3; + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix; + } + } + //if (*pc!=*pct || !bTransparent){ + //if ((head.biClrUsed && ci!=cit) || ((!head.biClrUsed||bSmooth) && *pc!=*pct) || !bTransparent){ + if ((head.biClrUsed && ci!=cit) || (!head.biClrUsed && *pc!=*pct) || !bTransparent){ + // DJT, assume many pixels are fully transparent or opaque and thus avoid multiplication + if (a == 0) { // Transparent, retain dest + pdst+=3; + } else if (a == 255) { // opaque, ignore dest + *pdst++= c.rgbBlue; + *pdst++= c.rgbGreen; + *pdst++= c.rgbRed; + } else { // semi transparent + a1=(BYTE)~a; + *pdst++=(BYTE)((*pdst * a1 + a * c.rgbBlue)>>8); + *pdst++=(BYTE)((*pdst * a1 + a * c.rgbGreen)>>8); + *pdst++=(BYTE)((*pdst * a1 + a * c.rgbRed)>>8); + } + } else { + pdst+=3; + } + } + } + } else { + //NORMAL + iy=head.biHeight-ymax+y; + for(yy=0;yy>8); + + if (head.biClrUsed){ + ci = GetPixelIndex(ix,iy); + c = GetPaletteColor((BYTE)ci); + if (info.bAlphaPaletteEnabled){ + a = (BYTE)((a*(1+c.rgbReserved))>>8); + } + } else { + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix++; + } + + //if (*pc!=*pct || !bTransparent){ + if ((head.biClrUsed && ci!=cit) || (!head.biClrUsed && *pc!=*pct) || !bTransparent){ + // DJT, assume many pixels are fully transparent or opaque and thus avoid multiplication + if (a == 0) { // Transparent, retain dest + pdst+=3; + } else if (a == 255) { // opaque, ignore dest + *pdst++= c.rgbBlue; + *pdst++= c.rgbGreen; + *pdst++= c.rgbRed; + } else { // semi transparent + a1=(BYTE)~a; + *pdst++=(BYTE)((*pdst * a1 + a * c.rgbBlue)>>8); + *pdst++=(BYTE)((*pdst * a1 + a * c.rgbGreen)>>8); + *pdst++=(BYTE)((*pdst * a1 + a * c.rgbRed)>>8); + } + } else { + pdst+=3; + } + } + } + } + } + //paint the image & cleanup + SetDIBitsToDevice(hdc,paintbox.left,paintbox.top,destw,desth,0,0,0,desth,pbase,&bmInfo,0); + DeleteObject(SelectObject(TmpDC,TmpObj)); + DeleteDC(TmpDC); + } + +#if !defined (_WIN32_WCE) + if (pClipRect){ // (experimental) + HRGN rgn = CreateRectRgnIndirect(&mainbox); + ExtSelectClipRgn(hdc,rgn,RGN_OR); + DeleteObject(rgn); + } +#endif + + ::RestoreDC(hdc,hdc_Restore); + return 1; +} +//////////////////////////////////////////////////////////////////////////////// +long CxImage::Draw2(HDC hdc, const RECT& rect) +{ + return Draw2(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Draws (stretch) the image with single transparency support + * \param hdc : destination device context + * \param x,y : (optional) offset + * \param cx,cy : (optional) size. + * - If cx or cy are not specified (or less than 0), the normal width or height will be used + * - If cx or cy are different than width or height, the image will be stretched + * + * \return true if everything is ok + */ +long CxImage::Draw2(HDC hdc, long x, long y, long cx, long cy) +{ + if((pDib==0)||(hdc==0)||(cx==0)||(cy==0)||(!info.bEnabled)) return 0; + if (cx < 0) cx = head.biWidth; + if (cy < 0) cy = head.biHeight; + bool bTransparent = (info.nBkgndIndex >= 0); + + //required for MM_ANISOTROPIC, MM_HIENGLISH, and similar modes [Greg Peatfield] + int hdc_Restore = ::SaveDC(hdc); + if (!hdc_Restore) + return 0; + + if (!bTransparent){ +#if !defined (_WIN32_WCE) + SetStretchBltMode(hdc,COLORONCOLOR); +#endif + StretchDIBits(hdc, x, y, cx, cy, 0, 0, head.biWidth, head.biHeight, + info.pImage,(BITMAPINFO*)pDib, DIB_RGB_COLORS,SRCCOPY); + } else { + // draw image with transparent background + const int safe = 0; // or else GDI fails in the following - sometimes + RECT rcDst = {x+safe, y+safe, x+cx, y+cy}; + if (RectVisible(hdc, &rcDst)){ + ///////////////////////////////////////////////////////////////// + // True Mask Method - Thanks to Paul Reynolds and Ron Gery + int nWidth = head.biWidth; + int nHeight = head.biHeight; + // Create two memory dcs for the image and the mask + HDC dcImage=CreateCompatibleDC(hdc); + HDC dcTrans=CreateCompatibleDC(hdc); + // Select the image into the appropriate dc + HBITMAP bm = CreateCompatibleBitmap(hdc, nWidth, nHeight); + HBITMAP pOldBitmapImage = (HBITMAP)SelectObject(dcImage,bm); +#if !defined (_WIN32_WCE) + SetStretchBltMode(dcImage,COLORONCOLOR); +#endif + StretchDIBits(dcImage, 0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight, + info.pImage,(BITMAPINFO*)pDib,DIB_RGB_COLORS,SRCCOPY); + + // Create the mask bitmap + HBITMAP bitmapTrans = CreateBitmap(nWidth, nHeight, 1, 1, NULL); + // Select the mask bitmap into the appropriate dc + HBITMAP pOldBitmapTrans = (HBITMAP)SelectObject(dcTrans, bitmapTrans); + // Build mask based on transparent colour + RGBQUAD rgbBG; + if (head.biBitCount<24) rgbBG = GetPaletteColor((BYTE)info.nBkgndIndex); + else rgbBG = info.nBkgndColor; + COLORREF crColour = RGB(rgbBG.rgbRed, rgbBG.rgbGreen, rgbBG.rgbBlue); + COLORREF crOldBack = SetBkColor(dcImage,crColour); + BitBlt(dcTrans,0, 0, nWidth, nHeight, dcImage, 0, 0, SRCCOPY); + + // Do the work - True Mask method - cool if not actual display + StretchBlt(hdc,x, y,cx,cy, dcImage, 0, 0, nWidth, nHeight, SRCINVERT); + StretchBlt(hdc,x, y,cx,cy, dcTrans, 0, 0, nWidth, nHeight, SRCAND); + StretchBlt(hdc,x, y,cx,cy, dcImage, 0, 0, nWidth, nHeight, SRCINVERT); + + // Restore settings + SelectObject(dcImage,pOldBitmapImage); + SelectObject(dcTrans,pOldBitmapTrans); + SetBkColor(hdc,crOldBack); + DeleteObject( bitmapTrans ); // RG 29/01/2002 + DeleteDC(dcImage); + DeleteDC(dcTrans); + DeleteObject(bm); + } + } + ::RestoreDC(hdc,hdc_Restore); + return 1; +} +//////////////////////////////////////////////////////////////////////////////// +long CxImage::Stretch(HDC hdc, const RECT& rect, DWORD dwRop) +{ + return Stretch(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, dwRop); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Stretch the image. Obsolete: use Draw() or Draw2() + * \param hdc : destination device context + * \param xoffset,yoffset : (optional) offset + * \param xsize,ysize : size. + * \param dwRop : raster operation code (see BitBlt documentation) + * \return true if everything is ok + */ +long CxImage::Stretch(HDC hdc, long xoffset, long yoffset, long xsize, long ysize, DWORD dwRop) +{ + if((pDib)&&(hdc)) { + //palette must be correctly filled +#if !defined (_WIN32_WCE) + SetStretchBltMode(hdc,COLORONCOLOR); +#endif + StretchDIBits(hdc, xoffset, yoffset, + xsize, ysize, 0, 0, head.biWidth, head.biHeight, + info.pImage,(BITMAPINFO*)pDib,DIB_RGB_COLORS,dwRop); + return 1; + } + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Tiles the device context in the specified rectangle with the image. + * \param hdc : destination device context + * \param rc : tiled rectangle in the output device context + * \return true if everything is ok + */ +long CxImage::Tile(HDC hdc, RECT *rc) +{ + if((pDib)&&(hdc)&&(rc)) { + int w = rc->right - rc->left; + int h = rc->bottom - rc->top; + int x,y,z; + int bx=head.biWidth; + int by=head.biHeight; + for (y = 0 ; y < h ; y += by){ + if ((y+by)>h) by=h-y; + z=bx; + for (x = 0 ; x < w ; x += z){ + if ((x+z)>w) z=w-x; + RECT r = {rc->left + x,rc->top + y,rc->left + x + z,rc->top + y + by}; + Draw(hdc,rc->left + x, rc->top + y,-1,-1,&r); + } + } + return 1; + } + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +// For UNICODE support: char -> TCHAR +long CxImage::DrawString(HDC hdc, long x, long y, const TCHAR* text, RGBQUAD color, const TCHAR* font, long lSize, long lWeight, BYTE bItalic, BYTE bUnderline, bool bSetAlpha) +//long CxImage::DrawString(HDC hdc, long x, long y, const char* text, RGBQUAD color, const char* font, long lSize, long lWeight, BYTE bItalic, BYTE bUnderline, bool bSetAlpha) +{ + if (IsValid()){ + //get the background + HDC pDC; + if (hdc) pDC=hdc; else pDC = ::GetDC(0); + if (pDC==NULL) return 0; + HDC TmpDC=CreateCompatibleDC(pDC); + if (hdc==NULL) ::ReleaseDC(0, pDC); + if (TmpDC==NULL) return 0; + //choose the font + HFONT m_Font; + LOGFONT* m_pLF; + m_pLF=(LOGFONT*)calloc(1,sizeof(LOGFONT)); + _tcsncpy(m_pLF->lfFaceName,font,31); // For UNICODE support + //strncpy(m_pLF->lfFaceName,font,31); + m_pLF->lfHeight=lSize; + m_pLF->lfWeight=lWeight; + m_pLF->lfItalic=bItalic; + m_pLF->lfUnderline=bUnderline; + m_Font=CreateFontIndirect(m_pLF); + //select the font in the dc + HFONT pOldFont=NULL; + if (m_Font) + pOldFont = (HFONT)SelectObject(TmpDC,m_Font); + else + pOldFont = (HFONT)SelectObject(TmpDC,GetStockObject(DEFAULT_GUI_FONT)); + + //Set text color + SetTextColor(TmpDC,RGB(255,255,255)); + SetBkColor(TmpDC,RGB(0,0,0)); + //draw the text + SetBkMode(TmpDC,OPAQUE); + //Set text position; + RECT pos = {0,0,0,0}; + //long len = (long)strlen(text); + long len = (long)_tcslen(text); // For UNICODE support + ::DrawText(TmpDC,text,len,&pos,DT_CALCRECT); + pos.right+=pos.bottom; //for italics + + //Preparing Bitmap Info + long width=pos.right; + long height=pos.bottom; + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=width; + bmInfo.bmiHeader.biHeight=height; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + BYTE *pbase; //points to the final dib + + HBITMAP TmpBmp=CreateDIBSection(TmpDC,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + memset(pbase,0,height*((((24 * width) + 31) / 32) * 4)); + + ::DrawText(TmpDC,text,len,&pos,0); + + CxImage itext; + itext.CreateFromHBITMAP(TmpBmp); + + y=head.biHeight-y-1; + for (long ix=0;ix +long CxImage::DrawStringEx(HDC hdc, long x, long y, CXTEXTINFO *pTextType, bool bSetAlpha ) +{ + if (!IsValid()) + return -1; + + //get the background + HDC pDC; + if (hdc) pDC=hdc; else pDC = ::GetDC(0); + if (pDC==NULL) return 0; + HDC TmpDC=CreateCompatibleDC(pDC); + if (hdc==NULL) ::ReleaseDC(0, pDC); + if (TmpDC==NULL) return 0; + + //choose the font + HFONT m_Font; + m_Font=CreateFontIndirect( &pTextType->lfont ); + + // get colors in RGBQUAD + RGBQUAD p_forecolor = RGBtoRGBQUAD(pTextType->fcolor); + RGBQUAD p_backcolor = RGBtoRGBQUAD(pTextType->bcolor); + + // check alignment and re-set default if necessary + if ( pTextType->align != DT_CENTER && + pTextType->align != DT_LEFT && + pTextType->align != DT_RIGHT ) + pTextType->align = DT_CENTER; + + // check rounding radius and re-set default if necessary + if ( pTextType->b_round > 50 ) + pTextType->b_round = 10; + + // check opacity and re-set default if necessary + if ( pTextType->b_opacity > 1. || pTextType->b_opacity < .0 ) + pTextType->b_opacity = 0.; + + //select the font in the dc + HFONT pOldFont=NULL; + if (m_Font) + pOldFont = (HFONT)SelectObject(TmpDC,m_Font); + else + pOldFont = (HFONT)SelectObject(TmpDC,GetStockObject(DEFAULT_GUI_FONT)); + + //Set text color + SetTextColor(TmpDC,RGB(255,255,255)); + SetBkColor(TmpDC,RGB(0,0,0)); + SetBkMode(TmpDC,OPAQUE); + //Set text position; + RECT pos = {0,0,0,0}; + + // get text length and number of lines + long i=0, numlines=1, len=(long)_tcsclen(pTextType->text); + while (itext[i++]==13 ) + numlines++; + } + + ::DrawText(TmpDC, pTextType->text, len, &pos, /*DT_EDITCONTROL|DT_EXTERNALLEADING|*/DT_NOPREFIX | DT_CALCRECT ); + + // increase only if it's really italics, and only one line height + if ( pTextType->lfont.lfItalic ) + pos.right += pos.bottom/2/numlines; + + // background frame and rounding radius + int frame = 0, roundR = 0; + if ( pTextType->opaque ) + { + roundR= (int)(pos.bottom/numlines * pTextType->b_round / 100 ) ; + frame = (int)(/*3.5 + */0.29289*roundR ) ; + pos.right += pos.bottom/numlines/3 ; // JUST FOR BEAUTY + } + + //Preparing Bitmap Info + long width=pos.right +frame*2; + long height=pos.bottom +frame*2; + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=width; + bmInfo.bmiHeader.biHeight=height; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + BYTE *pbase; //points to the final dib + + HBITMAP TmpBmp=CreateDIBSection(TmpDC,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + memset(pbase,0,height*((((24 * width) + 31) / 32) * 4)); + + ::DrawText(TmpDC,pTextType->text,len, &pos, /*DT_EDITCONTROL|DT_EXTERNALLEADING|*/DT_NOPREFIX| pTextType->align ); + + CxImage itext; + itext.CreateFromHBITMAP(TmpBmp); + y=head.biHeight-y-1; + + itext.Negative(); +#if CXIMAGE_SUPPORT_DSP + if (pTextType->smooth==FALSE){ + itext.Threshold(128); + } else { + //itext.TextBlur(); + } +#endif + + //move the insertion point according to alignment type + // DT_CENTER: cursor points to the center of text rectangle + // DT_RIGHT: cursor points to right side end of text rectangle + // DT_LEFT: cursor points to left end of text rectangle + if ( pTextType->align == DT_CENTER ) + x -= width/2; + else if ( pTextType->align == DT_RIGHT ) + x -= width; + if (x<0) x=0; + + //draw the background first, if it exists + long ix,iy; + if ( pTextType->opaque ) + { + int ixf=0; + for (ix=0;ix=width-roundR-1 ) + ixf = (int)(.5+roundR-sqrt((float)(roundR*roundR-(width-1-ix-roundR)*(width-1-ix-roundR)))); + else + ixf=0; + + for (iy=0;iy height-ixf-1 || iy < ixf )) || + (ix>=width-roundR-1 && ( iy > height-ixf-1 || iy < ixf )) ) + continue; + else + if ( pTextType->b_opacity > 0.0 && pTextType->b_opacity < 1.0 ) + { + RGBQUAD bcolor, pcolor; + // calculate a transition color from original image to background color: + pcolor = GetPixelColor(x+ix,y+iy); + bcolor.rgbBlue = (unsigned char)(pTextType->b_opacity * pcolor.rgbBlue + (1.0-pTextType->b_opacity) * p_backcolor.rgbBlue ); + bcolor.rgbRed = (unsigned char)(pTextType->b_opacity * pcolor.rgbRed + (1.0-pTextType->b_opacity) * p_backcolor.rgbRed ) ; + bcolor.rgbGreen = (unsigned char)(pTextType->b_opacity * pcolor.rgbGreen + (1.0-pTextType->b_opacity) * p_backcolor.rgbGreen ) ; + bcolor.rgbReserved = 0; + SetPixelColor(x+ix,y+iy,bcolor,bSetAlpha); + } + else + SetPixelColor(x+ix,y+iy,p_backcolor,bSetAlpha); + } + } + } + + // draw the text itself + for (ix=0;ixlfont.lfHeight = -36; + txt->lfont.lfCharSet = EASTEUROPE_CHARSET; // just for Central-European users + txt->lfont.lfWeight = FW_NORMAL; + txt->lfont.lfWidth = 0; + txt->lfont.lfEscapement = 0; + txt->lfont.lfOrientation = 0; + txt->lfont.lfItalic = FALSE; + txt->lfont.lfUnderline = FALSE; + txt->lfont.lfStrikeOut = FALSE; + txt->lfont.lfOutPrecision = OUT_DEFAULT_PRECIS; + txt->lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + txt->lfont.lfQuality = PROOF_QUALITY; + txt->lfont.lfPitchAndFamily= DEFAULT_PITCH | FF_DONTCARE ; + _stprintf( txt->lfont.lfFaceName, _T("Arial")); //use TCHAR mappings + + // initial colors + txt->fcolor = RGB( 255,255,160 ); // default foreground: light goldyellow + txt->bcolor = RGB( 0, 80,160 ); // default background: light blue + + // background + txt->opaque = TRUE; // text has a non-transparent background; + txt->smooth = TRUE; + txt->b_opacity = 0.0; // default: opaque background + txt->b_outline = 0; // default: no outline (OUTLINE NOT IMPLEMENTED AT THIS TIME) + txt->b_round = 20; // default: rounding radius is 20% of the rectangle height + // the text + _stprintf( txt->text, _T("Sample Text 01234õû")); // text use TCHAR mappings + txt->align = DT_CENTER; + return; +} + +#if CXIMAGE_SUPPORT_LAYERS +//////////////////////////////////////////////////////////////////////////////// +long CxImage::LayerDrawAll(HDC hdc, const RECT& rect, RECT* pClipRect, bool bSmooth) +{ + return LayerDrawAll(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pClipRect,bSmooth); +} +//////////////////////////////////////////////////////////////////////////////// +long CxImage::LayerDrawAll(HDC hdc, long x, long y, long cx, long cy, RECT* pClipRect, bool bSmooth) +{ + long n=0; + CxImage* pLayer; + while(pLayer=GetLayer(n++)){ + if (pLayer->Draw(hdc,x+pLayer->info.xOffset,y+pLayer->info.yOffset,cx,cy,pClipRect,bSmooth)==0) + return 0; + if (pLayer->LayerDrawAll(hdc,x+pLayer->info.xOffset,y+pLayer->info.yOffset,cx,cy,pClipRect,bSmooth)==0) + return 0; + } + return 1; +} +#endif //CXIMAGE_SUPPORT_LAYERS + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_WINDOWS +//////////////////////////////////////////////////////////////////////////////// diff --git a/CxImage/xiofile.h b/CxImage/xiofile.h new file mode 100644 index 00000000..592c6d6e --- /dev/null +++ b/CxImage/xiofile.h @@ -0,0 +1,125 @@ +#if !defined(__xiofile_h) +#define __xiofile_h + +#include "xfile.h" +//#include + +class DLL_EXP CxIOFile : public CxFile + { +public: + CxIOFile(FILE* fp = NULL) + { + m_fp = fp; + m_bCloseFile = (bool)(fp==0); + } + + ~CxIOFile() + { + Close(); + } +////////////////////////////////////////////////////////// + bool Open(LPCTSTR filename, LPCTSTR mode) + { + if (m_fp) return false; // Can't re-open without closing first + + m_fp = _tfopen(filename, mode); + if (!m_fp) return false; + + m_bCloseFile = true; + + return true; + } +////////////////////////////////////////////////////////// + virtual bool Close() + { + int iErr = 0; + if ( (m_fp) && (m_bCloseFile) ){ + iErr = fclose(m_fp); + m_fp = NULL; + } + return (bool)(iErr==0); + } +////////////////////////////////////////////////////////// + virtual size_t Read(void *buffer, size_t size, size_t count) + { + if (!m_fp) return 0; + return fread(buffer, size, count, m_fp); + } +////////////////////////////////////////////////////////// + virtual size_t Write(const void *buffer, size_t size, size_t count) + { + if (!m_fp) return 0; + return fwrite(buffer, size, count, m_fp); + } +////////////////////////////////////////////////////////// + virtual bool Seek(long offset, int origin) + { + if (!m_fp) return false; + return (bool)(fseek(m_fp, offset, origin) == 0); + } +////////////////////////////////////////////////////////// + virtual long Tell() + { + if (!m_fp) return 0; + return ftell(m_fp); + } +////////////////////////////////////////////////////////// + virtual long Size() + { + if (!m_fp) return -1; + long pos,size; + pos = ftell(m_fp); + (void)fseek(m_fp, 0, SEEK_END); + size = ftell(m_fp); + (void)fseek(m_fp, pos,SEEK_SET); + return size; + } +////////////////////////////////////////////////////////// + virtual bool Flush() + { + if (!m_fp) return false; + return (bool)(fflush(m_fp) == 0); + } +////////////////////////////////////////////////////////// + virtual bool Eof() + { + if (!m_fp) return true; + return (bool)(feof(m_fp) != 0); + } +////////////////////////////////////////////////////////// + virtual long Error() + { + if (!m_fp) return -1; + return ferror(m_fp); + } +////////////////////////////////////////////////////////// + virtual bool PutC(unsigned char c) + { + if (!m_fp) return false; + return (bool)(fputc(c, m_fp) == c); + } +////////////////////////////////////////////////////////// + virtual long GetC() + { + if (!m_fp) return EOF; + return getc(m_fp); + } +////////////////////////////////////////////////////////// + virtual char * GetS(char *string, int n) + { + if (!m_fp) return NULL; + return fgets(string,n,m_fp); + } +////////////////////////////////////////////////////////// + virtual long Scanf(const char *format, void* output) + { + if (!m_fp) return EOF; + return fscanf(m_fp, format, output); + } +////////////////////////////////////////////////////////// +protected: + FILE *m_fp; + bool m_bCloseFile; + }; + +#endif diff --git a/CxImage/xmemfile.cpp b/CxImage/xmemfile.cpp new file mode 100644 index 00000000..af8db945 --- /dev/null +++ b/CxImage/xmemfile.cpp @@ -0,0 +1,202 @@ +#include "xmemfile.h" + +////////////////////////////////////////////////////////// +CxMemFile::CxMemFile(BYTE* pBuffer, DWORD size) +{ + m_pBuffer = pBuffer; + m_Position = 0; + m_Size = m_Edge = size; + m_bFreeOnClose = (bool)(pBuffer==0); +} +////////////////////////////////////////////////////////// +CxMemFile::~CxMemFile() +{ + Close(); +} +////////////////////////////////////////////////////////// +bool CxMemFile::Close() +{ + if ( (m_pBuffer) && (m_bFreeOnClose) ){ + free(m_pBuffer); + m_pBuffer = NULL; + m_Size = 0; + } + return true; +} +////////////////////////////////////////////////////////// +bool CxMemFile::Open() +{ + if (m_pBuffer) return false; // Can't re-open without closing first + + m_Position = m_Size = m_Edge = 0; + m_pBuffer=(BYTE*)malloc(1); + m_bFreeOnClose = true; + + return (m_pBuffer!=0); +} +////////////////////////////////////////////////////////// +BYTE* CxMemFile::GetBuffer(bool bDetachBuffer) +{ + //can only detach, avoid inadvertantly attaching to + // memory that may not be ours [Jason De Arte] + if( bDetachBuffer ) + m_bFreeOnClose = false; + return m_pBuffer; +} +////////////////////////////////////////////////////////// +size_t CxMemFile::Read(void *buffer, size_t size, size_t count) +{ + if (buffer==NULL) return 0; + + if (m_pBuffer==NULL) return 0; + if (m_Position >= (long)m_Size) return 0; + + long nCount = (long)(count*size); + if (nCount == 0) return 0; + + long nRead; + if (m_Position + nCount > (long)m_Size) + nRead = (m_Size - m_Position); + else + nRead = nCount; + + memcpy(buffer, m_pBuffer + m_Position, nRead); + m_Position += nRead; + + return (size_t)(nRead/size); +} +////////////////////////////////////////////////////////// +size_t CxMemFile::Write(const void *buffer, size_t size, size_t count) +{ + if (m_pBuffer==NULL) return 0; + if (buffer==NULL) return 0; + + long nCount = (long)(count*size); + if (nCount == 0) return 0; + + if (m_Position + nCount > m_Edge){ + if (!Alloc(m_Position + nCount)){ + return false; + } + } + + memcpy(m_pBuffer + m_Position, buffer, nCount); + + m_Position += nCount; + + if (m_Position > (long)m_Size) m_Size = m_Position; + + return count; +} +////////////////////////////////////////////////////////// +bool CxMemFile::Seek(long offset, int origin) +{ + if (m_pBuffer==NULL) return false; + long lNewPos = m_Position; + + if (origin == SEEK_SET) lNewPos = offset; + else if (origin == SEEK_CUR) lNewPos += offset; + else if (origin == SEEK_END) lNewPos = m_Size + offset; + else return false; + + if (lNewPos < 0) lNewPos = 0; + + m_Position = lNewPos; + return true; +} +////////////////////////////////////////////////////////// +long CxMemFile::Tell() +{ + if (m_pBuffer==NULL) return -1; + return m_Position; +} +////////////////////////////////////////////////////////// +long CxMemFile::Size() +{ + if (m_pBuffer==NULL) return -1; + return m_Size; +} +////////////////////////////////////////////////////////// +bool CxMemFile::Flush() +{ + if (m_pBuffer==NULL) return false; + return true; +} +////////////////////////////////////////////////////////// +bool CxMemFile::Eof() +{ + if (m_pBuffer==NULL) return true; + return (m_Position >= (long)m_Size); +} +////////////////////////////////////////////////////////// +long CxMemFile::Error() +{ + if (m_pBuffer==NULL) return -1; + return (m_Position > (long)m_Size); +} +////////////////////////////////////////////////////////// +bool CxMemFile::PutC(unsigned char c) +{ + if (m_pBuffer==NULL) return false; + + if (m_Position >= m_Edge){ + if (!Alloc(m_Position + 1)){ + return false; + } + } + + m_pBuffer[m_Position++] = c; + + if (m_Position > (long)m_Size) m_Size = m_Position; + + return true; +} +////////////////////////////////////////////////////////// +long CxMemFile::GetC() +{ + if (Eof()) return EOF; + return *(BYTE*)((BYTE*)m_pBuffer + m_Position++); +} +////////////////////////////////////////////////////////// +char * CxMemFile::GetS(char *string, int n) +{ + n--; + long c,i=0; + while (i (DWORD)m_Edge) + { + // find new buffer size + DWORD dwNewBufferSize = (DWORD)(((dwNewLen>>16)+1)<<16); + + // allocate new buffer + if (m_pBuffer == NULL) m_pBuffer = (BYTE*)malloc(dwNewBufferSize); + else m_pBuffer = (BYTE*)realloc(m_pBuffer, dwNewBufferSize); + // I own this buffer now (caller knows nothing about it) + m_bFreeOnClose = true; + + m_Edge = dwNewBufferSize; + } + return (m_pBuffer!=0); +} +////////////////////////////////////////////////////////// +void CxMemFile::Free() +{ + Close(); +} +////////////////////////////////////////////////////////// diff --git a/CxImage/xmemfile.h b/CxImage/xmemfile.h new file mode 100644 index 00000000..f87e4f9b --- /dev/null +++ b/CxImage/xmemfile.h @@ -0,0 +1,41 @@ +#if !defined(__xmemfile_h) +#define __xmemfile_h + +#include "xfile.h" + +////////////////////////////////////////////////////////// +class DLL_EXP CxMemFile : public CxFile +{ +public: + CxMemFile(BYTE* pBuffer = NULL, DWORD size = 0); + ~CxMemFile(); + + bool Open(); + BYTE* GetBuffer(bool bDetachBuffer = true); + + virtual bool Close(); + virtual size_t Read(void *buffer, size_t size, size_t count); + virtual size_t Write(const void *buffer, size_t size, size_t count); + virtual bool Seek(long offset, int origin); + virtual long Tell(); + virtual long Size(); + virtual bool Flush(); + virtual bool Eof(); + virtual long Error(); + virtual bool PutC(unsigned char c); + virtual long GetC(); + virtual char * GetS(char *string, int n); + virtual long Scanf(const char *format, void* output); + +protected: + bool Alloc(DWORD nBytes); + void Free(); + + BYTE* m_pBuffer; + DWORD m_Size; + bool m_bFreeOnClose; + long m_Position; //current position + long m_Edge; //buffer size +}; + +#endif diff --git a/DeadSourceList.cpp b/DeadSourceList.cpp new file mode 100644 index 00000000..9720eed4 --- /dev/null +++ b/DeadSourceList.cpp @@ -0,0 +1,172 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "StdAfx.h" +#include "deadsourcelist.h" +#include "preferences.h" +#include "opcodes.h" +#include "updownclient.h" +#include "partfile.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define CLEANUPTIME MIN2MS(60) + +#define BLOCKTIME (::GetTickCount() + (m_bGlobalList ? MIN2MS(15):MIN2MS(45))) +#define BLOCKTIMEFW (::GetTickCount() + (m_bGlobalList ? MIN2MS(30):MIN2MS(45))) + +/////////////////////////////////////////////////////////////////////////////////////// +//// CDeadSource + +CDeadSource::CDeadSource(uint32 dwID, uint16 nPort, uint32 dwServerIP, uint16 nKadPort){ + m_dwID = dwID; + m_dwServerIP = dwServerIP; + m_nPort = nPort; + m_nKadPort = nKadPort; + md4clr(m_aucHash); +} + +CDeadSource::CDeadSource(const uchar* paucHash){ + m_dwID = 0; + m_dwServerIP = 0; + m_nPort = 0; + m_nKadPort = 0; + md4cpy(m_aucHash, paucHash); + +} + +bool operator==(const CDeadSource& ds1,const CDeadSource& ds2){ + //ASSERT( ((ds1.m_dwID + ds1.m_dwServerIP) ^ isnulmd4(ds1.m_aucHash)) != 0 ); + //ASSERT( ((ds2.m_dwID + ds2.m_dwServerIP) ^ isnulmd4(ds2.m_aucHash)) != 0 ); + return ( + // lowid ed2k and highid kad + ed2k check + ( (ds1.m_dwID != 0 && ds1.m_dwID == ds2.m_dwID) && ((ds1.m_nPort != 0 && ds1.m_nPort == ds2.m_nPort) || (ds1.m_nKadPort != 0 && ds1.m_nKadPort == ds2.m_nKadPort)) && (ds1.m_dwServerIP == ds2.m_dwServerIP || !IsLowID(ds1.m_dwID)) ) + // lowid kad check + || ( IsLowID(ds1.m_dwID) && isnulmd4(ds1.m_aucHash) == FALSE && md4cmp(ds1.m_aucHash, ds2.m_aucHash) == 0) ); +} + +CDeadSource& CDeadSource::operator=(const CDeadSource& ds){ + m_dwID = ds.m_dwID; + m_dwServerIP = ds.m_dwServerIP; + m_nPort = ds.m_nPort; + m_nKadPort = ds.m_nKadPort; + md4cpy(m_aucHash, ds.m_aucHash); + return *this; +} + +/////////////////////////////////////////////////////////////////////////////////////// +//// CDeadSourceList + +CDeadSourceList::CDeadSourceList() +{ + m_dwLastCleanUp = 0; +} + +CDeadSourceList::~CDeadSourceList() +{ +} + +void CDeadSourceList::Init(bool bGlobalList){ + m_dwLastCleanUp = ::GetTickCount(); + if(bGlobalList){ + m_mapDeadSources.InitHashTable(3001); + } + else{ + m_mapDeadSources.InitHashTable(503); + } + m_bGlobalList = bGlobalList; +} + +bool CDeadSourceList::IsDeadSource(const CUpDownClient* pToCheck) const{ + uint32 dwExpTime; + bool bDbgCheck = false; + if(!pToCheck->HasLowID() || pToCheck->GetServerIP() != 0){ + if (m_mapDeadSources.Lookup(CDeadSource(pToCheck->GetUserIDHybrid(), pToCheck->GetUserPort(), pToCheck->GetServerIP(), pToCheck->GetKadPort()), dwExpTime)){ + if (dwExpTime > ::GetTickCount()) + return true; + } + bDbgCheck = true; + } + if (((pToCheck->HasValidBuddyID() || pToCheck->SupportsDirectUDPCallback()) && isnulmd4(pToCheck->GetUserHash()) == FALSE) || (pToCheck->HasLowID() && pToCheck->GetServerIP() == 0) ){ + if (m_mapDeadSources.Lookup(CDeadSource(pToCheck->GetUserHash()), dwExpTime)){ + if (dwExpTime > ::GetTickCount()) + return true; + } + bDbgCheck = true; + } + //ASSERT ( bDbgCheck ); + return false; +} + +void CDeadSourceList::AddDeadSource(const CUpDownClient* pToAdd){ + //if (thePrefs.GetLogFilteredIPs()) + // AddDebugLogLine(DLP_VERYLOW, false, _T("Added source to bad source list (%s) - file %s : %s") + // , m_bGlobalList? _T("Global"):_T("Local"), (pToAdd->GetRequestFile() != NULL)? pToAdd->GetRequestFile()->GetFileName() : _T("???"), pToAdd->DbgGetClientInfo() ); + + if(!pToAdd->HasLowID()) + m_mapDeadSources.SetAt(CDeadSource(pToAdd->GetUserIDHybrid(), pToAdd->GetUserPort(), pToAdd->GetServerIP(), pToAdd->GetKadPort()), BLOCKTIME ); + else{ + bool bDbgCheck = false; + if(pToAdd->GetServerIP() != 0){ + bDbgCheck = true; + m_mapDeadSources.SetAt(CDeadSource(pToAdd->GetUserIDHybrid(), pToAdd->GetUserPort(), pToAdd->GetServerIP(), 0), BLOCKTIMEFW); + } + if (pToAdd->HasValidBuddyID() || pToAdd->SupportsDirectUDPCallback()){ + bDbgCheck = true; + m_mapDeadSources.SetAt(CDeadSource(pToAdd->GetUserHash()), BLOCKTIMEFW); + } + //ASSERT( bDbgCheck ); + } + if (::GetTickCount() - m_dwLastCleanUp > CLEANUPTIME) + CleanUp(); +} + +void CDeadSourceList::RemoveDeadSource(const CUpDownClient* client) +{ + if (!client->HasLowID()) + m_mapDeadSources.RemoveKey(CDeadSource(client->GetUserIDHybrid(), client->GetUserPort(), client->GetServerIP(), client->GetKadPort())); + else + { + if (client->GetServerIP() != 0) + m_mapDeadSources.RemoveKey(CDeadSource(client->GetUserIDHybrid(), client->GetUserPort(), client->GetServerIP(), 0)); + if (client->HasValidBuddyID() || client->SupportsDirectUDPCallback()) + m_mapDeadSources.RemoveKey(CDeadSource(client->GetUserHash())); + } +} + +void CDeadSourceList::CleanUp(){ + m_dwLastCleanUp = ::GetTickCount(); + //if (thePrefs.GetLogFilteredIPs()) + // AddDebugLogLine(DLP_VERYLOW, false, _T("Cleaning up DeadSourceList (%s), %i clients on List..."), m_bGlobalList ? _T("Global") : _T("Local"), m_mapDeadSources.GetCount()); + POSITION pos = m_mapDeadSources.GetStartPosition(); + CDeadSource dsKey; + uint32 dwExpTime; + uint32 dwTick = ::GetTickCount(); + while (pos != NULL){ + m_mapDeadSources.GetNextAssoc( pos, dsKey, dwExpTime ); + if (dwExpTime < dwTick){ + m_mapDeadSources.RemoveKey(dsKey); + } + } + //if (thePrefs.GetLogFilteredIPs()) + // AddDebugLogLine(DLP_VERYLOW, false, _T("...done, %i clients left on list"), m_mapDeadSources.GetCount()); +} \ No newline at end of file diff --git a/DeadSourceList.h b/DeadSourceList.h new file mode 100644 index 00000000..05700d42 --- /dev/null +++ b/DeadSourceList.h @@ -0,0 +1,77 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "otherfunctions.h" + +/////////////////////////////////////////////////////////////////////////////////////// +//// CDeadSource + +class CDeadSource : public CObject{ +public: + CDeadSource(const CDeadSource& ds) {*this = ds;} + CDeadSource(uint32 dwID = 0, uint16 nPort = 0, uint32 dwServerIP = 0, uint16 nKadPort = 0); + CDeadSource(const uchar* paucHash); + + CDeadSource& operator=(const CDeadSource& ds); + friend bool operator==(const CDeadSource& ds1,const CDeadSource& ds2); + + uint32 m_dwID; + uint32 m_dwServerIP; + uint16 m_nPort; + uint16 m_nKadPort; + uchar m_aucHash[16]; +}; + +template<> inline UINT AFXAPI HashKey(const CDeadSource& ds){ + uint32 hash = 0; + if (ds.m_dwID != 0){ + hash = ds.m_dwID; + if (IsLowID(ds.m_dwID)) + hash ^= ds.m_dwServerIP; + } + else{ + ASSERT( isnulmd4(ds.m_aucHash) == 0 ); + hash++; + for (int i = 0;i != 16;i++) + hash += (ds.m_aucHash[i]+1)*((i*i)+1); + } + return hash; +}; + +/////////////////////////////////////////////////////////////////////////////////////// +//// CDeadSourceList +class CUpDownClient; +class CDeadSourceList +{ +public: + CDeadSourceList(void); + ~CDeadSourceList(void); + void AddDeadSource(const CUpDownClient* pToAdd); + void RemoveDeadSource(const CUpDownClient* client); + bool IsDeadSource(const CUpDownClient* pToCheck) const; + uint32 GetDeadSourcesCount() const { return m_mapDeadSources.GetCount(); } + void Init(bool bGlobalList); + +protected: + void CleanUp(); + +private: + CMap m_mapDeadSources; + uint32 m_dwLastCleanUp; + bool m_bGlobalList; +}; diff --git a/DebugHelpers.h b/DebugHelpers.h new file mode 100644 index 00000000..9fcc3663 --- /dev/null +++ b/DebugHelpers.h @@ -0,0 +1,14 @@ +#pragma once + +#define CHECK_OBJ(pObj) if (pObj != NULL) ASSERT_VALID(pObj) +#define CHECK_PTR(ptr) ASSERT( ptr == NULL || AfxIsValidAddress(ptr, sizeof(*ptr)) ); +#define CHECK_ARR(ptr, len) ASSERT( (ptr == NULL && len == 0) || (ptr != NULL && len != 0 && AfxIsValidAddress(ptr, len)) ); +#define CHECK_BOOL(bVal) ASSERT( (UINT)(bVal) == 0 || (UINT)(bVal) == 1 ); + +#define CRASH_HERE() (*((int*)NULL) = 0) + +#if defined(_DEBUG) || defined(_BETA) || defined(_DEVBUILD) +#ifndef NO_USE_CLIENT_TCP_CATCH_ALL_HANDLER +#define NO_USE_CLIENT_TCP_CATCH_ALL_HANDLER 1 +#endif +#endif diff --git a/Debug_FileSize.h b/Debug_FileSize.h new file mode 100644 index 00000000..a1ffac94 --- /dev/null +++ b/Debug_FileSize.h @@ -0,0 +1,134 @@ +//this file is part of eMule +//Copyright (C)2002-2005 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. +#pragma once +enum E_DebugFSAtion{ + DFSA_ADD = 0, + DFSA_SUB, + DFSA_MUL, + DFSA_DIV +}; + +class CEMFileSize { +public: + CEMFileSize() {m_nSize = (uint64)(-1);} + CEMFileSize(uint64 nSize) {m_nSize = nSize; Check();} + __declspec(deprecated) CEMFileSize(uint32 nSize) {m_nSize = nSize; Check();} + + CEMFileSize& operator=(const CEMFileSize& k1) {m_nSize = k1.m_nSize; Check(); return *this; } + CEMFileSize& operator=(uint64 k1) {m_nSize = k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator=(sint64 k1) {m_nSize = k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator=(uint32 k1) {m_nSize = k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator=(sint32 k1) {m_nSize = k1; Check(); return *this; } + + CEMFileSize& operator-=(const CEMFileSize& k1) {Check(); m_nSize -= k1.m_nSize; Check(); return *this; } + CEMFileSize& operator-=(uint64 k1) {Check(); m_nSize -= k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator-=(sint64 k1) {Check(); m_nSize -= k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator-=(uint32 k1) {Check(); m_nSize -= k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator-=(sint32 k1) {Check(); m_nSize -= k1; Check(); return *this; } + + CEMFileSize& operator+=(const CEMFileSize& k1) {Check(); m_nSize += k1.m_nSize; Check(); return *this; } + CEMFileSize& operator+=(uint64 k1) {Check(); m_nSize += k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator+=(sint64 k1) {Check(); m_nSize += k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator+=(uint32 k1) {Check(); m_nSize += k1; Check(); return *this; } + __declspec(deprecated) CEMFileSize& operator+=(sint32 k1) {Check(); m_nSize += k1; Check(); return *this; } + + + operator uint64() const {return m_nSize;} + operator double() const {return (double)m_nSize;} + /*__declspec(deprecated)*/ operator float() const {return (float)m_nSize;} + /*__declspec(deprecated)*/ operator sint64() const {return (sint64)m_nSize;} + __declspec(deprecated) operator uint32() const {ASSERT( m_nSize < 0xFFFFFFFF ); return (uint32)m_nSize;} + __declspec(deprecated) operator sint32() const {ASSERT( m_nSize < 0x7FFFFFFF ); return (sint32)m_nSize;} + + friend bool operator==(const CEMFileSize& k1,const CEMFileSize& k2) {return k1.m_nSize == k2.m_nSize;} + friend bool operator==(const CEMFileSize& k1,uint64 k2) {return k1.m_nSize == k2;} + friend bool operator==(uint64 k1,const CEMFileSize& k2) {return k1 == k2.m_nSize;} + __declspec(deprecated) friend bool operator==(uint32 k1,const CEMFileSize& k2) {return k1 == k2.m_nSize;} + __declspec(deprecated) friend bool operator==(const CEMFileSize& k1,uint32 k2) {return k1.m_nSize == k2;} + + friend bool operator!=(const CEMFileSize& k1,const CEMFileSize& k2) {return k1.m_nSize != k2.m_nSize;} + friend bool operator!=(const CEMFileSize& k1,uint64 k2) {return k1.m_nSize != k2;} + friend bool operator!=(uint64 k1,const CEMFileSize& k2) {return k1 != k2.m_nSize;} + __declspec(deprecated) friend bool operator!=(uint32 k1,const CEMFileSize& k2) {return k1 != k2.m_nSize;} + __declspec(deprecated) friend bool operator!=(const CEMFileSize& k1,uint32 k2) {return k1.m_nSize != k2;} + + friend bool operator>(const CEMFileSize& k1,const CEMFileSize& k2) {return k1.m_nSize > k2.m_nSize;} + friend bool operator>(const CEMFileSize& k1,uint64 k2) {return k1.m_nSize > k2;} + friend bool operator>(uint64 k1,const CEMFileSize& k2) {return k1 > k2.m_nSize;} + __declspec(deprecated) friend bool operator>(uint32 k1,const CEMFileSize& k2) {return k1 > k2.m_nSize;} + __declspec(deprecated) friend bool operator>(const CEMFileSize& k1,uint32 k2) {return k1.m_nSize > k2;} + + friend bool operator<(const CEMFileSize& k1,const CEMFileSize& k2) {return k1.m_nSize < k2.m_nSize;} + friend bool operator<(const CEMFileSize& k1,uint64 k2) {return k1.m_nSize < k2;} + friend bool operator<(uint64 k1,const CEMFileSize& k2) {return k1 < k2.m_nSize;} + __declspec(deprecated) friend bool operator<(uint32 k1,const CEMFileSize& k2) {return k1 < k2.m_nSize;} + __declspec(deprecated) friend bool operator<(const CEMFileSize& k1,uint32 k2) {return k1.m_nSize < k2;} + + friend bool operator>=(const CEMFileSize& k1,const CEMFileSize& k2) {return k1.m_nSize >= k2.m_nSize;} + friend bool operator>=(const CEMFileSize& k1,uint64 k2) {return k1.m_nSize >= k2;} + friend bool operator>=(uint64 k1,const CEMFileSize& k2) {return k1 >= k2.m_nSize;} + __declspec(deprecated) friend bool operator>=(uint32 k1,const CEMFileSize& k2) {return k1 >= k2.m_nSize;} + __declspec(deprecated) friend bool operator>=(const CEMFileSize& k1,uint32 k2) {return k1.m_nSize >= k2;} + + friend bool operator<=(const CEMFileSize& k1,const CEMFileSize& k2) {return k1.m_nSize <= k2.m_nSize;} + friend bool operator<=(const CEMFileSize& k1,uint64 k2) {return k1.m_nSize <= k2;} + friend bool operator<=(uint64 k1,const CEMFileSize& k2) {return k1 <= k2.m_nSize;} + __declspec(deprecated) friend bool operator<=(uint32 k1,const CEMFileSize& k2) {return k1 <= k2.m_nSize;} + __declspec(deprecated) friend bool operator<=(const CEMFileSize& k1,uint32 k2) {return k1.m_nSize <= k2;} + + friend CEMFileSize operator+(const CEMFileSize& k1,const CEMFileSize& k2) {return CEMFileSize(k1.m_nSize, k2.m_nSize, DFSA_ADD);} + friend CEMFileSize operator+(const CEMFileSize& k1,uint64 k2) {return CEMFileSize(k1.m_nSize, k2, DFSA_ADD);} + friend CEMFileSize operator+(uint64 k1,const CEMFileSize& k2) {return CEMFileSize(k1, k2.m_nSize, DFSA_ADD);} + + friend CEMFileSize operator-(const CEMFileSize& k1,const CEMFileSize& k2) {return CEMFileSize(k1.m_nSize, k2.m_nSize, DFSA_SUB);} + friend CEMFileSize operator-(const CEMFileSize& k1,uint64 k2) {return CEMFileSize(k1.m_nSize, k2, DFSA_SUB);} + friend CEMFileSize operator-(uint64 k1,const CEMFileSize& k2) {return CEMFileSize(k1, k2.m_nSize, DFSA_SUB);} + __declspec(deprecated) friend CEMFileSize operator-(uint32 k1,const CEMFileSize& k2) {return CEMFileSize(k1, k2.m_nSize, DFSA_SUB);} + + friend CEMFileSize operator*(const CEMFileSize& k1,const CEMFileSize& k2) {return CEMFileSize(k1.m_nSize, k2.m_nSize, DFSA_MUL);} + friend CEMFileSize operator*(const CEMFileSize& k1,uint64 k2) {return CEMFileSize(k1.m_nSize, k2, DFSA_MUL);} + friend CEMFileSize operator*(uint64 k1,const CEMFileSize& k2) {return CEMFileSize(k1, k2.m_nSize, DFSA_MUL);} + + friend CEMFileSize operator/(const CEMFileSize& k1,const CEMFileSize& k2) {return CEMFileSize(k1.m_nSize, k2.m_nSize, DFSA_DIV);} + friend CEMFileSize operator/(const CEMFileSize& k1,uint64 k2) {return CEMFileSize(k1.m_nSize, k2, DFSA_DIV);} + friend CEMFileSize operator/(uint64 k1,const CEMFileSize& k2) {return CEMFileSize(k1, k2.m_nSize, DFSA_DIV);} + +private: + CEMFileSize(uint64 nSize1, uint64 nSize2, E_DebugFSAtion edfsAction) { + if (edfsAction == DFSA_ADD){ + m_nSize = nSize1 + nSize2; + ASSERT( m_nSize >= nSize1 && m_nSize >= nSize2 && m_nSize <= 0x4000000000 ); + } + else if (edfsAction == DFSA_SUB){ + m_nSize = nSize1 - nSize2; + ASSERT( m_nSize <= nSize1 && m_nSize <= 0x4000000000 ); + } + else if (edfsAction == DFSA_DIV){ + if ( nSize2 != 0 ) + m_nSize = nSize1 / nSize2; + else + ASSERT( false ); + } + else if (edfsAction == DFSA_MUL){ + m_nSize = nSize1 * nSize2; + ASSERT( m_nSize >= nSize1 && m_nSize >= nSize2 && m_nSize <= 0x4000000000 ); + } + } + void Check() { ASSERT( m_nSize != (uint64)(-1) && m_nSize <= 0x4000000000 ); } + uint64 m_nSize; +}; + diff --git a/DialogMinTrayBtn.cpp b/DialogMinTrayBtn.cpp new file mode 100644 index 00000000..42e1ab88 --- /dev/null +++ b/DialogMinTrayBtn.cpp @@ -0,0 +1,536 @@ +// ------------------------------------------------------------ +// CDialogMinTrayBtn template class +// MFC CDialog with minimize to systemtray button (0.04) +// Supports WinXP styles (thanks to David Yuheng Zhao for CVisualStylesXP - yuheng_zhao@yahoo.com) +// ------------------------------------------------------------ +// DialogMinTrayBtn.hpp +// zegzav - 2002,2003 - eMule project (http://www.emule-project.net) +// ------------------------------------------------------------ +#include "stdafx.h" +#include "DialogMinTrayBtn.h" +#include "VisualStylesXP.h" +#include "ResizableLib\ResizableDialog.h" +#include "AfxBeginMsgMapTemplate.h" +#include "OtherFunctions.h" +#include "MenuCmds.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +static BOOL (WINAPI *s_pfnTransparentBlt)(HDC, int, int, int, int, HDC, int, int, int, int, UINT) = NULL; + +#if 0 +// define this to use that source file as template +#define TEMPLATE template +#else +// define this to instantiate functions for class 'BASE' right in this CPP module +#if _MSC_VER >= 1310 +#define TEMPLATE template <> +#else +#define TEMPLATE +#endif +#define BASE CResizableDialog +#endif + +// ------------------------------ +// constants +// ------------------------------ + +#define CAPTION_BUTTONSPACE (2) +#define CAPTION_MINHEIGHT (8) + +#define TIMERMINTRAYBTN_ID 0x76617a67 +#define TIMERMINTRAYBTN_PERIOD 200 // ms + +#define WP_TRAYBUTTON WP_MINBUTTON + +BEGIN_TM_PART_STATES(TRAYBUTTON) + TM_STATE(1, TRAYBS, NORMAL) + TM_STATE(2, TRAYBS, HOT) + TM_STATE(3, TRAYBS, PUSHED) + TM_STATE(4, TRAYBS, DISABLED) + // Inactive + TM_STATE(5, TRAYBS, INORMAL) + TM_STATE(6, TRAYBS, IHOT) + TM_STATE(7, TRAYBS, IPUSHED) + TM_STATE(8, TRAYBS, IDISABLED) +END_TM_PART_STATES() + +#define BMP_TRAYBTN_WIDTH (21) +#define BMP_TRAYBTN_HEIGHT (21) +#define BMP_TRAYBTN_BLUE _T("TRAYBTN_BLUE") +#define BMP_TRAYBTN_METALLIC _T("TRAYBTN_METALLIC") +#define BMP_TRAYBTN_HOMESTEAD _T("TRAYBTN_HOMESTEAD") +#define BMP_TRAYBTN_TRANSCOLOR (RGB(255,0,255)) + +TEMPLATE const TCHAR *CDialogMinTrayBtn::m_pszMinTrayBtnBmpName[] = { BMP_TRAYBTN_BLUE, BMP_TRAYBTN_METALLIC, BMP_TRAYBTN_HOMESTEAD }; + +#define VISUALSTYLESXP_DEFAULTFILE L"LUNA.MSSTYLES" +#define VISUALSTYLESXP_BLUE 0 +#define VISUALSTYLESXP_METALLIC 1 +#define VISUALSTYLESXP_HOMESTEAD 2 +#define VISUALSTYLESXP_NAMEBLUE L"NORMALCOLOR" +#define VISUALSTYLESXP_NAMEMETALLIC L"METALLIC" +#define VISUALSTYLESXP_NAMEHOMESTEAD L"HOMESTEAD" + +// _WIN32_WINNT >= 0x0501 (XP only) +#define _WM_THEMECHANGED 0x031A +#if _MFC_VER>=0x0800 +#define _ON_WM_THEMECHANGED() \ + { _WM_THEMECHANGED, 0, 0, 0, AfxSig_l, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: _OnThemeChanged)) }, +#else +#define _ON_WM_THEMECHANGED() \ + { _WM_THEMECHANGED, 0, 0, 0, AfxSig_l, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(void) > (_OnThemeChanged)) \ + }, +#endif + + +BEGIN_MESSAGE_MAP_TEMPLATE(TEMPLATE, CDialogMinTrayBtn, CDialogMinTrayBtn, BASE) + ON_WM_NCPAINT() + ON_WM_NCACTIVATE() + ON_WM_NCHITTEST() + ON_WM_NCLBUTTONDOWN() + ON_WM_NCRBUTTONDOWN() + ON_WM_LBUTTONUP() + ON_WM_MOUSEMOVE() + ON_WM_TIMER() + _ON_WM_THEMECHANGED() +END_MESSAGE_MAP() + + +TEMPLATE CDialogMinTrayBtn::CDialogMinTrayBtn() : + m_MinTrayBtnPos(0,0), m_MinTrayBtnSize(0,0), m_bMinTrayBtnEnabled(TRUE), m_bMinTrayBtnVisible(TRUE), + m_bMinTrayBtnUp(TRUE), m_bMinTrayBtnCapture(FALSE), m_bMinTrayBtnActive(FALSE), m_bMinTrayBtnHitTest(FALSE) +{ + MinTrayBtnInit(); +} + +TEMPLATE CDialogMinTrayBtn::CDialogMinTrayBtn(LPCTSTR lpszTemplateName, CWnd* pParentWnd) : BASE(lpszTemplateName, pParentWnd), + m_MinTrayBtnPos(0,0), m_MinTrayBtnSize(0,0), m_bMinTrayBtnEnabled(TRUE), m_bMinTrayBtnVisible(TRUE), + m_bMinTrayBtnUp(TRUE), m_bMinTrayBtnCapture(FALSE), m_bMinTrayBtnActive(FALSE), m_bMinTrayBtnHitTest(FALSE) +{ + MinTrayBtnInit(); +} + +TEMPLATE CDialogMinTrayBtn::CDialogMinTrayBtn(UINT nIDTemplate, CWnd* pParentWnd) : BASE(nIDTemplate, pParentWnd), + m_MinTrayBtnPos(0,0), m_MinTrayBtnSize(0,0), m_bMinTrayBtnEnabled(TRUE), m_bMinTrayBtnVisible(TRUE), + m_bMinTrayBtnUp(TRUE), m_bMinTrayBtnCapture(FALSE), m_bMinTrayBtnActive(FALSE), m_bMinTrayBtnHitTest(FALSE) +{ + MinTrayBtnInit(); +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnInit() +{ + m_nMinTrayBtnTimerId = 0; + BOOL bBmpResult = MinTrayBtnInitBitmap(); + // - Never use the 'TransparentBlt' function under Win9x (read SDK) + // - Load the 'MSIMG32.DLL' only, if it's really needed. + if (!afxIsWin95() && bBmpResult && !s_pfnTransparentBlt) + { + HMODULE hMsImg32 = LoadLibrary(_T("MSIMG32.DLL")); + if (hMsImg32) + { + (FARPROC &)s_pfnTransparentBlt = GetProcAddress(hMsImg32, "TransparentBlt"); + if (!s_pfnTransparentBlt) + FreeLibrary(hMsImg32); + } + } +} + +TEMPLATE BOOL CDialogMinTrayBtn::OnInitDialog() +{ + BOOL bReturn = BASE::OnInitDialog(); + InitWindowStyles(this); + m_nMinTrayBtnTimerId = SetTimer(TIMERMINTRAYBTN_ID, TIMERMINTRAYBTN_PERIOD, NULL); + return bReturn; +} + +TEMPLATE void CDialogMinTrayBtn::OnNcPaint() +{ + BASE::OnNcPaint(); + MinTrayBtnUpdatePosAndSize(); + MinTrayBtnDraw(); +} + +TEMPLATE BOOL CDialogMinTrayBtn::OnNcActivate(BOOL bActive) +{ + MinTrayBtnUpdatePosAndSize(); + BOOL bResult = BASE::OnNcActivate(bActive); + m_bMinTrayBtnActive = bActive; + MinTrayBtnDraw(); + return bResult; +} + +TEMPLATE +#if _MFC_VER>=0x0800 +LRESULT +#else +UINT +#endif +CDialogMinTrayBtn::OnNcHitTest(CPoint point) +{ + BOOL bPreviousHitTest = m_bMinTrayBtnHitTest; + m_bMinTrayBtnHitTest = MinTrayBtnHitTest(point); + if (!IsWindowsClassicStyle() && m_bMinTrayBtnHitTest != bPreviousHitTest) + MinTrayBtnDraw(); // Windows XP Style (hot button) + if (m_bMinTrayBtnHitTest) + return HTMINTRAYBUTTON; + return BASE::OnNcHitTest(point); +} + +TEMPLATE void CDialogMinTrayBtn::OnNcLButtonDown(UINT nHitTest, CPoint point) +{ + if ((GetStyle() & WS_DISABLED) || !MinTrayBtnIsEnabled() || !MinTrayBtnIsVisible() || !MinTrayBtnHitTest(point)) + { + BASE::OnNcLButtonDown(nHitTest, point); + return; + } + SetCapture(); + m_bMinTrayBtnCapture = TRUE; + MinTrayBtnSetDown(); +} + +TEMPLATE void CDialogMinTrayBtn::OnNcRButtonDown(UINT nHitTest, CPoint point) +{ + if ((GetStyle() & WS_DISABLED) || !MinTrayBtnIsVisible() || !MinTrayBtnHitTest(point)) + BASE::OnNcRButtonDown(nHitTest, point); +} + +TEMPLATE void CDialogMinTrayBtn::OnMouseMove(UINT nFlags, CPoint point) +{ + if ((GetStyle() & WS_DISABLED) || !m_bMinTrayBtnCapture) + { + BASE::OnMouseMove(nFlags, point); + return; + } + + ClientToScreen(&point); + m_bMinTrayBtnHitTest = MinTrayBtnHitTest(point); + if (m_bMinTrayBtnHitTest) + { + if (m_bMinTrayBtnUp) + MinTrayBtnSetDown(); + } + else + { + if (!m_bMinTrayBtnUp) + MinTrayBtnSetUp(); + } +} + +TEMPLATE void CDialogMinTrayBtn::OnLButtonUp(UINT nFlags, CPoint point) +{ + if ((GetStyle() & WS_DISABLED) || !m_bMinTrayBtnCapture) + { + BASE::OnLButtonUp(nFlags, point); + return; + } + + ReleaseCapture(); + m_bMinTrayBtnCapture = FALSE; + MinTrayBtnSetUp(); + + ClientToScreen(&point); + if (MinTrayBtnHitTest(point)) + SendMessage(WM_SYSCOMMAND, MP_MINIMIZETOTRAY, MAKELONG(point.x, point.y)); +} + +TEMPLATE void CDialogMinTrayBtn::OnTimer(UINT_PTR nIDEvent) +{ + if (!IsWindowsClassicStyle() && nIDEvent == m_nMinTrayBtnTimerId) + { + // Visual XP Style (hot button) + CPoint point; + GetCursorPos(&point); + BOOL bHitTest = MinTrayBtnHitTest(point); + if (m_bMinTrayBtnHitTest != bHitTest) + { + m_bMinTrayBtnHitTest = bHitTest; + MinTrayBtnDraw(); + } + } +} + +TEMPLATE LRESULT CDialogMinTrayBtn::_OnThemeChanged() +{ + // BASE::OnThemeChanged(); + MinTrayBtnInitBitmap(); + return 0; +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnUpdatePosAndSize() +{ + DWORD dwStyle = GetStyle(); + DWORD dwExStyle = GetExStyle(); + + INT cyCaption = ((dwExStyle & WS_EX_TOOLWINDOW) == 0) ? GetSystemMetrics(SM_CYCAPTION) - 1 : GetSystemMetrics(SM_CYSMCAPTION) - 1; + if (cyCaption < CAPTION_MINHEIGHT) + cyCaption = CAPTION_MINHEIGHT; + + CSize borderfixed(-GetSystemMetrics(SM_CXFIXEDFRAME), GetSystemMetrics(SM_CYFIXEDFRAME)); + CSize bordersize(-GetSystemMetrics(SM_CXSIZEFRAME), GetSystemMetrics(SM_CYSIZEFRAME)); + + CRect rcWnd; + GetWindowRect(&rcWnd); + + // get Windows' frame window button width/height (this may not always be a square!) + CSize szBtn; + szBtn.cy = cyCaption - (CAPTION_BUTTONSPACE * 2); + if (IsWindowsClassicStyle()) + szBtn.cx = GetSystemMetrics(SM_CXSIZE) - 2; + else + szBtn.cx = GetSystemMetrics(SM_CXSIZE) - 4; + + // set our frame window button width/height... + if (IsWindowsClassicStyle()){ + // ...this is same as Windows' buttons for non WinXP + m_MinTrayBtnSize = szBtn; + } + else{ + // ...this is a square for WinXP + m_MinTrayBtnSize.cx = szBtn.cy; + m_MinTrayBtnSize.cy = szBtn.cy; + } + + m_MinTrayBtnPos.x = rcWnd.Width() - (CAPTION_BUTTONSPACE + m_MinTrayBtnSize.cx + CAPTION_BUTTONSPACE + szBtn.cx); + m_MinTrayBtnPos.y = CAPTION_BUTTONSPACE; + + if ((dwStyle & WS_THICKFRAME) != 0) + { + // resizable window + m_MinTrayBtnPos += bordersize; + } + else + { + // fixed window + m_MinTrayBtnPos += borderfixed; + } + + if ( ((dwExStyle & WS_EX_TOOLWINDOW) == 0) && (((dwStyle & WS_MINIMIZEBOX) != 0) || ((dwStyle & WS_MAXIMIZEBOX) != 0)) ) + { + if (IsWindowsClassicStyle()) + m_MinTrayBtnPos.x -= (szBtn.cx * 2) + CAPTION_BUTTONSPACE; + else + m_MinTrayBtnPos.x -= (szBtn.cx + CAPTION_BUTTONSPACE) * 2; + } +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnShow() +{ + if (MinTrayBtnIsVisible()) + return; + + m_bMinTrayBtnVisible = TRUE; + if (IsWindowVisible()) + RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW); +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnHide() +{ + if (!MinTrayBtnIsVisible()) + return; + + m_bMinTrayBtnVisible = FALSE; + if (IsWindowVisible()) + RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW); +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnEnable() +{ + if (MinTrayBtnIsEnabled()) + return; + + m_bMinTrayBtnEnabled = TRUE; + MinTrayBtnSetUp(); +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnDisable() +{ + if (!MinTrayBtnIsEnabled()) + return; + + m_bMinTrayBtnEnabled = FALSE; + if (m_bMinTrayBtnCapture) + { + ReleaseCapture(); + m_bMinTrayBtnCapture = FALSE; + } + MinTrayBtnSetUp(); +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnDraw() +{ + if (!MinTrayBtnIsVisible()) + return; + + CDC *pDC= GetWindowDC(); + if (!pDC) + return; // panic! + + if (IsWindowsClassicStyle()) + { + CBrush black(GetSysColor(COLOR_BTNTEXT)); + CBrush gray(GetSysColor(COLOR_GRAYTEXT)); + CBrush gray2(GetSysColor(COLOR_BTNHILIGHT)); + + // button + if (m_bMinTrayBtnUp) + pDC->DrawFrameControl(MinTrayBtnGetRect(), DFC_BUTTON, DFCS_BUTTONPUSH); + else + pDC->DrawFrameControl(MinTrayBtnGetRect(), DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED); + + // dot + CRect btn = MinTrayBtnGetRect(); + btn.DeflateRect(2,2); + UINT caption = MinTrayBtnGetSize().cy + (CAPTION_BUTTONSPACE * 2); + UINT pixratio = (caption >= 14) ? ((caption >= 20) ? 2 + ((caption - 20) / 8) : 2) : 1; + UINT pixratio2 = (caption >= 12) ? 1 + (caption - 12) / 8: 0; + UINT dotwidth = (1 + pixratio * 3) >> 1; + UINT dotheight = pixratio; + CRect dot(CPoint(0,0), CPoint(dotwidth, dotheight)); + CSize spc((1 + pixratio2 * 3) >> 1, pixratio2); + dot -= dot.Size(); + dot += btn.BottomRight(); + dot -= spc; + if (!m_bMinTrayBtnUp) + dot += CPoint(1,1); + if (m_bMinTrayBtnEnabled) + { + pDC->FillRect(dot, &black); + } + else + { + pDC->FillRect(dot + CPoint(1,1), &gray2); + pDC->FillRect(dot, &gray); + } + } + else + { + // VisualStylesXP + CRect btn = MinTrayBtnGetRect(); + int iState; + if (!m_bMinTrayBtnEnabled) + iState = TRAYBS_DISABLED; + else if (GetStyle() & WS_DISABLED) + iState = MINBS_NORMAL; + else if (m_bMinTrayBtnHitTest) + iState = (m_bMinTrayBtnCapture) ? MINBS_PUSHED : MINBS_HOT; + else + iState = MINBS_NORMAL; + // inactive + if (!m_bMinTrayBtnActive) + iState += 4; // inactive state TRAYBS_Ixxx + + if (m_bmMinTrayBtnBitmap.m_hObject && s_pfnTransparentBlt) + { + // known theme (bitmap) + CBitmap *pBmpOld; + CDC dcMem; + if (dcMem.CreateCompatibleDC(pDC) && (pBmpOld = dcMem.SelectObject(&m_bmMinTrayBtnBitmap)) != NULL) + { + s_pfnTransparentBlt(pDC->m_hDC, btn.left, btn.top, btn.Width(), btn.Height(), dcMem.m_hDC, 0, BMP_TRAYBTN_HEIGHT * (iState - 1), BMP_TRAYBTN_WIDTH, BMP_TRAYBTN_HEIGHT, BMP_TRAYBTN_TRANSCOLOR); + dcMem.SelectObject(pBmpOld); + } + } + else + { + // unknown theme (ThemeData) + HTHEME hTheme = g_xpStyle.OpenThemeData(m_hWnd, L"Window"); + if (hTheme) + { + btn.top += btn.Height() / 8; + g_xpStyle.DrawThemeBackground(hTheme, pDC->m_hDC, WP_TRAYBUTTON, iState, &btn, NULL); + g_xpStyle.CloseThemeData(hTheme); + } + } + } + + ReleaseDC(pDC); +} + +TEMPLATE BOOL CDialogMinTrayBtn::MinTrayBtnHitTest(CPoint ptScreen) const +{ + CRect rcWnd; + GetWindowRect(&rcWnd); + // adjust 'ptScreen' with possible RTL window layout + CRect rcWndOrg(rcWnd); + CPoint ptScreenOrg(ptScreen); + if (::MapWindowPoints(HWND_DESKTOP, m_hWnd, &rcWnd.TopLeft(), 2) == 0 || + ::MapWindowPoints(HWND_DESKTOP, m_hWnd, &ptScreen, 1) == 0) + { + // several bug reports about not working on NT SP6 (?). in case of any problems with + // 'MapWindowPoints' we fall back to old code (does not work for RTL window layout though) + rcWnd = rcWndOrg; + ptScreen = ptScreenOrg; + } + ptScreen.Offset(-rcWnd.TopLeft()); + + CRect rcBtn = MinTrayBtnGetRect(); + rcBtn.InflateRect(0, CAPTION_BUTTONSPACE); + return rcBtn.PtInRect(ptScreen); +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnSetUp() +{ + m_bMinTrayBtnUp = TRUE; + MinTrayBtnDraw(); +} + +TEMPLATE void CDialogMinTrayBtn::MinTrayBtnSetDown() +{ + m_bMinTrayBtnUp = FALSE; + MinTrayBtnDraw(); +} + +TEMPLATE BOOL CDialogMinTrayBtn::IsWindowsClassicStyle() const +{ + return m_bMinTrayBtnWindowsClassicStyle; +} + +TEMPLATE void CDialogMinTrayBtn::SetWindowText(LPCTSTR lpszString) +{ + BASE::SetWindowText(lpszString); + MinTrayBtnDraw(); +} + +TEMPLATE INT CDialogMinTrayBtn::GetVisualStylesXPColor() const +{ + if (IsWindowsClassicStyle()) + return -1; + + WCHAR szwThemeFile[MAX_PATH]; + WCHAR szwThemeColor[256]; + if (g_xpStyle.GetCurrentThemeName(szwThemeFile, MAX_PATH, szwThemeColor, _countof(szwThemeColor), NULL, 0) != S_OK) + return -1; + WCHAR* p; + if ((p = wcsrchr(szwThemeFile, _T('\\'))) == NULL) + return -1; + p++; + if (_wcsicmp(p, VISUALSTYLESXP_DEFAULTFILE) != 0) + return -1; + if (_wcsicmp(szwThemeColor, VISUALSTYLESXP_NAMEBLUE) == 0) + return VISUALSTYLESXP_BLUE; + if (_wcsicmp(szwThemeColor, VISUALSTYLESXP_NAMEMETALLIC) == 0) + return VISUALSTYLESXP_METALLIC; + if (_wcsicmp(szwThemeColor, VISUALSTYLESXP_NAMEHOMESTEAD) == 0) + return VISUALSTYLESXP_HOMESTEAD; + return -1; +} + +TEMPLATE BOOL CDialogMinTrayBtn::MinTrayBtnInitBitmap() +{ + m_bMinTrayBtnWindowsClassicStyle = !(g_xpStyle.IsThemeActive() && g_xpStyle.IsAppThemed()); + + INT nColor; + m_bmMinTrayBtnBitmap.DeleteObject(); + if ((nColor = GetVisualStylesXPColor()) == -1) + return FALSE; + return m_bmMinTrayBtnBitmap.LoadBitmap(m_pszMinTrayBtnBmpName[nColor]); +} diff --git a/DialogMinTrayBtn.h b/DialogMinTrayBtn.h new file mode 100644 index 00000000..0f17eaf9 --- /dev/null +++ b/DialogMinTrayBtn.h @@ -0,0 +1,81 @@ +// ------------------------------------------------------------ +// CDialogMinTrayBtn template class +// MFC CDialog with minimize to systemtray button (0.04) +// Supports WinXP styles (thanks to David Yuheng Zhao for CVisualStylesXP - yuheng_zhao@yahoo.com) +// ------------------------------------------------------------ +// DialogMinTrayBtn.h +// zegzav - 2002,2003 - eMule project (http://www.emule-project.net) +// ------------------------------------------------------------ +#pragma once +#define HTMINTRAYBUTTON 65 + +template class CDialogMinTrayBtn : public BASE +{ +public: + // constructor + CDialogMinTrayBtn(); + CDialogMinTrayBtn(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL); + CDialogMinTrayBtn(UINT nIDTemplate, CWnd* pParentWnd = NULL); + + // methods + void MinTrayBtnShow(); + void MinTrayBtnHide(); + __inline BOOL MinTrayBtnIsVisible() const { return m_bMinTrayBtnVisible; } + + void MinTrayBtnEnable(); + void MinTrayBtnDisable(); + __inline BOOL MinTrayBtnIsEnabled() const { return m_bMinTrayBtnEnabled; } + + void SetWindowText(LPCTSTR lpszString); + +protected: + // messages + virtual BOOL OnInitDialog(); + afx_msg void OnNcPaint(); + afx_msg BOOL OnNcActivate(BOOL bActive); +#if _MFC_VER>=0x0800 + afx_msg LRESULT OnNcHitTest(CPoint point); +#else + afx_msg UINT OnNcHitTest(CPoint point); +#endif + afx_msg void OnNcLButtonDown(UINT nHitTest, CPoint point); + afx_msg void OnNcRButtonDown(UINT nHitTest, CPoint point); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg LRESULT _OnThemeChanged(); + DECLARE_MESSAGE_MAP() + +private: + // internal methods + void MinTrayBtnInit(); + void MinTrayBtnDraw(); + BOOL MinTrayBtnHitTest(CPoint point) const; + void MinTrayBtnUpdatePosAndSize(); + + void MinTrayBtnSetUp(); + void MinTrayBtnSetDown(); + + __inline const CPoint &MinTrayBtnGetPos() const { return m_MinTrayBtnPos; } + __inline const CSize &MinTrayBtnGetSize() const { return m_MinTrayBtnSize; } + __inline CRect MinTrayBtnGetRect() const { return CRect(MinTrayBtnGetPos(), MinTrayBtnGetSize()); } + + BOOL IsWindowsClassicStyle() const; + INT GetVisualStylesXPColor() const; + + BOOL MinTrayBtnInitBitmap(); + + // data members + CPoint m_MinTrayBtnPos; + CSize m_MinTrayBtnSize; + BOOL m_bMinTrayBtnVisible; + BOOL m_bMinTrayBtnEnabled; + BOOL m_bMinTrayBtnUp; + BOOL m_bMinTrayBtnCapture; + BOOL m_bMinTrayBtnActive; + BOOL m_bMinTrayBtnHitTest; + UINT_PTR m_nMinTrayBtnTimerId; + CBitmap m_bmMinTrayBtnBitmap; + BOOL m_bMinTrayBtnWindowsClassicStyle; + static const TCHAR *m_pszMinTrayBtnBmpName[]; +}; diff --git a/DirectDownloadDlg.cpp b/DirectDownloadDlg.cpp new file mode 100644 index 00000000..ef749a3e --- /dev/null +++ b/DirectDownloadDlg.cpp @@ -0,0 +1,186 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "DirectDownloadDlg.h" +#include "OtherFunctions.h" +#include "emuleDlg.h" +#include "DownloadQueue.h" +#include "ED2KLink.h" +#include "Preferences.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define PREF_INI_SECTION _T("DirectDownloadDlg") + +IMPLEMENT_DYNAMIC(CDirectDownloadDlg, CDialog) + +BEGIN_MESSAGE_MAP(CDirectDownloadDlg, CResizableDialog) + ON_EN_KILLFOCUS(IDC_ELINK, OnEnKillfocusElink) + ON_EN_UPDATE(IDC_ELINK, OnEnUpdateElink) +END_MESSAGE_MAP() + +CDirectDownloadDlg::CDirectDownloadDlg(CWnd* pParent /*=NULL*/) + : CResizableDialog(CDirectDownloadDlg::IDD, pParent) +{ + m_icnWnd = NULL; +} + +CDirectDownloadDlg::~CDirectDownloadDlg() +{ + if (m_icnWnd) + VERIFY( DestroyIcon(m_icnWnd) ); +} + +void CDirectDownloadDlg::DoDataExchange(CDataExchange* pDX) +{ + CResizableDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_DDOWN_FRM, m_ctrlDirectDlFrm); + DDX_Control(pDX, IDC_CATS, m_cattabs); +} + +void CDirectDownloadDlg::UpdateControls() +{ + GetDlgItem(IDOK)->EnableWindow(GetDlgItem(IDC_ELINK)->GetWindowTextLength() > 0); +} + +void CDirectDownloadDlg::OnEnUpdateElink() +{ + UpdateControls(); +} + +void CDirectDownloadDlg::OnEnKillfocusElink() +{ + CString strLinks; + GetDlgItem(IDC_ELINK)->GetWindowText(strLinks); + if (strLinks.IsEmpty() || strLinks.Find(_T('\n')) == -1) + return; + strLinks.Replace(_T("\n"), _T("\r\n")); + strLinks.Replace(_T("\r\r"), _T("\r")); + GetDlgItem(IDC_ELINK)->SetWindowText(strLinks); +} + +void CDirectDownloadDlg::OnOK() +{ + CString strLinks; + GetDlgItem(IDC_ELINK)->GetWindowText(strLinks); + + int curPos = 0; + CString strTok = strLinks.Tokenize(_T(" \t\r\n"), curPos); // tokenize by whitespaces + while (!strTok.IsEmpty()) + { + if (strTok.Right(1) != _T("/")) + strTok += _T("/"); + try + { + CED2KLink* pLink = CED2KLink::CreateLinkFromUrl(strTok); + if (pLink) + { + if (pLink->GetKind() == CED2KLink::kFile) + { + theApp.downloadqueue->AddFileLinkToDownload(pLink->GetFileLink(), (thePrefs.GetCatCount() == 0) ? 0 : m_cattabs.GetCurSel()); + } + else + { + delete pLink; + throw CString(_T("bad link")); + } + delete pLink; + } + } + catch(CString error) + { + TCHAR szBuffer[200]; + _sntprintf(szBuffer, _countof(szBuffer), GetResString(IDS_ERR_INVALIDLINK), error); + szBuffer[_countof(szBuffer) - 1] = _T('\0'); + CString strError; + strError.Format(GetResString(IDS_ERR_LINKERROR), szBuffer); + AfxMessageBox(strError); + return; + } + strTok = strLinks.Tokenize(_T(" \t\r\n"), curPos); // tokenize by whitespaces + } + + CResizableDialog::OnOK(); +} + +BOOL CDirectDownloadDlg::OnInitDialog() +{ + CResizableDialog::OnInitDialog(); + InitWindowStyles(this); + SetIcon(m_icnWnd = theApp.LoadIcon(_T("PasteLink")), FALSE); + + if (theApp.IsVistaThemeActive()) + m_cattabs.ModifyStyle(0, TCS_HOTTRACK); + + AddAnchor(IDC_DDOWN_FRM, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_ELINK, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDCANCEL, BOTTOM_RIGHT); + AddAnchor(IDOK, BOTTOM_RIGHT); + AddAnchor(IDC_CATLABEL, BOTTOM_LEFT); + AddAnchor(IDC_CATS, BOTTOM_LEFT,BOTTOM_RIGHT); + + EnableSaveRestore(PREF_INI_SECTION); + + SetWindowText(GetResString(IDS_SW_DIRECTDOWNLOAD)); + m_ctrlDirectDlFrm.SetIcon(_T("Download")); + m_ctrlDirectDlFrm.SetWindowText(GetResString(IDS_SW_DIRECTDOWNLOAD)); + GetDlgItem(IDOK)->SetWindowText(GetResString(IDS_DOWNLOAD)); + GetDlgItem(IDC_FSTATIC2)->SetWindowText(GetResString(IDS_SW_LINK)); + GetDlgItem(IDC_CATLABEL)->SetWindowText(GetResString(IDS_CAT)+_T(":")); + + GetDlgItem(IDOK)->SetWindowText(GetResString(IDS_DOWNLOAD)); + GetDlgItem(IDCANCEL)->SetWindowText(GetResString(IDS_CANCEL)); + + + if (thePrefs.GetCatCount()==0) { + GetDlgItem(IDC_CATLABEL)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_CATS)->ShowWindow(SW_HIDE); + } + else { + UpdateCatTabs(); + if (theApp.m_fontSymbol.m_hObject){ + GetDlgItem(IDC_CATLABEL)->SetFont(&theApp.m_fontSymbol); + GetDlgItem(IDC_CATLABEL)->SetWindowText(GetExStyle() & WS_EX_LAYOUTRTL ? _T("3") : _T("4")); // show a right-arrow + } + + } + + UpdateControls(); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CDirectDownloadDlg::UpdateCatTabs() { + int oldsel=m_cattabs.GetCurSel(); + m_cattabs.DeleteAllItems(); + for (int ix=0;ixstrTitle; + label.Replace(_T("&"),_T("&&")); + m_cattabs.InsertItem(ix,label); + } + if (oldsel>=m_cattabs.GetItemCount() || oldsel==-1) + oldsel=0; + + m_cattabs.SetCurSel(oldsel); +} \ No newline at end of file diff --git a/DirectDownloadDlg.h b/DirectDownloadDlg.h new file mode 100644 index 00000000..ce16fecb --- /dev/null +++ b/DirectDownloadDlg.h @@ -0,0 +1,49 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "ResizableLib/ResizableDialog.h" +#include "IconStatic.h" +#include "ButtonsTabCtrl.h" + + +class CDirectDownloadDlg : public CResizableDialog +{ + DECLARE_DYNAMIC(CDirectDownloadDlg) + +public: + CDirectDownloadDlg(CWnd* pParent = NULL); // standard constructor + virtual ~CDirectDownloadDlg(); + +// Dialog Data + enum { IDD = IDD_DIRECT_DOWNLOAD }; + +protected: + HICON m_icnWnd; + CIconStatic m_ctrlDirectDlFrm; + CButtonsTabCtrl m_cattabs; + + void UpdateControls(); + void UpdateCatTabs(); + + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual void OnOK(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnEnKillfocusElink(); + afx_msg void OnEnUpdateElink(); +}; diff --git a/DirectoryTreeCtrl.cpp b/DirectoryTreeCtrl.cpp new file mode 100644 index 00000000..b9845366 --- /dev/null +++ b/DirectoryTreeCtrl.cpp @@ -0,0 +1,623 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "DirectoryTreeCtrl.h" +#include "otherfunctions.h" +#include "Preferences.h" +#include "TitleMenu.h" +#include "UserMsgs.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +///////////////////////////////////////////// +// written by robert rostek - tecxx@rrs.at // +///////////////////////////////////////////// + +struct STreeItem +{ + CString strPath; +}; + + +// CDirectoryTreeCtrl + +IMPLEMENT_DYNAMIC(CDirectoryTreeCtrl, CTreeCtrl) + +BEGIN_MESSAGE_MAP(CDirectoryTreeCtrl, CTreeCtrl) + ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnTvnItemexpanding) + ON_NOTIFY_REFLECT(TVN_GETDISPINFO, OnTvnGetdispinfo) + ON_WM_LBUTTONDOWN() + ON_NOTIFY_REFLECT(TVN_DELETEITEM, OnTvnDeleteItem) + ON_WM_CONTEXTMENU() + ON_WM_RBUTTONDOWN() + ON_WM_KEYDOWN() + ON_WM_CHAR() + ON_WM_DESTROY() +END_MESSAGE_MAP() + +CDirectoryTreeCtrl::CDirectoryTreeCtrl() +{ + m_bSelectSubDirs = false; +} + +CDirectoryTreeCtrl::~CDirectoryTreeCtrl() +{ + // don't destroy the system's image list + m_image.Detach(); +} + +void CDirectoryTreeCtrl::OnDestroy() +{ + // If a treeview control is created with TVS_CHECKBOXES, the application has to + // delete the image list which was implicitly created by the control. + CImageList *piml = GetImageList(TVSIL_STATE); + if (piml) + piml->DeleteImageList(); + + CTreeCtrl::OnDestroy(); +} + +void CDirectoryTreeCtrl::OnTvnItemexpanding(NMHDR *pNMHDR, LRESULT *pResult) +{ + CWaitCursor curWait; + SetRedraw(FALSE); + + LPNMTREEVIEW pNMTreeView = reinterpret_cast(pNMHDR); + HTREEITEM hItem = pNMTreeView->itemNew.hItem; + // remove all subitems + HTREEITEM hRemove = GetChildItem(hItem); + while(hRemove) + { + DeleteItem(hRemove); + hRemove = GetChildItem(hItem); + } + + // get the directory + CString strDir = GetFullPath(hItem); + + // fetch all subdirectories and add them to the node + AddSubdirectories(hItem, strDir); + + SetRedraw(TRUE); + Invalidate(); + *pResult = 0; +} + +void CDirectoryTreeCtrl::ShareSubDirTree(HTREEITEM hItem, BOOL bRecurse) +{ + CWaitCursor curWait; + SetRedraw(FALSE); + + HTREEITEM hItemVisibleItem = GetFirstVisibleItem(); + CheckChanged(hItem, !GetCheck(hItem)); + if (bRecurse) + { + Expand(hItem, TVE_TOGGLE); + HTREEITEM hChild = GetChildItem(hItem); + while (hChild != NULL) + { + MarkChilds(hChild, !GetCheck(hItem)); + hChild = GetNextSiblingItem(hChild); + } + Expand(hItem, TVE_TOGGLE); + } + if (hItemVisibleItem) + SelectSetFirstVisible(hItemVisibleItem); + + SetRedraw(TRUE); + Invalidate(); +} + +void CDirectoryTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point) +{ + //VQB adjustments to provide for sharing or unsharing of subdirectories when control key is Down + UINT uHitFlags; + HTREEITEM hItem = HitTest(point, &uHitFlags); + if (hItem && (uHitFlags & TVHT_ONITEMSTATEICON)) + ShareSubDirTree(hItem, nFlags & MK_CONTROL); + CTreeCtrl::OnLButtonDown(nFlags, point); +} + +void CDirectoryTreeCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + if (nChar == VK_SPACE) + { + HTREEITEM hItem = GetSelectedItem(); + if (hItem) + { + ShareSubDirTree(hItem, GetKeyState(VK_CONTROL) & 0x8000); + + // if Ctrl+Space is passed to the tree control, it just beeps and does not check/uncheck the item! + SetCheck(hItem, !GetCheck(hItem)); + return; + } + } + + CTreeCtrl::OnKeyDown(nChar, nRepCnt, nFlags); +} + +void CDirectoryTreeCtrl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + // If we let any keystrokes which are handled by us -- but not by the tree + // control -- pass to the control, the user will hear a system event + // sound (Standard Error!) + BOOL bCallDefault = TRUE; + + if (GetKeyState(VK_CONTROL) & 0x8000) + { + if (nChar == VK_SPACE) + bCallDefault = FALSE; + } + + if (bCallDefault) + CTreeCtrl::OnChar(nChar, nRepCnt, nFlags); +} + +void CDirectoryTreeCtrl::MarkChilds(HTREEITEM hChild,bool mark) { + CheckChanged(hChild, mark); + SetCheck(hChild,mark); + Expand(hChild, TVE_TOGGLE); // VQB - make sure tree has entries + HTREEITEM hChild2; + hChild2 = GetChildItem(hChild); + while( hChild2 != NULL) + { + MarkChilds(hChild2,mark); + hChild2 = GetNextSiblingItem( hChild2 ); + } + Expand(hChild, TVE_TOGGLE); // VQB - restore tree to initial disposition +} + +void CDirectoryTreeCtrl::OnTvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMTVDISPINFO pTVDispInfo = reinterpret_cast(pNMHDR); + pTVDispInfo->item.cChildren = 1; + *pResult = 0; +} + +void CDirectoryTreeCtrl::Init(void) +{ + // Win98: Explicitly set to Unicode to receive Unicode notifications. + SendMessage(CCM_SETUNICODEFORMAT, TRUE); + + ShowWindow(SW_HIDE); + DeleteAllItems(); + + // START: added by FoRcHa ///////////// + WORD wWinVer = thePrefs.GetWindowsVersion(); // maybe causes problems on 98 & nt4 + if (wWinVer != _WINVER_95_ && wWinVer != _WINVER_NT4_) + { + SHFILEINFO shFinfo; + HIMAGELIST hImgList = NULL; + + // Get the system image list using a "path" which is available on all systems. [patch by bluecow] + hImgList = (HIMAGELIST)SHGetFileInfo(_T("."), 0, &shFinfo, sizeof(shFinfo), + SHGFI_SYSICONINDEX | SHGFI_SMALLICON); + if(!hImgList) + { + TRACE(_T("Cannot retrieve the Handle of SystemImageList!")); + //return; + } + + m_image.m_hImageList = hImgList; + SetImageList(&m_image, TVSIL_NORMAL); + } + //////////////////////////////// + + + TCHAR drivebuffer[500]; + DWORD dwRet = GetLogicalDriveStrings(_countof(drivebuffer) - 1, drivebuffer); + if (dwRet > 0 && dwRet < _countof(drivebuffer)) + { + drivebuffer[_countof(drivebuffer) - 1] = _T('\0'); + + const TCHAR* pos = drivebuffer; + while(*pos != _T('\0')){ + + // Copy drive name + TCHAR drive[4]; + _tcsncpy(drive, pos, _countof(drive)); + drive[_countof(drive) - 1] = _T('\0'); + + drive[2] = _T('\0'); + AddChildItem(NULL, drive); // e.g. "C:" + + // Point to the next drive + pos += _tcslen(pos) + 1; + } + } + ShowWindow(SW_SHOW); +} + +HTREEITEM CDirectoryTreeCtrl::AddChildItem(HTREEITEM hRoot, CString strText) +{ + CString strPath = GetFullPath(hRoot); + if (hRoot != NULL && strPath.Right(1) != _T("\\")) + strPath += _T("\\"); + CString strDir = strPath + strText; + TVINSERTSTRUCT itInsert = {0}; + + // START: changed by FoRcHa ///// + WORD wWinVer = thePrefs.GetWindowsVersion(); + if (wWinVer != _WINVER_95_ && wWinVer != _WINVER_NT4_) + { + itInsert.item.mask = TVIF_CHILDREN | TVIF_HANDLE | TVIF_TEXT | + TVIF_STATE | TVIF_IMAGE | TVIF_SELECTEDIMAGE; + itInsert.item.stateMask = TVIS_BOLD | TVIS_STATEIMAGEMASK; + } + else + { + itInsert.item.mask = TVIF_CHILDREN | TVIF_HANDLE | TVIF_TEXT | TVIF_STATE; + itInsert.item.stateMask = TVIS_BOLD; + } + // END: changed by FoRcHa /////// + + if (HasSharedSubdirectory(strDir)) + itInsert.item.state = TVIS_BOLD; + else + itInsert.item.state = 0; + if (HasSubdirectories(strDir)) + itInsert.item.cChildren = I_CHILDRENCALLBACK; // used to display the + symbol next to each item + else + itInsert.item.cChildren = 0; + + itInsert.item.pszText = const_cast((LPCTSTR)strText); + itInsert.hInsertAfter = hRoot ? TVI_SORT : TVI_LAST; + itInsert.hParent = hRoot; + + // START: added by FoRcHa //////////////// + if (wWinVer != _WINVER_95_ && wWinVer != _WINVER_NT4_) + { + CString strTemp = strDir; + if(strTemp.Right(1) != _T("\\")) + strTemp += _T("\\"); + + UINT nType = GetDriveType(strTemp); + if(DRIVE_REMOVABLE <= nType && nType <= DRIVE_RAMDISK) + itInsert.item.iImage = nType; + + SHFILEINFO shFinfo; + shFinfo.szDisplayName[0] = _T('\0'); + if(!SHGetFileInfo(strTemp, 0, &shFinfo, sizeof(shFinfo), + SHGFI_ICON | SHGFI_SMALLICON | SHGFI_DISPLAYNAME)) + { + TRACE(_T("Error Gettting SystemFileInfo!")); + itInsert.itemex.iImage = 0; // :( + } + else + { + itInsert.itemex.iImage = shFinfo.iIcon; + DestroyIcon(shFinfo.hIcon); + if (hRoot == NULL && shFinfo.szDisplayName[0] != _T('\0')) + { + STreeItem* pti = new STreeItem; + pti->strPath = strText; + strText = shFinfo.szDisplayName; + itInsert.item.pszText = const_cast((LPCTSTR)strText); + itInsert.item.mask |= TVIF_PARAM; + itInsert.item.lParam = (LPARAM)pti; + } + } + + if(!SHGetFileInfo(strTemp, 0, &shFinfo, sizeof(shFinfo), + SHGFI_ICON | SHGFI_OPENICON | SHGFI_SMALLICON)) + { + TRACE(_T("Error Gettting SystemFileInfo!")); + itInsert.itemex.iImage = 0; + } + else + { + itInsert.itemex.iSelectedImage = shFinfo.iIcon; + DestroyIcon(shFinfo.hIcon); + } + } + // END: added by FoRcHa ////////////// + + HTREEITEM hItem = InsertItem(&itInsert); + if (IsShared(strDir)) + SetCheck(hItem); + + return hItem; +} + +CString CDirectoryTreeCtrl::GetFullPath(HTREEITEM hItem) +{ + CString strDir; + HTREEITEM hSearchItem = hItem; + while(hSearchItem != NULL) + { + CString strSearchItemDir; + STreeItem* pti = (STreeItem*)GetItemData(hSearchItem); + if (pti) + strSearchItemDir = pti->strPath; + else + strSearchItemDir = GetItemText(hSearchItem); + strDir = strSearchItemDir + _T("\\") + strDir; + hSearchItem = GetParentItem(hSearchItem); + } + return strDir; +} + +void CDirectoryTreeCtrl::AddSubdirectories(HTREEITEM hRoot, CString strDir) +{ + if (strDir.Right(1) != _T("\\")) + strDir += _T("\\"); + CFileFind finder; + BOOL bWorking = finder.FindFile(strDir+_T("*.*")); + while (bWorking) + { + bWorking = finder.FindNextFile(); + if (finder.IsDots()) + continue; + if (finder.IsSystem()) + continue; + if (!finder.IsDirectory()) + continue; + + CString strFilename = finder.GetFileName(); + if (strFilename.ReverseFind(_T('\\')) != -1) + strFilename = strFilename.Mid(strFilename.ReverseFind(_T('\\')) + 1); + AddChildItem(hRoot, strFilename); + } + finder.Close(); +} + +bool CDirectoryTreeCtrl::HasSubdirectories(CString strDir) +{ + if (strDir.Right(1) != _T('\\')) + strDir += _T('\\'); + // Never try to enumerate the files of a drive and thus physically access the drive, just + // for the information whether the drive has sub directories in the root folder. Depending + // on the physical drive type (floppy disk, CD-ROM drive, etc.) this creates an annoying + // physical access to that drive - which is to be avoided in each case. Even Windows + // Explorer shows all drives by default with a '+' sign (which means that the user has + // to explicitly open the drive to really get the content) - and that approach will be fine + // for eMule as well. + // Since the restriction for drives 'A:' and 'B:' was removed, this gets more important now. + if (PathIsRoot(strDir)) + return true; + CFileFind finder; + BOOL bWorking = finder.FindFile(strDir+_T("*.*")); + while (bWorking) + { + bWorking = finder.FindNextFile(); + if (finder.IsDots()) + continue; + if (finder.IsSystem()) + continue; + if (!finder.IsDirectory()) + continue; + finder.Close(); + return true; + } + finder.Close(); + return false; +} + +void CDirectoryTreeCtrl::GetSharedDirectories(CStringList* list) +{ + for (POSITION pos = m_lstShared.GetHeadPosition(); pos != NULL; ) + list->AddTail(m_lstShared.GetNext(pos)); +} + +void CDirectoryTreeCtrl::SetSharedDirectories(CStringList* list) +{ + m_lstShared.RemoveAll(); + + for (POSITION pos = list->GetHeadPosition(); pos != NULL; ) + { + CString str = list->GetNext(pos); + if (str.Left(2)==_T("\\\\")) continue; + if (str.Right(1) != _T('\\')) + str += _T('\\'); + m_lstShared.AddTail(str); + } + Init(); +} + +bool CDirectoryTreeCtrl::HasSharedSubdirectory(CString strDir) +{ + if (strDir.Right(1) != _T('\\')) + strDir += _T('\\'); + strDir.MakeLower(); + for (POSITION pos = m_lstShared.GetHeadPosition(); pos != NULL; ) + { + CString str = m_lstShared.GetNext(pos); + str.MakeLower(); + if (str.Find(strDir) == 0 && strDir != str)//strDir.GetLength() != str.GetLength()) + return true; + } + return false; +} + +void CDirectoryTreeCtrl::CheckChanged(HTREEITEM hItem, bool bChecked) +{ + CString strDir = GetFullPath(hItem); + if (bChecked) + AddShare(strDir); + else + DelShare(strDir); + + UpdateParentItems(hItem); + GetParent()->SendMessage(WM_COMMAND, UM_ITEMSTATECHANGED, (long)m_hWnd); +} + +bool CDirectoryTreeCtrl::IsShared(CString strDir) +{ + if (strDir.Right(1) != _T('\\')) + strDir += _T('\\'); + for (POSITION pos = m_lstShared.GetHeadPosition(); pos != NULL; ) + { + CString str = m_lstShared.GetNext(pos); + if (str.Right(1) != _T('\\')) + str += _T('\\'); + if (str.CompareNoCase(strDir) == 0) + return true; + } + return false; +} + +void CDirectoryTreeCtrl::AddShare(CString strDir) +{ + if (strDir.Right(1) != _T('\\')) + strDir += _T('\\'); + + if (IsShared(strDir) || !strDir.CompareNoCase(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR))) + return; + + m_lstShared.AddTail(strDir); +} + +void CDirectoryTreeCtrl::DelShare(CString strDir) +{ + if (strDir.Right(1) != _T('\\')) + strDir += _T('\\'); + for (POSITION pos = m_lstShared.GetHeadPosition(); pos != NULL; ) + { + POSITION pos2 = pos; + CString str = m_lstShared.GetNext(pos); + if (str.CompareNoCase(strDir) == 0) + m_lstShared.RemoveAt(pos2); + } +} + +void CDirectoryTreeCtrl::UpdateParentItems(HTREEITEM hChild) +{ + HTREEITEM hSearch = GetParentItem(hChild); + while(hSearch != NULL) + { + if (HasSharedSubdirectory(GetFullPath(hSearch))) + SetItemState(hSearch, TVIS_BOLD, TVIS_BOLD); + else + SetItemState(hSearch, 0, TVIS_BOLD); + hSearch = GetParentItem(hSearch); + } +} + +void CDirectoryTreeCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + if (point.x != -1 || point.y != -1) { + CRect rcClient; + GetClientRect(&rcClient); + ClientToScreen(&rcClient); + if (!rcClient.PtInRect(point)) { + Default(); + return; + } + } + + CPoint ptMenu(-1, -1); + if (point.x != -1 && point.y != -1) + { + ptMenu = point; + ScreenToClient(&point); + } + else + { + HTREEITEM hSel = GetNextItem(TVI_ROOT, TVGN_CARET); + if (hSel) + { + CRect rcItem; + if (GetItemRect(hSel, &rcItem, TRUE)) + { + ptMenu.x = rcItem.left; + ptMenu.y = rcItem.top; + ClientToScreen(&ptMenu); + } + } + else + { + ptMenu.SetPoint(0, 0); + ClientToScreen(&ptMenu); + } + } + + HTREEITEM hItem = HitTest(point); + + // create the menu + CTitleMenu SharedMenu; + SharedMenu.CreatePopupMenu(); + SharedMenu.AddMenuTitle(GetResString(IDS_SHAREDFOLDERS)); + bool bMenuIsEmpty = true; + + // add all shared directories + int iCnt = 0; + for (POSITION pos = m_lstShared.GetHeadPosition(); pos != NULL; iCnt++) + { + CString strDisplayPath(m_lstShared.GetNext(pos)); + PathRemoveBackslash(strDisplayPath.GetBuffer(strDisplayPath.GetLength())); + strDisplayPath.ReleaseBuffer(); + SharedMenu.AppendMenu(MF_STRING,MP_SHAREDFOLDERS_FIRST+iCnt, GetResString(IDS_VIEW1) + strDisplayPath); + bMenuIsEmpty = false; + } + + // add right clicked folder, if any + if (hItem) + { + m_strLastRightClicked = GetFullPath(hItem); + if (!IsShared(m_strLastRightClicked)) + { + CString strDisplayPath(m_strLastRightClicked); + PathRemoveBackslash(strDisplayPath.GetBuffer(strDisplayPath.GetLength())); + strDisplayPath.ReleaseBuffer(); + if (!bMenuIsEmpty) + SharedMenu.AppendMenu(MF_SEPARATOR); + SharedMenu.AppendMenu(MF_STRING, MP_SHAREDFOLDERS_FIRST-1, GetResString(IDS_VIEW1) + strDisplayPath + GetResString(IDS_VIEW2)); + bMenuIsEmpty = false; + } + } + + // display menu + if (!bMenuIsEmpty) + SharedMenu.TrackPopupMenu(TPM_LEFTALIGN |TPM_RIGHTBUTTON, ptMenu.x, ptMenu.y, this); + VERIFY( SharedMenu.DestroyMenu() ); +} + +void CDirectoryTreeCtrl::OnRButtonDown(UINT /*nFlags*/, CPoint /*point*/) +{ + // catch WM_RBUTTONDOWN and do not route it the default way.. otherwise we won't get a WM_CONTEXTMENU. + //CTreeCtrl::OnRButtonDown(nFlags, point); +} + +BOOL CDirectoryTreeCtrl::OnCommand(WPARAM wParam, LPARAM /*lParam*/) +{ + if (wParam < MP_SHAREDFOLDERS_FIRST) + { + ShellExecute(NULL, _T("open"), m_strLastRightClicked, NULL, NULL, SW_SHOW); + } + else + { + POSITION pos = m_lstShared.FindIndex(wParam - MP_SHAREDFOLDERS_FIRST); + if (pos) + ShellExecute(NULL, _T("open"), m_lstShared.GetAt(pos), NULL, NULL, SW_SHOW); + } + + return TRUE; +} + +void CDirectoryTreeCtrl::OnTvnDeleteItem(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMTREEVIEW pNMTreeView = reinterpret_cast(pNMHDR); + if (pNMTreeView->itemOld.lParam) + delete (STreeItem*)pNMTreeView->itemOld.lParam; + *pResult = 0; +} diff --git a/DirectoryTreeCtrl.h b/DirectoryTreeCtrl.h new file mode 100644 index 00000000..158a7df0 --- /dev/null +++ b/DirectoryTreeCtrl.h @@ -0,0 +1,65 @@ +#pragma once +///////////////////////////////////////////// +// written by robert rostek - tecxx@rrs.at // +///////////////////////////////////////////// + +#define MP_SHAREDFOLDERS_FIRST 46901 + +class CDirectoryTreeCtrl : public CTreeCtrl +{ + DECLARE_DYNAMIC(CDirectoryTreeCtrl) + +public: + // initialize control + void Init(void); + // get all shared directories + void GetSharedDirectories(CStringList* list); + // set shared directories + void SetSharedDirectories(CStringList* list); + +private: + CImageList m_image; + // add a new item + HTREEITEM AddChildItem(HTREEITEM hRoot, CString strText); + // add subdirectory items + void AddSubdirectories(HTREEITEM hRoot, CString strDir); + // return the full path of an item (like C:\abc\somewhere\inheaven\) + CString GetFullPath(HTREEITEM hItem); + // returns true if strDir has at least one subdirectory + bool HasSubdirectories(CString strDir); + // check status of an item has changed + void CheckChanged(HTREEITEM hItem, bool bChecked); + // returns true if a subdirectory of strDir is shared + bool HasSharedSubdirectory(CString strDir); + // when sharing a directory, make all parent directories bold + void UpdateParentItems(HTREEITEM hChild); + void ShareSubDirTree(HTREEITEM hItem, BOOL bShare); + + // share list access + bool IsShared(CString strDir); + void AddShare(CString strDir); + void DelShare(CString strDir); + void MarkChilds(HTREEITEM hChild,bool mark); + + CStringList m_lstShared; + CString m_strLastRightClicked; + bool m_bSelectSubDirs; + +public: + // construction / destruction + CDirectoryTreeCtrl(); + virtual ~CDirectoryTreeCtrl(); + virtual BOOL OnCommand(WPARAM wParam,LPARAM lParam ); + +protected: + DECLARE_MESSAGE_MAP() + afx_msg void OnTvnItemexpanding(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnTvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnTvnDeleteItem(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/); + afx_msg void OnRButtonDown(UINT nFlags, CPoint point); + afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg void OnDestroy(); +}; diff --git a/DownloadClient.cpp b/DownloadClient.cpp new file mode 100644 index 00000000..90639f4b --- /dev/null +++ b/DownloadClient.cpp @@ -0,0 +1,2532 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include +#include "UpDownClient.h" +#include "PartFile.h" +#include "OtherFunctions.h" +#include "ListenSocket.h" +#include "PeerCacheSocket.h" +#include "Preferences.h" +#include "SafeFile.h" +#include "Packets.h" +#include "Statistics.h" +#include "ClientCredits.h" +#include "DownloadQueue.h" +#include "ClientUDPSocket.h" +#include "emuledlg.h" +#include "TransferDlg.h" +#include "PeerCacheFinder.h" +#include "Exceptions.h" +#include "clientlist.h" +#include "Kademlia/Kademlia/Kademlia.h" +#include "Kademlia/Kademlia/Prefs.h" +#include "Kademlia/Kademlia/Search.h" +#include "SHAHashSet.h" +#include "SharedFileList.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +// members of CUpDownClient +// which are mainly used for downloading functions +CBarShader CUpDownClient::s_StatusBar(16); +void CUpDownClient::DrawStatusBar(CDC* dc, LPCRECT rect, bool onlygreyrect, bool bFlat) const +{ + if (g_bLowColorDesktop) + bFlat = true; + + COLORREF crNeither; + if (bFlat) { + if (g_bLowColorDesktop) + crNeither = RGB(192, 192, 192); + else + crNeither = RGB(224, 224, 224); + } else { + crNeither = RGB(240, 240, 240); + } + + ASSERT(reqfile); + s_StatusBar.SetFileSize(reqfile->GetFileSize()); + s_StatusBar.SetHeight(rect->bottom - rect->top); + s_StatusBar.SetWidth(rect->right - rect->left); + s_StatusBar.Fill(crNeither); + + if (!onlygreyrect && reqfile && m_abyPartStatus) + { + COLORREF crBoth; + COLORREF crClientOnly; + COLORREF crPending; + COLORREF crNextPending; + if (g_bLowColorDesktop) { + crBoth = RGB(0, 0, 0); + crClientOnly = RGB(0, 0, 255); + crPending = RGB(0, 255, 0); + crNextPending = RGB(255, 255, 0); + } else if (bFlat) { + crBoth = RGB(0, 0, 0); + crClientOnly = RGB(0, 100, 255); + crPending = RGB(0, 150, 0); + crNextPending = RGB(255, 208, 0); + } else { + crBoth = RGB(104, 104, 104); + crClientOnly = RGB(0, 100, 255); + crPending = RGB(0, 150, 0); + crNextPending = RGB(255, 208, 0); + } + + char* pcNextPendingBlks = NULL; + if (m_nDownloadState == DS_DOWNLOADING){ + pcNextPendingBlks = new char[m_nPartCount]; + memset(pcNextPendingBlks, 'N', m_nPartCount); // do not use '_strnset' for uninitialized memory! + for (POSITION pos = m_PendingBlocks_list.GetHeadPosition(); pos != 0; ){ + UINT uPart = (UINT)(m_PendingBlocks_list.GetNext(pos)->block->StartOffset / PARTSIZE); + if (uPart < m_nPartCount) + pcNextPendingBlks[uPart] = 'Y'; + } + } + + for (UINT i = 0; i < m_nPartCount; i++){ + if (m_abyPartStatus[i]){ + uint64 uEnd; + if ( PARTSIZE*(uint64)(i+1) > reqfile->GetFileSize()) + uEnd = reqfile->GetFileSize(); + else + uEnd = PARTSIZE*(uint64)(i+1); + + if (reqfile->IsComplete(PARTSIZE*(uint64)i,PARTSIZE*(uint64)(i+1)-1, false)) + s_StatusBar.FillRange(PARTSIZE*(uint64)i, uEnd, crBoth); + else if (GetSessionDown() > 0 && m_nDownloadState == DS_DOWNLOADING && m_nLastBlockOffset >= PARTSIZE*(uint64)i && m_nLastBlockOffset < uEnd) + s_StatusBar.FillRange(PARTSIZE*(uint64)i, uEnd, crPending); + else if (pcNextPendingBlks != NULL && pcNextPendingBlks[i] == 'Y') + s_StatusBar.FillRange(PARTSIZE*(uint64)i, uEnd, crNextPending); + else + s_StatusBar.FillRange(PARTSIZE*(uint64)i, uEnd, crClientOnly); + } + } + delete[] pcNextPendingBlks; + } + s_StatusBar.Draw(dc, rect->left, rect->top, bFlat); +} + +bool CUpDownClient::Compare(const CUpDownClient* tocomp, bool bIgnoreUserhash) const +{ + //Compare only the user hash.. + if(!bIgnoreUserhash && HasValidHash() && tocomp->HasValidHash()) + return !md4cmp(this->GetUserHash(), tocomp->GetUserHash()); + + if (HasLowID()) + { + //User is firewalled.. Must do two checks.. + if (GetIP()!=0 && GetIP() == tocomp->GetIP()) + { + //The IP of both match + if (GetUserPort()!=0 && GetUserPort() == tocomp->GetUserPort()) + //IP-UserPort matches + return true; + if (GetKadPort()!=0 && GetKadPort() == tocomp->GetKadPort()) + //IP-KadPort Matches + return true; + } + if (GetUserIDHybrid()!=0 + && GetUserIDHybrid() == tocomp->GetUserIDHybrid() + && GetServerIP()!=0 + && GetServerIP() == tocomp->GetServerIP() + && GetServerPort()!=0 + && GetServerPort() == tocomp->GetServerPort()) + //Both have the same lowID, Same serverIP and Port.. + return true; + +#if defined(_DEBUG) + if ( HasValidBuddyID() && tocomp->HasValidBuddyID() ) + { + //JOHNTODO: This is for future use to see if this will be needed... + if(!md4cmp(GetBuddyID(), tocomp->GetBuddyID())) + return true; + } +#endif + + //Both IP, and Server do not match.. + return false; + } + + //User is not firewalled. + if (GetUserPort()!=0) + { + //User has a Port, lets check the rest. + if (GetIP() != 0 && tocomp->GetIP() != 0) + { + //Both clients have a verified IP.. + if(GetIP() == tocomp->GetIP() && GetUserPort() == tocomp->GetUserPort()) + //IP and UserPort match.. + return true; + } + else + { + //One of the two clients do not have a verified IP + if (GetUserIDHybrid() == tocomp->GetUserIDHybrid() && GetUserPort() == tocomp->GetUserPort()) + //ID and Port Match.. + return true; + } + } + if(GetKadPort()!=0) + { + //User has a Kad Port. + if(GetIP() != 0 && tocomp->GetIP() != 0) + { + //Both clients have a verified IP. + if(GetIP() == tocomp->GetIP() && GetKadPort() == tocomp->GetKadPort()) + //IP and KadPort Match.. + return true; + } + else + { + //One of the users do not have a verified IP. + if (GetUserIDHybrid() == tocomp->GetUserIDHybrid() && GetKadPort() == tocomp->GetKadPort()) + //ID and KadProt Match.. + return true; + } + } + //No Matches.. + return false; +} + +// Return bool is not if you asked or not.. +// false = Client was deleted! +// true = client was not deleted! +bool CUpDownClient::AskForDownload() +{ + + if (m_bUDPPending) + { + m_nFailedUDPPackets++; + theApp.downloadqueue->AddFailedUDPFileReasks(); + } + m_bUDPPending = false; + if (!(socket && socket->IsConnected())) // already connected, skip all the special checks + { + if (theApp.listensocket->TooManySockets()) + { + if (GetDownloadState() != DS_TOOMANYCONNS) + SetDownloadState(DS_TOOMANYCONNS); + return true; + } + m_dwLastTriedToConnect = ::GetTickCount(); + // if its a lowid client which is on our queue we may delay the reask up to 20 min, to give the lowid the chance to + // connect to us for its own reask + if (HasLowID() && GetUploadState() == US_ONUPLOADQUEUE && !m_bReaskPending && GetLastAskedTime() > 0){ + SetDownloadState(DS_ONQUEUE); + m_bReaskPending = true; + return true; + } + // if we are lowid <-> lowid but contacted the source before already, keep it in the hope that we might turn highid again + if (HasLowID() && !theApp.CanDoCallback(this) && GetLastAskedTime() > 0){ + if (GetDownloadState() != DS_LOWTOLOWIP) + SetDownloadState(DS_LOWTOLOWIP); + m_bReaskPending = true; + return true; + } + } + + m_dwLastTriedToConnect = ::GetTickCount(); + SwapToAnotherFile(_T("A4AF check before tcp file reask. CUpDownClient::AskForDownload()"), true, false, false, NULL, true, true); + SetDownloadState(DS_CONNECTING); + return TryToConnect(); +} + +bool CUpDownClient::IsSourceRequestAllowed() const +{ + return IsSourceRequestAllowed(reqfile); +} + +bool CUpDownClient::IsSourceRequestAllowed(CPartFile* partfile, bool sourceExchangeCheck) const +{ + DWORD dwTickCount = ::GetTickCount() + CONNECTION_LATENCY; + unsigned int nTimePassedClient = dwTickCount - GetLastSrcAnswerTime(); + unsigned int nTimePassedFile = dwTickCount - partfile->GetLastAnsweredTime(); + bool bNeverAskedBefore = GetLastAskedForSources() == 0; + UINT uSources = partfile->GetSourceCount(); + UINT uValidSources = partfile->GetValidSourcesCount(); + + if (partfile != reqfile) { + uSources++; + uValidSources++; + } + + UINT uReqValidSources = reqfile->GetValidSourcesCount(); + + return ( + //if client has the correct extended protocol + ExtProtocolAvailable() && (SupportsSourceExchange2() || GetSourceExchange1Version() > 1) && + //AND if we need more sources + reqfile->GetMaxSourcePerFileSoft() > uSources && + //AND if... + ( + //source is not complete and file is very rare + ( !m_bCompleteSource + && (bNeverAskedBefore || nTimePassedClient > SOURCECLIENTREASKS) + && (uSources <= RARE_FILE/5) + && (!sourceExchangeCheck || partfile == reqfile || uValidSources < uReqValidSources && uReqValidSources > 3) + ) || + //source is not complete and file is rare + ( !m_bCompleteSource + && (bNeverAskedBefore || nTimePassedClient > SOURCECLIENTREASKS) + && (uSources <= RARE_FILE || (!sourceExchangeCheck || partfile == reqfile) && uSources <= RARE_FILE / 2 + uValidSources) + && (nTimePassedFile > SOURCECLIENTREASKF) + && (!sourceExchangeCheck || partfile == reqfile || uValidSources < SOURCECLIENTREASKS/SOURCECLIENTREASKF && uValidSources < uReqValidSources) + ) || + // OR if file is not rare + ( (bNeverAskedBefore || nTimePassedClient > (unsigned)(SOURCECLIENTREASKS * MINCOMMONPENALTY)) + && (nTimePassedFile > (unsigned)(SOURCECLIENTREASKF * MINCOMMONPENALTY)) + && (!sourceExchangeCheck || partfile == reqfile || uValidSources < SOURCECLIENTREASKS/SOURCECLIENTREASKF && uValidSources < uReqValidSources) + ) + ) + ); +} + +void CUpDownClient::SendFileRequest() +{ + // normally asktime has already been reset here, but then SwapToAnotherFile will return without much work, so check to make sure + SwapToAnotherFile(_T("A4AF check before tcp file reask. CUpDownClient::SendFileRequest()"), true, false, false, NULL, true, true); + + ASSERT(reqfile != NULL); + if (!reqfile) + return; + AddAskedCountDown(); + + if (SupportMultiPacket() || SupportsFileIdentifiers()) + { + CSafeMemFile dataFileReq(96); + if (SupportsFileIdentifiers()) + { + reqfile->GetFileIdentifier().WriteIdentifier(&dataFileReq); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__MultiPacket_Ext2", this, reqfile->GetFileHash()); + } + else + { + dataFileReq.WriteHash16(reqfile->GetFileHash()); + if (SupportExtMultiPacket()){ + dataFileReq.WriteUInt64(reqfile->GetFileSize()); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__MultiPacket_Ext", this, reqfile->GetFileHash()); + } + else{ + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__MultiPacket", this, reqfile->GetFileHash()); + } + } + + // OP_REQUESTFILENAME + ExtInfo + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__MPReqFileName", this, reqfile->GetFileHash()); + dataFileReq.WriteUInt8(OP_REQUESTFILENAME); + if (GetExtendedRequestsVersion() > 0) + reqfile->WritePartStatus(&dataFileReq); + if (GetExtendedRequestsVersion() > 1) + reqfile->WriteCompleteSourcesCount(&dataFileReq); + + // OP_SETREQFILEID + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__MPSetReqFileID", this, reqfile->GetFileHash()); + if (reqfile->GetPartCount() > 1) + dataFileReq.WriteUInt8(OP_SETREQFILEID); + + if (IsEmuleClient()) + { + SetRemoteQueueFull(true); + SetRemoteQueueRank(0); + } + + // OP_REQUESTSOURCES // OP_REQUESTSOURCES2 + if (IsSourceRequestAllowed()) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) { + DebugSend("OP__MPReqSources", this, reqfile->GetFileHash()); + if (GetLastAskedForSources() == 0) + Debug(_T(" first source request\n")); + else + Debug(_T(" last source request was before %s\n"), CastSecondsToHM((GetTickCount() - GetLastAskedForSources())/1000)); + } + if (SupportsSourceExchange2()){ + dataFileReq.WriteUInt8(OP_REQUESTSOURCES2); + dataFileReq.WriteUInt8(SOURCEEXCHANGE2_VERSION); + const uint16 nOptions = 0; // 16 ... Reserved + dataFileReq.WriteUInt16(nOptions); + } + else{ + dataFileReq.WriteUInt8(OP_REQUESTSOURCES); + } + reqfile->SetLastAnsweredTimeTimeout(); + SetLastAskedForSources(); + if (thePrefs.GetDebugSourceExchange()) + AddDebugLogLine(false, _T("SXSend (%s): Client source request; %s, File=\"%s\""),SupportsSourceExchange2() ? _T("Version 2") : _T("Version 1"), DbgGetClientInfo(), reqfile->GetFileName()); + } + + // OP_AICHFILEHASHREQ - deprecated with fileidentifiers + if (IsSupportingAICH() && !SupportsFileIdentifiers()) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__MPAichFileHashReq", this, reqfile->GetFileHash()); + dataFileReq.WriteUInt8(OP_AICHFILEHASHREQ); + } + + Packet* packet = new Packet(&dataFileReq, OP_EMULEPROT); + if (SupportsFileIdentifiers()) + packet->opcode = OP_MULTIPACKET_EXT2; + else if (SupportExtMultiPacket()) + packet->opcode = OP_MULTIPACKET_EXT; + else + packet->opcode = OP_MULTIPACKET; + theStats.AddUpDataOverheadFileRequest(packet->size); + SendPacket(packet, true); + } + else + { + CSafeMemFile dataFileReq(96); + dataFileReq.WriteHash16(reqfile->GetFileHash()); + //This is extended information + if (GetExtendedRequestsVersion() > 0) + reqfile->WritePartStatus(&dataFileReq); + if (GetExtendedRequestsVersion() > 1) + reqfile->WriteCompleteSourcesCount(&dataFileReq); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__FileRequest", this, reqfile->GetFileHash()); + Packet* packet = new Packet(&dataFileReq); + packet->opcode = OP_REQUESTFILENAME; + theStats.AddUpDataOverheadFileRequest(packet->size); + SendPacket(packet, true); + + // 26-Jul-2003: removed requesting the file status for files <= PARTSIZE for better compatibility with ed2k protocol (eDonkeyHybrid). + // if the remote client answers the OP_REQUESTFILENAME with OP_REQFILENAMEANSWER the file is shared by the remote client. if we + // know that the file is shared, we know also that the file is complete and don't need to request the file status. + if (reqfile->GetPartCount() > 1) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__SetReqFileID", this, reqfile->GetFileHash()); + CSafeMemFile dataSetReqFileID(16); + dataSetReqFileID.WriteHash16(reqfile->GetFileHash()); + packet = new Packet(&dataSetReqFileID); + packet->opcode = OP_SETREQFILEID; + theStats.AddUpDataOverheadFileRequest(packet->size); + SendPacket(packet, true); + } + + if (IsEmuleClient()) + { + SetRemoteQueueFull(true); + SetRemoteQueueRank(0); + } + + if (IsSourceRequestAllowed()) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) { + DebugSend("OP__RequestSources", this, reqfile->GetFileHash()); + if (GetLastAskedForSources() == 0) + Debug(_T(" first source request\n")); + else + Debug(_T(" last source request was before %s\n"), CastSecondsToHM((GetTickCount() - GetLastAskedForSources())/1000)); + } + reqfile->SetLastAnsweredTimeTimeout(); + + Packet* packet; + if (SupportsSourceExchange2()){ + packet = new Packet(OP_REQUESTSOURCES2,19,OP_EMULEPROT); + PokeUInt8(&packet->pBuffer[0], SOURCEEXCHANGE2_VERSION); + const uint16 nOptions = 0; // 16 ... Reserved + PokeUInt16(&packet->pBuffer[1], nOptions); + md4cpy(&packet->pBuffer[3],reqfile->GetFileHash()); + } + else{ + packet = new Packet(OP_REQUESTSOURCES,16,OP_EMULEPROT); + md4cpy(packet->pBuffer,reqfile->GetFileHash()); + } + + theStats.AddUpDataOverheadSourceExchange(packet->size); + SendPacket(packet, true); + SetLastAskedForSources(); + if (thePrefs.GetDebugSourceExchange()) + AddDebugLogLine(false, _T("SXSend (%s): Client source request; %s, File=\"%s\""),SupportsSourceExchange2() ? _T("Version 2") : _T("Version 1"), DbgGetClientInfo(), reqfile->GetFileName()); + } + + if (IsSupportingAICH()) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__AichFileHashReq", this, reqfile->GetFileHash()); + Packet* packet = new Packet(OP_AICHFILEHASHREQ,16,OP_EMULEPROT); + md4cpy(packet->pBuffer,reqfile->GetFileHash()); + theStats.AddUpDataOverheadFileRequest(packet->size); + SendPacket(packet, true); + } + } + SetLastAskedTime(); +} + +void CUpDownClient::SendStartupLoadReq() +{ + if (socket==NULL || reqfile==NULL) + { + ASSERT(0); + return; + } + m_fQueueRankPending = 1; + m_fUnaskQueueRankRecv = 0; + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__StartupLoadReq", this); + CSafeMemFile dataStartupLoadReq(16); + dataStartupLoadReq.WriteHash16(reqfile->GetFileHash()); + Packet* packet = new Packet(&dataStartupLoadReq); + packet->opcode = OP_STARTUPLOADREQ; + theStats.AddUpDataOverheadFileRequest(packet->size); + SetDownloadState(DS_ONQUEUE); + SendPacket(packet, true); +} + +void CUpDownClient::ProcessFileInfo(CSafeMemFile* data, CPartFile* file) +{ + if (file==NULL) + throw GetResString(IDS_ERR_WRONGFILEID) + _T(" (ProcessFileInfo; file==NULL)"); + if (reqfile==NULL) + throw GetResString(IDS_ERR_WRONGFILEID) + _T(" (ProcessFileInfo; reqfile==NULL)"); + if (file != reqfile) + throw GetResString(IDS_ERR_WRONGFILEID) + _T(" (ProcessFileInfo; reqfile!=file)"); + m_strClientFilename = data->ReadString(GetUnicodeSupport()!=utf8strNone); + if (thePrefs.GetDebugClientTCPLevel() > 0) + Debug(_T(" Filename=\"%s\"\n"), m_strClientFilename); + // 26-Jul-2003: removed requesting the file status for files <= PARTSIZE for better compatibility with ed2k protocol (eDonkeyHybrid). + // if the remote client answers the OP_REQUESTFILENAME with OP_REQFILENAMEANSWER the file is shared by the remote client. if we + // know that the file is shared, we know also that the file is complete and don't need to request the file status. + if (reqfile->GetPartCount() == 1) + { + delete[] m_abyPartStatus; + m_abyPartStatus = NULL; + m_nPartCount = reqfile->GetPartCount(); + m_abyPartStatus = new uint8[m_nPartCount]; + memset(m_abyPartStatus,1,m_nPartCount); + m_bCompleteSource = true; + + if (thePrefs.GetDebugClientTCPLevel() > 0) + { + int iNeeded = 0; + UINT i; + for (i = 0; i < m_nPartCount; i++) { + if (!reqfile->IsComplete((uint64)i*PARTSIZE, ((uint64)(i+1)*PARTSIZE)-1, false)) + iNeeded++; + } + char* psz = new char[m_nPartCount + 1]; + for (i = 0; i < m_nPartCount; i++) + psz[i] = m_abyPartStatus[i] ? '#' : '.'; + psz[i] = '\0'; + Debug(_T(" Parts=%u %hs Needed=%u\n"), m_nPartCount, psz, iNeeded); + delete[] psz; + } + UpdateDisplayedInfo(); + reqfile->UpdateAvailablePartsCount(); + // even if the file is <= PARTSIZE, we _may_ need the hashset for that file (if the file size == PARTSIZE) + if (reqfile->m_bMD4HashsetNeeded || (reqfile->IsAICHPartHashSetNeeded() && SupportsFileIdentifiers() + && GetReqFileAICHHash() != NULL && *GetReqFileAICHHash() == reqfile->GetFileIdentifier().GetAICHHash())) + SendHashSetRequest(); + else + SendStartupLoadReq(); + reqfile->UpdatePartsInfo(); + } +} + +void CUpDownClient::ProcessFileStatus(bool bUdpPacket, CSafeMemFile* data, CPartFile* file) +{ + if ( !reqfile || file != reqfile ) + { + if (reqfile==NULL) + throw GetResString(IDS_ERR_WRONGFILEID) + _T(" (ProcessFileStatus; reqfile==NULL)"); + throw GetResString(IDS_ERR_WRONGFILEID) + _T(" (ProcessFileStatus; reqfile!=file)"); + } + + if (file->GetStatus() == PS_COMPLETE || file->GetStatus() == PS_COMPLETING) + return; + + uint16 nED2KPartCount = data->ReadUInt16(); + delete[] m_abyPartStatus; + m_abyPartStatus = NULL; + bool bPartsNeeded = false; + int iNeeded = 0; + if (!nED2KPartCount) + { + m_nPartCount = reqfile->GetPartCount(); + m_abyPartStatus = new uint8[m_nPartCount]; + memset(m_abyPartStatus, 1, m_nPartCount); + bPartsNeeded = true; + m_bCompleteSource = true; + if (bUdpPacket ? (thePrefs.GetDebugClientUDPLevel() > 0) : (thePrefs.GetDebugClientTCPLevel() > 0)) + { + for (UINT i = 0; i < m_nPartCount; i++) + { + if (!reqfile->IsComplete((uint64)i*PARTSIZE, ((uint64)(i+1)*PARTSIZE)-1, false)) + iNeeded++; + } + } + } + else + { + if (reqfile->GetED2KPartCount() != nED2KPartCount) { + if (thePrefs.GetVerbose()) { + DebugLogWarning(_T("FileName: \"%s\""), m_strClientFilename); + DebugLogWarning(_T("FileStatus: %s"), DbgGetFileStatus(nED2KPartCount, data)); + } + CString strError; + strError.Format(_T("ProcessFileStatus - wrong part number recv=%u expected=%u %s"), nED2KPartCount, reqfile->GetED2KPartCount(), DbgGetFileInfo(reqfile->GetFileHash())); + m_nPartCount = 0; + throw strError; + } + m_nPartCount = reqfile->GetPartCount(); + + m_bCompleteSource = false; + m_abyPartStatus = new uint8[m_nPartCount]; + UINT done = 0; + while (done != m_nPartCount) + { + uint8 toread = data->ReadUInt8(); + for (UINT i = 0; i != 8; i++) + { + m_abyPartStatus[done] = ((toread>>i)&1)? 1:0; + if (m_abyPartStatus[done]) + { + if (!reqfile->IsComplete((uint64)done*PARTSIZE, ((uint64)(done+1)*PARTSIZE)-1, false)){ + bPartsNeeded = true; + iNeeded++; + } + } + done++; + if (done == m_nPartCount) + break; + } + } + } + + if (bUdpPacket ? (thePrefs.GetDebugClientUDPLevel() > 0) : (thePrefs.GetDebugClientTCPLevel() > 0)) + { + TCHAR* psz = new TCHAR[m_nPartCount + 1]; + UINT i; + for (i = 0; i < m_nPartCount; i++) + psz[i] = m_abyPartStatus[i] ? _T('#') : _T('.'); + psz[i] = _T('\0'); + Debug(_T(" Parts=%u %s Needed=%u\n"), m_nPartCount, psz, iNeeded); + delete[] psz; + } + + UpdateDisplayedInfo(bUdpPacket); + reqfile->UpdateAvailablePartsCount(); + + // NOTE: This function is invoked from TCP and UDP socket! + if (!bUdpPacket) + { + if (!bPartsNeeded) + { + SetDownloadState(DS_NONEEDEDPARTS); + SwapToAnotherFile(_T("A4AF for NNP file. CUpDownClient::ProcessFileStatus() TCP"), true, false, false, NULL, true, true); + } + else if (reqfile->m_bMD4HashsetNeeded || (reqfile->IsAICHPartHashSetNeeded() && SupportsFileIdentifiers() + && GetReqFileAICHHash() != NULL && *GetReqFileAICHHash() == reqfile->GetFileIdentifier().GetAICHHash())) //If we are using the eMule filerequest packets, this is taken care of in the Multipacket! + SendHashSetRequest(); + else + SendStartupLoadReq(); + } + else + { + if (!bPartsNeeded) + { + SetDownloadState(DS_NONEEDEDPARTS); + //SwapToAnotherFile(_T("A4AF for NNP file. CUpDownClient::ProcessFileStatus() UDP"), true, false, false, NULL, true, false); + } + else + SetDownloadState(DS_ONQUEUE); + } + reqfile->UpdatePartsInfo(); +} + +bool CUpDownClient::AddRequestForAnotherFile(CPartFile* file){ + for (POSITION pos = m_OtherNoNeeded_list.GetHeadPosition();pos != 0;){ + if (m_OtherNoNeeded_list.GetNext(pos) == file) + return false; + } + for (POSITION pos = m_OtherRequests_list.GetHeadPosition();pos != 0;){ + if (m_OtherRequests_list.GetNext(pos) == file) + return false; + } + m_OtherRequests_list.AddTail(file); + file->A4AFsrclist.AddTail(this); // [enkeyDEV(Ottavio84) -A4AF-] + + return true; +} + +void CUpDownClient::ClearDownloadBlockRequests() +{ + for (POSITION pos = m_PendingBlocks_list.GetHeadPosition();pos != 0;){ + Pending_Block_Struct *pending = m_PendingBlocks_list.GetNext(pos); + if (reqfile){ + reqfile->RemoveBlockFromList(pending->block->StartOffset, pending->block->EndOffset); + } + + delete pending->block; + // Not always allocated + if (pending->zStream){ + inflateEnd(pending->zStream); + delete pending->zStream; + } + delete pending; + } + m_PendingBlocks_list.RemoveAll(); +} + +void CUpDownClient::SetDownloadState(EDownloadState nNewState, LPCTSTR pszReason){ + if (m_nDownloadState != nNewState){ + switch( nNewState ) + { + case DS_CONNECTING: + m_dwLastTriedToConnect = ::GetTickCount(); + break; + case DS_TOOMANYCONNSKAD: + //This client had already been set to DS_CONNECTING. + //So we reset this time so it isn't stuck at TOOMANYCONNS for 20mins. + m_dwLastTriedToConnect = ::GetTickCount()-20*60*1000; + break; + case DS_WAITCALLBACKKAD: + case DS_WAITCALLBACK: + break; + case DS_NONEEDEDPARTS: + // Since tcp asks never sets reask time if the result is DS_NONEEDEDPARTS + // If we set this, we will not reask for that file until some time has passed. + SetLastAskedTime(); + //DontSwapTo(reqfile); + + /*default: + switch( m_nDownloadState ) + { + case DS_WAITCALLBACK: + case DS_WAITCALLBACKKAD: + break; + default: + m_dwLastTriedToConnect = ::GetTickCount()-20*60*1000; + break; + } + break;*/ + } + + if (reqfile){ + if(nNewState == DS_DOWNLOADING){ + if(thePrefs.GetLogUlDlEvents()) + AddDebugLogLine(DLP_VERYLOW, false, _T("Download session started. User: %s in SetDownloadState(). New State: %i"), DbgGetClientInfo(), nNewState); + + reqfile->AddDownloadingSource(this); + } + else if(m_nDownloadState == DS_DOWNLOADING){ + reqfile->RemoveDownloadingSource(this); + } + } + + if(nNewState == DS_DOWNLOADING && socket){ + socket->SetTimeOut(CONNECTION_TIMEOUT*4); + } + + if (m_nDownloadState == DS_DOWNLOADING ){ + if(socket) + socket->SetTimeOut(CONNECTION_TIMEOUT); + + if (thePrefs.GetLogUlDlEvents()) { + switch( nNewState ) + { + case DS_NONEEDEDPARTS: + pszReason = _T("NNP. You don't need any parts from this client."); + } + + if(thePrefs.GetLogUlDlEvents()) + AddDebugLogLine(DLP_VERYLOW, false, _T("Download session ended: %s User: %s in SetDownloadState(). New State: %i, Length: %s, Payload: %s, Transferred: %s, Req blocks not yet completed: %i."), pszReason, DbgGetClientInfo(), nNewState, CastSecondsToHM(GetDownTimeDifference(false)/1000), CastItoXBytes(GetSessionPayloadDown(), false, false), CastItoXBytes(GetSessionDown(), false, false), m_PendingBlocks_list.GetCount()); + } + + ResetSessionDown(); + + // -khaos--+++> Extended Statistics (Successful/Failed Download Sessions) + if ( m_bTransferredDownMini && nNewState != DS_ERROR ) + thePrefs.Add2DownSuccessfulSessions(); // Increment our counters for successful sessions (Cumulative AND Session) + else + thePrefs.Add2DownFailedSessions(); // Increment our counters failed sessions (Cumulative AND Session) + thePrefs.Add2DownSAvgTime(GetDownTimeDifference()/1000); + // <-----khaos- + + m_nDownloadState = (_EDownloadState)nNewState; + + ClearDownloadBlockRequests(); + + m_nDownDatarate = 0; + m_AvarageDDR_list.RemoveAll(); + m_nSumForAvgDownDataRate = 0; + + if (nNewState == DS_NONE){ + delete[] m_abyPartStatus; + m_abyPartStatus = NULL; + m_nPartCount = 0; + } + if (socket && nNewState != DS_ERROR ) + socket->DisableDownloadLimit(); + } + m_nDownloadState = (_EDownloadState)nNewState; + if( GetDownloadState() == DS_DOWNLOADING ){ + if ( IsEmuleClient() ) + SetRemoteQueueFull(false); + SetRemoteQueueRank(0); + SetAskedCountDown(0); + } + UpdateDisplayedInfo(true); + } +} + +void CUpDownClient::ProcessHashSet(const uchar* packet, uint32 size, bool bFileIdentifiers) +{ + CSafeMemFile data(packet, size); + if (bFileIdentifiers) + { + if (!m_fHashsetRequestingMD4 && !m_fHashsetRequestingAICH) + throw CString(_T("unwanted hashset2")); + CFileIdentifierSA fileIdent; + if (!fileIdent.ReadIdentifier(&data)) + throw CString(_T("Invalid FileIdentifier")); + if (reqfile == NULL || !reqfile->GetFileIdentifier().CompareRelaxed(fileIdent)) + { + CheckFailedFileIdReqs(packet); + throw GetResString(IDS_ERR_WRONGFILEID) + _T(" (ProcessHashSet2)"); + } + bool bMD4 = m_fHashsetRequestingMD4 != 0; + bool bAICH = m_fHashsetRequestingAICH != 0; + if (!reqfile->GetFileIdentifier().ReadHashSetsFromPacket(&data, bMD4, bAICH)) + { + if (m_fHashsetRequestingMD4) + reqfile->m_bMD4HashsetNeeded = true; + if (m_fHashsetRequestingAICH) + reqfile->SetAICHHashSetNeeded(true); + m_fHashsetRequestingMD4 = 0; + m_fHashsetRequestingAICH = 0; + throw GetResString(IDS_ERR_BADHASHSET); + } + if (m_fHashsetRequestingMD4 && !bMD4) + { + DebugLogWarning(_T("Client was unable to deliver requested MD4 hashset (shouldn't happen) - %s, file: %s"), DbgGetClientInfo(), reqfile->GetFileName()); + reqfile->m_bMD4HashsetNeeded = true; + } + else if (m_fHashsetRequestingMD4) + DebugLog(_T("Received valid MD4 Hashset (FileIdentifiers) form %s, file: %s"), DbgGetClientInfo(), reqfile->GetFileName()); + + if (m_fHashsetRequestingAICH && !bAICH) + { + DebugLogWarning(_T("Client was unable to deliver requested AICH part hashset, asking other clients - %s, file: %s"), DbgGetClientInfo(), reqfile->GetFileName()); + reqfile->SetAICHHashSetNeeded(true); + } + else if (m_fHashsetRequestingAICH) + DebugLog(_T("Received valid AICH Part Hashset form %s, file: %s"), DbgGetClientInfo(), reqfile->GetFileName()); + m_fHashsetRequestingMD4 = 0; + m_fHashsetRequestingAICH = 0; + } + else + { + if (!m_fHashsetRequestingMD4) + throw CString(_T("unwanted hashset")); + if ( (!reqfile) || md4cmp(packet,reqfile->GetFileHash())) + { + CheckFailedFileIdReqs(packet); + throw GetResString(IDS_ERR_WRONGFILEID) + _T(" (ProcessHashSet)"); + } + m_fHashsetRequestingMD4 = 0; + if (!reqfile->GetFileIdentifier().LoadMD4HashsetFromFile(&data, true)) + { + reqfile->m_bMD4HashsetNeeded = true; + throw GetResString(IDS_ERR_BADHASHSET); + } + } + SendStartupLoadReq(); +} + +void CUpDownClient::CreateBlockRequests(int iMinBlocks, int iMaxBlocks) +{ + ASSERT( iMinBlocks >= 1 && iMaxBlocks >= iMinBlocks/*&& iMaxBlocks <= 3*/ ); + + uint16 count; + if(iMinBlocks > m_PendingBlocks_list.GetCount()) + { + count = (uint16)(iMaxBlocks - m_PendingBlocks_list.GetCount()); + } + else + return; + + Requested_Block_Struct** toadd = new Requested_Block_Struct*[count]; + if (reqfile->GetNextRequestedBlock(this, toadd, &count)){ + for (UINT i = 0; i < count; i++) + { + Pending_Block_Struct* pblock = new Pending_Block_Struct; + pblock->block = toadd[i]; + m_PendingBlocks_list.AddTail(pblock); + ASSERT( m_PendingBlocks_list.GetCount() <= iMaxBlocks ); + } + } + delete[] toadd; +} + +void CUpDownClient::SendBlockRequests() +{ + m_dwLastBlockReceived = ::GetTickCount(); + if (!reqfile) + return; + + // prevent locking of too many blocks when we are on a slow (probably standby/trickle) slot + int blockCount = 3; // max pending block requests + int maxBlockDelta = 1; // blockcount - maxBlockDelta = minPendingBlockRequests + if(IsEmuleClient() && m_byCompatibleClient==0 && reqfile->GetFileSize()-reqfile->GetCompletedSize() <= (uint64)PARTSIZE*4) { + // if there's less than two chunks left, request fewer blocks for + // slow downloads, so they don't lock blocks from faster clients. + // Only trust eMule clients to be able to handle less blocks than three + if(GetDownloadDatarate() < 600 || GetSessionPayloadDown() < 40*1024) { + blockCount = 1; + maxBlockDelta = 0; + } else if(GetDownloadDatarate() < 1200) { + blockCount = 2; + maxBlockDelta = 0; + } + } + else if (GetDownloadDatarate() > 1024*75) + { + blockCount = 6; + maxBlockDelta = 2; + } + + CreateBlockRequests(blockCount - maxBlockDelta, blockCount); + int queued = 0; + for (POSITION pos = m_PendingBlocks_list.GetHeadPosition(); pos != NULL; ) + { + Pending_Block_Struct* pending = m_PendingBlocks_list.GetNext(pos); + if (pending->fQueued) + queued++; + } + + if (m_PendingBlocks_list.IsEmpty()){ + SendCancelTransfer(); + SetDownloadState(DS_NONEEDEDPARTS); + SwapToAnotherFile(_T("A4AF for NNP file. CUpDownClient::SendBlockRequests()"), true, false, false, NULL, true, true); + return; + } + + CTypedPtrList listToRequest; + bool bI64Offsets = false; + for (POSITION pos = m_PendingBlocks_list.GetHeadPosition(); pos != NULL; ) + { + Pending_Block_Struct* pending = m_PendingBlocks_list.GetNext(pos); + if (pending->fQueued) + continue; + ASSERT( pending->block->StartOffset <= pending->block->EndOffset ); + if (pending->block->StartOffset > 0xFFFFFFFF || pending->block->EndOffset >= 0xFFFFFFFF){ + bI64Offsets = true; + if (!SupportsLargeFiles()){ + ASSERT( false ); + SendCancelTransfer(); + SetDownloadState(DS_ERROR); + return; + } + } + listToRequest.AddTail(pending); + if (listToRequest.GetCount() >= 3) + break; + } + if (!IsEmuleClient() && listToRequest.GetCount() < 3) + { + for (POSITION pos = m_PendingBlocks_list.GetHeadPosition(); pos != NULL; ) + { + Pending_Block_Struct* pending = m_PendingBlocks_list.GetNext(pos); + if (!pending->fQueued) + continue; + ASSERT( pending->block->StartOffset <= pending->block->EndOffset ); + if (pending->block->StartOffset > 0xFFFFFFFF || pending->block->EndOffset >= 0xFFFFFFFF){ + bI64Offsets = true; + if (!SupportsLargeFiles()){ + ASSERT( false ); + SendCancelTransfer(); + SetDownloadState(DS_ERROR); + return; + } + } + listToRequest.AddTail(pending); + if (listToRequest.GetCount() >= 3) + break; + } + } + else if (listToRequest.IsEmpty()) + { + // do not rerequest blocks, at least eMule clients don't need expect this tow ork properly so its + // just overhead (and its not protocol standard, but we used to do so since forever) + // by adding a range of min to max pending blocks this means we do not send a request after every received + // packet anymore + return; + } + + Packet* packet; + if (bI64Offsets){ + const int iPacketSize = 16+(3*8)+(3*8); // 64 + packet = new Packet(OP_REQUESTPARTS_I64, iPacketSize, OP_EMULEPROT); + CSafeMemFile data((const BYTE*)packet->pBuffer, iPacketSize); + data.WriteHash16(reqfile->GetFileHash()); + POSITION pos = listToRequest.GetHeadPosition(); + for (uint32 i = 0; i != 3; i++){ + if (pos){ + Pending_Block_Struct* pending = listToRequest.GetNext(pos); + ASSERT( pending->block->StartOffset <= pending->block->EndOffset ); + //ASSERT( pending->zStream == NULL ); + //ASSERT( pending->totalUnzipped == 0 ); + pending->fZStreamError = 0; + pending->fRecovered = 0; + pending->fQueued = 1; + data.WriteUInt64(pending->block->StartOffset); + } + else + data.WriteUInt64(0); + } + pos = listToRequest.GetHeadPosition(); + for (uint32 i = 0; i != 3; i++){ + if (pos){ + Requested_Block_Struct* block = listToRequest.GetNext(pos)->block; + uint64 endpos = block->EndOffset+1; + data.WriteUInt64(endpos); + if (thePrefs.GetDebugClientTCPLevel() > 0){ + CString strInfo; + strInfo.Format(_T(" Block request %u: "), i); + strInfo += DbgGetBlockInfo(block); + strInfo.AppendFormat(_T(", Complete=%s"), reqfile->IsComplete(block->StartOffset, block->EndOffset, false) ? _T("Yes(NOTE:)") : _T("No")); + strInfo.AppendFormat(_T(", PureGap=%s"), reqfile->IsPureGap(block->StartOffset, block->EndOffset) ? _T("Yes") : _T("No(NOTE:)")); + strInfo.AppendFormat(_T(", AlreadyReq=%s"), reqfile->IsAlreadyRequested(block->StartOffset, block->EndOffset) ? _T("Yes") : _T("No(NOTE:)")); + strInfo += _T('\n'); + Debug(strInfo); + } + } + else + { + data.WriteUInt64(0); + if (thePrefs.GetDebugClientTCPLevel() > 0) + Debug(_T(" Block request %u: \n"), i); + } + } + } + else{ + const int iPacketSize = 16+(3*4)+(3*4); // 40 + packet = new Packet(OP_REQUESTPARTS,iPacketSize); + CSafeMemFile data((const BYTE*)packet->pBuffer, iPacketSize); + data.WriteHash16(reqfile->GetFileHash()); + POSITION pos = listToRequest.GetHeadPosition(); + for (uint32 i = 0; i != 3; i++){ + if (pos){ + Pending_Block_Struct* pending = listToRequest.GetNext(pos); + ASSERT( pending->block->StartOffset <= pending->block->EndOffset ); + //ASSERT( pending->zStream == NULL ); + //ASSERT( pending->totalUnzipped == 0 ); + pending->fZStreamError = 0; + pending->fRecovered = 0; + pending->fQueued = 1; + data.WriteUInt32((uint32)pending->block->StartOffset); + } + else + data.WriteUInt32(0); + } + pos = listToRequest.GetHeadPosition(); + for (uint32 i = 0; i != 3; i++){ + if (pos){ + Requested_Block_Struct* block = listToRequest.GetNext(pos)->block; + uint64 endpos = block->EndOffset+1; + data.WriteUInt32((uint32)endpos); + if (thePrefs.GetDebugClientTCPLevel() > 0){ + CString strInfo; + strInfo.Format(_T(" Block request %u: "), i); + strInfo += DbgGetBlockInfo(block); + strInfo.AppendFormat(_T(", Complete=%s"), reqfile->IsComplete(block->StartOffset, block->EndOffset, false) ? _T("Yes(NOTE:)") : _T("No")); + strInfo.AppendFormat(_T(", PureGap=%s"), reqfile->IsPureGap(block->StartOffset, block->EndOffset) ? _T("Yes") : _T("No(NOTE:)")); + strInfo.AppendFormat(_T(", AlreadyReq=%s"), reqfile->IsAlreadyRequested(block->StartOffset, block->EndOffset) ? _T("Yes") : _T("No(NOTE:)")); + strInfo += _T('\n'); + Debug(strInfo); + } + } + else + { + data.WriteUInt32(0); + if (thePrefs.GetDebugClientTCPLevel() > 0) + Debug(_T(" Block request %u: \n"), i); + } + } + } + + theStats.AddUpDataOverheadFileRequest(packet->size); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__RequestParts", this, reqfile!=NULL ? reqfile->GetFileHash() : NULL); + SendPacket(packet, true, true); + // on highspeed downloads, we want this packet to get out asap, so wakeup the throttler if he is sleeping + // because there was nothing to send yet + theApp.uploadBandwidthThrottler->NewUploadDataAvailable(); +} + +/* Barry - Originally this only wrote to disk when a full 180k block + had been received from a client, and only asked for data in + 180k blocks. + + This meant that on average 90k was lost for every connection + to a client data source. That is a lot of wasted data. + + To reduce the lost data, packets are now written to a buffer + and flushed to disk regularly regardless of size downloaded. + This includes compressed packets. + + Data is also requested only where gaps are, not in 180k blocks. + The requests will still not exceed 180k, but may be smaller to + fill a gap. +*/ +void CUpDownClient::ProcessBlockPacket(const uchar *packet, uint32 size, bool packed, bool bI64Offsets) +{ + if (!bI64Offsets) { + uint32 nDbgStartPos = *((uint32*)(packet+16)); + if (thePrefs.GetDebugClientTCPLevel() > 1){ + if (packed) + Debug(_T(" Start=%u BlockSize=%u Size=%u %s\n"), nDbgStartPos, *((uint32*)(packet + 16+4)), size-24, DbgGetFileInfo(packet)); + else + Debug(_T(" Start=%u End=%u Size=%u %s\n"), nDbgStartPos, *((uint32*)(packet + 16+4)), *((uint32*)(packet + 16+4)) - nDbgStartPos, DbgGetFileInfo(packet)); + } + } + + // Ignore if no data required + if (!(GetDownloadState() == DS_DOWNLOADING || GetDownloadState() == DS_NONEEDEDPARTS)){ + TRACE("%s - Invalid download state\n", __FUNCTION__); + return; + } + + + + // Update stats + m_dwLastBlockReceived = ::GetTickCount(); + + // Read data from packet + CSafeMemFile data(packet, size); + uchar fileID[16]; + data.ReadHash16(fileID); + int nHeaderSize = 16; + + // Check that this data is for the correct file + if ( (!reqfile) || md4cmp(packet, reqfile->GetFileHash())) + { + throw GetResString(IDS_ERR_WRONGFILEID) + _T(" (ProcessBlockPacket)"); + } + + // Find the start & end positions, and size of this chunk of data + uint64 nStartPos; + uint64 nEndPos; + uint32 nBlockSize = 0; + + if (bI64Offsets){ + nStartPos = data.ReadUInt64(); + nHeaderSize += 8; + } + else{ + nStartPos = data.ReadUInt32(); + nHeaderSize += 4; + } + if (packed) + { + nBlockSize = data.ReadUInt32(); + nHeaderSize += 4; + nEndPos = nStartPos + (size - nHeaderSize); + } + else{ + if (bI64Offsets){ + nEndPos = data.ReadUInt64(); + nHeaderSize += 8; + } + else{ + nEndPos = data.ReadUInt32(); + nHeaderSize += 4; + } + } + uint32 uTransferredFileDataSize = size - nHeaderSize; + + // Check that packet size matches the declared data size + header size (24) + if (nEndPos == nStartPos || size != ((nEndPos - nStartPos) + nHeaderSize)) + throw GetResString(IDS_ERR_BADDATABLOCK) + _T(" (ProcessBlockPacket)"); + + // -khaos--+++> + // Extended statistics information based on which client and remote port sent this data. + // The new function adds the bytes to the grand total as well as the given client/port. + // bFromPF is not relevant to downloaded data. It is purely an uploads statistic. + thePrefs.Add2SessionTransferData(GetClientSoft(), GetUserPort(), false, false, uTransferredFileDataSize, false); + // <-----khaos- + + m_nDownDataRateMS += uTransferredFileDataSize; + if (credits) + credits->AddDownloaded(uTransferredFileDataSize, GetIP()); + + // Move end back one, should be inclusive + nEndPos--; + + // Loop through to find the reserved block that this is within + for (POSITION pos = m_PendingBlocks_list.GetHeadPosition(); pos != NULL; ) + { + POSITION posLast = pos; + Pending_Block_Struct *cur_block = m_PendingBlocks_list.GetNext(pos); + if ((cur_block->block->StartOffset <= nStartPos) && (cur_block->block->EndOffset >= nStartPos)) + { + // Found reserved block + + if (cur_block->fZStreamError){ + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("PrcBlkPkt: Ignoring %u bytes of block starting at %I64u because of errornous zstream state for file \"%s\" - %s"), uTransferredFileDataSize, nStartPos, reqfile->GetFileName(), DbgGetClientInfo()); + reqfile->RemoveBlockFromList(cur_block->block->StartOffset, cur_block->block->EndOffset); + return; + } + + // Remember this start pos, used to draw part downloading in list + m_nLastBlockOffset = nStartPos; + + // Occasionally packets are duplicated, no point writing it twice + // This will be 0 in these cases, or the length written otherwise + uint32 lenWritten = 0; + + // Handle differently depending on whether packed or not + if (!packed) + { + // security sanitize check + if (nEndPos > cur_block->block->EndOffset){ + DebugLogError(_T("Received Blockpacket exceeds requested boundaries (requested end: %I64u, Part %u, received end %I64u, Part %u), file %s, client %s"), cur_block->block->EndOffset + , (uint32)(cur_block->block->EndOffset / PARTSIZE), nEndPos, (uint32)(nEndPos / PARTSIZE), reqfile->GetFileName(), DbgGetClientInfo()); + reqfile->RemoveBlockFromList(cur_block->block->StartOffset, cur_block->block->EndOffset); + return; + } + // Write to disk (will be buffered in part file class) + lenWritten = reqfile->WriteToBuffer(uTransferredFileDataSize, + packet + nHeaderSize, + nStartPos, + nEndPos, + cur_block->block, + this); + } + else // Packed + { + ASSERT( (int)size > 0 ); + // Create space to store unzipped data, the size is only an initial guess, will be resized in unzip() if not big enough + uint32 lenUnzipped = (size * 2); + // Don't get too big + if (lenUnzipped > (EMBLOCKSIZE + 300)) + lenUnzipped = (EMBLOCKSIZE + 300); + BYTE *unzipped = new BYTE[lenUnzipped]; + + // Try to unzip the packet + int result = unzip(cur_block, packet + nHeaderSize, uTransferredFileDataSize, &unzipped, &lenUnzipped); + // no block can be uncompressed to >2GB, 'lenUnzipped' is obviously errornous. + if (result == Z_OK && (int)lenUnzipped >= 0) + { + if (lenUnzipped > 0) // Write any unzipped data to disk + { + ASSERT( (int)lenUnzipped > 0 ); + + // Use the current start and end positions for the uncompressed data + nStartPos = cur_block->block->StartOffset + cur_block->totalUnzipped - lenUnzipped; + nEndPos = cur_block->block->StartOffset + cur_block->totalUnzipped - 1; + + if (nStartPos > cur_block->block->EndOffset || nEndPos > cur_block->block->EndOffset){ + DebugLogError(_T("PrcBlkPkt: ") + GetResString(IDS_ERR_CORRUPTCOMPRPKG),reqfile->GetFileName(),666); + reqfile->RemoveBlockFromList(cur_block->block->StartOffset, cur_block->block->EndOffset); + // There is no chance to recover from this error + } + else{ + // Write uncompressed data to file + lenWritten = reqfile->WriteToBuffer(uTransferredFileDataSize, + unzipped, + nStartPos, + nEndPos, + cur_block->block, + this); + } + } + } + else + { + if (thePrefs.GetVerbose()) + { + CString strZipError; + if (cur_block->zStream && cur_block->zStream->msg) + strZipError.Format(_T(" - %hs"), cur_block->zStream->msg); + if (result == Z_OK && (int)lenUnzipped < 0){ + ASSERT(0); + strZipError.AppendFormat(_T("; Z_OK,lenUnzipped=%d"), lenUnzipped); + } + DebugLogError(_T("PrcBlkPkt: ") + GetResString(IDS_ERR_CORRUPTCOMPRPKG) + strZipError, reqfile->GetFileName(), result); + } + reqfile->RemoveBlockFromList(cur_block->block->StartOffset, cur_block->block->EndOffset); + + // If we had an zstream error, there is no chance that we could recover from it nor that we + // could use the current zstream (which is in error state) any longer. + if (cur_block->zStream){ + inflateEnd(cur_block->zStream); + delete cur_block->zStream; + cur_block->zStream = NULL; + } + + // Although we can't further use the current zstream, there is no need to disconnect the sending + // client because the next zstream (a series of 10K-blocks which build a 180K-block) could be + // valid again. Just ignore all further blocks for the current zstream. + cur_block->fZStreamError = 1; + cur_block->totalUnzipped = 0; + } + delete [] unzipped; + } + + // These checks only need to be done if any data was written + if (lenWritten > 0) + { + m_nTransferredDown += uTransferredFileDataSize; + m_nCurSessionPayloadDown += lenWritten; + SetTransferredDownMini(); + + // If finished reserved block + if (nEndPos == cur_block->block->EndOffset) + { + reqfile->RemoveBlockFromList(cur_block->block->StartOffset, cur_block->block->EndOffset); + delete cur_block->block; + // Not always allocated + if (cur_block->zStream){ + inflateEnd(cur_block->zStream); + delete cur_block->zStream; + } + delete cur_block; + m_PendingBlocks_list.RemoveAt(posLast); + + // Request next block + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("More block requests", this); + SendBlockRequests(); + } + } + + // Stop looping and exit method + return; + } + } + + TRACE("%s - Dropping packet\n", __FUNCTION__); +} + +int CUpDownClient::unzip(Pending_Block_Struct* block, const BYTE* zipped, uint32 lenZipped, BYTE** unzipped, uint32* lenUnzipped, int iRecursion) +{ +//#define TRACE_UNZIP TRACE +#define TRACE_UNZIP __noop + TRACE_UNZIP("unzip: Zipd=%6u Unzd=%6u Rcrs=%d", lenZipped, *lenUnzipped, iRecursion); + int err = Z_DATA_ERROR; + try + { + // Save some typing + z_stream *zS = block->zStream; + + // Is this the first time this block has been unzipped + if (zS == NULL) + { + // Create stream + block->zStream = new z_stream; + zS = block->zStream; + + // Initialise stream values + zS->zalloc = (alloc_func)0; + zS->zfree = (free_func)0; + zS->opaque = (voidpf)0; + + // Set output data streams, do this here to avoid overwriting on recursive calls + zS->next_out = (*unzipped); + zS->avail_out = (*lenUnzipped); + + // Initialise the z_stream + err = inflateInit(zS); + if (err != Z_OK){ + TRACE_UNZIP("; Error: new stream failed: %d\n", err); + return err; + } + + ASSERT( block->totalUnzipped == 0 ); + } + + // Use whatever input is provided + zS->next_in = const_cast(zipped); + zS->avail_in = lenZipped; + + // Only set the output if not being called recursively + if (iRecursion == 0) + { + zS->next_out = (*unzipped); + zS->avail_out = (*lenUnzipped); + } + + // Try to unzip the data + TRACE_UNZIP("; inflate(ain=%6u tin=%6u aout=%6u tout=%6u)", zS->avail_in, zS->total_in, zS->avail_out, zS->total_out); + err = inflate(zS, Z_SYNC_FLUSH); + + // Is zip finished reading all currently available input and writing all generated output + if (err == Z_STREAM_END) + { + // Finish up + err = inflateEnd(zS); + if (err != Z_OK){ + TRACE_UNZIP("; Error: end stream failed: %d\n", err); + return err; + } + TRACE_UNZIP("; Z_STREAM_END\n"); + + // Got a good result, set the size to the amount unzipped in this call (including all recursive calls) + (*lenUnzipped) = (zS->total_out - block->totalUnzipped); + block->totalUnzipped = zS->total_out; + } + else if ((err == Z_OK) && (zS->avail_out == 0) && (zS->avail_in != 0)) + { + // Output array was not big enough, call recursively until there is enough space + TRACE_UNZIP("; output array not big enough (ain=%u)\n", zS->avail_in); + + // What size should we try next + uint32 newLength = (*lenUnzipped) *= 2; + if (newLength == 0) + newLength = lenZipped * 2; + + // Copy any data that was successfully unzipped to new array + BYTE *temp = new BYTE[newLength]; + ASSERT( zS->total_out - block->totalUnzipped <= newLength ); + memcpy(temp, (*unzipped), (zS->total_out - block->totalUnzipped)); + delete[] (*unzipped); + (*unzipped) = temp; + (*lenUnzipped) = newLength; + + // Position stream output to correct place in new array + zS->next_out = (*unzipped) + (zS->total_out - block->totalUnzipped); + zS->avail_out = (*lenUnzipped) - (zS->total_out - block->totalUnzipped); + + // Try again + err = unzip(block, zS->next_in, zS->avail_in, unzipped, lenUnzipped, iRecursion + 1); + } + else if ((err == Z_OK) && (zS->avail_in == 0)) + { + TRACE_UNZIP("; all input processed\n"); + // All available input has been processed, everything ok. + // Set the size to the amount unzipped in this call (including all recursive calls) + (*lenUnzipped) = (zS->total_out - block->totalUnzipped); + block->totalUnzipped = zS->total_out; + } + else + { + // Should not get here unless input data is corrupt + if (thePrefs.GetVerbose()) + { + CString strZipError; + if (zS->msg) + strZipError.Format(_T(" %d: '%hs'"), err, zS->msg); + else if (err != Z_OK) + strZipError.Format(_T(" %d: '%hs'"), err, zError(err)); + TRACE_UNZIP("; Error: %s\n", strZipError); + DebugLogError(_T("Unexpected zip error%s in file \"%s\""), strZipError, reqfile ? reqfile->GetFileName() : NULL); + } + } + + if (err != Z_OK) + (*lenUnzipped) = 0; + } + catch (...){ + if (thePrefs.GetVerbose()) + DebugLogError(_T("Unknown exception in %hs: file \"%s\""), __FUNCTION__, reqfile ? reqfile->GetFileName() : NULL); + err = Z_DATA_ERROR; + ASSERT(0); + } + + return err; +} + +uint32 CUpDownClient::CalculateDownloadRate(){ + // Patch By BadWolf - Accurate datarate Calculation + TransferredData newitem = {m_nDownDataRateMS,::GetTickCount()}; + m_AvarageDDR_list.AddTail(newitem); + m_nSumForAvgDownDataRate += m_nDownDataRateMS; + m_nDownDataRateMS = 0; + + while (m_AvarageDDR_list.GetCount()>500) + m_nSumForAvgDownDataRate -= m_AvarageDDR_list.RemoveHead().datalen; + + if(m_AvarageDDR_list.GetCount() > 1){ + DWORD dwDuration = m_AvarageDDR_list.GetTail().timestamp - m_AvarageDDR_list.GetHead().timestamp; + if (dwDuration) + m_nDownDatarate = (UINT)(1000U * (ULONGLONG)m_nSumForAvgDownDataRate / dwDuration); + } + else + m_nDownDatarate = 0; + // END Patch By BadWolf + m_cShowDR++; + if (m_cShowDR == 30){ + m_cShowDR = 0; + UpdateDisplayedInfo(); + } + + return m_nDownDatarate; +} + +void CUpDownClient::CheckDownloadTimeout() +{ + if (IsDownloadingFromPeerCache() && m_pPCDownSocket && m_pPCDownSocket->IsConnected()) + { + ASSERT( DOWNLOADTIMEOUT < m_pPCDownSocket->GetTimeOut() ); + if (GetTickCount() - m_dwLastBlockReceived > DOWNLOADTIMEOUT) + { + OnPeerCacheDownSocketTimeout(); + } + } + else + { + if ((::GetTickCount() - m_dwLastBlockReceived) > DOWNLOADTIMEOUT) + { + ASSERT( socket != NULL ); + if (socket != NULL) + { + ASSERT( !socket->IsRawDataMode() ); + if (!socket->IsRawDataMode()) + SendCancelTransfer(); + } + SetDownloadState(DS_ONQUEUE, _T("Timeout. More than 100 seconds since last complete block was received.")); + } + } +} + +uint16 CUpDownClient::GetAvailablePartCount() const +{ + UINT result = 0; + for (UINT i = 0; i < m_nPartCount; i++){ + if (IsPartAvailable(i)) + result++; + } + return (uint16)result; +} + +void CUpDownClient::SetRemoteQueueRank(UINT nr, bool bUpdateDisplay) +{ + m_nRemoteQueueRank = nr; + UpdateDisplayedInfo(bUpdateDisplay); +} + +void CUpDownClient::UDPReaskACK(uint16 nNewQR) +{ + m_bUDPPending = false; + SetRemoteQueueRank(nNewQR, true); + SetLastAskedTime(); +} + +void CUpDownClient::UDPReaskFNF() +{ + m_bUDPPending = false; + if (GetDownloadState() != DS_DOWNLOADING){ // avoid premature deletion of 'this' client + if (thePrefs.GetVerbose()) + AddDebugLogLine(DLP_LOW, false, _T("UDP FNF-Answer: %s - %s"),DbgGetClientInfo(), DbgGetFileInfo(reqfile ? reqfile->GetFileHash() : NULL)); + if (reqfile) + reqfile->m_DeadSourceList.AddDeadSource(this); + switch (GetDownloadState()) { + case DS_ONQUEUE: + case DS_NONEEDEDPARTS: + DontSwapTo(reqfile); + if (SwapToAnotherFile(_T("Source says it doesn't have the file. CUpDownClient::UDPReaskFNF()"), true, true, true, NULL, false, false)) + break; + /*fall through*/ + default: + theApp.downloadqueue->RemoveSource(this); + if (!socket){ + if (Disconnected(_T("UDPReaskFNF socket=NULL"))) + delete this; + } + } + } + else + { + if (thePrefs.GetVerbose()) + DebugLogWarning(_T("UDP FNF-Answer: %s - did not remove client because of current download state"),GetUserName()); + } +} + +void CUpDownClient::UDPReaskForDownload() +{ + ASSERT ( reqfile ); + if(!reqfile || m_bUDPPending) + return; + + //TODO: This should be changed to determine if the last 4 UDP packets failed, not the total one. + if( m_nTotalUDPPackets > 3 && ((float)(m_nFailedUDPPackets/m_nTotalUDPPackets) > .3)) + return; + + if (GetUDPPort() != 0 && GetUDPVersion() != 0 && thePrefs.GetUDPPort() != 0 && + !theApp.IsFirewalled() && !(socket && socket->IsConnected()) && !thePrefs.GetProxySettings().UseProxy) + { + if( !HasLowID() ) + { + //don't use udp to ask for sources + if(IsSourceRequestAllowed()) + return; + + if(SwapToAnotherFile(_T("A4AF check before OP__ReaskFilePing. CUpDownClient::UDPReaskForDownload()"), true, false, false, NULL, true, true)) { + return; // we swapped, so need to go to tcp + } + + m_bUDPPending = true; + CSafeMemFile data(128); + data.WriteHash16(reqfile->GetFileHash()); + if (GetUDPVersion() > 3) + { + if (reqfile->IsPartFile()) + ((CPartFile*)reqfile)->WritePartStatus(&data); + else + data.WriteUInt16(0); + } + if (GetUDPVersion() > 2) + data.WriteUInt16(reqfile->m_nCompleteSourcesCount); + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugSend("OP__ReaskFilePing", this, reqfile->GetFileHash()); + Packet* response = new Packet(&data, OP_EMULEPROT); + response->opcode = OP_REASKFILEPING; + theStats.AddUpDataOverheadFileRequest(response->size); + theApp.downloadqueue->AddUDPFileReasks(); + theApp.clientudp->SendPacket(response, GetIP(), GetUDPPort(), ShouldReceiveCryptUDPPackets(), GetUserHash(), false, 0); + m_nTotalUDPPackets++; + } + else if (HasLowID() && GetBuddyIP() && GetBuddyPort() && HasValidBuddyID()) + { + m_bUDPPending = true; + CSafeMemFile data(128); + data.WriteHash16(GetBuddyID()); + data.WriteHash16(reqfile->GetFileHash()); + if (GetUDPVersion() > 3) + { + if (reqfile->IsPartFile()) + ((CPartFile*)reqfile)->WritePartStatus(&data); + else + data.WriteUInt16(0); + } + if (GetUDPVersion() > 2) + data.WriteUInt16(reqfile->m_nCompleteSourcesCount); + if (thePrefs.GetDebugClientUDPLevel() > 0) + DebugSend("OP__ReaskCallbackUDP", this, reqfile->GetFileHash()); + Packet* response = new Packet(&data, OP_EMULEPROT); + response->opcode = OP_REASKCALLBACKUDP; + theStats.AddUpDataOverheadFileRequest(response->size); + theApp.downloadqueue->AddUDPFileReasks(); + // FIXME: We dont know which kadversion the buddy has, so we need to send unencrypted + theApp.clientudp->SendPacket(response, GetBuddyIP(), GetBuddyPort(), false, NULL, true, 0); + m_nTotalUDPPackets++; + } + } +} + +void CUpDownClient::UpdateDisplayedInfo(bool force) +{ +#ifdef _DEBUG + force = true; +#endif + DWORD curTick = ::GetTickCount(); + if(force || curTick-m_lastRefreshedDLDisplay > MINWAIT_BEFORE_DLDISPLAY_WINDOWUPDATE+m_random_update_wait) { + theApp.emuledlg->transferwnd->GetDownloadList()->UpdateItem(this); + theApp.emuledlg->transferwnd->GetClientList()->RefreshClient(this); + theApp.emuledlg->transferwnd->GetDownloadClientsList()->RefreshClient(this); + m_lastRefreshedDLDisplay = curTick; + } +} + +const bool CUpDownClient::IsInNoNeededList(const CPartFile* fileToCheck) const +{ + for (POSITION pos = m_OtherNoNeeded_list.GetHeadPosition();pos != 0;m_OtherNoNeeded_list.GetNext(pos)) + { + if (m_OtherNoNeeded_list.GetAt(pos) == fileToCheck) + return true; + } + + return false; +} + +const bool CUpDownClient::SwapToRightFile(CPartFile* SwapTo, CPartFile* cur_file, bool ignoreSuspensions, bool SwapToIsNNPFile, bool curFileisNNPFile, bool& wasSkippedDueToSourceExchange, bool doAgressiveSwapping, bool debug) +{ + bool printDebug = debug && thePrefs.GetLogA4AF(); + + if(printDebug) { + AddDebugLogLine(DLP_LOW, false, _T("oooo Debug: SwapToRightFile. Start compare SwapTo: %s and cur_file %s"), SwapTo?SwapTo->GetFileName():_T("null"), cur_file->GetFileName()); + AddDebugLogLine(DLP_LOW, false, _T("oooo Debug: doAgressiveSwapping: %s"), doAgressiveSwapping?_T("true"):_T("false")); + } + + if (!SwapTo) { + return true; + } + + if(!curFileisNNPFile && cur_file->GetSourceCount() < cur_file->GetMaxSources() || + curFileisNNPFile && cur_file->GetSourceCount() < cur_file->GetMaxSources()*.8) { + + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: cur_file does probably not have too many sources.")); + + if(SwapTo->GetSourceCount() > SwapTo->GetMaxSources() || + SwapTo->GetSourceCount() >= SwapTo->GetMaxSources()*.8 && + SwapTo == reqfile && + ( + GetDownloadState() == DS_LOWTOLOWIP || + GetDownloadState() == DS_REMOTEQUEUEFULL + ) + ) { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: SwapTo is about to be deleted due to too many sources on that file, so we can steal it.")); + return true; + } + + if(ignoreSuspensions || !IsSwapSuspended(cur_file, doAgressiveSwapping, curFileisNNPFile)) { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: No suspend block.")); + + DWORD tempTick = ::GetTickCount(); + bool rightFileHasHigherPrio = CPartFile::RightFileHasHigherPrio(SwapTo, cur_file); + uint32 allNnpReaskTime = FILEREASKTIME*2*(m_OtherNoNeeded_list.GetSize() + ((GetDownloadState() == DS_NONEEDEDPARTS)?1:0)); // wait two reask interval for each nnp file before reasking an nnp file + + if(!SwapToIsNNPFile && (!curFileisNNPFile || GetLastAskedTime(cur_file) == 0 || tempTick-GetLastAskedTime(cur_file) > allNnpReaskTime) && rightFileHasHigherPrio || + SwapToIsNNPFile && curFileisNNPFile && + ( + GetLastAskedTime(SwapTo) != 0 && + ( + GetLastAskedTime(cur_file) == 0 || + tempTick-GetLastAskedTime(SwapTo) < tempTick-GetLastAskedTime(cur_file) && (tempTick-GetLastAskedTime(cur_file) > allNnpReaskTime || rightFileHasHigherPrio && tempTick-GetLastAskedTime(SwapTo) < allNnpReaskTime) + ) || + rightFileHasHigherPrio && GetLastAskedTime(SwapTo) == 0 && GetLastAskedTime(cur_file) == 0 + ) || + SwapToIsNNPFile && !curFileisNNPFile) { + if(printDebug) + if(!SwapToIsNNPFile && !curFileisNNPFile && rightFileHasHigherPrio) + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: Higher prio.")); + else if(!SwapToIsNNPFile && (GetLastAskedTime(cur_file) == 0 || tempTick-GetLastAskedTime(cur_file) > allNnpReaskTime) && rightFileHasHigherPrio) + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: Time to reask nnp and it had higher prio.")); + else if(GetLastAskedTime(SwapTo) != 0 && + ( + GetLastAskedTime(cur_file) == 0 || + tempTick-GetLastAskedTime(SwapTo) < tempTick-GetLastAskedTime(cur_file) && (tempTick-GetLastAskedTime(cur_file) > allNnpReaskTime || rightFileHasHigherPrio && tempTick-GetLastAskedTime(SwapTo) < allNnpReaskTime) + ) + ) + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: Both nnp and cur_file has longer time since reasked.")); + else if(SwapToIsNNPFile && !curFileisNNPFile) + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: SwapToIsNNPFile && !curFileisNNPFile")); + else + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: Higher prio for unknown reason!")); + + if(IsSourceRequestAllowed(cur_file) && (cur_file->AllowSwapForSourceExchange() || cur_file == reqfile && RecentlySwappedForSourceExchange()) || + !(IsSourceRequestAllowed(SwapTo) && (SwapTo->AllowSwapForSourceExchange() || SwapTo == reqfile && RecentlySwappedForSourceExchange())) || + (GetDownloadState()==DS_ONQUEUE && GetRemoteQueueRank() <= 50)) { + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("oooo Debug: Source Request check ok.")); + return true; + } else { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: Source Request check failed.")); + wasSkippedDueToSourceExchange = true; + } + } + + if(IsSourceRequestAllowed(cur_file, true) && (cur_file->AllowSwapForSourceExchange() || cur_file == reqfile && RecentlySwappedForSourceExchange()) && + !(IsSourceRequestAllowed(SwapTo, true) && (SwapTo->AllowSwapForSourceExchange() || SwapTo == reqfile && RecentlySwappedForSourceExchange())) && + (GetDownloadState()!=DS_ONQUEUE || GetDownloadState()==DS_ONQUEUE && GetRemoteQueueRank() > 50)) { + wasSkippedDueToSourceExchange = true; + + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("oooo Debug: Source Exchange.")); + return true; + } + } else if(printDebug) { + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: Suspend block.")); + } + } else if(printDebug) { + AddDebugLogLine(DLP_VERYLOW, false, _T("oooo Debug: cur_file probably has too many sources.")); + } + + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("oooo Debug: Return false")); + + return false; +} + +bool CUpDownClient::SwapToAnotherFile(LPCTSTR reason, bool bIgnoreNoNeeded, bool ignoreSuspensions, bool bRemoveCompletely, CPartFile* toFile, bool allowSame, bool isAboutToAsk, bool debug) +{ + bool printDebug = debug && thePrefs.GetLogA4AF(); + + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: Switching source %s Remove = %s; bIgnoreNoNeeded = %s; allowSame = %s; Reason = \"%s\""), DbgGetClientInfo(), (bRemoveCompletely ? _T("Yes") : _T("No")), (bIgnoreNoNeeded ? _T("Yes") : _T("No")), (allowSame ? _T("Yes") : _T("No")), reason); + + if(!bRemoveCompletely && allowSame && thePrefs.GetA4AFSaveCpu()) { + // Only swap if we can't keep the old source + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: return false since prefs setting to save cpu is enabled.")); + return false; + } + + bool doAgressiveSwapping = (bRemoveCompletely || !allowSame || isAboutToAsk); + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: doAgressiveSwapping: %s"), doAgressiveSwapping?_T("true"):_T("false")); + + if(!bRemoveCompletely && !ignoreSuspensions && allowSame && GetTimeUntilReask(reqfile, doAgressiveSwapping, true, false) > 0 && (GetDownloadState() != DS_NONEEDEDPARTS || m_OtherRequests_list.IsEmpty())) { + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: return false due to not reached reask time: GetTimeUntilReask(...) > 0")); + + return false; + } + + if(!bRemoveCompletely && allowSame && m_OtherRequests_list.IsEmpty() && (/* !bIgnoreNoNeeded ||*/ m_OtherNoNeeded_list.IsEmpty())) { + // no file to swap too, and it's ok to keep it + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: return false due to no file to swap too, and it's ok to keep it.")); + return false; + } + + if (!bRemoveCompletely && + (GetDownloadState() != DS_ONQUEUE && + GetDownloadState() != DS_NONEEDEDPARTS && + GetDownloadState() != DS_TOOMANYCONNS && + GetDownloadState() != DS_REMOTEQUEUEFULL && + GetDownloadState() != DS_CONNECTED + )) { + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: return false due to wrong state.")); + return false; + } + + CPartFile* SwapTo = NULL; + CPartFile* cur_file = NULL; + POSITION finalpos = NULL; + CTypedPtrList* usedList = NULL; + + if(allowSame && !bRemoveCompletely) { + SwapTo = reqfile; + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: allowSame: File %s SourceReq: %s"), reqfile->GetFileName(), IsSourceRequestAllowed(reqfile)?_T("true"):_T("false")); + } + + bool SwapToIsNNP = (SwapTo != NULL && SwapTo == reqfile && GetDownloadState() == DS_NONEEDEDPARTS); + + CPartFile* skippedDueToSourceExchange = NULL; + bool skippedIsNNP = false; + + if (!m_OtherRequests_list.IsEmpty()){ + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: m_OtherRequests_list")); + + for (POSITION pos = m_OtherRequests_list.GetHeadPosition();pos != 0;m_OtherRequests_list.GetNext(pos)){ + cur_file = m_OtherRequests_list.GetAt(pos); + + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Checking file: %s SoureReq: %s"), cur_file->GetFileName(), IsSourceRequestAllowed(cur_file)?_T("true"):_T("false")); + + if(!bRemoveCompletely && !ignoreSuspensions && allowSame && IsSwapSuspended(cur_file, doAgressiveSwapping, false)) { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: continue due to IsSwapSuspended(file) == true")); + continue; + } + + if (cur_file != reqfile && theApp.downloadqueue->IsPartFile(cur_file) && !cur_file->IsStopped() + && (cur_file->GetStatus(false) == PS_READY || cur_file->GetStatus(false) == PS_EMPTY)) + { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: It's a partfile, not stopped, etc.")); + + if (toFile != NULL){ + if (cur_file == toFile){ + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Found toFile.")); + + SwapTo = cur_file; + SwapToIsNNP = false; + usedList = &m_OtherRequests_list; + finalpos = pos; + break; + } + } else { + bool wasSkippedDueToSourceExchange = false; + if(SwapToRightFile(SwapTo, cur_file, ignoreSuspensions, SwapToIsNNP, false, wasSkippedDueToSourceExchange, doAgressiveSwapping, debug)) { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Swapping to file %s"), cur_file->GetFileName()); + + if(SwapTo && wasSkippedDueToSourceExchange) { + if(debug && thePrefs.GetLogA4AF()) AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Swapped due to source exchange possibility")); + bool discardSkipped = false; + if(SwapToRightFile(skippedDueToSourceExchange, SwapTo, ignoreSuspensions, skippedIsNNP, SwapToIsNNP, discardSkipped, doAgressiveSwapping, debug)) { + skippedDueToSourceExchange = SwapTo; + skippedIsNNP = skippedIsNNP?true:(SwapTo == reqfile && GetDownloadState() == DS_NONEEDEDPARTS); + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Skipped file was better than last skipped file.")); + } + } + + SwapTo = cur_file; + SwapToIsNNP = false; + usedList = &m_OtherRequests_list; + finalpos=pos; + } else { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Keeping file %s"), SwapTo->GetFileName()); + if(wasSkippedDueToSourceExchange) { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Kept the file due to source exchange possibility")); + bool discardSkipped = false; + if(SwapToRightFile(skippedDueToSourceExchange, cur_file, ignoreSuspensions, skippedIsNNP, false, discardSkipped, doAgressiveSwapping, debug)) { + skippedDueToSourceExchange = cur_file; + skippedIsNNP = false; + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Skipped file was better than last skipped file.")); + } + } + } + } + } + } + } + + //if ((!SwapTo || SwapTo == reqfile && GetDownloadState() == DS_NONEEDEDPARTS) && bIgnoreNoNeeded){ + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: m_OtherNoNeeded_list")); + + for (POSITION pos = m_OtherNoNeeded_list.GetHeadPosition();pos != 0;m_OtherNoNeeded_list.GetNext(pos)){ + cur_file = m_OtherNoNeeded_list.GetAt(pos); + + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Checking file: %s "), cur_file->GetFileName()); + + if(!bRemoveCompletely && !ignoreSuspensions && allowSame && IsSwapSuspended(cur_file, doAgressiveSwapping, true)) { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: continue due to !IsSwapSuspended(file) == true")); + continue; + } + + if (cur_file != reqfile && theApp.downloadqueue->IsPartFile(cur_file) && !cur_file->IsStopped() + && (cur_file->GetStatus(false) == PS_READY || cur_file->GetStatus(false) == PS_EMPTY) ) + { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: It's a partfile, not stopped, etc.")); + + if (toFile != NULL){ + if (cur_file == toFile){ + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Found toFile.")); + + SwapTo = cur_file; + usedList = &m_OtherNoNeeded_list; + finalpos = pos; + break; + } + } else { + bool wasSkippedDueToSourceExchange = false; + if(SwapToRightFile(SwapTo, cur_file, ignoreSuspensions, SwapToIsNNP, true, wasSkippedDueToSourceExchange, doAgressiveSwapping, debug)) { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Swapping to file %s"), cur_file->GetFileName()); + + if(SwapTo && wasSkippedDueToSourceExchange) { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Swapped due to source exchange possibility")); + bool discardSkipped = false; + if(SwapToRightFile(skippedDueToSourceExchange, SwapTo, ignoreSuspensions, skippedIsNNP, SwapToIsNNP, discardSkipped, doAgressiveSwapping, debug)) { + skippedDueToSourceExchange = SwapTo; + skippedIsNNP = skippedIsNNP?true:(SwapTo == reqfile && GetDownloadState() == DS_NONEEDEDPARTS); + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Skipped file was better than last skipped file.")); + } + } + + SwapTo = cur_file; + SwapToIsNNP = true; + usedList = &m_OtherNoNeeded_list; + finalpos=pos; + } else { + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Keeping file %s"), SwapTo->GetFileName()); + if(wasSkippedDueToSourceExchange) { + if(debug && thePrefs.GetVerbose()) AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Kept the file due to source exchange possibility")); + bool discardSkipped = false; + if(SwapToRightFile(skippedDueToSourceExchange, cur_file, ignoreSuspensions, skippedIsNNP, true, discardSkipped, doAgressiveSwapping, debug)) { + skippedDueToSourceExchange = cur_file; + skippedIsNNP = true; + if(printDebug) + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Skipped file was better than last skipped file.")); + } + } + } + } + } + } + //} + + if (SwapTo){ + if(printDebug) { + if(SwapTo != reqfile) { + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: Found file to swap to %s"), SwapTo->GetFileName()); + } else { + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: Will keep current file. %s"), SwapTo->GetFileName()); + } + } + + CString strInfo(reason); + if(skippedDueToSourceExchange) { + bool wasSkippedDueToSourceExchange = false; + bool skippedIsBetter = SwapToRightFile(SwapTo, skippedDueToSourceExchange, ignoreSuspensions, SwapToIsNNP, skippedIsNNP, wasSkippedDueToSourceExchange, doAgressiveSwapping, debug); + if(skippedIsBetter || wasSkippedDueToSourceExchange) { + SwapTo->SetSwapForSourceExchangeTick(); + SetSwapForSourceExchangeTick(); + + strInfo = _T("******SourceExchange-Swap****** ") + strInfo; + if(printDebug) { + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Due to sourceExchange.")); + } else if(thePrefs.GetLogA4AF() && reqfile == SwapTo) { + AddDebugLogLine(DLP_LOW, false, _T("ooo Didn't swap source due to source exchange possibility. %s Remove = %s '%s' Reason: %s"), DbgGetClientInfo(), (bRemoveCompletely ? _T("Yes") : _T("No") ), (this->reqfile)?this->reqfile->GetFileName():_T("null"), strInfo); + } + } else if(printDebug) { + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Normal. SwapTo better than skippedDueToSourceExchange.")); + } + } else if(printDebug) { + AddDebugLogLine(DLP_VERYLOW, false, _T("ooo Debug: Normal. skippedDueToSourceExchange == NULL")); + } + + if (SwapTo != reqfile && DoSwap(SwapTo,bRemoveCompletely, strInfo)){ + if(debug && thePrefs.GetLogA4AF()) AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: Swap successful.")); + if(usedList && finalpos) { + usedList->RemoveAt(finalpos); + } + return true; + } else if(printDebug) { + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: Swap didn't happen.")); + } + } + + if(printDebug) + AddDebugLogLine(DLP_LOW, false, _T("ooo Debug: Done %s"), DbgGetClientInfo()); + + return false; +} + +bool CUpDownClient::DoSwap(CPartFile* SwapTo, bool bRemoveCompletely, LPCTSTR reason) +{ + if (thePrefs.GetLogA4AF()) + AddDebugLogLine(DLP_LOW, false, _T("ooo Swapped source %s Remove = %s '%s' --> %s Reason: %s"), DbgGetClientInfo(), (bRemoveCompletely ? _T("Yes") : _T("No") ), (this->reqfile)?this->reqfile->GetFileName():_T("null"), SwapTo->GetFileName(), reason); + + // 17-Dez-2003 [bc]: This "reqfile->srclists[sourcesslot].Find(this)" was the only place where + // the usage of the "CPartFile::srclists[100]" is more effective than using one list. If this + // function here is still (again) a performance problem there is a more effective way to handle + // the 'Find' situation. Hint: usage of a node ptr which is stored in the CUpDownClient. + POSITION pos = reqfile->srclist.Find(this); + if(pos) + { + reqfile->srclist.RemoveAt(pos); + } else { + AddDebugLogLine(DLP_HIGH, true, _T("o-o Unsync between parfile->srclist and client otherfiles list. Swapping client where client has file as reqfile, but file doesn't have client in srclist. %s Remove = %s '%s' --> '%s' SwapReason: %s"), DbgGetClientInfo(), (bRemoveCompletely ? _T("Yes") : _T("No") ), (this->reqfile)?this->reqfile->GetFileName():_T("null"), SwapTo->GetFileName(), reason); + } + + // remove this client from the A4AF list of our new reqfile + POSITION pos2 = SwapTo->A4AFsrclist.Find(this); + if (pos2) { + SwapTo->A4AFsrclist.RemoveAt(pos2); + } else { + AddDebugLogLine(DLP_HIGH, true, _T("o-o Unsync between parfile->srclist and client otherfiles list. Swapping client where client has file in another list, but file doesn't have client in a4af srclist. %s Remove = %s '%s' --> '%s' SwapReason: %s"), DbgGetClientInfo(), (bRemoveCompletely ? _T("Yes") : _T("No") ), (this->reqfile)?this->reqfile->GetFileName():_T("null"), SwapTo->GetFileName(), reason); + } + theApp.emuledlg->transferwnd->GetDownloadList()->RemoveSource(this,SwapTo); + + reqfile->RemoveDownloadingSource(this); + + if(!bRemoveCompletely) + { + reqfile->A4AFsrclist.AddTail(this); + if (GetDownloadState() == DS_NONEEDEDPARTS) + m_OtherNoNeeded_list.AddTail(reqfile); + else + m_OtherRequests_list.AddTail(reqfile); + + theApp.emuledlg->transferwnd->GetDownloadList()->AddSource(reqfile,this,true); + } else { + m_fileReaskTimes.RemoveKey(reqfile); + } + + SetDownloadState(DS_NONE); + CPartFile* pOldRequestFile = reqfile; + SetRequestFile(SwapTo); + pOldRequestFile->UpdatePartsInfo(); + pOldRequestFile->UpdateAvailablePartsCount(); + + SwapTo->srclist.AddTail(this); + theApp.emuledlg->transferwnd->GetDownloadList()->AddSource(SwapTo,this,false); + + return true; +} + +void CUpDownClient::DontSwapTo(/*const*/ CPartFile* file) +{ + DWORD dwNow = ::GetTickCount(); + + for (POSITION pos = m_DontSwap_list.GetHeadPosition(); pos != 0; m_DontSwap_list.GetNext(pos)) + if(m_DontSwap_list.GetAt(pos).file == file) { + m_DontSwap_list.GetAt(pos).timestamp = dwNow ; + return; + } + PartFileStamp newfs = {file, dwNow }; + m_DontSwap_list.AddHead(newfs); +} + +bool CUpDownClient::IsSwapSuspended(const CPartFile* file, const bool allowShortReaskTime, const bool fileIsNNP) +{ + if(file == reqfile) { + return false; + } + + // Don't swap if we have reasked this client too recently + if(GetTimeUntilReask(file, allowShortReaskTime, true, fileIsNNP) > 0) + return true; + + if (m_DontSwap_list.GetCount()==0) + return false; + + for (POSITION pos = m_DontSwap_list.GetHeadPosition(); pos != 0 && m_DontSwap_list.GetCount()>0; m_DontSwap_list.GetNext(pos)){ + if(m_DontSwap_list.GetAt(pos).file == file){ + if ( ::GetTickCount() - m_DontSwap_list.GetAt(pos).timestamp >= PURGESOURCESWAPSTOP ) { + m_DontSwap_list.RemoveAt(pos); + return false; + } + else + return true; + } + else if (m_DontSwap_list.GetAt(pos).file == NULL) // in which cases should this happen? + m_DontSwap_list.RemoveAt(pos); + } + + return false; +} + +uint32 CUpDownClient::GetTimeUntilReask(const CPartFile* file, const bool allowShortReaskTime, const bool useGivenNNP, const bool givenNNP) const { + DWORD lastAskedTimeTick = GetLastAskedTime(file); + if(lastAskedTimeTick != 0) { + DWORD tick = ::GetTickCount(); + + DWORD reaskTime; + if(allowShortReaskTime || file == reqfile && GetDownloadState() == DS_NONE) { + reaskTime = MIN_REQUESTTIME; + } else if(useGivenNNP && givenNNP || + file == reqfile && GetDownloadState() == DS_NONEEDEDPARTS || + file != reqfile && IsInNoNeededList(file)) { + reaskTime = FILEREASKTIME*2; + } else { + reaskTime = FILEREASKTIME; + } + + if(tick-lastAskedTimeTick < reaskTime) { + return reaskTime-(tick-lastAskedTimeTick); + } else { + return 0; + } + } else { + return 0; + } +} + +uint32 CUpDownClient::GetTimeUntilReask(const CPartFile* file) const { + return GetTimeUntilReask(file, false); +} + +uint32 CUpDownClient::GetTimeUntilReask() const { + return GetTimeUntilReask(reqfile); +} + +bool CUpDownClient::IsValidSource() const +{ + bool valid = false; + switch(GetDownloadState()) + { + case DS_DOWNLOADING: + case DS_ONQUEUE: + case DS_CONNECTED: + case DS_NONEEDEDPARTS: + case DS_REMOTEQUEUEFULL: + case DS_REQHASHSET: + valid = IsEd2kClient(); + } + return valid; +} + +void CUpDownClient::StartDownload() +{ + SetDownloadState(DS_DOWNLOADING); + InitTransferredDownMini(); + SetDownStartTime(); + m_lastPartAsked = (uint16)-1; + SendBlockRequests(); +} + +void CUpDownClient::SendCancelTransfer(Packet* packet) +{ + if (socket == NULL || !IsEd2kClient()){ + ASSERT(0); + return; + } + + if (!GetSentCancelTransfer()) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__CancelTransfer", this); + + bool bDeletePacket; + Packet* pCancelTransferPacket; + if (packet) + { + pCancelTransferPacket = packet; + bDeletePacket = false; + } + else + { + pCancelTransferPacket = new Packet(OP_CANCELTRANSFER, 0); + bDeletePacket = true; + } + theStats.AddUpDataOverheadFileRequest(pCancelTransferPacket->size); + socket->SendPacket(pCancelTransferPacket,bDeletePacket,true); + SetSentCancelTransfer(1); + } + + if (m_pPCDownSocket) + { + m_pPCDownSocket->Safe_Delete(); + m_pPCDownSocket = NULL; + SetPeerCacheDownState(PCDS_NONE); + } +} + +void CUpDownClient::SetRequestFile(CPartFile* pReqFile) +{ + if (pReqFile != reqfile || reqfile == NULL) + ResetFileStatusInfo(); + reqfile = pReqFile; +} + +void CUpDownClient::ProcessAcceptUpload() +{ + m_fQueueRankPending = 1; + if (reqfile && !reqfile->IsStopped() && (reqfile->GetStatus()==PS_READY || reqfile->GetStatus()==PS_EMPTY)) + { + SetSentCancelTransfer(0); + if (GetDownloadState() == DS_ONQUEUE) + { + // PC-TODO: If remote client does not answer the PeerCache query within a timeout, + // automatically fall back to ed2k download. + if ( !SupportPeerCache() // client knows peercache protocol + || !thePrefs.IsPeerCacheDownloadEnabled() // user has enabled peercache downloads + || !theApp.m_pPeerCache->IsCacheAvailable() // we have found our cache and its usable + || !theApp.m_pPeerCache->IsClientPCCompatible(GetVersion(), GetClientSoft()) // the client version is accepted by the cache + || !SendPeerCacheFileRequest()) // request made + { + StartDownload(); + } + } + } + else + { + SendCancelTransfer(); + SetDownloadState((reqfile==NULL || reqfile->IsStopped()) ? DS_NONE : DS_ONQUEUE); + } +} + +void CUpDownClient::ProcessEdonkeyQueueRank(const uchar* packet, UINT size) +{ + CSafeMemFile data(packet, size); + uint32 rank = data.ReadUInt32(); + if (thePrefs.GetDebugClientTCPLevel() > 0) + Debug(_T(" QR=%u (prev. %d)\n"), rank, IsRemoteQueueFull() ? (UINT)-1 : (UINT)GetRemoteQueueRank()); + SetRemoteQueueRank(rank, GetDownloadState() == DS_ONQUEUE); + CheckQueueRankFlood(); +} + +void CUpDownClient::ProcessEmuleQueueRank(const uchar* packet, UINT size) +{ + if (size != 12) + throw GetResString(IDS_ERR_BADSIZE); + uint16 rank = PeekUInt16(packet); + if (thePrefs.GetDebugClientTCPLevel() > 0) + Debug(_T(" QR=%u\n"), rank); // no prev. QR available for eMule clients + SetRemoteQueueFull(false); + SetRemoteQueueRank(rank, GetDownloadState() == DS_ONQUEUE); + CheckQueueRankFlood(); +} + +void CUpDownClient::CheckQueueRankFlood() +{ + if (m_fQueueRankPending == 0) + { + if (GetDownloadState() != DS_DOWNLOADING) + { + if (m_fUnaskQueueRankRecv < 3) // NOTE: Do not increase this nr. without increasing the bits for 'm_fUnaskQueueRankRecv' + m_fUnaskQueueRankRecv++; + if (m_fUnaskQueueRankRecv == 3) + { + if (theApp.clientlist->GetBadRequests(this) < 2) + theApp.clientlist->TrackBadRequest(this, 1); + if (theApp.clientlist->GetBadRequests(this) == 2){ + theApp.clientlist->TrackBadRequest(this, -2); // reset so the client will not be rebanned right after the ban is lifted + Ban(_T("QR flood")); + } + throw CString(thePrefs.GetLogBannedClients() ? _T("QR flood") : _T("")); + } + } + } + else + { + m_fQueueRankPending = 0; + m_fUnaskQueueRankRecv = 0; + } +} + +uint32 CUpDownClient::GetLastAskedTime(const CPartFile* partFile) const +{ + CPartFile* file = (CPartFile*)partFile; + if (file == NULL) { + file = reqfile; + } + + DWORD lastChangedTick; + return m_fileReaskTimes.Lookup(file, lastChangedTick)? lastChangedTick : 0; +} + +void CUpDownClient::SetReqFileAICHHash(CAICHHash* val) +{ // TODO fileident optimize to save some memory + if (m_pReqFileAICHHash != NULL && m_pReqFileAICHHash != val) + delete m_pReqFileAICHHash; + m_pReqFileAICHHash = val; +} + +void CUpDownClient::SendAICHRequest(CPartFile* pForFile, uint16 nPart) +{ + CAICHRequestedData request; + request.m_nPart = nPart; + request.m_pClient = this; + request.m_pPartFile = pForFile; + CAICHRecoveryHashSet::m_liRequestedData.AddTail(request); + m_fAICHRequested = TRUE; + CSafeMemFile data; + data.WriteHash16(pForFile->GetFileHash()); + data.WriteUInt16(nPart); + pForFile->GetAICHRecoveryHashSet()->GetMasterHash().Write(&data); + Packet* packet = new Packet(&data, OP_EMULEPROT, OP_AICHREQUEST); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__AichRequest", this, (uchar*)packet->pBuffer); + theStats.AddUpDataOverheadFileRequest(packet->size); + SafeConnectAndSendPacket(packet); +} + +void CUpDownClient::ProcessAICHAnswer(const uchar* packet, UINT size) +{ + if (m_fAICHRequested == FALSE){ + throw CString(_T("Received unrequested AICH Packet")); + } + m_fAICHRequested = FALSE; + + CSafeMemFile data(packet, size); + if (size <= 16){ + CAICHRecoveryHashSet::ClientAICHRequestFailed(this); + return; + } + uchar abyHash[16]; + data.ReadHash16(abyHash); + CPartFile* pPartFile = theApp.downloadqueue->GetFileByID(abyHash); + CAICHRequestedData request = CAICHRecoveryHashSet::GetAICHReqDetails(this); + uint16 nPart = data.ReadUInt16(); + if (pPartFile != NULL && request.m_pPartFile == pPartFile && request.m_pClient == this && nPart == request.m_nPart){ + CAICHHash ahMasterHash(&data); + if ( (pPartFile->GetAICHRecoveryHashSet()->GetStatus() == AICH_TRUSTED || pPartFile->GetAICHRecoveryHashSet()->GetStatus() == AICH_VERIFIED) + && ahMasterHash == pPartFile->GetAICHRecoveryHashSet()->GetMasterHash()) + { + if(pPartFile->GetAICHRecoveryHashSet()->ReadRecoveryData((uint64)request.m_nPart*PARTSIZE, &data)){ + // finally all checks passed, everythings seem to be fine + AddDebugLogLine(DLP_DEFAULT, false, _T("AICH Packet Answer: Succeeded to read and validate received recoverydata")); + CAICHRecoveryHashSet::RemoveClientAICHRequest(this); + pPartFile->AICHRecoveryDataAvailable(request.m_nPart); + return; + } + else + DebugLogError(_T("AICH Packet Answer: Failed to read and validate received recoverydata")); + } + else + AddDebugLogLine(DLP_HIGH, false, _T("AICH Packet Answer: Masterhash differs from packethash or hashset has no trusted Masterhash")); + } + else + AddDebugLogLine(DLP_HIGH, false, _T("AICH Packet Answer: requested values differ from values in packet")); + + CAICHRecoveryHashSet::ClientAICHRequestFailed(this); +} + +void CUpDownClient::ProcessAICHRequest(const uchar* packet, UINT size) +{ + if (size != (UINT)(16 + 2 + CAICHHash::GetHashSize())) + throw CString(_T("Received AICH Request Packet with wrong size")); + + CSafeMemFile data(packet, size); + uchar abyHash[16]; + data.ReadHash16(abyHash); + uint16 nPart = data.ReadUInt16(); + CAICHHash ahMasterHash(&data); + CKnownFile* pKnownFile = theApp.sharedfiles->GetFileByID(abyHash); + if (pKnownFile != NULL){ + if (pKnownFile->IsAICHRecoverHashSetAvailable() && pKnownFile->GetFileIdentifier().HasAICHHash() + && pKnownFile->GetFileIdentifier().GetAICHHash() == ahMasterHash && pKnownFile->GetPartCount() > nPart + && pKnownFile->GetFileSize() > (uint64)EMBLOCKSIZE && (uint64)pKnownFile->GetFileSize() - PARTSIZE*(uint64)nPart > EMBLOCKSIZE) + { + CSafeMemFile fileResponse; + fileResponse.WriteHash16(pKnownFile->GetFileHash()); + fileResponse.WriteUInt16(nPart); + pKnownFile->GetFileIdentifier().GetAICHHash().Write(&fileResponse); + CAICHRecoveryHashSet recHashSet(pKnownFile, pKnownFile->GetFileSize()); + recHashSet.SetMasterHash(pKnownFile->GetFileIdentifier().GetAICHHash(), AICH_HASHSETCOMPLETE); + if (recHashSet.CreatePartRecoveryData((uint64)nPart*PARTSIZE, &fileResponse)){ + AddDebugLogLine(DLP_HIGH, false, _T("AICH Packet Request: Successfully created and send recoverydata for %s to %s"), pKnownFile->GetFileName(), DbgGetClientInfo()); + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__AichAnswer", this, pKnownFile->GetFileHash()); + Packet* packAnswer = new Packet(&fileResponse, OP_EMULEPROT, OP_AICHANSWER); + theStats.AddUpDataOverheadFileRequest(packAnswer->size); + SafeConnectAndSendPacket(packAnswer); + return; + } + else + AddDebugLogLine(DLP_HIGH, false, _T("AICH Packet Request: Failed to create recoverydata for %s to %s"), pKnownFile->GetFileName(), DbgGetClientInfo()); + } + else{ + AddDebugLogLine(DLP_HIGH, false, _T("AICH Packet Request: Failed to create recoverydata - Hashset not ready or requested Hash differs from Masterhash for %s to %s"), pKnownFile->GetFileName(), DbgGetClientInfo()); + } + + } + else + AddDebugLogLine(DLP_HIGH, false, _T("AICH Packet Request: Failed to find requested shared file - %s"), DbgGetClientInfo()); + + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__AichAnswer", this, abyHash); + Packet* packAnswer = new Packet(OP_AICHANSWER, 16, OP_EMULEPROT); + md4cpy(packAnswer->pBuffer, abyHash); + theStats.AddUpDataOverheadFileRequest(packAnswer->size); + SafeConnectAndSendPacket(packAnswer); +} + +void CUpDownClient::ProcessAICHFileHash(CSafeMemFile* data, CPartFile* file, const CAICHHash* pAICHHash) +{ + CPartFile* pPartFile = file; + if (pPartFile == NULL && data != NULL){ + uchar abyHash[16]; + data->ReadHash16(abyHash); + pPartFile = theApp.downloadqueue->GetFileByID(abyHash); + } + CAICHHash ahMasterHash; + if (pAICHHash == NULL && data != NULL) + ahMasterHash.Read(data); + else + ahMasterHash = *pAICHHash; + if(pPartFile != NULL && pPartFile == GetRequestFile()){ + SetReqFileAICHHash(new CAICHHash(ahMasterHash)); + pPartFile->GetAICHRecoveryHashSet()->UntrustedHashReceived(ahMasterHash, GetConnectIP()); + + if (pPartFile->GetFileIdentifierC().HasAICHHash() && pPartFile->GetFileIdentifierC().GetAICHHash() != ahMasterHash) + { + // this an legacy client and he sent us a hash different from our verified one, which menas the fileidentifiers + // are different. We handle this just like a FNF-Answer to our downloadrequest and remove the client from our sourcelist, because we + // sure don't want to download from him + pPartFile->m_DeadSourceList.AddDeadSource(this); + DebugLogWarning(_T("Client answered with different AICH hash than local verified on in ProcessAICHFileHash, removing source. File %s, client %s"), pPartFile->GetFileName(), DbgGetClientInfo()); + // if that client does not have my file maybe has another different + // we try to swap to another file ignoring no needed parts files + switch (GetDownloadState()) + { + case DS_REQHASHSET: + // for the love of eMule, don't accept a hashset from him :) + if (m_fHashsetRequestingMD4) + { + DebugLogWarning(_T("... also cancelled hash set request from client due to AICH mismatch")); + pPartFile->m_bMD4HashsetNeeded = true; + } + if (m_fHashsetRequestingAICH) + { + ASSERT( false ); + pPartFile->SetAICHHashSetNeeded(true); + } + m_fHashsetRequestingMD4 = false; + m_fHashsetRequestingAICH = false; + case DS_CONNECTED: + case DS_ONQUEUE: + case DS_NONEEDEDPARTS: + case DS_DOWNLOADING: + DontSwapTo(pPartFile); // ZZ:DownloadManager + if (!SwapToAnotherFile(_T("Source says it doesn't have the file (AICH mismatch). CUpDownClient::ProcessAICHFileHash"), true, true, true, NULL, false, false)) { // ZZ:DownloadManager + theApp.downloadqueue->RemoveSource(this); + } + return; + } + } + } + else + AddDebugLogLine(DLP_HIGH, false, _T("ProcessAICHFileHash(): PartFile not found or Partfile differs from requested file, %s"), DbgGetClientInfo()); +} + +void CUpDownClient::SendHashSetRequest() +{ + if (socket && socket->IsConnected()) + { + Packet* packet = NULL; + if (SupportsFileIdentifiers()) + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__HashSetRequest2", this, reqfile->GetFileHash()); + CSafeMemFile filePacket(60); + reqfile->GetFileIdentifier().WriteIdentifier(&filePacket); + // 6 Request Options - RESERVED + // 1 Request AICH HashSet + // 1 Request MD4 HashSet + uint8 byOptions = 0; + if (reqfile->m_bMD4HashsetNeeded) + { + m_fHashsetRequestingMD4 = 1; + byOptions |= 0x01; + reqfile->m_bMD4HashsetNeeded = false; + } + if (reqfile->IsAICHPartHashSetNeeded() && GetReqFileAICHHash() != NULL && *GetReqFileAICHHash() == reqfile->GetFileIdentifier().GetAICHHash()) + { + m_fHashsetRequestingAICH = 1; + byOptions |= 0x02; + reqfile->SetAICHHashSetNeeded(false); + } + if (byOptions == 0) + { + ASSERT( false ); + return; + } + DEBUG_ONLY( DebugLog(_T("Sending HashSet Request: MD4 %s, AICH %s to client %s"), m_fHashsetRequestingMD4 ? _T("Yes") : _T("No") + , m_fHashsetRequestingAICH ? _T("Yes") : _T("No"), DbgGetClientInfo()) ); + filePacket.WriteUInt8(byOptions); + packet = new Packet(&filePacket, OP_EMULEPROT, OP_HASHSETREQUEST2); + } + else + { + if (thePrefs.GetDebugClientTCPLevel() > 0) + DebugSend("OP__HashSetRequest", this, reqfile->GetFileHash()); + packet = new Packet(OP_HASHSETREQUEST,16); + md4cpy(packet->pBuffer,reqfile->GetFileHash()); + m_fHashsetRequestingMD4 = 1; + reqfile->m_bMD4HashsetNeeded = false; + } + theStats.AddUpDataOverheadFileRequest(packet->size); + SendPacket(packet, true); + SetDownloadState(DS_REQHASHSET); + } + else + ASSERT(0); +} \ No newline at end of file diff --git a/DownloadClientsCtrl.cpp b/DownloadClientsCtrl.cpp new file mode 100644 index 00000000..183515b7 --- /dev/null +++ b/DownloadClientsCtrl.cpp @@ -0,0 +1,606 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "emuledlg.h" +#include "DownloadClientsCtrl.h" +#include "ClientDetailDialog.h" +#include "MemDC.h" +#include "MenuCmds.h" +#include "FriendList.h" +#include "TransferDlg.h" +#include "ChatWnd.h" +#include "UpDownClient.h" +#include "UploadQueue.h" +#include "ClientCredits.h" +#include "PartFile.h" +#include "Kademlia/Kademlia/Kademlia.h" +#include "SharedFileList.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +IMPLEMENT_DYNAMIC(CDownloadClientsCtrl, CMuleListCtrl) + +BEGIN_MESSAGE_MAP(CDownloadClientsCtrl, CMuleListCtrl) + ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnLvnColumnClick) + ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetDispInfo) + ON_NOTIFY_REFLECT(NM_DBLCLK, OnNmDblClk) + ON_WM_CONTEXTMENU() + ON_WM_SYSCOLORCHANGE() +END_MESSAGE_MAP() + +CDownloadClientsCtrl::CDownloadClientsCtrl() + : CListCtrlItemWalk(this) +{ + SetGeneralPurposeFind(true); + SetSkinKey(L"DownloadingLv"); +} + +void CDownloadClientsCtrl::Init() +{ + SetPrefsKey(_T("DownloadClientsCtrl")); + SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + + InsertColumn(0, GetResString(IDS_QL_USERNAME), LVCFMT_LEFT, DFLT_CLIENTNAME_COL_WIDTH); + InsertColumn(1, GetResString(IDS_CD_CSOFT), LVCFMT_LEFT, DFLT_CLIENTSOFT_COL_WIDTH); + InsertColumn(2, GetResString(IDS_FILE), LVCFMT_LEFT, DFLT_FILENAME_COL_WIDTH); + InsertColumn(3, GetResString(IDS_DL_SPEED), LVCFMT_RIGHT, DFLT_DATARATE_COL_WIDTH); + InsertColumn(4, GetResString(IDS_AVAILABLEPARTS), LVCFMT_LEFT, DFLT_PARTSTATUS_COL_WIDTH); + InsertColumn(5, GetResString(IDS_CL_TRANSFDOWN), LVCFMT_RIGHT, DFLT_SIZE_COL_WIDTH); + InsertColumn(6, GetResString(IDS_CL_TRANSFUP), LVCFMT_RIGHT, DFLT_SIZE_COL_WIDTH); + InsertColumn(7, GetResString(IDS_META_SRCTYPE), LVCFMT_LEFT, 100); + + SetAllIcons(); + Localize(); + LoadSettings(); + SetSortArrow(); + SortItems(SortProc, GetSortItem() + (GetSortAscending() ? 0 : 100)); +} + +void CDownloadClientsCtrl::Localize() +{ + CHeaderCtrl *pHeaderCtrl = GetHeaderCtrl(); + HDITEM hdi; + hdi.mask = HDI_TEXT; + + CString strRes; + strRes = GetResString(IDS_QL_USERNAME); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(0, &hdi); + + strRes = GetResString(IDS_CD_CSOFT); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(1, &hdi); + + strRes = GetResString(IDS_FILE); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(2, &hdi); + + strRes = GetResString(IDS_DL_SPEED); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(3, &hdi); + + strRes = GetResString(IDS_AVAILABLEPARTS); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(4, &hdi); + + strRes = GetResString(IDS_CL_TRANSFDOWN); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(5, &hdi); + + strRes = GetResString(IDS_CL_TRANSFUP); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(6, &hdi); + + strRes = GetResString(IDS_META_SRCTYPE); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(7, &hdi); +} + +void CDownloadClientsCtrl::OnSysColorChange() +{ + CMuleListCtrl::OnSysColorChange(); + SetAllIcons(); +} + +void CDownloadClientsCtrl::SetAllIcons() +{ + ApplyImageList(NULL); + m_ImageList.DeleteImageList(); + m_ImageList.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + m_ImageList.Add(CTempIconLoader(_T("ClientEDonkey"))); + m_ImageList.Add(CTempIconLoader(_T("ClientCompatible"))); + m_ImageList.Add(CTempIconLoader(_T("ClientEDonkeyPlus"))); + m_ImageList.Add(CTempIconLoader(_T("ClientCompatiblePlus"))); + m_ImageList.Add(CTempIconLoader(_T("Friend"))); + m_ImageList.Add(CTempIconLoader(_T("ClientMLDonkey"))); + m_ImageList.Add(CTempIconLoader(_T("ClientMLDonkeyPlus"))); + m_ImageList.Add(CTempIconLoader(_T("ClientEDonkeyHybrid"))); + m_ImageList.Add(CTempIconLoader(_T("ClientEDonkeyHybridPlus"))); + m_ImageList.Add(CTempIconLoader(_T("ClientShareaza"))); + m_ImageList.Add(CTempIconLoader(_T("ClientShareazaPlus"))); + m_ImageList.Add(CTempIconLoader(_T("ClientAMule"))); + m_ImageList.Add(CTempIconLoader(_T("ClientAMulePlus"))); + m_ImageList.Add(CTempIconLoader(_T("ClientLPhant"))); + m_ImageList.Add(CTempIconLoader(_T("ClientLPhantPlus"))); + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("ClientSecureOvl"))), 1); + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("OverlayObfu"))), 2); + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("OverlaySecureObfu"))), 3); + // Apply the image list also to the listview control, even if we use our own 'DrawItem'. + // This is needed to give the listview control a chance to initialize the row height. + ASSERT( (GetStyle() & LVS_SHAREIMAGELISTS) != 0 ); + VERIFY( ApplyImageList(m_ImageList) == NULL ); +} + +void CDownloadClientsCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (!theApp.emuledlg->IsRunning()) + return; + if (!lpDrawItemStruct->itemData) + return; + + CMemDC dc(CDC::FromHandle(lpDrawItemStruct->hDC), &lpDrawItemStruct->rcItem); + BOOL bCtrlFocused; + InitItemMemDC(dc, lpDrawItemStruct, bCtrlFocused); + CRect cur_rec(lpDrawItemStruct->rcItem); + CRect rcClient; + GetClientRect(&rcClient); + const CUpDownClient *client = (CUpDownClient *)lpDrawItemStruct->itemData; + + CHeaderCtrl *pHeaderCtrl = GetHeaderCtrl(); + int iCount = pHeaderCtrl->GetItemCount(); + cur_rec.right = cur_rec.left - sm_iLabelOffset; + cur_rec.left += sm_iIconOffset; + for (int iCurrent = 0; iCurrent < iCount; iCurrent++) + { + int iColumn = pHeaderCtrl->OrderToIndex(iCurrent); + if (!IsColumnHidden(iColumn)) + { + UINT uDrawTextAlignment; + int iColumnWidth = GetColumnWidth(iColumn, uDrawTextAlignment); + cur_rec.right += iColumnWidth; + if (cur_rec.left < cur_rec.right && HaveIntersection(rcClient, cur_rec)) + { + TCHAR szItem[1024]; + GetItemDisplayText(client, iColumn, szItem, _countof(szItem)); + switch (iColumn) + { + case 0:{ + int iImage; + if (client->credits != NULL) + { + if (client->IsFriend()) + iImage = 4; + else if (client->GetClientSoft() == SO_EDONKEYHYBRID) { + if (client->credits->GetScoreRatio(client->GetIP()) > 1) + iImage = 8; + else + iImage = 7; + } + else if (client->GetClientSoft() == SO_MLDONKEY) { + if (client->credits->GetScoreRatio(client->GetIP()) > 1) + iImage = 6; + else + iImage = 5; + } + else if (client->GetClientSoft() == SO_SHAREAZA) { + if (client->credits->GetScoreRatio(client->GetIP()) > 1) + iImage = 10; + else + iImage = 9; + } + else if (client->GetClientSoft() == SO_AMULE) { + if (client->credits->GetScoreRatio(client->GetIP()) > 1) + iImage = 12; + else + iImage = 11; + } + else if (client->GetClientSoft() == SO_LPHANT) { + if (client->credits->GetScoreRatio(client->GetIP()) > 1) + iImage = 14; + else + iImage = 13; + } + else if (client->ExtProtocolAvailable()) { + if (client->credits->GetScoreRatio(client->GetIP()) > 1) + iImage = 3; + else + iImage = 1; + } + else { + if (client->credits->GetScoreRatio(client->GetIP()) > 1) + iImage = 2; + else + iImage = 0; + } + } + else + iImage = 0; + + UINT nOverlayImage = 0; + if ((client->Credits() && client->Credits()->GetCurrentIdentState(client->GetIP()) == IS_IDENTIFIED)) + nOverlayImage |= 1; + if (client->IsObfuscatedConnectionEstablished()) + nOverlayImage |= 2; + int iIconPosY = (cur_rec.Height() > 16) ? ((cur_rec.Height() - 16) / 2) : 1; + POINT point = { cur_rec.left, cur_rec.top + iIconPosY }; + m_ImageList.Draw(dc, iImage, point, ILD_NORMAL | INDEXTOOVERLAYMASK(nOverlayImage)); + + cur_rec.left += 16 + sm_iLabelOffset; + dc.DrawText(szItem, -1, &cur_rec, MLC_DT_TEXT | uDrawTextAlignment); + cur_rec.left -= 16; + cur_rec.right -= sm_iSubItemInset; + break; + } + + case 4: + cur_rec.bottom--; + cur_rec.top++; + client->DrawStatusBar(dc, &cur_rec, false, thePrefs.UseFlatBar()); + cur_rec.bottom++; + cur_rec.top--; + break; + + default: + dc.DrawText(szItem, -1, &cur_rec, MLC_DT_TEXT | uDrawTextAlignment); + break; + } + } + cur_rec.left += iColumnWidth; + } + } + + DrawFocusRect(dc, lpDrawItemStruct->rcItem, lpDrawItemStruct->itemState & ODS_FOCUS, bCtrlFocused, lpDrawItemStruct->itemState & ODS_SELECTED); +} + +void CDownloadClientsCtrl::GetItemDisplayText(const CUpDownClient *client, int iSubItem, LPTSTR pszText, int cchTextMax) +{ + if (pszText == NULL || cchTextMax <= 0) { + ASSERT(0); + return; + } + pszText[0] = _T('\0'); + switch (iSubItem) + { + case 0: + if (client->GetUserName() == NULL) + _sntprintf(pszText, cchTextMax, _T("(%s)"), GetResString(IDS_UNKNOWN)); + else + _tcsncpy(pszText, client->GetUserName(), cchTextMax); + break; + + case 1: + _tcsncpy(pszText, client->GetClientSoftVer(), cchTextMax); + break; + + case 2: + _tcsncpy(pszText, client->GetRequestFile()->GetFileName(), cchTextMax); + break; + + case 3: + _tcsncpy(pszText, CastItoXBytes((float)client->GetDownloadDatarate(), false, true), cchTextMax); + break; + + case 4: + _tcsncpy(pszText, GetResString(IDS_AVAILABLEPARTS), cchTextMax); + break; + + case 5: + if (client->credits && client->GetSessionDown() < client->credits->GetDownloadedTotal()) + _sntprintf(pszText, cchTextMax, _T("%s (%s)"), CastItoXBytes(client->GetSessionDown()), CastItoXBytes(client->credits->GetDownloadedTotal())); + else + _tcsncpy(pszText, CastItoXBytes(client->GetSessionDown()), cchTextMax); + break; + + case 6: + if (client->credits && client->GetSessionUp() < client->credits->GetUploadedTotal()) + _sntprintf(pszText, cchTextMax, _T("%s (%s)"), CastItoXBytes(client->GetSessionUp()), CastItoXBytes(client->credits->GetUploadedTotal())); + else + _tcsncpy(pszText, CastItoXBytes(client->GetSessionUp()), cchTextMax); + break; + + case 7: + switch (client->GetSourceFrom()) + { + case SF_SERVER: + _tcsncpy(pszText, _T("eD2K Server"), cchTextMax); + break; + case SF_KADEMLIA: + _tcsncpy(pszText, GetResString(IDS_KADEMLIA), cchTextMax); + break; + case SF_SOURCE_EXCHANGE: + _tcsncpy(pszText, GetResString(IDS_SE), cchTextMax); + break; + case SF_PASSIVE: + _tcsncpy(pszText, GetResString(IDS_PASSIVE), cchTextMax); + break; + case SF_LINK: + _tcsncpy(pszText, GetResString(IDS_SW_LINK), cchTextMax); + break; + default: + _tcsncpy(pszText, GetResString(IDS_UNKNOWN), cchTextMax); + break; + } + break; + } + pszText[cchTextMax - 1] = _T('\0'); +} + +void CDownloadClientsCtrl::OnLvnGetDispInfo(NMHDR *pNMHDR, LRESULT *pResult) +{ + if (theApp.emuledlg->IsRunning()) { + // Although we have an owner drawn listview control we store the text for the primary item in the listview, to be + // capable of quick searching those items via the keyboard. Because our listview items may change their contents, + // we do this via a text callback function. The listview control will send us the LVN_DISPINFO notification if + // it needs to know the contents of the primary item. + // + // But, the listview control sends this notification all the time, even if we do not search for an item. At least + // this notification is only sent for the visible items and not for all items in the list. Though, because this + // function is invoked *very* often, do *NOT* put any time consuming code in here. + // + // Vista: That callback is used to get the strings for the label tips for the sub(!) items. + // + NMLVDISPINFO *pDispInfo = reinterpret_cast(pNMHDR); + if (pDispInfo->item.mask & LVIF_TEXT) { + const CUpDownClient* pClient = reinterpret_cast(pDispInfo->item.lParam); + if (pClient != NULL) + GetItemDisplayText(pClient, pDispInfo->item.iSubItem, pDispInfo->item.pszText, pDispInfo->item.cchTextMax); + } + } + *pResult = 0; +} + +void CDownloadClientsCtrl::OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult) +{ + NMLISTVIEW *pNMListView = (NMLISTVIEW *)pNMHDR; + bool sortAscending; + if (GetSortItem() != pNMListView->iSubItem) + { + switch (pNMListView->iSubItem) + { + case 1: // Client Software + case 3: // Download Rate + case 4: // Part Count + case 5: // Session Down + case 6: // Session Up + sortAscending = false; + break; + default: + sortAscending = true; + break; + } + } + else + sortAscending = !GetSortAscending(); + + // Sort table + UpdateSortHistory(pNMListView->iSubItem + (sortAscending ? 0 : 100)); + SetSortArrow(pNMListView->iSubItem, sortAscending); + SortItems(SortProc, pNMListView->iSubItem + (sortAscending ? 0 : 100)); + + *pResult = 0; +} + +int CDownloadClientsCtrl::SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + const CUpDownClient *item1 = (CUpDownClient *)lParam1; + const CUpDownClient *item2 = (CUpDownClient *)lParam2; + int iColumn = (lParamSort >= 100) ? lParamSort - 100 : lParamSort; + int iResult = 0; + switch (iColumn) + { + case 0: + if (item1->GetUserName() && item2->GetUserName()) + iResult = CompareLocaleStringNoCase(item1->GetUserName(), item2->GetUserName()); + else if (item1->GetUserName() == NULL) + iResult = 1; // place clients with no usernames at bottom + else if (item2->GetUserName() == NULL) + iResult = -1; // place clients with no usernames at bottom + break; + + case 1: + if (item1->GetClientSoft() == item2->GetClientSoft()) + iResult = item1->GetVersion() - item2->GetVersion(); + else + iResult = -(item1->GetClientSoft() - item2->GetClientSoft()); // invert result to place eMule's at top + break; + + case 2: { + const CKnownFile *file1 = item1->GetRequestFile(); + const CKnownFile *file2 = item2->GetRequestFile(); + if( (file1 != NULL) && (file2 != NULL)) + iResult = CompareLocaleStringNoCase(file1->GetFileName(), file2->GetFileName()); + else if (file1 == NULL) + iResult = 1; + else + iResult = -1; + break; + } + + case 3: + iResult = CompareUnsigned(item1->GetDownloadDatarate(), item2->GetDownloadDatarate()); + break; + + case 4: + iResult = CompareUnsigned(item1->GetPartCount(), item2->GetPartCount()); + break; + + case 5: + iResult = CompareUnsigned(item1->GetSessionDown(), item2->GetSessionDown()); + break; + + case 6: + iResult = CompareUnsigned(item1->GetSessionUp(), item2->GetSessionUp()); + break; + + case 7: + iResult = item1->GetSourceFrom() - item2->GetSourceFrom(); + break; + } + + if (lParamSort >= 100) + iResult = -iResult; + + //call secondary sortorder, if this one results in equal + int dwNextSort; + if (iResult == 0 && (dwNextSort = theApp.emuledlg->transferwnd->GetDownloadClientsList()->GetNextSortOrder(lParamSort)) != -1) + iResult = SortProc(lParam1, lParam2, dwNextSort); + + return iResult; +} + +void CDownloadClientsCtrl::OnNmDblClk(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1) { + CUpDownClient* client = (CUpDownClient*)GetItemData(iSel); + if (client){ + CClientDetailDialog dialog(client, this); + dialog.DoModal(); + } + } + *pResult = 0; +} + +void CDownloadClientsCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + const CUpDownClient* client = (iSel != -1) ? (CUpDownClient*)GetItemData(iSel) : NULL; + + CTitleMenu ClientMenu; + ClientMenu.CreatePopupMenu(); + ClientMenu.AddMenuTitle(GetResString(IDS_CLIENTS), true); + ClientMenu.AppendMenu(MF_STRING | (client ? MF_ENABLED : MF_GRAYED), MP_DETAIL, GetResString(IDS_SHOWDETAILS), _T("CLIENTDETAILS")); + ClientMenu.SetDefaultItem(MP_DETAIL); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && !client->IsFriend()) ? MF_ENABLED : MF_GRAYED), MP_ADDFRIEND, GetResString(IDS_ADDFRIEND), _T("ADDFRIEND")); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient()) ? MF_ENABLED : MF_GRAYED), MP_MESSAGE, GetResString(IDS_SEND_MSG), _T("SENDMESSAGE")); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && client->GetViewSharedFilesSupport()) ? MF_ENABLED : MF_GRAYED), MP_SHOWLIST, GetResString(IDS_VIEWFILES), _T("VIEWFILES")); + if (Kademlia::CKademlia::IsRunning() && !Kademlia::CKademlia::IsConnected()) + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && client->GetKadPort()!=0 && client->GetKadVersion() > 1) ? MF_ENABLED : MF_GRAYED), MP_BOOT, GetResString(IDS_BOOTSTRAP)); + ClientMenu.AppendMenu(MF_STRING | (GetItemCount() > 0 ? MF_ENABLED : MF_GRAYED), MP_FIND, GetResString(IDS_FIND), _T("Search")); + GetPopupMenuPos(*this, point); + ClientMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); +} + +BOOL CDownloadClientsCtrl::OnCommand(WPARAM wParam, LPARAM /*lParam*/) +{ + wParam = LOWORD(wParam); + + switch (wParam) + { + case MP_FIND: + OnFindStart(); + return TRUE; + } + + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1){ + CUpDownClient* client = (CUpDownClient*)GetItemData(iSel); + switch (wParam){ + case MP_SHOWLIST: + client->RequestSharedFileList(); + break; + case MP_MESSAGE: + theApp.emuledlg->chatwnd->StartSession(client); + break; + case MP_ADDFRIEND: + if (theApp.friendlist->AddFriend(client)) + Update(iSel); + break; + case MP_DETAIL: + case MPG_ALTENTER: + case IDA_ENTER: + { + CClientDetailDialog dialog(client, this); + dialog.DoModal(); + break; + } + case MP_BOOT: + if (client->GetKadPort() && client->GetKadVersion() > 1) + Kademlia::CKademlia::Bootstrap(ntohl(client->GetIP()), client->GetKadPort()); + break; + } + } + return true; +} + +void CDownloadClientsCtrl::AddClient(const CUpDownClient *client) +{ + if (!theApp.emuledlg->IsRunning()) + return; + + int iItemCount = GetItemCount(); + InsertItem(LVIF_TEXT | LVIF_PARAM, iItemCount, LPSTR_TEXTCALLBACK, 0, 0, 0, (LPARAM)client); + theApp.emuledlg->transferwnd->UpdateListCount(CTransferDlg::wnd2Downloading, iItemCount + 1); +} + +void CDownloadClientsCtrl::RemoveClient(const CUpDownClient *client) +{ + if (!theApp.emuledlg->IsRunning()) + return; + + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)client; + int result = FindItem(&find); + if (result != -1) { + DeleteItem(result); + theApp.emuledlg->transferwnd->UpdateListCount(CTransferDlg::wnd2Downloading, GetItemCount()); + } +} + +void CDownloadClientsCtrl::RefreshClient(const CUpDownClient *client) +{ + if (!theApp.emuledlg->IsRunning()) + return; + + if (theApp.emuledlg->activewnd != theApp.emuledlg->transferwnd || !theApp.emuledlg->transferwnd->GetDownloadClientsList()->IsWindowVisible()) + return; + + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)client; + int result = FindItem(&find); + if (result != -1) + Update(result); +} + +void CDownloadClientsCtrl::ShowSelectedUserDetails() +{ + POINT point; + ::GetCursorPos(&point); + CPoint p = point; + ScreenToClient(&p); + int it = HitTest(p); + if (it == -1) + return; + + SetItemState(-1, 0, LVIS_SELECTED); + SetItemState(it, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + SetSelectionMark(it); // display selection mark correctly! + + CUpDownClient* client = (CUpDownClient*)GetItemData(GetSelectionMark()); + if (client){ + CClientDetailDialog dialog(client, this); + dialog.DoModal(); + } +} diff --git a/DownloadClientsCtrl.h b/DownloadClientsCtrl.h new file mode 100644 index 00000000..3ed8f6b8 --- /dev/null +++ b/DownloadClientsCtrl.h @@ -0,0 +1,55 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "MuleListCtrl.h" +#include "ListCtrlItemWalk.h" + +class CUpDownClient; + +class CDownloadClientsCtrl : public CMuleListCtrl, public CListCtrlItemWalk +{ + DECLARE_DYNAMIC(CDownloadClientsCtrl) + +public: + CDownloadClientsCtrl(); + + void Init(); + void AddClient(const CUpDownClient *client); + void RemoveClient(const CUpDownClient *client); + void RefreshClient(const CUpDownClient *client); + void Hide() { ShowWindow(SW_HIDE); } + void Show() { ShowWindow(SW_SHOW); } + void Localize(); + void ShowSelectedUserDetails(); + +protected: + CImageList m_ImageList; + + void SetAllIcons(); + void GetItemDisplayText(const CUpDownClient *client, int iSubItem, LPTSTR pszText, int cchTextMax); + static int CALLBACK SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + + DECLARE_MESSAGE_MAP() + afx_msg void OnContextMenu(CWnd *pWnd, CPoint point); + afx_msg void OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnGetDispInfo(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmDblClk(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnSysColorChange(); +}; \ No newline at end of file diff --git a/DownloadListCtrl.cpp b/DownloadListCtrl.cpp new file mode 100644 index 00000000..4b9b7608 --- /dev/null +++ b/DownloadListCtrl.cpp @@ -0,0 +1,3107 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "DownloadListCtrl.h" +#include "otherfunctions.h" +#include "updownclient.h" +#include "MenuCmds.h" +#include "ClientDetailDialog.h" +#include "FileDetailDialog.h" +#include "commentdialoglst.h" +#include "MetaDataDlg.h" +#include "InputBox.h" +#include "KademliaWnd.h" +#include "emuledlg.h" +#include "DownloadQueue.h" +#include "FriendList.h" +#include "PartFile.h" +#include "ClientCredits.h" +#include "MemDC.h" +#include "ChatWnd.h" +#include "TransferDlg.h" +#include "Kademlia/Kademlia/Kademlia.h" +#include "Kademlia/Kademlia/Prefs.h" +#include "Kademlia/net/KademliaUDPListener.h" +#include "WebServices.h" +#include "Preview.h" +#include "StringConversion.h" +#include "AddSourceDlg.h" +#include "ToolTipCtrlX.h" +#include "CollectionViewDialog.h" +#include "SearchDlg.h" +#include "SharedFileList.h" +#include "ToolbarWnd.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// CDownloadListCtrl + +#define DLC_BARUPDATE 512 + +#define RATING_ICON_WIDTH 16 + + +IMPLEMENT_DYNAMIC(CtrlItem_Struct, CObject) + +IMPLEMENT_DYNAMIC(CDownloadListCtrl, CMuleListCtrl) + +BEGIN_MESSAGE_MAP(CDownloadListCtrl, CMuleListCtrl) + ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnLvnColumnClick) + ON_NOTIFY_REFLECT(LVN_DELETEITEM, OnListModified) + ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetDispInfo) + ON_NOTIFY_REFLECT(LVN_GETINFOTIP, OnLvnGetInfoTip) + ON_NOTIFY_REFLECT(LVN_INSERTITEM, OnListModified) + ON_NOTIFY_REFLECT(LVN_ITEMACTIVATE, OnLvnItemActivate) + ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnListModified) + ON_NOTIFY_REFLECT(NM_DBLCLK, OnNmDblClk) + ON_WM_CONTEXTMENU() + ON_WM_SYSCOLORCHANGE() +END_MESSAGE_MAP() + +CDownloadListCtrl::CDownloadListCtrl() + : CDownloadListListCtrlItemWalk(this) +{ + m_pFontBold = NULL; + m_tooltip = new CToolTipCtrlX; + SetGeneralPurposeFind(true); + SetSkinKey(L"DownloadsLv"); + m_dwLastAvailableCommandsCheck = 0; + m_availableCommandsDirty = true; +} + +CDownloadListCtrl::~CDownloadListCtrl() +{ + if (m_PreviewMenu) + VERIFY( m_PreviewMenu.DestroyMenu() ); + if (m_PrioMenu) + VERIFY( m_PrioMenu.DestroyMenu() ); + if (m_SourcesMenu) + VERIFY( m_SourcesMenu.DestroyMenu() ); + if (m_FileMenu) + VERIFY( m_FileMenu.DestroyMenu() ); + + while (m_ListItems.empty() == false) { + delete m_ListItems.begin()->second; // second = CtrlItem_Struct* + m_ListItems.erase(m_ListItems.begin()); + } + delete m_tooltip; +} + +void CDownloadListCtrl::Init() +{ + SetPrefsKey(_T("DownloadListCtrl")); + SetStyle(); + ASSERT( (GetStyle() & LVS_SINGLESEL) == 0 ); + + CToolTipCtrl* tooltip = GetToolTips(); + if (tooltip){ + m_tooltip->SetFileIconToolTip(true); + m_tooltip->SubclassWindow(*tooltip); + tooltip->ModifyStyle(0, TTS_NOPREFIX); + tooltip->SetDelayTime(TTDT_AUTOPOP, 20000); + tooltip->SetDelayTime(TTDT_INITIAL, thePrefs.GetToolTipDelay()*1000); + } + + InsertColumn(0, GetResString(IDS_DL_FILENAME), LVCFMT_LEFT, DFLT_FILENAME_COL_WIDTH); + InsertColumn(1, GetResString(IDS_DL_SIZE), LVCFMT_RIGHT, DFLT_SIZE_COL_WIDTH); + InsertColumn(2, GetResString(IDS_DL_TRANSF), LVCFMT_RIGHT, DFLT_SIZE_COL_WIDTH, -1, true); + InsertColumn(3, GetResString(IDS_DL_TRANSFCOMPL), LVCFMT_RIGHT, DFLT_SIZE_COL_WIDTH); + InsertColumn(4, GetResString(IDS_DL_SPEED), LVCFMT_RIGHT, DFLT_DATARATE_COL_WIDTH); + InsertColumn(5, GetResString(IDS_DL_PROGRESS), LVCFMT_LEFT, DFLT_PARTSTATUS_COL_WIDTH); + InsertColumn(6, GetResString(IDS_DL_SOURCES), LVCFMT_RIGHT, 60); + InsertColumn(7, GetResString(IDS_PRIORITY), LVCFMT_LEFT, DFLT_PRIORITY_COL_WIDTH); + InsertColumn(8, GetResString(IDS_STATUS), LVCFMT_LEFT, 70); + InsertColumn(9, GetResString(IDS_DL_REMAINS), LVCFMT_LEFT, 110); + CString lsctitle = GetResString(IDS_LASTSEENCOMPL); + lsctitle.Remove(_T(':')); + InsertColumn(10, lsctitle, LVCFMT_LEFT, 150, -1, true); + lsctitle = GetResString(IDS_FD_LASTCHANGE); + lsctitle.Remove(_T(':')); + InsertColumn(11, lsctitle, LVCFMT_LEFT, 120, -1, true); + InsertColumn(12, GetResString(IDS_CAT), LVCFMT_LEFT, 100, -1, true); + InsertColumn(13, GetResString(IDS_ADDEDON), LVCFMT_LEFT, 120); + + SetAllIcons(); + Localize(); + LoadSettings(); + curTab=0; + + if (thePrefs.GetShowActiveDownloadsBold()) + { + if (thePrefs.GetUseSystemFontForMainControls()) + { + CFont *pFont = GetFont(); + LOGFONT lfFont = {0}; + pFont->GetLogFont(&lfFont); + lfFont.lfWeight = FW_BOLD; + m_fontBold.CreateFontIndirect(&lfFont); + m_pFontBold = &m_fontBold; + } + else + m_pFontBold = &theApp.m_fontDefaultBold; + } + + // Barry - Use preferred sort order from preferences + m_bRemainSort = thePrefs.TransferlistRemainSortStyle(); + int adder = 0; + if (GetSortItem() != 9 || !m_bRemainSort) + SetSortArrow(); + else { + SetSortArrow(GetSortItem(), GetSortAscending() ? arrowDoubleUp : arrowDoubleDown); + adder = 81; + } + SortItems(SortProc, GetSortItem() + (GetSortAscending() ? 0 : 100) + adder); +} + +void CDownloadListCtrl::OnSysColorChange() +{ + CMuleListCtrl::OnSysColorChange(); + SetAllIcons(); + CreateMenues(); +} + +void CDownloadListCtrl::SetAllIcons() +{ + ApplyImageList(NULL); + m_ImageList.DeleteImageList(); + m_ImageList.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + m_ImageList.Add(CTempIconLoader(_T("SrcDownloading"))); + m_ImageList.Add(CTempIconLoader(_T("SrcOnQueue"))); + m_ImageList.Add(CTempIconLoader(_T("SrcConnecting"))); + m_ImageList.Add(CTempIconLoader(_T("SrcNNPQF"))); + m_ImageList.Add(CTempIconLoader(_T("SrcUnknown"))); + m_ImageList.Add(CTempIconLoader(_T("ClientCompatible"))); + m_ImageList.Add(CTempIconLoader(_T("Friend"))); + m_ImageList.Add(CTempIconLoader(_T("ClientEDonkey"))); + m_ImageList.Add(CTempIconLoader(_T("ClientMLDonkey"))); + m_ImageList.Add(CTempIconLoader(_T("ClientEDonkeyHybrid"))); + m_ImageList.Add(CTempIconLoader(_T("ClientShareaza"))); + m_ImageList.Add(CTempIconLoader(_T("Server"))); + m_ImageList.Add(CTempIconLoader(_T("ClientAMule"))); + m_ImageList.Add(CTempIconLoader(_T("ClientLPhant"))); + m_ImageList.Add(CTempIconLoader(_T("Rating_NotRated"))); + m_ImageList.Add(CTempIconLoader(_T("Rating_Fake"))); + m_ImageList.Add(CTempIconLoader(_T("Rating_Poor"))); + m_ImageList.Add(CTempIconLoader(_T("Rating_Fair"))); + m_ImageList.Add(CTempIconLoader(_T("Rating_Good"))); + m_ImageList.Add(CTempIconLoader(_T("Rating_Excellent"))); + m_ImageList.Add(CTempIconLoader(_T("Collection_Search"))); // rating for comments are searched on kad + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("ClientSecureOvl"))), 1); + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("OverlayObfu"))), 2); + m_ImageList.SetOverlayImage(m_ImageList.Add(CTempIconLoader(_T("OverlaySecureObfu"))), 3); + // Apply the image list also to the listview control, even if we use our own 'DrawItem'. + // This is needed to give the listview control a chance to initialize the row height. + ASSERT( (GetStyle() & LVS_SHAREIMAGELISTS) != 0 ); + VERIFY( ApplyImageList(m_ImageList) == NULL ); +} + +void CDownloadListCtrl::Localize() +{ + CHeaderCtrl* pHeaderCtrl = GetHeaderCtrl(); + HDITEM hdi; + hdi.mask = HDI_TEXT; + CString strRes; + + strRes = GetResString(IDS_DL_FILENAME); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(0, &hdi); + + strRes = GetResString(IDS_DL_SIZE); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(1, &hdi); + + strRes = GetResString(IDS_DL_TRANSF); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(2, &hdi); + + strRes = GetResString(IDS_DL_TRANSFCOMPL); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(3, &hdi); + + strRes = GetResString(IDS_DL_SPEED); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(4, &hdi); + + strRes = GetResString(IDS_DL_PROGRESS); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(5, &hdi); + + strRes = GetResString(IDS_DL_SOURCES); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(6, &hdi); + + strRes = GetResString(IDS_PRIORITY); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(7, &hdi); + + strRes = GetResString(IDS_STATUS); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(8, &hdi); + + strRes = GetResString(IDS_DL_REMAINS); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(9, &hdi); + + strRes = GetResString(IDS_LASTSEENCOMPL); + strRes.Remove(_T(':')); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(10, &hdi); + + strRes = GetResString(IDS_FD_LASTCHANGE); + strRes.Remove(_T(':')); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(11, &hdi); + + strRes = GetResString(IDS_CAT); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(12, &hdi); + + strRes = GetResString(IDS_ADDEDON); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(13, &hdi); + + CreateMenues(); + ShowFilesCount(); +} + +void CDownloadListCtrl::AddFile(CPartFile* toadd) +{ + // Create new Item + CtrlItem_Struct* newitem = new CtrlItem_Struct; + int itemnr = GetItemCount(); + newitem->owner = NULL; + newitem->type = FILE_TYPE; + newitem->value = toadd; + newitem->parent = NULL; + newitem->dwUpdated = 0; + + // The same file shall be added only once + ASSERT(m_ListItems.find(toadd) == m_ListItems.end()); + m_ListItems.insert(ListItemsPair(toadd, newitem)); + + if (toadd->CheckShowItemInGivenCat(curTab)) + InsertItem(LVIF_PARAM | LVIF_TEXT, itemnr, LPSTR_TEXTCALLBACK, 0, 0, 0, (LPARAM)newitem); + + ShowFilesCount(); +} + +void CDownloadListCtrl::AddSource(CPartFile* owner, CUpDownClient* source, bool notavailable) +{ + // Create new Item + CtrlItem_Struct* newitem = new CtrlItem_Struct; + newitem->owner = owner; + newitem->type = (notavailable) ? UNAVAILABLE_SOURCE : AVAILABLE_SOURCE; + newitem->value = source; + newitem->dwUpdated = 0; + + // Update cross link to the owner + ListItems::const_iterator ownerIt = m_ListItems.find(owner); + ASSERT(ownerIt != m_ListItems.end()); + CtrlItem_Struct* ownerItem = ownerIt->second; + ASSERT(ownerItem->value == owner); + newitem->parent = ownerItem; + + // The same source could be added a few time but only one time per file + { + // Update the other instances of this source + bool bFound = false; + std::pair rangeIt = m_ListItems.equal_range(source); + for(ListItems::const_iterator it = rangeIt.first; it != rangeIt.second; it++){ + CtrlItem_Struct* cur_item = it->second; + + // Check if this source has been already added to this file => to be sure + if(cur_item->owner == owner){ + // Update this instance with its new setting + cur_item->type = newitem->type; + cur_item->dwUpdated = 0; + bFound = true; + } + else if(notavailable == false){ + // The state 'Available' is exclusive + cur_item->type = UNAVAILABLE_SOURCE; + cur_item->dwUpdated = 0; + } + } + + if(bFound == true){ + delete newitem; + return; + } + } + m_ListItems.insert(ListItemsPair(source, newitem)); + + if (owner->srcarevisible) { + // find parent from the CListCtrl to add source + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)ownerItem; + int result = FindItem(&find); + if (result != -1) + InsertItem(LVIF_PARAM | LVIF_TEXT, result + 1, LPSTR_TEXTCALLBACK, 0, 0, 0, (LPARAM)newitem); + } +} + +void CDownloadListCtrl::RemoveSource(CUpDownClient* source, CPartFile* owner) +{ + if (!theApp.emuledlg->IsRunning()) + return; + + // Retrieve all entries matching the source + std::pair rangeIt = m_ListItems.equal_range(source); + for(ListItems::iterator it = rangeIt.first; it != rangeIt.second; ){ + CtrlItem_Struct* delItem = it->second; + if(owner == NULL || owner == delItem->owner){ + // Remove it from the m_ListItems + it = m_ListItems.erase(it); + + // Remove it from the CListCtrl + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)delItem; + int result = FindItem(&find); + if (result != -1) + DeleteItem(result); + + // finally it could be delete + delete delItem; + } + else{ + it++; + } + } +} + +bool CDownloadListCtrl::RemoveFile(const CPartFile* toremove) +{ + bool bResult = false; + if (!theApp.emuledlg->IsRunning()) + return bResult; + // Retrieve all entries matching the File or linked to the file + // Remark: The 'asked another files' clients must be removed from here + ASSERT(toremove != NULL); + for(ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); ){ + CtrlItem_Struct* delItem = it->second; + if(delItem->owner == toremove || delItem->value == (void*)toremove){ + // Remove it from the m_ListItems + it = m_ListItems.erase(it); + + // Remove it from the CListCtrl + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)delItem; + int result = FindItem(&find); + if (result != -1) + DeleteItem(result); + + // finally it could be delete + delete delItem; + bResult = true; + } + else { + it++; + } + } + ShowFilesCount(); + return bResult; +} + +void CDownloadListCtrl::UpdateItem(void* toupdate) +{ + if (!theApp.emuledlg->IsRunning()) + return; + + // Retrieve all entries matching the source + std::pair rangeIt = m_ListItems.equal_range(toupdate); + for(ListItems::const_iterator it = rangeIt.first; it != rangeIt.second; it++){ + CtrlItem_Struct* updateItem = it->second; + + // Find entry in CListCtrl and update object + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)updateItem; + int result = FindItem(&find); + if (result != -1){ + updateItem->dwUpdated = 0; + Update(result); + } + } + m_availableCommandsDirty = true; +} + +void CDownloadListCtrl::GetFileItemDisplayText(CPartFile *lpPartFile, int iSubItem, LPTSTR pszText, int cchTextMax) +{ + if (pszText == NULL || cchTextMax <= 0) { + ASSERT(0); + return; + } + pszText[0] = _T('\0'); + switch (iSubItem) + { + case 0: // file name + _tcsncpy(pszText, lpPartFile->GetFileName(), cchTextMax); + break; + + case 1: // size + _tcsncpy(pszText, CastItoXBytes(lpPartFile->GetFileSize(), false, false), cchTextMax); + break; + + case 2: // transferred + _tcsncpy(pszText, CastItoXBytes(lpPartFile->GetTransferred(), false, false), cchTextMax); + break; + + case 3: // transferred complete + _tcsncpy(pszText, CastItoXBytes(lpPartFile->GetCompletedSize(), false, false), cchTextMax); + break; + + case 4: // speed + if (lpPartFile->GetTransferringSrcCount()) + _tcsncpy(pszText, CastItoXBytes(lpPartFile->GetDatarate(), false, true), cchTextMax); + break; + + case 5: // progress + _sntprintf(pszText, cchTextMax, _T("%s: %.1f%%"), GetResString(IDS_DL_PROGRESS), lpPartFile->GetPercentCompleted()); + break; + + case 6: { // sources + CString strBuffer; + UINT sc = lpPartFile->GetSourceCount(); +// ZZ:DownloadManager --> + if (!(lpPartFile->GetStatus() == PS_PAUSED && sc == 0) && lpPartFile->GetStatus() != PS_COMPLETE) + { + UINT ncsc = lpPartFile->GetNotCurrentSourcesCount(); + strBuffer.Format(_T("%i"), sc - ncsc); + if (ncsc > 0) + strBuffer.AppendFormat(_T("/%i"), sc); + if (thePrefs.IsExtControlsEnabled() && lpPartFile->GetSrcA4AFCount() > 0) + strBuffer.AppendFormat(_T("+%i"), lpPartFile->GetSrcA4AFCount()); + if (lpPartFile->GetTransferringSrcCount() > 0) + strBuffer.AppendFormat(_T(" (%i)"), lpPartFile->GetTransferringSrcCount()); + } +// <-- ZZ:DownloadManager + if (thePrefs.IsExtControlsEnabled() && lpPartFile->GetPrivateMaxSources() != 0) + strBuffer.AppendFormat(_T(" [%i]"), lpPartFile->GetPrivateMaxSources()); + _tcsncpy(pszText, strBuffer, cchTextMax); + break; + } + + case 7: // prio + switch (lpPartFile->GetDownPriority()) + { + case PR_LOW: + if (lpPartFile->IsAutoDownPriority()) + _tcsncpy(pszText, GetResString(IDS_PRIOAUTOLOW), cchTextMax); + else + _tcsncpy(pszText, GetResString(IDS_PRIOLOW), cchTextMax); + break; + + case PR_NORMAL: + if (lpPartFile->IsAutoDownPriority()) + _tcsncpy(pszText, GetResString(IDS_PRIOAUTONORMAL), cchTextMax); + else + _tcsncpy(pszText, GetResString(IDS_PRIONORMAL), cchTextMax); + break; + + case PR_HIGH: + if (lpPartFile->IsAutoDownPriority()) + _tcsncpy(pszText, GetResString(IDS_PRIOAUTOHIGH), cchTextMax); + else + _tcsncpy(pszText, GetResString(IDS_PRIOHIGH), cchTextMax); + break; + } + break; + + case 8: + _tcsncpy(pszText, lpPartFile->getPartfileStatus(), cchTextMax); + break; + + case 9: // remaining time & size + if (lpPartFile->GetStatus() != PS_COMPLETING && lpPartFile->GetStatus() != PS_COMPLETE) + { + time_t restTime; + if (!thePrefs.UseSimpleTimeRemainingComputation()) + restTime = lpPartFile->getTimeRemaining(); + else + restTime = lpPartFile->getTimeRemainingSimple(); + _sntprintf(pszText, cchTextMax, _T("%s (%s)"), CastSecondsToHM(restTime), CastItoXBytes((lpPartFile->GetFileSize() - lpPartFile->GetCompletedSize()), false, false)); + } + break; + + case 10: { // last seen complete + CString strBuffer; + if (lpPartFile->m_nCompleteSourcesCountLo == 0) + strBuffer.Format(_T("< %u"), lpPartFile->m_nCompleteSourcesCountHi); + else if (lpPartFile->m_nCompleteSourcesCountLo == lpPartFile->m_nCompleteSourcesCountHi) + strBuffer.Format(_T("%u"), lpPartFile->m_nCompleteSourcesCountLo); + else + strBuffer.Format(_T("%u - %u"), lpPartFile->m_nCompleteSourcesCountLo, lpPartFile->m_nCompleteSourcesCountHi); + + if (lpPartFile->lastseencomplete == NULL) + _sntprintf(pszText, cchTextMax, _T("%s (%s)"), GetResString(IDS_NEVER), strBuffer); + else + _sntprintf(pszText, cchTextMax, _T("%s (%s)"), lpPartFile->lastseencomplete.Format(thePrefs.GetDateTimeFormat4Lists()), strBuffer); + break; + } + + case 11: // last receive + if (lpPartFile->GetFileDate() != NULL && lpPartFile->GetCompletedSize() > (uint64)0) + _tcsncpy(pszText, lpPartFile->GetCFileDate().Format(thePrefs.GetDateTimeFormat4Lists()), cchTextMax); + else + _tcsncpy(pszText, GetResString(IDS_NEVER), cchTextMax); + break; + + case 12: // cat + _tcsncpy(pszText, (lpPartFile->GetCategory() != 0) ? thePrefs.GetCategory(lpPartFile->GetCategory())->strTitle : _T(""), cchTextMax); + break; + case 13: // added on + if (lpPartFile->GetCrCFileDate() != NULL) + _tcsncpy(pszText, lpPartFile->GetCrCFileDate().Format(thePrefs.GetDateTimeFormat4Lists()), cchTextMax); + else + _tcsncpy(pszText, _T("?"), cchTextMax); + break; + } + pszText[cchTextMax - 1] = _T('\0'); +} + +void CDownloadListCtrl::DrawFileItem(CDC *dc, int nColumn, LPCRECT lpRect, UINT uDrawTextAlignment, CtrlItem_Struct *pCtrlItem) +{ + /*const*/ CPartFile *pPartFile = (CPartFile *)pCtrlItem->value; + TCHAR szItem[1024]; + GetFileItemDisplayText(pPartFile, nColumn, szItem, _countof(szItem)); + switch (nColumn) + { + case 0: { // file name + CRect rcDraw(lpRect); + int iIconPosY = (rcDraw.Height() > theApp.GetSmallSytemIconSize().cy) ? ((rcDraw.Height() - theApp.GetSmallSytemIconSize().cy) / 2) : 0; + int iImage = theApp.GetFileTypeSystemImageIdx(pPartFile->GetFileName()); + if (theApp.GetSystemImageList() != NULL) + ::ImageList_Draw(theApp.GetSystemImageList(), iImage, dc->GetSafeHdc(), rcDraw.left, rcDraw.top + iIconPosY, ILD_TRANSPARENT); + rcDraw.left += theApp.GetSmallSytemIconSize().cx; + + if (thePrefs.ShowRatingIndicator() && (pPartFile->HasComment() || pPartFile->HasRating() || pPartFile->IsKadCommentSearchRunning())){ + m_ImageList.Draw(dc, pPartFile->UserRating(true) + 14, CPoint(rcDraw.left + 2, rcDraw.top + iIconPosY), ILD_NORMAL); + rcDraw.left += 2 + RATING_ICON_WIDTH; + } + + rcDraw.left += sm_iLabelOffset; + dc->DrawText(szItem, -1, &rcDraw, MLC_DT_TEXT | uDrawTextAlignment); + break; + } + + case 5: { // progress + CRect rcDraw(*lpRect); + rcDraw.bottom--; + rcDraw.top++; + + int iWidth = rcDraw.Width(); + int iHeight = rcDraw.Height(); + if (pCtrlItem->status == (HBITMAP)NULL) + VERIFY(pCtrlItem->status.CreateBitmap(1, 1, 1, 8, NULL)); + CDC cdcStatus; + HGDIOBJ hOldBitmap; + cdcStatus.CreateCompatibleDC(dc); + int cx = pCtrlItem->status.GetBitmapDimension().cx; + DWORD dwTicks = GetTickCount(); + if (pCtrlItem->dwUpdated + DLC_BARUPDATE < dwTicks || cx != iWidth || !pCtrlItem->dwUpdated) + { + pCtrlItem->status.DeleteObject(); + pCtrlItem->status.CreateCompatibleBitmap(dc, iWidth, iHeight); + pCtrlItem->status.SetBitmapDimension(iWidth, iHeight); + hOldBitmap = cdcStatus.SelectObject(pCtrlItem->status); + + RECT rec_status; + rec_status.left = 0; + rec_status.top = 0; + rec_status.bottom = iHeight; + rec_status.right = iWidth; + pPartFile->DrawStatusBar(&cdcStatus, &rec_status, thePrefs.UseFlatBar()); + pCtrlItem->dwUpdated = dwTicks + (rand() % 128); + } + else + hOldBitmap = cdcStatus.SelectObject(pCtrlItem->status); + dc->BitBlt(rcDraw.left, rcDraw.top, iWidth, iHeight, &cdcStatus, 0, 0, SRCCOPY); + cdcStatus.SelectObject(hOldBitmap); + + if (thePrefs.GetUseDwlPercentage()) + { + COLORREF oldclr = dc->SetTextColor(RGB(255, 255, 255)); + int iOMode = dc->SetBkMode(TRANSPARENT); + _sntprintf(szItem, _countof(szItem), _T("%.1f%%"), pPartFile->GetPercentCompleted()); + szItem[_countof(szItem) - 1] = _T('\0'); + dc->DrawText(szItem, -1, &rcDraw, (MLC_DT_TEXT & ~DT_LEFT) | DT_CENTER); + dc->SetBkMode(iOMode); + dc->SetTextColor(oldclr); + } + break; + } + + default: + dc->DrawText(szItem, -1, const_cast(lpRect), MLC_DT_TEXT | uDrawTextAlignment); + break; + } +} + +void CDownloadListCtrl::GetSourceItemDisplayText(const CtrlItem_Struct *pCtrlItem, int iSubItem, LPTSTR pszText, int cchTextMax) +{ + if (pszText == NULL || cchTextMax <= 0) { + ASSERT(0); + return; + } + const CUpDownClient *pClient = (CUpDownClient *)pCtrlItem->value; + pszText[0] = _T('\0'); + switch (iSubItem) + { + case 0: // icon, name, status + if (pClient->GetUserName() == NULL) + _sntprintf(pszText, cchTextMax, _T("(%s)"), GetResString(IDS_UNKNOWN)); + else + _tcsncpy(pszText, pClient->GetUserName(), cchTextMax); + break; + + case 1: // size + switch (pClient->GetSourceFrom()) + { + case SF_SERVER: + _tcsncpy(pszText, _T("eD2K Server"), cchTextMax); + break; + case SF_KADEMLIA: + _tcsncpy(pszText, GetResString(IDS_KADEMLIA), cchTextMax); + break; + case SF_SOURCE_EXCHANGE: + _tcsncpy(pszText, GetResString(IDS_SE), cchTextMax); + break; + case SF_PASSIVE: + _tcsncpy(pszText, GetResString(IDS_PASSIVE), cchTextMax); + break; + case SF_LINK: + _tcsncpy(pszText, GetResString(IDS_SW_LINK), cchTextMax); + break; + } + break; + + case 2: // transferred + case 3: // completed + // - 'Transferred' column: Show transferred data + // - 'Completed' column: If 'Transferred' column is hidden, show the amount of transferred data + // in 'Completed' column. This is plain wrong (at least when receiving compressed data), but + // users seem to got used to it. + if (iSubItem == 2 || IsColumnHidden(2)) { + if (pCtrlItem->type == AVAILABLE_SOURCE && pClient->GetTransferredDown()) + _tcsncpy(pszText, CastItoXBytes(pClient->GetTransferredDown(), false, false), cchTextMax); + } + break; + + case 4: // speed + if (pCtrlItem->type == AVAILABLE_SOURCE && pClient->GetDownloadDatarate()) { + if (pClient->GetDownloadDatarate()) + _tcsncpy(pszText, CastItoXBytes(pClient->GetDownloadDatarate(), false, true), cchTextMax); + } + break; + + case 5: // file info + _tcsncpy(pszText, GetResString(IDS_DL_PROGRESS), cchTextMax); + break; + + case 6: // sources + _tcsncpy(pszText, pClient->GetClientSoftVer(), cchTextMax); + break; + + case 7: // prio + if (pClient->GetDownloadState() == DS_ONQUEUE) + { + if (pClient->IsRemoteQueueFull()) + _tcsncpy(pszText, GetResString(IDS_QUEUEFULL), cchTextMax); + else if (pClient->GetRemoteQueueRank()) + _sntprintf(pszText, cchTextMax, _T("QR: %u"), pClient->GetRemoteQueueRank()); + } + break; + + case 8: { // status + CString strBuffer; + if (pCtrlItem->type == AVAILABLE_SOURCE) { + strBuffer = pClient->GetDownloadStateDisplayString(); + } + else { + strBuffer = GetResString(IDS_ASKED4ANOTHERFILE); +// ZZ:DownloadManager --> + if (thePrefs.IsExtControlsEnabled()) { + if (pClient->IsInNoNeededList(pCtrlItem->owner)) + strBuffer += _T(" (") + GetResString(IDS_NONEEDEDPARTS) + _T(')'); + else if (pClient->GetDownloadState() == DS_DOWNLOADING) + strBuffer += _T(" (") + GetResString(IDS_TRANSFERRING) + _T(')'); + else if (const_cast(pClient)->IsSwapSuspended(pClient->GetRequestFile())) + strBuffer += _T(" (") + GetResString(IDS_SOURCESWAPBLOCKED) + _T(')'); + + if (pClient->GetRequestFile() && pClient->GetRequestFile()->GetFileName()) + strBuffer.AppendFormat(_T(": \"%s\""), pClient->GetRequestFile()->GetFileName()); + } + } + + if (thePrefs.IsExtControlsEnabled() && !pClient->m_OtherRequests_list.IsEmpty()) + strBuffer.Append(_T("*")); +// ZZ:DownloadManager <-- + _tcsncpy(pszText, strBuffer, cchTextMax); + break; + } + + case 9: // remaining time & size + break; + + case 10: // last seen complete + break; + + case 11: // last received + break; + + case 12: // category + break; + + case 13: // added on + break; + } + pszText[cchTextMax - 1] = _T('\0'); +} + +void CDownloadListCtrl::DrawSourceItem(CDC *dc, int nColumn, LPCRECT lpRect, UINT uDrawTextAlignment, CtrlItem_Struct *pCtrlItem) +{ + const CUpDownClient *pClient = (CUpDownClient *)pCtrlItem->value; + TCHAR szItem[1024]; + GetSourceItemDisplayText(pCtrlItem, nColumn, szItem, _countof(szItem)); + switch (nColumn) + { + case 0: { // icon, name, status + CRect cur_rec(*lpRect); + int iIconPosY = (cur_rec.Height() > 16) ? ((cur_rec.Height() - 16) / 2) : 1; + POINT point = {cur_rec.left, cur_rec.top + iIconPosY}; + if (pCtrlItem->type == AVAILABLE_SOURCE) + { + switch (pClient->GetDownloadState()) + { + case DS_CONNECTING: + m_ImageList.Draw(dc, 2, point, ILD_NORMAL); + break; + case DS_CONNECTED: + m_ImageList.Draw(dc, 2, point, ILD_NORMAL); + break; + case DS_WAITCALLBACKKAD: + case DS_WAITCALLBACK: + m_ImageList.Draw(dc, 2, point, ILD_NORMAL); + break; + case DS_ONQUEUE: + if (pClient->IsRemoteQueueFull()) + m_ImageList.Draw(dc, 3, point, ILD_NORMAL); + else + m_ImageList.Draw(dc, 1, point, ILD_NORMAL); + break; + case DS_DOWNLOADING: + m_ImageList.Draw(dc, 0, point, ILD_NORMAL); + break; + case DS_REQHASHSET: + m_ImageList.Draw(dc, 0, point, ILD_NORMAL); + break; + case DS_NONEEDEDPARTS: + m_ImageList.Draw(dc, 3, point, ILD_NORMAL); + break; + case DS_ERROR: + m_ImageList.Draw(dc, 3, point, ILD_NORMAL); + break; + case DS_TOOMANYCONNS: + case DS_TOOMANYCONNSKAD: + m_ImageList.Draw(dc, 2, point, ILD_NORMAL); + break; + default: + m_ImageList.Draw(dc, 4, point, ILD_NORMAL); + break; + } + } + else { + m_ImageList.Draw(dc, 3, point, ILD_NORMAL); + } + cur_rec.left += 20; + + UINT uOvlImg = 0; + if ((pClient->Credits() && pClient->Credits()->GetCurrentIdentState(pClient->GetIP()) == IS_IDENTIFIED)) + uOvlImg |= 1; + if (pClient->IsObfuscatedConnectionEstablished()) + uOvlImg |= 2; + + POINT point2 = {cur_rec.left, cur_rec.top + iIconPosY}; + if (pClient->IsFriend()) + m_ImageList.Draw(dc, 6, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + else if (pClient->GetClientSoft() == SO_EDONKEYHYBRID) + m_ImageList.Draw(dc, 9, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + else if (pClient->GetClientSoft() == SO_MLDONKEY) + m_ImageList.Draw(dc, 8, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + else if (pClient->GetClientSoft() == SO_SHAREAZA) + m_ImageList.Draw(dc, 10, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + else if (pClient->GetClientSoft() == SO_URL) + m_ImageList.Draw(dc, 11, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + else if (pClient->GetClientSoft() == SO_AMULE) + m_ImageList.Draw(dc, 12, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + else if (pClient->GetClientSoft() == SO_LPHANT) + m_ImageList.Draw(dc, 13, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + else if (pClient->ExtProtocolAvailable()) + m_ImageList.Draw(dc, 5, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + else + m_ImageList.Draw(dc, 7, point2, ILD_NORMAL | INDEXTOOVERLAYMASK(uOvlImg)); + cur_rec.left += 20; + + dc->DrawText(szItem, -1, &cur_rec, MLC_DT_TEXT | uDrawTextAlignment); + break; + } + + case 5: { // file info + CRect rcDraw(*lpRect); + rcDraw.bottom--; + rcDraw.top++; + + int iWidth = rcDraw.Width(); + int iHeight = rcDraw.Height(); + if (pCtrlItem->status == (HBITMAP)NULL) + VERIFY(pCtrlItem->status.CreateBitmap(1, 1, 1, 8, NULL)); + CDC cdcStatus; + HGDIOBJ hOldBitmap; + cdcStatus.CreateCompatibleDC(dc); + int cx = pCtrlItem->status.GetBitmapDimension().cx; + DWORD dwTicks = GetTickCount(); + if (pCtrlItem->dwUpdated + DLC_BARUPDATE < dwTicks || cx != iWidth || !pCtrlItem->dwUpdated) + { + pCtrlItem->status.DeleteObject(); + pCtrlItem->status.CreateCompatibleBitmap(dc, iWidth, iHeight); + pCtrlItem->status.SetBitmapDimension(iWidth, iHeight); + hOldBitmap = cdcStatus.SelectObject(pCtrlItem->status); + + RECT rec_status; + rec_status.left = 0; + rec_status.top = 0; + rec_status.bottom = iHeight; + rec_status.right = iWidth; + pClient->DrawStatusBar(&cdcStatus, &rec_status,(pCtrlItem->type == UNAVAILABLE_SOURCE), thePrefs.UseFlatBar()); + pCtrlItem->dwUpdated = dwTicks + (rand() % 128); + } + else + hOldBitmap = cdcStatus.SelectObject(pCtrlItem->status); + dc->BitBlt(rcDraw.left, rcDraw.top, iWidth, iHeight, &cdcStatus, 0, 0, SRCCOPY); + cdcStatus.SelectObject(hOldBitmap); + break; + } + + case 9: // remaining time & size + case 10: // last seen complete + case 11: // last received + case 12: // category + case 13: // added on + break; + + default: + dc->DrawText(szItem, -1, const_cast(lpRect), MLC_DT_TEXT | uDrawTextAlignment); + break; + } +} + +void CDownloadListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (!theApp.emuledlg->IsRunning()) + return; + if (!lpDrawItemStruct->itemData) + return; + + CMemDC dc(CDC::FromHandle(lpDrawItemStruct->hDC), &lpDrawItemStruct->rcItem); + BOOL bCtrlFocused; + InitItemMemDC(dc, lpDrawItemStruct, bCtrlFocused); + CRect cur_rec(lpDrawItemStruct->rcItem); + CRect rcClient; + GetClientRect(&rcClient); + CtrlItem_Struct *content = (CtrlItem_Struct *)lpDrawItemStruct->itemData; + if (m_pFontBold) + { + if (content->type == FILE_TYPE && ((const CPartFile *)content->value)->GetTransferringSrcCount()) + dc.SelectObject(m_pFontBold); + else if ((content->type == UNAVAILABLE_SOURCE || content->type == AVAILABLE_SOURCE) && (((const CUpDownClient *)content->value)->GetDownloadState() == DS_DOWNLOADING)) + dc.SelectObject(m_pFontBold); + } + + BOOL notLast = lpDrawItemStruct->itemID + 1 != (UINT)GetItemCount(); + BOOL notFirst = lpDrawItemStruct->itemID != 0; + int tree_start=0; + int tree_end=0; + + int iTreeOffset = 6; + CHeaderCtrl *pHeaderCtrl = GetHeaderCtrl(); + int iCount = pHeaderCtrl->GetItemCount(); + cur_rec.right = cur_rec.left - sm_iLabelOffset; + cur_rec.left += sm_iIconOffset; + + if (content->type == FILE_TYPE) + { + if (!g_bLowColorDesktop && (lpDrawItemStruct->itemState & ODS_SELECTED) == 0) { + DWORD dwCatColor = thePrefs.GetCatColor(((/*const*/ CPartFile*)content->value)->GetCategory(), COLOR_WINDOWTEXT); + if (dwCatColor > 0) + dc.SetTextColor(dwCatColor); + } + + for (int iCurrent = 0; iCurrent < iCount; iCurrent++) + { + int iColumn = pHeaderCtrl->OrderToIndex(iCurrent); + if (!IsColumnHidden(iColumn)) + { + UINT uDrawTextAlignment; + int iColumnWidth = GetColumnWidth(iColumn, uDrawTextAlignment); + if (iColumn == 5) + { + int iNextLeft = cur_rec.left + iColumnWidth; + int iNextRight = cur_rec.right + iColumnWidth; + //set up tree vars + cur_rec.left = cur_rec.right + iTreeOffset; + cur_rec.right = cur_rec.left + min(8, iColumnWidth); + tree_start = cur_rec.left + 1; + tree_end = cur_rec.right; + //normal column stuff + cur_rec.left = cur_rec.right + 1; + cur_rec.right = tree_start + iColumnWidth - iTreeOffset; + if (cur_rec.left < cur_rec.right && HaveIntersection(rcClient, cur_rec)) + DrawFileItem(dc, 5, &cur_rec, uDrawTextAlignment, content); + cur_rec.left = iNextLeft; + cur_rec.right = iNextRight; + } + else + { + cur_rec.right += iColumnWidth; + if (cur_rec.left < cur_rec.right && HaveIntersection(rcClient, cur_rec)) + DrawFileItem(dc, iColumn, &cur_rec, uDrawTextAlignment, content); + if (iColumn == 0) { + cur_rec.left += sm_iLabelOffset; + cur_rec.right -= sm_iSubItemInset; + } + cur_rec.left += iColumnWidth; + } + } + } + } + else if (content->type == UNAVAILABLE_SOURCE || content->type == AVAILABLE_SOURCE) + { + for (int iCurrent = 0; iCurrent < iCount; iCurrent++) + { + int iColumn = pHeaderCtrl->OrderToIndex(iCurrent); + if (!IsColumnHidden(iColumn)) + { + UINT uDrawTextAlignment; + int iColumnWidth = GetColumnWidth(iColumn, uDrawTextAlignment); + if (iColumn == 5) + { + int iNextLeft = cur_rec.left + iColumnWidth; + int iNextRight = cur_rec.right + iColumnWidth; + //set up tree vars + cur_rec.left = cur_rec.right + iTreeOffset; + cur_rec.right = cur_rec.left + min(8, iColumnWidth); + tree_start = cur_rec.left + 1; + tree_end = cur_rec.right; + //normal column stuff + cur_rec.left = cur_rec.right + 1; + cur_rec.right = tree_start + iColumnWidth - iTreeOffset; + if (cur_rec.left < cur_rec.right && HaveIntersection(rcClient, cur_rec)) + DrawSourceItem(dc, 5, &cur_rec, uDrawTextAlignment, content); + cur_rec.left = iNextLeft; + cur_rec.right = iNextRight; + } + else + { + cur_rec.right += iColumnWidth; + if (cur_rec.left < cur_rec.right && HaveIntersection(rcClient, cur_rec)) + DrawSourceItem(dc, iColumn, &cur_rec, uDrawTextAlignment, content); + if (iColumn == 0) { + cur_rec.left += sm_iLabelOffset; + cur_rec.right -= sm_iSubItemInset; + } + cur_rec.left += iColumnWidth; + } + } + } + } + + DrawFocusRect(dc, lpDrawItemStruct->rcItem, lpDrawItemStruct->itemState & ODS_FOCUS, bCtrlFocused, lpDrawItemStruct->itemState & ODS_SELECTED); + + //draw tree last so it draws over selected and focus (looks better) + if(tree_start < tree_end) { + //set new bounds + RECT tree_rect; + tree_rect.top = lpDrawItemStruct->rcItem.top; + tree_rect.bottom = lpDrawItemStruct->rcItem.bottom; + tree_rect.left = tree_start; + tree_rect.right = tree_end; + dc.SetBoundsRect(&tree_rect, DCB_DISABLE); + + //gather some information + BOOL hasNext = notLast && + ((CtrlItem_Struct*)this->GetItemData(lpDrawItemStruct->itemID + 1))->type != FILE_TYPE; + BOOL isOpenRoot = hasNext && content->type == FILE_TYPE; + BOOL isChild = content->type != FILE_TYPE; + //BOOL isExpandable = !isChild && ((CPartFile*)content->value)->GetSourceCount() > 0; + //might as well calculate these now + int treeCenter = tree_start + 3; + int middle = (cur_rec.top + cur_rec.bottom + 1) / 2; + + //set up a new pen for drawing the tree + CPen pn, *oldpn; + pn.CreatePen(PS_SOLID, 1, m_crWindowText); + oldpn = dc.SelectObject(&pn); + + if(isChild) { + //draw the line to the status bar + dc.MoveTo(tree_end, middle); + dc.LineTo(tree_start + 3, middle); + + //draw the line to the child node + if(hasNext) { + dc.MoveTo(treeCenter, middle); + dc.LineTo(treeCenter, cur_rec.bottom + 1); + } + } else if(isOpenRoot) { + //draw circle + RECT circle_rec; + COLORREF crBk = dc.GetBkColor(); + circle_rec.top = middle - 2; + circle_rec.bottom = middle + 3; + circle_rec.left = treeCenter - 2; + circle_rec.right = treeCenter + 3; + dc.FrameRect(&circle_rec, &CBrush(m_crWindowText)); + dc.SetPixelV(circle_rec.left, circle_rec.top, crBk); + dc.SetPixelV(circle_rec.right - 1, circle_rec.top, crBk); + dc.SetPixelV(circle_rec.left, circle_rec.bottom - 1, crBk); + dc.SetPixelV(circle_rec.right - 1, circle_rec.bottom - 1, crBk); + //draw the line to the child node + if(hasNext) { + dc.MoveTo(treeCenter, middle + 3); + dc.LineTo(treeCenter, cur_rec.bottom + 1); + } + } /*else if(isExpandable) { + //draw a + sign + dc.MoveTo(treeCenter, middle - 2); + dc.LineTo(treeCenter, middle + 3); + dc.MoveTo(treeCenter - 2, middle); + dc.LineTo(treeCenter + 3, middle); + }*/ + + //draw the line back up to parent node + if(notFirst && isChild) { + dc.MoveTo(treeCenter, middle); + dc.LineTo(treeCenter, cur_rec.top - 1); + } + + //put the old pen back + dc.SelectObject(oldpn); + pn.DeleteObject(); + } +} + +void CDownloadListCtrl::HideSources(CPartFile* toCollapse) +{ + SetRedraw(false); + int pre = 0; + int post = 0; + for (int i = 0; i < GetItemCount(); i++) + { + CtrlItem_Struct* item = (CtrlItem_Struct*)GetItemData(i); + if (item != NULL && item->owner == toCollapse) + { + pre++; + item->dwUpdated = 0; + item->status.DeleteObject(); + DeleteItem(i--); + post++; + } + } + if (pre - post == 0) + toCollapse->srcarevisible = false; + SetRedraw(true); +} + +void CDownloadListCtrl::ExpandCollapseItem(int iItem, int iAction, bool bCollapseSource) +{ + if (iItem == -1) + return; + CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(iItem); + + // to collapse/expand files when one of its source is selected + if (content != NULL && bCollapseSource && content->parent != NULL) + { + content=content->parent; + + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)content; + iItem = FindItem(&find); + if (iItem == -1) + return; + } + + if (!content || content->type != FILE_TYPE) + return; + + CPartFile* partfile = reinterpret_cast(content->value); + if (!partfile) + return; + + if (partfile->CanOpenFile()) { + partfile->OpenFile(); + return; + } + + // Check if the source branch is disable + if (!partfile->srcarevisible) + { + if (iAction > COLLAPSE_ONLY) + { + SetRedraw(false); + + // Go throught the whole list to find out the sources for this file + // Remark: don't use GetSourceCount() => UNAVAILABLE_SOURCE + for (ListItems::const_iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++) + { + const CtrlItem_Struct* cur_item = it->second; + if (cur_item->owner == partfile) + { + partfile->srcarevisible = true; + InsertItem(LVIF_PARAM | LVIF_TEXT, iItem + 1, LPSTR_TEXTCALLBACK, 0, 0, 0, (LPARAM)cur_item); + } + } + + SetRedraw(true); + } + } + else { + if (iAction == EXPAND_COLLAPSE || iAction == COLLAPSE_ONLY) + { + if (GetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED) != (LVIS_SELECTED | LVIS_FOCUSED)) + { + SetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + SetSelectionMark(iItem); + } + HideSources(partfile); + } + } +} + +void CDownloadListCtrl::OnLvnItemActivate(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMITEMACTIVATE pNMIA = reinterpret_cast(pNMHDR); + + if (thePrefs.IsDoubleClickEnabled() || pNMIA->iSubItem > 0) + ExpandCollapseItem(pNMIA->iItem, EXPAND_COLLAPSE); + *pResult = 0; +} + +void CDownloadListCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + int iSel = GetNextItem(-1, LVIS_SELECTED); + if (iSel != -1) + { + const CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(iSel); + if (content != NULL && content->type == FILE_TYPE) + { + // get merged settings + bool bFirstItem = true; + int iSelectedItems = 0; + int iFilesNotDone = 0; + int iFilesToPause = 0; + int iFilesToStop = 0; + int iFilesToResume = 0; + int iFilesToOpen = 0; + int iFilesGetPreviewParts = 0; + int iFilesPreviewType = 0; + int iFilesToPreview = 0; + int iFilesToCancel = 0; + int iFilesCanPauseOnPreview = 0; + int iFilesDoPauseOnPreview = 0; + int iFilesInCats = 0; + UINT uPrioMenuItem = 0; + const CPartFile* file1 = NULL; + POSITION pos = GetFirstSelectedItemPosition(); + while (pos) + { + const CtrlItem_Struct* pItemData = (CtrlItem_Struct*)GetItemData(GetNextSelectedItem(pos)); + if (pItemData == NULL || pItemData->type != FILE_TYPE) + continue; + const CPartFile* pFile = (CPartFile*)pItemData->value; + if (bFirstItem) + file1 = pFile; + iSelectedItems++; + + bool bFileDone = (pFile->GetStatus()==PS_COMPLETE || pFile->GetStatus()==PS_COMPLETING); + iFilesToCancel += pFile->GetStatus() != PS_COMPLETING ? 1 : 0; + iFilesNotDone += !bFileDone ? 1 : 0; + iFilesToStop += pFile->CanStopFile() ? 1 : 0; + iFilesToPause += pFile->CanPauseFile() ? 1 : 0; + iFilesToResume += pFile->CanResumeFile() ? 1 : 0; + iFilesToOpen += pFile->CanOpenFile() ? 1 : 0; + iFilesGetPreviewParts += pFile->GetPreviewPrio() ? 1 : 0; + iFilesPreviewType += pFile->IsPreviewableFileType() ? 1 : 0; + iFilesToPreview += pFile->IsReadyForPreview() ? 1 : 0; + iFilesCanPauseOnPreview += (pFile->IsPreviewableFileType() && !pFile->IsReadyForPreview() && pFile->CanPauseFile()) ? 1 : 0; + iFilesDoPauseOnPreview += (pFile->IsPausingOnPreview()) ? 1 : 0; + iFilesInCats += (!pFile->HasDefaultCategory()) ? 1 : 0; + + UINT uCurPrioMenuItem = 0; + if (pFile->IsAutoDownPriority()) + uCurPrioMenuItem = MP_PRIOAUTO; + else if (pFile->GetDownPriority() == PR_HIGH) + uCurPrioMenuItem = MP_PRIOHIGH; + else if (pFile->GetDownPriority() == PR_NORMAL) + uCurPrioMenuItem = MP_PRIONORMAL; + else if (pFile->GetDownPriority() == PR_LOW) + uCurPrioMenuItem = MP_PRIOLOW; + else + ASSERT(0); + + if (bFirstItem) + uPrioMenuItem = uCurPrioMenuItem; + else if (uPrioMenuItem != uCurPrioMenuItem) + uPrioMenuItem = 0; + + bFirstItem = false; + } + + m_FileMenu.EnableMenuItem((UINT_PTR)m_PrioMenu.m_hMenu, iFilesNotDone > 0 ? MF_ENABLED : MF_GRAYED); + m_PrioMenu.CheckMenuRadioItem(MP_PRIOLOW, MP_PRIOAUTO, uPrioMenuItem, 0); + + // enable commands if there is at least one item which can be used for the action + m_FileMenu.EnableMenuItem(MP_CANCEL, iFilesToCancel > 0 ? MF_ENABLED : MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_STOP, iFilesToStop > 0 ? MF_ENABLED : MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_PAUSE, iFilesToPause > 0 ? MF_ENABLED : MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_RESUME, iFilesToResume > 0 ? MF_ENABLED : MF_GRAYED); + + bool bOpenEnabled = (iSelectedItems == 1 && iFilesToOpen == 1); + m_FileMenu.EnableMenuItem(MP_OPEN, bOpenEnabled ? MF_ENABLED : MF_GRAYED); + + CMenu PreviewWithMenu; + PreviewWithMenu.CreateMenu(); + int iPreviewMenuEntries = thePreviewApps.GetAllMenuEntries(PreviewWithMenu, (iSelectedItems == 1) ? file1 : NULL); + if(thePrefs.IsExtControlsEnabled()) + { + if (!thePrefs.GetPreviewPrio()) + { + m_PreviewMenu.EnableMenuItem(MP_TRY_TO_GET_PREVIEW_PARTS, (iSelectedItems == 1 && iFilesPreviewType == 1 && iFilesToPreview == 0 && iFilesNotDone == 1) ? MF_ENABLED : MF_GRAYED); + m_PreviewMenu.CheckMenuItem(MP_TRY_TO_GET_PREVIEW_PARTS, (iSelectedItems == 1 && iFilesGetPreviewParts == 1) ? MF_CHECKED : MF_UNCHECKED); + } + m_PreviewMenu.EnableMenuItem(MP_PREVIEW, (iSelectedItems == 1 && iFilesToPreview == 1) ? MF_ENABLED : MF_GRAYED); + m_PreviewMenu.EnableMenuItem(MP_PAUSEONPREVIEW, iFilesCanPauseOnPreview > 0 ? MF_ENABLED : MF_GRAYED); + m_PreviewMenu.CheckMenuItem(MP_PAUSEONPREVIEW, (iSelectedItems > 0 && iFilesDoPauseOnPreview == iSelectedItems) ? MF_CHECKED : MF_UNCHECKED); + m_FileMenu.EnableMenuItem((UINT_PTR)m_PreviewMenu.m_hMenu, m_PreviewMenu.HasEnabledItems() ? MF_ENABLED : MF_GRAYED); + + if (iPreviewMenuEntries > 0 && !thePrefs.GetExtraPreviewWithMenu()) + m_PreviewMenu.InsertMenu(1, MF_POPUP | MF_BYPOSITION | (iSelectedItems == 1 ? MF_ENABLED : MF_GRAYED), (UINT_PTR)PreviewWithMenu.m_hMenu, GetResString(IDS_PREVIEWWITH)); + else if (iPreviewMenuEntries > 0) + m_FileMenu.InsertMenu(MP_METINFO, MF_POPUP | MF_BYCOMMAND | (iSelectedItems == 1 ? MF_ENABLED : MF_GRAYED), (UINT_PTR)PreviewWithMenu.m_hMenu, GetResString(IDS_PREVIEWWITH)); + } + else { + m_FileMenu.EnableMenuItem(MP_PREVIEW, (iSelectedItems == 1 && iFilesToPreview == 1) ? MF_ENABLED : MF_GRAYED); + if (iPreviewMenuEntries) + m_FileMenu.InsertMenu(MP_METINFO, MF_POPUP | MF_BYCOMMAND | (iSelectedItems == 1 ? MF_ENABLED : MF_GRAYED), (UINT_PTR)PreviewWithMenu.m_hMenu, GetResString(IDS_PREVIEWWITH)); + } + + bool bDetailsEnabled = (iSelectedItems > 0); + m_FileMenu.EnableMenuItem(MP_METINFO, bDetailsEnabled ? MF_ENABLED : MF_GRAYED); + if (thePrefs.IsDoubleClickEnabled() && bOpenEnabled) + m_FileMenu.SetDefaultItem(MP_OPEN); + else if (!thePrefs.IsDoubleClickEnabled() && bDetailsEnabled) + m_FileMenu.SetDefaultItem(MP_METINFO); + else + m_FileMenu.SetDefaultItem((UINT)-1); + m_FileMenu.EnableMenuItem(MP_VIEWFILECOMMENTS, (iSelectedItems >= 1 /*&& iFilesNotDone == 1*/) ? MF_ENABLED : MF_GRAYED); + + int total; + m_FileMenu.EnableMenuItem(MP_CLEARCOMPLETED, GetCompleteDownloads(curTab, total) > 0 ? MF_ENABLED : MF_GRAYED); + + if (m_SourcesMenu && thePrefs.IsExtControlsEnabled()) { + m_FileMenu.EnableMenuItem((UINT_PTR)m_SourcesMenu.m_hMenu, MF_ENABLED); + m_SourcesMenu.EnableMenuItem(MP_ADDSOURCE, (iSelectedItems == 1 && iFilesToStop == 1) ? MF_ENABLED : MF_GRAYED); + m_SourcesMenu.EnableMenuItem(MP_SETSOURCELIMIT, (iFilesNotDone == iSelectedItems) ? MF_ENABLED : MF_GRAYED); + } + + m_FileMenu.EnableMenuItem(thePrefs.GetShowCopyEd2kLinkCmd() ? MP_GETED2KLINK : MP_SHOWED2KLINK, iSelectedItems > 0 ? MF_ENABLED : MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_PASTE, theApp.IsEd2kFileLinkInClipboard() ? MF_ENABLED : MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_FIND, GetItemCount() > 0 ? MF_ENABLED : MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_SEARCHRELATED, theApp.emuledlg->searchwnd->CanSearchRelatedFiles() ? MF_ENABLED : MF_GRAYED); + + CTitleMenu WebMenu; + WebMenu.CreateMenu(); + WebMenu.AddMenuTitle(NULL, true); + int iWebMenuEntries = theWebServices.GetFileMenuEntries(&WebMenu); + UINT flag = (iWebMenuEntries == 0 || iSelectedItems != 1) ? MF_GRAYED : MF_ENABLED; + m_FileMenu.AppendMenu(MF_POPUP | flag, (UINT_PTR)WebMenu.m_hMenu, GetResString(IDS_WEBSERVICES), _T("WEB")); + + // create cat-submenue + CMenu CatsMenu; + CatsMenu.CreateMenu(); + FillCatsMenu(CatsMenu, iFilesInCats); + m_FileMenu.AppendMenu(MF_POPUP, (UINT_PTR)CatsMenu.m_hMenu, GetResString(IDS_TOCAT), _T("CATEGORY")); + + bool bToolbarItem = !thePrefs.IsDownloadToolbarEnabled(); + if (bToolbarItem) + { + m_FileMenu.AppendMenu(MF_SEPARATOR); + m_FileMenu.AppendMenu(MF_STRING, MP_TOGGLEDTOOLBAR, GetResString(IDS_SHOWTOOLBAR)); + } + + GetPopupMenuPos(*this, point); + m_FileMenu.TrackPopupMenu(TPM_LEFTALIGN |TPM_RIGHTBUTTON, point.x, point.y, this); + if (bToolbarItem) + { + VERIFY( m_FileMenu.RemoveMenu(m_FileMenu.GetMenuItemCount() - 1, MF_BYPOSITION) ); + VERIFY( m_FileMenu.RemoveMenu(m_FileMenu.GetMenuItemCount() - 1, MF_BYPOSITION) ); + } + VERIFY( m_FileMenu.RemoveMenu(m_FileMenu.GetMenuItemCount() - 1, MF_BYPOSITION) ); + VERIFY( m_FileMenu.RemoveMenu(m_FileMenu.GetMenuItemCount() - 1, MF_BYPOSITION) ); + if (iPreviewMenuEntries && thePrefs.IsExtControlsEnabled() && !thePrefs.GetExtraPreviewWithMenu()) + VERIFY( m_PreviewMenu.RemoveMenu((UINT)PreviewWithMenu.m_hMenu, MF_BYCOMMAND) ); + else if (iPreviewMenuEntries) + VERIFY( m_FileMenu.RemoveMenu((UINT)PreviewWithMenu.m_hMenu, MF_BYCOMMAND) ); + VERIFY( WebMenu.DestroyMenu() ); + VERIFY( CatsMenu.DestroyMenu() ); + VERIFY( PreviewWithMenu.DestroyMenu() ); + } + else + { + const CUpDownClient* client = content != NULL ? (CUpDownClient*)content->value : NULL; + CTitleMenu ClientMenu; + ClientMenu.CreatePopupMenu(); + ClientMenu.AddMenuTitle(GetResString(IDS_CLIENTS), true); + ClientMenu.AppendMenu(MF_STRING, MP_DETAIL, GetResString(IDS_SHOWDETAILS), _T("CLIENTDETAILS")); + ClientMenu.SetDefaultItem(MP_DETAIL); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && !client->IsFriend()) ? MF_ENABLED : MF_GRAYED), MP_ADDFRIEND, GetResString(IDS_ADDFRIEND), _T("ADDFRIEND")); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient()) ? MF_ENABLED : MF_GRAYED), MP_MESSAGE, GetResString(IDS_SEND_MSG), _T("SENDMESSAGE")); + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && client->GetViewSharedFilesSupport()) ? MF_ENABLED : MF_GRAYED), MP_SHOWLIST, GetResString(IDS_VIEWFILES), _T("VIEWFILES")); + if (Kademlia::CKademlia::IsRunning() && !Kademlia::CKademlia::IsConnected()) + ClientMenu.AppendMenu(MF_STRING | ((client && client->IsEd2kClient() && client->GetKadPort()!=0 && client->GetKadVersion() > 1) ? MF_ENABLED : MF_GRAYED), MP_BOOT, GetResString(IDS_BOOTSTRAP)); + ClientMenu.AppendMenu(MF_STRING | (GetItemCount() > 0 ? MF_ENABLED : MF_GRAYED), MP_FIND, GetResString(IDS_FIND), _T("Search")); + + CMenu A4AFMenu; + A4AFMenu.CreateMenu(); + if (thePrefs.IsExtControlsEnabled()) { +// ZZ:DownloadManager --> +#ifdef _DEBUG + if (content->type == UNAVAILABLE_SOURCE) { + A4AFMenu.AppendMenu(MF_STRING,MP_A4AF_CHECK_THIS_NOW,GetResString(IDS_A4AF_CHECK_THIS_NOW)); + } +# endif +// <-- ZZ:DownloadManager + if (A4AFMenu.GetMenuItemCount()>0) + ClientMenu.AppendMenu(MF_STRING|MF_POPUP,(UINT_PTR)A4AFMenu.m_hMenu, GetResString(IDS_A4AF)); + } + + GetPopupMenuPos(*this, point); + ClientMenu.TrackPopupMenu(TPM_LEFTALIGN |TPM_RIGHTBUTTON, point.x, point.y, this); + + VERIFY( A4AFMenu.DestroyMenu() ); + VERIFY( ClientMenu.DestroyMenu() ); + } + } + else{ // nothing selected + int total; + m_FileMenu.EnableMenuItem((UINT_PTR)m_PrioMenu.m_hMenu, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_CANCEL, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_PAUSE, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_STOP, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_RESUME, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_OPEN, MF_GRAYED); + + if (thePrefs.IsExtControlsEnabled()) { + m_FileMenu.EnableMenuItem((UINT_PTR)m_PreviewMenu.m_hMenu, MF_GRAYED); + if (!thePrefs.GetPreviewPrio()) + { + m_PreviewMenu.EnableMenuItem(MP_TRY_TO_GET_PREVIEW_PARTS, MF_GRAYED); + m_PreviewMenu.CheckMenuItem(MP_TRY_TO_GET_PREVIEW_PARTS, MF_UNCHECKED); + } + m_PreviewMenu.EnableMenuItem(MP_PREVIEW, MF_GRAYED); + m_PreviewMenu.EnableMenuItem(MP_PAUSEONPREVIEW, MF_GRAYED); + } + else { + m_FileMenu.EnableMenuItem(MP_PREVIEW, MF_GRAYED); + } + m_FileMenu.EnableMenuItem(MP_METINFO, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_VIEWFILECOMMENTS, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_CLEARCOMPLETED, GetCompleteDownloads(curTab,total) > 0 ? MF_ENABLED : MF_GRAYED); + m_FileMenu.EnableMenuItem(thePrefs.GetShowCopyEd2kLinkCmd() ? MP_GETED2KLINK : MP_SHOWED2KLINK, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_PASTE, theApp.IsEd2kFileLinkInClipboard() ? MF_ENABLED : MF_GRAYED); + m_FileMenu.SetDefaultItem((UINT)-1); + if (m_SourcesMenu) + m_FileMenu.EnableMenuItem((UINT_PTR)m_SourcesMenu.m_hMenu, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_SEARCHRELATED, MF_GRAYED); + m_FileMenu.EnableMenuItem(MP_FIND, GetItemCount() > 0 ? MF_ENABLED : MF_GRAYED); + + // also show the "Web Services" entry, even if its disabled and therefore not useable, it though looks a little + // less confusing this way. + CTitleMenu WebMenu; + WebMenu.CreateMenu(); + WebMenu.AddMenuTitle(NULL, true); + theWebServices.GetFileMenuEntries(&WebMenu); + m_FileMenu.AppendMenu(MF_POPUP | MF_GRAYED, (UINT_PTR)WebMenu.m_hMenu, GetResString(IDS_WEBSERVICES), _T("WEB")); + + bool bToolbarItem = !thePrefs.IsDownloadToolbarEnabled(); + if (bToolbarItem) + { + m_FileMenu.AppendMenu(MF_SEPARATOR); + m_FileMenu.AppendMenu(MF_STRING, MP_TOGGLEDTOOLBAR, GetResString(IDS_SHOWTOOLBAR)); + } + + GetPopupMenuPos(*this, point); + m_FileMenu.TrackPopupMenu(TPM_LEFTALIGN |TPM_RIGHTBUTTON, point.x, point.y, this); + if (bToolbarItem) + { + VERIFY( m_FileMenu.RemoveMenu(m_FileMenu.GetMenuItemCount() - 1, MF_BYPOSITION) ); + VERIFY( m_FileMenu.RemoveMenu(m_FileMenu.GetMenuItemCount() - 1, MF_BYPOSITION) ); + } + m_FileMenu.RemoveMenu(m_FileMenu.GetMenuItemCount() - 1, MF_BYPOSITION); + VERIFY( WebMenu.DestroyMenu() ); + } +} + +void CDownloadListCtrl::FillCatsMenu(CMenu& rCatsMenu, int iFilesInCats) +{ + ASSERT(rCatsMenu.m_hMenu); + if (iFilesInCats == (-1)) + { + iFilesInCats = 0; + int iSel = GetNextItem(-1, LVIS_SELECTED); + if (iSel != -1) + { + const CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(iSel); + if (content != NULL && content->type == FILE_TYPE) + { + POSITION pos = GetFirstSelectedItemPosition(); + while (pos) + { + const CtrlItem_Struct* pItemData = (CtrlItem_Struct*)GetItemData(GetNextSelectedItem(pos)); + if (pItemData == NULL || pItemData->type != FILE_TYPE) + continue; + const CPartFile* pFile = (CPartFile*)pItemData->value; + iFilesInCats += (!pFile->HasDefaultCategory()) ? 1 : 0; + } + } + } + } + rCatsMenu.AppendMenu(MF_STRING, MP_NEWCAT, GetResString(IDS_NEW) + _T("...")); + CString label = GetResString(IDS_CAT_UNASSIGN); + label.Remove('('); + label.Remove(')'); // Remove brackets without having to put a new/changed ressource string in + rCatsMenu.AppendMenu(MF_STRING | ((iFilesInCats == 0) ? MF_GRAYED : MF_ENABLED), MP_ASSIGNCAT, label); + if (thePrefs.GetCatCount() > 1) + { + rCatsMenu.AppendMenu(MF_SEPARATOR); + for (int i = 1; i < thePrefs.GetCatCount(); i++){ + label = thePrefs.GetCategory(i)->strTitle; + label.Replace(_T("&"), _T("&&") ); + rCatsMenu.AppendMenu(MF_STRING, MP_ASSIGNCAT + i, label); + } + } +} + +CTitleMenu* CDownloadListCtrl::GetPrioMenu() +{ + UINT uPrioMenuItem = 0; + int iSel = GetNextItem(-1, LVIS_SELECTED); + if (iSel != -1) + { + const CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(iSel); + if (content != NULL && content->type == FILE_TYPE) + { + bool bFirstItem = true; + POSITION pos = GetFirstSelectedItemPosition(); + while (pos) + { + const CtrlItem_Struct* pItemData = (CtrlItem_Struct*)GetItemData(GetNextSelectedItem(pos)); + if (pItemData == NULL || pItemData->type != FILE_TYPE) + continue; + const CPartFile* pFile = (CPartFile*)pItemData->value; + UINT uCurPrioMenuItem = 0; + if (pFile->IsAutoDownPriority()) + uCurPrioMenuItem = MP_PRIOAUTO; + else if (pFile->GetDownPriority() == PR_HIGH) + uCurPrioMenuItem = MP_PRIOHIGH; + else if (pFile->GetDownPriority() == PR_NORMAL) + uCurPrioMenuItem = MP_PRIONORMAL; + else if (pFile->GetDownPriority() == PR_LOW) + uCurPrioMenuItem = MP_PRIOLOW; + else + ASSERT(0); + + if (bFirstItem) + uPrioMenuItem = uCurPrioMenuItem; + else if (uPrioMenuItem != uCurPrioMenuItem) + { + uPrioMenuItem = 0; + break; + } + bFirstItem = false; + } + } + } + m_PrioMenu.CheckMenuRadioItem(MP_PRIOLOW, MP_PRIOAUTO, uPrioMenuItem, 0); + return &m_PrioMenu; +} + +BOOL CDownloadListCtrl::OnCommand(WPARAM wParam, LPARAM /*lParam*/) +{ + wParam = LOWORD(wParam); + + switch (wParam) + { + case MP_PASTE: + if (theApp.IsEd2kFileLinkInClipboard()) + theApp.PasteClipboard(curTab); + return TRUE; + case MP_FIND: + OnFindStart(); + return TRUE; + case MP_TOGGLEDTOOLBAR: + thePrefs.SetDownloadToolbar(true); + theApp.emuledlg->transferwnd->ShowToolbar(true); + return TRUE; + } + + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel == -1) + iSel = GetNextItem(-1, LVIS_SELECTED); + if (iSel != -1) + { + const CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(iSel); + if (content != NULL && content->type == FILE_TYPE) + { + //for multiple selections + UINT selectedCount = 0; + CTypedPtrList selectedList; + POSITION pos = GetFirstSelectedItemPosition(); + while(pos != NULL) + { + int index = GetNextSelectedItem(pos); + if(index > -1) + { + if (((const CtrlItem_Struct*)GetItemData(index))->type == FILE_TYPE) + { + selectedCount++; + selectedList.AddTail((CPartFile*)((const CtrlItem_Struct*)GetItemData(index))->value); + } + } + } + + CPartFile* file = (CPartFile*)content->value; + switch (wParam) + { + case MP_CANCEL: + case MPG_DELETE: // keyboard del will continue to remove completed files from the screen while cancel will now also be available for complete files + { + if (selectedCount > 0) + { + SetRedraw(false); + CString fileList; + bool validdelete = false; + bool removecompl = false; + int cFiles = 0; + const int iMaxDisplayFiles = 10; + for (pos = selectedList.GetHeadPosition(); pos != 0; ) + { + CPartFile* cur_file = selectedList.GetNext(pos); + if (cur_file->GetStatus() != PS_COMPLETING && (cur_file->GetStatus() != PS_COMPLETE || wParam == MP_CANCEL)){ + validdelete = true; + cFiles++; + if (cFiles < iMaxDisplayFiles) + fileList.Append(_T("\n") + CString(cur_file->GetFileName())); + else if(cFiles == iMaxDisplayFiles && pos != NULL) + fileList.Append(_T("\n...")); + } + else if (cur_file->GetStatus() == PS_COMPLETE) + removecompl = true; + } + CString quest; + if (selectedCount == 1) + quest = GetResString(IDS_Q_CANCELDL2); + else + quest = GetResString(IDS_Q_CANCELDL); + if ((removecompl && !validdelete) || (validdelete && AfxMessageBox(quest + fileList, MB_DEFBUTTON2 | MB_ICONQUESTION | MB_YESNO) == IDYES)) + { + bool bRemovedItems = false; + while (!selectedList.IsEmpty()) + { + HideSources(selectedList.GetHead()); + switch (selectedList.GetHead()->GetStatus()) + { + case PS_WAITINGFORHASH: + case PS_HASHING: + case PS_COMPLETING: + selectedList.RemoveHead(); + bRemovedItems = true; + break; + case PS_COMPLETE: + if (wParam == MP_CANCEL){ + bool delsucc = ShellDeleteFile(selectedList.GetHead()->GetFilePath()); + if (delsucc){ + theApp.sharedfiles->RemoveFile(selectedList.GetHead(), true); + } + else{ + CString strError; + strError.Format( GetResString(IDS_ERR_DELFILE) + _T("\r\n\r\n%s"), selectedList.GetHead()->GetFilePath(), GetErrorMessage(GetLastError())); + AfxMessageBox(strError); + } + } + RemoveFile(selectedList.GetHead()); + selectedList.RemoveHead(); + bRemovedItems = true; + break; + case PS_PAUSED: + selectedList.GetHead()->DeleteFile(); + selectedList.RemoveHead(); + bRemovedItems = true; + break; + default: + if (selectedList.GetHead()->GetCategory()) + theApp.downloadqueue->StartNextFileIfPrefs(selectedList.GetHead()->GetCategory()); + selectedList.GetHead()->DeleteFile(); + selectedList.RemoveHead(); + bRemovedItems = true; + } + } + if (bRemovedItems) + { + AutoSelectItem(); + theApp.emuledlg->transferwnd->UpdateCatTabTitles(); + } + } + SetRedraw(true); + } + break; + } + case MP_PRIOHIGH: + SetRedraw(false); + while (!selectedList.IsEmpty()){ + CPartFile* partfile = selectedList.GetHead(); + partfile->SetAutoDownPriority(false); + partfile->SetDownPriority(PR_HIGH); + selectedList.RemoveHead(); + } + SetRedraw(true); + break; + case MP_PRIOLOW: + SetRedraw(false); + while (!selectedList.IsEmpty()){ + CPartFile* partfile = selectedList.GetHead(); + partfile->SetAutoDownPriority(false); + partfile->SetDownPriority(PR_LOW); + selectedList.RemoveHead(); + } + SetRedraw(true); + break; + case MP_PRIONORMAL: + SetRedraw(false); + while (!selectedList.IsEmpty()){ + CPartFile* partfile = selectedList.GetHead(); + partfile->SetAutoDownPriority(false); + partfile->SetDownPriority(PR_NORMAL); + selectedList.RemoveHead(); + } + SetRedraw(true); + break; + case MP_PRIOAUTO: + SetRedraw(false); + while (!selectedList.IsEmpty()){ + CPartFile* partfile = selectedList.GetHead(); + partfile->SetAutoDownPriority(true); + partfile->SetDownPriority(PR_HIGH); + selectedList.RemoveHead(); + } + SetRedraw(true); + break; + case MP_PAUSE: + SetRedraw(false); + while (!selectedList.IsEmpty()){ + CPartFile* partfile = selectedList.GetHead(); + if (partfile->CanPauseFile()) + partfile->PauseFile(); + selectedList.RemoveHead(); + } + SetRedraw(true); + break; + case MP_RESUME: + SetRedraw(false); + while (!selectedList.IsEmpty()){ + CPartFile* partfile = selectedList.GetHead(); + if (partfile->CanResumeFile()){ + if (partfile->GetStatus() == PS_INSUFFICIENT) + partfile->ResumeFileInsufficient(); + else + partfile->ResumeFile(); + } + selectedList.RemoveHead(); + } + SetRedraw(true); + break; + case MP_STOP: + SetRedraw(false); + while (!selectedList.IsEmpty()){ + CPartFile *partfile = selectedList.GetHead(); + if (partfile->CanStopFile()){ + HideSources(partfile); + partfile->StopFile(); + } + selectedList.RemoveHead(); + } + SetRedraw(true); + theApp.emuledlg->transferwnd->UpdateCatTabTitles(); + break; + case MP_CLEARCOMPLETED: + SetRedraw(false); + ClearCompleted(); + SetRedraw(true); + break; + case MPG_F2: + if (GetAsyncKeyState(VK_CONTROL) < 0 || selectedCount > 1) { + // when ctrl is pressed -> filename cleanup + if (IDYES==AfxMessageBox(GetResString(IDS_MANUAL_FILENAMECLEANUP),MB_YESNO)) + while (!selectedList.IsEmpty()){ + CPartFile *partfile = selectedList.GetHead(); + if (partfile->IsPartFile()) { + partfile->SetFileName(CleanupFilename(partfile->GetFileName())); + } + selectedList.RemoveHead(); + } + } else { + if (file->GetStatus() != PS_COMPLETE && file->GetStatus() != PS_COMPLETING) + { + InputBox inputbox; + CString title = GetResString(IDS_RENAME); + title.Remove(_T('&')); + inputbox.SetLabels(title, GetResString(IDS_DL_FILENAME), file->GetFileName()); + inputbox.SetEditFilenameMode(); + if (inputbox.DoModal()==IDOK && !inputbox.GetInput().IsEmpty() && IsValidEd2kString(inputbox.GetInput())) + { + file->SetFileName(inputbox.GetInput(), true); + file->UpdateDisplayedInfo(); + file->SavePartFile(); + } + } + else + MessageBeep(MB_OK); + } + break; + case MP_METINFO: + case MPG_ALTENTER: + ShowFileDialog(0); + break; + case MP_COPYSELECTED: + case MP_GETED2KLINK:{ + CString str; + while (!selectedList.IsEmpty()){ + if (!str.IsEmpty()) + str += _T("\r\n"); + str += ((CAbstractFile*)selectedList.GetHead())->GetED2kLink(); + selectedList.RemoveHead(); + } + theApp.CopyTextToClipboard(str); + break; + } + case MP_SEARCHRELATED: + theApp.emuledlg->searchwnd->SearchRelatedFiles(selectedList); + theApp.emuledlg->SetActiveDialog(theApp.emuledlg->searchwnd); + break; + case MP_OPEN: + case IDA_ENTER: + if (selectedCount > 1) + break; + if (file->CanOpenFile()) + file->OpenFile(); + break; + case MP_OPENFOLDER: + if (selectedCount != 1) + break; + ShellOpenFile(file->GetPath(), _T("open")); + break; + case MP_TRY_TO_GET_PREVIEW_PARTS: + if (selectedCount > 1) + break; + file->SetPreviewPrio(!file->GetPreviewPrio()); + break; + case MP_PREVIEW: + if (selectedCount > 1) + break; + file->PreviewFile(); + break; + case MP_PAUSEONPREVIEW: + { + bool bAllPausedOnPreview = true; + for (pos = selectedList.GetHeadPosition(); pos != 0; ) + bAllPausedOnPreview = ((CPartFile*)selectedList.GetNext(pos))->IsPausingOnPreview() && bAllPausedOnPreview; + while (!selectedList.IsEmpty()){ + CPartFile* pPartFile = selectedList.RemoveHead(); + if (pPartFile->IsPreviewableFileType() && !pPartFile->IsReadyForPreview()) + pPartFile->SetPauseOnPreview(!bAllPausedOnPreview); + + } + break; + } + case MP_VIEWFILECOMMENTS: + ShowFileDialog(IDD_COMMENTLST); + break; + case MP_SHOWED2KLINK: + ShowFileDialog(IDD_ED2KLINK); + break; + case MP_SETSOURCELIMIT: { + CString temp; + temp.Format(_T("%u"),file->GetPrivateMaxSources()); + InputBox inputbox; + CString title = GetResString(IDS_SETPFSLIMIT); + inputbox.SetLabels(title, GetResString(IDS_SETPFSLIMITEXPLAINED), temp ); + + if (inputbox.DoModal() == IDOK) + { + temp = inputbox.GetInput(); + int newlimit = _tstoi(temp); + while (!selectedList.IsEmpty()){ + CPartFile *partfile = selectedList.GetHead(); + partfile->SetPrivateMaxSources(newlimit); + selectedList.RemoveHead(); + partfile->UpdateDisplayedInfo(true); + } + } + break; + } + case MP_ADDSOURCE: { + if (selectedCount > 1) + break; + CAddSourceDlg as; + as.SetFile(file); + as.DoModal(); + break; + } + default: + if (wParam>=MP_WEBURL && wParam<=MP_WEBURL+99){ + theWebServices.RunURL(file, wParam); + } + else if ((wParam >= MP_ASSIGNCAT && wParam<=MP_ASSIGNCAT+99) || wParam == MP_NEWCAT){ + int nCatNumber; + if (wParam == MP_NEWCAT) + { + nCatNumber = theApp.emuledlg->transferwnd->AddCategoryInteractive(); + if (nCatNumber == 0) // Creation canceled + break; + } + else + nCatNumber = wParam - MP_ASSIGNCAT; + SetRedraw(FALSE); + while (!selectedList.IsEmpty()){ + CPartFile *partfile = selectedList.GetHead(); + partfile->SetCategory(nCatNumber); + partfile->UpdateDisplayedInfo(true); + selectedList.RemoveHead(); + } + SetRedraw(TRUE); + UpdateCurrentCategoryView(); + if (thePrefs.ShowCatTabInfos()) + theApp.emuledlg->transferwnd->UpdateCatTabTitles(); + } + else if (wParam>=MP_PREVIEW_APP_MIN && wParam<=MP_PREVIEW_APP_MAX){ + thePreviewApps.RunApp(file, wParam); + } + break; + } + } + else if (content != NULL) + { + CUpDownClient* client = (CUpDownClient*)content->value; + + switch (wParam){ + case MP_SHOWLIST: + client->RequestSharedFileList(); + break; + case MP_MESSAGE: + theApp.emuledlg->chatwnd->StartSession(client); + break; + case MP_ADDFRIEND: + if (theApp.friendlist->AddFriend(client)) + UpdateItem(client); + break; + case MP_DETAIL: + case MPG_ALTENTER: + ShowClientDialog(client); + break; + case MP_BOOT: + if (client->GetKadPort() && client->GetKadVersion() > 1) + Kademlia::CKademlia::Bootstrap(ntohl(client->GetIP()), client->GetKadPort()); + break; +// ZZ:DownloadManager --> +#ifdef _DEBUG + case MP_A4AF_CHECK_THIS_NOW: { + CPartFile* file = (CPartFile*)content->owner; + if (file->GetStatus(false) == PS_READY || file->GetStatus(false) == PS_EMPTY) + { + if (client->GetDownloadState() != DS_DOWNLOADING) + { + client->SwapToAnotherFile(_T("Manual init of source check. Test to be like ProcessA4AFClients(). CDownloadListCtrl::OnCommand() MP_SWAP_A4AF_DEBUG_THIS"), false, false, false, NULL, true, true, true); // ZZ:DownloadManager + UpdateItem(file); + } + } + break; + } +#endif +// <-- ZZ:DownloadManager + } + } + } + else /*nothing selected*/ + { + switch (wParam){ + case MP_CLEARCOMPLETED: + ClearCompleted(); + break; + } + } + m_availableCommandsDirty = true; + return TRUE; +} + +void CDownloadListCtrl::OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult) +{ + NMLISTVIEW *pNMListView = (NMLISTVIEW *)pNMHDR; + bool sortAscending; + if (GetSortItem() != pNMListView->iSubItem) + { + switch (pNMListView->iSubItem) + { + case 2: // Transferred + case 3: // Completed + case 4: // Download rate + case 5: // Progress + case 6: // Sources / Client Software + sortAscending = false; + break; + case 9: + // Keep the current 'm_bRemainSort' for that column, but reset to 'ascending' + sortAscending = true; + break; + default: + sortAscending = true; + break; + } + } + else + sortAscending = !GetSortAscending(); + + // Ornis 4-way-sorting + int adder = 0; + if (pNMListView->iSubItem == 9) + { + if (GetSortItem() == 9 && sortAscending) // check for 'ascending' because the initial sort order is also 'ascending' + m_bRemainSort = !m_bRemainSort; + adder = !m_bRemainSort ? 0 : 81; + } + + // Sort table + if (adder == 0) + SetSortArrow(pNMListView->iSubItem, sortAscending); + else + SetSortArrow(pNMListView->iSubItem, sortAscending ? arrowDoubleUp : arrowDoubleDown); + UpdateSortHistory(pNMListView->iSubItem + (sortAscending ? 0 : 100) + adder); + SortItems(SortProc, pNMListView->iSubItem + (sortAscending ? 0 : 100) + adder); + + // Save new preferences + thePrefs.TransferlistRemainSortStyle(m_bRemainSort); + + *pResult = 0; +} + +int CDownloadListCtrl::SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + const CtrlItem_Struct *item1 = (CtrlItem_Struct *)lParam1; + const CtrlItem_Struct *item2 = (CtrlItem_Struct *)lParam2; + + int dwOrgSort = lParamSort; + int sortMod = 1; + if (lParamSort >= 100) + { + sortMod = -1; + lParamSort -= 100; + } + + int comp; + if (item1->type == FILE_TYPE && item2->type != FILE_TYPE) + { + if (item1->value == item2->parent->value) + return -1; + comp = Compare((const CPartFile *)item1->value, (const CPartFile *)(item2->parent->value), lParamSort); + } + else if (item2->type == FILE_TYPE && item1->type != FILE_TYPE) + { + if (item1->parent->value == item2->value) + return 1; + comp = Compare((const CPartFile *)(item1->parent->value), (const CPartFile *)item2->value, lParamSort); + } + else if (item1->type == FILE_TYPE) + { + const CPartFile *file1 = (const CPartFile *)item1->value; + const CPartFile *file2 = (const CPartFile *)item2->value; + comp = Compare(file1, file2, lParamSort); + } + else + { + if (item1->parent->value!=item2->parent->value) + { + comp = Compare((const CPartFile *)(item1->parent->value), (const CPartFile *)(item2->parent->value), lParamSort); + return sortMod * comp; + } + if (item1->type != item2->type) + return item1->type - item2->type; + + const CUpDownClient *client1 = (const CUpDownClient *)item1->value; + const CUpDownClient *client2 = (const CUpDownClient *)item2->value; + comp = Compare(client1, client2, lParamSort); + } + + //call secondary sortorder, if this one results in equal + int dwNextSort; + if (comp == 0 && (dwNextSort = theApp.emuledlg->transferwnd->GetDownloadList()->GetNextSortOrder(dwOrgSort)) != -1) + return SortProc(lParam1, lParam2, dwNextSort); + + return sortMod * comp; +} + +void CDownloadListCtrl::ClearCompleted(int incat){ + if (incat==-2) + incat=curTab; + + // Search for completed file(s) + for(ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); ){ + CtrlItem_Struct* cur_item = it->second; + it++; // Already point to the next iterator. + if(cur_item->type == FILE_TYPE){ + CPartFile* file = reinterpret_cast(cur_item->value); + if(file->IsPartFile() == false && (file->CheckShowItemInGivenCat(incat) || incat==-1) ){ + if (RemoveFile(file)) + it = m_ListItems.begin(); + } + } + } + if (thePrefs.ShowCatTabInfos()) + theApp.emuledlg->transferwnd->UpdateCatTabTitles(); +} + +void CDownloadListCtrl::ClearCompleted(const CPartFile* pFile) +{ + if (!pFile->IsPartFile()) + { + for (ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); ) + { + CtrlItem_Struct* cur_item = it->second; + it++; + if (cur_item->type == FILE_TYPE) + { + const CPartFile* pCurFile = reinterpret_cast(cur_item->value); + if (pCurFile == pFile) + { + RemoveFile(pCurFile); + return; + } + } + } + } +} + +void CDownloadListCtrl::SetStyle() +{ + if (thePrefs.IsDoubleClickEnabled()) + SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + else + SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_ONECLICKACTIVATE); +} + +void CDownloadListCtrl::OnListModified(NMHDR *pNMHDR, LRESULT * /*pResult*/) +{ + NMLISTVIEW *pNMListView = (NMLISTVIEW *)pNMHDR; + + //this works because true is equal to 1 and false equal to 0 + BOOL notLast = pNMListView->iItem + 1 != GetItemCount(); + BOOL notFirst = pNMListView->iItem != 0; + RedrawItems(pNMListView->iItem - (int)notFirst, pNMListView->iItem + (int)notLast); + m_availableCommandsDirty = true; +} + +int CDownloadListCtrl::Compare(const CPartFile *file1, const CPartFile *file2, LPARAM lParamSort) +{ + int comp = 0; + switch (lParamSort) + { + case 0: //filename asc + comp = CompareLocaleStringNoCase(file1->GetFileName(), file2->GetFileName()); + break; + + case 1: //size asc + comp = CompareUnsigned64(file1->GetFileSize(), file2->GetFileSize()); + break; + + case 2: //transferred asc + comp = CompareUnsigned64(file1->GetTransferred(), file2->GetTransferred()); + break; + + case 3: //completed asc + comp = CompareUnsigned64(file1->GetCompletedSize(), file2->GetCompletedSize()); + break; + + case 4: //speed asc + comp = CompareUnsigned(file1->GetDatarate(), file2->GetDatarate()); + break; + + case 5: //progress asc + comp = CompareFloat(file1->GetPercentCompleted(), file2->GetPercentCompleted()); + break; + + case 6: //sources asc + comp = CompareUnsigned(file1->GetSourceCount(), file2->GetSourceCount()); + break; + + case 7: //priority asc + comp = CompareUnsigned(file1->GetDownPriority(), file2->GetDownPriority()); + break; + + case 8: //Status asc + comp = CompareUnsigned(file1->getPartfileStatusRang(), file2->getPartfileStatusRang()); + break; + + case 9: //Remaining Time asc + { + //Make ascending sort so we can have the smaller remaining time on the top + //instead of unknowns so we can see which files are about to finish better.. + time_t f1 = file1->getTimeRemaining(); + time_t f2 = file2->getTimeRemaining(); + //Same, do nothing. + if (f1 == f2) { + comp = 0; + break; + } + + //If descending, put first on top as it is unknown + //If ascending, put first on bottom as it is unknown + if (f1 == -1) { + comp = 1; + break; + } + + //If descending, put second on top as it is unknown + //If ascending, put second on bottom as it is unknown + if (f2 == -1) { + comp = -1; + break; + } + + //If descending, put first on top as it is bigger. + //If ascending, put first on bottom as it is bigger. + comp = CompareUnsigned(f1, f2); + break; + } + + case 90: //Remaining SIZE asc + comp = CompareUnsigned64(file1->GetFileSize() - file1->GetCompletedSize(), file2->GetFileSize() - file2->GetCompletedSize()); + break; + + case 10: //last seen complete asc + if (file1->lastseencomplete > file2->lastseencomplete) + comp = 1; + else if (file1->lastseencomplete < file2->lastseencomplete) + comp = -1; + break; + + case 11: //last received Time asc + if (file1->GetFileDate() > file2->GetFileDate()) + comp = 1; + else if(file1->GetFileDate() < file2->GetFileDate()) + comp = -1; + break; + + case 12: + //TODO: 'GetCategory' SHOULD be a 'const' function and 'GetResString' should NOT be called.. + comp = CompareLocaleStringNoCase((const_cast(file1)->GetCategory() != 0) ? thePrefs.GetCategory(const_cast(file1)->GetCategory())->strTitle:GetResString(IDS_ALL), + (const_cast(file2)->GetCategory() != 0) ? thePrefs.GetCategory(const_cast(file2)->GetCategory())->strTitle:GetResString(IDS_ALL) ); + break; + + case 13: // addeed on asc + if (file1->GetCrCFileDate() > file2->GetCrCFileDate()) + comp = 1; + else if(file1->GetCrCFileDate() < file2->GetCrCFileDate()) + comp = -1; + break; + } + return comp; +} + +int CDownloadListCtrl::Compare(const CUpDownClient *client1, const CUpDownClient *client2, LPARAM lParamSort) +{ + switch (lParamSort) + { + case 0: //name asc + if (client1->GetUserName() && client2->GetUserName()) + return CompareLocaleStringNoCase(client1->GetUserName(), client2->GetUserName()); + else if (client1->GetUserName() == NULL) + return 1; // place clients with no usernames at bottom + else if (client2->GetUserName() == NULL) + return -1; // place clients with no usernames at bottom + return 0; + + case 1: //size but we use status asc + return client1->GetSourceFrom() - client2->GetSourceFrom(); + + case 2://transferred asc + case 3://completed asc + return CompareUnsigned(client1->GetTransferredDown(), client2->GetTransferredDown()); + + case 4: //speed asc + return CompareUnsigned(client1->GetDownloadDatarate(), client2->GetDownloadDatarate()); + + case 5: //progress asc + return CompareUnsigned(client1->GetAvailablePartCount(), client2->GetAvailablePartCount()); + + case 6: + if (client1->GetClientSoft() == client2->GetClientSoft()) + return client1->GetVersion() - client2->GetVersion(); + return -(client1->GetClientSoft() - client2->GetClientSoft()); // invert result to place eMule's at top + + case 7: //qr asc + if (client1->GetDownloadState() == DS_DOWNLOADING) { + if (client2->GetDownloadState() == DS_DOWNLOADING) + return 0; + else + return -1; + } + else if (client2->GetDownloadState() == DS_DOWNLOADING) + return 1; + + if (client1->GetRemoteQueueRank() == 0 && client1->GetDownloadState() == DS_ONQUEUE && client1->IsRemoteQueueFull()) + return 1; + if (client2->GetRemoteQueueRank() == 0 && client2->GetDownloadState() == DS_ONQUEUE && client2->IsRemoteQueueFull()) + return -1; + if (client1->GetRemoteQueueRank() == 0) + return 1; + if (client2->GetRemoteQueueRank() == 0) + return -1; + return CompareUnsigned(client1->GetRemoteQueueRank(), client2->GetRemoteQueueRank()); + + case 8: + if (client1->GetDownloadState() == client2->GetDownloadState()) + { + if (client1->IsRemoteQueueFull() && client2->IsRemoteQueueFull()) + return 0; + else if (client1->IsRemoteQueueFull()) + return 1; + else if (client2->IsRemoteQueueFull()) + return -1; + } + return client1->GetDownloadState() - client2->GetDownloadState(); + } + return 0; +} + +void CDownloadListCtrl::OnNmDblClk(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + int iSel = GetSelectionMark(); + if (iSel != -1) + { + const CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(iSel); + if (content && content->value) + { + if (content->type == FILE_TYPE) + { + if (!thePrefs.IsDoubleClickEnabled()) + { + CPoint pt; + ::GetCursorPos(&pt); + ScreenToClient(&pt); + LVHITTESTINFO hit; + hit.pt = pt; + if (HitTest(&hit) >= 0 && (hit.flags & LVHT_ONITEM)) + { + LVHITTESTINFO subhit; + subhit.pt = pt; + if (SubItemHitTest(&subhit) >= 0 && subhit.iSubItem == 0) + { + CPartFile* file = (CPartFile*)content->value; + if (thePrefs.ShowRatingIndicator() + && (file->HasComment() || file->HasRating() || file->IsKadCommentSearchRunning()) + && pt.x >= sm_iIconOffset + theApp.GetSmallSytemIconSize().cx + && pt.x <= sm_iIconOffset + theApp.GetSmallSytemIconSize().cx + RATING_ICON_WIDTH) + ShowFileDialog(IDD_COMMENTLST); + else if (thePrefs.GetPreviewOnIconDblClk() + && pt.x >= sm_iIconOffset + && pt.x < sm_iIconOffset + theApp.GetSmallSytemIconSize().cx) { + if (file->IsReadyForPreview()) + file->PreviewFile(); + else + MessageBeep(MB_OK); + } + else + ShowFileDialog(0); + } + } + } + } + else + { + ShowClientDialog((CUpDownClient*)content->value); + } + } + } + + *pResult = 0; +} + +void CDownloadListCtrl::CreateMenues() +{ + if (m_PreviewMenu) + VERIFY( m_PreviewMenu.DestroyMenu() ); + if (m_PrioMenu) + VERIFY( m_PrioMenu.DestroyMenu() ); + if (m_SourcesMenu) + VERIFY( m_SourcesMenu.DestroyMenu() ); + if (m_FileMenu) + VERIFY( m_FileMenu.DestroyMenu() ); + + m_FileMenu.CreatePopupMenu(); + m_FileMenu.AddMenuTitle(GetResString(IDS_DOWNLOADMENUTITLE), true); + + // Add 'Download Priority' sub menu + // + m_PrioMenu.CreateMenu(); + m_PrioMenu.AddMenuTitle(NULL, true); + m_PrioMenu.AppendMenu(MF_STRING, MP_PRIOLOW, GetResString(IDS_PRIOLOW)); + m_PrioMenu.AppendMenu(MF_STRING, MP_PRIONORMAL, GetResString(IDS_PRIONORMAL)); + m_PrioMenu.AppendMenu(MF_STRING, MP_PRIOHIGH, GetResString(IDS_PRIOHIGH)); + m_PrioMenu.AppendMenu(MF_STRING, MP_PRIOAUTO, GetResString(IDS_PRIOAUTO)); + m_FileMenu.AppendMenu(MF_STRING|MF_POPUP, (UINT_PTR)m_PrioMenu.m_hMenu, GetResString(IDS_PRIORITY) + _T(" (") + GetResString(IDS_DOWNLOAD) + _T(")"), _T("FILEPRIORITY")); + + // Add file commands + // + m_FileMenu.AppendMenu(MF_STRING, MP_PAUSE, GetResString(IDS_DL_PAUSE), _T("PAUSE")); + m_FileMenu.AppendMenu(MF_STRING, MP_STOP, GetResString(IDS_DL_STOP), _T("STOP")); + m_FileMenu.AppendMenu(MF_STRING, MP_RESUME, GetResString(IDS_DL_RESUME), _T("RESUME")); + m_FileMenu.AppendMenu(MF_STRING, MP_CANCEL, GetResString(IDS_MAIN_BTN_CANCEL), _T("DELETE")); + m_FileMenu.AppendMenu(MF_SEPARATOR); + m_FileMenu.AppendMenu(MF_STRING, MP_OPEN, GetResString(IDS_DL_OPEN), _T("OPENFILE")); + + // Extended: Submenu with Preview options, Normal: Preview and possibly 'Preview with' item + if (thePrefs.IsExtControlsEnabled()) + { + m_PreviewMenu.CreateMenu(); + m_PreviewMenu.AddMenuTitle(NULL, true); + m_PreviewMenu.AppendMenu(MF_STRING, MP_PREVIEW, GetResString(IDS_DL_PREVIEW), _T("PREVIEW")); + m_PreviewMenu.AppendMenu(MF_STRING, MP_PAUSEONPREVIEW, GetResString(IDS_PAUSEONPREVIEW)); + if (!thePrefs.GetPreviewPrio()) + m_PreviewMenu.AppendMenu(MF_STRING, MP_TRY_TO_GET_PREVIEW_PARTS, GetResString(IDS_DL_TRY_TO_GET_PREVIEW_PARTS)); + m_FileMenu.AppendMenu(MF_STRING|MF_POPUP, (UINT_PTR)m_PreviewMenu.m_hMenu, GetResString(IDS_DL_PREVIEW), _T("PREVIEW")); + } + else + m_FileMenu.AppendMenu(MF_STRING, MP_PREVIEW, GetResString(IDS_DL_PREVIEW), _T("PREVIEW")); + + m_FileMenu.AppendMenu(MF_STRING, MP_METINFO, GetResString(IDS_DL_INFO), _T("FILEINFO")); + m_FileMenu.AppendMenu(MF_STRING, MP_VIEWFILECOMMENTS, GetResString(IDS_CMT_SHOWALL), _T("FILECOMMENTS")); + m_FileMenu.AppendMenu(MF_SEPARATOR); + m_FileMenu.AppendMenu(MF_STRING, MP_CLEARCOMPLETED, GetResString(IDS_DL_CLEAR), _T("CLEARCOMPLETE")); + + // Add (extended user mode) 'Source Handling' sub menu + // + if (thePrefs.IsExtControlsEnabled()) { + m_SourcesMenu.CreateMenu(); + m_SourcesMenu.AppendMenu(MF_STRING, MP_ADDSOURCE, GetResString(IDS_ADDSRCMANUALLY)); + m_SourcesMenu.AppendMenu(MF_STRING, MP_SETSOURCELIMIT, GetResString(IDS_SETPFSLIMIT)); + m_FileMenu.AppendMenu(MF_STRING|MF_POPUP, (UINT_PTR)m_SourcesMenu.m_hMenu, GetResString(IDS_A4AF)); + } + m_FileMenu.AppendMenu(MF_SEPARATOR); + + // Add 'Copy & Paste' commands + // + if (thePrefs.GetShowCopyEd2kLinkCmd()) + m_FileMenu.AppendMenu(MF_STRING, MP_GETED2KLINK, GetResString(IDS_DL_LINK1), _T("ED2KLINK")); + else + m_FileMenu.AppendMenu(MF_STRING, MP_SHOWED2KLINK, GetResString(IDS_DL_SHOWED2KLINK), _T("ED2KLINK")); + m_FileMenu.AppendMenu(MF_STRING, MP_PASTE, GetResString(IDS_SW_DIRECTDOWNLOAD), _T("PASTELINK")); + m_FileMenu.AppendMenu(MF_SEPARATOR); + + // Search commands + // + m_FileMenu.AppendMenu(MF_STRING, MP_FIND, GetResString(IDS_FIND), _T("Search")); + m_FileMenu.AppendMenu(MF_STRING, MP_SEARCHRELATED, GetResString(IDS_SEARCHRELATED), _T("KadFileSearch")); + // Web-services and categories will be added on-the-fly.. +} + +CString CDownloadListCtrl::getTextList() +{ + CString out; + + for (ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++) + { + const CtrlItem_Struct* cur_item = it->second; + if (cur_item->type == FILE_TYPE) + { + const CPartFile* file = reinterpret_cast(cur_item->value); + + CString temp; + temp.Format(_T("\n%s\t [%.1f%%] %i/%i - %s"), + file->GetFileName(), + file->GetPercentCompleted(), + file->GetTransferringSrcCount(), + file->GetSourceCount(), + file->getPartfileStatus()); + + out += temp; + } + } + + return out; +} + +float CDownloadListCtrl::GetFinishedSize() +{ + float fsize = 0; + + for (ListItems::const_iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++) + { + CtrlItem_Struct* cur_item = it->second; + if (cur_item->type == FILE_TYPE) + { + CPartFile* file = (CPartFile*)cur_item->value; + if (file->GetStatus() == PS_COMPLETE) { + fsize += (uint64)file->GetFileSize(); + } + } + } + return fsize; +} + +int CDownloadListCtrl::GetFilesCountInCurCat() +{ + int iCount = 0; + for (ListItems::const_iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++) + { + CtrlItem_Struct* cur_item = it->second; + if (cur_item->type == FILE_TYPE) + { + CPartFile* file = (CPartFile*)cur_item->value; + if (file->CheckShowItemInGivenCat(curTab)) + iCount++; + } + } + return iCount; +} + +void CDownloadListCtrl::ShowFilesCount() +{ + theApp.emuledlg->transferwnd->UpdateFilesCount(GetFilesCountInCurCat()); +} + +void CDownloadListCtrl::ShowSelectedFileDetails() +{ + POINT point; + ::GetCursorPos(&point); + CPoint pt = point; + ScreenToClient(&pt); + int it = HitTest(pt); + if (it == -1) + return; + + SetItemState(-1, 0, LVIS_SELECTED); + SetItemState(it, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + SetSelectionMark(it); // display selection mark correctly! + + CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(GetSelectionMark()); + if (content != NULL) + { + if (content->type == FILE_TYPE) + { + CPartFile* file = (CPartFile*)content->value; + if (thePrefs.ShowRatingIndicator() + && (file->HasComment() || file->HasRating() || file->IsKadCommentSearchRunning()) + && pt.x >= sm_iIconOffset + theApp.GetSmallSytemIconSize().cx + && pt.x <= sm_iIconOffset + theApp.GetSmallSytemIconSize().cx + RATING_ICON_WIDTH) + ShowFileDialog(IDD_COMMENTLST); + else + ShowFileDialog(0); + } + else + { + ShowClientDialog((CUpDownClient*)content->value); + } + } +} + +int CDownloadListCtrl::GetCompleteDownloads(int cat, int& total) +{ + total = 0; + int count = 0; + for (ListItems::const_iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++) + { + const CtrlItem_Struct* cur_item = it->second; + if (cur_item->type == FILE_TYPE) + { + /*const*/ CPartFile* file = reinterpret_cast(cur_item->value); + if (file->CheckShowItemInGivenCat(cat) || cat==-1) + { + total++; + if (file->GetStatus() == PS_COMPLETE) + count++; + } + } + } + return count; +} + +void CDownloadListCtrl::UpdateCurrentCategoryView(){ + ChangeCategory(curTab); +} + +void CDownloadListCtrl::UpdateCurrentCategoryView(CPartFile* thisfile) { + + ListItems::const_iterator it = m_ListItems.find(thisfile); + if (it != m_ListItems.end()) { + const CtrlItem_Struct* cur_item = it->second; + if (cur_item->type == FILE_TYPE){ + CPartFile* file = reinterpret_cast(cur_item->value); + + if (!file->CheckShowItemInGivenCat(curTab)) + HideFile(file); + else + ShowFile(file); + } + } + +} + +void CDownloadListCtrl::ChangeCategory(int newsel){ + + SetRedraw(FALSE); + + // remove all displayed files with a different cat and show the correct ones + for(ListItems::const_iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++){ + const CtrlItem_Struct* cur_item = it->second; + if (cur_item->type == FILE_TYPE){ + CPartFile* file = reinterpret_cast(cur_item->value); + + if (!file->CheckShowItemInGivenCat(newsel)) + HideFile(file); + else + ShowFile(file); + } + } + + SetRedraw(TRUE); + curTab=newsel; + ShowFilesCount(); +} + +void CDownloadListCtrl::HideFile(CPartFile* tohide) +{ + HideSources(tohide); + + // Retrieve all entries matching the source + std::pair rangeIt = m_ListItems.equal_range(tohide); + for(ListItems::const_iterator it = rangeIt.first; it != rangeIt.second; it++){ + CtrlItem_Struct* updateItem = it->second; + + // Find entry in CListCtrl and update object + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)updateItem; + int result = FindItem(&find); + if (result != -1){ + DeleteItem(result); + return; + } + } +} + +void CDownloadListCtrl::ShowFile(CPartFile* toshow){ + // Retrieve all entries matching the source + std::pair rangeIt = m_ListItems.equal_range(toshow); + ListItems::const_iterator it = rangeIt.first; + if(it != rangeIt.second){ + CtrlItem_Struct* updateItem = it->second; + + // Check if entry is already in the List + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)updateItem; + int result = FindItem(&find); + if (result == -1) + InsertItem(LVIF_PARAM | LVIF_TEXT, GetItemCount(), LPSTR_TEXTCALLBACK, 0, 0, 0, (LPARAM)updateItem); + } +} + +void CDownloadListCtrl::GetDisplayedFiles(CArray *list){ + for(ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); ){ + CtrlItem_Struct* cur_item = it->second; + it++; // Already point to the next iterator. + if(cur_item->type == FILE_TYPE){ + CPartFile* file = reinterpret_cast(cur_item->value); + list->Add(file); + } + } +} + +void CDownloadListCtrl::MoveCompletedfilesCat(uint8 from, uint8 to) +{ + int mycat; + + for(ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); ){ + CtrlItem_Struct* cur_item = it->second; + it++; // Already point to the next iterator. + if(cur_item->type == FILE_TYPE){ + CPartFile* file = reinterpret_cast(cur_item->value); + if (!file->IsPartFile()){ + mycat=file->GetCategory(); + if ( mycat>=min(from,to) && mycat<=max(from,to)) { + if (mycat==from) + file->SetCategory(to); + else + if (fromSetCategory(mycat-1); + else + file->SetCategory(mycat+1); + } + } + } + } +} + +void CDownloadListCtrl::OnLvnGetDispInfo(NMHDR *pNMHDR, LRESULT *pResult) +{ + if (theApp.emuledlg->IsRunning()) { + // Although we have an owner drawn listview control we store the text for the primary item in the listview, to be + // capable of quick searching those items via the keyboard. Because our listview items may change their contents, + // we do this via a text callback function. The listview control will send us the LVN_DISPINFO notification if + // it needs to know the contents of the primary item. + // + // But, the listview control sends this notification all the time, even if we do not search for an item. At least + // this notification is only sent for the visible items and not for all items in the list. Though, because this + // function is invoked *very* often, do *NOT* put any time consuming code in here. + // + // Vista: That callback is used to get the strings for the label tips for the sub(!) items. + // + NMLVDISPINFO *pDispInfo = (NMLVDISPINFO *)pNMHDR; + /*TRACE("CDownloadListCtrl::OnLvnGetDispInfo iItem=%d iSubItem=%d", pDispInfo->item.iItem, pDispInfo->item.iSubItem); + if (pDispInfo->item.mask & LVIF_TEXT) + TRACE(" LVIF_TEXT"); + if (pDispInfo->item.mask & LVIF_IMAGE) + TRACE(" LVIF_IMAGE"); + if (pDispInfo->item.mask & LVIF_STATE) + TRACE(" LVIF_STATE"); + TRACE("\n");*/ + if (pDispInfo->item.mask & LVIF_TEXT) { + const CtrlItem_Struct *pCtrlItem = reinterpret_cast(pDispInfo->item.lParam); + if (pCtrlItem != NULL && pCtrlItem->value != NULL) { + if (pCtrlItem->type == FILE_TYPE) + GetFileItemDisplayText((CPartFile *)pCtrlItem->value, pDispInfo->item.iSubItem, pDispInfo->item.pszText, pDispInfo->item.cchTextMax); + else if (pCtrlItem->type == UNAVAILABLE_SOURCE || pCtrlItem->type == AVAILABLE_SOURCE) + GetSourceItemDisplayText(pCtrlItem, pDispInfo->item.iSubItem, pDispInfo->item.pszText, pDispInfo->item.cchTextMax); + else + ASSERT(0); + } + } + } + *pResult = 0; +} + +void CDownloadListCtrl::OnLvnGetInfoTip(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast(pNMHDR); + if (pGetInfoTip->iSubItem == 0) + { + LVHITTESTINFO hti = {0}; + ::GetCursorPos(&hti.pt); + ScreenToClient(&hti.pt); + if (SubItemHitTest(&hti) == -1 || hti.iItem != pGetInfoTip->iItem || hti.iSubItem != 0){ + // don't show the default label tip for the main item, if the mouse is not over the main item + if ((pGetInfoTip->dwFlags & LVGIT_UNFOLDED) == 0 && pGetInfoTip->cchTextMax > 0 && pGetInfoTip->pszText[0] != _T('\0')) + pGetInfoTip->pszText[0] = _T('\0'); + return; + } + + const CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(pGetInfoTip->iItem); + if (content && pGetInfoTip->pszText && pGetInfoTip->cchTextMax > 0) + { + CString info; + + // build info text and display it + if (content->type == 1) // for downloading files + { + const CPartFile* partfile = (CPartFile*)content->value; + info = partfile->GetInfoSummary(); + } + else if (content->type == 3 || content->type == 2) // for sources + { + const CUpDownClient* client = (CUpDownClient*)content->value; + if (client->IsEd2kClient()) + { + in_addr server; + server.S_un.S_addr = client->GetServerIP(); + info.Format(GetResString(IDS_USERINFO) + + GetResString(IDS_SERVER) + _T(": %s:%u\n\n") + + GetResString(IDS_NEXT_REASK) + _T(": %s"), + client->GetUserName() ? client->GetUserName() : (_T('(') + GetResString(IDS_UNKNOWN) + _T(')')), + ipstr(server), client->GetServerPort(), + CastSecondsToHM(client->GetTimeUntilReask(client->GetRequestFile()) / 1000)); + if (thePrefs.IsExtControlsEnabled()) + info.AppendFormat(_T(" (%s)"), CastSecondsToHM(client->GetTimeUntilReask(content->owner) / 1000)); + info += _T('\n'); + info.AppendFormat(GetResString(IDS_SOURCEINFO), client->GetAskedCountDown(), client->GetAvailablePartCount()); + info += _T('\n'); + + if (content->type == 2) + { + info += GetResString(IDS_CLIENTSOURCENAME) + (!client->GetClientFilename().IsEmpty() ? client->GetClientFilename() : _T("-")); + if (!client->GetFileComment().IsEmpty()) + info += _T('\n') + GetResString(IDS_CMT_READ) + _T(' ') + client->GetFileComment(); + if (client->GetFileRating()) + info += _T('\n') + GetResString(IDS_QL_RATING) + _T(':') + GetRateString(client->GetFileRating()); + } + else + { // client asked twice + info += GetResString(IDS_ASKEDFAF); + if (client->GetRequestFile() && client->GetRequestFile()->GetFileName()) + info.AppendFormat(_T(": %s"), client->GetRequestFile()->GetFileName()); + } + + if (thePrefs.IsExtControlsEnabled() && !client->m_OtherRequests_list.IsEmpty()) + { + CSimpleArray apstrFileNames; + POSITION pos = client->m_OtherRequests_list.GetHeadPosition(); + while (pos) + apstrFileNames.Add(&client->m_OtherRequests_list.GetNext(pos)->GetFileName()); + Sort(apstrFileNames); + if (content->type == 2) + info += _T('\n'); + info += _T('\n'); + info += GetResString(IDS_A4AF_FILES); + info += _T(':'); + for (int i = 0; i < apstrFileNames.GetSize(); i++) + { + const CString* pstrFileName = apstrFileNames[i]; + if (info.GetLength() + (i > 0 ? 2 : 0) + pstrFileName->GetLength() >= pGetInfoTip->cchTextMax) { + static const TCHAR szEllipses[] = _T("\n:..."); + if (info.GetLength() + (int)ARRSIZE(szEllipses) - 1 < pGetInfoTip->cchTextMax) + info += szEllipses; + break; + } + if (i > 0) + info += _T("\n:"); + info += *pstrFileName; + } + } + } + else + { + info.Format(_T("URL: %s\nAvailable parts: %u"), client->GetUserName(), client->GetAvailablePartCount()); + } + } + + info += TOOLTIP_AUTOFORMAT_SUFFIX_CH; + _tcsncpy(pGetInfoTip->pszText, info, pGetInfoTip->cchTextMax); + pGetInfoTip->pszText[pGetInfoTip->cchTextMax-1] = _T('\0'); + } + } + *pResult = 0; +} + +void CDownloadListCtrl::ShowFileDialog(UINT uInvokePage) +{ + CSimpleArray aFiles; + POSITION pos = GetFirstSelectedItemPosition(); + while (pos != NULL) + { + int iItem = GetNextSelectedItem(pos); + if (iItem != -1) + { + const CtrlItem_Struct* pCtrlItem = (CtrlItem_Struct*)GetItemData(iItem); + if (pCtrlItem != NULL && pCtrlItem->type == FILE_TYPE) + aFiles.Add((CPartFile*)pCtrlItem->value); + } + } + + if (aFiles.GetSize() > 0) + { + CDownloadListListCtrlItemWalk::SetItemType(FILE_TYPE); + CFileDetailDialog dialog(&aFiles, uInvokePage, this); + dialog.DoModal(); + } +} + +CDownloadListListCtrlItemWalk::CDownloadListListCtrlItemWalk(CDownloadListCtrl* pListCtrl) + : CListCtrlItemWalk(pListCtrl) +{ + m_pDownloadListCtrl = pListCtrl; + m_eItemType = (ItemType)-1; +} + +CObject* CDownloadListListCtrlItemWalk::GetPrevSelectableItem() +{ + ASSERT( m_pDownloadListCtrl != NULL ); + if (m_pDownloadListCtrl == NULL) + return NULL; + ASSERT( m_eItemType != (ItemType)-1 ); + + int iItemCount = m_pDownloadListCtrl->GetItemCount(); + if (iItemCount >= 2) + { + POSITION pos = m_pDownloadListCtrl->GetFirstSelectedItemPosition(); + if (pos) + { + int iItem = m_pDownloadListCtrl->GetNextSelectedItem(pos); + int iCurSelItem = iItem; + while (iItem-1 >= 0) + { + iItem--; + + const CtrlItem_Struct* ctrl_item = (CtrlItem_Struct*)m_pDownloadListCtrl->GetItemData(iItem); + if (ctrl_item != NULL && (ctrl_item->type == m_eItemType || (m_eItemType != FILE_TYPE && ctrl_item->type != FILE_TYPE))) + { + m_pDownloadListCtrl->SetItemState(iCurSelItem, 0, LVIS_SELECTED | LVIS_FOCUSED); + m_pDownloadListCtrl->SetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + m_pDownloadListCtrl->SetSelectionMark(iItem); + m_pDownloadListCtrl->EnsureVisible(iItem, FALSE); + return STATIC_DOWNCAST(CObject, (CObject*)ctrl_item->value); + } + } + } + } + return NULL; +} + +CObject* CDownloadListListCtrlItemWalk::GetNextSelectableItem() +{ + ASSERT( m_pDownloadListCtrl != NULL ); + if (m_pDownloadListCtrl == NULL) + return NULL; + ASSERT( m_eItemType != (ItemType)-1 ); + + int iItemCount = m_pDownloadListCtrl->GetItemCount(); + if (iItemCount >= 2) + { + POSITION pos = m_pDownloadListCtrl->GetFirstSelectedItemPosition(); + if (pos) + { + int iItem = m_pDownloadListCtrl->GetNextSelectedItem(pos); + int iCurSelItem = iItem; + while (iItem+1 < iItemCount) + { + iItem++; + + const CtrlItem_Struct* ctrl_item = (CtrlItem_Struct*)m_pDownloadListCtrl->GetItemData(iItem); + if (ctrl_item != NULL && (ctrl_item->type == m_eItemType || (m_eItemType != FILE_TYPE && ctrl_item->type != FILE_TYPE))) + { + m_pDownloadListCtrl->SetItemState(iCurSelItem, 0, LVIS_SELECTED | LVIS_FOCUSED); + m_pDownloadListCtrl->SetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + m_pDownloadListCtrl->SetSelectionMark(iItem); + m_pDownloadListCtrl->EnsureVisible(iItem, FALSE); + return STATIC_DOWNCAST(CObject, (CObject*)ctrl_item->value); + } + } + } + } + return NULL; +} + +void CDownloadListCtrl::ShowClientDialog(CUpDownClient* pClient) +{ + CDownloadListListCtrlItemWalk::SetItemType(AVAILABLE_SOURCE); // just set to something !=FILE_TYPE + CClientDetailDialog dialog(pClient, this); + dialog.DoModal(); +} + +CImageList *CDownloadListCtrl::CreateDragImage(int /*iItem*/, LPPOINT lpPoint) +{ + const int iMaxSelectedItems = 30; + int iSelectedItems = 0; + CRect rcSelectedItems; + POSITION pos = GetFirstSelectedItemPosition(); + while (pos && iSelectedItems < iMaxSelectedItems) + { + int iItem = GetNextSelectedItem(pos); + const CtrlItem_Struct *pCtrlItem = (CtrlItem_Struct *)GetItemData(iItem); + if (pCtrlItem != NULL && pCtrlItem->type == FILE_TYPE) + { + CRect rcLabel; + GetItemRect(iItem, rcLabel, LVIR_LABEL); + if (iSelectedItems == 0) + { + rcSelectedItems.left = sm_iIconOffset; + rcSelectedItems.top = rcLabel.top; + rcSelectedItems.right = rcLabel.right; + rcSelectedItems.bottom = rcLabel.bottom; + } + rcSelectedItems.UnionRect(rcSelectedItems, rcLabel); + iSelectedItems++; + } + } + if (iSelectedItems == 0) + return NULL; + + CClientDC dc(this); + CDC dcMem; + if (!dcMem.CreateCompatibleDC(&dc)) + return NULL; + + CBitmap bmpMem; + if (!bmpMem.CreateCompatibleBitmap(&dc, rcSelectedItems.Width(), rcSelectedItems.Height())) + return NULL; + + CBitmap *pOldBmp = dcMem.SelectObject(&bmpMem); + CFont *pOldFont = dcMem.SelectObject(GetFont()); + + COLORREF crBackground = GetSysColor(COLOR_WINDOW); + dcMem.FillSolidRect(0, 0, rcSelectedItems.Width(), rcSelectedItems.Height(), crBackground); + dcMem.SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); + + iSelectedItems = 0; + pos = GetFirstSelectedItemPosition(); + while (pos && iSelectedItems < iMaxSelectedItems) + { + int iItem = GetNextSelectedItem(pos); + const CtrlItem_Struct *pCtrlItem = (CtrlItem_Struct *)GetItemData(iItem); + if (pCtrlItem && pCtrlItem->type == FILE_TYPE) + { + const CPartFile *pPartFile = (CPartFile *)pCtrlItem->value; + CRect rcLabel; + GetItemRect(iItem, rcLabel, LVIR_LABEL); + + CRect rcItem; + rcItem.left = 0; + rcItem.top = rcLabel.top - rcSelectedItems.top; + rcItem.right = rcLabel.right; + rcItem.bottom = rcItem.top + rcLabel.Height(); + + if (theApp.GetSystemImageList()) + { + int iImage = theApp.GetFileTypeSystemImageIdx(pPartFile->GetFileName()); + ImageList_Draw(theApp.GetSystemImageList(), iImage, dcMem, rcItem.left, rcItem.top, ILD_TRANSPARENT); + } + + rcItem.left += 16 + sm_iLabelOffset; + dcMem.DrawText(pPartFile->GetFileName(), -1, rcItem, MLC_DT_TEXT); + rcItem.left -= 16 + sm_iLabelOffset; + + iSelectedItems++; + } + } + dcMem.SelectObject(pOldBmp); + dcMem.SelectObject(pOldFont); + + // At this point the bitmap in 'bmpMem' may or may not contain alpha data and we have to take special + // care about passing such a bitmap further into Windows (GDI). Strange things can happen due to that + // not all GDI functions can deal with RGBA bitmaps. Thus, create an image list with ILC_COLORDDB. + CImageList *pimlDrag = new CImageList(); + pimlDrag->Create(rcSelectedItems.Width(), rcSelectedItems.Height(), ILC_COLORDDB | ILC_MASK, 1, 0); + pimlDrag->Add(&bmpMem, crBackground); + bmpMem.DeleteObject(); + + if (lpPoint) + { + CPoint ptCursor; + GetCursorPos(&ptCursor); + ScreenToClient(&ptCursor); + lpPoint->x = ptCursor.x - rcSelectedItems.left; + lpPoint->y = ptCursor.y - rcSelectedItems.top; + } + + return pimlDrag; +} + +bool CDownloadListCtrl::ReportAvailableCommands(CList& liAvailableCommands) +{ + if ((m_dwLastAvailableCommandsCheck > ::GetTickCount() - SEC2MS(3) && !m_availableCommandsDirty)) + return false; + m_dwLastAvailableCommandsCheck = ::GetTickCount(); + m_availableCommandsDirty = false; + + int iSel = GetNextItem(-1, LVIS_SELECTED); + if (iSel != -1) + { + const CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(iSel); + if (content != NULL && content->type == FILE_TYPE) + { + // get merged settings + int iSelectedItems = 0; + int iFilesNotDone = 0; + int iFilesToPause = 0; + int iFilesToStop = 0; + int iFilesToResume = 0; + int iFilesToOpen = 0; + int iFilesGetPreviewParts = 0; + int iFilesPreviewType = 0; + int iFilesToPreview = 0; + int iFilesToCancel = 0; + POSITION pos = GetFirstSelectedItemPosition(); + while (pos) + { + const CtrlItem_Struct* pItemData = (CtrlItem_Struct*)GetItemData(GetNextSelectedItem(pos)); + if (pItemData == NULL || pItemData->type != FILE_TYPE) + continue; + const CPartFile* pFile = (CPartFile*)pItemData->value; + iSelectedItems++; + + bool bFileDone = (pFile->GetStatus()==PS_COMPLETE || pFile->GetStatus()==PS_COMPLETING); + iFilesToCancel += pFile->GetStatus() != PS_COMPLETING ? 1 : 0; + iFilesNotDone += !bFileDone ? 1 : 0; + iFilesToStop += pFile->CanStopFile() ? 1 : 0; + iFilesToPause += pFile->CanPauseFile() ? 1 : 0; + iFilesToResume += pFile->CanResumeFile() ? 1 : 0; + iFilesToOpen += pFile->CanOpenFile() ? 1 : 0; + iFilesGetPreviewParts += pFile->GetPreviewPrio() ? 1 : 0; + iFilesPreviewType += pFile->IsPreviewableFileType() ? 1 : 0; + iFilesToPreview += pFile->IsReadyForPreview() ? 1 : 0; + } + + + // enable commands if there is at least one item which can be used for the action + if (iFilesToCancel > 0) + liAvailableCommands.AddTail(MP_CANCEL); + if (iFilesToStop > 0) + liAvailableCommands.AddTail(MP_STOP); + if (iFilesToPause > 0) + liAvailableCommands.AddTail(MP_PAUSE); + if (iFilesToResume > 0) + liAvailableCommands.AddTail(MP_RESUME); + if (iSelectedItems == 1 && iFilesToOpen == 1) + liAvailableCommands.AddTail(MP_OPEN); + if (iSelectedItems == 1 && iFilesToPreview == 1) + liAvailableCommands.AddTail(MP_PREVIEW); + if (iSelectedItems == 1) + liAvailableCommands.AddTail(MP_OPENFOLDER); + if (iSelectedItems > 0) + { + liAvailableCommands.AddTail(MP_METINFO); + liAvailableCommands.AddTail(MP_VIEWFILECOMMENTS); + liAvailableCommands.AddTail(MP_SHOWED2KLINK); + liAvailableCommands.AddTail(MP_NEWCAT); + liAvailableCommands.AddTail(MP_PRIOLOW); + if (theApp.emuledlg->searchwnd->CanSearchRelatedFiles()) + liAvailableCommands.AddTail(MP_SEARCHRELATED); + } + } + } + int total; + if (GetCompleteDownloads(curTab, total) > 0) + liAvailableCommands.AddTail(MP_CLEARCOMPLETED); + if (GetItemCount() > 0) + liAvailableCommands.AddTail(MP_FIND); + return true; +} diff --git a/DownloadListCtrl.h b/DownloadListCtrl.h new file mode 100644 index 00000000..c8014a7e --- /dev/null +++ b/DownloadListCtrl.h @@ -0,0 +1,159 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "MuleListCtrl.h" +#include "TitleMenu.h" +#include +#include "ListCtrlItemWalk.h" + +#define COLLAPSE_ONLY 0 +#define EXPAND_ONLY 1 +#define EXPAND_COLLAPSE 2 + +// Foward declaration +class CPartFile; +class CUpDownClient; +class CDownloadListCtrl; +class CToolTipCtrlX; + + +/////////////////////////////////////////////////////////////////////////////// +// CtrlItem_Struct + +enum ItemType {FILE_TYPE = 1, AVAILABLE_SOURCE = 2, UNAVAILABLE_SOURCE = 3}; + +class CtrlItem_Struct : public CObject +{ + DECLARE_DYNAMIC(CtrlItem_Struct) + +public: + ~CtrlItem_Struct() { status.DeleteObject(); } + + ItemType type; + CPartFile* owner; + void* value; // could be both CPartFile or CUpDownClient + CtrlItem_Struct* parent; + DWORD dwUpdated; + CBitmap status; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// CDownloadListListCtrlItemWalk + +class CDownloadListListCtrlItemWalk : public CListCtrlItemWalk +{ +public: + CDownloadListListCtrlItemWalk(CDownloadListCtrl* pListCtrl); + + virtual CObject* GetNextSelectableItem(); + virtual CObject* GetPrevSelectableItem(); + + void SetItemType(ItemType eItemType) { m_eItemType = eItemType; } + +protected: + CDownloadListCtrl* m_pDownloadListCtrl; + ItemType m_eItemType; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// CDownloadListCtrl + +class CDownloadListCtrl : public CMuleListCtrl, public CDownloadListListCtrlItemWalk +{ + DECLARE_DYNAMIC(CDownloadListCtrl) + friend class CDownloadListListCtrlItemWalk; + +public: + CDownloadListCtrl(); + virtual ~CDownloadListCtrl(); + + UINT curTab; + + void UpdateItem(void* toupdate); + void Init(); + void AddFile(CPartFile* toadd); + void AddSource(CPartFile* owner, CUpDownClient* source, bool notavailable); + void RemoveSource(CUpDownClient* source, CPartFile* owner); + bool RemoveFile(const CPartFile* toremove); + void ClearCompleted(int incat=-2); + void ClearCompleted(const CPartFile* pFile); + void SetStyle(); + void CreateMenues(); + void Localize(); + void ShowFilesCount(); + void ChangeCategory(int newsel); + CString getTextList(); + void ShowSelectedFileDetails(); + void HideFile(CPartFile* tohide); + void ShowFile(CPartFile* tohide); + void ExpandCollapseItem(int iItem, int iAction, bool bCollapseSource = false); + void HideSources(CPartFile* toCollapse); + void GetDisplayedFiles(CArray* list); + void MoveCompletedfilesCat(uint8 from, uint8 to); + int GetCompleteDownloads(int cat,int &total); + void UpdateCurrentCategoryView(); + void UpdateCurrentCategoryView(CPartFile* thisfile); + CImageList *CreateDragImage(int iItem, LPPOINT lpPoint); + void FillCatsMenu(CMenu& rCatsMenu, int iFilesInCats = (-1)); + CTitleMenu* GetPrioMenu(); + float GetFinishedSize(); + bool ReportAvailableCommands(CList& liAvailableCommands); + +protected: + CImageList m_ImageList; + CTitleMenu m_PrioMenu; + CTitleMenu m_FileMenu; + CTitleMenu m_PreviewMenu; + CMenu m_SourcesMenu; + bool m_bRemainSort; + typedef std::pair ListItemsPair; + typedef std::multimap ListItems; + ListItems m_ListItems; + CFont m_fontBold; // may contain a locally created bold font + CFont* m_pFontBold;// points to the bold font which is to be used (may be the locally created or the default bold font) + CToolTipCtrlX* m_tooltip; + uint32 m_dwLastAvailableCommandsCheck; + bool m_availableCommandsDirty; + + void ShowFileDialog(UINT uInvokePage); + void ShowClientDialog(CUpDownClient* pClient); + void SetAllIcons(); + void DrawFileItem(CDC *dc, int nColumn, LPCRECT lpRect, UINT uDrawTextAlignment, CtrlItem_Struct *pCtrlItem); + void DrawSourceItem(CDC *dc, int nColumn, LPCRECT lpRect, UINT uDrawTextAlignment, CtrlItem_Struct *pCtrlItem); + int GetFilesCountInCurCat(); + void GetFileItemDisplayText(CPartFile *lpPartFile, int iSubItem, LPTSTR pszText, int cchTextMax); + void GetSourceItemDisplayText(const CtrlItem_Struct *pCtrlItem, int iSubItem, LPTSTR pszText, int cchTextMax); + + static int CALLBACK SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + static int Compare(const CPartFile* file1, const CPartFile* file2, LPARAM lParamSort); + static int Compare(const CUpDownClient* client1, const CUpDownClient* client2, LPARAM lParamSort); + + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + + DECLARE_MESSAGE_MAP() + afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); + afx_msg void OnListModified(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnLvnGetInfoTip(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnItemActivate(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmDblClk(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnSysColorChange(); +}; diff --git a/DownloadQueue.cpp b/DownloadQueue.cpp new file mode 100644 index 00000000..beed4339 --- /dev/null +++ b/DownloadQueue.cpp @@ -0,0 +1,2005 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include +#include "emule.h" +#include "DownloadQueue.h" +#include "UpDownClient.h" +#include "PartFile.h" +#include "ed2kLink.h" +#include "SearchFile.h" +#include "ClientList.h" +#include "Statistics.h" +#include "SharedFileList.h" +#include "OtherFunctions.h" +#include "SafeFile.h" +#include "Sockets.h" +#include "ServerList.h" +#include "Server.h" +#include "Packets.h" +#include "Kademlia/Kademlia/Kademlia.h" +#include "kademlia/utils/uint128.h" +#include "ipfilter.h" +#include "emuledlg.h" +#include "TransferDlg.h" +#include "TaskbarNotifier.h" +#include "MenuCmds.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +CDownloadQueue::CDownloadQueue() +{ + filesrdy = 0; + datarate = 0; + cur_udpserver = 0; + lastfile = 0; + lastcheckdiskspacetime = 0; + lastudpsearchtime = 0; + lastudpstattime = 0; + SetLastKademliaFileRequest(); + udcounter = 0; + m_iSearchedServers = 0; + m_datarateMS=0; + m_nUDPFileReasks = 0; + m_nFailedUDPFileReasks = 0; + m_dwNextTCPSrcReq = 0; + m_cRequestsSentToServer = 0; + m_dwLastA4AFtime = 0; // ZZ:DownloadManager +} + +void CDownloadQueue::AddPartFilesToShare() +{ + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ) + { + CPartFile* cur_file = filelist.GetNext(pos); + if (cur_file->GetStatus(true) == PS_READY) + theApp.sharedfiles->SafeAddKFile(cur_file, true); + } +} + +void CDownloadQueue::Init(){ + // find all part files, read & hash them if needed and store into a list + CFileFind ff; + int count = 0; + + for (int i=0;iLoadPartFile(thePrefs.GetTempDir(i), ff.GetFileName()); + if (eResult == PLR_FAILED_METFILE_CORRUPT) + { + // .met file is corrupted, try to load the latest backup of this file + delete toadd; + toadd = new CPartFile(); + eResult = toadd->LoadPartFile(thePrefs.GetTempDir(i), ff.GetFileName() + PARTMET_BAK_EXT); + if (eResult == PLR_LOADSUCCESS) + { + toadd->SavePartFile(true); // don't override our just used .bak file yet + AddLogLine(false, GetResString(IDS_RECOVERED_PARTMET), toadd->GetFileName()); + } + } + + if (eResult == PLR_LOADSUCCESS) + { + count++; + filelist.AddTail(toadd); // to downloadqueue + if (toadd->GetStatus(true) == PS_READY) + theApp.sharedfiles->SafeAddKFile(toadd); // part files are always shared files + theApp.emuledlg->transferwnd->GetDownloadList()->AddFile(toadd);// show in downloadwindow + } + else + delete toadd; + } + ff.Close(); + + //try recovering any part.met files + searchPath += _T(".backup"); + end = !ff.FindFile(searchPath, 0); + while (!end){ + end = !ff.FindNextFile(); + if (ff.IsDirectory()) + continue; + CPartFile* toadd = new CPartFile(); + if (toadd->LoadPartFile(thePrefs.GetTempDir(i), ff.GetFileName()) == PLR_LOADSUCCESS){ + toadd->SavePartFile(true); // resave backup, don't overwrite existing bak files yet + count++; + filelist.AddTail(toadd); // to downloadqueue + if (toadd->GetStatus(true) == PS_READY) + theApp.sharedfiles->SafeAddKFile(toadd); // part files are always shared files + theApp.emuledlg->transferwnd->GetDownloadList()->AddFile(toadd);// show in downloadwindow + + AddLogLine(false, GetResString(IDS_RECOVERED_PARTMET), toadd->GetFileName()); + } + else { + delete toadd; + } + } + ff.Close(); + } + if(count == 0) { + AddLogLine(false,GetResString(IDS_NOPARTSFOUND)); + } else { + AddLogLine(false,GetResString(IDS_FOUNDPARTS),count); + SortByPriority(); + CheckDiskspace(); + } + VERIFY( m_srcwnd.CreateEx(0, AfxRegisterWndClass(0), _T("eMule Async DNS Resolve Socket Wnd #2"), WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL)); + + ExportPartMetFilesOverview(); +} + +CDownloadQueue::~CDownloadQueue(){ + for (POSITION pos = filelist.GetHeadPosition();pos != 0;) + delete filelist.GetNext(pos); + m_srcwnd.DestroyWindow(); // just to avoid a MFC warning +} + +void CDownloadQueue::AddSearchToDownload(CSearchFile* toadd, uint8 paused, int cat) +{ + if (toadd->GetFileSize()== (uint64)0 || IsFileExisting(toadd->GetFileHash())) + return; + + if (toadd->GetFileSize() > OLD_MAX_EMULE_FILE_SIZE && !thePrefs.CanFSHandleLargeFiles(cat)){ + LogError(LOG_STATUSBAR, GetResString(IDS_ERR_FSCANTHANDLEFILE)); + return; + } + + CPartFile* newfile = new CPartFile(toadd,cat); + if (newfile->GetStatus() == PS_ERROR){ + delete newfile; + return; + } + + if (paused == 2) + paused = (uint8)thePrefs.AddNewFilesPaused(); + AddDownload(newfile, (paused==1)); + + // If the search result is from OP_GLOBSEARCHRES there may also be a source + if (toadd->GetClientID() && toadd->GetClientPort()){ + CSafeMemFile sources(1+4+2); + try{ + sources.WriteUInt8(1); + sources.WriteUInt32(toadd->GetClientID()); + sources.WriteUInt16(toadd->GetClientPort()); + sources.SeekToBegin(); + newfile->AddSources(&sources, toadd->GetClientServerIP(), toadd->GetClientServerPort(), false); + } + catch(CFileException* error){ + ASSERT(0); + error->Delete(); + } + } + + // Add more sources which were found via global UDP search + const CSimpleArray& aClients = toadd->GetClients(); + for (int i = 0; i < aClients.GetSize(); i++){ + CSafeMemFile sources(1+4+2); + try{ + sources.WriteUInt8(1); + sources.WriteUInt32(aClients[i].m_nIP); + sources.WriteUInt16(aClients[i].m_nPort); + sources.SeekToBegin(); + newfile->AddSources(&sources,aClients[i].m_nServerIP, aClients[i].m_nServerPort, false); + } + catch(CFileException* error){ + ASSERT(0); + error->Delete(); + break; + } + } +} + +void CDownloadQueue::AddSearchToDownload(CString link, uint8 paused, int cat) +{ + CPartFile* newfile = new CPartFile(link, cat); + if (newfile->GetStatus() == PS_ERROR){ + delete newfile; + return; + } + + if (paused == 2) + paused = (uint8)thePrefs.AddNewFilesPaused(); + AddDownload(newfile, (paused==1)); +} + +void CDownloadQueue::StartNextFileIfPrefs(int cat) { + if (thePrefs.StartNextFile()) + StartNextFile((thePrefs.StartNextFile() > 1?cat:-1), (thePrefs.StartNextFile()!=3)); +} + +void CDownloadQueue::StartNextFile(int cat, bool force){ + + CPartFile* pfile = NULL; + CPartFile* cur_file ; + POSITION pos; + + if (cat != -1) { + // try to find in specified category + for (pos = filelist.GetHeadPosition();pos != 0;){ + cur_file = filelist.GetNext(pos); + if (cur_file->GetStatus()==PS_PAUSED && + ( + cur_file->GetCategory()==(UINT)cat || + cat==0 && thePrefs.GetCategory(0)->filter==0 && cur_file->GetCategory()>0 + ) && + CPartFile::RightFileHasHigherPrio(pfile, cur_file) + ) { + pfile = cur_file; + } + } + if (pfile == NULL && !force) + return; + } + + if(cat == -1 || pfile == NULL && force) { + for (pos = filelist.GetHeadPosition();pos != 0;){ + cur_file = filelist.GetNext(pos); + if (cur_file->GetStatus() == PS_PAUSED && + CPartFile::RightFileHasHigherPrio(pfile, cur_file)) + { + // pick first found matching file, since they are sorted in prio order with most important file first. + pfile = cur_file; + } + } + } + if (pfile) pfile->ResumeFile(); +} + +void CDownloadQueue::AddFileLinkToDownload(CED2KFileLink* pLink, int cat) +{ + CPartFile* newfile = new CPartFile(pLink, cat); + if (newfile->GetStatus() == PS_ERROR){ + delete newfile; + newfile=NULL; + } + else { + AddDownload(newfile,thePrefs.AddNewFilesPaused()); + } + + CPartFile* partfile = newfile; + if (partfile == NULL) + partfile = GetFileByID(pLink->GetHashKey()); + if (partfile) + { + // match the fileidentifier and only if the are the same add possible sources + CFileIdentifierSA tmpFileIdent(pLink->GetHashKey(), pLink->GetSize(), pLink->GetAICHHash(), pLink->HasValidAICHHash()); + if (partfile->GetFileIdentifier().CompareRelaxed(tmpFileIdent)) + { + if (pLink->HasValidSources()) + partfile->AddClientSources(pLink->SourcesList, 1, false); + if (!partfile->GetFileIdentifier().HasAICHHash() && tmpFileIdent.HasAICHHash()) + { + partfile->GetFileIdentifier().SetAICHHash(tmpFileIdent.GetAICHHash()); + partfile->GetAICHRecoveryHashSet()->SetMasterHash(tmpFileIdent.GetAICHHash(), AICH_VERIFIED); + partfile->GetAICHRecoveryHashSet()->FreeHashSet(); + + } + } + else + DebugLogWarning(_T("FileIdentifier mismatch when trying to add ed2k link to existing download - AICH Hash or Size might differ, no sources added. File: %s"), + partfile->GetFileName()); + } + + if (pLink->HasHostnameSources()) + { + POSITION pos = pLink->m_HostnameSourcesList.GetHeadPosition(); + while (pos != NULL) + { + const SUnresolvedHostname* pUnresHost = pLink->m_HostnameSourcesList.GetNext(pos); + m_srcwnd.AddToResolve(pLink->GetHashKey(), pUnresHost->strHostname, pUnresHost->nPort, pUnresHost->strURL); + } + } +} + +void CDownloadQueue::AddToResolved( CPartFile* pFile, SUnresolvedHostname* pUH ) +{ + if( pFile && pUH ) + m_srcwnd.AddToResolve( pFile->GetFileHash(), pUH->strHostname, pUH->nPort, pUH->strURL); +} + +void CDownloadQueue::AddDownload(CPartFile* newfile,bool paused) { + // Barry - Add in paused mode if required + if (paused) + newfile->PauseFile(); + + SetAutoCat(newfile);// HoaX_69 / Slugfiller: AutoCat + + filelist.AddTail(newfile); + SortByPriority(); + CheckDiskspace(); + theApp.emuledlg->transferwnd->GetDownloadList()->AddFile(newfile); + AddLogLine(true, GetResString(IDS_NEWDOWNLOAD), newfile->GetFileName()); + CString msgTemp; + msgTemp.Format(GetResString(IDS_NEWDOWNLOAD) + _T("\n"), newfile->GetFileName()); + theApp.emuledlg->ShowNotifier(msgTemp, TBN_DOWNLOADADDED); + ExportPartMetFilesOverview(); +} + +bool CDownloadQueue::IsFileExisting(const uchar* fileid, bool bLogWarnings) const +{ + const CKnownFile* file = theApp.sharedfiles->GetFileByID(fileid); + if (file){ + if (bLogWarnings){ + if (file->IsPartFile()) + LogWarning(LOG_STATUSBAR, GetResString(IDS_ERR_ALREADY_DOWNLOADING), file->GetFileName()); + else + LogWarning(LOG_STATUSBAR, GetResString(IDS_ERR_ALREADY_DOWNLOADED), file->GetFileName()); + } + return true; + } + else if ((file = GetFileByID(fileid)) != NULL){ + if (bLogWarnings) + LogWarning(LOG_STATUSBAR, GetResString(IDS_ERR_ALREADY_DOWNLOADING), file->GetFileName()); + return true; + } + return false; +} + +void CDownloadQueue::Process(){ + + ProcessLocalRequests(); // send src requests to local server + + uint32 downspeed = 0; + uint64 maxDownload = thePrefs.GetMaxDownloadInBytesPerSec(true); + if (maxDownload != UNLIMITED*1024 && datarate > 1500){ + downspeed = (UINT)((maxDownload*100)/(datarate+1)); + if (downspeed < 50) + downspeed = 50; + else if (downspeed > 200) + downspeed = 200; + } + + while(avarage_dr_list.GetCount()>0 && (GetTickCount() - avarage_dr_list.GetHead().timestamp > 10*1000) ) + m_datarateMS-=avarage_dr_list.RemoveHead().datalen; + + if (avarage_dr_list.GetCount()>1){ + datarate = (UINT)(m_datarateMS / avarage_dr_list.GetCount()); + } else { + datarate = 0; + } + + uint32 datarateX=0; + udcounter++; + + theStats.m_fGlobalDone = 0; + theStats.m_fGlobalSize = 0; + theStats.m_dwOverallStatus=0; + //filelist is already sorted by prio, therefore I removed all the extra loops.. + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + + // maintain global download stats + theStats.m_fGlobalDone += (uint64)cur_file->GetCompletedSize(); + theStats.m_fGlobalSize += (uint64)cur_file->GetFileSize(); + + if (cur_file->GetTransferringSrcCount()>0) + theStats.m_dwOverallStatus |= STATE_DOWNLOADING; + if (cur_file->GetStatus()==PS_ERROR) + theStats.m_dwOverallStatus |= STATE_ERROROUS; + + + if (cur_file->GetStatus() == PS_READY || cur_file->GetStatus() == PS_EMPTY){ + datarateX += cur_file->Process(downspeed, udcounter); + } + else{ + //This will make sure we don't keep old sources to paused and stoped files.. + cur_file->StopPausedFile(); + } + } + + TransferredData newitem = {datarateX, ::GetTickCount()}; + avarage_dr_list.AddTail(newitem); + m_datarateMS+=datarateX; + + if (udcounter == 5){ + if (theApp.serverconnect->IsUDPSocketAvailable()){ + if((!lastudpstattime) || (::GetTickCount() - lastudpstattime) > UDPSERVERSTATTIME){ + lastudpstattime = ::GetTickCount(); + theApp.serverlist->ServerStats(); + } + } + } + + if (udcounter == 10){ + udcounter = 0; + if (theApp.serverconnect->IsUDPSocketAvailable()){ + if ((!lastudpsearchtime) || (::GetTickCount() - lastudpsearchtime) > UDPSERVERREASKTIME) + SendNextUDPPacket(); + } + } + + CheckDiskspaceTimed(); + +// ZZ:DownloadManager --> + if((!m_dwLastA4AFtime) || (::GetTickCount() - m_dwLastA4AFtime) > MIN2MS(8)) { + theApp.clientlist->ProcessA4AFClients(); + m_dwLastA4AFtime = ::GetTickCount(); + } +// <-- ZZ:DownloadManager +} + +CPartFile* CDownloadQueue::GetFileByIndex(int index) const +{ + POSITION pos = filelist.FindIndex(index); + if (pos) + return filelist.GetAt(pos); + return NULL; +} + +CPartFile* CDownloadQueue::GetFileByID(const uchar* filehash) const +{ + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ) + { + CPartFile* cur_file = filelist.GetNext(pos); + if (!md4cmp(filehash, cur_file->GetFileHash())) + return cur_file; + } + return NULL; +} + +CPartFile* CDownloadQueue::GetFileByKadFileSearchID(uint32 id) const +{ + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ) + { + CPartFile* cur_file = filelist.GetNext(pos); + if (id == cur_file->GetKadFileSearchID()) + return cur_file; + } + return NULL; +} + +bool CDownloadQueue::IsPartFile(const CKnownFile* file) const +{ + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ) + { + if (file == filelist.GetNext(pos)) + return true; + } + return false; +} + +bool CDownloadQueue::CheckAndAddSource(CPartFile* sender,CUpDownClient* source){ + if (sender->IsStopped()){ + delete source; + return false; + } + + if (source->HasValidHash()) + { + if(!md4cmp(source->GetUserHash(), thePrefs.GetUserHash())) + { + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("Tried to add source with matching hash to your own.")); + delete source; + return false; + } + } + // filter sources which are known to be temporarily dead/useless + if (theApp.clientlist->m_globDeadSourceList.IsDeadSource(source) || sender->m_DeadSourceList.IsDeadSource(source)){ + //if (thePrefs.GetLogFilteredIPs()) + // AddDebugLogLine(DLP_DEFAULT, false, _T("Rejected source because it was found on the DeadSourcesList (%s) for file %s : %s") + // ,sender->m_DeadSourceList.IsDeadSource(source)? _T("Local") : _T("Global"), sender->GetFileName(), source->DbgGetClientInfo() ); + delete source; + return false; + } + + // filter sources which are incompatible with our encryption setting (one requires it, and the other one doesn't supports it) + if ( (source->RequiresCryptLayer() && (!thePrefs.IsClientCryptLayerSupported() || !source->HasValidHash())) || (thePrefs.IsClientCryptLayerRequired() && (!source->SupportsCryptLayer() || !source->HasValidHash()))) + { +#if defined(_DEBUG) || defined(_BETA) || defined(_DEVBUILD) + //if (thePrefs.GetDebugSourceExchange()) // TODO: Uncomment after testing + AddDebugLogLine(DLP_DEFAULT, false, _T("Rejected source because CryptLayer-Setting (Obfuscation) was incompatible for file %s : %s"), sender->GetFileName(), source->DbgGetClientInfo() ); +#endif + delete source; + return false; + } + + // "Filter LAN IPs" and/or "IPfilter" is not required here, because it was already done in parent functions + + // uses this only for temp. clients + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + for (POSITION pos2 = cur_file->srclist.GetHeadPosition();pos2 != 0; ){ + CUpDownClient* cur_client = cur_file->srclist.GetNext(pos2); + if (cur_client->Compare(source, true) || cur_client->Compare(source, false)){ + if (cur_file == sender){ // this file has already this source + delete source; + return false; + } + // set request for this source + if (cur_client->AddRequestForAnotherFile(sender)){ + theApp.emuledlg->transferwnd->GetDownloadList()->AddSource(sender,cur_client,true); + delete source; + if(cur_client->GetDownloadState() != DS_CONNECTED) { + cur_client->SwapToAnotherFile(_T("New A4AF source found. CDownloadQueue::CheckAndAddSource()"), false, false, false, NULL, true, false); // ZZ:DownloadManager + } + return false; + } + else{ + delete source; + return false; + } + } + } + } + //our new source is real new but maybe it is already uploading to us? + //if yes the known client will be attached to the var "source" + //and the old sourceclient will be deleted + if (theApp.clientlist->AttachToAlreadyKnown(&source,0)){ +#ifdef _DEBUG + if (thePrefs.GetVerbose() && source->GetRequestFile()){ + // if a client sent us wrong sources (sources for some other file for which we asked but which we are also + // downloading) we may get a little in trouble here when "moving" this source to some other partfile without + // further checks and updates. + if (md4cmp(source->GetRequestFile()->GetFileHash(), sender->GetFileHash()) != 0) + AddDebugLogLine(false, _T("*** CDownloadQueue::CheckAndAddSource -- added potential wrong source (%u)(diff. filehash) to file \"%s\""), source->GetUserIDHybrid(), sender->GetFileName()); + if (source->GetRequestFile()->GetPartCount() != 0 && source->GetRequestFile()->GetPartCount() != sender->GetPartCount()) + AddDebugLogLine(false, _T("*** CDownloadQueue::CheckAndAddSource -- added potential wrong source (%u)(diff. partcount) to file \"%s\""), source->GetUserIDHybrid(), sender->GetFileName()); + } +#endif + source->SetRequestFile(sender); + } + else{ + // here we know that the client instance 'source' is a new created client instance (see callers) + // which is therefor not already in the clientlist, we can avoid the check for duplicate client list entries + // when adding this client + theApp.clientlist->AddClient(source,true); + } + +#ifdef _DEBUG + if (thePrefs.GetVerbose() && source->GetPartCount()!=0 && source->GetPartCount()!=sender->GetPartCount()){ + DEBUG_ONLY(AddDebugLogLine(false, _T("*** CDownloadQueue::CheckAndAddSource -- New added source (%u, %s) had still value in partcount"), source->GetUserIDHybrid(), sender->GetFileName())); + } +#endif + + sender->srclist.AddTail(source); + theApp.emuledlg->transferwnd->GetDownloadList()->AddSource(sender,source,false); + return true; +} + +bool CDownloadQueue::CheckAndAddKnownSource(CPartFile* sender,CUpDownClient* source, bool bIgnoreGlobDeadList){ + if (sender->IsStopped()) + return false; + + // filter sources which are known to be temporarily dead/useless + if ( (theApp.clientlist->m_globDeadSourceList.IsDeadSource(source) && !bIgnoreGlobDeadList) || sender->m_DeadSourceList.IsDeadSource(source)){ + //if (thePrefs.GetLogFilteredIPs()) + // AddDebugLogLine(DLP_DEFAULT, false, _T("Rejected source because it was found on the DeadSourcesList (%s) for file %s : %s") + // ,sender->m_DeadSourceList.IsDeadSource(source)? _T("Local") : _T("Global"), sender->GetFileName(), source->DbgGetClientInfo() ); + return false; + } + + // filter sources which are incompatible with our encryption setting (one requires it, and the other one doesn't supports it) + if ( (source->RequiresCryptLayer() && (!thePrefs.IsClientCryptLayerSupported() || !source->HasValidHash())) || (thePrefs.IsClientCryptLayerRequired() && (!source->SupportsCryptLayer() || !source->HasValidHash()))) + { +#if defined(_DEBUG) || defined(_BETA) || defined(_DEVBUILD) + //if (thePrefs.GetDebugSourceExchange()) // TODO: Uncomment after testing + AddDebugLogLine(DLP_DEFAULT, false, _T("Rejected source because CryptLayer-Setting (Obfuscation) was incompatible for file %s : %s"), sender->GetFileName(), source->DbgGetClientInfo() ); +#endif + return false; + } + + // "Filter LAN IPs" -- this may be needed here in case we are connected to the internet and are also connected + // to a LAN and some client from within the LAN connected to us. Though this situation may be supported in future + // by adding that client to the source list and filtering that client's LAN IP when sending sources to + // a client within the internet. + // + // "IPfilter" is not needed here, because that "known" client was already IPfiltered when receiving OP_HELLO. + if (!source->HasLowID()){ + uint32 nClientIP = ntohl(source->GetUserIDHybrid()); + if (!IsGoodIP(nClientIP)){ // check for 0-IP, localhost and LAN addresses + //if (thePrefs.GetLogFilteredIPs()) + // AddDebugLogLine(false, _T("Ignored already known source with IP=%s"), ipstr(nClientIP)); + return false; + } + } + + // use this for client which are already know (downloading for example) + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + if (cur_file->srclist.Find(source)){ + if (cur_file == sender) + return false; + if (source->AddRequestForAnotherFile(sender)) + theApp.emuledlg->transferwnd->GetDownloadList()->AddSource(sender,source,true); + if(source->GetDownloadState() != DS_CONNECTED) { + source->SwapToAnotherFile(_T("New A4AF source found. CDownloadQueue::CheckAndAddKnownSource()"), false, false, false, NULL, true, false); // ZZ:DownloadManager + } + return false; + } + } +#ifdef _DEBUG + if (thePrefs.GetVerbose() && source->GetRequestFile()){ + // if a client sent us wrong sources (sources for some other file for which we asked but which we are also + // downloading) we may get a little in trouble here when "moving" this source to some other partfile without + // further checks and updates. + if (md4cmp(source->GetRequestFile()->GetFileHash(), sender->GetFileHash()) != 0) + AddDebugLogLine(false, _T("*** CDownloadQueue::CheckAndAddKnownSource -- added potential wrong source (%u)(diff. filehash) to file \"%s\""), source->GetUserIDHybrid(), sender->GetFileName()); + if (source->GetRequestFile()->GetPartCount() != 0 && source->GetRequestFile()->GetPartCount() != sender->GetPartCount()) + AddDebugLogLine(false, _T("*** CDownloadQueue::CheckAndAddKnownSource -- added potential wrong source (%u)(diff. partcount) to file \"%s\""), source->GetUserIDHybrid(), sender->GetFileName()); + } +#endif + source->SetRequestFile(sender); + sender->srclist.AddTail(source); + source->SetSourceFrom(SF_PASSIVE); + if (thePrefs.GetDebugSourceExchange()) + AddDebugLogLine(false, _T("SXRecv: Passively added source; %s, File=\"%s\""), source->DbgGetClientInfo(), sender->GetFileName()); +#ifdef _DEBUG + if (thePrefs.GetVerbose() && source->GetPartCount()!=0 && source->GetPartCount()!=sender->GetPartCount()){ + DEBUG_ONLY(AddDebugLogLine(false, _T("*** CDownloadQueue::CheckAndAddKnownSource -- New added source (%u, %s) had still value in partcount"), source->GetUserIDHybrid(), sender->GetFileName())); + } +#endif + + theApp.emuledlg->transferwnd->GetDownloadList()->AddSource(sender,source,false); + //UpdateDisplayedInfo(); + return true; +} + +bool CDownloadQueue::RemoveSource(CUpDownClient* toremove, bool bDoStatsUpdate) +{ + bool bRemovedSrcFromPartFile = false; + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + for (POSITION pos2 = cur_file->srclist.GetHeadPosition();pos2 != 0; cur_file->srclist.GetNext(pos2)){ + if (toremove == cur_file->srclist.GetAt(pos2)){ + cur_file->srclist.RemoveAt(pos2); + bRemovedSrcFromPartFile = true; + if ( bDoStatsUpdate ){ + cur_file->RemoveDownloadingSource(toremove); + cur_file->UpdatePartsInfo(); + } + break; + } + } + if ( bDoStatsUpdate ) + cur_file->UpdateAvailablePartsCount(); + } + + // remove this source on all files in the downloadqueue who link this source + // pretty slow but no way arround, maybe using a Map is better, but that's slower on other parts + POSITION pos3, pos4; + for(pos3 = toremove->m_OtherRequests_list.GetHeadPosition();(pos4=pos3)!=NULL;) + { + toremove->m_OtherRequests_list.GetNext(pos3); + POSITION pos5 = toremove->m_OtherRequests_list.GetAt(pos4)->A4AFsrclist.Find(toremove); + if(pos5) + { + toremove->m_OtherRequests_list.GetAt(pos4)->A4AFsrclist.RemoveAt(pos5); + theApp.emuledlg->transferwnd->GetDownloadList()->RemoveSource(toremove,toremove->m_OtherRequests_list.GetAt(pos4)); + toremove->m_OtherRequests_list.RemoveAt(pos4); + } + } + for(pos3 = toremove->m_OtherNoNeeded_list.GetHeadPosition();(pos4=pos3)!=NULL;) + { + toremove->m_OtherNoNeeded_list.GetNext(pos3); + POSITION pos5 = toremove->m_OtherNoNeeded_list.GetAt(pos4)->A4AFsrclist.Find(toremove); + if(pos5) + { + toremove->m_OtherNoNeeded_list.GetAt(pos4)->A4AFsrclist.RemoveAt(pos5); + theApp.emuledlg->transferwnd->GetDownloadList()->RemoveSource(toremove,toremove->m_OtherNoNeeded_list.GetAt(pos4)); + toremove->m_OtherNoNeeded_list.RemoveAt(pos4); + } + } + + if (bRemovedSrcFromPartFile && (toremove->HasFileRating() || !toremove->GetFileComment().IsEmpty())) + { + CPartFile* pFile = toremove->GetRequestFile(); + if(pFile) + pFile->UpdateFileRatingCommentAvail(); + } + + toremove->SetDownloadState(DS_NONE); + theApp.emuledlg->transferwnd->GetDownloadList()->RemoveSource(toremove,0); + toremove->SetRequestFile(NULL); + return bRemovedSrcFromPartFile; +} + +void CDownloadQueue::RemoveFile(CPartFile* toremove) +{ + RemoveLocalServerRequest(toremove); + + for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){ + if (toremove == filelist.GetAt(pos)){ + filelist.RemoveAt(pos); + break; + } + } + SortByPriority(); + CheckDiskspace(); + ExportPartMetFilesOverview(); +} + +void CDownloadQueue::DeleteAll(){ + POSITION pos; + for (pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + cur_file->srclist.RemoveAll(); + // Barry - Should also remove all requested blocks + // Don't worry about deleting the blocks, that gets handled + // when CUpDownClient is deleted in CClientList::DeleteAll() + cur_file->RemoveAllRequestedBlocks(); + } +} + +// Max. file IDs per UDP packet +// ---------------------------- +// 576 - 30 bytes of header (28 for UDP, 2 for "E3 9A" edonkey proto) = 546 bytes +// 546 / 16 = 34 +#define MAX_UDP_PACKET_DATA 510 +#define BYTES_PER_FILE_G1 16 +#define BYTES_PER_FILE_G2 20 +#define ADDITIONAL_BYTES_PER_LARGEFILE 8 + +#define MAX_REQUESTS_PER_SERVER 35 + +bool CDownloadQueue::IsMaxFilesPerUDPServerPacketReached(uint32 nFiles, uint32 nIncludedLargeFiles) const +{ + if (cur_udpserver && cur_udpserver->GetUDPFlags() & SRV_UDPFLG_EXT_GETSOURCES) + { + + const int nBytesPerNormalFile = ((cur_udpserver->GetUDPFlags() & SRV_UDPFLG_EXT_GETSOURCES2) > 0)? BYTES_PER_FILE_G2 : BYTES_PER_FILE_G1; + const int nUsedBytes = nFiles*nBytesPerNormalFile + nIncludedLargeFiles*ADDITIONAL_BYTES_PER_LARGEFILE; + if (nIncludedLargeFiles > 0){ + ASSERT( cur_udpserver->SupportsLargeFilesUDP() ); + ASSERT( cur_udpserver->GetUDPFlags() & SRV_UDPFLG_EXT_GETSOURCES2 ); + } + return (m_cRequestsSentToServer >= MAX_REQUESTS_PER_SERVER) || (nUsedBytes >= MAX_UDP_PACKET_DATA); + } + else{ + ASSERT( nIncludedLargeFiles == 0); + return nFiles != 0; + } +} + +bool CDownloadQueue::SendGlobGetSourcesUDPPacket(CSafeMemFile* data, bool bExt2Packet, uint32 nFiles, uint32 nIncludedLargeFiles) +{ + bool bSentPacket = false; + + if (cur_udpserver) + { +#ifdef _DEBUG + int iPacketSize = (int)data->GetLength(); +#endif + Packet packet(data); + data = NULL; + if (bExt2Packet){ + ASSERT( iPacketSize > 0 && (uint32)iPacketSize == nFiles*20 + nIncludedLargeFiles*8); + packet.opcode = OP_GLOBGETSOURCES2; + } + else{ + ASSERT( iPacketSize > 0 && (uint32)iPacketSize == nFiles*16 && nIncludedLargeFiles == 0); + packet.opcode = OP_GLOBGETSOURCES; + } + if (thePrefs.GetDebugServerUDPLevel() > 0) + Debug(_T(">>> Sending %s to server %-21s (%3u of %3u); FileIDs=%u(%u large)\n"), (packet.opcode == OP_GLOBGETSOURCES2) ? _T("OP__GlobGetSources2") : _T("OP__GlobGetSources1"), ipstr(cur_udpserver->GetAddress(), cur_udpserver->GetPort()), m_iSearchedServers + 1, theApp.serverlist->GetServerCount(), nFiles, nIncludedLargeFiles); + + theStats.AddUpDataOverheadServer(packet.size); + theApp.serverconnect->SendUDPPacket(&packet, cur_udpserver, false); + + m_cRequestsSentToServer += nFiles; + bSentPacket = true; + } + + return bSentPacket; +} + +bool CDownloadQueue::SendNextUDPPacket() +{ + if ( filelist.IsEmpty() + || !theApp.serverconnect->IsUDPSocketAvailable() + || !theApp.serverconnect->IsConnected() + || thePrefs.IsClientCryptLayerRequired()) // we cannot use sources received without userhash, so dont ask + return false; + + CServer* pConnectedServer = theApp.serverconnect->GetCurrentServer(); + if (pConnectedServer) + pConnectedServer = theApp.serverlist->GetServerByAddress(pConnectedServer->GetAddress(), pConnectedServer->GetPort()); + + if (!cur_udpserver) + { + while ((cur_udpserver = theApp.serverlist->GetSuccServer(cur_udpserver)) != NULL) { + if (cur_udpserver == pConnectedServer) + continue; + if (cur_udpserver->GetFailedCount() >= thePrefs.GetDeadServerRetries()) + continue; + break; + } + if (cur_udpserver == NULL) { + StopUDPRequests(); + return false; + } + m_cRequestsSentToServer = 0; + } + + bool bGetSources2Packet = (cur_udpserver->GetUDPFlags() & SRV_UDPFLG_EXT_GETSOURCES2) > 0; + bool bServerSupportsLargeFiles = cur_udpserver->SupportsLargeFilesUDP(); + + // loop until the packet is filled or a packet was sent + bool bSentPacket = false; + CSafeMemFile dataGlobGetSources(20); + int iFiles = 0; + int iLargeFiles = 0; + while (!IsMaxFilesPerUDPServerPacketReached(iFiles, iLargeFiles) && !bSentPacket) + { + // get next file to search sources for + CPartFile* nextfile = NULL; + while (!bSentPacket && !(nextfile && (nextfile->GetStatus() == PS_READY || nextfile->GetStatus() == PS_EMPTY))) + { + if (lastfile == NULL) // we just started the global source searching or have switched the server + { + // get first file to search sources for + nextfile = filelist.GetHead(); + lastfile = nextfile; + } + else + { + POSITION pos = filelist.Find(lastfile); + if (pos == 0) // the last file is no longer in the DL-list (may have been finished or canceld) + { + // get first file to search sources for + nextfile = filelist.GetHead(); + lastfile = nextfile; + } + else + { + filelist.GetNext(pos); + if (pos == 0) // finished asking the current server for all files + { + // if there are pending requests for the current server, send them + if (dataGlobGetSources.GetLength() > 0) + { + if (SendGlobGetSourcesUDPPacket(&dataGlobGetSources, bGetSources2Packet, iFiles, iLargeFiles)) + bSentPacket = true; + dataGlobGetSources.SetLength(0); + iFiles = 0; + iLargeFiles = 0; + } + + // get next server to ask + while ((cur_udpserver = theApp.serverlist->GetSuccServer(cur_udpserver)) != NULL) { + if (cur_udpserver == pConnectedServer) + continue; + if (cur_udpserver->GetFailedCount() >= thePrefs.GetDeadServerRetries()) + continue; + break; + } + m_cRequestsSentToServer = 0; + if (cur_udpserver == NULL) { + // finished asking all servers for all files + if (thePrefs.GetDebugServerUDPLevel() > 0 && thePrefs.GetDebugServerSourcesLevel() > 0) + Debug(_T("Finished UDP search processing for all servers (%u)\n"), theApp.serverlist->GetServerCount()); + StopUDPRequests(); + return false; // finished (processed all file & all servers) + } + m_iSearchedServers++; + + // if we already sent a packet, switch to the next file at next function call + if (bSentPacket){ + lastfile = NULL; + break; + } + + bGetSources2Packet = (cur_udpserver->GetUDPFlags() & SRV_UDPFLG_EXT_GETSOURCES2) > 0; + bServerSupportsLargeFiles = cur_udpserver->SupportsLargeFilesUDP(); + + // have selected a new server; get first file to search sources for + nextfile = filelist.GetHead(); + lastfile = nextfile; + } + else + { + nextfile = filelist.GetAt(pos); + lastfile = nextfile; + } + } + } + } + + if (!bSentPacket && nextfile && nextfile->GetSourceCount() < nextfile->GetMaxSourcePerFileUDP() && (bServerSupportsLargeFiles || !nextfile->IsLargeFile()) ) + { + if (bGetSources2Packet){ + if (nextfile->IsLargeFile()){ + // GETSOURCES2 Packet Large File ( *) + iLargeFiles++; + dataGlobGetSources.WriteHash16(nextfile->GetFileHash()); + dataGlobGetSources.WriteUInt32(0); + dataGlobGetSources.WriteUInt64(nextfile->GetFileSize()); + } + else{ + // GETSOURCES2 Packet ( *) + dataGlobGetSources.WriteHash16(nextfile->GetFileHash()); + dataGlobGetSources.WriteUInt32((uint32)(uint64)nextfile->GetFileSize()); + } + } + else{ + // GETSOURCES Packet ( *) + dataGlobGetSources.WriteHash16(nextfile->GetFileHash()); + } + iFiles++; + if (thePrefs.GetDebugServerUDPLevel() > 0 && thePrefs.GetDebugServerSourcesLevel() > 0) + Debug(_T(">>> Queued %s to server %-21s (%3u of %3u); Buff %u(%u)=%s\n"), bGetSources2Packet ? _T("OP__GlobGetSources2") : _T("OP__GlobGetSources1"), ipstr(cur_udpserver->GetAddress(), cur_udpserver->GetPort()), m_iSearchedServers + 1, theApp.serverlist->GetServerCount(), iFiles, iLargeFiles, DbgGetFileInfo(nextfile->GetFileHash())); + } + } + + ASSERT( dataGlobGetSources.GetLength() == 0 || !bSentPacket ); + + if (!bSentPacket && dataGlobGetSources.GetLength() > 0) + SendGlobGetSourcesUDPPacket(&dataGlobGetSources, bGetSources2Packet, iFiles, iLargeFiles); + + // send max 35 UDP request to one server per interval + // if we have more than 35 files, we rotate the list and use it as queue + if (m_cRequestsSentToServer >= MAX_REQUESTS_PER_SERVER) + { + if (thePrefs.GetDebugServerUDPLevel() > 0 && thePrefs.GetDebugServerSourcesLevel() > 0) + Debug(_T("Rotating file list\n")); + + // move the last 35 files to the head + if (filelist.GetCount() >= MAX_REQUESTS_PER_SERVER) { + for (int i = 0; i != MAX_REQUESTS_PER_SERVER; i++) + filelist.AddHead(filelist.RemoveTail()); + } + + // and next server + while ((cur_udpserver = theApp.serverlist->GetSuccServer(cur_udpserver)) != NULL) { + if (cur_udpserver == pConnectedServer) + continue; + if (cur_udpserver->GetFailedCount() >= thePrefs.GetDeadServerRetries()) + continue; + break; + } + m_cRequestsSentToServer = 0; + if (cur_udpserver == NULL) { + if (thePrefs.GetDebugServerUDPLevel() > 0 && thePrefs.GetDebugServerSourcesLevel() > 0) + Debug(_T("Finished UDP search processing for all servers (%u)\n"), theApp.serverlist->GetServerCount()); + StopUDPRequests(); + return false; // finished (processed all file & all servers) + } + m_iSearchedServers++; + lastfile = NULL; + } + + return true; +} + +void CDownloadQueue::StopUDPRequests() +{ + cur_udpserver = NULL; + lastudpsearchtime = ::GetTickCount(); + lastfile = NULL; + m_iSearchedServers = 0; +} + +bool CDownloadQueue::CompareParts(POSITION pos1, POSITION pos2){ + CPartFile* file1 = filelist.GetAt(pos1); + CPartFile* file2 = filelist.GetAt(pos2); + return CPartFile::RightFileHasHigherPrio(file1, file2); +} + +void CDownloadQueue::SwapParts(POSITION pos1, POSITION pos2){ + CPartFile* file1 = filelist.GetAt(pos1); + CPartFile* file2 = filelist.GetAt(pos2); + filelist.SetAt(pos1, file2); + filelist.SetAt(pos2, file1); +} + +void CDownloadQueue::HeapSort(UINT first, UINT last) +{ + UINT r; + POSITION pos1 = filelist.FindIndex(first); + for ( r = first; !(r & (UINT)INT_MIN) && (r<<1) < last; ){ + UINT r2 = (r<<1)+1; + POSITION pos2 = filelist.FindIndex(r2); + if (r2 != last){ + POSITION pos3 = pos2; + filelist.GetNext(pos3); + if (!CompareParts(pos2, pos3)){ + pos2 = pos3; + r2++; + } + } + if (!CompareParts(pos1, pos2)) { + SwapParts(pos1, pos2); + r = r2; + pos1 = pos2; + } + else + break; + } +} + +void CDownloadQueue::SortByPriority() +{ + UINT n = filelist.GetCount(); + if (!n) + return; + UINT i; + for ( i = n/2; i--; ) + HeapSort(i, n-1); + for ( i = n; --i; ){ + SwapParts(filelist.FindIndex(0), filelist.FindIndex(i)); + HeapSort(0, i-1); + } +} + +void CDownloadQueue::CheckDiskspaceTimed() +{ + if ((!lastcheckdiskspacetime) || (::GetTickCount() - lastcheckdiskspacetime) > DISKSPACERECHECKTIME) + CheckDiskspace(); +} + +void CDownloadQueue::CheckDiskspace(bool bNotEnoughSpaceLeft) +{ + lastcheckdiskspacetime = ::GetTickCount(); + + // sorting the list could be done here, but I prefer to "see" that function call in the calling functions. + //SortByPriority(); + + // If disabled, resume any previously paused files + if (!thePrefs.IsCheckDiskspaceEnabled()) + { + if (!bNotEnoughSpaceLeft) // avoid worse case, if we already had 'disk full' + { + for( POSITION pos1 = filelist.GetHeadPosition(); pos1 != NULL; ) + { + CPartFile* cur_file = filelist.GetNext(pos1); + switch(cur_file->GetStatus()) + { + case PS_PAUSED: + case PS_ERROR: + case PS_COMPLETING: + case PS_COMPLETE: + continue; + } + cur_file->ResumeFileInsufficient(); + } + } + return; + } + + uint64 nTotalAvailableSpaceMain = bNotEnoughSpaceLeft ? 0 : GetFreeDiskSpaceX(thePrefs.GetTempDir()); + + // 'bNotEnoughSpaceLeft' - avoid worse case, if we already had 'disk full' + if (thePrefs.GetMinFreeDiskSpace() == 0) + { + for( POSITION pos1 = filelist.GetHeadPosition(); pos1 != NULL; ) + { + CPartFile* cur_file = filelist.GetNext(pos1); + + uint64 nTotalAvailableSpace = bNotEnoughSpaceLeft ? 0 : + ((thePrefs.GetTempDirCount()==1)?nTotalAvailableSpaceMain:GetFreeDiskSpaceX(cur_file->GetTempPath())); + + switch(cur_file->GetStatus()) + { + case PS_PAUSED: + case PS_ERROR: + case PS_COMPLETING: + case PS_COMPLETE: + continue; + } + + // Pause the file only if it would grow in size and would exceed the currently available free space + uint64 nSpaceToGo = cur_file->GetNeededSpace(); + if (nSpaceToGo <= nTotalAvailableSpace) + { + nTotalAvailableSpace -= nSpaceToGo; + cur_file->ResumeFileInsufficient(); + } + else + cur_file->PauseFile(true/*bInsufficient*/); + } + } + else + { + for( POSITION pos1 = filelist.GetHeadPosition(); pos1 != NULL; ) + { + CPartFile* cur_file = filelist.GetNext(pos1); + switch(cur_file->GetStatus()) + { + case PS_PAUSED: + case PS_ERROR: + case PS_COMPLETING: + case PS_COMPLETE: + continue; + } + + uint64 nTotalAvailableSpace = bNotEnoughSpaceLeft ? 0 : + ((thePrefs.GetTempDirCount()==1)?nTotalAvailableSpaceMain:GetFreeDiskSpaceX(cur_file->GetTempPath())); + if (nTotalAvailableSpace < thePrefs.GetMinFreeDiskSpace()) + { + if (cur_file->IsNormalFile()) + { + // Normal files: pause the file only if it would still grow + uint64 nSpaceToGrow = cur_file->GetNeededSpace(); + if (nSpaceToGrow > 0) + cur_file->PauseFile(true/*bInsufficient*/); + } + else + { + // Compressed/sparse files: always pause the file + cur_file->PauseFile(true/*bInsufficient*/); + } + } + else + { + // doesn't work this way. resuming the file without checking if there is a chance to successfully + // flush any available buffered file data will pause the file right after it was resumed and disturb + // the StopPausedFile function. + //cur_file->ResumeFileInsufficient(); + } + } + } +} + +void CDownloadQueue::GetDownloadSourcesStats(SDownloadStats& results) +{ + memset(&results, 0, sizeof results); + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ) + { + const CPartFile* cur_file = filelist.GetNext(pos); + + results.a[0] += cur_file->GetSourceCount(); + results.a[1] += cur_file->GetTransferringSrcCount(); + results.a[2] += cur_file->GetSrcStatisticsValue(DS_ONQUEUE); + results.a[3] += cur_file->GetSrcStatisticsValue(DS_REMOTEQUEUEFULL); + results.a[4] += cur_file->GetSrcStatisticsValue(DS_NONEEDEDPARTS); + results.a[5] += cur_file->GetSrcStatisticsValue(DS_CONNECTED); + results.a[6] += cur_file->GetSrcStatisticsValue(DS_REQHASHSET); + results.a[7] += cur_file->GetSrcStatisticsValue(DS_CONNECTING); + results.a[8] += cur_file->GetSrcStatisticsValue(DS_WAITCALLBACK); + results.a[8] += cur_file->GetSrcStatisticsValue(DS_WAITCALLBACKKAD); + results.a[9] += cur_file->GetSrcStatisticsValue(DS_TOOMANYCONNS); + results.a[9] += cur_file->GetSrcStatisticsValue(DS_TOOMANYCONNSKAD); + results.a[10] += cur_file->GetSrcStatisticsValue(DS_LOWTOLOWIP); + results.a[11] += cur_file->GetSrcStatisticsValue(DS_NONE); + results.a[12] += cur_file->GetSrcStatisticsValue(DS_ERROR); + results.a[13] += cur_file->GetSrcStatisticsValue(DS_BANNED); + results.a[14] += cur_file->src_stats[3]; + results.a[15] += cur_file->GetSrcA4AFCount(); + results.a[16] += cur_file->src_stats[0]; + results.a[17] += cur_file->src_stats[1]; + results.a[18] += cur_file->src_stats[2]; + results.a[19] += cur_file->net_stats[0]; + results.a[20] += cur_file->net_stats[1]; + results.a[21] += cur_file->net_stats[2]; + results.a[22] += cur_file->m_DeadSourceList.GetDeadSourcesCount(); + } +} + +CUpDownClient* CDownloadQueue::GetDownloadClientByIP(uint32 dwIP){ + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + for (POSITION pos2 = cur_file->srclist.GetHeadPosition();pos2 != 0; ){ + CUpDownClient* cur_client = cur_file->srclist.GetNext(pos2); + if (dwIP == cur_client->GetIP()){ + return cur_client; + } + } + } + return NULL; +} + +CUpDownClient* CDownloadQueue::GetDownloadClientByIP_UDP(uint32 dwIP, uint16 nUDPPort, bool bIgnorePortOnUniqueIP, bool* pbMultipleIPs){ + CUpDownClient* pMatchingIPClient = NULL; + uint32 cMatches = 0; + + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + for (POSITION pos2 = cur_file->srclist.GetHeadPosition();pos2 != 0;){ + CUpDownClient* cur_client = cur_file->srclist.GetNext(pos2); + if (dwIP == cur_client->GetIP() && nUDPPort == cur_client->GetUDPPort()){ + return cur_client; + } + else if (dwIP == cur_client->GetIP() && bIgnorePortOnUniqueIP && cur_client != pMatchingIPClient){ + pMatchingIPClient = cur_client; + cMatches++; + } + } + } + if (pbMultipleIPs != NULL) + *pbMultipleIPs = cMatches > 1; + + if (pMatchingIPClient != NULL && cMatches == 1) + return pMatchingIPClient; + else + return NULL; +} + +bool CDownloadQueue::IsInList(const CUpDownClient* client) const +{ + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + for (POSITION pos2 = cur_file->srclist.GetHeadPosition();pos2 != 0;){ + if (cur_file->srclist.GetNext(pos2) == client) + return true; + } + } + return false; +} + +void CDownloadQueue::ResetCatParts(UINT cat) +{ + CPartFile* cur_file; + + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ){ + cur_file = filelist.GetNext(pos); + + if (cur_file->GetCategory() == cat) + cur_file->SetCategory(0); + else if (cur_file->GetCategory() > cat) + cur_file->SetCategory(cur_file->GetCategory() - 1); + } +} + +void CDownloadQueue::SetCatPrio(UINT cat, uint8 newprio) +{ + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ){ + CPartFile* cur_file = filelist.GetNext(pos); + if (cat==0 || cur_file->GetCategory()==cat) + if (newprio==PR_AUTO) { + cur_file->SetAutoDownPriority(true); + cur_file->SetDownPriority(PR_HIGH, false); + } + else { + cur_file->SetAutoDownPriority(false); + cur_file->SetDownPriority(newprio, false); + } + } + + theApp.downloadqueue->SortByPriority(); + theApp.downloadqueue->CheckDiskspaceTimed(); +} + +// ZZ:DownloadManager --> +void CDownloadQueue::RemoveAutoPrioInCat(UINT cat, uint8 newprio){ + CPartFile* cur_file; + for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){ + cur_file = filelist.GetAt(pos); + if (cur_file->IsAutoDownPriority() && (cat==0 || cur_file->GetCategory()==cat)) { + cur_file->SetAutoDownPriority(false); + cur_file->SetDownPriority(newprio, false); + } + } + + theApp.downloadqueue->SortByPriority(); + theApp.downloadqueue->CheckDiskspaceTimed(); +} +// <-- ZZ:DownloadManager + +void CDownloadQueue::SetCatStatus(UINT cat, int newstatus) +{ + bool reset = false; + bool resort = false; + + POSITION pos= filelist.GetHeadPosition(); + while (pos != 0) + { + CPartFile* cur_file = filelist.GetNext(pos); + if (!cur_file) + continue; + + if (cat==-1 || + (cat==-2 && cur_file->GetCategory()==0) || + (cat==0 && cur_file->CheckShowItemInGivenCat(cat)) || + (cat>0 && cat==cur_file->GetCategory())) + { + switch (newstatus){ + case MP_CANCEL: + cur_file->DeleteFile(); + reset = true; + break; + case MP_PAUSE: + cur_file->PauseFile(false, false); + resort = true; + break; + case MP_STOP: + cur_file->StopFile(false, false); + resort = true; + break; + case MP_RESUME: + if (cur_file->CanResumeFile()){ + if (cur_file->GetStatus() == PS_INSUFFICIENT) + cur_file->ResumeFileInsufficient(); + else { + cur_file->ResumeFile(false); + resort = true; + } + } + break; + } + } + if (reset) + { + reset = false; + pos = filelist.GetHeadPosition(); + } + } + + if(resort) { + theApp.downloadqueue->SortByPriority(); + theApp.downloadqueue->CheckDiskspace(); + } +} + +void CDownloadQueue::MoveCat(UINT from, UINT to) +{ + if (from < to) + --to; + + POSITION pos= filelist.GetHeadPosition(); + while (pos != 0) + { + CPartFile* cur_file = filelist.GetNext(pos); + if (!cur_file) + continue; + + UINT mycat = cur_file->GetCategory(); + if ((mycat>=min(from,to) && mycat<=max(from,to))) + { + //if ((fromto)) || (from>to && (mycat>from || mycatSetCategory(to); + else{ + if (from < to) + cur_file->SetCategory(mycat - 1); + else + cur_file->SetCategory(mycat + 1); + } + } + } +} + +UINT CDownloadQueue::GetDownloadingFileCount() const +{ + UINT result = 0; + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + UINT uStatus = filelist.GetNext(pos)->GetStatus(); + if (uStatus == PS_READY || uStatus == PS_EMPTY) + result++; + } + return result; +} + +UINT CDownloadQueue::GetPausedFileCount() const +{ + UINT result = 0; + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + if (filelist.GetNext(pos)->GetStatus() == PS_PAUSED) + result++; + } + return result; +} + +void CDownloadQueue::SetAutoCat(CPartFile* newfile){ + if(thePrefs.GetCatCount()==1) + return; + CString catExt; + + for (int ix=1;ixautocat; + if (catExt.IsEmpty()) + continue; + + if (!thePrefs.GetCategory(ix)->ac_regexpeval) { + // simple string comparison + + int curPos = 0; + catExt.MakeLower(); + + CString fullname = newfile->GetFileName(); + fullname.MakeLower(); + CString cmpExt = catExt.Tokenize(_T("|"), curPos); + + while (!cmpExt.IsEmpty()) { + // HoaX_69: Allow wildcards in autocat string + // thanks to: bluecow, khaos and SlugFiller + if(cmpExt.Find(_T("*")) != -1 || cmpExt.Find(_T("?")) != -1){ + // Use wildcards + if(PathMatchSpec(fullname, cmpExt)){ + newfile->SetCategory(ix); + return; + } + }else{ + if(fullname.Find(cmpExt) != -1){ + newfile->SetCategory(ix); + return; + } + } + cmpExt = catExt.Tokenize(_T("|"),curPos); + } + } else { + // regular expression evaluation + if (RegularExpressionMatch(catExt,newfile->GetFileName())) + newfile->SetCategory(ix); + } + } +} + +void CDownloadQueue::ResetLocalServerRequests() +{ + m_dwNextTCPSrcReq = 0; + m_localServerReqQueue.RemoveAll(); + + POSITION pos = filelist.GetHeadPosition(); + while (pos != NULL) + { + CPartFile* pFile = filelist.GetNext(pos); + UINT uState = pFile->GetStatus(); + if (uState == PS_READY || uState == PS_EMPTY) + pFile->ResumeFile(); + pFile->m_bLocalSrcReqQueued = false; + } +} + +void CDownloadQueue::RemoveLocalServerRequest(CPartFile* pFile) +{ + POSITION pos1, pos2; + for( pos1 = m_localServerReqQueue.GetHeadPosition(); ( pos2 = pos1 ) != NULL; ) + { + m_localServerReqQueue.GetNext(pos1); + if (m_localServerReqQueue.GetAt(pos2) == pFile) + { + m_localServerReqQueue.RemoveAt(pos2); + pFile->m_bLocalSrcReqQueued = false; + // could 'break' here.. fail safe: go through entire list.. + } + } +} + +void CDownloadQueue::ProcessLocalRequests() +{ + if ( (!m_localServerReqQueue.IsEmpty()) && (m_dwNextTCPSrcReq < ::GetTickCount()) ) + { + CSafeMemFile dataTcpFrame(22); + const int iMaxFilesPerTcpFrame = 15; + int iFiles = 0; + while (!m_localServerReqQueue.IsEmpty() && iFiles < iMaxFilesPerTcpFrame) + { + // find the file with the longest waitingtime + POSITION pos1, pos2; + uint32 dwBestWaitTime = 0xFFFFFFFF; + POSITION posNextRequest = NULL; + CPartFile* cur_file; + for( pos1 = m_localServerReqQueue.GetHeadPosition(); ( pos2 = pos1 ) != NULL; ){ + m_localServerReqQueue.GetNext(pos1); + cur_file = m_localServerReqQueue.GetAt(pos2); + if (cur_file->GetStatus() == PS_READY || cur_file->GetStatus() == PS_EMPTY) + { + uint8 nPriority = cur_file->GetDownPriority(); + if (nPriority > PR_HIGH){ + ASSERT(0); + nPriority = PR_HIGH; + } + + if (cur_file->m_LastSearchTime + (PR_HIGH-nPriority) < dwBestWaitTime ){ + dwBestWaitTime = cur_file->m_LastSearchTime + (PR_HIGH-nPriority); + posNextRequest = pos2; + } + } + else{ + m_localServerReqQueue.RemoveAt(pos2); + cur_file->m_bLocalSrcReqQueued = false; + if (thePrefs.GetDebugSourceExchange()) + AddDebugLogLine(false, _T("SXSend: Local server source request for file \"%s\" not sent because of status '%s'"), cur_file->GetFileName(), cur_file->getPartfileStatus()); + } + } + + if (posNextRequest != NULL) + { + cur_file = m_localServerReqQueue.GetAt(posNextRequest); + cur_file->m_bLocalSrcReqQueued = false; + cur_file->m_LastSearchTime = ::GetTickCount(); + m_localServerReqQueue.RemoveAt(posNextRequest); + + if (cur_file->IsLargeFile() && (theApp.serverconnect->GetCurrentServer() == NULL || !theApp.serverconnect->GetCurrentServer()->SupportsLargeFilesTCP())){ + ASSERT( false ); + DebugLogError(_T("Large file (%s) on local requestqueue for server without support for large files"), cur_file->GetFileName()); + continue; + } + + iFiles++; + + // create request packet + CSafeMemFile smPacket; + smPacket.WriteHash16(cur_file->GetFileHash()); + if (!cur_file->IsLargeFile()){ + smPacket.WriteUInt32((uint32)(uint64)cur_file->GetFileSize()); + } + else{ + smPacket.WriteUInt32(0); // indicates that this is a large file and a uint64 follows + smPacket.WriteUInt64(cur_file->GetFileSize()); + } + + uint8 byOpcode = 0; + if (thePrefs.IsClientCryptLayerSupported() && theApp.serverconnect->GetCurrentServer() != NULL && theApp.serverconnect->GetCurrentServer()->SupportsGetSourcesObfuscation()) + byOpcode = OP_GETSOURCES_OBFU; + else + byOpcode = OP_GETSOURCES; + + Packet* packet = new Packet(&smPacket, OP_EDONKEYPROT, byOpcode); + if (thePrefs.GetDebugServerTCPLevel() > 0) + Debug(_T(">>> Sending OP__GetSources%s(%2u/%2u); %s\n"), (byOpcode == OP_GETSOURCES) ? _T("") : _T("_OBFU"), iFiles, iMaxFilesPerTcpFrame, DbgGetFileInfo(cur_file->GetFileHash())); + dataTcpFrame.Write(packet->GetPacket(), packet->GetRealPacketSize()); + delete packet; + + if (thePrefs.GetDebugSourceExchange()) + AddDebugLogLine(false, _T("SXSend: Local server source request; File=\"%s\""), cur_file->GetFileName()); + } + } + + int iSize = (int)dataTcpFrame.GetLength(); + if (iSize > 0) + { + // create one 'packet' which contains all buffered OP_GETSOURCES eD2K packets to be sent with one TCP frame + // server credits: 16*iMaxFilesPerTcpFrame+1 = 241 + Packet* packet = new Packet(new char[iSize], (UINT)dataTcpFrame.GetLength(), true, false); + dataTcpFrame.Seek(0, CFile::begin); + dataTcpFrame.Read(packet->GetPacket(), iSize); + theStats.AddUpDataOverheadServer(packet->size); + theApp.serverconnect->SendPacket(packet, true); + } + + // next TCP frame with up to 15 source requests is allowed to be sent in.. + m_dwNextTCPSrcReq = ::GetTickCount() + SEC2MS(iMaxFilesPerTcpFrame*(16+4)); + } +} + +void CDownloadQueue::SendLocalSrcRequest(CPartFile* sender){ + ASSERT ( !m_localServerReqQueue.Find(sender) ); + m_localServerReqQueue.AddTail(sender); +} + +int CDownloadQueue::GetDownloadFilesStats(uint64 &rui64TotalFileSize, + uint64 &rui64TotalLeftToTransfer, + uint64 &rui64TotalAdditionalNeededSpace) +{ + int iActiveFiles = 0; + for (POSITION pos = filelist.GetHeadPosition();pos != 0; ) + { + const CPartFile* cur_file = filelist.GetNext(pos); + UINT uState = cur_file->GetStatus(); + if (uState == PS_READY || uState == PS_EMPTY) + { + uint64 ui64LeftToTransfer = 0; + uint64 ui64AdditionalNeededSpace = 0; + cur_file->GetLeftToTransferAndAdditionalNeededSpace(ui64LeftToTransfer, ui64AdditionalNeededSpace); + rui64TotalFileSize += (uint64)cur_file->GetFileSize(); + rui64TotalLeftToTransfer += ui64LeftToTransfer; + rui64TotalAdditionalNeededSpace += ui64AdditionalNeededSpace; + iActiveFiles++; + } + } + return iActiveFiles; +} + +/////////////////////////////////////////////////////////////////////////////// +// CSourceHostnameResolveWnd + +#define WM_HOSTNAMERESOLVED (WM_USER + 0x101) // does not need to be placed in "UserMsgs.h" + +BEGIN_MESSAGE_MAP(CSourceHostnameResolveWnd, CWnd) + ON_MESSAGE(WM_HOSTNAMERESOLVED, OnHostnameResolved) +END_MESSAGE_MAP() + +CSourceHostnameResolveWnd::CSourceHostnameResolveWnd() +{ +} + +CSourceHostnameResolveWnd::~CSourceHostnameResolveWnd() +{ + while (!m_toresolve.IsEmpty()) + delete m_toresolve.RemoveHead(); +} + +void CSourceHostnameResolveWnd::AddToResolve(const uchar* fileid, LPCSTR pszHostname, uint16 port, LPCTSTR pszURL) +{ + bool bResolving = !m_toresolve.IsEmpty(); + + // double checking + if (!theApp.downloadqueue->GetFileByID(fileid)) + return; + + Hostname_Entry* entry = new Hostname_Entry; + md4cpy(entry->fileid, fileid); + entry->strHostname = pszHostname; + entry->port = port; + entry->strURL = pszURL; + m_toresolve.AddTail(entry); + + if (bResolving) + return; + + memset(m_aucHostnameBuffer, 0, sizeof(m_aucHostnameBuffer)); + if (WSAAsyncGetHostByName(m_hWnd, WM_HOSTNAMERESOLVED, entry->strHostname, m_aucHostnameBuffer, sizeof m_aucHostnameBuffer) != 0) + return; + m_toresolve.RemoveHead(); + delete entry; +} + +LRESULT CSourceHostnameResolveWnd::OnHostnameResolved(WPARAM /*wParam*/, LPARAM lParam) +{ + if (m_toresolve.IsEmpty()) + return TRUE; + Hostname_Entry* resolved = m_toresolve.RemoveHead(); + if (WSAGETASYNCERROR(lParam) == 0) + { + int iBufLen = WSAGETASYNCBUFLEN(lParam); + if (iBufLen >= sizeof(HOSTENT)) + { + LPHOSTENT pHost = (LPHOSTENT)m_aucHostnameBuffer; + if (pHost->h_length == 4 && pHost->h_addr_list && pHost->h_addr_list[0]) + { + uint32 nIP = ((LPIN_ADDR)(pHost->h_addr_list[0]))->s_addr; + + CPartFile* file = theApp.downloadqueue->GetFileByID(resolved->fileid); + if (file) + { + if (resolved->strURL.IsEmpty()) + { + CSafeMemFile sources(1+4+2); + sources.WriteUInt8(1); + sources.WriteUInt32(nIP); + sources.WriteUInt16(resolved->port); + sources.SeekToBegin(); + file->AddSources(&sources,0,0, false); + } + else + { + file->AddSource(resolved->strURL, nIP); + } + } + } + } + } + delete resolved; + + while (!m_toresolve.IsEmpty()) + { + Hostname_Entry* entry = m_toresolve.GetHead(); + memset(m_aucHostnameBuffer, 0, sizeof(m_aucHostnameBuffer)); + if (WSAAsyncGetHostByName(m_hWnd, WM_HOSTNAMERESOLVED, entry->strHostname, m_aucHostnameBuffer, sizeof m_aucHostnameBuffer) != 0) + return TRUE; + m_toresolve.RemoveHead(); + delete entry; + } + return TRUE; +} + +bool CDownloadQueue::DoKademliaFileRequest() +{ + return ((::GetTickCount() - lastkademliafilerequest) > KADEMLIAASKTIME); +} + +void CDownloadQueue::KademliaSearchFile(uint32 searchID, const Kademlia::CUInt128* pcontactID, const Kademlia::CUInt128* pbuddyID, uint8 type, uint32 ip, uint16 tcp, uint16 udp, uint32 dwBuddyIP, uint16 dwBuddyPort, uint8 byCryptOptions) +{ + //Safty measure to make sure we are looking for these sources + CPartFile* temp = GetFileByKadFileSearchID(searchID); + if( !temp ) + return; + //Do we need more sources? + if(!(!temp->IsStopped() && temp->GetMaxSources() > temp->GetSourceCount())) + return; + + uint32 ED2Kip = ntohl(ip); + if (theApp.ipfilter->IsFiltered(ED2Kip)) + { + if (thePrefs.GetLogFilteredIPs()) + AddDebugLogLine(false, _T("IPfiltered source IP=%s (%s) received from Kademlia"), ipstr(ED2Kip), theApp.ipfilter->GetLastHit()); + return; + } + if( (ip == Kademlia::CKademlia::GetIPAddress() || ED2Kip == theApp.serverconnect->GetClientID()) && tcp == thePrefs.GetPort()) + return; + CUpDownClient* ctemp = NULL; + //DEBUG_ONLY( DebugLog(_T("Kadsource received, type %u, IP %s"), type, ipstr(ED2Kip)) ); + switch( type ) + { + case 4: + case 1: + { + //NonFirewalled users + if(!tcp) + { + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("Ignored source (IP=%s) received from Kademlia, no tcp port received"), ipstr(ip)); + return; + } + ctemp = new CUpDownClient(temp,tcp,ip,0,0,false); + ctemp->SetSourceFrom(SF_KADEMLIA); + // not actually sent or needed for HighID sources + //ctemp->SetServerIP(serverip); + //ctemp->SetServerPort(serverport); + ctemp->SetKadPort(udp); + byte cID[16]; + pcontactID->ToByteArray(cID); + ctemp->SetUserHash(cID); + break; + } + case 2: + { + //Don't use this type... Some clients will process it wrong.. + break; + } + case 5: + case 3: + { + //This will be a firewaled client connected to Kad only. + // if we are firewalled ourself, the source is useless to us + if (theApp.IsFirewalled()) + break; + + //We set the clientID to 1 as a Kad user only has 1 buddy. + ctemp = new CUpDownClient(temp,tcp,1,0,0,false); + //The only reason we set the real IP is for when we get a callback + //from this firewalled source, the compare method will match them. + ctemp->SetSourceFrom(SF_KADEMLIA); + ctemp->SetKadPort(udp); + byte cID[16]; + pcontactID->ToByteArray(cID); + ctemp->SetUserHash(cID); + pbuddyID->ToByteArray(cID); + ctemp->SetBuddyID(cID); + ctemp->SetBuddyIP(dwBuddyIP); + ctemp->SetBuddyPort(dwBuddyPort); + break; + } + case 6: + { + // firewalled source which supports direct udp callback + // if we are firewalled ourself, the source is useless to us + if (theApp.IsFirewalled()) + break; + + if ((byCryptOptions & 0x08) == 0){ + DebugLogWarning(_T("Received Kad source type 6 (direct callback) which has the direct callback flag not set (%s)"), ipstr(ED2Kip)); + break; + } + ctemp = new CUpDownClient(temp, tcp, 1, 0, 0, false); + ctemp->SetSourceFrom(SF_KADEMLIA); + ctemp->SetKadPort(udp); + ctemp->SetIP(ED2Kip); // need to set the Ip address, which cannot be used for TCP but for UDP + byte cID[16]; + pcontactID->ToByteArray(cID); + ctemp->SetUserHash(cID); + } + } + + if (ctemp != NULL){ + // add encryption settings + ctemp->SetConnectOptions(byCryptOptions); + CheckAndAddSource(temp, ctemp); + } +} + +void CDownloadQueue::ExportPartMetFilesOverview() const +{ + CString strFileListPath = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + _T("downloads.txt"); + + CString strTmpFileListPath = strFileListPath; + PathRenameExtension(strTmpFileListPath.GetBuffer(MAX_PATH), _T(".tmp")); + strTmpFileListPath.ReleaseBuffer(); + + CSafeBufferedFile file; + CFileException fexp; + if (!file.Open(strTmpFileListPath, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary | CFile::shareDenyWrite, &fexp)) + { + CString strError; + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(_T("Failed to create part.met file list%s"), strError); + return; + } + + // write Unicode byte-order mark 0xFEFF + fputwc(0xFEFF, file.m_pStream); + + try + { + file.printf(_T("Date: %s\r\n"), CTime::GetCurrentTime().Format(_T("%c"))); + if (thePrefs.GetTempDirCount()==1) + file.printf(_T("Directory: %s\r\n"), thePrefs.GetTempDir()); + file.printf(_T("\r\n")); + file.printf(_T("Part file\teD2K link\r\n")); + file.printf(_T("--------------------------------------------------------------------------------\r\n")); + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ) + { + const CPartFile* pPartFile = filelist.GetNext(pos); + if (pPartFile->GetStatus(true) != PS_COMPLETE) + { + CString strPartFilePath(pPartFile->GetFilePath()); + TCHAR szNam[_MAX_FNAME]; + TCHAR szExt[_MAX_EXT]; + _tsplitpath(strPartFilePath, NULL, NULL, szNam, szExt); + if (thePrefs.GetTempDirCount()==1) + file.printf(_T("%s%s\t%s\r\n"), szNam, szExt, pPartFile->GetED2kLink()); + else + file.printf(_T("%s\t%s\r\n"), pPartFile->GetFullName(), pPartFile->GetED2kLink()); + } + } + + if (thePrefs.GetCommitFiles() >= 2 || (thePrefs.GetCommitFiles() >= 1 && !theApp.emuledlg->IsRunning())){ + file.Flush(); // flush file stream buffers to disk buffers + if (_commit(_fileno(file.m_pStream)) != 0) // commit disk buffers to disk + AfxThrowFileException(CFileException::hardIO, GetLastError(), file.GetFileName()); + } + file.Close(); + + CString strBakFileListPath = strFileListPath; + PathRenameExtension(strBakFileListPath.GetBuffer(MAX_PATH), _T(".bak")); + strBakFileListPath.ReleaseBuffer(); + + if (_taccess(strBakFileListPath, 0) == 0) + CFile::Remove(strBakFileListPath); + if (_taccess(strFileListPath, 0) == 0) + CFile::Rename(strFileListPath, strBakFileListPath); + CFile::Rename(strTmpFileListPath, strFileListPath); + } + catch(CFileException* e) + { + CString strError; + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (e->GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(_T("Failed to write part.met file list%s"), strError); + e->Delete(); + file.Abort(); + (void)_tremove(file.GetFilePath()); + } +} + +void CDownloadQueue::OnConnectionState(bool bConnected) +{ + for (POSITION pos = filelist.GetHeadPosition(); pos != 0; ) + { + CPartFile* pPartFile = filelist.GetNext(pos); + if (pPartFile->GetStatus() == PS_READY || pPartFile->GetStatus() == PS_EMPTY) + pPartFile->SetActive(bConnected); + } +} + +CString CDownloadQueue::GetOptimalTempDir(UINT nCat, EMFileSize nFileSize){ + // shortcut + if (thePrefs.tempdir.GetCount() == 1) + return thePrefs.GetTempDir(); + + CMap mapNeededSpaceOnDrive; + CMap mapFreeSpaceOnDrive; + + sint64 llBuffer = 0; + sint64 llHighestFreeSpace = 0; + int nHighestFreeSpaceDrive = -1; + // first collect the free space on drives + for (int i = 0; i < thePrefs.tempdir.GetCount(); i++) { + const int nDriveNumber = GetPathDriveNumber(thePrefs.GetTempDir(i)); + if (mapFreeSpaceOnDrive.Lookup(nDriveNumber, llBuffer)) + continue; + llBuffer = GetFreeDiskSpaceX(thePrefs.GetTempDir(i)) - thePrefs.GetMinFreeDiskSpace(); + mapFreeSpaceOnDrive.SetAt(nDriveNumber, llBuffer); + if (llBuffer > llHighestFreeSpace){ + nHighestFreeSpaceDrive = nDriveNumber; + llHighestFreeSpace = llBuffer; + } + + } + + // now get the space we would need to download all files in the current queue + POSITION pos = filelist.GetHeadPosition(); + while (pos != NULL){ + CPartFile* pCurFile = filelist.GetNext(pos); + const int nDriveNumber = GetPathDriveNumber(pCurFile->GetTempPath()); + + sint64 llNeededForCompletion = 0; + switch(pCurFile->GetStatus(false)){ + case PS_READY: + case PS_EMPTY: + case PS_WAITINGFORHASH: + case PS_INSUFFICIENT: + llNeededForCompletion = pCurFile->GetFileSize() - pCurFile->GetRealFileSize(); + if (llNeededForCompletion < 0) + llNeededForCompletion = 0; + } + llBuffer = 0; + mapNeededSpaceOnDrive.Lookup(nDriveNumber, llBuffer); + llBuffer += llNeededForCompletion; + mapNeededSpaceOnDrive.SetAt(nDriveNumber, llBuffer); + } + + sint64 llHighestTotalSpace = 0; + int nHighestTotalSpaceDir = -1; + int nHighestFreeSpaceDir = -1; + int nAnyAvailableDir = -1; + // first round (0): on same drive as incomming and enough space for all downloading + // second round (1): enough space for all downloading + // third round (2): most actual free space + for (int i = 0; i < thePrefs.tempdir.GetCount(); i++) { + const int nDriveNumber = GetPathDriveNumber(thePrefs.GetTempDir(i)); + llBuffer = 0; + + sint64 llAvailableSpace = 0; + mapFreeSpaceOnDrive.Lookup(nDriveNumber, llAvailableSpace); + mapNeededSpaceOnDrive.Lookup(nDriveNumber, llBuffer); + llAvailableSpace -= llBuffer; + + // no condition can be met for a large file on a FAT volume + if (nFileSize <= OLD_MAX_EMULE_FILE_SIZE || !IsFileOnFATVolume(thePrefs.GetTempDir(i))){ + // condition 0 + // needs to be same drive and enough space + if (GetPathDriveNumber(thePrefs.GetCatPath(nCat)) == nDriveNumber && + llAvailableSpace > (sint64)nFileSize) + { + //this one is perfect + return thePrefs.GetTempDir(i); + } + // condition 1 + // needs to have enough space for downloading + if (llAvailableSpace > (sint64)nFileSize && llAvailableSpace > llHighestTotalSpace){ + llHighestTotalSpace = llAvailableSpace; + nHighestTotalSpaceDir = i; + } + // condition 2 + // first one which has the highest actualy free space + if ( nDriveNumber == nHighestFreeSpaceDrive && nHighestFreeSpaceDir == (-1)){ + nHighestFreeSpaceDir = i; + } + // condition 3 + // any directory which can be used for this file (ak not FAT for large files) + if ( nAnyAvailableDir == (-1)){ + nAnyAvailableDir = i; + } + } + } + + if (nHighestTotalSpaceDir != (-1)){ //condtion 0 was apperently too much, take 1 + return thePrefs.GetTempDir(nHighestTotalSpaceDir); + } + else if (nHighestFreeSpaceDir != (-1)){ // condtion 1 could not be met too, take 2 + return thePrefs.GetTempDir(nHighestFreeSpaceDir); + } + else if( nAnyAvailableDir != (-1)){ + return thePrefs.GetTempDir(nAnyAvailableDir); + } + else{ // so was condtion 2 and 3, take 4.. wait there is no 3 - this must be a bug + ASSERT( false ); + return thePrefs.GetTempDir(); + } +} + +void CDownloadQueue::RefilterAllComments(){ + for (POSITION pos = filelist.GetHeadPosition();pos != 0;){ + CPartFile* cur_file = filelist.GetNext(pos); + cur_file->RefilterFileComments(); + } +} diff --git a/DownloadQueue.h b/DownloadQueue.h new file mode 100644 index 00000000..dcb3879f --- /dev/null +++ b/DownloadQueue.h @@ -0,0 +1,191 @@ +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +class CSafeMemFile; +class CSearchFile; +class CUpDownClient; +class CServer; +class CPartFile; +class CSharedFileList; +class CKnownFile; +struct SUnresolvedHostname; + +namespace Kademlia +{ + class CUInt128; +}; + +class CSourceHostnameResolveWnd : public CWnd +{ +// Construction +public: + CSourceHostnameResolveWnd(); + virtual ~CSourceHostnameResolveWnd(); + + void AddToResolve(const uchar* fileid, LPCSTR pszHostname, uint16 port, LPCTSTR pszURL = NULL); + +protected: + DECLARE_MESSAGE_MAP() + afx_msg LRESULT OnHostnameResolved(WPARAM wParam, LPARAM lParam); + +private: + struct Hostname_Entry { + uchar fileid[16]; + CStringA strHostname; + uint16 port; + CString strURL; + }; + CTypedPtrList m_toresolve; + char m_aucHostnameBuffer[MAXGETHOSTSTRUCT]; +}; + + +class CDownloadQueue +{ + friend class CAddFileThread; + friend class CServerSocket; + +public: + CDownloadQueue(); + ~CDownloadQueue(); + + void Process(); + void Init(); + + // add/remove entries + void AddPartFilesToShare(); + void AddDownload(CPartFile* newfile, bool paused); + void AddSearchToDownload(CSearchFile* toadd, uint8 paused = 2, int cat = 0); + void AddSearchToDownload(CString link, uint8 paused = 2, int cat = 0); + void AddFileLinkToDownload(class CED2KFileLink* pLink, int cat = 0); + void RemoveFile(CPartFile* toremove); + void DeleteAll(); + + int GetFileCount() const { return filelist.GetCount(); } + UINT GetDownloadingFileCount() const; + UINT GetPausedFileCount() const; + + bool IsFileExisting(const uchar* fileid, bool bLogWarnings = true) const; + bool IsPartFile(const CKnownFile* file) const; + + CPartFile* GetFileByID(const uchar* filehash) const; + CPartFile* GetFileByIndex(int index) const; + CPartFile* GetFileByKadFileSearchID(uint32 ID) const; + + void StartNextFileIfPrefs(int cat); + void StartNextFile(int cat=-1,bool force=false); + + void RefilterAllComments(); + + // sources + CUpDownClient* GetDownloadClientByIP(uint32 dwIP); + CUpDownClient* GetDownloadClientByIP_UDP(uint32 dwIP, uint16 nUDPPort, bool bIgnorePortOnUniqueIP, bool* pbMultipleIPs = NULL); + bool IsInList(const CUpDownClient* client) const; + + bool CheckAndAddSource(CPartFile* sender,CUpDownClient* source); + bool CheckAndAddKnownSource(CPartFile* sender,CUpDownClient* source, bool bIgnoreGlobDeadList = false); + bool RemoveSource(CUpDownClient* toremove, bool bDoStatsUpdate = true); + + // statistics + typedef struct{ + int a[23]; + } SDownloadStats; + void GetDownloadSourcesStats(SDownloadStats& results); + int GetDownloadFilesStats(uint64 &ui64TotalFileSize, uint64 &ui64TotalLeftToTransfer, uint64 &ui64TotalAdditionalNeededSpace); + uint32 GetDatarate() {return datarate;} + + void AddUDPFileReasks() {m_nUDPFileReasks++;} + uint32 GetUDPFileReasks() const {return m_nUDPFileReasks;} + void AddFailedUDPFileReasks() {m_nFailedUDPFileReasks++;} + uint32 GetFailedUDPFileReasks() const {return m_nFailedUDPFileReasks;} + + // categories + void ResetCatParts(UINT cat); + void SetCatPrio(UINT cat, uint8 newprio); + void RemoveAutoPrioInCat(UINT cat, uint8 newprio); // ZZ:DownloadManager + void SetCatStatus(UINT cat, int newstatus); + void MoveCat(UINT from, UINT to); + void SetAutoCat(CPartFile* newfile); + + // searching on local server + void SendLocalSrcRequest(CPartFile* sender); + void RemoveLocalServerRequest(CPartFile* pFile); + void ResetLocalServerRequests(); + + // searching in Kad + void SetLastKademliaFileRequest() {lastkademliafilerequest = ::GetTickCount();} + bool DoKademliaFileRequest(); + void KademliaSearchFile(uint32 searchID, const Kademlia::CUInt128* pcontactID, const Kademlia::CUInt128* pkadID, uint8 type, uint32 ip, uint16 tcp, uint16 udp, uint32 dwBuddyIP, uint16 dwBuddyPort, uint8 byCryptOptions); + + // searching on global servers + void StopUDPRequests(); + + // check diskspace + void SortByPriority(); + void CheckDiskspace(bool bNotEnoughSpaceLeft = false); + void CheckDiskspaceTimed(); + + void ExportPartMetFilesOverview() const; + void OnConnectionState(bool bConnected); + + void AddToResolved( CPartFile* pFile, SUnresolvedHostname* pUH ); + + CString GetOptimalTempDir(UINT nCat, EMFileSize nFileSize); + + CServer* cur_udpserver; + +protected: + bool SendNextUDPPacket(); + void ProcessLocalRequests(); + bool IsMaxFilesPerUDPServerPacketReached(uint32 nFiles, uint32 nIncludedLargeFiles) const; + bool SendGlobGetSourcesUDPPacket(CSafeMemFile* data, bool bExt2Packet, uint32 nFiles, uint32 nIncludedLargeFiles); + +private: + bool CompareParts(POSITION pos1, POSITION pos2); + void SwapParts(POSITION pos1, POSITION pos2); + void HeapSort(UINT first, UINT last); + CTypedPtrList filelist; + CTypedPtrList m_localServerReqQueue; + uint16 filesrdy; + uint32 datarate; + + CPartFile* lastfile; + uint32 lastcheckdiskspacetime; + uint32 lastudpsearchtime; + uint32 lastudpstattime; + UINT udcounter; + UINT m_cRequestsSentToServer; + uint32 m_dwNextTCPSrcReq; + int m_iSearchedServers; + uint32 lastkademliafilerequest; + + uint64 m_datarateMS; + uint32 m_nUDPFileReasks; + uint32 m_nFailedUDPFileReasks; + + // By BadWolf - Accurate Speed Measurement + typedef struct TransferredData { + uint32 datalen; + DWORD timestamp; + }; + CList avarage_dr_list; + // END By BadWolf - Accurate Speed Measurement + + CSourceHostnameResolveWnd m_srcwnd; + + DWORD m_dwLastA4AFtime; // ZZ:DownloadManager +}; diff --git a/Drawgdix.h b/Drawgdix.h new file mode 100644 index 00000000..9d4db565 --- /dev/null +++ b/Drawgdix.h @@ -0,0 +1,736 @@ +#pragma once + +/******************************************************************* + +GDI Helper Classes, MFC Version, by G. Bavestrelli, Techint S.p.A. +Any feedback is welcome, you can contact me at: + giovanni.bavestrelli@pomini.it + +class CSelStock +class CSelPen +class CSelBrush +class CSelFont +class CSelBitmap +class CSelPalette +class CSelROP2 +class CSelBkMode +class CSelBkColor +class CSelTextColor +class CSelTextAlign +class CSelMapMode +class CSaveDC + +class CSelStock + CSelStock(CDC * pDC, int index) + void Select(int index) + CGdiObject * Old() + +class CSelPen + CSelPen(CDC * pDC, COLORREF col, int sty=PS_SOLID, int wid = 0) + CSelPen(CDC * pDC, CPen * pPen) + void Select(CPen * pPen) + void Select(COLORREF col, int sty=PS_SOLID, int wid = 0) + +class CSelBrush + CSelBrush(CDC * pDC, CBrush * pBrush) + CSelBrush(CDC * pDC, COLORREF crColor) + CSelBrush(CDC * pDC, int index, COLORREF crColor) // Hatch brush + CSelBrush(CDC * pDC, CBitmap * pBitmap) // Pattern brush + CSelBrush(CDC * pDC, HGLOBAL hPackedDib, UINT usage) // DIB Pattern brush + void Select(CBrush * pBrush) + void Select(COLORREF col) + void Select(int index, COLORREF col) // Hatch brush + void Select(CBitmap * pBitmap) // Pattern brush + void Select(HGLOBAL hPackedDib, UINT usage) // DIB Pattern brush + +class CSelFont + CSelFont(CDC * pDC, int size, LPCTSTR face = NULL, BOOL bold = 0, + BOOL italic = 0, BOOL underlined = 0, BOOL fixed = 0, + BOOL hiquality = 0, int angleindegrees = 0) + CSelFont(CDC * pDC, CFont * pFont) + CSelFont(CDC * pDC, const LOGFONT* lpLogFont) + + void Select(int size, LPCTSTR face = NULL, BOOL bold = 0, + BOOL italic = 0, BOOL underlined = 0, BOOL fixed = 0, + BOOL hiquality = 0, int angleindegrees = 0) + void Select(CFont * pFont) + void Select(const LOGFONT* lpLogFont) + +class CSelBitmap + CSelBitmap(CDC * pDC, CBitmap * pBitmap) + CSelBitmap(CDC * SelectInDC, CDC * pCompatibleToDC, int w, int h) + void Select(CBitmap * pBitmap) + void Select(CDC * pCompatibleToDC, int w, int h) + +class CSelPalette + CSelPalette(CDC * pDC, CPalette * pPalette, + BOOL fForceBackground = FALSE, BOOL fRealize = TRUE) + UINT Select(CPalette * pPalette, + BOOL fForceBackground = FALSE, BOOL fRealize = TRUE) + void ChangeRestoreFlags(BOOL fForceBackground, BOOL fRealize) + +class CSelROP2 + CSelROP2(CDC * pDC, int drawMode) + void Select(int drawmode) + +class CSelBkMode + CSelBkMode(CDC * pDC, int BkMode) + void Select(int bkmode) + +class CSelBkColor + CSelBkColor(CDC * pDC, COLORREF BkColor) + void Select(COLORREF color) + +class CSelTextColor + CSelTextColor(CDC * pDC, COLORREF TextColor) + void Select(COLORREF color) + +class CSelTextAlign + CSelTextAlign(CDC * pDC, UINT TextAlign) + void Select(UINT align) + +class CSelMapMode + CSelMapMode(CDC * pDC, int MapMode) + void Select(int mode) + +class CSaveDC + CSaveDC(CDC * pDC) // saving the complete state of the DC + +every class also have: + (CDC* pDC) // constructor w/o selection + void Restore() // restores original object and destroys new object if neccessary + Old() // returns original object + +*******************************************************************/ + +//****************************************************************** +// Base class, stores CDC * +//****************************************************************** + +class CSelect +{ +protected: + CDC * const m_pDC; + CSelect(CDC * pDC):m_pDC(pDC) { ASSERT(m_pDC);} + virtual ~CSelect() {} + +private: + + // Disable copying + CSelect& operator=(const CSelect& d); + CSelect(const CSelect &); +}; + +//****************************************************************** +// Class for Stock Objects +//****************************************************************** + +class CSelStock : public CSelect +{ + CGdiObject * m_pOldObj; + +public: + + CSelStock(CDC * pDC) + :CSelect(pDC), m_pOldObj(NULL) {} + + CSelStock(CDC * pDC, int index) + :CSelect(pDC), m_pOldObj(NULL) + { Select(index); } + + ~CSelStock() { Restore(); } + + void Select(int index) + { + CGdiObject * pOld = m_pDC->SelectStockObject(index); ASSERT(pOld); + if (!m_pOldObj) m_pOldObj = pOld; + } + + void Restore() + { + if (m_pOldObj) VERIFY(m_pDC->SelectObject(m_pOldObj)); + m_pOldObj = NULL; + } + + CGdiObject * Old() const { return m_pOldObj; } +}; + +//****************************************************************** +// Pens +//****************************************************************** + +class CSelPen : public CSelect +{ + CPen m_NewPen; + CPen * m_pOldPen; + +public: + + CSelPen(CDC * pDC) + :CSelect(pDC), m_pOldPen(NULL){} + + CSelPen(CDC * pDC, COLORREF col, int sty = PS_SOLID, int wid = 0) + :CSelect(pDC), m_pOldPen(NULL) + { Select(col, sty, wid); } + + CSelPen(CDC * pDC, CPen * pPen) + :CSelect(pDC), m_pOldPen(NULL) + { Select(pPen); } + + ~CSelPen() { Restore(); } + + void Select(CPen * pPen) + { + ASSERT(pPen); + ASSERT(pPen != &m_NewPen); + CPen * pOld = m_pDC->SelectObject(pPen); ASSERT(pOld); + if (!m_pOldPen) m_pOldPen = pOld; + m_NewPen.DeleteObject(); + } + + void Select(COLORREF col, int sty = PS_SOLID, int wid = 0) + { + if (m_pOldPen) Select(m_pOldPen); + VERIFY(m_NewPen.CreatePen(sty, wid, col)); + VERIFY(m_pOldPen = m_pDC->SelectObject(&m_NewPen)); + } + + void Restore() + { + if (m_pOldPen) VERIFY(m_pDC->SelectObject(m_pOldPen)); + m_pOldPen = NULL; + m_NewPen.DeleteObject(); + } + + CPen * Old() const { return m_pOldPen; } +}; + +//****************************************************************** +// Brushes +//****************************************************************** + +class CSelBrush : public CSelect +{ + CBrush m_NewBrush; + CBrush * m_pOldBrush; + +public: + CSelBrush(CDC * pDC) + :CSelect(pDC), m_pOldBrush(NULL) {} + + // Solid brush + CSelBrush(CDC * pDC, COLORREF crColor) + :CSelect(pDC), m_pOldBrush(NULL) + { Select(crColor); } + + // Hatch brush + CSelBrush(CDC * pDC, int index, COLORREF crColor) + :CSelect(pDC), m_pOldBrush(NULL) + { Select(index, crColor); } + + // Pattern brush + CSelBrush(CDC * pDC, CBitmap * pBitmap) + :CSelect(pDC), m_pOldBrush(NULL) + { Select(pBitmap); } + + // DIB Pattern brush + CSelBrush(CDC * pDC, HGLOBAL hPackedDib, UINT usage) + :CSelect(pDC), m_pOldBrush(NULL) + { Select(hPackedDib, usage); } + + CSelBrush(CDC * pDC, CBrush * pBrush) + :CSelect(pDC), m_pOldBrush(NULL) + { Select(pBrush); } + + ~CSelBrush() { Restore(); } + + void Select(CBrush * pBrush) + { + ASSERT(pBrush); + ASSERT(pBrush != &m_NewBrush); + CBrush * pOld = m_pDC->SelectObject(pBrush); ASSERT(pOld); + if (!m_pOldBrush) m_pOldBrush=pOld; + m_NewBrush.DeleteObject(); + } + + // Solid brush + void Select(COLORREF col) + { + if (m_pOldBrush) Select(m_pOldBrush); + VERIFY(m_NewBrush.CreateSolidBrush(col)); + VERIFY(m_pOldBrush = m_pDC->SelectObject(&m_NewBrush)); + } + + // Hatch brush + void Select(int index, COLORREF col) + { + if (m_pOldBrush) Select(m_pOldBrush); + VERIFY(m_NewBrush.CreateHatchBrush(index, col)); + VERIFY(m_pOldBrush = m_pDC->SelectObject(&m_NewBrush)); + } + + // Pattern brush + void Select(CBitmap * pBitmap) + { + ASSERT(pBitmap); + if (m_pOldBrush) Select(m_pOldBrush); + VERIFY(m_NewBrush.CreatePatternBrush(pBitmap)); + VERIFY(m_pOldBrush = m_pDC->SelectObject(&m_NewBrush)); + } + + // DIB Pattern brush + void Select(HGLOBAL hPackedDib, UINT usage) + { + if (m_pOldBrush) Select(m_pOldBrush); + VERIFY(m_NewBrush.CreateDIBPatternBrush(hPackedDib, usage)); + VERIFY(m_pOldBrush=m_pDC->SelectObject(&m_NewBrush)); + } + + void Restore() + { + if (m_pOldBrush) VERIFY(m_pDC->SelectObject(m_pOldBrush)); + m_pOldBrush = NULL; + m_NewBrush.DeleteObject(); + } + + CBrush * Old() const { return m_pOldBrush; } +}; + + + +//****************************************************************** +// My own font with different constructor and creation function +//****************************************************************** + +class CMyFont : public CFont +{ +public: + CMyFont(){} + CMyFont(CDC * pDC, int size, LPCTSTR face = NULL, BOOL bold = 0, + BOOL italic = 0, BOOL underlined = 0, BOOL fixed = 0, + BOOL hiquality = 0, int angleindegrees = 0) + { + VERIFY(MyCreateFont(pDC, size, face, bold, italic, underlined, + fixed, hiquality, angleindegrees)); + } + BOOL MyCreateFont(CDC * pDC, int size, LPCTSTR face = NULL, + BOOL bold = 0, BOOL italic = 0, BOOL underlined = 0, + BOOL fixed = 0, BOOL hiquality = 0, + int angleindegrees = 0) + { + ASSERT(pDC); + CSize Size(0, MulDiv(size, pDC->GetDeviceCaps(LOGPIXELSY), 72)); + pDC->DPtoLP(&Size); + return CreateFont(-abs(Size.cy), 0, 10*angleindegrees, 0, + bold ? FW_BOLD : FW_NORMAL, BYTE(italic), + BYTE(underlined), 0, ANSI_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + BYTE(hiquality ? PROOF_QUALITY : DEFAULT_QUALITY), + BYTE(fixed ? FF_MODERN|FIXED_PITCH : + FF_SWISS|VARIABLE_PITCH), + face && *face ? face : NULL); + } +}; + + +//****************************************************************** +// Fonts +//****************************************************************** + + +class CSelFont : public CSelect +{ + CMyFont m_NewFont; + CFont * m_pOldFont; + +public: + + CSelFont(CDC * pDC) + : CSelect(pDC), m_pOldFont(NULL) {} + + CSelFont(CDC * pDC, int size, LPCTSTR face = NULL, BOOL bold = 0, + BOOL italic = 0, BOOL underlined = 0, BOOL fixed = 0, + BOOL hiquality = 0, int angleindegrees = 0) + : CSelect(pDC), m_pOldFont(NULL) + { + Select(size, face, bold, italic, underlined, + fixed, hiquality, angleindegrees); + } + + CSelFont(CDC * pDC, CFont * pFont) + : CSelect(pDC), m_pOldFont(NULL) + { Select(pFont); } + + CSelFont(CDC * pDC, const LOGFONT* lpLogFont) + : CSelect(pDC), m_pOldFont(NULL) + { Select(lpLogFont); } + + ~CSelFont() { Restore(); } + + void Select(CFont * pFont) + { + ASSERT(pFont); + ASSERT(pFont != &m_NewFont); + CFont * pOld = m_pDC->SelectObject(pFont); ASSERT(pOld); + if (!m_pOldFont) m_pOldFont = pOld; + m_NewFont.DeleteObject(); + } + + void Select(int size, LPCTSTR face = NULL, BOOL bold = 0, + BOOL italic = 0, BOOL underlined = 0, BOOL fixed = 0, + BOOL hiquality = 0, int angleindegrees = 0) + { + if (m_pOldFont) Select(m_pOldFont); + VERIFY(m_NewFont.MyCreateFont(m_pDC, size, face, bold, italic, + underlined, fixed, hiquality, angleindegrees)); + VERIFY(m_pOldFont = m_pDC->SelectObject(&m_NewFont)); + } + + void Select(const LOGFONT* lpLogFont) + { + if (m_pOldFont) Select(m_pOldFont); + VERIFY(m_NewFont.CreateFontIndirect(lpLogFont)); + VERIFY(m_pOldFont = m_pDC->SelectObject(&m_NewFont)); + } + + void Restore() + { + if (m_pOldFont) VERIFY(m_pDC->SelectObject(m_pOldFont)); + m_pOldFont = NULL; + m_NewFont.DeleteObject(); + } + + CFont * Old() const { return m_pOldFont; } +}; + + +//****************************************************************** +// Bitmaps +//****************************************************************** + +class CSelBitmap : public CSelect +{ + CBitmap m_NewBmp; + CBitmap * m_pOldBmp; + +public: + CSelBitmap(CDC * pDC) + : CSelect(pDC), m_pOldBmp(NULL) {} + + CSelBitmap(CDC * SelectInDC, CDC * pCompatibleToDC, int w, int h) + : CSelect(SelectInDC), m_pOldBmp(NULL) + { Select(pCompatibleToDC, w, h); } + + CSelBitmap(CDC * pDC, CBitmap * pBitmap) + : CSelect(pDC), m_pOldBmp(NULL) + { Select(pBitmap); } + + ~CSelBitmap() { Restore(); } + + void Select(CBitmap * pBitmap) + { + ASSERT(pBitmap); + ASSERT(pBitmap != &m_NewBmp); + CBitmap * pOld = m_pDC->SelectObject(pBitmap); ASSERT(pOld); + if (!m_pOldBmp) m_pOldBmp = pOld; + m_NewBmp.DeleteObject(); + } + + void Select(CDC * pCompatibleToDC, int w, int h) + { + ASSERT(pCompatibleToDC); + if (m_pOldBmp) Select(m_pOldBmp); + VERIFY(m_NewBmp.CreateCompatibleBitmap(pCompatibleToDC, w, h)); + VERIFY(m_pOldBmp = m_pDC->SelectObject(&m_NewBmp)); + } + + void Restore() + { + if (m_pOldBmp) VERIFY(m_pDC->SelectObject(m_pOldBmp)); + m_pOldBmp = NULL; + m_NewBmp.DeleteObject(); + } + + CBitmap * Old() const { return m_pOldBmp; } +}; + +// This class is a bit different +class CSelPalette : public CSelect +{ + // You need your own palette, use CPalette + CPalette * m_pOldPalette; + BOOL m_fForceBackground; + BOOL m_fRealizePalette; + +public: + + CSelPalette(CDC * pDC) + : CSelect(pDC), m_pOldPalette(NULL) {} + + CSelPalette(CDC * pDC, CPalette * pPalette, + BOOL fForceBackground = FALSE, BOOL fRealize = TRUE) + : CSelect(pDC), m_pOldPalette(NULL) + { Select(pPalette, fForceBackground, fRealize); } + + ~CSelPalette() { Restore(); } + + UINT Select(CPalette * pPalette, + BOOL fForceBackground = FALSE, BOOL fRealize = TRUE) + { + ASSERT(pPalette); + ASSERT(m_pDC->GetDeviceCaps(RASTERCAPS)&RC_PALETTE); + CPalette * pOld=m_pDC->SelectPalette(pPalette, fForceBackground); + ASSERT(pOld); + if (!m_pOldPalette) m_pOldPalette=pOld; + m_fForceBackground = fForceBackground; + m_fRealizePalette = fRealize; + return fRealize ? m_pDC->RealizePalette() : 0; + } + + void ChangeRestoreFlags(BOOL fForceBackground, BOOL fRealize) + { + m_fForceBackground = fForceBackground; + m_fRealizePalette = fRealize; + } + + void Restore() + { + if (!m_pOldPalette) + return; + + VERIFY(m_pDC->SelectPalette(m_pOldPalette, m_fForceBackground)); + if (m_fRealizePalette) + m_pDC->RealizePalette(); + m_pOldPalette = NULL; + } + + CPalette * Old() const { return m_pOldPalette; } +}; + + +//****************************************************************** +// Set and restore other characteristics of the DC (no GDI objects) +//****************************************************************** + + +class CSelROP2 : public CSelect +{ + int m_OldRop; + +public: + + CSelROP2(CDC * pDC) + : CSelect(pDC), m_OldRop(0) + { /*VERIFY(m_OldRop=m_pDC->GetROP2());*/ } + + CSelROP2(CDC * pDC, int drawMode) + : CSelect(pDC), m_OldRop(0) + { Select(drawMode); } + + ~CSelROP2() { Restore(); } + + void Select(int drawmode) + { + int old = m_pDC->SetROP2(drawmode); ASSERT(old); + if (!m_OldRop) m_OldRop = old; + } + + void Restore() + { + if (m_OldRop) VERIFY(m_pDC->SetROP2(m_OldRop)); + m_OldRop = 0; + } + + int Old() const { return m_OldRop; } +}; + + +class CSelBkMode : public CSelect +{ + int m_OldBkMode; + +public: + + CSelBkMode(CDC * pDC) + : CSelect(pDC), m_OldBkMode(0) + { /*VERIFY(m_OldBkMode = m_pDC->GetBkMode());*/ } + + CSelBkMode(CDC * pDC, int BkMode) + : CSelect(pDC), m_OldBkMode(0) + { Select(BkMode); } + + ~CSelBkMode() { Restore(); } + + void Select(int BkMode) + { + int old = m_pDC->SetBkMode(BkMode); ASSERT(old); + if (!m_OldBkMode) m_OldBkMode = old; + } + + void Restore() + { + if (m_OldBkMode) VERIFY(m_pDC->SetBkMode(m_OldBkMode)); + m_OldBkMode = 0; + } + + int Old() const { return m_OldBkMode; } +}; + + +class CSelBkColor : public CSelect +{ + COLORREF m_OldBkColor; + +public: + + CSelBkColor(CDC * pDC) + : CSelect(pDC), m_OldBkColor(CLR_INVALID) + { m_OldBkColor = m_pDC->GetBkColor(); } + + CSelBkColor(CDC * pDC, COLORREF BkColor) + :CSelect(pDC), m_OldBkColor(CLR_INVALID) + { Select(BkColor); } + + ~CSelBkColor() { Restore(); } + + void Select(COLORREF color) + { + ASSERT(color != CLR_INVALID); + int old = m_pDC->SetBkColor(color); ASSERT(old != CLR_INVALID); + if (m_OldBkColor == CLR_INVALID) m_OldBkColor = old; + } + + void Restore() + { + if(m_OldBkColor == CLR_INVALID) return; + VERIFY(m_pDC->SetBkColor(m_OldBkColor) != CLR_INVALID); + m_OldBkColor = CLR_INVALID; + } + + COLORREF Old() const { return m_OldBkColor; } +}; + + +class CSelTextColor : public CSelect +{ + COLORREF m_OldTextColor; + +public: + + CSelTextColor(CDC * pDC) + : CSelect(pDC), m_OldTextColor(CLR_INVALID) + { m_OldTextColor = m_pDC->GetTextColor(); } + + CSelTextColor(CDC * pDC, COLORREF TextColor) + :CSelect(pDC), m_OldTextColor(CLR_INVALID) + { Select(TextColor); } + + ~CSelTextColor() { Restore(); } + + void Select(COLORREF color) + { + ASSERT(color != CLR_INVALID); + int old = m_pDC->SetTextColor(color); ASSERT(old != CLR_INVALID); + if (m_OldTextColor == CLR_INVALID) m_OldTextColor = old; + } + + void Restore() + { + if(m_OldTextColor == CLR_INVALID) return; + VERIFY(m_pDC->SetTextColor(m_OldTextColor) != CLR_INVALID); + m_OldTextColor = CLR_INVALID; + } + + COLORREF Old() const { return m_OldTextColor; } +}; + + +class CSelTextAlign : public CSelect +{ + UINT m_OldTextAlign; + +public: + + CSelTextAlign(CDC * pDC) + : CSelect(pDC), m_OldTextAlign(GDI_ERROR) + { m_OldTextAlign = m_pDC->GetTextAlign(); } + + CSelTextAlign(CDC * pDC, UINT TextAlign) + : CSelect(pDC), m_OldTextAlign(GDI_ERROR) + { Select(TextAlign); } + + ~CSelTextAlign() { Restore(); } + + void Select(UINT align) + { + ASSERT(align != GDI_ERROR); + int old = m_pDC->SetTextAlign(align); ASSERT(old != GDI_ERROR); + if (m_OldTextAlign == GDI_ERROR) m_OldTextAlign = old; + } + + void Restore() + { + if(m_OldTextAlign == GDI_ERROR) return; + VERIFY(m_pDC->SetTextAlign(m_OldTextAlign) != GDI_ERROR); + m_OldTextAlign = GDI_ERROR; + } + + UINT Old() const { return m_OldTextAlign; } +}; + + +class CSelMapMode : public CSelect +{ + int m_OldMapMode; + +public: + + CSelMapMode(CDC * pDC) + : CSelect(pDC), m_OldMapMode(0) + { /*VERIFY(m_OldMapMode = m_pDC->GetMapMode());*/ } + + CSelMapMode(CDC * pDC, int MapMode) + : CSelect(pDC), m_OldMapMode(0) + { Select(MapMode); } + + ~CSelMapMode() { Restore(); } + + void Select(int MapMode) + { + int old = m_pDC->SetMapMode(MapMode); ASSERT(old); + if (!m_OldMapMode) m_OldMapMode = old; + } + + void Restore() + { + if (m_OldMapMode) VERIFY(m_pDC->SetMapMode(m_OldMapMode)); + m_OldMapMode = 0; + } + + UINT Old() const { return m_OldMapMode; } +}; + +//****************************************************************** +// Class for saving the complete state of the DC +//****************************************************************** + +class CSaveDC : public CSelect +{ + int m_SavedDC; + +public: + + CSaveDC(CDC * pDC) + : CSelect(pDC) + { VERIFY(m_SavedDC = m_pDC->SaveDC()); } + + ~CSaveDC() { Restore(); } + + void Restore() + { + if (m_SavedDC) VERIFY(m_pDC->RestoreDC(m_SavedDC)); + m_SavedDC = 0; + } + + int Old() const { return m_SavedDC; } +}; diff --git a/DropDownButton.cpp b/DropDownButton.cpp new file mode 100644 index 00000000..02936244 --- /dev/null +++ b/DropDownButton.cpp @@ -0,0 +1,182 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "DropDownButton.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +IMPLEMENT_DYNAMIC(CDropDownButton, CToolBarCtrlX) + +BEGIN_MESSAGE_MAP(CDropDownButton, CToolBarCtrlX) + ON_WM_SIZE() + ON_WM_SETTINGCHANGE() +END_MESSAGE_MAP() + +CDropDownButton::CDropDownButton() +{ + m_bSingleDropDownBtn = true; +} + +CDropDownButton::~CDropDownButton() +{ +} + +BOOL CDropDownButton::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, bool bSingleDropDownBtn) +{ + m_bSingleDropDownBtn = bSingleDropDownBtn; + dwStyle |= CCS_NOMOVEY + | CCS_NOPARENTALIGN + | CCS_NORESIZE // prevent adjusting of specified width & height(!) by Create func.. + | CCS_NODIVIDER + | TBSTYLE_LIST + | TBSTYLE_FLAT + | TBSTYLE_TRANSPARENT + | 0; + + if (!CToolBarCtrlX::Create(dwStyle, rect, pParentWnd, nID)) + return FALSE; + return Init(bSingleDropDownBtn); +} + +BOOL CDropDownButton::Init(bool bSingleDropDownBtn, bool bWholeDropDown) +{ + DeleteAllButtons(); + m_bSingleDropDownBtn = bSingleDropDownBtn; + + // If a toolbar control was created indirectly via a dialog resource one can not + // add any buttons without setting an image list before. (?) + // So, for this to work, we have to attach an image list to the toolbar control! + // The image list can be empty, and it does not need to be used at all, but it has + // to be attached. + CImageList* piml = GetImageList(); + if (piml == NULL || piml->m_hImageList == NULL) + { + CImageList iml; + iml.Create(16, 16, ILC_COLOR, 0, 0); + SetImageList(&iml); + iml.Detach(); + } + if (m_bSingleDropDownBtn) + { + TBBUTTON atb[1] = {0}; + atb[0].iBitmap = -1; + atb[0].idCommand = GetWindowLong(m_hWnd, GWL_ID); + atb[0].fsState = TBSTATE_ENABLED; + atb[0].fsStyle = m_bSingleDropDownBtn ? (bWholeDropDown ? BTNS_WHOLEDROPDOWN : BTNS_DROPDOWN) : BTNS_BUTTON; + atb[0].iString = -1; + VERIFY( AddButtons(1, atb) ); + + ResizeToMaxWidth(); + SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS); + } + return TRUE; +} + +void CDropDownButton::SetWindowText(LPCTSTR pszString) +{ + int cx = 0; + if (!m_bSingleDropDownBtn) + cx = GetBtnWidth(GetWindowLong(m_hWnd, GWL_ID)); + + TBBUTTONINFO tbbi = {0}; + tbbi.cbSize = sizeof tbbi; + tbbi.dwMask = TBIF_TEXT; + tbbi.pszText = const_cast(pszString); + SetButtonInfo(GetWindowLong(m_hWnd, GWL_ID), &tbbi); + + if (cx) + SetBtnWidth(GetWindowLong(m_hWnd, GWL_ID), cx); +} + +void CDropDownButton::SetIcon(LPCTSTR pszResourceID) +{ + if (!m_bSingleDropDownBtn) + return; + + CImageList iml; + iml.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 1, 1); + iml.Add(CTempIconLoader(pszResourceID)); + CImageList* pImlOld = SetImageList(&iml); + iml.Detach(); + if (pImlOld) + pImlOld->DeleteImageList(); + + TBBUTTONINFO tbbi = {0}; + tbbi.cbSize = sizeof tbbi; + tbbi.dwMask = TBIF_IMAGE; + tbbi.iImage = 0; + SetButtonInfo(GetWindowLong(m_hWnd, GWL_ID), &tbbi); +} + +void CDropDownButton::ResizeToMaxWidth() +{ + if (!m_bSingleDropDownBtn) + return; + + CRect rcWnd; + GetWindowRect(&rcWnd); + if (rcWnd.Width() > 0) + { + TBBUTTONINFO tbbi = {0}; + tbbi.cbSize = sizeof tbbi; + tbbi.dwMask = TBIF_SIZE; + tbbi.cx = (WORD)rcWnd.Width(); + SetButtonInfo(GetWindowLong(m_hWnd, GWL_ID), &tbbi); + } +} + +void CDropDownButton::OnSize(UINT nType, int cx, int cy) +{ + CToolBarCtrlX::OnSize(nType, cx, cy); + + if (cx > 0 && cy > 0) + ResizeToMaxWidth(); +} + +void CDropDownButton::RecalcLayout(bool bForce) +{ + // If toolbar has at least one button with the button style BTNS_DROPDOWN, the + // entire toolbar is resized with too large height. So, remove the BTNS_DROPDOWN + // button style(s) and force the toolbar to resize and apply them again. + // + // TODO: Should be moved to CToolBarCtrlX + bool bDropDownBtn = (GetBtnStyle(GetWindowLong(m_hWnd, GWL_ID)) & BTNS_DROPDOWN) != 0; + if (bDropDownBtn) + RemoveBtnStyle(GetWindowLong(m_hWnd, GWL_ID), BTNS_DROPDOWN); + if (bDropDownBtn || bForce) + { + CToolBarCtrlX::RecalcLayout(); + if (bDropDownBtn) + AddBtnStyle(GetWindowLong(m_hWnd, GWL_ID), BTNS_DROPDOWN); + } +} + +void CDropDownButton::OnSettingChange(UINT uFlags, LPCTSTR lpszSection) +{ + CToolBarCtrlX::OnSettingChange(uFlags, lpszSection); + + // The toolbar resizes itself when the system fonts were changed, + // especially when large/small system fonts were selected. Need + // to recalc the layout because we have a fixed control size. + RecalcLayout(); +} diff --git a/DropDownButton.h b/DropDownButton.h new file mode 100644 index 00000000..4fe5663b --- /dev/null +++ b/DropDownButton.h @@ -0,0 +1,41 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "ToolBarCtrlX.h" + +class CDropDownButton : public CToolBarCtrlX +{ + DECLARE_DYNAMIC(CDropDownButton) +public: + CDropDownButton(); + virtual ~CDropDownButton(); + + BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, bool bSingleDropDownBtn = true); + BOOL Init(bool bSingleDropDownBtn = true, bool bWholeDropDown = false); + + void SetWindowText(LPCTSTR pszString); + void SetIcon(LPCTSTR pszResourceID); + void ResizeToMaxWidth(); + void RecalcLayout(bool bForce = false); + +protected: + bool m_bSingleDropDownBtn; + + DECLARE_MESSAGE_MAP() + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnSettingChange(UINT uFlags, LPCTSTR lpszSection); +}; diff --git a/DropTarget.cpp b/DropTarget.cpp new file mode 100644 index 00000000..b5c19252 --- /dev/null +++ b/DropTarget.cpp @@ -0,0 +1,629 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "emuledlg.h" +#include "DropTarget.h" +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define FILETYPE_INETSHRTCUT _T("Internet Shortcut File") +#define FILEEXT_INETSHRTCUTA "url" // ANSI string +#define FILEEXT_INETSHRTCUTW L"url" // Unicode string +#define FILEEXT_INETSHRTCUT _T(FILEEXT_INETSHRTCUTA) +#define FILEEXTDOT_INETSHRTCUTA "." FILEEXT_INETSHRTCUTA // ANSI string +#define FILEEXTDOT_INETSHRTCUTW L"." FILEEXT_INETSHRTCUTW // Unicode string +#define FILEEXTDOT_INETSHRTCUT _T(".") FILEEXT_INETSHRTCUT +#define FILEFLT_INETSHRTCUT FILETYPE_INETSHRTCUT _T("s (*") FILEEXTDOT_INETSHRTCUT _T(")|*") FILEEXTDOT_INETSHRTCUT _T("|") + +BOOL IsUrlSchemeSupportedA(LPCSTR pszUrl) +{ + static const struct SCHEME + { + LPCSTR pszPrefix; + int iLen; + } _aSchemes[] = + { +#define SCHEME_ENTRY(prefix) { prefix, ARRSIZE(prefix)-1 } + SCHEME_ENTRY("ed2k://") +#undef SCHEME_ENTRY + }; + + for (int i = 0; i < ARRSIZE(_aSchemes); i++) + { + if (strncmp(pszUrl, _aSchemes[i].pszPrefix, _aSchemes[i].iLen) == 0) + return TRUE; + } + return FALSE; +} + +BOOL IsUrlSchemeSupportedW(LPCWSTR pszUrl) +{ + static const struct SCHEME + { + LPCWSTR pszPrefix; + int iLen; + } _aSchemes[] = + { +#define SCHEME_ENTRY(prefix) { prefix, ARRSIZE(prefix)-1 } + SCHEME_ENTRY(L"ed2k://") +#undef SCHEME_ENTRY + }; + + for (int i = 0; i < ARRSIZE(_aSchemes); i++) + { + if (wcsncmp(pszUrl, _aSchemes[i].pszPrefix, _aSchemes[i].iLen) == 0) + return TRUE; + } + return FALSE; +} + +// GetFileExtA -- ANSI version +// +// This function is thought to be used only for filenames which have been +// validated by 'GetFullPathName' or similar functions. +LPCSTR GetFileExtA(LPCSTR pszPathA, int iLen /*= -1*/) +{ + // Just search the last '.'-character which comes after an optionally + // available last '\'-char. + int iPos = iLen >= 0 ? iLen : strlen(pszPathA); + while (iPos-- > 0) + { + if (pszPathA[iPos] == '.') + return &pszPathA[iPos]; + if (pszPathA[iPos] == '\\') + break; + } + + return NULL; +} + +// GetFileExtW -- Unicode version +// +// This function is thought to be used only for filenames which have been +// validated by 'GetFullPathName' or similar functions. +LPCWSTR GetFileExtW(LPCWSTR pszPathW, int iLen /*= -1*/) +{ + // Just search the last '.'-character which comes after an optionally + // available last '\'-char. + int iPos = iLen >= 0 ? iLen : wcslen(pszPathW); + while (iPos-- > 0) + { + if (pszPathW[iPos] == L'.') + return &pszPathW[iPos]; + if (pszPathW[iPos] == L'\\') + break; + } + + return NULL; +} + + +////////////////////////////////////////////////////////////////////////////// +// PASTEURLDATA + +struct PASTEURLDATA +{ + PASTEURLDATA() + { + m_eType = (DataType)-1; + m_dwFlags = 0; + } + PASTEURLDATA(BSTR bstrText, DWORD dwFlags = 0) + { + m_eType = HTMLText; + m_bstrURLs = bstrText; + m_dwFlags = dwFlags; + } + PASTEURLDATA(IDispatch *pIDispatch, DWORD dwFlags = 0) + { + m_eType = Document; + m_pIDispDoc = pIDispatch; + m_dwFlags = dwFlags; + } + + enum DataType + { + Text, + HTMLText, + Document + } m_eType; + DWORD m_dwFlags; + union + { + BSTR m_bstrURLs; + IDispatch *m_pIDispDoc; + }; +}; + + +////////////////////////////////////////////////////////////////////////////// +// CMainFrameDropTarget + +CMainFrameDropTarget::CMainFrameDropTarget() +{ + m_bDropDataValid = FALSE; + + m_cfHTML = (CLIPFORMAT)RegisterClipboardFormat(_T("HTML Format")); + ASSERT(m_cfHTML != 0); + + m_cfShellURL = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_SHELLURL); + ASSERT(m_cfShellURL != 0); +} + +HRESULT CMainFrameDropTarget::PasteHTMLDocument(IHTMLDocument2* doc, PASTEURLDATA* /*pPaste*/) +{ + HRESULT hrPasteResult = S_FALSE; // default: nothing was pasted + int iURLElements = 0; + + // get_links HREF all and elements -> that's *wrong* it also contains all elements! + // get_anchors HREF all elements which have a NAME or ID value! + + // + // Links + // + CComPtr links; + if (doc->get_links(&links) == S_OK) + { + long lLinks; + if (links->get_length(&lLinks) == S_OK && lLinks > 0) + { + iURLElements += lLinks; + CComVariant vaIndex((long)0); + CComVariant vaNull((long)0); + for (long i = 0; i < lLinks; i++) + { + vaIndex.lVal = i; + CComPtr item; + if (links->item(vaIndex, vaNull, &item) == S_OK) + { + CComPtr anchor; + if (SUCCEEDED(item->QueryInterface(&anchor))) + { + CComBSTR bstrHref; + if (anchor->get_href(&bstrHref) == S_OK && bstrHref.Length() > 0 && IsUrlSchemeSupportedW(bstrHref)) + { + theApp.emuledlg->ProcessED2KLink(CString(bstrHref)); + hrPasteResult = S_OK; + } + anchor.Release(); // conserve memory + } + } + } + } + links.Release(); // conserve memory + } + + // + // Text + // + // The explicit handling of text is needed, if we're looking at contents which were copied + // to the clipboard in HTML format -- although it is simple raw text!! This situation applies, + // if the user opens the "View Partial Source" HTML window for some selected HTML contents, + // and copies some text (e.g. an URL) to the clipboard. In that case we'll get the raw text + // as HTML contents!!! + // + // PROBLEM: We can *not* always process the HTML elements (anchors, ...) *and* the inner text. + // The following example (a rather *usual* one) would lead to the adding of the same URL twice + // because the URL is noted as a HREF *and* as the inner text. + // + //

http://www.domain.com/image.gif

+ // + // So, in practice, the examination of the 'innerText' is only done, if there were no other + // HTML elements in the document. + // + if (iURLElements == 0) + { + CComPtr el; + if (doc->get_body(&el) == S_OK) + { + CComBSTR bstr; + if (el->get_innerText(&bstr) == S_OK && bstr.Length() > 0) + { + LPCWSTR pwsz = bstr; + while (*pwsz != L'\0' && iswspace(*pwsz)) // Skip white spaces + pwsz++; + + // PROBLEM: The 'innerText' does not contain any HTML tags, but it *MAY* contain + // HTML comments like "...". Those + // tags have to be explicitly parsed to get the real raw text contents. + // Those Start- and End-tags are available if the text is copied into the clipboard + // from a HTML window which was open with "View Partial Source"! + static const WCHAR _wszStartFrag[] = L""; + if (wcsncmp(pwsz, _wszStartFrag, ARRSIZE(_wszStartFrag)-1) == 0) + { + pwsz += ARRSIZE(_wszStartFrag)-1; + + // If there's a Start-tag, search for an End-tag. + static const WCHAR _wszEndFrag[] = L""; + LPWSTR pwszEnd = (LPWSTR)bstr + bstr.Length(); + pwszEnd -= ARRSIZE(_wszEndFrag)-1; + if (pwszEnd >= pwsz) + { + if (wcsncmp(pwszEnd, _wszEndFrag, ARRSIZE(_wszEndFrag)-1) == 0) + *pwszEnd = L'\0'; // Ugly but efficient, terminate the BSTR! + } + } + + // Search all white-space terminated strings and check for a valid URL-scheme + while (*pwsz != L'\0') + { + while (*pwsz != L'\0' && iswspace(*pwsz)) // Skip white spaces + pwsz++; + + if (IsUrlSchemeSupportedW(pwsz)) + { + LPCWSTR pwszEnd = pwsz; + while (*pwszEnd != L'\0' && !iswspace(*pwszEnd)) // Search next white space (end of current string) + pwszEnd++; + int iLen = pwszEnd - pwsz; + if (iLen > 0) + { + CString strURL(pwsz, iLen); + theApp.emuledlg->ProcessED2KLink(strURL); + hrPasteResult = S_OK; + pwsz += iLen; + } + } + else + { + while (*pwsz != L'\0' && !iswspace(*pwsz)) // Search next white space (end of current string) + pwsz++; + } + + while (*pwsz != L'\0' && iswspace(*pwsz)) // Skip white spaces + pwsz++; + } + } + } + } + + return hrPasteResult; +} + +HRESULT CMainFrameDropTarget::PasteHTML(PASTEURLDATA* pPaste) +{ + HRESULT hrPasteResult = S_FALSE; // default: nothing was pasted + if (pPaste->m_bstrURLs[0] != L'\0') + { + HRESULT hr; + CComPtr doc; + if (SUCCEEDED(hr = doc.CoCreateInstance(CLSID_HTMLDocument, NULL))) + { + SAFEARRAY* psfHtmlLines = SafeArrayCreateVector(VT_VARIANT, 0, 1); + if (psfHtmlLines != NULL) + { + VARIANT* pva; + if (SafeArrayAccessData(psfHtmlLines, (void**)&pva) == S_OK) + { + pva->vt = VT_BSTR; + pva->bstrVal = pPaste->m_bstrURLs; + VERIFY( SafeArrayUnaccessData(psfHtmlLines) == S_OK ); + + // Build the HTML document + // + // NOTE: 'bstrHTML' may contain a complete HTML document (see CF_HTML) or + // just a fragment (without , , ... tags). + // + // WOW! We even can pump partially (but well defined) HTML stuff into the + // document (e.g. contents without , ...) *and* we are capable + // of accessing the HTML object model (can use 'get_links'...) + if ((hr = doc->write(psfHtmlLines)) == S_OK) + hrPasteResult = PasteHTMLDocument(doc, pPaste); + else + hrPasteResult = E_FAIL; + } + else + hrPasteResult = E_OUTOFMEMORY; + + // Destroy the array *and* all of the data (BSTRs!) + if (SafeArrayAccessData(psfHtmlLines, (void**)&pva) == S_OK) + { + // 'Remove' the BSTR which was specified before, to *NOT* have it deleted by 'SafeArrayDestroy' + pva->vt = VT_NULL; + pva->bstrVal = NULL; + VERIFY( SafeArrayUnaccessData(psfHtmlLines) == S_OK ); + } + VERIFY( SafeArrayDestroy(psfHtmlLines) == S_OK ); + } + else + hrPasteResult = E_OUTOFMEMORY; + } + else + hrPasteResult = E_FAIL; + } + return hrPasteResult; +} + +HRESULT CMainFrameDropTarget::PasteHTML(COleDataObject& data) +{ + HRESULT hrPasteResult = E_FAIL; + HGLOBAL hMem; + if ((hMem = data.GetGlobalData(m_cfHTML)) != NULL) + { + LPCSTR pszClipboard; + if ((pszClipboard = (LPCSTR)GlobalLock(hMem)) != NULL) + { + hrPasteResult = S_FALSE; // default: nothing was pasted + LPCSTR pszHTML = strchr(pszClipboard, '<'); + if (pszHTML != NULL) + { + CComBSTR bstrHTMLText((LPCOLESTR)CA2W(pszHTML)); + PASTEURLDATA Paste(bstrHTMLText); + hrPasteResult = PasteHTML(&Paste); + } + GlobalUnlock(hMem); + } + GlobalFree(hMem); + } + return hrPasteResult; +} + +HRESULT CMainFrameDropTarget::PasteText(CLIPFORMAT cfData, COleDataObject& data) +{ + HRESULT hrPasteResult = E_FAIL; + HANDLE hMem; + if ((hMem = data.GetGlobalData(cfData)) != NULL) + { + LPCSTR pszUrlA; + if ((pszUrlA = (LPCSTR)GlobalLock(hMem)) != NULL) + { + // skip white space + while (isspace((unsigned char)*pszUrlA)) + pszUrlA++; + + hrPasteResult = S_FALSE; // default: nothing was pasted + if (_strnicmp(pszUrlA, "ed2k://|", 8) == 0) + { + CString strData(pszUrlA); + int iPos = 0; + CString str = strData.Tokenize(_T("\r\n"), iPos); + while (!str.IsEmpty()) + { + theApp.emuledlg->ProcessED2KLink(str); + hrPasteResult = S_OK; + str = strData.Tokenize(_T("\r\n"), iPos); + } + } + + GlobalUnlock(hMem); + } + GlobalFree(hMem); + } + return hrPasteResult; +} + +HRESULT CMainFrameDropTarget::AddUrlFileContents(LPCTSTR pszFileName) +{ + HRESULT hrResult = S_FALSE; + + TCHAR szExt[_MAX_EXT]; + _tsplitpath(pszFileName, NULL, NULL, NULL, szExt); + if (_tcsicmp(szExt, FILEEXTDOT_INETSHRTCUT) == 0) + { + CComPtr pIUrl; + if (SUCCEEDED(hrResult = CoCreateInstance(CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER, IID_IUniformResourceLocatorW, (void**)&pIUrl))) + { + CComPtr pIFile; + if (SUCCEEDED(hrResult = pIUrl.QueryInterface(&pIFile))) + { + if (SUCCEEDED(hrResult = pIFile->Load(CComBSTR(pszFileName), STGM_READ | STGM_SHARE_DENY_WRITE))) + { + LPWSTR pwszUrl; + if ((hrResult = pIUrl->GetURL(&pwszUrl)) == S_OK) + { + if (pwszUrl != NULL && pwszUrl[0] != L'\0' && IsUrlSchemeSupportedW(pwszUrl)) + { + theApp.emuledlg->ProcessED2KLink(pwszUrl); + hrResult = S_OK; + } + ::CoTaskMemFree(pwszUrl); + } + } + } + } + } + + return hrResult; +} + +HRESULT CMainFrameDropTarget::PasteHDROP(COleDataObject &data) +{ + HRESULT hrPasteResult = E_FAIL; + HANDLE hMem; + if ((hMem = data.GetGlobalData(CF_HDROP)) != NULL) + { + LPDROPFILES lpDrop; + if ((lpDrop = (LPDROPFILES)GlobalLock(hMem)) != NULL) + { + if (lpDrop->fWide) + { + LPCWSTR pszFileNameW = (LPCWSTR)((LPBYTE)lpDrop + lpDrop->pFiles); + while (*pszFileNameW != L'\0') + { + if (FAILED(AddUrlFileContents(pszFileNameW))) + break; + hrPasteResult = S_OK; + pszFileNameW += wcslen(pszFileNameW) + 1; + } + } + else + { + LPCSTR pszFileNameA = (LPCSTR)((LPBYTE)lpDrop + lpDrop->pFiles); + while (*pszFileNameA != '\0') + { + if (FAILED(AddUrlFileContents(CString(pszFileNameA)))) + break; + hrPasteResult = S_OK; + pszFileNameA += strlen(pszFileNameA) + 1; + } + } + GlobalUnlock(hMem); + } + GlobalFree(hMem); + } + return hrPasteResult; +} + +BOOL CMainFrameDropTarget::IsSupportedDropData(COleDataObject* pDataObject) +{ + BOOL bResult; + + //************************************************************************ + //*** THIS FUNCTION HAS TO BE AS FAST AS POSSIBLE!!! + //************************************************************************ + + if (m_cfHTML && pDataObject->IsDataAvailable(m_cfHTML)) + { + // If the data is in 'HTML Format', there is no need to check the contents. + bResult = TRUE; + } + else if (m_cfShellURL && pDataObject->IsDataAvailable(m_cfShellURL)) + { + // If the data is in 'UniformResourceLocator', there is no need to check the contents. + bResult = TRUE; + } + else if (pDataObject->IsDataAvailable(CF_TEXT)) + { + // + // Check text data + // + bResult = FALSE; + + HANDLE hMem; + if ((hMem = pDataObject->GetGlobalData(CF_TEXT)) != NULL) + { + LPCSTR lpszUrl; + if ((lpszUrl = (LPCSTR)GlobalLock(hMem)) != NULL) + { + // skip white space + while (isspace((unsigned char)*lpszUrl)) + lpszUrl++; + bResult = IsUrlSchemeSupportedA(lpszUrl); + GlobalUnlock(hMem); + } + GlobalFree(hMem); + } + } + else if (pDataObject->IsDataAvailable(CF_HDROP)) + { + // + // Check HDROP data + // + bResult = FALSE; + + HANDLE hMem; + if ((hMem = pDataObject->GetGlobalData(CF_HDROP)) != NULL) + { + LPDROPFILES lpDrop; + if ((lpDrop = (LPDROPFILES)GlobalLock(hMem)) != NULL) + { + // Just check, if there's at least one file we can import + if (lpDrop->fWide) + { + LPCWSTR pszFileW = (LPCWSTR)((LPBYTE)lpDrop + lpDrop->pFiles); + while (*pszFileW != L'\0') + { + int iLen = wcslen(pszFileW); + LPCWSTR pszExtW = GetFileExtW(pszFileW, iLen); + if (pszExtW != NULL && _wcsicmp(pszExtW, FILEEXTDOT_INETSHRTCUTW) == 0) + { + bResult = TRUE; + break; + } + pszFileW += iLen + 1; + } + } + else + { + LPCSTR pszFileA = (LPCSTR)((LPBYTE)lpDrop + lpDrop->pFiles); + while (*pszFileA != '\0') + { + int iLen = strlen(pszFileA); + LPCSTR pszExtA = GetFileExtA(pszFileA, iLen); + if (pszExtA != NULL && _stricmp(pszExtA, FILEEXTDOT_INETSHRTCUTA) == 0) + { + bResult = TRUE; + break; + } + pszFileA += iLen + 1; + } + } + GlobalUnlock(hMem); + } + GlobalFree(hMem); + } + } + else + { + // Unknown data format + bResult = FALSE; + } + + return bResult; +} + +DROPEFFECT CMainFrameDropTarget::OnDragEnter(CWnd*, COleDataObject* pDataObject, DWORD, CPoint) +{ + m_bDropDataValid = IsSupportedDropData(pDataObject); + return m_bDropDataValid ? DROPEFFECT_COPY : DROPEFFECT_NONE; +} + +DROPEFFECT CMainFrameDropTarget::OnDragOver(CWnd*, COleDataObject*, DWORD, CPoint) +{ + return m_bDropDataValid ? DROPEFFECT_COPY : DROPEFFECT_NONE; +} + +BOOL CMainFrameDropTarget::OnDrop(CWnd*, COleDataObject* pDataObject, DROPEFFECT /*dropEffect*/, CPoint /*point*/) +{ + BOOL bResult = FALSE; + if (m_bDropDataValid) + { + if (m_cfHTML && pDataObject->IsDataAvailable(m_cfHTML)) + { + PasteHTML(*pDataObject); + } + else if (m_cfShellURL && pDataObject->IsDataAvailable(m_cfShellURL)) + { + PasteText(m_cfShellURL, *pDataObject); + } + else if (pDataObject->IsDataAvailable(CF_TEXT)) + { + PasteText(CF_TEXT, *pDataObject); + } + else if (pDataObject->IsDataAvailable(CF_HDROP)) + { + return PasteHDROP(*pDataObject) == S_OK; + } + bResult = TRUE; + } + return bResult; +} + +void CMainFrameDropTarget::OnDragLeave(CWnd*) +{ + // Do *NOT* set 'm_bDropDataValid=FALSE'! + // 'OnDragLeave' may be called from MFC when scrolling! In that case it's + // not really a "leave". + //m_bDropDataValid = FALSE; +} diff --git a/DropTarget.h b/DropTarget.h new file mode 100644 index 00000000..cb243b29 --- /dev/null +++ b/DropTarget.h @@ -0,0 +1,30 @@ +#pragma once + +struct PASTEURLDATA; + +////////////////////////////////////////////////////////////////////////////// +// CMainFrameDropTarget + +class CMainFrameDropTarget : public COleDropTarget +{ +public: + CMainFrameDropTarget(); + + virtual DROPEFFECT OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point); + virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point); + virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point); + virtual void OnDragLeave(CWnd* pWnd); + +protected: + BOOL m_bDropDataValid; + CLIPFORMAT m_cfHTML; + CLIPFORMAT m_cfShellURL; + + BOOL IsSupportedDropData(COleDataObject* pDataObject); + HRESULT PasteHTMLDocument(IHTMLDocument2* doc, PASTEURLDATA* pPaste); + HRESULT PasteHTML(PASTEURLDATA* pPaste); + HRESULT PasteHTML(COleDataObject &data); + HRESULT PasteText(CLIPFORMAT cfData, COleDataObject& data); + HRESULT PasteHDROP(COleDataObject &data); + HRESULT AddUrlFileContents(LPCTSTR pszFileName); +}; diff --git a/ED2KLink.cpp b/ED2KLink.cpp new file mode 100644 index 00000000..4448c738 --- /dev/null +++ b/ED2KLink.cpp @@ -0,0 +1,626 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include +#include "resource.h" +#include "ED2KLink.h" +#include "OtherFunctions.h" +#include "SafeFile.h" +#include "StringConversion.h" +#include "opcodes.h" +#include "preferences.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +namespace { + struct autoFree { + autoFree(TCHAR* p) : m_p(p) {} + ~autoFree() { free(m_p); } + private: + TCHAR * m_p; + }; + inline unsigned int FromHexDigit(TCHAR digit) { + switch (digit) { + case _T('0'): return 0; + case _T('1'): return 1; + case _T('2'): return 2; + case _T('3'): return 3; + case _T('4'): return 4; + case _T('5'): return 5; + case _T('6'): return 6; + case _T('7'): return 7; + case _T('8'): return 8; + case _T('9'): return 9; + case _T('A'): return 10; + case _T('B'): return 11; + case _T('C'): return 12; + case _T('D'): return 13; + case _T('E'): return 14; + case _T('F'): return 15; + case _T('a'): return 10; + case _T('b'): return 11; + case _T('c'): return 12; + case _T('d'): return 13; + case _T('e'): return 14; + case _T('f'): return 15; + default: throw GetResString(IDS_ERR_ILLFORMEDHASH); + } + } +} + +CED2KLink::~CED2KLink() +{ +} + +///////////////////////////////////////////// +// CED2KServerListLink implementation +///////////////////////////////////////////// +CED2KServerListLink::CED2KServerListLink(const TCHAR* address) +{ + m_address = address; +} + +CED2KServerListLink::~CED2KServerListLink() +{ +} + +void CED2KServerListLink::GetLink(CString& lnk) const +{ + lnk.Format(_T("ed2k://|serverlist|%s|/"), m_address); +} + +CED2KServerListLink* CED2KServerListLink::GetServerListLink() +{ + return this; +} + +CED2KServerLink* CED2KServerListLink::GetServerLink() +{ + return NULL; +} + +CED2KFileLink* CED2KServerListLink::GetFileLink() +{ + return NULL; +} + +CED2KLink::LinkType CED2KServerListLink::GetKind() const +{ + return kServerList; +} + +///////////////////////////////////////////// +// CED2KNodesListLink implementation +///////////////////////////////////////////// +CED2KNodesListLink::CED2KNodesListLink(const TCHAR* address) +{ + m_address = address; +} + +CED2KNodesListLink::~CED2KNodesListLink() +{ +} + +void CED2KNodesListLink::GetLink(CString& lnk) const +{ + lnk.Format(_T("ed2k://|nodeslist|%s|/"), m_address); +} + +CED2KServerListLink* CED2KNodesListLink::GetServerListLink() +{ + return NULL; +} + +CED2KNodesListLink* CED2KNodesListLink::GetNodesListLink() +{ + return this; +} + +CED2KServerLink* CED2KNodesListLink::GetServerLink() +{ + return NULL; +} + +CED2KFileLink* CED2KNodesListLink::GetFileLink() +{ + return NULL; +} + +CED2KLink::LinkType CED2KNodesListLink::GetKind() const +{ + return kNodesList; +} + +///////////////////////////////////////////// +// CED2KServerLink implementation +///////////////////////////////////////////// +CED2KServerLink::CED2KServerLink(const TCHAR* ip, const TCHAR* port) +{ + m_strAddress = ip; + unsigned long ul = _tcstoul(port, 0, 10); + if (ul > 0xFFFF) + throw GetResString(IDS_ERR_BADPORT); + m_port = static_cast(ul); + m_defaultName = _T("Server "); + m_defaultName += ip; + m_defaultName += _T(":"); + m_defaultName += port; +} + +CED2KServerLink::~CED2KServerLink() +{ +} + +void CED2KServerLink::GetLink(CString& lnk) const +{ + lnk.Format(_T("ed2k://|server|%s|%u|/"), GetAddress(), (UINT)GetPort()); +} + +CED2KServerListLink* CED2KServerLink::GetServerListLink() +{ + return NULL; +} + +CED2KServerLink* CED2KServerLink::GetServerLink() +{ + return this; +} + +CED2KFileLink* CED2KServerLink::GetFileLink() +{ + return NULL; +} + +CED2KLink::LinkType CED2KServerLink::GetKind() const +{ + return kServer; +} + +///////////////////////////////////////////// +// CED2KSearchLink implementation +///////////////////////////////////////////// +CED2KSearchLink::CED2KSearchLink(const TCHAR* pszSearchTerm) +{ + m_strSearchTerm = OptUtf8ToStr(URLDecode(pszSearchTerm)); +} + +CED2KSearchLink::~CED2KSearchLink() +{ +} + +void CED2KSearchLink::GetLink(CString& lnk) const +{ + lnk.Format(_T("ed2k://|search|%s|/"), EncodeUrlUtf8(m_strSearchTerm)); +} + +CED2KServerListLink* CED2KSearchLink::GetServerListLink() +{ + return NULL; +} + +CED2KSearchLink* CED2KSearchLink::GetSearchLink() +{ + return this; +} + +CED2KServerLink* CED2KSearchLink::GetServerLink() +{ + return NULL; +} + +CED2KFileLink* CED2KSearchLink::GetFileLink() +{ + return NULL; +} + +CED2KNodesListLink* CED2KSearchLink::GetNodesListLink() +{ + return NULL; +} + +CED2KLink::LinkType CED2KSearchLink::GetKind() const +{ + return kSearch; +} + + +///////////////////////////////////////////// +// CED2KFileLink implementation +///////////////////////////////////////////// +CED2KFileLink::CED2KFileLink(const TCHAR* pszName, const TCHAR* pszSize, const TCHAR* pszHash, + const CStringArray& astrParams, const TCHAR* pszSources) + : m_size(pszSize) +{ + // Here we have a little problem.. Actually the proper solution would be to decode from UTF8, + // only if the string does contain escape sequences. But if user pastes a raw UTF8 encoded + // string (for whatever reason), we would miss to decode that string. On the other side, + // always decoding UTF8 can give flaws in case the string is valid for Unicode and UTF8 + // at the same time. However, to avoid the pasting of raw UTF8 strings (which would lead + // to a greater mess in the network) we always try to decode from UTF8, even if the string + // did not contain escape sequences. + m_name = OptUtf8ToStr(URLDecode(pszName)); + m_name.Trim(); + if (m_name.IsEmpty()) + throw GetResString(IDS_ERR_NOTAFILELINK); + + SourcesList = NULL; + m_hashset = NULL; + m_bAICHHashValid = false; + + if (_tcslen(pszHash) != 32) + throw GetResString(IDS_ERR_ILLFORMEDHASH); + + if (_tstoi64(pszSize) >= MAX_EMULE_FILE_SIZE) + throw GetResString(IDS_ERR_TOOLARGEFILE); + if (_tstoi64(pszSize)<=0) + throw GetResString(IDS_ERR_NOTAFILELINK); + if (_tstoi64(pszSize) > OLD_MAX_EMULE_FILE_SIZE && !thePrefs.CanFSHandleLargeFiles(0)) + throw GetResString(IDS_ERR_FSCANTHANDLEFILE); + + for (int idx = 0; idx < 16; ++idx) { + m_hash[idx] = (BYTE)(FromHexDigit(*pszHash++)*16); + m_hash[idx] = (BYTE)(m_hash[idx] + FromHexDigit(*pszHash++)); + } + + bool bError = false; + for (int i = 0; !bError && i < astrParams.GetCount(); i++) + { + const CString& strParam = astrParams.GetAt(i); + ASSERT( !strParam.IsEmpty() ); + + CString strTok; + int iPos = strParam.Find(_T('=')); + if (iPos != -1) + strTok = strParam.Left(iPos); + if (strTok == _T("s")) + { + CString strURL = strParam.Mid(iPos + 1); + if (!strURL.IsEmpty()) + { + TCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH]; + TCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH]; + TCHAR szUrlPath[INTERNET_MAX_PATH_LENGTH]; + TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; + TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH]; + TCHAR szExtraInfo[INTERNET_MAX_URL_LENGTH]; + URL_COMPONENTS Url = {0}; + Url.dwStructSize = sizeof(Url); + Url.lpszScheme = szScheme; + Url.dwSchemeLength = ARRSIZE(szScheme); + Url.lpszHostName = szHostName; + Url.dwHostNameLength = ARRSIZE(szHostName); + Url.lpszUserName = szUserName; + Url.dwUserNameLength = ARRSIZE(szUserName); + Url.lpszPassword = szPassword; + Url.dwPasswordLength = ARRSIZE(szPassword); + Url.lpszUrlPath = szUrlPath; + Url.dwUrlPathLength = ARRSIZE(szUrlPath); + Url.lpszExtraInfo = szExtraInfo; + Url.dwExtraInfoLength = ARRSIZE(szExtraInfo); + if (InternetCrackUrl(strURL, 0, 0, &Url) && Url.dwHostNameLength > 0 && Url.dwHostNameLength < INTERNET_MAX_HOST_NAME_LENGTH) + { + SUnresolvedHostname* hostname = new SUnresolvedHostname; + hostname->strURL = strURL; + hostname->strHostname = szHostName; + m_HostnameSourcesList.AddTail(hostname); + } + } + else + ASSERT(0); + } + else if (strTok == _T("p")) + { + CString strPartHashs = strParam.Tokenize(_T("="), iPos); + + if (m_hashset != NULL){ + ASSERT(0); + bError = true; + break; + } + + m_hashset = new CSafeMemFile(256); + m_hashset->WriteHash16(m_hash); + m_hashset->WriteUInt16(0); + + int iPartHashs = 0; + int iPosPH = 0; + CString strHash = strPartHashs.Tokenize(_T(":"), iPosPH); + while (!strHash.IsEmpty()) + { + uchar aucPartHash[16]; + if (!strmd4(strHash, aucPartHash)){ + bError = true; + break; + } + m_hashset->WriteHash16(aucPartHash); + iPartHashs++; + strHash = strPartHashs.Tokenize(_T(":"), iPosPH); + } + if (bError) + break; + + m_hashset->Seek(16, CFile::begin); + m_hashset->WriteUInt16((uint16)iPartHashs); + m_hashset->Seek(0, CFile::begin); + } + else if (strTok == _T("h")) + { + CString strHash = strParam.Mid(iPos + 1); + if (!strHash.IsEmpty()) + { + if (DecodeBase32(strHash, m_AICHHash.GetRawHash(), CAICHHash::GetHashSize()) == (UINT)CAICHHash::GetHashSize()){ + m_bAICHHashValid = true; + ASSERT( m_AICHHash.GetString().CompareNoCase(strHash) == 0 ); + } + else + ASSERT( false ); + } + else + ASSERT( false ); + } + else + ASSERT(0); + } + + if (bError) + { + delete m_hashset; + m_hashset = NULL; + } + + if (pszSources) + { + TCHAR* pNewString = _tcsdup(pszSources); + autoFree liberator(pNewString); + TCHAR* pCh = pNewString; + TCHAR* pEnd; + TCHAR* pIP; + TCHAR* pPort; + + bool bAllowSources; + TCHAR date[3]; + COleDateTime expirationDate; + int nYear,nMonth,nDay; + + uint16 nCount = 0; + uint32 dwID; + uint16 nPort; + uint32 dwServerIP = 0; + uint16 nServerPort = 0; + unsigned long ul; + + int nInvalid = 0; + + pCh = _tcsstr( pCh, _T("sources") ); + if( pCh != NULL ) { + pCh = pCh + 7; // point to char after "sources" + pEnd = pCh; + while( *pEnd ) pEnd++; // make pEnd point to the terminating NULL + bAllowSources=true; + // if there's an expiration date... + if( *pCh == _T('@') && (pEnd-pCh) > 7 ) + { + pCh++; // after '@' + date[2] = 0; // terminate the two character string + date[0] = *(pCh++); date[1] = *(pCh++); + nYear = _tcstol( date, 0, 10 ) + 2000; + date[0] = *(pCh++); date[1] = *(pCh++); + nMonth = _tcstol( date, 0, 10 ); + date[0] = *(pCh++); date[1] = *(pCh++); + nDay = _tcstol( date, 0, 10 ); + bAllowSources = ( expirationDate.SetDate(nYear,nMonth,nDay) == 0 ); + if (bAllowSources) bAllowSources=(COleDateTime::GetCurrentTime() < expirationDate); + } + + // increment pCh to point to the first "ip:port" and check for sources + if ( bAllowSources && ++pCh < pEnd ) { + SourcesList = new CSafeMemFile(256); + SourcesList->WriteUInt16(nCount); // init to 0, we'll fix this at the end. + // for each "ip:port" source string until the end + // limit to prevent overflow (uint16 due to CPartFile::AddClientSources) + while( *pCh != 0 && nCount < MAXSHORT ) { + pIP = pCh; + // find the end of this ip:port string & start of next ip:port string. + if( (pCh = _tcschr(pCh, _T(','))) != NULL ) { + *pCh = 0; // terminate current "ip:port" + pCh++; // point to next "ip:port" + } + else + pCh = pEnd; + + // if port is not present for this ip, go to the next ip. + if( (pPort = _tcschr(pIP, _T(':'))) == NULL ) + { nInvalid++; continue; } + + *pPort = 0; // terminate ip string + pPort++; // point pPort to port string. + + dwID = inet_addr(CStringA(pIP)); + ul = _tcstoul( pPort, 0, 10 ); + nPort = static_cast(ul); + + // skip bad ips / ports + if (ul > 0xFFFF || ul == 0 ) // port + { nInvalid++; continue; } + if( dwID == INADDR_NONE) { // hostname? + if (_tcslen(pIP) > 512) + { nInvalid++; continue; } + SUnresolvedHostname* hostname = new SUnresolvedHostname; + hostname->nPort = nPort; + hostname->strHostname = pIP; + m_HostnameSourcesList.AddTail(hostname); + continue; + } + //TODO: This will filter out *.*.*.0 clients. Is there a nice way to fix? + if( IsLowID(dwID) ) // ip + { nInvalid++; continue; } + + SourcesList->WriteUInt32(dwID); + SourcesList->WriteUInt16(nPort); + SourcesList->WriteUInt32(dwServerIP); + SourcesList->WriteUInt16(nServerPort); + nCount++; + } + SourcesList->SeekToBegin(); + SourcesList->WriteUInt16(nCount); + SourcesList->SeekToBegin(); + if (nCount==0) { + delete SourcesList; + SourcesList=NULL; + } + } + } + } +} + +CED2KFileLink::~CED2KFileLink() +{ + delete SourcesList; + while (!m_HostnameSourcesList.IsEmpty()) + delete m_HostnameSourcesList.RemoveHead(); + delete m_hashset; +} + +void CED2KFileLink::GetLink(CString& lnk) const +{ + lnk = _T("ed2k://|file|"); + lnk += EncodeUrlUtf8(m_name); + lnk += _T("|"); + lnk += m_size; + lnk += _T("|"); + for (int idx=0; idx != 16 ; ++idx ) { + unsigned int ui1 = m_hash[idx] / 16; + unsigned int ui2 = m_hash[idx] % 16; + lnk+= static_cast( ui1 > 9 ? (_T('0')+ui1) : (_T('A')+(ui1-10)) ); + lnk+= static_cast( ui2 > 9 ? (_T('0')+ui2) : (_T('A')+(ui2-10)) ); + } + lnk += _T("|/"); +} + +CED2KServerListLink* CED2KFileLink::GetServerListLink() +{ + return NULL; +} + +CED2KServerLink* CED2KFileLink::GetServerLink() +{ + return NULL; +} + +CED2KFileLink* CED2KFileLink::GetFileLink() +{ + return this; +} + +CED2KLink::LinkType CED2KFileLink::GetKind() const +{ + return kFile; +} + +CED2KLink* CED2KLink::CreateLinkFromUrl(const TCHAR* uri) +{ + CString strURI(uri); + strURI.Trim(); // This function is used for various sources, trim the string again. + int iPos = 0; + CString strTok = GetNextString(strURI, _T("|"), iPos); + if (strTok.CompareNoCase(_T("ed2k://")) == 0) + { + strTok = GetNextString(strURI, _T("|"), iPos); + if (strTok == _T("file")) + { + CString strName = GetNextString(strURI, _T("|"), iPos); + if (!strName.IsEmpty()) + { + CString strSize = GetNextString(strURI, _T("|"), iPos); + if (!strSize.IsEmpty()) + { + CString strHash = GetNextString(strURI, _T("|"), iPos); + if (!strHash.IsEmpty()) + { + CStringArray astrEd2kParams; + bool bEmuleExt = false; + CString strEmuleExt; + + CString strLastTok; + strTok = GetNextString(strURI, _T("|"), iPos); + while (!strTok.IsEmpty()) + { + strLastTok = strTok; + if (strTok == _T("/")) + { + if (bEmuleExt) + break; + bEmuleExt = true; + } + else + { + if (bEmuleExt) + { + if (!strEmuleExt.IsEmpty()) + strEmuleExt += _T('|'); + strEmuleExt += strTok; + } + else + astrEd2kParams.Add(strTok); + } + strTok = GetNextString(strURI, _T("|"), iPos); + } + + if (strLastTok == _T("/")) + return new CED2KFileLink(strName, strSize, strHash, astrEd2kParams, strEmuleExt.IsEmpty() ? (LPCTSTR)NULL : (LPCTSTR)strEmuleExt); + } + } + } + } + else if (strTok == _T("serverlist")) + { + CString strURL = GetNextString(strURI, _T("|"), iPos); + if (!strURL.IsEmpty() && GetNextString(strURI, _T("|"), iPos) == _T("/")) + return new CED2KServerListLink(strURL); + } + else if (strTok == _T("server")) + { + CString strServer = GetNextString(strURI, _T("|"), iPos); + if (!strServer.IsEmpty()) + { + CString strPort = GetNextString(strURI, _T("|"), iPos); + if (!strPort.IsEmpty() && GetNextString(strURI, _T("|"), iPos) == _T("/")) + return new CED2KServerLink(strServer, strPort); + } + } + else if (strTok == _T("nodeslist")) + { + CString strURL = GetNextString(strURI, _T("|"), iPos); + if (!strURL.IsEmpty() && GetNextString(strURI, _T("|"), iPos) == _T("/")) + return new CED2KNodesListLink(strURL); + } + else if (strTok == _T("search")) + { + CString strSearchTerm = GetNextString(strURI, _T("|"), iPos); + // might be extended with more parameters in future versions + if (!strSearchTerm.IsEmpty()) + return new CED2KSearchLink(strSearchTerm); + } + } + + throw GetResString(IDS_ERR_NOSLLINK); +} diff --git a/ED2KLink.h b/ED2KLink.h new file mode 100644 index 00000000..cabcd8bd --- /dev/null +++ b/ED2KLink.h @@ -0,0 +1,177 @@ +//this file is part of eMule +// +//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. +#pragma once +#include "shahashset.h" +class CSafeMemFile; + +struct SUnresolvedHostname +{ + SUnresolvedHostname() + { + nPort = 0; + } + CStringA strHostname; + uint16 nPort; + CString strURL; +}; + + +class CED2KLink +{ +public: + static CED2KLink* CreateLinkFromUrl(const TCHAR* url); + virtual ~CED2KLink(); + + typedef enum { kServerList, kServer , kFile , kNodesList, kSearch, kInvalid } LinkType; + + virtual LinkType GetKind() const = 0; + virtual void GetLink(CString& lnk) const = 0; + virtual class CED2KServerListLink* GetServerListLink() = 0; + virtual class CED2KServerLink* GetServerLink() = 0; + virtual class CED2KFileLink* GetFileLink() = 0; + virtual class CED2KNodesListLink* GetNodesListLink() = 0; + virtual class CED2KSearchLink* GetSearchLink() = 0; +}; + + +class CED2KServerLink : public CED2KLink +{ +public: + CED2KServerLink(const TCHAR* ip,const TCHAR* port); + virtual ~CED2KServerLink(); + + virtual LinkType GetKind() const; + virtual void GetLink(CString& lnk) const; + virtual CED2KServerListLink* GetServerListLink(); + virtual CED2KServerLink* GetServerLink(); + virtual CED2KFileLink* GetFileLink(); + virtual CED2KNodesListLink* GetNodesListLink() { return NULL; } + virtual CED2KSearchLink* GetSearchLink() { return NULL; } + + const CString& GetAddress() const { return m_strAddress; } + uint16 GetPort() const { return m_port;} + void GetDefaultName(CString& defName) const { defName = m_defaultName; } + +private: + CED2KServerLink(); + CED2KServerLink(const CED2KServerLink&); + CED2KServerLink& operator=(const CED2KServerLink&); + + CString m_strAddress; + uint16 m_port; + CString m_defaultName; +}; + + +class CED2KFileLink : public CED2KLink +{ +public: + CED2KFileLink(const TCHAR* pszName, const TCHAR* pszSize, const TCHAR* pszHash, const CStringArray& param, const TCHAR* pszSources); + virtual ~CED2KFileLink(); + + virtual LinkType GetKind() const; + virtual void GetLink(CString& lnk) const; + virtual CED2KServerListLink* GetServerListLink(); + virtual CED2KServerLink* GetServerLink(); + virtual CED2KFileLink* GetFileLink(); + virtual CED2KNodesListLink* GetNodesListLink() { return NULL; } + virtual CED2KSearchLink* GetSearchLink() { return NULL; } + + const TCHAR* GetName() const { return m_name; } + const uchar* GetHashKey() const { return m_hash;} + const CAICHHash& GetAICHHash() const { return m_AICHHash;} + EMFileSize GetSize() const { return (uint64)_tstoi64(m_size); } + bool HasValidSources() const { return (SourcesList != NULL); } + bool HasHostnameSources() const { return (!m_HostnameSourcesList.IsEmpty()); } + bool HasValidAICHHash() const { return m_bAICHHashValid; } + + CSafeMemFile* SourcesList; + CSafeMemFile* m_hashset; + CTypedPtrList m_HostnameSourcesList; + +private: + CED2KFileLink(); + CED2KFileLink(const CED2KFileLink&); + CED2KFileLink& operator=(const CED2KFileLink&); + + CString m_name; + CString m_size; + uchar m_hash[16]; + bool m_bAICHHashValid; + CAICHHash m_AICHHash; +}; + + +class CED2KServerListLink : public CED2KLink +{ +public: + CED2KServerListLink(const TCHAR* pszAddress); + virtual ~CED2KServerListLink(); + + virtual LinkType GetKind() const; + virtual void GetLink(CString& lnk) const; + virtual CED2KServerListLink* GetServerListLink(); + virtual CED2KServerLink* GetServerLink(); + virtual CED2KFileLink* GetFileLink(); + virtual CED2KNodesListLink* GetNodesListLink() { return NULL; } + virtual CED2KSearchLink* GetSearchLink() { return NULL; } + + const TCHAR* GetAddress() const { return m_address; } + +private: + CString m_address; +}; + + +class CED2KNodesListLink : public CED2KLink +{ +public: + CED2KNodesListLink(const TCHAR* pszAddress); + virtual ~CED2KNodesListLink(); + + virtual LinkType GetKind() const; + virtual void GetLink(CString& lnk) const; + virtual CED2KServerListLink* GetServerListLink(); + virtual CED2KServerLink* GetServerLink(); + virtual CED2KFileLink* GetFileLink(); + virtual CED2KNodesListLink* GetNodesListLink(); + virtual CED2KSearchLink* GetSearchLink() { return NULL; } + + const TCHAR* GetAddress() const { return m_address; } + +private: + CString m_address; +}; + +class CED2KSearchLink : public CED2KLink +{ +public: + CED2KSearchLink(const TCHAR* pszSearchTerm); + virtual ~CED2KSearchLink(); + + virtual LinkType GetKind() const; + virtual void GetLink(CString& lnk) const; + virtual CED2KServerListLink* GetServerListLink(); + virtual CED2KServerLink* GetServerLink(); + virtual CED2KFileLink* GetFileLink(); + virtual CED2KNodesListLink* GetNodesListLink(); + virtual CED2KSearchLink* GetSearchLink(); + + CString GetSearchTerm() const { return m_strSearchTerm; } + +private: + CString m_strSearchTerm; +}; diff --git a/ED2kLinkDlg.cpp b/ED2kLinkDlg.cpp new file mode 100644 index 00000000..7df6bade --- /dev/null +++ b/ED2kLinkDlg.cpp @@ -0,0 +1,226 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "ED2kLinkDlg.h" +#include "KnownFile.h" +#include "partfile.h" +#include "preferences.h" +#include "shahashset.h" +#include "UserMsgs.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +IMPLEMENT_DYNAMIC(CED2kLinkDlg, CResizablePage) + +BEGIN_MESSAGE_MAP(CED2kLinkDlg, CResizablePage) + ON_BN_CLICKED(IDC_LD_CLIPBOARDBUT, OnBnClickedClipboard) + ON_BN_CLICKED(IDC_LD_SOURCECHE, OnSettingsChange) + ON_BN_CLICKED(IDC_LD_HTMLCHE, OnSettingsChange) + ON_BN_CLICKED(IDC_LD_HOSTNAMECHE, OnSettingsChange) + ON_BN_CLICKED(IDC_LD_HASHSETCHE, OnSettingsChange) + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) +END_MESSAGE_MAP() + +CED2kLinkDlg::CED2kLinkDlg() + : CResizablePage(CED2kLinkDlg::IDD, IDS_CMT_READALL) +{ + m_paFiles = NULL; + m_bDataChanged = false; + m_strCaption = GetResString(IDS_SW_LINK); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; + m_bReducedDlg = false; +} + +CED2kLinkDlg::~CED2kLinkDlg() +{ +} + +void CED2kLinkDlg::DoDataExchange(CDataExchange* pDX) +{ + CResizablePage::DoDataExchange(pDX); + DDX_Control(pDX, IDC_LD_LINKEDI, m_ctrlLinkEdit); +} + +BOOL CED2kLinkDlg::OnInitDialog() +{ + CResizablePage::OnInitDialog(); + InitWindowStyles(this); + + + + if (!m_bReducedDlg) + { + AddAnchor(IDC_LD_BASICGROUP,BOTTOM_LEFT,BOTTOM_RIGHT); + AddAnchor(IDC_LD_SOURCECHE,BOTTOM_LEFT,BOTTOM_LEFT); + AddAnchor(IDC_LD_ADVANCEDGROUP,BOTTOM_LEFT,BOTTOM_RIGHT); + AddAnchor(IDC_LD_HTMLCHE,BOTTOM_LEFT,BOTTOM_LEFT); + AddAnchor(IDC_LD_HASHSETCHE,BOTTOM_LEFT,BOTTOM_LEFT); + AddAnchor(IDC_LD_HOSTNAMECHE,BOTTOM_LEFT,BOTTOM_LEFT); + + // enabled/disable checkbox depending on situation + if (theApp.IsConnected() && !theApp.IsFirewalled()) + GetDlgItem(IDC_LD_SOURCECHE)->EnableWindow(TRUE); + else{ + GetDlgItem(IDC_LD_SOURCECHE)->EnableWindow(FALSE); + ((CButton*)GetDlgItem(IDC_LD_SOURCECHE))->SetCheck(BST_UNCHECKED); + } + if (theApp.IsConnected() && !theApp.IsFirewalled() && !thePrefs.GetYourHostname().IsEmpty() && thePrefs.GetYourHostname().Find(_T('.')) != -1) + GetDlgItem(IDC_LD_HOSTNAMECHE)->EnableWindow(TRUE); + else{ + GetDlgItem(IDC_LD_HOSTNAMECHE)->EnableWindow(FALSE); + ((CButton*)GetDlgItem(IDC_LD_HOSTNAMECHE))->SetCheck(BST_UNCHECKED); + } + } + else + { + CRect rcDefault, rcNew; + GetDlgItem(IDC_LD_LINKGROUP)->GetWindowRect(rcDefault); + GetDlgItem(IDC_LD_ADVANCEDGROUP)->GetWindowRect(rcNew); + int nDeltaY = rcNew.bottom - rcDefault.bottom; + GetDlgItem(IDC_LD_LINKGROUP)->SetWindowPos(NULL, 0, 0, rcDefault.Width(), rcDefault.Height() + nDeltaY, SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOACTIVATE); + GetDlgItem(IDC_LD_LINKEDI)->GetWindowRect(rcDefault); + GetDlgItem(IDC_LD_LINKEDI)->SetWindowPos(NULL, 0, 0, rcDefault.Width(), rcDefault.Height() + nDeltaY, SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOACTIVATE); + GetDlgItem(IDC_LD_CLIPBOARDBUT)->GetWindowRect(rcDefault); + ScreenToClient(rcDefault); + GetDlgItem(IDC_LD_CLIPBOARDBUT)->SetWindowPos(NULL, rcDefault.left, rcDefault.top + nDeltaY, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOACTIVATE); + + GetDlgItem(IDC_LD_BASICGROUP)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_LD_SOURCECHE)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_LD_ADVANCEDGROUP)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_LD_HTMLCHE)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_LD_HASHSETCHE)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_LD_HOSTNAMECHE)->ShowWindow(SW_HIDE); + } + AddAnchor(IDC_LD_LINKGROUP,TOP_LEFT,BOTTOM_RIGHT); + AddAnchor(IDC_LD_LINKEDI,TOP_LEFT,BOTTOM_RIGHT); + AddAnchor(IDC_LD_CLIPBOARDBUT,BOTTOM_RIGHT); + Localize(); + + return TRUE; +} + +BOOL CED2kLinkDlg::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + + if (m_bDataChanged) + { + //hashsetlink - check if at least one file has a hasset + BOOL bShowHashset = FALSE; + BOOL bShowAICH = FALSE; + BOOL bShowHTML = FALSE; + for (int i = 0; i != m_paFiles->GetSize(); i++){ + if (!(*m_paFiles)[i]->IsKindOf(RUNTIME_CLASS(CKnownFile))) + continue; + bShowHTML = TRUE; + CKnownFile* file = STATIC_DOWNCAST(CKnownFile, (*m_paFiles)[i]); + if (file->GetFileIdentifier().GetAvailableMD4PartHashCount() > 0 && file->GetFileIdentifier().HasExpectedMD4HashCount()) + { + bShowHashset = TRUE; + } + if (file->GetFileIdentifier().HasAICHHash()) + { + bShowAICH = TRUE; + } + if (bShowHashset && bShowAICH) + break; + } + GetDlgItem(IDC_LD_HASHSETCHE)->EnableWindow(bShowHashset); + if (!bShowHashset) + ((CButton*)GetDlgItem(IDC_LD_HASHSETCHE))->SetCheck(BST_UNCHECKED); + + GetDlgItem(IDC_LD_HTMLCHE)->EnableWindow(bShowHTML); + + UpdateLink(); + m_bDataChanged = false; + } + + return TRUE; +} + +LRESULT CED2kLinkDlg::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +void CED2kLinkDlg::Localize(void) +{ + GetDlgItem(IDC_LD_LINKGROUP)->SetWindowText(GetResString(IDS_SW_LINK)); + GetDlgItem(IDC_LD_CLIPBOARDBUT)->SetWindowText(GetResString(IDS_LD_COPYCLIPBOARD)); + if (!m_bReducedDlg) + { + GetDlgItem(IDC_LD_BASICGROUP)->SetWindowText(GetResString(IDS_LD_BASICOPT)); + GetDlgItem(IDC_LD_SOURCECHE)->SetWindowText(GetResString(IDS_LD_ADDSOURCE)); + GetDlgItem(IDC_LD_ADVANCEDGROUP)->SetWindowText(GetResString(IDS_LD_ADVANCEDOPT)); + GetDlgItem(IDC_LD_HTMLCHE)->SetWindowText(GetResString(IDS_LD_ADDHTML)); + GetDlgItem(IDC_LD_HASHSETCHE)->SetWindowText(GetResString(IDS_LD_ADDHASHSET)); + GetDlgItem(IDC_LD_HOSTNAMECHE)->SetWindowText(GetResString(IDS_LD_HOSTNAME)); + } +} + +void CED2kLinkDlg::UpdateLink() +{ + CString strLinks; + CString strBuffer; + const bool bHashset = ((CButton*)GetDlgItem(IDC_LD_HASHSETCHE))->GetCheck() == BST_CHECKED; + const bool bHTML = ((CButton*)GetDlgItem(IDC_LD_HTMLCHE))->GetCheck() == BST_CHECKED; + const bool bSource = ((CButton*)GetDlgItem(IDC_LD_SOURCECHE))->GetCheck() == BST_CHECKED && theApp.IsConnected() && theApp.GetPublicIP() != 0 && !theApp.IsFirewalled(); + const bool bHostname = ((CButton*)GetDlgItem(IDC_LD_HOSTNAMECHE))->GetCheck() == BST_CHECKED && theApp.IsConnected() && !theApp.IsFirewalled() + && !thePrefs.GetYourHostname().IsEmpty() && thePrefs.GetYourHostname().Find(_T('.')) != -1; + + for (int i = 0; i != m_paFiles->GetSize(); i++) + { + if (!(*m_paFiles)[i]->IsKindOf(RUNTIME_CLASS(CKnownFile))) + continue; + + if (!strLinks.IsEmpty()) + strLinks += _T("\r\n\r\n"); + + CKnownFile* file = STATIC_DOWNCAST(CKnownFile, (*m_paFiles)[i]); + strLinks += file->GetED2kLink(bHashset, bHTML, bHostname, bSource, theApp.GetPublicIP()); + } + m_ctrlLinkEdit.SetWindowText(strLinks); + +} + +void CED2kLinkDlg::OnBnClickedClipboard() +{ + CString strBuffer; + m_ctrlLinkEdit.GetWindowText(strBuffer); + theApp.CopyTextToClipboard(strBuffer); +} + +void CED2kLinkDlg::OnSettingsChange() +{ + UpdateLink(); +} + +BOOL CED2kLinkDlg::OnCommand(WPARAM wParam, LPARAM lParam) +{ + if (LOWORD(wParam) == IDCANCEL) + return ::SendMessage(::GetParent(m_hWnd), WM_COMMAND, wParam, lParam); + return CResizablePage::OnCommand(wParam, lParam); +} diff --git a/ED2kLinkDlg.h b/ED2kLinkDlg.h new file mode 100644 index 00000000..797d0c26 --- /dev/null +++ b/ED2kLinkDlg.h @@ -0,0 +1,59 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "ResizableLib/ResizablePage.h" + +class CKnownFile; + +/////////////////////////////////////////////////////////////////////////////// +// CED2kLinkDlg + +class CED2kLinkDlg : public CResizablePage +{ + DECLARE_DYNAMIC(CED2kLinkDlg) + +public: + CED2kLinkDlg(); + virtual ~CED2kLinkDlg(); + + void SetFiles(const CSimpleArray* paFiles) { m_paFiles = paFiles; m_bDataChanged = true; } + void SetReducedDialog() { m_bReducedDlg = true; } + +// Dialog Data + enum { IDD = IDD_ED2KLINK }; + +protected: + CEdit m_ctrlLinkEdit; + CString m_strCaption; + const CSimpleArray* m_paFiles; + bool m_bDataChanged; + bool m_bReducedDlg; + + void Localize(); + void UpdateLink(); + + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnSetActive(); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + + DECLARE_MESSAGE_MAP() + afx_msg void OnBnClickedClipboard(); + afx_msg void OnSettingsChange(); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); +}; diff --git a/EMSocket.cpp b/EMSocket.cpp new file mode 100644 index 00000000..3235b411 --- /dev/null +++ b/EMSocket.cpp @@ -0,0 +1,1394 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#ifdef _DEBUG +#include "DebugHelpers.h" +#endif +#include "emule.h" +#include "emsocket.h" +#include "AsyncProxySocketLayer.h" +#include "Packets.h" +#include "OtherFunctions.h" +#include "UploadBandwidthThrottler.h" +#include "Preferences.h" +#include "emuleDlg.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +namespace { + inline void EMTrace(char* fmt, ...) { +#ifdef EMSOCKET_DEBUG + va_list argptr; + char bufferline[512]; + va_start(argptr, fmt); + _vsnprintf(bufferline, 512, fmt, argptr); + va_end(argptr); + //(Ornis+) + char osDate[30],osTime[30]; + char temp[1024]; + _strtime( osTime ); + _strdate( osDate ); + int len = _snprintf(temp,1021,"%s %s: %s",osDate,osTime,bufferline); + temp[len++] = 0x0d; + temp[len++] = 0x0a; + temp[len+1] = 0; + HANDLE hFile = CreateFile("c:\\EMSocket.log", // open MYFILE.TXT + GENERIC_WRITE, // open for reading + FILE_SHARE_READ, // share for reading + NULL, // no security + OPEN_ALWAYS, // existing file only + FILE_ATTRIBUTE_NORMAL, // normal file + NULL); // no attr. template + + if (hFile != INVALID_HANDLE_VALUE) + { + DWORD nbBytesWritten = 0; + SetFilePointer(hFile, 0, NULL, FILE_END); + BOOL b = WriteFile( + hFile, // handle to file + temp, // data buffer + len, // number of bytes to write + &nbBytesWritten, // number of bytes written + NULL // overlapped buffer + ); + CloseHandle(hFile); + } +#else + //va_list argptr; + //va_start(argptr, fmt); + //va_end(argptr); + UNREFERENCED_PARAMETER(fmt); +#endif //EMSOCKET_DEBUG + } +} + +IMPLEMENT_DYNAMIC(CEMSocket, CEncryptedStreamSocket) + +CEMSocket::CEMSocket(void){ + byConnected = ES_NOTCONNECTED; + m_uTimeOut = CONNECTION_TIMEOUT; // default timeout for ed2k sockets + + // Download (pseudo) rate control + downloadLimit = 0; + downloadLimitEnable = false; + pendingOnReceive = false; + + // Download partial header + pendingHeaderSize = 0; + + // Download partial packet + pendingPacket = NULL; + pendingPacketSize = 0; + + // Upload control + sendbuffer = NULL; + sendblen = 0; + sent = 0; + //m_bLinkedPackets = false; + + // deadlake PROXYSUPPORT + m_pProxyLayer = NULL; + m_bProxyConnectFailed = false; + + //m_startSendTick = 0; + //m_lastSendLatency = 0; + //m_latency_sum = 0; + //m_wasBlocked = false; + + m_currentPacket_is_controlpacket = false; + m_currentPackageIsFromPartFile = false; + + m_numberOfSentBytesCompleteFile = 0; + m_numberOfSentBytesPartFile = 0; + m_numberOfSentBytesControlPacket = 0; + + lastCalledSend = ::GetTickCount(); + lastSent = ::GetTickCount()-1000; + + m_bAccelerateUpload = false; + + m_actualPayloadSize = 0; + m_actualPayloadSizeSent = 0; + + m_bBusy = false; + m_hasSent = false; + m_bUsesBigSendBuffers = false; + m_pPendingSendOperation = NULL; + // Overlapped sockets are under investigations for buggyness (heap corruption), only use for testing + m_bOverlappedSending = thePrefs.GetUseOverlappedSockets() && theApp.IsWinSock2Available(); +} + +CEMSocket::~CEMSocket(){ + EMTrace("CEMSocket::~CEMSocket() on %d",(SOCKET)this); + + // need to be locked here to know that the other methods + // won't be in the middle of things + sendLocker.Lock(); + byConnected = ES_DISCONNECTED; + sendLocker.Unlock(); + CleanUpOverlappedSendOperation(true); + + // now that we know no other method will keep adding to the queue + // we can remove ourself from the queue + theApp.uploadBandwidthThrottler->RemoveFromAllQueues(this); + + ClearQueues(); + RemoveAllLayers(); // deadlake PROXYSUPPORT + AsyncSelect(0); +} + +// deadlake PROXYSUPPORT +// By Maverick: Connection initialisition is done by class itself +BOOL CEMSocket::Connect(LPCSTR lpszHostAddress, UINT nHostPort) +{ + InitProxySupport(); + return CEncryptedStreamSocket::Connect(lpszHostAddress, nHostPort); +} +// end deadlake + +// deadlake PROXYSUPPORT +// By Maverick: Connection initialisition is done by class itself +//BOOL CEMSocket::Connect(LPCTSTR lpszHostAddress, UINT nHostPort) +BOOL CEMSocket::Connect(SOCKADDR* pSockAddr, int iSockAddrLen) +{ + InitProxySupport(); + return CEncryptedStreamSocket::Connect(pSockAddr, iSockAddrLen); +} +// end deadlake + +void CEMSocket::InitProxySupport() +{ + m_bProxyConnectFailed = false; + + // ProxyInitialisation + const ProxySettings& settings = thePrefs.GetProxySettings(); + if (settings.UseProxy && settings.type != PROXYTYPE_NOPROXY) + { + m_bOverlappedSending = false; + Close(); + + m_pProxyLayer = new CAsyncProxySocketLayer; + switch (settings.type) + { + case PROXYTYPE_SOCKS4: + case PROXYTYPE_SOCKS4A: + m_pProxyLayer->SetProxy(settings.type, settings.name, settings.port); + break; + case PROXYTYPE_SOCKS5: + case PROXYTYPE_HTTP10: + case PROXYTYPE_HTTP11: + if (settings.EnablePassword) + m_pProxyLayer->SetProxy(settings.type, settings.name, settings.port, settings.user, settings.password); + else + m_pProxyLayer->SetProxy(settings.type, settings.name, settings.port); + break; + default: + ASSERT(0); + } + AddLayer(m_pProxyLayer); + + // Connection Initialisation + Create(0, SOCK_STREAM, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, thePrefs.GetBindAddrA()); + AsyncSelect(FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE); + } +} + +void CEMSocket::ClearQueues(){ + EMTrace("CEMSocket::ClearQueues on %d",(SOCKET)this); + + sendLocker.Lock(); + for(POSITION pos = controlpacket_queue.GetHeadPosition(); pos != NULL; ) + delete controlpacket_queue.GetNext(pos); + controlpacket_queue.RemoveAll(); + + for(POSITION pos = standartpacket_queue.GetHeadPosition(); pos != NULL; ) + delete standartpacket_queue.GetNext(pos).packet; + standartpacket_queue.RemoveAll(); + sendLocker.Unlock(); + + // Download (pseudo) rate control + downloadLimit = 0; + downloadLimitEnable = false; + pendingOnReceive = false; + + // Download partial header + pendingHeaderSize = 0; + + // Download partial packet + delete pendingPacket; + pendingPacket = NULL; + pendingPacketSize = 0; + + // Upload control + delete[] sendbuffer; + sendbuffer = NULL; + + sendblen = 0; + sent = 0; +} + +void CEMSocket::OnClose(int nErrorCode){ + // need to be locked here to know that the other methods + // won't be in the middle of things + sendLocker.Lock(); + byConnected = ES_DISCONNECTED; + sendLocker.Unlock(); + + // now that we know no other method will keep adding to the queue + // we can remove ourself from the queue + theApp.uploadBandwidthThrottler->RemoveFromAllQueues(this); + + CEncryptedStreamSocket::OnClose(nErrorCode); // deadlake changed socket to PROXYSUPPORT ( AsyncSocketEx ) + RemoveAllLayers(); // deadlake PROXYSUPPORT + ClearQueues(); +} + +BOOL CEMSocket::AsyncSelect(long lEvent){ +#ifdef EMSOCKET_DEBUG + if (lEvent&FD_READ) + EMTrace(" FD_READ"); + if (lEvent&FD_CLOSE) + EMTrace(" FD_CLOSE"); + if (lEvent&FD_WRITE) + EMTrace(" FD_WRITE"); +#endif + // deadlake changed to AsyncSocketEx PROXYSUPPORT + if (m_SocketData.hSocket != INVALID_SOCKET) + return CEncryptedStreamSocket::AsyncSelect(lEvent); + return true; +} + +void CEMSocket::OnReceive(int nErrorCode){ + // the 2 meg size was taken from another place + static char GlobalReadBuffer[2000000]; + + // Check for an error code + if(nErrorCode != 0){ + OnError(nErrorCode); + return; + } + + // Check current connection state + if(byConnected == ES_DISCONNECTED){ + return; + } + else { + byConnected = ES_CONNECTED; // ES_DISCONNECTED, ES_NOTCONNECTED, ES_CONNECTED + } + + // CPU load improvement + if(downloadLimitEnable == true && downloadLimit == 0){ + EMTrace("CEMSocket::OnReceive blocked by limit"); + pendingOnReceive = true; + + //Receive(GlobalReadBuffer + pendingHeaderSize, 0); + return; + } + + // Remark: an overflow can not occur here + uint32 readMax = sizeof(GlobalReadBuffer) - pendingHeaderSize; + if(downloadLimitEnable == true && readMax > downloadLimit) { + readMax = downloadLimit; + } + + // We attempt to read up to 2 megs at a time (minus whatever is in our internal read buffer) + uint32 ret = Receive(GlobalReadBuffer + pendingHeaderSize, readMax); + if(ret == SOCKET_ERROR || byConnected == ES_DISCONNECTED){ + return; + } + + // Bandwidth control + if(downloadLimitEnable == true){ + // Update limit + downloadLimit -= GetRealReceivedBytes(); + } + + // CPU load improvement + // Detect if the socket's buffer is empty (or the size did match...) + pendingOnReceive = m_bFullReceive; + + if (ret == 0) + return; + + // Copy back the partial header into the global read buffer for processing + if(pendingHeaderSize > 0) { + memcpy(GlobalReadBuffer, pendingHeader, pendingHeaderSize); + ret += pendingHeaderSize; + pendingHeaderSize = 0; + } + + if (IsRawDataMode()) + { + DataReceived((BYTE*)GlobalReadBuffer, ret); + return; + } + + char *rptr = GlobalReadBuffer; // floating index initialized with begin of buffer + const char *rend = GlobalReadBuffer + ret; // end of buffer + + // Loop, processing packets until we run out of them + while ((rend - rptr >= PACKET_HEADER_SIZE) || ((pendingPacket != NULL) && (rend - rptr > 0))) + { + // Two possibilities here: + // + // 1. There is no pending incoming packet + // 2. There is already a partial pending incoming packet + // + // It's important to remember that emule exchange two kinds of packet + // - The control packet + // - The data packet for the transport of the block + // + // The biggest part of the traffic is done with the data packets. + // The default size of one block is 10240 bytes (or less if compressed), but the + // maximal size for one packet on the network is 1300 bytes. It's the reason + // why most of the Blocks are splitted before to be sent. + // + // Conclusion: When the download limit is disabled, this method can be at least + // called 8 times (10240/1300) by the lower layer before a splitted packet is + // rebuild and transferred to the above layer for processing. + // + // The purpose of this algorithm is to limit the amount of data exchanged between buffers + + if(pendingPacket == NULL){ + pendingPacket = new Packet(rptr); // Create new packet container. + rptr += 6; // Only the header is initialized so far + + // Bugfix We still need to check for a valid protocol + // Remark: the default eMule v0.26b had removed this test...... + switch (pendingPacket->prot){ + case OP_EDONKEYPROT: + case OP_PACKEDPROT: + case OP_EMULEPROT: + break; + default: + EMTrace("CEMSocket::OnReceive ERROR Wrong header"); + delete pendingPacket; + pendingPacket = NULL; + OnError(ERR_WRONGHEADER); + return; + } + + // Security: Check for buffer overflow (2MB) + if(pendingPacket->size > sizeof(GlobalReadBuffer)) { + delete pendingPacket; + pendingPacket = NULL; + OnError(ERR_TOOBIG); + return; + } + + // Init data buffer + pendingPacket->pBuffer = new char[pendingPacket->size + 1]; + pendingPacketSize = 0; + } + + // Bytes ready to be copied into packet's internal buffer + ASSERT(rptr <= rend); + uint32 toCopy = ((pendingPacket->size - pendingPacketSize) < (uint32)(rend - rptr)) ? + (pendingPacket->size - pendingPacketSize) : (uint32)(rend - rptr); + + // Copy Bytes from Global buffer to packet's internal buffer + memcpy(&pendingPacket->pBuffer[pendingPacketSize], rptr, toCopy); + pendingPacketSize += toCopy; + rptr += toCopy; + + // Check if packet is complet + ASSERT(pendingPacket->size >= pendingPacketSize); + if(pendingPacket->size == pendingPacketSize){ +#ifdef EMSOCKET_DEBUG + EMTrace("CEMSocket::PacketReceived on %d, opcode=%X, realSize=%d", + (SOCKET)this, pendingPacket->opcode, pendingPacket->GetRealPacketSize()); +#endif + + // Process packet + bool bPacketResult = PacketReceived(pendingPacket); + delete pendingPacket; + pendingPacket = NULL; + pendingPacketSize = 0; + + if (!bPacketResult) + return; + } + } + + // Finally, if there is any data left over, save it for next time + ASSERT(rptr <= rend); + ASSERT(rend - rptr < PACKET_HEADER_SIZE); + if(rptr != rend) { + // Keep the partial head + pendingHeaderSize = rend - rptr; + memcpy(pendingHeader, rptr, pendingHeaderSize); + } +} + +void CEMSocket::SetDownloadLimit(uint32 limit){ + downloadLimit = limit; + downloadLimitEnable = true; + + // CPU load improvement + if(limit > 0 && pendingOnReceive == true){ + OnReceive(0); + } +} + +void CEMSocket::DisableDownloadLimit(){ + downloadLimitEnable = false; + + // CPU load improvement + if(pendingOnReceive == true){ + OnReceive(0); + } +} + +/** + * Queues up the packet to be sent. Another thread will actually send the packet. + * + * If the packet is not a control packet, and if the socket decides that its queue is + * full and forceAdd is false, then the socket is allowed to refuse to add the packet + * to its queue. It will then return false and it is up to the calling thread to try + * to call SendPacket for that packet again at a later time. + * + * @param packet address to the packet that should be added to the queue + * + * @param delpacket if true, the responsibility for deleting the packet after it has been sent + * has been transferred to this object. If false, don't delete the packet after it + * has been sent. + * + * @param controlpacket the packet is a controlpacket + * + * @param forceAdd this packet must be added to the queue, even if it is full. If this flag is true + * then the method can not refuse to add the packet, and therefore not return false. + * + * @return true if the packet was added to the queue, false otherwise + */ +void CEMSocket::SendPacket(Packet* packet, bool delpacket, bool controlpacket, uint32 actualPayloadSize, bool bForceImmediateSend){ + //EMTrace("CEMSocket::OnSenPacked1 linked: %i, controlcount %i, standartcount %i, isbusy: %i",m_bLinkedPackets, controlpacket_queue.GetCount(), standartpacket_queue.GetCount(), IsBusy()); + + if (byConnected == ES_DISCONNECTED) + { + if(delpacket) { + delete packet; + } + return; + } else { + if (!delpacket){ + //ASSERT ( !packet->IsSplitted() ); + Packet* copy = new Packet(packet->opcode,packet->size); + memcpy(copy->pBuffer,packet->pBuffer,packet->size); + packet = copy; + } + + //if(m_startSendTick > 0) { + // m_lastSendLatency = ::GetTickCount() - m_startSendTick; + //} + sendLocker.Lock(); + if (controlpacket) { + controlpacket_queue.AddTail(packet); + + // queue up for controlpacket + theApp.uploadBandwidthThrottler->QueueForSendingControlPacket(this, HasSent()); + } else { + bool first = !((sendbuffer && !m_currentPacket_is_controlpacket) || !standartpacket_queue.IsEmpty()); + StandardPacketQueueEntry queueEntry = { actualPayloadSize, packet }; + standartpacket_queue.AddTail(queueEntry); + + // reset timeout for the first time + if (first) { + lastFinishedStandard = ::GetTickCount(); + m_bAccelerateUpload = true; // Always accelerate first packet in a block + } + } + } + + sendLocker.Unlock(); + if (bForceImmediateSend){ + ASSERT( controlpacket_queue.GetSize() == 1 ); + Send(1024, 0, true); + } +} + +uint64 CEMSocket::GetSentBytesCompleteFileSinceLastCallAndReset() { + sendLocker.Lock(); + + uint64 sentBytes = m_numberOfSentBytesCompleteFile; + m_numberOfSentBytesCompleteFile = 0; + + sendLocker.Unlock(); + + return sentBytes; +} + +uint64 CEMSocket::GetSentBytesPartFileSinceLastCallAndReset() { + sendLocker.Lock(); + + uint64 sentBytes = m_numberOfSentBytesPartFile; + m_numberOfSentBytesPartFile = 0; + + sendLocker.Unlock(); + + return sentBytes; +} + +uint64 CEMSocket::GetSentBytesControlPacketSinceLastCallAndReset() { + sendLocker.Lock(); + + uint64 sentBytes = m_numberOfSentBytesControlPacket; + m_numberOfSentBytesControlPacket = 0; + + sendLocker.Unlock(); + + return sentBytes; +} + +uint64 CEMSocket::GetSentPayloadSinceLastCall(bool bReset) { + if (!bReset) + return m_actualPayloadSizeSent; + sendLocker.Lock(); + + uint64 sentBytes = m_actualPayloadSizeSent; + m_actualPayloadSizeSent = 0; + + sendLocker.Unlock(); + + return sentBytes; +} + +void CEMSocket::OnSend(int nErrorCode){ + //onSendWillBeCalledOuter = false; + + if (nErrorCode){ + OnError(nErrorCode); + return; + } + + //EMTrace("CEMSocket::OnSend linked: %i, controlcount %i, standartcount %i, isbusy: %i",m_bLinkedPackets, controlpacket_queue.GetCount(), standartpacket_queue.GetCount(), IsBusy()); + CEncryptedStreamSocket::OnSend(0); + + m_bBusy = false; + + // stopped sending here. + //StoppedSendSoUpdateStats(); + + if (byConnected == ES_DISCONNECTED) { + return; + } else + byConnected = ES_CONNECTED; + + if(m_currentPacket_is_controlpacket) { + // queue up for control packet + theApp.uploadBandwidthThrottler->QueueForSendingControlPacket(this, HasSent()); + } + + if (!m_bOverlappedSending && (!standartpacket_queue.IsEmpty() || sendbuffer != NULL)) + theApp.uploadBandwidthThrottler->SocketAvailable(); +} + +//void CEMSocket::StoppedSendSoUpdateStats() { +// if(m_startSendTick > 0) { +// m_lastSendLatency = ::GetTickCount()-m_startSendTick; +// +// if(m_lastSendLatency > 0) { +// if(m_wasBlocked == true) { +// SocketTransferStats newLatencyStat = { m_lastSendLatency, ::GetTickCount() }; +// m_Average_sendlatency_list.AddTail(newLatencyStat); +// m_latency_sum += m_lastSendLatency; +// } +// +// m_startSendTick = 0; +// m_wasBlocked = false; +// +// CleanSendLatencyList(); +// } +// } +//} +// +//void CEMSocket::CleanSendLatencyList() { +// while(m_Average_sendlatency_list.GetCount() > 0 && ::GetTickCount() - m_Average_sendlatency_list.GetHead().timestamp > 3*1000) { +// SocketTransferStats removedLatencyStat = m_Average_sendlatency_list.RemoveHead(); +// m_latency_sum -= removedLatencyStat.latency; +// } +//} + + +SocketSentBytes CEMSocket::Send(uint32 maxNumberOfBytesToSend, uint32 minFragSize, bool onlyAllowedToSendControlPacket) +{ + if (byConnected == ES_DISCONNECTED) + { + SocketSentBytes returnVal = { false, 0, 0 }; + return returnVal; + } + if (m_bOverlappedSending) + return SendOv(maxNumberOfBytesToSend, minFragSize, onlyAllowedToSendControlPacket); + else + return SendStd(maxNumberOfBytesToSend, minFragSize, onlyAllowedToSendControlPacket); +} + +/** + * Try to put queued up data on the socket. + * + * Control packets have higher priority, and will be sent first, if possible. + * Standard packets can be split up in several package containers. In that case + * all the parts of a split package must be sent in a row, without any control packet + * in between. + * + * @param maxNumberOfBytesToSend This is the maximum number of bytes that is allowed to be put on the socket + * this call. The actual number of sent bytes will be returned from the method. + * + * @param onlyAllowedToSendControlPacket This call we only try to put control packets on the sockets. + * If there's a standard packet "in the way", and we think that this socket + * is no longer an upload slot, then it is ok to send the standard packet to + * get it out of the way. But it is not allowed to pick a new standard packet + * from the queue during this call. Several split packets are counted as one + * standard packet though, so it is ok to finish them all off if necessary. + * + * @return the actual number of bytes that were put on the socket. + */ +SocketSentBytes CEMSocket::SendStd(uint32 maxNumberOfBytesToSend, uint32 minFragSize, bool onlyAllowedToSendControlPacket) { + //EMTrace("CEMSocket::Send linked: %i, controlcount %i, standartcount %i, isbusy: %i",m_bLinkedPackets, controlpacket_queue.GetCount(), standartpacket_queue.GetCount(), IsBusy()); + sendLocker.Lock(); + bool anErrorHasOccured = false; + uint32 sentStandardPacketBytesThisCall = 0; + uint32 sentControlPacketBytesThisCall = 0; + + if(byConnected == ES_CONNECTED && IsEncryptionLayerReady()) { + if(minFragSize < 1) { + minFragSize = 1; + } + + maxNumberOfBytesToSend = GetNextFragSize(maxNumberOfBytesToSend, minFragSize); + + bool bWasLongTimeSinceSend = (::GetTickCount() - lastSent) > 1000; + + lastCalledSend = ::GetTickCount(); + + while(sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall < maxNumberOfBytesToSend && anErrorHasOccured == false && // don't send more than allowed. Also, there should have been no error in earlier loop + (sendbuffer != NULL || !controlpacket_queue.IsEmpty() || !standartpacket_queue.IsEmpty()) && // there must exist something to send + (onlyAllowedToSendControlPacket == false || // this means we are allowed to send both types of packets, so proceed + sendbuffer != NULL && m_currentPacket_is_controlpacket == true || // We are in the progress of sending a control packet. We are always allowed to send those + sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall > 0 && (sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall) % minFragSize != 0 || // Once we've started, continue to send until an even minFragsize to minimize packet overhead + sendbuffer == NULL && !controlpacket_queue.IsEmpty() || // There's a control packet in queue, and we are not currently sending anything, so we will handle the control packet next + sendbuffer != NULL && m_currentPacket_is_controlpacket == false && bWasLongTimeSinceSend && !controlpacket_queue.IsEmpty() && (sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall) < minFragSize // We have waited to long to clean the current packet (which may be a standard packet that is in the way). Proceed no matter what the value of onlyAllowedToSendControlPacket. + ) + ) + { + + // If we are currently not in the progress of sending a packet, we will need to find the next one to send + if(sendbuffer == NULL) { + Packet* curPacket = NULL; + if(!controlpacket_queue.IsEmpty()) { + // There's a control packet to send + m_currentPacket_is_controlpacket = true; + curPacket = controlpacket_queue.RemoveHead(); + } else if(!standartpacket_queue.IsEmpty()) { + // There's a standard packet to send + m_currentPacket_is_controlpacket = false; + StandardPacketQueueEntry queueEntry = standartpacket_queue.RemoveHead(); + curPacket = queueEntry.packet; + m_actualPayloadSize = queueEntry.actualPayloadSize; + + // remember this for statistics purposes. + m_currentPackageIsFromPartFile = curPacket->IsFromPF(); + } else { + // Just to be safe. Shouldn't happen? + sendLocker.Unlock(); + + // if we reach this point, then there's something wrong with the while condition above! + ASSERT(0); + theApp.QueueDebugLogLine(true,_T("EMSocket: Couldn't get a new packet! There's an error in the first while condition in EMSocket::Send()")); + + SocketSentBytes returnVal = { true, sentStandardPacketBytesThisCall, sentControlPacketBytesThisCall }; + return returnVal; + } + + // We found a package to send. Get the data to send from the + // package container and dispose of the container. + sendblen = curPacket->GetRealPacketSize(); + sendbuffer = curPacket->DetachPacket(); + sent = 0; + delete curPacket; + + // encrypting which cannot be done transparent by base class + CryptPrepareSendData((uchar*)sendbuffer, sendblen); + } + + // At this point we've got a packet to send in sendbuffer. Try to send it. Loop until entire packet + // is sent, or until we reach maximum bytes to send for this call, or until we get an error. + // NOTE! If send would block (returns WSAEWOULDBLOCK), we will return from this method INSIDE this loop. + while (sent < sendblen && + sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall < maxNumberOfBytesToSend && + ( + onlyAllowedToSendControlPacket == false || // this means we are allowed to send both types of packets, so proceed + m_currentPacket_is_controlpacket || + bWasLongTimeSinceSend && (sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall) < minFragSize || + (sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall) % minFragSize != 0 + ) && + anErrorHasOccured == false) + { + uint32 tosend = sendblen-sent; + if(!onlyAllowedToSendControlPacket || m_currentPacket_is_controlpacket) { + if (maxNumberOfBytesToSend >= sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall && tosend > maxNumberOfBytesToSend-(sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall)) + tosend = maxNumberOfBytesToSend-(sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall); + } else if(bWasLongTimeSinceSend && (sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall) < minFragSize) { + if (minFragSize >= sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall && tosend > minFragSize-(sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall)) + tosend = minFragSize-(sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall); + } else { + uint32 nextFragMaxBytesToSent = GetNextFragSize(sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall, minFragSize); + if (nextFragMaxBytesToSent >= sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall && tosend > nextFragMaxBytesToSent-(sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall)) + tosend = nextFragMaxBytesToSent-(sentStandardPacketBytesThisCall + sentControlPacketBytesThisCall); + } + ASSERT (tosend != 0 && tosend <= sendblen-sent); + + //DWORD tempStartSendTick = ::GetTickCount(); + + lastSent = ::GetTickCount(); + + uint32 result = CEncryptedStreamSocket::Send(sendbuffer+sent,tosend); // deadlake PROXYSUPPORT - changed to AsyncSocketEx + if (result == (uint32)SOCKET_ERROR){ + uint32 error = GetLastError(); + if (error == WSAEWOULDBLOCK){ + m_bBusy = true; + + //m_wasBlocked = true; + sendLocker.Unlock(); + + SocketSentBytes returnVal = { true, sentStandardPacketBytesThisCall, sentControlPacketBytesThisCall }; + return returnVal; // Send() blocked, onsend will be called when ready to send again + } else{ + // Send() gave an error + anErrorHasOccured = true; + //DEBUG_ONLY( AddDebugLogLine(true,"EMSocket: An error has occured: %i", error) ); + } + } else { + // we managed to send some bytes. Perform bookkeeping. + m_bBusy = false; + m_hasSent = true; + + sent += result; + + // Log send bytes in correct class + if(m_currentPacket_is_controlpacket == false) { + sentStandardPacketBytesThisCall += result; + + if(m_currentPackageIsFromPartFile == true) { + m_numberOfSentBytesPartFile += result; + } else { + m_numberOfSentBytesCompleteFile += result; + } + } else { + sentControlPacketBytesThisCall += result; + m_numberOfSentBytesControlPacket += result; + } + } + } + + if (sent == sendblen){ + // we are done sending the current package. Delete it and set + // sendbuffer to NULL so a new packet can be fetched. + delete[] sendbuffer; + sendbuffer = NULL; + sendblen = 0; + + if(!m_currentPacket_is_controlpacket) { + m_actualPayloadSizeSent += m_actualPayloadSize; + m_actualPayloadSize = 0; + + lastFinishedStandard = ::GetTickCount(); // reset timeout + m_bAccelerateUpload = false; // Safe until told otherwise + } + + sent = 0; + } + } + } + + if(onlyAllowedToSendControlPacket && (!controlpacket_queue.IsEmpty() || sendbuffer != NULL && m_currentPacket_is_controlpacket)) { + // enter control packet send queue + // we might enter control packet queue several times for the same package, + // but that costs very little overhead. Less overhead than trying to make sure + // that we only enter the queue once. + theApp.uploadBandwidthThrottler->QueueForSendingControlPacket(this, HasSent()); + } + + //CleanSendLatencyList(); + + sendLocker.Unlock(); + + SocketSentBytes returnVal = { !anErrorHasOccured, sentStandardPacketBytesThisCall, sentControlPacketBytesThisCall }; + return returnVal; +} + +/** + * Try to put queued up data on the socket with Overlapped methods. + * + * Control packets have higher priority, and will be sent first, if possible. + * + * @param maxNumberOfBytesToSend This is the maximum number of bytes that is allowed to be put on the socket + * this call. The actual number of sent bytes will be returned from the method. + * + * @param onlyAllowedToSendControlPacket This call we only try to put control packets on the sockets. + * + * @return the actual number of bytes that were put on the socket. + */ +SocketSentBytes CEMSocket::SendOv(uint32 maxNumberOfBytesToSend, uint32 minFragSize, bool onlyAllowedToSendControlPacket) { + //EMTrace("CEMSocket::Send linked: %i, controlcount %i, standartcount %i, isbusy: %i",m_bLinkedPackets, controlpacket_queue.GetCount(), standartpacket_queue.GetCount(), IsBusy()); + ASSERT( m_pProxyLayer == NULL ); + sendLocker.Lock(); + bool anErrorHasOccured = false; + uint32 sentStandardPacketBytesThisCall = 0; + uint32 sentControlPacketBytesThisCall = 0; + + if(byConnected == ES_CONNECTED && IsEncryptionLayerReady() && !IsBusyExtensiveCheck() && maxNumberOfBytesToSend > 0) { + if(minFragSize < 1) { + minFragSize = 1; + } + + maxNumberOfBytesToSend = GetNextFragSize(maxNumberOfBytesToSend, minFragSize); + lastCalledSend = ::GetTickCount(); + ASSERT( m_pPendingSendOperation == NULL && m_aBufferSend.IsEmpty()); + sint32 nBytesLeft = maxNumberOfBytesToSend; + if (sendbuffer != NULL || !controlpacket_queue.IsEmpty() || !standartpacket_queue.IsEmpty()) + { + // WSASend takes multiple buffers which is quite nice for our case, as we have to call send + // only once regardless how many packets we want to ship without memorymoving. But before + // we can do this, collect all buffers we want to send in this call + + // first send the existing sendbuffer (already started packet) + if (sendbuffer != NULL) + { + WSABUF pCurBuf;; + pCurBuf.len = min(sendblen - sent, (uint32)nBytesLeft); + pCurBuf.buf = new CHAR[pCurBuf.len]; + memcpy(pCurBuf.buf, sendbuffer + sent, pCurBuf.len); + sent += pCurBuf.len; + m_aBufferSend.Add(pCurBuf); + nBytesLeft -= pCurBuf.len; + if (sent == sendblen) //finished the buffer + { + delete[] sendbuffer; + sendbuffer = NULL; + sendblen = 0; + } + sentStandardPacketBytesThisCall += pCurBuf.len; // Sendbuffer is always a standard packet in this method + lastFinishedStandard = ::GetTickCount(); + m_bAccelerateUpload = false; + m_actualPayloadSizeSent += m_actualPayloadSize; + m_actualPayloadSize = 0; + if(m_currentPackageIsFromPartFile) + m_numberOfSentBytesPartFile += pCurBuf.len; + else + m_numberOfSentBytesCompleteFile += pCurBuf.len; + } + + // next send all control packets if there are any and we have bytes left + while (!controlpacket_queue.IsEmpty() && nBytesLeft > 0) + { + // send controlpackets always completely, ignoring the limit by a few bytes if we must + WSABUF pCurBuf; + Packet* curPacket = controlpacket_queue.RemoveHead(); + pCurBuf.len = curPacket->GetRealPacketSize(); + pCurBuf.buf = curPacket->DetachPacket(); + delete curPacket; + // encrypting which cannot be done transparent by base class + CryptPrepareSendData((uchar*)pCurBuf.buf, pCurBuf.len); + m_aBufferSend.Add(pCurBuf); + nBytesLeft -= pCurBuf.len; + sentControlPacketBytesThisCall += pCurBuf.len; + } + + // and now finally the standard packets if there are any and we have bytes left and we are allowed to + while (!standartpacket_queue.IsEmpty() && nBytesLeft > 0 && !onlyAllowedToSendControlPacket) + { + StandardPacketQueueEntry queueEntry = standartpacket_queue.RemoveHead(); + WSABUF pCurBuf; + Packet* curPacket = queueEntry.packet; + m_currentPackageIsFromPartFile = curPacket->IsFromPF(); + + // can we send it right away or only a part of it? + if (queueEntry.packet->GetRealPacketSize() <= (uint32)nBytesLeft) + { + // yay + pCurBuf.len = curPacket->GetRealPacketSize(); + pCurBuf.buf = curPacket->DetachPacket(); + CryptPrepareSendData((uchar*)pCurBuf.buf, pCurBuf.len);// encrypting which cannot be done transparent by base class + m_actualPayloadSizeSent += queueEntry.actualPayloadSize; + lastFinishedStandard = ::GetTickCount(); + m_bAccelerateUpload = false; + } + else + { // aww, well first stuff everything into the sendbuffer and then send what we can of it + ASSERT( sendbuffer == NULL ); + m_actualPayloadSize = queueEntry.actualPayloadSize; + sendblen = curPacket->GetRealPacketSize(); + sendbuffer = curPacket->DetachPacket(); + sent = 0; + CryptPrepareSendData((uchar*)sendbuffer, sendblen); // encrypting which cannot be done transparent by base class + pCurBuf.len = min(sendblen - sent, (uint32)nBytesLeft); + pCurBuf.buf = new CHAR[pCurBuf.len]; + memcpy(pCurBuf.buf, sendbuffer, pCurBuf.len); + sent += pCurBuf.len; + ASSERT (sent < sendblen); + m_currentPacket_is_controlpacket = false; + } + delete curPacket; + m_aBufferSend.Add(pCurBuf); + nBytesLeft -= pCurBuf.len; + sentStandardPacketBytesThisCall += pCurBuf.len; + if(m_currentPackageIsFromPartFile) + m_numberOfSentBytesPartFile += pCurBuf.len; + else + m_numberOfSentBytesCompleteFile += pCurBuf.len; + } + // allright, prepare to send our collected buffers + m_pPendingSendOperation = new WSAOVERLAPPED; + ZeroMemory(m_pPendingSendOperation, sizeof(WSAOVERLAPPED)); + m_pPendingSendOperation->hEvent = theApp.uploadBandwidthThrottler->GetSocketAvailableEvent(); + DWORD dwBytesSent = 0; + if (CEncryptedStreamSocket::SendOv(m_aBufferSend, dwBytesSent, m_pPendingSendOperation) == 0) + { + ASSERT( dwBytesSent > 0 ); + CleanUpOverlappedSendOperation(false); + } + else + { + int nError = WSAGetLastError(); + if (nError != WSA_IO_PENDING) + { + anErrorHasOccured = true; + theApp.QueueDebugLogLineEx(ERROR, _T("WSASend() Error: %u, %s"), nError, GetErrorMessage(nError)); + } + } + } + } + + if(onlyAllowedToSendControlPacket && !controlpacket_queue.IsEmpty()) + { + // enter control packet send queue + // we might enter control packet queue several times for the same package, + // but that costs very little overhead. Less overhead than trying to make sure + // that we only enter the queue once. + theApp.uploadBandwidthThrottler->QueueForSendingControlPacket(this, HasSent()); + } + + sendLocker.Unlock(); + SocketSentBytes returnVal = { !anErrorHasOccured, sentStandardPacketBytesThisCall, sentControlPacketBytesThisCall }; + return returnVal; +} + +uint32 CEMSocket::GetNextFragSize(uint32 current, uint32 minFragSize) { + if(current % minFragSize == 0) { + return current; + } else { + return minFragSize*(current/minFragSize+1); + } +} + +/** + * Decides the (minimum) amount the socket needs to send to prevent timeout. + * + * @author SlugFiller + */ +uint32 CEMSocket::GetNeededBytes() { + sendLocker.Lock(); + if (byConnected == ES_DISCONNECTED) { + sendLocker.Unlock(); + return 0; + } + + if (!((sendbuffer && !m_currentPacket_is_controlpacket) || !standartpacket_queue.IsEmpty())) { + // No standard packet to send. Even if data needs to be sent to prevent timout, there's nothing to send. + sendLocker.Unlock(); + return 0; + } + + if (((sendbuffer && !m_currentPacket_is_controlpacket)) && !controlpacket_queue.IsEmpty()) + m_bAccelerateUpload = true; // We might be trying to send a block request, accelerate packet + + uint32 sendgap = ::GetTickCount() - lastCalledSend; + + uint64 timetotal = m_bAccelerateUpload?45000:90000; + uint64 timeleft = ::GetTickCount() - lastFinishedStandard; + uint64 sizeleft, sizetotal; + if (sendbuffer && !m_currentPacket_is_controlpacket) { + sizeleft = sendblen-sent; + sizetotal = sendblen; + } + else { + sizeleft = sizetotal = standartpacket_queue.GetHead().packet->GetRealPacketSize(); + } + sendLocker.Unlock(); + + if (timeleft >= timetotal) + return (UINT)sizeleft; + timeleft = timetotal-timeleft; + if (timeleft*sizetotal >= timetotal*sizeleft) { + // don't use 'GetTimeOut' here in case the timeout value is high, + if (sendgap > SEC2MS(20)) + return 1; // Don't let the socket itself time out - Might happen when switching from spread(non-focus) slot to trickle slot + return 0; + } + uint64 decval = timeleft*sizetotal/timetotal; + if (!decval) + return (UINT)sizeleft; + if (decval < sizeleft) + return (UINT)(sizeleft-decval+1); // Round up + else + return 1; +} + +// pach2: +// written this overriden Receive to handle transparently FIN notifications coming from calls to recv() +// This was maybe(??) the cause of a lot of socket error, notably after a brutal close from peer +// also added trace so that we can debug after the fact ... +int CEMSocket::Receive(void* lpBuf, int nBufLen, int nFlags) +{ +// EMTrace("CEMSocket::Receive on %d, maxSize=%d",(SOCKET)this,nBufLen); + int recvRetCode = CEncryptedStreamSocket::Receive(lpBuf,nBufLen,nFlags); // deadlake PROXYSUPPORT - changed to AsyncSocketEx + switch (recvRetCode) { + case 0: + if (GetRealReceivedBytes() > 0) // we received data but it was for the underlying encryption layer - all fine + return 0; + //EMTrace("CEMSocket::##Received FIN on %d, maxSize=%d",(SOCKET)this,nBufLen); + // FIN received on socket // Connection is being closed by peer + //ASSERT (false); + if ( 0 == AsyncSelect(FD_CLOSE|FD_WRITE) ) { // no more READ notifications ... + //int waserr = GetLastError(); // oups, AsyncSelect failed !!! + ASSERT(false); + } + return 0; + case SOCKET_ERROR: + switch(GetLastError()) { + case WSANOTINITIALISED: + ASSERT(false); + EMTrace("CEMSocket::OnReceive:A successful AfxSocketInit must occur before using this API."); + break; + case WSAENETDOWN: + ASSERT(true); + EMTrace("CEMSocket::OnReceive:The socket %d received a net down error",(SOCKET)this); + break; + case WSAENOTCONN: // The socket is not connected. + EMTrace("CEMSocket::OnReceive:The socket %d is not connected",(SOCKET)this); + break; + case WSAEINPROGRESS: // A blocking Windows Sockets operation is in progress. + EMTrace("CEMSocket::OnReceive:The socket %d is blocked",(SOCKET)this); + break; + case WSAEWOULDBLOCK: // The socket is marked as nonblocking and the Receive operation would block. + EMTrace("CEMSocket::OnReceive:The socket %d would block",(SOCKET)this); + break; + case WSAENOTSOCK: // The descriptor is not a socket. + EMTrace("CEMSocket::OnReceive:The descriptor %d is not a socket (may have been closed or never created)",(SOCKET)this); + break; + case WSAEOPNOTSUPP: // MSG_OOB was specified, but the socket is not of type SOCK_STREAM. + break; + case WSAESHUTDOWN: // The socket has been shut down; it is not possible to call Receive on a socket after ShutDown has been invoked with nHow set to 0 or 2. + EMTrace("CEMSocket::OnReceive:The socket %d has been shut down",(SOCKET)this); + break; + case WSAEMSGSIZE: // The datagram was too large to fit into the specified buffer and was truncated. + EMTrace("CEMSocket::OnReceive:The datagram was too large to fit and was truncated (socket %d)",(SOCKET)this); + break; + case WSAEINVAL: // The socket has not been bound with Bind. + EMTrace("CEMSocket::OnReceive:The socket %d has not been bound",(SOCKET)this); + break; + case WSAECONNABORTED: // The virtual circuit was aborted due to timeout or other failure. + EMTrace("CEMSocket::OnReceive:The socket %d has not been bound",(SOCKET)this); + break; + case WSAECONNRESET: // The virtual circuit was reset by the remote side. + EMTrace("CEMSocket::OnReceive:The socket %d has not been bound",(SOCKET)this); + break; + default: + EMTrace("CEMSocket::OnReceive:Unexpected socket error %x on socket %d",GetLastError(),(SOCKET)this); + break; + } + break; + default: +// EMTrace("CEMSocket::OnReceive on %d, receivedSize=%d",(SOCKET)this,recvRetCode); + return recvRetCode; + } + return SOCKET_ERROR; +} + +void CEMSocket::RemoveAllLayers() +{ + CEncryptedStreamSocket::RemoveAllLayers(); + delete m_pProxyLayer; + m_pProxyLayer = NULL; +} + +int CEMSocket::OnLayerCallback(const CAsyncSocketExLayer *pLayer, int nType, int nCode, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(wParam); + ASSERT( pLayer ); + if (nType == LAYERCALLBACK_STATECHANGE) + { + /*CString logline; + if (pLayer==m_pProxyLayer) + { + //logline.Format(_T("ProxyLayer changed state from %d to %d"), wParam, nCode); + //AddLogLine(false,logline); + }else + //logline.Format(_T("Layer @ %d changed state from %d to %d"), pLayer, wParam, nCode); + //AddLogLine(false,logline);*/ + return 1; + } + else if (nType == LAYERCALLBACK_LAYERSPECIFIC) + { + if (pLayer == m_pProxyLayer) + { + switch (nCode) + { + case PROXYERROR_NOCONN: + // We failed to connect to the proxy. + m_bProxyConnectFailed = true; + /* fall through */ + case PROXYERROR_REQUESTFAILED: + // We are connected to the proxy but it failed to connect to the peer. + if (thePrefs.GetVerbose()) { + m_strLastProxyError = GetProxyError(nCode); + if (lParam && ((LPCSTR)lParam)[0] != '\0') { + m_strLastProxyError += _T(" - "); + m_strLastProxyError += (LPCSTR)lParam; + } + // Appending the Winsock error code is actually not needed because that error code + // gets reported by to the original caller anyway and will get reported eventually + // by calling 'GetFullErrorMessage', + /*if (wParam) { + CString strErrInf; + if (GetErrorMessage(wParam, strErrInf, 1)) + m_strLastProxyError += _T(" - ") + strErrInf; + }*/ + } + break; + default: + m_strLastProxyError = GetProxyError(nCode); + LogWarning(false, _T("Proxy-Error: %s"), m_strLastProxyError); + } + } + } + return 1; +} + +/** + * Removes all packets from the standard queue that don't have to be sent for the socket to be able to send a control packet. + * + * Before a socket can send a new packet, the current packet has to be finished. If the current packet is part of + * a split packet, then all parts of that split packet must be sent before the socket can send a control packet. + * + * This method keeps in standard queue only those packets that must be sent (rest of split packet), and removes everything + * after it. The method doesn't touch the control packet queue. + */ +void CEMSocket::TruncateQueues() { + sendLocker.Lock(); + + // Clear the standard queue totally + // Please note! There may still be a standardpacket in the sendbuffer variable! + for(POSITION pos = standartpacket_queue.GetHeadPosition(); pos != NULL; ) + delete standartpacket_queue.GetNext(pos).packet; + standartpacket_queue.RemoveAll(); + + sendLocker.Unlock(); +} + +#ifdef _DEBUG +void CEMSocket::AssertValid() const +{ + CEncryptedStreamSocket::AssertValid(); + + const_cast(this)->sendLocker.Lock(); + + ASSERT( byConnected==ES_DISCONNECTED || byConnected==ES_NOTCONNECTED || byConnected==ES_CONNECTED ); + CHECK_BOOL(m_bProxyConnectFailed); + CHECK_PTR(m_pProxyLayer); + (void)downloadLimit; + CHECK_BOOL(downloadLimitEnable); + CHECK_BOOL(pendingOnReceive); + //char pendingHeader[PACKET_HEADER_SIZE]; + pendingHeaderSize; + CHECK_PTR(pendingPacket); + (void)pendingPacketSize; + CHECK_ARR(sendbuffer, sendblen); + (void)sent; + controlpacket_queue.AssertValid(); + standartpacket_queue.AssertValid(); + CHECK_BOOL(m_currentPacket_is_controlpacket); + //(void)sendLocker; + (void)m_numberOfSentBytesCompleteFile; + (void)m_numberOfSentBytesPartFile; + (void)m_numberOfSentBytesControlPacket; + CHECK_BOOL(m_currentPackageIsFromPartFile); + (void)lastCalledSend; + (void)m_actualPayloadSize; + (void)m_actualPayloadSizeSent; + + const_cast(this)->sendLocker.Unlock(); +} +#endif + +#ifdef _DEBUG +void CEMSocket::Dump(CDumpContext& dc) const +{ + CEncryptedStreamSocket::Dump(dc); +} +#endif + +void CEMSocket::DataReceived(const BYTE*, UINT) +{ + ASSERT(0); +} + +UINT CEMSocket::GetTimeOut() const +{ + return m_uTimeOut; +} + +void CEMSocket::SetTimeOut(UINT uTimeOut) +{ + m_uTimeOut = uTimeOut; +} + +CString CEMSocket::GetFullErrorMessage(DWORD nErrorCode) +{ + CString strError; + + // Proxy error + if (!GetLastProxyError().IsEmpty()) + { + strError = GetLastProxyError(); + // If we had a proxy error and the socket error is WSAECONNABORTED, we just 'aborted' + // the TCP connection ourself - no need to show that self-created error too. + if (nErrorCode == WSAECONNABORTED) + return strError; + } + + // Winsock error + if (nErrorCode) + { + if (!strError.IsEmpty()) + strError += _T(": "); + strError += GetErrorMessage(nErrorCode, 1); + } + + return strError; +} + +// increases the send buffer to a bigger size +bool CEMSocket::UseBigSendBuffer() +{ +#define BIGSIZE 128 * 1024 + if (m_bUsesBigSendBuffers) + return true; + m_bUsesBigSendBuffers = true; + int val = BIGSIZE; + int vallen = sizeof(int); + int oldval = 0; + GetSockOpt(SO_SNDBUF, &oldval, &vallen); + if (val > oldval) + SetSockOpt(SO_SNDBUF, &val, sizeof(int)); + val = 0; + vallen = sizeof(int); + GetSockOpt(SO_SNDBUF, &val, &vallen); +#if defined(_DEBUG) || defined(_BETA) || defined(_DEVBUILD) + if (val == BIGSIZE) + theApp.QueueDebugLogLine(false, _T("Increased Sendbuffer for uploading socket from %uKB to %uKB"), oldval/1024, val/1024); + else + theApp.QueueDebugLogLine(false, _T("Failed to increase Sendbuffer for uploading socket, stays at %uKB"), oldval/1024); +#endif + return val == BIGSIZE; +} + +bool CEMSocket::IsBusyExtensiveCheck() +{ + + if (!m_bOverlappedSending) + return m_bBusy; + + CSingleLock lockSend(&sendLocker, TRUE); + if (m_pPendingSendOperation == NULL) + return false; + else + { + DWORD dwTransferred = 0; + DWORD dwFlags; + if (WSAGetOverlappedResult(GetSocketHandle(), m_pPendingSendOperation, &dwTransferred, FALSE, &dwFlags) == TRUE) + { + CleanUpOverlappedSendOperation(false); + OnSend(0); + return false; + } + else + { + int nError = WSAGetLastError(); + if (nError == WSA_IO_INCOMPLETE) + return true; + else + { + CleanUpOverlappedSendOperation(false); + theApp.QueueDebugLogLineEx(LOG_ERROR, _T("WSAGetOverlappedResult return error: %s"), GetErrorMessage(nError)); + return false; + } + } + } +} + +// won't always deliver the proper result (sometimes reports busy even if it isn't anymore and thread related errors) but doesn't needs locks or function calls +bool CEMSocket::IsBusyQuickCheck() const +{ + if (!m_bOverlappedSending) + return m_bBusy; + else + return m_pPendingSendOperation != NULL; +} + +void CEMSocket::CleanUpOverlappedSendOperation(bool bCancelRequestFirst) +{ + CSingleLock lockSend(&sendLocker, TRUE); + if (m_pPendingSendOperation != NULL) + { + if (bCancelRequestFirst) + CancelIo((HANDLE)GetSocketHandle()); + delete m_pPendingSendOperation; + m_pPendingSendOperation = NULL; + for (int i = 0; i < m_aBufferSend.GetCount(); i++) + { + WSABUF pDel = m_aBufferSend[i]; + delete[] pDel.buf; + } + m_aBufferSend.RemoveAll(); + } +} + +bool CEMSocket::HasQueues(bool bOnlyStandardPackets) const +{ + // not trustworthy threaded? but it's ok if we don't get the correct result now and then + return sendbuffer || standartpacket_queue.GetCount() > 0 || (controlpacket_queue.GetCount() > 0 && !bOnlyStandardPackets); +} + +bool CEMSocket::IsEnoughFileDataQueued(uint32 nMinFilePayloadBytes) const +{ + // check we have at least nMinFilePayloadBytes Payload data in our standardqueue + for (POSITION pos = standartpacket_queue.GetHeadPosition(); pos != NULL; standartpacket_queue.GetNext(pos)) + { + if (standartpacket_queue.GetAt(pos).actualPayloadSize > nMinFilePayloadBytes) + return true; + else + nMinFilePayloadBytes -= standartpacket_queue.GetAt(pos).actualPayloadSize; + } + return false; +} diff --git a/EMSocket.h b/EMSocket.h new file mode 100644 index 00000000..c60bc7e3 --- /dev/null +++ b/EMSocket.h @@ -0,0 +1,151 @@ +//this file is part of eMule +//Copyright (C)2002-2010 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "EncryptedStreamSocket.h" +#include "OtherFunctions.h" +#include "ThrottledSocket.h" // ZZ:UploadBandWithThrottler (UDP) + +class CAsyncProxySocketLayer; +class Packet; + +#define ES_DISCONNECTED 0xFF +#define ES_NOTCONNECTED 0x00 +#define ES_CONNECTED 0x01 + +#define PACKET_HEADER_SIZE 6 + +struct StandardPacketQueueEntry { + uint32 actualPayloadSize; + Packet* packet; +}; + +class CEMSocket : public CEncryptedStreamSocket, public ThrottledFileSocket // ZZ:UploadBandWithThrottler +{ + DECLARE_DYNAMIC(CEMSocket) +public: + CEMSocket(); + virtual ~CEMSocket(); + + virtual void SendPacket(Packet* packet, bool delpacket = true, bool controlpacket = true, uint32 actualPayloadSize = 0, bool bForceImmediateSend = false); + bool IsConnected() const {return byConnected == ES_CONNECTED;} + uint8 GetConState() const {return byConnected;} + virtual bool IsRawDataMode() const { return false; } + void SetDownloadLimit(uint32 limit); + void DisableDownloadLimit(); + BOOL AsyncSelect(long lEvent); + virtual bool IsBusyExtensiveCheck(); + virtual bool IsBusyQuickCheck() const; + virtual bool HasQueues(bool bOnlyStandardPackets = false) const; + virtual bool IsEnoughFileDataQueued(uint32 nMinFilePayloadBytes) const; + virtual bool UseBigSendBuffer(); + int DbgGetStdQueueCount() const {return standartpacket_queue.GetCount();} + + virtual UINT GetTimeOut() const; + virtual void SetTimeOut(UINT uTimeOut); + + virtual BOOL Connect(LPCSTR lpszHostAddress, UINT nHostPort); + virtual BOOL Connect(SOCKADDR* pSockAddr, int iSockAddrLen); + + void InitProxySupport(); + virtual void RemoveAllLayers(); + const CString GetLastProxyError() const { return m_strLastProxyError; } + bool GetProxyConnectFailed() const { return m_bProxyConnectFailed; } + + CString GetFullErrorMessage(DWORD dwError); + + DWORD GetLastCalledSend() { return lastCalledSend; } + uint64 GetSentBytesCompleteFileSinceLastCallAndReset(); + uint64 GetSentBytesPartFileSinceLastCallAndReset(); + uint64 GetSentBytesControlPacketSinceLastCallAndReset(); + uint64 GetSentPayloadSinceLastCall(bool bReset); + void TruncateQueues(); + + virtual SocketSentBytes SendControlData(uint32 maxNumberOfBytesToSend, uint32 minFragSize) { return Send(maxNumberOfBytesToSend, minFragSize, true); }; + virtual SocketSentBytes SendFileAndControlData(uint32 maxNumberOfBytesToSend, uint32 minFragSize) { return Send(maxNumberOfBytesToSend, minFragSize, false); }; + + uint32 GetNeededBytes(); +#ifdef _DEBUG + // Diagnostic Support + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +protected: + virtual int OnLayerCallback(const CAsyncSocketExLayer *pLayer, int nType, int nCode, WPARAM wParam, LPARAM lParam); + + virtual void DataReceived(const BYTE* pcData, UINT uSize); + virtual bool PacketReceived(Packet* packet) = 0; + virtual void OnError(int nErrorCode) = 0; + virtual void OnClose(int nErrorCode); + virtual void OnSend(int nErrorCode); + virtual void OnReceive(int nErrorCode); + uint8 byConnected; + UINT m_uTimeOut; + bool m_bProxyConnectFailed; + CAsyncProxySocketLayer* m_pProxyLayer; + CString m_strLastProxyError; + +private: + virtual SocketSentBytes Send(uint32 maxNumberOfBytesToSend, uint32 minFragSize, bool onlyAllowedToSendControlPacket); + SocketSentBytes SendStd(uint32 maxNumberOfBytesToSend, uint32 minFragSize, bool onlyAllowedToSendControlPacket); + SocketSentBytes SendOv(uint32 maxNumberOfBytesToSend, uint32 minFragSize, bool onlyAllowedToSendControlPacket); + void ClearQueues(); + virtual int Receive(void* lpBuf, int nBufLen, int nFlags = 0); + void CleanUpOverlappedSendOperation(bool bCancelRequestFirst); + + uint32 GetNextFragSize(uint32 current, uint32 minFragSize); + bool HasSent() { return m_hasSent; } + + // Download (pseudo) rate control + uint32 downloadLimit; + bool downloadLimitEnable; + bool pendingOnReceive; + + // Download partial header + char pendingHeader[PACKET_HEADER_SIZE]; // actually, this holds only 'PACKET_HEADER_SIZE-1' bytes. + uint32 pendingHeaderSize; + + // Download partial packet + Packet* pendingPacket; + uint32 pendingPacketSize; + + // Upload control + char* sendbuffer; + uint32 sendblen; + uint32 sent; + LPWSAOVERLAPPED m_pPendingSendOperation; + CArray m_aBufferSend; + + CTypedPtrList controlpacket_queue; + CList standartpacket_queue; + bool m_currentPacket_is_controlpacket; + CCriticalSection sendLocker; + uint64 m_numberOfSentBytesCompleteFile; + uint64 m_numberOfSentBytesPartFile; + uint64 m_numberOfSentBytesControlPacket; + bool m_currentPackageIsFromPartFile; + bool m_bAccelerateUpload; + DWORD lastCalledSend; + DWORD lastSent; + uint32 lastFinishedStandard; + uint32 m_actualPayloadSize; // Payloadsize of the data currently in sendbuffer + uint32 m_actualPayloadSizeSent; + bool m_bBusy; + bool m_hasSent; + bool m_bUsesBigSendBuffers; + bool m_bOverlappedSending; +}; diff --git a/EditDelayed.cpp b/EditDelayed.cpp new file mode 100644 index 00000000..147ce3ea --- /dev/null +++ b/EditDelayed.cpp @@ -0,0 +1,417 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +/* + Edit Control with Combined Reset and Column Selector Button (as seen on Thunderbird) + TODO: + - Handle font changes etc properly + - maybe save filter settings (?) + - maybe keyboard shortcuts +*/ +#include "stdafx.h" +#include "EditDelayed.h" +#include "UserMsgs.h" +#include "emule.h" +#include "MenuCmds.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define DELAYED_EVALUATE_TIMER_ID 1 +#define ICON_LEFTSPACE 20 + + +BEGIN_MESSAGE_MAP(CEditDelayed, CEdit) + ON_WM_SETFOCUS() + ON_WM_KILLFOCUS() + ON_WM_TIMER() + ON_CONTROL_REFLECT(EN_CHANGE, OnEnChange) + ON_WM_DESTROY() + ON_WM_LBUTTONDOWN() + ON_WM_LBUTTONUP() + ON_WM_SETCURSOR() + ON_WM_MOUSEMOVE() + ON_WM_CTLCOLOR_REFLECT() + ON_WM_SIZE() +END_MESSAGE_MAP() + +CEditDelayed::CEditDelayed() +{ + m_uTimerResult = 0; + m_dwLastModified = 0; + m_bShuttingDown = false; + m_bShowResetButton = false; + m_bShowsColumnText = false; +} + +void CEditDelayed::OnDestroy() +{ + if (m_uTimerResult != 0) + VERIFY( KillTimer(DELAYED_EVALUATE_TIMER_ID) ); + + // WM_DESTROY sends another WM_SETFOCUS/WM_KILLFOCUS to the window!? + m_bShuttingDown = true; + CEdit::OnDestroy(); +} + +void CEditDelayed::OnTimer(UINT nIDEvent) +{ + //ASSERT( nIDEvent == DELAYED_EVALUATE_TIMER_ID ); + if (nIDEvent == DELAYED_EVALUATE_TIMER_ID) + { + DWORD dwElapsed = GetTickCount() - m_dwLastModified; + if (dwElapsed >= 400) + { + DoDelayedEvalute(); + m_dwLastModified = GetTickCount(); + } + } + + CEdit::OnTimer(nIDEvent); +} + +void CEditDelayed::OnSetFocus(CWnd* pOldWnd) +{ + CEdit::OnSetFocus(pOldWnd); + + if (!m_bShuttingDown) + { + // Create timer + ASSERT( m_uTimerResult == 0 ); + m_uTimerResult = SetTimer(DELAYED_EVALUATE_TIMER_ID, 100, NULL); + ASSERT( m_uTimerResult != 0 ); + + ShowColumnText(false); + } +} + +void CEditDelayed::OnKillFocus(CWnd* pNewWnd) +{ + if (!m_bShuttingDown) + { + // Kill timer + ASSERT( m_uTimerResult != 0 ); + VERIFY( KillTimer(DELAYED_EVALUATE_TIMER_ID) ); + m_uTimerResult = 0; + + // If there was something modified since the last evaluation.. + DoDelayedEvalute(); + + if (GetWindowTextLength() == 0) + ShowColumnText(true); + } + + CEdit::OnKillFocus(pNewWnd); +} + +void CEditDelayed::OnEnChange() +{ + if (m_uTimerResult != 0) { + // Edit control contents were changed while the control was active (had focus) + ASSERT( GetFocus() == this ); + m_dwLastModified = GetTickCount(); + } + else { + // Edit control contents were changed while the control was not active (e.g. + // someone called 'SetWindowText' from within an other window). + ASSERT( GetFocus() != this ); + DoDelayedEvalute(); + } + if (GetWindowTextLength() == 0 && m_bShowResetButton) { + m_iwReset.ShowIcon(0); + m_bShowResetButton = false; + SetEditRect(true); + m_iwReset.ShowWindow(SW_HIDE); + } + else if (GetWindowTextLength() > 0 && !m_bShowResetButton) { + m_bShowResetButton = true; + SetEditRect(true); + m_iwReset.ShowWindow(SW_SHOW); + } +} + +void CEditDelayed::DoDelayedEvalute(bool bForce) +{ + if (m_bShowsColumnText){ + ASSERT(0); + return; + } + + // Fire 'evaluate' event only, if content really has changed. + CString strContent; + GetWindowText(strContent); + if (m_strLastEvaluatedContent == strContent && !bForce) + return; + m_strLastEvaluatedContent = strContent; + GetParent()->SendMessage(UM_DELAYED_EVALUATE, (WPARAM)m_nCurrentColumnIdx, (LPARAM)(LPCTSTR)m_strLastEvaluatedContent); +} + +void CEditDelayed::OnInit(CHeaderCtrl* pColumnHeader, CArray* paIgnoredColums) +{ + SetEditRect(false); + CRect rectWindow; + GetClientRect(rectWindow); + + m_pctrlColumnHeader = pColumnHeader; + m_hCursor = LoadCursor(NULL, IDC_ARROW); + m_nCurrentColumnIdx = 0; + + CImageList* pImageList = new CImageList(); + pImageList->Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + if (pColumnHeader != NULL) + pImageList->Add(CTempIconLoader(_T("SEARCHEDIT"))); + else + pImageList->Add(CTempIconLoader(_T("KADNODESEARCH"))); + m_iwColumn.SetImageList(pImageList); + m_iwColumn.Create(_T(""), WS_CHILD | WS_VISIBLE, CRect(0, 0, ICON_LEFTSPACE, rectWindow.bottom), this, 1); + + pImageList = new CImageList(); + pImageList->Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + pImageList->Add(CTempIconLoader(_T("FILTERCLEAR1"))); + pImageList->Add(CTempIconLoader(_T("FILTERCLEAR2"))); + m_iwReset.SetImageList(pImageList); + m_iwReset.Create(_T(""), WS_CHILD , CRect(0, 0, ICON_LEFTSPACE, rectWindow.bottom), this, 1); + + if (paIgnoredColums != NULL) + m_aIgnoredColums.Copy(*paIgnoredColums); + ShowColumnText(true); +} + +void CEditDelayed::SetEditRect(bool bUpdateResetButtonPos, bool bUpdateColumnButton) +{ + ASSERT( GetStyle() & ES_MULTILINE ); + + CRect editRect; + GetClientRect(&editRect); + + editRect.left += ICON_LEFTSPACE; + + if (m_bShowResetButton) + editRect.right -= 20; + SetRect(&editRect); + + if (m_bShowResetButton && bUpdateResetButtonPos) + m_iwReset.MoveWindow(editRect.right + 1, 0, 16, editRect.bottom); + if (bUpdateColumnButton) + m_iwColumn.MoveWindow(0, 0, ICON_LEFTSPACE, editRect.bottom); +} + +void CEditDelayed::OnLButtonDown(UINT nFlags, CPoint point) +{ + if (m_pctrlColumnHeader != NULL) + { + if (point.x <= ICON_LEFTSPACE) + { + // construct a popup menu out of the columnheader for the filter setting + CMenu menu; + menu.CreatePopupMenu(); + + HDITEM hdi; + TCHAR szBuffer[256]; + hdi.mask = HDI_TEXT | HDI_WIDTH; + hdi.pszText = szBuffer; + hdi.cchTextMax = _countof(szBuffer); + int nCount = m_pctrlColumnHeader->GetItemCount(); + int nIdx; + for (int i = 0; i < nCount ;i++) { + nIdx = m_pctrlColumnHeader->OrderToIndex(i); + m_pctrlColumnHeader->GetItem(nIdx, &hdi); + szBuffer[_countof(szBuffer) - 1] = _T('\0'); + bool bIgnored = false; + for (int i = 0; i < m_aIgnoredColums.GetCount(); i++){ + if (m_aIgnoredColums[i] == nIdx){ + bIgnored = true; + break; + } + } + if (hdi.cxy > 0 && !bIgnored) // ignore hidden columns + menu.AppendMenu(MF_STRING | ((m_nCurrentColumnIdx == nIdx) ? MF_CHECKED : MF_UNCHECKED), MP_FILTERCOLUMNS + nIdx, hdi.pszText); + } + + // draw the menu on a fixed position so it doesnt hides the inputtext + CRect editRect; + GetClientRect(&editRect); + CPoint pointMenu(2, editRect.bottom); + ClientToScreen(&pointMenu); + menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pointMenu.x, pointMenu.y, this); + return; + } + } + + CRect editRect; + GetClientRect(&editRect); + if (m_pointMousePos.x > editRect.right - ICON_LEFTSPACE && m_bShowResetButton) { + m_iwReset.ShowIcon(1); + SetCapture(); + return; + } + + CEdit::OnLButtonDown(nFlags, point); +} + +void CEditDelayed::OnLButtonUp(UINT nFlags, CPoint point) +{ + if (m_bShowResetButton) + { + m_iwReset.ShowIcon(0); + ReleaseCapture(); + + CRect editRect; + GetClientRect(&editRect); + if (m_pointMousePos.x > editRect.right - ICON_LEFTSPACE) + { + SetWindowText(_T("")); + DoDelayedEvalute(); + m_bShowResetButton = false; + SetEditRect(true); + m_iwReset.ShowWindow(SW_HIDE); + SetFocus(); + return; + } + } + CEdit::OnLButtonUp(nFlags, point); +} + +BOOL CEditDelayed::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) +{ + // show an arrow when hovering over any selfmade buttons instead of the caret + if (nHitTest == HTCLIENT) + { + CRect editRect; + GetClientRect(&editRect); + if (m_pointMousePos.x <= ICON_LEFTSPACE || (m_pointMousePos.x > editRect.right - ICON_LEFTSPACE && m_bShowResetButton)) { + ::SetCursor(m_hCursor); + return TRUE; + } + } + return CEdit::OnSetCursor(pWnd, nHitTest, message); +} + +void CEditDelayed::OnMouseMove(UINT nFlags, CPoint point) +{ + m_pointMousePos = point; + CEdit::OnMouseMove(nFlags, point); +} + +// show the title of the column selected for filtering if the control is empty and has no focus +void CEditDelayed::ShowColumnText(bool bShow) +{ + if (bShow) + { + if (GetWindowTextLength() != 0 && !m_bShowsColumnText) + return; + + m_bShowsColumnText = true; + if (m_pctrlColumnHeader != NULL) + { + HDITEM hdi; + TCHAR szBuffer[256]; + hdi.mask = HDI_TEXT | HDI_WIDTH; + hdi.pszText = szBuffer; + hdi.cchTextMax = _countof(szBuffer); + if (m_pctrlColumnHeader->GetItem(m_nCurrentColumnIdx, &hdi)) + { + szBuffer[_countof(szBuffer) - 1] = _T('\0'); + SetWindowText(hdi.pszText); + } + } + else + SetWindowText(m_strAlternateText); + } + else if (!bShow && m_bShowsColumnText) + { + m_bShowsColumnText = false; + SetWindowText(_T("")); + } +} + +HBRUSH CEditDelayed::CtlColor(CDC* pDC, UINT) +{ + // Use gray text color when showing the column text so it doesn't gets confused with typed in text + HBRUSH hbr = ::GetSysColorBrush(COLOR_WINDOW); + pDC->SetTextColor(::GetSysColor(m_bShowsColumnText ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT)); + pDC->SetBkColor(::GetSysColor(COLOR_WINDOW)); + return hbr; +} + +BOOL CEditDelayed::OnCommand(WPARAM wParam, LPARAM /*lParam*/) +{ + wParam = LOWORD(wParam); + if (wParam >= MP_FILTERCOLUMNS && wParam <= MP_FILTERCOLUMNS + 50) + { + if (m_nCurrentColumnIdx != (int)wParam - MP_FILTERCOLUMNS) + { + m_nCurrentColumnIdx = wParam - MP_FILTERCOLUMNS; + if (m_bShowsColumnText) + ShowColumnText(true); + else if (GetWindowTextLength() != 0) + DoDelayedEvalute(true); + } + } + return TRUE; +} + +void CEditDelayed::OnSize(UINT nType, int cx, int cy) +{ + CEdit::OnSize(nType, cx, cy); + SetEditRect(true, true); +} + + +///////////////////////////////////////////////////////////////////////////// +// CIconWnd + +BEGIN_MESSAGE_MAP(CIconWnd, CStatic) + ON_WM_PAINT() + ON_WM_ERASEBKGND() +END_MESSAGE_MAP() + +CIconWnd::CIconWnd() +{ + m_pImageList = NULL; + m_nCurrentIcon = 0; +} + +CIconWnd::~CIconWnd() +{ + delete m_pImageList; +} + +void CIconWnd::OnPaint() +{ + CPaintDC dc(this); + CRect rect; + GetClientRect(&rect); + dc.FillSolidRect(rect, GetSysColor(COLOR_WINDOW)); + m_pImageList->Draw(&dc, m_nCurrentIcon, CPoint(2, (rect.bottom - 16) / 2), ILD_NORMAL); +} + +BOOL CIconWnd::OnEraseBkgnd(CDC*) +{ + return TRUE; +} + +void CIconWnd::ShowIcon(int nIconNumber) +{ + if (nIconNumber == m_nCurrentIcon) + return; + m_nCurrentIcon = nIconNumber; + Invalidate(); + UpdateWindow(); +} diff --git a/EditDelayed.h b/EditDelayed.h new file mode 100644 index 00000000..45464ee9 --- /dev/null +++ b/EditDelayed.h @@ -0,0 +1,88 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once + +////////////////////////////////////////////////////////////////////////////// +// CIconWnd + +class CIconWnd : public CStatic +{ +public: + CIconWnd(); + virtual ~CIconWnd(); + + void SetImageList(CImageList* pImageList) { m_pImageList = pImageList; } + void ShowIcon(int nIconNumber); + +protected: + int m_nCurrentIcon; + CImageList* m_pImageList; + + DECLARE_MESSAGE_MAP() + afx_msg void OnPaint(); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); +}; + + +////////////////////////////////////////////////////////////////////////////// +// CEditDelayed + +class CEditDelayed : public CEdit +{ +public: + CEditDelayed(); + + void OnInit(CHeaderCtrl* pColumnHeader, CArray* paIgnoredColums = NULL); + void ShowColumnText(bool bShow); + + // when not using pColumnHeader this text will be shown when the control is empty and has no focus + void SetAlternateText(const CString& rstrText) { m_strAlternateText = rstrText; } + +protected: + bool m_bShuttingDown; + UINT_PTR m_uTimerResult; + DWORD m_dwLastModified; + CString m_strLastEvaluatedContent; + CIconWnd m_iwReset; + CIconWnd m_iwColumn; + HCURSOR m_hCursor; + CPoint m_pointMousePos; + bool m_bShowResetButton; + bool m_bShowsColumnText; + int m_nCurrentColumnIdx; + CString m_strAlternateText; + CHeaderCtrl* m_pctrlColumnHeader; + CArray m_aIgnoredColums; + + void DoDelayedEvalute(bool bForce = false); + void SetEditRect(bool bUpdateResetButtonPos, bool bUpdateColumnButton = false); + + DECLARE_MESSAGE_MAP() + afx_msg void OnSetFocus(CWnd* pOldWnd); + afx_msg void OnKillFocus(CWnd* pNewWnd); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnEnChange(); + afx_msg void OnDestroy(); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor); + afx_msg BOOL OnCommand(WPARAM wParam, LPARAM lParam); + afx_msg void OnSize(UINT nType, int cx, int cy); +}; diff --git a/EditX.cpp b/EditX.cpp new file mode 100644 index 00000000..7430d201 --- /dev/null +++ b/EditX.cpp @@ -0,0 +1,87 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "EditX.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +BEGIN_MESSAGE_MAP(CEditX, CEdit) + ON_WM_LBUTTONDOWN() + ON_WM_LBUTTONDBLCLK() + ON_WM_SETTINGCHANGE() + ON_WM_KEYDOWN() + ON_WM_CREATE() +END_MESSAGE_MAP() + +CEditX::CEditX() +{ + m_dwLastDblClick = 0; + m_dwThirdClickTime = 0; +} + +void CEditX::PreSubclassWindow() +{ + UpdateMetrics(); + CEdit::PreSubclassWindow(); +} + +int CEditX::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CEdit::OnCreate(lpCreateStruct) == -1) + return -1; + UpdateMetrics(); + return 0; +} + +void CEditX::UpdateMetrics() +{ + m_dwThirdClickTime = GetDoubleClickTime() / 2; + m_dwThirdClickTime += (m_dwThirdClickTime * 10) / 100; +} + +void CEditX::OnSettingChange(UINT uFlags, LPCTSTR lpszSection) +{ + UpdateMetrics(); + CEdit::OnSettingChange(uFlags, lpszSection); +} + +void CEditX::OnLButtonDown(UINT nFlags, CPoint point) +{ + // check for triple click: if we already had a double click, check if the current click is inside the threshold. + if ((GetCurrentMessage()->time - m_dwLastDblClick) <= m_dwThirdClickTime) + { + SetSel(0, -1); + // don't reset 'm_dwLastDblClick', if there is another click within the threshold time, the selection + // would be vanish again. + } + else + { + CEdit::OnLButtonDown(nFlags, point); + m_dwLastDblClick = 0; + } +} + +void CEditX::OnLButtonDblClk(UINT nFlags, CPoint point) +{ + CEdit::OnLButtonDblClk(nFlags, point); + m_dwLastDblClick = GetCurrentMessage()->time; +} diff --git a/EditX.h b/EditX.h new file mode 100644 index 00000000..db0b4b3f --- /dev/null +++ b/EditX.h @@ -0,0 +1,20 @@ +#pragma once + +class CEditX : public CEdit +{ +public: + CEditX(); + +protected: + DWORD m_dwLastDblClick; + DWORD m_dwThirdClickTime; + void UpdateMetrics(); + + virtual void PreSubclassWindow(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnSettingChange(UINT uFlags, LPCTSTR lpszSection); +}; diff --git a/EnBitmap.cpp b/EnBitmap.cpp new file mode 100644 index 00000000..96a752d6 --- /dev/null +++ b/EnBitmap.cpp @@ -0,0 +1,261 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "EnBitmap.h" +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +const int HIMETRIC_INCH = 2540; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CEnBitmap::CEnBitmap() +{ + +} + +CEnBitmap::~CEnBitmap() +{ + +} + +BOOL CEnBitmap::LoadImage(UINT uIDRes, LPCTSTR pszResourceType, HMODULE hInst, COLORREF crBack) +{ + return LoadImage(MAKEINTRESOURCE(uIDRes), pszResourceType, hInst, crBack); +} + +BOOL CEnBitmap::LoadImage(LPCTSTR lpszResourceName, LPCTSTR szResourceType, HMODULE hInst, COLORREF crBack) +{ + ASSERT(m_hObject == NULL); // only attach once, detach on destroy + + if (m_hObject != NULL) + return FALSE; + + BYTE* pBuff = NULL; + int nSize = 0; + BOOL bResult = FALSE; + + // first call is to get buffer size + if (GetResource(lpszResourceName, szResourceType, hInst, 0, nSize)) + { + if (nSize > 0) + { + pBuff = new BYTE[nSize]; + + // this loads it + if (GetResource(lpszResourceName, szResourceType, hInst, pBuff, nSize)) + { + IPicture* pPicture = LoadFromBuffer(pBuff, nSize); + + if (pPicture) + { + bResult = Attach(pPicture, crBack); + pPicture->Release(); + } + } + + delete [] pBuff; + } + } + + return bResult; +} + +BOOL CEnBitmap::LoadImage(LPCTSTR szImagePath, COLORREF crBack) +{ + ASSERT(m_hObject == NULL); // only attach once, detach on destroy + + if (m_hObject != NULL) + return FALSE; + + // If GDI+ is available, use that API because it supports more file formats and images with alpha channels. + // That DLL is installed with WinXP is available as redistributable from Microsoft for Win98+. As this DLL + // may not be available on some OS but we have to link statically to it, we have to take some special care. + // + extern bool g_bGdiPlusInstalled; + if (g_bGdiPlusInstalled) + { + CImage img; + if (SUCCEEDED(img.Load(szImagePath))) + { + CBitmap::Attach(img.Detach()); + return TRUE; + } + } + + BOOL bResult = FALSE; + CFile cFile; + CFileException e; + + if (cFile.Open(szImagePath, CFile::modeRead | CFile::typeBinary | CFile::shareDenyWrite, &e)) + { + int nSize = (int)cFile.GetLength(); + BYTE* pBuff = new BYTE[nSize]; + if (cFile.Read(pBuff, nSize) > 0) + { + IPicture* pPicture = LoadFromBuffer(pBuff, nSize); + + if (pPicture) + { + bResult = Attach(pPicture, crBack); + pPicture->Release(); + } + } + delete [] pBuff; + } + + return bResult; +} + +IPicture* CEnBitmap::LoadFromBuffer(BYTE* pBuff, int nSize) +{ + IPicture* pPicture = NULL; + + HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, nSize); + if (hGlobal != NULL) + { + void* pData = GlobalLock(hGlobal); + if (pData != NULL) + { + memcpy(pData, pBuff, nSize); + GlobalUnlock(hGlobal); + + IStream* pStream = NULL; + if (CreateStreamOnHGlobal(hGlobal, TRUE/*fDeleteOnRelease*/, &pStream) == S_OK) + { + // Not sure what the 'KeepOriginalFormat' property is really used for. But if 'OleLoadPicture' + // is invoked with 'fRunmode=FALSE' the function always creates a temporary file which even + // does not get deleted when all COM pointers were released. It eventually gets deleted only + // when process terminated. Using 'fRunmode=TRUE' does prevent this behaviour and does not + // seem to have any other side effects. + VERIFY( OleLoadPicture(pStream, nSize, TRUE/*FALSE*/, IID_IPicture, (LPVOID*)&pPicture) == S_OK ); + pStream->Release(); + } + else + GlobalFree(hGlobal); + } + else + GlobalFree(hGlobal); + } + + return pPicture; // caller releases +} + +BOOL CEnBitmap::GetResource(LPCTSTR lpName, LPCTSTR lpType, HMODULE hInst, void* pResource, int& nBufSize) +{ + HRSRC hResInfo; + HANDLE hRes; + LPSTR lpRes = NULL; + bool bResult = FALSE; + + // Find the resource + hResInfo = FindResource(hInst, lpName, lpType); + + if (hResInfo == NULL) + return false; + + // Load the resource + hRes = LoadResource(hInst, hResInfo); + + if (hRes == NULL) + return false; + + // Lock the resource + lpRes = (char*)LockResource(hRes); + + if (lpRes != NULL) + { + if (pResource == NULL) + { + nBufSize = SizeofResource(hInst, hResInfo); + bResult = true; + } + else + { + if (nBufSize >= (int)SizeofResource(hInst, hResInfo)) + { + memcpy(pResource, lpRes, nBufSize); + bResult = true; + } + } + + UnlockResource(hRes); + } + + // Free the resource + FreeResource(hRes); + + return bResult; +} + +BOOL CEnBitmap::Attach(IPicture* pPicture, COLORREF crBack) +{ + ASSERT(m_hObject == NULL); // only attach once, detach on destroy + + if (m_hObject != NULL) + return FALSE; + + ASSERT(pPicture); + + if (!pPicture) + return FALSE; + + BOOL bResult = FALSE; + + CDC dcMem; + CDC* pDC = CWnd::GetDesktopWindow()->GetDC(); + + if (dcMem.CreateCompatibleDC(pDC)) + { + long hmWidth; + long hmHeight; + + pPicture->get_Width(&hmWidth); + pPicture->get_Height(&hmHeight); + + int nWidth = MulDiv(hmWidth, pDC->GetDeviceCaps(LOGPIXELSX), HIMETRIC_INCH); + int nHeight = MulDiv(hmHeight, pDC->GetDeviceCaps(LOGPIXELSY), HIMETRIC_INCH); + + CBitmap bmMem; + + if (bmMem.CreateCompatibleBitmap(pDC, nWidth, nHeight)) + { + CBitmap* pOldBM = dcMem.SelectObject(&bmMem); + + if (crBack != -1) + dcMem.FillSolidRect(0, 0, nWidth, nHeight, crBack); + + HRESULT hr = pPicture->Render(dcMem, 0, 0, nWidth, nHeight, 0, hmHeight, hmWidth, -hmHeight, NULL); + dcMem.SelectObject(pOldBM); + + if (hr == S_OK) + bResult = CBitmap::Attach(bmMem.Detach()); + } + } + + CWnd::GetDesktopWindow()->ReleaseDC(pDC); + + return bResult; +} diff --git a/EnBitmap.h b/EnBitmap.h new file mode 100644 index 00000000..32e7db05 --- /dev/null +++ b/EnBitmap.h @@ -0,0 +1,20 @@ +#pragma once + +class CEnBitmap : public CBitmap +{ +public: + CEnBitmap(); + virtual ~CEnBitmap(); + + BOOL LoadImage(LPCTSTR pszImagePath, COLORREF crBack = 0); + BOOL LoadImage(UINT uIDRes, LPCTSTR pszResourceType, HMODULE hInst = NULL, COLORREF crBack = 0); + BOOL LoadImage(LPCTSTR lpszResourceName, LPCTSTR pszResourceType, HMODULE hInst = NULL, COLORREF crBack = 0); + + // helpers + static BOOL GetResource(LPCTSTR lpName, LPCTSTR lpType, HMODULE hInst, void* pResource, int& nBufSize); + static IPicture* LoadFromBuffer(BYTE* pBuff, int nSize); + +protected: + BOOL Attach(IPicture* pPicture, COLORREF crBack); + +}; diff --git a/EncryptedDatagramSocket.cpp b/EncryptedDatagramSocket.cpp new file mode 100644 index 00000000..dc7f32e7 --- /dev/null +++ b/EncryptedDatagramSocket.cpp @@ -0,0 +1,483 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +/* Basic Obfusicated Handshake Protocol UDP: + see EncryptedStreamSocket.h + +****************************** ED2K Packets + + -Keycreation Client <-> Clinet: + - Client A (Outgoing connection): + Sendkey: Md5() 23 + - Client B (Incomming connection): + Receivekey: Md5() 23 + - Note: The first 1024 Bytes will be _NOT_ discarded for UDP keys to safe CPU time + + - Handshake + -> The handshake is encrypted - except otherwise noted - by the Keys created above + -> Padding is cucrently not used for UDP meaning that PaddingLen will be 0, using PaddingLens up to 16 Bytes is acceptable however + Client A: + + - Additional Comments: + - For obvious reasons the UDP handshake is actually no handshake. If a different Encryption method (or better a different Key) is to be used this has to be negotiated in a TCP connection + - SemiRandomNotProtocolMarker is a Byte which has a value unequal any Protocol header byte. This is a compromiss, turning in complete randomness (and nice design) but gaining + a lower CPU usage + - Kad/Ed2k Marker are only indicators, which possibility could be tried first, and should not be trusted + +****************************** Server Packets + + -Keycreation Client <-> Server: + - Client A (Outgoing connection client -> server): + Sendkey: Md5() 7 + - Client B (Incomming connection): + Receivekey: Md5() 7 + - Note: The first 1024 Bytes will be _NOT_ discarded for UDP keys to safe CPU time + + - Handshake + -> The handshake is encrypted - except otherwise noted - by the Keys created above + -> Padding is cucrently not used for UDP meaning that PaddingLen will be 0, using PaddingLens up to 16 Bytes is acceptable however + Client A: + + - Overhead: 8 Bytes per UDP Packet + + - Security for Basic Obfuscation: + - Random looking packets, very limited protection against passive eavesdropping single packets + + - Additional Comments: + - For obvious reasons the UDP handshake is actually no handshake. If a different Encryption method (or better a different Key) is to be used this has to be negotiated in a TCP connection + - SemiRandomNotProtocolMarker is a Byte which has a value unequal any Protocol header byte. This is a compromiss, turning in complete randomness (and nice design) but gaining + a lower CPU usage + +****************************** KAD Packets + + -Keycreation Client <-> Client: + (Used in general in request packets) + - Client A (Outgoing connection): + Sendkey: Md5() 18 + - Client B (Incomming connection): + Receivekey: Md5() 18 + -- OR -- (Used in general in response packets) + - Client A (Outgoing connection): + Sendkey: Md5() 6 + - Client B (Incomming connection): + Receivekey: Md5() 6 + + - Note: The first 1024 Bytes will be _NOT_ discarded for UDP keys to safe CPU time + + - Handshake + -> The handshake is encrypted - except otherwise noted - by the Keys created above + -> Padding is cucrently not used for UDP meaning that PaddingLen will be 0, using PaddingLens up to 16 Bytes is acceptable however + Client A: + + - Overhead: 16 Bytes per UDP Packet + + - Kad/Ed2k Marker: + x 1 -> Most likely an ED2k Packet, try Userhash as Key first + 0 0 -> Most likely an Kad Packet, try NodeID as Key first + 1 0 -> Most likely an Kad Packet, try SenderKey as Key first + + - Additional Comments: + - For obvious reasons the UDP handshake is actually no handshake. If a different Encryption method (or better a different Key) is to be used this has to be negotiated in a TCP connection + - SemiRandomNotProtocolMarker is a Byte which has a value unequal any Protocol header byte. This is a compromiss, turning in complete randomness (and nice design) but gaining + a lower CPU usage + - Kad/Ed2k Marker are only indicators, which possibility could be tried first, and need not be trusted + - Packets which use the senderkey are prone to BruteForce attacks, which take only a few minutes (2^32) + which is while not acceptable for encryption fair enough for obfuscation +*/ + +#include "stdafx.h" +#include "EncryptedDatagramSocket.h" +#include "emule.h" +#include "md5sum.h" +#include "Log.h" +#include "preferences.h" +#include "opcodes.h" +#include "otherfunctions.h" +#include "Statistics.h" +#include "safefile.h" +#include "./kademlia/kademlia/prefs.h" +#include "./kademlia/kademlia/kademlia.h" +// random generator +#pragma warning(disable:4516) // access-declarations are deprecated; member using-declarations provide a better alternative +#pragma warning(disable:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4702) // unreachable code +#include +#pragma warning(default:4702) // unreachable code +#pragma warning(default:4100) // unreferenced formal parameter +#pragma warning(default:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(default:4516) // access-declarations are deprecated; member using-declarations provide a better alternative + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define CRYPT_HEADER_WITHOUTPADDING 8 +#define MAGICVALUE_UDP 91 +#define MAGICVALUE_UDP_SYNC_CLIENT 0x395F2EC1 +#define MAGICVALUE_UDP_SYNC_SERVER 0x13EF24D5 +#define MAGICVALUE_UDP_SERVERCLIENT 0xA5 +#define MAGICVALUE_UDP_CLIENTSERVER 0x6B + +static CryptoPP::AutoSeededRandomPool cryptRandomGen; + +CEncryptedDatagramSocket::CEncryptedDatagramSocket(){ +} + +CEncryptedDatagramSocket::~CEncryptedDatagramSocket(){ + +} + +int CEncryptedDatagramSocket::DecryptReceivedClient(BYTE* pbyBufIn, int nBufLen, BYTE** ppbyBufOut, uint32 dwIP, uint32* nReceiverVerifyKey, uint32* nSenderVerifyKey) const{ + int nResult = nBufLen; + *ppbyBufOut = pbyBufIn; + + if (nReceiverVerifyKey == NULL || nSenderVerifyKey == NULL){ + ASSERT( false ); + return nResult; + } + + *nReceiverVerifyKey = 0; + *nSenderVerifyKey = 0; + + if (nResult <= CRYPT_HEADER_WITHOUTPADDING /*|| !thePrefs.IsClientCryptLayerSupported()*/) + return nResult; + + switch (pbyBufIn[0]){ + case OP_EMULEPROT: + case OP_KADEMLIAPACKEDPROT: + case OP_KADEMLIAHEADER: + case OP_UDPRESERVEDPROT1: + case OP_UDPRESERVEDPROT2: + case OP_PACKEDPROT: + return nResult; // no encrypted packet (see description on top) + } + + // might be an encrypted packet, try to decrypt + RC4_Key_Struct keyReceiveKey; + uint32 dwValue = 0; + // check the marker bit which type this packet could be and which key to test first, this is only an indicator since old clients have it set random + // see the header for marker bits explanation + byte byCurrentTry = ((pbyBufIn[0] & 0x03) == 3) ? 1 : (pbyBufIn[0] & 0x03); + byte byTries; + if (Kademlia::CKademlia::GetPrefs() == NULL) { + // if kad never run, no point in checking anything except for ed2k encryption + byTries = 1; + byCurrentTry = 1; + } + else + byTries = 3; + bool bKadRecvKeyUsed = false; + bool bKad = false; + do{ + byTries--; + MD5Sum md5; + if (byCurrentTry == 0) { + // kad packet with NodeID as key + bKad = true; + bKadRecvKeyUsed = false; + if (Kademlia::CKademlia::GetPrefs()) { + uchar achKeyData[18]; + memcpy(achKeyData, Kademlia::CKademlia::GetPrefs()->GetKadID().GetData(), 16); + memcpy(achKeyData + 16, pbyBufIn + 1, 2); // random key part sent from remote client + md5.Calculate(achKeyData, sizeof(achKeyData)); + } + } + else if (byCurrentTry == 1) { + // ed2k packet + bKad = false; + bKadRecvKeyUsed = false; + uchar achKeyData[23]; + md4cpy(achKeyData, thePrefs.GetUserHash()); + achKeyData[20] = MAGICVALUE_UDP; + memcpy(achKeyData + 16, &dwIP, 4); + memcpy(achKeyData + 21, pbyBufIn + 1, 2); // random key part sent from remote client + md5.Calculate(achKeyData, sizeof(achKeyData)); + } + else if (byCurrentTry == 2) { + // kad packet with ReceiverKey as key + bKad = true; + bKadRecvKeyUsed = true; + if (Kademlia::CKademlia::GetPrefs()) { + uchar achKeyData[6]; + PokeUInt32(achKeyData, Kademlia::CPrefs::GetUDPVerifyKey(dwIP)); + memcpy(achKeyData + 4, pbyBufIn + 1, 2); // random key part sent from remote client + md5.Calculate(achKeyData, sizeof(achKeyData)); + } + } + else + ASSERT( false ); + + RC4CreateKey(md5.GetRawHash(), 16, &keyReceiveKey, true); + RC4Crypt(pbyBufIn + 3, (uchar*)&dwValue, sizeof(dwValue), &keyReceiveKey); + byCurrentTry = (byCurrentTry + 1) % 3; + } while (dwValue != MAGICVALUE_UDP_SYNC_CLIENT && byTries > 0); // try to decrypt as ed2k as well as kad packet if needed (max 3 rounds) + + if (dwValue == MAGICVALUE_UDP_SYNC_CLIENT){ + // yup this is an encrypted packet + // debugoutput notices + // the following cases are "allowed" but shouldn't happen given that there is only our implementation yet + if (bKad && (pbyBufIn[0] & 0x01) != 0) + DebugLog(_T("Received obfuscated UDP packet from clientIP: %s with wrong key marker bits (kad packet, ed2k bit)"), ipstr(dwIP)); + else if (bKad && !bKadRecvKeyUsed && (pbyBufIn[0] & 0x02) != 0) + DebugLog(_T("Received obfuscated UDP packet from clientIP: %s with wrong key marker bits (kad packet, nodeid key, recvkey bit)"), ipstr(dwIP)); + else if (bKad && bKadRecvKeyUsed && (pbyBufIn[0] & 0x02) == 0) + DebugLog(_T("Received obfuscated UDP packet from clientIP: %s with wrong key marker bits (kad packet, recvkey key, nodeid bit)"), ipstr(dwIP)); + + uint8 byPadLen; + RC4Crypt(pbyBufIn + 7, (uchar*)&byPadLen, 1, &keyReceiveKey); + nResult -= CRYPT_HEADER_WITHOUTPADDING; + if (nResult <= byPadLen){ + DebugLogError(_T("Invalid obfuscated UDP packet from clientIP: %s, Paddingsize (%u) larger than received bytes"), ipstr(dwIP), byPadLen); + return nBufLen; // pass through, let the Receivefunction do the errorhandling on this junk + } + if (byPadLen > 0) + RC4Crypt(NULL, NULL, byPadLen, &keyReceiveKey); + nResult -= byPadLen; + + if (bKad){ + if (nResult <= 8){ + DebugLogError(_T("Obfuscated Kad packet with mismatching size (verify keys missing) received from clientIP: %s"), ipstr(dwIP)); + return nBufLen; // pass through, let the Receivefunction do the errorhandling on this junk; + } + // read the verify keys + RC4Crypt(pbyBufIn + CRYPT_HEADER_WITHOUTPADDING + byPadLen, (uchar*)nReceiverVerifyKey, 4, &keyReceiveKey); + RC4Crypt(pbyBufIn + CRYPT_HEADER_WITHOUTPADDING + byPadLen + 4, (uchar*)nSenderVerifyKey, 4, &keyReceiveKey); + nResult -= 8; + } + *ppbyBufOut = pbyBufIn + (nBufLen - nResult); + RC4Crypt((uchar*)*ppbyBufOut, (uchar*)*ppbyBufOut, nResult, &keyReceiveKey); + theStats.AddDownDataOverheadCrypt(nBufLen - nResult); + //DEBUG_ONLY( DebugLog(_T("Received obfuscated UDP packet from clientIP: %s, Key: %s, RKey: %u, SKey: %u"), ipstr(dwIP), bKad ? (bKadRecvKeyUsed ? _T("ReceiverKey") : _T("NodeID")) : _T("UserHash") + // , nReceiverVerifyKey != 0 ? *nReceiverVerifyKey : 0, nSenderVerifyKey != 0 ? *nSenderVerifyKey : 0) ); + return nResult; // done + } + else{ + DebugLogWarning(_T("Obfuscated packet expected but magicvalue mismatch on UDP packet from clientIP: %s, Possible RecvKey: %u"), ipstr(dwIP), Kademlia::CPrefs::GetUDPVerifyKey(dwIP)); + return nBufLen; // pass through, let the Receivefunction do the errorhandling on this junk + } +} + +// Encrypt packet. Key used: +// pachClientHashOrKadID != NULL -> pachClientHashOrKadID +// pachClientHashOrKadID == NULL && bKad && nReceiverVerifyKey != 0 -> nReceiverVerifyKey +// else -> ASSERT +int CEncryptedDatagramSocket::EncryptSendClient(uchar** ppbyBuf, int nBufLen, const uchar* pachClientHashOrKadID, bool bKad, uint32 nReceiverVerifyKey, uint32 nSenderVerifyKey) const{ + ASSERT( theApp.GetPublicIP() != 0 || bKad ); + ASSERT( thePrefs.IsClientCryptLayerSupported() ); + ASSERT( pachClientHashOrKadID != NULL || nReceiverVerifyKey != 0 ); + ASSERT( (nReceiverVerifyKey == 0 && nSenderVerifyKey == 0) || bKad ); + + uint8 byPadLen = 0; // padding disabled for UDP currently + const uint32 nCryptHeaderLen = byPadLen + CRYPT_HEADER_WITHOUTPADDING + (bKad ? 8 : 0); + + uint32 nCryptedLen = nBufLen + nCryptHeaderLen; + uchar* pachCryptedBuffer = new uchar[nCryptedLen]; + bool bKadRecKeyUsed = false; + + uint16 nRandomKeyPart = (uint16)cryptRandomGen.GenerateWord32(0x0000, 0xFFFF); + MD5Sum md5; + if (bKad){ + if ((pachClientHashOrKadID == NULL || isnulmd4(pachClientHashOrKadID)) && nReceiverVerifyKey != 0) { + bKadRecKeyUsed = true; + uchar achKeyData[6]; + PokeUInt32(achKeyData, nReceiverVerifyKey); + PokeUInt16(achKeyData+4, nRandomKeyPart); + md5.Calculate(achKeyData, sizeof(achKeyData)); + //DEBUG_ONLY( DebugLog(_T("Creating obfuscated Kad packet encrypted by ReceiverKey (%u)"), nReceiverVerifyKey) ); + } + else if (pachClientHashOrKadID != NULL && !isnulmd4(pachClientHashOrKadID)) { + uchar achKeyData[18]; + md4cpy(achKeyData, pachClientHashOrKadID); + PokeUInt16(achKeyData+16, nRandomKeyPart); + md5.Calculate(achKeyData, sizeof(achKeyData)); + //DEBUG_ONLY( DebugLog(_T("Creating obfuscated Kad packet encrypted by Hash/NodeID %s"), md4str(pachClientHashOrKadID)) ); + } + else { + ASSERT( false ); + delete[] pachCryptedBuffer; + return nBufLen; + } + } + else{ + uchar achKeyData[23]; + md4cpy(achKeyData, pachClientHashOrKadID); + uint32 dwIP = theApp.GetPublicIP(); + memcpy(achKeyData+16, &dwIP, 4); + memcpy(achKeyData+21, &nRandomKeyPart, 2); + achKeyData[20] = MAGICVALUE_UDP; + md5.Calculate(achKeyData, sizeof(achKeyData)); + } + RC4_Key_Struct keySendKey; + RC4CreateKey(md5.GetRawHash(), 16, &keySendKey, true); + + // create the semi random byte encryption header + uint8 bySemiRandomNotProtocolMarker = 0; + int i; + for (i = 0; i < 128; i++){ + bySemiRandomNotProtocolMarker = cryptRandomGen.GenerateByte(); + bySemiRandomNotProtocolMarker = bKad ? (bySemiRandomNotProtocolMarker & 0xFE) : (bySemiRandomNotProtocolMarker | 0x01); // set the ed2k/kad marker bit + if (bKad) + bySemiRandomNotProtocolMarker = bKadRecKeyUsed ? ((bySemiRandomNotProtocolMarker & 0xFE) | 0x02) : (bySemiRandomNotProtocolMarker & 0xFC); // set the ed2k/kad and nodeid/reckey markerbit + else + bySemiRandomNotProtocolMarker = (bySemiRandomNotProtocolMarker | 0x01); // set the ed2k/kad marker bit + + bool bOk = false; + switch (bySemiRandomNotProtocolMarker){ // not allowed values + case OP_EMULEPROT: + case OP_KADEMLIAPACKEDPROT: + case OP_KADEMLIAHEADER: + case OP_UDPRESERVEDPROT1: + case OP_UDPRESERVEDPROT2: + case OP_PACKEDPROT: + break; + default: + bOk = true; + } + if (bOk) + break; + } + if (i >= 128){ + // either we have _really_ bad luck or the randomgenerator is a bit messed up + ASSERT( false ); + bySemiRandomNotProtocolMarker = 0x01; + } + + uint32 dwMagicValue = MAGICVALUE_UDP_SYNC_CLIENT; + pachCryptedBuffer[0] = bySemiRandomNotProtocolMarker; + memcpy(pachCryptedBuffer + 1, &nRandomKeyPart, 2); + RC4Crypt((uchar*)&dwMagicValue, pachCryptedBuffer + 3, 4, &keySendKey); + RC4Crypt((uchar*)&byPadLen, pachCryptedBuffer + 7, 1, &keySendKey); + + for (int j = 0; j < byPadLen; j++){ + uint8 byRand = (uint8)rand(); // they actually dont really need to be random, but it doesn't hurts either + RC4Crypt((uchar*)&byRand, pachCryptedBuffer + CRYPT_HEADER_WITHOUTPADDING + j, 1, &keySendKey); + } + + if (bKad){ + RC4Crypt((uchar*)&nReceiverVerifyKey, pachCryptedBuffer + CRYPT_HEADER_WITHOUTPADDING + byPadLen, 4, &keySendKey); + RC4Crypt((uchar*)&nSenderVerifyKey, pachCryptedBuffer + CRYPT_HEADER_WITHOUTPADDING + byPadLen + 4, 4, &keySendKey); + } + + RC4Crypt(*ppbyBuf, pachCryptedBuffer + nCryptHeaderLen, nBufLen, &keySendKey); + delete[] *ppbyBuf; + *ppbyBuf = pachCryptedBuffer; + + theStats.AddUpDataOverheadCrypt(nCryptedLen - nBufLen); + return nCryptedLen; +} + +int CEncryptedDatagramSocket::DecryptReceivedServer(BYTE* pbyBufIn, int nBufLen, BYTE** ppbyBufOut, uint32 dwBaseKey, uint32 dbgIP) const{ + int nResult = nBufLen; + *ppbyBufOut = pbyBufIn; + + if (nResult <= CRYPT_HEADER_WITHOUTPADDING || !thePrefs.IsServerCryptLayerUDPEnabled() || dwBaseKey == 0) + return nResult; + + if(pbyBufIn[0] == OP_EDONKEYPROT) + return nResult; // no encrypted packet (see description on top) + + // might be an encrypted packet, try to decrypt + uchar achKeyData[7]; + memcpy(achKeyData, &dwBaseKey, 4); + achKeyData[4] = MAGICVALUE_UDP_SERVERCLIENT; + memcpy(achKeyData + 5, pbyBufIn + 1, 2); // random key part sent from remote server + MD5Sum md5(achKeyData, sizeof(achKeyData)); + RC4_Key_Struct keyReceiveKey; + RC4CreateKey(md5.GetRawHash(), 16, &keyReceiveKey, true); + + uint32 dwValue; + RC4Crypt(pbyBufIn + 3, (uchar*)&dwValue, sizeof(dwValue), &keyReceiveKey); + if (dwValue == MAGICVALUE_UDP_SYNC_SERVER){ + // yup this is an encrypted packet + if (thePrefs.GetDebugServerUDPLevel() > 0) + DEBUG_ONLY( DebugLog(_T("Received obfuscated UDP packet from ServerIP: %s"), ipstr(dbgIP)) ); + uint8 byPadLen; + RC4Crypt(pbyBufIn + 7, (uchar*)&byPadLen, 1, &keyReceiveKey); + byPadLen &= 15; + nResult -= CRYPT_HEADER_WITHOUTPADDING; + if (nResult <= byPadLen){ + DebugLogError(_T("Invalid obfuscated UDP packet from ServerIP: %s, Paddingsize (%u) larger than received bytes"), ipstr(dbgIP), byPadLen); + return nBufLen; // pass through, let the Receivefunction do the errorhandling on this junk + } + if (byPadLen > 0) + RC4Crypt(NULL, NULL, byPadLen, &keyReceiveKey); + nResult -= byPadLen; + *ppbyBufOut = pbyBufIn + (nBufLen - nResult); + RC4Crypt((uchar*)*ppbyBufOut, (uchar*)*ppbyBufOut, nResult, &keyReceiveKey); + + theStats.AddDownDataOverheadCrypt(nBufLen - nResult); + return nResult; // done + } + else{ + DebugLogWarning(_T("Obfuscated packet expected but magicvalue mismatch on UDP packet from ServerIP: %s"), ipstr(dbgIP)); + return nBufLen; // pass through, let the Receivefunction do the errorhandling on this junk + } +} + +int CEncryptedDatagramSocket::EncryptSendServer(uchar** ppbyBuf, int nBufLen, uint32 dwBaseKey) const{ + ASSERT( thePrefs.IsServerCryptLayerUDPEnabled() ); + ASSERT( dwBaseKey != 0 ); + + uint8 byPadLen = 0; // padding disabled for UDP currently + uint32 nCryptedLen = nBufLen + byPadLen + CRYPT_HEADER_WITHOUTPADDING; + uchar* pachCryptedBuffer = new uchar[nCryptedLen]; + + uint16 nRandomKeyPart = (uint16)cryptRandomGen.GenerateWord32(0x0000, 0xFFFF); + + uchar achKeyData[7]; + memcpy(achKeyData, &dwBaseKey, 4); + achKeyData[4] = MAGICVALUE_UDP_CLIENTSERVER; + memcpy(achKeyData + 5, &nRandomKeyPart, 2); + MD5Sum md5(achKeyData, sizeof(achKeyData)); + RC4_Key_Struct keySendKey; + RC4CreateKey(md5.GetRawHash(), 16, &keySendKey, true); + + // create the semi random byte encryption header + uint8 bySemiRandomNotProtocolMarker = 0; + int i; + for (i = 0; i < 128; i++){ + bySemiRandomNotProtocolMarker = cryptRandomGen.GenerateByte(); + if (bySemiRandomNotProtocolMarker != OP_EDONKEYPROT) // not allowed values + break; + } + if (i >= 128){ + // either we have _real_ bad luck or the randomgenerator is a bit messed up + ASSERT( false ); + bySemiRandomNotProtocolMarker = 0x01; + } + + uint32 dwMagicValue = MAGICVALUE_UDP_SYNC_SERVER; + pachCryptedBuffer[0] = bySemiRandomNotProtocolMarker; + memcpy(pachCryptedBuffer + 1, &nRandomKeyPart, 2); + RC4Crypt((uchar*)&dwMagicValue, pachCryptedBuffer + 3, 4, &keySendKey); + RC4Crypt((uchar*)&byPadLen, pachCryptedBuffer + 7, 1, &keySendKey); + + for (int j = 0; j < byPadLen; j++){ + uint8 byRand = (uint8)rand(); // they actually dont really need to be random, but it doesn't hurts either + RC4Crypt((uchar*)&byRand, pachCryptedBuffer + CRYPT_HEADER_WITHOUTPADDING + j, 1, &keySendKey); + } + RC4Crypt(*ppbyBuf, pachCryptedBuffer + CRYPT_HEADER_WITHOUTPADDING + byPadLen, nBufLen, &keySendKey); + delete[] *ppbyBuf; + *ppbyBuf = pachCryptedBuffer; + + theStats.AddUpDataOverheadCrypt(nCryptedLen - nBufLen); + return nCryptedLen; +} diff --git a/EncryptedDatagramSocket.h b/EncryptedDatagramSocket.h new file mode 100644 index 00000000..260f9418 --- /dev/null +++ b/EncryptedDatagramSocket.h @@ -0,0 +1,32 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +class CEncryptedDatagramSocket +{ +public: + CEncryptedDatagramSocket(); + virtual ~CEncryptedDatagramSocket(); + +protected: + int DecryptReceivedClient(BYTE* pbyBufIn, int nBufLen, BYTE** ppbyBufOut, uint32 dwIP, uint32* nReceiverVerifyKey, uint32* nSenderVerifyKey) const; + int EncryptSendClient(uchar** ppbyBuf, int nBufLen, const uchar* pachClientHashOrKadID, bool bKad, uint32 nReceiverVerifyKey, uint32 nSenderVerifyKey) const; + + int DecryptReceivedServer(BYTE* pbyBufIn, int nBufLen, BYTE** ppbyBufOut, uint32 dwBaseKey, uint32 dbgIP) const; + int EncryptSendServer(uchar** ppbyBuf, int nBufLen, uint32 dwBaseKey) const; + +}; \ No newline at end of file diff --git a/EncryptedStreamSocket.cpp b/EncryptedStreamSocket.cpp new file mode 100644 index 00000000..686b9a0f --- /dev/null +++ b/EncryptedStreamSocket.cpp @@ -0,0 +1,760 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +/* Basic Obfuscated Handshake Protocol Client <-> Client: + -Keycreation: + - Client A (Outgoing connection): + Sendkey: Md5() 21 + Receivekey: Md5() 21 + - Client B (Incomming connection): + Sendkey: Md5() 21 + Receivekey: Md5() 21 + NOTE: First 1024 Bytes are discarded + + - Handshake + -> The handshake is encrypted - except otherwise noted - by the Keys created above + -> Handshake is blocking - do not start sending an answer before the request is completly received (this includes the random bytes) + -> EncryptionMethod = 0 is Obfuscation and the only supported right now + Client A: + Client B: + -> The basic handshake is finished here, if an additional/different EncryptionMethod was selected it may continue negotiating details for this one + + - Overhead: 18-48 (~33) Bytes + 2 * IP/TCP Headers per Connection + + - Security for Basic Obfuscation: + - Random looking stream, very limited protection against passive eavesdropping single connections + + - Additional Comments: + - RandomKeyPart is needed to make multiple connections between two clients look different (but still random), since otherwise the same key + would be used and RC4 would create the same output. Since the key is a MD5 hash it doesnt weakens the key if that part is known + - Why DH-KeyAgreement isn't used as basic obfuscation key: It doesn't offers substantial more protection against passive connection based protocol identification, it has about 200 bytes more overhead, + needs more CPU time, we cannot say if the received data is junk, unencrypted or part of the keyagreement before the handshake is finished without loosing the complete randomness, + it doesn't offers substantial protection against eavesdropping without added authentification + +Basic Obfuscated Handshake Protocol Client <-> Server: + - RC4 Keycreation: + - Client (Outgoing connection): + Sendkey: Md5() 97 + Receivekey: Md5() 97 + - Server (Incomming connection): + Sendkey: Md5() 97 + Receivekey: Md5() 97 + + NOTE: First 1024 Bytes are discarded + + - Handshake + -> The handshake is encrypted - except otherwise noted - by the Keys created above + -> Handshake is blocking - do not start sending an answer before the request is completly received (this includes the random bytes) + -> EncryptionMethod = 0 is Obfuscation and the only supported right now + + Client: + Server: + Client: (Answer delayed till first payload to save a frame) + + + -> The basic handshake is finished here, if an additional/different EncryptionMethod was selected it may continue negotiating details for this one + + - Overhead: 206-251 (~229) Bytes + 2 * IP/TCP Headers Headers per Connectionon + + - DH Agreement Specifics: sizeof(a) and sizeof(b) = 128 Bits, g = 2, p = dh768_p (see below), sizeof p, s, etc. = 768 bits +*/ + +#include "stdafx.h" +#include "EncryptedStreamSocket.h" +#include "emule.h" +#include "md5sum.h" +#include "Log.h" +#include "preferences.h" +#include "otherfunctions.h" +#include "safefile.h" +#include "opcodes.h" +#include "clientlist.h" +#include "sockets.h" +// cryptoPP used for DH integer calculations +#pragma warning(disable:4516) // access-declarations are deprecated; member using-declarations provide a better alternative +#pragma warning(disable:4100) // unreferenced formal parameter +#include +#pragma warning(default:4100) // unreferenced formal parameter +#pragma warning(default:4516) // access-declarations are deprecated; member using-declarations provide a better alternative + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define MAGICVALUE_REQUESTER 34 // modification of the requester-send and server-receive key +#define MAGICVALUE_SERVER 203 // modification of the server-send and requester-send key +#define MAGICVALUE_SYNC 0x835E6FC4 // value to check if we have a working encrypted stream +#define DHAGREEMENT_A_BITS 128 + +#define PRIMESIZE_BYTES 96 +static unsigned char dh768_p[]={ + 0xF2,0xBF,0x52,0xC5,0x5F,0x58,0x7A,0xDD,0x53,0x71,0xA9,0x36, + 0xE8,0x86,0xEB,0x3C,0x62,0x17,0xA3,0x3E,0xC3,0x4C,0xB4,0x0D, + 0xC7,0x3A,0x41,0xA6,0x43,0xAF,0xFC,0xE7,0x21,0xFC,0x28,0x63, + 0x66,0x53,0x5B,0xDB,0xCE,0x25,0x9F,0x22,0x86,0xDA,0x4A,0x91, + 0xB2,0x07,0xCB,0xAA,0x52,0x55,0xD4,0xF6,0x1C,0xCE,0xAE,0xD4, + 0x5A,0xD5,0xE0,0x74,0x7D,0xF7,0x78,0x18,0x28,0x10,0x5F,0x34, + 0x0F,0x76,0x23,0x87,0xF8,0x8B,0x28,0x91,0x42,0xFB,0x42,0x68, + 0x8F,0x05,0x15,0x0F,0x54,0x8B,0x5F,0x43,0x6A,0xF7,0x0D,0xF3, + }; + +static CryptoPP::AutoSeededRandomPool cryptRandomGen; + +IMPLEMENT_DYNAMIC(CEncryptedStreamSocket, CAsyncSocketEx) + +CEncryptedStreamSocket::CEncryptedStreamSocket(){ + m_StreamCryptState = thePrefs.IsClientCryptLayerSupported() ? ECS_UNKNOWN : ECS_NONE; + m_NegotiatingState = ONS_NONE; + m_pRC4ReceiveKey = NULL; + m_pRC4SendKey = NULL; + m_nObfuscationBytesReceived = 0; + m_bFullReceive = true; + m_dbgbyEncryptionSupported = 0xFF; + m_dbgbyEncryptionRequested = 0xFF; + m_dbgbyEncryptionMethodSet = 0xFF; + m_nReceiveBytesWanted = 0; + m_pfiReceiveBuffer = NULL; + m_pfiSendBuffer = NULL; + m_EncryptionMethod = ENM_OBFUSCATION; + m_nRandomKeyPart = 0; + m_bServerCrypt = false; +}; + +CEncryptedStreamSocket::~CEncryptedStreamSocket(){ + delete m_pRC4ReceiveKey; + delete m_pRC4SendKey; + if (m_pfiReceiveBuffer != NULL) + free(m_pfiReceiveBuffer->Detach()); + delete m_pfiReceiveBuffer; + delete m_pfiSendBuffer; +}; + +void CEncryptedStreamSocket::CryptPrepareSendData(uchar* pBuffer, uint32 nLen){ + if (!IsEncryptionLayerReady()){ + ASSERT( false ); // must be a bug + return; + } + if (m_StreamCryptState == ECS_UNKNOWN){ + //this happens when the encryption option was not set on a outgoing connection + //or if we try to send before receiving on a incoming connection - both shouldn't happen + m_StreamCryptState = ECS_NONE; + DebugLogError(_T("CEncryptedStreamSocket: Overwriting State ECS_UNKNOWN with ECS_NONE because of premature Send() (%s)"), DbgGetIPString()); + } + if (m_StreamCryptState == ECS_ENCRYPTING) + RC4Crypt(pBuffer, pBuffer, nLen, m_pRC4SendKey); +} + +// unfortunatly sending cannot be made transparent for the derived class, because of WSA_WOULDBLOCK +// together with the fact that each byte must pass the keystream only once +int CEncryptedStreamSocket::Send(const void* lpBuf, int nBufLen, int nFlags){ + if (!IsEncryptionLayerReady()){ + ASSERT( false ); // must be a bug + return 0; + } + else if (m_bServerCrypt && m_StreamCryptState == ECS_ENCRYPTING && m_pfiSendBuffer != NULL){ + ASSERT( m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING ); + // handshakedata was delayed to put it into one frame with the first paypload to the server + // do so now with the payload attached + int nRes = SendNegotiatingData(lpBuf, nBufLen, nBufLen); + ASSERT( nRes != SOCKET_ERROR ); + (void)nRes; + return nBufLen; // report a full send, even if we didn't for some reason - the data is know in our buffer and will be handled later + } + else if (m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING) + ASSERT( false ); + + if (m_StreamCryptState == ECS_UNKNOWN){ + //this happens when the encryption option was not set on a outgoing connection + //or if we try to send before receiving on a incoming connection - both shouldn't happen + m_StreamCryptState = ECS_NONE; + DebugLogError(_T("CEncryptedStreamSocket: Overwriting State ECS_UNKNOWN with ECS_NONE because of premature Send() (%s)"), DbgGetIPString()); + } + return CAsyncSocketEx::Send(lpBuf, nBufLen, nFlags); +} + +int CEncryptedStreamSocket::SendOv(CArray& raBuffer, DWORD& dwBytesSent, LPWSAOVERLAPPED lpOverlapped) +{ + if (!IsEncryptionLayerReady()){ + ASSERT( false ); // must be a bug + return -1; + } + else if (m_bServerCrypt && m_StreamCryptState == ECS_ENCRYPTING && m_pfiSendBuffer != NULL){ + ASSERT( m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING ); + // handshakedata was delayed to put it into one frame with the first paypload to the server + // attach it now to the sendbuffer + WSABUF pCurBuf; + pCurBuf.len = (ULONG)m_pfiSendBuffer->GetLength(); + pCurBuf.buf = reinterpret_cast(m_pfiSendBuffer->Detach()); + raBuffer.InsertAt(0, pCurBuf); + m_NegotiatingState = ONS_COMPLETE; + delete m_pfiSendBuffer; + m_pfiSendBuffer = NULL; + } + else if (m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING) + ASSERT( false ); + + if (m_StreamCryptState == ECS_UNKNOWN){ + //this happens when the encryption option was not set on a outgoing connection + //or if we try to send before receiving on a incoming connection - both shouldn't happen + m_StreamCryptState = ECS_NONE; + DebugLogError(_T("CEncryptedStreamSocket: Overwriting State ECS_UNKNOWN with ECS_NONE because of premature Send() (%s)"), DbgGetIPString()); + } + return WSASend(GetSocketHandle(), raBuffer.GetData(), raBuffer.GetCount(), &dwBytesSent, 0, lpOverlapped, NULL); +} + + +bool CEncryptedStreamSocket::IsEncryptionLayerReady(){ + return ( (m_StreamCryptState == ECS_NONE || m_StreamCryptState == ECS_ENCRYPTING || m_StreamCryptState == ECS_UNKNOWN ) + && (m_pfiSendBuffer == NULL || (m_bServerCrypt && m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING)) ); +} + + +int CEncryptedStreamSocket::Receive(void* lpBuf, int nBufLen, int nFlags){ + m_nObfuscationBytesReceived = CAsyncSocketEx::Receive(lpBuf, nBufLen, nFlags); + m_bFullReceive = m_nObfuscationBytesReceived == (uint32)nBufLen; + + if(m_nObfuscationBytesReceived == SOCKET_ERROR || m_nObfuscationBytesReceived <= 0){ + return m_nObfuscationBytesReceived; + } + switch (m_StreamCryptState) { + case ECS_NONE: // disabled, just pass it through + return m_nObfuscationBytesReceived; + case ECS_PENDING: + case ECS_PENDING_SERVER: + ASSERT( false ); + DebugLogError(_T("CEncryptedStreamSocket Received data before sending on outgoing connection")); + m_StreamCryptState = ECS_NONE; + return m_nObfuscationBytesReceived; + case ECS_UNKNOWN:{ + uint32 nRead = 1; + bool bNormalHeader = false; + switch (((uchar*)lpBuf)[0]){ + case OP_EDONKEYPROT: + case OP_PACKEDPROT: + case OP_EMULEPROT: + bNormalHeader = true; + break; + } + if (!bNormalHeader){ + StartNegotiation(false); + const uint32 nNegRes = Negotiate((uchar*)lpBuf + nRead, m_nObfuscationBytesReceived - nRead); + if (nNegRes == (-1)) + return 0; + nRead += nNegRes; + if (nRead != (uint32)m_nObfuscationBytesReceived){ + // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen + // (note: even if it just finished the handshake here, there still can be no data left, since the other client didnt received our response yet) + DebugLogError(_T("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (1)"), DbgGetIPString()); + OnError(ERR_ENCRYPTION); + } + return 0; + } + else{ + // doesn't seems to be encrypted + m_StreamCryptState = ECS_NONE; + + // if we require an encrypted connection, cut the connection here. This shouldn't happen that often + // at least with other up-to-date eMule clients because they check for incompability before connecting if possible + if (thePrefs.IsClientCryptLayerRequired()){ + // TODO: Remove me when i have been solved + // Even if the Require option is enabled, we currently have to accept unencrypted connection which are made + // for lowid/firewall checks from servers and other from us selected client. Otherwise, this option would + // always result in a lowid/firewalled status. This is of course not nice, but we can't avoid this walkarround + // untill servers and kad completely support encryption too, which will at least for kad take a bit + // only exception is the .ini option ClientCryptLayerRequiredStrict which will even ignore test connections + // Update: New server now support encrypted callbacks + + SOCKADDR_IN sockAddr = {0}; + int nSockAddrLen = sizeof(sockAddr); + GetPeerName((SOCKADDR*)&sockAddr, &nSockAddrLen); + if (thePrefs.IsClientCryptLayerRequiredStrict() || (!theApp.serverconnect->AwaitingTestFromIP(sockAddr.sin_addr.S_un.S_addr) + && !theApp.clientlist->IsKadFirewallCheckIP(sockAddr.sin_addr.S_un.S_addr)) ) + { +#if defined(_DEBUG) || defined(_BETA) || defined(_DEVBUILD) + // TODO: Remove after testing + AddDebugLogLine(DLP_DEFAULT, false, _T("Rejected incoming connection because Obfuscation was required but not used %s"), DbgGetIPString() ); +#endif + OnError(ERR_ENCRYPTION_NOTALLOWED); + return 0; + } + else + AddDebugLogLine(DLP_DEFAULT, false, _T("Incoming unencrypted firewallcheck connection permitted despite RequireEncryption setting - %s"), DbgGetIPString() ); + } + + return m_nObfuscationBytesReceived; // buffer was unchanged, we can just pass it through + } + } + case ECS_ENCRYPTING: + // basic obfuscation enabled and set, so decrypt and pass along + RC4Crypt((uchar*)lpBuf, (uchar*)lpBuf, m_nObfuscationBytesReceived, m_pRC4ReceiveKey); + return m_nObfuscationBytesReceived; + case ECS_NEGOTIATING:{ + const uint32 nRead = Negotiate((uchar*)lpBuf, m_nObfuscationBytesReceived); + if (nRead == (-1)) + return 0; + else if (nRead != (uint32)m_nObfuscationBytesReceived && m_StreamCryptState != ECS_ENCRYPTING){ + // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen + DebugLogError(_T("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (2)"), DbgGetIPString()); + OnError(ERR_ENCRYPTION); + return 0; + } + else if (nRead != (uint32)m_nObfuscationBytesReceived && m_StreamCryptState == ECS_ENCRYPTING){ + // we finished the handshake and if we this was an outgoing connection it is allowed (but strange and unlikely) that the client sent payload + DebugLogWarning(_T("CEncryptedStreamSocket: Client %s has finished the handshake but also sent payload on a outgoing connection"), DbgGetIPString()); + memmove(lpBuf, (uchar*)lpBuf + nRead, m_nObfuscationBytesReceived - nRead); + return m_nObfuscationBytesReceived - nRead; + } + else + return 0; + } + default: + ASSERT( false ); + return m_nObfuscationBytesReceived; + } +} + +void CEncryptedStreamSocket::SetConnectionEncryption(bool bEnabled, const uchar* pTargetClientHash, bool bServerConnection){ + if (m_StreamCryptState != ECS_UNKNOWN && m_StreamCryptState != ECS_NONE){ + if (!m_StreamCryptState == ECS_NONE || bEnabled) + ASSERT( false ); + return; + } + ASSERT( m_pRC4SendKey == NULL ); + ASSERT( m_pRC4ReceiveKey == NULL ); + + if (bEnabled && pTargetClientHash != NULL && !bServerConnection){ + m_StreamCryptState = ECS_PENDING; + // create obfuscation keys, see on top for key format + + // use the crypt random generator + m_nRandomKeyPart = cryptRandomGen.GenerateWord32(); + + uchar achKeyData[21]; + md4cpy(achKeyData, pTargetClientHash); + memcpy(achKeyData + 17, &m_nRandomKeyPart, 4); + + achKeyData[16] = MAGICVALUE_REQUESTER; + MD5Sum md5(achKeyData, sizeof(achKeyData)); + m_pRC4SendKey = RC4CreateKey(md5.GetRawHash(), 16, NULL); + + achKeyData[16] = MAGICVALUE_SERVER; + md5.Calculate(achKeyData, sizeof(achKeyData)); + m_pRC4ReceiveKey = RC4CreateKey(md5.GetRawHash(), 16, NULL); + } + else if (bServerConnection && bEnabled){ + m_bServerCrypt = true; + m_StreamCryptState = ECS_PENDING_SERVER; + } + else{ + ASSERT( !bEnabled ); + m_StreamCryptState = ECS_NONE; + } +} + +void CEncryptedStreamSocket::OnSend(int){ + // if the socket just connected and this is outgoing, we might want to start the handshake here + if (m_StreamCryptState == ECS_PENDING || m_StreamCryptState == ECS_PENDING_SERVER){ + StartNegotiation(true); + return; + } + // check if we have negotiating data pending + if (m_pfiSendBuffer != NULL){ + ASSERT( m_StreamCryptState >= ECS_NEGOTIATING ); + SendNegotiatingData(NULL, 0); + } +} + +void CEncryptedStreamSocket::StartNegotiation(bool bOutgoing){ + + if (!bOutgoing){ + m_NegotiatingState = ONS_BASIC_CLIENTA_RANDOMPART; + m_StreamCryptState = ECS_NEGOTIATING; + m_nReceiveBytesWanted = 4; + } + else if (m_StreamCryptState == ECS_PENDING){ + + CSafeMemFile fileRequest(29); + const uint8 bySemiRandomNotProtocolMarker = GetSemiRandomNotProtocolMarker(); + fileRequest.WriteUInt8(bySemiRandomNotProtocolMarker); + fileRequest.WriteUInt32(m_nRandomKeyPart); + fileRequest.WriteUInt32(MAGICVALUE_SYNC); + const uint8 bySupportedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version + fileRequest.WriteUInt8(bySupportedEncryptionMethod); + fileRequest.WriteUInt8(bySupportedEncryptionMethod); // so we also prefer this one + uint8 byPadding = (uint8)(cryptRandomGen.GenerateByte() % (thePrefs.GetCryptTCPPaddingLength() + 1)); + fileRequest.WriteUInt8(byPadding); + for (int i = 0; i < byPadding; i++) + fileRequest.WriteUInt8(cryptRandomGen.GenerateByte()); + + m_NegotiatingState = ONS_BASIC_CLIENTB_MAGICVALUE; + m_StreamCryptState = ECS_NEGOTIATING; + m_nReceiveBytesWanted = 4; + + SendNegotiatingData(fileRequest.GetBuffer(), (uint32)fileRequest.GetLength(), 5); + } + else if (m_StreamCryptState == ECS_PENDING_SERVER){ + CSafeMemFile fileRequest(113); + const uint8 bySemiRandomNotProtocolMarker = GetSemiRandomNotProtocolMarker(); + fileRequest.WriteUInt8(bySemiRandomNotProtocolMarker); + + m_cryptDHA.Randomize(cryptRandomGen, DHAGREEMENT_A_BITS); // our random a + ASSERT( m_cryptDHA.MinEncodedSize() <= DHAGREEMENT_A_BITS / 8 ); + CryptoPP::Integer cryptDHPrime((byte*)dh768_p, PRIMESIZE_BYTES); // our fixed prime + // calculate g^a % p + CryptoPP::Integer cryptDHGexpAmodP = CryptoPP::a_exp_b_mod_c(CryptoPP::Integer(2), m_cryptDHA, cryptDHPrime); + ASSERT( m_cryptDHA.MinEncodedSize() <= PRIMESIZE_BYTES ); + // put the result into a buffer + uchar aBuffer[PRIMESIZE_BYTES]; + cryptDHGexpAmodP.Encode(aBuffer, PRIMESIZE_BYTES); + + fileRequest.Write(aBuffer, PRIMESIZE_BYTES); + uint8 byPadding = (uint8)(cryptRandomGen.GenerateByte() % 16); // add random padding + fileRequest.WriteUInt8(byPadding); + for (int i = 0; i < byPadding; i++) + fileRequest.WriteUInt8(cryptRandomGen.GenerateByte()); + + m_NegotiatingState = ONS_BASIC_SERVER_DHANSWER; + m_StreamCryptState = ECS_NEGOTIATING; + m_nReceiveBytesWanted = 96; + + SendNegotiatingData(fileRequest.GetBuffer(), (uint32)fileRequest.GetLength(), (uint32)fileRequest.GetLength()); + } + else{ + ASSERT( false ); + m_StreamCryptState = ECS_NONE; + return; + } +} + +int CEncryptedStreamSocket::Negotiate(const uchar* pBuffer, uint32 nLen){ + uint32 nRead = 0; + ASSERT( m_nReceiveBytesWanted > 0 ); + try{ + while (m_NegotiatingState != ONS_COMPLETE && m_nReceiveBytesWanted > 0){ + if (m_nReceiveBytesWanted > 512){ + ASSERT( false ); + return 0; + } + + if (m_pfiReceiveBuffer == NULL){ + BYTE* pReceiveBuffer = (BYTE*)malloc(512); // use a fixed size buffer + if (pReceiveBuffer == NULL) + AfxThrowMemoryException(); + m_pfiReceiveBuffer = new CSafeMemFile(pReceiveBuffer, 512); + } + const uint32 nToRead = min(nLen - nRead, m_nReceiveBytesWanted); + m_pfiReceiveBuffer->Write(pBuffer + nRead, nToRead); + nRead += nToRead; + m_nReceiveBytesWanted -= nToRead; + if (m_nReceiveBytesWanted > 0) + return nRead; + const uint32 nCurrentBytesLen = (uint32)m_pfiReceiveBuffer->GetPosition(); + + if (m_NegotiatingState != ONS_BASIC_CLIENTA_RANDOMPART && m_NegotiatingState != ONS_BASIC_SERVER_DHANSWER){ // don't have the keys yet + BYTE* pCryptBuffer = m_pfiReceiveBuffer->Detach(); + RC4Crypt(pCryptBuffer, pCryptBuffer, nCurrentBytesLen, m_pRC4ReceiveKey); + m_pfiReceiveBuffer->Attach(pCryptBuffer, 512); + } + m_pfiReceiveBuffer->SeekToBegin(); + + switch (m_NegotiatingState){ + case ONS_NONE: // would be a bug + ASSERT( false ); + return 0; + case ONS_BASIC_CLIENTA_RANDOMPART:{ + ASSERT( m_pRC4ReceiveKey == NULL ); + + uchar achKeyData[21]; + md4cpy(achKeyData, thePrefs.GetUserHash()); + achKeyData[16] = MAGICVALUE_REQUESTER; + m_pfiReceiveBuffer->Read(achKeyData + 17, 4); // random key part sent from remote client + + MD5Sum md5(achKeyData, sizeof(achKeyData)); + m_pRC4ReceiveKey = RC4CreateKey(md5.GetRawHash(), 16, NULL); + achKeyData[16] = MAGICVALUE_SERVER; + md5.Calculate(achKeyData, sizeof(achKeyData)); + m_pRC4SendKey = RC4CreateKey(md5.GetRawHash(), 16, NULL); + + m_NegotiatingState = ONS_BASIC_CLIENTA_MAGICVALUE; + m_nReceiveBytesWanted = 4; + break; + } + case ONS_BASIC_CLIENTA_MAGICVALUE:{ + uint32 dwValue = m_pfiReceiveBuffer->ReadUInt32(); + if (dwValue == MAGICVALUE_SYNC){ + // yup, the one or the other way it worked, this is an encrypted stream + //DEBUG_ONLY( DebugLog(_T("Received proper magic value, clientIP: %s"), DbgGetIPString()) ); + // set the receiver key + m_NegotiatingState = ONS_BASIC_CLIENTA_METHODTAGSPADLEN; + m_nReceiveBytesWanted = 3; + } + else{ + DebugLogError(_T("CEncryptedStreamSocket: Received wrong magic value from clientIP %s on a supposly encrytped stream / Wrong Header"), DbgGetIPString()); + OnError(ERR_ENCRYPTION); + return (-1); + } + break; + } + case ONS_BASIC_CLIENTA_METHODTAGSPADLEN: + m_dbgbyEncryptionSupported = m_pfiReceiveBuffer->ReadUInt8(); + m_dbgbyEncryptionRequested = m_pfiReceiveBuffer->ReadUInt8(); + if (m_dbgbyEncryptionRequested != ENM_OBFUSCATION) + AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Client %s preffered unsupported encryption method (%i)"), DbgGetIPString(), m_dbgbyEncryptionRequested); + m_nReceiveBytesWanted = m_pfiReceiveBuffer->ReadUInt8(); + m_NegotiatingState = ONS_BASIC_CLIENTA_PADDING; + //if (m_nReceiveBytesWanted > 16) + // AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Client %s sent more than 16 (%i) padding bytes"), DbgGetIPString(), m_nReceiveBytesWanted); + if (m_nReceiveBytesWanted > 0) + break; + case ONS_BASIC_CLIENTA_PADDING:{ + // ignore the random bytes, send the response, set status complete + CSafeMemFile fileResponse(26); + fileResponse.WriteUInt32(MAGICVALUE_SYNC); + const uint8 bySelectedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version, so no need to look which the other client preferred + fileResponse.WriteUInt8(bySelectedEncryptionMethod); + + SOCKADDR_IN sockAddr = {0}; + int nSockAddrLen = sizeof(sockAddr); + GetPeerName((SOCKADDR*)&sockAddr, &nSockAddrLen); + const uint8 byPaddingLen = theApp.serverconnect->AwaitingTestFromIP(sockAddr.sin_addr.S_un.S_addr) ? 16 : (thePrefs.GetCryptTCPPaddingLength() + 1); + uint8 byPadding = (uint8)(cryptRandomGen.GenerateByte() % byPaddingLen); + + fileResponse.WriteUInt8(byPadding); + for (int i = 0; i < byPadding; i++) + fileResponse.WriteUInt8((uint8)rand()); + SendNegotiatingData(fileResponse.GetBuffer(), (uint32)fileResponse.GetLength()); + m_NegotiatingState = ONS_COMPLETE; + m_StreamCryptState = ECS_ENCRYPTING; + //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (incoming)"), DbgGetIPString()) ); + break; + } + case ONS_BASIC_CLIENTB_MAGICVALUE:{ + if (m_pfiReceiveBuffer->ReadUInt32() != MAGICVALUE_SYNC){ + DebugLogError(_T("CEncryptedStreamSocket: EncryptedstreamSyncError: Client sent wrong Magic Value as answer, cannot complete handshake (%s)"), DbgGetIPString()); + OnError(ERR_ENCRYPTION); + return (-1); + } + m_NegotiatingState = ONS_BASIC_CLIENTB_METHODTAGSPADLEN; + m_nReceiveBytesWanted = 2; + break; + } + case ONS_BASIC_CLIENTB_METHODTAGSPADLEN:{ + m_dbgbyEncryptionMethodSet = m_pfiReceiveBuffer->ReadUInt8(); + if (m_dbgbyEncryptionMethodSet != ENM_OBFUSCATION){ + DebugLogError( _T("CEncryptedStreamSocket: Client %s set unsupported encryption method (%i), handshake failed"), DbgGetIPString(), m_dbgbyEncryptionMethodSet); + OnError(ERR_ENCRYPTION); + return (-1); + } + m_nReceiveBytesWanted = m_pfiReceiveBuffer->ReadUInt8(); + m_NegotiatingState = ONS_BASIC_CLIENTB_PADDING; + if (m_nReceiveBytesWanted > 0) + break; + } + case ONS_BASIC_CLIENTB_PADDING: + // ignore the random bytes, the handshake is complete + m_NegotiatingState = ONS_COMPLETE; + m_StreamCryptState = ECS_ENCRYPTING; + //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (outgoing)"), DbgGetIPString()) ); + break; + case ONS_BASIC_SERVER_DHANSWER:{ + ASSERT( !m_cryptDHA.IsZero() ); + uchar aBuffer[PRIMESIZE_BYTES + 1]; + m_pfiReceiveBuffer->Read(aBuffer, PRIMESIZE_BYTES); + CryptoPP::Integer cryptDHAnswer((byte*)aBuffer, PRIMESIZE_BYTES); + CryptoPP::Integer cryptDHPrime((byte*)dh768_p, PRIMESIZE_BYTES); // our fixed prime + CryptoPP::Integer cryptResult = CryptoPP::a_exp_b_mod_c(cryptDHAnswer, m_cryptDHA, cryptDHPrime); + + m_cryptDHA = 0; + DEBUG_ONLY( ZeroMemory(aBuffer, sizeof(aBuffer)) ); + ASSERT( cryptResult.MinEncodedSize() <= PRIMESIZE_BYTES ); + + // create the keys + cryptResult.Encode(aBuffer, PRIMESIZE_BYTES); + aBuffer[PRIMESIZE_BYTES] = MAGICVALUE_REQUESTER; + MD5Sum md5(aBuffer, sizeof(aBuffer)); + m_pRC4SendKey = RC4CreateKey(md5.GetRawHash(), 16, NULL); + aBuffer[PRIMESIZE_BYTES] = MAGICVALUE_SERVER; + md5.Calculate(aBuffer, sizeof(aBuffer)); + m_pRC4ReceiveKey = RC4CreateKey(md5.GetRawHash(), 16, NULL); + + m_NegotiatingState = ONS_BASIC_SERVER_MAGICVALUE; + m_nReceiveBytesWanted = 4; + break; + } + case ONS_BASIC_SERVER_MAGICVALUE:{ + uint32 dwValue = m_pfiReceiveBuffer->ReadUInt32(); + if (dwValue == MAGICVALUE_SYNC){ + // yup, the one or the other way it worked, this is an encrypted stream + DebugLog(_T("Received proper magic value after DH-Agreement from Serverconnection IP: %s"), DbgGetIPString()); + // set the receiver key + m_NegotiatingState = ONS_BASIC_SERVER_METHODTAGSPADLEN; + m_nReceiveBytesWanted = 3; + } + else{ + DebugLogError(_T("CEncryptedStreamSocket: Received wrong magic value after DH-Agreement from Serverconnection"), DbgGetIPString()); + OnError(ERR_ENCRYPTION); + return (-1); + } + break; + } + case ONS_BASIC_SERVER_METHODTAGSPADLEN: + m_dbgbyEncryptionSupported = m_pfiReceiveBuffer->ReadUInt8(); + m_dbgbyEncryptionRequested = m_pfiReceiveBuffer->ReadUInt8(); + if (m_dbgbyEncryptionRequested != ENM_OBFUSCATION) + AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Server %s preffered unsupported encryption method (%i)"), DbgGetIPString(), m_dbgbyEncryptionRequested); + m_nReceiveBytesWanted = m_pfiReceiveBuffer->ReadUInt8(); + m_NegotiatingState = ONS_BASIC_SERVER_PADDING; + if (m_nReceiveBytesWanted > 16) + AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Server %s sent more than 16 (%i) padding bytes"), DbgGetIPString(), m_nReceiveBytesWanted); + if (m_nReceiveBytesWanted > 0) + break; + case ONS_BASIC_SERVER_PADDING:{ + // ignore the random bytes (they are decrypted already), send the response, set status complete + CSafeMemFile fileResponse(26); + fileResponse.WriteUInt32(MAGICVALUE_SYNC); + const uint8 bySelectedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version, so no need to look which the other client preferred + fileResponse.WriteUInt8(bySelectedEncryptionMethod); + uint8 byPadding = (uint8)(cryptRandomGen.GenerateByte() % 16); + fileResponse.WriteUInt8(byPadding); + for (int i = 0; i < byPadding; i++) + fileResponse.WriteUInt8((uint8)rand()); + + m_NegotiatingState = ONS_BASIC_SERVER_DELAYEDSENDING; + SendNegotiatingData(fileResponse.GetBuffer(), (uint32)fileResponse.GetLength(), 0, true); // don't actually send it right now, store it in our sendbuffer + m_StreamCryptState = ECS_ENCRYPTING; + DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished DH Obufscation handshake with Server %s"), DbgGetIPString()) ); + break; + } + default: + ASSERT( false ); + } + m_pfiReceiveBuffer->SeekToBegin(); + } + if (m_pfiReceiveBuffer != NULL) + free(m_pfiReceiveBuffer->Detach()); + delete m_pfiReceiveBuffer; + m_pfiReceiveBuffer = NULL; + return nRead; + } + catch(CFileException* error){ + // can only be caused by a bug in negationhandling, not by the datastream + error->Delete(); + ASSERT( false ); + OnError(ERR_ENCRYPTION); + if (m_pfiReceiveBuffer != NULL) + free(m_pfiReceiveBuffer->Detach()); + delete m_pfiReceiveBuffer; + m_pfiReceiveBuffer = NULL; + return (-1); + } + +} + +int CEncryptedStreamSocket::SendNegotiatingData(const void* lpBuf, uint32 nBufLen, uint32 nStartCryptFromByte, bool bDelaySend){ + ASSERT( m_StreamCryptState == ECS_NEGOTIATING || m_StreamCryptState == ECS_ENCRYPTING ); + ASSERT( nStartCryptFromByte <= nBufLen ); + ASSERT( m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING || !bDelaySend ); + + BYTE* pBuffer = NULL; + bool bProcess = false; + if (lpBuf != NULL){ + pBuffer = (BYTE*)malloc(nBufLen); + if (pBuffer == NULL) + AfxThrowMemoryException(); + if (nStartCryptFromByte > 0) + memcpy(pBuffer, lpBuf, nStartCryptFromByte); + if (nBufLen - nStartCryptFromByte > 0) + RC4Crypt((uchar*)lpBuf + nStartCryptFromByte, pBuffer + nStartCryptFromByte, nBufLen - nStartCryptFromByte, m_pRC4SendKey); + if (m_pfiSendBuffer != NULL){ + // we already have data pending. Attach it and try to send + if (m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING) + m_NegotiatingState = ONS_COMPLETE; + else + ASSERT( false ); + m_pfiSendBuffer->SeekToEnd(); + m_pfiSendBuffer->Write(pBuffer, nBufLen); + free(pBuffer); + pBuffer = NULL; + nStartCryptFromByte = 0; + bProcess = true; // we want to try to send it right now + } + } + if (lpBuf == NULL || bProcess){ + // this call is for processing pending data + if (m_pfiSendBuffer == NULL || nStartCryptFromByte != 0){ + ASSERT( false ); + return 0; // or not + } + nBufLen = (uint32)m_pfiSendBuffer->GetLength(); + pBuffer = m_pfiSendBuffer->Detach(); + delete m_pfiSendBuffer; + m_pfiSendBuffer = NULL; + } + ASSERT( m_pfiSendBuffer == NULL ); + uint32 result = 0; + if (!bDelaySend) + result = CAsyncSocketEx::Send(pBuffer, nBufLen); + if (result == (uint32)SOCKET_ERROR || bDelaySend){ + m_pfiSendBuffer = new CSafeMemFile(128); + m_pfiSendBuffer->Write(pBuffer, nBufLen); + free(pBuffer); + return result; + } + else { + if (result < nBufLen){ + m_pfiSendBuffer = new CSafeMemFile(128); + m_pfiSendBuffer->Write(pBuffer + result, nBufLen - result); + } + free(pBuffer); + return result; + } +} + +CString CEncryptedStreamSocket::DbgGetIPString(){ + SOCKADDR_IN sockAddr = {0}; + int nSockAddrLen = sizeof(sockAddr); + GetPeerName((SOCKADDR*)&sockAddr, &nSockAddrLen); + return ipstr(sockAddr.sin_addr.S_un.S_addr); +} + +uint8 CEncryptedStreamSocket::GetSemiRandomNotProtocolMarker() const{ + uint8 bySemiRandomNotProtocolMarker = 0; + int i; + for (i = 0; i < 128; i++){ + bySemiRandomNotProtocolMarker = cryptRandomGen.GenerateByte(); + bool bOk = false; + switch (bySemiRandomNotProtocolMarker){ // not allowed values + case OP_EDONKEYPROT: + case OP_PACKEDPROT: + case OP_EMULEPROT: + break; + default: + bOk = true; + } + if (bOk) + break; + } + if (i >= 128){ + // either we have _real_ bad luck or the randomgenerator is a bit messed up + ASSERT( false ); + bySemiRandomNotProtocolMarker = 0x01; + } + return bySemiRandomNotProtocolMarker; +} diff --git a/EncryptedStreamSocket.h b/EncryptedStreamSocket.h new file mode 100644 index 00000000..c4ef0f30 --- /dev/null +++ b/EncryptedStreamSocket.h @@ -0,0 +1,128 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +/* This class supports obfuscation and encryption for a eMule tcp connection. + Right now only basic obfuscation is supported, but this can be expanded, as their is a + dedicated handshake to negotiate the encryption method used. + + Please note, even if obfuscation uses encryption methods, it does not fulfill cryptographic standards since it + doesn't use secret (and for rc4 important: unique) keys +*/ + +#pragma once +#include "AsyncSocketEx.h" + +// cryptoPP used for DH integer calculations +#pragma warning(disable:4244) // conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4100) // unreferenced formal parameter +#include +#pragma warning(default:4100) // unreferenced formal parameter +#pragma warning(default:4244) // conversion from 'type1' to 'type2', possible loss of data + + + +#define ERR_WRONGHEADER 0x01 +#define ERR_TOOBIG 0x02 +#define ERR_ENCRYPTION 0x03 +#define ERR_ENCRYPTION_NOTALLOWED 0x04 + +enum EStreamCryptState { + ECS_NONE = 0, // Disabled or not available + ECS_UNKNOWN, // Incoming connection, will test the first incoming data for encrypted protocol + ECS_PENDING, // Outgoing connection, will start sending encryption protocol + ECS_PENDING_SERVER, // Outgoing serverconnection, will start sending encryption protocol + ECS_NEGOTIATING, // Encryption supported, handshake still uncompleted + ECS_ENCRYPTING // Encryption enabled +}; + +enum ENegotiatingState { + ONS_NONE, + + ONS_BASIC_CLIENTA_RANDOMPART, + ONS_BASIC_CLIENTA_MAGICVALUE, + ONS_BASIC_CLIENTA_METHODTAGSPADLEN, + ONS_BASIC_CLIENTA_PADDING, + + ONS_BASIC_CLIENTB_MAGICVALUE, + ONS_BASIC_CLIENTB_METHODTAGSPADLEN, + ONS_BASIC_CLIENTB_PADDING, + + ONS_BASIC_SERVER_DHANSWER, + ONS_BASIC_SERVER_MAGICVALUE, + ONS_BASIC_SERVER_METHODTAGSPADLEN, + ONS_BASIC_SERVER_PADDING, + ONS_BASIC_SERVER_DELAYEDSENDING, + + ONS_COMPLETE +}; + +enum EEncryptionMethods { + ENM_OBFUSCATION = 0x00 +}; + +class CSafeMemFile; +struct RC4_Key_Struct; + +class CEncryptedStreamSocket : public CAsyncSocketEx +{ + DECLARE_DYNAMIC(CEncryptedStreamSocket) +public: + CEncryptedStreamSocket(); + virtual ~CEncryptedStreamSocket(); + + void SetConnectionEncryption(bool bEnabled, const uchar* pTargetClientHash, bool bServerConnection); + uint32 GetRealReceivedBytes() const { return m_nObfuscationBytesReceived; } // indicates how many bytes were received including obfusication so that the parent knows if the receive limit was reached + bool IsObfusicating() const { return m_StreamCryptState == ECS_ENCRYPTING && m_EncryptionMethod == ENM_OBFUSCATION; } + + bool IsServerCryptEnabledConnection() const { return m_bServerCrypt; } + + uint8 m_dbgbyEncryptionSupported; + uint8 m_dbgbyEncryptionRequested; + uint8 m_dbgbyEncryptionMethodSet; + +protected: + int Send(const void* lpBuf, int nBufLen, int nFlags = 0); + int SendOv(CArray& raBuffer, DWORD& dwBytesSent, LPWSAOVERLAPPED lpOverlapped); + int Receive(void* lpBuf, int nBufLen, int nFlags = 0); + virtual void OnError(int nErrorCode) = 0; + virtual void OnSend(int nErrorCode); + CString DbgGetIPString(); + void CryptPrepareSendData(uchar* pBuffer, uint32 nLen); + bool IsEncryptionLayerReady(); + uint8 GetSemiRandomNotProtocolMarker() const; + + uint32 m_nObfuscationBytesReceived; + EStreamCryptState m_StreamCryptState; + EEncryptionMethods m_EncryptionMethod; + bool m_bFullReceive; + bool m_bServerCrypt; + +private: + int Negotiate(const uchar* pBuffer, uint32 nLen); + void StartNegotiation(bool bOutgoing); + int SendNegotiatingData(const void* lpBuf, uint32 nBufLen, uint32 nStartCryptFromByte = 0, bool bDelaySend = false); + + RC4_Key_Struct* m_pRC4SendKey; + RC4_Key_Struct* m_pRC4ReceiveKey; + ENegotiatingState m_NegotiatingState; + CSafeMemFile* m_pfiReceiveBuffer; + uint32 m_nReceiveBytesWanted; + CSafeMemFile* m_pfiSendBuffer; + uint32 m_nRandomKeyPart; + CryptoPP::Integer m_cryptDHA; + +}; \ No newline at end of file diff --git a/Exceptions.h b/Exceptions.h new file mode 100644 index 00000000..5d4145a8 --- /dev/null +++ b/Exceptions.h @@ -0,0 +1,70 @@ +#pragma once + +#ifdef _DEBUG +#define CATCH_DFLT_ALL(fname) +#else +#define CATCH_DFLT_ALL(fname) \ + catch(...){ \ + if (thePrefs.GetVerbose()) \ + DebugLogError(LOG_STATUSBAR, _T("Unknown exception in ") fname); \ + ASSERT(0); \ + } +#endif + +// This type of "last chance" exception handling is to be used at least in several callback functions to avoid memory leaks. +// It is *not* thought as a proper handling of exceptions in general! +// -> Use explicit exception handlers where needed! + +#define CATCH_MFC_EXCEPTION(fname) \ + catch(CException* e){ \ + TCHAR szError[1024]; \ + e->GetErrorMessage(szError, _countof(szError)); \ + const CRuntimeClass* pRuntimeClass = e->GetRuntimeClass(); \ + LPCSTR pszClassName = (pRuntimeClass) ? pRuntimeClass->m_lpszClassName : NULL; \ + if (!pszClassName) \ + pszClassName = "CException"; \ + if (thePrefs.GetVerbose()) \ + DebugLogError(LOG_STATUSBAR, _T("Unknown %hs exception in ") fname _T(" - %s"), pszClassName, szError); \ + e->Delete(); \ + } + +#define CATCH_STR_EXCEPTION(fname) \ + catch(CString strError){ \ + if (thePrefs.GetVerbose()) \ + DebugLogError(LOG_STATUSBAR, _T("Unknown CString exception in ") fname _T(" - %s"), strError); \ + } + +#define CATCH_DFLT_EXCEPTIONS(fname) \ + CATCH_MFC_EXCEPTION(fname) \ + CATCH_STR_EXCEPTION(fname) + + +class CMsgBoxException : public CException +{ + DECLARE_DYNAMIC(CMsgBoxException) +public: + explicit CMsgBoxException(LPCTSTR pszMsg, UINT uType = MB_ICONWARNING, UINT uHelpID = 0) + { + m_strMsg = pszMsg; + m_uType = uType; + m_uHelpID = uHelpID; + } + + CString m_strMsg; + UINT m_uType; + UINT m_uHelpID; +}; + +class CClientException : public CException +{ + DECLARE_DYNAMIC(CClientException) +public: + CClientException(LPCTSTR pszMsg, bool bDelete) + { + m_strMsg = pszMsg; + m_bDelete = bDelete; + } + + CString m_strMsg; + bool m_bDelete; +}; diff --git a/FileDetailDialog.cpp b/FileDetailDialog.cpp new file mode 100644 index 00000000..bff3ea22 --- /dev/null +++ b/FileDetailDialog.cpp @@ -0,0 +1,253 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "FileDetailDialog.h" +#include "PartFile.h" +#include "HighColorTab.hpp" +#include "UserMsgs.h" +#include "DownloadListCtrl.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + + +////////////////////////////////////////////////////////////////////////////// +// Helper Functions for FileDetail and SharedFileDetailsSheet dialogs + +bool NeedArchiveInfoPage(const CSimpleArray* paItems) +{ + if (paItems->GetSize() == 1) + { + CShareableFile *pFile = STATIC_DOWNCAST(CShareableFile, (*paItems)[0]); + EFileType eFileType = GetFileTypeEx(pFile); + switch (eFileType) { + case ARCHIVE_ZIP: + case ARCHIVE_RAR: + case ARCHIVE_ACE: + case IMAGE_ISO: + return true; + } + } + return false; +} + +void UpdateFileDetailsPages(CListViewPropertySheet *pSheet, + CResizablePage *pArchiveInfo, CResizablePage *pMediaInfo) +{ + if (pSheet->GetItems().GetSize() == 1) + { + bool bUpdateWindow = false; + CPropertyPage *pActivePage = pSheet->GetActivePage(); + bool bNeedArchiveInfoPage = NeedArchiveInfoPage(&pSheet->GetItems()); + if (bNeedArchiveInfoPage) + { + bool bFound = false; + for (int i = 0; !bFound && i < pSheet->GetPages().GetSize(); i++) { + if (pSheet->GetPages()[i] == pArchiveInfo) + bFound = true; + } + + int iMediaInfoPage = pSheet->GetPageIndex(pMediaInfo); + bool bMediaInfoPageWasActive = false; + if (iMediaInfoPage >= 0) { + if (pActivePage == pMediaInfo) + bMediaInfoPageWasActive = true; + if (!bUpdateWindow) { + pSheet->SetRedraw(FALSE); + bUpdateWindow = true; + } + pSheet->RemovePage(pMediaInfo); + } + + if (!bFound) { + ASSERT( iMediaInfoPage >= 0 ); + if (!bUpdateWindow) { + pSheet->SetRedraw(FALSE); + bUpdateWindow = true; + } + pSheet->InsertPage(iMediaInfoPage, pArchiveInfo); + if (bMediaInfoPageWasActive) + pSheet->SetActivePage(iMediaInfoPage); + } + } + else + { + bool bFound = false; + for (int i = 0; !bFound && i < pSheet->GetPages().GetSize(); i++) { + if (pSheet->GetPages()[i] == pMediaInfo) + bFound = true; + } + + int iArchiveInfoPage = pSheet->GetPageIndex(pArchiveInfo); + bool bArchiveInfoPageWasActive = false; + if (iArchiveInfoPage >= 0) { + if (pActivePage == pArchiveInfo) + bArchiveInfoPageWasActive = true; + if (!bUpdateWindow) { + pSheet->SetRedraw(FALSE); + bUpdateWindow = true; + } + pSheet->RemovePage(pArchiveInfo); + } + + if (!bFound) { + ASSERT( iArchiveInfoPage >= 0 ); + if (!bUpdateWindow) { + pSheet->SetRedraw(FALSE); + bUpdateWindow = true; + } + pSheet->InsertPage(iArchiveInfoPage, pMediaInfo); + if (bArchiveInfoPageWasActive) + pSheet->SetActivePage(pMediaInfo); + } + } + if (bUpdateWindow) { + pSheet->SetRedraw(TRUE); + pSheet->Invalidate(); + pSheet->UpdateWindow(); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// CFileDetailDialog + +LPCTSTR CFileDetailDialog::m_pPshStartPage; + +IMPLEMENT_DYNAMIC(CFileDetailDialog, CListViewWalkerPropertySheet) + +BEGIN_MESSAGE_MAP(CFileDetailDialog, CListViewWalkerPropertySheet) + ON_WM_DESTROY() + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) +END_MESSAGE_MAP() + +CFileDetailDialog::CFileDetailDialog(const CSimpleArray* paFiles, UINT uPshInvokePage, CListCtrlItemWalk* pListCtrl) + : CListViewWalkerPropertySheet(pListCtrl) +{ + m_uPshInvokePage = uPshInvokePage; + for (int i = 0; i < paFiles->GetSize(); i++) + m_aItems.Add((*paFiles)[i]); + m_psh.dwFlags &= ~PSH_HASHELP; + + m_wndInfo.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndInfo.m_psp.dwFlags |= PSP_USEICONID; + m_wndInfo.m_psp.pszIcon = _T("FILEINFO"); + m_wndInfo.SetFiles(&m_aItems); + AddPage(&m_wndInfo); + + if (m_aItems.GetSize() == 1) + { + m_wndName.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndName.m_psp.dwFlags |= PSP_USEICONID; + m_wndName.m_psp.pszIcon = _T("FILERENAME"); + m_wndName.SetFiles(&m_aItems); + AddPage(&m_wndName); + } + + m_wndComments.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndComments.m_psp.dwFlags |= PSP_USEICONID; + m_wndComments.m_psp.pszIcon = _T("FILECOMMENTS"); + m_wndComments.SetFiles(&m_aItems); + AddPage(&m_wndComments); + + m_wndArchiveInfo.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndArchiveInfo.m_psp.dwFlags |= PSP_USEICONID; + m_wndArchiveInfo.m_psp.pszIcon = _T("ARCHIVE_PREVIEW"); + m_wndArchiveInfo.SetFiles(&m_aItems); + + m_wndMediaInfo.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndMediaInfo.m_psp.dwFlags |= PSP_USEICONID; + m_wndMediaInfo.m_psp.pszIcon = _T("MEDIAINFO"); + m_wndMediaInfo.SetFiles(&m_aItems); + if (NeedArchiveInfoPage(&m_aItems)) + AddPage(&m_wndArchiveInfo); + else + AddPage(&m_wndMediaInfo); + + if (m_aItems.GetSize() == 1) + { + if (thePrefs.IsExtControlsEnabled()) { + m_wndMetaData.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndMetaData.m_psp.dwFlags |= PSP_USEICONID; + m_wndMetaData.m_psp.pszIcon = _T("METADATA"); + m_wndMetaData.SetFiles(&m_aItems); + AddPage(&m_wndMetaData); + } + } + + m_wndFileLink.m_psp.dwFlags &= ~PSP_HASHELP; + m_wndFileLink.m_psp.dwFlags |= PSP_USEICONID; + m_wndFileLink.m_psp.pszIcon = _T("ED2KLINK"); + m_wndFileLink.SetFiles(&m_aItems); + AddPage(&m_wndFileLink); + + LPCTSTR pPshStartPage = m_pPshStartPage; + if (m_uPshInvokePage != 0) + pPshStartPage = MAKEINTRESOURCE(m_uPshInvokePage); + for (int i = 0; i < m_pages.GetSize(); i++) + { + CPropertyPage* pPage = GetPage(i); + if (pPage->m_psp.pszTemplate == pPshStartPage) + { + m_psh.nStartPage = i; + break; + } + } +} + +CFileDetailDialog::~CFileDetailDialog() +{ +} + +void CFileDetailDialog::OnDestroy() +{ + if (m_uPshInvokePage == 0) + m_pPshStartPage = GetPage(GetActiveIndex())->m_psp.pszTemplate; + CListViewWalkerPropertySheet::OnDestroy(); +} + +BOOL CFileDetailDialog::OnInitDialog() +{ + EnableStackedTabs(FALSE); + BOOL bResult = CListViewWalkerPropertySheet::OnInitDialog(); + HighColorTab::UpdateImageList(*this); + InitWindowStyles(this); + EnableSaveRestore(_T("FileDetailDialog")); // call this after(!) OnInitDialog + UpdateTitle(); + + return bResult; +} + +LRESULT CFileDetailDialog::OnDataChanged(WPARAM, LPARAM) +{ + UpdateTitle(); + UpdateFileDetailsPages(this, &m_wndArchiveInfo, &m_wndMediaInfo); + return 1; +} + +void CFileDetailDialog::UpdateTitle() +{ + if (m_aItems.GetSize() == 1) + SetWindowText(GetResString(IDS_DETAILS) + _T(": ") + STATIC_DOWNCAST(CAbstractFile, m_aItems[0])->GetFileName()); + else + SetWindowText(GetResString(IDS_DETAILS)); +} diff --git a/FileDetailDialog.h b/FileDetailDialog.h new file mode 100644 index 00000000..87d00597 --- /dev/null +++ b/FileDetailDialog.h @@ -0,0 +1,56 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "ListViewWalkerPropertySheet.h" +#include "FileDetailDialogInfo.h" +#include "FileDetailDialogName.h" +#include "CommentDialogLst.h" +#include "FileInfoDialog.h" +#include "MetaDataDlg.h" +#include "ED2kLinkDlg.h" +#include "ArchivePreviewDlg.h" + +class CSearchFile; + +class CFileDetailDialog : public CListViewWalkerPropertySheet +{ + DECLARE_DYNAMIC(CFileDetailDialog) + +public: + CFileDetailDialog(const CSimpleArray* paFiles, UINT uInvokePage = 0, CListCtrlItemWalk* pListCtrl = NULL); + virtual ~CFileDetailDialog(); + +protected: + CFileDetailDialogInfo m_wndInfo; + CFileDetailDialogName m_wndName; + CCommentDialogLst m_wndComments; + CFileInfoDialog m_wndMediaInfo; + CMetaDataDlg m_wndMetaData; + CED2kLinkDlg m_wndFileLink; + CArchivePreviewDlg m_wndArchiveInfo; + + UINT m_uPshInvokePage; + static LPCTSTR m_pPshStartPage; + + void UpdateTitle(); + + virtual BOOL OnInitDialog(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnDestroy(); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); +}; diff --git a/FileDetailDialogInfo.cpp b/FileDetailDialogInfo.cpp new file mode 100644 index 00000000..30bbcd7c --- /dev/null +++ b/FileDetailDialogInfo.cpp @@ -0,0 +1,406 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "eMule.h" +#include "FileDetailDialogInfo.h" +#include "UserMsgs.h" +#include "OtherFunctions.h" +#include "PartFile.h" +#include "Preferences.h" +#include "shahashset.h" +#include "UpDownClient.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +/////////////////////////////////////////////////////////////////////////////// +// CFileDetailDialogInfo dialog + +LPCTSTR CFileDetailDialogInfo::sm_pszNotAvail = _T("-"); + +IMPLEMENT_DYNAMIC(CFileDetailDialogInfo, CResizablePage) + +BEGIN_MESSAGE_MAP(CFileDetailDialogInfo, CResizablePage) + ON_WM_TIMER() + ON_WM_DESTROY() + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) + ON_WM_CTLCOLOR() +END_MESSAGE_MAP() + +CFileDetailDialogInfo::CFileDetailDialogInfo() + : CResizablePage(CFileDetailDialogInfo::IDD, 0) +{ + m_paFiles = NULL; + m_bDataChanged = false; + m_strCaption = GetResString(IDS_FD_GENERAL); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; + m_timer = 0; + m_bShowFileTypeWarning = false; +} + +CFileDetailDialogInfo::~CFileDetailDialogInfo() +{ +} + +void CFileDetailDialogInfo::OnTimer(UINT /*nIDEvent*/) +{ + RefreshData(); +} + +void CFileDetailDialogInfo::DoDataExchange(CDataExchange* pDX) +{ + CResizablePage::DoDataExchange(pDX); +} + +BOOL CFileDetailDialogInfo::OnInitDialog() +{ + CResizablePage::OnInitDialog(); + InitWindowStyles(this); + + AddAnchor(IDC_FD_X0, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FD_X6, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FD_X8, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FD_X11, TOP_LEFT, TOP_RIGHT); + + AddAnchor(IDC_FNAME, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_METFILE, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FHASH, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FD_AICHHASH, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FSIZE, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_PARTCOUNT, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_HASHSET, TOP_LEFT, TOP_RIGHT); + + AddAnchor(IDC_SOURCECOUNT, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_DATARATE, TOP_LEFT, TOP_RIGHT); + + AddAnchor(IDC_FILECREATED, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_DL_ACTIVE_TIME, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_LASTSEENCOMPL, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_LASTRECEIVED, TOP_LEFT, TOP_RIGHT); + + Localize(); + + // no need to explicitly call 'RefreshData' here, 'OnSetActive' will be called right after 'OnInitDialog' + ; + + // start time for calling 'RefreshData' + VERIFY( (m_timer = SetTimer(301, 5000, 0)) != NULL ); + + return TRUE; +} + +BOOL CFileDetailDialogInfo::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + if (m_bDataChanged) + { + RefreshData(); + m_bDataChanged = false; + } + return TRUE; +} + +LRESULT CFileDetailDialogInfo::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +void CFileDetailDialogInfo::RefreshData() +{ + CString str; + + if (m_paFiles->GetSize() == 1) + { + CPartFile* file = STATIC_DOWNCAST(CPartFile, (*m_paFiles)[0]); + + // if file is completed, we output the 'file path' and not the 'part.met file path' + if (file->GetStatus(true) == PS_COMPLETE) + GetDlgItem(IDC_FD_X2)->SetWindowText(GetResString(IDS_DL_FILENAME)); + + SetDlgItemText(IDC_FNAME, file->GetFileName()); + SetDlgItemText(IDC_METFILE, file->GetFullName()); + SetDlgItemText(IDC_FHASH, md4str(file->GetFileHash())); + + if (file->GetTransferringSrcCount() > 0) + str.Format(GetResString(IDS_PARTINFOS2), file->GetTransferringSrcCount()); + else + str = file->getPartfileStatus(); + SetDlgItemText(IDC_PFSTATUS, str); + + str.Format(_T("%u; %s: %u (%.1f%%)"), file->GetPartCount(), GetResString(IDS_AVAILABLE) , file->GetAvailablePartCount(), (float)((file->GetAvailablePartCount()*100)/file->GetPartCount())); + SetDlgItemText(IDC_PARTCOUNT, str); + + // date created + if (file->GetCrFileDate() != 0) { + str.Format(_T("%s ") + GetResString(IDS_TIMEBEFORE), + file->GetCrCFileDate().Format(thePrefs.GetDateTimeFormat()), + CastSecondsToLngHM(time(NULL) - file->GetCrFileDate())); + } + else + str = GetResString(IDS_UNKNOWN); + SetDlgItemText(IDC_FILECREATED, str); + + // active download time + uint32 nDlActiveTime = file->GetDlActiveTime(); + if (nDlActiveTime) + str = CastSecondsToLngHM(nDlActiveTime); + else + str = GetResString(IDS_UNKNOWN); + SetDlgItemText(IDC_DL_ACTIVE_TIME, str); + + // last seen complete + struct tm tmTemp; + struct tm* ptimLastSeenComplete = file->lastseencomplete.GetLocalTm(&tmTemp); + if (file->lastseencomplete == NULL || ptimLastSeenComplete == NULL) + str.Format(GetResString(IDS_NEVER)); + else { + str.Format(_T("%s ") + GetResString(IDS_TIMEBEFORE), + file->lastseencomplete.Format(thePrefs.GetDateTimeFormat()), + CastSecondsToLngHM(time(NULL) - safe_mktime(ptimLastSeenComplete))); + } + SetDlgItemText(IDC_LASTSEENCOMPL, str); + + // last receive + if (file->GetFileDate() != 0 && file->GetRealFileSize() > (uint64)0) + { + // 'Last Modified' sometimes is up to 2 seconds greater than the current time ??? + // If it's related to the FAT32 seconds time resolution the max. failure should still be only 1 sec. + // Happens at least on FAT32 with very high download speed. + uint32 tLastModified = file->GetFileDate(); + uint32 tNow = time(NULL); + uint32 tAgo; + if (tNow >= tLastModified) + tAgo = tNow - tLastModified; + else{ + TRACE("tNow = %s\n", CTime(tNow).Format("%X")); + TRACE("tLMd = %s, +%u\n", CTime(tLastModified).Format("%X"), tLastModified - tNow); + TRACE("\n"); + tAgo = 0; + } + str.Format(_T("%s ") + GetResString(IDS_TIMEBEFORE), + file->GetCFileDate().Format(thePrefs.GetDateTimeFormat()), + CastSecondsToLngHM(tAgo)); + } + else + str = GetResString(IDS_NEVER); + SetDlgItemText(IDC_LASTRECEIVED, str); + + // AICH Hash + switch (file->GetAICHRecoveryHashSet()->GetStatus()) { + case AICH_TRUSTED: + case AICH_VERIFIED: + case AICH_HASHSETCOMPLETE: + if (file->GetAICHRecoveryHashSet()->HasValidMasterHash()) { + SetDlgItemText(IDC_FD_AICHHASH, file->GetAICHRecoveryHashSet()->GetMasterHash().GetString()); + break; + } + default: + SetDlgItemText(IDC_FD_AICHHASH, GetResString(IDS_UNKNOWN)); + } + + // file type + CString ext; + bool showwarning = false; + int pos = file->GetFileName().ReverseFind(_T('.')); + if (file->GetFileName().ReverseFind(_T('\\')) < pos) { + ext = file->GetFileName().Mid(pos + 1); + ext.MakeUpper(); + } + + EFileType bycontent = GetFileTypeEx((CKnownFile *)file, false, true); + if (bycontent != FILETYPE_UNKNOWN) { + str = GetFileTypeName(bycontent) + _T(" ("); + str.Append(GetResString(IDS_VERIFIED) + _T(')')); + + int extLevel = IsExtensionTypeOf(bycontent, ext); + if (extLevel == -1) { + showwarning = true; + str.Append(_T(" - ")); + str.Append(GetResString(IDS_INVALIDFILEEXT) + _T(": ")); + str.Append(ext); + } + else if (extLevel == 0) { + str.Append(_T(" - ")); + str.Append(GetResString(IDS_UNKNOWNFILEEXT) + _T(": ")); + str.Append(ext); + } + } + else { + // not verified + if (pos != -1) { + str =file->GetFileName().Mid(pos + 1); + str.MakeUpper(); + str.Append(_T(" (") ); + str.Append( GetResString(IDS_UNVERIFIED) + _T(')')); + } + else + str = GetResString(IDS_UNKNOWN); + } + m_bShowFileTypeWarning = showwarning; + SetDlgItemText(IDC_FD_X11,str); + } + else + { + SetDlgItemText(IDC_FNAME, sm_pszNotAvail); + SetDlgItemText(IDC_METFILE, sm_pszNotAvail); + SetDlgItemText(IDC_FHASH, sm_pszNotAvail); + + SetDlgItemText(IDC_PFSTATUS, sm_pszNotAvail); + SetDlgItemText(IDC_PARTCOUNT, sm_pszNotAvail); + SetDlgItemText(IDC_FD_X11, sm_pszNotAvail); + + SetDlgItemText(IDC_FILECREATED, sm_pszNotAvail); + SetDlgItemText(IDC_DL_ACTIVE_TIME, sm_pszNotAvail); + SetDlgItemText(IDC_LASTSEENCOMPL, sm_pszNotAvail); + SetDlgItemText(IDC_LASTRECEIVED, sm_pszNotAvail); + SetDlgItemText(IDC_FD_AICHHASH, sm_pszNotAvail); + } + + uint64 uFileSize = 0; + uint64 uRealFileSize = 0; + uint64 uTransferred = 0; + uint64 uCorrupted = 0; + uint32 uRecoveredParts = 0; + uint64 uCompression = 0; + uint64 uCompleted = 0; + int iMD4HashsetAvailable = 0; + int iAICHHashsetAvailable = 0; + uint32 uDataRate = 0; + UINT uSources = 0; + UINT uValidSources = 0; + UINT uNNPSources = 0; + UINT uA4AFSources = 0; + for (int i = 0; i < m_paFiles->GetSize(); i++) + { + CPartFile* file = STATIC_DOWNCAST(CPartFile, (*m_paFiles)[i]); + + uFileSize += (uint64)file->GetFileSize(); + uRealFileSize += (uint64)file->GetRealFileSize(); + uTransferred += (uint64)file->GetTransferred(); + uCorrupted += file->GetCorruptionLoss(); + uRecoveredParts += file->GetRecoveredPartsByICH(); + uCompression += file->GetCompressionGain(); + uDataRate += file->GetDatarate(); + uCompleted += (uint64)file->GetCompletedSize(); + iMD4HashsetAvailable += (file->GetFileIdentifier().HasExpectedMD4HashCount()) ? 1 : 0; + iAICHHashsetAvailable += (file->GetFileIdentifier().HasExpectedAICHHashCount()) ? 1 : 0; + + if (file->IsPartFile()) + { + uSources += file->GetSourceCount(); + uValidSources += file->GetValidSourcesCount(); + uNNPSources += file->GetSrcStatisticsValue(DS_NONEEDEDPARTS); + uA4AFSources += file->GetSrcA4AFCount(); + } + } + + str.Format(_T("%s (%s %s); %s %s"), CastItoXBytes(uFileSize, false, false), GetFormatedUInt64(uFileSize), GetResString(IDS_BYTES), GetResString(IDS_ONDISK), CastItoXBytes(uRealFileSize, false, false)); + SetDlgItemText(IDC_FSIZE, str); + + if (m_paFiles->GetSize() == 1) + { + if (iAICHHashsetAvailable == 0 && iMD4HashsetAvailable == 0) + SetDlgItemText(IDC_HASHSET, GetResString(IDS_NO)); + else if (iAICHHashsetAvailable == 1 && iMD4HashsetAvailable == 1) + SetDlgItemText(IDC_HASHSET, GetResString(IDS_YES) + _T(" (eD2K + AICH)")); + else if (iAICHHashsetAvailable == 1) + SetDlgItemText(IDC_HASHSET, GetResString(IDS_YES) + _T(" (AICH)")); + else if (iMD4HashsetAvailable == 1) + SetDlgItemText(IDC_HASHSET, GetResString(IDS_YES) + _T(" (eD2K)")); + } + else + { + if (iAICHHashsetAvailable == 0 && iMD4HashsetAvailable == 0) + SetDlgItemText(IDC_HASHSET, GetResString(IDS_NO)); + else if (iMD4HashsetAvailable == m_paFiles->GetSize() && iAICHHashsetAvailable == m_paFiles->GetSize()) + SetDlgItemText(IDC_HASHSET, GetResString(IDS_YES) + + _T(" (eD2K + AICH)")); + else + SetDlgItemText(IDC_HASHSET, _T("")); + } + + str.Format(GetResString(IDS_SOURCESINFO), uSources, uValidSources, uNNPSources, uA4AFSources); + SetDlgItemText(IDC_SOURCECOUNT, str); + + SetDlgItemText(IDC_DATARATE, CastItoXBytes(uDataRate, false, true)); + + SetDlgItemText(IDC_TRANSFERRED, CastItoXBytes(uTransferred, false, false)); + + str.Format(_T("%s (%.1f%%)"), CastItoXBytes(uCompleted, false, false), uFileSize!=0 ? (uCompleted * 100.0 / uFileSize) : 0.0); + SetDlgItemText(IDC_COMPLSIZE, str); + + str.Format(_T("%s (%.1f%%)"), CastItoXBytes(uCorrupted, false, false), uTransferred!=0 ? (uCorrupted * 100.0 / uTransferred) : 0.0); + SetDlgItemText(IDC_CORRUPTED, str); + + str.Format(_T("%s (%.1f%%)"), CastItoXBytes(uFileSize - uCompleted, false, false), uFileSize!=0 ? ((uFileSize - uCompleted) * 100.0 / uFileSize) : 0.0); + SetDlgItemText(IDC_REMAINING, str); + + str.Format(_T("%u %s"), uRecoveredParts, GetResString(IDS_FD_PARTS)); + SetDlgItemText(IDC_RECOVERED, str); + + str.Format(_T("%s (%.1f%%)"), CastItoXBytes(uCompression, false, false), uTransferred!=0 ? (uCompression * 100.0 / uTransferred) : 0.0); + SetDlgItemText(IDC_COMPRESSION, str); +} + +void CFileDetailDialogInfo::OnDestroy() +{ + if (m_timer){ + KillTimer(m_timer); + m_timer = 0; + } +} + +void CFileDetailDialogInfo::Localize() +{ + GetDlgItem(IDC_FD_X0)->SetWindowText(GetResString(IDS_FD_GENERAL)); + GetDlgItem(IDC_FD_X1)->SetWindowText(GetResString(IDS_SW_NAME)+_T(':')); + GetDlgItem(IDC_FD_X2)->SetWindowText(GetResString(IDS_FD_MET)); + GetDlgItem(IDC_FD_X3)->SetWindowText(GetResString(IDS_FD_HASH)); + GetDlgItem(IDC_FD_X4)->SetWindowText(GetResString(IDS_DL_SIZE)+_T(':')); + GetDlgItem(IDC_FD_X9)->SetWindowText(GetResString(IDS_FD_PARTS)+_T(':')); + GetDlgItem(IDC_FD_X5)->SetWindowText(GetResString(IDS_STATUS)+_T(':')); + GetDlgItem(IDC_FD_X6)->SetWindowText(GetResString(IDS_FD_TRANSFER)); + GetDlgItem(IDC_FD_X7)->SetWindowText(GetResString(IDS_DL_SOURCES)+_T(':')); + GetDlgItem(IDC_FD_X14)->SetWindowText(GetResString(IDS_FD_TRANS)); + GetDlgItem(IDC_FD_X12)->SetWindowText(GetResString(IDS_FD_COMPSIZE)); + GetDlgItem(IDC_FD_X13)->SetWindowText(GetResString(IDS_FD_DATARATE)); + GetDlgItem(IDC_FD_X15)->SetWindowText(GetResString(IDS_LASTSEENCOMPL)); + GetDlgItem(IDC_FD_LASTCHANGE)->SetWindowText(GetResString(IDS_FD_LASTCHANGE)); + GetDlgItem(IDC_FD_X8)->SetWindowText(GetResString(IDS_FD_TIMEDATE)); + GetDlgItem(IDC_FD_X16)->SetWindowText(GetResString(IDS_FD_DOWNLOADSTARTED)); + GetDlgItem(IDC_DL_ACTIVE_TIME_LBL)->SetWindowText(GetResString(IDS_DL_ACTIVE_TIME)+_T(':')); + GetDlgItem(IDC_HSAV)->SetWindowText(GetResString(IDS_HSAV)+_T(':')); + GetDlgItem(IDC_FD_CORR)->SetWindowText(GetResString(IDS_FD_CORR)+_T(':')); + GetDlgItem(IDC_FD_RECOV)->SetWindowText(GetResString(IDS_FD_RECOV)+_T(':')); + GetDlgItem(IDC_FD_COMPR)->SetWindowText(GetResString(IDS_FD_COMPR)+_T(':')); + GetDlgItem(IDC_FD_XAICH)->SetWindowText(GetResString(IDS_AICHHASH)+_T(':')); + SetDlgItemText(IDC_REMAINING_TEXT, GetResString(IDS_DL_REMAINS)+_T(':')); + SetDlgItemText(IDC_FD_X10, GetResString(IDS_TYPE)+_T(':') ); +} + +HBRUSH CFileDetailDialogInfo::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) +{ + HBRUSH hbr = CResizablePage::OnCtlColor(pDC, pWnd, nCtlColor); + if (m_bShowFileTypeWarning && pWnd->GetDlgCtrlID() == IDC_FD_X11) + pDC->SetTextColor(RGB(255,0,0)); + return hbr; +} diff --git a/FileDetailDialogInfo.h b/FileDetailDialogInfo.h new file mode 100644 index 00000000..60673eaa --- /dev/null +++ b/FileDetailDialogInfo.h @@ -0,0 +1,52 @@ +//this file is part of eMule +// +//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. +#pragma once +#include "ResizableLib/ResizablePage.h" + +class CFileDetailDialogInfo : public CResizablePage +{ + DECLARE_DYNAMIC(CFileDetailDialogInfo) + +public: + CFileDetailDialogInfo(); // standard constructor + virtual ~CFileDetailDialogInfo(); + + void SetFiles(const CSimpleArray* paFiles) { m_paFiles = paFiles; m_bDataChanged = true; } + + // Dialog Data + enum { IDD = IDD_FILEDETAILS_INFO }; + +protected: + CString m_strCaption; + const CSimpleArray* m_paFiles; + bool m_bDataChanged; + uint32 m_timer; + static LPCTSTR sm_pszNotAvail; + bool m_bShowFileTypeWarning; + + void Localize(); + void RefreshData(); + + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnSetActive(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnDestroy(); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); + afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); +}; diff --git a/FileDetailDialogName.cpp b/FileDetailDialogName.cpp new file mode 100644 index 00000000..79f37e52 --- /dev/null +++ b/FileDetailDialogName.cpp @@ -0,0 +1,374 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "FileDetailDialogName.h" +#include "UserMsgs.h" +#include "OtherFunctions.h" +#include "PartFile.h" +#include "UpDownClient.h" +#include "TitleMenu.h" +#include "MenuCmds.h" +#include "StringConversion.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +IMPLEMENT_DYNAMIC(CFileDetailDialogName, CResizablePage) + +BEGIN_MESSAGE_MAP(CFileDetailDialogName, CResizablePage) + ON_BN_CLICKED(IDC_BUTTONSTRIP, OnBnClickedButtonStrip) + ON_BN_CLICKED(IDC_TAKEOVER, TakeOver) + ON_EN_CHANGE(IDC_FILENAME, OnEnChangeFilename) + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) + ON_NOTIFY(LVN_COLUMNCLICK, IDC_LISTCTRLFILENAMES, OnLvnColumnClick) + ON_NOTIFY(NM_DBLCLK, IDC_LISTCTRLFILENAMES, OnNmDblClkList) + ON_NOTIFY(NM_RCLICK, IDC_LISTCTRLFILENAMES, OnNmRClickList) + ON_WM_DESTROY() + ON_WM_TIMER() +END_MESSAGE_MAP() + +CFileDetailDialogName::CFileDetailDialogName() + : CResizablePage(CFileDetailDialogName::IDD, 0) +{ + m_paFiles = NULL; + m_bDataChanged = false; + m_strCaption = GetResString(IDS_SW_NAME); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; + m_timer = 0; + memset(m_aiColWidths, 0, sizeof m_aiColWidths); + m_bAppliedSystemImageList = false; + m_bSelf = false; +} + +CFileDetailDialogName::~CFileDetailDialogName() +{ +} + +void CFileDetailDialogName::OnTimer(UINT /*nIDEvent*/) +{ + RefreshData(); +} + +void CFileDetailDialogName::DoDataExchange(CDataExchange* pDX) +{ + CResizablePage::DoDataExchange(pDX); + DDX_Control(pDX, IDC_LISTCTRLFILENAMES, m_listFileNames); +} + +BOOL CFileDetailDialogName::OnInitDialog() +{ + CResizablePage::OnInitDialog(); + InitWindowStyles(this); + + AddAnchor(IDC_FD_SN, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_LISTCTRLFILENAMES, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_TAKEOVER, BOTTOM_LEFT); + AddAnchor(IDC_BUTTONSTRIP, BOTTOM_RIGHT); + AddAnchor(IDC_FILENAME, BOTTOM_LEFT, BOTTOM_RIGHT); + + m_listFileNames.SetPrefsKey(_T("FileDetailDlgName")); + m_listFileNames.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + m_listFileNames.InsertColumn(0, GetResString(IDS_DL_FILENAME), LVCFMT_LEFT, /*DFLT_FILENAME_COL_WIDTH*/450); + m_listFileNames.InsertColumn(1, GetResString(IDS_DL_SOURCES), LVCFMT_LEFT, 60); + ASSERT( (m_listFileNames.GetStyle() & LVS_SHAREIMAGELISTS) != 0 ); + m_listFileNames.LoadSettings(); + + m_listFileNames.SetSortArrow(); + m_listFileNames.SortItems(&CompareListNameItems, m_listFileNames.GetSortItem() + (m_listFileNames.GetSortAscending() ? 0 : 10)); + + Localize(); + + // start time for calling 'RefreshData' + VERIFY( (m_timer = SetTimer(301, 5000, 0)) != NULL ); + + return TRUE; +} + +BOOL CFileDetailDialogName::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + if (m_bDataChanged) + { + m_bSelf = true; + GetDlgItem(IDC_FILENAME)->SetWindowText(STATIC_DOWNCAST(CPartFile, (*m_paFiles)[0])->GetFileName()); + m_bSelf = false; + RefreshData(); + m_bDataChanged = false; + } + return TRUE; +} + +LRESULT CFileDetailDialogName::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +void CFileDetailDialogName::RefreshData() +{ + bool bEnableRename = CanRenameFile(); + GetDlgItem(IDC_FILENAME)->EnableWindow(bEnableRename); + GetDlgItem(IDC_BUTTONSTRIP)->EnableWindow(bEnableRename); + GetDlgItem(IDC_TAKEOVER)->EnableWindow(bEnableRename); + + FillSourcenameList(); +} + +void CFileDetailDialogName::OnDestroy() +{ + m_listFileNames.SaveSettings(); + + for (int i=0;iSetWindowText(GetResString(IDS_TAKEOVER)); + GetDlgItem(IDC_BUTTONSTRIP)->SetWindowText(GetResString(IDS_CLEANUP)); + GetDlgItem(IDC_FD_SN)->SetWindowText(GetResString(IDS_SOURCENAMES)); +} + +void CFileDetailDialogName::FillSourcenameList() +{ + LVFINDINFO info; + info.flags = LVFI_STRING; + int itempos; + + CString strText; + + // reset + for (int i=0;icount=0; + } + + // update + const CPartFile* file = STATIC_DOWNCAST(CPartFile, (*m_paFiles)[0]); + for (POSITION pos = file->srclist.GetHeadPosition(); pos != NULL; ) + { + CUpDownClient* cur_src = file->srclist.GetNext(pos); + if (cur_src->GetRequestFile() != file || cur_src->GetClientFilename().GetLength()==0) + continue; + + info.psz = cur_src->GetClientFilename(); + if ((itempos=m_listFileNames.FindItem(&info, -1)) == -1) + { + FCtrlItem_Struct* newitem= new FCtrlItem_Struct(); + newitem->count=1; + newitem->filename=cur_src->GetClientFilename(); + + int iSystemIconIdx = theApp.GetFileTypeSystemImageIdx(cur_src->GetClientFilename()); + if (theApp.GetSystemImageList() && !m_bAppliedSystemImageList) + { + m_listFileNames.ApplyImageList(theApp.GetSystemImageList()); + ASSERT( (m_listFileNames.GetStyle() & LVS_SHAREIMAGELISTS) != 0 ); + m_bAppliedSystemImageList = true; + } + + int ix = m_listFileNames.InsertItem(LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE, m_listFileNames.GetItemCount() ,cur_src->GetClientFilename(), 0, 0, iSystemIconIdx, (LPARAM)newitem); + m_listFileNames.SetItemText(ix, 1, _T("1")); + } + else + { + FCtrlItem_Struct* item= (FCtrlItem_Struct*)m_listFileNames.GetItemData(itempos); + item->count+=1; + strText.Format(_T("%i"),item->count); + m_listFileNames.SetItemText(itempos, 1,strText ); + } + } + + // remove 0'er + for (int i=0;icount==0) + { + delete item; + m_listFileNames.DeleteItem(i); + i=0; + } + } + + m_listFileNames.SortItems(&CompareListNameItems, m_listFileNames.GetSortItem() + (m_listFileNames.GetSortAscending() ? 0 : 10)); +} + +void CFileDetailDialogName::TakeOver() +{ + int iSel = m_listFileNames.GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1) + GetDlgItem(IDC_FILENAME)->SetWindowText(m_listFileNames.GetItemText(iSel, 0)); +} + +void CFileDetailDialogName::Copy() +{ + int iSel = m_listFileNames.GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1) + theApp.CopyTextToClipboard(m_listFileNames.GetItemText(iSel, 0)); +} + +void CFileDetailDialogName::OnBnClickedButtonStrip() +{ + CString filename; + GetDlgItem(IDC_FILENAME)->GetWindowText(filename); + GetDlgItem(IDC_FILENAME)->SetWindowText(CleanupFilename(filename)); +} + +void CFileDetailDialogName::OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult) +{ + NMLISTVIEW *pNMListView = (NMLISTVIEW *)pNMHDR; + bool sortAscending; + if (m_listFileNames.GetSortItem() != pNMListView->iSubItem) + { + switch (pNMListView->iSubItem) + { + case 1: // Count + sortAscending = false; + break; + default: + sortAscending = true; + break; + } + } + else + sortAscending = !m_listFileNames.GetSortAscending(); + + m_listFileNames.SetSortArrow(pNMListView->iSubItem, sortAscending); + m_listFileNames.SortItems(&CompareListNameItems, pNMListView->iSubItem + (sortAscending ? 0 : 10)); + + *pResult = 0; +} + +int CALLBACK CFileDetailDialogName::CompareListNameItems(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + const FCtrlItem_Struct *item1 = (FCtrlItem_Struct *)lParam1; + const FCtrlItem_Struct *item2 = (FCtrlItem_Struct *)lParam2; + + int iResult = 0; + switch (lParamSort) + { + case 0: + case 10: + iResult = CompareLocaleStringNoCase(item1->filename, item2->filename); + break; + + case 1: + case 11: + iResult = CompareUnsigned(item1->count, item2->count); + break; + } + + if (lParamSort >= 10) + iResult = -iResult; + + return iResult; +} + +void CFileDetailDialogName::OnNmDblClkList(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + TakeOver(); + *pResult = 0; +} + +void CFileDetailDialogName::OnNmRClickList(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + UINT flag = MF_STRING; + if (m_listFileNames.GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED) == -1) + flag = MF_GRAYED; + + POINT point; + ::GetCursorPos(&point); + CTitleMenu popupMenu; + popupMenu.CreatePopupMenu(); + popupMenu.AppendMenu(flag,MP_MESSAGE, GetResString(IDS_TAKEOVER)); + popupMenu.AppendMenu(flag,MP_COPYSELECTED, GetResString(IDS_COPY)); + popupMenu.AppendMenu(MF_STRING,MP_RESTORE, GetResString(IDS_SV_UPDATE)); + popupMenu.SetDefaultItem(MP_MESSAGE); + popupMenu.TrackPopupMenu(TPM_LEFTALIGN |TPM_RIGHTBUTTON, point.x, point.y, this); + VERIFY( popupMenu.DestroyMenu() ); + + *pResult = 0; +} + +BOOL CFileDetailDialogName::OnCommand(WPARAM wParam,LPARAM lParam ) +{ + int iSel = m_listFileNames.GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1) + { + switch (wParam) + { + case MP_MESSAGE: + TakeOver(); + return true; + case MP_COPYSELECTED: + Copy(); + return true; + case MP_RESTORE: + FillSourcenameList(); + return true; + } + } + return CResizablePage::OnCommand(wParam, lParam); +} + +void CFileDetailDialogName::RenameFile() +{ + if (CanRenameFile()) + { + CString strNewFileName; + GetDlgItem(IDC_FILENAME)->GetWindowText(strNewFileName); + strNewFileName.Trim(); + if (strNewFileName.IsEmpty() || !IsValidEd2kString(strNewFileName)) + return; + CPartFile* file = STATIC_DOWNCAST(CPartFile, (*m_paFiles)[0]); + file->SetFileName(strNewFileName, true); + file->UpdateDisplayedInfo(); + file->SavePartFile(); + } +} + +bool CFileDetailDialogName::CanRenameFile() const +{ + const CPartFile* file = STATIC_DOWNCAST(CPartFile, (*m_paFiles)[0]); + return (file->GetStatus() != PS_COMPLETE && file->GetStatus() != PS_COMPLETING); +} + +void CFileDetailDialogName::OnEnChangeFilename() +{ + if (!m_bSelf) + SetModified(); +} + +BOOL CFileDetailDialogName::OnApply() +{ + if (!m_bDataChanged) + RenameFile(); + return CResizablePage::OnApply(); +} \ No newline at end of file diff --git a/FileDetailDialogName.h b/FileDetailDialogName.h new file mode 100644 index 00000000..f476e7ab --- /dev/null +++ b/FileDetailDialogName.h @@ -0,0 +1,75 @@ +//this file is part of eMule +// +//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. +#pragma once +#include "ResizableLib/ResizablePage.h" +#include "MuleListCtrl.h" + +struct FCtrlItem_Struct +{ + CString filename; + uint16 count; +}; + +class CFileDetailDialogName : public CResizablePage +{ + DECLARE_DYNAMIC(CFileDetailDialogName) + +public: + CFileDetailDialogName(); // standard constructor + virtual ~CFileDetailDialogName(); + + void SetFiles(const CSimpleArray* paFiles) { m_paFiles = paFiles; m_bDataChanged = true; } + + // Dialog Data + enum { IDD = IDD_FILEDETAILS_NAME }; + +protected: + CString m_strCaption; + const CSimpleArray* m_paFiles; + bool m_bDataChanged; + bool m_bAppliedSystemImageList; + CMuleListCtrl m_listFileNames; + bool m_bSelf; + + uint32 m_timer; + int m_aiColWidths[2]; + + void Localize(); + void RefreshData(); + void FillSourcenameList(); + void Copy(); + bool CanRenameFile() const; + void RenameFile(); + + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnSetActive(); + virtual BOOL OnApply(); + + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + static int CALLBACK CompareListNameItems(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + + DECLARE_MESSAGE_MAP() + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); + afx_msg void OnBnClickedButtonStrip(); + afx_msg void OnDestroy(); + afx_msg void OnEnChangeFilename(); + afx_msg void OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmDblClkList(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmRClickList(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void TakeOver(); +}; diff --git a/FileDetailDlgStatistics.cpp b/FileDetailDlgStatistics.cpp new file mode 100644 index 00000000..4bc4dfc8 --- /dev/null +++ b/FileDetailDlgStatistics.cpp @@ -0,0 +1,329 @@ +//this file is part of eMule +//Copyright (C)2002-2010 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "filedetaildlgstatistics.h" +#include "UserMsgs.h" +#include "OtherFunctions.h" +#include "KnownFile.h" +#include "KnownFileList.h" +#include "SharedFileList.h" +#include "UploadQueue.h" +#include "emuledlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define REFRESH_TIMER_ID 9042 + +IMPLEMENT_DYNAMIC(CFileDetailDlgStatistics, CResizablePage) + +BEGIN_MESSAGE_MAP(CFileDetailDlgStatistics, CResizablePage) + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) + ON_WM_DESTROY() + ON_WM_SYSCOLORCHANGE() + ON_WM_TIMER() +END_MESSAGE_MAP() + +CFileDetailDlgStatistics::CFileDetailDlgStatistics() + : CResizablePage(CFileDetailDlgStatistics::IDD, 0) +{ + m_paFiles = NULL; + m_bDataChanged = false; + m_strCaption = GetResString(IDS_SF_STATISTICS); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; + nLastRequestCount = 0; + m_hRefreshTimer = 0; +} + +CFileDetailDlgStatistics::~CFileDetailDlgStatistics() +{ +} + + +void CFileDetailDlgStatistics::DoDataExchange(CDataExchange* pDX) +{ + CResizablePage::DoDataExchange(pDX); + DDX_Control(pDX, IDC_POPBAR, pop_bar); + DDX_Control(pDX, IDC_POPBAR2, pop_baraccept); + DDX_Control(pDX, IDC_POPBAR3, pop_bartrans); + DDX_Control(pDX, IDC_POPBAR4, pop_bar2); + DDX_Control(pDX, IDC_POPBAR5, pop_baraccept2); + DDX_Control(pDX, IDC_POPBAR6, pop_bartrans2); +} + +BOOL CFileDetailDlgStatistics::OnInitDialog() +{ + CResizablePage::OnInitDialog(); + InitWindowStyles(this); + + AddAnchor(IDC_FSTATIC4, TOP_LEFT); + AddAnchor(IDC_SREQUESTED, TOP_LEFT); + AddAnchor(IDC_FSTATIC5, TOP_LEFT); + AddAnchor(IDC_SACCEPTED, TOP_LEFT); + AddAnchor(IDC_FSTATIC6, TOP_LEFT); + AddAnchor(IDC_STRANSFERRED, TOP_LEFT); + AddAnchor(pop_bar, TOP_LEFT, TOP_RIGHT); + AddAnchor(pop_baraccept, TOP_LEFT, TOP_RIGHT); + AddAnchor(pop_bartrans, TOP_LEFT, TOP_RIGHT); + AddAnchor(pop_bar2, TOP_LEFT, TOP_RIGHT); + AddAnchor(pop_baraccept2, TOP_LEFT, TOP_RIGHT); + AddAnchor(pop_bartrans2, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_SREQUESTED2,TOP_LEFT); + AddAnchor(IDC_FSTATIC7,TOP_LEFT); + AddAnchor(IDC_FSTATIC8,TOP_LEFT); + AddAnchor(IDC_FSTATIC9,TOP_LEFT); + AddAnchor(IDC_STRANSFERRED2,TOP_LEFT); + AddAnchor(IDC_SACCEPTED2,TOP_LEFT); + AddAnchor(IDC_STATISTICS_GB_TOTAL, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_STATISTICS_GB_SESSION, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FS_POPULARITY_LBL,TOP_RIGHT); + AddAnchor(IDC_FS_POPULARITY_VAL,TOP_RIGHT); + AddAnchor(IDC_FS_ONQUEUE_LBL,TOP_RIGHT); + AddAnchor(IDC_FS_ONQUEUE_VAL,TOP_RIGHT); + AddAnchor(IDC_FS_UPLOADING_LBL,TOP_RIGHT); + AddAnchor(IDC_FS_UPLOADING_VAL,TOP_RIGHT); + AddAnchor(IDC_FS_POPULARITY2_LBL,TOP_RIGHT); + AddAnchor(IDC_FS_POPULARITY2_VAL,TOP_RIGHT); + + pop_bar.SetGradientColors(RGB(255,255,240),RGB(255,255,0)); + pop_bar.SetTextColor(RGB(20,70,255)); + pop_baraccept.SetGradientColors(RGB(255,255,240),RGB(255,255,0)); + pop_baraccept.SetTextColor(RGB(20,70,255)); + pop_bartrans.SetGradientColors(RGB(255,255,240),RGB(255,255,0)); + pop_bartrans.SetTextColor(RGB(20,70,255)); + pop_bar2.SetGradientColors(RGB(255,255,240),RGB(255,255,0)); + pop_bar2.SetTextColor(RGB(20,70,255)); + pop_baraccept2.SetGradientColors(RGB(255,255,240),RGB(255,255,0)); + pop_baraccept2.SetTextColor(RGB(20,70,255)); + pop_bartrans2.SetGradientColors(RGB(255,255,240),RGB(255,255,0)); + pop_bartrans2.SetTextColor(RGB(20,70,255)); + + pop_baraccept.SetShowPercent(); + pop_bar.SetShowPercent(); + pop_bartrans.SetShowPercent(); + pop_baraccept2.SetShowPercent(); + pop_bar2.SetShowPercent(); + pop_bartrans2.SetShowPercent(); + Localize(); + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +BOOL CFileDetailDlgStatistics::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + if (m_hRefreshTimer == 0) + { + m_hRefreshTimer = SetTimer(REFRESH_TIMER_ID, 3000, NULL); + } + if (m_bDataChanged) + { + RefreshData(); + m_bDataChanged = false; + } + return TRUE; +} + +BOOL CFileDetailDlgStatistics::OnKillActive() +{ + if (m_hRefreshTimer) + { + KillTimer(REFRESH_TIMER_ID); + m_hRefreshTimer = 0; + } + return CResizablePage::OnKillActive(); +} + +LRESULT CFileDetailDlgStatistics::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +void CFileDetailDlgStatistics::RefreshData() +{ + if (m_paFiles == NULL) + return; + const CKnownFile* pTheFile = NULL; + int iFiles = 0; + uint64 uTransferred = 0; + UINT uRequests = 0; + UINT uAccepted = 0; + uint64 uAllTimeTransferred = 0; + UINT uAllTimeRequests = 0; + UINT uAllTimeAccepted = 0; + for (int i = 0; i != m_paFiles->GetSize(); i++){ + if (!(*m_paFiles)[i]->IsKindOf(RUNTIME_CLASS(CKnownFile))) + continue; + const CKnownFile* pFile = (CKnownFile*)(*m_paFiles)[i]; + if (theApp.sharedfiles->GetFileByIdentifier(pFile->GetFileIdentifierC()) == NULL) + continue; + iFiles++; + if (iFiles == 1) + pTheFile = pFile; + + uTransferred += pFile->statistic.GetTransferred(); + uRequests += pFile->statistic.GetRequests(); + uAccepted += pFile->statistic.GetAccepts(); + + uAllTimeTransferred += pFile->statistic.GetAllTimeTransferred(); + uAllTimeRequests += pFile->statistic.GetAllTimeRequests(); + uAllTimeAccepted += pFile->statistic.GetAllTimeAccepts(); + } + + if (iFiles != 0) + { + pop_bartrans.SetEmpty(false); + pop_bartrans.SetRange32(0, (int)(theApp.knownfiles->transferred/1024)); + pop_bartrans.SetPos((int)(uTransferred/1024)); + SetDlgItemText(IDC_STRANSFERRED, CastItoXBytes(uTransferred, false, false)); + + pop_bar.SetEmpty(false); + pop_bar.SetRange32(0, theApp.knownfiles->requested); + pop_bar.SetPos(uRequests); + SetDlgItemInt(IDC_SREQUESTED, uRequests, FALSE); + + pop_baraccept.SetEmpty(false); + pop_baraccept.SetRange32(0, theApp.knownfiles->accepted); + pop_baraccept.SetPos(uAccepted); + SetDlgItemInt(IDC_SACCEPTED, uAccepted, FALSE); + + pop_bartrans2.SetEmpty(false); + pop_bartrans2.SetRange32(0, (int)(theApp.knownfiles->m_nTransferredTotal/(1024*1024))); + pop_bartrans2.SetPos((int)(uAllTimeTransferred/(1024*1024))); + + pop_bar2.SetEmpty(false); + pop_bar2.SetRange32(0, theApp.knownfiles->m_nRequestedTotal); + pop_bar2.SetPos(uAllTimeRequests); + + pop_baraccept2.SetEmpty(false); + pop_baraccept2.SetRange32(0, theApp.knownfiles->m_nAcceptedTotal); + pop_baraccept2.SetPos(uAllTimeAccepted); + + SetDlgItemText(IDC_STRANSFERRED2, CastItoXBytes(uAllTimeTransferred, false, false)); + SetDlgItemInt(IDC_SREQUESTED2, uAllTimeRequests, FALSE); + SetDlgItemInt(IDC_SACCEPTED2, uAllTimeAccepted, FALSE); + + uint32 nQueueCount = theApp.uploadqueue->GetWaitingUserForFileCount(*m_paFiles, !m_bDataChanged); + if (nQueueCount != (-1)) + { + SetDlgItemInt(IDC_FS_ONQUEUE_VAL, nQueueCount, FALSE); + } + SetDlgItemText(IDC_FS_UPLOADING_VAL, CastItoXBytes(theApp.uploadqueue->GetDatarateForFile(*m_paFiles), false, true)); + + + if (iFiles == 1) + { + if (m_bDataChanged || nLastRequestCount != theApp.knownfiles->m_nRequestedTotal) + { + nLastRequestCount = theApp.knownfiles->m_nRequestedTotal; + uint32 nSession = 0, nTotal = 0; + if (theApp.sharedfiles->GetPopularityRank(pTheFile, nSession, nTotal)) + { + SetDlgItemInt(IDC_FS_POPULARITY_VAL, nSession, FALSE); + SetDlgItemInt(IDC_FS_POPULARITY2_VAL, nTotal, FALSE); + } + } + } + else + { + SetDlgItemText(IDC_FS_POPULARITY_VAL, _T("-")); + SetDlgItemText(IDC_FS_POPULARITY2_VAL, _T("-")); + } + } + else + { + SetDlgItemText(IDC_STRANSFERRED, _T("-")); + SetDlgItemText(IDC_SREQUESTED, _T("-")); + SetDlgItemText(IDC_SACCEPTED, _T("-")); + SetDlgItemText(IDC_STRANSFERRED2, _T("-")); + SetDlgItemText(IDC_SREQUESTED2, _T("-")); + SetDlgItemText(IDC_SACCEPTED2, _T("-")); + SetDlgItemText(IDC_FS_POPULARITY_VAL, _T("-")); + SetDlgItemText(IDC_FS_POPULARITY2_VAL, _T("-")); + SetDlgItemText(IDC_FS_ONQUEUE_VAL, _T("-")); + SetDlgItemText(IDC_FS_UPLOADING_VAL, _T("-")); + + pop_bartrans.SetEmpty(true, true); + pop_bar.SetEmpty(true, true); + pop_baraccept.SetEmpty(true, true); + pop_bartrans2.SetEmpty(true, true); + pop_bar2.SetEmpty(true, true); + pop_baraccept2.SetEmpty(true, true); + } +} + + +void CFileDetailDlgStatistics::OnDestroy() +{ + if (m_hRefreshTimer) + { + KillTimer(REFRESH_TIMER_ID); + m_hRefreshTimer = 0; + } + CResizablePage::OnDestroy(); +} + +void CFileDetailDlgStatistics::Localize() +{ + GetDlgItem(IDC_STATISTICS_GB_SESSION)->SetWindowText(GetResString(IDS_SF_CURRENT)); + GetDlgItem(IDC_STATISTICS_GB_TOTAL)->SetWindowText(GetResString(IDS_SF_TOTAL)); + GetDlgItem(IDC_FSTATIC6)->SetWindowText(GetResString(IDS_SF_TRANS)); + GetDlgItem(IDC_FSTATIC5)->SetWindowText(GetResString(IDS_SF_ACCEPTED)); + GetDlgItem(IDC_FSTATIC4)->SetWindowText(GetResString(IDS_SF_REQUESTS)+_T(":")); + GetDlgItem(IDC_FSTATIC9)->SetWindowText(GetResString(IDS_SF_TRANS)); + GetDlgItem(IDC_FSTATIC8)->SetWindowText(GetResString(IDS_SF_ACCEPTED)); + GetDlgItem(IDC_FSTATIC7)->SetWindowText(GetResString(IDS_SF_REQUESTS)+_T(":")); + GetDlgItem(IDC_FS_POPULARITY_LBL)->SetWindowText(GetResString(IDS_POPULARITY) + _T(":")); + GetDlgItem(IDC_FS_POPULARITY2_LBL)->SetWindowText(GetResString(IDS_POPULARITY) + _T(":")); + GetDlgItem(IDC_FS_ONQUEUE_LBL)->SetWindowText(GetResString(IDS_ONQUEUE) + _T(":")); + GetDlgItem(IDC_FS_UPLOADING_LBL)->SetWindowText(GetResString(IDS_UPLOADING) + _T(":")); +} + +void CFileDetailDlgStatistics::OnSysColorChange() +{ + pop_bar.SetBkColor(GetSysColor(COLOR_3DFACE)); + pop_baraccept.SetBkColor(GetSysColor(COLOR_3DFACE)); + pop_bartrans.SetBkColor(GetSysColor(COLOR_3DFACE)); + pop_bar2.SetBkColor(GetSysColor(COLOR_3DFACE)); + pop_baraccept2.SetBkColor(GetSysColor(COLOR_3DFACE)); + pop_bartrans2.SetBkColor(GetSysColor(COLOR_3DFACE)); + CResizablePage::OnSysColorChange(); +} + +void CFileDetailDlgStatistics::OnTimer(UINT nIDEvent) +{ + if (nIDEvent == REFRESH_TIMER_ID) + { + if (theApp.emuledlg == NULL || !theApp.emuledlg->IsRunning() || !GetParent()->IsWindowVisible() + || theApp.emuledlg->GetActiveDialog() != (CWnd*)theApp.emuledlg->sharedfileswnd) + { + KillTimer(REFRESH_TIMER_ID); + m_hRefreshTimer = 0; + } + else + RefreshData(); + } + else + CResizablePage::OnTimer(nIDEvent); +} diff --git a/FileDetailDlgStatistics.h b/FileDetailDlgStatistics.h new file mode 100644 index 00000000..a888feb5 --- /dev/null +++ b/FileDetailDlgStatistics.h @@ -0,0 +1,62 @@ +//this file is part of eMule +//Copyright (C)2002-2010 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "ProgressCtrlX.h" +#include "ResizableLib/ResizablePage.h" + +class CAbstractFile; + +class CFileDetailDlgStatistics : public CResizablePage +{ + DECLARE_DYNAMIC(CFileDetailDlgStatistics) + +public: + CFileDetailDlgStatistics(); + virtual ~CFileDetailDlgStatistics(); + + void SetFiles(const CSimpleArray* paFiles) { m_paFiles = paFiles; m_bDataChanged = true; } + void Localize(); + +// Dialog Data + enum { IDD = IDD_FILESTATISTICS }; + +protected: + const CSimpleArray* m_paFiles; + bool m_bDataChanged; + CProgressCtrlX pop_bar; + CProgressCtrlX pop_baraccept; + CProgressCtrlX pop_bartrans; + CProgressCtrlX pop_bar2; + CProgressCtrlX pop_baraccept2; + CProgressCtrlX pop_bartrans2; + + void RefreshData(); + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + virtual BOOL OnSetActive(); + virtual BOOL OnKillActive(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnDestroy(); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); + afx_msg void OnSysColorChange(); + afx_msg void OnTimer(UINT nIDEvent); + + uint32 nLastRequestCount; + UINT_PTR m_hRefreshTimer; +}; diff --git a/FileIdentifier.cpp b/FileIdentifier.cpp new file mode 100644 index 00000000..006513d1 --- /dev/null +++ b/FileIdentifier.cpp @@ -0,0 +1,532 @@ +//this file is part of eMule +//Copyright (C)2002-2010 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "StdAfx.h" +#include ".\fileidentifier.h" +#include "otherfunctions.h" +#include "safefile.h" +#include "knownfile.h" +#include "log.h" + +// File size Data parts ED2K parts ED2K part hashs AICH part hashs +// ------------------------------------------------------------------------------------------- +// 1..PARTSIZE-1 1 1 0(!) 0 (!) +// PARTSIZE 1 2(!) 2(!) 0 (!) +// PARTSIZE+1 2 2 2 2 +// PARTSIZE*2 2 3(!) 3(!) 2 +// PARTSIZE*2+1 3 3 3 3 + +/////////////////////////////////////////////////////////////////////////////////////////////// +// CFileIdentifierBase +CFileIdentifierBase::CFileIdentifierBase() +{ + md4clr(m_abyMD4Hash); + m_bHasValidAICHHash = false; +} + +CFileIdentifierBase::CFileIdentifierBase(const CFileIdentifierBase& rFileIdentifier) +{ + md4cpy(m_abyMD4Hash, rFileIdentifier.m_abyMD4Hash); + m_bHasValidAICHHash = rFileIdentifier.m_bHasValidAICHHash; + m_AICHFileHash = rFileIdentifier.m_AICHFileHash; +} + +CFileIdentifierBase::~CFileIdentifierBase(void) +{ + +} + +// TODO: Remove me +EMFileSize CFileIdentifierBase::GetFileSize() const +{ + ASSERT( false ); + return (uint64)0; +} + +void CFileIdentifierBase::SetMD4Hash(const uchar* pucFileHash) +{ + md4cpy(m_abyMD4Hash, pucFileHash); +} + +void CFileIdentifierBase::SetMD4Hash(CFileDataIO* pFile) +{ + pFile->ReadHash16(m_abyMD4Hash); +} + +void CFileIdentifierBase::SetAICHHash(const CAICHHash& Hash) +{ + m_AICHFileHash = Hash; + m_bHasValidAICHHash = true; +} + +bool CFileIdentifierBase::CompareRelaxed(const CFileIdentifierBase& rFileIdentifier) const +{ + ASSERT( !isnulmd4(m_abyMD4Hash) ); + ASSERT( !isnulmd4(rFileIdentifier.m_abyMD4Hash) ); + return md4cmp(m_abyMD4Hash, rFileIdentifier.m_abyMD4Hash) == 0 + && (GetFileSize() == (uint64)0 || rFileIdentifier.GetFileSize() == (uint64)0 || GetFileSize() == rFileIdentifier.GetFileSize()) + && (!m_bHasValidAICHHash || !rFileIdentifier.m_bHasValidAICHHash || m_AICHFileHash == rFileIdentifier.m_AICHFileHash); +} + +bool CFileIdentifierBase::CompareStrict(const CFileIdentifierBase& rFileIdentifier) const +{ + ASSERT( !isnulmd4(m_abyMD4Hash) ); + ASSERT( !isnulmd4(rFileIdentifier.m_abyMD4Hash) ); + return md4cmp(m_abyMD4Hash, rFileIdentifier.m_abyMD4Hash) == 0 + && GetFileSize() == rFileIdentifier.GetFileSize() + && !(m_bHasValidAICHHash ^ rFileIdentifier.m_bHasValidAICHHash) + && m_AICHFileHash == rFileIdentifier.m_AICHFileHash; +} + +void CFileIdentifierBase::WriteIdentifier(CFileDataIO* pFile, bool bKadExcludeMD4) const +{ + ASSERT( !isnulmd4(m_abyMD4Hash) ); + ASSERT( GetFileSize() != (uint64)0 ); + const UINT uIncludesMD4 = bKadExcludeMD4 ? 0 : 1; // This is (currently) mandatory except for Kad + const UINT uIncludesSize = (GetFileSize() != (uint64)0) ? 1 : 0; + const UINT uIncludesAICH = HasAICHHash() ? 1 : 0; + const UINT uMandatoryOptions = 0; // RESERVED - Identifier invalid if we encounter unknown options + const UINT uOptions = 0; // RESERVED + + uint8 byIdentifierDesc = (uint8) + ((uOptions << 5) | + (uMandatoryOptions << 3) | + (uIncludesAICH << 2) | + (uIncludesSize << 1) | + (uIncludesMD4 << 0)); + //DebugLog(_T("Write IdentifierDesc: %u"), byIdentifierDesc); + pFile->WriteUInt8(byIdentifierDesc); + if (!bKadExcludeMD4) + pFile->WriteHash16(m_abyMD4Hash); + if (GetFileSize() != (uint64)0) + pFile->WriteUInt64(GetFileSize()); + if (HasAICHHash()) + m_AICHFileHash.Write(pFile); +} + +CString CFileIdentifierBase::DbgInfo() const +{ + //TODO fileident + return _T(""); +} +/////////////////////////////////////////////////////////////////////////////////////////////// +// CFileIdentifier +CFileIdentifier::CFileIdentifier(EMFileSize& rFileSize) + : m_rFileSize(rFileSize) +{ + +} + +CFileIdentifier::CFileIdentifier(const CFileIdentifier& rFileIdentifier, EMFileSize& rFileSize) + : CFileIdentifierBase(rFileIdentifier), m_rFileSize(rFileSize) +{ + for (int i = 0; i < rFileIdentifier.m_aMD4HashSet.GetCount(); i++) + { + uchar* pucHashSetPart = new uchar[16]; + md4cpy(pucHashSetPart, rFileIdentifier.m_aMD4HashSet[i]); + m_aMD4HashSet.Add(pucHashSetPart); + } + for (int i = 0; i < rFileIdentifier.m_aAICHPartHashSet.GetCount(); i++) + m_aAICHPartHashSet.Add(rFileIdentifier.m_aAICHPartHashSet[i]); +} + +CFileIdentifier::~CFileIdentifier(void) +{ + DeleteMD4Hashset(); +} + +bool CFileIdentifier::CalculateMD4HashByHashSet(bool bVerifyOnly, bool bDeleteOnVerifyFail) +{ + if (m_aMD4HashSet.GetCount() <= 1) + { + ASSERT( false ); + return false; + } + uchar* buffer = new uchar[m_aMD4HashSet.GetCount() * 16]; + for (int i = 0; i < m_aMD4HashSet.GetCount(); i++) + md4cpy(buffer + (i*16), m_aMD4HashSet[i]); + uchar aucResult[16]; + CKnownFile::CreateHash(buffer, m_aMD4HashSet.GetCount()*16, aucResult); + delete[] buffer; + if (bVerifyOnly) + { + if (md4cmp(aucResult, m_abyMD4Hash) != 0) + { + if (bDeleteOnVerifyFail) + DeleteMD4Hashset(); + return false; + } + else + return true; + } + else + { + md4cpy(m_abyMD4Hash, aucResult); + return true; + } +} + +bool CFileIdentifier::LoadMD4HashsetFromFile(CFileDataIO* file, bool bVerifyExistingHash) +{ + uchar checkid[16]; + file->ReadHash16(checkid); + //TRACE("File size: %u (%u full parts + %u bytes)\n", GetFileSize(), GetFileSize()/PARTSIZE, GetFileSize()%PARTSIZE); + //TRACE("File hash: %s\n", md4str(checkid)); + ASSERT( m_aMD4HashSet.IsEmpty() ); + ASSERT( !isnulmd4(m_abyMD4Hash) || !bVerifyExistingHash); + DeleteMD4Hashset(); + + UINT parts = file->ReadUInt16(); + //TRACE("Nr. hashs: %u\n", (UINT)parts); + if (bVerifyExistingHash && (md4cmp(m_abyMD4Hash, checkid) != 0 || parts != GetTheoreticalMD4PartHashCount())) + return false; + for (UINT i = 0; i < parts; i++){ + uchar* cur_hash = new uchar[16]; + file->ReadHash16(cur_hash); + //TRACE("Hash[%3u]: %s\n", i, md4str(cur_hash)); + m_aMD4HashSet.Add(cur_hash); + } + + if (!bVerifyExistingHash) + md4cpy(m_abyMD4Hash, checkid); + + // Calculate hash out of hashset and compare to existing filehash + if (!m_aMD4HashSet.IsEmpty()) + return CalculateMD4HashByHashSet(true, true); + else + return true; +} + +bool CFileIdentifier::SetMD4HashSet(const CArray& aHashset) +{ + // delete hashset + for (int i = 0; i < m_aMD4HashSet.GetCount(); i++) + delete[] m_aMD4HashSet[i]; + m_aMD4HashSet.RemoveAll(); + + // set new hash + for (int i = 0; i < aHashset.GetSize(); i++) + { + uchar* pucHash = new uchar[16]; + md4cpy(pucHash, aHashset.GetAt(i)); + m_aMD4HashSet.Add(pucHash); + } + + // verify new hash + if (m_aMD4HashSet.IsEmpty()) + return true; + else + return CalculateMD4HashByHashSet(true, true); +} + +uchar* CFileIdentifier::GetMD4PartHash(UINT part) const +{ + if (part >= (UINT)m_aMD4HashSet.GetCount()) + return NULL; + return m_aMD4HashSet[part]; +} + +// nr. of part hashs according the file size wrt ED2K protocol +// nr. of parts to be used with OP_HASHSETANSWER +uint16 CFileIdentifier::GetTheoreticalMD4PartHashCount() const +{ + if (m_rFileSize == (uint64)0) + { + ASSERT( false ); + return 0; + } + uint16 uResult = (uint16)((uint64)m_rFileSize / PARTSIZE); + if (uResult > 0) + uResult++; + return uResult; +} + +void CFileIdentifier::WriteMD4HashsetToFile(CFileDataIO* pFile) const +{ + ASSERT( !isnulmd4(m_abyMD4Hash) ); + pFile->WriteHash16(m_abyMD4Hash); + UINT uParts = m_aMD4HashSet.GetCount(); + pFile->WriteUInt16((uint16)uParts); + for (UINT i = 0; i < uParts; i++) + pFile->WriteHash16(m_aMD4HashSet[i]); +} + +void CFileIdentifier::WriteHashSetsToPacket(CFileDataIO* pFile, bool bMD4, bool bAICH) const +{ + // 6 Options - RESERVED + // 1 AICH HashSet + // 1 MD4 HashSet + uint8 byOptions = 0; + if (bMD4) + { + if (GetTheoreticalMD4PartHashCount() == 0) + { + bMD4 = false; + DebugLogWarning(_T("CFileIdentifier::WriteHashSetsToPacket - requested zerosized MD4 HashSet")); + } + else if (HasExpectedMD4HashCount()) + byOptions |= 0x01; + else + { + bMD4 = false; + DebugLogError(_T("CFileIdentifier::WriteHashSetsToPacket - unable to write MD4 HashSet")); + } + } + if (bAICH) + { + if (GetTheoreticalAICHPartHashCount() == 0) + { + bAICH = false; + DebugLogWarning(_T("CFileIdentifier::WriteHashSetsToPacket - requested zerosized AICH HashSet")); + } + else if (HasExpectedAICHHashCount() && HasAICHHash()) + byOptions |= 0x02; + else + { + bAICH = false; + DEBUG_ONLY(DebugLog(_T("CFileIdentifier::WriteHashSetsToPacket - unable to write AICH HashSet"))); + } + } + pFile->WriteUInt8(byOptions); + if (bMD4) + WriteMD4HashsetToFile(pFile); + if (bAICH) + WriteAICHHashsetToFile(pFile); +} + +bool CFileIdentifier::ReadHashSetsFromPacket(CFileDataIO* pFile, bool& rbMD4, bool& rbAICH) +{ + ASSERT( rbMD4 || rbAICH ); + uint8 byOptions = pFile->ReadUInt8(); + bool bMD4Present = (byOptions & 0x01) > 0; + bool bAICHPresent = (byOptions & 0x02) > 0; + // We don't abort on unkown option, because even if there is another unknown hashset, there is no data afterwards we + // try to read on the only occasion this function is used. So we might be able to add optional flags in the future + // without having to adjust the protocol any further (new additional data/hashs should not be appended without adjustement however) + if ((byOptions >> 2) > 0) + DebugLogWarning(_T("Unknown Options/HashSets set in CFileIdentifier::ReadHashSetsFromPacket")); + + if (bMD4Present && !rbMD4) + { + DebugLogWarning(_T("CFileIdentifier::ReadHashSetsFromPacket: MD4 HashSet present but unrequested")); + // Even if we don't want it, we still have to read the file to skip it + uchar tmpHash[16]; + pFile->ReadHash16(tmpHash); + UINT parts = pFile->ReadUInt16(); + for (UINT i = 0; i < parts; i++) + pFile->ReadHash16(tmpHash); + } + else if (!bMD4Present) + rbMD4 = false; + else if (bMD4Present && rbMD4) + { + if (!LoadMD4HashsetFromFile(pFile, true)) + { // corrupt + rbMD4 = false; + rbAICH = false; + return false; + } + } + + if (bAICHPresent && !rbAICH) + { + DebugLogWarning(_T("CFileIdentifier::ReadHashSetsFromPacket: AICH HashSet present but unrequested")); + // Even if we don't want it, we still have to read the file to skip it + CAICHHash tmpHash(pFile); + uint16 nCount = pFile->ReadUInt16(); + for (int i = 0; i < nCount; i++) + tmpHash.Read(pFile); + } + else if (!bAICHPresent || !HasAICHHash()) + { + ASSERT( !bAICHPresent ); + rbAICH = false; + } + else if (bAICHPresent && rbAICH) + { + if (!LoadAICHHashsetFromFile(pFile, true)) + { // corrupt + if (rbMD4) + { + DeleteMD4Hashset(); + rbMD4 = false; + } + rbAICH = false; + return false; + } + } + return true; +} + +void CFileIdentifier::DeleteMD4Hashset() +{ + for (int i = 0; i < m_aMD4HashSet.GetCount(); i++) + delete[] m_aMD4HashSet[i]; + m_aMD4HashSet.RemoveAll(); +} + +uint16 CFileIdentifier::GetTheoreticalAICHPartHashCount() const +{ + return (m_rFileSize <= PARTSIZE) ? 0 : ((uint16)(((uint64)m_rFileSize + (PARTSIZE - 1)) / PARTSIZE)); +} + +bool CFileIdentifier::SetAICHHashSet(const CAICHRecoveryHashSet& sourceHashSet) +{ + ASSERT( m_bHasValidAICHHash ); + if (sourceHashSet.GetStatus() != AICH_HASHSETCOMPLETE || sourceHashSet.GetMasterHash() != m_AICHFileHash) + { + ASSERT( false ); + DebugLogError(_T("Unexpected error SetAICHHashSet(), AICHPartHashSet not loaded")); + return false; + } + return sourceHashSet.GetPartHashs(m_aAICHPartHashSet) && HasExpectedAICHHashCount(); +} + +bool CFileIdentifier::SetAICHHashSet(const CFileIdentifier& rSourceHashSet) +{ + if (!rSourceHashSet.HasAICHHash() || !rSourceHashSet.HasExpectedAICHHashCount()) + { + ASSERT( false ); + return false; + } + m_aAICHPartHashSet.RemoveAll(); + for (int i = 0; i < rSourceHashSet.m_aAICHPartHashSet.GetCount(); i++) + m_aAICHPartHashSet.Add(rSourceHashSet.m_aAICHPartHashSet[i]); + ASSERT( HasExpectedAICHHashCount() ); + return HasExpectedAICHHashCount(); +} + +bool CFileIdentifier::LoadAICHHashsetFromFile(CFileDataIO* pFile, bool bVerify) +{ + ASSERT( m_aAICHPartHashSet.IsEmpty() ); + m_aAICHPartHashSet.RemoveAll(); + CAICHHash masterHash(pFile); + if (HasAICHHash() && masterHash != m_AICHFileHash) + { + ASSERT( false ); + DebugLogError(_T("Loading AICH Part Hashset error: HashSet Masterhash doesn't matches with existing masterhash - hashset not loaded")); + return false; + } + uint16 nCount = pFile->ReadUInt16(); + for (int i = 0; i < nCount; i++) + m_aAICHPartHashSet.Add(CAICHHash(pFile)); + if (bVerify) + return VerifyAICHHashSet(); + else + return true; +} + +void CFileIdentifier::WriteAICHHashsetToFile(CFileDataIO* pFile) const +{ + ASSERT( HasAICHHash() ); + ASSERT( HasExpectedAICHHashCount() ); + m_AICHFileHash.Write(pFile); + UINT uParts = m_aAICHPartHashSet.GetCount(); + pFile->WriteUInt16((uint16)uParts); + for (UINT i = 0; i < uParts; i++) + m_aAICHPartHashSet[i].Write(pFile); +} + +bool CFileIdentifier::VerifyAICHHashSet() +{ + if (m_rFileSize == (uint64)0 || !m_bHasValidAICHHash) + { + ASSERT( false ); + return false; + } + if (!HasExpectedAICHHashCount()) + return false; + CAICHRecoveryHashSet tmpAICHHashSet(NULL, m_rFileSize); + tmpAICHHashSet.SetMasterHash(m_AICHFileHash, AICH_HASHSETCOMPLETE); + + uint32 uPartCount = (uint16)(((uint64)m_rFileSize + (PARTSIZE - 1)) / PARTSIZE); + if (uPartCount <= 1) + return true; // No AICH Part Hashs + for (uint32 nPart = 0; nPart < uPartCount; nPart++) + { + uint64 nPartStartPos = (uint64)nPart*PARTSIZE; + uint32 nPartSize = (uint32)min(PARTSIZE, (uint64)GetFileSize()-nPartStartPos); + CAICHHashTree* pPartHashTree = tmpAICHHashSet.m_pHashTree.FindHash(nPartStartPos, nPartSize); + if (pPartHashTree != NULL) + { + pPartHashTree->m_Hash = m_aAICHPartHashSet[nPart]; + pPartHashTree->m_bHashValid = true; + } + else + { + ASSERT( false ); + return false; + } + } + if (!tmpAICHHashSet.VerifyHashTree(false)) + { + m_aAICHPartHashSet.RemoveAll(); + return false; + } + else + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// CFileIdentifierSA + +CFileIdentifierSA::CFileIdentifierSA() +{ + m_nFileSize = (uint64)0; +} +CFileIdentifierSA::CFileIdentifierSA(const uchar* pucFileHash, EMFileSize nFileSize, const CAICHHash& rHash, bool bAICHHashValid) +{ + SetMD4Hash(pucFileHash); + m_nFileSize = nFileSize; + if (bAICHHashValid) + SetAICHHash(rHash); +} + +bool CFileIdentifierSA::ReadIdentifier(CFileDataIO* pFile, bool bKadValidWithoutMd4) +{ + uint8 byIdentifierDesc = pFile->ReadUInt8(); + //DebugLog(_T("Read IdentifierDesc: %u"), byIdentifierDesc); + bool bMD4 = ((byIdentifierDesc >> 0) & 0x01) > 0; + bool bSize = ((byIdentifierDesc >> 1) & 0x01) > 0; + bool bAICH = ((byIdentifierDesc >> 2) & 0x01) > 0; + uint8 byMOpt= ((byIdentifierDesc >> 3) & 0x03); + uint8 byOpts= ((byIdentifierDesc >> 5) & 0x07); + if (byMOpt > 0) + { + DebugLogError(_T("Unknown mandatory options (%u) set on reading fileidentifier, aborting"), byMOpt); + return false; + } + if (byOpts > 0) + DebugLogWarning(_T("Unknown options (%u) set on reading fileidentifier"), byOpts); + if (!bMD4 && !bKadValidWithoutMd4) + { + DebugLogError(_T("Mandatory MD4 hash not included on reading fileidentifier, aborting")); + return false; + } + if (!bSize) + DebugLogWarning(_T("Size not included on reading fileidentifier"), byOpts); + + if (bMD4) + pFile->ReadHash16(m_abyMD4Hash); + if (bSize) + m_nFileSize = pFile->ReadUInt64(); + if (bAICH) + { + m_AICHFileHash.Read(pFile); + m_bHasValidAICHHash = true; + } + return true; +} \ No newline at end of file diff --git a/FileIdentifier.h b/FileIdentifier.h new file mode 100644 index 00000000..fd0f188e --- /dev/null +++ b/FileIdentifier.h @@ -0,0 +1,139 @@ +//this file is part of eMule +//Copyright (C)2002-2010 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "ShaHashset.h" + +class CFileDataIO; + +/////////////////////////////////////////////////////////////////////////////////////////////// +// CFileIdentifierBase +class CFileIdentifierBase +{ +public: + virtual ~CFileIdentifierBase(); + + virtual EMFileSize GetFileSize() const; + + void WriteIdentifier(CFileDataIO* pFile, bool bKadExcludeMD4 = false) const; + bool CompareRelaxed(const CFileIdentifierBase& rFileIdentifier) const; + bool CompareStrict(const CFileIdentifierBase& rFileIdentifier) const; + + CString DbgInfo() const; + + //******************** MD4 Related + void SetMD4Hash(const uchar* pucFileHash); + void SetMD4Hash(CFileDataIO* pFile); + const uchar* GetMD4Hash() const { return m_abyMD4Hash; } + + //******************** AICH Related + const CAICHHash& GetAICHHash() const { ASSERT(m_bHasValidAICHHash); return m_AICHFileHash; } + void SetAICHHash(const CAICHHash& Hash); + bool HasAICHHash() const { return m_bHasValidAICHHash; } + void ClearAICHHash() { m_bHasValidAICHHash = false; } + +protected: + CFileIdentifierBase(); + CFileIdentifierBase(const CFileIdentifierBase& rFileIdentifier); + + uchar m_abyMD4Hash[16]; + CAICHHash m_AICHFileHash; + bool m_bHasValidAICHHash; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////// +// CFileIdentifier +// Member of all CAbstractFiles, including hashsets +class CFileIdentifier : public CFileIdentifierBase +{ +public: + CFileIdentifier(EMFileSize& rFileSize); + CFileIdentifier(const CFileIdentifier& rFileIdentifier, EMFileSize& rFileSize); + virtual ~CFileIdentifier(); + + //******************** Common + void WriteHashSetsToPacket(CFileDataIO* pFile, bool bMD4, bool bAICH) const; // not compatible with old single md4 hashset + bool ReadHashSetsFromPacket(CFileDataIO* pFile, bool& rbMD4, bool& rbAICH); // not compatible with old single md4 hashset + virtual EMFileSize GetFileSize() const { return m_rFileSize; } + + //******************** MD4 Related + bool CalculateMD4HashByHashSet(bool bVerifyOnly, bool bDeleteOnVerifyFail = true); + bool LoadMD4HashsetFromFile(CFileDataIO* file, bool bVerifyExistingHash); + void WriteMD4HashsetToFile(CFileDataIO* pFile) const; + + bool SetMD4HashSet(const CArray& aHashset); + uchar* GetMD4PartHash(UINT part) const; + void DeleteMD4Hashset(); + + // nr. of part hashs according the file size wrt ED2K protocol + uint16 GetTheoreticalMD4PartHashCount() const; /* prev: GetED2KPartHashCount()*/ + uint16 GetAvailableMD4PartHashCount() const { return (uint16)m_aMD4HashSet.GetCount(); } /* prev: GetHashCount() */ + bool HasExpectedMD4HashCount() const { return GetTheoreticalMD4PartHashCount() == GetAvailableMD4PartHashCount(); } + + CArray& GetRawMD4HashSet() { return m_aMD4HashSet; } + + //******************** AICH Related + bool LoadAICHHashsetFromFile(CFileDataIO* pFile, bool bVerify = true); // only set verify to false if you call VerifyAICHHashSet yourself immediately after + void WriteAICHHashsetToFile(CFileDataIO* pFile) const; + + bool SetAICHHashSet(const CAICHRecoveryHashSet& rSourceHashSet); + bool SetAICHHashSet(const CFileIdentifier& rSourceHashSet); + + bool VerifyAICHHashSet(); + uint16 GetTheoreticalAICHPartHashCount() const; + uint16 GetAvailableAICHPartHashCount() const { return (uint16)m_aAICHPartHashSet.GetCount(); } + bool HasExpectedAICHHashCount() const { return GetTheoreticalAICHPartHashCount() == GetAvailableAICHPartHashCount(); } + + const CArray& GetRawAICHHashSet() const { return m_aAICHPartHashSet; } +private: + EMFileSize& m_rFileSize; + CArray m_aMD4HashSet; + CArray m_aAICHPartHashSet; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////// +// CFileIdentifierSA +// Stand alone file identifier, for creating out of protocol packages, comparing and stand-alone storing, exluding hashsets +class CFileIdentifierSA : public CFileIdentifierBase +{ +public: + CFileIdentifierSA(const uchar* pucFileHash, EMFileSize FileSize, const CAICHHash& rHash, bool bAICHHashValid); + CFileIdentifierSA(); + + virtual ~CFileIdentifierSA() { } + virtual EMFileSize GetFileSize() const { return m_nFileSize; } + + bool ReadIdentifier(CFileDataIO* pFile, bool bKadValidWithoutMd4 = false); + +private: + EMFileSize m_nFileSize; +}; + +// Compare Helper for lists +__inline int CompareAICHHash(const CFileIdentifier& ident1,const CFileIdentifier& ident2, bool bSortAscending) +{ + if (ident1.HasAICHHash()) + { + if (ident2.HasAICHHash()) + return memcmp(ident1.GetAICHHash().GetRawHashC(),ident2.GetAICHHash().GetRawHashC(), CAICHHash::GetHashSize()); + else + return bSortAscending ? 1 : -1; + } + else if (ident2.HasAICHHash()) + return bSortAscending ? -1 : 1; + else + return 0; +} diff --git a/FileInfoDialog.cpp b/FileInfoDialog.cpp new file mode 100644 index 00000000..ab2e5949 --- /dev/null +++ b/FileInfoDialog.cpp @@ -0,0 +1,2297 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "eMule.h" +#include "FileInfoDialog.h" +#include "OtherFunctions.h" +#include "MediaInfo.h" +#include "PartFile.h" +#include "Preferences.h" +#include "UserMsgs.h" +#include "SplitterControl.h" + +// id3lib +#pragma warning(disable:4100) // unreferenced formal parameter +#include +#include +#pragma warning(default:4100) // unreferenced formal parameter + +// MediaInfoDLL +/** @brief Kinds of Stream */ +typedef enum _stream_t +{ + Stream_General, + Stream_Video, + Stream_Audio, + Stream_Text, + Stream_Chapters, + Stream_Image, + Stream_Max +} stream_t_C; + +/** @brief Kinds of Info */ +typedef enum _info_t +{ + Info_Name, + Info_Text, + Info_Measure, + Info_Options, + Info_Name_Text, + Info_Measure_Text, + Info_Info, + Info_HowTo, + Info_Max +} info_t_C; + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +///////////////////////////////////////////////////////////////////////////// +// SMediaInfoThreadResult + +struct SMediaInfoThreadResult +{ + ~SMediaInfoThreadResult() { + delete paMediaInfo; + } + CArray* paMediaInfo; + CStringA strInfo; +}; + +///////////////////////////////////////////////////////////////////////////// +// CGetMediaInfoThread + +class CGetMediaInfoThread : public CWinThread +{ + DECLARE_DYNCREATE(CGetMediaInfoThread) + +protected: + CGetMediaInfoThread() + { + m_hWndOwner = NULL; + } + +public: + virtual BOOL InitInstance(); + virtual int Run(); + void SetValues(HWND hWnd, const CSimpleArray* paFiles, HFONT hFont) + { + m_hWndOwner = hWnd; + for (int i = 0; i < paFiles->GetSize(); i++) + m_aFiles.Add(STATIC_DOWNCAST(CShareableFile, (*paFiles)[i])); + m_hFont = hFont; + } + +private: + bool GetMediaInfo(HWND hWndOwner, const CShareableFile* pFile, SMediaInfo* mi, bool bSingleFile); + void WarnAboutWrongFileExtension(SMediaInfo* mi, LPCTSTR pszFileName, LPCTSTR pszExtensions); + + HWND m_hWndOwner; + CSimpleArray m_aFiles; + HFONT m_hFont; +}; + + +///////////////////////////////////////////////////////////////////////////// +// CMediaInfoDLL + +class CMediaInfoDLL +{ +public: + CMediaInfoDLL() + { + m_bInitialized = FALSE; + m_hLib = NULL; + m_ullVersion = 0; + // MediaInfoLib - v0.4.0.1 + m_pfnMediaInfo4_Open = NULL; + m_pfnMediaInfo4_Close = NULL; + m_pfnMediaInfo4_Get = NULL; + m_pfnMediaInfo4_Count_Get = NULL; + // MediaInfoLib - v0.5 - v0.6.1 + m_pfnMediaInfo5_Open = NULL; + // MediaInfoLib - v0.7+ + m_pfnMediaInfo_New = NULL; + m_pfnMediaInfo_Delete = NULL; + m_pfnMediaInfo_Open = NULL; + m_pfnMediaInfo_Close = NULL; + m_pfnMediaInfo_Get = NULL; + m_pfnMediaInfo_Count_Get = NULL; + } + ~CMediaInfoDLL() + { + if (m_hLib) + FreeLibrary(m_hLib); + } + + BOOL Initialize() + { + if (!m_bInitialized) + { + m_bInitialized = TRUE; + + CString strPath = theApp.GetProfileString(_T("eMule"), _T("MediaInfo_MediaInfoDllPath"), _T("MEDIAINFO.DLL")); + if (strPath == _T("")) + return false; + m_hLib = LoadLibrary(strPath); + if (m_hLib == NULL) + { + CRegKey key; + if (key.Open(HKEY_CURRENT_USER, _T("Software\\MediaInfo"), KEY_READ) == ERROR_SUCCESS) + { + TCHAR szPath[MAX_PATH]; + ULONG ulChars = _countof(szPath); + if (key.QueryStringValue(_T("Path"), szPath, &ulChars) == ERROR_SUCCESS) + { + LPTSTR pszResult = PathCombine(strPath.GetBuffer(MAX_PATH), szPath, _T("MEDIAINFO.DLL")); + strPath.ReleaseBuffer(); + if (pszResult) + m_hLib = LoadLibrary(strPath); + } + } + } + if (m_hLib == NULL) + { + CString strProgramFiles = ShellGetFolderPath(CSIDL_PROGRAM_FILES); + if (!strProgramFiles.IsEmpty()) + { + LPTSTR pszResult = PathCombine(strPath.GetBuffer(MAX_PATH), strProgramFiles, _T("MediaInfo\\MEDIAINFO.DLL")); + strPath.ReleaseBuffer(); + if (pszResult) + m_hLib = LoadLibrary(strPath); + } + } + if (m_hLib != NULL) + { + ULONGLONG ullVersion = GetModuleVersion(m_hLib); + if (ullVersion == 0) // MediaInfoLib - v0.4.0.1 does not have a Win32 version info resource record + { + char* (__stdcall *fpMediaInfo4_Info_Version)(); + (FARPROC &)fpMediaInfo4_Info_Version = GetProcAddress(m_hLib, "MediaInfo_Info_Version"); + if (fpMediaInfo4_Info_Version) + { + char* pszVersion = (*fpMediaInfo4_Info_Version)(); + if (pszVersion && strcmp(pszVersion, "MediaInfoLib - v0.4.0.1 - http://mediainfo.sourceforge.net") == 0) + { + (FARPROC &)m_pfnMediaInfo4_Open = GetProcAddress(m_hLib, "MediaInfo_Open"); + (FARPROC &)m_pfnMediaInfo4_Close = GetProcAddress(m_hLib, "MediaInfo_Close"); + (FARPROC &)m_pfnMediaInfo4_Get = GetProcAddress(m_hLib, "MediaInfo_Get"); + (FARPROC &)m_pfnMediaInfo4_Count_Get = GetProcAddress(m_hLib, "MediaInfo_Count_Get"); + if (m_pfnMediaInfo4_Open && m_pfnMediaInfo4_Close && m_pfnMediaInfo4_Get) { + m_ullVersion = MAKEDLLVERULL(0, 4, 0, 1); + return TRUE; + } + m_pfnMediaInfo4_Open = NULL; + m_pfnMediaInfo4_Close = NULL; + m_pfnMediaInfo4_Get = NULL; + m_pfnMediaInfo4_Count_Get = NULL; + } + } + } + // Note from MediaInfo developer + // ----------------------------- + // Note : versioning method, for people who develop with LoadLibrary method + // - if one of 2 first numbers change, there is no guaranties that the DLL is compatible with old one + // - if one of 2 last numbers change, there is a garanty that the DLL is compatible with old one. + // So you should test the version of the DLL, and if one of the 2 first numbers change, not load it. + // --- + // eMule currently handles v0.5.1.0, v0.6.0.0, v0.6.1.0 + else if (ullVersion >= MAKEDLLVERULL(0, 5, 0, 0) && ullVersion < MAKEDLLVERULL(0, 7, 0, 0)) + { + // Don't use 'MediaInfo_Info_Version' with version v0.5+. This function is exported, + // can be called, but does not return a valid version string.. + + (FARPROC &)m_pfnMediaInfo5_Open = GetProcAddress(m_hLib, "MediaInfo_Open"); + (FARPROC &)m_pfnMediaInfo_Close = GetProcAddress(m_hLib, "MediaInfo_Close"); + (FARPROC &)m_pfnMediaInfo_Get = GetProcAddress(m_hLib, "MediaInfo_Get"); + (FARPROC &)m_pfnMediaInfo_Count_Get = GetProcAddress(m_hLib, "MediaInfo_Count_Get"); + if (m_pfnMediaInfo5_Open && m_pfnMediaInfo_Close && m_pfnMediaInfo_Get) { + m_ullVersion = ullVersion; + return TRUE; + } + m_pfnMediaInfo5_Open = NULL; + m_pfnMediaInfo_Close = NULL; + m_pfnMediaInfo_Get = NULL; + m_pfnMediaInfo_Count_Get = NULL; + } + else if (ullVersion >= MAKEDLLVERULL(0, 7, 0, 0) && ullVersion < MAKEDLLVERULL(0, 8, 0, 0)) + { + (FARPROC &)m_pfnMediaInfo_New = GetProcAddress(m_hLib, "MediaInfo_New"); + (FARPROC &)m_pfnMediaInfo_Delete = GetProcAddress(m_hLib, "MediaInfo_Delete"); + (FARPROC &)m_pfnMediaInfo_Open = GetProcAddress(m_hLib, "MediaInfo_Open"); + (FARPROC &)m_pfnMediaInfo_Close = GetProcAddress(m_hLib, "MediaInfo_Close"); + (FARPROC &)m_pfnMediaInfo_Get = GetProcAddress(m_hLib, "MediaInfo_Get"); + (FARPROC &)m_pfnMediaInfo_Count_Get = GetProcAddress(m_hLib, "MediaInfo_Count_Get"); + if (m_pfnMediaInfo_New && m_pfnMediaInfo_Delete && m_pfnMediaInfo_Open && m_pfnMediaInfo_Close && m_pfnMediaInfo_Get) { + m_ullVersion = ullVersion; + return TRUE; + } + m_pfnMediaInfo_New = NULL; + m_pfnMediaInfo_Delete = NULL; + m_pfnMediaInfo_Open = NULL; + m_pfnMediaInfo_Close = NULL; + m_pfnMediaInfo_Get = NULL; + m_pfnMediaInfo_Count_Get = NULL; + } + FreeLibrary(m_hLib); + m_hLib = NULL; + } + } + return m_hLib != NULL; + } + + ULONGLONG GetVersion() const + { + return m_ullVersion; + } + + void* Open(LPCTSTR File) + { + if (m_pfnMediaInfo4_Open) + return (*m_pfnMediaInfo4_Open)(CT2A(File)); + else if (m_pfnMediaInfo5_Open) + return (*m_pfnMediaInfo5_Open)(File); + else if (m_pfnMediaInfo_New) { + void* Handle = (*m_pfnMediaInfo_New)(); + if (Handle) + (*m_pfnMediaInfo_Open)(Handle, File); + return Handle; + } + return NULL; + } + + void Close(void* Handle) + { + if (m_pfnMediaInfo_Delete) + (*m_pfnMediaInfo_Delete)(Handle); // File is automaticly closed + else if (m_pfnMediaInfo4_Close) + (*m_pfnMediaInfo4_Close)(Handle); + else if (m_pfnMediaInfo_Close) + (*m_pfnMediaInfo_Close)(Handle); + } + + CString Get(void* Handle, stream_t_C StreamKind, int StreamNumber, LPCTSTR Parameter, info_t_C KindOfInfo, info_t_C KindOfSearch) + { + if (m_pfnMediaInfo4_Get) + return CString((*m_pfnMediaInfo4_Get)(Handle, StreamKind, StreamNumber, CT2A(Parameter), KindOfInfo, KindOfSearch)); + else if (m_pfnMediaInfo_Get) { + CString strNewParameter(Parameter); + if (m_ullVersion >= MAKEDLLVERULL(0, 7, 1, 0)) { + // Convert old tags to new tags + strNewParameter.Replace(_T('_'), _T('/')); + + // Workaround for a bug in MediaInfoLib + if (strNewParameter == _T("Channels")) + strNewParameter = _T("Channel(s)"); + } + return (*m_pfnMediaInfo_Get)(Handle, StreamKind, StreamNumber, strNewParameter, KindOfInfo, KindOfSearch); + } + return _T(""); + } + + int Count_Get(void* Handle, stream_t_C StreamKind, int StreamNumber) + { + if (m_pfnMediaInfo4_Get) + return (*m_pfnMediaInfo4_Count_Get)(Handle, StreamKind, StreamNumber); + else if (m_pfnMediaInfo_Count_Get) + return (*m_pfnMediaInfo_Count_Get)(Handle, StreamKind, StreamNumber); + return 0; + } + +protected: + ULONGLONG m_ullVersion; + BOOL m_bInitialized; + HINSTANCE m_hLib; + + // MediaInfoLib - v0.4.0.1 + void* (__stdcall *m_pfnMediaInfo4_Open)(char* File) throw(...); + void (__stdcall *m_pfnMediaInfo4_Close)(void* Handle) throw(...); + char* (__stdcall *m_pfnMediaInfo4_Get)(void* Handle, stream_t_C StreamKind, int StreamNumber, char* Parameter, info_t_C KindOfInfo, info_t_C KindOfSearch) throw(...); + int (__stdcall *m_pfnMediaInfo4_Count_Get)(void* Handle, stream_t_C StreamKind, int StreamNumber) throw(...); + + // MediaInfoLib - v0.5+ + void* (__stdcall *m_pfnMediaInfo5_Open)(const wchar_t* File) throw(...); + void (__stdcall *m_pfnMediaInfo_Close)(void* Handle) throw(...); + const wchar_t* (__stdcall *m_pfnMediaInfo_Get)(void* Handle, stream_t_C StreamKind, int StreamNumber, const wchar_t* Parameter, info_t_C KindOfInfo, info_t_C KindOfSearch) throw(...); + int (__stdcall *m_pfnMediaInfo_Count_Get)(void* Handle, stream_t_C StreamKind, int StreamNumber) throw(...); + + // MediaInfoLib - v0.7+ + int (__stdcall *m_pfnMediaInfo_Open)(void* Handle, const wchar_t* File) throw(...); + void* (__stdcall *m_pfnMediaInfo_New)() throw(...); + void (__stdcall *m_pfnMediaInfo_Delete)(void* Handle) throw(...); +}; + +CMediaInfoDLL theMediaInfoDLL; + + +///////////////////////////////////////////////////////////////////////////// +// CFileInfoDialog dialog + +IMPLEMENT_DYNAMIC(CFileInfoDialog, CResizablePage) + +BEGIN_MESSAGE_MAP(CFileInfoDialog, CResizablePage) + ON_MESSAGE(UM_MEDIA_INFO_RESULT, OnMediaInfoResult) + ON_MESSAGE(UM_DATA_CHANGED, OnDataChanged) + ON_WM_DESTROY() +END_MESSAGE_MAP() + +CFileInfoDialog::CFileInfoDialog() + : CResizablePage(CFileInfoDialog::IDD, 0) +{ + m_paFiles = NULL; + m_bDataChanged = false; + m_strCaption = GetResString(IDS_CONTENT_INFO); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; + m_bReducedDlg = false; +} + +CFileInfoDialog::~CFileInfoDialog() +{ +} + +BOOL CFileInfoDialog::OnInitDialog() +{ + CWaitCursor curWait; // we may get quite busy here.. + ReplaceRichEditCtrl(GetDlgItem(IDC_FULL_FILE_INFO), this, GetDlgItem(IDC_FD_XI1)->GetFont()); + CResizablePage::OnInitDialog(); + InitWindowStyles(this); + + if (!m_bReducedDlg) + { + AddAnchor(IDC_FILESIZE, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FULL_FILE_INFO, TOP_LEFT, BOTTOM_RIGHT); + + m_fi.LimitText(afxIsWin95() ? 0xFFFF : 0x7FFFFFFF); + m_fi.SendMessage(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(3, 3)); + m_fi.SetAutoURLDetect(); + m_fi.SetEventMask(m_fi.GetEventMask() | ENM_LINK); + } + else + { + GetDlgItem(IDC_FILESIZE)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_FULL_FILE_INFO)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_FD_XI1)->ShowWindow(SW_HIDE); + + CRect rc; + GetDlgItem(IDC_FILESIZE)->GetWindowRect(rc); + int nDelta = rc.Height(); + + CSplitterControl::ChangeHeight(GetDlgItem(IDC_GENERAL), -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_LENGTH), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FORMAT), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI3), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_VCODEC), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_VBITRATE), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_VWIDTH), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_VASPECT), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_VFPS), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI6), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI8), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI10), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI12), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_STATIC_LANGUAGE), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI4), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_ACODEC), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_ABITRATE), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_ACHANNEL), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_ASAMPLERATE), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_ALANGUAGE), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI5), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI9), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI7), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI14), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI13), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_FD_XI2), 0, -nDelta); + CSplitterControl::ChangePos(GetDlgItem(IDC_STATICFI), 0, -nDelta); + } + + // General Group + AddAnchor(IDC_GENERAL, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_LENGTH, TOP_LEFT, TOP_RIGHT); + AddAnchor(IDC_FORMAT, TOP_LEFT, TOP_RIGHT); + + // Video Group + AddAnchor(IDC_FD_XI3, TOP_LEFT, TOP_CENTER); + AddAnchor(IDC_VCODEC, TOP_LEFT, TOP_CENTER); + AddAnchor(IDC_VBITRATE, TOP_LEFT, TOP_CENTER); + AddAnchor(IDC_VWIDTH, TOP_LEFT, TOP_CENTER); + AddAnchor(IDC_VASPECT, TOP_LEFT, TOP_CENTER); + AddAnchor(IDC_VFPS, TOP_LEFT, TOP_CENTER); + + // Audio Group - Labels + AddAnchor(IDC_FD_XI6, TOP_CENTER, TOP_CENTER); + AddAnchor(IDC_FD_XI8, TOP_CENTER, TOP_CENTER); + AddAnchor(IDC_FD_XI10, TOP_CENTER, TOP_CENTER); + AddAnchor(IDC_FD_XI12, TOP_CENTER, TOP_CENTER); + AddAnchor(IDC_STATIC_LANGUAGE, TOP_CENTER, TOP_CENTER); + + // Audio Group - Frame and Values + AddAnchor(IDC_FD_XI4, TOP_CENTER, TOP_RIGHT); + AddAnchor(IDC_ACODEC, TOP_CENTER, TOP_RIGHT); + AddAnchor(IDC_ABITRATE, TOP_CENTER, TOP_RIGHT); + AddAnchor(IDC_ACHANNEL, TOP_CENTER, TOP_RIGHT); + AddAnchor(IDC_ASAMPLERATE, TOP_CENTER, TOP_RIGHT); + AddAnchor(IDC_ALANGUAGE, TOP_CENTER, TOP_RIGHT); + + + + CResizablePage::UpdateData(FALSE); + Localize(); + return TRUE; +} + +void CFileInfoDialog::OnDestroy() +{ + // This property sheet's window may get destroyed and re-created several times although + // the corresponding C++ class is kept -> explicitly reset ResizeableLib state + RemoveAllAnchors(); + + CResizablePage::OnDestroy(); +} + +BOOL CFileInfoDialog::OnSetActive() +{ + if (!CResizablePage::OnSetActive()) + return FALSE; + if (m_bDataChanged) + { + CString strWait = GetResString(IDS_FSTAT_WAITING); + SetDlgItemText(IDC_FORMAT, strWait); + SetDlgItemText(IDC_FILESIZE, strWait); + SetDlgItemText(IDC_LENGTH, strWait); + SetDlgItemText(IDC_VCODEC, strWait); + SetDlgItemText(IDC_VBITRATE, strWait); + SetDlgItemText(IDC_VWIDTH, strWait); + SetDlgItemText(IDC_VASPECT, strWait); + SetDlgItemText(IDC_VFPS, strWait); + SetDlgItemText(IDC_ACODEC, strWait); + SetDlgItemText(IDC_ACHANNEL, strWait); + SetDlgItemText(IDC_ASAMPLERATE, strWait); + SetDlgItemText(IDC_ABITRATE, strWait); + SetDlgItemText(IDC_ALANGUAGE, strWait); + SetDlgItemText(IDC_FULL_FILE_INFO, strWait); + + CGetMediaInfoThread* pThread = (CGetMediaInfoThread*)AfxBeginThread(RUNTIME_CLASS(CGetMediaInfoThread), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); + if (pThread) + { + pThread->SetValues(m_hWnd, m_paFiles, (HFONT)GetDlgItem(IDC_FD_XI1)->GetFont()->m_hObject); + pThread->ResumeThread(); + } + m_bDataChanged = false; + } + return TRUE; +} + +LRESULT CFileInfoDialog::OnDataChanged(WPARAM, LPARAM) +{ + m_bDataChanged = true; + return 1; +} + +IMPLEMENT_DYNCREATE(CGetMediaInfoThread, CWinThread) + +BOOL CGetMediaInfoThread::InitInstance() +{ + DbgSetThreadName("GetMediaInfo"); + InitThreadLocale(); + return TRUE; +} + +int CGetMediaInfoThread::Run() +{ + CoInitialize(NULL); + + HWND hwndRE = CreateWindow(RICHEDIT_CLASS, _T(""), ES_MULTILINE | ES_READONLY | WS_DISABLED, 0, 0, 200, 200, NULL, NULL, NULL, NULL); + ASSERT( hwndRE ); + if (hwndRE && m_hFont) + ::SendMessage(hwndRE, WM_SETFONT, (WPARAM)m_hFont, 0); + + CArray* paMediaInfo = new CArray; + try + { + CRichEditStream re; + re.Attach(hwndRE); + re.LimitText(afxIsWin95() ? 0xFFFF : 0x7FFFFFFF); + PARAFORMAT pf = {0}; + pf.cbSize = sizeof pf; + if (re.GetParaFormat(pf)) { + pf.dwMask |= PFM_TABSTOPS; + pf.cTabCount = 1; + pf.rgxTabs[0] = 3000; + re.SetParaFormat(pf); + } + re.Detach(); + + for (int i = 0; i < m_aFiles.GetSize(); i++) + { + SMediaInfo mi; + mi.bOutputFileName = m_aFiles.GetSize() > 1; + mi.strFileName = m_aFiles[i]->GetFileName(); + mi.strInfo.Attach(hwndRE); + mi.strInfo.InitColors(); + if (IsWindow(m_hWndOwner) && GetMediaInfo(m_hWndOwner, m_aFiles[i], &mi, m_aFiles.GetSize() == 1)) { + mi.strInfo.Detach(); + paMediaInfo->Add(mi); + } + else { + mi.strInfo.Detach(); + delete paMediaInfo; + paMediaInfo = NULL; + break; + } + } + } + catch(...) + { + ASSERT(0); + } + + SMediaInfoThreadResult* pThreadRes = new SMediaInfoThreadResult; + pThreadRes->paMediaInfo = paMediaInfo; + CRichEditStream re; + re.Attach(hwndRE); + re.GetRTFText(pThreadRes->strInfo); + re.Detach(); + VERIFY( DestroyWindow(hwndRE) ); + + // Usage of 'PostMessage': The idea is to post a message to the window in that other + // thread and never deadlock (because of the post). This is safe, but leads to the problem + // that we may create memory leaks in case the target window is currently in the process + // of getting destroyed! E.g. if the target window gets destroyed after we put the message + // into the queue, we have no chance of determining that and the memory wouldn't get freed. + //if (!IsWindow(m_hWndOwner) || !PostMessage(m_hWndOwner, UM_MEDIA_INFO_RESULT, 0, (LPARAM)pThreadRes)) + // delete pThreadRes; + + // Usage of 'SendMessage': Using 'SendMessage' seems to be dangerous because of potential + // deadlocks. Basically it depends on what the target thread/window is currently doing + // whether there is a risk for a deadlock. However, even with extensive stress testing + // there does not show any problem. The worse thing which can happen, is that we call + // 'SendMessage', then the target window gets destroyed (while we are still waiting in + // 'SendMessage') and would get blocked. Though, this does not happen, it seems that Windows + // is catching that case internally and lets our 'SendMessage' call return (with a result + // of '0'). If that happened, the 'IsWindow(m_hWndOwner)' returns FALSE, which positively + // indicates that the target window was destroyed while we were waiting in 'SendMessage'. + // So, everything should be fine (with that special scenario) with using 'SendMessage'. + // Let's be brave. :) + if (!IsWindow(m_hWndOwner) || !SendMessage(m_hWndOwner, UM_MEDIA_INFO_RESULT, 0, (LPARAM)pThreadRes)) + delete pThreadRes; + + CoUninitialize(); + return 0; +} + +LRESULT CFileInfoDialog::OnMediaInfoResult(WPARAM, LPARAM lParam) +{ + SetDlgItemText(IDC_FD_XI3, GetResString(IDS_VIDEO)); + SetDlgItemText(IDC_FD_XI4, GetResString(IDS_AUDIO)); + SetDlgItemText(IDC_FORMAT, _T("-")); + SetDlgItemText(IDC_FILESIZE, _T("-")); + SetDlgItemText(IDC_LENGTH, _T("-")); + SetDlgItemText(IDC_VCODEC, _T("-")); + SetDlgItemText(IDC_VBITRATE, _T("-")); + SetDlgItemText(IDC_VWIDTH, _T("-")); + SetDlgItemText(IDC_VASPECT, _T("-")); + SetDlgItemText(IDC_VFPS, _T("-")); + SetDlgItemText(IDC_ACODEC, _T("-")); + SetDlgItemText(IDC_ACHANNEL, _T("-")); + SetDlgItemText(IDC_ASAMPLERATE, _T("-")); + SetDlgItemText(IDC_ABITRATE, _T("-")); + SetDlgItemText(IDC_ALANGUAGE, _T("-")); + SetDlgItemText(IDC_FULL_FILE_INFO, _T("")); + + SMediaInfoThreadResult* pThreadRes = (SMediaInfoThreadResult*)lParam; + if (pThreadRes == NULL) + return 1; + CArray* paMediaInfo = pThreadRes->paMediaInfo; + if (paMediaInfo == NULL) { + delete pThreadRes; + return 1; + } + + if (paMediaInfo->GetSize() != m_paFiles->GetSize()) + { + SetDlgItemText(IDC_FORMAT, _T("")); + SetDlgItemText(IDC_FILESIZE, _T("")); + SetDlgItemText(IDC_LENGTH, _T("")); + SetDlgItemText(IDC_VCODEC, _T("")); + SetDlgItemText(IDC_VBITRATE, _T("")); + SetDlgItemText(IDC_VWIDTH, _T("")); + SetDlgItemText(IDC_VASPECT, _T("")); + SetDlgItemText(IDC_VFPS, _T("")); + SetDlgItemText(IDC_ACODEC, _T("")); + SetDlgItemText(IDC_ACHANNEL, _T("")); + SetDlgItemText(IDC_ASAMPLERATE, _T("")); + SetDlgItemText(IDC_ABITRATE, _T("")); + SetDlgItemText(IDC_ALANGUAGE, _T("")); + SetDlgItemText(IDC_FULL_FILE_INFO, _T("")); + delete pThreadRes; + return 1; + } + + uint64 uTotalFileSize = 0; + SMediaInfo ami; + bool bDiffVideoStreamCount = false; + bool bDiffVideoCompression = false; + bool bDiffVideoWidth = false; + bool bDiffVideoHeight = false; + bool bDiffVideoFrameRate = false; + bool bDiffVideoBitRate = false; + bool bDiffVideoAspectRatio = false; + bool bDiffAudioStreamCount = false; + bool bDiffAudioCompression = false; + bool bDiffAudioChannels = false; + bool bDiffAudioSamplesPerSec = false; + bool bDiffAudioAvgBytesPerSec = false; + bool bDiffAudioLanguage = false; + for (int i = 0; i < paMediaInfo->GetSize(); i++) + { + SMediaInfo& mi = paMediaInfo->GetAt(i); + + mi.InitFileLength(); + uTotalFileSize += (uint64)mi.ulFileSize; + if (i == 0) + { + ami = mi; + } + else + { + if (ami.strFileFormat != mi.strFileFormat) + ami.strFileFormat.Empty(); + + if (ami.strMimeType != mi.strMimeType) + ami.strMimeType.Empty(); + + ami.fFileLengthSec += mi.fFileLengthSec; + if (mi.bFileLengthEstimated) + ami.bFileLengthEstimated = true; + + ami.fVideoLengthSec += mi.fVideoLengthSec; + if (mi.bVideoLengthEstimated) + ami.bVideoLengthEstimated = true; + if (ami.iVideoStreams == 0 && mi.iVideoStreams > 0 || ami.iVideoStreams > 0 && mi.iVideoStreams == 0) + { + if (ami.iVideoStreams == 0) + ami.iVideoStreams = mi.iVideoStreams; + bDiffVideoStreamCount = true; + bDiffVideoCompression = true; + bDiffVideoWidth = true; + bDiffVideoHeight = true; + bDiffVideoFrameRate = true; + bDiffVideoBitRate = true; + bDiffVideoAspectRatio = true; + } + else + { + if (ami.iVideoStreams != mi.iVideoStreams) + bDiffVideoStreamCount = true; + if (ami.strVideoFormat != mi.strVideoFormat) + bDiffVideoCompression = true; + if (ami.video.bmiHeader.biWidth != mi.video.bmiHeader.biWidth) + bDiffVideoWidth = true; + if (ami.video.bmiHeader.biHeight != mi.video.bmiHeader.biHeight) + bDiffVideoHeight = true; + if (ami.fVideoFrameRate != mi.fVideoFrameRate) + bDiffVideoFrameRate = true; + if (ami.video.dwBitRate != mi.video.dwBitRate) + bDiffVideoBitRate = true; + if (ami.fVideoAspectRatio != mi.fVideoAspectRatio) + bDiffVideoAspectRatio = true; + } + + ami.fAudioLengthSec += mi.fAudioLengthSec; + if (mi.bAudioLengthEstimated) + ami.bAudioLengthEstimated = true; + if (ami.iAudioStreams == 0 && mi.iAudioStreams > 0 || ami.iAudioStreams > 0 && mi.iAudioStreams == 0) + { + if (ami.iAudioStreams == 0) + ami.iAudioStreams = mi.iAudioStreams; + bDiffAudioStreamCount = true; + bDiffAudioCompression = true; + bDiffAudioChannels = true; + bDiffAudioSamplesPerSec = true; + bDiffAudioAvgBytesPerSec = true; + bDiffAudioLanguage = true; + } + else + { + if (ami.iAudioStreams != mi.iAudioStreams) + bDiffAudioStreamCount = true; + if (ami.strAudioFormat != mi.strAudioFormat) + bDiffAudioCompression = true; + if (ami.audio.nChannels != mi.audio.nChannels) + bDiffAudioChannels = true; + if (ami.audio.nSamplesPerSec != mi.audio.nSamplesPerSec) + bDiffAudioSamplesPerSec = true; + if (ami.audio.nAvgBytesPerSec != mi.audio.nAvgBytesPerSec) + bDiffAudioAvgBytesPerSec = true; + if (ami.strAudioLanguage.CompareNoCase(mi.strAudioLanguage) != 0) + bDiffAudioLanguage = true; + } + } + } + + CString buffer; + + buffer = ami.strFileFormat; + if (!ami.strMimeType.IsEmpty()) + { + if (!buffer.IsEmpty()) + buffer += _T("; MIME type="); + buffer.AppendFormat(_T("%s"), ami.strMimeType); + } + SetDlgItemText(IDC_FORMAT, buffer); + + if (uTotalFileSize) + SetDlgItemText(IDC_FILESIZE, CastItoXBytes(uTotalFileSize, false, false)); + + if (ami.fFileLengthSec) { + CString strLength(CastSecondsToHM((time_t)ami.fFileLengthSec)); + if (ami.bFileLengthEstimated) + strLength += _T(" (") + GetResString(IDS_ESTIMATED)+ _T(")"); + SetDlgItemText(IDC_LENGTH, strLength); + } + + if (ami.iVideoStreams) + { + if (!bDiffVideoStreamCount && ami.iVideoStreams > 1) + SetDlgItemText(IDC_FD_XI3, GetResString(IDS_VIDEO) + _T(" #1")); + else + SetDlgItemText(IDC_FD_XI3, GetResString(IDS_VIDEO)); + + if (!bDiffVideoCompression && !ami.strVideoFormat.IsEmpty()) + SetDlgItemText(IDC_VCODEC, ami.strVideoFormat); + else + SetDlgItemText(IDC_VCODEC, _T("")); + + if (!bDiffVideoBitRate && ami.video.dwBitRate) + { + if (ami.video.dwBitRate == (DWORD)-1) + buffer = _T("Variable"); + else + buffer.Format(_T("%u %s"), (ami.video.dwBitRate + 500) / 1000, GetResString(IDS_KBITSSEC)); + SetDlgItemText(IDC_VBITRATE, buffer); + } + else + SetDlgItemText(IDC_VBITRATE, _T("")); + + buffer.Empty(); + if (!bDiffVideoWidth && ami.video.bmiHeader.biWidth && !bDiffVideoHeight && ami.video.bmiHeader.biHeight) + { + buffer.AppendFormat(_T("%u x %u"), abs(ami.video.bmiHeader.biWidth), abs(ami.video.bmiHeader.biHeight)); + SetDlgItemText(IDC_VWIDTH, buffer); + } + else + SetDlgItemText(IDC_VWIDTH, _T("")); + + if (!bDiffVideoAspectRatio && ami.fVideoAspectRatio) + { + buffer.Format(_T("%.3f"), ami.fVideoAspectRatio); + CString strAR = GetKnownAspectRatioDisplayString((float)ami.fVideoAspectRatio); + if (!strAR.IsEmpty()) + buffer.AppendFormat(_T(" (%s)"), strAR); + SetDlgItemText(IDC_VASPECT, buffer); + } + else + SetDlgItemText(IDC_VASPECT, _T("")); + + if (!bDiffVideoFrameRate && ami.fVideoFrameRate) + { + buffer.Format(_T("%.2f"), ami.fVideoFrameRate); + SetDlgItemText(IDC_VFPS, buffer); + } + else + SetDlgItemText(IDC_VFPS, _T("")); + } + + if (ami.iAudioStreams) + { + if (!bDiffAudioStreamCount && ami.iAudioStreams > 1) + SetDlgItemText(IDC_FD_XI4, GetResString(IDS_AUDIO) + _T(" #1")); + else + SetDlgItemText(IDC_FD_XI4, GetResString(IDS_AUDIO)); + + if (!bDiffAudioCompression && !ami.strAudioFormat.IsEmpty()) + SetDlgItemText(IDC_ACODEC, ami.strAudioFormat); + else + SetDlgItemText(IDC_ACODEC, _T("")); + + if (!bDiffAudioChannels && ami.audio.nChannels) + { + switch (ami.audio.nChannels) + { + case 1: + SetDlgItemText(IDC_ACHANNEL, _T("1 (Mono)")); + break; + case 2: + SetDlgItemText(IDC_ACHANNEL, _T("2 (Stereo)")); + break; + case 5: + SetDlgItemText(IDC_ACHANNEL, _T("5.1 (Surround)")); + break; + default: + SetDlgItemInt(IDC_ACHANNEL, ami.audio.nChannels, FALSE); + break; + } + } + else + SetDlgItemText(IDC_ACHANNEL, _T("")); + + if (!bDiffAudioSamplesPerSec && ami.audio.nSamplesPerSec) + { + buffer.Format(_T("%.3f kHz"), ami.audio.nSamplesPerSec / 1000.0); + SetDlgItemText(IDC_ASAMPLERATE, buffer); + } + else + SetDlgItemText(IDC_ASAMPLERATE, _T("")); + + if (!bDiffAudioAvgBytesPerSec && ami.audio.nAvgBytesPerSec) + { + if (ami.audio.nAvgBytesPerSec == (DWORD)-1) + buffer = _T("Variable"); + else + buffer.Format(_T("%u %s"), (UINT)((ami.audio.nAvgBytesPerSec * 8) / 1000.0 + 0.5), GetResString(IDS_KBITSSEC)); + SetDlgItemText(IDC_ABITRATE, buffer); + } + else + SetDlgItemText(IDC_ABITRATE, _T("")); + + if (!bDiffAudioLanguage && !ami.strAudioLanguage.IsEmpty()) + SetDlgItemText(IDC_ALANGUAGE, ami.strAudioLanguage); + else + SetDlgItemText(IDC_ALANGUAGE, _T("")); + } + + if (!m_bReducedDlg) + { + m_fi.SetRTFText(pThreadRes->strInfo); + m_fi.SetSel(0, 0); + } + + delete pThreadRes; + return 1; +} + +void CGetMediaInfoThread::WarnAboutWrongFileExtension(SMediaInfo* mi, LPCTSTR pszFileName, LPCTSTR pszExtensions) +{ + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\r\n"); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfRed); + mi->strInfo.AppendFormat(GetResString(IDS_WARNING_WRONGFILEEXTENTION), pszFileName, pszExtensions); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfDef); +} + +wchar_t *ID3_GetStringW(const ID3_Frame *frame, ID3_FieldID fldName) +{ + // ID3LIB BUG: Use the Unicode support of id3lib only if the tag is already + // in Unicode format, thus avoiding couple of bugs with character conversion in + // id3lib to get triggered. + ID3_Field* fld; + if (NULL != frame && NULL != (fld = frame->GetField(fldName)) + && fld->GetEncoding() == ID3TE_UTF16) + { + unicode_t *text = NULL; + size_t nText = fld->Size(); + text = new unicode_t[nText/sizeof(unicode_t) + 1]; + fld->Get(text, nText/sizeof(unicode_t)); + text[nText/sizeof(unicode_t)] = L'\0'; + for (unsigned int i = 0; i < nText/sizeof(unicode_t); i++) + text[i] = _byteswap_ushort(text[i]); + return (wchar_t*)text; + } + + char *text = ID3_GetString(frame, fldName); + CStringW wstr(text); + delete[] text; + wchar_t *pwsz = new wchar_t[wstr.GetLength() + 1]; + wcscpy(pwsz, wstr); + return pwsz; +} + +wchar_t *ID3_GetStringW(const ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex) +{ + // Do not use 'ID3_FieldImpl::Get(unicode_t *buffer, size_t maxLength, size_t itemNum)'. + // That function is broken in id3lib (the bug is in 'GetRawUnicodeTextItem') + if (nIndex == 0) + return ID3_GetStringW(frame, fldName); + return NULL; +} + +bool CGetMediaInfoThread::GetMediaInfo(HWND hWndOwner, const CShareableFile* pFile, SMediaInfo* mi, bool bSingleFile) +{ + if (!pFile) + return false; + ASSERT( !pFile->GetFilePath().IsEmpty() ); + + bool bHasDRM = false; + if (!pFile->IsPartFile() || ((CPartFile*)pFile)->IsComplete(0, 1024, true)) { + GetMimeType(pFile->GetFilePath(), mi->strMimeType); + bHasDRM = GetDRM(pFile->GetFilePath()); + if (bHasDRM) { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\r\n"); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfRed); + mi->strInfo.AppendFormat(GetResString(IDS_MEDIAINFO_DRMWARNING), pFile->GetFileName()); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfDef); + } + } + + mi->ulFileSize = pFile->GetFileSize(); + + bool bFoundHeader = false; + if (pFile->IsPartFile()) + { + // Do *not* pass a part file which does not have the beginning of file to the following code. + // - The MP3 reading code will skip all 0-bytes from the beginning of the file and may block + // the main thread for a long time. + // + // - The RIFF reading code will not work without the file header. + // + // - Most (if not all) other code will also not work without the beginning of the file available. + if (!((CPartFile*)pFile)->IsComplete(0, 16*1024, true)) + return bFoundHeader || !mi->strMimeType.IsEmpty(); + } + + TCHAR szExt[_MAX_EXT]; + _tsplitpath(pFile->GetFileName(), NULL, NULL, NULL, szExt); + _tcslwr(szExt); + + //////////////////////////////////////////////////////////////////////////// + // Check for AVI file + // + bool bIsAVI = false; + if (theApp.GetProfileInt(_T("eMule"), _T("MediaInfo_RIFF"), 1)) + { + try + { + if (GetRIFFHeaders(pFile->GetFilePath(), mi, bIsAVI, true)) + { + if (bIsAVI && _tcscmp(szExt, _T(".avi")) != 0) + WarnAboutWrongFileExtension(mi, pFile->GetFileName(), _T("avi")); + return true; + } + } + catch(...) + { + ASSERT(0); + } + } + + if (!IsWindow(hWndOwner)) + return false; + + //////////////////////////////////////////////////////////////////////////// + // Check for RM file + // + bool bIsRM = false; + if (theApp.GetProfileInt(_T("eMule"), _T("MediaInfo_RM"), 1)) + { + try + { + if (GetRMHeaders(pFile->GetFilePath(), mi, bIsRM, true)) + { + if (bIsRM && (_tcscmp(szExt, _T(".rm")) != 0 && _tcscmp(szExt, _T(".rmvb")) != 0 && _tcscmp(szExt, _T(".ra")) != 0)) + WarnAboutWrongFileExtension(mi, pFile->GetFileName(), _T("rm rmvb ra")); + return true; + } + } + catch(...) + { + ASSERT(0); + } + } + + if (!IsWindow(hWndOwner)) + return false; + + //////////////////////////////////////////////////////////////////////////// + // Check for WM file + // +#ifdef HAVE_WMSDK_H + bool bIsWM = false; + if (theApp.GetProfileInt(_T("eMule"), _T("MediaInfo_WM"), 1)) + { + try + { + if (GetWMHeaders(pFile->GetFilePath(), mi, bIsWM, true)) + { + if (bIsWM && ( _tcscmp(szExt, _T(".asf")) != 0 + && _tcscmp(szExt, _T(".wm")) != 0 + && _tcscmp(szExt, _T(".wma")) != 0 + && _tcscmp(szExt, _T(".wmv")) != 0 + && _tcscmp(szExt, _T(".dvr-ms")) != 0)) + WarnAboutWrongFileExtension(mi, pFile->GetFileName(), _T("asf wm wma wmv dvr-ms")); + return true; + } + } + catch(...) + { + ASSERT(0); + } + } + + if (!IsWindow(hWndOwner)) + return false; +#endif//HAVE_WMSDK_H + + //////////////////////////////////////////////////////////////////////////// + // Check for MPEG Audio file + // + if (theApp.GetProfileInt(_T("eMule"), _T("MediaInfo_ID3LIB"), 1) && + (_tcscmp(szExt, _T(".mp3"))==0 || _tcscmp(szExt, _T(".mp2"))==0 || _tcscmp(szExt, _T(".mp1"))==0 || _tcscmp(szExt, _T(".mpa"))==0)) + { + try + { + // ID3LIB BUG: If there are ID3v2 _and_ ID3v1 tags available, id3lib + // destroys (actually corrupts) the Unicode strings from ID3v2 tags due to + // converting Unicode to ASCII and then convertion back from ASCII to Unicode. + // To prevent this, we force the reading of ID3v2 tags only, in case there are + // also ID3v1 tags available. + ID3_Tag myTag; + CStringA strFilePathA(pFile->GetFilePath()); + size_t id3Size = myTag.Link(strFilePathA, ID3TT_ID3V2); + if (id3Size == 0) { + myTag.Clear(); + myTag.Link(strFilePathA, ID3TT_ID3V1); + } + + const Mp3_Headerinfo* mp3info; + mp3info = myTag.GetMp3HeaderInfo(); + if (mp3info) + { + mi->strFileFormat = _T("MPEG audio"); + + mi->OutputFileName(); + if (!bSingleFile) { + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << _T("MP3 Header Info\n"); + } + + switch (mp3info->version) + { + case MPEGVERSION_2_5: + mi->strAudioFormat = _T("MPEG-2.5,"); + mi->audio.wFormatTag = WAVE_FORMAT_MPEGLAYER3; + break; + case MPEGVERSION_2: + mi->strAudioFormat = _T("MPEG-2,"); + mi->audio.wFormatTag = WAVE_FORMAT_MPEGLAYER3; + break; + case MPEGVERSION_1: + mi->strAudioFormat = _T("MPEG-1,"); + mi->audio.wFormatTag = WAVE_FORMAT_MPEGLAYER3; + break; + default: + break; + } + mi->strAudioFormat += _T(" "); + + switch (mp3info->layer) + { + case MPEGLAYER_III: + mi->strAudioFormat += _T("Layer 3"); + break; + case MPEGLAYER_II: + mi->strAudioFormat += _T("Layer 2"); + break; + case MPEGLAYER_I: + mi->strAudioFormat += _T("Layer 1"); + break; + default: + break; + } + if (!bSingleFile) + { + mi->strInfo << _T(" ") << GetResString(IDS_CODEC) << _T(":\t") << mi->strAudioFormat << _T("\n"); + mi->strInfo << _T(" ") << GetResString(IDS_BITRATE) << _T(":\t") << ((mp3info->vbr_bitrate ? mp3info->vbr_bitrate : mp3info->bitrate) + 500) / 1000 << _T(" ") << GetResString(IDS_KBITSSEC) << _T("\n"); + mi->strInfo << _T(" ") << GetResString(IDS_SAMPLERATE) << _T(":\t") << mp3info->frequency / 1000.0 << _T(" kHz\n"); + } + + mi->iAudioStreams++; + mi->audio.nAvgBytesPerSec = mp3info->vbr_bitrate ? mp3info->vbr_bitrate/8 : mp3info->bitrate/8; + mi->audio.nSamplesPerSec = mp3info->frequency; + + if (!bSingleFile) + mi->strInfo << _T(" Mode:\t"); + switch (mp3info->channelmode){ + case MP3CHANNELMODE_STEREO: + if (!bSingleFile) + mi->strInfo << _T("Stereo"); + mi->audio.nChannels = 2; + break; + case MP3CHANNELMODE_JOINT_STEREO: + if (!bSingleFile) + mi->strInfo << _T("Joint Stereo"); + mi->audio.nChannels = 2; + break; + case MP3CHANNELMODE_DUAL_CHANNEL: + if (!bSingleFile) + mi->strInfo << _T("Dual Channel"); + mi->audio.nChannels = 2; + break; + case MP3CHANNELMODE_SINGLE_CHANNEL: + if (!bSingleFile) + mi->strInfo << _T("Mono"); + mi->audio.nChannels = 1; + break; + } + if (!bSingleFile) + mi->strInfo << _T("\n"); + + // length + if (mp3info->time) + { + if (!bSingleFile) + { + CString strLength; + SecToTimeLength(mp3info->time, strLength); + mi->strInfo << _T(" ") << GetResString(IDS_LENGTH) << _T(":\t") << strLength; + if (pFile->IsPartFile()){ + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfRed); + mi->strInfo << _T(" (") + GetResString(IDS_ESTIMATED)+ _T(")"); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfDef); + } + mi->strInfo << "\n"; + } + mi->fAudioLengthSec = mp3info->time; + if (pFile->IsPartFile()) + mi->bAudioLengthEstimated = true; + } + + bFoundHeader = true; + if (mi->strMimeType.IsEmpty()) + mi->strMimeType = _T("audio/mpeg"); + } + + int iTag = 0; + ID3_Tag::Iterator* iter = myTag.CreateIterator(); + const ID3_Frame* frame; + while ((frame = iter->GetNext()) != NULL) + { + if (iTag == 0) + { + if (mp3info && !bSingleFile) + mi->strInfo << _T("\n"); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << _T("MP3 Tags\n"); + } + iTag++; + + LPCSTR desc = frame->GetDescription(); + if (!desc) + desc = frame->GetTextID(); + + CStringStream strFidInfo; + ID3_FrameID eFrameID = frame->GetID(); + switch (eFrameID) + { + case ID3FID_ALBUM: + case ID3FID_COMPOSER: + case ID3FID_CONTENTTYPE: + case ID3FID_COPYRIGHT: + case ID3FID_DATE: + case ID3FID_PLAYLISTDELAY: + case ID3FID_ENCODEDBY: + case ID3FID_LYRICIST: + case ID3FID_FILETYPE: + case ID3FID_TIME: + case ID3FID_CONTENTGROUP: + case ID3FID_TITLE: + case ID3FID_SUBTITLE: + case ID3FID_INITIALKEY: + case ID3FID_LANGUAGE: + case ID3FID_MEDIATYPE: + case ID3FID_ORIGALBUM: + case ID3FID_ORIGFILENAME: + case ID3FID_ORIGLYRICIST: + case ID3FID_ORIGARTIST: + case ID3FID_ORIGYEAR: + case ID3FID_FILEOWNER: + case ID3FID_LEADARTIST: + case ID3FID_BAND: + case ID3FID_CONDUCTOR: + case ID3FID_MIXARTIST: + case ID3FID_PARTINSET: + case ID3FID_PUBLISHER: + case ID3FID_TRACKNUM: + case ID3FID_RECORDINGDATES: + case ID3FID_NETRADIOSTATION: + case ID3FID_NETRADIOOWNER: + case ID3FID_SIZE: + case ID3FID_ISRC: + case ID3FID_ENCODERSETTINGS: + case ID3FID_YEAR: + { + wchar_t *sText = ID3_GetStringW(frame, ID3FN_TEXT); + CString strText(sText); + strText.Trim(); + strFidInfo << strText; + delete[] sText; + break; + } + case ID3FID_BPM: + { + wchar_t *sText = ID3_GetStringW(frame, ID3FN_TEXT); + long lLength = _wtol(sText); + if (lLength) // check for != "0" + strFidInfo << sText; + delete[] sText; + break; + } + case ID3FID_SONGLEN: + { + wchar_t *sText = ID3_GetStringW(frame, ID3FN_TEXT); + long lLength = _wtol(sText) / 1000; + if (lLength) + { + CString strLength; + SecToTimeLength(lLength, strLength); + strFidInfo << strLength; + } + delete[] sText; + break; + } + case ID3FID_USERTEXT: + { + wchar_t + *sText = ID3_GetStringW(frame, ID3FN_TEXT), + *sDesc = ID3_GetStringW(frame, ID3FN_DESCRIPTION); + CString strText(sText); + strText.Trim(); + if (!strText.IsEmpty()) + { + CString strDesc(sDesc); + strDesc.Trim(); + if (!strDesc.IsEmpty()) + strFidInfo << _T("(") << strDesc << _T(")"); + + if (!strDesc.IsEmpty()) + strFidInfo << _T(": "); + strFidInfo << strText; + } + delete[] sText; + delete[] sDesc; + break; + } + case ID3FID_COMMENT: + case ID3FID_UNSYNCEDLYRICS: + { + wchar_t + *sText = ID3_GetStringW(frame, ID3FN_TEXT), + *sDesc = ID3_GetStringW(frame, ID3FN_DESCRIPTION), + *sLang = ID3_GetStringW(frame, ID3FN_LANGUAGE); + CString strText(sText); + strText.Trim(); + if (!strText.IsEmpty()) + { + CString strDesc(sDesc); + strDesc.Trim(); + if (strDesc == _T("ID3v1 Comment")) + strDesc.Empty(); + if (!strDesc.IsEmpty()) + strFidInfo << _T("(") << strDesc << _T(")"); + + CString strLang(sLang); + strLang.Trim(); + if (!strLang.IsEmpty()) + strFidInfo << _T("[") << strLang << _T("]"); + + if (!strDesc.IsEmpty() || !strLang.IsEmpty()) + strFidInfo << _T(": "); + strFidInfo << strText; + } + delete[] sText; + delete[] sDesc; + delete[] sLang; + break; + } + case ID3FID_WWWAUDIOFILE: + case ID3FID_WWWARTIST: + case ID3FID_WWWAUDIOSOURCE: + case ID3FID_WWWCOMMERCIALINFO: + case ID3FID_WWWCOPYRIGHT: + case ID3FID_WWWPUBLISHER: + case ID3FID_WWWPAYMENT: + case ID3FID_WWWRADIOPAGE: + { + wchar_t *sURL = ID3_GetStringW(frame, ID3FN_URL); + CString strURL(sURL); + strURL.Trim(); + strFidInfo << strURL; + delete[] sURL; + break; + } + case ID3FID_WWWUSER: + { + wchar_t + *sURL = ID3_GetStringW(frame, ID3FN_URL), + *sDesc = ID3_GetStringW(frame, ID3FN_DESCRIPTION); + CString strURL(sURL); + strURL.Trim(); + if (!strURL.IsEmpty()) + { + CString strDesc(sDesc); + strDesc.Trim(); + if (!strDesc.IsEmpty()) + strFidInfo << _T("(") << strDesc << _T(")"); + + if (!strDesc.IsEmpty()) + strFidInfo << _T(": "); + strFidInfo << strURL; + } + delete[] sURL; + delete[] sDesc; + break; + } + case ID3FID_INVOLVEDPEOPLE: + { + size_t nItems = frame->GetField(ID3FN_TEXT)->GetNumTextItems(); + for (size_t nIndex = 0; nIndex < nItems; nIndex++) + { + wchar_t *sPeople = ID3_GetStringW(frame, ID3FN_TEXT, nIndex); + strFidInfo << sPeople; + delete[] sPeople; + if (nIndex + 1 < nItems) + strFidInfo << _T(", "); + } + break; + } + case ID3FID_PICTURE: + { + wchar_t + *sMimeType = ID3_GetStringW(frame, ID3FN_MIMETYPE), + *sDesc = ID3_GetStringW(frame, ID3FN_DESCRIPTION), + *sFormat = ID3_GetStringW(frame, ID3FN_IMAGEFORMAT); + size_t + nPicType = frame->GetField(ID3FN_PICTURETYPE)->Get(), + nDataSize = frame->GetField(ID3FN_DATA)->Size(); + strFidInfo << _T("(") << sDesc << _T(")[") << sFormat << _T(", ") + << nPicType << _T("]: ") << sMimeType << _T(", ") << nDataSize << _T(" bytes"); + delete[] sMimeType; + delete[] sDesc; + delete[] sFormat; + break; + } + case ID3FID_GENERALOBJECT: + { + wchar_t + *sMimeType = ID3_GetStringW(frame, ID3FN_MIMETYPE), + *sDesc = ID3_GetStringW(frame, ID3FN_DESCRIPTION), + *sFileName = ID3_GetStringW(frame, ID3FN_FILENAME); + size_t + nDataSize = frame->GetField(ID3FN_DATA)->Size(); + strFidInfo << _T("(") << sDesc << _T(")[") + << sFileName << _T("]: ") << sMimeType << _T(", ") << nDataSize << _T(" bytes"); + delete[] sMimeType; + delete[] sDesc; + delete[] sFileName; + break; + } + case ID3FID_UNIQUEFILEID: + { + wchar_t *sOwner = ID3_GetStringW(frame, ID3FN_OWNER); + size_t nDataSize = frame->GetField(ID3FN_DATA)->Size(); + strFidInfo << sOwner << _T(", ") << nDataSize << _T(" bytes"); + delete[] sOwner; + break; + } + case ID3FID_PLAYCOUNTER: + { + size_t nCounter = frame->GetField(ID3FN_COUNTER)->Get(); + strFidInfo << nCounter; + break; + } + case ID3FID_POPULARIMETER: + { + wchar_t *sEmail = ID3_GetStringW(frame, ID3FN_EMAIL); + size_t + nCounter = frame->GetField(ID3FN_COUNTER)->Get(), + nRating = frame->GetField(ID3FN_RATING)->Get(); + strFidInfo << sEmail << _T(", counter=") << nCounter << _T(" rating=") << nRating; + delete[] sEmail; + break; + } + case ID3FID_CRYPTOREG: + case ID3FID_GROUPINGREG: + { + wchar_t *sOwner = ID3_GetStringW(frame, ID3FN_OWNER); + size_t + nSymbol = frame->GetField(ID3FN_ID)->Get(), + nDataSize = frame->GetField(ID3FN_DATA)->Size(); + strFidInfo << _T("(") << nSymbol << _T("): ") << sOwner << _T(", ") << nDataSize << _T(" bytes"); + break; + } + case ID3FID_SYNCEDLYRICS: + { + wchar_t + *sDesc = ID3_GetStringW(frame, ID3FN_DESCRIPTION), + *sLang = ID3_GetStringW(frame, ID3FN_LANGUAGE); + size_t + //nTimestamp = frame->GetField(ID3FN_TIMESTAMPFORMAT)->Get(), + nRating = frame->GetField(ID3FN_CONTENTTYPE)->Get(); + //const char* format = (2 == nTimestamp) ? "ms" : "frames"; + strFidInfo << _T("(") << sDesc << _T(")[") << sLang << _T("]: "); + switch (nRating) + { + case ID3CT_OTHER: strFidInfo << _T("Other"); break; + case ID3CT_LYRICS: strFidInfo << _T("Lyrics"); break; + case ID3CT_TEXTTRANSCRIPTION: strFidInfo << _T("Text transcription"); break; + case ID3CT_MOVEMENT: strFidInfo << _T("Movement/part name"); break; + case ID3CT_EVENTS: strFidInfo << _T("Events"); break; + case ID3CT_CHORD: strFidInfo << _T("Chord"); break; + case ID3CT_TRIVIA: strFidInfo << _T("Trivia/'pop up' information"); break; + } + /*ID3_Field* fld = frame->GetField(ID3FN_DATA); + if (fld) + { + ID3_MemoryReader mr(fld->GetRawBinary(), fld->BinSize()); + while (!mr.atEnd()) + { + strFidInfo << io::readString(mr).c_str(); + strFidInfo << " [" << io::readBENumber(mr, sizeof(uint32)) << " " + << format << "] "; + } + }*/ + delete[] sDesc; + delete[] sLang; + break; + } + case ID3FID_AUDIOCRYPTO: + case ID3FID_EQUALIZATION: + case ID3FID_EVENTTIMING: + case ID3FID_CDID: + case ID3FID_MPEGLOOKUP: + case ID3FID_OWNERSHIP: + case ID3FID_PRIVATE: + case ID3FID_POSITIONSYNC: + case ID3FID_BUFFERSIZE: + case ID3FID_VOLUMEADJ: + case ID3FID_REVERB: + case ID3FID_SYNCEDTEMPO: + case ID3FID_METACRYPTO: + //strFidInfo << _T(" (unimplemented)"); + break; + default: + //strFidInfo << _T(" frame"); + break; + } + + if (!strFidInfo.IsEmpty()) + { + mi->strInfo << _T(" ") << CString(desc) << _T(":"); + int iPos = 0; + CString strFidInfoLine = strFidInfo.GetText().Tokenize(_T("\r\n"), iPos); + while (!strFidInfoLine.IsEmpty()) + { + mi->strInfo << _T("\t") << strFidInfoLine << _T("\r\n"); + strFidInfoLine = strFidInfo.GetText().Tokenize(_T("\r\n"), iPos); + } + } + } + delete iter; + } + catch(...) + { + ASSERT(0); + } + + if (bFoundHeader) { + mi->InitFileLength(); + return true; + } + } + + if (!IsWindow(hWndOwner)) + return false; + + // starting the MediaDet object takes a noticeable amount of time.. avoid starting that object + // for files which are not expected to contain any Audio/Video data. + // note also: MediaDet does not work well for too short files (e.g. 16K) + // + // same applies for MediaInfoLib, its even slower than MediaDet -> avoid calling for non AV files. + // + // since we have a thread here, this should not be a performance problem any longer. + + bool bGiveMediaInfoLibHint = false; + // check again for AV type; MediaDet object has trouble with RAR files (?) + EED2KFileType eFileType = GetED2KFileTypeID(pFile->GetFileName()); + if (thePrefs.GetInspectAllFileTypes() + || (eFileType == ED2KFT_AUDIO || eFileType == ED2KFT_VIDEO)) + { + ///////////////////////////////////////////////////////////////////////////// + // Try MediaInfo lib + // + // Use MediaInfo only for non AVI files.. Reading potentially broken AVI files + // with the VfW API (as MediaInfo is doing) is rather dangerous. + // + if (!bIsAVI) + { + try + { + if (theMediaInfoDLL.Initialize()) + { + void* Handle = theMediaInfoDLL.Open(pFile->GetFilePath()); + if (Handle) + { + CString str; + + mi->strFileFormat = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Format"), Info_Text, Info_Name); + str = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Format_String"), Info_Text, Info_Name); + if (!str.IsEmpty() && str.Compare(mi->strFileFormat) != 0) + mi->strFileFormat += _T(" (") + str + _T(")"); + + if (szExt[0] == _T('.') && szExt[1] != _T('\0')) + { + str = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Format_Extensions"), Info_Text, Info_Name); + if (!str.IsEmpty()) + { + // minor bug in MediaInfo lib.. some file extension lists have a ')' character in there.. + str.Remove(_T(')')); + str.Remove(_T('(')); + + str.MakeLower(); + bool bFoundExt = false; + int iPos = 0; + CString strFmtExt(str.Tokenize(_T(" "), iPos)); + while (!strFmtExt.IsEmpty()) + { + if (_tcscmp(strFmtExt, szExt + 1) == 0) + { + bFoundExt = true; + break; + } + strFmtExt = str.Tokenize(_T(" "), iPos); + } + + if (!bFoundExt) + WarnAboutWrongFileExtension(mi, pFile->GetFileName(), str); + } + } + + CString strTitle = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Title"), Info_Text, Info_Name); + CString strTitleMore = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Title_More"), Info_Text, Info_Name); + if (!strTitleMore.IsEmpty() && !strTitle.IsEmpty() && strTitleMore != strTitle) + strTitle += _T("; ") + strTitleMore; + CString strAuthor = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Author"), Info_Text, Info_Name); + CString strCopyright = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Copyright"), Info_Text, Info_Name); + CString strComments = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Comments"), Info_Text, Info_Name); + if (strComments.IsEmpty()) + strComments = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Comment"), Info_Text, Info_Name); + CString strDate = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Date"), Info_Text, Info_Name); + if (strDate.IsEmpty()) + strDate = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("Encoded_Date"), Info_Text, Info_Name); + struct tm tmUtc = {0}; + if (_stscanf(strDate, _T("UTC %u-%u-%u %u:%u:%u"), &tmUtc.tm_year, &tmUtc.tm_mon, &tmUtc.tm_mday, &tmUtc.tm_hour, &tmUtc.tm_min, &tmUtc.tm_sec) == 6) + { + tmUtc.tm_mon -= 1; + tmUtc.tm_year -= 1900; + tmUtc.tm_isdst = -1; + time_t tLocal = mktime(&tmUtc); // convert UTC to local time + if (tLocal != -1) + { + // convert local time to UTC + time_t tUtc = tLocal - _timezone; + if (tmUtc.tm_isdst == 1) + tUtc -= _dstbias; + + // output 'date+time' or just 'date' + struct tm* tmLoc = localtime(&tUtc); + if (tmLoc && tmLoc->tm_hour == 0 && tmLoc->tm_min == 0 && tmLoc->tm_sec == 0) + strDate = CTime(tUtc).Format(_T("%x")); + else + strDate = CTime(tUtc).Format(_T("%c")); + } + } + + if (!strTitle.IsEmpty() || !strAuthor.IsEmpty() || !strCopyright.IsEmpty() || !strComments.IsEmpty() || !strDate.IsEmpty()) + { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\n"); + mi->OutputFileName(); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << GetResString(IDS_FD_GENERAL) << _T("\n"); + if (!strTitle.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_TITLE) << _T(":\t") << strTitle << _T("\n"); + if (!strAuthor.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_AUTHOR) << _T(":\t") << strAuthor << _T("\n"); + if (!strCopyright.IsEmpty()) + mi->strInfo << _T(" Copyright:\t") << strCopyright << _T("\n"); + if (!strComments.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_COMMENT) << _T(":\t") << strComments << _T("\n"); + if (!strDate.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_DATE) << _T(":\t") << strDate << _T("\n"); + } + + str = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("PlayTime"), Info_Text, Info_Name); + float fFileLengthSec = _tstoi(str) / 1000.0F; + UINT uAllBitrates = 0; + + str = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("VideoCount"), Info_Text, Info_Name); + int iVideoStreams = _tstoi(str); + if (iVideoStreams > 0) + { + mi->iVideoStreams = iVideoStreams; + mi->fVideoLengthSec = fFileLengthSec; + + CString strCodec = theMediaInfoDLL.Get(Handle, Stream_Video, 0, _T("Codec"), Info_Text, Info_Name); + mi->strVideoFormat = strCodec; + if (!strCodec.IsEmpty()) { + CStringA strCodecA(strCodec); + if (!strCodecA.IsEmpty()) + mi->video.bmiHeader.biCompression = *(LPDWORD)(LPCSTR)strCodecA; + } + str = theMediaInfoDLL.Get(Handle, Stream_Video, 0, _T("Codec_String"), Info_Text, Info_Name); + if (!str.IsEmpty() && str.Compare(mi->strVideoFormat) != 0) + mi->strVideoFormat += _T(" (") + str + _T(")"); + + str = theMediaInfoDLL.Get(Handle, Stream_Video, 0, _T("Width"), Info_Text, Info_Name); + mi->video.bmiHeader.biWidth = _tstoi(str); + + str = theMediaInfoDLL.Get(Handle, Stream_Video, 0, _T("Height"), Info_Text, Info_Name); + mi->video.bmiHeader.biHeight = _tstoi(str); + + str = theMediaInfoDLL.Get(Handle, Stream_Video, 0, _T("FrameRate"), Info_Text, Info_Name); + mi->fVideoFrameRate = _tstof(str); + + str = theMediaInfoDLL.Get(Handle, Stream_Video, 0, _T("BitRate_Mode"), Info_Text, Info_Name); + if (str.CompareNoCase(_T("VBR")) == 0) { + mi->video.dwBitRate = (DWORD)-1; + uAllBitrates = (UINT)-1; + } + else { + str = theMediaInfoDLL.Get(Handle, Stream_Video, 0, _T("BitRate"), Info_Text, Info_Name); + int iBitrate = _tstoi(str); + mi->video.dwBitRate = iBitrate == -1 ? -1 : iBitrate; + if (iBitrate == -1) + uAllBitrates = (UINT)-1; + else if (uAllBitrates != (UINT)-1) + uAllBitrates += iBitrate; + } + + str = theMediaInfoDLL.Get(Handle, Stream_Video, 0, _T("AspectRatio"), Info_Text, Info_Name); + mi->fVideoAspectRatio = _tstof(str); + + for (int s = 1; s < iVideoStreams; s++) + { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\n"); + mi->OutputFileName(); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << GetResString(IDS_VIDEO) << _T(" #") << s+1 << _T("\n"); + + CString strCodec = theMediaInfoDLL.Get(Handle, Stream_Video, s, _T("Codec"), Info_Text, Info_Name); + CString strVideoFormat = strCodec; + str = theMediaInfoDLL.Get(Handle, Stream_Video, s, _T("Codec_String"), Info_Text, Info_Name); + if (!str.IsEmpty() && str.Compare(strVideoFormat) != 0) + strVideoFormat += _T(" (") + str + _T(")"); + if (!strVideoFormat.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_CODEC) << _T(":\t") << strVideoFormat << _T("\n"); + + CString strBitrate; + str = theMediaInfoDLL.Get(Handle, Stream_Video, s, _T("BitRate_Mode"), Info_Text, Info_Name); + if (str.CompareNoCase(_T("VBR")) == 0) { + strBitrate = _T("Variable"); + uAllBitrates = (UINT)-1; + } + else { + str = theMediaInfoDLL.Get(Handle, Stream_Video, s, _T("BitRate"), Info_Text, Info_Name); + int iBitrate = _tstoi(str); + if (iBitrate != 0) { + if (iBitrate == -1) { + strBitrate = _T("Variable"); + uAllBitrates = (UINT)-1; + } + else { + strBitrate.Format(_T("%u %s"), (iBitrate + 500) / 1000, GetResString(IDS_KBITSSEC)); + if (uAllBitrates != (UINT)-1) + uAllBitrates += iBitrate; + } + } + } + if (!strBitrate.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_BITRATE) << _T(":\t") << strBitrate << _T("\n"); + + CString strWidth = theMediaInfoDLL.Get(Handle, Stream_Video, s, _T("Width"), Info_Text, Info_Name); + CString strHeight = theMediaInfoDLL.Get(Handle, Stream_Video, s, _T("Height"), Info_Text, Info_Name); + if (!strWidth.IsEmpty() && !strHeight.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_WIDTH) << _T(" x ") << GetResString(IDS_HEIGHT) << _T(":\t") << strWidth << _T(" x ") << strHeight << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Video, s, _T("AspectRatio"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_ASPECTRATIO) << _T(":\t") << str << _T(" (") << GetKnownAspectRatioDisplayString((float)_tstof(str)) << _T(")\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Video, s, _T("FrameRate"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_FPS) << _T(":\t") << str << _T("\n"); + } + + bFoundHeader = true; + } + + str = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("AudioCount"), Info_Text, Info_Name); + int iAudioStreams = _tstoi(str); + if (iAudioStreams > 0) + { + mi->iAudioStreams = iAudioStreams; + mi->fAudioLengthSec = fFileLengthSec; + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("Codec"), Info_Text, Info_Name); + if (_stscanf(str, _T("%hx"), &mi->audio.wFormatTag) != 1) { + mi->strAudioFormat = str; + str = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("Codec_String"), Info_Text, Info_Name); + if (!str.IsEmpty() && str.Compare(mi->strAudioFormat) != 0) + mi->strAudioFormat += _T(" (") + str + _T(")"); + } + else { + mi->strAudioFormat = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("Codec_String"), Info_Text, Info_Name); + str = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("Codec_Info"), Info_Text, Info_Name); + if (!str.IsEmpty() && str.Compare(mi->strAudioFormat) != 0) + mi->strAudioFormat += _T(" (") + str + _T(")"); + } + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("Channels"), Info_Text, Info_Name); + mi->audio.nChannels = (WORD)_tstoi(str); + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("SamplingRate"), Info_Text, Info_Name); + mi->audio.nSamplesPerSec = _tstoi(str); + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("BitRate_Mode"), Info_Text, Info_Name); + if (str.CompareNoCase(_T("VBR")) == 0) { + mi->audio.nAvgBytesPerSec = (DWORD)-1; + uAllBitrates = (UINT)-1; + } + else { + str = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("BitRate"), Info_Text, Info_Name); + int iBitrate = _tstoi(str); + mi->audio.nAvgBytesPerSec = iBitrate == -1 ? -1 : iBitrate / 8; + if (iBitrate == -1) + uAllBitrates = (UINT)-1; + else if (uAllBitrates != (UINT)-1) + uAllBitrates += iBitrate; + } + + mi->strAudioLanguage = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("Language_String"), Info_Text, Info_Name); + if (mi->strAudioLanguage.IsEmpty()) + mi->strAudioLanguage = theMediaInfoDLL.Get(Handle, Stream_Audio, 0, _T("Language"), Info_Text, Info_Name); + + for (int s = 1; s < iAudioStreams; s++) + { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\n"); + mi->OutputFileName(); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << GetResString(IDS_AUDIO) << _T(" #") << s+1 << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("Codec"), Info_Text, Info_Name); + WORD wFormatTag; + CString strAudioFormat; + if (_stscanf(str, _T("%hx"), &wFormatTag) != 1) { + strAudioFormat = str; + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("Codec_String"), Info_Text, Info_Name); + if (!str.IsEmpty() && str.Compare(strAudioFormat) != 0) + strAudioFormat += _T(" (") + str + _T(")"); + } + else { + strAudioFormat = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("Codec_String"), Info_Text, Info_Name); + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("Codec_Info"), Info_Text, Info_Name); + if (!str.IsEmpty() && str.Compare(strAudioFormat) != 0) + strAudioFormat += _T(" (") + str + _T(")"); + } + if (!strAudioFormat.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_CODEC) << _T(":\t") << strAudioFormat << _T("\n"); + + CString strBitrate; + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("BitRate_Mode"), Info_Text, Info_Name); + if (str.CompareNoCase(_T("VBR")) == 0) { + strBitrate = _T("Variable"); + uAllBitrates = (UINT)-1; + } + else { + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("BitRate"), Info_Text, Info_Name); + int iBitrate = _tstoi(str); + if (iBitrate != 0) { + if (iBitrate == -1) { + strBitrate = _T("Variable"); + uAllBitrates = (UINT)-1; + } + else { + strBitrate.Format(_T("%u %s"), (iBitrate + 500) / 1000, GetResString(IDS_KBITSSEC)); + if (uAllBitrates != (UINT)-1) + uAllBitrates += iBitrate; + } + } + } + if (!strBitrate.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_BITRATE) << _T(":\t") << strBitrate << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("Channels"), Info_Text, Info_Name); + if (!str.IsEmpty()) + { + int iChannels = _tstoi(str); + mi->strInfo << _T(" ") << GetResString(IDS_CHANNELS) << _T(":\t"); + if (iChannels == 1) + mi->strInfo << _T("1 (Mono)"); + else if (iChannels == 2) + mi->strInfo << _T("2 (Stereo)"); + else if (iChannels == 5) + mi->strInfo << _T("5.1 (Surround)"); + else + mi->strInfo << iChannels; + mi->strInfo << _T("\n"); + } + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("SamplingRate"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_SAMPLERATE) << _T(":\t") << _tstoi(str) / 1000.0 << _T(" kHz\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("Language_String"), Info_Text, Info_Name); + if (str.IsEmpty()) + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("Language"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_PW_LANG) << _T(":\t") << str << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Audio, s, _T("Language_Info"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_PW_LANG) << _T(":\t") << str << _T("\n"); + } + + bFoundHeader = true; + } + + str = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("TextCount"), Info_Text, Info_Name); + int iTextStreams = _tstoi(str); + for (int s = 0; s < iTextStreams; s++) + { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\n"); + mi->OutputFileName(); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << _T("Subtitle") << _T(" #") << s+1 << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Text, s, _T("Codec"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_CODEC) << _T(":\t") << str << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Text, s, _T("Language_String"), Info_Text, Info_Name); + if (str.IsEmpty()) + str = theMediaInfoDLL.Get(Handle, Stream_Text, s, _T("Language"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_PW_LANG) << _T(":\t") << str << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Text, s, _T("Language_Info"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_PW_LANG) << _T(":\t") << str << _T("\n"); + } + + str = theMediaInfoDLL.Get(Handle, Stream_General, 0, _T("ChaptersCount"), Info_Text, Info_Name); + int iChapterStreams = _tstoi(str); + for (int s = 0; s < iChapterStreams; s++) + { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\n"); + mi->OutputFileName(); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << _T("Chapter") << _T(" #") << s+1 << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Chapters, s, _T("Codec"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_CODEC) << _T(":\t") << str << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Chapters, s, _T("Language_String"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_PW_LANG) << _T(":\t") << str << _T("\n"); + + str = theMediaInfoDLL.Get(Handle, Stream_Chapters, s, _T("Language_Info"), Info_Text, Info_Name); + if (!str.IsEmpty()) + mi->strInfo << _T(" ") << GetResString(IDS_PW_LANG) << _T(":\t") << str << _T("\n"); + } + + theMediaInfoDLL.Close(Handle); + + // MediaInfoLib does not handle MPEG files correctly in regards of + // play length property -- even for completed files (applies also for + // v0.7.2.1). So, we try to calculate the play length by using the + // various bitrates. We could do this only for part files which are + // still not having the final file length, but MediaInfoLib also + // fails to determine play length for completed files (Hint: one can + // not use GOPs to determine the play length (properly)). + // + //"MPEG 1" v0.7.0.0 + //"MPEG-1 PS" v0.7.2.1 + if (mi->strFileFormat.Find(_T("MPEG")) == 0) /* MPEG container? */ + { + if ( uAllBitrates != 0 /* do we have any bitrates? */ + && uAllBitrates != (UINT)-1 /* do we have CBR only? */ + ) { + // Though, its not that easy to calculate the real play length + // without including the container's overhead. The value we + // calculate with this simple formular is slightly too large! + // But, its still better than using GOP-derived values which are + // sometimes completely wrong. + fFileLengthSec = (float)((double)pFile->GetFileSize() * 8.0 / uAllBitrates); + + if (mi->iVideoStreams > 0) { + // Try to compensate the error from above by estimating the overhead + if (mi->fVideoFrameRate > 0) { + ULONGLONG uFrames = (ULONGLONG)(fFileLengthSec * mi->fVideoFrameRate); + fFileLengthSec = (float)((double)(pFile->GetFileSize() - uFrames*24) * 8.0 / uAllBitrates); + } + mi->fVideoLengthSec = fFileLengthSec; + mi->bVideoLengthEstimated = true; + } + if (mi->iAudioStreams > 0) { + mi->fAudioLengthSec = fFileLengthSec; + mi->bAudioLengthEstimated = true; + } + } + else { + // set the 'estimated' flags in case we any VBR stream + if (mi->iVideoStreams > 0) + mi->bVideoLengthEstimated = true; + if (mi->iAudioStreams > 0) + mi->bAudioLengthEstimated = true; + } + } + + if (bFoundHeader) { + mi->InitFileLength(); + return true; + } + } + } + else + { + EED2KFileType eED2KFileType = GetED2KFileTypeID(pFile->GetFilePath()); + if (eED2KFileType == ED2KFT_AUDIO || eED2KFileType == ED2KFT_VIDEO) + bGiveMediaInfoLibHint = true; + } + } + catch(...) + { + ASSERT(0); + } + } + + if (!IsWindow(hWndOwner)) + return false; + + ///////////////////////////////////////////////////////////////////////////// + // Try MediaDet object + // + // Avoid processing of some file types which are known to crash due to bugged DirectShow filters. +#ifdef HAVE_QEDIT_H + if (theApp.GetProfileInt(_T("eMule"), _T("MediaInfo_MediaDet"), 1) + && ( thePrefs.GetInspectAllFileTypes() + || (_tcscmp(szExt, _T(".ogm"))!=0 && _tcscmp(szExt, _T(".ogg"))!=0 && _tcscmp(szExt, _T(".mkv"))!=0))) + { + try + { + CComPtr pMediaDet; + HRESULT hr = pMediaDet.CoCreateInstance(__uuidof(MediaDet)); + if (SUCCEEDED(hr)) + { + if (SUCCEEDED(hr = pMediaDet->put_Filename(CComBSTR(pFile->GetFilePath())))) + { + long lStreams; + if (SUCCEEDED(hr = pMediaDet->get_OutputStreams(&lStreams))) + { + for (long i = 0; i < lStreams; i++) + { + if (SUCCEEDED(hr = pMediaDet->put_CurrentStream(i))) + { + GUID major_type; + if (SUCCEEDED(hr = pMediaDet->get_StreamType(&major_type))) + { + if (major_type == MEDIATYPE_Video) + { + mi->iVideoStreams++; + + if (mi->iVideoStreams > 1) + { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\n"); + mi->OutputFileName(); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << GetResString(IDS_VIDEO) << _T(" #") << mi->iVideoStreams << _T("\n"); + } + + AM_MEDIA_TYPE mt = {0}; + if (SUCCEEDED(hr = pMediaDet->get_StreamMediaType(&mt))) + { + if (mt.formattype == FORMAT_VideoInfo) + { + VIDEOINFOHEADER* pVIH = (VIDEOINFOHEADER*)mt.pbFormat; + + if (mi->iVideoStreams == 1) + { + mi->video = *pVIH; + if (mi->video.bmiHeader.biWidth && mi->video.bmiHeader.biHeight) + mi->fVideoAspectRatio = (float)abs(mi->video.bmiHeader.biWidth) / (float)abs(mi->video.bmiHeader.biHeight); + mi->video.dwBitRate = 0; // don't use this value + mi->strVideoFormat = GetVideoFormatName(mi->video.bmiHeader.biCompression); + pMediaDet->get_FrameRate(&mi->fVideoFrameRate); + bFoundHeader = true; + } + else + { + mi->strInfo << _T(" ") << GetResString(IDS_CODEC) << _T(":\t") << GetVideoFormatName(pVIH->bmiHeader.biCompression) << _T("\n"); + mi->strInfo << _T(" ") << GetResString(IDS_WIDTH) << _T(" x ") << GetResString(IDS_HEIGHT) << _T(":\t") << abs(pVIH->bmiHeader.biWidth) << _T(" x ") << abs(pVIH->bmiHeader.biHeight) << _T("\n"); + // do not use that 'dwBitRate', whatever this number is, it's not + // the bitrate of the *encoded* video stream. seems to be the bitrate + // of the *decoded* stream + //if (pVIH->dwBitRate) + // mi->strInfo << " Bitrate:\t" << (UINT)(pVIH->dwBitRate / 1000) << " " << GetResString(IDS_KBITSSEC) << "\n"; + + double fFrameRate = 0.0; + if (SUCCEEDED(pMediaDet->get_FrameRate(&fFrameRate)) && fFrameRate) + mi->strInfo << _T(" ") << GetResString(IDS_FPS) << _T(":\t") << fFrameRate << _T("\n"); + } + } + } + + double fLength = 0.0; + if (SUCCEEDED(pMediaDet->get_StreamLength(&fLength)) && fLength) + { + if (mi->iVideoStreams == 1) + mi->fVideoLengthSec = fLength; + else + { + CString strLength; + SecToTimeLength((ULONG)fLength, strLength); + mi->strInfo << _T(" ") << GetResString(IDS_LENGTH) << _T(":\t") << strLength; + if (pFile->IsPartFile()){ + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfRed); + mi->strInfo << _T(" (") + GetResString(IDS_ESTIMATED)+ _T(")"); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfDef); + } + mi->strInfo << _T("\n"); + } + } + + if (mt.pUnk != NULL) + mt.pUnk->Release(); + if (mt.pbFormat != NULL) + CoTaskMemFree(mt.pbFormat); + } + else if (major_type == MEDIATYPE_Audio) + { + mi->iAudioStreams++; + + if (mi->iAudioStreams > 1) + { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\n"); + mi->OutputFileName(); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << GetResString(IDS_AUDIO) << _T(" #") << mi->iAudioStreams << _T("\n"); + } + + AM_MEDIA_TYPE mt = {0}; + if (SUCCEEDED(hr = pMediaDet->get_StreamMediaType(&mt))) + { + if (mt.formattype == FORMAT_WaveFormatEx) + { + WAVEFORMATEX* wfx = (WAVEFORMATEX*)mt.pbFormat; + + // Try to determine if the stream is VBR. + // + // MediaDet seems to only look at the AVI stream headers to get a hint + // about CBR/VBR. If the stream headers are looking "odd", MediaDet + // reports "VBR". Typically, files muxed with Nandub get identified as + // VBR (just because of the stream headers) even if they are CBR. Also, + // real VBR MP3 files still get reported as CBR. Though, basically it's + // better to report VBR even if it's CBR. The other way round is even + // more ugly. + if (!mt.bFixedSizeSamples) + wfx->nAvgBytesPerSec = (DWORD)-1; + + if (mi->iAudioStreams == 1) + { + memcpy(&mi->audio, wfx, sizeof mi->audio); + mi->strAudioFormat = GetAudioFormatName(wfx->wFormatTag); + } + else + { + mi->strInfo << _T(" ") << GetResString(IDS_CODEC) << _T(":\t") << GetAudioFormatName(wfx->wFormatTag) << _T("\n"); + + if (wfx->nAvgBytesPerSec) + { + CString strBitrate; + if (wfx->nAvgBytesPerSec == (DWORD)-1) + strBitrate = _T("Variable"); + else + strBitrate.Format(_T("%u %s"), (UINT)(((wfx->nAvgBytesPerSec * 8.0) + 500.0) / 1000.0), GetResString(IDS_KBITSSEC)); + mi->strInfo << _T(" ") << GetResString(IDS_BITRATE) << _T(":\t") << strBitrate << _T("\n"); + } + + if (wfx->nChannels) + { + mi->strInfo << _T(" ") << GetResString(IDS_CHANNELS) << _T(":\t"); + if (wfx->nChannels == 1) + mi->strInfo << _T("1 (Mono)"); + else if (wfx->nChannels == 2) + mi->strInfo << _T("2 (Stereo)"); + else if (wfx->nChannels == 5) + mi->strInfo << _T("5.1 (Surround)"); + else + mi->strInfo << wfx->nChannels; + mi->strInfo << _T("\n"); + } + + if (wfx->nSamplesPerSec) + mi->strInfo << _T(" ") << GetResString(IDS_SAMPLERATE) << _T(":\t") << wfx->nSamplesPerSec / 1000.0 << _T(" kHz\n"); + + if (wfx->wBitsPerSample) + mi->strInfo << _T(" Bit/sample:\t") << wfx->wBitsPerSample << _T(" Bit\n"); + } + bFoundHeader = true; + } + } + + double fLength = 0.0; + if (SUCCEEDED(pMediaDet->get_StreamLength(&fLength)) && fLength) + { + if (mi->iAudioStreams == 1) + mi->fAudioLengthSec = fLength; + else + { + CString strLength; + SecToTimeLength((ULONG)fLength, strLength); + mi->strInfo << _T(" ") << GetResString(IDS_LENGTH) << _T(":\t") << strLength; + if (pFile->IsPartFile()){ + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfRed); + mi->strInfo << _T(" (") + GetResString(IDS_ESTIMATED)+ _T(")"); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfDef); + } + mi->strInfo << _T("\n"); + } + } + + if (mt.pUnk != NULL) + mt.pUnk->Release(); + if (mt.pbFormat != NULL) + CoTaskMemFree(mt.pbFormat); + } + else + { + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\n"); + mi->OutputFileName(); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfBold); + mi->strInfo << GetResString(IDS_UNKNOWN) << _T(" Stream #") << i+1 << _T("\n"); + + double fLength = 0.0; + if (SUCCEEDED(pMediaDet->get_StreamLength(&fLength)) && fLength) + { + CString strLength; + SecToTimeLength((ULONG)fLength, strLength); + mi->strInfo << _T(" ") << GetResString(IDS_LENGTH) << _T(":\t") << strLength; + if (pFile->IsPartFile()) { + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfRed); + mi->strInfo << _T(" (") + GetResString(IDS_ESTIMATED)+ _T(")"); + mi->strInfo.SetSelectionCharFormat(mi->strInfo.m_cfDef); + } + mi->strInfo << _T("\n"); + } + mi->strInfo << _T("\n"); + } + } + } + } + if (bFoundHeader) + mi->InitFileLength(); + } + } + else{ + TRACE(_T("Failed to open \"%s\" - %s\n"), pFile->GetFilePath(), GetErrorMessage(hr, 1)); + } + } + } + catch(...){ + ASSERT(0); + } + } +#else//HAVE_QEDIT_H +#pragma message("WARNING: Missing 'qedit.h' header file - some features will get disabled. See the file 'emule_site_config.h' for more information.") +#endif//HAVE_QEDIT_H + } + + if (!bFoundHeader && bGiveMediaInfoLibHint) + { + TCHAR szBuff[MAX_PATH]; + DWORD dwModPathLen = ::GetModuleFileName(theApp.m_hInstance, szBuff, _countof(szBuff)); + if (dwModPathLen == 0 || dwModPathLen == _countof(szBuff)) + szBuff[0] = _T('\0'); + CString strInstFolder(szBuff); + PathRemoveFileSpec(strInstFolder.GetBuffer(strInstFolder.GetLength())); + strInstFolder.ReleaseBuffer(); + CString strHint; + strHint.Format(GetResString(IDS_MEDIAINFO_DLLMISSING), strInstFolder); + if (!mi->strInfo.IsEmpty()) + mi->strInfo << _T("\r\n"); + mi->strInfo << strHint; + } + + return bFoundHeader || !mi->strMimeType.IsEmpty() || bHasDRM; +} + +void CFileInfoDialog::DoDataExchange(CDataExchange* pDX) +{ + CResizablePage::DoDataExchange(pDX); + DDX_Control(pDX, IDC_FULL_FILE_INFO, m_fi); +} + +void CFileInfoDialog::Localize() +{ + GetDlgItem(IDC_GENERAL)->SetWindowText(GetResString(IDS_FD_GENERAL)); + GetDlgItem(IDC_FD_XI2)->SetWindowText(GetResString(IDS_LENGTH)+_T(":")); + GetDlgItem(IDC_FD_XI3)->SetWindowText(GetResString(IDS_VIDEO)); + GetDlgItem(IDC_FD_XI4)->SetWindowText(GetResString(IDS_AUDIO)); + GetDlgItem(IDC_FD_XI5)->SetWindowText(GetResString(IDS_CODEC)+_T(":")); + GetDlgItem(IDC_FD_XI6)->SetWindowText(GetResString(IDS_CODEC)+_T(":")); + GetDlgItem(IDC_FD_XI7)->SetWindowText(GetResString(IDS_BITRATE)+_T(":")); + GetDlgItem(IDC_FD_XI8)->SetWindowText(GetResString(IDS_BITRATE)+_T(":")); + GetDlgItem(IDC_FD_XI9)->SetWindowText(GetResString(IDS_WIDTH)+_T(" x ")+GetResString(IDS_HEIGHT)+_T(":")); + GetDlgItem(IDC_FD_XI13)->SetWindowText(GetResString(IDS_FPS)+_T(":")); + GetDlgItem(IDC_FD_XI10)->SetWindowText(GetResString(IDS_CHANNELS)+_T(":")); + GetDlgItem(IDC_FD_XI12)->SetWindowText(GetResString(IDS_SAMPLERATE)+_T(":")); + GetDlgItem(IDC_STATICFI)->SetWindowText(GetResString(IDS_FILEFORMAT)+_T(":")); + GetDlgItem(IDC_FD_XI14)->SetWindowText(GetResString(IDS_ASPECTRATIO)+_T(":")); + GetDlgItem(IDC_STATIC_LANGUAGE)->SetWindowText(GetResString(IDS_PW_LANG)+_T(":")); + if (!m_bReducedDlg) + GetDlgItem(IDC_FD_XI1)->SetWindowText(GetResString(IDS_FD_SIZE)); + +} + +void CFileInfoDialog::AddFileInfo(LPCTSTR pszFmt, ...) +{ + if (m_bReducedDlg) + return; + va_list pArgp; + va_start(pArgp, pszFmt); + CString strInfo; + strInfo.FormatV(pszFmt, pArgp); + va_end(pArgp); + + m_fi.SetSel(m_fi.GetWindowTextLength(), m_fi.GetWindowTextLength()); + m_fi.ReplaceSel(strInfo); +} diff --git a/FileInfoDialog.h b/FileInfoDialog.h new file mode 100644 index 00000000..11deb7c4 --- /dev/null +++ b/FileInfoDialog.h @@ -0,0 +1,62 @@ +//this file is part of eMule +// +//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. +#pragma once +#include "ResizableLib/ResizablePage.h" +#include "RichEditCtrlX.h" + +class CKnownFile; +struct SMediaInfo; + +///////////////////////////////////////////////////////////////////////////// +// CFileInfoDialog dialog + +class CFileInfoDialog : public CResizablePage +{ + DECLARE_DYNAMIC(CFileInfoDialog) + +public: + CFileInfoDialog(); // standard constructor + virtual ~CFileInfoDialog(); + + void SetFiles(const CSimpleArray* paFiles) { m_paFiles = paFiles; m_bDataChanged = true; } + void SetReducedDialog() { m_bReducedDlg = true; } + +// Dialog Data + enum { IDD = IDD_FILEINFO }; + +protected: + CString m_strCaption; + const CSimpleArray* m_paFiles; + bool m_bDataChanged; + CRichEditCtrlX m_fi; + bool m_bReducedDlg; +// CHARFORMAT m_cfDef; +// CHARFORMAT m_cfBold; +// CHARFORMAT m_cfRed; + + bool GetMediaInfo(const CKnownFile* file, SMediaInfo* mi, bool bSingleFile); + void Localize(); + void AddFileInfo(LPCTSTR pszFmt, ...); + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + virtual BOOL OnSetActive(); + + DECLARE_MESSAGE_MAP() + afx_msg LRESULT OnMediaInfoResult(WPARAM, LPARAM); + afx_msg LRESULT OnDataChanged(WPARAM, LPARAM); + afx_msg void OnDestroy(); +}; diff --git a/FirewallOpener.cpp b/FirewallOpener.cpp new file mode 100644 index 00000000..06e81ff2 --- /dev/null +++ b/FirewallOpener.cpp @@ -0,0 +1,294 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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 + +// class to configure the ICS-Firewall of Windows XP - will not work with WinXP-SP2 yet + +#include "StdAfx.h" +#include "firewallopener.h" +#include "emule.h" +#include "preferences.h" +#include "otherfunctions.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define RETURN_ON_FAIL(x) if (!SUCCEEDED(x)) return false; + +CFirewallOpener::CFirewallOpener(void) +{ + m_bInited = false; + m_pINetSM = NULL; +} + +CFirewallOpener::~CFirewallOpener(void) +{ + UnInit(); +} + +bool CFirewallOpener::Init(bool bPreInit) +{ + if (!m_bInited){ + ASSERT ( m_liAddedRules.IsEmpty() ); + if (thePrefs.GetWindowsVersion() != _WINVER_XP_ || !SUCCEEDED(CoInitialize(NULL))) + return false; + HRESULT hr = CoInitializeSecurity (NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); + if (!SUCCEEDED(hr) || !SUCCEEDED(::CoCreateInstance (__uuidof(NetSharingManager), NULL, CLSCTX_ALL, __uuidof(INetSharingManager), (void**)&m_pINetSM)) ){ + CoUninitialize(); + return false; + } + } + m_bInited = true; + if (bPreInit){ + // will return here in order to not create an instance when not really needed + // preinit is only used to call CoInitializeSecurity before its too late for that (aka something else called it) + // will have to look deeper into this issue in order to find a nicer way if possible + return true; + } + + if (m_pINetSM == NULL){ + if (!SUCCEEDED(::CoCreateInstance (__uuidof(NetSharingManager), NULL, CLSCTX_ALL, __uuidof(INetSharingManager), (void**)&m_pINetSM)) ){ + UnInit(); + return false; + } + } + return true; +} + +void CFirewallOpener::UnInit(){ + if (!m_bInited) + return; + + for (int i = 0; i != m_liAddedRules.GetCount(); i++){ + if (m_liAddedRules[i].m_bRemoveOnExit) + RemoveRule(m_liAddedRules[i]); + } + m_liAddedRules.RemoveAll(); + + m_bInited = false; + if (m_pINetSM != NULL){ + m_pINetSM->Release(); + m_pINetSM = NULL; + } + else + ASSERT ( false ); + CoUninitialize(); +} + +bool CFirewallOpener::DoAction(const EFOCAction eAction, const CICSRuleInfo& riPortRule){ + if ( !Init() ) + return false; + //TODO lets see if we can find a reliable method to find out the internet standard connection set by the user + + bool bSuccess = true; + bool bPartialSucceeded = false; + bool bFoundAtLeastOneConn = false; + + INetSharingEveryConnectionCollectionPtr NSECCP; + IEnumVARIANTPtr varEnum; + IUnknownPtr pUnk; + RETURN_ON_FAIL(m_pINetSM->get_EnumEveryConnection(&NSECCP)); + RETURN_ON_FAIL(NSECCP->get__NewEnum(&pUnk)); + RETURN_ON_FAIL(pUnk->QueryInterface(__uuidof(IEnumVARIANT), (void**)&varEnum)); + + _variant_t var; + while (S_OK == varEnum->Next(1, &var, NULL)) { + INetConnectionPtr NCP; + if (V_VT(&var) == VT_UNKNOWN + && SUCCEEDED(V_UNKNOWN(&var)->QueryInterface(__uuidof(INetConnection),(void**)&NCP))) + { + INetConnectionPropsPtr pNCP; + if ( !SUCCEEDED(m_pINetSM->get_NetConnectionProps (NCP, &pNCP)) ) + continue; + DWORD dwCharacteristics = 0; + pNCP->get_Characteristics(&dwCharacteristics); + if (dwCharacteristics & (NCCF_FIREWALLED)) { + NETCON_MEDIATYPE MediaType = NCM_NONE; + pNCP->get_MediaType (&MediaType); + if ((MediaType != NCM_SHAREDACCESSHOST_LAN) && (MediaType != NCM_SHAREDACCESSHOST_RAS) ){ + INetSharingConfigurationPtr pNSC; + if ( !SUCCEEDED(m_pINetSM->get_INetSharingConfigurationForINetConnection (NCP, &pNSC)) ) + continue; + VARIANT_BOOL varbool = VARIANT_FALSE; + pNSC->get_InternetFirewallEnabled(&varbool); + if (varbool == VARIANT_FALSE) + continue; + bFoundAtLeastOneConn = true; + switch(eAction){ + case FOC_ADDRULE:{ + bool bResult; + // we do not want to overwrite an existing rule + if (FindRule(FOC_FINDRULEBYPORT, riPortRule, pNSC, NULL)){ + bResult = true; + } + else + bResult = AddRule(riPortRule, pNSC, pNCP); + bSuccess = bSuccess && bResult; + if (bResult && !bPartialSucceeded) + m_liAddedRules.Add(riPortRule); // keep track of added rules + bPartialSucceeded = bPartialSucceeded || bResult; + break; + } + case FOC_FWCONNECTIONEXISTS: + return true; + case FOC_DELETERULEBYNAME: + case FOC_DELETERULEEXCACT: + bSuccess = bSuccess && FindRule(eAction, riPortRule, pNSC, NULL); + break; + case FOC_FINDRULEBYNAME: + if (FindRule(FOC_FINDRULEBYNAME, riPortRule, pNSC, NULL)) + return true; + else + bSuccess = false; + break; + case FOC_FINDRULEBYPORT: + if (FindRule(FOC_FINDRULEBYPORT, riPortRule, pNSC, NULL)) + return true; + else + bSuccess = false; + break; + default: + ASSERT ( false ); + } + } + } + } + var.Clear(); + } + return bSuccess && bFoundAtLeastOneConn; +} + +bool CFirewallOpener::AddRule(const CICSRuleInfo& riPortRule, const INetSharingConfigurationPtr pNSC, const INetConnectionPropsPtr pNCP){ + INetSharingPortMappingPtr pNSPM; + HRESULT hr = pNSC->AddPortMapping(riPortRule.m_strRuleName.AllocSysString(), + riPortRule.m_byProtocol, + riPortRule.m_nPortNumber, + riPortRule.m_nPortNumber, + 0, + CComBSTR(L"127.0.0.1"), + ICSTT_IPADDRESS, + &pNSPM); + CComBSTR bstrName; + pNCP->get_Name(&bstrName); + if ( SUCCEEDED(hr) && SUCCEEDED(pNSPM->Enable())){ + theApp.QueueDebugLogLine(false, _T("Succeeded to add Rule '%s' for Port '%u' on Connection '%s'"),riPortRule.m_strRuleName, riPortRule.m_nPortNumber, CString(bstrName)); + return true; + } + else{ + theApp.QueueDebugLogLine(false, _T("Failed to add Rule '%s' for Port '%u' on Connection '%s'"),riPortRule.m_strRuleName, riPortRule.m_nPortNumber, CString(bstrName)); + return false; + } +} + +bool CFirewallOpener::FindRule(const EFOCAction eAction, const CICSRuleInfo& riPortRule, const INetSharingConfigurationPtr pNSC, INetSharingPortMappingPropsPtr* outNSPMP){ + INetSharingPortMappingCollectionPtr pNSPMC; + RETURN_ON_FAIL(pNSC->get_EnumPortMappings (ICSSC_DEFAULT, &pNSPMC)); + + INetSharingPortMappingPtr pNSPM; + IEnumVARIANTPtr varEnum; + IUnknownPtr pUnk; + RETURN_ON_FAIL(pNSPMC->get__NewEnum(&pUnk)); + RETURN_ON_FAIL(pUnk->QueryInterface(__uuidof(IEnumVARIANT), (void**)&varEnum)); + _variant_t var; + while (S_OK == varEnum->Next(1, &var, NULL)) { + INetSharingPortMappingPropsPtr pNSPMP; + if (V_VT(&var) == VT_DISPATCH + && SUCCEEDED(V_DISPATCH(&var)->QueryInterface(__uuidof(INetSharingPortMapping),(void**)&pNSPM)) + && SUCCEEDED(pNSPM->get_Properties (&pNSPMP))) + { + UCHAR ucProt = 0; + long uExternal = 0; + CComBSTR bstrName; + pNSPMP->get_IPProtocol (&ucProt); + pNSPMP->get_ExternalPort (&uExternal); + pNSPMP->get_Name(&bstrName); + switch(eAction){ + case FOC_FINDRULEBYPORT: + if (riPortRule.m_nPortNumber == uExternal && riPortRule.m_byProtocol == ucProt){ + if (outNSPMP != NULL) + *outNSPMP = pNSPM; + return true; + } + break; + case FOC_FINDRULEBYNAME: + if (riPortRule.m_strRuleName == CString(bstrName)){ + if (outNSPMP != NULL) + *outNSPMP = pNSPM; + return true; + } + break; + case FOC_DELETERULEEXCACT: + if (riPortRule.m_strRuleName == CString(bstrName) + && riPortRule.m_nPortNumber == uExternal && riPortRule.m_byProtocol == ucProt) + { + RETURN_ON_FAIL(pNSC->RemovePortMapping(pNSPM)); + theApp.QueueDebugLogLine(false,_T("Rule removed")); + } + break; + case FOC_DELETERULEBYNAME: + if (riPortRule.m_strRuleName == CString(bstrName)){ + RETURN_ON_FAIL(pNSC->RemovePortMapping(pNSPM)); + theApp.QueueDebugLogLine(false,_T("Rule removed")); + } + break; + default: + ASSERT( false ); + } + } + var.Clear(); + } + + switch(eAction){ + case FOC_DELETERULEBYNAME: + case FOC_DELETERULEEXCACT: + return true; + case FOC_FINDRULEBYPORT: + case FOC_FINDRULEBYNAME: + default: + return false; + } +} + +bool CFirewallOpener::RemoveRule(const CString strName){ + return DoAction(FOC_DELETERULEBYNAME, CICSRuleInfo(0, 0, strName) ); +} + +bool CFirewallOpener::RemoveRule(const CICSRuleInfo& riPortRule){ + return DoAction(FOC_DELETERULEEXCACT, riPortRule); +} + +bool CFirewallOpener::DoesRuleExist(const CString strName){ + return DoAction(FOC_FINDRULEBYNAME, CICSRuleInfo(0, 0, strName) ); +} + +bool CFirewallOpener::DoesRuleExist(const uint16 nPortNumber,const uint8 byProtocol){ + return DoAction(FOC_FINDRULEBYPORT, CICSRuleInfo(nPortNumber, byProtocol, _T("")) ); +} + +bool CFirewallOpener::OpenPort(const uint16 nPortNumber,const uint8 byProtocol,const CString strRuleName, const bool bRemoveOnExit){ + return DoAction(FOC_ADDRULE, CICSRuleInfo(nPortNumber, byProtocol, strRuleName, bRemoveOnExit)); +} + +bool CFirewallOpener::OpenPort(const CICSRuleInfo& riPortRule){ + return DoAction(FOC_ADDRULE, riPortRule); +} + +bool CFirewallOpener::DoesFWConnectionExist(){ + return DoAction(FOC_FWCONNECTIONEXISTS, CICSRuleInfo()); +} \ No newline at end of file diff --git a/FirewallOpener.h b/FirewallOpener.h new file mode 100644 index 00000000..c4b406af --- /dev/null +++ b/FirewallOpener.h @@ -0,0 +1,105 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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 + +// class to configure the ICS-Firewall of Windows XP - will not work with WinXP-SP2 yet + +#pragma once +#include +#include +#include + +typedef _com_ptr_t<_com_IIID > INetSharingEveryConnectionCollectionPtr; +typedef _com_ptr_t<_com_IIID > INetConnectionPtr; +typedef _com_ptr_t<_com_IIID > INetConnectionPropsPtr; +typedef _com_ptr_t<_com_IIID > INetSharingPortMappingPtr; +typedef _com_ptr_t<_com_IIID > INetSharingConfigurationPtr; +typedef _com_ptr_t<_com_IIID > INetSharingPortMappingPropsPtr; +typedef _com_ptr_t<_com_IIID > INetSharingPortMappingCollectionPtr; + +enum EFOCAction{ + FOC_ADDRULE, + FOC_DELETERULEBYNAME, + FOC_FINDRULEBYNAME, + FOC_FINDRULEBYPORT, + FOC_DELETERULEEXCACT, + FOC_FWCONNECTIONEXISTS +}; + +#define EMULE_DEFAULTRULENAME_UDP _T("eMule_UDP_Port") +#define EMULE_DEFAULTRULENAME_TCP _T("eMule_TCP_Port") + +#define NAT_PROTOCOL_TCP 6 +#define NAT_PROTOCOL_UDP 17 + +/////////////////////////////////////////////////////////////////////////////////////// +/// CICSRuleInfo +class CICSRuleInfo{ +public: + CICSRuleInfo() {} + CICSRuleInfo(const CICSRuleInfo& ri) {*this = ri;} + CICSRuleInfo(uint16 nPortNumber, uint8 byProtocol, CString strRuleName, bool bRemoveOnExit = false) + { + m_nPortNumber = nPortNumber; + m_byProtocol = byProtocol; + m_strRuleName = strRuleName; + m_bRemoveOnExit = bRemoveOnExit; + } + + CICSRuleInfo& operator=(const CICSRuleInfo& ri) + { + m_nPortNumber = ri.m_nPortNumber; + m_byProtocol = ri.m_byProtocol; + m_strRuleName = ri.m_strRuleName; + m_bRemoveOnExit = ri.m_bRemoveOnExit; + return *this; + } + + uint16 m_nPortNumber; + uint8 m_byProtocol; + bool m_bRemoveOnExit; + CString m_strRuleName; +}; + +/////////////////////////////////////////////////////////////////////////////////////// +/// CFirewallOpener + +class CFirewallOpener +{ +public: + CFirewallOpener(void); + ~CFirewallOpener(void); + bool OpenPort(const CICSRuleInfo& riPortRule); + bool OpenPort(const uint16 nPortNumber,const uint8 byProtocol,const CString strRuleName, const bool bRemoveOnExit = false); + bool RemoveRule(const CString strName); + bool RemoveRule(const CICSRuleInfo& riPortRule); + bool DoesRuleExist(const CString strName); + bool DoesRuleExist(const uint16 nPortNumber,const uint8 byProtocol); + bool DoesFWConnectionExist(); + void UnInit(); + bool Init(bool bPreInit = false); + +protected: + + bool AddRule(const CICSRuleInfo& riPortRule, const INetSharingConfigurationPtr pNSC, const INetConnectionPropsPtr pNCP); + bool DoAction(const EFOCAction eAction, const CICSRuleInfo& riPortRule); + bool FindRule(const EFOCAction eAction, const CICSRuleInfo& riPortRule, const INetSharingConfigurationPtr pNSC, INetSharingPortMappingPropsPtr* outNSPMP); + + CArray m_liAddedRules; + +private: + INetSharingManager* m_pINetSM; + bool m_bInited; +}; diff --git a/Flex.skl b/Flex.skl new file mode 100644 index 00000000..58b2da52 --- /dev/null +++ b/Flex.skl @@ -0,0 +1,1551 @@ +/* A lexical scanner generated by flex */ + +/* Scanner skeleton version: + * $Header: /home/daffy/u0/vern/flex/RCS/flex.skl,v 2.91 96/09/10 16:58:48 vern Exp $ + */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 + +%- +#include +#include +%* + +/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */ +#ifdef c_plusplus +#ifndef __cplusplus +#define __cplusplus +#endif +#endif + + +#ifdef __cplusplus + +#include +%+ +#include +class istream; +using std::cin; +using std::cout; +using std::cerr; +%* +#ifndef _WIN32 +#include +#endif + +/* Use prototypes in function declarations. */ +#define YY_USE_PROTOS + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_PROTOS +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef __TURBOC__ + #pragma warn -rch + #pragma warn -use +#include +#include +#define YY_USE_CONST +#define YY_USE_PROTOS +#endif + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + + +#ifdef YY_USE_PROTOS +#define YY_PROTO(proto) proto +#else +#define YY_PROTO(proto) () +#endif + + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yy_start = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yy_start - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#define YY_BUF_SIZE 16384 + +typedef struct yy_buffer_state *YY_BUFFER_STATE; + +extern int yyleng; +%- +extern FILE *yyin, *yyout; +%* + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + +/* The funky do-while in the following #define is used to turn the definition + * int a single C statement (which needs a semi-colon terminator). This + * avoids problems with code like: + * + * if ( condition_holds ) + * yyless( 5 ); + * else + * do_something_else(); + * + * Prior to using the do-while the compiler would get upset at the + * "else" because it interpreted the "if" statement as being all + * done when it reached the ';' after the yyless() call. + */ + +/* Return all but the first 'n' matched characters back to the input stream. */ + +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + *yy_cp = yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yy_c_buf_p = yy_cp = yy_bp + n - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, yytext_ptr ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ +typedef unsigned int yy_size_t; + + +struct yy_buffer_state + { +%- + FILE *yy_input_file; +%+ + istream* yy_input_file; +%* + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + }; + +%- Standard (non-C++) definition +static YY_BUFFER_STATE yy_current_buffer = 0; +%* + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + */ +#define YY_CURRENT_BUFFER yy_current_buffer + + +%- Standard (non-C++) definition +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; + +static int yy_n_chars; /* number of characters read into yy_ch_buf */ + + +int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 1; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void yyrestart YY_PROTO(( FILE *input_file )); + +void yy_switch_to_buffer YY_PROTO(( YY_BUFFER_STATE new_buffer )); +void yy_load_buffer_state YY_PROTO(( void )); +YY_BUFFER_STATE yy_create_buffer YY_PROTO(( FILE *file, int size )); +void yy_delete_buffer YY_PROTO(( YY_BUFFER_STATE b )); +void yy_init_buffer YY_PROTO(( YY_BUFFER_STATE b, FILE *file )); +void yy_flush_buffer YY_PROTO(( YY_BUFFER_STATE b )); +#define YY_FLUSH_BUFFER yy_flush_buffer( yy_current_buffer ) + +YY_BUFFER_STATE yy_scan_buffer YY_PROTO(( char *base, yy_size_t size )); +YY_BUFFER_STATE yy_scan_string YY_PROTO(( yyconst char *yy_str )); +YY_BUFFER_STATE yy_scan_bytes YY_PROTO(( yyconst char *bytes, int len )); +%* + +static void *yy_flex_alloc YY_PROTO(( yy_size_t )); +static void *yy_flex_realloc YY_PROTO(( void *, yy_size_t )); +static void yy_flex_free YY_PROTO(( void * )); + +#define yy_new_buffer yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! yy_current_buffer ) \ + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \ + yy_current_buffer->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! yy_current_buffer ) \ + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \ + yy_current_buffer->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (yy_current_buffer->yy_at_bol) + +%% yytext/yyin/yyout/yy_state_type/yylineno etc. def's & init go here + +%- Standard (non-C++) definition +static yy_state_type yy_get_previous_state YY_PROTO(( void )); +static yy_state_type yy_try_NUL_trans YY_PROTO(( yy_state_type current_state )); +static int yy_get_next_buffer YY_PROTO(( void )); +static void yy_fatal_error YY_PROTO(( yyconst char msg[] )); +%* + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yytext_ptr = yy_bp; \ +%% code to fiddle yytext and yyleng for yymore() goes here + yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ +%% code to copy yytext_ptr to yytext[] goes here, if %array + yy_c_buf_p = yy_cp; + +%% data tables for the DFA and the user's section 1 definitions go here + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap YY_PROTO(( void )); +#else +extern int yywrap YY_PROTO(( void )); +#endif +#endif + +%- +#ifndef YY_NO_UNPUT +static void yyunput YY_PROTO(( int c, char *buf_ptr )); +#endif +%* + +#ifndef yytext_ptr +static void yy_flex_strncpy YY_PROTO(( char *, yyconst char *, int )); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen YY_PROTO(( yyconst char * )); +#endif + +#ifndef YY_NO_INPUT +%- Standard (non-C++) definition +#ifdef __cplusplus +static int yyinput YY_PROTO(( void )); +#else +static int input YY_PROTO(( void )); +#endif +%* +#endif + +#if YY_STACK_USED +static int yy_start_stack_ptr = 0; +static int yy_start_stack_depth = 0; +static int *yy_start_stack = 0; +#ifndef YY_NO_PUSH_STATE +static void yy_push_state YY_PROTO(( int new_state )); +#endif +#ifndef YY_NO_POP_STATE +static void yy_pop_state YY_PROTO(( void )); +#endif +#ifndef YY_NO_TOP_STATE +static int yy_top_state YY_PROTO(( void )); +#endif + +#else +#define YY_NO_PUSH_STATE 1 +#define YY_NO_POP_STATE 1 +#define YY_NO_TOP_STATE 1 +#endif + +#ifdef YY_MALLOC_DECL +YY_MALLOC_DECL +#else +#if __STDC__ +#ifndef __cplusplus +#include +#endif +#else +/* Just try to get by without declaring the routines. This will fail + * miserably on non-ANSI systems for which sizeof(size_t) != sizeof(int) + * or sizeof(void*) != sizeof(int). + */ +#endif +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ + +#ifndef ECHO +%- Standard (non-C++) definition +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( yytext, yyleng, 1, yyout ) +%+ C++ definition +#define ECHO LexerOutput( yytext, yyleng ) +%* +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ +%% fread()/read() definition of YY_INPUT goes here unless we're doing C++ +%+ C++ definition + if ( (result = LexerInput( (char *) buf, max_size )) < 0 ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); +%* +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +%- +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +%+ +#define YY_FATAL_ERROR(msg) LexerError( msg ) +%* +#endif + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +%- Standard (non-C++) definition +#define YY_DECL int yylex YY_PROTO(( void )) +%+ C++ definition +#define YY_DECL int yyFlexLexer::yylex() +%* +#endif + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +%% YY_RULE_SETUP definition goes here + +YY_DECL + { + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +%% user's declarations go here + + if ( yy_init ) + { + yy_init = 0; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yy_start ) + yy_start = 1; /* first start state */ + + if ( ! yyin ) +%- + yyin = stdin; +%+ + yyin = &cin; +%* + + if ( ! yyout ) +%- + yyout = stdout; +%+ + yyout = &cout; +%* + + if ( ! yy_current_buffer ) + yy_current_buffer = + yy_create_buffer( yyin, YY_BUF_SIZE ); + + yy_load_buffer_state(); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { +%% yymore()-related code goes here + yy_cp = yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + +%% code to set up and find next match goes here + +yy_find_action: +%% code to find the action number goes here + + YY_DO_BEFORE_ACTION; + +%% code for yylineno update goes here + +do_action: /* This label is used only to access EOF actions. */ + +%% debug code goes here + + switch ( yy_act ) + { /* beginning of action switch */ +%% actions go here + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between yy_current_buffer and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yy_n_chars = yy_current_buffer->yy_n_chars; + yy_current_buffer->yy_input_file = yyin; + yy_current_buffer->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yy_c_buf_p <= &yy_current_buffer->yy_ch_buf[yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yy_c_buf_p = yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state(); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { +%% code to do back-up for compressed tables and set up yy_cp goes here + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer() ) + { + case EOB_ACT_END_OF_FILE: + { + yy_did_buffer_switch_on_eof = 0; + + if ( yywrap() ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yy_c_buf_p = yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yy_c_buf_p = + yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state(); + + yy_cp = yy_c_buf_p; + yy_bp = yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yy_c_buf_p = + &yy_current_buffer->yy_ch_buf[yy_n_chars]; + + yy_current_state = yy_get_previous_state(); + + yy_cp = yy_c_buf_p; + yy_bp = yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of yylex */ + +%+ +yyFlexLexer::yyFlexLexer( istream* arg_yyin, ostream* arg_yyout ) + { + yyin = arg_yyin; + yyout = arg_yyout; + yy_c_buf_p = 0; + yy_init = 1; + yy_start = 0; + yy_flex_debug = 0; + yylineno = 1; // this will only get updated if %option yylineno + + yy_did_buffer_switch_on_eof = 0; + + yy_looking_for_trail_begin = 0; + yy_more_flag = 0; + yy_more_len = 0; + yy_more_offset = yy_prev_more_offset = 0; + + yy_start_stack_ptr = yy_start_stack_depth = 0; + yy_start_stack = 0; + + yy_current_buffer = 0; + +#ifdef YY_USES_REJECT + yy_state_buf = new yy_state_type[YY_BUF_SIZE + 2]; +#else + yy_state_buf = 0; +#endif + } + +yyFlexLexer::~yyFlexLexer() + { + delete[] yy_state_buf; + yy_delete_buffer( yy_current_buffer ); + } + +void yyFlexLexer::switch_streams( istream* new_in, ostream* new_out ) + { + if ( new_in ) + { + yy_delete_buffer( yy_current_buffer ); + yy_switch_to_buffer( yy_create_buffer( new_in, YY_BUF_SIZE ) ); + } + + if ( new_out ) + yyout = new_out; + } + +#ifdef YY_INTERACTIVE +int yyFlexLexer::LexerInput( char* buf, int /* max_size */ ) +#else +int yyFlexLexer::LexerInput( char* buf, int max_size ) +#endif + { + if ( yyin->eof() || yyin->fail() ) + return 0; + +#ifdef YY_INTERACTIVE + yyin->get( buf[0] ); + + if ( yyin->eof() ) + return 0; + + if ( yyin->bad() ) + return -1; + + return 1; + +#else + (void) yyin->read( buf, max_size ); + + if ( yyin->bad() ) + return -1; + else + return yyin->gcount(); +#endif + } + +void yyFlexLexer::LexerOutput( const char* buf, int size ) + { + (void) yyout->write( buf, size ); + } +%* + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ + +%- +static int yy_get_next_buffer() +%+ +int yyFlexLexer::yy_get_next_buffer() +%* + { + register char *dest = yy_current_buffer->yy_ch_buf; + register char *source = yytext_ptr; + register int number_to_move, i; + int ret_val; + + if ( yy_c_buf_p > &yy_current_buffer->yy_ch_buf[yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( yy_current_buffer->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yy_c_buf_p - yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yy_c_buf_p - yytext_ptr) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + yy_current_buffer->yy_n_chars = yy_n_chars = 0; + + else + { + int num_to_read = + yy_current_buffer->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ +#ifdef YY_USES_REJECT + YY_FATAL_ERROR( +"input buffer overflow, can't enlarge buffer because scanner uses REJECT" ); +#else + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = yy_current_buffer; + + int yy_c_buf_p_offset = + (int) (yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yy_flex_realloc( (void *) b->yy_ch_buf, + b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = yy_current_buffer->yy_buf_size - + number_to_move - 1; +#endif + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&yy_current_buffer->yy_ch_buf[number_to_move]), + yy_n_chars, num_to_read ); + + yy_current_buffer->yy_n_chars = yy_n_chars; + } + + if ( yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + yy_current_buffer->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + yy_n_chars += number_to_move; + yy_current_buffer->yy_ch_buf[yy_n_chars] = YY_END_OF_BUFFER_CHAR; + yy_current_buffer->yy_ch_buf[yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yytext_ptr = &yy_current_buffer->yy_ch_buf[0]; + + return ret_val; + } + + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +%- +static yy_state_type yy_get_previous_state() +%+ +yy_state_type yyFlexLexer::yy_get_previous_state() +%* + { + register yy_state_type yy_current_state; + register char *yy_cp; + +%% code to get the start state into yy_current_state goes here + + for ( yy_cp = yytext_ptr + YY_MORE_ADJ; yy_cp < yy_c_buf_p; ++yy_cp ) + { +%% code to find the next state goes here + } + + return yy_current_state; + } + + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + +%- +#ifdef YY_USE_PROTOS +static yy_state_type yy_try_NUL_trans( yy_state_type yy_current_state ) +#else +static yy_state_type yy_try_NUL_trans( yy_current_state ) +yy_state_type yy_current_state; +#endif +%+ +yy_state_type yyFlexLexer::yy_try_NUL_trans( yy_state_type yy_current_state ) +%* + { + register int yy_is_jam; +%% code to find the next state, and perhaps do backing up, goes here + + return yy_is_jam ? 0 : yy_current_state; + } + + +%- +#ifndef YY_NO_UNPUT +#ifdef YY_USE_PROTOS +static void yyunput( int c, register char *yy_bp ) +#else +static void yyunput( c, yy_bp ) +int c; +register char *yy_bp; +#endif +%+ +void yyFlexLexer::yyunput( int c, register char* yy_bp ) +%* + { + register char *yy_cp = yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yy_hold_char; + + if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = yy_n_chars + 2; + register char *dest = &yy_current_buffer->yy_ch_buf[ + yy_current_buffer->yy_buf_size + 2]; + register char *source = + &yy_current_buffer->yy_ch_buf[number_to_move]; + + while ( source > yy_current_buffer->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + yy_current_buffer->yy_n_chars = + yy_n_chars = yy_current_buffer->yy_buf_size; + + if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + +%% update yylineno here + + yytext_ptr = yy_bp; + yy_hold_char = *yy_cp; + yy_c_buf_p = yy_cp; + } +%- +#endif /* ifndef YY_NO_UNPUT */ +%* + + +%- +#ifdef __cplusplus +static int yyinput() +#else +static int input() +#endif +%+ +int yyFlexLexer::yyinput() +%* + { + int c; + + *yy_c_buf_p = yy_hold_char; + + if ( *yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yy_c_buf_p < &yy_current_buffer->yy_ch_buf[yy_n_chars] ) + /* This was really a NUL. */ + *yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = yy_c_buf_p - yytext_ptr; + ++yy_c_buf_p; + + switch ( yy_get_next_buffer() ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin ); + + /* fall through */ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap() ) + return EOF; + + if ( ! yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yy_c_buf_p = yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yy_c_buf_p; /* cast for 8-bit char's */ + *yy_c_buf_p = '\0'; /* preserve yytext */ + yy_hold_char = *++yy_c_buf_p; + +%% update BOL and yylineno + + return c; + } + + +%- +#ifdef YY_USE_PROTOS +void yyrestart( FILE *input_file ) +#else +void yyrestart( input_file ) +FILE *input_file; +#endif +%+ +void yyFlexLexer::yyrestart( istream* input_file ) +%* + { + if ( ! yy_current_buffer ) + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); + + yy_init_buffer( yy_current_buffer, input_file ); + yy_load_buffer_state(); + } + + +%- +#ifdef YY_USE_PROTOS +void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer ) +#else +void yy_switch_to_buffer( new_buffer ) +YY_BUFFER_STATE new_buffer; +#endif +%+ +void yyFlexLexer::yy_switch_to_buffer( YY_BUFFER_STATE new_buffer ) +%* + { + if ( yy_current_buffer == new_buffer ) + return; + + if ( yy_current_buffer ) + { + /* Flush out information for old buffer. */ + *yy_c_buf_p = yy_hold_char; + yy_current_buffer->yy_buf_pos = yy_c_buf_p; + yy_current_buffer->yy_n_chars = yy_n_chars; + } + + yy_current_buffer = new_buffer; + yy_load_buffer_state(); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yy_did_buffer_switch_on_eof = 1; + } + + +%- +#ifdef YY_USE_PROTOS +void yy_load_buffer_state( void ) +#else +void yy_load_buffer_state() +#endif +%+ +void yyFlexLexer::yy_load_buffer_state() +%* + { + yy_n_chars = yy_current_buffer->yy_n_chars; + yytext_ptr = yy_c_buf_p = yy_current_buffer->yy_buf_pos; + yyin = yy_current_buffer->yy_input_file; + yy_hold_char = *yy_c_buf_p; + } + + +%- +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_create_buffer( FILE *file, int size ) +#else +YY_BUFFER_STATE yy_create_buffer( file, size ) +FILE *file; +int size; +#endif +%+ +YY_BUFFER_STATE yyFlexLexer::yy_create_buffer( istream* file, int size ) +%* + { + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yy_flex_alloc( b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; + } + + +%- +#ifdef YY_USE_PROTOS +void yy_delete_buffer( YY_BUFFER_STATE b ) +#else +void yy_delete_buffer( b ) +YY_BUFFER_STATE b; +#endif +%+ +void yyFlexLexer::yy_delete_buffer( YY_BUFFER_STATE b ) +%* + { + if ( ! b ) + return; + + if ( b == yy_current_buffer ) + yy_current_buffer = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yy_flex_free( (void *) b->yy_ch_buf ); + + yy_flex_free( (void *) b ); + } + + +%- +#ifndef _WIN32 +#include +#else +#ifndef YY_ALWAYS_INTERACTIVE +#ifndef YY_NEVER_INTERACTIVE +extern int isatty YY_PROTO(( int )); +#endif +#endif +#endif + +#ifdef YY_USE_PROTOS +void yy_init_buffer( YY_BUFFER_STATE b, FILE *file ) +#else +void yy_init_buffer( b, file ) +YY_BUFFER_STATE b; +FILE *file; +#endif + +%+ +void yyFlexLexer::yy_init_buffer( YY_BUFFER_STATE b, istream* file ) +%* + + { + yy_flush_buffer( b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + +%- +#if YY_ALWAYS_INTERACTIVE + b->yy_is_interactive = 1; +#else +#if YY_NEVER_INTERACTIVE + b->yy_is_interactive = 0; +#else + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; +#endif +#endif +%+ + b->yy_is_interactive = 0; +%* + } + + +%- +#ifdef YY_USE_PROTOS +void yy_flush_buffer( YY_BUFFER_STATE b ) +#else +void yy_flush_buffer( b ) +YY_BUFFER_STATE b; +#endif + +%+ +void yyFlexLexer::yy_flush_buffer( YY_BUFFER_STATE b ) +%* + { + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == yy_current_buffer ) + yy_load_buffer_state(); + } +%* + + +#ifndef YY_NO_SCAN_BUFFER +%- +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_buffer( char *base, yy_size_t size ) +#else +YY_BUFFER_STATE yy_scan_buffer( base, size ) +char *base; +yy_size_t size; +#endif + { + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b ); + + return b; + } +%* +#endif + + +#ifndef YY_NO_SCAN_STRING +%- +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_string( yyconst char *yy_str ) +#else +YY_BUFFER_STATE yy_scan_string( yy_str ) +yyconst char *yy_str; +#endif + { + int len; + for ( len = 0; yy_str[len]; ++len ) + ; + + return yy_scan_bytes( yy_str, len ); + } +%* +#endif + + +#ifndef YY_NO_SCAN_BYTES +%- +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_bytes( yyconst char *bytes, int len ) +#else +YY_BUFFER_STATE yy_scan_bytes( bytes, len ) +yyconst char *bytes; +int len; +#endif + { + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = len + 2; + buf = (char *) yy_flex_alloc( n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < len; ++i ) + buf[i] = bytes[i]; + + buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; + } +%* +#endif + + +#ifndef YY_NO_PUSH_STATE +%- +#ifdef YY_USE_PROTOS +static void yy_push_state( int new_state ) +#else +static void yy_push_state( new_state ) +int new_state; +#endif +%+ +void yyFlexLexer::yy_push_state( int new_state ) +%* + { + if ( yy_start_stack_ptr >= yy_start_stack_depth ) + { + yy_size_t new_size; + + yy_start_stack_depth += YY_START_STACK_INCR; + new_size = yy_start_stack_depth * sizeof( int ); + + if ( ! yy_start_stack ) + yy_start_stack = (int *) yy_flex_alloc( new_size ); + + else + yy_start_stack = (int *) yy_flex_realloc( + (void *) yy_start_stack, new_size ); + + if ( ! yy_start_stack ) + YY_FATAL_ERROR( + "out of memory expanding start-condition stack" ); + } + + yy_start_stack[yy_start_stack_ptr++] = YY_START; + + BEGIN(new_state); + } +#endif + + +#ifndef YY_NO_POP_STATE +%- +static void yy_pop_state() +%+ +void yyFlexLexer::yy_pop_state() +%* + { + if ( --yy_start_stack_ptr < 0 ) + YY_FATAL_ERROR( "start-condition stack underflow" ); + + BEGIN(yy_start_stack[yy_start_stack_ptr]); + } +#endif + + +#ifndef YY_NO_TOP_STATE +%- +static int yy_top_state() +%+ +int yyFlexLexer::yy_top_state() +%* + { + return yy_start_stack[yy_start_stack_ptr - 1]; + } +#endif + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +%- +#ifdef YY_USE_PROTOS +static void yy_fatal_error( yyconst char msg[] ) +#else +static void yy_fatal_error( msg ) +char msg[]; +#endif + { + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); + } + +%+ + +void yyFlexLexer::LexerError( yyconst char msg[] ) + { + cerr << msg << '\n'; + exit( YY_EXIT_FAILURE ); + } +%* + + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + yytext[yyleng] = yy_hold_char; \ + yy_c_buf_p = yytext + n; \ + yy_hold_char = *yy_c_buf_p; \ + *yy_c_buf_p = '\0'; \ + yyleng = n; \ + } \ + while ( 0 ) + + +/* Internal utility routines. */ + +#ifndef yytext_ptr +#ifdef YY_USE_PROTOS +static void yy_flex_strncpy( char *s1, yyconst char *s2, int n ) +#else +static void yy_flex_strncpy( s1, s2, n ) +char *s1; +yyconst char *s2; +int n; +#endif + { + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; + } +#endif + +#ifdef YY_NEED_STRLEN +#ifdef YY_USE_PROTOS +static int yy_flex_strlen( yyconst char *s ) +#else +static int yy_flex_strlen( s ) +yyconst char *s; +#endif + { + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; + } +#endif + + +#ifdef YY_USE_PROTOS +static void *yy_flex_alloc( yy_size_t size ) +#else +static void *yy_flex_alloc( size ) +yy_size_t size; +#endif + { + return (void *) malloc( size ); + } + +#ifdef YY_USE_PROTOS +static void *yy_flex_realloc( void *ptr, yy_size_t size ) +#else +static void *yy_flex_realloc( ptr, size ) +void *ptr; +yy_size_t size; +#endif + { + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); + } + +#ifdef YY_USE_PROTOS +static void yy_flex_free( void *ptr ) +#else +static void yy_flex_free( ptr ) +void *ptr; +#endif + { + free( ptr ); + } + +#if YY_MAIN +int main() + { + yylex(); + return 0; + } +#endif diff --git a/FrameGrabThread.cpp b/FrameGrabThread.cpp new file mode 100644 index 00000000..473b3ac4 --- /dev/null +++ b/FrameGrabThread.cpp @@ -0,0 +1,257 @@ +//this file is part of eMule +//Copyright (C)2003 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "FrameGrabThread.h" +#include "CxImage/xImage.h" +#include "OtherFunctions.h" +#include "quantize.h" +#ifndef HAVE_QEDIT_H +// This is a remote feature and not optional, in order to keep to working properly for other clients who want to use it +// Check emule_site_config.h to fix it +#error Missing 'qedit.h', look at "emule_site_config.h" for further information. +#endif + +// DirectShow MediaDet +#include +//#include +#define _DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID DECLSPEC_SELECTANY name \ + = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +_DEFINE_GUID(MEDIATYPE_Video, 0x73646976, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +_DEFINE_GUID(MEDIATYPE_Audio, 0x73647561, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +_DEFINE_GUID(FORMAT_VideoInfo,0x05589f80, 0xc356, 0x11ce, 0xbf, 0x01, 0x00, 0xaa, 0x00, 0x55, 0x59, 0x5a); +_DEFINE_GUID(FORMAT_WaveFormatEx,0x05589f81, 0xc356, 0x11ce, 0xbf, 0x01, 0x00, 0xaa, 0x00, 0x55, 0x59, 0x5a); +//#define MMNODRV // mmsystem: Installable driver support +#define MMNOSOUND // mmsystem: Sound support +//#define MMNOWAVE // mmsystem: Waveform support +#define MMNOMIDI // mmsystem: MIDI support +#define MMNOAUX // mmsystem: Auxiliary audio support +#define MMNOMIXER // mmsystem: Mixer support +#define MMNOTIMER // mmsystem: Timer support +#define MMNOJOY // mmsystem: Joystick support +#define MMNOMCI // mmsystem: MCI support +//#define MMNOMMIO // mmsystem: Multimedia file I/O support +#define MMNOMMSYSTEM // mmsystem: General MMSYSTEM functions +// NOTE: If you get a compile error due to missing 'qedit.h', look at "emule_site_config.h" for further information. +#include +typedef struct tagVIDEOINFOHEADER { + RECT rcSource; // The bit we really want to use + RECT rcTarget; // Where the video should go + DWORD dwBitRate; // Approximate bit data rate + DWORD dwBitErrorRate; // Bit error rate for this stream + REFERENCE_TIME AvgTimePerFrame; // Average time per frame (100ns units) + BITMAPINFOHEADER bmiHeader; +} VIDEOINFOHEADER; +#include "emuledlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +IMPLEMENT_DYNCREATE(CFrameGrabThread, CWinThread) + +CFrameGrabThread::CFrameGrabThread() +{ +} + +CFrameGrabThread::~CFrameGrabThread() +{ +} + +BOOL CFrameGrabThread::InitInstance() +{ + DbgSetThreadName("FrameGrabThread"); + InitThreadLocale(); + return TRUE; +} + +BOOL CFrameGrabThread::Run(){ + imgResults = new CxImage*[nFramesToGrab]; + FrameGrabResult_Struct* result = new FrameGrabResult_Struct; + CoInitialize(NULL); + result->nImagesGrabbed = (uint8)GrabFrames(); + CoUninitialize(); + result->imgResults = imgResults; + result->pSender = pSender; + if (!PostMessage(theApp.emuledlg->m_hWnd,TM_FRAMEGRABFINISHED, (WPARAM)pOwner,(LPARAM)result)) { + for (int i = 0; i < result->nImagesGrabbed; i++) + delete result->imgResults[i]; + delete[] result->imgResults; + delete result; + } + return 0; +} + +UINT CFrameGrabThread::GrabFrames(){ + #define TIMEBETWEENFRAMES 50.0 // could be a param later, if needed + for (int i = 0; i!= nFramesToGrab; i++) + imgResults[i] = NULL; + try{ + HRESULT hr; + CComPtr pDet; + hr = pDet.CoCreateInstance(__uuidof(MediaDet)); + if (!SUCCEEDED(hr)) + return 0; + + // Convert the file name to a BSTR. + CComBSTR bstrFilename(strFileName); + hr = pDet->put_Filename(bstrFilename); + + long lStreams; + bool bFound = false; + hr = pDet->get_OutputStreams(&lStreams); + for (long i = 0; i < lStreams; i++) + { + GUID major_type; + hr = pDet->put_CurrentStream(i); + hr = pDet->get_StreamType(&major_type); + if (major_type == MEDIATYPE_Video) + { + bFound = true; + break; + } + } + + if (!bFound) + return 0; + + double dLength = 0; + pDet->get_StreamLength(&dLength); + if (dStartTime > dLength) + dStartTime = 0; + + long width = 0, height = 0; + AM_MEDIA_TYPE mt; + hr = pDet->get_StreamMediaType(&mt); + if (mt.formattype == FORMAT_VideoInfo) + { + VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)(mt.pbFormat); + width = pVih->bmiHeader.biWidth; + height = pVih->bmiHeader.biHeight; + + + // We want the absolute height, don't care about orientation. + if (height < 0) height *= -1; + } + else { + return 0; // Should not happen, in theory. + } + + /*FreeMediaType(mt); = */ + if (mt.cbFormat != 0){ + CoTaskMemFree((PVOID)mt.pbFormat); + mt.cbFormat = 0; + mt.pbFormat = NULL; + } + if (mt.pUnk != NULL){ + mt.pUnk->Release(); + mt.pUnk = NULL; + } + /**/ + + + long size; + uint32 nFramesGrabbed; + for (nFramesGrabbed = 0; nFramesGrabbed != nFramesToGrab; nFramesGrabbed++){ + hr = pDet->GetBitmapBits(dStartTime + (nFramesGrabbed*TIMEBETWEENFRAMES), &size, NULL, width, height); + if (SUCCEEDED(hr)) + { + // we could also directly create a Bitmap in memory, however this caused problems/failed with *some* movie files + // when I tried it for the MMPreview, while this method works always - so I'll continue to use this one + long nFullBufferLen = sizeof( BITMAPFILEHEADER ) + size; + char* buffer = new char[nFullBufferLen]; + + BITMAPFILEHEADER bfh; + memset( &bfh, 0, sizeof( bfh ) ); + bfh.bfType = 'MB'; + bfh.bfSize = nFullBufferLen; + bfh.bfOffBits = sizeof( BITMAPINFOHEADER ) + sizeof( BITMAPFILEHEADER ); + memcpy(buffer,&bfh,sizeof( bfh ) ); + + try { + hr = pDet->GetBitmapBits(dStartTime+ (nFramesGrabbed*TIMEBETWEENFRAMES), NULL, buffer + sizeof( bfh ), width, height); + } + catch (...) { + ASSERT(0); + hr = E_FAIL; + } + if (SUCCEEDED(hr)) + { + // decode + CxImage* imgResult = new CxImage(); + imgResult->Decode((BYTE*)buffer, nFullBufferLen, CXIMAGE_FORMAT_BMP); + delete[] buffer; + if (!imgResult->IsValid()){ + delete imgResult; + break; + } + + // resize if needed + if (nMaxWidth > 0 && nMaxWidth < width){ + float scale = (float)nMaxWidth / imgResult->GetWidth(); + int nMaxHeigth = (int)(imgResult->GetHeight() * scale); + imgResult->Resample(nMaxWidth, nMaxHeigth, 0); + } + + // decrease bpp if needed + if (bReduceColor){ + RGBQUAD* ppal=(RGBQUAD*)malloc(256*sizeof(RGBQUAD)); + if (ppal) { + CQuantizer q(256,8); + q.ProcessImage(imgResult->GetDIB()); + q.SetColorTable(ppal); + imgResult->DecreaseBpp(8, true, ppal); + free(ppal); + } + } + + //CString TestName; + //TestName.Format("G:\\testframe%i.png",nFramesGrabbed); + //imgResult->Save(TestName,CXIMAGE_FORMAT_PNG); + // done + imgResults[nFramesGrabbed] = imgResult; + } + else{ + delete[] buffer; + break; + } + } + } + return nFramesGrabbed; + } + catch(...){ + ASSERT(0); + return 0; + } +} + +void CFrameGrabThread::SetValues(const CKnownFile* in_pOwner, CString in_strFileName,uint8 in_nFramesToGrab, double in_dStartTime, bool in_bReduceColor, uint16 in_nMaxWidth, void* in_pSender){ + strFileName =in_strFileName; + nFramesToGrab = in_nFramesToGrab; + dStartTime = in_dStartTime; + bReduceColor = in_bReduceColor; + nMaxWidth = in_nMaxWidth; + pOwner = in_pOwner; + pSender = in_pSender; +} + +BEGIN_MESSAGE_MAP(CFrameGrabThread, CWinThread) +END_MESSAGE_MAP() diff --git a/FrameGrabThread.h b/FrameGrabThread.h new file mode 100644 index 00000000..c5f206ae --- /dev/null +++ b/FrameGrabThread.h @@ -0,0 +1,53 @@ +//this file is part of eMule +//Copyright (C)2003 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +// +//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. +#pragma once + +class CxImage; +class CKnownFile; + +struct FrameGrabResult_Struct{ + CxImage** imgResults; + uint8 nImagesGrabbed; + void* pSender; +}; + +// CPreview + +class CFrameGrabThread : public CWinThread +{ + DECLARE_DYNCREATE(CFrameGrabThread) + +protected: + CFrameGrabThread(); // protected constructor used by dynamic creation + virtual ~CFrameGrabThread(); + DECLARE_MESSAGE_MAP() + UINT GrabFrames(); +public: + virtual BOOL InitInstance(); + virtual int Run(); + void SetValues(const CKnownFile* in_pOwner,CString in_strFileName,uint8 in_nFramesToGrab, double in_dStartTime, bool in_bReduceColor, uint16 in_nMaxWidth, void* pSender); + +private: + CString strFileName; + uint8 nFramesToGrab; + CxImage** imgResults; + double dStartTime; + bool bReduceColor; + uint16 nMaxWidth; + const CKnownFile* pOwner; + void* pSender; +}; diff --git a/Friend.cpp b/Friend.cpp new file mode 100644 index 00000000..805b0c11 --- /dev/null +++ b/Friend.cpp @@ -0,0 +1,457 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "Friend.h" +#include "FriendList.h" +#include "OtherFunctions.h" +#include "UpDownClient.h" +#include "Packets.h" +#include "SafeFile.h" +#include "clientlist.h" +#include "ListenSocket.h" +#include "Kademlia/Kademlia/Kademlia.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +CFriend::CFriend(void) +{ + m_dwLastSeen = 0; + m_dwLastUsedIP = 0; + m_nLastUsedPort = 0; + m_dwLastChatted = 0; + (void)m_strName; + m_LinkedClient = 0; + md4clr(m_abyUserhash); + md4clr(m_abyKadID); + m_FriendConnectState = FCS_NONE; + m_dwLastKadSearch = 0; + + m_friendSlot = false; +} + +//Added this to work with the IRC.. Probably a better way to do it.. But wanted this in the release.. +CFriend::CFriend(const uchar* abyUserhash, uint32 dwLastSeen, uint32 dwLastUsedIP, uint16 nLastUsedPort, + uint32 dwLastChatted, LPCTSTR pszName, uint32 dwHasHash){ + m_dwLastSeen = dwLastSeen; + m_dwLastUsedIP = dwLastUsedIP; + m_nLastUsedPort = nLastUsedPort; + m_dwLastChatted = dwLastChatted; + if(dwHasHash && abyUserhash){ + md4cpy(m_abyUserhash,abyUserhash); + } + else + md4clr(m_abyUserhash); + md4clr(m_abyKadID); + m_strName = pszName; + m_LinkedClient = 0; + m_friendSlot = false; + m_FriendConnectState = FCS_NONE; + m_dwLastKadSearch = 0; +} + +CFriend::CFriend(CUpDownClient* client){ + ASSERT ( client ); + m_dwLastSeen = time(NULL); + m_dwLastUsedIP = client->GetIP(); + m_nLastUsedPort = client->GetUserPort(); + m_dwLastChatted = 0; + m_LinkedClient = NULL; + m_friendSlot = false; + m_FriendConnectState = FCS_NONE; + m_dwLastKadSearch = 0; + md4clr(m_abyKadID); + md4cpy(m_abyUserhash, client->GetUserHash()); + SetLinkedClient(client); +} + +CFriend::~CFriend(void) +{ + if(GetLinkedClient(true) != NULL) { + m_LinkedClient->SetFriendSlot(false); + m_LinkedClient->m_Friend = NULL; + m_LinkedClient = NULL; + } + // remove any possible pending kad request + if (Kademlia::CKademlia::IsRunning()) + Kademlia::CKademlia::CancelClientSearch(*this); +} + +void CFriend::LoadFromFile(CFileDataIO* file) +{ + file->ReadHash16(m_abyUserhash); + m_dwLastUsedIP = file->ReadUInt32(); + m_nLastUsedPort = file->ReadUInt16(); + m_dwLastSeen = file->ReadUInt32(); + m_dwLastChatted = file->ReadUInt32(); + + UINT tagcount = file->ReadUInt32(); + for (UINT j = 0; j < tagcount; j++){ + CTag* newtag = new CTag(file, false); + switch (newtag->GetNameID()){ + case FF_NAME:{ + ASSERT( newtag->IsStr() ); + if (newtag->IsStr()){ + if (m_strName.IsEmpty()) + m_strName = newtag->GetStr(); + } + break; + } + case FF_KADID:{ + ASSERT( newtag->IsHash() ); + if (newtag->IsHash()) + md4cpy(m_abyKadID, newtag->GetHash()); + break; + } + } + delete newtag; + } +} + +void CFriend::WriteToFile(CFileDataIO* file) +{ + file->WriteHash16(m_abyUserhash); + file->WriteUInt32(m_dwLastUsedIP); + file->WriteUInt16(m_nLastUsedPort); + file->WriteUInt32(m_dwLastSeen); + file->WriteUInt32(m_dwLastChatted); + + uint32 uTagCount = 0; + ULONG uTagCountFilePos = (ULONG)file->GetPosition(); + file->WriteUInt32(uTagCount); + + if (!m_strName.IsEmpty()){ + CTag nametag(FF_NAME, m_strName); + nametag.WriteTagToFile(file, utf8strOptBOM); + uTagCount++; + } + if (HasKadID()){ + CTag tag(FF_KADID, (const BYTE*)m_abyKadID); + tag.WriteNewEd2kTag(file); + uTagCount++; + } + + file->Seek(uTagCountFilePos, CFile::begin); + file->WriteUInt32(uTagCount); + file->Seek(0, CFile::end); +} + +bool CFriend::HasUserhash() const +{ + return isnulmd4(m_abyUserhash) == 0; +} + +bool CFriend::HasKadID() const +{ + return isnulmd4(m_abyKadID) == 0; +} + +void CFriend::SetFriendSlot(bool newValue) { + if(GetLinkedClient() != NULL) { + m_LinkedClient->SetFriendSlot(newValue); + } + + m_friendSlot = newValue; +} + +bool CFriend::GetFriendSlot() const { + if(GetLinkedClient() != NULL) { + return m_LinkedClient->GetFriendSlot(); + } else { + return m_friendSlot; + } +} + +void CFriend::SetLinkedClient(CUpDownClient* linkedClient) { + if(linkedClient != m_LinkedClient) { + if(linkedClient != NULL) { + if(m_LinkedClient == NULL) { + linkedClient->SetFriendSlot(m_friendSlot); + } else { + linkedClient->SetFriendSlot(m_LinkedClient->GetFriendSlot()); + } + + m_dwLastSeen = time(NULL); + m_dwLastUsedIP = linkedClient->GetConnectIP(); + m_nLastUsedPort = linkedClient->GetUserPort(); + m_strName = linkedClient->GetUserName(); + md4cpy(m_abyUserhash,linkedClient->GetUserHash()); + + linkedClient->m_Friend = this; + } + else if(m_LinkedClient != NULL) { + m_friendSlot = m_LinkedClient->GetFriendSlot(); + } + + if(m_LinkedClient != NULL) { + // the old client is no longer friend, since it is no longer the linked client + m_LinkedClient->SetFriendSlot(false); + m_LinkedClient->m_Friend = NULL; + } + + m_LinkedClient = linkedClient; + } + theApp.friendlist->RefreshFriend(this); +} + +CUpDownClient* CFriend::GetLinkedClient(bool bValidCheck) const +{ + if (bValidCheck && m_LinkedClient != NULL && !theApp.clientlist->IsValidClient(m_LinkedClient)){ + ASSERT( false ); + return NULL; + } + return m_LinkedClient; +}; + +CUpDownClient* CFriend::GetClientForChatSession() +{ + CUpDownClient* pResult; + if (GetLinkedClient(true) != NULL) + pResult = GetLinkedClient(false); + else{ + pResult = new CUpDownClient(0, m_nLastUsedPort, m_dwLastUsedIP, 0, 0, true); + pResult->SetUserName(m_strName); + pResult->SetUserHash(m_abyUserhash); + theApp.clientlist->AddClient(pResult); + SetLinkedClient(pResult); + } + pResult->SetChatState(MS_CHATTING); + return pResult; +}; + +bool CFriend::TryToConnect(CFriendConnectionListener* pConnectionReport) +{ + if (m_FriendConnectState != FCS_NONE){ + m_liConnectionReport.AddTail(pConnectionReport); + return true; + } + if (isnulmd4(m_abyKadID) && (m_dwLastUsedIP == 0 || m_nLastUsedPort == 0) + && (GetLinkedClient() == NULL || GetLinkedClient()->GetIP() == 0 || GetLinkedClient()->GetUserPort() == 0)) + { + pConnectionReport->ReportConnectionProgress(m_LinkedClient, _T("*** ") + GetResString(IDS_CONNECTING), false); + pConnectionReport->ConnectingResult(GetLinkedClient(), false); + return false; + } + + m_liConnectionReport.AddTail(pConnectionReport); + if (GetLinkedClient(true) == NULL) + { + //ASSERT( pConnectionReport != &theApp.emuledlg->chatwnd->chatselector ); // shouldn't happen, if the chat connector calls, we he always should have a client for the session already + ASSERT( false ); + GetClientForChatSession(); + } + ASSERT( GetLinkedClient(true) != NULL ); + m_FriendConnectState = FCS_CONNECTING; + m_LinkedClient->SetChatState(MS_CONNECTING); + if (m_LinkedClient->socket != NULL && m_LinkedClient->socket->IsConnected()) + { + // this client is already connected, but we need to check if it has also passed the secureident already + UpdateFriendConnectionState(FCR_ESTABLISHED); + } + // otherwise (standard case) try to connect + pConnectionReport->ReportConnectionProgress(m_LinkedClient, _T("*** ") + GetResString(IDS_CONNECTING), false); + m_LinkedClient->TryToConnect(true); + return true; +} +void CFriend::UpdateFriendConnectionState(EFriendConnectReport eEvent) +{ +/*#ifdef _DEBUG + CString strDbg; + strDbg.Format(_T("*** Debug: UpdateFriendConnectionState, Report: %u, CurrentState: %u \n"), eEvent, m_FriendConnectState); + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0;) + m_liConnectionReport.GetNext(pos)->ReportConnectionProgress(GetLinkedClient(), strDbg, false); +#endif*/ + if (m_FriendConnectState == FCS_NONE || GetLinkedClient(true) == NULL){ + // we aren't currently trying to build up a friendconnection, we shouldn't be called + ASSERT( false ); + return; + } + switch (eEvent){ + case FCR_ESTABLISHED: + case FCR_USERHASHVERIFIED: + // connection established, userhash fits, check secureident + if (GetLinkedClient()->HasPassedSecureIdent(true)) { + // well here we are done, connecting worked out fine + m_FriendConnectState = FCS_NONE; + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0;) + m_liConnectionReport.GetNext(pos)->ConnectingResult(GetLinkedClient(), true); + m_liConnectionReport.RemoveAll(); + FindKadID(); // fetch the kadid of this friend if we don't have it already + } + else + { + ASSERT( eEvent != FCR_USERHASHVERIFIED ); + // we connected, the userhash matches, now we wait for the authentification + // nothing todo, just report about it + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0; m_liConnectionReport.GetNext(pos)){ + m_liConnectionReport.GetAt(pos)->ReportConnectionProgress(GetLinkedClient(), _T(" ...") + GetResString(IDS_TREEOPTIONS_OK) + _T("\n"), true); + m_liConnectionReport.GetAt(pos)->ReportConnectionProgress(GetLinkedClient(), _T("*** ") + CString(_T("Authenticating friend")) /*to stringlist*/, false); + } + if (m_FriendConnectState == FCS_CONNECTING) + m_FriendConnectState = FCS_AUTH; + else{ // client must have connected to use while we tried something else (like search for him an kad) + ASSERT( false ); + m_FriendConnectState = FCS_AUTH; + } + } + break; + case FCR_DISCONNECTED: + // disconnected, lets see which state we were in + if (m_FriendConnectState == FCS_CONNECTING || m_FriendConnectState == FCS_AUTH){ + if (m_FriendConnectState == FCS_CONNECTING && Kademlia::CKademlia::IsRunning() + && Kademlia::CKademlia::IsConnected() && !isnulmd4(m_abyKadID) + && (m_dwLastKadSearch == 0 || ::GetTickCount() - m_dwLastKadSearch > MIN2MS(10))) + { + // connecting failed to the last known IP, now we search kad for an updated IP of our friend + m_FriendConnectState = FCS_KADSEARCHING; + m_dwLastKadSearch = ::GetTickCount(); + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0; m_liConnectionReport.GetNext(pos)){ + m_liConnectionReport.GetAt(pos)->ReportConnectionProgress(GetLinkedClient(), _T(" ...") + GetResString(IDS_FAILED) + _T("\n"), true); + m_liConnectionReport.GetAt(pos)->ReportConnectionProgress(GetLinkedClient(), _T("*** ") + GetResString(IDS_SEARCHINGFRIENDKAD), false); + } + Kademlia::CKademlia::FindIPByNodeID(*this, m_abyKadID); + break; + } + m_FriendConnectState = FCS_NONE; + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0;) + m_liConnectionReport.GetNext(pos)->ConnectingResult(GetLinkedClient(), false); + m_liConnectionReport.RemoveAll(); + } + else // FCS_KADSEARCHING, shouldn't happen + ASSERT( false ); + break; + case FCR_USERHASHFAILED:{ + // the client we connected to, had a different userhash then we expected + // drop the linked client object and create a new one, because we don't want to have anything todo + // with this instance as it is not our friend which we try to connect to + // the connection try counts as failed + CUpDownClient* pOld = m_LinkedClient; + SetLinkedClient(NULL); // removing old one + GetClientForChatSession(); // creating new instance with the hash we search for + m_LinkedClient->SetChatState(MS_CONNECTING); + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0;) // inform others about the change + m_liConnectionReport.GetNext(pos)->ClientObjectChanged(pOld, GetLinkedClient()); + pOld->SetChatState(MS_NONE); + + if (m_FriendConnectState == FCS_CONNECTING || m_FriendConnectState == FCS_AUTH){ + ASSERT( m_FriendConnectState == FCS_AUTH ); + // todo: kad here + m_FriendConnectState = FCS_NONE; + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0;) + m_liConnectionReport.GetNext(pos)->ConnectingResult(GetLinkedClient(), false); + m_liConnectionReport.RemoveAll(); + } + else // FCS_KADSEARCHING, shouldn't happen + ASSERT( false ); + break; + } + case FCR_SECUREIDENTFAILED: + // the client has the fitting userhash, but failed secureident - so we don't want to talk to him + // we stop our search here in any case, multiple clientobjects with the same userhash would mess with other things + // and its unlikely that we would find him on kad in this case too + ASSERT( m_FriendConnectState == FCS_AUTH ); + m_FriendConnectState = FCS_NONE; + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0;) + m_liConnectionReport.GetNext(pos)->ConnectingResult(GetLinkedClient(), false); + m_liConnectionReport.RemoveAll(); + break; + case FCR_DELETED: + // mh, this should actually never happen i'm sure + // todo: in any case, stop any connection tries, notify other etc + m_FriendConnectState = FCS_NONE; + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0;) + m_liConnectionReport.GetNext(pos)->ConnectingResult(GetLinkedClient(), false); + m_liConnectionReport.RemoveAll(); + break; + default: + ASSERT( false ); + } +} + +void CFriend::FindKadID(){ + if (!HasKadID() && Kademlia::CKademlia::IsRunning() && GetLinkedClient(true) != NULL + && GetLinkedClient()->GetKadPort() != 0 && GetLinkedClient()->GetKadVersion() >= 2) + { + DebugLog(_T("Searching KadID for friend %s by IP %s"), m_strName.IsEmpty() ? _T("(Unknown)") : m_strName, ipstr(GetLinkedClient()->GetConnectIP())); + Kademlia::CKademlia::FindNodeIDByIP(*this, GetLinkedClient()->GetConnectIP(), GetLinkedClient()->GetUserPort(), GetLinkedClient()->GetKadPort()); + } +} + +void CFriend::KadSearchNodeIDByIPResult(Kademlia::EKadClientSearchRes eStatus, const uchar* pachNodeID){ + if (!theApp.friendlist->IsValid(this)) + { + ASSERT( false ); + return; + } + if (eStatus == Kademlia::KCSR_SUCCEEDED){ + ASSERT( pachNodeID != NULL ); + DebugLog(_T("Successfully fetched KadID (%s) for friend %s"), md4str(pachNodeID), m_strName.IsEmpty() ? _T("(Unknown)") : m_strName); + md4cpy(m_abyKadID, pachNodeID); + } + else + DebugLog(_T("Failed to fetch KadID for friend %s (%s)"), m_strName.IsEmpty() ? _T("(Unknown)") : m_strName, ipstr(m_dwLastUsedIP)); +} + +void CFriend::KadSearchIPByNodeIDResult(Kademlia::EKadClientSearchRes eStatus, uint32 dwIP, uint16 nPort){ + if (!theApp.friendlist->IsValid(this)) + { + ASSERT( false ); + return; + } + if (m_FriendConnectState == FCS_KADSEARCHING ){ + if (eStatus == Kademlia::KCSR_SUCCEEDED && GetLinkedClient(true) != NULL){ + DebugLog(_T("Successfully fetched IP (%s) by KadID (%s) for friend %s"), ipstr(dwIP), md4str(m_abyKadID), m_strName.IsEmpty() ? _T("(Unknown)") : m_strName); + if (GetLinkedClient()->GetIP() != dwIP || GetLinkedClient()->GetUserPort() != nPort){ + // retry to connect with our new found IP + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0; m_liConnectionReport.GetNext(pos)){ + m_liConnectionReport.GetAt(pos)->ReportConnectionProgress(GetLinkedClient(), _T(" ...") + GetResString(IDS_FOUND) + _T("\n"), true); + m_liConnectionReport.GetAt(pos)->ReportConnectionProgress(m_LinkedClient, _T("*** ") + GetResString(IDS_CONNECTING), false); + } + m_FriendConnectState = FCS_CONNECTING; + m_LinkedClient->SetChatState(MS_CONNECTING); + if (m_LinkedClient->socket != NULL && m_LinkedClient->socket->IsConnected()) + { + // we shouldnt get he since we checked for FCS_KADSEARCHING + ASSERT( false ); + UpdateFriendConnectionState(FCR_ESTABLISHED); + } + m_dwLastUsedIP = dwIP; + m_nLastUsedPort = nPort; + m_LinkedClient->SetIP(dwIP); + m_LinkedClient->SetUserPort(nPort); + m_LinkedClient->TryToConnect(true); + return; + } + else + DebugLog(_T("KadSearchIPByNodeIDResult: Result IP is the same as known (not working) IP (%s)"), ipstr(dwIP)); + } + DebugLog(_T("Failed to fetch IP by KadID (%s) for friend %s"), md4str(m_abyKadID), m_strName.IsEmpty() ? _T("(Unknown)") : m_strName); + // here ends our journey to connect to our friend unsuccessfully + m_FriendConnectState = FCS_NONE; + for (POSITION pos = m_liConnectionReport.GetHeadPosition(); pos != 0;) + m_liConnectionReport.GetNext(pos)->ConnectingResult(GetLinkedClient(), false); + m_liConnectionReport.RemoveAll(); + } + else + ASSERT( false ); +} \ No newline at end of file diff --git a/Friend.h b/Friend.h new file mode 100644 index 00000000..8744cc7a --- /dev/null +++ b/Friend.h @@ -0,0 +1,104 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "Kademlia/Utils/KadClientSearcher.h" + +class CUpDownClient; +class CFileDataIO; +class CFriendConnectionListener; + +enum EFriendConnectState{ + FCS_NONE = 0, + FCS_CONNECTING, + FCS_AUTH, + FCS_KADSEARCHING, +}; + +enum EFriendConnectReport{ + FCR_ESTABLISHED = 0, + FCR_DISCONNECTED, + FCR_USERHASHVERIFIED, + FCR_USERHASHFAILED, + FCR_SECUREIDENTFAILED, + FCR_DELETED +}; + +#define FF_NAME 0x01 +#define FF_KADID 0x02 + +/////////////////////////////////////////////////////////////////////////////// +// CFriendConnectionListener + +class CFriendConnectionListener +{ +public: + virtual void ReportConnectionProgress(CUpDownClient* pClient, CString strProgressDesc, bool bNoTimeStamp) = 0; + virtual void ConnectingResult(CUpDownClient* pClient, bool bSuccess) = 0; + virtual void ClientObjectChanged(CUpDownClient* pOldClient, CUpDownClient* pNewClient) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// CFriend +class CFriend : public Kademlia::CKadClientSearcher +{ +public: + CFriend(); + CFriend(CUpDownClient* client); + CFriend(const uchar* abyUserhash, uint32 dwLastSeen, uint32 dwLastUsedIP, uint16 nLastUsedPort, + uint32 dwLastChatted, LPCTSTR pszName, uint32 dwHasHash); + ~CFriend(); + + uchar m_abyUserhash[16]; + + uint32 m_dwLastSeen; + uint32 m_dwLastUsedIP; + uint16 m_nLastUsedPort; + uint32 m_dwLastChatted; + CString m_strName; + + CUpDownClient* GetLinkedClient(bool bValidCheck = false) const; + void SetLinkedClient(CUpDownClient* linkedClient); + CUpDownClient* GetClientForChatSession(); + + void LoadFromFile(CFileDataIO* file); + void WriteToFile(CFileDataIO* file); + + bool TryToConnect(CFriendConnectionListener* pConnectionReport); + void UpdateFriendConnectionState(EFriendConnectReport eEvent); + bool IsTryingToConnect() const {return m_FriendConnectState != FCS_NONE;} + bool CancelTryToConnect(CFriendConnectionListener* pConnectionReport); + void FindKadID(); + void KadSearchNodeIDByIPResult(Kademlia::EKadClientSearchRes eStatus, const uchar* pachNodeID); + void KadSearchIPByNodeIDResult(Kademlia::EKadClientSearchRes eStatus, uint32 dwIP, uint16 nPort); + + void SendMessage(CString strMessage); + + void SetFriendSlot(bool newValue); + bool GetFriendSlot() const; + + bool HasUserhash() const; + bool HasKadID() const; + +private: + uchar m_abyKadID[16]; + bool m_friendSlot; + uint32 m_dwLastKadSearch; + + EFriendConnectState m_FriendConnectState; + CTypedPtrList m_liConnectionReport; + CUpDownClient* m_LinkedClient; +}; diff --git a/FriendList.cpp b/FriendList.cpp new file mode 100644 index 00000000..a0efbd83 --- /dev/null +++ b/FriendList.cpp @@ -0,0 +1,265 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include +#include "emule.h" +#include "FriendList.h" +#include "UpDownClient.h" +#include "Friend.h" +#include "Preferences.h" +#include "SafeFile.h" +#include "OtherFunctions.h" +#include "opcodes.h" +#include "emuledlg.h" +#include "FriendListCtrl.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define EMFRIENDS_MET_FILENAME _T("emfriends.met") + +CFriendList::CFriendList() +{ + LoadList(); + m_nLastSaved = ::GetTickCount(); +} + +CFriendList::~CFriendList() +{ + SaveList(); + for (POSITION pos = m_listFriends.GetHeadPosition();pos != 0;) + delete m_listFriends.GetNext(pos); +} + +bool CFriendList::LoadList(){ + CString strFileName = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + EMFRIENDS_MET_FILENAME; + CSafeBufferedFile file; + CFileException fexp; + if (!file.Open(strFileName, CFile::modeRead | CFile::osSequentialScan | CFile::typeBinary | CFile::shareDenyWrite, &fexp)){ + if (fexp.m_cause != CFileException::fileNotFound){ + CString strError(GetResString(IDS_ERR_READEMFRIENDS)); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + } + return false; + } + + try { + uint8 header = file.ReadUInt8(); + if (header != MET_HEADER){ + file.Close(); + return false; + } + UINT nRecordsNumber = file.ReadUInt32(); + for (UINT i = 0; i < nRecordsNumber; i++) { + CFriend* Record = new CFriend(); + Record->LoadFromFile(&file); + m_listFriends.AddTail(Record); + } + file.Close(); + } + catch(CFileException* error){ + if (error->m_cause == CFileException::endOfFile) + LogError(LOG_STATUSBAR,GetResString(IDS_ERR_EMFRIENDSINVALID)); + else{ + TCHAR buffer[MAX_CFEXP_ERRORMSG]; + error->GetErrorMessage(buffer, ARRSIZE(buffer)); + LogError(LOG_STATUSBAR,GetResString(IDS_ERR_READEMFRIENDS),buffer); + } + error->Delete(); + return false; + } + + return true; +} + +void CFriendList::SaveList(){ + if (thePrefs.GetLogFileSaving()) + AddDebugLogLine(false, _T("Saving friends list file \"%s\""), EMFRIENDS_MET_FILENAME); + m_nLastSaved = ::GetTickCount(); + + CString strFileName = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + EMFRIENDS_MET_FILENAME; + CSafeBufferedFile file; + CFileException fexp; + if (!file.Open(strFileName, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary | CFile::shareDenyWrite, &fexp)){ + CString strError(_T("Failed to save ") EMFRIENDS_MET_FILENAME _T(" file")); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + return; + } + setvbuf(file.m_pStream, NULL, _IOFBF, 16384); + + try{ + file.WriteUInt8(MET_HEADER); + file.WriteUInt32(m_listFriends.GetCount()); + for (POSITION pos = m_listFriends.GetHeadPosition();pos != 0;) + m_listFriends.GetNext(pos)->WriteToFile(&file); + if (thePrefs.GetCommitFiles() >= 2 || (thePrefs.GetCommitFiles() >= 1 && !theApp.emuledlg->IsRunning())){ + file.Flush(); // flush file stream buffers to disk buffers + if (_commit(_fileno(file.m_pStream)) != 0) // commit disk buffers to disk + AfxThrowFileException(CFileException::hardIO, GetLastError(), file.GetFileName()); + } + file.Close(); + } + catch(CFileException* error){ + CString strError(_T("Failed to save ") EMFRIENDS_MET_FILENAME _T(" file")); + TCHAR szError[MAX_CFEXP_ERRORMSG]; + if (error->GetErrorMessage(szError, ARRSIZE(szError))){ + strError += _T(" - "); + strError += szError; + } + LogError(LOG_STATUSBAR, _T("%s"), strError); + error->Delete(); + } +} + +CFriend* CFriendList::SearchFriend(const uchar* abyUserHash, uint32 dwIP, uint16 nPort) const { + POSITION pos = m_listFriends.GetHeadPosition(); + while (pos){ + CFriend* cur_friend = m_listFriends.GetNext(pos); + // to avoid that unwanted clients become a friend, we have to distinguish between friends with + // a userhash and of friends which are identified by IP+port only. + if (abyUserHash != NULL && cur_friend->HasUserhash()){ + // check for a friend which has the same userhash as the specified one + if (!md4cmp(cur_friend->m_abyUserhash, abyUserHash)) + return cur_friend; + } + else{ + if (cur_friend->m_dwLastUsedIP == dwIP && dwIP != 0 && cur_friend->m_nLastUsedPort == nPort && nPort != 0) + return cur_friend; + } + } + return NULL; +} + +void CFriendList::RefreshFriend(CFriend* torefresh) const { + if (m_wndOutput) + m_wndOutput->RefreshFriend(torefresh); +} + +void CFriendList::ShowFriends() const { + if (!m_wndOutput){ + ASSERT ( false ); + return; + } + m_wndOutput->DeleteAllItems(); + for (POSITION pos = m_listFriends.GetHeadPosition();pos != 0;) + m_wndOutput->AddFriend(m_listFriends.GetNext(pos)); + m_wndOutput->UpdateList(); +} + +//You can add a friend without a IP to allow the IRC to trade links with lowID users. +bool CFriendList::AddFriend(const uchar* abyUserhash, uint32 dwLastSeen, uint32 dwLastUsedIP, uint16 nLastUsedPort, + uint32 dwLastChatted, LPCTSTR pszName, uint32 dwHasHash){ + // client must have an IP (HighID) or a hash + // TODO: check if this can be switched to a hybridID so clients with *.*.*.0 can be added.. + if (IsLowID(dwLastUsedIP) && dwHasHash==0) + return false; + if (SearchFriend(abyUserhash, dwLastUsedIP, nLastUsedPort) != NULL) + return false; + CFriend* Record = new CFriend( abyUserhash, dwLastSeen, dwLastUsedIP, nLastUsedPort, dwLastChatted, pszName, dwHasHash ); + m_listFriends.AddTail(Record); + ShowFriends(); + SaveList(); + return true; +} + + +bool CFriendList::IsAlreadyFriend(CString strUserHash) const +{ + for (POSITION pos = m_listFriends.GetHeadPosition();pos != 0;){ + const CFriend* cur_friend = m_listFriends.GetNext(pos); + if (cur_friend->HasUserhash() && strUserHash.Compare(md4str(cur_friend->m_abyUserhash)) == 0) + return true; + } + return false; +} + +bool CFriendList::AddFriend(CUpDownClient* toadd){ + if (toadd->IsFriend()) + return false; + // client must have an IP (HighID) or a hash + if (toadd->HasLowID() && !toadd->HasValidHash()) + return false; + if (SearchFriend(toadd->GetUserHash(), toadd->GetIP(), toadd->GetUserPort()) != NULL) + return false; + + CFriend* NewFriend = new CFriend(toadd); + toadd->m_Friend = NewFriend; + m_listFriends.AddTail(NewFriend); + if (m_wndOutput){ + m_wndOutput->AddFriend(NewFriend); + m_wndOutput->UpdateList(); + } + SaveList(); + NewFriend->FindKadID(); // fetch the kadid of this friend if we don't have it already + return true; +} + +void CFriendList::RemoveFriend(CFriend* todel){ + POSITION pos = m_listFriends.Find(todel); + if (!pos){ + ASSERT ( false ); + return; + } + + todel->SetLinkedClient(NULL); + + if (m_wndOutput) + m_wndOutput->RemoveFriend(todel); + m_listFriends.RemoveAt(pos); + delete todel; + SaveList(); + if (m_wndOutput) + m_wndOutput->UpdateList(); +} + +void CFriendList::RemoveAllFriendSlots(){ + for (POSITION pos = m_listFriends.GetHeadPosition();pos != 0;){ + CFriend* cur_friend = m_listFriends.GetNext(pos); + cur_friend->SetFriendSlot(false); + } +} + +void CFriendList::Process() +{ + if (::GetTickCount() - m_nLastSaved > MIN2MS(19)) + SaveList(); +} + +bool CFriendList::IsValid(CFriend* pToCheck) const +{ + // debug/sanity check function + for (POSITION pos = m_listFriends.GetHeadPosition();pos != 0;){ + if (pToCheck == m_listFriends.GetNext(pos)) + return true; + } + return false; +} \ No newline at end of file diff --git a/FriendList.h b/FriendList.h new file mode 100644 index 00000000..e2ad3b70 --- /dev/null +++ b/FriendList.h @@ -0,0 +1,49 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +class CFriend; +class CFriendListCtrl; +class CUpDownClient; + +class CFriendList +{ +public: + CFriendList(); + ~CFriendList(); + + bool IsAlreadyFriend(CString strUserHash) const; + bool IsValid(CFriend* pToCheck) const; + void SaveList(); + bool LoadList(); + void RefreshFriend(CFriend* torefresh) const; + CFriend* SearchFriend(const uchar* achUserHash, uint32 dwIP, uint16 nPort) const; + void SetWindow(CFriendListCtrl* NewWnd) { m_wndOutput = NewWnd; } + void ShowFriends() const; + bool AddFriend(CUpDownClient* toadd); + bool AddFriend(const uchar* abyUserhash, uint32 dwLastSeen, uint32 dwLastUsedIP, uint16 nLastUsedPort, + uint32 dwLastChatted, LPCTSTR pszName, uint32 dwHasHash); + void RemoveFriend(CFriend* todel); + void RemoveAllFriendSlots(); + void Process(); + int GetCount() { return m_listFriends.GetCount(); } + +private: + CTypedPtrList m_listFriends; + CFriendListCtrl* m_wndOutput; + uint32 m_nLastSaved; +}; diff --git a/FriendListCtrl.cpp b/FriendListCtrl.cpp new file mode 100644 index 00000000..a8dea889 --- /dev/null +++ b/FriendListCtrl.cpp @@ -0,0 +1,331 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "FriendListCtrl.h" +#include "friend.h" +#include "ClientDetailDialog.h" +#include "Addfriend.h" +#include "FriendList.h" +#include "emuledlg.h" +#include "ClientList.h" +#include "OtherFunctions.h" +#include "UpDownClient.h" +#include "ListenSocket.h" +#include "MenuCmds.h" +#include "ChatWnd.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +IMPLEMENT_DYNAMIC(CFriendListCtrl, CMuleListCtrl) + +BEGIN_MESSAGE_MAP(CFriendListCtrl, CMuleListCtrl) + ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnLvnColumnClick) + ON_NOTIFY_REFLECT(NM_DBLCLK, OnNmDblClk) + ON_WM_CONTEXTMENU() + ON_WM_SYSCOLORCHANGE() +END_MESSAGE_MAP() + +CFriendListCtrl::CFriendListCtrl() +{ + SetGeneralPurposeFind(true); + SetSkinKey(L"FriendsLv"); +} + +CFriendListCtrl::~CFriendListCtrl() +{ +} + +void CFriendListCtrl::Init() +{ + SetPrefsKey(_T("FriendListCtrl")); + + SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + + RECT rcWindow; + GetWindowRect(&rcWindow); + InsertColumn(0, GetResString(IDS_QL_USERNAME), LVCFMT_LEFT, rcWindow.right - rcWindow.left - 4); + + SetAllIcons(); + theApp.friendlist->SetWindow(this); + LoadSettings(); + SetSortArrow(); +} + +void CFriendListCtrl::OnSysColorChange() +{ + CMuleListCtrl::OnSysColorChange(); + SetAllIcons(); +} + +void CFriendListCtrl::SetAllIcons() +{ + CImageList iml; + iml.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + iml.Add(CTempIconLoader(_T("FriendNoClient"))); + iml.Add(CTempIconLoader(_T("FriendWithClient"))); + iml.Add(CTempIconLoader(_T("FriendConnected"))); + ASSERT( (GetStyle() & LVS_SHAREIMAGELISTS) == 0 ); + HIMAGELIST himlOld = ApplyImageList(iml.Detach()); + if (himlOld) + ImageList_Destroy(himlOld); +} + +void CFriendListCtrl::Localize() +{ + CHeaderCtrl* pHeaderCtrl = GetHeaderCtrl(); + HDITEM hdi; + hdi.mask = HDI_TEXT; + CString strRes; + + strRes = GetResString(IDS_QL_USERNAME); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(0, &hdi); + + int iItems = GetItemCount(); + for (int i = 0; i < iItems; i++) + UpdateFriend(i, (CFriend*)GetItemData(i)); +} + +void CFriendListCtrl::UpdateFriend(int iItem, const CFriend* pFriend) +{ + SetItemText(iItem, 0, pFriend->m_strName.IsEmpty() ? _T('(') + GetResString(IDS_UNKNOWN) + _T(')') : pFriend->m_strName); + + int iImage; + if (!pFriend->GetLinkedClient()) + iImage = 0; + else if (pFriend->GetLinkedClient()->socket && pFriend->GetLinkedClient()->socket->IsConnected()) + iImage = 2; + else + iImage = 1; + SetItem(iItem, 0, LVIF_IMAGE, 0, iImage, 0, 0, 0, 0); +} + +void CFriendListCtrl::AddFriend(const CFriend* pFriend) +{ + int iItem = InsertItem(LVIF_TEXT | LVIF_PARAM, GetItemCount(), pFriend->m_strName, 0, 0, 0, (LPARAM)pFriend); + if (iItem >= 0) + UpdateFriend(iItem, pFriend); + theApp.emuledlg->chatwnd->UpdateFriendlistCount(theApp.friendlist->GetCount()); +} + +void CFriendListCtrl::RemoveFriend(const CFriend* pFriend) +{ + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)pFriend; + int iItem = FindItem(&find); + if (iItem != -1) + DeleteItem(iItem); + theApp.emuledlg->chatwnd->UpdateFriendlistCount(theApp.friendlist->GetCount()); +} + +void CFriendListCtrl::RefreshFriend(const CFriend* pFriend) +{ + LVFINDINFO find; + find.flags = LVFI_PARAM; + find.lParam = (LPARAM)pFriend; + int iItem = FindItem(&find); + if (iItem != -1) + UpdateFriend(iItem, pFriend); +} + +void CFriendListCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + CTitleMenu ClientMenu; + ClientMenu.CreatePopupMenu(); + ClientMenu.AddMenuTitle(GetResString(IDS_FRIENDLIST), true); + + const CFriend* cur_friend = NULL; + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1) { + cur_friend = (CFriend*)GetItemData(iSel); + ClientMenu.AppendMenu(MF_STRING,MP_DETAIL, GetResString(IDS_SHOWDETAILS), _T("CLIENTDETAILS")); + ClientMenu.SetDefaultItem(MP_DETAIL); + } + + ClientMenu.AppendMenu(MF_STRING, MP_ADDFRIEND, GetResString(IDS_ADDAFRIEND), _T("ADDFRIEND")); + ClientMenu.AppendMenu(MF_STRING | (cur_friend ? MF_ENABLED : MF_GRAYED), MP_REMOVEFRIEND, GetResString(IDS_REMOVEFRIEND), _T("DELETEFRIEND")); + ClientMenu.AppendMenu(MF_STRING | (cur_friend ? MF_ENABLED : MF_GRAYED), MP_MESSAGE, GetResString(IDS_SEND_MSG), _T("SENDMESSAGE")); + ClientMenu.AppendMenu(MF_STRING | ((cur_friend==NULL || (cur_friend && cur_friend->GetLinkedClient(true) && !cur_friend->GetLinkedClient(true)->GetViewSharedFilesSupport())) ? MF_GRAYED : MF_ENABLED), MP_SHOWLIST, GetResString(IDS_VIEWFILES) , _T("VIEWFILES")); + ClientMenu.AppendMenu(MF_STRING, MP_FRIENDSLOT, GetResString(IDS_FRIENDSLOT), _T("FRIENDSLOT")); + ClientMenu.AppendMenu(MF_STRING | (GetItemCount() > 0 ? MF_ENABLED : MF_GRAYED), MP_FIND, GetResString(IDS_FIND), _T("Search")); + + ClientMenu.EnableMenuItem(MP_FRIENDSLOT, (cur_friend)?MF_ENABLED : MF_GRAYED); + ClientMenu.CheckMenuItem(MP_FRIENDSLOT, (cur_friend && cur_friend->GetFriendSlot()) ? MF_CHECKED : MF_UNCHECKED); + + GetPopupMenuPos(*this, point); + ClientMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); +} + +BOOL CFriendListCtrl::OnCommand(WPARAM wParam, LPARAM /*lParam*/) +{ + wParam = LOWORD(wParam); + + CFriend* cur_friend = NULL; + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1) + cur_friend = (CFriend*)GetItemData(iSel); + + switch (wParam) + { + case MP_MESSAGE: + if (cur_friend) + { + theApp.emuledlg->chatwnd->StartSession(cur_friend->GetClientForChatSession()); + } + break; + case MP_REMOVEFRIEND: + if (cur_friend) + { + theApp.friendlist->RemoveFriend(cur_friend); + // auto select next item after deleted one. + if (iSel < GetItemCount()) { + SetSelectionMark(iSel); + SetItemState(iSel, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + } + theApp.emuledlg->chatwnd->UpdateSelectedFriendMsgDetails(); + } + break; + case MP_ADDFRIEND:{ + CAddFriend dialog2; + dialog2.DoModal(); + break; + } + case MP_DETAIL: + case MPG_ALTENTER: + case IDA_ENTER: + if (cur_friend) + ShowFriendDetails(cur_friend); + break; + case MP_SHOWLIST: + if (cur_friend) + { + if (cur_friend->GetLinkedClient(true)) + cur_friend->GetLinkedClient()->RequestSharedFileList(); + else + { + CUpDownClient* newclient = new CUpDownClient(0, cur_friend->m_nLastUsedPort, cur_friend->m_dwLastUsedIP, 0, 0, true); + newclient->SetUserName(cur_friend->m_strName); + theApp.clientlist->AddClient(newclient); + newclient->RequestSharedFileList(); + } + } + break; + case MP_FRIENDSLOT: + if (cur_friend) + { + bool bIsAlready = cur_friend->GetFriendSlot(); + theApp.friendlist->RemoveAllFriendSlots(); + if (!bIsAlready) + cur_friend->SetFriendSlot(true); + } + break; + case MP_FIND: + OnFindStart(); + break; + } + return true; +} + +void CFriendListCtrl::OnNmDblClk(NMHDR* /*pNMHDR*/, LRESULT* pResult) +{ + int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + if (iSel != -1) + ShowFriendDetails((CFriend*)GetItemData(iSel)); + *pResult = 0; +} + +void CFriendListCtrl::ShowFriendDetails(const CFriend* pFriend) +{ + if (pFriend) + { + if (pFriend->GetLinkedClient(true)) + { + CClientDetailDialog dialog(pFriend->GetLinkedClient()); + dialog.DoModal(); + } + else + { + CAddFriend dlg; + dlg.m_pShowFriend = const_cast(pFriend); + dlg.DoModal(); + } + } +} + +BOOL CFriendListCtrl::PreTranslateMessage(MSG* pMsg) +{ + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_DELETE) + PostMessage(WM_COMMAND, MP_REMOVEFRIEND, 0); + else if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_INSERT) + PostMessage(WM_COMMAND, MP_ADDFRIEND, 0); + + return CMuleListCtrl::PreTranslateMessage(pMsg); +} + +void CFriendListCtrl::OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult) +{ + NMLISTVIEW *pNMListView = (NMLISTVIEW *)pNMHDR; + + // Determine ascending based on whether already sorted on this column + int iSortItem = GetSortItem(); + bool bOldSortAscending = GetSortAscending(); + bool bSortAscending = (iSortItem != pNMListView->iSubItem) ? true : !bOldSortAscending; + + // Item is column clicked + iSortItem = pNMListView->iSubItem; + + // Sort table + SetSortArrow(iSortItem, bSortAscending); + SortItems(SortProc, MAKELONG(iSortItem, (bSortAscending ? 0 : 0x0001))); + + *pResult = 0; +} + +int CFriendListCtrl::SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + const CFriend *item1 = (CFriend *)lParam1; + const CFriend *item2 = (CFriend *)lParam2; + if (item1 == NULL || item2 == NULL) + return 0; + + int iResult; + switch (LOWORD(lParamSort)) + { + case 0: + iResult = CompareLocaleStringNoCase(item1->m_strName, item2->m_strName); + break; + + default: + return 0; + } + if (HIWORD(lParamSort)) + iResult = -iResult; + return iResult; +} + +void CFriendListCtrl::UpdateList() +{ + theApp.emuledlg->chatwnd->UpdateFriendlistCount(theApp.friendlist->GetCount()); + SortItems(SortProc, MAKELONG(GetSortItem(), (GetSortAscending() ? 0 : 0x0001))); +} diff --git a/FriendListCtrl.h b/FriendListCtrl.h new file mode 100644 index 00000000..74799fd1 --- /dev/null +++ b/FriendListCtrl.h @@ -0,0 +1,52 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "MuleListCtrl.h" + +class CFriend; + +class CFriendListCtrl : public CMuleListCtrl +{ + DECLARE_DYNAMIC(CFriendListCtrl) + friend class CFriendList; + +public: + CFriendListCtrl(); + virtual ~CFriendListCtrl(); + + void Init(); + void Localize(); + void UpdateList(); + +protected: + void SetAllIcons(); + void AddFriend(const CFriend* pFriend); + void RemoveFriend(const CFriend* pFriend); + void RefreshFriend(const CFriend* pFriend); + void UpdateFriend(int iItem, const CFriend* pFriend); + void ShowFriendDetails(const CFriend* pFriend); + static int CALLBACK SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + virtual BOOL PreTranslateMessage(MSG* pMsg); + + DECLARE_MESSAGE_MAP() + afx_msg void OnContextMenu(CWnd *pWnd, CPoint point); + afx_msg void OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmDblClk(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnSysColorChange(); +}; diff --git a/GDIThread.cpp b/GDIThread.cpp new file mode 100644 index 00000000..5bdf9bcd --- /dev/null +++ b/GDIThread.cpp @@ -0,0 +1,139 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "GDIThread.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// critical section to protect while drawing to the DC +CCriticalSection CGDIThread::m_csGDILock; + +// m_hAnotherDead is used to signal that one or more threads have ended +// (it is an auto-reset event; and starts out not signaled) +//HANDLE CGDIThread::m_hAnotherDead = CreateEvent(NULL, FALSE, FALSE, NULL); + +///////////////////////////////////////////////////////////////////////////// +// CGDIThread + +IMPLEMENT_DYNAMIC(CGDIThread, CWinThread) + +BEGIN_MESSAGE_MAP(CGDIThread, CWinThread) + //{{AFX_MSG_MAP(CGDIThread) + // NOTE - the ClassWizard will add and remove mapping macros here. + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +CGDIThread::CGDIThread(CWnd* pWnd, HDC hDC) +{ + m_bAutoDelete = FALSE; + m_pMainWnd = pWnd; + m_hDC = hDC; + + m_nDelay = 50; + m_nScrollInc = SCROLL_UP; + m_bWaitVRT = FALSE; + + // kill event starts out in the signaled state + m_hEventKill = CreateEvent(NULL, TRUE, FALSE, NULL); + m_hEventDead = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +// The reason we don't just get a CDC from our owner and simply use it is because +// MFC GDI objects can't be passed between threads. So we must instead pass a +// handle and then reconvert them back to MFC objects. The reason for this is +// because MFC maintains a list of all GDI objects on a per thread basis. So if +// you pass a GDI object to another thread, it won't be in the correct list +// and MFC will assert. + +BOOL CGDIThread::InitInstance() +{ + // thread setup + m_dc.Attach(m_hDC); + + // loop but check for kill notification + while (WaitForSingleObject(m_hEventKill, 0) == WAIT_TIMEOUT) + SingleStep(); + + // thread cleanup + m_dc.Detach(); + + // avoid entering standard message loop by returning FALSE + return FALSE; +} + +void CGDIThread::Delete() +{ + // calling the base here won't do anything but it is a good habit + CWinThread::Delete(); + + // acknowledge receipt of kill notification + VERIFY(SetEvent(m_hEventDead)); + //VERIFY(SetEvent(m_hAnotherDead)); // what is this good for? nobody is waiting on that event! +} + +CGDIThread::~CGDIThread() +{ + CloseHandle(m_hEventKill); + CloseHandle(m_hEventDead); + //CloseHandle(m_hAnotherDead); +} + +void CGDIThread::KillThread() +{ + // Note: this function is called in the context of other threads, + // not the thread itself. + + // reset the m_hEventKill which signals the thread to shutdown + VERIFY(SetEvent(m_hEventKill)); + + // allow thread to run at higher priority during kill process + SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL); + WaitForSingleObject(m_hEventDead, INFINITE); + WaitForSingleObject(m_hThread, INFINITE); + + // now delete CWinThread object since no longer necessary + delete this; +} + +///////////////////////////////////////////////////////////////////////////// +// CGDIThread message handlers + +int CGDIThread::SetDelay(int nDelay) +{ + int nTmp = m_nDelay; + m_nDelay = nDelay; + return nTmp; +} + +int CGDIThread::SetScrollDirection(int nDirection) +{ + int nTmp = m_nScrollInc; + m_nScrollInc = nDirection; + return nTmp; +} + +BOOL CGDIThread::SetWaitVRT(BOOL bWait) +{ + BOOL bTmp = m_bWaitVRT; + m_bWaitVRT = bWait; + return bTmp; +} diff --git a/GDIThread.h b/GDIThread.h new file mode 100644 index 00000000..bce01df6 --- /dev/null +++ b/GDIThread.h @@ -0,0 +1,59 @@ +#pragma once + +// GDIThread.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CGDIThread thread + +class CGDIThread : public CWinThread +{ +public: + DECLARE_DYNAMIC(CGDIThread) + CGDIThread(CWnd* pWnd, HDC hDC); + +// Attributes +public: + HDC m_hDC; + CDC m_dc; + HANDLE m_hEventKill; + HANDLE m_hEventDead; + //static HANDLE m_hAnotherDead; + + // options + int m_nDelay; + int m_nScrollInc; + BOOL m_bWaitVRT; + + enum {SCROLL_UP = 1, SCROLL_PAUSE = 0, SCROLL_DOWN = -1}; + + static CCriticalSection m_csGDILock; + +// Operations +public: + void KillThread(); + virtual void SingleStep() = 0; + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGDIThread) + //}}AFX_VIRTUAL + +// Implementation +public: + BOOL SetWaitVRT(BOOL bWait = TRUE); + int SetScrollDirection(int nDirection); + int SetDelay(int nDelay); + virtual ~CGDIThread(); + virtual void Delete(); + +protected: + virtual BOOL InitInstance(); + + // Generated message map functions + //{{AFX_MSG(CGDIThread) + // NOTE - the ClassWizard will add and remove member functions here. + //}}AFX_MSG + + DECLARE_MESSAGE_MAP() +}; diff --git a/GZipFile.cpp b/GZipFile.cpp new file mode 100644 index 00000000..f2c73df2 --- /dev/null +++ b/GZipFile.cpp @@ -0,0 +1,120 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include +#include +#include +#include +#include +#include "GZipFile.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +CGZIPFile::CGZIPFile() +{ + m_gzFile = 0; +} + +bool CGZIPFile::Open(LPCTSTR pszFilePath) +{ + ASSERT( m_gzFile == 0 ); + Close(); + + m_gzFile = gzopen(CT2CA(pszFilePath), "rb"); + if (m_gzFile) + { + // Use gzip-uncompress only for real gzip-compressed files and do not let handle it also uncompressed files. + // This way the 'Open' function can be used to check if that file is a 'gzip' file at all. + if (gzdirect(m_gzFile) != 0) + Close(); + else + m_strGzFilePath = pszFilePath; + } + return m_gzFile != 0; +} + +void CGZIPFile::Close() +{ + if (m_gzFile) + { + VERIFY( gzclose(m_gzFile) == Z_OK ); + m_gzFile = 0; + } + m_strGzFilePath.Empty(); +} + +CString CGZIPFile::GetUncompressedFilePath() const +{ + // return path of input file without ".gz" extension + CString strUncompressedFilePath; + LPCTSTR pszExt = PathFindExtension(m_strGzFilePath); + if (pszExt != NULL && _tcsicmp(pszExt, _T(".gz")) == 0) + { + strUncompressedFilePath = m_strGzFilePath; + PathRemoveExtension(strUncompressedFilePath.GetBuffer(strUncompressedFilePath.GetLength())); + strUncompressedFilePath.ReleaseBuffer(); + } + return strUncompressedFilePath; +} + +CString CGZIPFile::GetUncompressedFileName() const +{ + // return name (without path) of input file without ".gz" extension + CString strUncompressedFileName = GetUncompressedFilePath(); + if (!strUncompressedFileName.IsEmpty()) + { + // skip any possible available directories + LPCTSTR pszFileName = PathFindFileName(strUncompressedFileName); + if (pszFileName) + strUncompressedFileName = pszFileName; + } + return strUncompressedFileName; +} + +bool CGZIPFile::Extract(LPCTSTR pszFilePath) +{ + int fdOut = _tsopen(pszFilePath, _O_CREAT | _O_TRUNC | _O_WRONLY | _O_BINARY, _SH_DENYWR, _S_IREAD | _S_IWRITE); + if (fdOut == -1) + return false; + + bool bResult = true; + const int iBuffSize = 32768; + BYTE* pucBuff = new BYTE[iBuffSize]; + while (!gzeof(m_gzFile)) + { + int iRead = gzread(m_gzFile, pucBuff, iBuffSize); + if (iRead == 0) + break; + if (iRead < 0){ + bResult = false; + break; + } + if (_write(fdOut, pucBuff, iRead) != iRead){ + bResult = false; + break; + } + } + delete[] pucBuff; + _close(fdOut); + if (!bResult) + VERIFY( _tremove(pszFilePath) == 0 ); + return bResult; +} diff --git a/GZipFile.h b/GZipFile.h new file mode 100644 index 00000000..0ae1072d --- /dev/null +++ b/GZipFile.h @@ -0,0 +1,35 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +typedef void* gzFile; + +class CGZIPFile +{ +public: + CGZIPFile(); + + bool Open(LPCTSTR pszFilePath); + void Close(); + CString GetUncompressedFileName() const; + CString GetUncompressedFilePath() const; + bool Extract(LPCTSTR pszFilePath); + +protected: + CString m_strGzFilePath; + gzFile m_gzFile; +}; diff --git a/GradientStatic.cpp b/GradientStatic.cpp new file mode 100644 index 00000000..d4493db9 --- /dev/null +++ b/GradientStatic.cpp @@ -0,0 +1,229 @@ +// GradientStatic.cpp : implementation file +// + +#include "stdafx.h" +#include "GradientStatic.h" +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +long l = 0; + +///////////////////////////////////////////////////////////////////////////// +// CGradientStatic + +CGradientStatic::CGradientStatic() +{ + m_bInit = TRUE; + m_bHorizontal = TRUE; + m_bInvert = FALSE; + + m_crColorLB = RGB(0,0,0); + m_crColorRT = RGB(255,255,255); + m_crTextColor = RGB(127,127,127); +} + +CGradientStatic::~CGradientStatic() +{ + if(m_Mem.dc.GetSafeHdc() && m_Mem.pold) + m_Mem.dc.SelectObject(m_Mem.pold); + if(m_Mem.bmp.GetSafeHandle()) + m_Mem.bmp.DeleteObject(); + if(m_Mem.dc.GetSafeHdc()) + m_Mem.dc.DeleteDC(); +} + + +BEGIN_MESSAGE_MAP(CGradientStatic, CStatic) + //{{AFX_MSG_MAP(CGradientStatic) + ON_WM_PAINT() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CGradientStatic message handlers + +void CGradientStatic::OnPaint() +{ + CPaintDC dc(this); // device context for painting + + CRect rClient; + GetClientRect(rClient); + + if(m_bInit) + { + CreateGradient(&dc, &rClient); + m_bInit = false; + } + + dc.BitBlt(0,0,m_Mem.cx, m_Mem.cy, &m_Mem.dc, 0,0, SRCCOPY); +} + +void CGradientStatic::CreateGradient(CDC *pDC, CRect *pRect) +{ + m_Mem.cx = pRect->Width(); + m_Mem.cy = pRect->Height(); + + if(m_Mem.dc.GetSafeHdc()) + { + if(m_Mem.bmp.GetSafeHandle() && m_Mem.pold) + m_Mem.dc.SelectObject(m_Mem.pold); + m_Mem.dc.DeleteDC(); + } + m_Mem.dc.CreateCompatibleDC(pDC); + + if(m_Mem.bmp.GetSafeHandle()) + m_Mem.bmp.DeleteObject(); + m_Mem.bmp.CreateCompatibleBitmap(pDC, m_Mem.cx, m_Mem.cy); + + m_Mem.pold = m_Mem.dc.SelectObject(&m_Mem.bmp); + +//----------------------------------------------------------------- + + if(m_bHorizontal) + { + DrawHorizontalGradient(); + DrawHorizontalText(pRect); + } + else + { + DrawVerticalGradient(); + DrawVerticalText(pRect); + } +} + +void CGradientStatic::DrawHorizontalGradient() +{ + double dblRstep = (GetRValue(m_crColorRT) - GetRValue(m_crColorLB)) / static_cast(m_Mem.cx); + double dblGstep = (GetGValue(m_crColorRT) - GetGValue(m_crColorLB)) / static_cast(m_Mem.cx); + double dblBstep = (GetBValue(m_crColorRT) - GetBValue(m_crColorLB)) / static_cast(m_Mem.cx); + double r = GetRValue(m_crColorLB); + double g = GetGValue(m_crColorLB); + double b = GetBValue(m_crColorLB); + + for(int x = 0; x < m_Mem.cx; x++) + { + CPen Pen(PS_SOLID, 1, RGB(r,g,b)); + CPen* pOld = m_Mem.dc.SelectObject(&Pen); + m_Mem.dc.MoveTo(x,0); + m_Mem.dc.LineTo(x,m_Mem.cy); + m_Mem.dc.SelectObject(pOld); + + r += dblRstep; + g += dblGstep; + b += dblBstep; + } +} + +void CGradientStatic::DrawVerticalGradient() +{ + double dblRstep = (GetRValue(m_crColorLB) - GetRValue(m_crColorRT)) / static_cast(m_Mem.cy); + double dblGstep = (GetGValue(m_crColorLB) - GetGValue(m_crColorRT)) / static_cast(m_Mem.cy); + double dblBstep = (GetBValue(m_crColorLB) - GetBValue(m_crColorRT)) / static_cast(m_Mem.cy); + double r = GetRValue(m_crColorRT); + double g = GetGValue(m_crColorRT); + double b = GetBValue(m_crColorRT); + + for(int y = 0; y < m_Mem.cy; y++) + { + CPen Pen(PS_SOLID, 1, RGB(r,g,b)), *pOld; + pOld = m_Mem.dc.SelectObject(&Pen); + m_Mem.dc.MoveTo(0,y); + m_Mem.dc.LineTo(m_Mem.cx,y); + m_Mem.dc.SelectObject(pOld); + + r += dblRstep; + g += dblGstep; + b += dblBstep; + } +} + +void CGradientStatic::DrawHorizontalText(CRect *pRect) +{ + CFont *pOldFont = NULL; + if (m_cfFont.GetSafeHandle()) + pOldFont = m_Mem.dc.SelectObject(&m_cfFont); + + CString strText; + GetWindowText(strText); + + m_Mem.dc.SetTextColor(m_crTextColor); + m_Mem.dc.SetBkMode(TRANSPARENT); + m_Mem.dc.DrawText(strText, pRect, DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (pOldFont) + m_Mem.dc.SelectObject(pOldFont); +} + +void DrawRotatedText(HDC hdc, LPCTSTR str, LPRECT rect, double angle, UINT nOptions = 0) +{ + // convert angle to radian + double pi = 3.141592654; + double radian = pi * 2 / 360 * angle; + + // get the center of a not-rotated text + SIZE TextSize;; + GetTextExtentPoint32(hdc, str, _tcslen(str), &TextSize); + + POINT center; + center.x = TextSize.cx / 2; + center.y = TextSize.cy / 2; + + // now calculate the center of the rotated text + POINT rcenter; + rcenter.x = long(cos(radian) * center.x - sin(radian) * center.y); + rcenter.y = long(sin(radian) * center.x + cos(radian) * center.y); + + // finally draw the text and move it to the center of the rectangle + SetTextAlign(hdc, TA_BOTTOM); + SetBkMode(hdc, TRANSPARENT); + ExtTextOut(hdc, rect->left + (rect->right - rect->left) / 2 - rcenter.x, + rect->bottom, nOptions, rect, str, _tcslen(str), NULL); +} + +void CGradientStatic::DrawVerticalText(CRect *pRect) +{ + CFont *pOldFont = NULL;; + + LOGFONT lfFont; + if(m_cfFont.GetSafeHandle()) + { + m_cfFont.GetLogFont(&lfFont); + } + else + { + CFont *pFont = GetFont(); + pFont->GetLogFont(&lfFont); + _tcscpy(lfFont.lfFaceName, _T("Arial")); // some fonts won't turn :( + } + lfFont.lfEscapement = 900; + + CFont Font; + Font.CreateFontIndirect(&lfFont); + pOldFont = m_Mem.dc.SelectObject(&Font); + + CString strText; + GetWindowText(strText); + + m_Mem.dc.SetTextColor(m_crTextColor); + m_Mem.dc.SetBkColor(TRANSPARENT); + CRect rText = pRect; + rText.bottom -= 5; + DrawRotatedText(m_Mem.dc.m_hDC, strText, rText, 90); + + m_Mem.dc.SelectObject(pOldFont); +} + +void CGradientStatic::SetFont(CFont *pFont) +{ + LOGFONT lfFont; + pFont->GetLogFont(&lfFont); + + if(m_cfFont.GetSafeHandle()) + m_cfFont.DeleteObject(); + m_cfFont.CreateFontIndirect(&lfFont); +} diff --git a/GradientStatic.h b/GradientStatic.h new file mode 100644 index 00000000..0f767218 --- /dev/null +++ b/GradientStatic.h @@ -0,0 +1,71 @@ +#pragma once + +///////////////////////////////////////////////////////////////////////////// +// CGradientStatic window + +class CGradientStatic : public CStatic +{ +// Construction +public: + CGradientStatic(); + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGradientStatic) + //}}AFX_VIRTUAL + +// Implementation +public: + void SetFont(CFont *pFont); + virtual ~CGradientStatic(); + + void SetInit(bool bInit) { m_bInit = bInit; } + void SetHorizontal(bool bHorz = true) { m_bHorizontal = bHorz; } + void SetInvert(bool bInvert = false) { m_bInvert = bInvert; } + void SetColors(COLORREF crText, COLORREF crLB, COLORREF crRT) + { + m_crTextColor = crText; + m_crColorLB = crLB; + m_crColorRT = crRT; + } + + // Generated message map functions +protected: + bool m_bInit; + bool m_bHorizontal; + bool m_bInvert; + + COLORREF m_crColorRT; + COLORREF m_crColorLB; + COLORREF m_crTextColor; + + CFont m_cfFont; + + struct _TAG_GRADIENTSTATIC_MEM_ + { + CDC dc; + CBitmap bmp; + CBitmap *pold; + int cx; + int cy; + + }m_Mem; + + //{{AFX_MSG(CGradientStatic) + afx_msg void OnPaint(); + //}}AFX_MSG + + DECLARE_MESSAGE_MAP() +private: + void DrawVerticalText(CRect *pRect); + void DrawHorizontalText(CRect *pRect); + void DrawVerticalGradient(); + void DrawHorizontalGradient(); + void CreateGradient(CDC *pDC, CRect *pRect); +}; diff --git a/HTRichEditCtrl.cpp b/HTRichEditCtrl.cpp new file mode 100644 index 00000000..0eb9b4c7 --- /dev/null +++ b/HTRichEditCtrl.cpp @@ -0,0 +1,1509 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include +#include "emule.h" +#include "HTRichEditCtrl.h" +#include "OtherFunctions.h" +#include "Preferences.h" +#include "MenuCmds.h" +#include "Log.h" +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +int CHTRichEditCtrl::sm_iSmileyClients = 0; +CComPtr CHTRichEditCtrl::sm_pIStorageSmileys; +CMapStringToPtr CHTRichEditCtrl::sm_aSmileyBitmaps; + +IMPLEMENT_DYNAMIC(CHTRichEditCtrl, CRichEditCtrl) + +BEGIN_MESSAGE_MAP(CHTRichEditCtrl, CRichEditCtrl) + ON_WM_CONTEXTMENU() + ON_WM_KEYDOWN() + ON_CONTROL_REFLECT(EN_ERRSPACE, OnEnErrspace) + ON_CONTROL_REFLECT(EN_MAXTEXT, OnEnMaxtext) + ON_NOTIFY_REFLECT_EX(EN_LINK, OnEnLink) + ON_WM_CREATE() + ON_WM_SYSCOLORCHANGE() + ON_WM_SETCURSOR() + ON_WM_SIZE() +END_MESSAGE_MAP() + +CHTRichEditCtrl::CHTRichEditCtrl() +{ + m_bAutoScroll = true; + m_bNoPaint = false; + m_bEnErrSpace = false; + m_bRestoreFormat = false; + memset(&m_cfDefault, 0, sizeof m_cfDefault); + m_bForceArrowCursor = false; + m_hArrowCursor = ::LoadCursor(NULL, IDC_ARROW); + m_bEnableSmileys = false; + m_crForeground = CLR_DEFAULT; + m_crBackground = CLR_DEFAULT; + m_crDfltForeground = CLR_DEFAULT; + m_crDfltBackground = CLR_DEFAULT; + m_bDfltForeground = false; + m_bDfltBackground = false; +} + +CHTRichEditCtrl::~CHTRichEditCtrl() +{ + EnableSmileys(false); +} + +void CHTRichEditCtrl::Localize() +{ +} + +void CHTRichEditCtrl::Init(LPCTSTR pszTitle, LPCTSTR pszSkinKey) +{ + SetProfileSkinKey(pszSkinKey); + SetTitle(pszTitle); + + VERIFY( SendMessage(EM_SETUNDOLIMIT, 0, 0) == 0 ); + int iMaxLogBuff = thePrefs.GetMaxLogBuff(); + if (afxIsWin95()) + LimitText(iMaxLogBuff > 0xFFFF ? 0xFFFF : iMaxLogBuff); + else + LimitText(iMaxLogBuff ? iMaxLogBuff : 128*1024); + m_iLimitText = GetLimitText(); + + VERIFY( GetSelectionCharFormat(m_cfDefault) ); + + // prevent the RE control to change the font height within single log lines (may happen with some Unicode chars) + DWORD dwLangOpts = SendMessage(EM_GETLANGOPTIONS); + SendMessage(EM_SETLANGOPTIONS, 0, dwLangOpts & ~(IMF_AUTOFONT /*| IMF_AUTOFONTSIZEADJUST*/)); + //SendMessage(EM_SETEDITSTYLE, SES_EMULATESYSEDIT, SES_EMULATESYSEDIT); +} + +void CHTRichEditCtrl::EnableSmileys(bool bEnable) +{ + if (bEnable) + { + if (!m_bEnableSmileys) + { + m_bEnableSmileys = true; + sm_iSmileyClients++; + } + } + else + { + if (m_bEnableSmileys) + { + m_bEnableSmileys = false; + sm_iSmileyClients--; + if (sm_iSmileyClients <= 0) + { + PurgeSmileyCaches(); + sm_iSmileyClients = 0; + } + } + } +} + +void CHTRichEditCtrl::PurgeSmileyCaches() +{ + POSITION pos = sm_aSmileyBitmaps.GetStartPosition(); + while (pos) + { + CString strKey; + void *pValue; + sm_aSmileyBitmaps.GetNextAssoc(pos, strKey, pValue); +#ifdef USE_METAFILE + VERIFY( DeleteEnhMetaFile((HENHMETAFILE)pValue) ); +#else + VERIFY( DeleteObject((HBITMAP)pValue) ); +#endif + } + sm_aSmileyBitmaps.RemoveAll(); + sm_pIStorageSmileys.Release(); +} + +void CHTRichEditCtrl::SetProfileSkinKey(LPCTSTR pszSkinKey) +{ + m_strSkinKey = pszSkinKey; +} + +void CHTRichEditCtrl::SetTitle(LPCTSTR pszTitle) +{ + m_strTitle = pszTitle; +} + +int CHTRichEditCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CRichEditCtrl::OnCreate(lpCreateStruct) == -1) + return -1; + Init(NULL); + return 0; +} + +LRESULT CHTRichEditCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_ERASEBKGND: + if (m_bNoPaint) + return TRUE; + case WM_PAINT: + if (m_bNoPaint) + return TRUE; + } + return CRichEditCtrl::WindowProc(message, wParam, lParam); +} + +void CHTRichEditCtrl::OnSize(UINT nType, int cx, int cy) +{ + // Use the 'ScrollInfo' only, if there is a scrollbar available, otherwise we would + // use a scrollinfo which points to the top and we would thus stay at the top. + bool bAtEndOfScroll; + SCROLLINFO si; + si.cbSize = sizeof si; + si.fMask = SIF_ALL; + if ((GetStyle() & WS_VSCROLL) && GetScrollInfo(SB_VERT, &si)) + bAtEndOfScroll = (si.nPos >= (int)(si.nMax - si.nPage)); + else + bAtEndOfScroll = true; + + CRichEditCtrl::OnSize(nType, cx, cy); + + if (bAtEndOfScroll) + ScrollToLastLine(); +} + +COLORREF GetLogLineColor(UINT eMsgType) +{ + if (eMsgType == LOG_ERROR) + return thePrefs.m_crLogError; + if (eMsgType == LOG_WARNING) + return thePrefs.m_crLogWarning; + if (eMsgType == LOG_SUCCESS) + return thePrefs.m_crLogSuccess; + ASSERT( eMsgType == LOG_INFO ); + return CLR_DEFAULT; +} + +void CHTRichEditCtrl::FlushBuffer() +{ + if (m_astrBuff.GetSize() > 0) // flush buffer + { + for (int i = 0; i < m_astrBuff.GetSize(); i++) + { + const CString& rstrLine = m_astrBuff[i]; + if (!rstrLine.IsEmpty()) + { + if ((_TUCHAR)rstrLine[0] < 8) + AddLine((LPCTSTR)rstrLine + 1, rstrLine.GetLength() - 1, false, GetLogLineColor((_TUCHAR)rstrLine[0])); + else + AddLine((LPCTSTR)rstrLine, rstrLine.GetLength()); + } + } + m_astrBuff.RemoveAll(); + } +} + +void CHTRichEditCtrl::AddEntry(LPCTSTR pszMsg) +{ + CString strLine(pszMsg); + strLine += _T("\n"); + if (m_hWnd == NULL){ + m_astrBuff.Add(strLine); + } + else{ + FlushBuffer(); + AddLine(strLine, strLine.GetLength()); + } +} + +void CHTRichEditCtrl::Add(LPCTSTR pszMsg, int iLen) +{ + if (m_hWnd == NULL){ + CString strLine(pszMsg); + m_astrBuff.Add(strLine); + } + else{ + FlushBuffer(); + AddLine(pszMsg, iLen); + } +} + +void CHTRichEditCtrl::AddTyped(LPCTSTR pszMsg, int iLen, UINT eMsgType) +{ + if (m_hWnd == NULL) + { + CString strLine; + strLine = (TCHAR)(eMsgType & LOGMSGTYPEMASK); + strLine += pszMsg; + m_astrBuff.Add(strLine); + } + else + { + FlushBuffer(); + AddLine(pszMsg, iLen, false, GetLogLineColor(eMsgType & LOGMSGTYPEMASK)); + } +} + +void CHTRichEditCtrl::AddLine(LPCTSTR pszMsg, int iLen, bool bLink, COLORREF cr, COLORREF bk, DWORD mask) +{ + int iMsgLen = (iLen == -1) ? _tcslen(pszMsg) : iLen; + if (iMsgLen == 0) + return; + + // Get Edit contents dimensions and cursor position + long lStartChar, lEndChar; + GetSel(lStartChar, lEndChar); + int iSize = GetWindowTextLength(); + + // Get Auto-AutoScroll state depending on scrollbar position + bool bAutoAutoScroll = m_bAutoScroll; + // Use the 'ScrollInfo' only, if there is a scrollbar available, otherwise we would + // use a scrollinfo which points to the top and we would thus stay at the top. + bool bScrollInfo = false; + SCROLLINFO si; + si.cbSize = sizeof si; + si.fMask = SIF_ALL; + if ((GetStyle() & WS_VSCROLL) && GetScrollInfo(SB_VERT, &si)) { + bScrollInfo = true; + // use some threshold to determine if at end or "very near" at end, unfortunately + // this is needed to get around richedit specific stuff. this threshold (pixels) + // should somewhat reflect the font size used in the control. + bAutoAutoScroll = (si.nPos >= (int)(si.nMax - si.nPage - 20)); + } + + // Reduce flicker by ignoring WM_PAINT + m_bNoPaint = true; + BOOL bIsVisible = IsWindowVisible(); + if (bIsVisible) + SetRedraw(FALSE); + + // Remember where we are + //int iFirstLine = !bAutoAutoScroll ? GetFirstVisibleLine() : 0; + POINT ptScrollPos; + SendMessage(EM_GETSCROLLPOS, 0, (LPARAM)&ptScrollPos); + + // Select at the end of text and replace the selection + SafeAddLine(iSize, pszMsg, iMsgLen, lStartChar, lEndChar, bLink, cr, bk, mask); + SetSel(lStartChar, lEndChar); // Restore previous selection + + if (bAutoAutoScroll) + ScrollToLastLine(); + else { + //LineScroll(iFirstLine - GetFirstVisibleLine()); + SendMessage(EM_SETSCROLLPOS, 0, (LPARAM)&ptScrollPos); + } + + m_bNoPaint = false; + if (bIsVisible) { + SetRedraw(); + Invalidate(); + } +} + +void CHTRichEditCtrl::OnEnErrspace() +{ + m_bEnErrSpace = true; +} + +void CHTRichEditCtrl::OnEnMaxtext() +{ + m_bEnErrSpace = true; +} + +void CHTRichEditCtrl::ScrollToLastLine(bool bForceLastLineAtBottom) +{ + if (bForceLastLineAtBottom) + { + int iFirstVisible = GetFirstVisibleLine(); + if (iFirstVisible > 0) + LineScroll(-iFirstVisible); + } + + // WM_VSCROLL does not work correctly under Win98 (or older version of comctl.dll) + SendMessage(WM_VSCROLL, SB_BOTTOM); + if (afxIsWin95()) + { + // older version of comctl.dll seem to need this to properly update the display + int iPos = GetScrollPos(SB_VERT); + SendMessage(WM_VSCROLL, MAKELONG(SB_THUMBPOSITION, iPos)); + SendMessage(WM_VSCROLL, SB_ENDSCROLL); + } +} + +void CHTRichEditCtrl::ScrollToFirstLine() +{ + // WM_VSCROLL does not work correctly under Win98 (or older version of comctl.dll) + SendMessage(WM_VSCROLL, SB_TOP); + if (afxIsWin95()) + { + // older version of comctl.dll seem to need this to properly update the display + int iPos = GetScrollPos(SB_VERT); + SendMessage(WM_VSCROLL, MAKELONG(SB_THUMBPOSITION, iPos)); + SendMessage(WM_VSCROLL, SB_ENDSCROLL); + } +} + +void CHTRichEditCtrl::AddString(int nPos, LPCTSTR pszString, bool bLink, COLORREF cr, COLORREF bk, DWORD mask) +{ + bool bRestoreFormat = false; + m_bEnErrSpace = false; + SetSel(nPos, nPos); + if (bLink) + { + CHARFORMAT2 cf; + memset(&cf, 0, sizeof cf); + GetSelectionCharFormat(cf); + cf.dwMask |= CFM_LINK; + cf.dwEffects |= CFE_LINK; + SetSelectionCharFormat(cf); + } + else if (cr != CLR_DEFAULT || bk != CLR_DEFAULT || (mask & (CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE)) != 0) + { + CHARFORMAT2 cf; + memset(&cf, 0, sizeof(cf)); + GetSelectionCharFormat(cf); + + cf.dwMask |= CFM_COLOR; + if (cr == CLR_DEFAULT) + { + if (m_bDfltForeground) + cf.dwEffects |= CFE_AUTOCOLOR; + else { + ASSERT( m_crForeground != CLR_DEFAULT ); + cf.dwEffects &= ~CFE_AUTOCOLOR; + cf.crTextColor = m_crForeground; + } + } + else + { + cf.dwEffects &= ~CFE_AUTOCOLOR; + cf.crTextColor = cr; + } + + if (bk == CLR_DEFAULT) + { + // Background color is a little different than foreground color. Even if the + // background color is set to a non-standard value (e.g. via skin), the + // CFE_AUTOBACKCOLOR can be used to get this value. The usage of this flag instead + // of the explicit color has the advantage that on a change of the skin or on a + // change of the windows system colors, the control can display the colored + // text in the new color scheme better (e.g. the text is at least readable and + // not shown as black-on-black or white-on-white). + //if (m_bDfltBackground) { + cf.dwMask |= CFM_BACKCOLOR; + cf.dwEffects |= CFE_AUTOBACKCOLOR; + //} + //else { + // ASSERT( m_crBackground != CLR_DEFAULT ); + // cf.dwEffects &= ~CFE_AUTOBACKCOLOR; + // cf.crBackColor = m_crBackground; + //} + } + else + { + cf.dwMask |= CFM_BACKCOLOR; + cf.dwEffects &= ~CFE_AUTOBACKCOLOR; + cf.crBackColor = bk; + } + + cf.dwMask |= mask; + + if (mask & CFM_BOLD) + cf.dwEffects |= CFE_BOLD; + else if (cf.dwEffects & CFE_BOLD) + cf.dwEffects ^= CFE_BOLD; + + if (mask & CFM_ITALIC) + cf.dwEffects |= CFE_ITALIC; + else if (cf.dwEffects & CFE_ITALIC) + cf.dwEffects ^= CFE_ITALIC; + + if (mask & CFM_UNDERLINE) + cf.dwEffects |= CFE_UNDERLINE; + else if (cf.dwEffects & CFE_UNDERLINE) + cf.dwEffects ^= CFE_UNDERLINE; + + SetSelectionCharFormat(cf); + bRestoreFormat = true; + } + else if (m_bRestoreFormat) + { + SetSelectionCharFormat(m_cfDefault); + } + + if (m_bEnableSmileys) + AddSmileys(pszString); + else + ReplaceSel(pszString); + + m_bRestoreFormat = bRestoreFormat; +} + +void CHTRichEditCtrl::SafeAddLine(int nPos, LPCTSTR pszLine, int iLen, long& lStartChar, long& lEndChar, bool bLink, COLORREF cr, COLORREF bk, DWORD mask) +{ + // EN_ERRSPACE and EN_MAXTEXT are not working for rich edit control (at least not same as for standard control), + // need to explicitly check the log buffer limit.. + int iCurSize = nPos; + if (iCurSize + iLen >= m_iLimitText) + { + bool bOldNoPaint = m_bNoPaint; + m_bNoPaint = true; + BOOL bIsVisible = IsWindowVisible(); + if (bIsVisible) + SetRedraw(FALSE); + + while (iCurSize > 0 && iCurSize + iLen > m_iLimitText) + { + // delete 1st line + int iLine0Len = LineLength(0) + 1; // add NL character + SetSel(0, iLine0Len); + ReplaceSel(_T("")); + + // update any possible available selection + lStartChar -= iLine0Len; + if (lStartChar < 0) + lStartChar = 0; + lEndChar -= iLine0Len; + if (lEndChar < 0) + lEndChar = 0; + + iCurSize = GetWindowTextLength(); + } + + m_bNoPaint = bOldNoPaint; + if (bIsVisible && !m_bNoPaint) + { + SetRedraw(); + Invalidate(); + } + } + + AddString(nPos, pszLine, bLink, cr, bk, mask); + + if (m_bEnErrSpace) + { + bool bOldNoPaint = m_bNoPaint; + m_bNoPaint = true; + BOOL bIsVisible = IsWindowVisible(); + if (bIsVisible) + SetRedraw(FALSE); + + // remove the first line as long as we are capable of adding the new line + int iSafetyCounter = 0; + while (m_bEnErrSpace && iSafetyCounter < 10) + { + // delete the previous partially added line + SetSel(nPos, -1); + ReplaceSel(_T("")); + + // delete 1st line + int iLine0Len = LineLength(0) + 1; // add NL character + SetSel(0, iLine0Len); + ReplaceSel(_T("")); + + // update any possible available selection + lStartChar -= iLine0Len; + if (lStartChar < 0) + lStartChar = 0; + lEndChar -= iLine0Len; + if (lEndChar < 0) + lEndChar = 0; + + // add the new line again + nPos = GetWindowTextLength(); + AddString(nPos, pszLine, bLink, cr, bk, mask); + + if (m_bEnErrSpace && nPos == 0){ + // should never happen: if we tried to add the line another time in the 1st line, there + // will be no chance to add the line at all -> avoid endless loop! + break; + } + iSafetyCounter++; // never ever create an endless loop! + } + m_bNoPaint = bOldNoPaint; + if (bIsVisible && !m_bNoPaint) + { + SetRedraw(); + Invalidate(); + } + } +} + +void CHTRichEditCtrl::Reset() +{ + m_astrBuff.RemoveAll(); + SetRedraw(FALSE); + SetWindowText(_T("")); + SetRedraw(); + Invalidate(); +} + +void CHTRichEditCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + if (point.x != -1 || point.y != -1) { + CRect rcClient; + GetClientRect(&rcClient); + ClientToScreen(&rcClient); + if (!rcClient.PtInRect(point)) { + Default(); + return; + } + } + + long lSelStart, lSelEnd; + GetSel(lSelStart, lSelEnd); + + // ugly, simulate a left click to get around the text cursor problem when right clicking. + if (point.x != -1 && point.y != -1 && lSelStart == lSelEnd) + { + ASSERT( GetStyle() & ES_NOHIDESEL ); // this works only if ES_NOHIDESEL is set + CPoint ptMouse(point); + ScreenToClient(&ptMouse); + SendMessage(WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(ptMouse.x, ptMouse.y)); + SendMessage(WM_LBUTTONUP, MK_LBUTTON, MAKELONG(ptMouse.x, ptMouse.y)); + } + + int iTextLen = GetWindowTextLength(); + + CTitleMenu menu; + menu.CreatePopupMenu(); + menu.AddMenuTitle(GetResString(IDS_LOGENTRY)); + menu.AppendMenu(MF_STRING | (lSelEnd > lSelStart ? MF_ENABLED : MF_GRAYED), MP_COPYSELECTED, GetResString(IDS_COPY)); + menu.AppendMenu(MF_SEPARATOR); + menu.AppendMenu(MF_STRING | (iTextLen > 0 ? MF_ENABLED : MF_GRAYED), MP_SELECTALL, GetResString(IDS_SELECTALL)); + menu.AppendMenu(MF_STRING | (iTextLen > 0 ? MF_ENABLED : MF_GRAYED), MP_REMOVEALL , GetResString(IDS_PW_RESET)); + menu.AppendMenu(MF_STRING | (iTextLen > 0 ? MF_ENABLED : MF_GRAYED), MP_SAVELOG, GetResString(IDS_SAVELOG) + _T("...")); + menu.AppendMenu(MF_SEPARATOR); + menu.AppendMenu(MF_STRING | (m_bAutoScroll ? MF_CHECKED : MF_UNCHECKED), MP_AUTOSCROLL, GetResString(IDS_AUTOSCROLL)); + + if (point.x == -1 && point.y == -1) + { + point.x = 16; + point.y = 32; + ClientToScreen(&point); + } + + // Cheap workaround for the "Text cursor is showing while context menu is open" glitch. It could be solved properly + // with the RE's COM interface, but because the according messages are not routed with a unique control ID, it's not + // really useable (e.g. if there are more RE controls in one window). Would to envelope each RE window to get a unique ID.. + m_bForceArrowCursor = true; + menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); + m_bForceArrowCursor = false; + + VERIFY( menu.DestroyMenu() ); +} + +BOOL CHTRichEditCtrl::OnCommand(WPARAM wParam, LPARAM /*lParam*/) +{ + switch (wParam) + { + case MP_COPYSELECTED: + CopySelectedItems(); + break; + case MP_SELECTALL: + SelectAllItems(); + break; + case MP_REMOVEALL: + Reset(); + break; + case MP_SAVELOG: + SaveLog(); + break; + case MP_AUTOSCROLL: + m_bAutoScroll = !m_bAutoScroll; + break; + } + return TRUE; +} + +bool CHTRichEditCtrl::SaveLog(LPCTSTR pszDefName) +{ + bool bResult = false; + CFileDialog dlg(FALSE, _T("log"), pszDefName ? pszDefName : (LPCTSTR)m_strTitle, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("Log Files (*.log)|*.log||"), this, 0); + if (dlg.DoModal() == IDOK) + { + FILE* fp = _tfsopen(dlg.GetPathName(), _T("wb"), _SH_DENYWR); + if (fp) + { + // write Unicode byte-order mark 0xFEFF + fputwc(0xFEFF, fp); + + CString strText; + GetWindowText(strText); + fwrite(strText, sizeof(TCHAR), strText.GetLength(), fp); + if (ferror(fp)){ + CString strError; + strError.Format(_T("Failed to write log file \"%s\" - %s"), dlg.GetPathName(), _tcserror(errno)); + AfxMessageBox(strError, MB_ICONERROR); + } + else + bResult = true; + fclose(fp); + } + else{ + CString strError; + strError.Format(_T("Failed to create log file \"%s\" - %s"), dlg.GetPathName(), _tcserror(errno)); + AfxMessageBox(strError, MB_ICONERROR); + } + } + return bResult; +} + +CString CHTRichEditCtrl::GetLastLogEntry() +{ + CString strLog; + int iLastLine = GetLineCount() - 2; + if (iLastLine >= 0) + { + GetLine(iLastLine, strLog.GetBuffer(1024), 1024); + strLog.ReleaseBuffer(); + } + return strLog; +} + +CString CHTRichEditCtrl::GetAllLogEntries() +{ + CString strLog; + GetWindowText(strLog); + return strLog; +} + +void CHTRichEditCtrl::SelectAllItems() +{ + SetSel(0, -1); +} + +void CHTRichEditCtrl::CopySelectedItems() +{ + Copy(); +} + +void CHTRichEditCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + if (nChar == 'A' && (GetKeyState(VK_CONTROL) & 0x8000)) + { + ////////////////////////////////////////////////////////////////// + // Ctrl+A: Select all items + SelectAllItems(); + } + else if (nChar == 'C' && (GetKeyState(VK_CONTROL) & 0x8000)) + { + ////////////////////////////////////////////////////////////////// + // Ctrl+C: Copy listview items to clipboard + CopySelectedItems(); + } + else if (nChar == VK_ESCAPE) + { + // dont minimize CHTRichEditCtrl + return ; + } + + CRichEditCtrl::OnKeyDown(nChar, nRepCnt, nFlags); +} + +static const struct +{ + LPCTSTR pszScheme; + int iLen; +} s_apszSchemes[] = +{ + { _T("ed2k://"), 7 }, + { _T("http://"), 7 }, + { _T("https://"), 8 }, + { _T("ftp://"), 6 }, + { _T("www."), 4 }, + { _T("ftp."), 4 }, + { _T("mailto:"), 7 } +}; + +void CHTRichEditCtrl::AppendText(const CString& sText) +{ + LPCTSTR psz = sText; + LPCTSTR pszStart = psz; + while (*psz != _T('\0')) + { + bool bFoundScheme = false; + for (int i = 0; i < _countof(s_apszSchemes); i++) + { + if (_tcsncmp(psz, s_apszSchemes[i].pszScheme, s_apszSchemes[i].iLen) == 0) + { + // output everything before the URL + if (psz - pszStart > 0){ + CString str(pszStart, psz - pszStart); + AddLine(str, str.GetLength()); + } + + // search next space or EOL + int iLen = _tcscspn(psz, _T(" \t\r\n")); + if (iLen == 0){ + AddLine(psz, -1, true); + psz += _tcslen(psz); + } + else{ + CString str(psz, iLen); + AddLine(str, str.GetLength(), true); + psz += iLen; + } + pszStart = psz; + bFoundScheme = true; + break; + } + } + if (!bFoundScheme) + psz = _tcsinc(psz); + } + + if (*pszStart != _T('\0')) + AddLine(pszStart, -1); +} + +void CHTRichEditCtrl::AppendHyperLink(const CString& sText, const CString& sTitle, const CString& sCommand, const CString& sDirectory) +{ + UNREFERENCED_PARAMETER(sText); + UNREFERENCED_PARAMETER(sTitle); + UNREFERENCED_PARAMETER(sDirectory); + ASSERT( sText.IsEmpty() ); + ASSERT( sTitle.IsEmpty() ); + ASSERT( sDirectory.IsEmpty() ); + AddLine(sCommand, sCommand.GetLength(), true); +} + +void CHTRichEditCtrl::AppendColoredText(LPCTSTR pszText, COLORREF cr, COLORREF bk, DWORD mask) +{ + AddLine(pszText, -1, false, cr, bk, mask); +} + +void CHTRichEditCtrl::AppendKeyWord(const CString& str, COLORREF cr) +{ + AppendColoredText(str, cr); +} + +BOOL CHTRichEditCtrl::OnEnLink(NMHDR *pNMHDR, LRESULT *pResult) +{ + BOOL bMsgHandled = FALSE; + *pResult = 0; + ENLINK* pEnLink = reinterpret_cast(pNMHDR); + if (pEnLink && pEnLink->msg == WM_LBUTTONDOWN) + { + CString strUrl; + GetTextRange(pEnLink->chrg.cpMin, pEnLink->chrg.cpMax, strUrl); + + // check if that "URL" has a valid URL scheme. if it does not have, pass that notification up to the + // parent window which may interpret that "URL" in some other way. + for (int i = 0; i < _countof(s_apszSchemes); i++){ + if (_tcsncmp(strUrl, s_apszSchemes[i].pszScheme, s_apszSchemes[i].iLen) == 0){ + ShellExecute(NULL, NULL, strUrl, NULL, NULL, SW_SHOWDEFAULT); + *pResult = 1; + bMsgHandled = TRUE; // do not route this message to any parent + break; + } + } + } + return bMsgHandled; +} + +CString CHTRichEditCtrl::GetText() const +{ + CString strText; + GetWindowText(strText); + return strText; +} + +void CHTRichEditCtrl::SetFont(CFont* pFont, BOOL bRedraw) +{ + // Use the 'ScrollInfo' only, if there is a scrollbar available, otherwise we would + // use a scrollinfo which points to the top and we would thus stay at the top. + bool bAtEndOfScroll; + SCROLLINFO si; + si.cbSize = sizeof si; + si.fMask = SIF_ALL; + if ((GetStyle() & WS_VSCROLL) && GetScrollInfo(SB_VERT, &si)) + bAtEndOfScroll = (si.nPos >= (int)(si.nMax - si.nPage)); + else + bAtEndOfScroll = true; + + LOGFONT lf = {0}; + pFont->GetLogFont(&lf); + + CHARFORMAT cf = {0}; + cf.cbSize = sizeof cf; + + cf.dwMask |= CFM_BOLD; + cf.dwEffects |= (lf.lfWeight == FW_BOLD) ? CFE_BOLD : 0; + + cf.dwMask |= CFM_ITALIC; + cf.dwEffects |= (lf.lfItalic) ? CFE_ITALIC : 0; + + cf.dwMask |= CFM_UNDERLINE; + cf.dwEffects |= (lf.lfUnderline) ? CFE_UNDERLINE : 0; + + cf.dwMask |= CFM_STRIKEOUT; + cf.dwEffects |= (lf.lfStrikeOut) ? CFE_STRIKEOUT : 0; + + cf.dwMask |= CFM_SIZE; + HDC hDC = ::GetDC(HWND_DESKTOP); + int iPointSize = -MulDiv(lf.lfHeight, 72, GetDeviceCaps(hDC, LOGPIXELSY)); + cf.yHeight = iPointSize * 20; + ::ReleaseDC(NULL, hDC); + + cf.dwMask |= CFM_FACE; + cf.bPitchAndFamily = lf.lfPitchAndFamily; + _tcsncpy(cf.szFaceName, lf.lfFaceName, _countof(cf.szFaceName)); + cf.szFaceName[_countof(cf.szFaceName) - 1] = _T('\0'); + + // although this should work correctly (according SDK) it may give false results (e.g. the "click here..." text + // which is shown in the server info window may not be entirely used as a hyperlink???) + // cf.dwMask |= CFM_CHARSET; + // cf.bCharSet = lf.lfCharSet; + + cf.yOffset = 0; + VERIFY( SetDefaultCharFormat(cf) ); + + // copy everything except the color + m_cfDefault.dwMask = (cf.dwMask & ~CFM_COLOR) | (m_cfDefault.dwMask & CFM_COLOR); + m_cfDefault.dwEffects = cf.dwEffects; + m_cfDefault.yHeight = cf.yHeight; + m_cfDefault.yOffset = cf.yOffset; + //m_cfDefault.crTextColor = cf.crTextColor; + m_cfDefault.bCharSet = cf.bCharSet; + m_cfDefault.bPitchAndFamily = cf.bPitchAndFamily; + memcpy(m_cfDefault.szFaceName, cf.szFaceName, sizeof(m_cfDefault.szFaceName)); + + PurgeSmileyCaches(); + + if (bAtEndOfScroll) + ScrollToLastLine(); + + if (bRedraw) + { + Invalidate(); + UpdateWindow(); + } +} + +CFont* CHTRichEditCtrl::GetFont() const +{ + ASSERT(0); + return NULL; +} + +void CHTRichEditCtrl::OnSysColorChange() +{ + CRichEditCtrl::OnSysColorChange(); + ApplySkin(); +} + +void CHTRichEditCtrl::ApplySkin() +{ + if (!m_strSkinKey.IsEmpty()) + { + // Use the 'ScrollInfo' only, if there is a scrollbar available, otherwise we would + // use a scrollinfo which points to the top and we would thus stay at the top. + bool bAtEndOfScroll; + SCROLLINFO si; + si.cbSize = sizeof si; + si.fMask = SIF_ALL; + if ((GetStyle() & WS_VSCROLL) && GetScrollInfo(SB_VERT, &si)) + bAtEndOfScroll = (si.nPos >= (int)(si.nMax - si.nPage)); + else + bAtEndOfScroll = true; + + COLORREF cr; + if (theApp.LoadSkinColor(m_strSkinKey + _T("Fg"), cr)) { + m_bDfltForeground = false; + m_crForeground = cr; + } + else { + m_bDfltForeground = m_crDfltForeground == CLR_DEFAULT; + m_crForeground = m_bDfltForeground ? GetSysColor(COLOR_WINDOWTEXT) : m_crDfltForeground; + } + + bool bSetCharFormat = false; + CHARFORMAT cf; + GetDefaultCharFormat(cf); + if (!m_bDfltForeground && (cf.dwEffects & CFE_AUTOCOLOR)) { + cf.dwEffects &= ~CFE_AUTOCOLOR; + bSetCharFormat = true; + } + else if (m_bDfltForeground && !(cf.dwEffects & CFE_AUTOCOLOR)) { + cf.dwEffects |= CFE_AUTOCOLOR; + bSetCharFormat = true; + } + if (bSetCharFormat) { + cf.dwMask |= CFM_COLOR; + cf.crTextColor = m_crForeground; + VERIFY( SetDefaultCharFormat(cf) ); + VERIFY( GetSelectionCharFormat(m_cfDefault) ); + } + + if (theApp.LoadSkinColor(m_strSkinKey + _T("Bk"), cr)) { + m_bDfltBackground = false; + m_crBackground = cr; + SetBackgroundColor(FALSE, m_crBackground); + } + else { + m_bDfltBackground = m_crDfltBackground == CLR_DEFAULT; + m_crBackground = m_bDfltBackground ? GetSysColor(COLOR_WINDOW) : m_crDfltBackground; + SetBackgroundColor(m_bDfltBackground, m_crBackground); + } + + if (bAtEndOfScroll) + ScrollToLastLine(); + } + else + { + m_bDfltForeground = m_crDfltForeground == CLR_DEFAULT; + m_crForeground = m_bDfltForeground ? GetSysColor(COLOR_WINDOWTEXT) : m_crDfltForeground; + + m_bDfltBackground = m_crDfltBackground == CLR_DEFAULT; + m_crBackground = m_bDfltBackground ? GetSysColor(COLOR_WINDOW) : m_crDfltBackground; + + VERIFY( GetSelectionCharFormat(m_cfDefault) ); + } + PurgeSmileyCaches(); +} + +BOOL CHTRichEditCtrl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) +{ + // Cheap workaround for the "Text cursor is showing while context menu is open" glitch. It could be solved properly + // with the RE's COM interface, but because the according messages are not routed with a unique control ID, it's not + // really useable (e.g. if there are more RE controls in one window). Would to envelope each RE window to get a unique ID.. + if (m_bForceArrowCursor && m_hArrowCursor) + { + ::SetCursor(m_hArrowCursor); + return TRUE; + } + return CRichEditCtrl::OnSetCursor(pWnd, nHitTest, message); +} + + +class CBitmapDataObject : public CCmdTarget +{ +public: + CBitmapDataObject(HBITMAP hBitmap); + virtual ~CBitmapDataObject(); + +protected: + DECLARE_INTERFACE_MAP(); + + BEGIN_INTERFACE_PART(DataObject, IDataObject) + STDMETHOD(GetData)(FORMATETC *pformatetcIn, STGMEDIUM *pmedium); + STDMETHOD(GetDataHere)(FORMATETC *pformatetc, STGMEDIUM *pmedium); + STDMETHOD(QueryGetData)(FORMATETC *pformatetc); + STDMETHOD(GetCanonicalFormatEtc)(FORMATETC *pformatectIn, FORMATETC *pformatetcOut); + STDMETHOD(SetData)(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease); + STDMETHOD(EnumFormatEtc)(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc); + STDMETHOD(DAdvise)(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection); + STDMETHOD(DUnadvise)(DWORD dwConnection); + STDMETHOD(EnumDAdvise)(IEnumSTATDATA **ppenumAdvise); + END_INTERFACE_PART(DataObject) + + HBITMAP m_hBitmap; +}; + +BEGIN_INTERFACE_MAP(CBitmapDataObject, CCmdTarget) + INTERFACE_PART(CBitmapDataObject, IID_IDataObject, DataObject) +END_INTERFACE_MAP() + +CBitmapDataObject::CBitmapDataObject(HBITMAP hBitmap) +{ + m_hBitmap = hBitmap; +} + +CBitmapDataObject::~CBitmapDataObject() +{ +} + +#pragma warning(disable:4100) // unreferenced paramter +#pragma warning(disable:4555) // expression has no effect; expected expression with side-effect (because of the 'METHOD_PROLOGUE' macro) + +STDMETHODIMP CBitmapDataObject::XDataObject::QueryInterface(REFIID riid, void** ppvObj) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj); +} + +STDMETHODIMP_(ULONG) CBitmapDataObject::XDataObject::AddRef() +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return pThis->ExternalAddRef(); +} + +STDMETHODIMP_(ULONG) CBitmapDataObject::XDataObject::Release() +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return pThis->ExternalRelease(); +} + +STDMETHODIMP CBitmapDataObject::XDataObject::GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); +#ifdef USE_METAFILE + pmedium->tymed = TYMED_ENHMF; + pmedium->hEnhMetaFile = (HENHMETAFILE)pThis->m_hBitmap; +#else + pmedium->tymed = TYMED_GDI; + pmedium->hBitmap = (HBITMAP)CopyImage(pThis->m_hBitmap, IMAGE_BITMAP, 0, 0, 0); +#endif + pmedium->pUnkForRelease = NULL; + return S_OK; +} + +STDMETHODIMP CBitmapDataObject::XDataObject::GetDataHere(FORMATETC *pformatetc, STGMEDIUM *pmedium) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return E_NOTIMPL; +} + +STDMETHODIMP CBitmapDataObject::XDataObject::QueryGetData(FORMATETC *pformatetc) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return E_NOTIMPL; +} + +STDMETHODIMP CBitmapDataObject::XDataObject::GetCanonicalFormatEtc(FORMATETC *pformatectIn, FORMATETC *pformatetcOut) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return E_NOTIMPL; +} + +STDMETHODIMP CBitmapDataObject::XDataObject::SetData(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return E_NOTIMPL; +} + +STDMETHODIMP CBitmapDataObject::XDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return E_NOTIMPL; +} + +STDMETHODIMP CBitmapDataObject::XDataObject::DAdvise(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return E_NOTIMPL; +} + +STDMETHODIMP CBitmapDataObject::XDataObject::DUnadvise(DWORD dwConnection) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return E_NOTIMPL; +} + +STDMETHODIMP CBitmapDataObject::XDataObject::EnumDAdvise(IEnumSTATDATA **ppenumAdvise) +{ + METHOD_PROLOGUE(CBitmapDataObject, DataObject); + return E_NOTIMPL; +} + +#pragma warning(default:4555) // expression has no effect; expected expression with side-effect (because of the 'METHOD_PROLOGUE' macro) +#pragma warning(default:4100) // unreferenced paramter + +static const struct +{ + LPCTSTR pszSmiley; + int iLen; + LPCTSTR pszID; +} s_apszSmileys[] = +{ + // :) :))) ;) :D :/ :P :-x :( :'-( :-| :-* :ph34r: =) :] :[ :-O <_< +#define S(str, id) { _T(str), _countof(str)-1, _T(id) } + S(":)", "smile"), + S(":-)", "smile"), + + S(":]", "smileq"), + S(":-]", "smileq"), + + S(":))", "happy"), + S(":)))", "happy"), + S(":-))", "happy"), + S(":-)))", "happy"), + S("^_^", "happy"), + S("(^.^)", "happy"), + + S(";)", "wink"), + S(";-)", "wink"), + + S(":D", "laugh"), + S(":-D", "laugh"), + S("lol", "laugh"), + S("LOL", "laugh"), + S(":lol:", "laugh"), + S(":LOL:", "laugh"), + S("*lol*", "laugh"), + S("*LOL*", "laugh"), + + S("=)", "interest"), + S("=-)", "interest"), + + S(":/", "skeptic"), + S(":-/", "skeptic"), + S(":\\", "skeptic"), + S(":-\\", "skeptic"), + + S("<.<", "lookside"), + S("<_<", "lookside"), + S(">.>", "lookside"), + S(">_>", "lookside"), + + S(":P", "tongue"), + S(":-P", "tongue"), + S(":p", "tongue"), + S(":-p", "tongue"), + + S(":-x", "sealed"), + S(":-X", "sealed"), + + S(":-|", "disgust"), + + S(":(", "sad"), + S(":-(", "sad"), + + S(":[", "sadq"), + S(":-[", "sadq"), + + S(":cry:", "cry"), + S(":'-(", "cry"), + S(":~-(", "cry"), + S(":~(~~~", "cry"), + S(":~(~~", "cry"), + S(":~(~", "cry"), + S(":,(", "cry"), + S(";(", "cry"), + S(";-(", "cry"), + S("&.(..", "cry"), + S(":'(", "cry"), + S(":,-(", "cry"), + + S(":o", "omg"), + S(":O", "omg"), + S(":-o", "omg"), + S(":-O", "omg"), + + S(":love:", "love"), + S("(*_*)", "love"), + S("<*_*>", "love"), + S(":kiss:", "love"), + S(":-*", "love"), + + S("-_-", "ph34r"), + S(":ph34r:", "ph34r"), +#undef S +}; + +void CHTRichEditCtrl::AddSmileys(LPCTSTR pszLine) +{ + int iPos = 0; + LPCTSTR psz = pszLine; + LPCTSTR pszStart = psz; + while (*psz != _T('\0')) + { + bool bFoundSmiley = false; + if (iPos == 0 || psz[-1] == _T(' ') || psz[-1] == _T('.')) + { + for (int i = 0; i < _countof(s_apszSmileys); i++) + { + if (_tcsncmp(psz, s_apszSmileys[i].pszSmiley, s_apszSmileys[i].iLen) == 0 + && ( psz[s_apszSmileys[i].iLen] == _T('\0') + || psz[s_apszSmileys[i].iLen] == _T('\r') + || psz[s_apszSmileys[i].iLen] == _T('\n') + || psz[s_apszSmileys[i].iLen] == _T(' '))) + { + if (psz - pszStart > 0) + ReplaceSel(CString(pszStart, psz - pszStart)); + if (!InsertSmiley(s_apszSmileys[i].pszID)) + ReplaceSel(s_apszSmileys[i].pszSmiley); + psz += s_apszSmileys[i].iLen; + iPos += s_apszSmileys[i].iLen; + pszStart = psz; + bFoundSmiley = true; + break; + } + } + } + if (!bFoundSmiley) + { + psz = _tcsinc(psz); + iPos++; + } + } + if (*pszStart != _T('\0')) + ReplaceSel(pszStart); +} + +HBITMAP IconToBitmap(HICON hIcon, COLORREF crBackground, int cx = 16, int cy = 16) +{ + if (cx <= 0 || cy <= 0) + { + ICONINFO ii; + if (!GetIconInfo(hIcon, &ii)) + return NULL; + BITMAP bmi; + int iSize = GetObject(ii.hbmColor, sizeof(bmi), &bmi); + DeleteObject(ii.hbmMask); + DeleteObject(ii.hbmColor); + if (iSize < sizeof(bmi) - sizeof(bmi.bmBits)) + return NULL; + cx = bmi.bmWidth; + cy = bmi.bmHeight; + } + + // Draw icon into bitmap for keeping any available transparency. Another way todo this + // might be to use a metafile - would need to test.. + HBITMAP hBitmap = NULL; + try { + // Does *not* create a transparent bitmap + //ULONG_PTR gdiplusToken = 0; + //Gdiplus::GdiplusStartupInput gdiplusStartupInput; + //if (Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) == Gdiplus::Ok) + //{ + // Gdiplus::Bitmap bmp(hIcon); + // Gdiplus::Color colorBackground(255, GetRValue(crBackground), GetGValue(crBackground), GetBValue(crBackground)); + // bmp.GetHBITMAP(colorBackground, &hBitmap); + //} + //Gdiplus::GdiplusShutdown(gdiplusToken); + +#ifdef USE_METAFILE + { + HDC hdc = GetDC(HWND_DESKTOP); + int iWidthMM = GetDeviceCaps(hdc, HORZSIZE); + int iHeightMM = GetDeviceCaps(hdc, VERTSIZE); + int iWidthPels = GetDeviceCaps(hdc, HORZRES); + int iHeightPels = GetDeviceCaps(hdc, VERTRES); + CRect rcMF(0, 0, ((cx + 1) * iWidthMM * 100)/iWidthPels, ((cy + 1) * iHeightMM * 100)/iHeightPels); + HDC hdcEnhMF = CreateEnhMetaFile(NULL, NULL, &rect, NULL); + if (hdcEnhMF) + { + SetBkColor(hdcEnhMF, crBackground); + DrawIconEx(hdcEnhMF, 0, 0, hIcon, cx, cy, 0, 0, DI_NORMAL); + hBitmap = (HBITMAP)CloseEnhMetaFile(hdcEnhMF); + } + ReleaseDC(HWND_DESKTOP, hdc); + } +#else + CClientDC dcScreen(CWnd::GetDesktopWindow()); + CDC dcMem; + if (dcMem.CreateCompatibleDC(&dcScreen)) + { + CBitmap bmp; + if (bmp.CreateCompatibleBitmap(&dcScreen, cx, cy)) + { + CBitmap *pbmpOld = dcMem.SelectObject(&bmp); + dcMem.FillSolidRect(0, 0, cx, cy, crBackground); + DrawIconEx(dcMem, 0, 0, hIcon, cx, cy, 0, 0, DI_NORMAL); + dcMem.SelectObject(pbmpOld); + hBitmap = (HBITMAP)bmp.Detach(); + } + } +#endif + } + catch(CException *ex){ + ASSERT(0); + ex->Delete(); + } + + return hBitmap; +} + +HBITMAP CHTRichEditCtrl::GetSmileyBitmap(LPCTSTR pszSmileyID) +{ + void *pvData; + if (sm_aSmileyBitmaps.Lookup(pszSmileyID, pvData)) + return (HBITMAP)pvData; + + int cx = 16, cy = 16; + CHARFORMAT cf; + GetDefaultCharFormat(cf); + if (cf.cbSize == sizeof(cf) && (cf.dwMask & CFM_SIZE)) + { + HDC hDC = ::GetDC(HWND_DESKTOP); + int iPixelFontSize = abs(-MulDiv(cf.yHeight, GetDeviceCaps(hDC, LOGPIXELSY), 20) / 72); + ::ReleaseDC(NULL, hDC); + //Point Pixel Icon + //10 13 14 + //11 14 14 + //12 16 18 + //14 18 24 + //16 21 24 + //18 24 24 + //20 26 32 + if (iPixelFontSize <= 14) + cx = cy = 16; + else if (iPixelFontSize <= 17) + cx = cy = 18; + else if (iPixelFontSize <= 25) + cx = cy = 24; + else + cx = cy = 32; + } + + CString strResourceName(_T("Smiley_")); + strResourceName += pszSmileyID; + HICON hIcon = theApp.LoadIcon(strResourceName, cx, cy, LR_DEFAULTCOLOR); + if (hIcon == NULL) + return (HBITMAP)NULL; + + // Don't specify an icon size for the bitmap creation, we could use 'cx' and 'cy' only for + // the builtin icons because we know their sizes, but if there is a skin active, the + // icons (which can also be GIF images) can have any size. + HBITMAP hBitmap = IconToBitmap(hIcon, m_crBackground, 0, 0); + if (hBitmap == NULL) + return (HBITMAP)NULL; + + sm_aSmileyBitmaps.SetAt(pszSmileyID, hBitmap); + return hBitmap; +} + +bool CHTRichEditCtrl::InsertSmiley(LPCTSTR pszSmileyID) +{ + HRESULT hr; + HBITMAP hbmp = GetSmileyBitmap(pszSmileyID); + if (hbmp == NULL) + return false; + + CBitmapDataObject *pbdo = new CBitmapDataObject(hbmp); + CComPtr pIDataObject; + pIDataObject.Attach((IDataObject *)pbdo->GetInterface(&IID_IDataObject)); + + CComPtr pIRichEditOle; + pIRichEditOle.Attach(GetIRichEditOle()); + if (!pIRichEditOle) + return false; + + CComPtr pIOleClientSite; + pIRichEditOle->GetClientSite(&pIOleClientSite); + if (!pIOleClientSite) + return false; + + if (sm_pIStorageSmileys == NULL) + { + CComPtr pILockBytes; + if ((hr = CreateILockBytesOnHGlobal(NULL, TRUE, &pILockBytes)) != S_OK) + return false; + if ((hr = StgCreateDocfileOnILockBytes(pILockBytes, STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_READWRITE, 0, &sm_pIStorageSmileys)) != S_OK) + return false; + } + + FORMATETC FormatEtc; +#ifdef USE_METAFILE + FormatEtc.cfFormat = CF_ENHMETAFILE; +#else + FormatEtc.cfFormat = CF_BITMAP; +#endif + FormatEtc.ptd = NULL; + FormatEtc.dwAspect = DVASPECT_CONTENT; + FormatEtc.lindex = -1; +#ifdef USE_METAFILE + FormatEtc.tymed = TYMED_ENHMF; +#else + FormatEtc.tymed = TYMED_GDI; +#endif + + CComPtr pIOleObject; + if ((hr = OleCreateStaticFromData(pIDataObject, __uuidof(pIOleObject), OLERENDER_FORMAT, &FormatEtc, pIOleClientSite, sm_pIStorageSmileys, (void **)&pIOleObject)) != S_OK) + return false; + OleSetContainedObject(pIOleObject, TRUE); + + REOBJECT reobject = {0}; + reobject.cbStruct = sizeof(reobject); + if ((hr = pIOleObject->GetUserClassID(&reobject.clsid)) != S_OK) + return false; + reobject.cp = REO_CP_SELECTION; + reobject.dvaspect = DVASPECT_CONTENT; + reobject.poleobj = pIOleObject; + reobject.polesite = pIOleClientSite; + reobject.pstg = sm_pIStorageSmileys; + reobject.dwFlags = REO_BELOWBASELINE; + + if ((hr = pIRichEditOle->InsertObject(&reobject)) != S_OK) + return false; + + return true; +} + +bool CHTRichEditCtrl::AddCaptcha(HBITMAP hbmp) +{ + HRESULT hr; + if (hbmp == NULL) + return false; + + SetSel(GetWindowTextLength(), GetWindowTextLength()); + CBitmapDataObject *pbdo = new CBitmapDataObject(hbmp); + CComPtr pIDataObject; + pIDataObject.Attach((IDataObject *)pbdo->GetInterface(&IID_IDataObject)); + + CComPtr pIRichEditOle; + pIRichEditOle.Attach(GetIRichEditOle()); + if (!pIRichEditOle) + return false; + + CComPtr pIOleClientSite; + pIRichEditOle->GetClientSite(&pIOleClientSite); + if (!pIOleClientSite) + return false; + + if (m_pIStorageCaptchas == NULL) + { + CComPtr pILockBytes; + if ((hr = CreateILockBytesOnHGlobal(NULL, TRUE, &pILockBytes)) != S_OK) + return false; + if ((hr = StgCreateDocfileOnILockBytes(pILockBytes, STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_READWRITE, 0, &m_pIStorageCaptchas)) != S_OK) + return false; + } + + FORMATETC FormatEtc; +#ifdef USE_METAFILE + FormatEtc.cfFormat = CF_ENHMETAFILE; +#else + FormatEtc.cfFormat = CF_BITMAP; +#endif + FormatEtc.ptd = NULL; + FormatEtc.dwAspect = DVASPECT_CONTENT; + FormatEtc.lindex = -1; +#ifdef USE_METAFILE + FormatEtc.tymed = TYMED_ENHMF; +#else + FormatEtc.tymed = TYMED_GDI; +#endif + + CComPtr pIOleObject; + if ((hr = OleCreateStaticFromData(pIDataObject, __uuidof(pIOleObject), OLERENDER_FORMAT, &FormatEtc, pIOleClientSite, m_pIStorageCaptchas, (void **)&pIOleObject)) != S_OK) + return false; + OleSetContainedObject(pIOleObject, TRUE); + + REOBJECT reobject = {0}; + reobject.cbStruct = sizeof(reobject); + if ((hr = pIOleObject->GetUserClassID(&reobject.clsid)) != S_OK) + return false; + reobject.cp = REO_CP_SELECTION; + reobject.dvaspect = DVASPECT_CONTENT; + reobject.poleobj = pIOleObject; + reobject.polesite = pIOleClientSite; + reobject.pstg = m_pIStorageCaptchas; + reobject.dwFlags = REO_BELOWBASELINE; + + if ((hr = pIRichEditOle->InsertObject(&reobject)) != S_OK) + return false; + ReplaceSel(_T("")); + + return true; +} diff --git a/HTRichEditCtrl.h b/HTRichEditCtrl.h new file mode 100644 index 00000000..c23904f7 --- /dev/null +++ b/HTRichEditCtrl.h @@ -0,0 +1,97 @@ +#pragma once + +#include "TitleMenu.h" + +class CHTRichEditCtrl : public CRichEditCtrl +{ + DECLARE_DYNAMIC(CHTRichEditCtrl) + +public: + CHTRichEditCtrl(); + virtual ~CHTRichEditCtrl(); + + void Init(LPCTSTR pszTitle, LPCTSTR pszSkinKey = NULL); + void SetProfileSkinKey(LPCTSTR pszSkinKey); + void SetTitle(LPCTSTR pszTitle); + void Localize(); + void ApplySkin(); + void EnableSmileys(bool bEnable = true); + + void AddEntry(LPCTSTR pszMsg); + void Add(LPCTSTR pszMsg, int iLen = -1); + void AddTyped(LPCTSTR pszMsg, int iLen, UINT uFlags); + void AddLine(LPCTSTR pszMsg, int iLen = -1, bool bLink = false, COLORREF cr = CLR_DEFAULT, COLORREF bk = CLR_DEFAULT, DWORD mask = 0); + bool AddCaptcha(HBITMAP hbmp); + void Reset(); + CString GetLastLogEntry(); + CString GetAllLogEntries(); + bool SaveLog(LPCTSTR pszDefName = NULL); + + void AppendText(const CString& sText); + void AppendHyperLink(const CString& sText, const CString& sTitle, const CString& sCommand, const CString& sDirectory); + void AppendKeyWord(const CString& sText, COLORREF cr); + void AppendColoredText(LPCTSTR pszText, COLORREF cr, COLORREF bk = CLR_DEFAULT, DWORD mask = 0); + COLORREF GetForegroundColor() const { return m_crForeground; } + COLORREF GetBackgroundColor() const { return m_crBackground; } + void SetDfltForegroundColor(COLORREF crColor) { m_crDfltForeground = crColor; } + void SetDfltBackgroundColor(COLORREF crColor) { m_crDfltBackground = crColor; } + + CString GetText() const; + bool IsAutoScroll() const { return m_bAutoScroll; } + void SetAutoScroll(bool bAutoScroll) { m_bAutoScroll = bAutoScroll; } + void ScrollToLastLine(bool bForceLastLineAtBottom = false); + void ScrollToFirstLine(); + + void SetFont(CFont* pFont, BOOL bRedraw = TRUE); + CFont* GetFont() const; + +protected: + bool m_bRichEdit; + int m_iLimitText; + bool m_bAutoScroll; + CStringArray m_astrBuff; + bool m_bNoPaint; + bool m_bEnErrSpace; + CString m_strTitle; + CString m_strSkinKey; + bool m_bRestoreFormat; + CHARFORMAT m_cfDefault; + bool m_bForceArrowCursor; + HCURSOR m_hArrowCursor; + bool m_bEnableSmileys; + COLORREF m_crForeground; + COLORREF m_crBackground; + bool m_bDfltForeground; + bool m_bDfltBackground; + COLORREF m_crDfltForeground; + COLORREF m_crDfltBackground; + static int sm_iSmileyClients; + static CComPtr sm_pIStorageSmileys; + static CMapStringToPtr sm_aSmileyBitmaps; + CComPtr m_pIStorageCaptchas; + + void SelectAllItems(); + void CopySelectedItems(); + int GetMaxSize(); + void SafeAddLine(int nPos, LPCTSTR pszLine, int iLen, long& nStartChar, long& nEndChar, bool bLink, COLORREF cr, COLORREF bk, DWORD mask); + void FlushBuffer(); + void AddString(int nPos, LPCTSTR pszString, bool bLink, COLORREF cr, COLORREF bk, DWORD mask); + bool InsertSmiley(LPCTSTR pszSmileyID); + HBITMAP GetSmileyBitmap(LPCTSTR pszSmileyID); + void AddSmileys(LPCTSTR pszLine); + void PurgeSmileyCaches(); + + virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); + + DECLARE_MESSAGE_MAP() + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg void OnEnErrspace(); + afx_msg void OnEnMaxtext(); + afx_msg BOOL OnEnLink(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnSysColorChange(); + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + afx_msg void OnSize(UINT nType, int cx, int cy); +}; diff --git a/HelpIDs.h b/HelpIDs.h new file mode 100644 index 00000000..6ca4f1b3 --- /dev/null +++ b/HelpIDs.h @@ -0,0 +1,149 @@ +#define eMule_FAQ_Addresses_To_Server_Lists 1 +#define eMule_FAQ_Agressive_Client_Ban 2 +#define eMule_FAQ_Beginner_s_Guide 3 +#define eMule_FAQ_Categories 4 +#define eMule_FAQ_Client_To_Client_Source_Exchange 5 +#define eMule_FAQ_Colors_Of_The_Progess_Bar 6 +#define eMule_FAQ_Credit_System 7 +#define eMule_FAQ_Dynamic_DNS_Resolution_For_Servers 8 +#define eMule_FAQ_eMule_Help 9 +#define eMule_FAQ_eMule_Support_In_Various_Languages 10 +#define eMule_FAQ_Error_Messages 11 +#define eMule_FAQ_Error_Messages_error_versiontag 12 +#define eMule_FAQ_FAQ_Credits 13 +#define eMule_FAQ_FAQ_Credits_clientsmet 14 +#define eMule_FAQ_FAQ_Credits_credits 15 +#define eMule_FAQ_FAQ_Credits_cryptkey 16 +#define eMule_FAQ_FAQ_Credits_own 17 +#define eMule_FAQ_FAQ_Credits_secure 18 +#define eMule_FAQ_FAQ_Despair 19 +#define eMule_FAQ_FAQ_Downloads 20 +#define eMule_FAQ_FAQ_Downloads_a4af 21 +#define eMule_FAQ_FAQ_Downloads_bar 22 +#define eMule_FAQ_FAQ_Downloads_comment 23 +#define eMule_FAQ_FAQ_Downloads_connections 24 +#define eMule_FAQ_FAQ_Downloads_count 25 +#define eMule_FAQ_FAQ_Downloads_downloads 26 +#define eMule_FAQ_FAQ_Downloads_errorious 27 +#define eMule_FAQ_FAQ_Downloads_eternity 28 +#define eMule_FAQ_FAQ_Downloads_fake 29 +#define eMule_FAQ_FAQ_Downloads_filename 30 +#define eMule_FAQ_FAQ_Downloads_incoming 31 +#define eMule_FAQ_FAQ_Downloads_qr 32 +#define eMule_FAQ_FAQ_Downloads_rating 33 +#define eMule_FAQ_FAQ_Downloads_temp 34 +#define eMule_FAQ_FAQ_Downloads_unshare 35 +#define eMule_FAQ_FAQ_Index 36 +#define eMule_FAQ_FAQ_Installation 37 +#define eMule_FAQ_FAQ_Installation_binaries 38 +#define eMule_FAQ_FAQ_Installation_oleacc 39 +#define eMule_FAQ_FAQ_Installation_OS 40 +#define eMule_FAQ_FAQ_Installation_update 41 +#define eMule_FAQ_FAQ_Installation_upgrade 42 +#define eMule_FAQ_FAQ_Misc 43 +#define eMule_FAQ_FAQ_Misc_anon 44 +#define eMule_FAQ_FAQ_Misc_copy 45 +#define eMule_FAQ_FAQ_Misc_filter 46 +#define eMule_FAQ_FAQ_Misc_firewall 47 +#define eMule_FAQ_FAQ_Misc_help 48 +#define eMule_FAQ_FAQ_Misc_mail 49 +#define eMule_FAQ_FAQ_Misc_messages 50 +#define eMule_FAQ_FAQ_Misc_mods1 51 +#define eMule_FAQ_FAQ_Misc_mods2 52 +#define eMule_FAQ_FAQ_Misc_surfing 53 +#define eMule_FAQ_FAQ_Server 54 +#define eMule_FAQ_FAQ_Server_best 55 +#define eMule_FAQ_FAQ_Server_bootstrap 56 +#define eMule_FAQ_FAQ_Server_connection 57 +#define eMule_FAQ_FAQ_Server_id 58 +#define eMule_FAQ_FAQ_Server_loss 59 +#define eMule_FAQ_FAQ_Server_serverlist 60 +#define eMule_FAQ_FAQ_Server_update 61 +#define eMule_FAQ_FAQ_Settings 62 +#define eMule_FAQ_FAQ_Settings_emule 63 +#define eMule_FAQ_FAQ_Settings_expect 64 +#define eMule_FAQ_FAQ_Settings_faster 65 +#define eMule_FAQ_FAQ_Settings_queue 66 +#define eMule_FAQ_FAQ_Settings_settings 67 +#define eMule_FAQ_FAQ_Settings_speed 68 +#define eMule_FAQ_FAQ_Using_eMule 69 +#define eMule_FAQ_FAQ_Using_eMule_ban 70 +#define eMule_FAQ_FAQ_Using_eMule_friend 71 +#define eMule_FAQ_FAQ_Using_eMule_general 72 +#define eMule_FAQ_FAQ_Using_eMule_icons 73 +#define eMule_FAQ_FAQ_Using_eMule_priority 74 +#define eMule_FAQ_FAQ_Using_eMule_results 75 +#define eMule_FAQ_FAQ_Using_eMule_search 76 +#define eMule_FAQ_FAQ_Using_eMule_upload 77 +#define eMule_FAQ_Files_eMule_uses 78 +#define eMule_FAQ_Firewalls 79 +#define eMule_FAQ_Friends 80 +#define eMule_FAQ_ICF 81 +#define eMule_FAQ_Icons 82 +#define eMule_FAQ_ID_Explained 83 +#define eMule_FAQ_IP_Filtering 84 +#define eMule_FAQ_IRC_Chat 85 +#define eMule_FAQ_Linksys_BEFSX41 86 +#define eMule_FAQ_Linksys_BEFSX41_alternate 87 +#define eMule_FAQ_Linksys_BEFSX41_step4 88 +#define eMule_FAQ_Linksys_BEFSX41_triggering 89 +#define eMule_FAQ_Lists 90 +#define eMule_FAQ_Lucent_CellPipe_20A_GX_E 91 +#define eMule_FAQ_MuleMRTG 92 +#define eMule_FAQ_Network_Guide 93 +#define eMule_FAQ_Online_Signature 94 +#define eMule_FAQ_Ports 95 +#define eMule_FAQ_Preferences_Connection 96 +#define eMule_FAQ_Preferences_Directories 97 +#define eMule_FAQ_Preferences_Display 98 +#define eMule_FAQ_Preferences_Extended_Settings 99 +#define eMule_FAQ_Preferences_Files 100 +#define eMule_FAQ_Preferences_General 101 +#define eMule_FAQ_Preferences_IRC 102 +#define eMule_FAQ_Preferences_Notifications 103 +#define eMule_FAQ_Preferences_Preferences_ini 104 +#define eMule_FAQ_Preferences_Proxy 105 +#define eMule_FAQ_Preferences_Scheduler 106 +#define eMule_FAQ_Preferences_Security 107 +#define eMule_FAQ_Preferences_Server 108 +#define eMule_FAQ_Preferences_Statistics 109 +#define eMule_FAQ_Preferences_WebInterface 110 +#define eMule_FAQ_Preview_Files 111 +#define eMule_FAQ_Queue_Ranking 112 +#define eMule_FAQ_Rating 113 +#define eMule_FAQ_Routers 114 +#define eMule_FAQ_Search 115 +#define eMule_FAQ_Secure_User_Identification 116 +#define eMule_FAQ_SMC_Barricade_7004_ABR 117 +#define eMule_FAQ_SMC_Barricade_7004_ABR_1 118 +#define eMule_FAQ_SMC_Barricade_7004_ABR_1_1 119 +#define eMule_FAQ_SMC_Barricade_7004_ABR_2 120 +#define eMule_FAQ_SMC_Barricade_7004_ABR_2_1a 121 +#define eMule_FAQ_SMC_Barricade_7004_ABR_3_1 122 +#define eMule_FAQ_SMC_Barricade_7004_ABR_3_2 123 +#define eMule_FAQ_SMC_Barricade_7004_ABR_end 124 +#define eMule_FAQ_SMC_Barricade_7004_ABR_foreword 125 +#define eMule_FAQ_SMC_Barricade_7004_ABR_m_1 126 +#define eMule_FAQ_SMC_Barricade_7004_ABR_m_2 127 +#define eMule_FAQ_SMC_Barricade_7004_ABR_2_1b 128 +#define eMule_FAQ_Solwise_SAR_7x5 129 +#define eMule_FAQ_Tools 130 +#define eMule_FAQ_Troubleshooting 131 +#define eMule_FAQ_Troubleshooting_blacklist 132 +#define eMule_FAQ_Troubleshooting_disconnecting 133 +#define eMule_FAQ_Troubleshooting_ed2k_weblinks 134 +#define eMule_FAQ_Troubleshooting_lost_downloads 135 +#define eMule_FAQ_Troubleshooting_strange_behaviour 136 +#define eMule_FAQ_Troubleshooting_webserver 137 +#define eMule_FAQ_Update_Server 138 +#define eMule_FAQ_WebServer 139 +#define eMule_part_file_access_module_for_VideoLAN_README 140 +#define eMule_FAQ_Preferences_Messages 141 +#define eMule_FAQ_GUI_Kad 200 +#define eMule_FAQ_GUI_Server 201 +#define eMule_FAQ_GUI_Transfers 202 +#define eMule_FAQ_GUI_Search 203 +#define eMule_FAQ_GUI_SharedFiles 204 +#define eMule_FAQ_GUI_Messages 205 +#define eMule_FAQ_GUI_IRC 206 +#define eMule_FAQ_GUI_Statistics 207 diff --git a/HighColorTab.hpp b/HighColorTab.hpp new file mode 100644 index 00000000..e4abc784 --- /dev/null +++ b/HighColorTab.hpp @@ -0,0 +1,145 @@ +// HighColorTab.hpp +// +// Author: Yves Tkaczyk (yves@tkaczyk.net) +// +// This software is released into the public domain. You are free to use it +// in any way you like BUT LEAVE THIS HEADER INTACT. +// +// This software is provided "as is" with no expressed or implied warranty. +// I accept no liability for any damage or loss of business that this software +// may cause. +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include + +#if _MSC_VER>=1400 +#pragma warning(disable:4350) // behavior change: 'std::auto_ptr<_Ty>::auto_ptr(std::auto_ptr_ref<_Ty>) throw()' called instead of 'std::auto_ptr<_Ty>::auto_ptr(std::auto_ptr<_Ty> &) throw()' +#endif + +namespace HighColorTab +{ + /*! \brief Policy class for creating image list. + + Policy for creating a high color (32 bits) image list. The policy + ensure that there is a Win32 image list associated with the CImageList. + If this is not the case, a NULL pointer shall be returned. + + Returned image list is wrapped in an std::auto_ptr. + + \sa UpdateImageListFull */ + struct CHighColorListCreator + { + /*! Create the image list. + \retval std::auto_ptr Not null if success. */ + static std::auto_ptr CreateImageList() + { + std::auto_ptr apILNew( new CImageList() ); + if( NULL == apILNew.get() ) + { + // ASSERT: The CImageList object creation failed. + ASSERT( FALSE ); + return std::auto_ptr(); + } + + if( 0 == apILNew->Create( 16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1 ) ) + { + // ASSERT: The image list (Win32) creation failed. + ASSERT( FALSE ); + return std::auto_ptr(); + } + + return apILNew; + } + }; + + + + /*! \brief Change the image list of the provided control (property sheet interface) + + This method provides full customization via policy over image list creation. The policy + must have a method with the signature: + static std::auto_ptr CreateImageList() + + \author Yves Tkaczyk (yves@tkaczyk.net) + \date 02/2004 */ + template + bool UpdateImageListFull(TSheet& rSheet) + { + // Get the tab control... + CTabCtrl* pTab = rSheet.GetTabControl(); + if (!IsWindow(pTab->GetSafeHwnd())) + { + // ASSERT: Tab control could not be retrieved or it is not a valid window. + ASSERT( FALSE ); + return false; + } + + // Create the replacement image list via policy. + std::auto_ptr apILNew( TListCreator::CreateImageList() ); + + bool bSuccess = (NULL != apILNew.get() ); + + // Reload the icons from the property pages. + int nTotalPageCount = rSheet.GetPageCount(); + for(int nCurrentPage = 0; nCurrentPage < nTotalPageCount && bSuccess; ++nCurrentPage ) + { + // Get the page. + CPropertyPage* pPage = rSheet.GetPage( nCurrentPage ); + ASSERT( pPage ); + // Set the icon in the image list from the page properties. + if( pPage && ( pPage->m_psp.dwFlags & PSP_USEHICON ) ) + { + /*bSuccess &=*/ ( -1 != apILNew->Add( pPage->m_psp.hIcon ) ); + } + + if( pPage && ( pPage->m_psp.dwFlags & PSP_USEICONID ) ) + { + HICON hIcon = theApp.LoadIcon( pPage->m_psp.pszIcon, 16, 16 ); + if (hIcon) + { + /*bSuccess &=*/ ( -1 != apILNew->Add( hIcon ) ); + DestroyIcon(hIcon); + } + } + } + + if( !bSuccess ) + { + // This ASSERT because either the image list could not be created or icon insertion failed. + ASSERT( FALSE ); + // Cleanup what we have in the new image list. + if( apILNew.get() ) + { + apILNew->DeleteImageList(); + } + + return false; + } + + // Replace the image list from the tab control. + CImageList* pilOld = pTab->SetImageList( CImageList::FromHandle( apILNew->Detach() ) ); + // Clean the old image list if there was one. + if( pilOld ) + { + pilOld->DeleteImageList(); + } + + return true; + }; + + /*! \brief Change the image list of the provided control (property sheet) + + This method uses 32 bits image list creation default policy. */ + template + bool UpdateImageList(TSheet& rSheet) + { + return UpdateImageListFull( rSheet ); + }; +}; + +#if _MSC_VER>=1400 +#pragma warning(default:4350) +#endif diff --git a/HttpClientReqSocket.cpp b/HttpClientReqSocket.cpp new file mode 100644 index 00000000..bdbc4de7 --- /dev/null +++ b/HttpClientReqSocket.cpp @@ -0,0 +1,344 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "UpDownClient.h" +#include "SafeFile.h" +#include "Packets.h" +#include "ListenSocket.h" +#include "HttpClientReqSocket.h" +#include "Preferences.h" +#include "Statistics.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// CHttpClientReqSocket +// + +IMPLEMENT_DYNCREATE(CHttpClientReqSocket, CClientReqSocket) + +CHttpClientReqSocket::CHttpClientReqSocket(CUpDownClient* client) + : CClientReqSocket(client) +{ + SetHttpState(HttpStateUnknown); + SetConnectionEncryption(false, NULL, false); // just to make sure - disable protocol encryption explicit +} + +CHttpClientReqSocket::~CHttpClientReqSocket() +{ +} + +void CHttpClientReqSocket::SetHttpState(EHttpSocketState eState) +{ + m_eHttpState = eState; + if (m_eHttpState == HttpStateRecvExpected || m_eHttpState == HttpStateUnknown) + ClearHttpHeaders(); +} + +void CHttpClientReqSocket::ClearHttpHeaders() +{ + m_strHttpCurHdrLine.Empty(); + m_astrHttpHeaders.RemoveAll(); + m_iHttpHeadersSize = 0; +} + +void CHttpClientReqSocket::SendPacket(Packet* packet, bool delpacket, bool controlpacket, uint32 actualPayloadSize, bool bForceImmediateSend) +{ + // just for safety -- never send an ed2k/emule packet via HTTP. + if (packet->opcode != 0x00 || packet->prot != 0x00){ + ASSERT(0); + return; + } + CClientReqSocket::SendPacket(packet, delpacket, controlpacket, actualPayloadSize, bForceImmediateSend); +} + +void CHttpClientReqSocket::OnConnect(int nErrorCode) +{ + CClientReqSocket::OnConnect(nErrorCode); + if (GetClient()) + GetClient()->OnSocketConnected(nErrorCode); +} + +void CHttpClientReqSocket::DataReceived(const BYTE* pucData, UINT uSize) +{ + bool bResult = false; + CString strError; + try + { + bResult = ProcessHttpPacket(pucData, uSize); + } + catch(CMemoryException* ex) + { + strError.Format(_T("Error: HTTP socket: Memory exception; %s"), DbgGetClientInfo()); + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("%s"), strError); + ex->Delete(); + } + catch(CFileException* ex) + { + TCHAR szError[MAX_CFEXP_ERRORMSG]; + ex->GetErrorMessage(szError, ARRSIZE(szError)); + strError.Format(_T("Error: HTTP socket: File exception - %s"), szError); + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("%s"), strError); + ex->Delete(); + } + catch(CString ex) + { + strError.Format(_T("Error: HTTP socket: %s; %s"), ex, DbgGetClientInfo()); + if (thePrefs.GetVerbose()) + AddDebugLogLine(false, _T("%s"), strError); + } + + if (!bResult && !deletethis) + { + if (thePrefs.GetVerbose() && thePrefs.GetDebugClientTCPLevel() <= 0) + { + for (int i = 0; i < m_astrHttpHeaders.GetCount(); i++) + AddDebugLogLine(false, _T("<%hs"), m_astrHttpHeaders.GetAt(i)); + } + + // In case this socket is attached to an CUrlClient, we are dealing with the real CUpDownClient here + // In case this socket is a PeerCacheUp/Down socket, we are dealing with the attached CUpDownClient here +// if (GetClient()) +// GetClient()->SetDownloadState(DS_ERROR); + if (client) // NOTE: The usage of 'client' and 'GetClient' makes quite a difference here! + client->SetDownloadState(DS_ERROR); + + if (strError.IsEmpty()) + strError = _T("Error: HTTP socket"); + + // In case this socket is attached to an CUrlClient, we are disconnecting the real CUpDownClient here + // In case this socket is a PeerCacheUp/Down socket, we are not disconnecting the attached CUpDownClient here + // PC-TODO: This needs to be cleaned up thoroughly because that client dependency is somewhat hidden in the + // usage of CClientReqSocket::client and CHttpClientReqSocket::GetClient. + Disconnect(strError); + } +} + +bool CHttpClientReqSocket::ProcessHttpPacket(const BYTE* pucData, UINT uSize) +{ + if (GetHttpState() == HttpStateRecvExpected || GetHttpState() == HttpStateRecvHeaders) + { + // search for EOH + LPBYTE pBody = NULL; + int iSizeBody = 0; + ProcessHttpHeaderPacket((const char*)pucData, uSize, pBody, iSizeBody); + + if (pBody) // EOH found, packet may contain partial body + { + if (thePrefs.GetDebugClientTCPLevel() > 0){ + Debug(_T("Received HTTP\n")); + DebugHttpHeaders(m_astrHttpHeaders); + } + + // PC-TODO: Should be done right in 'ProcessHttpHeaderPacket' + int iSizeHeader = 2; + for (int i = 0; i < m_astrHttpHeaders.GetCount(); i++) + iSizeHeader += m_astrHttpHeaders[i].GetLength() + 2; + theStats.AddDownDataOverheadFileRequest(iSizeHeader); + + if (iSizeBody < 0) + throw CString(_T("Internal HTTP header/body parsing error")); + + if (m_astrHttpHeaders[0].GetLength() >= 4 && memcmp((LPCSTR)m_astrHttpHeaders[0], "HTTP", 4) == 0) + { + if (!ProcessHttpResponse()) + return false; + + SetHttpState(HttpStateRecvBody); + if (iSizeBody > 0){ + // packet contained HTTP headers and (partial) body + ProcessHttpResponseBody(pBody, iSizeBody); + } + else{ + // packet contained HTTP headers but no body (packet terminates with EOH) + // body will be processed because of HTTP state 'HttpStateRecvBody' with next recv + ; + } + } + else if (m_astrHttpHeaders[0].GetLength() >= 3 && memcmp((LPCSTR)m_astrHttpHeaders[0], "GET", 3) == 0) + { + if (!ProcessHttpRequest()) + return false; + if (iSizeBody != 0){ + ASSERT(0); // no body for GET requests allowed yet + return false; + } + } + else + throw CString(_T("Invalid HTTP header received")); + } + else + { + TRACE("+++ Received partial HTTP header packet\n"); + } + } + else if (GetHttpState() == HttpStateRecvBody) + { + ProcessHttpResponseBody(pucData, uSize); + } + else{ + theStats.AddDownDataOverheadFileRequest(uSize); + throw CString(_T("Invalid HTTP socket state")); + } + + return true; +} + +bool CHttpClientReqSocket::ProcessHttpResponse() +{ + ASSERT(0); + return false; +} + +bool CHttpClientReqSocket::ProcessHttpResponseBody(const BYTE* /*pucData*/, UINT /*uSize*/) +{ + ASSERT(0); + return false; +} + +bool CHttpClientReqSocket::ProcessHttpRequest() +{ + ASSERT(0); + return false; +} + +void SplitHeaders(LPCSTR pszHeaders, CStringArray& astrHeaders) +{ + const char* p = pszHeaders; + const char* pCrLf; + while ((pCrLf = strstr(p, "\r\n")) != NULL) + { + int iLineLen = pCrLf - p; + const char* pLine = p; + p = pCrLf + 2; + ASSERT( iLineLen >= 0 ); + if (iLineLen == 0) + break; + + CString strHdr(pLine, iLineLen); + astrHeaders.Add(strHdr); + } +} + +#define MAX_HTTP_HEADERS_SIZE 2048 +#define MAX_HTTP_HEADER_LINE_SIZE 1024 + +void CHttpClientReqSocket::ProcessHttpHeaderPacket(const char* packet, UINT size, LPBYTE& pBody, int& iSizeBody) +{ + LPCSTR p = packet; + int iLeft = size; + while (iLeft > 0 && pBody == NULL) + { + LPCSTR pszNl = (LPCSTR)memchr(p, '\n', iLeft); + if (pszNl) + { + // append current (partial) line to any already received partial line + int iLineLen = pszNl - p; + ASSERT( iLineLen >= 0 ); + if (iLineLen > 0) + m_strHttpCurHdrLine += CStringA(p, iLineLen - 1); // do not copy the '\r' character + + // in case the CRLF were split up in different packets, the currenty line may contain a '\r' character, remove it + int iCurHdrLineLen = m_strHttpCurHdrLine.GetLength(); + if (iCurHdrLineLen > 0 && m_strHttpCurHdrLine[iCurHdrLineLen-1] == '\r') + (void)m_strHttpCurHdrLine.GetBufferSetLength(iCurHdrLineLen-1); // quickly remove the last character + + p += iLineLen + 1; + iLeft -= iLineLen + 1; + ASSERT( iLeft >= 0 ); + + if (m_strHttpCurHdrLine.IsEmpty()) // if current line is empty, we have found 2(!) CRLFs -> start of body + { + pBody = (LPBYTE)p; + iSizeBody = iLeft; + ASSERT( iSizeBody >= 0 ); + } + else + { + // add current line to headers + m_astrHttpHeaders.Add(m_strHttpCurHdrLine); + m_iHttpHeadersSize += m_strHttpCurHdrLine.GetLength(); + m_strHttpCurHdrLine.Empty(); + + // safety check + if (m_iHttpHeadersSize > MAX_HTTP_HEADERS_SIZE) + throw CString(_T("Received HTTP headers exceed limit")); + } + } + else + { + // partial line, add to according buffer + m_strHttpCurHdrLine += CStringA(p, iLeft); + iLeft = 0; + + // safety check + if (m_strHttpCurHdrLine.GetLength() > MAX_HTTP_HEADER_LINE_SIZE) + throw CString(_T("Received HTTP header line exceeds limit")); + } + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// CHttpClientDownSocket +// + +IMPLEMENT_DYNCREATE(CHttpClientDownSocket, CHttpClientReqSocket) + +CHttpClientDownSocket::CHttpClientDownSocket(CUpDownClient* client) + : CHttpClientReqSocket(client) +{ +} + +CHttpClientDownSocket::~CHttpClientDownSocket() +{ +} + +bool CHttpClientDownSocket::ProcessHttpResponse() +{ + if (GetClient() == NULL) + throw CString(__FUNCTION__ " - No client attached to HTTP socket"); + + if (!GetClient()->ProcessHttpDownResponse(m_astrHttpHeaders)) + return false; + + return true; +} + +bool CHttpClientDownSocket::ProcessHttpResponseBody(const BYTE* pucData, UINT size) +{ + if (GetClient() == NULL) + throw CString(__FUNCTION__ " - No client attached to HTTP socket"); + + GetClient()->ProcessHttpDownResponseBody(pucData, size); + + return true; +} + +bool CHttpClientDownSocket::ProcessHttpRequest() +{ + throw CString(_T("Unexpected HTTP request received")); +} diff --git a/HttpClientReqSocket.h b/HttpClientReqSocket.h new file mode 100644 index 00000000..a0ec11be --- /dev/null +++ b/HttpClientReqSocket.h @@ -0,0 +1,83 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +class Packet; + +typedef enum EHttpSocketState +{ + HttpStateUnknown = 0, + HttpStateRecvExpected, + HttpStateRecvHeaders, + HttpStateRecvBody +}; + +/////////////////////////////////////////////////////////////////////////////// +// CHttpClientReqSocket + +class CHttpClientReqSocket : public CClientReqSocket +{ + DECLARE_DYNCREATE(CHttpClientReqSocket) + +public: + virtual CUpDownClient* GetClient() const { return client; } + + virtual void SendPacket(Packet* packet, bool delpacket = true, bool controlpacket = true, uint32 actualPayloadSize = 0, bool bForceImmediateSend = false); + virtual bool IsRawDataMode() const { return true; } + + EHttpSocketState GetHttpState() const { return m_eHttpState; } + void SetHttpState(EHttpSocketState eState); + void ClearHttpHeaders(); + +protected: + CHttpClientReqSocket(CUpDownClient* client = NULL); + virtual ~CHttpClientReqSocket(); + + virtual void DataReceived(const BYTE* pucData, UINT uSize); + virtual void OnConnect(int nErrorCode); + + EHttpSocketState m_eHttpState; + CStringA m_strHttpCurHdrLine; + CStringAArray m_astrHttpHeaders; + int m_iHttpHeadersSize; + + bool ProcessHttpPacket(const BYTE* packet, UINT size); + void ProcessHttpHeaderPacket(const char* packet, UINT size, LPBYTE& pBody, int& iSizeBody); + + virtual bool ProcessHttpResponse(); + virtual bool ProcessHttpResponseBody(const BYTE* pucData, UINT size); + virtual bool ProcessHttpRequest(); +}; + + +/////////////////////////////////////////////////////////////////////////////// +// CHttpClientDownSocket + +class CHttpClientDownSocket : public CHttpClientReqSocket +{ + DECLARE_DYNCREATE(CHttpClientDownSocket) + +public: + CHttpClientDownSocket(CUpDownClient* client = NULL); + +protected: + virtual ~CHttpClientDownSocket(); + + virtual bool ProcessHttpResponse(); + virtual bool ProcessHttpResponseBody(const BYTE* pucData, UINT size); + virtual bool ProcessHttpRequest(); +}; diff --git a/HttpDownloadDlg.cpp b/HttpDownloadDlg.cpp new file mode 100644 index 00000000..3ec4a145 --- /dev/null +++ b/HttpDownloadDlg.cpp @@ -0,0 +1,844 @@ +/* +Module : HTTPDOWNLOADDLG.CPP +Purpose: Defines the implementation for an MFC dialog which performs HTTP downloads + similiar to the Internet Explorer download dialog +Created: PJN / 14-11-1999 +History: PJN / 25-01-2000 1. Fixed a problem where server authentication was not being detected correctly, + while proxy authentication was being handled. + 2. Updated the way and periodicity certain UI controls are updated during the + HTTP download + +Copyright (c) 1999 - 2000 by PJ Naughter. +All rights reserved. + + +*/ + + +///////////////////////////////// Includes ////////////////////////////////// +#include "stdafx.h" +#include "emule.h" +#include "HttpDownloadDlg.h" +#include "OtherFunctions.h" +#include "Log.h" + +///////////////////////////////// Defines ///////////////////////////////////// +#define HAS_ZLIB + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +void InitWindowStyles(CWnd* pWnd); + +const UINT WM_HTTPDOWNLOAD_THREAD_FINISHED = WM_APP + 1; + + +////////////////////////////////////// gzip /////////////////////////////////// +//in the spirit of zlib, lets do something horrible with defines ;) +#ifdef HAS_ZLIB + +#include + +static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ + +/* gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xE0 /* bits 5..7: reserved */ + +static int get_byte(HINTERNET m_hHttpFile) { + unsigned char c; + DWORD dwBytesRead; + BOOL b = ::InternetReadFile(m_hHttpFile, &c, 1, &dwBytesRead); + if(!b) + return EOF; + else + return c; +} + +static int check_header(z_stream *stream, HINTERNET m_hHttpFile) { + int method; /* method byte */ + int flags; /* flags byte */ + uInt len; + int c; + + /* Check the gzip magic header */ + for(len = 0; len < 2; len++) { + c = get_byte(m_hHttpFile); + if(c != gz_magic[len]) { + if(len != 0) stream->avail_in++, stream->next_in--; + if(c != EOF) { + stream->avail_in++, stream->next_in--; + //do not support transparent streams + return stream->avail_in != 0 ? Z_DATA_ERROR : Z_STREAM_END; + } + return stream->avail_in != 0 ? Z_OK : Z_STREAM_END; + } + } + method = get_byte(m_hHttpFile); + flags = get_byte(m_hHttpFile); + if(method != Z_DEFLATED || (flags & RESERVED) != 0) + return Z_DATA_ERROR; + + /* Discard time, xflags and OS code: */ + for(len = 0; len < 6; len++) (void)get_byte(m_hHttpFile); + + if((flags & EXTRA_FIELD) != 0) { /* skip the extra field */ + len = (uInt)get_byte(m_hHttpFile); + len += ((uInt)get_byte(m_hHttpFile))<<8; + /* len is garbage if EOF but the loop below will quit anyway */ + while(len-- != 0 && get_byte(m_hHttpFile) != EOF) ; + } + if((flags & ORIG_NAME) != 0) { /* skip the original file name */ + while((c = get_byte(m_hHttpFile)) != 0 && c != EOF) ; + } + if((flags & COMMENT) != 0) { /* skip the .gz file comment */ + while((c = get_byte(m_hHttpFile)) != 0 && c != EOF) ; + } + if((flags & HEAD_CRC) != 0) { /* skip the header crc */ + for(len = 0; len < 2; len++) (void)get_byte(m_hHttpFile); + } + //return Z_DATA_ERROR if we hit EOF? + return Z_OK; +} + +#define ACCEPT_ENCODING_HEADER _T("Accept-Encoding: gzip, x-gzip, identity, *;q=0\r\n") + +#define ENCODING_CLEAN_UP if(bEncodedWithGZIP) inflateEnd(&zs) + +#define ENCODING_INIT BOOL bEncodedWithGZIP = FALSE; \ + z_stream zs; \ + unsigned char cBufferGZIP[1024 * 8] + +#define ENCODING_QUERY { \ + /*check for gzip or x-gzip stream*/ \ + TCHAR szContentEncoding[32]; \ + DWORD dwEncodeStringSize = _countof(szContentEncoding); \ + if(::HttpQueryInfo(m_hHttpFile, HTTP_QUERY_CONTENT_ENCODING, \ + szContentEncoding, &dwEncodeStringSize, NULL)) { \ + if(szContentEncoding[0] == 'x' && szContentEncoding[1] == '-') \ + szContentEncoding += 2; \ + if(!stricmp(szContentEncoding, "gzip") \ + bEncodedWithGZIP = TRUE; \ + } \ + } + +#define PREPARE_DECODER \ + if(bEncodedWithGZIP) { \ + zs.next_out = cBufferGZIP; \ + zs.zalloc = (alloc_func)0; \ + zs.zfree = (free_func)0; \ + zs.opaque = (voidpf)0; \ + zs.next_in = (unsigned char*)szReadBuf; \ + zs.next_out = Z_NULL; \ + zs.avail_in = 0; \ + zs.avail_out = sizeof(szReadBuf); \ + \ + VERIFY(inflateInit2(&zs, -MAX_WBITS) == Z_OK); \ + int result = check_header(&zs, m_hHttpFile); \ + if(result != Z_OK) { \ + TRACE(_T("An exception occured while decoding the download file\n")); \ + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_ERROR_READFILE));\ + inflateEnd(&zs); \ + } \ + } + +#define DECODE_DATA(CFILE, DATA, LEN) \ + if(bEncodedWithGZIP) { \ + zs.next_in = (unsigned char*)DATA; \ + zs.avail_in = LEN; \ + int iResult; \ + do { \ + zs.total_out = 0; \ + zs.next_out = cBufferGZIP; \ + zs.avail_out = 1024; \ + iResult = inflate(&zs, Z_SYNC_FLUSH); \ + CFILE.Write(cBufferGZIP, zs.total_out); \ + if(iResult == Z_STREAM_ERROR || iResult == Z_DATA_ERROR) { \ + TRACE(_T("An exception occured while decoding the download file\n"));\ + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_ERROR_READFILE));\ + ENCODING_CLEAN_UP; \ + return; \ + } \ + /*if(iResult == Z_STREAM_END) {*/ \ + /*}*/ \ + } while(iResult == Z_OK && zs.avail_out == 0); \ + } else \ + CFILE.Write(DATA, LEN) + +#else + +#define ACCEPT_ENCODING_HEADER _T("Accept-Encoding: identity, *;q=0\r\n") + +#define ENCODING_CLEAN_UP ((void)0) + +#define ENCODING_INIT ((void)0) + +#define ENCODING_QUERY ((void)0) + +#define PREPARE_DECODER ((void)0) + +#define DECODE_DATA(CFILE, DATA, LEN) CFILE.Write(DATA, LEN) + +#endif + + +///////////////////////////////// Implementation ////////////////////////////// +IMPLEMENT_DYNAMIC(CHttpDownloadDlg, CDialog); + +BEGIN_MESSAGE_MAP(CHttpDownloadDlg, CDialog) + ON_WM_DESTROY() + ON_WM_CLOSE() + ON_MESSAGE(WM_HTTPDOWNLOAD_THREAD_FINISHED, OnThreadFinished) +END_MESSAGE_MAP() + +ULONGLONG CHttpDownloadDlg::sm_ullWinInetVer; + +CHttpDownloadDlg::CHttpDownloadDlg(CWnd* pParent /*=NULL*/) + : CDialog(CHttpDownloadDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CHttpDownloadDlg) + //}}AFX_DATA_INIT + m_hInternetSession = NULL; + m_hHttpConnection = NULL; + m_hHttpFile = NULL; + m_bAbort = FALSE; + m_bSafeToClose = FALSE; + m_pThread = NULL; + if (sm_ullWinInetVer == 0) + sm_ullWinInetVer = GetModuleVersion(GetModuleHandle(_T("wininet"))); +} + +void CHttpDownloadDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CHttpDownloadDlg) + DDX_Control(pDX, IDC_STATUS, m_ctrlStatus); + DDX_Control(pDX, IDC_TRANSFER_RATE, m_ctrlTransferRate); + DDX_Control(pDX, IDC_TIMELEFT, m_ctrlTimeLeft); + DDX_Control(pDX, IDC_PROGRESS1, m_ctrlProgress); + DDX_Control(pDX, IDC_FILESTATUS, m_ctrlFileStatus); + DDX_Control(pDX, IDC_ANIMATE1, m_ctrlAnimate); + //}}AFX_DATA_MAP +} + +LRESULT CHttpDownloadDlg::OnThreadFinished(WPARAM wParam, LPARAM /*lParam*/) +{ + //It's now safe to close since the thread has signaled us + m_bSafeToClose = TRUE; + + //Stop the animation + m_ctrlAnimate.Stop(); + Sleep(1000); + //If an error occured display the message box + if (m_bAbort) + EndDialog(IDCANCEL); + else if (wParam) + { + if (!m_sError.IsEmpty()) + LogError(LOG_STATUSBAR, _T("%s"), m_sError); + EndDialog(IDCANCEL); + } + else + EndDialog(IDOK); + + return 0L; +} + +BOOL CHttpDownloadDlg::OnInitDialog() +{ + CString cap; + cap = GetResString(IDS_CANCEL); + GetDlgItem(IDCANCEL)->SetWindowText(cap); + + if (!m_strTitle.IsEmpty()) + SetWindowText(m_strTitle); + + //Let the parent class do its thing + CDialog::OnInitDialog(); + InitWindowStyles(this); + + //Setup the animation control + m_ctrlAnimate.Open(IDR_HTTPDOWNLOAD_ANI); + + //Validate the URL + ASSERT(m_sURLToDownload.GetLength()); //Did you forget to specify the file to download + if (!AfxParseURL(m_sURLToDownload, m_dwServiceType, m_sServer, m_sObject, m_nPort)) + { + //Try sticking "http://" before it + m_sURLToDownload = _T("http://") + m_sURLToDownload; + if (!AfxParseURL(m_sURLToDownload, m_dwServiceType, m_sServer, m_sObject, m_nPort)) + { + TRACE(_T("Failed to parse the URL: %s\n"), m_sURLToDownload); + EndDialog(IDCANCEL); + return TRUE; + } + } + + //Check to see if the file we are downloading to exists and if + //it does, then ask the user if they were it overwritten + // edited: we always want to overwrite old language dlls and server.mets + /*CFileStatus fs; + ASSERT(m_sFileToDownloadInto.GetLength()); + if (CFile::GetStatus(m_sFileToDownloadInto, fs)) + { + CString sMsg; + sMsg.Format(GetResString(IDS_HTTPDOWNLOAD_OK_TO_OVERWRITE), m_sFileToDownloadInto); + if (AfxMessageBox(sMsg, MB_YESNO) != IDYES) + { + TRACE(_T("Failed to confirm file overwrite, download aborted\n")); + EndDialog(IDCANCEL); + return TRUE; + } + }*/ + + //Try and open the file we will download into + if (!m_FileToWrite.Open(m_sFileToDownloadInto, CFile::modeCreate | CFile::modeWrite | CFile::shareDenyWrite)) + { + DWORD dwError = GetLastError(); + CString sMsg; + sMsg.Format(GetResString(IDS_HTTPDOWNLOAD_FAIL_FILE_OPEN), GetErrorMessage(dwError)); + AfxMessageBox(sMsg); + EndDialog(IDCANCEL); + return TRUE; + } + + //Pull out just the filename component + int nSlash = m_sObject.ReverseFind(_T('/')); + if (nSlash == -1) + nSlash = m_sObject.ReverseFind(_T('\\')); + if (nSlash != -1 && m_sObject.GetLength() > 1) + m_sFilename = m_sObject.Right(m_sObject.GetLength() - nSlash - 1); + else + m_sFilename = m_sObject; + + //Set the file status text + CString sFileStatus; + ASSERT(m_sObject.GetLength()); + ASSERT(m_sServer.GetLength()); + sFileStatus.Format(GetResString(IDS_HTTPDOWNLOAD_FILESTATUS), m_sFilename, m_sServer); + m_ctrlFileStatus.SetWindowText(sFileStatus); + + // set labels + SetDlgItemText(IDC_TIMELEFTTEXT,GetResString(IDS_ESTTIMELEFT)); + SetDlgItemText(IDC_TRANSFER_RATE_LABEL,GetResString(IDS_TRANSFER_RATE_LABEL)); + + //Spin off the background thread which will do the actual downloading + m_pThread = AfxBeginThread(_DownloadThread, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); + if (m_pThread == NULL) + { + TRACE(_T("Failed to create download thread, dialog is aborting\n")); + EndDialog(IDCANCEL); + return TRUE; + } + m_pThread->m_bAutoDelete = FALSE; + m_pThread->ResumeThread(); + + return TRUE; +} + +UINT AFX_CDECL CHttpDownloadDlg::_DownloadThread(LPVOID pParam) +{ + DbgSetThreadName("HttpDownload"); + InitThreadLocale(); + //Convert from the SDK world to the C++ world + CHttpDownloadDlg* pDlg = (CHttpDownloadDlg*) pParam; + ASSERT(pDlg); + ASSERT(pDlg->IsKindOf(RUNTIME_CLASS(CHttpDownloadDlg))); + pDlg->DownloadThread(); + return 0; +} + +void CHttpDownloadDlg::SetPercentage(int nPercentage) +{ + //Change the caption text + CString sPercentage; + sPercentage.Format(_T("%d"), nPercentage); + CString sCaption; + sCaption.Format(GetResString(IDS_HTTPDOWNLOAD_PERCENTAGE), sPercentage, m_sFilename); + SetWindowText(sCaption); +} + +void CHttpDownloadDlg::SetProgressRange(DWORD dwFileSize) +{ + m_ctrlProgress.SetRange(0, (short)((dwFileSize+512)/1024)); +} + +void CHttpDownloadDlg::SetProgress(DWORD dwBytesRead) +{ + m_ctrlProgress.SetPos(dwBytesRead/1024); +} + +void CHttpDownloadDlg::SetTimeLeft(DWORD dwSecondsLeft, DWORD dwBytesRead, DWORD dwFileSize) +{ + CString sOf; + sOf.Format(GetResString(IDS_HTTPDOWNLOAD_OF), CastItoXBytes((uint64)dwBytesRead, false, false), CastItoXBytes((uint64)dwFileSize, false, false)); + + CString sTimeLeft; + sTimeLeft.Format(GetResString(IDS_HTTPDOWNLOAD_TIMELEFT), CastSecondsToHM(dwSecondsLeft), sOf); + m_ctrlTimeLeft.SetWindowText(sTimeLeft); +} + +void CHttpDownloadDlg::SetStatus(const CString& sCaption) +{ + m_ctrlStatus.SetWindowText(sCaption); +} + +void CHttpDownloadDlg::SetStatus(CString strFmt, LPCTSTR lpsz1) +{ + CString sStatus; + sStatus.Format(strFmt, lpsz1); + SetStatus(sStatus); +} + +void CHttpDownloadDlg::SetTransferRate(double KbPerSecond) +{ + CString sRate; + sRate.Format(_T("%s"), CastItoXBytes(KbPerSecond, true, true)); + m_ctrlTransferRate.SetWindowText(sRate); +} + +void CHttpDownloadDlg::PlayAnimation() +{ + m_ctrlAnimate.Play(0, (UINT)-1, (UINT)-1); +} + +void CHttpDownloadDlg::HandleThreadErrorWithLastError(CString strIDError, DWORD dwLastError) +{ + if (dwLastError == 0) + dwLastError = GetLastError(); + CString strLastError; + if (dwLastError >= INTERNET_ERROR_BASE && dwLastError <= INTERNET_ERROR_LAST) + GetModuleErrorString(dwLastError, strLastError, _T("wininet")); + else + GetSystemErrorString(dwLastError, strLastError); + m_sError.Format(strIDError, _T(" ") + strLastError); + + //Delete the file being downloaded to if it is present + try { + m_FileToWrite.Close(); + } + catch (CFileException *ex) { + ex->Delete(); + } + ::DeleteFile(m_sFileToDownloadInto); + + PostMessage(WM_HTTPDOWNLOAD_THREAD_FINISHED, 1); +} + +void CHttpDownloadDlg::HandleThreadError(CString strIDError) +{ + m_sError = strIDError; + PostMessage(WM_HTTPDOWNLOAD_THREAD_FINISHED, 1); +} + +void CHttpDownloadDlg::DownloadThread() +{ + ENCODING_INIT; + //Create the Internet session handle + ASSERT(m_hInternetSession == NULL); + m_hInternetSession = ::InternetOpen(AfxGetAppName(), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); + if (m_hInternetSession == NULL) + { + TRACE(_T("Failed in call to InternetOpen, Error:%d\n"), ::GetLastError()); + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_GENERIC_ERROR)); + return; + } + + //Should we exit the thread + if (m_bAbort) + { + PostMessage(WM_HTTPDOWNLOAD_THREAD_FINISHED); + return; + } + + //Setup the status callback function + if (::InternetSetStatusCallback(m_hInternetSession, _OnStatusCallBack) == INTERNET_INVALID_STATUS_CALLBACK) + { + TRACE(_T("Failed in call to InternetSetStatusCallback, Error:%d\n"), ::GetLastError()); + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_GENERIC_ERROR)); + return; + } + + //Should we exit the thread + if (m_bAbort) + { + PostMessage(WM_HTTPDOWNLOAD_THREAD_FINISHED); + return; + } + + //Make the connection to the HTTP server + ASSERT(m_hHttpConnection == NULL); + if (m_sUserName.GetLength()) + // Elandal: Assumes sizeof(void*) == sizeof(unsigned long) + m_hHttpConnection = ::InternetConnect(m_hInternetSession, m_sServer, m_nPort, m_sUserName, + m_sPassword, m_dwServiceType, 0, (DWORD) this); + else + // Elandal: Assumes sizeof(void*) == sizeof(unsigned long) + m_hHttpConnection = ::InternetConnect(m_hInternetSession, m_sServer, m_nPort, NULL, + NULL, m_dwServiceType, 0, (DWORD) this); + if (m_hHttpConnection == NULL) + { + TRACE(_T("Failed in call to InternetConnect, Error:%d\n"), ::GetLastError()); + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_FAIL_CONNECT_SERVER)); + return; + } + + //Should we exit the thread + if (m_bAbort) + { + PostMessage(WM_HTTPDOWNLOAD_THREAD_FINISHED); + return; + } + + //Start the animation to signify that the download is taking place + PlayAnimation(); + + //Issue the request to read the file + LPCTSTR ppszAcceptTypes[2]; + ppszAcceptTypes[0] = _T("*/*"); //We support accepting any mime file type since this is a simple download of a file + ppszAcceptTypes[1] = NULL; + ASSERT(m_hHttpFile == NULL); + // Elandal: Assumes sizeof(void*) == sizeof(unsigned long) + m_hHttpFile = HttpOpenRequest(m_hHttpConnection, NULL, m_sObject, NULL, NULL, ppszAcceptTypes, INTERNET_FLAG_RELOAD | + INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_KEEP_CONNECTION, (DWORD)this); + if (m_hHttpFile == NULL) + { + TRACE(_T("Failed in call to HttpOpenRequest, Error:%d\n"), ::GetLastError()); + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_FAIL_CONNECT_SERVER)); + return; + } + + //Should we exit the thread + if (m_bAbort) + { + PostMessage(WM_HTTPDOWNLOAD_THREAD_FINISHED); + return; + } + + //fill in what encoding we support + HttpAddRequestHeaders(m_hHttpFile, ACCEPT_ENCODING_HEADER, (DWORD)-1L, HTTP_ADDREQ_FLAG_ADD); + + // some sites give unacceptable low download speed if they don't see a well known user agent in the headers... + HttpAddRequestHeaders(m_hHttpFile, _T("User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1)\r\n"), (DWORD)-1L, HTTP_ADDREQ_FLAG_ADD); + +//label used to jump to if we need to resend the request +resend: + + //Issue the request + BOOL bSend = ::HttpSendRequest(m_hHttpFile, NULL, 0, NULL, 0); + if (!bSend) + { + TRACE(_T("Failed in call to HttpSendRequest, Error:%d\n"), ::GetLastError()); + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_FAIL_CONNECT_SERVER)); + return; + } + + //Check the HTTP status code + TCHAR szStatusCode[32]; + DWORD dwInfoSize = _countof(szStatusCode); + if (!HttpQueryInfo(m_hHttpFile, HTTP_QUERY_STATUS_CODE, szStatusCode, &dwInfoSize, NULL)) + { + TRACE(_T("Failed in call to HttpQueryInfo for HTTP query status code, Error:%d\n"), ::GetLastError()); + HandleThreadError(GetResString(IDS_HTTPDOWNLOAD_INVALID_SERVER_RESPONSE)); + return; + } + else + { + long nStatusCode = _ttol(szStatusCode); + + //Handle any authentication errors + if (nStatusCode == HTTP_STATUS_PROXY_AUTH_REQ || nStatusCode == HTTP_STATUS_DENIED) + { + // We have to read all outstanding data on the Internet handle + // before we can resubmit request. Just discard the data. + char szData[51]; + DWORD dwSize; + do + { + ::InternetReadFile(m_hHttpFile, (LPVOID)szData, 50, &dwSize); + } + while (dwSize != 0); + + //Bring up the standard authentication dialog + if (::InternetErrorDlg(GetSafeHwnd(), m_hHttpFile, ERROR_INTERNET_INCORRECT_PASSWORD, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | + FLAGS_ERROR_UI_FLAGS_GENERATE_DATA | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS, NULL) == ERROR_INTERNET_FORCE_RETRY) + goto resend; + } + else if (nStatusCode != HTTP_STATUS_OK) + { + TRACE(_T("Failed to retrieve a HTTP 200 status, Status Code:%d\n"), nStatusCode); + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_INVALID_HTTP_RESPONSE), nStatusCode); + return; + } + } + + //Check to see if any encodings are supported + // ENCODING_QUERY; + TCHAR szContentEncoding[32]; + DWORD dwEncodeStringSize = _countof(szContentEncoding); + if(::HttpQueryInfo(m_hHttpFile, HTTP_QUERY_CONTENT_ENCODING, szContentEncoding, &dwEncodeStringSize, NULL)) + { + if(!_tcsicmp(szContentEncoding, _T("gzip")) || !_tcsicmp(szContentEncoding, _T("x-gzip"))) + bEncodedWithGZIP = TRUE; + } + + //Update the status control to reflect that we are getting the file information + SetStatus(GetResString(IDS_HTTPDOWNLOAD_GETTING_FILE_INFORMATION)); + + // Get the length of the file. + TCHAR szContentLength[32]; + dwInfoSize = _countof(szContentLength); + DWORD dwFileSize = 0; + BOOL bGotFileSize = FALSE; + if (::HttpQueryInfo(m_hHttpFile, HTTP_QUERY_CONTENT_LENGTH, szContentLength, &dwInfoSize, NULL)) + { + //Set the progress control range + bGotFileSize = TRUE; + dwFileSize = (DWORD) _ttol(szContentLength); + SetProgressRange(dwFileSize); + } + + //Update the status to say that we are now downloading the file + SetStatus(GetResString(IDS_HTTPDOWNLOAD_RETREIVEING_FILE)); + + //Now do the actual read of the file + DWORD dwStartTicks = ::GetTickCount(); + DWORD dwCurrentTicks = dwStartTicks; + DWORD dwBytesRead = 0; + char szReadBuf[1024]; + DWORD dwBytesToRead = 1024; + DWORD dwTotalBytesRead = 0; + DWORD dwLastTotalBytes = 0; + DWORD dwLastPercentage = 0; + + PREPARE_DECODER; + do + { + if (!::InternetReadFile(m_hHttpFile, szReadBuf, dwBytesToRead, &dwBytesRead)) + { + TRACE(_T("Failed in call to InternetReadFile, Error:%d\n"), ::GetLastError()); + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_ERROR_READFILE)); + ENCODING_CLEAN_UP; + return; + } + else if (dwBytesRead && !m_bAbort) + { + //Write the data to file + try + { + DECODE_DATA(m_FileToWrite, szReadBuf, dwBytesRead); + } + catch(CFileException *e) + { + TRACE(_T("An exception occured while writing to the download file\n")); + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_ERROR_READFILE), e->m_lOsError); + e->Delete(); + //clean up any encoding data before we return + ENCODING_CLEAN_UP; + return; + } + + //Increment the total number of bytes read + dwTotalBytesRead += dwBytesRead; + + UpdateControlsDuringTransfer(dwStartTicks, dwCurrentTicks, dwTotalBytesRead, dwLastTotalBytes, + dwLastPercentage, bGotFileSize, dwFileSize); + } + } + while (dwBytesRead && !m_bAbort); + + //clean up any encoding data before we return + ENCODING_CLEAN_UP; + + //Delete the file being downloaded to if it is present and the download was aborted + try { + m_FileToWrite.Close(); + } + catch (CFileException *ex) { + HandleThreadErrorWithLastError(GetResString(IDS_HTTPDOWNLOAD_ERROR_READFILE), ex->m_lOsError); + ex->Delete(); + return; + } + if (m_bAbort) + ::DeleteFile(m_sFileToDownloadInto); + + //We're finished + PostMessage(WM_HTTPDOWNLOAD_THREAD_FINISHED); +} + +void CHttpDownloadDlg::UpdateControlsDuringTransfer(DWORD dwStartTicks, DWORD& dwCurrentTicks, DWORD dwTotalBytesRead, DWORD& dwLastTotalBytes, + DWORD& dwLastPercentage, BOOL bGotFileSize, DWORD dwFileSize) +{ + if (bGotFileSize) + { + //Update the percentage downloaded in the caption + DWORD dwPercentage = (DWORD) (dwTotalBytesRead * 100.0 / dwFileSize); + if (dwPercentage != dwLastPercentage) + { + SetPercentage(dwPercentage); + dwLastPercentage = dwPercentage; + + //Update the progress control bar + SetProgress(dwTotalBytesRead); + } + } + + //Update the transfer rate amd estimated time left every second + DWORD dwNowTicks = GetTickCount(); + DWORD dwTimeTaken = dwNowTicks - dwCurrentTicks; + if (dwTimeTaken > 1000) + { + double KbPerSecond = ((double)(dwTotalBytesRead) - (double)(dwLastTotalBytes)) / ((double)(dwTimeTaken)); + SetTransferRate(KbPerSecond); + + //Setup for the next time around the loop + dwCurrentTicks = dwNowTicks; + dwLastTotalBytes = dwTotalBytesRead; + + if (bGotFileSize) + { + //Update the estimated time left + if (dwTotalBytesRead) + { + DWORD dwSecondsLeft = (DWORD) (((double)dwNowTicks - dwStartTicks) / dwTotalBytesRead * + (dwFileSize - dwTotalBytesRead) / 1000); + SetTimeLeft(dwSecondsLeft, dwTotalBytesRead, dwFileSize); + } + } + } +} + +void CALLBACK CHttpDownloadDlg::_OnStatusCallBack(HINTERNET hInternet, DWORD dwContext, DWORD dwInternetStatus, + LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) +{ + CHttpDownloadDlg* pDlg = (CHttpDownloadDlg*) dwContext; + ASSERT(pDlg); + ASSERT(pDlg->IsKindOf(RUNTIME_CLASS(CHttpDownloadDlg))); + pDlg->OnStatusCallBack(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); +} + +CString CHttpDownloadDlg::GetStatusInfo(LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) +{ + CString strStatus; + // Try to figure out if it is ANSI or Unicode. IE is playing a strange game with that data... + // In some cases the strings are even encoded as Unicode *with* a trailing NUL-byte, which + // means that the nr. of bytes in the Unicode string is odd! Thus, the Windows API function + // 'IsTextUnicode' must not be invoked with 'IS_TEXT_UNICODE_ODD_LENGTH', otherwise it will + // again give false results. + // + // INTERNET_STATUS_RESOLVING_NAME Unicode: server name + // INTERNET_STATUS_NAME_RESOLVED ANSI: IP address + // INTERNET_STATUS_CONNECTING_TO_SERVER ANSI: IP address + // INTERNET_STATUS_CONNECTED_TO_SERVER ANSI: IP address + // + INT uFlags = IS_TEXT_UNICODE_UNICODE_MASK; + if (IsTextUnicode(lpvStatusInformation, dwStatusInformationLength, &uFlags)) + strStatus = CString((LPCWSTR)lpvStatusInformation); + else + strStatus = CString((LPCSTR)lpvStatusInformation); + return strStatus; +} + +void CHttpDownloadDlg::OnStatusCallBack(HINTERNET /*hInternet*/, DWORD dwInternetStatus, + LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) +{ + switch (dwInternetStatus) + { + case INTERNET_STATUS_RESOLVING_NAME: + SetStatus(GetResString(IDS_HTTPDOWNLOAD_RESOLVING_NAME), GetStatusInfo(lpvStatusInformation, dwStatusInformationLength)); + break; + case INTERNET_STATUS_NAME_RESOLVED: + SetStatus(GetResString(IDS_HTTPDOWNLOAD_RESOLVED_NAME), GetStatusInfo(lpvStatusInformation, dwStatusInformationLength)); + break; + case INTERNET_STATUS_CONNECTING_TO_SERVER: + SetStatus(GetResString(IDS_HTTPDOWNLOAD_CONNECTING), GetStatusInfo(lpvStatusInformation, dwStatusInformationLength)); + break; + case INTERNET_STATUS_CONNECTED_TO_SERVER: + SetStatus(GetResString(IDS_HTTPDOWNLOAD_CONNECTED), GetStatusInfo(lpvStatusInformation, dwStatusInformationLength)); + break; + case INTERNET_STATUS_REDIRECT: + SetStatus(GetResString(IDS_HTTPDOWNLOAD_REDIRECTING), GetStatusInfo(lpvStatusInformation, dwStatusInformationLength)); + break; + } +} + +void CHttpDownloadDlg::OnDestroy() +{ + //Wait for the worker thread to exit + if (m_pThread) + { + WaitForSingleObject(m_pThread->m_hThread, INFINITE); + delete m_pThread; + m_pThread = NULL; + } + + //Free up the internet handles we may be using + if (m_hHttpFile) + { + ::InternetCloseHandle(m_hHttpFile); + m_hHttpFile = NULL; + } + if (m_hHttpConnection) + { + ::InternetCloseHandle(m_hHttpConnection); + m_hHttpConnection = NULL; + } + if (m_hInternetSession) + { + ::InternetCloseHandle(m_hInternetSession); + m_hInternetSession = NULL; + } + + //Let the parent class do its thing + CDialog::OnDestroy(); +} + +void CHttpDownloadDlg::OnCancel() +{ + // Asynchronously free up the internet handles we may be using. + // Otherwise we may get some kind of deadlock situation, because 'InternetConnect' + // may not return for a very long time... + if (m_hHttpFile) + { + ::InternetCloseHandle(m_hHttpFile); + m_hHttpFile = NULL; + } + if (m_hHttpConnection) + { + ::InternetCloseHandle(m_hHttpConnection); + m_hHttpConnection = NULL; + } + if (m_hInternetSession) + { + ::InternetCloseHandle(m_hInternetSession); + m_hInternetSession = NULL; + } + + //Just set the abort flag to TRUE and + //disable the cancel button + m_bAbort = TRUE; + GetDlgItem(IDCANCEL)->EnableWindow(FALSE); + SetStatus(GetResString(IDS_HTTPDOWNLOAD_ABORTING_TRANSFER)); +} + +void CHttpDownloadDlg::OnClose() +{ + if (m_bSafeToClose) + CDialog::OnClose(); + else + { + //Just set the abort flag to TRUE and + //disable the cancel button + m_bAbort = TRUE; + GetDlgItem(IDCANCEL)->EnableWindow(FALSE); + SetStatus(GetResString(IDS_HTTPDOWNLOAD_ABORTING_TRANSFER)); + } +} diff --git a/HttpDownloadDlg.h b/HttpDownloadDlg.h new file mode 100644 index 00000000..a2b99b2b --- /dev/null +++ b/HttpDownloadDlg.h @@ -0,0 +1,94 @@ +/* +Module : HTTPDOWNLOADDLG.H +Purpose: Defines the interface for an MFC dialog which performs HTTP downloads + similiar to the Internet Explorer download dialog + +Copyright (c) 1999 - 2000 by PJ Naughter. +All rights reserved. + +*/ + + +////////////////////////////////// Macros /////////////////////////// +#pragma once +#include + + +/////////////////////////// Classes ///////////////////////////////// + +class CHttpDownloadDlg : public CDialog +{ +public: +//Constructors / Destructors + CHttpDownloadDlg(CWnd* pParent = NULL); + +//Public Member variables + CString m_strTitle; + CString m_sURLToDownload; + CString m_sFileToDownloadInto; + CString m_sUserName; + CString m_sPassword; + + const CString& GetError() const { return m_sError; } + +protected: + //{{AFX_DATA(CHttpDownloadDlg) + enum { IDD = IDD_HTTPDOWNLOAD }; + CStatic m_ctrlStatus; + CStatic m_ctrlTransferRate; + CStatic m_ctrlTimeLeft; + CProgressCtrl m_ctrlProgress; + CStatic m_ctrlFileStatus; + CAnimateCtrl m_ctrlAnimate; + //}}AFX_DATA + + //{{AFX_VIRTUAL(CHttpDownloadDlg) + virtual void DoDataExchange(CDataExchange* pDX); + //}}AFX_VIRTUAL + + //{{AFX_MSG(CHttpDownloadDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnDestroy(); + virtual void OnCancel(); + afx_msg void OnClose(); + //}}AFX_MSG + afx_msg LRESULT OnThreadFinished(WPARAM wParam, LPARAM lParam); + + DECLARE_MESSAGE_MAP() + DECLARE_DYNAMIC(CHttpDownloadDlg); + + static void CALLBACK _OnStatusCallBack(HINTERNET hInternet, DWORD dwContext, DWORD dwInternetStatus, + LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); + void OnStatusCallBack(HINTERNET hInternet, DWORD dwInternetStatus, + LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); + CString GetStatusInfo(LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); + static UINT _DownloadThread(LPVOID pParam); + void HandleThreadErrorWithLastError(CString strIDError, DWORD dwLastError = 0); + void HandleThreadError(CString strIDError); + void DownloadThread(); + void SetPercentage(int nPercentage); + void SetTimeLeft(DWORD dwSecondsLeft, DWORD dwBytesRead, DWORD dwFileSize); + void SetProgressRange(DWORD dwFileSize); + void SetStatus(const CString& sCaption); + void SetStatus(CString strFmt, LPCTSTR lpsz1); + void SetTransferRate(double KbPerSecond); + void PlayAnimation(); + void SetProgress(DWORD dwBytesRead); + void UpdateControlsDuringTransfer(DWORD dwStartTicks, DWORD& dwCurrentTicks, DWORD dwTotalBytesRead, DWORD& dwLastTotalBytes, + DWORD& dwLastPercentage, BOOL bGotFileSize, DWORD dwFileSize); + + CString m_sError; + CString m_sServer; + CString m_sObject; + CString m_sFilename; + INTERNET_PORT m_nPort; + DWORD m_dwServiceType; + HINTERNET m_hInternetSession; + HINTERNET m_hHttpConnection; + HINTERNET m_hHttpFile; + BOOL m_bAbort; + BOOL m_bSafeToClose; + CFile m_FileToWrite; + CWinThread* m_pThread; + static ULONGLONG sm_ullWinInetVer; +}; diff --git a/HyperTextCtrl.cpp b/HyperTextCtrl.cpp new file mode 100644 index 00000000..02cdcf48 --- /dev/null +++ b/HyperTextCtrl.cpp @@ -0,0 +1,1766 @@ +/******************************************************************** + HyperTextCtrl.h - Controls that shows hyperlinks + in text + + Copyright (C) 2001-2002 Magomed G. Abdurakhmanov +********************************************************************/ + +//edited by (C)2002 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +//-> converted it to MFC +//-> included colored keywords +//-> fixed GPF bugs +//-> made it flickerfree +//-> some other small changes +// (the whole code still needs some work though) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "emuledlg.h" +#include "hypertextctrl.h" +#include + +#pragma warning(disable:4244) // conversion from to , possible loss of data +#pragma warning(disable:4018) // signed/unsigned mismatch +#pragma warning(disable:4100) // unreferenced formal parameter + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// CHyperLink +CHyperLink::CHyperLink(int iBegin, uint16 iEnd, const CString& sTitle, const CString& sCommand, const CString& sDirectory){ + m_Type = lt_Shell; + m_iBegin = iBegin; + m_iEnd = iEnd; + m_sTitle = sTitle; + m_sCommand = sCommand; + m_sDirectory = sDirectory; + // [i_a] used for lt_Message + m_hWnd = 0; + m_uMsg = 0; + m_wParam = 0; + m_lParam = 0; +} // [/i_a] + +CHyperLink::CHyperLink(int iBegin, uint16 iEnd, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ + m_Type = lt_Message; + m_iBegin = iBegin; + m_iEnd = iEnd; + m_sTitle = sTitle; + m_hWnd = hWnd; + m_uMsg = uMsg; + m_wParam = wParam; + m_lParam = lParam; +} + CHyperLink::CHyperLink(){ // [i_a] + m_Type = lt_Unknown; + m_iBegin = 0; + m_iEnd = 0; + m_sTitle.Empty(); + m_sCommand.Empty(); + m_sDirectory.Empty(); + m_hWnd = 0; + m_uMsg = 0; + m_wParam = 0; + m_lParam = 0; + } // [/i_a] +CHyperLink::CHyperLink(const CHyperLink& Src){ + m_Type = Src.m_Type; + m_iBegin = Src.m_iBegin; + m_iEnd = Src.m_iEnd; + m_sTitle = Src.m_sTitle; + m_sCommand = Src.m_sCommand; + m_sDirectory = Src.m_sDirectory; + m_hWnd = Src.m_hWnd; + m_uMsg = Src.m_uMsg; + m_wParam = Src.m_wParam; + m_lParam = Src.m_lParam; +} + +void CHyperLink::Execute(){ + switch(m_Type) + { + case lt_Shell: + ShellExecute(NULL, NULL, m_sCommand, NULL, m_sDirectory, SW_SHOWDEFAULT); + break; + + case lt_Message: + PostMessage(m_hWnd, m_uMsg, m_wParam, m_lParam); + break; + } +} + +// CKeyWord +CKeyWord::CKeyWord(int iBegin, uint16 iEnd, COLORREF icolor){ + color = icolor; + m_iBegin = iBegin; + m_iEnd = iEnd; +} + +// CPreparedHyperText +void CPreparedHyperText::PrepareText(const CString& sText) +{ + m_sText = sText; + m_Links.clear(); + + enum { + unknown, + space, + http0, /* http:// */ + http1, http2, http3, http4, http5, http6, + ftp0, /* ftp:// */ + ftp1, ftp2, ftp3, ftp4, ftp5, + ftp, /* ftp. */ + www0, /* www. */ + www1, www2, www3, + mailto0, /* mailto: */ + mailto1, mailto2, mailto3, mailto4, mailto5, mailto6, + mail, /* xxx@yyy */ + ed2k0, /* ed2k:// */ + ed2k1, ed2k2, ed2k3, ed2k4, ed2k5, ed2k6 + } state = space; + + int WordPos = 0; + TCHAR sz[2]; + TCHAR& c = sz[0]; + sz[1] = 0; + int last = m_sText.GetLength() -1; + for(int i = 0; i <= last; i++) + { + c = m_sText[i]; + _tcslwr(sz); + + switch(state) + { + case unknown: + if(tspace(c)) + state = space; + else + if(c == _T('@') && WordPos != i) + state = mail; + break; + + case space: + WordPos = i; + switch(c) + { + case _T('h'): state = http0; break; + case _T('f'): state = ftp0; break; + case _T('w'): state = www0; break; + case _T('m'): state = mailto0; break; + case _T('e'): state = ed2k0; break; + default: + if(!tspace(c)) + state = unknown; + } + break; + + /*----------------- http -----------------*/ + case http0: + if(c == _T('t')) + state = http1; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case http1: + if(c == _T('t')) + state = http2; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case http2: + if(c == _T('p')) + state = http3; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case http3: + if(c == _T(':')) + state = http4; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case http4: + if(c == _T('/')) + state = http5; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case http5: + if(c == _T('/')) + state = http6; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case http6: + if(tspace(c) || i == last) + { + int len = i == last ? i - WordPos + 1 : i - WordPos; + CString s = m_sText.Mid(WordPos, len); + RemoveLastSign(s); + m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL)); + state = space; + } + break; + + /*----------------- ed2k -----------------*/ + case ed2k0: + if(c == _T('d')) + state = ed2k1; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ed2k1: + if(c == _T('2')) + state = ed2k2; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ed2k2: + if(c == _T('k')) + state = ed2k3; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ed2k3: + if(c == _T(':')) + state = ed2k4; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ed2k4: + if(c == _T('/')) + state = ed2k5; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ed2k5: + if(c == _T('/')) + state = ed2k6; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ed2k6: + if(tspace(c) || i == last) + { + int len = i == last ? i - WordPos + 1 : i - WordPos; + CString s = m_sText.Mid(WordPos, len); + RemoveLastSign(s); + m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL)); + state = space; + } + break; + /*----------------- ftp -----------------*/ + case ftp0: + if(c == _T('t')) + state = ftp1; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ftp1: + if(c == _T('p')) + state = ftp2; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ftp2: + if(c == _T(':')) + state = ftp3; + else + if(c == _T('.')) + state = ftp; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ftp3: + if(c == _T('/')) + state = ftp4; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ftp4: + if(c == _T('/')) + state = ftp5; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case ftp: + if(tspace(c) || i == last) + { + int len = i == last ? i - WordPos + 1 : i - WordPos; + CString s = CString(_T("ftp://")) + m_sText.Mid(WordPos, len); + RemoveLastSign(s); + m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL)); + state = space; + } + break; + + case ftp5: + if(tspace(c) || i == last) + { + int len = i == last ? i - WordPos + 1 : i - WordPos; + CString s = m_sText.Mid(WordPos, len); + RemoveLastSign(s); + m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL)); + state = space; + } + break; + + /*----------------- www -----------------*/ + case www0: + if(c == _T('w')) + state = www1; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case www1: + if(c == _T('w')) + state = www2; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case www2: + if(c == _T('.')) + state = www3; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case www3: + if(tspace(c) || i == last) + { + int len = i == last ? i - WordPos + 1 : i - WordPos; + CString s = CString(_T("http://")) + m_sText.Mid(WordPos, len); + RemoveLastSign(s); + m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL)); + state = space; + } + break; + + /*----------------- mailto -----------------*/ + case mailto0: + if(c == _T('a')) + state = mailto1; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case mailto1: + if(c == _T('i')) + state = mailto2; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case mailto2: + if(c == _T('l')) + state = mailto3; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case mailto3: + if(c == _T('t')) + state = mailto4; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case mailto4: + if(c == _T('o')) + state = mailto5; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case mailto5: + if(c == _T(':')) + state = mailto6; + else + if(tspace(c)) + state = space; + else + state = unknown; + break; + + case mailto6: + if(tspace(c) || i == last) + { + int len = i == last ? i - WordPos + 1 : i - WordPos; + CString s = m_sText.Mid(WordPos, len); + RemoveLastSign(s); + m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL)); + state = space; + } + break; + + /*----------------- mailto -----------------*/ + case mail: + if(tspace(c) || i == last) + { + int len = i == last ? i - WordPos + 1 : i - WordPos; + CString s = CString(_T("mailto:")) + m_sText.Mid(WordPos, len); + RemoveLastSign(s); + m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL)); + state = space; + } + break; + } + } + + m_Links.sort(); +} + + void CPreparedHyperText::RemoveLastSign(CString& sLink) +{ + int len = sLink.GetLength(); + if(len > 0) + { + TCHAR c = sLink[len-1]; + switch(c) + { + case _T('.'): + case _T(','): + case _T(';'): + case _T('\"'): + case _T('\''): + case _T('('): + case _T(')'): + case _T('['): + case _T(']'): + case _T('{'): + case _T('}'): + sLink.Delete(len -1, 1); + break; + } + } +} + +CPreparedHyperText::CPreparedHyperText(const CString& sText){ + PrepareText(sText); +} + +CPreparedHyperText::CPreparedHyperText(const CPreparedHyperText& src){ + m_sText = src.m_sText; + m_Links.assign(src.m_Links.begin(), src.m_Links.end()); +} + +void CPreparedHyperText::Clear(){ + m_sText.Empty(); + m_Links.erase(m_Links.begin(), m_Links.end()); +} + +void CPreparedHyperText::SetText(const CString& sText){ + Clear(); + PrepareText(sText); +} + +void CPreparedHyperText::AppendText(const CString& sText){ + int len = m_sText.GetLength(); + //////////////////////////////////////////////// + //Top:The Original code didn't check to see if the buffer was full.. + //////////////////////////////////////////////// + bool flag = true; + if( len > 60000 ){ + m_sText = m_sText.Right(50000); + int shift = len - m_sText.GetLength(); + while( flag == true ){ + CHyperLink &test = m_Links.front(); + if( !m_Links.empty() ){ + if( test.Begin() < shift ) + m_Links.pop_front(); + else + flag = false; + } + else + flag = false; + } + flag = true; + while( flag == true ){ + CKeyWord &test = m_KeyWords.front(); + if( !m_KeyWords.empty() ){ + if( test.Begin() < shift ) + m_KeyWords.pop_front(); + else + flag = false; + } + else + flag = false; + } + len = m_sText.GetLength(); + CHyperLink <est = m_Links.front(); + int litest = ltest.Begin() - shift; + CKeyWord &wtest = m_KeyWords.front(); + int witest = wtest.Begin() - shift; + flag = true; + while( flag == true && !m_Links.empty() ){ + CHyperLink &temp = m_Links.front(); + CHyperLink backup( temp); + backup.SetBegin( backup.Begin() - shift ); + backup.SetEnd( backup.End() - shift ); + m_Links.pop_front(); + m_Links.push_back( backup ); + if( ((CHyperLink)m_Links.front()).Begin() == (UINT)litest ) + flag = false; + } + flag = true; + while( flag == true && !m_KeyWords.empty() ){ + CKeyWord &temp = m_KeyWords.front(); + CKeyWord backup( temp.Begin()-shift, temp.End()-shift, temp.Color()); + m_KeyWords.pop_front(); + m_KeyWords.push_back( backup ); + if( ((CKeyWord)m_KeyWords.front()).Begin() == (UINT)witest ) + flag = false; + } + } + //////////////////////////////////////////////// + //Bottom: May not be the nicest code but it works. + //////////////////////////////////////////////// + CPreparedHyperText ht(sText); + m_sText+=sText; + for(std::list::iterator it = ht.m_Links.begin(); it != ht.m_Links.end(); it++) + { + CHyperLink hl = *it; + hl.m_iBegin += len; + hl.m_iEnd += len; + m_Links.push_back(hl); + } +} + +void CPreparedHyperText::AppendHyperLink(const CString& sText, const CString& sTitle, const CString& sCommand, const CString& sDirectory){ + if (!(sText.GetLength() && sCommand.GetLength())) + return; + int len = m_sText.GetLength(); + //////////////////////////////////////////////// + //Top:The Original code didn't check to see if the buffer was full.. + //////////////////////////////////////////////// + bool flag = true; + if( len > 60000 ){ + m_sText = m_sText.Right(50000); + int shift = len - m_sText.GetLength(); + while( flag == true ){ + CHyperLink &test = m_Links.front(); + if( !m_Links.empty() ){ + if( test.Begin() < shift ) + m_Links.pop_front(); + else + flag = false; + } + else + flag = false; + } + flag = true; + while( flag == true ){ + CKeyWord &test = m_KeyWords.front(); + if( !m_KeyWords.empty() ){ + if( test.Begin() < shift ) + m_KeyWords.pop_front(); + else + flag = false; + } + else + flag = false; + } + len = m_sText.GetLength(); + CHyperLink <est = m_Links.front(); + int litest = ltest.Begin() - shift; + CKeyWord &wtest = m_KeyWords.front(); + int witest = wtest.Begin() - shift; + flag = true; + while( flag == true && !m_Links.empty() ){ + CHyperLink &temp = m_Links.front(); + CHyperLink backup( temp); + backup.SetBegin( backup.Begin() - shift ); + backup.SetEnd( backup.End() - shift ); + m_Links.pop_front(); + m_Links.push_back( backup ); + if( ((CHyperLink)m_Links.front()).Begin() == (UINT)litest ) + flag = false; + } + flag = true; + while( flag == true && !m_KeyWords.empty() ){ + CKeyWord &temp = m_KeyWords.front(); + CKeyWord backup( temp.Begin()-shift, temp.End()-shift, temp.Color()); + m_KeyWords.pop_front(); + m_KeyWords.push_back( backup ); + if( ((CKeyWord)m_KeyWords.front()).Begin() == (UINT)witest ) + flag = false; + } + } + //////////////////////////////////////////////// + //Bottom: May not be the nicest code but it works. + //////////////////////////////////////////////// + m_sText+=sText; + m_Links.push_back(CHyperLink(len, len + sText.GetLength() - 1, sTitle, sCommand, sDirectory)); +} + +void CPreparedHyperText::AppendHyperLink(const CString& sText, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ + if (!sText.GetLength()) + return; + int len = m_sText.GetLength(); + //////////////////////////////////////////////// + //Top:The Original code didn't check to see if the buffer was full.. + //////////////////////////////////////////////// + bool flag = true; + if( len > 60000 ){ + m_sText = m_sText.Right(50000); + int shift = len - m_sText.GetLength(); + while( flag == true ){ + CHyperLink &test = m_Links.front(); + if( !m_Links.empty() ){ + if( test.Begin() < shift ) + m_Links.pop_front(); + else + flag = false; + } + else + flag = false; + } + flag = true; + while( flag == true ){ + CKeyWord &test = m_KeyWords.front(); + if( !m_KeyWords.empty() ){ + if( test.Begin() < shift ) + m_KeyWords.pop_front(); + else + flag = false; + } + else + flag = false; + } + len = m_sText.GetLength(); + CHyperLink <est = m_Links.front(); + int litest = ltest.Begin() - shift; + CKeyWord &wtest = m_KeyWords.front(); + int witest = wtest.Begin() - shift; + flag = true; + while( flag == true && !m_Links.empty() ){ + CHyperLink &temp = m_Links.front(); + CHyperLink backup( temp); + backup.SetBegin( backup.Begin() - shift ); + backup.SetEnd( backup.End() - shift ); + m_Links.pop_front(); + m_Links.push_back( backup ); + if( ((CHyperLink)m_Links.front()).Begin() == (UINT)litest ) + flag = false; + } + flag = true; + while( flag == true && !m_KeyWords.empty() ){ + CKeyWord &temp = m_KeyWords.front(); + CKeyWord backup( temp.Begin()-shift, temp.End()-shift, temp.Color()); + m_KeyWords.pop_front(); + m_KeyWords.push_back( backup ); + if( ((CKeyWord)m_KeyWords.front()).Begin() == (UINT)witest ) + flag = false; + } + } + //////////////////////////////////////////////// + //Bottom: May not be the nicest code but it works. + //////////////////////////////////////////////// + m_sText+=sText; + m_Links.push_back(CHyperLink(len, len + sText.GetLength() - 1, sTitle, hWnd, uMsg, wParam, lParam)); +} + +void CPreparedHyperText::AppendKeyWord(const CString& sText, COLORREF iColor){ + if (!sText.GetLength()) + return; + int len = m_sText.GetLength(); + //////////////////////////////////////////////// + //Top:The Original code didn't check to see if the buffer was full.. + //////////////////////////////////////////////// + bool flag = true; + if( len > 60000 ){ + m_sText = m_sText.Right(50000); + int shift = len - m_sText.GetLength(); + while( flag == true ){ + CHyperLink &test = m_Links.front(); + if( !m_Links.empty() ){ + if( test.Begin() < shift ) + m_Links.pop_front(); + else + flag = false; + } + else + flag = false; + } + flag = true; + while( flag == true ){ + CKeyWord &test = m_KeyWords.front(); + if( !m_KeyWords.empty() ){ + if( test.Begin() < shift ) + m_KeyWords.pop_front(); + else + flag = false; + } + else + flag = false; + } + len = m_sText.GetLength(); + CHyperLink <est = m_Links.front(); + int litest = ltest.Begin() - shift; + CKeyWord &wtest = m_KeyWords.front(); + int witest = wtest.Begin() - shift; + flag = true; + while( flag == true && !m_Links.empty() ){ + CHyperLink &temp = m_Links.front(); + CHyperLink backup( temp); + backup.SetBegin( backup.Begin() - shift ); + backup.SetEnd( backup.End() - shift ); + m_Links.pop_front(); + m_Links.push_back( backup ); + if( ((CHyperLink)m_Links.front()).Begin() == (UINT)litest ) + flag = false; + } + flag = true; + while( flag == true && !m_KeyWords.empty() ){ + CKeyWord &temp = m_KeyWords.front(); + CKeyWord backup( temp.Begin()-shift, temp.End()-shift, temp.Color()); + m_KeyWords.pop_front(); + m_KeyWords.push_back( backup ); + if( ((CKeyWord)m_KeyWords.front()).Begin() == (UINT)witest ) + flag = false; + } + } + //////////////////////////////////////////////// + //Bottom: May not be the nicest code but it works. + //////////////////////////////////////////////// + m_sText+=sText; + m_KeyWords.push_back(CKeyWord(len, len + sText.GetLength() - 1, iColor)); +} + +//CLinePartInfo + + CLinePartInfo::CLinePartInfo(int iBegin, uint16 iEnd, CHyperLink* pHyperLink, CKeyWord* pKeyWord){ + m_xBegin = (uint16)iBegin; + m_xEnd = iEnd; + m_pHyperLink = pHyperLink; + m_pKeyWord = pKeyWord; +} + + CLinePartInfo::CLinePartInfo(const CLinePartInfo& Src){ + m_xBegin = Src.m_xBegin; + m_xEnd = Src.m_xEnd; + m_pHyperLink = Src.m_pHyperLink; + m_pKeyWord = Src.m_pKeyWord; +} + + +//CLineInfo + + CLineInfo::CLineInfo(int iBegin, uint16 iEnd){ + m_iBegin = iBegin; + m_iEnd = iEnd; +} + + CLineInfo::CLineInfo(const CLineInfo& Src){ + m_iBegin = Src.m_iBegin; + m_iEnd = Src.m_iEnd; + assign(Src.begin(), Src.end()); +} + + +//CVisPart + CVisPart::CVisPart(const CLinePartInfo& LinePartInfo, const CRect& rcBounds, int iRealBegin, uint16 iRealLen,CVisPart* pPrev,CVisPart* pNext) : CLinePartInfo(LinePartInfo) +{ + m_rcBounds = rcBounds; + m_iRealBegin = iRealBegin; + m_iRealLen = iRealLen; + m_pPrev = pPrev; + m_pNext = pNext; +} + + CVisPart::CVisPart(const CVisPart& Src) : CLinePartInfo(Src){ + m_rcBounds = Src.m_rcBounds; + m_iRealBegin = Src.m_iRealBegin; + m_iRealLen = Src.m_iRealLen; + m_pPrev = Src.m_pPrev; + m_pNext = Src.m_pNext; +} + + + +// -------------------------------------------------------------- +// CHyperTextCtrl +IMPLEMENT_DYNAMIC(CHyperTextCtrl, CWnd) + +BEGIN_MESSAGE_MAP(CHyperTextCtrl, CWnd) + ON_MESSAGE(WM_PAINT,OnPaint) + ON_WM_MOUSEMOVE() + ON_WM_LBUTTONDOWN() + ON_WM_MOUSEWHEEL() + ON_MESSAGE(WM_SIZE, OnSize) + ON_MESSAGE(WM_SHOWWINDOW, OnShowWindow) + ON_MESSAGE(WM_CREATE, OnCreate) + ON_MESSAGE(WM_DESTROY, OnDestroy) + ON_MESSAGE(WM_SETTEXT, OnSetText) + ON_MESSAGE(WM_GETTEXT, OnGetText) + ON_MESSAGE(WM_SETFONT, OnSetFont) + ON_MESSAGE(WM_GETFONT, OnGetFont) + ON_WM_ERASEBKGND() + ON_MESSAGE(WM_HSCROLL, OnHScroll) + ON_MESSAGE(WM_VSCROLL, OnVScroll) + ON_MESSAGE(WM_CAPTURECHANGED, OnCaptureChanged) + ON_WM_SYSCOLORCHANGE() + //REFLECT_NOTIFICATIONS() +END_MESSAGE_MAP() + +CHyperTextCtrl::CHyperTextCtrl() +{ + m_Text = &standart_Text; + vscrollon = false; + m_Font = NULL; + m_BkColor = RGB(0,0,0); + m_TextColor = RGB(0,0,0); + m_LinkColor = RGB(0,0,0); + m_HoverColor = RGB(0,0,0); + m_LinkCursor = NULL; + m_DefaultCursor = NULL; + vscrollon = false; + m_iMaxWidth = 0; + m_iLineHeight = 0; + m_iLinesHeight = 0; + m_bDontUpdateSizeInfo = false; + m_iVertPos = 0; + m_iHorzPos = 0; + m_pActivePart = NULL; + m_iWheelDelta = 0; +} + +//message handlers + +LRESULT CHyperTextCtrl::OnDestroy(WPARAM wParam, LPARAM lParam){ + if (m_LinkCursor){ + SetCursor(m_DefaultCursor); + VERIFY( DestroyCursor(m_LinkCursor) ); + } + m_LinkCursor = NULL; + + return 0; +} + +LRESULT CHyperTextCtrl::OnCreate(WPARAM wParam, LPARAM lParam){ + //LPCREATESTRUCT lpCreateStruct = (LPCREATESTRUCT)lParam; + m_iMaxWidth = 0; + m_iLinesHeight = 0; + m_bDontUpdateSizeInfo = false; + m_iHorzPos = 0; + m_iVertPos = 0; + m_Font = &theApp.m_fontHyperText; + SetColors(); + LoadHandCursor(); + m_DefaultCursor = LoadCursor(NULL,IDC_ARROW); + m_pActivePart = NULL; + m_iWheelDelta = 0; + + // create a tool tip + m_tip.Create(this); + if(m_tip) + m_tip.Activate(TRUE); + + UpdateFonts(); + return 0; +} + +LRESULT CHyperTextCtrl::OnPaint(WPARAM wParam, LPARAM lParam){ + CPaintDC dc(this); // device context for painting + CFont* hOldFont = dc.SelectObject(m_Font); + + dc.SetBkColor(m_BkColor); + + int ypos = 0; + LPCTSTR s = m_Text->GetText(); + + CRect rc; + CRect rcClient; + GetClientRect(rcClient); + rc.left = dc.m_ps.rcPaint.left; + rc.right = 2; + rc.top = dc.m_ps.rcPaint.top; + rc.bottom = dc.m_ps.rcPaint.bottom; + + CBrush brBk; + brBk.CreateSolidBrush(m_BkColor); + dc.FillRect(rc, &brBk); + + for(std::vector::iterator it = m_VisLines.begin(); it != m_VisLines.end(); it++){ + int iLastX = dc.m_ps.rcPaint.left; + + for(CVisLine::iterator jt = it->begin(); jt != it->end(); jt++){ + if (jt->m_pKeyWord) + dc.SetTextColor(jt->m_pKeyWord->Color()); + else if(jt->m_pHyperLink == NULL) + dc.SetTextColor(m_TextColor); + else{ + if(m_pActivePart != NULL && m_pActivePart->m_pHyperLink == jt->m_pHyperLink){ + dc.SetTextColor(m_HoverColor); + dc.SelectObject(m_HoverFont); + } + else{ + dc.SetTextColor(m_LinkColor); + dc.SelectObject(m_LinksFont); + } + } + + TextOut(dc, jt->m_rcBounds.left, jt->m_rcBounds.top, s + jt->m_iRealBegin, jt->m_iRealLen); + + if(jt->m_pHyperLink != NULL) + dc.SelectObject(m_Font); + + iLastX = jt->m_rcBounds.right; + } + + rc.left = iLastX; + rc.right = dc.m_ps.rcPaint.right; + rc.top = ypos; + rc.bottom = ypos + m_iLineHeight; + + dc.FillRect(rc, &brBk); + + ypos+=m_iLineHeight; + } + + rc.left = dc.m_ps.rcPaint.left; + rc.right = dc.m_ps.rcPaint.right; + rc.top = ypos; + rc.bottom = dc.m_ps.rcPaint.bottom; + dc.FillRect(rc, &brBk); + + dc.SelectObject(hOldFont); + return 0; +} + +LRESULT CHyperTextCtrl::OnSize(WPARAM wParam, LPARAM lParam){ + WORD cx, cy; + cx = LOWORD(lParam); + cy = HIWORD(lParam); + + UpdateSize(IsWindowVisible() == TRUE); + return 0; +} + +LRESULT CHyperTextCtrl::OnShowWindow(WPARAM wParam, LPARAM lParam){ + if(TRUE == (BOOL)wParam) + UpdateSize(false); + return 0; +} + +LRESULT CHyperTextCtrl::OnSetText(WPARAM wParam, LPARAM lParam){ + m_Text->SetText((LPTSTR)lParam); + UpdateSize(IsWindowVisible() == TRUE); + return TRUE; +} + +LRESULT CHyperTextCtrl::OnGetText(WPARAM wParam, LPARAM lParam){ + int bufsize = wParam; + LPTSTR buf = (LPTSTR)lParam; + if(lParam == NULL || bufsize == 0 || m_Text->GetText().IsEmpty()) + return 0; + int cpy = m_Text->GetText().GetLength() > (bufsize-1) ? (bufsize-1) : m_Text->GetText().GetLength(); + _tcsncpy(buf, m_Text->GetText(), cpy); + return cpy; +} + +LRESULT CHyperTextCtrl::OnSetFont(WPARAM wParam, LPARAM lParam){ + m_Font = CFont::FromHandle((HFONT)wParam); + UpdateFonts(); + UpdateSize(LOWORD(lParam) != 0); + return 0; +} + +LRESULT CHyperTextCtrl::OnGetFont(WPARAM wParam, LPARAM lParam){ + return (LRESULT)m_Font->m_hObject; +} + +LRESULT CHyperTextCtrl::OnHScroll(WPARAM wParam, LPARAM lParam){ + SCROLLINFO si; + si.cbSize = sizeof(si); + si.fMask = SIF_ALL; + + GetScrollInfo(SB_HORZ, &si); + + switch(LOWORD(wParam)) + { + case SB_LEFT: + si.nPos=si.nMin; + break; + + case SB_RIGHT: + si.nPos=si.nMax; + break; + + case SB_LINELEFT: + if(si.nPos > si.nMin) + si.nPos-=1; + break; + + case SB_LINERIGHT: + if(si.nPos < si.nMax) + si.nPos+=1; + break; + + case SB_PAGELEFT: + if(si.nPos > si.nMin) + si.nPos-=si.nPage; + if(si.nPos < si.nMin) + si.nPos = si.nMin; + break; + + case SB_PAGERIGHT: + if(si.nPos < si.nMax) + si.nPos+=si.nPage; + if(si.nPos > si.nMax) + si.nPos = si.nMax; + break; + + case SB_THUMBTRACK: + si.nPos=si.nTrackPos; + break; + } + + if(si.nMax != si.nMin) + m_iHorzPos = si.nPos * 100 / (si.nMax - si.nMin); + SetScrollInfo(SB_HORZ, &si); + UpdateVisLines(); + InvalidateRect(NULL,FALSE); + return TRUE; +} + +LRESULT CHyperTextCtrl::OnVScroll(WPARAM wParam, LPARAM lParam){ + SCROLLINFO si; + si.cbSize = sizeof(si); + si.fMask = SIF_ALL; + GetScrollInfo(SB_VERT, &si); + + switch(LOWORD(wParam)) + { + case SB_TOP: + si.nPos=si.nMin; + break; + + case SB_BOTTOM: + si.nPos=si.nMax; + break; + + case SB_LINEUP: + if(si.nPos > si.nMin) + si.nPos-=1; + break; + + case SB_LINEDOWN: + if(si.nPos < si.nMax) + si.nPos+=1; + break; + + case SB_PAGEUP: + if(si.nPos > si.nMin) + si.nPos-=si.nPage; + if(si.nPos < si.nMin) + si.nPos = si.nMin; + break; + + case SB_PAGEDOWN: + if(si.nPos < si.nMax) + si.nPos+=si.nPage; + if(si.nPos > si.nMax) + si.nPos = si.nMax; + break; + + case SB_THUMBTRACK: + si.nPos=si.nTrackPos; + break; + } + + if(si.nMax != si.nMin) + m_iVertPos = si.nPos * 100 / (si.nMax - si.nMin); + SetScrollInfo(SB_VERT, &si); + UpdateVisLines(); + InvalidateRect(NULL,FALSE); + return TRUE; +} + +void CHyperTextCtrl::OnMouseMove(UINT nFlags,CPoint pt){ + CRect rcClient; + GetClientRect(rcClient); + if(PtInRect(rcClient, pt) && m_iLineHeight) + { + bool bFound = false; + UINT i = pt.y / m_iLineHeight; + if(i < m_VisLines.size()) + { + std::vector::iterator it = m_VisLines.begin() + i; + for(CVisLine::iterator jt = it->begin(); jt != it->end(); jt++) + if(pt.x >= jt->m_rcBounds.left && pt.x <= jt->m_rcBounds.right) + { + if(jt->m_pHyperLink != NULL) + { + HighlightLink(&*jt, pt); + bFound = true; + if (GetCapture() != this) + SetCapture(); + } + break; + } + } + if(!bFound){ + RestoreLink(); + if (GetCapture() == this) + ReleaseCapture(); + } + } + else + ReleaseCapture(); +} + +void CHyperTextCtrl::OnLButtonDown(UINT nFlags,CPoint pt){ + CRect rcClient; + GetClientRect(rcClient); + if(PtInRect(rcClient, pt) && m_iLineHeight) + { + bool bFound = false; + UINT i = pt.y / m_iLineHeight; + if(i < m_VisLines.size()){ + std::vector::iterator it = m_VisLines.begin() + i; + for(CVisLine::iterator jt = it->begin(); jt != it->end(); jt++) + if(pt.x >= jt->m_rcBounds.left && pt.x <= jt->m_rcBounds.right) + { + if(jt->m_pHyperLink != NULL) + { + jt->m_pHyperLink->Execute(); + bFound = true; + } + break; + } + } + + } + //m_tip.OnLButtonDown(nFlags,pt); +} + +BOOL CHyperTextCtrl::OnMouseWheel(UINT nFlags,short zDelta,CPoint pt){ + CRect rc; + GetWindowRect(rc); + if(PtInRect(rc, pt)) + { + int iScrollLines; + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, + 0, + &iScrollLines, + 0); + + m_iWheelDelta -= zDelta; + if(abs(m_iWheelDelta) >= WHEEL_DELTA) + { + if(m_iWheelDelta > 0) + { + for(int i = 0; iAppendText(sText); + UpdateSize(bInvalidate); +} + +void CHyperTextCtrl::AppendHyperLink(const CString& sText, const CString& sTitle, const CString& sCommand, const CString& sDirectory, bool bInvalidate){ + m_Text->AppendHyperLink(sText, sTitle, sCommand, sDirectory); + UpdateSize(bInvalidate); +} + +void CHyperTextCtrl::AppendKeyWord(const CString& sText, COLORREF icolor){ + m_Text->AppendKeyWord(sText,icolor); + UpdateSize(true); +} + +void CHyperTextCtrl::AppendHyperLink(const CString& sText, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool bInvalidate){ + m_Text->AppendHyperLink(sText, sTitle, hWnd, uMsg, wParam, lParam); + UpdateSize(bInvalidate); +} + +void CHyperTextCtrl::SetLinkColor(COLORREF LinkColor, bool bInvalidate){ + m_LinkColor = LinkColor; + if(bInvalidate) + InvalidateRect(NULL,FALSE); +} + +void CHyperTextCtrl::UpdateSize(bool bRepaint){ + if(m_bDontUpdateSizeInfo) + return; + m_bDontUpdateSizeInfo = true; + DWORD dwStyle = GetWindowLongPtr(m_hWnd,GWL_STYLE); + bool vscrollneeded = false; + + CClientDC dc(this); + CFont* hOldFont = dc.SelectObject(m_Font); + + + int iScrollHeight = GetSystemMetrics(SM_CYHSCROLL); + + m_Lines.clear(); + CRect rc; + GetClientRect(rc); + rc.DeflateRect(2,0); + m_iMaxWidth = 0; + m_iLinesHeight = 0; + long iMaxWidthChars = 0; + SIZE sz; + + if(rc.Width() > 5 && rc.Height() > 5) + { + std::list::iterator it = m_Text->GetLinks().begin(); + std::list::iterator ht = m_Text->GetKeywords().begin(); + LPCTSTR s = m_Text->GetText(); + int len = m_Text->GetText().GetLength(); + int width = rc.Width(); + + int npos, // new position + pos = 0, // current position + ll, // line length + rll; // line length with wordwrap (if used) + + while(len>0) + { + ll = len; + npos = ll; + for(int i = 0; i < len; i++) + { + if(s[i] == _T('\r') || s[i] == _T('\n')) + { + if(s[i] == _T('\r') && ((i+1) < len) && s[i+1] == _T('\n')) + npos = i + 2; + else + npos = i + 1; + + ll = i; + break; + } + } + + if(!::GetTextExtentExPoint(dc, s , (ll > 512) ? 512 : ll, width, &rll, NULL, &sz) || sz.cy == 0) + { + ::GetTextExtentExPoint(dc, _T(" ") , 1, 0, NULL, NULL, &sz); + sz.cx = 0; + rll = ll; + } + + if(rll>ll) + rll = ll; + + if(!check_bits(dwStyle, HTC_WORDWRAP)) + rll = ll; + else + if(rll < ll) + npos = rll; + + if(rll>0) + { + if((rll < len) && !_istspace((_TUCHAR)s[rll])) + for(int i = rll - 1; i >= 0; i--) + if(_istspace((_TUCHAR)s[i])) + { + rll = i; + npos = i + 1; + break; + } + } + + if(npos == 0) + npos = 1; + + CLineInfo li(pos, pos + rll - 1); + CLinePartInfo pl(pos, pos + rll - 1); + + while(it != m_Text->GetLinks().end() && it->End() < pos) + it++; + while(ht != m_Text->GetKeywords().end() && ht->End() < pos) + ht++; + + //split the line into parts of hypertext, normaltext, keywords etc + for (int i = pl.Begin(); i < pl.End(); i++){ + if (it != m_Text->GetLinks().end() && i >= it->Begin() && it->End() > i){ // i_a + if (i > pl.m_xBegin){ + CLinePartInfo pln(pl.m_xBegin,i-1); + li.push_back(pln); + } + + if (it->End() > pl.End()){ + pl.m_xBegin = pl.End() + 1; + CLinePartInfo pln(i, pl.End(), &*it); + li.push_back(pln); + break; + } + else{ + pl.m_xBegin = it->End() + 1; + CLinePartInfo pln((uint16)i, (uint16)it->End(), &*it); + li.push_back(pln); + i = pl.m_xBegin; + it++; + } + + } + else if (ht != m_Text->GetKeywords().end() && i >= ht->Begin() && ht->End() > i){ // i_a + if (i > pl.m_xBegin){ + CLinePartInfo pln(pl.m_xBegin,i-1); + li.push_back(pln); + } + if (ht->End() > pl.End()){ + pl.m_xBegin = pl.End() + 1; + CLinePartInfo pln(i, pl.End(),0, &*ht); + li.push_back(pln); + break; + } + else{ + pl.m_xBegin = ht->End() + 1; + CLinePartInfo pln((uint16)i, (uint16)ht->End(),0, &*ht); + li.push_back(pln); + i = pl.m_xBegin; + ht++; + } + + } + + } + + if(pl.Len()>0) + li.push_back(pl); + + m_iLineHeight = sz.cy; + m_iLinesHeight+=m_iLineHeight; + if(sz.cx > m_iMaxWidth) + m_iMaxWidth = sz.cx; + if(iMaxWidthChars < li.Len()) + iMaxWidthChars = li.Len(); + + m_Lines.push_back(li); + + pos+=npos; + s+=npos; + len-=npos; + + if((m_iLinesHeight + iScrollHeight) > rc.Height()){ + vscrollneeded = true; + } + } + if(bRepaint) + InvalidateRect(rc); + } + + dc.SelectObject(hOldFont); + + // Update scroll bars + dwStyle = GetWindowLongPtr(m_hWnd,GWL_STYLE); + if (check_bits(dwStyle, HTC_AUTO_SCROLL_BARS)){ + if (vscrollneeded){ + if (!vscrollon) + ShowScrollBar(SB_VERT,TRUE); + dwStyle|=WS_VSCROLL; + vscrollon = true; + } + else if (!vscrollneeded){ + ShowScrollBar(SB_VERT,FALSE); + vscrollon = false; + } + } + + if(check_bits(dwStyle, HTC_AUTO_SCROLL_BARS) && !check_bits(dwStyle, HTC_WORDWRAP)) + { + if(m_iMaxWidth > rc.Width()) + { + ShowScrollBar(SB_HORZ,TRUE); + dwStyle|=WS_HSCROLL; + }; + + } + + SCROLLINFO si; + si.cbSize = sizeof(si); + si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; + + if(check_bits(dwStyle,WS_HSCROLL) && m_iMaxWidth != 0) + { + si.nMin = 0; + si.nMax = iMaxWidthChars + iMaxWidthChars/2; + si.nPos = (int)(double(si.nMax) * m_iHorzPos / 100); + si.nPage = (rc.Width() * si.nMax)/m_iMaxWidth; + SetScrollInfo(SB_HORZ, &si, FALSE); + } + + if(check_bits(dwStyle,WS_VSCROLL) && m_iLinesHeight != 0) + { + si.nMin = 0; + si.nMax = (int)m_Lines.size(); + si.nPos = si.nMax;//(int)(double(si.nMax) * m_iVertPos / 100); + si.nPage = (rc.Height() * si.nMax)/m_iLinesHeight; + SetScrollInfo(SB_VERT, &si, TRUE); + } + + m_bDontUpdateSizeInfo = false; + UpdateVisLines(); +} + +void CHyperTextCtrl::UpdateFonts(){ + DWORD dwStyle = GetWindowLongPtr(m_hWnd,GWL_STYLE); + m_LinksFont.DeleteObject(); + m_HoverFont.DeleteObject(); + + LOGFONT lf; + m_Font->GetLogFont(&lf); + if(check_bits(dwStyle, HTC_UNDERLINE_LINKS)) + lf.lfUnderline = TRUE; + m_LinksFont.CreateFontIndirect(&lf); + + m_Font->GetLogFont(&lf); + if(check_bits(dwStyle, HTC_UNDERLINE_HOVER)) + lf.lfUnderline = TRUE; + m_HoverFont.CreateFontIndirect(&lf); +} + +void CHyperTextCtrl::UpdateVisLines(){ + RestoreLink(); + DWORD dwStyle = ::GetWindowLongPtr(m_hWnd,GWL_STYLE); + int id = 1; + if(check_bits(dwStyle, HTC_ENABLE_TOOLTIPS)) + { + for(std::vector::iterator itv = m_VisLines.begin(); itv != m_VisLines.end(); itv++) + for(CVisLine::iterator jt = itv->begin(); jt != itv->end(); jt++) + { + if(jt->m_pHyperLink != NULL) + m_tip.DelTool(this, id++); + } + } + + m_VisLines.clear(); + + std::vector::iterator it = m_Lines.begin(); + int iVertPos = 0; + int iHorzPos = 0; + if(check_bits(dwStyle,WS_VSCROLL)) + iVertPos = GetScrollPos(SB_VERT); + if(check_bits(dwStyle,WS_HSCROLL)) + iHorzPos = GetScrollPos(SB_HORZ); + + if(iVertPos >= (int)m_Lines.size()) + return; + + it+=iVertPos; + + CClientDC dc(this); // device context for painting + + CFont* hOldFont = dc.SelectObject(m_Font); + + int ypos = 0; + LPCTSTR s = m_Text->GetText(); + + CRect rcClient; + GetClientRect(rcClient); + + for(; it != m_Lines.end(); it++) + { + int XPos = 2; + UINT LinePos = it->Begin(); + UINT Offset = 0; + UINT Len = 0; + + CVisLine vl; + CRect rcBounds; + + std::vector::iterator jt; + + for(jt = it->begin(); jt != it->end(); jt++) + { + if(jt->Begin() <= (LinePos + iHorzPos) && jt->End() >= (LinePos + iHorzPos)) + { + Offset = LinePos + iHorzPos; + Len = jt->Len() - ((LinePos + iHorzPos) - jt->Begin()); + break; + } + } + + while(jt != it->end()) + { + if(Len > 0) + { + SIZE sz; + ::GetTextExtentExPoint(dc, s + Offset, Len, 0, NULL, NULL, &sz); + + rcBounds.left = XPos; + XPos += sz.cx; + rcBounds.right = XPos; + rcBounds.top = ypos; + rcBounds.bottom = ypos+m_iLineHeight; + + vl.push_back(CVisPart(*jt, rcBounds, Offset, (uint16)Len, NULL, NULL)); + } + + if(XPos > rcClient.Width()) + break; + + jt++; + if (jt == it->end()) + break; + Offset = jt->m_xBegin; + Len = jt->Len(); + + } + + m_VisLines.push_back(vl); + ypos+=m_iLineHeight; + if(ypos>rcClient.bottom) + break; + } + + CVisPart *pPrev = NULL, *pNext; + + id = 1; + for(std::vector::iterator it2 = m_VisLines.begin(); it2 != m_VisLines.end(); it2++) + for(CVisLine::iterator jt = it2->begin(); jt != it2->end(); jt++) + { + pNext = &*jt; + if(pPrev != NULL && + pPrev->m_pHyperLink != NULL && + pPrev->m_pHyperLink == pNext->m_pHyperLink && + pPrev != pNext) + { + pPrev->m_pNext = pNext; + pNext->m_pPrev = pPrev; + } + pPrev = pNext; + + if(check_bits(dwStyle, HTC_ENABLE_TOOLTIPS) && jt->m_pHyperLink != NULL) + m_tip.AddTool(this, (LPCTSTR)jt->m_pHyperLink->Title(), jt->m_rcBounds, id++); + } + + dc.SelectObject(hOldFont); +} + +void CHyperTextCtrl::HighlightLink(CVisPart* Part, const CPoint& MouseCoords){ + if(m_pActivePart == Part) + return; + + if(m_pActivePart != Part && m_pActivePart != NULL && Part != NULL && m_pActivePart->m_pHyperLink != Part->m_pHyperLink) + RestoreLink(); + + m_pActivePart = Part; + while(m_pActivePart->m_pPrev != NULL) + m_pActivePart = m_pActivePart->m_pPrev; + + CClientDC dc(this); + CFont* hOldFont = dc.SelectObject(&m_HoverFont); + dc.SetBkColor(m_BkColor); + dc.SetTextColor(m_HoverColor); + LPCTSTR s = m_Text->GetText(); + + CVisPart* p = m_pActivePart; + while(p != NULL) + { + TextOut(dc, p->m_rcBounds.left, p->m_rcBounds.top, + s + p->m_iRealBegin, p->m_iRealLen); + p = p->m_pNext; + } + + dc.SelectObject(hOldFont); + + SetCursor(m_LinkCursor); +} + +void CHyperTextCtrl::RestoreLink(){ + if(m_pActivePart == NULL) + return; + + CClientDC dc(this); + CFont* hOldFont = dc.SelectObject(&m_LinksFont); + dc.SetBkColor(m_BkColor); + dc.SetTextColor(m_LinkColor); + LPCTSTR s = m_Text->GetText(); + + CVisPart* p = m_pActivePart; + while(p != NULL) + { + TextOut(dc, p->m_rcBounds.left, p->m_rcBounds.top, + s + p->m_iRealBegin, p->m_iRealLen); + p = p->m_pNext; + } + + dc.SelectObject(hOldFont); + + m_pActivePart = NULL; + SetCursor(m_DefaultCursor); +} + +void CHyperTextCtrl::OnSysColorChange() { + //adjust colors + CWnd::OnSysColorChange(); + SetColors(); +} + +void CHyperTextCtrl::SetColors() { + m_BkColor = GetSysColor(COLOR_WINDOW); + m_TextColor = GetSysColor(COLOR_WINDOWTEXT); + //perhaps some sort of check against the bk and text color can be made + //before blindly using these default link colors? + m_LinkColor = RGB(0,0,255); + m_HoverColor = RGB(255,0,0); +} + +void CHyperTextCtrl::LoadHandCursor() +{ + CString windir; + (void)GetWindowsDirectory(windir.GetBuffer(MAX_PATH), MAX_PATH); + windir.ReleaseBuffer(); + windir += _T("\\winhlp32.exe"); + HMODULE hModule = LoadLibrary(windir); + ASSERT( m_LinkCursor == NULL ); + if (hModule) + { + HCURSOR hTempCursor = ::LoadCursor(hModule, MAKEINTRESOURCE(106)); + if (hTempCursor) + m_LinkCursor = CopyCursor(hTempCursor); + FreeLibrary(hModule); + } + + if (m_LinkCursor == NULL) + m_LinkCursor = CopyCursor(::LoadCursor(NULL,IDC_ARROW)); +} diff --git a/HyperTextCtrl.h b/HyperTextCtrl.h new file mode 100644 index 00000000..6fb442bb --- /dev/null +++ b/HyperTextCtrl.h @@ -0,0 +1,270 @@ +/******************************************************************** + + HyperTextCtrl.h - Controls that shows hyperlinks + in text + + Copyright (C) 2001-2002 Magomed G. Abdurakhmanov + +********************************************************************/ +//edited by (C)2002 Merkur ( devs@emule-project.net / http://www.emule-project.net ) +//-> converted it to MFC +//-> included colored keywords +//-> fixed GPF bugs +//-> made it flickerfree +//-> some other small changes +// (the whole code still needs some work though) +// +//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. +#pragma once +#include +#include + +#define HTC_WORDWRAP 1 // word wrap text +#define HTC_AUTO_SCROLL_BARS 2 // auto hide scroll bars +#define HTC_UNDERLINE_LINKS 4 // underline links +#define HTC_UNDERLINE_HOVER 8 // underline hover links +#define HTC_ENABLE_TOOLTIPS 16 // enable hyperlink tool tips + +// -------------------------------------------------------------- +// CHyperLink + +class CHyperLink{ + friend class CPreparedHyperText; +public: + CHyperLink(); // i_a + CHyperLink(int iBegin, uint16 iEnd, const CString& sTitle, const CString& sCommand, const CString& sDirectory); + CHyperLink(int iBegin, uint16 iEnd, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + CHyperLink(const CHyperLink& Src); + + void Execute(); + bool operator < (const CHyperLink& Arg) const {return m_iEnd < Arg.m_iEnd;} + UINT Begin() const {return m_iBegin;} + UINT End() const {return m_iEnd;} + UINT Len() const {return m_iEnd - m_iBegin + 1;} + CString Title() const {return m_sTitle;} + void SetBegin( uint16 m_iInBegin ) {m_iBegin = m_iInBegin;} + void SetEnd( uint16 m_iInEnd ) {m_iEnd = m_iInEnd;} + +protected: + int m_iBegin; + int m_iEnd; + CString m_sTitle; + + enum LinkType + { + lt_Unknown = 0, // i_a + lt_Shell = 0, /* http:// mailto:*/ + lt_Message = 1 /* WM_COMMAND */ + } m_Type; + + // used for lt_Shell + CString m_sCommand; + CString m_sDirectory; + // used for lt_Message + HWND m_hWnd; + UINT m_uMsg; + WPARAM m_wParam; + LPARAM m_lParam; +}; + +// -------------------------------------------------------------- +// CKeyWord + +class CKeyWord{ + friend class CPreparedHyperText; +public: + CKeyWord(); + CKeyWord(int iBegin, uint16 iEnd, COLORREF icolor); + + bool operator< (const CKeyWord& Arg) const {return m_iEnd < Arg.m_iEnd;} + UINT Begin() const {return m_iBegin;} + UINT End() const {return m_iEnd;} + void SetBegin( uint16 m_iInBegin ) {m_iBegin = m_iInBegin;} + void SetEnd( uint16 m_iInEnd ) {m_iEnd = m_iInEnd;} + COLORREF Color() const {return color;} + UINT Len() const {return m_iEnd - m_iBegin + 1;} +protected: + int m_iBegin; + int m_iEnd; + COLORREF color; +}; + +// -------------------------------------------------------------- +// CPreparedHyperText + +class CPreparedHyperText{ +public: + CPreparedHyperText() {} + CPreparedHyperText(const CString& sText); + CPreparedHyperText(const CPreparedHyperText& src); + + void Clear(); + void SetText(const CString& sText); + void AppendText(const CString& sText); + void AppendHyperLink(const CString& sText, const CString& sTitle, const CString& sCommand, const CString& sDirectory); + void AppendHyperLink(const CString& sText, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void AppendKeyWord(const CString& sText, COLORREF iColor); + + CString& GetText() {return m_sText;} + std::list& GetLinks() {return m_Links;} + std::list& GetKeywords() {return m_KeyWords;} + //friend class CHyperTextCtrl; + +protected: + CString m_sText; + std::list m_Links; + std::list m_KeyWords; + + void RemoveLastSign(CString& sLink); + void PrepareText(const CString& sText); + bool tspace(TCHAR c) {return _istspace((_TUCHAR)c) || /*c < _T(' ') || */c == _T(';') || c == _T('!');} + +}; +// -------------------------------------------------------------- +// CLinePartInfo +class CLinePartInfo{ +public: + uint16 m_xBegin; + uint16 m_xEnd; + CHyperLink* m_pHyperLink; + CKeyWord* m_pKeyWord; + + CLinePartInfo(int iBegin, uint16 iEnd, CHyperLink* pHyperLink = NULL, CKeyWord* pKeyWord = NULL); + CLinePartInfo(const CLinePartInfo& Src); + uint16 Begin() {return m_xBegin;} + uint16 End() {return m_xEnd;} + uint16 Len() {return ((m_xEnd - m_xBegin) + 1);} +}; + +// -------------------------------------------------------------- +// CLineInfo +class CLineInfo : public std::vector{ +public: + int m_iBegin; + int m_iEnd; + + CLineInfo(int iBegin, uint16 iEnd); + CLineInfo(const CLineInfo& Src); + UINT Begin() {return m_iBegin;} + UINT End() {return m_iEnd;} + UINT Len() {return m_iEnd - m_iBegin + 1;} +}; + +// -------------------------------------------------------------- +// CVisPart +class CVisPart : public CLinePartInfo { +public: + CRect m_rcBounds; + int m_iRealBegin; + int m_iRealLen; + CVisPart* m_pPrev; + CVisPart* m_pNext; + + CVisPart(const CLinePartInfo& LinePartInfo, const CRect& rcBounds, + int iRealBegin, uint16 iRealLen,CVisPart* pPrev,CVisPart* pNext); + CVisPart(const CVisPart& Src); +}; + +class CVisLine : public std::vector +{ }; + + +// -------------------------------------------------------------- +// CHyperTextCtrl + +class CHyperTextCtrl : public CWnd{ + DECLARE_DYNAMIC(CHyperTextCtrl) +protected: + CPreparedHyperText* m_Text; + CPreparedHyperText standart_Text; + std::vector m_Lines; + CFont* m_Font; + COLORREF m_BkColor; + COLORREF m_TextColor; + COLORREF m_LinkColor; + COLORREF m_HoverColor; + HCURSOR m_LinkCursor; + HCURSOR m_DefaultCursor; + + CToolTipCtrl m_tip; + + //temporary variables + bool vscrollon; + int m_iMaxWidth; // The maximum line width + int m_iLineHeight; // Height of one line + int m_iLinesHeight; // Sum of height of all lines + bool m_bDontUpdateSizeInfo; // Used to prevent recursive call of the UpdateSize() method + int m_iVertPos; // Vertical position in percents + int m_iHorzPos; // Horizontal position in percents + CFont m_DefaultFont; // This font is set by default + CFont m_LinksFont; // Copied from main font to faster draw (link) + CFont m_HoverFont; // Copied from main font to faster draw (hover link) + std::vector m_VisLines; // Currently visible text + CVisPart* m_pActivePart; // Active part of link (hovered) + int m_iWheelDelta; // Mouse wheel scroll delta + DECLARE_MESSAGE_MAP() + +public: + CHyperTextCtrl(); + virtual BOOL PreTranslateMessage(MSG* /*pMsg*/) {return FALSE;} + void Clear() {m_Text->Clear();UpdateSize(true);} + + //message handlers + afx_msg void OnMouseMove(UINT nFlags,CPoint pt); + afx_msg void OnLButtonDown(UINT nFlags,CPoint pt); + afx_msg BOOL OnMouseWheel(UINT nFlags,short zDelta,CPoint pt); + afx_msg LRESULT OnCreate(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnDestroy(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnPaint(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnSize(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnShowWindow(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnSetText(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnGetText(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnSetFont(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnGetFont(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnHScroll(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnVScroll(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnCaptureChanged(WPARAM wParam, LPARAM lParam); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnSysColorChange(); + // Operations + CPreparedHyperText* GetHyperText(); + void SetHyperText(CPreparedHyperText* Src, bool bInvalidate = true); + void AppendText(const CString& sText, bool bInvalidate = true); + void AppendHyperLink(const CString& sText, const CString& sTitle, const CString& sCommand, const CString& sDirectory, bool bInvalidate = true); + void AppendHyperLink(const CString& sText, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool bInvalidate = true); + void AppendKeyWord(const CString& sText, COLORREF icolor); + COLORREF GetBkColor() {return m_BkColor;} + void SetBkColor(COLORREF Color) {m_BkColor = Color;} + COLORREF GetTextColor() {return m_TextColor;} + void SetTextColor(COLORREF Color) {m_TextColor = Color;} + COLORREF GetLinkColor() {return m_LinkColor;} + void SetLinkColor(COLORREF LinkColor, bool bInvalidate = true); + COLORREF GetHoverColor() {return m_HoverColor;} + void SetHoverColor(COLORREF HoverColor) {m_HoverColor = HoverColor;} + HCURSOR GetLinkCursor() {return m_LinkCursor;} + void SetLinkCursor(HCURSOR LinkCursor) {m_LinkCursor = LinkCursor;} + HCURSOR GetDefaultCursor() {return m_DefaultCursor;} + void SetDefaultCursor(HCURSOR DefaultCursor) {m_DefaultCursor = DefaultCursor;} + void UpdateSize(bool bRepaint); +protected: + bool check_bits(DWORD Value, DWORD Mask) {return (Value & Mask) == Mask;} + void UpdateFonts(); + void UpdateVisLines(); + void HighlightLink(CVisPart* Part, const CPoint& MouseCoords); + void RestoreLink(); + void SetColors(); + void LoadHandCursor(); +}; diff --git a/I18n.cpp b/I18n.cpp new file mode 100644 index 00000000..e130317e --- /dev/null +++ b/I18n.cpp @@ -0,0 +1,490 @@ +#include "stdafx.h" +#include +#include "emule.h" +#include "OtherFunctions.h" +#include "Preferences.h" +#include "langids.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +static HINSTANCE s_hLangDLL = NULL; + +CString GetResString(UINT uStringID, WORD wLanguageID) +{ + CString resString; + if (s_hLangDLL) + (void)resString.LoadString(s_hLangDLL, uStringID, wLanguageID); + if (resString.IsEmpty()) + (void)resString.LoadString(GetModuleHandle(NULL), uStringID, LANGID_EN_US); + return resString; +} + +CString GetResString(UINT uStringID) +{ + CString resString; + if (s_hLangDLL) + resString.LoadString(s_hLangDLL, uStringID); + if (resString.IsEmpty()) + resString.LoadString(GetModuleHandle(NULL), uStringID); + return resString; +} + +struct SLanguage { + LANGID lid; + LPCTSTR pszLocale; + BOOL bSupported; + LPCTSTR pszISOLocale; + UINT uCodepage; + LPCTSTR pszHtmlCharset; +}; + + +// Codepages (Windows) +// --------------------------------------------------------------------- +// 1250 ANSI - Central European +// 1251 ANSI - Cyrillic +// 1252 ANSI - Latin I +// 1253 ANSI - Greek +// 1254 ANSI - Turkish +// 1255 ANSI - Hebrew +// 1256 ANSI - Arabic +// 1257 ANSI - Baltic +// 1258 ANSI/OEM - Vietnamese +// 932 ANSI/OEM - Japanese, Shift-JIS +// 936 ANSI/OEM - Simplified Chinese (PRC, Singapore) +// 949 ANSI/OEM - Korean (Unified Hangeul Code) +// 950 ANSI/OEM - Traditional Chinese (Taiwan; Hong Kong SAR, PRC) + +// HTML charsets CodePg +// ------------------------------------------- +// windows-1250 1250 Central European (Windows) +// windows-1251 1251 Cyrillic (Windows) +// windows-1252 1252 Western European (Windows) +// windows-1253 1253 Greek (Windows) +// windows-1254 1254 Turkish (Windows) +// windows-1255 1255 Hebrew (Windows) +// windows-1256 1256 Arabic (Windows) +// windows-1257 1257 Baltic (Windows) +// +// NOTE: the 'iso-...' charsets are more backward compatible than the 'windows-...' charsets. +// NOTE-ALSO: some of the 'iso-...' charsets are by default *not* installed by IE6 (e.g. Arabic (ISO)) or show up +// with wrong chars - so, better use the 'windows-' charsets.. +// +// iso-8859-1 1252 Western European (ISO) +// iso-8859-2 1250 Central European (ISO) +// iso-8859-3 1254 Latin 3 (ISO) +// iso-8859-4 1257 Baltic (ISO) +// iso-8859-5 1251 Cyrillic (ISO) does not show up correctly in IE6 +// iso-8859-6 1256 Arabic (ISO) not installed (by default) with IE6 +// iso-8859-7 1253 Greek (ISO) +// iso-8859-8 1255 Hebrew (ISO-Visual) +// iso-8859-9 1254 Turkish (ISO) +// iso-8859-15 1252 Latin 9 (ISO) +// iso-2022-jp 932 Japanese (JIS) +// iso-2022-kr 949 Korean (ISO) + +static SLanguage s_aLanguages[] = +{ + {LANGID_AR_AE, _T(""), FALSE, _T("ar_AE"), 1256, _T("windows-1256")}, // Arabic (UAE) + {LANGID_BA_BA, _T(""), FALSE, _T("ba_BA"), 1252, _T("windows-1252")}, // Basque + {LANGID_BG_BG, _T("hun"), FALSE, _T("bg_BG"), 1251, _T("windows-1251")}, // Bulgarian + {LANGID_CA_ES, _T(""), FALSE, _T("ca_ES"), 1252, _T("windows-1252")}, // Catalan + {LANGID_CZ_CZ, _T("czech"), FALSE, _T("cz_CZ"), 1250, _T("windows-1250")}, // Czech + {LANGID_DA_DK, _T("danish"), FALSE, _T("da_DK"), 1252, _T("windows-1252")}, // Danish + {LANGID_DE_DE, _T("german"), FALSE, _T("de_DE"), 1252, _T("windows-1252")}, // German (Germany) + {LANGID_EL_GR, _T("greek"), FALSE, _T("el_GR"), 1253, _T("windows-1253")}, // Greek + {LANGID_EN_US, _T("english"), TRUE, _T("en_US"), 1252, _T("windows-1252")}, // English + {LANGID_ES_ES_T,_T("spanish"), FALSE, _T("es_ES_T"), 1252, _T("windows-1252")}, // Spanish (Castilian) + {LANGID_ES_AS, _T("spanish"), FALSE, _T("es_AS"), 1252, _T("windows-1252")}, // Asturian + {LANGID_VA_ES, _T(""), FALSE, _T("va_ES"), 1252, _T("windows-1252")}, // Valencian AVL + {LANGID_VA_ES_RACV, _T(""), FALSE, _T("va_ES_RACV"), 1252, _T("windows-1252")},// Valencian RACV + {LANGID_ET_EE, _T(""), FALSE, _T("et_EE"), 1257, _T("windows-1257")}, // Estonian + {LANGID_FA_IR, _T("farsi"), FALSE, _T("fa_IR"), 1256, _T("windows-1256")}, // Farsi + {LANGID_FI_FI, _T("finnish"), FALSE, _T("fi_FI"), 1252, _T("windows-1252")}, // Finnish + {LANGID_FR_FR, _T("french"), FALSE, _T("fr_FR"), 1252, _T("windows-1252")}, // French (France) + {LANGID_FR_BR, _T("french"), FALSE, _T("fr_BR"), 1252, _T("windows-1252")}, // French (Breton) + {LANGID_GL_ES, _T(""), FALSE, _T("gl_ES"), 1252, _T("windows-1252")}, // Galician + {LANGID_HE_IL, _T(""), FALSE, _T("he_IL"), 1255, _T("windows-1255")}, // Hebrew + {LANGID_HU_HU, _T("hungarian"), FALSE, _T("hu_HU"), 1250, _T("windows-1250")}, // Hungarian + {LANGID_IT_IT, _T("italian"), FALSE, _T("it_IT"), 1252, _T("windows-1252")}, // Italian (Italy) + {LANGID_JP_JP, _T("japanese"), FALSE, _T("jp_JP"), 932, _T("shift_jis")}, // Japanese + {LANGID_KO_KR, _T("korean"), FALSE, _T("ko_KR"), 949, _T("euc-kr")}, // Korean + {LANGID_LT_LT, _T(""), FALSE, _T("lt_LT"), 1257, _T("windows-1257")}, // Lithuanian + {LANGID_LV_LV, _T(""), FALSE, _T("lv_LV"), 1257, _T("windows-1257")}, // Latvian + {LANGID_MT_MT, _T("mt"), FALSE, _T("mt_MT"), 1254, _T("windows-1254")}, // Maltese + {LANGID_NB_NO, _T("nor"), FALSE, _T("nb_NO"), 1252, _T("windows-1252")}, // Norwegian (Bokmal) + {LANGID_NN_NO, _T("non"), FALSE, _T("nn_NO"), 1252, _T("windows-1252")}, // Norwegian (Nynorsk) + {LANGID_NL_NL, _T("dutch"), FALSE, _T("nl_NL"), 1252, _T("windows-1252")}, // Dutch (Netherlands) + {LANGID_PL_PL, _T("polish"), FALSE, _T("pl_PL"), 1250, _T("windows-1250")}, // Polish + {LANGID_PT_BR, _T("ptb"), FALSE, _T("pt_BR"), 1252, _T("windows-1252")}, // Portuguese (Brazil) + {LANGID_PT_PT, _T("ptg"), FALSE, _T("pt_PT"), 1252, _T("windows-1252")}, // Portuguese (Portugal) + {LANGID_RO_RO, _T(""), FALSE, _T("ro_RO"), 1250, _T("windows-1250")}, // Romanian + {LANGID_RU_RU, _T("russian"), FALSE, _T("ru_RU"), 1251, _T("windows-1251")}, // Russian + {LANGID_SL_SI, _T("slovak"), FALSE, _T("sl_SI"), 1250, _T("windows-1250")}, // Slovenian + {LANGID_SQ_AL, _T(""), FALSE, _T("sq_AL"), 1252, _T("windows-1252")}, // Albanian (Albania) + {LANGID_SV_SE, _T("swedish"), FALSE, _T("sv_SE"), 1252, _T("windows-1252")}, // Swedish + {LANGID_TR_TR, _T("turkish"), FALSE, _T("tr_TR"), 1254, _T("windows-1254")}, // Turkish + {LANGID_UA_UA, _T(""), FALSE, _T("ua_UA"), 1251, _T("windows-1251")}, // Ukrainian + {LANGID_ZH_CN, _T("chs"), FALSE, _T("zh_CN"), 936, _T("gb2312")}, // Chinese (P.R.C.) + {LANGID_ZH_TW, _T("cht"), FALSE, _T("zh_TW"), 950, _T("big5")}, // Chinese (Taiwan) + {LANGID_VI_VN, _T(""), FALSE, _T("vi_VN"), 1258, _T("windows-1258")}, // Vietnamese + {LANGID_UG_CN, _T(""), FALSE, _T("ug_CN"), 1256, _T("windows-1256")}, // Uighur + {0, NULL, 0, 0} +}; + +static void InitLanguages(const CString& rstrLangDir1, const CString& rstrLangDir2, bool bReInit = false) +{ + static BOOL _bInitialized = FALSE; + if (_bInitialized && !bReInit) + return; + _bInitialized = TRUE; + + CFileFind ff; + bool bEnd = !ff.FindFile(rstrLangDir1 + _T("*.dll"), 0); + bool bFirstDir = rstrLangDir1.CompareNoCase(rstrLangDir2) != 0; + while (!bEnd) + { + bEnd = !ff.FindNextFile(); + if (ff.IsDirectory()) + continue; + TCHAR szLandDLLFileName[_MAX_FNAME]; + _tsplitpath(ff.GetFileName(), NULL, NULL, szLandDLLFileName, NULL); + + SLanguage* pLangs = s_aLanguages; + if (pLangs){ + while (pLangs->lid){ + if (_tcsicmp(pLangs->pszISOLocale, szLandDLLFileName) == 0){ + pLangs->bSupported = TRUE; + break; + } + pLangs++; + } + } + if (bEnd && bFirstDir){ + ff.Close(); + bEnd = !ff.FindFile(rstrLangDir2 + _T("*.dll"), 0); + bFirstDir = false; + } + + } + ff.Close(); +} + +static void FreeLangDLL() +{ + if (s_hLangDLL != NULL && s_hLangDLL != GetModuleHandle(NULL)){ + VERIFY( FreeLibrary(s_hLangDLL) ); + s_hLangDLL = NULL; + } +} + +void CPreferences::GetLanguages(CWordArray& aLanguageIDs) +{ + const SLanguage* pLang = s_aLanguages; + while (pLang->lid){ + //if (pLang->bSupported) + //show all languages, offer download if not supported ones later + aLanguageIDs.Add(pLang->lid); + pLang++; + } +} + +WORD CPreferences::GetLanguageID() +{ + return m_wLanguageID; +} + +void CPreferences::SetLanguageID(WORD lid) +{ + m_wLanguageID = lid; +} + +static bool CheckLangDLLVersion(const CString& rstrLangDLL) +{ + bool bResult = false; + DWORD dwUnused; + DWORD dwVerInfSize = GetFileVersionInfoSize(const_cast((LPCTSTR)rstrLangDLL), &dwUnused); + if (dwVerInfSize != 0) + { + LPBYTE pucVerInf = (LPBYTE)calloc(dwVerInfSize, 1); + if (pucVerInf) + { + if (GetFileVersionInfo(const_cast((LPCTSTR)rstrLangDLL), 0, dwVerInfSize, pucVerInf)) + { + VS_FIXEDFILEINFO* pFileInf = NULL; + UINT uLen = 0; + if (VerQueryValue(pucVerInf, _T("\\"), (LPVOID*)&pFileInf, &uLen) && pFileInf && uLen) + { + bResult = (pFileInf->dwProductVersionMS == theApp.m_dwProductVersionMS && + pFileInf->dwProductVersionLS == theApp.m_dwProductVersionLS); + } + } + free(pucVerInf); + } + } + + return bResult; +} + +static bool LoadLangLib(const CString& rstrLangDir1, const CString& rstrLangDir2, LANGID lid) +{ + const SLanguage* pLangs = s_aLanguages; + if (pLangs){ + while (pLangs->lid){ + if (pLangs->bSupported && pLangs->lid == lid){ + FreeLangDLL(); + + bool bLoadedLib = false; + if (pLangs->lid == LANGID_EN_US){ + s_hLangDLL = NULL; + bLoadedLib = true; + } + else{ + CString strLangDLL = rstrLangDir1; + strLangDLL += pLangs->pszISOLocale; + strLangDLL += _T(".dll"); + if (CheckLangDLLVersion(strLangDLL)){ + s_hLangDLL = LoadLibrary(strLangDLL); + if (s_hLangDLL) + bLoadedLib = true; + } + if (rstrLangDir1.CompareNoCase(rstrLangDir2) != 0){ + strLangDLL = rstrLangDir2; + strLangDLL += pLangs->pszISOLocale; + strLangDLL += _T(".dll"); + if (CheckLangDLLVersion(strLangDLL)){ + s_hLangDLL = LoadLibrary(strLangDLL); + if (s_hLangDLL) + bLoadedLib = true; + } + } + } + if (bLoadedLib) + return true; + break; + } + pLangs++; + } + } + return false; +} + +void CPreferences::SetLanguage() +{ + InitLanguages(GetMuleDirectory(EMULE_INSTLANGDIR), GetMuleDirectory(EMULE_ADDLANGDIR, false)); + + bool bFoundLang = false; + if (m_wLanguageID) + bFoundLang = LoadLangLib(GetMuleDirectory(EMULE_INSTLANGDIR), GetMuleDirectory(EMULE_ADDLANGDIR, false), m_wLanguageID); + + if (!bFoundLang){ + LANGID lidLocale = (LANGID)::GetThreadLocale(); + //LANGID lidLocalePri = PRIMARYLANGID(::GetThreadLocale()); + //LANGID lidLocaleSub = SUBLANGID(::GetThreadLocale()); + + bFoundLang = LoadLangLib(GetMuleDirectory(EMULE_INSTLANGDIR), GetMuleDirectory(EMULE_ADDLANGDIR, false), lidLocale); + if (!bFoundLang){ + LoadLangLib(GetMuleDirectory(EMULE_INSTLANGDIR), GetMuleDirectory(EMULE_ADDLANGDIR, false), LANGID_EN_US); + m_wLanguageID = LANGID_EN_US; + CString strLngEnglish = GetResString(IDS_MB_LANGUAGEINFO); + AfxMessageBox(strLngEnglish, MB_ICONASTERISK); + } + else + m_wLanguageID = lidLocale; + } + + // if loading a string fails, set language to English + if (GetResString(IDS_MB_LANGUAGEINFO).IsEmpty()) { + LoadLangLib(GetMuleDirectory(EMULE_INSTLANGDIR), GetMuleDirectory(EMULE_ADDLANGDIR, false), LANGID_EN_US); + m_wLanguageID = LANGID_EN_US; + } + + InitThreadLocale(); +} + +bool CPreferences::IsLanguageSupported(LANGID lidSelected, bool bUpdateBefore){ + InitLanguages(GetMuleDirectory(EMULE_INSTLANGDIR), GetMuleDirectory(EMULE_ADDLANGDIR, false), bUpdateBefore); + if (lidSelected == LANGID_EN_US) + return true; + const SLanguage* pLang = s_aLanguages; + for (;pLang->lid;pLang++){ + if (pLang->lid == lidSelected && pLang->bSupported){ + bool bResult = CheckLangDLLVersion(GetMuleDirectory(EMULE_INSTLANGDIR) + CString(pLang->pszISOLocale) + _T(".dll")); + return bResult || CheckLangDLLVersion(GetMuleDirectory(EMULE_ADDLANGDIR, false) + CString(pLang->pszISOLocale) + _T(".dll")); + } + } + return false; +} + +CString CPreferences::GetLangDLLNameByID(LANGID lidSelected){ + const SLanguage* pLang = s_aLanguages; + for (;pLang->lid;pLang++){ + if (pLang->lid == lidSelected) + return CString(pLang->pszISOLocale) + _T(".dll"); + } + ASSERT ( false ); + return CString(_T("")); +} + +void CPreferences::SetRtlLocale(LCID lcid) +{ + const SLanguage* pLangs = s_aLanguages; + while (pLangs->lid) + { + if (pLangs->lid == LANGIDFROMLCID(lcid)) + { + if (pLangs->uCodepage) + { + CString strCodepage; + strCodepage.Format(_T(".%u"), pLangs->uCodepage); + _tsetlocale(LC_CTYPE, strCodepage); + } + break; + } + pLangs++; + } +} + +void CPreferences::InitThreadLocale() +{ + ASSERT( m_wLanguageID != 0 ); + + // NOTE: This function is for testing multi language support only. + // NOTE: This function is *NOT* to be enabled in release builds nor to be offered by any Mod! + if (theApp.GetProfileInt(_T("eMule"), _T("SetLanguageACP"), 0) != 0) + { + //LCID lcidUser = GetUserDefaultLCID(); // Installation, or altered by user in control panel (WinXP) + + // get the ANSI code page which is to be used for all non-Unicode conversions. + LANGID lidSystem = m_wLanguageID; + + // get user's sorting preferences + //UINT uSortIdUser = SORTIDFROMLCID(lcidUser); + //UINT uSortVerUser = SORTVERSIONFROMLCID(lcidUser); + // we can't use the same sorting paramters for 2 different Languages.. + UINT uSortIdUser = SORT_DEFAULT; + UINT uSortVerUser = 0; + + // set thread locale, this is used for: + // - MBCS->Unicode conversions (e.g. search results). + // - Unicode->MBCS conversions (e.g. publishing local files (names) in network, or savint text files on local disk)... + LCID lcid = MAKESORTLCID(lidSystem, uSortIdUser, uSortVerUser); + SetThreadLocale(lcid); + + // if we set the thread locale (see comments above) we also have to specify the proper + // code page for the C-RTL, otherwise we may not be able to store some strings as MBCS + // (Unicode->MBCS conversion may fail) + SetRtlLocale(lcid); + } +} + +void InitThreadLocale() +{ + thePrefs.InitThreadLocale(); +} + +CString GetCodePageNameForLocale(LCID lcid) +{ + CString strCodePage; + int iResult = GetLocaleInfo(lcid, LOCALE_IDEFAULTANSICODEPAGE, strCodePage.GetBuffer(6), 6); + strCodePage.ReleaseBuffer(); + + if (iResult > 0 && !strCodePage.IsEmpty()) + { + UINT uCodePage = _tcstoul(strCodePage, NULL, 10); + if (uCodePage != ULONG_MAX) + { + CPINFOEXW CPInfoEx = {0}; + BOOL (WINAPI *pfnGetCPInfoEx)(UINT, DWORD, LPCPINFOEXW); + (FARPROC&)pfnGetCPInfoEx = GetProcAddress(GetModuleHandle(_T("kernel32")), "GetCPInfoExW"); + if (pfnGetCPInfoEx&& (*pfnGetCPInfoEx)(uCodePage, 0, &CPInfoEx)) + strCodePage = CPInfoEx.CodePageName; + } + } + return strCodePage; +} + +CString CPreferences::GetHtmlCharset() +{ + ASSERT( m_wLanguageID != 0 ); + + LPCTSTR pszHtmlCharset = NULL; + const SLanguage* pLangs = s_aLanguages; + while (pLangs->lid) + { + if (pLangs->lid == m_wLanguageID) + { + pszHtmlCharset = pLangs->pszHtmlCharset; + break; + } + pLangs++; + } + + if (pszHtmlCharset == NULL || pszHtmlCharset[0] == _T('\0')) + { + ASSERT(0); // should never come here + + // try to get charset from code page + LPCTSTR pszLcLocale = _tsetlocale(LC_CTYPE, NULL); + if (pszLcLocale) + { + TCHAR szLocaleID[128]; + UINT uCodepage = 0; + if (_stscanf(pszLcLocale, _T("%[a-zA-Z_].%u"), szLocaleID, &uCodepage) == 2 && uCodepage != 0) + { + CString strHtmlCodepage; + strHtmlCodepage.Format(_T("windows-%u"), uCodepage); + return strHtmlCodepage; + } + } + } + + return pszHtmlCharset; +} + +static HHOOK s_hRTLWindowsLayoutOldCbtFilterHook = NULL; + +LRESULT CALLBACK RTLWindowsLayoutCbtFilterHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code == HCBT_CREATEWND) + { + //LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs; + + //if ((lpcs->style & WS_CHILD) == 0) + // lpcs->dwExStyle |= WS_EX_LAYOUTRTL; // doesn't seem to have any effect, but shouldn't hurt + + HWND hWnd = (HWND)wParam; + if ((GetWindowLong(hWnd, GWL_STYLE) & WS_CHILD) == 0) { + SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYOUTRTL); + } + } + return CallNextHookEx(s_hRTLWindowsLayoutOldCbtFilterHook, code, wParam, lParam); +} + +void CemuleApp::EnableRTLWindowsLayout() +{ + BOOL (WINAPI *pfnSetProcessDefaultLayout)(DWORD dwFlags); + (FARPROC&)pfnSetProcessDefaultLayout = GetProcAddress(GetModuleHandle(_T("user32")), "SetProcessDefaultLayout"); + if (pfnSetProcessDefaultLayout) + (*pfnSetProcessDefaultLayout)(LAYOUT_RTL); + + s_hRTLWindowsLayoutOldCbtFilterHook = SetWindowsHookEx(WH_CBT, RTLWindowsLayoutCbtFilterHook, NULL, GetCurrentThreadId()); +} + +void CemuleApp::DisableRTLWindowsLayout() +{ + if (s_hRTLWindowsLayoutOldCbtFilterHook) + { + VERIFY( UnhookWindowsHookEx(s_hRTLWindowsLayoutOldCbtFilterHook) ); + s_hRTLWindowsLayoutOldCbtFilterHook = NULL; + + BOOL (WINAPI *pfnSetProcessDefaultLayout)(DWORD dwFlags); + (FARPROC&)pfnSetProcessDefaultLayout = GetProcAddress(GetModuleHandle(_T("user32")), "SetProcessDefaultLayout"); + if (pfnSetProcessDefaultLayout) + (*pfnSetProcessDefaultLayout)(0); + } +} diff --git a/IESecurity.cpp b/IESecurity.cpp new file mode 100644 index 00000000..3e139728 --- /dev/null +++ b/IESecurity.cpp @@ -0,0 +1,308 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "IESecurity.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// CMuleBrowserControlSite + +BEGIN_INTERFACE_MAP(CMuleBrowserControlSite, CBrowserControlSite) + INTERFACE_PART(CMuleBrowserControlSite, IID_IInternetSecurityManager, InternetSecurityManager) + INTERFACE_PART(CMuleBrowserControlSite, IID_IServiceProvider, ServiceProvider) +END_INTERFACE_MAP() + +CMuleBrowserControlSite::CMuleBrowserControlSite(COleControlContainer* pCtrlCont, CDHtmlDialog* pHandler) + : CBrowserControlSite(pCtrlCont, pHandler) +{ + // Compiler bug?, MFC bug?, eMule bug (compiler settings)? + // + // When this class is compiled with _AFXDLL and /Zp4, the offset for 'CMuleBrowserControlSite::m_eUrlZone' + // and 'CBrowserControlSite::m_pHandler' are *EQUAL* !! + // + // Also, the offset for 'CBrowserControlSite::m_pHandler' is not the same in static MFC and shared MFC, + // though this might have a different reason. + // + // When compiled with: + // _AFXDLL, /Zp8 (default packing) OK + // _AFXDLL, /Zp4 *ERROR* + // + // Note also, MFC's internally used packing is 4 (_AFX_PACKING), and though it creates wrong offsets + // when we compile with /Zp4. + struct S1 { + char c; + __int64 ll; + }; + ASSERT( offsetof(S1, ll) == 8 ); + + m_eUrlZone = URLZONE_UNTRUSTED; + InitInternetSecurityZone(); +} + +void CMuleBrowserControlSite::InitInternetSecurityZone() +{ + CString strZone = AfxGetApp()->GetProfileString(_T("eMule"), _T("InternetSecurityZone"), _T("Untrusted")); + if (strZone.CompareNoCase(_T("LocalMachine"))==0) + m_eUrlZone = URLZONE_LOCAL_MACHINE; + else if (strZone.CompareNoCase(_T("Intranet"))==0) + m_eUrlZone = URLZONE_INTRANET; + else if (strZone.CompareNoCase(_T("Trusted"))==0) + m_eUrlZone = URLZONE_TRUSTED; + else if (strZone.CompareNoCase(_T("Internet"))==0) + m_eUrlZone = URLZONE_INTERNET; + else { + ASSERT( strZone.CompareNoCase(_T("Untrusted"))==0 ); + m_eUrlZone = URLZONE_UNTRUSTED; + } +} + + +#ifdef _DEBUG +#define DUMPIID(iid, name) DumpIID(iid, name) +#else +#define DUMPIID(iid, name) /**/ +#endif + +#ifdef _DEBUG +void DumpIID(REFIID iid, LPCTSTR pszClassName) +{ + CRegKey key; + TCHAR szName[100]; + DWORD dwType; + DWORD dw = sizeof(szName); + + LPOLESTR pszGUID = NULL; + if (FAILED(StringFromCLSID(iid, &pszGUID))) + return; + + OutputDebugString(pszClassName); + OutputDebugString(_T(" - ")); + + bool bFound = false; + // Attempt to find it in the interfaces section + if (key.Open(HKEY_CLASSES_ROOT, _T("Interface"), KEY_READ) == ERROR_SUCCESS) + { + if (key.Open(key, pszGUID, KEY_READ) == ERROR_SUCCESS) + { + *szName = 0; + if (RegQueryValueEx(key.m_hKey, (LPTSTR)NULL, NULL, &dwType, (LPBYTE)szName, &dw) == ERROR_SUCCESS) + { + OutputDebugString(szName); + bFound = true; + } + } + } + // Attempt to find it in the clsid section + else if (key.Open(HKEY_CLASSES_ROOT, _T("CLSID"), KEY_READ) == ERROR_SUCCESS) + { + if (key.Open(key, pszGUID, KEY_READ) == ERROR_SUCCESS) + { + *szName = 0; + if (RegQueryValueEx(key.m_hKey, (LPTSTR)NULL, NULL, &dwType, (LPBYTE)szName, &dw) == ERROR_SUCCESS) + { + OutputDebugString(_T("(CLSID\?\?\?) ")); + OutputDebugString(szName); + bFound = true; + } + } + } + + if (!bFound) + OutputDebugString(pszGUID); + OutputDebugString(_T("\n")); + CoTaskMemFree(pszGUID); +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// InternetSecurityManager +// +#pragma warning(disable:4555) // expression has no effect; expected expression with side-effect (because of the 'METHOD_PROLOGUE' macro) + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::QueryInterface(REFIID riid, void** ppvObj) +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + return (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj); +} + +STDMETHODIMP_(ULONG) CMuleBrowserControlSite::XInternetSecurityManager::AddRef() +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + return pThis->ExternalAddRef(); +} + +STDMETHODIMP_(ULONG) CMuleBrowserControlSite::XInternetSecurityManager::Release() +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + return pThis->ExternalRelease(); +} + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::SetSecuritySite(IInternetSecurityMgrSite* /*pSite*/) +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + TRACE(_T("%hs\n"), "SetSecuritySite"); + return INET_E_DEFAULT_ACTION; +} + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::GetSecuritySite(IInternetSecurityMgrSite** /*ppSite*/) +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + TRACE(_T("%hs\n"), "GetSecuritySite"); + return INET_E_DEFAULT_ACTION; +} + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::MapUrlToZone( + LPCWSTR pwszUrl, + DWORD* pdwZone, + DWORD dwFlags) +{ + UNREFERENCED_PARAMETER(pwszUrl); + UNREFERENCED_PARAMETER(dwFlags); + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + TRACE(_T("%hs: URL=%ls, Zone=%d, Flags=0x%x\n"), "MapUrlToZone", pwszUrl, *pdwZone, dwFlags); + if (pdwZone != NULL) + { + *pdwZone = (DWORD)pThis->m_eUrlZone; + return S_OK; + } + return INET_E_DEFAULT_ACTION; +} + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::GetSecurityId( + LPCWSTR pwszUrl, + BYTE* /*pbSecurityId*/, DWORD* /*pcbSecurityId*/, + DWORD dwReserved) +{ + UNREFERENCED_PARAMETER(pwszUrl); + UNREFERENCED_PARAMETER(dwReserved); + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + TRACE(_T("%hs: URL=%ls, Reserved=%u\n"), "GetSecurityId", pwszUrl, dwReserved); + return INET_E_DEFAULT_ACTION; +} + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::ProcessUrlAction( + LPCWSTR pwszUrl, + DWORD dwAction, + BYTE* /*pPolicy*/, DWORD /*cbPolicy*/, + BYTE* /*pContext*/, DWORD /*cbContext*/, + DWORD dwFlags, DWORD dwReserved) +{ + UNREFERENCED_PARAMETER(pwszUrl); + UNREFERENCED_PARAMETER(dwAction); + UNREFERENCED_PARAMETER(dwFlags); + UNREFERENCED_PARAMETER(dwReserved); + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + TRACE(_T("%hs: URL=%ls, Action=%u, Flags=0x%x, Reserved=%u\n"), "ProcessUrlAction", pwszUrl, dwAction, dwFlags, dwReserved); + +#if 0 + DWORD dwPolicy = URLPOLICY_DISALLOW; + if (cbPolicy >= sizeof(DWORD)) + { + *(DWORD*)pPolicy = dwPolicy; + return S_OK; + } + return S_FALSE; +#else + // Use the policy for the zone which was specified with 'MapUrlToZone' + // If that particular policy setting is specified as 'Ask User', the control *WILL OPEN* a message box! + return INET_E_DEFAULT_ACTION; +#endif +} + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::QueryCustomPolicy( + LPCWSTR pwszUrl, + REFGUID /*guidKey*/, + BYTE** /*ppPolicy*/, DWORD* /*pcbPolicy*/, + BYTE* /*pContext*/, DWORD /*cbContext*/, + DWORD /*dwReserved*/) +{ + UNREFERENCED_PARAMETER(pwszUrl); + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + TRACE(_T("%hs: URL=%ls\n"), "QueryCustomPolicy", pwszUrl); + return INET_E_DEFAULT_ACTION; +} + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::SetZoneMapping( + DWORD dwZone, + LPCWSTR lpszPattern, + DWORD dwFlags) +{ + UNREFERENCED_PARAMETER(dwZone); + UNREFERENCED_PARAMETER(lpszPattern); + UNREFERENCED_PARAMETER(dwFlags); + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + TRACE(_T("%hs: Zone=%d, Pattern=%ls, Flags=0x%x\n"), "SetZoneMapping", dwZone, lpszPattern, dwFlags); + return INET_E_DEFAULT_ACTION; +} + +STDMETHODIMP CMuleBrowserControlSite::XInternetSecurityManager::GetZoneMappings( + DWORD dwZone, + IEnumString** /*ppenumString*/, + DWORD dwFlags) +{ + UNREFERENCED_PARAMETER(dwZone); + UNREFERENCED_PARAMETER(dwFlags); + METHOD_PROLOGUE(CMuleBrowserControlSite, InternetSecurityManager); + TRACE(_T("%hs: Zone=%d, Flags=0x%s\n"), "GetZoneMappings", dwZone, dwFlags); + return INET_E_DEFAULT_ACTION; +} + + +/////////////////////////////////////////////////////////////////////////////// +// IServiceProvider +// + +STDMETHODIMP_(ULONG) CMuleBrowserControlSite::XServiceProvider::AddRef() +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, ServiceProvider); + return pThis->ExternalAddRef(); +} + +STDMETHODIMP_(ULONG) CMuleBrowserControlSite::XServiceProvider::Release() +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, ServiceProvider); + return pThis->ExternalRelease(); +} + +STDMETHODIMP CMuleBrowserControlSite::XServiceProvider::QueryInterface(REFIID riid, void** ppvObj) +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, ServiceProvider); + return (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj); +} + +STDMETHODIMP CMuleBrowserControlSite::XServiceProvider::QueryService(REFGUID guidService, REFIID riid, void** ppvObject) +{ + METHOD_PROLOGUE(CMuleBrowserControlSite, ServiceProvider); + //DUMPIID(guidService, _T("guidService")); + //DUMPIID(riid, _T("riid")); + if (guidService == SID_SInternetSecurityManager && riid == IID_IInternetSecurityManager) + { + TRACE(_T("%hs\n"), "QueryService"); + return (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObject); + } + *ppvObject = NULL; + return E_NOINTERFACE; +} + +#pragma warning(default:4555) // expression has no effect; expected expression with side-effect (because of the 'METHOD_PROLOGUE' macro) diff --git a/IESecurity.h b/IESecurity.h new file mode 100644 index 00000000..b317306d --- /dev/null +++ b/IESecurity.h @@ -0,0 +1,28 @@ +#pragma once + +class CMuleBrowserControlSite : public CBrowserControlSite +{ +public: + CMuleBrowserControlSite(COleControlContainer* pCtrlCont, CDHtmlDialog *pHandler); + +protected: + URLZONE m_eUrlZone; + void InitInternetSecurityZone(); + + DECLARE_INTERFACE_MAP(); + + BEGIN_INTERFACE_PART(InternetSecurityManager, IInternetSecurityManager) + STDMETHOD(SetSecuritySite)(IInternetSecurityMgrSite*); + STDMETHOD(GetSecuritySite)(IInternetSecurityMgrSite**); + STDMETHOD(MapUrlToZone)(LPCWSTR,DWORD*, DWORD); + STDMETHOD(GetSecurityId)(LPCWSTR,BYTE*, DWORD*, DWORD); + STDMETHOD(ProcessUrlAction)(LPCWSTR pwszUrl, DWORD dwAction, BYTE __RPC_FAR *pPolicy, DWORD cbPolicy, BYTE __RPC_FAR *pContext, DWORD cbContext, DWORD dwFlags, DWORD dwReserved = 0); + STDMETHOD(QueryCustomPolicy)(LPCWSTR, REFGUID, BYTE**, DWORD*, BYTE*, DWORD, DWORD); + STDMETHOD(SetZoneMapping)(DWORD, LPCWSTR, DWORD); + STDMETHOD(GetZoneMappings)(DWORD, IEnumString**, DWORD); + END_INTERFACE_PART(InternetSecurityManager) + + BEGIN_INTERFACE_PART(ServiceProvider, IServiceProvider) + STDMETHOD(QueryService)(REFGUID, REFIID, void**); + END_INTERFACE_PART(ServiceProvider) +}; diff --git a/IPFilter.cpp b/IPFilter.cpp new file mode 100644 index 00000000..a0e9da81 --- /dev/null +++ b/IPFilter.cpp @@ -0,0 +1,494 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include +#include +#include +#include "emule.h" +#include "IPFilter.h" +#include "OtherFunctions.h" +#include "StringConversion.h" +#include "Preferences.h" +#include "emuledlg.h" +#include "Log.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define DFLT_FILTER_LEVEL 100 // default filter level if non specified + +CIPFilter::CIPFilter() +{ + m_pLastHit = NULL; + m_bModified = false; + LoadFromDefaultFile(false); +} + +CIPFilter::~CIPFilter() +{ + if (m_bModified) + { + try{ + SaveToDefaultFile(); + } + catch(CString){ + } + } + RemoveAllIPFilters(); +} + +static int __cdecl CmpSIPFilterByStartAddr(const void* p1, const void* p2) +{ + const SIPFilter* rng1 = *(SIPFilter**)p1; + const SIPFilter* rng2 = *(SIPFilter**)p2; + return CompareUnsigned(rng1->start, rng2->start); +} + +CString CIPFilter::GetDefaultFilePath() const +{ + return thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + DFLT_IPFILTER_FILENAME; +} + +int CIPFilter::LoadFromDefaultFile(bool bShowResponse) +{ + RemoveAllIPFilters(); + return AddFromFile(GetDefaultFilePath(), bShowResponse); +} + +int CIPFilter::AddFromFile(LPCTSTR pszFilePath, bool bShowResponse) +{ + DWORD dwStart = GetTickCount(); + FILE* readFile = _tfsopen(pszFilePath, _T("r"), _SH_DENYWR); + if (readFile != NULL) + { + enum EIPFilterFileType + { + Unknown = 0, + FilterDat = 1, // ipfilter.dat/ip.prefix format + PeerGuardian = 2, // PeerGuardian text format + PeerGuardian2 = 3 // PeerGuardian binary format + } eFileType = Unknown; + + setvbuf(readFile, NULL, _IOFBF, 32768); + + TCHAR szNam[_MAX_FNAME]; + TCHAR szExt[_MAX_EXT]; + _tsplitpath(pszFilePath, NULL, NULL, szNam, szExt); + if (_tcsicmp(szExt, _T(".p2p")) == 0 || (_tcsicmp(szNam, _T("guarding.p2p")) == 0 && _tcsicmp(szExt, _T(".txt")) == 0)) + eFileType = PeerGuardian; + else if (_tcsicmp(szExt, _T(".prefix")) == 0) + eFileType = FilterDat; + else + { + VERIFY( _setmode(_fileno(readFile), _O_BINARY) != -1 ); + static const BYTE _aucP2Bheader[] = "\xFF\xFF\xFF\xFFP2B"; + BYTE aucHeader[sizeof _aucP2Bheader - 1]; + if (fread(aucHeader, sizeof aucHeader, 1, readFile) == 1) + { + if (memcmp(aucHeader, _aucP2Bheader, sizeof _aucP2Bheader - 1)==0) + eFileType = PeerGuardian2; + else + { + (void)fseek(readFile, 0, SEEK_SET); + VERIFY( _setmode(_fileno(readFile), _O_TEXT) != -1 ); // ugly! + } + } + } + + int iFoundRanges = 0; + int iLine = 0; + if (eFileType == PeerGuardian2) + { + // Version 1: strings are ISO-8859-1 encoded + // Version 2: strings are UTF-8 encoded + uint8 nVersion; + if (fread(&nVersion, sizeof nVersion, 1, readFile)==1 && (nVersion==1 || nVersion==2)) + { + while (!feof(readFile)) + { + CHAR szName[256]; + int iLen = 0; + for (;;) // read until NUL or EOF + { + int iChar = getc(readFile); + if (iChar == EOF) + break; + if (iLen < sizeof szName - 1) + szName[iLen++] = (CHAR)iChar; + if (iChar == '\0') + break; + } + szName[iLen] = '\0'; + + uint32 uStart; + if (fread(&uStart, sizeof uStart, 1, readFile) != 1) + break; + uStart = ntohl(uStart); + + uint32 uEnd; + if (fread(&uEnd, sizeof uEnd, 1, readFile) != 1) + break; + uEnd = ntohl(uEnd); + + iLine++; + // (nVersion == 2) ? OptUtf8ToStr(szName, iLen) : + AddIPRange(uStart, uEnd, DFLT_FILTER_LEVEL, CStringA(szName, iLen)); + iFoundRanges++; + } + } + } + else + { + CStringA sbuffer; + CHAR szBuffer[1024]; + while (fgets(szBuffer, _countof(szBuffer), readFile) != NULL) + { + iLine++; + sbuffer = szBuffer; + + // ignore comments & too short lines + if (sbuffer.GetAt(0) == '#' || sbuffer.GetAt(0) == '/' || sbuffer.GetLength() < 5) { + sbuffer.Trim(" \t\r\n"); + DEBUG_ONLY( (!sbuffer.IsEmpty()) ? TRACE("IP filter: ignored line %u\n", iLine) : 0 ); + continue; + } + + if (eFileType == Unknown) + { + // looks like html + if (sbuffer.Find('>') > -1 && sbuffer.Find('<') > -1) + sbuffer.Delete(0, sbuffer.ReverseFind('>') + 1); + + // check for - at start of line + UINT u1, u2, u3, u4, u5, u6, u7, u8; + if (sscanf(sbuffer, "%u.%u.%u.%u - %u.%u.%u.%u", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) == 8) + { + eFileType = FilterDat; + } + else + { + // check for ':' '-' + int iColon = sbuffer.Find(':'); + if (iColon > -1) + { + CStringA strIPRange = sbuffer.Mid(iColon + 1); + UINT u1, u2, u3, u4, u5, u6, u7, u8; + if (sscanf(strIPRange, "%u.%u.%u.%u - %u.%u.%u.%u", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) == 8) + { + eFileType = PeerGuardian; + } + } + } + } + + bool bValid = false; + uint32 start = 0; + uint32 end = 0; + UINT level = 0; + CStringA desc; + if (eFileType == FilterDat) + bValid = ParseFilterLine1(sbuffer, start, end, level, desc); + else if (eFileType == PeerGuardian) + bValid = ParseFilterLine2(sbuffer, start, end, level, desc); + + // add a filter + if (bValid) + { + AddIPRange(start, end, level, desc); + iFoundRanges++; + } + else + { + sbuffer.Trim(" \t\r\n"); + DEBUG_ONLY( (!sbuffer.IsEmpty()) ? TRACE("IP filter: ignored line %u\n", iLine) : 0 ); + } + } + } + fclose(readFile); + + // sort the IP filter list by IP range start addresses + qsort(m_iplist.GetData(), m_iplist.GetCount(), sizeof(m_iplist[0]), CmpSIPFilterByStartAddr); + + // merge overlapping and adjacent filter ranges + int iDuplicate = 0; + int iMerged = 0; + if (m_iplist.GetCount() >= 2) + { + // On large IP-filter lists there is a noticeable performance problem when merging the list. + // The 'CIPFilterArray::RemoveAt' call is way too expensive to get called during the merging, + // thus we use temporary helper arrays to copy only the entries into the final list which + // are not get deleted. + + // Reserve a byte array (its used as a boolean array actually) as large as the current + // IP-filter list, so we can set a 'to delete' flag for each entry in the current IP-filter list. + char* pcToDelete = new char[m_iplist.GetCount()]; + memset(pcToDelete, 0, m_iplist.GetCount()); + int iNumToDelete = 0; + + SIPFilter* pPrv = m_iplist[0]; + int i = 1; + while (i < m_iplist.GetCount()) + { + SIPFilter* pCur = m_iplist[i]; + if ( pCur->start >= pPrv->start && pCur->start <= pPrv->end // overlapping + || pCur->start == pPrv->end+1 && pCur->level == pPrv->level) // adjacent + { + if (pCur->start != pPrv->start || pCur->end != pPrv->end) // don't merge identical entries + { + //TODO: not yet handled, overlapping entries with different 'level' + if (pCur->end > pPrv->end) + pPrv->end = pCur->end; + //pPrv->desc += _T("; ") + pCur->desc; // this may create a very very long description string... + iMerged++; + } + else + { + // if we have identical entries, use the lowest 'level' + if (pCur->level < pPrv->level) + pPrv->level = pCur->level; + iDuplicate++; + } + delete pCur; + //m_iplist.RemoveAt(i); // way too expensive (read above) + pcToDelete[i] = 1; // mark this entry as 'to delete' + iNumToDelete++; + i++; + continue; + } + pPrv = pCur; + i++; + } + + // Create new IP-filter list which contains only the entries from the original IP-filter list + // which are not to be deleted. + if (iNumToDelete > 0) + { + CIPFilterArray newList; + newList.SetSize(m_iplist.GetCount() - iNumToDelete); + int iNewListIndex = 0; + for (int i = 0; i < m_iplist.GetCount(); i++) { + if (!pcToDelete[i]) + newList[iNewListIndex++] = m_iplist[i]; + } + ASSERT( iNewListIndex == newList.GetSize() ); + + // Replace current list with new list. Dump, but still fast enough (only 1 memcpy) + m_iplist.RemoveAll(); + m_iplist.Append(newList); + newList.RemoveAll(); + m_bModified = true; + } + delete[] pcToDelete; + } + + if (thePrefs.GetVerbose()) + { + DWORD dwEnd = GetTickCount(); + AddDebugLogLine(false, _T("Loaded IP filters from \"%s\""), pszFilePath); + AddDebugLogLine(false, _T("Parsed lines/entries:%u Found IP ranges:%u Duplicate:%u Merged:%u Time:%s"), iLine, iFoundRanges, iDuplicate, iMerged, CastSecondsToHM((dwEnd-dwStart+500)/1000)); + } + AddLogLine(bShowResponse, GetResString(IDS_IPFILTERLOADED), m_iplist.GetCount()); + } + return m_iplist.GetCount(); +} + +void CIPFilter::SaveToDefaultFile() +{ + CString strFilePath = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + DFLT_IPFILTER_FILENAME; + FILE* fp = _tfsopen(strFilePath, _T("wt"), _SH_DENYWR); + if (fp != NULL) + { + for (int i = 0; i < m_iplist.GetCount(); i++) + { + const SIPFilter* flt = m_iplist[i]; + + CHAR szStart[16]; + ipstrA(szStart, _countof(szStart), htonl(flt->start)); + + CHAR szEnd[16]; + ipstrA(szEnd, _countof(szEnd), htonl(flt->end)); + + if (fprintf(fp, "%-15s - %-15s , %3u , %s\n", szStart, szEnd, flt->level, flt->desc) == 0 || ferror(fp)) + { + fclose(fp); + CString strError; + strError.Format(_T("Failed to save IP filter to file \"%s\" - %s"), strFilePath, _tcserror(errno)); + throw strError; + } + } + fclose(fp); + m_bModified = false; + } + else + { + CString strError; + strError.Format(_T("Failed to save IP filter to file \"%s\" - %s"), strFilePath, _tcserror(errno)); + throw strError; + } +} + +bool CIPFilter::ParseFilterLine1(const CStringA& sbuffer, uint32& ip1, uint32& ip2, UINT& level, CStringA& desc) const +{ + UINT u1, u2, u3, u4, u5, u6, u7, u8, uLevel = DFLT_FILTER_LEVEL; + int iDescStart = 0; + int iItems = sscanf(sbuffer, "%u.%u.%u.%u - %u.%u.%u.%u , %u , %n", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8, &uLevel, &iDescStart); + if (iItems < 8) + return false; + + ((BYTE*)&ip1)[0] = (BYTE)u4; + ((BYTE*)&ip1)[1] = (BYTE)u3; + ((BYTE*)&ip1)[2] = (BYTE)u2; + ((BYTE*)&ip1)[3] = (BYTE)u1; + + ((BYTE*)&ip2)[0] = (BYTE)u8; + ((BYTE*)&ip2)[1] = (BYTE)u7; + ((BYTE*)&ip2)[2] = (BYTE)u6; + ((BYTE*)&ip2)[3] = (BYTE)u5; + + if (iItems == 8) { + level = DFLT_FILTER_LEVEL; // set default level + return true; + } + + level = uLevel; + + if (iDescStart > 0) + { + LPCSTR pszDescStart = (LPCSTR)sbuffer + iDescStart; + int iDescLen = sbuffer.GetLength() - iDescStart; + if (iDescLen > 0) { + if (*(pszDescStart + iDescLen - 1) == '\n') + --iDescLen; + } + memcpy(desc.GetBuffer(iDescLen), pszDescStart, iDescLen * sizeof(pszDescStart[0])); + desc.ReleaseBuffer(iDescLen); + } + + return true; +} + +bool CIPFilter::ParseFilterLine2(const CStringA& sbuffer, uint32& ip1, uint32& ip2, UINT& level, CStringA& desc) const +{ + int iPos = sbuffer.ReverseFind(':'); + if (iPos < 0) + return false; + + desc = sbuffer.Left(iPos); + desc.Replace("PGIPDB", ""); + desc.Trim(); + + CStringA strIPRange = sbuffer.Mid(iPos + 1, sbuffer.GetLength() - iPos); + UINT u1, u2, u3, u4, u5, u6, u7, u8; + if (sscanf(strIPRange, "%u.%u.%u.%u - %u.%u.%u.%u", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) != 8) + return false; + + ((BYTE*)&ip1)[0] = (BYTE)u4; + ((BYTE*)&ip1)[1] = (BYTE)u3; + ((BYTE*)&ip1)[2] = (BYTE)u2; + ((BYTE*)&ip1)[3] = (BYTE)u1; + + ((BYTE*)&ip2)[0] = (BYTE)u8; + ((BYTE*)&ip2)[1] = (BYTE)u7; + ((BYTE*)&ip2)[2] = (BYTE)u6; + ((BYTE*)&ip2)[3] = (BYTE)u5; + + level = DFLT_FILTER_LEVEL; + + return true; +} + +void CIPFilter::RemoveAllIPFilters() +{ + for (int i = 0; i < m_iplist.GetCount(); i++) + delete m_iplist[i]; + m_iplist.RemoveAll(); + m_pLastHit = NULL; +} + +bool CIPFilter::IsFiltered(uint32 ip) /*const*/ +{ + return IsFiltered(ip, thePrefs.GetIPFilterLevel()); +} + +static int __cdecl CmpSIPFilterByAddr(const void* pvKey, const void* pvElement) +{ + uint32 ip = *(uint32*)pvKey; + const SIPFilter* pIPFilter = *(SIPFilter**)pvElement; + + if (ip < pIPFilter->start) + return -1; + if (ip > pIPFilter->end) + return 1; + return 0; +} + +bool CIPFilter::IsFiltered(uint32 ip, UINT level) /*const*/ +{ + if (m_iplist.GetCount() == 0 || ip == 0) + return false; + + ip = htonl(ip); + + // to speed things up we use a binary search + // *) the IP filter list must be sorted by IP range start addresses + // *) the IP filter list is not allowed to contain overlapping IP ranges (see also the IP range merging code when + // loading the list) + // *) the filter 'level' is ignored during the binary search and is evaluated only for the found element + // + // TODO: this can still be improved even more: + // *) use a pre assembled list of IP ranges which contains only the IP ranges for the currently used filter level + // *) use a dumb plain array for storing the IP range structures. this will give more cach hits when processing + // the list. but(!) this would require to also use a dumb SIPFilter structure (don't use data items with ctors). + // otherwise the creation of the array would be rather slow. + SIPFilter** ppFound = (SIPFilter**)bsearch(&ip, m_iplist.GetData(), m_iplist.GetCount(), sizeof(m_iplist[0]), CmpSIPFilterByAddr); + if (ppFound && (*ppFound)->level < level) + { + (*ppFound)->hits++; + m_pLastHit = *ppFound; + return true; + } + + return false; +} + +CString CIPFilter::GetLastHit() const +{ + return m_pLastHit ? CString(m_pLastHit->desc) : _T("Not available"); +} + +const CIPFilterArray& CIPFilter::GetIPFilter() const +{ + return m_iplist; +} + +bool CIPFilter::RemoveIPFilter(const SIPFilter* pFilter) +{ + for (int i = 0; i < m_iplist.GetCount(); i++) + { + if (m_iplist[i] == pFilter) + { + delete m_iplist[i]; + m_iplist.RemoveAt(i); + return true; + } + } + return false; +} diff --git a/IPFilter.h b/IPFilter.h new file mode 100644 index 00000000..db9d29b3 --- /dev/null +++ b/IPFilter.h @@ -0,0 +1,73 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +struct SIPFilter +{ + SIPFilter(uint32 newStart, uint32 newEnd, UINT newLevel, const CStringA& newDesc) + : start(newStart), + end(newEnd), + level(newLevel), + desc(newDesc), + hits(0) + { } + uint32 start; + uint32 end; + UINT level; + CStringA desc; + UINT hits; +}; + +#define DFLT_IPFILTER_FILENAME _T("ipfilter.dat") + +// 'CArray' would give us more cach hits, but would also be slow in array element creation +// (because of the implicit ctor in 'SIPFilter' +//typedef CArray CIPFilterArray; +typedef CTypedPtrArray CIPFilterArray; + +class CIPFilter +{ +public: + CIPFilter(); + ~CIPFilter(); + + CString GetDefaultFilePath() const; + + void AddIPRange(uint32 start, uint32 end, UINT level, const CStringA& rstrDesc) { + m_iplist.Add(new SIPFilter(start, end, level, rstrDesc)); + } + void RemoveAllIPFilters(); + bool RemoveIPFilter(const SIPFilter* pFilter); + void SetModified(bool bModified = true) { m_bModified = bModified; } + + int AddFromFile(LPCTSTR pszFilePath, bool bShowResponse = true); + int LoadFromDefaultFile(bool bShowResponse = true); + void SaveToDefaultFile(); + + bool IsFiltered(uint32 IP) /*const*/; + bool IsFiltered(uint32 IP, UINT level) /*const*/; + CString GetLastHit() const; + const CIPFilterArray& GetIPFilter() const; + +private: + const SIPFilter* m_pLastHit; + CIPFilterArray m_iplist; + bool m_bModified; + + bool ParseFilterLine1(const CStringA& rstrBuffer, uint32& ip1, uint32& ip2, UINT& level, CStringA& rstrDesc) const; + bool ParseFilterLine2(const CStringA& rstrBuffer, uint32& ip1, uint32& ip2, UINT& level, CStringA& rstrDesc) const; +}; diff --git a/IPFilterDlg.cpp b/IPFilterDlg.cpp new file mode 100644 index 00000000..dad4ad9a --- /dev/null +++ b/IPFilterDlg.cpp @@ -0,0 +1,615 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "emuleDlg.h" +#include "ServerWnd.h" +#include "ServerListCtrl.h" +#include "IPFilterDlg.h" +#include "IPFilter.h" +#include "OtherFunctions.h" +#include "Preferences.h" +#include "MenuCmds.h" +#include "ZipFile.h" +#include "GZipFile.h" +#include "RarFile.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +////////////////////////////////////////////////////////////////////////////// +// COLUMN_INIT -- List View Columns + +enum EIPFilterCols +{ + IPFILTER_COL_START = 0, + IPFILTER_COL_END, + IPFILTER_COL_LEVEL, + IPFILTER_COL_HITS, + IPFILTER_COL_DESC +}; + +static LCX_COLUMN_INIT s_aColumns[] = +{ + { IPFILTER_COL_START, _T("Start"), IDS_IP_START, LVCFMT_LEFT, -1, 0, ASCENDING, NONE, _T("255.255.255.255") }, + { IPFILTER_COL_END, _T("End"), IDS_IP_END, LVCFMT_LEFT, -1, 1, ASCENDING, NONE, _T("255.255.255.255")}, + { IPFILTER_COL_LEVEL, _T("Level"), IDS_IP_LEVEL, LVCFMT_RIGHT, -1, 2, ASCENDING, NONE, _T("999") }, + { IPFILTER_COL_HITS, _T("Hits"), IDS_IP_HITS, LVCFMT_RIGHT, -1, 3, DESCENDING, NONE, _T("99999") }, + { IPFILTER_COL_DESC, _T("Description"), IDS_IP_DESC, LVCFMT_LEFT, -1, 4, ASCENDING, NONE, _T("long long long long long long long long file name") }, +}; + +#define PREF_INI_SECTION _T("IPFilterDlg") + +int CIPFilterDlg::sm_iSortColumn = IPFILTER_COL_HITS; + +IMPLEMENT_DYNAMIC(CIPFilterDlg, CDialog) + +BEGIN_MESSAGE_MAP(CIPFilterDlg, CResizableDialog) + ON_BN_CLICKED(IDC_APPEND, OnBnClickedAppend) + ON_BN_CLICKED(IDC_REMOVE, OnBnClickedDelete) + ON_BN_CLICKED(IDC_SAVE, OnBnClickedSave) + ON_COMMAND(MP_COPYSELECTED, OnCopyIPFilter) + ON_COMMAND(MP_FIND, OnFind) + ON_COMMAND(MP_REMOVE, OnDeleteIPFilter) + ON_COMMAND(MP_SELECTALL, OnSelectAllIPFilter) + ON_NOTIFY(LVN_COLUMNCLICK, IDC_IPFILTER, OnLvnColumnClickIPFilter) + ON_NOTIFY(LVN_DELETEITEM, IDC_IPFILTER, OnLvnDeleteItemIPFilter) + ON_NOTIFY(LVN_GETDISPINFO, IDC_IPFILTER, OnLvnGetDispInfoIPFilter) + ON_NOTIFY(LVN_KEYDOWN, IDC_IPFILTER, OnLvnKeyDownIPFilter) + ON_WM_DESTROY() +END_MESSAGE_MAP() + +CIPFilterDlg::CIPFilterDlg(CWnd* pParent /*=NULL*/) + : CResizableDialog(CIPFilterDlg::IDD, pParent) +{ + m_uIPFilterItems = 0; + m_ppIPFilterItems = NULL; + m_icoDlg = NULL; + m_pMenuIPFilter = NULL; + m_ulFilteredIPs = 0; + m_ipfilter.m_pParent = this; + m_ipfilter.SetRegistryKey(PREF_INI_SECTION); + m_ipfilter.SetRegistryPrefix(_T("IPfilters_")); + m_ipfilter.m_pfnFindItem = FindItem; + m_ipfilter.m_lFindItemParam = (DWORD_PTR)this; +} + +CIPFilterDlg::~CIPFilterDlg() +{ + free(m_ppIPFilterItems); + delete m_pMenuIPFilter; + sm_iSortColumn = m_ipfilter.GetSortColumn(); + if (m_icoDlg) + VERIFY( ::DestroyIcon(m_icoDlg) ); +} + +void CIPFilterDlg::DoDataExchange(CDataExchange* pDX) +{ + CResizableDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_IPFILTER, m_ipfilter); +} + +static int s_lParamSort = 0; + +int __cdecl CompareIPFilterItems(const void* lParam1, const void* lParam2) +{ +#define COMPARE_NUM( a, b ) ((a) < (b)) \ + ? -1 \ + : ( ((b) < (a)) \ + ? 1 \ + : 0 \ + ) + + int iResult; + + if (s_lParamSort == IPFILTER_COL_START) + { + iResult = COMPARE_NUM((*((const SIPFilter**)lParam1))->start, (*((const SIPFilter**)lParam2))->start); + } + else if (s_lParamSort == IPFILTER_COL_END) + { + iResult = COMPARE_NUM((*((const SIPFilter**)lParam1))->end, (*((const SIPFilter**)lParam2))->end); + } + else if (s_lParamSort == IPFILTER_COL_LEVEL) + { + iResult = COMPARE_NUM((*((const SIPFilter**)lParam1))->level, (*((const SIPFilter**)lParam2))->level); + } + else if (s_lParamSort == IPFILTER_COL_HITS) + { + iResult = COMPARE_NUM((*((const SIPFilter**)lParam1))->hits, (*((const SIPFilter**)lParam2))->hits); + } + else if (s_lParamSort == IPFILTER_COL_DESC) + { + iResult = _stricmp/*CompareLocaleStringNoCase*/((*((const SIPFilter**)lParam1))->desc, (*((const SIPFilter**)lParam2))->desc); + } + else + { + ASSERT(0); + iResult = 0; + } + +#undef COMPARE_NUM + + if (s_aColumns[s_lParamSort].eSortOrder == DESCENDING) + return -iResult; + else + return iResult; +} + +void CIPFilterDlg::SortIPFilterItems() +{ + // Update (sort, if needed) the listview items + if (m_ipfilter.GetSortColumn() != -1) + { + s_lParamSort = m_ipfilter.GetSortColumn(); + qsort((void*)m_ppIPFilterItems, m_uIPFilterItems, sizeof(*m_ppIPFilterItems), CompareIPFilterItems); + } +} + +void CIPFilterDlg::OnLvnColumnClickIPFilter(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMLISTVIEW pNMLV = reinterpret_cast(pNMHDR); + m_ipfilter.UpdateSortOrder(pNMLV, ARRSIZE(s_aColumns), s_aColumns); + SortIPFilterItems(); + m_ipfilter.Invalidate(); + m_ipfilter.UpdateWindow(); + *pResult = 0; +} + +BOOL CIPFilterDlg::OnInitDialog() +{ + CResizableDialog::OnInitDialog(); + InitWindowStyles(this); + + AddAnchor(IDC_IPFILTER, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_TOTAL_IPFILTER_LABEL, BOTTOM_LEFT); + AddAnchor(IDC_TOTAL_IPFILTER, BOTTOM_LEFT); + AddAnchor(IDC_TOTAL_IPS_LABEL, BOTTOM_LEFT); + AddAnchor(IDC_TOTAL_IPS, BOTTOM_LEFT); + AddAnchor(IDC_REMOVE, BOTTOM_RIGHT); + AddAnchor(IDC_APPEND, BOTTOM_RIGHT); + AddAnchor(IDC_SAVE, BOTTOM_RIGHT); + AddAnchor(IDOK, BOTTOM_RIGHT); + EnableSaveRestore(PREF_INI_SECTION); + + ASSERT( m_ipfilter.GetStyle() & LVS_OWNERDATA ); + // Win98: Explicitly set to Unicode to receive Unicode notifications. + m_ipfilter.SendMessage(CCM_SETUNICODEFORMAT, TRUE); + m_ipfilter.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_GRIDLINES); + m_ipfilter.EnableHdrCtrlSortBitmaps(); + m_ipfilter.ReadColumnStats(ARRSIZE(s_aColumns), s_aColumns); + m_ipfilter.CreateColumns(ARRSIZE(s_aColumns), s_aColumns); + m_ipfilter.InitColumnOrders(ARRSIZE(s_aColumns), s_aColumns); + m_ipfilter.UpdateSortColumn(ARRSIZE(s_aColumns), s_aColumns); + + SetIcon(m_icoDlg = theApp.LoadIcon(_T("IPFilter")), FALSE); + + InitIPFilters(); + + m_pMenuIPFilter = new CMenu(); + if (m_pMenuIPFilter->CreatePopupMenu()) + { + m_pMenuIPFilter->AppendMenu(MF_ENABLED | MF_STRING, MP_COPYSELECTED, GetResString(IDS_COPY)); + m_pMenuIPFilter->AppendMenu(MF_ENABLED | MF_STRING, MP_REMOVE, GetResString(IDS_REMOVE)); + m_pMenuIPFilter->AppendMenu(MF_SEPARATOR); + m_pMenuIPFilter->AppendMenu(MF_ENABLED | MF_STRING, MP_SELECTALL, GetResString(IDS_SELECTALL)); + m_pMenuIPFilter->AppendMenu(MF_SEPARATOR); + m_pMenuIPFilter->AppendMenu(MF_ENABLED | MF_STRING, MP_FIND, GetResString(IDS_FIND)); + } + m_ipfilter.m_pMenu = m_pMenuIPFilter; + m_ipfilter.m_pParent = this; + + // localize + SetWindowText(GetResString(IDS_IPFILTER)); + SetDlgItemText(IDC_STATICIPLABEL,GetResString(IDS_IP_RULES)); + SetDlgItemText(IDC_TOTAL_IPFILTER_LABEL,GetResString(IDS_TOTAL_IPFILTER_LABEL)); + SetDlgItemText(IDC_TOTAL_IPS_LABEL,GetResString(IDS_TOTAL_IPS_LABEL)); + SetDlgItemText(IDC_REMOVE,GetResString(IDS_DELETESELECTED)); + SetDlgItemText(IDC_APPEND,GetResString(IDS_APPEND)); + SetDlgItemText(IDC_SAVE,GetResString(IDS_SAVE)); + SetDlgItemText(IDOK,GetResString(IDS_FD_CLOSE)); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CIPFilterDlg::InitIPFilters() +{ + CWaitCursor curWait; + + m_uIPFilterItems = 0; + free(m_ppIPFilterItems); + m_ppIPFilterItems = NULL; + + const CIPFilterArray& ipfilter = theApp.ipfilter->GetIPFilter(); + m_uIPFilterItems = ipfilter.GetCount(); + m_ppIPFilterItems = (const SIPFilter**)malloc(sizeof(*m_ppIPFilterItems) * m_uIPFilterItems); + if (m_ppIPFilterItems == NULL) + m_uIPFilterItems = 0; + + m_ulFilteredIPs = 0; + for (UINT i = 0; i < m_uIPFilterItems; i++) + { + const SIPFilter* pFilter = ipfilter[i]; + m_ppIPFilterItems[i] = pFilter; + m_ulFilteredIPs += pFilter->end - pFilter->start + 1; + } + SortIPFilterItems(); + m_ipfilter.SetItemCount(m_uIPFilterItems); + SetDlgItemText(IDC_TOTAL_IPFILTER, GetFormatedUInt(m_uIPFilterItems)); + SetDlgItemText(IDC_TOTAL_IPS, GetFormatedUInt(m_ulFilteredIPs)); +} + +void CIPFilterDlg::OnLvnGetDispInfoIPFilter(NMHDR *pNMHDR, LRESULT *pResult) +{ + NMLVDISPINFO* pDispInfo = reinterpret_cast(pNMHDR); + if (pDispInfo->item.mask & LVIF_TEXT) // *have* to check that flag!! + { + switch (pDispInfo->item.iSubItem) + { + case IPFILTER_COL_START: + if (pDispInfo->item.cchTextMax > 0){ + _tcsncpy(pDispInfo->item.pszText, ipstr(htonl(m_ppIPFilterItems[pDispInfo->item.iItem]->start)), pDispInfo->item.cchTextMax); + pDispInfo->item.pszText[pDispInfo->item.cchTextMax - 1] = _T('\0'); + } + break; + case IPFILTER_COL_END: + if (pDispInfo->item.cchTextMax > 0){ + _tcsncpy(pDispInfo->item.pszText, ipstr(htonl(m_ppIPFilterItems[pDispInfo->item.iItem]->end)), pDispInfo->item.cchTextMax); + pDispInfo->item.pszText[pDispInfo->item.cchTextMax - 1] = _T('\0'); + } + break; + case IPFILTER_COL_LEVEL: + if (pDispInfo->item.cchTextMax > 0){ + _tcsncpy(pDispInfo->item.pszText, _itot(m_ppIPFilterItems[pDispInfo->item.iItem]->level, pDispInfo->item.pszText, 10), pDispInfo->item.cchTextMax); + pDispInfo->item.pszText[pDispInfo->item.cchTextMax - 1] = _T('\0'); + } + break; + case IPFILTER_COL_HITS: + if (pDispInfo->item.cchTextMax > 0){ + _tcsncpy(pDispInfo->item.pszText, _itot(m_ppIPFilterItems[pDispInfo->item.iItem]->hits, pDispInfo->item.pszText, 10), pDispInfo->item.cchTextMax); + pDispInfo->item.pszText[pDispInfo->item.cchTextMax - 1] = _T('\0'); + } + break; + case IPFILTER_COL_DESC: + if (pDispInfo->item.cchTextMax > 0){ + _tcsncpy(pDispInfo->item.pszText, CA2T(m_ppIPFilterItems[pDispInfo->item.iItem]->desc), pDispInfo->item.cchTextMax); + pDispInfo->item.pszText[pDispInfo->item.cchTextMax - 1] = _T('\0'); + } + break; + } + } + *pResult = 0; +} + +void CIPFilterDlg::OnCopyIPFilter() +{ + CWaitCursor curWait; + int iSelected = 0; + CString strData; + POSITION pos = m_ipfilter.GetFirstSelectedItemPosition(); + while (pos) + { + int iItem = m_ipfilter.GetNextSelectedItem(pos); + if (!strData.IsEmpty()) + strData += _T("\r\n"); + + strData.AppendFormat(_T("%-15s - %-15s Hits=%-5s %s"), + m_ipfilter.GetItemText(iItem, IPFILTER_COL_START), + m_ipfilter.GetItemText(iItem, IPFILTER_COL_END), + m_ipfilter.GetItemText(iItem, IPFILTER_COL_HITS), + m_ipfilter.GetItemText(iItem, IPFILTER_COL_DESC)); + iSelected++; + } + + if (!strData.IsEmpty()) + { + if (iSelected > 1) + strData += _T("\r\n"); + theApp.CopyTextToClipboard(strData); + } +} + +void CIPFilterDlg::OnSelectAllIPFilter() +{ + m_ipfilter.SelectAllItems(); +} + +void CIPFilterDlg::OnBnClickedAppend() +{ + // Save/Restore the current directory + TCHAR szCurDir[MAX_PATH]; + DWORD dwCurDirLen = GetCurrentDirectory(_countof(szCurDir), szCurDir); + if (dwCurDirLen == 0 || dwCurDirLen >= _countof(szCurDir)) + szCurDir[0] = _T('\0'); + + CString strFilePath; // Do NOT localize that string + if (DialogBrowseFile(strFilePath, _T("All IP Filter Files (*ipfilter.dat;*ip.prefix;*.p2b;*.p2p;*.p2p.txt;*.zip;*.gz;*.rar)|*ipfilter.dat;*ip.prefix;*.p2b;*.p2p;*.p2p.txt;*.zip;*.gz;*.rar|eMule IP Filter Files (*ipfilter.dat;*ip.prefix)|*ipfilter.dat;*ip.prefix|Peer Guardian Files (*.p2b;*.p2p;*.p2p.txt)|*.p2b;*.p2p;*.p2p.txt|Text Files (*.txt)|*.txt|ZIP Files (*.zip;*.gz)|*.zip;*.gz|RAR Files (*.rar)|*.rar|All Files (*.*)|*.*||"))) + { + CWaitCursor curWait; + + TCHAR szExt[_MAX_EXT]; + _tsplitpath(strFilePath, NULL, NULL, NULL, szExt); + _tcslwr(szExt); + bool bIsArchiveFile = _tcscmp(szExt, _T(".zip"))==0 || _tcscmp(szExt, _T(".rar"))==0 || _tcscmp(szExt, _T(".gz"))==0; + bool bExtractedArchive = false; + + CString strTempUnzipFilePath; + if (_tcscmp(szExt, _T(".zip")) == 0) + { + CZIPFile zip; + if (zip.Open(strFilePath)) + { + CZIPFile::File* zfile = zip.GetFile(_T("ipfilter.dat")); + if (zfile == NULL) + zfile = zip.GetFile(_T("guarding.p2p")); + if (zfile == NULL) + zfile = zip.GetFile(_T("guardian.p2p")); + if (zfile) + { + _tmakepathlimit(strTempUnzipFilePath.GetBuffer(MAX_PATH), NULL, thePrefs.GetMuleDirectory(EMULE_CONFIGDIR), DFLT_IPFILTER_FILENAME, _T(".unzip.tmp")); + strTempUnzipFilePath.ReleaseBuffer(); + if (zfile->Extract(strTempUnzipFilePath)) + { + strFilePath = strTempUnzipFilePath; + bExtractedArchive = true; + } + else + { + CString strError; + strError.Format(_T("Failed to extract IP filter file from ZIP file \"%s\"."), strFilePath); + AfxMessageBox(strError, MB_ICONERROR); + } + } + else + { + CString strError; + strError.Format(_T("Failed to find IP filter file \"guarding.p2p\", \"guardian.p2p\" or \"ipfilter.dat\" in ZIP file \"%s\"."), strFilePath); + AfxMessageBox(strError, MB_ICONERROR); + } + zip.Close(); + } + else + { + CString strError; + strError.Format(_T("Failed to open file \"%s\".\r\n\r\nInvalid file format?"), strFilePath); + AfxMessageBox(strError, MB_ICONERROR); + } + } + else if (_tcscmp(szExt, _T(".rar")) == 0) + { + CRARFile rar; + if (rar.Open(strFilePath)) + { + CString strFile; + if (rar.GetNextFile(strFile) + && ( strFile.CompareNoCase(_T("ipfilter.dat")) == 0 + || strFile.CompareNoCase(_T("guarding.p2p")) == 0 + || strFile.CompareNoCase(_T("guardian.p2p")) == 0)) + { + _tmakepathlimit(strTempUnzipFilePath.GetBuffer(MAX_PATH), NULL, thePrefs.GetMuleDirectory(EMULE_CONFIGDIR), DFLT_IPFILTER_FILENAME, _T(".unzip.tmp")); + strTempUnzipFilePath.ReleaseBuffer(); + if (rar.Extract(strTempUnzipFilePath)) + { + strFilePath = strTempUnzipFilePath; + bExtractedArchive = true; + } + else + { + CString strError; + strError.Format(_T("Failed to extract IP filter file from RAR file \"%s\"."), strFilePath); + AfxMessageBox(strError, MB_ICONERROR); + } + } + else + { + CString strError; + strError.Format(_T("Failed to find IP filter file \"guarding.p2p\", \"guardian.p2p\" or \"ipfilter.dat\" in RAR file \"%s\"."), strFilePath); + AfxMessageBox(strError, MB_ICONERROR); + } + rar.Close(); + } + else + { + CString strError; + strError.Format(_T("Failed to open file \"%s\".\r\n\r\nInvalid file format?\r\n\r\nDownload latest version of UNRAR.DLL from http://www.rarlab.com and copy UNRAR.DLL into eMule installation folder."), strFilePath); + AfxMessageBox(strError, MB_ICONERROR); + } + } + else if (_tcscmp(szExt, _T(".gz")) == 0) + { + CGZIPFile gz; + if (gz.Open(strFilePath)) + { + _tmakepathlimit(strTempUnzipFilePath.GetBuffer(MAX_PATH), NULL, thePrefs.GetMuleDirectory(EMULE_CONFIGDIR), DFLT_IPFILTER_FILENAME, _T(".unzip.tmp")); + strTempUnzipFilePath.ReleaseBuffer(); + + // add filename and extension of uncompressed file to temporary file + CString strUncompressedFileName = gz.GetUncompressedFileName(); + if (!strUncompressedFileName.IsEmpty()) + { + strTempUnzipFilePath += _T('.'); + strTempUnzipFilePath += strUncompressedFileName; + } + + if (gz.Extract(strTempUnzipFilePath)) + { + strFilePath = strTempUnzipFilePath; + bExtractedArchive = true; + } + gz.Close(); + } + else + { + CString strError; + strError.Format(_T("Failed to open file \"%s\".\r\n\r\nInvalid file format?"), strFilePath); + AfxMessageBox(strError, MB_ICONERROR); + } + } + + if ((!bIsArchiveFile || bExtractedArchive) && theApp.ipfilter->AddFromFile(strFilePath, true)) + { + if (thePrefs.GetFilterServerByIP()) + theApp.emuledlg->serverwnd->serverlistctrl.RemoveAllFilteredServers(); + InitIPFilters(); + m_ipfilter.Update(-1); + } + + if (!strTempUnzipFilePath.IsEmpty()) + VERIFY( _tremove(strTempUnzipFilePath) == 0); + } + + if (szCurDir[0] != _T('\0')) + VERIFY( SetCurrentDirectory(szCurDir) ); +} + +void CIPFilterDlg::OnLvnDeleteItemIPFilter(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMLISTVIEW pNMLV = reinterpret_cast(pNMHDR); + + ASSERT( m_uIPFilterItems > 0 ); + if (m_uIPFilterItems > 0) + { + ASSERT( (UINT)pNMLV->iItem < m_uIPFilterItems ); + if ((UINT)pNMLV->iItem < m_uIPFilterItems - 1) + memmove(m_ppIPFilterItems + pNMLV->iItem, m_ppIPFilterItems + pNMLV->iItem + 1, (m_uIPFilterItems - (pNMLV->iItem + 1)) * sizeof(*m_ppIPFilterItems)); + m_uIPFilterItems--; + const SIPFilter** ppNewIPFilterItems = (const SIPFilter**)realloc(m_ppIPFilterItems, sizeof(*m_ppIPFilterItems) * m_uIPFilterItems); + if (ppNewIPFilterItems != NULL) + m_ppIPFilterItems = ppNewIPFilterItems; + else + ; // Keep the old list + } + + *pResult = 0; +} + +void CIPFilterDlg::OnBnClickedDelete() +{ + if (m_ipfilter.GetSelectedCount() == 0) + return; + if (AfxMessageBox(GetResString(IDS_DELETEIPFILTERS), MB_YESNOCANCEL) != IDYES) + return; + + CWaitCursor curWait; + + if (m_ipfilter.GetSelectedCount() == m_uIPFilterItems) + { + theApp.ipfilter->RemoveAllIPFilters(); + theApp.ipfilter->SetModified(); + m_uIPFilterItems = 0; + free(m_ppIPFilterItems); + m_ppIPFilterItems = NULL; + m_ipfilter.SetItemCount(m_uIPFilterItems); + m_ulFilteredIPs = 0; + } + else + { + CUIntArray aItems; + POSITION pos = m_ipfilter.GetFirstSelectedItemPosition(); + while (pos) + { + int iItem = m_ipfilter.GetNextSelectedItem(pos); + const SIPFilter* pFilter = m_ppIPFilterItems[iItem]; + if (pFilter) + { + ULONG ulIPRange = pFilter->end - pFilter->start + 1; + if (theApp.ipfilter->RemoveIPFilter(pFilter)) + { + theApp.ipfilter->SetModified(); + aItems.Add(iItem); + m_ulFilteredIPs -= ulIPRange; + } + } + } + + m_ipfilter.SetRedraw(FALSE); + for (int i = aItems.GetCount() - 1; i >= 0; i--) + m_ipfilter.DeleteItem(aItems[i]); + if (aItems.GetCount() > 0) + { + int iNextSelItem = aItems[0]; + if (iNextSelItem >= m_ipfilter.GetItemCount()) + iNextSelItem--; + if (iNextSelItem >= 0) + { + m_ipfilter.SetItemState(iNextSelItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + m_ipfilter.SetSelectionMark(iNextSelItem); + } + } + m_ipfilter.SetRedraw(); + } + ASSERT( m_uIPFilterItems == (UINT)m_ipfilter.GetItemCount() ); + SetDlgItemText(IDC_TOTAL_IPFILTER, GetFormatedUInt(m_ipfilter.GetItemCount())); + SetDlgItemText(IDC_TOTAL_IPS, GetFormatedUInt(m_ulFilteredIPs)); +} + +void CIPFilterDlg::OnDeleteIPFilter() +{ + OnBnClickedDelete(); +} + +void CIPFilterDlg::OnLvnKeyDownIPFilter(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMLVKEYDOWN pLVKeyDow = reinterpret_cast(pNMHDR); + + if (pLVKeyDow->wVKey == VK_DELETE) + OnDeleteIPFilter(); + else if (pLVKeyDow->wVKey == 'C' && (GetKeyState(VK_CONTROL) & 0x8000)) + OnCopyIPFilter(); + *pResult = 0; +} + +void CIPFilterDlg::OnDestroy() +{ + m_ipfilter.WriteColumnStats(ARRSIZE(s_aColumns), s_aColumns); + CResizableDialog::OnDestroy(); +} + +void CIPFilterDlg::OnBnClickedSave() +{ + CWaitCursor curWait; + try + { + theApp.ipfilter->SaveToDefaultFile(); + } + catch(CString err) + { + AfxMessageBox(err, MB_ICONERROR); + } +} + +bool CIPFilterDlg::FindItem(const CListCtrlX& lv, int iItem, DWORD_PTR lParam) +{ + if (lv.GetFindColumn() != IPFILTER_COL_START) + return CListCtrlX::FindItem(lv, iItem, lParam); + + const CIPFilterDlg* dlg = reinterpret_cast(lParam); + ASSERT_VALID(dlg); + u_long ip = htonl(inet_addr(CT2A(lv.GetFindText()))); + const SIPFilter* filter = dlg->m_ppIPFilterItems[iItem]; + return (ip >= filter->start && ip <= filter->end); +} + +void CIPFilterDlg::OnFind() +{ + m_ipfilter.OnFindStart(); +} diff --git a/IPFilterDlg.h b/IPFilterDlg.h new file mode 100644 index 00000000..d932353a --- /dev/null +++ b/IPFilterDlg.h @@ -0,0 +1,48 @@ +#pragma once +#include "ResizableLib/ResizableDialog.h" +#include "ListCtrlX.h" + +struct SIPFilter; + +class CIPFilterDlg : public CResizableDialog +{ + DECLARE_DYNAMIC(CIPFilterDlg) + +public: + CIPFilterDlg(CWnd* pParent = NULL); // standard constructor + virtual ~CIPFilterDlg(); + +// Dialog Data + enum { IDD = IDD_IPFILTER }; + +protected: + static int sm_iSortColumn; + CMenu* m_pMenuIPFilter; + CListCtrlX m_ipfilter; + HICON m_icoDlg; + UINT m_uIPFilterItems; + const SIPFilter** m_ppIPFilterItems; + ULONG m_ulFilteredIPs; + + void SortIPFilterItems(); + void InitIPFilters(); + static bool FindItem(const CListCtrlX& lv, int iItem, DWORD_PTR lParam); + + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + + DECLARE_MESSAGE_MAP() + afx_msg void OnDestroy(); + afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/); + afx_msg void OnLvnColumnClickIPFilter(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnKeyDownIPFilter(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnBnClickedAppend(); + afx_msg void OnBnClickedDelete(); + afx_msg void OnBnClickedSave(); + afx_msg void OnCopyIPFilter(); + afx_msg void OnDeleteIPFilter(); + afx_msg void OnSelectAllIPFilter(); + afx_msg void OnFind(); + afx_msg void OnLvnGetDispInfoIPFilter(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnDeleteItemIPFilter(NMHDR *pNMHDR, LRESULT *pResult); +}; diff --git a/IconStatic.cpp b/IconStatic.cpp new file mode 100644 index 00000000..6bf817ab --- /dev/null +++ b/IconStatic.cpp @@ -0,0 +1,165 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "emule.h" +#include "IconStatic.h" +#include "VisualStylesXP.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +///////////////////////////////////////////////////////////////////////////// +// CIconStatic + +IMPLEMENT_DYNAMIC(CIconStatic, CStatic) + +BEGIN_MESSAGE_MAP(CIconStatic, CStatic) + //ON_WM_SYSCOLORCHANGE() +END_MESSAGE_MAP() + +CIconStatic::CIconStatic() +{ +} + +CIconStatic::~CIconStatic() +{ + m_MemBMP.DeleteObject(); +} + +void CIconStatic::SetWindowText(LPCTSTR pszText) +{ + m_strText = pszText; + SetIcon(m_strIconID); +} + +void CIconStatic::SetIcon(LPCTSTR pszIconID) +{ + m_strIconID = pszIconID; + + // If this function is called for the first time and we did not yet call 'SetWindowText', we take + // the window label which is already specified for the window (the label which comes from the resource) + CString strText; + CStatic::GetWindowText(strText); + CStatic::SetWindowText(_T("")); + if (!strText.IsEmpty() && m_strText.IsEmpty()) + m_strText = strText; + + CRect rRect; + GetClientRect(rRect); + + CDC *pDC = GetDC(); + CDC MemDC; + CBitmap *pOldBMP; + + VERIFY( MemDC.CreateCompatibleDC(pDC) ); + + CFont *pOldFont = MemDC.SelectObject(GetFont()); + + CRect rCaption(0,0,0,0); + MemDC.DrawText(m_strText, rCaption, DT_CALCRECT); + ASSERT( rCaption.Width() >= 0 ); + ASSERT( rCaption.Height() >= 0 ); + if (rCaption.Height() < 16) + rCaption.bottom = rCaption.top + 16; + rCaption.right += 25; + if (rRect.Width() >= 16 && rCaption.Width() > rRect.Width() - 16) + rCaption.right = rCaption.left + rRect.Width() - 16; + + if (m_MemBMP.m_hObject) + VERIFY( m_MemBMP.DeleteObject() ); + VERIFY( m_MemBMP.CreateCompatibleBitmap(pDC, rCaption.Width(), rCaption.Height()) ); + pOldBMP = MemDC.SelectObject(&m_MemBMP); + + // Get the background color from the parent window. This way the controls which are + // embedded in a dialog window can get painted with the same background color as + // the dialog window. + HBRUSH hbr = (HBRUSH)GetParent()->SendMessage(WM_CTLCOLORSTATIC, (WPARAM)MemDC.m_hDC, (LPARAM)m_hWnd); + FillRect(MemDC, &rCaption, hbr); + + if (!m_strIconID.IsEmpty()) + VERIFY( DrawState( MemDC.m_hDC, NULL, NULL, (LPARAM)(HICON)CTempIconLoader(m_strIconID, 16, 16), NULL, 3, 0, 16, 16, DST_ICON | DSS_NORMAL) ); + + // clear all alpha channel data + BITMAP bmMem; + if (m_MemBMP.GetObject(sizeof bmMem, &bmMem) >= sizeof bmMem && bmMem.bmBitsPixel == 32) + { + DWORD dwSize = m_MemBMP.GetBitmapBits(0, NULL); + if (dwSize) + { + LPBYTE pPixels = (LPBYTE)malloc(dwSize); + if (pPixels) + { + if (m_MemBMP.GetBitmapBits(dwSize, pPixels) == dwSize) + { + LPBYTE pLine = pPixels; + int iLines = bmMem.bmHeight; + while (iLines-- > 0) + { + LPDWORD pdwPixel = (LPDWORD)pLine; + for (int x = 0; x < bmMem.bmWidth; x++) + *pdwPixel++ &= 0x00FFFFFF; + pLine += bmMem.bmWidthBytes; + } + m_MemBMP.SetBitmapBits(dwSize, pPixels); + } + free(pPixels); + } + } + } + + rCaption.left += 22; + + if(g_xpStyle.IsThemeActive() && g_xpStyle.IsAppThemed()) + { + HTHEME hTheme = g_xpStyle.OpenThemeData(NULL, L"BUTTON"); + g_xpStyle.DrawThemeText(hTheme, MemDC.m_hDC, BP_GROUPBOX, GBS_NORMAL, m_strText, m_strText.GetLength(), + DT_WORDBREAK | DT_CENTER | DT_WORD_ELLIPSIS, NULL, &rCaption); + g_xpStyle.CloseThemeData(hTheme); + } + else + { + MemDC.SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); + MemDC.DrawText(m_strText, rCaption, DT_SINGLELINE | DT_LEFT | DT_END_ELLIPSIS); + } + + ReleaseDC(pDC); + + MemDC.SelectObject(pOldBMP); + MemDC.SelectObject(pOldFont); + + if (m_wndPicture.m_hWnd == NULL) + m_wndPicture.Create(NULL, WS_CHILD | WS_VISIBLE | SS_BITMAP, CRect(0,0,0,0), this); + m_wndPicture.SetWindowPos(NULL, rRect.left+8, rRect.top, rCaption.Width()+22, rCaption.Height(), SWP_SHOWWINDOW); + m_wndPicture.SetBitmap(m_MemBMP); + + CRect r; + GetWindowRect(r); + r.bottom = r.top + 20; + GetParent()->ScreenToClient(&r); + GetParent()->RedrawWindow(r); +} + +void CIconStatic::OnSysColorChange() +{ + CStatic::OnSysColorChange(); + if (!m_strIconID.IsEmpty()) + SetIcon(m_strIconID); +} diff --git a/IconStatic.h b/IconStatic.h new file mode 100644 index 00000000..258a245a --- /dev/null +++ b/IconStatic.h @@ -0,0 +1,24 @@ +#pragma once + +///////////////////////////////////////////////////////////////////////////// +// CIconStatic + +class CIconStatic : public CStatic +{ + DECLARE_DYNAMIC(CIconStatic) +public: + CIconStatic(); + virtual ~CIconStatic(); + + void SetIcon(LPCTSTR pszIconID); + void SetWindowText(LPCTSTR lpszString); + +protected: + CStatic m_wndPicture; + CString m_strIconID; + CString m_strText; + CBitmap m_MemBMP; + + DECLARE_MESSAGE_MAP() + afx_msg void OnSysColorChange(); +}; diff --git a/InputBox.cpp b/InputBox.cpp new file mode 100644 index 00000000..1dfc6476 --- /dev/null +++ b/InputBox.cpp @@ -0,0 +1,93 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#include "stdafx.h" +#include "resource.h" +#include "eMule.h" +#include "InputBox.h" +#include "OtherFunctions.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +IMPLEMENT_DYNAMIC(InputBox, CDialog) + +BEGIN_MESSAGE_MAP(InputBox, CDialog) + ON_BN_CLICKED(IDC_CLEANFILENAME, OnCleanFilename) +END_MESSAGE_MAP() + +InputBox::InputBox(CWnd* pParent /*=NULL*/) + : CDialog(InputBox::IDD, pParent) +{ + m_cancel = true; + m_bFilenameMode = false; + m_icMain = NULL; +} + +InputBox::~InputBox() +{ + if (m_icMain) + VERIFY( DestroyIcon(m_icMain) ); +} + +void InputBox::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); +} + +void InputBox::OnOK() +{ + m_cancel = false; + GetDlgItemText(IDC_TEXT, m_return); + m_return.Trim(); + CDialog::OnOK(); +} + +void InputBox::SetLabels(CString title, CString label, CString defaultStr) +{ + m_label = label; + m_title = title; + m_default = defaultStr; +} + +BOOL InputBox::OnInitDialog() +{ + CDialog::OnInitDialog(); + InitWindowStyles(this); + SetIcon( m_icMain = theApp.LoadIcon(_T("RENAME")),FALSE); + + GetDlgItem(IDC_IBLABEL)->SetWindowText(m_label); + GetDlgItem(IDC_TEXT)->SetWindowText(m_default); + SetWindowText(m_title); + + SetDlgItemText(IDOK, GetResString(IDS_TREEOPTIONS_OK) ); + GetDlgItem(IDCANCEL)->SetWindowText(GetResString(IDS_CANCEL)); + SetDlgItemText(IDC_CLEANFILENAME,GetResString(IDS_CLEANUP)); + GetDlgItem(IDC_CLEANFILENAME)->ShowWindow(m_bFilenameMode ? SW_NORMAL : SW_HIDE); + + return TRUE; +} + +void InputBox::OnCleanFilename() +{ + CString filename; + GetDlgItem(IDC_TEXT)->GetWindowText(filename); + GetDlgItem(IDC_TEXT)->SetWindowText(CleanupFilename(filename)); +} diff --git a/InputBox.h b/InputBox.h new file mode 100644 index 00000000..fe4f3d2b --- /dev/null +++ b/InputBox.h @@ -0,0 +1,49 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once + +class InputBox : public CDialog +{ + DECLARE_DYNAMIC(InputBox) +public: + InputBox(CWnd* pParent = NULL); // standard constructor + virtual ~InputBox(); + +// Dialog Data + enum { IDD = IDD_INPUTBOX }; + + void SetLabels(CString title, CString label, CString defaultStr); + const CString& GetInput() const { return m_return; } + bool WasCancelled() const { return m_cancel;} + void SetEditFilenameMode(bool isfilenamemode = true) { m_bFilenameMode = isfilenamemode; } + +protected: + CString m_label; + CString m_title; + CString m_default; + CString m_return; + bool m_cancel; + bool m_bFilenameMode; + HICON m_icMain; + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + + afx_msg void OnOK(); + afx_msg void OnCleanFilename(); + DECLARE_MESSAGE_MAP() +}; diff --git a/IrcChannelListCtrl.cpp b/IrcChannelListCtrl.cpp new file mode 100644 index 00000000..5634fff1 --- /dev/null +++ b/IrcChannelListCtrl.cpp @@ -0,0 +1,263 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#include "StdAfx.h" +#include "IrcChannelListCtrl.h" +#include "emuleDlg.h" +#include "otherfunctions.h" +#include "MenuCmds.h" +#include "IrcWnd.h" +#include "IrcMain.h" +#include "emule.h" +#include "MemDC.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +struct ChannelName +{ + ChannelName(const CString& sName, UINT uUsers, const CString& sDesc) + : m_sName(sName), m_uUsers(uUsers), m_sDesc(sDesc) + { } + CString m_sName; + UINT m_uUsers; + CString m_sDesc; +}; + +IMPLEMENT_DYNAMIC(CIrcChannelListCtrl, CMuleListCtrl) + +BEGIN_MESSAGE_MAP(CIrcChannelListCtrl, CMuleListCtrl) + ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnLvnColumnClick) + ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetDispInfo) + ON_NOTIFY_REFLECT(NM_DBLCLK, OnNmDblClk) + ON_WM_CONTEXTMENU() +END_MESSAGE_MAP() + +CIrcChannelListCtrl::CIrcChannelListCtrl() +{ + m_pParent = NULL; + SetSkinKey(L"IRCChannelsLv"); +} + +CIrcChannelListCtrl::~CIrcChannelListCtrl() +{ + ResetServerChannelList(true); +} + +void CIrcChannelListCtrl::Init() +{ + SetPrefsKey(_T("IrcChannelListCtrl")); + SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + + InsertColumn(0, GetResString(IDS_IRC_NAME), LVCFMT_LEFT, 200); + InsertColumn(1, GetResString(IDS_UUSERS), LVCFMT_RIGHT, 50); + InsertColumn(2, GetResString(IDS_DESCRIPTION), LVCFMT_LEFT, 350); + + LoadSettings(); + SetSortArrow(); + SortItems(&SortProc, GetSortItem() + (GetSortAscending() ? 0 : 10)); +} + +void CIrcChannelListCtrl::Localize() +{ + CHeaderCtrl* pHeaderCtrl = GetHeaderCtrl(); + HDITEM hdi; + hdi.mask = HDI_TEXT; + CString strRes; + + strRes = GetResString(IDS_IRC_NAME); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(0, &hdi); + + strRes = GetResString(IDS_UUSERS); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(1, &hdi); + + strRes = GetResString(IDS_DESCRIPTION); + hdi.pszText = const_cast((LPCTSTR)strRes); + pHeaderCtrl->SetItem(2, &hdi); +} + +void CIrcChannelListCtrl::GetItemDisplayText(const ChannelName *pChannel, int iSubItem, LPTSTR pszText, int cchTextMax) +{ + if (pszText == NULL || cchTextMax <= 0) { + ASSERT(0); + return; + } + pszText[0] = _T('\0'); + switch (iSubItem) + { + case 0: + _tcsncpy(pszText, pChannel->m_sName, cchTextMax); + break; + + case 1: + _sntprintf(pszText, cchTextMax, _T("%u"), pChannel->m_uUsers); + break; + + case 2: + _tcsncpy(pszText, pChannel->m_sDesc, cchTextMax); + break; + } + pszText[cchTextMax - 1] = _T('\0'); +} + +void CIrcChannelListCtrl::OnLvnGetDispInfo(NMHDR *pNMHDR, LRESULT *pResult) +{ + if (theApp.emuledlg->IsRunning()) { + // Although we have an owner drawn listview control we store the text for the primary item in the listview, to be + // capable of quick searching those items via the keyboard. Because our listview items may change their contents, + // we do this via a text callback function. The listview control will send us the LVN_DISPINFO notification if + // it needs to know the contents of the primary item. + // + // But, the listview control sends this notification all the time, even if we do not search for an item. At least + // this notification is only sent for the visible items and not for all items in the list. Though, because this + // function is invoked *very* often, do *NOT* put any time consuming code in here. + // + // Vista: That callback is used to get the strings for the label tips for the sub(!) items. + // + NMLVDISPINFO *pDispInfo = reinterpret_cast(pNMHDR); + if (pDispInfo->item.mask & LVIF_TEXT) { + const ChannelName *pChannel = (ChannelName *)pDispInfo->item.lParam; + if (pChannel != NULL) + GetItemDisplayText(pChannel, pDispInfo->item.iSubItem, pDispInfo->item.pszText, pDispInfo->item.cchTextMax); + } + } + *pResult = 0; +} + +void CIrcChannelListCtrl::OnLvnColumnClick(NMHDR* pNMHDR, LRESULT* pResult) +{ + LPNMLISTVIEW pNMLV = reinterpret_cast(pNMHDR); + + bool bSortAscending = (GetSortItem() != pNMLV->iSubItem) ? true : !GetSortAscending(); + SetSortArrow(pNMLV->iSubItem, bSortAscending); + SortItems(&SortProc, pNMLV->iSubItem + (bSortAscending ? 0 : 10)); + + *pResult = 0; +} + +int CIrcChannelListCtrl::SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + const ChannelName* pItem1 = (ChannelName*)lParam1; + const ChannelName* pItem2 = (ChannelName*)lParam2; + int iColumn = lParamSort >= 10 ? lParamSort - 10 : lParamSort; + int iResult = 0; + switch (iColumn) + { + case 0: + iResult = pItem1->m_sName.CompareNoCase(pItem2->m_sName); + break; + + case 1: + iResult = CompareUnsigned(pItem1->m_uUsers, pItem2->m_uUsers); + break; + + case 2: + iResult = pItem1->m_sDesc.CompareNoCase(pItem2->m_sDesc); + break; + + default: + return 0; + } + if (lParamSort >= 10) + iResult = -iResult; + return iResult; +} + +void CIrcChannelListCtrl::OnNmDblClk(NMHDR*, LRESULT* pResult) +{ + JoinChannels(); + *pResult = 0; +} + +void CIrcChannelListCtrl::OnContextMenu(CWnd*, CPoint point) +{ + int iCurSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED); + CTitleMenu menuChannel; + menuChannel.CreatePopupMenu(); + menuChannel.AddMenuTitle(GetResString(IDS_IRC_CHANNEL)); + menuChannel.AppendMenu(MF_STRING, Irc_Join, GetResString(IDS_IRC_JOIN)); + if (iCurSel == -1) + menuChannel.EnableMenuItem(Irc_Join, MF_GRAYED); + GetPopupMenuPos(*this, point); + menuChannel.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); + VERIFY( menuChannel.DestroyMenu() ); +} + +BOOL CIrcChannelListCtrl::OnCommand(WPARAM wParam, LPARAM) +{ + switch (wParam) + { + case Irc_Join: + //Pressed the join button. + JoinChannels(); + return TRUE; + } + return TRUE; +} + +bool CIrcChannelListCtrl::AddChannelToList(const CString& sName, const CString& sUsers, const CString& sDesc) +{ + UINT uUsers = _tstoi(sUsers); + if (thePrefs.GetIRCUseChannelFilter()) + { + if (uUsers < thePrefs.GetIRCChannelUserFilter()) + return false; + // was already filtered with "/LIST" command + //if (!thePrefs.GetIRCChannelFilter().IsEmpty()) + //{ + // if (stristr(sName, thePrefs.GetIRCChannelFilter()) == NULL) + // return false; + //} + } + + ChannelName* pChannel = new ChannelName(sName, uUsers, m_pParent->StripMessageOfFontCodes(sDesc)); + m_lstChannelNames.AddTail(pChannel); + int iItem = InsertItem(LVIF_TEXT | LVIF_PARAM, GetItemCount(), LPSTR_TEXTCALLBACK, 0, 0, 0, (LPARAM)pChannel); + if (iItem < 0) + return false; + SetItemText(iItem, 1, LPSTR_TEXTCALLBACK); + SetItemText(iItem, 2, LPSTR_TEXTCALLBACK); + return true; +} + +void CIrcChannelListCtrl::ResetServerChannelList(bool bShutDown) +{ + POSITION pos = m_lstChannelNames.GetHeadPosition(); + while (pos) + delete m_lstChannelNames.GetNext(pos); + m_lstChannelNames.RemoveAll(); + if (!bShutDown) + DeleteAllItems(); +} + +void CIrcChannelListCtrl::JoinChannels() +{ + if (!m_pParent->IsConnected()) + return; + POSITION pos = GetFirstSelectedItemPosition(); + while (pos != NULL) + { + int iIndex = GetNextSelectedItem(pos); + if (iIndex >= 0) + m_pParent->m_pIrcMain->SendString(_T("JOIN ") + GetItemText(iIndex, 0)); + } +} diff --git a/IrcChannelListCtrl.h b/IrcChannelListCtrl.h new file mode 100644 index 00000000..5abc92f0 --- /dev/null +++ b/IrcChannelListCtrl.h @@ -0,0 +1,50 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. +#pragma once +#include "MuleListCtrl.h" + +struct ChannelName; + +class CIrcChannelListCtrl : public CMuleListCtrl +{ + DECLARE_DYNAMIC(CIrcChannelListCtrl) +public: + CIrcChannelListCtrl(); + virtual ~CIrcChannelListCtrl(); + + void ResetServerChannelList(bool bShutdown = false); + bool AddChannelToList(const CString& sName, const CString& sUser, const CString& sDescription); + void JoinChannels(); + void Localize(); + void Init(); + +protected: + friend class CIrcWnd; + CTypedPtrList m_lstChannelNames; + CIrcWnd* m_pParent; + + static int CALLBACK SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + void GetItemDisplayText(const ChannelName *pChannel, int iSubItem, LPTSTR pszText, int cchTextMax); + + virtual BOOL OnCommand(WPARAM wParam,LPARAM lParam ); + + DECLARE_MESSAGE_MAP() + afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); + afx_msg void OnLvnColumnClick(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnGetDispInfo(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnNmDblClk(NMHDR *pNMHDR, LRESULT *pResult); +}; diff --git a/IrcChannelTabCtrl.cpp b/IrcChannelTabCtrl.cpp new file mode 100644 index 00000000..81f121f7 --- /dev/null +++ b/IrcChannelTabCtrl.cpp @@ -0,0 +1,831 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#include "StdAfx.h" +#define MMNODRV // mmsystem: Installable driver support +//#define MMNOSOUND // mmsystem: Sound support +#define MMNOWAVE // mmsystem: Waveform support +#define MMNOMIDI // mmsystem: MIDI support +#define MMNOAUX // mmsystem: Auxiliary audio support +#define MMNOMIXER // mmsystem: Mixer support +#define MMNOTIMER // mmsystem: Timer support +#define MMNOJOY // mmsystem: Joystick support +#define MMNOMCI // mmsystem: MCI support +#define MMNOMMIO // mmsystem: Multimedia file I/O support +#define MMNOMMSYSTEM // mmsystem: General MMSYSTEM functions +#include +#include "IrcChannelTabCtrl.h" +#include "emule.h" +#include "IrcWnd.h" +#include "IrcMain.h" +#include "otherfunctions.h" +#include "MenuCmds.h" +#include "HTRichEditCtrl.h" +#include "UserMsgs.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +IMPLEMENT_DYNAMIC(CIrcChannelTabCtrl, CClosableTabCtrl) + +BEGIN_MESSAGE_MAP(CIrcChannelTabCtrl, CClosableTabCtrl) + ON_MESSAGE(UM_CLOSETAB, OnCloseTab) + ON_MESSAGE(UM_QUERYTAB, OnQueryTab) + ON_NOTIFY_REFLECT(TCN_SELCHANGE, OnTcnSelChange) +END_MESSAGE_MAP() + +CIrcChannelTabCtrl::CIrcChannelTabCtrl() +{ + m_pCurrentChannel = NULL; + m_pChanStatus = NULL; + m_pChanList = NULL; + m_pParent = NULL; + m_bCloseable = true; +} + +CIrcChannelTabCtrl::~CIrcChannelTabCtrl() +{ + //Remove and delete all our open channels. + DeleteAllChannels(); +} + +void CIrcChannelTabCtrl::Init() +{ + //This adds the two static windows, Status and ChanneList + m_pChanStatus = NewChannel(GetResString(IDS_STATUS), Channel::ctStatus); + m_pChanList = NewChannel(GetResString(IDS_IRC_CHANNELLIST), Channel::ctChannelList); + //Initialize the IRC window to be in the ChannelName + m_pCurrentChannel = m_lstChannels.GetTail(); + SetCurSel(0); + OnTcnSelChange(NULL, NULL); + SetAllIcons(); +} + +void CIrcChannelTabCtrl::OnSysColorChange() +{ + CClosableTabCtrl::OnSysColorChange(); + SetAllIcons(); +} + +void CIrcChannelTabCtrl::SetAllIcons() +{ + CImageList imlist; + imlist.Create(16, 16, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1); + imlist.Add(CTempIconLoader(_T("Log"))); + imlist.Add(CTempIconLoader(_T("IRC"))); + imlist.Add(CTempIconLoader(_T("Message"))); + imlist.Add(CTempIconLoader(_T("MessagePending"))); + SetImageList(&imlist); + m_imlistIRC.DeleteImageList(); + m_imlistIRC.Attach(imlist.Detach()); + SetPadding(CSize(12, 3)); +} + +Channel* CIrcChannelTabCtrl::FindChannelByName(const CString& sName) +{ + CString sTempName(sName); + sTempName.Trim(); + + POSITION pos = m_lstChannels.GetHeadPosition(); + while (pos) + { + Channel* pChannel = m_lstChannels.GetNext(pos); + if (pChannel->m_sName.CompareNoCase(sTempName) == 0 && (pChannel->m_eType == Channel::ctNormal || pChannel->m_eType == Channel::ctPrivate)) + return pChannel; + } + return NULL; +} + +Channel* CIrcChannelTabCtrl::NewChannel(const CString& sName, Channel::EType eType) +{ + Channel* pChannel = new Channel; + pChannel->m_sName = sName; + pChannel->m_sTitle = sName; + pChannel->m_eType = eType; + pChannel->m_iHistoryPos = 0; + if (eType != Channel::ctChannelList) + { + CRect rcChannelPane; + m_pParent->m_wndChanList.GetWindowRect(&rcChannelPane); + m_pParent->ScreenToClient(rcChannelPane); + + CRect rcLog(rcChannelPane); + if (eType == Channel::ctNormal +#ifdef _DEBUG +//#define DEBUG_IRC_TEXT +#endif +#ifdef DEBUG_IRC_TEXT + || eType == Channel::ctStatus +#endif + ) + { + CRect rcTitle(rcChannelPane.left, rcChannelPane.top, + rcChannelPane.right, rcChannelPane.top + IRC_TITLE_WND_DFLT_HEIGHT); + pChannel->m_wndTitle.Create(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VSCROLL | ES_MULTILINE | ES_READONLY | ES_NOHIDESEL, rcTitle, m_pParent, IDC_TITLEWINDOW); + pChannel->m_wndTitle.ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_FRAMECHANGED); + pChannel->m_wndTitle.SendMessage(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(3, 3)); + pChannel->m_wndTitle.SetEventMask(pChannel->m_wndTitle.GetEventMask() | ENM_LINK); + pChannel->m_wndTitle.SetAutoScroll(false); + pChannel->m_wndTitle.SetFont(&theApp.m_fontHyperText); + pChannel->m_wndTitle.SetTitle(sName); + pChannel->m_wndTitle.SetProfileSkinKey(_T("IRCChannel")); + // The idea is to show the channel title with black background, thus giving the + // user a chance to more easier read the colorful strings which are often created + // to be read against a black or at least dark background. Though, there is one + // problem. If the user has customized the system default selection color to + // black, he would not be able to read any highlighted URLs against a black + // background any longer because the RE control is using that very same color for + // hyperlinks. So, we select a black background only if the user is already using + // the default windows background (white), because it is very unlikely that such + // a user would have costomized also the selection color to white. + if (GetSysColor(COLOR_WINDOW) == RGB(255,255,255)) + { + pChannel->m_wndTitle.SetDfltForegroundColor(RGB(255,255,255)); + pChannel->m_wndTitle.SetDfltBackgroundColor(RGB(0,0,0)); + } + pChannel->m_wndTitle.ApplySkin(); + pChannel->m_wndTitle.EnableSmileys(thePrefs.GetIRCEnableSmileys()); + +#ifdef DEBUG_IRC_TEXT + if (eType == Channel::ctStatus) { + pChannel->m_wndTitle.AddLine(_T("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ^°1234567890!\"§$%&/()=?´`²³{[]}ß\\öäüÖÄÜ+*~#',.-;:_")); + } +#endif + + CRect rcSplitter(rcChannelPane.left, rcTitle.bottom, + rcChannelPane.right, rcTitle.bottom + IRC_CHANNEL_SPLITTER_HEIGHT); + pChannel->m_wndSplitter.Create(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, rcSplitter, m_pParent, IDC_SPLITTER_IRC_CHANNEL); + pChannel->m_wndSplitter.SetRange(rcTitle.top + IRC_TITLE_WND_MIN_HEIGHT + IRC_CHANNEL_SPLITTER_HEIGHT/2, rcTitle.top + IRC_TITLE_WND_MAX_HEIGHT - IRC_CHANNEL_SPLITTER_HEIGHT/2); + rcLog.top = rcSplitter.bottom; + rcLog.bottom = rcChannelPane.bottom; + } + + pChannel->m_wndLog.Create(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VSCROLL | ES_MULTILINE | ES_READONLY | ES_NOHIDESEL, rcLog, m_pParent, (UINT)-1); + pChannel->m_wndLog.ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_FRAMECHANGED); + pChannel->m_wndLog.SendMessage(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(3, 3)); + pChannel->m_wndLog.SetEventMask(pChannel->m_wndLog.GetEventMask() | ENM_LINK); + pChannel->m_wndLog.SetFont(&theApp.m_fontHyperText); + pChannel->m_wndLog.SetTitle(sName); + pChannel->m_wndLog.SetProfileSkinKey(_T("IRCChannel")); + pChannel->m_wndLog.ApplySkin(); + pChannel->m_wndLog.EnableSmileys(thePrefs.GetIRCEnableSmileys()); + + if (eType == Channel::ctNormal || eType == Channel::ctPrivate) + { + PARAFORMAT pf = {0}; + pf.cbSize = sizeof pf; + pf.dwMask = PFM_OFFSET; + pf.dxOffset = 150; + pChannel->m_wndLog.SetParaFormat(pf); + } + +#ifdef DEBUG_IRC_TEXT + if (eType == Channel::ctStatus){ + //pChannel->m_wndLog.AddLine(_T(":) debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug debug")); + m_pParent->AddColorLine(L"normal\002bold\002normal\r\n", pChannel->m_wndLog); + m_pParent->AddColorLine(L"\0034red\002bold\002red\r\n", pChannel->m_wndLog); + m_pParent->AddColorLine(L"normal\r\n", pChannel->m_wndLog); + m_pParent->AddColorLine(L"\0032,4red\002bold\002red\r\n", pChannel->m_wndLog); + m_pParent->AddColorLine(L"\017normal\r\n", pChannel->m_wndLog); + + LPCWSTR log = L"C:\\Programme\\mIRC 2\\channels\\MindForge_Sorted_X.txt"; + FILE *fp = _wfopen(log, L"rt"); + if (fp) + { + int i = 0; + int iMax = 10000; + TCHAR szLine[1024]; + while (i++ < iMax && fgetws(szLine, _countof(szLine), fp)) + { + size_t nLen = wcslen(szLine); + if (nLen >= 1 && szLine[nLen - 1] == L'\n') + --nLen; + szLine[nLen++] = L'\r'; + szLine[nLen++] = L'\n'; + //TRACE(_T("%u: %s\n"), i, szLine); + CString strLine(szLine, nLen); + m_pParent->AddColorLine(strLine, pChannel->m_wndLog); + } + fclose(fp); + } + } +#endif + } + m_lstChannels.AddTail(pChannel); + + TCITEM newitem; + newitem.mask = TCIF_PARAM | TCIF_TEXT | TCIF_IMAGE; + newitem.lParam = (LPARAM)pChannel; + CString strTcLabel(sName); + strTcLabel.Replace(_T("&"), _T("&&")); + newitem.pszText = const_cast((LPCTSTR)strTcLabel); + if (eType == Channel::ctStatus) + newitem.iImage = 0; + else if (eType == Channel::ctChannelList) + newitem.iImage = 1; + else + newitem.iImage = 2; + int iInsertAt = GetItemCount(); + int iItem = InsertItem(iInsertAt, &newitem); + if (eType == Channel::ctNormal) + SelectChannel(iItem); + return pChannel; +} + +int CIrcChannelTabCtrl::FindChannel(const Channel *pChannel) +{ + TCITEM item; + item.mask = TCIF_PARAM; + item.lParam = -1; + int iItems = GetItemCount(); + int iIndex; + for (iIndex = 0; iIndex < iItems; iIndex++) + { + GetItem(iIndex, &item); + if ((const Channel*)item.lParam == pChannel) + return iIndex; + } + return -1; +} + +void CIrcChannelTabCtrl::SelectChannel(int iItem) +{ + ASSERT( iItem >= 0 && iItem < GetItemCount() ); + SetCurSel(iItem); + SetCurFocus(iItem); + OnTcnSelChange(NULL, NULL); +} + +void CIrcChannelTabCtrl::SelectChannel(const Channel *pChannel) +{ + int iItem = FindChannel(pChannel); + if (iItem < 0) + return; + SelectChannel(iItem); +} + +void CIrcChannelTabCtrl::RemoveChannel(const CString& sChannel) +{ + Channel* pToDel = FindChannelByName(sChannel); + if (!pToDel) + return; + + ASSERT( pToDel != m_pChanStatus ); + ASSERT( pToDel != m_pChanList ); + + TCITEM item; + item.mask = TCIF_PARAM; + item.lParam = -1; + int iItems = GetItemCount(); + int iIndex; + for (iIndex = 0; iIndex < iItems; iIndex++) + { + GetItem(iIndex, &item); + if ((Channel*)item.lParam == pToDel) + break; + } + if ((Channel*)item.lParam != pToDel) + return; + DeleteItem(iIndex); + + if (pToDel == m_pCurrentChannel) + { + m_pParent->m_wndNicks.DeleteAllItems(); + if (GetItemCount() > 2 && iIndex > 1) + { + if (iIndex == 2) + iIndex++; + SetCurSel(iIndex - 1); + SetCurFocus(iIndex - 1); + OnTcnSelChange(NULL, NULL); + } + else + { + SetCurSel(0); + SetCurFocus(0); + OnTcnSelChange(NULL, NULL); + } + } + m_pParent->m_wndNicks.DeleteAllNick(pToDel->m_sName); + m_lstChannels.RemoveAt(m_lstChannels.Find(pToDel)); + delete pToDel; +} + +void CIrcChannelTabCtrl::DeleteAllChannels() +{ + POSITION pos1, pos2; + for (pos1 = m_lstChannels.GetHeadPosition(); (pos2 = pos1) != NULL; ) + { + m_lstChannels.GetNext(pos1); + Channel* pCurChannel = m_lstChannels.GetAt(pos2); + m_pParent->m_wndNicks.DeleteAllNick(pCurChannel->m_sName); + m_lstChannels.RemoveAt(pos2); + delete pCurChannel; + } + m_pChanStatus = NULL; + m_pChanList = NULL; +} + +bool CIrcChannelTabCtrl::ChangeChanMode(const CString& sChannel, const CString& sParam, const CString& sDir, const CString& sCommand) +{ + (void)sParam; + + //Must have a Command. + if (sCommand.IsEmpty()) + return false; + + //Must be a channel! + if (sChannel.IsEmpty() || sChannel[0] != _T('#')) + return false; + + //Get Channel. + //Make sure we have this channel in our list. + Channel* pUpdate = FindChannelByName(sChannel); + if (!pUpdate) + return false; + + //Update modes. + int iCommandIndex = m_sChannelModeSettingsTypeA.Find(sCommand); + if (iCommandIndex != -1) + { + //Remove the setting. This takes care of "-" and makes sure we don't add the same symbol twice. + pUpdate->m_sModesA.Replace(sCommand, _T("")); + if (sDir == _T("+")) + { + //Update mode. + if (pUpdate->m_sModesA.IsEmpty()) + pUpdate->m_sModesA = sCommand; + else + { + //The chan does have other modes.. Lets make sure we put things in order.. + int iChannelModeSettingsTypeALength = m_sChannelModeSettingsTypeA.GetLength(); + //This will pad the mode string.. + for (int iIndex = 0; iIndex < iChannelModeSettingsTypeALength; iIndex++) + { + if (pUpdate->m_sModesA.Find(m_sChannelModeSettingsTypeA[iIndex]) == -1) + pUpdate->m_sModesA.Insert(iIndex, _T(" ")); + } + //Insert the new mode + pUpdate->m_sModesA.Insert(iCommandIndex, sCommand); + //Remove pads + pUpdate->m_sModesA.Remove(_T(' ')); + } + } + return true; + } + + iCommandIndex = m_sChannelModeSettingsTypeB.Find(sCommand); + if (iCommandIndex != -1) + { + //Remove the setting. This takes care of "-" and makes sure we don't add the same symbol twice. + pUpdate->m_sModesB.Replace(sCommand, _T("")); + if (sDir == _T("+")) + { + //Update mode. + if (pUpdate->m_sModesB.IsEmpty()) + pUpdate->m_sModesB = sCommand; + else + { + //The chan does have other modes.. Lets make sure we put things in order.. + int iChannelModeSettingsTypeBLength = m_sChannelModeSettingsTypeB.GetLength(); + //This will pad the mode string.. + for (int iIndex = 0; iIndex < iChannelModeSettingsTypeBLength; iIndex++) + { + if (pUpdate->m_sModesB.Find(m_sChannelModeSettingsTypeB[iIndex]) == -1) + pUpdate->m_sModesB.Insert(iIndex, _T(" ")); + } + //Insert the new mode + pUpdate->m_sModesB.Insert(iCommandIndex, sCommand); + //Remove pads + pUpdate->m_sModesB.Remove(_T(' ')); + } + } + return true; + } + + iCommandIndex = m_sChannelModeSettingsTypeC.Find(sCommand); + if (iCommandIndex != -1) + { + //Remove the setting. This takes care of "-" and makes sure we don't add the same symbol twice. + pUpdate->m_sModesC.Replace(sCommand, _T("")); + if (sDir == _T("+")) + { + //Update mode. + if (pUpdate->m_sModesC.IsEmpty()) + pUpdate->m_sModesC = sCommand; + else + { + //The chan does have other modes.. Lets make sure we put things in order.. + int iChannelModeSettingsTypeCLength = m_sChannelModeSettingsTypeC.GetLength(); + //This will pad the mode string.. + for (int iIndex = 0; iIndex < iChannelModeSettingsTypeCLength; iIndex++) + { + if (pUpdate->m_sModesC.Find(m_sChannelModeSettingsTypeC[iIndex]) == -1) + pUpdate->m_sModesC.Insert(iIndex, _T(" ")); + } + //Insert the new mode + pUpdate->m_sModesC.Insert(iCommandIndex, sCommand); + //Remove pads + pUpdate->m_sModesC.Remove(_T(' ')); + } + } + return true; + } + + iCommandIndex = m_sChannelModeSettingsTypeD.Find(sCommand); + if (iCommandIndex != -1) + { + //Remove the setting. This takes care of "-" and makes sure we don't add the same symbol twice. + pUpdate->m_sModesD.Replace(sCommand, _T("")); + if (sDir == _T("+")) + { + //Update mode. + if (pUpdate->m_sModesD.IsEmpty()) + pUpdate->m_sModesD = sCommand; + else + { + //The chan does have other modes.. Lets make sure we put things in order.. + int iChannelModeSettingsTypeDLength = m_sChannelModeSettingsTypeD.GetLength(); + //This will pad the mode string.. + for (int iIndex = 0; iIndex < iChannelModeSettingsTypeDLength; iIndex++) + { + if (pUpdate->m_sModesD.Find(m_sChannelModeSettingsTypeD[iIndex]) == -1) + pUpdate->m_sModesD.Insert(iIndex, _T(" ")); + } + //Insert the new mode + pUpdate->m_sModesD.Insert(iCommandIndex, sCommand); + //Remove pads + pUpdate->m_sModesD.Remove(_T(' ')); + } + } + return true; + } + + return false; +} + +void CIrcChannelTabCtrl::OnTcnSelChange(NMHDR*, LRESULT* pResult) +{ + //What channel did we select? + int iCurSel = GetCurSel(); + if (iCurSel == -1) { + //No channel, abort.. + return; + } + + TCITEM item; + item.mask = TCIF_PARAM; + if (!GetItem(iCurSel, &item)) { + //We had no valid item here.. Something isn't right.. + //TODO: this should never happen, so maybe we should remove this tab? + return; + } + Channel* pUpdate = (Channel*)item.lParam; + Channel* pCh2 = NULL; + + //Set our current channel to the new one for quick reference.. + m_pCurrentChannel = pUpdate; + + //We entered the channel, set activity flag off. + SetActivity(m_pCurrentChannel, false); + + if (m_pCurrentChannel->m_eType == Channel::ctChannelList) + { + //Since some channels can have a LOT of nicks, hide the window then remove them to speed it up.. + m_pParent->m_wndNicks.ShowWindow(SW_HIDE); + m_pParent->m_wndNicks.DeleteAllItems(); + m_pParent->m_wndNicks.UpdateNickCount(); + m_pParent->m_wndNicks.ShowWindow(SW_SHOW); + //Show our ChanList.. + m_pParent->m_wndChanList.ShowWindow(SW_SHOW); + TCITEM tci; + tci.mask = TCIF_PARAM; + //Go through the channel tabs and hide the channels.. + //Maybe overkill? Maybe just remember our previous channel and hide it? + int iIndex = 0; + while (GetItem(iIndex++, &tci)) + { + pCh2 = (Channel*)tci.lParam; + if (pCh2 != m_pCurrentChannel && pCh2->m_wndLog.m_hWnd != NULL) + pCh2->Hide(); + } + return; + } + + //Show new current channel.. + m_pCurrentChannel->Show(); + m_pParent->UpdateChannelChildWindowsSize(); + + //Hide all channels not in focus.. + //Maybe overkill? Maybe remember previous channel and hide? + TCITEM tci; + tci.mask = TCIF_PARAM; + int iIndex = 0; + while (GetItem(iIndex++, &tci)) + { + pCh2 = (Channel*)tci.lParam; + if (pCh2 != m_pCurrentChannel && pCh2->m_wndLog.m_hWnd != NULL) + pCh2->Hide(); + } + + //Make sure channelList is hidden. + m_pParent->m_wndChanList.ShowWindow(SW_HIDE); + //Update nicklist to the new channel.. + m_pParent->m_wndNicks.RefreshNickList(pUpdate->m_sName); + //Push focus back to the input box.. + m_pParent->m_wndInput.SetFocus(); + if (pResult) + *pResult = 0; +} + +void CIrcChannelTabCtrl::ScrollHistory(bool bDown) +{ + if ((m_pCurrentChannel->m_iHistoryPos == 0 && !bDown) || (m_pCurrentChannel->m_iHistoryPos == m_pCurrentChannel->m_astrHistory.GetCount() && bDown)) + return; + + if (bDown) + ++m_pCurrentChannel->m_iHistoryPos; + else + --m_pCurrentChannel->m_iHistoryPos; + + CString sBuffer = (m_pCurrentChannel->m_iHistoryPos == m_pCurrentChannel->m_astrHistory.GetCount()) ? _T("") : m_pCurrentChannel->m_astrHistory.GetAt(m_pCurrentChannel->m_iHistoryPos); + m_pParent->m_wndInput.SetWindowText(sBuffer); + m_pParent->m_wndInput.SetSel(sBuffer.GetLength(), sBuffer.GetLength()); +} + +void CIrcChannelTabCtrl::SetActivity(Channel *pChannel, bool bFlag) +{ + if (bFlag && pChannel == m_pCurrentChannel) + return; + if (!pChannel) { + pChannel = m_lstChannels.GetHead(); + if (!pChannel) + return; + } + + TCITEM item; + item.mask = TCIF_PARAM; + item.lParam = -1; + int iItems = GetItemCount(); + int iIndex; + for (iIndex = 0; iIndex < iItems; iIndex++) + { + GetItem(iIndex, &item); + if ((Channel*)item.lParam == pChannel) + break; + } + if ((Channel*)item.lParam != pChannel) + return; + + if (pChannel->m_eType == Channel::ctNormal || pChannel->m_eType == Channel::ctPrivate) + { + item.mask = TCIF_IMAGE; + GetItem(iIndex, &item); + if (bFlag) + { + if (item.iImage != 3) { + item.iImage = 3; // 'MessagePending' + SetItem(iIndex, &item); + } + } + else + { + if (item.iImage != 2) { + item.iImage = 2; // 'Message' + SetItem(iIndex, &item); + } + } + } + if (bFlag) + HighlightItem(iIndex, TRUE); + else + HighlightItem(iIndex, FALSE); +} + +void CIrcChannelTabCtrl::ChatSend(CString sSend) +{ + if (sSend.IsEmpty()) + return; + if (!m_pParent->IsConnected()) + return; + + if ((UINT)m_pCurrentChannel->m_astrHistory.GetCount() == thePrefs.GetMaxChatHistoryLines()) + m_pCurrentChannel->m_astrHistory.RemoveAt(0); + m_pCurrentChannel->m_astrHistory.Add(sSend); + m_pCurrentChannel->m_iHistoryPos = m_pCurrentChannel->m_astrHistory.GetCount(); + + if (sSend.Left(4) == _T("/hop")) + { + if (m_pCurrentChannel->m_sName.Left(1) == _T("#")) + { + m_pParent->m_pIrcMain->SendString(_T("PART ") + m_pCurrentChannel->m_sName); + m_pParent->m_pIrcMain->SendString(_T("JOIN ") + m_pCurrentChannel->m_sName); + } + return; + } + + if ( sSend.Left(1) == _T("/") + && sSend.Left(3).MakeLower() != _T("/me") + && sSend.Left(6).MakeLower() != _T("/sound")) + { + if (sSend.Left(4) == _T("/msg")) + { + if (m_pCurrentChannel->m_eType == Channel::ctNormal || m_pCurrentChannel->m_eType == Channel::ctPrivate) + m_pParent->AddInfoMessageF(m_pCurrentChannel->m_sName, _T("* >> %s"), sSend.Mid(5)); + else + m_pParent->AddStatusF(_T("* >> %s"), sSend.Mid(5)); + sSend = _T("/PRIVMSG") + sSend.Mid(4); + } + else if (sSend.Left(7).CompareNoCase(_T("/notice")) == 0) + { + if (m_pCurrentChannel->m_eType == Channel::ctNormal || m_pCurrentChannel->m_eType == Channel::ctPrivate) + m_pParent->AddInfoMessageF(m_pCurrentChannel->m_sName, _T("* >> %s"), sSend.Mid(8)); + else + m_pParent->AddStatusF(_T("* >> %s"), sSend.Mid(8)); + } + + if (sSend.Left(17).CompareNoCase(_T("/PRIVMSG nickserv")) == 0) + { + sSend = _T("/ns") + sSend.Mid(17); + } + else if (sSend.Left(17).CompareNoCase(_T("/PRIVMSG chanserv")) == 0) + { + sSend = _T("/cs") + sSend.Mid(17); + } + else if (sSend.Left(8).CompareNoCase(_T("/PRIVMSG")) == 0) + { + int iIndex = sSend.Find(_T(' '), sSend.Find(_T(' ')) + 1); + sSend.Insert(iIndex + 1, _T(":")); + } + else if (sSend.Left(6).CompareNoCase(_T("/TOPIC")) == 0) + { + int iIndex = sSend.Find(_T(' '), sSend.Find(_T(' ')) + 1); + sSend.Insert(iIndex + 1, _T(":")); + } + m_pParent->m_pIrcMain->SendString(sSend.Mid(1)); + return; + } + + if (m_pCurrentChannel->m_eType < Channel::ctNormal) + { + m_pParent->m_pIrcMain->SendString(sSend); + return; + } + + CString sBuild; + if (sSend.Left(3) == _T("/me")) + { + sBuild.Format(_T("PRIVMSG %s :\001ACTION %s\001"), m_pCurrentChannel->m_sName, sSend.Mid(4)); + m_pParent->AddInfoMessageF(m_pCurrentChannel->m_sName, _T("* %s %s"), m_pParent->m_pIrcMain->GetNick(), sSend.Mid(4)); + m_pParent->m_pIrcMain->SendString(sBuild); + return; + } + + if (sSend.Left(6) == _T("/sound")) + { + CString sound; + sBuild.Format(_T("PRIVMSG %s :\001SOUND %s\001"), m_pCurrentChannel->m_sName, sSend.Mid(7)); + m_pParent->m_pIrcMain->SendString(sBuild); + sSend = sSend.Mid(7); + int soundlen = sSend.Find(_T(' ')); + if (soundlen != -1) + { + sBuild = sSend.Left(soundlen); + sBuild.Remove(_T('\\')); + sSend = sSend.Left(soundlen); + } + else + { + sBuild = sSend; + sSend = _T("[SOUND]"); + } + sound.Format(_T("%sSounds\\IRC\\%s"), thePrefs.GetMuleDirectory(EMULE_EXECUTEABLEDIR), sBuild); + m_pParent->AddInfoMessageF(m_pCurrentChannel->m_sName, _T("* %s %s"), m_pParent->m_pIrcMain->GetNick(), sSend); + PlaySound(sound, NULL, SND_FILENAME | SND_NOSTOP | SND_NOWAIT | SND_ASYNC); + return; + } + + sBuild = _T("PRIVMSG ") + m_pCurrentChannel->m_sName + _T(" :") + sSend; + m_pParent->m_pIrcMain->SendString(sBuild); + m_pParent->AddMessageF(m_pCurrentChannel->m_sName, m_pParent->m_pIrcMain->GetNick(), _T("%s"), sSend); +} + +void CIrcChannelTabCtrl::Localize() +{ + int iItems = GetItemCount(); + for (int iIndex = 0; iIndex < iItems; iIndex++) + { + TCITEM item; + item.mask = TCIF_PARAM; + item.lParam = -1; + GetItem(iIndex, &item); + Channel* pCurChan = (Channel*)item.lParam; + if (pCurChan != NULL) + { + if (pCurChan->m_eType == Channel::ctStatus) + { + pCurChan->m_sTitle = GetResString(IDS_STATUS); + item.mask = TCIF_TEXT; + CString strTcLabel(pCurChan->m_sTitle); + strTcLabel.Replace(_T("&"), _T("&&")); + item.pszText = const_cast((LPCTSTR)strTcLabel); + SetItem(iIndex, &item); + } + else if (pCurChan->m_eType == Channel::ctChannelList) + { + pCurChan->m_sTitle = GetResString(IDS_IRC_CHANNELLIST); + item.mask = TCIF_TEXT; + CString strTcLabel(pCurChan->m_sTitle); + strTcLabel.Replace(_T("&"), _T("&&")); + item.pszText = const_cast((LPCTSTR)strTcLabel); + SetItem(iIndex, &item); + } + } + } + SetAllIcons(); +} + +LRESULT CIrcChannelTabCtrl::OnCloseTab(WPARAM wParam, LPARAM /*lParam*/) +{ + m_pParent->OnBnClickedCloseChannel((int)wParam); + return TRUE; +} + +LRESULT CIrcChannelTabCtrl::OnQueryTab(WPARAM wParam, LPARAM /*lParam*/) +{ + int iItem = (int)wParam; + + TCITEM item; + item.mask = TCIF_PARAM; + GetItem(iItem, &item); + Channel* pPartChannel = (Channel*)item.lParam; + if (pPartChannel && (pPartChannel->m_eType == Channel::ctNormal || pPartChannel->m_eType == Channel::ctPrivate)) + return 0; + return 1; +} + +void CIrcChannelTabCtrl::EnableSmileys(bool bEnable) +{ + POSITION pos = m_lstChannels.GetHeadPosition(); + while (pos){ + Channel* pChannel = m_lstChannels.GetNext(pos); + if (pChannel->m_eType != Channel::ctChannelList){ + pChannel->m_wndLog.EnableSmileys(bEnable); + if (pChannel->m_eType == Channel::ctNormal) + pChannel->m_wndTitle.EnableSmileys(bEnable); + } + } +} + +void Channel::Show() +{ + if (m_wndTitle.m_hWnd) { + m_wndTitle.EnableWindow(TRUE); + m_wndTitle.ShowWindow(SW_SHOW); + } + + if (m_wndSplitter.m_hWnd) { + m_wndSplitter.EnableWindow(TRUE); + m_wndSplitter.ShowWindow(SW_SHOW); + } + + if (m_wndLog.m_hWnd) { + m_wndLog.EnableWindow(TRUE); + m_wndLog.ShowWindow(SW_SHOW); + } +} + +void Channel::Hide() +{ + if (m_wndTitle.m_hWnd) { + m_wndTitle.ShowWindow(SW_HIDE); + m_wndTitle.EnableWindow(FALSE); + } + + if (m_wndSplitter.m_hWnd) { + m_wndSplitter.ShowWindow(SW_HIDE); + m_wndSplitter.EnableWindow(FALSE); + } + + if (m_wndLog.m_hWnd) { + m_wndLog.ShowWindow(SW_HIDE); + m_wndLog.EnableWindow(FALSE); + } +} diff --git a/IrcChannelTabCtrl.h b/IrcChannelTabCtrl.h new file mode 100644 index 00000000..d5ce7c4e --- /dev/null +++ b/IrcChannelTabCtrl.h @@ -0,0 +1,102 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +#pragma once +#include "ClosableTabCtrl.h" +#include "HTRichEditCtrl.h" +#include "SplitterControl.h" + +#define IRC_TITLE_WND_DFLT_HEIGHT 36 // min. space for 2 lines with "MS Sans Serif" or "Verdana" at 10pt +#define IRC_TITLE_WND_MIN_HEIGHT (IRC_TITLE_WND_DFLT_HEIGHT/2) +#define IRC_TITLE_WND_MAX_HEIGHT (IRC_TITLE_WND_MIN_HEIGHT*6) +#define IRC_CHANNEL_SPLITTER_HEIGHT 4 + +struct Nick; + +struct Channel +{ + CString m_sName; + CString m_sModesA; + CString m_sModesB; + CString m_sModesC; + CString m_sModesD; + CHTRichEditCtrl m_wndTitle; + CSplitterControl m_wndSplitter; + CHTRichEditCtrl m_wndLog; + CString m_sTitle; + CTypedPtrList m_lstNicks; + CStringArray m_astrHistory; + int m_iHistoryPos; + // Type is mainly so that we can use this for IRC and the eMule Messages.. + // 1-Status, 2-Channel list, 4-Channel, 5-Private Channel, 6-eMule Message(Add later) + enum EType { + ctStatus = 1, + ctChannelList = 2, + ctNormal = 4, + ctPrivate = 5 + } m_eType; + + void Show(); + void Hide(); +}; + +class CIrcChannelTabCtrl : public CClosableTabCtrl +{ + DECLARE_DYNAMIC(CIrcChannelTabCtrl) + +public: + CIrcChannelTabCtrl(); + virtual ~CIrcChannelTabCtrl(); + + void Init(); + void Localize(); + Channel* FindChannelByName(const CString& sName); + Channel* NewChannel(const CString& sName, Channel::EType uType); + void RemoveChannel(const CString& sChannel); + void SelectChannel(const Channel *pChannel); + void DeleteAllChannels(); + bool ChangeChanMode(const CString& sChannel, const CString& sParam, const CString& sDir, const CString& sCommand); + void ScrollHistory(bool bDown); + void ChatSend(CString sSend); + void SetActivity(Channel *pChannel, bool bFlag); + void EnableSmileys(bool bEnable); + + CString m_sChannelModeSettingsTypeA; + CString m_sChannelModeSettingsTypeB; + CString m_sChannelModeSettingsTypeC; + CString m_sChannelModeSettingsTypeD; + CTypedPtrList m_lstChannels; + Channel* m_pCurrentChannel; + Channel* m_pChanStatus; + Channel* m_pChanList; + +protected: + friend class CIrcWnd; + + CIrcWnd* m_pParent; + CImageList m_imlistIRC; + + void SetAllIcons(); + int FindChannel(const Channel *pChannel); + void SelectChannel(int iItem); + + DECLARE_MESSAGE_MAP() + afx_msg void OnTcnSelChange(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnSysColorChange(); + afx_msg LRESULT OnCloseTab(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnQueryTab(WPARAM wParam, LPARAM lParam); +}; diff --git a/IrcMain.cpp b/IrcMain.cpp new file mode 100644 index 00000000..7a97c8a0 --- /dev/null +++ b/IrcMain.cpp @@ -0,0 +1,1649 @@ +//this file is part of eMule +//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net ) +// +//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. + +//A lot of documentation for the IRC protocol within this code came +//directly from http://www.irchelp.org/irchelp/rfc/rfc2812.txt +//Much of it may never be used, but it's here just in case.. +#include "stdafx.h" +#define MMNODRV // mmsystem: Installable driver support +//#define MMNOSOUND // mmsystem: Sound support +#define MMNOWAVE // mmsystem: Waveform support +#define MMNOMIDI // mmsystem: MIDI support +#define MMNOAUX // mmsystem: Auxiliary audio support +#define MMNOMIXER // mmsystem: Mixer support +#define MMNOTIMER // mmsystem: Timer support +#define MMNOJOY // mmsystem: Joystick support +#define MMNOMCI // mmsystem: MCI support +#define MMNOMMIO // mmsystem: Multimedia file I/O support +#define MMNOMMSYSTEM // mmsystem: General MMSYSTEM functions +#include +#include "IrcMain.h" +#include "emule.h" +#include "otherfunctions.h" +#include "ED2KLink.h" +#include "DownloadQueue.h" +#include "server.h" +#include "IrcSocket.h" +#include "MenuCmds.h" +#include "sockets.h" +#include "FriendList.h" +#include "emuleDlg.h" +#include "ServerWnd.h" +#include "IrcWnd.h" +#include "StringConversion.h" +#include "Log.h" +#include "Exceptions.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define Irc_Version _T("(SMIRCv00.69)") + + +CIrcMain::CIrcMain(void) +{ + m_pIRCSocket = NULL; + m_pwndIRC = 0; + srand( (unsigned)time( NULL ) ); + SetVerify(); + m_dwLastRequest = 0; +} + +CIrcMain::~CIrcMain(void) +{ +} + +void CIrcMain::PreParseMessage(const char *pszBufferA) +{ + try + { + m_sPreParseBufferA += pszBufferA; + int iIndex = m_sPreParseBufferA.Find('\n'); + while (iIndex != -1) + { + int iRawMessageLen = iIndex; + LPCSTR pszRawMessageA = m_sPreParseBufferA; + if (iRawMessageLen > 0 && pszRawMessageA[iRawMessageLen - 1] == '\r') + iRawMessageLen -= 1; + else + ASSERT(0); + TRACE(_T("%s\n"), CString(pszRawMessageA, iRawMessageLen)); + ParseMessage(CString(pszRawMessageA, iRawMessageLen)); + + m_sPreParseBufferA = m_sPreParseBufferA.Mid(iIndex + 1); + iIndex = m_sPreParseBufferA.Find('\n'); + } + } + CATCH_DFLT_EXCEPTIONS(_T(__FUNCTION__)) + CATCH_DFLT_ALL(_T(__FUNCTION__)) +} + +void CIrcMain::ProcessLink( CString sED2KLink ) +{ + try + { + CString sLink = OptUtf8ToStr(URLDecode(sED2KLink)); + CED2KLink* pLink = CED2KLink::CreateLinkFromUrl(sLink); + _ASSERT( pLink !=0 ); + switch (pLink->GetKind()) + { + case CED2KLink::kFile: + { + CED2KFileLink* pFileLink = pLink->GetFileLink(); + _ASSERT(pFileLink !=0); + theApp.downloadqueue->AddFileLinkToDownload(pFileLink); + break; + } + case CED2KLink::kServerList: + { + CED2KServerListLink* pListLink = pLink->GetServerListLink(); + _ASSERT( pListLink !=0 ); + CString sAddress = pListLink->GetAddress(); + if(sAddress.GetLength() != 0) + theApp.emuledlg->serverwnd->UpdateServerMetFromURL(sAddress); + break; + } + case CED2KLink::kServer: + { + CString sDefName; + CED2KServerLink* pSrvLink = pLink->GetServerLink(); + _ASSERT( pSrvLink !=0 ); + CServer* pSrv = new CServer(pSrvLink->GetPort(), pSrvLink->GetAddress()); + _ASSERT( pSrv !=0 ); + pSrvLink->GetDefaultName(sDefName); + pSrv->SetListName(sDefName); + + if (thePrefs.GetManualAddedServersHighPriority()) + pSrv->SetPreference(SRV_PR_HIGH); + + if (!theApp.emuledlg->serverwnd->serverlistctrl.AddServer(pSrv,true)) + delete pSrv; + else + AddLogLine(true,GetResString(IDS_SERVERADDED), pSrv->GetListName()); + break; + } + default: + break; + } + delete pLink; + } + catch(...) + { + LogWarning(LOG_STATUSBAR, GetResString(IDS_LINKNOTADDED)); + ASSERT(0); + } +} + +void CIrcMain::ParseMessage(CString sRawMessage) +{ + try + { + if (sRawMessage.GetLength() < 6) + { + //TODO : We probably should disconnect here as I don't know of anything that should + //come from the server this small.. + return; + } + if (_tcsncmp(sRawMessage, _T("PING :"), 6) == 0) + { + //If the server pinged us, we must pong back or get disconnected.. + //Anything after the ":" must be sent back or it will fail.. + sRawMessage.Replace(_T("PING :"), _T("PONG ")); + m_pIRCSocket->SendString(sRawMessage); + m_pwndIRC->AddStatus(_T("PING?/PONG"), false); + return; + } + if (_tcsncmp(sRawMessage, _T("ERROR"), 5) == 0) + { + m_pwndIRC->AddStatus(sRawMessage); + return; + } + int iIndex = 0; + CString sCommand; + CString sServername; + CString sNickname; + CString sUser; + CString sHost; + if (sRawMessage[0] != _T(':')) + { + sCommand = sRawMessage.Tokenize(_T(" "), iIndex); + } + else + { + sServername = sRawMessage.Tokenize(_T(" "), iIndex); + sServername.Remove(_T(':')); + int iPos; + if ((iPos = sServername.Find(_T('!'))) != -1) + { + sNickname = sServername.Left(iPos); + sServername = sServername.Mid(iPos + 1); + iPos = sServername.Find(_T('@')); + if (iPos != -1) + { + sUser = sServername.Left(iPos); + sHost = sServername.Mid(iPos + 1); + } + sServername.Empty(); + } + sCommand = sRawMessage.Tokenize(_T(" "), iIndex); + } + + if (sCommand.IsEmpty()) + throw CString(_T("SMIRC Error: Received a message had no command.")); + + if (sRawMessage.IsEmpty()) + throw CString(_T("SMIRC Errow: Received a message with no target or message.")); + + if (sCommand == _T("PRIVMSG")) + { + CString sTarget = sRawMessage.Tokenize(_T(" "), iIndex); + + // Channel and Private message were merged into this one if statement, this check allows that to happen. + if (sTarget.GetLength() >= 1 && sTarget[0] != _T('#')) + sTarget = sNickname; + + //If this is a special message.. Find out what kind.. + if (sRawMessage.Mid(iIndex, 2) == _T(":\001")) + { + //Check if this is a ACTION message.. + if( sRawMessage.Mid(iIndex, 8) == _T(":\001ACTION") ) + { + iIndex += 8; + CString sMessage = sRawMessage.Mid(iIndex); + sMessage.Remove(_T('\001')); + //Channel Action.. + m_pwndIRC->AddInfoMessageF(sTarget, _T("* %s %s"), sNickname, sMessage); + return; + } + //Check if this is a SOUND message. + if( sRawMessage.Mid(iIndex, 7) == _T(":\001SOUND") ) + { + if(!thePrefs.GetIRCPlaySoundEvents()) + return; + iIndex += 7; + CString sSound = sRawMessage.Tokenize(_T(" "), iIndex); + sSound.Remove(_T('\001')); + //Check if the there was a message to the sound. + CString sMessage = sRawMessage.Mid(iIndex); + sMessage.Remove(_T('\001')); + if(sMessage.IsEmpty()) + sMessage = _T("[SOUND]"); + //Check for proper form. + sSound.Remove(_T('\\')); + sSound.Remove(_T('/')); + sSound = sSound.MakeLower(); + if( (sSound.Right(4) != _T(".wav")) && (sSound.Right(4) != _T(".mp3")) ) + return; + sSound.Format(_T("%sSounds\\IRC\\%s"), thePrefs.GetMuleDirectory(EMULE_EXECUTEABLEDIR), sSound); + PlaySound(sSound, NULL, SND_FILENAME | SND_NOSTOP | SND_NOWAIT | SND_ASYNC); + m_pwndIRC->AddInfoMessageF(sTarget, _T("* %s %s"), sNickname, sMessage); + return; + } + //Check if this was a VERSION message. + if( sRawMessage.Mid(iIndex, 9) == _T(":\001VERSION") ) + { + if (::GetTickCount() - m_dwLastRequest < 1000){ // excess flood protection + m_dwLastRequest = ::GetTickCount(); + return; + } + m_dwLastRequest = ::GetTickCount(); + + //Get client version. + iIndex += 9; + m_sVersion = _T("eMule") + theApp.m_strCurVersionLong + Irc_Version; + CString sBuild; + sBuild.Format( _T("NOTICE %s :\001VERSION %s\001"), sNickname, m_sVersion ); + m_pIRCSocket->SendString( sBuild ); + return; + } + if( sRawMessage.Mid(iIndex, 6) == _T(":\001PING") ) + { + if (::GetTickCount() - m_dwLastRequest < 1000){ // excess flood protection + m_dwLastRequest = ::GetTickCount(); + return; + } + m_dwLastRequest = ::GetTickCount(); + iIndex += 6; + CString sVerify = sRawMessage.Tokenize(_T(" "), iIndex); + sVerify.Remove(_T('\001')); + CString sBuild; + sBuild.Format( _T("NOTICE %s :\001PING %s\001"), sNickname, sVerify ); + m_pIRCSocket->SendString( sBuild ); + return; + } + if( sRawMessage.Mid(iIndex, 11) == _T(":\001RQSFRIEND") ) + { + if (::GetTickCount() - m_dwLastRequest < 1000){ // excess flood protection + m_dwLastRequest = ::GetTickCount(); + return; + } + m_dwLastRequest = ::GetTickCount(); + + //eMule user requested to add you as friend. + if( !thePrefs.GetIRCAllowEmuleAddFriend() ) + return; + iIndex += 11; + //Adds a little protection + CString sVerify = sRawMessage.Tokenize(_T("|"), iIndex); + CString sIP; + CString sPort; + if(theApp.serverconnect->IsConnected()) + { + //Tell them what server we are on for possible lowID support later. + sIP.Format( _T("%i"), theApp.serverconnect->GetCurrentServer()->GetIP()); + sPort.Format( _T("%i"), theApp.serverconnect->GetCurrentServer()->GetPort()); + } + else + { + //Not on a server. + sIP = _T("0.0.0.0"); + sPort = _T("0"); + } + //Create our response. + CString sBuild; + sBuild.Format(_T("PRIVMSG %s :\001REPFRIEND eMule%s%s|%s|%u:%u|%s:%s|%s|\001"), sTarget, theApp.m_strCurVersionLong, Irc_Version, sVerify, theApp.IsFirewalled() ? 0 : theApp.GetID(), thePrefs.GetPort(), sIP, sPort, md4str(thePrefs.GetUserHash())); + m_pIRCSocket->SendString(sBuild); + sBuild.Format(_T("%s %s"), sTarget, GetResString(IDS_IRC_ADDASFRIEND)); + if (!thePrefs.GetIRCIgnoreEmuleAddFriendMsgs()) + m_pwndIRC->NoticeMessage(_T("*EmuleProto*"), _T(""), sBuild); + return; + } + if( sRawMessage.Mid(iIndex, 11) == _T(":\001REPFRIEND") ) + { + iIndex += 11; + CString sVersion = sRawMessage.Tokenize(_T("|"), iIndex); + CString sVerify = sRawMessage.Tokenize(_T("|"), iIndex); + //Make sure we requested this! + if( m_uVerify != (UINT)_tstoi(sVerify)) + return; + //Pick a new random verify. + SetVerify(); + uint32 uNewClientID = _tstoi(sRawMessage.Tokenize(_T(":"), iIndex)); + uint16 uNewClientPort = (uint16)_tstoi(sRawMessage.Tokenize(_T("|"), iIndex)); + sRawMessage.Tokenize(_T(":"), iIndex); + sRawMessage.Tokenize(_T("|"), iIndex); + CString sHash = sRawMessage.Tokenize(_T("|"), iIndex); + if (theApp.friendlist->IsAlreadyFriend(sHash)) + return; + uchar ucharUserID[16]; + if (!strmd4(sHash,ucharUserID)) + throw CString( _T("SMIRC Error: Received Invalid friend reply") ); + theApp.friendlist->AddFriend( ucharUserID, 0, uNewClientID, uNewClientPort, 0, sTarget, 1); + return; + } + if( sRawMessage.Mid(iIndex, 10) == _T(":\001SENDLINK") ) + { + //Received a ED2K link from someone. + iIndex += 10; + if ( !thePrefs.GetIRCAcceptLinks() ) + { + if( !thePrefs.GetIRCIgnoreEmuleSendLinkMsgs() ) + { + m_pwndIRC->NoticeMessage(_T("*EmuleProto*"), _T(""), sTarget + _T(" attempted to send you a file. If you wanted to accept the files from this person, enable Receive files in the IRC Preferences.")); + } + return; + } + CString sHash = sRawMessage.Tokenize(_T("|"), iIndex); + if (thePrefs.GetIRCAcceptLinksFriendsOnly() && !theApp.friendlist->IsAlreadyFriend(sHash)){ + if( !thePrefs.GetIRCIgnoreEmuleSendLinkMsgs() ) + { + m_pwndIRC->NoticeMessage(_T("*EmuleProto*"), _T(""), sTarget + _T(" attempted to send you a file. If you wanted to accept the files from this person, add him as a friend or change your IRC Preferences.")); + } + return; + } + CString sLink = sRawMessage.Mid(iIndex); + sLink.Remove(_T('\001')); + if( !sLink.IsEmpty() ) + { + CString sBuild; + sBuild.Format( GetResString(IDS_IRC_RECIEVEDLINK), sTarget, sLink ); + if (!thePrefs.GetIRCIgnoreEmuleSendLinkMsgs()) + m_pwndIRC->NoticeMessage(_T("*EmuleProto*"), _T(""), sBuild); + ProcessLink( sLink ); + } + return; + } + } + else + { + //This is a normal channel message.. + if (iIndex < sRawMessage.GetLength() && sRawMessage[iIndex] == _T(':')) + iIndex++; + m_pwndIRC->AddMessage(sTarget, sNickname, sRawMessage.Mid(iIndex)); + return; + } + } + if( sCommand == _T("JOIN") ) + { + //Channel join + CString sChannel = sRawMessage.Tokenize(_T(":"), iIndex); + //If this was you, just add a message to create a new channel.. + //The list of Nicks received from the server will include you, so don't + //add yourself here. + if( sNickname == m_sNick ) + { + m_pwndIRC->AddInfoMessageF(sChannel, GetResString(IDS_IRC_HASJOINED), sNickname, sChannel); + return; + } + + if( !thePrefs.GetIRCIgnoreJoinMessages() || sNickname == m_sNick ) + m_pwndIRC->AddInfoMessageF(sChannel, GetResString(IDS_IRC_HASJOINED), sNickname, sChannel); + //Add new nick to your channel. + m_pwndIRC->m_wndNicks.NewNick( sChannel, sNickname); + return; + + } + if( sCommand == _T("PART") ) + { + //Part message + CString sChannel = sRawMessage.Tokenize(_T(" "), iIndex); + if(sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + if ( sNickname == m_sNick ) + { + //This was you, so remove channel. + m_pwndIRC->m_wndChanSel.RemoveChannel( sChannel ); + return; + } + if( !thePrefs.GetIRCIgnorePartMessages() ) + m_pwndIRC->AddInfoMessageF(sChannel, GetResString(IDS_IRC_HASPARTED), sNickname, sChannel, sRawMessage.Mid(iIndex)); + //Remove nick from your channel. + m_pwndIRC->m_wndNicks.RemoveNick( sChannel, sNickname ); + return; + } + + if( sCommand == _T("TOPIC") ) + { + CString sChannel = sRawMessage.Tokenize(_T(" "), iIndex); + if(sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + CString sMessage = sRawMessage.Mid(iIndex); + m_pwndIRC->AddInfoMessageF(sChannel, _T("* %s changes topic to '%s'"), sNickname, sMessage); + m_pwndIRC->SetTitle( sChannel, sMessage ); + return; + } + + if( sCommand == _T("QUIT") ) + { + if(sRawMessage.Mid(iIndex,1) == _T(":")) + iIndex++; + CString sMessage = sRawMessage.Mid(iIndex); + //This user left the network.. Remove from all Channels.. + m_pwndIRC->m_wndNicks.DeleteNickInAll( sNickname, sMessage ); + return; + } + + if( sCommand == _T("NICK") ) + { + // Someone changed a nick.. + if (sRawMessage.Mid(iIndex,1) == _T(":")) + iIndex++; + bool bOwnNick = false; + CString sNewNick = sRawMessage.Tokenize(_T(" "), iIndex); + if (sNickname == m_sNick) + { + // It was you.. Update! + m_sNick = sNewNick; + thePrefs.SetIRCNick(m_sNick); + bOwnNick = true; + } + // Update new nick in all channels.. + if (!m_pwndIRC->m_wndNicks.ChangeAllNick(sNickname, sNewNick) && bOwnNick) + m_pwndIRC->AddStatusF(GetResString(IDS_IRC_NOWKNOWNAS), sNickname, sNewNick); + return; + } + + if( sCommand == _T("KICK") ) + { + //Someone was kicked from a channel.. + CString sChannel = sRawMessage.Tokenize(_T(" "), iIndex); + CString sNick = sRawMessage.Tokenize(_T(" "), iIndex); + if(sRawMessage.Mid(iIndex,1) == _T(":")) + iIndex++; + + if( sNick == m_sNick ) + { + //It was you! + m_pwndIRC->m_wndChanSel.RemoveChannel( sChannel ); + m_pwndIRC->AddStatusF(GetResString(IDS_IRC_WASKICKEDBY), sNick, sNickname, sRawMessage.Mid(iIndex)); + return; + } + if( !thePrefs.GetIRCIgnoreMiscMessages() ) + m_pwndIRC->AddInfoMessageF(sChannel, GetResString(IDS_IRC_WASKICKEDBY), sNick, sNickname, sRawMessage.Mid(iIndex)); + //Remove nick from your channel. + m_pwndIRC->m_wndNicks.RemoveNick( sChannel, sNick ); + return; + } + if( sCommand == _T("MODE") ) + { + //A mode was set.. + CString sTarget = sRawMessage.Tokenize(_T(" "), iIndex); + if( sTarget.Left(1) == _T("#") ) + { + CString sCommands = sRawMessage.Tokenize(_T(" "), iIndex); + + if( sCommand.IsEmpty() ) + throw CString( _T("SMIRC Error: Received Invalid Mode change.") ); + + CString sParams = sRawMessage.Mid(iIndex); + m_pwndIRC->ParseChangeMode( sTarget, sNickname, sCommands, sParams ); + return; + } + else + { + //The server just set a server user mode that relates to no channels! + //Atm, we do not handle these modes. + return; + } + } + if (sCommand == _T("NOTICE")) + { + CString sTarget = sRawMessage.Tokenize(_T(" "), iIndex); + if (sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + if (!sNickname.IsEmpty()) + m_pwndIRC->NoticeMessage(sNickname, sTarget, sRawMessage.Mid(iIndex)); + else if (!sServername.IsEmpty()) + m_pwndIRC->NoticeMessage(sServername, sTarget, sRawMessage.Mid(iIndex)); + return; + } + + //TODO: Double and trible check this.. I don't know of any 3 letter commands + //So I'm currently assuming it's a numberical command.. + if( sCommand.GetLength() == 3 ) + { + CString sUser = sRawMessage.Tokenize(_T(" "), iIndex); + UINT uCommand = _tstoi(sCommand); + switch(uCommand) + { + //- The server sends Replies 001 to 004 to a user upon + //successful registration. + //001 RPL_WELCOME + //"Welcome to the Internet Relay Network + //!@" + //002 RPL_YOURHOST + //"Your host is , running version " + //003 RPL_CREATED + //"This server was created " + //004 RPL_MYINFO + //" + //" + case 1: + m_pwndIRC->SetLoggedIn(true); + if (thePrefs.GetIRCGetChannelsOnConnect()) + { + CString strCommand(_T("LIST")); + if (thePrefs.GetIRCUseChannelFilter() && !thePrefs.GetIRCChannelFilter().IsEmpty()) { + strCommand += _T(' '); + strCommand += thePrefs.GetIRCChannelFilter(); + } + m_pIRCSocket->SendString(strCommand); + } + ParsePerform(); + /* fall through */ + case 2: + case 3: + case 4: + if (sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + m_pwndIRC->AddStatus(sRawMessage.Mid(iIndex)); + return; + + //- Sent by the server to a user to suggest an alternative + //server. This is often used when the connection is + //refused because the server is already full. + //005 RPL_BOUNCE + //"Try server , port " + + //005 is actually confusing.. Different sites say different things? + //It appears this is also RPL_ISUPPORT which tells you what modes are + //availabile to this server.. + case 5: + { + //This get our viable user modes + m_pwndIRC->AddStatus(sRawMessage.Mid(iIndex)); + CString sSettings = sRawMessage.Tokenize(_T(" "), iIndex); + while (!sSettings.IsEmpty()) + { + if (_tcsncmp(sSettings, _T("PREFIX"), 6) == 0) + { + sSettings = sSettings.Mid(sSettings.Find(_T('='))+1); + CString sModes = sSettings.Mid(1, sSettings.Find(_T(')'))-1); + sSettings = sSettings.Mid(sSettings.Find(_T(')'))+1); + + //Set our channel modes actions + m_pwndIRC->m_wndNicks.m_sUserModeSettings = sModes; + //Set our channel modes symbols + m_pwndIRC->m_wndNicks.m_sUserModeSymbols = sSettings; + } + if (_tcsncmp(sSettings, _T("CHANMODES"), 9) == 0) + { + sSettings = sSettings.Mid(sSettings.Find(_T('='))+1); + + //Mode that adds or removes a nick or address to a list. Always has a parameter. + //Modes of type A return the list when there is no parameter present. + CString sModes = sSettings.Left(sSettings.Find(_T(','))); + sSettings = sSettings.Mid(sSettings.Find(_T(','))+1); + m_pwndIRC->m_wndChanSel.m_sChannelModeSettingsTypeA = sModes; + + //Mode that changes a setting and always has a parameter. + sModes = sSettings.Left(sSettings.Find(_T(','))); + sSettings = sSettings.Mid(sSettings.Find(_T(','))+1); + m_pwndIRC->m_wndChanSel.m_sChannelModeSettingsTypeB = sModes; + + //Mode that changes a setting and only has a parameter when set. + sModes = sSettings.Left(sSettings.Find(_T(','))); + sSettings = sSettings.Mid(sSettings.Find(_T(','))+1); + m_pwndIRC->m_wndChanSel.m_sChannelModeSettingsTypeC = sModes; + + //Mode that changes a setting and never has a parameter. + m_pwndIRC->m_wndChanSel.m_sChannelModeSettingsTypeD = sSettings; + } + sSettings = sRawMessage.Tokenize(_T(" "),iIndex); + } + return; + } + + //- Returned when a NICK message is processed that results + //in an attempt to change to a currently existing + //nickname. + //433 ERR_NICKNAMEINUSE + //" :Nickname is already in use" + case 433: + if (sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + m_pwndIRC->AddStatus(sRawMessage.Mid(iIndex), true, uCommand); + // clear nick and enter the IRC-nick message box on next connect + thePrefs.SetIRCNick(_T("")); + return; + + //- Reply format used by USERHOST to list replies to + //the query list. The reply string is composed as + //follows: + // + //reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname + // + //The '*' indicates whether the client has registered + //as an Operator. The '-' or '+' characters represent + //whether the client has set an AWAY message or not + //respectively. + //302 RPL_USERHOST + //":*1 *( " " )" + case 302: + + //- Reply format used by ISON to list replies to the + //query list. + + //303 RPL_ISON + //":*1 *( " " )" + case 303: + + //- These replies are used with the AWAY command (if + //allowed). RPL_AWAY is sent to any client sending a + //PRIVMSG to a client which is away. RPL_AWAY is only + //sent by the server to which the client is connected. + //Replies RPL_UNAWAY and RPL_NOWAWAY are sent when the + //client removes and sets an AWAY message. + //301 RPL_AWAY + //" :" + //305 RPL_UNAWAY + //":You are no longer marked as being away" + //306 RPL_NOWAWAY + //":You have been marked as being away" + case 301: + case 305: + case 306: + + //- Replies 311 - 313, 317 - 319 are all replies + //generated in response to a WHOIS message. Given that + //there are enough parameters present, the answering + //server MUST either formulate a reply out of the above + //numerics (if the query nick is found) or return an + //error reply. The '*' in RPL_WHOISUSER is there as + //the literal character and not as a wild card. For + //each reply set, only RPL_WHOISCHANNELS may appear + //more than once (for long lists of channel names). + //The '@' and '+' characters next to the channel name + //indicate whether a client is a channel operator or + //has been granted permission to speak on a moderated + //channel. The RPL_ENDOFWHOIS reply is used to mark + //the end of processing a WHOIS message. + //311 RPL_WHOISUSER + //" * :" + //312 RPL_WHOISSERVER + //" :" + //313 RPL_WHOISOPERATOR + //" :is an IRC operator" + //317 RPL_WHOISIDLE + //" :seconds idle" + //318 RPL_ENDOFWHOIS + //" :End of WHOIS list" + //319 RPL_WHOISCHANNELS + //" :*( ( "@" / "+" ) " " )" + case 311: + case 312: + case 313: + case 317: + case 318: + case 319: + + //- When replying to a WHOWAS message, a server MUST use + //the replies RPL_WHOWASUSER, RPL_WHOISSERVER or + //ERR_WASNOSUCHNICK for each nickname in the presented + //list. At the end of all reply batches, there MUST + //be RPL_ENDOFWHOWAS (even if there was only one reply + //and it was an error). + //314 RPL_WHOWASUSER + //" * :" + //369 RPL_ENDOFWHOWAS + //" :End of WHOWAS" + case 314: + case 369: + if (sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + m_pwndIRC->AddStatus(sRawMessage.Mid(iIndex)); + return; + + //- Replies RPL_LIST, RPL_LISTEND mark the actual replies + //with data and end of the server's response to a LIST + //command. If there are no channels available to return, + //only the end reply MUST be sent. + //321 RPL_LISTSTART + //Obsolete. Not used. + //322 RPL_LIST + //" <# visible> :" + //323 RPL_LISTEND + //":End of LIST" + case 321: + //Although it says this is obsolete, so far every server has sent it, so I use it.. :/ + m_pwndIRC->AddStatus(_T("Start of /LIST")); + m_pwndIRC->m_wndChanList.ResetServerChannelList(); + return; + + case 322: { + CString sChannel = sRawMessage.Tokenize(_T(" "), iIndex); + if (sChannel.GetLength() >= 1 && sChannel[0] != _T('#')) + return; + CString sChanNum = sRawMessage.Tokenize(_T(" "), iIndex); + iIndex++; + if (m_pwndIRC->m_wndChanList.AddChannelToList(sChannel, sChanNum, sRawMessage.Mid(iIndex))) + m_pwndIRC->m_wndChanSel.SetActivity(m_pwndIRC->m_wndChanSel.m_pChanList, true); + return; + } + case 323: + + //- When sending a TOPIC message to determine the + //channel topic, one of two replies is sent. If + //the topic is set, RPL_TOPIC is sent back else + //RPL_NOTOPIC. + //325 RPL_UNIQOPIS + //" " + //324 RPL_CHANNELMODEIS + //" " + //331 RPL_NOTOPIC + //" :No topic is set" + //332 RPL_TOPIC + //" :" + case 325: + if (sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + m_pwndIRC->AddStatus(sRawMessage.Mid(iIndex)); + return; + + case 324: { + //A mode was set.. + CString sChannel = sRawMessage.Tokenize(_T(" "), iIndex); + CString sCommands = sRawMessage.Tokenize(_T(" "), iIndex); + m_pwndIRC->ParseChangeMode( sChannel, sNickname, sCommands, sRawMessage.Mid(iIndex) ); + return; + } + case 331: + if (sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + m_pwndIRC->AddStatus(sRawMessage.Mid(iIndex)); + return; + + case 332: { + //Set Channel Topic + CString sChannel = sRawMessage.Tokenize(_T(" "), iIndex); + if (sChannel.GetLength() >= 1 && sChannel[0] != _T('#')) + return; + if (sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + m_pwndIRC->SetTitle(sChannel, sRawMessage.Mid(iIndex)); + m_pwndIRC->AddInfoMessageF(sChannel, _T("* Channel Title: %s"), sRawMessage.Mid(iIndex)); + return; + } + + //- Returned by the server to indicate that the + //attempted INVITE message was successful and is + //being passed onto the end client. + //341 RPL_INVITING + //" " + case 341: + + //- Returned by a server answering a SUMMON message to + //indicate that it is summoning that user. + //342 RPL_SUMMONING + //" :Summoning user to IRC" + case 342: + + //- When listing the 'invitations masks' for a given channel, + //a server is required to send the list back using the + //RPL_INVITELIST and RPL_ENDOFINVITELIST messages. A + //separate RPL_INVITELIST is sent for each active mask. + //After the masks have been listed (or if none present) a + //RPL_ENDOFINVITELIST MUST be sent. + //346 RPL_INVITELIST + //" " + //347 RPL_ENDOFINVITELIST + //" :End of channel invite list + case 346: + case 347: + + //- When listing the 'exception masks' for a given channel, + //a server is required to send the list back using the + //RPL_EXCEPTLIST and RPL_ENDOFEXCEPTLIST messages. A + //separate RPL_EXCEPTLIST is sent for each active mask. + //After the masks have been listed (or if none present) + //a RPL_ENDOFEXCEPTLIST MUST be sent. + //348 RPL_EXCEPTLIST + //" " + //349 RPL_ENDOFEXCEPTLIST + //" :End of channel exception list" + case 348: + case 349: + + //- Reply by the server showing its version details. + //The is the version of the software being + //used (including any patchlevel revisions) and the + // is used to indicate if the server is + //running in "debug mode". + //The "comments" field may contain any comments about + //the version or further version details. + //351 RPL_VERSION + //". :" + case 351: + + //- The RPL_WHOREPLY and RPL_ENDOFWHO pair are used + //to answer a WHO message. The RPL_WHOREPLY is only + //sent if there is an appropriate match to the WHO + //query. If there is a list of parameters supplied + //with a WHO message, a RPL_ENDOFWHO MUST be sent + //after processing each list item with being + //the item. + //352 RPL_WHOREPLY + //" + //( "H" / "G" > ["*"] [ ( "@" / "+" ) ] + //: " + //315 RPL_ENDOFWHO + //" :End of WHO list" + case 352: + case 315: + { + if(sRawMessage.Mid(iIndex, 1) == _T(":")) + iIndex++; + m_pwndIRC->AddStatus(sRawMessage.Mid(iIndex)); + return; + } + + //- To reply to a NAMES message, a reply pair consisting + //of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the + //server back to the client. If there is no channel + //found as in the query, then only RPL_ENDOFNAMES is + //returned. The exception to this is when a NAMES + //message is sent with no parameters and all visible + //channels and contents are sent back in a series of + //RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark + //the end. + //353 RPL_NAMREPLY + //"( "=" / "*" / "@" ) + //:[ "@" / "+" ] *( " " [ "@" / "+" ] ) + //- "@" is used for secret channels, "*" for private + //channels, and "=" for others (public channels). + //366 RPL_ENDOFNAMES + //" :End of NAMES list" + case 353: { + m_pwndIRC->m_wndNicks.ShowWindow(SW_HIDE); + CString sChannelType = sRawMessage.Tokenize(_T(" "), iIndex); + CString sChannel = sRawMessage.Tokenize(_T(" "), iIndex); + if (sChannel.GetLength() >= 1 && sChannel[0] != _T('#')) + return; + iIndex++; + CString sNewNick = sRawMessage.Tokenize(_T(" "), iIndex); + while (!sNewNick.IsEmpty()) + { + m_pwndIRC->m_wndNicks.NewNick(sChannel, sNewNick); + sNewNick = sRawMessage.Tokenize(_T(" "), iIndex); + } + return; + } + case 366: { + CString sChannel = sRawMessage.Tokenize(_T(" "), iIndex); + SendString(_T("MODE ") + sChannel); + m_pwndIRC->m_wndNicks.ShowWindow(SW_SHOW); + iIndex++; + m_pwndIRC->AddStatus(sRawMessage.Mid(iIndex)); + return; + } + + //- In replying to the LINKS message, a server MUST send + //replies back using the RPL_LINKS numeric and mark the + //end of the list using an RPL_ENDOFLINKS reply. + //364 RPL_LINKS + //" : " + //365 RPL_ENDOFLINKS + //" :End of LINKS list" + case 364: + case 365: + + //- When listing the active 'bans' for a given channel, + //a server is required to send the list back using the + //RPL_BANLIST and RPL_ENDOFBANLIST messages. A separate + //RPL_BANLIST is sent for each active banmask. After the + //banmasks have been listed (or if none present) a + //RPL_ENDOFBANLIST MUST be sent. + //367 RPL_BANLIST + //" " + //368 RPL_ENDOFBANLIST + //" :End of channel ban list" + case 367: + case 368: + + //- A server responding to an INFO message is required to + //send all its 'info' in a series of RPL_INFO messages + //with a RPL_ENDOFINFO reply to indicate the end of the + //replies. + //371 RPL_INFO + //":" + //374 RPL_ENDOFINFO + //":End of INFO list" + case 371: + case 374: + + //- When responding to the MOTD message and the MOTD file + //is found, the file is displayed line by line, with + //each line no longer than 80 characters, using + //RPL_MOTD format replies. These MUST be surrounded + //by a RPL_MOTDSTART (before the RPL_MOTDs) and an + //RPL_ENDOFMOTD (after). + //375 RPL_MOTDSTART + //":- Message of the day - " + //372 RPL_MOTD + //":- " + //376 RPL_ENDOFMOTD + //":End of MOTD command" + case 375: + case 372: + case 376: + + //- RPL_YOUREOPER is sent back to a client which has + //just successfully issued an OPER message and gained + //operator status. + //381 RPL_YOUREOPER + //":You are now an IRC operator" + case 381: + + //- If the REHASH option is used and an operator sends + //a REHASH message, an RPL_REHASHING is sent back to + //the operator. + //382 RPL_REHASHING + //" :Rehashing" + case 382: + + //- Sent by the server to a service upon successful + //registration. + //383 RPL_YOURESERVICE + //"You are service " + case 383: + //- When replying to the TIME message, a server MUST send + //the reply using the RPL_TIME format above. The string + //showing the time need only contain the correct day and + //time there. There is no further requirement for the + //time string. + //391 RPL_TIME + //" :" + case 391: + + //- If the USERS message is handled by a server, the + //replies RPL_USERSTART, RPL_USERS, RPL_ENDOFUSERS and + //RPL_NOUSERS are used. RPL_USERSSTART MUST be sent + //first, following by either a sequence of RPL_USERS + //or a single RPL_NOUSER. Following this is + //RPL_ENDOFUSERS. + //392 RPL_USERSSTART + //":UserID Terminal Host" + //393 RPL_USERS + //": " + // 394 RPL_ENDOFUSERS + // ":End of users" + //395 RPL_NOUSERS + //":Nobody logged in" + case 392: + case 393: + case 395: + + //- The RPL_TRACE* are all returned by the server in + //response to the TRACE message. How many are + //returned is dependent on the TRACE message and + //whether it was sent by an operator or not. There + //is no predefined order for which occurs first. + //Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and + //RPL_TRACEHANDSHAKE are all used for connections + //which have not been fully established and are either + //unknown, still attempting to connect or in the + //process of completing the 'server handshake'. + //RPL_TRACELINK is sent by any server which handles + //a TRACE message and has to pass it on to another + //server. The list of RPL_TRACELINKs sent in + //response to a TRACE command traversing the IRC + //network should reflect the actual connectivity of + //the servers themselves along that path. + //RPL_TRACENEWTYPE is to be used for any connection + //which does not fit in the other categories but is + //being displayed anyway. + //RPL_TRACEEND is sent to indicate the end of the list. + //200 RPL_TRACELINK + //"Link + // V + // + //" + //201 RPL_TRACECONNECTING + //"Try. " + //202 RPL_TRACEHANDSHAKE + //"H.S. " + //203 RPL_TRACEUNKNOWN + //"???? []" + //204 RPL_TRACEOPERATOR + //"Oper " + //205 RPL_TRACEUSER + //"User " + //206 RPL_TRACESERVER + //"Serv S C + //@ V" + //207 RPL_TRACESERVICE + //"Service " + //208 RPL_TRACENEWTYPE + //" 0 " + //209 RPL_TRACECLASS + //"Class " + //210 RPL_TRACERECONNECT + //Unused. + //261 RPL_TRACELOG + //"File " + //262 RPL_TRACEEND + //" :End of TRACE" + case 200: + case 201: + case 202: + case 203: + case 204: + case 205: + case 206: + case 207: + case 208: + case 209: + case 210: + case 261: + case 262: + + //- reports statistics on a connection. + //identifies the particular connection, is + //the amount of data that is queued and waiting to be + //sent the number of messages sent, + //and the amount of data sent, in + //Kbytes. and + //are the equivalent of and for received data, respectively.