2019-12-26 15:32:37 -05:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
2019-02-01 09:28:28 -05:00
|
|
|
|
|
|
|
|
#include "FileUtilities/ZipArchiveWriter.h"
|
|
|
|
|
|
|
|
|
|
#if WITH_ENGINE
|
|
|
|
|
|
2024-05-08 12:51:51 -04:00
|
|
|
#include "Containers/Utf8String.h"
|
2022-11-28 18:24:58 -05:00
|
|
|
#include "GenericPlatform/GenericPlatformFile.h"
|
|
|
|
|
#include "ZipArchivePrivate.h"
|
|
|
|
|
|
|
|
|
|
DEFINE_LOG_CATEGORY(LogZipArchive);
|
|
|
|
|
|
2019-02-01 09:28:28 -05:00
|
|
|
FZipArchiveWriter::FZipArchiveWriter(IFileHandle* InFile)
|
|
|
|
|
: File(InFile)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FZipArchiveWriter::~FZipArchiveWriter()
|
|
|
|
|
{
|
|
|
|
|
// Zip File Format Specification:
|
|
|
|
|
// https://www.loc.gov/preservation/digital/formats/digformatspecs/APPNOTE%2820120901%29_Version_6.3.3.txt
|
|
|
|
|
|
2022-11-28 18:24:58 -05:00
|
|
|
UE_LOG(LogZipArchive, Display, TEXT("Closing zip file with %d entries."), Files.Num());
|
2019-02-01 09:28:28 -05:00
|
|
|
|
|
|
|
|
// Write the file directory
|
|
|
|
|
uint64 DirStartOffset = Tell();
|
|
|
|
|
for (FFileEntry& Entry : Files)
|
|
|
|
|
{
|
2024-05-08 12:51:51 -04:00
|
|
|
// Central directory File header: (from specification linked above)
|
2019-02-01 09:28:28 -05:00
|
|
|
const static uint8 Footer[] =
|
|
|
|
|
{
|
2024-05-08 12:51:51 -04:00
|
|
|
0x50, 0x4b, 0x01, 0x02, // Central file header signature
|
|
|
|
|
0x3f, 0x00, // Version made by (MS-DOS - v6.3)
|
|
|
|
|
0x2d, 0x00, // Version needed to extract (MS-DOS - v4.5)
|
|
|
|
|
0x00, 0x08, // General purpose bit flag (Language encoding flag = 1)
|
|
|
|
|
0x00, 0x00 // Compression method (none)
|
2019-02-01 09:28:28 -05:00
|
|
|
};
|
|
|
|
|
Write((void*)Footer, sizeof(Footer));
|
|
|
|
|
Write(Entry.Time);
|
|
|
|
|
Write(Entry.Crc32);
|
|
|
|
|
|
2024-05-08 12:51:51 -04:00
|
|
|
// Compressed and Uncompressed size - unused.
|
2019-02-01 09:28:28 -05:00
|
|
|
Write((uint64)0xffffffffffffffff);
|
2024-05-08 12:51:51 -04:00
|
|
|
|
|
|
|
|
FUtf8String UTF8Filename = *Entry.Filename;
|
|
|
|
|
Write((uint16)UTF8Filename.Len());
|
2019-02-01 09:28:28 -05:00
|
|
|
const static uint8 Fields[] =
|
|
|
|
|
{
|
2024-06-24 11:37:25 -04:00
|
|
|
0x1c, 0x00, // Length of extra fields (Zip64 Extended Information)
|
2024-05-08 12:51:51 -04:00
|
|
|
0x00, 0x00, // File comment length
|
|
|
|
|
0x00, 0x00, // Disk number start
|
|
|
|
|
0x00, 0x00, // Internal file attributes
|
|
|
|
|
0x20, 0x00, 0x00, 0x00, // External file attributes
|
2024-06-24 11:37:25 -04:00
|
|
|
0xff, 0xff, 0xff, 0xff // Relative offset of local header (set to 0xff as it is provided in the Zip64 block)
|
2019-02-01 09:28:28 -05:00
|
|
|
};
|
|
|
|
|
Write((void*)Fields, sizeof(Fields));
|
2024-05-08 12:51:51 -04:00
|
|
|
Write((void*)GetData(UTF8Filename), UTF8Filename.Len());
|
2019-02-01 09:28:28 -05:00
|
|
|
|
2024-05-08 12:51:51 -04:00
|
|
|
// Zip64 Extended Information block
|
2024-06-24 11:37:25 -04:00
|
|
|
Write((uint16)0x01); // Tag the Zip64
|
|
|
|
|
Write((uint16)0x18); // Size of this block (24 bytes)
|
2019-02-01 09:28:28 -05:00
|
|
|
|
2024-06-24 11:37:25 -04:00
|
|
|
Write((uint64)Entry.Length); // Uncompressed size
|
|
|
|
|
Write((uint64)Entry.Length); // Compressed Size
|
|
|
|
|
Write((uint64)Entry.Offset); // Offset of local header record
|
2019-02-01 09:28:28 -05:00
|
|
|
|
|
|
|
|
Flush();
|
|
|
|
|
}
|
|
|
|
|
uint64 DirEndOffset = Tell();
|
|
|
|
|
|
|
|
|
|
uint64 DirectorySizeInBytes = DirEndOffset - DirStartOffset;
|
|
|
|
|
|
|
|
|
|
// Write ZIP64 end of central directory record
|
|
|
|
|
const static uint8 Record[] =
|
|
|
|
|
{
|
2024-06-24 11:37:25 -04:00
|
|
|
0x50, 0x4b, 0x06, 0x06, // Zip64 end of central directory record signature
|
|
|
|
|
0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Size of the end of central directory record
|
|
|
|
|
0x2d, 0x00, // Version Creator (MS-DOS - v4.5)
|
|
|
|
|
0x2d, 0x00, // Version Viewer (MS-DOS - v4.5)
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, // Disk Number
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, // Disk with central directory
|
2019-02-01 09:28:28 -05:00
|
|
|
};
|
|
|
|
|
Write((void*)Record, sizeof(Record));
|
2024-06-24 11:37:25 -04:00
|
|
|
Write((uint64)Files.Num()); // Number of central directory records
|
|
|
|
|
Write((uint64)Files.Num()); // Total number of records
|
|
|
|
|
Write(DirectorySizeInBytes); // Size of central directory
|
|
|
|
|
Write(DirStartOffset); // Offset of central directory
|
2019-02-01 09:28:28 -05:00
|
|
|
|
|
|
|
|
// Write ZIP64 end of central directory locator
|
|
|
|
|
const static uint8 Locator[] =
|
|
|
|
|
{
|
2024-06-24 11:37:25 -04:00
|
|
|
0x50, 0x4b, 0x06, 0x07, // Zip64 end of central directory locator signature
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, // Disk with end of central directory record
|
2019-02-01 09:28:28 -05:00
|
|
|
};
|
|
|
|
|
Write((void*)Locator, sizeof(Locator));
|
2024-06-24 11:37:25 -04:00
|
|
|
Write(DirEndOffset); // Offset of end of central directory
|
|
|
|
|
Write((uint32)0x01); // Total number of disks
|
2019-02-01 09:28:28 -05:00
|
|
|
|
|
|
|
|
// Write normal end of central directory record
|
|
|
|
|
const static uint8 EndRecord[] =
|
|
|
|
|
{
|
2024-06-24 11:37:25 -04:00
|
|
|
0x50, 0x4b, 0x05, 0x06, // End of central directory record signature
|
|
|
|
|
0x00, 0x00, // Number of this disk
|
|
|
|
|
0x00, 0x00, // Number of the disk with the start of the central directory
|
|
|
|
|
0xff, 0xff, // Total number of entries in the central directory on this disk
|
|
|
|
|
0xff, 0xff, // Total number of entries
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, // Size of central directory
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, // Offset of central directory
|
|
|
|
|
0x00, 0x00 // comment length
|
2019-02-01 09:28:28 -05:00
|
|
|
};
|
|
|
|
|
Write((void*)EndRecord, sizeof(EndRecord));
|
|
|
|
|
|
|
|
|
|
Flush();
|
|
|
|
|
|
|
|
|
|
if (File)
|
|
|
|
|
{
|
|
|
|
|
// Close the file
|
|
|
|
|
delete File;
|
|
|
|
|
File = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 18:11:47 -04:00
|
|
|
void FZipArchiveWriter::AddFile(const FString& Filename, TConstArrayView<uint8> Data, const FDateTime& Timestamp)
|
2019-02-01 09:28:28 -05:00
|
|
|
{
|
2023-07-21 10:47:01 -04:00
|
|
|
if (!ensureMsgf(!Filename.IsEmpty(), TEXT("Failed to write data to zip file; filename is empty.")))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-02-01 09:28:28 -05:00
|
|
|
uint32 Crc = FCrc::MemCrc32(Data.GetData(), Data.Num());
|
|
|
|
|
|
|
|
|
|
// Convert the date-time to a zip file timestamp (2-second resolution).
|
|
|
|
|
uint32 ZipTime =
|
|
|
|
|
(Timestamp.GetSecond() / 2) |
|
|
|
|
|
(Timestamp.GetMinute() << 5) |
|
|
|
|
|
(Timestamp.GetHour() << 11) |
|
|
|
|
|
(Timestamp.GetDay() << 16) |
|
|
|
|
|
(Timestamp.GetMonth() << 21) |
|
|
|
|
|
((Timestamp.GetYear() - 1980) << 25);
|
|
|
|
|
|
|
|
|
|
uint64 FileOffset = Tell();
|
|
|
|
|
|
|
|
|
|
FFileEntry* Entry = new (Files) FFileEntry(Filename, Crc, Data.Num(), FileOffset, ZipTime);
|
|
|
|
|
|
2024-05-08 12:51:51 -04:00
|
|
|
// Local File Header
|
2019-02-01 09:28:28 -05:00
|
|
|
static const uint8 Header[] =
|
|
|
|
|
{
|
2024-05-08 12:51:51 -04:00
|
|
|
0x50, 0x4b, 0x03, 0x04, // Local file header signature
|
|
|
|
|
0x2d, 0x00, // Version needed to extract (MS DOS - v4.5)
|
|
|
|
|
0x00, 0x08, // General purpose bit flag (Language encoding flag = 1)
|
|
|
|
|
0x00, 0x00 // Compression method (none)
|
2019-02-01 09:28:28 -05:00
|
|
|
};
|
|
|
|
|
Write((void*)Header, sizeof(Header));
|
|
|
|
|
Write(ZipTime);
|
|
|
|
|
Write(Crc);
|
2024-05-08 12:51:51 -04:00
|
|
|
|
2024-06-24 11:37:25 -04:00
|
|
|
// Compressed and Uncompressed size - (set to 0xff as it is provided by the Zip64 block).
|
2019-02-01 09:28:28 -05:00
|
|
|
Write((uint64)0xffffffffffffffff);
|
|
|
|
|
|
2024-05-08 12:51:51 -04:00
|
|
|
FUtf8String UTF8Filename = *Entry->Filename;
|
|
|
|
|
Write((uint16)UTF8Filename.Len());
|
2024-06-24 11:37:25 -04:00
|
|
|
Write((uint16)0x14); // Length of extra fields (Zip64 Extended Information)
|
2024-05-08 12:51:51 -04:00
|
|
|
Write((void*)GetData(UTF8Filename), UTF8Filename.Len());
|
2019-02-01 09:28:28 -05:00
|
|
|
|
2024-05-08 12:51:51 -04:00
|
|
|
// Zip64 Extended Information block
|
2024-06-24 11:37:25 -04:00
|
|
|
Write((uint16)0x01); // Zip64 tag
|
|
|
|
|
Write((uint16)0x10); // Size of this block (16 bytes)
|
|
|
|
|
Write((uint64)Data.Num()); // Uncompressed size
|
|
|
|
|
Write((uint64)Data.Num()); // Compressed size
|
2019-02-01 09:28:28 -05:00
|
|
|
|
|
|
|
|
Write((void*)Data.GetData(), Data.Num());
|
|
|
|
|
|
|
|
|
|
Flush();
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 18:11:47 -04:00
|
|
|
void FZipArchiveWriter::AddFile(const FString& Filename, const TArray<uint8>& Data, const FDateTime& Timestamp)
|
|
|
|
|
{
|
|
|
|
|
AddFile(Filename, TConstArrayView<uint8>(Data), Timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-01 09:28:28 -05:00
|
|
|
void FZipArchiveWriter::Flush()
|
|
|
|
|
{
|
|
|
|
|
if (Buffer.Num())
|
|
|
|
|
{
|
|
|
|
|
if (File && !File->Write(Buffer.GetData(), Buffer.Num()))
|
|
|
|
|
{
|
2022-11-28 18:24:58 -05:00
|
|
|
UE_LOG(LogZipArchive, Error, TEXT("Failed to write to zip file. Zip file writing aborted."));
|
2019-02-01 09:28:28 -05:00
|
|
|
delete File;
|
|
|
|
|
File = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Buffer.Reset(Buffer.Num());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|