载入中...
搜索中...
未找到
zip_writer.h
浏览该文件的文档.
1#pragma once
2
9#include <cstdint>
10#include <cstdio>
11#include <cstring>
12#include <filesystem>
13#include <fstream>
14#include <string>
15#include <vector>
16
17#include <zlib.h>
18
19namespace eve::cmdline {
20
22#define EVE_ZIP_METHOD 8
23
24namespace detail {
25
27inline bool rawDeflate(const char* in, size_t inLen, std::vector<char>& out) {
28 z_stream stream = {};
29 stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(in));
30 stream.avail_in = static_cast<uInt>(inLen);
31
32 if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK)
33 return false;
34
35 out.resize(inLen > 0 ? static_cast<size_t>(deflateBound(&stream, static_cast<uLong>(inLen))) : 1024);
36
37 stream.next_out = reinterpret_cast<Bytef*>(out.data());
38 stream.avail_out = static_cast<uInt>(out.size());
39
40 int ret = deflate(&stream, Z_FINISH);
41 bool ok = (ret == Z_STREAM_END);
43 deflateEnd(&stream);
44
45 if (!ok) return false;
46 out.resize(stream.total_out);
47 return true;
48}
49
51inline void put16(std::vector<char>& b, uint16_t v) {
52 b.push_back(static_cast<char>(v & 0xFF));
53 b.push_back(static_cast<char>((v >> 8) & 0xFF));
54}
56inline void put32(std::vector<char>& b, uint32_t v) {
57 b.push_back(static_cast<char>(v & 0xFF));
58 b.push_back(static_cast<char>((v >> 8) & 0xFF));
59 b.push_back(static_cast<char>((v >> 16) & 0xFF));
60 b.push_back(static_cast<char>((v >> 24) & 0xFF));
61}
62
63} // namespace detail
64
66class ZipWriter {
67public:
69 bool open(const std::string& outPath) {
70 outFile_.open(outPath, std::ios::binary | std::ios::trunc);
71 return outFile_.is_open();
72 }
73
75 bool addFile(const std::string& relPath, const char* data, size_t size) {
76 const uint32_t localOffset = static_cast<uint32_t>(offset_);
77 uint32_t crc = static_cast<uint32_t>(crc32(0, reinterpret_cast<const Bytef*>(data),
78 static_cast<uInt>(size)));
79
80 std::vector<char> compressed;
81#if EVE_ZIP_METHOD == 8
82 if (!detail::rawDeflate(data, size, compressed)) return false;
83#else
84 compressed.assign(data, data + size);
85#endif
86
87 std::vector<char> lfh;
88 lfh.reserve(30 + relPath.size());
90 detail::put32(lfh, 0x04034B50); // local file header signature
92 detail::put16(lfh, 20); // version needed
94 detail::put16(lfh, 0); // general purpose bit flag
96 detail::put16(lfh, EVE_ZIP_METHOD); // compression method
98 detail::put16(lfh, 0); // last mod time
100 detail::put16(lfh, 0x21); // last mod date (1980-01-01)
102 detail::put32(lfh, crc);
104 detail::put32(lfh, static_cast<uint32_t>(compressed.size()));
106 detail::put32(lfh, static_cast<uint32_t>(size));
108 detail::put16(lfh, static_cast<uint16_t>(relPath.size()));
110 detail::put16(lfh, 0); // extra field length
111 lfh.insert(lfh.end(), relPath.begin(), relPath.end());
112
113 if (!writeRaw(lfh)) return false;
114 if (!writeRaw(compressed)) return false;
115
116 CentralEntry e;
117 e.relPath = relPath;
118 e.crc = crc;
119 e.compressedSz = static_cast<uint32_t>(compressed.size());
120 e.uncompressedSz = static_cast<uint32_t>(size);
121 e.localOffset = localOffset;
122 entries_.push_back(std::move(e));
123
124 return true;
125 }
126
128 bool finish() {
129 if (!outFile_.is_open()) return false;
130 const uint32_t cdStart = static_cast<uint32_t>(offset_);
131 for (const auto& e : entries_) {
132 std::vector<char> cd;
133 cd.reserve(46 + e.relPath.size());
135 detail::put32(cd, 0x02014B50); // central directory signature
137 detail::put16(cd, 20); // version made by
139 detail::put16(cd, 20); // version needed
141 detail::put16(cd, 0); // flags
143 detail::put16(cd, EVE_ZIP_METHOD); // method
145 detail::put16(cd, 0); // mod time
147 detail::put16(cd, 0x21); // mod date
149 detail::put32(cd, e.crc);
151 detail::put32(cd, e.compressedSz);
153 detail::put32(cd, e.uncompressedSz);
155 detail::put16(cd, static_cast<uint16_t>(e.relPath.size()));
157 detail::put16(cd, 0); // extra
159 detail::put16(cd, 0); // comment
161 detail::put16(cd, 0); // disk number
163 detail::put16(cd, 0); // internal attrs
165 detail::put32(cd, 0); // external attrs
167 detail::put32(cd, e.localOffset);
168 cd.insert(cd.end(), e.relPath.begin(), e.relPath.end());
169
170 if (!writeRaw(cd)) return false;
171 }
172 const uint32_t cdEnd = static_cast<uint32_t>(offset_);
173
174 std::vector<char> eocd;
176 detail::put32(eocd, 0x06054B50); // end of central directory
178 detail::put16(eocd, 0); // disk number
180 detail::put16(eocd, 0); // cd start disk
182 detail::put16(eocd, static_cast<uint16_t>(entries_.size()));
184 detail::put16(eocd, static_cast<uint16_t>(entries_.size()));
186 detail::put32(eocd, cdEnd - cdStart);
188 detail::put32(eocd, cdStart);
190 detail::put16(eocd, 0); // comment length
191 if (!writeRaw(eocd)) return false;
192
193 outFile_.close();
194 return true;
195 }
196
197private:
198 struct CentralEntry {
199 std::string relPath;
200 uint32_t crc = 0;
201 uint32_t compressedSz = 0;
202 uint32_t uncompressedSz = 0;
203 uint32_t localOffset = 0;
204 };
205
206 bool writeRaw(const std::vector<char>& data) {
207 if (!outFile_.is_open()) return false;
208 outFile_.write(data.data(), static_cast<std::streamsize>(data.size()));
209 offset_ += data.size();
210 return static_cast<bool>(outFile_);
211 }
212
213 std::ofstream outFile_;
214 uint64_t offset_ = 0;
215 std::vector<CentralEntry> entries_;
216};
217
218 // Walk a game directory and write every regular file into a single .eve archive.
220 inline bool createGameArchive(
221 const std::filesystem::path& gameDir, const std::filesystem::path& outArchive,
222 const std::vector<std::pair<std::string, std::string>>& generatedEntries = {}) {
223 std::error_code ec;
224 if (!std::filesystem::is_directory(gameDir, ec)) return false;
225
226 ZipWriter writer;
227 if (!writer.open(outArchive.string())) return false;
228
229 // Recursively collect files, preserving relative paths with '/' separators.
230 std::vector<std::filesystem::path> files;
231 // Never archive the output file itself: `eve zip .` writes <cwd>.eve into
232 // the directory being walked, and a mid-write snapshot of the archive would
233 // be embedded (or fail to open) depending on the OS's file-sharing rules.
234 std::filesystem::path outAbs;
235 {
236 std::error_code aec;
237 outAbs = std::filesystem::absolute(outArchive, aec).lexically_normal();
238 if (aec) outAbs.clear();
239 }
241 std::filesystem::recursive_directory_iterator it(
242 gameDir, std::filesystem::directory_options::skip_permission_denied, ec);
243 std::filesystem::recursive_directory_iterator end;
244 for (; it != end; it.increment(ec)) {
245 if (ec) { ec.clear(); continue; }
246 if (!it->is_regular_file()) continue;
247 if (!outAbs.empty()) {
248 std::error_code aec;
249 if (std::filesystem::absolute(it->path(), aec).lexically_normal() == outAbs)
250 continue;
251 }
252 files.push_back(it->path());
253 }
254
255 for (const auto& file : files) {
256 std::string rel = std::filesystem::relative(file, gameDir, ec).generic_string();
257 if (rel.empty()) continue;
258 // Always use '/' inside the archive.
259 for (char& c : rel) if (c == '\\') c = '/';
260
262 std::ifstream in(file, std::ios::binary);
263 if (!in) continue;
264 in.seekg(0, std::ios::end);
265 const auto size = static_cast<size_t>(in.tellg());
266 in.seekg(0, std::ios::beg);
267
269 std::vector<char> buf(size);
270 if (size > 0) in.read(buf.data(), static_cast<std::streamsize>(size));
271 if (!in && size > 0) continue;
272
273 if (!writer.addFile(rel, buf.data(), buf.size())) return false;
274 }
275
276 for (const auto& [name, contents] : generatedEntries) {
277 if (name.empty() || name.front() == '/' || name.find("..") != std::string::npos) return false;
278 if (!writer.addFile(name, contents.data(), contents.size())) return false;
279 }
280
281 return writer.finish();
282 }
283
284} // namespace eve::cmdline
285
std::uint64_t localOffset
std::uint32_t crc
filesystem::File * file
float v
std::int32_t c
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
int detail
float size
Definition TreeMesh.cpp:156
流式 ZIP 写入器:open → addFile×N → finish。
Definition zip_writer.h:66
bool open(const std::string &outPath)
打开目标归档;必须在任何 addFile() 之前调用。
Definition zip_writer.h:69
bool finish()
写入中央目录 + EOCD 并关闭归档。
Definition zip_writer.h:128
bool addFile(const std::string &relPath, const char *data, size_t size)
添加一个普通文件;relPath 使用 '/' 分隔、相对归档根。
Definition zip_writer.h:75
void put16(std::vector< char > &b, uint16_t v)
以小端序写入 uint16 / uint32。
Definition zip_writer.h:51
bool rawDeflate(const char *in, size_t inLen, std::vector< char > &out)
Raw deflate(无 zlib/gzip 包装),ZIP method 8 条目需要。
Definition zip_writer.h:27
void put32(std::vector< char > &b, uint32_t v)
Put 32.
Definition zip_writer.h:56
Minimal streaming ZIP writer used by eve zip / eve package to produce a PhysFS-mountable game archive...
Definition zip_writer.h:19
bool createGameArchive(const std::filesystem::path &gameDir, const std::filesystem::path &outArchive, const std::vector< std::pair< std::string, std::string > > &generatedEntries={})
Creates game archive.
Definition zip_writer.h:220
#define EVE_ZIP_METHOD
0 = STORED(不压缩),8 = DEFLATE。
Definition zip_writer.h:22