Skip to content

Commit

Permalink
Implemented unload of PCK files
Browse files Browse the repository at this point in the history
  • Loading branch information
rsubtil committed Oct 1, 2023
1 parent 030c1a9 commit 41d2f26
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 23 deletions.
23 changes: 23 additions & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,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) {
if (p_from_version <= 3) {
// Converts the actions from array to dictionary (array of events to dictionary with deadzone + events)
Expand Down Expand Up @@ -1223,6 +1244,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 @@ -133,6 +133,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 @@ -232,6 +358,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 @@ -373,16 +501,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(), "Can't open pack-referenced file '" + String(pf.pack) + "'.");
f(FileAccess::open(pf.pack.name, FileAccess::READ)) {
ERR_FAIL_COND_MSG(f.is_null(), "Can't open pack-referenced file '" + 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(), "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
ERR_FAIL_COND_MSG(fae.is_null(), "Can't open encrypted pack-referenced file '" + String(pf.pack.name) + "'.");

Vector<uint8_t> key;
key.resize(32);
Expand All @@ -391,7 +519,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, "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
ERR_FAIL_COND_MSG(err, "Can't open encrypted pack-referenced file '" + 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 @@ -59,30 +59,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 @@ -93,10 +80,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 @@ -110,12 +127,18 @@ 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);

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

_FORCE_INLINE_ Ref<FileAccess> try_open_path(const String &p_path);
_FORCE_INLINE_ bool has_path(const String &p_path);
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 @@ -565,6 +565,12 @@ void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
lock.unlock();
}

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

int ResourceCache::get_cached_resource_count() {
lock.lock();
int rc = 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 @@ -166,6 +166,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

0 comments on commit 41d2f26

Please sign in to comment.