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

Implement unload of PCK files #61286

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
23 changes: 23 additions & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,27 @@ bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_f
return true;
}

bool ProjectSettings::_unload_resource_pack(const String &p_pack) {
if (PackedData::get_singleton()->is_disabled()) {
return false;
}

bool ok = PackedData::get_singleton()->remove_pack(p_pack) == OK;
if (!ok) {
return false;
}

return true;
}

bool ProjectSettings::_is_pack_loaded(const String &p_pack) {
if (PackedData::get_singleton()->is_disabled()) {
return false;
}

return PackedData::get_singleton()->is_pack_loaded(p_pack);
}

void ProjectSettings::_convert_to_last_version(int p_from_version) {
#ifndef DISABLE_DEPRECATED
if (p_from_version <= 3) {
Expand Down Expand Up @@ -1389,6 +1410,8 @@ void ProjectSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("globalize_path", "path"), &ProjectSettings::globalize_path);
ClassDB::bind_method(D_METHOD("save"), &ProjectSettings::save);
ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::_load_resource_pack, DEFVAL(true), DEFVAL(0));
ClassDB::bind_method(D_METHOD("unload_resource_pack", "pack"), &ProjectSettings::_unload_resource_pack);
ClassDB::bind_method(D_METHOD("is_pack_loaded", "name"), &ProjectSettings::_is_pack_loaded);

ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd);

Expand Down
2 changes: 2 additions & 0 deletions core/config/project_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ class ProjectSettings : public Object {
void _convert_to_last_version(int p_from_version);

bool _load_resource_pack(const String &p_pack, bool p_replace_files = true, int p_offset = 0);
bool _unload_resource_pack(const String &p_pack);
bool _is_pack_loaded(const String &p_pack);

void _add_property_info_bind(const Dictionary &p_info);

Expand Down
140 changes: 134 additions & 6 deletions core/io/file_access_pack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@
#include "file_access_pack.h"

#include "core/io/file_access_encrypted.h"
#include "core/io/resource.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/version.h"

#include "modules/modules_enabled.gen.h" // For gdscript.
#ifdef MODULE_GDSCRIPT_ENABLED
#include "modules/gdscript/gdscript_cache.h"
#endif

#include <stdio.h>

Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) {
Expand All @@ -47,29 +53,86 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t
return ERR_FILE_UNRECOGNIZED;
}

