You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1095 lines
38 KiB
C++
1095 lines
38 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
|
|
#include <AzCore/PlatformIncl.h>
|
|
#include <AzCore/Casting/numeric_cast.h>
|
|
#include <AzCore/IO/Path/Path.h>
|
|
#include <AzCore/Memory/OSAllocator.h>
|
|
#include <AzFramework/Archive/Codec.h>
|
|
#include <AzFramework/Archive/IArchive.h>
|
|
#include <AzFramework/Archive/ZipFileFormat.h>
|
|
#include <AzFramework/Archive/ZipDirStructures.h>
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
#include <zstd.h>
|
|
#include <lz4frame.h>
|
|
#include <zlib.h>
|
|
|
|
namespace AZ::IO::ZipDir::ZipDirStructuresInternal
|
|
{
|
|
static void* ZlibAlloc(void* userData, uint32_t item, uint32_t size)
|
|
{
|
|
auto allocator = reinterpret_cast<AZ::IAllocatorAllocate*>(userData);
|
|
return allocator->Allocate(item * size, alignof(uint8_t), 0, "ZLibAlloc");
|
|
}
|
|
|
|
static void ZlibFree(void* userData, void* ptr)
|
|
{
|
|
auto allocator = reinterpret_cast<AZ::IAllocatorAllocate*>(userData);
|
|
allocator->DeAllocate(ptr);
|
|
}
|
|
|
|
static void ZlibInflateElement_Impl(const void* pCompressed, void* pUncompressed, size_t compressedSize, size_t nUnCompressedSize, size_t* pUncompressedSize, int* pReturnCode);
|
|
|
|
static void ZlibOverlapInflate(int* pReturnCode, z_stream* pZStream, UncompressLookahead& lookahead, uint8_t* pOutput, size_t nOutputLen, const uint8_t* pInput, size_t nInputLen)
|
|
{
|
|
pZStream->total_in = 0;
|
|
pZStream->avail_in = 0;
|
|
pZStream->avail_out = 0;
|
|
|
|
Bytef* pIn = (Bytef*)pInput;
|
|
uInt nIn = aznumeric_cast<uint32_t>(nInputLen);
|
|
uint8_t* pOutputEnd = pOutput + nOutputLen;
|
|
|
|
uInt nInCursorZ = lookahead.cachedStartIdx;
|
|
uInt nInCursorW = lookahead.cachedEndIdx;
|
|
|
|
do
|
|
{
|
|
// Capture some more input
|
|
|
|
{
|
|
uInt nWrZ = nInCursorZ % sizeof(lookahead.buffer);
|
|
uInt nWrW = nInCursorW % sizeof(lookahead.buffer);
|
|
|
|
uInt nBytesToCache = nWrZ > nWrW
|
|
? nWrZ - nWrW
|
|
: sizeof(lookahead.buffer) - nWrW;
|
|
nBytesToCache = (AZStd::min)(nBytesToCache, nIn);
|
|
|
|
if (nBytesToCache)
|
|
{
|
|
memcpy(lookahead.buffer + nWrW, pIn, nBytesToCache);
|
|
pIn += nBytesToCache;
|
|
nIn -= nBytesToCache;
|
|
nInCursorW += nBytesToCache;
|
|
}
|
|
}
|
|
|
|
if (!pZStream->avail_in)
|
|
{
|
|
// Need more input storage - provide it from the local window
|
|
uInt nBytesInWindow = nInCursorW - nInCursorZ;
|
|
if (nBytesInWindow > 0)
|
|
{
|
|
uInt nWrZ = nInCursorZ % sizeof(lookahead.buffer);
|
|
uInt nWrW = nInCursorW % sizeof(lookahead.buffer);
|
|
pZStream->next_in = (Bytef*)lookahead.buffer + nWrZ;
|
|
pZStream->avail_in = nWrW <= nWrZ
|
|
? sizeof(lookahead.buffer) - nWrZ
|
|
: nWrW - nWrZ;
|
|
}
|
|
else if (!nIn)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
pZStream->total_in = 0;
|
|
|
|
// Limit so that output doesn't overflow into next read block
|
|
pZStream->avail_out = (uInt)AZStd::min<uint64_t>(pIn - pZStream->next_out, (pOutputEnd - pZStream->next_out));
|
|
|
|
int nAvailIn = pZStream->avail_in;
|
|
int nAvailOut = pZStream->avail_out;
|
|
|
|
*pReturnCode = inflate(pZStream, Z_SYNC_FLUSH);
|
|
|
|
if (*pReturnCode == Z_BUF_ERROR)
|
|
{
|
|
// As long as we consumed something, keep going. Only fail permanently if we've stalled.
|
|
if (nAvailIn != static_cast<int>(pZStream->avail_in) || nAvailOut != static_cast<int>(pZStream->avail_out))
|
|
{
|
|
*pReturnCode = Z_OK;
|
|
}
|
|
else if (!nIn)
|
|
{
|
|
*pReturnCode = Z_OK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
nInCursorZ += pZStream->total_in;
|
|
} while (*pReturnCode == Z_OK);
|
|
|
|
lookahead.cachedStartIdx = nInCursorZ;
|
|
lookahead.cachedEndIdx = nInCursorW;
|
|
}
|
|
|
|
|
|
static int ZlibInflateIndependentWriteCombined(z_stream* pZS)
|
|
{
|
|
Bytef outputLocal[16384];
|
|
|
|
Bytef* pOutRemote = pZS->next_out;
|
|
uInt nOutRemoteLen = pZS->avail_out;
|
|
|
|
int err = Z_OK;
|
|
|
|
while ((err == Z_OK) && (nOutRemoteLen > 0) && (pZS->avail_in > 0))
|
|
{
|
|
pZS->next_out = outputLocal;
|
|
pZS->avail_out = AZStd::min(static_cast<uInt>(sizeof(outputLocal)), nOutRemoteLen);
|
|
|
|
err = inflate(pZS, Z_SYNC_FLUSH);
|
|
|
|
int nEmitted = (int)(pZS->next_out - outputLocal);
|
|
memcpy(pOutRemote, outputLocal, nEmitted);
|
|
pOutRemote += nEmitted;
|
|
nOutRemoteLen -= nEmitted;
|
|
|
|
if ((err == Z_BUF_ERROR) && (nEmitted > 0))
|
|
{
|
|
err = Z_OK;
|
|
}
|
|
}
|
|
|
|
pZS->next_out = pOutRemote;
|
|
pZS->avail_out = nOutRemoteLen;
|
|
|
|
return err;
|
|
}
|
|
|
|
//Works on an initialized z_stream, is responsible for calling ...init and ...end
|
|
void ZlibInflateElementPartial_Impl(
|
|
int* pReturnCode, z_stream* pZStream, UncompressLookahead* pLookahead,
|
|
uint8_t* pOutput, size_t nOutputLen, bool bOutputWriteOnly,
|
|
const uint8_t* pInput, size_t nInputLen, size_t* pTotalOut)
|
|
{
|
|
z_stream localStream;
|
|
bool bUsingLocal = false;
|
|
|
|
if (!pZStream)
|
|
{
|
|
memset(&localStream, 0, sizeof(localStream));
|
|
pZStream = &localStream;
|
|
bUsingLocal = true;
|
|
}
|
|
|
|
if (pZStream->total_out == 0)
|
|
{
|
|
*pReturnCode = inflateInit2(pZStream, -MAX_WBITS);
|
|
|
|
if (*pReturnCode != Z_OK)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
pZStream->next_out = pOutput;
|
|
pZStream->avail_out = aznumeric_cast<uint32_t>(nOutputLen);
|
|
|
|
// If src/dst overlap (in place decompress), then inflate in chunks, copying src locally to ensure
|
|
// pointers don't foul each other.
|
|
if ((pInput + nInputLen) <= pOutput || pInput >= (pOutput + nOutputLen))
|
|
{
|
|
pZStream->next_in = (Bytef*)pInput;
|
|
pZStream->avail_in = aznumeric_cast<uint32_t>(nInputLen);
|
|
|
|
if (!bOutputWriteOnly)
|
|
{
|
|
*pReturnCode = inflate(pZStream, Z_SYNC_FLUSH);
|
|
}
|
|
else
|
|
{
|
|
*pReturnCode = ZlibInflateIndependentWriteCombined(pZStream);
|
|
}
|
|
}
|
|
else if (pLookahead)
|
|
{
|
|
ZlibOverlapInflate(pReturnCode, pZStream, *pLookahead, pOutput, nOutputLen, pInput, nInputLen);
|
|
}
|
|
else
|
|
{
|
|
UncompressLookahead lookahead;
|
|
ZlibOverlapInflate(pReturnCode, pZStream, lookahead, pOutput, nOutputLen, pInput, nInputLen);
|
|
}
|
|
|
|
if (pTotalOut)
|
|
{
|
|
*pTotalOut = pZStream->total_out;
|
|
}
|
|
|
|
//error during inflate
|
|
if (*pReturnCode != Z_STREAM_END && *pReturnCode != Z_OK)
|
|
{
|
|
inflateEnd(pZStream);
|
|
return;
|
|
}
|
|
|
|
//check if we have finished the read
|
|
if (*pReturnCode == Z_STREAM_END)
|
|
{
|
|
inflateEnd(pZStream);
|
|
}
|
|
else if (bUsingLocal)
|
|
{
|
|
*pReturnCode = Z_VERSION_ERROR;
|
|
}
|
|
}
|
|
|
|
// function to do the zlib decompression, on SPU, we use ZlibInflateElement, which is defined in zlib_spu (which in case only forward to edge zlib decompression)
|
|
void ZlibInflateElement_Impl(const void* pCompressed, void* pUncompressed, size_t compressedSize, size_t nUnCompressedSize, size_t* pUncompressedSize, int* pReturnCode)
|
|
{
|
|
int err;
|
|
z_stream stream;
|
|
|
|
Bytef* pIn = const_cast<Bytef*>(static_cast<const Bytef*>(pCompressed));
|
|
uInt nIn = static_cast<uInt>(compressedSize);
|
|
|
|
stream.next_out = static_cast<Bytef*>(pUncompressed);
|
|
stream.avail_out = static_cast<uInt>(nUnCompressedSize);
|
|
|
|
stream.zalloc = &ZlibAlloc;
|
|
stream.zfree = &ZlibFree;
|
|
stream.opaque = &AZ::AllocatorInstance<AZ::OSAllocator>::Get();
|
|
|
|
err = inflateInit2(&stream, -MAX_WBITS);
|
|
if (err != Z_OK)
|
|
{
|
|
*pReturnCode = err;
|
|
return;
|
|
}
|
|
|
|
// If src/dst overlap (in place decompress), then inflate in chunks, copying src locally to ensure
|
|
// pointers don't foul each other.
|
|
if ((pIn + nIn) <= stream.next_out || pIn >= (stream.next_out + stream.avail_out))
|
|
{
|
|
stream.next_in = pIn;
|
|
stream.avail_in = nIn;
|
|
|
|
// for some strange reason, passing Z_FINISH doesn't work -
|
|
// it seems the stream isn't finished for some files and
|
|
// inflate returns an error due to stream-end-not-reached (though expected) problem
|
|
err = inflate(&stream, Z_SYNC_FLUSH);
|
|
}
|
|
else
|
|
{
|
|
UncompressLookahead lookahead;
|
|
ZlibOverlapInflate(&err, &stream, lookahead, (uint8_t*)pUncompressed, nUnCompressedSize, (uint8_t*)pCompressed, compressedSize);
|
|
}
|
|
|
|
if (err != Z_STREAM_END && err != Z_OK)
|
|
{
|
|
inflateEnd(&stream);
|
|
*pReturnCode = err == Z_OK ? Z_BUF_ERROR : err;
|
|
return;
|
|
}
|
|
|
|
*pUncompressedSize = stream.total_out;
|
|
|
|
err = inflateEnd(&stream);
|
|
*pReturnCode = err;
|
|
}
|
|
|
|
static AZStd::intrusive_ptr<AZ::IO::MemoryBlock> CreateMemoryBlock(size_t size, const char* usage)
|
|
{
|
|
if (!AZ::AllocatorInstance<AZ::OSAllocator>::IsReady())
|
|
{
|
|
AZ_Error("Pak", false, "OSAllocator is not ready. It cannot be used to allocate a MemoryBlock");
|
|
return {};
|
|
}
|
|
|
|
AZ::IAllocatorAllocate* allocator = &AZ::AllocatorInstance<AZ::OSAllocator>::Get();
|
|
AZStd::intrusive_ptr<AZ::IO::MemoryBlock> memoryBlock{ new (allocator->Allocate(sizeof(AZ::IO::MemoryBlock), alignof(AZ::IO::MemoryBlock))) AZ::IO::MemoryBlock{AZ::IO::MemoryBlockDeleter{ &AZ::AllocatorInstance<AZ::OSAllocator>::Get() }} };
|
|
auto CreateFunc = [](size_t byteSize, size_t byteAlignment, const char* name)
|
|
{
|
|
return reinterpret_cast<uint8_t*>(AZ::AllocatorInstance<AZ::OSAllocator>::Get().Allocate(byteSize, byteAlignment, 0, name));
|
|
};
|
|
auto DeleterFunc = [](uint8_t* ptrArray)
|
|
{
|
|
if (ptrArray)
|
|
{
|
|
AZ::AllocatorInstance<AZ::OSAllocator>::Get().DeAllocate(ptrArray);
|
|
}
|
|
};
|
|
memoryBlock->m_address = AZ::IO::MemoryBlock::AddressPtr{ CreateFunc(size, alignof(uint8_t), usage), AZ::IO::MemoryBlock::AddressDeleter{DeleterFunc} };
|
|
memoryBlock->m_size = size;
|
|
|
|
return memoryBlock;
|
|
}
|
|
}
|
|
|
|
namespace AZ::IO::ZipDir
|
|
{
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CZipFile::LoadToMemory(AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData)
|
|
{
|
|
if (!m_pInMemoryData)
|
|
{
|
|
const char* szUsage = "In Memory Zip File";
|
|
m_nCursor = 0;
|
|
|
|
if (pData)
|
|
{
|
|
m_pInMemoryData = AZStd::move(pData);
|
|
|
|
m_nSize = m_pInMemoryData->m_size;
|
|
}
|
|
else
|
|
{
|
|
AZ::IO::HandleType realFileHandle = m_fileHandle;
|
|
|
|
AZ::u64 fileSize = 0;
|
|
if (!m_fileIOBase->Size(realFileHandle, fileSize))
|
|
{
|
|
// Error
|
|
m_nSize = 0;
|
|
return;
|
|
}
|
|
const size_t nFileSize = static_cast<size_t>(fileSize);
|
|
|
|
m_pInMemoryData = ZipDirStructuresInternal::CreateMemoryBlock(nFileSize, szUsage);
|
|
|
|
m_nSize = nFileSize;
|
|
|
|
if (!m_fileIOBase->Seek(realFileHandle, 0, AZ::IO::SeekType::SeekFromStart))
|
|
{
|
|
// Error
|
|
m_nSize = 0;
|
|
return;
|
|
}
|
|
if (!m_fileIOBase->Read(realFileHandle, m_pInMemoryData->m_address.get(), nFileSize, true))
|
|
{
|
|
// Error
|
|
m_nSize = 0;
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CZipFile::UnloadFromMemory()
|
|
{
|
|
m_pInMemoryData.reset();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CZipFile::Close(bool bUnloadFromMem)
|
|
{
|
|
if (m_fileHandle != AZ::IO::InvalidHandle)
|
|
{
|
|
m_fileIOBase->Close(m_fileHandle);
|
|
m_fileHandle = AZ::IO::InvalidHandle;
|
|
}
|
|
|
|
#ifdef SUPPORT_UNBUFFERED_IO
|
|
if (m_unbufferedFile.IsOpen())
|
|
{
|
|
m_unbufferedFile.Close();
|
|
}
|
|
|
|
if (m_pReadTarget)
|
|
{
|
|
_aligned_free(m_pReadTarget);
|
|
m_pReadTarget = nullptr;
|
|
}
|
|
#endif
|
|
|
|
if (bUnloadFromMem)
|
|
{
|
|
UnloadFromMemory();
|
|
}
|
|
|
|
if (!m_pInMemoryData)
|
|
{
|
|
m_nCursor = 0;
|
|
m_nSize = 0;
|
|
}
|
|
}
|
|
|
|
#ifdef SUPPORT_UNBUFFERED_IO
|
|
bool CZipFile::OpenUnbuffered(const char* filename)
|
|
{
|
|
if (!EvaluateSectorSize(filename))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// defining file attributes for opening files using constants to avoid the need to include windows headers
|
|
constexpr int FileFlagNoBuffering = 0x20000000;
|
|
constexpr int FileAttributeNormal = 0x00000080;
|
|
if (m_unbufferedFile.Open(filename, AZ::IO::SystemFile::OpenMode::SF_OPEN_READ_ONLY, FileFlagNoBuffering | FileAttributeNormal))
|
|
{
|
|
m_nSize = aznumeric_cast<int64_t>(m_unbufferedFile.Length());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CZipFile::EvaluateSectorSize(const char* filename)
|
|
{
|
|
AZ::IO::FixedMaxPath volume;
|
|
|
|
if (AZ::IO::PathView(filename).IsRelative())
|
|
{
|
|
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(volume, filename);
|
|
}
|
|
else
|
|
{
|
|
volume = filename;
|
|
}
|
|
|
|
AZ::IO::FixedMaxPath drive = volume.RootName();
|
|
if (drive.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DWORD bytesPerSector;
|
|
if (GetDiskFreeSpaceA(drive.c_str(),nullptr, &bytesPerSector, nullptr, nullptr))
|
|
{
|
|
m_nSectorSize = bytesPerSector;
|
|
AZ::IAllocatorAllocate& allocator = AZ::AllocatorInstance<AZ::OSAllocator>::Get();
|
|
if (m_pReadTarget)
|
|
{
|
|
allocator.DeAllocate(m_pReadTarget);
|
|
}
|
|
m_pReadTarget = allocator.Allocate(128 * 1024, m_nSectorSize);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
CZipFile::CZipFile() : m_fileHandle(AZ::IO::InvalidHandle)
|
|
#ifdef SUPPORT_UNBUFFERED_IO
|
|
, m_unbufferedFile()
|
|
, m_nSectorSize(0)
|
|
, m_pReadTarget(nullptr)
|
|
#endif
|
|
, m_nSize(0)
|
|
, m_nCursor(0)
|
|
, m_szFilename(nullptr)
|
|
, m_pInMemoryData(nullptr)
|
|
{
|
|
|
|
}
|
|
|
|
void CZipFile::Swap(CZipFile& other)
|
|
{
|
|
AZStd::swap(m_fileHandle, other.m_fileHandle);
|
|
#ifdef SUPPORT_UNBUFFERED_IO
|
|
AZStd::swap(m_unbufferedFile, other.m_unbufferedFile);
|
|
AZStd::swap(m_nSectorSize, other.m_nSectorSize);
|
|
AZStd::swap(m_pReadTarget, other.m_pReadTarget);
|
|
#endif
|
|
AZStd::swap(m_nSize, other.m_nSize);
|
|
AZStd::swap(m_nCursor, other.m_nCursor);
|
|
AZStd::swap(m_szFilename, other.m_szFilename);
|
|
AZStd::swap(m_pInMemoryData, other.m_pInMemoryData);
|
|
m_fileIOBase = other.m_fileIOBase;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
FileEntryBase::FileEntryBase(const ZipFile::CDRFileHeader& header, const SExtraZipFileData& extra)
|
|
{
|
|
desc = header.desc;
|
|
nFileHeaderOffset = header.lLocalHeaderOffset;
|
|
|
|
nMethod = header.nMethod;
|
|
nNameOffset = 0; // we don't know yet
|
|
nLastModTime = header.nLastModTime;
|
|
nLastModDate = header.nLastModDate;
|
|
nNTFS_LastModifyTime = extra.nLastModifyTime;
|
|
|
|
// make an estimation (at least this offset should be there), but we don't actually know yet
|
|
nFileDataOffset = header.lLocalHeaderOffset + sizeof(ZipFile::LocalFileHeader) + header.nFileNameLength + header.nExtraFieldLength;
|
|
nEOFOffset = nFileDataOffset + header.desc.lSizeCompressed;
|
|
}
|
|
|
|
// Uncompresses raw (without wrapping) data that is compressed with method 8 (deflated) in the Zip file
|
|
// returns one of the Z_* errors (Z_OK upon success)
|
|
// This function just mimics the standard uncompress (with modification taken from unzReadCurrentFile)
|
|
// with 2 differences: there are no 16-bit checks, and
|
|
// it initializes the inflation to start without waiting for compression method byte, as this is the
|
|
// way it's stored into zip file
|
|
int ZipRawUncompress(void* pUncompressed, size_t* pDestSize, const void* pCompressed, size_t nSrcSize)
|
|
{
|
|
int nReturnCode = Z_OK;
|
|
|
|
//check first 4 bytes to see what compression codec was used
|
|
if (CompressionCodec::TestForZSTDMagic(pCompressed))
|
|
{
|
|
size_t result = ZSTD_decompress(pUncompressed, *pDestSize, pCompressed, nSrcSize);
|
|
|
|
if (ZSTD_isError(result))
|
|
{
|
|
AZ_Error("ZipDirStructures", false, "Error decompressing using zstd: %s", ZSTD_getErrorName(result));
|
|
nReturnCode = Z_BUF_ERROR;
|
|
}
|
|
else
|
|
{
|
|
*pDestSize = result;
|
|
}
|
|
return nReturnCode;
|
|
}
|
|
else if (CompressionCodec::TestForLZ4Magic(pCompressed))
|
|
{
|
|
size_t result;
|
|
LZ4F_decompressionContext_t dctx;
|
|
result = LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION);
|
|
if (LZ4F_isError(result))
|
|
{
|
|
AZ_Error("ZipDirStructures", false, "Error creating lz4 decompression context: %s", LZ4F_getErrorName(result));
|
|
return Z_BUF_ERROR;
|
|
}
|
|
|
|
size_t dstSize = *pDestSize;
|
|
size_t srcSize = nSrcSize;
|
|
result = LZ4F_decompress(dctx, pUncompressed, &dstSize, pCompressed, &srcSize, nullptr);
|
|
if (LZ4F_isError(result))
|
|
{
|
|
AZ_Error("ZipDirStructures", false, "Error decompressing using lz4: %s", LZ4F_getErrorName(result));
|
|
nReturnCode = Z_BUF_ERROR;
|
|
}
|
|
else
|
|
{
|
|
*pDestSize = dstSize;
|
|
}
|
|
|
|
size_t freeCode = LZ4F_freeDecompressionContext(dctx);
|
|
if (LZ4F_isError(freeCode))
|
|
{
|
|
//We are not changing the return code in this case, but it is good to record that releasing the
|
|
//decompression context failed.
|
|
AZ_Error("ZipDirStructures", false, "Error releasing lz4 decompression context: %s", LZ4F_getErrorName(freeCode));
|
|
}
|
|
|
|
return nReturnCode;
|
|
}
|
|
|
|
//fallback to zlib
|
|
ZipDirStructuresInternal::ZlibInflateElement_Impl(pCompressed, pUncompressed, nSrcSize, *pDestSize, pDestSize, &nReturnCode);
|
|
|
|
return nReturnCode;
|
|
}
|
|
|
|
// compresses the raw data into raw data. The buffer for compressed data itself with the heap passed. Uses method 8 (deflate)
|
|
// returns one of the Z_* errors (Z_OK upon success)
|
|
int ZipRawCompress(const void* pUncompressed, size_t* pDestSize, void* pCompressed, size_t nSrcSize, int nLevel)
|
|
{
|
|
z_stream stream;
|
|
int err;
|
|
|
|
stream.next_in = const_cast<Bytef*>(static_cast<const Bytef*>(pUncompressed));
|
|
stream.next_out = reinterpret_cast<Bytef*>(pCompressed);
|
|
|
|
|
|
stream.avail_in = static_cast<uInt>(nSrcSize);
|
|
stream.avail_out = static_cast<uInt>(*pDestSize);
|
|
|
|
AZ_Assert(AZ::AllocatorInstance<AZ::OSAllocator>::IsReady(), "OS Allocator must be ready in order to override zlib allocator");
|
|
stream.zalloc = &ZipDirStructuresInternal::ZlibAlloc;
|
|
stream.zfree = &ZipDirStructuresInternal::ZlibFree;
|
|
stream.opaque = &AZ::AllocatorInstance<AZ::OSAllocator>::Get();
|
|
|
|
err = deflateInit2(&stream, nLevel, Z_DEFLATED, -MAX_WBITS, 9, Z_DEFAULT_STRATEGY);
|
|
if (err != Z_OK)
|
|
{
|
|
return err;
|
|
}
|
|
|
|
err = deflate(&stream, Z_FINISH);
|
|
if (err != Z_STREAM_END)
|
|
{
|
|
deflateEnd(&stream);
|
|
return err == Z_OK ? Z_BUF_ERROR : err;
|
|
}
|
|
*pDestSize = stream.total_out;
|
|
|
|
err = deflateEnd(&stream);
|
|
return err;
|
|
}
|
|
|
|
int ZipRawCompressZSTD(const void* pUncompressed, size_t* pDestSize, void* pCompressed, size_t nSrcSize, [[maybe_unused]] int nLevel)
|
|
{
|
|
size_t result = ZSTD_compress(pCompressed, *pDestSize, pUncompressed, nSrcSize, 1);
|
|
|
|
int err = Z_OK;
|
|
|
|
if (ZSTD_isError(result))
|
|
{
|
|
AZ_Error("Error compressing using zstd:%s", false, ZSTD_getErrorName(result));
|
|
err = Z_BUF_ERROR;
|
|
}
|
|
else
|
|
{
|
|
*pDestSize = static_cast<size_t>(result);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int ZipRawCompressLZ4(const void* pUncompressed, size_t* pDestSize, void* pCompressed, size_t nSrcSize, [[maybe_unused]] int nLevel)
|
|
{
|
|
int returnCode = Z_OK;
|
|
const size_t compressedBufferMaxSize = aznumeric_caster(*pDestSize);
|
|
size_t lz4_code = LZ4F_compressFrame(pCompressed, compressedBufferMaxSize, pUncompressed, aznumeric_caster(nSrcSize), nullptr);
|
|
|
|
if (LZ4F_isError(lz4_code))
|
|
{
|
|
returnCode = Z_BUF_ERROR;
|
|
}
|
|
else
|
|
{
|
|
*pDestSize = aznumeric_caster(lz4_code);
|
|
}
|
|
|
|
return returnCode;
|
|
}
|
|
|
|
|
|
// finds the subdirectory entry by the name, using the names from the name pool
|
|
// assumes: all directories are sorted in alphabetical order.
|
|
// case-sensitive (must be lower-case if case-insensitive search in Win32 is performed)
|
|
DirEntry* DirHeader::FindSubdirEntry(AZStd::string_view szName)
|
|
{
|
|
if (this->numDirs)
|
|
{
|
|
const char* pNamePool = GetNamePool();
|
|
DirEntrySortPred pred(pNamePool);
|
|
DirEntry* pBegin = GetSubdirEntry(0);
|
|
DirEntry* pEnd = pBegin + this->numDirs;
|
|
DirEntry* pEntry = AZStd::lower_bound(pBegin, pEnd, szName, pred);
|
|
#if AZ_TRAIT_LEGACY_CRYPAK_UNIX_LIKE_FILE_SYSTEM
|
|
AZ::IO::PathView searchPath(szName, AZ::IO::WindowsPathSeparator);
|
|
AZ::IO::PathView entryPath(pEntry->GetName(pNamePool), AZ::IO::WindowsPathSeparator);
|
|
if (pEntry != pEnd && searchPath == entryPath)
|
|
#else
|
|
if (pEntry != pEnd && szName == pEntry->GetName(pNamePool))
|
|
#endif
|
|
{
|
|
return pEntry;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
// finds the file entry by the name, using the names from the name pool
|
|
// assumes: all directories are sorted in alphabetical order.
|
|
// case-sensitive (must be lower-case if case-insensitive search in Win32 is performed)
|
|
FileEntry* DirHeader::FindFileEntry(AZStd::string_view szName)
|
|
{
|
|
if (this->numFiles)
|
|
{
|
|
const char* pNamePool = GetNamePool();
|
|
DirEntrySortPred pred(pNamePool);
|
|
FileEntry* pBegin = GetFileEntry(0);
|
|
FileEntry* pEnd = pBegin + this->numFiles;
|
|
FileEntry* pEntry = AZStd::lower_bound(pBegin, pEnd, szName, pred);
|
|
#if AZ_TRAIT_LEGACY_CRYPAK_UNIX_LIKE_FILE_SYSTEM
|
|
AZ::IO::PathView searchPath(szName, AZ::IO::WindowsPathSeparator);
|
|
AZ::IO::PathView entryPath(pEntry->GetName(pNamePool), AZ::IO::WindowsPathSeparator);
|
|
if (pEntry != pEnd && searchPath == entryPath)
|
|
#else
|
|
if (pEntry != pEnd && szName == pEntry->GetName(pNamePool))
|
|
#endif
|
|
{
|
|
return pEntry;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
|
|
// tries to refresh the file entry from the given file (reads from there if needed)
|
|
// returns the error code if the operation was impossible to complete
|
|
ErrorEnum Refresh(CZipFile* f, FileEntryBase* pFileEntry)
|
|
{
|
|
if (pFileEntry->nFileDataOffset != pFileEntry->INVALID_DATA_OFFSET)
|
|
{
|
|
return ZD_ERROR_SUCCESS;
|
|
}
|
|
|
|
if (FSeek(f, pFileEntry->nFileHeaderOffset, SEEK_SET))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
// No validation in release or profile!
|
|
|
|
// read the local file header and the name (for validation) into the buffer
|
|
ZipFile::LocalFileHeader fileHeader;
|
|
//if (1 != fread (&fileHeader, sizeof(fileHeader), 1, f))
|
|
if (1 != FRead(f, &fileHeader, sizeof(fileHeader), 1))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
if (fileHeader.desc != pFileEntry->desc
|
|
|| fileHeader.nMethod != pFileEntry->nMethod)
|
|
{
|
|
AZ_Fatal("Pak", "ERROR: File header doesn't match previously cached file entry record (%s) \n fileheader desc=(%d,%d,%d), method=%d, \n fileentry desc=(%d,%d,%d),method=%d",
|
|
"Unknown" /*f->_tmpfname*/,
|
|
fileHeader.desc.lCRC32, fileHeader.desc.lSizeCompressed, fileHeader.desc.lSizeUncompressed,
|
|
fileHeader.nMethod,
|
|
pFileEntry->desc.lCRC32, pFileEntry->desc.lSizeCompressed, pFileEntry->desc.lSizeUncompressed,
|
|
pFileEntry->nMethod);
|
|
|
|
return ZD_ERROR_IO_FAILED;
|
|
//CryFatalError(szErrDesc);
|
|
//THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED,szErrDesc);
|
|
}
|
|
pFileEntry->nFileDataOffset = pFileEntry->nFileHeaderOffset + sizeof(ZipFile::LocalFileHeader) + fileHeader.nFileNameLength + fileHeader.nExtraFieldLength;
|
|
pFileEntry->nEOFOffset = pFileEntry->nFileDataOffset + pFileEntry->desc.lSizeCompressed;
|
|
|
|
|
|
return ZD_ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
// writes into the file local header (NOT including the name, only the header structure)
|
|
// the file must be opened both for reading and writing
|
|
ErrorEnum UpdateLocalHeader(AZ::IO::HandleType fileHandle, FileEntryBase* pFileEntry)
|
|
{
|
|
|
|
if (!AZ::IO::FileIOBase::GetDirectInstance()->Seek(fileHandle, pFileEntry->nFileHeaderOffset, AZ::IO::SeekType::SeekFromStart))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
ZipFile::LocalFileHeader header;
|
|
if (!AZ::IO::FileIOBase::GetDirectInstance()->Read(fileHandle, &header, sizeof(header), true))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
AZ_Assert(header.lSignature == header.SIGNATURE, "header signature %u does not match signature %u", header.lSignature, header.SIGNATURE);
|
|
|
|
header.desc.lCRC32 = pFileEntry->desc.lCRC32;
|
|
header.desc.lSizeCompressed = pFileEntry->desc.lSizeCompressed;
|
|
header.desc.lSizeUncompressed = pFileEntry->desc.lSizeUncompressed;
|
|
header.nMethod = pFileEntry->nMethod;
|
|
header.nFlags &= ~ZipFile::GPF_ENCRYPTED; // we don't support encrypted files
|
|
|
|
if (!AZ::IO::FileIOBase::GetDirectInstance()->Seek(fileHandle, pFileEntry->nFileHeaderOffset, AZ::IO::SeekType::SeekFromStart))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
if (!AZ::IO::FileIOBase::GetDirectInstance()->Write(fileHandle, &header, sizeof(header)))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
return ZD_ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
// writes into the file local header - without Extra data
|
|
// puts the new offset to the file data to the file entry
|
|
// in case of error can put INVALID_DATA_OFFSET into the data offset field of file entry
|
|
ErrorEnum WriteLocalHeader(AZ::IO::HandleType fileHandle, FileEntryBase* pFileEntry, AZStd::string_view szRelativePath)
|
|
{
|
|
size_t nFileNameLength = szRelativePath.size();
|
|
size_t nHeaderSize = sizeof(ZipFile::LocalFileHeader) + nFileNameLength;
|
|
|
|
pFileEntry->nFileDataOffset = aznumeric_cast<uint32_t>(pFileEntry->nFileHeaderOffset + nHeaderSize);
|
|
pFileEntry->nEOFOffset = pFileEntry->nFileDataOffset + pFileEntry->desc.lSizeCompressed;
|
|
|
|
if (!AZ::IO::FileIOBase::GetDirectInstance()->Seek(fileHandle, pFileEntry->nFileHeaderOffset, AZ::IO::SeekType::SeekFromStart))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
ZipFile::LocalFileHeader header;
|
|
header.lSignature = ZipFile::LocalFileHeader::SIGNATURE;
|
|
header.nVersionNeeded = 10;
|
|
header.nFlags = 0;
|
|
header.nMethod = pFileEntry->nMethod;
|
|
header.nLastModDate = pFileEntry->nLastModDate;
|
|
header.nLastModTime = pFileEntry->nLastModTime;
|
|
header.desc = pFileEntry->desc;
|
|
header.nFileNameLength = aznumeric_cast<uint16_t>(nFileNameLength);
|
|
header.nExtraFieldLength = 0;
|
|
|
|
if (!AZ::IO::FileIOBase::GetDirectInstance()->Write(fileHandle, &header, sizeof(header)))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
if (nFileNameLength > 0)
|
|
{
|
|
if (!AZ::IO::FileIOBase::GetDirectInstance()->Write(fileHandle, szRelativePath.data(), nFileNameLength))
|
|
{
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
}
|
|
|
|
return ZD_ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
// conversion routines for the date/time fields used in Zip
|
|
uint16_t DOSDate(tm* t)
|
|
{
|
|
return static_cast<uint16_t>(
|
|
((t->tm_year - 80) << 9)
|
|
| (t->tm_mon << 5)
|
|
| t->tm_mday);
|
|
}
|
|
|
|
uint16_t DOSTime(tm* t)
|
|
{
|
|
return static_cast<uint16_t>(
|
|
((t->tm_hour) << 11)
|
|
| ((t->tm_min) << 5)
|
|
| ((t->tm_sec) >> 1));
|
|
}
|
|
|
|
// sets the current time to modification time
|
|
// calculates CRC32 for the new data
|
|
void FileEntry::OnNewFileData(const void* pUncompressed, uint64_t nSize, uint64_t nCompressedSize, uint32_t nCompressionMethod, bool bContinuous)
|
|
{
|
|
time_t nTime;
|
|
time(&nTime);
|
|
tm t;
|
|
azlocaltime(&nTime, &t);
|
|
|
|
// While local time converts the month to a 0 to 11 interval...
|
|
// ...the pack file expects months from 1 to 12...
|
|
// Therefore, for correct date, we have to do t->tm_mon+=1;
|
|
t.tm_mon += 1;
|
|
|
|
this->nLastModTime = DOSTime(&t);
|
|
this->nLastModDate = DOSDate(&t);
|
|
|
|
if (!bContinuous)
|
|
{
|
|
this->desc.lSizeCompressed = aznumeric_cast<uint32_t>(nCompressedSize);
|
|
this->desc.lSizeUncompressed = aznumeric_cast<uint32_t>(nSize);
|
|
}
|
|
|
|
// we'll need CRC32 of the file to pack it
|
|
this->desc.lCRC32 = AZ::Crc32(pUncompressed, nSize);
|
|
|
|
this->nMethod = static_cast<uint16_t>(nCompressionMethod);
|
|
}
|
|
|
|
uint64_t FileEntry::GetModificationTime()
|
|
{
|
|
int year = (nLastModDate >> 9) + 1980;
|
|
int month = ((nLastModDate >> 5) & 0xF);
|
|
int day = (nLastModDate & 0x1F);
|
|
int hour = (nLastModTime >> 11);
|
|
int minute = (nLastModTime >> 5) & 0x3F;
|
|
int second = (nLastModTime << 1) & 0x3F;
|
|
|
|
constexpr int YearLengths[2] = { 365, 366 };
|
|
constexpr int MonthLengths[2][12] =
|
|
{
|
|
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
|
|
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
|
|
};
|
|
|
|
auto IsLeapYear = [](int Year)
|
|
{
|
|
return Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) ? 1 : 0;
|
|
};
|
|
|
|
// Add seconds overflow above a minute to the minute value
|
|
minute += second / 60;
|
|
second = second % 60;
|
|
// Add minute overflow above an hour to the hour value
|
|
hour += minute / 60;
|
|
minute = minute % 60;
|
|
// Add hour overflow above a day to the day value
|
|
day += hour / 24;
|
|
hour = hour % 24;
|
|
// Add days overflow above the length of the current month to a new month
|
|
while (day > MonthLengths[IsLeapYear(year)][(month % 12) - 1])
|
|
{
|
|
day -= MonthLengths[IsLeapYear(year)][(month % 12) - 1];
|
|
++month;
|
|
}
|
|
// Add months overflow above the a year to the year value
|
|
year += month / 12;
|
|
month = month % 12;
|
|
|
|
uint64_t filetime{};
|
|
constexpr int EpochYear = 1601;
|
|
for (int currYear = EpochYear; currYear < year; ++currYear)
|
|
{
|
|
filetime += YearLengths[IsLeapYear(currYear)];
|
|
}
|
|
for (int currMonth = 1; currMonth < month; ++currMonth)
|
|
{
|
|
filetime += MonthLengths[IsLeapYear(year)][currMonth - 1];
|
|
}
|
|
filetime += day - 1;
|
|
// Convert days to second
|
|
filetime *= 24 * 60 * 60;
|
|
filetime += hour * (60 * 60) + minute * 60 + second;
|
|
|
|
AZStd::chrono::seconds filetimeSeconds(filetime);
|
|
|
|
// Returns time in units of second * 10^-7
|
|
using ModTimeUnits = AZStd::chrono::duration<AZStd::sys_time_t, AZStd::ratio<10000000>>;
|
|
return AZStd::chrono::duration_cast<ModTimeUnits>(filetimeSeconds).count();
|
|
}
|
|
|
|
const char* DOSTimeCStr(uint16_t nTime)
|
|
{
|
|
static char szBuf[16];
|
|
azsnprintf(szBuf, AZ_ARRAY_SIZE(szBuf), "%02d:%02d.%02d", (nTime >> 11), ((nTime & ((1 << 11) - 1)) >> 5), ((nTime & ((1 << 5) - 1)) << 1));
|
|
return szBuf;
|
|
}
|
|
|
|
const char* DOSDateCStr(uint16_t nTime)
|
|
{
|
|
static char szBuf[32];
|
|
azsnprintf(szBuf, AZ_ARRAY_SIZE(szBuf), "%02d.%02d.%04d", (nTime & 0x1F), (nTime >> 5) & 0xF, (nTime >> 9) + 1980);
|
|
return szBuf;
|
|
}
|
|
|
|
const char* Error::getError()
|
|
{
|
|
switch (this->nError)
|
|
{
|
|
#define DECLARE_ERROR(x) case ZD_ERROR_##x: \
|
|
return #x;
|
|
DECLARE_ERROR(SUCCESS);
|
|
DECLARE_ERROR(IO_FAILED);
|
|
DECLARE_ERROR(UNEXPECTED);
|
|
DECLARE_ERROR(UNSUPPORTED);
|
|
DECLARE_ERROR(INVALID_SIGNATURE);
|
|
DECLARE_ERROR(ZIP_FILE_IS_CORRUPT);
|
|
DECLARE_ERROR(DATA_IS_CORRUPT);
|
|
DECLARE_ERROR(NO_CDR);
|
|
DECLARE_ERROR(CDR_IS_CORRUPT);
|
|
DECLARE_ERROR(NO_MEMORY);
|
|
DECLARE_ERROR(VALIDATION_FAILED);
|
|
DECLARE_ERROR(CRC32_CHECK);
|
|
DECLARE_ERROR(ZLIB_FAILED);
|
|
DECLARE_ERROR(ZLIB_CORRUPTED_DATA);
|
|
DECLARE_ERROR(ZLIB_NO_MEMORY);
|
|
DECLARE_ERROR(CORRUPTED_DATA);
|
|
DECLARE_ERROR(INVALID_CALL);
|
|
DECLARE_ERROR(NOT_IMPLEMENTED);
|
|
DECLARE_ERROR(FILE_NOT_FOUND);
|
|
DECLARE_ERROR(DIR_NOT_FOUND);
|
|
DECLARE_ERROR(NAME_TOO_LONG);
|
|
DECLARE_ERROR(INVALID_PATH);
|
|
DECLARE_ERROR(FILE_ALREADY_EXISTS);
|
|
#undef DECLARE_ERROR
|
|
default:
|
|
return "Unknown ZD_ERROR code";
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
int64_t FSeek(CZipFile* file, int64_t origin, int command)
|
|
{
|
|
if (file->IsInMemory())
|
|
{
|
|
int64_t retCode = -1;
|
|
int64_t newPos;
|
|
switch (command)
|
|
{
|
|
case SEEK_SET:
|
|
newPos = origin;
|
|
if (newPos >= 0 && newPos <= file->m_nSize)
|
|
{
|
|
file->m_nCursor = newPos;
|
|
retCode = 0;
|
|
}
|
|
break;
|
|
case SEEK_CUR:
|
|
newPos = origin + file->m_nCursor;
|
|
if (newPos >= 0 && newPos <= file->m_nSize)
|
|
{
|
|
file->m_nCursor = newPos;
|
|
retCode = 0;
|
|
}
|
|
break;
|
|
case SEEK_END:
|
|
newPos = file->m_nSize - origin;
|
|
if (newPos >= 0 && newPos <= file->m_nSize)
|
|
{
|
|
file->m_nCursor = newPos;
|
|
retCode = 0;
|
|
}
|
|
break;
|
|
default:
|
|
// Not valid disk operation!
|
|
AZ_Assert(false, "Invalid seek mode %d supplied to FSeek", command);
|
|
}
|
|
return retCode;
|
|
}
|
|
else
|
|
{
|
|
if (AZ::IO::FileIOBase::GetDirectInstance()->Seek(file->m_fileHandle, origin, AZ::IO::GetSeekTypeFromFSeekMode(command)))
|
|
{
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int64_t FRead(CZipFile* file, void* data, size_t elementSize, size_t count)
|
|
{
|
|
if (file->IsInMemory())
|
|
{
|
|
int64_t nRead = count * elementSize;
|
|
int64_t nCanBeRead = file->m_nSize - file->m_nCursor;
|
|
if (nRead > nCanBeRead)
|
|
{
|
|
nRead = nCanBeRead;
|
|
}
|
|
|
|
if (file->m_nCursor + nRead <= aznumeric_cast<int64_t>(file->m_pInMemoryData->m_size))
|
|
{
|
|
memcpy(data, reinterpret_cast<uint8_t*>(&file->m_pInMemoryData->m_address[file->m_nCursor]), nRead);
|
|
}
|
|
file->m_nCursor += nRead;
|
|
return nRead / elementSize;
|
|
}
|
|
else
|
|
{
|
|
AZ::IO::HandleType fileHandle = file->m_fileHandle;
|
|
AZ::u64 bytesRead = 0;
|
|
AZ::IO::FileIOBase::GetDirectInstance()->Read(fileHandle, data, elementSize * count, false, &bytesRead);
|
|
return bytesRead / elementSize;
|
|
}
|
|
}
|
|
|
|
int64_t FTell(CZipFile* file)
|
|
{
|
|
if (file->IsInMemory())
|
|
{
|
|
return file->m_nCursor;
|
|
}
|
|
else
|
|
{
|
|
AZ::u64 tellResult = 0;
|
|
AZ::IO::FileIOBase::GetDirectInstance()->Tell(file->m_fileHandle, tellResult);
|
|
return tellResult;
|
|
}
|
|
}
|
|
|
|
int FEof(CZipFile* zipFile)
|
|
{
|
|
if (zipFile->IsInMemory())
|
|
{
|
|
return zipFile->m_nCursor == zipFile->m_nSize;
|
|
}
|
|
else
|
|
{
|
|
return AZ::IO::FileIOBase::GetDirectInstance()->Eof(zipFile->m_fileHandle);
|
|
}
|
|
}
|
|
}
|