// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
//-----------------------------------------------------------------------------
// includes
//-----------------------------------------------------------------------------
#include "stdpch.h"
#include "nel/misc/path.h"
#include "nel/misc/sha1.h"
#include "bnp_patch.h"
#define PERSISTENT_TOKEN_FAMILY RyzomTokenFamily
//-----------------------------------------------------------------------------
// Handy utility routines
//-----------------------------------------------------------------------------
void normaliseBnpFileName(std::string& fileName)
{
BOMB_IF(fileName.empty(),"Can't normalise an empty bnp file name",return);
if (NLMISC::CFile::getExtension(fileName).empty() && fileName[fileName.size()-1]!='.')
fileName+=".bnp";
}
void applyDate (const std::string &sFilename, uint32 nDate)
{
// change the file time
if(nDate != 0)
{
// _utimbuf utb;
// utb.actime = utb.modtime = nDate;
NLMISC::CFile::setRWAccess(sFilename);
NLMISC::CFile::setFileModificationDate(sFilename, nDate);
// _utime (sFilename.c_str (), &utb);
}
}
//-----------------------------------------------------------------------------
// class CBNPFileVersion
//-----------------------------------------------------------------------------
CBNPFileVersion::CBNPFileVersion()
{
_FileTime= 0;
_FileSize= 0;
_7ZFileSize=0;
_PatchSize= 0;
_VersionNumber= std::numeric_limits::max();
}
// setup record contents from a file name and version number
// returns false if the file didn't exist
bool CBNPFileVersion::setup(const std::string &fileName, uint32 versionNumber)
{
// make sure the file exists...
BOMB_IF(!NLMISC::CFile::fileExists(fileName),("File not found: "+fileName).c_str(),return false);
// generate a hash key for the file and store it in a vector of uint32
CHashKey hashKey= getSHA1(fileName);
nlassert(hashKey.HashKeyString.size()==20);
_HashKey.clear();
for (uint32 i=0;i<5;++i)
_HashKey.push_back(*(uint32*)&hashKey.HashKeyString[4*i]);
// get the other file properties
_FileTime= NLMISC::CFile::getFileModificationDate(fileName);
_FileSize= NLMISC::CFile::getFileSize(fileName);
// setup the version number
_VersionNumber= versionNumber;
return true;
}
void CBNPFileVersion::setVersionNumber(uint32 nVersionNumber)
{
_VersionNumber = nVersionNumber;
}
void CBNPFileVersion::set7ZipFileSize(uint32 n7ZFileSize)
{
_7ZFileSize = n7ZFileSize;
}
void CBNPFileVersion::setPatchSize(uint32 nPatchSize)
{
_PatchSize = nPatchSize;
}
void CBNPFileVersion::setTimeStamp(uint32 nTimeStamp)
{
_FileTime = nTimeStamp;
}
// accessors
uint32 CBNPFileVersion::getVersionNumber() const
{
return _VersionNumber;
}
uint32 CBNPFileVersion::getTimeStamp() const
{
return _FileTime;
}
uint32 CBNPFileVersion::getFileSize() const
{
return _FileSize;
}
uint32 CBNPFileVersion::get7ZFileSize() const
{
return _7ZFileSize;
}
uint32 CBNPFileVersion::getPatchSize() const
{
return _PatchSize;
}
CHashKey CBNPFileVersion::getHashKey() const
{
nlassert(_HashKey.size()==5);
CHashKey hashKey;
for (uint32 i=0;i<5;++i)
{
*(uint32*)&hashKey.HashKeyString[4*i]=_HashKey[i];
}
return hashKey;
}
// == operator
bool CBNPFileVersion::operator==(const CBNPFileVersion& other) const
{
// make sure the file sizes match
if (_FileSize!=other._FileSize)
return false;
// make sure the hash keys match
if (_HashKey!=other._HashKey)
return false;
// we don't compare version numbers or file dates as they're not interesting
return true;
}
// != operator
bool CBNPFileVersion::operator!=(const CBNPFileVersion& other) const
{
return !operator==(other);
}
//-----------------------------------------------------------------------------
// Persistent data for CBNPFileVersion
//-----------------------------------------------------------------------------
#define PERSISTENT_CLASS CBNPFileVersion
#define PERSISTENT_DATA \
PROP(uint32,_VersionNumber) \
PROP(uint32,_FileSize) \
PROP(uint32,_7ZFileSize) \
PROP(uint32,_FileTime) \
PROP(uint32,_PatchSize) \
PROP_VECT(uint32,_HashKey)
//# pragma message( PERSISTENT_GENERATION_MESSAGE )
#include "persistent_data_template.h"
#undef PERSISTENT_CLASS
#undef PERSISTENT_DATA
//-----------------------------------------------------------------------------
// class CBNPFile
//-----------------------------------------------------------------------------
CBNPFile::CBNPFile()
{
_IsIncremental= true;
}
bool CBNPFile::addVersion(const std::string& bnpDirectory, const std::string& /* refDirectory */, IVersionNumberGenerator& version)
{
nlinfo("Checking need to add new version to file: %s",_FileName.c_str());
// perform a quick check to see if the time stamp and file size of the new BNP file match the last version in the index
std::string fullFileName= bnpDirectory+_FileName;
if (!NLMISC::CFile::fileExists(fullFileName))
return false;
if (!_Versions.empty())
{
if ((NLMISC::CFile::getFileSize(fullFileName)==(uint32)_Versions.back().getFileSize())
&& (NLMISC::CFile::getFileModificationDate(fullFileName)==(uint32)_Versions.back().getTimeStamp()))
return true;
NLMISC::InfoLog->displayNL("File: %s\n size(%d != %d) || time(%d != %d)",
fullFileName.c_str(),
NLMISC::CFile::getFileSize(fullFileName),
(uint32)_Versions.back().getFileSize(),
NLMISC::CFile::getFileModificationDate(fullFileName),
(uint32)_Versions.back().getTimeStamp()
);
}
// create a new record for the BNP file that we have on the disk at the moment
// if no file was found then give up (return)
CBNPFileVersion fileVersion;
bool result= fileVersion.setup(fullFileName,~0u);
if (result==false)
return false;
// compare the fileVersion record to the last record in the history.
// If they don't match then append it
if (_Versions.empty() || _Versions.back()!=fileVersion)
{
// if we haven't yet generated the version number for this version then go for it now
version.grabVersionNumber();
fileVersion.setVersionNumber(version.getPackageVersionNumber());
// make sure that our version numbers are ever increasing... it would be fatal to have an out-of-order version
if (!_Versions.empty())
nlassert(_Versions.back().getVersionNumber()1)
{
_Versions[0]= _Versions.back();
_Versions.resize(1);
}
return true;
}
uint32 CBNPFile::getLatestVersionNumber(uint32 max) const
{
if (_Versions.empty())
return 0;
uint32 i=0;
for (i=(uint32)_Versions.size();i--;)
if (_Versions[i].getVersionNumber()<=max)
return _Versions[i].getVersionNumber();
nlinfo("File %s didn't exist before version %d",_FileName.c_str(),max);
return 0;
}
uint32 CBNPFile::versionCount() const
{
return (uint32)_Versions.size();
}
const CBNPFileVersion& CBNPFile::getVersion(uint32 idx) const
{
nlassert(idx1)
{
_Versions[0]= _Versions.back();
_Versions.resize(1);
}
}
bool CBNPFile::isIncremental()
{
return _IsIncremental;
}
//-----------------------------------------------------------------------------
// Persistent data for CBNPFile
//-----------------------------------------------------------------------------
#define PERSISTENT_CLASS CBNPFile
#define PERSISTENT_DATA\
PROP(std::string,_FileName)\
STRUCT_VECT(_Versions)
//# pragma message( PERSISTENT_GENERATION_MESSAGE )
#include "persistent_data_template.h"
#undef PERSISTENT_CLASS
#undef PERSISTENT_DATA
//-----------------------------------------------------------------------------
// class CBNPFileSet
//-----------------------------------------------------------------------------
void CBNPFileSet::removeFile(const std::string &filename)
{
for( uint k = 0; k < _Files.size(); ++k)
{
if (_Files[k].getFileName() == filename)
{
_Files.erase(_Files.begin() + k);
}
}
}
// add a version to the file
// returns highest version number in files after operation
uint32 CBNPFileSet::addVersion(const std::string& bnpDirectory, const std::string& refDirectory, IVersionNumberGenerator& version)
{
nlinfo("Updating package index...");
uint32 result=0;
// add versions to different files
for (uint32 i=(uint32)_Files.size();i--;)
if (_Files[i].addVersion(bnpDirectory,refDirectory,version)!=false)
result= std::max(result,_Files[i].getLatestVersionNumber());
return result;
}
// look through the referenced files for the highest version number
uint32 CBNPFileSet::getVersionNumber() const
{
uint32 result=0;
for (uint32 i=(uint32)_Files.size();i--;)
result= std::max(result,_Files[i].getLatestVersionNumber());
return result;
}
void CBNPFileSet::clear()
{
_Files.clear();
}
uint32 CBNPFileSet::fileCount() const
{
return (uint32)_Files.size();
}
const CBNPFile& CBNPFileSet::getFile(uint32 idx) const
{
return const_cast(this)->getFile(idx);
}
const CBNPFile* CBNPFileSet::getFileByName(const std::string& fileName) const
{
return const_cast(this)->getFileByName(fileName);
}
CBNPFile& CBNPFileSet::getFile(uint32 idx)
{
nlassert(idxsetIncremental(false);
return;
}
// file is new so need to add it
std::string s= fileName;
normaliseBnpFileName(s);
nlinfo("- adding file: %s",s.c_str());
_Files.resize(_Files.size()+1);
_Files.back().setFileName(s);
_Files.back().setIncremental(isIncremental);
}
//-----------------------------------------------------------------------------
// Persistent data for CBNPFileSet
//-----------------------------------------------------------------------------
#define PERSISTENT_CLASS CBNPFileSet
#define PERSISTENT_DATA\
STRUCT_VECT(_Files)
//# pragma message( PERSISTENT_GENERATION_MESSAGE )
#include "persistent_data_template.h"
#undef PERSISTENT_CLASS
#undef PERSISTENT_DATA
//-----------------------------------------------------------------------------
// class CBNPCategory
//-----------------------------------------------------------------------------
CBNPCategory::CBNPCategory()
{
_IsOptional=true;
_IsIncremental=true;
_Hidden=false;
}
bool CBNPCategory::hasFile(const std::string &fileName) const
{
return std::find(_Files.begin(), _Files.end(), fileName) != _Files.end();
}
const std::string& CBNPCategory::getName() const
{
return _Name;
}
void CBNPCategory::setName(const std::string& name)
{
_Name=name;
}
void CBNPCategory::setOptional(bool value)
{
_IsOptional= value;
}
bool CBNPCategory::isOptional() const
{
return _IsOptional;
}
void CBNPCategory::setUnpackTo(const std::string &n)
{
_UnpackTo = n;
}
const std::string &CBNPCategory::getUnpackTo() const
{
return _UnpackTo;
}
void CBNPCategory::setIncremental(bool value)
{
_IsIncremental= value;
}
bool CBNPCategory::isIncremental() const
{
return _IsIncremental;
}
void CBNPCategory::setCatRequired(const std::string &cat)
{
_CatRequired = cat;
}
const std::string &CBNPCategory::getCatRequired() const
{
return _CatRequired;
}
void CBNPCategory::setHidden(bool value)
{
_Hidden = value;
}
bool CBNPCategory::isHidden() const
{
return _Hidden;
}
uint32 CBNPCategory::fileCount() const
{
return (uint32)_Files.size();
}
const std::string& CBNPCategory::getFile(uint32 idx) const
{
nlassert(idx::const_iterator it = _Category.begin(); it != _Category.end(); ++it)
{
if (it->hasFile(fileName))
{
return &(*it);
}
}
return NULL;
}
void CBNPCategorySet::deleteCategory(uint32 index)
{
nlassert(index < _Category.size());
_Category.erase(_Category.begin() + index);
}
uint32 CBNPCategorySet::fileCount() const
{
uint32 result=0;
for (uint32 i=0;i<_Category.size();++i)
result+=_Category[i].fileCount();
return result;
}
const std::string& CBNPCategorySet::getFile(uint32 idx) const
{
uint32 i=0;
for (;;++i)
{
nlassert(i<_Category.size());
if (_Category[i].fileCount()>idx)
break;
idx-=_Category[i].fileCount();
}
return _Category[i].getFile(idx);
}
uint32 CBNPCategorySet::categoryCount() const
{
return (uint32)_Category.size();
}
CBNPCategory& CBNPCategorySet::getCategory(uint32 idx)
{
nlassert(idx(this)->getCategory(idx);
}
const CBNPCategory* CBNPCategorySet::getCategory(const std::string& categoryName) const
{
// look for a category with matching name
for (uint32 i=0;ifileCount();++i)
if (theCategory->getFile(i)==fileName)
return;
// the file doesn't already exist so add it
theCategory->addFile(fileName);
nlinfo("- File added to category %s::%s",categoryName.c_str(),fileName.c_str());
}
bool CBNPCategorySet::isFileIncremental(const std::string& fileName) const
{
// for each category
for (uint32 i=0;iisOptional() &&
// _SelectedCategories.find(theCategory->getName())==_SelectedCategories.end()))
// continue;
// }
// else
// {
// if (theCategory->isOptional()!=isOptionalFlag)
// continue;
// }
//
// // if one of the files is not up to date then we return 'true' as we need to patch
// for (uint32 j=0;jfileCount();++j)
// if (!isFileUpToDate(theCategory->getFile(j)))
// return true;
// }
//
// return false;
//}
//
//bool CBNPUnpatcher::isPatchMandatory()
//{
// return _isPatch(false,false);
//}
//
//bool CBNPUnpatcher::isPatchOptional()
//{
// return _isPatch(false,true);
//}
//
//bool CBNPUnpatcher::isPatchRequired()
//{
// return _isPatch(true);
//}
//
//// return true if it's necessary to download patches for the selected options
//bool CBNPUnpatcher::isDownloadRequired()
//{
// if (!isPatchRequired())
// return false;
//
// std::vector hold;
// getDownloadPatches(hold);
// return !hold.empty();
//}
//
//// return true if it's necessary to download the next patch that needs to be applied (in order)
//bool CBNPUnpatcher::isNextPatchDownloadRequired()
//{
// if (!isPatchRequired())
// return false;
//
// return getNextPatch().getRequiresDownload();
//}
//
//
////-----------------------------------------------------------------------------
//// crunching routines
//
//// scan the directories for files - identifies the set of required patches
//// and also the set of these patches that is missing from the patch directory
//void CBNPUnpatcher::scanForFiles()
//{
// std::vector patchFiles;
// std::vector patchFiles;
// std::vector dataFiles;
//
// // get the list of files in the patch directory
// NLMISC::CPath::getPathContent(getPatchDirectory(),false,false,true,patchFiles);
//
// // get the list of files in the data directory
// NLMISC::CPath::getPathContent(getDataDirectory(),false,false,true,dataFiles);
//
// for (uint32 i=0;i<_Categories.fileCount();++i)
// {
// _Categories.getFile()
// result.push_back(_Categories.getCategory(i).getName());
// }
// xxx
//}
//
//// apply the mandatory and selected optional patches
//// nlerror if isDownloadRequired() is not false
//void CBNPUnpatcher::applyPatches()
//{
// nlassert(isIndexUpToDate()==true);
// nlassert(!isDownloadRequired());
// xxx
//}
//
//// apply the next patch (in order)
//// nlerror if isNextPatchDownloadRequired() is not false
//void CBNPUnpatcher::applyNextPatch()
//{
// // note that if the index isn't up to date then it is classed as the next patch
// nlassert(!isNextPatchDownloadRequired());
// xxx
//}
//
//
////-----------------------------------------------------------------------------
//// managing the set of selected optional patch categories
//
//// get the names of all optional categories
//void CBNPUnpatcher::getAllOptionalCategories(std::vector& result)
//{
// nlassert(isIndexUpToDate()==true);
//
// result.clear();
// for (uint32 i=0;i<_Categories.categoryCount();++i)
// {
// result.push_back(_Categories.getCategory(i).getName());
// }
//}
//
//// get the names of the optional categories that require patching
//void CBNPUnpatcher::getPatchableOptionalCategories(std::vector& result)
//{
// nlassert(isIndexUpToDate()==true);
//
// result.clear();
// for (uint32 i=0;i<_Categories.categoryCount();++i)
// {
// CBNPCategory* theCategory= _Categories.getCategory(i);
// uint32 j;
//
// // if one of the files is not up to date then we return 'true' as we need to patch
// for (j=0;jfileCount();++j)
// if (!isFileUpToDate(theCategory->getFile(j)))
// break;
//
// // if we broke out before the end of the for loop then we need to add this category
// if (jfileCount())
// result.push_back(_Categories.getCategory(i).getName());
// }
//}
//
//// select or unselect an optional package
//void CBNPUnpatcher::setOptionalCategorySelectFlag(const std::string& categoryName, bool value)
//{
// nlassert(isIndexUpToDate()==true);
// _SelectedCategories.insert(categoryName);
//}
//
//// select or unselect all optional packages
//void CBNPUnpatcher::setAllOptionalCategorySelectFlags(bool value)
//{
// nlassert(isIndexUpToDate()==true);
//
// _SelectedCategories.clear();
// for (uint32 i=0;i<_Categories.categoryCount();++i)
// {
// _SelectedCategories.insert(_Categories.getCategory(i).getName());
// }
//}
//
//
////-----------------------------------------------------------------------------
//// getting lists of applicable patches
//
//// get the ordered list of mandatory + optional patches that need to be applied to update selected packages
//void CBNPUnpatcher::getSelectedPatches(std::vector& result)
//{
// nlassert(isIndexUpToDate()==true);
//
// std::vector mandatoryPatches;
// getMandatoryPatches(mandatoryPatches);
//
// std::vector optionalPatches;
// getSelectedOptionalPatches(optionalPatches);
//
// result= mandatoryPatches+ optionalPatches;
//}
//
//// get the ordered list of optional patches that need to be applied to update selected packages
//void CBNPUnpatcher::getSelectedOptionalPatches(std::vector& result)
//{
// nlassert(isIndexUpToDate()==true);
// xxx
//}
//
//// get the ordered list of patches that need to be applied for a minimum update
//void CBNPUnpatcher::getMandatoryPatches(std::vector& result)
//{
// nlassert(isIndexUpToDate()==true);
// xxx
//}
//
//// get an ordered list of the patches that need to be applied for a full update
//void CBNPUnpatcher::getAllPatches(std::vector& result)
//{
// // store the selected category set in temporary variable
// std::set selectedCategories= _SelectedCategories;
//
// // select all of the categories and delegate to getSelectedPatches()
// setAllOptionalCategorySelectFlags();
// getSelectedPatches(result);
//
// // restore the _SelectedCategories set from temp variable
// _SelectedCategories= selectedCategories;
//}
//
//// get the name of the next patch that needs to be applied (for progress display)
//const std::string& CBNPUnpatcher::getNextPatchName()
//{
// CBNPPatchDescription patch= getNextPatch();
// return patch.getTargetFileName()+NLMISC::toString(":%d",patch.getVersion());
//}
//
//// get the patch description for the next patch to apply
//CBNPPatchDescription CBNPUnpatcher::getNextPatch()
//{
// // make sure that index is up to date and patching is required
// nlassert(isIndexUpToDate());
// nlassert(isPatchRequired());
//
// // treat the case of !uptodate() here and get the index file as the next patch
// if (!isIndexUpToDate())
// {
// return getIndexFileDownloadDescription();
// }
//
// for (uint32 i=0;i<_Categories.categoryCount();++i)
// {
// CBNPCategory* theCategory= _Categories.getCategory(i);
// uint32 j;
//
// // if one of the files is not up to date then we return 'true' as we need to patch
// for (j=0;jfileCount();++j)
// if (!isFileUpToDate(theCategory->getFile(j)))
// break;
//
// // if we broke out before the end of the for loop then we need to add this category
// if (jfileCount())
// result.push_back(_Categories.getCategory(i).getName());
// }
//
// xxx
//}
//
//// get the list of patches that need to be downloaded
//void CBNPUnpatcher::getSelectedDownloadPatches(std::vector& result)
//{
// nlassert(isIndexUpToDate()==true);
// xxx
//}
//
//// get the list of patches that need to be downloaded
//void CBNPUnpatcher::getAllDownloadPatches(std::vector& result)
//{
// // store the selected category set in temporary variable
// std::set selectedCategories= _SelectedCategories;
//
// // select all of the categories and delegate to getSelectedDownloadPatches()
// setAllOptionalCategorySelectFlags();
// getSelectedDownloadPatches(result);
//
// // restore the _SelectedCategories set from temp variable
// _SelectedCategories= selectedCategories;
//}
//
//
////-----------------------------------------------------------------------------
//
//
//#endif