!I (1670414, 1670415, 1670416, 1670424, 1670431):
[CRYENGINE.git] / Code / Tools / CryCommonTools / ZipDir / ZipDirCacheRW.cpp
blobb4765fa21c8323beb759113c22699c7fc5800b02
1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
3 #include "StdAfx.h"
4 #include <CryCore/smartptr.h>
5 #include "Util.h"
6 #include "ZipFileFormat.h"
7 #include "zipdirstructures.h"
8 #include "ZipDirTree.h"
9 #include "ZipDirList.h"
10 #include "ZipDirCache.h"
11 #include "ZipDirCacheRW.h"
12 #include "ZipDirCacheFactory.h"
13 #include "ZipDirFindRW.h"
15 #include "ThreadUtils.h"
17 #include <zlib.h> // declaration of Z_OK for ZipRawDecompress
19 using namespace ZipFile;
21 enum PackFileStatus
23 PACKFILE_COMPRESSED,
25 PACKFILE_ADDED,
26 PACKFILE_UPTODATE,
27 PACKFILE_SKIPPED,
28 PACKFILE_MISSING,
29 PACKFILE_FAILED
32 class PackFilePool;
33 struct PackFileBatch
35 PackFilePool* pool;
37 int zipMaxSize;
38 int sourceMinSize;
39 int sourceMaxSize;
40 int compressionMethod;
41 int compressionLevel;
43 PackFileBatch()
44 : pool(0)
45 , sourceMinSize(0)
46 , sourceMaxSize(0)
47 , zipMaxSize(0)
48 , compressionMethod(0)
49 , compressionLevel(0)
54 class PackFilePool;
55 struct PackFileJob
57 int index;
58 int key;
59 PackFileBatch* batch;
60 const char* relativePathSrc;
61 const char* realFilename;
63 unsigned int existingCRC;
65 void* compressedData;
66 unsigned long compressedSize;
67 unsigned long compressedSizePreviously;
69 void* uncompressedData;
70 unsigned long uncompressedSize;
71 unsigned long uncompressedSizePreviously;
73 __int64 modTime;
74 ZipDir::ErrorEnum zdError;
75 PackFileStatus status;
77 PackFileJob()
78 : index(0)
79 , key(0)
80 , batch(0)
81 , realFilename(0)
82 , relativePathSrc(0)
83 , existingCRC(0)
84 , compressedData(0)
85 , compressedSize(0)
86 , compressedSizePreviously(0)
87 , uncompressedData(0)
88 , uncompressedSize(0)
89 , uncompressedSizePreviously(0)
90 , modTime(0)
91 , zdError(ZipDir::ZD_ERROR_NOT_IMPLEMENTED)
92 , status(PACKFILE_FAILED)
96 void DetachUncompressedData()
98 if (uncompressedData && uncompressedData == compressedData)
100 compressedData = 0;
101 compressedSize = 0;
104 uncompressedData = 0;
105 uncompressedSize = 0;
108 ~PackFileJob()
110 if (compressedData && compressedData != uncompressedData)
112 free(compressedData);
113 compressedData = 0;
116 if (uncompressedData)
118 free(uncompressedData);
119 uncompressedData = 0;
125 // ---------------------------------------------------------------------------
127 static void PackFileFromDisc(PackFileJob* job);
129 class PackFilePool
131 public:
132 PackFilePool(int numFiles, size_t memoryLimit)
133 : m_pool(false)
134 , m_skip(false)
135 , m_awaitedFile(0)
136 , m_memoryLimit(memoryLimit)
137 , m_allocatedMemory(0)
139 m_files.reserve(numFiles);
142 ~PackFilePool()
146 void Submit(int key, const PackFileJob& job)
148 PackFileJob* newJob = new PackFileJob(job);
150 // index in queue, and custom key for identification
151 newJob->index = int(m_files.size());
152 newJob->key = key;
154 m_files.push_back(newJob);
157 PackFileJob* WaitForFile(int index)
159 while(true)
162 ThreadUtils::AutoLock lock(m_filesLock);
163 m_awaitedFile = index;
164 if (size_t(index) >= m_files.size())
165 return 0;
166 if (m_files[index])
167 return m_files[index];
169 Sleep(0);
172 assert(0);
173 return 0;
176 void Start(int numThreads)
178 for (size_t i = 0; i < m_files.size(); ++i)
180 PackFileJob* job = m_files[i];
181 m_files[i] = 0;
182 m_pool.Submit(&ProcessFile, job);
185 m_pool.Start(numThreads);
188 size_t GetJobCount() const
190 return m_files.size();
193 void SkipPendingFiles()
195 m_skip = true;
198 void ReleaseFile(int index)
200 assert(m_files[index] != 0);
201 if (m_files[index])
203 if (m_memoryLimit != 0)
205 ThreadUtils::AutoLock lock(m_filesLock);
207 m_allocatedMemory -= m_files[index]->uncompressedSize;
208 m_allocatedMemory -= m_files[index]->compressedSize;
211 delete m_files[index];
212 m_files[index] = 0;
216 private:
218 // called from non-main thread
219 static void ProcessFile(PackFileJob* job)
221 PackFilePool* self = job->batch->pool;
223 if (!self->m_skip)
225 if (self->m_memoryLimit != 0)
227 while (true)
229 size_t allocatedMemory = 0;
230 int awaitedFile = 0;
232 ThreadUtils::AutoLock lock(self->m_filesLock);
233 allocatedMemory = self->m_allocatedMemory;
234 awaitedFile = self->m_awaitedFile;
237 if (allocatedMemory > self->m_memoryLimit && job->index > awaitedFile + 1)
238 Sleep(10); // give time to main thread to write data to file
239 else
240 break;
244 PackFileFromDisc(job);
247 self->FileCompleted(job);
250 // called from non-main thread
251 void FileCompleted(PackFileJob* job)
253 ThreadUtils::AutoLock lock(m_filesLock);
255 assert(job);
256 assert(job->index < m_files.size());
257 assert(m_files[job->index] == 0);
258 m_files[job->index] = job;
260 if (m_memoryLimit != 0)
262 m_allocatedMemory += job->uncompressedSize;
263 m_allocatedMemory += job->compressedSize;
267 size_t m_memoryLimit;
269 ThreadUtils::CriticalSection m_filesLock;
270 std::vector<PackFileJob*> m_files;
271 int m_awaitedFile;
272 size_t m_allocatedMemory;
273 bool m_skip;
275 ThreadUtils::SimpleThreadPool m_pool;
278 //////////////////////////////////////////////////////////////////////////
279 static size_t AlignTo(size_t offset, size_t alignment)
281 const size_t remainder = offset % alignment;
282 return remainder ? offset + alignment - remainder : offset;
284 //////////////////////////////////////////////////////////////////////////
285 // Calculates new offset of the header to make sure that following data are
286 // aligned properly
287 static size_t CalculateAlignedHeaderOffset(const char* fileName, size_t currentOffset, size_t alignment)
289 // Since file should start from header
290 if (currentOffset == 0)
292 return 0;
295 // Local header is followed by filename
296 const size_t totalHeaderSize = sizeof(LocalFileHeader) + strlen(fileName);
298 // Align end of the header
299 const size_t dataOffset = AlignTo(currentOffset + totalHeaderSize, alignment);
301 return dataOffset - totalHeaderSize;
304 //////////////////////////////////////////////////////////////////////////
305 ZipDir::CacheRW::CacheRW(bool encryptHeaders, const EncryptionKey& encryptionKey)
306 : m_pFile (NULL)
307 , m_nFlags (0)
308 , m_lCDROffset (0)
309 , m_fileAlignment (1)
310 , m_bEncryptedHeaders(encryptHeaders)
311 , m_bHeadersEncryptedOnClose(encryptHeaders)
312 , m_encryptionKey(encryptionKey)
315 m_nRefCount = 0;
317 //////////////////////////////////////////////////////////////////////////
318 ZipDir::CacheRW::~CacheRW()
320 Close();
322 //////////////////////////////////////////////////////////////////////////
323 void ZipDir::CacheRW::AddRef()
325 ++m_nRefCount;
328 //////////////////////////////////////////////////////////////////////////
329 void ZipDir::CacheRW::Release()
331 if (--m_nRefCount <= 0)
332 delete this;
335 void ZipDir::CacheRW::Close()
337 if (m_pFile)
339 if (!(m_nFlags & FLAGS_READ_ONLY))
341 if ((m_nFlags & FLAGS_UNCOMPACTED) && !(m_nFlags&FLAGS_DONT_COMPACT))
343 if (!RelinkZip())
344 WriteCDR();
346 else
347 if (m_nFlags & FLAGS_CDR_DIRTY)
348 WriteCDR();
351 if(m_pFile) // RelinkZip() might have closed the file
352 fclose (m_pFile);
354 m_pFile = NULL;
356 m_treeDir.Clear();
359 //////////////////////////////////////////////////////////////////////////
360 char* ZipDir::CacheRW::UnifyPath( char *const str, const char *pPath )
362 assert(str);
363 const char *src = pPath;
364 char *trg = str;
365 while (*src)
367 if (*src != '/')
369 *trg++ = ::tolower(*src++);
371 else
373 *trg++ = '\\';
374 src++;
377 *trg = 0;
378 return str;
381 //////////////////////////////////////////////////////////////////////////
382 char* ZipDir::CacheRW::ToUnixPath( char *const str, const char *pPath )
384 assert(str);
385 const char *src = pPath;
386 char *trg = str;
387 while (*src)
389 if (*src != '/')
391 *trg++ = *src++;
393 else
395 *trg++ = '\\';
396 src++;
399 *trg = 0;
400 return str;
403 //////////////////////////////////////////////////////////////////////////
404 char* ZipDir::CacheRW::AllocPath( const char *pPath )
406 char str[_MAX_PATH];
407 char *temp = ToUnixPath(str, pPath);
408 temp = m_tempStringPool.Append( temp,strlen(temp) );
409 return temp;
412 static void PackFileFromMemory(PackFileJob* job)
414 if (job->existingCRC != 0)
416 unsigned int crcCode = (unsigned int)crc32(0, (unsigned char*)job->uncompressedData, job->uncompressedSize);
417 if (crcCode == job->existingCRC)
419 job->compressedData = 0;
420 job->compressedSize = 0;
421 job->status = PACKFILE_UPTODATE;
422 job->zdError = ZipDir::ZD_ERROR_SUCCESS;
423 // This file with same data already in pak, skip it.
424 return;
428 switch (job->batch->compressionMethod)
430 case METHOD_DEFLATE_AND_ENCRYPT:
431 case METHOD_DEFLATE:
433 // allocate memory for compression. Min is nSize * 1.001 + 12
434 if (job->uncompressedSize > 0)
436 job->compressedSize = job->uncompressedSize + (job->uncompressedSize >> 3) + 32;
437 job->compressedData = malloc(job->compressedSize);
438 int error = ZipDir::ZipRawCompress(job->uncompressedData, &job->compressedSize, job->compressedData, job->uncompressedSize, job->batch->compressionLevel);
439 if (error == Z_OK)
441 job->status = PACKFILE_COMPRESSED;
442 job->zdError = ZipDir::ZD_ERROR_SUCCESS;
444 else
446 job->status = PACKFILE_FAILED;
447 job->zdError = ZipDir::ZD_ERROR_ZLIB_FAILED;
450 else
452 job->status = PACKFILE_COMPRESSED;
453 job->zdError = ZipDir::ZD_ERROR_SUCCESS;
455 job->compressedSize = 0;
456 job->compressedData = 0;
458 break;
460 case METHOD_STORE:
461 job->compressedData = job->uncompressedData;
462 job->compressedSize = job->uncompressedSize;
463 job->status = PACKFILE_COMPRESSED;
464 job->zdError = ZipDir::ZD_ERROR_SUCCESS;
465 break;
467 default:
468 job->status = PACKFILE_FAILED;
469 job->zdError = ZipDir::ZD_ERROR_UNSUPPORTED;
470 break;
474 bool ZipDir::CacheRW::WriteCompressedData(const char* data, size_t size, bool encrypt, FILE* file)
476 if (size == 0)
478 return true;
481 std::vector<char> buffer;
482 if (encrypt)
484 buffer.resize(size);
485 memcpy(&buffer[0], data, size);
486 ZipDir::Encrypt(&buffer[0], size, m_encryptionKey);
487 data = &buffer[0];
490 // Danny - writing a single large chunk (more than 6MB?) causes
491 // Windows fwrite to (silently?!) fail. So we're writing data
492 // in small chunks.
493 while (size > 0)
495 const size_t sizeToWrite = Util::getMin(size, size_t(1024 * 1024));
496 if (fwrite(data, sizeToWrite, 1, file) != 1)
498 return false;
500 data += sizeToWrite;
501 size -= sizeToWrite;
504 return true;
507 static bool WriteRandomData(FILE* file, size_t size)
509 if (size == 0)
511 return true;
514 const size_t bufferSize = Util::getMin(size, size_t(1024 * 1024));
515 std::vector<char> buffer(bufferSize);
517 while (size > 0)
519 const size_t sizeToWrite = Util::getMin(size, bufferSize);
521 for (size_t i = 0; i < sizeToWrite; ++i)
523 buffer[i] = rand() & 0xff;
526 if (fwrite(&buffer[0], sizeToWrite, 1, file) != 1)
528 return false;
531 size -= sizeToWrite;
534 return true;
537 bool ZipDir::CacheRW::WriteNullData(size_t size)
539 if (size == 0)
541 return true;
544 const size_t bufferSize = Util::getMin(size, size_t(1024 * 1024));
545 std::vector<char> buffer(bufferSize, 0);
547 while (size > 0)
549 const size_t sizeToWrite = Util::getMin(size, bufferSize);
551 if (fwrite(&buffer[0], sizeToWrite, 1, m_pFile) != 1)
553 return false;
556 size -= sizeToWrite;
559 return true;
562 void ZipDir::CacheRW::StorePackedFile(PackFileJob* job)
564 if (job->batch->zipMaxSize > 0 && GetTotalFileSize() > job->batch->zipMaxSize)
566 job->status = PACKFILE_SKIPPED;
567 job->zdError = ZipDir::ZD_ERROR_SUCCESS;
568 return;
571 job->status = PACKFILE_FAILED;
573 char str[_MAX_PATH];
574 char* relativePath = UnifyPath(str, job->relativePathSrc);
576 // create or find the file entry.. this object will rollback (delete the object
577 // if the operation fails) if needed.
578 FileEntryTransactionAdd pFileEntry(this, AllocPath(job->relativePathSrc), AllocPath(relativePath));
580 if (!pFileEntry)
582 job->zdError = ZipDir::ZD_ERROR_INVALID_PATH;
583 return;
586 pFileEntry->OnNewFileData(job->uncompressedData, job->uncompressedSize,
587 job->compressedSize, job->batch->compressionMethod, false);
588 pFileEntry->SetFromFileTimeNTFS(job->modTime);
590 // since we changed the time, we'll have to update CDR
591 m_nFlags |= FLAGS_CDR_DIRTY;
593 // the new CDR position, if the operation completes successfully
594 unsigned lNewCDROffset = m_lCDROffset;
596 if (pFileEntry->IsInitialized())
598 // this file entry is already allocated in CDR
600 // check if the new compressed data fits into the old place
601 unsigned nFreeSpace = pFileEntry->nEOFOffset - pFileEntry->nFileHeaderOffset - (unsigned)sizeof(ZipFile::LocalFileHeader) - (unsigned)strlen(relativePath);
603 if (nFreeSpace != job->compressedSize)
604 m_nFlags |= FLAGS_UNCOMPACTED;
606 if (nFreeSpace >= job->compressedSize)
608 // and we can just override the compressed data in the file
609 ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, job->relativePathSrc, m_bEncryptedHeaders);
610 if (e != ZipDir::ZD_ERROR_SUCCESS)
612 job->zdError = e;
613 return;
616 else
618 // we need to write the file anew - in place of current CDR
619 pFileEntry->nFileHeaderOffset = CalculateAlignedHeaderOffset(job->relativePathSrc, m_lCDROffset, m_fileAlignment);
620 ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, job->relativePathSrc, m_bEncryptedHeaders);
621 lNewCDROffset = pFileEntry->nEOFOffset;
622 if (e != ZipDir::ZD_ERROR_SUCCESS)
624 job->zdError = e;
625 return;
629 else
631 pFileEntry->nFileHeaderOffset = CalculateAlignedHeaderOffset(job->relativePathSrc, m_lCDROffset, m_fileAlignment);
632 ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, job->relativePathSrc, m_bEncryptedHeaders);
633 if (e != ZipDir::ZD_ERROR_SUCCESS)
635 job->zdError = e;
636 return;
639 lNewCDROffset = pFileEntry->nFileDataOffset + job->compressedSize;
641 m_nFlags |= FLAGS_CDR_DIRTY;
644 // now we have the fresh local header and data offset
646 #if CRY_PLATFORM_WINDOWS
647 if (_fseeki64 (m_pFile, (__int64)pFileEntry->nFileDataOffset, SEEK_SET)!=0)
648 #else
649 if (fseek (m_pFile, pFileEntry->nFileDataOffset, SEEK_SET)!=0)
650 #endif
652 job->zdError = ZD_ERROR_IO_FAILED;
653 return;
656 const bool encrypt = pFileEntry->nMethod == METHOD_DEFLATE_AND_ENCRYPT;
658 if (!WriteCompressedData((char*)job->compressedData, job->compressedSize, encrypt, m_pFile))
660 job->zdError = ZD_ERROR_IO_FAILED;
661 return;
664 // since we wrote the file successfully, update the new CDR position
665 m_lCDROffset = lNewCDROffset;
666 pFileEntry.Commit();
668 job->status = PACKFILE_ADDED;
669 job->zdError = ZD_ERROR_SUCCESS;
672 // Adds a new file to the zip or update an existing one
673 // adds a directory (creates several nested directories if needed)
674 ZipDir::ErrorEnum ZipDir::CacheRW::UpdateFile (const char* szRelativePathSrc, void* pUncompressed, unsigned nSize,
675 unsigned nCompressionMethod, int nCompressionLevel, __time64_t modTime)
677 char str[_MAX_PATH];
678 char *szRelativePath = UnifyPath(str, szRelativePathSrc);
681 PackFileBatch batch;
682 batch.compressionMethod = nCompressionMethod;
683 batch.compressionLevel = nCompressionLevel;
685 PackFileJob job;
686 job.relativePathSrc = szRelativePathSrc;
687 job.modTime = modTime;
688 job.uncompressedData = pUncompressed;
689 job.uncompressedSize = nSize;
690 job.batch = &batch;
692 // crc will be used to check if this file need to be updated at all
693 ZipDir::FileEntry* entry = FindFile(szRelativePath);
694 if (entry)
695 job.existingCRC = entry->desc.lCRC32;
697 PackFileFromMemory(&job);
699 switch (job.status)
701 case PACKFILE_SKIPPED :
702 case PACKFILE_MISSING :
703 case PACKFILE_FAILED :
704 return ZD_ERROR_IO_FAILED;
707 StorePackedFile(&job);
708 job.DetachUncompressedData();
709 return job.zdError;
712 static FILETIME GetFileWriteTimeAndSize( uint64* fileSize, const char *filename )
714 // Warning: FindFirstFile on NTFS may report file size that
715 // is not up-to-date with the actual file content.
716 // http://blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx
718 FILETIME fileTime;
720 WIN32_FIND_DATAA FindFileData;
721 HANDLE hFind = FindFirstFileA( filename, &FindFileData );
723 if (hFind == INVALID_HANDLE_VALUE)
725 fileTime.dwLowDateTime = 0;
726 fileTime.dwHighDateTime = 0;
727 if (fileSize)
729 *fileSize = 0;
732 else
734 fileTime.dwLowDateTime = FindFileData.ftLastWriteTime.dwLowDateTime;
735 fileTime.dwHighDateTime = FindFileData.ftLastWriteTime.dwHighDateTime;
736 if (fileSize)
738 *fileSize = (uint64(FindFileData.nFileSizeHigh) << 32) + FindFileData.nFileSizeLow;
740 FindClose(hFind);
743 return fileTime;
746 static void PackFileFromDisc(PackFileJob* job)
748 const FILETIME ft = GetFileWriteTimeAndSize(0, job->realFilename);
749 LARGE_INTEGER lt;
750 lt.HighPart = ft.dwHighDateTime;
751 lt.LowPart = ft.dwLowDateTime;
752 job->modTime = lt.QuadPart;
754 FILE *f = fopen(job->realFilename, "rb");
755 if (!f)
757 job->status = PACKFILE_FAILED;
758 job->zdError = ZipDir::ZD_ERROR_FILE_NOT_FOUND;
759 return;
762 fseek(f, 0, SEEK_END);
763 size_t fileSize = (size_t)ftell(f);
765 if (fileSize < job->batch->sourceMinSize || (job->batch->sourceMaxSize > 0 && fileSize > job->batch->sourceMaxSize))
767 fclose(f);
769 job->status = PACKFILE_SKIPPED;
770 job->zdError = ZipDir::ZD_ERROR_SUCCESS;
771 return;
774 job->uncompressedData = malloc(fileSize);
776 fseek(f, 0, SEEK_SET);
777 if (fread(job->uncompressedData, 1, fileSize, f) != fileSize)
779 free(job->uncompressedData);
780 job->uncompressedData = 0;
781 fclose(f);
783 job->status = PACKFILE_FAILED;
784 job->zdError = ZipDir::ZD_ERROR_IO_FAILED;
785 return;
787 fclose(f);
788 job->uncompressedSize = fileSize;
790 PackFileFromMemory(job);
793 bool ZipDir::CacheRW::UpdateMultipleFiles(const char** realFilenames, const char** filenamesInZip, size_t fileCount,
794 int compressionLevel, bool encryptContent, size_t zipMaxSize, int sourceMinSize, int sourceMaxSize,
795 int numExtraThreads, ZipDir::IReporter* reporter, ZipDir::ISplitter* splitter)
797 int compressionMethod = METHOD_DEFLATE;
798 if (encryptContent)
799 compressionMethod = METHOD_DEFLATE_AND_ENCRYPT;
800 else if (compressionLevel == 0)
801 compressionMethod = METHOD_STORE;
803 uint64 totalSize = 0;
805 clock_t startTime = clock();
807 PackFileBatch batch;
808 batch.compressionLevel = compressionLevel;
809 batch.compressionMethod = compressionMethod;
810 batch.pool = 0;
811 batch.sourceMinSize = sourceMinSize;
812 batch.sourceMaxSize = sourceMaxSize;
813 batch.zipMaxSize = zipMaxSize;
815 if (numExtraThreads == 0)
817 for (int i = 0; i < fileCount; ++i)
819 const char* realFilename = realFilenames[i];
820 const char* filenameInZip = filenamesInZip[i];
822 PackFileJob job;
824 job.key = i;
825 job.relativePathSrc = filenameInZip;
826 job.realFilename = realFilename;
827 job.batch = &batch;
830 // crc will be used to check if this file need to be updated at all
831 ZipDir::FileEntry* entry = FindFile(filenameInZip);
832 if (entry)
834 uint64 fileSize = 0;
836 const FILETIME ft = GetFileWriteTimeAndSize(&fileSize, realFilename);
837 LARGE_INTEGER lt;
839 lt.HighPart = ft.dwHighDateTime;
840 lt.LowPart = ft.dwLowDateTime;
841 job.modTime = lt.QuadPart;
842 job.existingCRC = entry->desc.lCRC32;
843 job.compressedSizePreviously = entry->desc.lSizeCompressed;
844 job.uncompressedSizePreviously = entry->desc.lSizeUncompressed;
846 // Check if file with the same name, timestamp and size already exists in pak.
847 if (entry->CompareFileTimeNTFS(job.modTime) && fileSize == entry->desc.lSizeUncompressed)
849 if (reporter)
850 reporter->ReportUpToDate(filenameInZip);
851 continue;
856 PackFileFromDisc(&job);
858 if (job.status == PACKFILE_COMPRESSED)
860 if (splitter != NULL)
862 size_t dsk = GetTotalFileSizeOnDiskSoFar();
863 size_t bse = 0;
864 size_t add = 0;
865 size_t sub = 0;
867 bse += sizeof(ZipFile::CDRFileHeader) + strlen(job.relativePathSrc);
868 bse += sizeof(ZipFile::LocalFileHeader) + strlen(job.relativePathSrc);
870 if (job.compressedSize)
871 add += bse + job.compressedSize;
872 if (job.compressedSizePreviously)
873 sub += bse + job.compressedSizePreviously;
875 if (splitter->CheckWriteLimit(dsk, add, sub))
877 splitter->SetLastFile(dsk, add, sub, job.key - 1);
878 break;
882 StorePackedFile(&job);
885 switch (job.status)
887 case PACKFILE_ADDED:
888 if (reporter)
889 reporter->ReportAdded(filenameInZip);
891 totalSize += job.uncompressedSize;
892 break;
893 case PACKFILE_MISSING:
894 if (reporter)
895 reporter->ReportMissing(realFilename);
896 break;
897 case PACKFILE_UPTODATE:
898 if (reporter)
899 reporter->ReportUpToDate(realFilename);
900 break;
901 case PACKFILE_SKIPPED:
902 if (reporter)
903 reporter->ReportSkipped(realFilename);
904 break;
905 default:
906 if (reporter)
907 reporter->ReportFailed(realFilename, ""); // TODO reason
908 continue;
913 else
915 const size_t memoryLimit = 1024 * 1024 * 1024; // prevents threads from generating more than 1GB of data
916 PackFilePool pool(fileCount, memoryLimit);
917 batch.pool = &pool;
919 for (int i = 0; i < fileCount; ++i)
921 const char* realFilename = realFilenames[i];
922 const char* filenameInZip = filenamesInZip[i];
924 PackFileJob job;
926 job.relativePathSrc = filenameInZip;
927 job.realFilename = realFilename;
928 job.batch = &batch;
931 // crc will be used to check if this file need to be updated at all
932 ZipDir::FileEntry* entry = FindFile(filenameInZip);
933 if (entry)
935 uint64 fileSize = 0;
936 const FILETIME ft = GetFileWriteTimeAndSize(&fileSize, realFilename);
937 LARGE_INTEGER lt;
938 lt.HighPart = ft.dwHighDateTime;
939 lt.LowPart = ft.dwLowDateTime;
940 job.modTime = lt.QuadPart;
941 job.existingCRC = entry->desc.lCRC32;
942 job.compressedSizePreviously = entry->desc.lSizeCompressed;
943 job.uncompressedSizePreviously = entry->desc.lSizeUncompressed;
945 // Check if file with the same name, timestamp and size already exists in pak.
946 if (entry->CompareFileTimeNTFS(job.modTime) && fileSize == entry->desc.lSizeUncompressed)
948 if (reporter)
949 reporter->ReportUpToDate(filenameInZip);
950 continue;
955 pool.Submit(i, job);
958 // Get the number of submitted jobs, which is at most
959 // as large as the largest successfully submitted file-index.
960 // Any number of files can be skipped for submittion.
961 const int jobCount = pool.GetJobCount();
962 if (jobCount == 0)
964 return true;
967 pool.Start(numExtraThreads);
969 for (int i = 0; i < jobCount; ++i)
971 PackFileJob* job = pool.WaitForFile(i);
972 if (!job)
974 assert(job);
975 continue;
978 if (job->status == PACKFILE_COMPRESSED)
980 if (splitter != NULL)
982 size_t dsk = GetTotalFileSizeOnDiskSoFar();
983 size_t bse = 0;
984 size_t add = 0;
985 size_t sub = 0;
987 bse += sizeof(ZipFile::CDRFileHeader) + strlen(job->relativePathSrc);
988 bse += sizeof(ZipFile::LocalFileHeader) + strlen(job->relativePathSrc);
990 if (job->compressedSize)
991 add += bse + job->compressedSize;
992 if (job->compressedSizePreviously)
993 sub += bse + job->compressedSizePreviously;
995 if (splitter->CheckWriteLimit(dsk, add, sub))
997 splitter->SetLastFile(dsk, add, sub, job->key - 1);
999 // deplete the pool before leaving the loop
1000 pool.SkipPendingFiles();
1001 for (; i < jobCount; ++i)
1003 pool.WaitForFile(i);
1004 pool.ReleaseFile(i);
1007 break;
1011 StorePackedFile(job);
1014 switch (job->status)
1016 case PACKFILE_ADDED:
1017 if (reporter)
1018 reporter->ReportAdded(job->relativePathSrc);
1020 totalSize += job->uncompressedSize;
1021 break;
1022 case PACKFILE_MISSING:
1023 if (reporter)
1024 reporter->ReportMissing(job->realFilename);
1025 break;
1026 case PACKFILE_UPTODATE:
1027 if (reporter)
1028 reporter->ReportUpToDate(job->realFilename);
1029 break;
1030 case PACKFILE_SKIPPED:
1031 if (reporter)
1032 reporter->ReportSkipped(job->realFilename);
1033 break;
1034 default:
1035 if (reporter)
1036 reporter->ReportFailed(job->realFilename, ""); // TODO reason
1037 continue;
1040 pool.ReleaseFile(i);
1044 clock_t endTime = clock();
1045 double timeSeconds = double(endTime - startTime) / CLOCKS_PER_SEC;
1046 double speed = (endTime - startTime) == 0 ? 0.0 : double(totalSize) / timeSeconds;
1048 if (reporter)
1049 reporter->ReportSpeed(speed);
1051 return true;
1055 // Adds a new file to the zip or update an existing one if it is not compressed - just stored - start a big file
1056 ZipDir::ErrorEnum ZipDir::CacheRW::StartContinuousFileUpdate( const char* szRelativePathSrc, unsigned nSize )
1058 char str[_MAX_PATH];
1059 char *szRelativePath = UnifyPath(str, szRelativePathSrc);
1061 SmartPtr pBufferDestroyer;
1063 // create or find the file entry.. this object will rollback (delete the object
1064 // if the operation fails) if needed.
1065 FileEntryTransactionAdd pFileEntry(this, AllocPath(szRelativePathSrc), AllocPath(szRelativePath));
1067 if (!pFileEntry)
1068 return ZD_ERROR_INVALID_PATH;
1070 pFileEntry->OnNewFileData (NULL, nSize, nSize, METHOD_STORE, false );
1071 // since we changed the time, we'll have to update CDR
1072 m_nFlags |= FLAGS_CDR_DIRTY;
1074 // the new CDR position, if the operation completes successfully
1075 unsigned lNewCDROffset = m_lCDROffset;
1076 if (pFileEntry->IsInitialized())
1078 // check if the new compressed data fits into the old place
1079 unsigned nFreeSpace = pFileEntry->nEOFOffset - pFileEntry->nFileHeaderOffset - (unsigned)sizeof(ZipFile::LocalFileHeader) - (unsigned)strlen(szRelativePath);
1081 if (nFreeSpace != nSize)
1082 m_nFlags |= FLAGS_UNCOMPACTED;
1084 if (nFreeSpace >= nSize)
1086 // and we can just override the compressed data in the file
1087 ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, szRelativePathSrc, m_bEncryptedHeaders);
1088 if (e != ZD_ERROR_SUCCESS)
1089 return e;
1091 else
1093 // we need to write the file anew - in place of current CDR
1094 pFileEntry->nFileHeaderOffset = CalculateAlignedHeaderOffset(szRelativePathSrc, m_lCDROffset, m_fileAlignment);
1095 ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, szRelativePathSrc, m_bEncryptedHeaders);
1096 lNewCDROffset = pFileEntry->nEOFOffset;
1097 if (e != ZD_ERROR_SUCCESS)
1098 return e;
1101 else
1103 pFileEntry->nFileHeaderOffset = CalculateAlignedHeaderOffset(szRelativePathSrc, m_lCDROffset, m_fileAlignment);
1104 ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, szRelativePathSrc, m_bEncryptedHeaders);
1105 if (e != ZD_ERROR_SUCCESS)
1106 return e;
1108 lNewCDROffset = pFileEntry->nFileDataOffset + nSize;
1110 m_nFlags |= FLAGS_CDR_DIRTY;
1113 #if CRY_PLATFORM_WINDOWS
1114 if (_fseeki64 (m_pFile, (__int64)pFileEntry->nFileDataOffset, SEEK_SET)!=0)
1115 #else
1116 if (fseek (m_pFile, pFileEntry->nFileDataOffset, SEEK_SET)!=0)
1117 #endif
1119 return ZD_ERROR_IO_FAILED;
1122 if (!WriteNullData(nSize))
1124 return ZD_ERROR_IO_FAILED;
1127 pFileEntry->nEOFOffset = pFileEntry->nFileDataOffset;
1129 // since we wrote the file successfully, update the new CDR position
1130 m_lCDROffset = lNewCDROffset;
1131 pFileEntry.Commit();
1133 return ZD_ERROR_SUCCESS;
1136 // Adds a new file to the zip or update an existing's segment if it is not compressed - just stored
1137 // adds a directory (creates several nested directories if needed)
1138 ZipDir::ErrorEnum ZipDir::CacheRW::UpdateFileContinuousSegment (const char* szRelativePathSrc, unsigned nSize, void* pUncompressed, unsigned nSegmentSize, unsigned nOverwriteSeekPos )
1140 char str[_MAX_PATH];
1141 char *szRelativePath = UnifyPath(str, szRelativePathSrc);
1143 SmartPtr pBufferDestroyer;
1145 // create or find the file entry.. this object will rollback (delete the object
1146 // if the operation fails) if needed.
1147 FileEntryTransactionAdd pFileEntry(this, AllocPath(szRelativePathSrc), AllocPath(szRelativePath));
1149 if (!pFileEntry)
1150 return ZD_ERROR_INVALID_PATH;
1152 pFileEntry->OnNewFileData (pUncompressed, nSegmentSize, nSegmentSize, METHOD_STORE, true);
1153 // since we changed the time, we'll have to update CDR
1154 m_nFlags |= FLAGS_CDR_DIRTY;
1156 // this file entry is already allocated in CDR
1157 unsigned lSegmentOffset = pFileEntry->nEOFOffset;
1159 #if CRY_PLATFORM_WINDOWS
1160 if (_fseeki64 (m_pFile, (__int64)pFileEntry->nFileHeaderOffset, SEEK_SET)!=0)
1161 #else
1162 if (fseek (m_pFile, pFileEntry->nFileHeaderOffset, SEEK_SET)!=0)
1163 #endif
1164 return ZD_ERROR_IO_FAILED;
1166 // and we can just override the compressed data in the file
1167 ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, szRelativePath, m_bEncryptedHeaders);
1168 if (e != ZD_ERROR_SUCCESS)
1169 return e;
1171 if(nOverwriteSeekPos!=0xffffffff)
1172 lSegmentOffset = pFileEntry->nFileDataOffset + nOverwriteSeekPos;
1174 // now we have the fresh local header and data offset
1175 #if CRY_PLATFORM_WINDOWS
1176 if (_fseeki64 (m_pFile, (__int64)lSegmentOffset, SEEK_SET)!=0)
1177 #else
1178 if (fseek (m_pFile, lSegmentOffset, SEEK_SET)!=0)
1179 #endif
1180 return ZD_ERROR_IO_FAILED;
1182 const bool encrypt = false; // encryption is not supported for continous updates
1183 if (!WriteCompressedData((char*)pUncompressed, nSegmentSize, encrypt, m_pFile))
1185 return ZD_ERROR_IO_FAILED;
1188 if(nOverwriteSeekPos==0xffffffff)
1189 pFileEntry->nEOFOffset = lSegmentOffset + nSegmentSize;
1191 // since we wrote the file successfully, update CDR
1192 pFileEntry.Commit();
1193 return ZD_ERROR_SUCCESS;
1197 ZipDir::ErrorEnum ZipDir::CacheRW::UpdateFileCRC (const char* szRelativePathSrc, unsigned dwCRC32 )
1199 char str[_MAX_PATH];
1200 char *szRelativePath = UnifyPath(str, szRelativePathSrc);
1202 SmartPtr pBufferDestroyer;
1204 // create or find the file entry.. this object will rollback (delete the object
1205 // if the operation fails) if needed.
1206 FileEntryTransactionAdd pFileEntry(this, AllocPath(szRelativePathSrc), AllocPath(szRelativePath));
1208 if (!pFileEntry)
1209 return ZD_ERROR_INVALID_PATH;
1211 // since we changed the time, we'll have to update CDR
1212 m_nFlags |= FLAGS_CDR_DIRTY;
1214 pFileEntry->desc.lCRC32=dwCRC32;
1216 #if CRY_PLATFORM_WINDOWS
1217 if (_fseeki64 (m_pFile, (__int64)pFileEntry->nFileHeaderOffset, SEEK_SET)!=0)
1218 #else
1219 if (fseek (m_pFile, pFileEntry->nFileHeaderOffset, SEEK_SET)!=0)
1220 #endif
1221 return ZD_ERROR_IO_FAILED;
1223 // and we can just override the compressed data in the file
1224 ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, szRelativePath, m_bEncryptedHeaders);
1225 if (e != ZD_ERROR_SUCCESS)
1226 return e;
1228 // since we wrote the file successfully, update
1229 pFileEntry.Commit();
1230 return ZD_ERROR_SUCCESS;
1234 // deletes the file from the archive
1235 ZipDir::ErrorEnum ZipDir::CacheRW::RemoveFile (const char* szRelativePathSrc)
1237 char str[_MAX_PATH];
1238 char *szRelativePath = UnifyPath(str, szRelativePathSrc);
1240 // find the last slash in the path
1241 const char* pSlash = (std::max)(strrchr(szRelativePath, '/'), strrchr(szRelativePath, '\\'));
1243 const char* pFileName; // the name of the file to delete
1245 FileEntryTree* pDir; // the dir from which the subdir will be deleted
1247 if (pSlash)
1249 FindDirRW fd (GetRoot());
1250 // the directory to remove
1251 pDir = fd.FindExact(string (szRelativePath, pSlash-szRelativePath).c_str());
1252 if (!pDir)
1253 return ZD_ERROR_DIR_NOT_FOUND;// there is no such directory
1254 pFileName = pSlash+1;
1256 else
1258 pDir = GetRoot();
1259 pFileName = szRelativePath;
1262 ErrorEnum e = pDir->RemoveFile (pFileName);
1263 if (e == ZD_ERROR_SUCCESS)
1264 m_nFlags |= FLAGS_UNCOMPACTED|FLAGS_CDR_DIRTY;
1265 return e;
1269 // deletes the directory, with all its descendants (files and subdirs)
1270 ZipDir::ErrorEnum ZipDir::CacheRW::RemoveDir (const char* szRelativePathSrc)
1272 char str[_MAX_PATH];
1273 char *szRelativePath = UnifyPath(str, szRelativePathSrc);
1275 // find the last slash in the path
1276 const char* pSlash = (std::max)(strrchr(szRelativePath, '/'), strrchr(szRelativePath, '\\'));
1278 const char* pDirName; // the name of the dir to delete
1280 FileEntryTree* pDir; // the dir from which the subdir will be deleted
1282 if (pSlash)
1284 FindDirRW fd (GetRoot());
1285 // the directory to remove
1286 pDir = fd.FindExact(string (szRelativePath, pSlash-szRelativePath).c_str());
1287 if (!pDir)
1288 return ZD_ERROR_DIR_NOT_FOUND;// there is no such directory
1289 pDirName = pSlash+1;
1291 else
1293 pDir = GetRoot();
1294 pDirName = szRelativePath;
1297 ErrorEnum e = pDir->RemoveDir (pDirName);
1298 if (e == ZD_ERROR_SUCCESS)
1299 m_nFlags |= FLAGS_UNCOMPACTED|FLAGS_CDR_DIRTY;
1300 return e;
1303 // deletes all files and directories in this archive
1304 ZipDir::ErrorEnum ZipDir::CacheRW::RemoveAll()
1306 ErrorEnum e = m_treeDir.RemoveAll();
1307 if (e == ZD_ERROR_SUCCESS)
1308 m_nFlags |= FLAGS_UNCOMPACTED|FLAGS_CDR_DIRTY;
1309 return e;
1312 ZipDir::ErrorEnum ZipDir::CacheRW::ReadFile (FileEntry* pFileEntry, void* pCompressed, void* pUncompressed)
1314 if (!pFileEntry)
1315 return ZD_ERROR_INVALID_CALL;
1317 if (pFileEntry->desc.lSizeUncompressed == 0)
1319 assert (pFileEntry->desc.lSizeCompressed == 0);
1320 return ZD_ERROR_SUCCESS;
1323 assert (pFileEntry->desc.lSizeCompressed > 0);
1325 ErrorEnum nError = Refresh(pFileEntry);
1326 if (nError != ZD_ERROR_SUCCESS)
1327 return nError;
1329 #if CRY_PLATFORM_WINDOWS
1330 if (_fseeki64 (m_pFile, (__int64)pFileEntry->nFileDataOffset, SEEK_SET))
1331 #else
1332 if (fseek (m_pFile, pFileEntry->nFileDataOffset, SEEK_SET))
1333 #endif
1334 return ZD_ERROR_IO_FAILED;
1336 SmartPtr pBufferDestroyer;
1338 void* pBuffer = pCompressed; // the buffer where the compressed data will go
1340 if (pFileEntry->nMethod == 0 && pUncompressed)
1342 // we can directly read into the uncompress buffer
1343 pBuffer = pUncompressed;
1346 if (!pBuffer)
1348 if (!pUncompressed)
1349 // what's the sense of it - no buffers at all?
1350 return ZD_ERROR_INVALID_CALL;
1352 pBuffer = malloc(pFileEntry->desc.lSizeCompressed);
1353 pBufferDestroyer.Attach(pBuffer); // we want it auto-freed once we return
1356 if (fread((char*)pBuffer, pFileEntry->desc.lSizeCompressed, 1, m_pFile) != 1)
1358 return ZD_ERROR_IO_FAILED;
1361 if (pFileEntry->nMethod == METHOD_DEFLATE_AND_ENCRYPT)
1363 ZipDir::Decrypt((char*)pBuffer, pFileEntry->desc.lSizeCompressed, m_encryptionKey);
1366 // if there's a buffer for uncompressed data, uncompress it to that buffer
1367 if (pUncompressed)
1369 if (pFileEntry->nMethod == 0)
1371 assert (pBuffer == pUncompressed);
1372 //assert (pFileEntry->nSizeCompressed == pFileEntry->nSizeUncompressed);
1373 //memcpy (pUncompressed, pBuffer, pFileEntry->nSizeCompressed);
1375 else
1377 unsigned long nSizeUncompressed = pFileEntry->desc.lSizeUncompressed;
1378 if (nSizeUncompressed > 0)
1380 if (Z_OK != ZipRawUncompress(pUncompressed, &nSizeUncompressed, pBuffer, pFileEntry->desc.lSizeCompressed))
1381 return ZD_ERROR_CORRUPTED_DATA;
1386 return ZD_ERROR_SUCCESS;
1390 //////////////////////////////////////////////////////////////////////////
1391 // finds the file by exact path
1392 ZipDir::FileEntry* ZipDir::CacheRW::FindFile (const char* szPathSrc, bool bFullInfo)
1394 char str[_MAX_PATH];
1395 char *szPath = UnifyPath(str, szPathSrc);
1397 if (!this)
1398 return NULL;
1399 ZipDir::FindFileRW fd (GetRoot());
1400 if (!fd.FindExact(szPath))
1402 assert (!fd.GetFileEntry());
1403 return NULL;
1405 ZipDir::FileEntry* const p = fd.GetFileEntry();
1406 assert (p);
1407 return p;
1410 // returns the size of memory occupied by the instance referred to by this cache
1411 size_t ZipDir::CacheRW::GetSize() const
1413 return sizeof(*this) + m_strFilePath.capacity() + m_treeDir.GetSize() - sizeof(m_treeDir);
1416 // returns the compressed size of all the entries
1417 size_t ZipDir::CacheRW::GetCompressedSize() const
1419 return m_treeDir.GetCompressedFileSize();
1422 // returns the total size of memory occupied by the instance of this cache and all the compressed files
1423 size_t ZipDir::CacheRW::GetTotalFileSize() const
1425 return GetSize() + GetCompressedSize();
1428 // returns the total size of space occupied on disk by the instance of this cache and all the compressed files
1429 size_t ZipDir::CacheRW::GetTotalFileSizeOnDiskSoFar()
1431 FileRecordList arrFiles(GetRoot());
1432 FileRecordList::ZipStats statFiles = arrFiles.GetStats();
1434 return m_lCDROffset + statFiles.nSizeCDR;
1437 // refreshes information about the given file entry into this file entry
1438 ZipDir::ErrorEnum ZipDir::CacheRW::Refresh (FileEntry* pFileEntry)
1440 if (!pFileEntry)
1441 return ZD_ERROR_INVALID_CALL;
1443 if (pFileEntry->nFileDataOffset != pFileEntry->INVALID_DATA_OFFSET)
1444 return ZD_ERROR_SUCCESS; // the data offset has been successfully read..
1446 if (!this)
1447 return ZD_ERROR_INVALID_CALL; // from which cache is this file entry???
1449 return ZipDir::Refresh(m_pFile, pFileEntry, m_bEncryptedHeaders);
1453 // writes the CDR to the disk
1454 bool ZipDir::CacheRW::WriteCDR(FILE* fTarget, bool encryptCDR)
1456 if (!fTarget)
1457 return false;
1459 #if CRY_PLATFORM_WINDOWS
1460 if (_fseeki64(fTarget, (__int64)m_lCDROffset, SEEK_SET))
1461 #else
1462 if (fseek(fTarget, m_lCDROffset, SEEK_SET))
1463 #endif
1464 return false;
1466 FileRecordList arrFiles(GetRoot());
1467 //arrFiles.SortByFileOffset();
1468 size_t nSizeCDR = arrFiles.GetStats().nSizeCDR;
1469 void* pCDR = malloc(nSizeCDR);
1470 size_t nSizeCDRSerialized = arrFiles.MakeZipCDR(m_lCDROffset, pCDR, encryptCDR);
1471 assert (nSizeCDRSerialized == nSizeCDR);
1473 if (encryptCDR)
1475 // We do not encrypt CDREnd, so we could find it by signature
1476 ZipDir::Encrypt((char*)pCDR, nSizeCDR - sizeof(ZipFile::CDREnd), m_encryptionKey);
1479 size_t nWriteRes = fwrite (pCDR, nSizeCDR, 1, fTarget);
1480 free(pCDR);
1481 return nWriteRes == 1;
1484 // generates random file name
1485 string ZipDir::CacheRW::GetRandomName(int nAttempt)
1487 if (nAttempt)
1489 char szBuf[8];
1490 int i;
1491 for (i = 0; i < sizeof(szBuf)-1;++i)
1493 int r = rand()%(10 + 'z' - 'a' + 1);
1494 szBuf[i] = r > 9 ? (r-10)+'a' : '0' + r;
1496 szBuf[i] = '\0';
1497 return szBuf;
1499 else
1500 return string();
1503 bool ZipDir::CacheRW::RelinkZip()
1505 for (int nAttempt = 0; nAttempt < 32; ++nAttempt)
1507 string strNewFilePath = m_strFilePath + "$" + GetRandomName(nAttempt);
1509 FILE* f = fopen (strNewFilePath.c_str(), "wb");
1510 if (f)
1512 bool bOk = RelinkZip(f);
1513 fclose (f); // we don't need the temporary file handle anyway
1515 if (!bOk)
1517 // we don't need the temporary file
1518 DeleteFileA(strNewFilePath.c_str());
1519 return false;
1522 // we successfully relinked, now copy the temporary file to the original file
1523 fclose (m_pFile);
1524 m_pFile = NULL;
1526 DeleteFileA(m_strFilePath.c_str());
1527 if (MoveFileA(strNewFilePath.c_str(), m_strFilePath.c_str()) == 0)
1529 // successfully renamed - reopen
1530 m_pFile = fopen (m_strFilePath.c_str(), "r+b");
1531 return m_pFile == NULL;
1533 else
1535 // could not rename
1537 //m_pFile = fopen (strNewFilePath.c_str(), "r+b");
1538 return false;
1543 // couldn't open temp file
1544 return false;
1547 bool ZipDir::CacheRW::RelinkZip(FILE* fTmp)
1549 FileRecordList arrFiles(GetRoot());
1550 arrFiles.SortByFileOffset();
1552 // we back up our file entries, because we'll need to restore them
1553 // in case the operation fails
1554 std::vector<FileEntry> arrFileEntryBackup;
1555 arrFiles.Backup (arrFileEntryBackup);
1557 // this is the set of files that are to be written out - compressed data and the file record iterator
1558 std::vector<FileDataRecordPtr> queFiles;
1559 queFiles.reserve (g_nMaxItemsRelinkBuffer);
1561 // the total size of data in the queue
1562 unsigned nQueueSize = 0;
1564 for (FileRecordList::iterator it = arrFiles.begin(); it != arrFiles.end(); ++it)
1566 FileEntry* entry = it->pFileEntry;
1567 // find the file data offset
1568 if (ZD_ERROR_SUCCESS != Refresh(entry))
1569 return false;
1571 // go to the file data
1572 #if CRY_PLATFORM_WINDOWS
1573 if (_fseeki64 (m_pFile, (__int64)entry->nFileDataOffset, SEEK_SET) != 0)
1574 #else
1575 if (fseek (m_pFile, entry->nFileDataOffset, SEEK_SET) != 0)
1576 #endif
1577 return false;
1579 // allocate memory for the file compressed data
1580 FileDataRecordPtr pFile = FileDataRecord::New (*it);
1582 if(!pFile)
1583 return false;
1585 // read the compressed data
1586 if (entry->desc.lSizeCompressed && fread (pFile->GetData(), entry->desc.lSizeCompressed, 1, m_pFile) != 1)
1587 return false;
1589 if (entry->nMethod == METHOD_DEFLATE_AND_ENCRYPT)
1591 ZipDir::Decrypt((char*)pFile->GetData(), entry->desc.lSizeCompressed, m_encryptionKey);
1594 // put the file into the queue for copying (writing)
1595 queFiles.push_back(pFile);
1596 nQueueSize += entry->desc.lSizeCompressed;
1598 // if the queue is big enough, write it out
1599 if(nQueueSize > g_nSizeRelinkBuffer || queFiles.size() >= g_nMaxItemsRelinkBuffer)
1601 nQueueSize = 0;
1602 if (!WriteZipFiles(queFiles, fTmp))
1603 return false;
1607 if (!WriteZipFiles(queFiles, fTmp))
1608 return false;
1610 ZipFile::ulong lOldCDROffset = m_lCDROffset;
1611 // the file data has now been written out. Now write the CDR
1612 #if CRY_PLATFORM_WINDOWS
1613 m_lCDROffset = (ZipFile::ulong)_ftelli64(fTmp);
1614 #else
1615 m_lCDROffset = ftell(fTmp);
1616 #endif
1617 if (m_lCDROffset != (ZipFile::ulong)-1L && WriteCDR(fTmp, m_bHeadersEncryptedOnClose) && 0 == fflush (fTmp))
1619 // the new file positions are already there - just discard the backup and return
1620 return true;
1622 // recover from backup
1623 arrFiles.Restore (arrFileEntryBackup);
1624 m_lCDROffset = lOldCDROffset;
1625 m_bEncryptedHeaders = m_bHeadersEncryptedOnClose;
1626 return false;
1629 // writes out the file data in the queue into the given file. Empties the queue
1630 bool ZipDir::CacheRW::WriteZipFiles(std::vector<FileDataRecordPtr>& queFiles, FILE* fTmp)
1632 for (std::vector<FileDataRecordPtr>::iterator it = queFiles.begin(); it != queFiles.end(); ++it)
1634 // set the new header offset to the file entry - we won't need it
1635 #if CRY_PLATFORM_WINDOWS
1636 const unsigned long currentPos = (unsigned long)_ftelli64 (fTmp);
1637 #else
1638 const unsigned long currentPos = ftell (fTmp);
1639 #endif
1640 (*it)->pFileEntry->nFileHeaderOffset = CalculateAlignedHeaderOffset((*it)->strPath.c_str(), currentPos, m_fileAlignment);
1642 // while writing the local header, the data offset will also be calculated
1643 if (ZD_ERROR_SUCCESS != WriteLocalHeader(fTmp, (*it)->pFileEntry, (*it)->strPath.c_str(), m_bHeadersEncryptedOnClose))
1644 return false;;
1646 // write the compressed file data
1647 const bool encrypt = (*it)->pFileEntry->nMethod == METHOD_DEFLATE_AND_ENCRYPT;
1648 if (!WriteCompressedData((char*)(*it)->GetData(), (*it)->pFileEntry->desc.lSizeCompressed, encrypt, fTmp))
1649 return false;
1651 #if CRY_PLATFORM_WINDOWS
1652 assert ((*it)->pFileEntry->nEOFOffset == (unsigned long)_ftelli64 (fTmp));
1653 #else
1654 assert ((*it)->pFileEntry->nEOFOffset == ftell (fTmp));
1655 #endif
1657 queFiles.clear();
1658 queFiles.reserve (g_nMaxItemsRelinkBuffer);
1659 return true;
1662 void TruncateFile(FILE* file, size_t newLength)
1664 int filedes = _fileno(file);
1665 _chsize_s(filedes, newLength);
1668 bool ZipDir::CacheRW::EncryptArchive(EncryptionChange change, IEncryptPredicate* encryptContentPredicate, int* numChanged, int* numSkipped)
1670 FileRecordList arrFiles(GetRoot());
1671 arrFiles.SortByFileOffset();
1673 size_t unusedSpace = 0;
1674 size_t lastDataEnd = 0;
1676 for (FileRecordList::iterator it = arrFiles.begin(); it != arrFiles.end(); ++it)
1678 FileEntry* entry = it->pFileEntry;
1680 if (entry->nFileHeaderOffset > lastDataEnd)
1682 fseek(m_pFile, lastDataEnd, SEEK_SET);
1683 size_t gapLength = entry->nFileHeaderOffset - lastDataEnd;
1684 unusedSpace += gapLength;
1685 if (change == ENCRYPT)
1687 if (!WriteRandomData(m_pFile, gapLength))
1689 return false;
1692 else
1694 if (!WriteNullData(gapLength))
1696 return false;
1700 lastDataEnd = entry->nEOFOffset;
1702 if (numSkipped)
1704 ++(*numSkipped);
1707 // find the file data offset
1708 if (ZD_ERROR_SUCCESS != Refresh (entry))
1710 return false;
1713 ZipFile::ushort oldMethod = entry->nMethod;
1714 ZipFile::ushort newMethod = oldMethod;
1715 if (change == ENCRYPT)
1717 if (entry->nMethod == METHOD_DEFLATE)
1719 newMethod = METHOD_DEFLATE_AND_ENCRYPT;
1722 else
1724 if (entry->nMethod == METHOD_DEFLATE_AND_ENCRYPT)
1726 newMethod = METHOD_DEFLATE;
1730 // allow encryption only for matching files
1731 if (newMethod == METHOD_DEFLATE_AND_ENCRYPT &&
1732 (!encryptContentPredicate || !encryptContentPredicate->Match(it->strPath.c_str())))
1734 newMethod = METHOD_DEFLATE;
1737 entry->nMethod = newMethod;
1739 const bool encryptHeaders = change == ENCRYPT;
1740 // encryption is toggled or compression method changed...
1741 if (newMethod != oldMethod || encryptHeaders != m_bEncryptedHeaders)
1743 // ... update header
1744 if (ZipDir::WriteLocalHeader(m_pFile, entry, it->strPath.c_str(), encryptHeaders) != ZD_ERROR_SUCCESS)
1746 return false;
1750 if (newMethod == oldMethod)
1752 // no need to update file content
1753 continue;
1756 // go to the file data
1757 #if CRY_PLATFORM_WINDOWS
1758 if (_fseeki64 (m_pFile, (__int64)entry->nFileDataOffset, SEEK_SET) != 0)
1759 #else
1760 if (fseek (m_pFile, entry->nFileDataOffset, SEEK_SET) != 0)
1761 #endif
1762 return false;
1764 // allocate memory for the file compressed data
1765 FileDataRecordPtr pFile = FileDataRecord::New(*it);
1766 if (!pFile)
1768 return false;
1771 // read the compressed data
1772 if (entry->desc.lSizeCompressed && fread (pFile->GetData(), entry->desc.lSizeCompressed, 1, m_pFile) != 1)
1774 return false;
1777 if (oldMethod == METHOD_DEFLATE_AND_ENCRYPT)
1779 ZipDir::Decrypt((char*)pFile->GetData(), entry->desc.lSizeCompressed, m_encryptionKey);
1782 #if CRY_PLATFORM_WINDOWS
1783 if (_fseeki64 (m_pFile, (__int64)entry->nFileDataOffset, SEEK_SET) != 0)
1784 #else
1785 if (fseek (m_pFile, entry->nFileDataOffset, SEEK_SET) != 0)
1786 #endif
1788 return false;
1791 const bool encryptContent = newMethod == METHOD_DEFLATE_AND_ENCRYPT;
1792 if (!WriteCompressedData((const char*)pFile->GetData(), entry->desc.lSizeCompressed, encryptContent, m_pFile))
1794 return false;
1797 if (numSkipped)
1799 --(*numSkipped);
1801 if (numChanged)
1803 ++(*numChanged);
1807 m_bEncryptedHeaders = change == ENCRYPT;
1808 m_bHeadersEncryptedOnClose = m_bEncryptedHeaders;
1810 if (!WriteCDR(m_pFile, m_bEncryptedHeaders))
1812 return false;
1815 if (fflush (m_pFile) != 0)
1817 return false;
1820 size_t endOfCDR = (size_t)ftell(m_pFile);
1822 fseek(m_pFile, 0, SEEK_END);
1823 size_t fileSize = (size_t)ftell(m_pFile);
1825 if (fileSize != endOfCDR)
1827 TruncateFile(m_pFile, endOfCDR);
1831 if (unusedSpace > 0)
1833 // RCLog("Archive contains %i bytes of uncompacted space.", (int)unusedSpace);
1836 fclose(m_pFile);
1837 m_pFile = 0;
1838 m_treeDir.Clear();
1839 return true;