SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
ZIP.cpp
Go to the documentation of this file.
1#include <vpkpp/format/ZIP.h>
2
3#include <cstring>
4#include <ctime>
5#include <filesystem>
6
7#include <mz.h>
8#include <mz_os.h>
9#include <mz_strm.h>
10#include <mz_strm_os.h>
11#include <mz_zip.h>
12#include <mz_zip_rw.h>
13
14#include <FileStream.h>
16#include <sourcepp/String.h>
17
18using namespace sourcepp;
19using namespace vpkpp;
20
21ZIP::ZIP(const std::string& fullFilePath_)
22 : PackFile(fullFilePath_)
23 , tempZIPPath((std::filesystem::temp_directory_path() / (string::generateUUIDv4() + ".zip")).string()) {}
24
26 this->closeZIP();
27}
28
29std::unique_ptr<PackFile> ZIP::create(const std::string& path) {
30 {
31 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
32
33 // I just created an empty zip with Windows and pasted the result because minizip-ng wasn't behaving
34 stream
35 .write('P')
36 .write('K')
37 .write<uint8_t>(5)
38 .write<uint8_t>(6)
39 .write(std::array<uint16_t, 4>{})
40 .write(std::array<uint32_t, 2>{})
41 .write<uint16_t>(0);
42 }
43 return ZIP::open(path);
44}
45
46std::unique_ptr<PackFile> ZIP::open(const std::string& path, const EntryCallback& callback) {
47 if (!std::filesystem::exists(path)) {
48 // File does not exist
49 return nullptr;
50 }
51
52 auto* zip = new ZIP{path};
53 auto packFile = std::unique_ptr<PackFile>(zip);
54
55 if (!zip->openZIP(zip->fullFilePath)) {
56 return nullptr;
57 }
58
59 for (auto code = mz_zip_goto_first_entry(zip->zipHandle); code == MZ_OK; code = mz_zip_goto_next_entry(zip->zipHandle)) {
60 if (mz_zip_entry_is_dir(zip->zipHandle) == MZ_OK) {
61 continue;
62 }
63
64 mz_zip_file* fileInfo = nullptr;
65 if (mz_zip_entry_get_info(zip->zipHandle, &fileInfo)) {
66 return nullptr;
67 }
68
69 auto entryPath = zip->cleanEntryPath(fileInfo->filename);
70
71 Entry entry = createNewEntry();
72 entry.flags = fileInfo->compression_method << 16;
73 entry.length = fileInfo->uncompressed_size;
74 entry.compressedLength = fileInfo->compressed_size;
75 entry.crc32 = fileInfo->crc;
76
77 zip->entries.insert(entryPath, entry);
78
79 if (callback) {
80 callback(entryPath, entry);
81 }
82 }
83
84 return packFile;
85}
86
87std::vector<std::string> ZIP::verifyEntryChecksums() const {
88 return this->verifyEntryChecksumsUsingCRC32();
89}
90
91std::optional<std::vector<std::byte>> ZIP::readEntry(const std::string& path_) const {
92 const auto path = this->cleanEntryPath(path_);
93 const auto entry = this->findEntry(path);
94 if (!entry) {
95 return std::nullopt;
96 }
97 if (entry->unbaked) {
98 return readUnbakedEntry(*entry);
99 }
100
101 // It's baked into the file on disk
102 if (!this->streamOpen || !this->zipOpen) {
103 return std::nullopt;
104 }
105 if (mz_zip_locate_entry(this->zipHandle, path.c_str(), !this->isCaseSensitive()) != MZ_OK) {
106 return std::nullopt;
107 }
108 if (mz_zip_entry_read_open(this->zipHandle, 0, nullptr) != MZ_OK) {
109 return std::nullopt;
110 }
111 std::vector<std::byte> out;
112 out.resize(entry->length);
113 mz_zip_entry_read(this->zipHandle, out.data(), static_cast<int>(entry->length));
114 mz_zip_entry_close(this->zipHandle);
115 return out;
116}
117
118void ZIP::addEntryInternal(Entry& entry, const std::string& path, std::vector<std::byte>& buffer, EntryOptions options) {
120 entry.length = buffer.size();
121 entry.compressedLength = 0;
122 entry.crc32 = crypto::computeCRC32(buffer);
123}
124
125bool ZIP::bake(const std::string& outputDir_, BakeOptions options, const EntryCallback& callback) {
126 // Get the proper file output folder
127 const std::string outputDir = this->getBakeOutputDir(outputDir_);
128 const std::string outputPath = outputDir + '/' + this->getFilename();
129
130 // Use temp folder so we can read from the current ZIP
131 if (!this->bakeTempZip(this->tempZIPPath, options, callback)) {
132 return false;
133 }
134 this->mergeUnbakedEntries();
135
136 // Close our ZIP and reopen it
137 this->closeZIP();
138 std::filesystem::rename(this->tempZIPPath, outputPath);
139 if (!this->openZIP(outputPath)) {
140 return false;
141 }
142 PackFile::setFullFilePath(outputDir);
143 return true;
144}
145
147 using enum Attribute;
148 return LENGTH | CRC32;
149}
150
151EntryCompressionType ZIP::getEntryCompressionType(const std::string& path_) const {
152 const auto path = this->cleanEntryPath(path_);
153 if (this->entries.count(path)) {
154 return static_cast<EntryCompressionType>(this->entries.at(path).flags >> 16);
155 }
157}
158
159void ZIP::setEntryCompressionType(const std::string& path_, EntryCompressionType type) {
160 const auto path = this->cleanEntryPath(path_);
161 if (this->entries.count(path)) {
162 this->entries.at(path).flags = (static_cast<uint32_t>(type) << 16) | (this->entries.at(path).flags & 0xffff);
163 }
164}
165
166int16_t ZIP::getEntryCompressionStrength(const std::string& path_) const {
167 const auto path = this->cleanEntryPath(path_);
168 if (this->entries.count(path)) {
169 return static_cast<int16_t>(this->entries.at(path).flags & 0xffff);
170 }
171 return 0;
172}
173
174void ZIP::setEntryCompressionStrength(const std::string& path_, int16_t strength) {
175 const auto path = this->cleanEntryPath(path_);
176 if (this->entries.count(path)) {
177 this->entries.at(path).flags = (this->entries.at(path).flags & 0xffff0000) | strength;
178 }
179}
180
181bool ZIP::bakeTempZip(const std::string& writeZipPath, BakeOptions options, const EntryCallback& callback) const {
182 void* writeStreamHandle = mz_stream_os_create();
183 if (mz_stream_open(writeStreamHandle, writeZipPath.c_str(), MZ_OPEN_MODE_CREATE | MZ_OPEN_MODE_WRITE)) {
184 return false;
185 }
186
187 void* writeZipHandle = mz_zip_writer_create();
188 if (mz_zip_writer_open(writeZipHandle, writeStreamHandle, 0)) {
189 return false;
190 }
191
192 mz_zip_set_version_madeby(writeZipHandle, MZ_VERSION_MADEBY);
193
194 const auto time = std::time(nullptr);
195
196 const bool overrideCompression = options.zip_compressionTypeOverride != EntryCompressionType::NO_OVERRIDE;
197
198 bool noneFailed = true;
199 this->runForAllEntries([this, &options, &callback, writeZipHandle, time, overrideCompression, &noneFailed](const std::string& path, const Entry& entry) {
200 auto binData = this->readEntry(path);
201 if (!binData) {
202 return;
203 }
204
205 if (overrideCompression) {
206 mz_zip_writer_set_compress_method(writeZipHandle, static_cast<uint16_t>(options.zip_compressionTypeOverride));
207 mz_zip_writer_set_compress_level(writeZipHandle, options.zip_compressionStrength);
208 } else {
209 mz_zip_writer_set_compress_method(writeZipHandle, entry.flags >> 16);
210 mz_zip_writer_set_compress_level(writeZipHandle, (entry.flags & 0xffff) > 0 ? static_cast<int16_t>(entry.flags & 0xffff) : options.zip_compressionStrength);
211 }
212
213 mz_zip_entry fileInfo{};
214 fileInfo.filename = path.c_str();
215 fileInfo.filename_size = path.length();
216 fileInfo.version_madeby = MZ_VERSION_MADEBY;
217 fileInfo.creation_date = time;
218 fileInfo.modified_date = time;
219 fileInfo.compression_method = overrideCompression ? static_cast<uint16_t>(options.zip_compressionTypeOverride) : entry.flags >> 16;
220 fileInfo.flag = MZ_ZIP_FLAG_DATA_DESCRIPTOR | MZ_ZIP_FLAG_UTF8;
221 fileInfo.uncompressed_size = static_cast<int64_t>(entry.length);
222 fileInfo.compressed_size = static_cast<int64_t>(entry.compressedLength);
223 fileInfo.crc = entry.crc32;
224 if (mz_zip_writer_add_buffer(writeZipHandle, binData->data(), static_cast<int>(binData->size()), &fileInfo)) {
225 noneFailed = false;
226 return;
227 }
228
229 if (callback) {
230 callback(path, entry);
231 }
232 });
233
234 if (mz_zip_writer_close(writeZipHandle)) {
235 return false;
236 }
237 mz_zip_writer_delete(&writeZipHandle);
238
239 if (mz_stream_close(writeStreamHandle)) {
240 return false;
241 }
242 mz_stream_delete(&writeStreamHandle);
243
244 return noneFailed;
245}
246
247bool ZIP::openZIP(std::string_view path) {
248 this->streamHandle = mz_stream_os_create();
249 if (mz_stream_open(this->streamHandle, path.data(), MZ_OPEN_MODE_READ) != MZ_OK) {
250 mz_stream_delete(&this->streamHandle);
251 return false;
252 }
253 this->streamOpen = true;
254
255 this->zipHandle = mz_zip_create();
256 if (mz_zip_open(this->zipHandle, this->streamHandle, MZ_OPEN_MODE_READ) != MZ_OK) {
257 mz_zip_delete(&this->zipHandle);
258 return false; // No need to delete the stream, it's done when we destruct
259 }
260 this->zipOpen = true;
261
262 return true;
263}
264
266 if (this->zipOpen) {
267 mz_zip_close(this->zipHandle);
268 mz_zip_delete(&this->zipHandle);
269 this->zipOpen = false;
270 }
271 if (this->streamOpen) {
272 mz_stream_close(this->streamHandle);
273 mz_stream_delete(&this->streamHandle);
274 this->streamOpen = false;
275 }
276}
This class represents the metadata that a file has inside a PackFile.
Definition: Entry.h:14
uint32_t flags
Format-specific flags (PCK: File flags, VPK: Internal parser state, ZIP: Compression method / strengt...
Definition: Entry.h:19
uint64_t compressedLength
If the format supports compression, this is the compressed length.
Definition: Entry.h:30
uint32_t crc32
CRC32 checksum - 0 if unused.
Definition: Entry.h:40
uint64_t length
Length in bytes (in formats with compression, this is the uncompressed length)
Definition: Entry.h:26
EntryCallbackBase< void > EntryCallback
Definition: PackFile.h:30
void mergeUnbakedEntries()
Definition: PackFile.cpp:656
std::optional< Entry > findEntry(const std::string &path_, bool includeUnbaked=true) const
Try to find an entry given the file path.
Definition: PackFile.cpp:163
EntryTrie entries
Definition: PackFile.h:220
std::vector< std::string > verifyEntryChecksumsUsingCRC32() const
Definition: PackFile.cpp:626
std::string getFilename() const
/home/user/pak01_dir.vpk -> pak01_dir.vpk
Definition: PackFile.cpp:591
std::string getBakeOutputDir(const std::string &outputDir) const
Definition: PackFile.cpp:640
void runForAllEntries(const EntryCallback &operation, bool includeUnbaked=true) const
Run a callback for each entry in the pack file.
Definition: PackFile.cpp:499
void setFullFilePath(const std::string &outputDir)
Definition: PackFile.cpp:672
std::string cleanEntryPath(const std::string &path) const
Definition: PackFile.cpp:677
static Entry createNewEntry()
Definition: PackFile.cpp:686
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
Definition: PackFile.cpp:690
Definition: ZIP.h:15
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) override
Definition: ZIP.cpp:118
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition: ZIP.cpp:146
ZIP(const std::string &fullFilePath_)
Definition: ZIP.cpp:21
const std::string tempZIPPath
Definition: ZIP.h:66
void setEntryCompressionType(const std::string &path_, EntryCompressionType type)
Definition: ZIP.cpp:159
void setEntryCompressionStrength(const std::string &path_, int16_t strength)
Definition: ZIP.cpp:174
void closeZIP()
Definition: ZIP.cpp:265
void * streamHandle
Definition: ZIP.h:68
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open a ZIP file.
Definition: ZIP.cpp:46
std::vector< std::string > verifyEntryChecksums() const override
Verify the checksums of each file, if a file fails the check its path will be added to the vector If ...
Definition: ZIP.cpp:87
int16_t getEntryCompressionStrength(const std::string &path_) const
Definition: ZIP.cpp:166
bool streamOpen
Definition: ZIP.h:69
bool openZIP(std::string_view path)
Definition: ZIP.cpp:247
bool bake(const std::string &outputDir_, BakeOptions options, const EntryCallback &callback) override
If output folder is an empty string, it will overwrite the original.
Definition: ZIP.cpp:125
bool zipOpen
Definition: ZIP.h:72
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
Definition: ZIP.cpp:91
void * zipHandle
Definition: ZIP.h:71
~ZIP() override
Definition: ZIP.cpp:25
bool bakeTempZip(const std::string &writeZipPath, BakeOptions options, const EntryCallback &callback) const
Definition: ZIP.cpp:181
static std::unique_ptr< PackFile > create(const std::string &path)
Create a ZIP file.
Definition: ZIP.cpp:29
EntryCompressionType getEntryCompressionType(const std::string &path_) const
Definition: ZIP.cpp:151
uint32_t computeCRC32(std::span< const std::byte > buffer)
Definition: CRC32.cpp:7
Definition: LZMA.h:11
Definition: Attribute.h:5
Attribute
Definition: Attribute.h:7
EntryCompressionType
Definition: Options.h:7
EntryCompressionType zip_compressionTypeOverride
BSP/ZIP - Override compression type of all stored entries.
Definition: Options.h:21
int16_t zip_compressionStrength
BSP/VPK/ZIP - Compression strength.
Definition: Options.h:24
EntryCompressionType zip_compressionType
BSP/ZIP - The compression format.
Definition: Options.h:35
int16_t zip_compressionStrength
BSP/ZIP - The compression strength.
Definition: Options.h:38