Linux multi-monitor fullscreen support
[ryzomcore.git] / ryzom / tools / patch_gen / patch_gen_common.cpp
blob799804c84ebaa72e1fba8bcd15c483bebc3d03bb
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2011-2013 Matt RAYKOWSKI (sfb) <matt.raykowski@gmail.com>
6 // Copyright (C) 2014 Matthew LAGOE (Botanic) <cyberempires@gmail.com>
7 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
8 //
9 // This program is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU Affero General Public License as
11 // published by the Free Software Foundation, either version 3 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU Affero General Public License for more details.
19 // You should have received a copy of the GNU Affero General Public License
20 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 //-----------------------------------------------------------------------------
23 // includes
24 //-----------------------------------------------------------------------------
26 #include <cstdio>
27 #include <limits>
29 #include "game_share/bnp_patch.h"
30 #include "nel/misc/path.h"
31 #include "nel/misc/file.h"
32 #include "nel/misc/command.h"
33 #include "nel/misc/sstring.h"
34 #include "nel/misc/seven_zip.h"
35 #include "game_share/singleton_registry.h"
37 using namespace std;
38 using namespace NLMISC;
40 #define PERSISTENT_TOKEN_FAMILY RyzomTokenFamily
43 //-----------------------------------------------------------------------------
44 // Handy utility functions
45 //-----------------------------------------------------------------------------
47 void normalisePackageDescriptionFileName(std::string& fileName)
49 if (fileName.empty())
50 fileName="package_description";
51 if (NLMISC::CFile::getExtension(fileName).empty() && fileName[fileName.size()-1]!='.')
52 fileName+=".xml";
55 void GeneratePatch(const std::string& srcFileName,const std::string& destFileName,const std::string& patchFileName)
57 std::string cmd = toString("xdelta delta %s %s %s", srcFileName.c_str(), destFileName.c_str(), patchFileName.c_str());
59 nlinfo("Executing system command: %s", cmd.c_str());
61 #ifdef NL_OS_WINDOWS
62 _spawnlp(_P_WAIT, "xdelta.exe", "xdelta.exe", "delta", srcFileName.c_str(), destFileName.c_str(), patchFileName.c_str(), NULL);
63 #else // NL_OS_WINDOWS
64 // xdelta-1.x behaves like "diff" and returns 0 for identical files, 1 for different files, 2 for errors
65 sint error = system (cmd.c_str());
67 if (error == 2)
68 nlwarning("'%s' failed with error code %d", cmd.c_str(), error);
69 #endif // NL_OS_WINDOWS
72 void ApplyPatch(const std::string& srcFileName,const std::string& destFileName,const std::string& patchFileName=std::string())
74 std::string cmd = toString("xdelta patch %s %s %s", patchFileName.c_str(), srcFileName.c_str(), destFileName.c_str());
76 nlinfo("Executing system command: %s", cmd.c_str());
78 #ifdef NL_OS_WINDOWS
79 _spawnlp(_P_WAIT, "xdelta.exe", "xdelta.exe", "patch",patchFileName.c_str(), srcFileName.c_str(), destFileName.c_str(), NULL);
80 #else // NL_OS_WINDOWS
81 // xdelta-1.x behaves like "diff" and returns 0 for identical files, 1 for different files, 2 for errors
82 sint error = system (cmd.c_str());
84 if (error == 2)
85 nlwarning("'%s' failed with error code %d", cmd.c_str(), error);
86 #endif // NL_OS_WINDOWS
89 void GenerateLZMA(const std::string &sourceFile, const std::string &outputFile)
92 nlinfo("Compressing %s to %s using LZMA...", sourceFile.c_str(), outputFile.c_str());
95 if (!packLZMA(sourceFile, outputFile))
97 nlwarning("LZMA compress failed");
102 //-----------------------------------------------------------------------------
103 // class CPackageDescription
104 //-----------------------------------------------------------------------------
106 class CPackageDescription: public IVersionNumberGenerator
108 private:
109 DECLARE_PERSISTENCE_METHODS
111 public:
112 CPackageDescription();
114 void clear();
116 void setup(const std::string& packageName);
117 void storeToPdr(CPersistentDataRecord& pdr) const;
119 void readIndex(CBNPFileSet& packageIndex) const;
120 void writeIndex(const CBNPFileSet& packageIndex) const;
122 void getCategories(CPersistentDataRecord &pdr) const;
124 void updateIndexFileList(CBNPFileSet& packageIndex) const;
125 void generateClientIndex(CProductDescriptionForClient& theClientPackage,const CBNPFileSet& packageIndex) const;
126 void addVersion(CBNPFileSet& packageIndex);
127 void generatePatches(CBNPFileSet& packageIndex) const;
128 void createDirectories() const;
129 void buildDefaultFileList();
131 void updatePatchSizes(CBNPFileSet& packageIndex) const;
133 // specialisation of IVersionNumberGenerator
134 void grabVersionNumber();
135 uint32 getPackageVersionNumber();
137 private:
138 CBNPCategorySet _Categories;
139 std::string _IndexFileName;
140 std::string _ClientIndexFileName;
141 std::string _RootDirectory;
142 std::string _PatchDirectory;
143 std::string _BnpDirectory;
144 std::string _RefDirectory;
145 std::string _NextVersionFile;
147 uint32 _NextVersionNumber;
148 bool _VersionNumberReserved;
152 //-----------------------------------------------------------------------------
153 // methods CPackageDescription
154 //-----------------------------------------------------------------------------
156 CPackageDescription::CPackageDescription()
158 clear();
161 void CPackageDescription::clear()
163 _NextVersionNumber= std::numeric_limits<uint32>::max();
164 _VersionNumberReserved = false;
165 _Categories.clear();
166 _IndexFileName.clear();
167 _ClientIndexFileName.clear();
168 _PatchDirectory.clear();
169 _BnpDirectory.clear();
170 _RefDirectory.clear();
173 void CPackageDescription::setup(const std::string& packageName)
175 nlinfo("Reading package description: %s ...",packageName.c_str());
177 // clear out old contents before reading from input file
178 clear();
180 // read new contents from input file
181 static CPersistentDataRecord pdr;
182 pdr.clear();
183 pdr.readFromTxtFile(packageName);
184 apply(pdr);
186 // root directory
187 if (_RootDirectory.empty())
188 _RootDirectory = NLMISC::CFile::getPath(packageName);
189 _RootDirectory = NLMISC::CPath::standardizePath(_RootDirectory, true);
191 // patch directory
192 if (_PatchDirectory.empty())
193 _PatchDirectory = _RootDirectory + "patch";
194 _PatchDirectory = NLMISC::CPath::standardizePath(_PatchDirectory, true);
196 // BNP directory
197 if (_BnpDirectory.empty())
198 _BnpDirectory = _RootDirectory+"bnp";
199 _BnpDirectory = NLMISC::CPath::standardizePath(_BnpDirectory, true);
201 // ref directory
202 if (_RefDirectory.empty())
203 _RefDirectory = _RootDirectory+"ref";
204 _RefDirectory = NLMISC::CPath::standardizePath(_RefDirectory, true);
206 // client index file
207 if (_ClientIndexFileName.empty())
208 _ClientIndexFileName = NLMISC::CFile::getFilenameWithoutExtension(packageName)+".idx";
210 // index file
211 if (_IndexFileName.empty())
212 _IndexFileName = NLMISC::CFile::getFilenameWithoutExtension(_ClientIndexFileName)+".hist";
215 void CPackageDescription::storeToPdr(CPersistentDataRecord& pdr) const
217 pdr.clear();
218 store(pdr);
221 void CPackageDescription::readIndex(CBNPFileSet& packageIndex) const
223 std::string indexPath = _RootDirectory + _IndexFileName;
225 nlinfo("Reading history file: %s ...", indexPath.c_str());
227 // clear out old contents before reading from input file
228 packageIndex.clear();
230 // read new contents from input file
231 if (NLMISC::CFile::fileExists(indexPath))
233 static CPersistentDataRecord pdr;
234 pdr.clear();
235 pdr.readFromTxtFile(indexPath);
236 packageIndex.apply(pdr);
240 void CPackageDescription::writeIndex(const CBNPFileSet& packageIndex) const
242 std::string indexPath = _RootDirectory + _IndexFileName;
244 nlinfo("Writing history file: %s ...", indexPath.c_str());
246 // write contents to output file
247 static CPersistentDataRecordRyzomStore pdr;
248 pdr.clear();
249 packageIndex.store(pdr);
250 pdr.writeToTxtFile(indexPath);
253 void CPackageDescription::getCategories(CPersistentDataRecord &pdr) const
255 pdr.clear();
256 _Categories.store(pdr);
259 void CPackageDescription::updateIndexFileList(CBNPFileSet& packageIndex) const
261 nlinfo("Updating file list from package categories (%d files) ...",_Categories.fileCount());
262 for (uint32 i=_Categories.fileCount();i--;)
264 const std::string& fileName= _Categories.getFile(i);
265 packageIndex.addFile(fileName,_Categories.isFileIncremental(fileName));
267 // if the file is flagged as non-incremental then we need to add its refference file too
268 if (!_Categories.isFileIncremental(fileName))
270 std::string refName= NLMISC::CFile::getFilenameWithoutExtension(_Categories.getFile(i))+"_.ref";
271 packageIndex.addFile(refName);
273 // if the ref file doesn't exist then create it by copying the original
274 if (NLMISC::CFile::fileExists(_BnpDirectory+fileName) && !NLMISC::CFile::fileExists(_BnpDirectory+refName))
276 NLMISC::CFile::copyFile(_BnpDirectory+refName,_BnpDirectory+fileName);
277 nlassert(NLMISC::CFile::getFileSize(_BnpDirectory+refName)== NLMISC::CFile::getFileSize(_BnpDirectory+fileName));
283 void CPackageDescription::generateClientIndex(CProductDescriptionForClient& theClientPackage, const CBNPFileSet& packageIndex) const
285 std::string patchNumber = toString("%05u", packageIndex.getVersionNumber());
286 std::string patchDirectory = _PatchDirectory + patchNumber;
287 std::string patchFile = patchDirectory + "/" + _ClientIndexFileName;
289 nlinfo("Generating client index: %s...", patchFile.c_str());
291 // make sure the version sub directory exist
292 CFile::createDirectory(patchDirectory);
294 // clear out the client package before we start
295 theClientPackage.clear();
297 // copy the categories using a pdr record
298 static CPersistentDataRecordRyzomStore pdr;
299 pdr.clear();
300 _Categories.store(pdr);
301 theClientPackage.setCategories(pdr);
303 // copy the files using a pdr record
304 pdr.clear();
305 packageIndex.store(pdr);
306 theClientPackage.setFiles(pdr);
308 // create the output file
309 pdr.clear();
310 theClientPackage.store(pdr);
312 std::string newName = patchDirectory + "/" + NLMISC::CFile::getFilenameWithoutExtension(_ClientIndexFileName) + "_" + patchNumber;
314 pdr.writeToBinFile(newName + ".idx");
315 pdr.writeToTxtFile(newName + "_debug.xml");
318 void CPackageDescription::addVersion(CBNPFileSet& packageIndex)
320 // calculate the last version number in the index file
321 // nlinfo("Calculating package version number...");
322 // uint32 versionNumber= packageIndex.getVersionNumber();
323 // nlinfo("Last version number = %d",versionNumber);
324 // uint32 newVersionNumber= packageIndex.addVersion(_BnpDirectory,versionNumber+1);
325 // nlinfo("New version number = %d",newVersionNumber);
327 // setup the default next version number by scanning the package index for the highest existing version number
328 nlinfo("Calculating package version number...");
329 _NextVersionNumber= packageIndex.getVersionNumber();
330 nlinfo("Last version number = %u",_NextVersionNumber);
331 ++_NextVersionNumber;
333 // have the package index check its file list to see if a new version is required
334 uint32 newVersionNumber= packageIndex.addVersion(_BnpDirectory,_RefDirectory,*this);
335 nlinfo("Added files for version: %u",newVersionNumber);
338 void CPackageDescription::generatePatches(CBNPFileSet& packageIndex) const
340 nlinfo("Generating patches ...");
342 for (uint32 i = packageIndex.fileCount(); i--;)
344 bool deleteRefAfterDelta = true;
345 bool usingTemporaryFile = false;
346 // generate file name root
347 std::string bnpFileName = _BnpDirectory + packageIndex.getFile(i).getFileName();
348 std::string refNameRoot = _RefDirectory + NLMISC::CFile::getFilenameWithoutExtension(bnpFileName);
349 std::string patchNameRoot = _PatchDirectory + NLMISC::CFile::getFilenameWithoutExtension(bnpFileName);
351 // if the file has no versions then skip on to the next file
352 if (packageIndex.getFile(i).versionCount()==0)
353 continue;
355 // get the last version number and the related file name
356 const CBNPFileVersion& curVersion= packageIndex.getFile(i).getVersion(packageIndex.getFile(i).versionCount()-1);
357 std::string curVersionFileName= refNameRoot+NLMISC::toString("_%05u.%s",curVersion.getVersionNumber(),NLMISC::CFile::getExtension(bnpFileName).c_str());
358 // std::string patchFileName= patchNameRoot+NLMISC::toString("_%05d.patch",curVersion.getVersionNumber());
359 std::string patchFileName= _PatchDirectory + toString("%05u/",curVersion.getVersionNumber())+NLMISC::CFile::getFilenameWithoutExtension(bnpFileName)+toString("_%05u",curVersion.getVersionNumber())+".patch";
361 // get the second last version number and the related file name
362 std::string prevVersionFileName;
363 if (packageIndex.getFile(i).versionCount()==1)
365 prevVersionFileName= _RootDirectory + "empty";
366 CFile::createEmptyFile(prevVersionFileName);
367 usingTemporaryFile = true;
368 deleteRefAfterDelta= false;
370 else
372 const CBNPFileVersion& prevVersion= packageIndex.getFile(i).getVersion(packageIndex.getFile(i).versionCount()-2);
373 prevVersionFileName= refNameRoot+NLMISC::toString("_%05u.%s",prevVersion.getVersionNumber(),NLMISC::CFile::getExtension(bnpFileName).c_str());
375 std::string refVersionFileName= prevVersionFileName;
377 // create the subdirectory for this patch number
378 string versionSubDir = _PatchDirectory + toString("%05u/", curVersion.getVersionNumber());
379 CFile::createDirectory(versionSubDir);
381 // generate the lzma packed version of the bnp if needed (lzma file are slow to generate)
382 string lzmaFile = versionSubDir+CFile::getFilename(bnpFileName)+".lzma";
383 if (!CFile::fileExists(lzmaFile))
385 // build the lzma compression in a temp file (avoid leaving dirty file if the
386 // process cannot terminate)
387 GenerateLZMA(bnpFileName, lzmaFile+".tmp");
388 // rename the tmp file
389 CFile::moveFile(lzmaFile, lzmaFile+".tmp");
392 // store the lzma file size in the descriptor
393 packageIndex.getFile(i).getVersion(packageIndex.getFile(i).versionCount()-1).set7ZipFileSize(CFile::getFileSize(lzmaFile));
395 // if we need to generate a new patch then do it and create the new ref file
396 if (!NLMISC::CFile::fileExists(curVersionFileName))
398 nlinfo("- Creating patch: %s",patchFileName.c_str());
400 // in the case where we compress against a ref file...
401 if (!_Categories.isFileIncremental(NLMISC::CFile::getFilename(bnpFileName)))
403 // setup the name of the reference file to patch against
404 refVersionFileName= _BnpDirectory+NLMISC::CFile::getFilenameWithoutExtension(bnpFileName)+"_.ref";
406 // delete the previous patch - because we only need the latest patch for non-incremental files
407 std::string lastPatch= _PatchDirectory + NLMISC::CFile::getFilenameWithoutExtension(prevVersionFileName)+".patch";
408 if (NLMISC::CFile::fileExists(lastPatch.c_str()))
409 NLMISC::CFile::deleteFile(lastPatch.c_str());
412 // call xdelta to generate the patch
413 GeneratePatch(refVersionFileName, bnpFileName, patchFileName);
414 nlassert(NLMISC::CFile::fileExists(patchFileName));
416 uint32 nPatchSize = NLMISC::CFile::getFileSize(patchFileName);
417 packageIndex.getFile(i).getVersion(packageIndex.getFile(i).versionCount()-1).setPatchSize(nPatchSize);
419 // apply the incremental patch to the old ref file to create the new ref file
420 // and ensure that the new ref file matches the BNP
421 ApplyPatch(refVersionFileName, curVersionFileName, patchFileName);
422 nlassert(NLMISC::CFile::fileExists(curVersionFileName));
423 nlassert(NLMISC::CFile::thoroughFileCompare(bnpFileName, curVersionFileName));
426 // if we have a ref file still hanging about from the previous patch then delete it
427 if (NLMISC::CFile::fileExists(prevVersionFileName))
429 NLMISC::CFile::deleteFile(prevVersionFileName);
434 void CPackageDescription::createDirectories() const
436 NLMISC::CFile::createDirectoryTree(_RootDirectory);
437 NLMISC::CFile::createDirectoryTree(_PatchDirectory);
438 NLMISC::CFile::createDirectoryTree(_BnpDirectory);
439 NLMISC::CFile::createDirectoryTree(_RefDirectory);
442 void CPackageDescription::buildDefaultFileList()
444 // make sure the default categories exist
445 CBNPCategory* packedCategory= _Categories.getCategory("main", true);
446 packedCategory->setOptional(false);
447 packedCategory->setIncremental(true);
449 CBNPCategory* unpackedCategory= _Categories.getCategory("unpacked", true);
450 unpackedCategory->setUnpackTo("./");
451 unpackedCategory->setOptional(false);
452 unpackedCategory->setIncremental(false);
454 CBNPCategory* optionCategory= _Categories.getCategory("optional", true);
455 optionCategory->setOptional(true);
456 optionCategory->setIncremental(true);
458 // look for BNP files in the BNP directry and add them to the main category
459 std::vector<std::string> fileList;
460 NLMISC::CPath::getPathContent(_BnpDirectory,false,false,true,fileList);
461 for (uint32 i=0;i<fileList.size();++i)
462 if (NLMISC::toLowerAscii(NLMISC::CFile::getExtension(fileList[i]))=="bnp"
463 || NLMISC::toLowerAscii(NLMISC::CFile::getExtension(fileList[i]))=="snp")
464 _Categories.addFile("main",NLMISC::toLowerAscii(NLMISC::CFile::getFilename(fileList[i])));
466 _Categories.addFile("unpacked","root.bnp");
469 void CPackageDescription::updatePatchSizes(CBNPFileSet& packageIndex) const
473 void CPackageDescription::grabVersionNumber()
475 // if we've already grabbed the next version number then just return
476 if (_VersionNumberReserved)
477 return;
479 // if we don't have a version file to deal with then we're done
480 if (_NextVersionFile.empty())
481 return;
483 // read the version number from the '_NextVersion' file
484 nlassert(NLMISC::CFile::fileExists(_NextVersionFile));
485 NLMISC::CSString fileContents;
486 fileContents.readFromFile(_NextVersionFile);
487 uint32 versionFromFile= fileContents.atoui();
488 nlinfo("Version number read from file (%s) = %u",_NextVersionFile.c_str(),versionFromFile);
490 // select the higher of the 2 version numbers
491 _NextVersionNumber= std::max(_NextVersionNumber,versionFromFile);
492 nlinfo("New version number = %u",_NextVersionNumber);
494 // write the result +1 back to the '_NextVersion' file
495 (NLMISC::CSString()<<(_NextVersionNumber+1)).writeToFile(_NextVersionFile);
496 fileContents.readFromFile(_NextVersionFile);
497 versionFromFile= fileContents.atoui();
498 nlassert( versionFromFile == (_NextVersionNumber+1) );
500 // success so flag the version number as reserved
501 _VersionNumberReserved= true;
504 uint32 CPackageDescription::getPackageVersionNumber()
506 return _NextVersionNumber;
510 //-----------------------------------------------------------------------------
511 // Persistent data for CPackageDescription
512 //-----------------------------------------------------------------------------
514 #define PERSISTENT_CLASS CPackageDescription
515 #define PERSISTENT_DATA\
516 STRUCT(_Categories)\
517 PROP(std::string,_IndexFileName)\
518 PROP(std::string,_PatchDirectory)\
519 PROP(std::string,_BnpDirectory)\
520 PROP(std::string,_RefDirectory)\
521 PROP(std::string,_NextVersionFile)\
523 //#pragma message( PERSISTENT_GENERATION_MESSAGE )
524 #include "game_share/persistent_data_template.h"
526 #undef PERSISTENT_CLASS
527 #undef PERSISTENT_DATA
530 //-----------------------------------------------------------------------------
531 // work routines
532 //-----------------------------------------------------------------------------
534 static bool createNewProduct(std::string fileName)
536 // normalise the file name (and path)
537 normalisePackageDescriptionFileName(fileName);
539 // make sure the file doesn't exist
540 BOMB_IF(NLMISC::CFile::fileExists(fileName),("Failed to careate new package because file already exists: "+fileName).c_str(),return false);
542 // create the directory tree required for the file
543 NLMISC::CFile::createDirectoryTree(NLMISC::CFile::getPath(fileName));
545 // create a new package, store it to a persistent data record and write the latter to a file
546 CPackageDescription package;
547 static CPersistentDataRecordRyzomStore pdr;
548 pdr.clear();
549 package.storeToPdr(pdr);
550 pdr.writeToTxtFile(fileName);
551 package.setup(fileName);
552 package.createDirectories();
553 package.buildDefaultFileList();
554 package.storeToPdr(pdr);
555 pdr.writeToTxtFile(fileName);
557 BOMB_IF(!NLMISC::CFile::fileExists(fileName),("Failed to create new package file: "+fileName).c_str(),return false);
558 nlinfo("New package description file created successfully: %s", fileName.c_str());
560 return true;
563 static bool updateProduct(std::string fileName)
565 // normalise the file name (and path)
566 normalisePackageDescriptionFileName(fileName);
568 // make sure the file exists
569 BOMB_IF(!NLMISC::CFile::fileExists(fileName),("Failed to process package because file not found: "+fileName).c_str(),return false);
571 // read the package description file
572 CPackageDescription thePackage;
573 thePackage.setup(fileName);
575 // read the index file for the package
576 CBNPFileSet packageIndex;
577 thePackage.readIndex(packageIndex);
579 // update the files list in the index
580 thePackage.updateIndexFileList(packageIndex);
582 // update the index for the package
583 thePackage.addVersion(packageIndex);
585 // save the updated index file
586 thePackage.writeIndex(packageIndex);
588 // generate patches as required
589 thePackage.generatePatches(packageIndex);
591 // add patch sizes to index file
592 thePackage.updatePatchSizes(packageIndex);
594 // save the updated index file
595 thePackage.writeIndex(packageIndex);
597 // generate client index file
598 CProductDescriptionForClient theClientPackage;
599 thePackage.generateClientIndex(theClientPackage,packageIndex);
601 return true;
605 //-----------------------------------------------------------------------------
606 // commands
607 //-----------------------------------------------------------------------------
609 NLMISC_COMMAND(createNewProduct,"create a new package description file","<package description file name>")
611 if (args.size()!=1)
612 return false;
614 createNewProduct(args[0]);
616 return true;
619 NLMISC_COMMAND(updateProduct,"process a package","<package description file name>")
621 if (args.size()!=1)
622 return false;
624 updateProduct(args[0]);
626 return true;
629 NLMISC_COMMAND(go,"perform a 'createNewProduct' if required and 'updateProduct' on patch_test/test0/test_package.xml","")
631 if (args.size()!=0)
632 return false;
634 if (!NLMISC::CFile::fileExists("patch_test/test0/test_package.xml"))
635 createNewProduct("patch_test/test0/test_package.xml");
636 updateProduct("patch_test/test0/test_package.xml");
638 return true;