1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include "login_xdelta.h"
27 #include "nel/misc/file.h"
36 // ---------------------------------------------------------------------------
38 using namespace NLMISC
;
41 // ---------------------------------------------------------------------------
43 static uint32
netToHost(uint32 src
)
45 #ifdef NL_LITTLE_ENDIAN
46 return ((src
& 0x000000ff) << 24) + ((src
& 0x0000ff00) << 8) + ((src
& 0x00ff0000) >> 8) + ((src
& 0xff000000) >> 24);
52 // ---------------------------------------------------------------------------
53 // ---------------------------------------------------------------------------
55 // ---------------------------------------------------------------------------
56 // ---------------------------------------------------------------------------
58 // ---------------------------------------------------------------------------
59 CXDPFileReader::CXDPFileReader()
65 _OptimPage
= 1024*1024; // 1 mo
68 // ---------------------------------------------------------------------------
69 CXDPFileReader::~CXDPFileReader()
90 // ---------------------------------------------------------------------------
91 bool CXDPFileReader::init(const std::string
&sFilename
, sint32 nLowerBound
, sint32 nUpperBound
, bool bCompressed
)
93 _LowerBound
= nLowerBound
;
94 _UpperBound
= nUpperBound
;
95 _Compressed
= bCompressed
;
99 // First open the file with a normal function
101 int fd
= _wopen(nlUtf8ToWide(sFilename
), _O_BINARY
| _O_RDONLY
);
103 int fd
= open(sFilename
.c_str(), O_RDONLY
);
108 if (_lseek (fd
, nLowerBound
, SEEK_SET
) == -1L)
110 if (lseek (fd
, nLowerBound
, SEEK_SET
) == -1L)
113 nlwarning("%s: corrupt or truncated delta: cannot seek to %d", sFilename
.c_str(), nLowerBound
);
116 _GzFile
= gzdopen(fd
, "rb");
119 nlwarning("gzdopen failed");
128 uint8
*newBuf
= new uint8
[_OptimPage
];
129 int nbBytesRead
= gzread(_GzFile
, newBuf
, _OptimPage
);
130 if (nbBytesRead
== 0)
137 _ZipMem
.push_back(newBuf
);
138 if (nbBytesRead
< int(_OptimPage
))
149 _File
= nlfopen(sFilename
, "rb");
152 fseek(_File
, nLowerBound
, SEEK_SET
);
157 // ---------------------------------------------------------------------------
158 bool CXDPFileReader::read(uint8
*pBuf
, sint32 nSize
)
166 uint32 nPage
= _Pos
/ _OptimPage
;
167 uint32 nOffset
= _Pos
% _OptimPage
;
168 nlassert(nPage
< _ZipMem
.size());
171 uint32 nSizeLeftInPage
= _OptimPage
- nOffset
;
172 if (nSize
< sint32(nSizeLeftInPage
))
175 nSizeToRead
= nSizeLeftInPage
;
177 memcpy(pBuf
, _ZipMem
[nPage
]+nOffset
, nSizeToRead
);
179 nSize
-= nSizeToRead
;
187 return gzread(_GzFile
, pBuf
, nSize
) == nSize
;
192 if (!_File
) return false;
193 return fread(pBuf
, 1, nSize
, _File
) == (uint32
)nSize
;
197 // ---------------------------------------------------------------------------
198 bool CXDPFileReader::readUInt32(uint32
&val
)
200 if (!read((uint8
*)&val
,4)) return false;
204 // ---------------------------------------------------------------------------
205 bool CXDPFileReader::readUInt(uint32
&val
)
207 // This is mostly because I dislike endian, and less to save space on small ints
230 for (i
-= 1; i
>= 0; i
-= 1)
239 // ---------------------------------------------------------------------------
240 bool CXDPFileReader::readBool(bool &val
)
243 if (!read((uint8
*)&nTmp
,1)) return false;
248 // ---------------------------------------------------------------------------
249 bool CXDPFileReader::readString(std::string
&s
)
253 if (!readUInt(nLen
)) return false;
254 for (uint32 i
= 0; i
< nLen
; ++i
)
257 if (!read(&c
,1)) return false;
263 // ---------------------------------------------------------------------------
264 uint32
CXDPFileReader::getFileSize()
268 nlassert(true); // Not implemented for the moment
273 sint32 nPos
= ftell(_File
);
274 if (nPos
== -1) return 0;
275 fseek(_File
, 0, SEEK_END
);
276 sint32 nFileSize
= ftell(_File
);
277 fseek(_File
, nPos
, SEEK_SET
);
282 // ---------------------------------------------------------------------------
283 bool CXDPFileReader::seek(uint32 pos
)
293 if (gzseek(_GzFile
, pos
, SEEK_SET
) == -1)
299 if (!_File
) return false;
300 if (fseek(_File
, pos
, SEEK_SET
) != 0)
306 // ---------------------------------------------------------------------------
307 void CXDPFileReader::freeZipMem()
309 for (uint32 i
= 0; i
< _ZipMem
.size(); ++i
)
311 delete [] _ZipMem
[i
];
316 // ---------------------------------------------------------------------------
317 // ---------------------------------------------------------------------------
319 // ---------------------------------------------------------------------------
320 // ---------------------------------------------------------------------------
323 // ---------------------------------------------------------------------------
324 bool SXDeltaCtrl::SSourceInfo::read(CXDPFileReader
&fr
)
326 if (!fr
.readString(Name
)) return false;
327 if (!fr
.read(MD5
.Data
, 16)) return false;
328 if (!fr
.readUInt(Len
)) return false;
329 if (!fr
.readBool(IsData
)) return false;
330 if (!fr
.readBool(Sequential
)) return false;
336 // ---------------------------------------------------------------------------
337 bool SXDeltaCtrl::SInstruction::read(CXDPFileReader
&fr
)
339 if (!fr
.readUInt(Index
)) return false;
340 if (!fr
.readUInt(Offset
)) return false;
341 if (!fr
.readUInt(Length
)) return false;
346 // ---------------------------------------------------------------------------
347 bool SXDeltaCtrl::read(CXDPFileReader
&fr
)
350 if (!fr
.readUInt32(nType
)) return false;
351 nType
= netToHost(nType
);
352 if (nType
!= XDELTA_TYPE_CONTROL
)
354 nlwarning("Bad Control type found");
357 if (!fr
.readUInt32(nSize
)) return false;
358 nSize
= netToHost(nSize
);
362 if (!fr
.read(ToMD5
.Data
, 16)) return false;
363 if (!fr
.readUInt(ToLen
)) return false;
364 if (!fr
.readBool(HasData
)) return false;
366 uint32 i
, nSourceInfoLen
, nInstLen
;
368 if (!fr
.readUInt(nSourceInfoLen
)) return false;
369 SourceInfo
.resize(nSourceInfoLen
);
370 for (i
= 0; i
< nSourceInfoLen
; ++i
)
371 SourceInfo
[i
].read(fr
);
373 if (!fr
.readUInt(nInstLen
)) return false;
374 Inst
.resize(nInstLen
);
375 for (i
= 0; i
< nInstLen
; ++i
)
378 // /////////////////// //
379 // Unpack Instructions //
380 // /////////////////// //
382 for (i
= 0; i
< SourceInfo
.size(); ++i
)
384 SSourceInfo
&rInfo
= SourceInfo
[i
];
387 rInfo
.CopyLength
= 0;
390 for (i
= 0; i
< Inst
.size(); ++i
)
392 SSourceInfo
*pInfo
= NULL
;
393 SInstruction
*pInst
= &Inst
[i
];
395 if (pInst
->Index
>= SourceInfo
.size())
397 nlwarning("Out Of Range Source Index : %d", pInst
->Index
);
401 pInfo
= &SourceInfo
[pInst
->Index
];
403 if (pInfo
->Sequential
)
405 pInst
->Offset
= pInfo
->Position
;
406 pInfo
->Position
= pInst
->Offset
+ pInst
->Length
;
410 pInfo
->CopyLength
+= pInst
->Length
;
416 // ---------------------------------------------------------------------------
417 // ---------------------------------------------------------------------------
419 // ---------------------------------------------------------------------------
420 // ---------------------------------------------------------------------------
422 CXDeltaPatch::ICallBack
*CXDeltaPatch::_CallBack
= NULL
;
424 // ---------------------------------------------------------------------------
425 bool CXDeltaPatch::load(const string
&sFilename
)
431 _FileName
= sFilename
;
432 if (!in
.open(sFilename
))
435 uint8 vMagicBuffer
[XDELTA_PREFIX_LEN
];
436 in
.serialBuffer(vMagicBuffer
, XDELTA_PREFIX_LEN
);
438 uint32 vHeader
[XDELTA_HEADER_WORDS
];
439 in
.serialBuffer((uint8
*)&vHeader
[0], XDELTA_HEADER_SPACE
);
441 for (i
= 0; i
< XDELTA_HEADER_WORDS
; ++i
)
442 vHeader
[i
] = netToHost(vHeader
[i
]);
445 if (strncmp ((const char *)&vMagicBuffer
[0], XDELTA_110_PREFIX
, XDELTA_PREFIX_LEN
) != 0)
447 nlwarning("%s bad version or not a delta patch", sFilename
.c_str());
455 uint32 nFromNameLen
= vHeader
[1] >> 16;
456 uint32 nToNameLen
= vHeader
[1] & 0xffff;
461 for (i
= 0; i
< nFromNameLen
; ++i
)
467 for (i
= 0; i
< nToNameLen
; ++i
)
473 _HeaderOffset
= in
.getPos();
475 // Go to the end of the file
476 in
.seek (0, NLMISC::IStream::end
);
477 uint32 nFileSize
= in
.getPos();
478 uint32 nEndCtrlOffset
= nFileSize
-(4+XDELTA_PREFIX_LEN
);
479 in
.seek (nEndCtrlOffset
, NLMISC::IStream::begin
);
482 in
.serialBuffer((uint8
*)&nCtrlOffset
, 4);
483 nCtrlOffset
= netToHost(nCtrlOffset
);
484 _CtrlOffset
= nCtrlOffset
;
486 // Check at the end of the file if we got the same 'prefix'
487 in
.serialBuffer(vMagicBuffer
, XDELTA_PREFIX_LEN
);
488 if (strncmp ((const char *)&vMagicBuffer
[0], XDELTA_110_PREFIX
, XDELTA_PREFIX_LEN
) != 0)
490 nlwarning("%s has bad end of file delta is corrupted", sFilename
.c_str());
496 // if the flag patch compressed is on the part from nCtrlOffset to nEndCtrlOffset is the delta compressed
497 CXDPFileReader frFile
;
498 if (!frFile
.init(sFilename
, nCtrlOffset
, nEndCtrlOffset
, (_Flags
& XDELTA_FLAG_PATCH_COMPRESSED
) != 0))
500 nlwarning("%s cannot init file reader", sFilename
.c_str());
509 // ---------------------------------------------------------------------------
510 CXDeltaPatch::TApplyResult
CXDeltaPatch::apply(const std::string
&sFileToPatch
, const std::string
&sFileOutput
, std::string
&errorMsg
)
512 if ((_Flags
& XDELTA_FLAG_FROM_COMPRESSED
) || (_Flags
& XDELTA_FLAG_TO_COMPRESSED
))
514 errorMsg
= "do not handle compressed from_file or to_file";
515 return ApplyResult_UnsupportedXDeltaFormat
;
518 if (_Ctrl
.SourceInfo
.empty())
520 errorMsg
= "no source info";
521 return ApplyResult_Error
;
524 if (_Ctrl
.SourceInfo
.size() > 2)
526 errorMsg
= "incompatible delta";
527 return ApplyResult_Error
;
530 SXDeltaCtrl::SSourceInfo
*pFromSource
= NULL
;
531 // SXDeltaCtrl::SSourceInfo *pDataSource = NULL;
533 if (!_Ctrl
.SourceInfo
.empty())
535 SXDeltaCtrl::SSourceInfo
&rInfo
= _Ctrl
.SourceInfo
[0];
539 // pDataSource = &rInfo;
543 pFromSource
= &rInfo
;
545 if (_Ctrl
.SourceInfo
.size() > 1)
547 errorMsg
= "incompatible delta";
548 return ApplyResult_Error
;
553 if (_Ctrl
.SourceInfo
.size() > 1)
555 pFromSource
= &_Ctrl
.SourceInfo
[1];
560 // Open the file output
561 if (NLMISC::CFile::fileExists(sFileOutput
))
563 errorMsg
= toString("output file %s already exists", sFileOutput
.c_str());
564 return ApplyResult_Error
;
566 FILE *outFILE
= nlfopen(sFileOutput
, "wb");
569 errorMsg
= toString("cant create %s", sFileOutput
.c_str());
570 return ApplyResult_Error
;
573 // Open the file to patch
574 FILE *ftpFILE
= NULL
;
575 bool ftpPresent
= false;
578 ftpFILE
= nlfopen(sFileToPatch
, "rb");
581 errorMsg
= toString("expecting file %s", sFileToPatch
.c_str());
583 return ApplyResult_Error
;
585 fseek (ftpFILE
, 0, SEEK_END
);
586 uint32 nFileSize
= ftell(ftpFILE
);
587 fseek (ftpFILE
, 0, SEEK_SET
);
591 if (nFileSize
!= pFromSource
->Len
)
593 errorMsg
= toString("expect from file (%s) of length %d bytes\n", sFileToPatch
.c_str(), pFromSource
->Len
);
595 return ApplyResult_Error
;
600 CXDPFileReader XDFR
[2];
602 if (_Ctrl
.SourceInfo
.size() == 1)
604 SXDeltaCtrl::SSourceInfo
&rInfo
= _Ctrl
.SourceInfo
[0];
608 // index 0 == Data from patch file
609 if (!XDFR
[0].init(_FileName
, _HeaderOffset
, _CtrlOffset
, isPatchCompressed()))
612 errorMsg
= toString("cant load file %s", _FileName
.c_str());
613 return ApplyResult_Error
;
618 // index 0 == Data from file to patch
619 nlassert(ftpPresent
); // If not should be returned before
620 if (!XDFR
[0].init(sFileToPatch
, 0, 1024*1024*1024, false))
623 errorMsg
= toString("cant load file %s", sFileToPatch
.c_str());
624 return ApplyResult_Error
;
629 if (_Ctrl
.SourceInfo
.size() == 2)
631 // _Ctrl.SourceInfo[0].IsData must be true
632 nlassert(_Ctrl
.SourceInfo
[0].IsData
);
633 // index 0 == Data from patch file
634 if (!XDFR
[0].init(_FileName
, _HeaderOffset
, _CtrlOffset
, isPatchCompressed()))
637 errorMsg
= toString("cant load file %s", _FileName
.c_str());
638 return ApplyResult_Error
;
640 // index 1 == Data from file to patch
641 if (!XDFR
[1].init(sFileToPatch
, 0, 1024*1024*1024, false))
644 errorMsg
= toString("cant load file %s", sFileToPatch
.c_str());
645 return ApplyResult_Error
;
649 // Apply Patch : Copy Delta Region
650 uint nSaveWritten
= 0;
651 uint nLastSaveWritten
= 0;
652 uint nStep
= _Ctrl
.ToLen
/ 100;
654 for (uint32 i
= 0; i
< _Ctrl
.Inst
.size(); ++i
)
656 const SXDeltaCtrl::SInstruction
*pInst
= &_Ctrl
.Inst
[i
];
658 if (pInst
->Index
>= _Ctrl
.SourceInfo
.size())
661 errorMsg
= toString("Out Of Range Source Index (%d)", pInst
->Index
);
662 return ApplyResult_Error
;
666 CXDPFileReader
&rFromXDFR
= XDFR
[pInst
->Index
];
667 rFromXDFR
.seek(pInst
->Offset
);
670 uint32 len
= pInst
->Length
;
674 uint r
= min((uint32
)1024, len
);
676 if (!rFromXDFR
.read(buf
, r
))
679 errorMsg
= ("problem reading source");
680 return ApplyResult_Error
;
683 if (fwrite(buf
, 1, r
, outFILE
) != r
)
685 errorMsg
= ("problem writing dest");
686 TApplyResult ar
= ApplyResult_Error
;
689 errorMsg
+= std::string(" : ") + strerror(errno
);
690 if (errno
== 28 /*ENOSPC*/)
692 ar
= ApplyResult_DiskFull
;
696 ar
= ApplyResult_WriteError
;
707 if ((nSaveWritten
-nLastSaveWritten
) > nStep
)
709 nLastSaveWritten
= nSaveWritten
;
710 if (_CallBack
!= NULL
)
712 _CallBack
->progress((float)nSaveWritten
/(float)_Ctrl
.ToLen
);
721 CXDPFileReader xdfrOut
;
722 if (!xdfrOut
.init(sFileOutput
, 0, 1024*1024*1024, false))
724 errorMsg
= toString("cant open file %s", sFileOutput
.c_str());
725 return ApplyResult_Error
;
727 if (!checkIntegrity (xdfrOut
, _Ctrl
.ToMD5
, _Ctrl
.ToLen
))
729 errorMsg
= toString("integrity problem with output file %s", sFileOutput
.c_str());
731 // trap : ok cant do the following for the moment !
733 // to better report errors, check if the inputs were invalid now
735 for (i = 0; i < cont->source_info_len; i += 1)
737 check_stream_integrity (cont->source_info[i]->in,
738 cont->source_info[i]->md5,
739 cont->source_info[i]->len);
742 return ApplyResult_Error
;
745 return ApplyResult_Ok
;
748 // ---------------------------------------------------------------------------
749 bool CXDeltaPatch::checkIntegrity(CXDPFileReader
&rFR
, const CHashKeyMD5
&md5
, uint32 nLength
)
751 if (nLength
!= rFR
.getFileSize())
753 nlwarning("file size different from expected");
764 uint r
= min((uint32
)1024, nLength
);
766 if (!rFR
.read(buf
, r
))
768 nlwarning("problem reading file");
780 nlwarning("integrity test failed");
788 // ---------------------------------------------------------------------------
789 CXDeltaPatch::TApplyResult
CXDeltaPatch::apply(const string
&sPatchFilename
,
790 const string
&sFileToPatchFilename
,
791 const string
&sOutputFilename
,
792 std::string
&errorMsg
,
796 if (!patch
.load(sPatchFilename
))
798 errorMsg
= toString("cant load patch %s", sPatchFilename
.c_str());
799 nlwarning(errorMsg
.c_str());
800 return ApplyResult_Error
;
803 TApplyResult ar
= patch
.apply(sFileToPatchFilename
, sOutputFilename
, errorMsg
);
804 if (ar
!= ApplyResult_Ok
)
806 nlwarning(errorMsg
.c_str());
811 // ---------------------------------------------------------------------------
812 bool CXDeltaPatch::info(const std::string
&sPatchFilename
)
815 if (!patch
.load(sPatchFilename
))
817 nlwarning ("cant load patch %s",sPatchFilename
.c_str());
820 nlinfo("Patch Name : %s", sPatchFilename
.c_str());
821 nlinfo("Flag No Verify : %s", patch
.isNoVerify()?"on":"off");
822 nlinfo("Flag From Compressed : %s", patch
.isFromCompressed()?"on":"off");
823 nlinfo("Flag To Compressed : %s", patch
.isToCompressed()?"on":"off");
824 nlinfo("Flag Patch Compressed : %s\n", patch
.isPatchCompressed()?"on":"off");
826 nlinfo("Output name : %s", patch
.getToName().c_str());
827 nlinfo("Output length : %d", patch
.getCtrl().ToLen
);
828 nlinfo("Output md5 : %s\n", patch
.getCtrl().ToMD5
.toString().c_str());
830 nlinfo("Patch from segments: %d\n", patch
.getCtrl().SourceInfo
.size());
831 nlinfo("MD5\t\t\t\t\tLength\tCopies\tUsed\tSeq?\tName");
833 for (uint32 i
= 0; i
< patch
.getCtrl().SourceInfo
.size(); ++i
)
835 const SXDeltaCtrl::SSourceInfo
&rSI
= patch
.getCtrl().SourceInfo
[i
];
837 nlinfo("%s\t%d\t%d\t%d\t%s\t%s\n",
838 rSI
.MD5
.toString().c_str(),
842 rSI
.Sequential
? "yes" : "no",