1 | /**************************************************************************/ |
2 | /* file_access_pack.h */ |
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 | #ifndef FILE_ACCESS_PACK_H |
32 | #define FILE_ACCESS_PACK_H |
33 | |
34 | #include "core/io/dir_access.h" |
35 | #include "core/io/file_access.h" |
36 | #include "core/string/print_string.h" |
37 | #include "core/templates/hash_set.h" |
38 | #include "core/templates/list.h" |
39 | #include "core/templates/rb_map.h" |
40 | |
41 | // Godot's packed file magic header ("GDPC" in ASCII). |
42 | #define 0x43504447 |
43 | // The current packed file format version number. |
44 | #define PACK_FORMAT_VERSION 2 |
45 | |
46 | enum PackFlags { |
47 | PACK_DIR_ENCRYPTED = 1 << 0 |
48 | }; |
49 | |
50 | enum PackFileFlags { |
51 | PACK_FILE_ENCRYPTED = 1 << 0 |
52 | }; |
53 | |
54 | class PackSource; |
55 | |
56 | class PackedData { |
57 | friend class FileAccessPack; |
58 | friend class DirAccessPack; |
59 | friend class PackSource; |
60 | |
61 | public: |
62 | struct PackedFile { |
63 | String pack; |
64 | uint64_t offset; //if offset is ZERO, the file was ERASED |
65 | uint64_t size; |
66 | uint8_t md5[16]; |
67 | PackSource *src = nullptr; |
68 | bool encrypted; |
69 | }; |
70 | |
71 | private: |
72 | struct PackedDir { |
73 | PackedDir *parent = nullptr; |
74 | String name; |
75 | HashMap<String, PackedDir *> subdirs; |
76 | HashSet<String> files; |
77 | }; |
78 | |
79 | struct PathMD5 { |
80 | uint64_t a = 0; |
81 | uint64_t b = 0; |
82 | |
83 | bool operator==(const PathMD5 &p_val) const { |
84 | return (a == p_val.a) && (b == p_val.b); |
85 | } |
86 | static uint32_t hash(const PathMD5 &p_val) { |
87 | uint32_t h = hash_murmur3_one_32(p_val.a); |
88 | return hash_fmix32(hash_murmur3_one_32(p_val.b, h)); |
89 | } |
90 | |
91 | PathMD5() {} |
92 | |
93 | explicit PathMD5(const Vector<uint8_t> &p_buf) { |
94 | a = *((uint64_t *)&p_buf[0]); |
95 | b = *((uint64_t *)&p_buf[8]); |
96 | } |
97 | }; |
98 | |
99 | HashMap<PathMD5, PackedFile, PathMD5> files; |
100 | |
101 | Vector<PackSource *> sources; |
102 | |
103 | PackedDir *root = nullptr; |
104 | |
105 | static PackedData *singleton; |
106 | bool disabled = false; |
107 | |
108 | void _free_packed_dirs(PackedDir *p_dir); |
109 | |
110 | public: |
111 | void add_pack_source(PackSource *p_source); |
112 | void add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false); // for PackSource |
113 | |
114 | void set_disabled(bool p_disabled) { disabled = p_disabled; } |
115 | _FORCE_INLINE_ bool is_disabled() const { return disabled; } |
116 | |
117 | static PackedData *get_singleton() { return singleton; } |
118 | Error add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset); |
119 | |
120 | _FORCE_INLINE_ Ref<FileAccess> try_open_path(const String &p_path); |
121 | _FORCE_INLINE_ bool has_path(const String &p_path); |
122 | |
123 | _FORCE_INLINE_ Ref<DirAccess> try_open_directory(const String &p_path); |
124 | _FORCE_INLINE_ bool has_directory(const String &p_path); |
125 | |
126 | PackedData(); |
127 | ~PackedData(); |
128 | }; |
129 | |
130 | class PackSource { |
131 | public: |
132 | virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) = 0; |
133 | virtual Ref<FileAccess> get_file(const String &p_path, PackedData::PackedFile *p_file) = 0; |
134 | virtual ~PackSource() {} |
135 | }; |
136 | |
137 | class PackedSourcePCK : public PackSource { |
138 | public: |
139 | virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) override; |
140 | virtual Ref<FileAccess> get_file(const String &p_path, PackedData::PackedFile *p_file) override; |
141 | }; |
142 | |
143 | class FileAccessPack : public FileAccess { |
144 | PackedData::PackedFile pf; |
145 | |
146 | mutable uint64_t pos; |
147 | mutable bool eof; |
148 | uint64_t off; |
149 | |
150 | Ref<FileAccess> f; |
151 | virtual Error open_internal(const String &p_path, int p_mode_flags) override; |
152 | virtual uint64_t _get_modified_time(const String &p_file) override { return 0; } |
153 | virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } |
154 | virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; } |
155 | |
156 | virtual bool _get_hidden_attribute(const String &p_file) override { return false; } |
157 | virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } |
158 | virtual bool _get_read_only_attribute(const String &p_file) override { return false; } |
159 | virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; } |
160 | |
161 | public: |
162 | virtual bool is_open() const override; |
163 | |
164 | virtual void seek(uint64_t p_position) override; |
165 | virtual void seek_end(int64_t p_position = 0) override; |
166 | virtual uint64_t get_position() const override; |
167 | virtual uint64_t get_length() const override; |
168 | |
169 | virtual bool eof_reached() const override; |
170 | |
171 | virtual uint8_t get_8() const override; |
172 | |
173 | virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; |
174 | |
175 | virtual void set_big_endian(bool p_big_endian) override; |
176 | |
177 | virtual Error get_error() const override; |
178 | |
179 | virtual void flush() override; |
180 | virtual void store_8(uint8_t p_dest) override; |
181 | |
182 | virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; |
183 | |
184 | virtual bool file_exists(const String &p_name) override; |
185 | |
186 | virtual void close() override; |
187 | |
188 | FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file); |
189 | }; |
190 | |
191 | Ref<FileAccess> PackedData::try_open_path(const String &p_path) { |
192 | String simplified_path = p_path.simplify_path(); |
193 | PathMD5 pmd5(simplified_path.md5_buffer()); |
194 | HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5); |
195 | if (!E) { |
196 | return nullptr; //not found |
197 | } |
198 | if (E->value.offset == 0) { |
199 | return nullptr; //was erased |
200 | } |
201 | |
202 | return E->value.src->get_file(p_path, &E->value); |
203 | } |
204 | |
205 | bool PackedData::has_path(const String &p_path) { |
206 | return files.has(PathMD5(p_path.simplify_path().md5_buffer())); |
207 | } |
208 | |
209 | bool PackedData::has_directory(const String &p_path) { |
210 | Ref<DirAccess> da = try_open_directory(p_path); |
211 | if (da.is_valid()) { |
212 | return true; |
213 | } else { |
214 | return false; |
215 | } |
216 | } |
217 | |
218 | class DirAccessPack : public DirAccess { |
219 | PackedData::PackedDir *current; |
220 | |
221 | List<String> list_dirs; |
222 | List<String> list_files; |
223 | bool cdir = false; |
224 | |
225 | PackedData::PackedDir *_find_dir(String p_dir); |
226 | |
227 | public: |
228 | virtual Error list_dir_begin() override; |
229 | virtual String get_next() override; |
230 | virtual bool current_is_dir() const override; |
231 | virtual bool current_is_hidden() const override; |
232 | virtual void list_dir_end() override; |
233 | |
234 | virtual int get_drive_count() override; |
235 | virtual String get_drive(int p_drive) override; |
236 | |
237 | virtual Error change_dir(String p_dir) override; |
238 | virtual String get_current_dir(bool p_include_drive = true) const override; |
239 | |
240 | virtual bool file_exists(String p_file) override; |
241 | virtual bool dir_exists(String p_dir) override; |
242 | |
243 | virtual Error make_dir(String p_dir) override; |
244 | |
245 | virtual Error rename(String p_from, String p_to) override; |
246 | virtual Error remove(String p_name) override; |
247 | |
248 | uint64_t get_space_left() override; |
249 | |
250 | virtual bool is_link(String p_file) override { return false; } |
251 | virtual String read_link(String p_file) override { return p_file; } |
252 | virtual Error create_link(String p_source, String p_target) override { return FAILED; } |
253 | |
254 | virtual String get_filesystem_type() const override; |
255 | |
256 | DirAccessPack(); |
257 | }; |
258 | |
259 | Ref<DirAccess> PackedData::try_open_directory(const String &p_path) { |
260 | Ref<DirAccess> da = memnew(DirAccessPack()); |
261 | if (da->change_dir(p_path) != OK) { |
262 | da = Ref<DirAccess>(); |
263 | } |
264 | return da; |
265 | } |
266 | |
267 | #endif // FILE_ACCESS_PACK_H |
268 | |