Forráskód Böngészése

Adds PCK encryption support (using script encryption key for export).
Change default encryption mode from ECB to CFB.

bruvzg 5 éve
szülő
commit
f043eabdd8

+ 16 - 4
core/crypto/crypto_core.cpp

@@ -140,13 +140,19 @@ Error CryptoCore::AESContext::encrypt_ecb(const uint8_t p_src[16], uint8_t r_dst
 	return ret ? FAILED : OK;
 }
 
-Error CryptoCore::AESContext::decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]) {
-	int ret = mbedtls_aes_crypt_ecb((mbedtls_aes_context *)ctx, MBEDTLS_AES_DECRYPT, p_src, r_dst);
+Error CryptoCore::AESContext::encrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
+	int ret = mbedtls_aes_crypt_cbc((mbedtls_aes_context *)ctx, MBEDTLS_AES_ENCRYPT, p_length, r_iv, p_src, r_dst);
 	return ret ? FAILED : OK;
 }
 
-Error CryptoCore::AESContext::encrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
-	int ret = mbedtls_aes_crypt_cbc((mbedtls_aes_context *)ctx, MBEDTLS_AES_ENCRYPT, p_length, r_iv, p_src, r_dst);
+Error CryptoCore::AESContext::encrypt_cfb(size_t p_length, uint8_t p_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
+	size_t iv_off = 0; // Ignore and assume 16-byte alignment.
+	int ret = mbedtls_aes_crypt_cfb128((mbedtls_aes_context *)ctx, MBEDTLS_AES_ENCRYPT, p_length, &iv_off, p_iv, p_src, r_dst);
+	return ret ? FAILED : OK;
+}
+
+Error CryptoCore::AESContext::decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]) {
+	int ret = mbedtls_aes_crypt_ecb((mbedtls_aes_context *)ctx, MBEDTLS_AES_DECRYPT, p_src, r_dst);
 	return ret ? FAILED : OK;
 }
 
@@ -155,6 +161,12 @@ Error CryptoCore::AESContext::decrypt_cbc(size_t p_length, uint8_t r_iv[16], con
 	return ret ? FAILED : OK;
 }
 
+Error CryptoCore::AESContext::decrypt_cfb(size_t p_length, uint8_t p_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
+	size_t iv_off = 0; // Ignore and assume 16-byte alignment.
+	int ret = mbedtls_aes_crypt_cfb128((mbedtls_aes_context *)ctx, MBEDTLS_AES_DECRYPT, p_length, &iv_off, p_iv, p_src, r_dst);
+	return ret ? FAILED : OK;
+}
+
 // CryptoCore
 String CryptoCore::b64_encode_str(const uint8_t *p_src, int p_src_len) {
 	int b64len = p_src_len / 3 * 4 + 4 + 1;

+ 2 - 0
core/crypto/crypto_core.h

@@ -88,6 +88,8 @@ public:
 		Error decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]);
 		Error encrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst);
 		Error decrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst);
+		Error encrypt_cfb(size_t p_length, uint8_t p_iv[16], const uint8_t *p_src, uint8_t *r_dst);
+		Error decrypt_cfb(size_t p_length, uint8_t p_iv[16], const uint8_t *p_src, uint8_t *r_dst);
 	};
 
 	static String b64_encode_str(const uint8_t *p_src, int p_src_len);

+ 45 - 28
core/io/file_access_encrypted.cpp

@@ -37,52 +37,54 @@
 
 #include <stdio.h>
 
