Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to export patch packs #97118

Merged
merged 1 commit into from
Sep 26, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add ability to export patch packs
Co-authored-by: Poq Xert <poqxert@poqxert.ru>
mihe and PoqXert committed Sep 25, 2024
commit d3be030ea6f3e295603ccf6cc9080a1d32051332
16 changes: 16 additions & 0 deletions core/io/file_access_pack.cpp
Original file line number Diff line number Diff line change
@@ -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() {
3 changes: 3 additions & 0 deletions core/io/file_access_pack.h
Original file line number Diff line number Diff line change
@@ -111,13 +111,16 @@ class PackedData {
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; }

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);

42 changes: 42 additions & 0 deletions doc/classes/EditorExportPlatform.xml
Original file line number Diff line number Diff line change
@@ -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" />
32 changes: 30 additions & 2 deletions doc/classes/EditorExportPlatformExtension.xml
Original file line number Diff line number Diff line change
@@ -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">
6 changes: 6 additions & 0 deletions doc/classes/EditorExportPreset.xml
Original file line number Diff line number Diff line change
@@ -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>
16 changes: 13 additions & 3 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
@@ -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;
@@ -5147,12 +5155,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;
}
4 changes: 3 additions & 1 deletion editor/editor_node.h
Original file line number Diff line number Diff line change
@@ -246,6 +246,8 @@ class EditorNode : public Node {
bool debug = false;
bool pack_only = false;
bool android_build_template = false;
bool patch = false;
Vector<String> patches;
} export_defer;

static EditorNode *singleton;
@@ -879,7 +881,7 @@ class EditorNode : public Node {

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; }
3 changes: 3 additions & 0 deletions editor/export/editor_export.cpp
Original file line number Diff line number Diff line change
@@ -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"));
174 changes: 168 additions & 6 deletions editor/export/editor_export_platform.cpp
Original file line number Diff line number Diff line change
@@ -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,13 +306,23 @@ 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;
}

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);
20 changes: 18 additions & 2 deletions editor/export/editor_export_platform.h
Original file line number Diff line number Diff line change
@@ -101,6 +101,7 @@ class EditorExportPlatform : public RefCounted {
void *zip = nullptr;
EditorProgress *ep = nullptr;
Vector<SharedObject> *so_files = nullptr;
int file_count = 0;
};

Vector<ExportMessage> messages;
@@ -109,10 +110,14 @@ class EditorExportPlatform : public RefCounted {
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 @@ class EditorExportPlatform : public RefCounted {
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 @@ class EditorExportPlatform : public RefCounted {
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 @@ class EditorExportPlatform : public RefCounted {
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://"; }
40 changes: 40 additions & 0 deletions editor/export/editor_export_platform_extension.cpp
Original file line number Diff line number Diff line change
@@ -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) {
6 changes: 6 additions & 0 deletions editor/export/editor_export_platform_extension.h
Original file line number Diff line number Diff line change
@@ -136,6 +136,12 @@ class EditorExportPlatformExtension : public EditorExportPlatform {
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);

2 changes: 1 addition & 1 deletion editor/export/editor_export_platform_pc.cpp
Original file line number Diff line number Diff line change
@@ -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."));
37 changes: 37 additions & 0 deletions editor/export/editor_export_preset.cpp
Original file line number Diff line number Diff line change
@@ -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();
9 changes: 9 additions & 0 deletions editor/export/editor_export_preset.h
Original file line number Diff line number Diff line change
@@ -74,6 +74,8 @@ class EditorExportPreset : public RefCounted {
bool advanced_options_enabled = false;
bool dedicated_server = false;

Vector<String> patches;

friend class EditorExport;
friend class EditorExportPlatform;

@@ -144,6 +146,13 @@ class EditorExportPreset : public RefCounted {
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;

189 changes: 186 additions & 3 deletions editor/export/project_export.cpp
Original file line number Diff line number Diff line change
@@ -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);

13 changes: 13 additions & 0 deletions editor/export/project_export.h
Original file line number Diff line number Diff line change
@@ -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);
27 changes: 25 additions & 2 deletions main/main.cpp
Original file line number Diff line number Diff line change
@@ -650,6 +650,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.
@@ -1469,12 +1471,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");
@@ -3480,9 +3493,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
@@ -3572,6 +3587,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
@@ -3975,7 +3998,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.
}