Merge branch '138-toggle-free-look-with-hotkey' into 'main/atys-live'
[ryzomcore.git] / nel / src / misc / seven_zip.cpp
blob7a89c20c69bedbfc59ddaf900c5959a3ffe7f444
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2019-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include <nel/misc/seven_zip.h>
22 #include <nel/misc/types_nl.h>
23 #include <nel/misc/file.h>
24 #include <nel/misc/path.h>
26 #include <seven_zip/7z.h>
27 #include <seven_zip/7zAlloc.h>
28 #include <seven_zip/7zCrc.h>
29 #include <seven_zip/7zVersion.h>
30 #include <seven_zip/LzmaLib.h>
31 #include <seven_zip/LzmaDec.h>
33 #include <memory>
36 // Namespaces
39 using namespace std;
40 using namespace NLMISC;
42 #ifdef DEBUG_NEW
43 #define new DEBUG_NEW
44 #endif
46 namespace NLMISC {
48 /// Input stream class for 7zip archive
49 class CNel7ZipInStream : public ISeekInStream
51 NLMISC::IStream *_Stream;
53 public:
54 /// Constructor, only allow file stream because 7zip will 'seek' in the stream
55 CNel7ZipInStream(NLMISC::IStream *s)
56 : _Stream(s)
58 Read = readFunc;
59 Seek = seekFunc;
62 // the read function called by 7zip to read data
63 static SRes readFunc(const ISeekInStream *object, void *buffer, size_t *size)
65 try
67 CNel7ZipInStream *me = (CNel7ZipInStream*)object;
68 uint len = (uint)*size;
69 me->_Stream->serialBuffer((uint8*)buffer, len);
70 return SZ_OK;
72 catch (...)
74 return SZ_ERROR_READ;
78 // the seek function called by seven zip to seek inside stream
79 static SRes seekFunc(const ISeekInStream *object, Int64 *pos, ESzSeek origin)
81 try
83 CNel7ZipInStream *me = (CNel7ZipInStream*)object;
84 sint32 offset = (sint32)*pos;
85 bool ret = me->_Stream->seek(offset, (NLMISC::IStream::TSeekOrigin)origin);
87 if (ret)
89 *pos = (Int64)me->_Stream->getPos();
90 return SZ_OK;
93 catch (...)
97 return SZ_ERROR_READ;
101 bool unpack7Zip(const std::string &sevenZipFile, const std::string &destFileName)
103 nlinfo("Uncompressing 7zip archive '%s' to '%s'", sevenZipFile.c_str(), destFileName.c_str());
105 // init seven zip
106 ISzAlloc allocImp;
107 allocImp.Alloc = SzAlloc;
108 allocImp.Free = SzFree;
110 ISzAlloc allocTempImp;
111 allocTempImp.Alloc = SzAllocTemp;
112 allocTempImp.Free = SzFreeTemp;
114 // wrap file in a CIFile
115 CIFile input(sevenZipFile);
116 CNel7ZipInStream inStr(&input);
118 CLookToRead2 lookStream;
119 lookStream.realStream = &inStr;
120 LookToRead2_CreateVTable(&lookStream, False);
122 size_t bufferSize = 1024;
125 lookStream.buf = (Byte*)ISzAlloc_Alloc(&allocImp, bufferSize);
127 if (!lookStream.buf)
129 nlerror("Unable to allocate %zu bytes", bufferSize);
130 return false;
133 lookStream.bufSize = bufferSize;
134 lookStream.realStream = &inStr;
135 LookToRead2_Init(&lookStream);
138 CrcGenerateTable();
140 CSzArEx db;
141 SzArEx_Init(&db);
143 // unpack the file using the 7zip API
144 SRes res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp);
146 if (res != SZ_OK)
148 nlerror("Failed to open archive file %s", sevenZipFile.c_str());
149 return false;
152 if (db.NumFiles != 1)
154 nlerror("Seven zip archive with more than 1 file are unsupported");
155 return false;
158 UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
159 Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */
160 size_t outBufferSize = 0; /* it can have any value before first call (if outBuffer = 0) */
162 size_t offset;
163 size_t outSizeProcessed = 0;
165 // get the first file
166 res = SzArEx_Extract(&db, &lookStream.vt, 0, &blockIndex, &outBuffer, &outBufferSize, &offset, &outSizeProcessed, &allocImp, &allocTempImp);
168 // get the length of first file
169 size_t nameLen = SzArEx_GetFileNameUtf16(&db, 0, NULL);
171 ucstring filename;
172 filename.resize(nameLen);
174 // write filename into ucstring
175 SzArEx_GetFileNameUtf16(&db, 0, reinterpret_cast<UInt16 *>(&filename[0]));
177 // write the extracted file
178 FILE *outputHandle = nlfopen(destFileName, "wb+");
180 if (outputHandle == 0)
182 nlerror("Can not open output file '%s'", destFileName.c_str());
183 return false;
186 UInt32 processedSize = (UInt32)fwrite(outBuffer + offset, 1, outSizeProcessed, outputHandle);
188 if (processedSize != outSizeProcessed)
190 nlerror("Failed to write %u char to output file '%s'", outSizeProcessed-processedSize, destFileName.c_str());
191 return false;
194 fclose(outputHandle);
196 IAlloc_Free(&allocImp, outBuffer);
198 // free 7z context
199 SzArEx_Free(&db, &allocImp);
201 // ok, all is fine, file is unpacked
202 return true;
205 bool unpackLZMA(const std::string &lzmaFile, const std::string &destFileName)
207 nldebug("unpackLZMA: decompress LZMA file '%s' to '%s", lzmaFile.c_str(), destFileName.c_str());
209 // open input file
210 CIFile inStream(lzmaFile);
211 uint32 inSize = inStream.getFileSize();
213 if (inSize < LZMA_PROPS_SIZE + 8)
215 nlwarning("unpackLZMA: Invalid file size, too small file '%s'", lzmaFile.c_str());
216 return false;
219 // allocate input buffer for props
220 std::vector<uint8> propsBuffer(LZMA_PROPS_SIZE);
222 // size of LZMA content
223 inSize -= LZMA_PROPS_SIZE + 8;
225 // allocate input buffer for lzma data
226 std::vector<uint8> inBuffer(inSize);
228 uint64 fileSize = 0;
232 // read props
233 inStream.serialBuffer(&propsBuffer[0], LZMA_PROPS_SIZE);
235 // read uncompressed size
236 inStream.serial(fileSize);
238 // read lzma content
239 inStream.serialBuffer(&inBuffer[0], inSize);
241 catch(const EReadError &e)
243 nlwarning("unpackLZMA: Error while reading '%s': %s", lzmaFile.c_str(), e.what());
244 return false;
247 // allocate the output buffer
248 std::vector<uint8> outBuffer(fileSize);
250 // in and out file sizes
251 SizeT outProcessed = (SizeT)fileSize;
252 SizeT inProcessed = (SizeT)inSize;
254 // decompress the file in memory
255 sint res = LzmaUncompress(&outBuffer[0], &outProcessed, &inBuffer[0], &inProcessed, &propsBuffer[0], LZMA_PROPS_SIZE);
257 if (res != 0 || outProcessed != fileSize)
259 nlwarning("unpackLZMA: Failed to decode lzma file '%s' with status %d", lzmaFile.c_str(), res);
260 return false;
263 // store on output buffer
264 COFile outStream(destFileName);
268 // write content
269 outStream.serialBuffer(&outBuffer[0], (uint)fileSize);
271 catch(const EFile &e)
273 nlwarning("unpackLZMA: Error while writing '%s': %s", destFileName.c_str(), e.what());
274 CFile::deleteFile(destFileName);
275 return false;
278 return true;
281 bool unpackLZMA(const std::string &lzmaFile, const std::string &destFileName, CHashKey &sha1)
283 nldebug("unpackLZMA: decompress LZMA file '%s' to '%s", lzmaFile.c_str(), destFileName.c_str());
285 // open input file
286 CIFile inStream(lzmaFile);
287 uint32 inSize = inStream.getFileSize();
289 if (inSize < LZMA_PROPS_SIZE + 8)
291 nlwarning("unpackLZMA: Invalid file size, too small file '%s'", lzmaFile.c_str());
292 return false;
295 // allocate input buffer for props
296 std::vector<uint8> propsBuffer(LZMA_PROPS_SIZE);
298 // size of LZMA content
299 inSize -= LZMA_PROPS_SIZE + 8;
301 // allocate input buffer for lzma data
302 std::vector<uint8> inBuffer(inSize);
304 uint64 fileSize = 0;
308 // read props
309 inStream.serialBuffer(&propsBuffer[0], LZMA_PROPS_SIZE);
311 // read uncompressed size
312 inStream.serial(fileSize);
314 // read lzma content
315 inStream.serialBuffer(&inBuffer[0], inSize);
317 catch(const EReadError &e)
319 nlwarning("unpackLZMA: Error while reading '%s': %s", lzmaFile.c_str(), e.what());
320 return false;
323 // allocate the output buffer
324 std::vector<uint8> outBuffer(fileSize);
326 // in and out file sizes
327 SizeT outProcessed = (SizeT)fileSize;
328 SizeT inProcessed = (SizeT)inSize;
330 // decompress the file in memory
331 sint res = LzmaUncompress(&outBuffer[0], &outProcessed, &inBuffer[0], &inProcessed, &propsBuffer[0], LZMA_PROPS_SIZE);
333 if (res != 0 || outProcessed != fileSize)
335 nlwarning("unpackLZMA: Failed to decode lzma file '%s' with status %d", lzmaFile.c_str(), res);
336 return false;
339 // get hash
340 sha1 = getSHA1(&outBuffer[0], outBuffer.size());
342 // store on output buffer
343 COFile outStream(destFileName);
347 // write content
348 outStream.serialBuffer(&outBuffer[0], (uint)fileSize);
350 catch(const EFile &e)
352 nlwarning("unpackLZMA: Error while writing '%s': %s", destFileName.c_str(), e.what());
353 CFile::deleteFile(destFileName);
354 return false;
357 return true;
360 bool packLZMA(const std::string &srcFileName, const std::string &lzmaFileName)
362 nldebug("packLZMA: compress '%s' to LZMA file '%s", srcFileName.c_str(), lzmaFileName.c_str());
364 // open file
365 CIFile inStream(srcFileName);
366 size_t inSize = inStream.getFileSize();
368 // file empty
369 if (!inSize)
371 nlwarning("packLZMA: File '%s' not found or empty", srcFileName.c_str());
372 return false;
375 // allocate input buffer
376 std::vector<uint8> inBuffer(inSize);
380 // read file in buffer
381 inStream.serialBuffer(&inBuffer[0], inSize);
383 catch(const EReadError &e)
385 nlwarning("packLZMA: Error while reading '%s': %s", srcFileName.c_str(), e.what());
386 return false;
389 // allocate output buffer
390 size_t outSize = (11 * inSize / 10) + 65536; // worst case = 1.1 * size + 64K
391 std::vector<uint8> outBuffer(outSize);
393 // allocate buffer for props
394 size_t outPropsSize = LZMA_PROPS_SIZE;
395 std::vector<uint8> outProps(outPropsSize);
397 // compress with best compression and other default settings
398 sint res = LzmaCompress(&outBuffer[0], &outSize, &inBuffer[0], inSize, &outProps[0], &outPropsSize, 9, 1 << 27, -1, -1, -1, -1, 1);
400 switch(res)
402 case SZ_OK:
404 // store on output buffer
405 COFile outStream(lzmaFileName);
407 // unable to create file
408 if (!outStream.isOpen())
410 nlwarning("packLZMA: Unable to create '%s'", srcFileName.c_str());
411 return false;
416 // write props
417 outStream.serialBuffer(&outProps[0], (uint)outPropsSize);
419 // write uncompressed size
420 uint64 uncompressSize = inSize;
421 outStream.serial(uncompressSize);
423 // write content
424 outStream.serialBuffer(&outBuffer[0], (uint)outSize);
426 catch(const EFile &e)
428 nlwarning("packLZMA: Error while writing '%s': %s", lzmaFileName.c_str(), e.what());
429 CFile::deleteFile(lzmaFileName);
430 return false;
433 return true;
436 case SZ_ERROR_MEM:
437 nlwarning("packLZMA: Memory allocation error while compressing '%s' (input buffer size: %u, output buffer size: %u)", srcFileName.c_str(), (uint)inSize, (uint)outSize);
438 break;
440 case SZ_ERROR_PARAM:
441 nlwarning("packLZMA: Incorrect parameter while compressing '%s'", srcFileName.c_str());
442 break;
444 case SZ_ERROR_OUTPUT_EOF:
445 nlwarning("packLZMA: Output buffer overflow while compressing '%s' (input buffer size: %u, output buffer size: %u)", srcFileName.c_str(), (uint)inSize, (uint)outSize);
446 break;
448 case SZ_ERROR_THREAD:
449 nlwarning("packLZMA: Errors in multithreading functions (only for Mt version)");
450 break;
452 default:
453 nlwarning("packLZMA: Unknown error (%d) while compressing '%s'", res, srcFileName.c_str());
456 return false;