1 | /**************************************************************************/ |
2 | /* editor_export.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 "editor_export.h" |
32 | |
33 | #include "core/config/project_settings.h" |
34 | #include "core/io/config_file.h" |
35 | |
36 | EditorExport *EditorExport::singleton = nullptr; |
37 | |
38 | void EditorExport::_save() { |
39 | Ref<ConfigFile> config; |
40 | Ref<ConfigFile> credentials; |
41 | config.instantiate(); |
42 | credentials.instantiate(); |
43 | for (int i = 0; i < export_presets.size(); i++) { |
44 | Ref<EditorExportPreset> preset = export_presets[i]; |
45 | String section = "preset." + itos(i); |
46 | |
47 | config->set_value(section, "name" , preset->get_name()); |
48 | config->set_value(section, "platform" , preset->get_platform()->get_name()); |
49 | config->set_value(section, "runnable" , preset->is_runnable()); |
50 | config->set_value(section, "dedicated_server" , preset->is_dedicated_server()); |
51 | config->set_value(section, "custom_features" , preset->get_custom_features()); |
52 | |
53 | bool save_files = false; |
54 | switch (preset->get_export_filter()) { |
55 | case EditorExportPreset::EXPORT_ALL_RESOURCES: { |
56 | config->set_value(section, "export_filter" , "all_resources" ); |
57 | } break; |
58 | case EditorExportPreset::EXPORT_SELECTED_SCENES: { |
59 | config->set_value(section, "export_filter" , "scenes" ); |
60 | save_files = true; |
61 | } break; |
62 | case EditorExportPreset::EXPORT_SELECTED_RESOURCES: { |
63 | config->set_value(section, "export_filter" , "resources" ); |
64 | save_files = true; |
65 | } break; |
66 | case EditorExportPreset::EXCLUDE_SELECTED_RESOURCES: { |
67 | config->set_value(section, "export_filter" , "exclude" ); |
68 | save_files = true; |
69 | } break; |
70 | case EditorExportPreset::EXPORT_CUSTOMIZED: { |
71 | config->set_value(section, "export_filter" , "customized" ); |
72 | config->set_value(section, "customized_files" , preset->get_customized_files()); |
73 | save_files = false; |
74 | }; |
75 | } |
76 | |
77 | if (save_files) { |
78 | Vector<String> export_files = preset->get_files_to_export(); |
79 | config->set_value(section, "export_files" , export_files); |
80 | } |
81 | config->set_value(section, "include_filter" , preset->get_include_filter()); |
82 | config->set_value(section, "exclude_filter" , preset->get_exclude_filter()); |
83 | config->set_value(section, "export_path" , preset->get_export_path()); |
84 | config->set_value(section, "encryption_include_filters" , preset->get_enc_in_filter()); |
85 | config->set_value(section, "encryption_exclude_filters" , preset->get_enc_ex_filter()); |
86 | config->set_value(section, "encrypt_pck" , preset->get_enc_pck()); |
87 | config->set_value(section, "encrypt_directory" , preset->get_enc_directory()); |
88 | credentials->set_value(section, "script_encryption_key" , preset->get_script_encryption_key()); |
89 | |
90 | String option_section = "preset." + itos(i) + ".options" ; |
91 | |
92 | for (const KeyValue<StringName, Variant> &E : preset->values) { |
93 | PropertyInfo *prop = preset->properties.getptr(E.key); |
94 | if (prop && prop->usage & PROPERTY_USAGE_SECRET) { |
95 | credentials->set_value(option_section, E.key, E.value); |
96 | } else { |
97 | config->set_value(option_section, E.key, E.value); |
98 | } |
99 | } |
100 | } |
101 | |
102 | config->save("res://export_presets.cfg" ); |
103 | credentials->save("res://.godot/export_credentials.cfg" ); |
104 | } |
105 | |
106 | void EditorExport::save_presets() { |
107 | if (block_save) { |
108 | return; |
109 | } |
110 | save_timer->start(); |
111 | } |
112 | |
113 | void EditorExport::_bind_methods() { |
114 | ADD_SIGNAL(MethodInfo("export_presets_updated" )); |
115 | } |
116 | |
117 | void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) { |
118 | export_platforms.push_back(p_platform); |
119 | should_update_presets = true; |
120 | } |
121 | |
122 | int EditorExport::get_export_platform_count() { |
123 | return export_platforms.size(); |
124 | } |
125 | |
126 | Ref<EditorExportPlatform> EditorExport::get_export_platform(int p_idx) { |
127 | ERR_FAIL_INDEX_V(p_idx, export_platforms.size(), Ref<EditorExportPlatform>()); |
128 | |
129 | return export_platforms[p_idx]; |
130 | } |
131 | |
132 | void EditorExport::add_export_preset(const Ref<EditorExportPreset> &p_preset, int p_at_pos) { |
133 | if (p_at_pos < 0) { |
134 | export_presets.push_back(p_preset); |
135 | } else { |
136 | export_presets.insert(p_at_pos, p_preset); |
137 | } |
138 | } |
139 | |
140 | int EditorExport::get_export_preset_count() const { |
141 | return export_presets.size(); |
142 | } |
143 | |
144 | Ref<EditorExportPreset> EditorExport::get_export_preset(int p_idx) { |
145 | ERR_FAIL_INDEX_V(p_idx, export_presets.size(), Ref<EditorExportPreset>()); |
146 | return export_presets[p_idx]; |
147 | } |
148 | |
149 | void EditorExport::remove_export_preset(int p_idx) { |
150 | export_presets.remove_at(p_idx); |
151 | save_presets(); |
152 | } |
153 | |
154 | void EditorExport::add_export_plugin(const Ref<EditorExportPlugin> &p_plugin) { |
155 | if (!export_plugins.has(p_plugin)) { |
156 | export_plugins.push_back(p_plugin); |
157 | should_update_presets = true; |
158 | } |
159 | } |
160 | |
161 | void EditorExport::remove_export_plugin(const Ref<EditorExportPlugin> &p_plugin) { |
162 | export_plugins.erase(p_plugin); |
163 | should_update_presets = true; |
164 | } |
165 | |
166 | Vector<Ref<EditorExportPlugin>> EditorExport::get_export_plugins() { |
167 | return export_plugins; |
168 | } |
169 | |
170 | void EditorExport::_notification(int p_what) { |
171 | switch (p_what) { |
172 | case NOTIFICATION_ENTER_TREE: { |
173 | load_config(); |
174 | } break; |
175 | |
176 | case NOTIFICATION_PROCESS: { |
177 | update_export_presets(); |
178 | } break; |
179 | |
180 | case NOTIFICATION_EXIT_TREE: { |
181 | for (int i = 0; i < export_platforms.size(); i++) { |
182 | export_platforms.write[i]->cleanup(); |
183 | } |
184 | } break; |
185 | } |
186 | } |
187 | |
188 | void EditorExport::load_config() { |
189 | Ref<ConfigFile> config; |
190 | config.instantiate(); |
191 | Error err = config->load("res://export_presets.cfg" ); |
192 | if (err != OK) { |
193 | return; |
194 | } |
195 | |
196 | Ref<ConfigFile> credentials; |
197 | credentials.instantiate(); |
198 | err = credentials->load("res://.godot/export_credentials.cfg" ); |
199 | if (!(err == OK || err == ERR_FILE_NOT_FOUND)) { |
200 | return; |
201 | } |
202 | |
203 | block_save = true; |
204 | |
205 | int index = 0; |
206 | while (true) { |
207 | String section = "preset." + itos(index); |
208 | if (!config->has_section(section)) { |
209 | break; |
210 | } |
211 | |
212 | String platform = config->get_value(section, "platform" ); |
213 | |
214 | Ref<EditorExportPreset> preset; |
215 | |
216 | for (int i = 0; i < export_platforms.size(); i++) { |
217 | if (export_platforms[i]->get_name() == platform) { |
218 | preset = export_platforms.write[i]->create_preset(); |
219 | break; |
220 | } |
221 | } |
222 | |
223 | if (!preset.is_valid()) { |
224 | index++; |
225 | ERR_CONTINUE(!preset.is_valid()); |
226 | } |
227 | |
228 | preset->set_name(config->get_value(section, "name" )); |
229 | preset->set_runnable(config->get_value(section, "runnable" )); |
230 | preset->set_dedicated_server(config->get_value(section, "dedicated_server" , false)); |
231 | |
232 | if (config->has_section_key(section, "custom_features" )) { |
233 | preset->set_custom_features(config->get_value(section, "custom_features" )); |
234 | } |
235 | |
236 | String export_filter = config->get_value(section, "export_filter" ); |
237 | |
238 | bool get_files = false; |
239 | |
240 | if (export_filter == "all_resources" ) { |
241 | preset->set_export_filter(EditorExportPreset::EXPORT_ALL_RESOURCES); |
242 | } else if (export_filter == "scenes" ) { |
243 | preset->set_export_filter(EditorExportPreset::EXPORT_SELECTED_SCENES); |
244 | get_files = true; |
245 | } else if (export_filter == "resources" ) { |
246 | preset->set_export_filter(EditorExportPreset::EXPORT_SELECTED_RESOURCES); |
247 | get_files = true; |
248 | } else if (export_filter == "exclude" ) { |
249 | preset->set_export_filter(EditorExportPreset::EXCLUDE_SELECTED_RESOURCES); |
250 | get_files = true; |
251 | } else if (export_filter == "customized" ) { |
252 | preset->set_export_filter(EditorExportPreset::EXPORT_CUSTOMIZED); |
253 | preset->set_customized_files(config->get_value(section, "customized_files" , Dictionary())); |
254 | get_files = false; |
255 | } |
256 | |
257 | if (get_files) { |
258 | Vector<String> files = config->get_value(section, "export_files" ); |
259 | |
260 | for (int i = 0; i < files.size(); i++) { |
261 | if (!FileAccess::exists(files[i])) { |
262 | preset->remove_export_file(files[i]); |
263 | } else { |
264 | preset->add_export_file(files[i]); |
265 | } |
266 | } |
267 | } |
268 | |
269 | preset->set_include_filter(config->get_value(section, "include_filter" )); |
270 | preset->set_exclude_filter(config->get_value(section, "exclude_filter" )); |
271 | preset->set_export_path(config->get_value(section, "export_path" , "" )); |
272 | |
273 | if (config->has_section_key(section, "encrypt_pck" )) { |
274 | preset->set_enc_pck(config->get_value(section, "encrypt_pck" )); |
275 | } |
276 | if (config->has_section_key(section, "encrypt_directory" )) { |
277 | preset->set_enc_directory(config->get_value(section, "encrypt_directory" )); |
278 | } |
279 | if (config->has_section_key(section, "encryption_include_filters" )) { |
280 | preset->set_enc_in_filter(config->get_value(section, "encryption_include_filters" )); |
281 | } |
282 | if (config->has_section_key(section, "encryption_exclude_filters" )) { |
283 | preset->set_enc_ex_filter(config->get_value(section, "encryption_exclude_filters" )); |
284 | } |
285 | if (credentials->has_section_key(section, "script_encryption_key" )) { |
286 | preset->set_script_encryption_key(credentials->get_value(section, "script_encryption_key" )); |
287 | } |
288 | |
289 | String option_section = "preset." + itos(index) + ".options" ; |
290 | |
291 | List<String> options; |
292 | config->get_section_keys(option_section, &options); |
293 | |
294 | for (const String &E : options) { |
295 | Variant value = config->get_value(option_section, E); |
296 | preset->set(E, value); |
297 | } |
298 | |
299 | if (credentials->has_section(option_section)) { |
300 | options.clear(); |
301 | credentials->get_section_keys(option_section, &options); |
302 | |
303 | for (const String &E : options) { |
304 | // Drop values for secret properties that no longer exist, or during the next save they would end up in the regular config file. |
305 | if (preset->get_properties().has(E)) { |
306 | Variant value = credentials->get_value(option_section, E); |
307 | preset->set(E, value); |
308 | } |
309 | } |
310 | } |
311 | |
312 | add_export_preset(preset); |
313 | index++; |
314 | } |
315 | |
316 | block_save = false; |
317 | } |
318 | |
319 | void EditorExport::update_export_presets() { |
320 | HashMap<StringName, List<EditorExportPlatform::ExportOption>> platform_options; |
321 | |
322 | for (int i = 0; i < export_platforms.size(); i++) { |
323 | Ref<EditorExportPlatform> platform = export_platforms[i]; |
324 | |
325 | bool should_update = should_update_presets; |
326 | should_update |= platform->should_update_export_options(); |
327 | for (int j = 0; j < export_plugins.size(); j++) { |
328 | should_update |= export_plugins.write[j]->_should_update_export_options(platform); |
329 | } |
330 | |
331 | if (should_update) { |
332 | List<EditorExportPlatform::ExportOption> options; |
333 | platform->get_export_options(&options); |
334 | |
335 | for (int j = 0; j < export_plugins.size(); j++) { |
336 | export_plugins[j]->_get_export_options(platform, &options); |
337 | } |
338 | |
339 | platform_options[platform->get_name()] = options; |
340 | } |
341 | } |
342 | should_update_presets = false; |
343 | |
344 | bool export_presets_updated = false; |
345 | for (int i = 0; i < export_presets.size(); i++) { |
346 | Ref<EditorExportPreset> preset = export_presets[i]; |
347 | if (platform_options.has(preset->get_platform()->get_name())) { |
348 | export_presets_updated = true; |
349 | |
350 | List<EditorExportPlatform::ExportOption> options = platform_options[preset->get_platform()->get_name()]; |
351 | |
352 | // Clear the preset properties prior to reloading, keep the values to preserve options from plugins that may be currently disabled. |
353 | preset->properties.clear(); |
354 | preset->update_visibility.clear(); |
355 | |
356 | for (const EditorExportPlatform::ExportOption &E : options) { |
357 | StringName option_name = E.option.name; |
358 | preset->properties[option_name] = E.option; |
359 | if (!preset->has(option_name)) { |
360 | preset->values[option_name] = E.default_value; |
361 | } |
362 | preset->update_visibility[option_name] = E.update_visibility; |
363 | } |
364 | } |
365 | } |
366 | |
367 | if (export_presets_updated) { |
368 | emit_signal(_export_presets_updated); |
369 | } |
370 | } |
371 | |
372 | bool EditorExport::poll_export_platforms() { |
373 | bool changed = false; |
374 | for (int i = 0; i < export_platforms.size(); i++) { |
375 | if (export_platforms.write[i]->poll_export()) { |
376 | changed = true; |
377 | } |
378 | } |
379 | |
380 | return changed; |
381 | } |
382 | |
383 | EditorExport::EditorExport() { |
384 | save_timer = memnew(Timer); |
385 | add_child(save_timer); |
386 | save_timer->set_wait_time(0.8); |
387 | save_timer->set_one_shot(true); |
388 | save_timer->connect("timeout" , callable_mp(this, &EditorExport::_save)); |
389 | |
390 | _export_presets_updated = "export_presets_updated" ; |
391 | |
392 | singleton = this; |
393 | set_process(true); |
394 | } |
395 | |
396 | EditorExport::~EditorExport() { |
397 | } |
398 | |