Added spawnCrystalItem
[ryzomcore.git] / ryzom / client / src / login_patch.cpp
blobb97d410befe72e084b1d20b6e2d2f7613762253f
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2020 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014 Matthew LAGOE (Botanic) <cyberempires@gmail.com>
6 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 //
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 // Includes
25 #include "stdpch.h"
27 #include <sys/stat.h>
29 #ifndef NL_OS_WINDOWS
30 #include <unistd.h>
31 #endif
33 #ifdef NL_OS_MAC
34 #include "app_bundle_utils.h"
35 #endif
37 #include <memory>
38 #include <errno.h>
40 #define USE_CURL
42 #ifdef USE_CURL
43 #include <curl/curl.h>
44 #endif
46 #include <zlib.h>
48 #include "nel/misc/debug.h"
49 #include "nel/misc/path.h"
50 #include "nel/misc/thread.h"
51 #include "nel/misc/sha1.h"
52 #include "nel/misc/big_file.h"
53 #include "nel/misc/i18n.h"
54 #include "nel/misc/cmd_args.h"
55 #include "nel/misc/seven_zip.h"
56 #include "nel/web/curl_certificates.h"
58 #include "game_share/bg_downloader_msg.h"
60 #include "login_patch.h"
61 #include "login.h"
62 #include "user_agent.h"
65 #ifndef RY_BG_DOWNLOADER
66 #include "client_cfg.h"
67 #else
68 #include "../client_background_downloader/client_background_downloader.h"
69 #define __CLIENT_INSTALL_EXE__
70 #endif
72 #ifdef NL_OS_WINDOWS
73 #include <direct.h>
74 #endif
77 // Namespaces
81 using namespace std;
82 using namespace NLMISC;
85 extern string VersionName;
86 extern string R2ServerVersion;
88 #ifdef __CLIENT_INSTALL_EXE__
89 extern std::string TheTmpInstallDirectory;
90 extern std::string ClientLauncherUrl;
91 #else
92 std::string TheTmpInstallDirectory = "patch/client_install";
93 #endif
95 extern NLMISC::CCmdArgs Args;
97 // ****************************************************************************
98 // ****************************************************************************
99 // ****************************************************************************
100 // CPatchManager
101 // ****************************************************************************
102 // ****************************************************************************
103 // ****************************************************************************
105 struct EPatchDownloadException : public Exception
107 EPatchDownloadException() : Exception( "Download Error" ) {}
108 EPatchDownloadException( const std::string& str ) : Exception( str ) {}
109 virtual ~EPatchDownloadException() throw(){}
113 CPatchManager *CPatchManager::_Instance = NULL;
115 static std::string ClientRootPath;
117 // ****************************************************************************
118 CPatchManager::CPatchManager() : State("t_state"), DataScanState("t_data_scan_state")
120 DescFilename = "ryzom_xxxxx.idx";
122 #ifdef NL_OS_WINDOWS
123 UpdateBatchFilename = "updt_nl.bat";
124 UpgradeBatchFilename = "upgd_nl.bat";
125 #else
126 UpdateBatchFilename = "updt_nl.sh";
127 UpgradeBatchFilename = "upgd_nl.sh";
128 #endif
130 std::string rootPath;
132 if (ClientCfg.getDefaultConfigLocation(rootPath))
134 // use same directory as client_default.cfg
135 rootPath = CFile::getPath(rootPath);
137 else
139 // use current directory
140 rootPath = CPath::getCurrentPath();
143 setClientRootPath(rootPath);
145 VerboseLog = true;
147 PatchThread = NULL;
148 CheckThread = NULL;
149 InstallThread = NULL;
150 ScanDataThread = NULL;
151 DownloadThread = NULL;
152 Thread = NULL;
154 LogSeparator = "\n";
155 ValidDescFile = false;
157 MustLaunchBatFile = false;
159 DownloadInProgress = false;
160 _AsyncDownloader = NULL;
161 _StateListener = NULL;
162 _StartRyzomAtEnd = true;
165 // ****************************************************************************
166 void CPatchManager::setClientRootPath(const std::string& clientRootPath)
168 ClientRootPath = CPath::standardizePath(clientRootPath);
169 ClientPatchPath = CPath::standardizePath(ClientRootPath + "unpack");
171 // Delete the .sh file because it's not useful anymore
172 std::string fullUpdateBatchFilename = ClientRootPath + UpdateBatchFilename;
174 if (NLMISC::CFile::fileExists(fullUpdateBatchFilename))
175 NLMISC::CFile::deleteFile(fullUpdateBatchFilename);
177 WritableClientDataPath = CPath::standardizePath(ClientRootPath + "data");
179 #ifdef NL_OS_MAC
180 ReadableClientDataPath = CPath::standardizePath(getAppBundlePath() + "/Contents/Resources/data");
181 #elif defined(NL_OS_UNIX)
182 ReadableClientDataPath = CPath::standardizePath(getRyzomSharePrefix() + "/data");
183 if (CFile::isDirectory(ReadableClientDataPath)) ReadableClientDataPath.clear();
184 #else
185 ReadableClientDataPath.clear();
186 #endif
188 if (ReadableClientDataPath.empty()) ReadableClientDataPath = WritableClientDataPath;
191 // ****************************************************************************
192 void CPatchManager::setErrorMessage(const std::string &message)
194 _ErrorMessage = message;
197 // ****************************************************************************
198 void CPatchManager::forceStopCheckThread()
200 PatchThread->StopAsked = true;
203 // ****************************************************************************
204 void CPatchManager::forceStopPatchThread()
206 CheckThread->StopAsked = true;
209 // ****************************************************************************
210 void CPatchManager::init(const std::vector<std::string>& patchURIs, const std::string &sServerPath, const std::string &sServerVersion)
212 uint i;
213 PatchServers.clear();
215 for (i=0; i<patchURIs.size(); ++i)
217 PatchServers.push_back(CPatchServer(patchURIs[i]));
220 srand(NLMISC::CTime::getSecondsSince1970());
221 UsedServer = (sint)(((double)rand() / ((double)RAND_MAX+1.0)) * (double)PatchServers.size());
223 ServerPath = CPath::standardizePath (sServerPath);
224 ServerVersion = sServerVersion;
226 string::size_type pos = ServerPath.find ("@");
227 if (pos != string::npos)
228 DisplayedServerPath = "http://" + ServerPath.substr (pos+1);
229 else
230 DisplayedServerPath = ServerPath;
232 NLMISC::CFile::createDirectory(ClientPatchPath);
233 NLMISC::CFile::createDirectory(WritableClientDataPath);
236 // try to read the version file from the server (that will replace the version number)
239 CConfigFile *cf;
241 #ifdef RY_BG_DOWNLOADER
242 cf = &theApp.ConfigFile;
243 #else
244 cf = &ClientCfg.ConfigFile;
245 #endif
247 // App name matches Domain on the SQL server
248 std::string appName = cf->getVarPtr("Application")
249 ? cf->getVar("Application").asString(0)
250 : "default";
252 std::string versionFileName = appName + ".version";
253 getServerFile(versionFileName);
255 // ok, we have the file, extract version number (aka build number) and the
256 // version name if present
258 CIFile versionFile(ClientPatchPath + versionFileName);
259 char buffer[1024];
260 versionFile.getline(buffer, 1024);
261 CSString line(buffer);
263 #ifdef NL_DEBUG
264 CConfigFile::CVar *forceVersion = cf->getVarPtr("ForceVersion");
266 if (forceVersion != NULL)
268 line = forceVersion->asString();
270 #endif
272 // Use the version specified in this file, if the file does not contain an asterisk
273 if (line[0] != '*')
275 ServerVersion = line.firstWord(true);
276 VersionName = line.firstWord(true);
279 // force the R2ServerVersion
280 R2ServerVersion = ServerVersion;
282 #ifdef __CLIENT_INSTALL_EXE__
284 //The install program load a the url of the mini web site in the patch directory
286 std::string clientLauncherUrl = "client_launcher_url.txt";
287 bool ok = true;
290 uint32 nServerVersion;
291 fromString(ServerVersion, nServerVersion);
292 std::string url = toString("%05u/%s", nServerVersion, clientLauncherUrl.c_str());
293 // The client version is different from the server version : download new description file
294 getServerFile(url.c_str(), false); // For the moment description file is not zipped
296 catch (...)
298 // fallback to patch root directory
301 getServerFile(clientLauncherUrl.c_str(), false); // For the moment description file is not zipped
303 catch(...)
305 ok = false;
309 if (ok)
311 CIFile versionFile;
312 if (versionFile.open(ClientPatchPath+clientLauncherUrl) )
314 char buffer[1024];
315 versionFile.getline(buffer, 1024);
316 ClientLauncherUrl = std::string(buffer);
320 #endif
322 catch (...)
324 // no version file
328 // retrieve the current client version, according to .idx
329 readClientVersionAndDescFile();
332 // ***************************************************************************
333 void CPatchManager::readClientVersionAndDescFile()
337 ValidDescFile = false;
338 vector<string> vFiles;
339 CPath::getPathContent(ClientPatchPath, false, false, true, vFiles);
340 uint32 nVersion = 0xFFFFFFFF;
341 uint32 nNewVersion;
342 for (uint32 i = 0; i < vFiles.size(); ++i)
344 string sName = NLMISC::CFile::getFilename(vFiles[i]);
345 string sExt = NLMISC::CFile::getExtension(sName);
346 string sBase = sName.substr(0, sName.rfind('_'));
347 if ((sExt == "idx") && (sBase == "ryzom"))
349 string val = sName.substr(sName.rfind('_')+1, 5);
350 if (fromString(val, nNewVersion) && ((nNewVersion > nVersion) || (nVersion == 0xFFFFFFFF)))
351 nVersion = nNewVersion;
354 if (nVersion != 0xFFFFFFFF)
355 readDescFile(nVersion);
356 else
357 DescFilename = "unknown";
358 ValidDescFile = true;
360 catch(const Exception &)
362 nlwarning("EXCEPTION CATCH: readClientVersionAndDescFile() failed - not important");
363 // Not important that there is no desc file
367 // ****************************************************************************
368 void CPatchManager::startCheckThread(bool includeBackgroundPatch)
370 if (CheckThread != NULL)
372 nlwarning ("check thread is already running");
373 return;
375 if (Thread != NULL)
377 nlwarning ("a thread is already running");
378 return;
381 _ErrorMessage.clear();
383 CheckThread = new CCheckThread(includeBackgroundPatch);
384 nlassert (CheckThread != NULL);
386 Thread = IThread::create (CheckThread);
387 nlassert (Thread != NULL);
388 Thread->start ();
391 // ****************************************************************************
392 bool CPatchManager::isCheckThreadEnded(bool &ok)
394 if (CheckThread == NULL)
396 ok = false;
397 return true;
400 bool end = CheckThread->Ended;
401 if (end)
403 ok = CheckThread->CheckOk;
404 stopCheckThread();
407 return end;
410 // ****************************************************************************
411 void CPatchManager::stopCheckThread()
413 if(CheckThread && Thread)
415 Thread->wait();
416 delete Thread;
417 Thread = NULL;
418 delete CheckThread;
419 CheckThread = NULL;
423 // Return the position of the inserted/found category
424 sint32 updateCat(vector<CPatchManager::SPatchInfo::SCat> &rOutVec, CPatchManager::SPatchInfo::SCat &rInCat)
426 uint32 i;
427 for (i = 0; i < rOutVec.size(); ++i)
429 if (rOutVec[i].Name == rInCat.Name)
430 break;
433 if (i == rOutVec.size())
435 rOutVec.push_back(rInCat);
437 else
439 rOutVec[i].Size += rInCat.Size;
440 rOutVec[i].FinalFileSize += rInCat.FinalFileSize;
441 rOutVec[i].SZipSize += rInCat.SZipSize;
444 return i;
447 // ****************************************************************************
448 void CPatchManager::getInfoToDisp(SPatchInfo &piOut)
450 piOut.NonOptCat.clear();
451 piOut.OptCat.clear();
452 piOut.ReqCat.clear();
454 // Convert FilesToPatch vector that must be initialized into something human readable
455 uint32 i;
456 for (i = 0; i < FilesToPatch.size(); ++i)
458 SFileToPatch &rFTP = FilesToPatch[i];
459 // Find the category of the file
460 sint32 nCat = -1;
461 const CBNPCategorySet &rAllCats = DescFile.getCategories();
462 for (uint32 j = 0; j < rAllCats.categoryCount(); ++j)
464 const CBNPCategory &rCat = rAllCats.getCategory(j);
465 for (uint32 k = 0; k < rCat.fileCount(); ++k)
467 if (rCat.getFile(k) == rFTP.FileName)
469 nCat = j;
470 break;
474 if (nCat != -1)
475 break;
478 if (nCat != -1)
480 // Add the category found, if already there just update the size
481 const CBNPCategory &rCat = rAllCats.getCategory(nCat);
482 string sCatName = rCat.getName();
483 // Size of all patches
484 uint32 nTotalPatchesSize = 0;
485 for (uint32 j = 0; j < rFTP.PatcheSizes.size(); ++j)
486 nTotalPatchesSize += rFTP.PatcheSizes[j];
488 SPatchInfo::SCat c;
489 c.Name = sCatName;
490 c.Size = nTotalPatchesSize;
491 c.SZipSize = rFTP.SZFileSize;
492 c.FinalFileSize = rFTP.FinalFileSize;
493 if (!rCat.getCatRequired().empty())
495 // Ensure the required cat exists
496 SPatchInfo::SCat rc;
497 rc.Name = rCat.getCatRequired();
498 c.Req = updateCat(piOut.ReqCat, rc);
501 // In which category of category should we add it ?
502 if (rCat.isOptional())
504 if (rCat.isHidden())
505 updateCat(piOut.ReqCat, c);
506 else
507 updateCat(piOut.OptCat, c);
509 else
511 updateCat(piOut.NonOptCat, c);
516 for (i = 0; i < OptionalCat.size(); ++i)
518 const CBNPCategory *pCat = DescFile.getCategories().getCategory(OptionalCat[i]);
519 nlassert(pCat != NULL);
521 SPatchInfo::SCat c;
522 c.Name = pCat->getName();
524 if (!pCat->getCatRequired().empty())
526 // Ensure the required cat exists
527 SPatchInfo::SCat rc;
528 rc.Name = pCat->getCatRequired();
529 c.Req = updateCat(piOut.ReqCat, rc);
532 updateCat(piOut.OptCat, c);
536 void stopSoundMngr();
538 // ****************************************************************************
539 // TODO : use selected categories to patch a list of files
540 void CPatchManager::startPatchThread(const vector<string> &CategoriesSelected, bool applyPatch)
542 if (PatchThread != NULL)
544 nlwarning ("check thread is already running");
545 return;
547 if (Thread != NULL)
549 nlwarning ("a thread is already running");
550 return;
553 _ErrorMessage.clear();
555 PatchThread = new CPatchThread(applyPatch);
556 nlassert (PatchThread != NULL);
558 // Select all the files we have to patch depending on non-optional categories and selected categories
559 uint32 i, j, k;
560 // Add non-optional categories
561 vector<string> CatsSelected = CategoriesSelected;
562 PatchThread->clear();
563 const CBNPCategorySet &rAllCats = DescFile.getCategories();
564 for (i = 0; i < rAllCats.categoryCount(); ++i)
566 const CBNPCategory &rCat = rAllCats.getCategory(i);
567 if (!rCat.isOptional())
568 CatsSelected.push_back(rCat.getName());
571 // Add all required categories
572 uint32 nSize = (uint32)CatsSelected.size();
575 nSize = (uint32)CatsSelected.size();
577 for (i = 0; i < CatsSelected.size(); ++i)
579 const CBNPCategory *pCat = rAllCats.getCategory(CatsSelected[i]);
580 if (pCat == NULL) continue;
581 if (pCat->getCatRequired().empty()) continue;
582 // Check if the category required is already present
583 for (j = 0; j < CatsSelected.size(); ++j)
585 const CBNPCategory *pCat2 = rAllCats.getCategory(CatsSelected[j]);
586 if (pCat2->getName() == pCat->getCatRequired())
587 break;
589 // Not present ?
590 if (j == CatsSelected.size())
592 CatsSelected.push_back(pCat->getCatRequired());
593 break;
597 while(nSize != CatsSelected.size());
599 // Select files
600 for (i = 0; i < CatsSelected.size(); ++i)
602 // Find the category from the name
603 const CBNPCategory *pCat = rAllCats.getCategory(CatsSelected[i]);
604 if (pCat != NULL)
606 for (j = 0; j < pCat->fileCount(); ++j)
608 const string &rFilename = pCat->getFile(j);
609 const CBNPFileSet &rFileSet = DescFile.getFiles();
610 const CBNPFile *pFile = rFileSet.getFileByName(rFilename);
611 if (pFile != NULL)
613 // Look if it's a file to patch
614 for (k = 0; k < FilesToPatch.size(); ++k)
615 if (FilesToPatch[k].FileName == pFile->getFileName())
616 break;
618 if (k < FilesToPatch.size())
620 FilesToPatch[k].Incremental = pCat->isIncremental();
621 if (!pCat->getUnpackTo().empty())
622 FilesToPatch[k].ExtractPath = CPath::standardizePath(pCat->getUnpackTo());
624 PatchThread->add(FilesToPatch[k]);
626 // Close opened big files
627 CBigFile::getInstance().remove(FilesToPatch[k].FileName);
629 if (NLMISC::startsWith(FilesToPatch[k].FileName, "sound"))
631 // Stop sound playback
632 stopSoundMngr();
640 // Launch the thread
641 Thread = IThread::create (PatchThread);
642 nlassert (Thread != NULL);
643 Thread->start ();
646 // ****************************************************************************
647 bool CPatchManager::isPatchThreadEnded (bool &ok)
649 if (PatchThread == NULL)
651 ok = false;
652 return true;
655 bool end = PatchThread->Ended;
656 if (end)
658 ok = PatchThread->PatchOk;
659 stopPatchThread();
662 return end;
665 // ****************************************************************************
666 // Called in main thread
667 bool CPatchManager::getThreadState (std::string &stateOut, vector<string> &stateLogOut)
669 if ((PatchThread == NULL) && (CheckThread == NULL) && (ScanDataThread==NULL))
670 return false;
672 // clear output
673 stateOut.clear();
674 stateLogOut.clear();
676 // Get access to the state
677 bool changed= false;
679 CSynchronized<CState>::CAccessor as(&State);
680 CState &rState= as.value();
681 if (rState.StateChanged)
683 // and retrieve info
684 changed= true;
685 stateOut = rState.State;
686 stateLogOut = rState.StateLog;
687 // clear state
688 rState.StateLog.clear();
689 rState.StateChanged= false;
693 // verbose log
694 if (isVerboseLog() && !stateLogOut.empty())
695 for (uint32 i = 0; i < stateLogOut.size(); ++i)
696 nlinfo("%s", stateLogOut[i].c_str());
698 return changed;
701 // ****************************************************************************
702 void CPatchManager::stopPatchThread()
704 if(PatchThread && Thread)
706 Thread->wait();
707 delete Thread;
708 Thread = NULL;
709 delete PatchThread;
710 PatchThread = NULL;
714 // ****************************************************************************
715 void CPatchManager::deleteBatchFile()
717 deleteFile(ClientRootPath + UpdateBatchFilename, false, false);
720 // ****************************************************************************
721 void CPatchManager::createBatchFile(CProductDescriptionForClient &descFile, bool wantRyzomRestart, bool useBatchFile)
723 uint nblab = 0;
725 std::string content;
727 // Unpack files with category ExtractPath non empty
728 const CBNPCategorySet &rDescCats = descFile.getCategories();
729 OptionalCat.clear();
731 for (uint32 i = 0; i < rDescCats.categoryCount(); ++i)
733 // For all optional categories check if there is a 'file to patch' in it
734 const CBNPCategory &rCat = rDescCats.getCategory(i);
736 nlinfo("Category = %s", rCat.getName().c_str());
738 if (!rCat.getUnpackTo().empty())
739 for (uint32 j = 0; j < rCat.fileCount(); ++j)
741 string rFilename = ClientPatchPath + rCat.getFile(j);
743 nlinfo("\tFileName = %s", rFilename.c_str());
745 // Extract to patch
746 vector<string> vFilenames;
748 bool result = false;
752 result = bnpUnpack(rFilename, ClientPatchPath, vFilenames);
754 catch(...)
756 throw;
759 if (!result)
761 // TODO: handle exception?
762 string err = toString("Error unpacking %s", rFilename.c_str());
764 throw Exception (err);
766 else
768 for (uint32 fff = 0; fff < vFilenames.size (); fff++)
770 // this file must be moved
771 string fullDstPath = CPath::standardizePath(rCat.getUnpackTo()); // to be sure there is a / at the end
772 NLMISC::CFile::createDirectoryTree(fullDstPath);
774 std::string FileName = vFilenames[fff];
776 bool succeeded = false;
778 if (!useBatchFile)
780 // don't check result, because it's possible the olk file doesn't exist
781 CFile::deleteFile(fullDstPath + FileName);
783 // try to move it, if fails move it later in a script
784 if (CFile::moveFile(fullDstPath + FileName, ClientPatchPath + FileName))
785 succeeded = true;
788 // if we didn't succeed to delete or move the file, create a batch file anyway
789 if (!succeeded)
791 string batchRelativeDstPath;
793 // should be always true
794 if (fullDstPath.compare(0, ClientRootPath.length(), ClientRootPath) == 0)
796 batchRelativeDstPath = fullDstPath.substr(ClientRootPath.length()) + FileName;
798 else
800 batchRelativeDstPath = fullDstPath + FileName;
803 #ifdef NL_OS_WINDOWS
804 // only fix backslashes for .bat
805 batchRelativeDstPath = CPath::standardizeDosPath(batchRelativeDstPath);
807 // use DSTPATH and SRCPATH variables and append filenames
808 string realDstPath = toString("\"%%ROOTPATH%%\\%s\"", batchRelativeDstPath.c_str());
809 string realSrcPath = toString("\"%%UNPACKPATH%%\\%s\"", FileName.c_str());
811 content += toString(":loop%u\n", nblab);
812 content += toString("attrib -r -a -s -h %s\n", realDstPath.c_str());
813 content += toString("del %s\n", realDstPath.c_str());
814 content += toString("if exist %s goto loop%u\n", realDstPath.c_str(), nblab);
815 content += toString("move %s %s\n", realSrcPath.c_str(), realDstPath.c_str());
816 #else
817 // use DSTPATH and SRCPATH variables and append filenames
818 string realDstPath = toString("\"$ROOTPATH/%s\"", batchRelativeDstPath.c_str());
819 string realSrcPath = toString("\"$UNPACKPATH/%s\"", FileName.c_str());
821 content += toString("rm -rf %s\n", realDstPath.c_str());
822 content += toString("mv %s %s\n", realSrcPath.c_str(), realDstPath.c_str());
823 #endif
825 content += "\n";
828 nblab++;
834 std::string patchDirectory = CPath::standardizePath(ClientRootPath + "patch");
836 // Finalize batch file
837 if (NLMISC::CFile::isExists(patchDirectory) && NLMISC::CFile::isDirectory(patchDirectory))
839 std::string patchContent;
841 vector<string> vFileList;
842 CPath::getPathContent (patchDirectory, false, false, true, vFileList, NULL, false);
844 for(uint32 i = 0; i < vFileList.size(); ++i)
846 bool succeeded = false;
848 if (!useBatchFile)
850 if (CFile::deleteFile(vFileList[i]))
851 succeeded = true;
854 // if we didn't succeed to delete, create a batch file anyway
855 if (!succeeded)
857 #ifdef NL_OS_WINDOWS
858 patchContent += toString("del \"%%ROOTPATH%%\\patch\\%s\"\n", vFileList[i].c_str());
859 #else
860 patchContent += toString("rm -f \"$ROOTPATH/patch/%s\"\n", vFileList[i].c_str());
861 #endif
865 if (!patchContent.empty())
867 #ifdef NL_OS_WINDOWS
868 content += ":looppatch\n";
870 content += patchContent;
872 content += "rd /Q /S \"%%ROOTPATH%%\\patch\"\n";
873 content += "if exist \"%%ROOTPATH%%\\patch\" goto looppatch\n";
874 #else
875 content += "rm -rf \"$ROOTPATH/patch\"\n";
876 #endif
878 else
880 CFile::deleteDirectory(patchDirectory);
884 if (!content.empty())
886 deleteBatchFile();
888 // batch full path
889 std::string batchFilename = ClientRootPath + UpdateBatchFilename;
891 // write windows .bat format else write sh format
892 FILE *fp = nlfopen (batchFilename, "wt");
894 if (fp == NULL)
896 string err = toString("Can't open file '%s' for writing: code=%d %s (error code 29)", batchFilename.c_str(), errno, strerror(errno));
897 throw Exception (err);
899 else
901 nlinfo("Creating %s...", batchFilename.c_str());
904 string contentPrefix;
906 //use bat if windows if not use sh
907 #ifdef NL_OS_WINDOWS
908 contentPrefix += "@echo off\n";
909 contentPrefix += "set RYZOM_CLIENT=%~1\n";
910 contentPrefix += "set UNPACKPATH=%~2\n";
911 contentPrefix += "set ROOTPATH=%~3\n";
912 contentPrefix += "set STARTUPPATH=%~4\n";
913 contentPrefix += toString("set UPGRADE_FILE=%%ROOTPATH%%\\%s\n", UpgradeBatchFilename.c_str());
914 contentPrefix += "\n";
915 contentPrefix += "set LOGIN=%~5\n";
916 contentPrefix += "set PASSWORD=%~6\n";
917 contentPrefix += "set SHARDID=%~7\n";
918 #else
919 contentPrefix += "#!/bin/sh\n";
920 contentPrefix += "export RYZOM_CLIENT=\"$1\"\n";
921 contentPrefix += "export UNPACKPATH=\"$2\"\n";
922 contentPrefix += "export ROOTPATH=\"$3\"\n";
923 contentPrefix += "export STARTUPPATH=\"$4\"\n";
924 contentPrefix += toString("export UPGRADE_FILE=$ROOTPATH/%s\n", UpgradeBatchFilename.c_str());
925 contentPrefix += "\n";
926 contentPrefix += "LOGIN=\"$5\"\n";
927 contentPrefix += "PASSWORD=\"$6\"\n";
928 contentPrefix += "SHARDID=\"$7\"\n";
929 #endif
931 contentPrefix += "\n";
933 string contentSuffix;
935 // if we need to restart Ryzom, we need to launch it in batch
936 std::string additionalParams;
938 if (Args.haveLongArg("profile"))
940 additionalParams = "--profile " + Args.getLongArg("profile").front();
943 #ifdef NL_OS_WINDOWS
944 // launch upgrade script if present (it'll execute additional steps like moving or deleting files)
945 contentSuffix += "if exist \"%UPGRADE_FILE%\" call \"%UPGRADE_FILE%\"\n";
947 if (wantRyzomRestart)
949 // client shouldn't be in memory anymore else it couldn't be overwritten
950 contentSuffix += toString("start \"\" /D \"%%STARTUPPATH%%\" \"%%RYZOM_CLIENT%%\" %s \"%%LOGIN%%\" \"%%PASSWORD%%\" \"%%SHARDID%%\"\n", additionalParams.c_str());
952 #else
953 if (wantRyzomRestart)
955 // wait until client not in memory anymore
956 contentSuffix += toString("until ! pgrep -x \"%s\" > /dev/null; do sleep 1; done\n", CFile::getFilename(RyzomFilename).c_str());
959 // launch upgrade script if present (it'll execute additional steps like moving or deleting files)
960 contentSuffix += "if [ -e \"$UPGRADE_FILE\" ]; then chmod +x \"$UPGRADE_FILE\" && \"$UPGRADE_FILE\"; fi\n\n";
962 // be sure file is executable
963 contentSuffix += "chmod +x \"$RYZOM_CLIENT\"\n\n";
965 if (wantRyzomRestart)
967 // change to previous client directory
968 contentSuffix += "cd \"$STARTUPPATH\"\n\n";
970 // launch new client
971 #ifdef NL_OS_MAC
972 // use exec command under OS X
973 contentSuffix += toString("exec \"$RYZOM_CLIENT\" %s \"$LOGIN\" \"$PASSWORD\" \"$SHARDID\"\n", additionalParams.c_str());
974 #else
975 contentSuffix += toString("\"$RYZOM_CLIENT\" %s \"$LOGIN\" \"$PASSWORD\" \"$SHARDID\" &\n", additionalParams.c_str());
976 #endif
978 #endif
980 // append content of script
981 fputs(contentPrefix.c_str(), fp);
982 fputs(content.c_str(), fp);
983 fputs(contentSuffix.c_str(), fp);
985 bool writeError = ferror(fp) != 0;
986 bool diskFull = ferror(fp) && errno == 28 /* ENOSPC */;
987 fclose(fp);
988 if (diskFull)
990 throw NLMISC::EDiskFullError(batchFilename.c_str());
992 if (writeError)
994 throw NLMISC::EWriteError(batchFilename.c_str());
999 // ****************************************************************************
1000 void CPatchManager::executeBatchFile()
1002 // normal quit
1003 extern void quitCrashReport ();
1004 quitCrashReport ();
1006 bool r2Mode = false;
1008 #ifndef RY_BG_DOWNLOADER
1009 r2Mode = ClientCfg.R2Mode;
1010 #endif
1012 std::string batchFilename;
1014 std::vector<std::string> arguments;
1016 std::string startupPath = Args.getStartupPath();
1018 // 3 first parameters are Ryzom client full path, patch directory full path and client root directory full path
1019 #ifdef NL_OS_WINDOWS
1020 batchFilename = CPath::standardizeDosPath(ClientRootPath);
1022 arguments.push_back(CPath::standardizeDosPath(RyzomFilename));
1023 arguments.push_back(CPath::standardizeDosPath(ClientPatchPath));
1024 arguments.push_back(CPath::standardizeDosPath(ClientRootPath));
1025 arguments.push_back(CPath::standardizeDosPath(startupPath));
1026 #else
1027 batchFilename = ClientRootPath;
1029 arguments.push_back(RyzomFilename);
1030 arguments.push_back(ClientPatchPath);
1031 arguments.push_back(ClientRootPath);
1032 arguments.push_back(startupPath);
1033 #endif
1035 // log parameters passed to Ryzom client
1036 nlinfo("Restarting Ryzom...");
1037 nlinfo("RyzomFilename = %s", RyzomFilename.c_str());
1038 nlinfo("ClientPatchPath = %s", ClientPatchPath.c_str());
1039 nlinfo("ClientRootPath = %s", ClientRootPath.c_str());
1040 nlinfo("StartupPath = %s", startupPath.c_str());
1042 batchFilename += UpdateBatchFilename;
1044 // make script executable
1045 CFile::setRWAccess(batchFilename);
1046 CFile::setExecutable(batchFilename);
1048 // append login, password and shard
1049 if (!LoginLogin.empty())
1051 arguments.push_back(LoginLogin);
1053 if (!LoginPassword.empty())
1055 // encode password in hexadecimal to avoid invalid characters on command-line
1056 arguments.push_back("0x" + toHexa(LoginPassword));
1058 if (!r2Mode)
1060 arguments.push_back(toString(LoginShardId));
1065 // launchProgram with array of strings as argument will escape arguments with spaces
1066 if (!launchProgramArray(batchFilename, arguments, false))
1068 // error occurs during the launch
1069 string str = toString("Can't execute '%s': code=%d %s (error code 30)", batchFilename.c_str(), errno, strerror(errno));
1070 throw Exception (str);
1074 // ****************************************************************************
1075 void CPatchManager::reboot()
1077 onFileInstallFinished(); //In install program wait for the player to select in the gui of the install program if we want to Launch Ryzom or not
1078 createBatchFile(DescFile, _StartRyzomAtEnd);
1079 executeBatchFile();
1083 // ****************************************************************************
1084 int CPatchManager::getTotalFilesToGet()
1086 if (CheckThread != NULL)
1087 return CheckThread->TotalFileToCheck;
1089 if (PatchThread != NULL)
1090 return PatchThread->getNbFileToPatch();
1092 if (ScanDataThread != NULL)
1093 return ScanDataThread->TotalFileToScan;
1095 return 1;
1098 // ****************************************************************************
1099 int CPatchManager::getCurrentFilesToGet()
1101 if (CheckThread != NULL)
1102 return CheckThread->CurrentFileChecked;
1104 if (PatchThread != NULL)
1105 return PatchThread->getCurrentFilePatched();
1107 if (ScanDataThread != NULL)
1108 return ScanDataThread->CurrentFileScanned;
1110 return 1;
1113 // ****************************************************************************
1114 int CPatchManager::getPatchingSize()
1116 if (PatchThread != NULL)
1117 return PatchThread->getPatchingSize();
1118 return 0;
1121 // ****************************************************************************
1122 float CPatchManager::getCurrentFileProgress() const
1124 if (PatchThread != NULL)
1125 return PatchThread->getCurrentFileProgress();
1126 return 0.f;
1129 // ****************************************************************************
1130 void CPatchManager::setRWAccess (const string &filename, bool bThrowException)
1132 string s = CI18N::get("uiSetAttrib") + " " + CFile::getFilename(filename);
1133 setState(true, s);
1135 if (!NLMISC::CFile::setRWAccess(filename) && bThrowException)
1137 s = CI18N::get("uiAttribErr") + " " + CFile::getFilename(filename) + " (" + toString(errno) + "," + strerror(errno) + ")";
1138 setState(true, s);
1139 throw Exception (s);
1143 // ****************************************************************************
1144 string CPatchManager::deleteFile (const string &filename, bool bThrowException, bool bWarning)
1146 string s = CI18N::get("uiDelFile") + " " + CFile::getFilename(filename);
1147 setState(true, s);
1149 if (!NLMISC::CFile::fileExists(filename))
1151 s = CI18N::get("uiDelNoFile");
1152 setState(true, s);
1153 return s;
1156 if (!NLMISC::CFile::deleteFile(filename))
1158 s = CI18N::get("uiDelErr") + " " + CFile::getFilename(filename) + " (" + toString(errno) + "," + strerror(errno) + ")";
1159 if(bWarning)
1160 setState(true, s);
1161 if(bThrowException)
1162 throw Exception (s);
1163 return s;
1165 return "";
1168 // ****************************************************************************
1169 void CPatchManager::renameFile (const string &src, const string &dst)
1171 string s = CI18N::get("uiRenameFile") + " " + NLMISC::CFile::getFilename(src);
1172 setState(true, s);
1174 if (!NLMISC::CFile::moveFile(dst, src))
1176 s = CI18N::get("uiRenameErr") + " " + src + " -> " + dst + " (" + toString(errno) + "," + strerror(errno) + ")";
1177 setState(true, s);
1178 throw Exception (s);
1182 // ****************************************************************************
1183 // Take care this function is called by the thread
1184 void CPatchManager::setState (bool bOutputToLog, const string &ucsNewState)
1187 CSynchronized<CState>::CAccessor as(&State);
1188 CState &rState= as.value();
1189 rState.State= ucsNewState;
1190 if(bOutputToLog)
1191 rState.StateLog.push_back(ucsNewState);
1192 rState.StateChanged= true;
1194 if (_StateListener)
1196 _StateListener->setState(bOutputToLog, ucsNewState);
1200 // ****************************************************************************
1201 void CPatchManager::touchState ()
1204 CSynchronized<CState>::CAccessor as(&State);
1205 as.value().StateChanged= true;
1209 // ****************************************************************************
1210 string CPatchManager::getClientVersion()
1212 if (!ValidDescFile)
1213 return "";
1215 return toString(DescFile.getFiles().getVersionNumber());
1218 // ****************************************************************************
1219 void CPatchManager::readDescFile(sint32 nVersion)
1221 DescFilename = toString("ryzom_%05d.idx", nVersion);
1222 string srcName = ClientPatchPath + DescFilename;
1223 DescFile.clear();
1224 if (!DescFile.load(srcName))
1225 throw Exception ("Can't open file '%s'", srcName.c_str ());
1227 uint cat;
1229 if (ClientRootPath != "./")
1231 // fix relative paths
1232 for (cat = 0; cat < DescFile.getCategories().categoryCount(); ++cat)
1234 CBNPCategory &category = const_cast<CBNPCategory &>(DescFile.getCategories().getCategory(cat));
1236 std::string unpackTo = category.getUnpackTo();
1238 if (unpackTo.substr(0, 1) == ".")
1240 unpackTo = CPath::makePathAbsolute(unpackTo, ClientRootPath, true);
1241 category.setUnpackTo(unpackTo);
1246 // patch category for current platform
1247 std::string platformPatchCategory;
1249 #if defined(NL_OS_WIN64)
1250 platformPatchCategory = "main_exedll_win64";
1251 #elif defined(NL_OS_WINDOWS)
1252 platformPatchCategory = "main_exedll_win32";
1253 #elif defined(NL_OS_MAC)
1254 platformPatchCategory = "main_exedll_osx";
1255 #elif defined(NL_OS_UNIX) && defined(_LP64)
1256 platformPatchCategory = "main_exedll_linux64";
1257 #else
1258 platformPatchCategory = "main_exedll_linux32";
1259 #endif
1261 // check if we are using main_exedll or specific main_exedll_* for platform
1262 bool foundPlatformPatchCategory = false;
1264 for (cat = 0; cat < DescFile.getCategories().categoryCount(); ++cat)
1266 CBNPCategory &category = const_cast<CBNPCategory &>(DescFile.getCategories().getCategory(cat));
1268 if (category.getName() == platformPatchCategory)
1270 foundPlatformPatchCategory = true;
1271 break;
1275 if (foundPlatformPatchCategory)
1277 std::set<std::string> forceRemovePatchCategories;
1279 // only download binaries for current platform
1280 forceRemovePatchCategories.insert("main_exedll");
1281 forceRemovePatchCategories.insert("main_exedll_win32");
1282 forceRemovePatchCategories.insert("main_exedll_win64");
1283 forceRemovePatchCategories.insert("main_exedll_linux32");
1284 forceRemovePatchCategories.insert("main_exedll_linux64");
1285 forceRemovePatchCategories.insert("main_exedll_osx");
1287 // remove current platform category from remove list
1288 forceRemovePatchCategories.erase(platformPatchCategory);
1290 CBNPFileSet &bnpFS = const_cast<CBNPFileSet &>(DescFile.getFiles());
1292 // TODO: .ref files are expected to follow platform category naming (they are in 'main' category)
1293 std::set<std::string>::const_iterator it;
1294 for(it = forceRemovePatchCategories.begin(); it != forceRemovePatchCategories.end(); ++it)
1296 std::string name = *it;
1297 std::string::size_type pos = name.find("_");
1298 if (pos != std::string::npos)
1300 name = name.substr(pos+1) + "_.ref";
1301 bnpFS.removeFile(name);
1305 for (cat = 0; cat < DescFile.getCategories().categoryCount();)
1307 const CBNPCategory &bnpCat = DescFile.getCategories().getCategory(cat);
1309 if (std::find(forceRemovePatchCategories.begin(), forceRemovePatchCategories.end(),
1310 bnpCat.getName()) != forceRemovePatchCategories.end())
1312 for (uint file = 0; file < bnpCat.fileCount(); ++file)
1314 std::string fileName = bnpCat.getFile(file);
1315 bnpFS.removeFile(fileName);
1317 const_cast<CBNPCategorySet &>(DescFile.getCategories()).deleteCategory(cat);
1319 else
1321 ++cat;
1327 // ****************************************************************************
1328 void CPatchManager::getServerFile (const std::string &name, bool bZipped, const std::string& specifyDestName, NLMISC::IProgressCallback *progress)
1330 string srcName = name;
1331 if (bZipped) srcName += ".ngz";
1333 string dstName;
1334 if (specifyDestName.empty())
1336 dstName = ClientPatchPath + NLMISC::CFile::getFilename(name);
1338 else
1340 dstName = specifyDestName;
1342 if (bZipped) dstName += ".ngz";
1344 bool downloadSuccess = false;
1346 while (!downloadSuccess)
1348 std::string serverPath;
1349 std::string serverDisplayPath;
1351 if (UsedServer >= 0 && !PatchServers.empty())
1353 // first use main patch servers
1354 serverPath = PatchServers[UsedServer].ServerPath;
1355 serverDisplayPath = PatchServers[UsedServer].DisplayedServerPath;
1357 else
1359 // else use alternative emergency patch server
1360 serverPath = ServerPath;
1361 serverDisplayPath = DisplayedServerPath;
1362 UsedServer = -1;
1367 string s = CI18N::get("uiLoginGetFile") + " " + NLMISC::CFile::getFilename(srcName);
1368 setState(true, s);
1370 // get the new file
1371 downloadFile (serverPath+srcName, dstName, progress);
1373 downloadSuccess = true;
1375 catch (const EPatchDownloadException& e)
1377 //nlwarning("EXCEPTION CATCH: getServerFile() failed - try to find an alternative: %i: %s",UsedServer,PatchServers[UsedServer].DisplayedServerPath.c_str());
1379 nlwarning("EXCEPTION CATCH: getServerFile() failed - try to find an alternative : %s", (serverPath+srcName).c_str());
1380 nlwarning("%i", UsedServer);
1381 if (UsedServer >= 0 && UsedServer < (int) PatchServers.size())
1383 nlwarning("%s", PatchServers[UsedServer].DisplayedServerPath.c_str());
1386 // if emergency patch server, this is a real issue, rethrow exception
1387 if (UsedServer < 0)
1389 string s = CI18N::get("uiDLFailed");
1390 setState(true, s);
1392 throw Exception(e.what());
1395 string s = CI18N::get("uiDLURIFailed") + " " + serverDisplayPath;
1396 setState(true, s);
1398 // this server is unavailable
1399 PatchServers[UsedServer].Available = false;
1401 sint nextServer = (UsedServer+1) % PatchServers.size();
1403 while (nextServer != UsedServer && !PatchServers[nextServer].Available)
1404 nextServer = (nextServer+1) % PatchServers.size();
1406 // scanned all servers? use alternative
1407 if (nextServer == UsedServer)
1409 string s = CI18N::get("uiNoMoreURI");
1410 setState(true, s);
1411 UsedServer = -1;
1412 nlwarning("EXCEPTION CATCH: getServerFile() failed - no alternative found");
1414 else
1416 UsedServer = nextServer;
1417 nlwarning("EXCEPTION CATCH: getServerFile() failed - trying server: %i: %s",UsedServer,PatchServers[UsedServer].DisplayedServerPath.c_str());
1422 // decompress it
1423 if (bZipped)
1424 decompressFile (dstName);
1427 // ****************************************************************************
1428 void CPatchManager::downloadFileWithCurl (const string &source, const string &dest, NLMISC::IProgressCallback *progress)
1430 DownloadInProgress = true;
1433 #ifdef USE_CURL
1434 string s = CI18N::get("uiDLWithCurl") + " " + CFile::getFilename(dest);
1435 setState(true, s);
1437 // user agent = nel_launcher
1439 CURL *curl;
1440 CURLcode res;
1442 string sTranslate = CI18N::get("uiLoginGetFile") + " " + NLMISC::CFile::getFilename (source);
1443 setState(true, sTranslate);
1444 CurrentFile = NLMISC::CFile::getFilename (source);
1446 curl_global_init(CURL_GLOBAL_ALL);
1447 curl = curl_easy_init();
1448 if(curl == NULL)
1450 // file not found, delete local file
1451 throw Exception ("curl init failed");
1454 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
1455 curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, downloadProgressFunc);
1456 curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, (void *) progress);
1457 curl_easy_setopt(curl, CURLOPT_URL, source.c_str());
1458 if (source.length() > 8 && (source[4] == 's' || source[4] == 'S')) // 01234 https
1460 NLWEB::CCurlCertificates::addCertificateFile("cacert.pem");
1461 NLWEB::CCurlCertificates::useCertificates(curl);
1462 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
1463 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
1466 // create the local file
1467 if (NLMISC::CFile::fileExists(dest))
1469 setRWAccess(dest, false);
1470 NLMISC::CFile::deleteFile(dest.c_str());
1473 FILE *fp = nlfopen (dest, "wb");
1475 if (fp == NULL)
1477 curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, NULL);
1478 throw Exception ("Can't open file '%s' for writing: code=%d %s (error code 37)", dest.c_str (), errno, strerror(errno));
1481 curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
1482 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
1484 //CurrentFilesToGet++;
1486 res = curl_easy_perform(curl);
1488 curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, NULL);
1490 long r;
1491 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r);
1493 curl_easy_cleanup(curl);
1495 bool diskFull = ferror(fp) && errno == 28 /*ENOSPC*/;
1497 fclose(fp);
1498 curl_global_cleanup();
1500 CurrentFile.clear();
1502 if (diskFull)
1504 NLMISC::CFile::deleteFile(dest.c_str());
1505 throw NLMISC::EDiskFullError(dest);
1508 if(CURLE_WRITE_ERROR == res)
1510 // file not found, delete local file
1511 NLMISC::CFile::deleteFile(dest.c_str());
1512 throw NLMISC::EWriteError(dest);
1515 if(CURLE_FTP_COULDNT_RETR_FILE == res)
1517 // file not found, delete local file
1518 NLMISC::CFile::deleteFile(dest.c_str());
1519 throw EPatchDownloadException (NLMISC::toString("curl download failed: (ec %d %d)", res, r));
1522 if(CURLE_OK != res)
1524 NLMISC::CFile::deleteFile(dest.c_str());
1525 throw EPatchDownloadException (NLMISC::toString("curl download failed: (ec %d %d)", res, r));
1528 if(r == 404)
1530 // file not found, delete it
1531 NLMISC::CFile::deleteFile(dest.c_str());
1532 throw EPatchDownloadException (NLMISC::toString("curl download failed: (ec %d %d)", res, r));
1535 if(r < 200 || r >= 300)
1537 // file not found, delete it
1538 NLMISC::CFile::deleteFile(dest.c_str());
1539 throw EPatchDownloadException (NLMISC::toString("curl download failed: (ec %d %d)", res, r));
1542 #else
1543 throw Exception("USE_CURL is not defined, no curl method");
1544 #endif
1546 catch(...)
1548 DownloadInProgress = false;
1549 throw;
1551 if (progress)
1553 // set progress to 1
1554 validateProgress((void *) progress, 1, 1, 0, 0);
1556 DownloadInProgress = false;
1559 // ****************************************************************************
1560 void CPatchManager::downloadFile (const string &source, const string &dest, NLMISC::IProgressCallback *progress)
1562 // For the moment use only curl
1563 const string sourceLower = toLowerAscii(source.substr(0, 6));
1565 if (startsWith(sourceLower, "http:")
1566 || startsWith(sourceLower, "https:")
1567 || startsWith(sourceLower, "ftp:")
1568 || startsWith(sourceLower, "file:"))
1570 nldebug("Download patch file %s", source.c_str());
1571 downloadFileWithCurl(source, dest, progress);
1573 else
1575 if (!NLMISC::CFile::copyFile(dest, source, false, progress))
1577 if (errno == 28)
1579 throw NLMISC::EDiskFullError(dest);
1581 throw Exception ("cannot copy file %s to %s", source.c_str(), dest.c_str());
1586 // ****************************************************************************
1587 // TODO : Review this uncompress routine to uncompress in a temp file before overwriting destination file
1589 void CPatchManager::decompressFile (const string &filename)
1591 string sTranslate = CI18N::get("uiDecompressing") + " " + NLMISC::CFile::getFilename(filename);
1592 setState(true, sTranslate);
1594 //if(isVerboseLog()) nlinfo("Calling gzopen('%s','rb')", filename.c_str());
1595 gzFile gz = gzopen (filename.c_str (), "rb");
1596 if (gz == NULL)
1598 string err = toString("Can't open compressed file '%s' : ", filename.c_str());
1599 if(errno == 0)
1601 // gzerror
1602 int gzerrno;
1603 const char *gzerr = gzerror (gz, &gzerrno);
1604 err += toString("code=%d %s", gzerrno, gzerr);
1606 else
1608 err += toString("code=%d %s", errno, strerror (errno));
1610 err += " (error code 31)";
1611 deleteFile (filename);
1612 throw Exception (err);
1615 string dest = filename.substr(0, filename.size ()-4);
1616 setRWAccess(dest, false);
1617 //if(isVerboseLog()) nlinfo("Calling nlfopen('%s','wb')", dest.c_str());
1618 FILE *fp = nlfopen (dest, "wb");
1619 if (fp == NULL)
1621 string err = toString("Can't open file '%s' : code=%d %s, (error code 32)", dest.c_str(), errno, strerror(errno));
1623 gzclose(gz);
1624 deleteFile (filename);
1625 throw Exception (err);
1628 //if(isVerboseLog()) nlinfo("Entering the while loop decompression");
1630 uint32 currentSize = 0;
1631 uint8 buffer[10000];
1632 while (!gzeof(gz))
1634 //if(isVerboseLog()) nlinfo("Calling gzread");
1635 int res = gzread (gz, buffer, 10000);
1636 //if(isVerboseLog()) nlinfo("gzread returns %d", res);
1637 if (res == -1)
1639 int gzerrno;
1640 const char *gzerr = gzerror (gz, &gzerrno);
1641 gzclose(gz);
1642 fclose(fp);
1643 //deleteFile (filename);
1644 throw Exception ("Can't read compressed file '%s' (after %d bytes) : code=%d %s, (error code 33)", filename.c_str(), currentSize, gzerrno, gzerr);
1647 currentSize += res;
1649 //if(isVerboseLog()) nlinfo("Calling fwrite for %d bytes", res);
1650 int res2 = (int)fwrite (buffer, 1, res, fp);
1651 //if(isVerboseLog()) nlinfo("fwrite returns %d", res2);
1652 if (res2 != res)
1654 bool diskFull = (errno == 28 /* ENOSPC */);
1655 string err = toString("Can't write file '%s' : code=%d %s (error code 34)", dest.c_str(), errno, strerror(errno));
1657 gzclose(gz);
1658 fclose(fp);
1659 deleteFile (filename);
1660 if (diskFull)
1662 throw NLMISC::EDiskFullError(err);
1664 else
1666 throw Exception (err);
1671 //if(isVerboseLog()) nlinfo("Exiting the while loop decompression");
1673 //if(isVerboseLog()) nlinfo("Calling gzclose");
1674 gzclose(gz);
1675 //if(isVerboseLog()) nlinfo("Calling fclose");
1676 fclose(fp);
1677 deleteFile(filename);
1679 //if(isVerboseLog()) nlinfo("Exiting the decompressing file");
1682 // ****************************************************************************
1683 void CPatchManager::applyDate (const string &sFilename, uint32 nDate)
1685 // change the file time
1686 if(nDate != 0)
1688 setRWAccess(sFilename, false);
1689 string s = CI18N::get("uiChangeDate") + " " + NLMISC::CFile::getFilename(sFilename) + " " + timestampToHumanReadable(NLMISC::CFile::getFileModificationDate (sFilename)) +
1690 " -> " + timestampToHumanReadable(nDate);
1691 setState(true,s);
1693 if (!NLMISC::CFile::setFileModificationDate(sFilename, nDate))
1695 int err = NLMISC::getLastError();
1696 s = CI18N::get("uiChgDateErr") + " " + CFile::getFilename(sFilename) + " (" + toString(err) + ", " + formatErrorMessage(err) + ")";
1697 setState(true,s);
1699 s = CI18N::get("uiNowDate") + " " + CFile::getFilename(sFilename) + " " + timestampToHumanReadable(NLMISC::CFile::getFileModificationDate (sFilename));
1700 setState(true,s);
1704 // ****************************************************************************
1705 // Get all the patches that need to be applied to a file from the description of this file given by the server
1706 void CPatchManager::getPatchFromDesc(SFileToPatch &ftpOut, const CBNPFile &fIn, bool forceCheckSumTest)
1708 uint32 j;
1709 const CBNPFile rFile = fIn;
1710 const string &rFilename = rFile.getFileName();
1711 string sFilePath;
1712 bool inPatchDir = false;
1713 // first see if there's a version that was downloaded from the background downloader
1714 if (NLMISC::CFile::fileExists(ClientPatchPath + rFilename + ".tmp"))
1716 sFilePath = ClientPatchPath + rFilename + ".tmp";
1717 ftpOut.SrcFileName = sFilePath;
1718 inPatchDir = true;
1721 bool needUnpack = false;
1722 const CBNPCategory *cat = DescFile.getCategories().getCategoryFromFile(rFilename);
1723 if (cat)
1725 needUnpack = !cat->getUnpackTo().empty();
1727 else
1729 nlwarning("Can't find file category for %s:", rFilename.c_str());
1732 // Does the BNP exists ?
1733 // following lines added by Sadge to ensure that the correct file is patched
1734 // Only look in data path if the file should not be unpack (otherwise it should only remains in the "unpack" directory)
1735 if (!needUnpack)
1737 if (sFilePath.empty())
1739 if (NLMISC::CFile::fileExists(WritableClientDataPath + rFilename))
1741 // if file exists in writable directory, use it
1742 sFilePath = WritableClientDataPath + rFilename;
1744 else if (NLMISC::CFile::fileExists(ReadableClientDataPath + rFilename))
1746 // if file exists in readable directory, use it
1747 sFilePath = ReadableClientDataPath + rFilename;
1752 if (sFilePath.empty() && NLMISC::CFile::fileExists(ClientPatchPath + rFilename))
1754 sFilePath = ClientPatchPath + rFilename;
1757 // following lines removed by Sadge to ensure that the correct file is patched
1758 // string sFilePath = CPath::lookup(rFilename, false, false);
1759 // if (sFilePath.empty())
1760 // {
1761 // if (NLMISC::CFile::fileExists(ClientPatchPath + rFilename))
1762 // sFilePath = ClientPatchPath + rFilename;
1763 // }
1765 // if file not found anywhere
1766 if (sFilePath.empty())
1768 ftpOut.FileName = rFilename;
1769 ftpOut.LocalFileToDelete = false;
1770 ftpOut.LocalFileExists = false;
1771 // It happens some time (maybe a bug) that the versionCount is 0... =>
1772 // it happens if the BNP file is empty (8 bytes)
1773 ftpOut.FinalFileSize = EmptyBnpFileSize;
1774 // BNP does not exists : get all the patches version
1775 for (j = 0; j < rFile.versionCount(); ++j)
1777 ftpOut.Patches.push_back(rFile.getVersion(j).getVersionNumber());
1778 ftpOut.PatcheSizes.push_back(rFile.getVersion(j).getPatchSize());
1779 ftpOut.LastFileDate = rFile.getVersion(j).getTimeStamp();
1780 ftpOut.FinalFileSize = rFile.getVersion(j).getFileSize();
1781 ftpOut.SZFileSize = rFile.getVersion(j).get7ZFileSize();
1784 else
1786 // The local BNP file exists : find its version
1787 uint32 nLocalSize = NLMISC::CFile::getFileSize(sFilePath);
1788 uint32 nLocalTime = NLMISC::CFile::getFileModificationDate(sFilePath);
1789 // From the couple time, size look the version of the file
1790 uint32 nVersionFound = 0xFFFFFFFF;
1791 // If forceChecksum is wanted (slow), then don't do the test with filesize/date
1792 if(!forceCheckSumTest)
1794 for (j = 0; j < rFile.versionCount(); ++j)
1796 const CBNPFileVersion &rVersion = rFile.getVersion(j);
1797 uint32 nServerSize = rVersion.getFileSize();
1798 uint32 nServerTime = rVersion.getTimeStamp();
1799 // Does the time and size match a version ?
1800 if ((nServerSize == nLocalSize) && (abs((sint32)(nServerTime - nLocalTime)) <= 2) )
1802 nVersionFound = rVersion.getVersionNumber();
1803 // break; // ace -> get the last good version (if more than one version of the same file exists)
1808 // If the version cannot be found with size and time try with sha1
1809 if (nVersionFound == 0xFFFFFFFF)
1811 string sTranslate = CI18N::get("uiCheckInt") + " " + rFilename;
1812 setState(true, sTranslate);
1813 CHashKey hkLocalSHA1 = getSHA1(sFilePath);
1814 for (j = 0; j < rFile.versionCount(); ++j)
1816 const CBNPFileVersion &rVersion = rFile.getVersion(j);
1817 CHashKey hkServerSHA1 = rVersion.getHashKey();
1818 // Does the sha1 match a version ?
1819 if (hkServerSHA1 == hkLocalSHA1)
1821 nVersionFound = rVersion.getVersionNumber();
1822 applyDate(sFilePath, rVersion.getTimeStamp());
1823 // break; // ace -> same as above
1828 // No version available found
1829 if (nVersionFound == 0xFFFFFFFF)
1831 string sTranslate = CI18N::get("uiNoVersionFound");
1832 setState(true, sTranslate);
1833 // Get all patches from beginning (first patch is reference file)
1834 ftpOut.FileName = rFilename;
1835 ftpOut.LocalFileToDelete = true;
1836 ftpOut.LocalFileExists = !inPatchDir;
1837 // It happens some time (maybe a bug) that the versionCount is 0... =>
1838 // it happens if the BNP file is empty (8 bytes)
1839 ftpOut.FinalFileSize = EmptyBnpFileSize;
1840 // Get all the patches version
1841 for (j = 0; j < rFile.versionCount(); ++j)
1843 ftpOut.Patches.push_back(rFile.getVersion(j).getVersionNumber());
1844 ftpOut.PatcheSizes.push_back(rFile.getVersion(j).getPatchSize());
1845 ftpOut.LastFileDate = rFile.getVersion(j).getTimeStamp();
1846 ftpOut.FinalFileSize = rFile.getVersion(j).getFileSize();
1847 ftpOut.SZFileSize = rFile.getVersion(j).get7ZFileSize();
1850 else // A version of the file has been found
1852 string sTranslate = CI18N::get("uiVersionFound") + " " + toString(nVersionFound);
1853 setState(true, sTranslate);
1854 // Get All patches from this version !
1855 ftpOut.FileName = rFilename;
1856 ftpOut.LocalFileToDelete = false;
1857 ftpOut.LocalFileExists = !inPatchDir;
1858 // Go to the version
1859 for (j = 0; j < rFile.versionCount(); ++j)
1860 if (rFile.getVersion(j).getVersionNumber() == nVersionFound)
1861 break;
1863 nlassert(j != rFile.versionCount()); // Not normal if we cant find the version we found previously
1865 // Point on the next version
1866 j++;
1867 // If there are newer versions
1868 if (j != rFile.versionCount())
1870 // Add all version until the last one
1871 for (; j < rFile.versionCount(); ++j)
1873 ftpOut.Patches.push_back(rFile.getVersion(j).getVersionNumber());
1874 ftpOut.PatcheSizes.push_back(rFile.getVersion(j).getPatchSize());
1875 ftpOut.LastFileDate = rFile.getVersion(j).getTimeStamp();
1876 ftpOut.SZFileSize = rFile.getVersion(j).get7ZFileSize();
1879 // Else this file is up to date !
1881 // For info, get its final file size
1882 ftpOut.FinalFileSize= rFile.getVersion(rFile.versionCount()-1).getFileSize();
1884 } // end of else local BNP file exists
1887 // ****************************************************************************
1888 bool CPatchManager::bnpUnpack(const string &srcBigfile, const string &dstPath, vector<string> &vFilenames)
1890 nldebug("bnpUnpack: srcBigfile=%s", srcBigfile.c_str());
1891 string SourceName, DestPath;
1893 // following lines added by Sadge to ensure that the correct file gets patched
1894 // if (NLMISC::CFile::fileExists(ClientDataPath + srcBigfile)) SourceName = ClientDataPath + srcBigfile;
1895 // else if (NLMISC::CFile::fileExists(srcBigfile)) SourceName = ClientDataPath + srcBigfile;
1896 SourceName= srcBigfile;
1898 // following lines removed by Sadge to ensure that the correct file gets patched
1899 // SourceName = CPath::lookup(srcBigfile, false, false);
1900 // if (SourceName.empty())
1901 // SourceName = ClientPatchPath + srcBigfile;
1903 if (dstPath.empty())
1904 DestPath = ClientRootPath;
1905 else
1906 DestPath = CPath::standardizePath (dstPath);
1908 string s = CI18N::get("uiUnpack") + " " + NLMISC::CFile::getFilename(SourceName);
1909 setState(true,s);
1911 // Read Header of the BNP File
1912 CBigFile::BNP bnpFile;
1913 bnpFile.BigFileName = SourceName;
1915 if (!bnpFile.readHeader())
1917 string s = CI18N::get("uiUnpackErrHead") + " " + CFile::getFilename(SourceName);
1918 setState(true,s);
1919 return false;
1922 // Unpack
1923 if (!bnpFile.unpack(DestPath))
1924 return false;
1926 // Return names
1928 vFilenames.clear();
1929 for (uint32 i = 0; i < bnpFile.SFiles.size(); ++i)
1930 vFilenames.push_back(bnpFile.SFiles[i].Name);
1933 return true;
1937 // ****************************************************************************
1938 int CPatchManager::downloadProgressFunc(void *foo, double t, double d, double ultotal, double ulnow)
1940 if (d != t)
1942 // In the case of progress = 1, don't update because, this will be called in case of error to signal the end of the download, though
1943 // no download actually occurred. Instead, we set progress to 1.f at the end of downloadWithCurl if everything went fine
1944 return validateProgress(foo, t, d, ultotal, ulnow);
1946 return 0;
1949 // ****************************************************************************
1950 int CPatchManager::validateProgress(void *foo, double t, double d, double /* ultotal */, double /* ulnow */)
1952 static std::vector<std::string> units;
1954 if (units.empty())
1956 units.push_back(CI18N::get("uiByte"));
1957 units.push_back(CI18N::get("uiKb"));
1958 units.push_back(CI18N::get("uiMb"));
1961 CPatchManager *pPM = CPatchManager::getInstance();
1962 double pour1 = t!=0.0?d*100.0/t:0.0;
1963 string sTranslate = CI18N::get("uiLoginGetFile") + toString(" %s : %s / %s (%.02f %%)", NLMISC::CFile::getFilename(pPM->CurrentFile).c_str(),
1964 NLMISC::bytesToHumanReadableUnits((uint64)d, units).c_str(), NLMISC::bytesToHumanReadableUnits((uint64)t, units).c_str(), pour1);
1965 pPM->setState(false, sTranslate);
1966 if (foo)
1968 ((NLMISC::IProgressCallback *) foo)->progress((float) (t != 0 ? d / t : 0));
1970 return 0;
1973 // ****************************************************************************
1974 void CPatchManager::MyPatchingCB::progress(float f)
1976 CPatchManager *pPM = CPatchManager::getInstance();
1977 double p = 100.0*f;
1978 string sTranslate = CI18N::get("uiApplyingDelta") + toString(" %s (%.02f %%)", CFile::getFilename(patchFilename).c_str(), p);
1979 pPM->setState(false, sTranslate);
1982 // ***************************************************************************
1983 void CPatchManager::startScanDataThread()
1985 if (ScanDataThread != NULL)
1987 nlwarning ("scan data thread is already running");
1988 return;
1990 if (Thread != NULL)
1992 nlwarning ("a thread is already running");
1993 return;
1996 _ErrorMessage.clear();
1998 // Reset result
1999 clearDataScanLog();
2001 // Read now the client version and Desc File.
2002 readClientVersionAndDescFile();
2004 // start thread
2005 ScanDataThread = new CScanDataThread();
2006 nlassert (ScanDataThread != NULL);
2008 Thread = IThread::create (ScanDataThread);
2009 nlassert (Thread != NULL);
2010 Thread->start ();
2013 // ****************************************************************************
2014 bool CPatchManager::isScanDataThreadEnded(bool &ok)
2016 if (ScanDataThread == NULL)
2018 ok = false;
2019 return true;
2022 bool end = ScanDataThread->Ended;
2023 if (end)
2025 ok = ScanDataThread->CheckOk;
2026 stopScanDataThread();
2029 return end;
2032 // ****************************************************************************
2033 void CPatchManager::stopScanDataThread()
2035 if(ScanDataThread && Thread)
2037 Thread->wait();
2038 delete Thread;
2039 Thread = NULL;
2040 delete ScanDataThread;
2041 ScanDataThread = NULL;
2045 // ***************************************************************************
2046 void CPatchManager::askForStopScanDataThread()
2048 if(!ScanDataThread)
2049 return;
2051 ScanDataThread->AskForCancel= true;
2054 // ***************************************************************************
2055 uint CPatchManager::applyScanDataResult()
2057 // if still running, abort
2058 if(ScanDataThread)
2059 return 0;
2061 uint numError= 0;
2064 TSyncDataScanState::CAccessor ac(&DataScanState);
2065 CDataScanState &val= ac.value();
2066 numError= (uint)val.FilesWithScanDataError.size();
2068 // Touch the files with error (will be reloaded at next patch)
2069 for(uint i=0;i<numError;i++)
2071 SFileToPatch &ftp= val.FilesWithScanDataError[i];
2073 // if the file was not found (just loggued for information)
2074 if(!ftp.LocalFileExists)
2075 continue;
2077 // get file path
2078 // following lines added by Sadge to ensure that the correct file gets patched
2079 string sFilePath;
2080 if (NLMISC::CFile::fileExists(WritableClientDataPath + ftp.FileName)) sFilePath = WritableClientDataPath + ftp.FileName;
2081 if (sFilePath.empty() && NLMISC::CFile::fileExists(ReadableClientDataPath + ftp.FileName)) sFilePath = ReadableClientDataPath + ftp.FileName;
2082 if (sFilePath.empty() && NLMISC::CFile::fileExists(ClientPatchPath + ftp.FileName)) sFilePath = ClientPatchPath + ftp.FileName;
2084 // following lines removed by Sadge to ensure that the correct file gets patched
2085 // string sFilePath = CPath::lookup(ftp.FileName, false, false);
2086 // if (sFilePath.empty())
2087 // {
2088 // if (NLMISC::CFile::fileExists(ClientPatchPath + ftp.FileName))
2089 // sFilePath = ClientPatchPath + ftp.FileName;
2090 // }
2092 // Reset to a dummy date, so the patch will be fully/checked/patched next time
2093 if(!sFilePath.empty())
2094 applyDate(sFilePath, DefaultResetDate);
2095 else
2096 nlwarning("File '%s' Not Found. should exist...", ftp.FileName.c_str());
2100 return numError;
2103 // ***************************************************************************
2104 bool CPatchManager::getDataScanLog(string &text)
2106 text.clear();
2107 bool changed= false;
2109 TSyncDataScanState::CAccessor ac(&DataScanState);
2110 CDataScanState &val= ac.value();
2111 changed= val.Changed;
2112 // if changed, build the log
2113 if(changed)
2115 for(uint i=0;i<val.FilesWithScanDataError.size();i++)
2117 string str;
2118 getCorruptedFileInfo(val.FilesWithScanDataError[i], str);
2119 text+= str + "\n";
2122 // then reset
2123 val.Changed= false;
2126 return changed;
2129 // ***************************************************************************
2130 void CPatchManager::addDataScanLogCorruptedFile(const SFileToPatch &ftp)
2133 TSyncDataScanState::CAccessor ac(&DataScanState);
2134 CDataScanState &val= ac.value();
2135 val.FilesWithScanDataError.push_back(ftp);
2136 val.Changed= true;
2140 // ***************************************************************************
2141 void CPatchManager::clearDataScanLog()
2144 TSyncDataScanState::CAccessor ac(&DataScanState);
2145 CDataScanState &val= ac.value();
2146 val.FilesWithScanDataError.clear();
2147 val.Changed= true;
2151 // ***************************************************************************
2152 void CPatchManager::getCorruptedFileInfo(const SFileToPatch &ftp, string &sTranslate)
2154 sTranslate = CI18N::get("uiCorruptedFile") + " " + ftp.FileName + " (" +
2155 toString("%.1f ", (float)ftp.FinalFileSize/1000000.f) + CI18N::get("uiMb") + ")";
2159 // ****************************************************************************
2160 // ****************************************************************************
2161 // ****************************************************************************
2162 // CCheckThread
2163 // ****************************************************************************
2164 // ****************************************************************************
2165 // ****************************************************************************
2167 // ****************************************************************************
2168 CCheckThread::CCheckThread(bool includeBackgroundPatch)
2170 Ended = false;
2171 CheckOk = false;
2172 TotalFileToCheck = 0;
2173 CurrentFileChecked = 0;
2174 IncludeBackgroundPatch = includeBackgroundPatch;
2175 StopAsked = false;
2178 // ****************************************************************************
2179 void CCheckThread::run ()
2181 nlwarning("CCheckThread::start");
2182 StopAsked = false;
2183 CPatchManager *pPM = CPatchManager::getInstance();
2184 pPM->MustLaunchBatFile = false;
2187 nlwarning("CCheckThread : get version");
2188 uint32 i, j, k;
2189 // Check if the client version is the same as the server version
2190 string sClientVersion = pPM->getClientVersion();
2191 string sServerVersion = pPM->getServerVersion();
2192 string sTranslate = CI18N::get("uiClientVersion") + " (" + sClientVersion + ") ";
2193 sTranslate += CI18N::get("uiServerVersion") + " (" + sServerVersion + ")";
2194 pPM->setState(true, sTranslate);
2196 if (sServerVersion.empty())
2198 // No need to patch
2199 CheckOk = true;
2200 Ended = true;
2201 return;
2204 sint32 nServerVersion, nClientVersion;
2205 fromString(sServerVersion, nServerVersion);
2206 fromString(sClientVersion, nClientVersion);
2208 if (nClientVersion != nServerVersion)
2210 // first, try in the version subdirectory
2213 pPM->DescFilename = toString("%05d/ryzom_%05d.idx", nServerVersion, nServerVersion);
2214 // The client version is different from the server version : download new description file
2215 pPM->getServerFile(pPM->DescFilename, false); // For the moment description file is not zipped
2217 catch (...)
2219 // fallback to patch root directory
2220 pPM->DescFilename = toString("ryzom_%05d.idx", nServerVersion);
2221 // The client version is different from the server version : download new description file
2222 pPM->getServerFile(pPM->DescFilename, false); // For the moment description file is not zipped
2226 nlwarning("CCheckThread : read description files");
2228 // Read the description file
2229 pPM->readDescFile(nServerVersion);
2231 nlwarning("CCheckThread : check files");
2233 // For all bnp in the description file get all patches to apply
2234 // depending on the version of the client bnp files
2235 const CBNPFileSet &rDescFiles = pPM->DescFile.getFiles();
2236 pPM->FilesToPatch.clear();
2237 TotalFileToCheck = rDescFiles.fileCount();
2238 for (i = 0; i < rDescFiles.fileCount(); ++i)
2240 CPatchManager::SFileToPatch ftp;
2241 sTranslate = CI18N::get("uiCheckingFile") + " " + rDescFiles.getFile(i).getFileName();
2242 pPM->setState(true, sTranslate);
2243 // get list of patch to apply to this file. don't to a full checksum test if possible
2244 nlwarning(rDescFiles.getFile(i).getFileName().c_str());
2245 pPM->getPatchFromDesc(ftp, rDescFiles.getFile(i), false);
2246 // add the file if there are some patches to apply, or if an already patched version was found in the unpack directory
2247 if (!ftp.Patches.empty() || (IncludeBackgroundPatch && !ftp.SrcFileName.empty()))
2249 pPM->FilesToPatch.push_back(ftp);
2250 sTranslate = CI18N::get("uiNeededPatches") + " " + toString (ftp.Patches.size());
2251 pPM->setState(true, sTranslate);
2253 CurrentFileChecked = i;
2256 // Here we got all the files to patch in FilesToPatch and all the versions that must be obtained
2257 // Now we have to get the optional categories
2258 const CBNPCategorySet &rDescCats = pPM->DescFile.getCategories();
2259 pPM->OptionalCat.clear();
2260 for (i = 0; i < rDescCats.categoryCount(); ++i)
2262 // For all optional categories check if there is a 'file to patch' in it
2263 const CBNPCategory &rCat = rDescCats.getCategory(i);
2264 if (rCat.isOptional())
2265 for (j = 0; j < rCat.fileCount(); ++j)
2267 const string &rFilename = rCat.getFile(j);
2268 bool bAdded = false;
2269 for (k = 0; k < pPM->FilesToPatch.size(); ++k)
2271 if (stricmp(pPM->FilesToPatch[k].FileName.c_str(), rFilename.c_str()) == 0)
2273 pPM->OptionalCat.push_back(rCat.getName());
2274 bAdded = true;
2275 break;
2278 if (bAdded)
2279 break;
2283 // For all categories that required an optional category if the cat required is present the category that
2284 // reference it must be present
2285 for (i = 0; i < rDescCats.categoryCount(); ++i)
2287 // For all optional categories check if there is a 'file to patch' in it
2288 const CBNPCategory &rCat = rDescCats.getCategory(i);
2289 if (rCat.isOptional() && !rCat.getCatRequired().empty())
2291 // Does the rCat is already present ?
2292 bool bFound = false;
2293 for (j = 0; j < pPM->OptionalCat.size(); ++j)
2295 if (rCat.getName() == pPM->OptionalCat[j])
2297 bFound = true;
2298 break;
2302 if (!bFound)
2304 // rCat is not present but perhaps its required cat is present
2305 const string &sCatReq = rCat.getCatRequired();
2306 for (j = 0; j < pPM->OptionalCat.size(); ++j)
2308 if (sCatReq == pPM->OptionalCat[j])
2310 // Required Cat present -> Add the category rCat
2311 pPM->OptionalCat.push_back(rCat.getName());
2312 break;
2320 // Delete categories optional cat that are hidden
2321 for (i = 0; i < rDescCats.categoryCount(); ++i)
2323 // For all optional categories check if there is a 'file to patch' in it
2324 const CBNPCategory &rCat = rDescCats.getCategory(i);
2325 if (rCat.isOptional() && rCat.isHidden())
2327 // Does the rCat is present ?
2328 for (j = 0; j < pPM->OptionalCat.size(); ++j)
2330 if (rCat.getName() == pPM->OptionalCat[j])
2332 // remove it
2333 /*#ifdef RY_BG_DOWNLOADER
2334 // fixme : strange stlport link error for the background downloader when using erase ...
2335 std::copy(pPM->OptionalCat.begin() + j + 1, pPM->OptionalCat.end(), pPM->OptionalCat.begin()+j);
2336 pPM->OptionalCat.resize(pPM->OptionalCat.size() - 1);
2337 #else*/
2338 pPM->OptionalCat.erase(pPM->OptionalCat.begin()+j);
2339 //#endif
2340 break;
2346 // Get all extract to category and check files inside the bnp with real files
2347 for (i = 0; i < rDescCats.categoryCount(); ++i)
2349 // For all optional categories check if there is a 'file to patch' in it
2350 const CBNPCategory &rCat = rDescCats.getCategory(i);
2351 if (!rCat.getUnpackTo().empty())
2353 for (j = 0; j < rCat.fileCount(); ++j)
2355 string sBNPFilename = pPM->ClientPatchPath + rCat.getFile(j);
2357 sTranslate = CI18N::get("uiCheckInBNP") + " " + rCat.getFile(j);
2358 pPM->setState(true, sTranslate);
2359 CBigFile::BNP bnpFile;
2360 bnpFile.BigFileName = sBNPFilename;
2362 if (bnpFile.readHeader())
2364 // read the file inside the bnp and calculate the sha1
2365 FILE *bnp = nlfopen (sBNPFilename, "rb");
2366 if (bnp != NULL)
2368 for (uint32 k = 0; k < bnpFile.SFiles.size(); ++k)
2370 const CBigFile::SBNPFile &rBNPFile = bnpFile.SFiles[k];
2371 // Is the real file exists ?
2372 string sRealFilename = rCat.getUnpackTo() + rBNPFile.Name;
2373 if (NLMISC::CFile::fileExists(sRealFilename))
2375 // Yes compare the sha1 with the sha1 of the BNP File
2376 CHashKey sha1BNPFile;
2377 nlfseek64 (bnp, rBNPFile.Pos, SEEK_SET);
2378 uint8 *pPtr = new uint8[rBNPFile.Size];
2379 if (fread (pPtr, rBNPFile.Size, 1, bnp) != 1)
2381 delete [] pPtr;
2382 break;
2385 sha1BNPFile = getSHA1(pPtr, rBNPFile.Size);
2386 delete [] pPtr;
2387 CHashKey sha1RealFile = getSHA1(sRealFilename, true);
2388 if ( ! (sha1RealFile == sha1BNPFile))
2390 sTranslate = CI18N::get("uiSHA1Diff") + " " + rBNPFile.Name;
2391 pPM->setState(true, sTranslate);
2392 pPM->MustLaunchBatFile = true;
2395 else
2397 // File dest do not exist
2398 sTranslate = CI18N::get("uiSHA1Diff") + " " + rBNPFile.Name;
2399 pPM->setState(true, sTranslate);
2400 pPM->MustLaunchBatFile = true;
2404 fclose (bnp);
2406 if (StopAsked)
2408 StopAsked = false;
2409 return;
2416 sTranslate = CI18N::get("uiCheckEndNoErr");
2417 pPM->setState(true, sTranslate);
2418 CheckOk = true;
2419 Ended = true;
2421 catch (const NLMISC::EDiskFullError &)
2423 // more explicit message for this common error case
2424 nlwarning("EXCEPTION CATCH: disk full");
2425 pPM->setState(true, CI18N::get("uiCheckEndWithErr"));
2426 pPM->setErrorMessage(CI18N::get("uiPatchDiskFull"));
2427 CheckOk = false;
2428 Ended = true;
2430 catch (const Exception &e)
2432 nlwarning("EXCEPTION CATCH: CCheckThread::run() failed");
2433 string sTranslate = CI18N::get("uiCheckEndWithErr") + " " + e.what();
2434 pPM->setState(true, CI18N::get("uiCheckEndWithErr"));
2435 pPM->setErrorMessage(sTranslate);
2436 CheckOk = false;
2437 Ended = true;
2441 // ****************************************************************************
2442 // ****************************************************************************
2443 // ****************************************************************************
2444 // CPatchThread
2445 // ****************************************************************************
2446 // ****************************************************************************
2447 // ****************************************************************************
2449 // ****************************************************************************
2450 CPatchThread::CPatchThread(bool commitPatch)
2452 Ended = false;
2453 PatchOk = false;
2454 CurrentFilePatched = 0;
2455 PatchSizeProgress = 0;
2456 _CommitPatch = commitPatch;
2457 StopAsked = false;
2460 // ****************************************************************************
2461 void CPatchThread::clear()
2463 AllFilesToPatch.clear();
2466 // ****************************************************************************
2467 // trap : optimize this if needed
2468 void CPatchThread::add(const CPatchManager::SFileToPatch &ftp)
2470 for (uint32 i = 0; i < AllFilesToPatch.size(); ++i)
2472 if (AllFilesToPatch[i].FileName == ftp.FileName)
2473 return;
2475 AllFilesToPatch.push_back(ftp);
2479 // ****************************************************************************
2480 void CPatchThread::run()
2482 StopAsked = false;
2483 CPatchManager *pPM = CPatchManager::getInstance();
2484 bool bErr = false;
2485 uint32 i;
2487 string sServerVersion = pPM->getServerVersion();
2488 PatchSizeProgress = 0;
2490 if (sServerVersion.empty())
2492 // No need to patch
2493 PatchOk = true;
2494 Ended = true;
2495 return;
2498 // Patch all the files
2499 // If at least one file has been patched relaunch the client
2501 CurrentFilePatched = 0.f;
2503 string sTranslate;
2506 // First do all ref files
2507 // ----------------------
2509 for (i = 0; i < AllFilesToPatch.size(); ++i)
2511 CPatchManager::SFileToPatch &rFTP = AllFilesToPatch[i];
2512 string ext = NLMISC::CFile::getExtension(rFTP.FileName);
2513 if (ext == "ref")
2515 float oldCurrentFilePatched = CurrentFilePatched;
2516 processFile (rFTP);
2517 pPM->MustLaunchBatFile = true;
2518 CurrentFilePatched = oldCurrentFilePatched + 1.f;
2520 if (StopAsked)
2522 StopAsked = false;
2523 return;
2527 // Second do all bnp files
2528 // -----------------------
2530 for (i = 0; i < AllFilesToPatch.size(); ++i)
2532 CPatchManager::SFileToPatch &rFTP = AllFilesToPatch[i];
2534 string ext = NLMISC::CFile::getExtension(rFTP.FileName);
2535 if (ext == "bnp" || ext == "snp")
2537 float oldCurrentFilePatched = CurrentFilePatched;
2538 processFile (rFTP);
2539 pPM->MustLaunchBatFile = true;
2540 CurrentFilePatched = oldCurrentFilePatched + 1.f;
2542 if (StopAsked)
2544 StopAsked = false;
2545 return;
2550 catch (const NLMISC::EDiskFullError &)
2552 // more explicit message for this common error case
2553 nlwarning("EXCEPTION CATCH: CPatchThread::run() Disk Full");
2554 pPM->setState(true, CI18N::get("uiPatchEndWithErr"));
2555 sTranslate = CI18N::get("uiPatchDiskFull");
2556 bErr = true;
2558 catch(const Exception &e)
2560 nlwarning("EXCEPTION CATCH: CPatchThread::run() failed");
2561 pPM->setState(true, string(e.what()));
2562 sTranslate = CI18N::get("uiPatchEndWithErr");
2563 bErr = true;
2567 // Unpack all files with the UnpackTo flag
2568 // ---------------------------------------
2569 // To do that we create a batch file that will copy all files (unpacked to patch directory)
2570 // to the directory we want (the UnpackTo string)
2572 // Recreate batch file
2573 if (!pPM->MustLaunchBatFile && !pPM->getAsyncDownloader())
2575 pPM->deleteFile(pPM->UpdateBatchFilename, false, false);
2578 if (!bErr)
2580 sTranslate = CI18N::get("uiPatchEndNoErr");
2582 else
2584 // Set a more explicit error message
2585 pPM->setErrorMessage(sTranslate);
2588 PatchOk = !bErr;
2589 Ended = true;
2593 class CPatchThreadDownloadProgress : public NLMISC::IProgressCallback
2595 public:
2596 CPatchThread *PatchThread;
2597 float Scale;
2598 float Bias;
2599 uint CurrentFilePatched;
2600 CPatchThreadDownloadProgress() : PatchThread(NULL),
2601 Scale(1.f),
2602 Bias(0.f),
2603 CurrentFilePatched(0)
2606 virtual void progress (float progressValue)
2608 clamp(progressValue, 0.f, 1.f);
2609 nlassert(PatchThread);
2610 //PatchThread->CurrentFilePatched = std::max(PatchThread->CurrentFilePatched, (float) this->CurrentFilePatched + Bias + Scale * progressValue);
2611 PatchThread->CurrentFilePatched = this->CurrentFilePatched + Bias + Scale * progressValue;
2612 CPatchManager::getInstance()->touchState();
2616 // ****************************************************************************
2617 void CPatchThread::processFile (CPatchManager::SFileToPatch &rFTP)
2619 CPatchManager *pPM = CPatchManager::getInstance();
2621 // Source File Name (in writable or readable directory)
2622 string SourceName;
2624 // Destination File Name (in writable directory)
2625 string DestinationName;
2627 if (rFTP.ExtractPath.empty())
2629 DestinationName = pPM->WritableClientDataPath + rFTP.FileName;
2631 if (rFTP.LocalFileExists)
2633 // following lines added by Sadge to ensure that the correct file gets patched
2634 SourceName.clear();
2636 if (NLMISC::CFile::fileExists(pPM->WritableClientDataPath + rFTP.FileName))
2638 SourceName = pPM->WritableClientDataPath + rFTP.FileName;
2640 else if (NLMISC::CFile::fileExists(pPM->ReadableClientDataPath + rFTP.FileName))
2642 SourceName = pPM->ReadableClientDataPath + rFTP.FileName;
2645 // version from previous download
2646 if (SourceName.empty()) throw Exception (std::string("ERROR: Failed to find file: ")+rFTP.FileName);
2648 // following lines removed by Sadge to ensure that the correct file gets patched
2649 // SourceName = CPath::lookup(rFTP.FileName); // exception if file do not exists
2651 else
2653 // note : if file was background downloaded, we have :
2654 // rFTP.LocalFileExists = false
2655 // rFTP.SrcFileName = "unpack/filename.bnp.tmp"
2656 SourceName = DestinationName;
2659 else
2661 SourceName = pPM->ClientPatchPath + rFTP.FileName;
2662 DestinationName = SourceName;
2665 if (rFTP.LocalFileToDelete)
2667 // corrupted or invalid file ? ....
2668 NLMISC::CFile::deleteFile(SourceName);
2669 rFTP.LocalFileExists = false;
2672 string sTranslate;
2673 sTranslate = CI18N::get("uiProcessing") + " " + rFTP.FileName;
2674 pPM->setState(true, sTranslate);
2676 if (pPM->getAsyncDownloader())
2678 if (!rFTP.ExtractPath.empty())
2680 std::string info = rFTP.FileName;
2682 string PatchName = toString("%05d/%s%s", rFTP.Patches[rFTP.Patches.size() - 1], rFTP.FileName.c_str(), ".lzma");
2683 pPM->getAsyncDownloader()->addToDownloadList(PatchName, SourceName, rFTP.LastFileDate, rFTP.ExtractPath, rFTP.SZFileSize, rFTP.FinalFileSize );
2684 return;
2688 std::string tmpSourceName = rFTP.SrcFileName.empty() ? SourceName : rFTP.SrcFileName; // source is same than destination, or possibly
2689 // a patch from a previous background downloader session
2690 if (rFTP.LocalFileToDelete)
2692 // corrupted or invalid file ? ....
2693 rFTP.LocalFileExists = false;
2694 NLMISC::CFile::deleteFile(tmpSourceName);
2697 string OutFilename;
2698 bool usePatchFile = true;
2699 bool haveAllreadyTryiedDownloadingOfFile = false;
2701 // compute the total size of patch to download
2702 uint32 totalPatchSize = 0;
2703 if (rFTP.Incremental)
2705 for (uint i=0; i<rFTP.PatcheSizes.size(); ++i)
2706 totalPatchSize += rFTP.PatcheSizes[i];
2708 else if (!rFTP.PatcheSizes.empty())
2710 totalPatchSize = rFTP.PatcheSizes.back();
2714 CPatchThreadDownloadProgress progress;
2715 progress.PatchThread = this;
2716 progress.CurrentFilePatched = (uint) floorf(CurrentFilePatched);
2718 // look for the source file, if not present or invalid (not matching
2719 // a known version) or if total patch size greater than half the
2720 // uncompressed final size or if total patch size is greater
2721 // than lzma compressed file, then load the lzma file
2722 if ((rFTP.Incremental && !rFTP.LocalFileExists) // incremental and no base file, we need a complete file
2723 || totalPatchSize > rFTP.FinalFileSize / 2 // patch is too big regarding the final file, patch applying time will be slow
2724 || rFTP.SZFileSize < totalPatchSize) // lzma is smaller than patch !
2726 breakable
2728 usePatchFile = false;
2729 // compute the seven zip filename
2730 string lzmaFile = rFTP.FileName+".lzma";
2732 // download the 7zip file
2735 // first, try in the file version subfolfer
2738 progress.Scale = 1.f;
2739 progress.Bias = 0.f;
2740 if (!rFTP.Patches.empty())
2742 pPM->getServerFile(toString("%05u/", rFTP.Patches.back())+lzmaFile, false, "", &progress);
2744 // else -> file comes from a previous download (with .tmp extension, and is up to date)
2745 // the remaining code will just rename it with good name and exit
2747 catch (const NLMISC::EWriteError &)
2749 // this is a local error, rethrow ...
2750 throw;
2752 catch(...)
2754 // failed with version subfolder, try in the root patch directory
2755 pPM->getServerFile(lzmaFile, false, "", &progress);
2758 catch (const NLMISC::EWriteError &)
2760 // this is a local error, rethrow ...
2761 throw;
2763 catch (...)
2765 // can not load the 7zip file, use normal patching
2766 usePatchFile = true;
2767 haveAllreadyTryiedDownloadingOfFile = true;
2768 break;
2771 OutFilename = pPM->ClientPatchPath + NLMISC::CFile::getFilename(rFTP.FileName);
2772 // try to unpack the file
2775 if (!unpackLZMA(pPM->ClientPatchPath+lzmaFile, OutFilename+".tmp"))
2777 // fallback to standard patch method
2778 usePatchFile = true;
2779 haveAllreadyTryiedDownloadingOfFile = true;
2780 break;
2783 catch (const NLMISC::EWriteError&)
2785 throw;
2787 catch (...)
2789 nlwarning("Failed to unpack lzma file %s", (pPM->ClientPatchPath+lzmaFile).c_str());
2790 // fallback to standard patch method
2791 usePatchFile = true;
2792 haveAllreadyTryiedDownloadingOfFile = true;
2793 break;
2796 if (rFTP.LocalFileExists)
2797 pPM->deleteFile(SourceName);
2799 pPM->deleteFile(pPM->ClientPatchPath+lzmaFile); // delete the archive file
2800 pPM->deleteFile(SourceName, false, false); // File can exists if bad BNP loading
2801 if (_CommitPatch)
2803 pPM->renameFile(OutFilename+".tmp", DestinationName);
2807 if (usePatchFile)
2809 uint32 currentPatchedSize = 0;
2810 for (uint32 j = 0; j < rFTP.Patches.size(); ++j)
2812 // Get the patch
2813 // first, try in the patch subdirectory
2814 string PatchName;
2817 PatchName = toString("%05d/", rFTP.Patches[j]) + rFTP.FileName.substr(0, rFTP.FileName.size()-4) + toString("_%05d", rFTP.Patches[j]) + ".patch";
2818 sTranslate = CI18N::get("uiLoginGetFile") + " " + PatchName;
2819 pPM->setState(true, sTranslate);
2820 progress.Scale = 1.f;
2821 progress.Bias = totalPatchSize != 0 ? (float) currentPatchedSize / totalPatchSize : 0.f;
2822 progress.Scale = totalPatchSize != 0 ? (float) rFTP.PatcheSizes[j] / totalPatchSize : 1.f;
2823 pPM->getServerFile(PatchName, false, "", &progress);
2824 // remove the subfolder name
2825 PatchName = NLMISC::CFile::getFilename(PatchName);
2827 catch (const NLMISC::EWriteError &)
2829 throw;
2831 catch (...)
2833 // fallback to patch root directory
2834 PatchName = rFTP.FileName.substr(0, rFTP.FileName.size()-4);
2835 PatchName += toString("_%05d", rFTP.Patches[j]) + ".patch";
2836 sTranslate = CI18N::get("uiLoginGetFile") + " " + PatchName;
2837 pPM->setState(true, sTranslate);
2838 pPM->getServerFile(PatchName, false, "", &progress);
2841 // Apply the patch
2842 // If the patches are not to be applied on the last version
2843 string SourceNameXD = tmpSourceName;
2844 if (!rFTP.Incremental)
2846 // Some note about non incremental patches :
2847 // Usually, files such as '.exe' or '.dll' do not work well with incremental patch, so it is better
2848 // to apply a single patch from a refrence version to them. (so here we necessarily got
2849 // 'rFTP.Patches.size() == 1' !)
2850 // The reference version itself is incrementally patched, however, and may be rebuilt from time to time
2851 // when the delta to the reference has become too big (but is most of the time < to the sum of equivallent delta patchs)
2852 // The non-incremental final patch (from ref file to final bnp), is guaranteed to be done last
2853 // after the reference file has been updated
2855 // Find the reference file to apply the patch
2856 SourceNameXD = rFTP.FileName;
2857 SourceNameXD = SourceNameXD.substr(0, SourceNameXD.rfind('.'));
2858 SourceNameXD += "_.ref";
2860 if (!_CommitPatch)
2862 // works
2863 std::string tmpRefFile = SourceNameXD + ".tmp";
2864 if (!NLMISC::CFile::fileExists(pPM->ClientPatchPath + tmpRefFile))
2866 // Not found in the patch directory -> version in data directory should be good, or would have been
2867 // detected by the check thread else.
2869 else
2871 SourceNameXD = tmpRefFile; // good ref file download by bg downloader
2875 // following lines added by Sadge to ensure that the correct file gets patched
2876 // string SourceNameXDFull;
2877 // if (NLMISC::CFile::fileExists(pPM->ClientDataPath + SourceNameXD)) SourceNameXDFull = pPM->ClientDataPath + SourceNameXD;
2879 // following lines removed by Sadge to ensure that the correct file gets patched
2880 // string SourceNameXDFull = CPath::lookup(SourceNameXD, false, false);
2881 // if (SourceNameXDFull.empty())
2882 // SourceNameXDFull = pPM->ClientDataPath + SourceNameXD;
2883 // SourceNameXD = SourceNameXDFull;
2884 if (CFile::fileExists(pPM->WritableClientDataPath + SourceNameXD))
2886 SourceNameXD = pPM->WritableClientDataPath + SourceNameXD;
2888 else if (CFile::fileExists(pPM->ReadableClientDataPath + SourceNameXD))
2890 SourceNameXD = pPM->ReadableClientDataPath + SourceNameXD;
2894 PatchName = pPM->ClientPatchPath + PatchName;
2896 string OutFilename = pPM->ClientPatchPath + rFTP.FileName + ".tmp__" + toString(j);
2898 sTranslate = CI18N::get("uiApplyingDelta") + " " + CFile::getFilename(PatchName);
2899 pPM->setState(true, sTranslate);
2901 bool deltaPatchResult = xDeltaPatch(PatchName, SourceNameXD, OutFilename);
2903 if (!deltaPatchResult && !haveAllreadyTryiedDownloadingOfFile) // Patch failed, try to download and apply lzma
2905 breakable
2907 // compute the seven zip filename
2908 string lzmaFile = rFTP.FileName+".lzma";
2910 // download the 7zip file
2913 // first, try in the file version subfolfer
2916 progress.Scale = 1.f;
2917 progress.Bias = 0.f;
2918 if (!rFTP.Patches.empty())
2920 pPM->getServerFile(toString("%05u/", rFTP.Patches.back())+lzmaFile, false, "", &progress);
2922 // else -> file comes from a previous download (with .tmp extension, and is up to date)
2923 // the remaining code will just rename it with good name and exit
2925 catch (const NLMISC::EWriteError &)
2927 // this is a local error, rethrow ...
2928 throw;
2930 catch(...)
2932 // failed with version subfolder, try in the root patch directory
2933 pPM->getServerFile(lzmaFile, false, "", &progress);
2936 catch (const NLMISC::EWriteError &)
2938 // this is a local error, rethrow ...
2939 throw;
2941 catch (...)
2943 break;
2946 OutFilename = pPM->ClientPatchPath + NLMISC::CFile::getFilename(rFTP.FileName);
2947 // try to unpack the file
2950 if (!unpackLZMA(pPM->ClientPatchPath+lzmaFile, OutFilename+".tmp"))
2952 break;
2955 catch (const NLMISC::EWriteError&)
2957 throw;
2959 catch (...)
2961 nlwarning("Failed to unpack lzma file %s", (pPM->ClientPatchPath+lzmaFile).c_str());
2962 break;
2965 if (rFTP.LocalFileExists)
2966 pPM->deleteFile(SourceName);
2968 pPM->deleteFile(pPM->ClientPatchPath+lzmaFile); // delete the archive file
2969 pPM->deleteFile(SourceName, false, false); // File can exists if bad BNP loading
2970 if (_CommitPatch)
2972 pPM->renameFile(OutFilename+".tmp", DestinationName);
2976 else
2978 if (rFTP.LocalFileExists)
2979 pPM->deleteFile(SourceName);
2980 pPM->deleteFile(PatchName);
2982 if (j > 0)
2984 pPM->deleteFile(SourceNameXD, false, false); // File can exists if bad BNP loading
2986 tmpSourceName = OutFilename;
2987 PatchSizeProgress += rFTP.PatcheSizes[j];
2988 currentPatchedSize += rFTP.PatcheSizes[j];
2991 if (tmpSourceName != DestinationName)
2993 pPM->deleteFile(SourceName, false, false); // File can exists if bad BNP loading
2994 if (!_CommitPatch)
2996 // let the patch in the unpack directory
2997 pPM->renameFile(tmpSourceName, pPM->ClientPatchPath + rFTP.FileName + ".tmp");
2999 else
3001 pPM->renameFile(tmpSourceName, DestinationName);
3005 else
3007 PatchSizeProgress += totalPatchSize;
3010 // If all patches applied with success so file size should be ok
3011 // We just have to change file date to match the last patch applied
3012 pPM->applyDate(DestinationName, rFTP.LastFileDate);
3013 //progress.progress(1.f);
3016 // ****************************************************************************
3017 bool CPatchThread::xDeltaPatch(const string &patch, const string &src, const string &out)
3019 // Internal xdelta
3021 CPatchManager::MyPatchingCB patchingCB;
3023 patchingCB.patchFilename = patch;
3024 patchingCB.srcFilename = src;
3026 CPatchManager *pPM = CPatchManager::getInstance();
3027 pPM->deleteFile(out, false, false);
3029 std::string errorMsg;
3030 CXDeltaPatch::TApplyResult ar = CXDeltaPatch::apply(patch, src, out, errorMsg, &patchingCB);
3031 if (ar != CXDeltaPatch::ApplyResult_Ok)
3033 switch(ar)
3035 case CXDeltaPatch::ApplyResult_WriteError:
3036 throw NLMISC::EWriteError(out);
3037 break;
3038 case CXDeltaPatch::ApplyResult_DiskFull:
3039 throw NLMISC::EDiskFullError(out);
3040 break;
3041 default:
3043 nlinfo("Error applying %s to %s giving %s", patch.c_str(), src.c_str(), out.c_str());
3044 return false;
3046 break;
3048 } else
3049 return true;
3052 // Launching xdelta
3054 // Start the child process.
3055 string strCmdLine = "xdelta patch " + patch + " " + src + " " + out;
3060 // ****************************************************************************
3061 // ****************************************************************************
3062 // ****************************************************************************
3063 // CScanDataThread
3064 // ****************************************************************************
3065 // ****************************************************************************
3066 // ****************************************************************************
3068 // ****************************************************************************
3069 CScanDataThread::CScanDataThread()
3071 AskForCancel= false;
3072 Ended = false;
3073 CheckOk = false;
3074 TotalFileToScan = 1;
3075 CurrentFileScanned = 1;
3078 // ****************************************************************************
3079 void CScanDataThread::run ()
3081 CPatchManager *pPM = CPatchManager::getInstance();
3084 uint32 i;
3085 // Check if the client version is the same as the server version
3086 string sClientVersion = pPM->getClientVersion();
3087 string sTranslate = CI18N::get("uiClientVersion") + " (" + sClientVersion + ") ";
3088 pPM->setState(true, sTranslate);
3090 // For all bnp in the description file get all patches to apply
3091 // depending on the version of the client bnp files
3092 const CBNPFileSet &rDescFiles = pPM->DescFile.getFiles();
3093 TotalFileToScan = rDescFiles.fileCount();
3094 for (i = 0; i < rDescFiles.fileCount(); ++i)
3096 sTranslate = CI18N::get("uiCheckingFile") + " " + rDescFiles.getFile(i).getFileName();
3097 pPM->setState(true, sTranslate);
3099 // get list of file to apply to this patch, performing a full checksum test (slow...)
3100 CPatchManager::SFileToPatch ftp;
3101 pPM->getPatchFromDesc(ftp, rDescFiles.getFile(i), false);
3102 // if the file has been found but don't correspond to any local version (SHA1)
3103 // or if the file has not been found (or the .bnp is very very buggy so that addSearchFile() failed)
3104 if( (ftp.LocalFileExists && ftp.LocalFileToDelete) ||
3105 (!ftp.LocalFileExists && !ftp.LocalFileToDelete) )
3107 pPM->addDataScanLogCorruptedFile(ftp);
3108 CPatchManager::getCorruptedFileInfo(ftp, sTranslate);
3109 pPM->setState(true, sTranslate);
3111 CurrentFileScanned = i;
3113 // if the user ask to cancel the thread, stop now
3114 if(AskForCancel)
3115 break;
3118 sTranslate = CI18N::get("uiCheckEndNoErr");
3119 pPM->setState(true, sTranslate);
3120 CheckOk = true;
3121 Ended = true;
3123 catch (const Exception &e)
3125 nlwarning("EXCEPTION CATCH: CScanDataThread::run() failed");
3126 string sTranslate = CI18N::get("uiCheckEndWithErr") + " " + e.what();
3127 pPM->setState(true, sTranslate);
3128 CheckOk = false;
3129 Ended = true;
3134 // ****************************************************************************
3135 uint32 CPatchManager::SPatchInfo::getAvailablePatchsBitfield() const
3137 // About the test (until a patch enum is added, we use the 'optional' flag)
3138 // Non optional -> must patch it (will be for RoS)
3139 // Optional -> Will be for Mainland
3140 // Required : stands for 'bnp' required by the Optional bnps !! so ignore only RoS is wanted
3142 uint32 result = 0;
3143 if (!NonOptCat.empty())
3145 result |= (1 << BGDownloader::DownloadID_RoS);
3147 if (!OptCat.empty() || !ReqCat.empty())
3149 result |= (1 << BGDownloader::DownloadID_MainLand);
3151 return result;
3154 // ***************************************************************************
3155 void CPatchManager::setAsyncDownloader(IAsyncDownloader* asyncDownloader)
3157 _AsyncDownloader = asyncDownloader;
3159 // ***************************************************************************
3160 IAsyncDownloader* CPatchManager::getAsyncDownloader() const
3162 return _AsyncDownloader;
3164 // **************************************************************************
3165 // ****************************************************************************
3166 void CPatchManager::startInstallThread(const std::vector<CInstallThreadEntry>& entries)
3168 InstallThread = new CInstallThread(entries);
3169 Thread = IThread::create (InstallThread);
3170 nlassert (Thread != NULL);
3171 Thread->start ();
3174 void CPatchManager::startDownloadThread(const std::vector<CInstallThreadEntry>& entries)
3176 DownloadThread = new CDownloadThread(entries);
3177 Thread = IThread::create (DownloadThread);
3178 nlassert (Thread != NULL);
3179 Thread->start ();
3183 void CPatchManager::onFileInstallFinished()
3185 if (_AsyncDownloader)
3187 _AsyncDownloader->onFileInstallFinished();
3192 void CPatchManager::onFileDownloadFinished()
3194 if (_AsyncDownloader)
3196 _AsyncDownloader->onFileDownloadFinished();
3201 void CPatchManager::onFileDownloading(const std::string& sourceName, uint32 rate, uint32 fileIndex, uint32 fileCount, uint64 fileSize, uint64 fullSize)
3203 if (_AsyncDownloader)
3205 _AsyncDownloader->onFileDownloading(sourceName, rate, fileIndex, fileCount, fileSize, fullSize);
3209 void CPatchManager::onFileInstalling(const std::string& sourceName, uint32 rate, uint32 fileIndex, uint32 fileCount, uint64 fileSize, uint64 fullSize)
3211 if (_AsyncDownloader)
3213 _AsyncDownloader->onFileInstalling(sourceName, rate, fileIndex, fileCount, fileSize, fullSize);
3217 bool CPatchManager::download(const std::string& patchFullname, const std::string& sourceFullname,
3218 const std::string& tmpDirectory, uint32 timestamp)
3220 CPatchManager *pPM = CPatchManager::getInstance();
3222 static std::string zsStr = ".lzma";
3223 static std::string::size_type zsStrLength = zsStr.size();
3225 // we delete file if present and diferent timestamp
3226 // if the file is the same then indicates its not necessary to download it a second time
3227 if (NLMISC::CFile::fileExists(sourceFullname))
3229 uint32 t = NLMISC::CFile::getFileModificationDate(sourceFullname);
3230 if (t == timestamp)
3231 { //we didn't downl oad
3232 return true;
3234 pPM->deleteFile(sourceFullname);
3236 // file will be save to a .tmp file
3237 std::string extension;
3238 if (patchFullname.size() >= zsStrLength && patchFullname.substr(patchFullname.size() - zsStrLength) == zsStr)
3240 extension = zsStr;
3242 // remove tmp file if exist
3243 std::string patchName = tmpDirectory + NLMISC::CFile::getFilename(sourceFullname) + extension + std::string(".tmp");
3244 if (NLMISC::CFile::fileExists(patchName))
3246 pPM->deleteFile(patchName);
3248 // creates directory tree if necessary
3249 NLMISC::CFile::createDirectoryTree( NLMISC::CFile::getPath(patchName) );
3250 NLMISC::CFile::createDirectoryTree( NLMISC::CFile::getPath(sourceFullname) );
3252 // try to download
3255 pPM->getServerFile(patchFullname, false, patchName);
3257 catch ( const std::exception& e)
3259 nlwarning("%s", e.what());
3260 pPM->setState(true, string(e.what()) );
3261 return false;
3264 // install
3265 if (!NLMISC::CFile::fileExists(patchName))
3267 return false;
3269 // if file is compressed unpack then commit (rename)
3270 if (patchName.size() >= zsStrLength
3271 && patchName.substr(patchName.size() - zsStrLength) == zsStr)
3273 std::string outFilename = patchName.substr(0, patchName.size() - zsStrLength);
3274 unpack7Zip(patchName, outFilename);
3275 pPM->deleteFile(patchName);
3276 pPM->renameFile(outFilename, sourceFullname);
3278 else
3280 // file is not compressed so rename the .tmp file to final name
3281 pPM->renameFile(patchName, sourceFullname);
3283 // apply modification date
3284 pPM->applyDate(sourceFullname, timestamp);
3286 return true;
3290 bool CPatchManager::extract(const std::string& patchPath,
3291 const std::vector<std::string>& sourceFilename,
3292 const std::vector<std::string>& extractPath,
3293 const std::string& updateBatchFilename,
3294 const std::string& execName,
3295 void (*stopFun)() )
3297 nlassert(sourceFilename.size() == extractPath.size());
3298 CPatchManager *pPM = CPatchManager::getInstance();
3300 bool ok = false;
3301 for (uint32 j = 0; j < extractPath.size() && !ok; ++j)
3303 if (!extractPath[j].empty())
3305 ok = true;
3309 if (!ok)
3311 // nothing to extract
3312 return false;
3315 // extract
3316 uint nblab = 0;
3317 pPM->deleteFile(updateBatchFilename, false, false);
3319 FILE *fp = nlfopen (updateBatchFilename, "wt");
3321 if (fp == 0)
3323 string err = toString("Can't open file '%s' for writing: code=%d %s (error code 29)", updateBatchFilename.c_str(), errno, strerror(errno));
3324 throw Exception (err);
3327 #ifdef NL_OS_WINDOWS
3328 fprintf(fp, "@echo off\n");
3329 fprintf(fp, "ping 127.0.0.1 -n 7 -w 1000 > nul\n"); // wait
3330 #else
3331 fprintf(fp, "#!/bin/sh\n");
3332 fprintf(fp, "sleep 7\n"); // wait
3333 #endif
3335 // Unpack files with category ExtractPath non empty
3336 for (uint32 j = 0; j < sourceFilename.size(); ++j)
3338 if (!extractPath[j].empty())
3340 string rFilename = sourceFilename[j];
3341 // Extract to patch
3342 vector<string> vFilenames;
3343 if (!pPM->bnpUnpack(rFilename, patchPath, vFilenames))
3346 string err = toString("Error unpacking %s", rFilename.c_str());
3347 throw Exception (err);
3350 else
3352 std::string sourcePath = NLMISC::CFile::getPath(sourceFilename[j]);
3353 for (uint32 fff = 0; fff < vFilenames.size (); fff++)
3355 string SrcPath = CPath::standardizeDosPath(sourcePath);
3356 string SrcName = SrcPath + vFilenames[fff];
3357 string DstPath = CPath::standardizeDosPath(extractPath[j]);
3358 string DstName = DstPath + vFilenames[fff];
3359 NLMISC::CFile::createDirectoryTree(extractPath[j]);
3361 // this file must be moved
3362 #ifdef NL_OS_WINDOWS
3363 fprintf(fp, ":loop%u\n", nblab);
3364 fprintf(fp, "attrib -r -a -s -h %s\n", DstName.c_str());
3365 fprintf(fp, "del %s\n", DstName.c_str());
3366 fprintf(fp, "if exist %s goto loop%u\n", DstName.c_str(), nblab);
3367 fprintf(fp, "move %s %s\n", SrcName.c_str(), DstPath.c_str());
3368 #else
3369 // TODO: for Linux and OS X
3370 #endif
3372 nblab++;
3380 #ifdef NL_OS_WINDOWS
3381 fprintf(fp, "start %s %%1 %%2 %%3\n", execName.c_str());
3382 #else
3383 // TODO: for Linux and OS X
3384 #endif
3386 fclose(fp);
3388 if (stopFun)
3390 stopFun();
3393 if (!launchProgram(updateBatchFilename, "", false))
3395 // error occurs during the launch
3396 string str = toString("Can't execute '%s': code=%d %s (error code 30)", updateBatchFilename.c_str(), errno, strerror(errno));
3397 throw Exception (str);
3400 return true;
3404 void CPatchManager::setStateListener(IPatchManagerStateListener* stateListener)
3406 _StateListener = stateListener;
3410 void CPatchManager::fatalError(const std::string& errorId, const std::string& param1, const std::string& param2)
3412 if (_AsyncDownloader)
3414 _AsyncDownloader->fatalError(errorId, param1, param2);
3419 // ****************************************************************************
3420 void CDownloadThread::run()
3422 CPatchManager *pPM = CPatchManager::getInstance();
3424 std::string patchPath = CPath::standardizePath (ClientRootPath+TheTmpInstallDirectory)+"patch/";
3427 static bool _FirstTime = true;
3428 static uint64 _FullSize = 0;
3429 static uint64 _CurrentSize = 0;
3430 static uint32 _Start;
3432 // At first launch calculat the amount of data need to download
3433 if (_FirstTime)
3435 for (uint first = 0,last = (uint)_Entries.size() ; first != last; ++first)
3437 _FullSize += _Entries[first].SZipFileSize;
3439 _Start = NLMISC::CTime::getSecondsSince1970();
3440 _FirstTime = false;
3444 for (uint first = 0,last = (uint)_Entries.size() ; first != last; ++first)
3446 std::string patchName = CPath::standardizePath (_Entries[first].PatchName, false);
3447 std::string sourceName = CPath::standardizePath (_Entries[first].SourceName, false);
3449 _CurrentSize += _Entries[first].SZipFileSize;
3451 uint32 rate = 0;
3452 // Calculate the time since last update
3453 uint32 dt = NLMISC::CTime::getSecondsSince1970() - _Start;
3454 if (dt)
3456 rate = uint32(_CurrentSize / dt);
3458 // update Gui
3459 pPM->onFileDownloading(sourceName, rate, first+1, last, _CurrentSize, _FullSize);
3461 std::string finalFile =patchPath+ patchName;
3462 std::string tmpFile = finalFile + std::string(".part");
3465 bool toDownload = true;
3466 static volatile bool simulateDownload = false;
3467 if (simulateDownload)
3469 nlSleep(100);
3471 else
3473 // Do not download if file are identicaly (check size and modification date)
3474 if (NLMISC::CFile::fileExists(finalFile))
3476 uint64 fz = NLMISC::CFile::getFileSize(finalFile);
3477 uint32 timestamp = _Entries[first].Timestamp;
3478 uint32 timestamp2 = NLMISC::CFile::getFileModificationDate(finalFile);
3479 if ( fz == _Entries[first].SZipFileSize && timestamp == timestamp2)
3481 toDownload = false;
3483 else
3485 NLMISC::CFile::deleteFile(finalFile);
3489 // file need to be download
3490 if (toDownload)
3492 // delete tmp file if exist
3493 if (NLMISC::CFile::fileExists(tmpFile))
3495 NLMISC::CFile::deleteFile(tmpFile);
3497 std::string path = NLMISC::CFile::getPath(tmpFile);
3498 // create directory tree
3499 NLMISC::CFile::createDirectoryTree(path);
3500 // Try to download, rename, applyDate and send error msg to gui in case of error
3503 pPM->getServerFile(patchName, false, tmpFile);
3504 NLMISC::CFile::moveFile(finalFile, tmpFile);
3506 pPM->applyDate(finalFile, _Entries[first].Timestamp);
3508 catch ( const std::exception& e)
3510 nlwarning("%s", e.what());
3511 pPM->setState(true, string(e.what()) );
3512 pPM->fatalError("uiCanNotDownload", patchName.c_str(), "");
3514 catch (...)
3516 pPM->fatalError("uiCanNotDownload", patchName.c_str(), "");
3522 // message to gui to indicates that all file are downloaded
3523 pPM->onFileDownloadFinished();
3527 void CInstallThread::run()
3529 std::string patchPath = CPath::standardizePath (ClientRootPath+TheTmpInstallDirectory)+"patch/";
3530 CPatchManager *pPM = CPatchManager::getInstance();
3532 std::set<std::string> allowed;
3534 uint first, last;
3536 for (first = 0,last = (uint)_Entries.size() ; first != last; ++first)
3538 std::string correct = CPath::standardizePath (patchPath + _Entries[first].PatchName, false);
3539 allowed.insert(correct);
3541 // Delete file from tmp directory that are not "allowed" (torrent protocol can download partial file that are not asked)
3542 std::vector<std::string> vFiles;
3543 CPath::getPathContent(patchPath , true, false, true, vFiles);
3544 for (uint32 i = 0; i < vFiles.size(); ++i)
3546 std::string sName2 = CPath::standardizePath (vFiles[i], false);
3547 if (allowed.find(sName2) == allowed.end() )
3549 pPM->deleteFile(sName2 , false, false);
3553 static bool _FirstTime = true;
3554 static uint64 _FullSize = 0;
3555 static uint64 _CurrentSize = 0;
3556 static uint32 _Start;
3558 // calculate size of data to download in order to know the install speed
3559 if (_FirstTime)
3561 for (uint first = 0,last = (uint)_Entries.size() ; first != last; ++first)
3563 _FullSize += _Entries[first].Size;
3565 _Start = NLMISC::CTime::getSecondsSince1970();
3566 _FirstTime = false;
3570 for (first = 0,last = (uint)_Entries.size() ; first != last; ++first)
3572 std::string patchName = CPath::standardizePath (patchPath +_Entries[first].PatchName, false);
3573 std::string sourceName = CPath::standardizePath (_Entries[first].SourceName, false);
3574 uint32 lastFileDate = _Entries[first].Timestamp;
3577 _CurrentSize += _Entries[first].Size;
3578 uint32 rate = 0;
3579 // calcule the install speed
3580 uint32 dt = NLMISC::CTime::getSecondsSince1970() - _Start;
3581 if (dt)
3583 uint64 size = _CurrentSize / dt;
3584 rate = uint32 (size);
3587 // File can exists if bad BNP loading
3588 if (NLMISC::CFile::fileExists(sourceName))
3590 pPM->deleteFile(sourceName, false, false);
3593 if (NLMISC::CFile::fileExists(patchName))
3595 static std::string zsStr = ".lzma";
3596 static std::string::size_type zsStrLength = zsStr.size();
3599 // if file is compressed unpack the file (decompress, rename tempory file, apply modification date)
3600 if (patchName.size() >= zsStrLength
3601 && patchName.substr(patchName.size() - zsStrLength) == zsStr)
3603 std::string outFilename = patchName.substr(0, patchName.size() - zsStrLength);
3604 std::string localOutFilename = CPath::standardizeDosPath(outFilename);
3606 if ( unpackLZMA(patchName, localOutFilename) )
3608 pPM->deleteFile(patchName);
3609 pPM->renameFile(outFilename, sourceName);
3610 pPM->applyDate(sourceName, lastFileDate);
3612 else
3614 throw NLMISC::Exception("Can not unpack");
3618 else
3620 // if file is uncompressed rename tempory file and apply modification date)
3621 pPM->renameFile(patchName, sourceName);
3622 pPM->applyDate(sourceName, lastFileDate);
3625 catch ( const std::exception& e)
3627 nlwarning("%s", e.what());
3628 pPM->setState(true, string(e.what()) );
3629 pPM->fatalError("uiCanNotInstall", patchName.c_str(), "");
3630 return;
3633 catch (...)
3635 pPM->fatalError("uiCanNotInstall", patchName.c_str(), "");
3636 return;
3639 // update gui
3640 pPM->onFileInstalling(sourceName, rate, first+1, last, _CurrentSize, _FullSize);
3643 // extract bnp
3646 // remove date from tmp directory (because install is finished)
3647 std::string install = CPath::standardizePath (ClientRootPath+TheTmpInstallDirectory);
3649 std::vector<std::string> vFiles;
3650 // Delete all classic file from tmp directory
3651 CPath::getPathContent(install, true, false, true, vFiles);
3652 for (uint32 i = 0; i < vFiles.size(); ++i)
3654 NLMISC::CFile::deleteFile(vFiles[i]);
3656 // Delete all directory from tmp directory
3659 vFiles.clear();
3660 CPath::getPathContent(install, true, true, false, vFiles);
3661 for (uint32 i = 0; i < vFiles.size(); ++i)
3663 NLMISC::CFile::deleteDirectory(vFiles[i]);
3666 while ( !vFiles.empty() );
3667 // delete tmp directory
3668 NLMISC::CFile::deleteDirectory(install);
3669 // delete libtorrent_logs directory if exist (not activate)
3670 if (NLMISC::CFile::fileExists("libtorrent_logs"))
3674 vFiles.clear();
3675 CPath::getPathContent("libtorrent_logs", true, true, false, vFiles);
3676 for (uint32 i = 0; i < vFiles.size(); ++i)
3678 NLMISC::CFile::deleteDirectory(vFiles[i]);
3681 while ( !vFiles.empty() );
3682 NLMISC::CFile::deleteDirectory("libtorrent_logs");
3687 pPM->reboot(); // do not reboot just run the extract .bat