Skip to content

Commit 2ac562c

Browse files
reduzmihe
andcommitted
Add ability for PCK patches to remove files
Co-authored-by: Mikael Hermansson <mikael@hermansson.io>
1 parent 0f5f3bc commit 2ac562c

9 files changed

+210
-56
lines changed

core/io/file_access_pack.cpp

+57-11
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t
4848
}
4949

5050
void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted) {
51-
String simplified_path = p_path.simplify_path();
51+
String simplified_path = p_path.simplify_path().trim_prefix("res://");
5252
PathMD5 pmd5(simplified_path.md5_buffer());
5353

5454
bool exists = files.has(pmd5);
@@ -68,13 +68,11 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
6868
}
6969

7070
if (!exists) {
71-
//search for dir
72-
String p = simplified_path.replace_first("res://", "");
71+
// Search for directory.
7372
PackedDir *cd = root;
7473

75-
if (p.contains("/")) { //in a subdir
76-
77-
Vector<String> ds = p.get_base_dir().split("/");
74+
if (simplified_path.contains("/")) { // In a subdirectory.
75+
Vector<String> ds = simplified_path.get_base_dir().split("/");
7876

7977
for (int j = 0; j < ds.size(); j++) {
8078
if (!cd->subdirs.has(ds[j])) {
@@ -89,29 +87,73 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
8987
}
9088
}
9189
String filename = simplified_path.get_file();
92-
// Don't add as a file if the path points to a directory
90+
// Don't add as a file if the path points to a directory.
9391
if (!filename.is_empty()) {
9492
cd->files.insert(filename);
9593
}
9694
}
9795
}
9896

97+
void PackedData::remove_path(const String &p_path) {
98+
String simplified_path = p_path.simplify_path().trim_prefix("res://");
99+
PathMD5 pmd5(simplified_path.md5_buffer());
100+
if (!files.has(pmd5)) {
101+
return;
102+
}
103+
104+
// Search for directory.
105+
PackedDir *cd = root;
106+
107+
if (simplified_path.contains("/")) { // In a subdirectory.
108+
Vector<String> ds = simplified_path.get_base_dir().split("/");
109+
110+
for (int j = 0; j < ds.size(); j++) {
111+
if (!cd->subdirs.has(ds[j])) {
112+
return; // Subdirectory does not exist, do not bother creating.
113+
} else {
114+
cd = cd->subdirs[ds[j]];
115+
}
116+
}
117+
}
118+
119+
cd->files.erase(simplified_path.get_file());
120+
121+
files.erase(pmd5);
122+
}
123+
99124
void PackedData::add_pack_source(PackSource *p_source) {
100125
if (p_source != nullptr) {
101126
sources.push_back(p_source);
102127
}
103128
}
104129

105130
uint8_t *PackedData::get_file_hash(const String &p_path) {
106-
PathMD5 pmd5(p_path.md5_buffer());
131+
String simplified_path = p_path.simplify_path().trim_prefix("res://");
132+
PathMD5 pmd5(simplified_path.md5_buffer());
107133
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
108-
if (!E || E->value.offset == 0) {
134+
if (!E) {
109135
return nullptr;
110136
}
111137

112138
return E->value.md5;
113139
}
114140

141+
HashSet<String> PackedData::get_file_paths() const {
142+
HashSet<String> file_paths;
143+
_get_file_paths(root, root->name, file_paths);
144+
return file_paths;
145+
}
146+
147+
void PackedData::_get_file_paths(PackedDir *p_dir, const String &p_parent_dir, HashSet<String> &r_paths) const {
148+
for (const String &E : p_dir->files) {
149+
r_paths.insert(p_parent_dir.path_join(E));
150+
}
151+
152+
for (const KeyValue<String, PackedDir *> &E : p_dir->subdirs) {
153+
_get_file_paths(E.value, p_parent_dir.path_join(E.key), r_paths);
154+
}
155+
}
156+
115157
void PackedData::clear() {
116158
files.clear();
117159
_free_packed_dirs(root);
@@ -269,13 +311,17 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
269311
String path;
270312
path.parse_utf8(cs.ptr());
271313

272-
uint64_t ofs = file_base + f->get_64();
314+
uint64_t ofs = f->get_64();
273315
uint64_t size = f->get_64();
274316
uint8_t md5[16];
275317
f->get_buffer(md5, 16);
276318
uint32_t flags = f->get_32();
277319

278-
PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED));
320+
if (flags & PACK_FILE_REMOVAL) { // The file was removed.
321+
PackedData::get_singleton()->remove_path(path);
322+
} else {
323+
PackedData::get_singleton()->add_path(p_path, path, file_base + ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED));
324+
}
279325
}
280326

