1 /*************************************************************************/
2 /* editor_export.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
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 "editor_export.h"
32
33 #include "core/crypto/crypto_core.h"
34 #include "core/io/config_file.h"
35 #include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION
36 #include "core/io/resource_loader.h"
37 #include "core/io/resource_saver.h"
38 #include "core/io/zip_io.h"
39 #include "core/os/dir_access.h"
40 #include "core/os/file_access.h"
41 #include "core/project_settings.h"
42 #include "core/script_language.h"
43 #include "core/version.h"
44 #include "editor/editor_file_system.h"
45 #include "editor/plugins/script_editor_plugin.h"
46 #include "editor_node.h"
47 #include "editor_settings.h"
48 #include "scene/resources/resource_format_text.h"
49
_get_pad(int p_alignment,int p_n)50 static int _get_pad(int p_alignment, int p_n) {
51
52 int rest = p_n % p_alignment;
53 int pad = 0;
54 if (rest > 0) {
55 pad = p_alignment - rest;
56 };
57
58 return pad;
59 }
60
61 #define PCK_PADDING 16
62
_set(const StringName & p_name,const Variant & p_value)63 bool EditorExportPreset::_set(const StringName &p_name, const Variant &p_value) {
64
65 if (values.has(p_name)) {
66 values[p_name] = p_value;
67 EditorExport::singleton->save_presets();
68 return true;
69 }
70
71 return false;
72 }
73
_get(const StringName & p_name,Variant & r_ret) const74 bool EditorExportPreset::_get(const StringName &p_name, Variant &r_ret) const {
75
76 if (values.has(p_name)) {
77 r_ret = values[p_name];
78 return true;
79 }
80
81 return false;
82 }
83
_get_property_list(List<PropertyInfo> * p_list) const84 void EditorExportPreset::_get_property_list(List<PropertyInfo> *p_list) const {
85
86 for (const List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
87
88 if (platform->get_option_visibility(E->get().name, values)) {
89 p_list->push_back(E->get());
90 }
91 }
92 }
93
get_platform() const94 Ref<EditorExportPlatform> EditorExportPreset::get_platform() const {
95
96 return platform;
97 }
98
update_files_to_export()99 void EditorExportPreset::update_files_to_export() {
100 Vector<String> to_remove;
101 for (Set<String>::Element *E = selected_files.front(); E; E = E->next()) {
102 if (!FileAccess::exists(E->get())) {
103 to_remove.push_back(E->get());
104 }
105 }
106 for (int i = 0; i < to_remove.size(); ++i) {
107 selected_files.erase(to_remove[i]);
108 }
109 }
110
get_files_to_export() const111 Vector<String> EditorExportPreset::get_files_to_export() const {
112
113 Vector<String> files;
114 for (Set<String>::Element *E = selected_files.front(); E; E = E->next()) {
115 files.push_back(E->get());
116 }
117 return files;
118 }
119
set_name(const String & p_name)120 void EditorExportPreset::set_name(const String &p_name) {
121 name = p_name;
122 EditorExport::singleton->save_presets();
123 }
124
get_name() const125 String EditorExportPreset::get_name() const {
126 return name;
127 }
128
set_runnable(bool p_enable)129 void EditorExportPreset::set_runnable(bool p_enable) {
130
131 runnable = p_enable;
132 EditorExport::singleton->save_presets();
133 }
134
is_runnable() const135 bool EditorExportPreset::is_runnable() const {
136
137 return runnable;
138 }
139
set_export_filter(ExportFilter p_filter)140 void EditorExportPreset::set_export_filter(ExportFilter p_filter) {
141
142 export_filter = p_filter;
143 EditorExport::singleton->save_presets();
144 }
145
get_export_filter() const146 EditorExportPreset::ExportFilter EditorExportPreset::get_export_filter() const {
147 return export_filter;
148 }
149
set_include_filter(const String & p_include)150 void EditorExportPreset::set_include_filter(const String &p_include) {
151
152 include_filter = p_include;
153 EditorExport::singleton->save_presets();
154 }
155
get_include_filter() const156 String EditorExportPreset::get_include_filter() const {
157
158 return include_filter;
159 }
160
set_export_path(const String & p_path)161 void EditorExportPreset::set_export_path(const String &p_path) {
162
163 export_path = p_path;
164 /* NOTE(SonerSound): if there is a need to implement a PropertyHint that specifically indicates a relative path,
165 * this should be removed. */
166 if (export_path.is_abs_path()) {
167 String res_path = OS::get_singleton()->get_resource_dir();
168 export_path = res_path.path_to_file(export_path);
169 }
170 EditorExport::singleton->save_presets();
171 }
172
get_export_path() const173 String EditorExportPreset::get_export_path() const {
174
175 return export_path;
176 }
177
set_exclude_filter(const String & p_exclude)178 void EditorExportPreset::set_exclude_filter(const String &p_exclude) {
179
180 exclude_filter = p_exclude;
181 EditorExport::singleton->save_presets();
182 }
183
get_exclude_filter() const184 String EditorExportPreset::get_exclude_filter() const {
185
186 return exclude_filter;
187 }
188
add_export_file(const String & p_path)189 void EditorExportPreset::add_export_file(const String &p_path) {
190
191 selected_files.insert(p_path);
192 EditorExport::singleton->save_presets();
193 }
194
remove_export_file(const String & p_path)195 void EditorExportPreset::remove_export_file(const String &p_path) {
196 selected_files.erase(p_path);
197 EditorExport::singleton->save_presets();
198 }
199
has_export_file(const String & p_path)200 bool EditorExportPreset::has_export_file(const String &p_path) {
201
202 return selected_files.has(p_path);
203 }
204
add_patch(const String & p_path,int p_at_pos)205 void EditorExportPreset::add_patch(const String &p_path, int p_at_pos) {
206
207 if (p_at_pos < 0)
208 patches.push_back(p_path);
209 else
210 patches.insert(p_at_pos, p_path);
211 EditorExport::singleton->save_presets();
212 }
213
remove_patch(int p_idx)214 void EditorExportPreset::remove_patch(int p_idx) {
215 patches.remove(p_idx);
216 EditorExport::singleton->save_presets();
217 }
218
set_patch(int p_index,const String & p_path)219 void EditorExportPreset::set_patch(int p_index, const String &p_path) {
220 ERR_FAIL_INDEX(p_index, patches.size());
221 patches.write[p_index] = p_path;
222 EditorExport::singleton->save_presets();
223 }
get_patch(int p_index)224 String EditorExportPreset::get_patch(int p_index) {
225
226 ERR_FAIL_INDEX_V(p_index, patches.size(), String());
227 return patches[p_index];
228 }
229
get_patches() const230 Vector<String> EditorExportPreset::get_patches() const {
231 return patches;
232 }
233
set_custom_features(const String & p_custom_features)234 void EditorExportPreset::set_custom_features(const String &p_custom_features) {
235
236 custom_features = p_custom_features;
237 EditorExport::singleton->save_presets();
238 }
239
get_custom_features() const240 String EditorExportPreset::get_custom_features() const {
241
242 return custom_features;
243 }
244
set_script_export_mode(int p_mode)245 void EditorExportPreset::set_script_export_mode(int p_mode) {
246
247 script_mode = p_mode;
248 EditorExport::singleton->save_presets();
249 }
250
get_script_export_mode() const251 int EditorExportPreset::get_script_export_mode() const {
252
253 return script_mode;
254 }
255
set_script_encryption_key(const String & p_key)256 void EditorExportPreset::set_script_encryption_key(const String &p_key) {
257
258 script_key = p_key;
259 EditorExport::singleton->save_presets();
260 }
261
get_script_encryption_key() const262 String EditorExportPreset::get_script_encryption_key() const {
263
264 return script_key;
265 }
266
EditorExportPreset()267 EditorExportPreset::EditorExportPreset() :
268 export_filter(EXPORT_ALL_RESOURCES),
269 export_path(""),
270 runnable(false),
271 script_mode(MODE_SCRIPT_COMPILED) {
272 }
273
274 ///////////////////////////////////
275
gen_debug_flags(Vector<String> & r_flags,int p_flags)276 void EditorExportPlatform::gen_debug_flags(Vector<String> &r_flags, int p_flags) {
277
278 String host = EditorSettings::get_singleton()->get("network/debug/remote_host");
279 int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
280
281 if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)
282 host = "localhost";
283
284 if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
285 int port = EditorSettings::get_singleton()->get("filesystem/file_server/port");
286 String passwd = EditorSettings::get_singleton()->get("filesystem/file_server/password");
287 r_flags.push_back("--remote-fs");
288 r_flags.push_back(host + ":" + itos(port));
289 if (passwd != "") {
290 r_flags.push_back("--remote-fs-password");
291 r_flags.push_back(passwd);
292 }
293 }
294
295 if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) {
296
297 r_flags.push_back("--remote-debug");
298
299 r_flags.push_back(host + ":" + String::num(remote_port));
300
301 List<String> breakpoints;
302 ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
303
304 if (breakpoints.size()) {
305
306 r_flags.push_back("--breakpoints");
307 String bpoints;
308 for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
309
310 bpoints += E->get().replace(" ", "%20");
311 if (E->next())
312 bpoints += ",";
313 }
314
315 r_flags.push_back(bpoints);
316 }
317 }
318
319 if (p_flags & DEBUG_FLAG_VIEW_COLLISONS) {
320
321 r_flags.push_back("--debug-collisions");
322 }
323
324 if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
325
326 r_flags.push_back("--debug-navigation");
327 }
328 }
329
_save_pack_file(void * p_userdata,const String & p_path,const Vector<uint8_t> & p_data,int p_file,int p_total)330 Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
331
332 PackData *pd = (PackData *)p_userdata;
333
334 SavedData sd;
335 sd.path_utf8 = p_path.utf8();
336 sd.ofs = pd->f->get_position();
337 sd.size = p_data.size();
338
339 pd->f->store_buffer(p_data.ptr(), p_data.size());
340 int pad = _get_pad(PCK_PADDING, sd.size);
341 for (int i = 0; i < pad; i++) {
342 pd->f->store_8(0);
343 }
344
345 {
346 unsigned char hash[16];
347 CryptoCore::md5(p_data.ptr(), p_data.size(), hash);
348 sd.md5.resize(16);
349 for (int i = 0; i < 16; i++) {
350 sd.md5.write[i] = hash[i];
351 }
352 }
353
354 pd->file_ofs.push_back(sd);
355
356 if (pd->ep->step(TTR("Storing File:") + " " + p_path, 2 + p_file * 100 / p_total, false)) {
357 return ERR_SKIP;
358 }
359
360 return OK;
361 }
362
_save_zip_file(void * p_userdata,const String & p_path,const Vector<uint8_t> & p_data,int p_file,int p_total)363 Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
364
365 String path = p_path.replace_first("res://", "");
366
367 ZipData *zd = (ZipData *)p_userdata;
368
369 zipFile zip = (zipFile)zd->zip;
370
371 zipOpenNewFileInZip(zip,
372 path.utf8().get_data(),
373 NULL,
374 NULL,
375 0,
376 NULL,
377 0,
378 NULL,
379 Z_DEFLATED,
380 Z_DEFAULT_COMPRESSION);
381
382 zipWriteInFileInZip(zip, p_data.ptr(), p_data.size());
383 zipCloseFileInZip(zip);
384
385 if (zd->ep->step(TTR("Storing File:") + " " + p_path, 2 + p_file * 100 / p_total, false)) {
386 return ERR_SKIP;
387 }
388
389 return OK;
390 }
391
get_option_icon(int p_index) const392 Ref<ImageTexture> EditorExportPlatform::get_option_icon(int p_index) const {
393 Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
394 ERR_FAIL_COND_V(theme.is_null(), Ref<ImageTexture>());
395 return theme->get_icon("Play", "EditorIcons");
396 }
397
find_export_template(String template_file_name,String * err) const398 String EditorExportPlatform::find_export_template(String template_file_name, String *err) const {
399
400 String current_version = VERSION_FULL_CONFIG;
401 String template_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version).plus_file(template_file_name);
402
403 if (FileAccess::exists(template_path)) {
404 return template_path;
405 }
406
407 // Not found
408 if (err) {
409 *err += TTR("No export template found at the expected path:") + "\n" + template_path + "\n";
410 }
411 return String();
412 }
413
exists_export_template(String template_file_name,String * err) const414 bool EditorExportPlatform::exists_export_template(String template_file_name, String *err) const {
415 return find_export_template(template_file_name, err) != "";
416 }
417
create_preset()418 Ref<EditorExportPreset> EditorExportPlatform::create_preset() {
419
420 Ref<EditorExportPreset> preset;
421 preset.instance();
422 preset->platform = Ref<EditorExportPlatform>(this);
423
424 List<ExportOption> options;
425 get_export_options(&options);
426
427 for (List<ExportOption>::Element *E = options.front(); E; E = E->next()) {
428
429 preset->properties.push_back(E->get().option);
430 preset->values[E->get().option.name] = E->get().default_value;
431 }
432
433 return preset;
434 }
435
_export_find_resources(EditorFileSystemDirectory * p_dir,Set<String> & p_paths)436 void EditorExportPlatform::_export_find_resources(EditorFileSystemDirectory *p_dir, Set<String> &p_paths) {
437
438 for (int i = 0; i < p_dir->get_subdir_count(); i++) {
439 _export_find_resources(p_dir->get_subdir(i), p_paths);
440 }
441
442 for (int i = 0; i < p_dir->get_file_count(); i++) {
443 p_paths.insert(p_dir->get_file_path(i));
444 }
445 }
446
_export_find_dependencies(const String & p_path,Set<String> & p_paths)447 void EditorExportPlatform::_export_find_dependencies(const String &p_path, Set<String> &p_paths) {
448
449 if (p_paths.has(p_path))
450 return;
451
452 p_paths.insert(p_path);
453
454 EditorFileSystemDirectory *dir;
455 int file_idx;
456 dir = EditorFileSystem::get_singleton()->find_file(p_path, &file_idx);
457 if (!dir)
458 return;
459
460 Vector<String> deps = dir->get_file_deps(file_idx);
461
462 for (int i = 0; i < deps.size(); i++) {
463
464 _export_find_dependencies(deps[i], p_paths);
465 }
466 }
467
_edit_files_with_filter(DirAccess * da,const Vector<String> & p_filters,Set<String> & r_list,bool exclude)468 void EditorExportPlatform::_edit_files_with_filter(DirAccess *da, const Vector<String> &p_filters, Set<String> &r_list, bool exclude) {
469
470 da->list_dir_begin();
471 String cur_dir = da->get_current_dir().replace("\\", "/");
472 if (!cur_dir.ends_with("/"))
473 cur_dir += "/";
474 String cur_dir_no_prefix = cur_dir.replace("res://", "");
475
476 Vector<String> dirs;
477 String f;
478 while ((f = da->get_next()) != "") {
479 if (da->current_is_dir())
480 dirs.push_back(f);
481 else {
482 String fullpath = cur_dir + f;
483 // Test also against path without res:// so that filters like `file.txt` can work.
484 String fullpath_no_prefix = cur_dir_no_prefix + f;
485 for (int i = 0; i < p_filters.size(); ++i) {
486 if (fullpath.matchn(p_filters[i]) || fullpath_no_prefix.matchn(p_filters[i])) {
487 if (!exclude) {
488 r_list.insert(fullpath);
489 } else {
490 r_list.erase(fullpath);
491 }
492 }
493 }
494 }
495 }
496
497 da->list_dir_end();
498
499 for (int i = 0; i < dirs.size(); ++i) {
500 String dir = dirs[i];
501 if (dir.begins_with("."))
502 continue;
503 da->change_dir(dir);
504 _edit_files_with_filter(da, p_filters, r_list, exclude);
505 da->change_dir("..");
506 }
507 }
508
_edit_filter_list(Set<String> & r_list,const String & p_filter,bool exclude)509 void EditorExportPlatform::_edit_filter_list(Set<String> &r_list, const String &p_filter, bool exclude) {
510
511 if (p_filter == "")
512 return;
513 Vector<String> split = p_filter.split(",");
514 Vector<String> filters;
515 for (int i = 0; i < split.size(); i++) {
516 String f = split[i].strip_edges();
517 if (f.empty())
518 continue;
519 filters.push_back(f);
520 }
521
522 DirAccess *da = DirAccess::open("res://");
523 ERR_FAIL_NULL(da);
524 _edit_files_with_filter(da, filters, r_list, exclude);
525 memdelete(da);
526 }
527
set_export_preset(const Ref<EditorExportPreset> & p_preset)528 void EditorExportPlugin::set_export_preset(const Ref<EditorExportPreset> &p_preset) {
529
530 if (p_preset.is_valid()) {
531 export_preset = p_preset;
532 }
533 }
534
get_export_preset() const535 Ref<EditorExportPreset> EditorExportPlugin::get_export_preset() const {
536
537 return export_preset;
538 }
539
add_file(const String & p_path,const Vector<uint8_t> & p_file,bool p_remap)540 void EditorExportPlugin::add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap) {
541
542 ExtraFile ef;
543 ef.data = p_file;
544 ef.path = p_path;
545 ef.remap = p_remap;
546 extra_files.push_back(ef);
547 }
548
add_shared_object(const String & p_path,const Vector<String> & tags)549 void EditorExportPlugin::add_shared_object(const String &p_path, const Vector<String> &tags) {
550
551 shared_objects.push_back(SharedObject(p_path, tags));
552 }
553
add_ios_framework(const String & p_path)554 void EditorExportPlugin::add_ios_framework(const String &p_path) {
555 ios_frameworks.push_back(p_path);
556 }
557
add_ios_embedded_framework(const String & p_path)558 void EditorExportPlugin::add_ios_embedded_framework(const String &p_path) {
559 ios_embedded_frameworks.push_back(p_path);
560 }
561
get_ios_frameworks() const562 Vector<String> EditorExportPlugin::get_ios_frameworks() const {
563 return ios_frameworks;
564 }
565
get_ios_embedded_frameworks() const566 Vector<String> EditorExportPlugin::get_ios_embedded_frameworks() const {
567 return ios_embedded_frameworks;
568 }
569
add_ios_plist_content(const String & p_plist_content)570 void EditorExportPlugin::add_ios_plist_content(const String &p_plist_content) {
571 ios_plist_content += p_plist_content + "\n";
572 }
573
get_ios_plist_content() const574 String EditorExportPlugin::get_ios_plist_content() const {
575 return ios_plist_content;
576 }
577
add_ios_linker_flags(const String & p_flags)578 void EditorExportPlugin::add_ios_linker_flags(const String &p_flags) {
579 if (ios_linker_flags.length() > 0) {
580 ios_linker_flags += ' ';
581 }
582 ios_linker_flags += p_flags;
583 }
584
get_ios_linker_flags() const585 String EditorExportPlugin::get_ios_linker_flags() const {
586 return ios_linker_flags;
587 }
588
add_ios_bundle_file(const String & p_path)589 void EditorExportPlugin::add_ios_bundle_file(const String &p_path) {
590 ios_bundle_files.push_back(p_path);
591 }
592
get_ios_bundle_files() const593 Vector<String> EditorExportPlugin::get_ios_bundle_files() const {
594 return ios_bundle_files;
595 }
596
add_ios_cpp_code(const String & p_code)597 void EditorExportPlugin::add_ios_cpp_code(const String &p_code) {
598 ios_cpp_code += p_code;
599 }
600
get_ios_cpp_code() const601 String EditorExportPlugin::get_ios_cpp_code() const {
602 return ios_cpp_code;
603 }
604
add_ios_project_static_lib(const String & p_path)605 void EditorExportPlugin::add_ios_project_static_lib(const String &p_path) {
606 ios_project_static_libs.push_back(p_path);
607 }
608
get_ios_project_static_libs() const609 Vector<String> EditorExportPlugin::get_ios_project_static_libs() const {
610 return ios_project_static_libs;
611 }
612
_export_file_script(const String & p_path,const String & p_type,const PoolVector<String> & p_features)613 void EditorExportPlugin::_export_file_script(const String &p_path, const String &p_type, const PoolVector<String> &p_features) {
614
615 if (get_script_instance()) {
616 get_script_instance()->call("_export_file", p_path, p_type, p_features);
617 }
618 }
619
_export_begin_script(const PoolVector<String> & p_features,bool p_debug,const String & p_path,int p_flags)620 void EditorExportPlugin::_export_begin_script(const PoolVector<String> &p_features, bool p_debug, const String &p_path, int p_flags) {
621
622 if (get_script_instance()) {
623 get_script_instance()->call("_export_begin", p_features, p_debug, p_path, p_flags);
624 }
625 }
626
_export_end_script()627 void EditorExportPlugin::_export_end_script() {
628
629 if (get_script_instance()) {
630 get_script_instance()->call("_export_end");
631 }
632 }
633
_export_file(const String & p_path,const String & p_type,const Set<String> & p_features)634 void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const Set<String> &p_features) {
635 }
636
_export_begin(const Set<String> & p_features,bool p_debug,const String & p_path,int p_flags)637 void EditorExportPlugin::_export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags) {
638 }
639
skip()640 void EditorExportPlugin::skip() {
641
642 skipped = true;
643 }
644
_bind_methods()645 void EditorExportPlugin::_bind_methods() {
646
647 ClassDB::bind_method(D_METHOD("add_shared_object", "path", "tags"), &EditorExportPlugin::add_shared_object);
648 ClassDB::bind_method(D_METHOD("add_ios_project_static_lib", "path"), &EditorExportPlugin::add_ios_project_static_lib);
649 ClassDB::bind_method(D_METHOD("add_file", "path", "file", "remap"), &EditorExportPlugin::add_file);
650 ClassDB::bind_method(D_METHOD("add_ios_framework", "path"), &EditorExportPlugin::add_ios_framework);
651 ClassDB::bind_method(D_METHOD("add_ios_embedded_framework", "path"), &EditorExportPlugin::add_ios_embedded_framework);
652 ClassDB::bind_method(D_METHOD("add_ios_plist_content", "plist_content"), &EditorExportPlugin::add_ios_plist_content);
653 ClassDB::bind_method(D_METHOD("add_ios_linker_flags", "flags"), &EditorExportPlugin::add_ios_linker_flags);
654 ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_ios_bundle_file);
655 ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_ios_cpp_code);
656 ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip);
657
658 BIND_VMETHOD(MethodInfo("_export_file", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::POOL_STRING_ARRAY, "features")));
659 BIND_VMETHOD(MethodInfo("_export_begin", PropertyInfo(Variant::POOL_STRING_ARRAY, "features"), PropertyInfo(Variant::BOOL, "is_debug"), PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "flags")));
660 BIND_VMETHOD(MethodInfo("_export_end"));
661 }
662
EditorExportPlugin()663 EditorExportPlugin::EditorExportPlugin() {
664 skipped = false;
665 }
666
get_feature_containers(const Ref<EditorExportPreset> & p_preset)667 EditorExportPlatform::FeatureContainers EditorExportPlatform::get_feature_containers(const Ref<EditorExportPreset> &p_preset) {
668 Ref<EditorExportPlatform> platform = p_preset->get_platform();
669 List<String> feature_list;
670 platform->get_platform_features(&feature_list);
671 platform->get_preset_features(p_preset, &feature_list);
672
673 FeatureContainers result;
674 for (List<String>::Element *E = feature_list.front(); E; E = E->next()) {
675 result.features.insert(E->get());
676 result.features_pv.push_back(E->get());
677 }
678
679 if (p_preset->get_custom_features() != String()) {
680
681 Vector<String> tmp_custom_list = p_preset->get_custom_features().split(",");
682
683 for (int i = 0; i < tmp_custom_list.size(); i++) {
684 String f = tmp_custom_list[i].strip_edges();
685 if (f != String()) {
686 result.features.insert(f);
687 result.features_pv.push_back(f);
688 }
689 }
690 }
691
692 return result;
693 }
694
ExportNotifier(EditorExportPlatform & p_platform,const Ref<EditorExportPreset> & p_preset,bool p_debug,const String & p_path,int p_flags)695 EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
696 FeatureContainers features = p_platform.get_feature_containers(p_preset);
697 Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
698 //initial export plugin callback
699 for (int i = 0; i < export_plugins.size(); i++) {
700 if (export_plugins[i]->get_script_instance()) { //script based
701 export_plugins.write[i]->_export_begin_script(features.features_pv, p_debug, p_path, p_flags);
702 } else {
703 export_plugins.write[i]->_export_begin(features.features, p_debug, p_path, p_flags);
704 }
705 }
706 }
707
~ExportNotifier()708 EditorExportPlatform::ExportNotifier::~ExportNotifier() {
709 Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
710 for (int i = 0; i < export_plugins.size(); i++) {
711 if (export_plugins[i]->get_script_instance()) {
712 export_plugins.write[i]->_export_end_script();
713 }
714 export_plugins.write[i]->_export_end();
715 }
716 }
717
export_project_files(const Ref<EditorExportPreset> & p_preset,EditorExportSaveFunction p_func,void * p_udata,EditorExportSaveSharedObject p_so_func)718 Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) {
719 //figure out paths of files that will be exported
720 Set<String> paths;
721 Vector<String> path_remaps;
722
723 if (p_preset->get_export_filter() == EditorExportPreset::EXPORT_ALL_RESOURCES) {
724 //find stuff
725 _export_find_resources(EditorFileSystem::get_singleton()->get_filesystem(), paths);
726 } else {
727 bool scenes_only = p_preset->get_export_filter() == EditorExportPreset::EXPORT_SELECTED_SCENES;
728
729 Vector<String> files = p_preset->get_files_to_export();
730 for (int i = 0; i < files.size(); i++) {
731 if (scenes_only && ResourceLoader::get_resource_type(files[i]) != "PackedScene")
732 continue;
733
734 _export_find_dependencies(files[i], paths);
735 }
736 }
737
738 //add native icons to non-resource include list
739 _edit_filter_list(paths, String("*.icns"), false);
740 _edit_filter_list(paths, String("*.ico"), false);
741
742 _edit_filter_list(paths, p_preset->get_include_filter(), false);
743 _edit_filter_list(paths, p_preset->get_exclude_filter(), true);
744
745 Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
746 for (int i = 0; i < export_plugins.size(); i++) {
747
748 export_plugins.write[i]->set_export_preset(p_preset);
749
750 if (p_so_func) {
751 for (int j = 0; j < export_plugins[i]->shared_objects.size(); j++) {
752 p_so_func(p_udata, export_plugins[i]->shared_objects[j]);
753 }
754 }
755 for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) {
756 p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size());
757 }
758
759 export_plugins.write[i]->_clear();
760 }
761
762 FeatureContainers feature_containers = get_feature_containers(p_preset);
763 Set<String> &features = feature_containers.features;
764 PoolVector<String> &features_pv = feature_containers.features_pv;
765
766 //store everything in the export medium
767 int idx = 0;
768 int total = paths.size();
769
770 for (Set<String>::Element *E = paths.front(); E; E = E->next()) {
771
772 String path = E->get();
773 String type = ResourceLoader::get_resource_type(path);
774
775 if (FileAccess::exists(path + ".import")) {
776 //file is imported, replace by what it imports
777 Ref<ConfigFile> config;
778 config.instance();
779 Error err = config->load(path + ".import");
780 if (err != OK) {
781 ERR_PRINTS("Could not parse: '" + path + "', not exported.");
782 continue;
783 }
784
785 List<String> remaps;
786 config->get_section_keys("remap", &remaps);
787
788 Set<String> remap_features;
789
790 for (List<String>::Element *F = remaps.front(); F; F = F->next()) {
791
792 String remap = F->get();
793 String feature = remap.get_slice(".", 1);
794 if (features.has(feature)) {
795 remap_features.insert(feature);
796 }
797 }
798
799 if (remap_features.size() > 1) {
800 this->resolve_platform_feature_priorities(p_preset, remap_features);
801 }
802
803 err = OK;
804
805 for (List<String>::Element *F = remaps.front(); F; F = F->next()) {
806
807 String remap = F->get();
808 if (remap == "path") {
809 String remapped_path = config->get_value("remap", remap);
810 Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path);
811 err = p_func(p_udata, remapped_path, array, idx, total);
812 } else if (remap.begins_with("path.")) {
813 String feature = remap.get_slice(".", 1);
814
815 if (remap_features.has(feature)) {
816 String remapped_path = config->get_value("remap", remap);
817 Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path);
818 err = p_func(p_udata, remapped_path, array, idx, total);
819 }
820 }
821 }
822
823 if (err != OK) {
824 return err;
825 }
826
827 //also save the .import file
828 Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import");
829 err = p_func(p_udata, path + ".import", array, idx, total);
830
831 if (err != OK) {
832 return err;
833 }
834
835 } else {
836
837 bool do_export = true;
838 for (int i = 0; i < export_plugins.size(); i++) {
839 if (export_plugins[i]->get_script_instance()) { //script based
840 export_plugins.write[i]->_export_file_script(path, type, features_pv);
841 } else {
842 export_plugins.write[i]->_export_file(path, type, features);
843 }
844 if (p_so_func) {
845 for (int j = 0; j < export_plugins[i]->shared_objects.size(); j++) {
846 p_so_func(p_udata, export_plugins[i]->shared_objects[j]);
847 }
848 }
849
850 for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) {
851 p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total);
852 if (export_plugins[i]->extra_files[j].remap) {
853 do_export = false; //if remap, do not
854 path_remaps.push_back(path);
855 path_remaps.push_back(export_plugins[i]->extra_files[j].path);
856 }
857 }
858
859 if (export_plugins[i]->skipped) {
860 do_export = false;
861 }
862 export_plugins.write[i]->_clear();
863
864 if (!do_export)
865 break; //apologies, not exporting
866 }
867 //just store it as it comes
868 if (do_export) {
869 Vector<uint8_t> array = FileAccess::get_file_as_array(path);
870 p_func(p_udata, path, array, idx, total);
871 }
872 }
873
874 idx++;
875 }
876
877 //save config!
878
879 Vector<String> custom_list;
880
881 if (p_preset->get_custom_features() != String()) {
882
883 Vector<String> tmp_custom_list = p_preset->get_custom_features().split(",");
884
885 for (int i = 0; i < tmp_custom_list.size(); i++) {
886 String f = tmp_custom_list[i].strip_edges();
887 if (f != String()) {
888 custom_list.push_back(f);
889 }
890 }
891 }
892
893 ProjectSettings::CustomMap custom_map;
894 if (path_remaps.size()) {
895 if (1) { //new remap mode, use always as it's friendlier with multiple .pck exports
896 for (int i = 0; i < path_remaps.size(); i += 2) {
897 String from = path_remaps[i];
898 String to = path_remaps[i + 1];
899 String remap_file = "[remap]\n\npath=\"" + to.c_escape() + "\"\n";
900 CharString utf8 = remap_file.utf8();
901 Vector<uint8_t> new_file;
902 new_file.resize(utf8.length());
903 for (int j = 0; j < utf8.length(); j++) {
904 new_file.write[j] = utf8[j];
905 }
906
907 p_func(p_udata, from + ".remap", new_file, idx, total);
908 }
909 } else {
910 //old remap mode, will still work, but it's unused because it's not multiple pck export friendly
911 custom_map["path_remap/remapped_paths"] = path_remaps;
912 }
913 }
914
915 // Store icon and splash images directly, they need to bypass the import system and be loaded as images
916 String icon = ProjectSettings::get_singleton()->get("application/config/icon");
917 String splash = ProjectSettings::get_singleton()->get("application/boot_splash/image");
918 if (icon != String() && FileAccess::exists(icon)) {
919 Vector<uint8_t> array = FileAccess::get_file_as_array(icon);
920 p_func(p_udata, icon, array, idx, total);
921 }
922 if (splash != String() && FileAccess::exists(splash) && icon != splash) {
923 Vector<uint8_t> array = FileAccess::get_file_as_array(splash);
924 p_func(p_udata, splash, array, idx, total);
925 }
926
927 String config_file = "project.binary";
928 String engine_cfb = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp" + config_file);
929 ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list);
930 Vector<uint8_t> data = FileAccess::get_file_as_array(engine_cfb);
931 DirAccess::remove_file_or_error(engine_cfb);
932
933 p_func(p_udata, "res://" + config_file, data, idx, total);
934
935 return OK;
936 }
937
_add_shared_object(void * p_userdata,const SharedObject & p_so)938 Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) {
939 PackData *pack_data = (PackData *)p_userdata;
940 if (pack_data->so_files) {
941 pack_data->so_files->push_back(p_so);
942 }
943
944 return OK;
945 }
946
save_pack(const Ref<EditorExportPreset> & p_preset,const String & p_path,Vector<SharedObject> * p_so_files,bool p_embed,int64_t * r_embedded_start,int64_t * r_embedded_size)947 Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
948
949 EditorProgress ep("savepack", TTR("Packing"), 102, true);
950
951 String tmppath = EditorSettings::get_singleton()->get_cache_dir().plus_file("packtmp");
952 FileAccess *ftmp = FileAccess::open(tmppath, FileAccess::WRITE);
953 ERR_FAIL_COND_V_MSG(!ftmp, ERR_CANT_CREATE, "Cannot create file '" + tmppath + "'.");
954
955 PackData pd;
956 pd.ep = &ep;
957 pd.f = ftmp;
958 pd.so_files = p_so_files;
959
960 Error err = export_project_files(p_preset, _save_pack_file, &pd, _add_shared_object);
961
962 memdelete(ftmp); //close tmp file
963
964 if (err != OK) {
965 DirAccess::remove_file_or_error(tmppath);
966 return err;
967 }
968
969 pd.file_ofs.sort(); //do sort, so we can do binary search later
970
971 FileAccess *f;
972 int64_t embed_pos = 0;
973 if (!p_embed) {
974 // Regular output to separate PCK file
975 f = FileAccess::open(p_path, FileAccess::WRITE);
976 if (!f) {
977 DirAccess::remove_file_or_error(tmppath);
978 ERR_FAIL_V(ERR_CANT_CREATE);
979 }
980 } else {
981 // Append to executable
982 f = FileAccess::open(p_path, FileAccess::READ_WRITE);
983 if (!f) {
984 DirAccess::remove_file_or_error(tmppath);
985 ERR_FAIL_V(ERR_FILE_CANT_OPEN);
986 }
987
988 f->seek_end();
989 embed_pos = f->get_position();
990
991 if (r_embedded_start) {
992 *r_embedded_start = embed_pos;
993 }
994
995 // Ensure embedded PCK starts at a 64-bit multiple
996 int pad = f->get_position() % 8;
997 for (int i = 0; i < pad; i++) {
998 f->store_8(0);
999 }
1000 }
1001
1002 int64_t pck_start_pos = f->get_position();
1003
1004 f->store_32(PACK_HEADER_MAGIC);
1005 f->store_32(PACK_FORMAT_VERSION);
1006 f->store_32(VERSION_MAJOR);
1007 f->store_32(VERSION_MINOR);
1008 f->store_32(VERSION_PATCH);
1009
1010 for (int i = 0; i < 16; i++) {
1011 //reserved
1012 f->store_32(0);
1013 }
1014
1015 f->store_32(pd.file_ofs.size()); //amount of files
1016
1017 int64_t header_size = f->get_position();
1018
1019 //precalculate header size
1020
1021 for (int i = 0; i < pd.file_ofs.size(); i++) {
1022 header_size += 4; // size of path string (32 bits is enough)
1023 int string_len = pd.file_ofs[i].path_utf8.length();
1024 header_size += string_len + _get_pad(4, string_len); ///size of path string
1025 header_size += 8; // offset to file _with_ header size included
1026 header_size += 8; // size of file
1027 header_size += 16; // md5
1028 }
1029
1030 int header_padding = _get_pad(PCK_PADDING, header_size);
1031
1032 for (int i = 0; i < pd.file_ofs.size(); i++) {
1033
1034 int string_len = pd.file_ofs[i].path_utf8.length();
1035 int pad = _get_pad(4, string_len);
1036
1037 f->store_32(string_len + pad);
1038 f->store_buffer((const uint8_t *)pd.file_ofs[i].path_utf8.get_data(), string_len);
1039 for (int j = 0; j < pad; j++) {
1040 f->store_8(0);
1041 }
1042
1043 f->store_64(pd.file_ofs[i].ofs + header_padding + header_size);
1044 f->store_64(pd.file_ofs[i].size); // pay attention here, this is where file is
1045 f->store_buffer(pd.file_ofs[i].md5.ptr(), 16); //also save md5 for file
1046 }
1047
1048 for (int i = 0; i < header_padding; i++) {
1049 f->store_8(0);
1050 }
1051
1052 // Save the rest of the data.
1053
1054 ftmp = FileAccess::open(tmppath, FileAccess::READ);
1055 if (!ftmp) {
1056 memdelete(f);
1057 DirAccess::remove_file_or_error(tmppath);
1058 ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open file to read from path '" + String(tmppath) + "'.");
1059 }
1060
1061 const int bufsize = 16384;
1062 uint8_t buf[bufsize];
1063
1064 while (true) {
1065
1066 int got = ftmp->get_buffer(buf, bufsize);
1067 if (got <= 0)
1068 break;
1069 f->store_buffer(buf, got);
1070 }
1071
1072 memdelete(ftmp);
1073
1074 if (p_embed) {
1075 // Ensure embedded data ends at a 64-bit multiple
1076 int64_t embed_end = f->get_position() - embed_pos + 12;
1077 int pad = embed_end % 8;
1078 for (int i = 0; i < pad; i++) {
1079 f->store_8(0);
1080 }
1081
1082 int64_t pck_size = f->get_position() - pck_start_pos;
1083 f->store_64(pck_size);
1084 f->store_32(PACK_HEADER_MAGIC);
1085
1086 if (r_embedded_size) {
1087 *r_embedded_size = f->get_position() - embed_pos;
1088 }
1089 }
1090
1091 memdelete(f);
1092 DirAccess::remove_file_or_error(tmppath);
1093
1094 return OK;
1095 }
1096
save_zip(const Ref<EditorExportPreset> & p_preset,const String & p_path)1097 Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
1098
1099 EditorProgress ep("savezip", TTR("Packing"), 102, true);
1100
1101 FileAccess *src_f;
1102 zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
1103 zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io);
1104
1105 ZipData zd;
1106 zd.ep = &ep;
1107 zd.zip = zip;
1108
1109 Error err = export_project_files(p_preset, _save_zip_file, &zd);
1110 if (err != OK && err != ERR_SKIP)
1111 ERR_PRINT("Failed to export project files");
1112
1113 zipClose(zip, NULL);
1114
1115 return OK;
1116 }
1117
export_pack(const Ref<EditorExportPreset> & p_preset,bool p_debug,const String & p_path,int p_flags)1118 Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
1119 ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
1120 return save_pack(p_preset, p_path);
1121 }
1122
export_zip(const Ref<EditorExportPreset> & p_preset,bool p_debug,const String & p_path,int p_flags)1123 Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
1124 ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
1125 return save_zip(p_preset, p_path);
1126 }
1127
gen_export_flags(Vector<String> & r_flags,int p_flags)1128 void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags) {
1129
1130 String host = EditorSettings::get_singleton()->get("network/debug/remote_host");
1131 int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
1132
1133 if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)
1134 host = "localhost";
1135
1136 if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
1137 int port = EditorSettings::get_singleton()->get("filesystem/file_server/port");
1138 String passwd = EditorSettings::get_singleton()->get("filesystem/file_server/password");
1139 r_flags.push_back("--remote-fs");
1140 r_flags.push_back(host + ":" + itos(port));
1141 if (passwd != "") {
1142 r_flags.push_back("--remote-fs-password");
1143 r_flags.push_back(passwd);
1144 }
1145 }
1146
1147 if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) {
1148
1149 r_flags.push_back("--remote-debug");
1150
1151 r_flags.push_back(host + ":" + String::num(remote_port));
1152
1153 List<String> breakpoints;
1154 ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
1155
1156 if (breakpoints.size()) {
1157
1158 r_flags.push_back("--breakpoints");
1159 String bpoints;
1160 for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
1161
1162 bpoints += E->get().replace(" ", "%20");
1163 if (E->next())
1164 bpoints += ",";
1165 }
1166
1167 r_flags.push_back(bpoints);
1168 }
1169 }
1170
1171 if (p_flags & DEBUG_FLAG_VIEW_COLLISONS) {
1172
1173 r_flags.push_back("--debug-collisions");
1174 }
1175
1176 if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
1177
1178 r_flags.push_back("--debug-navigation");
1179 }
1180 }
EditorExportPlatform()1181 EditorExportPlatform::EditorExportPlatform() {
1182 }
1183
1184 ////
1185
1186 EditorExport *EditorExport::singleton = NULL;
1187
_save()1188 void EditorExport::_save() {
1189
1190 Ref<ConfigFile> config;
1191 config.instance();
1192 for (int i = 0; i < export_presets.size(); i++) {
1193
1194 Ref<EditorExportPreset> preset = export_presets[i];
1195 String section = "preset." + itos(i);
1196
1197 config->set_value(section, "name", preset->get_name());
1198 config->set_value(section, "platform", preset->get_platform()->get_name());
1199 config->set_value(section, "runnable", preset->is_runnable());
1200 config->set_value(section, "custom_features", preset->get_custom_features());
1201
1202 bool save_files = false;
1203 switch (preset->get_export_filter()) {
1204 case EditorExportPreset::EXPORT_ALL_RESOURCES: {
1205 config->set_value(section, "export_filter", "all_resources");
1206 } break;
1207 case EditorExportPreset::EXPORT_SELECTED_SCENES: {
1208 config->set_value(section, "export_filter", "scenes");
1209 save_files = true;
1210 } break;
1211 case EditorExportPreset::EXPORT_SELECTED_RESOURCES: {
1212 config->set_value(section, "export_filter", "resources");
1213 save_files = true;
1214 } break;
1215 }
1216
1217 if (save_files) {
1218 Vector<String> export_files = preset->get_files_to_export();
1219 config->set_value(section, "export_files", export_files);
1220 }
1221 config->set_value(section, "include_filter", preset->get_include_filter());
1222 config->set_value(section, "exclude_filter", preset->get_exclude_filter());
1223 config->set_value(section, "export_path", preset->get_export_path());
1224 config->set_value(section, "patch_list", preset->get_patches());
1225 config->set_value(section, "script_export_mode", preset->get_script_export_mode());
1226 config->set_value(section, "script_encryption_key", preset->get_script_encryption_key());
1227
1228 String option_section = "preset." + itos(i) + ".options";
1229
1230 for (const List<PropertyInfo>::Element *E = preset->get_properties().front(); E; E = E->next()) {
1231 config->set_value(option_section, E->get().name, preset->get(E->get().name));
1232 }
1233 }
1234
1235 config->save("res://export_presets.cfg");
1236 }
1237
save_presets()1238 void EditorExport::save_presets() {
1239
1240 if (block_save)
1241 return;
1242 save_timer->start();
1243 }
1244
_bind_methods()1245 void EditorExport::_bind_methods() {
1246
1247 ClassDB::bind_method("_save", &EditorExport::_save);
1248
1249 ADD_SIGNAL(MethodInfo("export_presets_updated"));
1250 }
1251
add_export_platform(const Ref<EditorExportPlatform> & p_platform)1252 void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
1253
1254 export_platforms.push_back(p_platform);
1255 }
1256
get_export_platform_count()1257 int EditorExport::get_export_platform_count() {
1258
1259 return export_platforms.size();
1260 }
1261
get_export_platform(int p_idx)1262 Ref<EditorExportPlatform> EditorExport::get_export_platform(int p_idx) {
1263
1264 ERR_FAIL_INDEX_V(p_idx, export_platforms.size(), Ref<EditorExportPlatform>());
1265
1266 return export_platforms[p_idx];
1267 }
1268
add_export_preset(const Ref<EditorExportPreset> & p_preset,int p_at_pos)1269 void EditorExport::add_export_preset(const Ref<EditorExportPreset> &p_preset, int p_at_pos) {
1270
1271 if (p_at_pos < 0)
1272 export_presets.push_back(p_preset);
1273 else
1274 export_presets.insert(p_at_pos, p_preset);
1275 }
1276
test_etc2() const1277 String EditorExportPlatform::test_etc2() const {
1278
1279 String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name");
1280 bool driver_fallback = ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2");
1281 bool etc_supported = ProjectSettings::get_singleton()->get("rendering/vram_compression/import_etc");
1282 bool etc2_supported = ProjectSettings::get_singleton()->get("rendering/vram_compression/import_etc2");
1283
1284 if (driver == "GLES2" && !etc_supported) {
1285 return TTR("Target platform requires 'ETC' texture compression for GLES2. Enable 'Import Etc' in Project Settings.");
1286 } else if (driver == "GLES3") {
1287 String err;
1288 if (!etc2_supported) {
1289 err += TTR("Target platform requires 'ETC2' texture compression for GLES3. Enable 'Import Etc 2' in Project Settings.");
1290 }
1291 if (driver_fallback && !etc_supported) {
1292 if (err != String())
1293 err += "\n";
1294 err += TTR("Target platform requires 'ETC' texture compression for the driver fallback to GLES2.\nEnable 'Import Etc' in Project Settings, or disable 'Driver Fallback Enabled'.");
1295 }
1296 return err;
1297 }
1298 return String();
1299 }
1300
get_export_preset_count() const1301 int EditorExport::get_export_preset_count() const {
1302
1303 return export_presets.size();
1304 }
1305
get_export_preset(int p_idx)1306 Ref<EditorExportPreset> EditorExport::get_export_preset(int p_idx) {
1307
1308 ERR_FAIL_INDEX_V(p_idx, export_presets.size(), Ref<EditorExportPreset>());
1309 return export_presets[p_idx];
1310 }
1311
remove_export_preset(int p_idx)1312 void EditorExport::remove_export_preset(int p_idx) {
1313
1314 export_presets.remove(p_idx);
1315 save_presets();
1316 }
1317
add_export_plugin(const Ref<EditorExportPlugin> & p_plugin)1318 void EditorExport::add_export_plugin(const Ref<EditorExportPlugin> &p_plugin) {
1319
1320 if (export_plugins.find(p_plugin) == -1) {
1321 export_plugins.push_back(p_plugin);
1322 }
1323 }
1324
remove_export_plugin(const Ref<EditorExportPlugin> & p_plugin)1325 void EditorExport::remove_export_plugin(const Ref<EditorExportPlugin> &p_plugin) {
1326
1327 export_plugins.erase(p_plugin);
1328 }
1329
get_export_plugins()1330 Vector<Ref<EditorExportPlugin> > EditorExport::get_export_plugins() {
1331
1332 return export_plugins;
1333 }
1334
_notification(int p_what)1335 void EditorExport::_notification(int p_what) {
1336
1337 switch (p_what) {
1338 case NOTIFICATION_ENTER_TREE: {
1339 load_config();
1340 } break;
1341 case NOTIFICATION_PROCESS: {
1342 update_export_presets();
1343 } break;
1344 }
1345 }
1346
load_config()1347 void EditorExport::load_config() {
1348
1349 Ref<ConfigFile> config;
1350 config.instance();
1351 Error err = config->load("res://export_presets.cfg");
1352 if (err != OK)
1353 return;
1354
1355 block_save = true;
1356
1357 int index = 0;
1358 while (true) {
1359
1360 String section = "preset." + itos(index);
1361 if (!config->has_section(section))
1362 break;
1363
1364 String platform = config->get_value(section, "platform");
1365
1366 Ref<EditorExportPreset> preset;
1367
1368 for (int i = 0; i < export_platforms.size(); i++) {
1369 if (export_platforms[i]->get_name() == platform) {
1370 preset = export_platforms.write[i]->create_preset();
1371 break;
1372 }
1373 }
1374
1375 if (!preset.is_valid()) {
1376 index++;
1377 ERR_CONTINUE(!preset.is_valid());
1378 }
1379
1380 preset->set_name(config->get_value(section, "name"));
1381 preset->set_runnable(config->get_value(section, "runnable"));
1382
1383 if (config->has_section_key(section, "custom_features")) {
1384 preset->set_custom_features(config->get_value(section, "custom_features"));
1385 }
1386
1387 String export_filter = config->get_value(section, "export_filter");
1388
1389 bool get_files = false;
1390
1391 if (export_filter == "all_resources") {
1392 preset->set_export_filter(EditorExportPreset::EXPORT_ALL_RESOURCES);
1393 } else if (export_filter == "scenes") {
1394 preset->set_export_filter(EditorExportPreset::EXPORT_SELECTED_SCENES);
1395 get_files = true;
1396 } else if (export_filter == "resources") {
1397 preset->set_export_filter(EditorExportPreset::EXPORT_SELECTED_RESOURCES);
1398 get_files = true;
1399 }
1400
1401 if (get_files) {
1402
1403 Vector<String> files = config->get_value(section, "export_files");
1404
1405 for (int i = 0; i < files.size(); i++) {
1406 if (!FileAccess::exists(files[i])) {
1407 preset->remove_export_file(files[i]);
1408 } else {
1409 preset->add_export_file(files[i]);
1410 }
1411 }
1412 }
1413
1414 preset->set_include_filter(config->get_value(section, "include_filter"));
1415 preset->set_exclude_filter(config->get_value(section, "exclude_filter"));
1416 preset->set_export_path(config->get_value(section, "export_path", ""));
1417
1418 Vector<String> patch_list = config->get_value(section, "patch_list");
1419
1420 for (int i = 0; i < patch_list.size(); i++) {
1421 preset->add_patch(patch_list[i]);
1422 }
1423
1424 if (config->has_section_key(section, "script_export_mode")) {
1425 preset->set_script_export_mode(config->get_value(section, "script_export_mode"));
1426 }
1427 if (config->has_section_key(section, "script_encryption_key")) {
1428 preset->set_script_encryption_key(config->get_value(section, "script_encryption_key"));
1429 }
1430
1431 String option_section = "preset." + itos(index) + ".options";
1432
1433 List<String> options;
1434
1435 config->get_section_keys(option_section, &options);
1436
1437 for (List<String>::Element *E = options.front(); E; E = E->next()) {
1438
1439 Variant value = config->get_value(option_section, E->get());
1440
1441 preset->set(E->get(), value);
1442 }
1443
1444 add_export_preset(preset);
1445 index++;
1446 }
1447
1448 block_save = false;
1449 }
1450
update_export_presets()1451 void EditorExport::update_export_presets() {
1452 Map<StringName, List<EditorExportPlatform::ExportOption> > platform_options;
1453
1454 for (int i = 0; i < export_platforms.size(); i++) {
1455 Ref<EditorExportPlatform> platform = export_platforms[i];
1456
1457 if (platform->should_update_export_options()) {
1458 List<EditorExportPlatform::ExportOption> options;
1459 platform->get_export_options(&options);
1460
1461 platform_options[platform->get_name()] = options;
1462 }
1463 }
1464
1465 bool export_presets_updated = false;
1466 for (int i = 0; i < export_presets.size(); i++) {
1467 Ref<EditorExportPreset> preset = export_presets[i];
1468 if (platform_options.has(preset->get_platform()->get_name())) {
1469 export_presets_updated = true;
1470
1471 List<EditorExportPlatform::ExportOption> options = platform_options[preset->get_platform()->get_name()];
1472
1473 // Copy the previous preset values
1474 Map<StringName, Variant> previous_values = preset->values;
1475
1476 // Clear the preset properties and values prior to reloading
1477 preset->properties.clear();
1478 preset->values.clear();
1479
1480 for (List<EditorExportPlatform::ExportOption>::Element *E = options.front(); E; E = E->next()) {
1481 preset->properties.push_back(E->get().option);
1482
1483 StringName option_name = E->get().option.name;
1484 preset->values[option_name] = previous_values.has(option_name) ? previous_values[option_name] : E->get().default_value;
1485 }
1486 }
1487 }
1488
1489 if (export_presets_updated) {
1490 emit_signal(_export_presets_updated);
1491 }
1492 }
1493
poll_export_platforms()1494 bool EditorExport::poll_export_platforms() {
1495
1496 bool changed = false;
1497 for (int i = 0; i < export_platforms.size(); i++) {
1498 if (export_platforms.write[i]->poll_export()) {
1499 changed = true;
1500 }
1501 }
1502
1503 return changed;
1504 }
1505
EditorExport()1506 EditorExport::EditorExport() {
1507
1508 save_timer = memnew(Timer);
1509 add_child(save_timer);
1510 save_timer->set_wait_time(0.8);
1511 save_timer->set_one_shot(true);
1512 save_timer->connect("timeout", this, "_save");
1513 block_save = false;
1514
1515 _export_presets_updated = "export_presets_updated";
1516
1517 singleton = this;
1518 set_process(true);
1519 }
1520
~EditorExport()1521 EditorExport::~EditorExport() {
1522 }
1523
1524 //////////
1525
get_preset_features(const Ref<EditorExportPreset> & p_preset,List<String> * r_features)1526 void EditorExportPlatformPC::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
1527
1528 if (p_preset->get("texture_format/s3tc")) {
1529 r_features->push_back("s3tc");
1530 }
1531 if (p_preset->get("texture_format/etc")) {
1532 r_features->push_back("etc");
1533 }
1534 if (p_preset->get("texture_format/etc2")) {
1535 r_features->push_back("etc2");
1536 }
1537
1538 if (p_preset->get("binary_format/64_bits")) {
1539 r_features->push_back("64");
1540 } else {
1541 r_features->push_back("32");
1542 }
1543 }
1544
get_export_options(List<ExportOption> * r_options)1545 void EditorExportPlatformPC::get_export_options(List<ExportOption> *r_options) {
1546
1547 r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/bptc"), false));
1548 r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
1549 r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false));
1550 r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false));
1551 r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/no_bptc_fallbacks"), true));
1552 r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "binary_format/64_bits"), true));
1553 r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "binary_format/embed_pck"), false));
1554 r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE), ""));
1555 r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE), ""));
1556 }
1557
get_name() const1558 String EditorExportPlatformPC::get_name() const {
1559
1560 return name;
1561 }
1562
get_os_name() const1563 String EditorExportPlatformPC::get_os_name() const {
1564
1565 return os_name;
1566 }
get_logo() const1567 Ref<Texture> EditorExportPlatformPC::get_logo() const {
1568
1569 return logo;
1570 }
1571
can_export(const Ref<EditorExportPreset> & p_preset,String & r_error,bool & r_missing_templates) const1572 bool EditorExportPlatformPC::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
1573
1574 String err;
1575 bool valid = false;
1576
1577 // Look for export templates (first official, and if defined custom templates).
1578
1579 bool use64 = p_preset->get("binary_format/64_bits");
1580 bool dvalid = exists_export_template(use64 ? debug_file_64 : debug_file_32, &err);
1581 bool rvalid = exists_export_template(use64 ? release_file_64 : release_file_32, &err);
1582
1583 if (p_preset->get("custom_template/debug") != "") {
1584 dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
1585 if (!dvalid) {
1586 err += TTR("Custom debug template not found.") + "\n";
1587 }
1588 }
1589 if (p_preset->get("custom_template/release") != "") {
1590 rvalid = FileAccess::exists(p_preset->get("custom_template/release"));
1591 if (!rvalid) {
1592 err += TTR("Custom release template not found.") + "\n";
1593 }
1594 }
1595
1596 valid = dvalid || rvalid;
1597 r_missing_templates = !valid;
1598
1599 if (!err.empty())
1600 r_error = err;
1601 return valid;
1602 }
1603
get_binary_extensions(const Ref<EditorExportPreset> & p_preset) const1604 List<String> EditorExportPlatformPC::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
1605 List<String> list;
1606 for (Map<String, String>::Element *E = extensions.front(); E; E = E->next()) {
1607 if (p_preset->get(E->key())) {
1608 list.push_back(extensions[E->key()]);
1609 return list;
1610 }
1611 }
1612
1613 if (extensions.has("default")) {
1614 list.push_back(extensions["default"]);
1615 return list;
1616 }
1617
1618 return list;
1619 }
1620
export_project(const Ref<EditorExportPreset> & p_preset,bool p_debug,const String & p_path,int p_flags)1621 Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
1622 ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
1623
1624 if (!DirAccess::exists(p_path.get_base_dir())) {
1625 return ERR_FILE_BAD_PATH;
1626 }
1627
1628 String custom_debug = p_preset->get("custom_template/debug");
1629 String custom_release = p_preset->get("custom_template/release");
1630
1631 String template_path = p_debug ? custom_debug : custom_release;
1632
1633 template_path = template_path.strip_edges();
1634
1635 if (template_path == String()) {
1636
1637 if (p_preset->get("binary_format/64_bits")) {
1638 if (p_debug) {
1639 template_path = find_export_template(debug_file_64);
1640 } else {
1641 template_path = find_export_template(release_file_64);
1642 }
1643 } else {
1644 if (p_debug) {
1645 template_path = find_export_template(debug_file_32);
1646 } else {
1647 template_path = find_export_template(release_file_32);
1648 }
1649 }
1650 }
1651
1652 if (template_path != String() && !FileAccess::exists(template_path)) {
1653 EditorNode::get_singleton()->show_warning(TTR("Template file not found:") + "\n" + template_path);
1654 return ERR_FILE_NOT_FOUND;
1655 }
1656
1657 DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1658 Error err = da->copy(template_path, p_path, get_chmod_flags());
1659 memdelete(da);
1660
1661 if (err == OK) {
1662 String pck_path;
1663 if (p_preset->get("binary_format/embed_pck")) {
1664 pck_path = p_path;
1665 } else {
1666 pck_path = p_path.get_basename() + ".pck";
1667 }
1668
1669 Vector<SharedObject> so_files;
1670
1671 int64_t embedded_pos;
1672 int64_t embedded_size;
1673 err = save_pack(p_preset, pck_path, &so_files, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size);
1674 if (err == OK && p_preset->get("binary_format/embed_pck")) {
1675
1676 if (embedded_size >= 0x100000000 && !p_preset->get("binary_format/64_bits")) {
1677 EditorNode::get_singleton()->show_warning(TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB."));
1678 return ERR_INVALID_PARAMETER;
1679 }
1680
1681 FixUpEmbeddedPckFunc fixup_func = get_fixup_embedded_pck_func();
1682 if (fixup_func) {
1683 err = fixup_func(p_path, embedded_pos, embedded_size);
1684 }
1685 }
1686
1687 if (err == OK && !so_files.empty()) {
1688 //if shared object files, copy them
1689 da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1690 for (int i = 0; i < so_files.size() && err == OK; i++) {
1691 err = da->copy(so_files[i].path, p_path.get_base_dir().plus_file(so_files[i].path.get_file()));
1692 if (err == OK) {
1693 err = sign_shared_object(p_preset, p_debug, p_path.get_base_dir().plus_file(so_files[i].path.get_file()));
1694 }
1695 }
1696 memdelete(da);
1697 }
1698 }
1699
1700 return err;
1701 }
1702
sign_shared_object(const Ref<EditorExportPreset> & p_preset,bool p_debug,const String & p_path)1703 Error EditorExportPlatformPC::sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
1704 return OK;
1705 }
1706
set_extension(const String & p_extension,const String & p_feature_key)1707 void EditorExportPlatformPC::set_extension(const String &p_extension, const String &p_feature_key) {
1708 extensions[p_feature_key] = p_extension;
1709 }
1710
set_name(const String & p_name)1711 void EditorExportPlatformPC::set_name(const String &p_name) {
1712 name = p_name;
1713 }
1714
set_os_name(const String & p_name)1715 void EditorExportPlatformPC::set_os_name(const String &p_name) {
1716 os_name = p_name;
1717 }
1718
set_logo(const Ref<Texture> & p_logo)1719 void EditorExportPlatformPC::set_logo(const Ref<Texture> &p_logo) {
1720 logo = p_logo;
1721 }
1722
set_release_64(const String & p_file)1723 void EditorExportPlatformPC::set_release_64(const String &p_file) {
1724
1725 release_file_64 = p_file;
1726 }
1727
set_release_32(const String & p_file)1728 void EditorExportPlatformPC::set_release_32(const String &p_file) {
1729
1730 release_file_32 = p_file;
1731 }
set_debug_64(const String & p_file)1732 void EditorExportPlatformPC::set_debug_64(const String &p_file) {
1733
1734 debug_file_64 = p_file;
1735 }
set_debug_32(const String & p_file)1736 void EditorExportPlatformPC::set_debug_32(const String &p_file) {
1737
1738 debug_file_32 = p_file;
1739 }
1740
add_platform_feature(const String & p_feature)1741 void EditorExportPlatformPC::add_platform_feature(const String &p_feature) {
1742
1743 extra_features.insert(p_feature);
1744 }
1745
get_platform_features(List<String> * r_features)1746 void EditorExportPlatformPC::get_platform_features(List<String> *r_features) {
1747 r_features->push_back("pc"); //all pcs support "pc"
1748 r_features->push_back("s3tc"); //all pcs support "s3tc" compression
1749 r_features->push_back(get_os_name()); //OS name is a feature
1750 for (Set<String>::Element *E = extra_features.front(); E; E = E->next()) {
1751 r_features->push_back(E->get());
1752 }
1753 }
1754
resolve_platform_feature_priorities(const Ref<EditorExportPreset> & p_preset,Set<String> & p_features)1755 void EditorExportPlatformPC::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
1756
1757 if (p_features.has("bptc")) {
1758 if (p_preset->has("texture_format/no_bptc_fallbacks")) {
1759 p_features.erase("s3tc");
1760 }
1761 }
1762 }
1763
get_chmod_flags() const1764 int EditorExportPlatformPC::get_chmod_flags() const {
1765
1766 return chmod_flags;
1767 }
1768
set_chmod_flags(int p_flags)1769 void EditorExportPlatformPC::set_chmod_flags(int p_flags) {
1770
1771 chmod_flags = p_flags;
1772 }
1773
get_fixup_embedded_pck_func() const1774 EditorExportPlatformPC::FixUpEmbeddedPckFunc EditorExportPlatformPC::get_fixup_embedded_pck_func() const {
1775
1776 return fixup_embedded_pck_func;
1777 }
1778
set_fixup_embedded_pck_func(FixUpEmbeddedPckFunc p_fixup_embedded_pck_func)1779 void EditorExportPlatformPC::set_fixup_embedded_pck_func(FixUpEmbeddedPckFunc p_fixup_embedded_pck_func) {
1780
1781 fixup_embedded_pck_func = p_fixup_embedded_pck_func;
1782 }
1783
EditorExportPlatformPC()1784 EditorExportPlatformPC::EditorExportPlatformPC() {
1785
1786 chmod_flags = -1;
1787 fixup_embedded_pck_func = NULL;
1788 }
1789
1790 ///////////////////////
1791
_export_file(const String & p_path,const String & p_type,const Set<String> & p_features)1792 void EditorExportTextSceneToBinaryPlugin::_export_file(const String &p_path, const String &p_type, const Set<String> &p_features) {
1793
1794 String extension = p_path.get_extension().to_lower();
1795 if (extension != "tres" && extension != "tscn") {
1796 return;
1797 }
1798
1799 bool convert = GLOBAL_GET("editor/convert_text_resources_to_binary_on_export");
1800 if (!convert)
1801 return;
1802 String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpfile.res");
1803 Error err = ResourceFormatLoaderText::convert_file_to_binary(p_path, tmp_path);
1804 if (err != OK) {
1805 DirAccess::remove_file_or_error(tmp_path);
1806 ERR_FAIL();
1807 }
1808 Vector<uint8_t> data = FileAccess::get_file_as_array(tmp_path);
1809 if (data.size() == 0) {
1810 DirAccess::remove_file_or_error(tmp_path);
1811 ERR_FAIL();
1812 }
1813 DirAccess::remove_file_or_error(tmp_path);
1814 add_file(p_path + ".converted.res", data, true);
1815 }
1816
EditorExportTextSceneToBinaryPlugin()1817 EditorExportTextSceneToBinaryPlugin::EditorExportTextSceneToBinaryPlugin() {
1818
1819 GLOBAL_DEF("editor/convert_text_resources_to_binary_on_export", false);
1820 }
1821