Add support for delta encoding to patch PCKs
This commit is contained in:
110
core/io/delta_encoding.cpp
Normal file
110
core/io/delta_encoding.cpp
Normal 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
39
core/io/delta_encoding.h
Normal 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);
|
||||
};
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
203
core/io/file_access_patched.cpp
Normal file
203
core/io/file_access_patched.cpp
Normal 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);
|
||||
}
|
||||
84
core/io/file_access_patched.h
Normal file
84
core/io/file_access_patched.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user