281327
return true;

core/io/file_access_pack.h

+7-6
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ enum PackFlags {
4949
};
5050

5151
enum PackFileFlags {
52-
PACK_FILE_ENCRYPTED = 1 << 0
52+
PACK_FILE_ENCRYPTED = 1 << 0,
53+
PACK_FILE_REMOVAL = 1 << 1,
5354
};
5455

5556
class PackSource;
@@ -107,11 +108,14 @@ class PackedData {
107108
bool disabled = false;
108109

109110
void _free_packed_dirs(PackedDir *p_dir);
111+
void _get_file_paths(PackedDir *p_dir, const String &p_parent_dir, HashSet<String> &r_paths) const;
110112

111113
public:
112114
void add_pack_source(PackSource *p_source);
113115
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
116+
void remove_path(const String &p_path);
114117
uint8_t *get_file_hash(const String &p_path);
118+
HashSet<String> get_file_paths() const;
115119

116120
void set_disabled(bool p_disabled) { disabled = p_disabled; }
117121
_FORCE_INLINE_ bool is_disabled() const { return disabled; }
@@ -190,14 +194,11 @@ class FileAccessPack : public FileAccess {
190194
};
191195

192196
Ref<FileAccess> PackedData::try_open_path(const String &p_path) {
193-
String simplified_path = p_path.simplify_path();
197+
String simplified_path = p_path.simplify_path().trim_prefix("res://");
194198
PathMD5 pmd5(simplified_path.md5_buffer());
195199
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
196200
if (!E) {
197-
return nullptr; //not found
198-
}
199-
if (E->value.offset == 0) {
200-
return nullptr; //was erased
201+
return nullptr; // Not found.
201202
}
202203

203204
return E->value.src->get_file(p_path, &E->value);

core/io/pck_packer.cpp

+34-7
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ static int _get_pad(int p_alignment, int p_n) {
4848

4949
void PCKPacker::_bind_methods() {
5050
ClassDB::bind_method(D_METHOD("pck_start", "pck_path", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false));
51-
ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false));
51+
ClassDB::bind_method(D_METHOD("add_file", "target_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false));
52+
ClassDB::bind_method(D_METHOD("add_file_removal", "target_path"), &PCKPacker::add_file_removal);
5253
ClassDB::bind_method(D_METHOD("flush", "verbose"), &PCKPacker::flush, DEFVAL(false));
5354
}
5455

@@ -106,23 +107,42 @@ Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const Stri
106107
return OK;
107108
}
108109

109-
Error PCKPacker::add_file(const String &p_pck_path, const String &p_src, bool p_encrypt) {
110+
Error PCKPacker::add_file_removal(const String &p_target_path) {
110111
ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use.");
111112

112-
Ref<FileAccess> f = FileAccess::open(p_src, FileAccess::READ);
113+
File pf;
114+
// Simplify path here and on every 'files' access so that paths that have extra '/'
115+
// symbols or 'res://' in them still match the MD5 hash for the saved path.
116+
pf.path = p_target_path.simplify_path().trim_prefix("res://");
117+
pf.ofs = ofs;
118+
pf.size = 0;
119+
pf.removal = true;
120+
121+
pf.md5.resize(16);
122+
pf.md5.fill(0);
123+
124+
files.push_back(pf);
125+
126+
return OK;
127+
}
128+
129+
Error PCKPacker::add_file(const String &p_target_path, const String &p_source_path, bool p_encrypt) {
130+
ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use.");
131+
132+
Ref<FileAccess> f = FileAccess::open(p_source_path, FileAccess::READ);
113133
if (f.is_null()) {
114134
return ERR_FILE_CANT_OPEN;
115135
}
116136

117137
File pf;
118138
// Simplify path here and on every 'files' access so that paths that have extra '/'
119-
// symbols in them still match to the MD5 hash for the saved path.
120-
pf.path = p_pck_path.simplify_path();
121-
pf.src_path = p_src;
139+
// symbols or 'res://' in them still match the MD5 hash for the saved path.
140+
pf.path = p_target_path.simplify_path().trim_prefix("res://");
141+
pf.src_path = p_source_path;
122142
pf.ofs = ofs;
123143
pf.size = f->get_length();
124144

125-
Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_src);
145+
Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_source_path);
126146
{
127147
unsigned char hash[16];
128148
CryptoCore::md5(data.ptr(), data.size(), hash);
@@ -195,6 +215,9 @@ Error PCKPacker::flush(bool p_verbose) {
195215
if (files[i].encrypted) {
196216
flags |= PACK_FILE_ENCRYPTED;
197217
}
218+
if (files[i].removal) {
219+
flags |= PACK_FILE_REMOVAL;
220+
}
198221
fhead->store_32(flags);
199222
}
200223

@@ -218,6 +241,10 @@ Error PCKPacker::flush(bool p_verbose) {
218241

219242
int count = 0;
220243
for (int i = 0; i < files.size(); i++) {
244+
if (files[i].removal) {
245+
continue;
246+
}
247+
221248
Ref<FileAccess> src = FileAccess::open(files[i].src_path, FileAccess::READ);
222249
uint64_t to_write = files[i].size;
223250

core/io/pck_packer.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,15 @@ class PCKPacker : public RefCounted {
5353
uint64_t ofs = 0;
5454
uint64_t size = 0;
5555
bool encrypted = false;
56+
bool removal = false;
5657
Vector<uint8_t> md5;
5758
};
5859
Vector<File> files;
5960

6061
public:
6162
Error pck_start(const String &p_pck_path, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false);
62-
Error add_file(const String &p_pck_path, const String &p_src, bool p_encrypt = false);
63+
Error add_file(const String &p_target_path, const String &p_source_path, bool p_encrypt = false);
64+
Error add_file_removal(const String &p_target_path);
6365
Error flush(bool p_verbose = false);
6466

6567
PCKPacker() {}

doc/classes/PCKPacker.xml

+9-2
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,18 @@
2626
<methods>
2727
<method name="add_file">
2828
<return type="int" enum="Error" />
29-
<param index="0" name="pck_path" type="String" />
29+
<param index="0" name="target_path" type="String" />
3030
<param index="1" name="source_path" type="String" />
3131
<param index="2" name="encrypt" type="bool" default="false" />
3232
<description>
33-
Adds the [param source_path] file to the current PCK package at the [param pck_path] internal path (should start with [code]res://[/code]).
33+
Adds the [param source_path] file to the current PCK package at the [param target_path] internal path. The [code]res://[/code] prefix for [param target_path] is optional and stripped internally.
34+
</description>
35+
</method>
36+
<method name="add_file_removal">
37+
<return type="int" enum="Error" />
38+
<param index="0" name="target_path" type="String" />
39+
<description>
40+
Registers a file removal of the [param target_path] internal path to the PCK. This is mainly used for patches. If the file at this path has been loaded from a previous PCK, it will be removed. The [code]res://[/code] prefix for [param target_path] is optional and stripped internally.
3441
</description>
3542
</method>
3643
<method name="flush">

0 commit comments

Comments
 (0)