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