2016-06-18 14:46:12 +02:00
/*************************************************************************/
/* export.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
2017-08-27 14:16:55 +02:00
/* https://godotengine.org */
2016-06-18 14:46:12 +02:00
/*************************************************************************/
2021-01-01 20:13:46 +01:00
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
2016-06-18 14:46:12 +02:00
/* */
/* 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. */
/*************************************************************************/
2017-06-21 11:11:38 +02:00
2014-02-10 02:10:30 +01:00
# include "export.h"
2019-08-09 13:45:30 +02:00
2020-11-07 23:33:38 +01:00
# include "core/config/project_settings.h"
2021-06-11 14:51:48 +02:00
# include "core/io/dir_access.h"
# include "core/io/file_access.h"
2018-09-11 18:13:45 +02:00
# include "core/io/marshalls.h"
# include "core/io/resource_saver.h"
# include "core/io/zip_io.h"
# include "core/os/os.h"
# include "core/version.h"
2017-03-05 14:21:25 +01:00
# include "editor/editor_export.h"
# include "editor/editor_node.h"
2017-03-05 16:44:50 +01:00
# include "editor/editor_settings.h"
2017-06-23 17:03:41 +02:00
# include "platform/osx/logo.gen.h"
2020-12-03 08:55:09 +01:00
2017-07-02 13:23:33 +02:00
# include <sys/stat.h>
2014-02-10 02:10:30 +01:00
class EditorExportPlatformOSX : public EditorExportPlatform {
2017-06-21 11:11:38 +02:00
GDCLASS ( EditorExportPlatformOSX , EditorExportPlatform ) ;
2014-02-10 02:10:30 +01:00
2020-11-24 10:12:55 +01:00
int version_code = 0 ;
2014-02-10 02:10:30 +01:00
Ref < ImageTexture > logo ;
2017-06-21 11:11:38 +02:00
void _fix_plist ( const Ref < EditorExportPreset > & p_preset , Vector < uint8_t > & plist , const String & p_binary ) ;
void _make_icon ( const Ref < Image > & p_icon , Vector < uint8_t > & p_data ) ;
2017-10-03 20:09:57 +02:00
2020-06-10 14:28:50 +02:00
Error _notarize ( const Ref < EditorExportPreset > & p_preset , const String & p_path ) ;
2021-03-03 13:13:55 +01:00
Error _code_sign ( const Ref < EditorExportPreset > & p_preset , const String & p_path , const String & p_ent_path ) ;
2017-07-02 13:23:33 +02:00
Error _create_dmg ( const String & p_dmg_path , const String & p_pkg_name , const String & p_app_path_name ) ;
2019-11-08 13:38:23 +01:00
void _zip_folder_recursive ( zipFile & p_zip , const String & p_root_path , const String & p_folder , const String & p_pkg_name ) ;
2017-10-03 20:09:57 +02:00
# ifdef OSX_ENABLED
bool use_codesign ( ) const { return true ; }
bool use_dmg ( ) const { return true ; }
# else
bool use_codesign ( ) const { return false ; }
bool use_dmg ( ) const { return false ; }
2017-07-02 13:23:33 +02:00
# endif
2020-06-10 14:28:50 +02:00
bool is_package_name_valid ( const String & p_package , String * r_error = nullptr ) const {
String pname = p_package ;
if ( pname . length ( ) = = 0 ) {
if ( r_error ) {
* r_error = TTR ( " Identifier is missing. " ) ;
}
return false ;
}
for ( int i = 0 ; i < pname . length ( ) ; i + + ) {
2020-07-27 12:43:20 +02:00
char32_t c = pname [ i ] ;
2020-06-10 14:28:50 +02:00
if ( ! ( ( c > = ' a ' & & c < = ' z ' ) | | ( c > = ' A ' & & c < = ' Z ' ) | | ( c > = ' 0 ' & & c < = ' 9 ' ) | | c = = ' - ' | | c = = ' . ' ) ) {
if ( r_error ) {
* r_error = vformat ( TTR ( " The character '%s' is not allowed in Identifier. " ) , String : : chr ( c ) ) ;
}
return false ;
}
}
return true ;
}
2014-02-10 02:10:30 +01:00
protected :
2020-07-10 12:34:39 +02:00
virtual void get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) override ;
virtual void get_export_options ( List < ExportOption > * r_options ) override ;
2014-02-10 02:10:30 +01:00
public :
2020-08-02 08:43:14 +02:00
virtual String get_name ( ) const override { return " macOS " ; }
virtual String get_os_name ( ) const override { return " macOS " ; }
2020-07-10 12:34:39 +02:00
virtual Ref < Texture2D > get_logo ( ) const override { return logo ; }
2014-02-10 02:10:30 +01:00
2020-07-10 12:34:39 +02:00
virtual List < String > get_binary_extensions ( const Ref < EditorExportPreset > & p_preset ) const override {
2018-10-29 22:18:49 +01:00
List < String > list ;
if ( use_dmg ( ) ) {
list . push_back ( " dmg " ) ;
}
list . push_back ( " zip " ) ;
return list ;
}
2020-07-10 12:34:39 +02:00
virtual Error export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags = 0 ) override ;
2014-02-10 02:10:30 +01:00
2020-07-10 12:34:39 +02:00
virtual bool can_export ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates ) const override ;
2014-02-10 02:10:30 +01:00
2020-07-10 12:34:39 +02:00
virtual void get_platform_features ( List < String > * r_features ) override {
2017-07-19 22:00:46 +02:00
r_features - > push_back ( " pc " ) ;
r_features - > push_back ( " s3tc " ) ;
2020-11-22 07:17:15 +01:00
r_features - > push_back ( " macOS " ) ;
2017-07-19 22:00:46 +02:00
}
2020-07-10 12:34:39 +02:00
virtual void resolve_platform_feature_priorities ( const Ref < EditorExportPreset > & p_preset , Set < String > & p_features ) override {
2018-08-22 04:56:04 +02:00
}
2014-02-10 02:10:30 +01:00
EditorExportPlatformOSX ( ) ;
~ EditorExportPlatformOSX ( ) ;
} ;
2017-06-21 11:11:38 +02:00
void EditorExportPlatformOSX : : get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) {
2017-09-08 03:39:32 +02:00
if ( p_preset - > get ( " texture_format/s3tc " ) ) {
r_features - > push_back ( " s3tc " ) ;
}
if ( p_preset - > get ( " texture_format/etc " ) ) {
r_features - > push_back ( " etc " ) ;
}
if ( p_preset - > get ( " texture_format/etc2 " ) ) {
r_features - > push_back ( " etc2 " ) ;
}
2017-10-02 21:38:39 +02:00
2018-02-19 12:49:31 +01:00
r_features - > push_back ( " 64 " ) ;
2014-02-10 02:10:30 +01:00
}
2017-06-21 11:11:38 +02:00
void EditorExportPlatformOSX : : get_export_options ( List < ExportOption > * r_options ) {
2020-01-07 13:29:02 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " custom_template/debug " , PROPERTY_HINT_GLOBAL_FILE , " *.zip " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " custom_template/release " , PROPERTY_HINT_GLOBAL_FILE , " *.zip " ) , " " ) ) ;
2017-06-21 11:11:38 +02:00
2018-08-17 03:50:12 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/name " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Game Name " ) , " " ) ) ;
2017-06-21 11:11:38 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/info " ) , " Made with Godot Engine " ) ) ;
2019-05-17 07:49:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/icon " , PROPERTY_HINT_FILE , " *.png,*.icns " ) , " " ) ) ;
2020-03-22 17:27:32 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/bundle_identifier " , PROPERTY_HINT_PLACEHOLDER_TEXT , " com.example.game " ) , " " ) ) ;
2018-08-17 03:50:12 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/signature " ) , " " ) ) ;
2017-06-21 11:11:38 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/short_version " ) , " 1.0 " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/version " ) , " 1.0 " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/copyright " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " display/high_res " ) , false ) ) ;
2019-12-13 22:10:23 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/camera_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use the camera " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/microphone_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use the microphone " ) , " " ) ) ;
2017-07-02 13:23:33 +02:00
# ifdef OSX_ENABLED
2021-03-28 23:04:48 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/enable " ) , true ) ) ;
2019-10-04 17:01:13 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " codesign/identity " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Type: Name (ID) " ) , " " ) ) ;
2019-10-02 20:00:16 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/timestamp " ) , true ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/hardened_runtime " ) , true ) ) ;
2021-03-18 22:00:05 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/replace_existing_signature " ) , true ) ) ;
2021-03-03 13:13:55 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " codesign/entitlements/custom_file " , PROPERTY_HINT_GLOBAL_FILE , " *.plist " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/allow_jit_code_execution " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/allow_unsigned_executable_memory " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/allow_dyld_environment_variables " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/disable_library_validation " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/audio_input " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/camera " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/location " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/address_book " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/calendars " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/photos_library " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/apple_events " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/app_sandbox/enabled " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/app_sandbox/network_server " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/app_sandbox/network_client " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/app_sandbox/device_usb " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/app_sandbox/device_bluetooth " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " codesign/entitlements/app_sandbox/files_downloads " , PROPERTY_HINT_ENUM , " No,Read-only,Read-write " ) , 0 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " codesign/entitlements/app_sandbox/files_pictures " , PROPERTY_HINT_ENUM , " No,Read-only,Read-write " ) , 0 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " codesign/entitlements/app_sandbox/files_music " , PROPERTY_HINT_ENUM , " No,Read-only,Read-write " ) , 0 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " codesign/entitlements/app_sandbox/files_movies " , PROPERTY_HINT_ENUM , " No,Read-only,Read-write " ) , 0 ) ) ;
2020-02-17 22:06:54 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : PACKED_STRING_ARRAY , " codesign/custom_options " ) , PackedStringArray ( ) ) ) ;
2020-06-10 14:28:50 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " notarization/enable " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " notarization/apple_id_name " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Apple ID email " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " notarization/apple_id_password " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Enable two-factor authentication and provide app-specific password " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " notarization/apple_team_id " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide team ID if your Apple ID belongs to multiple teams " ) , " " ) ) ;
2017-07-02 13:23:33 +02:00
# endif
2017-09-08 03:39:32 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " texture_format/s3tc " ) , true ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " texture_format/etc " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " texture_format/etc2 " ) , false ) ) ;
2014-02-10 02:10:30 +01:00
}
2020-02-17 22:06:54 +01:00
void _rgba8_to_packbits_encode ( int p_ch , int p_size , Vector < uint8_t > & p_source , Vector < uint8_t > & p_dest ) {
2018-11-26 15:01:38 +01:00
int src_len = p_size * p_size ;
Vector < uint8_t > result ;
result . resize ( src_len * 1.25 ) ; //temp vector for rle encoded data, make it 25% larger for worst case scenario
int res_size = 0 ;
uint8_t buf [ 128 ] ;
int buf_size = 0 ;
int i = 0 ;
while ( i < src_len ) {
2020-02-17 22:06:54 +01:00
uint8_t cur = p_source . ptr ( ) [ i * 4 + p_ch ] ;
2018-11-26 15:01:38 +01:00
if ( i < src_len - 2 ) {
2020-02-17 22:06:54 +01:00
if ( ( p_source . ptr ( ) [ ( i + 1 ) * 4 + p_ch ] = = cur ) & & ( p_source . ptr ( ) [ ( i + 2 ) * 4 + p_ch ] = = cur ) ) {
2018-11-26 15:01:38 +01:00
if ( buf_size > 0 ) {
result . write [ res_size + + ] = ( uint8_t ) ( buf_size - 1 ) ;
2021-04-27 16:19:21 +02:00
memcpy ( & result . write [ res_size ] , & buf , buf_size ) ;
2018-11-26 15:01:38 +01:00
res_size + = buf_size ;
buf_size = 0 ;
}
uint8_t lim = i + 130 > = src_len ? src_len - i - 1 : 130 ;
bool hit_lim = true ;
for ( int j = 3 ; j < = lim ; j + + ) {
2020-02-17 22:06:54 +01:00
if ( p_source . ptr ( ) [ ( i + j ) * 4 + p_ch ] ! = cur ) {
2018-11-26 15:01:38 +01:00
hit_lim = false ;
i = i + j - 1 ;
result . write [ res_size + + ] = ( uint8_t ) ( j - 3 + 0x80 ) ;
result . write [ res_size + + ] = cur ;
break ;
}
}
if ( hit_lim ) {
result . write [ res_size + + ] = ( uint8_t ) ( lim - 3 + 0x80 ) ;
result . write [ res_size + + ] = cur ;
i = i + lim ;
}
} else {
buf [ buf_size + + ] = cur ;
if ( buf_size = = 128 ) {
result . write [ res_size + + ] = ( uint8_t ) ( buf_size - 1 ) ;
2021-04-27 16:19:21 +02:00
memcpy ( & result . write [ res_size ] , & buf , buf_size ) ;
2018-11-26 15:01:38 +01:00
res_size + = buf_size ;
buf_size = 0 ;
}
}
} else {
buf [ buf_size + + ] = cur ;
result . write [ res_size + + ] = ( uint8_t ) ( buf_size - 1 ) ;
2021-04-27 16:19:21 +02:00
memcpy ( & result . write [ res_size ] , & buf , buf_size ) ;
2018-11-26 15:01:38 +01:00
res_size + = buf_size ;
buf_size = 0 ;
}
i + + ;
}
int ofs = p_dest . size ( ) ;
p_dest . resize ( p_dest . size ( ) + res_size ) ;
2021-04-27 16:19:21 +02:00
memcpy ( & p_dest . write [ ofs ] , result . ptr ( ) , res_size ) ;
2018-11-26 15:01:38 +01:00
}
2017-06-21 11:11:38 +02:00
void EditorExportPlatformOSX : : _make_icon ( const Ref < Image > & p_icon , Vector < uint8_t > & p_data ) {
Ref < ImageTexture > it = memnew ( ImageTexture ) ;
2014-02-10 02:10:30 +01:00
Vector < uint8_t > data ;
data . resize ( 8 ) ;
2018-07-25 03:11:03 +02:00
data . write [ 0 ] = ' i ' ;
data . write [ 1 ] = ' c ' ;
data . write [ 2 ] = ' n ' ;
data . write [ 3 ] = ' s ' ;
2014-02-10 02:10:30 +01:00
2018-11-26 15:01:38 +01:00
struct MacOSIconInfo {
const char * name ;
const char * mask_name ;
bool is_png ;
int size ;
} ;
static const MacOSIconInfo icon_infos [ ] = {
{ " ic10 " , " " , true , 1024 } , //1024x1024 32-bit PNG and 512x512@2x 32-bit "retina" PNG
{ " ic09 " , " " , true , 512 } , //512× 512 32-bit PNG
{ " ic14 " , " " , true , 512 } , //256x256@2x 32-bit "retina" PNG
{ " ic08 " , " " , true , 256 } , //256× 256 32-bit PNG
{ " ic13 " , " " , true , 256 } , //128x128@2x 32-bit "retina" PNG
{ " ic07 " , " " , true , 128 } , //128x128 32-bit PNG
{ " ic12 " , " " , true , 64 } , //32x32@2x 32-bit "retina" PNG
{ " ic11 " , " " , true , 32 } , //16x16@2x 32-bit "retina" PNG
{ " il32 " , " l8mk " , false , 32 } , //32x32 24-bit RLE + 8-bit uncompressed mask
{ " is32 " , " s8mk " , false , 16 } //16x16 24-bit RLE + 8-bit uncompressed mask
} ;
2019-09-22 18:45:08 +02:00
for ( uint64_t i = 0 ; i < ( sizeof ( icon_infos ) / sizeof ( icon_infos [ 0 ] ) ) ; + + i ) {
2017-06-21 11:11:38 +02:00
Ref < Image > copy = p_icon ; // does this make sense? doesn't this just increase the reference count instead of making a copy? Do we even need a copy?
copy - > convert ( Image : : FORMAT_RGBA8 ) ;
2018-11-26 15:01:38 +01:00
copy - > resize ( icon_infos [ i ] . size , icon_infos [ i ] . size ) ;
if ( icon_infos [ i ] . is_png ) {
2019-08-09 13:45:30 +02:00
// Encode PNG icon.
2018-11-26 15:01:38 +01:00
it - > create_from_image ( copy ) ;
2021-05-25 02:25:11 +02:00
String path = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( " icon.png " ) ;
2018-11-26 15:01:38 +01:00
ResourceSaver : : save ( path , it ) ;
FileAccess * f = FileAccess : : open ( path , FileAccess : : READ ) ;
2019-08-09 13:45:30 +02:00
if ( ! f ) {
// Clean up generated file.
DirAccess : : remove_file_or_error ( path ) ;
ERR_FAIL ( ) ;
}
2018-11-26 15:01:38 +01:00
int ofs = data . size ( ) ;
2021-05-25 08:58:49 +02:00
uint64_t len = f - > get_length ( ) ;
2018-11-26 15:01:38 +01:00
data . resize ( data . size ( ) + len + 8 ) ;
f - > get_buffer ( & data . write [ ofs + 8 ] , len ) ;
memdelete ( f ) ;
len + = 8 ;
len = BSWAP32 ( len ) ;
2021-04-27 16:19:21 +02:00
memcpy ( & data . write [ ofs ] , icon_infos [ i ] . name , 4 ) ;
2018-11-26 15:01:38 +01:00
encode_uint32 ( len , & data . write [ ofs + 4 ] ) ;
2019-08-09 13:45:30 +02:00
// Clean up generated file.
DirAccess : : remove_file_or_error ( path ) ;
2018-11-26 15:01:38 +01:00
} else {
2020-02-17 22:06:54 +01:00
Vector < uint8_t > src_data = copy - > get_data ( ) ;
2018-11-26 15:01:38 +01:00
//encode 24bit RGB RLE icon
{
int ofs = data . size ( ) ;
data . resize ( data . size ( ) + 8 ) ;
_rgba8_to_packbits_encode ( 0 , icon_infos [ i ] . size , src_data , data ) ; // encode R
_rgba8_to_packbits_encode ( 1 , icon_infos [ i ] . size , src_data , data ) ; // encode G
_rgba8_to_packbits_encode ( 2 , icon_infos [ i ] . size , src_data , data ) ; // encode B
int len = data . size ( ) - ofs ;
len = BSWAP32 ( len ) ;
2021-04-27 16:19:21 +02:00
memcpy ( & data . write [ ofs ] , icon_infos [ i ] . name , 4 ) ;
2018-11-26 15:01:38 +01:00
encode_uint32 ( len , & data . write [ ofs + 4 ] ) ;
}
//encode 8bit mask uncompressed icon
{
int ofs = data . size ( ) ;
int len = copy - > get_width ( ) * copy - > get_height ( ) ;
data . resize ( data . size ( ) + len + 8 ) ;
for ( int j = 0 ; j < len ; j + + ) {
2020-02-17 22:06:54 +01:00
data . write [ ofs + 8 + j ] = src_data . ptr ( ) [ j * 4 + 3 ] ;
2018-11-26 15:01:38 +01:00
}
len + = 8 ;
len = BSWAP32 ( len ) ;
2021-04-27 16:19:21 +02:00
memcpy ( & data . write [ ofs ] , icon_infos [ i ] . mask_name , 4 ) ;
2018-11-26 15:01:38 +01:00
encode_uint32 ( len , & data . write [ ofs + 4 ] ) ;
}
}
2014-02-10 02:10:30 +01:00
}
uint32_t total_len = data . size ( ) ;
total_len = BSWAP32 ( total_len ) ;
2018-07-25 03:11:03 +02:00
encode_uint32 ( total_len , & data . write [ 4 ] ) ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
p_data = data ;
2014-02-10 02:10:30 +01:00
}
2017-06-21 11:11:38 +02:00
void EditorExportPlatformOSX : : _fix_plist ( const Ref < EditorExportPreset > & p_preset , Vector < uint8_t > & plist , const String & p_binary ) {
2014-02-10 02:10:30 +01:00
String str ;
String strnew ;
2017-06-21 11:11:38 +02:00
str . parse_utf8 ( ( const char * ) plist . ptr ( ) , plist . size ( ) ) ;
Vector < String > lines = str . split ( " \n " ) ;
for ( int i = 0 ; i < lines . size ( ) ; i + + ) {
if ( lines [ i ] . find ( " $binary " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $binary " , p_binary ) + " \n " ;
} else if ( lines [ i ] . find ( " $name " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $name " , p_binary ) + " \n " ;
} else if ( lines [ i ] . find ( " $info " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $info " , p_preset - > get ( " application/info " ) ) + " \n " ;
2020-03-22 17:27:32 +01:00
} else if ( lines [ i ] . find ( " $bundle_identifier " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $bundle_identifier " , p_preset - > get ( " application/bundle_identifier " ) ) + " \n " ;
2017-06-21 11:11:38 +02:00
} else if ( lines [ i ] . find ( " $short_version " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $short_version " , p_preset - > get ( " application/short_version " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $version " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $version " , p_preset - > get ( " application/version " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $signature " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $signature " , p_preset - > get ( " application/signature " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $copyright " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $copyright " , p_preset - > get ( " application/copyright " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $highres " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $highres " , p_preset - > get ( " display/high_res " ) ? " <true/> " : " <false/> " ) + " \n " ;
2019-12-13 22:10:23 +01:00
} else if ( lines [ i ] . find ( " $camera_usage_description " ) ! = - 1 ) {
String description = p_preset - > get ( " privacy/camera_usage_description " ) ;
strnew + = lines [ i ] . replace ( " $camera_usage_description " , description ) + " \n " ;
} else if ( lines [ i ] . find ( " $microphone_usage_description " ) ! = - 1 ) {
String description = p_preset - > get ( " privacy/microphone_usage_description " ) ;
strnew + = lines [ i ] . replace ( " $microphone_usage_description " , description ) + " \n " ;
2014-02-10 02:10:30 +01:00
} else {
2017-06-21 11:11:38 +02:00
strnew + = lines [ i ] + " \n " ;
2014-02-10 02:10:30 +01:00
}
}
CharString cs = strnew . utf8 ( ) ;
2017-07-05 16:14:05 +02:00
plist . resize ( cs . size ( ) - 1 ) ;
for ( int i = 0 ; i < cs . size ( ) - 1 ; i + + ) {
2018-07-25 03:11:03 +02:00
plist . write [ i ] = cs [ i ] ;
2014-02-10 02:10:30 +01:00
}
}
2017-07-02 13:23:33 +02:00
/**
If we ' re running the OSX version of the Godot editor we ' ll :
- export our application bundle to a temporary folder
- attempt to code sign it
- and then wrap it up in a DMG
* */
2020-06-10 14:28:50 +02:00
Error EditorExportPlatformOSX : : _notarize ( const Ref < EditorExportPreset > & p_preset , const String & p_path ) {
# ifdef OSX_ENABLED
List < String > args ;
args . push_back ( " altool " ) ;
args . push_back ( " --notarize-app " ) ;
args . push_back ( " --primary-bundle-id " ) ;
2020-03-22 17:27:32 +01:00
args . push_back ( p_preset - > get ( " application/bundle_identifier " ) ) ;
2020-06-10 14:28:50 +02:00
args . push_back ( " --username " ) ;
args . push_back ( p_preset - > get ( " notarization/apple_id_name " ) ) ;
args . push_back ( " --password " ) ;
args . push_back ( p_preset - > get ( " notarization/apple_id_password " ) ) ;
args . push_back ( " --type " ) ;
args . push_back ( " osx " ) ;
if ( p_preset - > get ( " notarization/apple_team_id " ) ) {
args . push_back ( " --asc-provider " ) ;
args . push_back ( p_preset - > get ( " notarization/apple_team_id " ) ) ;
}
args . push_back ( " --file " ) ;
args . push_back ( p_path ) ;
String str ;
2020-12-18 19:49:13 +01:00
Error err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & str , nullptr , true ) ;
2020-06-10 14:28:50 +02:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
print_line ( " altool ( " + p_path + " ): \n " + str ) ;
if ( str . find ( " RequestUUID " ) = = - 1 ) {
EditorNode : : add_io_error ( " altool: " + str ) ;
return FAILED ;
} else {
print_line ( " Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email. " ) ;
print_line ( " You can check progress manually by opening a Terminal and running the following command: " ) ;
print_line ( " \" xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd> \" " ) ;
}
# endif
return OK ;
}
2021-03-03 13:13:55 +01:00
Error EditorExportPlatformOSX : : _code_sign ( const Ref < EditorExportPreset > & p_preset , const String & p_path , const String & p_ent_path ) {
2019-11-08 13:38:23 +01:00
# ifdef OSX_ENABLED
2017-07-02 13:23:33 +02:00
List < String > args ;
2017-10-03 20:09:57 +02:00
2019-10-02 20:00:16 +02:00
if ( p_preset - > get ( " codesign/timestamp " ) ) {
args . push_back ( " --timestamp " ) ;
}
if ( p_preset - > get ( " codesign/hardened_runtime " ) ) {
args . push_back ( " --options " ) ;
args . push_back ( " runtime " ) ;
}
2021-03-03 13:13:55 +01:00
if ( p_path . get_extension ( ) ! = " dmg " ) {
2019-10-02 20:00:16 +02:00
args . push_back ( " --entitlements " ) ;
2021-03-03 13:13:55 +01:00
args . push_back ( p_ent_path ) ;
2017-07-02 13:23:33 +02:00
}
2019-10-04 17:01:13 +02:00
2020-02-17 22:06:54 +01:00
PackedStringArray user_args = p_preset - > get ( " codesign/custom_options " ) ;
2019-10-04 17:01:13 +02:00
for ( int i = 0 ; i < user_args . size ( ) ; i + + ) {
String user_arg = user_args [ i ] . strip_edges ( ) ;
2020-12-15 13:04:21 +01:00
if ( ! user_arg . is_empty ( ) ) {
2019-10-04 17:01:13 +02:00
args . push_back ( user_arg ) ;
}
}
2017-07-02 13:23:33 +02:00
args . push_back ( " -s " ) ;
2021-03-28 23:04:48 +02:00
if ( p_preset - > get ( " codesign/identity " ) = = " " ) {
args . push_back ( " - " ) ;
} else {
args . push_back ( p_preset - > get ( " codesign/identity " ) ) ;
}
2019-10-04 17:01:13 +02:00
2017-07-02 13:23:33 +02:00
args . push_back ( " -v " ) ; /* provide some more feedback */
2019-10-04 17:01:13 +02:00
2021-03-18 22:00:05 +01:00
if ( p_preset - > get ( " codesign/replace_existing_signature " ) ) {
args . push_back ( " -f " ) ;
}
2017-07-02 13:23:33 +02:00
args . push_back ( p_path ) ;
2017-10-03 20:09:57 +02:00
String str ;
2020-12-18 19:49:13 +01:00
Error err = OS : : get_singleton ( ) - > execute ( " codesign " , args , & str , nullptr , true ) ;
2017-10-03 20:09:57 +02:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
2020-06-10 14:28:50 +02:00
print_line ( " codesign ( " + p_path + " ): \n " + str ) ;
2017-10-03 20:09:57 +02:00
if ( str . find ( " no identity found " ) ! = - 1 ) {
EditorNode : : add_io_error ( " codesign: no identity found " ) ;
return FAILED ;
}
2019-10-02 20:00:16 +02:00
if ( ( str . find ( " unrecognized blob type " ) ! = - 1 ) | | ( str . find ( " cannot read entitlement data " ) ! = - 1 ) ) {
EditorNode : : add_io_error ( " codesign: invalid entitlements file " ) ;
return FAILED ;
}
2019-11-08 13:38:23 +01:00
# endif
2017-07-02 13:23:33 +02:00
return OK ;
}
Error EditorExportPlatformOSX : : _create_dmg ( const String & p_dmg_path , const String & p_pkg_name , const String & p_app_path_name ) {
List < String > args ;
2017-10-03 20:09:57 +02:00
2019-10-02 20:00:16 +02:00
if ( FileAccess : : exists ( p_dmg_path ) ) {
OS : : get_singleton ( ) - > move_to_trash ( p_dmg_path ) ;
}
2017-10-03 20:09:57 +02:00
2017-07-02 13:23:33 +02:00
args . push_back ( " create " ) ;
args . push_back ( p_dmg_path ) ;
args . push_back ( " -volname " ) ;
args . push_back ( p_pkg_name ) ;
args . push_back ( " -fs " ) ;
args . push_back ( " HFS+ " ) ;
args . push_back ( " -srcfolder " ) ;
args . push_back ( p_app_path_name ) ;
2017-10-03 20:09:57 +02:00
String str ;
2020-12-18 19:49:13 +01:00
Error err = OS : : get_singleton ( ) - > execute ( " hdiutil " , args , & str , nullptr , true ) ;
2017-10-03 20:09:57 +02:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
print_line ( " hdiutil returned: " + str ) ;
if ( str . find ( " create failed " ) ! = - 1 ) {
if ( str . find ( " File exists " ) ! = - 1 ) {
EditorNode : : add_io_error ( " hdiutil: create failed - file exists " ) ;
} else {
EditorNode : : add_io_error ( " hdiutil: create failed " ) ;
}
return FAILED ;
}
2017-07-02 13:23:33 +02:00
return OK ;
}
2017-06-21 11:11:38 +02:00
Error EditorExportPlatformOSX : : export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags ) {
2017-10-02 17:01:43 +02:00
ExportNotifier notifier ( * this , p_preset , p_debug , p_path , p_flags ) ;
2014-02-10 02:10:30 +01:00
2017-07-02 13:23:33 +02:00
String src_pkg_name ;
2014-02-10 02:10:30 +01:00
2018-12-19 19:50:40 +01:00
EditorProgress ep ( " export " , " Exporting for OSX " , 3 , true ) ;
2014-02-10 02:10:30 +01:00
2020-05-14 16:41:43 +02:00
if ( p_debug ) {
2020-01-07 13:29:02 +01:00
src_pkg_name = p_preset - > get ( " custom_template/debug " ) ;
2020-05-14 16:41:43 +02:00
} else {
2020-01-07 13:29:02 +01:00
src_pkg_name = p_preset - > get ( " custom_template/release " ) ;
2020-05-14 16:41:43 +02:00
}
2015-11-30 01:26:51 +01:00
2017-07-02 13:23:33 +02:00
if ( src_pkg_name = = " " ) {
2015-11-30 01:26:51 +01:00
String err ;
2017-07-02 13:23:33 +02:00
src_pkg_name = find_export_template ( " osx.zip " , & err ) ;
if ( src_pkg_name = = " " ) {
2015-11-30 01:26:51 +01:00
EditorNode : : add_io_error ( err ) ;
return ERR_FILE_NOT_FOUND ;
}
2014-02-10 02:10:30 +01:00
}
2019-03-06 13:20:18 +01:00
if ( ! DirAccess : : exists ( p_path . get_base_dir ( ) ) ) {
2019-03-05 08:52:45 +01:00
return ERR_FILE_BAD_PATH ;
}
2020-04-02 01:20:12 +02:00
FileAccess * src_f = nullptr ;
2014-02-10 02:10:30 +01:00
zlib_filefunc_def io = zipio_create_io_from_file ( & src_f ) ;
2018-12-19 19:50:40 +01:00
if ( ep . step ( " Creating app " , 0 ) ) {
return ERR_SKIP ;
}
2014-02-10 02:10:30 +01:00
2017-07-02 13:23:33 +02:00
unzFile src_pkg_zip = unzOpen2 ( src_pkg_name . utf8 ( ) . get_data ( ) , & io ) ;
if ( ! src_pkg_zip ) {
EditorNode : : add_io_error ( " Could not find template app to export: \n " + src_pkg_name ) ;
2014-02-10 02:10:30 +01:00
return ERR_FILE_NOT_FOUND ;
}
2017-07-02 13:23:33 +02:00
int ret = unzGoToFirstFile ( src_pkg_zip ) ;
2014-02-10 02:10:30 +01:00
2018-02-19 12:49:31 +01:00
String binary_to_use = " godot_osx_ " + String ( p_debug ? " debug " : " release " ) + " .64 " ;
2014-02-10 02:10:30 +01:00
String pkg_name ;
2020-05-14 16:41:43 +02:00
if ( p_preset - > get ( " application/name " ) ! = " " ) {
2017-06-21 11:11:38 +02:00
pkg_name = p_preset - > get ( " application/name " ) ; // app_name
2020-05-14 16:41:43 +02:00
} else if ( String ( ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ) ! = " " ) {
2017-07-19 22:00:46 +02:00
pkg_name = String ( ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ) ;
2020-05-14 16:41:43 +02:00
} else {
2017-06-21 11:11:38 +02:00
pkg_name = " Unnamed " ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2020-12-03 08:55:09 +01:00
pkg_name = OS : : get_singleton ( ) - > get_safe_dir_name ( pkg_name ) ;
2017-10-03 20:09:57 +02:00
2018-10-29 22:18:49 +01:00
String export_format = use_dmg ( ) & & p_path . ends_with ( " dmg " ) ? " dmg " : " zip " ;
2017-07-02 13:23:33 +02:00
2019-11-08 13:38:23 +01:00
// Create our application bundle.
2020-12-04 14:16:49 +01:00
String tmp_app_dir_name = pkg_name + " .app " ;
2021-05-25 02:25:11 +02:00
String tmp_app_path_name = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( tmp_app_dir_name ) ;
2019-11-08 13:38:23 +01:00
print_line ( " Exporting to " + tmp_app_path_name ) ;
2020-12-03 08:55:09 +01:00
Error err = OK ;
DirAccessRef tmp_app_dir = DirAccess : : create_for_path ( tmp_app_path_name ) ;
if ( ! tmp_app_dir ) {
2019-11-08 13:38:23 +01:00
err = ERR_CANT_CREATE ;
}
2017-07-02 13:23:33 +02:00
2019-11-08 13:38:23 +01:00
// Create our folder structure.
if ( err = = OK ) {
print_line ( " Creating " + tmp_app_path_name + " /Contents/MacOS " ) ;
2020-12-03 08:55:09 +01:00
err = tmp_app_dir - > make_dir_recursive ( tmp_app_path_name + " /Contents/MacOS " ) ;
2019-11-08 13:38:23 +01:00
}
2018-01-04 20:36:44 +01:00
2019-11-08 13:38:23 +01:00
if ( err = = OK ) {
print_line ( " Creating " + tmp_app_path_name + " /Contents/Frameworks " ) ;
2020-12-03 08:55:09 +01:00
err = tmp_app_dir - > make_dir_recursive ( tmp_app_path_name + " /Contents/Frameworks " ) ;
2017-10-03 20:09:57 +02:00
}
2017-07-02 13:23:33 +02:00
2019-11-08 13:38:23 +01:00
if ( err = = OK ) {
print_line ( " Creating " + tmp_app_path_name + " /Contents/Resources " ) ;
2020-12-03 08:55:09 +01:00
err = tmp_app_dir - > make_dir_recursive ( tmp_app_path_name + " /Contents/Resources " ) ;
2019-11-08 13:38:23 +01:00
}
// Now process our template.
2016-07-09 00:46:10 +02:00
bool found_binary = false ;
2017-07-02 13:23:33 +02:00
int total_size = 0 ;
2021-03-03 13:13:55 +01:00
Vector < String > dylibs_found ;
2016-07-09 00:46:10 +02:00
2017-10-03 20:09:57 +02:00
while ( ret = = UNZ_OK & & err = = OK ) {
2017-07-02 13:23:33 +02:00
bool is_execute = false ;
2014-02-10 02:10:30 +01:00
2019-11-08 13:38:23 +01:00
// Get filename.
2014-02-10 02:10:30 +01:00
unz_file_info info ;
char fname [ 16384 ] ;
2020-04-02 01:20:12 +02:00
ret = unzGetCurrentFileInfo ( src_pkg_zip , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
String file = fname ;
2014-02-10 02:10:30 +01:00
Vector < uint8_t > data ;
data . resize ( info . uncompressed_size ) ;
2019-11-08 13:38:23 +01:00
// Read.
2017-07-02 13:23:33 +02:00
unzOpenCurrentFile ( src_pkg_zip ) ;
2017-11-25 04:07:54 +01:00
unzReadCurrentFile ( src_pkg_zip , data . ptrw ( ) , data . size ( ) ) ;
2017-07-02 13:23:33 +02:00
unzCloseCurrentFile ( src_pkg_zip ) ;
2014-02-10 02:10:30 +01:00
2019-11-08 13:38:23 +01:00
// Write.
2017-06-21 11:11:38 +02:00
file = file . replace_first ( " osx_template.app/ " , " " ) ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
if ( file = = " Contents/Info.plist " ) {
_fix_plist ( p_preset , data , pkg_name ) ;
2014-02-10 02:10:30 +01:00
}
if ( file . begins_with ( " Contents/MacOS/godot_ " ) ) {
2017-06-21 11:11:38 +02:00
if ( file ! = " Contents/MacOS/ " + binary_to_use ) {
2017-07-02 13:23:33 +02:00
ret = unzGoToNextFile ( src_pkg_zip ) ;
2019-11-08 13:38:23 +01:00
continue ; // skip
2014-02-10 02:10:30 +01:00
}
2016-07-09 00:46:10 +02:00
found_binary = true ;
2017-07-02 13:23:33 +02:00
is_execute = true ;
2017-06-21 11:11:38 +02:00
file = " Contents/MacOS/ " + pkg_name ;
2014-02-10 02:10:30 +01:00
}
2017-06-21 11:11:38 +02:00
if ( file = = " Contents/Resources/icon.icns " ) {
2019-11-08 13:38:23 +01:00
// See if there is an icon.
2017-06-21 11:11:38 +02:00
String iconpath ;
2020-05-14 16:41:43 +02:00
if ( p_preset - > get ( " application/icon " ) ! = " " ) {
2017-06-21 11:11:38 +02:00
iconpath = p_preset - > get ( " application/icon " ) ;
2020-05-14 16:41:43 +02:00
} else {
2017-07-19 22:00:46 +02:00
iconpath = ProjectSettings : : get_singleton ( ) - > get ( " application/config/icon " ) ;
2020-05-14 16:41:43 +02:00
}
2018-08-24 09:35:07 +02:00
2017-06-21 11:11:38 +02:00
if ( iconpath ! = " " ) {
2019-05-17 07:49:21 +02:00
if ( iconpath . get_extension ( ) = = " icns " ) {
FileAccess * icon = FileAccess : : open ( iconpath , FileAccess : : READ ) ;
if ( icon ) {
2021-05-25 08:58:49 +02:00
data . resize ( icon - > get_length ( ) ) ;
icon - > get_buffer ( & data . write [ 0 ] , icon - > get_length ( ) ) ;
2019-05-17 07:49:21 +02:00
icon - > close ( ) ;
memdelete ( icon ) ;
}
} else {
Ref < Image > icon ;
icon . instance ( ) ;
icon - > load ( iconpath ) ;
2020-12-15 13:04:21 +01:00
if ( ! icon - > is_empty ( ) ) {
2019-05-17 07:49:21 +02:00
_make_icon ( icon , data ) ;
}
2014-02-10 02:10:30 +01:00
}
}
}
2017-07-02 13:23:33 +02:00
if ( data . size ( ) > 0 ) {
2020-01-10 19:08:01 +01:00
if ( file . find ( " /data.mono.osx.64.release_debug/ " ) ! = - 1 ) {
if ( ! p_debug ) {
ret = unzGoToNextFile ( src_pkg_zip ) ;
2019-11-08 13:38:23 +01:00
continue ; // skip
2020-01-10 19:08:01 +01:00
}
2020-11-22 07:17:15 +01:00
file = file . replace ( " /data.mono.osx.64.release_debug/ " , " /GodotSharp/ " ) ;
2020-01-10 19:08:01 +01:00
}
if ( file . find ( " /data.mono.osx.64.release/ " ) ! = - 1 ) {
if ( p_debug ) {
ret = unzGoToNextFile ( src_pkg_zip ) ;
2019-11-08 13:38:23 +01:00
continue ; // skip
2020-01-10 19:08:01 +01:00
}
2020-11-22 07:17:15 +01:00
file = file . replace ( " /data.mono.osx.64.release/ " , " /GodotSharp/ " ) ;
2020-01-10 19:08:01 +01:00
}
2021-03-03 13:13:55 +01:00
if ( file . ends_with ( " .dylib " ) ) {
dylibs_found . push_back ( file ) ;
}
2017-07-02 13:23:33 +02:00
print_line ( " ADDING: " + file + " size: " + itos ( data . size ( ) ) ) ;
total_size + = data . size ( ) ;
2019-11-08 13:38:23 +01:00
// Write it into our application bundle.
file = tmp_app_path_name . plus_file ( file ) ;
if ( err = = OK ) {
2020-12-03 08:55:09 +01:00
err = tmp_app_dir - > make_dir_recursive ( file . get_base_dir ( ) ) ;
2019-11-08 13:38:23 +01:00
}
if ( err = = OK ) {
FileAccess * f = FileAccess : : open ( file , FileAccess : : WRITE ) ;
if ( f ) {
f - > store_buffer ( data . ptr ( ) , data . size ( ) ) ;
f - > close ( ) ;
if ( is_execute ) {
// chmod with 0755 if the file is executable.
FileAccess : : set_unix_permissions ( file , 0755 ) ;
2017-10-03 20:09:57 +02:00
}
2019-11-08 13:38:23 +01:00
memdelete ( f ) ;
} else {
err = ERR_CANT_CREATE ;
2017-10-03 20:09:57 +02:00
}
2017-07-02 13:23:33 +02:00
}
}
ret = unzGoToNextFile ( src_pkg_zip ) ;
}
2019-11-08 13:38:23 +01:00
// We're done with our source zip.
2017-07-02 13:23:33 +02:00
unzClose ( src_pkg_zip ) ;
if ( ! found_binary ) {
2019-11-06 17:03:04 +01:00
ERR_PRINT ( " Requested template binary ' " + binary_to_use + " ' not found. It might be missing from your template archive. " ) ;
2017-10-03 20:09:57 +02:00
err = ERR_FILE_NOT_FOUND ;
2017-07-02 13:23:33 +02:00
}
2017-10-03 20:09:57 +02:00
if ( err = = OK ) {
2018-12-19 19:50:40 +01:00
if ( ep . step ( " Making PKG " , 1 ) ) {
return ERR_SKIP ;
}
2017-07-02 13:23:33 +02:00
2019-11-08 13:38:23 +01:00
String pack_path = tmp_app_path_name + " /Contents/Resources/ " + pkg_name + " .pck " ;
Vector < SharedObject > shared_objects ;
err = save_pack ( p_preset , pack_path , & shared_objects ) ;
2017-07-02 13:23:33 +02:00
2019-11-08 13:38:23 +01:00
// See if we can code sign our new package.
bool sign_enabled = p_preset - > get ( " codesign/enable " ) ;
2018-01-04 20:36:44 +01:00
2021-03-03 13:13:55 +01:00
String ent_path = p_preset - > get ( " codesign/entitlements/custom_file " ) ;
if ( sign_enabled & & ( ent_path = = " " ) ) {
2021-05-25 02:25:11 +02:00
ent_path = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( pkg_name + " .entitlements " ) ;
2021-03-03 13:13:55 +01:00
FileAccess * ent_f = FileAccess : : open ( ent_path , FileAccess : : WRITE ) ;
if ( ent_f ) {
ent_f - > store_line ( " <?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> " ) ;
ent_f - > store_line ( " <!DOCTYPE plist PUBLIC \" -//Apple//DTD PLIST 1.0//EN \" \" http://www.apple.com/DTDs/PropertyList-1.0.dtd \" > " ) ;
ent_f - > store_line ( " <plist version= \" 1.0 \" > " ) ;
ent_f - > store_line ( " <dict> " ) ;
if ( ( bool ) p_preset - > get ( " codesign/entitlements/allow_jit_code_execution " ) ) {
ent_f - > store_line ( " <key>com.apple.security.cs.allow-jit</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/allow_unsigned_executable_memory " ) ) {
ent_f - > store_line ( " <key>com.apple.security.cs.allow-unsigned-executable-memory</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/allow_dyld_environment_variables " ) ) {
ent_f - > store_line ( " <key>com.apple.security.cs.allow-dyld-environment-variables</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/disable_library_validation " ) ) {
ent_f - > store_line ( " <key>com.apple.security.cs.disable-library-validation</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/audio_input " ) ) {
ent_f - > store_line ( " <key>com.apple.security.device.audio-input</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/camera " ) ) {
ent_f - > store_line ( " <key>com.apple.security.device.camera</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/location " ) ) {
ent_f - > store_line ( " <key>com.apple.security.personal-information.location</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/address_book " ) ) {
ent_f - > store_line ( " <key>com.apple.security.personal-information.addressbook</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/calendars " ) ) {
ent_f - > store_line ( " <key>com.apple.security.personal-information.calendars</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/photos_library " ) ) {
ent_f - > store_line ( " <key>com.apple.security.personal-information.photos-library</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/apple_events " ) ) {
ent_f - > store_line ( " <key>com.apple.security.automation.apple-events</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/enabled " ) ) {
ent_f - > store_line ( " <key>com.apple.security.app-sandbox</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/network_server " ) ) {
ent_f - > store_line ( " <key>com.apple.security.network.server</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/network_client " ) ) {
ent_f - > store_line ( " <key>com.apple.security.network.client</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/device_usb " ) ) {
ent_f - > store_line ( " <key>com.apple.security.device.usb</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/device_bluetooth " ) ) {
ent_f - > store_line ( " <key>com.apple.security.device.bluetooth</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_downloads " ) = = 1 ) {
ent_f - > store_line ( " <key>com.apple.security.files.downloads.read-only</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_downloads " ) = = 2 ) {
ent_f - > store_line ( " <key>com.apple.security.files.downloads.read-write</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_pictures " ) = = 1 ) {
ent_f - > store_line ( " <key>com.apple.security.files.pictures.read-only</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_pictures " ) = = 2 ) {
ent_f - > store_line ( " <key>com.apple.security.files.pictures.read-write</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_music " ) = = 1 ) {
ent_f - > store_line ( " <key>com.apple.security.files.music.read-only</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_music " ) = = 2 ) {
ent_f - > store_line ( " <key>com.apple.security.files.music.read-write</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_movies " ) = = 1 ) {
ent_f - > store_line ( " <key>com.apple.security.files.movies.read-only</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_movies " ) = = 2 ) {
ent_f - > store_line ( " <key>com.apple.security.files.movies.read-write</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
}
ent_f - > store_line ( " </dict> " ) ;
ent_f - > store_line ( " </plist> " ) ;
ent_f - > close ( ) ;
memdelete ( ent_f ) ;
} else {
err = ERR_CANT_CREATE ;
}
}
2019-11-08 13:38:23 +01:00
if ( err = = OK ) {
DirAccess * da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
for ( int i = 0 ; i < shared_objects . size ( ) ; i + + ) {
2021-03-10 11:56:15 +01:00
String src_path = ProjectSettings : : get_singleton ( ) - > globalize_path ( shared_objects [ i ] . path ) ;
if ( da - > dir_exists ( src_path ) ) {
# ifndef UNIX_ENABLED
WARN_PRINT ( " Relative symlinks are not supported, exported " + src_path . get_file ( ) + " might be broken! " ) ;
# endif
print_verbose ( " export framework: " + src_path + " -> " + tmp_app_path_name + " /Contents/Frameworks/ " + src_path . get_file ( ) ) ;
err = da - > make_dir_recursive ( tmp_app_path_name + " /Contents/Frameworks/ " + src_path . get_file ( ) ) ;
if ( err = = OK ) {
err = da - > copy_dir ( src_path , tmp_app_path_name + " /Contents/Frameworks/ " + src_path . get_file ( ) , - 1 , true ) ;
}
} else {
print_verbose ( " export dylib: " + src_path + " -> " + tmp_app_path_name + " /Contents/Frameworks/ " + src_path . get_file ( ) ) ;
err = da - > copy ( src_path , tmp_app_path_name + " /Contents/Frameworks/ " + src_path . get_file ( ) ) ;
}
2019-11-08 13:38:23 +01:00
if ( err = = OK & & sign_enabled ) {
2021-03-10 11:56:15 +01:00
err = _code_sign ( p_preset , tmp_app_path_name + " /Contents/Frameworks/ " + src_path . get_file ( ) , ent_path ) ;
2018-01-04 20:36:44 +01:00
}
}
2019-11-08 13:38:23 +01:00
memdelete ( da ) ;
}
2018-01-04 20:36:44 +01:00
2021-03-03 13:13:55 +01:00
if ( sign_enabled ) {
for ( int i = 0 ; i < dylibs_found . size ( ) ; i + + ) {
if ( err = = OK ) {
err = _code_sign ( p_preset , tmp_app_path_name + " / " + dylibs_found [ i ] , ent_path ) ;
}
}
}
2019-11-08 13:38:23 +01:00
if ( err = = OK & & sign_enabled ) {
if ( ep . step ( " Code signing bundle " , 2 ) ) {
return ERR_SKIP ;
2017-10-03 20:09:57 +02:00
}
2021-03-03 13:13:55 +01:00
err = _code_sign ( p_preset , tmp_app_path_name + " /Contents/MacOS/ " + pkg_name , ent_path ) ;
2019-11-08 13:38:23 +01:00
}
2017-07-02 13:23:33 +02:00
2019-11-08 13:38:23 +01:00
if ( export_format = = " dmg " ) {
// Create a DMG.
2017-10-03 20:09:57 +02:00
if ( err = = OK ) {
2018-12-19 19:50:40 +01:00
if ( ep . step ( " Making DMG " , 3 ) ) {
return ERR_SKIP ;
}
2017-10-03 20:09:57 +02:00
err = _create_dmg ( p_path , pkg_name , tmp_app_path_name ) ;
2017-07-02 13:23:33 +02:00
}
2019-11-08 13:38:23 +01:00
// Sign DMG.
if ( err = = OK & & sign_enabled ) {
if ( ep . step ( " Code signing DMG " , 3 ) ) {
return ERR_SKIP ;
2017-10-03 20:09:57 +02:00
}
2021-03-03 13:13:55 +01:00
err = _code_sign ( p_preset , p_path , ent_path ) ;
2018-03-06 16:13:18 +01:00
}
2019-11-08 13:38:23 +01:00
} else {
// Create ZIP.
2018-03-06 16:13:18 +01:00
if ( err = = OK ) {
2019-11-08 13:38:23 +01:00
if ( ep . step ( " Making ZIP " , 3 ) ) {
return ERR_SKIP ;
}
if ( FileAccess : : exists ( p_path ) ) {
OS : : get_singleton ( ) - > move_to_trash ( p_path ) ;
2018-01-04 19:42:29 +01:00
}
2019-08-09 13:45:30 +02:00
2019-11-08 13:38:23 +01:00
FileAccess * dst_f = nullptr ;
zlib_filefunc_def io_dst = zipio_create_io_from_file ( & dst_f ) ;
zipFile zip = zipOpen2 ( p_path . utf8 ( ) . get_data ( ) , APPEND_STATUS_CREATE , nullptr , & io_dst ) ;
2021-05-25 02:25:11 +02:00
_zip_folder_recursive ( zip , EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) , pkg_name + " .app " , pkg_name ) ;
2019-11-08 13:38:23 +01:00
zipClose ( zip , nullptr ) ;
}
2014-02-10 02:10:30 +01:00
}
2020-06-10 14:28:50 +02:00
bool noto_enabled = p_preset - > get ( " notarization/enable " ) ;
if ( err = = OK & & noto_enabled ) {
if ( ep . step ( " Sending archive for notarization " , 4 ) ) {
return ERR_SKIP ;
}
err = _notarize ( p_preset , p_path ) ;
}
2019-11-08 13:38:23 +01:00
// Clean up temporary .app dir.
2020-12-03 08:55:09 +01:00
tmp_app_dir - > change_dir ( tmp_app_path_name ) ;
tmp_app_dir - > erase_contents_recursive ( ) ;
tmp_app_dir - > change_dir ( " .. " ) ;
tmp_app_dir - > remove ( tmp_app_dir_name ) ;
2016-07-09 00:46:10 +02:00
}
2018-03-06 16:13:18 +01:00
return err ;
2014-02-10 02:10:30 +01:00
}
2019-11-08 13:38:23 +01:00
void EditorExportPlatformOSX : : _zip_folder_recursive ( zipFile & p_zip , const String & p_root_path , const String & p_folder , const String & p_pkg_name ) {
String dir = p_root_path . plus_file ( p_folder ) ;
DirAccess * da = DirAccess : : open ( dir ) ;
da - > list_dir_begin ( ) ;
String f ;
while ( ( f = da - > get_next ( ) ) ! = " " ) {
if ( f = = " . " | | f = = " .. " ) {
continue ;
}
2021-03-10 11:56:15 +01:00
if ( da - > is_link ( f ) ) {
OS : : Time time = OS : : get_singleton ( ) - > get_time ( ) ;
OS : : Date date = OS : : get_singleton ( ) - > get_date ( ) ;
zip_fileinfo zipfi ;
zipfi . tmz_date . tm_hour = time . hour ;
zipfi . tmz_date . tm_mday = date . day ;
2021-05-24 13:54:05 +02:00
zipfi . tmz_date . tm_min = time . minute ;
2021-03-10 11:56:15 +01:00
zipfi . tmz_date . tm_mon = date . month - 1 ; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/
2021-05-24 13:54:05 +02:00
zipfi . tmz_date . tm_sec = time . second ;
2021-03-10 11:56:15 +01:00
zipfi . tmz_date . tm_year = date . year ;
zipfi . dosDate = 0 ;
// 0120000: symbolic link type
// 0000755: permissions rwxr-xr-x
// 0000644: permissions rw-r--r--
uint32_t _mode = 0120644 ;
zipfi . external_fa = ( _mode < < 16L ) | ! ( _mode & 0200 ) ;
zipfi . internal_fa = 0 ;
zipOpenNewFileInZip4 ( p_zip ,
p_folder . plus_file ( f ) . utf8 ( ) . get_data ( ) ,
& zipfi ,
nullptr ,
0 ,
nullptr ,
0 ,
nullptr ,
Z_DEFLATED ,
Z_DEFAULT_COMPRESSION ,
0 ,
- MAX_WBITS ,
DEF_MEM_LEVEL ,
Z_DEFAULT_STRATEGY ,
nullptr ,
0 ,
0x0314 , // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions
0 ) ;
String target = da - > read_link ( f ) ;
zipWriteInFileInZip ( p_zip , target . utf8 ( ) . get_data ( ) , target . utf8 ( ) . size ( ) ) ;
zipCloseFileInZip ( p_zip ) ;
} else if ( da - > current_is_dir ( ) ) {
2019-11-08 13:38:23 +01:00
_zip_folder_recursive ( p_zip , p_root_path , p_folder . plus_file ( f ) , p_pkg_name ) ;
} else {
bool is_executable = ( p_folder . ends_with ( " MacOS " ) & & ( f = = p_pkg_name ) ) ;
OS : : Time time = OS : : get_singleton ( ) - > get_time ( ) ;
OS : : Date date = OS : : get_singleton ( ) - > get_date ( ) ;
zip_fileinfo zipfi ;
zipfi . tmz_date . tm_hour = time . hour ;
zipfi . tmz_date . tm_mday = date . day ;
2021-05-24 13:54:05 +02:00
zipfi . tmz_date . tm_min = time . minute ;
2020-06-30 09:16:02 +02:00
zipfi . tmz_date . tm_mon = date . month - 1 ; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/
2021-05-24 13:54:05 +02:00
zipfi . tmz_date . tm_sec = time . second ;
2019-11-08 13:38:23 +01:00
zipfi . tmz_date . tm_year = date . year ;
zipfi . dosDate = 0 ;
2020-06-20 10:04:18 +02:00
// 0100000: regular file type
// 0000755: permissions rwxr-xr-x
// 0000644: permissions rw-r--r--
2020-06-30 09:16:02 +02:00
uint32_t _mode = ( is_executable ? 0100755 : 0100644 ) ;
zipfi . external_fa = ( _mode < < 16L ) | ! ( _mode & 0200 ) ;
2019-11-08 13:38:23 +01:00
zipfi . internal_fa = 0 ;
zipOpenNewFileInZip4 ( p_zip ,
p_folder . plus_file ( f ) . utf8 ( ) . get_data ( ) ,
& zipfi ,
nullptr ,
0 ,
nullptr ,
0 ,
nullptr ,
Z_DEFLATED ,
Z_DEFAULT_COMPRESSION ,
0 ,
- MAX_WBITS ,
DEF_MEM_LEVEL ,
Z_DEFAULT_STRATEGY ,
nullptr ,
0 ,
0x0314 , // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions
0 ) ;
Vector < uint8_t > array = FileAccess : : get_file_as_array ( dir . plus_file ( f ) ) ;
zipWriteInFileInZip ( p_zip , array . ptr ( ) , array . size ( ) ) ;
zipCloseFileInZip ( p_zip ) ;
}
}
da - > list_dir_end ( ) ;
memdelete ( da ) ;
}
2017-06-21 11:11:38 +02:00
bool EditorExportPlatformOSX : : can_export ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates ) const {
2014-02-10 02:10:30 +01:00
String err ;
2020-01-07 13:29:02 +01:00
bool valid = false ;
2014-02-10 02:10:30 +01:00
2020-01-07 13:29:02 +01:00
// Look for export templates (first official, and if defined custom templates).
2014-02-10 02:10:30 +01:00
2020-01-07 13:29:02 +01:00
bool dvalid = exists_export_template ( " osx.zip " , & err ) ;
bool rvalid = dvalid ; // Both in the same ZIP.
if ( p_preset - > get ( " custom_template/debug " ) ! = " " ) {
dvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/debug " ) ) ;
if ( ! dvalid ) {
2019-01-21 18:34:53 +01:00
err + = TTR ( " Custom debug template not found. " ) + " \n " ;
2018-03-03 14:23:00 +01:00
}
2014-02-10 02:10:30 +01:00
}
2020-01-07 13:29:02 +01:00
if ( p_preset - > get ( " custom_template/release " ) ! = " " ) {
rvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/release " ) ) ;
if ( ! rvalid ) {
2019-01-21 18:34:53 +01:00
err + = TTR ( " Custom release template not found. " ) + " \n " ;
2018-03-03 14:23:00 +01:00
}
2014-02-10 02:10:30 +01:00
}
2020-01-07 13:29:02 +01:00
valid = dvalid | | rvalid ;
r_missing_templates = ! valid ;
2020-03-22 17:27:32 +01:00
String identifier = p_preset - > get ( " application/bundle_identifier " ) ;
2020-06-10 14:28:50 +02:00
String pn_err ;
if ( ! is_package_name_valid ( identifier , & pn_err ) ) {
err + = TTR ( " Invalid bundle identifier: " ) + " " + pn_err + " \n " ;
valid = false ;
}
bool sign_enabled = p_preset - > get ( " codesign/enable " ) ;
bool noto_enabled = p_preset - > get ( " notarization/enable " ) ;
if ( noto_enabled ) {
if ( ! sign_enabled ) {
err + = TTR ( " Notarization: code signing required. " ) + " \n " ;
valid = false ;
}
bool hr_enabled = p_preset - > get ( " codesign/hardened_runtime " ) ;
if ( ! hr_enabled ) {
err + = TTR ( " Notarization: hardened runtime required. " ) + " \n " ;
valid = false ;
}
if ( p_preset - > get ( " notarization/apple_id_name " ) = = " " ) {
err + = TTR ( " Notarization: Apple ID name not specified. " ) + " \n " ;
valid = false ;
}
if ( p_preset - > get ( " notarization/apple_id_password " ) = = " " ) {
err + = TTR ( " Notarization: Apple ID password not specified. " ) + " \n " ;
valid = false ;
}
}
2020-12-15 13:04:21 +01:00
if ( ! err . is_empty ( ) ) {
2017-06-21 11:11:38 +02:00
r_error = err ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
return valid ;
}
2017-06-21 11:11:38 +02:00
EditorExportPlatformOSX : : EditorExportPlatformOSX ( ) {
Ref < Image > img = memnew ( Image ( _osx_logo ) ) ;
logo . instance ( ) ;
logo - > create_from_image ( img ) ;
}
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
EditorExportPlatformOSX : : ~ EditorExportPlatformOSX ( ) {
2014-02-10 02:10:30 +01:00
}
void register_osx_exporter ( ) {
2017-06-21 11:11:38 +02:00
Ref < EditorExportPlatformOSX > platform ;
platform . instance ( ) ;
EditorExport : : get_singleton ( ) - > add_export_platform ( platform ) ;
2014-02-10 02:10:30 +01:00
}