-#define COMP_MAGIC 0x43454447
-
-Error FileAccessEncrypted::open_and_parse(FileAccess *p_base, const Vector<uint8_t> &p_key, Mode p_mode) {
+Error FileAccessEncrypted::open_and_parse(FileAccess *p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
 	ERR_FAIL_COND_V_MSG(file != nullptr, ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
 	ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
 
 	pos = 0;
 	eofed = false;
+	use_magic = p_with_magic;
 
 	if (p_mode == MODE_WRITE_AES256) {
 		data.clear();
 		writing = true;
 		file = p_base;
-		mode = p_mode;
 		key = p_key;
 
 	} else if (p_mode == MODE_READ) {
 		writing = false;
 		key = p_key;
-		uint32_t magic = p_base->get_32();
-		ERR_FAIL_COND_V(magic != COMP_MAGIC, ERR_FILE_UNRECOGNIZED);
 
-		mode = Mode(p_base->get_32());
-		ERR_FAIL_INDEX_V(mode, MODE_MAX, ERR_FILE_CORRUPT);
-		ERR_FAIL_COND_V(mode == 0, ERR_FILE_CORRUPT);
+		if (use_magic) {
+			uint32_t magic = p_base->get_32();
+			ERR_FAIL_COND_V(magic != ENCRYPTED_HEADER_MAGIC, ERR_FILE_UNRECOGNIZED);
+		}
 
 		unsigned char md5d[16];
 		p_base->get_buffer(md5d, 16);
 		length = p_base->get_64();
+
+		unsigned char iv[16];
+		for (int i = 0; i < 16; i++) {
+			iv[i] = p_base->get_8();
+		}
+
 		base = p_base->get_position();
 		ERR_FAIL_COND_V(p_base->get_len() < base + length, ERR_FILE_CORRUPT);
 		uint32_t ds = length;
 		if (ds % 16) {
 			ds += 16 - (ds % 16);
 		}
-
 		data.resize(ds);
 
 		uint32_t blen = p_base->get_buffer(data.ptrw(), ds);
 		ERR_FAIL_COND_V(blen != ds, ERR_FILE_CORRUPT);
 
-		CryptoCore::AESContext ctx;
-		ctx.set_decode_key(key.ptrw(), 256);
+		{
+			CryptoCore::AESContext ctx;
 
-		for (size_t i = 0; i < ds; i += 16) {
-			ctx.decrypt_ecb(&data.write[i], &data.write[i]);
+			ctx.set_encode_key(key.ptrw(), 256); // Due to the nature of CFB, same key schedule is used for both encryption and decryption!
+			ctx.decrypt_cfb(ds, iv, data.ptrw(), data.ptrw());
 		}
 
 		data.resize(length);
@@ -119,6 +121,25 @@ void FileAccessEncrypted::close() {
 		return;
 	}
 
+	_release();
+
+	file->close();
+	memdelete(file);
+
+	file = nullptr;
+}
+
+void FileAccessEncrypted::release() {
+	if (!file) {
+		return;
+	}
+
+	_release();
+
+	file = nullptr;
+}
+
+void FileAccessEncrypted::_release() {
 	if (writing) {
 		Vector<uint8_t> compressed;
 		size_t len = data.size();
@@ -138,27 +159,23 @@ void FileAccessEncrypted::close() {
 		CryptoCore::AESContext ctx;
 		ctx.set_encode_key(key.ptrw(), 256);
 
-		for (size_t i = 0; i < len; i += 16) {
-			ctx.encrypt_ecb(&compressed.write[i], &compressed.write[i]);
+		if (use_magic) {
+			file->store_32(ENCRYPTED_HEADER_MAGIC);
 		}
 
-		file->store_32(COMP_MAGIC);
-		file->store_32(mode);
-
 		file->store_buffer(hash, 16);
 		file->store_64(data.size());
 
-		file->store_buffer(compressed.ptr(), compressed.size());
-		file->close();
-		memdelete(file);
-		file = nullptr;
-		data.clear();
+		unsigned char iv[16];
+		for (int i = 0; i < 16; i++) {
+			iv[i] = Math::rand() % 256;
+			file->store_8(iv[i]);
+		}
 
-	} else {
-		file->close();
-		memdelete(file);
+		ctx.encrypt_cfb(len, iv, compressed.ptrw(), compressed.ptrw());
+
+		file->store_buffer(compressed.ptr(), compressed.size());
 		data.clear();
-		file = nullptr;
 	}
 }
 

+ 7 - 2
core/io/file_access_encrypted.h

@@ -33,6 +33,8 @@
 
 #include "core/os/file_access.h"
 
+#define ENCRYPTED_HEADER_MAGIC 0x43454447
+
 class FileAccessEncrypted : public FileAccess {
 public:
 	enum Mode {
@@ -42,7 +44,6 @@ public:
 	};
 
 private:
-	Mode mode = MODE_MAX;
 	Vector<uint8_t> key;
 	bool writing = false;
 	FileAccess *file = nullptr;
@@ -51,13 +52,17 @@ private:
 	Vector<uint8_t> data;
 	mutable int pos = 0;
 	mutable bool eofed = false;
+	bool use_magic = true;
+
+	void _release();
 
 public:
-	Error open_and_parse(FileAccess *p_base, const Vector<uint8_t> &p_key, Mode p_mode);
+	Error open_and_parse(FileAccess *p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic = true);
 	Error open_and_parse_password(FileAccess *p_base, const String &p_key, Mode p_mode);
 
 	virtual Error _open(const String &p_path, int p_mode_flags); ///< open a file
 	virtual void close(); ///< close a file
+	virtual void release(); ///< finish and keep base file open
 	virtual bool is_open() const; ///< true when file is open
 
 	virtual String get_path() const; /// returns the path for the current open file

+ 61 - 4
core/io/file_access_pack.cpp

@@ -30,6 +30,8 @@
 
 #include "file_access_pack.h"
 
+#include "core/io/file_access_encrypted.h"
+#include "core/script_language.h"
 #include "core/version.h"
 
 #include <stdio.h>
@@ -44,13 +46,14 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, size_t p_
 	return ERR_FILE_UNRECOGNIZED;
 }
 
-void PackedData::add_path(const String &pkg_path, const String &path, uint64_t ofs, uint64_t size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files) {
+void PackedData::add_path(const String &pkg_path, const String &path, uint64_t ofs, uint64_t size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted) {
 	PathMD5 pmd5(path.md5_buffer());
 	//printf("adding path %s, %lli, %lli\n", path.utf8().get_data(), pmd5.a, pmd5.b);
 
 	bool exists = files.has(pmd5);
 
 	PackedFile pf;
+	pf.encrypted = p_encrypted;
 	pf.pack = pkg_path;
 	pf.offset = ofs;
 	pf.size = size;
@@ -179,6 +182,11 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
 		ERR_FAIL_V_MSG(false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + ".");
 	}
 
+	uint32_t pack_flags = f->get_32();
+	uint64_t file_base = f->get_64();
+
+	bool enc_directory = (pack_flags & PACK_DIR_ENCRYPTED);
+
 	for (int i = 0; i < 16; i++) {
 		//reserved
 		f->get_32();
@@ -186,6 +194,30 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
 
 	int file_count = f->get_32();
 
+	if (enc_directory) {
+		FileAccessEncrypted *fae = memnew(FileAccessEncrypted);
+		if (!fae) {
+			f->close();
+			memdelete(f);
+			ERR_FAIL_V_MSG(false, "Can't open encrypted pack directory.");
+		}
+
+		Vector<uint8_t> key;
+		key.resize(32);
+		for (int i = 0; i < key.size(); i++) {
+			key.write[i] = script_encryption_key[i];
+		}
+
+		Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
+		if (err) {
+			f->close();
+			memdelete(f);
+			memdelete(fae);
+			ERR_FAIL_V_MSG(false, "Can't open encrypted pack directory.");
+		}
+		f = fae;
+	}
+
 	for (int i = 0; i < file_count; i++) {
 		uint32_t sl = f->get_32();
 		CharString cs;
@@ -196,11 +228,13 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
 		String path;
 		path.parse_utf8(cs.ptr());
 
-		uint64_t ofs = f->get_64();
+		uint64_t ofs = file_base + f->get_64();
 		uint64_t size = f->get_64();
 		uint8_t md5[16];
 		f->get_buffer(md5, 16);
-		PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, this, p_replace_files);
+		uint32_t flags = f->get_32();
+
+		PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED));
 	}
 
 	f->close();
@@ -234,7 +268,7 @@ void FileAccessPack::seek(size_t p_position) {
 		eof = false;
 	}
 
-	f->seek(pf.offset + p_position);
+	f->seek(off + p_position);
 	pos = p_position;
 }
 
@@ -319,12 +353,35 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
 	ERR_FAIL_COND_MSG(!f, "Can't open pack-referenced file '" + String(pf.pack) + "'.");
 
 	f->seek(pf.offset);
+	off = pf.offset;
+
+	if (pf.encrypted) {
+		FileAccessEncrypted *fae = memnew(FileAccessEncrypted);
+		if (!fae) {
+			ERR_FAIL_MSG("Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
+		}
+
+		Vector<uint8_t> key;
+		key.resize(32);
+		for (int i = 0; i < key.size(); i++) {
+			key.write[i] = script_encryption_key[i];
+		}
+
+		Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
+		if (err) {
+			memdelete(fae);
+			ERR_FAIL_MSG("Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
+		}
+		f = fae;
+		off = 0;
+	}
 	pos = 0;
 	eof = false;
 }
 
 FileAccessPack::~FileAccessPack() {
 	if (f) {
+		f->close();
 		memdelete(f);
 	}
 }

+ 12 - 2
core/io/file_access_pack.h

@@ -40,7 +40,15 @@
 // Godot's packed file magic header ("GDPC" in ASCII).
 #define PACK_HEADER_MAGIC 0x43504447
 // The current packed file format version number.
-#define PACK_FORMAT_VERSION 1
+#define PACK_FORMAT_VERSION 2
+
+enum PackFlags {
+	PACK_DIR_ENCRYPTED = 1 << 0
+};
+
+enum PackFileFlags {
+	PACK_FILE_ENCRYPTED = 1 << 0
+};
 
 class PackSource;
 
@@ -56,6 +64,7 @@ public:
 		uint64_t size;
 		uint8_t md5[16];
 		PackSource *src;
+		bool encrypted;
 	};
 
 private:
@@ -102,7 +111,7 @@ private:
 
 public:
 	void add_pack_source(PackSource *p_source);
-	void add_path(const String &pkg_path, const String &path, uint64_t ofs, uint64_t size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files); // for PackSource
+	void add_path(const String &pkg_path, const String &path, uint64_t ofs, uint64_t size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false); // for PackSource
 
 	void set_disabled(bool p_disabled) { disabled = p_disabled; }
 	_FORCE_INLINE_ bool is_disabled() const { return disabled; }
@@ -135,6 +144,7 @@ class FileAccessPack : public FileAccess {
 
 	mutable size_t pos;
 	mutable bool eof;
+	uint64_t off;
 
 	FileAccess *f;
 	virtual Error _open(const String &p_path, int p_mode_flags);

+ 1 - 1
core/io/file_access_zip.cpp

@@ -200,7 +200,7 @@ bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, size_
 		files[fname] = f;
 
 		uint8_t md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
-		PackedData::get_singleton()->add_path(p_path, fname, 1, 0, md5, this, p_replace_files);
+		PackedData::get_singleton()->add_path(p_path, fname, 1, 0, md5, this, p_replace_files, false);
 		//printf("packed data add path %s, %s\n", p_name.utf8().get_data(), fname.utf8().get_data());
 
 		if ((i + 1) < gi.number_entry) {

+ 146 - 45
core/io/pck_packer.cpp

@@ -30,36 +30,58 @@
 
 #include "pck_packer.h"
 
+#include "core/crypto/crypto_core.h"
+#include "core/io/file_access_encrypted.h"
 #include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION
 #include "core/os/file_access.h"
 #include "core/version.h"
 
-static uint64_t _align(uint64_t p_n, int p_alignment) {
-	if (p_alignment == 0) {
-		return p_n;
+static int _get_pad(int p_alignment, int p_n) {
+	int rest = p_n % p_alignment;
+	int pad = 0;
+	if (rest > 0) {
+		pad = p_alignment - rest;
 	}
 
-	uint64_t rest = p_n % p_alignment;
-	if (rest == 0) {
-		return p_n;
-	} else {
-		return p_n + (p_alignment - rest);
-	}
-}
-
-static void _pad(FileAccess *p_file, int p_bytes) {
-	for (int i = 0; i < p_bytes; i++) {
-		p_file->store_8(0);
-	}
+	return pad;
 }
 
 void PCKPacker::_bind_methods() {
-	ClassDB::bind_method(D_METHOD("pck_start", "pck_name", "alignment"), &PCKPacker::pck_start, DEFVAL(0));
-	ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path"), &PCKPacker::add_file);
+	ClassDB::bind_method(D_METHOD("pck_start", "pck_name", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(0), DEFVAL(String()), DEFVAL(false));
+	ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("flush", "verbose"), &PCKPacker::flush, DEFVAL(false));
 }
 
-Error PCKPacker::pck_start(const String &p_file, int p_alignment) {
+Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &p_key, bool p_encrypt_directory) {
+	ERR_FAIL_COND_V_MSG((p_key.empty() || !p_key.is_valid_hex_number(false) || p_key.length() != 64), ERR_CANT_CREATE, "Invalid Encryption Key (must be 64 characters long).");
+
+	String _key = p_key.to_lower();
+	key.resize(32);
+	for (int i = 0; i < 32; i++) {
+		int v = 0;
+		if (i * 2 < _key.length()) {
+			char32_t ct = _key[i * 2];
+			if (ct >= '0' && ct <= '9') {
+				ct = ct - '0';
+			} else if (ct >= 'a' && ct <= 'f') {
+				ct = 10 + ct - 'a';
+			}
+			v |= ct << 4;
+		}
+
+		if (i * 2 + 1 < _key.length()) {
+			char32_t ct = _key[i * 2 + 1];
+			if (ct >= '0' && ct <= '9') {
+				ct = ct - '0';
+			} else if (ct >= 'a' && ct <= 'f') {
+				ct = 10 + ct - 'a';
+			}
+			v |= ct;
+		}
+		key.write[i] = v;
+	}
+	enc_dir = p_encrypt_directory;
+
 	if (file != nullptr) {
 		memdelete(file);
 	}
@@ -76,16 +98,19 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment) {
 	file->store_32(VERSION_MINOR);
 	file->store_32(VERSION_PATCH);
 
-	for (int i = 0; i < 16; i++) {
-		file->store_32(0); // reserved
+	uint32_t pack_flags = 0;
+	if (enc_dir) {
+		pack_flags |= PACK_DIR_ENCRYPTED;
 	}
+	file->store_32(pack_flags); // flags
 
 	files.clear();
+	ofs = 0;
 
 	return OK;
 }
 
-Error PCKPacker::add_file(const String &p_file, const String &p_src) {
+Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encrypt) {
 	FileAccess *f = FileAccess::open(p_src, FileAccess::READ);
 	if (!f) {
 		return ERR_FILE_CANT_OPEN;
@@ -94,8 +119,32 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src) {
 	File pf;
 	pf.path = p_file;
 	pf.src_path = p_src;
+	pf.ofs = ofs;
 	pf.size = f->get_len();
-	pf.offset_offset = 0;
+
+	Vector<uint8_t> data = FileAccess::get_file_as_array(p_src);
+	{
+		unsigned char hash[16];
+		CryptoCore::md5(data.ptr(), data.size(), hash);
+		pf.md5.resize(16);
+		for (int i = 0; i < 16; i++) {
+			pf.md5.write[i] = hash[i];
+		}
+	}
+	pf.encrypted = p_encrypt;
+
+	uint64_t _size = pf.size;
+	if (p_encrypt) { // Add encryption overhead.
+		if (_size % 16) { // Pad to encryption block size.
+			_size += 16 - (_size % 16);
+		}
+		_size += 16; // hash
+		_size += 8; // data size
+		_size += 16; // iv
+	}
+
+	int pad = _get_pad(alignment, ofs + _size);
+	ofs = ofs + _size + pad;
 
 	files.push_back(pf);
 
@@ -108,27 +157,64 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src) {
 Error PCKPacker::flush(bool p_verbose) {
 	ERR_FAIL_COND_V_MSG(!file, ERR_INVALID_PARAMETER, "File must be opened before use.");
 
-	// write the index
+	int64_t file_base_ofs = file->get_position();
+	file->store_64(0); // files base
 
+	for (int i = 0; i < 16; i++) {
+		file->store_32(0); // reserved
+	}
+
+	// write the index
 	file->store_32(files.size());
 
+	FileAccessEncrypted *fae = nullptr;
+	FileAccess *fhead = file;
+
+	if (enc_dir) {
+		fae = memnew(FileAccessEncrypted);
+		ERR_FAIL_COND_V(!fae, ERR_CANT_CREATE);
+
+		Error err = fae->open_and_parse(file, key, FileAccessEncrypted::MODE_WRITE_AES256, false);
+		ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
+
+		fhead = fae;
+	}
+
 	for (int i = 0; i < files.size(); i++) {
-		file->store_pascal_string(files[i].path);
-		files.write[i].offset_offset = file->get_position();
-		file->store_64(0); // offset
-		file->store_64(files[i].size); // size
-
-		// # empty md5
-		file->store_32(0);
-		file->store_32(0);
-		file->store_32(0);
-		file->store_32(0);
+		int string_len = files[i].path.utf8().length();
+		int pad = _get_pad(4, string_len);
+
+		fhead->store_32(string_len + pad);
+		fhead->store_buffer((const uint8_t *)files[i].path.utf8().get_data(), string_len);
+		for (int j = 0; j < pad; j++) {
+			fhead->store_8(0);
+		}
+
+		fhead->store_64(files[i].ofs);
+		fhead->store_64(files[i].size); // pay attention here, this is where file is
+		fhead->store_buffer(files[i].md5.ptr(), 16); //also save md5 for file
+
+		uint32_t flags = 0;
+		if (files[i].encrypted) {
+			flags |= PACK_FILE_ENCRYPTED;
+		}
+		fhead->store_32(flags);
+	}
+
+	if (fae) {
+		fae->release();
+		memdelete(fae);
 	}
 
-	uint64_t ofs = file->get_position();
-	ofs = _align(ofs, alignment);
+	int header_padding = _get_pad(alignment, file->get_position());
+	for (int i = 0; i < header_padding; i++) {
+		file->store_8(Math::rand() % 256);
+	}
 
-	_pad(file, ofs - file->get_position());
+	int64_t file_base = file->get_position();
+	file->seek(file_base_ofs);
+	file->store_64(file_base); // update files base
+	file->seek(file_base);
 
 	const uint32_t buf_max = 65536;
 	uint8_t *buf = memnew_arr(uint8_t, buf_max);
@@ -137,26 +223,41 @@ Error PCKPacker::flush(bool p_verbose) {
 	for (int i = 0; i < files.size(); i++) {
 		FileAccess *src = FileAccess::open(files[i].src_path, FileAccess::READ);
 		uint64_t to_write = files[i].size;
+
+		fae = nullptr;
+		FileAccess *ftmp = file;
+		if (files[i].encrypted) {
+			fae = memnew(FileAccessEncrypted);
+			ERR_FAIL_COND_V(!fae, ERR_CANT_CREATE);
+
+			Error err = fae->open_and_parse(file, key, FileAccessEncrypted::MODE_WRITE_AES256, false);
+			ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
+			ftmp = fae;
+		}
+
 		while (to_write > 0) {
 			int read = src->get_buffer(buf, MIN(to_write, buf_max));
-			file->store_buffer(buf, read);
+			ftmp->store_buffer(buf, read);
 			to_write -= read;
 		}
 
-		uint64_t pos = file->get_position();
-		file->seek(files[i].offset_offset); // go back to store the file's offset
-		file->store_64(ofs);
-		file->seek(pos);
+		if (fae) {
+			fae->release();
+			memdelete(fae);
+		}
 
-		ofs = _align(ofs + files[i].size, alignment);
-		_pad(file, ofs - pos);
+		int pad = _get_pad(alignment, file->get_position());
+		for (int j = 0; j < pad; j++) {
+			file->store_8(Math::rand() % 256);
+		}
 
 		src->close();
 		memdelete(src);
 		count += 1;
-		if (p_verbose && files.size() > 0) {
+		const int file_num = files.size();
+		if (p_verbose && (file_num > 0)) {
 			if (count % 100 == 0) {
-				printf("%i/%i (%.2f)\r", count, files.size(), float(count) / files.size() * 100);
+				printf("%i/%i (%.2f)\r", count, file_num, float(count) / file_num * 100);
 				fflush(stdout);
 			}
 		}

+ 10 - 4
core/io/pck_packer.h

@@ -40,20 +40,26 @@ class PCKPacker : public Reference {
 
 	FileAccess *file = nullptr;
 	int alignment;
+	uint64_t ofs = 0;
+
+	Vector<uint8_t> key;
+	bool enc_dir = false;
 
 	static void _bind_methods();
 
 	struct File {
 		String path;
 		String src_path;
-		int size;
-		uint64_t offset_offset;
+		uint64_t ofs;
+		uint64_t size;
+		bool encrypted;
+		Vector<uint8_t> md5;
 	};
 	Vector<File> files;
 
 public:
-	Error pck_start(const String &p_file, int p_alignment = 0);
-	Error add_file(const String &p_file, const String &p_src);
+	Error pck_start(const String &p_file, int p_alignment = 0, const String &p_key = String(), bool p_encrypt_directory = false);
+	Error add_file(const String &p_file, const String &p_src, bool p_encrypt = false);
 	Error flush(bool p_verbose = false);
 
 	PCKPacker() {}

+ 6 - 0
doc/classes/PCKPacker.xml

@@ -23,6 +23,8 @@
 			</argument>
 			<argument index="1" name="source_path" type="String">
 			</argument>
+			<argument index="2" name="encrypt" type="bool" default="false">
+			</argument>
 			<description>
 				Adds the [code]source_path[/code] file to the current PCK package at the [code]pck_path[/code] internal path (should start with [code]res://[/code]).
 			</description>
@@ -43,6 +45,10 @@
 			</argument>
 			<argument index="1" name="alignment" type="int" default="0">
 			</argument>
+			<argument index="2" name="key" type="String" default="&quot;&quot;">
+			</argument>
+			<argument index="3" name="encrypt_directory" type="bool" default="false">
+			</argument>
 			<description>
 				Creates a new PCK file with the name [code]pck_name[/code]. The [code].pck[/code] file extension isn't added automatically, so it should be part of [code]pck_name[/code] (even though it's not required).
 			</description>

+ 229 - 33
editor/editor_export.cpp

@@ -32,6 +32,7 @@
 
 #include "core/crypto/crypto_core.h"
 #include "core/io/config_file.h"
+#include "core/io/file_access_encrypted.h"
 #include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION
 #include "core/io/resource_loader.h"
 #include "core/io/resource_saver.h"
@@ -222,6 +223,42 @@ String EditorExportPreset::get_custom_features() const {
 	return custom_features;
 }
 
+void EditorExportPreset::set_enc_in_filter(const String &p_filter) {
+	enc_in_filters = p_filter;
+	EditorExport::singleton->save_presets();
+}
+
+String EditorExportPreset::get_enc_in_filter() const {
+	return enc_in_filters;
+}
+
+void EditorExportPreset::set_enc_ex_filter(const String &p_filter) {
+	enc_ex_filters = p_filter;
+	EditorExport::singleton->save_presets();
+}
+
+String EditorExportPreset::get_enc_ex_filter() const {
+	return enc_ex_filters;
+}
+
+void EditorExportPreset::set_enc_pck(bool p_enabled) {
+	enc_pck = p_enabled;
+	EditorExport::singleton->save_presets();
+}
+
+bool EditorExportPreset::get_enc_pck() const {
+	return enc_pck;
+}
+
+void EditorExportPreset::set_enc_directory(bool p_enabled) {
+	enc_directory = p_enabled;
+	EditorExport::singleton->save_presets();
+}
+
+bool EditorExportPreset::get_enc_directory() const {
+	return enc_directory;
+}
+
 void EditorExportPreset::set_script_export_mode(int p_mode) {
 	script_mode = p_mode;
 	EditorExport::singleton->save_presets();
@@ -292,20 +329,55 @@ void EditorExportPlatform::gen_debug_flags(Vector<String> &r_flags, int p_flags)
 	}
 }
 
-Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
 	PackData *pd = (PackData *)p_userdata;
 
 	SavedData sd;
 	sd.path_utf8 = p_path.utf8();
 	sd.ofs = pd->f->get_position();
 	sd.size = p_data.size();
+	sd.encrypted = false;
+
+	for (int i = 0; i < p_enc_in_filters.size(); ++i) {
+		if (p_path.matchn(p_enc_in_filters[i]) || p_path.replace("res://", "").matchn(p_enc_in_filters[i])) {
+			sd.encrypted = true;
+			break;
+		}
+	}
+
+	for (int i = 0; i < p_enc_ex_filters.size(); ++i) {
+		if (p_path.matchn(p_enc_ex_filters[i]) || p_path.replace("res://", "").matchn(p_enc_ex_filters[i])) {
+			sd.encrypted = false;
+			break;
+		}
+	}
 
-	pd->f->store_buffer(p_data.ptr(), p_data.size());
-	int pad = _get_pad(PCK_PADDING, sd.size);
+	FileAccessEncrypted *fae = nullptr;
+	FileAccess *ftmp = pd->f;
+
+	if (sd.encrypted) {
+		fae = memnew(FileAccessEncrypted);
+		ERR_FAIL_COND_V(!fae, ERR_SKIP);
+
+		Error err = fae->open_and_parse(ftmp, p_key, FileAccessEncrypted::MODE_WRITE_AES256, false);
+		ERR_FAIL_COND_V(err != OK, ERR_SKIP);
+		ftmp = fae;
+	}
+
+	// Store file content.
+	ftmp->store_buffer(p_data.ptr(), p_data.size());
+
+	if (fae) {
+		fae->release();
+		memdelete(fae);
+	}
+
+	int pad = _get_pad(PCK_PADDING, pd->f->get_position());
 	for (int i = 0; i < pad; i++) {
-		pd->f->store_8(0);
+		pd->f->store_8(Math::rand() % 256);
 	}
 
+	// Store MD5 of original file.
 	{
 		unsigned char hash[16];
 		CryptoCore::md5(p_data.ptr(), p_data.size(), hash);
@@ -324,7 +396,7 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa
 	return OK;
 }
 
-Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
 	String path = p_path.replace_first("res://", "");
 
 	ZipData *zd = (ZipData *)p_userdata;
@@ -694,6 +766,61 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 	_edit_filter_list(paths, p_preset->get_include_filter(), false);
 	_edit_filter_list(paths, p_preset->get_exclude_filter(), true);
 
+	// Get encryption filters.
+	bool enc_pck = p_preset->get_enc_pck();
+	Vector<String> enc_in_filters;
+	Vector<String> enc_ex_filters;
+	Vector<uint8_t> key;
+
+	if (enc_pck) {
+		Vector<String> enc_in_split = p_preset->get_enc_in_filter().split(",");
+		for (int i = 0; i < enc_in_split.size(); i++) {
+			String f = enc_in_split[i].strip_edges();
+			if (f.empty()) {
+				continue;
+			}
+			enc_in_filters.push_back(f);
+		}
+
+		Vector<String> enc_ex_split = p_preset->get_enc_ex_filter().split(",");
+		for (int i = 0; i < enc_ex_split.size(); i++) {
+			String f = enc_ex_split[i].strip_edges();
+			if (f.empty()) {
+				continue;
+			}
+			enc_ex_filters.push_back(f);
+		}
+
+		// Get encryption key.
+		String script_key = p_preset->get_script_encryption_key().to_lower();
+		key.resize(32);
+		if (script_key.length() == 64) {
+			for (int i = 0; i < 32; i++) {
+				int v = 0;
+				if (i * 2 < script_key.length()) {
+					char32_t ct = script_key[i * 2];
+					if (ct >= '0' && ct <= '9') {
+						ct = ct - '0';
+					} else if (ct >= 'a' && ct <= 'f') {
+						ct = 10 + ct - 'a';
+					}
+					v |= ct << 4;
+				}
+
+				if (i * 2 + 1 < script_key.length()) {
+					char32_t ct = script_key[i * 2 + 1];
+					if (ct >= '0' && ct <= '9') {
+						ct = ct - '0';
+					} else if (ct >= 'a' && ct <= 'f') {
+						ct = 10 + ct - 'a';
+					}
+					v |= ct;
+				}
+				key.write[i] = v;
+			}
+		}
+	}
+
 	Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
 	for (int i = 0; i < export_plugins.size(); i++) {
 		export_plugins.write[i]->set_export_preset(p_preset);
@@ -704,7 +831,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 			}
 		}
 		for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) {
-			p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size());
+			p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size(), enc_in_filters, enc_ex_filters, key);
 		}
 
 		export_plugins.write[i]->_clear();
@@ -756,14 +883,14 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 				if (remap == "path") {
 					String remapped_path = config->get_value("remap", remap);
 					Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path);
-					err = p_func(p_udata, remapped_path, array, idx, total);
+					err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
 				} else if (remap.begins_with("path.")) {
 					String feature = remap.get_slice(".", 1);
 
 					if (remap_features.has(feature)) {
 						String remapped_path = config->get_value("remap", remap);
 						Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path);
-						err = p_func(p_udata, remapped_path, array, idx, total);
+						err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
 					}
 				}
 			}
@@ -774,7 +901,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 
 			//also save the .import file
 			Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import");
-			err = p_func(p_udata, path + ".import", array, idx, total);
+			err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key);
 
 			if (err != OK) {
 				return err;
@@ -795,7 +922,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 				}
 
 				for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) {
-					p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total);
+					p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, key);
 					if (export_plugins[i]->extra_files[j].remap) {
 						do_export = false; //if remap, do not
 						path_remaps.push_back(path);
@@ -815,7 +942,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 			//just store it as it comes
 			if (do_export) {
 				Vector<uint8_t> array = FileAccess::get_file_as_array(path);
-				p_func(p_udata, path, array, idx, total);
+				p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key);
 			}
 		}
 
@@ -851,7 +978,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 					new_file.write[j] = utf8[j];
 				}
 
-				p_func(p_udata, from + ".remap", new_file, idx, total);
+				p_func(p_udata, from + ".remap", new_file, idx, total, enc_in_filters, enc_ex_filters, key);
 			}
 		} else {
 			//old remap mode, will still work, but it's unused because it's not multiple pck export friendly
@@ -864,11 +991,11 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 	String splash = ProjectSettings::get_singleton()->get("application/boot_splash/image");
 	if (icon != String() && FileAccess::exists(icon)) {
 		Vector<uint8_t> array = FileAccess::get_file_as_array(icon);
-		p_func(p_udata, icon, array, idx, total);
+		p_func(p_udata, icon, array, idx, total, enc_in_filters, enc_ex_filters, key);
 	}
 	if (splash != String() && FileAccess::exists(splash) && icon != splash) {
 		Vector<uint8_t> array = FileAccess::get_file_as_array(splash);
-		p_func(p_udata, splash, array, idx, total);
+		p_func(p_udata, splash, array, idx, total, enc_in_filters, enc_ex_filters, key);
 	}
 
 	String config_file = "project.binary";
@@ -877,7 +1004,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 	Vector<uint8_t> data = FileAccess::get_file_as_array(engine_cfb);
 	DirAccess::remove_file_or_error(engine_cfb);
 
-	p_func(p_udata, "res://" + config_file, data, idx, total);
+	p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key);
 
 	return OK;
 }
