1 | /**************************************************************************/ |
2 | /* export_plugin.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 "export_plugin.h" |
32 | |
33 | #include "logo_svg.gen.h" |
34 | #include "run_icon_svg.gen.h" |
35 | |
36 | #include "core/config/project_settings.h" |
37 | #include "core/io/image_loader.h" |
38 | #include "editor/editor_node.h" |
39 | #include "editor/editor_paths.h" |
40 | #include "editor/editor_scale.h" |
41 | #include "editor/editor_string_names.h" |
42 | #include "editor/export/editor_export.h" |
43 | |
44 | #include "modules/modules_enabled.gen.h" // For svg. |
45 | #ifdef MODULE_SVG_ENABLED |
46 | #include "modules/svg/image_loader_svg.h" |
47 | #endif |
48 | |
49 | Error EditorExportPlatformWindows::_process_icon(const Ref<EditorExportPreset> &p_preset, const String &p_src_path, const String &p_dst_path) { |
50 | static const uint8_t icon_size[] = { 16, 32, 48, 64, 128, 0 /*256*/ }; |
51 | |
52 | struct IconData { |
53 | Vector<uint8_t> data; |
54 | uint8_t pal_colors = 0; |
55 | uint16_t planes = 0; |
56 | uint16_t bpp = 32; |
57 | }; |
58 | |
59 | HashMap<uint8_t, IconData> images; |
60 | Error err; |
61 | |
62 | if (p_src_path.get_extension() == "ico" ) { |
63 | Ref<FileAccess> f = FileAccess::open(p_src_path, FileAccess::READ, &err); |
64 | if (err != OK) { |
65 | return err; |
66 | } |
67 | |
68 | // Read ICONDIR. |
69 | f->get_16(); // Reserved. |
70 | uint16_t icon_type = f->get_16(); // Image type: 1 - ICO. |
71 | uint16_t icon_count = f->get_16(); // Number of images. |
72 | ERR_FAIL_COND_V(icon_type != 1, ERR_CANT_OPEN); |
73 | |
74 | for (uint16_t i = 0; i < icon_count; i++) { |
75 | // Read ICONDIRENTRY. |
76 | uint16_t w = f->get_8(); // Width in pixels. |
77 | uint16_t h = f->get_8(); // Height in pixels. |
78 | uint8_t pal_colors = f->get_8(); // Number of colors in the palette (0 - no palette). |
79 | f->get_8(); // Reserved. |
80 | uint16_t planes = f->get_16(); // Number of color planes. |
81 | uint16_t bpp = f->get_16(); // Bits per pixel. |
82 | uint32_t img_size = f->get_32(); // Image data size in bytes. |
83 | uint32_t img_offset = f->get_32(); // Image data offset. |
84 | if (w != h) { |
85 | continue; |
86 | } |
87 | |
88 | // Read image data. |
89 | uint64_t prev_offset = f->get_position(); |
90 | images[w].pal_colors = pal_colors; |
91 | images[w].planes = planes; |
92 | images[w].bpp = bpp; |
93 | images[w].data.resize(img_size); |
94 | f->seek(img_offset); |
95 | f->get_buffer(images[w].data.ptrw(), img_size); |
96 | f->seek(prev_offset); |
97 | } |
98 | } else { |
99 | Ref<Image> src_image; |
100 | src_image.instantiate(); |
101 | err = ImageLoader::load_image(p_src_path, src_image); |
102 | ERR_FAIL_COND_V(err != OK || src_image->is_empty(), ERR_CANT_OPEN); |
103 | for (size_t i = 0; i < sizeof(icon_size) / sizeof(icon_size[0]); ++i) { |
104 | int size = (icon_size[i] == 0) ? 256 : icon_size[i]; |
105 | |
106 | Ref<Image> res_image = src_image->duplicate(); |
107 | ERR_FAIL_COND_V(res_image.is_null() || res_image->is_empty(), ERR_CANT_OPEN); |
108 | res_image->resize(size, size, (Image::Interpolation)(p_preset->get("application/icon_interpolation" ).operator int())); |
109 | images[icon_size[i]].data = res_image->save_png_to_buffer(); |
110 | } |
111 | } |
112 | |
113 | uint16_t valid_icon_count = 0; |
114 | for (size_t i = 0; i < sizeof(icon_size) / sizeof(icon_size[0]); ++i) { |
115 | if (images.has(icon_size[i])) { |
116 | valid_icon_count++; |
117 | } else { |
118 | int size = (icon_size[i] == 0) ? 256 : icon_size[i]; |
119 | add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification" ), vformat(TTR("Icon size \"%d\" is missing." ), size)); |
120 | } |
121 | } |
122 | ERR_FAIL_COND_V(valid_icon_count == 0, ERR_CANT_OPEN); |
123 | |
124 | Ref<FileAccess> fw = FileAccess::open(p_dst_path, FileAccess::WRITE, &err); |
125 | if (err != OK) { |
126 | return err; |
127 | } |
128 | |
129 | // Write ICONDIR. |
130 | fw->store_16(0); // Reserved. |
131 | fw->store_16(1); // Image type: 1 - ICO. |
132 | fw->store_16(valid_icon_count); // Number of images. |
133 | |
134 | // Write ICONDIRENTRY. |
135 | uint32_t img_offset = 6 + 16 * valid_icon_count; |
136 | for (size_t i = 0; i < sizeof(icon_size) / sizeof(icon_size[0]); ++i) { |
137 | if (images.has(icon_size[i])) { |
138 | const IconData &di = images[icon_size[i]]; |
139 | fw->store_8(icon_size[i]); // Width in pixels. |
140 | fw->store_8(icon_size[i]); // Height in pixels. |
141 | fw->store_8(di.pal_colors); // Number of colors in the palette (0 - no palette). |
142 | fw->store_8(0); // Reserved. |
143 | fw->store_16(di.planes); // Number of color planes. |
144 | fw->store_16(di.bpp); // Bits per pixel. |
145 | fw->store_32(di.data.size()); // Image data size in bytes. |
146 | fw->store_32(img_offset); // Image data offset. |
147 | |
148 | img_offset += di.data.size(); |
149 | } |
150 | } |
151 | |
152 | // Write image data. |
153 | for (size_t i = 0; i < sizeof(icon_size) / sizeof(icon_size[0]); ++i) { |
154 | if (images.has(icon_size[i])) { |
155 | const IconData &di = images[icon_size[i]]; |
156 | fw->store_buffer(di.data.ptr(), di.data.size()); |
157 | } |
158 | } |
159 | return OK; |
160 | } |
161 | |
162 | Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { |
163 | if (p_preset->get("codesign/enable" )) { |
164 | return _code_sign(p_preset, p_path); |
165 | } else { |
166 | return OK; |
167 | } |
168 | } |
169 | |
170 | Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { |
171 | if (p_preset->get("application/modify_resources" )) { |
172 | _rcedit_add_data(p_preset, p_path, false); |
173 | String wrapper_path = p_path.get_basename() + ".console.exe" ; |
174 | if (FileAccess::exists(wrapper_path)) { |
175 | _rcedit_add_data(p_preset, wrapper_path, true); |
176 | } |
177 | } |
178 | return OK; |
179 | } |
180 | |
181 | Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { |
182 | bool export_as_zip = p_path.ends_with("zip" ); |
183 | bool embedded = p_preset->get("binary_format/embed_pck" ); |
184 | |
185 | String pkg_name; |
186 | if (String(ProjectSettings::get_singleton()->get("application/config/name" )) != "" ) { |
187 | pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name" )); |
188 | } else { |
189 | pkg_name = "Unnamed" ; |
190 | } |
191 | |
192 | pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); |
193 | |
194 | // Setup temp folder. |
195 | String path = p_path; |
196 | String tmp_dir_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name); |
197 | Ref<DirAccess> tmp_app_dir = DirAccess::create_for_path(tmp_dir_path); |
198 | if (export_as_zip) { |
199 | if (tmp_app_dir.is_null()) { |
200 | return ERR_CANT_CREATE; |
201 | } |
202 | if (DirAccess::exists(tmp_dir_path)) { |
203 | if (tmp_app_dir->change_dir(tmp_dir_path) == OK) { |
204 | tmp_app_dir->erase_contents_recursive(); |
205 | } |
206 | } |
207 | tmp_app_dir->make_dir_recursive(tmp_dir_path); |
208 | path = tmp_dir_path.path_join(p_path.get_file().get_basename() + ".exe" ); |
209 | } |
210 | |
211 | // Export project. |
212 | String pck_path = path; |
213 | if (embedded) { |
214 | pck_path = pck_path.get_basename() + ".tmp" ; |
215 | } |
216 | Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, pck_path, p_flags); |
217 | if (p_preset->get("codesign/enable" ) && err == OK) { |
218 | _code_sign(p_preset, pck_path); |
219 | String wrapper_path = p_path.get_basename() + ".console.exe" ; |
220 | if (FileAccess::exists(wrapper_path)) { |
221 | _code_sign(p_preset, wrapper_path); |
222 | } |
223 | } |
224 | |
225 | if (embedded && err == OK) { |
226 | Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); |
227 | err = tmp_dir->rename(pck_path, p_path); |
228 | if (err != OK) { |
229 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), vformat(TTR("Failed to rename temporary file \"%s\"." ), pck_path)); |
230 | } |
231 | } |
232 | |
233 | // ZIP project. |
234 | if (export_as_zip) { |
235 | if (FileAccess::exists(p_path)) { |
236 | OS::get_singleton()->move_to_trash(p_path); |
237 | } |
238 | |
239 | Ref<FileAccess> io_fa_dst; |
240 | zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst); |
241 | zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); |
242 | |
243 | zip_folder_recursive(zip, tmp_dir_path, "" , pkg_name); |
244 | |
245 | zipClose(zip, nullptr); |
246 | |
247 | if (tmp_app_dir->change_dir(tmp_dir_path) == OK) { |
248 | tmp_app_dir->erase_contents_recursive(); |
249 | tmp_app_dir->change_dir(".." ); |
250 | tmp_app_dir->remove(pkg_name); |
251 | } |
252 | } |
253 | |
254 | return err; |
255 | } |
256 | |
257 | String EditorExportPlatformWindows::get_template_file_name(const String &p_target, const String &p_arch) const { |
258 | return "windows_" + p_target + "_" + p_arch + ".exe" ; |
259 | } |
260 | |
261 | List<String> EditorExportPlatformWindows::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { |
262 | List<String> list; |
263 | list.push_back("exe" ); |
264 | list.push_back("zip" ); |
265 | return list; |
266 | } |
267 | |
268 | String EditorExportPlatformWindows::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { |
269 | if (p_preset) { |
270 | if (p_name == "application/icon" ) { |
271 | String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon" )); |
272 | if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) { |
273 | return TTR("Invalid icon path." ); |
274 | } |
275 | } else if (p_name == "application/file_version" ) { |
276 | String file_version = p_preset->get("application/file_version" ); |
277 | if (!file_version.is_empty()) { |
278 | PackedStringArray version_array = file_version.split("." , false); |
279 | if (version_array.size() != 4 || !version_array[0].is_valid_int() || |
280 | !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || |
281 | !version_array[3].is_valid_int() || file_version.find("-" ) > -1) { |
282 | return TTR("Invalid file version." ); |
283 | } |
284 | } |
285 | } else if (p_name == "application/product_version" ) { |
286 | String product_version = p_preset->get("application/product_version" ); |
287 | if (!product_version.is_empty()) { |
288 | PackedStringArray version_array = product_version.split("." , false); |
289 | if (version_array.size() != 4 || !version_array[0].is_valid_int() || |
290 | !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || |
291 | !version_array[3].is_valid_int() || product_version.find("-" ) > -1) { |
292 | return TTR("Invalid product version." ); |
293 | } |
294 | } |
295 | } |
296 | } |
297 | return EditorExportPlatformPC::get_export_option_warning(p_preset, p_name); |
298 | } |
299 | |
300 | bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { |
301 | // This option is not supported by "osslsigncode", used on non-Windows host. |
302 | if (!OS::get_singleton()->has_feature("windows" ) && p_option == "codesign/identity_type" ) { |
303 | return false; |
304 | } |
305 | |
306 | // Hide codesign. |
307 | bool codesign = p_preset->get("codesign/enable" ); |
308 | if (!codesign && p_option != "codesign/enable" && p_option.begins_with("codesign/" )) { |
309 | return false; |
310 | } |
311 | |
312 | // Hide resources. |
313 | bool mod_res = p_preset->get("application/modify_resources" ); |
314 | if (!mod_res && p_option != "application/modify_resources" && p_option.begins_with("application/" )) { |
315 | return false; |
316 | } |
317 | |
318 | // Hide SSH options. |
319 | bool ssh = p_preset->get("ssh_remote_deploy/enabled" ); |
320 | if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/" )) { |
321 | return false; |
322 | } |
323 | |
324 | return true; |
325 | } |
326 | |
327 | void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) const { |
328 | EditorExportPlatformPC::get_export_options(r_options); |
329 | |
330 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture" , PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64" ), "x86_64" )); |
331 | |
332 | r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable" ), false, true)); |
333 | r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type" , PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA-1 hash)" , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), 0)); |
334 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity" , PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12" , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "" )); |
335 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password" , PROPERTY_HINT_PASSWORD, "" , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "" )); |
336 | r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp" ), true)); |
337 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/timestamp_server_url" ), "" )); |
338 | r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/digest_algorithm" , PROPERTY_HINT_ENUM, "SHA1,SHA256" ), 1)); |
339 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/description" ), "" )); |
340 | r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options" ), PackedStringArray())); |
341 | |
342 | r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/modify_resources" ), true, true)); |
343 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon" , PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg" ), "" , false, true)); |
344 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/console_wrapper_icon" , PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg" ), "" )); |
345 | r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation" , PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos" ), 4)); |
346 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version" , PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version" ), "" )); |
347 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version" , PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version" ), "" )); |
348 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name" , PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name" ), "" )); |
349 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name" , PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name" ), "" )); |
350 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description" ), "" )); |
351 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright" ), "" )); |
352 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/trademarks" ), "" )); |
353 | |
354 | String run_script = "Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'\n" |
355 | "$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'\n" |
356 | "$trigger = New-ScheduledTaskTrigger -Once -At 00:00\n" |
357 | "$settings = New-ScheduledTaskSettingsSet\n" |
358 | "$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings\n" |
359 | "Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true\n" |
360 | "Start-ScheduledTask -TaskName godot_remote_debug\n" |
361 | "while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }\n" |
362 | "Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" ; |
363 | |
364 | String cleanup_script = "Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue\n" |
365 | "Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue\n" |
366 | "Remove-Item -Recurse -Force '{temp_dir}'" ; |
367 | |
368 | r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled" ), false, true)); |
369 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host" ), "user@host_ip" )); |
370 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port" ), "22" )); |
371 | |
372 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_ssh" , PROPERTY_HINT_MULTILINE_TEXT), "" )); |
373 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_scp" , PROPERTY_HINT_MULTILINE_TEXT), "" )); |
374 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/run_script" , PROPERTY_HINT_MULTILINE_TEXT), run_script)); |
375 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/cleanup_script" , PROPERTY_HINT_MULTILINE_TEXT), cleanup_script)); |
376 | } |
377 | |
378 | Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path, bool p_console_icon) { |
379 | String rcedit_path = EDITOR_GET("export/windows/rcedit" ); |
380 | |
381 | if (rcedit_path != String() && !FileAccess::exists(rcedit_path)) { |
382 | add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification" ), vformat(TTR("Could not find rcedit executable at \"%s\"." ), rcedit_path)); |
383 | return ERR_FILE_NOT_FOUND; |
384 | } |
385 | |
386 | if (rcedit_path == String()) { |
387 | rcedit_path = "rcedit" ; // try to run rcedit from PATH |
388 | } |
389 | |
390 | #ifndef WINDOWS_ENABLED |
391 | // On non-Windows we need WINE to run rcedit |
392 | String wine_path = EDITOR_GET("export/windows/wine" ); |
393 | |
394 | if (!wine_path.is_empty() && !FileAccess::exists(wine_path)) { |
395 | add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification" ), vformat(TTR("Could not find wine executable at \"%s\"." ), wine_path)); |
396 | return ERR_FILE_NOT_FOUND; |
397 | } |
398 | |
399 | if (wine_path.is_empty()) { |
400 | wine_path = "wine" ; // try to run wine from PATH |
401 | } |
402 | #endif |
403 | |
404 | String icon_path; |
405 | if (p_preset->get("application/icon" ) != "" ) { |
406 | icon_path = p_preset->get("application/icon" ); |
407 | } else if (GLOBAL_GET("application/config/windows_native_icon" ) != "" ) { |
408 | icon_path = GLOBAL_GET("application/config/windows_native_icon" ); |
409 | } else { |
410 | icon_path = GLOBAL_GET("application/config/icon" ); |
411 | } |
412 | icon_path = ProjectSettings::get_singleton()->globalize_path(icon_path); |
413 | |
414 | if (p_console_icon) { |
415 | String console_icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/console_wrapper_icon" )); |
416 | if (!console_icon_path.is_empty() && FileAccess::exists(console_icon_path)) { |
417 | icon_path = console_icon_path; |
418 | } |
419 | } |
420 | |
421 | String tmp_icon_path = EditorPaths::get_singleton()->get_cache_dir().path_join("_rcedit.ico" ); |
422 | if (!icon_path.is_empty()) { |
423 | if (_process_icon(p_preset, icon_path, tmp_icon_path) != OK) { |
424 | add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification" ), vformat(TTR("Invalid icon file \"%s\"." ), icon_path)); |
425 | icon_path = String(); |
426 | } |
427 | } |
428 | |
429 | String file_verion = p_preset->get_version("application/file_version" , true); |
430 | String product_version = p_preset->get_version("application/product_version" , true); |
431 | String company_name = p_preset->get("application/company_name" ); |
432 | String product_name = p_preset->get("application/product_name" ); |
433 | String file_description = p_preset->get("application/file_description" ); |
434 | String copyright = p_preset->get("application/copyright" ); |
435 | String trademarks = p_preset->get("application/trademarks" ); |
436 | String = p_preset->get("application/comments" ); |
437 | |
438 | List<String> args; |
439 | args.push_back(p_path); |
440 | if (!icon_path.is_empty()) { |
441 | args.push_back("--set-icon" ); |
442 | args.push_back(tmp_icon_path); |
443 | } |
444 | if (!file_verion.is_empty()) { |
445 | args.push_back("--set-file-version" ); |
446 | args.push_back(file_verion); |
447 | } |
448 | if (!product_version.is_empty()) { |
449 | args.push_back("--set-product-version" ); |
450 | args.push_back(product_version); |
451 | } |
452 | if (!company_name.is_empty()) { |
453 | args.push_back("--set-version-string" ); |
454 | args.push_back("CompanyName" ); |
455 | args.push_back(company_name); |
456 | } |
457 | if (!product_name.is_empty()) { |
458 | args.push_back("--set-version-string" ); |
459 | args.push_back("ProductName" ); |
460 | args.push_back(product_name); |
461 | } |
462 | if (!file_description.is_empty()) { |
463 | args.push_back("--set-version-string" ); |
464 | args.push_back("FileDescription" ); |
465 | args.push_back(file_description); |
466 | } |
467 | if (!copyright.is_empty()) { |
468 | args.push_back("--set-version-string" ); |
469 | args.push_back("LegalCopyright" ); |
470 | args.push_back(copyright); |
471 | } |
472 | if (!trademarks.is_empty()) { |
473 | args.push_back("--set-version-string" ); |
474 | args.push_back("LegalTrademarks" ); |
475 | args.push_back(trademarks); |
476 | } |
477 | |
478 | #ifndef WINDOWS_ENABLED |
479 | // On non-Windows we need WINE to run rcedit |
480 | args.push_front(rcedit_path); |
481 | rcedit_path = wine_path; |
482 | #endif |
483 | |
484 | String str; |
485 | Error err = OS::get_singleton()->execute(rcedit_path, args, &str, nullptr, true); |
486 | |
487 | if (FileAccess::exists(tmp_icon_path)) { |
488 | DirAccess::remove_file_or_error(tmp_icon_path); |
489 | } |
490 | |
491 | if (err != OK || (str.find("not found" ) != -1) || (str.find("not recognized" ) != -1)) { |
492 | add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification" ), TTR("Could not start rcedit executable. Configure rcedit path in the Editor Settings (Export > Windows > rcedit), or disable \"Application > Modify Resources\" in the export preset." )); |
493 | return err; |
494 | } |
495 | print_line("rcedit (" + p_path + "): " + str); |
496 | |
497 | if (str.find("Fatal error" ) != -1) { |
498 | add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification" ), vformat(TTR("rcedit failed to modify executable: %s." ), str)); |
499 | return FAILED; |
500 | } |
501 | |
502 | return OK; |
503 | } |
504 | |
505 | Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { |
506 | List<String> args; |
507 | |
508 | #ifdef WINDOWS_ENABLED |
509 | String signtool_path = EDITOR_GET("export/windows/signtool" ); |
510 | if (!signtool_path.is_empty() && !FileAccess::exists(signtool_path)) { |
511 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), vformat(TTR("Could not find signtool executable at \"%s\"." ), signtool_path)); |
512 | return ERR_FILE_NOT_FOUND; |
513 | } |
514 | if (signtool_path.is_empty()) { |
515 | signtool_path = "signtool" ; // try to run signtool from PATH |
516 | } |
517 | #else |
518 | String signtool_path = EDITOR_GET("export/windows/osslsigncode" ); |
519 | if (!signtool_path.is_empty() && !FileAccess::exists(signtool_path)) { |
520 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), vformat(TTR("Could not find osslsigncode executable at \"%s\"." ), signtool_path)); |
521 | return ERR_FILE_NOT_FOUND; |
522 | } |
523 | if (signtool_path.is_empty()) { |
524 | signtool_path = "osslsigncode" ; // try to run signtool from PATH |
525 | } |
526 | #endif |
527 | |
528 | args.push_back("sign" ); |
529 | |
530 | //identity |
531 | #ifdef WINDOWS_ENABLED |
532 | int id_type = p_preset->get_or_env("codesign/identity_type" , ENV_WIN_CODESIGN_ID_TYPE); |
533 | if (id_type == 0) { //auto select |
534 | args.push_back("/a" ); |
535 | } else if (id_type == 1) { //pkcs12 |
536 | if (p_preset->get_or_env("codesign/identity" , ENV_WIN_CODESIGN_ID) != "" ) { |
537 | args.push_back("/f" ); |
538 | args.push_back(p_preset->get_or_env("codesign/identity" , ENV_WIN_CODESIGN_ID)); |
539 | } else { |
540 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), TTR("No identity found." )); |
541 | return FAILED; |
542 | } |
543 | } else if (id_type == 2) { //Windows certificate store |
544 | if (p_preset->get_or_env("codesign/identity" , ENV_WIN_CODESIGN_ID) != "" ) { |
545 | args.push_back("/sha1" ); |
546 | args.push_back(p_preset->get_or_env("codesign/identity" , ENV_WIN_CODESIGN_ID)); |
547 | } else { |
548 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), TTR("No identity found." )); |
549 | return FAILED; |
550 | } |
551 | } else { |
552 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), TTR("Invalid identity type." )); |
553 | return FAILED; |
554 | } |
555 | #else |
556 | int id_type = 1; |
557 | if (p_preset->get_or_env("codesign/identity" , ENV_WIN_CODESIGN_ID) != "" ) { |
558 | args.push_back("-pkcs12" ); |
559 | args.push_back(p_preset->get_or_env("codesign/identity" , ENV_WIN_CODESIGN_ID)); |
560 | } else { |
561 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), TTR("No identity found." )); |
562 | return FAILED; |
563 | } |
564 | #endif |
565 | |
566 | //password |
567 | if ((id_type == 1) && (p_preset->get_or_env("codesign/password" , ENV_WIN_CODESIGN_PASS) != "" )) { |
568 | #ifdef WINDOWS_ENABLED |
569 | args.push_back("/p" ); |
570 | #else |
571 | args.push_back("-pass" ); |
572 | #endif |
573 | args.push_back(p_preset->get_or_env("codesign/password" , ENV_WIN_CODESIGN_PASS)); |
574 | } |
575 | |
576 | //timestamp |
577 | if (p_preset->get("codesign/timestamp" )) { |
578 | if (p_preset->get("codesign/timestamp_server" ) != "" ) { |
579 | #ifdef WINDOWS_ENABLED |
580 | args.push_back("/tr" ); |
581 | args.push_back(p_preset->get("codesign/timestamp_server_url" )); |
582 | args.push_back("/td" ); |
583 | if ((int)p_preset->get("codesign/digest_algorithm" ) == 0) { |
584 | args.push_back("sha1" ); |
585 | } else { |
586 | args.push_back("sha256" ); |
587 | } |
588 | #else |
589 | args.push_back("-ts" ); |
590 | args.push_back(p_preset->get("codesign/timestamp_server_url" )); |
591 | #endif |
592 | } else { |
593 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), TTR("Invalid timestamp server." )); |
594 | return FAILED; |
595 | } |
596 | } |
597 | |
598 | //digest |
599 | #ifdef WINDOWS_ENABLED |
600 | args.push_back("/fd" ); |
601 | #else |
602 | args.push_back("-h" ); |
603 | #endif |
604 | if ((int)p_preset->get("codesign/digest_algorithm" ) == 0) { |
605 | args.push_back("sha1" ); |
606 | } else { |
607 | args.push_back("sha256" ); |
608 | } |
609 | |
610 | //description |
611 | if (p_preset->get("codesign/description" ) != "" ) { |
612 | #ifdef WINDOWS_ENABLED |
613 | args.push_back("/d" ); |
614 | #else |
615 | args.push_back("-n" ); |
616 | #endif |
617 | args.push_back(p_preset->get("codesign/description" )); |
618 | } |
619 | |
620 | //user options |
621 | PackedStringArray user_args = p_preset->get("codesign/custom_options" ); |
622 | for (int i = 0; i < user_args.size(); i++) { |
623 | String user_arg = user_args[i].strip_edges(); |
624 | if (!user_arg.is_empty()) { |
625 | args.push_back(user_arg); |
626 | } |
627 | } |
628 | |
629 | #ifndef WINDOWS_ENABLED |
630 | args.push_back("-in" ); |
631 | #endif |
632 | args.push_back(p_path); |
633 | #ifndef WINDOWS_ENABLED |
634 | args.push_back("-out" ); |
635 | args.push_back(p_path + "_signed" ); |
636 | #endif |
637 | |
638 | String str; |
639 | Error err = OS::get_singleton()->execute(signtool_path, args, &str, nullptr, true); |
640 | if (err != OK || (str.find("not found" ) != -1) || (str.find("not recognized" ) != -1)) { |
641 | #ifdef WINDOWS_ENABLED |
642 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), TTR("Could not start signtool executable. Configure signtool path in the Editor Settings (Export > Windows > signtool), or disable \"Codesign\" in the export preset." )); |
643 | #else |
644 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), TTR("Could not start osslsigncode executable. Configure signtool path in the Editor Settings (Export > Windows > osslsigncode), or disable \"Codesign\" in the export preset." )); |
645 | #endif |
646 | return err; |
647 | } |
648 | |
649 | print_line("codesign (" + p_path + "): " + str); |
650 | #ifndef WINDOWS_ENABLED |
651 | if (str.find("SignTool Error" ) != -1) { |
652 | #else |
653 | if (str.find("Failed" ) != -1) { |
654 | #endif |
655 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), vformat(TTR("Signtool failed to sign executable: %s." ), str)); |
656 | return FAILED; |
657 | } |
658 | |
659 | #ifndef WINDOWS_ENABLED |
660 | Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); |
661 | |
662 | err = tmp_dir->remove(p_path); |
663 | if (err != OK) { |
664 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), vformat(TTR("Failed to remove temporary file \"%s\"." ), p_path)); |
665 | return err; |
666 | } |
667 | |
668 | err = tmp_dir->rename(p_path + "_signed" , p_path); |
669 | if (err != OK) { |
670 | add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing" ), vformat(TTR("Failed to rename temporary file \"%s\"." ), p_path + "_signed" )); |
671 | return err; |
672 | } |
673 | #endif |
674 | |
675 | return OK; |
676 | } |
677 | |
678 | bool EditorExportPlatformWindows::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { |
679 | String err = "" ; |
680 | bool valid = EditorExportPlatformPC::has_valid_export_configuration(p_preset, err, r_missing_templates, p_debug); |
681 | |
682 | String rcedit_path = EDITOR_GET("export/windows/rcedit" ); |
683 | if (p_preset->get("application/modify_resources" ) && rcedit_path.is_empty()) { |
684 | err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > rcedit) to change the icon or app information data." ) + "\n" ; |
685 | } |
686 | |
687 | if (!err.is_empty()) { |
688 | r_error = err; |
689 | } |
690 | |
691 | return valid; |
692 | } |
693 | |
694 | bool EditorExportPlatformWindows::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { |
695 | String err = "" ; |
696 | bool valid = true; |
697 | |
698 | List<ExportOption> options; |
699 | get_export_options(&options); |
700 | for (const EditorExportPlatform::ExportOption &E : options) { |
701 | if (get_export_option_visibility(p_preset.ptr(), E.option.name)) { |
702 | String warn = get_export_option_warning(p_preset.ptr(), E.option.name); |
703 | if (!warn.is_empty()) { |
704 | err += warn + "\n" ; |
705 | if (E.required) { |
706 | valid = false; |
707 | } |
708 | } |
709 | } |
710 | } |
711 | |
712 | if (!err.is_empty()) { |
713 | r_error = err; |
714 | } |
715 | |
716 | return valid; |
717 | } |
718 | |
719 | Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { |
720 | // Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data |
721 | |
722 | if (p_embedded_size + p_embedded_start >= 0x100000000) { // Check for total executable size |
723 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), TTR("Windows executables cannot be >= 4 GiB." )); |
724 | return ERR_INVALID_DATA; |
725 | } |
726 | |
727 | Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE); |
728 | if (f.is_null()) { |
729 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), vformat(TTR("Failed to open executable file \"%s\"." ), p_path)); |
730 | return ERR_CANT_OPEN; |
731 | } |
732 | |
733 | // Jump to the PE header and check the magic number |
734 | { |
735 | f->seek(0x3c); |
736 | uint32_t pe_pos = f->get_32(); |
737 | |
738 | f->seek(pe_pos); |
739 | uint32_t magic = f->get_32(); |
740 | if (magic != 0x00004550) { |
741 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), TTR("Executable file header corrupted." )); |
742 | return ERR_FILE_CORRUPT; |
743 | } |
744 | } |
745 | |
746 | // Process header |
747 | |
748 | int num_sections; |
749 | { |
750 | int64_t = f->get_position(); |
751 | |
752 | f->seek(header_pos + 2); |
753 | num_sections = f->get_16(); |
754 | f->seek(header_pos + 16); |
755 | uint16_t = f->get_16(); |
756 | |
757 | // Skip rest of header + optional header to go to the section headers |
758 | f->seek(f->get_position() + 2 + opt_header_size); |
759 | } |
760 | |
761 | // Search for the "pck" section |
762 | |
763 | int64_t section_table_pos = f->get_position(); |
764 | |
765 | bool found = false; |
766 | for (int i = 0; i < num_sections; ++i) { |
767 | int64_t = section_table_pos + i * 40; |
768 | f->seek(section_header_pos); |
769 | |
770 | uint8_t section_name[9]; |
771 | f->get_buffer(section_name, 8); |
772 | section_name[8] = '\0'; |
773 | |
774 | if (strcmp((char *)section_name, "pck" ) == 0) { |
775 | // "pck" section found, let's patch! |
776 | |
777 | // Set virtual size to a little to avoid it taking memory (zero would give issues) |
778 | f->seek(section_header_pos + 8); |
779 | f->store_32(8); |
780 | |
781 | f->seek(section_header_pos + 16); |
782 | f->store_32(p_embedded_size); |
783 | f->seek(section_header_pos + 20); |
784 | f->store_32(p_embedded_start); |
785 | |
786 | found = true; |
787 | break; |
788 | } |
789 | } |
790 | |
791 | if (!found) { |
792 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), TTR("Executable \"pck\" section not found." )); |
793 | return ERR_FILE_CORRUPT; |
794 | } |
795 | return OK; |
796 | } |
797 | |
798 | Ref<Texture2D> EditorExportPlatformWindows::get_run_icon() const { |
799 | return run_icon; |
800 | } |
801 | |
802 | bool EditorExportPlatformWindows::poll_export() { |
803 | Ref<EditorExportPreset> preset; |
804 | |
805 | for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { |
806 | Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i); |
807 | if (ep->is_runnable() && ep->get_platform() == this) { |
808 | preset = ep; |
809 | break; |
810 | } |
811 | } |
812 | |
813 | int prev = menu_options; |
814 | menu_options = (preset.is_valid() && preset->get("ssh_remote_deploy/enabled" ).operator bool()); |
815 | if (ssh_pid != 0 || !cleanup_commands.is_empty()) { |
816 | if (menu_options == 0) { |
817 | cleanup(); |
818 | } else { |
819 | menu_options += 1; |
820 | } |
821 | } |
822 | return menu_options != prev; |
823 | } |
824 | |
825 | Ref<ImageTexture> EditorExportPlatformWindows::get_option_icon(int p_index) const { |
826 | return p_index == 1 ? stop_icon : EditorExportPlatform::get_option_icon(p_index); |
827 | } |
828 | |
829 | int EditorExportPlatformWindows::get_options_count() const { |
830 | return menu_options; |
831 | } |
832 | |
833 | String EditorExportPlatformWindows::get_option_label(int p_index) const { |
834 | return (p_index) ? TTR("Stop and uninstall" ) : TTR("Run on remote Windows system" ); |
835 | } |
836 | |
837 | String EditorExportPlatformWindows::get_option_tooltip(int p_index) const { |
838 | return (p_index) ? TTR("Stop and uninstall running project from the remote system" ) : TTR("Run exported project on remote Windows system" ); |
839 | } |
840 | |
841 | void EditorExportPlatformWindows::cleanup() { |
842 | if (ssh_pid != 0 && OS::get_singleton()->is_process_running(ssh_pid)) { |
843 | print_line("Terminating connection..." ); |
844 | OS::get_singleton()->kill(ssh_pid); |
845 | OS::get_singleton()->delay_usec(1000); |
846 | } |
847 | |
848 | if (!cleanup_commands.is_empty()) { |
849 | print_line("Stopping and deleting previous version..." ); |
850 | for (const SSHCleanupCommand &cmd : cleanup_commands) { |
851 | if (cmd.wait) { |
852 | ssh_run_on_remote(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); |
853 | } else { |
854 | ssh_run_on_remote_no_wait(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); |
855 | } |
856 | } |
857 | } |
858 | ssh_pid = 0; |
859 | cleanup_commands.clear(); |
860 | } |
861 | |
862 | Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { |
863 | cleanup(); |
864 | if (p_device) { // Stop command, cleanup only. |
865 | return OK; |
866 | } |
867 | |
868 | EditorProgress ep("run" , TTR("Running..." ), 5); |
869 | |
870 | const String dest = EditorPaths::get_singleton()->get_cache_dir().path_join("windows" ); |
871 | Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); |
872 | if (!da->dir_exists(dest)) { |
873 | Error err = da->make_dir_recursive(dest); |
874 | if (err != OK) { |
875 | EditorNode::get_singleton()->show_warning(TTR("Could not create temp directory:" ) + "\n" + dest); |
876 | return err; |
877 | } |
878 | } |
879 | |
880 | String host = p_preset->get("ssh_remote_deploy/host" ).operator String(); |
881 | String port = p_preset->get("ssh_remote_deploy/port" ).operator String(); |
882 | if (port.is_empty()) { |
883 | port = "22" ; |
884 | } |
885 | Vector<String> = p_preset->get("ssh_remote_deploy/extra_args_ssh" ).operator String().split(" " , false); |
886 | Vector<String> = p_preset->get("ssh_remote_deploy/extra_args_scp" ).operator String().split(" " , false); |
887 | |
888 | const String basepath = dest.path_join("tmp_windows_export" ); |
889 | |
890 | #define CLEANUP_AND_RETURN(m_err) \ |
891 | { \ |
892 | if (da->file_exists(basepath + ".zip")) { \ |
893 | da->remove(basepath + ".zip"); \ |
894 | } \ |
895 | if (da->file_exists(basepath + "_start.ps1")) { \ |
896 | da->remove(basepath + "_start.ps1"); \ |
897 | } \ |
898 | if (da->file_exists(basepath + "_clean.ps1")) { \ |
899 | da->remove(basepath + "_clean.ps1"); \ |
900 | } \ |
901 | return m_err; \ |
902 | } \ |
903 | ((void)0) |
904 | |
905 | if (ep.step(TTR("Exporting project..." ), 1)) { |
906 | return ERR_SKIP; |
907 | } |
908 | Error err = export_project(p_preset, true, basepath + ".zip" , p_debug_flags); |
909 | if (err != OK) { |
910 | DirAccess::remove_file_or_error(basepath + ".zip" ); |
911 | return err; |
912 | } |
913 | |
914 | String cmd_args; |
915 | { |
916 | Vector<String> cmd_args_list; |
917 | gen_debug_flags(cmd_args_list, p_debug_flags); |
918 | for (int i = 0; i < cmd_args_list.size(); i++) { |
919 | if (i != 0) { |
920 | cmd_args += " " ; |
921 | } |
922 | cmd_args += cmd_args_list[i]; |
923 | } |
924 | } |
925 | |
926 | const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); |
927 | int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port" ); |
928 | |
929 | print_line("Creating temporary directory..." ); |
930 | ep.step(TTR("Creating temporary directory..." ), 2); |
931 | String temp_dir; |
932 | #ifndef WINDOWS_ENABLED |
933 | err = ssh_run_on_remote(host, port, extra_args_ssh, "powershell -command \\\"\\$tmp = Join-Path \\$Env:Temp \\$(New-Guid); New-Item -Type Directory -Path \\$tmp | Out-Null; Write-Output \\$tmp\\\"" , &temp_dir); |
934 | #else |
935 | err = ssh_run_on_remote(host, port, extra_args_ssh, "powershell -command \"$tmp = Join-Path $Env:Temp $(New-Guid); New-Item -Type Directory -Path $tmp ^| Out-Null; Write-Output $tmp\"" , &temp_dir); |
936 | #endif |
937 | if (err != OK || temp_dir.is_empty()) { |
938 | CLEANUP_AND_RETURN(err); |
939 | } |
940 | |
941 | print_line("Uploading archive..." ); |
942 | ep.step(TTR("Uploading archive..." ), 3); |
943 | err = ssh_push_to_remote(host, port, extra_args_scp, basepath + ".zip" , temp_dir); |
944 | if (err != OK) { |
945 | CLEANUP_AND_RETURN(err); |
946 | } |
947 | |
948 | if (cmd_args.is_empty()) { |
949 | cmd_args = " " ; |
950 | } |
951 | |
952 | { |
953 | String run_script = p_preset->get("ssh_remote_deploy/run_script" ); |
954 | run_script = run_script.replace("{temp_dir}" , temp_dir); |
955 | run_script = run_script.replace("{archive_name}" , basepath.get_file() + ".zip" ); |
956 | run_script = run_script.replace("{exe_name}" , basepath.get_file() + ".exe" ); |
957 | run_script = run_script.replace("{cmd_args}" , cmd_args); |
958 | |
959 | Ref<FileAccess> f = FileAccess::open(basepath + "_start.ps1" , FileAccess::WRITE); |
960 | if (f.is_null()) { |
961 | CLEANUP_AND_RETURN(err); |
962 | } |
963 | |
964 | f->store_string(run_script); |
965 | } |
966 | |
967 | { |
968 | String clean_script = p_preset->get("ssh_remote_deploy/cleanup_script" ); |
969 | clean_script = clean_script.replace("{temp_dir}" , temp_dir); |
970 | clean_script = clean_script.replace("{archive_name}" , basepath.get_file() + ".zip" ); |
971 | clean_script = clean_script.replace("{exe_name}" , basepath.get_file() + ".exe" ); |
972 | clean_script = clean_script.replace("{cmd_args}" , cmd_args); |
973 | |
974 | Ref<FileAccess> f = FileAccess::open(basepath + "_clean.ps1" , FileAccess::WRITE); |
975 | if (f.is_null()) { |
976 | CLEANUP_AND_RETURN(err); |
977 | } |
978 | |
979 | f->store_string(clean_script); |
980 | } |
981 | |
982 | print_line("Uploading scripts..." ); |
983 | ep.step(TTR("Uploading scripts..." ), 4); |
984 | err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_start.ps1" , temp_dir); |
985 | if (err != OK) { |
986 | CLEANUP_AND_RETURN(err); |
987 | } |
988 | err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_clean.ps1" , temp_dir); |
989 | if (err != OK) { |
990 | CLEANUP_AND_RETURN(err); |
991 | } |
992 | |
993 | print_line("Starting project..." ); |
994 | ep.step(TTR("Starting project..." ), 5); |
995 | err = ssh_run_on_remote_no_wait(host, port, extra_args_ssh, vformat("powershell -file \"%s\\%s\"" , temp_dir, basepath.get_file() + "_start.ps1" ), &ssh_pid, (use_remote) ? dbg_port : -1); |
996 | if (err != OK) { |
997 | CLEANUP_AND_RETURN(err); |
998 | } |
999 | |
1000 | cleanup_commands.clear(); |
1001 | cleanup_commands.push_back(SSHCleanupCommand(host, port, extra_args_ssh, vformat("powershell -file \"%s\\%s\"" , temp_dir, basepath.get_file() + "_clean.ps1" ))); |
1002 | |
1003 | print_line("Project started." ); |
1004 | |
1005 | CLEANUP_AND_RETURN(OK); |
1006 | #undef CLEANUP_AND_RETURN |
1007 | } |
1008 | |
1009 | EditorExportPlatformWindows::EditorExportPlatformWindows() { |
1010 | if (EditorNode::get_singleton()) { |
1011 | #ifdef MODULE_SVG_ENABLED |
1012 | Ref<Image> img = memnew(Image); |
1013 | const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); |
1014 | |
1015 | ImageLoaderSVG::create_image_from_string(img, _windows_logo_svg, EDSCALE, upsample, false); |
1016 | set_logo(ImageTexture::create_from_image(img)); |
1017 | |
1018 | ImageLoaderSVG::create_image_from_string(img, _windows_run_icon_svg, EDSCALE, upsample, false); |
1019 | run_icon = ImageTexture::create_from_image(img); |
1020 | #endif |
1021 | |
1022 | Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); |
1023 | if (theme.is_valid()) { |
1024 | stop_icon = theme->get_icon(SNAME("Stop" ), EditorStringName(EditorIcons)); |
1025 | } else { |
1026 | stop_icon.instantiate(); |
1027 | } |
1028 | } |
1029 | } |
1030 | |