1 | /**************************************************************************/ |
2 | /* file_access.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 "file_access.h" |
32 | |
33 | #include "core/config/project_settings.h" |
34 | #include "core/crypto/crypto_core.h" |
35 | #include "core/io/file_access_compressed.h" |
36 | #include "core/io/file_access_encrypted.h" |
37 | #include "core/io/file_access_pack.h" |
38 | #include "core/io/marshalls.h" |
39 | #include "core/os/os.h" |
40 | |
41 | FileAccess::CreateFunc FileAccess::create_func[ACCESS_MAX] = { nullptr, nullptr }; |
42 | |
43 | FileAccess::FileCloseFailNotify FileAccess::close_fail_notify = nullptr; |
44 | |
45 | bool FileAccess::backup_save = false; |
46 | thread_local Error FileAccess::last_file_open_error = OK; |
47 | |
48 | Ref<FileAccess> FileAccess::create(AccessType p_access) { |
49 | ERR_FAIL_INDEX_V(p_access, ACCESS_MAX, nullptr); |
50 | |
51 | Ref<FileAccess> ret = create_func[p_access](); |
52 | ret->_set_access_type(p_access); |
53 | return ret; |
54 | } |
55 | |
56 | bool FileAccess::exists(const String &p_name) { |
57 | if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && PackedData::get_singleton()->has_path(p_name)) { |
58 | return true; |
59 | } |
60 | |
61 | Ref<FileAccess> f = open(p_name, READ); |
62 | if (f.is_null()) { |
63 | return false; |
64 | } |
65 | return true; |
66 | } |
67 | |
68 | void FileAccess::_set_access_type(AccessType p_access) { |
69 | _access_type = p_access; |
70 | } |
71 | |
72 | Ref<FileAccess> FileAccess::create_for_path(const String &p_path) { |
73 | Ref<FileAccess> ret; |
74 | if (p_path.begins_with("res://" )) { |
75 | ret = create(ACCESS_RESOURCES); |
76 | } else if (p_path.begins_with("user://" )) { |
77 | ret = create(ACCESS_USERDATA); |
78 | |
79 | } else { |
80 | ret = create(ACCESS_FILESYSTEM); |
81 | } |
82 | |
83 | return ret; |
84 | } |
85 | |
86 | Error FileAccess::reopen(const String &p_path, int p_mode_flags) { |
87 | return open_internal(p_path, p_mode_flags); |
88 | } |
89 | |
90 | Ref<FileAccess> FileAccess::open(const String &p_path, int p_mode_flags, Error *r_error) { |
91 | //try packed data first |
92 | |
93 | Ref<FileAccess> ret; |
94 | if (!(p_mode_flags & WRITE) && PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled()) { |
95 | ret = PackedData::get_singleton()->try_open_path(p_path); |
96 | if (ret.is_valid()) { |
97 | if (r_error) { |
98 | *r_error = OK; |
99 | } |
100 | return ret; |
101 | } |
102 | } |
103 | |
104 | ret = create_for_path(p_path); |
105 | Error err = ret->open_internal(p_path, p_mode_flags); |
106 | |
107 | if (r_error) { |
108 | *r_error = err; |
109 | } |
110 | if (err != OK) { |
111 | ret.unref(); |
112 | } |
113 | |
114 | return ret; |
115 | } |
116 | |
117 | Ref<FileAccess> FileAccess::_open(const String &p_path, ModeFlags p_mode_flags) { |
118 | Error err = OK; |
119 | Ref<FileAccess> fa = open(p_path, p_mode_flags, &err); |
120 | last_file_open_error = err; |
121 | if (err) { |
122 | return Ref<FileAccess>(); |
123 | } |
124 | return fa; |
125 | } |
126 | |
127 | Ref<FileAccess> FileAccess::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key) { |
128 | Ref<FileAccess> fa = _open(p_path, p_mode_flags); |
129 | if (fa.is_null()) { |
130 | return fa; |
131 | } |
132 | |
133 | Ref<FileAccessEncrypted> fae; |
134 | fae.instantiate(); |
135 | Error err = fae->open_and_parse(fa, p_key, (p_mode_flags == WRITE) ? FileAccessEncrypted::MODE_WRITE_AES256 : FileAccessEncrypted::MODE_READ); |
136 | last_file_open_error = err; |
137 | if (err) { |
138 | return Ref<FileAccess>(); |
139 | } |
140 | return fae; |
141 | } |
142 | |
143 | Ref<FileAccess> FileAccess::open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass) { |
144 | Ref<FileAccess> fa = _open(p_path, p_mode_flags); |
145 | if (fa.is_null()) { |
146 | return fa; |
147 | } |
148 | |
149 | Ref<FileAccessEncrypted> fae; |
150 | fae.instantiate(); |
151 | Error err = fae->open_and_parse_password(fa, p_pass, (p_mode_flags == WRITE) ? FileAccessEncrypted::MODE_WRITE_AES256 : FileAccessEncrypted::MODE_READ); |
152 | last_file_open_error = err; |
153 | if (err) { |
154 | return Ref<FileAccess>(); |
155 | } |
156 | return fae; |
157 | } |
158 | |
159 | Ref<FileAccess> FileAccess::open_compressed(const String &p_path, ModeFlags p_mode_flags, CompressionMode p_compress_mode) { |
160 | Ref<FileAccessCompressed> fac; |
161 | fac.instantiate(); |
162 | fac->configure("GCPF" , (Compression::Mode)p_compress_mode); |
163 | Error err = fac->open_internal(p_path, p_mode_flags); |
164 | last_file_open_error = err; |
165 | if (err) { |
166 | return Ref<FileAccess>(); |
167 | } |
168 | |
169 | return fac; |
170 | } |
171 | |
172 | Error FileAccess::get_open_error() { |
173 | return last_file_open_error; |
174 | } |
175 | |
176 | FileAccess::CreateFunc FileAccess::get_create_func(AccessType p_access) { |
177 | return create_func[p_access]; |
178 | } |
179 | |
180 | FileAccess::AccessType FileAccess::get_access_type() const { |
181 | return _access_type; |
182 | } |
183 | |
184 | String FileAccess::fix_path(const String &p_path) const { |
185 | //helper used by file accesses that use a single filesystem |
186 | |
187 | String r_path = p_path.replace("\\" , "/" ); |
188 | |
189 | switch (_access_type) { |
190 | case ACCESS_RESOURCES: { |
191 | if (ProjectSettings::get_singleton()) { |
192 | if (r_path.begins_with("res://" )) { |
193 | String resource_path = ProjectSettings::get_singleton()->get_resource_path(); |
194 | if (!resource_path.is_empty()) { |
195 | return r_path.replace("res:/" , resource_path); |
196 | } |
197 | return r_path.replace("res://" , "" ); |
198 | } |
199 | } |
200 | |
201 | } break; |
202 | case ACCESS_USERDATA: { |
203 | if (r_path.begins_with("user://" )) { |
204 | String data_dir = OS::get_singleton()->get_user_data_dir(); |
205 | if (!data_dir.is_empty()) { |
206 | return r_path.replace("user:/" , data_dir); |
207 | } |
208 | return r_path.replace("user://" , "" ); |
209 | } |
210 | |
211 | } break; |
212 | case ACCESS_FILESYSTEM: { |
213 | return r_path; |
214 | } break; |
215 | case ACCESS_MAX: |
216 | break; // Can't happen, but silences warning |
217 | } |
218 | |
219 | return r_path; |
220 | } |
221 | |
222 | /* these are all implemented for ease of porting, then can later be optimized */ |
223 | |
224 | uint16_t FileAccess::get_16() const { |
225 | uint16_t res; |
226 | uint8_t a, b; |
227 | |
228 | a = get_8(); |
229 | b = get_8(); |
230 | |
231 | if (big_endian) { |
232 | SWAP(a, b); |
233 | } |
234 | |
235 | res = b; |
236 | res <<= 8; |
237 | res |= a; |
238 | |
239 | return res; |
240 | } |
241 | |
242 | uint32_t FileAccess::get_32() const { |
243 | uint32_t res; |
244 | uint16_t a, b; |
245 | |
246 | a = get_16(); |
247 | b = get_16(); |
248 | |
249 | if (big_endian) { |
250 | SWAP(a, b); |
251 | } |
252 | |
253 | res = b; |
254 | res <<= 16; |
255 | res |= a; |
256 | |
257 | return res; |
258 | } |
259 | |
260 | uint64_t FileAccess::get_64() const { |
261 | uint64_t res; |
262 | uint32_t a, b; |
263 | |
264 | a = get_32(); |
265 | b = get_32(); |
266 | |
267 | if (big_endian) { |
268 | SWAP(a, b); |
269 | } |
270 | |
271 | res = b; |
272 | res <<= 32; |
273 | res |= a; |
274 | |
275 | return res; |
276 | } |
277 | |
278 | float FileAccess::get_float() const { |
279 | MarshallFloat m; |
280 | m.i = get_32(); |
281 | return m.f; |
282 | } |
283 | |
284 | real_t FileAccess::get_real() const { |
285 | if (real_is_double) { |
286 | return get_double(); |
287 | } else { |
288 | return get_float(); |
289 | } |
290 | } |
291 | |
292 | Variant FileAccess::get_var(bool p_allow_objects) const { |
293 | uint32_t len = get_32(); |
294 | Vector<uint8_t> buff = get_buffer(len); |
295 | ERR_FAIL_COND_V((uint32_t)buff.size() != len, Variant()); |
296 | |
297 | const uint8_t *r = buff.ptr(); |
298 | |
299 | Variant v; |
300 | Error err = decode_variant(v, &r[0], len, nullptr, p_allow_objects); |
301 | ERR_FAIL_COND_V_MSG(err != OK, Variant(), "Error when trying to encode Variant." ); |
302 | |
303 | return v; |
304 | } |
305 | |
306 | double FileAccess::get_double() const { |
307 | MarshallDouble m; |
308 | m.l = get_64(); |
309 | return m.d; |
310 | } |
311 | |
312 | String FileAccess::get_token() const { |
313 | CharString token; |
314 | |
315 | char32_t c = get_8(); |
316 | |
317 | while (!eof_reached()) { |
318 | if (c <= ' ') { |
319 | if (token.length()) { |
320 | break; |
321 | } |
322 | } else { |
323 | token += c; |
324 | } |
325 | c = get_8(); |
326 | } |
327 | |
328 | return String::utf8(token.get_data()); |
329 | } |
330 | |
331 | class CharBuffer { |
332 | Vector<char> vector; |
333 | char stack_buffer[256]; |
334 | |
335 | char *buffer = nullptr; |
336 | int capacity = 0; |
337 | int written = 0; |
338 | |
339 | bool grow() { |
340 | if (vector.resize(next_power_of_2(1 + written)) != OK) { |
341 | return false; |
342 | } |
343 | |
344 | if (buffer == stack_buffer) { // first chunk? |
345 | |
346 | for (int i = 0; i < written; i++) { |
347 | vector.write[i] = stack_buffer[i]; |
348 | } |
349 | } |
350 | |
351 | buffer = vector.ptrw(); |
352 | capacity = vector.size(); |
353 | ERR_FAIL_COND_V(written >= capacity, false); |
354 | |
355 | return true; |
356 | } |
357 | |
358 | public: |
359 | _FORCE_INLINE_ CharBuffer() : |
360 | buffer(stack_buffer), |
361 | capacity(sizeof(stack_buffer) / sizeof(char)) { |
362 | } |
363 | |
364 | _FORCE_INLINE_ void push_back(char c) { |
365 | if (written >= capacity) { |
366 | ERR_FAIL_COND(!grow()); |
367 | } |
368 | |
369 | buffer[written++] = c; |
370 | } |
371 | |
372 | _FORCE_INLINE_ const char *get_data() const { |
373 | return buffer; |
374 | } |
375 | }; |
376 | |
377 | String FileAccess::get_line() const { |
378 | CharBuffer line; |
379 | |
380 | char32_t c = get_8(); |
381 | |
382 | while (!eof_reached()) { |
383 | if (c == '\n' || c == '\0') { |
384 | line.push_back(0); |
385 | return String::utf8(line.get_data()); |
386 | } else if (c != '\r') { |
387 | line.push_back(c); |
388 | } |
389 | |
390 | c = get_8(); |
391 | } |
392 | line.push_back(0); |
393 | return String::utf8(line.get_data()); |
394 | } |
395 | |
396 | Vector<String> FileAccess::get_csv_line(const String &p_delim) const { |
397 | ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector<String>(), "Only single character delimiters are supported to parse CSV lines." ); |
398 | ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector<String>(), "The double quotation mark character (\") is not supported as a delimiter for CSV lines." ); |
399 | |
400 | String line; |
401 | |
402 | // CSV can support entries with line breaks as long as they are enclosed |
403 | // in double quotes. So our "line" might be more than a single line in the |
404 | // text file. |
405 | int qc = 0; |
406 | do { |
407 | if (eof_reached()) { |
408 | break; |
409 | } |
410 | line += get_line() + "\n" ; |
411 | qc = 0; |
412 | for (int i = 0; i < line.length(); i++) { |
413 | if (line[i] == '"') { |
414 | qc++; |
415 | } |
416 | } |
417 | } while (qc % 2); |
418 | |
419 | // Remove the extraneous newline we've added above. |
420 | line = line.substr(0, line.length() - 1); |
421 | |
422 | Vector<String> strings; |
423 | |
424 | bool in_quote = false; |
425 | String current; |
426 | for (int i = 0; i < line.length(); i++) { |
427 | char32_t c = line[i]; |
428 | // A delimiter ends the current entry, unless it's in a quoted string. |
429 | if (!in_quote && c == p_delim[0]) { |
430 | strings.push_back(current); |
431 | current = String(); |
432 | } else if (c == '"') { |
433 | // Doubled quotes are escapes for intentional quotes in the string. |
434 | if (line[i + 1] == '"' && in_quote) { |
435 | current += '"'; |
436 | i++; |
437 | } else { |
438 | in_quote = !in_quote; |
439 | } |
440 | } else { |
441 | current += c; |
442 | } |
443 | } |
444 | |
445 | if (in_quote) { |
446 | WARN_PRINT(vformat("Reached end of file before closing '\"' in CSV file '%s'." , get_path())); |
447 | } |
448 | |
449 | strings.push_back(current); |
450 | |
451 | return strings; |
452 | } |
453 | |
454 | String FileAccess::get_as_text(bool p_skip_cr) const { |
455 | uint64_t original_pos = get_position(); |
456 | const_cast<FileAccess *>(this)->seek(0); |
457 | |
458 | String text = get_as_utf8_string(p_skip_cr); |
459 | |
460 | const_cast<FileAccess *>(this)->seek(original_pos); |
461 | |
462 | return text; |
463 | } |
464 | |
465 | uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const { |
466 | ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); |
467 | |
468 | uint64_t i = 0; |
469 | for (i = 0; i < p_length && !eof_reached(); i++) { |
470 | p_dst[i] = get_8(); |
471 | } |
472 | |
473 | return i; |
474 | } |
475 | |
476 | Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const { |
477 | Vector<uint8_t> data; |
478 | |
479 | ERR_FAIL_COND_V_MSG(p_length < 0, data, "Length of buffer cannot be smaller than 0." ); |
480 | if (p_length == 0) { |
481 | return data; |
482 | } |
483 | |
484 | Error err = data.resize(p_length); |
485 | ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements." ); |
486 | |
487 | uint8_t *w = data.ptrw(); |
488 | int64_t len = get_buffer(&w[0], p_length); |
489 | |
490 | if (len < p_length) { |
491 | data.resize(len); |
492 | } |
493 | |
494 | return data; |
495 | } |
496 | |
497 | String FileAccess::get_as_utf8_string(bool p_skip_cr) const { |
498 | Vector<uint8_t> sourcef; |
499 | uint64_t len = get_length(); |
500 | sourcef.resize(len + 1); |
501 | |
502 | uint8_t *w = sourcef.ptrw(); |
503 | uint64_t r = get_buffer(w, len); |
504 | ERR_FAIL_COND_V(r != len, String()); |
505 | w[len] = 0; |
506 | |
507 | String s; |
508 | s.parse_utf8((const char *)w, -1, p_skip_cr); |
509 | return s; |
510 | } |
511 | |
512 | void FileAccess::store_16(uint16_t p_dest) { |
513 | uint8_t a, b; |
514 | |
515 | a = p_dest & 0xFF; |
516 | b = p_dest >> 8; |
517 | |
518 | if (big_endian) { |
519 | SWAP(a, b); |
520 | } |
521 | |
522 | store_8(a); |
523 | store_8(b); |
524 | } |
525 | |
526 | void FileAccess::store_32(uint32_t p_dest) { |
527 | uint16_t a, b; |
528 | |
529 | a = p_dest & 0xFFFF; |
530 | b = p_dest >> 16; |
531 | |
532 | if (big_endian) { |
533 | SWAP(a, b); |
534 | } |
535 | |
536 | store_16(a); |
537 | store_16(b); |
538 | } |
539 | |
540 | void FileAccess::store_64(uint64_t p_dest) { |
541 | uint32_t a, b; |
542 | |
543 | a = p_dest & 0xFFFFFFFF; |
544 | b = p_dest >> 32; |
545 | |
546 | if (big_endian) { |
547 | SWAP(a, b); |
548 | } |
549 | |
550 | store_32(a); |
551 | store_32(b); |
552 | } |
553 | |
554 | void FileAccess::store_real(real_t p_real) { |
555 | if constexpr (sizeof(real_t) == 4) { |
556 | store_float(p_real); |
557 | } else { |
558 | store_double(p_real); |
559 | } |
560 | } |
561 | |
562 | void FileAccess::store_float(float p_dest) { |
563 | MarshallFloat m; |
564 | m.f = p_dest; |
565 | store_32(m.i); |
566 | } |
567 | |
568 | void FileAccess::store_double(double p_dest) { |
569 | MarshallDouble m; |
570 | m.d = p_dest; |
571 | store_64(m.l); |
572 | } |
573 | |
574 | uint64_t FileAccess::get_modified_time(const String &p_file) { |
575 | if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { |
576 | return 0; |
577 | } |
578 | |
579 | Ref<FileAccess> fa = create_for_path(p_file); |
580 | ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'." ); |
581 | |
582 | uint64_t mt = fa->_get_modified_time(p_file); |
583 | return mt; |
584 | } |
585 | |
586 | BitField<FileAccess::UnixPermissionFlags> FileAccess::get_unix_permissions(const String &p_file) { |
587 | if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { |
588 | return 0; |
589 | } |
590 | |
591 | Ref<FileAccess> fa = create_for_path(p_file); |
592 | ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'." ); |
593 | |
594 | return fa->_get_unix_permissions(p_file); |
595 | } |
596 | |
597 | Error FileAccess::set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) { |
598 | if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { |
599 | return ERR_UNAVAILABLE; |
600 | } |
601 | |
602 | Ref<FileAccess> fa = create_for_path(p_file); |
603 | ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'." ); |
604 | |
605 | Error err = fa->_set_unix_permissions(p_file, p_permissions); |
606 | return err; |
607 | } |
608 | |
609 | bool FileAccess::get_hidden_attribute(const String &p_file) { |
610 | if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { |
611 | return false; |
612 | } |
613 | |
614 | Ref<FileAccess> fa = create_for_path(p_file); |
615 | ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'." ); |
616 | |
617 | return fa->_get_hidden_attribute(p_file); |
618 | } |
619 | |
620 | Error FileAccess::set_hidden_attribute(const String &p_file, bool p_hidden) { |
621 | if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { |
622 | return ERR_UNAVAILABLE; |
623 | } |
624 | |
625 | Ref<FileAccess> fa = create_for_path(p_file); |
626 | ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'." ); |
627 | |
628 | Error err = fa->_set_hidden_attribute(p_file, p_hidden); |
629 | return err; |
630 | } |
631 | |
632 | bool FileAccess::get_read_only_attribute(const String &p_file) { |
633 | if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { |
634 | return false; |
635 | } |
636 | |
637 | Ref<FileAccess> fa = create_for_path(p_file); |
638 | ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'." ); |
639 | |
640 | return fa->_get_read_only_attribute(p_file); |
641 | } |
642 | |
643 | Error FileAccess::set_read_only_attribute(const String &p_file, bool p_ro) { |
644 | if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { |
645 | return ERR_UNAVAILABLE; |
646 | } |
647 | |
648 | Ref<FileAccess> fa = create_for_path(p_file); |
649 | ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'." ); |
650 | |
651 | Error err = fa->_set_read_only_attribute(p_file, p_ro); |
652 | return err; |
653 | } |
654 | |
655 | void FileAccess::store_string(const String &p_string) { |
656 | if (p_string.length() == 0) { |
657 | return; |
658 | } |
659 | |
660 | CharString cs = p_string.utf8(); |
661 | store_buffer((uint8_t *)&cs[0], cs.length()); |
662 | } |
663 | |
664 | void FileAccess::store_pascal_string(const String &p_string) { |
665 | CharString cs = p_string.utf8(); |
666 | store_32(cs.length()); |
667 | store_buffer((uint8_t *)&cs[0], cs.length()); |
668 | } |
669 | |
670 | String FileAccess::get_pascal_string() { |
671 | uint32_t sl = get_32(); |
672 | CharString cs; |
673 | cs.resize(sl + 1); |
674 | get_buffer((uint8_t *)cs.ptr(), sl); |
675 | cs[sl] = 0; |
676 | |
677 | String ret; |
678 | ret.parse_utf8(cs.ptr()); |
679 | return ret; |
680 | } |
681 | |
682 | void FileAccess::store_line(const String &p_line) { |
683 | store_string(p_line); |
684 | store_8('\n'); |
685 | } |
686 | |
687 | void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_delim) { |
688 | ERR_FAIL_COND(p_delim.length() != 1); |
689 | |
690 | String line = "" ; |
691 | int size = p_values.size(); |
692 | for (int i = 0; i < size; ++i) { |
693 | String value = p_values[i]; |
694 | |
695 | if (value.contains("\"" ) || value.contains(p_delim) || value.contains("\n" )) { |
696 | value = "\"" + value.replace("\"" , "\"\"" ) + "\"" ; |
697 | } |
698 | if (i < size - 1) { |
699 | value += p_delim; |
700 | } |
701 | |
702 | line += value; |
703 | } |
704 | |
705 | store_line(line); |
706 | } |
707 | |
708 | void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) { |
709 | ERR_FAIL_COND(!p_src && p_length > 0); |
710 | for (uint64_t i = 0; i < p_length; i++) { |
711 | store_8(p_src[i]); |
712 | } |
713 | } |
714 | |
715 | void FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) { |
716 | uint64_t len = p_buffer.size(); |
717 | if (len == 0) { |
718 | return; |
719 | } |
720 | |
721 | const uint8_t *r = p_buffer.ptr(); |
722 | |
723 | store_buffer(&r[0], len); |
724 | } |
725 | |
726 | void FileAccess::store_var(const Variant &p_var, bool p_full_objects) { |
727 | int len; |
728 | Error err = encode_variant(p_var, nullptr, len, p_full_objects); |
729 | ERR_FAIL_COND_MSG(err != OK, "Error when trying to encode Variant." ); |
730 | |
731 | Vector<uint8_t> buff; |
732 | buff.resize(len); |
733 | |
734 | uint8_t *w = buff.ptrw(); |
735 | err = encode_variant(p_var, &w[0], len, p_full_objects); |
736 | ERR_FAIL_COND_MSG(err != OK, "Error when trying to encode Variant." ); |
737 | |
738 | store_32(len); |
739 | store_buffer(buff); |
740 | } |
741 | |
742 | Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_error) { |
743 | Ref<FileAccess> f = FileAccess::open(p_path, READ, r_error); |
744 | if (f.is_null()) { |
745 | if (r_error) { // if error requested, do not throw error |
746 | return Vector<uint8_t>(); |
747 | } |
748 | ERR_FAIL_V_MSG(Vector<uint8_t>(), "Can't open file from path '" + String(p_path) + "'." ); |
749 | } |
750 | Vector<uint8_t> data; |
751 | data.resize(f->get_length()); |
752 | f->get_buffer(data.ptrw(), data.size()); |
753 | return data; |
754 | } |
755 | |
756 | String FileAccess::get_file_as_string(const String &p_path, Error *r_error) { |
757 | Error err; |
758 | Vector<uint8_t> array = get_file_as_bytes(p_path, &err); |
759 | if (r_error) { |
760 | *r_error = err; |
761 | } |
762 | if (err != OK) { |
763 | if (r_error) { |
764 | return String(); |
765 | } |
766 | ERR_FAIL_V_MSG(String(), "Can't get file as string from path '" + String(p_path) + "'." ); |
767 | } |
768 | |
769 | String ret; |
770 | ret.parse_utf8((const char *)array.ptr(), array.size()); |
771 | return ret; |
772 | } |
773 | |
774 | String FileAccess::get_md5(const String &p_file) { |
775 | Ref<FileAccess> f = FileAccess::open(p_file, READ); |
776 | if (f.is_null()) { |
777 | return String(); |
778 | } |
779 | |
780 | CryptoCore::MD5Context ctx; |
781 | ctx.start(); |
782 | |
783 | unsigned char step[32768]; |
784 | |
785 | while (true) { |
786 | uint64_t br = f->get_buffer(step, 32768); |
787 | if (br > 0) { |
788 | ctx.update(step, br); |
789 | } |
790 | if (br < 4096) { |
791 | break; |
792 | } |
793 | } |
794 | |
795 | unsigned char hash[16]; |
796 | ctx.finish(hash); |
797 | |
798 | return String::md5(hash); |
799 | } |
800 | |
801 | String FileAccess::get_multiple_md5(const Vector<String> &p_file) { |
802 | CryptoCore::MD5Context ctx; |
803 | ctx.start(); |
804 | |
805 | for (int i = 0; i < p_file.size(); i++) { |
806 | Ref<FileAccess> f = FileAccess::open(p_file[i], READ); |
807 | ERR_CONTINUE(f.is_null()); |
808 | |
809 | unsigned char step[32768]; |
810 | |
811 | while (true) { |
812 | uint64_t br = f->get_buffer(step, 32768); |
813 | if (br > 0) { |
814 | ctx.update(step, br); |
815 | } |
816 | if (br < 4096) { |
817 | break; |
818 | } |
819 | } |
820 | } |
821 | |
822 | unsigned char hash[16]; |
823 | ctx.finish(hash); |
824 | |
825 | return String::md5(hash); |
826 | } |
827 | |
828 | String FileAccess::get_sha256(const String &p_file) { |
829 | Ref<FileAccess> f = FileAccess::open(p_file, READ); |
830 | if (f.is_null()) { |
831 | return String(); |
832 | } |
833 | |
834 | CryptoCore::SHA256Context ctx; |
835 | ctx.start(); |
836 | |
837 | unsigned char step[32768]; |
838 | |
839 | while (true) { |
840 | uint64_t br = f->get_buffer(step, 32768); |
841 | if (br > 0) { |
842 | ctx.update(step, br); |
843 | } |
844 | if (br < 4096) { |
845 | break; |
846 | } |
847 | } |
848 | |
849 | unsigned char hash[32]; |
850 | ctx.finish(hash); |
851 | |
852 | return String::hex_encode_buffer(hash, 32); |
853 | } |
854 | |
855 | void FileAccess::_bind_methods() { |
856 | ClassDB::bind_static_method("FileAccess" , D_METHOD("open" , "path" , "flags" ), &FileAccess::_open); |
857 | ClassDB::bind_static_method("FileAccess" , D_METHOD("open_encrypted" , "path" , "mode_flags" , "key" ), &FileAccess::open_encrypted); |
858 | ClassDB::bind_static_method("FileAccess" , D_METHOD("open_encrypted_with_pass" , "path" , "mode_flags" , "pass" ), &FileAccess::open_encrypted_pass); |
859 | ClassDB::bind_static_method("FileAccess" , D_METHOD("open_compressed" , "path" , "mode_flags" , "compression_mode" ), &FileAccess::open_compressed, DEFVAL(0)); |
860 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_open_error" ), &FileAccess::get_open_error); |
861 | |
862 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_file_as_bytes" , "path" ), &FileAccess::_get_file_as_bytes); |
863 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_file_as_string" , "path" ), &FileAccess::_get_file_as_string); |
864 | |
865 | ClassDB::bind_method(D_METHOD("flush" ), &FileAccess::flush); |
866 | ClassDB::bind_method(D_METHOD("get_path" ), &FileAccess::get_path); |
867 | ClassDB::bind_method(D_METHOD("get_path_absolute" ), &FileAccess::get_path_absolute); |
868 | ClassDB::bind_method(D_METHOD("is_open" ), &FileAccess::is_open); |
869 | ClassDB::bind_method(D_METHOD("seek" , "position" ), &FileAccess::seek); |
870 | ClassDB::bind_method(D_METHOD("seek_end" , "position" ), &FileAccess::seek_end, DEFVAL(0)); |
871 | ClassDB::bind_method(D_METHOD("get_position" ), &FileAccess::get_position); |
872 | ClassDB::bind_method(D_METHOD("get_length" ), &FileAccess::get_length); |
873 | ClassDB::bind_method(D_METHOD("eof_reached" ), &FileAccess::eof_reached); |
874 | ClassDB::bind_method(D_METHOD("get_8" ), &FileAccess::get_8); |
875 | ClassDB::bind_method(D_METHOD("get_16" ), &FileAccess::get_16); |
876 | ClassDB::bind_method(D_METHOD("get_32" ), &FileAccess::get_32); |
877 | ClassDB::bind_method(D_METHOD("get_64" ), &FileAccess::get_64); |
878 | ClassDB::bind_method(D_METHOD("get_float" ), &FileAccess::get_float); |
879 | ClassDB::bind_method(D_METHOD("get_double" ), &FileAccess::get_double); |
880 | ClassDB::bind_method(D_METHOD("get_real" ), &FileAccess::get_real); |
881 | ClassDB::bind_method(D_METHOD("get_buffer" , "length" ), (Vector<uint8_t>(FileAccess::*)(int64_t) const) & FileAccess::get_buffer); |
882 | ClassDB::bind_method(D_METHOD("get_line" ), &FileAccess::get_line); |
883 | ClassDB::bind_method(D_METHOD("get_csv_line" , "delim" ), &FileAccess::get_csv_line, DEFVAL("," )); |
884 | ClassDB::bind_method(D_METHOD("get_as_text" , "skip_cr" ), &FileAccess::get_as_text, DEFVAL(false)); |
885 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_md5" , "path" ), &FileAccess::get_md5); |
886 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_sha256" , "path" ), &FileAccess::get_sha256); |
887 | ClassDB::bind_method(D_METHOD("is_big_endian" ), &FileAccess::is_big_endian); |
888 | ClassDB::bind_method(D_METHOD("set_big_endian" , "big_endian" ), &FileAccess::set_big_endian); |
889 | ClassDB::bind_method(D_METHOD("get_error" ), &FileAccess::get_error); |
890 | ClassDB::bind_method(D_METHOD("get_var" , "allow_objects" ), &FileAccess::get_var, DEFVAL(false)); |
891 | |
892 | ClassDB::bind_method(D_METHOD("store_8" , "value" ), &FileAccess::store_8); |
893 | ClassDB::bind_method(D_METHOD("store_16" , "value" ), &FileAccess::store_16); |
894 | ClassDB::bind_method(D_METHOD("store_32" , "value" ), &FileAccess::store_32); |
895 | ClassDB::bind_method(D_METHOD("store_64" , "value" ), &FileAccess::store_64); |
896 | ClassDB::bind_method(D_METHOD("store_float" , "value" ), &FileAccess::store_float); |
897 | ClassDB::bind_method(D_METHOD("store_double" , "value" ), &FileAccess::store_double); |
898 | ClassDB::bind_method(D_METHOD("store_real" , "value" ), &FileAccess::store_real); |
899 | ClassDB::bind_method(D_METHOD("store_buffer" , "buffer" ), (void(FileAccess::*)(const Vector<uint8_t> &)) & FileAccess::store_buffer); |
900 | ClassDB::bind_method(D_METHOD("store_line" , "line" ), &FileAccess::store_line); |
901 | ClassDB::bind_method(D_METHOD("store_csv_line" , "values" , "delim" ), &FileAccess::store_csv_line, DEFVAL("," )); |
902 | ClassDB::bind_method(D_METHOD("store_string" , "string" ), &FileAccess::store_string); |
903 | ClassDB::bind_method(D_METHOD("store_var" , "value" , "full_objects" ), &FileAccess::store_var, DEFVAL(false)); |
904 | |
905 | ClassDB::bind_method(D_METHOD("store_pascal_string" , "string" ), &FileAccess::store_pascal_string); |
906 | ClassDB::bind_method(D_METHOD("get_pascal_string" ), &FileAccess::get_pascal_string); |
907 | |
908 | ClassDB::bind_method(D_METHOD("close" ), &FileAccess::close); |
909 | |
910 | ClassDB::bind_static_method("FileAccess" , D_METHOD("file_exists" , "path" ), &FileAccess::exists); |
911 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_modified_time" , "file" ), &FileAccess::get_modified_time); |
912 | |
913 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_unix_permissions" , "file" ), &FileAccess::get_unix_permissions); |
914 | ClassDB::bind_static_method("FileAccess" , D_METHOD("set_unix_permissions" , "file" , "permissions" ), &FileAccess::set_unix_permissions); |
915 | |
916 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_hidden_attribute" , "file" ), &FileAccess::get_hidden_attribute); |
917 | ClassDB::bind_static_method("FileAccess" , D_METHOD("set_hidden_attribute" , "file" , "hidden" ), &FileAccess::set_hidden_attribute); |
918 | ClassDB::bind_static_method("FileAccess" , D_METHOD("set_read_only_attribute" , "file" , "ro" ), &FileAccess::set_read_only_attribute); |
919 | ClassDB::bind_static_method("FileAccess" , D_METHOD("get_read_only_attribute" , "file" ), &FileAccess::get_read_only_attribute); |
920 | |
921 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "big_endian" ), "set_big_endian" , "is_big_endian" ); |
922 | |
923 | BIND_ENUM_CONSTANT(READ); |
924 | BIND_ENUM_CONSTANT(WRITE); |
925 | BIND_ENUM_CONSTANT(READ_WRITE); |
926 | BIND_ENUM_CONSTANT(WRITE_READ); |
927 | |
928 | BIND_ENUM_CONSTANT(COMPRESSION_FASTLZ); |
929 | BIND_ENUM_CONSTANT(COMPRESSION_DEFLATE); |
930 | BIND_ENUM_CONSTANT(COMPRESSION_ZSTD); |
931 | BIND_ENUM_CONSTANT(COMPRESSION_GZIP); |
932 | BIND_ENUM_CONSTANT(COMPRESSION_BROTLI); |
933 | |
934 | BIND_BITFIELD_FLAG(UNIX_READ_OWNER); |
935 | BIND_BITFIELD_FLAG(UNIX_WRITE_OWNER); |
936 | BIND_BITFIELD_FLAG(UNIX_EXECUTE_OWNER); |
937 | BIND_BITFIELD_FLAG(UNIX_READ_GROUP); |
938 | BIND_BITFIELD_FLAG(UNIX_WRITE_GROUP); |
939 | BIND_BITFIELD_FLAG(UNIX_EXECUTE_GROUP); |
940 | BIND_BITFIELD_FLAG(UNIX_READ_OTHER); |
941 | BIND_BITFIELD_FLAG(UNIX_WRITE_OTHER); |
942 | BIND_BITFIELD_FLAG(UNIX_EXECUTE_OTHER); |
943 | BIND_BITFIELD_FLAG(UNIX_SET_USER_ID); |
944 | BIND_BITFIELD_FLAG(UNIX_SET_GROUP_ID); |
945 | BIND_BITFIELD_FLAG(UNIX_RESTRICTED_DELETE); |
946 | } |
947 | |