@@ -953,6 +1080,17 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c
 	f->store_32(VERSION_MINOR);
 	f->store_32(VERSION_PATCH);
 
+	uint32_t pack_flags = 0;
+	bool enc_pck = p_preset->get_enc_pck();
+	bool enc_directory = p_preset->get_enc_directory();
+	if (enc_pck && enc_directory) {
+		pack_flags |= PACK_DIR_ENCRYPTED;
+	}
+	f->store_32(pack_flags); // flags
+
+	uint64_t file_base_ofs = f->get_position();
+	f->store_64(0); // files base
+
 	for (int i = 0; i < 16; i++) {
 		//reserved
 		f->store_32(0);
@@ -960,40 +1098,82 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c
 
 	f->store_32(pd.file_ofs.size()); //amount of files
 
-	int64_t header_size = f->get_position();
+	FileAccessEncrypted *fae = nullptr;
+	FileAccess *fhead = f;
+
+	if (enc_pck && enc_directory) {
+		String script_key = p_preset->get_script_encryption_key().to_lower();
+		Vector<uint8_t> key;
+		key.resize(32);
+		if (script_key.length() == 64) {
+			for (int i = 0; i < 32; i++) {
+				int v = 0;
+				if (i * 2 < script_key.length()) {
+					char32_t ct = script_key[i * 2];
+					if (ct >= '0' && ct <= '9') {
+						ct = ct - '0';
+					} else if (ct >= 'a' && ct <= 'f') {
+						ct = 10 + ct - 'a';
+					}
+					v |= ct << 4;
+				}
 
-	//precalculate header size
+				if (i * 2 + 1 < script_key.length()) {
+					char32_t ct = script_key[i * 2 + 1];
+					if (ct >= '0' && ct <= '9') {
+						ct = ct - '0';
+					} else if (ct >= 'a' && ct <= 'f') {
+						ct = 10 + ct - 'a';
+					}
+					v |= ct;
+				}
+				key.write[i] = v;
+			}
+		}
+		fae = memnew(FileAccessEncrypted);
+		ERR_FAIL_COND_V(!fae, ERR_SKIP);
 
-	for (int i = 0; i < pd.file_ofs.size(); i++) {
-		header_size += 4; // size of path string (32 bits is enough)
-		int string_len = pd.file_ofs[i].path_utf8.length();
-		header_size += string_len + _get_pad(4, string_len); ///size of path string
-		header_size += 8; // offset to file _with_ header size included
-		header_size += 8; // size of file
-		header_size += 16; // md5
-	}
+		err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_WRITE_AES256, false);
+		ERR_FAIL_COND_V(err != OK, ERR_SKIP);
 
-	int header_padding = _get_pad(PCK_PADDING, header_size);
+		fhead = fae;
+	}
 
 	for (int i = 0; i < pd.file_ofs.size(); i++) {
 		int string_len = pd.file_ofs[i].path_utf8.length();
 		int pad = _get_pad(4, string_len);
 
-		f->store_32(string_len + pad);
-		f->store_buffer((const uint8_t *)pd.file_ofs[i].path_utf8.get_data(), string_len);
+		fhead->store_32(string_len + pad);
+		fhead->store_buffer((const uint8_t *)pd.file_ofs[i].path_utf8.get_data(), string_len);
 		for (int j = 0; j < pad; j++) {
-			f->store_8(0);
+			fhead->store_8(0);
 		}
 
-		f->store_64(pd.file_ofs[i].ofs + header_padding + header_size);
-		f->store_64(pd.file_ofs[i].size); // pay attention here, this is where file is
-		f->store_buffer(pd.file_ofs[i].md5.ptr(), 16); //also save md5 for file
+		fhead->store_64(pd.file_ofs[i].ofs);
+		fhead->store_64(pd.file_ofs[i].size); // pay attention here, this is where file is
+		fhead->store_buffer(pd.file_ofs[i].md5.ptr(), 16); //also save md5 for file
+		uint32_t flags = 0;
+		if (pd.file_ofs[i].encrypted) {
+			flags |= PACK_FILE_ENCRYPTED;
+		}
+		fhead->store_32(flags);
 	}
 