Error PackedData::remove_pack(const String &p_path) {
if (!is_pack_loaded(p_path)) {
return ERR_FILE_UNRECOGNIZED;
}

Vector<PathMD5> reload_packs;
Vector<HashMap<PathMD5, PackedFile, PathMD5>::Iterator> to_remove;
for (HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.begin(); E; ++E) {
PackedFile pf = E->value;
if (pf.pack.name != p_path) {
continue;
}

PathMD5 pmd5 = pf.pack.replaced_pack;
if (pmd5.set) {
if (!reload_packs.has(pmd5)) {
reload_packs.push_back(pmd5);
}
}

remove_path(pf.filepath);
to_remove.push_back(E);
}

for (const HashMap<PathMD5, PackedFile, PathMD5>::Iterator &E : to_remove) {
files.remove(E);
}
remove_loaded_pack(p_path);

// For reloading files unloaded by more recent packs, we simply need to reload packs with replace_files disabled.
for (PathMD5 pmd5 : reload_packs) {
if (!loaded_packs.has(pmd5)) {
continue;
}
LoadedPackInfo pack_info = loaded_packs[pmd5];
Error err = add_pack(pack_info.name, false, pack_info.offset);
if (err) {
return err;
}
}

return OK;
}

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) {
String simplified_path = p_path.simplify_path();
PathMD5 pmd5(simplified_path.md5_buffer());

// When running on the editor, the base files are not loaded from a main pack file.
// This extra check prevents packs from overriding those base files.
#ifdef TOOLS_ENABLED
bool exists = files.has(pmd5) || FileAccess::exists(simplified_path);
#else
bool exists = files.has(pmd5);
#endif

PackInfo pi;
pi.name = p_pkg_path;
if (p_replace_files && exists) {
pi.replaced_pack = PathMD5(files[pmd5].pack.name.md5_buffer());
}

PackedFile pf;
pf.encrypted = p_encrypted;
pf.pack = p_pkg_path;
pf.pack = pi;
pf.offset = p_ofs;
pf.size = p_size;
for (int i = 0; i < 16; i++) {
pf.md5[i] = p_md5[i];
}
pf.src = p_src;
pf.filepath = simplified_path.replace_first("res://", "");

if (!exists || p_replace_files) {
files[pmd5] = pf;
}

if (!exists) {
//search for dir
String p = simplified_path.replace_first("res://", "");
String p = pf.filepath;
PackedDir *cd = root;

if (p.contains("/")) { //in a subdir
Expand All @@ -96,6 +159,69 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
}
}

void PackedData::remove_path(const String &p_path) {
String p = p_path;
PackedDir *cd = root;

if (p.contains("/")) {
Vector<String> ds = p.get_base_dir().split("/");

for (int j = 0; j < ds.size(); j++) {
if (!cd->subdirs.has(ds[j])) {
return;
} else {
cd = cd->subdirs[ds[j]];
}
}
}
String filename = p_path.get_file();
cd->files.erase(filename);

// Clear empty folders.
while (cd && cd->files.is_empty() && cd->subdirs.is_empty()) {
String name = cd->name;
cd = cd->parent;
if (cd) {
cd->subdirs.erase(name);
}
}

String res_path = "res://" + p_path;

// Remove paths from cache.
if (ResourceCache::has(res_path)) {
ResourceCache::remove_cached_resource(res_path);
}

// GDScript also caches scripts internally, so they too must be removed.
#ifdef MODULE_GDSCRIPT_ENABLED
if (GDScriptCache::get_cached_script(res_path).is_valid()) {
GDScriptCache::remove_script(res_path);
}
#endif
}

void PackedData::add_loaded_pack(const String &p_path, const uint64_t &p_offset) {
if (!is_pack_loaded(p_path)) {
LoadedPackInfo pack_info;
pack_info.name = p_path;
pack_info.offset = p_offset;

PathMD5 pmd5(p_path.md5_buffer());
loaded_packs.insert(pmd5, pack_info);
}
}

void PackedData::remove_loaded_pack(const String &p_path) {
PathMD5 pmd5(p_path.md5_buffer());
loaded_packs.erase(pmd5);
}

bool PackedData::is_pack_loaded(const String &p_pack_path) const {
PathMD5 pmd5(p_pack_path.md5_buffer());
return loaded_packs.has(pmd5);
}

void PackedData::add_pack_source(PackSource *p_source) {
if (p_source != nullptr) {
sources.push_back(p_source);
Expand Down Expand Up @@ -259,6 +385,8 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
f = fae;
}

PackedData::get_singleton()->add_loaded_pack(p_path, p_offset);

for (int i = 0; i < file_count; i++) {
uint32_t sl = f->get_32();
CharString cs;
Expand Down Expand Up @@ -385,16 +513,16 @@ void FileAccessPack::close() {

FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file) :
pf(p_file),
f(FileAccess::open(pf.pack, FileAccess::READ)) {
ERR_FAIL_COND_MSG(f.is_null(), vformat("Can't open pack-referenced file '%s'.", String(pf.pack)));
f(FileAccess::open(pf.pack.name, FileAccess::READ)) {
ERR_FAIL_COND_MSG(f.is_null(), vformat("Can't open pack-referenced file '%s'.", String(pf.pack.name)));

f->seek(pf.offset);
off = pf.offset;

if (pf.encrypted) {
Ref<FileAccessEncrypted> fae;
fae.instantiate();
ERR_FAIL_COND_MSG(fae.is_null(), vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
ERR_FAIL_COND_MSG(fae.is_null(), vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack.name)));

Vector<uint8_t> key;
key.resize(32);
Expand All @@ -403,7 +531,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
}

Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
ERR_FAIL_COND_MSG(err, vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
ERR_FAIL_COND_MSG(err, vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack.name)));
f = fae;
off = 0;
}
Expand Down
57 changes: 40 additions & 17 deletions core/io/file_access_pack.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,17 @@ class PackedData {
friend class PackSource;

public:
struct PackedFile {
String pack;
uint64_t offset; //if offset is ZERO, the file was ERASED
uint64_t size;
uint8_t md5[16];
PackSource *src = nullptr;
bool encrypted;
};

private:
struct PackedDir {
PackedDir *parent = nullptr;
String name;
HashMap<String, PackedDir *> subdirs;
HashSet<String> files;
};

struct PathMD5 {
uint64_t a = 0;
uint64_t b = 0;
bool set = false;

bool operator==(const PathMD5 &p_val) const {
return (a == p_val.a) && (b == p_val.b);
}
bool operator!=(const PathMD5 &p_val) const {
return (a != p_val.a) || (b != p_val.b);
}
static uint32_t hash(const PathMD5 &p_val) {
uint32_t h = hash_murmur3_one_32(p_val.a);
return hash_fmix32(hash_murmur3_one_32(p_val.b, h));
Expand All @@ -94,10 +81,40 @@ class PackedData {
explicit PathMD5(const Vector<uint8_t> &p_buf) {
a = *((uint64_t *)&p_buf[0]);
b = *((uint64_t *)&p_buf[8]);
set = true;
}
};

struct PackInfo {
String name;
PathMD5 replaced_pack;
};

struct PackedFile {
PackInfo pack;
String filepath;
uint64_t offset; //if offset is ZERO, the file was ERASED
uint64_t size;
uint8_t md5[16];
PackSource *src = nullptr;
bool encrypted;
};

private:
struct PackedDir {
PackedDir *parent = nullptr;
String name;
HashMap<String, PackedDir *> subdirs;
HashSet<String> files;
};

struct LoadedPackInfo {
String name;
uint64_t offset;
};

HashMap<PathMD5, PackedFile, PathMD5> files;
HashMap<PathMD5, LoadedPackInfo, PathMD5> loaded_packs;

Vector<PackSource *> sources;

Expand All @@ -111,13 +128,19 @@ 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
void remove_path(const String &p_path);
uint8_t *get_file_hash(const String &p_path);

void add_loaded_pack(const String &p_path, const uint64_t &p_offset);
void remove_loaded_pack(const String &p_path);
bool is_pack_loaded(const String &p_pack_path) const;

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);
Error remove_pack(const String &p_path);

void clear();

Expand Down
2 changes: 2 additions & 0 deletions core/io/file_access_zip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint6
packages.push_back(pkg);
int pkg_num = packages.size() - 1;

PackedData::get_singleton()->add_loaded_pack(p_path, 0);

for (uint64_t i = 0; i < gi.number_entry; i++) {
char filename_inzip[256];

Expand Down
6 changes: 6 additions & 0 deletions core/io/resource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,12 @@ void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
}
}

void ResourceCache::remove_cached_resource(const String &p_path) {
lock.lock();
resources.erase(p_path);
lock.unlock();
}

int ResourceCache::get_cached_resource_count() {
MutexLock mutex_lock(lock);
return resources.size();
Expand Down
1 change: 1 addition & 0 deletions core/io/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class ResourceCache {
static bool has(const String &p_path);
static Ref<Resource> get_ref(const String &p_path);
static void get_cached_resources(List<Ref<Resource>> *p_resources);
static void remove_cached_resource(const String &p_path);
static int get_cached_resource_count();
};

Expand Down
Loading
Loading