|
|
|
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
//
|
|
|
|
// Includes
|
|
|
|
//
|
|
|
|
#include "stdpch.h"
|
|
|
|
|
|
|
|
#include "login_xdelta.h"
|
|
|
|
|
|
|
|
#include "nel/misc/file.h"
|
|
|
|
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
|
|
#include <io.h>
|
|
|
|
#endif
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
using namespace NLMISC;
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ntohl like
|
|
|
|
static uint32 netToHost(uint32 src)
|
|
|
|
{
|
|
|
|
#ifdef NL_LITTLE_ENDIAN
|
|
|
|
return ((src & 0x000000ff) << 24) + ((src & 0x0000ff00) << 8) + ((src & 0x00ff0000) >> 8) + ((src & 0xff000000) >> 24);
|
|
|
|
#else
|
|
|
|
return src;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// CXDPFileReader
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
CXDPFileReader::CXDPFileReader()
|
|
|
|
{
|
|
|
|
_GzFile = NULL;
|
|
|
|
_File = NULL;
|
|
|
|
_Pos = 0;
|
|
|
|
_Optimize = false;
|
|
|
|
_OptimPage = 1024*1024; // 1 mo
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
CXDPFileReader::~CXDPFileReader()
|
|
|
|
{
|
|
|
|
if (_Compressed)
|
|
|
|
{
|
|
|
|
if (_Optimize)
|
|
|
|
{
|
|
|
|
freeZipMem();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (_GzFile != NULL)
|
|
|
|
gzclose(_GzFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (_File != NULL)
|
|
|
|
fclose(_File);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDPFileReader::init(const std::string &sFilename, sint32 nLowerBound, sint32 nUpperBound, bool bCompressed)
|
|
|
|
{
|
|
|
|
_LowerBound = nLowerBound;
|
|
|
|
_UpperBound = nUpperBound;
|
|
|
|
_Compressed = bCompressed;
|
|
|
|
|
|
|
|
if (bCompressed)
|
|
|
|
{
|
|
|
|
// First open the file with a normal function
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
|
|
int fd = _open(sFilename.c_str(), _O_BINARY | _O_RDONLY);
|
|
|
|
#else
|
|
|
|
int fd = open(sFilename.c_str(), O_RDONLY);
|
|
|
|
#endif
|
|
|
|
if (fd == -1)
|
|
|
|
return false;
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
|
|
if (_lseek (fd, nLowerBound, SEEK_SET) == -1L)
|
|
|
|
#else
|
|
|
|
if (lseek (fd, nLowerBound, SEEK_SET) == -1L)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
nlwarning("%s: corrupt or truncated delta: cannot seek to %d", sFilename.c_str(), nLowerBound);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
_GzFile = gzdopen(fd, "rb");
|
|
|
|
if (_GzFile == NULL)
|
|
|
|
{
|
|
|
|
nlwarning("gzdopen failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_Optimize)
|
|
|
|
{
|
|
|
|
freeZipMem();
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
uint8 *newBuf = new uint8[_OptimPage];
|
|
|
|
int nbBytesRead = gzread(_GzFile, newBuf, _OptimPage);
|
|
|
|
if (nbBytesRead == 0)
|
|
|
|
{
|
|
|
|
delete [] newBuf;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_ZipMem.push_back(newBuf);
|
|
|
|
if (nbBytesRead < int(_OptimPage))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
gzclose(_GzFile);
|
|
|
|
_GzFile = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_File = fopen(sFilename.c_str(), "rb");
|
|
|
|
if (_File == NULL)
|
|
|
|
return false;
|
|
|
|
fseek(_File, nLowerBound, SEEK_SET);
|
|
|
|
}
|
|
|
|
_Pos = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDPFileReader::read(uint8 *pBuf, sint32 nSize)
|
|
|
|
{
|
|
|
|
if (_Compressed)
|
|
|
|
{
|
|
|
|
if (_Optimize)
|
|
|
|
{
|
|
|
|
while (nSize > 0)
|
|
|
|
{
|
|
|
|
uint32 nPage = _Pos / _OptimPage;
|
|
|
|
uint32 nOffset = _Pos % _OptimPage;
|
|
|
|
nlassert(nPage < _ZipMem.size());
|
|
|
|
|
|
|
|
uint32 nSizeToRead;
|
|
|
|
uint32 nSizeLeftInPage = _OptimPage - nOffset;
|
|
|
|
if (nSize < sint32(nSizeLeftInPage))
|
|
|
|
nSizeToRead = nSize;
|
|
|
|
else
|
|
|
|
nSizeToRead = nSizeLeftInPage;
|
|
|
|
|
|
|
|
memcpy(pBuf, _ZipMem[nPage]+nOffset, nSizeToRead);
|
|
|
|
|
|
|
|
nSize -= nSizeToRead;
|
|
|
|
_Pos += nSizeToRead;
|
|
|
|
pBuf += nSizeToRead;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return gzread(_GzFile, pBuf, nSize) == nSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!_File) return false;
|
|
|
|
return fread(pBuf, 1, nSize, _File) == (uint32)nSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDPFileReader::readUInt32(uint32 &val)
|
|
|
|
{
|
|
|
|
if (!read((uint8*)&val,4)) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDPFileReader::readUInt(uint32 &val)
|
|
|
|
{
|
|
|
|
// This is mostly because I dislike endian, and less to save space on small ints
|
|
|
|
uint8 c;
|
|
|
|
uint8 arr[16];
|
|
|
|
int i = 0;
|
|
|
|
int donebit = 1;
|
|
|
|
int bits;
|
|
|
|
|
|
|
|
while (read(&c, 1))
|
|
|
|
{
|
|
|
|
donebit = c & 0x80;
|
|
|
|
bits = c & 0x7f;
|
|
|
|
|
|
|
|
arr[i++] = bits;
|
|
|
|
|
|
|
|
if (!donebit)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (donebit)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
val = 0;
|
|
|
|
|
|
|
|
for (i -= 1; i >= 0; i -= 1)
|
|
|
|
{
|
|
|
|
val <<= 7;
|
|
|
|
val |= arr[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDPFileReader::readBool(bool &val)
|
|
|
|
{
|
|
|
|
uint8 nTmp;
|
|
|
|
if (!read((uint8*)&nTmp,1)) return false;
|
|
|
|
val = (nTmp != 0);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDPFileReader::readString(std::string &s)
|
|
|
|
{
|
|
|
|
uint32 nLen;
|
|
|
|
s = "";
|
|
|
|
if (!readUInt(nLen)) return false;
|
|
|
|
for (uint32 i = 0; i < nLen; ++i)
|
|
|
|
{
|
|
|
|
uint8 c;
|
|
|
|
if (!read(&c,1)) return false;
|
|
|
|
s += c;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
uint32 CXDPFileReader::getFileSize()
|
|
|
|
{
|
|
|
|
if (_Compressed)
|
|
|
|
{
|
|
|
|
nlassert(true); // Not implemented for the moment
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sint32 nPos = ftell(_File);
|
|
|
|
if (nPos == -1) return 0;
|
|
|
|
fseek(_File, 0, SEEK_END);
|
|
|
|
sint32 nFileSize = ftell(_File);
|
|
|
|
fseek(_File, nPos, SEEK_SET);
|
|
|
|
return nFileSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDPFileReader::seek(uint32 pos)
|
|
|
|
{
|
|
|
|
if (_Compressed)
|
|
|
|
{
|
|
|
|
if (_Optimize)
|
|
|
|
{
|
|
|
|
_Pos = pos;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (gzseek(_GzFile, pos, SEEK_SET) == -1)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!_File) return false;
|
|
|
|
if (fseek(_File, pos, SEEK_SET) != 0)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void CXDPFileReader::freeZipMem()
|
|
|
|
{
|
|
|
|
for (uint32 i = 0; i < _ZipMem.size(); ++i)
|
|
|
|
{
|
|
|
|
delete [] _ZipMem[i];
|
|
|
|
}
|
|
|
|
_ZipMem.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// CXDeltaCtrl
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// SSourceInfo
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool SXDeltaCtrl::SSourceInfo::read(CXDPFileReader &fr)
|
|
|
|
{
|
|
|
|
if (!fr.readString(Name)) return false;
|
|
|
|
if (!fr.read(MD5.Data, 16)) return false;
|
|
|
|
if (!fr.readUInt(Len)) return false;
|
|
|
|
if (!fr.readBool(IsData)) return false;
|
|
|
|
if (!fr.readBool(Sequential)) return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// SXDeltaInst
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool SXDeltaCtrl::SInstruction::read(CXDPFileReader &fr)
|
|
|
|
{
|
|
|
|
if (!fr.readUInt(Index)) return false;
|
|
|
|
if (!fr.readUInt(Offset)) return false;
|
|
|
|
if (!fr.readUInt(Length)) return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool SXDeltaCtrl::read(CXDPFileReader &fr)
|
|
|
|
{
|
|
|
|
uint32 nType, nSize;
|
|
|
|
if (!fr.readUInt32(nType)) return false;
|
|
|
|
nType = netToHost(nType);
|
|
|
|
if (nType != XDELTA_TYPE_CONTROL)
|
|
|
|
{
|
|
|
|
nlwarning("Bad Control type found");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!fr.readUInt32(nSize)) return false;
|
|
|
|
nSize = netToHost(nSize);
|
|
|
|
|
|
|
|
// ----
|
|
|
|
|
|
|
|
if (!fr.read(ToMD5.Data, 16)) return false;
|
|
|
|
if (!fr.readUInt(ToLen)) return false;
|
|
|
|
if (!fr.readBool(HasData)) return false;
|
|
|
|
|
|
|
|
uint32 i, nSourceInfoLen, nInstLen;
|
|
|
|
|
|
|
|
if (!fr.readUInt(nSourceInfoLen)) return false;
|
|
|
|
SourceInfo.resize(nSourceInfoLen);
|
|
|
|
for (i = 0; i < nSourceInfoLen; ++i)
|
|
|
|
SourceInfo[i].read(fr);
|
|
|
|
|
|
|
|
if (!fr.readUInt(nInstLen)) return false;
|
|
|
|
Inst.resize(nInstLen);
|
|
|
|
for (i = 0; i < nInstLen; ++i)
|
|
|
|
Inst[i].read(fr);
|
|
|
|
|
|
|
|
// /////////////////// //
|
|
|
|
// Unpack Instructions //
|
|
|
|
// /////////////////// //
|
|
|
|
|
|
|
|
for (i = 0; i < SourceInfo.size(); ++i)
|
|
|
|
{
|
|
|
|
SSourceInfo &rInfo = SourceInfo[i];
|
|
|
|
rInfo.Position = 0;
|
|
|
|
rInfo.Copies = 0;
|
|
|
|
rInfo.CopyLength = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < Inst.size(); ++i)
|
|
|
|
{
|
|
|
|
SSourceInfo *pInfo = NULL;
|
|
|
|
SInstruction *pInst = &Inst[i];
|
|
|
|
|
|
|
|
if (pInst->Index >= SourceInfo.size())
|
|
|
|
{
|
|
|
|
nlwarning("Out Of Range Source Index : %d", pInst->Index);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
pInfo = &SourceInfo[pInst->Index];
|
|
|
|
|
|
|
|
if (pInfo->Sequential)
|
|
|
|
{
|
|
|
|
pInst->Offset = pInfo->Position;
|
|
|
|
pInfo->Position = pInst->Offset + pInst->Length;
|
|
|
|
}
|
|
|
|
|
|
|
|
pInfo->Copies += 1;
|
|
|
|
pInfo->CopyLength += pInst->Length;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// CXDeltaPatch
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
CXDeltaPatch::ICallBack *CXDeltaPatch::_CallBack = NULL;
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDeltaPatch::load(const string &sFilename)
|
|
|
|
{
|
|
|
|
uint32 i;
|
|
|
|
uint8 c;
|
|
|
|
CIFile in;
|
|
|
|
|
|
|
|
_FileName = sFilename;
|
|
|
|
if (!in.open(sFilename))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
uint8 vMagicBuffer[XDELTA_PREFIX_LEN];
|
|
|
|
in.serialBuffer(vMagicBuffer, XDELTA_PREFIX_LEN);
|
|
|
|
|
|
|
|
uint32 vHeader[XDELTA_HEADER_WORDS];
|
|
|
|
in.serialBuffer((uint8*)&vHeader[0], XDELTA_HEADER_SPACE);
|
|
|
|
|
|
|
|
for (i = 0; i < XDELTA_HEADER_WORDS; ++i)
|
|
|
|
vHeader[i] = netToHost(vHeader[i]);
|
|
|
|
|
|
|
|
// Check the version
|
|
|
|
if (strncmp ((const char *)&vMagicBuffer[0], XDELTA_110_PREFIX, XDELTA_PREFIX_LEN) != 0)
|
|
|
|
{
|
|
|
|
nlwarning("%s bad version or not a delta patch", sFilename.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_Version = "1.1";
|
|
|
|
_Flags = vHeader[0];
|
|
|
|
|
|
|
|
// Get names
|
|
|
|
uint32 nFromNameLen = vHeader[1] >> 16;
|
|
|
|
uint32 nToNameLen = vHeader[1] & 0xffff;
|
|
|
|
|
|
|
|
_FromName = "";
|
|
|
|
_ToName = "";
|
|
|
|
|
|
|
|
for (i = 0; i < nFromNameLen; ++i)
|
|
|
|
{
|
|
|
|
in.serial(c);
|
|
|
|
_FromName += c;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < nToNameLen; ++i)
|
|
|
|
{
|
|
|
|
in.serial(c);
|
|
|
|
_ToName += c;
|
|
|
|
}
|
|
|
|
|
|
|
|
_HeaderOffset = in.getPos();
|
|
|
|
|
|
|
|
// Go to the end of the file
|
|
|
|
in.seek (0, NLMISC::IStream::end);
|
|
|
|
uint32 nFileSize = in.getPos();
|
|
|
|
uint32 nEndCtrlOffset = nFileSize-(4+XDELTA_PREFIX_LEN);
|
|
|
|
in.seek (nEndCtrlOffset, NLMISC::IStream::begin);
|
|
|
|
|
|
|
|
uint32 nCtrlOffset;
|
|
|
|
in.serialBuffer((uint8*)&nCtrlOffset, 4);
|
|
|
|
nCtrlOffset = netToHost(nCtrlOffset);
|
|
|
|
_CtrlOffset = nCtrlOffset;
|
|
|
|
|
|
|
|
// Check at the end of the file if we got the same 'prefix'
|
|
|
|
in.serialBuffer(vMagicBuffer, XDELTA_PREFIX_LEN);
|
|
|
|
if (strncmp ((const char *)&vMagicBuffer[0], XDELTA_110_PREFIX, XDELTA_PREFIX_LEN) != 0)
|
|
|
|
{
|
|
|
|
nlwarning("%s has bad end of file delta is corrupted", sFilename.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
in.close();
|
|
|
|
|
|
|
|
// if the flag patch compressed is on the part from nCtrlOffset to nEndCtrlOffset is the delta compressed
|
|
|
|
CXDPFileReader frFile;
|
|
|
|
if (!frFile.init(sFilename, nCtrlOffset, nEndCtrlOffset, (_Flags & XDELTA_FLAG_PATCH_COMPRESSED) != 0))
|
|
|
|
{
|
|
|
|
nlwarning("%s cannot init file reader", sFilename.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_Ctrl.read(frFile);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
CXDeltaPatch::TApplyResult CXDeltaPatch::apply(const std::string &sFileToPatch, const std::string &sFileOutput, std::string &errorMsg)
|
|
|
|
{
|
|
|
|
if ((_Flags & XDELTA_FLAG_FROM_COMPRESSED) || (_Flags & XDELTA_FLAG_TO_COMPRESSED))
|
|
|
|
{
|
|
|
|
errorMsg = "do not handle compressed from_file or to_file";
|
|
|
|
return ApplyResult_UnsupportedXDeltaFormat;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_Ctrl.SourceInfo.size() == 0)
|
|
|
|
{
|
|
|
|
errorMsg = "no source info";
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_Ctrl.SourceInfo.size() > 2)
|
|
|
|
{
|
|
|
|
errorMsg = "incompatible delta";
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
SXDeltaCtrl::SSourceInfo *pFromSource = NULL;
|
|
|
|
SXDeltaCtrl::SSourceInfo *pDataSource = NULL;
|
|
|
|
|
|
|
|
if (_Ctrl.SourceInfo.size() > 0)
|
|
|
|
{
|
|
|
|
SXDeltaCtrl::SSourceInfo &rInfo = _Ctrl.SourceInfo[0];
|
|
|
|
|
|
|
|
if (rInfo.IsData)
|
|
|
|
{
|
|
|
|
pDataSource = &rInfo;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pFromSource = &rInfo;
|
|
|
|
|
|
|
|
if (_Ctrl.SourceInfo.size() > 1)
|
|
|
|
{
|
|
|
|
errorMsg = "incompatible delta";
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_Ctrl.SourceInfo.size() > 1)
|
|
|
|
{
|
|
|
|
pFromSource = &_Ctrl.SourceInfo[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---
|
|
|
|
|
|
|
|
// Open the file output
|
|
|
|
if (NLMISC::CFile::fileExists(sFileOutput))
|
|
|
|
{
|
|
|
|
errorMsg = toString("output file %s already exists", sFileOutput.c_str());
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
FILE *outFILE = fopen(sFileOutput.c_str(), "wb");
|
|
|
|
if (outFILE == NULL)
|
|
|
|
{
|
|
|
|
errorMsg = toString("cant create %s", sFileOutput.c_str());
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the file to patch
|
|
|
|
FILE *ftpFILE = NULL;
|
|
|
|
bool ftpPresent = false;
|
|
|
|
if (pFromSource)
|
|
|
|
{
|
|
|
|
ftpFILE = fopen(sFileToPatch.c_str(), "rb");
|
|
|
|
if (ftpFILE == NULL)
|
|
|
|
{
|
|
|
|
errorMsg = toString("expecting file %s", sFileToPatch.c_str());
|
|
|
|
fclose(outFILE);
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
fseek (ftpFILE, 0, SEEK_END);
|
|
|
|
uint32 nFileSize = ftell(ftpFILE);
|
|
|
|
fseek (ftpFILE, 0, SEEK_SET);
|
|
|
|
|
|
|
|
fclose (ftpFILE);
|
|
|
|
|
|
|
|
if (nFileSize != pFromSource->Len)
|
|
|
|
{
|
|
|
|
errorMsg = toString("expect from file (%s) of length %d bytes\n", sFileToPatch.c_str(), pFromSource->Len);
|
|
|
|
fclose(outFILE);
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
ftpPresent = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
CXDPFileReader XDFR[2];
|
|
|
|
|
|
|
|
if (_Ctrl.SourceInfo.size() == 1)
|
|
|
|
{
|
|
|
|
SXDeltaCtrl::SSourceInfo &rInfo = _Ctrl.SourceInfo[0];
|
|
|
|
|
|
|
|
if (rInfo.IsData)
|
|
|
|
{
|
|
|
|
// index 0 == Data from patch file
|
|
|
|
if (!XDFR[0].init(_FileName, _HeaderOffset, _CtrlOffset, isPatchCompressed()))
|
|
|
|
{
|
|
|
|
fclose(outFILE);
|
|
|
|
errorMsg = toString("cant load file %s", _FileName.c_str());
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// index 0 == Data from file to patch
|
|
|
|
nlassert(ftpPresent); // If not should be returned before
|
|
|
|
if (!XDFR[0].init(sFileToPatch, 0, 1024*1024*1024, false))
|
|
|
|
{
|
|
|
|
fclose(outFILE);
|
|
|
|
errorMsg = toString("cant load file %s", sFileToPatch.c_str());
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_Ctrl.SourceInfo.size() == 2)
|
|
|
|
{
|
|
|
|
// _Ctrl.SourceInfo[0].IsData must be true
|
|
|
|
nlassert(_Ctrl.SourceInfo[0].IsData);
|
|
|
|
// index 0 == Data from patch file
|
|
|
|
if (!XDFR[0].init(_FileName, _HeaderOffset, _CtrlOffset, isPatchCompressed()))
|
|
|
|
{
|
|
|
|
fclose(outFILE);
|
|
|
|
errorMsg = toString("cant load file %s", _FileName.c_str());
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
// index 1 == Data from file to patch
|
|
|
|
if (!XDFR[1].init(sFileToPatch, 0, 1024*1024*1024, false))
|
|
|
|
{
|
|
|
|
fclose(outFILE);
|
|
|
|
errorMsg = toString("cant load file %s", sFileToPatch.c_str());
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply Patch : Copy Delta Region
|
|
|
|
uint nSaveWritten = 0;
|
|
|
|
uint nLastSaveWritten = 0;
|
|
|
|
uint nStep = _Ctrl.ToLen / 100;
|
|
|
|
|
|
|
|
for (uint32 i = 0; i < _Ctrl.Inst.size(); ++i)
|
|
|
|
{
|
|
|
|
const SXDeltaCtrl::SInstruction *pInst = &_Ctrl.Inst[i];
|
|
|
|
|
|
|
|
if (pInst->Index >= _Ctrl.SourceInfo.size())
|
|
|
|
{
|
|
|
|
fclose(outFILE);
|
|
|
|
errorMsg = toString("Out Of Range Source Index (%d)", pInst->Index);
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
// From
|
|
|
|
CXDPFileReader &rFromXDFR = XDFR[pInst->Index];
|
|
|
|
rFromXDFR.seek(pInst->Offset);
|
|
|
|
|
|
|
|
uint8 buf[1024];
|
|
|
|
uint32 len = pInst->Length;
|
|
|
|
|
|
|
|
while (len > 0)
|
|
|
|
{
|
|
|
|
uint r = min((uint32)1024, len);
|
|
|
|
|
|
|
|
if (!rFromXDFR.read(buf, r))
|
|
|
|
{
|
|
|
|
fclose(outFILE);
|
|
|
|
errorMsg = ("problem reading source");
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fwrite(buf, 1, r, outFILE) != r)
|
|
|
|
{
|
|
|
|
errorMsg = ("problem writing dest");
|
|
|
|
TApplyResult ar = ApplyResult_Error;
|
|
|
|
if (ferror(outFILE))
|
|
|
|
{
|
|
|
|
errorMsg += std::string(" : ") + strerror(errno);
|
|
|
|
if (errno == 28 /*ENOSPC*/)
|
|
|
|
{
|
|
|
|
ar = ApplyResult_DiskFull;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ar = ApplyResult_WriteError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose(outFILE);
|
|
|
|
return ar;
|
|
|
|
}
|
|
|
|
len -= r;
|
|
|
|
|
|
|
|
nSaveWritten += r;
|
|
|
|
|
|
|
|
// Call back
|
|
|
|
if ((nSaveWritten-nLastSaveWritten) > nStep)
|
|
|
|
{
|
|
|
|
nLastSaveWritten = nSaveWritten;
|
|
|
|
if (_CallBack != NULL)
|
|
|
|
if (_Ctrl.ToLen > 0)
|
|
|
|
_CallBack->progress((float)nSaveWritten/(float)_Ctrl.ToLen);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(outFILE);
|
|
|
|
|
|
|
|
// Check output file
|
|
|
|
CXDPFileReader xdfrOut;
|
|
|
|
if (!xdfrOut.init(sFileOutput, 0, 1024*1024*1024, false))
|
|
|
|
{
|
|
|
|
errorMsg = toString("cant open file %s", sFileOutput.c_str());
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
if (!checkIntegrity (xdfrOut, _Ctrl.ToMD5, _Ctrl.ToLen))
|
|
|
|
{
|
|
|
|
errorMsg = toString("integrity problem with output file %s", sFileOutput.c_str());
|
|
|
|
|
|
|
|
// trap : ok cant do the following for the moment !
|
|
|
|
|
|
|
|
// to better report errors, check if the inputs were invalid now
|
|
|
|
/*
|
|
|
|
for (i = 0; i < cont->source_info_len; i += 1)
|
|
|
|
{
|
|
|
|
check_stream_integrity (cont->source_info[i]->in,
|
|
|
|
cont->source_info[i]->md5,
|
|
|
|
cont->source_info[i]->len);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
errorMsg = "";
|
|
|
|
return ApplyResult_Ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDeltaPatch::checkIntegrity(CXDPFileReader &rFR, const CHashKeyMD5 &md5, uint32 nLength)
|
|
|
|
{
|
|
|
|
if (nLength != rFR.getFileSize())
|
|
|
|
{
|
|
|
|
nlwarning("file size different from expected");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
CHashKeyMD5 fileMD5;
|
|
|
|
CMD5Context ctx;
|
|
|
|
ctx.init();
|
|
|
|
|
|
|
|
uint8 buf[1024];
|
|
|
|
while (nLength > 0)
|
|
|
|
{
|
|
|
|
uint r = min((uint32)1024, nLength);
|
|
|
|
|
|
|
|
if (!rFR.read(buf, r))
|
|
|
|
{
|
|
|
|
nlwarning("problem reading file");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.update(buf, r);
|
|
|
|
|
|
|
|
nLength -= r;
|
|
|
|
}
|
|
|
|
ctx.final(fileMD5);
|
|
|
|
|
|
|
|
if (md5 != fileMD5)
|
|
|
|
{
|
|
|
|
nlwarning("integrity test failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tools
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
CXDeltaPatch::TApplyResult CXDeltaPatch::apply(const string &sPatchFilename,
|
|
|
|
const string &sFileToPatchFilename,
|
|
|
|
const string &sOutputFilename,
|
|
|
|
std::string &errorMsg,
|
|
|
|
ICallBack *pCB)
|
|
|
|
{
|
|
|
|
CXDeltaPatch patch;
|
|
|
|
if (!patch.load(sPatchFilename))
|
|
|
|
{
|
|
|
|
errorMsg = toString("cant load patch %s", sPatchFilename.c_str());
|
|
|
|
nlwarning(errorMsg.c_str());
|
|
|
|
return ApplyResult_Error;
|
|
|
|
}
|
|
|
|
_CallBack = pCB;
|
|
|
|
TApplyResult ar = patch.apply(sFileToPatchFilename, sOutputFilename, errorMsg);
|
|
|
|
if (ar != ApplyResult_Ok)
|
|
|
|
{
|
|
|
|
nlwarning(errorMsg.c_str());
|
|
|
|
}
|
|
|
|
return ar;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool CXDeltaPatch::info(const std::string &sPatchFilename)
|
|
|
|
{
|
|
|
|
CXDeltaPatch patch;
|
|
|
|
if (!patch.load(sPatchFilename))
|
|
|
|
{
|
|
|
|
nlwarning ("cant load patch %s",sPatchFilename.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
nlinfo("Patch Name : %s", sPatchFilename.c_str());
|
|
|
|
nlinfo("Flag No Verify : %s", patch.isNoVerify()?"on":"off");
|
|
|
|
nlinfo("Flag From Compressed : %s", patch.isFromCompressed()?"on":"off");
|
|
|
|
nlinfo("Flag To Compressed : %s", patch.isToCompressed()?"on":"off");
|
|
|
|
nlinfo("Flag Patch Compressed : %s\n", patch.isPatchCompressed()?"on":"off");
|
|
|
|
|
|
|
|
nlinfo("Output name : %s", patch.getToName().c_str());
|
|
|
|
nlinfo("Output length : %d", patch.getCtrl().ToLen);
|
|
|
|
nlinfo("Output md5 : %s\n", patch.getCtrl().ToMD5.toString().c_str());
|
|
|
|
|
|
|
|
nlinfo("Patch from segments: %d\n", patch.getCtrl().SourceInfo.size());
|
|
|
|
nlinfo("MD5\t\t\t\t\tLength\tCopies\tUsed\tSeq?\tName");
|
|
|
|
|
|
|
|
for (uint32 i = 0; i < patch.getCtrl().SourceInfo.size(); ++i)
|
|
|
|
{
|
|
|
|
const SXDeltaCtrl::SSourceInfo &rSI = patch.getCtrl().SourceInfo[i];
|
|
|
|
|
|
|
|
nlinfo("%s\t%d\t%d\t%d\t%s\t%s\n",
|
|
|
|
rSI.MD5.toString().c_str(),
|
|
|
|
rSI.Len,
|
|
|
|
rSI.Copies,
|
|
|
|
rSI.CopyLength,
|
|
|
|
rSI.Sequential ? "yes" : "no",
|
|
|
|
rSI.Name.c_str());
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|