Skip to content

Fix ZIPPacker creating empty directory entries#120069

Merged
Repiteo merged 1 commit into
godotengine:masterfrom
TheOathMan:fix/zippacker-remove-empty-directory-entries
Jun 8, 2026
Merged

Fix ZIPPacker creating empty directory entries#120069
Repiteo merged 1 commit into
godotengine:masterfrom
TheOathMan:fix/zippacker-remove-empty-directory-entries

Conversation

@TheOathMan

@TheOathMan TheOathMan commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

What problem(s) does this PR solve?

Additional information

When calling start_file with a path that includes a subdirectory (e.g. "folder/file.txt"), the packer auto creates the parent directory entry. Due to a missing trailing /, the entry is stored as "folder" instead of "folder/", which ZIP tools interpret as a zero-byte file rather than a directory.

This results in the archive containing both a zero-byte file named "folder" and the intended file at "folder/file.txt". This is a duplicate name conflict that causes Windows native ZIP extraction to fail or skip entries, and breaks repacked binary formats like .aar files which are sensitive to duplicate or malformed entries.

Fix: Append "/" to the path passed to add_directory in start_file.


See issue #120068 for full details, test case, and root cause analysis.

Note: 4.7 specific regression. Directory and permission support was added in #115946

@TheOathMan TheOathMan requested a review from a team as a code owner June 7, 2026 20:04
@Ivorforce Ivorforce requested a review from bruvzg June 7, 2026 20:22

@bruvzg bruvzg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing records for directories is not required, but it is a normal behavior, so this is not a bug and no reason for this change.

Seems like Windows doesn't store directories, but most Unix tools do (probably because directories can have specific permissions set).

@TheOathMan

TheOathMan commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Storing records for directories is not required, but it is a normal behavior, so this is not a bug and no reason for this change.

Seems like Windows doesn't store directories, but most Unix tools do (probably because directories can have specific permissions set).

It shouldn't be the default behavior, or at the very least, there should be an option to disable it. Leaving it as is exposes two major failure areas: first, Windows users cannot unzip folders compressed by Godot, and second, it is impossible to repack a file to its original state.

@TheOathMan

Copy link
Copy Markdown
Contributor Author

repack a file to its original state is essential not only to my plugin but I imagine to so many Godot plugins out there, because they either rely on a user unzipping a file on windows or for my use case, repacking a file to its original state/format.

@bruvzg

bruvzg commented Jun 7, 2026

Copy link
Copy Markdown
Member

Windows users cannot unzip folders compressed by Godot, and second, it is impossible to repack a file to its original state.

What specific tool can't unzip it? Window should be be able to handle Unix created ZIP without issues.

If there's an issue with some tool it might be caused by names of stored directories (Unix zip seems to add / to the end of directory name, and Godot doesn't).

@TheOathMan

Copy link
Copy Markdown
Contributor Author

What specific tool can't unzip it? Window should be be able to handle Unix created ZIP without issues.

right click -> Extract all...

@bruvzg

bruvzg commented Jun 7, 2026

Copy link
Copy Markdown
Member

I'll check it tomorrow.

@bruvzg

bruvzg commented Jun 8, 2026

Copy link
Copy Markdown
Member

If there's an issue with some tool it might be caused by names of stored directories (Unix zip seems to add / to the end of directory name, and Godot doesn't).

I can reproduce the issue on Windows, and it is fixed by adding / to the path.

diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp
index 8bc13ba7c9..35da37782d 100644
--- a/modules/zip/zip_packer.cpp
+++ b/modules/zip/zip_packer.cpp
@@ -70,7 +70,7 @@ int ZIPPacker::get_compression_level() const {
 Error ZIPPacker::start_file(const String &p_path, BitField<FileAccess::UnixPermissionFlags> p_permissions, uint64_t p_modified_time) {
 	ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use.");
 
-	if (!p_path.get_base_dir().is_empty() && !directories.has(p_path.get_base_dir())) {
+	if (!p_path.get_base_dir().is_empty() && !directories.has(p_path.get_base_dir() + "/")) {
 		add_directory(p_path.get_base_dir(), 0755, p_modified_time);
 	}
 
@@ -136,8 +136,10 @@ Error ZIPPacker::close_file() {
 }
 
 Error ZIPPacker::add_directory(const String &p_path, BitField<FileAccess::UnixPermissionFlags> p_permissions, uint64_t p_modified_time) {
+	String path = p_path.ends_with("/") ? p_path : p_path + "/";
+
 	ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use.");
-	ERR_FAIL_COND_V_MSG(directories.has(p_path), ERR_CANT_CREATE, vformat("Directory '%s' already exists.", p_path));
+	ERR_FAIL_COND_V_MSG(directories.has(path), ERR_CANT_CREATE, vformat("Directory '%s' already exists.", path));
 
 	uint64_t time = p_modified_time;
 	if (time == 0) {
@@ -168,7 +170,7 @@ Error ZIPPacker::add_directory(const String &p_path, BitField<FileAccess::UnixPe
 	zipfi.internal_fa = 0;
 
 	int err = zipOpenNewFileInZip4(zf,
-			p_path.utf8().get_data(),
+			path.utf8().get_data(),
 			&zipfi,
 			nullptr,
 			0,
@@ -190,7 +192,7 @@ Error ZIPPacker::add_directory(const String &p_path, BitField<FileAccess::UnixPe
 		return FAILED;
 	}
 
-	directories.insert(p_path);
+	directories.insert(path);
 	return OK;
 }

@TheOathMan

Copy link
Copy Markdown
Contributor Author
String path = p_path.ends_with("/") ? p_path : p_path + "/";

tried your fixed, also works. Thanks

@bruvzg bruvzg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please squash the commits, see The interactive rebase.

@bruvzg bruvzg added this to the 4.7 milestone Jun 8, 2026
@TheOathMan TheOathMan force-pushed the fix/zippacker-remove-empty-directory-entries branch from 270216f to 43b2eda Compare June 8, 2026 06:20
@TheOathMan

Copy link
Copy Markdown
Contributor Author

Please squash the commits, see The interactive rebase.

done.

@Repiteo Repiteo merged commit 5a49c75 into godotengine:master Jun 8, 2026
20 checks passed
@Repiteo

Repiteo commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Thanks! Congratulations on your first merged contribution! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ZIPPacker creates empty file entries for parent directories when adding nested files

5 participants