Path: blob/master/core/io/file_access_compressed.cpp
20850 views
/**************************************************************************/1/* file_access_compressed.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "file_access_compressed.h"3132void FileAccessCompressed::configure(const String &p_magic, Compression::Mode p_mode, uint32_t p_block_size) {33magic = p_magic.ascii().get_data();34magic = (magic + " ").substr(0, 4);3536cmode = p_mode;37block_size = p_block_size;38}3940Error FileAccessCompressed::open_after_magic(Ref<FileAccess> p_base) {41f = p_base;42cmode = (Compression::Mode)f->get_32();43block_size = f->get_32();44if (block_size == 0) {45f.unref();46ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("Can't open compressed file '%s' with block size 0, it is corrupted.", p_base->get_path()));47}48read_total = f->get_32();49uint32_t bc = (read_total / block_size) + 1;50uint64_t acc_ofs = f->get_position() + bc * 4;51uint32_t max_bs = 0;52for (uint32_t i = 0; i < bc; i++) {53ReadBlock rb;54rb.offset = acc_ofs;55rb.csize = f->get_32();56acc_ofs += rb.csize;57max_bs = MAX(max_bs, rb.csize);58read_blocks.push_back(rb);59}6061comp_buffer.resize(max_bs);62buffer.resize(block_size);63read_ptr = buffer.ptrw();64f->get_buffer(comp_buffer.ptrw(), read_blocks[0].csize);65at_end = false;66read_eof = false;67read_block_count = bc;68read_block_size = read_blocks.size() == 1 ? read_total : block_size;6970const int64_t ret = Compression::decompress(buffer.ptrw(), read_block_size, comp_buffer.ptr(), read_blocks[0].csize, cmode);71read_block = 0;72read_pos = 0;7374return ret == -1 ? ERR_FILE_CORRUPT : OK;75}7677Error FileAccessCompressed::open_internal(const String &p_path, int p_mode_flags) {78ERR_FAIL_COND_V(p_mode_flags == READ_WRITE, ERR_UNAVAILABLE);79_close();8081Error err;82f = FileAccess::open(p_path, p_mode_flags, &err);83if (err != OK) {84//not openable85f.unref();86return err;87}8889if (p_mode_flags & WRITE) {90buffer.clear();91writing = true;92write_pos = 0;93write_buffer_size = 256;94buffer.resize(256);95write_max = 0;96write_ptr = buffer.ptrw();9798//don't store anything else unless it's done saving!99} else {100char rmagic[5];101f->get_buffer((uint8_t *)rmagic, 4);102rmagic[4] = 0;103err = ERR_FILE_UNRECOGNIZED;104if (magic != rmagic || (err = open_after_magic(f)) != OK) {105f.unref();106return err;107}108}109110return OK;111}112113void FileAccessCompressed::_close() {114if (f.is_null()) {115return;116}117118if (writing) {119//save block table and all compressed blocks120121CharString mgc = magic.utf8();122f->store_buffer((const uint8_t *)mgc.get_data(), mgc.length()); //write header 4123f->store_32(cmode); //write compression mode 4124f->store_32(block_size); //write block size 4125f->store_32(uint32_t(write_max)); //max amount of data written 4126uint32_t bc = (write_max / block_size) + 1;127128for (uint32_t i = 0; i < bc; i++) {129f->store_32(0); //compressed sizes, will update later130}131132uint32_t last_block_size = write_max % block_size;133134// Temporary buffer for compressed data blocks.135LocalVector<uint8_t> temp_cblock;136temp_cblock.resize(Compression::get_max_compressed_buffer_size(bc == 1 ? last_block_size : block_size, cmode));137uint8_t *temp_cblock_ptr = temp_cblock.ptr();138139// Compress and store the blocks.140LocalVector<uint32_t> block_sizes;141for (uint32_t i = 0; i < bc; i++) {142uint32_t bl = i == (bc - 1) ? last_block_size : block_size;143uint8_t *bp = &write_ptr[i * block_size];144145const int64_t compressed_size = Compression::compress(temp_cblock_ptr, bp, bl, cmode);146ERR_FAIL_COND_MSG(compressed_size < 0, "FileAccessCompressed: Error compressing data.");147148f->store_buffer(temp_cblock_ptr, (uint64_t)compressed_size);149block_sizes.push_back(compressed_size);150}151152f->seek(16); //ok write block sizes153for (uint32_t i = 0; i < bc; i++) {154f->store_32(block_sizes[i]);155}156f->seek_end();157f->store_buffer((const uint8_t *)mgc.get_data(), mgc.length()); //magic at the end too158} else {159comp_buffer.clear();160read_blocks.clear();161}162buffer.clear();163f.unref();164}165166bool FileAccessCompressed::is_open() const {167return f.is_valid();168}169170String FileAccessCompressed::get_path() const {171if (f.is_valid()) {172return f->get_path();173} else {174return "";175}176}177178String FileAccessCompressed::get_path_absolute() const {179if (f.is_valid()) {180return f->get_path_absolute();181} else {182return "";183}184}185186void FileAccessCompressed::seek(uint64_t p_position) {187ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");188189if (writing) {190ERR_FAIL_COND(p_position > write_max);191192write_pos = p_position;193194} else {195ERR_FAIL_COND(p_position > read_total);196if (p_position == read_total) {197at_end = true;198} else {199at_end = false;200read_eof = false;201uint32_t block_idx = p_position / block_size;202if (block_idx != read_block) {203read_block = block_idx;204f->seek(read_blocks[read_block].offset);205f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);206const int64_t ret = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);207ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");208read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;209}210211read_pos = p_position % block_size;212}213}214}215216void FileAccessCompressed::seek_end(int64_t p_position) {217ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");218if (writing) {219seek(write_max + p_position);220} else {221seek(read_total + p_position);222}223}224225uint64_t FileAccessCompressed::get_position() const {226ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");227if (writing) {228return write_pos;229} else {230return (uint64_t)read_block * block_size + read_pos;231}232}233234uint64_t FileAccessCompressed::get_length() const {235ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");236if (writing) {237return write_max;238} else {239return read_total;240}241}242243bool FileAccessCompressed::eof_reached() const {244ERR_FAIL_COND_V_MSG(f.is_null(), false, "File must be opened before use.");245if (writing) {246return false;247} else {248return read_eof;249}250}251252uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const {253if (p_length == 0) {254return 0;255}256257ERR_FAIL_NULL_V(p_dst, -1);258ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");259ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");260261if (at_end) {262read_eof = true;263return 0;264}265266uint64_t dst_idx = 0;267while (true) {268// Copy over as much of our current block as possible.269const uint32_t copied_bytes_count = MIN(p_length - dst_idx, read_block_size - read_pos);270memcpy(p_dst + dst_idx, read_ptr + read_pos, copied_bytes_count);271dst_idx += copied_bytes_count;272read_pos += copied_bytes_count;273274if (dst_idx == p_length) {275// We're done! We read back all that was requested.276return p_length;277}278279// We're not done yet; try reading the next block.280read_block++;281282if (read_block >= read_block_count) {283// We're done! We read back the whole file.284read_block--;285at_end = true;286if (dst_idx + 1 < p_length) {287read_eof = true;288}289return dst_idx;290}291292// Read the next block of compressed data.293f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);294const int64_t ret = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);295ERR_FAIL_COND_V_MSG(ret == -1, -1, "Compressed file is corrupt.");296read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;297read_pos = 0;298}299300return p_length;301}302303Error FileAccessCompressed::get_error() const {304return read_eof ? ERR_FILE_EOF : OK;305}306307void FileAccessCompressed::flush() {308ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");309ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");310311// compressed files keep data in memory till close()312}313314bool FileAccessCompressed::store_buffer(const uint8_t *p_src, uint64_t p_length) {315ERR_FAIL_COND_V_MSG(f.is_null(), false, "File must be opened before use.");316ERR_FAIL_COND_V_MSG(!writing, false, "File has not been opened in write mode.");317318if (write_pos + (p_length) > write_max) {319write_max = write_pos + (p_length);320}321if (write_max > write_buffer_size) {322write_buffer_size = next_power_of_2(write_max);323ERR_FAIL_COND_V(buffer.resize(write_buffer_size) != OK, false);324write_ptr = buffer.ptrw();325}326327if (p_length) {328memcpy(write_ptr + write_pos, p_src, p_length);329}330331write_pos += p_length;332return true;333}334335bool FileAccessCompressed::file_exists(const String &p_name) {336Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);337if (fa.is_null()) {338return false;339}340return true;341}342343uint64_t FileAccessCompressed::_get_modified_time(const String &p_file) {344if (f.is_valid()) {345return f->get_modified_time(p_file);346} else {347return 0;348}349}350351uint64_t FileAccessCompressed::_get_access_time(const String &p_file) {352if (f.is_valid()) {353return f->get_access_time(p_file);354} else {355return 0;356}357}358359int64_t FileAccessCompressed::_get_size(const String &p_file) {360if (f.is_valid()) {361return f->get_size(p_file);362} else {363return -1;364}365}366367BitField<FileAccess::UnixPermissionFlags> FileAccessCompressed::_get_unix_permissions(const String &p_file) {368if (f.is_valid()) {369return f->_get_unix_permissions(p_file);370}371return 0;372}373374Error FileAccessCompressed::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {375if (f.is_valid()) {376return f->_set_unix_permissions(p_file, p_permissions);377}378return FAILED;379}380381bool FileAccessCompressed::_get_hidden_attribute(const String &p_file) {382if (f.is_valid()) {383return f->_get_hidden_attribute(p_file);384}385return false;386}387388Error FileAccessCompressed::_set_hidden_attribute(const String &p_file, bool p_hidden) {389if (f.is_valid()) {390return f->_set_hidden_attribute(p_file, p_hidden);391}392return FAILED;393}394395bool FileAccessCompressed::_get_read_only_attribute(const String &p_file) {396if (f.is_valid()) {397return f->_get_read_only_attribute(p_file);398}399return false;400}401402Error FileAccessCompressed::_set_read_only_attribute(const String &p_file, bool p_ro) {403if (f.is_valid()) {404return f->_set_read_only_attribute(p_file, p_ro);405}406return FAILED;407}408409void FileAccessCompressed::close() {410_close();411}412413FileAccessCompressed::~FileAccessCompressed() {414_close();415}416417418