Linux multi-monitor fullscreen support
[ryzomcore.git] / ryzom / client / src / login_xdelta.cpp
blobad02f095a228a5f9859805a21cb6f3f2b4cd9f4e
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
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/>.
21 // Includes
23 #include "stdpch.h"
25 #include "login_xdelta.h"
27 #include "nel/misc/file.h"
29 #ifdef NL_OS_WINDOWS
30 #include <io.h>
31 #endif
32 #include <fcntl.h>
33 #include <errno.h>
36 // ---------------------------------------------------------------------------
38 using namespace NLMISC;
39 using namespace std;
41 // ---------------------------------------------------------------------------
42 // ntohl like
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);
47 #else
48 return src;
49 #endif
52 // ---------------------------------------------------------------------------
53 // ---------------------------------------------------------------------------
54 // CXDPFileReader
55 // ---------------------------------------------------------------------------
56 // ---------------------------------------------------------------------------
58 // ---------------------------------------------------------------------------
59 CXDPFileReader::CXDPFileReader()
61 _GzFile = NULL;
62 _File = NULL;
63 _Pos = 0;
64 _Optimize = false;
65 _OptimPage = 1024*1024; // 1 mo
68 // ---------------------------------------------------------------------------
69 CXDPFileReader::~CXDPFileReader()
71 if (_Compressed)
73 if (_Optimize)
75 freeZipMem();
77 else
79 if (_GzFile != NULL)
80 gzclose(_GzFile);
83 else
85 if (_File != NULL)
86 fclose(_File);
90 // ---------------------------------------------------------------------------
91 bool CXDPFileReader::init(const std::string &sFilename, sint32 nLowerBound, sint32 nUpperBound, bool bCompressed)
93 _LowerBound = nLowerBound;
94 _UpperBound = nUpperBound;
95 _Compressed = bCompressed;
97 if (bCompressed)
99 // First open the file with a normal function
100 #ifdef NL_OS_WINDOWS
101 int fd = _wopen(nlUtf8ToWide(sFilename), _O_BINARY | _O_RDONLY);
102 #else
103 int fd = open(sFilename.c_str(), O_RDONLY);
104 #endif
105 if (fd == -1)
106 return false;
107 #ifdef NL_OS_WINDOWS
108 if (_lseek (fd, nLowerBound, SEEK_SET) == -1L)
109 #else
110 if (lseek (fd, nLowerBound, SEEK_SET) == -1L)
111 #endif
113 nlwarning("%s: corrupt or truncated delta: cannot seek to %d", sFilename.c_str(), nLowerBound);
114 return false;
116 _GzFile = gzdopen(fd, "rb");
117 if (_GzFile == NULL)
119 nlwarning("gzdopen failed");
120 return false;
123 if (_Optimize)
125 freeZipMem();
126 for(;;)
128 uint8 *newBuf = new uint8[_OptimPage];
129 int nbBytesRead = gzread(_GzFile, newBuf, _OptimPage);
130 if (nbBytesRead == 0)
132 delete [] newBuf;
133 break;
135 else
137 _ZipMem.push_back(newBuf);
138 if (nbBytesRead < int(_OptimPage))
139 break;
142 gzclose(_GzFile);
143 _GzFile = NULL;
147 else
149 _File = nlfopen(sFilename, "rb");
150 if (_File == NULL)
151 return false;
152 fseek(_File, nLowerBound, SEEK_SET);
154 _Pos = 0;
155 return true;
157 // ---------------------------------------------------------------------------
158 bool CXDPFileReader::read(uint8 *pBuf, sint32 nSize)
160 if (_Compressed)
162 if (_Optimize)
164 while (nSize > 0)
166 uint32 nPage = _Pos / _OptimPage;
167 uint32 nOffset = _Pos % _OptimPage;
168 nlassert(nPage < _ZipMem.size());
170 uint32 nSizeToRead;
171 uint32 nSizeLeftInPage = _OptimPage - nOffset;
172 if (nSize < sint32(nSizeLeftInPage))
173 nSizeToRead = nSize;
174 else
175 nSizeToRead = nSizeLeftInPage;
177 memcpy(pBuf, _ZipMem[nPage]+nOffset, nSizeToRead);
179 nSize -= nSizeToRead;
180 _Pos += nSizeToRead;
181 pBuf += nSizeToRead;
183 return true;
185 else
187 return gzread(_GzFile, pBuf, nSize) == nSize;
190 else
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;
201 return true;
204 // ---------------------------------------------------------------------------
205 bool CXDPFileReader::readUInt(uint32 &val)
207 // This is mostly because I dislike endian, and less to save space on small ints
208 uint8 c;
209 uint8 arr[16];
210 int i = 0;
211 int donebit = 1;
212 int bits;
214 while (read(&c, 1))
216 donebit = c & 0x80;
217 bits = c & 0x7f;
219 arr[i++] = bits;
221 if (!donebit)
222 break;
225 if (donebit)
226 return false;
228 val = 0;
230 for (i -= 1; i >= 0; i -= 1)
232 val <<= 7;
233 val |= arr[i];
236 return true;
239 // ---------------------------------------------------------------------------
240 bool CXDPFileReader::readBool(bool &val)
242 uint8 nTmp;
243 if (!read((uint8*)&nTmp,1)) return false;
244 val = (nTmp != 0);
245 return true;
248 // ---------------------------------------------------------------------------
249 bool CXDPFileReader::readString(std::string &s)
251 uint32 nLen;
252 s.clear();
253 if (!readUInt(nLen)) return false;
254 for (uint32 i = 0; i < nLen; ++i)
256 uint8 c;
257 if (!read(&c,1)) return false;
258 s += c;
260 return true;
263 // ---------------------------------------------------------------------------
264 uint32 CXDPFileReader::getFileSize()
266 if (_Compressed)
268 nlassert(true); // Not implemented for the moment
269 return 0;
271 else
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);
278 return nFileSize;
282 // ---------------------------------------------------------------------------
283 bool CXDPFileReader::seek(uint32 pos)
285 if (_Compressed)
287 if (_Optimize)
289 _Pos = pos;
291 else
293 if (gzseek(_GzFile, pos, SEEK_SET) == -1)
294 return false;
297 else
299 if (!_File) return false;
300 if (fseek(_File, pos, SEEK_SET) != 0)
301 return false;
303 return true;
306 // ---------------------------------------------------------------------------
307 void CXDPFileReader::freeZipMem()
309 for (uint32 i = 0; i < _ZipMem.size(); ++i)
311 delete [] _ZipMem[i];
313 _ZipMem.clear();
316 // ---------------------------------------------------------------------------
317 // ---------------------------------------------------------------------------
318 // CXDeltaCtrl
319 // ---------------------------------------------------------------------------
320 // ---------------------------------------------------------------------------
322 // SSourceInfo
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;
332 return true;
335 // SXDeltaInst
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;
343 return true;
346 // ---------------------------------------------------------------------------
347 bool SXDeltaCtrl::read(CXDPFileReader &fr)
349 uint32 nType, nSize;
350 if (!fr.readUInt32(nType)) return false;
351 nType = netToHost(nType);
352 if (nType != XDELTA_TYPE_CONTROL)
354 nlwarning("Bad Control type found");
355 return false;
357 if (!fr.readUInt32(nSize)) return false;
358 nSize = netToHost(nSize);
360 // ----
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)
376 Inst[i].read(fr);
378 // /////////////////// //
379 // Unpack Instructions //
380 // /////////////////// //
382 for (i = 0; i < SourceInfo.size(); ++i)
384 SSourceInfo &rInfo = SourceInfo[i];
385 rInfo.Position = 0;
386 rInfo.Copies = 0;
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);
398 return false;
401 pInfo = &SourceInfo[pInst->Index];
403 if (pInfo->Sequential)
405 pInst->Offset = pInfo->Position;
406 pInfo->Position = pInst->Offset + pInst->Length;
409 pInfo->Copies += 1;
410 pInfo->CopyLength += pInst->Length;
413 return true;
416 // ---------------------------------------------------------------------------
417 // ---------------------------------------------------------------------------
418 // CXDeltaPatch
419 // ---------------------------------------------------------------------------
420 // ---------------------------------------------------------------------------
422 CXDeltaPatch::ICallBack *CXDeltaPatch::_CallBack = NULL;
424 // ---------------------------------------------------------------------------
425 bool CXDeltaPatch::load(const string &sFilename)
427 uint32 i;
428 uint8 c;
429 CIFile in;
431 _FileName = sFilename;
432 if (!in.open(sFilename))
433 return false;
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]);
444 // Check the version
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());
448 return false;
451 _Version = "1.1";
452 _Flags = vHeader[0];
454 // Get names
455 uint32 nFromNameLen = vHeader[1] >> 16;
456 uint32 nToNameLen = vHeader[1] & 0xffff;
458 _FromName.clear();
459 _ToName.clear();
461 for (i = 0; i < nFromNameLen; ++i)
463 in.serial(c);
464 _FromName += c;
467 for (i = 0; i < nToNameLen; ++i)
469 in.serial(c);
470 _ToName += c;
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);
481 uint32 nCtrlOffset;
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());
491 return false;
494 in.close();
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());
501 return false;
504 _Ctrl.read(frFile);
506 return true;
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];
537 if (rInfo.IsData)
539 // pDataSource = &rInfo;
541 else
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];
558 // ---
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");
567 if (outFILE == NULL)
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;
576 if (pFromSource)
578 ftpFILE = nlfopen(sFileToPatch, "rb");
579 if (ftpFILE == NULL)
581 errorMsg = toString("expecting file %s", sFileToPatch.c_str());
582 fclose(outFILE);
583 return ApplyResult_Error;
585 fseek (ftpFILE, 0, SEEK_END);
586 uint32 nFileSize = ftell(ftpFILE);
587 fseek (ftpFILE, 0, SEEK_SET);
589 fclose (ftpFILE);
591 if (nFileSize != pFromSource->Len)
593 errorMsg = toString("expect from file (%s) of length %d bytes\n", sFileToPatch.c_str(), pFromSource->Len);
594 fclose(outFILE);
595 return ApplyResult_Error;
597 ftpPresent = true;
600 CXDPFileReader XDFR[2];
602 if (_Ctrl.SourceInfo.size() == 1)
604 SXDeltaCtrl::SSourceInfo &rInfo = _Ctrl.SourceInfo[0];
606 if (rInfo.IsData)
608 // index 0 == Data from patch file
609 if (!XDFR[0].init(_FileName, _HeaderOffset, _CtrlOffset, isPatchCompressed()))
611 fclose(outFILE);
612 errorMsg = toString("cant load file %s", _FileName.c_str());
613 return ApplyResult_Error;
616 else
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))
622 fclose(outFILE);
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()))
636 fclose(outFILE);
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))
643 fclose(outFILE);
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())
660 fclose(outFILE);
661 errorMsg = toString("Out Of Range Source Index (%d)", pInst->Index);
662 return ApplyResult_Error;
665 // From
666 CXDPFileReader &rFromXDFR = XDFR[pInst->Index];
667 rFromXDFR.seek(pInst->Offset);
669 uint8 buf[1024];
670 uint32 len = pInst->Length;
672 while (len > 0)
674 uint r = min((uint32)1024, len);
676 if (!rFromXDFR.read(buf, r))
678 fclose(outFILE);
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;
687 if (ferror(outFILE))
689 errorMsg += std::string(" : ") + strerror(errno);
690 if (errno == 28 /*ENOSPC*/)
692 ar = ApplyResult_DiskFull;
694 else
696 ar = ApplyResult_WriteError;
699 fclose(outFILE);
700 return ar;
702 len -= r;
704 nSaveWritten += r;
706 // Call back
707 if ((nSaveWritten-nLastSaveWritten) > nStep)
709 nLastSaveWritten = nSaveWritten;
710 if (_CallBack != NULL)
711 if (_Ctrl.ToLen > 0)
712 _CallBack->progress((float)nSaveWritten/(float)_Ctrl.ToLen);
718 fclose(outFILE);
720 // Check output file
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;
744 errorMsg.clear();
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");
754 return false;
757 CHashKeyMD5 fileMD5;
758 CMD5Context ctx;
759 ctx.init();
761 uint8 buf[1024];
762 while (nLength > 0)
764 uint r = min((uint32)1024, nLength);
766 if (!rFR.read(buf, r))
768 nlwarning("problem reading file");
769 return false;
772 ctx.update(buf, r);
774 nLength -= r;
776 ctx.final(fileMD5);
778 if (md5 != fileMD5)
780 nlwarning("integrity test failed");
781 return false;
783 return true;
786 // Tools
788 // ---------------------------------------------------------------------------
789 CXDeltaPatch::TApplyResult CXDeltaPatch::apply(const string &sPatchFilename,
790 const string &sFileToPatchFilename,
791 const string &sOutputFilename,
792 std::string &errorMsg,
793 ICallBack *pCB)
795 CXDeltaPatch patch;
796 if (!patch.load(sPatchFilename))
798 errorMsg = toString("cant load patch %s", sPatchFilename.c_str());
799 nlwarning(errorMsg.c_str());
800 return ApplyResult_Error;
802 _CallBack = pCB;
803 TApplyResult ar = patch.apply(sFileToPatchFilename, sOutputFilename, errorMsg);
804 if (ar != ApplyResult_Ok)
806 nlwarning(errorMsg.c_str());
808 return ar;
811 // ---------------------------------------------------------------------------
812 bool CXDeltaPatch::info(const std::string &sPatchFilename)
814 CXDeltaPatch patch;
815 if (!patch.load(sPatchFilename))
817 nlwarning ("cant load patch %s",sPatchFilename.c_str());
818 return false;
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(),
839 rSI.Len,
840 rSI.Copies,
841 rSI.CopyLength,
842 rSI.Sequential ? "yes" : "no",
843 rSI.Name.c_str());
845 return true;