WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
ZipExtract.cpp
Go to the documentation of this file.
1#include "ZipExtract.h"
2#include "Logger.h"
3
4#include <cstring>
5#include <fstream>
6#include <vector>
7
8#include <zlib.h>
9
10// Minimal ZIP local file header reader.
11// ZIP files consist of a sequence of local-file-header + data entries.
12// We iterate through those entries and extract each file.
13
14namespace
15{
16 // Read a little-endian uint16 from a byte pointer.
17 uint16_t readU16(const unsigned char* p)
18 {
19 return static_cast<uint16_t>(p[0]) | (static_cast<uint16_t>(p[1]) << 8);
20 }
21
22 // Read a little-endian uint32 from a byte pointer.
23 uint32_t readU32(const unsigned char* p)
24 {
25 return static_cast<uint32_t>(p[0])
26 | (static_cast<uint32_t>(p[1]) << 8)
27 | (static_cast<uint32_t>(p[2]) << 16)
28 | (static_cast<uint32_t>(p[3]) << 24);
29 }
30
31 static constexpr uint32_t LOCAL_FILE_HEADER_SIG = 0x04034b50;
32}
33
34bool extractZip(const std::string& zipData, const std::filesystem::path& destDir)
35{
36 namespace fs = std::filesystem;
37
38 const auto* data = reinterpret_cast<const unsigned char*>(zipData.data());
39 const size_t dataSize = zipData.size();
40 size_t offset = 0;
41
42 while (offset + 30 <= dataSize)
43 {
44 const uint32_t sig = readU32(data + offset);
45 if (sig != LOCAL_FILE_HEADER_SIG)
46 break; // reached central directory or end
47
48 const uint16_t method = readU16(data + offset + 8);
49 const uint32_t compressedSize = readU32(data + offset + 18);
50 const uint32_t uncompressedSize = readU32(data + offset + 22);
51 const uint16_t nameLen = readU16(data + offset + 26);
52 const uint16_t extraLen = readU16(data + offset + 28);
53
54 if (offset + 30 + nameLen + extraLen > dataSize)
55 {
56 LOG_ERROR << "ZIP: truncated local file header at offset" << offset;
57 return false;
58 }
59
60 std::string fileName(reinterpret_cast<const char*>(data + offset + 30), nameLen);
61 const size_t dataStart = offset + 30 + nameLen + extraLen;
62
63 if (dataStart + compressedSize > dataSize)
64 {
65 LOG_ERROR << "ZIP: truncated file data for" << fileName;
66 return false;
67 }
68
69 // Skip directory entries
70 if (!fileName.empty() && fileName.back() != '/' && fileName.back() != '\\')
71 {
72 // Sanitise path separators
73 std::replace(fileName.begin(), fileName.end(), '/', '\\');
74
75 const fs::path outPath = destDir / fileName;
76
77 // Create parent directories
78 std::error_code ec;
79 fs::create_directories(outPath.parent_path(), ec);
80
81 std::vector<unsigned char> outBuf;
82
83 if (method == 0) // stored
84 {
85 outBuf.assign(data + dataStart, data + dataStart + compressedSize);
86 }
87 else if (method == 8) // deflated
88 {
89 outBuf.resize(uncompressedSize);
90
91 z_stream strm{};
92 strm.next_in = const_cast<unsigned char*>(data + dataStart);
93 strm.avail_in = compressedSize;
94 strm.next_out = outBuf.data();
95 strm.avail_out = uncompressedSize;
96
97 // -MAX_WBITS = raw deflate (no zlib/gzip header)
98 if (inflateInit2(&strm, -MAX_WBITS) != Z_OK)
99 {
100 LOG_ERROR << "ZIP: inflateInit2 failed for" << fileName;
101 return false;
102 }
103
104 const int ret = inflate(&strm, Z_FINISH);
105 inflateEnd(&strm);
106
107 if (ret != Z_STREAM_END)
108 {
109 LOG_ERROR << "ZIP: inflate failed for" << fileName << "ret=" << ret;
110 return false;
111 }
112 }
113 else
114 {
115 LOG_ERROR << "ZIP: unsupported compression method" << method << "for" << fileName;
116 offset = dataStart + compressedSize;
117 continue;
118 }
119
120 std::ofstream out(outPath, std::ios::binary);
121 if (!out.is_open())
122 {
123 LOG_ERROR << "ZIP: failed to create file" << outPath.string();
124 return false;
125 }
126 out.write(reinterpret_cast<const char*>(outBuf.data()), outBuf.size());
127 }
128
129 offset = dataStart + compressedSize;
130 }
131
132 return true;
133}
#define LOG_ERROR
Definition Logger.h:11
bool extractZip(const std::string &zipData, const std::filesystem::path &destDir)
Extract all files from a ZIP archive into the given directory.