+	if (fae) {
+		fae->release();
+		memdelete(fae);
+	}
+
+	int header_padding = _get_pad(PCK_PADDING, f->get_position());
 	for (int i = 0; i < header_padding; i++) {
-		f->store_8(0);
+		f->store_8(Math::rand() % 256);
 	}
 
+	uint64_t file_base = f->get_position();
+	f->seek(file_base_ofs);
+	f->store_64(file_base); // update files base
+	f->seek(file_base);
+
 	// Save the rest of the data.
 
 	ftmp = FileAccess::open(tmppath, FileAccess::READ);
@@ -1162,6 +1342,10 @@ void EditorExport::_save() {
 		config->set_value(section, "exclude_filter", preset->get_exclude_filter());
 		config->set_value(section, "export_path", preset->get_export_path());
 		config->set_value(section, "patch_list", preset->get_patches());
+		config->set_value(section, "encryption_include_filters", preset->get_enc_in_filter());
+		config->set_value(section, "encryption_exclude_filters", preset->get_enc_ex_filter());
+		config->set_value(section, "encrypt_pck", preset->get_enc_pck());
+		config->set_value(section, "encrypt_directory", preset->get_enc_directory());
 		config->set_value(section, "script_export_mode", preset->get_script_export_mode());
 		config->set_value(section, "script_encryption_key", preset->get_script_encryption_key());
 
@@ -1337,6 +1521,18 @@ void EditorExport::load_config() {
 			preset->add_patch(patch_list[i]);
 		}
 
+		if (config->has_section_key(section, "encrypt_pck")) {
+			preset->set_enc_pck(config->get_value(section, "encrypt_pck"));
+		}
+		if (config->has_section_key(section, "encrypt_directory")) {
+			preset->set_enc_directory(config->get_value(section, "encrypt_directory"));
+		}
+		if (config->has_section_key(section, "encryption_include_filters")) {
+			preset->set_enc_in_filter(config->get_value(section, "encryption_include_filters"));
+		}
+		if (config->has_section_key(section, "encryption_exclude_filters")) {
+			preset->set_enc_ex_filter(config->get_value(section, "encryption_exclude_filters"));
+		}
 		if (config->has_section_key(section, "script_export_mode")) {
 			preset->set_script_export_mode(config->get_value(section, "script_export_mode"));
 		}

+ 21 - 4
editor/editor_export.h

@@ -55,7 +55,6 @@ public:
 	enum ScriptExportMode {
 		MODE_SCRIPT_TEXT,
 		MODE_SCRIPT_COMPILED,
-		MODE_SCRIPT_ENCRYPTED,
 	};
 
 private:
@@ -81,6 +80,11 @@ private:
 
 	String custom_features;
 
+	String enc_in_filters;
+	String enc_ex_filters;
+	bool enc_pck = false;
+	bool enc_directory = false;
+
 	int script_mode = MODE_SCRIPT_COMPILED;
 	String script_key;
 
@@ -129,6 +133,18 @@ public:
 	void set_export_path(const String &p_path);
 	String get_export_path() const;
 
+	void set_enc_in_filter(const String &p_filter);
+	String get_enc_in_filter() const;
+
+	void set_enc_ex_filter(const String &p_filter);
+	String get_enc_ex_filter() const;
+
+	void set_enc_pck(bool p_enabled);
+	bool get_enc_pck() const;
+
+	void set_enc_directory(bool p_enabled);
+	bool get_enc_directory() const;
+
 	void set_script_export_mode(int p_mode);
 	int get_script_export_mode() const;
 
@@ -156,13 +172,14 @@ class EditorExportPlatform : public Reference {
 	GDCLASS(EditorExportPlatform, Reference);
 
 public:
-	typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
+	typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
 	typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so);
 
 private:
 	struct SavedData {
 		uint64_t ofs;
 		uint64_t size;
+		bool encrypted;
 		Vector<uint8_t> md5;
 		CharString path_utf8;
 
@@ -192,8 +209,8 @@ private:
 	void _export_find_dependencies(const String &p_path, Set<String> &p_paths);
 
 	void gen_debug_flags(Vector<String> &r_flags, int p_flags);
-	static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
-	static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
+	static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+	static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
 
 	void _edit_files_with_filter(DirAccess *da, const Vector<String> &p_filters, Set<String> &r_list, bool exclude);
 	void _edit_filter_list(Set<String> &r_list, const String &p_filter, bool exclude);

+ 113 - 12
editor/project_export.cpp

@@ -310,6 +310,24 @@ void ProjectExportDialog::_edit_preset(int p_index) {
 	_update_export_all();
 	child_controls_changed();
 
+	String enc_in_filters_str = current->get_enc_in_filter();
+	String enc_ex_filters_str = current->get_enc_ex_filter();
+	if (!updating_enc_filters) {
+		enc_in_filters->set_text(enc_in_filters_str);
+		enc_ex_filters->set_text(enc_ex_filters_str);
+	}
+
+	bool enc_pck_mode = current->get_enc_pck();
+	enc_pck->set_pressed(enc_pck_mode);
+
+	enc_directory->set_disabled(!enc_pck_mode);
+	enc_in_filters->set_editable(enc_pck_mode);
+	enc_ex_filters->set_editable(enc_pck_mode);
+	script_key->set_editable(enc_pck_mode);
+
+	bool enc_directory_mode = current->get_enc_directory();
+	enc_directory->set_pressed(enc_directory_mode);
+
 	int script_export_mode = current->get_script_export_mode();
 	script_mode->select(script_export_mode);
 
@@ -317,7 +335,7 @@ void ProjectExportDialog::_edit_preset(int p_index) {
 	if (!updating_script_key) {
 		script_key->set_text(key);
 	}
-	if (script_export_mode == EditorExportPreset::MODE_SCRIPT_ENCRYPTED) {
+	if (enc_pck_mode) {
 		script_key->set_editable(true);
 
 		bool key_valid = _validate_script_encryption_key(key);
@@ -519,6 +537,56 @@ void ProjectExportDialog::_export_path_changed(const StringName &p_property, con
 	_update_presets();
 }
 
+void ProjectExportDialog::_enc_filters_changed(const String &p_filters) {
+	if (updating) {
+		return;
+	}
+
+	Ref<EditorExportPreset> current = get_current_preset();
+	ERR_FAIL_COND(current.is_null());
+
+	current->set_enc_in_filter(enc_in_filters->get_text());
+	current->set_enc_ex_filter(enc_ex_filters->get_text());
+
+	updating_enc_filters = true;
+	_update_current_preset();
+	updating_enc_filters = false;
+}
+
+void ProjectExportDialog::_open_key_help_link() {
+	OS::get_singleton()->shell_open("https://docs.godotengine.org/en/latest/development/compiling/compiling_with_script_encryption_key.html");
+}
+
+void ProjectExportDialog::_enc_pck_changed(bool p_pressed) {
+	if (updating) {
+		return;
+	}
+
+	Ref<EditorExportPreset> current = get_current_preset();
+	ERR_FAIL_COND(current.is_null());
+
+	current->set_enc_pck(p_pressed);
+	enc_directory->set_disabled(!p_pressed);
+	enc_in_filters->set_editable(p_pressed);
+	enc_ex_filters->set_editable(p_pressed);
+	script_key->set_editable(p_pressed);
+
+	_update_current_preset();
+}
+
+void ProjectExportDialog::_enc_directory_changed(bool p_pressed) {
+	if (updating) {
+		return;
+	}
+
+	Ref<EditorExportPreset> current = get_current_preset();
+	ERR_FAIL_COND(current.is_null());
+
+	current->set_enc_directory(p_pressed);
+
+	_update_current_preset();
+}
+
 void ProjectExportDialog::_script_export_mode_changed(int p_mode) {
 	if (updating) {
 		return;
@@ -1148,6 +1216,12 @@ ProjectExportDialog::ProjectExportDialog() {
 			exclude_filters);
 	exclude_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_filter_changed));
 
+	script_mode = memnew(OptionButton);
+	resources_vb->add_margin_child(TTR("Script Export Mode:"), script_mode);
+	script_mode->add_item(TTR("Text"), (int)EditorExportPreset::MODE_SCRIPT_TEXT);
+	script_mode->add_item(TTR("Compiled"), (int)EditorExportPreset::MODE_SCRIPT_COMPILED);
+	script_mode->connect("item_selected", callable_mp(this, &ProjectExportDialog::_script_export_mode_changed));
+
 	// Patch packages.
 
 	VBoxContainer *patch_vb = memnew(VBoxContainer);
@@ -1205,23 +1279,50 @@ ProjectExportDialog::ProjectExportDialog() {
 	// Script export parameters.
 
 	updating_script_key = false;
+	updating_enc_filters = false;
+
+	VBoxContainer *sec_vb = memnew(VBoxContainer);
+	sec_vb->set_name(TTR("Encryption"));
+
+	enc_pck = memnew(CheckButton);
+	enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed));
+	enc_pck->set_text(TTR("Encrypt exported PCK"));
+	sec_vb->add_child(enc_pck);
+
+	enc_directory = memnew(CheckButton);
+	enc_directory->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_directory_changed));
+	enc_directory->set_text("Encrypt index (file names and info).");
+	sec_vb->add_child(enc_directory);
+
+	enc_in_filters = memnew(LineEdit);
+	enc_in_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_enc_filters_changed));
+	sec_vb->add_margin_child(
+			TTR("Filters to include files/folders\n(comma-separated, e.g: *.tscn, *.tres, scenes/*)"),
+			enc_in_filters);
+
+	enc_ex_filters = memnew(LineEdit);
+	enc_ex_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_enc_filters_changed));
+	sec_vb->add_margin_child(
+			TTR("Filters to exclude files/folders\n(comma-separated, e.g: *.stex, *.import, music/*)"),
+			enc_ex_filters);
 
