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 79cbe50685..8243483e17 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 0000000000..ed294b438f --- /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 0000000000..278d6d23cd --- /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 0000000000..48a2ba7eb3 --- /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/Square44x44Logo.scale-100.png b/tools/dist/uwp_template/Assets/Square44x44Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..ad059994ed724a0ed66a3d7df38169f90ba0c1d9 GIT binary patch literal 2067 zcmeAS@N?(olHy`uVBq!ia0y~yV9)_!4mJh`2Dz3fMg|53wj^(N7l!{JxM1({$qWn( zoCO|{#S9F**Fl)kNn>^e0|NtliKnkC`vXo!22JhuW0&SKFt9K1ba4!cIQ(`-bwJ2< z+2i)l@3lVF)mE?z)M^sYwDJwgb!8R3a4bXZY=ryUjN2BzIdcLXW-nVi!`-_1hUerh zveMbv$;ZM^Wym-Ru(+vb^}0&07Gyjs&UHv>{oT~>_b%IiIJk+Msp+u1rSb3M^Qzxj z&i}l7|L4Dd&++feWWMb(|NfMqB`H&VrZ$NSEYY~M(Z)RY9BaAr^Hk9zFAja++;v8@ z!@2!(`rL}F1E1|q7``f7e>AAegmGj4kDD{+&)ab4uC->QtF@}m+YLFAC+w@u%Kgz5K^9%mvxXmG2!>+>r9TZjIXVN z`_}kd+@AfFcW)Wz-N62&7kT?G@Rf1Ay%x19$ozp`qQI*1de143F4X*DwaE0o_-M&l z)rWI`Pu|=$-8*#78~ucpU6W5n9@RTFY0FL(8;}CIp9Mi-A7dKNOuewo!>IM*r|SI= z&-NY5R;*_U;}5X8k(T{z>oUQAN;_7*EPW6=!Om&JgMzFizv%})8*v|h-Bl!IP%2U6 z$dTuvIhoC*iF2a9>SD)ZB`?gfSAEMlc*}C?Iz7du7erH68|yUmNM_vl_B1s}J%VBK zqk!%96FeBcF4X>UK1Nj_n=SU8f`oYCqgTCMe`Nm`ZrUJ_bW`D6Mco@W)hE&owE~Z< zmQ2ZxZTK7{eBM)WGuy&SD+&5RPbco%zWXXcdzw6 za!9{AydxoAL~&lnkteTDwglAI^v0&MJ8jx+p?vVx@|Z_61DzwL+e{2x_U%N><&4zR z57%3k?_C(aV4oXv_Tk!dddcW0w8vd->lo_bA&u!|VLr$ko zYtOK;?E4;9*LQXPoQ12_Ej@l<ENmoEQz?ONCy zzO}*z9lzNxEwJ!^f1xBQDt3JV|F1W*?%5xRk6Wv5U@+mv4Nt$nA3t8U3{HKw@&B~A z?1WcaR<2le%kZV5o!vaO)pIx9_WjHJ=jO}TA@?_LFickN+$=01A&WkU73>kedH|MmPr_49 zcCS*}b@g6CeR9Hdy^RWw7o1EQ?Q@U!1cx-Rqix~-oj>UeUg)T}jpH8#9!X2|(@G>8W|Exy_} zbjQ`gE}0<<;5mh%jQ=s_Puv! z$9@lIZ|Rl0y#rek&IHI`JJXwTsGwf`L3ny!x?2iIneVQX@*!fi6*AH6q8>eR$Cfca z|M~uF^_FdxKOSyQmJojOx!^P7JvobwrEFn6)9PR6-YYw>_(c-0^t0m$dAW~Yzj9m7 z92|PxDR%Q&-??5J{zj;>d*$zP**B-uIPmVAPVVhenYqO_f9&m!^q0r~Gn;(aXUhG| zMSHeIHf@kDJ*+AI;Ew5M>B*b>>e!M_UVBvZuWrwz_jTGGl7WYJ1ihYh$EEF8u%%JM z?6lL#kN-D6Td38;@g?v453Ae<-)yyawX9!f{vps&a{K$p+r8R1fB$TI>6@48d+}+0 ziR&JgimjF`3G|{KRbO}!=ahzS{HSw?|7L0T>Z(F_XYWaGznjg$R(6PKHin}N^uIkakI92<~mz=D8&1S4O*n{f1mRNP{<>S}g zrhP+Be9Pqj1z#jf%o-vtO`lc7?@%nV@Q;~!XNEn^zx>= zmoJB(*GapO()@e3B(K`)Gkx8tntSduYriMQBZp4m69t@VjE!6VGf1hJa2R+W5xsC; o(USe!=btZ6>egIlOZ;tb*U!^BbJOo$1_lNOPgg&ebxsLQ0GAf@kN^Mx 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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 O1B0ilpUXO@geCyh6OV-e literal 0 HcmV?d00001 diff --git a/tools/dist/uwp_template/Assets/StoreLogo.scale-100.png b/tools/dist/uwp_template/Assets/StoreLogo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7a625c825d8b21c8186fd10cce6f208098ef83 GIT binary patch literal 2339 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 Ut9Z#?1_lNOPgg&ebxsLQ0CJ5H{r~^~ literal 0 HcmV?d00001