1 /*************************************************************************/
2 /*  export.cpp                                                           */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 #include "export.h"
31 #include "editor/editor_import_export.h"
32 #include "editor/editor_node.h"
33 #include "editor/editor_settings.h"
34 #include "globals.h"
35 #include "io/marshalls.h"
36 #include "io/resource_saver.h"
37 #include "io/zip_io.h"
38 #include "os/file_access.h"
39 #include "os/os.h"
40 #include "platform/osx/logo.gen.h"
41 #include "string.h"
42 #include "version.h"
43 
44 class EditorExportPlatformOSX : public EditorExportPlatform {
45 
46 	OBJ_TYPE(EditorExportPlatformOSX, EditorExportPlatform);
47 
48 	String custom_release_package;
49 	String custom_debug_package;
50 
51 	enum BitsMode {
52 		BITS_FAT,
53 		BITS_64,
54 		BITS_32
55 	};
56 
57 	int version_code;
58 
59 	String app_name;
60 	String info;
61 	String icon;
62 	String identifier;
63 	String short_version;
64 	String version;
65 	String signature;
66 	String copyright;
67 	String identity;
68 	String entitlements;
69 	BitsMode bits_mode;
70 	bool high_resolution;
71 
72 	Ref<ImageTexture> logo;
73 
74 	void _fix_plist(Vector<uint8_t> &plist, const String &p_binary);
75 	void _make_icon(const Image &p_icon, Vector<uint8_t> &data);
76 
77 	Error _code_sign(const String &p_path);
78 	Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
79 
80 #ifdef OSX_ENABLED
use_codesign() const81 	bool use_codesign() const { return true; }
use_dmg() const82 	bool use_dmg() const { return true; }
83 #else
use_codesign() const84 	bool use_codesign() const { return false; }
use_dmg() const85 	bool use_dmg() const { return false; }
86 #endif
87 
88 protected:
89 	bool _set(const StringName &p_name, const Variant &p_value);
90 	bool _get(const StringName &p_name, Variant &r_ret) const;
91 	void _get_property_list(List<PropertyInfo> *p_list) const;
92 
93 public:
get_name() const94 	virtual String get_name() const { return "Mac OSX"; }
get_image_compression() const95 	virtual ImageCompression get_image_compression() const { return IMAGE_COMPRESSION_BC; }
get_logo() const96 	virtual Ref<Texture> get_logo() const { return logo; }
97 
poll_devices()98 	virtual bool poll_devices() { return false; }
get_device_count() const99 	virtual int get_device_count() const { return 0; }
get_device_name(int p_device) const100 	virtual String get_device_name(int p_device) const { return String(); }
get_device_info(int p_device) const101 	virtual String get_device_info(int p_device) const { return String(); }
102 	virtual Error run(int p_device, int p_flags = 0);
103 
requires_password(bool p_debug) const104 	virtual bool requires_password(bool p_debug) const { return false; }
get_binary_extension() const105 	virtual String get_binary_extension() const { return use_dmg() ? "dmg" : "zip"; }
106 	virtual Error export_project(const String &p_path, bool p_debug, int p_flags = 0);
107 
108 	virtual bool can_export(String *r_error = NULL) const;
109 
110 	EditorExportPlatformOSX();
111 	~EditorExportPlatformOSX();
112 };
113 
_set(const StringName & p_name,const Variant & p_value)114 bool EditorExportPlatformOSX::_set(const StringName &p_name, const Variant &p_value) {
115 
116 	String n = p_name;
117 
118 	if (n == "custom_package/debug")
119 		custom_debug_package = p_value;
120 	else if (n == "custom_package/release")
121 		custom_release_package = p_value;
122 	else if (n == "application/name")
123 		app_name = p_value;
124 	else if (n == "application/info")
125 		info = p_value;
126 	else if (n == "application/icon")
127 		icon = p_value;
128 	else if (n == "application/identifier")
129 		identifier = p_value;
130 	else if (n == "application/signature")
131 		signature = p_value;
132 	else if (n == "application/short_version")
133 		short_version = p_value;
134 	else if (n == "application/version")
135 		version = p_value;
136 	else if (n == "application/copyright")
137 		copyright = p_value;
138 	else if (n == "application/bits_mode")
139 		bits_mode = BitsMode(int(p_value));
140 	else if (n == "display/high_res")
141 		high_resolution = p_value;
142 	else if (n == "codesign/identity")
143 		identity = p_value;
144 	else if (n == "codesign/entitlements")
145 		entitlements = p_value;
146 	else
147 		return false;
148 
149 	return true;
150 }
151 
_get(const StringName & p_name,Variant & r_ret) const152 bool EditorExportPlatformOSX::_get(const StringName &p_name, Variant &r_ret) const {
153 
154 	String n = p_name;
155 
156 	if (n == "custom_package/debug")
157 		r_ret = custom_debug_package;
158 	else if (n == "custom_package/release")
159 		r_ret = custom_release_package;
160 	else if (n == "application/name")
161 		r_ret = app_name;
162 	else if (n == "application/info")
163 		r_ret = info;
164 	else if (n == "application/icon")
165 		r_ret = icon;
166 	else if (n == "application/identifier")
167 		r_ret = identifier;
168 	else if (n == "application/signature")
169 		r_ret = signature;
170 	else if (n == "application/short_version")
171 		r_ret = short_version;
172 	else if (n == "application/version")
173 		r_ret = version;
174 	else if (n == "application/copyright")
175 		r_ret = copyright;
176 	else if (n == "application/bits_mode")
177 		r_ret = bits_mode;
178 	else if (n == "display/high_res")
179 		r_ret = high_resolution;
180 	else if (n == "codesign/identity")
181 		r_ret = identity;
182 	else if (n == "codesign/entitlements")
183 		r_ret = entitlements;
184 	else
185 		return false;
186 
187 	return true;
188 }
_get_property_list(List<PropertyInfo> * p_list) const189 void EditorExportPlatformOSX::_get_property_list(List<PropertyInfo> *p_list) const {
190 
191 	p_list->push_back(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "zip"));
192 	p_list->push_back(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "zip"));
193 
194 	p_list->push_back(PropertyInfo(Variant::STRING, "application/name"));
195 	p_list->push_back(PropertyInfo(Variant::STRING, "application/info"));
196 	p_list->push_back(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "png"));
197 	p_list->push_back(PropertyInfo(Variant::STRING, "application/identifier"));
198 	p_list->push_back(PropertyInfo(Variant::STRING, "application/signature"));
199 	p_list->push_back(PropertyInfo(Variant::STRING, "application/short_version"));
200 	p_list->push_back(PropertyInfo(Variant::STRING, "application/version"));
201 	p_list->push_back(PropertyInfo(Variant::STRING, "application/copyright"));
202 	p_list->push_back(PropertyInfo(Variant::INT, "application/bits_mode", PROPERTY_HINT_ENUM, "Fat (32 & 64 bits),64 bits,32 bits"));
203 	p_list->push_back(PropertyInfo(Variant::BOOL, "display/high_res"));
204 
205 	p_list->push_back(PropertyInfo(Variant::STRING, "codesign/identity"));
206 	p_list->push_back(PropertyInfo(Variant::STRING, "codesign/entitlements"));
207 }
208 
_make_icon(const Image & p_icon,Vector<uint8_t> & icon)209 void EditorExportPlatformOSX::_make_icon(const Image &p_icon, Vector<uint8_t> &icon) {
210 
211 	Ref<ImageTexture> it = memnew(ImageTexture);
212 	int size = 512;
213 
214 	Vector<uint8_t> data;
215 
216 	data.resize(8);
217 	data[0] = 'i';
218 	data[1] = 'c';
219 	data[2] = 'n';
220 	data[3] = 's';
221 
222 	const char *name[] = { "ic09", "ic08", "ic07", "icp6", "icp5", "icp4" };
223 	int index = 0;
224 
225 	while (size >= 16) {
226 
227 		Image copy = p_icon;
228 		copy.convert(Image::FORMAT_RGBA);
229 		copy.resize(size, size);
230 		it->create_from_image(copy);
231 		String path = EditorSettings::get_singleton()->get_settings_path() + "/tmp/icon.png";
232 		ResourceSaver::save(path, it);
233 
234 		FileAccess *f = FileAccess::open(path, FileAccess::READ);
235 		ERR_FAIL_COND(!f);
236 
237 		int ofs = data.size();
238 		uint32_t len = f->get_len();
239 		data.resize(data.size() + len + 8);
240 		f->get_buffer(&data[ofs + 8], len);
241 		memdelete(f);
242 		len += 8;
243 		len = BSWAP32(len);
244 		copymem(&data[ofs], name[index], 4);
245 		encode_uint32(len, &data[ofs + 4]);
246 		index++;
247 		size /= 2;
248 	}
249 
250 	uint32_t total_len = data.size();
251 	total_len = BSWAP32(total_len);
252 	encode_uint32(total_len, &data[4]);
253 
254 	icon = data;
255 }
256 
_fix_plist(Vector<uint8_t> & plist,const String & p_binary)257 void EditorExportPlatformOSX::_fix_plist(Vector<uint8_t> &plist, const String &p_binary) {
258 
259 	String str;
260 	String strnew;
261 	str.parse_utf8((const char *)plist.ptr(), plist.size());
262 	Vector<String> lines = str.split("\n");
263 	for (int i = 0; i < lines.size(); i++) {
264 		if (lines[i].find("$binary") != -1) {
265 			strnew += lines[i].replace("$binary", p_binary) + "\n";
266 		} else if (lines[i].find("$name") != -1) {
267 			strnew += lines[i].replace("$name", p_binary) + "\n";
268 		} else if (lines[i].find("$info") != -1) {
269 			strnew += lines[i].replace("$info", info) + "\n";
270 		} else if (lines[i].find("$identifier") != -1) {
271 			strnew += lines[i].replace("$identifier", identifier) + "\n";
272 		} else if (lines[i].find("$short_version") != -1) {
273 			strnew += lines[i].replace("$short_version", short_version) + "\n";
274 		} else if (lines[i].find("$version") != -1) {
275 			strnew += lines[i].replace("$version", version) + "\n";
276 		} else if (lines[i].find("$signature") != -1) {
277 			strnew += lines[i].replace("$signature", signature) + "\n";
278 		} else if (lines[i].find("$copyright") != -1) {
279 			strnew += lines[i].replace("$copyright", copyright) + "\n";
280 		} else if (lines[i].find("$highres") != -1) {
281 			strnew += lines[i].replace("$highres", high_resolution ? "<true/>" : "<false/>") + "\n";
282 		} else {
283 			strnew += lines[i] + "\n";
284 		}
285 	}
286 
287 	CharString cs = strnew.utf8();
288 	plist.resize(cs.size() - 1);
289 	for (int i = 0; i < cs.size() - 1; i++) {
290 		plist[i] = cs[i];
291 	}
292 }
293 
294 /**
295 	If we're running the OSX version of the Godot editor we'll:
296 	- export our application bundle to a temporary folder
297 	- attempt to code sign it
298 	- and then wrap it up in a DMG
299 **/
300 
_code_sign(const String & p_path)301 Error EditorExportPlatformOSX::_code_sign(const String &p_path) {
302 	List<String> args;
303 
304 	if (entitlements != "") {
305 		// 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
306 		args.push_back("-entitlements");
307 		args.push_back(entitlements);
308 	}
309 	args.push_back("-s");
310 	args.push_back(identity);
311 	args.push_back("-v"); // provide some more feedback
312 	args.push_back(p_path);
313 
314 	String str;
315 	Error err = OS::get_singleton()->execute("/usr/bin/codesign", args, true, NULL, &str, NULL, true);
316 	ERR_FAIL_COND_V(err != OK, err);
317 
318 	print_line("codesign: " + str);
319 	if (str.find("no identity found") != -1) {
320 		EditorNode::add_io_error("codesign: no identity found");
321 		return FAILED;
322 	}
323 
324 	return OK;
325 }
326 
_create_dmg(const String & p_dmg_path,const String & p_pkg_name,const String & p_app_path_name)327 Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name) {
328 
329 	OS::get_singleton()->move_path_to_trash(p_dmg_path);
330 
331 	List<String> args;
332 
333 	args.push_back("create");
334 	args.push_back(p_dmg_path);
335 	args.push_back("-volname");
336 	args.push_back(p_pkg_name);
337 	args.push_back("-fs");
338 	args.push_back("HFS+");
339 	args.push_back("-srcfolder");
340 	args.push_back(p_app_path_name);
341 
342 	String str;
343 	Error err = OS::get_singleton()->execute("/usr/bin/hdiutil", args, true, NULL, &str, NULL, true);
344 	ERR_FAIL_COND_V(err != OK, err);
345 
346 	print_line("hdiutil returned: " + str);
347 	if (str.find("create failed") != -1) {
348 		if (str.find("File exists") != -1) {
349 			EditorNode::add_io_error("hdiutil: create failed - file exists");
350 		} else {
351 			EditorNode::add_io_error("hdiutil: create failed");
352 		}
353 		return FAILED;
354 	}
355 
356 	return OK;
357 }
358 
export_project(const String & p_path,bool p_debug,int p_flags)359 Error EditorExportPlatformOSX::export_project(const String &p_path, bool p_debug, int p_flags) {
360 
361 	EditorProgress ep("export", "Exporting for OSX", 104);
362 
363 	String src_pkg = p_debug ? custom_debug_package : custom_release_package;
364 	if (src_pkg == "") {
365 		String err;
366 
367 		src_pkg = find_export_template("osx.zip", &err);
368 		if (src_pkg == "") {
369 			EditorNode::add_io_error(err);
370 			return ERR_FILE_NOT_FOUND;
371 		}
372 	}
373 
374 	FileAccess *src_f = NULL;
375 	zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
376 
377 	ep.step("Creating app", 0);
378 
379 	unzFile pkg = unzOpen2(src_pkg.utf8().get_data(), &io);
380 	if (!pkg) {
381 
382 		EditorNode::add_io_error("Could not find template app to export:\n" + src_pkg);
383 		return ERR_FILE_NOT_FOUND;
384 	}
385 
386 	int ret = unzGoToFirstFile(pkg);
387 
388 	zlib_filefunc_def io2 = io;
389 	FileAccess *dst_f = NULL;
390 	io2.opaque = &dst_f;
391 	zipFile dpkg = NULL;
392 
393 	if (!use_dmg()) {
394 		dpkg = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2);
395 		if (!dpkg) {
396 			unzClose(pkg);
397 			return ERR_CANT_OPEN;
398 		}
399 	}
400 
401 	String binary_to_use = "godot_osx_" + String(p_debug ? "debug" : "release") + ".";
402 	binary_to_use += String(bits_mode == BITS_FAT ? "fat" : bits_mode == BITS_64 ? "64" : "32");
403 
404 	print_line("binary: " + binary_to_use);
405 	String pkg_name;
406 	if (app_name != "")
407 		pkg_name = app_name;
408 	else if (String(Globals::get_singleton()->get("application/name")) != "")
409 		pkg_name = String(Globals::get_singleton()->get("application/name"));
410 	else
411 		pkg_name = "Unnamed";
412 
413 	Error err = OK;
414 	String tmp_app_path_name = "";
415 	if (use_dmg()) {
416 		// We're on OSX so we can export to DMG, but first we create our application bundle
417 		tmp_app_path_name = EditorSettings::get_singleton()->get_settings_path() + "/tmp/" + pkg_name + ".app";
418 		print_line("Exporting to " + tmp_app_path_name);
419 		DirAccess *da_tmp_app = DirAccess::create_for_path(tmp_app_path_name);
420 		if (!da_tmp_app) {
421 			err = ERR_CANT_CREATE;
422 		}
423 
424 		// Create our folder structure or rely on unzip?
425 		if (err == OK) {
426 			print_line("Creating " + tmp_app_path_name + "/Contents/MacOS");
427 			err = da_tmp_app->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
428 		}
429 
430 		if (err == OK) {
431 			print_line("Creating " + tmp_app_path_name + "/Contents/Resources");
432 			err = da_tmp_app->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
433 		}
434 	}
435 
436 	bool found_binary = false;
437 
438 	while (ret == UNZ_OK && err == OK) {
439 		bool is_execute = false;
440 
441 		//get filename
442 		unz_file_info info;
443 		char fname[16384];
444 		ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
445 
446 		String file = fname;
447 
448 		print_line("READ: " + file);
449 		Vector<uint8_t> data;
450 		data.resize(info.uncompressed_size);
451 
452 		//read
453 		unzOpenCurrentFile(pkg);
454 		unzReadCurrentFile(pkg, data.ptr(), data.size());
455 		unzCloseCurrentFile(pkg);
456 
457 		//write
458 		file = file.replace_first("osx_template.app/", "");
459 		if (file == "Contents/Info.plist") {
460 			print_line("parse plist");
461 			_fix_plist(data, pkg_name);
462 		}
463 
464 		if (file.begins_with("Contents/MacOS/godot_")) {
465 			if (file != "Contents/MacOS/" + binary_to_use) {
466 				ret = unzGoToNextFile(pkg);
467 				continue; //ignore!
468 			}
469 			found_binary = true;
470 			is_execute = true;
471 			file = "Contents/MacOS/" + pkg_name;
472 		}
473 
474 		if (file == "Contents/Resources/icon.icns") {
475 			//see if there is an icon
476 			String iconpath = Globals::get_singleton()->get("application/icon");
477 			print_line("icon? " + iconpath);
478 			if (iconpath != "") {
479 				Image icon;
480 				icon.load(iconpath);
481 				if (!icon.empty()) {
482 					print_line("loaded?");
483 					_make_icon(icon, data);
484 				}
485 			}
486 		}
487 
488 		if (data.size() > 0) {
489 			print_line("ADDING: " + file + " size: " + itos(data.size()));
490 
491 			if (use_dmg()) {
492 				// write it into our application bundle
493 				file = tmp_app_path_name + "/" + file;
494 
495 				// write the file
496 				FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
497 				if (f) {
498 					f->store_buffer(data.ptr(), data.size());
499 					f->close();
500 					if (is_execute) {
501 						// Chmod with 0755 if the file is executable
502 						err = f->_chmod(file, 0755);
503 					}
504 					memdelete(f);
505 				} else {
506 					err = ERR_CANT_CREATE;
507 				}
508 			} else {
509 				zip_fileinfo fi;
510 
511 				file = pkg_name + ".app/" + file;
512 
513 				fi.tmz_date.tm_hour = info.tmu_date.tm_hour;
514 				fi.tmz_date.tm_min = info.tmu_date.tm_min;
515 				fi.tmz_date.tm_sec = info.tmu_date.tm_sec;
516 				fi.tmz_date.tm_mon = info.tmu_date.tm_mon;
517 				fi.tmz_date.tm_mday = info.tmu_date.tm_mday;
518 				fi.tmz_date.tm_year = info.tmu_date.tm_year;
519 				fi.dosDate = info.dosDate;
520 				fi.internal_fa = info.internal_fa;
521 				fi.external_fa = info.external_fa;
522 
523 				int zerr = zipOpenNewFileInZip(dpkg,
524 						file.utf8().get_data(),
525 						&fi,
526 						NULL,
527 						0,
528 						NULL,
529 						0,
530 						NULL,
531 						Z_DEFLATED,
532 						Z_DEFAULT_COMPRESSION);
533 
534 				print_line("OPEN ERR: " + itos(zerr));
535 				zerr = zipWriteInFileInZip(dpkg, data.ptr(), data.size());
536 				print_line("WRITE ERR: " + itos(zerr));
537 				zipCloseFileInZip(dpkg);
538 			}
539 		}
540 
541 		ret = unzGoToNextFile(pkg);
542 	}
543 
544 	if (!found_binary) {
545 		ERR_PRINTS("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive.");
546 		err = ERR_FILE_NOT_FOUND;
547 	}
548 
549 	if (err == OK) {
550 		ep.step("Making PKG", 1);
551 
552 		String pack_path;
553 
554 		if (use_dmg()) {
555 			pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck";
556 		} else {
557 			pack_path = EditorSettings::get_singleton()->get_settings_path() + "/tmp/data.pck";
558 		}
559 
560 		FileAccess *pfs = FileAccess::open(pack_path, FileAccess::WRITE);
561 		if (pfs) {
562 			err = save_pack(pfs);
563 			memdelete(pfs);
564 		} else {
565 			err = ERR_CANT_OPEN;
566 		}
567 
568 		if (use_dmg()) {
569 			if (err == OK && use_codesign()) {
570 				/* see if we can code sign our new package */
571 				if (err == OK && identity != "") {
572 					ep.step("Code signing bundle", 2);
573 
574 					/* 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 */
575 
576 					// start with our application
577 					err = _code_sign(tmp_app_path_name + "/Contents/MacOS/" + pkg_name);
578 				}
579 
580 				///@TODO we should check the contents of /Contents/Frameworks for frameworks to sign
581 
582 				if (err == OK && identity != "") {
583 					// we should probably loop through all resources and sign them?
584 					err = _code_sign(tmp_app_path_name + "/Contents/Resources/icon.icns");
585 				}
586 
587 				if (err == OK && identity != "") {
588 					err = _code_sign(pack_path);
589 				}
590 
591 				if (err == OK && identity != "") {
592 					err = _code_sign(tmp_app_path_name + "/Contents/Info.plist");
593 				}
594 			}
595 
596 			if (err == OK) {
597 				// and finally create a DMG
598 				ep.step("Making DMG", 3);
599 				err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
600 			}
601 
602 			// Clean up temporary .app dir
603 			OS::get_singleton()->move_path_to_trash(tmp_app_path_name);
604 		} else if (err == OK) {
605 			//write datapack
606 
607 			int zerr = zipOpenNewFileInZip(dpkg,
608 					(pkg_name + ".app/Contents/Resources/data.pck").utf8().get_data(),
609 					NULL,
610 					NULL,
611 					0,
612 					NULL,
613 					0,
614 					NULL,
615 					Z_DEFLATED,
616 					Z_DEFAULT_COMPRESSION);
617 
618 			FileAccess *pf = FileAccess::open(pack_path, FileAccess::READ);
619 			if (pf) {
620 				const int BSIZE = 16384;
621 				uint8_t buf[BSIZE];
622 
623 				while (true) {
624 
625 					int r = pf->get_buffer(buf, BSIZE);
626 					if (r <= 0)
627 						break;
628 					zipWriteInFileInZip(dpkg, buf, r);
629 				}
630 
631 				zipCloseFileInZip(dpkg);
632 				memdelete(pf);
633 			} else {
634 				err = ERR_CANT_OPEN;
635 			}
636 		}
637 	}
638 
639 	if (dpkg) {
640 		zipClose(dpkg, NULL);
641 	}
642 	unzClose(pkg);
643 
644 	return err;
645 }
646 
run(int p_device,int p_flags)647 Error EditorExportPlatformOSX::run(int p_device, int p_flags) {
648 
649 	return OK;
650 }
651 
EditorExportPlatformOSX()652 EditorExportPlatformOSX::EditorExportPlatformOSX() {
653 
654 	Image img(_osx_logo);
655 	logo = Ref<ImageTexture>(memnew(ImageTexture));
656 	logo->create_from_image(img);
657 
658 	info = "Made with Godot Engine";
659 	identifier = "org.godotengine.macgame";
660 	signature = "godotmacgame";
661 	short_version = "1.0";
662 	version = "1.0";
663 	bits_mode = BITS_FAT;
664 	high_resolution = false;
665 	identity = "";
666 	entitlements = "";
667 }
668 
can_export(String * r_error) const669 bool EditorExportPlatformOSX::can_export(String *r_error) const {
670 
671 	bool valid = true;
672 	String err;
673 
674 	if (!exists_export_template("osx.zip")) {
675 		valid = false;
676 		err += "No export templates found.\nDownload and install export templates.\n";
677 	}
678 
679 	if (custom_debug_package != "" && !FileAccess::exists(custom_debug_package)) {
680 		valid = false;
681 		err += "Custom debug package not found.\n";
682 	}
683 
684 	if (custom_release_package != "" && !FileAccess::exists(custom_release_package)) {
685 		valid = false;
686 		err += "Custom release package not found.\n";
687 	}
688 
689 	if (r_error)
690 		*r_error = err;
691 
692 	return valid;
693 }
694 
~EditorExportPlatformOSX()695 EditorExportPlatformOSX::~EditorExportPlatformOSX() {
696 }
697 
register_osx_exporter()698 void register_osx_exporter() {
699 
700 	Ref<EditorExportPlatformOSX> exporter = Ref<EditorExportPlatformOSX>(memnew(EditorExportPlatformOSX));
701 	EditorImportExport::get_singleton()->add_export_platform(exporter);
702 }
703