1/**************************************************************************/
2/* file_access_zip.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#ifdef MINIZIP_ENABLED
32
33#include "file_access_zip.h"
34
35#include "core/io/file_access.h"
36
37ZipArchive *ZipArchive::instance = nullptr;
38
39extern "C" {
40
41struct ZipData {
42 Ref<FileAccess> f;
43};
44
45static void *godot_open(voidpf opaque, const char *p_fname, int mode) {
46 if (mode & ZLIB_FILEFUNC_MODE_WRITE) {
47 return nullptr;
48 }
49
50 Ref<FileAccess> f = FileAccess::open(String::utf8(p_fname), FileAccess::READ);
51 ERR_FAIL_COND_V(f.is_null(), nullptr);
52
53 ZipData *zd = memnew(ZipData);
54 zd->f = f;
55 return zd;
56}
57
58static uLong godot_read(voidpf opaque, voidpf stream, void *buf, uLong size) {
59 ZipData *zd = (ZipData *)stream;
60 zd->f->get_buffer((uint8_t *)buf, size);
61 return size;
62}
63
64static uLong godot_write(voidpf opaque, voidpf stream, const void *buf, uLong size) {
65 return 0;
66}
67
68static long godot_tell(voidpf opaque, voidpf stream) {
69 ZipData *zd = (ZipData *)stream;
70 return zd->f->get_position();
71}
72
73static long godot_seek(voidpf opaque, voidpf stream, uLong offset, int origin) {
74 ZipData *zd = (ZipData *)stream;
75
76 uint64_t pos = offset;
77 switch (origin) {
78 case ZLIB_FILEFUNC_SEEK_CUR:
79 pos = zd->f->get_position() + offset;
80 break;
81 case ZLIB_FILEFUNC_SEEK_END:
82 pos = zd->f->get_length() + offset;
83 break;
84 default:
85 break;
86 }
87
88 zd->f->seek(pos);
89 return 0;
90}
91
92static int godot_close(voidpf opaque, voidpf stream) {
93 ZipData *zd = (ZipData *)stream;
94 memdelete(zd);
95 return 0;
96}
97
98static int godot_testerror(voidpf opaque, voidpf stream) {
99 ZipData *zd = (ZipData *)stream;
100 return zd->f->get_error() != OK ? 1 : 0;
101}
102
103static voidpf godot_alloc(voidpf opaque, uInt items, uInt size) {
104 return memalloc((size_t)items * size);
105}
106
107static void godot_free(voidpf opaque, voidpf address) {
108 memfree(address);
109}
110} // extern "C"
111
112void ZipArchive::close_handle(unzFile p_file) const {
113 ERR_FAIL_COND_MSG(!p_file, "Cannot close a file if none is open.");
114 unzCloseCurrentFile(p_file);
115 unzClose(p_file);
116}
117
118unzFile ZipArchive::get_file_handle(String p_file) const {
119 ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, "File '" + p_file + " doesn't exist.");
120 File file = files[p_file];
121
122 zlib_filefunc_def io;
123 memset(&io, 0, sizeof(io));
124
125 io.opaque = nullptr;
126 io.zopen_file = godot_open;
127 io.zread_file = godot_read;
128 io.zwrite_file = godot_write;
129
130 io.ztell_file = godot_tell;
131 io.zseek_file = godot_seek;
132 io.zclose_file = godot_close;
133 io.zerror_file = godot_testerror;
134
135 io.alloc_mem = godot_alloc;
136 io.free_mem = godot_free;
137
138 unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io);
139 ERR_FAIL_COND_V_MSG(!pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'.");
140 int unz_err = unzGoToFilePos(pkg, &file.file_pos);
141 if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) {
142 unzClose(pkg);
143 ERR_FAIL_V(nullptr);
144 }
145
146 return pkg;
147}
148
149bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset = 0) {
150 // load with offset feature only supported for PCK files
151 ERR_FAIL_COND_V_MSG(p_offset != 0, false, "Invalid PCK data. Note that loading files with a non-zero offset isn't supported with ZIP archives.");
152
153 if (p_path.get_extension().nocasecmp_to("zip") != 0 && p_path.get_extension().nocasecmp_to("pcz") != 0) {
154 return false;
155 }
156
157 zlib_filefunc_def io;
158 memset(&io, 0, sizeof(io));
159
160 io.opaque = nullptr;
161 io.zopen_file = godot_open;
162 io.zread_file = godot_read;
163 io.zwrite_file = godot_write;
164
165 io.ztell_file = godot_tell;
166 io.zseek_file = godot_seek;
167 io.zclose_file = godot_close;
168 io.zerror_file = godot_testerror;
169
170 unzFile zfile = unzOpen2(p_path.utf8().get_data(), &io);
171 ERR_FAIL_COND_V(!zfile, false);
172
173 unz_global_info64 gi;
174 int err = unzGetGlobalInfo64(zfile, &gi);
175 ERR_FAIL_COND_V(err != UNZ_OK, false);
176
177 Package pkg;
178 pkg.filename = p_path;
179 pkg.zfile = zfile;
180 packages.push_back(pkg);
181 int pkg_num = packages.size() - 1;
182
183 for (uint64_t i = 0; i < gi.number_entry; i++) {
184 char filename_inzip[256];
185
186 unz_file_info64 file_info;
187 err = unzGetCurrentFileInfo64(zfile, &file_info, filename_inzip, sizeof(filename_inzip), nullptr, 0, nullptr, 0);
188 ERR_CONTINUE(err != UNZ_OK);
189
190 File f;
191 f.package = pkg_num;
192 unzGetFilePos(zfile, &f.file_pos);
193
194 String fname = String("res://") + String::utf8(filename_inzip);
195 files[fname] = f;
196
197 uint8_t md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
198 PackedData::get_singleton()->add_path(p_path, fname, 1, 0, md5, this, p_replace_files, false);
199 //printf("packed data add path %s, %s\n", p_name.utf8().get_data(), fname.utf8().get_data());
200
201 if ((i + 1) < gi.number_entry) {
202 unzGoToNextFile(zfile);
203 }
204 }
205
206 return true;
207}
208
209bool ZipArchive::file_exists(String p_name) const {
210 return files.has(p_name);
211}
212
213Ref<FileAccess> ZipArchive::get_file(const String &p_path, PackedData::PackedFile *p_file) {
214 return memnew(FileAccessZip(p_path, *p_file));
215}
216
217ZipArchive *ZipArchive::get_singleton() {
218 if (instance == nullptr) {
219 instance = memnew(ZipArchive);
220 }
221
222 return instance;
223}
224
225ZipArchive::ZipArchive() {
226 instance = this;
227}
228
229ZipArchive::~ZipArchive() {
230 for (int i = 0; i < packages.size(); i++) {
231 unzClose(packages[i].zfile);
232 }
233
234 packages.clear();
235}
236
237Error FileAccessZip::open_internal(const String &p_path, int p_mode_flags) {
238 _close();
239
240 ERR_FAIL_COND_V(p_mode_flags & FileAccess::WRITE, FAILED);
241 ZipArchive *arch = ZipArchive::get_singleton();
242 ERR_FAIL_NULL_V(arch, FAILED);
243 zfile = arch->get_file_handle(p_path);
244 ERR_FAIL_COND_V(!zfile, FAILED);
245
246 int err = unzGetCurrentFileInfo64(zfile, &file_info, nullptr, 0, nullptr, 0, nullptr, 0);
247 ERR_FAIL_COND_V(err != UNZ_OK, FAILED);
248
249 return OK;
250}
251
252void FileAccessZip::_close() {
253 if (!zfile) {
254 return;
255 }
256
257 ZipArchive *arch = ZipArchive::get_singleton();
258 ERR_FAIL_NULL(arch);
259 arch->close_handle(zfile);
260 zfile = nullptr;
261}
262
263bool FileAccessZip::is_open() const {
264 return zfile != nullptr;
265}
266
267void FileAccessZip::seek(uint64_t p_position) {
268 ERR_FAIL_COND(!zfile);
269
270 unzSeekCurrentFile(zfile, p_position);
271}
272
273void FileAccessZip::seek_end(int64_t p_position) {
274 ERR_FAIL_COND(!zfile);
275 unzSeekCurrentFile(zfile, get_length() + p_position);
276}
277
278uint64_t FileAccessZip::get_position() const {
279 ERR_FAIL_COND_V(!zfile, 0);
280 return unztell(zfile);
281}
282
283uint64_t FileAccessZip::get_length() const {
284 ERR_FAIL_COND_V(!zfile, 0);
285 return file_info.uncompressed_size;
286}
287
288bool FileAccessZip::eof_reached() const {
289 ERR_FAIL_COND_V(!zfile, true);
290
291 return at_eof;
292}
293
294uint8_t FileAccessZip::get_8() const {
295 uint8_t ret = 0;
296 get_buffer(&ret, 1);
297 return ret;
298}
299
300uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
301 ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
302 ERR_FAIL_COND_V(!zfile, -1);
303
304 at_eof = unzeof(zfile);
305 if (at_eof) {
306 return 0;
307 }
308 int64_t read = unzReadCurrentFile(zfile, p_dst, p_length);
309 ERR_FAIL_COND_V(read < 0, read);
310 if ((uint64_t)read < p_length) {
311 at_eof = true;
312 }
313 return read;
314}
315
316Error FileAccessZip::get_error() const {
317 if (!zfile) {
318 return ERR_UNCONFIGURED;
319 }
320 if (eof_reached()) {
321 return ERR_FILE_EOF;
322 }
323
324 return OK;
325}
326
327void FileAccessZip::flush() {
328 ERR_FAIL();
329}
330
331void FileAccessZip::store_8(uint8_t p_dest) {
332 ERR_FAIL();
333}
334
335bool FileAccessZip::file_exists(const String &p_name) {
336 return false;
337}
338
339void FileAccessZip::close() {
340 _close();
341}
342
343FileAccessZip::FileAccessZip(const String &p_path, const PackedData::PackedFile &p_file) {
344 open_internal(p_path, FileAccess::READ);
345}
346
347FileAccessZip::~FileAccessZip() {
348 _close();
349}
350
351#endif // MINIZIP_ENABLED
352