2016-06-18 14:46:12 +02:00
/*************************************************************************/
/* export.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* http://www.godotengine.org */
/*************************************************************************/
2017-01-01 22:01:57 +01:00
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
2017-04-08 00:11:42 +02:00
/* Copyright (c) 2014-2017 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"
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"
2014-02-10 02:10:30 +01:00
# include "io/marshalls.h"
# include "io/resource_saver.h"
2017-03-05 16:44:50 +01:00
# include "io/zip_io.h"
2014-02-10 02:10:30 +01:00
# include "os/file_access.h"
# include "os/os.h"
2017-06-23 17:03:41 +02:00
# include "platform/osx/logo.gen.h"
2017-07-19 22:00:46 +02:00
# include "project_settings.h"
2014-02-10 02:10:30 +01:00
# include "string.h"
2017-03-05 16:44:50 +01:00
# include "version.h"
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
int version_code ;
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-07-02 13:23:33 +02:00
# ifdef OSX_ENABLED
Error _code_sign ( const Ref < EditorExportPreset > & p_preset , const String & p_path ) ;
Error _create_dmg ( const String & p_dmg_path , const String & p_pkg_name , const String & p_app_path_name ) ;
# endif
2014-02-10 02:10:30 +01:00
protected :
2017-06-21 11:11:38 +02:00
virtual void get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) ;
virtual void get_export_options ( List < ExportOption > * r_options ) ;
2014-02-10 02:10:30 +01:00
public :
virtual String get_name ( ) const { return " Mac OSX " ; }
2017-07-19 22:00:46 +02:00
virtual String get_os_name ( ) const { return " OSX " ; }
2014-02-10 02:10:30 +01:00
virtual Ref < Texture > get_logo ( ) const { return logo ; }
2017-07-02 13:23:33 +02:00
# ifdef OSX_ENABLED
virtual String get_binary_extension ( ) const { return " dmg " ; }
# else
2014-02-10 02:10:30 +01:00
virtual String get_binary_extension ( ) const { return " zip " ; }
2017-07-02 13:23:33 +02:00
# endif
2017-06-21 11:11:38 +02:00
virtual Error export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags = 0 ) ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
virtual bool can_export ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates ) const ;
2014-02-10 02:10:30 +01:00
2017-07-19 22:00:46 +02:00
virtual void get_platform_features ( List < String > * r_features ) {
r_features - > push_back ( " pc " ) ;
r_features - > push_back ( " s3tc " ) ;
r_features - > push_back ( " OSX " ) ;
}
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 ) {
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
// what does this need to do?
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 ) {
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " custom_package/debug " , PROPERTY_HINT_GLOBAL_FILE , " zip " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " custom_package/release " , PROPERTY_HINT_GLOBAL_FILE , " zip " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/name " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/info " ) , " Made with Godot Engine " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/icon " , PROPERTY_HINT_FILE , " png " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/identifier " ) , " org.godotengine.macgame " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/signature " ) , " godotmacgame " ) ) ;
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 : : INT , " application/bits_mode " , PROPERTY_HINT_ENUM , " Fat (32 & 64 bits),64 bits,32 bits " ) , 0 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " display/high_res " ) , false ) ) ;
2017-07-02 13:23:33 +02:00
# ifdef OSX_ENABLED
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " codesign/identity " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " codesign/entitlements " ) , " " ) ) ;
# endif
2014-02-10 02:10:30 +01:00
}
2017-06-21 11:11:38 +02:00
void EditorExportPlatformOSX : : _make_icon ( const Ref < Image > & p_icon , Vector < uint8_t > & p_data ) {
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
Ref < ImageTexture > it = memnew ( ImageTexture ) ;
int size = 512 ;
2014-02-10 02:10:30 +01:00
Vector < uint8_t > data ;
data . resize ( 8 ) ;
2017-06-21 11:11:38 +02:00
data [ 0 ] = ' i ' ;
data [ 1 ] = ' c ' ;
data [ 2 ] = ' n ' ;
data [ 3 ] = ' s ' ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
const char * name [ ] = { " ic09 " , " ic08 " , " ic07 " , " icp6 " , " icp5 " , " icp4 " } ;
int index = 0 ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
while ( size > = 16 ) {
2014-02-10 02:10:30 +01:00
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 ) ;
copy - > resize ( size , size ) ;
2014-02-10 02:10:30 +01:00
it - > create_from_image ( copy ) ;
2017-06-21 11:11:38 +02:00
String path = EditorSettings : : get_singleton ( ) - > get_settings_path ( ) + " /tmp/icon.png " ;
ResourceSaver : : save ( path , it ) ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
FileAccess * f = FileAccess : : open ( path , FileAccess : : READ ) ;
2014-02-10 02:10:30 +01:00
ERR_FAIL_COND ( ! f ) ;
int ofs = data . size ( ) ;
uint32_t len = f - > get_len ( ) ;
2017-06-21 11:11:38 +02:00
data . resize ( data . size ( ) + len + 8 ) ;
f - > get_buffer ( & data [ ofs + 8 ] , len ) ;
2014-02-10 02:10:30 +01:00
memdelete ( f ) ;
2017-06-21 11:11:38 +02:00
len + = 8 ;
len = BSWAP32 ( len ) ;
copymem ( & data [ ofs ] , name [ index ] , 4 ) ;
encode_uint32 ( len , & data [ ofs + 4 ] ) ;
2014-02-10 02:10:30 +01:00
index + + ;
2017-06-21 11:11:38 +02:00
size / = 2 ;
2014-02-10 02:10:30 +01:00
}
uint32_t total_len = data . size ( ) ;
total_len = BSWAP32 ( total_len ) ;
2017-06-21 11:11:38 +02:00
encode_uint32 ( total_len , & data [ 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 " ;
} else if ( lines [ i ] . find ( " $identifier " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $identifier " , p_preset - > get ( " application/identifier " ) ) + " \n " ;
} 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 " ;
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 + + ) {
2017-06-21 11:11:38 +02:00
plist [ i ] = cs [ i ] ;
2014-02-10 02:10:30 +01:00
}
}
2017-07-02 13:23:33 +02:00
# ifdef OSX_ENABLED
/**
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
* */
Error EditorExportPlatformOSX : : _code_sign ( const Ref < EditorExportPreset > & p_preset , const String & p_path ) {
List < String > args ;
if ( p_preset - > get ( " codesign/entitlements " ) ! = " " ) {
/* this should point to our entitlements.plist file that sandboxes our application, I don't know if this should also be placed in our app bundle */
args . push_back ( " -entitlements " ) ;
args . push_back ( p_preset - > get ( " codesign/entitlements " ) ) ;
}
args . push_back ( " -s " ) ;
args . push_back ( p_preset - > get ( " codesign/identity " ) ) ;
args . push_back ( " -v " ) ; /* provide some more feedback */
args . push_back ( p_path ) ;
Error err = OS : : get_singleton ( ) - > execute ( " /usr/bin/codesign " , args , true ) ;
ERR_FAIL_COND_V ( err , err ) ;
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 ;
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 ) ;
Error err = OS : : get_singleton ( ) - > execute ( " /usr/bin/hdiutil " , args , true ) ;
ERR_FAIL_COND_V ( err , err ) ;
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 ) {
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
2017-07-02 13:23:33 +02:00
EditorProgress ep ( " export " , " Exporting for OSX " , 3 ) ;
2014-02-10 02:10:30 +01:00
2015-11-30 01:26:51 +01:00
if ( p_debug )
2017-07-02 13:23:33 +02:00
src_pkg_name = p_preset - > get ( " custom_package/debug " ) ;
2015-11-30 01:26:51 +01:00
else
2017-07-02 13:23:33 +02:00
src_pkg_name = p_preset - > get ( " custom_package/release " ) ;
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
}
2017-06-21 11:11:38 +02:00
FileAccess * src_f = NULL ;
2014-02-10 02:10:30 +01:00
zlib_filefunc_def io = zipio_create_io_from_file ( & src_f ) ;
2017-06-21 11:11:38 +02:00
ep . step ( " Creating app " , 0 ) ;
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 ) {
2014-02-10 02:10:30 +01:00
2017-07-02 13:23:33 +02:00
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
ERR_FAIL_COND_V ( ! src_pkg_zip , ERR_CANT_OPEN ) ;
int ret = unzGoToFirstFile ( src_pkg_zip ) ;
2014-02-10 02:10:30 +01:00
2016-07-09 00:46:10 +02:00
String binary_to_use = " godot_osx_ " + String ( p_debug ? " debug " : " release " ) + " . " ;
2017-06-21 11:11:38 +02:00
int bits_mode = p_preset - > get ( " application/bits_mode " ) ;
binary_to_use + = String ( bits_mode = = 0 ? " fat " : bits_mode = = 1 ? " 64 " : " 32 " ) ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
print_line ( " binary: " + binary_to_use ) ;
2014-02-10 02:10:30 +01:00
String pkg_name ;
2017-06-21 11:11:38 +02:00
if ( p_preset - > get ( " application/name " ) ! = " " )
pkg_name = p_preset - > get ( " application/name " ) ; // app_name
2017-07-19 22:00:46 +02:00
else if ( String ( ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ) ! = " " )
pkg_name = String ( ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ) ;
2014-02-10 02:10:30 +01:00
else
2017-06-21 11:11:38 +02:00
pkg_name = " Unnamed " ;
2014-02-10 02:10:30 +01:00
2017-07-02 13:23:33 +02:00
// We're on OSX so we can export to DMG, but first we create our application bundle
String tmp_app_path_name = p_path . get_base_dir ( ) + " / " + pkg_name + " .app " ;
print_line ( " Exporting to " + tmp_app_path_name ) ;
DirAccess * tmp_app_path = DirAccess : : create_for_path ( tmp_app_path_name ) ;
ERR_FAIL_COND_V ( ! tmp_app_path , ERR_CANT_CREATE )
///@TODO We should delete the existing application bundle especially if we attempt to code sign it, but what is a safe way to do this? Maybe call system function so it moves to trash?
// tmp_app_path->erase_contents_recursive();
// Create our folder structure or rely on unzip?
print_line ( " Creating " + tmp_app_path_name + " /Contents/MacOS " ) ;
Error dir_err = tmp_app_path - > make_dir_recursive ( tmp_app_path_name + " /Contents/MacOS " ) ;
ERR_FAIL_COND_V ( dir_err , ERR_CANT_CREATE )
print_line ( " Creating " + tmp_app_path_name + " /Contents/Resources " ) ;
dir_err = tmp_app_path - > make_dir_recursive ( tmp_app_path_name + " /Contents/Resources " ) ;
ERR_FAIL_COND_V ( dir_err , ERR_CANT_CREATE )
/* 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 ;
2016-07-09 00:46:10 +02:00
2017-06-21 11:11:38 +02:00
while ( ret = = UNZ_OK ) {
2017-07-02 13:23:33 +02:00
bool is_execute = false ;
2014-02-10 02:10:30 +01:00
//get filename
unz_file_info info ;
char fname [ 16384 ] ;
2017-07-02 13:23:33 +02:00
ret = unzGetCurrentFileInfo ( src_pkg_zip , & info , fname , 16384 , NULL , 0 , NULL , 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
2017-06-21 11:11:38 +02:00
print_line ( " READ: " + file ) ;
2014-02-10 02:10:30 +01:00
Vector < uint8_t > data ;
data . resize ( info . uncompressed_size ) ;
//read
2017-07-02 13:23:33 +02:00
unzOpenCurrentFile ( src_pkg_zip ) ;
unzReadCurrentFile ( src_pkg_zip , data . ptr ( ) , data . size ( ) ) ;
unzCloseCurrentFile ( src_pkg_zip ) ;
2014-02-10 02:10:30 +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 " ) {
2014-02-10 02:10:30 +01:00
print_line ( " parse plist " ) ;
2017-06-21 11:11:38 +02:00
_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 ) ;
2014-02-10 02:10:30 +01:00
continue ; //ignore!
}
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 " ) {
2014-02-10 02:10:30 +01:00
//see if there is an icon
2017-06-21 11:11:38 +02:00
String iconpath ;
if ( p_preset - > get ( " application/icon " ) ! = " " )
iconpath = p_preset - > get ( " application/icon " ) ;
else
2017-07-19 22:00:46 +02:00
iconpath = ProjectSettings : : get_singleton ( ) - > get ( " application/config/icon " ) ;
2017-06-21 11:11:38 +02:00
print_line ( " icon? " + iconpath ) ;
if ( iconpath ! = " " ) {
Ref < Image > icon ;
icon . instance ( ) ;
icon - > load ( iconpath ) ;
if ( ! icon - > empty ( ) ) {
2014-02-10 02:10:30 +01:00
print_line ( " loaded? " ) ;
2017-06-21 11:11:38 +02:00
_make_icon ( icon , data ) ;
2014-02-10 02:10:30 +01:00
}
}
//bleh?
}
2017-07-02 13:23:33 +02:00
if ( data . size ( ) > 0 ) {
print_line ( " ADDING: " + file + " size: " + itos ( data . size ( ) ) ) ;
total_size + = data . size ( ) ;
/* write it into our application bundle */
file = tmp_app_path_name + " / " + file ;
/* write the file, need to add chmod */
FileAccess * f = FileAccess : : open ( file , FileAccess : : WRITE ) ;
ERR_FAIL_COND_V ( ! f , ERR_CANT_CREATE )
f - > store_buffer ( data . ptr ( ) , data . size ( ) ) ;
f - > close ( ) ;
memdelete ( f ) ;
if ( is_execute ) {
// we need execute rights on this file
chmod ( file . utf8 ( ) . get_data ( ) , 0755 ) ;
} else {
// seems to already be set correctly
// chmod(file.utf8().get_data(), 0644);
}
}
ret = unzGoToNextFile ( src_pkg_zip ) ;
}
/* we're done with our source zip */
unzClose ( src_pkg_zip ) ;
if ( ! found_binary ) {
ERR_PRINTS ( " Requested template binary ' " + binary_to_use + " ' not found. It might be missing from your template archive. " ) ;
unzClose ( src_pkg_zip ) ;
return ERR_FILE_NOT_FOUND ;
}
ep . step ( " Making PKG " , 1 ) ;
String pack_path = tmp_app_path_name + " /Contents/Resources/ " + pkg_name + " .pck " ;
Error err = save_pack ( p_preset , pack_path ) ;
// chmod(pack_path.utf8().get_data(), 0644);
if ( err ) {
return err ;
}
/* see if we can code sign our new package */
if ( p_preset - > get ( " codesign/identity " ) ! = " " ) {
ep . step ( " Code signing bundle " , 2 ) ;
/* the order in which we code sign is important, this is a bit of a shame or we could do this in our loop that extracts the files from our ZIP */
// start with our application
err = _code_sign ( p_preset , tmp_app_path_name + " /Contents/MacOS/ " + pkg_name ) ;
ERR_FAIL_COND_V ( err , err ) ;
///@TODO we should check the contents of /Contents/Frameworks for frameworks to sign
// we should probably loop through all resources and sign them?
err = _code_sign ( p_preset , tmp_app_path_name + " /Contents/Resources/icon.icns " ) ;
ERR_FAIL_COND_V ( err , err ) ;
err = _code_sign ( p_preset , pack_path ) ;
ERR_FAIL_COND_V ( err , err ) ;
err = _code_sign ( p_preset , tmp_app_path_name + " /Contents/Info.plist " ) ;
ERR_FAIL_COND_V ( err , err ) ;
}
/* and finally create a DMG */
ep . step ( " Making DMG " , 3 ) ;
err = _create_dmg ( p_path , pkg_name , tmp_app_path_name ) ;
ERR_FAIL_COND_V ( err , err ) ;
return OK ;
}
# else
/**
When exporting for OSX from any other platform we don ' t have access to code signing or creating DMGs so we ' ll wrap the bundle into a zip file .
2017-07-19 03:25:35 +02:00
Should probably find a nicer way to have just one export method instead of duplicating the method like this but I would the code got very
2017-07-02 13:23:33 +02:00
messy with switches inside of it .
* */
Error EditorExportPlatformOSX : : export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags ) {
String src_pkg_name ;
EditorProgress ep ( " export " , " Exporting for OSX " , 104 ) ;
if ( p_debug )
src_pkg_name = p_preset - > get ( " custom_package/debug " ) ;
else
src_pkg_name = p_preset - > get ( " custom_package/release " ) ;
if ( src_pkg_name = = " " ) {
String err ;
src_pkg_name = find_export_template ( " osx.zip " , & err ) ;
if ( src_pkg_name = = " " ) {
EditorNode : : add_io_error ( err ) ;
return ERR_FILE_NOT_FOUND ;
}
}
FileAccess * src_f = NULL ;
zlib_filefunc_def io = zipio_create_io_from_file ( & src_f ) ;
ep . step ( " Creating app " , 0 ) ;
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 ) ;
return ERR_FILE_NOT_FOUND ;
}
ERR_FAIL_COND_V ( ! src_pkg_zip , ERR_CANT_OPEN ) ;
int ret = unzGoToFirstFile ( src_pkg_zip ) ;
String binary_to_use = " godot_osx_ " + String ( p_debug ? " debug " : " release " ) + " . " ;
int bits_mode = p_preset - > get ( " application/bits_mode " ) ;
binary_to_use + = String ( bits_mode = = 0 ? " fat " : bits_mode = = 1 ? " 64 " : " 32 " ) ;
print_line ( " binary: " + binary_to_use ) ;
String pkg_name ;
if ( p_preset - > get ( " application/name " ) ! = " " )
pkg_name = p_preset - > get ( " application/name " ) ; // app_name
2017-07-19 22:00:46 +02:00
else if ( String ( ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ) ! = " " )
pkg_name = String ( ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ) ;
2017-07-02 13:23:33 +02:00
else
pkg_name = " Unnamed " ;
/* Open our destination zip file */
zlib_filefunc_def io2 = io ;
FileAccess * dst_f = NULL ;
io2 . opaque = & dst_f ;
zipFile dst_pkg_zip = zipOpen2 ( p_path . utf8 ( ) . get_data ( ) , APPEND_STATUS_CREATE , NULL , & io2 ) ;
bool found_binary = false ;
while ( ret = = UNZ_OK ) {
//get filename
unz_file_info info ;
char fname [ 16384 ] ;
ret = unzGetCurrentFileInfo ( src_pkg_zip , & info , fname , 16384 , NULL , 0 , NULL , 0 ) ;
String file = fname ;
print_line ( " READ: " + file ) ;
Vector < uint8_t > data ;
data . resize ( info . uncompressed_size ) ;
//read
unzOpenCurrentFile ( src_pkg_zip ) ;
unzReadCurrentFile ( src_pkg_zip , data . ptr ( ) , data . size ( ) ) ;
unzCloseCurrentFile ( src_pkg_zip ) ;
//write
file = file . replace_first ( " osx_template.app/ " , " " ) ;
if ( file = = " Contents/Info.plist " ) {
print_line ( " parse plist " ) ;
_fix_plist ( p_preset , data , pkg_name ) ;
}
if ( file . begins_with ( " Contents/MacOS/godot_ " ) ) {
if ( file ! = " Contents/MacOS/ " + binary_to_use ) {
ret = unzGoToNextFile ( src_pkg_zip ) ;
continue ; //ignore!
}
found_binary = true ;
file = " Contents/MacOS/ " + pkg_name ;
}
if ( file = = " Contents/Resources/icon.icns " ) {
//see if there is an icon
String iconpath ;
if ( p_preset - > get ( " application/icon " ) ! = " " )
iconpath = p_preset - > get ( " application/icon " ) ;
else
2017-07-19 22:00:46 +02:00
iconpath = ProjectSettings : : get_singleton ( ) - > get ( " application/config/icon " ) ;
2017-07-02 13:23:33 +02:00
print_line ( " icon? " + iconpath ) ;
if ( iconpath ! = " " ) {
Ref < Image > icon ;
icon . instance ( ) ;
icon - > load ( iconpath ) ;
if ( ! icon - > empty ( ) ) {
print_line ( " loaded? " ) ;
_make_icon ( icon , data ) ;
}
}
//bleh?
}
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
if ( data . size ( ) > 0 ) {
print_line ( " ADDING: " + file + " size: " + itos ( data . size ( ) ) ) ;
2014-02-10 02:10:30 +01:00
2017-07-02 13:23:33 +02:00
/* add it to our zip file */
file = pkg_name + " .app/ " + file ;
2014-02-10 02:10:30 +01:00
zip_fileinfo fi ;
2017-06-21 11:11:38 +02:00
fi . tmz_date . tm_hour = info . tmu_date . tm_hour ;
fi . tmz_date . tm_min = info . tmu_date . tm_min ;
fi . tmz_date . tm_sec = info . tmu_date . tm_sec ;
fi . tmz_date . tm_mon = info . tmu_date . tm_mon ;
fi . tmz_date . tm_mday = info . tmu_date . tm_mday ;
fi . tmz_date . tm_year = info . tmu_date . tm_year ;
fi . dosDate = info . dosDate ;
fi . internal_fa = info . internal_fa ;
fi . external_fa = info . external_fa ;
2014-02-10 02:10:30 +01:00
2017-07-02 13:23:33 +02:00
int err = zipOpenNewFileInZip ( dst_pkg_zip ,
2017-06-21 11:11:38 +02:00
file . utf8 ( ) . get_data ( ) ,
& fi ,
NULL ,
0 ,
NULL ,
0 ,
NULL ,
Z_DEFLATED ,
Z_DEFAULT_COMPRESSION ) ;
print_line ( " OPEN ERR: " + itos ( err ) ) ;
2017-07-02 13:23:33 +02:00
err = zipWriteInFileInZip ( dst_pkg_zip , data . ptr ( ) , data . size ( ) ) ;
2017-06-21 11:11:38 +02:00
print_line ( " WRITE ERR: " + itos ( err ) ) ;
2017-07-02 13:23:33 +02:00
zipCloseFileInZip ( dst_pkg_zip ) ;
2014-02-10 02:10:30 +01:00
}
2017-07-02 13:23:33 +02:00
ret = unzGoToNextFile ( src_pkg_zip ) ;
2014-02-10 02:10:30 +01:00
}
2016-07-09 00:46:10 +02:00
if ( ! found_binary ) {
2017-06-21 11:11:38 +02:00
ERR_PRINTS ( " Requested template binary ' " + binary_to_use + " ' not found. It might be missing from your template archive. " ) ;
2017-07-02 13:23:33 +02:00
zipClose ( dst_pkg_zip , NULL ) ;
unzClose ( src_pkg_zip ) ;
2016-07-09 00:46:10 +02:00
return ERR_FILE_NOT_FOUND ;
}
2017-06-21 11:11:38 +02:00
ep . step ( " Making PKG " , 1 ) ;
2014-02-10 02:10:30 +01:00
2017-07-02 13:23:33 +02:00
String pack_path = EditorSettings : : get_singleton ( ) - > get_settings_path ( ) + " /tmp/ " + pkg_name + " .pck " ;
2017-06-21 11:11:38 +02:00
Error err = save_pack ( p_preset , pack_path ) ;
2014-02-10 02:10:30 +01:00
if ( err ) {
2017-07-02 13:23:33 +02:00
zipClose ( dst_pkg_zip , NULL ) ;
unzClose ( src_pkg_zip ) ;
2014-02-10 02:10:30 +01:00
return err ;
}
{
//write datapack
2017-07-02 13:23:33 +02:00
zipOpenNewFileInZip ( dst_pkg_zip ,
( pkg_name + " .app/Contents/Resources/ " + pkg_name + " .pck " ) . utf8 ( ) . get_data ( ) ,
2017-06-21 11:11:38 +02:00
NULL ,
NULL ,
0 ,
NULL ,
0 ,
NULL ,
Z_DEFLATED ,
Z_DEFAULT_COMPRESSION ) ;
FileAccess * pf = FileAccess : : open ( pack_path , FileAccess : : READ ) ;
ERR_FAIL_COND_V ( ! pf , ERR_CANT_OPEN ) ;
2014-02-10 02:10:30 +01:00
const int BSIZE = 16384 ;
uint8_t buf [ BSIZE ] ;
2017-06-21 11:11:38 +02:00
while ( true ) {
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
int r = pf - > get_buffer ( buf , BSIZE ) ;
if ( r < = 0 )
2014-02-10 02:10:30 +01:00
break ;
2017-07-02 13:23:33 +02:00
zipWriteInFileInZip ( dst_pkg_zip , buf , r ) ;
2014-02-10 02:10:30 +01:00
}
2017-07-02 13:23:33 +02:00
zipCloseFileInZip ( dst_pkg_zip ) ;
2014-02-10 02:10:30 +01:00
memdelete ( pf ) ;
}
2017-07-02 13:23:33 +02:00
zipClose ( dst_pkg_zip , NULL ) ;
unzClose ( src_pkg_zip ) ;
2014-02-10 02:10:30 +01:00
return OK ;
}
2017-07-02 13:23:33 +02:00
# endif
2014-02-10 02:10:30 +01:00
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
2017-06-21 11:11:38 +02:00
bool valid = true ;
2014-02-10 02:10:30 +01:00
String err ;
2017-06-21 11:11:38 +02:00
if ( ! exists_export_template ( " osx.zip " , & err ) ) {
valid = false ;
2014-02-10 02:10:30 +01:00
}
2017-06-21 11:11:38 +02:00
if ( p_preset - > get ( " custom_package/debug " ) ! = " " & & ! FileAccess : : exists ( p_preset - > get ( " custom_package/debug " ) ) ) {
valid = false ;
err + = " Custom debug package not found. \n " ;
2014-02-10 02:10:30 +01:00
}
2017-06-21 11:11:38 +02:00
if ( p_preset - > get ( " custom_package/release " ) ! = " " & & ! FileAccess : : exists ( p_preset - > get ( " custom_package/release " ) ) ) {
valid = false ;
err + = " Custom release package not found. \n " ;
2014-02-10 02:10:30 +01:00
}
2017-06-21 11:11:38 +02:00
if ( ! err . empty ( ) )
r_error = err ;
2014-02-10 02:10:30 +01:00
return valid ;
}
2017-06-21 11:11:38 +02:00
EditorExportPlatformOSX : : EditorExportPlatformOSX ( ) {
2014-02-10 02:10:30 +01:00
2017-06-21 11:11:38 +02:00
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
}