-	VBoxContainer *script_vb = memnew(VBoxContainer);
-	script_vb->set_name(TTR("Script"));
-	script_mode = memnew(OptionButton);
-	script_vb->add_margin_child(TTR("Script Export Mode:"), script_mode);
-	script_mode->add_item(TTR("Text"), (int)EditorExportPreset::MODE_SCRIPT_TEXT);
-	script_mode->add_item(TTR("Compiled"), (int)EditorExportPreset::MODE_SCRIPT_COMPILED);
-	script_mode->add_item(TTR("Encrypted (Provide Key Below)"), (int)EditorExportPreset::MODE_SCRIPT_ENCRYPTED);
-	script_mode->connect("item_selected", callable_mp(this, &ProjectExportDialog::_script_export_mode_changed));
 	script_key = memnew(LineEdit);
 	script_key->connect("text_changed", callable_mp(this, &ProjectExportDialog::_script_encryption_key_changed));
 	script_key_error = memnew(Label);
 	script_key_error->set_text("- " + TTR("Invalid Encryption Key (must be 64 characters long)"));
 	script_key_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color("error_color", "Editor"));
-	script_vb->add_margin_child(TTR("Script Encryption Key (256-bits as hex):"), script_key);
-	script_vb->add_child(script_key_error);
-	sections->add_child(script_vb);
+	sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hex):"), script_key);
+	sec_vb->add_child(script_key_error);
+	sections->add_child(sec_vb);
+
+	Label *sec_info = memnew(Label);
+	sec_info->set_text(TTR("Note: Encryption key needs to be stored in the binary,\nyou need to build the export templates from source."));
+	sec_vb->add_child(sec_info);
+
+	LinkButton *sec_more_info = memnew(LinkButton);
+	sec_more_info->set_text(TTR("More Info..."));
+	sec_more_info->connect("pressed", callable_mp(this, &ProjectExportDialog::_open_key_help_link));
+	sec_vb->add_child(sec_more_info);
 
 	sections->connect("tab_changed", callable_mp(this, &ProjectExportDialog::_tab_changed));
 

