Merge pull request #51310 from bruvzg/🍎🍏🍐🍎🍎
This commit is contained in:
commit
567b600362
|
@ -24,10 +24,7 @@
|
||||||
<string>$signature</string>
|
<string>$signature</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$version</string>
|
<string>$version</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
$usage_descriptions
|
||||||
<string>$microphone_usage_description</string>
|
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>$camera_usage_description</string>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>$copyright</string>
|
<string>$copyright</string>
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
|
@ -46,6 +43,6 @@
|
||||||
<string>10.12</string>
|
<string>10.12</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
$highres
|
$highres
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,369 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* 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/io/dir_access.h"
|
||||||
|
#include "core/io/file_access.h"
|
||||||
|
#include "core/object/ref_counted.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 RefCounted {
|
||||||
|
public:
|
||||||
|
virtual PackedByteArray get_hash_sha1() const = 0;
|
||||||
|
virtual PackedByteArray 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 {
|
||||||
|
PackedByteArray 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 PackedByteArray &p_data);
|
||||||
|
|
||||||
|
Vector<String> parse_requirements() const;
|
||||||
|
|
||||||
|
virtual PackedByteArray get_hash_sha1() const override;
|
||||||
|
virtual PackedByteArray 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 {
|
||||||
|
PackedByteArray blob;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CodeSignEntitlementsText();
|
||||||
|
CodeSignEntitlementsText(const String &p_string);
|
||||||
|
|
||||||
|
virtual PackedByteArray get_hash_sha1() const override;
|
||||||
|
virtual PackedByteArray 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 {
|
||||||
|
PackedByteArray blob;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CodeSignEntitlementsBinary();
|
||||||
|
CodeSignEntitlementsBinary(const String &p_string);
|
||||||
|
|
||||||
|
virtual PackedByteArray get_hash_sha1() const override;
|
||||||
|
virtual PackedByteArray 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:
|
||||||
|
PackedByteArray 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 PackedByteArray &p_hash, int p_slot);
|
||||||
|
|
||||||
|
virtual PackedByteArray get_hash_sha1() const override;
|
||||||
|
virtual PackedByteArray 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 {
|
||||||
|
PackedByteArray blob;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CodeSignSignature();
|
||||||
|
|
||||||
|
virtual PackedByteArray get_hash_sha1() const override;
|
||||||
|
virtual PackedByteArray 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 PackedByteArray file_hash_sha1(const String &p_path);
|
||||||
|
static PackedByteArray 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
|
|
@ -33,6 +33,9 @@
|
||||||
#include "export_plugin.h"
|
#include "export_plugin.h"
|
||||||
|
|
||||||
void register_osx_exporter() {
|
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;
|
Ref<EditorExportPlatformOSX> platform;
|
||||||
platform.instantiate();
|
platform.instantiate();
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "modules/modules_enabled.gen.h" // For regex.
|
||||||
|
|
||||||
|
#include "codesign.h"
|
||||||
#include "export_plugin.h"
|
#include "export_plugin.h"
|
||||||
|
|
||||||
void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
|
void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
|
||||||
|
@ -58,15 +61,25 @@ 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/version"), "1.0"));
|
||||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
|
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::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/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));
|
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::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/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::BOOL, "codesign/replace_existing_signature"), true));
|
||||||
|
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true));
|
||||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
|
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
|
||||||
|
|
||||||
if (!Engine::get_singleton()->has_singleton("GodotSharp")) {
|
if (!Engine::get_singleton()->has_singleton("GodotSharp")) {
|
||||||
|
@ -97,6 +110,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
|
||||||
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", 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));
|
||||||
r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array()));
|
r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array()));
|
||||||
|
|
||||||
|
#ifdef OSX_ENABLED
|
||||||
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
|
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
|
||||||
|
|
||||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false));
|
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false));
|
||||||
|
@ -305,13 +319,56 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
|
||||||
} else if (lines[i].find("$copyright") != -1) {
|
} else if (lines[i].find("$copyright") != -1) {
|
||||||
strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n";
|
strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n";
|
||||||
} else if (lines[i].find("$highres") != -1) {
|
} else if (lines[i].find("$highres") != -1) {
|
||||||
strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "<true/>" : "<false/>") + "\n";
|
strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n";
|
||||||
} else if (lines[i].find("$camera_usage_description") != -1) {
|
} else if (lines[i].find("$usage_descriptions") != -1) {
|
||||||
String description = p_preset->get("privacy/camera_usage_description");
|
String descriptions;
|
||||||
strnew += lines[i].replace("$camera_usage_description", description) + "\n";
|
if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) {
|
||||||
} else if (lines[i].find("$microphone_usage_description") != -1) {
|
descriptions += "\t<key>NSMicrophoneUsageDescription</key>\n";
|
||||||
String description = p_preset->get("privacy/microphone_usage_description");
|
descriptions += "\t<string>" + (String)p_preset->get("privacy/microphone_usage_description") + "</string>\n";
|
||||||
strnew += lines[i].replace("$microphone_usage_description", description) + "\n";
|
}
|
||||||
|
if (!((String)p_preset->get("privacy/camera_usage_description")).is_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")).is_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")).is_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")).is_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")).is_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")).is_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")).is_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")).is_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")).is_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")).is_empty()) {
|
||||||
|
descriptions += "\t<key>NSRemovableVolumesUsageDescription</key>\n";
|
||||||
|
descriptions += "\t<string>" + (String)p_preset->get("privacy/removable_volumes_usage_description") + "</string>\n";
|
||||||
|
}
|
||||||
|
if (!descriptions.is_empty()) {
|
||||||
|
strnew += lines[i].replace("$usage_descriptions", descriptions);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
strnew += lines[i] + "\n";
|
strnew += lines[i] + "\n";
|
||||||
}
|
}
|
||||||
|
@ -362,14 +419,16 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset
|
||||||
Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true);
|
Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true);
|
||||||
ERR_FAIL_COND_V(err != OK, err);
|
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) {
|
if (str.find("RequestUUID") == -1) {
|
||||||
EditorNode::add_io_error("altool: " + str);
|
EditorNode::add_io_error("altool: " + str);
|
||||||
return FAILED;
|
return FAILED;
|
||||||
} else {
|
} 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(TTR("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("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(" \"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
|
#endif
|
||||||
|
@ -378,71 +437,91 @@ 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) {
|
Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) {
|
||||||
#ifdef OSX_ENABLED
|
bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign");
|
||||||
List<String> args;
|
|
||||||
|
|
||||||
bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
|
bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
|
||||||
|
|
||||||
if (p_preset->get("codesign/timestamp")) {
|
if ((!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) || force_builtin_codesign) {
|
||||||
if (ad_hoc) {
|
print_verbose("using built-in codesign...");
|
||||||
|
#ifdef MODULE_REGEX_ENABLED
|
||||||
|
if (p_preset->get("codesign/timestamp")) {
|
||||||
WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!");
|
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 (p_preset->get("codesign/hardened_runtime")) {
|
|
||||||
if (ad_hoc) {
|
|
||||||
WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!");
|
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") {
|
String error_msg;
|
||||||
args.push_back("--entitlements");
|
Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg);
|
||||||
args.push_back(p_ent_path);
|
if (err != OK) {
|
||||||
}
|
EditorNode::add_io_error("Built-in CodeSign: " + error_msg);
|
||||||
|
return FAILED;
|
||||||
PackedStringArray user_args = p_preset->get("codesign/custom_options");
|
|
||||||
for (int i = 0; i < user_args.size(); i++) {
|
|
||||||
String user_arg = user_args[i].strip_edges();
|
|
||||||
if (!user_arg.is_empty()) {
|
|
||||||
args.push_back(user_arg);
|
|
||||||
}
|
}
|
||||||
}
|
#else
|
||||||
|
ERR_FAIL_V_MSG(FAILED, "Built-in CodeSign require regex module");
|
||||||
args.push_back("-s");
|
|
||||||
if (ad_hoc) {
|
|
||||||
args.push_back("-");
|
|
||||||
} else {
|
|
||||||
args.push_back(p_preset->get("codesign/identity"));
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push_back("-v"); /* provide some more feedback */
|
|
||||||
|
|
||||||
if (p_preset->get("codesign/replace_existing_signature")) {
|
|
||||||
args.push_back("-f");
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push_back(p_path);
|
|
||||||
|
|
||||||
String str;
|
|
||||||
Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
|
|
||||||
ERR_FAIL_COND_V(err != OK, err);
|
|
||||||
|
|
||||||
print_line("codesign (" + p_path + "):\n" + str);
|
|
||||||
if (str.find("no identity found") != -1) {
|
|
||||||
EditorNode::add_io_error("codesign: 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");
|
|
||||||
return FAILED;
|
|
||||||
}
|
|
||||||
#endif
|
#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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return OK;
|
if (p_path.get_extension() != "dmg") {
|
||||||
|
args.push_back("--entitlements");
|
||||||
|
args.push_back(p_ent_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedStringArray user_args = p_preset->get("codesign/custom_options");
|
||||||
|
for (int i = 0; i < user_args.size(); i++) {
|
||||||
|
String user_arg = user_args[i].strip_edges();
|
||||||
|
if (!user_arg.is_empty()) {
|
||||||
|
args.push_back(user_arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push_back("-s");
|
||||||
|
if (ad_hoc) {
|
||||||
|
args.push_back("-");
|
||||||
|
} else {
|
||||||
|
args.push_back(p_preset->get("codesign/identity"));
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push_back("-v"); /* provide some more feedback */
|
||||||
|
|
||||||
|
if (p_preset->get("codesign/replace_existing_signature")) {
|
||||||
|
args.push_back("-f");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push_back(p_path);
|
||||||
|
|
||||||
|
String str;
|
||||||
|
Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
|
||||||
|
ERR_FAIL_COND_V(err != OK, err);
|
||||||
|
|
||||||
|
print_verbose("codesign (" + p_path + "):\n" + str);
|
||||||
|
if (str.find("no identity found") != -1) {
|
||||||
|
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: " + TTR("Invalid entitlements file."));
|
||||||
|
return FAILED;
|
||||||
|
}
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
|
Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
|
||||||
|
@ -560,12 +639,12 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
|
||||||
Error err = OS::get_singleton()->execute("hdiutil", args, &str, nullptr, true);
|
Error err = OS::get_singleton()->execute("hdiutil", args, &str, nullptr, true);
|
||||||
ERR_FAIL_COND_V(err != OK, err);
|
ERR_FAIL_COND_V(err != OK, err);
|
||||||
|
|
||||||
print_line("hdiutil returned: " + str);
|
print_verbose("hdiutil returned: " + str);
|
||||||
if (str.find("create failed") != -1) {
|
if (str.find("create failed") != -1) {
|
||||||
if (str.find("File exists") != -1) {
|
if (str.find("File exists") != -1) {
|
||||||
EditorNode::add_io_error("hdiutil: create failed - file exists");
|
EditorNode::add_io_error("hdiutil: " + TTR("DMG creation failed, file already exists."));
|
||||||
} else {
|
} else {
|
||||||
EditorNode::add_io_error("hdiutil: create failed");
|
EditorNode::add_io_error("hdiutil: " + TTR("DMG create failed."));
|
||||||
}
|
}
|
||||||
return FAILED;
|
return FAILED;
|
||||||
}
|
}
|
||||||
|
@ -602,13 +681,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
FileAccess *src_f = nullptr;
|
FileAccess *src_f = nullptr;
|
||||||
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
|
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;
|
return ERR_SKIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
|
unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
|
||||||
if (!src_pkg_zip) {
|
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;
|
return ERR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,12 +706,27 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
|
|
||||||
pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name);
|
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.
|
// Create our application bundle.
|
||||||
String tmp_app_dir_name = pkg_name + ".app";
|
String tmp_app_dir_name = pkg_name + ".app";
|
||||||
String tmp_app_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name);
|
String tmp_app_path_name;
|
||||||
print_line("Exporting to " + tmp_app_path_name);
|
if (export_format == "app") {
|
||||||
|
tmp_app_path_name = p_path;
|
||||||
|
} else {
|
||||||
|
tmp_app_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name);
|
||||||
|
}
|
||||||
|
print_verbose("Exporting to " + tmp_app_path_name);
|
||||||
|
|
||||||
Error err = OK;
|
Error err = OK;
|
||||||
|
|
||||||
|
@ -641,16 +735,22 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
err = ERR_CANT_CREATE;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Array helpers = p_preset->get("codesign/entitlements/app_sandbox/helper_executables");
|
Array helpers = p_preset->get("codesign/entitlements/app_sandbox/helper_executables");
|
||||||
|
|
||||||
// Create our folder structure.
|
// Create our folder structure.
|
||||||
if (err == OK) {
|
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");
|
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err == OK) {
|
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");
|
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,7 +760,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err == OK) {
|
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");
|
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -689,6 +789,25 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
// Write.
|
// Write.
|
||||||
file = file.replace_first("osx_template.app/", "");
|
file = file.replace_first("osx_template.app/", "");
|
||||||
|
|
||||||
|
if (((info.external_fa >> 16L) & 0120000) == 0120000) {
|
||||||
|
#ifndef UNIX_ENABLED
|
||||||
|
WARN_PRINT(vformat("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") {
|
if (file == "Contents/Info.plist") {
|
||||||
_fix_plist(p_preset, data, pkg_name);
|
_fix_plist(p_preset, data, pkg_name);
|
||||||
}
|
}
|
||||||
|
@ -752,7 +871,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
dylibs_found.push_back(file);
|
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.
|
// Write it into our application bundle.
|
||||||
file = tmp_app_path_name.plus_file(file);
|
file = tmp_app_path_name.plus_file(file);
|
||||||
|
@ -782,12 +901,12 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
unzClose(src_pkg_zip);
|
unzClose(src_pkg_zip);
|
||||||
|
|
||||||
if (!found_binary) {
|
if (!found_binary) {
|
||||||
ERR_PRINT("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive.");
|
ERR_PRINT(vformat("Requested template binary '%s' not found. It might be missing from your template archive.", binary_to_use));
|
||||||
err = ERR_FILE_NOT_FOUND;
|
err = ERR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err == OK) {
|
if (err == OK) {
|
||||||
if (ep.step("Making PKG", 1)) {
|
if (ep.step(TTR("Making PKG"), 1)) {
|
||||||
return ERR_SKIP;
|
return ERR_SKIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -965,6 +1084,22 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755);
|
FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) {
|
||||||
|
ERR_PRINT("Application with an ad-hoc signature require 'Disable Library Validation' entitlement to load dynamic libraries.");
|
||||||
|
err = ERR_CANT_CREATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (err == OK) {
|
if (err == OK) {
|
||||||
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||||
for (int i = 0; i < shared_objects.size(); i++) {
|
for (int i = 0; i < shared_objects.size(); i++) {
|
||||||
|
@ -994,31 +1129,31 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err == OK && sign_enabled) {
|
if (err == OK && sign_enabled) {
|
||||||
if (ep.step("Code signing bundle", 2)) {
|
if (ep.step(TTR("Code signing bundle"), 2)) {
|
||||||
return ERR_SKIP;
|
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") {
|
if (export_format == "dmg") {
|
||||||
// Create a DMG.
|
// Create a DMG.
|
||||||
if (err == OK) {
|
if (err == OK) {
|
||||||
if (ep.step("Making DMG", 3)) {
|
if (ep.step(TTR("Making DMG"), 3)) {
|
||||||
return ERR_SKIP;
|
return ERR_SKIP;
|
||||||
}
|
}
|
||||||
err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
|
err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
|
||||||
}
|
}
|
||||||
// Sign DMG.
|
// Sign DMG.
|
||||||
if (err == OK && sign_enabled) {
|
if (err == OK && sign_enabled && !ad_hoc) {
|
||||||
if (ep.step("Code signing DMG", 3)) {
|
if (ep.step(TTR("Code signing DMG"), 3)) {
|
||||||
return ERR_SKIP;
|
return ERR_SKIP;
|
||||||
}
|
}
|
||||||
err = _code_sign(p_preset, p_path, ent_path);
|
err = _code_sign(p_preset, p_path, ent_path);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (export_format == "zip") {
|
||||||
// Create ZIP.
|
// Create ZIP.
|
||||||
if (err == OK) {
|
if (err == OK) {
|
||||||
if (ep.step("Making ZIP", 3)) {
|
if (ep.step(TTR("Making ZIP"), 3)) {
|
||||||
return ERR_SKIP;
|
return ERR_SKIP;
|
||||||
}
|
}
|
||||||
if (FileAccess::exists(p_path)) {
|
if (FileAccess::exists(p_path)) {
|
||||||
|
@ -1037,20 +1172,30 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
||||||
|
|
||||||
bool noto_enabled = p_preset->get("notarization/enable");
|
bool noto_enabled = p_preset->get("notarization/enable");
|
||||||
if (err == OK && noto_enabled) {
|
if (err == OK && noto_enabled) {
|
||||||
if (ep.step("Sending archive for notarization", 4)) {
|
if (export_format == "app") {
|
||||||
return ERR_SKIP;
|
WARN_PRINT("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);
|
||||||
}
|
}
|
||||||
err = _notarize(p_preset, p_path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up temporary entitlements files.
|
// Clean up temporary entitlements files.
|
||||||
DirAccess::remove_file_or_error(hlp_ent_path);
|
DirAccess::remove_file_or_error(hlp_ent_path);
|
||||||
|
|
||||||
// Clean up temporary .app dir.
|
// Clean up temporary .app dir and generated entitlements.
|
||||||
tmp_app_dir->change_dir(tmp_app_path_name);
|
if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") {
|
||||||
tmp_app_dir->erase_contents_recursive();
|
tmp_app_dir->remove(ent_path);
|
||||||
tmp_app_dir->change_dir("..");
|
}
|
||||||
tmp_app_dir->remove(tmp_app_dir_name);
|
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;
|
return err;
|
||||||
|
@ -1152,7 +1297,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String
|
||||||
|
|
||||||
FileAccessRef fa = FileAccess::open(dir.plus_file(f), FileAccess::READ);
|
FileAccessRef fa = FileAccess::open(dir.plus_file(f), FileAccess::READ);
|
||||||
if (!fa) {
|
if (!fa) {
|
||||||
ERR_FAIL_MSG("Can't open file to read from path '" + String(dir.plus_file(f)) + "'.");
|
ERR_FAIL_MSG(vformat("Can't open file to read from path \"%s\".", dir.plus_file(f)));
|
||||||
}
|
}
|
||||||
const int bufsize = 16384;
|
const int bufsize = 16384;
|
||||||
uint8_t buf[bufsize];
|
uint8_t buf[bufsize];
|
||||||
|
@ -1209,11 +1354,19 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef OSX_ENABLED
|
|
||||||
bool sign_enabled = p_preset->get("codesign/enable");
|
bool sign_enabled = p_preset->get("codesign/enable");
|
||||||
bool noto_enabled = p_preset->get("notarization/enable");
|
bool noto_enabled = p_preset->get("notarization/enable");
|
||||||
bool ad_hoc = ((p_preset->get("codesign/identity") == "") || (p_preset->get("codesign/identity") == "-"));
|
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 (noto_enabled) {
|
||||||
if (ad_hoc) {
|
if (ad_hoc) {
|
||||||
err += TTR("Notarization: Notarization with the ad-hoc signature is not supported.") + "\n";
|
err += TTR("Notarization: Notarization with the ad-hoc signature is not supported.") + "\n";
|
||||||
|
@ -1240,7 +1393,11 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err += TTR("Notarization is disabled. Exported project will be blocked by Gatekeeper, if it's downloaded from an unknown source.") + "\n";
|
#ifdef OSX_ENABLED
|
||||||
|
err += TTR("Warning: Notarization is disabled. 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. Exported project will be blocked by Gatekeeper, if it's downloaded from an unknown source.") + "\n";
|
||||||
|
#endif
|
||||||
if (!sign_enabled) {
|
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";
|
err += TTR("Code signing is disabled. Exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
|
||||||
} else {
|
} else {
|
||||||
|
@ -1252,9 +1409,33 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
err += TTR("macOS code signing and Notarization is not supported on the host OS. Exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
|
if (sign_enabled) {
|
||||||
#endif
|
if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).is_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")).is_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")).is_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")).is_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")).is_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")).is_empty()) {
|
||||||
|
err += TTR("Privacy: Photo library access is enabled, but usage description is not specified.") + "\n";
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!err.is_empty()) {
|
if (!err.is_empty()) {
|
||||||
r_error = err;
|
r_error = err;
|
||||||
|
|
|
@ -68,13 +68,13 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
|
||||||
Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
|
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);
|
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; }
|
bool use_codesign() const { return true; }
|
||||||
|
#ifdef OSX_ENABLED
|
||||||
bool use_dmg() const { return true; }
|
bool use_dmg() const { return true; }
|
||||||
#else
|
#else
|
||||||
bool use_codesign() const { return false; }
|
|
||||||
bool use_dmg() const { return false; }
|
bool use_dmg() const { return false; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
|
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
|
||||||
String pname = p_package;
|
String pname = p_package;
|
||||||
|
|
||||||
|
@ -113,6 +113,7 @@ public:
|
||||||
list.push_back("dmg");
|
list.push_back("dmg");
|
||||||
}
|
}
|
||||||
list.push_back("zip");
|
list.push_back("zip");
|
||||||
|
list.push_back("app");
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
|
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
|
||||||
|
|
|
@ -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 "modules/modules_enabled.gen.h" // For regex.
|
||||||
|
|
||||||
|
#include "lipo.h"
|
||||||
|
|
||||||
|
#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 PackedStringArray &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: Invalid MachO 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
|
|
@ -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/io/file_access.h"
|
||||||
|
#include "core/object/ref_counted.h"
|
||||||
|
#include "modules/modules_enabled.gen.h" // For regex.
|
||||||
|
|
||||||
|
#include "macho.h"
|
||||||
|
|
||||||
|
#ifdef MODULE_REGEX_ENABLED
|
||||||
|
|
||||||
|
class LipO : public RefCounted {
|
||||||
|
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 PackedStringArray &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
|
|
@ -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 "modules/modules_enabled.gen.h" // For regex.
|
||||||
|
|
||||||
|
#include "macho.h"
|
||||||
|
|
||||||
|
#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_length() + PAD(fa->get_length(), 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_length();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_length() + PAD(fa->get_length(), 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedByteArray MachO::get_cdhash_sha1() {
|
||||||
|
ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
|
||||||
|
if (signature_offset == 0) {
|
||||||
|
return PackedByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
fa->seek(get_signature_offset());
|
||||||
|
uint32_t magic = BSWAP32(fa->get_32());
|
||||||
|
if (magic != 0xfade0cc0) {
|
||||||
|
return PackedByteArray(); // 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 */
|
||||||
|
PackedByteArray hash;
|
||||||
|
hash.resize(0x14);
|
||||||
|
|
||||||
|
fa->seek(get_signature_offset() + offset);
|
||||||
|
PackedByteArray blob;
|
||||||
|
blob.resize(cdsize);
|
||||||
|
fa->get_buffer(blob.ptrw(), cdsize);
|
||||||
|
|
||||||
|
CryptoCore::SHA1Context ctx;
|
||||||
|
ctx.start();
|
||||||
|
ctx.update(blob.ptr(), blob.size());
|
||||||
|
ctx.finish(hash.ptrw());
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fa->seek(pos);
|
||||||
|
}
|
||||||
|
return PackedByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedByteArray MachO::get_cdhash_sha256() {
|
||||||
|
ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
|
||||||
|
if (signature_offset == 0) {
|
||||||
|
return PackedByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
fa->seek(get_signature_offset());
|
||||||
|
uint32_t magic = BSWAP32(fa->get_32());
|
||||||
|
if (magic != 0xfade0cc0) {
|
||||||
|
return PackedByteArray(); // 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 */
|
||||||
|
PackedByteArray hash;
|
||||||
|
hash.resize(0x20);
|
||||||
|
|
||||||
|
fa->seek(get_signature_offset() + offset);
|
||||||
|
PackedByteArray blob;
|
||||||
|
blob.resize(cdsize);
|
||||||
|
fa->get_buffer(blob.ptrw(), cdsize);
|
||||||
|
|
||||||
|
CryptoCore::SHA256Context ctx;
|
||||||
|
ctx.start();
|
||||||
|
ctx.update(blob.ptr(), blob.size());
|
||||||
|
ctx.finish(hash.ptrw());
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fa->seek(pos);
|
||||||
|
}
|
||||||
|
return PackedByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedByteArray MachO::get_requirements() {
|
||||||
|
ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
|
||||||
|
if (signature_offset == 0) {
|
||||||
|
return PackedByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
fa->seek(get_signature_offset());
|
||||||
|
uint32_t magic = BSWAP32(fa->get_32());
|
||||||
|
if (magic != 0xfade0cc0) {
|
||||||
|
return PackedByteArray(); // 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.
|
||||||
|
PackedByteArray blob;
|
||||||
|
fa->seek(get_signature_offset() + offset);
|
||||||
|
blob.resize(rqsize);
|
||||||
|
fa->get_buffer(blob.ptrw(), rqsize);
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
fa->seek(pos);
|
||||||
|
}
|
||||||
|
return PackedByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
|
@ -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/io/file_access.h"
|
||||||
|
#include "core/object/ref_counted.h"
|
||||||
|
#include "modules/modules_enabled.gen.h" // For regex.
|
||||||
|
|
||||||
|
#ifdef MODULE_REGEX_ENABLED
|
||||||
|
|
||||||
|
class MachO : public RefCounted {
|
||||||
|
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();
|
||||||
|
|
||||||
|
PackedByteArray get_cdhash_sha1();
|
||||||
|
PackedByteArray get_cdhash_sha256();
|
||||||
|
|
||||||
|
PackedByteArray 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
|
|
@ -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 "modules/modules_enabled.gen.h" // For regex.
|
||||||
|
|
||||||
|
#include "plist.h"
|
||||||
|
|
||||||
|
#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.is_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(PackedByteArray &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(PackedByteArray &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((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.is_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.is_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.is_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.is_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.is_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> token 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.is_empty() || !stack.back()->get()->push_subnode(var, key)) {
|
||||||
|
ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!stack.is_empty() || !done_plist) {
|
||||||
|
ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. Root node is not closed.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedByteArray PList::save_asn1() const {
|
||||||
|
if (root == nullptr) {
|
||||||
|
ERR_FAIL_V_MSG(PackedByteArray(), "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(PackedByteArray(), "PList: Data is too big for ASN.1 serializer, should be < 4 GiB.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedByteArray ret;
|
||||||
|
if (!root->store_asn1(ret, len_octets)) {
|
||||||
|
ERR_FAIL_V_MSG(PackedByteArray(), "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
|
|
@ -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/io/file_access.h"
|
||||||
|
#include "modules/modules_enabled.gen.h" // For regex.
|
||||||
|
|
||||||
|
#ifdef MODULE_REGEX_ENABLED
|
||||||
|
|
||||||
|
class PListNode;
|
||||||
|
|
||||||
|
class PList : public RefCounted {
|
||||||
|
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);
|
||||||
|
|
||||||
|
PackedByteArray save_asn1() const;
|
||||||
|
String save_text() const;
|
||||||
|
|
||||||
|
Ref<PListNode> get_root();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
class PListNode : public RefCounted {
|
||||||
|
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(PackedByteArray &p_stream, uint8_t p_len_octets) const;
|
||||||
|
bool store_asn1(PackedByteArray &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