Path: blob/master/platform/windows/export/export_plugin.cpp
10278 views
/**************************************************************************/1/* export_plugin.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "export_plugin.h"3132#include "logo_svg.gen.h"33#include "run_icon_svg.gen.h"34#include "template_modifier.h"3536#include "core/config/project_settings.h"37#include "core/io/image_loader.h"38#include "editor/editor_node.h"39#include "editor/editor_string_names.h"40#include "editor/export/editor_export.h"41#include "editor/file_system/editor_paths.h"42#include "editor/themes/editor_scale.h"4344#include "modules/svg/image_loader_svg.h"4546#ifdef WINDOWS_ENABLED47#include "shlobj.h"4849// Converts long path to Windows UNC format.50static String fix_path(const String &p_path) {51String path = p_path;52if (p_path.is_relative_path()) {53Char16String current_dir_name;54size_t str_len = GetCurrentDirectoryW(0, nullptr);55current_dir_name.resize_uninitialized(str_len + 1);56GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());57path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace_char('\\', '/').path_join(path);58}59path = path.simplify_path();60path = path.replace_char('/', '\\');61if (path.size() >= MAX_PATH && !path.is_network_share_path() && !path.begins_with(R"(\\?\)")) {62path = R"(\\?\)" + path;63}64return path;65}6667#endif6869Error EditorExportPlatformWindows::_process_icon(const Ref<EditorExportPreset> &p_preset, const String &p_src_path, const String &p_dst_path) {70static const uint8_t icon_size[] = { 16, 32, 48, 64, 128, 0 /*256*/ };7172struct IconData {73Vector<uint8_t> data;74uint8_t pal_colors = 0;75uint16_t planes = 0;76uint16_t bpp = 32;77};7879HashMap<uint8_t, IconData> images;80Error err;8182if (p_src_path.get_extension() == "ico") {83Ref<FileAccess> f = FileAccess::open(p_src_path, FileAccess::READ, &err);84if (err != OK) {85return err;86}8788// Read ICONDIR.89f->get_16(); // Reserved.90uint16_t icon_type = f->get_16(); // Image type: 1 - ICO.91uint16_t icon_count = f->get_16(); // Number of images.92ERR_FAIL_COND_V(icon_type != 1, ERR_CANT_OPEN);9394for (uint16_t i = 0; i < icon_count; i++) {95// Read ICONDIRENTRY.96uint16_t w = f->get_8(); // Width in pixels.97uint16_t h = f->get_8(); // Height in pixels.98uint8_t pal_colors = f->get_8(); // Number of colors in the palette (0 - no palette).99f->get_8(); // Reserved.100uint16_t planes = f->get_16(); // Number of color planes.101uint16_t bpp = f->get_16(); // Bits per pixel.102uint32_t img_size = f->get_32(); // Image data size in bytes.103uint32_t img_offset = f->get_32(); // Image data offset.104if (w != h) {105continue;106}107108// Read image data.109uint64_t prev_offset = f->get_position();110images[w].pal_colors = pal_colors;111images[w].planes = planes;112images[w].bpp = bpp;113images[w].data.resize(img_size);114f->seek(img_offset);115f->get_buffer(images[w].data.ptrw(), img_size);116f->seek(prev_offset);117}118} else {119Ref<Image> src_image = _load_icon_or_splash_image(p_src_path, &err);120ERR_FAIL_COND_V(err != OK || src_image.is_null() || src_image->is_empty(), ERR_CANT_OPEN);121122for (size_t i = 0; i < std::size(icon_size); ++i) {123int size = (icon_size[i] == 0) ? 256 : icon_size[i];124125Ref<Image> res_image = src_image->duplicate();126ERR_FAIL_COND_V(res_image.is_null() || res_image->is_empty(), ERR_CANT_OPEN);127res_image->resize(size, size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));128images[icon_size[i]].data = res_image->save_png_to_buffer();129}130}131132uint16_t valid_icon_count = 0;133for (size_t i = 0; i < std::size(icon_size); ++i) {134if (images.has(icon_size[i])) {135valid_icon_count++;136} else {137int size = (icon_size[i] == 0) ? 256 : icon_size[i];138add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Icon size \"%d\" is missing."), size));139}140}141ERR_FAIL_COND_V(valid_icon_count == 0, ERR_CANT_OPEN);142143Ref<FileAccess> fw = FileAccess::open(p_dst_path, FileAccess::WRITE, &err);144if (err != OK) {145return err;146}147148// Write ICONDIR.149fw->store_16(0); // Reserved.150fw->store_16(1); // Image type: 1 - ICO.151fw->store_16(valid_icon_count); // Number of images.152153// Write ICONDIRENTRY.154uint32_t img_offset = 6 + 16 * valid_icon_count;155for (size_t i = 0; i < std::size(icon_size); ++i) {156if (images.has(icon_size[i])) {157const IconData &di = images[icon_size[i]];158fw->store_8(icon_size[i]); // Width in pixels.159fw->store_8(icon_size[i]); // Height in pixels.160fw->store_8(di.pal_colors); // Number of colors in the palette (0 - no palette).161fw->store_8(0); // Reserved.162fw->store_16(di.planes); // Number of color planes.163fw->store_16(di.bpp); // Bits per pixel.164fw->store_32(di.data.size()); // Image data size in bytes.165fw->store_32(img_offset); // Image data offset.166167img_offset += di.data.size();168}169}170171// Write image data.172for (size_t i = 0; i < std::size(icon_size); ++i) {173if (images.has(icon_size[i])) {174const IconData &di = images[icon_size[i]];175fw->store_buffer(di.data.ptr(), di.data.size());176}177}178return OK;179}180181Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {182if (p_preset->get("codesign/enable")) {183return _code_sign(p_preset, p_path);184} else {185return OK;186}187}188189Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {190if (p_preset->get("application/modify_resources")) {191_add_data(p_preset, p_path, false);192String wrapper_path = p_path.get_basename() + ".console.exe";193if (FileAccess::exists(wrapper_path)) {194_add_data(p_preset, wrapper_path, true);195}196}197return OK;198}199200Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {201String custom_debug = p_preset->get("custom_template/debug");202String custom_release = p_preset->get("custom_template/release");203String arch = p_preset->get("binary_format/architecture");204205String template_path = p_debug ? custom_debug : custom_release;206template_path = template_path.strip_edges();207if (template_path.is_empty()) {208template_path = find_export_template(get_template_file_name(p_debug ? "debug" : "release", arch));209} else {210String exe_arch = _get_exe_arch(template_path);211if (arch != exe_arch) {212add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Mismatching custom export template executable architecture: found \"%s\", expected \"%s\"."), exe_arch, arch));213return ERR_CANT_CREATE;214}215}216217bool export_as_zip = p_path.ends_with("zip");218bool embedded = p_preset->get("binary_format/embed_pck");219220String pkg_name;221if (String(get_project_setting(p_preset, "application/config/name")) != "") {222pkg_name = String(get_project_setting(p_preset, "application/config/name"));223} else {224pkg_name = "Unnamed";225}226227pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name);228229// Setup temp folder.230String path = p_path;231String tmp_dir_path = EditorPaths::get_singleton()->get_temp_dir().path_join(pkg_name);232Ref<DirAccess> tmp_app_dir = DirAccess::create_for_path(tmp_dir_path);233if (export_as_zip) {234if (tmp_app_dir.is_null()) {235add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not create and open the directory: \"%s\""), tmp_dir_path));236return ERR_CANT_CREATE;237}238if (DirAccess::exists(tmp_dir_path)) {239if (tmp_app_dir->change_dir(tmp_dir_path) == OK) {240tmp_app_dir->erase_contents_recursive();241}242}243tmp_app_dir->make_dir_recursive(tmp_dir_path);244path = tmp_dir_path.path_join(p_path.get_file().get_basename() + ".exe");245}246247Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);248int export_angle = p_preset->get("application/export_angle");249bool include_angle_libs = false;250if (export_angle == 0) {251include_angle_libs = (String(get_project_setting(p_preset, "rendering/gl_compatibility/driver.windows")) == "opengl3_angle") && (String(get_project_setting(p_preset, "rendering/renderer/rendering_method")) == "gl_compatibility");252} else if (export_angle == 1) {253include_angle_libs = true;254}255if (include_angle_libs) {256if (da->file_exists(template_path.get_base_dir().path_join("libEGL." + arch + ".dll"))) {257da->copy(template_path.get_base_dir().path_join("libEGL." + arch + ".dll"), path.get_base_dir().path_join("libEGL.dll"), get_chmod_flags());258}259if (da->file_exists(template_path.get_base_dir().path_join("libGLESv2." + arch + ".dll"))) {260da->copy(template_path.get_base_dir().path_join("libGLESv2." + arch + ".dll"), path.get_base_dir().path_join("libGLESv2.dll"), get_chmod_flags());261}262}263if (da->file_exists(template_path.get_base_dir().path_join("accesskit." + arch + ".dll"))) {264da->copy(template_path.get_base_dir().path_join("accesskit." + arch + ".dll"), path.get_base_dir().path_join("accesskit." + arch + ".dll"), get_chmod_flags());265}266267int export_d3d12 = p_preset->get("application/export_d3d12");268bool agility_sdk_multiarch = p_preset->get("application/d3d12_agility_sdk_multiarch");269bool include_d3d12_extra_libs = false;270if (export_d3d12 == 0) {271include_d3d12_extra_libs = (String(get_project_setting(p_preset, "rendering/rendering_device/driver.windows")) == "d3d12") && (String(get_project_setting(p_preset, "rendering/renderer/rendering_method")) != "gl_compatibility");272} else if (export_d3d12 == 1) {273include_d3d12_extra_libs = true;274}275if (include_d3d12_extra_libs) {276if (da->file_exists(template_path.get_base_dir().path_join("D3D12Core." + arch + ".dll"))) {277if (agility_sdk_multiarch) {278da->make_dir_recursive(path.get_base_dir().path_join(arch));279da->copy(template_path.get_base_dir().path_join("D3D12Core." + arch + ".dll"), path.get_base_dir().path_join(arch).path_join("D3D12Core.dll"), get_chmod_flags());280} else {281da->copy(template_path.get_base_dir().path_join("D3D12Core." + arch + ".dll"), path.get_base_dir().path_join("D3D12Core.dll"), get_chmod_flags());282}283}284if (da->file_exists(template_path.get_base_dir().path_join("d3d12SDKLayers." + arch + ".dll"))) {285if (agility_sdk_multiarch) {286da->make_dir_recursive(path.get_base_dir().path_join(arch));287da->copy(template_path.get_base_dir().path_join("d3d12SDKLayers." + arch + ".dll"), path.get_base_dir().path_join(arch).path_join("d3d12SDKLayers.dll"), get_chmod_flags());288} else {289da->copy(template_path.get_base_dir().path_join("d3d12SDKLayers." + arch + ".dll"), path.get_base_dir().path_join("d3d12SDKLayers.dll"), get_chmod_flags());290}291}292if (da->file_exists(template_path.get_base_dir().path_join("WinPixEventRuntime." + arch + ".dll"))) {293da->copy(template_path.get_base_dir().path_join("WinPixEventRuntime." + arch + ".dll"), path.get_base_dir().path_join("WinPixEventRuntime.dll"), get_chmod_flags());294}295}296297// Export project.298String pck_path = path;299if (embedded) {300pck_path = pck_path.get_basename() + ".tmp";301}302303Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, pck_path, p_flags);304if (err != OK) {305// Message is supplied by the subroutine method.306return err;307}308309if (p_preset->get("codesign/enable")) {310_code_sign(p_preset, pck_path);311String wrapper_path = path.get_basename() + ".console.exe";312if (FileAccess::exists(wrapper_path)) {313_code_sign(p_preset, wrapper_path);314}315}316317if (embedded) {318Ref<DirAccess> tmp_dir = DirAccess::create_for_path(path.get_base_dir());319err = tmp_dir->rename(pck_path, path);320if (err != OK) {321add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to rename temporary file \"%s\"."), pck_path));322}323}324325// ZIP project.326if (export_as_zip) {327if (FileAccess::exists(p_path)) {328OS::get_singleton()->move_to_trash(p_path);329}330331Ref<FileAccess> io_fa_dst;332zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst);333zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst);334335zip_folder_recursive(zip, tmp_dir_path, "", pkg_name);336337zipClose(zip, nullptr);338339if (tmp_app_dir->change_dir(tmp_dir_path) == OK) {340tmp_app_dir->erase_contents_recursive();341tmp_app_dir->change_dir("..");342tmp_app_dir->remove(pkg_name);343}344#ifdef WINDOWS_ENABLED345} else {346// Update Windows icon cache.347String w_path = fix_path(path);348SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, (LPCWSTR)w_path.utf16().get_data(), nullptr);349350String wrapper_path = path.get_basename() + ".console.exe";351if (FileAccess::exists(wrapper_path)) {352String w_wrapper_path = fix_path(wrapper_path);353SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, (LPCWSTR)w_wrapper_path.utf16().get_data(), nullptr);354}355356w_path = fix_path(path.get_base_dir());357SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, (LPCWSTR)w_path.utf16().get_data(), nullptr);358359SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);360#endif361}362363return err;364}365366String EditorExportPlatformWindows::get_template_file_name(const String &p_target, const String &p_arch) const {367return "windows_" + p_target + "_" + p_arch + ".exe";368}369370List<String> EditorExportPlatformWindows::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {371List<String> list;372list.push_back("exe");373list.push_back("zip");374return list;375}376377String EditorExportPlatformWindows::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {378if (p_preset) {379if (p_name == "application/icon") {380String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon"));381if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) {382return TTR("Invalid icon path.");383}384} else if (p_name == "application/file_version") {385String file_version = p_preset->get("application/file_version");386if (!file_version.is_empty()) {387PackedStringArray version_array = file_version.split(".", false);388if (version_array.size() != 4 || !version_array[0].is_valid_int() ||389!version_array[1].is_valid_int() || !version_array[2].is_valid_int() ||390!version_array[3].is_valid_int() || file_version.contains_char('-')) {391return TTR("Invalid file version.");392}393}394} else if (p_name == "application/product_version") {395String product_version = p_preset->get("application/product_version");396if (!product_version.is_empty()) {397PackedStringArray version_array = product_version.split(".", false);398if (version_array.size() != 4 || !version_array[0].is_valid_int() ||399!version_array[1].is_valid_int() || !version_array[2].is_valid_int() ||400!version_array[3].is_valid_int() || product_version.contains_char('-')) {401return TTR("Invalid product version.");402}403}404}405}406return EditorExportPlatformPC::get_export_option_warning(p_preset, p_name);407}408409bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {410if (p_preset == nullptr) {411return true;412}413414// This option is not supported by "osslsigncode", used on non-Windows host.415if (!OS::get_singleton()->has_feature("windows") && p_option == "codesign/identity_type") {416return false;417}418419bool advanced_options_enabled = p_preset->are_advanced_options_enabled();420421// Hide codesign.422bool codesign = p_preset->get("codesign/enable");423if (!codesign && p_option != "codesign/enable" && p_option.begins_with("codesign/")) {424return false;425}426427// Hide resources.428bool mod_res = p_preset->get("application/modify_resources");429if (!mod_res && p_option != "application/modify_resources" && p_option != "application/export_angle" && p_option != "application/export_d3d12" && p_option != "application/d3d12_agility_sdk_multiarch" && p_option.begins_with("application/")) {430return false;431}432433// Hide SSH options.434bool ssh = p_preset->get("ssh_remote_deploy/enabled");435if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/")) {436return false;437}438439if (p_option == "dotnet/embed_build_outputs" ||440p_option == "custom_template/debug" ||441p_option == "custom_template/release" ||442p_option == "application/d3d12_agility_sdk_multiarch" ||443p_option == "application/export_angle" ||444p_option == "application/export_d3d12" ||445p_option == "application/icon_interpolation") {446return advanced_options_enabled;447}448return true;449}450451void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) const {452EditorExportPlatformPC::get_export_options(r_options);453454r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64"), "x86_64"));455456r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false, true));457r_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));458r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));459r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password", PROPERTY_HINT_PASSWORD, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));460r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));461r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/timestamp_server_url"), ""));462r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/digest_algorithm", PROPERTY_HINT_ENUM, "SHA1,SHA256"), 1));463r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/description"), ""));464r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));465466r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/modify_resources"), true, true));467r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg"), "", false, true));468r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/console_wrapper_icon", PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg"), ""));469r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4));470r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));471r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));472r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), ""));473r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));474r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), ""));475r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));476r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/trademarks"), ""));477r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_angle", PROPERTY_HINT_ENUM, "Auto,Yes,No"), 0, true));478r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_d3d12", PROPERTY_HINT_ENUM, "Auto,Yes,No"), 0, true));479r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/d3d12_agility_sdk_multiarch"), true, true));480481String run_script = "Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'\n"482"$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'\n"483"$trigger = New-ScheduledTaskTrigger -Once -At 00:00\n"484"$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries\n"485"$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings\n"486"Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true\n"487"Start-ScheduledTask -TaskName godot_remote_debug\n"488"while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }\n"489"Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue";490491String cleanup_script = "Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue\n"492"Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue\n"493"Remove-Item -Recurse -Force '{temp_dir}'";494495r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false, true));496r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host"), "user@host_ip"));497r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port"), "22"));498499r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_ssh", PROPERTY_HINT_MULTILINE_TEXT), ""));500r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_scp", PROPERTY_HINT_MULTILINE_TEXT), ""));501r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/run_script", PROPERTY_HINT_MULTILINE_TEXT), run_script));502r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/cleanup_script", PROPERTY_HINT_MULTILINE_TEXT), cleanup_script));503}504505Error EditorExportPlatformWindows::_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path, bool p_console_icon) {506String icon_path;507if (p_preset->get("application/icon") != "") {508icon_path = p_preset->get("application/icon");509} else if (get_project_setting(p_preset, "application/config/windows_native_icon") != "") {510icon_path = get_project_setting(p_preset, "application/config/windows_native_icon");511} else {512icon_path = get_project_setting(p_preset, "application/config/icon");513}514icon_path = ProjectSettings::get_singleton()->globalize_path(icon_path);515516if (p_console_icon) {517String console_icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/console_wrapper_icon"));518if (!console_icon_path.is_empty() && FileAccess::exists(console_icon_path)) {519icon_path = console_icon_path;520}521}522523String tmp_icon_path = EditorPaths::get_singleton()->get_temp_dir().path_join("_tmp.ico");524if (!icon_path.is_empty()) {525if (_process_icon(p_preset, icon_path, tmp_icon_path) != OK) {526add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Invalid icon file \"%s\"."), icon_path));527icon_path = String();528}529}530531TemplateModifier::modify(p_preset, p_path, tmp_icon_path);532533if (FileAccess::exists(tmp_icon_path)) {534DirAccess::remove_file_or_error(tmp_icon_path);535}536537return OK;538}539540Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) {541List<String> args;542543#ifdef WINDOWS_ENABLED544String signtool_path = EDITOR_GET("export/windows/signtool");545if (!signtool_path.is_empty() && !FileAccess::exists(signtool_path)) {546add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Could not find signtool executable at \"%s\"."), signtool_path));547return ERR_FILE_NOT_FOUND;548}549if (signtool_path.is_empty()) {550signtool_path = "signtool"; // try to run signtool from PATH551}552#else553String signtool_path = EDITOR_GET("export/windows/osslsigncode");554if (!signtool_path.is_empty() && !FileAccess::exists(signtool_path)) {555add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Could not find osslsigncode executable at \"%s\"."), signtool_path));556return ERR_FILE_NOT_FOUND;557}558if (signtool_path.is_empty()) {559signtool_path = "osslsigncode"; // try to run signtool from PATH560}561#endif562563args.push_back("sign");564565//identity566#ifdef WINDOWS_ENABLED567int id_type = p_preset->get_or_env("codesign/identity_type", ENV_WIN_CODESIGN_ID_TYPE);568if (id_type == 0) { //auto select569args.push_back("/a");570} else if (id_type == 1) { //pkcs12571if (p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID) != "") {572args.push_back("/f");573args.push_back(p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID));574} else {575add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));576return FAILED;577}578} else if (id_type == 2) { //Windows certificate store579if (p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID) != "") {580args.push_back("/sha1");581args.push_back(p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID));582} else {583add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));584return FAILED;585}586} else {587add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid identity type."));588return FAILED;589}590#else591int id_type = 1;592if (p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID) != "") {593args.push_back("-pkcs12");594args.push_back(p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID));595} else {596add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));597return FAILED;598}599#endif600601//password602if ((id_type == 1) && (p_preset->get_or_env("codesign/password", ENV_WIN_CODESIGN_PASS) != "")) {603#ifdef WINDOWS_ENABLED604args.push_back("/p");605#else606args.push_back("-pass");607#endif608args.push_back(p_preset->get_or_env("codesign/password", ENV_WIN_CODESIGN_PASS));609}610611//timestamp612if (p_preset->get("codesign/timestamp")) {613if (p_preset->get("codesign/timestamp_server") != "") {614#ifdef WINDOWS_ENABLED615args.push_back("/tr");616args.push_back(p_preset->get("codesign/timestamp_server_url"));617args.push_back("/td");618if ((int)p_preset->get("codesign/digest_algorithm") == 0) {619args.push_back("sha1");620} else {621args.push_back("sha256");622}623#else624args.push_back("-ts");625args.push_back(p_preset->get("codesign/timestamp_server_url"));626#endif627} else {628add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid timestamp server."));629return FAILED;630}631}632633//digest634#ifdef WINDOWS_ENABLED635args.push_back("/fd");636#else637args.push_back("-h");638#endif639if ((int)p_preset->get("codesign/digest_algorithm") == 0) {640args.push_back("sha1");641} else {642args.push_back("sha256");643}644645//description646if (p_preset->get("codesign/description") != "") {647#ifdef WINDOWS_ENABLED648args.push_back("/d");649#else650args.push_back("-n");651#endif652args.push_back(p_preset->get("codesign/description"));653}654655//user options656PackedStringArray user_args = p_preset->get("codesign/custom_options");657for (int i = 0; i < user_args.size(); i++) {658String user_arg = user_args[i].strip_edges();659if (!user_arg.is_empty()) {660args.push_back(user_arg);661}662}663664#ifndef WINDOWS_ENABLED665args.push_back("-in");666#endif667args.push_back(p_path);668#ifndef WINDOWS_ENABLED669args.push_back("-out");670args.push_back(p_path + "_signed");671#endif672673String str;674Error err = OS::get_singleton()->execute(signtool_path, args, &str, nullptr, true);675if (err != OK || str.contains("not found") || str.contains("not recognized")) {676#ifdef WINDOWS_ENABLED677add_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."));678#else679add_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."));680#endif681return err;682}683684print_line("codesign (" + p_path + "): " + str);685#ifndef WINDOWS_ENABLED686if (str.contains("SignTool Error")) {687#else688if (str.contains("Failed")) {689#endif690add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Signtool failed to sign executable: %s."), str));691return FAILED;692}693694#ifndef WINDOWS_ENABLED695Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());696697err = tmp_dir->remove(p_path);698if (err != OK) {699add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Failed to remove temporary file \"%s\"."), p_path));700return err;701}702703err = tmp_dir->rename(p_path + "_signed", p_path);704if (err != OK) {705add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Failed to rename temporary file \"%s\"."), p_path + "_signed"));706return err;707}708#endif709710return OK;711}712713bool EditorExportPlatformWindows::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {714String err;715bool valid = EditorExportPlatformPC::has_valid_export_configuration(p_preset, err, r_missing_templates, p_debug);716717String custom_debug = p_preset->get("custom_template/debug").operator String().strip_edges();718String custom_release = p_preset->get("custom_template/release").operator String().strip_edges();719String arch = p_preset->get("binary_format/architecture");720721if (!custom_debug.is_empty() && FileAccess::exists(custom_debug)) {722String exe_arch = _get_exe_arch(custom_debug);723if (arch != exe_arch) {724err += vformat(TTR("Mismatching custom debug export template executable architecture: found \"%s\", expected \"%s\"."), exe_arch, arch) + "\n";725}726}727if (!custom_release.is_empty() && FileAccess::exists(custom_release)) {728String exe_arch = _get_exe_arch(custom_release);729if (arch != exe_arch) {730err += vformat(TTR("Mismatching custom release export template executable architecture: found \"%s\", expected \"%s\"."), exe_arch, arch) + "\n";731}732}733734if (!err.is_empty()) {735r_error = err;736}737738return valid;739}740741bool EditorExportPlatformWindows::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {742String err;743bool valid = true;744745List<ExportOption> options;746get_export_options(&options);747for (const EditorExportPlatform::ExportOption &E : options) {748if (get_export_option_visibility(p_preset.ptr(), E.option.name)) {749String warn = get_export_option_warning(p_preset.ptr(), E.option.name);750if (!warn.is_empty()) {751err += warn + "\n";752if (E.required) {753valid = false;754}755}756}757}758759if (!err.is_empty()) {760r_error = err;761}762763return valid;764}765766String EditorExportPlatformWindows::_get_exe_arch(const String &p_path) const {767Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);768if (f.is_null()) {769return "invalid";770}771772// Jump to the PE header and check the magic number.773{774f->seek(0x3c);775uint32_t pe_pos = f->get_32();776777f->seek(pe_pos);778uint32_t magic = f->get_32();779if (magic != 0x00004550) {780return "invalid";781}782}783784// Process header.785uint16_t machine = f->get_16();786f->close();787788switch (machine) {789case 0x014c:790return "x86_32";791case 0x8664:792return "x86_64";793case 0x01c0:794case 0x01c4:795return "arm32";796case 0xaa64:797return "arm64";798default:799return "unknown";800}801}802803Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {804// Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data805806if (p_embedded_size + p_embedded_start >= 0x100000000) { // Check for total executable size807add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Windows executables cannot be >= 4 GiB."));808return ERR_INVALID_DATA;809}810811Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE);812if (f.is_null()) {813add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to open executable file \"%s\"."), p_path));814return ERR_CANT_OPEN;815}816817// Jump to the PE header and check the magic number818{819f->seek(0x3c);820uint32_t pe_pos = f->get_32();821822f->seek(pe_pos);823uint32_t magic = f->get_32();824if (magic != 0x00004550) {825add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable file header corrupted."));826return ERR_FILE_CORRUPT;827}828}829830// Process header831832int num_sections;833{834int64_t header_pos = f->get_position();835836f->seek(header_pos + 2);837num_sections = f->get_16();838f->seek(header_pos + 16);839uint16_t opt_header_size = f->get_16();840841// Skip rest of header + optional header to go to the section headers842f->seek(f->get_position() + 2 + opt_header_size);843}844845// Search for the "pck" section846847int64_t section_table_pos = f->get_position();848849bool found = false;850for (int i = 0; i < num_sections; ++i) {851int64_t section_header_pos = section_table_pos + i * 40;852f->seek(section_header_pos);853854uint8_t section_name[9];855f->get_buffer(section_name, 8);856section_name[8] = '\0';857858if (strcmp((char *)section_name, "pck") == 0) {859// "pck" section found, let's patch!860861// Set virtual size to a little to avoid it taking memory (zero would give issues)862f->seek(section_header_pos + 8);863f->store_32(8);864865f->seek(section_header_pos + 16);866f->store_32(p_embedded_size);867f->seek(section_header_pos + 20);868f->store_32(p_embedded_start);869870found = true;871break;872}873}874875if (!found) {876add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable \"pck\" section not found."));877return ERR_FILE_CORRUPT;878}879return OK;880}881882Ref<Texture2D> EditorExportPlatformWindows::get_run_icon() const {883return run_icon;884}885886bool EditorExportPlatformWindows::poll_export() {887Ref<EditorExportPreset> preset;888889for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {890Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i);891if (ep->is_runnable() && ep->get_platform() == this) {892preset = ep;893break;894}895}896897int prev = menu_options;898menu_options = (preset.is_valid() && preset->get("ssh_remote_deploy/enabled").operator bool());899if (ssh_pid != 0 || !cleanup_commands.is_empty()) {900if (menu_options == 0) {901cleanup();902} else {903menu_options += 1;904}905}906return menu_options != prev;907}908909Ref<Texture2D> EditorExportPlatformWindows::get_option_icon(int p_index) const {910if (p_index == 1) {911return stop_icon;912} else {913return EditorExportPlatform::get_option_icon(p_index);914}915}916917int EditorExportPlatformWindows::get_options_count() const {918return menu_options;919}920921String EditorExportPlatformWindows::get_option_label(int p_index) const {922return (p_index) ? TTR("Stop and uninstall") : TTR("Run on remote Windows system");923}924925String EditorExportPlatformWindows::get_option_tooltip(int p_index) const {926return (p_index) ? TTR("Stop and uninstall running project from the remote system") : TTR("Run exported project on remote Windows system");927}928929void EditorExportPlatformWindows::cleanup() {930if (ssh_pid != 0 && OS::get_singleton()->is_process_running(ssh_pid)) {931print_line("Terminating connection...");932OS::get_singleton()->kill(ssh_pid);933OS::get_singleton()->delay_usec(1000);934}935936if (!cleanup_commands.is_empty()) {937print_line("Stopping and deleting previous version...");938for (const SSHCleanupCommand &cmd : cleanup_commands) {939if (cmd.wait) {940ssh_run_on_remote(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args);941} else {942ssh_run_on_remote_no_wait(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args);943}944}945}946ssh_pid = 0;947cleanup_commands.clear();948}949950Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {951cleanup();952if (p_device) { // Stop command, cleanup only.953return OK;954}955956EditorProgress ep("run", TTR("Running..."), 5);957958const String dest = EditorPaths::get_singleton()->get_temp_dir().path_join("windows");959Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);960if (!da->dir_exists(dest)) {961Error err = da->make_dir_recursive(dest);962if (err != OK) {963EditorNode::get_singleton()->show_warning(TTR("Could not create temp directory:") + "\n" + dest);964return err;965}966}967968String host = p_preset->get("ssh_remote_deploy/host").operator String();969String port = p_preset->get("ssh_remote_deploy/port").operator String();970if (port.is_empty()) {971port = "22";972}973Vector<String> extra_args_ssh = p_preset->get("ssh_remote_deploy/extra_args_ssh").operator String().split(" ", false);974Vector<String> extra_args_scp = p_preset->get("ssh_remote_deploy/extra_args_scp").operator String().split(" ", false);975976const String basepath = dest.path_join("tmp_windows_export");977978#define CLEANUP_AND_RETURN(m_err) \979{ \980if (da->file_exists(basepath + ".zip")) { \981da->remove(basepath + ".zip"); \982} \983if (da->file_exists(basepath + "_start.ps1")) { \984da->remove(basepath + "_start.ps1"); \985} \986if (da->file_exists(basepath + "_clean.ps1")) { \987da->remove(basepath + "_clean.ps1"); \988} \989return m_err; \990} \991((void)0)992993if (ep.step(TTR("Exporting project..."), 1)) {994return ERR_SKIP;995}996Error err = export_project(p_preset, true, basepath + ".zip", p_debug_flags);997if (err != OK) {998DirAccess::remove_file_or_error(basepath + ".zip");999return err;1000}10011002String cmd_args;1003{1004Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);1005for (int i = 0; i < cmd_args_list.size(); i++) {1006if (i != 0) {1007cmd_args += " ";1008}1009cmd_args += cmd_args_list[i];1010}1011}10121013const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);1014int dbg_port = EDITOR_GET("network/debug/remote_port");10151016print_line("Creating temporary directory...");1017ep.step(TTR("Creating temporary directory..."), 2);1018String temp_dir;1019#ifndef WINDOWS_ENABLED1020err = 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);1021#else1022err = 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);1023#endif1024if (err != OK || temp_dir.is_empty()) {1025CLEANUP_AND_RETURN(err);1026}10271028print_line("Uploading archive...");1029ep.step(TTR("Uploading archive..."), 3);1030err = ssh_push_to_remote(host, port, extra_args_scp, basepath + ".zip", temp_dir);1031if (err != OK) {1032CLEANUP_AND_RETURN(err);1033}10341035if (cmd_args.is_empty()) {1036cmd_args = " ";1037}10381039{1040String run_script = p_preset->get("ssh_remote_deploy/run_script");1041run_script = run_script.replace("{temp_dir}", temp_dir);1042run_script = run_script.replace("{archive_name}", basepath.get_file() + ".zip");1043run_script = run_script.replace("{exe_name}", basepath.get_file() + ".exe");1044run_script = run_script.replace("{cmd_args}", cmd_args);10451046Ref<FileAccess> f = FileAccess::open(basepath + "_start.ps1", FileAccess::WRITE);1047if (f.is_null()) {1048CLEANUP_AND_RETURN(err);1049}10501051f->store_string(run_script);1052}10531054{1055String clean_script = p_preset->get("ssh_remote_deploy/cleanup_script");1056clean_script = clean_script.replace("{temp_dir}", temp_dir);1057clean_script = clean_script.replace("{archive_name}", basepath.get_file() + ".zip");1058clean_script = clean_script.replace("{exe_name}", basepath.get_file() + ".exe");1059clean_script = clean_script.replace("{cmd_args}", cmd_args);10601061Ref<FileAccess> f = FileAccess::open(basepath + "_clean.ps1", FileAccess::WRITE);1062if (f.is_null()) {1063CLEANUP_AND_RETURN(err);1064}10651066f->store_string(clean_script);1067}10681069print_line("Uploading scripts...");1070ep.step(TTR("Uploading scripts..."), 4);1071err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_start.ps1", temp_dir);1072if (err != OK) {1073CLEANUP_AND_RETURN(err);1074}1075err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_clean.ps1", temp_dir);1076if (err != OK) {1077CLEANUP_AND_RETURN(err);1078}10791080print_line("Starting project...");1081ep.step(TTR("Starting project..."), 5);1082err = 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);1083if (err != OK) {1084CLEANUP_AND_RETURN(err);1085}10861087cleanup_commands.clear();1088cleanup_commands.push_back(SSHCleanupCommand(host, port, extra_args_ssh, vformat("powershell -file \"%s\\%s\"", temp_dir, basepath.get_file() + "_clean.ps1")));10891090print_line("Project started.");10911092CLEANUP_AND_RETURN(OK);1093#undef CLEANUP_AND_RETURN1094}10951096EditorExportPlatformWindows::EditorExportPlatformWindows() {1097if (EditorNode::get_singleton()) {1098Ref<Image> img = memnew(Image);1099const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE);11001101ImageLoaderSVG::create_image_from_string(img, _windows_logo_svg, EDSCALE, upsample, false);1102set_logo(ImageTexture::create_from_image(img));11031104ImageLoaderSVG::create_image_from_string(img, _windows_run_icon_svg, EDSCALE, upsample, false);1105run_icon = ImageTexture::create_from_image(img);11061107Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();1108if (theme.is_valid()) {1109stop_icon = theme->get_icon(SNAME("Stop"), EditorStringName(EditorIcons));1110} else {1111stop_icon.instantiate();1112}1113}1114}111511161117