1/**************************************************************************/
2/* resource_format_text.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 "resource_format_text.h"
32
33#include "core/config/project_settings.h"
34#include "core/io/dir_access.h"
35#include "core/io/missing_resource.h"
36#include "core/io/resource_format_binary.h"
37#include "core/object/script_language.h"
38#include "core/version.h"
39
40// Version 2: changed names for Basis, AABB, Vectors, etc.
41// Version 3: new string ID for ext/subresources, breaks forward compat.
42#define FORMAT_VERSION 3
43
44#define BINARY_FORMAT_VERSION 4
45
46#include "core/io/dir_access.h"
47#include "core/version.h"
48
49#define _printerr() ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data());
50
51///
52
53Ref<Resource> ResourceLoaderText::get_resource() {
54 return resource;
55}
56
57Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) {
58 VariantParser::Token token;
59 VariantParser::get_token(p_stream, token, line, r_err_str);
60 if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) {
61 r_err_str = "Expected number (old style) or string (sub-resource index)";
62 return ERR_PARSE_ERROR;
63 }
64
65 if (p_data->no_placeholders) {
66 r_res.unref();
67 } else {
68 String unique_id = token.value;
69
70 if (!p_data->resource_map.has(unique_id)) {
71 r_err_str = "Found unique_id reference before mapping, sub-resources stored out of order in resource file";
72 return ERR_PARSE_ERROR;
73 }
74
75 r_res = p_data->resource_map[unique_id];
76 }
77
78 VariantParser::get_token(p_stream, token, line, r_err_str);
79 if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) {
80 r_err_str = "Expected ')'";
81 return ERR_PARSE_ERROR;
82 }
83
84 return OK;
85}
86
87Error ResourceLoaderText::_parse_ext_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) {
88 VariantParser::Token token;
89 VariantParser::get_token(p_stream, token, line, r_err_str);
90 if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) {
91 r_err_str = "Expected number (old style sub-resource index) or String (ext-resource ID)";
92 return ERR_PARSE_ERROR;
93 }
94
95 if (p_data->no_placeholders) {
96 r_res.unref();
97 } else {
98 String id = token.value;
99
100 ERR_FAIL_COND_V(!p_data->rev_external_resources.has(id), ERR_PARSE_ERROR);
101
102 r_res = p_data->rev_external_resources[id];
103 }
104
105 VariantParser::get_token(p_stream, token, line, r_err_str);
106 if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) {
107 r_err_str = "Expected ')'";
108 return ERR_PARSE_ERROR;
109 }
110
111 return OK;
112}
113
114Error ResourceLoaderText::_parse_sub_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) {
115 VariantParser::Token token;
116 VariantParser::get_token(p_stream, token, line, r_err_str);
117 if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) {
118 r_err_str = "Expected number (old style sub-resource index) or string";
119 return ERR_PARSE_ERROR;
120 }
121
122 String id = token.value;
123 ERR_FAIL_COND_V(!int_resources.has(id), ERR_INVALID_PARAMETER);
124 r_res = int_resources[id];
125
126 VariantParser::get_token(p_stream, token, line, r_err_str);
127 if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) {
128 r_err_str = "Expected ')'";
129 return ERR_PARSE_ERROR;
130 }
131
132 return OK;
133}
134
135Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) {
136 VariantParser::Token token;
137 VariantParser::get_token(p_stream, token, line, r_err_str);
138 if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) {
139 r_err_str = "Expected number (old style sub-resource index) or String (ext-resource ID)";
140 return ERR_PARSE_ERROR;
141 }
142
143 String id = token.value;
144 Error err = OK;
145
146 if (!ignore_resource_parsing) {
147 if (!ext_resources.has(id)) {
148 r_err_str = "Can't load cached ext-resource id: " + id;
149 return ERR_PARSE_ERROR;
150 }
151
152 String path = ext_resources[id].path;
153 String type = ext_resources[id].type;
154 Ref<ResourceLoader::LoadToken> &load_token = ext_resources[id].load_token;
155
156 if (load_token.is_valid()) { // If not valid, it's OK since then we know this load accepts broken dependencies.
157 Ref<Resource> res = ResourceLoader::_load_complete(*load_token.ptr(), &err);
158 if (res.is_null()) {
159 if (!ResourceLoader::is_cleaning_tasks()) {
160 if (ResourceLoader::get_abort_on_missing_resources()) {
161 error = ERR_FILE_MISSING_DEPENDENCIES;
162 error_text = "[ext_resource] referenced non-existent resource at: " + path;
163 _printerr();
164 err = error;
165 } else {
166 ResourceLoader::notify_dependency_error(local_path, path, type);
167 }
168 }
169 } else {
170#ifdef TOOLS_ENABLED
171 //remember ID for saving
172 res->set_id_for_path(local_path, id);
173#endif
174 r_res = res;
175 }
176 } else {
177 r_res = Ref<Resource>();
178 }
179 }
180
181 VariantParser::get_token(p_stream, token, line, r_err_str);
182 if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) {
183 r_err_str = "Expected ')'";
184 return ERR_PARSE_ERROR;
185 }
186
187 return err;
188}
189
190Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourceParser &parser) {
191 Ref<PackedScene> packed_scene;
192 packed_scene.instantiate();
193
194 while (true) {
195 if (next_tag.name == "node") {
196 int parent = -1;
197 int owner = -1;
198 int type = -1;
199 int name = -1;
200 int instance = -1;
201 int index = -1;
202 //int base_scene=-1;
203
204 if (next_tag.fields.has("name")) {
205 name = packed_scene->get_state()->add_name(next_tag.fields["name"]);
206 }
207
208 if (next_tag.fields.has("parent")) {
209 NodePath np = next_tag.fields["parent"];
210 np.prepend_period(); //compatible to how it manages paths internally
211 parent = packed_scene->get_state()->add_node_path(np);
212 }
213
214 if (next_tag.fields.has("type")) {
215 type = packed_scene->get_state()->add_name(next_tag.fields["type"]);
216 } else {
217 type = SceneState::TYPE_INSTANTIATED; //no type? assume this was instantiated
218 }
219
220 HashSet<StringName> path_properties;
221
222 if (next_tag.fields.has("node_paths")) {
223 Vector<String> paths = next_tag.fields["node_paths"];
224 for (int i = 0; i < paths.size(); i++) {
225 path_properties.insert(paths[i]);
226 }
227 }
228
229 if (next_tag.fields.has("instance")) {
230 instance = packed_scene->get_state()->add_value(next_tag.fields["instance"]);
231
232 if (packed_scene->get_state()->get_node_count() == 0 && parent == -1) {
233 packed_scene->get_state()->set_base_scene(instance);
234 instance = -1;
235 }
236 }
237
238 if (next_tag.fields.has("instance_placeholder")) {
239 String path = next_tag.fields["instance_placeholder"];
240
241 int path_v = packed_scene->get_state()->add_value(path);
242
243 if (packed_scene->get_state()->get_node_count() == 0) {
244 error = ERR_FILE_CORRUPT;
245 error_text = "Instance Placeholder can't be used for inheritance.";
246 _printerr();
247 return Ref<PackedScene>();
248 }
249
250 instance = path_v | SceneState::FLAG_INSTANCE_IS_PLACEHOLDER;
251 }
252
253 if (next_tag.fields.has("owner")) {
254 owner = packed_scene->get_state()->add_node_path(next_tag.fields["owner"]);
255 } else {
256 if (parent != -1 && !(type == SceneState::TYPE_INSTANTIATED && instance == -1)) {
257 owner = 0; //if no owner, owner is root
258 }
259 }
260
261 if (next_tag.fields.has("index")) {
262 index = next_tag.fields["index"];
263 }
264
265 int node_id = packed_scene->get_state()->add_node(parent, owner, type, name, instance, index);
266
267 if (next_tag.fields.has("groups")) {
268 Array groups = next_tag.fields["groups"];
269 for (int i = 0; i < groups.size(); i++) {
270 packed_scene->get_state()->add_node_group(node_id, packed_scene->get_state()->add_name(groups[i]));
271 }
272 }
273
274 while (true) {
275 String assign;
276 Variant value;
277
278 error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &parser);
279
280 if (error) {
281 if (error == ERR_FILE_MISSING_DEPENDENCIES) {
282 // Resource loading error, just skip it.
283 } else if (error != ERR_FILE_EOF) {
284 _printerr();
285 return Ref<PackedScene>();
286 } else {
287 error = OK;
288 return packed_scene;
289 }
290 }
291
292 if (!assign.is_empty()) {
293 StringName assign_name = assign;
294 int nameidx = packed_scene->get_state()->add_name(assign_name);
295 int valueidx = packed_scene->get_state()->add_value(value);
296 packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx, path_properties.has(assign_name));
297 //it's assignment
298 } else if (!next_tag.name.is_empty()) {
299 break;
300 }
301 }
302 } else if (next_tag.name == "connection") {
303 if (!next_tag.fields.has("from")) {
304 error = ERR_FILE_CORRUPT;
305 error_text = "missing 'from' field from connection tag";
306 return Ref<PackedScene>();
307 }
308
309 if (!next_tag.fields.has("to")) {
310 error = ERR_FILE_CORRUPT;
311 error_text = "missing 'to' field from connection tag";
312 return Ref<PackedScene>();
313 }
314
315 if (!next_tag.fields.has("signal")) {
316 error = ERR_FILE_CORRUPT;
317 error_text = "missing 'signal' field from connection tag";
318 return Ref<PackedScene>();
319 }
320
321 if (!next_tag.fields.has("method")) {
322 error = ERR_FILE_CORRUPT;
323 error_text = "missing 'method' field from connection tag";
324 return Ref<PackedScene>();
325 }
326
327 NodePath from = next_tag.fields["from"];
328 NodePath to = next_tag.fields["to"];
329 StringName method = next_tag.fields["method"];
330 StringName signal = next_tag.fields["signal"];
331 int flags = Object::CONNECT_PERSIST;
332 int unbinds = 0;
333 Array binds;
334
335 if (next_tag.fields.has("flags")) {
336 flags = next_tag.fields["flags"];
337 }
338
339 if (next_tag.fields.has("binds")) {
340 binds = next_tag.fields["binds"];
341 }
342
343 if (next_tag.fields.has("unbinds")) {
344 unbinds = next_tag.fields["unbinds"];
345 }
346
347 Vector<int> bind_ints;
348 for (int i = 0; i < binds.size(); i++) {
349 bind_ints.push_back(packed_scene->get_state()->add_value(binds[i]));
350 }
351
352 packed_scene->get_state()->add_connection(
353 packed_scene->get_state()->add_node_path(from.simplified()),
354 packed_scene->get_state()->add_node_path(to.simplified()),
355 packed_scene->get_state()->add_name(signal),
356 packed_scene->get_state()->add_name(method),
357 flags,
358 unbinds,
359 bind_ints);
360
361 error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &parser);
362
363 if (error) {
364 if (error != ERR_FILE_EOF) {
365 _printerr();
366 return Ref<PackedScene>();
367 } else {
368 error = OK;
369 return packed_scene;
370 }
371 }
372 } else if (next_tag.name == "editable") {
373 if (!next_tag.fields.has("path")) {
374 error = ERR_FILE_CORRUPT;
375 error_text = "missing 'path' field from editable tag";
376 _printerr();
377 return Ref<PackedScene>();
378 }
379
380 NodePath path = next_tag.fields["path"];
381
382 packed_scene->get_state()->add_editable_instance(path.simplified());
383
384 error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &parser);
385
386 if (error) {
387 if (error != ERR_FILE_EOF) {
388 _printerr();
389 return Ref<PackedScene>();
390 } else {
391 error = OK;
392 return packed_scene;
393 }
394 }
395 } else {
396 error = ERR_FILE_CORRUPT;
397 _printerr();
398 return Ref<PackedScene>();
399 }
400 }
401}
402
403Error ResourceLoaderText::load() {
404 if (error != OK) {
405 return error;
406 }
407
408 while (true) {
409 if (next_tag.name != "ext_resource") {
410 break;
411 }
412
413 if (!next_tag.fields.has("path")) {
414 error = ERR_FILE_CORRUPT;
415 error_text = "Missing 'path' in external resource tag";
416 _printerr();
417 return error;
418 }
419
420 if (!next_tag.fields.has("type")) {
421 error = ERR_FILE_CORRUPT;
422 error_text = "Missing 'type' in external resource tag";
423 _printerr();
424 return error;
425 }
426
427 if (!next_tag.fields.has("id")) {
428 error = ERR_FILE_CORRUPT;
429 error_text = "Missing 'id' in external resource tag";
430 _printerr();
431 return error;
432 }
433
434 String path = next_tag.fields["path"];
435 String type = next_tag.fields["type"];
436 String id = next_tag.fields["id"];
437
438 if (next_tag.fields.has("uid")) {
439 String uidt = next_tag.fields["uid"];
440 ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(uidt);
441 if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) {
442 // If a UID is found and the path is valid, it will be used, otherwise, it falls back to the path.
443 path = ResourceUID::get_singleton()->get_id_path(uid);
444 } else {
445#ifdef TOOLS_ENABLED
446 // Silence a warning that can happen during the initial filesystem scan due to cache being regenerated.
447 if (ResourceLoader::get_resource_uid(path) != uid) {
448 WARN_PRINT(String(res_path + ":" + itos(lines) + " - ext_resource, invalid UID: " + uidt + " - using text path instead: " + path).utf8().get_data());
449 }
450#else
451 WARN_PRINT(String(res_path + ":" + itos(lines) + " - ext_resource, invalid UID: " + uidt + " - using text path instead: " + path).utf8().get_data());
452#endif
453 }
454 }
455
456 if (!path.contains("://") && path.is_relative_path()) {
457 // path is relative to file being loaded, so convert to a resource path
458 path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().path_join(path));
459 }
460
461 if (remaps.has(path)) {
462 path = remaps[path];
463 }
464
465 ext_resources[id].path = path;
466 ext_resources[id].type = type;
467 ext_resources[id].load_token = ResourceLoader::_load_start(path, type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE);
468 if (!ext_resources[id].load_token.is_valid()) {
469 if (ResourceLoader::get_abort_on_missing_resources()) {
470 error = ERR_FILE_CORRUPT;
471 error_text = "[ext_resource] referenced non-existent resource at: " + path;
472 _printerr();
473 return error;
474 } else {
475 ResourceLoader::notify_dependency_error(local_path, path, type);
476 }
477 }
478
479 error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp);
480
481 if (error) {
482 _printerr();
483 return error;
484 }
485
486 resource_current++;
487 }
488
489 //these are the ones that count
490 resources_total -= resource_current;
491 resource_current = 0;
492
493 while (true) {
494 if (next_tag.name != "sub_resource") {
495 break;
496 }
497
498 if (!next_tag.fields.has("type")) {
499 error = ERR_FILE_CORRUPT;
500 error_text = "Missing 'type' in external resource tag";
501 _printerr();
502 return error;
503 }
504
505 if (!next_tag.fields.has("id")) {
506 error = ERR_FILE_CORRUPT;
507 error_text = "Missing 'id' in external resource tag";
508 _printerr();
509 return error;
510 }
511
512 String type = next_tag.fields["type"];
513 String id = next_tag.fields["id"];
514
515 String path = local_path + "::" + id;
516
517 //bool exists=ResourceCache::has(path);
518
519 Ref<Resource> res;
520 bool do_assign = false;
521
522 if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) {
523 //reuse existing
524 Ref<Resource> cache = ResourceCache::get_ref(path);
525 if (cache.is_valid() && cache->get_class() == type) {
526 res = cache;
527 res->reset_state();
528 do_assign = true;
529 }
530 }
531
532 MissingResource *missing_resource = nullptr;
533
534 if (res.is_null()) { //not reuse
535 Ref<Resource> cache = ResourceCache::get_ref(path);
536 if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && cache.is_valid()) { //only if it doesn't exist
537 //cached, do not assign
538 res = cache;
539 } else {
540 //create
541
542 Object *obj = ClassDB::instantiate(type);
543 if (!obj) {
544 if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
545 missing_resource = memnew(MissingResource);
546 missing_resource->set_original_class(type);
547 missing_resource->set_recording_properties(true);
548 obj = missing_resource;
549 } else {
550 error_text += "Can't create sub resource of type: " + type;
551 _printerr();
552 error = ERR_FILE_CORRUPT;
553 return error;
554 }
555 }
556
557 Resource *r = Object::cast_to<Resource>(obj);
558 if (!r) {
559 error_text += "Can't create sub resource of type, because not a resource: " + type;
560 _printerr();
561 error = ERR_FILE_CORRUPT;
562 return error;
563 }
564
565 res = Ref<Resource>(r);
566 do_assign = true;
567 }
568 }
569
570 resource_current++;
571
572 if (progress && resources_total > 0) {
573 *progress = resource_current / float(resources_total);
574 }
575
576 int_resources[id] = res; // Always assign int resources.
577 if (do_assign) {
578 if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
579 res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE);
580 }
581 res->set_scene_unique_id(id);
582 }
583
584 Dictionary missing_resource_properties;
585
586 while (true) {
587 String assign;
588 Variant value;
589
590 error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp);
591
592 if (error) {
593 _printerr();
594 return error;
595 }
596
597 if (!assign.is_empty()) {
598 if (do_assign) {
599 bool set_valid = true;
600
601 if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) {
602 // If the property being set is a missing resource (and the parent is not),
603 // then setting it will most likely not work.
604 // Instead, save it as metadata.
605
606 Ref<MissingResource> mr = value;
607 if (mr.is_valid()) {
608 missing_resource_properties[assign] = mr;
609 set_valid = false;
610 }
611 }
612
613 if (value.get_type() == Variant::ARRAY) {
614 Array set_array = value;
615 bool is_get_valid = false;
616 Variant get_value = res->get(assign, &is_get_valid);
617 if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
618 Array get_array = get_value;
619 if (!set_array.is_same_typed(get_array)) {
620 value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
621 }
622 }
623 }
624
625 if (set_valid) {
626 res->set(assign, value);
627 }
628 }
629 //it's assignment
630 } else if (!next_tag.name.is_empty()) {
631 error = OK;
632 break;
633 } else {
634 error = ERR_FILE_CORRUPT;
635 error_text = "Premature end of file while parsing [sub_resource]";
636 _printerr();
637 return error;
638 }
639 }
640
641 if (missing_resource) {
642 missing_resource->set_recording_properties(false);
643 }
644
645 if (!missing_resource_properties.is_empty()) {
646 res->set_meta(META_MISSING_RESOURCES, missing_resource_properties);
647 }
648 }
649
650 while (true) {
651 if (next_tag.name != "resource") {
652 break;
653 }
654
655 if (is_scene) {
656 error_text += "found the 'resource' tag on a scene file!";
657 _printerr();
658 error = ERR_FILE_CORRUPT;
659 return error;
660 }
661
662 Ref<Resource> cache = ResourceCache::get_ref(local_path);
663 if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && cache.is_valid() && cache->get_class() == res_type) {
664 cache->reset_state();
665 resource = cache;
666 }
667
668 MissingResource *missing_resource = nullptr;
669
670 if (!resource.is_valid()) {
671 Object *obj = ClassDB::instantiate(res_type);
672 if (!obj) {
673 if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
674 missing_resource = memnew(MissingResource);
675 missing_resource->set_original_class(res_type);
676 missing_resource->set_recording_properties(true);
677 obj = missing_resource;
678 } else {
679 error_text += "Can't create sub resource of type: " + res_type;
680 _printerr();
681 error = ERR_FILE_CORRUPT;
682 return error;
683 }
684 }
685
686 Resource *r = Object::cast_to<Resource>(obj);
687 if (!r) {
688 error_text += "Can't create sub resource of type, because not a resource: " + res_type;
689 _printerr();
690 error = ERR_FILE_CORRUPT;
691 return error;
692 }
693
694 resource = Ref<Resource>(r);
695 }
696
697 Dictionary missing_resource_properties;
698
699 while (true) {
700 String assign;
701 Variant value;
702
703 error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp);
704
705 if (error) {
706 if (error != ERR_FILE_EOF) {
707 _printerr();
708 } else {
709 error = OK;
710 if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
711 if (!ResourceCache::has(res_path)) {
712 resource->set_path(res_path);
713 }
714 resource->set_as_translation_remapped(translation_remapped);
715 }
716 }
717 return error;
718 }
719
720 if (!assign.is_empty()) {
721 bool set_valid = true;
722
723 if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) {
724 // If the property being set is a missing resource (and the parent is not),
725 // then setting it will most likely not work.
726 // Instead, save it as metadata.
727
728 Ref<MissingResource> mr = value;
729 if (mr.is_valid()) {
730 missing_resource_properties[assign] = mr;
731 set_valid = false;
732 }
733 }
734
735 if (value.get_type() == Variant::ARRAY) {
736 Array set_array = value;
737 bool is_get_valid = false;
738 Variant get_value = resource->get(assign, &is_get_valid);
739 if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
740 Array get_array = get_value;
741 if (!set_array.is_same_typed(get_array)) {
742 value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
743 }
744 }
745 }
746
747 if (set_valid) {
748 resource->set(assign, value);
749 }
750 //it's assignment
751 } else if (!next_tag.name.is_empty()) {
752 error = ERR_FILE_CORRUPT;
753 error_text = "Extra tag found when parsing main resource file";
754 _printerr();
755 return error;
756 } else {
757 break;
758 }
759 }
760
761 resource_current++;
762
763 if (progress && resources_total > 0) {
764 *progress = resource_current / float(resources_total);
765 }
766
767 if (missing_resource) {
768 missing_resource->set_recording_properties(false);
769 }
770
771 if (!missing_resource_properties.is_empty()) {
772 resource->set_meta(META_MISSING_RESOURCES, missing_resource_properties);
773 }
774
775 error = OK;
776
777 return error;
778 }
779
780 //for scene files
781
782 if (next_tag.name == "node") {
783 if (!is_scene) {
784 error_text += "found the 'node' tag on a resource file!";
785 _printerr();
786 error = ERR_FILE_CORRUPT;
787 return error;
788 }
789
790 Ref<PackedScene> packed_scene = _parse_node_tag(rp);
791
792 if (!packed_scene.is_valid()) {
793 return error;
794 }
795
796 error = OK;
797 //get it here
798 resource = packed_scene;
799 if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && !ResourceCache::has(res_path)) {
800 packed_scene->set_path(res_path);
801 }
802
803 resource_current++;
804
805 if (progress && resources_total > 0) {
806 *progress = resource_current / float(resources_total);
807 }
808
809 return error;
810 } else {
811 error_text += "Unknown tag in file: " + next_tag.name;
812 _printerr();
813 error = ERR_FILE_CORRUPT;
814 return error;
815 }
816}
817
818int ResourceLoaderText::get_stage() const {
819 return resource_current;
820}
821
822int ResourceLoaderText::get_stage_count() const {
823 return resources_total; //+ext_resources;
824}
825
826void ResourceLoaderText::set_translation_remapped(bool p_remapped) {
827 translation_remapped = p_remapped;
828}
829
830ResourceLoaderText::ResourceLoaderText() :
831 stream(false) {}
832
833void ResourceLoaderText::get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types) {
834 open(p_f);
835 ignore_resource_parsing = true;
836 ERR_FAIL_COND(error != OK);
837
838 while (next_tag.name == "ext_resource") {
839 if (!next_tag.fields.has("type")) {
840 error = ERR_FILE_CORRUPT;
841 error_text = "Missing 'type' in external resource tag";
842 _printerr();
843 return;
844 }
845
846 if (!next_tag.fields.has("id")) {
847 error = ERR_FILE_CORRUPT;
848 error_text = "Missing 'id' in external resource tag";
849 _printerr();
850 return;
851 }
852
853 String path = next_tag.fields["path"];
854 String type = next_tag.fields["type"];
855 String fallback_path;
856
857 bool using_uid = false;
858 if (next_tag.fields.has("uid")) {
859 // If uid exists, return uid in text format, not the path.
860 String uidt = next_tag.fields["uid"];
861 ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(uidt);
862 if (uid != ResourceUID::INVALID_ID) {
863 fallback_path = path; // Used by Dependency Editor, in case uid path fails.
864 path = ResourceUID::get_singleton()->id_to_text(uid);
865 using_uid = true;
866 }
867 }
868
869 if (!using_uid && !path.contains("://") && path.is_relative_path()) {
870 // Path is relative to file being loaded, so convert to a resource path.
871 path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().path_join(path));
872 }
873
874 if (p_add_types) {
875 path += "::" + type;
876 }
877 if (!fallback_path.is_empty()) {
878 if (!p_add_types) {
879 path += "::"; // Ensure that path comes third, even if there is no type.
880 }
881 path += "::" + fallback_path;
882 }
883
884 p_dependencies->push_back(path);
885
886 Error err = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp);
887
888 if (err) {
889 print_line(error_text + " - " + itos(lines));
890 error_text = "Unexpected end of file";
891 _printerr();
892 error = ERR_FILE_CORRUPT;
893 return;
894 }
895 }
896}
897
898Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String &p_path, const HashMap<String, String> &p_map) {
899 open(p_f, true);
900 ERR_FAIL_COND_V(error != OK, error);
901 ignore_resource_parsing = true;
902 //FileAccess
903
904 Ref<FileAccess> fw;
905
906 String base_path = local_path.get_base_dir();
907
908 uint64_t tag_end = f->get_position();
909
910 while (true) {
911 Error err = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp);
912
913 if (err != OK) {
914 error = ERR_FILE_CORRUPT;
915 ERR_FAIL_V(error);
916 }
917
918 if (next_tag.name != "ext_resource") {
919 //nothing was done
920 if (fw.is_null()) {
921 return OK;
922 }
923
924 break;
925
926 } else {
927 if (fw.is_null()) {
928 fw = FileAccess::open(p_path + ".depren", FileAccess::WRITE);
929
930 if (res_uid == ResourceUID::INVALID_ID) {
931 res_uid = ResourceSaver::get_resource_id_for_path(p_path);
932 }
933
934 String uid_text = "";
935 if (res_uid != ResourceUID::INVALID_ID) {
936 uid_text = " uid=\"" + ResourceUID::get_singleton()->id_to_text(res_uid) + "\"";
937 }
938
939 if (is_scene) {
940 fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n");
941 } else {
942 String script_res_text;
943 if (!script_class.is_empty()) {
944 script_res_text = "script_class=\"" + script_class + "\" ";
945 }
946 fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n");
947 }
948 }
949
950 if (!next_tag.fields.has("path") || !next_tag.fields.has("id") || !next_tag.fields.has("type")) {
951 error = ERR_FILE_CORRUPT;
952 ERR_FAIL_V(error);
953 }
954
955 String path = next_tag.fields["path"];
956 String id = next_tag.fields["id"];
957 String type = next_tag.fields["type"];
958
959 if (next_tag.fields.has("uid")) {
960 String uidt = next_tag.fields["uid"];
961 ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(uidt);
962 if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) {
963 // If a UID is found and the path is valid, it will be used, otherwise, it falls back to the path.
964 path = ResourceUID::get_singleton()->get_id_path(uid);
965 }
966 }
967 bool relative = false;
968 if (!path.begins_with("res://")) {
969 path = base_path.path_join(path).simplify_path();
970 relative = true;
971 }
972
973 if (p_map.has(path)) {
974 String np = p_map[path];
975 path = np;
976 }
977
978 if (relative) {
979 //restore relative
980 path = base_path.path_to_file(path);
981 }
982
983 String s = "[ext_resource type=\"" + type + "\"";
984
985 ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(path);
986 if (uid != ResourceUID::INVALID_ID) {
987 s += " uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\"";
988 }
989 s += " path=\"" + path + "\" id=\"" + id + "\"]";
990 fw->store_line(s); // Bundled.
991
992 tag_end = f->get_position();
993 }
994 }
995
996 f->seek(tag_end);
997
998 const uint32_t buffer_size = 2048;
999 uint8_t *buffer = (uint8_t *)alloca(buffer_size);
1000 uint32_t num_read;
1001
1002 num_read = f->get_buffer(buffer, buffer_size);
1003 ERR_FAIL_COND_V_MSG(num_read == UINT32_MAX, ERR_CANT_CREATE, "Failed to allocate memory for buffer.");
1004 ERR_FAIL_COND_V(num_read == 0, ERR_FILE_CORRUPT);
1005
1006 if (*buffer == '\n') {
1007 // Skip first newline character since we added one.
1008 if (num_read > 1) {
1009 fw->store_buffer(buffer + 1, num_read - 1);
1010 }
1011 } else {
1012 fw->store_buffer(buffer, num_read);
1013 }
1014
1015 while (!f->eof_reached()) {
1016 num_read = f->get_buffer(buffer, buffer_size);
1017 fw->store_buffer(buffer, num_read);
1018 }
1019
1020 bool all_ok = fw->get_error() == OK;
1021
1022 if (!all_ok) {
1023 return ERR_CANT_CREATE;
1024 }
1025
1026 return OK;
1027}
1028
1029void ResourceLoaderText::open(Ref<FileAccess> p_f, bool p_skip_first_tag) {
1030 error = OK;
1031
1032 lines = 1;
1033 f = p_f;
1034
1035 stream.f = f;
1036 is_scene = false;
1037 ignore_resource_parsing = false;
1038 resource_current = 0;
1039
1040 VariantParser::Tag tag;
1041 Error err = VariantParser::parse_tag(&stream, lines, error_text, tag);
1042
1043 if (err) {
1044 error = err;
1045 _printerr();
1046 return;
1047 }
1048
1049 if (tag.fields.has("format")) {
1050 int fmt = tag.fields["format"];
1051 if (fmt > FORMAT_VERSION) {
1052 error_text = "Saved with newer format version";
1053 _printerr();
1054 error = ERR_PARSE_ERROR;
1055 return;
1056 }
1057 }
1058
1059 if (tag.name == "gd_scene") {
1060 is_scene = true;
1061
1062 } else if (tag.name == "gd_resource") {
1063 if (!tag.fields.has("type")) {
1064 error_text = "Missing 'type' field in 'gd_resource' tag";
1065 _printerr();
1066 error = ERR_PARSE_ERROR;
1067 return;
1068 }
1069
1070 if (tag.fields.has("script_class")) {
1071 script_class = tag.fields["script_class"];
1072 }
1073
1074 res_type = tag.fields["type"];
1075
1076 } else {
1077 error_text = "Unrecognized file type: " + tag.name;
1078 _printerr();
1079 error = ERR_PARSE_ERROR;
1080 return;
1081 }
1082
1083 if (tag.fields.has("uid")) {
1084 res_uid = ResourceUID::get_singleton()->text_to_id(tag.fields["uid"]);
1085 } else {
1086 res_uid = ResourceUID::INVALID_ID;
1087 }
1088
1089 if (tag.fields.has("load_steps")) {
1090 resources_total = tag.fields["load_steps"];
1091 } else {
1092 resources_total = 0;
1093 }
1094
1095 if (!p_skip_first_tag) {
1096 err = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp);
1097
1098 if (err) {
1099 error_text = "Unexpected end of file";
1100 _printerr();
1101 error = ERR_FILE_CORRUPT;
1102 }
1103 }
1104
1105 rp.ext_func = _parse_ext_resources;
1106 rp.sub_func = _parse_sub_resources;
1107 rp.userdata = this;
1108}
1109
1110static void bs_save_unicode_string(Ref<FileAccess> p_f, const String &p_string, bool p_bit_on_len = false) {
1111 CharString utf8 = p_string.utf8();
1112 if (p_bit_on_len) {
1113 p_f->store_32((utf8.length() + 1) | 0x80000000);
1114 } else {
1115 p_f->store_32(utf8.length() + 1);
1116 }
1117 p_f->store_buffer((const uint8_t *)utf8.get_data(), utf8.length() + 1);
1118}
1119
1120Error ResourceLoaderText::save_as_binary(const String &p_path) {
1121 if (error) {
1122 return error;
1123 }
1124
1125 Ref<FileAccess> wf = FileAccess::open(p_path, FileAccess::WRITE);
1126 if (wf.is_null()) {
1127 return ERR_CANT_OPEN;
1128 }
1129
1130 //save header compressed
1131 static const uint8_t header[4] = { 'R', 'S', 'R', 'C' };
1132 wf->store_buffer(header, 4);
1133
1134 wf->store_32(0); //endianness, little endian
1135 wf->store_32(0); //64 bits file, false for now
1136 wf->store_32(VERSION_MAJOR);
1137 wf->store_32(VERSION_MINOR);
1138 static const int save_format_version = BINARY_FORMAT_VERSION;
1139 wf->store_32(save_format_version);
1140
1141 bs_save_unicode_string(wf, is_scene ? "PackedScene" : resource_type);
1142 wf->store_64(0); //offset to import metadata, this is no longer used
1143
1144 wf->store_32(ResourceFormatSaverBinaryInstance::FORMAT_FLAG_NAMED_SCENE_IDS | ResourceFormatSaverBinaryInstance::FORMAT_FLAG_UIDS);
1145
1146 wf->store_64(res_uid);
1147
1148 for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) {
1149 wf->store_32(0); // reserved
1150 }
1151
1152 wf->store_32(0); //string table size, will not be in use
1153 uint64_t ext_res_count_pos = wf->get_position();
1154
1155 wf->store_32(0); //zero ext resources, still parsing them
1156
1157 //go with external resources
1158
1159 DummyReadData dummy_read;
1160 VariantParser::ResourceParser rp_new;
1161 rp_new.ext_func = _parse_ext_resource_dummys;
1162 rp_new.sub_func = _parse_sub_resource_dummys;
1163 rp_new.userdata = &dummy_read;
1164
1165 while (next_tag.name == "ext_resource") {
1166 if (!next_tag.fields.has("path")) {
1167 error = ERR_FILE_CORRUPT;
1168 error_text = "Missing 'path' in external resource tag";
1169 _printerr();
1170 return error;
1171 }
1172
1173 if (!next_tag.fields.has("type")) {
1174 error = ERR_FILE_CORRUPT;
1175 error_text = "Missing 'type' in external resource tag";
1176 _printerr();
1177 return error;
1178 }
1179
1180 if (!next_tag.fields.has("id")) {
1181 error = ERR_FILE_CORRUPT;
1182 error_text = "Missing 'id' in external resource tag";
1183 _printerr();
1184 return error;
1185 }
1186
1187 String path = next_tag.fields["path"];
1188 String type = next_tag.fields["type"];
1189 String id = next_tag.fields["id"];
1190 ResourceUID::ID uid = ResourceUID::INVALID_ID;
1191 if (next_tag.fields.has("uid")) {
1192 String uidt = next_tag.fields["uid"];
1193 uid = ResourceUID::get_singleton()->text_to_id(uidt);
1194 }
1195
1196 bs_save_unicode_string(wf, type);
1197 bs_save_unicode_string(wf, path);
1198 wf->store_64(uid);
1199
1200 int lindex = dummy_read.external_resources.size();
1201 Ref<DummyResource> dr;
1202 dr.instantiate();
1203 dr->set_path("res://dummy" + itos(lindex)); //anything is good to detect it for saving as external
1204 dummy_read.external_resources[dr] = lindex;
1205 dummy_read.rev_external_resources[id] = dr;
1206
1207 error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp_new);
1208
1209 if (error) {
1210 _printerr();
1211 return error;
1212 }
1213 }
1214
1215 // save external resource table
1216 wf->seek(ext_res_count_pos);
1217 wf->store_32(dummy_read.external_resources.size());
1218 wf->seek_end();
1219
1220 //now, save resources to a separate file, for now
1221
1222 uint64_t sub_res_count_pos = wf->get_position();
1223 wf->store_32(0); //zero sub resources, still parsing them
1224
1225 String temp_file = p_path + ".temp";
1226 Vector<uint64_t> local_offsets;
1227 Vector<uint64_t> local_pointers_pos;
1228 {
1229 Ref<FileAccess> wf2 = FileAccess::open(temp_file, FileAccess::WRITE);
1230 if (wf2.is_null()) {
1231 return ERR_CANT_OPEN;
1232 }
1233
1234 while (next_tag.name == "sub_resource" || next_tag.name == "resource") {
1235 String type;
1236 String id;
1237 bool main_res;
1238
1239 if (next_tag.name == "sub_resource") {
1240 if (!next_tag.fields.has("type")) {
1241 error = ERR_FILE_CORRUPT;
1242 error_text = "Missing 'type' in external resource tag";
1243 _printerr();
1244 return error;
1245 }
1246
1247 if (!next_tag.fields.has("id")) {
1248 error = ERR_FILE_CORRUPT;
1249 error_text = "Missing 'id' in external resource tag";
1250 _printerr();
1251 return error;
1252 }
1253
1254 type = next_tag.fields["type"];
1255 id = next_tag.fields["id"];
1256 main_res = false;
1257
1258 if (!dummy_read.resource_map.has(id)) {
1259 Ref<DummyResource> dr;
1260 dr.instantiate();
1261 dr->set_scene_unique_id(id);
1262 dummy_read.resource_map[id] = dr;
1263 uint32_t im_size = dummy_read.resource_index_map.size();
1264 dummy_read.resource_index_map.insert(dr, im_size);
1265 }
1266
1267 } else {
1268 type = res_type;
1269 String uid_text = ResourceUID::get_singleton()->id_to_text(res_uid);
1270 id = type + "_" + uid_text.replace("uid://", "").replace("<invalid>", "0");
1271 main_res = true;
1272 }
1273
1274 local_offsets.push_back(wf2->get_position());
1275
1276 bs_save_unicode_string(wf, "local://" + id);
1277 local_pointers_pos.push_back(wf->get_position());
1278 wf->store_64(0); //temp local offset
1279
1280 bs_save_unicode_string(wf2, type);
1281 uint64_t propcount_ofs = wf2->get_position();
1282 wf2->store_32(0);
1283
1284 int prop_count = 0;
1285
1286 while (true) {
1287 String assign;
1288 Variant value;
1289
1290 error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp_new);
1291
1292 if (error) {
1293 if (main_res && error == ERR_FILE_EOF) {
1294 next_tag.name = ""; //exit
1295 break;
1296 }
1297
1298 _printerr();
1299 return error;
1300 }
1301
1302 if (!assign.is_empty()) {
1303 HashMap<StringName, int> empty_string_map; //unused
1304 bs_save_unicode_string(wf2, assign, true);
1305 ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map);
1306 prop_count++;
1307
1308 } else if (!next_tag.name.is_empty()) {
1309 error = OK;
1310 break;
1311 } else {
1312 error = ERR_FILE_CORRUPT;
1313 error_text = "Premature end of file while parsing [sub_resource]";
1314 _printerr();
1315 return error;
1316 }
1317 }
1318
1319 wf2->seek(propcount_ofs);
1320 wf2->store_32(prop_count);
1321 wf2->seek_end();
1322 }
1323
1324 if (next_tag.name == "node") {
1325 // This is a node, must save one more!
1326
1327 if (!is_scene) {
1328 error_text += "found the 'node' tag on a resource file!";
1329 _printerr();
1330 error = ERR_FILE_CORRUPT;
1331 return error;
1332 }
1333
1334 Ref<PackedScene> packed_scene = _parse_node_tag(rp_new);
1335
1336 if (!packed_scene.is_valid()) {
1337 return error;
1338 }
1339
1340 error = OK;
1341 //get it here
1342 List<PropertyInfo> props;
1343 packed_scene->get_property_list(&props);
1344
1345 String id = "PackedScene_" + ResourceUID::get_singleton()->id_to_text(res_uid).replace("uid://", "").replace("<invalid>", "0");
1346 bs_save_unicode_string(wf, "local://" + id);
1347 local_pointers_pos.push_back(wf->get_position());
1348 wf->store_64(0); //temp local offset
1349
1350 local_offsets.push_back(wf2->get_position());
1351 bs_save_unicode_string(wf2, "PackedScene");
1352 uint64_t propcount_ofs = wf2->get_position();
1353 wf2->store_32(0);
1354
1355 int prop_count = 0;
1356
1357 for (const PropertyInfo &E : props) {
1358 if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
1359 continue;
1360 }
1361
1362 String name = E.name;
1363 Variant value = packed_scene->get(name);
1364
1365 HashMap<StringName, int> empty_string_map; //unused
1366 bs_save_unicode_string(wf2, name, true);
1367 ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map);
1368 prop_count++;
1369 }
1370
1371 wf2->seek(propcount_ofs);
1372 wf2->store_32(prop_count);
1373 wf2->seek_end();
1374 }
1375 }
1376
1377 uint64_t offset_from = wf->get_position();
1378 wf->seek(sub_res_count_pos); //plus one because the saved one
1379 wf->store_32(local_offsets.size());
1380
1381 for (int i = 0; i < local_offsets.size(); i++) {
1382 wf->seek(local_pointers_pos[i]);
1383 wf->store_64(local_offsets[i] + offset_from);
1384 }
1385
1386 wf->seek_end();
1387
1388 Vector<uint8_t> data = FileAccess::get_file_as_bytes(temp_file);
1389 wf->store_buffer(data.ptr(), data.size());
1390 {
1391 Ref<DirAccess> dar = DirAccess::open(temp_file.get_base_dir());
1392 dar->remove(temp_file);
1393 }
1394
1395 wf->store_buffer((const uint8_t *)"RSRC", 4); //magic at end
1396
1397 return OK;
1398}
1399
1400Error ResourceLoaderText::get_classes_used(HashSet<StringName> *r_classes) {
1401 if (error) {
1402 return error;
1403 }
1404
1405 ignore_resource_parsing = true;
1406
1407 DummyReadData dummy_read;
1408 dummy_read.no_placeholders = true;
1409 VariantParser::ResourceParser rp_new;
1410 rp_new.ext_func = _parse_ext_resource_dummys;
1411 rp_new.sub_func = _parse_sub_resource_dummys;
1412 rp_new.userdata = &dummy_read;
1413
1414 while (next_tag.name == "ext_resource") {
1415 error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp_new);
1416
1417 if (error) {
1418 _printerr();
1419 return error;
1420 }
1421 }
1422
1423 while (next_tag.name == "sub_resource" || next_tag.name == "resource") {
1424 if (next_tag.name == "sub_resource") {
1425 if (!next_tag.fields.has("type")) {
1426 error = ERR_FILE_CORRUPT;
1427 error_text = "Missing 'type' in external resource tag";
1428 _printerr();
1429 return error;
1430 }
1431
1432 r_classes->insert(next_tag.fields["type"]);
1433
1434 } else {
1435 r_classes->insert(next_tag.fields["res_type"]);
1436 }
1437
1438 while (true) {
1439 String assign;
1440 Variant value;
1441
1442 error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp_new);
1443
1444 if (error) {
1445 if (error == ERR_FILE_EOF) {
1446 return OK;
1447 }
1448
1449 _printerr();
1450 return error;
1451 }
1452
1453 if (!assign.is_empty()) {
1454 continue;
1455 } else if (!next_tag.name.is_empty()) {
1456 error = OK;
1457 break;
1458 } else {
1459 error = ERR_FILE_CORRUPT;
1460 error_text = "Premature end of file while parsing [sub_resource]";
1461 _printerr();
1462 return error;
1463 }
1464 }
1465 }
1466
1467 while (next_tag.name == "node") {
1468 // This is a node, must save one more!
1469
1470 if (!is_scene) {
1471 error_text += "found the 'node' tag on a resource file!";
1472 _printerr();
1473 error = ERR_FILE_CORRUPT;
1474 return error;
1475 }
1476
1477 if (!next_tag.fields.has("type")) {
1478 error = ERR_FILE_CORRUPT;
1479 error_text = "Missing 'type' in external resource tag";
1480 _printerr();
1481 return error;
1482 }
1483
1484 r_classes->insert(next_tag.fields["type"]);
1485
1486 while (true) {
1487 String assign;
1488 Variant value;
1489
1490 error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp_new);
1491
1492 if (error) {
1493 if (error == ERR_FILE_MISSING_DEPENDENCIES) {
1494 // Resource loading error, just skip it.
1495 } else if (error != ERR_FILE_EOF) {
1496 _printerr();
1497 return error;
1498 } else {
1499 return OK;
1500 }
1501 }
1502
1503 if (!assign.is_empty()) {
1504 continue;
1505 } else if (!next_tag.name.is_empty()) {
1506 error = OK;
1507 break;
1508 } else {
1509 error = ERR_FILE_CORRUPT;
1510 error_text = "Premature end of file while parsing [sub_resource]";
1511 _printerr();
1512 return error;
1513 }
1514 }
1515 }
1516
1517 return OK;
1518}
1519
1520String ResourceLoaderText::recognize_script_class(Ref<FileAccess> p_f) {
1521 error = OK;
1522
1523 lines = 1;
1524 f = p_f;
1525
1526 stream.f = f;
1527
1528 ignore_resource_parsing = true;
1529
1530 VariantParser::Tag tag;
1531 Error err = VariantParser::parse_tag(&stream, lines, error_text, tag);
1532
1533 if (err) {
1534 _printerr();
1535 return "";
1536 }
1537
1538 if (tag.fields.has("format")) {
1539 int fmt = tag.fields["format"];
1540 if (fmt > FORMAT_VERSION) {
1541 error_text = "Saved with newer format version";
1542 _printerr();
1543 return "";
1544 }
1545 }
1546
1547 if (tag.name != "gd_resource") {
1548 return "";
1549 }
1550
1551 if (tag.fields.has("script_class")) {
1552 return tag.fields["script_class"];
1553 }
1554
1555 return "";
1556}
1557
1558String ResourceLoaderText::recognize(Ref<FileAccess> p_f) {
1559 error = OK;
1560
1561 lines = 1;
1562 f = p_f;
1563
1564 stream.f = f;
1565
1566 ignore_resource_parsing = true;
1567
1568 VariantParser::Tag tag;
1569 Error err = VariantParser::parse_tag(&stream, lines, error_text, tag);
1570
1571 if (err) {
1572 _printerr();
1573 return "";
1574 }
1575
1576 if (tag.fields.has("format")) {
1577 int fmt = tag.fields["format"];
1578 if (fmt > FORMAT_VERSION) {
1579 error_text = "Saved with newer format version";
1580 _printerr();
1581 return "";
1582 }
1583 }
1584
1585 if (tag.name == "gd_scene") {
1586 return "PackedScene";
1587 }
1588
1589 if (tag.name != "gd_resource") {
1590 return "";
1591 }
1592
1593 if (!tag.fields.has("type")) {
1594 error_text = "Missing 'type' field in 'gd_resource' tag";
1595 _printerr();
1596 return "";
1597 }
1598
1599 return tag.fields["type"];
1600}
1601
1602ResourceUID::ID ResourceLoaderText::get_uid(Ref<FileAccess> p_f) {
1603 error = OK;
1604
1605 lines = 1;
1606 f = p_f;
1607
1608 stream.f = f;
1609
1610 ignore_resource_parsing = true;
1611
1612 VariantParser::Tag tag;
1613 Error err = VariantParser::parse_tag(&stream, lines, error_text, tag);
1614
1615 if (err) {
1616 _printerr();
1617 return ResourceUID::INVALID_ID;
1618 }
1619
1620 if (tag.fields.has("uid")) { //field is optional
1621 String uidt = tag.fields["uid"];
1622 return ResourceUID::get_singleton()->text_to_id(uidt);
1623 }
1624
1625 return ResourceUID::INVALID_ID;
1626}
1627
1628/////////////////////
1629
1630Ref<Resource> ResourceFormatLoaderText::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
1631 if (r_error) {
1632 *r_error = ERR_CANT_OPEN;
1633 }
1634
1635 Error err;
1636
1637 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
1638
1639 ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot open file '" + p_path + "'.");
1640
1641 ResourceLoaderText loader;
1642 String path = !p_original_path.is_empty() ? p_original_path : p_path;
1643 loader.cache_mode = p_cache_mode;
1644 loader.use_sub_threads = p_use_sub_threads;
1645 loader.local_path = ProjectSettings::get_singleton()->localize_path(path);
1646 loader.progress = r_progress;
1647 loader.res_path = loader.local_path;
1648 loader.open(f);
1649 err = loader.load();
1650 if (r_error) {
1651 *r_error = err;
1652 }
1653 if (err == OK) {
1654 return loader.get_resource();
1655 } else {
1656 return Ref<Resource>();
1657 }
1658}
1659
1660void ResourceFormatLoaderText::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
1661 if (p_type.is_empty()) {
1662 get_recognized_extensions(p_extensions);
1663 return;
1664 }
1665
1666 if (ClassDB::is_parent_class("PackedScene", p_type)) {
1667 p_extensions->push_back("tscn");
1668 }
1669
1670 // Don't allow .tres for PackedScenes.
1671 if (p_type != "PackedScene") {
1672 p_extensions->push_back("tres");
1673 }
1674}
1675
1676void ResourceFormatLoaderText::get_recognized_extensions(List<String> *p_extensions) const {
1677 p_extensions->push_back("tscn");
1678 p_extensions->push_back("tres");
1679}
1680
1681bool ResourceFormatLoaderText::handles_type(const String &p_type) const {
1682 return true;
1683}
1684
1685void ResourceFormatLoaderText::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
1686 String ext = p_path.get_extension().to_lower();
1687 if (ext == "tscn") {
1688 r_classes->insert("PackedScene");
1689 }
1690
1691 // ...for anything else must test...
1692
1693 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
1694 if (f.is_null()) {
1695 return; // Could not read.
1696 }
1697
1698 ResourceLoaderText loader;
1699 loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
1700 loader.res_path = loader.local_path;
1701 loader.open(f);
1702 loader.get_classes_used(r_classes);
1703}
1704
1705String ResourceFormatLoaderText::get_resource_type(const String &p_path) const {
1706 String ext = p_path.get_extension().to_lower();
1707 if (ext == "tscn") {
1708 return "PackedScene";
1709 } else if (ext != "tres") {
1710 return String();
1711 }
1712
1713 // ...for anything else must test...
1714
1715 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
1716 if (f.is_null()) {
1717 return ""; //could not read
1718 }
1719
1720 ResourceLoaderText loader;
1721 loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
1722 loader.res_path = loader.local_path;
1723 String r = loader.recognize(f);
1724 return ClassDB::get_compatibility_remapped_class(r);
1725}
1726
1727String ResourceFormatLoaderText::get_resource_script_class(const String &p_path) const {
1728 String ext = p_path.get_extension().to_lower();
1729 if (ext != "tres") {
1730 return String();
1731 }
1732
1733 // ...for anything else must test...
1734
1735 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
1736 if (f.is_null()) {
1737 return ""; //could not read
1738 }
1739
1740 ResourceLoaderText loader;
1741 loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
1742 loader.res_path = loader.local_path;
1743 return loader.recognize_script_class(f);
1744}
1745
1746ResourceUID::ID ResourceFormatLoaderText::get_resource_uid(const String &p_path) const {
1747 String ext = p_path.get_extension().to_lower();
1748
1749 if (ext != "tscn" && ext != "tres") {
1750 return ResourceUID::INVALID_ID;
1751 }
1752
1753 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
1754 if (f.is_null()) {
1755 return ResourceUID::INVALID_ID; //could not read
1756 }
1757
1758 ResourceLoaderText loader;
1759 loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
1760 loader.res_path = loader.local_path;
1761 return loader.get_uid(f);
1762}
1763
1764void ResourceFormatLoaderText::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
1765 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
1766 if (f.is_null()) {
1767 ERR_FAIL();
1768 }
1769
1770 ResourceLoaderText loader;
1771 loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
1772 loader.res_path = loader.local_path;
1773 loader.get_dependencies(f, p_dependencies, p_add_types);
1774}
1775
1776Error ResourceFormatLoaderText::rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) {
1777 Error err = OK;
1778 {
1779 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
1780 if (f.is_null()) {
1781 ERR_FAIL_V(ERR_CANT_OPEN);
1782 }
1783
1784 ResourceLoaderText loader;
1785 loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
1786 loader.res_path = loader.local_path;
1787 err = loader.rename_dependencies(f, p_path, p_map);
1788 }
1789
1790 if (err == OK) {
1791 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1792 da->remove(p_path);
1793 da->rename(p_path + ".depren", p_path);
1794 }
1795
1796 return err;
1797}
1798
1799ResourceFormatLoaderText *ResourceFormatLoaderText::singleton = nullptr;
1800
1801Error ResourceFormatLoaderText::convert_file_to_binary(const String &p_src_path, const String &p_dst_path) {
1802 Error err;
1803 Ref<FileAccess> f = FileAccess::open(p_src_path, FileAccess::READ, &err);
1804
1805 ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_OPEN, "Cannot open file '" + p_src_path + "'.");
1806
1807 ResourceLoaderText loader;
1808 const String &path = p_src_path;
1809 loader.local_path = ProjectSettings::get_singleton()->localize_path(path);
1810 loader.res_path = loader.local_path;
1811 loader.open(f);
1812 return loader.save_as_binary(p_dst_path);
1813}
1814
1815/*****************************************************************************************************/
1816/*****************************************************************************************************/
1817/*****************************************************************************************************/
1818/*****************************************************************************************************/
1819/*****************************************************************************************************/
1820/*****************************************************************************************************/
1821/*****************************************************************************************************/
1822/*****************************************************************************************************/
1823/*****************************************************************************************************/
1824/*****************************************************************************************************/
1825
1826String ResourceFormatSaverTextInstance::_write_resources(void *ud, const Ref<Resource> &p_resource) {
1827 ResourceFormatSaverTextInstance *rsi = static_cast<ResourceFormatSaverTextInstance *>(ud);
1828 return rsi->_write_resource(p_resource);
1829}
1830
1831String ResourceFormatSaverTextInstance::_write_resource(const Ref<Resource> &res) {
1832 if (res->get_meta(SNAME("_skip_save_"), false)) {
1833 return "null";
1834 }
1835
1836 if (external_resources.has(res)) {
1837 return "ExtResource(\"" + external_resources[res] + "\")";
1838 } else {
1839 if (internal_resources.has(res)) {
1840 return "SubResource(\"" + internal_resources[res] + "\")";
1841 } else if (!res->is_built_in()) {
1842 if (res->get_path() == local_path) { //circular reference attempt
1843 return "null";
1844 }
1845 //external resource
1846 String path = relative_paths ? local_path.path_to_file(res->get_path()) : res->get_path();
1847 return "Resource(\"" + path + "\")";
1848 } else {
1849 ERR_FAIL_V_MSG("null", "Resource was not pre cached for the resource section, bug?");
1850 //internal resource
1851 }
1852 }
1853}
1854
1855void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, bool p_main) {
1856 switch (p_variant.get_type()) {
1857 case Variant::OBJECT: {
1858 Ref<Resource> res = p_variant;
1859
1860 if (res.is_null() || external_resources.has(res) || res->get_meta(SNAME("_skip_save_"), false)) {
1861 return;
1862 }
1863
1864 if (!p_main && (!bundle_resources) && !res->is_built_in()) {
1865 if (res->get_path() == local_path) {
1866 ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded.");
1867 return;
1868 }
1869
1870 // Use a numeric ID as a base, because they are sorted in natural order before saving.
1871 // This increases the chances of thread loading to fetch them first.
1872 String id = itos(external_resources.size() + 1) + "_" + Resource::generate_scene_unique_id();
1873 external_resources[res] = id;
1874 return;
1875 }
1876
1877 if (resource_set.has(res)) {
1878 return;
1879 }
1880
1881 resource_set.insert(res);
1882
1883 List<PropertyInfo> property_list;
1884
1885 res->get_property_list(&property_list);
1886 property_list.sort();
1887
1888 List<PropertyInfo>::Element *I = property_list.front();
1889
1890 while (I) {
1891 PropertyInfo pi = I->get();
1892
1893 if (pi.usage & PROPERTY_USAGE_STORAGE) {
1894 Variant v = res->get(I->get().name);
1895
1896 if (pi.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) {
1897 NonPersistentKey npk;
1898 npk.base = res;
1899 npk.property = pi.name;
1900 non_persistent_map[npk] = v;
1901
1902 Ref<Resource> sres = v;
1903 if (sres.is_valid()) {
1904 resource_set.insert(sres);
1905 saved_resources.push_back(sres);
1906 } else {
1907 _find_resources(v);
1908 }
1909 } else {
1910 _find_resources(v);
1911 }
1912 }
1913
1914 I = I->next();
1915 }
1916
1917 saved_resources.push_back(res); // Saved after, so the children it needs are available when loaded
1918
1919 } break;
1920 case Variant::ARRAY: {
1921 Array varray = p_variant;
1922 int len = varray.size();
1923 for (int i = 0; i < len; i++) {
1924 const Variant &v = varray.get(i);
1925 _find_resources(v);
1926 }
1927
1928 } break;
1929 case Variant::DICTIONARY: {
1930 Dictionary d = p_variant;
1931 List<Variant> keys;
1932 d.get_key_list(&keys);
1933 for (const Variant &E : keys) {
1934 // Of course keys should also be cached, after all we can't prevent users from using resources as keys, right?
1935 // See also ResourceFormatSaverBinaryInstance::_find_resources (when p_variant is of type Variant::DICTIONARY)
1936 _find_resources(E);
1937 Variant v = d[E];
1938 _find_resources(v);
1939 }
1940 } break;
1941 default: {
1942 }
1943 }
1944}
1945
1946static String _resource_get_class(Ref<Resource> p_resource) {
1947 Ref<MissingResource> missing_resource = p_resource;
1948 if (missing_resource.is_valid()) {
1949 return missing_resource->get_original_class();
1950 } else {
1951 return p_resource->get_class();
1952 }
1953}
1954
1955Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) {
1956 if (p_path.ends_with(".tscn")) {
1957 packed_scene = p_resource;
1958 }
1959
1960 Error err;
1961 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE, &err);
1962 ERR_FAIL_COND_V_MSG(err, ERR_CANT_OPEN, "Cannot save file '" + p_path + "'.");
1963 Ref<FileAccess> _fref(f);
1964
1965 local_path = ProjectSettings::get_singleton()->localize_path(p_path);
1966
1967 relative_paths = p_flags & ResourceSaver::FLAG_RELATIVE_PATHS;
1968 skip_editor = p_flags & ResourceSaver::FLAG_OMIT_EDITOR_PROPERTIES;
1969 bundle_resources = p_flags & ResourceSaver::FLAG_BUNDLE_RESOURCES;
1970 takeover_paths = p_flags & ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
1971 if (!p_path.begins_with("res://")) {
1972 takeover_paths = false;
1973 }
1974
1975 // Save resources.
1976 _find_resources(p_resource, true);
1977
1978 if (packed_scene.is_valid()) {
1979 // Add instances to external resources if saving a packed scene.
1980 for (int i = 0; i < packed_scene->get_state()->get_node_count(); i++) {
1981 if (packed_scene->get_state()->is_node_instance_placeholder(i)) {
1982 continue;
1983 }
1984
1985 Ref<PackedScene> instance = packed_scene->get_state()->get_node_instance(i);
1986 if (instance.is_valid() && !external_resources.has(instance)) {
1987 int index = external_resources.size() + 1;
1988 external_resources[instance] = itos(index) + "_" + Resource::generate_scene_unique_id(); // Keep the order for improved thread loading performance.
1989 }
1990 }
1991 }
1992
1993 {
1994 String title = packed_scene.is_valid() ? "[gd_scene " : "[gd_resource ";
1995 if (packed_scene.is_null()) {
1996 title += "type=\"" + _resource_get_class(p_resource) + "\" ";
1997 Ref<Script> script = p_resource->get_script();
1998 if (script.is_valid() && script->get_global_name()) {
1999 title += "script_class=\"" + String(script->get_global_name()) + "\" ";
2000 }
2001 }
2002
2003 int load_steps = saved_resources.size() + external_resources.size();
2004
2005 if (load_steps > 1) {
2006 title += "load_steps=" + itos(load_steps) + " ";
2007 }
2008 title += "format=" + itos(FORMAT_VERSION) + "";
2009
2010 ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true);
2011
2012 if (uid != ResourceUID::INVALID_ID) {
2013 title += " uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\"";
2014 }
2015
2016 f->store_string(title);
2017 f->store_line("]\n"); // One empty line.
2018 }
2019
2020#ifdef TOOLS_ENABLED
2021 // Keep order from cached ids.
2022 HashSet<String> cached_ids_found;
2023 for (KeyValue<Ref<Resource>, String> &E : external_resources) {
2024 String cached_id = E.key->get_id_for_path(local_path);
2025 if (cached_id.is_empty() || cached_ids_found.has(cached_id)) {
2026 int sep_pos = E.value.find("_");
2027 if (sep_pos != -1) {
2028 E.value = E.value.substr(0, sep_pos + 1); // Keep the order found, for improved thread loading performance.
2029 } else {
2030 E.value = "";
2031 }
2032
2033 } else {
2034 E.value = cached_id;
2035 cached_ids_found.insert(cached_id);
2036 }
2037 }
2038 // Create IDs for non cached resources.
2039 for (KeyValue<Ref<Resource>, String> &E : external_resources) {
2040 if (cached_ids_found.has(E.value)) { // Already cached, go on.
2041 continue;
2042 }
2043
2044 String attempt;
2045 while (true) {
2046 attempt = E.value + Resource::generate_scene_unique_id();
2047 if (!cached_ids_found.has(attempt)) {
2048 break;
2049 }
2050 }
2051
2052 cached_ids_found.insert(attempt);
2053 E.value = attempt;
2054 // Update also in resource.
2055 Ref<Resource> res = E.key;
2056 res->set_id_for_path(local_path, attempt);
2057 }
2058#else
2059 // Make sure to start from one, as it makes format more readable.
2060 int counter = 1;
2061 for (KeyValue<Ref<Resource>, String> &E : external_resources) {
2062 E.value = itos(counter++);
2063 }
2064#endif
2065
2066 Vector<ResourceSort> sorted_er;
2067
2068 for (const KeyValue<Ref<Resource>, String> &E : external_resources) {
2069 ResourceSort rs;
2070 rs.resource = E.key;
2071 rs.id = E.value;
2072 sorted_er.push_back(rs);
2073 }
2074
2075 sorted_er.sort();
2076
2077 for (int i = 0; i < sorted_er.size(); i++) {
2078 String p = sorted_er[i].resource->get_path();
2079
2080 String s = "[ext_resource type=\"" + sorted_er[i].resource->get_save_class() + "\"";
2081
2082 ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(p, false);
2083 if (uid != ResourceUID::INVALID_ID) {
2084 s += " uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\"";
2085 }
2086 s += " path=\"" + p + "\" id=\"" + sorted_er[i].id + "\"]\n";
2087 f->store_string(s); // Bundled.
2088 }
2089
2090 if (external_resources.size()) {
2091 f->store_line(String()); // Separate.
2092 }
2093
2094 HashSet<String> used_unique_ids;
2095
2096 for (List<Ref<Resource>>::Element *E = saved_resources.front(); E; E = E->next()) {
2097 Ref<Resource> res = E->get();
2098 if (E->next() && res->is_built_in()) {
2099 if (!res->get_scene_unique_id().is_empty()) {
2100 if (used_unique_ids.has(res->get_scene_unique_id())) {
2101 res->set_scene_unique_id(""); // Repeated.
2102 } else {
2103 used_unique_ids.insert(res->get_scene_unique_id());
2104 }
2105 }
2106 }
2107 }
2108
2109 for (List<Ref<Resource>>::Element *E = saved_resources.front(); E; E = E->next()) {
2110 Ref<Resource> res = E->get();
2111 ERR_CONTINUE(!resource_set.has(res));
2112 bool main = (E->next() == nullptr);
2113
2114 if (main && packed_scene.is_valid()) {
2115 break; // Save as a scene.
2116 }
2117
2118 if (main) {
2119 f->store_line("[resource]");
2120 } else {
2121 String line = "[sub_resource ";
2122 if (res->get_scene_unique_id().is_empty()) {
2123 String new_id;
2124 while (true) {
2125 new_id = _resource_get_class(res) + "_" + Resource::generate_scene_unique_id();
2126
2127 if (!used_unique_ids.has(new_id)) {
2128 break;
2129 }
2130 }
2131
2132 res->set_scene_unique_id(new_id);
2133 used_unique_ids.insert(new_id);
2134 }
2135
2136 String id = res->get_scene_unique_id();
2137 line += "type=\"" + _resource_get_class(res) + "\" id=\"" + id;
2138 f->store_line(line + "\"]");
2139 if (takeover_paths) {
2140 res->set_path(p_path + "::" + id, true);
2141 }
2142
2143 internal_resources[res] = id;
2144#ifdef TOOLS_ENABLED
2145 res->set_edited(false);
2146#endif
2147 }
2148
2149 Dictionary missing_resource_properties = p_resource->get_meta(META_MISSING_RESOURCES, Dictionary());
2150
2151 List<PropertyInfo> property_list;
2152 res->get_property_list(&property_list);
2153 for (List<PropertyInfo>::Element *PE = property_list.front(); PE; PE = PE->next()) {
2154 if (skip_editor && PE->get().name.begins_with("__editor")) {
2155 continue;
2156 }
2157 if (PE->get().name == META_PROPERTY_MISSING_RESOURCES) {
2158 continue;
2159 }
2160
2161 if (PE->get().usage & PROPERTY_USAGE_STORAGE) {
2162 String name = PE->get().name;
2163 Variant value;
2164 if (PE->get().usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) {
2165 NonPersistentKey npk;
2166 npk.base = res;
2167 npk.property = name;
2168 if (non_persistent_map.has(npk)) {
2169 value = non_persistent_map[npk];
2170 }
2171 } else {
2172 value = res->get(name);
2173 }
2174
2175 if (PE->get().type == Variant::OBJECT && missing_resource_properties.has(PE->get().name)) {
2176 // Was this missing resource overridden? If so do not save the old value.
2177 Ref<Resource> ures = value;
2178 if (ures.is_null()) {
2179 value = missing_resource_properties[PE->get().name];
2180 }
2181 }
2182
2183 Variant default_value = ClassDB::class_get_default_property_value(res->get_class(), name);
2184
2185 if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
2186 continue;
2187 }
2188
2189 if (PE->get().type == Variant::OBJECT && value.is_zero() && !(PE->get().usage & PROPERTY_USAGE_STORE_IF_NULL)) {
2190 continue;
2191 }
2192
2193 String vars;
2194 VariantWriter::write_to_string(value, vars, _write_resources, this);
2195 f->store_string(name.property_name_encode() + " = " + vars + "\n");
2196 }
2197 }
2198
2199 if (E->next()) {
2200 f->store_line(String());
2201 }
2202 }
2203
2204 if (packed_scene.is_valid()) {
2205 // If this is a scene, save nodes and connections!
2206 Ref<SceneState> state = packed_scene->get_state();
2207 for (int i = 0; i < state->get_node_count(); i++) {
2208 StringName type = state->get_node_type(i);
2209 StringName name = state->get_node_name(i);
2210 int index = state->get_node_index(i);
2211 NodePath path = state->get_node_path(i, true);
2212 NodePath owner = state->get_node_owner_path(i);
2213 Ref<PackedScene> instance = state->get_node_instance(i);
2214 String instance_placeholder = state->get_node_instance_placeholder(i);
2215 Vector<StringName> groups = state->get_node_groups(i);
2216 Vector<String> deferred_node_paths = state->get_node_deferred_nodepath_properties(i);
2217
2218 String header = "[node";
2219 header += " name=\"" + String(name).c_escape() + "\"";
2220 if (type != StringName()) {
2221 header += " type=\"" + String(type) + "\"";
2222 }
2223 if (path != NodePath()) {
2224 header += " parent=\"" + String(path.simplified()).c_escape() + "\"";
2225 }
2226 if (owner != NodePath() && owner != NodePath(".")) {
2227 header += " owner=\"" + String(owner.simplified()).c_escape() + "\"";
2228 }
2229 if (index >= 0) {
2230 header += " index=\"" + itos(index) + "\"";
2231 }
2232
2233 if (deferred_node_paths.size()) {
2234 header += " node_paths=" + Variant(deferred_node_paths).get_construct_string();
2235 }
2236
2237 if (groups.size()) {
2238 // Write all groups on the same line as they're part of a section header.
2239 // This improves readability while not impacting VCS friendliness too much,
2240 // since it's rare to have more than 5 groups assigned to a single node.
2241 groups.sort_custom<StringName::AlphCompare>();
2242 String sgroups = " groups=[";
2243 for (int j = 0; j < groups.size(); j++) {
2244 sgroups += "\"" + String(groups[j]).c_escape() + "\"";
2245 if (j < groups.size() - 1) {
2246 sgroups += ", ";
2247 }
2248 }
2249 sgroups += "]";
2250 header += sgroups;
2251 }
2252
2253 f->store_string(header);
2254
2255 if (!instance_placeholder.is_empty()) {
2256 String vars;
2257 f->store_string(" instance_placeholder=");
2258 VariantWriter::write_to_string(instance_placeholder, vars, _write_resources, this);
2259 f->store_string(vars);
2260 }
2261
2262 if (instance.is_valid()) {
2263 String vars;
2264 f->store_string(" instance=");
2265 VariantWriter::write_to_string(instance, vars, _write_resources, this);
2266 f->store_string(vars);
2267 }
2268
2269 f->store_line("]");
2270
2271 for (int j = 0; j < state->get_node_property_count(i); j++) {
2272 String vars;
2273 VariantWriter::write_to_string(state->get_node_property_value(i, j), vars, _write_resources, this);
2274
2275 f->store_string(String(state->get_node_property_name(i, j)).property_name_encode() + " = " + vars + "\n");
2276 }
2277
2278 if (i < state->get_node_count() - 1) {
2279 f->store_line(String());
2280 }
2281 }
2282
2283 for (int i = 0; i < state->get_connection_count(); i++) {
2284 if (i == 0) {
2285 f->store_line("");
2286 }
2287
2288 String connstr = "[connection";
2289 connstr += " signal=\"" + String(state->get_connection_signal(i)) + "\"";
2290 connstr += " from=\"" + String(state->get_connection_source(i).simplified()) + "\"";
2291 connstr += " to=\"" + String(state->get_connection_target(i).simplified()) + "\"";
2292 connstr += " method=\"" + String(state->get_connection_method(i)) + "\"";
2293 int flags = state->get_connection_flags(i);
2294 if (flags != Object::CONNECT_PERSIST) {
2295 connstr += " flags=" + itos(flags);
2296 }
2297
2298 int unbinds = state->get_connection_unbinds(i);
2299 if (unbinds > 0) {
2300 connstr += " unbinds=" + itos(unbinds);
2301 }
2302
2303 Array binds = state->get_connection_binds(i);
2304 f->store_string(connstr);
2305 if (binds.size()) {
2306 String vars;
2307 VariantWriter::write_to_string(binds, vars, _write_resources, this);
2308 f->store_string(" binds= " + vars);
2309 }
2310
2311 f->store_line("]");
2312 }
2313
2314 Vector<NodePath> editable_instances = state->get_editable_instances();
2315 for (int i = 0; i < editable_instances.size(); i++) {
2316 if (i == 0) {
2317 f->store_line("");
2318 }
2319 f->store_line("[editable path=\"" + editable_instances[i].operator String() + "\"]");
2320 }
2321 }
2322
2323 if (f->get_error() != OK && f->get_error() != ERR_FILE_EOF) {
2324 return ERR_CANT_CREATE;
2325 }
2326
2327 return OK;
2328}
2329
2330Error ResourceLoaderText::set_uid(Ref<FileAccess> p_f, ResourceUID::ID p_uid) {
2331 open(p_f, true);
2332 ERR_FAIL_COND_V(error != OK, error);
2333 ignore_resource_parsing = true;
2334
2335 Ref<FileAccess> fw;
2336
2337 fw = FileAccess::open(local_path + ".uidren", FileAccess::WRITE);
2338 if (is_scene) {
2339 fw->store_string("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
2340 } else {
2341 String script_res_text;
2342 if (!script_class.is_empty()) {
2343 script_res_text = "script_class=\"" + script_class + "\" ";
2344 }
2345
2346 fw->store_string("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
2347 }
2348
2349 uint8_t c = f->get_8();
2350 while (!f->eof_reached()) {
2351 fw->store_8(c);
2352 c = f->get_8();
2353 }
2354
2355 bool all_ok = fw->get_error() == OK;
2356
2357 if (!all_ok) {
2358 return ERR_CANT_CREATE;
2359 }
2360
2361 return OK;
2362}
2363
2364Error ResourceFormatSaverText::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
2365 if (p_path.ends_with(".tscn") && !Ref<PackedScene>(p_resource).is_valid()) {
2366 return ERR_FILE_UNRECOGNIZED;
2367 }
2368
2369 ResourceFormatSaverTextInstance saver;
2370 return saver.save(p_path, p_resource, p_flags);
2371}
2372
2373Error ResourceFormatSaverText::set_uid(const String &p_path, ResourceUID::ID p_uid) {
2374 String lc = p_path.to_lower();
2375 if (!lc.ends_with(".tscn") && !lc.ends_with(".tres")) {
2376 return ERR_FILE_UNRECOGNIZED;
2377 }
2378
2379 String local_path = ProjectSettings::get_singleton()->localize_path(p_path);
2380 Error err = OK;
2381 {
2382 Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ);
2383 if (file.is_null()) {
2384 ERR_FAIL_V(ERR_CANT_OPEN);
2385 }
2386
2387 ResourceLoaderText loader;
2388 loader.local_path = local_path;
2389 loader.res_path = loader.local_path;
2390 err = loader.set_uid(file, p_uid);
2391 }
2392
2393 if (err == OK) {
2394 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
2395 da->remove(local_path);
2396 da->rename(local_path + ".uidren", local_path);
2397 }
2398
2399 return err;
2400}
2401
2402bool ResourceFormatSaverText::recognize(const Ref<Resource> &p_resource) const {
2403 return true; // All resources recognized!
2404}
2405
2406void ResourceFormatSaverText::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const {
2407 if (Ref<PackedScene>(p_resource).is_valid()) {
2408 p_extensions->push_back("tscn"); // Text scene.
2409 } else {
2410 p_extensions->push_back("tres"); // Text resource.
2411 }
2412}
2413
2414ResourceFormatSaverText *ResourceFormatSaverText::singleton = nullptr;
2415ResourceFormatSaverText::ResourceFormatSaverText() {
2416 singleton = this;
2417}
2418