Add support for delta encoding to patch PCKs

This commit is contained in:
Mikael Hermansson
2025-10-24 16:10:45 +02:00
parent 9dd6c4dbac
commit 0cc88f34da
17 changed files with 851 additions and 92 deletions

110
core/io/delta_encoding.cpp Normal file
View File

@ -0,0 +1,110 @@
/**************************************************************************/
/* delta_encoding.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "delta_encoding.h"
#include <zstd.h>
#define ERR_FAIL_ZSTD_V_MSG(m_result, m_retval, m_msg) \
ERR_FAIL_COND_V_MSG(ZSTD_isError(m_result), m_retval, vformat("%s Zstandard reported error code %d: \"%s\".", m_msg, ZSTD_getErrorCode(m_result), ZSTD_getErrorString(ZSTD_getErrorCode(m_result))))
struct ZstdCompressionContext {
ZSTD_CCtx *context = ZSTD_createCCtx();
~ZstdCompressionContext() { ZSTD_freeCCtx(context); }
operator ZSTD_CCtx *() { return context; }
};
struct ZstdDecompressionContext {
ZSTD_DCtx *context = ZSTD_createDCtx();
~ZstdDecompressionContext() { ZSTD_freeDCtx(context); }
operator ZSTD_DCtx *() { return context; }
};
static constexpr uint8_t DELTA_MAGIC[4] = { 'G', 'D', 'D', 'L' };
static constexpr uint8_t DELTA_VERSION_NUMBER = 1;
static constexpr size_t DELTA_HEADER_SIZE = 5;
Error DeltaEncoding::encode_delta(Span<uint8_t> p_old_data, Span<uint8_t> p_new_data, Vector<uint8_t> &r_delta, int p_compression_level) {
size_t zstd_result = ZSTD_compressBound(p_new_data.size());
ERR_FAIL_ZSTD_V_MSG(zstd_result, FAILED, "Failed to encode delta. Calculating compression bounds failed.");
r_delta.reserve_exact(DELTA_HEADER_SIZE + zstd_result);
r_delta.resize(DELTA_HEADER_SIZE + zstd_result);
memcpy(r_delta.ptrw(), DELTA_MAGIC, 4);
r_delta.write[4] = DELTA_VERSION_NUMBER;
ZstdCompressionContext zstd_context;
ZSTD_parameters zstd_params = ZSTD_getParams(p_compression_level, p_new_data.size(), p_old_data.size());
zstd_params.fParams.contentSizeFlag = 1;
zstd_params.fParams.checksumFlag = 1;
zstd_result = ZSTD_CCtx_setParams(zstd_context, zstd_params);
ERR_FAIL_ZSTD_V_MSG(zstd_result, FAILED, "Failed to encode delta. Setting compression parameters failed.");
zstd_result = ZSTD_CCtx_refPrefix(zstd_context, p_old_data.ptr(), p_old_data.size());
ERR_FAIL_ZSTD_V_MSG(zstd_result, FAILED, "Failed to encode delta. Setting prefix dictionary failed.");
zstd_result = ZSTD_compress2(zstd_context, r_delta.ptrw() + DELTA_HEADER_SIZE, r_delta.size() - DELTA_HEADER_SIZE, p_new_data.ptr(), p_new_data.size());
ERR_FAIL_ZSTD_V_MSG(zstd_result, FAILED, "Failed to encode delta. Compression failed.");
r_delta.resize(DELTA_HEADER_SIZE + zstd_result);
return OK;
}
Error DeltaEncoding::decode_delta(Span<uint8_t> p_old_data, Span<uint8_t> p_delta, Vector<uint8_t> &r_new_data) {
ERR_FAIL_COND_V_MSG(p_delta.size() < DELTA_HEADER_SIZE, ERR_INVALID_DATA, vformat("Failed to decode delta. File size (%d) is too small.", p_delta.size()));
uint8_t magic[4];
memcpy(magic, p_delta.ptr(), sizeof(magic));
uint8_t version = p_delta[4];
ERR_FAIL_COND_V_MSG(memcmp(magic, DELTA_MAGIC, 4) != 0, ERR_FILE_CORRUPT, "Failed to decode delta. Header is invalid.");
ERR_FAIL_COND_V_MSG(version != DELTA_VERSION_NUMBER, ERR_FILE_UNRECOGNIZED, vformat("Failed to decode delta. Expected version %d but found %d.", DELTA_VERSION_NUMBER, version));
size_t zstd_result = ZSTD_findDecompressedSize(p_delta.ptr() + DELTA_HEADER_SIZE, p_delta.size() - DELTA_HEADER_SIZE);
ERR_FAIL_ZSTD_V_MSG(zstd_result, FAILED, "Failed to decode delta. Unable to find decompressed size.");
r_new_data.reserve_exact(zstd_result);
r_new_data.resize(zstd_result);
ZstdDecompressionContext zstd_context;
zstd_result = ZSTD_DCtx_refPrefix(zstd_context, p_old_data.ptr(), p_old_data.size());
ERR_FAIL_ZSTD_V_MSG(zstd_result, FAILED, "Failed to decode delta. Setting prefix dictionary failed.");
zstd_result = ZSTD_decompressDCtx(zstd_context, r_new_data.ptrw(), r_new_data.size(), p_delta.ptr() + DELTA_HEADER_SIZE, p_delta.size() - DELTA_HEADER_SIZE);
ERR_FAIL_ZSTD_V_MSG(zstd_result, FAILED, "Failed to decode delta. Decompression failed.");
ERR_FAIL_COND_V(zstd_result != (size_t)r_new_data.size(), ERR_FILE_CORRUPT);
return OK;
}

39
core/io/delta_encoding.h Normal file
View File

@ -0,0 +1,39 @@
/**************************************************************************/
/* delta_encoding.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/file_access.h"
class DeltaEncoding {
public:
static Error encode_delta(Span<uint8_t> p_old_data, Span<uint8_t> p_new_data, Vector<uint8_t> &r_delta, int p_compression_level = 19);
static Error decode_delta(Span<uint8_t> p_old_data, Span<uint8_t> p_delta, Vector<uint8_t> &r_new_data);
};

View File

@ -31,6 +31,7 @@
#include "file_access_pack.h"
#include "core/io/file_access_encrypted.h"
#include "core/io/file_access_patched.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/version.h"
@ -45,7 +46,7 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t
return ERR_FILE_UNRECOGNIZED;
}
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, bool p_bundle) {
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, bool p_bundle, bool p_delta) {
String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
@ -54,6 +55,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
PackedFile pf;
pf.encrypted = p_encrypted;
pf.bundle = p_bundle;
pf.delta = p_delta;
pf.pack = p_pkg_path;
pf.offset = p_ofs;
pf.size = p_size;
@ -62,8 +64,11 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
}
pf.src = p_src;
if (!exists || p_replace_files) {
if (p_delta) {
delta_patches[pmd5].push_back(pf);
} else if (!exists || p_replace_files) {
files[pmd5] = pf;
delta_patches[pmd5].clear();
}
if (!exists) {
@ -137,6 +142,28 @@ uint8_t *PackedData::get_file_hash(const String &p_path) {
return E->value.md5;
}
Vector<PackedData::PackedFile> PackedData::get_delta_patches(const String &p_path) const {
String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
HashMap<PathMD5, Vector<PackedFile>, PathMD5>::ConstIterator E = delta_patches.find(pmd5);
if (!E) {
return Vector<PackedFile>();
}
return E->value;
}
bool PackedData::has_delta_patches(const String &p_path) const {
String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
HashMap<PathMD5, Vector<PackedFile>, PathMD5>::ConstIterator E = delta_patches.find(pmd5);
if (!E) {
return false;
}
return !E->value.is_empty();
}
HashSet<String> PackedData::get_file_paths() const {
HashSet<String> file_paths;
_get_file_paths(root, root->name, file_paths);
@ -155,6 +182,7 @@ void PackedData::_get_file_paths(PackedDir *p_dir, const String &p_parent_dir, H
void PackedData::clear() {
files.clear();
delta_patches.clear();
_free_packed_dirs(root);
root = memnew(PackedDir);
}
@ -322,7 +350,7 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
if (flags & PACK_FILE_REMOVAL) { // The file was removed.
PackedData::get_singleton()->remove_path(path);
} else {
PackedData::get_singleton()->add_path(p_path, path, file_base + ofs, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED), sparse_bundle);
PackedData::get_singleton()->add_path(p_path, path, file_base + ofs, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED), sparse_bundle, (flags & PACK_FILE_DELTA));
}
}
@ -330,7 +358,17 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
}
Ref<FileAccess> PackedSourcePCK::get_file(const String &p_path, PackedData::PackedFile *p_file) {
return memnew(FileAccessPack(p_path, *p_file));
Ref<FileAccess> file(memnew(FileAccessPack(p_path, *p_file)));
if (PackedData::get_singleton()->has_delta_patches(p_path)) {
Ref<FileAccessPatched> file_patched;
file_patched.instantiate();
Error err = file_patched->open_custom(file);
ERR_FAIL_COND_V(err != OK, Ref<FileAccess>());
file = file_patched;
}
return file;
}
//////////////////////////////////////////////////////////////////
@ -362,7 +400,7 @@ void PackedSourceDirectory::add_directory(const String &p_path, bool p_replace_f
for (const String &file_name : da->get_files()) {
String file_path = p_path.path_join(file_name);
uint8_t md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
PackedData::get_singleton()->add_path(p_path, file_path, 0, 0, md5, this, p_replace_files, false, false);
PackedData::get_singleton()->add_path(p_path, file_path, 0, 0, md5, this, p_replace_files, false, false, false);
}
for (const String &sub_dir_name : da->get_directories()) {
@ -470,6 +508,7 @@ void FileAccessPack::close() {
}
FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file) {
path = p_path;
pf = p_file;
if (pf.bundle) {
String simplified_path = p_path.simplify_path();

View File

@ -54,6 +54,7 @@ enum PackFlags {
enum PackFileFlags {
PACK_FILE_ENCRYPTED = 1 << 0,
PACK_FILE_REMOVAL = 1 << 1,
PACK_FILE_DELTA = 1 << 2,
};
class PackSource;
@ -72,6 +73,7 @@ public:
PackSource *src = nullptr;
bool encrypted;
bool bundle;
bool delta;
};
private:
@ -103,6 +105,7 @@ private:
};
HashMap<PathMD5, PackedFile, PathMD5> files;
HashMap<PathMD5, Vector<PackedFile>, PathMD5> delta_patches;
Vector<PackSource *> sources;
@ -116,9 +119,11 @@ private:
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, bool p_bundle = false); // for PackSource
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, bool p_bundle = false, bool p_delta = false); // for PackSource
void remove_path(const String &p_path);
uint8_t *get_file_hash(const String &p_path);
Vector<PackedFile> get_delta_patches(const String &p_path) const;
bool has_delta_patches(const String &p_path) const;
HashSet<String> get_file_paths() const;
void set_disabled(bool p_disabled) { disabled = p_disabled; }
@ -166,6 +171,7 @@ class FileAccessPack : public FileAccess {
GDSOFTCLASS(FileAccessPack, FileAccess);
PackedData::PackedFile pf;
String path;
mutable uint64_t pos;
mutable bool eof;
uint64_t off;
@ -186,6 +192,9 @@ class FileAccessPack : public FileAccess {
public:
virtual bool is_open() const override;
virtual String get_path() const override { return path; }
virtual String get_path_absolute() const override { return path; }
virtual void seek(uint64_t p_position) override;
virtual void seek_end(int64_t p_position = 0) override;
virtual uint64_t get_position() const override;

View File

@ -0,0 +1,203 @@
/**************************************************************************/
/* file_access_patched.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "file_access_patched.h"
#include "file_access_pack.h"
#include "core/io/delta_encoding.h"
#include "core/os/os.h"
Error FileAccessPatched::_apply_patch() const {
ERR_FAIL_COND_V(!is_open(), FAILED);
String path = old_file->get_path();
Vector<PackedData::PackedFile> delta_patches = PackedData::get_singleton()->get_delta_patches(path);
Vector<uint8_t> old_file_data = old_file->get_buffer(old_file->get_length());
for (int i = 0; i < delta_patches.size(); ++i) {
const PackedData::PackedFile &delta_patch = delta_patches[i];
ERR_FAIL_COND_V(delta_patch.bundle, FAILED);
Error err = OK;
uint64_t total_usec_start = OS::get_singleton()->get_ticks_usec();
uint64_t io_usec_start = OS::get_singleton()->get_ticks_usec();
Ref<FileAccess> patch_file = FileAccess::open(delta_patch.pack, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, err);
patch_file->seek(delta_patch.offset);
ERR_FAIL_COND_V(patch_file->get_error() != OK, patch_file->get_error());
Vector<uint8_t> patch_data = patch_file->get_buffer(delta_patch.size);
ERR_FAIL_COND_V(patch_data.is_empty(), ERR_FILE_CANT_READ);
uint64_t io_usec_end = OS::get_singleton()->get_ticks_usec();
uint64_t decode_usec_start = OS::get_singleton()->get_ticks_usec();
Vector<uint8_t> new_file_data;
err = DeltaEncoding::decode_delta(old_file_data, patch_data, new_file_data);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to apply delta patch (%d of %d) to \"%s\".", i + 1, delta_patches.size(), path));
uint64_t decode_usec_end = OS::get_singleton()->get_ticks_usec();
old_file_data = new_file_data;
uint64_t total_usec_end = OS::get_singleton()->get_ticks_usec();
print_verbose(vformat(U"Applied delta patch to \"%s\" from \"%s\" in %d μs (%d μs I/O, %d μs decoding).", path, delta_patch.pack.get_file(), total_usec_end - total_usec_start, io_usec_end - io_usec_start, decode_usec_end - decode_usec_start));
}
patched_file_data = old_file_data;
patched_file.instantiate();
return patched_file->open_custom(patched_file_data.ptr(), patched_file_data.size());
}
bool FileAccessPatched::_try_apply_patch() const {
if (last_error != OK) {
return false;
}
if (patched_file.is_valid()) {
return true;
}
last_error = _apply_patch();
return last_error == OK;
}
Error FileAccessPatched::open_custom(const Ref<FileAccess> &p_old_file) {
close();
if (!p_old_file->is_open()) {
last_error = ERR_FILE_CANT_OPEN;
return last_error;
}
old_file = p_old_file;
return OK;
}
bool FileAccessPatched::is_open() const {
return old_file.is_valid() && old_file->is_open();
}
void FileAccessPatched::seek(uint64_t p_position) {
if (!_try_apply_patch()) {
return;
}
patched_file->seek(p_position);
}
void FileAccessPatched::seek_end(int64_t p_position) {
if (!_try_apply_patch()) {
return;
}
patched_file->seek_end(p_position);
}
uint64_t FileAccessPatched::get_position() const {
if (!_try_apply_patch()) {
return 0;
}
return patched_file->get_position();
}
uint64_t FileAccessPatched::get_length() const {
if (!_try_apply_patch()) {
return 0;
}
return patched_file->get_length();
}
bool FileAccessPatched::eof_reached() const {
if (!_try_apply_patch()) {
return true;
}
return patched_file->eof_reached();
}
Error FileAccessPatched::get_error() const {
if (last_error != OK) {
return last_error;
}
if (patched_file.is_valid()) {
Error inner_error = patched_file->get_error();
if (inner_error != OK) {
return inner_error;
}
}
return last_error;
}
bool FileAccessPatched::store_buffer(const uint8_t *p_src, uint64_t p_length) {
if (!_try_apply_patch()) {
return false;
}
return patched_file->store_buffer(p_src, p_length);
}
uint64_t FileAccessPatched::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
if (!_try_apply_patch()) {
return 0;
}
return patched_file->get_buffer(p_dst, p_length);
}
void FileAccessPatched::flush() {
if (!_try_apply_patch()) {
return;
}
patched_file->flush();
}
void FileAccessPatched::close() {
old_file = Ref<FileAccess>();
patched_file = Ref<FileAccessMemory>();
patched_file_data.clear();
last_error = OK;
}
bool FileAccessPatched::file_exists(const String &p_name) {
ERR_FAIL_COND_V(old_file.is_null(), false);
return old_file->file_exists(p_name);
}

View File

@ -0,0 +1,84 @@
/**************************************************************************/
/* file_access_patched.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "file_access.h"
#include "file_access_memory.h"
class FileAccessPatched : public FileAccess {
GDSOFTCLASS(FileAccessPatched, FileAccess);
Ref<FileAccess> old_file;
mutable Vector<uint8_t> patched_file_data;
mutable Ref<FileAccessMemory> patched_file;
mutable Error last_error = OK;
Error _apply_patch() const;
bool _try_apply_patch() const;
protected:
virtual BitField<UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
virtual Error _set_unix_permissions(const String &p_file, BitField<UnixPermissionFlags> p_permissions) override { return FAILED; }
virtual bool _get_hidden_attribute(const String &p_file) override { return false; }
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; }
virtual bool _get_read_only_attribute(const String &p_file) override { return false; }
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; }
virtual uint64_t _get_modified_time(const String &p_file) override { return 0; }
virtual uint64_t _get_access_time(const String &p_file) override { return 0; }
virtual int64_t _get_size(const String &p_file) override { return -1; }
virtual Error open_internal(const String &p_path, int p_mode_flags) override { return ERR_UNAVAILABLE; }
public:
Error open_custom(const Ref<FileAccess> &p_old_file);
virtual bool is_open() const override;
virtual void seek(uint64_t p_position) override;
virtual void seek_end(int64_t p_position = 0) override;
virtual uint64_t get_position() const override;
virtual uint64_t get_length() const override;
virtual bool eof_reached() const override;
virtual Error get_error() const override;
virtual bool store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void close() override;
virtual bool file_exists(const String &p_name) override;
};