1 | /**************************************************************************/ |
2 | /* pck_packer.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 | #include "pck_packer.h" |
32 | |
33 | #include "core/crypto/crypto_core.h" |
34 | #include "core/io/file_access.h" |
35 | #include "core/io/file_access_encrypted.h" |
36 | #include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION |
37 | #include "core/version.h" |
38 | |
39 | static int _get_pad(int p_alignment, int p_n) { |
40 | int rest = p_n % p_alignment; |
41 | int pad = 0; |
42 | if (rest > 0) { |
43 | pad = p_alignment - rest; |
44 | } |
45 | |
46 | return pad; |
47 | } |
48 | |
49 | void PCKPacker::_bind_methods() { |
50 | ClassDB::bind_method(D_METHOD("pck_start" , "pck_name" , "alignment" , "key" , "encrypt_directory" ), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000" ), DEFVAL(false)); |
51 | ClassDB::bind_method(D_METHOD("add_file" , "pck_path" , "source_path" , "encrypt" ), &PCKPacker::add_file, DEFVAL(false)); |
52 | ClassDB::bind_method(D_METHOD("flush" , "verbose" ), &PCKPacker::flush, DEFVAL(false)); |
53 | } |
54 | |
55 | Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &p_key, bool p_encrypt_directory) { |
56 | ERR_FAIL_COND_V_MSG((p_key.is_empty() || !p_key.is_valid_hex_number(false) || p_key.length() != 64), ERR_CANT_CREATE, "Invalid Encryption Key (must be 64 characters long)." ); |
57 | ERR_FAIL_COND_V_MSG(p_alignment <= 0, ERR_CANT_CREATE, "Invalid alignment, must be greater then 0." ); |
58 | |
59 | String _key = p_key.to_lower(); |
60 | key.resize(32); |
61 | for (int i = 0; i < 32; i++) { |
62 | int v = 0; |
63 | if (i * 2 < _key.length()) { |
64 | char32_t ct = _key[i * 2]; |
65 | if (is_digit(ct)) { |
66 | ct = ct - '0'; |
67 | } else if (ct >= 'a' && ct <= 'f') { |
68 | ct = 10 + ct - 'a'; |
69 | } |
70 | v |= ct << 4; |
71 | } |
72 | |
73 | if (i * 2 + 1 < _key.length()) { |
74 | char32_t ct = _key[i * 2 + 1]; |
75 | if (is_digit(ct)) { |
76 | ct = ct - '0'; |
77 | } else if (ct >= 'a' && ct <= 'f') { |
78 | ct = 10 + ct - 'a'; |
79 | } |
80 | v |= ct; |
81 | } |
82 | key.write[i] = v; |
83 | } |
84 | enc_dir = p_encrypt_directory; |
85 | |
86 | file = FileAccess::open(p_file, FileAccess::WRITE); |
87 | ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_file) + "." ); |
88 | |
89 | alignment = p_alignment; |
90 | |
91 | file->store_32(PACK_HEADER_MAGIC); |
92 | file->store_32(PACK_FORMAT_VERSION); |
93 | file->store_32(VERSION_MAJOR); |
94 | file->store_32(VERSION_MINOR); |
95 | file->store_32(VERSION_PATCH); |
96 | |
97 | uint32_t pack_flags = 0; |
98 | if (enc_dir) { |
99 | pack_flags |= PACK_DIR_ENCRYPTED; |
100 | } |
101 | file->store_32(pack_flags); // flags |
102 | |
103 | files.clear(); |
104 | ofs = 0; |
105 | |
106 | return OK; |
107 | } |
108 | |
109 | Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encrypt) { |
110 | ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use." ); |
111 | |
112 | Ref<FileAccess> f = FileAccess::open(p_src, FileAccess::READ); |
113 | if (f.is_null()) { |
114 | return ERR_FILE_CANT_OPEN; |
115 | } |
116 | |
117 | File pf; |
118 | // Simplify path here and on every 'files' access so that paths that have extra '/' |
119 | // symbols in them still match to the MD5 hash for the saved path. |
120 | pf.path = p_file.simplify_path(); |
121 | pf.src_path = p_src; |
122 | pf.ofs = ofs; |
123 | pf.size = f->get_length(); |
124 | |
125 | Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_src); |
126 | { |
127 | unsigned char hash[16]; |
128 | CryptoCore::md5(data.ptr(), data.size(), hash); |
129 | pf.md5.resize(16); |
130 | for (int i = 0; i < 16; i++) { |
131 | pf.md5.write[i] = hash[i]; |
132 | } |
133 | } |
134 | pf.encrypted = p_encrypt; |
135 | |
136 | uint64_t _size = pf.size; |
137 | if (p_encrypt) { // Add encryption overhead. |
138 | if (_size % 16) { // Pad to encryption block size. |
139 | _size += 16 - (_size % 16); |
140 | } |
141 | _size += 16; // hash |
142 | _size += 8; // data size |
143 | _size += 16; // iv |
144 | } |
145 | |
146 | int pad = _get_pad(alignment, ofs + _size); |
147 | ofs = ofs + _size + pad; |
148 | |
149 | files.push_back(pf); |
150 | |
151 | return OK; |
152 | } |
153 | |
154 | Error PCKPacker::flush(bool p_verbose) { |
155 | ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use." ); |
156 | |
157 | int64_t file_base_ofs = file->get_position(); |
158 | file->store_64(0); // files base |
159 | |
160 | for (int i = 0; i < 16; i++) { |
161 | file->store_32(0); // reserved |
162 | } |
163 | |
164 | // write the index |
165 | file->store_32(files.size()); |
166 | |
167 | Ref<FileAccessEncrypted> fae; |
168 | Ref<FileAccess> fhead = file; |
169 | |
170 | if (enc_dir) { |
171 | fae.instantiate(); |
172 | ERR_FAIL_COND_V(fae.is_null(), ERR_CANT_CREATE); |
173 | |
174 | Error err = fae->open_and_parse(file, key, FileAccessEncrypted::MODE_WRITE_AES256, false); |
175 | ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); |
176 | |
177 | fhead = fae; |
178 | } |
179 | |
180 | for (int i = 0; i < files.size(); i++) { |
181 | int string_len = files[i].path.utf8().length(); |
182 | int pad = _get_pad(4, string_len); |
183 | |
184 | fhead->store_32(string_len + pad); |
185 | fhead->store_buffer((const uint8_t *)files[i].path.utf8().get_data(), string_len); |
186 | for (int j = 0; j < pad; j++) { |
187 | fhead->store_8(0); |
188 | } |
189 | |
190 | fhead->store_64(files[i].ofs); |
191 | fhead->store_64(files[i].size); // pay attention here, this is where file is |
192 | fhead->store_buffer(files[i].md5.ptr(), 16); //also save md5 for file |
193 | |
194 | uint32_t flags = 0; |
195 | if (files[i].encrypted) { |
196 | flags |= PACK_FILE_ENCRYPTED; |
197 | } |
198 | fhead->store_32(flags); |
199 | } |
200 | |
201 | if (fae.is_valid()) { |
202 | fhead.unref(); |
203 | fae.unref(); |
204 | } |
205 | |
206 | int = _get_pad(alignment, file->get_position()); |
207 | for (int i = 0; i < header_padding; i++) { |
208 | file->store_8(0); |
209 | } |
210 | |
211 | int64_t file_base = file->get_position(); |
212 | file->seek(file_base_ofs); |
213 | file->store_64(file_base); // update files base |
214 | file->seek(file_base); |
215 | |
216 | const uint32_t buf_max = 65536; |
217 | uint8_t *buf = memnew_arr(uint8_t, buf_max); |
218 | |
219 | int count = 0; |
220 | for (int i = 0; i < files.size(); i++) { |
221 | Ref<FileAccess> src = FileAccess::open(files[i].src_path, FileAccess::READ); |
222 | uint64_t to_write = files[i].size; |
223 | |
224 | Ref<FileAccess> ftmp = file; |
225 | if (files[i].encrypted) { |
226 | fae.instantiate(); |
227 | ERR_FAIL_COND_V(fae.is_null(), ERR_CANT_CREATE); |
228 | |
229 | Error err = fae->open_and_parse(file, key, FileAccessEncrypted::MODE_WRITE_AES256, false); |
230 | ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); |
231 | ftmp = fae; |
232 | } |
233 | |
234 | while (to_write > 0) { |
235 | uint64_t read = src->get_buffer(buf, MIN(to_write, buf_max)); |
236 | ftmp->store_buffer(buf, read); |
237 | to_write -= read; |
238 | } |
239 | |
240 | if (fae.is_valid()) { |
241 | ftmp.unref(); |
242 | fae.unref(); |
243 | } |
244 | |
245 | int pad = _get_pad(alignment, file->get_position()); |
246 | for (int j = 0; j < pad; j++) { |
247 | file->store_8(0); |
248 | } |
249 | |
250 | count += 1; |
251 | const int file_num = files.size(); |
252 | if (p_verbose && (file_num > 0)) { |
253 | print_line(vformat("[%d/%d - %d%%] PCKPacker flush: %s -> %s" , count, file_num, float(count) / file_num * 100, files[i].src_path, files[i].path)); |
254 | } |
255 | } |
256 | |
257 | file.unref(); |
258 | memdelete_arr(buf); |
259 | |
260 | return OK; |
261 | } |
262 | |