From a378f9781d0aac23eb1e4851ff1a29086762f096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Fri, 12 Dec 2025 00:02:36 +0200 Subject: [PATCH] [SparsePCK] Add support for index encryption. --- core/io/file_access_pack.cpp | 25 +++++++++++++------ core/io/file_access_pack.h | 6 +++-- editor/export/editor_export_platform.cpp | 18 ++++++++++--- editor/export/editor_export_platform.h | 3 ++- platform/android/export/export_plugin.cpp | 22 ++++++++++++++-- .../android/export/gradle_export_util.cpp | 7 +++++- 6 files changed, 64 insertions(+), 17 deletions(-) diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 89967c1572..9e9e5e74b5 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -46,7 +46,7 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t return ERR_FILE_UNRECOGNIZED; } -void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted, bool p_bundle, bool p_delta) { +void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted, bool p_bundle, bool p_delta, const String &p_salt) { String simplified_path = p_path.simplify_path().trim_prefix("res://"); PathMD5 pmd5(simplified_path.md5_buffer()); @@ -57,6 +57,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64 pf.bundle = p_bundle; pf.delta = p_delta; pf.pack = p_pkg_path; + pf.salt = p_salt; pf.offset = p_ofs; pf.size = p_size; for (int i = 0; i < 16; i++) { @@ -291,22 +292,28 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, uint32_t ver_minor = f->get_32(); uint32_t ver_patch = f->get_32(); // Not used for validation. - ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION_V3 && version != PACK_FORMAT_VERSION_V2, false, vformat("Pack version unsupported: %d.", version)); + ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION_V4 && version != PACK_FORMAT_VERSION_V3 && version != PACK_FORMAT_VERSION_V2, false, vformat("Pack version unsupported: %d.", version)); ERR_FAIL_COND_V_MSG(ver_major > GODOT_VERSION_MAJOR || (ver_major == GODOT_VERSION_MAJOR && ver_minor > GODOT_VERSION_MINOR), false, vformat("Pack created with a newer version of the engine: %d.%d.%d.", ver_major, ver_minor, ver_patch)); uint32_t pack_flags = f->get_32(); bool enc_directory = (pack_flags & PACK_DIR_ENCRYPTED); bool rel_filebase = (pack_flags & PACK_REL_FILEBASE); // Note: Always enabled for V3. bool sparse_bundle = (pack_flags & PACK_SPARSE_BUNDLE); + String salt; uint64_t file_base = f->get_64(); - if ((version == PACK_FORMAT_VERSION_V3) || (version == PACK_FORMAT_VERSION_V2 && rel_filebase)) { + if ((version == PACK_FORMAT_VERSION_V4) || (version == PACK_FORMAT_VERSION_V3) || (version == PACK_FORMAT_VERSION_V2 && rel_filebase)) { file_base += pck_start_pos; } - if (version == PACK_FORMAT_VERSION_V3) { - // V3: Read directory offset and skip reserved part of the header. + if (version == PACK_FORMAT_VERSION_V3 || version == PACK_FORMAT_VERSION_V4) { + // V3/V4: Read directory offset and skip reserved part of the header. uint64_t dir_offset = f->get_64() + pck_start_pos; + if (sparse_bundle && enc_directory && version == PACK_FORMAT_VERSION_V4) { + // V4: Read encrypted directory salt. + Vector salt_data = f->get_buffer(32); + salt.append_latin1(Span((const char *)salt_data.ptr(), salt_data.size())); + } f->seek(dir_offset); } else if (version == PACK_FORMAT_VERSION_V2) { // V2: Directory directly after the header. @@ -350,7 +357,7 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, if (flags & PACK_FILE_REMOVAL) { // The file was removed. PackedData::get_singleton()->remove_path(path); } else { - PackedData::get_singleton()->add_path(p_path, path, file_base + ofs, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED), sparse_bundle, (flags & PACK_FILE_DELTA)); + PackedData::get_singleton()->add_path(p_path, path, file_base + ofs, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED), sparse_bundle, (flags & PACK_FILE_DELTA), salt); } } @@ -512,7 +519,11 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil pf = p_file; if (pf.bundle) { String simplified_path = p_path.simplify_path(); - f = FileAccess::open(simplified_path, FileAccess::READ | FileAccess::SKIP_PACK); + if (pf.salt.is_empty()) { + f = FileAccess::open(simplified_path, FileAccess::READ | FileAccess::SKIP_PACK); + } else { + f = FileAccess::open("res://" + (simplified_path + pf.salt).sha256_text(), FileAccess::READ | FileAccess::SKIP_PACK); + } ERR_FAIL_COND_MSG(f.is_null(), vformat(R"(Can't open pack-referenced file "%s" from sparse pack "%s".)", simplified_path, pf.pack)); off = 0; // For the sparse pack offset is always zero. } else { diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 121372b51e..3e43b74193 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -41,9 +41,10 @@ #define PACK_FORMAT_VERSION_V2 2 #define PACK_FORMAT_VERSION_V3 3 +#define PACK_FORMAT_VERSION_V4 4 // The current packed file format version number. -#define PACK_FORMAT_VERSION PACK_FORMAT_VERSION_V3 +#define PACK_FORMAT_VERSION PACK_FORMAT_VERSION_V4 enum PackFlags { PACK_DIR_ENCRYPTED = 1 << 0, @@ -74,6 +75,7 @@ public: bool encrypted; bool bundle; bool delta; + String salt; }; private: @@ -119,7 +121,7 @@ private: public: void add_pack_source(PackSource *p_source); - void add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false, bool p_bundle = false, bool p_delta = false); // for PackSource + void add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false, bool p_bundle = false, bool p_delta = false, const String &p_salt = String()); // for PackSource void remove_path(const String &p_path); uint8_t *get_file_hash(const String &p_path); Vector get_delta_patches(const String &p_path) const; diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 84c2e1ac9f..bd51cc3abf 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -1946,7 +1946,7 @@ Dictionary EditorExportPlatform::_save_zip_patch(const Ref & return ret; } -bool EditorExportPlatform::_store_header(Ref p_fd, bool p_enc, bool p_sparse, uint64_t &r_file_base_ofs, uint64_t &r_dir_base_ofs) { +bool EditorExportPlatform::_store_header(Ref p_fd, bool p_enc, bool p_sparse, uint64_t &r_file_base_ofs, uint64_t &r_dir_base_ofs, const String &p_salt) { p_fd->store_32(PACK_HEADER_MAGIC); p_fd->store_32(PACK_FORMAT_VERSION); p_fd->store_32(GODOT_VERSION_MAJOR); @@ -1968,8 +1968,18 @@ bool EditorExportPlatform::_store_header(Ref p_fd, bool p_enc, bool r_dir_base_ofs = p_fd->get_position(); p_fd->store_64(0); // Directory offset. - for (int i = 0; i < 16; i++) { - //reserved + if (p_enc && p_sparse && p_salt.length() == 32) { + CharString cs = p_salt.latin1(); + p_fd->store_buffer((const uint8_t *)cs.ptr(), 32); + } else { + for (int i = 0; i < 8; i++) { + // Reserved. + p_fd->store_32(0); + } + } + + for (int i = 0; i < 8; i++) { + // Reserved. p_fd->store_32(0); } return true; @@ -2094,7 +2104,7 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, b uint64_t file_base_ofs = 0; uint64_t dir_base_ofs = 0; - _store_header(f, p_preset->get_enc_pck() && p_preset->get_enc_directory(), false, file_base_ofs, dir_base_ofs); + _store_header(f, p_preset->get_enc_pck() && p_preset->get_enc_directory(), false, file_base_ofs, dir_base_ofs, String()); // Align for first file. int file_padding = _get_pad(PCK_PADDING, f->get_position()); diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 3c4f3c1cc9..8d0c29a123 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -94,6 +94,7 @@ public: struct PackData { String path; + String salt; Ref f; Vector file_ofs; EditorProgress *ep = nullptr; @@ -101,7 +102,7 @@ public: bool use_sparse_pck = false; }; - static bool _store_header(Ref p_fd, bool p_enc, bool p_sparse, uint64_t &r_file_base_ofs, uint64_t &r_dir_base_ofs); + static bool _store_header(Ref p_fd, bool p_enc, bool p_sparse, uint64_t &r_file_base_ofs, uint64_t &r_dir_base_ofs, const String &p_salt); static bool _encrypt_and_store_directory(Ref p_fd, PackData &p_pack_data, const Vector &p_key, uint64_t p_seed, uint64_t p_file_base); static Error _encrypt_and_store_data(Ref p_fd, const String &p_path, const Vector &p_data, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key, uint64_t p_seed, bool &r_encrypt); String _get_script_encryption_key(const Ref &p_preset) const; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index d443fbb4ae..a789be4ec3 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -39,6 +39,7 @@ #include "core/io/image_loader.h" #include "core/io/json.h" #include "core/io/marshalls.h" +#include "core/math/random_pcg.h" #include "core/string/translation_server.h" #include "core/version.h" #include "editor/editor_log.h" @@ -815,7 +816,12 @@ Error EditorExportPlatformAndroid::save_apk_file(const Ref & return err; } - const String dst_path = String("assets/") + simplified_path.trim_prefix("res://"); + String dst_path; + if (ed->pd.salt.length() == 32) { + dst_path = String("assets/") + (simplified_path + ed->pd.salt).sha256_text(); + } else { + dst_path = String("assets/") + simplified_path.trim_prefix("res://"); + } print_verbose("Saving project files from " + simplified_path + " into " + dst_path); store_in_apk(ed, dst_path, enc_data, _should_compress_asset(simplified_path, enc_data) ? Z_DEFLATED : 0); @@ -3531,7 +3537,7 @@ Error EditorExportPlatformAndroid::_generate_sparse_pck_metadata(const Refget_position(); uint64_t file_base_ofs = 0; uint64_t dir_base_ofs = 0; - EditorExportPlatform::_store_header(ftmp, p_preset->get_enc_pck() && p_preset->get_enc_directory(), true, file_base_ofs, dir_base_ofs); + EditorExportPlatform::_store_header(ftmp, p_preset->get_enc_pck() && p_preset->get_enc_directory(), true, file_base_ofs, dir_base_ofs, p_pack_data.salt); // Write directory. uint64_t dir_offset = ftmp->get_position(); @@ -3714,6 +3720,12 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget_enc_directory()) { + RandomPCG rng = RandomPCG(p_preset->get_seed()); + for (int i = 0; i < 32; i++) { + user_data.pd.salt += String::chr(1 + rng.rand() % 254); + } + } err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, nullptr, &user_data, copy_gradle_so); Vector enc_data; @@ -4219,6 +4231,12 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget_enc_directory()) { + RandomPCG rng = RandomPCG(p_preset->get_seed()); + for (int i = 0; i < 32; i++) { + ed.pd.salt += String::chr(1 + rng.rand() % 254); + } + } err = export_project_files(p_preset, p_debug, save_apk_file, nullptr, &ed, save_apk_so); Vector enc_data; diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 420c36e395..3e8f14720f 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -182,7 +182,12 @@ Error rename_and_store_file_in_gradle_project(const Ref &p_p return err; } - const String dst_path = export_data->assets_directory + String("/") + simplified_path.trim_prefix("res://"); + String dst_path; + if (export_data->pd.salt.length() == 32) { + dst_path = export_data->assets_directory + String("/") + (simplified_path + export_data->pd.salt).sha256_text(); + } else { + dst_path = export_data->assets_directory + String("/") + simplified_path.trim_prefix("res://"); + } print_verbose("Saving project files from " + simplified_path + " into " + dst_path); err = store_file_at_path(dst_path, enc_data);