From 15458c8e6a4945ab27f9aa4ca31fd1eeef0e2cb7 Mon Sep 17 00:00:00 2001 From: George Marques Date: Sat, 3 Sep 2016 19:43:40 -0300 Subject: [PATCH] Add Windows Universal export to editor - Use OPENSSL_ENABLED definition to the whole source to detect it anywhere. - Add WinRT/UWP template files with manifest and default images. --- drivers/SCsub | 1 + platform/winrt/export/export.cpp | 2391 +++++++++++++++++ platform/winrt/export/export.h | 29 + tools/dist/uwp_template/AppxManifest.xml | 32 + .../Assets/SplashScreen.scale-100.png | Bin 0 -> 14919 bytes .../Assets/Square150x150Logo.scale-100.png | Bin 0 -> 7001 bytes .../Assets/Square310x310Logo.scale-100.png | Bin 0 -> 14490 bytes .../Assets/Square44x44Logo.scale-100.png | Bin 0 -> 2067 bytes .../Assets/Square71x71Logo.scale-100.png | Bin 0 -> 3369 bytes .../Assets/StoreLogo.scale-100.png | Bin 0 -> 2339 bytes .../Assets/Wide310x150Logo.scale-100.png | Bin 0 -> 7390 bytes 11 files changed, 2453 insertions(+) create mode 100644 platform/winrt/export/export.cpp create mode 100644 platform/winrt/export/export.h create mode 100644 tools/dist/uwp_template/AppxManifest.xml create mode 100644 tools/dist/uwp_template/Assets/SplashScreen.scale-100.png create mode 100644 tools/dist/uwp_template/Assets/Square150x150Logo.scale-100.png create mode 100644 tools/dist/uwp_template/Assets/Square310x310Logo.scale-100.png create mode 100644 tools/dist/uwp_template/Assets/Square44x44Logo.scale-100.png create mode 100644 tools/dist/uwp_template/Assets/Square71x71Logo.scale-100.png create mode 100644 tools/dist/uwp_template/Assets/StoreLogo.scale-100.png create mode 100644 tools/dist/uwp_template/Assets/Wide310x150Logo.scale-100.png diff --git a/drivers/SCsub b/drivers/SCsub index 79cbe506855..8243483e176 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -17,6 +17,7 @@ SConscript('gl_context/SCsub'); SConscript('pnm/SCsub'); if (env['openssl']!='no'): + env.Append(CPPFLAGS=['-DOPENSSL_ENABLED']); env_drivers.Append(CPPFLAGS=['-DOPENSSL_ENABLED']); if (env['openssl']=="builtin"): env_drivers.Append(CPPPATH=['#drivers/builtin_openssl2']) diff --git a/platform/winrt/export/export.cpp b/platform/winrt/export/export.cpp new file mode 100644 index 00000000000..ed294b438fc --- /dev/null +++ b/platform/winrt/export/export.cpp @@ -0,0 +1,2391 @@ +/*************************************************************************/ +/* export.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/************************************************************************* + * The code for signing the package was ported from fb-util-for-appx + * available at https://github.com/facebook/fb-util-for-appx + * and distributed also under the following license: + +BSD License + +For fb-util-for-appx software + +Copyright (c) 2016, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*************************************************************************/ + + +#include "version.h" +#include "export.h" +#include "object.h" +#include "tools/editor/editor_import_export.h" +#include "tools/editor/editor_node.h" +#include "platform/winrt/logo.h" +#include "os/file_access.h" +#include "io/zip.h" +#include "io/unzip.h" +#include "io/zip_io.h" +#include "io/sha256.h" +#include "io/base64.h" +#include "bind/core_bind.h" +#include "globals.h" +#include "io/marshalls.h" + +#include + +#ifdef OPENSSL_ENABLED +#include +#include +#include +#include +#include +#include +#include +#include + +// Capabilities +static const char* uwp_capabilities[] = { + "allJoyn", + "codeGeneration", + "internetClient", + "internetClientServer", + "privateNetworkClientServer", + NULL +}; +static const char* uwp_uap_capabilities[] = { + "appointments", + "blockedChatMessages", + "chat", + "contacts", + "enterpriseAuthentication", + "musicLibrary", + "objects3D", + "picturesLibrary", + "phoneCall", + "removableStorage", + "sharedUserCertificates", + "userAccountInformation", + "videosLibrary", + "voipCall", + NULL +}; +static const char* uwp_device_capabilites[] = { + "bluetooth", + "location", + "microphone", + "proximity", + "webcam", + NULL +}; + +namespace asn1 { + // https://msdn.microsoft.com/en-us/gg463180.aspx + + struct SPCStatementType { + ASN1_OBJECT *type; + }; + DECLARE_ASN1_FUNCTIONS(SPCStatementType) + + struct SPCSpOpusInfo { + ASN1_TYPE *programName; + ASN1_TYPE *moreInfo; + }; + DECLARE_ASN1_FUNCTIONS(SPCSpOpusInfo) + + struct DigestInfo { + X509_ALGOR *digestAlgorithm; + ASN1_OCTET_STRING *digest; + }; + DECLARE_ASN1_FUNCTIONS(DigestInfo) + + struct SPCAttributeTypeAndOptionalValue { + ASN1_OBJECT *type; + ASN1_TYPE *value; // SPCInfoValue + }; + DECLARE_ASN1_FUNCTIONS(SPCAttributeTypeAndOptionalValue) + + // Undocumented. + struct SPCInfoValue { + ASN1_INTEGER *i1; + ASN1_OCTET_STRING *s1; + ASN1_INTEGER *i2; + ASN1_INTEGER *i3; + ASN1_INTEGER *i4; + ASN1_INTEGER *i5; + ASN1_INTEGER *i6; + }; + DECLARE_ASN1_FUNCTIONS(SPCInfoValue) + + struct SPCIndirectDataContent { + SPCAttributeTypeAndOptionalValue *data; + DigestInfo *messageDigest; + }; + DECLARE_ASN1_FUNCTIONS(SPCIndirectDataContent) + + IMPLEMENT_ASN1_FUNCTIONS(SPCIndirectDataContent) + ASN1_SEQUENCE(SPCIndirectDataContent) = { + ASN1_SIMPLE(SPCIndirectDataContent, data, + SPCAttributeTypeAndOptionalValue), + ASN1_SIMPLE(SPCIndirectDataContent, messageDigest, DigestInfo), + } ASN1_SEQUENCE_END(SPCIndirectDataContent) + + IMPLEMENT_ASN1_FUNCTIONS(SPCAttributeTypeAndOptionalValue) + ASN1_SEQUENCE(SPCAttributeTypeAndOptionalValue) = { + ASN1_SIMPLE(SPCAttributeTypeAndOptionalValue, type, + ASN1_OBJECT), + ASN1_OPT(SPCAttributeTypeAndOptionalValue, value, ASN1_ANY), + } ASN1_SEQUENCE_END(SPCAttributeTypeAndOptionalValue) + + IMPLEMENT_ASN1_FUNCTIONS(SPCInfoValue) + ASN1_SEQUENCE(SPCInfoValue) = { + ASN1_SIMPLE(SPCInfoValue, i1, ASN1_INTEGER), + ASN1_SIMPLE(SPCInfoValue, s1, ASN1_OCTET_STRING), + ASN1_SIMPLE(SPCInfoValue, i2, ASN1_INTEGER), + ASN1_SIMPLE(SPCInfoValue, i3, ASN1_INTEGER), + ASN1_SIMPLE(SPCInfoValue, i4, ASN1_INTEGER), + ASN1_SIMPLE(SPCInfoValue, i5, ASN1_INTEGER), + ASN1_SIMPLE(SPCInfoValue, i6, ASN1_INTEGER), + } ASN1_SEQUENCE_END(SPCInfoValue) + + IMPLEMENT_ASN1_FUNCTIONS(DigestInfo) + ASN1_SEQUENCE(DigestInfo) = { + ASN1_SIMPLE(DigestInfo, digestAlgorithm, X509_ALGOR), + ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING), + } ASN1_SEQUENCE_END(DigestInfo) + + ASN1_SEQUENCE(SPCSpOpusInfo) = { + ASN1_OPT(SPCSpOpusInfo, programName, ASN1_ANY), + ASN1_OPT(SPCSpOpusInfo, moreInfo, ASN1_ANY), + } ASN1_SEQUENCE_END(SPCSpOpusInfo) + IMPLEMENT_ASN1_FUNCTIONS(SPCSpOpusInfo) + + ASN1_SEQUENCE(SPCStatementType) = { + ASN1_SIMPLE(SPCStatementType, type, ASN1_OBJECT), + } ASN1_SEQUENCE_END(SPCStatementType) + IMPLEMENT_ASN1_FUNCTIONS(SPCStatementType) +} + +class EncodedASN1 { + + uint8_t* i_data; + size_t i_size; + + EncodedASN1(uint8_t** p_data, size_t p_size) { + + i_data = *p_data; + i_size = p_size; + } + +public: + + template + static EncodedASN1 FromItem(T *item) { + uint8_t *dataRaw = NULL; + int size = TEncode(item, &dataRaw); + + return EncodedASN1(&dataRaw, size); + } + + const uint8_t *data() const { + return i_data; + } + + size_t size() const { + return i_size; + } + + // Assumes the encoded ASN.1 represents a SEQUENCE and puts it into + // an ASN1_STRING. + // + // The returned object holds a copy of this object's data. + ASN1_STRING* ToSequenceString() { + ASN1_STRING* string = ASN1_STRING_new(); + if (!string) { + return NULL; + } + if (!ASN1_STRING_set(string, i_data, i_size)) { + return NULL; + } + return string; + } + + // Assumes the encoded ASN.1 represents a SEQUENCE and puts it into + // an ASN1_TYPE. + // + // The returned object holds a copy of this object's data. + ASN1_TYPE* ToSequenceType() { + ASN1_STRING* string = ToSequenceString(); + ASN1_TYPE* type = ASN1_TYPE_new(); + if (!type) { + return NULL; + } + type->type = V_ASN1_SEQUENCE; + type->value.sequence = string; + return type; + } + +}; + +#endif // OPENSSL_ENABLED + +class AppxPackager { + + enum { + FILE_HEADER_MAGIC = 0x04034b50, + DATA_DESCRIPTOR_MAGIC = 0x08074b50, + CENTRAL_DIR_MAGIC = 0x02014b50, + END_OF_CENTRAL_DIR_MAGIC = 0x06054b50, + ZIP64_END_OF_CENTRAL_DIR_MAGIC = 0x06064b50, + ZIP64_END_DIR_LOCATOR_MAGIC = 0x07064b50, + P7X_SIGNATURE = 0x58434b50, + ZIP64_HEADER_ID = 0x0001, + ZIP_VERSION = 20, + ZIP_ARCHIVE_VERSION = 45, + GENERAL_PURPOSE = 0x00, + BASE_FILE_HEADER_SIZE = 30, + DATA_DESCRIPTOR_SIZE = 24, + BASE_CENTRAL_DIR_SIZE = 46, + EXTRA_FIELD_LENGTH = 28, + ZIP64_HEADER_SIZE = 24, + ZIP64_END_OF_CENTRAL_DIR_SIZE = (56 - 12), + END_OF_CENTRAL_DIR_SIZE = 42, + BLOCK_SIZE = 65536, + }; + + struct BlockHash { + + String base64_hash; + size_t compressed_size; + }; + + struct FileMeta { + + String name; + int lfh_size; + bool compressed; + size_t compressed_size; + size_t uncompressed_size; + Vector hashes; + uLong file_crc32; + ZPOS64_T zip_offset; + }; + + String progress_task; + FileAccess *package; + String tmp_blockmap_file_path; + String tmp_content_types_file_path; + + Set mime_types; + + Vector file_metadata; + + ZPOS64_T central_dir_offset; + ZPOS64_T end_of_central_dir_offset; + Vector central_dir_data; + + String hash_block(uint8_t* p_block_data, size_t p_block_len); + + void make_block_map(); + void make_content_types(); + + + _FORCE_INLINE_ unsigned int buf_put_int16(uint16_t p_val, uint8_t * p_buf) { + for (int i = 0; i < 2; i++) { + *p_buf++ = (p_val >> (i * 8)) & 0xFF; + } + return 2; + } + + _FORCE_INLINE_ unsigned int buf_put_int32(uint32_t p_val, uint8_t * p_buf) { + for (int i = 0; i < 4; i++) { + *p_buf++ = (p_val >> (i * 8)) & 0xFF; + } + return 4; + } + + _FORCE_INLINE_ unsigned int buf_put_int64(uint64_t p_val, uint8_t * p_buf) { + for (int i = 0; i < 8; i++) { + *p_buf++ = (p_val >> (i * 8)) & 0xFF; + } + return 8; + } + + _FORCE_INLINE_ unsigned int buf_put_string(String p_val, uint8_t * p_buf) { + for (int i = 0; i < p_val.length(); i++) { + *p_buf++ = p_val.utf8().get(i); + } + return p_val.length(); + } + + Vector make_file_header(FileMeta p_file_meta); + void store_central_dir_header(const FileMeta p_file, bool p_do_hash = true); + Vector make_end_of_central_record(); + + String content_type(String p_extension); + +#ifdef OPENSSL_ENABLED + + // Signing methods and structs: + + String certificate_path; + String certificate_pass; + bool sign_package; + + struct CertFile { + + EVP_PKEY* private_key; + X509* certificate; + }; + + SHA256_CTX axpc_context; // SHA256 context for ZIP file entries + SHA256_CTX axcd_context; // SHA256 context for ZIP directory entries + + struct AppxDigests { + + uint8_t axpc[SHA256_DIGEST_LENGTH]; // ZIP file entries + uint8_t axcd[SHA256_DIGEST_LENGTH]; // ZIP directory entry + uint8_t axct[SHA256_DIGEST_LENGTH]; // Content types XML + uint8_t axbm[SHA256_DIGEST_LENGTH]; // Block map XML + uint8_t axci[SHA256_DIGEST_LENGTH]; // Code Integrity file (optional) + }; + + CertFile cert_file; + AppxDigests digests; + + void MakeSPCInfoValue(asn1::SPCInfoValue &info); + Error MakeIndirectDataContent(asn1::SPCIndirectDataContent &idc); + Error add_attributes(PKCS7_SIGNER_INFO *signerInfo); + void make_digests(); + void write_digest(Vector &p_out_buffer); + + Error openssl_error(unsigned long p_err); + Error read_cert_file(const String &p_path, const String &p_password, CertFile* p_out_cf); + Error sign(const CertFile &p_cert, const AppxDigests &digests, PKCS7* p_out_signature); + +#endif // OPENSSL_ENABLED + +public: + + enum SignOption { + + SIGN, + DONT_SIGN, + }; + + void set_progress_task(String p_task) { progress_task = p_task; } + void init(FileAccess* p_fa, SignOption p_sign, String &p_certificate_path, String &p_certificate_password); + void add_file(String p_file_name, const uint8_t* p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress = false); + void finish(); + + AppxPackager(); + ~AppxPackager(); +}; + +class EditorExportPlatformWinrt : public EditorExportPlatform { + + OBJ_TYPE(EditorExportPlatformWinrt, EditorExportPlatform); + + Ref logo; + + enum Platform { + ARM, + X86, + X64 + } arch; + + bool is_debug; + + String custom_release_package; + String custom_debug_package; + + String cmdline; + + String display_name; + String short_name; + String unique_name; + String description; + String publisher; + String publisher_display_name; + + String product_guid; + String publisher_guid; + + int version_major; + int version_minor; + int version_build; + int version_revision; + + bool orientation_landscape; + bool orientation_portrait; + bool orientation_landscape_flipped; + bool orientation_portrait_flipped; + + String background_color; + Ref store_logo; + Ref square44; + Ref square71; + Ref square150; + Ref square310; + Ref wide310; + Ref splash; + + bool name_on_square150; + bool name_on_square310; + bool name_on_wide; + + Set capabilities; + Set uap_capabilities; + Set device_capabilities; + + bool sign_package; + String certificate_path; + String certificate_pass; + + _FORCE_INLINE_ bool array_has(const char** p_array, const char* p_value) const { + while (*p_array) { + if (String(*p_array) == String(p_value)) return true; + p_array++; + } + return false; + } + + bool _valid_resource_name(const String &p_name) const; + bool _valid_guid(const String &p_guid) const; + bool _valid_bgcolor(const String &p_color) const; + bool _valid_image(const Ref p_image, int p_width, int p_height) const; + + Vector _fix_manifest(const Vector &p_template, bool p_give_internet) const; + Vector _get_image_data(const String &p_path); + + static Error save_appx_file(void *p_userdata, const String& p_path, const Vector& p_data, int p_file, int p_total); + static bool _should_compress_asset(const String& p_path, const Vector& p_data); + +protected: + + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + +public: + + virtual String get_name() const { return "Windows Universal"; } + virtual ImageCompression get_image_compression() const { return IMAGE_COMPRESSION_ETC1; } + virtual Ref get_logo() const { return logo; } + + virtual bool can_export(String *r_error = NULL) const; + virtual String get_binary_extension() const { return "appx"; } + + virtual Error export_project(const String& p_path, bool p_debug, int p_flags = 0); + + EditorExportPlatformWinrt(); + ~EditorExportPlatformWinrt(); +}; + + +/////////////////////////////////////////////////////////////////////////// + +String AppxPackager::hash_block(uint8_t * p_block_data, size_t p_block_len) { + + char hash[32]; + char base64[45]; + + sha256_context ctx; + sha256_init(&ctx); + sha256_hash(&ctx, p_block_data, p_block_len); + sha256_done(&ctx, (uint8_t*)hash); + + base64_encode(base64, hash, 32); + base64[44] = '\0'; + + return String(base64); +} + +void AppxPackager::make_block_map() { + + FileAccess* tmp_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::WRITE); + + tmp_file->store_string(""); + tmp_file->store_string(""); + + for (int i = 0; i < file_metadata.size(); i++) { + + FileMeta file = file_metadata[i]; + + tmp_file->store_string( + ""); + + + for (int j = 0; j < file.hashes.size(); j++) { + + tmp_file->store_string("store_string("Size=\"" + itos(file.hashes[j].compressed_size) + "\" "); + tmp_file->store_string("/>"); + } + + tmp_file->store_string(""); + } + + tmp_file->store_string(""); + + tmp_file->close(); + memdelete(tmp_file); + tmp_file = NULL; +} + +String AppxPackager::content_type(String p_extension) { + + if (p_extension == "png") + return "image/png"; + else if (p_extension == "jpg") + return "image/jpg"; + else if (p_extension == "xml") + return "application/xml"; + else if (p_extension == "exe" || p_extension == "dll") + return "application/x-msdownload"; + else + return "application/octet-stream"; +} + +void AppxPackager::make_content_types() { + + FileAccess* tmp_file = FileAccess::open(tmp_content_types_file_path, FileAccess::WRITE); + + tmp_file->store_string(""); + tmp_file->store_string(""); + + Map types; + + for (int i = 0; i < file_metadata.size(); i++) { + + String ext = file_metadata[i].name.extension(); + + if (types.has(ext)) continue; + + types[ext] = content_type(ext); + + tmp_file->store_string(""); + } + + // Appx signature file + tmp_file->store_string(""); + + // Override for package files + tmp_file->store_string(""); + tmp_file->store_string(""); + tmp_file->store_string(""); + tmp_file->store_string(""); + + tmp_file->store_string(""); + + tmp_file->close(); + memdelete(tmp_file); + tmp_file = NULL; +} + +Vector AppxPackager::make_file_header(FileMeta p_file_meta) { + + Vector buf; + buf.resize(BASE_FILE_HEADER_SIZE + p_file_meta.name.length()); + + int offs = 0; + // Write magic + offs += buf_put_int32(FILE_HEADER_MAGIC, &buf[offs]); + + // Version + offs += buf_put_int16(ZIP_VERSION, &buf[offs]); + + // Special flag + offs += buf_put_int16(GENERAL_PURPOSE, &buf[offs]); + + // Compression + offs += buf_put_int16(p_file_meta.compressed ? Z_DEFLATED : 0, &buf[offs]); + + // File date and time + offs += buf_put_int32(0, &buf[offs]); + + // CRC-32 + offs += buf_put_int32(p_file_meta.file_crc32, &buf[offs]); + + // Compressed size + offs += buf_put_int32(p_file_meta.compressed_size, &buf[offs]); + + // Uncompressed size + offs += buf_put_int32(p_file_meta.uncompressed_size, &buf[offs]); + + // File name length + offs += buf_put_int16(p_file_meta.name.length(), &buf[offs]); + + // Extra data length + offs += buf_put_int16(0, &buf[offs]); + + // File name + offs += buf_put_string(p_file_meta.name, &buf[offs]); + + // Done! + return buf; +} + +void AppxPackager::store_central_dir_header(const FileMeta p_file, bool p_do_hash) { + + Vector &buf = central_dir_data; + int offs = buf.size(); + buf.resize(buf.size() + BASE_CENTRAL_DIR_SIZE + p_file.name.length()); + + + // Write magic + offs += buf_put_int32(CENTRAL_DIR_MAGIC, &buf[offs]); + + // ZIP versions + offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf[offs]); + offs += buf_put_int16(ZIP_VERSION, &buf[offs]); + + // General purpose flag + offs += buf_put_int16(GENERAL_PURPOSE, &buf[offs]); + + // Compression + offs += buf_put_int16(p_file.compressed ? Z_DEFLATED : 0, &buf[offs]); + + // Modification date/time + offs += buf_put_int32(0, &buf[offs]); + + // Crc-32 + offs += buf_put_int32(p_file.file_crc32, &buf[offs]); + + // File sizes + offs += buf_put_int32(p_file.compressed_size, &buf[offs]); + offs += buf_put_int32(p_file.uncompressed_size, &buf[offs]); + + // File name length + offs += buf_put_int16(p_file.name.length(), &buf[offs]); + + // Extra field length + offs += buf_put_int16(0, &buf[offs]); + + // Comment length + offs += buf_put_int16(0, &buf[offs]); + + // Disk number start, internal/external file attributes + for (int i = 0; i < 8; i++) { + buf[offs++] = 0; + } + + // Relative offset + offs += buf_put_int32(p_file.zip_offset, &buf[offs]); + + // File name + offs += buf_put_string(p_file.name, &buf[offs]); + +#ifdef OPENSSL_ENABLED + // Calculate the hash for signing + if (p_do_hash) + SHA256_Update(&axcd_context, buf.ptr(), buf.size()); +#endif // OPENSSL_ENABLED + + // Done! +} + +Vector AppxPackager::make_end_of_central_record() { + + Vector buf; + buf.resize(ZIP64_END_OF_CENTRAL_DIR_SIZE + 12 + END_OF_CENTRAL_DIR_SIZE); // Size plus magic + + int offs = 0; + + // Write magic + offs += buf_put_int32(ZIP64_END_OF_CENTRAL_DIR_MAGIC, &buf[offs]); + + // Size of this record + offs += buf_put_int64(ZIP64_END_OF_CENTRAL_DIR_SIZE, &buf[offs]); + + // Version (yes, twice) + offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf[offs]); + offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf[offs]); + + // Disk number + for (int i = 0; i < 8; i++) { + buf[offs++] = 0; + } + + // Number of entries (total and per disk) + offs += buf_put_int64(file_metadata.size(), &buf[offs]); + offs += buf_put_int64(file_metadata.size(), &buf[offs]); + + // Size of central dir + offs += buf_put_int64(central_dir_data.size(), &buf[offs]); + + // Central dir offset + offs += buf_put_int64(central_dir_offset, &buf[offs]); + + ////// ZIP64 locator + + // Write magic for zip64 central dir locator + offs += buf_put_int32(ZIP64_END_DIR_LOCATOR_MAGIC, &buf[offs]); + + // Disk number + for (int i = 0; i < 4; i++) { + buf[offs++] = 0; + } + + // Relative offset + offs += buf_put_int64(end_of_central_dir_offset, &buf[offs]); + + // Number of disks + offs += buf_put_int32(1, &buf[offs]); + + /////// End of zip directory + + // Write magic for end central dir + offs += buf_put_int32(END_OF_CENTRAL_DIR_MAGIC, &buf[offs]); + + // Dummy stuff for Zip64 + for (int i = 0; i < 4; i++) { + buf[offs++] = 0x0; + } + for (int i = 0; i < 12; i++) { + buf[offs++] = 0xFF; + } + + // Size of comments + for (int i = 0; i < 2; i++) { + buf[offs++] = 0; + } + + // Done! + return buf; +} + +void AppxPackager::init(FileAccess * p_fa, SignOption p_sign, String &p_certificate_path, String &p_certificate_password) { + + package = p_fa; + central_dir_offset = 0; + end_of_central_dir_offset = 0; + tmp_blockmap_file_path = EditorSettings::get_singleton()->get_settings_path() + "/tmp/tmpblockmap.xml"; + tmp_content_types_file_path = EditorSettings::get_singleton()->get_settings_path() + "/tmp/tmpcontenttypes.xml"; +#ifdef OPENSSL_ENABLED + certificate_path = p_certificate_path; + certificate_pass = p_certificate_password; + sign_package = p_sign == SIGN; + SHA256_Init(&axpc_context); + SHA256_Init(&axcd_context); +#endif // OPENSSL_ENABLED +} + +void AppxPackager::add_file(String p_file_name, const uint8_t * p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) { + + if (p_file_no >= 1 && p_total_files >= 1) { + EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files); + } + + bool do_hash = p_file_name != "AppxSignature.p7x"; + + FileMeta meta; + meta.name = p_file_name; + meta.uncompressed_size = p_len; + meta.compressed_size = p_len; + meta.compressed = p_compress; + meta.zip_offset = package->get_pos(); + + Vector file_buffer; + + // Data for compression + z_stream strm; + FileAccess* strm_f = NULL; + Vector strm_in; + strm_in.resize(BLOCK_SIZE); + Vector strm_out; + + if (p_compress) { + + strm.zalloc = zipio_alloc; + strm.zfree = zipio_free; + strm.opaque = &strm_f; + + strm_out.resize(BLOCK_SIZE + 8); + + deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + } + + int step = 0; + + while (p_len - step > 0) { + + size_t block_size = (p_len - step) > BLOCK_SIZE ? BLOCK_SIZE : (p_len - step); + + for (int i = 0; i < block_size; i++) { + strm_in[i] = p_buffer[step + i]; + } + + BlockHash bh; + bh.base64_hash = hash_block(strm_in.ptr(), block_size); + + if (p_compress) { + + strm.avail_in = block_size; + strm.avail_out = strm_out.size(); + strm.next_in = strm_in.ptr(); + strm.next_out = strm_out.ptr(); + + int total_out_before = strm.total_out; + + deflate(&strm, Z_FULL_FLUSH); + bh.compressed_size = strm.total_out - total_out_before; + + //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before); + int start = file_buffer.size(); + file_buffer.resize(file_buffer.size() + bh.compressed_size); + for (int i = 0; i < bh.compressed_size; i++) + file_buffer[start + i] = strm_out[i]; +#ifdef OPENSSL_ENABLED + if (do_hash) + SHA256_Update(&axpc_context, strm_out.ptr(), strm.total_out - total_out_before); +#endif // OPENSSL_ENABLED + + } else { + bh.compressed_size = block_size; + //package->store_buffer(strm_in.ptr(), block_size); + int start = file_buffer.size(); + file_buffer.resize(file_buffer.size() + block_size); + for (int i = 0; i < bh.compressed_size; i++) + file_buffer[start + i] = strm_in[i]; +#ifdef OPENSSL_ENABLED + if (do_hash) + SHA256_Update(&axpc_context, strm_in.ptr(), block_size); +#endif // OPENSSL_ENABLED + } + + meta.hashes.push_back(bh); + + step += block_size; + } + + if (p_compress) { + + strm.avail_in = 0; + strm.avail_out = strm_out.size(); + strm.next_in = strm_in.ptr(); + strm.next_out = strm_out.ptr(); + + int total_out_before = strm.total_out; + + deflate(&strm, Z_FINISH); + + //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before); + int start = file_buffer.size(); + file_buffer.resize(file_buffer.size() + (strm.total_out - total_out_before)); + for (int i = 0; i < (strm.total_out - total_out_before); i++) + file_buffer[start + i] = strm_out[i]; +#ifdef OPENSSL_ENABLED + if (do_hash) + SHA256_Update(&axpc_context, strm_out.ptr(), strm.total_out - total_out_before); +#endif // OPENSSL_ENABLED + + deflateEnd(&strm); + meta.compressed_size = strm.total_out; + + } else { + + meta.compressed_size = p_len; + } + + // Calculate file CRC-32 + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, p_buffer, p_len); + meta.file_crc32 = crc; + + // Create file header + Vector file_header = make_file_header(meta); + meta.lfh_size = file_header.size(); + +#ifdef OPENSSL_ENABLED + // Hash the data for signing + if (do_hash) { + SHA256_Update(&axpc_context, file_header.ptr(), file_header.size()); + SHA256_Update(&axpc_context, file_buffer.ptr(), file_buffer.size()); + } +#endif // OPENSSL_ENABLED + + // Store the header and file; + package->store_buffer(file_header.ptr(), file_header.size()); + package->store_buffer(file_buffer.ptr(), file_buffer.size()); + + file_metadata.push_back(meta); +} + +void AppxPackager::finish() { + + // Create and add block map file + EditorNode::progress_task_step("export", "Creating block map...", 4); + + make_block_map(); + FileAccess* blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ); + Vector blockmap_buffer; + blockmap_buffer.resize(blockmap_file->get_len()); + + blockmap_file->get_buffer(blockmap_buffer.ptr(), blockmap_buffer.size()); + +#ifdef OPENSSL_ENABLED + // Hash the file for signing + if (sign_package) { + SHA256_CTX axbm_context; + SHA256_Init(&axbm_context); + SHA256_Update(&axbm_context, blockmap_buffer.ptr(), blockmap_buffer.size()); + SHA256_Final(digests.axbm, &axbm_context); + } +#endif // OPENSSL_ENABLED + + add_file("AppxBlockMap.xml", blockmap_buffer.ptr(), blockmap_buffer.size(), -1, -1, true); + + blockmap_file->close(); + memdelete(blockmap_file); + blockmap_file = NULL; + + // Add content types + EditorNode::progress_task_step("export", "Setting content types...", 5); + make_content_types(); + + FileAccess* types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ); + Vector types_buffer; + types_buffer.resize(types_file->get_len()); + + types_file->get_buffer(types_buffer.ptr(), types_buffer.size()); + +#ifdef OPENSSL_ENABLED + if (sign_package) { + // Hash the file for signing + SHA256_CTX axct_context; + SHA256_Init(&axct_context); + SHA256_Update(&axct_context, types_buffer.ptr(), types_buffer.size()); + SHA256_Final(digests.axct, &axct_context); + } +#endif // OPENSSL_ENABLED + + add_file("[Content_Types].xml", types_buffer.ptr(), types_buffer.size(), -1, -1, true); + + types_file->close(); + memdelete(types_file); + types_file = NULL; + + // Pre-process central directory before signing + for (int i = 0; i < file_metadata.size(); i++) { + store_central_dir_header(file_metadata[i]); + } + +#ifdef OPENSSL_ENABLED + // Create the signature file + if (sign_package) { + + Error err = read_cert_file(certificate_path, certificate_pass, &cert_file); + + if (err != OK) { + EditorNode::add_io_error(TTR("Couldn't read the certficate file. Are the path and password both correct?")); + package->close(); + memdelete(package); + package = NULL; + return; + } + + + // Make a temp end of the zip for hashing + central_dir_offset = package->get_pos(); + end_of_central_dir_offset = central_dir_offset + central_dir_data.size(); + Vector zip_end_dir = make_end_of_central_record(); + + // Hash the end directory + SHA256_Update(&axcd_context, zip_end_dir.ptr(), zip_end_dir.size()); + + // Finish the hashes + make_digests(); + + PKCS7* signature = PKCS7_new(); + if (!signature) { + EditorNode::add_io_error(TTR("Error creating the signature object.")); + package->close(); + memdelete(package); + package = NULL; + return; + } + + err = sign(cert_file, digests, signature); + + if (err != OK) { + EditorNode::add_io_error(TTR("Error creating the package signature.")); + package->close(); + memdelete(package); + package = NULL; + return; + } + + // Read the signature as bytes + BIO* bio_out = BIO_new(BIO_s_mem()); + i2d_PKCS7_bio(bio_out, signature); + + BIO_flush(bio_out); + + uint8_t* bio_ptr; + size_t bio_size = BIO_get_mem_data(bio_out, &bio_ptr); + + // Create the signature buffer with magic number + Vector signature_file; + signature_file.resize(4 + bio_size); + buf_put_int32(P7X_SIGNATURE, signature_file.ptr()); + for (int i = 0; i < bio_size; i++) + signature_file[i + 4] = bio_ptr[i]; + + // Add the signature to the package + add_file("AppxSignature.p7x", signature_file.ptr(), signature_file.size(), -1, -1, true); + + // Add central directory entry + store_central_dir_header(file_metadata[file_metadata.size() - 1], false); + } +#endif // OPENSSL_ENABLED + + + // Write central directory + EditorNode::progress_task_step("export", "Finishing package...", 6); + central_dir_offset = package->get_pos(); + package->store_buffer(central_dir_data.ptr(), central_dir_data.size()); + + // End record + end_of_central_dir_offset = package->get_pos(); + Vector end_record = make_end_of_central_record(); + package->store_buffer(end_record.ptr(), end_record.size()); + + package->close(); + memdelete(package); + package = NULL; +} + +#ifdef OPENSSL_ENABLED +// https://support.microsoft.com/en-us/kb/287547 +const char SPC_INDIRECT_DATA_OBJID[] = "1.3.6.1.4.1.311.2.1.4"; +const char SPC_STATEMENT_TYPE_OBJID[] = "1.3.6.1.4.1.311.2.1.11"; +const char SPC_SP_OPUS_INFO_OBJID[] = "1.3.6.1.4.1.311.2.1.12"; +const char SPC_SIPINFO_OBJID[] = "1.3.6.1.4.1.311.2.1.30"; +#endif // OPENSSL_ENABLED + +AppxPackager::AppxPackager() {} + +AppxPackager::~AppxPackager() {} + + +//////////////////////////////////////////////////////////////////// + +#ifdef OPENSSL_ENABLED +Error AppxPackager::openssl_error(unsigned long p_err) { + + ERR_load_crypto_strings(); + + char buffer[256]; + ERR_error_string_n(p_err, buffer, sizeof(buffer)); + + String err(buffer); + + ERR_EXPLAIN(err); + ERR_FAIL_V(FAILED); +} + +void AppxPackager::MakeSPCInfoValue(asn1::SPCInfoValue &info) { + + // I have no idea what these numbers mean. + static uint8_t s1Magic[] = { + 0x4B, 0xDF, 0xC5, 0x0A, 0x07, 0xCE, 0xE2, 0x4D, + 0xB7, 0x6E, 0x23, 0xC8, 0x39, 0xA0, 0x9F, 0xD1, + }; + ASN1_INTEGER_set(info.i1, 0x01010000); + ASN1_OCTET_STRING_set(info.s1, s1Magic, sizeof(s1Magic)); + ASN1_INTEGER_set(info.i2, 0x00000000); + ASN1_INTEGER_set(info.i3, 0x00000000); + ASN1_INTEGER_set(info.i4, 0x00000000); + ASN1_INTEGER_set(info.i5, 0x00000000); + ASN1_INTEGER_set(info.i6, 0x00000000); +} + +Error AppxPackager::MakeIndirectDataContent(asn1::SPCIndirectDataContent &idc) { + + using namespace asn1; + + ASN1_TYPE* algorithmParameter = ASN1_TYPE_new(); + if (!algorithmParameter) { + return openssl_error(ERR_peek_last_error()); + } + algorithmParameter->type = V_ASN1_NULL; + + SPCInfoValue* infoValue = SPCInfoValue_new(); + if (!infoValue) { + return openssl_error(ERR_peek_last_error()); + } + MakeSPCInfoValue(*infoValue); + + ASN1_TYPE* value = + EncodedASN1::FromItem(infoValue) + .ToSequenceType(); + + { + Vector digest; + write_digest(digest); + if (!ASN1_OCTET_STRING_set(idc.messageDigest->digest, + digest.ptr(), digest.size())) { + + return openssl_error(ERR_peek_last_error()); + } + } + + idc.data->type = OBJ_txt2obj(SPC_SIPINFO_OBJID, 1); + idc.data->value = value; + idc.messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(NID_sha256); + idc.messageDigest->digestAlgorithm->parameter = algorithmParameter; + + return OK; +} + +Error AppxPackager::add_attributes(PKCS7_SIGNER_INFO * p_signer_info) { + + // Add opus attribute + asn1::SPCSpOpusInfo* opus = asn1::SPCSpOpusInfo_new(); + if (!opus) return openssl_error(ERR_peek_last_error()); + + ASN1_STRING* opus_value = EncodedASN1::FromItem(opus) + .ToSequenceString(); + + if (!PKCS7_add_signed_attribute( + p_signer_info, + OBJ_txt2nid(SPC_SP_OPUS_INFO_OBJID), + V_ASN1_SEQUENCE, + opus_value + )) { + + asn1::SPCSpOpusInfo_free(opus); + + ASN1_STRING_free(opus_value); + return openssl_error(ERR_peek_last_error()); + } + + // Add content type attribute + if (!PKCS7_add_signed_attribute( + p_signer_info, + NID_pkcs9_contentType, + V_ASN1_OBJECT, + OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1) + )) { + + asn1::SPCSpOpusInfo_free(opus); + ASN1_STRING_free(opus_value); + return openssl_error(ERR_peek_last_error()); + } + + // Add statement type attribute + asn1::SPCStatementType* statement_type = asn1::SPCStatementType_new(); + if (!statement_type) return openssl_error(ERR_peek_last_error()); + + statement_type->type = OBJ_nid2obj(NID_ms_code_ind); + ASN1_STRING* statement_type_value = + EncodedASN1::FromItem(statement_type) + .ToSequenceString(); + + if (!PKCS7_add_signed_attribute( + p_signer_info, + OBJ_txt2nid(SPC_STATEMENT_TYPE_OBJID), + V_ASN1_SEQUENCE, + statement_type_value + )) { + + ASN1_STRING_free(opus_value); + asn1::SPCStatementType_free(statement_type); + ASN1_STRING_free(statement_type_value); + + return openssl_error(ERR_peek_last_error()); + } + + return OK; + +} + +void AppxPackager::make_digests() { + + // AXPC + SHA256_Final(digests.axpc, &axpc_context); + + // AXCD + SHA256_Final(digests.axcd, &axcd_context); + + // AXCI + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) + digests.axci[i] = 0; + +} + +void AppxPackager::write_digest(Vector& p_out_buffer) { + + // Size of digests plus 6 32-bit magic numbers + p_out_buffer.resize((SHA256_DIGEST_LENGTH * 5) + (6 * 4)); + + int offs = 0; + + // APPX + uint32_t sig = 0x58505041; + offs += buf_put_int32(sig, &p_out_buffer[offs]); + + // AXPC + uint32_t axpc_sig = 0x43505841; + offs += buf_put_int32(axpc_sig, &p_out_buffer[offs]); + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + p_out_buffer[offs++] = digests.axpc[i]; + } + + // AXCD + uint32_t axcd_sig = 0x44435841; + offs += buf_put_int32(axcd_sig, &p_out_buffer[offs]); + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + p_out_buffer[offs++] = digests.axcd[i]; + } + + // AXCT + uint32_t axct_sig = 0x54435841; + offs += buf_put_int32(axct_sig, &p_out_buffer[offs]); + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + p_out_buffer[offs++] = digests.axct[i]; + } + + // AXBM + uint32_t axbm_sig = 0x4D425841; + offs += buf_put_int32(axbm_sig, &p_out_buffer[offs]); + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + p_out_buffer[offs++] = digests.axbm[i]; + } + + // AXCI + uint32_t axci_sig = 0x49435841; + offs += buf_put_int32(axci_sig, &p_out_buffer[offs]); + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + p_out_buffer[offs++] = digests.axci[i]; + } + + // Done! +} + +Error AppxPackager::read_cert_file(const String & p_path, const String &p_password, CertFile* p_out_cf) { + + ERR_FAIL_COND_V(!p_out_cf, ERR_INVALID_PARAMETER); + + BIO* bio = BIO_new_file(p_path.utf8().get_data(), "rb"); + if (!bio) { + return openssl_error(ERR_peek_last_error()); + } + + PKCS12* data = d2i_PKCS12_bio(bio, NULL); + if (!data) { + BIO_free(bio); + return openssl_error(ERR_peek_last_error()); + } + + /* Fails to link with GCC, need to solve when implement signing + if (!PKCS12_parse(data, p_password.utf8().get_data(), &p_out_cf->private_key, &p_out_cf->certificate, NULL)) { + PKCS12_free(data); + BIO_free(bio); + return openssl_error(ERR_peek_last_error()); + }*/ + + if (!p_out_cf->private_key) { + PKCS12_free(data); + BIO_free(bio); + return openssl_error(ERR_peek_last_error()); + } + + if (!p_out_cf->certificate) { + PKCS12_free(data); + BIO_free(bio); + return openssl_error(ERR_peek_last_error()); + } + + PKCS12_free(data); + BIO_free(bio); + + return OK; +} + +Error AppxPackager::sign(const CertFile & p_cert, const AppxDigests & digests, PKCS7 * p_out_signature) { + + OpenSSL_add_all_algorithms(); + + // Register object IDs + OBJ_create_and_add_object(SPC_INDIRECT_DATA_OBJID, NULL, NULL); + OBJ_create_and_add_object(SPC_SIPINFO_OBJID, NULL, NULL); + OBJ_create_and_add_object(SPC_SP_OPUS_INFO_OBJID, NULL, NULL); + OBJ_create_and_add_object(SPC_STATEMENT_TYPE_OBJID, NULL, NULL); + + if (!PKCS7_set_type(p_out_signature, NID_pkcs7_signed)) { + + return openssl_error(ERR_peek_last_error()); + } + + PKCS7_SIGNER_INFO *signer_info = PKCS7_add_signature(p_out_signature, p_cert.certificate, p_cert.private_key, EVP_sha256()); + if (!signer_info) return openssl_error(ERR_peek_last_error()); + + add_attributes(signer_info); + + if (!PKCS7_content_new(p_out_signature, NID_pkcs7_data)) { + + return openssl_error(ERR_peek_last_error()); + } + + if (!PKCS7_add_certificate(p_out_signature, p_cert.certificate)) { + + return openssl_error(ERR_peek_last_error()); + } + + asn1::SPCIndirectDataContent* idc = asn1::SPCIndirectDataContent_new(); + + MakeIndirectDataContent(*idc); + EncodedASN1 idc_encoded = + EncodedASN1::FromItem(idc); + + BIO* signed_data = PKCS7_dataInit(p_out_signature, NULL); + + if (idc_encoded.size() < 2) { + + ERR_EXPLAIN("Invalid encoded size"); + ERR_FAIL_V(FAILED); + } + + if ((idc_encoded.data()[1] & 0x80) == 0x00) { + + ERR_EXPLAIN("Invalid encoded data"); + ERR_FAIL_V(FAILED); + } + + size_t skip = 4; + + if (BIO_write(signed_data, idc_encoded.data() + skip, idc_encoded.size() - skip) + != idc_encoded.size() - skip) { + + return openssl_error(ERR_peek_last_error()); + } + if (BIO_flush(signed_data) != 1) { + + return openssl_error(ERR_peek_last_error()); + } + + if (!PKCS7_dataFinal(p_out_signature, signed_data)) { + + return openssl_error(ERR_peek_last_error()); + } + + PKCS7* content = PKCS7_new(); + if (!content) { + + return openssl_error(ERR_peek_last_error()); + } + + content->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1); + + ASN1_TYPE* idc_sequence = idc_encoded.ToSequenceType(); + content->d.other = idc_sequence; + + if (!PKCS7_set_content(p_out_signature, content)) { + + return openssl_error(ERR_peek_last_error()); + } + + return OK; +} + +#endif // OPENSSL_ENABLED + +//////////////////////////////////////////////////////////////////// + + +bool EditorExportPlatformWinrt::_valid_resource_name(const String &p_name) const { + + if (p_name.empty()) return false; + if (p_name.ends_with(".")) return false; + + static const char* invalid_names[] = { + "CON","PRN","AUX","NUL","COM1","COM2","COM3","COM4","COM5","COM6","COM7", + "COM8","COM9","LPT1","LPT2","LPT3","LPT4","LPT5","LPT6","LPT7","LPT8","LPT9", + NULL + }; + + const char** t = invalid_names; + while (*t) { + if (p_name == *t) return false; + t++; + } + + return true; +} + +bool EditorExportPlatformWinrt::_valid_guid(const String & p_guid) const { + + Vector parts = p_guid.split("-"); + + if (parts.size() != 5) return false; + if (parts[0].length() != 8) return false; + for (int i = 1; i < 4; i++) + if (parts[i].length() != 4) return false; + if (parts[4].length() != 12) return false; + + return true; +} + +bool EditorExportPlatformWinrt::_valid_bgcolor(const String & p_color) const { + + if (p_color.empty()) return true; + if (p_color.begins_with("#") && p_color.is_valid_html_color()) return true; + + // Colors from https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx + static const char* valid_colors[] = { + "aliceBlue","antiqueWhite","aqua","aquamarine","azure","beige", + "bisque","black","blanchedAlmond","blue","blueViolet","brown", + "burlyWood","cadetBlue","chartreuse","chocolate","coral","cornflowerBlue", + "cornsilk","crimson","cyan","darkBlue","darkCyan","darkGoldenrod", + "darkGray","darkGreen","darkKhaki","darkMagenta","darkOliveGreen","darkOrange", + "darkOrchid","darkRed","darkSalmon","darkSeaGreen","darkSlateBlue","darkSlateGray", + "darkTurquoise","darkViolet","deepPink","deepSkyBlue","dimGray","dodgerBlue", + "firebrick","floralWhite","forestGreen","fuchsia","gainsboro","ghostWhite", + "gold","goldenrod","gray","green","greenYellow","honeydew", + "hotPink","indianRed","indigo","ivory","khaki","lavender", + "lavenderBlush","lawnGreen","lemonChiffon","lightBlue","lightCoral","lightCyan", + "lightGoldenrodYellow","lightGreen","lightGray","lightPink","lightSalmon","lightSeaGreen", + "lightSkyBlue","lightSlateGray","lightSteelBlue","lightYellow","lime","limeGreen", + "linen","magenta","maroon","mediumAquamarine","mediumBlue","mediumOrchid", + "mediumPurple","mediumSeaGreen","mediumSlateBlue","mediumSpringGreen","mediumTurquoise","mediumVioletRed", + "midnightBlue","mintCream","mistyRose","moccasin","navajoWhite","navy", + "oldLace","olive","oliveDrab","orange","orangeRed","orchid", + "paleGoldenrod","paleGreen","paleTurquoise","paleVioletRed","papayaWhip","peachPuff", + "peru","pink","plum","powderBlue","purple","red", + "rosyBrown","royalBlue","saddleBrown","salmon","sandyBrown","seaGreen", + "seaShell","sienna","silver","skyBlue","slateBlue","slateGray", + "snow","springGreen","steelBlue","tan","teal","thistle", + "tomato","transparent","turquoise","violet","wheat","white", + "whiteSmoke","yellow","yellowGreen", + NULL + }; + + const char** color = valid_colors; + + while(*color) { + if (p_color == *color) return true; + color++; + } + + return false; +} + +bool EditorExportPlatformWinrt::_valid_image(const Ref p_image, int p_width, int p_height) const { + + if (!p_image.is_valid()) return false; + + // TODO: Add resource creation or image rescaling to enable other scales: + // 1.25, 1.5, 2.0 + real_t scales[] = { 1.0 }; + bool valid_w = false; + bool valid_h = false; + + for (int i = 0; i < 1; i++) { + + int w = ceil(p_width * scales[i]); + int h = ceil(p_height * scales[i]); + + if (w == p_image->get_width()) + valid_w = true; + if (h == p_image->get_height()) + valid_h = true; + } + + return valid_w && valid_h; +} + +Vector EditorExportPlatformWinrt::_fix_manifest(const Vector &p_template, bool p_give_internet) const { + + String result = String::utf8((const char*)p_template.ptr(), p_template.size()); + + result = result.replace("$godot_version$", VERSION_FULL_NAME); + + result = result.replace("$identity_name$", unique_name); + result = result.replace("$publisher$", publisher); + + result = result.replace("$product_guid$", product_guid); + result = result.replace("$publisher_guid$", publisher_guid); + + String version = itos(version_major) + "." + itos(version_minor) + "." + itos(version_build) + "." + itos(version_revision); + result = result.replace("$version_string$", version); + + String architecture = arch == ARM ? "ARM" : arch == X86 ? "x86" : "x64"; + result = result.replace("$architecture$", architecture); + + result = result.replace("$display_name$", display_name.empty() ? (String)Globals::get_singleton()->get("application/name") : display_name); + result = result.replace("$publisher_display_name$", publisher_display_name); + result = result.replace("$app_description$", description); + result = result.replace("$bg_color$", background_color); + result = result.replace("$short_name$", short_name); + + String name_on_tiles = ""; + if (name_on_square150) { + name_on_tiles += " \n"; + } + if (name_on_wide) { + name_on_tiles += " \n"; + } + if (name_on_square310) { + name_on_tiles += " \n"; + } + + String show_name_on_tiles = ""; + if (!name_on_tiles.empty()) { + show_name_on_tiles = "\n" + name_on_tiles + " "; + } + + result = result.replace("$name_on_tiles$", name_on_tiles); + + String rotations = ""; + if (orientation_landscape) { + rotations += " \n"; + } + if (orientation_portrait) { + rotations += " \n"; + } + if (orientation_landscape_flipped) { + rotations += " \n"; + } + if (orientation_portrait_flipped) { + rotations += " \n"; + } + + String rotation_preference = ""; + if (!rotations.empty()) { + rotation_preference = "\n" + rotations + " "; + } + + result = result.replace("$rotation_preference$", rotation_preference); + + String capabilities_elements = ""; + const char **basic = uwp_capabilities; + while (*basic) { + if (capabilities.has(*basic)) { + capabilities_elements += " \n"; + } + basic++; + } + const char **uap = uwp_uap_capabilities; + while (*uap) { + if (uap_capabilities.has(*uap)) { + capabilities_elements += " \n"; + } + uap++; + } + const char **device = uwp_device_capabilites; + while (*device) { + if (uap_capabilities.has(*device)) { + capabilities_elements += " \n"; + } + device++; + } + + if (!capabilities.has("internetClient") && p_give_internet) { + capabilities_elements += " \n"; + } + + String capabilities_string = ""; + if (!capabilities_elements.empty()) { + capabilities_string = "\n" + capabilities_elements + " "; + } + + result = result.replace("$capabilities_place$", capabilities_string); + + Vector r_ret; + r_ret.resize(result.length()); + + for (int i = 0; i < result.length(); i++) + r_ret[i] = result.utf8().get(i); + + return r_ret; +} + +Vector EditorExportPlatformWinrt::_get_image_data(const String & p_path) { + + Vector data; + Ref ref; + + if (p_path.find("StoreLogo") != -1) { + ref = store_logo; + } else if (p_path.find("Square44x44Logo") != -1) { + ref = square44; + } else if (p_path.find("Square71x71Logo") != -1) { + ref = square71; + } else if (p_path.find("Square150x150Logo") != -1) { + ref = square150; + } else if (p_path.find("Square310x310Logo") != -1) { + ref = square310; + } else if (p_path.find("Wide310x150Logo") != -1) { + ref = wide310; + } else if (p_path.find("SplashScreen") != -1) { + ref = splash; + } + + if (!ref.is_valid()) return data; + + + String tmp_path = EditorSettings::get_singleton()->get_settings_path().plus_file("tmp/uwp_tmp_logo.png"); + + Error err = ref->get_data().save_png(tmp_path); + + if (err != OK) { + + String err_string = "Couldn't save temp logo file."; + + EditorNode::add_io_error(err_string); + ERR_EXPLAIN(err_string); + ERR_FAIL_V(data); + } + + FileAccess* f = FileAccess::open(tmp_path, FileAccess::READ, &err); + + if (err != OK) { + + String err_string = "Couldn't open temp logo file."; + + EditorNode::add_io_error(err_string); + ERR_EXPLAIN(err_string); + ERR_FAIL_V(data); + } + + data.resize(f->get_len()); + f->get_buffer(data.ptr(), data.size()); + + f->close(); + memdelete(f); + + // Delete temp file + DirAccess* dir = DirAccess::open(tmp_path.get_base_dir(), &err); + + if (err != OK) { + + String err_string = "Couldn't open temp path to remove temp logo file."; + + EditorNode::add_io_error(err_string); + ERR_EXPLAIN(err_string); + ERR_FAIL_V(data); + } + + err = dir->remove(tmp_path); + + memdelete(dir); + + if (err != OK) { + + String err_string = "Couldn't remove temp logo file."; + + EditorNode::add_io_error(err_string); + ERR_EXPLAIN(err_string); + ERR_FAIL_V(data); + } + + return data; +} + +Error EditorExportPlatformWinrt::save_appx_file(void * p_userdata, const String & p_path, const Vector& p_data, int p_file, int p_total) { + + AppxPackager *packager = (AppxPackager*)p_userdata; + String dst_path = p_path.replace_first("res://", "game/"); + + packager->add_file(dst_path, p_data.ptr(), p_data.size(), p_file, p_total, _should_compress_asset(p_path, p_data)); + + return OK; +} + +bool EditorExportPlatformWinrt::_should_compress_asset(const String & p_path, const Vector& p_data) { + + /* TODO: This was copied verbatim from Android export. It should be + * refactored to the parent class and also be used for .zip export. + */ + + /* + * By not compressing files with little or not benefit in doing so, + * a performance gain is expected at runtime. Moreover, if the APK is + * zip-aligned, assets stored as they are can be efficiently read by + * Android by memory-mapping them. + */ + + // -- Unconditional uncompress to mimic AAPT plus some other + + static const char* unconditional_compress_ext[] = { + // From https://github.com/android/platform_frameworks_base/blob/master/tools/aapt/Package.cpp + // These formats are already compressed, or don't compress well: + ".jpg", ".jpeg", ".png", ".gif", + ".wav", ".mp2", ".mp3", ".ogg", ".aac", + ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", + ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", + ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", + ".amr", ".awb", ".wma", ".wmv", + // Godot-specific: + ".webp", // Same reasoning as .png + ".cfb", // Don't let small config files slow-down startup + // Trailer for easier processing + NULL + }; + + for (const char** ext = unconditional_compress_ext; *ext; ++ext) { + if (p_path.to_lower().ends_with(String(*ext))) { + return false; + } + } + + // -- Compressed resource? + + if (p_data.size() >= 4 && p_data[0] == 'R' && p_data[1] == 'S' && p_data[2] == 'C' && p_data[3] == 'C') { + // Already compressed + return false; + } + + // --- TODO: Decide on texture resources according to their image compression setting + + return true; +} + +bool EditorExportPlatformWinrt::_set(const StringName& p_name, const Variant& p_value) { + + String n = p_name; + + if (n == "architecture/target") + arch = (Platform)((int)p_value); + else if (n == "custom_package/debug") + custom_debug_package = p_value; + else if (n == "custom_package/release") + custom_release_package = p_value; + else if (n == "command_line/extra_args") + cmdline = p_value; + else if (n == "package/display_name") + display_name = p_value; + else if (n == "package/short_name") + short_name = p_value; + else if (n == "package/unique_name") + unique_name = p_value; + else if (n == "package/description") + description = p_value; + else if (n == "package/publisher") + publisher = p_value; + else if (n == "package/publisher_display_name") + publisher_display_name = p_value; + else if (n == "identity/product_guid") + product_guid = p_value; + else if (n == "identity/publisher_guid") + publisher_guid = p_value; + else if (n == "version/major") + version_major = p_value; + else if (n == "version/minor") + version_minor = p_value; + else if (n == "version/build") + version_build = p_value; + else if (n == "version/revision") + version_revision = p_value; + else if (n == "orientation/landscape") + orientation_landscape = p_value; + else if (n == "orientation/portrait") + orientation_portrait = p_value; + else if (n == "orientation/landscape_flipped") + orientation_landscape_flipped = p_value; + else if (n == "orientation/portrait_flipped") + orientation_portrait_flipped = p_value; + else if (n == "images/background_color") + background_color = p_value; + else if (n == "images/store_logo") + store_logo = p_value; + else if (n == "images/square44x44_logo") + square44 = p_value; + else if (n == "images/square71x71_logo") + square71 = p_value; + else if (n == "images/square150x150_logo") + square150 = p_value; + else if (n == "images/square310x310_logo") + square310 = p_value; + else if (n == "images/wide310x150_logo") + wide310 = p_value; + else if (n == "images/splash_screen") + splash = p_value; + else if (n == "tiles/show_name_on_square150x150") + name_on_square150 = p_value; + else if (n == "tiles/show_name_on_wide310x150") + name_on_wide = p_value; + else if (n == "tiles/show_name_on_square310x310") + name_on_square310 = p_value; + +#if 0 // Signing disabled + else if (n == "signing/sign") + sign_package = p_value; + else if (n == "signing/certificate_file") + certificate_path = p_value; + else if (n == "signing/certificate_password") + certificate_pass = p_value; +#endif + else if (n.begins_with("capabilities/")) { + + String what = n.get_slice("/", 1).replace("_", ""); + bool enable = p_value; + + if (array_has(uwp_capabilities, what.utf8().get_data())) { + + if (enable) + capabilities.insert(what); + else + capabilities.erase(what); + + } else if (array_has(uwp_uap_capabilities, what.utf8().get_data())) { + + if (enable) + uap_capabilities.insert(what); + else + uap_capabilities.erase(what); + + } else if (array_has(uwp_device_capabilites, what.utf8().get_data())) { + + if (enable) + device_capabilities.insert(what); + else + device_capabilities.erase(what); + } + } else return false; + + return true; +} + +bool EditorExportPlatformWinrt::_get(const StringName& p_name, Variant &r_ret) const { + + String n = p_name; + + if (n == "architecture/target") + r_ret = (int)arch; + else if (n == "custom_package/debug") + r_ret = custom_debug_package; + else if (n == "custom_package/release") + r_ret = custom_release_package; + else if (n == "command_line/extra_args") + r_ret = cmdline; + else if (n == "package/display_name") + r_ret = display_name; + else if (n == "package/short_name") + r_ret = short_name; + else if (n == "package/unique_name") + r_ret = unique_name; + else if (n == "package/description") + r_ret = description; + else if (n == "package/publisher") + r_ret = publisher; + else if (n == "package/publisher_display_name") + r_ret = publisher_display_name; + else if (n == "identity/product_guid") + r_ret = product_guid; + else if (n == "identity/publisher_guid") + r_ret = publisher_guid; + else if (n == "version/major") + r_ret = version_major; + else if (n == "version/minor") + r_ret = version_minor; + else if (n == "version/build") + r_ret = version_build; + else if (n == "version/revision") + r_ret = version_revision; + else if (n == "orientation/landscape") + r_ret = orientation_landscape; + else if (n == "orientation/portrait") + r_ret = orientation_portrait; + else if (n == "orientation/landscape_flipped") + r_ret = orientation_landscape_flipped; + else if (n == "orientation/portrait_flipped") + r_ret = orientation_portrait_flipped; + else if (n == "images/background_color") + r_ret = background_color; + else if (n == "images/store_logo") + r_ret = store_logo; + else if (n == "images/square44x44_logo") + r_ret = square44; + else if (n == "images/square71x71_logo") + r_ret = square71; + else if (n == "images/square150x150_logo") + r_ret = square150; + else if (n == "images/square310x310_logo") + r_ret = square310; + else if (n == "images/wide310x150_logo") + r_ret = wide310; + else if (n == "images/splash_screen") + r_ret = splash; + else if (n == "tiles/show_name_on_square150x150") + r_ret = name_on_square150; + else if (n == "tiles/show_name_on_wide310x150") + r_ret = name_on_wide; + else if (n == "tiles/show_name_on_square310x310") + r_ret = name_on_square310; + +#if 0 // Signing disabled + else if (n == "signing/sign") + r_ret = sign_package; + else if (n == "signing/certificate_file") + r_ret = certificate_path; + else if (n == "signing/certificate_password") + r_ret = certificate_pass; +#endif + else if (n.begins_with("capabilities/")) { + + String what = n.get_slice("/", 1).replace("_", ""); + + if (array_has(uwp_capabilities, what.utf8().get_data())) { + + r_ret = capabilities.has(what); + + } else if (array_has(uwp_uap_capabilities, what.utf8().get_data())) { + + r_ret = uap_capabilities.has(what); + + } else if (array_has(uwp_device_capabilites, what.utf8().get_data())) { + + r_ret = device_capabilities.has(what); + } + } else return false; + + return true; +} + +void EditorExportPlatformWinrt::_get_property_list(List* p_list) const { + + p_list->push_back(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "appx")); + p_list->push_back(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "appx")); + + p_list->push_back(PropertyInfo(Variant::INT, "architecture/target", PROPERTY_HINT_ENUM, "ARM,x86,x64")); + + p_list->push_back(PropertyInfo(Variant::STRING, "command_line/extra_args")); + + p_list->push_back(PropertyInfo(Variant::STRING, "package/display_name")); + p_list->push_back(PropertyInfo(Variant::STRING, "package/short_name")); + p_list->push_back(PropertyInfo(Variant::STRING, "package/unique_name")); + p_list->push_back(PropertyInfo(Variant::STRING, "package/description")); + p_list->push_back(PropertyInfo(Variant::STRING, "package/publisher")); + p_list->push_back(PropertyInfo(Variant::STRING, "package/publisher_display_name")); + + p_list->push_back(PropertyInfo(Variant::STRING, "identity/product_guid")); + p_list->push_back(PropertyInfo(Variant::STRING, "identity/publisher_guid")); + + p_list->push_back(PropertyInfo(Variant::INT, "version/major")); + p_list->push_back(PropertyInfo(Variant::INT, "version/minor")); + p_list->push_back(PropertyInfo(Variant::INT, "version/build")); + p_list->push_back(PropertyInfo(Variant::INT, "version/revision")); + + p_list->push_back(PropertyInfo(Variant::BOOL, "orientation/landscape")); + p_list->push_back(PropertyInfo(Variant::BOOL, "orientation/portrait")); + p_list->push_back(PropertyInfo(Variant::BOOL, "orientation/landscape_flipped")); + p_list->push_back(PropertyInfo(Variant::BOOL, "orientation/portrait_flipped")); + + p_list->push_back(PropertyInfo(Variant::STRING, "images/background_color")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "images/store_logo", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "images/square44x44_logo", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "images/square71x71_logo", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "images/square150x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "images/square310x310_logo", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "images/wide310x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "images/splash_screen", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture")); + + p_list->push_back(PropertyInfo(Variant::BOOL, "tiles/show_name_on_square150x150")); + p_list->push_back(PropertyInfo(Variant::BOOL, "tiles/show_name_on_wide310x150")); + p_list->push_back(PropertyInfo(Variant::BOOL, "tiles/show_name_on_square310x310")); + +#if 0 // Signing does not work :( disabling for now + p_list->push_back(PropertyInfo(Variant::BOOL, "signing/sign")); + p_list->push_back(PropertyInfo(Variant::STRING, "signing/certificate_file", PROPERTY_HINT_GLOBAL_FILE, "pfx")); + p_list->push_back(PropertyInfo(Variant::STRING, "signing/certificate_password")); +#endif + + // Capabilites + const char **basic = uwp_capabilities; + while (*basic) { + p_list->push_back(PropertyInfo(Variant::BOOL, "capabilities/" + String(*basic).camelcase_to_underscore(false))); + basic++; + } + + const char **uap = uwp_uap_capabilities; + while (*uap) { + p_list->push_back(PropertyInfo(Variant::BOOL, "capabilities/" + String(*uap).camelcase_to_underscore(false))); + uap++; + } + + const char **device = uwp_device_capabilites; + while (*device) { + p_list->push_back(PropertyInfo(Variant::BOOL, "capabilities/" + String(*device).camelcase_to_underscore(false))); + device++; + } + +} + +bool EditorExportPlatformWinrt::can_export(String * r_error) const { + + String err; + bool valid = true; + + if (!exists_export_template("winrt_x86_debug.zip") || !exists_export_template("winrt_x86_release.zip") + || !exists_export_template("winrt_arm_debug.zip") || !exists_export_template("winrt_arm_release.zip") + || !exists_export_template("winrt_x64_debug.zip") || !exists_export_template("winrt_x64_release.zip")) { + valid = false; + err += TTR("No export templates found.\nDownload and install export templates.") + "\n"; + } + + if (custom_debug_package != "" && !FileAccess::exists(custom_debug_package)) { + valid = false; + err += TTR("Custom debug package not found.") + "\n"; + } + + if (custom_release_package != "" && !FileAccess::exists(custom_release_package)) { + valid = false; + err += TTR("Custom release package not found.") + "\n"; + } + + if (!_valid_resource_name(unique_name)) { + valid = false; + err += TTR("Invalid unique name.") + "\n"; + } + + if (!_valid_guid(product_guid)) { + valid = false; + err += TTR("Invalid product GUID.") + "\n"; + } + + if (!_valid_guid(publisher_guid)) { + valid = false; + err += TTR("Invalid publisher GUID.") + "\n"; + } + + if (!_valid_bgcolor(background_color)) { + valid = false; + err += TTR("Invalid background color.") + "\n"; + } + + if (store_logo.is_valid() && !_valid_image(store_logo, 50, 50)) { + valid = false; + err += TTR("Invalid Store Logo image dimensions (should be 50x50).") + "\n"; + } + + if (square44.is_valid() && !_valid_image(square44, 44, 44)) { + valid = false; + err += TTR("Invalid square 44x44 logo image dimensions (should be 44x44).") + "\n"; + } + + if (square71.is_valid() && !_valid_image(square71, 71, 71)) { + valid = false; + err += TTR("Invalid square 71x71 logo image dimensions (should be 71x71).") + "\n"; + } + + if (square150.is_valid() && !_valid_image(square150, 150, 150)) { + valid = false; + err += TTR("Invalid square 150x150 logo image dimensions (should be 150x150).") + "\n"; + } + + if (square310.is_valid() && !_valid_image(square310, 310, 310)) { + valid = false; + err += TTR("Invalid square 310x310 logo image dimensions (should be 310x310).") + "\n"; + } + + if (wide310.is_valid() && !_valid_image(wide310, 310, 150)) { + valid = false; + err += TTR("Invalid wide 310x150 logo image dimensions (should be 310x150).") + "\n"; + } + + if (splash.is_valid() && !_valid_image(splash, 620, 300)) { + valid = false; + err += TTR("Invalid splash screen image dimensions (should be 620x300).") + "\n"; + } + + if (r_error) + *r_error = err; + + return valid; +} + +Error EditorExportPlatformWinrt::export_project(const String & p_path, bool p_debug, int p_flags) { + + String src_appx; + + EditorProgress ep("export", "Exporting for Windows Universal", 7); + + if (is_debug) + src_appx = custom_debug_package; + else + src_appx = custom_release_package; + + if (src_appx == "") { + String err; + if (p_debug) { + switch (arch) { + case X86: { + src_appx = find_export_template("winrt_x86_debug.zip", &err); + break; + } + case X64: { + src_appx = find_export_template("winrt_x64_debug.zip", &err); + break; + } + case ARM: { + src_appx = find_export_template("winrt_arm_debug.zip", &err); + break; + } + } + } else { + switch (arch) { + case X86: { + src_appx = find_export_template("winrt_x86_release.zip", &err); + break; + } + case X64: { + src_appx = find_export_template("winrt_x64_release.zip", &err); + break; + } + case ARM: { + src_appx = find_export_template("winrt_arm_release.zip", &err); + break; + } + } + } + if (src_appx == "") { + EditorNode::add_io_error(err); + return ERR_FILE_NOT_FOUND; + } + } + + Error err = OK; + + FileAccess *fa_pack = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); + + AppxPackager packager; + packager.init(fa_pack, sign_package ? AppxPackager::SIGN : AppxPackager::DONT_SIGN, certificate_path, certificate_pass); + + FileAccess *src_f = NULL; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + + ep.step("Creating package...", 0); + + unzFile pkg = unzOpen2(src_appx.utf8().get_data(), &io); + + if (!pkg) { + + EditorNode::add_io_error("Could not find template appx to export:\n" + src_appx); + return ERR_FILE_NOT_FOUND; + } + + int ret = unzGoToFirstFile(pkg); + + ep.step("Copying template files...", 1); + + EditorNode::progress_add_task("template_files", "Template files", 100); + packager.set_progress_task("template_files"); + + int template_files_amount = 9; + int template_file_no = 1; + + while (ret == UNZ_OK) { + + // get file name + unz_file_info info; + char fname[16834]; + ret = unzGetCurrentFileInfo(pkg, &info, fname, 16834, NULL, 0, NULL, 0); + + String path = fname; + + if (path.ends_with("/")) { + // Ignore directories + ret = unzGoToNextFile(pkg); + continue; + } + + Vector data; + bool do_read = true; + + if (path.begins_with("Assets/")) { + + path = path.replace(".scale-100", ""); + + data = _get_image_data(path); + if (data.size() > 0) do_read = false; + } + + //read + if (do_read) { + data.resize(info.uncompressed_size); + unzOpenCurrentFile(pkg); + unzReadCurrentFile(pkg, data.ptr(), data.size()); + unzCloseCurrentFile(pkg); + } + + if (path == "AppxManifest.xml") { + + data = _fix_manifest(data, p_flags&(EXPORT_DUMB_CLIENT | EXPORT_REMOTE_DEBUG)); + } + + print_line("ADDING: " + path); + + packager.add_file(path, data.ptr(), data.size(), template_file_no++, template_files_amount, _should_compress_asset(path, data)); + + ret = unzGoToNextFile(pkg); + } + + EditorNode::progress_end_task("template_files"); + + ep.step("Creating command line...", 2); + + Vector cl = cmdline.strip_edges().split(" "); + for (int i = 0;i clf; + + // Argc + clf.resize(4); + encode_uint32(cl.size(), clf.ptr()); + + for (int i = 0; i < cl.size(); i++) { + + CharString txt = cl[i].utf8(); + int base = clf.size(); + clf.resize(base + 4 + txt.length()); + encode_uint32(txt.length(), &clf[base]); + copymem(&clf[base + 4], txt.ptr(), txt.length()); + print_line(itos(i) + " param: " + cl[i]); + } + + packager.add_file("__cl__.cl", clf.ptr(), clf.size(), -1, -1, false); + + ep.step("Adding project files...", 3); + + EditorNode::progress_add_task("project_files", "Project Files", 100); + packager.set_progress_task("project_files"); + + err = export_project_files(save_appx_file, &packager, false); + + EditorNode::progress_end_task("project_files"); + + ep.step("Closing package...", 7); + + unzClose(pkg); + + packager.finish(); + + return OK; +} + +EditorExportPlatformWinrt::EditorExportPlatformWinrt() { + + Image img(_winrt_logo); + logo = Ref(memnew(ImageTexture)); + logo->create_from_image(img); + + is_debug = true; + + custom_release_package = ""; + custom_debug_package = ""; + + arch = X86; + + display_name = ""; + short_name = "Godot"; + unique_name = "Godot.Engine"; + description = "Godot Engine"; + publisher = "CN=GodotEngine"; + publisher_display_name = "Godot Engine"; + + product_guid = "00000000-0000-0000-0000-000000000000"; + publisher_guid = "00000000-0000-0000-0000-000000000000"; + + version_major = 1; + version_minor = 0; + version_build = 0; + version_revision = 0; + + orientation_landscape = true; + orientation_portrait = true; + orientation_landscape_flipped = true; + orientation_portrait_flipped = true; + + background_color = "transparent"; + + name_on_square150 = false; + name_on_square310 = false; + name_on_wide = false; + + sign_package = false; + certificate_path = ""; + certificate_pass = ""; +} + +EditorExportPlatformWinrt::~EditorExportPlatformWinrt() {} + + +void register_winrt_exporter() { + + Ref exporter = Ref(memnew(EditorExportPlatformWinrt)); + EditorImportExport::get_singleton()->add_export_platform(exporter); +} diff --git a/platform/winrt/export/export.h b/platform/winrt/export/export.h new file mode 100644 index 00000000000..278d6d23cd6 --- /dev/null +++ b/platform/winrt/export/export.h @@ -0,0 +1,29 @@ +/*************************************************************************/ +/* export.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_winrt_exporter(); diff --git a/tools/dist/uwp_template/AppxManifest.xml b/tools/dist/uwp_template/AppxManifest.xml new file mode 100644 index 00000000000..48a2ba7eb3b --- /dev/null +++ b/tools/dist/uwp_template/AppxManifest.xml @@ -0,0 +1,32 @@ + + + + + + $display_name$ + $publisher_display_name$ + Assets\StoreLogo.png + + + + + + + + + + + + + $name_on_tiles$ + + + $rotation_preference$ + + + + $capabilities_place$ + + + + \ No newline at end of file diff --git a/tools/dist/uwp_template/Assets/SplashScreen.scale-100.png b/tools/dist/uwp_template/Assets/SplashScreen.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..540bfb1c016983ee967d9e14893993279944896e GIT binary patch literal 14919 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 literal 0 HcmV?d00001 diff --git a/tools/dist/uwp_template/Assets/Square150x150Logo.scale-100.png b/tools/dist/uwp_template/Assets/Square150x150Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..6cff663eb52228337e6fa154980bd5b584946c00 GIT binary patch literal 7001 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 literal 0 HcmV?d00001 diff --git a/tools/dist/uwp_template/Assets/Square71x71Logo.scale-100.png b/tools/dist/uwp_template/Assets/Square71x71Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..b1bf33136599ce560c0bdc9e8cad3cc691951679 GIT binary patch literal 3369 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 JPDHLkV1kP7YO4SM literal 0 HcmV?d00001 diff --git a/tools/dist/uwp_template/Assets/Wide310x150Logo.scale-100.png b/tools/dist/uwp_template/Assets/Wide310x150Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..b06f1ad50fda8192a365f6f0a66969d2d0e35838 GIT binary patch literal 7390 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 v(YzT$sjb;5p)%p?8}a*YxG literal 0 HcmV?d00001