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 | |
37 | ZipArchive *ZipArchive::instance = nullptr; |
38 | |
39 | extern "C" { |
40 | |
41 | struct ZipData { |
42 | Ref<FileAccess> f; |
43 | }; |
44 | |
45 | static 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 | |
58 | static 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 | |
64 | static uLong godot_write(voidpf opaque, voidpf stream, const void *buf, uLong size) { |
65 | return 0; |
66 | } |
67 | |
68 | static long godot_tell(voidpf opaque, voidpf stream) { |
69 | ZipData *zd = (ZipData *)stream; |
70 | return zd->f->get_position(); |
71 | } |
72 | |
73 | static 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 | |
92 | static int godot_close(voidpf opaque, voidpf stream) { |
93 | ZipData *zd = (ZipData *)stream; |
94 | memdelete(zd); |
95 | return 0; |
96 | } |
97 | |
98 | static int godot_testerror(voidpf opaque, voidpf stream) { |
99 | ZipData *zd = (ZipData *)stream; |
100 | return zd->f->get_error() != OK ? 1 : 0; |
101 | } |
102 | |
103 | static voidpf godot_alloc(voidpf opaque, uInt items, uInt size) { |
104 | return memalloc((size_t)items * size); |
105 | } |
106 | |
107 | static void godot_free(voidpf opaque, voidpf address) { |
108 | memfree(address); |
109 | } |
110 | } // extern "C" |
111 | |
112 | void 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 | |
118 | unzFile 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 | |
149 | bool 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 | |
209 | bool ZipArchive::file_exists(String p_name) const { |
210 | return files.has(p_name); |
211 | } |
212 | |
213 | Ref<FileAccess> ZipArchive::get_file(const String &p_path, PackedData::PackedFile *p_file) { |
214 | return memnew(FileAccessZip(p_path, *p_file)); |
215 | } |
216 | |
217 | ZipArchive *ZipArchive::get_singleton() { |
218 | if (instance == nullptr) { |
219 | instance = memnew(ZipArchive); |
220 | } |
221 | |
222 | return instance; |
223 | } |
224 | |
225 | ZipArchive::ZipArchive() { |
226 | instance = this; |
227 | } |
228 | |
229 | ZipArchive::~ZipArchive() { |
230 | for (int i = 0; i < packages.size(); i++) { |
231 | unzClose(packages[i].zfile); |
232 | } |
233 | |
234 | packages.clear(); |
235 | } |
236 | |
237 | Error 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 | |
252 | void 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 | |
263 | bool FileAccessZip::is_open() const { |
264 | return zfile != nullptr; |
265 | } |
266 | |
267 | void FileAccessZip::seek(uint64_t p_position) { |
268 | ERR_FAIL_COND(!zfile); |
269 | |
270 | unzSeekCurrentFile(zfile, p_position); |
271 | } |
272 | |
273 | void FileAccessZip::seek_end(int64_t p_position) { |
274 | ERR_FAIL_COND(!zfile); |
275 | unzSeekCurrentFile(zfile, get_length() + p_position); |
276 | } |
277 | |
278 | uint64_t FileAccessZip::get_position() const { |
279 | ERR_FAIL_COND_V(!zfile, 0); |
280 | return unztell(zfile); |
281 | } |
282 | |
283 | uint64_t FileAccessZip::get_length() const { |
284 | ERR_FAIL_COND_V(!zfile, 0); |
285 | return file_info.uncompressed_size; |
286 | } |
287 | |
288 | bool FileAccessZip::eof_reached() const { |
289 | ERR_FAIL_COND_V(!zfile, true); |
290 | |
291 | return at_eof; |
292 | } |
293 | |
294 | uint8_t FileAccessZip::get_8() const { |
295 | uint8_t ret = 0; |
296 | get_buffer(&ret, 1); |
297 | return ret; |
298 | } |
299 | |
300 | uint64_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 | |
316 | Error 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 | |
327 | void FileAccessZip::flush() { |
328 | ERR_FAIL(); |
329 | } |
330 | |
331 | void FileAccessZip::store_8(uint8_t p_dest) { |
332 | ERR_FAIL(); |
333 | } |
334 | |
335 | bool FileAccessZip::file_exists(const String &p_name) { |
336 | return false; |
337 | } |
338 | |
339 | void FileAccessZip::close() { |
340 | _close(); |
341 | } |
342 | |
343 | FileAccessZip::FileAccessZip(const String &p_path, const PackedData::PackedFile &p_file) { |
344 | open_internal(p_path, FileAccess::READ); |
345 | } |
346 | |
347 | FileAccessZip::~FileAccessZip() { |
348 | _close(); |
349 | } |
350 | |
351 | #endif // MINIZIP_ENABLED |
352 | |