mirror of
https://github.com/godotengine/godot.git
synced 2024-11-22 12:12:28 +00:00
[macOS export, 3.x] Implements ad-hoc signing on Linux/Windows, adds extra privacy settings, entitlements warnings and error checking.
This commit is contained in:
parent
e3e45deac2
commit
30ee208bd9
@ -246,12 +246,12 @@ void ProjectExportDialog::_edit_preset(int p_index) {
|
||||
}
|
||||
error += " - " + items[i];
|
||||
}
|
||||
|
||||
export_error->set_text(error);
|
||||
export_error->show();
|
||||
} else {
|
||||
export_error->hide();
|
||||
}
|
||||
export_warning->hide();
|
||||
if (needs_templates) {
|
||||
export_templates_error->show();
|
||||
} else {
|
||||
@ -262,6 +262,20 @@ void ProjectExportDialog::_edit_preset(int p_index) {
|
||||
get_ok()->set_disabled(true);
|
||||
|
||||
} else {
|
||||
if (error != String()) {
|
||||
Vector<String> items = error.split("\n", false);
|
||||
error = "";
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (i > 0) {
|
||||
error += "\n";
|
||||
}
|
||||
error += " - " + items[i];
|
||||
}
|
||||
export_warning->set_text(error);
|
||||
export_warning->show();
|
||||
} else {
|
||||
export_warning->hide();
|
||||
}
|
||||
export_error->hide();
|
||||
export_templates_error->hide();
|
||||
export_button->set_disabled(false);
|
||||
@ -1144,6 +1158,11 @@ ProjectExportDialog::ProjectExportDialog() {
|
||||
export_error->hide();
|
||||
export_error->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor"));
|
||||
|
||||
export_warning = memnew(Label);
|
||||
main_vb->add_child(export_warning);
|
||||
export_warning->hide();
|
||||
export_warning->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("warning_color", "Editor"));
|
||||
|
||||
export_templates_error = memnew(HBoxContainer);
|
||||
main_vb->add_child(export_templates_error);
|
||||
export_templates_error->hide();
|
||||
|
@ -99,6 +99,7 @@ private:
|
||||
Label *script_key_error;
|
||||
|
||||
Label *export_error;
|
||||
Label *export_warning;
|
||||
HBoxContainer *export_templates_error;
|
||||
|
||||
String default_filename;
|
||||
|
@ -24,10 +24,7 @@
|
||||
<string>$signature</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$version</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>$microphone_usage_description</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>$camera_usage_description</string>
|
||||
$usage_descriptions
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>$copyright</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
@ -46,6 +43,6 @@
|
||||
<string>10.12</string>
|
||||
</dict>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
$highres
|
||||
$highres
|
||||
</dict>
|
||||
</plist>
|
||||
|
1621
platform/osx/export/codesign.cpp
Normal file
1621
platform/osx/export/codesign.cpp
Normal file
File diff suppressed because it is too large
Load Diff
368
platform/osx/export/codesign.h
Normal file
368
platform/osx/export/codesign.h
Normal file
@ -0,0 +1,368 @@
|
||||
/*************************************************************************/
|
||||
/* codesign.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
// macOS code signature creation utility.
|
||||
//
|
||||
// Current implementation has the following limitation:
|
||||
// - Only version 11.3.0 signatures are supported.
|
||||
// - Only "framework" and "app" bundle types are supported.
|
||||
// - Page hash array scattering is not supported.
|
||||
// - Reading and writing binary property lists i snot supported (third-party frameworks with binary Info.plist will not work unless .plist is converted to text format).
|
||||
// - Requirements code generator is not implemented (only hard-coded requirements for the ad-hoc signing is supported).
|
||||
// - RFC5652/CMS blob generation is not implemented, supports ad-hoc signing only.
|
||||
|
||||
#ifndef CODESIGN_H
|
||||
#define CODESIGN_H
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/os/dir_access.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/reference.h"
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
#include "modules/regex/regex.h"
|
||||
#endif
|
||||
|
||||
#include "plist.h"
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignCodeResources */
|
||||
/*************************************************************************/
|
||||
|
||||
class CodeSignCodeResources {
|
||||
public:
|
||||
enum class CRMatch {
|
||||
CR_MATCH_NO,
|
||||
CR_MATCH_YES,
|
||||
CR_MATCH_NESTED,
|
||||
CR_MATCH_OPTIONAL,
|
||||
};
|
||||
|
||||
private:
|
||||
struct CRFile {
|
||||
String name;
|
||||
String hash;
|
||||
String hash2;
|
||||
bool optional;
|
||||
bool nested;
|
||||
String requirements;
|
||||
};
|
||||
|
||||
struct CRRule {
|
||||
String file_pattern;
|
||||
String key;
|
||||
int weight;
|
||||
bool store;
|
||||
CRRule() {
|
||||
weight = 1;
|
||||
store = true;
|
||||
}
|
||||
CRRule(const String &p_file_pattern, const String &p_key, int p_weight, bool p_store) {
|
||||
file_pattern = p_file_pattern;
|
||||
key = p_key;
|
||||
weight = p_weight;
|
||||
store = p_store;
|
||||
}
|
||||
};
|
||||
|
||||
Vector<CRRule> rules1;
|
||||
Vector<CRRule> rules2;
|
||||
|
||||
Vector<CRFile> files1;
|
||||
Vector<CRFile> files2;
|
||||
|
||||
String hash_sha1_base64(const String &p_path);
|
||||
String hash_sha256_base64(const String &p_path);
|
||||
|
||||
public:
|
||||
void add_rule1(const String &p_rule, const String &p_key = "", int p_weight = 0, bool p_store = true);
|
||||
void add_rule2(const String &p_rule, const String &p_key = "", int p_weight = 0, bool p_store = true);
|
||||
|
||||
CRMatch match_rules1(const String &p_path) const;
|
||||
CRMatch match_rules2(const String &p_path) const;
|
||||
|
||||
bool add_file1(const String &p_root, const String &p_path);
|
||||
bool add_file2(const String &p_root, const String &p_path);
|
||||
bool add_nested_file(const String &p_root, const String &p_path, const String &p_exepath);
|
||||
|
||||
bool add_folder_recursive(const String &p_root, const String &p_path = "", const String &p_main_exe_path = "");
|
||||
|
||||
bool save_to_file(const String &p_path);
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignBlob */
|
||||
/*************************************************************************/
|
||||
|
||||
class CodeSignBlob : public Reference {
|
||||
public:
|
||||
virtual PoolByteArray get_hash_sha1() const = 0;
|
||||
virtual PoolByteArray get_hash_sha256() const = 0;
|
||||
|
||||
virtual int get_size() const = 0;
|
||||
virtual uint32_t get_index_type() const = 0;
|
||||
|
||||
virtual void write_to_file(FileAccess *p_file) const = 0;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignRequirements */
|
||||
/*************************************************************************/
|
||||
|
||||
// Note: Proper code generator is not implemented (any we probably won't ever need it), just a hardcoded bytecode for the limited set of cases.
|
||||
|
||||
class CodeSignRequirements : public CodeSignBlob {
|
||||
PoolByteArray blob;
|
||||
|
||||
static inline size_t PAD(size_t s, size_t a) {
|
||||
return (s % a == 0) ? 0 : (a - s % a);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void _parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
|
||||
_FORCE_INLINE_ void _parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
|
||||
_FORCE_INLINE_ void _parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
|
||||
_FORCE_INLINE_ void _parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
|
||||
_FORCE_INLINE_ void _parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
|
||||
_FORCE_INLINE_ void _parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
|
||||
_FORCE_INLINE_ bool _parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
|
||||
|
||||
public:
|
||||
CodeSignRequirements();
|
||||
CodeSignRequirements(const PoolByteArray &p_data);
|
||||
|
||||
Vector<String> parse_requirements() const;
|
||||
|
||||
virtual PoolByteArray get_hash_sha1() const override;
|
||||
virtual PoolByteArray get_hash_sha256() const override;
|
||||
|
||||
virtual int get_size() const override;
|
||||
|
||||
virtual uint32_t get_index_type() const override { return 0x00000002; };
|
||||
virtual void write_to_file(FileAccess *p_file) const override;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignEntitlementsText */
|
||||
/*************************************************************************/
|
||||
|
||||
// PList formatted entitlements.
|
||||
|
||||
class CodeSignEntitlementsText : public CodeSignBlob {
|
||||
PoolByteArray blob;
|
||||
|
||||
public:
|
||||
CodeSignEntitlementsText();
|
||||
CodeSignEntitlementsText(const String &p_string);
|
||||
|
||||
virtual PoolByteArray get_hash_sha1() const override;
|
||||
virtual PoolByteArray get_hash_sha256() const override;
|
||||
|
||||
virtual int get_size() const override;
|
||||
|
||||
virtual uint32_t get_index_type() const override { return 0x00000005; };
|
||||
virtual void write_to_file(FileAccess *p_file) const override;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignEntitlementsBinary */
|
||||
/*************************************************************************/
|
||||
|
||||
// ASN.1 serialized entitlements.
|
||||
|
||||
class CodeSignEntitlementsBinary : public CodeSignBlob {
|
||||
PoolByteArray blob;
|
||||
|
||||
public:
|
||||
CodeSignEntitlementsBinary();
|
||||
CodeSignEntitlementsBinary(const String &p_string);
|
||||
|
||||
virtual PoolByteArray get_hash_sha1() const override;
|
||||
virtual PoolByteArray get_hash_sha256() const override;
|
||||
|
||||
virtual int get_size() const override;
|
||||
|
||||
virtual uint32_t get_index_type() const override { return 0x00000007; };
|
||||
virtual void write_to_file(FileAccess *p_file) const override;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignCodeDirectory */
|
||||
/*************************************************************************/
|
||||
|
||||
// Code Directory, runtime options, code segment and special structure hashes.
|
||||
|
||||
class CodeSignCodeDirectory : public CodeSignBlob {
|
||||
public:
|
||||
enum Slot {
|
||||
SLOT_INFO_PLIST = -1,
|
||||
SLOT_REQUIREMENTS = -2,
|
||||
SLOT_RESOURCES = -3,
|
||||
SLOT_APP_SPECIFIC = -4, // Unused.
|
||||
SLOT_ENTITLEMENTS = -5,
|
||||
SLOT_RESERVER1 = -6, // Unused.
|
||||
SLOT_DER_ENTITLEMENTS = -7,
|
||||
};
|
||||
|
||||
enum CodeSignExecSegFlags {
|
||||
EXECSEG_MAIN_BINARY = 0x1,
|
||||
EXECSEG_ALLOW_UNSIGNED = 0x10,
|
||||
EXECSEG_DEBUGGER = 0x20,
|
||||
EXECSEG_JIT = 0x40,
|
||||
EXECSEG_SKIP_LV = 0x80,
|
||||
EXECSEG_CAN_LOAD_CDHASH = 0x100,
|
||||
EXECSEG_CAN_EXEC_CDHASH = 0x200,
|
||||
};
|
||||
|
||||
enum CodeSignatureFlags {
|
||||
SIGNATURE_HOST = 0x0001,
|
||||
SIGNATURE_ADHOC = 0x0002,
|
||||
SIGNATURE_TASK_ALLOW = 0x0004,
|
||||
SIGNATURE_INSTALLER = 0x0008,
|
||||
SIGNATURE_FORCED_LV = 0x0010,
|
||||
SIGNATURE_INVALID_ALLOWED = 0x0020,
|
||||
SIGNATURE_FORCE_HARD = 0x0100,
|
||||
SIGNATURE_FORCE_KILL = 0x0200,
|
||||
SIGNATURE_FORCE_EXPIRATION = 0x0400,
|
||||
SIGNATURE_RESTRICT = 0x0800,
|
||||
SIGNATURE_ENFORCEMENT = 0x1000,
|
||||
SIGNATURE_LIBRARY_VALIDATION = 0x2000,
|
||||
SIGNATURE_ENTITLEMENTS_VALIDATED = 0x4000,
|
||||
SIGNATURE_NVRAM_UNRESTRICTED = 0x8000,
|
||||
SIGNATURE_RUNTIME = 0x10000,
|
||||
SIGNATURE_LINKER_SIGNED = 0x20000,
|
||||
};
|
||||
|
||||
private:
|
||||
PoolByteArray blob;
|
||||
|
||||
struct CodeDirectoryHeader {
|
||||
uint32_t version; // Using version 0x0020500.
|
||||
uint32_t flags; // // Option flags.
|
||||
uint32_t hash_offset; // Slot zero offset.
|
||||
uint32_t ident_offset; // Identifier string offset.
|
||||
uint32_t special_slots; // Nr. of slots with negative index.
|
||||
uint32_t code_slots; // Nr. of slots with index >= 0, (code_limit / page_size).
|
||||
uint32_t code_limit; // Everything before code signature load command offset.
|
||||
uint8_t hash_size; // 20 (SHA-1) or 32 (SHA-256).
|
||||
uint8_t hash_type; // 1 (SHA-1) or 2 (SHA-256).
|
||||
uint8_t platform; // Not used.
|
||||
uint8_t page_size; // Page size, power of two, 2^12 (4096).
|
||||
uint32_t spare2; // Not used.
|
||||
// Version 0x20100
|
||||
uint32_t scatter_vector_offset; // Set to 0 and ignore.
|
||||
// Version 0x20200
|
||||
uint32_t team_offset; // Team id string offset.
|
||||
// Version 0x20300
|
||||
uint32_t spare3; // Not used.
|
||||
uint64_t code_limit_64; // Set to 0 and ignore.
|
||||
// Version 0x20400
|
||||
uint64_t exec_seg_base; // Start of the signed code segmet.
|
||||
uint64_t exec_seg_limit; // Code segment (__TEXT) vmsize.
|
||||
uint64_t exec_seg_flags; // Executable segment flags.
|
||||
// Version 0x20500
|
||||
uint32_t runtime; // Runtime version.
|
||||
uint32_t pre_encrypt_offset; // Set to 0 and ignore.
|
||||
};
|
||||
|
||||
int32_t pages = 0;
|
||||
int32_t remain = 0;
|
||||
int32_t code_slots = 0;
|
||||
int32_t special_slots = 0;
|
||||
|
||||
public:
|
||||
CodeSignCodeDirectory();
|
||||
CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash_type, bool p_main, const CharString &p_id, const CharString &p_team_id, uint32_t p_page_size, uint64_t p_exe_limit, uint64_t p_code_limit);
|
||||
|
||||
int32_t get_page_count();
|
||||
int32_t get_page_remainder();
|
||||
|
||||
bool set_hash_in_slot(const PoolByteArray &p_hash, int p_slot);
|
||||
|
||||
virtual PoolByteArray get_hash_sha1() const override;
|
||||
virtual PoolByteArray get_hash_sha256() const override;
|
||||
|
||||
virtual int get_size() const override;
|
||||
virtual uint32_t get_index_type() const override { return 0x00000000; };
|
||||
|
||||
virtual void write_to_file(FileAccess *p_file) const override;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignSignature */
|
||||
/*************************************************************************/
|
||||
|
||||
class CodeSignSignature : public CodeSignBlob {
|
||||
PoolByteArray blob;
|
||||
|
||||
public:
|
||||
CodeSignSignature();
|
||||
|
||||
virtual PoolByteArray get_hash_sha1() const override;
|
||||
virtual PoolByteArray get_hash_sha256() const override;
|
||||
|
||||
virtual int get_size() const override;
|
||||
virtual uint32_t get_index_type() const override { return 0x00010000; };
|
||||
|
||||
virtual void write_to_file(FileAccess *p_file) const override;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignSuperBlob */
|
||||
/*************************************************************************/
|
||||
|
||||
class CodeSignSuperBlob {
|
||||
Vector<Ref<CodeSignBlob>> blobs;
|
||||
|
||||
public:
|
||||
bool add_blob(const Ref<CodeSignBlob> &p_blob);
|
||||
|
||||
int get_size() const;
|
||||
void write_to_file(FileAccess *p_file) const;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSign */
|
||||
/*************************************************************************/
|
||||
|
||||
class CodeSign {
|
||||
static PoolByteArray file_hash_sha1(const String &p_path);
|
||||
static PoolByteArray file_hash_sha256(const String &p_path);
|
||||
static Error _codesign_file(bool p_use_hardened_runtime, bool p_force, const String &p_info, const String &p_exe_path, const String &p_bundle_path, const String &p_ent_path, bool p_ios_bundle, String &r_error_msg);
|
||||
|
||||
public:
|
||||
static Error codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg);
|
||||
};
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
|
||||
#endif // CODESIGN_H
|
@ -29,6 +29,7 @@
|
||||
/*************************************************************************/
|
||||
|
||||
#include "export.h"
|
||||
#include "codesign.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
@ -41,6 +42,7 @@
|
||||
#include "editor/editor_export.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
#include "platform/osx/logo.gen.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
@ -68,11 +70,10 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
|
||||
Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
|
||||
void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
|
||||
|
||||
#ifdef OSX_ENABLED
|
||||
bool use_codesign() const { return true; }
|
||||
#ifdef OSX_ENABLED
|
||||
bool use_dmg() const { return true; }
|
||||
#else
|
||||
bool use_codesign() const { return false; }
|
||||
bool use_dmg() const { return false; }
|
||||
#endif
|
||||
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
|
||||
@ -113,6 +114,7 @@ public:
|
||||
list.push_back("dmg");
|
||||
}
|
||||
list.push_back("zip");
|
||||
list.push_back("app");
|
||||
return list;
|
||||
}
|
||||
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
|
||||
@ -160,14 +162,24 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
|
||||
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));
|
||||
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"), ""));
|
||||
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/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), ""));
|
||||
|
||||
#ifdef OSX_ENABLED
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), true));
|
||||
#ifdef OSX_ENABLED
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true));
|
||||
#endif
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/replace_existing_signature"), true));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
|
||||
|
||||
@ -198,6 +210,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
|
||||
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));
|
||||
|
||||
#ifdef OSX_ENABLED
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray()));
|
||||
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false));
|
||||
@ -406,13 +419,56 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
|
||||
} 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";
|
||||
} 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";
|
||||
strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n";
|
||||
} else if (lines[i].find("$usage_descriptions") != -1) {
|
||||
String descriptions;
|
||||
if (!((String)p_preset->get("privacy/microphone_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSMicrophoneUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/microphone_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/camera_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSCameraUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/camera_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/location_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSLocationUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/location_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/address_book_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSContactsUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/address_book_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/calendar_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSCalendarsUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/calendar_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/photos_library_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSPhotoLibraryUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/photos_library_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/desktop_folder_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSDesktopFolderUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/desktop_folder_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/documents_folder_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSDocumentsFolderUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/documents_folder_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/downloads_folder_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSDownloadsFolderUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/downloads_folder_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/network_volumes_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSNetworkVolumesUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/network_volumes_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!((String)p_preset->get("privacy/removable_volumes_usage_description")).empty()) {
|
||||
descriptions += "\t<key>NSRemovableVolumesUsageDescription</key>\n";
|
||||
descriptions += "\t<string>" + (String)p_preset->get("privacy/removable_volumes_usage_description") + "</string>\n";
|
||||
}
|
||||
if (!descriptions.empty()) {
|
||||
strnew += lines[i].replace("$usage_descriptions", descriptions);
|
||||
}
|
||||
} else {
|
||||
strnew += lines[i] + "\n";
|
||||
}
|
||||
@ -463,14 +519,16 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset
|
||||
Error err = OS::get_singleton()->execute("xcrun", args, true, NULL, &str, NULL, true);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
|
||||
print_line("altool (" + p_path + "):\n" + str);
|
||||
print_verbose("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(TTR("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."));
|
||||
print_line(" " + TTR("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>\"");
|
||||
print_line(" " + TTR("Run the following command to staple notarization ticket to the exported application (optional):"));
|
||||
print_line(" \"xcrun stapler staple <app path>\"");
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -479,16 +537,47 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset
|
||||
}
|
||||
|
||||
Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) {
|
||||
#ifdef OSX_ENABLED
|
||||
List<String> args;
|
||||
bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign");
|
||||
bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
|
||||
|
||||
if ((!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) || force_builtin_codesign) {
|
||||
print_verbose("using built-in codesign...");
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
if (p_preset->get("codesign/timestamp")) {
|
||||
args.push_back("--timestamp");
|
||||
WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!");
|
||||
}
|
||||
if (p_preset->get("codesign/hardened_runtime")) {
|
||||
WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!");
|
||||
}
|
||||
|
||||
String error_msg;
|
||||
Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg);
|
||||
if (err != OK) {
|
||||
EditorNode::add_io_error("Built-in CodeSign: " + error_msg);
|
||||
return FAILED;
|
||||
}
|
||||
#else
|
||||
ERR_FAIL_V_MSG(FAILED, "Built-in CodeSign require regex module");
|
||||
#endif
|
||||
return OK;
|
||||
} else {
|
||||
print_verbose("using external codesign...");
|
||||
List<String> args;
|
||||
if (p_preset->get("codesign/timestamp")) {
|
||||
if (ad_hoc) {
|
||||
WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!");
|
||||
} else {
|
||||
args.push_back("--timestamp");
|
||||
}
|
||||
}
|
||||
if (p_preset->get("codesign/hardened_runtime")) {
|
||||
if (ad_hoc) {
|
||||
WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!");
|
||||
} else {
|
||||
args.push_back("--options");
|
||||
args.push_back("runtime");
|
||||
}
|
||||
}
|
||||
|
||||
if (p_path.get_extension() != "dmg") {
|
||||
args.push_back("--entitlements");
|
||||
@ -504,7 +593,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
|
||||
}
|
||||
|
||||
args.push_back("-s");
|
||||
if (p_preset->get("codesign/identity") == "") {
|
||||
if (ad_hoc) {
|
||||
args.push_back("-");
|
||||
} else {
|
||||
args.push_back(p_preset->get("codesign/identity"));
|
||||
@ -522,18 +611,17 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
|
||||
Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
|
||||
print_line("codesign (" + p_path + "):\n" + str);
|
||||
print_verbose("codesign (" + p_path + "):\n" + str);
|
||||
if (str.find("no identity found") != -1) {
|
||||
EditorNode::add_io_error("codesign: no identity found");
|
||||
EditorNode::add_io_error("CodeSign: " + TTR("No identity found."));
|
||||
return FAILED;
|
||||
}
|
||||
if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) {
|
||||
EditorNode::add_io_error("codesign: invalid entitlements file");
|
||||
EditorNode::add_io_error("CodeSign: " + TTR("Invalid entitlements file."));
|
||||
return FAILED;
|
||||
}
|
||||
#endif
|
||||
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
|
||||
@ -693,13 +781,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
FileAccess *src_f = nullptr;
|
||||
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
|
||||
|
||||
if (ep.step("Creating app", 0)) {
|
||||
if (ep.step(TTR("Creating app bundle"), 0)) {
|
||||
return ERR_SKIP;
|
||||
}
|
||||
|
||||
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);
|
||||
EditorNode::add_io_error(TTR("Could not find template app to export:") + "\n" + src_pkg_name);
|
||||
return ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
@ -718,12 +806,27 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
|
||||
pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name);
|
||||
|
||||
String export_format = use_dmg() && p_path.ends_with("dmg") ? "dmg" : "zip";
|
||||
String export_format;
|
||||
if (use_dmg() && p_path.ends_with("dmg")) {
|
||||
export_format = "dmg";
|
||||
} else if (p_path.ends_with("zip")) {
|
||||
export_format = "zip";
|
||||
} else if (p_path.ends_with("app")) {
|
||||
export_format = "app";
|
||||
} else {
|
||||
EditorNode::add_io_error("Invalid export format");
|
||||
return ERR_CANT_CREATE;
|
||||
}
|
||||
|
||||
// Create our application bundle.
|
||||
String tmp_app_dir_name = pkg_name + ".app";
|
||||
String tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name);
|
||||
print_line("Exporting to " + tmp_app_path_name);
|
||||
String tmp_app_path_name;
|
||||
if (export_format == "app") {
|
||||
tmp_app_path_name = p_path;
|
||||
} else {
|
||||
tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name);
|
||||
}
|
||||
print_verbose("Exporting to " + tmp_app_path_name);
|
||||
|
||||
Error err = OK;
|
||||
|
||||
@ -732,19 +835,25 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
err = ERR_CANT_CREATE;
|
||||
}
|
||||
|
||||
if (DirAccess::exists(tmp_app_dir_name)) {
|
||||
if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) {
|
||||
tmp_app_dir->erase_contents_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
// Create our folder structure.
|
||||
if (err == OK) {
|
||||
print_line("Creating " + tmp_app_path_name + "/Contents/MacOS");
|
||||
print_verbose("Creating " + tmp_app_path_name + "/Contents/MacOS");
|
||||
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
|
||||
}
|
||||
|
||||
if (err == OK) {
|
||||
print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks");
|
||||
print_verbose("Creating " + tmp_app_path_name + "/Contents/Frameworks");
|
||||
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks");
|
||||
}
|
||||
|
||||
if (err == OK) {
|
||||
print_line("Creating " + tmp_app_path_name + "/Contents/Resources");
|
||||
print_verbose("Creating " + tmp_app_path_name + "/Contents/Resources");
|
||||
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
|
||||
}
|
||||
|
||||
@ -773,6 +882,25 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
// Write.
|
||||
file = file.replace_first("osx_template.app/", "");
|
||||
|
||||
if (((info.external_fa >> 16L) & 0120000) == 0120000) {
|
||||
#ifndef UNIX_ENABLED
|
||||
WARN_PRINT(vformat(TTR("Relative symlinks are not supported on this OS, exported project might be broken!")));
|
||||
#endif
|
||||
// Handle symlinks in the archive.
|
||||
file = tmp_app_path_name.plus_file(file);
|
||||
if (err == OK) {
|
||||
err = tmp_app_dir->make_dir_recursive(file.get_base_dir());
|
||||
}
|
||||
if (err == OK) {
|
||||
String lnk_data = String::utf8((const char *)data.ptr(), data.size());
|
||||
err = tmp_app_dir->create_link(lnk_data, file);
|
||||
print_verbose(vformat("ADDING SYMLINK %s => %s\n", file, lnk_data));
|
||||
}
|
||||
|
||||
ret = unzGoToNextFile(src_pkg_zip);
|
||||
continue; // next
|
||||
}
|
||||
|
||||
if (file == "Contents/Info.plist") {
|
||||
_fix_plist(p_preset, data, pkg_name);
|
||||
}
|
||||
@ -836,7 +964,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
dylibs_found.push_back(file);
|
||||
}
|
||||
|
||||
print_line("ADDING: " + file + " size: " + itos(data.size()));
|
||||
print_verbose("ADDING: " + file + " size: " + itos(data.size()));
|
||||
|
||||
// Write it into our application bundle.
|
||||
file = tmp_app_path_name.plus_file(file);
|
||||
@ -866,12 +994,12 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
unzClose(src_pkg_zip);
|
||||
|
||||
if (!found_binary) {
|
||||
ERR_PRINT("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive.");
|
||||
ERR_PRINT(vformat(TTR("Requested template binary '%s' not found. It might be missing from your template archive."), binary_to_use));
|
||||
err = ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (err == OK) {
|
||||
if (ep.step("Making PKG", 1)) {
|
||||
if (ep.step(TTR("Making PKG"), 1)) {
|
||||
return ERR_SKIP;
|
||||
}
|
||||
|
||||
@ -1016,6 +1144,21 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
}
|
||||
}
|
||||
|
||||
bool ad_hoc = true;
|
||||
if (err == OK) {
|
||||
#ifdef OSX_ENABLED
|
||||
String sign_identity = p_preset->get("codesign/identity");
|
||||
#else
|
||||
String sign_identity = "-";
|
||||
#endif
|
||||
ad_hoc = (sign_identity == "" || sign_identity == "-");
|
||||
bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation");
|
||||
if ((!dylibs_found.empty() || !shared_objects.empty()) && sign_enabled && ad_hoc && !lib_validation) {
|
||||
ERR_PRINT(TTR("Application with an ad-hoc signature require 'Disable Library Validation' entitlement to load dynamic libraries."));
|
||||
err = ERR_CANT_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
if (err == OK) {
|
||||
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
for (int i = 0; i < shared_objects.size(); i++) {
|
||||
@ -1045,31 +1188,31 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
}
|
||||
|
||||
if (err == OK && sign_enabled) {
|
||||
if (ep.step("Code signing bundle", 2)) {
|
||||
if (ep.step(TTR("Code signing bundle"), 2)) {
|
||||
return ERR_SKIP;
|
||||
}
|
||||
err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name, ent_path);
|
||||
err = _code_sign(p_preset, tmp_app_path_name, ent_path);
|
||||
}
|
||||
|
||||
if (export_format == "dmg") {
|
||||
// Create a DMG.
|
||||
if (err == OK) {
|
||||
if (ep.step("Making DMG", 3)) {
|
||||
if (ep.step(TTR("Making DMG"), 3)) {
|
||||
return ERR_SKIP;
|
||||
}
|
||||
err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
|
||||
}
|
||||
// Sign DMG.
|
||||
if (err == OK && sign_enabled) {
|
||||
if (ep.step("Code signing DMG", 3)) {
|
||||
if (err == OK && sign_enabled && !ad_hoc) {
|
||||
if (ep.step(TTR("Code signing DMG"), 3)) {
|
||||
return ERR_SKIP;
|
||||
}
|
||||
err = _code_sign(p_preset, p_path, ent_path);
|
||||
}
|
||||
} else {
|
||||
} else if (export_format == "zip") {
|
||||
// Create ZIP.
|
||||
if (err == OK) {
|
||||
if (ep.step("Making ZIP", 3)) {
|
||||
if (ep.step(TTR("Making ZIP"), 3)) {
|
||||
return ERR_SKIP;
|
||||
}
|
||||
if (FileAccess::exists(p_path)) {
|
||||
@ -1088,18 +1231,28 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||
|
||||
bool noto_enabled = p_preset->get("notarization/enable");
|
||||
if (err == OK && noto_enabled) {
|
||||
if (ep.step("Sending archive for notarization", 4)) {
|
||||
if (export_format == "app") {
|
||||
WARN_PRINT(TTR("Notarization require app to be archived first, select DMG or ZIP export format instead."));
|
||||
} else {
|
||||
if (ep.step(TTR("Sending archive for notarization"), 4)) {
|
||||
return ERR_SKIP;
|
||||
}
|
||||
err = _notarize(p_preset, p_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up temporary .app dir.
|
||||
tmp_app_dir->change_dir(tmp_app_path_name);
|
||||
// Clean up temporary .app dir and generated entitlements.
|
||||
if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") {
|
||||
tmp_app_dir->remove(ent_path);
|
||||
}
|
||||
if (export_format != "app") {
|
||||
if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) {
|
||||
tmp_app_dir->erase_contents_recursive();
|
||||
tmp_app_dir->change_dir("..");
|
||||
tmp_app_dir->remove(tmp_app_dir_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
@ -1223,10 +1376,9 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
|
||||
String err;
|
||||
bool valid = false;
|
||||
|
||||
// Look for export templates (first official, and if defined custom templates).
|
||||
|
||||
bool dvalid = exists_export_template("osx.zip", &err);
|
||||
bool rvalid = dvalid; // Both in the same ZIP.
|
||||
// Look for export templates (custom templates).
|
||||
bool dvalid = false;
|
||||
bool rvalid = false;
|
||||
|
||||
if (p_preset->get("custom_template/debug") != "") {
|
||||
dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
|
||||
@ -1241,6 +1393,12 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
|
||||
}
|
||||
}
|
||||
|
||||
// Look for export templates (official templates, check only is custom templates are not set).
|
||||
if (!dvalid || !rvalid) {
|
||||
dvalid = exists_export_template("osx.zip", &err);
|
||||
rvalid = dvalid; // Both in the same ZIP.
|
||||
}
|
||||
|
||||
valid = dvalid || rvalid;
|
||||
r_missing_templates = !valid;
|
||||
|
||||
@ -1253,14 +1411,32 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
|
||||
|
||||
bool sign_enabled = p_preset->get("codesign/enable");
|
||||
bool noto_enabled = p_preset->get("notarization/enable");
|
||||
bool ad_hoc = ((p_preset->get("codesign/identity") == "") || (p_preset->get("codesign/identity") == "-"));
|
||||
|
||||
#ifdef OSX_ENABLED
|
||||
if (!ad_hoc && (bool)EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign")) {
|
||||
err += TTR("Warning: Built-in \"codesign\" is selected in the Editor Settings. Code signing is limited to ad-hoc signature only.") + "\n";
|
||||
}
|
||||
if (!ad_hoc && !FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
|
||||
err += TTR("Warning: Xcode command line tools are not installed, using built-in \"codesign\". Code signing is limited to ad-hoc signature only.") + "\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (noto_enabled) {
|
||||
if (!sign_enabled) {
|
||||
err += TTR("Notarization: code signing required.") + "\n";
|
||||
if (ad_hoc) {
|
||||
err += TTR("Notarization: Notarization with the ad-hoc signature is not supported.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
bool hr_enabled = p_preset->get("codesign/hardened_runtime");
|
||||
if (!hr_enabled) {
|
||||
err += TTR("Notarization: hardened runtime required.") + "\n";
|
||||
if (!sign_enabled) {
|
||||
err += TTR("Notarization: Code signing is required for notarization.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
if (!(bool)p_preset->get("codesign/hardened_runtime")) {
|
||||
err += TTR("Notarization: Hardened runtime is required for notarization.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
if (!(bool)p_preset->get("codesign/timestamp")) {
|
||||
err += TTR("Notarization: Timestamp runtime is required for notarization.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
if (p_preset->get("notarization/apple_id_name") == "") {
|
||||
@ -1271,6 +1447,49 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
|
||||
err += TTR("Notarization: Apple ID password not specified.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
#ifdef OSX_ENABLED
|
||||
err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper, if it's downloaded from an unknown source.") + "\n";
|
||||
#else
|
||||
err += TTR("Warning: Notarization is not supported on this OS. The exported project will be blocked by Gatekeeper, if it's downloaded from an unknown source.") + "\n";
|
||||
#endif
|
||||
if (!sign_enabled) {
|
||||
err += TTR("Code signing is disabled. Exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
|
||||
} else {
|
||||
if ((bool)p_preset->get("codesign/hardened_runtime") && ad_hoc) {
|
||||
err += TTR("Hardened Runtime is not compatible with ad-hoc signature, and will be disabled!") + "\n";
|
||||
}
|
||||
if ((bool)p_preset->get("codesign/timestamp") && ad_hoc) {
|
||||
err += TTR("Timestamping is not compatible with ad-hoc signature, and will be disabled!") + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sign_enabled) {
|
||||
if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).empty()) {
|
||||
err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
if ((bool)p_preset->get("codesign/entitlements/camera") && ((String)p_preset->get("privacy/camera_usage_description")).empty()) {
|
||||
err += TTR("Privacy: Camera access is enabled, but usage description is not specified.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
if ((bool)p_preset->get("codesign/entitlements/location") && ((String)p_preset->get("privacy/location_usage_description")).empty()) {
|
||||
err += TTR("Privacy: Location information access is enabled, but usage description is not specified.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
if ((bool)p_preset->get("codesign/entitlements/address_book") && ((String)p_preset->get("privacy/address_book_usage_description")).empty()) {
|
||||
err += TTR("Privacy: Address book access is enabled, but usage description is not specified.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
if ((bool)p_preset->get("codesign/entitlements/calendars") && ((String)p_preset->get("privacy/calendar_usage_description")).empty()) {
|
||||
err += TTR("Privacy: Calendar access is enabled, but usage description is not specified.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
if ((bool)p_preset->get("codesign/entitlements/photos_library") && ((String)p_preset->get("privacy/photos_library_usage_description")).empty()) {
|
||||
err += TTR("Privacy: Photo library access is enabled, but usage description is not specified.") + "\n";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!err.empty()) {
|
||||
@ -1289,6 +1508,9 @@ EditorExportPlatformOSX::~EditorExportPlatformOSX() {
|
||||
}
|
||||
|
||||
void register_osx_exporter() {
|
||||
EDITOR_DEF("export/macos/force_builtin_codesign", false);
|
||||
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::BOOL, "export/macos/force_builtin_codesign", PROPERTY_HINT_NONE));
|
||||
|
||||
Ref<EditorExportPlatformOSX> platform;
|
||||
platform.instance();
|
||||
|
||||
|
243
platform/osx/export/lipo.cpp
Normal file
243
platform/osx/export/lipo.cpp
Normal file
@ -0,0 +1,243 @@
|
||||
/*************************************************************************/
|
||||
/* lipo.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "lipo.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
|
||||
bool LipO::is_lipo(const String &p_path) {
|
||||
FileAccessRef fb = FileAccess::open(p_path, FileAccess::READ);
|
||||
ERR_FAIL_COND_V_MSG(!fb, false, vformat("LipO: Can't open file: \"%s\".", p_path));
|
||||
uint32_t magic = fb->get_32();
|
||||
return (magic == 0xbebafeca || magic == 0xcafebabe || magic == 0xbfbafeca || magic == 0xcafebabf);
|
||||
}
|
||||
|
||||
bool LipO::create_file(const String &p_output_path, const PoolStringArray &p_files) {
|
||||
close();
|
||||
|
||||
fa = FileAccess::open(p_output_path, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_V_MSG(!fa, false, vformat("LipO: Can't open file: \"%s\".", p_output_path));
|
||||
|
||||
uint64_t max_size = 0;
|
||||
for (int i = 0; i < p_files.size(); i++) {
|
||||
MachO mh;
|
||||
if (!mh.open_file(p_files[i])) {
|
||||
ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s\".", p_files[i]));
|
||||
}
|
||||
|
||||
FatArch arch;
|
||||
arch.cputype = mh.get_cputype();
|
||||
arch.cpusubtype = mh.get_cpusubtype();
|
||||
arch.offset = 0;
|
||||
arch.size = mh.get_size();
|
||||
arch.align = mh.get_align();
|
||||
max_size += arch.size;
|
||||
|
||||
archs.push_back(arch);
|
||||
|
||||
FileAccessRef fb = FileAccess::open(p_files[i], FileAccess::READ);
|
||||
if (!fb) {
|
||||
close();
|
||||
ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s\".", p_files[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Write header.
|
||||
bool is_64 = (max_size >= std::numeric_limits<uint32_t>::max());
|
||||
if (is_64) {
|
||||
fa->store_32(0xbfbafeca);
|
||||
} else {
|
||||
fa->store_32(0xbebafeca);
|
||||
}
|
||||
fa->store_32(BSWAP32(archs.size()));
|
||||
uint64_t offset = archs.size() * (is_64 ? 32 : 20) + 8;
|
||||
for (int i = 0; i < archs.size(); i++) {
|
||||
archs.write[i].offset = offset + PAD(offset, uint64_t(1) << archs[i].align);
|
||||
if (is_64) {
|
||||
fa->store_32(BSWAP32(archs[i].cputype));
|
||||
fa->store_32(BSWAP32(archs[i].cpusubtype));
|
||||
fa->store_64(BSWAP64(archs[i].offset));
|
||||
fa->store_64(BSWAP64(archs[i].size));
|
||||
fa->store_32(BSWAP32(archs[i].align));
|
||||
fa->store_32(0);
|
||||
} else {
|
||||
fa->store_32(BSWAP32(archs[i].cputype));
|
||||
fa->store_32(BSWAP32(archs[i].cpusubtype));
|
||||
fa->store_32(BSWAP32(archs[i].offset));
|
||||
fa->store_32(BSWAP32(archs[i].size));
|
||||
fa->store_32(BSWAP32(archs[i].align));
|
||||
}
|
||||
offset = archs[i].offset + archs[i].size;
|
||||
}
|
||||
|
||||
// Write files and padding.
|
||||
for (int i = 0; i < archs.size(); i++) {
|
||||
FileAccessRef fb = FileAccess::open(p_files[i], FileAccess::READ);
|
||||
if (!fb) {
|
||||
close();
|
||||
ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s\".", p_files[i]));
|
||||
}
|
||||
uint64_t cur = fa->get_position();
|
||||
for (uint64_t j = cur; j < archs[i].offset; j++) {
|
||||
fa->store_8(0);
|
||||
}
|
||||
int pages = archs[i].size / 4096;
|
||||
int remain = archs[i].size % 4096;
|
||||
unsigned char step[4096];
|
||||
for (int j = 0; j < pages; j++) {
|
||||
uint64_t br = fb->get_buffer(step, 4096);
|
||||
if (br > 0) {
|
||||
fa->store_buffer(step, br);
|
||||
}
|
||||
}
|
||||
uint64_t br = fb->get_buffer(step, remain);
|
||||
if (br > 0) {
|
||||
fa->store_buffer(step, br);
|
||||
}
|
||||
fb->close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LipO::open_file(const String &p_path) {
|
||||
close();
|
||||
|
||||
fa = FileAccess::open(p_path, FileAccess::READ);
|
||||
ERR_FAIL_COND_V_MSG(!fa, false, vformat("LipO: Can't open file: \"%s\".", p_path));
|
||||
|
||||
uint32_t magic = fa->get_32();
|
||||
if (magic == 0xbebafeca) {
|
||||
// 32-bit fat binary, bswap.
|
||||
uint32_t nfat_arch = BSWAP32(fa->get_32());
|
||||
for (uint32_t i = 0; i < nfat_arch; i++) {
|
||||
FatArch arch;
|
||||
arch.cputype = BSWAP32(fa->get_32());
|
||||
arch.cpusubtype = BSWAP32(fa->get_32());
|
||||
arch.offset = BSWAP32(fa->get_32());
|
||||
arch.size = BSWAP32(fa->get_32());
|
||||
arch.align = BSWAP32(fa->get_32());
|
||||
|
||||
archs.push_back(arch);
|
||||
}
|
||||
} else if (magic == 0xcafebabe) {
|
||||
// 32-bit fat binary.
|
||||
uint32_t nfat_arch = fa->get_32();
|
||||
for (uint32_t i = 0; i < nfat_arch; i++) {
|
||||
FatArch arch;
|
||||
arch.cputype = fa->get_32();
|
||||
arch.cpusubtype = fa->get_32();
|
||||
arch.offset = fa->get_32();
|
||||
arch.size = fa->get_32();
|
||||
arch.align = fa->get_32();
|
||||
|
||||
archs.push_back(arch);
|
||||
}
|
||||
} else if (magic == 0xbfbafeca) {
|
||||
// 64-bit fat binary, bswap.
|
||||
uint32_t nfat_arch = BSWAP32(fa->get_32());
|
||||
for (uint32_t i = 0; i < nfat_arch; i++) {
|
||||
FatArch arch;
|
||||
arch.cputype = BSWAP32(fa->get_32());
|
||||
arch.cpusubtype = BSWAP32(fa->get_32());
|
||||
arch.offset = BSWAP64(fa->get_64());
|
||||
arch.size = BSWAP64(fa->get_64());
|
||||
arch.align = BSWAP32(fa->get_32());
|
||||
fa->get_32(); // Skip, reserved.
|
||||
|
||||
archs.push_back(arch);
|
||||
}
|
||||
} else if (magic == 0xcafebabf) {
|
||||
// 64-bit fat binary.
|
||||
uint32_t nfat_arch = fa->get_32();
|
||||
for (uint32_t i = 0; i < nfat_arch; i++) {
|
||||
FatArch arch;
|
||||
arch.cputype = fa->get_32();
|
||||
arch.cpusubtype = fa->get_32();
|
||||
arch.offset = fa->get_64();
|
||||
arch.size = fa->get_64();
|
||||
arch.align = fa->get_32();
|
||||
fa->get_32(); // Skip, reserved.
|
||||
|
||||
archs.push_back(arch);
|
||||
}
|
||||
} else {
|
||||
close();
|
||||
ERR_FAIL_V_MSG(false, vformat("LipO: Invalid fat binary: \"%s\".", p_path));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int LipO::get_arch_count() const {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "LipO: File not opened.");
|
||||
return archs.size();
|
||||
}
|
||||
|
||||
bool LipO::extract_arch(int p_index, const String &p_path) {
|
||||
ERR_FAIL_COND_V_MSG(!fa, false, "LipO: File not opened.");
|
||||
ERR_FAIL_INDEX_V(p_index, archs.size(), false);
|
||||
|
||||
FileAccessRef fb = FileAccess::open(p_path, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_V_MSG(!fb, false, vformat("LipO: Can't open file: \"%s\".", p_path));
|
||||
|
||||
fa->seek(archs[p_index].offset);
|
||||
|
||||
int pages = archs[p_index].size / 4096;
|
||||
int remain = archs[p_index].size % 4096;
|
||||
unsigned char step[4096];
|
||||
for (int i = 0; i < pages; i++) {
|
||||
uint64_t br = fa->get_buffer(step, 4096);
|
||||
if (br > 0) {
|
||||
fb->store_buffer(step, br);
|
||||
}
|
||||
}
|
||||
uint64_t br = fa->get_buffer(step, remain);
|
||||
if (br > 0) {
|
||||
fb->store_buffer(step, br);
|
||||
}
|
||||
fb->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void LipO::close() {
|
||||
if (fa) {
|
||||
fa->close();
|
||||
memdelete(fa);
|
||||
fa = nullptr;
|
||||
}
|
||||
archs.clear();
|
||||
}
|
||||
|
||||
LipO::~LipO() {
|
||||
close();
|
||||
}
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
76
platform/osx/export/lipo.h
Normal file
76
platform/osx/export/lipo.h
Normal file
@ -0,0 +1,76 @@
|
||||
/*************************************************************************/
|
||||
/* lipo.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
// Universal / Universal 2 fat binary file creator and extractor.
|
||||
|
||||
#ifndef LIPO_H
|
||||
#define LIPO_H
|
||||
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/reference.h"
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
|
||||
#include "macho.h"
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
|
||||
class LipO : public Reference {
|
||||
struct FatArch {
|
||||
uint32_t cputype;
|
||||
uint32_t cpusubtype;
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
uint32_t align;
|
||||
};
|
||||
|
||||
FileAccess *fa = nullptr;
|
||||
Vector<FatArch> archs;
|
||||
|
||||
static inline size_t PAD(size_t s, size_t a) {
|
||||
return (a - s % a);
|
||||
}
|
||||
|
||||
public:
|
||||
static bool is_lipo(const String &p_path);
|
||||
|
||||
bool create_file(const String &p_output_path, const PoolStringArray &p_files);
|
||||
|
||||
bool open_file(const String &p_path);
|
||||
int get_arch_count() const;
|
||||
bool extract_arch(int p_index, const String &p_path);
|
||||
|
||||
void close();
|
||||
|
||||
~LipO();
|
||||
};
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
|
||||
#endif // LIPO_H
|
556
platform/osx/export/macho.cpp
Normal file
556
platform/osx/export/macho.cpp
Normal file
@ -0,0 +1,556 @@
|
||||
/*************************************************************************/
|
||||
/* macho.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "macho.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
|
||||
uint32_t MachO::seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max) {
|
||||
uint32_t align = p_max;
|
||||
if (p_vmaddr != 0) {
|
||||
uint64_t seg_align = 1;
|
||||
align = 0;
|
||||
while ((seg_align & p_vmaddr) == 0) {
|
||||
seg_align = seg_align << 1;
|
||||
align++;
|
||||
}
|
||||
align = CLAMP(align, p_min, p_max);
|
||||
}
|
||||
return align;
|
||||
}
|
||||
|
||||
bool MachO::alloc_signature(uint64_t p_size) {
|
||||
ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
|
||||
if (signature_offset != 0) {
|
||||
// Nothing to do, already have signature load command.
|
||||
return true;
|
||||
}
|
||||
if (lc_limit == 0 || lc_limit + 16 > exe_base) {
|
||||
ERR_FAIL_V_MSG(false, "MachO: Can't allocate signature load command, please use \"codesign_allocate\" utility first.");
|
||||
} else {
|
||||
// Add signature load command.
|
||||
signature_offset = lc_limit;
|
||||
|
||||
fa->seek(lc_limit);
|
||||
LoadCommandHeader lc;
|
||||
lc.cmd = LC_CODE_SIGNATURE;
|
||||
lc.cmdsize = 16;
|
||||
if (swap) {
|
||||
lc.cmdsize = BSWAP32(lc.cmdsize);
|
||||
}
|
||||
fa->store_buffer((const uint8_t *)&lc, sizeof(LoadCommandHeader));
|
||||
|
||||
uint32_t lc_offset = fa->get_len() + PAD(fa->get_len(), 16);
|
||||
uint32_t lc_size = 0;
|
||||
if (swap) {
|
||||
lc_offset = BSWAP32(lc_offset);
|
||||
lc_size = BSWAP32(lc_size);
|
||||
}
|
||||
fa->store_32(lc_offset);
|
||||
fa->store_32(lc_size);
|
||||
|
||||
// Write new command number.
|
||||
fa->seek(0x10);
|
||||
uint32_t ncmds = fa->get_32();
|
||||
uint32_t cmdssize = fa->get_32();
|
||||
if (swap) {
|
||||
ncmds = BSWAP32(ncmds);
|
||||
cmdssize = BSWAP32(cmdssize);
|
||||
}
|
||||
ncmds += 1;
|
||||
cmdssize += 16;
|
||||
if (swap) {
|
||||
ncmds = BSWAP32(ncmds);
|
||||
cmdssize = BSWAP32(cmdssize);
|
||||
}
|
||||
fa->seek(0x10);
|
||||
fa->store_32(ncmds);
|
||||
fa->store_32(cmdssize);
|
||||
|
||||
lc_limit = lc_limit + sizeof(LoadCommandHeader) + 8;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool MachO::is_macho(const String &p_path) {
|
||||
FileAccessRef fb = FileAccess::open(p_path, FileAccess::READ);
|
||||
ERR_FAIL_COND_V_MSG(!fb, false, vformat("MachO: Can't open file: \"%s\".", p_path));
|
||||
uint32_t magic = fb->get_32();
|
||||
return (magic == 0xcefaedfe || magic == 0xfeedface || magic == 0xcffaedfe || magic == 0xfeedfacf);
|
||||
}
|
||||
|
||||
bool MachO::open_file(const String &p_path) {
|
||||
fa = FileAccess::open(p_path, FileAccess::READ_WRITE);
|
||||
ERR_FAIL_COND_V_MSG(!fa, false, vformat("MachO: Can't open file: \"%s\".", p_path));
|
||||
uint32_t magic = fa->get_32();
|
||||
MachHeader mach_header;
|
||||
|
||||
// Read MachO header.
|
||||
swap = (magic == 0xcffaedfe || magic == 0xcefaedfe);
|
||||
if (magic == 0xcefaedfe || magic == 0xfeedface) {
|
||||
// Thin 32-bit binary.
|
||||
fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader));
|
||||
} else if (magic == 0xcffaedfe || magic == 0xfeedfacf) {
|
||||
// Thin 64-bit binary.
|
||||
fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader));
|
||||
fa->get_32(); // Skip extra reserved field.
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(false, vformat("MachO: File is not a valid MachO binary: \"%s\".", p_path));
|
||||
}
|
||||
|
||||
if (swap) {
|
||||
mach_header.ncmds = BSWAP32(mach_header.ncmds);
|
||||
mach_header.cpusubtype = BSWAP32(mach_header.cpusubtype);
|
||||
mach_header.cputype = BSWAP32(mach_header.cputype);
|
||||
}
|
||||
cpusubtype = mach_header.cpusubtype;
|
||||
cputype = mach_header.cputype;
|
||||
align = 0;
|
||||
exe_base = std::numeric_limits<uint64_t>::max();
|
||||
exe_limit = 0;
|
||||
lc_limit = 0;
|
||||
link_edit_offset = 0;
|
||||
signature_offset = 0;
|
||||
|
||||
// Read load commands.
|
||||
for (uint32_t i = 0; i < mach_header.ncmds; i++) {
|
||||
LoadCommandHeader lc;
|
||||
fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader));
|
||||
if (swap) {
|
||||
lc.cmd = BSWAP32(lc.cmd);
|
||||
lc.cmdsize = BSWAP32(lc.cmdsize);
|
||||
}
|
||||
uint64_t ps = fa->get_position();
|
||||
switch (lc.cmd) {
|
||||
case LC_SEGMENT: {
|
||||
LoadCommandSegment lc_seg;
|
||||
fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment));
|
||||
if (swap) {
|
||||
lc_seg.nsects = BSWAP32(lc_seg.nsects);
|
||||
lc_seg.vmaddr = BSWAP32(lc_seg.vmaddr);
|
||||
lc_seg.vmsize = BSWAP32(lc_seg.vmsize);
|
||||
}
|
||||
align = MAX(align, seg_align(lc_seg.vmaddr, 2, 15));
|
||||
if (String(lc_seg.segname) == "__TEXT") {
|
||||
exe_limit = MAX(exe_limit, lc_seg.vmsize);
|
||||
for (uint32_t j = 0; j < lc_seg.nsects; j++) {
|
||||
Section lc_sect;
|
||||
fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section));
|
||||
if (String(lc_sect.sectname) == "__text") {
|
||||
if (swap) {
|
||||
exe_base = MIN(exe_base, BSWAP32(lc_sect.offset));
|
||||
} else {
|
||||
exe_base = MIN(exe_base, lc_sect.offset);
|
||||
}
|
||||
}
|
||||
if (swap) {
|
||||
align = MAX(align, BSWAP32(lc_sect.align));
|
||||
} else {
|
||||
align = MAX(align, lc_sect.align);
|
||||
}
|
||||
}
|
||||
} else if (String(lc_seg.segname) == "__LINKEDIT") {
|
||||
link_edit_offset = ps - 8;
|
||||
}
|
||||
} break;
|
||||
case LC_SEGMENT_64: {
|
||||
LoadCommandSegment64 lc_seg;
|
||||
fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64));
|
||||
if (swap) {
|
||||
lc_seg.nsects = BSWAP32(lc_seg.nsects);
|
||||
lc_seg.vmaddr = BSWAP64(lc_seg.vmaddr);
|
||||
lc_seg.vmsize = BSWAP64(lc_seg.vmsize);
|
||||
}
|
||||
align = MAX(align, seg_align(lc_seg.vmaddr, 3, 15));
|
||||
if (String(lc_seg.segname) == "__TEXT") {
|
||||
exe_limit = MAX(exe_limit, lc_seg.vmsize);
|
||||
for (uint32_t j = 0; j < lc_seg.nsects; j++) {
|
||||
Section64 lc_sect;
|
||||
fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section64));
|
||||
if (String(lc_sect.sectname) == "__text") {
|
||||
if (swap) {
|
||||
exe_base = MIN(exe_base, BSWAP32(lc_sect.offset));
|
||||
} else {
|
||||
exe_base = MIN(exe_base, lc_sect.offset);
|
||||
}
|
||||
if (swap) {
|
||||
align = MAX(align, BSWAP32(lc_sect.align));
|
||||
} else {
|
||||
align = MAX(align, lc_sect.align);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (String(lc_seg.segname) == "__LINKEDIT") {
|
||||
link_edit_offset = ps - 8;
|
||||
}
|
||||
} break;
|
||||
case LC_CODE_SIGNATURE: {
|
||||
signature_offset = ps - 8;
|
||||
} break;
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
fa->seek(ps + lc.cmdsize - 8);
|
||||
lc_limit = ps + lc.cmdsize - 8;
|
||||
}
|
||||
|
||||
if (exe_limit == 0 || lc_limit == 0) {
|
||||
ERR_FAIL_V_MSG(false, vformat("MachO: No load commands or executable code found: \"%s\".", p_path));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t MachO::get_exe_base() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
return exe_base;
|
||||
}
|
||||
|
||||
uint64_t MachO::get_exe_limit() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
return exe_limit;
|
||||
}
|
||||
|
||||
int32_t MachO::get_align() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
return align;
|
||||
}
|
||||
|
||||
uint32_t MachO::get_cputype() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
return cputype;
|
||||
}
|
||||
|
||||
uint32_t MachO::get_cpusubtype() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
return cpusubtype;
|
||||
}
|
||||
|
||||
uint64_t MachO::get_size() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
return fa->get_len();
|
||||
}
|
||||
|
||||
uint64_t MachO::get_signature_offset() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command.");
|
||||
|
||||
fa->seek(signature_offset + 8);
|
||||
if (swap) {
|
||||
return BSWAP32(fa->get_32());
|
||||
} else {
|
||||
return fa->get_32();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MachO::get_code_limit() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
|
||||
if (signature_offset == 0) {
|
||||
return fa->get_len() + PAD(fa->get_len(), 16);
|
||||
} else {
|
||||
return get_signature_offset();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MachO::get_signature_size() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
|
||||
ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command.");
|
||||
|
||||
fa->seek(signature_offset + 12);
|
||||
if (swap) {
|
||||
return BSWAP32(fa->get_32());
|
||||
} else {
|
||||
return fa->get_32();
|
||||
}
|
||||
}
|
||||
|
||||
bool MachO::is_signed() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
|
||||
if (signature_offset == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fa->seek(get_signature_offset());
|
||||
uint32_t magic = BSWAP32(fa->get_32());
|
||||
if (magic != 0xfade0cc0) {
|
||||
return false; // No SuperBlob found.
|
||||
}
|
||||
fa->get_32(); // Skip size field, unused.
|
||||
uint32_t count = BSWAP32(fa->get_32());
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
uint32_t index_type = BSWAP32(fa->get_32());
|
||||
uint32_t offset = BSWAP32(fa->get_32());
|
||||
if (index_type == 0x00000000) { // CodeDirectory index type.
|
||||
fa->seek(get_signature_offset() + offset + 12);
|
||||
uint32_t flags = BSWAP32(fa->get_32());
|
||||
if (flags & 0x20000) {
|
||||
return false; // Found CD, linker-signed.
|
||||
} else {
|
||||
return true; // Found CD, not linker-signed.
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // No CD found.
|
||||
}
|
||||
|
||||
PoolByteArray MachO::get_cdhash_sha1() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, PoolByteArray(), "MachO: File not opened.");
|
||||
if (signature_offset == 0) {
|
||||
return PoolByteArray();
|
||||
}
|
||||
|
||||
fa->seek(get_signature_offset());
|
||||
uint32_t magic = BSWAP32(fa->get_32());
|
||||
if (magic != 0xfade0cc0) {
|
||||
return PoolByteArray(); // No SuperBlob found.
|
||||
}
|
||||
fa->get_32(); // Skip size field, unused.
|
||||
uint32_t count = BSWAP32(fa->get_32());
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
fa->get_32(); // Index type, skip.
|
||||
uint32_t offset = BSWAP32(fa->get_32());
|
||||
uint64_t pos = fa->get_position();
|
||||
|
||||
fa->seek(get_signature_offset() + offset);
|
||||
uint32_t cdmagic = BSWAP32(fa->get_32());
|
||||
uint32_t cdsize = BSWAP32(fa->get_32());
|
||||
if (cdmagic == 0xfade0c02) { // CodeDirectory.
|
||||
fa->seek(get_signature_offset() + offset + 36);
|
||||
uint8_t hash_size = fa->get_8();
|
||||
uint8_t hash_type = fa->get_8();
|
||||
if (hash_size == 0x14 && hash_type == 0x01) { /* SHA-1 */
|
||||
PoolByteArray hash;
|
||||
hash.resize(0x14);
|
||||
|
||||
fa->seek(get_signature_offset() + offset);
|
||||
PoolByteArray blob;
|
||||
blob.resize(cdsize);
|
||||
fa->get_buffer(blob.write().ptr(), cdsize);
|
||||
|
||||
CryptoCore::SHA1Context ctx;
|
||||
ctx.start();
|
||||
ctx.update(blob.read().ptr(), blob.size());
|
||||
ctx.finish(hash.write().ptr());
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
fa->seek(pos);
|
||||
}
|
||||
return PoolByteArray();
|
||||
}
|
||||
|
||||
PoolByteArray MachO::get_cdhash_sha256() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, PoolByteArray(), "MachO: File not opened.");
|
||||
if (signature_offset == 0) {
|
||||
return PoolByteArray();
|
||||
}
|
||||
|
||||
fa->seek(get_signature_offset());
|
||||
uint32_t magic = BSWAP32(fa->get_32());
|
||||
if (magic != 0xfade0cc0) {
|
||||
return PoolByteArray(); // No SuperBlob found.
|
||||
}
|
||||
fa->get_32(); // Skip size field, unused.
|
||||
uint32_t count = BSWAP32(fa->get_32());
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
fa->get_32(); // Index type, skip.
|
||||
uint32_t offset = BSWAP32(fa->get_32());
|
||||
uint64_t pos = fa->get_position();
|
||||
|
||||
fa->seek(get_signature_offset() + offset);
|
||||
uint32_t cdmagic = BSWAP32(fa->get_32());
|
||||
uint32_t cdsize = BSWAP32(fa->get_32());
|
||||
if (cdmagic == 0xfade0c02) { // CodeDirectory.
|
||||
fa->seek(get_signature_offset() + offset + 36);
|
||||
uint8_t hash_size = fa->get_8();
|
||||
uint8_t hash_type = fa->get_8();
|
||||
if (hash_size == 0x20 && hash_type == 0x02) { /* SHA-256 */
|
||||
PoolByteArray hash;
|
||||
hash.resize(0x20);
|
||||
|
||||
fa->seek(get_signature_offset() + offset);
|
||||
PoolByteArray blob;
|
||||
blob.resize(cdsize);
|
||||
fa->get_buffer(blob.write().ptr(), cdsize);
|
||||
|
||||
CryptoCore::SHA256Context ctx;
|
||||
ctx.start();
|
||||
ctx.update(blob.read().ptr(), blob.size());
|
||||
ctx.finish(hash.write().ptr());
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
fa->seek(pos);
|
||||
}
|
||||
return PoolByteArray();
|
||||
}
|
||||
|
||||
PoolByteArray MachO::get_requirements() {
|
||||
ERR_FAIL_COND_V_MSG(!fa, PoolByteArray(), "MachO: File not opened.");
|
||||
if (signature_offset == 0) {
|
||||
return PoolByteArray();
|
||||
}
|
||||
|
||||
fa->seek(get_signature_offset());
|
||||
uint32_t magic = BSWAP32(fa->get_32());
|
||||
if (magic != 0xfade0cc0) {
|
||||
return PoolByteArray(); // No SuperBlob found.
|
||||
}
|
||||
fa->get_32(); // Skip size field, unused.
|
||||
uint32_t count = BSWAP32(fa->get_32());
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
fa->get_32(); // Index type, skip.
|
||||
uint32_t offset = BSWAP32(fa->get_32());
|
||||
uint64_t pos = fa->get_position();
|
||||
|
||||
fa->seek(get_signature_offset() + offset);
|
||||
uint32_t rqmagic = BSWAP32(fa->get_32());
|
||||
uint32_t rqsize = BSWAP32(fa->get_32());
|
||||
if (rqmagic == 0xfade0c01) { // Requirements.
|
||||
PoolByteArray blob;
|
||||
fa->seek(get_signature_offset() + offset);
|
||||
blob.resize(rqsize);
|
||||
fa->get_buffer(blob.write().ptr(), rqsize);
|
||||
return blob;
|
||||
}
|
||||
fa->seek(pos);
|
||||
}
|
||||
return PoolByteArray();
|
||||
}
|
||||
|
||||
const FileAccess *MachO::get_file() const {
|
||||
return fa;
|
||||
}
|
||||
|
||||
FileAccess *MachO::get_file() {
|
||||
return fa;
|
||||
}
|
||||
|
||||
bool MachO::set_signature_size(uint64_t p_size) {
|
||||
ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
|
||||
|
||||
// Ensure signature load command exists.
|
||||
ERR_FAIL_COND_V_MSG(link_edit_offset == 0, false, "MachO: No __LINKEDIT segment found.");
|
||||
ERR_FAIL_COND_V_MSG(!alloc_signature(p_size), false, "MachO: Can't allocate signature load command.");
|
||||
|
||||
// Update signature load command.
|
||||
uint64_t old_size = get_signature_size();
|
||||
uint64_t new_size = p_size + PAD(p_size, 16384);
|
||||
|
||||
if (new_size <= old_size) {
|
||||
fa->seek(get_signature_offset());
|
||||
for (uint64_t i = 0; i < old_size; i++) {
|
||||
fa->store_8(0x00);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fa->seek(signature_offset + 12);
|
||||
if (swap) {
|
||||
fa->store_32(BSWAP32(new_size));
|
||||
} else {
|
||||
fa->store_32(new_size);
|
||||
}
|
||||
|
||||
uint64_t end = get_signature_offset() + new_size;
|
||||
|
||||
// Update "__LINKEDIT" segment.
|
||||
LoadCommandHeader lc;
|
||||
fa->seek(link_edit_offset);
|
||||
fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader));
|
||||
if (swap) {
|
||||
lc.cmd = BSWAP32(lc.cmd);
|
||||
lc.cmdsize = BSWAP32(lc.cmdsize);
|
||||
}
|
||||
switch (lc.cmd) {
|
||||
case LC_SEGMENT: {
|
||||
LoadCommandSegment lc_seg;
|
||||
fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment));
|
||||
if (swap) {
|
||||
lc_seg.vmsize = BSWAP32(lc_seg.vmsize);
|
||||
lc_seg.filesize = BSWAP32(lc_seg.filesize);
|
||||
lc_seg.fileoff = BSWAP32(lc_seg.fileoff);
|
||||
}
|
||||
|
||||
lc_seg.vmsize = end - lc_seg.fileoff;
|
||||
lc_seg.vmsize += PAD(lc_seg.vmsize, 4096);
|
||||
lc_seg.filesize = end - lc_seg.fileoff;
|
||||
|
||||
if (swap) {
|
||||
lc_seg.vmsize = BSWAP32(lc_seg.vmsize);
|
||||
lc_seg.filesize = BSWAP32(lc_seg.filesize);
|
||||
}
|
||||
fa->seek(link_edit_offset + 8);
|
||||
fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment));
|
||||
} break;
|
||||
case LC_SEGMENT_64: {
|
||||
LoadCommandSegment64 lc_seg;
|
||||
fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64));
|
||||
if (swap) {
|
||||
lc_seg.vmsize = BSWAP64(lc_seg.vmsize);
|
||||
lc_seg.filesize = BSWAP64(lc_seg.filesize);
|
||||
lc_seg.fileoff = BSWAP64(lc_seg.fileoff);
|
||||
}
|
||||
lc_seg.vmsize = end - lc_seg.fileoff;
|
||||
lc_seg.vmsize += PAD(lc_seg.vmsize, 4096);
|
||||
lc_seg.filesize = end - lc_seg.fileoff;
|
||||
if (swap) {
|
||||
lc_seg.vmsize = BSWAP64(lc_seg.vmsize);
|
||||
lc_seg.filesize = BSWAP64(lc_seg.filesize);
|
||||
}
|
||||
fa->seek(link_edit_offset + 8);
|
||||
fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment64));
|
||||
} break;
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(false, "MachO: Invalid __LINKEDIT segment type.");
|
||||
} break;
|
||||
}
|
||||
fa->seek(get_signature_offset());
|
||||
for (uint64_t i = 0; i < new_size; i++) {
|
||||
fa->store_8(0x00);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MachO::~MachO() {
|
||||
if (fa) {
|
||||
fa->close();
|
||||
memdelete(fa);
|
||||
fa = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
217
platform/osx/export/macho.h
Normal file
217
platform/osx/export/macho.h
Normal file
@ -0,0 +1,217 @@
|
||||
/*************************************************************************/
|
||||
/* macho.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
// Mach-O binary object file format parser and editor.
|
||||
|
||||
#ifndef MACHO_H
|
||||
#define MACHO_H
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/reference.h"
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
|
||||
class MachO : public Reference {
|
||||
struct MachHeader {
|
||||
uint32_t cputype;
|
||||
uint32_t cpusubtype;
|
||||
uint32_t filetype;
|
||||
uint32_t ncmds;
|
||||
uint32_t sizeofcmds;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
enum LoadCommandID {
|
||||
LC_SEGMENT = 0x00000001,
|
||||
LC_SYMTAB = 0x00000002,
|
||||
LC_SYMSEG = 0x00000003,
|
||||
LC_THREAD = 0x00000004,
|
||||
LC_UNIXTHREAD = 0x00000005,
|
||||
LC_LOADFVMLIB = 0x00000006,
|
||||
LC_IDFVMLIB = 0x00000007,
|
||||
LC_IDENT = 0x00000008,
|
||||
LC_FVMFILE = 0x00000009,
|
||||
LC_PREPAGE = 0x0000000a,
|
||||
LC_DYSYMTAB = 0x0000000b,
|
||||
LC_LOAD_DYLIB = 0x0000000c,
|
||||
LC_ID_DYLIB = 0x0000000d,
|
||||
LC_LOAD_DYLINKER = 0x0000000e,
|
||||
LC_ID_DYLINKER = 0x0000000f,
|
||||
LC_PREBOUND_DYLIB = 0x00000010,
|
||||
LC_ROUTINES = 0x00000011,
|
||||
LC_SUB_FRAMEWORK = 0x00000012,
|
||||
LC_SUB_UMBRELLA = 0x00000013,
|
||||
LC_SUB_CLIENT = 0x00000014,
|
||||
LC_SUB_LIBRARY = 0x00000015,
|
||||
LC_TWOLEVEL_HINTS = 0x00000016,
|
||||
LC_PREBIND_CKSUM = 0x00000017,
|
||||
LC_LOAD_WEAK_DYLIB = 0x80000018,
|
||||
LC_SEGMENT_64 = 0x00000019,
|
||||
LC_ROUTINES_64 = 0x0000001a,
|
||||
LC_UUID = 0x0000001b,
|
||||
LC_RPATH = 0x8000001c,
|
||||
LC_CODE_SIGNATURE = 0x0000001d,
|
||||
LC_SEGMENT_SPLIT_INFO = 0x0000001e,
|
||||
LC_REEXPORT_DYLIB = 0x8000001f,
|
||||
LC_LAZY_LOAD_DYLIB = 0x00000020,
|
||||
LC_ENCRYPTION_INFO = 0x00000021,
|
||||
LC_DYLD_INFO = 0x00000022,
|
||||
LC_DYLD_INFO_ONLY = 0x80000022,
|
||||
LC_LOAD_UPWARD_DYLIB = 0x80000023,
|
||||
LC_VERSION_MIN_MACOSX = 0x00000024,
|
||||
LC_VERSION_MIN_IPHONEOS = 0x00000025,
|
||||
LC_FUNCTION_STARTS = 0x00000026,
|
||||
LC_DYLD_ENVIRONMENT = 0x00000027,
|
||||
LC_MAIN = 0x80000028,
|
||||
LC_DATA_IN_CODE = 0x00000029,
|
||||
LC_SOURCE_VERSION = 0x0000002a,
|
||||
LC_DYLIB_CODE_SIGN_DRS = 0x0000002b,
|
||||
LC_ENCRYPTION_INFO_64 = 0x0000002c,
|
||||
LC_LINKER_OPTION = 0x0000002d,
|
||||
LC_LINKER_OPTIMIZATION_HINT = 0x0000002e,
|
||||
LC_VERSION_MIN_TVOS = 0x0000002f,
|
||||
LC_VERSION_MIN_WATCHOS = 0x00000030,
|
||||
};
|
||||
|
||||
struct LoadCommandHeader {
|
||||
uint32_t cmd;
|
||||
uint32_t cmdsize;
|
||||
};
|
||||
|
||||
struct LoadCommandSegment {
|
||||
char segname[16];
|
||||
uint32_t vmaddr;
|
||||
uint32_t vmsize;
|
||||
uint32_t fileoff;
|
||||
uint32_t filesize;
|
||||
uint32_t maxprot;
|
||||
uint32_t initprot;
|
||||
uint32_t nsects;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
struct LoadCommandSegment64 {
|
||||
char segname[16];
|
||||
uint64_t vmaddr;
|
||||
uint64_t vmsize;
|
||||
uint64_t fileoff;
|
||||
uint64_t filesize;
|
||||
uint32_t maxprot;
|
||||
uint32_t initprot;
|
||||
uint32_t nsects;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
struct Section {
|
||||
char sectname[16];
|
||||
char segname[16];
|
||||
uint32_t addr;
|
||||
uint32_t size;
|
||||
uint32_t offset;
|
||||
uint32_t align;
|
||||
uint32_t reloff;
|
||||
uint32_t nreloc;
|
||||
uint32_t flags;
|
||||
uint32_t reserved1;
|
||||
uint32_t reserved2;
|
||||
};
|
||||
|
||||
struct Section64 {
|
||||
char sectname[16];
|
||||
char segname[16];
|
||||
uint64_t addr;
|
||||
uint64_t size;
|
||||
uint32_t offset;
|
||||
uint32_t align;
|
||||
uint32_t reloff;
|
||||
uint32_t nreloc;
|
||||
uint32_t flags;
|
||||
uint32_t reserved1;
|
||||
uint32_t reserved2;
|
||||
uint32_t reserved3;
|
||||
};
|
||||
|
||||
FileAccess *fa = nullptr;
|
||||
bool swap = false;
|
||||
|
||||
uint64_t lc_limit = 0;
|
||||
|
||||
uint64_t exe_limit = 0;
|
||||
uint64_t exe_base = std::numeric_limits<uint64_t>::max(); // Start of first __text section.
|
||||
uint32_t align = 0;
|
||||
uint32_t cputype = 0;
|
||||
uint32_t cpusubtype = 0;
|
||||
|
||||
uint64_t link_edit_offset = 0; // __LINKEDIT segment offset.
|
||||
uint64_t signature_offset = 0; // Load command offset.
|
||||
|
||||
uint32_t seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max);
|
||||
bool alloc_signature(uint64_t p_size);
|
||||
|
||||
static inline size_t PAD(size_t s, size_t a) {
|
||||
return (a - s % a);
|
||||
}
|
||||
|
||||
public:
|
||||
static bool is_macho(const String &p_path);
|
||||
|
||||
bool open_file(const String &p_path);
|
||||
|
||||
uint64_t get_exe_base();
|
||||
uint64_t get_exe_limit();
|
||||
int32_t get_align();
|
||||
uint32_t get_cputype();
|
||||
uint32_t get_cpusubtype();
|
||||
uint64_t get_size();
|
||||
uint64_t get_code_limit();
|
||||
|
||||
uint64_t get_signature_offset();
|
||||
bool is_signed();
|
||||
|
||||
PoolByteArray get_cdhash_sha1();
|
||||
PoolByteArray get_cdhash_sha256();
|
||||
|
||||
PoolByteArray get_requirements();
|
||||
|
||||
const FileAccess *get_file() const;
|
||||
FileAccess *get_file();
|
||||
|
||||
uint64_t get_signature_size();
|
||||
bool set_signature_size(uint64_t p_size);
|
||||
|
||||
~MachO();
|
||||
};
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
|
||||
#endif // MACHO_H
|
570
platform/osx/export/plist.cpp
Normal file
570
platform/osx/export/plist.cpp
Normal file
@ -0,0 +1,570 @@
|
||||
/*************************************************************************/
|
||||
/* plist.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "plist.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
|
||||
Ref<PListNode> PListNode::new_array() {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_ARRAY;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_dict() {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_string(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_STRING;
|
||||
node->data_string = p_string.utf8();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_data(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATA;
|
||||
node->data_string = p_string.utf8();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_date(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATE;
|
||||
node->data_string = p_string.utf8();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_bool(bool p_bool) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_BOOLEAN;
|
||||
node->data_bool = p_bool;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_int(int32_t p_int) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_INTEGER;
|
||||
node->data_int = p_int;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_real(float p_real) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_REAL;
|
||||
node->data_real = p_real;
|
||||
return node;
|
||||
}
|
||||
|
||||
bool PListNode::push_subnode(const Ref<PListNode> &p_node, const String &p_key) {
|
||||
ERR_FAIL_COND_V(p_node.is_null(), false);
|
||||
if (data_type == PList::PLNodeType::PL_NODE_TYPE_DICT) {
|
||||
ERR_FAIL_COND_V(p_key.empty(), false);
|
||||
ERR_FAIL_COND_V(data_dict.has(p_key), false);
|
||||
data_dict[p_key] = p_node;
|
||||
return true;
|
||||
} else if (data_type == PList::PLNodeType::PL_NODE_TYPE_ARRAY) {
|
||||
data_array.push_back(p_node);
|
||||
return true;
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(false, "PList: Invalid parent node type, should be DICT or ARRAY.");
|
||||
}
|
||||
}
|
||||
|
||||
size_t PListNode::get_asn1_size(uint8_t p_len_octets) const {
|
||||
// Get size of all data, excluding type and size information.
|
||||
switch (data_type) {
|
||||
case PList::PLNodeType::PL_NODE_TYPE_NIL: {
|
||||
return 0;
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATA:
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATE: {
|
||||
ERR_FAIL_V_MSG(0, "PList: DATE and DATA nodes are not supported by ASN.1 serialization.");
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_STRING: {
|
||||
return data_string.length();
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
|
||||
return 1;
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_INTEGER:
|
||||
case PList::PLNodeType::PL_NODE_TYPE_REAL: {
|
||||
return 4;
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
|
||||
size_t size = 0;
|
||||
for (int i = 0; i < data_array.size(); i++) {
|
||||
size += 1 + _asn1_size_len(p_len_octets) + data_array[i]->get_asn1_size(p_len_octets);
|
||||
}
|
||||
return size;
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DICT: {
|
||||
size_t size = 0;
|
||||
for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) {
|
||||
size += 1 + _asn1_size_len(p_len_octets); // Sequence.
|
||||
size += 1 + _asn1_size_len(p_len_octets) + it->key().utf8().length(); //Key.
|
||||
size += 1 + _asn1_size_len(p_len_octets) + it->value()->get_asn1_size(p_len_octets); // Value.
|
||||
}
|
||||
return size;
|
||||
} break;
|
||||
default: {
|
||||
return 0;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
int PListNode::_asn1_size_len(uint8_t p_len_octets) {
|
||||
if (p_len_octets > 1) {
|
||||
return p_len_octets + 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void PListNode::store_asn1_size(PoolByteArray &p_stream, uint8_t p_len_octets) const {
|
||||
uint32_t size = get_asn1_size(p_len_octets);
|
||||
if (p_len_octets > 1) {
|
||||
p_stream.push_back(0x80 + p_len_octets);
|
||||
}
|
||||
for (int i = p_len_octets - 1; i >= 0; i--) {
|
||||
uint8_t x = (size >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
}
|
||||
|
||||
bool PListNode::store_asn1(PoolByteArray &p_stream, uint8_t p_len_octets) const {
|
||||
// Convert to binary ASN1 stream.
|
||||
bool valid = true;
|
||||
switch (data_type) {
|
||||
case PList::PLNodeType::PL_NODE_TYPE_NIL: {
|
||||
// Nothing to store.
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATE:
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATA: {
|
||||
ERR_FAIL_V_MSG(false, "PList: DATE and DATA nodes are not supported by ASN.1 serialization.");
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_STRING: {
|
||||
p_stream.push_back(0x0C);
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (int i = 0; i < data_string.size(); i++) {
|
||||
p_stream.push_back(data_string[i]);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
|
||||
p_stream.push_back(0x01);
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
if (data_bool) {
|
||||
p_stream.push_back(0x01);
|
||||
} else {
|
||||
p_stream.push_back(0x00);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
|
||||
p_stream.push_back(0x02);
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
uint8_t x = (data_int >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_REAL: {
|
||||
p_stream.push_back(0x03);
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
uint8_t x = (data_int >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
|
||||
p_stream.push_back(0x30); // Sequence.
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (int i = 0; i < data_array.size(); i++) {
|
||||
valid = valid && data_array[i]->store_asn1(p_stream, p_len_octets);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DICT: {
|
||||
p_stream.push_back(0x31); // Set.
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) {
|
||||
CharString cs = it->key().utf8();
|
||||
uint32_t size = cs.length();
|
||||
|
||||
// Sequence.
|
||||
p_stream.push_back(0x30);
|
||||
uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + it->value()->get_asn1_size(p_len_octets);
|
||||
if (p_len_octets > 1) {
|
||||
p_stream.push_back(0x80 + p_len_octets);
|
||||
}
|
||||
for (int i = p_len_octets - 1; i >= 0; i--) {
|
||||
uint8_t x = (seq_size >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
// Key.
|
||||
p_stream.push_back(0x0C);
|
||||
if (p_len_octets > 1) {
|
||||
p_stream.push_back(0x80 + p_len_octets);
|
||||
}
|
||||
for (int i = p_len_octets - 1; i >= 0; i--) {
|
||||
uint8_t x = (size >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
p_stream.push_back(cs[i]);
|
||||
}
|
||||
// Value.
|
||||
valid = valid && it->value()->store_asn1(p_stream, p_len_octets);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
void PListNode::store_text(String &p_stream, uint8_t p_indent) const {
|
||||
// Convert to text XML stream.
|
||||
switch (data_type) {
|
||||
case PList::PLNodeType::PL_NODE_TYPE_NIL: {
|
||||
// Nothing to store.
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATA: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<data>\n";
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += data_string + "\n";
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "</data>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATE: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<date>";
|
||||
p_stream += data_string;
|
||||
p_stream += "</date>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_STRING: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<string>";
|
||||
p_stream += String::utf8(data_string);
|
||||
p_stream += "</string>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
if (data_bool) {
|
||||
p_stream += "<true/>\n";
|
||||
} else {
|
||||
p_stream += "<false/>\n";
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<integer>";
|
||||
p_stream += itos(data_int);
|
||||
p_stream += "</integer>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_REAL: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<real>";
|
||||
p_stream += rtos(data_real);
|
||||
p_stream += "</real>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<array>\n";
|
||||
for (int i = 0; i < data_array.size(); i++) {
|
||||
data_array[i]->store_text(p_stream, p_indent + 1);
|
||||
}
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "</array>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DICT: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<dict>\n";
|
||||
for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) {
|
||||
p_stream += String("\t").repeat(p_indent + 1);
|
||||
p_stream += "<key>";
|
||||
p_stream += it->key();
|
||||
p_stream += "</key>\n";
|
||||
it->value()->store_text(p_stream, p_indent + 1);
|
||||
}
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "</dict>\n";
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
PList::PList() {
|
||||
root = PListNode::new_dict();
|
||||
}
|
||||
|
||||
PList::PList(const String &p_string) {
|
||||
load_string(p_string);
|
||||
}
|
||||
|
||||
bool PList::load_file(const String &p_filename) {
|
||||
root = Ref<PListNode>();
|
||||
|
||||
FileAccessRef fb = FileAccess::open(p_filename, FileAccess::READ);
|
||||
if (!fb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char magic[8];
|
||||
fb->get_buffer(magic, 8);
|
||||
|
||||
if (String::utf8((const char *)magic, 8) == "bplist00") {
|
||||
ERR_FAIL_V_MSG(false, "PList: Binary property lists are not supported.");
|
||||
} else {
|
||||
// Load text plist.
|
||||
Error err;
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(p_filename, &err);
|
||||
ERR_FAIL_COND_V(err != OK, false);
|
||||
|
||||
String ret;
|
||||
ret.parse_utf8((const char *)array.ptr(), array.size());
|
||||
return load_string(ret);
|
||||
}
|
||||
}
|
||||
|
||||
bool PList::load_string(const String &p_string) {
|
||||
root = Ref<PListNode>();
|
||||
|
||||
int pos = 0;
|
||||
bool in_plist = false;
|
||||
bool done_plist = false;
|
||||
List<Ref<PListNode>> stack;
|
||||
String key;
|
||||
while (pos >= 0) {
|
||||
int open_token_s = p_string.find("<", pos);
|
||||
if (open_token_s == -1) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. No tags found.");
|
||||
}
|
||||
int open_token_e = p_string.find(">", open_token_s);
|
||||
pos = open_token_e;
|
||||
|
||||
String token = p_string.substr(open_token_s + 1, open_token_e - open_token_s - 1);
|
||||
if (token.empty()) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Invalid token name.");
|
||||
}
|
||||
String value;
|
||||
if (token[0] == '?' || token[0] == '!') { // Skip <?xml ... ?> and <!DOCTYPE ... >
|
||||
int end_token_e = p_string.find(">", open_token_s);
|
||||
pos = end_token_e;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.find("plist", 0) == 0) {
|
||||
in_plist = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "/plist") {
|
||||
in_plist = false;
|
||||
done_plist = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!in_plist) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Node outside of <plist> tag.");
|
||||
}
|
||||
|
||||
if (token == "dict") {
|
||||
if (!stack.empty()) {
|
||||
// Add subnode end enter it.
|
||||
Ref<PListNode> dict = PListNode::new_dict();
|
||||
dict->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
|
||||
if (!stack.back()->get()->push_subnode(dict, key)) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type.");
|
||||
}
|
||||
stack.push_back(dict);
|
||||
} else {
|
||||
// Add root node.
|
||||
if (!root.is_null()) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Root node already set.");
|
||||
}
|
||||
Ref<PListNode> dict = PListNode::new_dict();
|
||||
stack.push_back(dict);
|
||||
root = dict;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "/dict") {
|
||||
// Exit current dict.
|
||||
if (stack.empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_DICT) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Mismatched </dict> tag.");
|
||||
}
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "array") {
|
||||
if (!stack.empty()) {
|
||||
// Add subnode end enter it.
|
||||
Ref<PListNode> arr = PListNode::new_array();
|
||||
if (!stack.back()->get()->push_subnode(arr, key)) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type.");
|
||||
}
|
||||
stack.push_back(arr);
|
||||
} else {
|
||||
// Add root node.
|
||||
if (!root.is_null()) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Root node already set.");
|
||||
}
|
||||
Ref<PListNode> arr = PListNode::new_array();
|
||||
stack.push_back(arr);
|
||||
root = arr;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "/array") {
|
||||
// Exit current array.
|
||||
if (stack.empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_ARRAY) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Mismatched </array> tag.");
|
||||
}
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token[token.length() - 1] == '/') {
|
||||
token = token.substr(0, token.length() - 1);
|
||||
} else {
|
||||
int end_token_s = p_string.find("</", pos);
|
||||
if (end_token_s == -1) {
|
||||
ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> tag.", token));
|
||||
}
|
||||
int end_token_e = p_string.find(">", end_token_s);
|
||||
pos = end_token_e;
|
||||
String end_token = p_string.substr(end_token_s + 2, end_token_e - end_token_s - 2);
|
||||
if (end_token != token) {
|
||||
ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> and <%s> tag pair.", token, end_token));
|
||||
}
|
||||
value = p_string.substr(open_token_e + 1, end_token_s - open_token_e - 1);
|
||||
}
|
||||
if (token == "key") {
|
||||
key = value;
|
||||
} else {
|
||||
Ref<PListNode> var = nullptr;
|
||||
if (token == "true") {
|
||||
var = PListNode::new_bool(true);
|
||||
} else if (token == "false") {
|
||||
var = PListNode::new_bool(false);
|
||||
} else if (token == "integer") {
|
||||
var = PListNode::new_int(value.to_int());
|
||||
} else if (token == "real") {
|
||||
var = PListNode::new_real(value.to_float());
|
||||
} else if (token == "string") {
|
||||
var = PListNode::new_string(value);
|
||||
} else if (token == "data") {
|
||||
var = PListNode::new_data(value);
|
||||
} else if (token == "date") {
|
||||
var = PListNode::new_date(value);
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(false, "PList: Invalid value type.");
|
||||
}
|
||||
if (stack.empty() || !stack.back()->get()->push_subnode(var, key)) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stack.empty() || !done_plist) {
|
||||
ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. Root node is not closed.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
PoolByteArray PList::save_asn1() const {
|
||||
if (root == nullptr) {
|
||||
ERR_FAIL_V_MSG(PoolByteArray(), "PList: Invalid PList, no root node.");
|
||||
}
|
||||
size_t size = root->get_asn1_size(1);
|
||||
uint8_t len_octets = 0;
|
||||
if (size < 0x80) {
|
||||
len_octets = 1;
|
||||
} else {
|
||||
size = root->get_asn1_size(2);
|
||||
if (size < 0xFFFF) {
|
||||
len_octets = 2;
|
||||
} else {
|
||||
size = root->get_asn1_size(3);
|
||||
if (size < 0xFFFFFF) {
|
||||
len_octets = 3;
|
||||
} else {
|
||||
size = root->get_asn1_size(4);
|
||||
if (size < 0xFFFFFFFF) {
|
||||
len_octets = 4;
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(PoolByteArray(), "PList: Data is too big for ASN.1 serializer, should be < 4 GiB.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PoolByteArray ret;
|
||||
if (!root->store_asn1(ret, len_octets)) {
|
||||
ERR_FAIL_V_MSG(PoolByteArray(), "PList: ASN.1 serializer error.");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
String PList::save_text() const {
|
||||
if (root == nullptr) {
|
||||
ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node.");
|
||||
}
|
||||
|
||||
String ret;
|
||||
ret += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
ret += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
|
||||
ret += "<plist version=\"1.0\">\n";
|
||||
|
||||
root->store_text(ret, 0);
|
||||
|
||||
ret += "</plist>\n\n";
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ref<PListNode> PList::get_root() {
|
||||
return root;
|
||||
}
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
116
platform/osx/export/plist.h
Normal file
116
platform/osx/export/plist.h
Normal file
@ -0,0 +1,116 @@
|
||||
/*************************************************************************/
|
||||
/* plist.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
// Property list file format (application/x-plist) parser, property list ASN-1 serialization.
|
||||
|
||||
#ifndef PLIST_H
|
||||
#define PLIST_H
|
||||
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
|
||||
class PListNode;
|
||||
|
||||
class PList : public Reference {
|
||||
friend class PListNode;
|
||||
|
||||
public:
|
||||
enum PLNodeType {
|
||||
PL_NODE_TYPE_NIL,
|
||||
PL_NODE_TYPE_STRING,
|
||||
PL_NODE_TYPE_ARRAY,
|
||||
PL_NODE_TYPE_DICT,
|
||||
PL_NODE_TYPE_BOOLEAN,
|
||||
PL_NODE_TYPE_INTEGER,
|
||||
PL_NODE_TYPE_REAL,
|
||||
PL_NODE_TYPE_DATA,
|
||||
PL_NODE_TYPE_DATE,
|
||||
};
|
||||
|
||||
private:
|
||||
Ref<PListNode> root;
|
||||
|
||||
public:
|
||||
PList();
|
||||
PList(const String &p_string);
|
||||
|
||||
bool load_file(const String &p_filename);
|
||||
bool load_string(const String &p_string);
|
||||
|
||||
PoolByteArray save_asn1() const;
|
||||
String save_text() const;
|
||||
|
||||
Ref<PListNode> get_root();
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
class PListNode : public Reference {
|
||||
static int _asn1_size_len(uint8_t p_len_octets);
|
||||
|
||||
public:
|
||||
PList::PLNodeType data_type = PList::PLNodeType::PL_NODE_TYPE_NIL;
|
||||
|
||||
CharString data_string;
|
||||
Vector<Ref<PListNode>> data_array;
|
||||
Map<String, Ref<PListNode>> data_dict;
|
||||
union {
|
||||
int32_t data_int;
|
||||
bool data_bool;
|
||||
float data_real;
|
||||
};
|
||||
|
||||
static Ref<PListNode> new_array();
|
||||
static Ref<PListNode> new_dict();
|
||||
static Ref<PListNode> new_string(const String &p_string);
|
||||
static Ref<PListNode> new_data(const String &p_string);
|
||||
static Ref<PListNode> new_date(const String &p_string);
|
||||
static Ref<PListNode> new_bool(bool p_bool);
|
||||
static Ref<PListNode> new_int(int32_t p_int);
|
||||
static Ref<PListNode> new_real(float p_real);
|
||||
|
||||
bool push_subnode(const Ref<PListNode> &p_node, const String &p_key = "");
|
||||
|
||||
size_t get_asn1_size(uint8_t p_len_octets) const;
|
||||
|
||||
void store_asn1_size(PoolByteArray &p_stream, uint8_t p_len_octets) const;
|
||||
bool store_asn1(PoolByteArray &p_stream, uint8_t p_len_octets) const;
|
||||
void store_text(String &p_stream, uint8_t p_indent) const;
|
||||
|
||||
PListNode() {}
|
||||
~PListNode() {}
|
||||
};
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
|
||||
#endif // PLIST_H
|
Loading…
Reference in New Issue
Block a user