+ 11 - 0
editor/project_export.h

@@ -145,6 +145,11 @@ private:
 	CheckBox *export_debug;
 	CheckBox *export_pck_zip_debug;
 
+	CheckButton *enc_pck;
+	CheckButton *enc_directory;
+	LineEdit *enc_in_filters;
+	LineEdit *enc_ex_filters;
+
 	void _open_export_template_manager();
 
 	void _export_pck_zip();
@@ -161,10 +166,16 @@ private:
 	void _custom_features_changed(const String &p_text);
 
 	bool updating_script_key;
+	bool updating_enc_filters;
+	void _enc_pck_changed(bool p_pressed);
+	void _enc_directory_changed(bool p_pressed);
+	void _enc_filters_changed(const String &p_text);
 	void _script_export_mode_changed(int p_mode);
 	void _script_encryption_key_changed(const String &p_key);
 	bool _validate_script_encryption_key(const String &p_key);
 
+	void _open_key_help_link();
+
 	void _tab_changed(int);
 
 protected:

+ 1 - 1
modules/gdscript/register_types.cpp

@@ -84,7 +84,7 @@ public:
 			return;
 		}
 
-		// TODO: Readd compiled/encrypted GDScript on export.
+		// TODO: Readd compiled GDScript on export.
 		return;
 	}
 };

