summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRémi Verschelde <rverschelde@gmail.com>2024-09-26 12:45:42 +0200
committerRémi Verschelde <rverschelde@gmail.com>2024-09-26 12:45:42 +0200
commit2912cb99756dede609548503eeb3f6f1a2f2dfbf (patch)
treeb1c092f0ae2943ae5cfa1161f1442d2cbd91102f
parent991e6c92abd26435169ad7ea0df2622685cac6d1 (diff)
parentd3be030ea6f3e295603ccf6cc9080a1d32051332 (diff)
downloadredot-engine-2912cb99756dede609548503eeb3f6f1a2f2dfbf.tar.gz
Merge pull request #97118 from mihe/patch-exports
Add ability to export patch packs
-rw-r--r--core/io/file_access_pack.cpp16
-rw-r--r--core/io/file_access_pack.h3
-rw-r--r--doc/classes/EditorExportPlatform.xml42
-rw-r--r--doc/classes/EditorExportPlatformExtension.xml32
-rw-r--r--doc/classes/EditorExportPreset.xml6
-rw-r--r--editor/editor_node.cpp16
-rw-r--r--editor/editor_node.h4
-rw-r--r--editor/export/editor_export.cpp3
-rw-r--r--editor/export/editor_export_platform.cpp174
-rw-r--r--editor/export/editor_export_platform.h20
-rw-r--r--editor/export/editor_export_platform_extension.cpp40
-rw-r--r--editor/export/editor_export_platform_extension.h6
-rw-r--r--editor/export/editor_export_platform_pc.cpp2
-rw-r--r--editor/export/editor_export_preset.cpp37
-rw-r--r--editor/export/editor_export_preset.h9
-rw-r--r--editor/export/project_export.cpp189
-rw-r--r--editor/export/project_export.h13
-rw-r--r--main/main.cpp27
18 files changed, 619 insertions, 20 deletions
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index eec27ce0aa..1340382eaa 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -102,6 +102,22 @@ void PackedData::add_pack_source(PackSource *p_source) {
}
}
+uint8_t *PackedData::get_file_hash(const String &p_path) {
+ PathMD5 pmd5(p_path.md5_buffer());
+ HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
+ if (!E || E->value.offset == 0) {
+ return nullptr;
+ }
+
+ return E->value.md5;
+}
+
+void PackedData::clear() {
+ files.clear();
+ _free_packed_dirs(root);
+ root = memnew(PackedDir);
+}
+
PackedData *PackedData::singleton = nullptr;
PackedData::PackedData() {
diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h
index 595a36bca4..57b7a5f87f 100644
--- a/core/io/file_access_pack.h
+++ b/core/io/file_access_pack.h
@@ -111,6 +111,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); // for PackSource
+ uint8_t *get_file_hash(const String &p_path);
void set_disabled(bool p_disabled) { disabled = p_disabled; }
_FORCE_INLINE_ bool is_disabled() const { return disabled; }
@@ -118,6 +119,8 @@ public:
static PackedData *get_singleton() { return singleton; }
Error add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset);
+ void clear();
+
_FORCE_INLINE_ Ref<FileAccess> try_open_path(const String &p_path);
_FORCE_INLINE_ bool has_path(const String &p_path);
diff --git a/doc/classes/EditorExportPlatform.xml b/doc/classes/EditorExportPlatform.xml
index d4084e84a0..8792bbedc3 100644
--- a/doc/classes/EditorExportPlatform.xml
+++ b/doc/classes/EditorExportPlatform.xml
@@ -42,6 +42,18 @@
Creates a PCK archive at [param path] for the specified [param preset].
</description>
</method>
+ <method name="export_pack_patch">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="patches" type="PackedStringArray" default="PackedStringArray()" />
+ <param index="4" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Creates a patch PCK archive at [param path] for the specified [param preset], containing only the files that have changed since the last patch.
+ [b]Note:[/b] [param patches] is an optional override of the set of patches defined in the export preset. When empty the patches defined in the export preset will be used instead.
+ </description>
+ </method>
<method name="export_project">
<return type="int" enum="Error" />
<param index="0" name="preset" type="EditorExportPreset" />
@@ -75,6 +87,18 @@
Create a ZIP archive at [param path] for the specified [param preset].
</description>
</method>
+ <method name="export_zip_patch">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="patches" type="PackedStringArray" default="PackedStringArray()" />
+ <param index="4" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Create a patch ZIP archive at [param path] for the specified [param preset], containing only the files that have changed since the last patch.
+ [b]Note:[/b] [param patches] is an optional override of the set of patches defined in the export preset. When empty the patches defined in the export preset will be used instead.
+ </description>
+ </method>
<method name="find_export_template" qualifiers="const">
<return type="Dictionary" />
<param index="0" name="template_file_name" type="String" />
@@ -151,6 +175,15 @@
If [param embed] is [code]true[/code], PCK content is appended to the end of [param path] file and return [Dictionary] additionally include following keys: [code]embedded_start: int[/code] (embedded PCK offset) and [code]embedded_size: int[/code] (embedded PCK size).
</description>
</method>
+ <method name="save_pack_patch">
+ <return type="Dictionary" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <description>
+ Saves patch PCK archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
+ </description>
+ </method>
<method name="save_zip">
<return type="Dictionary" />
<param index="0" name="preset" type="EditorExportPreset" />
@@ -160,6 +193,15 @@
Saves ZIP archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
</description>
</method>
+ <method name="save_zip_patch">
+ <return type="Dictionary" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <description>
+ Saves patch ZIP archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
+ </description>
+ </method>
<method name="ssh_push_to_remote" qualifiers="const">
<return type="int" enum="Error" />
<param index="0" name="host" type="String" />
diff --git a/doc/classes/EditorExportPlatformExtension.xml b/doc/classes/EditorExportPlatformExtension.xml
index ef589e2f58..f2d14b1710 100644
--- a/doc/classes/EditorExportPlatformExtension.xml
+++ b/doc/classes/EditorExportPlatformExtension.xml
@@ -36,7 +36,21 @@
<description>
[b]Optional.[/b]
Creates a PCK archive at [param path] for the specified [param preset].
- This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and PCK is selected as a file type.
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, with "Export as Patch" disabled, and PCK is selected as a file type.
+ </description>
+ </method>
+ <method name="_export_pack_patch" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="patches" type="PackedStringArray" />
+ <param index="4" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ Creates a patch PCK archive at [param path] for the specified [param preset], containing only the files that have changed since the last patch.
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, with "Export as Patch" enabled, and PCK is selected as a file type.
+ [b]Note:[/b] The patches provided in [param patches] have already been loaded when this method is called and are merely provided as context. When empty the patches defined in the export preset have been loaded instead.
</description>
</method>
<method name="_export_project" qualifiers="virtual">
@@ -61,7 +75,21 @@
<description>
[b]Optional.[/b]
Create a ZIP archive at [param path] for the specified [param preset].
- This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and ZIP is selected as a file type.
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, with "Export as Patch" disabled, and ZIP is selected as a file type.
+ </description>
+ </method>
+ <method name="_export_zip_patch" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="patches" type="PackedStringArray" />
+ <param index="4" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ Create a ZIP archive at [param path] for the specified [param preset], containing only the files that have changed since the last patch.
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, with "Export as Patch" enabled, and ZIP is selected as a file type.
+ [b]Note:[/b] The patches provided in [param patches] have already been loaded when this method is called and are merely provided as context. When empty the patches defined in the export preset have been loaded instead.
</description>
</method>
<method name="_get_binary_extensions" qualifiers="virtual const">
diff --git a/doc/classes/EditorExportPreset.xml b/doc/classes/EditorExportPreset.xml
index bba79364e4..314f74340a 100644
--- a/doc/classes/EditorExportPreset.xml
+++ b/doc/classes/EditorExportPreset.xml
@@ -109,6 +109,12 @@
Returns export option value or value of environment variable if it is set.
</description>
</method>
+ <method name="get_patches" qualifiers="const">
+ <return type="PackedStringArray" />
+ <description>
+ Returns the list of packs on which to base a patch export on.
+ </description>
+ </method>
<method name="get_preset_name" qualifiers="const">
<return type="String" />
<description>
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f248d03140..665255b9b2 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -1007,9 +1007,17 @@ void EditorNode::_fs_changed() {
export_preset->update_value_overrides();
if (export_defer.pack_only) { // Only export .pck or .zip data pack.
if (export_path.ends_with(".zip")) {
- err = platform->export_zip(export_preset, export_defer.debug, export_path);
+ if (export_defer.patch) {
+ err = platform->export_zip_patch(export_preset, export_defer.debug, export_path, export_defer.patches);
+ } else {
+ err = platform->export_zip(export_preset, export_defer.debug, export_path);
+ }
} else if (export_path.ends_with(".pck")) {
- err = platform->export_pack(export_preset, export_defer.debug, export_path);
+ if (export_defer.patch) {
+ err = platform->export_pack_patch(export_preset, export_defer.debug, export_path, export_defer.patches);
+ } else {
+ err = platform->export_pack(export_preset, export_defer.debug, export_path);
+ }
} else {
ERR_PRINT(vformat("Export path \"%s\" doesn't end with a supported extension.", export_path));
err = FAILED;
@@ -5149,12 +5157,14 @@ void EditorNode::_begin_first_scan() {
requested_first_scan = true;
}
-Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template) {
+Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template, bool p_patch, const Vector<String> &p_patches) {
export_defer.preset = p_preset;
export_defer.path = p_path;
export_defer.debug = p_debug;
export_defer.pack_only = p_pack_only;
export_defer.android_build_template = p_android_build_template;
+ export_defer.patch = p_patch;
+ export_defer.patches = p_patches;
cmdline_export_mode = true;
return OK;
}
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 7ef38b4edb..36332e3d78 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -246,6 +246,8 @@ private:
bool debug = false;
bool pack_only = false;
bool android_build_template = false;
+ bool patch = false;
+ Vector<String> patches;
} export_defer;
static EditorNode *singleton;
@@ -880,7 +882,7 @@ public:
void _copy_warning(const String &p_str);
- Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template);
+ Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template, bool p_patch, const Vector<String> &p_patches);
bool is_project_exporting() const;
Control *get_gui_base() { return gui_base; }
diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp
index 975a601ae1..6ca83c5e25 100644
--- a/editor/export/editor_export.cpp
+++ b/editor/export/editor_export.cpp
@@ -83,6 +83,8 @@ void EditorExport::_save() {
config->set_value(section, "include_filter", preset->get_include_filter());
config->set_value(section, "exclude_filter", preset->get_exclude_filter());
config->set_value(section, "export_path", preset->get_export_path());
+ config->set_value(section, "patches", 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());
@@ -303,6 +305,7 @@ void EditorExport::load_config() {
preset->set_exclude_filter(config->get_value(section, "exclude_filter"));
preset->set_export_path(config->get_value(section, "export_path", ""));
preset->set_script_export_mode(config->get_value(section, "script_export_mode", EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED));
+ preset->set_patches(config->get_value(section, "patches", Vector<String>()));
if (config->has_section_key(section, "encrypt_pck")) {
preset->set_enc_pck(config->get_value(section, "encrypt_pck"));
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 983f4ee36a..58737c53ed 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -167,6 +167,44 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err)
return has_messages;
}
+bool EditorExportPlatform::_check_hash(const uint8_t *p_hash, const Vector<uint8_t> &p_data) {
+ if (p_hash == nullptr) {
+ return false;
+ }
+
+ unsigned char hash[16];
+ Error err = CryptoCore::md5(p_data.ptr(), p_data.size(), hash);
+ if (err != OK) {
+ return false;
+ }
+
+ for (int i = 0; i < 16; i++) {
+ if (p_hash[i] != hash[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+Error EditorExportPlatform::_load_patches(const Vector<String> &p_patches) {
+ Error err = OK;
+ if (!p_patches.is_empty()) {
+ for (const String &path : p_patches) {
+ err = PackedData::get_singleton()->add_pack(path, true, 0);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Patch Creation"), vformat(TTR("Could not load patch pack with path \"%s\"."), path));
+ return err;
+ }
+ }
+ }
+ return err;
+}
+
+void EditorExportPlatform::_unload_patches() {
+ PackedData::get_singleton()->clear();
+}
+
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) {
ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export.");
@@ -237,6 +275,14 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa
return OK;
}
+Error EditorExportPlatform::_save_pack_patch_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) {
+ if (_check_hash(PackedData::get_singleton()->get_file_hash(p_path), p_data)) {
+ return OK;
+ }
+
+ return _save_pack_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key);
+}
+
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) {
ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export.");
@@ -260,6 +306,8 @@ Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_pat
zipWriteInFileInZip(zip, p_data.ptr(), p_data.size());
zipCloseFileInZip(zip);
+ zd->file_count += 1;
+
if (zd->ep->step(TTR("Storing File:") + " " + p_path, 2 + p_file * 100 / p_total, false)) {
return ERR_SKIP;
}
@@ -267,6 +315,14 @@ Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_pat
return OK;
}
+Error EditorExportPlatform::_save_zip_patch_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) {
+ if (_check_hash(PackedData::get_singleton()->get_file_hash(p_path), p_data)) {
+ return OK;
+ }
+
+ return _save_zip_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key);
+}
+
Ref<ImageTexture> EditorExportPlatform::get_option_icon(int p_index) const {
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
ERR_FAIL_COND_V(theme.is_null(), Ref<ImageTexture>());
@@ -1561,7 +1617,7 @@ Dictionary EditorExportPlatform::_save_pack(const Ref<EditorExportPreset> &p_pre
Vector<SharedObject> so_files;
int64_t embedded_start = 0;
int64_t embedded_size = 0;
- Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, p_embed, &embedded_start, &embedded_size);
+ Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, nullptr, p_embed, &embedded_start, &embedded_size);
Dictionary ret;
ret["result"] = err_code;
@@ -1605,9 +1661,55 @@ Dictionary EditorExportPlatform::_save_zip(const Ref<EditorExportPreset> &p_pres
return ret;
}
-Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
+Dictionary EditorExportPlatform::_save_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+ Vector<SharedObject> so_files;
+ Error err_code = save_pack_patch(p_preset, p_debug, p_path, &so_files);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ }
+
+ return ret;
+}
+
+Dictionary EditorExportPlatform::_save_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+ Vector<SharedObject> so_files;
+ Error err_code = save_zip_patch(p_preset, p_debug, p_path, &so_files);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ }
+
+ return ret;
+}
+
+Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, EditorExportSaveFunction p_save_func, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
EditorProgress ep("savepack", TTR("Packing"), 102, true);
+ if (p_save_func == nullptr) {
+ p_save_func = _save_pack_file;
+ }
+
// Create the temporary export directory if it doesn't exist.
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
da->make_dir_recursive(EditorPaths::get_singleton()->get_cache_dir());
@@ -1624,7 +1726,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
pd.f = ftmp;
pd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _pack_add_shared_object);
+ Error err = export_project_files(p_preset, p_debug, p_save_func, &pd, _pack_add_shared_object);
// Close temp file.
pd.f.unref();
@@ -1636,6 +1738,12 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
return err;
}
+ if (pd.file_ofs.is_empty()) {
+ DirAccess::remove_file_or_error(tmppath);
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("No files or changes to export."));
+ return FAILED;
+ }
+
pd.file_ofs.sort(); //do sort, so we can do binary search later
Ref<FileAccess> f;
@@ -1831,28 +1939,56 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
return OK;
}
-Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) {
+Error EditorExportPlatform::save_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
+ return save_pack(p_preset, p_debug, p_path, p_so_files, _save_pack_patch_file, p_embed, r_embedded_start, r_embedded_size);
+}
+
+Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, EditorExportSaveFunction p_save_func) {
EditorProgress ep("savezip", TTR("Packing"), 102, true);
+ if (p_save_func == nullptr) {
+ p_save_func = _save_zip_file;
+ }
+
+ String tmppath = EditorPaths::get_singleton()->get_cache_dir().path_join("packtmp");
+
Ref<FileAccess> io_fa;
zlib_filefunc_def io = zipio_create_io(&io_fa);
- zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io);
+ zipFile zip = zipOpen2(tmppath.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io);
ZipData zd;
zd.ep = &ep;
zd.zip = zip;
zd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd, _zip_add_shared_object);
+ Error err = export_project_files(p_preset, p_debug, p_save_func, &zd, _zip_add_shared_object);
if (err != OK && err != ERR_SKIP) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files."));
}
zipClose(zip, nullptr);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ if (zd.file_count == 0) {
+ da->remove(tmppath);
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("No files or changes to export."));
+ return FAILED;
+ }
+
+ err = da->rename(tmppath, p_path);
+ if (err != OK) {
+ da->remove(tmppath);
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), vformat(TTR("Failed to move temporary file \"%s\" to \"%s\"."), tmppath, p_path));
+ }
+
return OK;
}
+Error EditorExportPlatform::save_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) {
+ return save_zip(p_preset, p_debug, p_path, p_so_files, _save_zip_patch_file);
+}
+
Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
return save_pack(p_preset, p_debug, p_path);
@@ -1863,6 +1999,28 @@ Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset,
return save_zip(p_preset, p_debug, p_path);
}
+Error EditorExportPlatform::export_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+ Error err = _load_patches(p_patches.is_empty() ? p_preset->get_patches() : p_patches);
+ if (err != OK) {
+ return err;
+ }
+ err = save_pack_patch(p_preset, p_debug, p_path);
+ _unload_patches();
+ return err;
+}
+
+Error EditorExportPlatform::export_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+ Error err = _load_patches(p_patches.is_empty() ? p_preset->get_patches() : p_patches);
+ if (err != OK) {
+ return err;
+ }
+ err = save_zip_patch(p_preset, p_debug, p_path);
+ _unload_patches();
+ return err;
+}
+
Vector<String> EditorExportPlatform::gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags) {
Vector<String> ret;
String host = EDITOR_GET("network/debug/remote_host");
@@ -2115,6 +2273,8 @@ void EditorExportPlatform::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_pack", "preset", "debug", "path", "embed"), &EditorExportPlatform::_save_pack, DEFVAL(false));
ClassDB::bind_method(D_METHOD("save_zip", "preset", "debug", "path"), &EditorExportPlatform::_save_zip);
+ ClassDB::bind_method(D_METHOD("save_pack_patch", "preset", "debug", "path"), &EditorExportPlatform::_save_pack_patch);
+ ClassDB::bind_method(D_METHOD("save_zip_patch", "preset", "debug", "path"), &EditorExportPlatform::_save_zip_patch);
ClassDB::bind_method(D_METHOD("gen_export_flags", "flags"), &EditorExportPlatform::gen_export_flags);
@@ -2123,6 +2283,8 @@ void EditorExportPlatform::_bind_methods() {
ClassDB::bind_method(D_METHOD("export_project", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_project, DEFVAL(0));
ClassDB::bind_method(D_METHOD("export_pack", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_pack, DEFVAL(0));
ClassDB::bind_method(D_METHOD("export_zip", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_zip, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_pack_patch", "preset", "debug", "path", "patches", "flags"), &EditorExportPlatform::export_pack_patch, DEFVAL(PackedStringArray()), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_zip_patch", "preset", "debug", "path", "patches", "flags"), &EditorExportPlatform::export_zip_patch, DEFVAL(PackedStringArray()), DEFVAL(0));
ClassDB::bind_method(D_METHOD("clear_messages"), &EditorExportPlatform::clear_messages);
ClassDB::bind_method(D_METHOD("add_message", "type", "category", "message"), &EditorExportPlatform::add_message);
diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h
index 95f21ceafb..ef3274c5e4 100644
--- a/editor/export/editor_export_platform.h
+++ b/editor/export/editor_export_platform.h
@@ -101,6 +101,7 @@ private:
void *zip = nullptr;
EditorProgress *ep = nullptr;
Vector<SharedObject> *so_files = nullptr;
+ int file_count = 0;
};
Vector<ExportMessage> messages;
@@ -109,10 +110,14 @@ private:
void _export_find_customized_resources(const Ref<EditorExportPreset> &p_preset, EditorFileSystemDirectory *p_dir, EditorExportPreset::FileExportMode p_mode, HashSet<String> &p_paths);
void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths);
+ static bool _check_hash(const uint8_t *p_hash, const Vector<uint8_t> &p_data);
+
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_pack_patch_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 _pack_add_shared_object(void *p_userdata, const SharedObject &p_so);
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);
+ static Error _save_zip_patch_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 _zip_add_shared_object(void *p_userdata, const SharedObject &p_so);
struct ScriptCallbackData {
@@ -188,6 +193,9 @@ protected:
Error ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid = nullptr, int p_port_fwd = -1) const;
Error ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const;
+ Error _load_patches(const Vector<String> &p_patches);
+ void _unload_patches();
+
public:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const = 0;
@@ -284,8 +292,14 @@ public:
Dictionary _save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed = false);
Dictionary _save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
- Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
- Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr);
+ Dictionary _save_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+ Dictionary _save_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+
+ Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, EditorExportSaveFunction p_save_func = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
+ Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, EditorExportSaveFunction p_save_func = nullptr);
+
+ Error save_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
+ Error save_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr);
virtual bool poll_export() { return false; }
virtual int get_options_count() const { return 0; }
@@ -307,6 +321,8 @@ public:
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) = 0;
virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
+ virtual Error export_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches = Vector<String>(), BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
+ virtual Error export_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches = Vector<String>(), BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
virtual void get_platform_features(List<String> *r_features) const = 0;
virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) {}
virtual String get_debug_protocol() const { return "tcp://"; }
diff --git a/editor/export/editor_export_platform_extension.cpp b/editor/export/editor_export_platform_extension.cpp
index 808a2076e2..5b75ff19a4 100644
--- a/editor/export/editor_export_platform_extension.cpp
+++ b/editor/export/editor_export_platform_extension.cpp
@@ -71,6 +71,8 @@ void EditorExportPlatformExtension::_bind_methods() {
GDVIRTUAL_BIND(_export_project, "preset", "debug", "path", "flags");
GDVIRTUAL_BIND(_export_pack, "preset", "debug", "path", "flags");
GDVIRTUAL_BIND(_export_zip, "preset", "debug", "path", "flags");
+ GDVIRTUAL_BIND(_export_pack_patch, "preset", "debug", "path", "patches", "flags");
+ GDVIRTUAL_BIND(_export_zip_patch, "preset", "debug", "path", "patches", "flags");
GDVIRTUAL_BIND(_get_platform_features);
@@ -291,6 +293,44 @@ Error EditorExportPlatformExtension::export_zip(const Ref<EditorExportPreset> &p
return save_zip(p_preset, p_debug, p_path);
}
+Error EditorExportPlatformExtension::export_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error err = _load_patches(p_patches.is_empty() ? p_preset->get_patches() : p_patches);
+ if (err != OK) {
+ return err;
+ }
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_pack_patch, p_preset, p_debug, p_path, p_patches, p_flags, ret)) {
+ _unload_patches();
+ return ret;
+ }
+
+ err = save_pack_patch(p_preset, p_debug, p_path);
+ _unload_patches();
+ return err;
+}
+
+Error EditorExportPlatformExtension::export_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error err = _load_patches(p_patches.is_empty() ? p_preset->get_patches() : p_patches);
+ if (err != OK) {
+ return err;
+ }
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_zip_patch, p_preset, p_debug, p_path, p_patches, p_flags, ret)) {
+ _unload_patches();
+ return ret;
+ }
+
+ err = save_zip_patch(p_preset, p_debug, p_path);
+ _unload_patches();
+ return err;
+}
+
void EditorExportPlatformExtension::get_platform_features(List<String> *r_features) const {
Vector<String> ret;
if (GDVIRTUAL_REQUIRED_CALL(_get_platform_features, ret) && r_features) {
diff --git a/editor/export/editor_export_platform_extension.h b/editor/export/editor_export_platform_extension.h
index 6391e65ac1..d1db922698 100644
--- a/editor/export/editor_export_platform_extension.h
+++ b/editor/export/editor_export_platform_extension.h
@@ -136,6 +136,12 @@ public:
virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
GDVIRTUAL4R(Error, _export_zip, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+ virtual Error export_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches = Vector<String>(), BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL5R(Error, _export_pack_patch, Ref<EditorExportPreset>, bool, const String &, const Vector<String> &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Error export_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches = Vector<String>(), BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL5R(Error, _export_zip_patch, Ref<EditorExportPreset>, bool, const String &, const Vector<String> &, BitField<EditorExportPlatform::DebugFlags>);
+
virtual void get_platform_features(List<String> *r_features) const override;
GDVIRTUAL0RC(Vector<String>, _get_platform_features);
diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp
index 24d89b7f34..31a5b60d77 100644
--- a/editor/export/editor_export_platform_pc.cpp
+++ b/editor/export/editor_export_platform_pc.cpp
@@ -194,7 +194,7 @@ Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset>
int64_t embedded_pos;
int64_t embedded_size;
- Error err = save_pack(p_preset, p_debug, pck_path, &so_files, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size);
+ Error err = save_pack(p_preset, p_debug, pck_path, &so_files, nullptr, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size);
if (err == OK && p_preset->get("binary_format/embed_pck")) {
if (embedded_size >= 0x100000000 && String(p_preset->get("binary_format/architecture")).contains("32")) {
add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB."));
diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp
index 9f805666d0..1ca72348e2 100644
--- a/editor/export/editor_export_preset.cpp
+++ b/editor/export/editor_export_preset.cpp
@@ -79,6 +79,7 @@ void EditorExportPreset::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_include_filter"), &EditorExportPreset::get_include_filter);
ClassDB::bind_method(D_METHOD("get_exclude_filter"), &EditorExportPreset::get_exclude_filter);
ClassDB::bind_method(D_METHOD("get_custom_features"), &EditorExportPreset::get_custom_features);
+ ClassDB::bind_method(D_METHOD("get_patches"), &EditorExportPreset::get_patches);
ClassDB::bind_method(D_METHOD("get_export_path"), &EditorExportPreset::get_export_path);
ClassDB::bind_method(D_METHOD("get_encryption_in_filter"), &EditorExportPreset::get_enc_in_filter);
ClassDB::bind_method(D_METHOD("get_encryption_ex_filter"), &EditorExportPreset::get_enc_ex_filter);
@@ -366,6 +367,42 @@ EditorExportPreset::FileExportMode EditorExportPreset::get_file_export_mode(cons
return p_default;
}
+void EditorExportPreset::add_patch(const String &p_path, int p_at_pos) {
+ ERR_FAIL_COND_EDMSG(patches.has(p_path), vformat("Failed to add patch \"%s\". Patches must be unique.", p_path));
+
+ if (p_at_pos < 0) {
+ patches.push_back(p_path);
+ } else {
+ patches.insert(p_at_pos, p_path);
+ }
+
+ EditorExport::singleton->save_presets();
+}
+
+void EditorExportPreset::set_patch(int p_index, const String &p_path) {
+ remove_patch(p_index);
+ add_patch(p_path, p_index);
+}
+
+String EditorExportPreset::get_patch(int p_index) {
+ ERR_FAIL_INDEX_V(p_index, patches.size(), String());
+ return patches[p_index];
+}
+
+void EditorExportPreset::remove_patch(int p_index) {
+ ERR_FAIL_INDEX(p_index, patches.size());
+ patches.remove_at(p_index);
+ EditorExport::singleton->save_presets();
+}
+
+void EditorExportPreset::set_patches(const Vector<String> &p_patches) {
+ patches = p_patches;
+}
+
+Vector<String> EditorExportPreset::get_patches() const {
+ return patches;
+}
+
void EditorExportPreset::set_custom_features(const String &p_custom_features) {
custom_features = p_custom_features;
EditorExport::singleton->save_presets();
diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h
index f220477461..af3a23fc50 100644
--- a/editor/export/editor_export_preset.h
+++ b/editor/export/editor_export_preset.h
@@ -74,6 +74,8 @@ private:
bool advanced_options_enabled = false;
bool dedicated_server = false;
+ Vector<String> patches;
+
friend class EditorExport;
friend class EditorExportPlatform;
@@ -144,6 +146,13 @@ public:
void set_exclude_filter(const String &p_exclude);
String get_exclude_filter() const;
+ void add_patch(const String &p_path, int p_at_pos = -1);
+ void set_patch(int p_index, const String &p_path);
+ String get_patch(int p_index);
+ void remove_patch(int p_index);
+ void set_patches(const Vector<String> &p_patches);
+ Vector<String> get_patches() const;
+
void set_custom_features(const String &p_custom_features);
String get_custom_features() const;
diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp
index be9e0f78ec..f9137082d7 100644
--- a/editor/export/project_export.cpp
+++ b/editor/export/project_export.cpp
@@ -102,11 +102,13 @@ void ProjectExportDialog::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
duplicate_preset->set_icon(presets->get_editor_theme_icon(SNAME("Duplicate")));
delete_preset->set_icon(presets->get_editor_theme_icon(SNAME("Remove")));
+ patch_add_btn->set_icon(get_editor_theme_icon(SNAME("Add")));
} break;
case NOTIFICATION_READY: {
duplicate_preset->set_icon(presets->get_editor_theme_icon(SNAME("Duplicate")));
delete_preset->set_icon(presets->get_editor_theme_icon(SNAME("Remove")));
+ patch_add_btn->set_icon(get_editor_theme_icon(SNAME("Add")));
connect(SceneStringName(confirmed), callable_mp(this, &ProjectExportDialog::_export_pck_zip));
_update_export_all();
} break;
@@ -248,6 +250,7 @@ void ProjectExportDialog::_edit_preset(int p_index) {
duplicate_preset->set_disabled(true);
delete_preset->set_disabled(true);
sections->hide();
+ patches->clear();
export_error->hide();
export_templates_error->hide();
return;
@@ -292,6 +295,21 @@ void ProjectExportDialog::_edit_preset(int p_index) {
exclude_filters->set_text(current->get_exclude_filter());
server_strip_message->set_visible(current->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED);
+ patches->clear();
+ TreeItem *patch_root = patches->create_item();
+ Vector<String> patch_list = current->get_patches();
+ for (int i = 0; i < patch_list.size(); i++) {
+ TreeItem *patch = patches->create_item(patch_root);
+ const String &patch_path = patch_list[i];
+ patch->set_cell_mode(0, TreeItem::CELL_MODE_STRING);
+ patch->set_editable(0, true);
+ patch->set_text(0, patch_path.get_file());
+ patch->set_tooltip_text(0, patch_path);
+ patch->set_metadata(0, i);
+ patch->add_button(0, get_editor_theme_icon(SNAME("Remove")), 0);
+ patch->add_button(0, get_editor_theme_icon(SNAME("FileBrowse")), 1);
+ }
+
_fill_resource_tree();
bool needs_templates;
@@ -664,6 +682,7 @@ void ProjectExportDialog::_duplicate_preset() {
preset->set_export_filter(current->get_export_filter());
preset->set_include_filter(current->get_include_filter());
preset->set_exclude_filter(current->get_exclude_filter());
+ preset->set_patches(current->get_patches());
preset->set_custom_features(current->get_custom_features());
for (const KeyValue<StringName, Variant> &E : current->get_values()) {
@@ -720,8 +739,22 @@ Variant ProjectExportDialog::get_drag_data_fw(const Point2 &p_point, Control *p_
return d;
}
- }
+ } else if (p_from == patches) {
+ TreeItem *item = patches->get_item_at_position(p_point);
+
+ if (item) {
+ int item_metadata = item->get_metadata(0);
+ Dictionary d;
+ d["type"] = "export_patch";
+ d["patch"] = item_metadata;
+ Label *label = memnew(Label);
+ label->set_text(item->get_text(0));
+ patches->set_drag_preview(label);
+
+ return d;
+ }
+ }
return Variant();
}
@@ -735,6 +768,18 @@ bool ProjectExportDialog::can_drop_data_fw(const Point2 &p_point, const Variant
if (presets->get_item_at_position(p_point, true) < 0 && !presets->is_pos_at_end_of_items(p_point)) {
return false;
}
+ } else if (p_from == patches) {
+ Dictionary d = p_data;
+ if (d.get("type", "") != "export_patch") {
+ return false;
+ }
+
+ TreeItem *item = patches->get_item_at_position(p_point);
+ if (!item) {
+ return false;
+ }
+
+ patches->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
}
return true;
@@ -771,6 +816,31 @@ void ProjectExportDialog::drop_data_fw(const Point2 &p_point, const Variant &p_d
} else {
_edit_preset(presets->get_item_count() - 1);
}
+ } else if (p_from == patches) {
+ Dictionary d = p_data;
+ int from_pos = d["patch"];
+
+ TreeItem *item = patches->get_item_at_position(p_point);
+ if (!item) {
+ return;
+ }
+
+ int to_pos = item->get_metadata(0);
+
+ if (patches->get_drop_section_at_position(p_point) > 0) {
+ to_pos++;
+ }
+
+ if (to_pos > from_pos) {
+ to_pos--;
+ }
+
+ Ref<EditorExportPreset> preset = get_current_preset();
+ String patch = preset->get_patch(from_pos);
+ preset->remove_patch(from_pos);
+ preset->add_patch(patch, to_pos);
+
+ _update_current_preset();
}
}
@@ -1026,6 +1096,75 @@ void ProjectExportDialog::_set_file_export_mode(int p_id) {
_propagate_file_export_mode(include_files->get_root(), EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED);
}
+void ProjectExportDialog::_patch_tree_button_clicked(Object *p_item, int p_column, int p_id, int p_mouse_button_index) {
+ TreeItem *ti = Object::cast_to<TreeItem>(p_item);
+
+ patch_index = ti->get_metadata(0);
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ if (p_id == 0) {
+ Vector<String> preset_patches = current->get_patches();
+ ERR_FAIL_INDEX(patch_index, preset_patches.size());
+ patch_erase->set_text(vformat(TTR("Delete patch '%s' from list?"), preset_patches[patch_index].get_file()));
+ patch_erase->popup_centered();
+ } else {
+ patch_dialog->popup_file_dialog();
+ }
+}
+
+void ProjectExportDialog::_patch_tree_item_edited() {
+ TreeItem *item = patches->get_edited();
+ if (!item) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ int index = item->get_metadata(0);
+ String patch_path = item->get_text(0);
+
+ current->set_patch(index, patch_path);
+ item->set_tooltip_text(0, patch_path);
+}
+
+void ProjectExportDialog::_patch_file_selected(const String &p_path) {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ String relative_path = ProjectSettings::get_singleton()->get_resource_path().path_to_file(p_path);
+
+ Vector<String> preset_patches = current->get_patches();
+ if (patch_index >= preset_patches.size()) {
+ current->add_patch(relative_path);
+ } else {
+ current->set_patch(patch_index, relative_path);
+ }
+
+ _update_current_preset();
+}
+
+void ProjectExportDialog::_patch_delete_confirmed() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ Vector<String> preset_patches = current->get_patches();
+ if (patch_index < preset_patches.size()) {
+ current->remove_patch(patch_index);
+ _update_current_preset();
+ }
+}
+
+void ProjectExportDialog::_patch_add_pack_pressed() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ patch_index = current->get_patches().size();
+ patch_dialog->popup_file_dialog();
+}
+
void ProjectExportDialog::_export_pck_zip() {
Ref<EditorExportPreset> current = get_current_preset();
ERR_FAIL_COND(current.is_null());
@@ -1044,11 +1183,20 @@ void ProjectExportDialog::_export_pck_zip_selected(const String &p_path) {
const Dictionary &fd_option = export_pck_zip->get_selected_options();
bool export_debug = fd_option.get(TTR("Export With Debug"), true);
+ bool export_as_patch = fd_option.get(TTR("Export As Patch"), true);
if (p_path.ends_with(".zip")) {
- platform->export_zip(current, export_debug, p_path);
+ if (export_as_patch) {
+ platform->export_zip_patch(current, export_debug, p_path);
+ } else {
+ platform->export_zip(current, export_debug, p_path);
+ }
} else if (p_path.ends_with(".pck")) {
- platform->export_pack(current, export_debug, p_path);
+ if (export_as_patch) {
+ platform->export_pack_patch(current, export_debug, p_path);
+ } else {
+ platform->export_pack(current, export_debug, p_path);
+ }
} else {
ERR_FAIL_MSG("Path must end with .pck or .zip");
}
@@ -1386,6 +1534,40 @@ ProjectExportDialog::ProjectExportDialog() {
exclude_filters);
exclude_filters->connect(SceneStringName(text_changed), callable_mp(this, &ProjectExportDialog::_filter_changed));
+ // Patch packages.
+
+ VBoxContainer *patch_vb = memnew(VBoxContainer);
+ sections->add_child(patch_vb);
+ patch_vb->set_name(TTR("Patches"));
+
+ patches = memnew(Tree);
+ patches->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ patches->set_hide_root(true);
+ patches->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
+ patches->connect("button_clicked", callable_mp(this, &ProjectExportDialog::_patch_tree_button_clicked));
+ patches->connect("item_edited", callable_mp(this, &ProjectExportDialog::_patch_tree_item_edited));
+ SET_DRAG_FORWARDING_GCD(patches, ProjectExportDialog);
+ patches->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
+ patch_vb->add_margin_child(TTR("Base Packs:"), patches, true);
+
+ patch_dialog = memnew(EditorFileDialog);
+ patch_dialog->add_filter("*.pck", TTR("Godot Project Pack"));
+ patch_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
+ patch_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ patch_dialog->connect("file_selected", callable_mp(this, &ProjectExportDialog::_patch_file_selected));
+ add_child(patch_dialog);
+
+ patch_erase = memnew(ConfirmationDialog);
+ patch_erase->set_ok_button_text(TTR("Delete"));
+ patch_erase->connect(SceneStringName(confirmed), callable_mp(this, &ProjectExportDialog::_patch_delete_confirmed));
+ add_child(patch_erase);
+
+ patch_add_btn = memnew(Button);
+ patch_add_btn->set_text(TTR("Add Pack"));
+ patch_add_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ patch_add_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectExportDialog::_patch_add_pack_pressed));
+ patch_vb->add_child(patch_add_btn);
+
// Feature tags.
VBoxContainer *feature_vb = memnew(VBoxContainer);
@@ -1569,6 +1751,7 @@ ProjectExportDialog::ProjectExportDialog() {
export_project->add_option(TTR("Export With Debug"), Vector<String>(), true);
export_pck_zip->add_option(TTR("Export With Debug"), Vector<String>(), true);
+ export_pck_zip->add_option(TTR("Export As Patch"), Vector<String>(), true);
set_hide_on_ok(false);
diff --git a/editor/export/project_export.h b/editor/export/project_export.h
index c3499177f3..e360596be6 100644
--- a/editor/export/project_export.h
+++ b/editor/export/project_export.h
@@ -105,6 +105,13 @@ class ProjectExportDialog : public ConfirmationDialog {
AcceptDialog *export_all_dialog = nullptr;
RBSet<String> feature_set;
+
+ Tree *patches = nullptr;
+ int patch_index = -1;
+ EditorFileDialog *patch_dialog = nullptr;
+ ConfirmationDialog *patch_erase = nullptr;
+ Button *patch_add_btn = nullptr;
+
LineEdit *custom_features = nullptr;
RichTextLabel *custom_feature_display = nullptr;
@@ -148,6 +155,12 @@ class ProjectExportDialog : public ConfirmationDialog {
void _tree_popup_edited(bool p_arrow_clicked);
void _set_file_export_mode(int p_id);
+ void _patch_tree_button_clicked(Object *p_item, int p_column, int p_id, int p_mouse_button_index);
+ void _patch_tree_item_edited();
+ void _patch_file_selected(const String &p_path);
+ void _patch_delete_confirmed();
+ void _patch_add_pack_pressed();
+
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
diff --git a/main/main.cpp b/main/main.cpp
index 75797e31de..792ecf2f8f 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -659,6 +659,8 @@ void Main::print_help(const char *p_binary) {
print_help_option("", "The target directory must exist.\n");
print_help_option("--export-debug <preset> <path>", "Export the project in debug mode using the given preset and output path. See --export-release description for other considerations.\n", CLI_OPTION_AVAILABILITY_EDITOR);
print_help_option("--export-pack <preset> <path>", "Export the project data only using the given preset and output path. The <path> extension determines whether it will be in PCK or ZIP format.\n", CLI_OPTION_AVAILABILITY_EDITOR);
+ print_help_option("--export-patch <preset> <path>", "Export pack with changed files only. See --export-pack description for other considerations.\n", CLI_OPTION_AVAILABILITY_EDITOR);
+ print_help_option("--patches <paths>", "List of patches to use with --export-patch. The list is comma-separated.\n", CLI_OPTION_AVAILABILITY_EDITOR);
print_help_option("--install-android-build-template", "Install the Android build template. Used in conjunction with --export-release or --export-debug.\n", CLI_OPTION_AVAILABILITY_EDITOR);
#ifndef DISABLE_DEPRECATED
// Commands are long; split the description to a second line.
@@ -1478,12 +1480,23 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
wait_for_import = true;
quit_after = 1;
} else if (arg == "--export-release" || arg == "--export-debug" ||
- arg == "--export-pack") { // Export project
+ arg == "--export-pack" || arg == "--export-patch") { // Export project
// Actually handling is done in start().
editor = true;
cmdline_tool = true;
wait_for_import = true;
main_args.push_back(arg);
+ } else if (arg == "--patches") {
+ if (N) {
+ // Actually handling is done in start().
+ main_args.push_back(arg);
+ main_args.push_back(N->get());
+
+ N = N->next();
+ } else {
+ OS::get_singleton()->print("Missing comma-separated list of patches after --patches, aborting.\n");
+ goto error;
+ }
#ifndef DISABLE_DEPRECATED
} else if (arg == "--export") { // For users used to 3.x syntax.
OS::get_singleton()->print("The Godot 3 --export option was changed to more explicit --export-release / --export-debug / --export-pack options.\nSee the --help output for details.\n");
@@ -3489,9 +3502,11 @@ int Main::start() {
bool doc_tool_implicit_cwd = false;
BitField<DocTools::GenerateFlags> gen_flags;
String _export_preset;
+ Vector<String> patches;
bool export_debug = false;
bool export_pack_only = false;
bool install_android_build_template = false;
+ bool export_patch = false;
#ifdef MODULE_GDSCRIPT_ENABLED
String gdscript_docs_path;
#endif
@@ -3581,6 +3596,14 @@ int Main::start() {
editor = true;
_export_preset = E->next()->get();
export_pack_only = true;
+ } else if (E->get() == "--export-patch") {
+ ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting.");
+ editor = true;
+ _export_preset = E->next()->get();
+ export_pack_only = true;
+ export_patch = true;
+ } else if (E->get() == "--patches") {
+ patches = E->next()->get().split(",", false);
#endif
} else {
// The parameter does not match anything known, don't skip the next argument
@@ -3984,7 +4007,7 @@ int Main::start() {
sml->get_root()->add_child(editor_node);
if (!_export_preset.is_empty()) {
- editor_node->export_preset(_export_preset, positional_arg, export_debug, export_pack_only, install_android_build_template);
+ editor_node->export_preset(_export_preset, positional_arg, export_debug, export_pack_only, install_android_build_template, export_patch, patches);
game_path = ""; // Do not load anything.
}