+ 3 - 3
platform/android/export/export.cpp

@@ -723,7 +723,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 		return OK;
 	}
 
-	static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+	static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
 		APKExportData *ed = (APKExportData *)p_userdata;
 		String dst_path = p_path.replace_first("res://", "assets/");
 
@@ -731,7 +731,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 		return OK;
 	}
 
-	static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+	static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
 		return OK;
 	}
 
@@ -1525,7 +1525,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 	}
 
 public:
-	typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
+	typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
 
 public:
 	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override {

+ 1 - 1
platform/android/export/gradle_export_util.h

@@ -99,7 +99,7 @@ Error store_string_at_path(const String &p_path, const String &p_data) {
 // It is used by the export_project_files method to save all the asset files into the gradle project.
 // It's functionality mirrors that of the method save_apk_file.
 // This method will be called ONLY when custom build is enabled.
-Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
 	String dst_path = p_path.replace_first("res://", "res://android/build/assets/");
 	Error err = store_file_at_path(dst_path, p_data);
 	return err;

+ 1 - 1
platform/uwp/export/export.cpp

@@ -961,7 +961,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
 		return true;
 	}
 
-	static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+	static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
 		AppxPackager *packager = (AppxPackager *)p_userdata;
 		String dst_path = p_path.replace_first("res://", "game/");