feat: godot-engine-source-4.3-stable
This commit is contained in:
parent
c59a7dcade
commit
7125d019b5
11149 changed files with 5070401 additions and 0 deletions
5
engine/core/io/SCsub
Normal file
5
engine/core/io/SCsub
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.core_sources, "*.cpp")
|
||||
357
engine/core/io/compression.cpp
Normal file
357
engine/core/io/compression.cpp
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/**************************************************************************/
|
||||
/* compression.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 "compression.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/zip_io.h"
|
||||
|
||||
#include "thirdparty/misc/fastlz.h"
|
||||
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#ifdef BROTLI_ENABLED
|
||||
#include <brotli/decode.h>
|
||||
#endif
|
||||
|
||||
int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
case MODE_BROTLI: {
|
||||
ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
|
||||
} break;
|
||||
case MODE_FASTLZ: {
|
||||
if (p_src_size < 16) {
|
||||
uint8_t src[16];
|
||||
memset(&src[p_src_size], 0, 16 - p_src_size);
|
||||
memcpy(src, p_src, p_src_size);
|
||||
return fastlz_compress(src, 16, p_dst);
|
||||
} else {
|
||||
return fastlz_compress(p_src, p_src_size, p_dst);
|
||||
}
|
||||
|
||||
} break;
|
||||
case MODE_DEFLATE:
|
||||
case MODE_GZIP: {
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = zipio_alloc;
|
||||
strm.zfree = zipio_free;
|
||||
strm.opaque = Z_NULL;
|
||||
int level = p_mode == MODE_DEFLATE ? zlib_level : gzip_level;
|
||||
int err = deflateInit2(&strm, level, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
|
||||
if (err != Z_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
strm.avail_in = p_src_size;
|
||||
int aout = deflateBound(&strm, p_src_size);
|
||||
strm.avail_out = aout;
|
||||
strm.next_in = (Bytef *)p_src;
|
||||
strm.next_out = p_dst;
|
||||
deflate(&strm, Z_FINISH);
|
||||
aout = aout - strm.avail_out;
|
||||
deflateEnd(&strm);
|
||||
return aout;
|
||||
|
||||
} break;
|
||||
case MODE_ZSTD: {
|
||||
ZSTD_CCtx *cctx = ZSTD_createCCtx();
|
||||
ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, zstd_level);
|
||||
if (zstd_long_distance_matching) {
|
||||
ZSTD_CCtx_setParameter(cctx, ZSTD_c_enableLongDistanceMatching, 1);
|
||||
ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, zstd_window_log_size);
|
||||
}
|
||||
int max_dst_size = get_max_compressed_buffer_size(p_src_size, MODE_ZSTD);
|
||||
int ret = ZSTD_compressCCtx(cctx, p_dst, max_dst_size, p_src, p_src_size, zstd_level);
|
||||
ZSTD_freeCCtx(cctx);
|
||||
return ret;
|
||||
} break;
|
||||
}
|
||||
|
||||
ERR_FAIL_V(-1);
|
||||
}
|
||||
|
||||
int Compression::get_max_compressed_buffer_size(int p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
case MODE_BROTLI: {
|
||||
ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
|
||||
} break;
|
||||
case MODE_FASTLZ: {
|
||||
int ss = p_src_size + p_src_size * 6 / 100;
|
||||
if (ss < 66) {
|
||||
ss = 66;
|
||||
}
|
||||
return ss;
|
||||
|
||||
} break;
|
||||
case MODE_DEFLATE:
|
||||
case MODE_GZIP: {
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = zipio_alloc;
|
||||
strm.zfree = zipio_free;
|
||||
strm.opaque = Z_NULL;
|
||||
int err = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
|
||||
if (err != Z_OK) {
|
||||
return -1;
|
||||
}
|
||||
int aout = deflateBound(&strm, p_src_size);
|
||||
deflateEnd(&strm);
|
||||
return aout;
|
||||
} break;
|
||||
case MODE_ZSTD: {
|
||||
return ZSTD_compressBound(p_src_size);
|
||||
} break;
|
||||
}
|
||||
|
||||
ERR_FAIL_V(-1);
|
||||
}
|
||||
|
||||
int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p_src, int p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
case MODE_BROTLI: {
|
||||
#ifdef BROTLI_ENABLED
|
||||
size_t ret_size = p_dst_max_size;
|
||||
BrotliDecoderResult res = BrotliDecoderDecompress(p_src_size, p_src, &ret_size, p_dst);
|
||||
ERR_FAIL_COND_V(res != BROTLI_DECODER_RESULT_SUCCESS, -1);
|
||||
return ret_size;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(-1, "Godot was compiled without brotli support.");
|
||||
#endif
|
||||
} break;
|
||||
case MODE_FASTLZ: {
|
||||
int ret_size = 0;
|
||||
|
||||
if (p_dst_max_size < 16) {
|
||||
uint8_t dst[16];
|
||||
fastlz_decompress(p_src, p_src_size, dst, 16);
|
||||
memcpy(p_dst, dst, p_dst_max_size);
|
||||
ret_size = p_dst_max_size;
|
||||
} else {
|
||||
ret_size = fastlz_decompress(p_src, p_src_size, p_dst, p_dst_max_size);
|
||||
}
|
||||
return ret_size;
|
||||
} break;
|
||||
case MODE_DEFLATE:
|
||||
case MODE_GZIP: {
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = zipio_alloc;
|
||||
strm.zfree = zipio_free;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
int err = inflateInit2(&strm, window_bits);
|
||||
ERR_FAIL_COND_V(err != Z_OK, -1);
|
||||
|
||||
strm.avail_in = p_src_size;
|
||||
strm.avail_out = p_dst_max_size;
|
||||
strm.next_in = (Bytef *)p_src;
|
||||
strm.next_out = p_dst;
|
||||
|
||||
err = inflate(&strm, Z_FINISH);
|
||||
int total = strm.total_out;
|
||||
inflateEnd(&strm);
|
||||
ERR_FAIL_COND_V(err != Z_STREAM_END, -1);
|
||||
return total;
|
||||
} break;
|
||||
case MODE_ZSTD: {
|
||||
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
||||
if (zstd_long_distance_matching) {
|
||||
ZSTD_DCtx_setParameter(dctx, ZSTD_d_windowLogMax, zstd_window_log_size);
|
||||
}
|
||||
int ret = ZSTD_decompressDCtx(dctx, p_dst, p_dst_max_size, p_src, p_src_size);
|
||||
ZSTD_freeDCtx(dctx);
|
||||
return ret;
|
||||
} break;
|
||||
}
|
||||
|
||||
ERR_FAIL_V(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
This will handle both Gzip and Deflate streams. It will automatically allocate the output buffer into the provided p_dst_vect Vector.
|
||||
This is required for compressed data whose final uncompressed size is unknown, as is the case for HTTP response bodies.
|
||||
This is much slower however than using Compression::decompress because it may result in multiple full copies of the output buffer.
|
||||
*/
|
||||
int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_size, const uint8_t *p_src, int p_src_size, Mode p_mode) {
|
||||
uint8_t *dst = nullptr;
|
||||
int out_mark = 0;
|
||||
|
||||
ERR_FAIL_COND_V(p_src_size <= 0, Z_DATA_ERROR);
|
||||
|
||||
if (p_mode == MODE_BROTLI) {
|
||||
#ifdef BROTLI_ENABLED
|
||||
BrotliDecoderResult ret;
|
||||
BrotliDecoderState *state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
|
||||
ERR_FAIL_NULL_V(state, Z_DATA_ERROR);
|
||||
|
||||
// Setup the stream inputs.
|
||||
const uint8_t *next_in = p_src;
|
||||
size_t avail_in = p_src_size;
|
||||
uint8_t *next_out = nullptr;
|
||||
size_t avail_out = 0;
|
||||
size_t total_out = 0;
|
||||
|
||||
// Ensure the destination buffer is empty.
|
||||
p_dst_vect->clear();
|
||||
|
||||
// Decompress until stream ends or end of file.
|
||||
do {
|
||||
// Add another chunk size to the output buffer.
|
||||
// This forces a copy of the whole buffer.
|
||||
p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
|
||||
// Get pointer to the actual output buffer.
|
||||
dst = p_dst_vect->ptrw();
|
||||
|
||||
// Set the stream to the new output stream.
|
||||
// Since it was copied, we need to reset the stream to the new buffer.
|
||||
next_out = &(dst[out_mark]);
|
||||
avail_out += gzip_chunk;
|
||||
|
||||
ret = BrotliDecoderDecompressStream(state, &avail_in, &next_in, &avail_out, &next_out, &total_out);
|
||||
if (ret == BROTLI_DECODER_RESULT_ERROR) {
|
||||
WARN_PRINT(BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state)));
|
||||
BrotliDecoderDestroyInstance(state);
|
||||
p_dst_vect->clear();
|
||||
return Z_DATA_ERROR;
|
||||
}
|
||||
|
||||
out_mark += gzip_chunk - avail_out;
|
||||
|
||||
// Enforce max output size.
|
||||
if (p_max_dst_size > -1 && total_out > (uint64_t)p_max_dst_size) {
|
||||
BrotliDecoderDestroyInstance(state);
|
||||
p_dst_vect->clear();
|
||||
return Z_BUF_ERROR;
|
||||
}
|
||||
} while (ret != BROTLI_DECODER_RESULT_SUCCESS);
|
||||
|
||||
// If all done successfully, resize the output if it's larger than the actual output.
|
||||
if ((unsigned long)p_dst_vect->size() > total_out) {
|
||||
p_dst_vect->resize(total_out);
|
||||
}
|
||||
|
||||
// Clean up and return.
|
||||
BrotliDecoderDestroyInstance(state);
|
||||
return Z_OK;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(Z_ERRNO, "Godot was compiled without brotli support.");
|
||||
#endif
|
||||
} else {
|
||||
// This function only supports GZip and Deflate.
|
||||
ERR_FAIL_COND_V(p_mode != MODE_DEFLATE && p_mode != MODE_GZIP, Z_ERRNO);
|
||||
|
||||
int ret;
|
||||
z_stream strm;
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
|
||||
// Initialize the stream.
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
|
||||
int err = inflateInit2(&strm, window_bits);
|
||||
ERR_FAIL_COND_V(err != Z_OK, -1);
|
||||
|
||||
// Setup the stream inputs.
|
||||
strm.next_in = (Bytef *)p_src;
|
||||
strm.avail_in = p_src_size;
|
||||
|
||||
// Ensure the destination buffer is empty.
|
||||
p_dst_vect->clear();
|
||||
|
||||
// Decompress until deflate stream ends or end of file.
|
||||
do {
|
||||
// Add another chunk size to the output buffer.
|
||||
// This forces a copy of the whole buffer.
|
||||
p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
|
||||
// Get pointer to the actual output buffer.
|
||||
dst = p_dst_vect->ptrw();
|
||||
|
||||
// Set the stream to the new output stream.
|
||||
// Since it was copied, we need to reset the stream to the new buffer.
|
||||
strm.next_out = &(dst[out_mark]);
|
||||
strm.avail_out = gzip_chunk;
|
||||
|
||||
// Run inflate() on input until output buffer is full and needs to be resized or input runs out.
|
||||
do {
|
||||
ret = inflate(&strm, Z_SYNC_FLUSH);
|
||||
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
ret = Z_DATA_ERROR;
|
||||
[[fallthrough]];
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
case Z_STREAM_ERROR:
|
||||
case Z_BUF_ERROR:
|
||||
if (strm.msg) {
|
||||
WARN_PRINT(strm.msg);
|
||||
}
|
||||
(void)inflateEnd(&strm);
|
||||
p_dst_vect->clear();
|
||||
return ret;
|
||||
}
|
||||
} while (strm.avail_out > 0 && strm.avail_in > 0);
|
||||
|
||||
out_mark += gzip_chunk;
|
||||
|
||||
// Enforce max output size.
|
||||
if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) {
|
||||
(void)inflateEnd(&strm);
|
||||
p_dst_vect->clear();
|
||||
return Z_BUF_ERROR;
|
||||
}
|
||||
} while (ret != Z_STREAM_END);
|
||||
|
||||
// If all done successfully, resize the output if it's larger than the actual output.
|
||||
if ((unsigned long)p_dst_vect->size() > strm.total_out) {
|
||||
p_dst_vect->resize(strm.total_out);
|
||||
}
|
||||
|
||||
// Clean up and return.
|
||||
(void)inflateEnd(&strm);
|
||||
return Z_OK;
|
||||
}
|
||||
}
|
||||
|
||||
int Compression::zlib_level = Z_DEFAULT_COMPRESSION;
|
||||
int Compression::gzip_level = Z_DEFAULT_COMPRESSION;
|
||||
int Compression::zstd_level = 3;
|
||||
bool Compression::zstd_long_distance_matching = false;
|
||||
int Compression::zstd_window_log_size = 27; // ZSTD_WINDOWLOG_LIMIT_DEFAULT
|
||||
int Compression::gzip_chunk = 16384;
|
||||
60
engine/core/io/compression.h
Normal file
60
engine/core/io/compression.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**************************************************************************/
|
||||
/* compression.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef COMPRESSION_H
|
||||
#define COMPRESSION_H
|
||||
|
||||
#include "core/templates/vector.h"
|
||||
#include "core/typedefs.h"
|
||||
|
||||
class Compression {
|
||||
public:
|
||||
static int zlib_level;
|
||||
static int gzip_level;
|
||||
static int zstd_level;
|
||||
static bool zstd_long_distance_matching;
|
||||
static int zstd_window_log_size;
|
||||
static int gzip_chunk;
|
||||
|
||||
enum Mode {
|
||||
MODE_FASTLZ,
|
||||
MODE_DEFLATE,
|
||||
MODE_ZSTD,
|
||||
MODE_GZIP,
|
||||
MODE_BROTLI
|
||||
};
|
||||
|
||||
static int compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode = MODE_ZSTD);
|
||||
static int get_max_compressed_buffer_size(int p_src_size, Mode p_mode = MODE_ZSTD);
|
||||
static int decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p_src, int p_src_size, Mode p_mode = MODE_ZSTD);
|
||||
static int decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_size, const uint8_t *p_src, int p_src_size, Mode p_mode);
|
||||
};
|
||||
|
||||
#endif // COMPRESSION_H
|
||||
350
engine/core/io/config_file.cpp
Normal file
350
engine/core/io/config_file.cpp
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
/**************************************************************************/
|
||||
/* config_file.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 "config_file.h"
|
||||
|
||||
#include "core/io/file_access_encrypted.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/string/string_builder.h"
|
||||
#include "core/variant/variant_parser.h"
|
||||
|
||||
PackedStringArray ConfigFile::_get_sections() const {
|
||||
List<String> s;
|
||||
get_sections(&s);
|
||||
PackedStringArray arr;
|
||||
arr.resize(s.size());
|
||||
int idx = 0;
|
||||
for (const String &E : s) {
|
||||
arr.set(idx++, E);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
PackedStringArray ConfigFile::_get_section_keys(const String &p_section) const {
|
||||
List<String> s;
|
||||
get_section_keys(p_section, &s);
|
||||
PackedStringArray arr;
|
||||
arr.resize(s.size());
|
||||
int idx = 0;
|
||||
for (const String &E : s) {
|
||||
arr.set(idx++, E);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
void ConfigFile::set_value(const String &p_section, const String &p_key, const Variant &p_value) {
|
||||
if (p_value.get_type() == Variant::NIL) { // Erase key.
|
||||
if (!values.has(p_section)) {
|
||||
return;
|
||||
}
|
||||
|
||||
values[p_section].erase(p_key);
|
||||
if (values[p_section].is_empty()) {
|
||||
values.erase(p_section);
|
||||
}
|
||||
} else {
|
||||
if (!values.has(p_section)) {
|
||||
// Insert section-less keys at the beginning.
|
||||
values.insert(p_section, HashMap<String, Variant>(), p_section.is_empty());
|
||||
}
|
||||
|
||||
values[p_section][p_key] = p_value;
|
||||
}
|
||||
}
|
||||
|
||||
Variant ConfigFile::get_value(const String &p_section, const String &p_key, const Variant &p_default) const {
|
||||
if (!values.has(p_section) || !values[p_section].has(p_key)) {
|
||||
ERR_FAIL_COND_V_MSG(p_default.get_type() == Variant::NIL, Variant(),
|
||||
vformat("Couldn't find the given section \"%s\" and key \"%s\", and no default was given.", p_section, p_key));
|
||||
return p_default;
|
||||
}
|
||||
|
||||
return values[p_section][p_key];
|
||||
}
|
||||
|
||||
bool ConfigFile::has_section(const String &p_section) const {
|
||||
return values.has(p_section);
|
||||
}
|
||||
|
||||
bool ConfigFile::has_section_key(const String &p_section, const String &p_key) const {
|
||||
if (!values.has(p_section)) {
|
||||
return false;
|
||||
}
|
||||
return values[p_section].has(p_key);
|
||||
}
|
||||
|
||||
void ConfigFile::get_sections(List<String> *r_sections) const {
|
||||
for (const KeyValue<String, HashMap<String, Variant>> &E : values) {
|
||||
r_sections->push_back(E.key);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigFile::get_section_keys(const String &p_section, List<String> *r_keys) const {
|
||||
ERR_FAIL_COND_MSG(!values.has(p_section), vformat("Cannot get keys from nonexistent section \"%s\".", p_section));
|
||||
|
||||
for (const KeyValue<String, Variant> &E : values[p_section]) {
|
||||
r_keys->push_back(E.key);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigFile::erase_section(const String &p_section) {
|
||||
ERR_FAIL_COND_MSG(!values.has(p_section), vformat("Cannot erase nonexistent section \"%s\".", p_section));
|
||||
values.erase(p_section);
|
||||
}
|
||||
|
||||
void ConfigFile::erase_section_key(const String &p_section, const String &p_key) {
|
||||
ERR_FAIL_COND_MSG(!values.has(p_section), vformat("Cannot erase key \"%s\" from nonexistent section \"%s\".", p_key, p_section));
|
||||
ERR_FAIL_COND_MSG(!values[p_section].has(p_key), vformat("Cannot erase nonexistent key \"%s\" from section \"%s\".", p_key, p_section));
|
||||
|
||||
values[p_section].erase(p_key);
|
||||
if (values[p_section].is_empty()) {
|
||||
values.erase(p_section);
|
||||
}
|
||||
}
|
||||
|
||||
String ConfigFile::encode_to_text() const {
|
||||
StringBuilder sb;
|
||||
bool first = true;
|
||||
for (const KeyValue<String, HashMap<String, Variant>> &E : values) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append("\n");
|
||||
}
|
||||
if (!E.key.is_empty()) {
|
||||
sb.append("[" + E.key + "]\n\n");
|
||||
}
|
||||
|
||||
for (const KeyValue<String, Variant> &F : E.value) {
|
||||
String vstr;
|
||||
VariantWriter::write_to_string(F.value, vstr);
|
||||
sb.append(F.key.property_name_encode() + "=" + vstr + "\n");
|
||||
}
|
||||
}
|
||||
return sb.as_string();
|
||||
}
|
||||
|
||||
Error ConfigFile::save(const String &p_path) {
|
||||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return _internal_save(file);
|
||||
}
|
||||
|
||||
Error ConfigFile::save_encrypted(const String &p_path, const Vector<uint8_t> &p_key) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
err = fae->open_and_parse(f, p_key, FileAccessEncrypted::MODE_WRITE_AES256);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
return _internal_save(fae);
|
||||
}
|
||||
|
||||
Error ConfigFile::save_encrypted_pass(const String &p_path, const String &p_pass) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
err = fae->open_and_parse_password(f, p_pass, FileAccessEncrypted::MODE_WRITE_AES256);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return _internal_save(fae);
|
||||
}
|
||||
|
||||
Error ConfigFile::_internal_save(Ref<FileAccess> file) {
|
||||
bool first = true;
|
||||
for (const KeyValue<String, HashMap<String, Variant>> &E : values) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
file->store_string("\n");
|
||||
}
|
||||
if (!E.key.is_empty()) {
|
||||
file->store_string("[" + E.key.replace("]", "\\]") + "]\n\n");
|
||||
}
|
||||
|
||||
for (const KeyValue<String, Variant> &F : E.value) {
|
||||
String vstr;
|
||||
VariantWriter::write_to_string(F.value, vstr);
|
||||
file->store_string(F.key.property_name_encode() + "=" + vstr + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ConfigFile::load(const String &p_path) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
|
||||
if (f.is_null()) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return _internal_load(p_path, f);
|
||||
}
|
||||
|
||||
Error ConfigFile::load_encrypted(const String &p_path, const Vector<uint8_t> &p_key) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
err = fae->open_and_parse(f, p_key, FileAccessEncrypted::MODE_READ);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
return _internal_load(p_path, fae);
|
||||
}
|
||||
|
||||
Error ConfigFile::load_encrypted_pass(const String &p_path, const String &p_pass) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
err = fae->open_and_parse_password(f, p_pass, FileAccessEncrypted::MODE_READ);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return _internal_load(p_path, fae);
|
||||
}
|
||||
|
||||
Error ConfigFile::_internal_load(const String &p_path, Ref<FileAccess> f) {
|
||||
VariantParser::StreamFile stream;
|
||||
stream.f = f;
|
||||
|
||||
Error err = _parse(p_path, &stream);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
Error ConfigFile::parse(const String &p_data) {
|
||||
VariantParser::StreamString stream;
|
||||
stream.s = p_data;
|
||||
return _parse("<string>", &stream);
|
||||
}
|
||||
|
||||
Error ConfigFile::_parse(const String &p_path, VariantParser::Stream *p_stream) {
|
||||
String assign;
|
||||
Variant value;
|
||||
VariantParser::Tag next_tag;
|
||||
|
||||
int lines = 0;
|
||||
String error_text;
|
||||
|
||||
String section;
|
||||
|
||||
while (true) {
|
||||
assign = Variant();
|
||||
next_tag.fields.clear();
|
||||
next_tag.name = String();
|
||||
|
||||
Error err = VariantParser::parse_tag_assign_eof(p_stream, lines, error_text, next_tag, assign, value, nullptr, true);
|
||||
if (err == ERR_FILE_EOF) {
|
||||
return OK;
|
||||
} else if (err != OK) {
|
||||
ERR_PRINT(vformat("ConfigFile parse error at %s:%d: %s.", p_path, lines, error_text));
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!assign.is_empty()) {
|
||||
set_value(section, assign, value);
|
||||
} else if (!next_tag.name.is_empty()) {
|
||||
section = next_tag.name.replace("\\]", "]");
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void ConfigFile::clear() {
|
||||
values.clear();
|
||||
}
|
||||
|
||||
void ConfigFile::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_value", "section", "key", "value"), &ConfigFile::set_value);
|
||||
ClassDB::bind_method(D_METHOD("get_value", "section", "key", "default"), &ConfigFile::get_value, DEFVAL(Variant()));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_section", "section"), &ConfigFile::has_section);
|
||||
ClassDB::bind_method(D_METHOD("has_section_key", "section", "key"), &ConfigFile::has_section_key);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_sections"), &ConfigFile::_get_sections);
|
||||
ClassDB::bind_method(D_METHOD("get_section_keys", "section"), &ConfigFile::_get_section_keys);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("erase_section", "section"), &ConfigFile::erase_section);
|
||||
ClassDB::bind_method(D_METHOD("erase_section_key", "section", "key"), &ConfigFile::erase_section_key);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("load", "path"), &ConfigFile::load);
|
||||
ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse);
|
||||
ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("encode_to_text"), &ConfigFile::encode_to_text);
|
||||
|
||||
BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted);
|
||||
ClassDB::bind_method(D_METHOD("load_encrypted_pass", "path", "password"), &ConfigFile::load_encrypted_pass);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("save_encrypted", "path", "key"), &ConfigFile::save_encrypted);
|
||||
ClassDB::bind_method(D_METHOD("save_encrypted_pass", "path", "password"), &ConfigFile::save_encrypted_pass);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("clear"), &ConfigFile::clear);
|
||||
}
|
||||
82
engine/core/io/config_file.h
Normal file
82
engine/core/io/config_file.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/**************************************************************************/
|
||||
/* config_file.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef CONFIG_FILE_H
|
||||
#define CONFIG_FILE_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/variant/variant_parser.h"
|
||||
|
||||
class ConfigFile : public RefCounted {
|
||||
GDCLASS(ConfigFile, RefCounted);
|
||||
|
||||
HashMap<String, HashMap<String, Variant>> values;
|
||||
|
||||
PackedStringArray _get_sections() const;
|
||||
PackedStringArray _get_section_keys(const String &p_section) const;
|
||||
Error _internal_load(const String &p_path, Ref<FileAccess> f);
|
||||
Error _internal_save(Ref<FileAccess> file);
|
||||
|
||||
Error _parse(const String &p_path, VariantParser::Stream *p_stream);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_value(const String &p_section, const String &p_key, const Variant &p_value);
|
||||
Variant get_value(const String &p_section, const String &p_key, const Variant &p_default = Variant()) const;
|
||||
|
||||
bool has_section(const String &p_section) const;
|
||||
bool has_section_key(const String &p_section, const String &p_key) const;
|
||||
|
||||
void get_sections(List<String> *r_sections) const;
|
||||
void get_section_keys(const String &p_section, List<String> *r_keys) const;
|
||||
|
||||
void erase_section(const String &p_section);
|
||||
void erase_section_key(const String &p_section, const String &p_key);
|
||||
|
||||
Error save(const String &p_path);
|
||||
Error load(const String &p_path);
|
||||
Error parse(const String &p_data);
|
||||
|
||||
String encode_to_text() const; // used by exporter
|
||||
|
||||
void clear();
|
||||
|
||||
Error load_encrypted(const String &p_path, const Vector<uint8_t> &p_key);
|
||||
Error load_encrypted_pass(const String &p_path, const String &p_pass);
|
||||
|
||||
Error save_encrypted(const String &p_path, const Vector<uint8_t> &p_key);
|
||||
Error save_encrypted_pass(const String &p_path, const String &p_pass);
|
||||
};
|
||||
|
||||
#endif // CONFIG_FILE_H
|
||||
600
engine/core/io/dir_access.cpp
Normal file
600
engine/core/io/dir_access.cpp
Normal file
|
|
@ -0,0 +1,600 @@
|
|||
/**************************************************************************/
|
||||
/* dir_access.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 "dir_access.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
thread_local Error DirAccess::last_dir_open_error = OK;
|
||||
|
||||
String DirAccess::_get_root_path() const {
|
||||
switch (_access_type) {
|
||||
case ACCESS_RESOURCES:
|
||||
return ProjectSettings::get_singleton()->get_resource_path();
|
||||
case ACCESS_USERDATA:
|
||||
return OS::get_singleton()->get_user_data_dir();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String DirAccess::_get_root_string() const {
|
||||
switch (_access_type) {
|
||||
case ACCESS_RESOURCES:
|
||||
return "res://";
|
||||
case ACCESS_USERDATA:
|
||||
return "user://";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
int DirAccess::get_current_drive() {
|
||||
String path = get_current_dir().to_lower();
|
||||
for (int i = 0; i < get_drive_count(); i++) {
|
||||
String d = get_drive(i).to_lower();
|
||||
if (path.begins_with(d)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DirAccess::drives_are_shortcuts() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static Error _erase_recursive(DirAccess *da) {
|
||||
List<String> dirs;
|
||||
List<String> files;
|
||||
|
||||
da->list_dir_begin();
|
||||
String n = da->get_next();
|
||||
while (!n.is_empty()) {
|
||||
if (n != "." && n != "..") {
|
||||
if (da->current_is_dir() && !da->is_link(n)) {
|
||||
dirs.push_back(n);
|
||||
} else {
|
||||
files.push_back(n);
|
||||
}
|
||||
}
|
||||
|
||||
n = da->get_next();
|
||||
}
|
||||
|
||||
da->list_dir_end();
|
||||
|
||||
for (const String &E : dirs) {
|
||||
Error err = da->change_dir(E);
|
||||
if (err == OK) {
|
||||
err = _erase_recursive(da);
|
||||
if (err) {
|
||||
da->change_dir("..");
|
||||
return err;
|
||||
}
|
||||
err = da->change_dir("..");
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
err = da->remove(da->get_current_dir().path_join(E));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
for (const String &E : files) {
|
||||
Error err = da->remove(da->get_current_dir().path_join(E));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error DirAccess::erase_contents_recursive() {
|
||||
return _erase_recursive(this);
|
||||
}
|
||||
|
||||
Error DirAccess::make_dir_recursive(const String &p_dir) {
|
||||
if (p_dir.length() < 1) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
String full_dir;
|
||||
|
||||
if (p_dir.is_relative_path()) {
|
||||
//append current
|
||||
full_dir = get_current_dir().path_join(p_dir);
|
||||
|
||||
} else {
|
||||
full_dir = p_dir;
|
||||
}
|
||||
|
||||
full_dir = full_dir.replace("\\", "/");
|
||||
|
||||
String base;
|
||||
|
||||
if (full_dir.begins_with("res://")) {
|
||||
base = "res://";
|
||||
} else if (full_dir.begins_with("user://")) {
|
||||
base = "user://";
|
||||
} else if (full_dir.is_network_share_path()) {
|
||||
int pos = full_dir.find("/", 2);
|
||||
ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER);
|
||||
pos = full_dir.find("/", pos + 1);
|
||||
ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER);
|
||||
base = full_dir.substr(0, pos + 1);
|
||||
} else if (full_dir.begins_with("/")) {
|
||||
base = "/";
|
||||
} else if (full_dir.contains(":/")) {
|
||||
base = full_dir.substr(0, full_dir.find(":/") + 2);
|
||||
} else {
|
||||
ERR_FAIL_V(ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
full_dir = full_dir.replace_first(base, "").simplify_path();
|
||||
|
||||
Vector<String> subdirs = full_dir.split("/");
|
||||
|
||||
String curpath = base;
|
||||
for (int i = 0; i < subdirs.size(); i++) {
|
||||
curpath = curpath.path_join(subdirs[i]);
|
||||
Error err = make_dir(curpath);
|
||||
if (err != OK && err != ERR_ALREADY_EXISTS) {
|
||||
ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath);
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
DirAccess::AccessType DirAccess::get_access_type() const {
|
||||
return _access_type;
|
||||
}
|
||||
|
||||
String DirAccess::fix_path(const String &p_path) const {
|
||||
switch (_access_type) {
|
||||
case ACCESS_RESOURCES: {
|
||||
if (ProjectSettings::get_singleton()) {
|
||||
if (p_path.begins_with("res://")) {
|
||||
String resource_path = ProjectSettings::get_singleton()->get_resource_path();
|
||||
if (!resource_path.is_empty()) {
|
||||
return p_path.replace_first("res:/", resource_path);
|
||||
}
|
||||
return p_path.replace_first("res://", "");
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
case ACCESS_USERDATA: {
|
||||
if (p_path.begins_with("user://")) {
|
||||
String data_dir = OS::get_singleton()->get_user_data_dir();
|
||||
if (!data_dir.is_empty()) {
|
||||
return p_path.replace_first("user:/", data_dir);
|
||||
}
|
||||
return p_path.replace_first("user://", "");
|
||||
}
|
||||
|
||||
} break;
|
||||
case ACCESS_FILESYSTEM: {
|
||||
return p_path;
|
||||
} break;
|
||||
case ACCESS_MAX:
|
||||
break; // Can't happen, but silences warning
|
||||
}
|
||||
|
||||
return p_path;
|
||||
}
|
||||
|
||||
DirAccess::CreateFunc DirAccess::create_func[ACCESS_MAX] = { nullptr, nullptr, nullptr };
|
||||
|
||||
Ref<DirAccess> DirAccess::create_for_path(const String &p_path) {
|
||||
Ref<DirAccess> da;
|
||||
if (p_path.begins_with("res://")) {
|
||||
da = create(ACCESS_RESOURCES);
|
||||
} else if (p_path.begins_with("user://")) {
|
||||
da = create(ACCESS_USERDATA);
|
||||
} else {
|
||||
da = create(ACCESS_FILESYSTEM);
|
||||
}
|
||||
|
||||
return da;
|
||||
}
|
||||
|
||||
Ref<DirAccess> DirAccess::open(const String &p_path, Error *r_error) {
|
||||
Ref<DirAccess> da = create_for_path(p_path);
|
||||
ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, "Cannot create DirAccess for path '" + p_path + "'.");
|
||||
Error err = da->change_dir(p_path);
|
||||
if (r_error) {
|
||||
*r_error = err;
|
||||
}
|
||||
if (err != OK) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return da;
|
||||
}
|
||||
|
||||
Ref<DirAccess> DirAccess::_open(const String &p_path) {
|
||||
Error err = OK;
|
||||
Ref<DirAccess> da = open(p_path, &err);
|
||||
last_dir_open_error = err;
|
||||
if (err) {
|
||||
return Ref<DirAccess>();
|
||||
}
|
||||
return da;
|
||||
}
|
||||
|
||||
int DirAccess::_get_drive_count() {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
return d->get_drive_count();
|
||||
}
|
||||
|
||||
String DirAccess::get_drive_name(int p_idx) {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
return d->get_drive(p_idx);
|
||||
}
|
||||
|
||||
Error DirAccess::make_dir_absolute(const String &p_dir) {
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(p_dir);
|
||||
return d->make_dir(p_dir);
|
||||
}
|
||||
|
||||
Error DirAccess::make_dir_recursive_absolute(const String &p_dir) {
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(p_dir);
|
||||
return d->make_dir_recursive(p_dir);
|
||||
}
|
||||
|
||||
bool DirAccess::dir_exists_absolute(const String &p_dir) {
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(p_dir);
|
||||
return d->dir_exists(p_dir);
|
||||
}
|
||||
|
||||
Error DirAccess::copy_absolute(const String &p_from, const String &p_to, int p_chmod_flags) {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
// Support copying from res:// to user:// etc.
|
||||
String from = ProjectSettings::get_singleton()->globalize_path(p_from);
|
||||
String to = ProjectSettings::get_singleton()->globalize_path(p_to);
|
||||
return d->copy(from, to, p_chmod_flags);
|
||||
}
|
||||
|
||||
Error DirAccess::rename_absolute(const String &p_from, const String &p_to) {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
String from = ProjectSettings::get_singleton()->globalize_path(p_from);
|
||||
String to = ProjectSettings::get_singleton()->globalize_path(p_to);
|
||||
return d->rename(from, to);
|
||||
}
|
||||
|
||||
Error DirAccess::remove_absolute(const String &p_path) {
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(p_path);
|
||||
return d->remove(p_path);
|
||||
}
|
||||
|
||||
Ref<DirAccess> DirAccess::create(AccessType p_access) {
|
||||
Ref<DirAccess> da = create_func[p_access] ? create_func[p_access]() : nullptr;
|
||||
if (da.is_valid()) {
|
||||
da->_access_type = p_access;
|
||||
|
||||
// for ACCESS_RESOURCES and ACCESS_FILESYSTEM, current_dir already defaults to where game was started
|
||||
// in case current directory is force changed elsewhere for ACCESS_RESOURCES
|
||||
if (p_access == ACCESS_RESOURCES) {
|
||||
da->change_dir("res://");
|
||||
} else if (p_access == ACCESS_USERDATA) {
|
||||
da->change_dir("user://");
|
||||
}
|
||||
}
|
||||
|
||||
return da;
|
||||
}
|
||||
|
||||
Error DirAccess::get_open_error() {
|
||||
return last_dir_open_error;
|
||||
}
|
||||
|
||||
String DirAccess::get_full_path(const String &p_path, AccessType p_access) {
|
||||
Ref<DirAccess> d = DirAccess::create(p_access);
|
||||
if (d.is_null()) {
|
||||
return p_path;
|
||||
}
|
||||
|
||||
d->change_dir(p_path);
|
||||
String full = d->get_current_dir();
|
||||
return full;
|
||||
}
|
||||
|
||||
Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flags) {
|
||||
ERR_FAIL_COND_V_MSG(p_from == p_to, ERR_INVALID_PARAMETER, "Source and destination path are equal.");
|
||||
|
||||
//printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data());
|
||||
Error err;
|
||||
{
|
||||
Ref<FileAccess> fsrc = FileAccess::open(p_from, FileAccess::READ, &err);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_from);
|
||||
|
||||
Ref<FileAccess> fdst = FileAccess::open(p_to, FileAccess::WRITE, &err);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_to);
|
||||
|
||||
const size_t copy_buffer_limit = 65536; // 64 KB
|
||||
|
||||
fsrc->seek_end(0);
|
||||
uint64_t size = fsrc->get_position();
|
||||
fsrc->seek(0);
|
||||
err = OK;
|
||||
size_t buffer_size = MIN(size * sizeof(uint8_t), copy_buffer_limit);
|
||||
LocalVector<uint8_t> buffer;
|
||||
buffer.resize(buffer_size);
|
||||
while (size > 0) {
|
||||
if (fsrc->get_error() != OK) {
|
||||
err = fsrc->get_error();
|
||||
break;
|
||||
}
|
||||
if (fdst->get_error() != OK) {
|
||||
err = fdst->get_error();
|
||||
break;
|
||||
}
|
||||
|
||||
int bytes_read = fsrc->get_buffer(buffer.ptr(), buffer_size);
|
||||
if (bytes_read <= 0) {
|
||||
err = FAILED;
|
||||
break;
|
||||
}
|
||||
fdst->store_buffer(buffer.ptr(), bytes_read);
|
||||
|
||||
size -= bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
if (err == OK && p_chmod_flags != -1) {
|
||||
err = FileAccess::set_unix_permissions(p_to, p_chmod_flags);
|
||||
// If running on a platform with no chmod support (i.e., Windows), don't fail
|
||||
if (err == ERR_UNAVAILABLE) {
|
||||
err = OK;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// Changes dir for the current scope, returning back to the original dir
|
||||
// when scope exits
|
||||
class DirChanger {
|
||||
DirAccess *da;
|
||||
String original_dir;
|
||||
|
||||
public:
|
||||
DirChanger(DirAccess *p_da, const String &p_dir) :
|
||||
da(p_da),
|
||||
original_dir(p_da->get_current_dir()) {
|
||||
p_da->change_dir(p_dir);
|
||||
}
|
||||
|
||||
~DirChanger() {
|
||||
da->change_dir(original_dir);
|
||||
}
|
||||
};
|
||||
|
||||
Error DirAccess::_copy_dir(Ref<DirAccess> &p_target_da, const String &p_to, int p_chmod_flags, bool p_copy_links) {
|
||||
List<String> dirs;
|
||||
|
||||
String curdir = get_current_dir();
|
||||
list_dir_begin();
|
||||
String n = get_next();
|
||||
while (!n.is_empty()) {
|
||||
if (n != "." && n != "..") {
|
||||
if (p_copy_links && is_link(get_current_dir().path_join(n))) {
|
||||
create_link(read_link(get_current_dir().path_join(n)), p_to + n);
|
||||
} else if (current_is_dir()) {
|
||||
dirs.push_back(n);
|
||||
} else {
|
||||
const String &rel_path = n;
|
||||
if (!n.is_relative_path()) {
|
||||
list_dir_end();
|
||||
return ERR_BUG;
|
||||
}
|
||||
Error err = copy(get_current_dir().path_join(n), p_to + rel_path, p_chmod_flags);
|
||||
if (err) {
|
||||
list_dir_end();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n = get_next();
|
||||
}
|
||||
|
||||
list_dir_end();
|
||||
|
||||
for (const String &rel_path : dirs) {
|
||||
String target_dir = p_to + rel_path;
|
||||
if (!p_target_da->dir_exists(target_dir)) {
|
||||
Error err = p_target_da->make_dir(target_dir);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'.");
|
||||
}
|
||||
|
||||
Error err = change_dir(rel_path);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + rel_path + "'.");
|
||||
|
||||
err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links);
|
||||
if (err) {
|
||||
change_dir("..");
|
||||
ERR_FAIL_V_MSG(err, "Failed to copy recursively.");
|
||||
}
|
||||
err = change_dir("..");
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to go back.");
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error DirAccess::copy_dir(const String &p_from, String p_to, int p_chmod_flags, bool p_copy_links) {
|
||||
ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
|
||||
|
||||
Ref<DirAccess> target_da = DirAccess::create_for_path(p_to);
|
||||
ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'.");
|
||||
|
||||
if (!target_da->dir_exists(p_to)) {
|
||||
Error err = target_da->make_dir_recursive(p_to);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'.");
|
||||
}
|
||||
|
||||
if (!p_to.ends_with("/")) {
|
||||
p_to = p_to + "/";
|
||||
}
|
||||
|
||||
DirChanger dir_changer(this, p_from);
|
||||
Error err = _copy_dir(target_da, p_to, p_chmod_flags, p_copy_links);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool DirAccess::exists(const String &p_dir) {
|
||||
Ref<DirAccess> da = DirAccess::create_for_path(p_dir);
|
||||
return da->change_dir(p_dir) == OK;
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::get_files() {
|
||||
return _get_contents(false);
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::get_files_at(const String &p_path) {
|
||||
Ref<DirAccess> da = DirAccess::open(p_path);
|
||||
ERR_FAIL_COND_V_MSG(da.is_null(), PackedStringArray(), vformat("Couldn't open directory at path \"%s\".", p_path));
|
||||
return da->get_files();
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::get_directories() {
|
||||
return _get_contents(true);
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::get_directories_at(const String &p_path) {
|
||||
Ref<DirAccess> da = DirAccess::open(p_path);
|
||||
ERR_FAIL_COND_V_MSG(da.is_null(), PackedStringArray(), vformat("Couldn't open directory at path \"%s\".", p_path));
|
||||
return da->get_directories();
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::_get_contents(bool p_directories) {
|
||||
PackedStringArray ret;
|
||||
|
||||
list_dir_begin();
|
||||
String s = _get_next();
|
||||
while (!s.is_empty()) {
|
||||
if (current_is_dir() == p_directories) {
|
||||
ret.append(s);
|
||||
}
|
||||
s = _get_next();
|
||||
}
|
||||
|
||||
ret.sort();
|
||||
return ret;
|
||||
}
|
||||
|
||||
String DirAccess::_get_next() {
|
||||
String next = get_next();
|
||||
while (!next.is_empty() && ((!include_navigational && (next == "." || next == "..")) || (!include_hidden && current_is_hidden()))) {
|
||||
next = get_next();
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
void DirAccess::set_include_navigational(bool p_enable) {
|
||||
include_navigational = p_enable;
|
||||
}
|
||||
|
||||
bool DirAccess::get_include_navigational() const {
|
||||
return include_navigational;
|
||||
}
|
||||
|
||||
void DirAccess::set_include_hidden(bool p_enable) {
|
||||
include_hidden = p_enable;
|
||||
}
|
||||
|
||||
bool DirAccess::get_include_hidden() const {
|
||||
return include_hidden;
|
||||
}
|
||||
|
||||
bool DirAccess::is_case_sensitive(const String &p_path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DirAccess::_bind_methods() {
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("list_dir_begin"), &DirAccess::list_dir_begin, DEFVAL(false), DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("get_next"), &DirAccess::_get_next);
|
||||
ClassDB::bind_method(D_METHOD("current_is_dir"), &DirAccess::current_is_dir);
|
||||
ClassDB::bind_method(D_METHOD("list_dir_end"), &DirAccess::list_dir_end);
|
||||
ClassDB::bind_method(D_METHOD("get_files"), &DirAccess::get_files);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_files_at", "path"), &DirAccess::get_files_at);
|
||||
ClassDB::bind_method(D_METHOD("get_directories"), &DirAccess::get_directories);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_directories_at", "path"), &DirAccess::get_directories_at);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_drive_count"), &DirAccess::_get_drive_count);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_drive_name", "idx"), &DirAccess::get_drive_name);
|
||||
ClassDB::bind_method(D_METHOD("get_current_drive"), &DirAccess::get_current_drive);
|
||||
ClassDB::bind_method(D_METHOD("change_dir", "to_dir"), &DirAccess::change_dir);
|
||||
ClassDB::bind_method(D_METHOD("get_current_dir", "include_drive"), &DirAccess::get_current_dir, DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("make_dir", "path"), &DirAccess::make_dir);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("make_dir_absolute", "path"), &DirAccess::make_dir_absolute);
|
||||
ClassDB::bind_method(D_METHOD("make_dir_recursive", "path"), &DirAccess::make_dir_recursive);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("make_dir_recursive_absolute", "path"), &DirAccess::make_dir_recursive_absolute);
|
||||
ClassDB::bind_method(D_METHOD("file_exists", "path"), &DirAccess::file_exists);
|
||||
ClassDB::bind_method(D_METHOD("dir_exists", "path"), &DirAccess::dir_exists);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("dir_exists_absolute", "path"), &DirAccess::dir_exists_absolute);
|
||||
ClassDB::bind_method(D_METHOD("get_space_left"), &DirAccess::get_space_left);
|
||||
ClassDB::bind_method(D_METHOD("copy", "from", "to", "chmod_flags"), &DirAccess::copy, DEFVAL(-1));
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("copy_absolute", "from", "to", "chmod_flags"), &DirAccess::copy_absolute, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("rename", "from", "to"), &DirAccess::rename);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("rename_absolute", "from", "to"), &DirAccess::rename_absolute);
|
||||
ClassDB::bind_method(D_METHOD("remove", "path"), &DirAccess::remove);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("remove_absolute", "path"), &DirAccess::remove_absolute);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_link", "path"), &DirAccess::is_link);
|
||||
ClassDB::bind_method(D_METHOD("read_link", "path"), &DirAccess::read_link);
|
||||
ClassDB::bind_method(D_METHOD("create_link", "source", "target"), &DirAccess::create_link);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_include_navigational", "enable"), &DirAccess::set_include_navigational);
|
||||
ClassDB::bind_method(D_METHOD("get_include_navigational"), &DirAccess::get_include_navigational);
|
||||
ClassDB::bind_method(D_METHOD("set_include_hidden", "enable"), &DirAccess::set_include_hidden);
|
||||
ClassDB::bind_method(D_METHOD("get_include_hidden"), &DirAccess::get_include_hidden);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_case_sensitive", "path"), &DirAccess::is_case_sensitive);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden");
|
||||
}
|
||||
168
engine/core/io/dir_access.h
Normal file
168
engine/core/io/dir_access.h
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
/**************************************************************************/
|
||||
/* dir_access.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DIR_ACCESS_H
|
||||
#define DIR_ACCESS_H
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/typedefs.h"
|
||||
|
||||
//@ TODO, excellent candidate for THREAD_SAFE MACRO, should go through all these and add THREAD_SAFE where it applies
|
||||
class DirAccess : public RefCounted {
|
||||
GDCLASS(DirAccess, RefCounted);
|
||||
|
||||
public:
|
||||
enum AccessType {
|
||||
ACCESS_RESOURCES,
|
||||
ACCESS_USERDATA,
|
||||
ACCESS_FILESYSTEM,
|
||||
ACCESS_MAX
|
||||
};
|
||||
|
||||
typedef Ref<DirAccess> (*CreateFunc)();
|
||||
|
||||
private:
|
||||
AccessType _access_type = ACCESS_FILESYSTEM;
|
||||
static CreateFunc create_func[ACCESS_MAX]; ///< set this to instance a filesystem object
|
||||
static Ref<DirAccess> _open(const String &p_path);
|
||||
|
||||
Error _copy_dir(Ref<DirAccess> &p_target_da, const String &p_to, int p_chmod_flags, bool p_copy_links);
|
||||
PackedStringArray _get_contents(bool p_directories);
|
||||
|
||||
thread_local static Error last_dir_open_error;
|
||||
bool include_navigational = false;
|
||||
bool include_hidden = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
String _get_root_path() const;
|
||||
virtual String _get_root_string() const;
|
||||
|
||||
AccessType get_access_type() const;
|
||||
virtual String fix_path(const String &p_path) const;
|
||||
|
||||
template <typename T>
|
||||
static Ref<DirAccess> _create_builtin() {
|
||||
return memnew(T);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual Error list_dir_begin() = 0; ///< This starts dir listing
|
||||
virtual String get_next() = 0;
|
||||
virtual bool current_is_dir() const = 0;
|
||||
virtual bool current_is_hidden() const = 0;
|
||||
|
||||
virtual void list_dir_end() = 0; ///<
|
||||
|
||||
virtual int get_drive_count() = 0;
|
||||
virtual String get_drive(int p_drive) = 0;
|
||||
virtual int get_current_drive();
|
||||
virtual bool drives_are_shortcuts();
|
||||
|
||||
virtual Error change_dir(String p_dir) = 0; ///< can be relative or absolute, return false on success
|
||||
virtual String get_current_dir(bool p_include_drive = true) const = 0; ///< return current dir location
|
||||
virtual Error make_dir(String p_dir) = 0;
|
||||
virtual Error make_dir_recursive(const String &p_dir);
|
||||
virtual Error erase_contents_recursive(); //super dangerous, use with care!
|
||||
|
||||
virtual bool file_exists(String p_file) = 0;
|
||||
virtual bool dir_exists(String p_dir) = 0;
|
||||
virtual bool is_readable(String p_dir) { return true; };
|
||||
virtual bool is_writable(String p_dir) { return true; };
|
||||
static bool exists(const String &p_dir);
|
||||
virtual uint64_t get_space_left() = 0;
|
||||
|
||||
Error copy_dir(const String &p_from, String p_to, int p_chmod_flags = -1, bool p_copy_links = false);
|
||||
virtual Error copy(const String &p_from, const String &p_to, int p_chmod_flags = -1);
|
||||
virtual Error rename(String p_from, String p_to) = 0;
|
||||
virtual Error remove(String p_name) = 0;
|
||||
|
||||
virtual bool is_link(String p_file) = 0;
|
||||
virtual String read_link(String p_file) = 0;
|
||||
virtual Error create_link(String p_source, String p_target) = 0;
|
||||
|
||||
// Meant for editor code when we want to quickly remove a file without custom
|
||||
// handling (e.g. removing a cache file).
|
||||
static void remove_file_or_error(const String &p_path) {
|
||||
Ref<DirAccess> da = create(ACCESS_FILESYSTEM);
|
||||
if (da->file_exists(p_path)) {
|
||||
if (da->remove(p_path) != OK) {
|
||||
ERR_FAIL_MSG("Cannot remove file or directory: " + p_path);
|
||||
}
|
||||
} else {
|
||||
ERR_FAIL_MSG("Cannot remove non-existent file or directory: " + p_path);
|
||||
}
|
||||
}
|
||||
|
||||
virtual String get_filesystem_type() const = 0;
|
||||
static String get_full_path(const String &p_path, AccessType p_access);
|
||||
static Ref<DirAccess> create_for_path(const String &p_path);
|
||||
|
||||
static Ref<DirAccess> create(AccessType p_access);
|
||||
static Error get_open_error();
|
||||
|
||||
template <typename T>
|
||||
static void make_default(AccessType p_access) {
|
||||
create_func[p_access] = _create_builtin<T>;
|
||||
}
|
||||
|
||||
static Ref<DirAccess> open(const String &p_path, Error *r_error = nullptr);
|
||||
|
||||
static int _get_drive_count();
|
||||
static String get_drive_name(int p_idx);
|
||||
|
||||
static Error make_dir_absolute(const String &p_dir);
|
||||
static Error make_dir_recursive_absolute(const String &p_dir);
|
||||
static bool dir_exists_absolute(const String &p_dir);
|
||||
|
||||
static Error copy_absolute(const String &p_from, const String &p_to, int p_chmod_flags = -1);
|
||||
static Error rename_absolute(const String &p_from, const String &p_to);
|
||||
static Error remove_absolute(const String &p_path);
|
||||
|
||||
PackedStringArray get_files();
|
||||
static PackedStringArray get_files_at(const String &p_path);
|
||||
PackedStringArray get_directories();
|
||||
static PackedStringArray get_directories_at(const String &p_path);
|
||||
String _get_next();
|
||||
|
||||
void set_include_navigational(bool p_enable);
|
||||
bool get_include_navigational() const;
|
||||
void set_include_hidden(bool p_enable);
|
||||
bool get_include_hidden() const;
|
||||
|
||||
virtual bool is_case_sensitive(const String &p_path) const;
|
||||
|
||||
DirAccess() {}
|
||||
virtual ~DirAccess() {}
|
||||
};
|
||||
|
||||
#endif // DIR_ACCESS_H
|
||||
53
engine/core/io/dtls_server.cpp
Normal file
53
engine/core/io/dtls_server.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**************************************************************************/
|
||||
/* dtls_server.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 "dtls_server.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
DTLSServer *(*DTLSServer::_create)() = nullptr;
|
||||
bool DTLSServer::available = false;
|
||||
|
||||
DTLSServer *DTLSServer::create() {
|
||||
if (_create) {
|
||||
return _create();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DTLSServer::is_available() {
|
||||
return available;
|
||||
}
|
||||
|
||||
void DTLSServer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("setup", "server_options"), &DTLSServer::setup);
|
||||
ClassDB::bind_method(D_METHOD("take_connection", "udp_peer"), &DTLSServer::take_connection);
|
||||
}
|
||||
57
engine/core/io/dtls_server.h
Normal file
57
engine/core/io/dtls_server.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/**************************************************************************/
|
||||
/* dtls_server.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DTLS_SERVER_H
|
||||
#define DTLS_SERVER_H
|
||||
|
||||
#include "core/io/net_socket.h"
|
||||
#include "core/io/packet_peer_dtls.h"
|
||||
|
||||
class DTLSServer : public RefCounted {
|
||||
GDCLASS(DTLSServer, RefCounted);
|
||||
|
||||
protected:
|
||||
static DTLSServer *(*_create)();
|
||||
static void _bind_methods();
|
||||
|
||||
static bool available;
|
||||
|
||||
public:
|
||||
static bool is_available();
|
||||
static DTLSServer *create();
|
||||
|
||||
virtual Error setup(Ref<TLSOptions> p_options) = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer) = 0;
|
||||
|
||||
DTLSServer() {}
|
||||
};
|
||||
|
||||
#endif // DTLS_SERVER_H
|
||||
952
engine/core/io/file_access.cpp
Normal file
952
engine/core/io/file_access.cpp
Normal file
|
|
@ -0,0 +1,952 @@
|
|||
/**************************************************************************/
|
||||
/* file_access.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.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/io/file_access_compressed.h"
|
||||
#include "core/io/file_access_encrypted.h"
|
||||
#include "core/io/file_access_pack.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
FileAccess::CreateFunc FileAccess::create_func[ACCESS_MAX] = {};
|
||||
|
||||
FileAccess::FileCloseFailNotify FileAccess::close_fail_notify = nullptr;
|
||||
|
||||
bool FileAccess::backup_save = false;
|
||||
thread_local Error FileAccess::last_file_open_error = OK;
|
||||
|
||||
Ref<FileAccess> FileAccess::create(AccessType p_access) {
|
||||
ERR_FAIL_INDEX_V(p_access, ACCESS_MAX, nullptr);
|
||||
ERR_FAIL_NULL_V(create_func[p_access], nullptr);
|
||||
|
||||
Ref<FileAccess> ret = create_func[p_access]();
|
||||
ret->_set_access_type(p_access);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileAccess::exists(const String &p_name) {
|
||||
if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && PackedData::get_singleton()->has_path(p_name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = open(p_name, READ);
|
||||
if (f.is_null()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileAccess::_set_access_type(AccessType p_access) {
|
||||
_access_type = p_access;
|
||||
}
|
||||
|
||||
Ref<FileAccess> FileAccess::create_for_path(const String &p_path) {
|
||||
Ref<FileAccess> ret;
|
||||
if (p_path.begins_with("res://")) {
|
||||
ret = create(ACCESS_RESOURCES);
|
||||
} else if (p_path.begins_with("user://")) {
|
||||
ret = create(ACCESS_USERDATA);
|
||||
} else if (p_path.begins_with("pipe://")) {
|
||||
ret = create(ACCESS_PIPE);
|
||||
} else {
|
||||
ret = create(ACCESS_FILESYSTEM);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Error FileAccess::reopen(const String &p_path, int p_mode_flags) {
|
||||
return open_internal(p_path, p_mode_flags);
|
||||
}
|
||||
|
||||
Ref<FileAccess> FileAccess::open(const String &p_path, int p_mode_flags, Error *r_error) {
|
||||
//try packed data first
|
||||
|
||||
Ref<FileAccess> ret;
|
||||
if (!(p_mode_flags & WRITE) && PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled()) {
|
||||
ret = PackedData::get_singleton()->try_open_path(p_path);
|
||||
if (ret.is_valid()) {
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = create_for_path(p_path);
|
||||
Error err = ret->open_internal(p_path, p_mode_flags);
|
||||
|
||||
if (r_error) {
|
||||
*r_error = err;
|
||||
}
|
||||
if (err != OK) {
|
||||
ret.unref();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ref<FileAccess> FileAccess::_open(const String &p_path, ModeFlags p_mode_flags) {
|
||||
Error err = OK;
|
||||
Ref<FileAccess> fa = open(p_path, p_mode_flags, &err);
|
||||
last_file_open_error = err;
|
||||
if (err) {
|
||||
return Ref<FileAccess>();
|
||||
}
|
||||
return fa;
|
||||
}
|
||||
|
||||
Ref<FileAccess> FileAccess::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key) {
|
||||
Ref<FileAccess> fa = _open(p_path, p_mode_flags);
|
||||
if (fa.is_null()) {
|
||||
return fa;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
Error err = fae->open_and_parse(fa, p_key, (p_mode_flags == WRITE) ? FileAccessEncrypted::MODE_WRITE_AES256 : FileAccessEncrypted::MODE_READ);
|
||||
last_file_open_error = err;
|
||||
if (err) {
|
||||
return Ref<FileAccess>();
|
||||
}
|
||||
return fae;
|
||||
}
|
||||
|
||||
Ref<FileAccess> FileAccess::open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass) {
|
||||
Ref<FileAccess> fa = _open(p_path, p_mode_flags);
|
||||
if (fa.is_null()) {
|
||||
return fa;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
Error err = fae->open_and_parse_password(fa, p_pass, (p_mode_flags == WRITE) ? FileAccessEncrypted::MODE_WRITE_AES256 : FileAccessEncrypted::MODE_READ);
|
||||
last_file_open_error = err;
|
||||
if (err) {
|
||||
return Ref<FileAccess>();
|
||||
}
|
||||
return fae;
|
||||
}
|
||||
|
||||
Ref<FileAccess> FileAccess::open_compressed(const String &p_path, ModeFlags p_mode_flags, CompressionMode p_compress_mode) {
|
||||
Ref<FileAccessCompressed> fac;
|
||||
fac.instantiate();
|
||||
fac->configure("GCPF", (Compression::Mode)p_compress_mode);
|
||||
Error err = fac->open_internal(p_path, p_mode_flags);
|
||||
last_file_open_error = err;
|
||||
if (err) {
|
||||
return Ref<FileAccess>();
|
||||
}
|
||||
|
||||
return fac;
|
||||
}
|
||||
|
||||
Error FileAccess::get_open_error() {
|
||||
return last_file_open_error;
|
||||
}
|
||||
|
||||
FileAccess::CreateFunc FileAccess::get_create_func(AccessType p_access) {
|
||||
return create_func[p_access];
|
||||
}
|
||||
|
||||
FileAccess::AccessType FileAccess::get_access_type() const {
|
||||
return _access_type;
|
||||
}
|
||||
|
||||
String FileAccess::fix_path(const String &p_path) const {
|
||||
//helper used by file accesses that use a single filesystem
|
||||
|
||||
String r_path = p_path.replace("\\", "/");
|
||||
|
||||
switch (_access_type) {
|
||||
case ACCESS_RESOURCES: {
|
||||
if (ProjectSettings::get_singleton()) {
|
||||
if (r_path.begins_with("res://")) {
|
||||
String resource_path = ProjectSettings::get_singleton()->get_resource_path();
|
||||
if (!resource_path.is_empty()) {
|
||||
return r_path.replace("res:/", resource_path);
|
||||
}
|
||||
return r_path.replace("res://", "");
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
case ACCESS_USERDATA: {
|
||||
if (r_path.begins_with("user://")) {
|
||||
String data_dir = OS::get_singleton()->get_user_data_dir();
|
||||
if (!data_dir.is_empty()) {
|
||||
return r_path.replace("user:/", data_dir);
|
||||
}
|
||||
return r_path.replace("user://", "");
|
||||
}
|
||||
|
||||
} break;
|
||||
case ACCESS_PIPE: {
|
||||
return r_path;
|
||||
} break;
|
||||
case ACCESS_FILESYSTEM: {
|
||||
return r_path;
|
||||
} break;
|
||||
case ACCESS_MAX:
|
||||
break; // Can't happen, but silences warning
|
||||
}
|
||||
|
||||
return r_path;
|
||||
}
|
||||
|
||||
/* these are all implemented for ease of porting, then can later be optimized */
|
||||
|
||||
uint16_t FileAccess::get_16() const {
|
||||
uint16_t res;
|
||||
uint8_t a, b;
|
||||
|
||||
a = get_8();
|
||||
b = get_8();
|
||||
|
||||
if (big_endian) {
|
||||
SWAP(a, b);
|
||||
}
|
||||
|
||||
res = b;
|
||||
res <<= 8;
|
||||
res |= a;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
uint32_t FileAccess::get_32() const {
|
||||
uint32_t res;
|
||||
uint16_t a, b;
|
||||
|
||||
a = get_16();
|
||||
b = get_16();
|
||||
|
||||
if (big_endian) {
|
||||
SWAP(a, b);
|
||||
}
|
||||
|
||||
res = b;
|
||||
res <<= 16;
|
||||
res |= a;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
uint64_t FileAccess::get_64() const {
|
||||
uint64_t res;
|
||||
uint32_t a, b;
|
||||
|
||||
a = get_32();
|
||||
b = get_32();
|
||||
|
||||
if (big_endian) {
|
||||
SWAP(a, b);
|
||||
}
|
||||
|
||||
res = b;
|
||||
res <<= 32;
|
||||
res |= a;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
float FileAccess::get_float() const {
|
||||
MarshallFloat m;
|
||||
m.i = get_32();
|
||||
return m.f;
|
||||
}
|
||||
|
||||
real_t FileAccess::get_real() const {
|
||||
if (real_is_double) {
|
||||
return get_double();
|
||||
} else {
|
||||
return get_float();
|
||||
}
|
||||
}
|
||||
|
||||
Variant FileAccess::get_var(bool p_allow_objects) const {
|
||||
uint32_t len = get_32();
|
||||
Vector<uint8_t> buff = get_buffer(len);
|
||||
ERR_FAIL_COND_V((uint32_t)buff.size() != len, Variant());
|
||||
|
||||
const uint8_t *r = buff.ptr();
|
||||
|
||||
Variant v;
|
||||
Error err = decode_variant(v, &r[0], len, nullptr, p_allow_objects);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, Variant(), "Error when trying to encode Variant.");
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
double FileAccess::get_double() const {
|
||||
MarshallDouble m;
|
||||
m.l = get_64();
|
||||
return m.d;
|
||||
}
|
||||
|
||||
String FileAccess::get_token() const {
|
||||
CharString token;
|
||||
|
||||
char32_t c = get_8();
|
||||
|
||||
while (!eof_reached()) {
|
||||
if (c <= ' ') {
|
||||
if (token.length()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
token += c;
|
||||
}
|
||||
c = get_8();
|
||||
}
|
||||
|
||||
return String::utf8(token.get_data());
|
||||
}
|
||||
|
||||
class CharBuffer {
|
||||
Vector<char> vector;
|
||||
char stack_buffer[256];
|
||||
|
||||
char *buffer = nullptr;
|
||||
int capacity = 0;
|
||||
int written = 0;
|
||||
|
||||
bool grow() {
|
||||
if (vector.resize(next_power_of_2(1 + written)) != OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer == stack_buffer) { // first chunk?
|
||||
|
||||
for (int i = 0; i < written; i++) {
|
||||
vector.write[i] = stack_buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
buffer = vector.ptrw();
|
||||
capacity = vector.size();
|
||||
ERR_FAIL_COND_V(written >= capacity, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ CharBuffer() :
|
||||
buffer(stack_buffer),
|
||||
capacity(sizeof(stack_buffer) / sizeof(char)) {
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void push_back(char c) {
|
||||
if (written >= capacity) {
|
||||
ERR_FAIL_COND(!grow());
|
||||
}
|
||||
|
||||
buffer[written++] = c;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ const char *get_data() const {
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
|
||||
String FileAccess::get_line() const {
|
||||
CharBuffer line;
|
||||
|
||||
char32_t c = get_8();
|
||||
|
||||
while (!eof_reached()) {
|
||||
if (c == '\n' || c == '\0') {
|
||||
line.push_back(0);
|
||||
return String::utf8(line.get_data());
|
||||
} else if (c != '\r') {
|
||||
line.push_back(c);
|
||||
}
|
||||
|
||||
c = get_8();
|
||||
}
|
||||
line.push_back(0);
|
||||
return String::utf8(line.get_data());
|
||||
}
|
||||
|
||||
Vector<String> FileAccess::get_csv_line(const String &p_delim) const {
|
||||
ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector<String>(), "Only single character delimiters are supported to parse CSV lines.");
|
||||
ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector<String>(), "The double quotation mark character (\") is not supported as a delimiter for CSV lines.");
|
||||
|
||||
String line;
|
||||
|
||||
// CSV can support entries with line breaks as long as they are enclosed
|
||||
// in double quotes. So our "line" might be more than a single line in the
|
||||
// text file.
|
||||
int qc = 0;
|
||||
do {
|
||||
if (eof_reached()) {
|
||||
break;
|
||||
}
|
||||
line += get_line() + "\n";
|
||||
qc = 0;
|
||||
for (int i = 0; i < line.length(); i++) {
|
||||
if (line[i] == '"') {
|
||||
qc++;
|
||||
}
|
||||
}
|
||||
} while (qc % 2);
|
||||
|
||||
// Remove the extraneous newline we've added above.
|
||||
line = line.substr(0, line.length() - 1);
|
||||
|
||||
Vector<String> strings;
|
||||
|
||||
bool in_quote = false;
|
||||
String current;
|
||||
for (int i = 0; i < line.length(); i++) {
|
||||
char32_t c = line[i];
|
||||
// A delimiter ends the current entry, unless it's in a quoted string.
|
||||
if (!in_quote && c == p_delim[0]) {
|
||||
strings.push_back(current);
|
||||
current = String();
|
||||
} else if (c == '"') {
|
||||
// Doubled quotes are escapes for intentional quotes in the string.
|
||||
if (line[i + 1] == '"' && in_quote) {
|
||||
current += '"';
|
||||
i++;
|
||||
} else {
|
||||
in_quote = !in_quote;
|
||||
}
|
||||
} else {
|
||||
current += c;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_quote) {
|
||||
WARN_PRINT(vformat("Reached end of file before closing '\"' in CSV file '%s'.", get_path()));
|
||||
}
|
||||
|
||||
strings.push_back(current);
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
String FileAccess::get_as_text(bool p_skip_cr) const {
|
||||
uint64_t original_pos = get_position();
|
||||
const_cast<FileAccess *>(this)->seek(0);
|
||||
|
||||
String text = get_as_utf8_string(p_skip_cr);
|
||||
|
||||
const_cast<FileAccess *>(this)->seek(original_pos);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
|
||||
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
|
||||
|
||||
uint64_t i = 0;
|
||||
for (i = 0; i < p_length && !eof_reached(); i++) {
|
||||
p_dst[i] = get_8();
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
|
||||
Vector<uint8_t> data;
|
||||
|
||||
ERR_FAIL_COND_V_MSG(p_length < 0, data, "Length of buffer cannot be smaller than 0.");
|
||||
if (p_length == 0) {
|
||||
return data;
|
||||
}
|
||||
|
||||
Error err = data.resize(p_length);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements.");
|
||||
|
||||
uint8_t *w = data.ptrw();
|
||||
int64_t len = get_buffer(&w[0], p_length);
|
||||
|
||||
if (len < p_length) {
|
||||
data.resize(len);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
String FileAccess::get_as_utf8_string(bool p_skip_cr) const {
|
||||
Vector<uint8_t> sourcef;
|
||||
uint64_t len = get_length();
|
||||
sourcef.resize(len + 1);
|
||||
|
||||
uint8_t *w = sourcef.ptrw();
|
||||
uint64_t r = get_buffer(w, len);
|
||||
ERR_FAIL_COND_V(r != len, String());
|
||||
w[len] = 0;
|
||||
|
||||
String s;
|
||||
s.parse_utf8((const char *)w, -1, p_skip_cr);
|
||||
return s;
|
||||
}
|
||||
|
||||
void FileAccess::store_16(uint16_t p_dest) {
|
||||
uint8_t a, b;
|
||||
|
||||
a = p_dest & 0xFF;
|
||||
b = p_dest >> 8;
|
||||
|
||||
if (big_endian) {
|
||||
SWAP(a, b);
|
||||
}
|
||||
|
||||
store_8(a);
|
||||
store_8(b);
|
||||
}
|
||||
|
||||
void FileAccess::store_32(uint32_t p_dest) {
|
||||
uint16_t a, b;
|
||||
|
||||
a = p_dest & 0xFFFF;
|
||||
b = p_dest >> 16;
|
||||
|
||||
if (big_endian) {
|
||||
SWAP(a, b);
|
||||
}
|
||||
|
||||
store_16(a);
|
||||
store_16(b);
|
||||
}
|
||||
|
||||
void FileAccess::store_64(uint64_t p_dest) {
|
||||
uint32_t a, b;
|
||||
|
||||
a = p_dest & 0xFFFFFFFF;
|
||||
b = p_dest >> 32;
|
||||
|
||||
if (big_endian) {
|
||||
SWAP(a, b);
|
||||
}
|
||||
|
||||
store_32(a);
|
||||
store_32(b);
|
||||
}
|
||||
|
||||
void FileAccess::store_real(real_t p_real) {
|
||||
if constexpr (sizeof(real_t) == 4) {
|
||||
store_float(p_real);
|
||||
} else {
|
||||
store_double(p_real);
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccess::store_float(float p_dest) {
|
||||
MarshallFloat m;
|
||||
m.f = p_dest;
|
||||
store_32(m.i);
|
||||
}
|
||||
|
||||
void FileAccess::store_double(double p_dest) {
|
||||
MarshallDouble m;
|
||||
m.d = p_dest;
|
||||
store_64(m.l);
|
||||
}
|
||||
|
||||
uint64_t FileAccess::get_modified_time(const String &p_file) {
|
||||
if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Ref<FileAccess> fa = create_for_path(p_file);
|
||||
ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
|
||||
|
||||
uint64_t mt = fa->_get_modified_time(p_file);
|
||||
return mt;
|
||||
}
|
||||
|
||||
BitField<FileAccess::UnixPermissionFlags> FileAccess::get_unix_permissions(const String &p_file) {
|
||||
if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Ref<FileAccess> fa = create_for_path(p_file);
|
||||
ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
|
||||
|
||||
return fa->_get_unix_permissions(p_file);
|
||||
}
|
||||
|
||||
Error FileAccess::set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
|
||||
if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Ref<FileAccess> fa = create_for_path(p_file);
|
||||
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
|
||||
|
||||
Error err = fa->_set_unix_permissions(p_file, p_permissions);
|
||||
return err;
|
||||
}
|
||||
|
||||
bool FileAccess::get_hidden_attribute(const String &p_file) {
|
||||
if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ref<FileAccess> fa = create_for_path(p_file);
|
||||
ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
|
||||
|
||||
return fa->_get_hidden_attribute(p_file);
|
||||
}
|
||||
|
||||
Error FileAccess::set_hidden_attribute(const String &p_file, bool p_hidden) {
|
||||
if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Ref<FileAccess> fa = create_for_path(p_file);
|
||||
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
|
||||
|
||||
Error err = fa->_set_hidden_attribute(p_file, p_hidden);
|
||||
return err;
|
||||
}
|
||||
|
||||
bool FileAccess::get_read_only_attribute(const String &p_file) {
|
||||
if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ref<FileAccess> fa = create_for_path(p_file);
|
||||
ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
|
||||
|
||||
return fa->_get_read_only_attribute(p_file);
|
||||
}
|
||||
|
||||
Error FileAccess::set_read_only_attribute(const String &p_file, bool p_ro) {
|
||||
if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Ref<FileAccess> fa = create_for_path(p_file);
|
||||
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
|
||||
|
||||
Error err = fa->_set_read_only_attribute(p_file, p_ro);
|
||||
return err;
|
||||
}
|
||||
|
||||
void FileAccess::store_string(const String &p_string) {
|
||||
if (p_string.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CharString cs = p_string.utf8();
|
||||
store_buffer((uint8_t *)&cs[0], cs.length());
|
||||
}
|
||||
|
||||
void FileAccess::store_pascal_string(const String &p_string) {
|
||||
CharString cs = p_string.utf8();
|
||||
store_32(cs.length());
|
||||
store_buffer((uint8_t *)&cs[0], cs.length());
|
||||
}
|
||||
|
||||
String FileAccess::get_pascal_string() {
|
||||
uint32_t sl = get_32();
|
||||
CharString cs;
|
||||
cs.resize(sl + 1);
|
||||
get_buffer((uint8_t *)cs.ptr(), sl);
|
||||
cs[sl] = 0;
|
||||
|
||||
String ret;
|
||||
ret.parse_utf8(cs.ptr());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void FileAccess::store_line(const String &p_line) {
|
||||
store_string(p_line);
|
||||
store_8('\n');
|
||||
}
|
||||
|
||||
void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_delim) {
|
||||
ERR_FAIL_COND(p_delim.length() != 1);
|
||||
|
||||
String line = "";
|
||||
int size = p_values.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
String value = p_values[i];
|
||||
|
||||
if (value.contains("\"") || value.contains(p_delim) || value.contains("\n")) {
|
||||
value = "\"" + value.replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
if (i < size - 1) {
|
||||
value += p_delim;
|
||||
}
|
||||
|
||||
line += value;
|
||||
}
|
||||
|
||||
store_line(line);
|
||||
}
|
||||
|
||||
void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) {
|
||||
ERR_FAIL_COND(!p_src && p_length > 0);
|
||||
for (uint64_t i = 0; i < p_length; i++) {
|
||||
store_8(p_src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) {
|
||||
uint64_t len = p_buffer.size();
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *r = p_buffer.ptr();
|
||||
|
||||
store_buffer(&r[0], len);
|
||||
}
|
||||
|
||||
void FileAccess::store_var(const Variant &p_var, bool p_full_objects) {
|
||||
int len;
|
||||
Error err = encode_variant(p_var, nullptr, len, p_full_objects);
|
||||
ERR_FAIL_COND_MSG(err != OK, "Error when trying to encode Variant.");
|
||||
|
||||
Vector<uint8_t> buff;
|
||||
buff.resize(len);
|
||||
|
||||
uint8_t *w = buff.ptrw();
|
||||
err = encode_variant(p_var, &w[0], len, p_full_objects);
|
||||
ERR_FAIL_COND_MSG(err != OK, "Error when trying to encode Variant.");
|
||||
|
||||
store_32(len);
|
||||
store_buffer(buff);
|
||||
}
|
||||
|
||||
Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_error) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, READ, r_error);
|
||||
if (f.is_null()) {
|
||||
if (r_error) { // if error requested, do not throw error
|
||||
return Vector<uint8_t>();
|
||||
}
|
||||
ERR_FAIL_V_MSG(Vector<uint8_t>(), "Can't open file from path '" + String(p_path) + "'.");
|
||||
}
|
||||
Vector<uint8_t> data;
|
||||
data.resize(f->get_length());
|
||||
f->get_buffer(data.ptrw(), data.size());
|
||||
return data;
|
||||
}
|
||||
|
||||
String FileAccess::get_file_as_string(const String &p_path, Error *r_error) {
|
||||
Error err;
|
||||
Vector<uint8_t> array = get_file_as_bytes(p_path, &err);
|
||||
if (r_error) {
|
||||
*r_error = err;
|
||||
}
|
||||
if (err != OK) {
|
||||
if (r_error) {
|
||||
return String();
|
||||
}
|
||||
ERR_FAIL_V_MSG(String(), "Can't get file as string from path '" + String(p_path) + "'.");
|
||||
}
|
||||
|
||||
String ret;
|
||||
ret.parse_utf8((const char *)array.ptr(), array.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
String FileAccess::get_md5(const String &p_file) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_file, READ);
|
||||
if (f.is_null()) {
|
||||
return String();
|
||||
}
|
||||
|
||||
CryptoCore::MD5Context ctx;
|
||||
ctx.start();
|
||||
|
||||
unsigned char step[32768];
|
||||
|
||||
while (true) {
|
||||
uint64_t br = f->get_buffer(step, 32768);
|
||||
if (br > 0) {
|
||||
ctx.update(step, br);
|
||||
}
|
||||
if (br < 4096) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char hash[16];
|
||||
ctx.finish(hash);
|
||||
|
||||
return String::md5(hash);
|
||||
}
|
||||
|
||||
String FileAccess::get_multiple_md5(const Vector<String> &p_file) {
|
||||
CryptoCore::MD5Context ctx;
|
||||
ctx.start();
|
||||
|
||||
for (int i = 0; i < p_file.size(); i++) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_file[i], READ);
|
||||
ERR_CONTINUE(f.is_null());
|
||||
|
||||
unsigned char step[32768];
|
||||
|
||||
while (true) {
|
||||
uint64_t br = f->get_buffer(step, 32768);
|
||||
if (br > 0) {
|
||||
ctx.update(step, br);
|
||||
}
|
||||
if (br < 4096) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char hash[16];
|
||||
ctx.finish(hash);
|
||||
|
||||
return String::md5(hash);
|
||||
}
|
||||
|
||||
String FileAccess::get_sha256(const String &p_file) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_file, READ);
|
||||
if (f.is_null()) {
|
||||
return String();
|
||||
}
|
||||
|
||||
CryptoCore::SHA256Context ctx;
|
||||
ctx.start();
|
||||
|
||||
unsigned char step[32768];
|
||||
|
||||
while (true) {
|
||||
uint64_t br = f->get_buffer(step, 32768);
|
||||
if (br > 0) {
|
||||
ctx.update(step, br);
|
||||
}
|
||||
if (br < 4096) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char hash[32];
|
||||
ctx.finish(hash);
|
||||
|
||||
return String::hex_encode_buffer(hash, 32);
|
||||
}
|
||||
|
||||
void FileAccess::_bind_methods() {
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("open", "path", "flags"), &FileAccess::_open);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted", "path", "mode_flags", "key"), &FileAccess::open_encrypted);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted_with_pass", "path", "mode_flags", "pass"), &FileAccess::open_encrypted_pass);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("open_compressed", "path", "mode_flags", "compression_mode"), &FileAccess::open_compressed, DEFVAL(0));
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_open_error"), &FileAccess::get_open_error);
|
||||
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_bytes", "path"), &FileAccess::_get_file_as_bytes);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_string", "path"), &FileAccess::_get_file_as_string);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("resize", "length"), &FileAccess::resize);
|
||||
ClassDB::bind_method(D_METHOD("flush"), &FileAccess::flush);
|
||||
ClassDB::bind_method(D_METHOD("get_path"), &FileAccess::get_path);
|
||||
ClassDB::bind_method(D_METHOD("get_path_absolute"), &FileAccess::get_path_absolute);
|
||||
ClassDB::bind_method(D_METHOD("is_open"), &FileAccess::is_open);
|
||||
ClassDB::bind_method(D_METHOD("seek", "position"), &FileAccess::seek);
|
||||
ClassDB::bind_method(D_METHOD("seek_end", "position"), &FileAccess::seek_end, DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("get_position"), &FileAccess::get_position);
|
||||
ClassDB::bind_method(D_METHOD("get_length"), &FileAccess::get_length);
|
||||
ClassDB::bind_method(D_METHOD("eof_reached"), &FileAccess::eof_reached);
|
||||
ClassDB::bind_method(D_METHOD("get_8"), &FileAccess::get_8);
|
||||
ClassDB::bind_method(D_METHOD("get_16"), &FileAccess::get_16);
|
||||
ClassDB::bind_method(D_METHOD("get_32"), &FileAccess::get_32);
|
||||
ClassDB::bind_method(D_METHOD("get_64"), &FileAccess::get_64);
|
||||
ClassDB::bind_method(D_METHOD("get_float"), &FileAccess::get_float);
|
||||
ClassDB::bind_method(D_METHOD("get_double"), &FileAccess::get_double);
|
||||
ClassDB::bind_method(D_METHOD("get_real"), &FileAccess::get_real);
|
||||
ClassDB::bind_method(D_METHOD("get_buffer", "length"), (Vector<uint8_t>(FileAccess::*)(int64_t) const) & FileAccess::get_buffer);
|
||||
ClassDB::bind_method(D_METHOD("get_line"), &FileAccess::get_line);
|
||||
ClassDB::bind_method(D_METHOD("get_csv_line", "delim"), &FileAccess::get_csv_line, DEFVAL(","));
|
||||
ClassDB::bind_method(D_METHOD("get_as_text", "skip_cr"), &FileAccess::get_as_text, DEFVAL(false));
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_md5", "path"), &FileAccess::get_md5);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_sha256", "path"), &FileAccess::get_sha256);
|
||||
ClassDB::bind_method(D_METHOD("is_big_endian"), &FileAccess::is_big_endian);
|
||||
ClassDB::bind_method(D_METHOD("set_big_endian", "big_endian"), &FileAccess::set_big_endian);
|
||||
ClassDB::bind_method(D_METHOD("get_error"), &FileAccess::get_error);
|
||||
ClassDB::bind_method(D_METHOD("get_var", "allow_objects"), &FileAccess::get_var, DEFVAL(false));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("store_8", "value"), &FileAccess::store_8);
|
||||
ClassDB::bind_method(D_METHOD("store_16", "value"), &FileAccess::store_16);
|
||||
ClassDB::bind_method(D_METHOD("store_32", "value"), &FileAccess::store_32);
|
||||
ClassDB::bind_method(D_METHOD("store_64", "value"), &FileAccess::store_64);
|
||||
ClassDB::bind_method(D_METHOD("store_float", "value"), &FileAccess::store_float);
|
||||
ClassDB::bind_method(D_METHOD("store_double", "value"), &FileAccess::store_double);
|
||||
ClassDB::bind_method(D_METHOD("store_real", "value"), &FileAccess::store_real);
|
||||
ClassDB::bind_method(D_METHOD("store_buffer", "buffer"), (void(FileAccess::*)(const Vector<uint8_t> &)) & FileAccess::store_buffer);
|
||||
ClassDB::bind_method(D_METHOD("store_line", "line"), &FileAccess::store_line);
|
||||
ClassDB::bind_method(D_METHOD("store_csv_line", "values", "delim"), &FileAccess::store_csv_line, DEFVAL(","));
|
||||
ClassDB::bind_method(D_METHOD("store_string", "string"), &FileAccess::store_string);
|
||||
ClassDB::bind_method(D_METHOD("store_var", "value", "full_objects"), &FileAccess::store_var, DEFVAL(false));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("store_pascal_string", "string"), &FileAccess::store_pascal_string);
|
||||
ClassDB::bind_method(D_METHOD("get_pascal_string"), &FileAccess::get_pascal_string);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("close"), &FileAccess::close);
|
||||
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("file_exists", "path"), &FileAccess::exists);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_modified_time", "file"), &FileAccess::get_modified_time);
|
||||
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_unix_permissions", "file"), &FileAccess::get_unix_permissions);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("set_unix_permissions", "file", "permissions"), &FileAccess::set_unix_permissions);
|
||||
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_hidden_attribute", "file"), &FileAccess::get_hidden_attribute);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("set_hidden_attribute", "file", "hidden"), &FileAccess::set_hidden_attribute);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("set_read_only_attribute", "file", "ro"), &FileAccess::set_read_only_attribute);
|
||||
ClassDB::bind_static_method("FileAccess", D_METHOD("get_read_only_attribute", "file"), &FileAccess::get_read_only_attribute);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "big_endian"), "set_big_endian", "is_big_endian");
|
||||
|
||||
BIND_ENUM_CONSTANT(READ);
|
||||
BIND_ENUM_CONSTANT(WRITE);
|
||||
BIND_ENUM_CONSTANT(READ_WRITE);
|
||||
BIND_ENUM_CONSTANT(WRITE_READ);
|
||||
|
||||
BIND_ENUM_CONSTANT(COMPRESSION_FASTLZ);
|
||||
BIND_ENUM_CONSTANT(COMPRESSION_DEFLATE);
|
||||
BIND_ENUM_CONSTANT(COMPRESSION_ZSTD);
|
||||
BIND_ENUM_CONSTANT(COMPRESSION_GZIP);
|
||||
BIND_ENUM_CONSTANT(COMPRESSION_BROTLI);
|
||||
|
||||
BIND_BITFIELD_FLAG(UNIX_READ_OWNER);
|
||||
BIND_BITFIELD_FLAG(UNIX_WRITE_OWNER);
|
||||
BIND_BITFIELD_FLAG(UNIX_EXECUTE_OWNER);
|
||||
BIND_BITFIELD_FLAG(UNIX_READ_GROUP);
|
||||
BIND_BITFIELD_FLAG(UNIX_WRITE_GROUP);
|
||||
BIND_BITFIELD_FLAG(UNIX_EXECUTE_GROUP);
|
||||
BIND_BITFIELD_FLAG(UNIX_READ_OTHER);
|
||||
BIND_BITFIELD_FLAG(UNIX_WRITE_OTHER);
|
||||
BIND_BITFIELD_FLAG(UNIX_EXECUTE_OTHER);
|
||||
BIND_BITFIELD_FLAG(UNIX_SET_USER_ID);
|
||||
BIND_BITFIELD_FLAG(UNIX_SET_GROUP_ID);
|
||||
BIND_BITFIELD_FLAG(UNIX_RESTRICTED_DELETE);
|
||||
}
|
||||
244
engine/core/io/file_access.h
Normal file
244
engine/core/io/file_access.h
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
/**************************************************************************/
|
||||
/* file_access.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef FILE_ACCESS_H
|
||||
#define FILE_ACCESS_H
|
||||
|
||||
#include "core/io/compression.h"
|
||||
#include "core/math/math_defs.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/typedefs.h"
|
||||
|
||||
/**
|
||||
* Multi-Platform abstraction for accessing to files.
|
||||
*/
|
||||
|
||||
class FileAccess : public RefCounted {
|
||||
GDCLASS(FileAccess, RefCounted);
|
||||
|
||||
public:
|
||||
enum AccessType {
|
||||
ACCESS_RESOURCES,
|
||||
ACCESS_USERDATA,
|
||||
ACCESS_FILESYSTEM,
|
||||
ACCESS_PIPE,
|
||||
ACCESS_MAX
|
||||
};
|
||||
|
||||
enum ModeFlags {
|
||||
READ = 1,
|
||||
WRITE = 2,
|
||||
READ_WRITE = 3,
|
||||
WRITE_READ = 7,
|
||||
};
|
||||
|
||||
enum UnixPermissionFlags {
|
||||
UNIX_EXECUTE_OTHER = 0x001,
|
||||
UNIX_WRITE_OTHER = 0x002,
|
||||
UNIX_READ_OTHER = 0x004,
|
||||
UNIX_EXECUTE_GROUP = 0x008,
|
||||
UNIX_WRITE_GROUP = 0x010,
|
||||
UNIX_READ_GROUP = 0x020,
|
||||
UNIX_EXECUTE_OWNER = 0x040,
|
||||
UNIX_WRITE_OWNER = 0x080,
|
||||
UNIX_READ_OWNER = 0x100,
|
||||
UNIX_RESTRICTED_DELETE = 0x200,
|
||||
UNIX_SET_GROUP_ID = 0x400,
|
||||
UNIX_SET_USER_ID = 0x800,
|
||||
};
|
||||
|
||||
enum CompressionMode {
|
||||
COMPRESSION_FASTLZ = Compression::MODE_FASTLZ,
|
||||
COMPRESSION_DEFLATE = Compression::MODE_DEFLATE,
|
||||
COMPRESSION_ZSTD = Compression::MODE_ZSTD,
|
||||
COMPRESSION_GZIP = Compression::MODE_GZIP,
|
||||
COMPRESSION_BROTLI = Compression::MODE_BROTLI,
|
||||
};
|
||||
|
||||
typedef void (*FileCloseFailNotify)(const String &);
|
||||
|
||||
typedef Ref<FileAccess> (*CreateFunc)();
|
||||
bool big_endian = false;
|
||||
bool real_is_double = false;
|
||||
|
||||
virtual BitField<UnixPermissionFlags> _get_unix_permissions(const String &p_file) = 0;
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<UnixPermissionFlags> p_permissions) = 0;
|
||||
|
||||
virtual bool _get_hidden_attribute(const String &p_file) = 0;
|
||||
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) = 0;
|
||||
virtual bool _get_read_only_attribute(const String &p_file) = 0;
|
||||
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) = 0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
AccessType get_access_type() const;
|
||||
virtual String fix_path(const String &p_path) const;
|
||||
virtual Error open_internal(const String &p_path, int p_mode_flags) = 0; ///< open a file
|
||||
virtual uint64_t _get_modified_time(const String &p_file) = 0;
|
||||
virtual void _set_access_type(AccessType p_access);
|
||||
|
||||
static FileCloseFailNotify close_fail_notify;
|
||||
|
||||
private:
|
||||
static bool backup_save;
|
||||
thread_local static Error last_file_open_error;
|
||||
|
||||
AccessType _access_type = ACCESS_FILESYSTEM;
|
||||
static CreateFunc create_func[ACCESS_MAX]; /** default file access creation function for a platform */
|
||||
template <typename T>
|
||||
static Ref<FileAccess> _create_builtin() {
|
||||
return memnew(T);
|
||||
}
|
||||
|
||||
static Ref<FileAccess> _open(const String &p_path, ModeFlags p_mode_flags);
|
||||
|
||||
public:
|
||||
static void set_file_close_fail_notify_callback(FileCloseFailNotify p_cbk) { close_fail_notify = p_cbk; }
|
||||
|
||||
virtual bool is_open() const = 0; ///< true when file is open
|
||||
|
||||
virtual String get_path() const { return ""; } /// returns the path for the current open file
|
||||
virtual String get_path_absolute() const { return ""; } /// returns the absolute path for the current open file
|
||||
|
||||
virtual void seek(uint64_t p_position) = 0; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position = 0) = 0; ///< seek from the end of file with negative offset
|
||||
virtual uint64_t get_position() const = 0; ///< get position in the file
|
||||
virtual uint64_t get_length() const = 0; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const = 0; ///< reading passed EOF
|
||||
|
||||
virtual uint8_t get_8() const = 0; ///< get a byte
|
||||
virtual uint16_t get_16() const; ///< get 16 bits uint
|
||||
virtual uint32_t get_32() const; ///< get 32 bits uint
|
||||
virtual uint64_t get_64() const; ///< get 64 bits uint
|
||||
|
||||
virtual float get_float() const;
|
||||
virtual double get_double() const;
|
||||
virtual real_t get_real() const;
|
||||
|
||||
Variant get_var(bool p_allow_objects = false) const;
|
||||
|
||||
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes
|
||||
Vector<uint8_t> get_buffer(int64_t p_length) const;
|
||||
virtual String get_line() const;
|
||||
virtual String get_token() const;
|
||||
virtual Vector<String> get_csv_line(const String &p_delim = ",") const;
|
||||
String get_as_text(bool p_skip_cr = false) const;
|
||||
virtual String get_as_utf8_string(bool p_skip_cr = false) const;
|
||||
|
||||
/**
|
||||
* Use this for files WRITTEN in _big_ endian machines (ie, amiga/mac)
|
||||
* It's not about the current CPU type but file formats.
|
||||
* This flag gets reset to `false` (little endian) on each open.
|
||||
*/
|
||||
virtual void set_big_endian(bool p_big_endian) { big_endian = p_big_endian; }
|
||||
inline bool is_big_endian() const { return big_endian; }
|
||||
|
||||
virtual Error get_error() const = 0; ///< get last error
|
||||
|
||||
virtual Error resize(int64_t p_length) = 0;
|
||||
virtual void flush() = 0;
|
||||
virtual void store_8(uint8_t p_dest) = 0; ///< store a byte
|
||||
virtual void store_16(uint16_t p_dest); ///< store 16 bits uint
|
||||
virtual void store_32(uint32_t p_dest); ///< store 32 bits uint
|
||||
virtual void store_64(uint64_t p_dest); ///< store 64 bits uint
|
||||
|
||||
virtual void store_float(float p_dest);
|
||||
virtual void store_double(double p_dest);
|
||||
virtual void store_real(real_t p_real);
|
||||
|
||||
virtual void store_string(const String &p_string);
|
||||
virtual void store_line(const String &p_line);
|
||||
virtual void store_csv_line(const Vector<String> &p_values, const String &p_delim = ",");
|
||||
|
||||
virtual void store_pascal_string(const String &p_string);
|
||||
virtual String get_pascal_string();
|
||||
|
||||
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes
|
||||
void store_buffer(const Vector<uint8_t> &p_buffer);
|
||||
|
||||
void store_var(const Variant &p_var, bool p_full_objects = false);
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual bool file_exists(const String &p_name) = 0; ///< return true if a file exists
|
||||
|
||||
virtual Error reopen(const String &p_path, int p_mode_flags); ///< does not change the AccessType
|
||||
|
||||
static Ref<FileAccess> create(AccessType p_access); /// Create a file access (for the current platform) this is the only portable way of accessing files.
|
||||
static Ref<FileAccess> create_for_path(const String &p_path);
|
||||
static Ref<FileAccess> open(const String &p_path, int p_mode_flags, Error *r_error = nullptr); /// Create a file access (for the current platform) this is the only portable way of accessing files.
|
||||
|
||||
static Ref<FileAccess> open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key);
|
||||
static Ref<FileAccess> open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass);
|
||||
static Ref<FileAccess> open_compressed(const String &p_path, ModeFlags p_mode_flags, CompressionMode p_compress_mode = COMPRESSION_FASTLZ);
|
||||
static Error get_open_error();
|
||||
|
||||
static CreateFunc get_create_func(AccessType p_access);
|
||||
static bool exists(const String &p_name); ///< return true if a file exists
|
||||
static uint64_t get_modified_time(const String &p_file);
|
||||
static BitField<FileAccess::UnixPermissionFlags> get_unix_permissions(const String &p_file);
|
||||
static Error set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions);
|
||||
|
||||
static bool get_hidden_attribute(const String &p_file);
|
||||
static Error set_hidden_attribute(const String &p_file, bool p_hidden);
|
||||
static bool get_read_only_attribute(const String &p_file);
|
||||
static Error set_read_only_attribute(const String &p_file, bool p_ro);
|
||||
|
||||
static void set_backup_save(bool p_enable) { backup_save = p_enable; };
|
||||
static bool is_backup_save_enabled() { return backup_save; };
|
||||
|
||||
static String get_md5(const String &p_file);
|
||||
static String get_sha256(const String &p_file);
|
||||
static String get_multiple_md5(const Vector<String> &p_file);
|
||||
|
||||
static Vector<uint8_t> get_file_as_bytes(const String &p_path, Error *r_error = nullptr);
|
||||
static String get_file_as_string(const String &p_path, Error *r_error = nullptr);
|
||||
|
||||
static PackedByteArray _get_file_as_bytes(const String &p_path) { return get_file_as_bytes(p_path, &last_file_open_error); }
|
||||
static String _get_file_as_string(const String &p_path) { return get_file_as_string(p_path, &last_file_open_error); }
|
||||
|
||||
template <typename T>
|
||||
static void make_default(AccessType p_access) {
|
||||
create_func[p_access] = _create_builtin<T>;
|
||||
}
|
||||
|
||||
FileAccess() {}
|
||||
virtual ~FileAccess() {}
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(FileAccess::CompressionMode);
|
||||
VARIANT_ENUM_CAST(FileAccess::ModeFlags);
|
||||
VARIANT_BITFIELD_CAST(FileAccess::UnixPermissionFlags);
|
||||
|
||||
#endif // FILE_ACCESS_H
|
||||
416
engine/core/io/file_access_compressed.cpp
Normal file
416
engine/core/io/file_access_compressed.cpp
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_compressed.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_compressed.h"
|
||||
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
void FileAccessCompressed::configure(const String &p_magic, Compression::Mode p_mode, uint32_t p_block_size) {
|
||||
magic = p_magic.ascii().get_data();
|
||||
magic = (magic + " ").substr(0, 4);
|
||||
|
||||
cmode = p_mode;
|
||||
block_size = p_block_size;
|
||||
}
|
||||
|
||||
#define WRITE_FIT(m_bytes) \
|
||||
{ \
|
||||
if (write_pos + (m_bytes) > write_max) { \
|
||||
write_max = write_pos + (m_bytes); \
|
||||
} \
|
||||
if (write_max > write_buffer_size) { \
|
||||
write_buffer_size = next_power_of_2(write_max); \
|
||||
buffer.resize(write_buffer_size); \
|
||||
write_ptr = buffer.ptrw(); \
|
||||
} \
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::open_after_magic(Ref<FileAccess> p_base) {
|
||||
f = p_base;
|
||||
cmode = (Compression::Mode)f->get_32();
|
||||
block_size = f->get_32();
|
||||
if (block_size == 0) {
|
||||
f.unref();
|
||||
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Can't open compressed file '" + p_base->get_path() + "' with block size 0, it is corrupted.");
|
||||
}
|
||||
read_total = f->get_32();
|
||||
uint32_t bc = (read_total / block_size) + 1;
|
||||
uint64_t acc_ofs = f->get_position() + bc * 4;
|
||||
uint32_t max_bs = 0;
|
||||
for (uint32_t i = 0; i < bc; i++) {
|
||||
ReadBlock rb;
|
||||
rb.offset = acc_ofs;
|
||||
rb.csize = f->get_32();
|
||||
acc_ofs += rb.csize;
|
||||
max_bs = MAX(max_bs, rb.csize);
|
||||
read_blocks.push_back(rb);
|
||||
}
|
||||
|
||||
comp_buffer.resize(max_bs);
|
||||
buffer.resize(block_size);
|
||||
read_ptr = buffer.ptrw();
|
||||
f->get_buffer(comp_buffer.ptrw(), read_blocks[0].csize);
|
||||
at_end = false;
|
||||
read_eof = false;
|
||||
read_block_count = bc;
|
||||
read_block_size = read_blocks.size() == 1 ? read_total : block_size;
|
||||
|
||||
int ret = Compression::decompress(buffer.ptrw(), read_block_size, comp_buffer.ptr(), read_blocks[0].csize, cmode);
|
||||
read_block = 0;
|
||||
read_pos = 0;
|
||||
|
||||
return ret == -1 ? ERR_FILE_CORRUPT : OK;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::open_internal(const String &p_path, int p_mode_flags) {
|
||||
ERR_FAIL_COND_V(p_mode_flags == READ_WRITE, ERR_UNAVAILABLE);
|
||||
_close();
|
||||
|
||||
Error err;
|
||||
f = FileAccess::open(p_path, p_mode_flags, &err);
|
||||
if (err != OK) {
|
||||
//not openable
|
||||
f.unref();
|
||||
return err;
|
||||
}
|
||||
|
||||
if (p_mode_flags & WRITE) {
|
||||
buffer.clear();
|
||||
writing = true;
|
||||
write_pos = 0;
|
||||
write_buffer_size = 256;
|
||||
buffer.resize(256);
|
||||
write_max = 0;
|
||||
write_ptr = buffer.ptrw();
|
||||
|
||||
//don't store anything else unless it's done saving!
|
||||
} else {
|
||||
char rmagic[5];
|
||||
f->get_buffer((uint8_t *)rmagic, 4);
|
||||
rmagic[4] = 0;
|
||||
err = ERR_FILE_UNRECOGNIZED;
|
||||
if (magic != rmagic || (err = open_after_magic(f)) != OK) {
|
||||
f.unref();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void FileAccessCompressed::_close() {
|
||||
if (f.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (writing) {
|
||||
//save block table and all compressed blocks
|
||||
|
||||
CharString mgc = magic.utf8();
|
||||
f->store_buffer((const uint8_t *)mgc.get_data(), mgc.length()); //write header 4
|
||||
f->store_32(cmode); //write compression mode 4
|
||||
f->store_32(block_size); //write block size 4
|
||||
f->store_32(write_max); //max amount of data written 4
|
||||
uint32_t bc = (write_max / block_size) + 1;
|
||||
|
||||
for (uint32_t i = 0; i < bc; i++) {
|
||||
f->store_32(0); //compressed sizes, will update later
|
||||
}
|
||||
|
||||
Vector<int> block_sizes;
|
||||
for (uint32_t i = 0; i < bc; i++) {
|
||||
uint32_t bl = i == (bc - 1) ? write_max % block_size : block_size;
|
||||
uint8_t *bp = &write_ptr[i * block_size];
|
||||
|
||||
Vector<uint8_t> cblock;
|
||||
cblock.resize(Compression::get_max_compressed_buffer_size(bl, cmode));
|
||||
int s = Compression::compress(cblock.ptrw(), bp, bl, cmode);
|
||||
|
||||
f->store_buffer(cblock.ptr(), s);
|
||||
block_sizes.push_back(s);
|
||||
}
|
||||
|
||||
f->seek(16); //ok write block sizes
|
||||
for (uint32_t i = 0; i < bc; i++) {
|
||||
f->store_32(block_sizes[i]);
|
||||
}
|
||||
f->seek_end();
|
||||
f->store_buffer((const uint8_t *)mgc.get_data(), mgc.length()); //magic at the end too
|
||||
|
||||
buffer.clear();
|
||||
|
||||
} else {
|
||||
comp_buffer.clear();
|
||||
buffer.clear();
|
||||
read_blocks.clear();
|
||||
}
|
||||
f.unref();
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::is_open() const {
|
||||
return f.is_valid();
|
||||
}
|
||||
|
||||
String FileAccessCompressed::get_path() const {
|
||||
if (f.is_valid()) {
|
||||
return f->get_path();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String FileAccessCompressed::get_path_absolute() const {
|
||||
if (f.is_valid()) {
|
||||
return f->get_path_absolute();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessCompressed::seek(uint64_t p_position) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
|
||||
if (writing) {
|
||||
ERR_FAIL_COND(p_position > write_max);
|
||||
|
||||
write_pos = p_position;
|
||||
|
||||
} else {
|
||||
ERR_FAIL_COND(p_position > read_total);
|
||||
if (p_position == read_total) {
|
||||
at_end = true;
|
||||
} else {
|
||||
at_end = false;
|
||||
read_eof = false;
|
||||
uint32_t block_idx = p_position / block_size;
|
||||
if (block_idx != read_block) {
|
||||
read_block = block_idx;
|
||||
f->seek(read_blocks[read_block].offset);
|
||||
f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
|
||||
int ret = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);
|
||||
ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");
|
||||
read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
|
||||
}
|
||||
|
||||
read_pos = p_position % block_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessCompressed::seek_end(int64_t p_position) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
if (writing) {
|
||||
seek(write_max + p_position);
|
||||
} else {
|
||||
seek(read_total + p_position);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::get_position() const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
|
||||
if (writing) {
|
||||
return write_pos;
|
||||
} else {
|
||||
return (uint64_t)read_block * block_size + read_pos;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::get_length() const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
|
||||
if (writing) {
|
||||
return write_max;
|
||||
} else {
|
||||
return read_total;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::eof_reached() const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), false, "File must be opened before use.");
|
||||
if (writing) {
|
||||
return false;
|
||||
} else {
|
||||
return read_eof;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t FileAccessCompressed::get_8() const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
|
||||
ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
|
||||
|
||||
if (at_end) {
|
||||
read_eof = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t ret = read_ptr[read_pos];
|
||||
|
||||
read_pos++;
|
||||
if (read_pos >= read_block_size) {
|
||||
read_block++;
|
||||
|
||||
if (read_block < read_block_count) {
|
||||
//read another block of compressed data
|
||||
f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
|
||||
int total = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);
|
||||
ERR_FAIL_COND_V_MSG(total == -1, 0, "Compressed file is corrupt.");
|
||||
read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
|
||||
read_pos = 0;
|
||||
|
||||
} else {
|
||||
read_block--;
|
||||
at_end = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
|
||||
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
|
||||
ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
|
||||
|
||||
if (at_end) {
|
||||
read_eof = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < p_length; i++) {
|
||||
p_dst[i] = read_ptr[read_pos];
|
||||
read_pos++;
|
||||
if (read_pos >= read_block_size) {
|
||||
read_block++;
|
||||
|
||||
if (read_block < read_block_count) {
|
||||
//read another block of compressed data
|
||||
f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
|
||||
int ret = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);
|
||||
ERR_FAIL_COND_V_MSG(ret == -1, -1, "Compressed file is corrupt.");
|
||||
read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
|
||||
read_pos = 0;
|
||||
|
||||
} else {
|
||||
read_block--;
|
||||
at_end = true;
|
||||
if (i + 1 < p_length) {
|
||||
read_eof = true;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p_length;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::get_error() const {
|
||||
return read_eof ? ERR_FILE_EOF : OK;
|
||||
}
|
||||
|
||||
void FileAccessCompressed::flush() {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
|
||||
|
||||
// compressed files keep data in memory till close()
|
||||
}
|
||||
|
||||
void FileAccessCompressed::store_8(uint8_t p_dest) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
|
||||
|
||||
WRITE_FIT(1);
|
||||
write_ptr[write_pos++] = p_dest;
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::file_exists(const String &p_name) {
|
||||
Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
|
||||
if (fa.is_null()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::_get_modified_time(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->get_modified_time(p_file);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
BitField<FileAccess::UnixPermissionFlags> FileAccessCompressed::_get_unix_permissions(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->_get_unix_permissions(p_file);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
|
||||
if (f.is_valid()) {
|
||||
return f->_set_unix_permissions(p_file, p_permissions);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::_get_hidden_attribute(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->_get_hidden_attribute(p_file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::_set_hidden_attribute(const String &p_file, bool p_hidden) {
|
||||
if (f.is_valid()) {
|
||||
return f->_set_hidden_attribute(p_file, p_hidden);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::_get_read_only_attribute(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->_get_read_only_attribute(p_file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::_set_read_only_attribute(const String &p_file, bool p_ro) {
|
||||
if (f.is_valid()) {
|
||||
return f->_set_read_only_attribute(p_file, p_ro);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
void FileAccessCompressed::close() {
|
||||
_close();
|
||||
}
|
||||
|
||||
FileAccessCompressed::~FileAccessCompressed() {
|
||||
_close();
|
||||
}
|
||||
112
engine/core/io/file_access_compressed.h
Normal file
112
engine/core/io/file_access_compressed.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_compressed.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef FILE_ACCESS_COMPRESSED_H
|
||||
#define FILE_ACCESS_COMPRESSED_H
|
||||
|
||||
#include "core/io/compression.h"
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
class FileAccessCompressed : public FileAccess {
|
||||
Compression::Mode cmode = Compression::MODE_ZSTD;
|
||||
bool writing = false;
|
||||
uint64_t write_pos = 0;
|
||||
uint8_t *write_ptr = nullptr;
|
||||
uint32_t write_buffer_size = 0;
|
||||
uint64_t write_max = 0;
|
||||
uint32_t block_size = 0;
|
||||
mutable bool read_eof = false;
|
||||
mutable bool at_end = false;
|
||||
|
||||
struct ReadBlock {
|
||||
uint32_t csize;
|
||||
uint64_t offset;
|
||||
};
|
||||
|
||||
mutable Vector<uint8_t> comp_buffer;
|
||||
uint8_t *read_ptr = nullptr;
|
||||
mutable uint32_t read_block = 0;
|
||||
uint32_t read_block_count = 0;
|
||||
mutable uint32_t read_block_size = 0;
|
||||
mutable uint64_t read_pos = 0;
|
||||
Vector<ReadBlock> read_blocks;
|
||||
uint64_t read_total = 0;
|
||||
|
||||
String magic = "GCMP";
|
||||
mutable Vector<uint8_t> buffer;
|
||||
Ref<FileAccess> f;
|
||||
|
||||
void _close();
|
||||
|
||||
public:
|
||||
void configure(const String &p_magic, Compression::Mode p_mode = Compression::MODE_ZSTD, uint32_t p_block_size = 4096);
|
||||
|
||||
Error open_after_magic(Ref<FileAccess> p_base);
|
||||
|
||||
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
|
||||
virtual bool is_open() const override; ///< true when file is open
|
||||
|
||||
virtual String get_path() const override; /// returns the path for the current open file
|
||||
virtual String get_path_absolute() const override; /// returns the absolute path for the current open file
|
||||
|
||||
virtual void seek(uint64_t p_position) override; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position = 0) override; ///< seek from the end of file
|
||||
virtual uint64_t get_position() const override; ///< get position in the file
|
||||
virtual uint64_t get_length() const override; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const override; ///< reading passed EOF
|
||||
|
||||
virtual uint8_t get_8() const override; ///< get a byte
|
||||
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
|
||||
|
||||
virtual Error get_error() const override; ///< get last error
|
||||
|
||||
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
|
||||
virtual void flush() override;
|
||||
virtual void store_8(uint8_t p_dest) override; ///< store a byte
|
||||
|
||||
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
|
||||
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override;
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override;
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override;
|
||||
|
||||
virtual bool _get_hidden_attribute(const String &p_file) override;
|
||||
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override;
|
||||
virtual bool _get_read_only_attribute(const String &p_file) override;
|
||||
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
FileAccessCompressed() {}
|
||||
virtual ~FileAccessCompressed();
|
||||
};
|
||||
|
||||
#endif // FILE_ACCESS_COMPRESSED_H
|
||||
336
engine/core/io/file_access_encrypted.cpp
Normal file
336
engine/core/io/file_access_encrypted.cpp
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_encrypted.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_encrypted.h"
|
||||
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
|
||||
ERR_FAIL_COND_V_MSG(file != nullptr, ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
|
||||
ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
|
||||
|
||||
pos = 0;
|
||||
eofed = false;
|
||||
use_magic = p_with_magic;
|
||||
|
||||
if (p_mode == MODE_WRITE_AES256) {
|
||||
data.clear();
|
||||
writing = true;
|
||||
file = p_base;
|
||||
key = p_key;
|
||||
|
||||
} else if (p_mode == MODE_READ) {
|
||||
writing = false;
|
||||
key = p_key;
|
||||
|
||||
if (use_magic) {
|
||||
uint32_t magic = p_base->get_32();
|
||||
ERR_FAIL_COND_V(magic != ENCRYPTED_HEADER_MAGIC, ERR_FILE_UNRECOGNIZED);
|
||||
}
|
||||
|
||||
unsigned char md5d[16];
|
||||
p_base->get_buffer(md5d, 16);
|
||||
length = p_base->get_64();
|
||||
|
||||
unsigned char iv[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
iv[i] = p_base->get_8();
|
||||
}
|
||||
|
||||
base = p_base->get_position();
|
||||
ERR_FAIL_COND_V(p_base->get_length() < base + length, ERR_FILE_CORRUPT);
|
||||
uint64_t ds = length;
|
||||
if (ds % 16) {
|
||||
ds += 16 - (ds % 16);
|
||||
}
|
||||
data.resize(ds);
|
||||
|
||||
uint64_t blen = p_base->get_buffer(data.ptrw(), ds);
|
||||
ERR_FAIL_COND_V(blen != ds, ERR_FILE_CORRUPT);
|
||||
|
||||
{
|
||||
CryptoCore::AESContext ctx;
|
||||
|
||||
ctx.set_encode_key(key.ptrw(), 256); // Due to the nature of CFB, same key schedule is used for both encryption and decryption!
|
||||
ctx.decrypt_cfb(ds, iv, data.ptrw(), data.ptrw());
|
||||
}
|
||||
|
||||
data.resize(length);
|
||||
|
||||
unsigned char hash[16];
|
||||
ERR_FAIL_COND_V(CryptoCore::md5(data.ptr(), data.size(), hash) != OK, ERR_BUG);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(String::md5(hash) != String::md5(md5d), ERR_FILE_CORRUPT, "The MD5 sum of the decrypted file does not match the expected value. It could be that the file is corrupt, or that the provided decryption key is invalid.");
|
||||
|
||||
file = p_base;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::open_and_parse_password(Ref<FileAccess> p_base, const String &p_key, Mode p_mode) {
|
||||
String cs = p_key.md5_text();
|
||||
ERR_FAIL_COND_V(cs.length() != 32, ERR_INVALID_PARAMETER);
|
||||
Vector<uint8_t> key_md5;
|
||||
key_md5.resize(32);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
key_md5.write[i] = cs[i];
|
||||
}
|
||||
|
||||
return open_and_parse(p_base, key_md5, p_mode);
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::open_internal(const String &p_path, int p_mode_flags) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::_close() {
|
||||
if (file.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (writing) {
|
||||
Vector<uint8_t> compressed;
|
||||
uint64_t len = data.size();
|
||||
if (len % 16) {
|
||||
len += 16 - (len % 16);
|
||||
}
|
||||
|
||||
unsigned char hash[16];
|
||||
ERR_FAIL_COND(CryptoCore::md5(data.ptr(), data.size(), hash) != OK); // Bug?
|
||||
|
||||
compressed.resize(len);
|
||||
memset(compressed.ptrw(), 0, len);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
compressed.write[i] = data[i];
|
||||
}
|
||||
|
||||
CryptoCore::AESContext ctx;
|
||||
ctx.set_encode_key(key.ptrw(), 256);
|
||||
|
||||
if (use_magic) {
|
||||
file->store_32(ENCRYPTED_HEADER_MAGIC);
|
||||
}
|
||||
|
||||
file->store_buffer(hash, 16);
|
||||
file->store_64(data.size());
|
||||
|
||||
unsigned char iv[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
iv[i] = Math::rand() % 256;
|
||||
file->store_8(iv[i]);
|
||||
}
|
||||
|
||||
ctx.encrypt_cfb(len, iv, compressed.ptrw(), compressed.ptrw());
|
||||
|
||||
file->store_buffer(compressed.ptr(), compressed.size());
|
||||
data.clear();
|
||||
}
|
||||
|
||||
file.unref();
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::is_open() const {
|
||||
return file != nullptr;
|
||||
}
|
||||
|
||||
String FileAccessEncrypted::get_path() const {
|
||||
if (file.is_valid()) {
|
||||
return file->get_path();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String FileAccessEncrypted::get_path_absolute() const {
|
||||
if (file.is_valid()) {
|
||||
return file->get_path_absolute();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::seek(uint64_t p_position) {
|
||||
if (p_position > get_length()) {
|
||||
p_position = get_length();
|
||||
}
|
||||
|
||||
pos = p_position;
|
||||
eofed = false;
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::seek_end(int64_t p_position) {
|
||||
seek(get_length() + p_position);
|
||||
}
|
||||
|
||||
uint64_t FileAccessEncrypted::get_position() const {
|
||||
return pos;
|
||||
}
|
||||
|
||||
uint64_t FileAccessEncrypted::get_length() const {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::eof_reached() const {
|
||||
return eofed;
|
||||
}
|
||||
|
||||
uint8_t FileAccessEncrypted::get_8() const {
|
||||
ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
|
||||
if (pos >= get_length()) {
|
||||
eofed = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t b = data[pos];
|
||||
pos++;
|
||||
return b;
|
||||
}
|
||||
|
||||
uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
|
||||
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
|
||||
ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
|
||||
|
||||
uint64_t to_copy = MIN(p_length, get_length() - pos);
|
||||
for (uint64_t i = 0; i < to_copy; i++) {
|
||||
p_dst[i] = data[pos++];
|
||||
}
|
||||
|
||||
if (to_copy < p_length) {
|
||||
eofed = true;
|
||||
}
|
||||
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::get_error() const {
|
||||
return eofed ? ERR_FILE_EOF : OK;
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length) {
|
||||
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
|
||||
ERR_FAIL_COND(!p_src && p_length > 0);
|
||||
|
||||
if (pos < get_length()) {
|
||||
for (uint64_t i = 0; i < p_length; i++) {
|
||||
store_8(p_src[i]);
|
||||
}
|
||||
} else if (pos == get_length()) {
|
||||
data.resize(pos + p_length);
|
||||
for (uint64_t i = 0; i < p_length; i++) {
|
||||
data.write[pos + i] = p_src[i];
|
||||
}
|
||||
pos += p_length;
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::flush() {
|
||||
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
|
||||
|
||||
// encrypted files keep data in memory till close()
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::store_8(uint8_t p_dest) {
|
||||
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
|
||||
|
||||
if (pos < get_length()) {
|
||||
data.write[pos] = p_dest;
|
||||
pos++;
|
||||
} else if (pos == get_length()) {
|
||||
data.push_back(p_dest);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::file_exists(const String &p_name) {
|
||||
Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
|
||||
if (fa.is_null()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t FileAccessEncrypted::_get_modified_time(const String &p_file) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
BitField<FileAccess::UnixPermissionFlags> FileAccessEncrypted::_get_unix_permissions(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->_get_unix_permissions(p_file);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
|
||||
if (file.is_valid()) {
|
||||
return file->_set_unix_permissions(p_file, p_permissions);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::_get_hidden_attribute(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->_get_hidden_attribute(p_file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::_set_hidden_attribute(const String &p_file, bool p_hidden) {
|
||||
if (file.is_valid()) {
|
||||
return file->_set_hidden_attribute(p_file, p_hidden);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::_get_read_only_attribute(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->_get_read_only_attribute(p_file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::_set_read_only_attribute(const String &p_file, bool p_ro) {
|
||||
if (file.is_valid()) {
|
||||
return file->_set_read_only_attribute(p_file, p_ro);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::close() {
|
||||
_close();
|
||||
}
|
||||
|
||||
FileAccessEncrypted::~FileAccessEncrypted() {
|
||||
_close();
|
||||
}
|
||||
103
engine/core/io/file_access_encrypted.h
Normal file
103
engine/core/io/file_access_encrypted.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_encrypted.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef FILE_ACCESS_ENCRYPTED_H
|
||||
#define FILE_ACCESS_ENCRYPTED_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
#define ENCRYPTED_HEADER_MAGIC 0x43454447
|
||||
|
||||
class FileAccessEncrypted : public FileAccess {
|
||||
public:
|
||||
enum Mode {
|
||||
MODE_READ,
|
||||
MODE_WRITE_AES256,
|
||||
MODE_MAX
|
||||
};
|
||||
|
||||
private:
|
||||
Vector<uint8_t> key;
|
||||
bool writing = false;
|
||||
Ref<FileAccess> file;
|
||||
uint64_t base = 0;
|
||||
uint64_t length = 0;
|
||||
Vector<uint8_t> data;
|
||||
mutable uint64_t pos = 0;
|
||||
mutable bool eofed = false;
|
||||
bool use_magic = true;
|
||||
|
||||
void _close();
|
||||
|
||||
public:
|
||||
Error open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic = true);
|
||||
Error open_and_parse_password(Ref<FileAccess> p_base, const String &p_key, Mode p_mode);
|
||||
|
||||
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
|
||||
virtual bool is_open() const override; ///< true when file is open
|
||||
|
||||
virtual String get_path() const override; /// returns the path for the current open file
|
||||
virtual String get_path_absolute() const override; /// returns the absolute path for the current open file
|
||||
|
||||
virtual void seek(uint64_t p_position) override; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position = 0) override; ///< seek from the end of file
|
||||
virtual uint64_t get_position() const override; ///< get position in the file
|
||||
virtual uint64_t get_length() const override; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const override; ///< reading passed EOF
|
||||
|
||||
virtual uint8_t get_8() const override; ///< get a byte
|
||||
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
|
||||
|
||||
virtual Error get_error() const override; ///< get last error
|
||||
|
||||
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
|
||||
virtual void flush() override;
|
||||
virtual void store_8(uint8_t p_dest) override; ///< store a byte
|
||||
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
|
||||
|
||||
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
|
||||
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override;
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override;
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override;
|
||||
|
||||
virtual bool _get_hidden_attribute(const String &p_file) override;
|
||||
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override;
|
||||
virtual bool _get_read_only_attribute(const String &p_file) override;
|
||||
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
FileAccessEncrypted() {}
|
||||
~FileAccessEncrypted();
|
||||
};
|
||||
|
||||
#endif // FILE_ACCESS_ENCRYPTED_H
|
||||
176
engine/core/io/file_access_memory.cpp
Normal file
176
engine/core/io/file_access_memory.cpp
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_memory.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_memory.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/templates/rb_map.h"
|
||||
|
||||
static HashMap<String, Vector<uint8_t>> *files = nullptr;
|
||||
|
||||
void FileAccessMemory::register_file(const String &p_name, const Vector<uint8_t> &p_data) {
|
||||
if (!files) {
|
||||
files = memnew((HashMap<String, Vector<uint8_t>>));
|
||||
}
|
||||
|
||||
String name;
|
||||
if (ProjectSettings::get_singleton()) {
|
||||
name = ProjectSettings::get_singleton()->globalize_path(p_name);
|
||||
} else {
|
||||
name = p_name;
|
||||
}
|
||||
//name = DirAccess::normalize_path(name);
|
||||
|
||||
(*files)[name] = p_data;
|
||||
}
|
||||
|
||||
void FileAccessMemory::cleanup() {
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
memdelete(files);
|
||||
}
|
||||
|
||||
Ref<FileAccess> FileAccessMemory::create() {
|
||||
return memnew(FileAccessMemory);
|
||||
}
|
||||
|
||||
bool FileAccessMemory::file_exists(const String &p_name) {
|
||||
String name = fix_path(p_name);
|
||||
//name = DirAccess::normalize_path(name);
|
||||
|
||||
return files && (files->find(name) != nullptr);
|
||||
}
|
||||
|
||||
Error FileAccessMemory::open_custom(const uint8_t *p_data, uint64_t p_len) {
|
||||
data = (uint8_t *)p_data;
|
||||
length = p_len;
|
||||
pos = 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error FileAccessMemory::open_internal(const String &p_path, int p_mode_flags) {
|
||||
ERR_FAIL_NULL_V(files, ERR_FILE_NOT_FOUND);
|
||||
|
||||
String name = fix_path(p_path);
|
||||
//name = DirAccess::normalize_path(name);
|
||||
|
||||
HashMap<String, Vector<uint8_t>>::Iterator E = files->find(name);
|
||||
ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, "Can't find file '" + p_path + "'.");
|
||||
|
||||
data = E->value.ptrw();
|
||||
length = E->value.size();
|
||||
pos = 0;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool FileAccessMemory::is_open() const {
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
void FileAccessMemory::seek(uint64_t p_position) {
|
||||
ERR_FAIL_NULL(data);
|
||||
pos = p_position;
|
||||
}
|
||||
|
||||
void FileAccessMemory::seek_end(int64_t p_position) {
|
||||
ERR_FAIL_NULL(data);
|
||||
pos = length + p_position;
|
||||
}
|
||||
|
||||
uint64_t FileAccessMemory::get_position() const {
|
||||
ERR_FAIL_NULL_V(data, 0);
|
||||
return pos;
|
||||
}
|
||||
|
||||
uint64_t FileAccessMemory::get_length() const {
|
||||
ERR_FAIL_NULL_V(data, 0);
|
||||
return length;
|
||||
}
|
||||
|
||||
bool FileAccessMemory::eof_reached() const {
|
||||
return pos >= length;
|
||||
}
|
||||
|
||||
uint8_t FileAccessMemory::get_8() const {
|
||||
uint8_t ret = 0;
|
||||
if (pos < length) {
|
||||
ret = data[pos];
|
||||
}
|
||||
++pos;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
|
||||
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
|
||||
ERR_FAIL_NULL_V(data, -1);
|
||||
|
||||
uint64_t left = length - pos;
|
||||
uint64_t read = MIN(p_length, left);
|
||||
|
||||
if (read < p_length) {
|
||||
WARN_PRINT("Reading less data than requested");
|
||||
}
|
||||
|
||||
memcpy(p_dst, &data[pos], read);
|
||||
pos += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
Error FileAccessMemory::get_error() const {
|
||||
return pos >= length ? ERR_FILE_EOF : OK;
|
||||
}
|
||||
|
||||
void FileAccessMemory::flush() {
|
||||
ERR_FAIL_NULL(data);
|
||||
}
|
||||
|
||||
void FileAccessMemory::store_8(uint8_t p_byte) {
|
||||
ERR_FAIL_NULL(data);
|
||||
ERR_FAIL_COND(pos >= length);
|
||||
data[pos++] = p_byte;
|
||||
}
|
||||
|
||||
void FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) {
|
||||
ERR_FAIL_COND(!p_src && p_length > 0);
|
||||
uint64_t left = length - pos;
|
||||
uint64_t write = MIN(p_length, left);
|
||||
if (write < p_length) {
|
||||
WARN_PRINT("Writing less data than requested");
|
||||
}
|
||||
|
||||
memcpy(&data[pos], p_src, write);
|
||||
pos += write;
|
||||
}
|
||||
85
engine/core/io/file_access_memory.h
Normal file
85
engine/core/io/file_access_memory.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_memory.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef FILE_ACCESS_MEMORY_H
|
||||
#define FILE_ACCESS_MEMORY_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
class FileAccessMemory : public FileAccess {
|
||||
uint8_t *data = nullptr;
|
||||
uint64_t length = 0;
|
||||
mutable uint64_t pos = 0;
|
||||
|
||||
static Ref<FileAccess> create();
|
||||
|
||||
public:
|
||||
static void register_file(const String &p_name, const Vector<uint8_t> &p_data);
|
||||
static void cleanup();
|
||||
|
||||
virtual Error open_custom(const uint8_t *p_data, uint64_t p_len); ///< open a file
|
||||
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
|
||||
virtual bool is_open() const override; ///< true when file is open
|
||||
|
||||
virtual void seek(uint64_t p_position) override; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position) override; ///< seek from the end of file
|
||||
virtual uint64_t get_position() const override; ///< get position in the file
|
||||
virtual uint64_t get_length() const override; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const override; ///< reading passed EOF
|
||||
|
||||
virtual uint8_t get_8() const override; ///< get a byte
|
||||
|
||||
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; ///< get an array of bytes
|
||||
|
||||
virtual Error get_error() const override; ///< get last error
|
||||
|
||||
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
|
||||
virtual void flush() override;
|
||||
virtual void store_8(uint8_t p_byte) override; ///< store a byte
|
||||
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
|
||||
|
||||
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
|
||||
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override { return 0; }
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::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 void close() override {}
|
||||
|
||||
FileAccessMemory() {}
|
||||
};
|
||||
|
||||
#endif // FILE_ACCESS_MEMORY_H
|
||||
583
engine/core/io/file_access_pack.cpp
Normal file
583
engine/core/io/file_access_pack.cpp
Normal file
|
|
@ -0,0 +1,583 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_pack.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_pack.h"
|
||||
|
||||
#include "core/io/file_access_encrypted.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/version.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) {
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
if (sources[i]->try_open_pack(p_path, p_replace_files, p_offset)) {
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
String simplified_path = p_path.simplify_path();
|
||||
PathMD5 pmd5(simplified_path.md5_buffer());
|
||||
|
||||
bool exists = files.has(pmd5);
|
||||
|
||||
PackedFile pf;
|
||||
pf.encrypted = p_encrypted;
|
||||
pf.pack = p_pkg_path;
|
||||
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;
|
||||
|
||||
if (!exists || p_replace_files) {
|
||||
files[pmd5] = pf;
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
//search for dir
|
||||
String p = simplified_path.replace_first("res://", "");
|
||||
PackedDir *cd = root;
|
||||
|
||||
if (p.contains("/")) { //in a subdir
|
||||
|
||||
Vector<String> ds = p.get_base_dir().split("/");
|
||||
|
||||
for (int j = 0; j < ds.size(); j++) {
|
||||
if (!cd->subdirs.has(ds[j])) {
|
||||
PackedDir *pd = memnew(PackedDir);
|
||||
pd->name = ds[j];
|
||||
pd->parent = cd;
|
||||
cd->subdirs[pd->name] = pd;
|
||||
cd = pd;
|
||||
} else {
|
||||
cd = cd->subdirs[ds[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
String filename = simplified_path.get_file();
|
||||
// Don't add as a file if the path points to a directory
|
||||
if (!filename.is_empty()) {
|
||||
cd->files.insert(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PackedData::add_pack_source(PackSource *p_source) {
|
||||
if (p_source != nullptr) {
|
||||
sources.push_back(p_source);
|
||||
}
|
||||
}
|
||||
|
||||
PackedData *PackedData::singleton = nullptr;
|
||||
|
||||
PackedData::PackedData() {
|
||||
singleton = this;
|
||||
root = memnew(PackedDir);
|
||||
|
||||
add_pack_source(memnew(PackedSourcePCK));
|
||||
}
|
||||
|
||||
void PackedData::_free_packed_dirs(PackedDir *p_dir) {
|
||||
for (const KeyValue<String, PackedDir *> &E : p_dir->subdirs) {
|
||||
_free_packed_dirs(E.value);
|
||||
}
|
||||
memdelete(p_dir);
|
||||
}
|
||||
|
||||
PackedData::~PackedData() {
|
||||
if (singleton == this) {
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
memdelete(sources[i]);
|
||||
}
|
||||
_free_packed_dirs(root);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pck_header_found = false;
|
||||
|
||||
// Search for the header at the start offset - standalone PCK file.
|
||||
f->seek(p_offset);
|
||||
uint32_t magic = f->get_32();
|
||||
if (magic == PACK_HEADER_MAGIC) {
|
||||
pck_header_found = true;
|
||||
}
|
||||
|
||||
// Search for the header in the executable "pck" section - self contained executable.
|
||||
if (!pck_header_found) {
|
||||
// Loading with offset feature not supported for self contained exe files.
|
||||
if (p_offset != 0) {
|
||||
ERR_FAIL_V_MSG(false, "Loading self-contained executable with offset not supported.");
|
||||
}
|
||||
|
||||
int64_t pck_off = OS::get_singleton()->get_embedded_pck_offset();
|
||||
if (pck_off != 0) {
|
||||
// Search for the header, in case PCK start and section have different alignment.
|
||||
for (int i = 0; i < 8; i++) {
|
||||
f->seek(pck_off);
|
||||
magic = f->get_32();
|
||||
if (magic == PACK_HEADER_MAGIC) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
print_verbose("PCK header found in executable pck section, loading from offset 0x" + String::num_int64(pck_off - 4, 16));
|
||||
#endif
|
||||
pck_header_found = true;
|
||||
break;
|
||||
}
|
||||
pck_off++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search for the header at the end of file - self contained executable.
|
||||
if (!pck_header_found) {
|
||||
// Loading with offset feature not supported for self contained exe files.
|
||||
if (p_offset != 0) {
|
||||
ERR_FAIL_V_MSG(false, "Loading self-contained executable with offset not supported.");
|
||||
}
|
||||
|
||||
f->seek_end();
|
||||
f->seek(f->get_position() - 4);
|
||||
magic = f->get_32();
|
||||
|
||||
if (magic == PACK_HEADER_MAGIC) {
|
||||
f->seek(f->get_position() - 12);
|
||||
uint64_t ds = f->get_64();
|
||||
f->seek(f->get_position() - ds - 8);
|
||||
magic = f->get_32();
|
||||
if (magic == PACK_HEADER_MAGIC) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
print_verbose("PCK header found at the end of executable, loading from offset 0x" + String::num_int64(f->get_position() - 4, 16));
|
||||
#endif
|
||||
pck_header_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pck_header_found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t pck_start_pos = f->get_position() - 4;
|
||||
|
||||
uint32_t version = f->get_32();
|
||||
uint32_t ver_major = f->get_32();
|
||||
uint32_t ver_minor = f->get_32();
|
||||
f->get_32(); // patch number, not used for validation.
|
||||
|
||||
ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, "Pack version unsupported: " + itos(version) + ".");
|
||||
ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + ".");
|
||||
|
||||
uint32_t pack_flags = f->get_32();
|
||||
uint64_t file_base = f->get_64();
|
||||
|
||||
bool enc_directory = (pack_flags & PACK_DIR_ENCRYPTED);
|
||||
bool rel_filebase = (pack_flags & PACK_REL_FILEBASE);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
//reserved
|
||||
f->get_32();
|
||||
}
|
||||
|
||||
int file_count = f->get_32();
|
||||
|
||||
if (rel_filebase) {
|
||||
file_base += pck_start_pos;
|
||||
}
|
||||
|
||||
if (enc_directory) {
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
ERR_FAIL_COND_V_MSG(fae.is_null(), false, "Can't open encrypted pack directory.");
|
||||
|
||||
Vector<uint8_t> key;
|
||||
key.resize(32);
|
||||
for (int i = 0; i < key.size(); i++) {
|
||||
key.write[i] = script_encryption_key[i];
|
||||
}
|
||||
|
||||
Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
|
||||
ERR_FAIL_COND_V_MSG(err, false, "Can't open encrypted pack directory.");
|
||||
f = fae;
|
||||
}
|
||||
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
uint32_t sl = f->get_32();
|
||||
CharString cs;
|
||||
cs.resize(sl + 1);
|
||||
f->get_buffer((uint8_t *)cs.ptr(), sl);
|
||||
cs[sl] = 0;
|
||||
|
||||
String path;
|
||||
path.parse_utf8(cs.ptr());
|
||||
|
||||
uint64_t ofs = file_base + f->get_64();
|
||||
uint64_t size = f->get_64();
|
||||
uint8_t md5[16];
|
||||
f->get_buffer(md5, 16);
|
||||
uint32_t flags = f->get_32();
|
||||
|
||||
PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<FileAccess> PackedSourcePCK::get_file(const String &p_path, PackedData::PackedFile *p_file) {
|
||||
return memnew(FileAccessPack(p_path, *p_file));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
Error FileAccessPack::open_internal(const String &p_path, int p_mode_flags) {
|
||||
ERR_PRINT("Can't open pack-referenced file.");
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
bool FileAccessPack::is_open() const {
|
||||
if (f.is_valid()) {
|
||||
return f->is_open();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessPack::seek(uint64_t p_position) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
|
||||
if (p_position > pf.size) {
|
||||
eof = true;
|
||||
} else {
|
||||
eof = false;
|
||||
}
|
||||
|
||||
f->seek(off + p_position);
|
||||
pos = p_position;
|
||||
}
|
||||
|
||||
void FileAccessPack::seek_end(int64_t p_position) {
|
||||
seek(pf.size + p_position);
|
||||
}
|
||||
|
||||
uint64_t FileAccessPack::get_position() const {
|
||||
return pos;
|
||||
}
|
||||
|
||||
uint64_t FileAccessPack::get_length() const {
|
||||
return pf.size;
|
||||
}
|
||||
|
||||
bool FileAccessPack::eof_reached() const {
|
||||
return eof;
|
||||
}
|
||||
|
||||
uint8_t FileAccessPack::get_8() const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
|
||||
if (pos >= pf.size) {
|
||||
eof = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pos++;
|
||||
return f->get_8();
|
||||
}
|
||||
|
||||
uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
|
||||
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
|
||||
|
||||
if (eof) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t to_read = p_length;
|
||||
if (to_read + pos > pf.size) {
|
||||
eof = true;
|
||||
to_read = (int64_t)pf.size - (int64_t)pos;
|
||||
}
|
||||
|
||||
pos += to_read;
|
||||
|
||||
if (to_read <= 0) {
|
||||
return 0;
|
||||
}
|
||||
f->get_buffer(p_dst, to_read);
|
||||
|
||||
return to_read;
|
||||
}
|
||||
|
||||
void FileAccessPack::set_big_endian(bool p_big_endian) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
|
||||
FileAccess::set_big_endian(p_big_endian);
|
||||
f->set_big_endian(p_big_endian);
|
||||
}
|
||||
|
||||
Error FileAccessPack::get_error() const {
|
||||
if (eof) {
|
||||
return ERR_FILE_EOF;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void FileAccessPack::flush() {
|
||||
ERR_FAIL();
|
||||
}
|
||||
|
||||
void FileAccessPack::store_8(uint8_t p_dest) {
|
||||
ERR_FAIL();
|
||||
}
|
||||
|
||||
void FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) {
|
||||
ERR_FAIL();
|
||||
}
|
||||
|
||||
bool FileAccessPack::file_exists(const String &p_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileAccessPack::close() {
|
||||
f = Ref<FileAccess>();
|
||||
}
|
||||
|
||||
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->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) + "'.");
|
||||
|
||||
Vector<uint8_t> key;
|
||||
key.resize(32);
|
||||
for (int i = 0; i < key.size(); i++) {
|
||||
key.write[i] = script_encryption_key[i];
|
||||
}
|
||||
|
||||
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) + "'.");
|
||||
f = fae;
|
||||
off = 0;
|
||||
}
|
||||
pos = 0;
|
||||
eof = false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// DIR ACCESS
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Error DirAccessPack::list_dir_begin() {
|
||||
list_dirs.clear();
|
||||
list_files.clear();
|
||||
|
||||
for (const KeyValue<String, PackedData::PackedDir *> &E : current->subdirs) {
|
||||
list_dirs.push_back(E.key);
|
||||
}
|
||||
|
||||
for (const String &E : current->files) {
|
||||
list_files.push_back(E);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
String DirAccessPack::get_next() {
|
||||
if (list_dirs.size()) {
|
||||
cdir = true;
|
||||
String d = list_dirs.front()->get();
|
||||
list_dirs.pop_front();
|
||||
return d;
|
||||
} else if (list_files.size()) {
|
||||
cdir = false;
|
||||
String f = list_files.front()->get();
|
||||
list_files.pop_front();
|
||||
return f;
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
bool DirAccessPack::current_is_dir() const {
|
||||
return cdir;
|
||||
}
|
||||
|
||||
bool DirAccessPack::current_is_hidden() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DirAccessPack::list_dir_end() {
|
||||
list_dirs.clear();
|
||||
list_files.clear();
|
||||
}
|
||||
|
||||
int DirAccessPack::get_drive_count() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String DirAccessPack::get_drive(int p_drive) {
|
||||
return "";
|
||||
}
|
||||
|
||||
PackedData::PackedDir *DirAccessPack::_find_dir(const String &p_dir) {
|
||||
String nd = p_dir.replace("\\", "/");
|
||||
|
||||
// Special handling since simplify_path() will forbid it
|
||||
if (p_dir == "..") {
|
||||
return current->parent;
|
||||
}
|
||||
|
||||
bool absolute = false;
|
||||
if (nd.begins_with("res://")) {
|
||||
nd = nd.replace_first("res://", "");
|
||||
absolute = true;
|
||||
}
|
||||
|
||||
nd = nd.simplify_path();
|
||||
|
||||
if (nd.is_empty()) {
|
||||
nd = ".";
|
||||
}
|
||||
|
||||
if (nd.begins_with("/")) {
|
||||
nd = nd.replace_first("/", "");
|
||||
absolute = true;
|
||||
}
|
||||
|
||||
Vector<String> paths = nd.split("/");
|
||||
|
||||
PackedData::PackedDir *pd;
|
||||
|
||||
if (absolute) {
|
||||
pd = PackedData::get_singleton()->root;
|
||||
} else {
|
||||
pd = current;
|
||||
}
|
||||
|
||||
for (int i = 0; i < paths.size(); i++) {
|
||||
const String &p = paths[i];
|
||||
if (p == ".") {
|
||||
continue;
|
||||
} else if (p == "..") {
|
||||
if (pd->parent) {
|
||||
pd = pd->parent;
|
||||
}
|
||||
} else if (pd->subdirs.has(p)) {
|
||||
pd = pd->subdirs[p];
|
||||
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return pd;
|
||||
}
|
||||
|
||||
Error DirAccessPack::change_dir(String p_dir) {
|
||||
PackedData::PackedDir *pd = _find_dir(p_dir);
|
||||
if (pd) {
|
||||
current = pd;
|
||||
return OK;
|
||||
} else {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
String DirAccessPack::get_current_dir(bool p_include_drive) const {
|
||||
PackedData::PackedDir *pd = current;
|
||||
String p = current->name;
|
||||
|
||||
while (pd->parent) {
|
||||
pd = pd->parent;
|
||||
p = pd->name.path_join(p);
|
||||
}
|
||||
|
||||
return "res://" + p;
|
||||
}
|
||||
|
||||
bool DirAccessPack::file_exists(String p_file) {
|
||||
p_file = fix_path(p_file);
|
||||
|
||||
PackedData::PackedDir *pd = _find_dir(p_file.get_base_dir());
|
||||
if (!pd) {
|
||||
return false;
|
||||
}
|
||||
return pd->files.has(p_file.get_file());
|
||||
}
|
||||
|
||||
bool DirAccessPack::dir_exists(String p_dir) {
|
||||
p_dir = fix_path(p_dir);
|
||||
|
||||
return _find_dir(p_dir) != nullptr;
|
||||
}
|
||||
|
||||
Error DirAccessPack::make_dir(String p_dir) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Error DirAccessPack::rename(String p_from, String p_to) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Error DirAccessPack::remove(String p_name) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
uint64_t DirAccessPack::get_space_left() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String DirAccessPack::get_filesystem_type() const {
|
||||
return "PCK";
|
||||
}
|
||||
|
||||
DirAccessPack::DirAccessPack() {
|
||||
current = PackedData::get_singleton()->root;
|
||||
}
|
||||
269
engine/core/io/file_access_pack.h
Normal file
269
engine/core/io/file_access_pack.h
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_pack.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef FILE_ACCESS_PACK_H
|
||||
#define FILE_ACCESS_PACK_H
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "core/templates/list.h"
|
||||
#include "core/templates/rb_map.h"
|
||||
|
||||
// Godot's packed file magic header ("GDPC" in ASCII).
|
||||
#define PACK_HEADER_MAGIC 0x43504447
|
||||
// The current packed file format version number.
|
||||
#define PACK_FORMAT_VERSION 2
|
||||
|
||||
enum PackFlags {
|
||||
PACK_DIR_ENCRYPTED = 1 << 0,
|
||||
PACK_REL_FILEBASE = 1 << 1,
|
||||
};
|
||||
|
||||
enum PackFileFlags {
|
||||
PACK_FILE_ENCRYPTED = 1 << 0
|
||||
};
|
||||
|
||||
class PackSource;
|
||||
|
||||
class PackedData {
|
||||
friend class FileAccessPack;
|
||||
friend class DirAccessPack;
|
||||
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 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));
|
||||
}
|
||||
|
||||
PathMD5() {}
|
||||
|
||||
explicit PathMD5(const Vector<uint8_t> &p_buf) {
|
||||
a = *((uint64_t *)&p_buf[0]);
|
||||
b = *((uint64_t *)&p_buf[8]);
|
||||
}
|
||||
};
|
||||
|
||||
HashMap<PathMD5, PackedFile, PathMD5> files;
|
||||
|
||||
Vector<PackSource *> sources;
|
||||
|
||||
PackedDir *root = nullptr;
|
||||
|
||||
static PackedData *singleton;
|
||||
bool disabled = false;
|
||||
|
||||
void _free_packed_dirs(PackedDir *p_dir);
|
||||
|
||||
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 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);
|
||||
|
||||
_FORCE_INLINE_ Ref<FileAccess> try_open_path(const String &p_path);
|
||||
_FORCE_INLINE_ bool has_path(const String &p_path);
|
||||
|
||||
_FORCE_INLINE_ Ref<DirAccess> try_open_directory(const String &p_path);
|
||||
_FORCE_INLINE_ bool has_directory(const String &p_path);
|
||||
|
||||
PackedData();
|
||||
~PackedData();
|
||||
};
|
||||
|
||||
class PackSource {
|
||||
public:
|
||||
virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) = 0;
|
||||
virtual Ref<FileAccess> get_file(const String &p_path, PackedData::PackedFile *p_file) = 0;
|
||||
virtual ~PackSource() {}
|
||||
};
|
||||
|
||||
class PackedSourcePCK : public PackSource {
|
||||
public:
|
||||
virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) override;
|
||||
virtual Ref<FileAccess> get_file(const String &p_path, PackedData::PackedFile *p_file) override;
|
||||
};
|
||||
|
||||
class FileAccessPack : public FileAccess {
|
||||
PackedData::PackedFile pf;
|
||||
|
||||
mutable uint64_t pos;
|
||||
mutable bool eof;
|
||||
uint64_t off;
|
||||
|
||||
Ref<FileAccess> f;
|
||||
virtual Error open_internal(const String &p_path, int p_mode_flags) override;
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override { return 0; }
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::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; }
|
||||
|
||||
public:
|
||||
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 uint8_t get_8() const override;
|
||||
|
||||
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
|
||||
|
||||
virtual void set_big_endian(bool p_big_endian) override;
|
||||
|
||||
virtual Error get_error() const override;
|
||||
|
||||
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
|
||||
virtual void flush() override;
|
||||
virtual void store_8(uint8_t p_dest) override;
|
||||
|
||||
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
|
||||
|
||||
virtual bool file_exists(const String &p_name) override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file);
|
||||
};
|
||||
|
||||
Ref<FileAccess> PackedData::try_open_path(const String &p_path) {
|
||||
String simplified_path = p_path.simplify_path();
|
||||
PathMD5 pmd5(simplified_path.md5_buffer());
|
||||
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
|
||||
if (!E) {
|
||||
return nullptr; //not found
|
||||
}
|
||||
if (E->value.offset == 0) {
|
||||
return nullptr; //was erased
|
||||
}
|
||||
|
||||
return E->value.src->get_file(p_path, &E->value);
|
||||
}
|
||||
|
||||
bool PackedData::has_path(const String &p_path) {
|
||||
return files.has(PathMD5(p_path.simplify_path().md5_buffer()));
|
||||
}
|
||||
|
||||
bool PackedData::has_directory(const String &p_path) {
|
||||
Ref<DirAccess> da = try_open_directory(p_path);
|
||||
if (da.is_valid()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class DirAccessPack : public DirAccess {
|
||||
PackedData::PackedDir *current;
|
||||
|
||||
List<String> list_dirs;
|
||||
List<String> list_files;
|
||||
bool cdir = false;
|
||||
|
||||
PackedData::PackedDir *_find_dir(const String &p_dir);
|
||||
|
||||
public:
|
||||
virtual Error list_dir_begin() override;
|
||||
virtual String get_next() override;
|
||||
virtual bool current_is_dir() const override;
|
||||
virtual bool current_is_hidden() const override;
|
||||
virtual void list_dir_end() override;
|
||||
|
||||
virtual int get_drive_count() override;
|
||||
virtual String get_drive(int p_drive) override;
|
||||
|
||||
virtual Error change_dir(String p_dir) override;
|
||||
virtual String get_current_dir(bool p_include_drive = true) const override;
|
||||
|
||||
virtual bool file_exists(String p_file) override;
|
||||
virtual bool dir_exists(String p_dir) override;
|
||||
|
||||
virtual Error make_dir(String p_dir) override;
|
||||
|
||||
virtual Error rename(String p_from, String p_to) override;
|
||||
virtual Error remove(String p_name) override;
|
||||
|
||||
uint64_t get_space_left() override;
|
||||
|
||||
virtual bool is_link(String p_file) override { return false; }
|
||||
virtual String read_link(String p_file) override { return p_file; }
|
||||
virtual Error create_link(String p_source, String p_target) override { return FAILED; }
|
||||
|
||||
virtual String get_filesystem_type() const override;
|
||||
|
||||
DirAccessPack();
|
||||
};
|
||||
|
||||
Ref<DirAccess> PackedData::try_open_directory(const String &p_path) {
|
||||
Ref<DirAccess> da = memnew(DirAccessPack());
|
||||
if (da->change_dir(p_path) != OK) {
|
||||
da = Ref<DirAccess>();
|
||||
}
|
||||
return da;
|
||||
}
|
||||
|
||||
#endif // FILE_ACCESS_PACK_H
|
||||
351
engine/core/io/file_access_zip.cpp
Normal file
351
engine/core/io/file_access_zip.cpp
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_zip.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifdef MINIZIP_ENABLED
|
||||
|
||||
#include "file_access_zip.h"
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
ZipArchive *ZipArchive::instance = nullptr;
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct ZipData {
|
||||
Ref<FileAccess> f;
|
||||
};
|
||||
|
||||
static void *godot_open(voidpf opaque, const char *p_fname, int mode) {
|
||||
if (mode & ZLIB_FILEFUNC_MODE_WRITE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(String::utf8(p_fname), FileAccess::READ);
|
||||
ERR_FAIL_COND_V(f.is_null(), nullptr);
|
||||
|
||||
ZipData *zd = memnew(ZipData);
|
||||
zd->f = f;
|
||||
return zd;
|
||||
}
|
||||
|
||||
static uLong godot_read(voidpf opaque, voidpf stream, void *buf, uLong size) {
|
||||
ZipData *zd = (ZipData *)stream;
|
||||
zd->f->get_buffer((uint8_t *)buf, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
static uLong godot_write(voidpf opaque, voidpf stream, const void *buf, uLong size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long godot_tell(voidpf opaque, voidpf stream) {
|
||||
ZipData *zd = (ZipData *)stream;
|
||||
return zd->f->get_position();
|
||||
}
|
||||
|
||||
static long godot_seek(voidpf opaque, voidpf stream, uLong offset, int origin) {
|
||||
ZipData *zd = (ZipData *)stream;
|
||||
|
||||
uint64_t pos = offset;
|
||||
switch (origin) {
|
||||
case ZLIB_FILEFUNC_SEEK_CUR:
|
||||
pos = zd->f->get_position() + offset;
|
||||
break;
|
||||
case ZLIB_FILEFUNC_SEEK_END:
|
||||
pos = zd->f->get_length() + offset;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
zd->f->seek(pos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int godot_close(voidpf opaque, voidpf stream) {
|
||||
ZipData *zd = (ZipData *)stream;
|
||||
memdelete(zd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int godot_testerror(voidpf opaque, voidpf stream) {
|
||||
ZipData *zd = (ZipData *)stream;
|
||||
return zd->f->get_error() != OK ? 1 : 0;
|
||||
}
|
||||
|
||||
static voidpf godot_alloc(voidpf opaque, uInt items, uInt size) {
|
||||
return memalloc((size_t)items * size);
|
||||
}
|
||||
|
||||
static void godot_free(voidpf opaque, voidpf address) {
|
||||
memfree(address);
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
void ZipArchive::close_handle(unzFile p_file) const {
|
||||
ERR_FAIL_NULL_MSG(p_file, "Cannot close a file if none is open.");
|
||||
unzCloseCurrentFile(p_file);
|
||||
unzClose(p_file);
|
||||
}
|
||||
|
||||
unzFile ZipArchive::get_file_handle(const String &p_file) const {
|
||||
ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, "File '" + p_file + " doesn't exist.");
|
||||
File file = files[p_file];
|
||||
|
||||
zlib_filefunc_def io;
|
||||
memset(&io, 0, sizeof(io));
|
||||
|
||||
io.opaque = nullptr;
|
||||
io.zopen_file = godot_open;
|
||||
io.zread_file = godot_read;
|
||||
io.zwrite_file = godot_write;
|
||||
|
||||
io.ztell_file = godot_tell;
|
||||
io.zseek_file = godot_seek;
|
||||
io.zclose_file = godot_close;
|
||||
io.zerror_file = godot_testerror;
|
||||
|
||||
io.alloc_mem = godot_alloc;
|
||||
io.free_mem = godot_free;
|
||||
|
||||
unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io);
|
||||
ERR_FAIL_NULL_V_MSG(pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'.");
|
||||
int unz_err = unzGoToFilePos(pkg, &file.file_pos);
|
||||
if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) {
|
||||
unzClose(pkg);
|
||||
ERR_FAIL_V(nullptr);
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset = 0) {
|
||||
// load with offset feature only supported for PCK files
|
||||
ERR_FAIL_COND_V_MSG(p_offset != 0, false, "Invalid PCK data. Note that loading files with a non-zero offset isn't supported with ZIP archives.");
|
||||
|
||||
if (p_path.get_extension().nocasecmp_to("zip") != 0 && p_path.get_extension().nocasecmp_to("pcz") != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
zlib_filefunc_def io;
|
||||
memset(&io, 0, sizeof(io));
|
||||
|
||||
io.opaque = nullptr;
|
||||
io.zopen_file = godot_open;
|
||||
io.zread_file = godot_read;
|
||||
io.zwrite_file = godot_write;
|
||||
|
||||
io.ztell_file = godot_tell;
|
||||
io.zseek_file = godot_seek;
|
||||
io.zclose_file = godot_close;
|
||||
io.zerror_file = godot_testerror;
|
||||
|
||||
unzFile zfile = unzOpen2(p_path.utf8().get_data(), &io);
|
||||
ERR_FAIL_NULL_V(zfile, false);
|
||||
|
||||
unz_global_info64 gi;
|
||||
int err = unzGetGlobalInfo64(zfile, &gi);
|
||||
ERR_FAIL_COND_V(err != UNZ_OK, false);
|
||||
|
||||
Package pkg;
|
||||
pkg.filename = p_path;
|
||||
pkg.zfile = zfile;
|
||||
packages.push_back(pkg);
|
||||
int pkg_num = packages.size() - 1;
|
||||
|
||||
for (uint64_t i = 0; i < gi.number_entry; i++) {
|
||||
char filename_inzip[256];
|
||||
|
||||
unz_file_info64 file_info;
|
||||
err = unzGetCurrentFileInfo64(zfile, &file_info, filename_inzip, sizeof(filename_inzip), nullptr, 0, nullptr, 0);
|
||||
ERR_CONTINUE(err != UNZ_OK);
|
||||
|
||||
File f;
|
||||
f.package = pkg_num;
|
||||
unzGetFilePos(zfile, &f.file_pos);
|
||||
|
||||
String fname = String("res://") + String::utf8(filename_inzip);
|
||||
files[fname] = f;
|
||||
|
||||
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, fname, 1, 0, md5, this, p_replace_files, false);
|
||||
//printf("packed data add path %s, %s\n", p_name.utf8().get_data(), fname.utf8().get_data());
|
||||
|
||||
if ((i + 1) < gi.number_entry) {
|
||||
unzGoToNextFile(zfile);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ZipArchive::file_exists(const String &p_name) const {
|
||||
return files.has(p_name);
|
||||
}
|
||||
|
||||
Ref<FileAccess> ZipArchive::get_file(const String &p_path, PackedData::PackedFile *p_file) {
|
||||
return memnew(FileAccessZip(p_path, *p_file));
|
||||
}
|
||||
|
||||
ZipArchive *ZipArchive::get_singleton() {
|
||||
if (instance == nullptr) {
|
||||
instance = memnew(ZipArchive);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
ZipArchive::ZipArchive() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
ZipArchive::~ZipArchive() {
|
||||
for (int i = 0; i < packages.size(); i++) {
|
||||
unzClose(packages[i].zfile);
|
||||
}
|
||||
|
||||
packages.clear();
|
||||
}
|
||||
|
||||
Error FileAccessZip::open_internal(const String &p_path, int p_mode_flags) {
|
||||
_close();
|
||||
|
||||
ERR_FAIL_COND_V(p_mode_flags & FileAccess::WRITE, FAILED);
|
||||
ZipArchive *arch = ZipArchive::get_singleton();
|
||||
ERR_FAIL_NULL_V(arch, FAILED);
|
||||
zfile = arch->get_file_handle(p_path);
|
||||
ERR_FAIL_NULL_V(zfile, FAILED);
|
||||
|
||||
int err = unzGetCurrentFileInfo64(zfile, &file_info, nullptr, 0, nullptr, 0, nullptr, 0);
|
||||
ERR_FAIL_COND_V(err != UNZ_OK, FAILED);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void FileAccessZip::_close() {
|
||||
if (!zfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
ZipArchive *arch = ZipArchive::get_singleton();
|
||||
ERR_FAIL_NULL(arch);
|
||||
arch->close_handle(zfile);
|
||||
zfile = nullptr;
|
||||
}
|
||||
|
||||
bool FileAccessZip::is_open() const {
|
||||
return zfile != nullptr;
|
||||
}
|
||||
|
||||
void FileAccessZip::seek(uint64_t p_position) {
|
||||
ERR_FAIL_NULL(zfile);
|
||||
|
||||
unzSeekCurrentFile(zfile, p_position);
|
||||
}
|
||||
|
||||
void FileAccessZip::seek_end(int64_t p_position) {
|
||||
ERR_FAIL_NULL(zfile);
|
||||
unzSeekCurrentFile(zfile, get_length() + p_position);
|
||||
}
|
||||
|
||||
uint64_t FileAccessZip::get_position() const {
|
||||
ERR_FAIL_NULL_V(zfile, 0);
|
||||
return unztell64(zfile);
|
||||
}
|
||||
|
||||
uint64_t FileAccessZip::get_length() const {
|
||||
ERR_FAIL_NULL_V(zfile, 0);
|
||||
return file_info.uncompressed_size;
|
||||
}
|
||||
|
||||
bool FileAccessZip::eof_reached() const {
|
||||
ERR_FAIL_NULL_V(zfile, true);
|
||||
|
||||
return at_eof;
|
||||
}
|
||||
|
||||
uint8_t FileAccessZip::get_8() const {
|
||||
uint8_t ret = 0;
|
||||
get_buffer(&ret, 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
|
||||
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
|
||||
ERR_FAIL_NULL_V(zfile, -1);
|
||||
|
||||
at_eof = unzeof(zfile);
|
||||
if (at_eof) {
|
||||
return 0;
|
||||
}
|
||||
int64_t read = unzReadCurrentFile(zfile, p_dst, p_length);
|
||||
ERR_FAIL_COND_V(read < 0, read);
|
||||
if ((uint64_t)read < p_length) {
|
||||
at_eof = true;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
Error FileAccessZip::get_error() const {
|
||||
if (!zfile) {
|
||||
return ERR_UNCONFIGURED;
|
||||
}
|
||||
if (eof_reached()) {
|
||||
return ERR_FILE_EOF;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void FileAccessZip::flush() {
|
||||
ERR_FAIL();
|
||||
}
|
||||
|
||||
void FileAccessZip::store_8(uint8_t p_dest) {
|
||||
ERR_FAIL();
|
||||
}
|
||||
|
||||
bool FileAccessZip::file_exists(const String &p_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileAccessZip::close() {
|
||||
_close();
|
||||
}
|
||||
|
||||
FileAccessZip::FileAccessZip(const String &p_path, const PackedData::PackedFile &p_file) {
|
||||
open_internal(p_path, FileAccess::READ);
|
||||
}
|
||||
|
||||
FileAccessZip::~FileAccessZip() {
|
||||
_close();
|
||||
}
|
||||
|
||||
#endif // MINIZIP_ENABLED
|
||||
126
engine/core/io/file_access_zip.h
Normal file
126
engine/core/io/file_access_zip.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**************************************************************************/
|
||||
/* file_access_zip.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef FILE_ACCESS_ZIP_H
|
||||
#define FILE_ACCESS_ZIP_H
|
||||
|
||||
#ifdef MINIZIP_ENABLED
|
||||
|
||||
#include "core/io/file_access_pack.h"
|
||||
#include "core/templates/rb_map.h"
|
||||
|
||||
#include "thirdparty/minizip/unzip.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
class ZipArchive : public PackSource {
|
||||
public:
|
||||
struct File {
|
||||
int package = -1;
|
||||
unz_file_pos file_pos;
|
||||
File() {}
|
||||
};
|
||||
|
||||
private:
|
||||
struct Package {
|
||||
String filename;
|
||||
unzFile zfile = nullptr;
|
||||
};
|
||||
Vector<Package> packages;
|
||||
|
||||
HashMap<String, File> files;
|
||||
|
||||
static ZipArchive *instance;
|
||||
|
||||
public:
|
||||
void close_handle(unzFile p_file) const;
|
||||
unzFile get_file_handle(const String &p_file) const;
|
||||
|
||||
Error add_package(const String &p_name);
|
||||
|
||||
bool file_exists(const String &p_name) const;
|
||||
|
||||
virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) override;
|
||||
Ref<FileAccess> get_file(const String &p_path, PackedData::PackedFile *p_file) override;
|
||||
|
||||
static ZipArchive *get_singleton();
|
||||
|
||||
ZipArchive();
|
||||
~ZipArchive();
|
||||
};
|
||||
|
||||
class FileAccessZip : public FileAccess {
|
||||
unzFile zfile = nullptr;
|
||||
unz_file_info64 file_info;
|
||||
|
||||
mutable bool at_eof = false;
|
||||
|
||||
void _close();
|
||||
|
||||
public:
|
||||
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
|
||||
virtual bool is_open() const override; ///< true when file is open
|
||||
|
||||
virtual void seek(uint64_t p_position) override; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position = 0) override; ///< seek from the end of file
|
||||
virtual uint64_t get_position() const override; ///< get position in the file
|
||||
virtual uint64_t get_length() const override; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const override; ///< reading passed EOF
|
||||
|
||||
virtual uint8_t get_8() const override; ///< get a byte
|
||||
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
|
||||
|
||||
virtual Error get_error() const override; ///< get last error
|
||||
|
||||
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
|
||||
virtual void flush() override;
|
||||
virtual void store_8(uint8_t p_dest) override; ///< store a byte
|
||||
|
||||
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
|
||||
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override { return 0; } // todo
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::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 void close() override;
|
||||
|
||||
FileAccessZip(const String &p_path, const PackedData::PackedFile &p_file);
|
||||
~FileAccessZip();
|
||||
};
|
||||
|
||||
#endif // MINIZIP_ENABLED
|
||||
|
||||
#endif // FILE_ACCESS_ZIP_H
|
||||
265
engine/core/io/http_client.cpp
Normal file
265
engine/core/io/http_client.cpp
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
/**************************************************************************/
|
||||
/* http_client.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 "http_client.h"
|
||||
|
||||
const char *HTTPClient::_methods[METHOD_MAX] = {
|
||||
"GET",
|
||||
"HEAD",
|
||||
"POST",
|
||||
"PUT",
|
||||
"DELETE",
|
||||
"OPTIONS",
|
||||
"TRACE",
|
||||
"CONNECT",
|
||||
"PATCH"
|
||||
};
|
||||
|
||||
HTTPClient *HTTPClient::create() {
|
||||
if (_create) {
|
||||
return _create();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HTTPClient::set_http_proxy(const String &p_host, int p_port) {
|
||||
WARN_PRINT("HTTP proxy feature is not available");
|
||||
}
|
||||
|
||||
void HTTPClient::set_https_proxy(const String &p_host, int p_port) {
|
||||
WARN_PRINT("HTTPS proxy feature is not available");
|
||||
}
|
||||
|
||||
Error HTTPClient::_request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
|
||||
int size = p_body.size();
|
||||
return request(p_method, p_url, p_headers, size > 0 ? p_body.ptr() : nullptr, size);
|
||||
}
|
||||
|
||||
Error HTTPClient::_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
|
||||
CharString body_utf8 = p_body.utf8();
|
||||
int size = body_utf8.length();
|
||||
return request(p_method, p_url, p_headers, size > 0 ? (const uint8_t *)body_utf8.get_data() : nullptr, size);
|
||||
}
|
||||
|
||||
String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
|
||||
String query = "";
|
||||
Array keys = p_dict.keys();
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
String encoded_key = String(keys[i]).uri_encode();
|
||||
const Variant &value = p_dict[keys[i]];
|
||||
switch (value.get_type()) {
|
||||
case Variant::ARRAY: {
|
||||
// Repeat the key with every values
|
||||
Array values = value;
|
||||
for (int j = 0; j < values.size(); ++j) {
|
||||
query += "&" + encoded_key + "=" + String(values[j]).uri_encode();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Variant::NIL: {
|
||||
// Add the key with no value
|
||||
query += "&" + encoded_key;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Add the key-value pair
|
||||
query += "&" + encoded_key + "=" + String(value).uri_encode();
|
||||
}
|
||||
}
|
||||
}
|
||||
return query.substr(1);
|
||||
}
|
||||
|
||||
Error HTTPClient::verify_headers(const Vector<String> &p_headers) {
|
||||
for (int i = 0; i < p_headers.size(); i++) {
|
||||
String sanitized = p_headers[i].strip_edges();
|
||||
ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, "Invalid HTTP header at index " + itos(i) + ": empty.");
|
||||
ERR_FAIL_COND_V_MSG(sanitized.find(":") < 1, ERR_INVALID_PARAMETER,
|
||||
"Invalid HTTP header at index " + itos(i) + ": String must contain header-value pair, delimited by ':', but was: " + p_headers[i]);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Dictionary HTTPClient::_get_response_headers_as_dictionary() {
|
||||
List<String> rh;
|
||||
get_response_headers(&rh);
|
||||
Dictionary ret;
|
||||
for (const String &s : rh) {
|
||||
int sp = s.find(":");
|
||||
if (sp == -1) {
|
||||
continue;
|
||||
}
|
||||
String key = s.substr(0, sp).strip_edges();
|
||||
String value = s.substr(sp + 1, s.length()).strip_edges();
|
||||
ret[key] = value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PackedStringArray HTTPClient::_get_response_headers() {
|
||||
List<String> rh;
|
||||
get_response_headers(&rh);
|
||||
PackedStringArray ret;
|
||||
ret.resize(rh.size());
|
||||
int idx = 0;
|
||||
for (const String &E : rh) {
|
||||
ret.set(idx++, E);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HTTPClient::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "tls_options"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(Ref<TLSOptions>()));
|
||||
ClassDB::bind_method(D_METHOD("set_connection", "connection"), &HTTPClient::set_connection);
|
||||
ClassDB::bind_method(D_METHOD("get_connection"), &HTTPClient::get_connection);
|
||||
ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::_request_raw);
|
||||
ClassDB::bind_method(D_METHOD("request", "method", "url", "headers", "body"), &HTTPClient::_request, DEFVAL(String()));
|
||||
ClassDB::bind_method(D_METHOD("close"), &HTTPClient::close);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_response"), &HTTPClient::has_response);
|
||||
ClassDB::bind_method(D_METHOD("is_response_chunked"), &HTTPClient::is_response_chunked);
|
||||
ClassDB::bind_method(D_METHOD("get_response_code"), &HTTPClient::get_response_code);
|
||||
ClassDB::bind_method(D_METHOD("get_response_headers"), &HTTPClient::_get_response_headers);
|
||||
ClassDB::bind_method(D_METHOD("get_response_headers_as_dictionary"), &HTTPClient::_get_response_headers_as_dictionary);
|
||||
ClassDB::bind_method(D_METHOD("get_response_body_length"), &HTTPClient::get_response_body_length);
|
||||
ClassDB::bind_method(D_METHOD("read_response_body_chunk"), &HTTPClient::read_response_body_chunk);
|
||||
ClassDB::bind_method(D_METHOD("set_read_chunk_size", "bytes"), &HTTPClient::set_read_chunk_size);
|
||||
ClassDB::bind_method(D_METHOD("get_read_chunk_size"), &HTTPClient::get_read_chunk_size);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_blocking_mode", "enabled"), &HTTPClient::set_blocking_mode);
|
||||
ClassDB::bind_method(D_METHOD("is_blocking_mode_enabled"), &HTTPClient::is_blocking_mode_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_status"), &HTTPClient::get_status);
|
||||
ClassDB::bind_method(D_METHOD("poll"), &HTTPClient::poll);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_http_proxy", "host", "port"), &HTTPClient::set_http_proxy);
|
||||
ClassDB::bind_method(D_METHOD("set_https_proxy", "host", "port"), &HTTPClient::set_https_proxy);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("query_string_from_dict", "fields"), &HTTPClient::query_string_from_dict);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blocking_mode_enabled"), "set_blocking_mode", "is_blocking_mode_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "connection", PROPERTY_HINT_RESOURCE_TYPE, "StreamPeer", PROPERTY_USAGE_NONE), "set_connection", "get_connection");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "read_chunk_size", PROPERTY_HINT_RANGE, "256,16777216"), "set_read_chunk_size", "get_read_chunk_size");
|
||||
|
||||
BIND_ENUM_CONSTANT(METHOD_GET);
|
||||
BIND_ENUM_CONSTANT(METHOD_HEAD);
|
||||
BIND_ENUM_CONSTANT(METHOD_POST);
|
||||
BIND_ENUM_CONSTANT(METHOD_PUT);
|
||||
BIND_ENUM_CONSTANT(METHOD_DELETE);
|
||||
BIND_ENUM_CONSTANT(METHOD_OPTIONS);
|
||||
BIND_ENUM_CONSTANT(METHOD_TRACE);
|
||||
BIND_ENUM_CONSTANT(METHOD_CONNECT);
|
||||
BIND_ENUM_CONSTANT(METHOD_PATCH);
|
||||
BIND_ENUM_CONSTANT(METHOD_MAX);
|
||||
|
||||
BIND_ENUM_CONSTANT(STATUS_DISCONNECTED);
|
||||
BIND_ENUM_CONSTANT(STATUS_RESOLVING); // Resolving hostname (if hostname was passed in)
|
||||
BIND_ENUM_CONSTANT(STATUS_CANT_RESOLVE);
|
||||
BIND_ENUM_CONSTANT(STATUS_CONNECTING); // Connecting to IP
|
||||
BIND_ENUM_CONSTANT(STATUS_CANT_CONNECT);
|
||||
BIND_ENUM_CONSTANT(STATUS_CONNECTED); // Connected, now accepting requests
|
||||
BIND_ENUM_CONSTANT(STATUS_REQUESTING); // Request in progress
|
||||
BIND_ENUM_CONSTANT(STATUS_BODY); // Request resulted in body which must be read
|
||||
BIND_ENUM_CONSTANT(STATUS_CONNECTION_ERROR);
|
||||
BIND_ENUM_CONSTANT(STATUS_TLS_HANDSHAKE_ERROR);
|
||||
|
||||
BIND_ENUM_CONSTANT(RESPONSE_CONTINUE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_SWITCHING_PROTOCOLS);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_PROCESSING);
|
||||
|
||||
// 2xx successful
|
||||
BIND_ENUM_CONSTANT(RESPONSE_OK);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_CREATED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_ACCEPTED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_NON_AUTHORITATIVE_INFORMATION);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_NO_CONTENT);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_RESET_CONTENT);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_PARTIAL_CONTENT);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_MULTI_STATUS);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_ALREADY_REPORTED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_IM_USED);
|
||||
|
||||
// 3xx redirection
|
||||
BIND_ENUM_CONSTANT(RESPONSE_MULTIPLE_CHOICES);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_MOVED_PERMANENTLY);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_FOUND);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_SEE_OTHER);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_NOT_MODIFIED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_USE_PROXY);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_SWITCH_PROXY);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_TEMPORARY_REDIRECT);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_PERMANENT_REDIRECT);
|
||||
|
||||
// 4xx client error
|
||||
BIND_ENUM_CONSTANT(RESPONSE_BAD_REQUEST);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_UNAUTHORIZED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_PAYMENT_REQUIRED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_FORBIDDEN);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_NOT_FOUND);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_METHOD_NOT_ALLOWED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_NOT_ACCEPTABLE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_PROXY_AUTHENTICATION_REQUIRED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_REQUEST_TIMEOUT);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_CONFLICT);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_GONE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_LENGTH_REQUIRED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_PRECONDITION_FAILED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_REQUEST_ENTITY_TOO_LARGE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_REQUEST_URI_TOO_LONG);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_UNSUPPORTED_MEDIA_TYPE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_EXPECTATION_FAILED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_IM_A_TEAPOT);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_MISDIRECTED_REQUEST);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_UNPROCESSABLE_ENTITY);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_LOCKED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_FAILED_DEPENDENCY);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_UPGRADE_REQUIRED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_PRECONDITION_REQUIRED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_TOO_MANY_REQUESTS);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_UNAVAILABLE_FOR_LEGAL_REASONS);
|
||||
|
||||
// 5xx server error
|
||||
BIND_ENUM_CONSTANT(RESPONSE_INTERNAL_SERVER_ERROR);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_NOT_IMPLEMENTED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_BAD_GATEWAY);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_SERVICE_UNAVAILABLE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_GATEWAY_TIMEOUT);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_HTTP_VERSION_NOT_SUPPORTED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_VARIANT_ALSO_NEGOTIATES);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_INSUFFICIENT_STORAGE);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_LOOP_DETECTED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_NOT_EXTENDED);
|
||||
BIND_ENUM_CONSTANT(RESPONSE_NETWORK_AUTH_REQUIRED);
|
||||
}
|
||||
209
engine/core/io/http_client.h
Normal file
209
engine/core/io/http_client.h
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/**************************************************************************/
|
||||
/* http_client.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef HTTP_CLIENT_H
|
||||
#define HTTP_CLIENT_H
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
#include "core/io/ip.h"
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class HTTPClient : public RefCounted {
|
||||
GDCLASS(HTTPClient, RefCounted);
|
||||
|
||||
public:
|
||||
enum ResponseCode {
|
||||
// 1xx informational
|
||||
RESPONSE_CONTINUE = 100,
|
||||
RESPONSE_SWITCHING_PROTOCOLS = 101,
|
||||
RESPONSE_PROCESSING = 102,
|
||||
|
||||
// 2xx successful
|
||||
RESPONSE_OK = 200,
|
||||
RESPONSE_CREATED = 201,
|
||||
RESPONSE_ACCEPTED = 202,
|
||||
RESPONSE_NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
RESPONSE_NO_CONTENT = 204,
|
||||
RESPONSE_RESET_CONTENT = 205,
|
||||
RESPONSE_PARTIAL_CONTENT = 206,
|
||||
RESPONSE_MULTI_STATUS = 207,
|
||||
RESPONSE_ALREADY_REPORTED = 208,
|
||||
RESPONSE_IM_USED = 226,
|
||||
|
||||
// 3xx redirection
|
||||
RESPONSE_MULTIPLE_CHOICES = 300,
|
||||
RESPONSE_MOVED_PERMANENTLY = 301,
|
||||
RESPONSE_FOUND = 302,
|
||||
RESPONSE_SEE_OTHER = 303,
|
||||
RESPONSE_NOT_MODIFIED = 304,
|
||||
RESPONSE_USE_PROXY = 305,
|
||||
RESPONSE_SWITCH_PROXY = 306,
|
||||
RESPONSE_TEMPORARY_REDIRECT = 307,
|
||||
RESPONSE_PERMANENT_REDIRECT = 308,
|
||||
|
||||
// 4xx client error
|
||||
RESPONSE_BAD_REQUEST = 400,
|
||||
RESPONSE_UNAUTHORIZED = 401,
|
||||
RESPONSE_PAYMENT_REQUIRED = 402,
|
||||
RESPONSE_FORBIDDEN = 403,
|
||||
RESPONSE_NOT_FOUND = 404,
|
||||
RESPONSE_METHOD_NOT_ALLOWED = 405,
|
||||
RESPONSE_NOT_ACCEPTABLE = 406,
|
||||
RESPONSE_PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
RESPONSE_REQUEST_TIMEOUT = 408,
|
||||
RESPONSE_CONFLICT = 409,
|
||||
RESPONSE_GONE = 410,
|
||||
RESPONSE_LENGTH_REQUIRED = 411,
|
||||
RESPONSE_PRECONDITION_FAILED = 412,
|
||||
RESPONSE_REQUEST_ENTITY_TOO_LARGE = 413,
|
||||
RESPONSE_REQUEST_URI_TOO_LONG = 414,
|
||||
RESPONSE_UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
RESPONSE_REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
||||
RESPONSE_EXPECTATION_FAILED = 417,
|
||||
RESPONSE_IM_A_TEAPOT = 418,
|
||||
RESPONSE_MISDIRECTED_REQUEST = 421,
|
||||
RESPONSE_UNPROCESSABLE_ENTITY = 422,
|
||||
RESPONSE_LOCKED = 423,
|
||||
RESPONSE_FAILED_DEPENDENCY = 424,
|
||||
RESPONSE_UPGRADE_REQUIRED = 426,
|
||||
RESPONSE_PRECONDITION_REQUIRED = 428,
|
||||
RESPONSE_TOO_MANY_REQUESTS = 429,
|
||||
RESPONSE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||
RESPONSE_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||
|
||||
// 5xx server error
|
||||
RESPONSE_INTERNAL_SERVER_ERROR = 500,
|
||||
RESPONSE_NOT_IMPLEMENTED = 501,
|
||||
RESPONSE_BAD_GATEWAY = 502,
|
||||
RESPONSE_SERVICE_UNAVAILABLE = 503,
|
||||
RESPONSE_GATEWAY_TIMEOUT = 504,
|
||||
RESPONSE_HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
RESPONSE_VARIANT_ALSO_NEGOTIATES = 506,
|
||||
RESPONSE_INSUFFICIENT_STORAGE = 507,
|
||||
RESPONSE_LOOP_DETECTED = 508,
|
||||
RESPONSE_NOT_EXTENDED = 510,
|
||||
RESPONSE_NETWORK_AUTH_REQUIRED = 511,
|
||||
|
||||
};
|
||||
|
||||
enum Method {
|
||||
METHOD_GET,
|
||||
METHOD_HEAD,
|
||||
METHOD_POST,
|
||||
METHOD_PUT,
|
||||
METHOD_DELETE,
|
||||
METHOD_OPTIONS,
|
||||
METHOD_TRACE,
|
||||
METHOD_CONNECT,
|
||||
METHOD_PATCH,
|
||||
METHOD_MAX
|
||||
|
||||
};
|
||||
|
||||
enum Status {
|
||||
STATUS_DISCONNECTED,
|
||||
STATUS_RESOLVING, // Resolving hostname (if passed a hostname)
|
||||
STATUS_CANT_RESOLVE,
|
||||
STATUS_CONNECTING, // Connecting to IP
|
||||
STATUS_CANT_CONNECT,
|
||||
STATUS_CONNECTED, // Connected, requests can be made
|
||||
STATUS_REQUESTING, // Request in progress
|
||||
STATUS_BODY, // Request resulted in body, which must be read
|
||||
STATUS_CONNECTION_ERROR,
|
||||
STATUS_TLS_HANDSHAKE_ERROR,
|
||||
|
||||
};
|
||||
|
||||
protected:
|
||||
static const char *_methods[METHOD_MAX];
|
||||
static const int HOST_MIN_LEN = 4;
|
||||
|
||||
enum Port {
|
||||
PORT_HTTP = 80,
|
||||
PORT_HTTPS = 443,
|
||||
|
||||
};
|
||||
|
||||
PackedStringArray _get_response_headers();
|
||||
Dictionary _get_response_headers_as_dictionary();
|
||||
Error _request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body);
|
||||
Error _request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body = String());
|
||||
|
||||
static HTTPClient *(*_create)();
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static HTTPClient *create();
|
||||
|
||||
String query_string_from_dict(const Dictionary &p_dict);
|
||||
Error verify_headers(const Vector<String> &p_headers);
|
||||
|
||||
virtual Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) = 0;
|
||||
virtual Error connect_to_host(const String &p_host, int p_port = -1, Ref<TLSOptions> p_tls_options = Ref<TLSOptions>()) = 0;
|
||||
|
||||
virtual void set_connection(const Ref<StreamPeer> &p_connection) = 0;
|
||||
virtual Ref<StreamPeer> get_connection() const = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual Status get_status() const = 0;
|
||||
|
||||
virtual bool has_response() const = 0;
|
||||
virtual bool is_response_chunked() const = 0;
|
||||
virtual int get_response_code() const = 0;
|
||||
virtual Error get_response_headers(List<String> *r_response) = 0;
|
||||
virtual int64_t get_response_body_length() const = 0;
|
||||
|
||||
virtual PackedByteArray read_response_body_chunk() = 0; // Can't get body as partial text because of most encodings UTF8, gzip, etc.
|
||||
|
||||
virtual void set_blocking_mode(bool p_enable) = 0; // Useful mostly if running in a thread
|
||||
virtual bool is_blocking_mode_enabled() const = 0;
|
||||
|
||||
virtual void set_read_chunk_size(int p_size) = 0;
|
||||
virtual int get_read_chunk_size() const = 0;
|
||||
|
||||
virtual Error poll() = 0;
|
||||
|
||||
// Use empty string or -1 to unset
|
||||
virtual void set_http_proxy(const String &p_host, int p_port);
|
||||
virtual void set_https_proxy(const String &p_host, int p_port);
|
||||
|
||||
HTTPClient() {}
|
||||
virtual ~HTTPClient() {}
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(HTTPClient::ResponseCode)
|
||||
VARIANT_ENUM_CAST(HTTPClient::Method);
|
||||
VARIANT_ENUM_CAST(HTTPClient::Status);
|
||||
|
||||
#endif // HTTP_CLIENT_H
|
||||
797
engine/core/io/http_client_tcp.cpp
Normal file
797
engine/core/io/http_client_tcp.cpp
Normal file
|
|
@ -0,0 +1,797 @@
|
|||
/**************************************************************************/
|
||||
/* http_client_tcp.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef WEB_ENABLED
|
||||
|
||||
#include "http_client_tcp.h"
|
||||
|
||||
#include "core/io/stream_peer_tls.h"
|
||||
#include "core/version.h"
|
||||
|
||||
HTTPClient *HTTPClientTCP::_create_func() {
|
||||
return memnew(HTTPClientTCP);
|
||||
}
|
||||
|
||||
Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Ref<TLSOptions> p_options) {
|
||||
close();
|
||||
|
||||
conn_port = p_port;
|
||||
conn_host = p_host;
|
||||
tls_options = p_options;
|
||||
|
||||
ip_candidates.clear();
|
||||
|
||||
String host_lower = conn_host.to_lower();
|
||||
if (host_lower.begins_with("http://")) {
|
||||
conn_host = conn_host.substr(7, conn_host.length() - 7);
|
||||
tls_options.unref();
|
||||
} else if (host_lower.begins_with("https://")) {
|
||||
if (tls_options.is_null()) {
|
||||
tls_options = TLSOptions::client();
|
||||
}
|
||||
conn_host = conn_host.substr(8, conn_host.length() - 8);
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(tls_options.is_valid() && tls_options->is_server(), ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V_MSG(tls_options.is_valid() && !StreamPeerTLS::is_available(), ERR_UNAVAILABLE, "HTTPS is not available in this build.");
|
||||
ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER);
|
||||
|
||||
if (conn_port < 0) {
|
||||
if (tls_options.is_valid()) {
|
||||
conn_port = PORT_HTTPS;
|
||||
} else {
|
||||
conn_port = PORT_HTTP;
|
||||
}
|
||||
}
|
||||
|
||||
connection = tcp_connection;
|
||||
|
||||
if (tls_options.is_valid() && https_proxy_port != -1) {
|
||||
proxy_client.instantiate(); // Needs proxy negotiation.
|
||||
server_host = https_proxy_host;
|
||||
server_port = https_proxy_port;
|
||||
} else if (tls_options.is_null() && http_proxy_port != -1) {
|
||||
server_host = http_proxy_host;
|
||||
server_port = http_proxy_port;
|
||||
} else {
|
||||
server_host = conn_host;
|
||||
server_port = conn_port;
|
||||
}
|
||||
|
||||
if (server_host.is_valid_ip_address()) {
|
||||
// Host contains valid IP.
|
||||
Error err = tcp_connection->connect_to_host(IPAddress(server_host), server_port);
|
||||
if (err) {
|
||||
status = STATUS_CANT_CONNECT;
|
||||
return err;
|
||||
}
|
||||
|
||||
status = STATUS_CONNECTING;
|
||||
} else {
|
||||
// Host contains hostname and needs to be resolved to IP.
|
||||
resolving = IP::get_singleton()->resolve_hostname_queue_item(server_host);
|
||||
if (resolving == IP::RESOLVER_INVALID_ID) {
|
||||
status = STATUS_CANT_RESOLVE;
|
||||
return ERR_CANT_RESOLVE;
|
||||
}
|
||||
status = STATUS_RESOLVING;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void HTTPClientTCP::set_connection(const Ref<StreamPeer> &p_connection) {
|
||||
ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object.");
|
||||
|
||||
if (tls_options.is_valid()) {
|
||||
ERR_FAIL_NULL_MSG(Object::cast_to<StreamPeerTLS>(p_connection.ptr()),
|
||||
"Connection is not a reference to a valid StreamPeerTLS object.");
|
||||
}
|
||||
|
||||
if (connection == p_connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
close();
|
||||
connection = p_connection;
|
||||
status = STATUS_CONNECTED;
|
||||
}
|
||||
|
||||
Ref<StreamPeer> HTTPClientTCP::get_connection() const {
|
||||
return connection;
|
||||
}
|
||||
|
||||
static bool _check_request_url(HTTPClientTCP::Method p_method, const String &p_url) {
|
||||
switch (p_method) {
|
||||
case HTTPClientTCP::METHOD_CONNECT: {
|
||||
// Authority in host:port format, as in RFC7231.
|
||||
int pos = p_url.find_char(':');
|
||||
return 0 < pos && pos < p_url.length() - 1;
|
||||
}
|
||||
case HTTPClientTCP::METHOD_OPTIONS: {
|
||||
if (p_url == "*") {
|
||||
return true;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
default:
|
||||
// Absolute path or absolute URL.
|
||||
return p_url.begins_with("/") || p_url.begins_with("http://") || p_url.begins_with("https://");
|
||||
}
|
||||
}
|
||||
|
||||
Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) {
|
||||
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
|
||||
|
||||
Error err = verify_headers(p_headers);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
String uri = p_url;
|
||||
if (tls_options.is_null() && http_proxy_port != -1) {
|
||||
uri = vformat("http://%s:%d%s", conn_host, conn_port, p_url);
|
||||
}
|
||||
|
||||
String request = String(_methods[p_method]) + " " + uri + " HTTP/1.1\r\n";
|
||||
bool add_host = true;
|
||||
bool add_clen = p_body_size > 0;
|
||||
bool add_uagent = true;
|
||||
bool add_accept = true;
|
||||
for (int i = 0; i < p_headers.size(); i++) {
|
||||
request += p_headers[i] + "\r\n";
|
||||
if (add_host && p_headers[i].findn("Host:") == 0) {
|
||||
add_host = false;
|
||||
}
|
||||
if (add_clen && p_headers[i].findn("Content-Length:") == 0) {
|
||||
add_clen = false;
|
||||
}
|
||||
if (add_uagent && p_headers[i].findn("User-Agent:") == 0) {
|
||||
add_uagent = false;
|
||||
}
|
||||
if (add_accept && p_headers[i].findn("Accept:") == 0) {
|
||||
add_accept = false;
|
||||
}
|
||||
}
|
||||
if (add_host) {
|
||||
if ((tls_options.is_valid() && conn_port == PORT_HTTPS) || (tls_options.is_null() && conn_port == PORT_HTTP)) {
|
||||
// Don't append the standard ports.
|
||||
request += "Host: " + conn_host + "\r\n";
|
||||
} else {
|
||||
request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
|
||||
}
|
||||
}
|
||||
if (add_clen) {
|
||||
request += "Content-Length: " + itos(p_body_size) + "\r\n";
|
||||
// Should it add utf8 encoding?
|
||||
}
|
||||
if (add_uagent) {
|
||||
request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n";
|
||||
}
|
||||
if (add_accept) {
|
||||
request += "Accept: */*\r\n";
|
||||
}
|
||||
request += "\r\n";
|
||||
CharString cs = request.utf8();
|
||||
|
||||
request_buffer->clear();
|
||||
request_buffer->put_data((const uint8_t *)cs.get_data(), cs.length());
|
||||
if (p_body_size > 0) {
|
||||
request_buffer->put_data(p_body, p_body_size);
|
||||
}
|
||||
request_buffer->seek(0);
|
||||
|
||||
status = STATUS_REQUESTING;
|
||||
head_request = p_method == METHOD_HEAD;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool HTTPClientTCP::has_response() const {
|
||||
return response_headers.size() != 0;
|
||||
}
|
||||
|
||||
bool HTTPClientTCP::is_response_chunked() const {
|
||||
return chunked;
|
||||
}
|
||||
|
||||
int HTTPClientTCP::get_response_code() const {
|
||||
return response_num;
|
||||
}
|
||||
|
||||
Error HTTPClientTCP::get_response_headers(List<String> *r_response) {
|
||||
if (!response_headers.size()) {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
for (int i = 0; i < response_headers.size(); i++) {
|
||||
r_response->push_back(response_headers[i]);
|
||||
}
|
||||
|
||||
response_headers.clear();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void HTTPClientTCP::close() {
|
||||
if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE) {
|
||||
tcp_connection->disconnect_from_host();
|
||||
}
|
||||
|
||||
connection.unref();
|
||||
proxy_client.unref();
|
||||
status = STATUS_DISCONNECTED;
|
||||
head_request = false;
|
||||
if (resolving != IP::RESOLVER_INVALID_ID) {
|
||||
IP::get_singleton()->erase_resolve_item(resolving);
|
||||
resolving = IP::RESOLVER_INVALID_ID;
|
||||
}
|
||||
|
||||
ip_candidates.clear();
|
||||
response_headers.clear();
|
||||
response_str.clear();
|
||||
request_buffer->clear();
|
||||
body_size = -1;
|
||||
body_left = 0;
|
||||
chunk_left = 0;
|
||||
chunk_trailer_part = false;
|
||||
read_until_eof = false;
|
||||
response_num = 0;
|
||||
handshaking = false;
|
||||
}
|
||||
|
||||
Error HTTPClientTCP::poll() {
|
||||
if (tcp_connection.is_valid()) {
|
||||
tcp_connection->poll();
|
||||
}
|
||||
switch (status) {
|
||||
case STATUS_RESOLVING: {
|
||||
ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG);
|
||||
|
||||
IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving);
|
||||
switch (rstatus) {
|
||||
case IP::RESOLVER_STATUS_WAITING:
|
||||
return OK; // Still resolving.
|
||||
|
||||
case IP::RESOLVER_STATUS_DONE: {
|
||||
ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving);
|
||||
IP::get_singleton()->erase_resolve_item(resolving);
|
||||
resolving = IP::RESOLVER_INVALID_ID;
|
||||
|
||||
Error err = ERR_BUG; // Should be at least one entry.
|
||||
while (ip_candidates.size() > 0) {
|
||||
err = tcp_connection->connect_to_host(ip_candidates.pop_front(), server_port);
|
||||
if (err == OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
status = STATUS_CANT_CONNECT;
|
||||
return err;
|
||||
}
|
||||
|
||||
status = STATUS_CONNECTING;
|
||||
} break;
|
||||
case IP::RESOLVER_STATUS_NONE:
|
||||
case IP::RESOLVER_STATUS_ERROR: {
|
||||
IP::get_singleton()->erase_resolve_item(resolving);
|
||||
resolving = IP::RESOLVER_INVALID_ID;
|
||||
close();
|
||||
status = STATUS_CANT_RESOLVE;
|
||||
return ERR_CANT_RESOLVE;
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
case STATUS_CONNECTING: {
|
||||
StreamPeerTCP::Status s = tcp_connection->get_status();
|
||||
switch (s) {
|
||||
case StreamPeerTCP::STATUS_CONNECTING: {
|
||||
return OK;
|
||||
} break;
|
||||
case StreamPeerTCP::STATUS_CONNECTED: {
|
||||
if (tls_options.is_valid() && proxy_client.is_valid()) {
|
||||
Error err = proxy_client->poll();
|
||||
if (err == ERR_UNCONFIGURED) {
|
||||
proxy_client->set_connection(tcp_connection);
|
||||
const Vector<String> headers;
|
||||
err = proxy_client->request(METHOD_CONNECT, vformat("%s:%d", conn_host, conn_port), headers, nullptr, 0);
|
||||
if (err != OK) {
|
||||
status = STATUS_CANT_CONNECT;
|
||||
return err;
|
||||
}
|
||||
} else if (err != OK) {
|
||||
status = STATUS_CANT_CONNECT;
|
||||
return err;
|
||||
}
|
||||
switch (proxy_client->get_status()) {
|
||||
case STATUS_REQUESTING: {
|
||||
return OK;
|
||||
} break;
|
||||
case STATUS_BODY: {
|
||||
proxy_client->read_response_body_chunk();
|
||||
return OK;
|
||||
} break;
|
||||
case STATUS_CONNECTED: {
|
||||
if (proxy_client->get_response_code() != RESPONSE_OK) {
|
||||
status = STATUS_CANT_CONNECT;
|
||||
return ERR_CANT_CONNECT;
|
||||
}
|
||||
proxy_client.unref();
|
||||
return OK;
|
||||
}
|
||||
case STATUS_DISCONNECTED:
|
||||
case STATUS_RESOLVING:
|
||||
case STATUS_CONNECTING: {
|
||||
status = STATUS_CANT_CONNECT;
|
||||
ERR_FAIL_V(ERR_BUG);
|
||||
} break;
|
||||
default: {
|
||||
status = STATUS_CANT_CONNECT;
|
||||
return ERR_CANT_CONNECT;
|
||||
} break;
|
||||
}
|
||||
} else if (tls_options.is_valid()) {
|
||||
Ref<StreamPeerTLS> tls_conn;
|
||||
if (!handshaking) {
|
||||
// Connect the StreamPeerTLS and start handshaking.
|
||||
tls_conn = Ref<StreamPeerTLS>(StreamPeerTLS::create());
|
||||
Error err = tls_conn->connect_to_stream(tcp_connection, conn_host, tls_options);
|
||||
if (err != OK) {
|
||||
close();
|
||||
status = STATUS_TLS_HANDSHAKE_ERROR;
|
||||
return ERR_CANT_CONNECT;
|
||||
}
|
||||
connection = tls_conn;
|
||||
handshaking = true;
|
||||
} else {
|
||||
// We are already handshaking, which means we can use your already active TLS connection.
|
||||
tls_conn = static_cast<Ref<StreamPeerTLS>>(connection);
|
||||
if (tls_conn.is_null()) {
|
||||
close();
|
||||
status = STATUS_TLS_HANDSHAKE_ERROR;
|
||||
return ERR_CANT_CONNECT;
|
||||
}
|
||||
|
||||
tls_conn->poll(); // Try to finish the handshake.
|
||||
}
|
||||
|
||||
if (tls_conn->get_status() == StreamPeerTLS::STATUS_CONNECTED) {
|
||||
// Handshake has been successful.
|
||||
handshaking = false;
|
||||
ip_candidates.clear();
|
||||
status = STATUS_CONNECTED;
|
||||
return OK;
|
||||
} else if (tls_conn->get_status() != StreamPeerTLS::STATUS_HANDSHAKING) {
|
||||
// Handshake has failed.
|
||||
close();
|
||||
status = STATUS_TLS_HANDSHAKE_ERROR;
|
||||
return ERR_CANT_CONNECT;
|
||||
}
|
||||
// ... we will need to poll more for handshake to finish.
|
||||
} else {
|
||||
ip_candidates.clear();
|
||||
status = STATUS_CONNECTED;
|
||||
}
|
||||
return OK;
|
||||
} break;
|
||||
case StreamPeerTCP::STATUS_ERROR:
|
||||
case StreamPeerTCP::STATUS_NONE: {
|
||||
Error err = ERR_CANT_CONNECT;
|
||||
while (ip_candidates.size() > 0) {
|
||||
tcp_connection->disconnect_from_host();
|
||||
err = tcp_connection->connect_to_host(ip_candidates.pop_front(), server_port);
|
||||
if (err == OK) {
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
close();
|
||||
status = STATUS_CANT_CONNECT;
|
||||
return err;
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
case STATUS_BODY:
|
||||
case STATUS_CONNECTED: {
|
||||
// Check if we are still connected.
|
||||
if (tls_options.is_valid()) {
|
||||
Ref<StreamPeerTLS> tmp = connection;
|
||||
tmp->poll();
|
||||
if (tmp->get_status() != StreamPeerTLS::STATUS_CONNECTED) {
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
} else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
// Connection established, requests can now be made.
|
||||
return OK;
|
||||
} break;
|
||||
case STATUS_REQUESTING: {
|
||||
if (request_buffer->get_available_bytes()) {
|
||||
int avail = request_buffer->get_available_bytes();
|
||||
int pos = request_buffer->get_position();
|
||||
const Vector<uint8_t> data = request_buffer->get_data_array();
|
||||
int wrote = 0;
|
||||
Error err;
|
||||
if (blocking) {
|
||||
err = connection->put_data(data.ptr() + pos, avail);
|
||||
wrote += avail;
|
||||
} else {
|
||||
err = connection->put_partial_data(data.ptr() + pos, avail, wrote);
|
||||
}
|
||||
if (err != OK) {
|
||||
close();
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
pos += wrote;
|
||||
request_buffer->seek(pos);
|
||||
if (avail - wrote > 0) {
|
||||
return OK;
|
||||
}
|
||||
request_buffer->clear();
|
||||
}
|
||||
while (true) {
|
||||
uint8_t byte;
|
||||
int rec = 0;
|
||||
Error err = _get_http_data(&byte, 1, rec);
|
||||
if (err != OK) {
|
||||
close();
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
|
||||
if (rec == 0) {
|
||||
return OK; // Still requesting, keep trying!
|
||||
}
|
||||
|
||||
response_str.push_back(byte);
|
||||
int rs = response_str.size();
|
||||
if (
|
||||
(rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') ||
|
||||
(rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) {
|
||||
// End of response, parse.
|
||||
response_str.push_back(0);
|
||||
String response;
|
||||
response.parse_utf8((const char *)response_str.ptr());
|
||||
Vector<String> responses = response.split("\n");
|
||||
body_size = -1;
|
||||
chunked = false;
|
||||
body_left = 0;
|
||||
chunk_left = 0;
|
||||
chunk_trailer_part = false;
|
||||
read_until_eof = false;
|
||||
response_str.clear();
|
||||
response_headers.clear();
|
||||
response_num = RESPONSE_OK;
|
||||
|
||||
// Per the HTTP 1.1 spec, keep-alive is the default.
|
||||
// Not following that specification breaks standard implementations.
|
||||
// Broken web servers should be fixed.
|
||||
bool keep_alive = true;
|
||||
|
||||
for (int i = 0; i < responses.size(); i++) {
|
||||
String header = responses[i].strip_edges();
|
||||
String s = header.to_lower();
|
||||
if (s.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (s.begins_with("content-length:")) {
|
||||
body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int();
|
||||
body_left = body_size;
|
||||
|
||||
} else if (s.begins_with("transfer-encoding:")) {
|
||||
String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges();
|
||||
if (encoding == "chunked") {
|
||||
chunked = true;
|
||||
}
|
||||
} else if (s.begins_with("connection: close")) {
|
||||
keep_alive = false;
|
||||
}
|
||||
|
||||
if (i == 0 && responses[i].begins_with("HTTP")) {
|
||||
String num = responses[i].get_slicec(' ', 1);
|
||||
response_num = num.to_int();
|
||||
} else {
|
||||
response_headers.push_back(header);
|
||||
}
|
||||
}
|
||||
|
||||
// This is a HEAD request, we won't receive anything.
|
||||
if (head_request) {
|
||||
body_size = 0;
|
||||
body_left = 0;
|
||||
}
|
||||
|
||||
if (body_size != -1 || chunked) {
|
||||
status = STATUS_BODY;
|
||||
} else if (!keep_alive) {
|
||||
read_until_eof = true;
|
||||
status = STATUS_BODY;
|
||||
} else {
|
||||
status = STATUS_CONNECTED;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case STATUS_DISCONNECTED: {
|
||||
return ERR_UNCONFIGURED;
|
||||
} break;
|
||||
case STATUS_CONNECTION_ERROR:
|
||||
case STATUS_TLS_HANDSHAKE_ERROR: {
|
||||
return ERR_CONNECTION_ERROR;
|
||||
} break;
|
||||
case STATUS_CANT_CONNECT: {
|
||||
return ERR_CANT_CONNECT;
|
||||
} break;
|
||||
case STATUS_CANT_RESOLVE: {
|
||||
return ERR_CANT_RESOLVE;
|
||||
} break;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int64_t HTTPClientTCP::get_response_body_length() const {
|
||||
return body_size;
|
||||
}
|
||||
|
||||
PackedByteArray HTTPClientTCP::read_response_body_chunk() {
|
||||
ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
|
||||
|
||||
PackedByteArray ret;
|
||||
Error err = OK;
|
||||
|
||||
if (chunked) {
|
||||
while (true) {
|
||||
if (chunk_trailer_part) {
|
||||
// We need to consume the trailer part too or keep-alive will break.
|
||||
uint8_t b;
|
||||
int rec = 0;
|
||||
err = _get_http_data(&b, 1, rec);
|
||||
|
||||
if (rec == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
chunk.push_back(b);
|
||||
int cs = chunk.size();
|
||||
if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) {
|
||||
if (cs == 2) {
|
||||
// Finally over.
|
||||
chunk_trailer_part = false;
|
||||
status = STATUS_CONNECTED;
|
||||
chunk.clear();
|
||||
break;
|
||||
} else {
|
||||
// We do not process nor return the trailer data.
|
||||
chunk.clear();
|
||||
}
|
||||
}
|
||||
} else if (chunk_left == 0) {
|
||||
// Reading length.
|
||||
uint8_t b;
|
||||
int rec = 0;
|
||||
err = _get_http_data(&b, 1, rec);
|
||||
|
||||
if (rec == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
chunk.push_back(b);
|
||||
|
||||
if (chunk.size() > 32) {
|
||||
ERR_PRINT("HTTP Invalid chunk hex len");
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') {
|
||||
int len = 0;
|
||||
for (int i = 0; i < chunk.size() - 2; i++) {
|
||||
char c = chunk[i];
|
||||
int v = 0;
|
||||
if (is_digit(c)) {
|
||||
v = c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
v = c - 'a' + 10;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
v = c - 'A' + 10;
|
||||
} else {
|
||||
ERR_PRINT("HTTP Chunk len not in hex!!");
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
break;
|
||||
}
|
||||
len <<= 4;
|
||||
len |= v;
|
||||
if (len > (1 << 24)) {
|
||||
ERR_PRINT("HTTP Chunk too big!! >16mb");
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
// End reached!
|
||||
chunk_trailer_part = true;
|
||||
chunk.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
chunk_left = len + 2;
|
||||
chunk.resize(chunk_left);
|
||||
}
|
||||
} else {
|
||||
int rec = 0;
|
||||
err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec);
|
||||
if (rec == 0) {
|
||||
break;
|
||||
}
|
||||
chunk_left -= rec;
|
||||
|
||||
if (chunk_left == 0) {
|
||||
if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') {
|
||||
ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)");
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
ret.resize(chunk.size() - 2);
|
||||
uint8_t *w = ret.ptrw();
|
||||
memcpy(w, chunk.ptr(), chunk.size() - 2);
|
||||
chunk.clear();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size;
|
||||
ret.resize(to_read);
|
||||
int _offset = 0;
|
||||
while (to_read > 0) {
|
||||
int rec = 0;
|
||||
{
|
||||
uint8_t *w = ret.ptrw();
|
||||
err = _get_http_data(w + _offset, to_read, rec);
|
||||
}
|
||||
if (rec <= 0) { // Ended up reading less.
|
||||
ret.resize(_offset);
|
||||
break;
|
||||
} else {
|
||||
_offset += rec;
|
||||
to_read -= rec;
|
||||
if (!read_until_eof) {
|
||||
body_left -= rec;
|
||||
}
|
||||
}
|
||||
if (err != OK) {
|
||||
ret.resize(_offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
close();
|
||||
|
||||
if (err == ERR_FILE_EOF) {
|
||||
status = STATUS_DISCONNECTED; // Server disconnected.
|
||||
} else {
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
}
|
||||
} else if (body_left == 0 && !chunked && !read_until_eof) {
|
||||
status = STATUS_CONNECTED;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
HTTPClientTCP::Status HTTPClientTCP::get_status() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
void HTTPClientTCP::set_blocking_mode(bool p_enable) {
|
||||
blocking = p_enable;
|
||||
}
|
||||
|
||||
bool HTTPClientTCP::is_blocking_mode_enabled() const {
|
||||
return blocking;
|
||||
}
|
||||
|
||||
Error HTTPClientTCP::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
|
||||
if (blocking) {
|
||||
// We can't use StreamPeer.get_data, since when reaching EOF we will get an
|
||||
// error without knowing how many bytes we received.
|
||||
Error err = ERR_FILE_EOF;
|
||||
int read = 0;
|
||||
int left = p_bytes;
|
||||
r_received = 0;
|
||||
while (left > 0) {
|
||||
err = connection->get_partial_data(p_buffer + r_received, left, read);
|
||||
if (err == OK) {
|
||||
r_received += read;
|
||||
} else if (err == ERR_FILE_EOF) {
|
||||
r_received += read;
|
||||
return err;
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
left -= read;
|
||||
}
|
||||
return err;
|
||||
} else {
|
||||
return connection->get_partial_data(p_buffer, p_bytes, r_received);
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPClientTCP::set_read_chunk_size(int p_size) {
|
||||
ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24));
|
||||
read_chunk_size = p_size;
|
||||
}
|
||||
|
||||
int HTTPClientTCP::get_read_chunk_size() const {
|
||||
return read_chunk_size;
|
||||
}
|
||||
|
||||
void HTTPClientTCP::set_http_proxy(const String &p_host, int p_port) {
|
||||
if (p_host.is_empty() || p_port == -1) {
|
||||
http_proxy_host = "";
|
||||
http_proxy_port = -1;
|
||||
} else {
|
||||
http_proxy_host = p_host;
|
||||
http_proxy_port = p_port;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPClientTCP::set_https_proxy(const String &p_host, int p_port) {
|
||||
if (p_host.is_empty() || p_port == -1) {
|
||||
https_proxy_host = "";
|
||||
https_proxy_port = -1;
|
||||
} else {
|
||||
https_proxy_host = p_host;
|
||||
https_proxy_port = p_port;
|
||||
}
|
||||
}
|
||||
|
||||
HTTPClientTCP::HTTPClientTCP() {
|
||||
tcp_connection.instantiate();
|
||||
request_buffer.instantiate();
|
||||
}
|
||||
|
||||
HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func;
|
||||
|
||||
#endif // WEB_ENABLED
|
||||
104
engine/core/io/http_client_tcp.h
Normal file
104
engine/core/io/http_client_tcp.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**************************************************************************/
|
||||
/* http_client_tcp.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef HTTP_CLIENT_TCP_H
|
||||
#define HTTP_CLIENT_TCP_H
|
||||
|
||||
#include "http_client.h"
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
|
||||
class HTTPClientTCP : public HTTPClient {
|
||||
private:
|
||||
Status status = STATUS_DISCONNECTED;
|
||||
IP::ResolverID resolving = IP::RESOLVER_INVALID_ID;
|
||||
Array ip_candidates;
|
||||
int conn_port = -1; // Server to make requests to.
|
||||
String conn_host;
|
||||
int server_port = -1; // Server to connect to (might be a proxy server).
|
||||
String server_host;
|
||||
int http_proxy_port = -1; // Proxy server for http requests.
|
||||
String http_proxy_host;
|
||||
int https_proxy_port = -1; // Proxy server for https requests.
|
||||
String https_proxy_host;
|
||||
bool blocking = false;
|
||||
bool handshaking = false;
|
||||
bool head_request = false;
|
||||
Ref<TLSOptions> tls_options;
|
||||
|
||||
Vector<uint8_t> response_str;
|
||||
|
||||
bool chunked = false;
|
||||
Vector<uint8_t> chunk;
|
||||
int chunk_left = 0;
|
||||
bool chunk_trailer_part = false;
|
||||
int64_t body_size = -1;
|
||||
int64_t body_left = 0;
|
||||
bool read_until_eof = false;
|
||||
|
||||
Ref<StreamPeerBuffer> request_buffer;
|
||||
Ref<StreamPeerTCP> tcp_connection;
|
||||
Ref<StreamPeer> connection;
|
||||
Ref<HTTPClientTCP> proxy_client; // Negotiate with proxy server.
|
||||
|
||||
int response_num = 0;
|
||||
Vector<String> response_headers;
|
||||
// 64 KiB by default (favors fast download speeds at the cost of memory usage).
|
||||
int read_chunk_size = 65536;
|
||||
|
||||
Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received);
|
||||
|
||||
public:
|
||||
static HTTPClient *_create_func();
|
||||
|
||||
Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override;
|
||||
|
||||
Error connect_to_host(const String &p_host, int p_port = -1, Ref<TLSOptions> p_tls_options = Ref<TLSOptions>()) override;
|
||||
void set_connection(const Ref<StreamPeer> &p_connection) override;
|
||||
Ref<StreamPeer> get_connection() const override;
|
||||
void close() override;
|
||||
Status get_status() const override;
|
||||
bool has_response() const override;
|
||||
bool is_response_chunked() const override;
|
||||
int get_response_code() const override;
|
||||
Error get_response_headers(List<String> *r_response) override;
|
||||
int64_t get_response_body_length() const override;
|
||||
PackedByteArray read_response_body_chunk() override;
|
||||
void set_blocking_mode(bool p_enable) override;
|
||||
bool is_blocking_mode_enabled() const override;
|
||||
void set_read_chunk_size(int p_size) override;
|
||||
int get_read_chunk_size() const override;
|
||||
Error poll() override;
|
||||
void set_http_proxy(const String &p_host, int p_port) override;
|
||||
void set_https_proxy(const String &p_host, int p_port) override;
|
||||
HTTPClientTCP();
|
||||
};
|
||||
|
||||
#endif // HTTP_CLIENT_TCP_H
|
||||
4138
engine/core/io/image.cpp
Normal file
4138
engine/core/io/image.cpp
Normal file
File diff suppressed because it is too large
Load diff
461
engine/core/io/image.h
Normal file
461
engine/core/io/image.h
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
/**************************************************************************/
|
||||
/* image.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef IMAGE_H
|
||||
#define IMAGE_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/math/color.h"
|
||||
#include "core/math/rect2.h"
|
||||
|
||||
/**
|
||||
* Image storage class. This is used to store an image in user memory, as well as
|
||||
* providing some basic methods for image manipulation.
|
||||
* Images can be loaded from a file, or registered into the Render object as textures.
|
||||
*/
|
||||
|
||||
class Image;
|
||||
|
||||
typedef Error (*SavePNGFunc)(const String &p_path, const Ref<Image> &p_img);
|
||||
typedef Vector<uint8_t> (*SavePNGBufferFunc)(const Ref<Image> &p_img);
|
||||
typedef Error (*SaveJPGFunc)(const String &p_path, const Ref<Image> &p_img, float p_quality);
|
||||
typedef Vector<uint8_t> (*SaveJPGBufferFunc)(const Ref<Image> &p_img, float p_quality);
|
||||
typedef Ref<Image> (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size);
|
||||
typedef Ref<Image> (*ScalableImageMemLoadFunc)(const uint8_t *p_data, int p_size, float p_scale);
|
||||
typedef Error (*SaveWebPFunc)(const String &p_path, const Ref<Image> &p_img, const bool p_lossy, const float p_quality);
|
||||
typedef Vector<uint8_t> (*SaveWebPBufferFunc)(const Ref<Image> &p_img, const bool p_lossy, const float p_quality);
|
||||
|
||||
typedef Error (*SaveEXRFunc)(const String &p_path, const Ref<Image> &p_img, bool p_grayscale);
|
||||
typedef Vector<uint8_t> (*SaveEXRBufferFunc)(const Ref<Image> &p_img, bool p_grayscale);
|
||||
|
||||
class Image : public Resource {
|
||||
GDCLASS(Image, Resource);
|
||||
|
||||
public:
|
||||
static SavePNGFunc save_png_func;
|
||||
static SaveJPGFunc save_jpg_func;
|
||||
static SaveEXRFunc save_exr_func;
|
||||
static SavePNGBufferFunc save_png_buffer_func;
|
||||
static SaveEXRBufferFunc save_exr_buffer_func;
|
||||
static SaveJPGBufferFunc save_jpg_buffer_func;
|
||||
static SaveWebPFunc save_webp_func;
|
||||
static SaveWebPBufferFunc save_webp_buffer_func;
|
||||
|
||||
enum {
|
||||
MAX_WIDTH = (1 << 24), // force a limit somehow
|
||||
MAX_HEIGHT = (1 << 24), // force a limit somehow
|
||||
MAX_PIXELS = 268435456
|
||||
};
|
||||
|
||||
enum Format {
|
||||
FORMAT_L8, //luminance
|
||||
FORMAT_LA8, //luminance-alpha
|
||||
FORMAT_R8,
|
||||
FORMAT_RG8,
|
||||
FORMAT_RGB8,
|
||||
FORMAT_RGBA8,
|
||||
FORMAT_RGBA4444,
|
||||
FORMAT_RGB565,
|
||||
FORMAT_RF, //float
|
||||
FORMAT_RGF,
|
||||
FORMAT_RGBF,
|
||||
FORMAT_RGBAF,
|
||||
FORMAT_RH, //half float
|
||||
FORMAT_RGH,
|
||||
FORMAT_RGBH,
|
||||
FORMAT_RGBAH,
|
||||
FORMAT_RGBE9995,
|
||||
FORMAT_DXT1, //s3tc bc1
|
||||
FORMAT_DXT3, //bc2
|
||||
FORMAT_DXT5, //bc3
|
||||
FORMAT_RGTC_R,
|
||||
FORMAT_RGTC_RG,
|
||||
FORMAT_BPTC_RGBA, //btpc bc7
|
||||
FORMAT_BPTC_RGBF, //float bc6h
|
||||
FORMAT_BPTC_RGBFU, //unsigned float bc6hu
|
||||
FORMAT_ETC, //etc1
|
||||
FORMAT_ETC2_R11, //etc2
|
||||
FORMAT_ETC2_R11S, //signed, NOT srgb.
|
||||
FORMAT_ETC2_RG11,
|
||||
FORMAT_ETC2_RG11S,
|
||||
FORMAT_ETC2_RGB8,
|
||||
FORMAT_ETC2_RGBA8,
|
||||
FORMAT_ETC2_RGB8A1,
|
||||
FORMAT_ETC2_RA_AS_RG, //used to make basis universal happy
|
||||
FORMAT_DXT5_RA_AS_RG, //used to make basis universal happy
|
||||
FORMAT_ASTC_4x4,
|
||||
FORMAT_ASTC_4x4_HDR,
|
||||
FORMAT_ASTC_8x8,
|
||||
FORMAT_ASTC_8x8_HDR,
|
||||
FORMAT_MAX
|
||||
};
|
||||
|
||||
static const char *format_names[FORMAT_MAX];
|
||||
enum Interpolation {
|
||||
INTERPOLATE_NEAREST,
|
||||
INTERPOLATE_BILINEAR,
|
||||
INTERPOLATE_CUBIC,
|
||||
INTERPOLATE_TRILINEAR,
|
||||
INTERPOLATE_LANCZOS,
|
||||
/* INTERPOLATE_TRICUBIC, */
|
||||
/* INTERPOLATE GAUSS */
|
||||
};
|
||||
|
||||
//this is used for compression
|
||||
enum UsedChannels {
|
||||
USED_CHANNELS_L,
|
||||
USED_CHANNELS_LA,
|
||||
USED_CHANNELS_R,
|
||||
USED_CHANNELS_RG,
|
||||
USED_CHANNELS_RGB,
|
||||
USED_CHANNELS_RGBA,
|
||||
};
|
||||
//some functions provided by something else
|
||||
|
||||
enum ASTCFormat {
|
||||
ASTC_FORMAT_4x4,
|
||||
ASTC_FORMAT_8x8,
|
||||
};
|
||||
|
||||
static ImageMemLoadFunc _png_mem_loader_func;
|
||||
static ImageMemLoadFunc _png_mem_unpacker_func;
|
||||
static ImageMemLoadFunc _jpg_mem_loader_func;
|
||||
static ImageMemLoadFunc _webp_mem_loader_func;
|
||||
static ImageMemLoadFunc _tga_mem_loader_func;
|
||||
static ImageMemLoadFunc _bmp_mem_loader_func;
|
||||
static ScalableImageMemLoadFunc _svg_scalable_mem_loader_func;
|
||||
static ImageMemLoadFunc _ktx_mem_loader_func;
|
||||
|
||||
static void (*_image_compress_bc_func)(Image *, UsedChannels p_channels);
|
||||
static void (*_image_compress_bptc_func)(Image *, UsedChannels p_channels);
|
||||
static void (*_image_compress_etc1_func)(Image *);
|
||||
static void (*_image_compress_etc2_func)(Image *, UsedChannels p_channels);
|
||||
static void (*_image_compress_astc_func)(Image *, ASTCFormat p_format);
|
||||
|
||||
static void (*_image_decompress_bc)(Image *);
|
||||
static void (*_image_decompress_bptc)(Image *);
|
||||
static void (*_image_decompress_etc1)(Image *);
|
||||
static void (*_image_decompress_etc2)(Image *);
|
||||
static void (*_image_decompress_astc)(Image *);
|
||||
|
||||
static Vector<uint8_t> (*webp_lossy_packer)(const Ref<Image> &p_image, float p_quality);
|
||||
static Vector<uint8_t> (*webp_lossless_packer)(const Ref<Image> &p_image);
|
||||
static Ref<Image> (*webp_unpacker)(const Vector<uint8_t> &p_buffer);
|
||||
static Vector<uint8_t> (*png_packer)(const Ref<Image> &p_image);
|
||||
static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer);
|
||||
static Vector<uint8_t> (*basis_universal_packer)(const Ref<Image> &p_image, UsedChannels p_channels);
|
||||
static Ref<Image> (*basis_universal_unpacker)(const Vector<uint8_t> &p_buffer);
|
||||
static Ref<Image> (*basis_universal_unpacker_ptr)(const uint8_t *p_data, int p_size);
|
||||
|
||||
_FORCE_INLINE_ Color _get_color_at_ofs(const uint8_t *ptr, uint32_t ofs) const;
|
||||
_FORCE_INLINE_ void _set_color_at_ofs(uint8_t *ptr, uint32_t ofs, const Color &p_color);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
Format format = FORMAT_L8;
|
||||
Vector<uint8_t> data;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
bool mipmaps = false;
|
||||
|
||||
void _copy_internals_from(const Image &p_image) {
|
||||
format = p_image.format;
|
||||
width = p_image.width;
|
||||
height = p_image.height;
|
||||
mipmaps = p_image.mipmaps;
|
||||
data = p_image.data;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data
|
||||
|
||||
static int64_t _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr);
|
||||
bool _can_modify(Format p_format) const;
|
||||
|
||||
_FORCE_INLINE_ void _get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const;
|
||||
|
||||
_FORCE_INLINE_ void _put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel);
|
||||
_FORCE_INLINE_ void _get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel);
|
||||
|
||||
_FORCE_INLINE_ void _repeat_pixel_over_subsequent_memory(uint8_t *p_pixel, int p_pixel_size, int p_count);
|
||||
|
||||
void _set_data(const Dictionary &p_data);
|
||||
Dictionary _get_data() const;
|
||||
|
||||
Error _load_from_buffer(const Vector<uint8_t> &p_array, ImageMemLoadFunc p_loader);
|
||||
|
||||
static void average_4_uint8(uint8_t &p_out, const uint8_t &p_a, const uint8_t &p_b, const uint8_t &p_c, const uint8_t &p_d);
|
||||
static void average_4_float(float &p_out, const float &p_a, const float &p_b, const float &p_c, const float &p_d);
|
||||
static void average_4_half(uint16_t &p_out, const uint16_t &p_a, const uint16_t &p_b, const uint16_t &p_c, const uint16_t &p_d);
|
||||
static void average_4_rgbe9995(uint32_t &p_out, const uint32_t &p_a, const uint32_t &p_b, const uint32_t &p_c, const uint32_t &p_d);
|
||||
static void renormalize_uint8(uint8_t *p_rgb);
|
||||
static void renormalize_float(float *p_rgb);
|
||||
static void renormalize_half(uint16_t *p_rgb);
|
||||
static void renormalize_rgbe9995(uint32_t *p_rgb);
|
||||
|
||||
public:
|
||||
int get_width() const; ///< Get image width
|
||||
int get_height() const; ///< Get image height
|
||||
Size2i get_size() const;
|
||||
bool has_mipmaps() const;
|
||||
int get_mipmap_count() const;
|
||||
|
||||
/**
|
||||
* Convert the image to another format, conversion only to raw byte format
|
||||
*/
|
||||
void convert(Format p_new_format);
|
||||
|
||||
/**
|
||||
* Get the current image format.
|
||||
*/
|
||||
Format get_format() const;
|
||||
|
||||
/**
|
||||
* Get where the mipmap begins in data.
|
||||
*/
|
||||
int64_t get_mipmap_offset(int p_mipmap) const;
|
||||
void get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const;
|
||||
void get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const;
|
||||
|
||||
enum Image3DValidateError {
|
||||
VALIDATE_3D_OK,
|
||||
VALIDATE_3D_ERR_IMAGE_EMPTY,
|
||||
VALIDATE_3D_ERR_MISSING_IMAGES,
|
||||
VALIDATE_3D_ERR_EXTRA_IMAGES,
|
||||
VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH,
|
||||
VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH,
|
||||
VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS,
|
||||
};
|
||||
|
||||
static Image3DValidateError validate_3d_image(Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_images);
|
||||
static String get_3d_image_validation_error_text(Image3DValidateError p_error);
|
||||
|
||||
/**
|
||||
* Resize the image, using the preferred interpolation method.
|
||||
*/
|
||||
void resize_to_po2(bool p_square = false, Interpolation p_interpolation = INTERPOLATE_BILINEAR);
|
||||
void resize(int p_width, int p_height, Interpolation p_interpolation = INTERPOLATE_BILINEAR);
|
||||
void shrink_x2();
|
||||
bool is_size_po2() const;
|
||||
/**
|
||||
* Crop the image to a specific size, if larger, then the image is filled by black
|
||||
*/
|
||||
void crop_from_point(int p_x, int p_y, int p_width, int p_height);
|
||||
void crop(int p_width, int p_height);
|
||||
|
||||
void rotate_90(ClockDirection p_direction);
|
||||
void rotate_180();
|
||||
|
||||
void flip_x();
|
||||
void flip_y();
|
||||
|
||||
/**
|
||||
* Generate a mipmap to an image (creates an image 1/4 the size, with averaging of 4->1)
|
||||
*/
|
||||
Error generate_mipmaps(bool p_renormalize = false);
|
||||
|
||||
enum RoughnessChannel {
|
||||
ROUGHNESS_CHANNEL_R,
|
||||
ROUGHNESS_CHANNEL_G,
|
||||
ROUGHNESS_CHANNEL_B,
|
||||
ROUGHNESS_CHANNEL_A,
|
||||
ROUGHNESS_CHANNEL_L,
|
||||
};
|
||||
|
||||
Error generate_mipmap_roughness(RoughnessChannel p_roughness_channel, const Ref<Image> &p_normal_map);
|
||||
|
||||
void clear_mipmaps();
|
||||
void normalize(); //for normal maps
|
||||
|
||||
/**
|
||||
* Creates new internal image data of a given size and format. Current image will be lost.
|
||||
*/
|
||||
void initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format);
|
||||
void initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
|
||||
void initialize_data(const char **p_xpm);
|
||||
|
||||
/**
|
||||
* returns true when the image is empty (0,0) in size
|
||||
*/
|
||||
bool is_empty() const;
|
||||
|
||||
Vector<uint8_t> get_data() const;
|
||||
|
||||
Error load(const String &p_path);
|
||||
static Ref<Image> load_from_file(const String &p_path);
|
||||
Error save_png(const String &p_path) const;
|
||||
Error save_jpg(const String &p_path, float p_quality = 0.75) const;
|
||||
Vector<uint8_t> save_png_to_buffer() const;
|
||||
Vector<uint8_t> save_jpg_to_buffer(float p_quality = 0.75) const;
|
||||
Vector<uint8_t> save_exr_to_buffer(bool p_grayscale = false) const;
|
||||
Error save_exr(const String &p_path, bool p_grayscale = false) const;
|
||||
Error save_webp(const String &p_path, const bool p_lossy = false, const float p_quality = 0.75f) const;
|
||||
Vector<uint8_t> save_webp_to_buffer(const bool p_lossy = false, const float p_quality = 0.75f) const;
|
||||
|
||||
static Ref<Image> create_empty(int p_width, int p_height, bool p_use_mipmaps, Format p_format);
|
||||
static Ref<Image> create_from_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
|
||||
void set_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
|
||||
|
||||
/**
|
||||
* create an empty image
|
||||
*/
|
||||
Image() {}
|
||||
/**
|
||||
* create an empty image of a specific size and format
|
||||
*/
|
||||
Image(int p_width, int p_height, bool p_use_mipmaps, Format p_format);
|
||||
/**
|
||||
* import an image of a specific size and format from a pointer
|
||||
*/
|
||||
Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
|
||||
|
||||
~Image() {}
|
||||
|
||||
enum AlphaMode {
|
||||
ALPHA_NONE,
|
||||
ALPHA_BIT,
|
||||
ALPHA_BLEND
|
||||
};
|
||||
|
||||
AlphaMode detect_alpha() const;
|
||||
bool is_invisible() const;
|
||||
|
||||
static int get_format_pixel_size(Format p_format);
|
||||
static int get_format_pixel_rshift(Format p_format);
|
||||
static int get_format_block_size(Format p_format);
|
||||
static void get_format_min_pixel_size(Format p_format, int &r_w, int &r_h);
|
||||
|
||||
static int64_t get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false);
|
||||
static int get_image_required_mipmaps(int p_width, int p_height, Format p_format);
|
||||
static Size2i get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap);
|
||||
static int64_t get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap);
|
||||
static int64_t get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h);
|
||||
|
||||
enum CompressMode {
|
||||
COMPRESS_S3TC,
|
||||
COMPRESS_ETC,
|
||||
COMPRESS_ETC2,
|
||||
COMPRESS_BPTC,
|
||||
COMPRESS_ASTC,
|
||||
COMPRESS_MAX,
|
||||
};
|
||||
enum CompressSource {
|
||||
COMPRESS_SOURCE_GENERIC,
|
||||
COMPRESS_SOURCE_SRGB,
|
||||
COMPRESS_SOURCE_NORMAL,
|
||||
COMPRESS_SOURCE_MAX,
|
||||
};
|
||||
|
||||
Error compress(CompressMode p_mode, CompressSource p_source = COMPRESS_SOURCE_GENERIC, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
|
||||
Error compress_from_channels(CompressMode p_mode, UsedChannels p_channels, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
|
||||
Error decompress();
|
||||
bool is_compressed() const;
|
||||
static bool is_format_compressed(Format p_format);
|
||||
|
||||
void fix_alpha_edges();
|
||||
void premultiply_alpha();
|
||||
void srgb_to_linear();
|
||||
void normal_map_to_xy();
|
||||
Ref<Image> rgbe_to_srgb();
|
||||
Ref<Image> get_image_from_mipmap(int p_mipmap) const;
|
||||
void bump_map_to_normal_map(float bump_scale = 1.0);
|
||||
|
||||
void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest);
|
||||
void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2i &p_src_rect, const Point2i &p_dest);
|
||||
void blend_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest);
|
||||
void blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2i &p_src_rect, const Point2i &p_dest);
|
||||
void fill(const Color &p_color);
|
||||
void fill_rect(const Rect2i &p_rect, const Color &p_color);
|
||||
|
||||
Rect2i get_used_rect() const;
|
||||
Ref<Image> get_region(const Rect2i &p_area) const;
|
||||
|
||||
static void set_compress_bc_func(void (*p_compress_func)(Image *, UsedChannels));
|
||||
static void set_compress_bptc_func(void (*p_compress_func)(Image *, UsedChannels));
|
||||
static String get_format_name(Format p_format);
|
||||
|
||||
Error load_png_from_buffer(const Vector<uint8_t> &p_array);
|
||||
Error load_jpg_from_buffer(const Vector<uint8_t> &p_array);
|
||||
Error load_webp_from_buffer(const Vector<uint8_t> &p_array);
|
||||
Error load_tga_from_buffer(const Vector<uint8_t> &p_array);
|
||||
Error load_bmp_from_buffer(const Vector<uint8_t> &p_array);
|
||||
Error load_ktx_from_buffer(const Vector<uint8_t> &p_array);
|
||||
|
||||
Error load_svg_from_buffer(const Vector<uint8_t> &p_array, float scale = 1.0);
|
||||
Error load_svg_from_string(const String &p_svg_str, float scale = 1.0);
|
||||
|
||||
void convert_rg_to_ra_rgba8();
|
||||
void convert_ra_rgba8_to_rg();
|
||||
void convert_rgba8_to_bgra8();
|
||||
|
||||
Image(const uint8_t *p_mem_png_jpg, int p_len = -1);
|
||||
Image(const char **p_xpm);
|
||||
|
||||
virtual Ref<Resource> duplicate(bool p_subresources = false) const override;
|
||||
|
||||
UsedChannels detect_used_channels(CompressSource p_source = COMPRESS_SOURCE_GENERIC) const;
|
||||
void optimize_channels();
|
||||
|
||||
Color get_pixelv(const Point2i &p_point) const;
|
||||
Color get_pixel(int p_x, int p_y) const;
|
||||
void set_pixelv(const Point2i &p_point, const Color &p_color);
|
||||
void set_pixel(int p_x, int p_y, const Color &p_color);
|
||||
|
||||
const uint8_t *ptr() const;
|
||||
uint8_t *ptrw();
|
||||
int64_t get_data_size() const;
|
||||
|
||||
void adjust_bcs(float p_brightness, float p_contrast, float p_saturation);
|
||||
|
||||
void set_as_black();
|
||||
|
||||
void copy_internals_from(const Ref<Image> &p_image) {
|
||||
ERR_FAIL_COND_MSG(p_image.is_null(), "Cannot copy image internals: invalid Image object.");
|
||||
format = p_image->format;
|
||||
width = p_image->width;
|
||||
height = p_image->height;
|
||||
mipmaps = p_image->mipmaps;
|
||||
data = p_image->data;
|
||||
}
|
||||
|
||||
Dictionary compute_image_metrics(const Ref<Image> p_compared_image, bool p_luma_metric = true);
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(Image::Format)
|
||||
VARIANT_ENUM_CAST(Image::Interpolation)
|
||||
VARIANT_ENUM_CAST(Image::CompressMode)
|
||||
VARIANT_ENUM_CAST(Image::CompressSource)
|
||||
VARIANT_ENUM_CAST(Image::UsedChannels)
|
||||
VARIANT_ENUM_CAST(Image::AlphaMode)
|
||||
VARIANT_ENUM_CAST(Image::RoughnessChannel)
|
||||
VARIANT_ENUM_CAST(Image::ASTCFormat)
|
||||
|
||||
#endif // IMAGE_H
|
||||
213
engine/core/io/image_loader.cpp
Normal file
213
engine/core/io/image_loader.cpp
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/**************************************************************************/
|
||||
/* image_loader.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 "image_loader.h"
|
||||
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
void ImageFormatLoader::_bind_methods() {
|
||||
BIND_BITFIELD_FLAG(FLAG_NONE);
|
||||
BIND_BITFIELD_FLAG(FLAG_FORCE_LINEAR);
|
||||
BIND_BITFIELD_FLAG(FLAG_CONVERT_COLORS);
|
||||
}
|
||||
|
||||
bool ImageFormatLoader::recognize(const String &p_extension) const {
|
||||
List<String> extensions;
|
||||
get_recognized_extensions(&extensions);
|
||||
for (const String &E : extensions) {
|
||||
if (E.nocasecmp_to(p_extension) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Error ImageFormatLoaderExtension::load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
|
||||
Error err = ERR_UNAVAILABLE;
|
||||
GDVIRTUAL_CALL(_load_image, p_image, p_fileaccess, p_flags, p_scale, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
void ImageFormatLoaderExtension::get_recognized_extensions(List<String> *p_extension) const {
|
||||
PackedStringArray ext;
|
||||
if (GDVIRTUAL_CALL(_get_recognized_extensions, ext)) {
|
||||
for (int i = 0; i < ext.size(); i++) {
|
||||
p_extension->push_back(ext[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageFormatLoaderExtension::add_format_loader() {
|
||||
ImageLoader::add_image_format_loader(this);
|
||||
}
|
||||
|
||||
void ImageFormatLoaderExtension::remove_format_loader() {
|
||||
ImageLoader::remove_image_format_loader(this);
|
||||
}
|
||||
|
||||
void ImageFormatLoaderExtension::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_get_recognized_extensions);
|
||||
GDVIRTUAL_BIND(_load_image, "image", "fileaccess", "flags", "scale");
|
||||
ClassDB::bind_method(D_METHOD("add_format_loader"), &ImageFormatLoaderExtension::add_format_loader);
|
||||
ClassDB::bind_method(D_METHOD("remove_format_loader"), &ImageFormatLoaderExtension::remove_format_loader);
|
||||
}
|
||||
|
||||
Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<FileAccess> p_custom, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
|
||||
ERR_FAIL_COND_V_MSG(p_image.is_null(), ERR_INVALID_PARAMETER, "Can't load an image: invalid Image object.");
|
||||
|
||||
Ref<FileAccess> f = p_custom;
|
||||
if (f.is_null()) {
|
||||
Error err;
|
||||
f = FileAccess::open(p_file, FileAccess::READ, &err);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), err, "Error opening file '" + p_file + "'.");
|
||||
}
|
||||
|
||||
String extension = p_file.get_extension();
|
||||
|
||||
for (int i = 0; i < loader.size(); i++) {
|
||||
if (!loader[i]->recognize(extension)) {
|
||||
continue;
|
||||
}
|
||||
Error err = loader.write[i]->load_image(p_image, f, p_flags, p_scale);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Error loading image: " + p_file);
|
||||
}
|
||||
|
||||
if (err != ERR_FILE_UNRECOGNIZED) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return ERR_FILE_UNRECOGNIZED;
|
||||
}
|
||||
|
||||
void ImageLoader::get_recognized_extensions(List<String> *p_extensions) {
|
||||
for (int i = 0; i < loader.size(); i++) {
|
||||
loader[i]->get_recognized_extensions(p_extensions);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<ImageFormatLoader> ImageLoader::recognize(const String &p_extension) {
|
||||
for (int i = 0; i < loader.size(); i++) {
|
||||
if (loader[i]->recognize(p_extension)) {
|
||||
return loader[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Vector<Ref<ImageFormatLoader>> ImageLoader::loader;
|
||||
|
||||
void ImageLoader::add_image_format_loader(Ref<ImageFormatLoader> p_loader) {
|
||||
loader.push_back(p_loader);
|
||||
}
|
||||
|
||||
void ImageLoader::remove_image_format_loader(Ref<ImageFormatLoader> p_loader) {
|
||||
loader.erase(p_loader);
|
||||
}
|
||||
|
||||
void ImageLoader::cleanup() {
|
||||
while (loader.size()) {
|
||||
remove_image_format_loader(loader[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
|
||||
Ref<Resource> ResourceFormatLoaderImage::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
if (r_error) {
|
||||
*r_error = ERR_CANT_OPEN;
|
||||
}
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
uint8_t header[4] = { 0, 0, 0, 0 };
|
||||
f->get_buffer(header, 4);
|
||||
|
||||
bool unrecognized = header[0] != 'G' || header[1] != 'D' || header[2] != 'I' || header[3] != 'M';
|
||||
if (unrecognized) {
|
||||
if (r_error) {
|
||||
*r_error = ERR_FILE_UNRECOGNIZED;
|
||||
}
|
||||
ERR_FAIL_V(Ref<Resource>());
|
||||
}
|
||||
|
||||
String extension = f->get_pascal_string();
|
||||
|
||||
int idx = -1;
|
||||
|
||||
for (int i = 0; i < ImageLoader::loader.size(); i++) {
|
||||
if (ImageLoader::loader[i]->recognize(extension)) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx == -1) {
|
||||
if (r_error) {
|
||||
*r_error = ERR_FILE_UNRECOGNIZED;
|
||||
}
|
||||
ERR_FAIL_V(Ref<Resource>());
|
||||
}
|
||||
|
||||
Ref<Image> image;
|
||||
image.instantiate();
|
||||
|
||||
Error err = ImageLoader::loader.write[idx]->load_image(image, f);
|
||||
|
||||
if (err != OK) {
|
||||
if (r_error) {
|
||||
*r_error = err;
|
||||
}
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void ResourceFormatLoaderImage::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("image");
|
||||
}
|
||||
|
||||
bool ResourceFormatLoaderImage::handles_type(const String &p_type) const {
|
||||
return p_type == "Image";
|
||||
}
|
||||
|
||||
String ResourceFormatLoaderImage::get_resource_type(const String &p_path) const {
|
||||
return p_path.get_extension().to_lower() == "image" ? "Image" : String();
|
||||
}
|
||||
112
engine/core/io/image_loader.h
Normal file
112
engine/core/io/image_loader.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/**************************************************************************/
|
||||
/* image_loader.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef IMAGE_LOADER_H
|
||||
#define IMAGE_LOADER_H
|
||||
|
||||
#include "core/core_bind.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/image.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/list.h"
|
||||
#include "core/variant/binder_common.h"
|
||||
|
||||
class ImageLoader;
|
||||
|
||||
class ImageFormatLoader : public RefCounted {
|
||||
GDCLASS(ImageFormatLoader, RefCounted);
|
||||
|
||||
friend class ImageLoader;
|
||||
friend class ResourceFormatLoaderImage;
|
||||
|
||||
public:
|
||||
enum LoaderFlags {
|
||||
FLAG_NONE = 0,
|
||||
FLAG_FORCE_LINEAR = 1,
|
||||
FLAG_CONVERT_COLORS = 2,
|
||||
};
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, BitField<ImageFormatLoader::LoaderFlags> p_flags = FLAG_NONE, float p_scale = 1.0) = 0;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const = 0;
|
||||
bool recognize(const String &p_extension) const;
|
||||
|
||||
public:
|
||||
virtual ~ImageFormatLoader() {}
|
||||
};
|
||||
|
||||
VARIANT_BITFIELD_CAST(ImageFormatLoader::LoaderFlags);
|
||||
|
||||
class ImageFormatLoaderExtension : public ImageFormatLoader {
|
||||
GDCLASS(ImageFormatLoaderExtension, ImageFormatLoader);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, BitField<ImageFormatLoader::LoaderFlags> p_flags = FLAG_NONE, float p_scale = 1.0) override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
|
||||
void add_format_loader();
|
||||
void remove_format_loader();
|
||||
|
||||
GDVIRTUAL0RC(PackedStringArray, _get_recognized_extensions);
|
||||
GDVIRTUAL4R(Error, _load_image, Ref<Image>, Ref<FileAccess>, BitField<ImageFormatLoader::LoaderFlags>, float);
|
||||
};
|
||||
|
||||
class ImageLoader {
|
||||
static Vector<Ref<ImageFormatLoader>> loader;
|
||||
friend class ResourceFormatLoaderImage;
|
||||
|
||||
protected:
|
||||
public:
|
||||
static Error load_image(const String &p_file, Ref<Image> p_image, Ref<FileAccess> p_custom = Ref<FileAccess>(), BitField<ImageFormatLoader::LoaderFlags> p_flags = ImageFormatLoader::FLAG_NONE, float p_scale = 1.0);
|
||||
static void get_recognized_extensions(List<String> *p_extensions);
|
||||
static Ref<ImageFormatLoader> recognize(const String &p_extension);
|
||||
|
||||
static void add_image_format_loader(Ref<ImageFormatLoader> p_loader);
|
||||
static void remove_image_format_loader(Ref<ImageFormatLoader> p_loader);
|
||||
|
||||
static void cleanup();
|
||||
};
|
||||
|
||||
class ResourceFormatLoaderImage : public ResourceFormatLoader {
|
||||
public:
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual bool handles_type(const String &p_type) const override;
|
||||
virtual String get_resource_type(const String &p_path) const override;
|
||||
};
|
||||
|
||||
#endif // IMAGE_LOADER_H
|
||||
354
engine/core/io/ip.cpp
Normal file
354
engine/core/io/ip.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/**************************************************************************/
|
||||
/* ip.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 "ip.h"
|
||||
|
||||
#include "core/os/semaphore.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/variant/typed_array.h"
|
||||
|
||||
/************* RESOLVER ******************/
|
||||
|
||||
struct _IP_ResolverPrivate {
|
||||
struct QueueItem {
|
||||
SafeNumeric<IP::ResolverStatus> status;
|
||||
|
||||
List<IPAddress> response;
|
||||
|
||||
String hostname;
|
||||
IP::Type type;
|
||||
|
||||
void clear() {
|
||||
status.set(IP::RESOLVER_STATUS_NONE);
|
||||
response.clear();
|
||||
type = IP::TYPE_NONE;
|
||||
hostname = "";
|
||||
};
|
||||
|
||||
QueueItem() {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
QueueItem queue[IP::RESOLVER_MAX_QUERIES];
|
||||
|
||||
IP::ResolverID find_empty_id() const {
|
||||
for (int i = 0; i < IP::RESOLVER_MAX_QUERIES; i++) {
|
||||
if (queue[i].status.get() == IP::RESOLVER_STATUS_NONE) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return IP::RESOLVER_INVALID_ID;
|
||||
}
|
||||
|
||||
Mutex mutex;
|
||||
Semaphore sem;
|
||||
|
||||
Thread thread;
|
||||
SafeFlag thread_abort;
|
||||
|
||||
void resolve_queues() {
|
||||
for (int i = 0; i < IP::RESOLVER_MAX_QUERIES; i++) {
|
||||
if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mutex.lock();
|
||||
List<IPAddress> response;
|
||||
String hostname = queue[i].hostname;
|
||||
IP::Type type = queue[i].type;
|
||||
mutex.unlock();
|
||||
|
||||
// We should not lock while resolving the hostname,
|
||||
// only when modifying the queue.
|
||||
IP::get_singleton()->_resolve_hostname(response, hostname, type);
|
||||
|
||||
MutexLock lock(mutex);
|
||||
// Could have been completed by another function, or deleted.
|
||||
if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) {
|
||||
continue;
|
||||
}
|
||||
// We might be overriding another result, but we don't care as long as the result is valid.
|
||||
if (response.size()) {
|
||||
String key = get_cache_key(hostname, type);
|
||||
cache[key] = response;
|
||||
}
|
||||
queue[i].response = response;
|
||||
queue[i].status.set(response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE);
|
||||
}
|
||||
}
|
||||
|
||||
static void _thread_function(void *self) {
|
||||
_IP_ResolverPrivate *ipr = static_cast<_IP_ResolverPrivate *>(self);
|
||||
|
||||
while (!ipr->thread_abort.is_set()) {
|
||||
ipr->sem.wait();
|
||||
ipr->resolve_queues();
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<String, List<IPAddress>> cache;
|
||||
|
||||
static String get_cache_key(const String &p_hostname, IP::Type p_type) {
|
||||
return itos(p_type) + p_hostname;
|
||||
}
|
||||
};
|
||||
|
||||
IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) {
|
||||
const PackedStringArray addresses = resolve_hostname_addresses(p_hostname, p_type);
|
||||
return addresses.size() ? (IPAddress)addresses[0] : IPAddress();
|
||||
}
|
||||
|
||||
PackedStringArray IP::resolve_hostname_addresses(const String &p_hostname, Type p_type) {
|
||||
List<IPAddress> res;
|
||||
String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type);
|
||||
|
||||
resolver->mutex.lock();
|
||||
if (resolver->cache.has(key)) {
|
||||
res = resolver->cache[key];
|
||||
} else {
|
||||
// This should be run unlocked so the resolver thread can keep resolving
|
||||
// other requests.
|
||||
resolver->mutex.unlock();
|
||||
_resolve_hostname(res, p_hostname, p_type);
|
||||
resolver->mutex.lock();
|
||||
// We might be overriding another result, but we don't care as long as the result is valid.
|
||||
if (res.size()) {
|
||||
resolver->cache[key] = res;
|
||||
}
|
||||
}
|
||||
resolver->mutex.unlock();
|
||||
|
||||
PackedStringArray result;
|
||||
for (const IPAddress &E : res) {
|
||||
result.push_back(String(E));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Type p_type) {
|
||||
MutexLock lock(resolver->mutex);
|
||||
|
||||
ResolverID id = resolver->find_empty_id();
|
||||
|
||||
if (id == RESOLVER_INVALID_ID) {
|
||||
WARN_PRINT("Out of resolver queries");
|
||||
return id;
|
||||
}
|
||||
|
||||
String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type);
|
||||
resolver->queue[id].hostname = p_hostname;
|
||||
resolver->queue[id].type = p_type;
|
||||
if (resolver->cache.has(key)) {
|
||||
resolver->queue[id].response = resolver->cache[key];
|
||||
resolver->queue[id].status.set(IP::RESOLVER_STATUS_DONE);
|
||||
} else {
|
||||
resolver->queue[id].response = List<IPAddress>();
|
||||
resolver->queue[id].status.set(IP::RESOLVER_STATUS_WAITING);
|
||||
if (resolver->thread.is_started()) {
|
||||
resolver->sem.post();
|
||||
} else {
|
||||
resolver->resolve_queues();
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
IP::ResolverStatus IP::get_resolve_item_status(ResolverID p_id) const {
|
||||
ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, IP::RESOLVER_STATUS_NONE, vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES));
|
||||
|
||||
IP::ResolverStatus res = resolver->queue[p_id].status.get();
|
||||
if (res == IP::RESOLVER_STATUS_NONE) {
|
||||
ERR_PRINT("Condition status == IP::RESOLVER_STATUS_NONE");
|
||||
return IP::RESOLVER_STATUS_NONE;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
IPAddress IP::get_resolve_item_address(ResolverID p_id) const {
|
||||
ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, IPAddress(), vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES));
|
||||
|
||||
MutexLock lock(resolver->mutex);
|
||||
|
||||
if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
|
||||
ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
|
||||
return IPAddress();
|
||||
}
|
||||
|
||||
List<IPAddress> res = resolver->queue[p_id].response;
|
||||
|
||||
for (const IPAddress &E : res) {
|
||||
if (E.is_valid()) {
|
||||
return E;
|
||||
}
|
||||
}
|
||||
return IPAddress();
|
||||
}
|
||||
|
||||
Array IP::get_resolve_item_addresses(ResolverID p_id) const {
|
||||
ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, Array(), vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES));
|
||||
MutexLock lock(resolver->mutex);
|
||||
|
||||
if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
|
||||
ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
|
||||
return Array();
|
||||
}
|
||||
|
||||
List<IPAddress> res = resolver->queue[p_id].response;
|
||||
|
||||
Array result;
|
||||
for (const IPAddress &E : res) {
|
||||
if (E.is_valid()) {
|
||||
result.push_back(String(E));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void IP::erase_resolve_item(ResolverID p_id) {
|
||||
ERR_FAIL_INDEX_MSG(p_id, IP::RESOLVER_MAX_QUERIES, vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES));
|
||||
|
||||
resolver->queue[p_id].status.set(IP::RESOLVER_STATUS_NONE);
|
||||
}
|
||||
|
||||
void IP::clear_cache(const String &p_hostname) {
|
||||
MutexLock lock(resolver->mutex);
|
||||
|
||||
if (p_hostname.is_empty()) {
|
||||
resolver->cache.clear();
|
||||
} else {
|
||||
resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_NONE));
|
||||
resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_IPV4));
|
||||
resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_IPV6));
|
||||
resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_ANY));
|
||||
}
|
||||
}
|
||||
|
||||
PackedStringArray IP::_get_local_addresses() const {
|
||||
PackedStringArray addresses;
|
||||
List<IPAddress> ip_addresses;
|
||||
get_local_addresses(&ip_addresses);
|
||||
for (const IPAddress &E : ip_addresses) {
|
||||
addresses.push_back(E);
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
TypedArray<Dictionary> IP::_get_local_interfaces() const {
|
||||
TypedArray<Dictionary> results;
|
||||
HashMap<String, Interface_Info> interfaces;
|
||||
get_local_interfaces(&interfaces);
|
||||
for (KeyValue<String, Interface_Info> &E : interfaces) {
|
||||
Interface_Info &c = E.value;
|
||||
Dictionary rc;
|
||||
rc["name"] = c.name;
|
||||
rc["friendly"] = c.name_friendly;
|
||||
rc["index"] = c.index;
|
||||
|
||||
Array ips;
|
||||
for (const IPAddress &F : c.ip_addresses) {
|
||||
ips.push_front(F);
|
||||
}
|
||||
rc["addresses"] = ips;
|
||||
|
||||
results.push_front(rc);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void IP::get_local_addresses(List<IPAddress> *r_addresses) const {
|
||||
HashMap<String, Interface_Info> interfaces;
|
||||
get_local_interfaces(&interfaces);
|
||||
for (const KeyValue<String, Interface_Info> &E : interfaces) {
|
||||
for (const IPAddress &F : E.value.ip_addresses) {
|
||||
r_addresses->push_front(F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IP::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("resolve_hostname", "host", "ip_type"), &IP::resolve_hostname, DEFVAL(IP::TYPE_ANY));
|
||||
ClassDB::bind_method(D_METHOD("resolve_hostname_addresses", "host", "ip_type"), &IP::resolve_hostname_addresses, DEFVAL(IP::TYPE_ANY));
|
||||
ClassDB::bind_method(D_METHOD("resolve_hostname_queue_item", "host", "ip_type"), &IP::resolve_hostname_queue_item, DEFVAL(IP::TYPE_ANY));
|
||||
ClassDB::bind_method(D_METHOD("get_resolve_item_status", "id"), &IP::get_resolve_item_status);
|
||||
ClassDB::bind_method(D_METHOD("get_resolve_item_address", "id"), &IP::get_resolve_item_address);
|
||||
ClassDB::bind_method(D_METHOD("get_resolve_item_addresses", "id"), &IP::get_resolve_item_addresses);
|
||||
ClassDB::bind_method(D_METHOD("erase_resolve_item", "id"), &IP::erase_resolve_item);
|
||||
ClassDB::bind_method(D_METHOD("get_local_addresses"), &IP::_get_local_addresses);
|
||||
ClassDB::bind_method(D_METHOD("get_local_interfaces"), &IP::_get_local_interfaces);
|
||||
ClassDB::bind_method(D_METHOD("clear_cache", "hostname"), &IP::clear_cache, DEFVAL(""));
|
||||
|
||||
BIND_ENUM_CONSTANT(RESOLVER_STATUS_NONE);
|
||||
BIND_ENUM_CONSTANT(RESOLVER_STATUS_WAITING);
|
||||
BIND_ENUM_CONSTANT(RESOLVER_STATUS_DONE);
|
||||
BIND_ENUM_CONSTANT(RESOLVER_STATUS_ERROR);
|
||||
|
||||
BIND_CONSTANT(RESOLVER_MAX_QUERIES);
|
||||
BIND_CONSTANT(RESOLVER_INVALID_ID);
|
||||
|
||||
BIND_ENUM_CONSTANT(TYPE_NONE);
|
||||
BIND_ENUM_CONSTANT(TYPE_IPV4);
|
||||
BIND_ENUM_CONSTANT(TYPE_IPV6);
|
||||
BIND_ENUM_CONSTANT(TYPE_ANY);
|
||||
}
|
||||
|
||||
IP *IP::singleton = nullptr;
|
||||
|
||||
IP *IP::get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
IP *(*IP::_create)() = nullptr;
|
||||
|
||||
IP *IP::create() {
|
||||
ERR_FAIL_COND_V_MSG(singleton, nullptr, "IP singleton already exist.");
|
||||
ERR_FAIL_NULL_V(_create, nullptr);
|
||||
return _create();
|
||||
}
|
||||
|
||||
IP::IP() {
|
||||
singleton = this;
|
||||
resolver = memnew(_IP_ResolverPrivate);
|
||||
|
||||
resolver->thread_abort.clear();
|
||||
resolver->thread.start(_IP_ResolverPrivate::_thread_function, resolver);
|
||||
}
|
||||
|
||||
IP::~IP() {
|
||||
resolver->thread_abort.set();
|
||||
resolver->sem.post();
|
||||
resolver->thread.wait_to_finish();
|
||||
|
||||
memdelete(resolver);
|
||||
}
|
||||
114
engine/core/io/ip.h
Normal file
114
engine/core/io/ip.h
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/**************************************************************************/
|
||||
/* ip.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef IP_H
|
||||
#define IP_H
|
||||
|
||||
#include "core/io/ip_address.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
template <typename T>
|
||||
class TypedArray;
|
||||
|
||||
struct _IP_ResolverPrivate;
|
||||
|
||||
class IP : public Object {
|
||||
GDCLASS(IP, Object);
|
||||
|
||||
public:
|
||||
enum ResolverStatus {
|
||||
RESOLVER_STATUS_NONE,
|
||||
RESOLVER_STATUS_WAITING,
|
||||
RESOLVER_STATUS_DONE,
|
||||
RESOLVER_STATUS_ERROR,
|
||||
};
|
||||
|
||||
enum Type {
|
||||
TYPE_NONE = 0,
|
||||
TYPE_IPV4 = 1,
|
||||
TYPE_IPV6 = 2,
|
||||
TYPE_ANY = 3,
|
||||
};
|
||||
|
||||
enum {
|
||||
RESOLVER_MAX_QUERIES = 256,
|
||||
RESOLVER_INVALID_ID = -1
|
||||
};
|
||||
|
||||
typedef int ResolverID;
|
||||
|
||||
private:
|
||||
_IP_ResolverPrivate *resolver = nullptr;
|
||||
|
||||
protected:
|
||||
static IP *singleton;
|
||||
static void _bind_methods();
|
||||
|
||||
PackedStringArray _get_local_addresses() const;
|
||||
TypedArray<Dictionary> _get_local_interfaces() const;
|
||||
|
||||
static IP *(*_create)();
|
||||
|
||||
public:
|
||||
struct Interface_Info {
|
||||
String name;
|
||||
String name_friendly;
|
||||
String index;
|
||||
List<IPAddress> ip_addresses;
|
||||
};
|
||||
|
||||
IPAddress resolve_hostname(const String &p_hostname, Type p_type = TYPE_ANY);
|
||||
PackedStringArray resolve_hostname_addresses(const String &p_hostname, Type p_type = TYPE_ANY);
|
||||
// async resolver hostname
|
||||
ResolverID resolve_hostname_queue_item(const String &p_hostname, Type p_type = TYPE_ANY);
|
||||
ResolverStatus get_resolve_item_status(ResolverID p_id) const;
|
||||
IPAddress get_resolve_item_address(ResolverID p_id) const;
|
||||
virtual void get_local_addresses(List<IPAddress> *r_addresses) const;
|
||||
|
||||
virtual void _resolve_hostname(List<IPAddress> &r_addresses, const String &p_hostname, Type p_type = TYPE_ANY) const = 0;
|
||||
Array get_resolve_item_addresses(ResolverID p_id) const;
|
||||
|
||||
virtual void get_local_interfaces(HashMap<String, Interface_Info> *r_interfaces) const = 0;
|
||||
void erase_resolve_item(ResolverID p_id);
|
||||
|
||||
void clear_cache(const String &p_hostname = "");
|
||||
|
||||
static IP *get_singleton();
|
||||
|
||||
static IP *create();
|
||||
|
||||
IP();
|
||||
~IP();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(IP::Type);
|
||||
VARIANT_ENUM_CAST(IP::ResolverStatus);
|
||||
|
||||
#endif // IP_H
|
||||
244
engine/core/io/ip_address.cpp
Normal file
244
engine/core/io/ip_address.cpp
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
/**************************************************************************/
|
||||
/* ip_address.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 "ip_address.h"
|
||||
/*
|
||||
IPAddress::operator Variant() const {
|
||||
return operator String();
|
||||
}*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
IPAddress::operator String() const {
|
||||
if (wildcard) {
|
||||
return "*";
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (is_ipv4()) {
|
||||
// IPv4 address mapped to IPv6
|
||||
return itos(field8[12]) + "." + itos(field8[13]) + "." + itos(field8[14]) + "." + itos(field8[15]);
|
||||
}
|
||||
String ret;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (i > 0) {
|
||||
ret = ret + ":";
|
||||
}
|
||||
uint16_t num = (field8[i * 2] << 8) + field8[i * 2 + 1];
|
||||
ret = ret + String::num_int64(num, 16);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void _parse_hex(const String &p_string, int p_start, uint8_t *p_dst) {
|
||||
uint16_t ret = 0;
|
||||
for (int i = p_start; i < p_start + 4; i++) {
|
||||
if (i >= p_string.length()) {
|
||||
break;
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
char32_t c = p_string[i];
|
||||
if (is_digit(c)) {
|
||||
n = c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
n = 10 + (c - 'a');
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
n = 10 + (c - 'A');
|
||||
} else if (c == ':') {
|
||||
break;
|
||||
} else {
|
||||
ERR_FAIL_MSG("Invalid character in IPv6 address: " + p_string + ".");
|
||||
}
|
||||
ret = ret << 4;
|
||||
ret += n;
|
||||
}
|
||||
|
||||
p_dst[0] = ret >> 8;
|
||||
p_dst[1] = ret & 0xff;
|
||||
}
|
||||
|
||||
void IPAddress::_parse_ipv6(const String &p_string) {
|
||||
static const int parts_total = 8;
|
||||
int parts[parts_total] = { 0 };
|
||||
int parts_count = 0;
|
||||
bool part_found = false;
|
||||
bool part_skip = false;
|
||||
bool part_ipv4 = false;
|
||||
int parts_idx = 0;
|
||||
|
||||
for (int i = 0; i < p_string.length(); i++) {
|
||||
char32_t c = p_string[i];
|
||||
if (c == ':') {
|
||||
if (i == 0) {
|
||||
continue; // next must be a ":"
|
||||
}
|
||||
if (!part_found) {
|
||||
part_skip = true;
|
||||
parts[parts_idx++] = -1;
|
||||
}
|
||||
part_found = false;
|
||||
} else if (c == '.') {
|
||||
part_ipv4 = true;
|
||||
|
||||
} else if (is_hex_digit(c)) {
|
||||
if (!part_found) {
|
||||
parts[parts_idx++] = i;
|
||||
part_found = true;
|
||||
++parts_count;
|
||||
}
|
||||
} else {
|
||||
ERR_FAIL_MSG("Invalid character in IPv6 address: " + p_string + ".");
|
||||
}
|
||||
}
|
||||
|
||||
int parts_extra = 0;
|
||||
if (part_skip) {
|
||||
parts_extra = parts_total - parts_count;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
for (int i = 0; i < parts_idx; i++) {
|
||||
if (parts[i] == -1) {
|
||||
for (int j = 0; j < parts_extra; j++) {
|
||||
field16[idx++] = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part_ipv4 && i == parts_idx - 1) {
|
||||
_parse_ipv4(p_string, parts[i], (uint8_t *)&field16[idx]); // should be the last one
|
||||
} else {
|
||||
_parse_hex(p_string, parts[i], (uint8_t *)&(field16[idx++]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IPAddress::_parse_ipv4(const String &p_string, int p_start, uint8_t *p_ret) {
|
||||
String ip;
|
||||
if (p_start != 0) {
|
||||
ip = p_string.substr(p_start, p_string.length() - p_start);
|
||||
} else {
|
||||
ip = p_string;
|
||||
}
|
||||
|
||||
int slices = ip.get_slice_count(".");
|
||||
ERR_FAIL_COND_MSG(slices != 4, "Invalid IP address string: " + ip + ".");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
p_ret[i] = ip.get_slicec('.', i).to_int();
|
||||
}
|
||||
}
|
||||
|
||||
void IPAddress::clear() {
|
||||
memset(&field8[0], 0, sizeof(field8));
|
||||
valid = false;
|
||||
wildcard = false;
|
||||
}
|
||||
|
||||
bool IPAddress::is_ipv4() const {
|
||||
return (field32[0] == 0 && field32[1] == 0 && field16[4] == 0 && field16[5] == 0xffff);
|
||||
}
|
||||
|
||||
const uint8_t *IPAddress::get_ipv4() const {
|
||||
ERR_FAIL_COND_V_MSG(!is_ipv4(), &(field8[12]), "IPv4 requested, but current IP is IPv6."); // Not the correct IPv4 (it's an IPv6), but we don't want to return a null pointer risking an engine crash.
|
||||
return &(field8[12]);
|
||||
}
|
||||
|
||||
void IPAddress::set_ipv4(const uint8_t *p_ip) {
|
||||
clear();
|
||||
valid = true;
|
||||
field16[5] = 0xffff;
|
||||
field32[3] = *((const uint32_t *)p_ip);
|
||||
}
|
||||
|
||||
const uint8_t *IPAddress::get_ipv6() const {
|
||||
return field8;
|
||||
}
|
||||
|
||||
void IPAddress::set_ipv6(const uint8_t *p_buf) {
|
||||
clear();
|
||||
valid = true;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
field8[i] = p_buf[i];
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(const String &p_string) {
|
||||
clear();
|
||||
|
||||
if (p_string == "*") {
|
||||
// Wildcard (not a valid IP)
|
||||
wildcard = true;
|
||||
|
||||
} else if (p_string.contains(":")) {
|
||||
// IPv6
|
||||
_parse_ipv6(p_string);
|
||||
valid = true;
|
||||
|
||||
} else if (p_string.get_slice_count(".") == 4) {
|
||||
// IPv4 (mapped to IPv6 internally)
|
||||
field16[5] = 0xffff;
|
||||
_parse_ipv4(p_string, 0, &field8[12]);
|
||||
valid = true;
|
||||
|
||||
} else {
|
||||
ERR_PRINT("Invalid IP address.");
|
||||
}
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ static void _32_to_buf(uint8_t *p_dst, uint32_t p_n) {
|
||||
p_dst[0] = (p_n >> 24) & 0xff;
|
||||
p_dst[1] = (p_n >> 16) & 0xff;
|
||||
p_dst[2] = (p_n >> 8) & 0xff;
|
||||
p_dst[3] = (p_n >> 0) & 0xff;
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(uint32_t p_a, uint32_t p_b, uint32_t p_c, uint32_t p_d, bool is_v6) {
|
||||
clear();
|
||||
valid = true;
|
||||
if (!is_v6) {
|
||||
// Mapped to IPv6
|
||||
field16[5] = 0xffff;
|
||||
field8[12] = p_a;
|
||||
field8[13] = p_b;
|
||||
field8[14] = p_c;
|
||||
field8[15] = p_d;
|
||||
} else {
|
||||
_32_to_buf(&field8[0], p_a);
|
||||
_32_to_buf(&field8[4], p_b);
|
||||
_32_to_buf(&field8[8], p_c);
|
||||
_32_to_buf(&field8[12], p_d);
|
||||
}
|
||||
}
|
||||
99
engine/core/io/ip_address.h
Normal file
99
engine/core/io/ip_address.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/**************************************************************************/
|
||||
/* ip_address.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef IP_ADDRESS_H
|
||||
#define IP_ADDRESS_H
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
struct IPAddress {
|
||||
private:
|
||||
union {
|
||||
uint8_t field8[16];
|
||||
uint16_t field16[8];
|
||||
uint32_t field32[4];
|
||||
};
|
||||
|
||||
bool valid;
|
||||
bool wildcard;
|
||||
|
||||
protected:
|
||||
void _parse_ipv6(const String &p_string);
|
||||
void _parse_ipv4(const String &p_string, int p_start, uint8_t *p_ret);
|
||||
|
||||
public:
|
||||
//operator Variant() const;
|
||||
bool operator==(const IPAddress &p_ip) const {
|
||||
if (p_ip.valid != valid) {
|
||||
return false;
|
||||
}
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (field32[i] != p_ip.field32[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator!=(const IPAddress &p_ip) const {
|
||||
if (p_ip.valid != valid) {
|
||||
return true;
|
||||
}
|
||||
if (!valid) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (field32[i] != p_ip.field32[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear();
|
||||
bool is_wildcard() const { return wildcard; }
|
||||
bool is_valid() const { return valid; }
|
||||
bool is_ipv4() const;
|
||||
const uint8_t *get_ipv4() const;
|
||||
void set_ipv4(const uint8_t *p_ip);
|
||||
|
||||
const uint8_t *get_ipv6() const;
|
||||
void set_ipv6(const uint8_t *p_buf);
|
||||
|
||||
operator String() const;
|
||||
IPAddress(const String &p_string);
|
||||
IPAddress(uint32_t p_a, uint32_t p_b, uint32_t p_c, uint32_t p_d, bool is_v6 = false);
|
||||
IPAddress() { clear(); }
|
||||
};
|
||||
|
||||
#endif // IP_ADDRESS_H
|
||||
678
engine/core/io/json.cpp
Normal file
678
engine/core/io/json.cpp
Normal file
|
|
@ -0,0 +1,678 @@
|
|||
/**************************************************************************/
|
||||
/* json.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 "json.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
const char *JSON::tk_name[TK_MAX] = {
|
||||
"'{'",
|
||||
"'}'",
|
||||
"'['",
|
||||
"']'",
|
||||
"identifier",
|
||||
"string",
|
||||
"number",
|
||||
"':'",
|
||||
"','",
|
||||
"EOF",
|
||||
};
|
||||
|
||||
String JSON::_make_indent(const String &p_indent, int p_size) {
|
||||
return p_indent.repeat(p_size);
|
||||
}
|
||||
|
||||
String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys, HashSet<const void *> &p_markers, bool p_full_precision) {
|
||||
ERR_FAIL_COND_V_MSG(p_cur_indent > Variant::MAX_RECURSION_DEPTH, "...", "JSON structure is too deep. Bailing.");
|
||||
|
||||
String colon = ":";
|
||||
String end_statement = "";
|
||||
|
||||
if (!p_indent.is_empty()) {
|
||||
colon += " ";
|
||||
end_statement += "\n";
|
||||
}
|
||||
|
||||
switch (p_var.get_type()) {
|
||||
case Variant::NIL:
|
||||
return "null";
|
||||
case Variant::BOOL:
|
||||
return p_var.operator bool() ? "true" : "false";
|
||||
case Variant::INT:
|
||||
return itos(p_var);
|
||||
case Variant::FLOAT: {
|
||||
double num = p_var;
|
||||
if (p_full_precision) {
|
||||
// Store unreliable digits (17) instead of just reliable
|
||||
// digits (14) so that the value can be decoded exactly.
|
||||
return String::num(num, 17 - (int)floor(log10(num)));
|
||||
} else {
|
||||
// Store only reliable digits (14) by default.
|
||||
return String::num(num, 14 - (int)floor(log10(num)));
|
||||
}
|
||||
}
|
||||
case Variant::PACKED_INT32_ARRAY:
|
||||
case Variant::PACKED_INT64_ARRAY:
|
||||
case Variant::PACKED_FLOAT32_ARRAY:
|
||||
case Variant::PACKED_FLOAT64_ARRAY:
|
||||
case Variant::PACKED_STRING_ARRAY:
|
||||
case Variant::ARRAY: {
|
||||
Array a = p_var;
|
||||
if (a.is_empty()) {
|
||||
return "[]";
|
||||
}
|
||||
String s = "[";
|
||||
s += end_statement;
|
||||
|
||||
ERR_FAIL_COND_V_MSG(p_markers.has(a.id()), "\"[...]\"", "Converting circular structure to JSON.");
|
||||
p_markers.insert(a.id());
|
||||
|
||||
bool first = true;
|
||||
for (const Variant &var : a) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
s += ",";
|
||||
s += end_statement;
|
||||
}
|
||||
s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(var, p_indent, p_cur_indent + 1, p_sort_keys, p_markers);
|
||||
}
|
||||
s += end_statement + _make_indent(p_indent, p_cur_indent) + "]";
|
||||
p_markers.erase(a.id());
|
||||
return s;
|
||||
}
|
||||
case Variant::DICTIONARY: {
|
||||
String s = "{";
|
||||
s += end_statement;
|
||||
Dictionary d = p_var;
|
||||
|
||||
ERR_FAIL_COND_V_MSG(p_markers.has(d.id()), "\"{...}\"", "Converting circular structure to JSON.");
|
||||
p_markers.insert(d.id());
|
||||
|
||||
List<Variant> keys;
|
||||
d.get_key_list(&keys);
|
||||
|
||||
if (p_sort_keys) {
|
||||
keys.sort();
|
||||
}
|
||||
|
||||
bool first_key = true;
|
||||
for (const Variant &E : keys) {
|
||||
if (first_key) {
|
||||
first_key = false;
|
||||
} else {
|
||||
s += ",";
|
||||
s += end_statement;
|
||||
}
|
||||
s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(String(E), p_indent, p_cur_indent + 1, p_sort_keys, p_markers);
|
||||
s += colon;
|
||||
s += _stringify(d[E], p_indent, p_cur_indent + 1, p_sort_keys, p_markers);
|
||||
}
|
||||
|
||||
s += end_statement + _make_indent(p_indent, p_cur_indent) + "}";
|
||||
p_markers.erase(d.id());
|
||||
return s;
|
||||
}
|
||||
default:
|
||||
return "\"" + String(p_var).json_escape() + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_token, int &line, String &r_err_str) {
|
||||
while (p_len > 0) {
|
||||
switch (p_str[index]) {
|
||||
case '\n': {
|
||||
line++;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 0: {
|
||||
r_token.type = TK_EOF;
|
||||
return OK;
|
||||
} break;
|
||||
case '{': {
|
||||
r_token.type = TK_CURLY_BRACKET_OPEN;
|
||||
index++;
|
||||
return OK;
|
||||
}
|
||||
case '}': {
|
||||
r_token.type = TK_CURLY_BRACKET_CLOSE;
|
||||
index++;
|
||||
return OK;
|
||||
}
|
||||
case '[': {
|
||||
r_token.type = TK_BRACKET_OPEN;
|
||||
index++;
|
||||
return OK;
|
||||
}
|
||||
case ']': {
|
||||
r_token.type = TK_BRACKET_CLOSE;
|
||||
index++;
|
||||
return OK;
|
||||
}
|
||||
case ':': {
|
||||
r_token.type = TK_COLON;
|
||||
index++;
|
||||
return OK;
|
||||
}
|
||||
case ',': {
|
||||
r_token.type = TK_COMMA;
|
||||
index++;
|
||||
return OK;
|
||||
}
|
||||
case '"': {
|
||||
index++;
|
||||
String str;
|
||||
while (true) {
|
||||
if (p_str[index] == 0) {
|
||||
r_err_str = "Unterminated String";
|
||||
return ERR_PARSE_ERROR;
|
||||
} else if (p_str[index] == '"') {
|
||||
index++;
|
||||
break;
|
||||
} else if (p_str[index] == '\\') {
|
||||
//escaped characters...
|
||||
index++;
|
||||
char32_t next = p_str[index];
|
||||
if (next == 0) {
|
||||
r_err_str = "Unterminated String";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
char32_t res = 0;
|
||||
|
||||
switch (next) {
|
||||
case 'b':
|
||||
res = 8;
|
||||
break;
|
||||
case 't':
|
||||
res = 9;
|
||||
break;
|
||||
case 'n':
|
||||
res = 10;
|
||||
break;
|
||||
case 'f':
|
||||
res = 12;
|
||||
break;
|
||||
case 'r':
|
||||
res = 13;
|
||||
break;
|
||||
case 'u': {
|
||||
// hex number
|
||||
for (int j = 0; j < 4; j++) {
|
||||
char32_t c = p_str[index + j + 1];
|
||||
if (c == 0) {
|
||||
r_err_str = "Unterminated String";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
if (!is_hex_digit(c)) {
|
||||
r_err_str = "Malformed hex constant in string";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
char32_t v;
|
||||
if (is_digit(c)) {
|
||||
v = c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
v = c - 'a';
|
||||
v += 10;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
v = c - 'A';
|
||||
v += 10;
|
||||
} else {
|
||||
ERR_PRINT("Bug parsing hex constant.");
|
||||
v = 0;
|
||||
}
|
||||
|
||||
res <<= 4;
|
||||
res |= v;
|
||||
}
|
||||
index += 4; //will add at the end anyway
|
||||
|
||||
if ((res & 0xfffffc00) == 0xd800) {
|
||||
if (p_str[index + 1] != '\\' || p_str[index + 2] != 'u') {
|
||||
r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
index += 2;
|
||||
char32_t trail = 0;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
char32_t c = p_str[index + j + 1];
|
||||
if (c == 0) {
|
||||
r_err_str = "Unterminated String";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
if (!is_hex_digit(c)) {
|
||||
r_err_str = "Malformed hex constant in string";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
char32_t v;
|
||||
if (is_digit(c)) {
|
||||
v = c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
v = c - 'a';
|
||||
v += 10;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
v = c - 'A';
|
||||
v += 10;
|
||||
} else {
|
||||
ERR_PRINT("Bug parsing hex constant.");
|
||||
v = 0;
|
||||
}
|
||||
|
||||
trail <<= 4;
|
||||
trail |= v;
|
||||
}
|
||||
if ((trail & 0xfffffc00) == 0xdc00) {
|
||||
res = (res << 10UL) + trail - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
|
||||
index += 4; //will add at the end anyway
|
||||
} else {
|
||||
r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
} else if ((res & 0xfffffc00) == 0xdc00) {
|
||||
r_err_str = "Invalid UTF-16 sequence in string, unpaired trail surrogate";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
} break;
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/': {
|
||||
res = next;
|
||||
} break;
|
||||
default: {
|
||||
r_err_str = "Invalid escape sequence.";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
str += res;
|
||||
|
||||
} else {
|
||||
if (p_str[index] == '\n') {
|
||||
line++;
|
||||
}
|
||||
str += p_str[index];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
r_token.type = TK_STRING;
|
||||
r_token.value = str;
|
||||
return OK;
|
||||
|
||||
} break;
|
||||
default: {
|
||||
if (p_str[index] <= 32) {
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (p_str[index] == '-' || is_digit(p_str[index])) {
|
||||
//a number
|
||||
const char32_t *rptr;
|
||||
double number = String::to_float(&p_str[index], &rptr);
|
||||
index += (rptr - &p_str[index]);
|
||||
r_token.type = TK_NUMBER;
|
||||
r_token.value = number;
|
||||
return OK;
|
||||
|
||||
} else if (is_ascii_alphabet_char(p_str[index])) {
|
||||
String id;
|
||||
|
||||
while (is_ascii_alphabet_char(p_str[index])) {
|
||||
id += p_str[index];
|
||||
index++;
|
||||
}
|
||||
|
||||
r_token.type = TK_IDENTIFIER;
|
||||
r_token.value = id;
|
||||
return OK;
|
||||
} else {
|
||||
r_err_str = "Unexpected character.";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
Error JSON::_parse_value(Variant &value, Token &token, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str) {
|
||||
if (p_depth > Variant::MAX_RECURSION_DEPTH) {
|
||||
r_err_str = "JSON structure is too deep. Bailing.";
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (token.type == TK_CURLY_BRACKET_OPEN) {
|
||||
Dictionary d;
|
||||
Error err = _parse_object(d, p_str, index, p_len, line, p_depth + 1, r_err_str);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
value = d;
|
||||
} else if (token.type == TK_BRACKET_OPEN) {
|
||||
Array a;
|
||||
Error err = _parse_array(a, p_str, index, p_len, line, p_depth + 1, r_err_str);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
value = a;
|
||||
} else if (token.type == TK_IDENTIFIER) {
|
||||
String id = token.value;
|
||||
if (id == "true") {
|
||||
value = true;
|
||||
} else if (id == "false") {
|
||||
value = false;
|
||||
} else if (id == "null") {
|
||||
value = Variant();
|
||||
} else {
|
||||
r_err_str = "Expected 'true','false' or 'null', got '" + id + "'.";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
} else if (token.type == TK_NUMBER) {
|
||||
value = token.value;
|
||||
} else if (token.type == TK_STRING) {
|
||||
value = token.value;
|
||||
} else {
|
||||
r_err_str = "Expected value, got " + String(tk_name[token.type]) + ".";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error JSON::_parse_array(Array &array, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str) {
|
||||
Token token;
|
||||
bool need_comma = false;
|
||||
|
||||
while (index < p_len) {
|
||||
Error err = _get_token(p_str, index, p_len, token, line, r_err_str);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (token.type == TK_BRACKET_CLOSE) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (need_comma) {
|
||||
if (token.type != TK_COMMA) {
|
||||
r_err_str = "Expected ','";
|
||||
return ERR_PARSE_ERROR;
|
||||
} else {
|
||||
need_comma = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Variant v;
|
||||
err = _parse_value(v, token, p_str, index, p_len, line, p_depth, r_err_str);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
array.push_back(v);
|
||||
need_comma = true;
|
||||
}
|
||||
|
||||
r_err_str = "Expected ']'";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
Error JSON::_parse_object(Dictionary &object, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str) {
|
||||
bool at_key = true;
|
||||
String key;
|
||||
Token token;
|
||||
bool need_comma = false;
|
||||
|
||||
while (index < p_len) {
|
||||
if (at_key) {
|
||||
Error err = _get_token(p_str, index, p_len, token, line, r_err_str);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (token.type == TK_CURLY_BRACKET_CLOSE) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (need_comma) {
|
||||
if (token.type != TK_COMMA) {
|
||||
r_err_str = "Expected '}' or ','";
|
||||
return ERR_PARSE_ERROR;
|
||||
} else {
|
||||
need_comma = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (token.type != TK_STRING) {
|
||||
r_err_str = "Expected key";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
key = token.value;
|
||||
err = _get_token(p_str, index, p_len, token, line, r_err_str);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
if (token.type != TK_COLON) {
|
||||
r_err_str = "Expected ':'";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
at_key = false;
|
||||
} else {
|
||||
Error err = _get_token(p_str, index, p_len, token, line, r_err_str);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Variant v;
|
||||
err = _parse_value(v, token, p_str, index, p_len, line, p_depth, r_err_str);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
object[key] = v;
|
||||
need_comma = true;
|
||||
at_key = true;
|
||||
}
|
||||
}
|
||||
|
||||
r_err_str = "Expected '}'";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
void JSON::set_data(const Variant &p_data) {
|
||||
data = p_data;
|
||||
text.clear();
|
||||
}
|
||||
|
||||
Error JSON::_parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line) {
|
||||
const char32_t *str = p_json.ptr();
|
||||
int idx = 0;
|
||||
int len = p_json.length();
|
||||
Token token;
|
||||
r_err_line = 0;
|
||||
String aux_key;
|
||||
|
||||
Error err = _get_token(str, idx, len, token, r_err_line, r_err_str);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = _parse_value(r_ret, token, str, idx, len, r_err_line, 0, r_err_str);
|
||||
|
||||
// Check if EOF is reached
|
||||
// or it's a type of the next token.
|
||||
if (err == OK && idx < len) {
|
||||
err = _get_token(str, idx, len, token, r_err_line, r_err_str);
|
||||
|
||||
if (err || token.type != TK_EOF) {
|
||||
r_err_str = "Expected 'EOF'";
|
||||
// Reset return value to empty `Variant`
|
||||
r_ret = Variant();
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
Error JSON::parse(const String &p_json_string, bool p_keep_text) {
|
||||
Error err = _parse_string(p_json_string, data, err_str, err_line);
|
||||
if (err == Error::OK) {
|
||||
err_line = 0;
|
||||
}
|
||||
if (p_keep_text) {
|
||||
text = p_json_string;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
String JSON::get_parsed_text() const {
|
||||
return text;
|
||||
}
|
||||
|
||||
String JSON::stringify(const Variant &p_var, const String &p_indent, bool p_sort_keys, bool p_full_precision) {
|
||||
Ref<JSON> jason;
|
||||
jason.instantiate();
|
||||
HashSet<const void *> markers;
|
||||
return jason->_stringify(p_var, p_indent, 0, p_sort_keys, markers, p_full_precision);
|
||||
}
|
||||
|
||||
Variant JSON::parse_string(const String &p_json_string) {
|
||||
Ref<JSON> jason;
|
||||
jason.instantiate();
|
||||
Error error = jason->parse(p_json_string);
|
||||
ERR_FAIL_COND_V_MSG(error != Error::OK, Variant(), vformat("Parse JSON failed. Error at line %d: %s", jason->get_error_line(), jason->get_error_message()));
|
||||
return jason->get_data();
|
||||
}
|
||||
|
||||
void JSON::_bind_methods() {
|
||||
ClassDB::bind_static_method("JSON", D_METHOD("stringify", "data", "indent", "sort_keys", "full_precision"), &JSON::stringify, DEFVAL(""), DEFVAL(true), DEFVAL(false));
|
||||
ClassDB::bind_static_method("JSON", D_METHOD("parse_string", "json_string"), &JSON::parse_string);
|
||||
ClassDB::bind_method(D_METHOD("parse", "json_text", "keep_text"), &JSON::parse, DEFVAL(false));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_data"), &JSON::get_data);
|
||||
ClassDB::bind_method(D_METHOD("set_data", "data"), &JSON::set_data);
|
||||
ClassDB::bind_method(D_METHOD("get_parsed_text"), &JSON::get_parsed_text);
|
||||
ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line);
|
||||
ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NIL, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_data", "get_data"); // Ensures that it can be serialized as binary.
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
////////////
|
||||
|
||||
Ref<Resource> ResourceFormatLoaderJSON::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
|
||||
if (r_error) {
|
||||
*r_error = ERR_FILE_CANT_OPEN;
|
||||
}
|
||||
|
||||
if (!FileAccess::exists(p_path)) {
|
||||
*r_error = ERR_FILE_NOT_FOUND;
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
Ref<JSON> json;
|
||||
json.instantiate();
|
||||
|
||||
Error err = json->parse(FileAccess::get_file_as_string(p_path), Engine::get_singleton()->is_editor_hint());
|
||||
if (err != OK) {
|
||||
String err_text = "Error parsing JSON file at '" + p_path + "', on line " + itos(json->get_error_line()) + ": " + json->get_error_message();
|
||||
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
// If running on editor, still allow opening the JSON so the code editor can edit it.
|
||||
WARN_PRINT(err_text);
|
||||
} else {
|
||||
if (r_error) {
|
||||
*r_error = err;
|
||||
}
|
||||
ERR_PRINT(err_text);
|
||||
return Ref<Resource>();
|
||||
}
|
||||
}
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void ResourceFormatLoaderJSON::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("json");
|
||||
}
|
||||
|
||||
bool ResourceFormatLoaderJSON::handles_type(const String &p_type) const {
|
||||
return (p_type == "JSON");
|
||||
}
|
||||
|
||||
String ResourceFormatLoaderJSON::get_resource_type(const String &p_path) const {
|
||||
String el = p_path.get_extension().to_lower();
|
||||
if (el == "json") {
|
||||
return "JSON";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Error ResourceFormatSaverJSON::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
|
||||
Ref<JSON> json = p_resource;
|
||||
ERR_FAIL_COND_V(json.is_null(), ERR_INVALID_PARAMETER);
|
||||
|
||||
String source = json->get_parsed_text().is_empty() ? JSON::stringify(json->get_data(), "\t", false, true) : json->get_parsed_text();
|
||||
|
||||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(err, err, "Cannot save json '" + p_path + "'.");
|
||||
|
||||
file->store_string(source);
|
||||
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
|
||||
return ERR_CANT_CREATE;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void ResourceFormatSaverJSON::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const {
|
||||
Ref<JSON> json = p_resource;
|
||||
if (json.is_valid()) {
|
||||
p_extensions->push_back("json");
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourceFormatSaverJSON::recognize(const Ref<Resource> &p_resource) const {
|
||||
return p_resource->get_class_name() == "JSON"; //only json, not inherited
|
||||
}
|
||||
114
engine/core/io/json.h
Normal file
114
engine/core/io/json.h
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/**************************************************************************/
|
||||
/* json.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef JSON_H
|
||||
#define JSON_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
class JSON : public Resource {
|
||||
GDCLASS(JSON, Resource);
|
||||
|
||||
enum TokenType {
|
||||
TK_CURLY_BRACKET_OPEN,
|
||||
TK_CURLY_BRACKET_CLOSE,
|
||||
TK_BRACKET_OPEN,
|
||||
TK_BRACKET_CLOSE,
|
||||
TK_IDENTIFIER,
|
||||
TK_STRING,
|
||||
TK_NUMBER,
|
||||
TK_COLON,
|
||||
TK_COMMA,
|
||||
TK_EOF,
|
||||
TK_MAX
|
||||
};
|
||||
|
||||
enum Expecting {
|
||||
EXPECT_OBJECT,
|
||||
EXPECT_OBJECT_KEY,
|
||||
EXPECT_COLON,
|
||||
EXPECT_OBJECT_VALUE,
|
||||
};
|
||||
|
||||
struct Token {
|
||||
TokenType type;
|
||||
Variant value;
|
||||
};
|
||||
|
||||
String text;
|
||||
Variant data;
|
||||
String err_str;
|
||||
int err_line = 0;
|
||||
|
||||
static const char *tk_name[];
|
||||
|
||||
static String _make_indent(const String &p_indent, int p_size);
|
||||
static String _stringify(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys, HashSet<const void *> &p_markers, bool p_full_precision = false);
|
||||
static Error _get_token(const char32_t *p_str, int &index, int p_len, Token &r_token, int &line, String &r_err_str);
|
||||
static Error _parse_value(Variant &value, Token &token, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str);
|
||||
static Error _parse_array(Array &array, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str);
|
||||
static Error _parse_object(Dictionary &object, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str);
|
||||
static Error _parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Error parse(const String &p_json_string, bool p_keep_text = false);
|
||||
String get_parsed_text() const;
|
||||
|
||||
static String stringify(const Variant &p_var, const String &p_indent = "", bool p_sort_keys = true, bool p_full_precision = false);
|
||||
static Variant parse_string(const String &p_json_string);
|
||||
|
||||
inline Variant get_data() const { return data; }
|
||||
void set_data(const Variant &p_data);
|
||||
inline int get_error_line() const { return err_line; }
|
||||
inline String get_error_message() const { return err_str; }
|
||||
};
|
||||
|
||||
class ResourceFormatLoaderJSON : public ResourceFormatLoader {
|
||||
public:
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual bool handles_type(const String &p_type) const override;
|
||||
virtual String get_resource_type(const String &p_path) const override;
|
||||
};
|
||||
|
||||
class ResourceFormatSaverJSON : public ResourceFormatSaver {
|
||||
public:
|
||||
virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override;
|
||||
virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override;
|
||||
virtual bool recognize(const Ref<Resource> &p_resource) const override;
|
||||
};
|
||||
|
||||
#endif // JSON_H
|
||||
284
engine/core/io/logger.cpp
Normal file
284
engine/core/io/logger.cpp
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
/**************************************************************************/
|
||||
/* logger.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 "logger.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/core_globals.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/os/time.h"
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
|
||||
#if defined(MINGW_ENABLED) || defined(_MSC_VER)
|
||||
#define sprintf sprintf_s
|
||||
#endif
|
||||
|
||||
bool Logger::should_log(bool p_err) {
|
||||
return (!p_err || CoreGlobals::print_error_enabled) && (p_err || CoreGlobals::print_line_enabled);
|
||||
}
|
||||
|
||||
bool Logger::_flush_stdout_on_print = true;
|
||||
|
||||
void Logger::set_flush_stdout_on_print(bool value) {
|
||||
_flush_stdout_on_print = value;
|
||||
}
|
||||
|
||||
void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) {
|
||||
if (!should_log(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *err_type = "ERROR";
|
||||
switch (p_type) {
|
||||
case ERR_ERROR:
|
||||
err_type = "ERROR";
|
||||
break;
|
||||
case ERR_WARNING:
|
||||
err_type = "WARNING";
|
||||
break;
|
||||
case ERR_SCRIPT:
|
||||
err_type = "SCRIPT ERROR";
|
||||
break;
|
||||
case ERR_SHADER:
|
||||
err_type = "SHADER ERROR";
|
||||
break;
|
||||
default:
|
||||
ERR_PRINT("Unknown error type");
|
||||
break;
|
||||
}
|
||||
|
||||
const char *err_details;
|
||||
if (p_rationale && *p_rationale) {
|
||||
err_details = p_rationale;
|
||||
} else {
|
||||
err_details = p_code;
|
||||
}
|
||||
|
||||
if (p_editor_notify) {
|
||||
logf_error("%s: %s\n", err_type, err_details);
|
||||
} else {
|
||||
logf_error("USER %s: %s\n", err_type, err_details);
|
||||
}
|
||||
logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line);
|
||||
}
|
||||
|
||||
void Logger::logf(const char *p_format, ...) {
|
||||
if (!should_log(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list argp;
|
||||
va_start(argp, p_format);
|
||||
|
||||
logv(p_format, argp, false);
|
||||
|
||||
va_end(argp);
|
||||
}
|
||||
|
||||
void Logger::logf_error(const char *p_format, ...) {
|
||||
if (!should_log(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list argp;
|
||||
va_start(argp, p_format);
|
||||
|
||||
logv(p_format, argp, true);
|
||||
|
||||
va_end(argp);
|
||||
}
|
||||
|
||||
void RotatedFileLogger::clear_old_backups() {
|
||||
int max_backups = max_files - 1; // -1 for the current file
|
||||
|
||||
String basename = base_path.get_file().get_basename();
|
||||
String extension = base_path.get_extension();
|
||||
|
||||
Ref<DirAccess> da = DirAccess::open(base_path.get_base_dir());
|
||||
if (da.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
da->list_dir_begin();
|
||||
String f = da->get_next();
|
||||
HashSet<String> backups;
|
||||
while (!f.is_empty()) {
|
||||
if (!da->current_is_dir() && f.begins_with(basename) && f.get_extension() == extension && f != base_path.get_file()) {
|
||||
backups.insert(f);
|
||||
}
|
||||
f = da->get_next();
|
||||
}
|
||||
da->list_dir_end();
|
||||
|
||||
if (backups.size() > (uint32_t)max_backups) {
|
||||
// since backups are appended with timestamp and Set iterates them in sorted order,
|
||||
// first backups are the oldest
|
||||
int to_delete = backups.size() - max_backups;
|
||||
for (HashSet<String>::Iterator E = backups.begin(); E && to_delete > 0; ++E, --to_delete) {
|
||||
da->remove(*E);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RotatedFileLogger::rotate_file() {
|
||||
file.unref();
|
||||
|
||||
if (FileAccess::exists(base_path)) {
|
||||
if (max_files > 1) {
|
||||
String timestamp = Time::get_singleton()->get_datetime_string_from_system().replace(":", ".");
|
||||
String backup_name = base_path.get_basename() + timestamp;
|
||||
if (!base_path.get_extension().is_empty()) {
|
||||
backup_name += "." + base_path.get_extension();
|
||||
}
|
||||
|
||||
Ref<DirAccess> da = DirAccess::open(base_path.get_base_dir());
|
||||
if (da.is_valid()) {
|
||||
da->copy(base_path, backup_name);
|
||||
}
|
||||
clear_old_backups();
|
||||
}
|
||||
} else {
|
||||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_USERDATA);
|
||||
if (da.is_valid()) {
|
||||
da->make_dir_recursive(base_path.get_base_dir());
|
||||
}
|
||||
}
|
||||
|
||||
file = FileAccess::open(base_path, FileAccess::WRITE);
|
||||
file->detach_from_objectdb(); // Note: This FileAccess instance will exist longer than ObjectDB, therefore can't be registered in ObjectDB.
|
||||
}
|
||||
|
||||
RotatedFileLogger::RotatedFileLogger(const String &p_base_path, int p_max_files) :
|
||||
base_path(p_base_path.simplify_path()),
|
||||
max_files(p_max_files > 0 ? p_max_files : 1) {
|
||||
rotate_file();
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
strip_ansi_regex.instantiate();
|
||||
strip_ansi_regex->detach_from_objectdb(); // Note: This RegEx instance will exist longer than ObjectDB, therefore can't be registered in ObjectDB.
|
||||
strip_ansi_regex->compile("\u001b\\[((?:\\d|;)*)([a-zA-Z])");
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
}
|
||||
|
||||
void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) {
|
||||
if (!should_log(p_err)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.is_valid()) {
|
||||
const int static_buf_size = 512;
|
||||
char static_buf[static_buf_size];
|
||||
char *buf = static_buf;
|
||||
va_list list_copy;
|
||||
va_copy(list_copy, p_list);
|
||||
int len = vsnprintf(buf, static_buf_size, p_format, p_list);
|
||||
if (len >= static_buf_size) {
|
||||
buf = (char *)Memory::alloc_static(len + 1);
|
||||
vsnprintf(buf, len + 1, p_format, list_copy);
|
||||
}
|
||||
va_end(list_copy);
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
// Strip ANSI escape codes (such as those inserted by `print_rich()`)
|
||||
// before writing to file, as text editors cannot display those
|
||||
// correctly.
|
||||
file->store_string(strip_ansi_regex->sub(String::utf8(buf), "", true));
|
||||
#else
|
||||
file->store_buffer((uint8_t *)buf, len);
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
|
||||
if (len >= static_buf_size) {
|
||||
Memory::free_static(buf);
|
||||
}
|
||||
|
||||
if (p_err || _flush_stdout_on_print) {
|
||||
// Don't always flush when printing stdout to avoid performance
|
||||
// issues when `print()` is spammed in release builds.
|
||||
file->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StdLogger::logv(const char *p_format, va_list p_list, bool p_err) {
|
||||
if (!should_log(p_err)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_err) {
|
||||
vfprintf(stderr, p_format, p_list);
|
||||
} else {
|
||||
vprintf(p_format, p_list);
|
||||
if (_flush_stdout_on_print) {
|
||||
// Don't always flush when printing stdout to avoid performance
|
||||
// issues when `print()` is spammed in release builds.
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompositeLogger::CompositeLogger(const Vector<Logger *> &p_loggers) :
|
||||
loggers(p_loggers) {
|
||||
}
|
||||
|
||||
void CompositeLogger::logv(const char *p_format, va_list p_list, bool p_err) {
|
||||
if (!should_log(p_err)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < loggers.size(); ++i) {
|
||||
va_list list_copy;
|
||||
va_copy(list_copy, p_list);
|
||||
loggers[i]->logv(p_format, list_copy, p_err);
|
||||
va_end(list_copy);
|
||||
}
|
||||
}
|
||||
|
||||
void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) {
|
||||
if (!should_log(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < loggers.size(); ++i) {
|
||||
loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type);
|
||||
}
|
||||
}
|
||||
|
||||
void CompositeLogger::add_logger(Logger *p_logger) {
|
||||
loggers.push_back(p_logger);
|
||||
}
|
||||
|
||||
CompositeLogger::~CompositeLogger() {
|
||||
for (int i = 0; i < loggers.size(); ++i) {
|
||||
memdelete(loggers[i]);
|
||||
}
|
||||
}
|
||||
117
engine/core/io/logger.h
Normal file
117
engine/core/io/logger.h
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/**************************************************************************/
|
||||
/* logger.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "modules/modules_enabled.gen.h" // For regex.
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
#include "modules/regex/regex.h"
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
class Logger {
|
||||
protected:
|
||||
bool should_log(bool p_err);
|
||||
|
||||
static bool _flush_stdout_on_print;
|
||||
|
||||
public:
|
||||
enum ErrorType {
|
||||
ERR_ERROR,
|
||||
ERR_WARNING,
|
||||
ERR_SCRIPT,
|
||||
ERR_SHADER
|
||||
};
|
||||
|
||||
static void set_flush_stdout_on_print(bool value);
|
||||
|
||||
virtual void logv(const char *p_format, va_list p_list, bool p_err) _PRINTF_FORMAT_ATTRIBUTE_2_0 = 0;
|
||||
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR);
|
||||
|
||||
void logf(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
|
||||
void logf_error(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
|
||||
|
||||
virtual ~Logger() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes messages to stdout/stderr.
|
||||
*/
|
||||
class StdLogger : public Logger {
|
||||
public:
|
||||
virtual void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0;
|
||||
virtual ~StdLogger() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes messages to the specified file. If the file already exists, creates a copy (backup)
|
||||
* of it with timestamp appended to the file name. Maximum number of backups is configurable.
|
||||
* When maximum is reached, the oldest backups are erased. With the maximum being equal to 1,
|
||||
* it acts as a simple file logger.
|
||||
*/
|
||||
class RotatedFileLogger : public Logger {
|
||||
String base_path;
|
||||
int max_files;
|
||||
|
||||
Ref<FileAccess> file;
|
||||
|
||||
void clear_old_backups();
|
||||
void rotate_file();
|
||||
|
||||
#ifdef MODULE_REGEX_ENABLED
|
||||
Ref<RegEx> strip_ansi_regex;
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
|
||||
public:
|
||||
explicit RotatedFileLogger(const String &p_base_path, int p_max_files = 10);
|
||||
|
||||
virtual void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0;
|
||||
};
|
||||
|
||||
class CompositeLogger : public Logger {
|
||||
Vector<Logger *> loggers;
|
||||
|
||||
public:
|
||||
explicit CompositeLogger(const Vector<Logger *> &p_loggers);
|
||||
|
||||
virtual void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0;
|
||||
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type = ERR_ERROR) override;
|
||||
|
||||
void add_logger(Logger *p_logger);
|
||||
|
||||
virtual ~CompositeLogger();
|
||||
};
|
||||
|
||||
#endif // LOGGER_H
|
||||
2078
engine/core/io/marshalls.cpp
Normal file
2078
engine/core/io/marshalls.cpp
Normal file
File diff suppressed because it is too large
Load diff
220
engine/core/io/marshalls.h
Normal file
220
engine/core/io/marshalls.h
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
/**************************************************************************/
|
||||
/* marshalls.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef MARSHALLS_H
|
||||
#define MARSHALLS_H
|
||||
|
||||
#include "core/math/math_defs.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
// uintr_t is only for pairing with real_t, and we only need it in here.
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
typedef uint64_t uintr_t;
|
||||
#else
|
||||
typedef uint32_t uintr_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Miscellaneous helpers for marshaling data types, and encoding
|
||||
* in an endian independent way
|
||||
*/
|
||||
|
||||
union MarshallFloat {
|
||||
uint32_t i; ///< int
|
||||
float f; ///< float
|
||||
};
|
||||
|
||||
union MarshallDouble {
|
||||
uint64_t l; ///< long long
|
||||
double d; ///< double
|
||||
};
|
||||
|
||||
// Behaves like one of the above, depending on compilation setting.
|
||||
union MarshallReal {
|
||||
uintr_t i;
|
||||
real_t r;
|
||||
};
|
||||
|
||||
static inline unsigned int encode_uint16(uint16_t p_uint, uint8_t *p_arr) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
*p_arr = p_uint & 0xFF;
|
||||
p_arr++;
|
||||
p_uint >>= 8;
|
||||
}
|
||||
|
||||
return sizeof(uint16_t);
|
||||
}
|
||||
|
||||
static inline unsigned int encode_uint32(uint32_t p_uint, uint8_t *p_arr) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
*p_arr = p_uint & 0xFF;
|
||||
p_arr++;
|
||||
p_uint >>= 8;
|
||||
}
|
||||
|
||||
return sizeof(uint32_t);
|
||||
}
|
||||
|
||||
static inline unsigned int encode_float(float p_float, uint8_t *p_arr) {
|
||||
MarshallFloat mf;
|
||||
mf.f = p_float;
|
||||
encode_uint32(mf.i, p_arr);
|
||||
|
||||
return sizeof(uint32_t);
|
||||
}
|
||||
|
||||
static inline unsigned int encode_uint64(uint64_t p_uint, uint8_t *p_arr) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
*p_arr = p_uint & 0xFF;
|
||||
p_arr++;
|
||||
p_uint >>= 8;
|
||||
}
|
||||
|
||||
return sizeof(uint64_t);
|
||||
}
|
||||
|
||||
static inline unsigned int encode_double(double p_double, uint8_t *p_arr) {
|
||||
MarshallDouble md;
|
||||
md.d = p_double;
|
||||
encode_uint64(md.l, p_arr);
|
||||
|
||||
return sizeof(uint64_t);
|
||||
}
|
||||
|
||||
static inline unsigned int encode_uintr(uintr_t p_uint, uint8_t *p_arr) {
|
||||
for (size_t i = 0; i < sizeof(uintr_t); i++) {
|
||||
*p_arr = p_uint & 0xFF;
|
||||
p_arr++;
|
||||
p_uint >>= 8;
|
||||
}
|
||||
|
||||
return sizeof(uintr_t);
|
||||
}
|
||||
|
||||
static inline unsigned int encode_real(real_t p_real, uint8_t *p_arr) {
|
||||
MarshallReal mr;
|
||||
mr.r = p_real;
|
||||
encode_uintr(mr.i, p_arr);
|
||||
|
||||
return sizeof(uintr_t);
|
||||
}
|
||||
|
||||
static inline int encode_cstring(const char *p_string, uint8_t *p_data) {
|
||||
int len = 0;
|
||||
|
||||
while (*p_string) {
|
||||
if (p_data) {
|
||||
*p_data = (uint8_t)*p_string;
|
||||
p_data++;
|
||||
}
|
||||
p_string++;
|
||||
len++;
|
||||
}
|
||||
|
||||
if (p_data) {
|
||||
*p_data = 0;
|
||||
}
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
static inline uint16_t decode_uint16(const uint8_t *p_arr) {
|
||||
uint16_t u = 0;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
uint16_t b = *p_arr;
|
||||
b <<= (i * 8);
|
||||
u |= b;
|
||||
p_arr++;
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
static inline uint32_t decode_uint32(const uint8_t *p_arr) {
|
||||
uint32_t u = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint32_t b = *p_arr;
|
||||
b <<= (i * 8);
|
||||
u |= b;
|
||||
p_arr++;
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
static inline float decode_float(const uint8_t *p_arr) {
|
||||
MarshallFloat mf;
|
||||
mf.i = decode_uint32(p_arr);
|
||||
return mf.f;
|
||||
}
|
||||
|
||||
static inline uint64_t decode_uint64(const uint8_t *p_arr) {
|
||||
uint64_t u = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uint64_t b = (*p_arr) & 0xFF;
|
||||
b <<= (i * 8);
|
||||
u |= b;
|
||||
p_arr++;
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
static inline double decode_double(const uint8_t *p_arr) {
|
||||
MarshallDouble md;
|
||||
md.l = decode_uint64(p_arr);
|
||||
return md.d;
|
||||
}
|
||||
|
||||
class EncodedObjectAsID : public RefCounted {
|
||||
GDCLASS(EncodedObjectAsID, RefCounted);
|
||||
|
||||
ObjectID id;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_object_id(ObjectID p_id);
|
||||
ObjectID get_object_id() const;
|
||||
|
||||
EncodedObjectAsID() {}
|
||||
};
|
||||
|
||||
Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len = nullptr, bool p_allow_objects = false, int p_depth = 0);
|
||||
Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects = false, int p_depth = 0);
|
||||
|
||||
Vector<float> vector3_to_float32_array(const Vector3 *vecs, size_t count);
|
||||
|
||||
#endif // MARSHALLS_H
|
||||
90
engine/core/io/missing_resource.cpp
Normal file
90
engine/core/io/missing_resource.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**************************************************************************/
|
||||
/* missing_resource.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 "missing_resource.h"
|
||||
|
||||
bool MissingResource::_set(const StringName &p_name, const Variant &p_value) {
|
||||
if (is_recording_properties()) {
|
||||
properties.insert(p_name, p_value);
|
||||
return true; //always valid to set (add)
|
||||
} else {
|
||||
if (!properties.has(p_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
properties[p_name] = p_value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool MissingResource::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
if (!properties.has(p_name)) {
|
||||
return false;
|
||||
}
|
||||
r_ret = properties[p_name];
|
||||
return true;
|
||||
}
|
||||
|
||||
void MissingResource::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (const KeyValue<StringName, Variant> &E : properties) {
|
||||
p_list->push_back(PropertyInfo(E.value.get_type(), E.key));
|
||||
}
|
||||
}
|
||||
|
||||
void MissingResource::set_original_class(const String &p_class) {
|
||||
original_class = p_class;
|
||||
}
|
||||
|
||||
String MissingResource::get_original_class() const {
|
||||
return original_class;
|
||||
}
|
||||
|
||||
void MissingResource::set_recording_properties(bool p_enable) {
|
||||
recording_properties = p_enable;
|
||||
}
|
||||
|
||||
bool MissingResource::is_recording_properties() const {
|
||||
return recording_properties;
|
||||
}
|
||||
|
||||
void MissingResource::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_original_class", "name"), &MissingResource::set_original_class);
|
||||
ClassDB::bind_method(D_METHOD("get_original_class"), &MissingResource::get_original_class);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_recording_properties", "enable"), &MissingResource::set_recording_properties);
|
||||
ClassDB::bind_method(D_METHOD("is_recording_properties"), &MissingResource::is_recording_properties);
|
||||
|
||||
// Expose, but not save.
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_class", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_original_class", "get_original_class");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "recording_properties", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_recording_properties", "is_recording_properties");
|
||||
}
|
||||
|
||||
MissingResource::MissingResource() {
|
||||
}
|
||||
63
engine/core/io/missing_resource.h
Normal file
63
engine/core/io/missing_resource.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**************************************************************************/
|
||||
/* missing_resource.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef MISSING_RESOURCE_H
|
||||
#define MISSING_RESOURCE_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
|
||||
#define META_PROPERTY_MISSING_RESOURCES "metadata/_missing_resources"
|
||||
#define META_MISSING_RESOURCES "_missing_resources"
|
||||
|
||||
class MissingResource : public Resource {
|
||||
GDCLASS(MissingResource, Resource)
|
||||
HashMap<StringName, Variant> properties;
|
||||
|
||||
String original_class;
|
||||
bool recording_properties = false;
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_original_class(const String &p_class);
|
||||
String get_original_class() const;
|
||||
|
||||
void set_recording_properties(bool p_enable);
|
||||
bool is_recording_properties() const;
|
||||
|
||||
MissingResource();
|
||||
};
|
||||
|
||||
#endif // MISSING_RESOURCE_H
|
||||
42
engine/core/io/net_socket.cpp
Normal file
42
engine/core/io/net_socket.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**************************************************************************/
|
||||
/* net_socket.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 "net_socket.h"
|
||||
|
||||
NetSocket *(*NetSocket::_create)() = nullptr;
|
||||
|
||||
NetSocket *NetSocket::create() {
|
||||
if (_create) {
|
||||
return _create();
|
||||
}
|
||||
|
||||
ERR_PRINT("Unable to create network socket, platform not supported");
|
||||
return nullptr;
|
||||
}
|
||||
81
engine/core/io/net_socket.h
Normal file
81
engine/core/io/net_socket.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**************************************************************************/
|
||||
/* net_socket.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef NET_SOCKET_H
|
||||
#define NET_SOCKET_H
|
||||
|
||||
#include "core/io/ip.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class NetSocket : public RefCounted {
|
||||
protected:
|
||||
static NetSocket *(*_create)();
|
||||
|
||||
public:
|
||||
static NetSocket *create();
|
||||
|
||||
enum PollType {
|
||||
POLL_TYPE_IN,
|
||||
POLL_TYPE_OUT,
|
||||
POLL_TYPE_IN_OUT
|
||||
};
|
||||
|
||||
enum Type {
|
||||
TYPE_NONE,
|
||||
TYPE_TCP,
|
||||
TYPE_UDP,
|
||||
};
|
||||
|
||||
virtual Error open(Type p_type, IP::Type &ip_type) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual Error bind(IPAddress p_addr, uint16_t p_port) = 0;
|
||||
virtual Error listen(int p_max_pending) = 0;
|
||||
virtual Error connect_to_host(IPAddress p_addr, uint16_t p_port) = 0;
|
||||
virtual Error poll(PollType p_type, int timeout) const = 0;
|
||||
virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) = 0;
|
||||
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) = 0;
|
||||
virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) = 0;
|
||||
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) = 0;
|
||||
virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) = 0;
|
||||
|
||||
virtual bool is_open() const = 0;
|
||||
virtual int get_available_bytes() const = 0;
|
||||
virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const = 0;
|
||||
|
||||
virtual Error set_broadcasting_enabled(bool p_enabled) = 0; // Returns OK if the socket option has been set successfully.
|
||||
virtual void set_blocking_enabled(bool p_enabled) = 0;
|
||||
virtual void set_ipv6_only_enabled(bool p_enabled) = 0;
|
||||
virtual void set_tcp_no_delay_enabled(bool p_enabled) = 0;
|
||||
virtual void set_reuse_address_enabled(bool p_enabled) = 0;
|
||||
virtual Error join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) = 0;
|
||||
virtual Error leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) = 0;
|
||||
};
|
||||
|
||||
#endif // NET_SOCKET_H
|
||||
403
engine/core/io/packed_data_container.cpp
Normal file
403
engine/core/io/packed_data_container.cpp
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
/**************************************************************************/
|
||||
/* packed_data_container.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 "packed_data_container.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
|
||||
Variant PackedDataContainer::getvar(const Variant &p_key, bool *r_valid) const {
|
||||
bool err = false;
|
||||
Variant ret = _key_at_ofs(0, p_key, err);
|
||||
if (r_valid) {
|
||||
*r_valid = !err;
|
||||
}
|
||||
if (err) {
|
||||
return Object::getvar(p_key, r_valid);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int PackedDataContainer::size() const {
|
||||
return _size(0);
|
||||
}
|
||||
|
||||
Variant PackedDataContainer::_iter_init_ofs(const Array &p_iter, uint32_t p_offset) {
|
||||
Array ref = p_iter;
|
||||
uint32_t size = _size(p_offset);
|
||||
if (size == 0 || ref.size() != 1) {
|
||||
return false;
|
||||
} else {
|
||||
ref[0] = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Variant PackedDataContainer::_iter_next_ofs(const Array &p_iter, uint32_t p_offset) {
|
||||
Array ref = p_iter;
|
||||
int size = _size(p_offset);
|
||||
if (ref.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
int pos = ref[0];
|
||||
if (pos < 0 || pos >= size) {
|
||||
return false;
|
||||
}
|
||||
pos += 1;
|
||||
ref[0] = pos;
|
||||
return pos != size;
|
||||
}
|
||||
|
||||
Variant PackedDataContainer::_iter_get_ofs(const Variant &p_iter, uint32_t p_offset) {
|
||||
int size = _size(p_offset);
|
||||
int pos = p_iter;
|
||||
if (pos < 0 || pos >= size) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
const uint8_t *rd = data.ptr();
|
||||
const uint8_t *r = &rd[p_offset];
|
||||
uint32_t type = decode_uint32(r);
|
||||
|
||||
bool err = false;
|
||||
if (type == TYPE_ARRAY) {
|
||||
uint32_t vpos = decode_uint32(rd + p_offset + 8 + pos * 4);
|
||||
return _get_at_ofs(vpos, rd, err);
|
||||
|
||||
} else if (type == TYPE_DICT) {
|
||||
uint32_t vpos = decode_uint32(rd + p_offset + 8 + pos * 12 + 4);
|
||||
return _get_at_ofs(vpos, rd, err);
|
||||
} else {
|
||||
ERR_FAIL_V(Variant());
|
||||
}
|
||||
}
|
||||
|
||||
Variant PackedDataContainer::_get_at_ofs(uint32_t p_ofs, const uint8_t *p_buf, bool &err) const {
|
||||
ERR_FAIL_COND_V(p_ofs + 4 > (uint32_t)data.size(), Variant());
|
||||
uint32_t type = decode_uint32(p_buf + p_ofs);
|
||||
|
||||
if (type == TYPE_ARRAY || type == TYPE_DICT) {
|
||||
Ref<PackedDataContainerRef> pdcr = memnew(PackedDataContainerRef);
|
||||
Ref<PackedDataContainer> pdc = Ref<PackedDataContainer>(const_cast<PackedDataContainer *>(this));
|
||||
|
||||
pdcr->from = pdc;
|
||||
pdcr->offset = p_ofs;
|
||||
return pdcr;
|
||||
} else {
|
||||
Variant v;
|
||||
Error rerr = decode_variant(v, p_buf + p_ofs, datalen - p_ofs, nullptr, false);
|
||||
|
||||
if (rerr != OK) {
|
||||
err = true;
|
||||
ERR_FAIL_COND_V_MSG(err != OK, Variant(), "Error when trying to decode Variant.");
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PackedDataContainer::_type_at_ofs(uint32_t p_ofs) const {
|
||||
ERR_FAIL_COND_V(p_ofs + 4 > (uint32_t)data.size(), 0);
|
||||
const uint8_t *rd = data.ptr();
|
||||
ERR_FAIL_NULL_V(rd, 0);
|
||||
const uint8_t *r = &rd[p_ofs];
|
||||
uint32_t type = decode_uint32(r);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
int PackedDataContainer::_size(uint32_t p_ofs) const {
|
||||
ERR_FAIL_COND_V(p_ofs + 4 > (uint32_t)data.size(), 0);
|
||||
const uint8_t *rd = data.ptr();
|
||||
ERR_FAIL_NULL_V(rd, 0);
|
||||
const uint8_t *r = &rd[p_ofs];
|
||||
uint32_t type = decode_uint32(r);
|
||||
|
||||
if (type == TYPE_ARRAY) {
|
||||
uint32_t len = decode_uint32(r + 4);
|
||||
return len;
|
||||
|
||||
} else if (type == TYPE_DICT) {
|
||||
uint32_t len = decode_uint32(r + 4);
|
||||
return len;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Variant PackedDataContainer::_key_at_ofs(uint32_t p_ofs, const Variant &p_key, bool &err) const {
|
||||
ERR_FAIL_COND_V(p_ofs + 4 > (uint32_t)data.size(), Variant());
|
||||
const uint8_t *rd = data.ptr();
|
||||
if (!rd) {
|
||||
err = true;
|
||||
ERR_FAIL_NULL_V(rd, Variant());
|
||||
}
|
||||
const uint8_t *r = &rd[p_ofs];
|
||||
uint32_t type = decode_uint32(r);
|
||||
|
||||
if (type == TYPE_ARRAY) {
|
||||
if (p_key.is_num()) {
|
||||
int idx = p_key;
|
||||
int len = decode_uint32(r + 4);
|
||||
if (idx < 0 || idx >= len) {
|
||||
err = true;
|
||||
return Variant();
|
||||
}
|
||||
uint32_t ofs = decode_uint32(r + 8 + 4 * idx);
|
||||
return _get_at_ofs(ofs, rd, err);
|
||||
|
||||
} else {
|
||||
err = true;
|
||||
return Variant();
|
||||
}
|
||||
|
||||
} else if (type == TYPE_DICT) {
|
||||
uint32_t hash = p_key.hash();
|
||||
uint32_t len = decode_uint32(r + 4);
|
||||
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
uint32_t khash = decode_uint32(r + 8 + i * 12 + 0);
|
||||
if (khash == hash) {
|
||||
Variant key = _get_at_ofs(decode_uint32(r + 8 + i * 12 + 4), rd, err);
|
||||
if (err) {
|
||||
return Variant();
|
||||
}
|
||||
if (key == p_key) {
|
||||
//key matches, return value
|
||||
return _get_at_ofs(decode_uint32(r + 8 + i * 12 + 8), rd, err);
|
||||
}
|
||||
found = true;
|
||||
} else {
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = true;
|
||||
return Variant();
|
||||
|
||||
} else {
|
||||
err = true;
|
||||
return Variant();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PackedDataContainer::_pack(const Variant &p_data, Vector<uint8_t> &tmpdata, HashMap<String, uint32_t> &string_cache) {
|
||||
switch (p_data.get_type()) {
|
||||
case Variant::STRING: {
|
||||
String s = p_data;
|
||||
if (string_cache.has(s)) {
|
||||
return string_cache[s];
|
||||
}
|
||||
|
||||
string_cache[s] = tmpdata.size();
|
||||
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Variant::NIL:
|
||||
case Variant::BOOL:
|
||||
case Variant::INT:
|
||||
case Variant::FLOAT:
|
||||
case Variant::VECTOR2:
|
||||
case Variant::RECT2:
|
||||
case Variant::VECTOR3:
|
||||
case Variant::TRANSFORM2D:
|
||||
case Variant::PLANE:
|
||||
case Variant::QUATERNION:
|
||||
case Variant::AABB:
|
||||
case Variant::BASIS:
|
||||
case Variant::TRANSFORM3D:
|
||||
case Variant::PACKED_BYTE_ARRAY:
|
||||
case Variant::PACKED_INT32_ARRAY:
|
||||
case Variant::PACKED_INT64_ARRAY:
|
||||
case Variant::PACKED_FLOAT32_ARRAY:
|
||||
case Variant::PACKED_FLOAT64_ARRAY:
|
||||
case Variant::PACKED_STRING_ARRAY:
|
||||
case Variant::PACKED_VECTOR2_ARRAY:
|
||||
case Variant::PACKED_VECTOR3_ARRAY:
|
||||
case Variant::PACKED_COLOR_ARRAY:
|
||||
case Variant::PACKED_VECTOR4_ARRAY:
|
||||
case Variant::STRING_NAME:
|
||||
case Variant::NODE_PATH: {
|
||||
uint32_t pos = tmpdata.size();
|
||||
int len;
|
||||
encode_variant(p_data, nullptr, len, false);
|
||||
tmpdata.resize(tmpdata.size() + len);
|
||||
encode_variant(p_data, &tmpdata.write[pos], len, false);
|
||||
return pos;
|
||||
|
||||
} break;
|
||||
// misc types
|
||||
case Variant::RID:
|
||||
case Variant::OBJECT: {
|
||||
return _pack(Variant(), tmpdata, string_cache);
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
Dictionary d = p_data;
|
||||
//size is known, use sort
|
||||
uint32_t pos = tmpdata.size();
|
||||
int len = d.size();
|
||||
tmpdata.resize(tmpdata.size() + len * 12 + 8);
|
||||
encode_uint32(TYPE_DICT, &tmpdata.write[pos + 0]);
|
||||
encode_uint32(len, &tmpdata.write[pos + 4]);
|
||||
|
||||
List<Variant> keys;
|
||||
d.get_key_list(&keys);
|
||||
List<DictKey> sortk;
|
||||
|
||||
for (const Variant &key : keys) {
|
||||
DictKey dk;
|
||||
dk.hash = key.hash();
|
||||
dk.key = key;
|
||||
sortk.push_back(dk);
|
||||
}
|
||||
|
||||
sortk.sort();
|
||||
|
||||
int idx = 0;
|
||||
for (const DictKey &E : sortk) {
|
||||
encode_uint32(E.hash, &tmpdata.write[pos + 8 + idx * 12 + 0]);
|
||||
uint32_t ofs = _pack(E.key, tmpdata, string_cache);
|
||||
encode_uint32(ofs, &tmpdata.write[pos + 8 + idx * 12 + 4]);
|
||||
ofs = _pack(d[E.key], tmpdata, string_cache);
|
||||
encode_uint32(ofs, &tmpdata.write[pos + 8 + idx * 12 + 8]);
|
||||
idx++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
|
||||
} break;
|
||||
case Variant::ARRAY: {
|
||||
Array a = p_data;
|
||||
//size is known, use sort
|
||||
uint32_t pos = tmpdata.size();
|
||||
int len = a.size();
|
||||
tmpdata.resize(tmpdata.size() + len * 4 + 8);
|
||||
encode_uint32(TYPE_ARRAY, &tmpdata.write[pos + 0]);
|
||||
encode_uint32(len, &tmpdata.write[pos + 4]);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
uint32_t ofs = _pack(a[i], tmpdata, string_cache);
|
||||
encode_uint32(ofs, &tmpdata.write[pos + 8 + i * 4]);
|
||||
}
|
||||
|
||||
return pos;
|
||||
|
||||
} break;
|
||||
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error PackedDataContainer::pack(const Variant &p_data) {
|
||||
ERR_FAIL_COND_V_MSG(p_data.get_type() != Variant::ARRAY && p_data.get_type() != Variant::DICTIONARY, ERR_INVALID_DATA, "PackedDataContainer can pack only Array and Dictionary type.");
|
||||
|
||||
Vector<uint8_t> tmpdata;
|
||||
HashMap<String, uint32_t> string_cache;
|
||||
_pack(p_data, tmpdata, string_cache);
|
||||
datalen = tmpdata.size();
|
||||
data.resize(tmpdata.size());
|
||||
uint8_t *w = data.ptrw();
|
||||
memcpy(w, tmpdata.ptr(), tmpdata.size());
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void PackedDataContainer::_set_data(const Vector<uint8_t> &p_data) {
|
||||
data = p_data;
|
||||
datalen = data.size();
|
||||
}
|
||||
|
||||
Vector<uint8_t> PackedDataContainer::_get_data() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
Variant PackedDataContainer::_iter_init(const Array &p_iter) {
|
||||
return _iter_init_ofs(p_iter, 0);
|
||||
}
|
||||
|
||||
Variant PackedDataContainer::_iter_next(const Array &p_iter) {
|
||||
return _iter_next_ofs(p_iter, 0);
|
||||
}
|
||||
|
||||
Variant PackedDataContainer::_iter_get(const Variant &p_iter) {
|
||||
return _iter_get_ofs(p_iter, 0);
|
||||
}
|
||||
|
||||
void PackedDataContainer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_set_data", "data"), &PackedDataContainer::_set_data);
|
||||
ClassDB::bind_method(D_METHOD("_get_data"), &PackedDataContainer::_get_data);
|
||||
ClassDB::bind_method(D_METHOD("_iter_init"), &PackedDataContainer::_iter_init);
|
||||
ClassDB::bind_method(D_METHOD("_iter_get"), &PackedDataContainer::_iter_get);
|
||||
ClassDB::bind_method(D_METHOD("_iter_next"), &PackedDataContainer::_iter_next);
|
||||
ClassDB::bind_method(D_METHOD("pack", "value"), &PackedDataContainer::pack);
|
||||
ClassDB::bind_method(D_METHOD("size"), &PackedDataContainer::size);
|
||||
|
||||
BIND_METHOD_ERR_RETURN_DOC("pack", ERR_INVALID_DATA);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "__data__", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
|
||||
}
|
||||
|
||||
//////////////////
|
||||
|
||||
Variant PackedDataContainerRef::_iter_init(const Array &p_iter) {
|
||||
return from->_iter_init_ofs(p_iter, offset);
|
||||
}
|
||||
|
||||
Variant PackedDataContainerRef::_iter_next(const Array &p_iter) {
|
||||
return from->_iter_next_ofs(p_iter, offset);
|
||||
}
|
||||
|
||||
Variant PackedDataContainerRef::_iter_get(const Variant &p_iter) {
|
||||
return from->_iter_get_ofs(p_iter, offset);
|
||||
}
|
||||
|
||||
void PackedDataContainerRef::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("size"), &PackedDataContainerRef::size);
|
||||
ClassDB::bind_method(D_METHOD("_iter_init"), &PackedDataContainerRef::_iter_init);
|
||||
ClassDB::bind_method(D_METHOD("_iter_get"), &PackedDataContainerRef::_iter_get);
|
||||
ClassDB::bind_method(D_METHOD("_iter_next"), &PackedDataContainerRef::_iter_next);
|
||||
}
|
||||
|
||||
Variant PackedDataContainerRef::getvar(const Variant &p_key, bool *r_valid) const {
|
||||
bool err = false;
|
||||
Variant ret = from->_key_at_ofs(offset, p_key, err);
|
||||
if (r_valid) {
|
||||
*r_valid = !err;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int PackedDataContainerRef::size() const {
|
||||
return from->_size(offset);
|
||||
}
|
||||
104
engine/core/io/packed_data_container.h
Normal file
104
engine/core/io/packed_data_container.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**************************************************************************/
|
||||
/* packed_data_container.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef PACKED_DATA_CONTAINER_H
|
||||
#define PACKED_DATA_CONTAINER_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
|
||||
class PackedDataContainer : public Resource {
|
||||
GDCLASS(PackedDataContainer, Resource);
|
||||
|
||||
enum {
|
||||
TYPE_DICT = 0xFFFFFFFF,
|
||||
TYPE_ARRAY = 0xFFFFFFFE,
|
||||
};
|
||||
|
||||
struct DictKey {
|
||||
uint32_t hash;
|
||||
Variant key;
|
||||
bool operator<(const DictKey &p_key) const { return hash < p_key.hash; }
|
||||
};
|
||||
|
||||
Vector<uint8_t> data;
|
||||
int datalen = 0;
|
||||
|
||||
uint32_t _pack(const Variant &p_data, Vector<uint8_t> &tmpdata, HashMap<String, uint32_t> &string_cache);
|
||||
|
||||
Variant _iter_init_ofs(const Array &p_iter, uint32_t p_offset);
|
||||
Variant _iter_next_ofs(const Array &p_iter, uint32_t p_offset);
|
||||
Variant _iter_get_ofs(const Variant &p_iter, uint32_t p_offset);
|
||||
|
||||
Variant _iter_init(const Array &p_iter);
|
||||
Variant _iter_next(const Array &p_iter);
|
||||
Variant _iter_get(const Variant &p_iter);
|
||||
|
||||
friend class PackedDataContainerRef;
|
||||
Variant _key_at_ofs(uint32_t p_ofs, const Variant &p_key, bool &err) const;
|
||||
Variant _get_at_ofs(uint32_t p_ofs, const uint8_t *p_buf, bool &err) const;
|
||||
uint32_t _type_at_ofs(uint32_t p_ofs) const;
|
||||
int _size(uint32_t p_ofs) const;
|
||||
|
||||
protected:
|
||||
void _set_data(const Vector<uint8_t> &p_data);
|
||||
Vector<uint8_t> _get_data() const;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override;
|
||||
Error pack(const Variant &p_data);
|
||||
|
||||
int size() const;
|
||||
|
||||
PackedDataContainer() {}
|
||||
};
|
||||
|
||||
class PackedDataContainerRef : public RefCounted {
|
||||
GDCLASS(PackedDataContainerRef, RefCounted);
|
||||
|
||||
friend class PackedDataContainer;
|
||||
uint32_t offset = 0;
|
||||
Ref<PackedDataContainer> from;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Variant _iter_init(const Array &p_iter);
|
||||
Variant _iter_next(const Array &p_iter);
|
||||
Variant _iter_get(const Variant &p_iter);
|
||||
|
||||
int size() const;
|
||||
virtual Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override;
|
||||
|
||||
PackedDataContainerRef() {}
|
||||
};
|
||||
|
||||
#endif // PACKED_DATA_CONTAINER_H
|
||||
326
engine/core/io/packet_peer.cpp
Normal file
326
engine/core/io/packet_peer.cpp
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
/**************************************************************************/
|
||||
/* packet_peer.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 "packet_peer.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/marshalls.h"
|
||||
|
||||
/* helpers / binders */
|
||||
|
||||
void PacketPeer::set_encode_buffer_max_size(int p_max_size) {
|
||||
ERR_FAIL_COND_MSG(p_max_size < 1024, "Max encode buffer must be at least 1024 bytes");
|
||||
ERR_FAIL_COND_MSG(p_max_size > 256 * 1024 * 1024, "Max encode buffer cannot exceed 256 MiB");
|
||||
encode_buffer_max_size = next_power_of_2(p_max_size);
|
||||
encode_buffer.clear();
|
||||
}
|
||||
|
||||
int PacketPeer::get_encode_buffer_max_size() const {
|
||||
return encode_buffer_max_size;
|
||||
}
|
||||
|
||||
Error PacketPeer::get_packet_buffer(Vector<uint8_t> &r_buffer) {
|
||||
const uint8_t *buffer;
|
||||
int buffer_size;
|
||||
Error err = get_packet(&buffer, buffer_size);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
r_buffer.resize(buffer_size);
|
||||
if (buffer_size == 0) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
uint8_t *w = r_buffer.ptrw();
|
||||
for (int i = 0; i < buffer_size; i++) {
|
||||
w[i] = buffer[i];
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error PacketPeer::put_packet_buffer(const Vector<uint8_t> &p_buffer) {
|
||||
int len = p_buffer.size();
|
||||
if (len == 0) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
const uint8_t *r = p_buffer.ptr();
|
||||
return put_packet(&r[0], len);
|
||||
}
|
||||
|
||||
Error PacketPeer::get_var(Variant &r_variant, bool p_allow_objects) {
|
||||
const uint8_t *buffer;
|
||||
int buffer_size;
|
||||
Error err = get_packet(&buffer, buffer_size);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return decode_variant(r_variant, buffer, buffer_size, nullptr, p_allow_objects);
|
||||
}
|
||||
|
||||
Error PacketPeer::put_var(const Variant &p_packet, bool p_full_objects) {
|
||||
int len;
|
||||
Error err = encode_variant(p_packet, nullptr, len, p_full_objects); // compute len first
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(len > encode_buffer_max_size, ERR_OUT_OF_MEMORY, "Failed to encode variant, encode size is bigger then encode_buffer_max_size. Consider raising it via 'set_encode_buffer_max_size'.");
|
||||
|
||||
if (unlikely(encode_buffer.size() < len)) {
|
||||
encode_buffer.resize(0); // Avoid realloc
|
||||
encode_buffer.resize(next_power_of_2(len));
|
||||
}
|
||||
|
||||
uint8_t *w = encode_buffer.ptrw();
|
||||
err = encode_variant(p_packet, w, len, p_full_objects);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Error when trying to encode Variant.");
|
||||
|
||||
return put_packet(w, len);
|
||||
}
|
||||
|
||||
Variant PacketPeer::_bnd_get_var(bool p_allow_objects) {
|
||||
Variant var;
|
||||
Error err = get_var(var, p_allow_objects);
|
||||
|
||||
ERR_FAIL_COND_V(err != OK, Variant());
|
||||
return var;
|
||||
}
|
||||
|
||||
Error PacketPeer::_put_packet(const Vector<uint8_t> &p_buffer) {
|
||||
return put_packet_buffer(p_buffer);
|
||||
}
|
||||
|
||||
Vector<uint8_t> PacketPeer::_get_packet() {
|
||||
Vector<uint8_t> raw;
|
||||
last_get_error = get_packet_buffer(raw);
|
||||
return raw;
|
||||
}
|
||||
|
||||
Error PacketPeer::_get_packet_error() const {
|
||||
return last_get_error;
|
||||
}
|
||||
|
||||
void PacketPeer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_var", "allow_objects"), &PacketPeer::_bnd_get_var, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("put_var", "var", "full_objects"), &PacketPeer::put_var, DEFVAL(false));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_packet"), &PacketPeer::_get_packet);
|
||||
ClassDB::bind_method(D_METHOD("put_packet", "buffer"), &PacketPeer::_put_packet);
|
||||
ClassDB::bind_method(D_METHOD("get_packet_error"), &PacketPeer::_get_packet_error);
|
||||
ClassDB::bind_method(D_METHOD("get_available_packet_count"), &PacketPeer::get_available_packet_count);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_encode_buffer_max_size"), &PacketPeer::get_encode_buffer_max_size);
|
||||
ClassDB::bind_method(D_METHOD("set_encode_buffer_max_size", "max_size"), &PacketPeer::set_encode_buffer_max_size);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "encode_buffer_max_size"), "set_encode_buffer_max_size", "get_encode_buffer_max_size");
|
||||
}
|
||||
|
||||
/***************/
|
||||
|
||||
Error PacketPeerExtension::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
|
||||
Error err;
|
||||
if (GDVIRTUAL_CALL(_get_packet, r_buffer, &r_buffer_size, err)) {
|
||||
return err;
|
||||
}
|
||||
WARN_PRINT_ONCE("PacketPeerExtension::_get_packet_native is unimplemented!");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
Error PacketPeerExtension::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
|
||||
Error err;
|
||||
if (GDVIRTUAL_CALL(_put_packet, p_buffer, p_buffer_size, err)) {
|
||||
return err;
|
||||
}
|
||||
WARN_PRINT_ONCE("PacketPeerExtension::_put_packet_native is unimplemented!");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
void PacketPeerExtension::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_get_packet, "r_buffer", "r_buffer_size");
|
||||
GDVIRTUAL_BIND(_put_packet, "p_buffer", "p_buffer_size");
|
||||
GDVIRTUAL_BIND(_get_available_packet_count);
|
||||
GDVIRTUAL_BIND(_get_max_packet_size);
|
||||
}
|
||||
|
||||
/***************/
|
||||
|
||||
void PacketPeerStream::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_stream_peer", "peer"), &PacketPeerStream::set_stream_peer);
|
||||
ClassDB::bind_method(D_METHOD("get_stream_peer"), &PacketPeerStream::get_stream_peer);
|
||||
ClassDB::bind_method(D_METHOD("set_input_buffer_max_size", "max_size_bytes"), &PacketPeerStream::set_input_buffer_max_size);
|
||||
ClassDB::bind_method(D_METHOD("set_output_buffer_max_size", "max_size_bytes"), &PacketPeerStream::set_output_buffer_max_size);
|
||||
ClassDB::bind_method(D_METHOD("get_input_buffer_max_size"), &PacketPeerStream::get_input_buffer_max_size);
|
||||
ClassDB::bind_method(D_METHOD("get_output_buffer_max_size"), &PacketPeerStream::get_output_buffer_max_size);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "input_buffer_max_size"), "set_input_buffer_max_size", "get_input_buffer_max_size");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "output_buffer_max_size"), "set_output_buffer_max_size", "get_output_buffer_max_size");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream_peer", PROPERTY_HINT_RESOURCE_TYPE, "StreamPeer", PROPERTY_USAGE_NONE), "set_stream_peer", "get_stream_peer");
|
||||
}
|
||||
|
||||
Error PacketPeerStream::_poll_buffer() const {
|
||||
ERR_FAIL_COND_V(peer.is_null(), ERR_UNCONFIGURED);
|
||||
|
||||
int read = 0;
|
||||
ERR_FAIL_COND_V(input_buffer.size() < ring_buffer.space_left(), ERR_UNAVAILABLE);
|
||||
Error err = peer->get_partial_data(input_buffer.ptrw(), ring_buffer.space_left(), read);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (read == 0) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int w = ring_buffer.write(&input_buffer[0], read);
|
||||
ERR_FAIL_COND_V(w != read, ERR_BUG);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int PacketPeerStream::get_available_packet_count() const {
|
||||
_poll_buffer();
|
||||
|
||||
uint32_t remaining = ring_buffer.data_left();
|
||||
|
||||
int ofs = 0;
|
||||
int count = 0;
|
||||
|
||||
while (remaining >= 4) {
|
||||
uint8_t lbuf[4];
|
||||
ring_buffer.copy(lbuf, ofs, 4);
|
||||
uint32_t len = decode_uint32(lbuf);
|
||||
remaining -= 4;
|
||||
ofs += 4;
|
||||
if (len > remaining) {
|
||||
break;
|
||||
}
|
||||
remaining -= len;
|
||||
ofs += len;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
Error PacketPeerStream::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
|
||||
ERR_FAIL_COND_V(peer.is_null(), ERR_UNCONFIGURED);
|
||||
_poll_buffer();
|
||||
|
||||
int remaining = ring_buffer.data_left();
|
||||
ERR_FAIL_COND_V(remaining < 4, ERR_UNAVAILABLE);
|
||||
uint8_t lbuf[4];
|
||||
ring_buffer.copy(lbuf, 0, 4);
|
||||
remaining -= 4;
|
||||
uint32_t len = decode_uint32(lbuf);
|
||||
ERR_FAIL_COND_V(remaining < (int)len, ERR_UNAVAILABLE);
|
||||
|
||||
ERR_FAIL_COND_V(input_buffer.size() < (int)len, ERR_UNAVAILABLE);
|
||||
ring_buffer.read(lbuf, 4); //get rid of first 4 bytes
|
||||
ring_buffer.read(input_buffer.ptrw(), len); // read packet
|
||||
|
||||
*r_buffer = &input_buffer[0];
|
||||
r_buffer_size = len;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error PacketPeerStream::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
|
||||
ERR_FAIL_COND_V(peer.is_null(), ERR_UNCONFIGURED);
|
||||
Error err = _poll_buffer(); //won't hurt to poll here too
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (p_buffer_size == 0) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(p_buffer_size < 0, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V(p_buffer_size + 4 > output_buffer.size(), ERR_INVALID_PARAMETER);
|
||||
|
||||
encode_uint32(p_buffer_size, output_buffer.ptrw());
|
||||
uint8_t *dst = &output_buffer.write[4];
|
||||
for (int i = 0; i < p_buffer_size; i++) {
|
||||
dst[i] = p_buffer[i];
|
||||
}
|
||||
|
||||
return peer->put_data(&output_buffer[0], p_buffer_size + 4);
|
||||
}
|
||||
|
||||
int PacketPeerStream::get_max_packet_size() const {
|
||||
return output_buffer.size();
|
||||
}
|
||||
|
||||
void PacketPeerStream::set_stream_peer(const Ref<StreamPeer> &p_peer) {
|
||||
if (p_peer.ptr() != peer.ptr()) {
|
||||
ring_buffer.advance_read(ring_buffer.data_left()); // Reset the ring buffer.
|
||||
}
|
||||
|
||||
peer = p_peer;
|
||||
}
|
||||
|
||||
Ref<StreamPeer> PacketPeerStream::get_stream_peer() const {
|
||||
return peer;
|
||||
}
|
||||
|
||||
void PacketPeerStream::set_input_buffer_max_size(int p_max_size) {
|
||||
ERR_FAIL_COND_MSG(p_max_size < 0, "Max size of input buffer size cannot be smaller than 0.");
|
||||
//warning may lose packets
|
||||
ERR_FAIL_COND_MSG(ring_buffer.data_left(), "Buffer in use, resizing would cause loss of data.");
|
||||
ring_buffer.resize(nearest_shift(next_power_of_2(p_max_size + 4)) - 1);
|
||||
input_buffer.resize(next_power_of_2(p_max_size + 4));
|
||||
}
|
||||
|
||||
int PacketPeerStream::get_input_buffer_max_size() const {
|
||||
return input_buffer.size() - 4;
|
||||
}
|
||||
|
||||
void PacketPeerStream::set_output_buffer_max_size(int p_max_size) {
|
||||
output_buffer.resize(next_power_of_2(p_max_size + 4));
|
||||
}
|
||||
|
||||
int PacketPeerStream::get_output_buffer_max_size() const {
|
||||
return output_buffer.size() - 4;
|
||||
}
|
||||
|
||||
PacketPeerStream::PacketPeerStream() {
|
||||
int64_t rbsize = GLOBAL_GET("network/limits/packet_peer_stream/max_buffer_po2");
|
||||
|
||||
ring_buffer.resize(rbsize);
|
||||
input_buffer.resize(int64_t(1) << rbsize);
|
||||
output_buffer.resize(int64_t(1) << rbsize);
|
||||
}
|
||||
128
engine/core/io/packet_peer.h
Normal file
128
engine/core/io/packet_peer.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/**************************************************************************/
|
||||
/* packet_peer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef PACKET_PEER_H
|
||||
#define PACKET_PEER_H
|
||||
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/templates/ring_buffer.h"
|
||||
|
||||
#include "core/extension/ext_wrappers.gen.inc"
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/variant/native_ptr.h"
|
||||
|
||||
class PacketPeer : public RefCounted {
|
||||
GDCLASS(PacketPeer, RefCounted);
|
||||
|
||||
Variant _bnd_get_var(bool p_allow_objects = false);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
Error _put_packet(const Vector<uint8_t> &p_buffer);
|
||||
Vector<uint8_t> _get_packet();
|
||||
Error _get_packet_error() const;
|
||||
|
||||
mutable Error last_get_error = OK;
|
||||
|
||||
int encode_buffer_max_size = 8 * 1024 * 1024;
|
||||
Vector<uint8_t> encode_buffer;
|
||||
|
||||
public:
|
||||
virtual int get_available_packet_count() const = 0;
|
||||
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) = 0; ///< buffer is GONE after next get_packet
|
||||
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) = 0;
|
||||
|
||||
virtual int get_max_packet_size() const = 0;
|
||||
|
||||
/* helpers / binders */
|
||||
|
||||
virtual Error get_packet_buffer(Vector<uint8_t> &r_buffer);
|
||||
virtual Error put_packet_buffer(const Vector<uint8_t> &p_buffer);
|
||||
|
||||
virtual Error get_var(Variant &r_variant, bool p_allow_objects = false);
|
||||
virtual Error put_var(const Variant &p_packet, bool p_full_objects = false);
|
||||
|
||||
void set_encode_buffer_max_size(int p_max_size);
|
||||
int get_encode_buffer_max_size() const;
|
||||
|
||||
PacketPeer() {}
|
||||
~PacketPeer() {}
|
||||
};
|
||||
|
||||
class PacketPeerExtension : public PacketPeer {
|
||||
GDCLASS(PacketPeerExtension, PacketPeer);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; ///< buffer is GONE after next get_packet
|
||||
GDVIRTUAL2R(Error, _get_packet, GDExtensionConstPtr<const uint8_t *>, GDExtensionPtr<int>);
|
||||
|
||||
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
|
||||
GDVIRTUAL2R(Error, _put_packet, GDExtensionConstPtr<const uint8_t>, int);
|
||||
|
||||
EXBIND0RC(int, get_available_packet_count);
|
||||
EXBIND0RC(int, get_max_packet_size);
|
||||
};
|
||||
|
||||
class PacketPeerStream : public PacketPeer {
|
||||
GDCLASS(PacketPeerStream, PacketPeer);
|
||||
|
||||
//the way the buffers work sucks, will change later
|
||||
|
||||
mutable Ref<StreamPeer> peer;
|
||||
mutable RingBuffer<uint8_t> ring_buffer;
|
||||
mutable Vector<uint8_t> input_buffer;
|
||||
mutable Vector<uint8_t> output_buffer;
|
||||
|
||||
Error _poll_buffer() const;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual int get_available_packet_count() const override;
|
||||
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
|
||||
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
|
||||
|
||||
virtual int get_max_packet_size() const override;
|
||||
|
||||
void set_stream_peer(const Ref<StreamPeer> &p_peer);
|
||||
Ref<StreamPeer> get_stream_peer() const;
|
||||
void set_input_buffer_max_size(int p_max_size);
|
||||
int get_input_buffer_max_size() const;
|
||||
void set_output_buffer_max_size(int p_max_size);
|
||||
int get_output_buffer_max_size() const;
|
||||
PacketPeerStream();
|
||||
};
|
||||
|
||||
#endif // PACKET_PEER_H
|
||||
60
engine/core/io/packet_peer_dtls.cpp
Normal file
60
engine/core/io/packet_peer_dtls.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**************************************************************************/
|
||||
/* packet_peer_dtls.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 "packet_peer_dtls.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
PacketPeerDTLS *(*PacketPeerDTLS::_create)() = nullptr;
|
||||
bool PacketPeerDTLS::available = false;
|
||||
|
||||
PacketPeerDTLS *PacketPeerDTLS::create() {
|
||||
if (_create) {
|
||||
return _create();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PacketPeerDTLS::is_available() {
|
||||
return available;
|
||||
}
|
||||
|
||||
void PacketPeerDTLS::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("poll"), &PacketPeerDTLS::poll);
|
||||
ClassDB::bind_method(D_METHOD("connect_to_peer", "packet_peer", "hostname", "client_options"), &PacketPeerDTLS::connect_to_peer, DEFVAL(Ref<TLSOptions>()));
|
||||
ClassDB::bind_method(D_METHOD("get_status"), &PacketPeerDTLS::get_status);
|
||||
ClassDB::bind_method(D_METHOD("disconnect_from_peer"), &PacketPeerDTLS::disconnect_from_peer);
|
||||
|
||||
BIND_ENUM_CONSTANT(STATUS_DISCONNECTED);
|
||||
BIND_ENUM_CONSTANT(STATUS_HANDSHAKING);
|
||||
BIND_ENUM_CONSTANT(STATUS_CONNECTED);
|
||||
BIND_ENUM_CONSTANT(STATUS_ERROR);
|
||||
BIND_ENUM_CONSTANT(STATUS_ERROR_HOSTNAME_MISMATCH);
|
||||
}
|
||||
68
engine/core/io/packet_peer_dtls.h
Normal file
68
engine/core/io/packet_peer_dtls.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/**************************************************************************/
|
||||
/* packet_peer_dtls.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef PACKET_PEER_DTLS_H
|
||||
#define PACKET_PEER_DTLS_H
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
#include "core/io/packet_peer_udp.h"
|
||||
|
||||
class PacketPeerDTLS : public PacketPeer {
|
||||
GDCLASS(PacketPeerDTLS, PacketPeer);
|
||||
|
||||
protected:
|
||||
static PacketPeerDTLS *(*_create)();
|
||||
static void _bind_methods();
|
||||
|
||||
static bool available;
|
||||
|
||||
public:
|
||||
enum Status {
|
||||
STATUS_DISCONNECTED,
|
||||
STATUS_HANDSHAKING,
|
||||
STATUS_CONNECTED,
|
||||
STATUS_ERROR,
|
||||
STATUS_ERROR_HOSTNAME_MISMATCH
|
||||
};
|
||||
|
||||
virtual void poll() = 0;
|
||||
virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0;
|
||||
virtual void disconnect_from_peer() = 0;
|
||||
virtual Status get_status() const = 0;
|
||||
|
||||
static PacketPeerDTLS *create();
|
||||
static bool is_available();
|
||||
|
||||
PacketPeerDTLS() {}
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(PacketPeerDTLS::Status);
|
||||
|
||||
#endif // PACKET_PEER_DTLS_H
|
||||
368
engine/core/io/packet_peer_udp.cpp
Normal file
368
engine/core/io/packet_peer_udp.cpp
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
/**************************************************************************/
|
||||
/* packet_peer_udp.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 "packet_peer_udp.h"
|
||||
|
||||
#include "core/io/ip.h"
|
||||
#include "core/io/udp_server.h"
|
||||
|
||||
void PacketPeerUDP::set_blocking_mode(bool p_enable) {
|
||||
blocking = p_enable;
|
||||
}
|
||||
|
||||
void PacketPeerUDP::set_broadcast_enabled(bool p_enabled) {
|
||||
ERR_FAIL_COND(udp_server);
|
||||
broadcast = p_enabled;
|
||||
if (_sock.is_valid() && _sock->is_open()) {
|
||||
_sock->set_broadcasting_enabled(p_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::join_multicast_group(IPAddress p_multi_address, const String &p_if_name) {
|
||||
ERR_FAIL_COND_V(udp_server, ERR_LOCKED);
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(!p_multi_address.is_valid(), ERR_INVALID_PARAMETER);
|
||||
|
||||
if (!_sock->is_open()) {
|
||||
IP::Type ip_type = p_multi_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
|
||||
Error err = _sock->open(NetSocket::TYPE_UDP, ip_type);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
_sock->set_blocking_enabled(false);
|
||||
_sock->set_broadcasting_enabled(broadcast);
|
||||
}
|
||||
return _sock->join_multicast_group(p_multi_address, p_if_name);
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::leave_multicast_group(IPAddress p_multi_address, const String &p_if_name) {
|
||||
ERR_FAIL_COND_V(udp_server, ERR_LOCKED);
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(!_sock->is_open(), ERR_UNCONFIGURED);
|
||||
return _sock->leave_multicast_group(p_multi_address, p_if_name);
|
||||
}
|
||||
|
||||
String PacketPeerUDP::_get_packet_ip() const {
|
||||
return get_packet_address();
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::_set_dest_address(const String &p_address, int p_port) {
|
||||
IPAddress ip;
|
||||
if (p_address.is_valid_ip_address()) {
|
||||
ip = p_address;
|
||||
} else {
|
||||
ip = IP::get_singleton()->resolve_hostname(p_address);
|
||||
if (!ip.is_valid()) {
|
||||
return ERR_CANT_RESOLVE;
|
||||
}
|
||||
}
|
||||
|
||||
set_dest_address(ip, p_port);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int PacketPeerUDP::get_available_packet_count() const {
|
||||
// TODO we should deprecate this, and expose poll instead!
|
||||
Error err = const_cast<PacketPeerUDP *>(this)->_poll();
|
||||
if (err != OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return queue_count;
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
|
||||
Error err = _poll();
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
if (queue_count == 0) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
uint32_t size = 0;
|
||||
uint8_t ipv6[16] = {};
|
||||
rb.read(ipv6, 16, true);
|
||||
packet_ip.set_ipv6(ipv6);
|
||||
rb.read((uint8_t *)&packet_port, 4, true);
|
||||
rb.read((uint8_t *)&size, 4, true);
|
||||
rb.read(packet_buffer, size, true);
|
||||
--queue_count;
|
||||
*r_buffer = packet_buffer;
|
||||
r_buffer_size = size;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(!peer_addr.is_valid(), ERR_UNCONFIGURED);
|
||||
|
||||
Error err;
|
||||
int sent = -1;
|
||||
|
||||
if (!_sock->is_open()) {
|
||||
IP::Type ip_type = peer_addr.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
|
||||
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
_sock->set_blocking_enabled(false);
|
||||
_sock->set_broadcasting_enabled(broadcast);
|
||||
}
|
||||
|
||||
do {
|
||||
if (connected && !udp_server) {
|
||||
err = _sock->send(p_buffer, p_buffer_size, sent);
|
||||
} else {
|
||||
err = _sock->sendto(p_buffer, p_buffer_size, sent, peer_addr, peer_port);
|
||||
}
|
||||
if (err != OK) {
|
||||
if (err != ERR_BUSY) {
|
||||
return FAILED;
|
||||
} else if (!blocking) {
|
||||
return ERR_BUSY;
|
||||
}
|
||||
// Keep trying to send full packet
|
||||
continue;
|
||||
}
|
||||
return OK;
|
||||
|
||||
} while (sent != p_buffer_size);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int PacketPeerUDP::get_max_packet_size() const {
|
||||
return 512; // uhm maybe not
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::bind(int p_port, const IPAddress &p_bind_address, int p_recv_buffer_size) {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE);
|
||||
ERR_FAIL_COND_V(!p_bind_address.is_valid() && !p_bind_address.is_wildcard(), ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V_MSG(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER, "The local port number must be between 0 and 65535 (inclusive).");
|
||||
|
||||
Error err;
|
||||
IP::Type ip_type = IP::TYPE_ANY;
|
||||
|
||||
if (p_bind_address.is_valid()) {
|
||||
ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
|
||||
}
|
||||
|
||||
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
|
||||
|
||||
if (err != OK) {
|
||||
return ERR_CANT_CREATE;
|
||||
}
|
||||
|
||||
_sock->set_blocking_enabled(false);
|
||||
_sock->set_broadcasting_enabled(broadcast);
|
||||
err = _sock->bind(p_bind_address, p_port);
|
||||
|
||||
if (err != OK) {
|
||||
_sock->close();
|
||||
return err;
|
||||
}
|
||||
rb.resize(nearest_shift(p_recv_buffer_size));
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::connect_shared_socket(Ref<NetSocket> p_sock, IPAddress p_ip, uint16_t p_port, UDPServer *p_server) {
|
||||
udp_server = p_server;
|
||||
connected = true;
|
||||
_sock = p_sock;
|
||||
peer_addr = p_ip;
|
||||
peer_port = p_port;
|
||||
packet_ip = peer_addr;
|
||||
packet_port = peer_port;
|
||||
return OK;
|
||||
}
|
||||
|
||||
void PacketPeerUDP::disconnect_shared_socket() {
|
||||
udp_server = nullptr;
|
||||
_sock = Ref<NetSocket>(NetSocket::create());
|
||||
close();
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::connect_to_host(const IPAddress &p_host, int p_port) {
|
||||
ERR_FAIL_COND_V(udp_server, ERR_LOCKED);
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(!p_host.is_valid(), ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, ERR_INVALID_PARAMETER, "The remote port number must be between 1 and 65535 (inclusive).");
|
||||
|
||||
Error err;
|
||||
|
||||
if (!_sock->is_open()) {
|
||||
IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
|
||||
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
|
||||
ERR_FAIL_COND_V(err != OK, ERR_CANT_OPEN);
|
||||
_sock->set_blocking_enabled(false);
|
||||
}
|
||||
|
||||
err = _sock->connect_to_host(p_host, p_port);
|
||||
|
||||
// I see no reason why we should get ERR_BUSY (wouldblock/eagain) here.
|
||||
// This is UDP, so connect is only used to tell the OS to which socket
|
||||
// it should deliver packets when multiple are bound on the same address/port.
|
||||
if (err != OK) {
|
||||
close();
|
||||
ERR_FAIL_V_MSG(FAILED, "Unable to connect");
|
||||
}
|
||||
|
||||
connected = true;
|
||||
|
||||
peer_addr = p_host;
|
||||
peer_port = p_port;
|
||||
|
||||
// Flush any packet we might still have in queue.
|
||||
rb.clear();
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool PacketPeerUDP::is_socket_connected() const {
|
||||
return connected;
|
||||
}
|
||||
|
||||
void PacketPeerUDP::close() {
|
||||
if (udp_server) {
|
||||
udp_server->remove_peer(peer_addr, peer_port);
|
||||
udp_server = nullptr;
|
||||
_sock = Ref<NetSocket>(NetSocket::create());
|
||||
} else if (_sock.is_valid()) {
|
||||
_sock->close();
|
||||
}
|
||||
rb.resize(16);
|
||||
queue_count = 0;
|
||||
connected = false;
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::wait() {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
return _sock->poll(NetSocket::POLL_TYPE_IN, -1);
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::_poll() {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
|
||||
if (!_sock->is_open()) {
|
||||
return FAILED;
|
||||
}
|
||||
if (udp_server) {
|
||||
return OK; // Handled by UDPServer.
|
||||
}
|
||||
|
||||
Error err;
|
||||
int read;
|
||||
IPAddress ip;
|
||||
uint16_t port;
|
||||
|
||||
while (true) {
|
||||
if (connected) {
|
||||
err = _sock->recv(recv_buffer, sizeof(recv_buffer), read);
|
||||
ip = peer_addr;
|
||||
port = peer_port;
|
||||
} else {
|
||||
err = _sock->recvfrom(recv_buffer, sizeof(recv_buffer), read, ip, port);
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
if (err == ERR_BUSY) {
|
||||
break;
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
err = store_packet(ip, port, recv_buffer, read);
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (err != OK) {
|
||||
WARN_PRINT("Buffer full, dropping packets!");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error PacketPeerUDP::store_packet(IPAddress p_ip, uint32_t p_port, uint8_t *p_buf, int p_buf_size) {
|
||||
if (rb.space_left() < p_buf_size + 24) {
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
rb.write(p_ip.get_ipv6(), 16);
|
||||
rb.write((uint8_t *)&p_port, 4);
|
||||
rb.write((uint8_t *)&p_buf_size, 4);
|
||||
rb.write(p_buf, p_buf_size);
|
||||
++queue_count;
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool PacketPeerUDP::is_bound() const {
|
||||
return _sock.is_valid() && _sock->is_open();
|
||||
}
|
||||
|
||||
IPAddress PacketPeerUDP::get_packet_address() const {
|
||||
return packet_ip;
|
||||
}
|
||||
|
||||
int PacketPeerUDP::get_packet_port() const {
|
||||
return packet_port;
|
||||
}
|
||||
|
||||
int PacketPeerUDP::get_local_port() const {
|
||||
uint16_t local_port;
|
||||
_sock->get_socket_address(nullptr, &local_port);
|
||||
return local_port;
|
||||
}
|
||||
|
||||
void PacketPeerUDP::set_dest_address(const IPAddress &p_address, int p_port) {
|
||||
ERR_FAIL_COND_MSG(connected, "Destination address cannot be set for connected sockets");
|
||||
peer_addr = p_address;
|
||||
peer_port = p_port;
|
||||
}
|
||||
|
||||
void PacketPeerUDP::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("bind", "port", "bind_address", "recv_buf_size"), &PacketPeerUDP::bind, DEFVAL("*"), DEFVAL(65536));
|
||||
ClassDB::bind_method(D_METHOD("close"), &PacketPeerUDP::close);
|
||||
ClassDB::bind_method(D_METHOD("wait"), &PacketPeerUDP::wait);
|
||||
ClassDB::bind_method(D_METHOD("is_bound"), &PacketPeerUDP::is_bound);
|
||||
ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &PacketPeerUDP::connect_to_host);
|
||||
ClassDB::bind_method(D_METHOD("is_socket_connected"), &PacketPeerUDP::is_socket_connected);
|
||||
ClassDB::bind_method(D_METHOD("get_packet_ip"), &PacketPeerUDP::_get_packet_ip);
|
||||
ClassDB::bind_method(D_METHOD("get_packet_port"), &PacketPeerUDP::get_packet_port);
|
||||
ClassDB::bind_method(D_METHOD("get_local_port"), &PacketPeerUDP::get_local_port);
|
||||
ClassDB::bind_method(D_METHOD("set_dest_address", "host", "port"), &PacketPeerUDP::_set_dest_address);
|
||||
ClassDB::bind_method(D_METHOD("set_broadcast_enabled", "enabled"), &PacketPeerUDP::set_broadcast_enabled);
|
||||
ClassDB::bind_method(D_METHOD("join_multicast_group", "multicast_address", "interface_name"), &PacketPeerUDP::join_multicast_group);
|
||||
ClassDB::bind_method(D_METHOD("leave_multicast_group", "multicast_address", "interface_name"), &PacketPeerUDP::leave_multicast_group);
|
||||
}
|
||||
|
||||
PacketPeerUDP::PacketPeerUDP() :
|
||||
_sock(Ref<NetSocket>(NetSocket::create())) {
|
||||
rb.resize(16);
|
||||
}
|
||||
|
||||
PacketPeerUDP::~PacketPeerUDP() {
|
||||
close();
|
||||
}
|
||||
101
engine/core/io/packet_peer_udp.h
Normal file
101
engine/core/io/packet_peer_udp.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/**************************************************************************/
|
||||
/* packet_peer_udp.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef PACKET_PEER_UDP_H
|
||||
#define PACKET_PEER_UDP_H
|
||||
|
||||
#include "core/io/ip.h"
|
||||
#include "core/io/net_socket.h"
|
||||
#include "core/io/packet_peer.h"
|
||||
|
||||
class UDPServer;
|
||||
|
||||
class PacketPeerUDP : public PacketPeer {
|
||||
GDCLASS(PacketPeerUDP, PacketPeer);
|
||||
|
||||
protected:
|
||||
enum {
|
||||
PACKET_BUFFER_SIZE = 65536
|
||||
};
|
||||
|
||||
RingBuffer<uint8_t> rb;
|
||||
uint8_t recv_buffer[PACKET_BUFFER_SIZE];
|
||||
uint8_t packet_buffer[PACKET_BUFFER_SIZE];
|
||||
IPAddress packet_ip;
|
||||
int packet_port = 0;
|
||||
int queue_count = 0;
|
||||
|
||||
IPAddress peer_addr;
|
||||
int peer_port = 0;
|
||||
bool connected = false;
|
||||
bool blocking = true;
|
||||
bool broadcast = false;
|
||||
UDPServer *udp_server = nullptr;
|
||||
Ref<NetSocket> _sock;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
String _get_packet_ip() const;
|
||||
|
||||
Error _set_dest_address(const String &p_address, int p_port);
|
||||
Error _poll();
|
||||
|
||||
public:
|
||||
void set_blocking_mode(bool p_enable);
|
||||
|
||||
Error bind(int p_port, const IPAddress &p_bind_address = IPAddress("*"), int p_recv_buffer_size = 65536);
|
||||
void close();
|
||||
Error wait();
|
||||
bool is_bound() const;
|
||||
|
||||
Error connect_shared_socket(Ref<NetSocket> p_sock, IPAddress p_ip, uint16_t p_port, UDPServer *ref); // Used by UDPServer
|
||||
void disconnect_shared_socket(); // Used by UDPServer
|
||||
Error store_packet(IPAddress p_ip, uint32_t p_port, uint8_t *p_buf, int p_buf_size); // Used internally and by UDPServer
|
||||
Error connect_to_host(const IPAddress &p_host, int p_port);
|
||||
bool is_socket_connected() const;
|
||||
|
||||
IPAddress get_packet_address() const;
|
||||
int get_packet_port() const;
|
||||
int get_local_port() const;
|
||||
void set_dest_address(const IPAddress &p_address, int p_port);
|
||||
|
||||
Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
|
||||
Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
|
||||
int get_available_packet_count() const override;
|
||||
int get_max_packet_size() const override;
|
||||
void set_broadcast_enabled(bool p_enabled);
|
||||
Error join_multicast_group(IPAddress p_multi_address, const String &p_if_name);
|
||||
Error leave_multicast_group(IPAddress p_multi_address, const String &p_if_name);
|
||||
|
||||
PacketPeerUDP();
|
||||
~PacketPeerUDP();
|
||||
};
|
||||
|
||||
#endif // PACKET_PEER_UDP_H
|
||||
261
engine/core/io/pck_packer.cpp
Normal file
261
engine/core/io/pck_packer.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/**************************************************************************/
|
||||
/* pck_packer.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 "pck_packer.h"
|
||||
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/file_access_encrypted.h"
|
||||
#include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION
|
||||
#include "core/version.h"
|
||||
|
||||
static int _get_pad(int p_alignment, int p_n) {
|
||||
int rest = p_n % p_alignment;
|
||||
int pad = 0;
|
||||
if (rest > 0) {
|
||||
pad = p_alignment - rest;
|
||||
}
|
||||
|
||||
return pad;
|
||||
}
|
||||
|
||||
void PCKPacker::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("pck_start", "pck_name", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("flush", "verbose"), &PCKPacker::flush, DEFVAL(false));
|
||||
}
|
||||
|
||||
Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &p_key, bool p_encrypt_directory) {
|
||||
ERR_FAIL_COND_V_MSG((p_key.is_empty() || !p_key.is_valid_hex_number(false) || p_key.length() != 64), ERR_CANT_CREATE, "Invalid Encryption Key (must be 64 characters long).");
|
||||
ERR_FAIL_COND_V_MSG(p_alignment <= 0, ERR_CANT_CREATE, "Invalid alignment, must be greater then 0.");
|
||||
|
||||
String _key = p_key.to_lower();
|
||||
key.resize(32);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int v = 0;
|
||||
if (i * 2 < _key.length()) {
|
||||
char32_t ct = _key[i * 2];
|
||||
if (is_digit(ct)) {
|
||||
ct = ct - '0';
|
||||
} else if (ct >= 'a' && ct <= 'f') {
|
||||
ct = 10 + ct - 'a';
|
||||
}
|
||||
v |= ct << 4;
|
||||
}
|
||||
|
||||
if (i * 2 + 1 < _key.length()) {
|
||||
char32_t ct = _key[i * 2 + 1];
|
||||
if (is_digit(ct)) {
|
||||
ct = ct - '0';
|
||||
} else if (ct >= 'a' && ct <= 'f') {
|
||||
ct = 10 + ct - 'a';
|
||||
}
|
||||
v |= ct;
|
||||
}
|
||||
key.write[i] = v;
|
||||
}
|
||||
enc_dir = p_encrypt_directory;
|
||||
|
||||
file = FileAccess::open(p_file, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_file) + ".");
|
||||
|
||||
alignment = p_alignment;
|
||||
|
||||
file->store_32(PACK_HEADER_MAGIC);
|
||||
file->store_32(PACK_FORMAT_VERSION);
|
||||
file->store_32(VERSION_MAJOR);
|
||||
file->store_32(VERSION_MINOR);
|
||||
file->store_32(VERSION_PATCH);
|
||||
|
||||
uint32_t pack_flags = 0;
|
||||
if (enc_dir) {
|
||||
pack_flags |= PACK_DIR_ENCRYPTED;
|
||||
}
|
||||
file->store_32(pack_flags); // flags
|
||||
|
||||
files.clear();
|
||||
ofs = 0;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encrypt) {
|
||||
ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use.");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(p_src, FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
return ERR_FILE_CANT_OPEN;
|
||||
}
|
||||
|
||||
File pf;
|
||||
// Simplify path here and on every 'files' access so that paths that have extra '/'
|
||||
// symbols in them still match to the MD5 hash for the saved path.
|
||||
pf.path = p_file.simplify_path();
|
||||
pf.src_path = p_src;
|
||||
pf.ofs = ofs;
|
||||
pf.size = f->get_length();
|
||||
|
||||
Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_src);
|
||||
{
|
||||
unsigned char hash[16];
|
||||
CryptoCore::md5(data.ptr(), data.size(), hash);
|
||||
pf.md5.resize(16);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
pf.md5.write[i] = hash[i];
|
||||
}
|
||||
}
|
||||
pf.encrypted = p_encrypt;
|
||||
|
||||
uint64_t _size = pf.size;
|
||||
if (p_encrypt) { // Add encryption overhead.
|
||||
if (_size % 16) { // Pad to encryption block size.
|
||||
_size += 16 - (_size % 16);
|
||||
}
|
||||
_size += 16; // hash
|
||||
_size += 8; // data size
|
||||
_size += 16; // iv
|
||||
}
|
||||
|
||||
int pad = _get_pad(alignment, ofs + _size);
|
||||
ofs = ofs + _size + pad;
|
||||
|
||||
files.push_back(pf);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error PCKPacker::flush(bool p_verbose) {
|
||||
ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use.");
|
||||
|
||||
int64_t file_base_ofs = file->get_position();
|
||||
file->store_64(0); // files base
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
file->store_32(0); // reserved
|
||||
}
|
||||
|
||||
// write the index
|
||||
file->store_32(files.size());
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
Ref<FileAccess> fhead = file;
|
||||
|
||||
if (enc_dir) {
|
||||
fae.instantiate();
|
||||
ERR_FAIL_COND_V(fae.is_null(), ERR_CANT_CREATE);
|
||||
|
||||
Error err = fae->open_and_parse(file, key, FileAccessEncrypted::MODE_WRITE_AES256, false);
|
||||
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
|
||||
|
||||
fhead = fae;
|
||||
}
|
||||
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
int string_len = files[i].path.utf8().length();
|
||||
int pad = _get_pad(4, string_len);
|
||||
|
||||
fhead->store_32(string_len + pad);
|
||||
fhead->store_buffer((const uint8_t *)files[i].path.utf8().get_data(), string_len);
|
||||
for (int j = 0; j < pad; j++) {
|
||||
fhead->store_8(0);
|
||||
}
|
||||
|
||||
fhead->store_64(files[i].ofs);
|
||||
fhead->store_64(files[i].size); // pay attention here, this is where file is
|
||||
fhead->store_buffer(files[i].md5.ptr(), 16); //also save md5 for file
|
||||
|
||||
uint32_t flags = 0;
|
||||
if (files[i].encrypted) {
|
||||
flags |= PACK_FILE_ENCRYPTED;
|
||||
}
|
||||
fhead->store_32(flags);
|
||||
}
|
||||
|
||||
if (fae.is_valid()) {
|
||||
fhead.unref();
|
||||
fae.unref();
|
||||
}
|
||||
|
||||
int header_padding = _get_pad(alignment, file->get_position());
|
||||
for (int i = 0; i < header_padding; i++) {
|
||||
file->store_8(0);
|
||||
}
|
||||
|
||||
int64_t file_base = file->get_position();
|
||||
file->seek(file_base_ofs);
|
||||
file->store_64(file_base); // update files base
|
||||
file->seek(file_base);
|
||||
|
||||
const uint32_t buf_max = 65536;
|
||||
uint8_t *buf = memnew_arr(uint8_t, buf_max);
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
Ref<FileAccess> src = FileAccess::open(files[i].src_path, FileAccess::READ);
|
||||
uint64_t to_write = files[i].size;
|
||||
|
||||
Ref<FileAccess> ftmp = file;
|
||||
if (files[i].encrypted) {
|
||||
fae.instantiate();
|
||||
ERR_FAIL_COND_V(fae.is_null(), ERR_CANT_CREATE);
|
||||
|
||||
Error err = fae->open_and_parse(file, key, FileAccessEncrypted::MODE_WRITE_AES256, false);
|
||||
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
|
||||
ftmp = fae;
|
||||
}
|
||||
|
||||
while (to_write > 0) {
|
||||
uint64_t read = src->get_buffer(buf, MIN(to_write, buf_max));
|
||||
ftmp->store_buffer(buf, read);
|
||||
to_write -= read;
|
||||
}
|
||||
|
||||
if (fae.is_valid()) {
|
||||
ftmp.unref();
|
||||
fae.unref();
|
||||
}
|
||||
|
||||
int pad = _get_pad(alignment, file->get_position());
|
||||
for (int j = 0; j < pad; j++) {
|
||||
file->store_8(0);
|
||||
}
|
||||
|
||||
count += 1;
|
||||
const int file_num = files.size();
|
||||
if (p_verbose && (file_num > 0)) {
|
||||
print_line(vformat("[%d/%d - %d%%] PCKPacker flush: %s -> %s", count, file_num, float(count) / file_num * 100, files[i].src_path, files[i].path));
|
||||
}
|
||||
}
|
||||
|
||||
file.unref();
|
||||
memdelete_arr(buf);
|
||||
|
||||
return OK;
|
||||
}
|
||||
68
engine/core/io/pck_packer.h
Normal file
68
engine/core/io/pck_packer.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/**************************************************************************/
|
||||
/* pck_packer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef PCK_PACKER_H
|
||||
#define PCK_PACKER_H
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class FileAccess;
|
||||
|
||||
class PCKPacker : public RefCounted {
|
||||
GDCLASS(PCKPacker, RefCounted);
|
||||
|
||||
Ref<FileAccess> file;
|
||||
int alignment = 0;
|
||||
uint64_t ofs = 0;
|
||||
|
||||
Vector<uint8_t> key;
|
||||
bool enc_dir = false;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
struct File {
|
||||
String path;
|
||||
String src_path;
|
||||
uint64_t ofs = 0;
|
||||
uint64_t size = 0;
|
||||
bool encrypted = false;
|
||||
Vector<uint8_t> md5;
|
||||
};
|
||||
Vector<File> files;
|
||||
|
||||
public:
|
||||
Error pck_start(const String &p_file, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false);
|
||||
Error add_file(const String &p_file, const String &p_src, bool p_encrypt = false);
|
||||
Error flush(bool p_verbose = false);
|
||||
|
||||
PCKPacker() {}
|
||||
};
|
||||
|
||||
#endif // PCK_PACKER_H
|
||||
868
engine/core/io/plist.cpp
Normal file
868
engine/core/io/plist.cpp
Normal file
|
|
@ -0,0 +1,868 @@
|
|||
/**************************************************************************/
|
||||
/* plist.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 "plist.h"
|
||||
|
||||
PList::PLNodeType PListNode::get_type() const {
|
||||
return data_type;
|
||||
}
|
||||
|
||||
Variant PListNode::get_value() const {
|
||||
switch (data_type) {
|
||||
case PList::PL_NODE_TYPE_NIL: {
|
||||
return Variant();
|
||||
} break;
|
||||
case PList::PL_NODE_TYPE_STRING: {
|
||||
return String::utf8(data_string.get_data());
|
||||
} break;
|
||||
case PList::PL_NODE_TYPE_ARRAY: {
|
||||
Array arr;
|
||||
for (const Ref<PListNode> &E : data_array) {
|
||||
arr.push_back(E);
|
||||
}
|
||||
return arr;
|
||||
} break;
|
||||
case PList::PL_NODE_TYPE_DICT: {
|
||||
Dictionary dict;
|
||||
for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
|
||||
dict[E.key] = E.value;
|
||||
}
|
||||
return dict;
|
||||
} break;
|
||||
case PList::PL_NODE_TYPE_BOOLEAN: {
|
||||
return data_bool;
|
||||
} break;
|
||||
case PList::PL_NODE_TYPE_INTEGER: {
|
||||
return data_int;
|
||||
} break;
|
||||
case PList::PL_NODE_TYPE_REAL: {
|
||||
return data_real;
|
||||
} break;
|
||||
case PList::PL_NODE_TYPE_DATA: {
|
||||
int strlen = data_string.length();
|
||||
|
||||
size_t arr_len = 0;
|
||||
Vector<uint8_t> buf;
|
||||
{
|
||||
buf.resize(strlen / 4 * 3 + 1);
|
||||
uint8_t *w = buf.ptrw();
|
||||
|
||||
ERR_FAIL_COND_V(CryptoCore::b64_decode(&w[0], buf.size(), &arr_len, (unsigned char *)data_string.get_data(), strlen) != OK, Vector<uint8_t>());
|
||||
}
|
||||
buf.resize(arr_len);
|
||||
return buf;
|
||||
} break;
|
||||
case PList::PL_NODE_TYPE_DATE: {
|
||||
return String(data_string.get_data());
|
||||
} break;
|
||||
}
|
||||
return Variant();
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_node(const Variant &p_value) {
|
||||
Ref<PListNode> node;
|
||||
node.instantiate();
|
||||
|
||||
switch (p_value.get_type()) {
|
||||
case Variant::NIL: {
|
||||
node->data_type = PList::PL_NODE_TYPE_NIL;
|
||||
} break;
|
||||
case Variant::BOOL: {
|
||||
node->data_type = PList::PL_NODE_TYPE_BOOLEAN;
|
||||
node->data_bool = p_value;
|
||||
} break;
|
||||
case Variant::INT: {
|
||||
node->data_type = PList::PL_NODE_TYPE_INTEGER;
|
||||
node->data_int = p_value;
|
||||
} break;
|
||||
case Variant::FLOAT: {
|
||||
node->data_type = PList::PL_NODE_TYPE_REAL;
|
||||
node->data_real = p_value;
|
||||
} break;
|
||||
case Variant::STRING_NAME:
|
||||
case Variant::STRING: {
|
||||
node->data_type = PList::PL_NODE_TYPE_STRING;
|
||||
node->data_string = p_value.operator String().utf8();
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
node->data_type = PList::PL_NODE_TYPE_DICT;
|
||||
Dictionary dict = p_value;
|
||||
const Variant *next = dict.next(nullptr);
|
||||
while (next) {
|
||||
Ref<PListNode> sub_node = dict[*next];
|
||||
ERR_FAIL_COND_V_MSG(sub_node.is_null(), Ref<PListNode>(), "Invalid dictionary element, should be PListNode.");
|
||||
node->data_dict[*next] = sub_node;
|
||||
next = dict.next(next);
|
||||
}
|
||||
} break;
|
||||
case Variant::ARRAY: {
|
||||
node->data_type = PList::PL_NODE_TYPE_ARRAY;
|
||||
Array ar = p_value;
|
||||
for (int i = 0; i < ar.size(); i++) {
|
||||
Ref<PListNode> sub_node = ar[i];
|
||||
ERR_FAIL_COND_V_MSG(sub_node.is_null(), Ref<PListNode>(), "Invalid array element, should be PListNode.");
|
||||
node->data_array.push_back(sub_node);
|
||||
}
|
||||
} break;
|
||||
case Variant::PACKED_BYTE_ARRAY: {
|
||||
node->data_type = PList::PL_NODE_TYPE_DATA;
|
||||
PackedByteArray buf = p_value;
|
||||
node->data_string = CryptoCore::b64_encode_str(buf.ptr(), buf.size()).utf8();
|
||||
} break;
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(Ref<PListNode>(), "Unsupported data type.");
|
||||
} break;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_array() {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_ARRAY;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_dict() {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_string(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_STRING;
|
||||
node->data_string = p_string.utf8();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_data(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATA;
|
||||
node->data_string = p_string.utf8();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_date(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATE;
|
||||
node->data_string = p_string.utf8();
|
||||
node->data_real = (double)Time::get_singleton()->get_unix_time_from_datetime_string(p_string) - 978307200.0;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_bool(bool p_bool) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_BOOLEAN;
|
||||
node->data_bool = p_bool;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_int(int64_t p_int) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_INTEGER;
|
||||
node->data_int = p_int;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_real(double p_real) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_REAL;
|
||||
node->data_real = p_real;
|
||||
return node;
|
||||
}
|
||||
|
||||
bool PListNode::push_subnode(const Ref<PListNode> &p_node, const String &p_key) {
|
||||
ERR_FAIL_COND_V(p_node.is_null(), false);
|
||||
if (data_type == PList::PLNodeType::PL_NODE_TYPE_DICT) {
|
||||
ERR_FAIL_COND_V(p_key.is_empty(), false);
|
||||
ERR_FAIL_COND_V(data_dict.has(p_key), false);
|
||||
data_dict[p_key] = p_node;
|
||||
return true;
|
||||
} else if (data_type == PList::PLNodeType::PL_NODE_TYPE_ARRAY) {
|
||||
data_array.push_back(p_node);
|
||||
return true;
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(false, "PList: Invalid parent node type, should be DICT or ARRAY.");
|
||||
}
|
||||
}
|
||||
|
||||
size_t PListNode::get_asn1_size(uint8_t p_len_octets) const {
|
||||
// Get size of all data, excluding type and size information.
|
||||
switch (data_type) {
|
||||
case PList::PLNodeType::PL_NODE_TYPE_NIL: {
|
||||
return 0;
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATA:
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATE: {
|
||||
ERR_FAIL_V_MSG(0, "PList: DATE and DATA nodes are not supported by ASN.1 serialization.");
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_STRING: {
|
||||
return data_string.length();
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
|
||||
return 1;
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_INTEGER:
|
||||
case PList::PLNodeType::PL_NODE_TYPE_REAL: {
|
||||
return 4;
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
|
||||
size_t size = 0;
|
||||
for (int i = 0; i < data_array.size(); i++) {
|
||||
size += 1 + _asn1_size_len(p_len_octets) + data_array[i]->get_asn1_size(p_len_octets);
|
||||
}
|
||||
return size;
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DICT: {
|
||||
size_t size = 0;
|
||||
|
||||
for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
|
||||
size += 1 + _asn1_size_len(p_len_octets); // Sequence.
|
||||
size += 1 + _asn1_size_len(p_len_octets) + E.key.utf8().length(); //Key.
|
||||
size += 1 + _asn1_size_len(p_len_octets) + E.value->get_asn1_size(p_len_octets); // Value.
|
||||
}
|
||||
return size;
|
||||
} break;
|
||||
default: {
|
||||
return 0;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
int PListNode::_asn1_size_len(uint8_t p_len_octets) {
|
||||
if (p_len_octets > 1) {
|
||||
return p_len_octets + 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void PListNode::store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const {
|
||||
uint32_t size = get_asn1_size(p_len_octets);
|
||||
if (p_len_octets > 1) {
|
||||
p_stream.push_back(0x80 + p_len_octets);
|
||||
}
|
||||
for (int i = p_len_octets - 1; i >= 0; i--) {
|
||||
uint8_t x = (size >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
}
|
||||
|
||||
bool PListNode::store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const {
|
||||
// Convert to binary ASN1 stream.
|
||||
bool valid = true;
|
||||
switch (data_type) {
|
||||
case PList::PLNodeType::PL_NODE_TYPE_NIL: {
|
||||
// Nothing to store.
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATE:
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATA: {
|
||||
ERR_FAIL_V_MSG(false, "PList: DATE and DATA nodes are not supported by ASN.1 serialization.");
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_STRING: {
|
||||
p_stream.push_back(0x0C);
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (int i = 0; i < data_string.size(); i++) {
|
||||
p_stream.push_back(data_string[i]);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
|
||||
p_stream.push_back(0x01);
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
if (data_bool) {
|
||||
p_stream.push_back(0x01);
|
||||
} else {
|
||||
p_stream.push_back(0x00);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
|
||||
p_stream.push_back(0x02);
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
uint8_t x = (data_int >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_REAL: {
|
||||
p_stream.push_back(0x03);
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
uint8_t x = (data_int >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
|
||||
p_stream.push_back(0x30); // Sequence.
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (int i = 0; i < data_array.size(); i++) {
|
||||
valid = valid && data_array[i]->store_asn1(p_stream, p_len_octets);
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DICT: {
|
||||
p_stream.push_back(0x31); // Set.
|
||||
store_asn1_size(p_stream, p_len_octets);
|
||||
for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
|
||||
CharString cs = E.key.utf8();
|
||||
uint32_t size = cs.length();
|
||||
|
||||
// Sequence.
|
||||
p_stream.push_back(0x30);
|
||||
uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + E.value->get_asn1_size(p_len_octets);
|
||||
if (p_len_octets > 1) {
|
||||
p_stream.push_back(0x80 + p_len_octets);
|
||||
}
|
||||
for (int i = p_len_octets - 1; i >= 0; i--) {
|
||||
uint8_t x = (seq_size >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
// Key.
|
||||
p_stream.push_back(0x0C);
|
||||
if (p_len_octets > 1) {
|
||||
p_stream.push_back(0x80 + p_len_octets);
|
||||
}
|
||||
for (int i = p_len_octets - 1; i >= 0; i--) {
|
||||
uint8_t x = (size >> i * 8) & 0xFF;
|
||||
p_stream.push_back(x);
|
||||
}
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
p_stream.push_back(cs[i]);
|
||||
}
|
||||
// Value.
|
||||
valid = valid && E.value->store_asn1(p_stream, p_len_octets);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
void PListNode::store_text(String &p_stream, uint8_t p_indent) const {
|
||||
// Convert to text XML stream.
|
||||
switch (data_type) {
|
||||
case PList::PLNodeType::PL_NODE_TYPE_NIL: {
|
||||
// Nothing to store.
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATA: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<data>\n";
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += data_string + "\n";
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "</data>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATE: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<date>";
|
||||
p_stream += data_string;
|
||||
p_stream += "</date>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_STRING: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<string>";
|
||||
p_stream += String::utf8(data_string);
|
||||
p_stream += "</string>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
if (data_bool) {
|
||||
p_stream += "<true/>\n";
|
||||
} else {
|
||||
p_stream += "<false/>\n";
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<integer>";
|
||||
p_stream += itos(data_int);
|
||||
p_stream += "</integer>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_REAL: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<real>";
|
||||
p_stream += rtos(data_real);
|
||||
p_stream += "</real>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<array>\n";
|
||||
for (int i = 0; i < data_array.size(); i++) {
|
||||
data_array[i]->store_text(p_stream, p_indent + 1);
|
||||
}
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "</array>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DICT: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<dict>\n";
|
||||
for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
|
||||
p_stream += String("\t").repeat(p_indent + 1);
|
||||
p_stream += "<key>";
|
||||
p_stream += E.key;
|
||||
p_stream += "</key>\n";
|
||||
E.value->store_text(p_stream, p_indent + 1);
|
||||
}
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "</dict>\n";
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
PList::PList() {
|
||||
root = PListNode::new_dict();
|
||||
}
|
||||
|
||||
PList::PList(const String &p_string) {
|
||||
String err_str;
|
||||
bool ok = load_string(p_string, err_str);
|
||||
ERR_FAIL_COND_MSG(!ok, "PList: " + err_str);
|
||||
}
|
||||
|
||||
uint64_t PList::read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size) {
|
||||
uint64_t pos = p_file->get_position();
|
||||
uint64_t ret = 0;
|
||||
switch (p_size) {
|
||||
case 1: {
|
||||
ret = p_file->get_8();
|
||||
} break;
|
||||
case 2: {
|
||||
ret = BSWAP16(p_file->get_16());
|
||||
} break;
|
||||
case 3: {
|
||||
ret = BSWAP32(p_file->get_32() & 0x00FFFFFF);
|
||||
} break;
|
||||
case 4: {
|
||||
ret = BSWAP32(p_file->get_32());
|
||||
} break;
|
||||
case 5: {
|
||||
ret = BSWAP64(p_file->get_64() & 0x000000FFFFFFFFFF);
|
||||
} break;
|
||||
case 6: {
|
||||
ret = BSWAP64(p_file->get_64() & 0x0000FFFFFFFFFFFF);
|
||||
} break;
|
||||
case 7: {
|
||||
ret = BSWAP64(p_file->get_64() & 0x00FFFFFFFFFFFFFF);
|
||||
} break;
|
||||
case 8: {
|
||||
ret = BSWAP64(p_file->get_64());
|
||||
} break;
|
||||
default: {
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
p_file->seek(pos + p_size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ref<PListNode> PList::read_bplist_obj(Ref<FileAccess> p_file, uint64_t p_offset_idx) {
|
||||
Ref<PListNode> node;
|
||||
node.instantiate();
|
||||
|
||||
uint64_t ot_off = trailer.offset_table_start + p_offset_idx * trailer.offset_size;
|
||||
p_file->seek(ot_off);
|
||||
uint64_t marker_off = read_bplist_var_size_int(p_file, trailer.offset_size);
|
||||
ERR_FAIL_COND_V_MSG(marker_off == 0, Ref<PListNode>(), "Invalid marker size.");
|
||||
|
||||
p_file->seek(marker_off);
|
||||
uint8_t marker = p_file->get_8();
|
||||
uint8_t marker_type = marker & 0xF0;
|
||||
uint64_t marker_size = marker & 0x0F;
|
||||
|
||||
switch (marker_type) {
|
||||
case 0x00: {
|
||||
if (marker_size == 0x00) {
|
||||
node->data_type = PL_NODE_TYPE_NIL;
|
||||
} else if (marker_size == 0x08) {
|
||||
node->data_type = PL_NODE_TYPE_BOOLEAN;
|
||||
node->data_bool = false;
|
||||
} else if (marker_size == 0x09) {
|
||||
node->data_type = PL_NODE_TYPE_BOOLEAN;
|
||||
node->data_bool = true;
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(Ref<PListNode>(), "Invalid nil/bool marker value.");
|
||||
}
|
||||
} break;
|
||||
case 0x10: {
|
||||
node->data_type = PL_NODE_TYPE_INTEGER;
|
||||
node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, pow(2, marker_size)));
|
||||
} break;
|
||||
case 0x20: {
|
||||
node->data_type = PL_NODE_TYPE_REAL;
|
||||
node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, pow(2, marker_size)));
|
||||
} break;
|
||||
case 0x30: {
|
||||
node->data_type = PL_NODE_TYPE_DATE;
|
||||
node->data_int = BSWAP64(p_file->get_64());
|
||||
node->data_string = Time::get_singleton()->get_datetime_string_from_unix_time(node->data_real + 978307200.0).utf8();
|
||||
} break;
|
||||
case 0x40: {
|
||||
if (marker_size == 0x0F) {
|
||||
uint8_t ext = p_file->get_8() & 0xF;
|
||||
marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
|
||||
}
|
||||
node->data_type = PL_NODE_TYPE_DATA;
|
||||
PackedByteArray buf;
|
||||
buf.resize(marker_size + 1);
|
||||
p_file->get_buffer(reinterpret_cast<uint8_t *>(buf.ptrw()), marker_size);
|
||||
node->data_string = CryptoCore::b64_encode_str(buf.ptr(), buf.size()).utf8();
|
||||
} break;
|
||||
case 0x50: {
|
||||
if (marker_size == 0x0F) {
|
||||
uint8_t ext = p_file->get_8() & 0xF;
|
||||
marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
|
||||
}
|
||||
node->data_type = PL_NODE_TYPE_STRING;
|
||||
node->data_string.resize(marker_size + 1);
|
||||
p_file->get_buffer(reinterpret_cast<uint8_t *>(node->data_string.ptrw()), marker_size);
|
||||
} break;
|
||||
case 0x60: {
|
||||
if (marker_size == 0x0F) {
|
||||
uint8_t ext = p_file->get_8() & 0xF;
|
||||
marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
|
||||
}
|
||||
Char16String cs16;
|
||||
cs16.resize(marker_size + 1);
|
||||
for (uint64_t i = 0; i < marker_size; i++) {
|
||||
cs16[i] = BSWAP16(p_file->get_16());
|
||||
}
|
||||
node->data_type = PL_NODE_TYPE_STRING;
|
||||
node->data_string = String::utf16(cs16.ptr(), cs16.length()).utf8();
|
||||
} break;
|
||||
case 0x80: {
|
||||
node->data_type = PL_NODE_TYPE_INTEGER;
|
||||
node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, marker_size + 1));
|
||||
} break;
|
||||
case 0xA0:
|
||||
case 0xC0: {
|
||||
if (marker_size == 0x0F) {
|
||||
uint8_t ext = p_file->get_8() & 0xF;
|
||||
marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
|
||||
}
|
||||
uint64_t pos = p_file->get_position();
|
||||
|
||||
node->data_type = PL_NODE_TYPE_ARRAY;
|
||||
for (uint64_t i = 0; i < marker_size; i++) {
|
||||
p_file->seek(pos + trailer.ref_size * i);
|
||||
uint64_t ref = read_bplist_var_size_int(p_file, trailer.ref_size);
|
||||
|
||||
Ref<PListNode> element = read_bplist_obj(p_file, ref);
|
||||
ERR_FAIL_COND_V(element.is_null(), Ref<PListNode>());
|
||||
node->data_array.push_back(element);
|
||||
}
|
||||
} break;
|
||||
case 0xD0: {
|
||||
if (marker_size == 0x0F) {
|
||||
uint8_t ext = p_file->get_8() & 0xF;
|
||||
marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
|
||||
}
|
||||
uint64_t pos = p_file->get_position();
|
||||
|
||||
node->data_type = PL_NODE_TYPE_DICT;
|
||||
for (uint64_t i = 0; i < marker_size; i++) {
|
||||
p_file->seek(pos + trailer.ref_size * i);
|
||||
uint64_t key_ref = read_bplist_var_size_int(p_file, trailer.ref_size);
|
||||
|
||||
p_file->seek(pos + trailer.ref_size * (i + marker_size));
|
||||
uint64_t obj_ref = read_bplist_var_size_int(p_file, trailer.ref_size);
|
||||
|
||||
Ref<PListNode> element_key = read_bplist_obj(p_file, key_ref);
|
||||
ERR_FAIL_COND_V(element_key.is_null() || element_key->data_type != PL_NODE_TYPE_STRING, Ref<PListNode>());
|
||||
Ref<PListNode> element = read_bplist_obj(p_file, obj_ref);
|
||||
ERR_FAIL_COND_V(element.is_null(), Ref<PListNode>());
|
||||
node->data_dict[String::utf8(element_key->data_string.ptr(), element_key->data_string.length())] = element;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(Ref<PListNode>(), "Invalid marker type.");
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
bool PList::load_file(const String &p_filename) {
|
||||
root = Ref<PListNode>();
|
||||
|
||||
Ref<FileAccess> fb = FileAccess::open(p_filename, FileAccess::READ);
|
||||
if (fb.is_null()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char magic[8];
|
||||
fb->get_buffer(magic, 8);
|
||||
|
||||
if (String((const char *)magic, 8) == "bplist00") {
|
||||
fb->seek_end(-26);
|
||||
trailer.offset_size = fb->get_8();
|
||||
trailer.ref_size = fb->get_8();
|
||||
trailer.object_num = BSWAP64(fb->get_64());
|
||||
trailer.root_offset_idx = BSWAP64(fb->get_64());
|
||||
trailer.offset_table_start = BSWAP64(fb->get_64());
|
||||
root = read_bplist_obj(fb, trailer.root_offset_idx);
|
||||
|
||||
return root.is_valid();
|
||||
} else {
|
||||
// Load text plist.
|
||||
Error err;
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_bytes(p_filename, &err);
|
||||
ERR_FAIL_COND_V(err != OK, false);
|
||||
|
||||
String ret;
|
||||
ret.parse_utf8((const char *)array.ptr(), array.size());
|
||||
String err_str;
|
||||
bool ok = load_string(ret, err_str);
|
||||
ERR_FAIL_COND_V_MSG(!ok, false, "PList: " + err_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool PList::load_string(const String &p_string, String &r_err_out) {
|
||||
root = Ref<PListNode>();
|
||||
|
||||
int pos = 0;
|
||||
bool in_plist = false;
|
||||
bool done_plist = false;
|
||||
List<Ref<PListNode>> stack;
|
||||
String key;
|
||||
while (pos >= 0) {
|
||||
int open_token_s = p_string.find("<", pos);
|
||||
if (open_token_s == -1) {
|
||||
r_err_out = "Unexpected end of data. No tags found.";
|
||||
return false;
|
||||
}
|
||||
int open_token_e = p_string.find(">", open_token_s);
|
||||
pos = open_token_e;
|
||||
|
||||
String token = p_string.substr(open_token_s + 1, open_token_e - open_token_s - 1);
|
||||
if (token.is_empty()) {
|
||||
r_err_out = "Invalid token name.";
|
||||
return false;
|
||||
}
|
||||
String value;
|
||||
if (token[0] == '?' || token[0] == '!') { // Skip <?xml ... ?> and <!DOCTYPE ... >
|
||||
int end_token_e = p_string.find(">", open_token_s);
|
||||
pos = end_token_e;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.find("plist", 0) == 0) {
|
||||
in_plist = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "/plist") {
|
||||
done_plist = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!in_plist) {
|
||||
r_err_out = "Node outside of <plist> tag.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (token == "dict") {
|
||||
if (!stack.is_empty()) {
|
||||
// Add subnode end enter it.
|
||||
Ref<PListNode> dict = PListNode::new_dict();
|
||||
dict->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
|
||||
if (!stack.back()->get()->push_subnode(dict, key)) {
|
||||
r_err_out = "Can't push subnode, invalid parent type.";
|
||||
return false;
|
||||
}
|
||||
stack.push_back(dict);
|
||||
} else {
|
||||
// Add root node.
|
||||
if (!root.is_null()) {
|
||||
r_err_out = "Root node already set.";
|
||||
return false;
|
||||
}
|
||||
Ref<PListNode> dict = PListNode::new_dict();
|
||||
stack.push_back(dict);
|
||||
root = dict;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "/dict") {
|
||||
// Exit current dict.
|
||||
if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_DICT) {
|
||||
r_err_out = "Mismatched </dict> tag.";
|
||||
return false;
|
||||
}
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "array") {
|
||||
if (!stack.is_empty()) {
|
||||
// Add subnode end enter it.
|
||||
Ref<PListNode> arr = PListNode::new_array();
|
||||
if (!stack.back()->get()->push_subnode(arr, key)) {
|
||||
r_err_out = "Can't push subnode, invalid parent type.";
|
||||
return false;
|
||||
}
|
||||
stack.push_back(arr);
|
||||
} else {
|
||||
// Add root node.
|
||||
if (!root.is_null()) {
|
||||
r_err_out = "Root node already set.";
|
||||
return false;
|
||||
}
|
||||
Ref<PListNode> arr = PListNode::new_array();
|
||||
stack.push_back(arr);
|
||||
root = arr;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "/array") {
|
||||
// Exit current array.
|
||||
if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_ARRAY) {
|
||||
r_err_out = "Mismatched </array> tag.";
|
||||
return false;
|
||||
}
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token[token.length() - 1] == '/') {
|
||||
token = token.substr(0, token.length() - 1);
|
||||
} else {
|
||||
int end_token_s = p_string.find("</", pos);
|
||||
if (end_token_s == -1) {
|
||||
r_err_out = vformat("Mismatched <%s> tag.", token);
|
||||
return false;
|
||||
}
|
||||
int end_token_e = p_string.find(">", end_token_s);
|
||||
pos = end_token_e;
|
||||
String end_token = p_string.substr(end_token_s + 2, end_token_e - end_token_s - 2);
|
||||
if (end_token != token) {
|
||||
r_err_out = vformat("Mismatched <%s> and <%s> token pair.", token, end_token);
|
||||
return false;
|
||||
}
|
||||
value = p_string.substr(open_token_e + 1, end_token_s - open_token_e - 1);
|
||||
}
|
||||
if (token == "key") {
|
||||
key = value;
|
||||
} else {
|
||||
Ref<PListNode> var = nullptr;
|
||||
if (token == "true") {
|
||||
var = PListNode::new_bool(true);
|
||||
} else if (token == "false") {
|
||||
var = PListNode::new_bool(false);
|
||||
} else if (token == "integer") {
|
||||
var = PListNode::new_int(value.to_int());
|
||||
} else if (token == "real") {
|
||||
var = PListNode::new_real(value.to_float());
|
||||
} else if (token == "string") {
|
||||
var = PListNode::new_string(value);
|
||||
} else if (token == "data") {
|
||||
var = PListNode::new_data(value);
|
||||
} else if (token == "date") {
|
||||
var = PListNode::new_date(value);
|
||||
} else {
|
||||
r_err_out = vformat("Invalid value type: %s.", token);
|
||||
return false;
|
||||
}
|
||||
if (stack.is_empty() || !stack.back()->get()->push_subnode(var, key)) {
|
||||
r_err_out = "Can't push subnode, invalid parent type.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stack.is_empty() || !done_plist) {
|
||||
r_err_out = "Unexpected end of data. Root node is not closed.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
PackedByteArray PList::save_asn1() const {
|
||||
if (root == nullptr) {
|
||||
ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node.");
|
||||
}
|
||||
size_t size = root->get_asn1_size(1);
|
||||
uint8_t len_octets = 0;
|
||||
if (size < 0x80) {
|
||||
len_octets = 1;
|
||||
} else {
|
||||
size = root->get_asn1_size(2);
|
||||
if (size < 0xFFFF) {
|
||||
len_octets = 2;
|
||||
} else {
|
||||
size = root->get_asn1_size(3);
|
||||
if (size < 0xFFFFFF) {
|
||||
len_octets = 3;
|
||||
} else {
|
||||
size = root->get_asn1_size(4);
|
||||
if (size < 0xFFFFFFFF) {
|
||||
len_octets = 4;
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(PackedByteArray(), "PList: Data is too big for ASN.1 serializer, should be < 4 GiB.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PackedByteArray ret;
|
||||
if (!root->store_asn1(ret, len_octets)) {
|
||||
ERR_FAIL_V_MSG(PackedByteArray(), "PList: ASN.1 serializer error.");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
String PList::save_text() const {
|
||||
if (root == nullptr) {
|
||||
ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node.");
|
||||
}
|
||||
|
||||
String ret;
|
||||
ret += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
ret += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
|
||||
ret += "<plist version=\"1.0\">\n";
|
||||
|
||||
root->store_text(ret, 0);
|
||||
|
||||
ret += "</plist>\n\n";
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ref<PListNode> PList::get_root() {
|
||||
return root;
|
||||
}
|
||||
128
engine/core/io/plist.h
Normal file
128
engine/core/io/plist.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/**************************************************************************/
|
||||
/* plist.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef PLIST_H
|
||||
#define PLIST_H
|
||||
|
||||
// Property list file format (application/x-plist) parser, property list ASN-1 serialization.
|
||||
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/os/time.h"
|
||||
|
||||
class PListNode;
|
||||
|
||||
class PList : public RefCounted {
|
||||
friend class PListNode;
|
||||
|
||||
public:
|
||||
enum PLNodeType {
|
||||
PL_NODE_TYPE_NIL,
|
||||
PL_NODE_TYPE_STRING,
|
||||
PL_NODE_TYPE_ARRAY,
|
||||
PL_NODE_TYPE_DICT,
|
||||
PL_NODE_TYPE_BOOLEAN,
|
||||
PL_NODE_TYPE_INTEGER,
|
||||
PL_NODE_TYPE_REAL,
|
||||
PL_NODE_TYPE_DATA,
|
||||
PL_NODE_TYPE_DATE,
|
||||
};
|
||||
|
||||
private:
|
||||
struct PListTrailer {
|
||||
uint8_t offset_size;
|
||||
uint8_t ref_size;
|
||||
uint64_t object_num;
|
||||
uint64_t root_offset_idx;
|
||||
uint64_t offset_table_start;
|
||||
};
|
||||
|
||||
PListTrailer trailer;
|
||||
Ref<PListNode> root;
|
||||
|
||||
uint64_t read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size);
|
||||
Ref<PListNode> read_bplist_obj(Ref<FileAccess> p_file, uint64_t p_offset_idx);
|
||||
|
||||
public:
|
||||
PList();
|
||||
PList(const String &p_string);
|
||||
|
||||
bool load_file(const String &p_filename);
|
||||
bool load_string(const String &p_string, String &r_err_out);
|
||||
|
||||
PackedByteArray save_asn1() const;
|
||||
String save_text() const;
|
||||
|
||||
Ref<PListNode> get_root();
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
class PListNode : public RefCounted {
|
||||
static int _asn1_size_len(uint8_t p_len_octets);
|
||||
|
||||
public:
|
||||
PList::PLNodeType data_type = PList::PLNodeType::PL_NODE_TYPE_NIL;
|
||||
|
||||
CharString data_string;
|
||||
Vector<Ref<PListNode>> data_array;
|
||||
HashMap<String, Ref<PListNode>> data_dict;
|
||||
union {
|
||||
int64_t data_int;
|
||||
bool data_bool;
|
||||
double data_real;
|
||||
};
|
||||
|
||||
PList::PLNodeType get_type() const;
|
||||
Variant get_value() const;
|
||||
|
||||
static Ref<PListNode> new_node(const Variant &p_value);
|
||||
static Ref<PListNode> new_array();
|
||||
static Ref<PListNode> new_dict();
|
||||
static Ref<PListNode> new_string(const String &p_string);
|
||||
static Ref<PListNode> new_data(const String &p_string);
|
||||
static Ref<PListNode> new_date(const String &p_string);
|
||||
static Ref<PListNode> new_bool(bool p_bool);
|
||||
static Ref<PListNode> new_int(int64_t p_int);
|
||||
static Ref<PListNode> new_real(double p_real);
|
||||
|
||||
bool push_subnode(const Ref<PListNode> &p_node, const String &p_key = "");
|
||||
|
||||
size_t get_asn1_size(uint8_t p_len_octets) const;
|
||||
|
||||
void store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const;
|
||||
bool store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const;
|
||||
void store_text(String &p_stream, uint8_t p_indent) const;
|
||||
|
||||
PListNode() {}
|
||||
~PListNode() {}
|
||||
};
|
||||
|
||||
#endif // PLIST_H
|
||||
329
engine/core/io/remote_filesystem_client.cpp
Normal file
329
engine/core/io/remote_filesystem_client.cpp
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
/**************************************************************************/
|
||||
/* remote_filesystem_client.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 "remote_filesystem_client.h"
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
#include "core/string/string_builder.h"
|
||||
|
||||
#define FILESYSTEM_CACHE_VERSION 1
|
||||
#define FILESYSTEM_PROTOCOL_VERSION 1
|
||||
#define PASSWORD_LENGTH 32
|
||||
|
||||
#define FILES_SUBFOLDER "remote_filesystem_files"
|
||||
#define FILES_CACHE_FILE "remote_filesystem.cache"
|
||||
|
||||
Vector<RemoteFilesystemClient::FileCache> RemoteFilesystemClient::_load_cache_file() {
|
||||
Ref<FileAccess> fa = FileAccess::open(cache_path.path_join(FILES_CACHE_FILE), FileAccess::READ);
|
||||
if (!fa.is_valid()) {
|
||||
return Vector<FileCache>(); // No cache, return empty
|
||||
}
|
||||
|
||||
int version = fa->get_line().to_int();
|
||||
if (version != FILESYSTEM_CACHE_VERSION) {
|
||||
return Vector<FileCache>(); // Version mismatch, ignore everything.
|
||||
}
|
||||
|
||||
String file_path = cache_path.path_join(FILES_SUBFOLDER);
|
||||
|
||||
Vector<FileCache> file_cache;
|
||||
|
||||
while (!fa->eof_reached()) {
|
||||
String l = fa->get_line();
|
||||
Vector<String> fields = l.split("::");
|
||||
if (fields.size() != 3) {
|
||||
break;
|
||||
}
|
||||
FileCache fc;
|
||||
fc.path = fields[0];
|
||||
fc.server_modified_time = fields[1].to_int();
|
||||
fc.modified_time = fields[2].to_int();
|
||||
|
||||
String full_path = file_path.path_join(fc.path);
|
||||
if (!FileAccess::exists(full_path)) {
|
||||
continue; // File is gone.
|
||||
}
|
||||
|
||||
if (FileAccess::get_modified_time(full_path) != fc.modified_time) {
|
||||
DirAccess::remove_absolute(full_path); // Take the chance to remove this file and assume we no longer have it.
|
||||
continue;
|
||||
}
|
||||
|
||||
file_cache.push_back(fc);
|
||||
}
|
||||
|
||||
return file_cache;
|
||||
}
|
||||
|
||||
Error RemoteFilesystemClient::_store_file(const String &p_path, const LocalVector<uint8_t> &p_file, uint64_t &modified_time) {
|
||||
modified_time = 0;
|
||||
String full_path = cache_path.path_join(FILES_SUBFOLDER).path_join(p_path);
|
||||
String base_file_dir = full_path.get_base_dir();
|
||||
|
||||
if (!validated_directories.has(base_file_dir)) {
|
||||
// Verify that path exists before writing file, but only verify once for performance.
|
||||
DirAccess::make_dir_recursive_absolute(base_file_dir);
|
||||
validated_directories.insert(base_file_dir);
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open file for writing to remote filesystem cache: " + p_path);
|
||||
f->store_buffer(p_file.ptr(), p_file.size());
|
||||
Error err = f->get_error();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
f.unref(); // Unref to ensure file is not locked and modified time can be obtained.
|
||||
|
||||
modified_time = FileAccess::get_modified_time(full_path);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error RemoteFilesystemClient::_remove_file(const String &p_path) {
|
||||
return DirAccess::remove_absolute(cache_path.path_join(FILES_SUBFOLDER).path_join(p_path));
|
||||
}
|
||||
Error RemoteFilesystemClient::_store_cache_file(const Vector<FileCache> &p_cache) {
|
||||
String full_path = cache_path.path_join(FILES_CACHE_FILE);
|
||||
String base_file_dir = full_path.get_base_dir();
|
||||
Error err = DirAccess::make_dir_recursive_absolute(base_file_dir);
|
||||
ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, "Unable to create base directory to store cache file: " + base_file_dir);
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open the remote cache file for writing: " + full_path);
|
||||
f->store_line(itos(FILESYSTEM_CACHE_VERSION));
|
||||
for (int i = 0; i < p_cache.size(); i++) {
|
||||
String l = p_cache[i].path + "::" + itos(p_cache[i].server_modified_time) + "::" + itos(p_cache[i].modified_time);
|
||||
f->store_line(l);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error RemoteFilesystemClient::synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path) {
|
||||
Error err = _synchronize_with_server(p_host, p_port, p_password, r_cache_path);
|
||||
// Ensure no memory is kept
|
||||
validated_directories.reset();
|
||||
cache_path = String();
|
||||
return err;
|
||||
}
|
||||
|
||||
void RemoteFilesystemClient::_update_cache_path(String &r_cache_path) {
|
||||
r_cache_path = cache_path.path_join(FILES_SUBFOLDER);
|
||||
}
|
||||
|
||||
Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path) {
|
||||
cache_path = r_cache_path;
|
||||
{
|
||||
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
dir->change_dir(cache_path);
|
||||
cache_path = dir->get_current_dir();
|
||||
}
|
||||
|
||||
Ref<StreamPeerTCP> tcp_client;
|
||||
tcp_client.instantiate();
|
||||
|
||||
IPAddress ip = p_host.is_valid_ip_address() ? IPAddress(p_host) : IP::get_singleton()->resolve_hostname(p_host);
|
||||
ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, "Unable to resolve remote filesystem server hostname: " + p_host);
|
||||
print_verbose(vformat("Remote Filesystem: Connecting to host %s, port %d.", ip, p_port));
|
||||
Error err = tcp_client->connect_to_host(ip, p_port);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to open connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
|
||||
|
||||
while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
|
||||
tcp_client->poll();
|
||||
OS::get_singleton()->delay_usec(100);
|
||||
}
|
||||
|
||||
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
|
||||
ERR_FAIL_V_MSG(ERR_CANT_CONNECT, "Connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
|
||||
}
|
||||
|
||||
// Connection OK, now send the current file state.
|
||||
print_verbose("Remote Filesystem: Connection OK.");
|
||||
|
||||
// Header (GRFS) - Godot Remote File System
|
||||
print_verbose("Remote Filesystem: Sending header");
|
||||
tcp_client->put_u8('G');
|
||||
tcp_client->put_u8('R');
|
||||
tcp_client->put_u8('F');
|
||||
tcp_client->put_u8('S');
|
||||
// Protocol version
|
||||
tcp_client->put_32(FILESYSTEM_PROTOCOL_VERSION);
|
||||
print_verbose("Remote Filesystem: Sending password");
|
||||
uint8_t password[PASSWORD_LENGTH]; // Send fixed size password, since it's easier and safe to validate.
|
||||
for (int i = 0; i < PASSWORD_LENGTH; i++) {
|
||||
if (i < p_password.length()) {
|
||||
password[i] = p_password[i];
|
||||
} else {
|
||||
password[i] = 0;
|
||||
}
|
||||
}
|
||||
tcp_client->put_data(password, PASSWORD_LENGTH);
|
||||
print_verbose("Remote Filesystem: Tags.");
|
||||
Vector<String> tags;
|
||||
{
|
||||
tags.push_back(OS::get_singleton()->get_identifier());
|
||||
switch (OS::get_singleton()->get_preferred_texture_format()) {
|
||||
case OS::PREFERRED_TEXTURE_FORMAT_S3TC_BPTC: {
|
||||
tags.push_back("bptc");
|
||||
tags.push_back("s3tc");
|
||||
} break;
|
||||
case OS::PREFERRED_TEXTURE_FORMAT_ETC2_ASTC: {
|
||||
tags.push_back("etc2");
|
||||
tags.push_back("astc");
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
tcp_client->put_32(tags.size());
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
tcp_client->put_utf8_string(tags[i]);
|
||||
}
|
||||
// Size of compressed list of files
|
||||
print_verbose("Remote Filesystem: Sending file list");
|
||||
|
||||
Vector<FileCache> file_cache = _load_cache_file();
|
||||
|
||||
// Encode file cache to send it via network.
|
||||
Vector<uint8_t> file_cache_buffer;
|
||||
if (file_cache.size()) {
|
||||
StringBuilder sbuild;
|
||||
for (int i = 0; i < file_cache.size(); i++) {
|
||||
sbuild.append(file_cache[i].path);
|
||||
sbuild.append("::");
|
||||
sbuild.append(itos(file_cache[i].server_modified_time));
|
||||
sbuild.append("\n");
|
||||
}
|
||||
String s = sbuild.as_string();
|
||||
CharString cs = s.utf8();
|
||||
file_cache_buffer.resize(Compression::get_max_compressed_buffer_size(cs.length(), Compression::MODE_ZSTD));
|
||||
int res_len = Compression::compress(file_cache_buffer.ptrw(), (const uint8_t *)cs.ptr(), cs.length(), Compression::MODE_ZSTD);
|
||||
file_cache_buffer.resize(res_len);
|
||||
|
||||
tcp_client->put_32(cs.length()); // Size of buffer uncompressed
|
||||
tcp_client->put_32(file_cache_buffer.size()); // Size of buffer compressed
|
||||
tcp_client->put_data(file_cache_buffer.ptr(), file_cache_buffer.size()); // Buffer
|
||||
} else {
|
||||
tcp_client->put_32(0); // No file cache buffer
|
||||
}
|
||||
|
||||
tcp_client->poll();
|
||||
ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected after sending header.");
|
||||
|
||||
uint32_t file_count = tcp_client->get_32();
|
||||
|
||||
ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected while waiting for file list");
|
||||
|
||||
LocalVector<uint8_t> file_buffer;
|
||||
|
||||
Vector<FileCache> temp_file_cache;
|
||||
|
||||
HashSet<String> files_processed;
|
||||
for (uint32_t i = 0; i < file_count; i++) {
|
||||
String file = tcp_client->get_utf8_string();
|
||||
ERR_FAIL_COND_V_MSG(file == String(), ERR_CONNECTION_ERROR, "Invalid file name received from remote filesystem.");
|
||||
uint64_t server_modified_time = tcp_client->get_u64();
|
||||
ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected while waiting for file info.");
|
||||
|
||||
FileCache fc;
|
||||
fc.path = file;
|
||||
fc.server_modified_time = server_modified_time;
|
||||
temp_file_cache.push_back(fc);
|
||||
|
||||
files_processed.insert(file);
|
||||
}
|
||||
|
||||
Vector<FileCache> new_file_cache;
|
||||
|
||||
// Get the actual files. As a robustness measure, if the connection is interrupted here, any file not yet received will be considered removed.
|
||||
// Since the file changed anyway, this makes it the easiest way to keep robustness.
|
||||
|
||||
bool server_disconnected = false;
|
||||
for (uint32_t i = 0; i < file_count; i++) {
|
||||
String file = temp_file_cache[i].path;
|
||||
|
||||
if (temp_file_cache[i].server_modified_time == 0 || server_disconnected) {
|
||||
// File was removed, or server disconnected before transferring it. Since it's no longer valid, remove anyway.
|
||||
_remove_file(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t file_size = tcp_client->get_u64();
|
||||
file_buffer.resize(file_size);
|
||||
|
||||
err = tcp_client->get_data(file_buffer.ptr(), file_size);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Error retrieving file from remote filesystem: " + file);
|
||||
server_disconnected = true;
|
||||
}
|
||||
|
||||
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
|
||||
// Early disconnect, stop accepting files.
|
||||
server_disconnected = true;
|
||||
}
|
||||
|
||||
if (server_disconnected) {
|
||||
// No more server, transfer is invalid, remove this file.
|
||||
_remove_file(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t modified_time = 0;
|
||||
err = _store_file(file, file_buffer, modified_time);
|
||||
if (err != OK) {
|
||||
server_disconnected = true;
|
||||
continue;
|
||||
}
|
||||
FileCache fc = temp_file_cache[i];
|
||||
fc.modified_time = modified_time;
|
||||
new_file_cache.push_back(fc);
|
||||
}
|
||||
|
||||
print_verbose("Remote Filesystem: Updating the cache file.");
|
||||
|
||||
// Go through the list of local files read initially (file_cache) and see which ones are
|
||||
// unchanged (not sent again from the server).
|
||||
// These need to be re-saved in the new list (new_file_cache).
|
||||
|
||||
for (int i = 0; i < file_cache.size(); i++) {
|
||||
if (files_processed.has(file_cache[i].path)) {
|
||||
continue; // This was either added or removed, so skip.
|
||||
}
|
||||
new_file_cache.push_back(file_cache[i]);
|
||||
}
|
||||
|
||||
err = _store_cache_file(new_file_cache);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, "Error writing the remote filesystem file cache.");
|
||||
|
||||
print_verbose("Remote Filesystem: Update success.");
|
||||
|
||||
_update_cache_path(r_cache_path);
|
||||
return OK;
|
||||
}
|
||||
65
engine/core/io/remote_filesystem_client.h
Normal file
65
engine/core/io/remote_filesystem_client.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**************************************************************************/
|
||||
/* remote_filesystem_client.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef REMOTE_FILESYSTEM_CLIENT_H
|
||||
#define REMOTE_FILESYSTEM_CLIENT_H
|
||||
|
||||
#include "core/io/ip_address.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
class RemoteFilesystemClient {
|
||||
String cache_path;
|
||||
HashSet<String> validated_directories;
|
||||
|
||||
protected:
|
||||
String _get_cache_path() { return cache_path; }
|
||||
struct FileCache {
|
||||
String path; // Local path (as in "folder/to/file.png")
|
||||
uint64_t server_modified_time = 0; // MD5 checksum.
|
||||
uint64_t modified_time = 0;
|
||||
};
|
||||
virtual bool _is_configured() { return !cache_path.is_empty(); }
|
||||
// Can be re-implemented per platform. If so, feel free to ignore get_cache_path()
|
||||
virtual Vector<FileCache> _load_cache_file();
|
||||
virtual Error _store_file(const String &p_path, const LocalVector<uint8_t> &p_file, uint64_t &modified_time);
|
||||
virtual Error _remove_file(const String &p_path);
|
||||
virtual Error _store_cache_file(const Vector<FileCache> &p_cache);
|
||||
virtual Error _synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path);
|
||||
|
||||
virtual void _update_cache_path(String &r_cache_path);
|
||||
|
||||
public:
|
||||
Error synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path);
|
||||
virtual ~RemoteFilesystemClient() {}
|
||||
};
|
||||
|
||||
#endif // REMOTE_FILESYSTEM_CLIENT_H
|
||||
685
engine/core/io/resource.cpp
Normal file
685
engine/core/io/resource.cpp
Normal file
|
|
@ -0,0 +1,685 @@
|
|||
/**************************************************************************/
|
||||
/* resource.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 "resource.h"
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/os.h"
|
||||
#include "scene/main/node.h" //only so casting works
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void Resource::emit_changed() {
|
||||
if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
|
||||
// Let the connection happen on the call queue, later, since signals are not thread-safe.
|
||||
call_deferred("emit_signal", CoreStringName(changed));
|
||||
} else {
|
||||
emit_signal(CoreStringName(changed));
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::_resource_path_changed() {
|
||||
}
|
||||
|
||||
void Resource::set_path(const String &p_path, bool p_take_over) {
|
||||
if (path_cache == p_path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_path.is_empty()) {
|
||||
p_take_over = false; // Can't take over an empty path
|
||||
}
|
||||
|
||||
ResourceCache::lock.lock();
|
||||
|
||||
if (!path_cache.is_empty()) {
|
||||
ResourceCache::resources.erase(path_cache);
|
||||
}
|
||||
|
||||
path_cache = "";
|
||||
|
||||
Ref<Resource> existing = ResourceCache::get_ref(p_path);
|
||||
|
||||
if (existing.is_valid()) {
|
||||
if (p_take_over) {
|
||||
existing->path_cache = String();
|
||||
ResourceCache::resources.erase(p_path);
|
||||
} else {
|
||||
ResourceCache::lock.unlock();
|
||||
ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
|
||||
}
|
||||
}
|
||||
|
||||
path_cache = p_path;
|
||||
|
||||
if (!path_cache.is_empty()) {
|
||||
ResourceCache::resources[path_cache] = this;
|
||||
}
|
||||
ResourceCache::lock.unlock();
|
||||
|
||||
_resource_path_changed();
|
||||
}
|
||||
|
||||
String Resource::get_path() const {
|
||||
return path_cache;
|
||||
}
|
||||
|
||||
void Resource::set_path_cache(const String &p_path) {
|
||||
path_cache = p_path;
|
||||
}
|
||||
|
||||
String Resource::generate_scene_unique_id() {
|
||||
// Generate a unique enough hash, but still user-readable.
|
||||
// If it's not unique it does not matter because the saver will try again.
|
||||
OS::DateTime dt = OS::get_singleton()->get_datetime();
|
||||
uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec());
|
||||
hash = hash_murmur3_one_32(dt.year, hash);
|
||||
hash = hash_murmur3_one_32(dt.month, hash);
|
||||
hash = hash_murmur3_one_32(dt.day, hash);
|
||||
hash = hash_murmur3_one_32(dt.hour, hash);
|
||||
hash = hash_murmur3_one_32(dt.minute, hash);
|
||||
hash = hash_murmur3_one_32(dt.second, hash);
|
||||
hash = hash_murmur3_one_32(Math::rand(), hash);
|
||||
|
||||
static constexpr uint32_t characters = 5;
|
||||
static constexpr uint32_t char_count = ('z' - 'a');
|
||||
static constexpr uint32_t base = char_count + ('9' - '0');
|
||||
String id;
|
||||
for (uint32_t i = 0; i < characters; i++) {
|
||||
uint32_t c = hash % base;
|
||||
if (c < char_count) {
|
||||
id += String::chr('a' + c);
|
||||
} else {
|
||||
id += String::chr('0' + (c - char_count));
|
||||
}
|
||||
hash /= base;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Resource::set_scene_unique_id(const String &p_id) {
|
||||
bool is_valid = true;
|
||||
for (int i = 0; i < p_id.length(); i++) {
|
||||
if (!is_ascii_identifier_char(p_id[i])) {
|
||||
is_valid = false;
|
||||
scene_unique_id = Resource::generate_scene_unique_id();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_MSG(!is_valid, "The scene unique ID must contain only letters, numbers, and underscores.");
|
||||
scene_unique_id = p_id;
|
||||
}
|
||||
|
||||
String Resource::get_scene_unique_id() const {
|
||||
return scene_unique_id;
|
||||
}
|
||||
|
||||
void Resource::set_name(const String &p_name) {
|
||||
name = p_name;
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
String Resource::get_name() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
void Resource::update_configuration_warning() {
|
||||
if (_update_configuration_warning) {
|
||||
_update_configuration_warning();
|
||||
}
|
||||
}
|
||||
|
||||
bool Resource::editor_can_reload_from_file() {
|
||||
return true; //by default yes
|
||||
}
|
||||
|
||||
void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) {
|
||||
if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
|
||||
// Let the check and connection happen on the call queue, later, since signals are not thread-safe.
|
||||
callable_mp(this, &Resource::connect_changed).call_deferred(p_callable, p_flags);
|
||||
return;
|
||||
}
|
||||
if (!is_connected(CoreStringName(changed), p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) {
|
||||
connect(CoreStringName(changed), p_callable, p_flags);
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::disconnect_changed(const Callable &p_callable) {
|
||||
if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
|
||||
// Let the check and disconnection happen on the call queue, later, since signals are not thread-safe.
|
||||
callable_mp(this, &Resource::disconnect_changed).call_deferred(p_callable);
|
||||
return;
|
||||
}
|
||||
if (is_connected(CoreStringName(changed), p_callable)) {
|
||||
disconnect(CoreStringName(changed), p_callable);
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::reset_state() {
|
||||
}
|
||||
|
||||
Error Resource::copy_from(const Ref<Resource> &p_resource) {
|
||||
ERR_FAIL_COND_V(p_resource.is_null(), ERR_INVALID_PARAMETER);
|
||||
if (get_class() != p_resource->get_class()) {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
reset_state(); // May want to reset state.
|
||||
|
||||
List<PropertyInfo> pi;
|
||||
p_resource->get_property_list(&pi);
|
||||
|
||||
for (const PropertyInfo &E : pi) {
|
||||
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
|
||||
continue;
|
||||
}
|
||||
if (E.name == "resource_path") {
|
||||
continue; //do not change path
|
||||
}
|
||||
|
||||
set(E.name, p_resource->get(E.name));
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void Resource::reload_from_file() {
|
||||
String path = get_path();
|
||||
if (!path.is_resource_file()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<Resource> s = ResourceLoader::load(ResourceLoader::path_remap(path), get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
|
||||
|
||||
if (!s.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
copy_from(s);
|
||||
}
|
||||
|
||||
void Resource::_dupe_sub_resources(Variant &r_variant, Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
|
||||
switch (r_variant.get_type()) {
|
||||
case Variant::ARRAY: {
|
||||
Array a = r_variant;
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
_dupe_sub_resources(a[i], p_for_scene, p_remap_cache);
|
||||
}
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
Dictionary d = r_variant;
|
||||
List<Variant> keys;
|
||||
d.get_key_list(&keys);
|
||||
for (Variant &k : keys) {
|
||||
if (k.get_type() == Variant::OBJECT) {
|
||||
// Replace in dictionary key.
|
||||
Ref<Resource> sr = k;
|
||||
if (sr.is_valid() && sr->is_local_to_scene()) {
|
||||
if (p_remap_cache.has(sr)) {
|
||||
d[p_remap_cache[sr]] = d[k];
|
||||
d.erase(k);
|
||||
} else {
|
||||
Ref<Resource> dupe = sr->duplicate_for_local_scene(p_for_scene, p_remap_cache);
|
||||
d[dupe] = d[k];
|
||||
d.erase(k);
|
||||
p_remap_cache[sr] = dupe;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_dupe_sub_resources(k, p_for_scene, p_remap_cache);
|
||||
}
|
||||
|
||||
_dupe_sub_resources(d[k], p_for_scene, p_remap_cache);
|
||||
}
|
||||
} break;
|
||||
case Variant::OBJECT: {
|
||||
Ref<Resource> sr = r_variant;
|
||||
if (sr.is_valid() && sr->is_local_to_scene()) {
|
||||
if (p_remap_cache.has(sr)) {
|
||||
r_variant = p_remap_cache[sr];
|
||||
} else {
|
||||
Ref<Resource> dupe = sr->duplicate_for_local_scene(p_for_scene, p_remap_cache);
|
||||
r_variant = dupe;
|
||||
p_remap_cache[sr] = dupe;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
|
||||
List<PropertyInfo> plist;
|
||||
get_property_list(&plist);
|
||||
|
||||
Ref<Resource> r = Object::cast_to<Resource>(ClassDB::instantiate(get_class()));
|
||||
ERR_FAIL_COND_V(r.is_null(), Ref<Resource>());
|
||||
|
||||
r->local_scene = p_for_scene;
|
||||
|
||||
for (const PropertyInfo &E : plist) {
|
||||
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
|
||||
continue;
|
||||
}
|
||||
Variant p = get(E.name).duplicate(true);
|
||||
|
||||
_dupe_sub_resources(p, p_for_scene, p_remap_cache);
|
||||
|
||||
r->set(E.name, p);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void Resource::_find_sub_resources(const Variant &p_variant, HashSet<Ref<Resource>> &p_resources_found) {
|
||||
switch (p_variant.get_type()) {
|
||||
case Variant::ARRAY: {
|
||||
Array a = p_variant;
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
_find_sub_resources(a[i], p_resources_found);
|
||||
}
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
Dictionary d = p_variant;
|
||||
List<Variant> keys;
|
||||
d.get_key_list(&keys);
|
||||
for (const Variant &k : keys) {
|
||||
_find_sub_resources(k, p_resources_found);
|
||||
_find_sub_resources(d[k], p_resources_found);
|
||||
}
|
||||
} break;
|
||||
case Variant::OBJECT: {
|
||||
Ref<Resource> r = p_variant;
|
||||
if (r.is_valid()) {
|
||||
p_resources_found.insert(r);
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
|
||||
List<PropertyInfo> plist;
|
||||
get_property_list(&plist);
|
||||
|
||||
reset_local_to_scene();
|
||||
local_scene = p_for_scene;
|
||||
|
||||
for (const PropertyInfo &E : plist) {
|
||||
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
|
||||
continue;
|
||||
}
|
||||
Variant p = get(E.name);
|
||||
|
||||
HashSet<Ref<Resource>> sub_resources;
|
||||
_find_sub_resources(p, sub_resources);
|
||||
|
||||
for (Ref<Resource> sr : sub_resources) {
|
||||
if (sr->is_local_to_scene()) {
|
||||
if (!p_remap_cache.has(sr)) {
|
||||
sr->configure_for_local_scene(p_for_scene, p_remap_cache);
|
||||
p_remap_cache[sr] = sr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Resource> Resource::duplicate(bool p_subresources) const {
|
||||
List<PropertyInfo> plist;
|
||||
get_property_list(&plist);
|
||||
|
||||
Ref<Resource> r = static_cast<Resource *>(ClassDB::instantiate(get_class()));
|
||||
ERR_FAIL_COND_V(r.is_null(), Ref<Resource>());
|
||||
|
||||
for (const PropertyInfo &E : plist) {
|
||||
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
|
||||
continue;
|
||||
}
|
||||
Variant p = get(E.name);
|
||||
|
||||
switch (p.get_type()) {
|
||||
case Variant::Type::DICTIONARY:
|
||||
case Variant::Type::ARRAY:
|
||||
case Variant::Type::PACKED_BYTE_ARRAY:
|
||||
case Variant::Type::PACKED_COLOR_ARRAY:
|
||||
case Variant::Type::PACKED_INT32_ARRAY:
|
||||
case Variant::Type::PACKED_INT64_ARRAY:
|
||||
case Variant::Type::PACKED_FLOAT32_ARRAY:
|
||||
case Variant::Type::PACKED_FLOAT64_ARRAY:
|
||||
case Variant::Type::PACKED_STRING_ARRAY:
|
||||
case Variant::Type::PACKED_VECTOR2_ARRAY:
|
||||
case Variant::Type::PACKED_VECTOR3_ARRAY:
|
||||
case Variant::Type::PACKED_VECTOR4_ARRAY: {
|
||||
r->set(E.name, p.duplicate(p_subresources));
|
||||
} break;
|
||||
|
||||
case Variant::Type::OBJECT: {
|
||||
if (!(E.usage & PROPERTY_USAGE_NEVER_DUPLICATE) && (p_subresources || (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE))) {
|
||||
Ref<Resource> sr = p;
|
||||
if (sr.is_valid()) {
|
||||
r->set(E.name, sr->duplicate(p_subresources));
|
||||
}
|
||||
} else {
|
||||
r->set(E.name, p);
|
||||
}
|
||||
} break;
|
||||
|
||||
default: {
|
||||
r->set(E.name, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void Resource::_set_path(const String &p_path) {
|
||||
set_path(p_path, false);
|
||||
}
|
||||
|
||||
void Resource::_take_over_path(const String &p_path) {
|
||||
set_path(p_path, true);
|
||||
}
|
||||
|
||||
RID Resource::get_rid() const {
|
||||
if (get_script_instance()) {
|
||||
Callable::CallError ce;
|
||||
RID ret = get_script_instance()->callp(SNAME("_get_rid"), nullptr, 0, ce);
|
||||
if (ce.error == Callable::CallError::CALL_OK && ret.is_valid()) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (_get_extension() && _get_extension()->get_rid) {
|
||||
RID ret = RID::from_uint64(_get_extension()->get_rid(_get_extension_instance()));
|
||||
if (ret.is_valid()) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return RID();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
uint32_t Resource::hash_edited_version_for_preview() const {
|
||||
uint32_t hash = hash_murmur3_one_32(get_edited_version());
|
||||
|
||||
List<PropertyInfo> plist;
|
||||
get_property_list(&plist);
|
||||
|
||||
for (const PropertyInfo &E : plist) {
|
||||
if (E.usage & PROPERTY_USAGE_STORAGE && E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_RESOURCE_TYPE) {
|
||||
Ref<Resource> res = get(E.name);
|
||||
if (res.is_valid()) {
|
||||
hash = hash_murmur3_one_32(res->hash_edited_version_for_preview(), hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Resource::set_local_to_scene(bool p_enable) {
|
||||
local_to_scene = p_enable;
|
||||
}
|
||||
|
||||
bool Resource::is_local_to_scene() const {
|
||||
return local_to_scene;
|
||||
}
|
||||
|
||||
Node *Resource::get_local_scene() const {
|
||||
if (local_scene) {
|
||||
return local_scene;
|
||||
}
|
||||
|
||||
if (_get_local_scene_func) {
|
||||
return _get_local_scene_func();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Resource::setup_local_to_scene() {
|
||||
emit_signal(SNAME("setup_local_to_scene_requested"));
|
||||
GDVIRTUAL_CALL(_setup_local_to_scene);
|
||||
}
|
||||
|
||||
void Resource::reset_local_to_scene() {
|
||||
// Restores the state as if setup_local_to_scene() hadn't been called.
|
||||
}
|
||||
|
||||
Node *(*Resource::_get_local_scene_func)() = nullptr;
|
||||
void (*Resource::_update_configuration_warning)() = nullptr;
|
||||
|
||||
void Resource::set_as_translation_remapped(bool p_remapped) {
|
||||
if (remapped_list.in_list() == p_remapped) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceCache::lock.lock();
|
||||
|
||||
if (p_remapped) {
|
||||
ResourceLoader::remapped_list.add(&remapped_list);
|
||||
} else {
|
||||
ResourceLoader::remapped_list.remove(&remapped_list);
|
||||
}
|
||||
|
||||
ResourceCache::lock.unlock();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
//helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored
|
||||
void Resource::set_id_for_path(const String &p_path, const String &p_id) {
|
||||
if (p_id.is_empty()) {
|
||||
ResourceCache::path_cache_lock.write_lock();
|
||||
ResourceCache::resource_path_cache[p_path].erase(get_path());
|
||||
ResourceCache::path_cache_lock.write_unlock();
|
||||
} else {
|
||||
ResourceCache::path_cache_lock.write_lock();
|
||||
ResourceCache::resource_path_cache[p_path][get_path()] = p_id;
|
||||
ResourceCache::path_cache_lock.write_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
String Resource::get_id_for_path(const String &p_path) const {
|
||||
ResourceCache::path_cache_lock.read_lock();
|
||||
if (ResourceCache::resource_path_cache[p_path].has(get_path())) {
|
||||
String result = ResourceCache::resource_path_cache[p_path][get_path()];
|
||||
ResourceCache::path_cache_lock.read_unlock();
|
||||
return result;
|
||||
} else {
|
||||
ResourceCache::path_cache_lock.read_unlock();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Resource::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_path", "path"), &Resource::_set_path);
|
||||
ClassDB::bind_method(D_METHOD("take_over_path", "path"), &Resource::_take_over_path);
|
||||
ClassDB::bind_method(D_METHOD("get_path"), &Resource::get_path);
|
||||
ClassDB::bind_method(D_METHOD("set_name", "name"), &Resource::set_name);
|
||||
ClassDB::bind_method(D_METHOD("get_name"), &Resource::get_name);
|
||||
ClassDB::bind_method(D_METHOD("get_rid"), &Resource::get_rid);
|
||||
ClassDB::bind_method(D_METHOD("set_local_to_scene", "enable"), &Resource::set_local_to_scene);
|
||||
ClassDB::bind_method(D_METHOD("is_local_to_scene"), &Resource::is_local_to_scene);
|
||||
ClassDB::bind_method(D_METHOD("get_local_scene"), &Resource::get_local_scene);
|
||||
ClassDB::bind_method(D_METHOD("setup_local_to_scene"), &Resource::setup_local_to_scene);
|
||||
|
||||
ClassDB::bind_static_method("Resource", D_METHOD("generate_scene_unique_id"), &Resource::generate_scene_unique_id);
|
||||
ClassDB::bind_method(D_METHOD("set_scene_unique_id", "id"), &Resource::set_scene_unique_id);
|
||||
ClassDB::bind_method(D_METHOD("get_scene_unique_id"), &Resource::get_scene_unique_id);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("emit_changed"), &Resource::emit_changed);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("duplicate", "subresources"), &Resource::duplicate, DEFVAL(false));
|
||||
ADD_SIGNAL(MethodInfo("changed"));
|
||||
ADD_SIGNAL(MethodInfo("setup_local_to_scene_requested"));
|
||||
|
||||
ADD_GROUP("Resource", "resource_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resource_local_to_scene"), "set_local_to_scene", "is_local_to_scene");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_path", "get_path");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_name"), "set_name", "get_name");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_scene_unique_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_unique_id", "get_scene_unique_id");
|
||||
|
||||
MethodInfo get_rid_bind("_get_rid");
|
||||
get_rid_bind.return_val.type = Variant::RID;
|
||||
|
||||
::ClassDB::add_virtual_method(get_class_static(), get_rid_bind, true, Vector<String>(), true);
|
||||
GDVIRTUAL_BIND(_setup_local_to_scene);
|
||||
}
|
||||
|
||||
Resource::Resource() :
|
||||
remapped_list(this) {}
|
||||
|
||||
Resource::~Resource() {
|
||||
if (unlikely(path_cache.is_empty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceCache::lock.lock();
|
||||
// Only unregister from the cache if this is the actual resource listed there.
|
||||
// (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.)
|
||||
HashMap<String, Resource *>::Iterator E = ResourceCache::resources.find(path_cache);
|
||||
if (likely(E && E->value == this)) {
|
||||
ResourceCache::resources.remove(E);
|
||||
}
|
||||
ResourceCache::lock.unlock();
|
||||
}
|
||||
|
||||
HashMap<String, Resource *> ResourceCache::resources;
|
||||
#ifdef TOOLS_ENABLED
|
||||
HashMap<String, HashMap<String, String>> ResourceCache::resource_path_cache;
|
||||
#endif
|
||||
|
||||
Mutex ResourceCache::lock;
|
||||
#ifdef TOOLS_ENABLED
|
||||
RWLock ResourceCache::path_cache_lock;
|
||||
#endif
|
||||
|
||||
void ResourceCache::clear() {
|
||||
if (!resources.is_empty()) {
|
||||
if (OS::get_singleton()->is_stdout_verbose()) {
|
||||
ERR_PRINT(vformat("%d resources still in use at exit.", resources.size()));
|
||||
for (const KeyValue<String, Resource *> &E : resources) {
|
||||
print_line(vformat("Resource still in use: %s (%s)", E.key, E.value->get_class()));
|
||||
}
|
||||
} else {
|
||||
ERR_PRINT(vformat("%d resources still in use at exit (run with --verbose for details).", resources.size()));
|
||||
}
|
||||
}
|
||||
|
||||
resources.clear();
|
||||
}
|
||||
|
||||
bool ResourceCache::has(const String &p_path) {
|
||||
lock.lock();
|
||||
|
||||
Resource **res = resources.getptr(p_path);
|
||||
|
||||
if (res && (*res)->get_reference_count() == 0) {
|
||||
// This resource is in the process of being deleted, ignore its existence.
|
||||
(*res)->path_cache = String();
|
||||
resources.erase(p_path);
|
||||
res = nullptr;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (!res) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<Resource> ResourceCache::get_ref(const String &p_path) {
|
||||
Ref<Resource> ref;
|
||||
lock.lock();
|
||||
|
||||
Resource **res = resources.getptr(p_path);
|
||||
|
||||
if (res) {
|
||||
ref = Ref<Resource>(*res);
|
||||
}
|
||||
|
||||
if (res && !ref.is_valid()) {
|
||||
// This resource is in the process of being deleted, ignore its existence
|
||||
(*res)->path_cache = String();
|
||||
resources.erase(p_path);
|
||||
res = nullptr;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
|
||||
lock.lock();
|
||||
|
||||
LocalVector<String> to_remove;
|
||||
|
||||
for (KeyValue<String, Resource *> &E : resources) {
|
||||
Ref<Resource> ref = Ref<Resource>(E.value);
|
||||
|
||||
if (!ref.is_valid()) {
|
||||
// This resource is in the process of being deleted, ignore its existence
|
||||
E.value->path_cache = String();
|
||||
to_remove.push_back(E.key);
|
||||
continue;
|
||||
}
|
||||
|
||||
p_resources->push_back(ref);
|
||||
}
|
||||
|
||||
for (const String &E : to_remove) {
|
||||
resources.erase(E);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
int ResourceCache::get_cached_resource_count() {
|
||||
lock.lock();
|
||||
int rc = resources.size();
|
||||
lock.unlock();
|
||||
|
||||
return rc;
|
||||
}
|
||||
175
engine/core/io/resource.h
Normal file
175
engine/core/io/resource.h
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
/**************************************************************************/
|
||||
/* resource.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef RESOURCE_H
|
||||
#define RESOURCE_H
|
||||
|
||||
#include "core/io/resource_uid.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
#include "core/templates/self_list.h"
|
||||
|
||||
class Node;
|
||||
|
||||
#define RES_BASE_EXTENSION(m_ext) \
|
||||
public: \
|
||||
static void register_custom_data_to_otdb() { ClassDB::add_resource_base_extension(m_ext, get_class_static()); } \
|
||||
virtual String get_base_extension() const override { return m_ext; } \
|
||||
\
|
||||
private:
|
||||
|
||||
class Resource : public RefCounted {
|
||||
GDCLASS(Resource, RefCounted);
|
||||
|
||||
public:
|
||||
static void register_custom_data_to_otdb() { ClassDB::add_resource_base_extension("res", get_class_static()); }
|
||||
virtual String get_base_extension() const { return "res"; }
|
||||
|
||||
private:
|
||||
friend class ResBase;
|
||||
friend class ResourceCache;
|
||||
|
||||
String name;
|
||||
String path_cache;
|
||||
String scene_unique_id;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
uint64_t last_modified_time = 0;
|
||||
uint64_t import_last_modified_time = 0;
|
||||
String import_path;
|
||||
#endif
|
||||
|
||||
bool local_to_scene = false;
|
||||
friend class SceneState;
|
||||
Node *local_scene = nullptr;
|
||||
|
||||
SelfList<Resource> remapped_list;
|
||||
|
||||
void _dupe_sub_resources(Variant &r_variant, Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
|
||||
void _find_sub_resources(const Variant &p_variant, HashSet<Ref<Resource>> &p_resources_found);
|
||||
|
||||
protected:
|
||||
virtual void _resource_path_changed();
|
||||
static void _bind_methods();
|
||||
|
||||
void _set_path(const String &p_path);
|
||||
void _take_over_path(const String &p_path);
|
||||
|
||||
virtual void reset_local_to_scene();
|
||||
GDVIRTUAL0(_setup_local_to_scene);
|
||||
|
||||
public:
|
||||
static Node *(*_get_local_scene_func)(); //used by editor
|
||||
static void (*_update_configuration_warning)(); //used by editor
|
||||
|
||||
void update_configuration_warning();
|
||||
virtual bool editor_can_reload_from_file();
|
||||
virtual void reset_state(); //for resources that use variable amount of properties, either via _validate_property or _get_property_list, this function needs to be implemented to correctly clear state
|
||||
virtual Error copy_from(const Ref<Resource> &p_resource);
|
||||
virtual void reload_from_file();
|
||||
|
||||
void emit_changed();
|
||||
void connect_changed(const Callable &p_callable, uint32_t p_flags = 0);
|
||||
void disconnect_changed(const Callable &p_callable);
|
||||
|
||||
void set_name(const String &p_name);
|
||||
String get_name() const;
|
||||
|
||||
virtual void set_path(const String &p_path, bool p_take_over = false);
|
||||
String get_path() const;
|
||||
virtual void set_path_cache(const String &p_path); // Set raw path without involving resource cache.
|
||||
_FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); }
|
||||
|
||||
static String generate_scene_unique_id();
|
||||
void set_scene_unique_id(const String &p_id);
|
||||
String get_scene_unique_id() const;
|
||||
|
||||
virtual Ref<Resource> duplicate(bool p_subresources = false) const;
|
||||
Ref<Resource> duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
|
||||
void configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
|
||||
|
||||
void set_local_to_scene(bool p_enable);
|
||||
bool is_local_to_scene() const;
|
||||
virtual void setup_local_to_scene();
|
||||
|
||||
Node *get_local_scene() const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
virtual uint32_t hash_edited_version_for_preview() const;
|
||||
|
||||
virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; }
|
||||
uint64_t get_last_modified_time() const { return last_modified_time; }
|
||||
|
||||
virtual void set_import_last_modified_time(uint64_t p_time) { import_last_modified_time = p_time; }
|
||||
uint64_t get_import_last_modified_time() const { return import_last_modified_time; }
|
||||
|
||||
void set_import_path(const String &p_path) { import_path = p_path; }
|
||||
String get_import_path() const { return import_path; }
|
||||
|
||||
#endif
|
||||
|
||||
void set_as_translation_remapped(bool p_remapped);
|
||||
|
||||
virtual RID get_rid() const; // some resources may offer conversion to RID
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
//helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored
|
||||
void set_id_for_path(const String &p_path, const String &p_id);
|
||||
String get_id_for_path(const String &p_path) const;
|
||||
#endif
|
||||
|
||||
Resource();
|
||||
~Resource();
|
||||
};
|
||||
|
||||
class ResourceCache {
|
||||
friend class Resource;
|
||||
friend class ResourceLoader; //need the lock
|
||||
static Mutex lock;
|
||||
static HashMap<String, Resource *> resources;
|
||||
#ifdef TOOLS_ENABLED
|
||||
static HashMap<String, HashMap<String, String>> resource_path_cache; // Each tscn has a set of resource paths and IDs.
|
||||
static RWLock path_cache_lock;
|
||||
#endif // TOOLS_ENABLED
|
||||
friend void unregister_core_types();
|
||||
static void clear();
|
||||
friend void register_core_types();
|
||||
|
||||
public:
|
||||
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 int get_cached_resource_count();
|
||||
};
|
||||
|
||||
#endif // RESOURCE_H
|
||||
2507
engine/core/io/resource_format_binary.cpp
Normal file
2507
engine/core/io/resource_format_binary.cpp
Normal file
File diff suppressed because it is too large
Load diff
192
engine/core/io/resource_format_binary.h
Normal file
192
engine/core/io/resource_format_binary.h
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/**************************************************************************/
|
||||
/* resource_format_binary.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef RESOURCE_FORMAT_BINARY_H
|
||||
#define RESOURCE_FORMAT_BINARY_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
|
||||
class ResourceLoaderBinary {
|
||||
bool translation_remapped = false;
|
||||
String local_path;
|
||||
String res_path;
|
||||
String type;
|
||||
Ref<Resource> resource;
|
||||
uint32_t ver_format = 0;
|
||||
|
||||
Ref<FileAccess> f;
|
||||
|
||||
uint64_t importmd_ofs = 0;
|
||||
|
||||
ResourceUID::ID uid = ResourceUID::INVALID_ID;
|
||||
|
||||
Vector<char> str_buf;
|
||||
List<Ref<Resource>> resource_cache;
|
||||
|
||||
Vector<StringName> string_map;
|
||||
|
||||
StringName _get_string();
|
||||
|
||||
struct ExtResource {
|
||||
String path;
|
||||
String type;
|
||||
ResourceUID::ID uid = ResourceUID::INVALID_ID;
|
||||
Ref<ResourceLoader::LoadToken> load_token;
|
||||
};
|
||||
|
||||
bool using_named_scene_ids = false;
|
||||
bool using_uids = false;
|
||||
String script_class;
|
||||
bool use_sub_threads = false;
|
||||
float *progress = nullptr;
|
||||
Vector<ExtResource> external_resources;
|
||||
|
||||
struct IntResource {
|
||||
String path;
|
||||
uint64_t offset;
|
||||
};
|
||||
|
||||
Vector<IntResource> internal_resources;
|
||||
HashMap<String, Ref<Resource>> internal_index_cache;
|
||||
|
||||
String get_unicode_string();
|
||||
void _advance_padding(uint32_t p_len);
|
||||
|
||||
HashMap<String, String> remaps;
|
||||
Error error = OK;
|
||||
|
||||
ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE;
|
||||
ResourceFormatLoader::CacheMode cache_mode_for_external = ResourceFormatLoader::CACHE_MODE_REUSE;
|
||||
|
||||
friend class ResourceFormatLoaderBinary;
|
||||
|
||||
Error parse_variant(Variant &r_v);
|
||||
|
||||
HashMap<String, Ref<Resource>> dependency_cache;
|
||||
|
||||
public:
|
||||
Ref<Resource> get_resource();
|
||||
Error load();
|
||||
void set_translation_remapped(bool p_remapped);
|
||||
|
||||
void set_remaps(const HashMap<String, String> &p_remaps) { remaps = p_remaps; }
|
||||
void open(Ref<FileAccess> p_f, bool p_no_resources = false, bool p_keep_uuid_paths = false);
|
||||
String recognize(Ref<FileAccess> p_f);
|
||||
String recognize_script_class(Ref<FileAccess> p_f);
|
||||
void get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types);
|
||||
void get_classes_used(Ref<FileAccess> p_f, HashSet<StringName> *p_classes);
|
||||
|
||||
ResourceLoaderBinary() {}
|
||||
};
|
||||
|
||||
class ResourceFormatLoaderBinary : public ResourceFormatLoader {
|
||||
public:
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual bool handles_type(const String &p_type) const override;
|
||||
virtual String get_resource_type(const String &p_path) const override;
|
||||
virtual String get_resource_script_class(const String &p_path) const override;
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override;
|
||||
virtual ResourceUID::ID get_resource_uid(const String &p_path) const override;
|
||||
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override;
|
||||
virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) override;
|
||||
};
|
||||
|
||||
class ResourceFormatSaverBinaryInstance {
|
||||
String local_path;
|
||||
String path;
|
||||
|
||||
bool relative_paths;
|
||||
bool bundle_resources;
|
||||
bool skip_editor;
|
||||
bool big_endian;
|
||||
bool takeover_paths;
|
||||
String magic;
|
||||
HashSet<Ref<Resource>> resource_set;
|
||||
|
||||
struct NonPersistentKey { //for resource properties generated on the fly
|
||||
Ref<Resource> base;
|
||||
StringName property;
|
||||
bool operator<(const NonPersistentKey &p_key) const { return base == p_key.base ? property < p_key.property : base < p_key.base; }
|
||||
};
|
||||
|
||||
RBMap<NonPersistentKey, Variant> non_persistent_map;
|
||||
HashMap<StringName, int> string_map;
|
||||
Vector<StringName> strings;
|
||||
|
||||
HashMap<Ref<Resource>, int> external_resources;
|
||||
List<Ref<Resource>> saved_resources;
|
||||
|
||||
struct Property {
|
||||
int name_idx;
|
||||
Variant value;
|
||||
PropertyInfo pi;
|
||||
};
|
||||
|
||||
struct ResourceData {
|
||||
String type;
|
||||
List<Property> properties;
|
||||
};
|
||||
|
||||
static void _pad_buffer(Ref<FileAccess> f, int p_bytes);
|
||||
void _find_resources(const Variant &p_variant, bool p_main = false);
|
||||
static void save_unicode_string(Ref<FileAccess> f, const String &p_string, bool p_bit_on_len = false);
|
||||
int get_string_index(const String &p_string);
|
||||
|
||||
public:
|
||||
enum {
|
||||
FORMAT_FLAG_NAMED_SCENE_IDS = 1,
|
||||
FORMAT_FLAG_UIDS = 2,
|
||||
FORMAT_FLAG_REAL_T_IS_DOUBLE = 4,
|
||||
FORMAT_FLAG_HAS_SCRIPT_CLASS = 8,
|
||||
|
||||
// Amount of reserved 32-bit fields in resource header
|
||||
RESERVED_FIELDS = 11
|
||||
};
|
||||
Error save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags = 0);
|
||||
Error set_uid(const String &p_path, ResourceUID::ID p_uid);
|
||||
static void write_variant(Ref<FileAccess> f, const Variant &p_property, HashMap<Ref<Resource>, int> &resource_map, HashMap<Ref<Resource>, int> &external_resources, HashMap<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo());
|
||||
};
|
||||
|
||||
class ResourceFormatSaverBinary : public ResourceFormatSaver {
|
||||
public:
|
||||
static ResourceFormatSaverBinary *singleton;
|
||||
virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override;
|
||||
virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid) override;
|
||||
virtual bool recognize(const Ref<Resource> &p_resource) const override;
|
||||
virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override;
|
||||
|
||||
ResourceFormatSaverBinary();
|
||||
};
|
||||
|
||||
#endif // RESOURCE_FORMAT_BINARY_H
|
||||
517
engine/core/io/resource_importer.cpp
Normal file
517
engine/core/io/resource_importer.cpp
Normal file
|
|
@ -0,0 +1,517 @@
|
|||
/**************************************************************************/
|
||||
/* resource_importer.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 "resource_importer.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/variant/variant_parser.h"
|
||||
|
||||
bool ResourceFormatImporter::SortImporterByName::operator()(const Ref<ResourceImporter> &p_a, const Ref<ResourceImporter> &p_b) const {
|
||||
return p_a->get_importer_name() < p_b->get_importer_name();
|
||||
}
|
||||
|
||||
Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndType &r_path_and_type, bool *r_valid) const {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path + ".import", FileAccess::READ, &err);
|
||||
|
||||
if (f.is_null()) {
|
||||
if (r_valid) {
|
||||
*r_valid = false;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
VariantParser::StreamFile stream;
|
||||
stream.f = f;
|
||||
|
||||
String assign;
|
||||
Variant value;
|
||||
VariantParser::Tag next_tag;
|
||||
|
||||
if (r_valid) {
|
||||
*r_valid = true;
|
||||
}
|
||||
|
||||
int lines = 0;
|
||||
String error_text;
|
||||
bool path_found = false; //first match must have priority
|
||||
while (true) {
|
||||
assign = Variant();
|
||||
next_tag.fields.clear();
|
||||
next_tag.name = String();
|
||||
|
||||
err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true);
|
||||
if (err == ERR_FILE_EOF) {
|
||||
return OK;
|
||||
} else if (err != OK) {
|
||||
ERR_PRINT("ResourceFormatImporter::load - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!assign.is_empty()) {
|
||||
if (!path_found && assign.begins_with("path.") && r_path_and_type.path.is_empty()) {
|
||||
String feature = assign.get_slicec('.', 1);
|
||||
if (OS::get_singleton()->has_feature(feature)) {
|
||||
r_path_and_type.path = value;
|
||||
path_found = true; //first match must have priority
|
||||
}
|
||||
|
||||
} else if (!path_found && assign == "path") {
|
||||
r_path_and_type.path = value;
|
||||
path_found = true; //first match must have priority
|
||||
} else if (assign == "type") {
|
||||
r_path_and_type.type = ClassDB::get_compatibility_remapped_class(value);
|
||||
} else if (assign == "importer") {
|
||||
r_path_and_type.importer = value;
|
||||
} else if (assign == "uid") {
|
||||
r_path_and_type.uid = ResourceUID::get_singleton()->text_to_id(value);
|
||||
} else if (assign == "group_file") {
|
||||
r_path_and_type.group_file = value;
|
||||
} else if (assign == "metadata") {
|
||||
r_path_and_type.metadata = value;
|
||||
} else if (assign == "valid") {
|
||||
if (r_valid) {
|
||||
*r_valid = value;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (next_tag.name != "remap") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (r_path_and_type.metadata && !r_path_and_type.path.is_empty()) {
|
||||
Dictionary meta = r_path_and_type.metadata;
|
||||
if (meta.has("has_editor_variant")) {
|
||||
r_path_and_type.path = r_path_and_type.path.get_basename() + ".editor." + r_path_and_type.path.get_extension();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (r_path_and_type.type.is_empty()) {
|
||||
return ERR_FILE_CORRUPT;
|
||||
}
|
||||
if (r_path_and_type.path.is_empty()) {
|
||||
// Some importers may not write files to the .godot folder, so the path can be empty.
|
||||
if (r_path_and_type.importer.is_empty()) {
|
||||
return ERR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
// It's only invalid if the extension for the importer is not empty.
|
||||
Ref<ResourceImporter> importer = get_importer_by_name(r_path_and_type.importer);
|
||||
if (importer.is_null() || !importer->get_save_extension().is_empty()) {
|
||||
return ERR_FILE_CORRUPT;
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err != OK) {
|
||||
if (r_error) {
|
||||
*r_error = err;
|
||||
}
|
||||
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
Ref<Resource> res = ResourceLoader::_load(pat.path, p_path, pat.type, p_cache_mode, r_error, p_use_sub_threads, r_progress);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (res.is_valid()) {
|
||||
res->set_import_last_modified_time(res->get_last_modified_time()); //pass this, if used
|
||||
res->set_import_path(pat.path);
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void ResourceFormatImporter::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
HashSet<String> found;
|
||||
|
||||
for (int i = 0; i < importers.size(); i++) {
|
||||
List<String> local_exts;
|
||||
importers[i]->get_recognized_extensions(&local_exts);
|
||||
for (const String &F : local_exts) {
|
||||
if (!found.has(F)) {
|
||||
p_extensions->push_back(F);
|
||||
found.insert(F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceFormatImporter::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
|
||||
if (p_type.is_empty()) {
|
||||
get_recognized_extensions(p_extensions);
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<String> found;
|
||||
|
||||
for (int i = 0; i < importers.size(); i++) {
|
||||
String res_type = importers[i]->get_resource_type();
|
||||
if (res_type.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ClassDB::is_parent_class(res_type, p_type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> local_exts;
|
||||
importers[i]->get_recognized_extensions(&local_exts);
|
||||
for (const String &F : local_exts) {
|
||||
if (!found.has(F)) {
|
||||
p_extensions->push_back(F);
|
||||
found.insert(F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourceFormatImporter::exists(const String &p_path) const {
|
||||
return FileAccess::exists(p_path + ".import");
|
||||
}
|
||||
|
||||
bool ResourceFormatImporter::recognize_path(const String &p_path, const String &p_for_type) const {
|
||||
return FileAccess::exists(p_path + ".import");
|
||||
}
|
||||
|
||||
Error ResourceFormatImporter::get_import_order_threads_and_importer(const String &p_path, int &r_order, bool &r_can_threads, String &r_importer) const {
|
||||
r_order = 0;
|
||||
r_importer = "";
|
||||
|
||||
r_can_threads = false;
|
||||
Ref<ResourceImporter> importer;
|
||||
|
||||
if (FileAccess::exists(p_path + ".import")) {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err == OK) {
|
||||
importer = get_importer_by_name(pat.importer);
|
||||
}
|
||||
} else {
|
||||
importer = get_importer_by_extension(p_path.get_extension().to_lower());
|
||||
}
|
||||
|
||||
if (importer.is_valid()) {
|
||||
r_order = importer->get_import_order();
|
||||
r_importer = importer->get_importer_name();
|
||||
r_can_threads = importer->can_import_threaded();
|
||||
return OK;
|
||||
} else {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
int ResourceFormatImporter::get_import_order(const String &p_path) const {
|
||||
Ref<ResourceImporter> importer;
|
||||
|
||||
if (FileAccess::exists(p_path + ".import")) {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err == OK) {
|
||||
importer = get_importer_by_name(pat.importer);
|
||||
}
|
||||
} else {
|
||||
importer = get_importer_by_extension(p_path.get_extension().to_lower());
|
||||
}
|
||||
|
||||
if (importer.is_valid()) {
|
||||
return importer->get_import_order();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ResourceFormatImporter::handles_type(const String &p_type) const {
|
||||
for (int i = 0; i < importers.size(); i++) {
|
||||
String res_type = importers[i]->get_resource_type();
|
||||
if (res_type.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
if (ClassDB::is_parent_class(res_type, p_type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String ResourceFormatImporter::get_internal_resource_path(const String &p_path) const {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err != OK) {
|
||||
return String();
|
||||
}
|
||||
|
||||
return pat.path;
|
||||
}
|
||||
|
||||
void ResourceFormatImporter::get_internal_resource_path_list(const String &p_path, List<String> *r_paths) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path + ".import", FileAccess::READ, &err);
|
||||
|
||||
if (f.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VariantParser::StreamFile stream;
|
||||
stream.f = f;
|
||||
|
||||
String assign;
|
||||
Variant value;
|
||||
VariantParser::Tag next_tag;
|
||||
|
||||
int lines = 0;
|
||||
String error_text;
|
||||
while (true) {
|
||||
assign = Variant();
|
||||
next_tag.fields.clear();
|
||||
next_tag.name = String();
|
||||
|
||||
err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true);
|
||||
if (err == ERR_FILE_EOF) {
|
||||
return;
|
||||
} else if (err != OK) {
|
||||
ERR_PRINT("ResourceFormatImporter::get_internal_resource_path_list - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assign.is_empty()) {
|
||||
if (assign.begins_with("path.")) {
|
||||
r_paths->push_back(value);
|
||||
} else if (assign == "path") {
|
||||
r_paths->push_back(value);
|
||||
}
|
||||
} else if (next_tag.name != "remap") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String ResourceFormatImporter::get_import_group_file(const String &p_path) const {
|
||||
bool valid = true;
|
||||
PathAndType pat;
|
||||
_get_path_and_type(p_path, pat, &valid);
|
||||
return valid ? pat.group_file : String();
|
||||
}
|
||||
|
||||
bool ResourceFormatImporter::is_import_valid(const String &p_path) const {
|
||||
bool valid = true;
|
||||
PathAndType pat;
|
||||
_get_path_and_type(p_path, pat, &valid);
|
||||
return valid;
|
||||
}
|
||||
|
||||
String ResourceFormatImporter::get_resource_type(const String &p_path) const {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err != OK) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return pat.type;
|
||||
}
|
||||
|
||||
ResourceUID::ID ResourceFormatImporter::get_resource_uid(const String &p_path) const {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err != OK) {
|
||||
return ResourceUID::INVALID_ID;
|
||||
}
|
||||
|
||||
return pat.uid;
|
||||
}
|
||||
|
||||
Variant ResourceFormatImporter::get_resource_metadata(const String &p_path) const {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err != OK) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
return pat.metadata;
|
||||
}
|
||||
void ResourceFormatImporter::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err != OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceLoader::get_classes_used(pat.path, r_classes);
|
||||
}
|
||||
|
||||
void ResourceFormatImporter::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err != OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceLoader::get_dependencies(pat.path, p_dependencies, p_add_types);
|
||||
}
|
||||
|
||||
Ref<ResourceImporter> ResourceFormatImporter::get_importer_by_name(const String &p_name) const {
|
||||
for (int i = 0; i < importers.size(); i++) {
|
||||
if (importers[i]->get_importer_name() == p_name) {
|
||||
return importers[i];
|
||||
}
|
||||
}
|
||||
|
||||
return Ref<ResourceImporter>();
|
||||
}
|
||||
|
||||
void ResourceFormatImporter::add_importer(const Ref<ResourceImporter> &p_importer, bool p_first_priority) {
|
||||
ERR_FAIL_COND(p_importer.is_null());
|
||||
if (p_first_priority) {
|
||||
importers.insert(0, p_importer);
|
||||
} else {
|
||||
importers.push_back(p_importer);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceFormatImporter::get_importers_for_extension(const String &p_extension, List<Ref<ResourceImporter>> *r_importers) {
|
||||
for (int i = 0; i < importers.size(); i++) {
|
||||
List<String> local_exts;
|
||||
importers[i]->get_recognized_extensions(&local_exts);
|
||||
for (const String &F : local_exts) {
|
||||
if (p_extension.to_lower() == F) {
|
||||
r_importers->push_back(importers[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceFormatImporter::get_importers(List<Ref<ResourceImporter>> *r_importers) {
|
||||
for (int i = 0; i < importers.size(); i++) {
|
||||
r_importers->push_back(importers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<ResourceImporter> ResourceFormatImporter::get_importer_by_extension(const String &p_extension) const {
|
||||
Ref<ResourceImporter> importer;
|
||||
float priority = 0;
|
||||
|
||||
for (int i = 0; i < importers.size(); i++) {
|
||||
List<String> local_exts;
|
||||
importers[i]->get_recognized_extensions(&local_exts);
|
||||
for (const String &F : local_exts) {
|
||||
if (p_extension.to_lower() == F && importers[i]->get_priority() > priority) {
|
||||
importer = importers[i];
|
||||
priority = importers[i]->get_priority();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return importer;
|
||||
}
|
||||
|
||||
String ResourceFormatImporter::get_import_base_path(const String &p_for_file) const {
|
||||
return ProjectSettings::get_singleton()->get_imported_files_path().path_join(p_for_file.get_file() + "-" + p_for_file.md5_text());
|
||||
}
|
||||
|
||||
bool ResourceFormatImporter::are_import_settings_valid(const String &p_path) const {
|
||||
bool valid = true;
|
||||
PathAndType pat;
|
||||
_get_path_and_type(p_path, pat, &valid);
|
||||
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < importers.size(); i++) {
|
||||
if (importers[i]->get_importer_name() == pat.importer) {
|
||||
if (!importers[i]->are_import_settings_valid(p_path)) { //importer thinks this is not valid
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String ResourceFormatImporter::get_import_settings_hash() const {
|
||||
Vector<Ref<ResourceImporter>> sorted_importers = importers;
|
||||
|
||||
sorted_importers.sort_custom<SortImporterByName>();
|
||||
|
||||
String hash;
|
||||
for (int i = 0; i < sorted_importers.size(); i++) {
|
||||
hash += ":" + sorted_importers[i]->get_importer_name() + ":" + sorted_importers[i]->get_import_settings_string();
|
||||
}
|
||||
return hash.md5_text();
|
||||
}
|
||||
|
||||
ResourceFormatImporter *ResourceFormatImporter::singleton = nullptr;
|
||||
|
||||
ResourceFormatImporter::ResourceFormatImporter() {
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
//////////////
|
||||
|
||||
void ResourceImporter::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(IMPORT_ORDER_DEFAULT);
|
||||
BIND_ENUM_CONSTANT(IMPORT_ORDER_SCENE);
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
Error ResourceFormatImporterSaver::set_uid(const String &p_path, ResourceUID::ID p_uid) {
|
||||
Ref<ConfigFile> cf;
|
||||
cf.instantiate();
|
||||
Error err = cf->load(p_path + ".import");
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
cf->set_value("remap", "uid", ResourceUID::get_singleton()->id_to_text(p_uid));
|
||||
cf->save(p_path + ".import");
|
||||
|
||||
return OK;
|
||||
}
|
||||
161
engine/core/io/resource_importer.h
Normal file
161
engine/core/io/resource_importer.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/**************************************************************************/
|
||||
/* resource_importer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef RESOURCE_IMPORTER_H
|
||||
#define RESOURCE_IMPORTER_H
|
||||
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
|
||||
class ResourceImporter;
|
||||
|
||||
class ResourceFormatImporter : public ResourceFormatLoader {
|
||||
struct PathAndType {
|
||||
String path;
|
||||
String type;
|
||||
String importer;
|
||||
String group_file;
|
||||
Variant metadata;
|
||||
uint64_t uid = ResourceUID::INVALID_ID;
|
||||
};
|
||||
|
||||
Error _get_path_and_type(const String &p_path, PathAndType &r_path_and_type, bool *r_valid = nullptr) const;
|
||||
|
||||
static ResourceFormatImporter *singleton;
|
||||
|
||||
//need them to stay in order to compute the settings hash
|
||||
struct SortImporterByName {
|
||||
bool operator()(const Ref<ResourceImporter> &p_a, const Ref<ResourceImporter> &p_b) const;
|
||||
};
|
||||
|
||||
Vector<Ref<ResourceImporter>> importers;
|
||||
|
||||
public:
|
||||
static ResourceFormatImporter *get_singleton() { return singleton; }
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override;
|
||||
virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const override;
|
||||
virtual bool handles_type(const String &p_type) const override;
|
||||
virtual String get_resource_type(const String &p_path) const override;
|
||||
virtual ResourceUID::ID get_resource_uid(const String &p_path) const override;
|
||||
virtual Variant get_resource_metadata(const String &p_path) const;
|
||||
virtual bool is_import_valid(const String &p_path) const override;
|
||||
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override;
|
||||
virtual bool is_imported(const String &p_path) const override { return recognize_path(p_path); }
|
||||
virtual String get_import_group_file(const String &p_path) const override;
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override;
|
||||
virtual bool exists(const String &p_path) const override;
|
||||
|
||||
virtual int get_import_order(const String &p_path) const override;
|
||||
|
||||
Error get_import_order_threads_and_importer(const String &p_path, int &r_order, bool &r_can_threads, String &r_importer) const;
|
||||
|
||||
String get_internal_resource_path(const String &p_path) const;
|
||||
void get_internal_resource_path_list(const String &p_path, List<String> *r_paths);
|
||||
|
||||
void add_importer(const Ref<ResourceImporter> &p_importer, bool p_first_priority = false);
|
||||
|
||||
void remove_importer(const Ref<ResourceImporter> &p_importer) { importers.erase(p_importer); }
|
||||
Ref<ResourceImporter> get_importer_by_name(const String &p_name) const;
|
||||
Ref<ResourceImporter> get_importer_by_extension(const String &p_extension) const;
|
||||
void get_importers_for_extension(const String &p_extension, List<Ref<ResourceImporter>> *r_importers);
|
||||
void get_importers(List<Ref<ResourceImporter>> *r_importers);
|
||||
|
||||
bool are_import_settings_valid(const String &p_path) const;
|
||||
String get_import_settings_hash() const;
|
||||
|
||||
String get_import_base_path(const String &p_for_file) const;
|
||||
ResourceFormatImporter();
|
||||
};
|
||||
|
||||
class ResourceImporter : public RefCounted {
|
||||
GDCLASS(ResourceImporter, RefCounted);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual String get_importer_name() const = 0;
|
||||
virtual String get_visible_name() const = 0;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const = 0;
|
||||
virtual String get_save_extension() const = 0;
|
||||
virtual String get_resource_type() const = 0;
|
||||
virtual float get_priority() const { return 1.0; }
|
||||
virtual int get_import_order() const { return IMPORT_ORDER_DEFAULT; }
|
||||
virtual int get_format_version() const { return 0; }
|
||||
|
||||
struct ImportOption {
|
||||
PropertyInfo option;
|
||||
Variant default_value;
|
||||
|
||||
ImportOption(const PropertyInfo &p_info, const Variant &p_default) :
|
||||
option(p_info),
|
||||
default_value(p_default) {
|
||||
}
|
||||
ImportOption() {}
|
||||
};
|
||||
|
||||
enum ImportOrder {
|
||||
IMPORT_ORDER_DEFAULT = 0,
|
||||
IMPORT_ORDER_SCENE = 100,
|
||||
};
|
||||
|
||||
virtual bool has_advanced_options() const { return false; }
|
||||
virtual void show_advanced_options(const String &p_path) {}
|
||||
|
||||
virtual int get_preset_count() const { return 0; }
|
||||
virtual String get_preset_name(int p_idx) const { return String(); }
|
||||
|
||||
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const = 0;
|
||||
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const = 0;
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {}
|
||||
virtual String get_option_group_file() const { return String(); }
|
||||
|
||||
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) = 0;
|
||||
virtual bool can_import_threaded() const { return true; }
|
||||
virtual void import_threaded_begin() {}
|
||||
virtual void import_threaded_end() {}
|
||||
|
||||
virtual Error import_group_file(const String &p_group_file, const HashMap<String, HashMap<StringName, Variant>> &p_source_file_options, const HashMap<String, String> &p_base_paths) { return ERR_UNAVAILABLE; }
|
||||
virtual bool are_import_settings_valid(const String &p_path) const { return true; }
|
||||
virtual String get_import_settings_string() const { return String(); }
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(ResourceImporter::ImportOrder);
|
||||
|
||||
class ResourceFormatImporterSaver : public ResourceFormatSaver {
|
||||
GDCLASS(ResourceFormatImporterSaver, ResourceFormatSaver)
|
||||
|
||||
public:
|
||||
virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid) override;
|
||||
};
|
||||
|
||||
#endif // RESOURCE_IMPORTER_H
|
||||
1327
engine/core/io/resource_loader.cpp
Normal file
1327
engine/core/io/resource_loader.cpp
Normal file
File diff suppressed because it is too large
Load diff
287
engine/core/io/resource_loader.h
Normal file
287
engine/core/io/resource_loader.h
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
/**************************************************************************/
|
||||
/* resource_loader.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef RESOURCE_LOADER_H
|
||||
#define RESOURCE_LOADER_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/object/worker_thread_pool.h"
|
||||
#include "core/os/semaphore.h"
|
||||
#include "core/os/thread.h"
|
||||
|
||||
class ConditionVariable;
|
||||
|
||||
template <int Tag>
|
||||
class SafeBinaryMutex;
|
||||
|
||||
class ResourceFormatLoader : public RefCounted {
|
||||
GDCLASS(ResourceFormatLoader, RefCounted);
|
||||
|
||||
public:
|
||||
enum CacheMode {
|
||||
CACHE_MODE_IGNORE,
|
||||
CACHE_MODE_REUSE,
|
||||
CACHE_MODE_REPLACE,
|
||||
CACHE_MODE_IGNORE_DEEP,
|
||||
CACHE_MODE_REPLACE_DEEP,
|
||||
};
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions)
|
||||
GDVIRTUAL2RC(bool, _recognize_path, String, StringName)
|
||||
GDVIRTUAL1RC(bool, _handles_type, StringName)
|
||||
GDVIRTUAL1RC(String, _get_resource_type, String)
|
||||
GDVIRTUAL1RC(String, _get_resource_script_class, String)
|
||||
GDVIRTUAL1RC(ResourceUID::ID, _get_resource_uid, String)
|
||||
GDVIRTUAL2RC(Vector<String>, _get_dependencies, String, bool)
|
||||
GDVIRTUAL1RC(Vector<String>, _get_classes_used, String)
|
||||
GDVIRTUAL2RC(Error, _rename_dependencies, String, Dictionary)
|
||||
GDVIRTUAL1RC(bool, _exists, String)
|
||||
|
||||
GDVIRTUAL4RC(Variant, _load, String, String, bool, int)
|
||||
|
||||
public:
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE);
|
||||
virtual bool exists(const String &p_path) const;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const;
|
||||
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const;
|
||||
virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const;
|
||||
virtual bool handles_type(const String &p_type) const;
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
|
||||
virtual String get_resource_type(const String &p_path) const;
|
||||
virtual String get_resource_script_class(const String &p_path) const;
|
||||
virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
|
||||
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
|
||||
virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map);
|
||||
virtual bool is_import_valid(const String &p_path) const { return true; }
|
||||
virtual bool is_imported(const String &p_path) const { return false; }
|
||||
virtual int get_import_order(const String &p_path) const { return 0; }
|
||||
virtual String get_import_group_file(const String &p_path) const { return ""; } //no group
|
||||
|
||||
virtual ~ResourceFormatLoader() {}
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(ResourceFormatLoader::CacheMode)
|
||||
|
||||
typedef void (*ResourceLoadErrorNotify)(const String &p_text);
|
||||
typedef void (*DependencyErrorNotify)(const String &p_loading, const String &p_which, const String &p_type);
|
||||
|
||||
typedef Error (*ResourceLoaderImport)(const String &p_path);
|
||||
typedef void (*ResourceLoadedCallback)(Ref<Resource> p_resource, const String &p_path);
|
||||
|
||||
class ResourceLoader {
|
||||
enum {
|
||||
MAX_LOADERS = 64
|
||||
};
|
||||
|
||||
public:
|
||||
enum ThreadLoadStatus {
|
||||
THREAD_LOAD_INVALID_RESOURCE,
|
||||
THREAD_LOAD_IN_PROGRESS,
|
||||
THREAD_LOAD_FAILED,
|
||||
THREAD_LOAD_LOADED
|
||||
};
|
||||
|
||||
enum LoadThreadMode {
|
||||
LOAD_THREAD_FROM_CURRENT,
|
||||
LOAD_THREAD_SPAWN_SINGLE,
|
||||
LOAD_THREAD_DISTRIBUTE,
|
||||
};
|
||||
|
||||
struct LoadToken : public RefCounted {
|
||||
String local_path;
|
||||
String user_path;
|
||||
Ref<Resource> res_if_unregistered;
|
||||
|
||||
void clear();
|
||||
|
||||
virtual ~LoadToken();
|
||||
};
|
||||
|
||||
static const int BINARY_MUTEX_TAG = 1;
|
||||
|
||||
static Ref<LoadToken> _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode);
|
||||
static Ref<Resource> _load_complete(LoadToken &p_load_token, Error *r_error);
|
||||
|
||||
private:
|
||||
static Ref<Resource> _load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock);
|
||||
|
||||
static Ref<ResourceFormatLoader> loader[MAX_LOADERS];
|
||||
static int loader_count;
|
||||
static bool timestamp_on_load;
|
||||
|
||||
static void *err_notify_ud;
|
||||
static ResourceLoadErrorNotify err_notify;
|
||||
static void *dep_err_notify_ud;
|
||||
static DependencyErrorNotify dep_err_notify;
|
||||
static bool abort_on_missing_resource;
|
||||
static bool create_missing_resources_if_class_unavailable;
|
||||
static HashMap<String, Vector<String>> translation_remaps;
|
||||
static HashMap<String, String> path_remaps;
|
||||
|
||||
static String _path_remap(const String &p_path, bool *r_translation_remapped = nullptr);
|
||||
friend class Resource;
|
||||
|
||||
static SelfList<Resource>::List remapped_list;
|
||||
|
||||
friend class ResourceFormatImporter;
|
||||
|
||||
static Ref<Resource> _load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress);
|
||||
|
||||
static ResourceLoadedCallback _loaded_callback;
|
||||
|
||||
static Ref<ResourceFormatLoader> _find_custom_resource_format_loader(const String &path);
|
||||
|
||||
struct ThreadLoadTask {
|
||||
WorkerThreadPool::TaskID task_id = 0; // Used if run on a worker thread from the pool.
|
||||
Thread::ID thread_id = 0; // Used if running on an user thread (e.g., simple non-threaded load).
|
||||
bool awaited = false; // If it's in the pool, this helps not awaiting from more than one dependent thread.
|
||||
ConditionVariable *cond_var = nullptr; // In not in the worker pool or already awaiting, this is used as a secondary awaiting mechanism.
|
||||
LoadToken *load_token = nullptr;
|
||||
String local_path;
|
||||
String remapped_path;
|
||||
String type_hint;
|
||||
float progress = 0.0f;
|
||||
float max_reported_progress = 0.0f;
|
||||
uint64_t last_progress_check_main_thread_frame = UINT64_MAX;
|
||||
ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS;
|
||||
ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE;
|
||||
Error error = OK;
|
||||
Ref<Resource> resource;
|
||||
bool xl_remapped = false;
|
||||
bool use_sub_threads = false;
|
||||
HashSet<String> sub_tasks;
|
||||
};
|
||||
|
||||
static void _thread_load_function(void *p_userdata);
|
||||
|
||||
static thread_local int load_nesting;
|
||||
static thread_local WorkerThreadPool::TaskID caller_task_id;
|
||||
static thread_local HashMap<int, HashMap<String, Ref<Resource>>> res_ref_overrides; // Outermost key is nesting level.
|
||||
static thread_local Vector<String> *load_paths_stack; // A pointer to avoid broken TLS implementations from double-running the destructor.
|
||||
static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex;
|
||||
static HashMap<String, ThreadLoadTask> thread_load_tasks;
|
||||
static bool cleaning_tasks;
|
||||
|
||||
static HashMap<String, LoadToken *> user_load_tokens;
|
||||
|
||||
static float _dependency_get_progress(const String &p_path);
|
||||
|
||||
static bool _ensure_load_progress();
|
||||
|
||||
public:
|
||||
static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE);
|
||||
static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr);
|
||||
static Ref<Resource> load_threaded_get(const String &p_path, Error *r_error = nullptr);
|
||||
|
||||
static bool is_within_load() { return load_nesting > 0; };
|
||||
|
||||
static Ref<Resource> load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, Error *r_error = nullptr);
|
||||
static bool exists(const String &p_path, const String &p_type_hint = "");
|
||||
|
||||
static void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions);
|
||||
static void add_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader, bool p_at_front = false);
|
||||
static void remove_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader);
|
||||
static void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
|
||||
static String get_resource_type(const String &p_path);
|
||||
static String get_resource_script_class(const String &p_path);
|
||||
static ResourceUID::ID get_resource_uid(const String &p_path);
|
||||
static void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
|
||||
static Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map);
|
||||
static bool is_import_valid(const String &p_path);
|
||||
static String get_import_group_file(const String &p_path);
|
||||
static bool is_imported(const String &p_path);
|
||||
static int get_import_order(const String &p_path);
|
||||
|
||||
static void set_timestamp_on_load(bool p_timestamp) { timestamp_on_load = p_timestamp; }
|
||||
static bool get_timestamp_on_load() { return timestamp_on_load; }
|
||||
|
||||
// Loaders can safely use this regardless which thread they are running on.
|
||||
static void notify_load_error(const String &p_err) {
|
||||
if (err_notify) {
|
||||
MessageQueue::get_main_singleton()->push_callable(callable_mp_static(err_notify).bind(p_err));
|
||||
}
|
||||
}
|
||||
static void set_error_notify_func(ResourceLoadErrorNotify p_err_notify) {
|
||||
err_notify = p_err_notify;
|
||||
}
|
||||
|
||||
// Loaders can safely use this regardless which thread they are running on.
|
||||
static void notify_dependency_error(const String &p_path, const String &p_dependency, const String &p_type) {
|
||||
if (dep_err_notify) {
|
||||
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||
dep_err_notify(p_path, p_dependency, p_type);
|
||||
} else {
|
||||
MessageQueue::get_main_singleton()->push_callable(callable_mp_static(dep_err_notify).bind(p_path, p_dependency, p_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
static void set_dependency_error_notify_func(DependencyErrorNotify p_err_notify) {
|
||||
dep_err_notify = p_err_notify;
|
||||
}
|
||||
|
||||
static void set_abort_on_missing_resources(bool p_abort) { abort_on_missing_resource = p_abort; }
|
||||
static bool get_abort_on_missing_resources() { return abort_on_missing_resource; }
|
||||
|
||||
static String path_remap(const String &p_path);
|
||||
static String import_remap(const String &p_path);
|
||||
|
||||
static void load_path_remaps();
|
||||
static void clear_path_remaps();
|
||||
|
||||
static void reload_translation_remaps();
|
||||
static void load_translation_remaps();
|
||||
static void clear_translation_remaps();
|
||||
|
||||
static void clear_thread_load_tasks();
|
||||
|
||||
static void set_load_callback(ResourceLoadedCallback p_callback);
|
||||
static ResourceLoaderImport import;
|
||||
|
||||
static bool add_custom_resource_format_loader(const String &script_path);
|
||||
static void add_custom_loaders();
|
||||
static void remove_custom_loaders();
|
||||
|
||||
static void set_create_missing_resources_if_class_unavailable(bool p_enable);
|
||||
_FORCE_INLINE_ static bool is_creating_missing_resources_if_class_unavailable_enabled() { return create_missing_resources_if_class_unavailable; }
|
||||
|
||||
static Ref<Resource> ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type);
|
||||
static Ref<Resource> get_resource_ref_override(const String &p_path);
|
||||
|
||||
static bool is_cleaning_tasks();
|
||||
|
||||
static void initialize();
|
||||
static void finalize();
|
||||
};
|
||||
|
||||
#endif // RESOURCE_LOADER_H
|
||||
293
engine/core/io/resource_saver.cpp
Normal file
293
engine/core/io/resource_saver.cpp
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
/**************************************************************************/
|
||||
/* resource_saver.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 "resource_saver.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
Ref<ResourceFormatSaver> ResourceSaver::saver[MAX_SAVERS];
|
||||
|
||||
int ResourceSaver::saver_count = 0;
|
||||
bool ResourceSaver::timestamp_on_save = false;
|
||||
ResourceSavedCallback ResourceSaver::save_callback = nullptr;
|
||||
ResourceSaverGetResourceIDForPath ResourceSaver::save_get_id_for_path = nullptr;
|
||||
|
||||
Error ResourceFormatSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
|
||||
Error err = ERR_METHOD_NOT_FOUND;
|
||||
GDVIRTUAL_CALL(_save, p_resource, p_path, p_flags, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
Error ResourceFormatSaver::set_uid(const String &p_path, ResourceUID::ID p_uid) {
|
||||
Error err = ERR_FILE_UNRECOGNIZED;
|
||||
GDVIRTUAL_CALL(_set_uid, p_path, p_uid, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
bool ResourceFormatSaver::recognize(const Ref<Resource> &p_resource) const {
|
||||
bool success = false;
|
||||
GDVIRTUAL_CALL(_recognize, p_resource, success);
|
||||
return success;
|
||||
}
|
||||
|
||||
void ResourceFormatSaver::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const {
|
||||
PackedStringArray exts;
|
||||
if (GDVIRTUAL_CALL(_get_recognized_extensions, p_resource, exts)) {
|
||||
const String *r = exts.ptr();
|
||||
for (int i = 0; i < exts.size(); ++i) {
|
||||
p_extensions->push_back(r[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourceFormatSaver::recognize_path(const Ref<Resource> &p_resource, const String &p_path) const {
|
||||
bool ret = false;
|
||||
if (GDVIRTUAL_CALL(_recognize_path, p_resource, p_path, ret)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
String extension = p_path.get_extension();
|
||||
|
||||
List<String> extensions;
|
||||
get_recognized_extensions(p_resource, &extensions);
|
||||
|
||||
for (const String &E : extensions) {
|
||||
if (E.nocasecmp_to(extension) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ResourceFormatSaver::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_save, "resource", "path", "flags");
|
||||
GDVIRTUAL_BIND(_set_uid, "path", "uid");
|
||||
GDVIRTUAL_BIND(_recognize, "resource");
|
||||
GDVIRTUAL_BIND(_get_recognized_extensions, "resource");
|
||||
GDVIRTUAL_BIND(_recognize_path, "resource", "path");
|
||||
}
|
||||
|
||||
Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
|
||||
ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'.");
|
||||
String path = p_path;
|
||||
if (path.is_empty()) {
|
||||
path = p_resource->get_path();
|
||||
}
|
||||
ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Can't save resource to empty path. Provide non-empty path or a Resource with non-empty resource_path.");
|
||||
|
||||
String extension = path.get_extension();
|
||||
Error err = ERR_FILE_UNRECOGNIZED;
|
||||
|
||||
for (int i = 0; i < saver_count; i++) {
|
||||
if (!saver[i]->recognize(p_resource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!saver[i]->recognize_path(p_resource, path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String old_path = p_resource->get_path();
|
||||
|
||||
String local_path = ProjectSettings::get_singleton()->localize_path(path);
|
||||
|
||||
if (p_flags & FLAG_CHANGE_PATH) {
|
||||
p_resource->set_path(local_path);
|
||||
}
|
||||
|
||||
err = saver[i]->save(p_resource, path, p_flags);
|
||||
|
||||
if (err == OK) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
((Resource *)p_resource.ptr())->set_edited(false);
|
||||
if (timestamp_on_save) {
|
||||
uint64_t mt = FileAccess::get_modified_time(path);
|
||||
|
||||
((Resource *)p_resource.ptr())->set_last_modified_time(mt);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (p_flags & FLAG_CHANGE_PATH) {
|
||||
p_resource->set_path(old_path);
|
||||
}
|
||||
|
||||
if (save_callback && path.begins_with("res://")) {
|
||||
save_callback(p_resource, path);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
Error ResourceSaver::set_uid(const String &p_path, ResourceUID::ID p_uid) {
|
||||
String path = p_path;
|
||||
|
||||
ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Can't update UID to empty path. Provide non-empty path.");
|
||||
|
||||
Error err = ERR_FILE_UNRECOGNIZED;
|
||||
|
||||
for (int i = 0; i < saver_count; i++) {
|
||||
err = saver[i]->set_uid(path, p_uid);
|
||||
if (err == OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void ResourceSaver::set_save_callback(ResourceSavedCallback p_callback) {
|
||||
save_callback = p_callback;
|
||||
}
|
||||
|
||||
void ResourceSaver::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) {
|
||||
ERR_FAIL_COND_MSG(p_resource.is_null(), "It's not a reference to a valid Resource object.");
|
||||
for (int i = 0; i < saver_count; i++) {
|
||||
saver[i]->get_recognized_extensions(p_resource, p_extensions);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceSaver::add_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver, bool p_at_front) {
|
||||
ERR_FAIL_COND_MSG(p_format_saver.is_null(), "It's not a reference to a valid ResourceFormatSaver object.");
|
||||
ERR_FAIL_COND(saver_count >= MAX_SAVERS);
|
||||
|
||||
if (p_at_front) {
|
||||
for (int i = saver_count; i > 0; i--) {
|
||||
saver[i] = saver[i - 1];
|
||||
}
|
||||
saver[0] = p_format_saver;
|
||||
saver_count++;
|
||||
} else {
|
||||
saver[saver_count++] = p_format_saver;
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceSaver::remove_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver) {
|
||||
ERR_FAIL_COND_MSG(p_format_saver.is_null(), "It's not a reference to a valid ResourceFormatSaver object.");
|
||||
|
||||
// Find saver
|
||||
int i = 0;
|
||||
for (; i < saver_count; ++i) {
|
||||
if (saver[i] == p_format_saver) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(i >= saver_count); // Not found
|
||||
|
||||
// Shift next savers up
|
||||
for (; i < saver_count - 1; ++i) {
|
||||
saver[i] = saver[i + 1];
|
||||
}
|
||||
saver[saver_count - 1].unref();
|
||||
--saver_count;
|
||||
}
|
||||
|
||||
Ref<ResourceFormatSaver> ResourceSaver::_find_custom_resource_format_saver(const String &path) {
|
||||
for (int i = 0; i < saver_count; ++i) {
|
||||
if (saver[i]->get_script_instance() && saver[i]->get_script_instance()->get_script()->get_path() == path) {
|
||||
return saver[i];
|
||||
}
|
||||
}
|
||||
return Ref<ResourceFormatSaver>();
|
||||
}
|
||||
|
||||
bool ResourceSaver::add_custom_resource_format_saver(const String &script_path) {
|
||||
if (_find_custom_resource_format_saver(script_path).is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ref<Resource> res = ResourceLoader::load(script_path);
|
||||
ERR_FAIL_COND_V(res.is_null(), false);
|
||||
ERR_FAIL_COND_V(!res->is_class("Script"), false);
|
||||
|
||||
Ref<Script> s = res;
|
||||
StringName ibt = s->get_instance_base_type();
|
||||
bool valid_type = ClassDB::is_parent_class(ibt, "ResourceFormatSaver");
|
||||
ERR_FAIL_COND_V_MSG(!valid_type, false, vformat("Failed to add a custom resource saver, script '%s' does not inherit 'ResourceFormatSaver'.", script_path));
|
||||
|
||||
Object *obj = ClassDB::instantiate(ibt);
|
||||
ERR_FAIL_NULL_V_MSG(obj, false, vformat("Failed to add a custom resource saver, cannot instantiate '%s'.", ibt));
|
||||
|
||||
Ref<ResourceFormatSaver> crl = Object::cast_to<ResourceFormatSaver>(obj);
|
||||
crl->set_script(s);
|
||||
ResourceSaver::add_resource_format_saver(crl);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResourceSaver::add_custom_savers() {
|
||||
// Custom resource savers exploits global class names
|
||||
|
||||
String custom_saver_base_class = ResourceFormatSaver::get_class_static();
|
||||
|
||||
List<StringName> global_classes;
|
||||
ScriptServer::get_global_class_list(&global_classes);
|
||||
|
||||
for (const StringName &class_name : global_classes) {
|
||||
StringName base_class = ScriptServer::get_global_class_native_base(class_name);
|
||||
|
||||
if (base_class == custom_saver_base_class) {
|
||||
String path = ScriptServer::get_global_class_path(class_name);
|
||||
add_custom_resource_format_saver(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceSaver::remove_custom_savers() {
|
||||
Vector<Ref<ResourceFormatSaver>> custom_savers;
|
||||
for (int i = 0; i < saver_count; ++i) {
|
||||
if (saver[i]->get_script_instance()) {
|
||||
custom_savers.push_back(saver[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < custom_savers.size(); ++i) {
|
||||
remove_resource_format_saver(custom_savers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ResourceUID::ID ResourceSaver::get_resource_id_for_path(const String &p_path, bool p_generate) {
|
||||
if (save_get_id_for_path) {
|
||||
return save_get_id_for_path(p_path, p_generate);
|
||||
}
|
||||
return ResourceUID::INVALID_ID;
|
||||
}
|
||||
|
||||
void ResourceSaver::set_get_resource_id_for_path(ResourceSaverGetResourceIDForPath p_callback) {
|
||||
save_get_id_for_path = p_callback;
|
||||
}
|
||||
107
engine/core/io/resource_saver.h
Normal file
107
engine/core/io/resource_saver.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/**************************************************************************/
|
||||
/* resource_saver.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef RESOURCE_SAVER_H
|
||||
#define RESOURCE_SAVER_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
|
||||
class ResourceFormatSaver : public RefCounted {
|
||||
GDCLASS(ResourceFormatSaver, RefCounted);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
GDVIRTUAL3R(Error, _save, Ref<Resource>, String, uint32_t)
|
||||
GDVIRTUAL2R(Error, _set_uid, String, ResourceUID::ID)
|
||||
GDVIRTUAL1RC(bool, _recognize, Ref<Resource>)
|
||||
GDVIRTUAL1RC(Vector<String>, _get_recognized_extensions, Ref<Resource>)
|
||||
GDVIRTUAL2RC(bool, _recognize_path, Ref<Resource>, String)
|
||||
|
||||
public:
|
||||
virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0);
|
||||
virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid);
|
||||
virtual bool recognize(const Ref<Resource> &p_resource) const;
|
||||
virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const;
|
||||
virtual bool recognize_path(const Ref<Resource> &p_resource, const String &p_path) const;
|
||||
|
||||
virtual ~ResourceFormatSaver() {}
|
||||
};
|
||||
|
||||
typedef void (*ResourceSavedCallback)(Ref<Resource> p_resource, const String &p_path);
|
||||
typedef ResourceUID::ID (*ResourceSaverGetResourceIDForPath)(const String &p_path, bool p_generate);
|
||||
|
||||
class ResourceSaver {
|
||||
enum {
|
||||
MAX_SAVERS = 64
|
||||
};
|
||||
|
||||
static Ref<ResourceFormatSaver> saver[MAX_SAVERS];
|
||||
static int saver_count;
|
||||
static bool timestamp_on_save;
|
||||
static ResourceSavedCallback save_callback;
|
||||
static ResourceSaverGetResourceIDForPath save_get_id_for_path;
|
||||
|
||||
static Ref<ResourceFormatSaver> _find_custom_resource_format_saver(const String &path);
|
||||
|
||||
public:
|
||||
enum SaverFlags {
|
||||
FLAG_NONE = 0,
|
||||
FLAG_RELATIVE_PATHS = 1,
|
||||
FLAG_BUNDLE_RESOURCES = 2,
|
||||
FLAG_CHANGE_PATH = 4,
|
||||
FLAG_OMIT_EDITOR_PROPERTIES = 8,
|
||||
FLAG_SAVE_BIG_ENDIAN = 16,
|
||||
FLAG_COMPRESS = 32,
|
||||
FLAG_REPLACE_SUBRESOURCE_PATHS = 64,
|
||||
};
|
||||
|
||||
static Error save(const Ref<Resource> &p_resource, const String &p_path = "", uint32_t p_flags = (uint32_t)FLAG_NONE);
|
||||
static void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions);
|
||||
static void add_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver, bool p_at_front = false);
|
||||
static void remove_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver);
|
||||
|
||||
static Error set_uid(const String &p_path, ResourceUID::ID p_uid);
|
||||
|
||||
static void set_timestamp_on_save(bool p_timestamp) { timestamp_on_save = p_timestamp; }
|
||||
static bool get_timestamp_on_save() { return timestamp_on_save; }
|
||||
|
||||
static ResourceUID::ID get_resource_id_for_path(const String &p_path, bool p_generate = false);
|
||||
|
||||
static void set_save_callback(ResourceSavedCallback p_callback);
|
||||
static void set_get_resource_id_for_path(ResourceSaverGetResourceIDForPath p_callback);
|
||||
|
||||
static bool add_custom_resource_format_saver(const String &script_path);
|
||||
static void add_custom_savers();
|
||||
static void remove_custom_savers();
|
||||
};
|
||||
|
||||
#endif // RESOURCE_SAVER_H
|
||||
270
engine/core/io/resource_uid.cpp
Normal file
270
engine/core/io/resource_uid.cpp
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/**************************************************************************/
|
||||
/* resource_uid.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 "resource_uid.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
// These constants are off by 1, causing the 'z' and '9' characters never to be used.
|
||||
// This cannot be fixed without breaking compatibility; see GH-83843.
|
||||
static constexpr uint32_t char_count = ('z' - 'a');
|
||||
static constexpr uint32_t base = char_count + ('9' - '0');
|
||||
|
||||
String ResourceUID::get_cache_file() {
|
||||
return ProjectSettings::get_singleton()->get_project_data_path().path_join("uid_cache.bin");
|
||||
}
|
||||
|
||||
String ResourceUID::id_to_text(ID p_id) const {
|
||||
if (p_id < 0) {
|
||||
return "uid://<invalid>";
|
||||
}
|
||||
String txt;
|
||||
|
||||
while (p_id) {
|
||||
uint32_t c = p_id % base;
|
||||
if (c < char_count) {
|
||||
txt = String::chr('a' + c) + txt;
|
||||
} else {
|
||||
txt = String::chr('0' + (c - char_count)) + txt;
|
||||
}
|
||||
p_id /= base;
|
||||
}
|
||||
|
||||
return "uid://" + txt;
|
||||
}
|
||||
|
||||
ResourceUID::ID ResourceUID::text_to_id(const String &p_text) const {
|
||||
if (!p_text.begins_with("uid://") || p_text == "uid://<invalid>") {
|
||||
return INVALID_ID;
|
||||
}
|
||||
|
||||
uint32_t l = p_text.length();
|
||||
uint64_t uid = 0;
|
||||
for (uint32_t i = 6; i < l; i++) {
|
||||
uid *= base;
|
||||
uint32_t c = p_text[i];
|
||||
if (is_ascii_lower_case(c)) {
|
||||
uid += c - 'a';
|
||||
} else if (is_digit(c)) {
|
||||
uid += c - '0' + char_count;
|
||||
} else {
|
||||
return INVALID_ID;
|
||||
}
|
||||
}
|
||||
return ID(uid & 0x7FFFFFFFFFFFFFFF);
|
||||
}
|
||||
|
||||
ResourceUID::ID ResourceUID::create_id() {
|
||||
while (true) {
|
||||
ID id = INVALID_ID;
|
||||
MutexLock lock(mutex);
|
||||
Error err = ((CryptoCore::RandomGenerator *)crypto)->get_random_bytes((uint8_t *)&id, sizeof(id));
|
||||
ERR_FAIL_COND_V(err != OK, INVALID_ID);
|
||||
id &= 0x7FFFFFFFFFFFFFFF;
|
||||
bool exists = unique_ids.has(id);
|
||||
if (!exists) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourceUID::has_id(ID p_id) const {
|
||||
MutexLock l(mutex);
|
||||
return unique_ids.has(p_id);
|
||||
}
|
||||
void ResourceUID::add_id(ID p_id, const String &p_path) {
|
||||
MutexLock l(mutex);
|
||||
ERR_FAIL_COND(unique_ids.has(p_id));
|
||||
Cache c;
|
||||
c.cs = p_path.utf8();
|
||||
unique_ids[p_id] = c;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
void ResourceUID::set_id(ID p_id, const String &p_path) {
|
||||
MutexLock l(mutex);
|
||||
ERR_FAIL_COND(!unique_ids.has(p_id));
|
||||
CharString cs = p_path.utf8();
|
||||
const char *update_ptr = cs.ptr();
|
||||
const char *cached_ptr = unique_ids[p_id].cs.ptr();
|
||||
if (update_ptr == nullptr && cached_ptr == nullptr) {
|
||||
return; // Both are empty strings.
|
||||
}
|
||||
if ((update_ptr == nullptr) != (cached_ptr == nullptr) || strcmp(update_ptr, cached_ptr) != 0) {
|
||||
unique_ids[p_id].cs = cs;
|
||||
unique_ids[p_id].saved_to_cache = false; //changed
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
String ResourceUID::get_id_path(ID p_id) const {
|
||||
MutexLock l(mutex);
|
||||
ERR_FAIL_COND_V(!unique_ids.has(p_id), String());
|
||||
const CharString &cs = unique_ids[p_id].cs;
|
||||
return String::utf8(cs.ptr());
|
||||
}
|
||||
void ResourceUID::remove_id(ID p_id) {
|
||||
MutexLock l(mutex);
|
||||
ERR_FAIL_COND(!unique_ids.has(p_id));
|
||||
unique_ids.erase(p_id);
|
||||
}
|
||||
|
||||
Error ResourceUID::save_to_cache() {
|
||||
String cache_file = get_cache_file();
|
||||
if (!FileAccess::exists(cache_file)) {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
d->make_dir_recursive(String(cache_file).get_base_dir()); //ensure base dir exists
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(cache_file, FileAccess::WRITE);
|
||||
if (f.is_null()) {
|
||||
return ERR_CANT_OPEN;
|
||||
}
|
||||
|
||||
MutexLock l(mutex);
|
||||
f->store_32(unique_ids.size());
|
||||
|
||||
cache_entries = 0;
|
||||
|
||||
for (KeyValue<ID, Cache> &E : unique_ids) {
|
||||
f->store_64(E.key);
|
||||
uint32_t s = E.value.cs.length();
|
||||
f->store_32(s);
|
||||
f->store_buffer((const uint8_t *)E.value.cs.ptr(), s);
|
||||
E.value.saved_to_cache = true;
|
||||
cache_entries++;
|
||||
}
|
||||
|
||||
changed = false;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ResourceUID::load_from_cache(bool p_reset) {
|
||||
Ref<FileAccess> f = FileAccess::open(get_cache_file(), FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
return ERR_CANT_OPEN;
|
||||
}
|
||||
|
||||
MutexLock l(mutex);
|
||||
if (p_reset) {
|
||||
unique_ids.clear();
|
||||
}
|
||||
|
||||
uint32_t entry_count = f->get_32();
|
||||
for (uint32_t i = 0; i < entry_count; i++) {
|
||||
int64_t id = f->get_64();
|
||||
int32_t len = f->get_32();
|
||||
Cache c;
|
||||
c.cs.resize(len + 1);
|
||||
ERR_FAIL_COND_V(c.cs.size() != len + 1, ERR_FILE_CORRUPT); // out of memory
|
||||
c.cs[len] = 0;
|
||||
int32_t rl = f->get_buffer((uint8_t *)c.cs.ptrw(), len);
|
||||
ERR_FAIL_COND_V(rl != len, ERR_FILE_CORRUPT);
|
||||
|
||||
c.saved_to_cache = true;
|
||||
unique_ids[id] = c;
|
||||
}
|
||||
|
||||
cache_entries = entry_count;
|
||||
changed = false;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ResourceUID::update_cache() {
|
||||
if (!changed) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (cache_entries == 0) {
|
||||
return save_to_cache();
|
||||
}
|
||||
MutexLock l(mutex);
|
||||
|
||||
Ref<FileAccess> f;
|
||||
for (KeyValue<ID, Cache> &E : unique_ids) {
|
||||
if (!E.value.saved_to_cache) {
|
||||
if (f.is_null()) {
|
||||
f = FileAccess::open(get_cache_file(), FileAccess::READ_WRITE); //append
|
||||
if (f.is_null()) {
|
||||
return ERR_CANT_OPEN;
|
||||
}
|
||||
f->seek_end();
|
||||
}
|
||||
f->store_64(E.key);
|
||||
uint32_t s = E.value.cs.length();
|
||||
f->store_32(s);
|
||||
f->store_buffer((const uint8_t *)E.value.cs.ptr(), s);
|
||||
E.value.saved_to_cache = true;
|
||||
cache_entries++;
|
||||
}
|
||||
}
|
||||
|
||||
if (f.is_valid()) {
|
||||
f->seek(0);
|
||||
f->store_32(cache_entries); //update amount of entries
|
||||
}
|
||||
|
||||
changed = false;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void ResourceUID::clear() {
|
||||
cache_entries = 0;
|
||||
unique_ids.clear();
|
||||
changed = false;
|
||||
}
|
||||
void ResourceUID::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("id_to_text", "id"), &ResourceUID::id_to_text);
|
||||
ClassDB::bind_method(D_METHOD("text_to_id", "text_id"), &ResourceUID::text_to_id);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("create_id"), &ResourceUID::create_id);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_id", "id"), &ResourceUID::has_id);
|
||||
ClassDB::bind_method(D_METHOD("add_id", "id", "path"), &ResourceUID::add_id);
|
||||
ClassDB::bind_method(D_METHOD("set_id", "id", "path"), &ResourceUID::set_id);
|
||||
ClassDB::bind_method(D_METHOD("get_id_path", "id"), &ResourceUID::get_id_path);
|
||||
ClassDB::bind_method(D_METHOD("remove_id", "id"), &ResourceUID::remove_id);
|
||||
|
||||
BIND_CONSTANT(INVALID_ID)
|
||||
}
|
||||
ResourceUID *ResourceUID::singleton = nullptr;
|
||||
ResourceUID::ResourceUID() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
singleton = this;
|
||||
crypto = memnew(CryptoCore::RandomGenerator);
|
||||
((CryptoCore::RandomGenerator *)crypto)->init();
|
||||
}
|
||||
ResourceUID::~ResourceUID() {
|
||||
memdelete((CryptoCore::RandomGenerator *)crypto);
|
||||
}
|
||||
88
engine/core/io/resource_uid.h
Normal file
88
engine/core/io/resource_uid.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/**************************************************************************/
|
||||
/* resource_uid.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef RESOURCE_UID_H
|
||||
#define RESOURCE_UID_H
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
|
||||
class ResourceUID : public Object {
|
||||
GDCLASS(ResourceUID, Object)
|
||||
public:
|
||||
typedef int64_t ID;
|
||||
enum {
|
||||
INVALID_ID = -1
|
||||
};
|
||||
|
||||
static String get_cache_file();
|
||||
|
||||
private:
|
||||
void *crypto = nullptr; // CryptoCore::RandomGenerator (avoid including crypto_core.h)
|
||||
Mutex mutex;
|
||||
struct Cache {
|
||||
CharString cs;
|
||||
bool saved_to_cache = false;
|
||||
};
|
||||
|
||||
HashMap<ID, Cache> unique_ids; //unique IDs and utf8 paths (less memory used)
|
||||
static ResourceUID *singleton;
|
||||
|
||||
uint32_t cache_entries = 0;
|
||||
bool changed = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
String id_to_text(ID p_id) const;
|
||||
ID text_to_id(const String &p_text) const;
|
||||
|
||||
ID create_id();
|
||||
bool has_id(ID p_id) const;
|
||||
void add_id(ID p_id, const String &p_path);
|
||||
void set_id(ID p_id, const String &p_path);
|
||||
String get_id_path(ID p_id) const;
|
||||
void remove_id(ID p_id);
|
||||
|
||||
Error load_from_cache(bool p_reset);
|
||||
Error save_to_cache();
|
||||
Error update_cache();
|
||||
|
||||
void clear();
|
||||
|
||||
static ResourceUID *get_singleton() { return singleton; }
|
||||
|
||||
ResourceUID();
|
||||
~ResourceUID();
|
||||
};
|
||||
|
||||
#endif // RESOURCE_UID_H
|
||||
566
engine/core/io/stream_peer.cpp
Normal file
566
engine/core/io/stream_peer.cpp
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
/**************************************************************************/
|
||||
/* stream_peer.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 "stream_peer.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
|
||||
Error StreamPeer::_put_data(const Vector<uint8_t> &p_data) {
|
||||
int len = p_data.size();
|
||||
if (len == 0) {
|
||||
return OK;
|
||||
}
|
||||
const uint8_t *r = p_data.ptr();
|
||||
return put_data(&r[0], len);
|
||||
}
|
||||
|
||||
Array StreamPeer::_put_partial_data(const Vector<uint8_t> &p_data) {
|
||||
Array ret;
|
||||
|
||||
int len = p_data.size();
|
||||
if (len == 0) {
|
||||
ret.push_back(OK);
|
||||
ret.push_back(0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const uint8_t *r = p_data.ptr();
|
||||
int sent;
|
||||
Error err = put_partial_data(&r[0], len, sent);
|
||||
|
||||
if (err != OK) {
|
||||
sent = 0;
|
||||
}
|
||||
ret.push_back(err);
|
||||
ret.push_back(sent);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Array StreamPeer::_get_data(int p_bytes) {
|
||||
Array ret;
|
||||
|
||||
Vector<uint8_t> data;
|
||||
data.resize(p_bytes);
|
||||
if (data.size() != p_bytes) {
|
||||
ret.push_back(ERR_OUT_OF_MEMORY);
|
||||
ret.push_back(Vector<uint8_t>());
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t *w = data.ptrw();
|
||||
Error err = get_data(&w[0], p_bytes);
|
||||
|
||||
ret.push_back(err);
|
||||
ret.push_back(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Array StreamPeer::_get_partial_data(int p_bytes) {
|
||||
Array ret;
|
||||
|
||||
Vector<uint8_t> data;
|
||||
data.resize(p_bytes);
|
||||
if (data.size() != p_bytes) {
|
||||
ret.push_back(ERR_OUT_OF_MEMORY);
|
||||
ret.push_back(Vector<uint8_t>());
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t *w = data.ptrw();
|
||||
int received;
|
||||
Error err = get_partial_data(&w[0], p_bytes, received);
|
||||
|
||||
if (err != OK) {
|
||||
data.clear();
|
||||
} else if (received != data.size()) {
|
||||
data.resize(received);
|
||||
}
|
||||
|
||||
ret.push_back(err);
|
||||
ret.push_back(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void StreamPeer::set_big_endian(bool p_big_endian) {
|
||||
big_endian = p_big_endian;
|
||||
}
|
||||
|
||||
bool StreamPeer::is_big_endian_enabled() const {
|
||||
return big_endian;
|
||||
}
|
||||
|
||||
void StreamPeer::put_u8(uint8_t p_val) {
|
||||
put_data((const uint8_t *)&p_val, 1);
|
||||
}
|
||||
|
||||
void StreamPeer::put_8(int8_t p_val) {
|
||||
put_data((const uint8_t *)&p_val, 1);
|
||||
}
|
||||
|
||||
void StreamPeer::put_u16(uint16_t p_val) {
|
||||
if (big_endian) {
|
||||
p_val = BSWAP16(p_val);
|
||||
}
|
||||
uint8_t buf[2];
|
||||
encode_uint16(p_val, buf);
|
||||
put_data(buf, 2);
|
||||
}
|
||||
|
||||
void StreamPeer::put_16(int16_t p_val) {
|
||||
if (big_endian) {
|
||||
p_val = BSWAP16(p_val);
|
||||
}
|
||||
uint8_t buf[2];
|
||||
encode_uint16(p_val, buf);
|
||||
put_data(buf, 2);
|
||||
}
|
||||
|
||||
void StreamPeer::put_u32(uint32_t p_val) {
|
||||
if (big_endian) {
|
||||
p_val = BSWAP32(p_val);
|
||||
}
|
||||
uint8_t buf[4];
|
||||
encode_uint32(p_val, buf);
|
||||
put_data(buf, 4);
|
||||
}
|
||||
|
||||
void StreamPeer::put_32(int32_t p_val) {
|
||||
if (big_endian) {
|
||||
p_val = BSWAP32(p_val);
|
||||
}
|
||||
uint8_t buf[4];
|
||||
encode_uint32(p_val, buf);
|
||||
put_data(buf, 4);
|
||||
}
|
||||
|
||||
void StreamPeer::put_u64(uint64_t p_val) {
|
||||
if (big_endian) {
|
||||
p_val = BSWAP64(p_val);
|
||||
}
|
||||
uint8_t buf[8];
|
||||
encode_uint64(p_val, buf);
|
||||
put_data(buf, 8);
|
||||
}
|
||||
|
||||
void StreamPeer::put_64(int64_t p_val) {
|
||||
if (big_endian) {
|
||||
p_val = BSWAP64(p_val);
|
||||
}
|
||||
uint8_t buf[8];
|
||||
encode_uint64(p_val, buf);
|
||||
put_data(buf, 8);
|
||||
}
|
||||
|
||||
void StreamPeer::put_float(float p_val) {
|
||||
uint8_t buf[4];
|
||||
|
||||
encode_float(p_val, buf);
|
||||
if (big_endian) {
|
||||
uint32_t *p32 = (uint32_t *)buf;
|
||||
*p32 = BSWAP32(*p32);
|
||||
}
|
||||
|
||||
put_data(buf, 4);
|
||||
}
|
||||
|
||||
void StreamPeer::put_double(double p_val) {
|
||||
uint8_t buf[8];
|
||||
encode_double(p_val, buf);
|
||||
if (big_endian) {
|
||||
uint64_t *p64 = (uint64_t *)buf;
|
||||
*p64 = BSWAP64(*p64);
|
||||
}
|
||||
put_data(buf, 8);
|
||||
}
|
||||
|
||||
void StreamPeer::put_string(const String &p_string) {
|
||||
CharString cs = p_string.ascii();
|
||||
put_u32(cs.length());
|
||||
put_data((const uint8_t *)cs.get_data(), cs.length());
|
||||
}
|
||||
|
||||
void StreamPeer::put_utf8_string(const String &p_string) {
|
||||
CharString cs = p_string.utf8();
|
||||
put_u32(cs.length());
|
||||
put_data((const uint8_t *)cs.get_data(), cs.length());
|
||||
}
|
||||
|
||||
void StreamPeer::put_var(const Variant &p_variant, bool p_full_objects) {
|
||||
int len = 0;
|
||||
Vector<uint8_t> buf;
|
||||
encode_variant(p_variant, nullptr, len, p_full_objects);
|
||||
buf.resize(len);
|
||||
put_32(len);
|
||||
encode_variant(p_variant, buf.ptrw(), len, p_full_objects);
|
||||
put_data(buf.ptr(), buf.size());
|
||||
}
|
||||
|
||||
uint8_t StreamPeer::get_u8() {
|
||||
uint8_t buf[1];
|
||||
get_data(buf, 1);
|
||||
return buf[0];
|
||||
}
|
||||
|
||||
int8_t StreamPeer::get_8() {
|
||||
uint8_t buf[1];
|
||||
get_data(buf, 1);
|
||||
return buf[0];
|
||||
}
|
||||
|
||||
uint16_t StreamPeer::get_u16() {
|
||||
uint8_t buf[2];
|
||||
get_data(buf, 2);
|
||||
uint16_t r = decode_uint16(buf);
|
||||
if (big_endian) {
|
||||
r = BSWAP16(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int16_t StreamPeer::get_16() {
|
||||
uint8_t buf[2];
|
||||
get_data(buf, 2);
|
||||
uint16_t r = decode_uint16(buf);
|
||||
if (big_endian) {
|
||||
r = BSWAP16(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
uint32_t StreamPeer::get_u32() {
|
||||
uint8_t buf[4];
|
||||
get_data(buf, 4);
|
||||
uint32_t r = decode_uint32(buf);
|
||||
if (big_endian) {
|
||||
r = BSWAP32(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int32_t StreamPeer::get_32() {
|
||||
uint8_t buf[4];
|
||||
get_data(buf, 4);
|
||||
uint32_t r = decode_uint32(buf);
|
||||
if (big_endian) {
|
||||
r = BSWAP32(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
uint64_t StreamPeer::get_u64() {
|
||||
uint8_t buf[8];
|
||||
get_data(buf, 8);
|
||||
uint64_t r = decode_uint64(buf);
|
||||
if (big_endian) {
|
||||
r = BSWAP64(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int64_t StreamPeer::get_64() {
|
||||
uint8_t buf[8];
|
||||
get_data(buf, 8);
|
||||
uint64_t r = decode_uint64(buf);
|
||||
if (big_endian) {
|
||||
r = BSWAP64(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
float StreamPeer::get_float() {
|
||||
uint8_t buf[4];
|
||||
get_data(buf, 4);
|
||||
|
||||
if (big_endian) {
|
||||
uint32_t *p32 = (uint32_t *)buf;
|
||||
*p32 = BSWAP32(*p32);
|
||||
}
|
||||
|
||||
return decode_float(buf);
|
||||
}
|
||||
|
||||
double StreamPeer::get_double() {
|
||||
uint8_t buf[8];
|
||||
get_data(buf, 8);
|
||||
|
||||
if (big_endian) {
|
||||
uint64_t *p64 = (uint64_t *)buf;
|
||||
*p64 = BSWAP64(*p64);
|
||||
}
|
||||
|
||||
return decode_double(buf);
|
||||
}
|
||||
|
||||
String StreamPeer::get_string(int p_bytes) {
|
||||
if (p_bytes < 0) {
|
||||
p_bytes = get_u32();
|
||||
}
|
||||
ERR_FAIL_COND_V(p_bytes < 0, String());
|
||||
|
||||
Vector<char> buf;
|
||||
Error err = buf.resize(p_bytes + 1);
|
||||
ERR_FAIL_COND_V(err != OK, String());
|
||||
err = get_data((uint8_t *)&buf[0], p_bytes);
|
||||
ERR_FAIL_COND_V(err != OK, String());
|
||||
buf.write[p_bytes] = 0;
|
||||
return buf.ptr();
|
||||
}
|
||||
|
||||
String StreamPeer::get_utf8_string(int p_bytes) {
|
||||
if (p_bytes < 0) {
|
||||
p_bytes = get_u32();
|
||||
}
|
||||
ERR_FAIL_COND_V(p_bytes < 0, String());
|
||||
|
||||
Vector<uint8_t> buf;
|
||||
Error err = buf.resize(p_bytes);
|
||||
ERR_FAIL_COND_V(err != OK, String());
|
||||
err = get_data(buf.ptrw(), p_bytes);
|
||||
ERR_FAIL_COND_V(err != OK, String());
|
||||
|
||||
String ret;
|
||||
ret.parse_utf8((const char *)buf.ptr(), buf.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
Variant StreamPeer::get_var(bool p_allow_objects) {
|
||||
int len = get_32();
|
||||
Vector<uint8_t> var;
|
||||
Error err = var.resize(len);
|
||||
ERR_FAIL_COND_V(err != OK, Variant());
|
||||
err = get_data(var.ptrw(), len);
|
||||
ERR_FAIL_COND_V(err != OK, Variant());
|
||||
|
||||
Variant ret;
|
||||
err = decode_variant(ret, var.ptr(), len, nullptr, p_allow_objects);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, Variant(), "Error when trying to decode Variant.");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void StreamPeer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("put_data", "data"), &StreamPeer::_put_data);
|
||||
ClassDB::bind_method(D_METHOD("put_partial_data", "data"), &StreamPeer::_put_partial_data);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_data", "bytes"), &StreamPeer::_get_data);
|
||||
ClassDB::bind_method(D_METHOD("get_partial_data", "bytes"), &StreamPeer::_get_partial_data);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_available_bytes"), &StreamPeer::get_available_bytes);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_big_endian", "enable"), &StreamPeer::set_big_endian);
|
||||
ClassDB::bind_method(D_METHOD("is_big_endian_enabled"), &StreamPeer::is_big_endian_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("put_8", "value"), &StreamPeer::put_8);
|
||||
ClassDB::bind_method(D_METHOD("put_u8", "value"), &StreamPeer::put_u8);
|
||||
ClassDB::bind_method(D_METHOD("put_16", "value"), &StreamPeer::put_16);
|
||||
ClassDB::bind_method(D_METHOD("put_u16", "value"), &StreamPeer::put_u16);
|
||||
ClassDB::bind_method(D_METHOD("put_32", "value"), &StreamPeer::put_32);
|
||||
ClassDB::bind_method(D_METHOD("put_u32", "value"), &StreamPeer::put_u32);
|
||||
ClassDB::bind_method(D_METHOD("put_64", "value"), &StreamPeer::put_64);
|
||||
ClassDB::bind_method(D_METHOD("put_u64", "value"), &StreamPeer::put_u64);
|
||||
ClassDB::bind_method(D_METHOD("put_float", "value"), &StreamPeer::put_float);
|
||||
ClassDB::bind_method(D_METHOD("put_double", "value"), &StreamPeer::put_double);
|
||||
ClassDB::bind_method(D_METHOD("put_string", "value"), &StreamPeer::put_string);
|
||||
ClassDB::bind_method(D_METHOD("put_utf8_string", "value"), &StreamPeer::put_utf8_string);
|
||||
ClassDB::bind_method(D_METHOD("put_var", "value", "full_objects"), &StreamPeer::put_var, DEFVAL(false));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_8"), &StreamPeer::get_8);
|
||||
ClassDB::bind_method(D_METHOD("get_u8"), &StreamPeer::get_u8);
|
||||
ClassDB::bind_method(D_METHOD("get_16"), &StreamPeer::get_16);
|
||||
ClassDB::bind_method(D_METHOD("get_u16"), &StreamPeer::get_u16);
|
||||
ClassDB::bind_method(D_METHOD("get_32"), &StreamPeer::get_32);
|
||||
ClassDB::bind_method(D_METHOD("get_u32"), &StreamPeer::get_u32);
|
||||
ClassDB::bind_method(D_METHOD("get_64"), &StreamPeer::get_64);
|
||||
ClassDB::bind_method(D_METHOD("get_u64"), &StreamPeer::get_u64);
|
||||
ClassDB::bind_method(D_METHOD("get_float"), &StreamPeer::get_float);
|
||||
ClassDB::bind_method(D_METHOD("get_double"), &StreamPeer::get_double);
|
||||
ClassDB::bind_method(D_METHOD("get_string", "bytes"), &StreamPeer::get_string, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("get_utf8_string", "bytes"), &StreamPeer::get_utf8_string, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("get_var", "allow_objects"), &StreamPeer::get_var, DEFVAL(false));
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "big_endian"), "set_big_endian", "is_big_endian_enabled");
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
Error StreamPeerExtension::get_data(uint8_t *r_buffer, int p_bytes) {
|
||||
Error err;
|
||||
int received = 0;
|
||||
if (GDVIRTUAL_CALL(_get_data, r_buffer, p_bytes, &received, err)) {
|
||||
return err;
|
||||
}
|
||||
WARN_PRINT_ONCE("StreamPeerExtension::_get_data is unimplemented!");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
Error StreamPeerExtension::get_partial_data(uint8_t *r_buffer, int p_bytes, int &r_received) {
|
||||
Error err;
|
||||
if (GDVIRTUAL_CALL(_get_partial_data, r_buffer, p_bytes, &r_received, err)) {
|
||||
return err;
|
||||
}
|
||||
WARN_PRINT_ONCE("StreamPeerExtension::_get_partial_data is unimplemented!");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
Error StreamPeerExtension::put_data(const uint8_t *p_data, int p_bytes) {
|
||||
Error err;
|
||||
int sent = 0;
|
||||
if (GDVIRTUAL_CALL(_put_data, p_data, p_bytes, &sent, err)) {
|
||||
return err;
|
||||
}
|
||||
WARN_PRINT_ONCE("StreamPeerExtension::_put_data is unimplemented!");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
Error StreamPeerExtension::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) {
|
||||
Error err;
|
||||
if (GDVIRTUAL_CALL(_put_data, p_data, p_bytes, &r_sent, err)) {
|
||||
return err;
|
||||
}
|
||||
WARN_PRINT_ONCE("StreamPeerExtension::_put_partial_data is unimplemented!");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
void StreamPeerExtension::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_get_data, "r_buffer", "r_bytes", "r_received");
|
||||
GDVIRTUAL_BIND(_get_partial_data, "r_buffer", "r_bytes", "r_received");
|
||||
GDVIRTUAL_BIND(_put_data, "p_data", "p_bytes", "r_sent");
|
||||
GDVIRTUAL_BIND(_put_partial_data, "p_data", "p_bytes", "r_sent");
|
||||
GDVIRTUAL_BIND(_get_available_bytes);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
void StreamPeerBuffer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("seek", "position"), &StreamPeerBuffer::seek);
|
||||
ClassDB::bind_method(D_METHOD("get_size"), &StreamPeerBuffer::get_size);
|
||||
ClassDB::bind_method(D_METHOD("get_position"), &StreamPeerBuffer::get_position);
|
||||
ClassDB::bind_method(D_METHOD("resize", "size"), &StreamPeerBuffer::resize);
|
||||
ClassDB::bind_method(D_METHOD("set_data_array", "data"), &StreamPeerBuffer::set_data_array);
|
||||
ClassDB::bind_method(D_METHOD("get_data_array"), &StreamPeerBuffer::get_data_array);
|
||||
ClassDB::bind_method(D_METHOD("clear"), &StreamPeerBuffer::clear);
|
||||
ClassDB::bind_method(D_METHOD("duplicate"), &StreamPeerBuffer::duplicate);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data_array"), "set_data_array", "get_data_array");
|
||||
}
|
||||
|
||||
Error StreamPeerBuffer::put_data(const uint8_t *p_data, int p_bytes) {
|
||||
if (p_bytes <= 0) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (pointer + p_bytes > data.size()) {
|
||||
data.resize(pointer + p_bytes);
|
||||
}
|
||||
|
||||
uint8_t *w = data.ptrw();
|
||||
memcpy(&w[pointer], p_data, p_bytes);
|
||||
|
||||
pointer += p_bytes;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerBuffer::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) {
|
||||
r_sent = p_bytes;
|
||||
return put_data(p_data, p_bytes);
|
||||
}
|
||||
|
||||
Error StreamPeerBuffer::get_data(uint8_t *p_buffer, int p_bytes) {
|
||||
int recv;
|
||||
get_partial_data(p_buffer, p_bytes, recv);
|
||||
if (recv != p_bytes) {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerBuffer::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
|
||||
if (pointer + p_bytes > data.size()) {
|
||||
r_received = data.size() - pointer;
|
||||
if (r_received <= 0) {
|
||||
r_received = 0;
|
||||
return OK; //you got 0
|
||||
}
|
||||
} else {
|
||||
r_received = p_bytes;
|
||||
}
|
||||
|
||||
const uint8_t *r = data.ptr();
|
||||
memcpy(p_buffer, r + pointer, r_received);
|
||||
|
||||
pointer += r_received;
|
||||
// FIXME: return what? OK or ERR_*
|
||||
// return OK for now so we don't maybe return garbage
|
||||
return OK;
|
||||
}
|
||||
|
||||
int StreamPeerBuffer::get_available_bytes() const {
|
||||
return data.size() - pointer;
|
||||
}
|
||||
|
||||
void StreamPeerBuffer::seek(int p_pos) {
|
||||
ERR_FAIL_COND(p_pos < 0);
|
||||
ERR_FAIL_COND(p_pos > data.size());
|
||||
pointer = p_pos;
|
||||
}
|
||||
|
||||
int StreamPeerBuffer::get_size() const {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
int StreamPeerBuffer::get_position() const {
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void StreamPeerBuffer::resize(int p_size) {
|
||||
data.resize(p_size);
|
||||
}
|
||||
|
||||
void StreamPeerBuffer::set_data_array(const Vector<uint8_t> &p_data) {
|
||||
data = p_data;
|
||||
pointer = 0;
|
||||
}
|
||||
|
||||
Vector<uint8_t> StreamPeerBuffer::get_data_array() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
void StreamPeerBuffer::clear() {
|
||||
data.clear();
|
||||
pointer = 0;
|
||||
}
|
||||
|
||||
Ref<StreamPeerBuffer> StreamPeerBuffer::duplicate() const {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->data = data;
|
||||
return spb;
|
||||
}
|
||||
154
engine/core/io/stream_peer.h
Normal file
154
engine/core/io/stream_peer.h
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/**************************************************************************/
|
||||
/* stream_peer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef STREAM_PEER_H
|
||||
#define STREAM_PEER_H
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
#include "core/extension/ext_wrappers.gen.inc"
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/variant/native_ptr.h"
|
||||
|
||||
class StreamPeer : public RefCounted {
|
||||
GDCLASS(StreamPeer, RefCounted);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
//bind helpers
|
||||
Error _put_data(const Vector<uint8_t> &p_data);
|
||||
Array _put_partial_data(const Vector<uint8_t> &p_data);
|
||||
|
||||
Array _get_data(int p_bytes);
|
||||
Array _get_partial_data(int p_bytes);
|
||||
|
||||
bool big_endian = false;
|
||||
|
||||
public:
|
||||
virtual Error put_data(const uint8_t *p_data, int p_bytes) = 0; ///< put a whole chunk of data, blocking until it sent
|
||||
virtual Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) = 0; ///< put as much data as possible, without blocking.
|
||||
|
||||
virtual Error get_data(uint8_t *p_buffer, int p_bytes) = 0; ///< read p_bytes of data, if p_bytes > available, it will block
|
||||
virtual Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) = 0; ///< read as much data as p_bytes into buffer, if less was read, return in r_received
|
||||
|
||||
virtual int get_available_bytes() const = 0;
|
||||
|
||||
/* helpers */
|
||||
void set_big_endian(bool p_big_endian);
|
||||
bool is_big_endian_enabled() const;
|
||||
|
||||
void put_8(int8_t p_val);
|
||||
void put_u8(uint8_t p_val);
|
||||
void put_16(int16_t p_val);
|
||||
void put_u16(uint16_t p_val);
|
||||
void put_32(int32_t p_val);
|
||||
void put_u32(uint32_t p_val);
|
||||
void put_64(int64_t p_val);
|
||||
void put_u64(uint64_t p_val);
|
||||
void put_float(float p_val);
|
||||
void put_double(double p_val);
|
||||
void put_string(const String &p_string);
|
||||
void put_utf8_string(const String &p_string);
|
||||
void put_var(const Variant &p_variant, bool p_full_objects = false);
|
||||
|
||||
uint8_t get_u8();
|
||||
int8_t get_8();
|
||||
uint16_t get_u16();
|
||||
int16_t get_16();
|
||||
uint32_t get_u32();
|
||||
int32_t get_32();
|
||||
uint64_t get_u64();
|
||||
int64_t get_64();
|
||||
float get_float();
|
||||
double get_double();
|
||||
String get_string(int p_bytes = -1);
|
||||
String get_utf8_string(int p_bytes = -1);
|
||||
Variant get_var(bool p_allow_objects = false);
|
||||
|
||||
StreamPeer() {}
|
||||
};
|
||||
|
||||
class StreamPeerExtension : public StreamPeer {
|
||||
GDCLASS(StreamPeerExtension, StreamPeer);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual Error put_data(const uint8_t *p_data, int p_bytes) override;
|
||||
GDVIRTUAL3R(Error, _put_data, GDExtensionConstPtr<const uint8_t>, int, GDExtensionPtr<int>);
|
||||
|
||||
virtual Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override;
|
||||
GDVIRTUAL3R(Error, _put_partial_data, GDExtensionConstPtr<const uint8_t>, int, GDExtensionPtr<int>);
|
||||
|
||||
virtual Error get_data(uint8_t *p_buffer, int p_bytes) override;
|
||||
GDVIRTUAL3R(Error, _get_data, GDExtensionPtr<uint8_t>, int, GDExtensionPtr<int>);
|
||||
|
||||
virtual Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override;
|
||||
GDVIRTUAL3R(Error, _get_partial_data, GDExtensionPtr<uint8_t>, int, GDExtensionPtr<int>);
|
||||
|
||||
EXBIND0RC(int, get_available_bytes);
|
||||
};
|
||||
|
||||
class StreamPeerBuffer : public StreamPeer {
|
||||
GDCLASS(StreamPeerBuffer, StreamPeer);
|
||||
|
||||
Vector<uint8_t> data;
|
||||
int pointer = 0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Error put_data(const uint8_t *p_data, int p_bytes) override;
|
||||
Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override;
|
||||
|
||||
Error get_data(uint8_t *p_buffer, int p_bytes) override;
|
||||
Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override;
|
||||
|
||||
virtual int get_available_bytes() const override;
|
||||
|
||||
void seek(int p_pos);
|
||||
int get_size() const;
|
||||
int get_position() const;
|
||||
void resize(int p_size);
|
||||
|
||||
void set_data_array(const Vector<uint8_t> &p_data);
|
||||
Vector<uint8_t> get_data_array() const;
|
||||
|
||||
void clear();
|
||||
|
||||
Ref<StreamPeerBuffer> duplicate() const;
|
||||
|
||||
StreamPeerBuffer() {}
|
||||
};
|
||||
|
||||
#endif // STREAM_PEER_H
|
||||
210
engine/core/io/stream_peer_gzip.cpp
Normal file
210
engine/core/io/stream_peer_gzip.cpp
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/**************************************************************************/
|
||||
/* stream_peer_gzip.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 "core/io/stream_peer_gzip.h"
|
||||
|
||||
#include "core/io/zip_io.h"
|
||||
#include <zlib.h>
|
||||
|
||||
void StreamPeerGZIP::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("start_compression", "use_deflate", "buffer_size"), &StreamPeerGZIP::start_compression, DEFVAL(false), DEFVAL(65535));
|
||||
ClassDB::bind_method(D_METHOD("start_decompression", "use_deflate", "buffer_size"), &StreamPeerGZIP::start_decompression, DEFVAL(false), DEFVAL(65535));
|
||||
ClassDB::bind_method(D_METHOD("finish"), &StreamPeerGZIP::finish);
|
||||
ClassDB::bind_method(D_METHOD("clear"), &StreamPeerGZIP::clear);
|
||||
}
|
||||
|
||||
StreamPeerGZIP::StreamPeerGZIP() {
|
||||
}
|
||||
|
||||
StreamPeerGZIP::~StreamPeerGZIP() {
|
||||
_close();
|
||||
}
|
||||
|
||||
void StreamPeerGZIP::_close() {
|
||||
if (ctx) {
|
||||
z_stream *strm = (z_stream *)ctx;
|
||||
if (compressing) {
|
||||
deflateEnd(strm);
|
||||
} else {
|
||||
inflateEnd(strm);
|
||||
}
|
||||
memfree(strm);
|
||||
ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void StreamPeerGZIP::clear() {
|
||||
_close();
|
||||
rb.clear();
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::start_compression(bool p_is_deflate, int buffer_size) {
|
||||
return _start(true, p_is_deflate, buffer_size);
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::start_decompression(bool p_is_deflate, int buffer_size) {
|
||||
return _start(false, p_is_deflate, buffer_size);
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::_start(bool p_compress, bool p_is_deflate, int buffer_size) {
|
||||
ERR_FAIL_COND_V(ctx != nullptr, ERR_ALREADY_IN_USE);
|
||||
ERR_FAIL_COND_V_MSG(buffer_size <= 0, ERR_INVALID_PARAMETER, "Invalid buffer size. It should be a positive integer.");
|
||||
clear();
|
||||
compressing = p_compress;
|
||||
rb.resize(nearest_shift(buffer_size - 1));
|
||||
buffer.resize(1024);
|
||||
|
||||
// Create ctx.
|
||||
ctx = memalloc(sizeof(z_stream));
|
||||
z_stream &strm = *(z_stream *)ctx;
|
||||
strm.next_in = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.zalloc = zipio_alloc;
|
||||
strm.zfree = zipio_free;
|
||||
strm.opaque = Z_NULL;
|
||||
int window_bits = p_is_deflate ? 15 : (15 + 16);
|
||||
int err = Z_OK;
|
||||
int level = Z_DEFAULT_COMPRESSION;
|
||||
if (compressing) {
|
||||
err = deflateInit2(&strm, level, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
|
||||
} else {
|
||||
err = inflateInit2(&strm, window_bits);
|
||||
}
|
||||
ERR_FAIL_COND_V(err != Z_OK, FAILED);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::_process(uint8_t *p_dst, int p_dst_size, const uint8_t *p_src, int p_src_size, int &r_consumed, int &r_out, bool p_close) {
|
||||
ERR_FAIL_NULL_V(ctx, ERR_UNCONFIGURED);
|
||||
z_stream &strm = *(z_stream *)ctx;
|
||||
strm.avail_in = p_src_size;
|
||||
strm.avail_out = p_dst_size;
|
||||
strm.next_in = (Bytef *)p_src;
|
||||
strm.next_out = (Bytef *)p_dst;
|
||||
int flush = p_close ? Z_FINISH : Z_NO_FLUSH;
|
||||
if (compressing) {
|
||||
int err = deflate(&strm, flush);
|
||||
ERR_FAIL_COND_V(err != (p_close ? Z_STREAM_END : Z_OK), FAILED);
|
||||
} else {
|
||||
int err = inflate(&strm, flush);
|
||||
ERR_FAIL_COND_V(err != Z_OK && err != Z_STREAM_END, FAILED);
|
||||
}
|
||||
r_out = p_dst_size - strm.avail_out;
|
||||
r_consumed = p_src_size - strm.avail_in;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::put_data(const uint8_t *p_data, int p_bytes) {
|
||||
int wrote = 0;
|
||||
Error err = put_partial_data(p_data, p_bytes, wrote);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
ERR_FAIL_COND_V(p_bytes != wrote, ERR_OUT_OF_MEMORY);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) {
|
||||
ERR_FAIL_NULL_V(ctx, ERR_UNCONFIGURED);
|
||||
ERR_FAIL_COND_V(p_bytes < 0, ERR_INVALID_PARAMETER);
|
||||
|
||||
// Ensure we have enough space in temporary buffer.
|
||||
if (buffer.size() < p_bytes) {
|
||||
buffer.resize(p_bytes);
|
||||
}
|
||||
|
||||
r_sent = 0;
|
||||
while (r_sent < p_bytes && rb.space_left() > 1024) { // Keep the ring buffer size meaningful.
|
||||
int sent = 0;
|
||||
int to_write = 0;
|
||||
// Compress or decompress
|
||||
Error err = _process(buffer.ptrw(), MIN(buffer.size(), rb.space_left()), p_data + r_sent, p_bytes - r_sent, sent, to_write);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
// When decompressing, we might need to do another round.
|
||||
r_sent += sent;
|
||||
|
||||
// We can't write more than this buffer is full.
|
||||
if (sent == 0 && to_write == 0) {
|
||||
return OK;
|
||||
}
|
||||
if (to_write) {
|
||||
// Copy to ring buffer.
|
||||
int wrote = rb.write(buffer.ptr(), to_write);
|
||||
ERR_FAIL_COND_V(wrote != to_write, ERR_BUG);
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::get_data(uint8_t *p_buffer, int p_bytes) {
|
||||
int received = 0;
|
||||
Error err = get_partial_data(p_buffer, p_bytes, received);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
ERR_FAIL_COND_V(p_bytes != received, ERR_UNAVAILABLE);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
|
||||
ERR_FAIL_COND_V(p_bytes < 0, ERR_INVALID_PARAMETER);
|
||||
|
||||
r_received = MIN(p_bytes, rb.data_left());
|
||||
if (r_received == 0) {
|
||||
return OK;
|
||||
}
|
||||
int received = rb.read(p_buffer, r_received);
|
||||
ERR_FAIL_COND_V(received != r_received, ERR_BUG);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int StreamPeerGZIP::get_available_bytes() const {
|
||||
return rb.data_left();
|
||||
}
|
||||
|
||||
Error StreamPeerGZIP::finish() {
|
||||
ERR_FAIL_COND_V(!ctx || !compressing, ERR_UNAVAILABLE);
|
||||
// Ensure we have enough space in temporary buffer.
|
||||
if (buffer.size() < 1024) {
|
||||
buffer.resize(1024); // 1024 should be more than enough.
|
||||
}
|
||||
int consumed = 0;
|
||||
int to_write = 0;
|
||||
Error err = _process(buffer.ptrw(), 1024, nullptr, 0, consumed, to_write, true); // compress
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
int wrote = rb.write(buffer.ptr(), to_write);
|
||||
ERR_FAIL_COND_V(wrote != to_write, ERR_OUT_OF_MEMORY);
|
||||
return OK;
|
||||
}
|
||||
76
engine/core/io/stream_peer_gzip.h
Normal file
76
engine/core/io/stream_peer_gzip.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**************************************************************************/
|
||||
/* stream_peer_gzip.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef STREAM_PEER_GZIP_H
|
||||
#define STREAM_PEER_GZIP_H
|
||||
|
||||
#include "core/io/stream_peer.h"
|
||||
|
||||
#include "core/core_bind.h"
|
||||
#include "core/io/compression.h"
|
||||
#include "core/templates/ring_buffer.h"
|
||||
|
||||
class StreamPeerGZIP : public StreamPeer {
|
||||
GDCLASS(StreamPeerGZIP, StreamPeer);
|
||||
|
||||
private:
|
||||
void *ctx = nullptr; // Will hold our z_stream instance.
|
||||
bool compressing = true;
|
||||
|
||||
RingBuffer<uint8_t> rb;
|
||||
Vector<uint8_t> buffer;
|
||||
|
||||
Error _process(uint8_t *p_dst, int p_dst_size, const uint8_t *p_src, int p_src_size, int &r_consumed, int &r_out, bool p_close = false);
|
||||
void _close();
|
||||
Error _start(bool p_compress, bool p_is_deflate, int buffer_size = 65535);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Error start_compression(bool p_is_deflate, int buffer_size = 65535);
|
||||
Error start_decompression(bool p_is_deflate, int buffer_size = 65535);
|
||||
|
||||
Error finish();
|
||||
void clear();
|
||||
|
||||
virtual Error put_data(const uint8_t *p_data, int p_bytes) override;
|
||||
virtual Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override;
|
||||
|
||||
virtual Error get_data(uint8_t *p_buffer, int p_bytes) override;
|
||||
virtual Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override;
|
||||
|
||||
virtual int get_available_bytes() const override;
|
||||
|
||||
StreamPeerGZIP();
|
||||
~StreamPeerGZIP();
|
||||
};
|
||||
|
||||
#endif // STREAM_PEER_GZIP_H
|
||||
339
engine/core/io/stream_peer_tcp.cpp
Normal file
339
engine/core/io/stream_peer_tcp.cpp
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
/**************************************************************************/
|
||||
/* stream_peer_tcp.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 "stream_peer_tcp.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
Error StreamPeerTCP::poll() {
|
||||
if (status == STATUS_CONNECTED) {
|
||||
Error err;
|
||||
err = _sock->poll(NetSocket::POLL_TYPE_IN, 0);
|
||||
if (err == OK) {
|
||||
// FIN received
|
||||
if (_sock->get_available_bytes() == 0) {
|
||||
disconnect_from_host();
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
// Also poll write
|
||||
err = _sock->poll(NetSocket::POLL_TYPE_IN_OUT, 0);
|
||||
if (err != OK && err != ERR_BUSY) {
|
||||
// Got an error
|
||||
disconnect_from_host();
|
||||
status = STATUS_ERROR;
|
||||
return err;
|
||||
}
|
||||
return OK;
|
||||
} else if (status != STATUS_CONNECTING) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error err = _sock->connect_to_host(peer_host, peer_port);
|
||||
|
||||
if (err == OK) {
|
||||
status = STATUS_CONNECTED;
|
||||
return OK;
|
||||
} else if (err == ERR_BUSY) {
|
||||
// Check for connect timeout
|
||||
if (OS::get_singleton()->get_ticks_msec() > timeout) {
|
||||
disconnect_from_host();
|
||||
status = STATUS_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
// Still trying to connect
|
||||
return OK;
|
||||
}
|
||||
|
||||
disconnect_from_host();
|
||||
status = STATUS_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
|
||||
void StreamPeerTCP::accept_socket(Ref<NetSocket> p_sock, IPAddress p_host, uint16_t p_port) {
|
||||
_sock = p_sock;
|
||||
_sock->set_blocking_enabled(false);
|
||||
|
||||
timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000);
|
||||
status = STATUS_CONNECTED;
|
||||
|
||||
peer_host = p_host;
|
||||
peer_port = p_port;
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::bind(int p_port, const IPAddress &p_host) {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE);
|
||||
ERR_FAIL_COND_V_MSG(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER, "The local port number must be between 0 and 65535 (inclusive).");
|
||||
|
||||
IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
|
||||
if (p_host.is_wildcard()) {
|
||||
ip_type = IP::TYPE_ANY;
|
||||
}
|
||||
Error err = _sock->open(NetSocket::TYPE_TCP, ip_type);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
_sock->set_blocking_enabled(false);
|
||||
return _sock->bind(p_host, p_port);
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(status != STATUS_NONE, ERR_ALREADY_IN_USE);
|
||||
ERR_FAIL_COND_V(!p_host.is_valid(), ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, ERR_INVALID_PARAMETER, "The remote port number must be between 1 and 65535 (inclusive).");
|
||||
|
||||
if (!_sock->is_open()) {
|
||||
IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
|
||||
Error err = _sock->open(NetSocket::TYPE_TCP, ip_type);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
_sock->set_blocking_enabled(false);
|
||||
}
|
||||
|
||||
timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000);
|
||||
Error err = _sock->connect_to_host(p_host, p_port);
|
||||
|
||||
if (err == OK) {
|
||||
status = STATUS_CONNECTED;
|
||||
} else if (err == ERR_BUSY) {
|
||||
status = STATUS_CONNECTING;
|
||||
} else {
|
||||
ERR_PRINT("Connection to remote host failed!");
|
||||
disconnect_from_host();
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
peer_host = p_host;
|
||||
peer_port = p_port;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block) {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
|
||||
if (status != STATUS_CONNECTED) {
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
Error err;
|
||||
int data_to_send = p_bytes;
|
||||
const uint8_t *offset = p_data;
|
||||
int total_sent = 0;
|
||||
|
||||
while (data_to_send) {
|
||||
int sent_amount = 0;
|
||||
err = _sock->send(offset, data_to_send, sent_amount);
|
||||
|
||||
if (err != OK) {
|
||||
if (err != ERR_BUSY) {
|
||||
disconnect_from_host();
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
if (!p_block) {
|
||||
r_sent = total_sent;
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Block and wait for the socket to accept more data
|
||||
err = _sock->poll(NetSocket::POLL_TYPE_OUT, -1);
|
||||
if (err != OK) {
|
||||
disconnect_from_host();
|
||||
return FAILED;
|
||||
}
|
||||
} else {
|
||||
data_to_send -= sent_amount;
|
||||
offset += sent_amount;
|
||||
total_sent += sent_amount;
|
||||
}
|
||||
}
|
||||
|
||||
r_sent = total_sent;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block) {
|
||||
if (status != STATUS_CONNECTED) {
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
Error err;
|
||||
int to_read = p_bytes;
|
||||
int total_read = 0;
|
||||
r_received = 0;
|
||||
|
||||
while (to_read) {
|
||||
int read = 0;
|
||||
err = _sock->recv(p_buffer + total_read, to_read, read);
|
||||
|
||||
if (err != OK) {
|
||||
if (err != ERR_BUSY) {
|
||||
disconnect_from_host();
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
if (!p_block) {
|
||||
r_received = total_read;
|
||||
return OK;
|
||||
}
|
||||
|
||||
err = _sock->poll(NetSocket::POLL_TYPE_IN, -1);
|
||||
|
||||
if (err != OK) {
|
||||
disconnect_from_host();
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
} else if (read == 0) {
|
||||
disconnect_from_host();
|
||||
r_received = total_read;
|
||||
return ERR_FILE_EOF;
|
||||
|
||||
} else {
|
||||
to_read -= read;
|
||||
total_read += read;
|
||||
|
||||
if (!p_block) {
|
||||
r_received = total_read;
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r_received = total_read;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void StreamPeerTCP::set_no_delay(bool p_enabled) {
|
||||
ERR_FAIL_COND(!_sock.is_valid() || !_sock->is_open());
|
||||
_sock->set_tcp_no_delay_enabled(p_enabled);
|
||||
}
|
||||
|
||||
StreamPeerTCP::Status StreamPeerTCP::get_status() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
void StreamPeerTCP::disconnect_from_host() {
|
||||
if (_sock.is_valid() && _sock->is_open()) {
|
||||
_sock->close();
|
||||
}
|
||||
|
||||
timeout = 0;
|
||||
status = STATUS_NONE;
|
||||
peer_host = IPAddress();
|
||||
peer_port = 0;
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::wait(NetSocket::PollType p_type, int p_timeout) {
|
||||
ERR_FAIL_COND_V(_sock.is_null() || !_sock->is_open(), ERR_UNAVAILABLE);
|
||||
return _sock->poll(p_type, p_timeout);
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::put_data(const uint8_t *p_data, int p_bytes) {
|
||||
int total;
|
||||
return write(p_data, p_bytes, total, true);
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) {
|
||||
return write(p_data, p_bytes, r_sent, false);
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::get_data(uint8_t *p_buffer, int p_bytes) {
|
||||
int total;
|
||||
return read(p_buffer, p_bytes, total, true);
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
|
||||
return read(p_buffer, p_bytes, r_received, false);
|
||||
}
|
||||
|
||||
int StreamPeerTCP::get_available_bytes() const {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), -1);
|
||||
return _sock->get_available_bytes();
|
||||
}
|
||||
|
||||
IPAddress StreamPeerTCP::get_connected_host() const {
|
||||
return peer_host;
|
||||
}
|
||||
|
||||
int StreamPeerTCP::get_connected_port() const {
|
||||
return peer_port;
|
||||
}
|
||||
|
||||
int StreamPeerTCP::get_local_port() const {
|
||||
uint16_t local_port;
|
||||
_sock->get_socket_address(nullptr, &local_port);
|
||||
return local_port;
|
||||
}
|
||||
|
||||
Error StreamPeerTCP::_connect(const String &p_address, int p_port) {
|
||||
IPAddress ip;
|
||||
if (p_address.is_valid_ip_address()) {
|
||||
ip = p_address;
|
||||
} else {
|
||||
ip = IP::get_singleton()->resolve_hostname(p_address);
|
||||
if (!ip.is_valid()) {
|
||||
return ERR_CANT_RESOLVE;
|
||||
}
|
||||
}
|
||||
|
||||
return connect_to_host(ip, p_port);
|
||||
}
|
||||
|
||||
void StreamPeerTCP::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("bind", "port", "host"), &StreamPeerTCP::bind, DEFVAL("*"));
|
||||
ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &StreamPeerTCP::_connect);
|
||||
ClassDB::bind_method(D_METHOD("poll"), &StreamPeerTCP::poll);
|
||||
ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerTCP::get_status);
|
||||
ClassDB::bind_method(D_METHOD("get_connected_host"), &StreamPeerTCP::get_connected_host);
|
||||
ClassDB::bind_method(D_METHOD("get_connected_port"), &StreamPeerTCP::get_connected_port);
|
||||
ClassDB::bind_method(D_METHOD("get_local_port"), &StreamPeerTCP::get_local_port);
|
||||
ClassDB::bind_method(D_METHOD("disconnect_from_host"), &StreamPeerTCP::disconnect_from_host);
|
||||
ClassDB::bind_method(D_METHOD("set_no_delay", "enabled"), &StreamPeerTCP::set_no_delay);
|
||||
|
||||
BIND_ENUM_CONSTANT(STATUS_NONE);
|
||||
BIND_ENUM_CONSTANT(STATUS_CONNECTING);
|
||||
BIND_ENUM_CONSTANT(STATUS_CONNECTED);
|
||||
BIND_ENUM_CONSTANT(STATUS_ERROR);
|
||||
}
|
||||
|
||||
StreamPeerTCP::StreamPeerTCP() :
|
||||
_sock(Ref<NetSocket>(NetSocket::create())) {
|
||||
}
|
||||
|
||||
StreamPeerTCP::~StreamPeerTCP() {
|
||||
disconnect_from_host();
|
||||
}
|
||||
96
engine/core/io/stream_peer_tcp.h
Normal file
96
engine/core/io/stream_peer_tcp.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/**************************************************************************/
|
||||
/* stream_peer_tcp.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef STREAM_PEER_TCP_H
|
||||
#define STREAM_PEER_TCP_H
|
||||
|
||||
#include "core/io/ip.h"
|
||||
#include "core/io/ip_address.h"
|
||||
#include "core/io/net_socket.h"
|
||||
#include "core/io/stream_peer.h"
|
||||
|
||||
class StreamPeerTCP : public StreamPeer {
|
||||
GDCLASS(StreamPeerTCP, StreamPeer);
|
||||
|
||||
public:
|
||||
enum Status {
|
||||
STATUS_NONE,
|
||||
STATUS_CONNECTING,
|
||||
STATUS_CONNECTED,
|
||||
STATUS_ERROR,
|
||||
};
|
||||
|
||||
protected:
|
||||
Ref<NetSocket> _sock;
|
||||
uint64_t timeout = 0;
|
||||
Status status = STATUS_NONE;
|
||||
IPAddress peer_host;
|
||||
uint16_t peer_port = 0;
|
||||
|
||||
Error _connect(const String &p_address, int p_port);
|
||||
Error write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block);
|
||||
Error read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void accept_socket(Ref<NetSocket> p_sock, IPAddress p_host, uint16_t p_port);
|
||||
|
||||
Error bind(int p_port, const IPAddress &p_host);
|
||||
Error connect_to_host(const IPAddress &p_host, int p_port);
|
||||
IPAddress get_connected_host() const;
|
||||
int get_connected_port() const;
|
||||
int get_local_port() const;
|
||||
void disconnect_from_host();
|
||||
|
||||
int get_available_bytes() const override;
|
||||
Status get_status() const;
|
||||
|
||||
void set_no_delay(bool p_enabled);
|
||||
|
||||
// Poll socket updating its state.
|
||||
Error poll();
|
||||
|
||||
// Wait or check for writable, readable.
|
||||
Error wait(NetSocket::PollType p_type, int p_timeout = 0);
|
||||
|
||||
// Read/Write from StreamPeer
|
||||
Error put_data(const uint8_t *p_data, int p_bytes) override;
|
||||
Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override;
|
||||
Error get_data(uint8_t *p_buffer, int p_bytes) override;
|
||||
Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override;
|
||||
|
||||
StreamPeerTCP();
|
||||
~StreamPeerTCP();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(StreamPeerTCP::Status);
|
||||
|
||||
#endif // STREAM_PEER_TCP_H
|
||||
61
engine/core/io/stream_peer_tls.cpp
Normal file
61
engine/core/io/stream_peer_tls.cpp
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/**************************************************************************/
|
||||
/* stream_peer_tls.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 "stream_peer_tls.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
|
||||
StreamPeerTLS *(*StreamPeerTLS::_create)() = nullptr;
|
||||
|
||||
StreamPeerTLS *StreamPeerTLS::create() {
|
||||
if (_create) {
|
||||
return _create();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool StreamPeerTLS::is_available() {
|
||||
return _create != nullptr;
|
||||
}
|
||||
|
||||
void StreamPeerTLS::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("poll"), &StreamPeerTLS::poll);
|
||||
ClassDB::bind_method(D_METHOD("accept_stream", "stream", "server_options"), &StreamPeerTLS::accept_stream);
|
||||
ClassDB::bind_method(D_METHOD("connect_to_stream", "stream", "common_name", "client_options"), &StreamPeerTLS::connect_to_stream, DEFVAL(Ref<TLSOptions>()));
|
||||
ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerTLS::get_status);
|
||||
ClassDB::bind_method(D_METHOD("get_stream"), &StreamPeerTLS::get_stream);
|
||||
ClassDB::bind_method(D_METHOD("disconnect_from_stream"), &StreamPeerTLS::disconnect_from_stream);
|
||||
|
||||
BIND_ENUM_CONSTANT(STATUS_DISCONNECTED);
|
||||
BIND_ENUM_CONSTANT(STATUS_HANDSHAKING);
|
||||
BIND_ENUM_CONSTANT(STATUS_CONNECTED);
|
||||
BIND_ENUM_CONSTANT(STATUS_ERROR);
|
||||
BIND_ENUM_CONSTANT(STATUS_ERROR_HOSTNAME_MISMATCH);
|
||||
}
|
||||
70
engine/core/io/stream_peer_tls.h
Normal file
70
engine/core/io/stream_peer_tls.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**************************************************************************/
|
||||
/* stream_peer_tls.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef STREAM_PEER_TLS_H
|
||||
#define STREAM_PEER_TLS_H
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
#include "core/io/stream_peer.h"
|
||||
|
||||
class StreamPeerTLS : public StreamPeer {
|
||||
GDCLASS(StreamPeerTLS, StreamPeer);
|
||||
|
||||
protected:
|
||||
static StreamPeerTLS *(*_create)();
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
enum Status {
|
||||
STATUS_DISCONNECTED,
|
||||
STATUS_HANDSHAKING,
|
||||
STATUS_CONNECTED,
|
||||
STATUS_ERROR,
|
||||
STATUS_ERROR_HOSTNAME_MISMATCH
|
||||
};
|
||||
|
||||
virtual void poll() = 0;
|
||||
virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options) = 0;
|
||||
virtual Error connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options) = 0;
|
||||
virtual Status get_status() const = 0;
|
||||
virtual Ref<StreamPeer> get_stream() const = 0;
|
||||
|
||||
virtual void disconnect_from_stream() = 0;
|
||||
|
||||
static StreamPeerTLS *create();
|
||||
|
||||
static bool is_available();
|
||||
|
||||
StreamPeerTLS() {}
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(StreamPeerTLS::Status);
|
||||
|
||||
#endif // STREAM_PEER_TLS_H
|
||||
132
engine/core/io/tcp_server.cpp
Normal file
132
engine/core/io/tcp_server.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/**************************************************************************/
|
||||
/* tcp_server.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 "tcp_server.h"
|
||||
|
||||
void TCPServer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("listen", "port", "bind_address"), &TCPServer::listen, DEFVAL("*"));
|
||||
ClassDB::bind_method(D_METHOD("is_connection_available"), &TCPServer::is_connection_available);
|
||||
ClassDB::bind_method(D_METHOD("is_listening"), &TCPServer::is_listening);
|
||||
ClassDB::bind_method(D_METHOD("get_local_port"), &TCPServer::get_local_port);
|
||||
ClassDB::bind_method(D_METHOD("take_connection"), &TCPServer::take_connection);
|
||||
ClassDB::bind_method(D_METHOD("stop"), &TCPServer::stop);
|
||||
}
|
||||
|
||||
Error TCPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE);
|
||||
ERR_FAIL_COND_V(!p_bind_address.is_valid() && !p_bind_address.is_wildcard(), ERR_INVALID_PARAMETER);
|
||||
|
||||
Error err;
|
||||
IP::Type ip_type = IP::TYPE_ANY;
|
||||
|
||||
// If the bind address is valid use its type as the socket type
|
||||
if (p_bind_address.is_valid()) {
|
||||
ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
|
||||
}
|
||||
|
||||
err = _sock->open(NetSocket::TYPE_TCP, ip_type);
|
||||
|
||||
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
|
||||
|
||||
_sock->set_blocking_enabled(false);
|
||||
_sock->set_reuse_address_enabled(true);
|
||||
|
||||
err = _sock->bind(p_bind_address, p_port);
|
||||
|
||||
if (err != OK) {
|
||||
_sock->close();
|
||||
return ERR_ALREADY_IN_USE;
|
||||
}
|
||||
|
||||
err = _sock->listen(MAX_PENDING_CONNECTIONS);
|
||||
|
||||
if (err != OK) {
|
||||
_sock->close();
|
||||
return FAILED;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int TCPServer::get_local_port() const {
|
||||
uint16_t local_port;
|
||||
_sock->get_socket_address(nullptr, &local_port);
|
||||
return local_port;
|
||||
}
|
||||
|
||||
bool TCPServer::is_listening() const {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), false);
|
||||
|
||||
return _sock->is_open();
|
||||
}
|
||||
|
||||
bool TCPServer::is_connection_available() const {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), false);
|
||||
|
||||
if (!_sock->is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Error err = _sock->poll(NetSocket::POLL_TYPE_IN, 0);
|
||||
return (err == OK);
|
||||
}
|
||||
|
||||
Ref<StreamPeerTCP> TCPServer::take_connection() {
|
||||
Ref<StreamPeerTCP> conn;
|
||||
if (!is_connection_available()) {
|
||||
return conn;
|
||||
}
|
||||
|
||||
Ref<NetSocket> ns;
|
||||
IPAddress ip;
|
||||
uint16_t port = 0;
|
||||
ns = _sock->accept(ip, port);
|
||||
if (!ns.is_valid()) {
|
||||
return conn;
|
||||
}
|
||||
|
||||
conn = Ref<StreamPeerTCP>(memnew(StreamPeerTCP));
|
||||
conn->accept_socket(ns, ip, port);
|
||||
return conn;
|
||||
}
|
||||
|
||||
void TCPServer::stop() {
|
||||
if (_sock.is_valid()) {
|
||||
_sock->close();
|
||||
}
|
||||
}
|
||||
|
||||
TCPServer::TCPServer() :
|
||||
_sock(Ref<NetSocket>(NetSocket::create())) {
|
||||
}
|
||||
|
||||
TCPServer::~TCPServer() {
|
||||
stop();
|
||||
}
|
||||
63
engine/core/io/tcp_server.h
Normal file
63
engine/core/io/tcp_server.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**************************************************************************/
|
||||
/* tcp_server.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TCP_SERVER_H
|
||||
#define TCP_SERVER_H
|
||||
|
||||
#include "core/io/ip.h"
|
||||
#include "core/io/net_socket.h"
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
|
||||
class TCPServer : public RefCounted {
|
||||
GDCLASS(TCPServer, RefCounted);
|
||||
|
||||
protected:
|
||||
enum {
|
||||
MAX_PENDING_CONNECTIONS = 8
|
||||
};
|
||||
|
||||
Ref<NetSocket> _sock;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Error listen(uint16_t p_port, const IPAddress &p_bind_address = IPAddress("*"));
|
||||
int get_local_port() const;
|
||||
bool is_listening() const;
|
||||
bool is_connection_available() const;
|
||||
Ref<StreamPeerTCP> take_connection();
|
||||
|
||||
void stop(); // Stop listening
|
||||
|
||||
TCPServer();
|
||||
~TCPServer();
|
||||
};
|
||||
|
||||
#endif // TCP_SERVER_H
|
||||
372
engine/core/io/translation_loader_po.cpp
Normal file
372
engine/core/io/translation_loader_po.cpp
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
/**************************************************************************/
|
||||
/* translation_loader_po.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 "translation_loader_po.h"
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/string/translation.h"
|
||||
#include "core/string/translation_po.h"
|
||||
|
||||
Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_error) {
|
||||
if (r_error) {
|
||||
*r_error = ERR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
const String path = f->get_path();
|
||||
Ref<TranslationPO> translation = Ref<TranslationPO>(memnew(TranslationPO));
|
||||
String config;
|
||||
|
||||
uint32_t magic = f->get_32();
|
||||
if (magic == 0x950412de) {
|
||||
// Load binary MO file.
|
||||
|
||||
uint16_t version_maj = f->get_16();
|
||||
uint16_t version_min = f->get_16();
|
||||
ERR_FAIL_COND_V_MSG(version_maj > 1, Ref<Resource>(), vformat("Unsupported MO file %s, version %d.%d.", path, version_maj, version_min));
|
||||
|
||||
uint32_t num_strings = f->get_32();
|
||||
uint32_t id_table_offset = f->get_32();
|
||||
uint32_t trans_table_offset = f->get_32();
|
||||
|
||||
// Read string tables.
|
||||
for (uint32_t i = 0; i < num_strings; i++) {
|
||||
String msg_id;
|
||||
String msg_id_plural;
|
||||
String msg_context;
|
||||
|
||||
// Read id strings and context.
|
||||
{
|
||||
Vector<uint8_t> data;
|
||||
f->seek(id_table_offset + i * 8);
|
||||
uint32_t str_start = 0;
|
||||
uint32_t str_len = f->get_32();
|
||||
uint32_t str_offset = f->get_32();
|
||||
|
||||
data.resize(str_len + 1);
|
||||
f->seek(str_offset);
|
||||
f->get_buffer(data.ptrw(), str_len);
|
||||
data.write[str_len] = 0;
|
||||
|
||||
bool is_plural = false;
|
||||
for (uint32_t j = 0; j < str_len + 1; j++) {
|
||||
if (data[j] == 0x04) {
|
||||
msg_context.parse_utf8((const char *)data.ptr(), j);
|
||||
str_start = j + 1;
|
||||
}
|
||||
if (data[j] == 0x00) {
|
||||
if (is_plural) {
|
||||
msg_id_plural.parse_utf8((const char *)(data.ptr() + str_start), j - str_start);
|
||||
} else {
|
||||
msg_id.parse_utf8((const char *)(data.ptr() + str_start), j - str_start);
|
||||
is_plural = true;
|
||||
}
|
||||
str_start = j + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read translated strings.
|
||||
{
|
||||
Vector<uint8_t> data;
|
||||
f->seek(trans_table_offset + i * 8);
|
||||
uint32_t str_len = f->get_32();
|
||||
uint32_t str_offset = f->get_32();
|
||||
|
||||
data.resize(str_len + 1);
|
||||
f->seek(str_offset);
|
||||
f->get_buffer(data.ptrw(), str_len);
|
||||
data.write[str_len] = 0;
|
||||
|
||||
if (msg_id.is_empty()) {
|
||||
config = String::utf8((const char *)data.ptr(), str_len);
|
||||
// Record plural rule.
|
||||
int p_start = config.find("Plural-Forms");
|
||||
if (p_start != -1) {
|
||||
int p_end = config.find("\n", p_start);
|
||||
translation->set_plural_rule(config.substr(p_start, p_end - p_start));
|
||||
}
|
||||
} else {
|
||||
uint32_t str_start = 0;
|
||||
Vector<String> plural_msg;
|
||||
for (uint32_t j = 0; j < str_len + 1; j++) {
|
||||
if (data[j] == 0x00) {
|
||||
if (msg_id_plural.is_empty()) {
|
||||
translation->add_message(msg_id, String::utf8((const char *)(data.ptr() + str_start), j - str_start), msg_context);
|
||||
} else {
|
||||
plural_msg.push_back(String::utf8((const char *)(data.ptr() + str_start), j - str_start));
|
||||
}
|
||||
str_start = j + 1;
|
||||
}
|
||||
}
|
||||
if (!plural_msg.is_empty()) {
|
||||
translation->add_plural_message(msg_id, plural_msg, msg_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Try to load as text PO file.
|
||||
f->seek(0);
|
||||
|
||||
enum Status {
|
||||
STATUS_NONE,
|
||||
STATUS_READING_ID,
|
||||
STATUS_READING_STRING,
|
||||
STATUS_READING_CONTEXT,
|
||||
STATUS_READING_PLURAL,
|
||||
};
|
||||
|
||||
Status status = STATUS_NONE;
|
||||
|
||||
String msg_id;
|
||||
String msg_str;
|
||||
String msg_context;
|
||||
Vector<String> msgs_plural;
|
||||
|
||||
if (r_error) {
|
||||
*r_error = ERR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
int line = 1;
|
||||
int plural_forms = 0;
|
||||
int plural_index = -1;
|
||||
bool entered_context = false;
|
||||
bool skip_this = false;
|
||||
bool skip_next = false;
|
||||
bool is_eof = false;
|
||||
|
||||
while (!is_eof) {
|
||||
String l = f->get_line().strip_edges();
|
||||
is_eof = f->eof_reached();
|
||||
|
||||
// If we reached last line and it's not a content line, break, otherwise let processing that last loop
|
||||
if (is_eof && l.is_empty()) {
|
||||
if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || (status == STATUS_READING_PLURAL && plural_index != plural_forms - 1)) {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), "Unexpected EOF while reading PO file at: " + path + ":" + itos(line));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (l.begins_with("msgctxt")) {
|
||||
ERR_FAIL_COND_V_MSG(status != STATUS_READING_STRING && status != STATUS_READING_PLURAL, Ref<Resource>(), "Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: " + path + ":" + itos(line));
|
||||
|
||||
// In PO file, "msgctxt" appears before "msgid". If we encounter a "msgctxt", we add what we have read
|
||||
// and set "entered_context" to true to prevent adding twice.
|
||||
if (!skip_this && !msg_id.is_empty()) {
|
||||
if (status == STATUS_READING_STRING) {
|
||||
translation->add_message(msg_id, msg_str, msg_context);
|
||||
} else if (status == STATUS_READING_PLURAL) {
|
||||
ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
|
||||
translation->add_plural_message(msg_id, msgs_plural, msg_context);
|
||||
}
|
||||
}
|
||||
msg_context = "";
|
||||
l = l.substr(7, l.length()).strip_edges();
|
||||
status = STATUS_READING_CONTEXT;
|
||||
entered_context = true;
|
||||
}
|
||||
|
||||
if (l.begins_with("msgid_plural")) {
|
||||
if (plural_forms == 0) {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), "PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: " + path + ":" + itos(line));
|
||||
} else if (status != STATUS_READING_ID) {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), "Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: " + path + ":" + itos(line));
|
||||
}
|
||||
// We don't record the message in "msgid_plural" itself as tr_n(), TTRN(), RTRN() interfaces provide the plural string already.
|
||||
// We just have to reset variables related to plurals for "msgstr[]" later on.
|
||||
l = l.substr(12, l.length()).strip_edges();
|
||||
plural_index = -1;
|
||||
msgs_plural.clear();
|
||||
msgs_plural.resize(plural_forms);
|
||||
status = STATUS_READING_PLURAL;
|
||||
} else if (l.begins_with("msgid")) {
|
||||
ERR_FAIL_COND_V_MSG(status == STATUS_READING_ID, Ref<Resource>(), "Unexpected 'msgid', was expecting 'msgstr' while parsing: " + path + ":" + itos(line));
|
||||
|
||||
if (!msg_id.is_empty()) {
|
||||
if (!skip_this && !entered_context) {
|
||||
if (status == STATUS_READING_STRING) {
|
||||
translation->add_message(msg_id, msg_str, msg_context);
|
||||
} else if (status == STATUS_READING_PLURAL) {
|
||||
ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
|
||||
translation->add_plural_message(msg_id, msgs_plural, msg_context);
|
||||
}
|
||||
}
|
||||
} else if (config.is_empty()) {
|
||||
config = msg_str;
|
||||
// Record plural rule.
|
||||
int p_start = config.find("Plural-Forms");
|
||||
if (p_start != -1) {
|
||||
int p_end = config.find("\n", p_start);
|
||||
translation->set_plural_rule(config.substr(p_start, p_end - p_start));
|
||||
plural_forms = translation->get_plural_forms();
|
||||
}
|
||||
}
|
||||
|
||||
l = l.substr(5, l.length()).strip_edges();
|
||||
status = STATUS_READING_ID;
|
||||
// If we did not encounter msgctxt, we reset context to empty to reset it.
|
||||
if (!entered_context) {
|
||||
msg_context = "";
|
||||
}
|
||||
msg_id = "";
|
||||
msg_str = "";
|
||||
skip_this = skip_next;
|
||||
skip_next = false;
|
||||
entered_context = false;
|
||||
}
|
||||
|
||||
if (l.begins_with("msgstr[")) {
|
||||
ERR_FAIL_COND_V_MSG(status != STATUS_READING_PLURAL, Ref<Resource>(), "Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: " + path + ":" + itos(line));
|
||||
plural_index++; // Increment to add to the next slot in vector msgs_plural.
|
||||
l = l.substr(9, l.length()).strip_edges();
|
||||
} else if (l.begins_with("msgstr")) {
|
||||
ERR_FAIL_COND_V_MSG(status != STATUS_READING_ID, Ref<Resource>(), "Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: " + path + ":" + itos(line));
|
||||
l = l.substr(6, l.length()).strip_edges();
|
||||
status = STATUS_READING_STRING;
|
||||
}
|
||||
|
||||
if (l.is_empty() || l.begins_with("#")) {
|
||||
if (l.contains("fuzzy")) {
|
||||
skip_next = true;
|
||||
}
|
||||
line++;
|
||||
continue; // Nothing to read or comment.
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(!l.begins_with("\"") || status == STATUS_NONE, Ref<Resource>(), "Invalid line '" + l + "' while parsing: " + path + ":" + itos(line));
|
||||
|
||||
l = l.substr(1, l.length());
|
||||
// Find final quote, ignoring escaped ones (\").
|
||||
// The escape_next logic is necessary to properly parse things like \\"
|
||||
// where the backslash is the one being escaped, not the quote.
|
||||
int end_pos = -1;
|
||||
bool escape_next = false;
|
||||
for (int i = 0; i < l.length(); i++) {
|
||||
if (l[i] == '\\' && !escape_next) {
|
||||
escape_next = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l[i] == '"' && !escape_next) {
|
||||
end_pos = i;
|
||||
break;
|
||||
}
|
||||
|
||||
escape_next = false;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(end_pos == -1, Ref<Resource>(), "Expected '\"' at end of message while parsing: " + path + ":" + itos(line));
|
||||
|
||||
l = l.substr(0, end_pos);
|
||||
l = l.c_unescape();
|
||||
|
||||
if (status == STATUS_READING_ID) {
|
||||
msg_id += l;
|
||||
} else if (status == STATUS_READING_STRING) {
|
||||
msg_str += l;
|
||||
} else if (status == STATUS_READING_CONTEXT) {
|
||||
msg_context += l;
|
||||
} else if (status == STATUS_READING_PLURAL && plural_index >= 0) {
|
||||
ERR_FAIL_COND_V_MSG(plural_index >= plural_forms, Ref<Resource>(), "Unexpected plural form while parsing: " + path + ":" + itos(line));
|
||||
msgs_plural.write[plural_index] = msgs_plural[plural_index] + l;
|
||||
}
|
||||
|
||||
line++;
|
||||
}
|
||||
|
||||
// Add the last set of data from last iteration.
|
||||
if (status == STATUS_READING_STRING) {
|
||||
if (!msg_id.is_empty()) {
|
||||
if (!skip_this) {
|
||||
translation->add_message(msg_id, msg_str, msg_context);
|
||||
}
|
||||
} else if (config.is_empty()) {
|
||||
config = msg_str;
|
||||
}
|
||||
} else if (status == STATUS_READING_PLURAL) {
|
||||
if (!skip_this && !msg_id.is_empty()) {
|
||||
ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
|
||||
translation->add_plural_message(msg_id, msgs_plural, msg_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(config.is_empty(), Ref<Resource>(), "No config found in file: " + path + ".");
|
||||
|
||||
Vector<String> configs = config.split("\n");
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
String c = configs[i].strip_edges();
|
||||
int p = c.find(":");
|
||||
if (p == -1) {
|
||||
continue;
|
||||
}
|
||||
String prop = c.substr(0, p).strip_edges();
|
||||
String value = c.substr(p + 1, c.length()).strip_edges();
|
||||
|
||||
if (prop == "X-Language" || prop == "Language") {
|
||||
translation->set_locale(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return translation;
|
||||
}
|
||||
|
||||
Ref<Resource> TranslationLoaderPO::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
|
||||
if (r_error) {
|
||||
*r_error = ERR_CANT_OPEN;
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), Ref<Resource>(), "Cannot open file '" + p_path + "'.");
|
||||
|
||||
return load_translation(f, r_error);
|
||||
}
|
||||
|
||||
void TranslationLoaderPO::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("po");
|
||||
p_extensions->push_back("mo");
|
||||
}
|
||||
|
||||
bool TranslationLoaderPO::handles_type(const String &p_type) const {
|
||||
return (p_type == "Translation");
|
||||
}
|
||||
|
||||
String TranslationLoaderPO::get_resource_type(const String &p_path) const {
|
||||
if (p_path.get_extension().to_lower() == "po" || p_path.get_extension().to_lower() == "mo") {
|
||||
return "Translation";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
49
engine/core/io/translation_loader_po.h
Normal file
49
engine/core/io/translation_loader_po.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**************************************************************************/
|
||||
/* translation_loader_po.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TRANSLATION_LOADER_PO_H
|
||||
#define TRANSLATION_LOADER_PO_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/string/translation.h"
|
||||
|
||||
class TranslationLoaderPO : public ResourceFormatLoader {
|
||||
public:
|
||||
static Ref<Resource> load_translation(Ref<FileAccess> f, Error *r_error = nullptr);
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual bool handles_type(const String &p_type) const override;
|
||||
virtual String get_resource_type(const String &p_path) const override;
|
||||
|
||||
TranslationLoaderPO() {}
|
||||
};
|
||||
|
||||
#endif // TRANSLATION_LOADER_PO_H
|
||||
205
engine/core/io/udp_server.cpp
Normal file
205
engine/core/io/udp_server.cpp
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/**************************************************************************/
|
||||
/* udp_server.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 "udp_server.h"
|
||||
|
||||
void UDPServer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("listen", "port", "bind_address"), &UDPServer::listen, DEFVAL("*"));
|
||||
ClassDB::bind_method(D_METHOD("poll"), &UDPServer::poll);
|
||||
ClassDB::bind_method(D_METHOD("is_connection_available"), &UDPServer::is_connection_available);
|
||||
ClassDB::bind_method(D_METHOD("get_local_port"), &UDPServer::get_local_port);
|
||||
ClassDB::bind_method(D_METHOD("is_listening"), &UDPServer::is_listening);
|
||||
ClassDB::bind_method(D_METHOD("take_connection"), &UDPServer::take_connection);
|
||||
ClassDB::bind_method(D_METHOD("stop"), &UDPServer::stop);
|
||||
ClassDB::bind_method(D_METHOD("set_max_pending_connections", "max_pending_connections"), &UDPServer::set_max_pending_connections);
|
||||
ClassDB::bind_method(D_METHOD("get_max_pending_connections"), &UDPServer::get_max_pending_connections);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_pending_connections", PROPERTY_HINT_RANGE, "0,256,1"), "set_max_pending_connections", "get_max_pending_connections");
|
||||
}
|
||||
|
||||
Error UDPServer::poll() {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
if (!_sock->is_open()) {
|
||||
return ERR_UNCONFIGURED;
|
||||
}
|
||||
Error err;
|
||||
int read;
|
||||
IPAddress ip;
|
||||
uint16_t port;
|
||||
while (true) {
|
||||
err = _sock->recvfrom(recv_buffer, sizeof(recv_buffer), read, ip, port);
|
||||
if (err != OK) {
|
||||
if (err == ERR_BUSY) {
|
||||
break;
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
Peer p;
|
||||
p.ip = ip;
|
||||
p.port = port;
|
||||
List<Peer>::Element *E = peers.find(p);
|
||||
if (!E) {
|
||||
E = pending.find(p);
|
||||
}
|
||||
if (E) {
|
||||
E->get().peer->store_packet(ip, port, recv_buffer, read);
|
||||
} else {
|
||||
if (pending.size() >= max_pending_connections) {
|
||||
// Drop connection.
|
||||
continue;
|
||||
}
|
||||
// It's a new peer, add it to the pending list.
|
||||
Peer peer;
|
||||
peer.ip = ip;
|
||||
peer.port = port;
|
||||
peer.peer = memnew(PacketPeerUDP);
|
||||
peer.peer->connect_shared_socket(_sock, ip, port, this);
|
||||
peer.peer->store_packet(ip, port, recv_buffer, read);
|
||||
pending.push_back(peer);
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error UDPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
|
||||
ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE);
|
||||
ERR_FAIL_COND_V(!p_bind_address.is_valid() && !p_bind_address.is_wildcard(), ERR_INVALID_PARAMETER);
|
||||
|
||||
Error err;
|
||||
IP::Type ip_type = IP::TYPE_ANY;
|
||||
|
||||
if (p_bind_address.is_valid()) {
|
||||
ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
|
||||
}
|
||||
|
||||
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
|
||||
|
||||
if (err != OK) {
|
||||
return ERR_CANT_CREATE;
|
||||
}
|
||||
|
||||
_sock->set_blocking_enabled(false);
|
||||
_sock->set_reuse_address_enabled(true);
|
||||
err = _sock->bind(p_bind_address, p_port);
|
||||
|
||||
if (err != OK) {
|
||||
stop();
|
||||
return err;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int UDPServer::get_local_port() const {
|
||||
uint16_t local_port;
|
||||
_sock->get_socket_address(nullptr, &local_port);
|
||||
return local_port;
|
||||
}
|
||||
|
||||
bool UDPServer::is_listening() const {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), false);
|
||||
|
||||
return _sock->is_open();
|
||||
}
|
||||
|
||||
bool UDPServer::is_connection_available() const {
|
||||
ERR_FAIL_COND_V(!_sock.is_valid(), false);
|
||||
|
||||
if (!_sock->is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pending.size() > 0;
|
||||
}
|
||||
|
||||
void UDPServer::set_max_pending_connections(int p_max) {
|
||||
ERR_FAIL_COND_MSG(p_max < 0, "Max pending connections value must be a positive number (0 means refuse new connections).");
|
||||
max_pending_connections = p_max;
|
||||
while (p_max > pending.size()) {
|
||||
List<Peer>::Element *E = pending.back();
|
||||
if (!E) {
|
||||
break;
|
||||
}
|
||||
memdelete(E->get().peer);
|
||||
pending.erase(E);
|
||||
}
|
||||
}
|
||||
|
||||
int UDPServer::get_max_pending_connections() const {
|
||||
return max_pending_connections;
|
||||
}
|
||||
|
||||
Ref<PacketPeerUDP> UDPServer::take_connection() {
|
||||
Ref<PacketPeerUDP> conn;
|
||||
if (!is_connection_available()) {
|
||||
return conn;
|
||||
}
|
||||
|
||||
Peer peer = pending.front()->get();
|
||||
pending.pop_front();
|
||||
peers.push_back(peer);
|
||||
return peer.peer;
|
||||
}
|
||||
|
||||
void UDPServer::remove_peer(IPAddress p_ip, int p_port) {
|
||||
Peer peer;
|
||||
peer.ip = p_ip;
|
||||
peer.port = p_port;
|
||||
List<Peer>::Element *E = peers.find(peer);
|
||||
if (E) {
|
||||
peers.erase(E);
|
||||
}
|
||||
}
|
||||
|
||||
void UDPServer::stop() {
|
||||
if (_sock.is_valid()) {
|
||||
_sock->close();
|
||||
}
|
||||
List<Peer>::Element *E = peers.front();
|
||||
while (E) {
|
||||
E->get().peer->disconnect_shared_socket();
|
||||
E = E->next();
|
||||
}
|
||||
E = pending.front();
|
||||
while (E) {
|
||||
E->get().peer->disconnect_shared_socket();
|
||||
memdelete(E->get().peer);
|
||||
E = E->next();
|
||||
}
|
||||
peers.clear();
|
||||
pending.clear();
|
||||
}
|
||||
|
||||
UDPServer::UDPServer() :
|
||||
_sock(Ref<NetSocket>(NetSocket::create())) {
|
||||
}
|
||||
|
||||
UDPServer::~UDPServer() {
|
||||
stop();
|
||||
}
|
||||
80
engine/core/io/udp_server.h
Normal file
80
engine/core/io/udp_server.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**************************************************************************/
|
||||
/* udp_server.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef UDP_SERVER_H
|
||||
#define UDP_SERVER_H
|
||||
|
||||
#include "core/io/net_socket.h"
|
||||
#include "core/io/packet_peer_udp.h"
|
||||
|
||||
class UDPServer : public RefCounted {
|
||||
GDCLASS(UDPServer, RefCounted);
|
||||
|
||||
protected:
|
||||
enum {
|
||||
PACKET_BUFFER_SIZE = 65536
|
||||
};
|
||||
|
||||
struct Peer {
|
||||
PacketPeerUDP *peer = nullptr;
|
||||
IPAddress ip;
|
||||
uint16_t port = 0;
|
||||
|
||||
bool operator==(const Peer &p_other) const {
|
||||
return (ip == p_other.ip && port == p_other.port);
|
||||
}
|
||||
};
|
||||
uint8_t recv_buffer[PACKET_BUFFER_SIZE];
|
||||
|
||||
List<Peer> peers;
|
||||
List<Peer> pending;
|
||||
int max_pending_connections = 16;
|
||||
|
||||
Ref<NetSocket> _sock;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void remove_peer(IPAddress p_ip, int p_port);
|
||||
Error listen(uint16_t p_port, const IPAddress &p_bind_address = IPAddress("*"));
|
||||
Error poll();
|
||||
int get_local_port() const;
|
||||
bool is_listening() const;
|
||||
bool is_connection_available() const;
|
||||
void set_max_pending_connections(int p_max);
|
||||
int get_max_pending_connections() const;
|
||||
Ref<PacketPeerUDP> take_connection();
|
||||
|
||||
void stop();
|
||||
|
||||
UDPServer();
|
||||
~UDPServer();
|
||||
};
|
||||
|
||||
#endif // UDP_SERVER_H
|
||||
557
engine/core/io/xml_parser.cpp
Normal file
557
engine/core/io/xml_parser.cpp
Normal file
|
|
@ -0,0 +1,557 @@
|
|||
/**************************************************************************/
|
||||
/* xml_parser.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 "xml_parser.h"
|
||||
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
//#define DEBUG_XML
|
||||
|
||||
static inline bool _is_white_space(char c) {
|
||||
return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
|
||||
}
|
||||
|
||||
//! sets the state that text was found. Returns true if set should be set
|
||||
bool XMLParser::_set_text(const char *start, const char *end) {
|
||||
// check if text is more than 2 characters, and if not, check if there is
|
||||
// only white space, so that this text won't be reported
|
||||
if (end - start < 3) {
|
||||
const char *p = start;
|
||||
for (; p != end; ++p) {
|
||||
if (!_is_white_space(*p)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p == end) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// set current text to the parsed text, and replace xml special characters
|
||||
String s = String::utf8(start, (int)(end - start));
|
||||
node_name = s.xml_unescape();
|
||||
|
||||
// current XML node type is text
|
||||
node_type = NODE_TEXT;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XMLParser::_parse_closing_xml_element() {
|
||||
node_type = NODE_ELEMENT_END;
|
||||
node_empty = false;
|
||||
attributes.clear();
|
||||
|
||||
next_char();
|
||||
const char *pBeginClose = P;
|
||||
|
||||
while (*P && *P != '>') {
|
||||
next_char();
|
||||
}
|
||||
|
||||
node_name = String::utf8(pBeginClose, (int)(P - pBeginClose));
|
||||
#ifdef DEBUG_XML
|
||||
print_line("XML CLOSE: " + node_name);
|
||||
#endif
|
||||
|
||||
if (*P) {
|
||||
next_char();
|
||||
}
|
||||
}
|
||||
|
||||
void XMLParser::_ignore_definition() {
|
||||
node_type = NODE_UNKNOWN;
|
||||
|
||||
const char *F = P;
|
||||
// move until end marked with '>' reached
|
||||
while (*P && *P != '>') {
|
||||
next_char();
|
||||
}
|
||||
node_name.parse_utf8(F, P - F);
|
||||
|
||||
if (*P) {
|
||||
next_char();
|
||||
}
|
||||
}
|
||||
|
||||
bool XMLParser::_parse_cdata() {
|
||||
if (*(P + 1) != '[') {
|
||||
return false;
|
||||
}
|
||||
|
||||
node_type = NODE_CDATA;
|
||||
|
||||
// skip '<![CDATA['
|
||||
int count = 0;
|
||||
while (*P && count < 8) {
|
||||
next_char();
|
||||
++count;
|
||||
}
|
||||
|
||||
if (!*P) {
|
||||
node_name = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *cDataBegin = P;
|
||||
const char *cDataEnd = nullptr;
|
||||
|
||||
// find end of CDATA
|
||||
while (*P && !cDataEnd) {
|
||||
if (*P == '>' &&
|
||||
(*(P - 1) == ']') &&
|
||||
(*(P - 2) == ']')) {
|
||||
cDataEnd = P - 2;
|
||||
}
|
||||
|
||||
next_char();
|
||||
}
|
||||
|
||||
if (!cDataEnd) {
|
||||
cDataEnd = P;
|
||||
}
|
||||
node_name = String::utf8(cDataBegin, (int)(cDataEnd - cDataBegin));
|
||||
#ifdef DEBUG_XML
|
||||
print_line("XML CDATA: " + node_name);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XMLParser::_parse_comment() {
|
||||
node_type = NODE_COMMENT;
|
||||
P += 1;
|
||||
|
||||
const char *pEndOfInput = data + length;
|
||||
const char *pCommentBegin;
|
||||
const char *pCommentEnd;
|
||||
|
||||
if (P + 1 < pEndOfInput && P[0] == '-' && P[1] == '-') {
|
||||
// Comment, use '-->' as end.
|
||||
pCommentBegin = P + 2;
|
||||
for (pCommentEnd = pCommentBegin; pCommentEnd + 2 < pEndOfInput; pCommentEnd++) {
|
||||
if (pCommentEnd[0] == '-' && pCommentEnd[1] == '-' && pCommentEnd[2] == '>') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pCommentEnd + 2 < pEndOfInput) {
|
||||
P = pCommentEnd + 3;
|
||||
} else {
|
||||
P = pCommentEnd = pEndOfInput;
|
||||
}
|
||||
} else {
|
||||
// Like document type definition, match angle brackets.
|
||||
pCommentBegin = P;
|
||||
|
||||
int count = 1;
|
||||
while (*P && count) {
|
||||
if (*P == '>') {
|
||||
--count;
|
||||
} else if (*P == '<') {
|
||||
++count;
|
||||
}
|
||||
next_char();
|
||||
}
|
||||
|
||||
if (count) {
|
||||
pCommentEnd = P;
|
||||
} else {
|
||||
pCommentEnd = P - 1;
|
||||
}
|
||||
}
|
||||
|
||||
node_name = String::utf8(pCommentBegin, (int)(pCommentEnd - pCommentBegin));
|
||||
#ifdef DEBUG_XML
|
||||
print_line("XML COMMENT: " + node_name);
|
||||
#endif
|
||||
}
|
||||
|
||||
void XMLParser::_parse_opening_xml_element() {
|
||||
node_type = NODE_ELEMENT;
|
||||
node_empty = false;
|
||||
attributes.clear();
|
||||
|
||||
// find name
|
||||
const char *startName = P;
|
||||
|
||||
// find end of element
|
||||
while (*P && *P != '>' && !_is_white_space(*P)) {
|
||||
next_char();
|
||||
}
|
||||
|
||||
const char *endName = P;
|
||||
|
||||
// find attributes
|
||||
while (*P && *P != '>') {
|
||||
if (_is_white_space(*P)) {
|
||||
next_char();
|
||||
} else {
|
||||
if (*P != '/') {
|
||||
// we've got an attribute
|
||||
|
||||
// read the attribute names
|
||||
const char *attributeNameBegin = P;
|
||||
|
||||
while (*P && !_is_white_space(*P) && *P != '=') {
|
||||
next_char();
|
||||
}
|
||||
|
||||
if (!*P) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char *attributeNameEnd = P;
|
||||
next_char();
|
||||
|
||||
// read the attribute value
|
||||
// check for quotes and single quotes, thx to murphy
|
||||
while ((*P != '\"') && (*P != '\'') && *P) {
|
||||
next_char();
|
||||
}
|
||||
|
||||
if (!*P) { // malformatted xml file
|
||||
break;
|
||||
}
|
||||
|
||||
const char attributeQuoteChar = *P;
|
||||
|
||||
next_char();
|
||||
const char *attributeValueBegin = P;
|
||||
|
||||
while (*P != attributeQuoteChar && *P) {
|
||||
next_char();
|
||||
}
|
||||
|
||||
const char *attributeValueEnd = P;
|
||||
if (*P) {
|
||||
next_char();
|
||||
}
|
||||
|
||||
Attribute attr;
|
||||
attr.name = String::utf8(attributeNameBegin,
|
||||
(int)(attributeNameEnd - attributeNameBegin));
|
||||
|
||||
String s = String::utf8(attributeValueBegin,
|
||||
(int)(attributeValueEnd - attributeValueBegin));
|
||||
|
||||
attr.value = s.xml_unescape();
|
||||
attributes.push_back(attr);
|
||||
} else {
|
||||
// tag is closed directly
|
||||
next_char();
|
||||
node_empty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if this tag is closing directly
|
||||
if (endName > startName && *(endName - 1) == '/') {
|
||||
// directly closing tag
|
||||
node_empty = true;
|
||||
endName--;
|
||||
}
|
||||
|
||||
node_name = String::utf8(startName, (int)(endName - startName));
|
||||
#ifdef DEBUG_XML
|
||||
print_line("XML OPEN: " + node_name);
|
||||
#endif
|
||||
|
||||
if (*P) {
|
||||
next_char();
|
||||
}
|
||||
}
|
||||
|
||||
void XMLParser::_parse_current_node() {
|
||||
const char *start = P;
|
||||
node_offset = P - data;
|
||||
|
||||
// more forward until '<' found
|
||||
while (*P != '<' && *P) {
|
||||
next_char();
|
||||
}
|
||||
|
||||
if (P - start > 0) {
|
||||
// we found some text, store it
|
||||
if (_set_text(start, P)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!*P) {
|
||||
return;
|
||||
}
|
||||
|
||||
next_char();
|
||||
|
||||
// based on current token, parse and report next element
|
||||
switch (*P) {
|
||||
case '/':
|
||||
_parse_closing_xml_element();
|
||||
break;
|
||||
case '?':
|
||||
_ignore_definition();
|
||||
break;
|
||||
case '!':
|
||||
if (!_parse_cdata()) {
|
||||
_parse_comment();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_parse_opening_xml_element();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t XMLParser::get_node_offset() const {
|
||||
return node_offset;
|
||||
}
|
||||
|
||||
Error XMLParser::seek(uint64_t p_pos) {
|
||||
ERR_FAIL_NULL_V(data, ERR_FILE_EOF);
|
||||
ERR_FAIL_COND_V(p_pos >= length, ERR_FILE_EOF);
|
||||
|
||||
P = data + p_pos;
|
||||
|
||||
return read();
|
||||
}
|
||||
|
||||
void XMLParser::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("read"), &XMLParser::read);
|
||||
ClassDB::bind_method(D_METHOD("get_node_type"), &XMLParser::get_node_type);
|
||||
ClassDB::bind_method(D_METHOD("get_node_name"), &XMLParser::get_node_name);
|
||||
ClassDB::bind_method(D_METHOD("get_node_data"), &XMLParser::get_node_data);
|
||||
ClassDB::bind_method(D_METHOD("get_node_offset"), &XMLParser::get_node_offset);
|
||||
ClassDB::bind_method(D_METHOD("get_attribute_count"), &XMLParser::get_attribute_count);
|
||||
ClassDB::bind_method(D_METHOD("get_attribute_name", "idx"), &XMLParser::get_attribute_name);
|
||||
ClassDB::bind_method(D_METHOD("get_attribute_value", "idx"), &XMLParser::get_attribute_value);
|
||||
ClassDB::bind_method(D_METHOD("has_attribute", "name"), &XMLParser::has_attribute);
|
||||
ClassDB::bind_method(D_METHOD("get_named_attribute_value", "name"), &XMLParser::get_named_attribute_value);
|
||||
ClassDB::bind_method(D_METHOD("get_named_attribute_value_safe", "name"), &XMLParser::get_named_attribute_value_safe);
|
||||
ClassDB::bind_method(D_METHOD("is_empty"), &XMLParser::is_empty);
|
||||
ClassDB::bind_method(D_METHOD("get_current_line"), &XMLParser::get_current_line);
|
||||
ClassDB::bind_method(D_METHOD("skip_section"), &XMLParser::skip_section);
|
||||
ClassDB::bind_method(D_METHOD("seek", "position"), &XMLParser::seek);
|
||||
ClassDB::bind_method(D_METHOD("open", "file"), &XMLParser::open);
|
||||
ClassDB::bind_method(D_METHOD("open_buffer", "buffer"), &XMLParser::open_buffer);
|
||||
|
||||
BIND_ENUM_CONSTANT(NODE_NONE);
|
||||
BIND_ENUM_CONSTANT(NODE_ELEMENT);
|
||||
BIND_ENUM_CONSTANT(NODE_ELEMENT_END);
|
||||
BIND_ENUM_CONSTANT(NODE_TEXT);
|
||||
BIND_ENUM_CONSTANT(NODE_COMMENT);
|
||||
BIND_ENUM_CONSTANT(NODE_CDATA);
|
||||
BIND_ENUM_CONSTANT(NODE_UNKNOWN);
|
||||
}
|
||||
|
||||
Error XMLParser::read() {
|
||||
// if end not reached, parse the node
|
||||
if (P && (P - data) < (int64_t)length - 1 && *P != 0) {
|
||||
_parse_current_node();
|
||||
return OK;
|
||||
}
|
||||
|
||||
return ERR_FILE_EOF;
|
||||
}
|
||||
|
||||
XMLParser::NodeType XMLParser::get_node_type() {
|
||||
return node_type;
|
||||
}
|
||||
|
||||
String XMLParser::get_node_data() const {
|
||||
ERR_FAIL_COND_V(node_type != NODE_TEXT, "");
|
||||
return node_name;
|
||||
}
|
||||
|
||||
String XMLParser::get_node_name() const {
|
||||
ERR_FAIL_COND_V(node_type == NODE_TEXT, "");
|
||||
return node_name;
|
||||
}
|
||||
|
||||
int XMLParser::get_attribute_count() const {
|
||||
return attributes.size();
|
||||
}
|
||||
|
||||
String XMLParser::get_attribute_name(int p_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_idx, attributes.size(), "");
|
||||
return attributes[p_idx].name;
|
||||
}
|
||||
|
||||
String XMLParser::get_attribute_value(int p_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_idx, attributes.size(), "");
|
||||
return attributes[p_idx].value;
|
||||
}
|
||||
|
||||
bool XMLParser::has_attribute(const String &p_name) const {
|
||||
for (int i = 0; i < attributes.size(); i++) {
|
||||
if (attributes[i].name == p_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String XMLParser::get_named_attribute_value(const String &p_name) const {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < attributes.size(); i++) {
|
||||
if (attributes[i].name == p_name) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(idx < 0, "", "Attribute not found: " + p_name + ".");
|
||||
|
||||
return attributes[idx].value;
|
||||
}
|
||||
|
||||
String XMLParser::get_named_attribute_value_safe(const String &p_name) const {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < attributes.size(); i++) {
|
||||
if (attributes[i].name == p_name) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx < 0) {
|
||||
return "";
|
||||
}
|
||||
return attributes[idx].value;
|
||||
}
|
||||
|
||||
bool XMLParser::is_empty() const {
|
||||
return node_empty;
|
||||
}
|
||||
|
||||
Error XMLParser::open_buffer(const Vector<uint8_t> &p_buffer) {
|
||||
ERR_FAIL_COND_V(p_buffer.is_empty(), ERR_INVALID_DATA);
|
||||
|
||||
if (data_copy) {
|
||||
memdelete_arr(data_copy);
|
||||
data_copy = nullptr;
|
||||
}
|
||||
|
||||
length = p_buffer.size();
|
||||
data_copy = memnew_arr(char, length + 1);
|
||||
memcpy(data_copy, p_buffer.ptr(), length);
|
||||
data_copy[length] = 0;
|
||||
data = data_copy;
|
||||
P = data;
|
||||
current_line = 0;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error XMLParser::_open_buffer(const uint8_t *p_buffer, size_t p_size) {
|
||||
ERR_FAIL_COND_V(p_size == 0, ERR_INVALID_DATA);
|
||||
ERR_FAIL_NULL_V(p_buffer, ERR_INVALID_DATA);
|
||||
|
||||
if (data_copy) {
|
||||
memdelete_arr(data_copy);
|
||||
data_copy = nullptr;
|
||||
}
|
||||
|
||||
length = p_size;
|
||||
data = (const char *)p_buffer;
|
||||
P = data;
|
||||
current_line = 0;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error XMLParser::open(const String &p_path) {
|
||||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
|
||||
|
||||
length = file->get_length();
|
||||
ERR_FAIL_COND_V(length < 1, ERR_FILE_CORRUPT);
|
||||
|
||||
if (data_copy) {
|
||||
memdelete_arr(data_copy);
|
||||
data_copy = nullptr;
|
||||
}
|
||||
|
||||
data_copy = memnew_arr(char, length + 1);
|
||||
file->get_buffer((uint8_t *)data_copy, length);
|
||||
data_copy[length] = 0;
|
||||
data = data_copy;
|
||||
P = data;
|
||||
current_line = 0;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void XMLParser::skip_section() {
|
||||
// skip if this element is empty anyway.
|
||||
if (is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read until we've reached the last element in this section
|
||||
int tagcount = 1;
|
||||
|
||||
while (tagcount && read() == OK) {
|
||||
if (get_node_type() == XMLParser::NODE_ELEMENT &&
|
||||
!is_empty()) {
|
||||
++tagcount;
|
||||
} else if (get_node_type() == XMLParser::NODE_ELEMENT_END) {
|
||||
--tagcount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XMLParser::close() {
|
||||
if (data_copy) {
|
||||
memdelete_arr(data);
|
||||
data_copy = nullptr;
|
||||
}
|
||||
data = nullptr;
|
||||
length = 0;
|
||||
P = nullptr;
|
||||
node_empty = false;
|
||||
node_type = NODE_NONE;
|
||||
node_offset = 0;
|
||||
}
|
||||
|
||||
int XMLParser::get_current_line() const {
|
||||
return current_line;
|
||||
}
|
||||
|
||||
XMLParser::~XMLParser() {
|
||||
if (data_copy) {
|
||||
memdelete_arr(data_copy);
|
||||
data_copy = nullptr;
|
||||
}
|
||||
}
|
||||
131
engine/core/io/xml_parser.h
Normal file
131
engine/core/io/xml_parser.h
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/**************************************************************************/
|
||||
/* xml_parser.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef XML_PARSER_H
|
||||
#define XML_PARSER_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/vector.h"
|
||||
|
||||
/*
|
||||
Based on irrXML (see their zlib license). Added mainly for compatibility with their Collada loader.
|
||||
*/
|
||||
|
||||
class XMLParser : public RefCounted {
|
||||
GDCLASS(XMLParser, RefCounted);
|
||||
|
||||
public:
|
||||
//! Enumeration of all supported source text file formats
|
||||
enum SourceFormat {
|
||||
SOURCE_ASCII,
|
||||
SOURCE_UTF8,
|
||||
SOURCE_UTF16_BE,
|
||||
SOURCE_UTF16_LE,
|
||||
SOURCE_UTF32_BE,
|
||||
SOURCE_UTF32_LE
|
||||
};
|
||||
|
||||
enum NodeType {
|
||||
NODE_NONE,
|
||||
NODE_ELEMENT,
|
||||
NODE_ELEMENT_END,
|
||||
NODE_TEXT,
|
||||
NODE_COMMENT,
|
||||
NODE_CDATA,
|
||||
NODE_UNKNOWN
|
||||
};
|
||||
|
||||
private:
|
||||
char *data_copy = nullptr;
|
||||
const char *data = nullptr;
|
||||
const char *P = nullptr;
|
||||
uint64_t length = 0;
|
||||
uint64_t current_line = 0;
|
||||
String node_name;
|
||||
bool node_empty = false;
|
||||
NodeType node_type = NODE_NONE;
|
||||
uint64_t node_offset = 0;
|
||||
|
||||
struct Attribute {
|
||||
String name;
|
||||
String value;
|
||||
};
|
||||
|
||||
Vector<Attribute> attributes;
|
||||
|
||||
bool _set_text(const char *start, const char *end);
|
||||
void _parse_closing_xml_element();
|
||||
void _ignore_definition();
|
||||
bool _parse_cdata();
|
||||
void _parse_comment();
|
||||
void _parse_opening_xml_element();
|
||||
void _parse_current_node();
|
||||
|
||||
_FORCE_INLINE_ void next_char() {
|
||||
if (*P == '\n') {
|
||||
current_line++;
|
||||
}
|
||||
P++;
|
||||
}
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Error read();
|
||||
NodeType get_node_type();
|
||||
String get_node_name() const;
|
||||
String get_node_data() const;
|
||||
uint64_t get_node_offset() const;
|
||||
int get_attribute_count() const;
|
||||
String get_attribute_name(int p_idx) const;
|
||||
String get_attribute_value(int p_idx) const;
|
||||
bool has_attribute(const String &p_name) const;
|
||||
String get_named_attribute_value(const String &p_name) const;
|
||||
String get_named_attribute_value_safe(const String &p_name) const; // do not print error if doesn't exist
|
||||
bool is_empty() const;
|
||||
int get_current_line() const;
|
||||
|
||||
void skip_section();
|
||||
Error seek(uint64_t p_pos);
|
||||
|
||||
Error open(const String &p_path);
|
||||
Error open_buffer(const Vector<uint8_t> &p_buffer);
|
||||
Error _open_buffer(const uint8_t *p_buffer, size_t p_size);
|
||||
|
||||
void close();
|
||||
|
||||
~XMLParser();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(XMLParser::NodeType);
|
||||
|
||||
#endif // XML_PARSER_H
|
||||
187
engine/core/io/zip_io.cpp
Normal file
187
engine/core/io/zip_io.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/**************************************************************************/
|
||||
/* zip_io.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 "zip_io.h"
|
||||
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
int godot_unzip_get_current_file_info(unzFile p_zip_file, unz_file_info64 &r_file_info, String &r_filepath) {
|
||||
const uLong short_file_path_buffer_size = 16384ul;
|
||||
char short_file_path_buffer[short_file_path_buffer_size];
|
||||
|
||||
int err = unzGetCurrentFileInfo64(p_zip_file, &r_file_info, short_file_path_buffer, short_file_path_buffer_size, nullptr, 0, nullptr, 0);
|
||||
if (unlikely((err != UNZ_OK) || (r_file_info.size_filename > short_file_path_buffer_size))) {
|
||||
LocalVector<char> long_file_path_buffer;
|
||||
long_file_path_buffer.resize(r_file_info.size_filename);
|
||||
|
||||
err = unzGetCurrentFileInfo64(p_zip_file, &r_file_info, long_file_path_buffer.ptr(), long_file_path_buffer.size(), nullptr, 0, nullptr, 0);
|
||||
if (err != UNZ_OK) {
|
||||
return err;
|
||||
}
|
||||
r_filepath = String::utf8(long_file_path_buffer.ptr(), r_file_info.size_filename);
|
||||
} else {
|
||||
r_filepath = String::utf8(short_file_path_buffer, r_file_info.size_filename);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int godot_unzip_locate_file(unzFile p_zip_file, const String &p_filepath, bool p_case_sensitive) {
|
||||
int err = unzGoToFirstFile(p_zip_file);
|
||||
while (err == UNZ_OK) {
|
||||
unz_file_info64 current_file_info;
|
||||
String current_filepath;
|
||||
err = godot_unzip_get_current_file_info(p_zip_file, current_file_info, current_filepath);
|
||||
if (err == UNZ_OK) {
|
||||
bool filepaths_are_equal = p_case_sensitive ? (p_filepath == current_filepath) : (p_filepath.nocasecmp_to(current_filepath) == 0);
|
||||
if (filepaths_are_equal) {
|
||||
return UNZ_OK;
|
||||
}
|
||||
err = unzGoToNextFile(p_zip_file);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void *zipio_open(voidpf opaque, const char *p_fname, int mode) {
|
||||
Ref<FileAccess> *fa = reinterpret_cast<Ref<FileAccess> *>(opaque);
|
||||
ERR_FAIL_NULL_V(fa, nullptr);
|
||||
|
||||
String fname;
|
||||
fname.parse_utf8(p_fname);
|
||||
|
||||
int file_access_mode = 0;
|
||||
if (mode & ZLIB_FILEFUNC_MODE_READ) {
|
||||
file_access_mode |= FileAccess::READ;
|
||||
}
|
||||
if (mode & ZLIB_FILEFUNC_MODE_WRITE) {
|
||||
file_access_mode |= FileAccess::WRITE;
|
||||
}
|
||||
if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
|
||||
file_access_mode |= FileAccess::WRITE_READ;
|
||||
}
|
||||
|
||||
(*fa) = FileAccess::open(fname, file_access_mode);
|
||||
if (fa->is_null()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return opaque;
|
||||
}
|
||||
|
||||
uLong zipio_read(voidpf opaque, voidpf stream, void *buf, uLong size) {
|
||||
Ref<FileAccess> *fa = reinterpret_cast<Ref<FileAccess> *>(opaque);
|
||||
ERR_FAIL_NULL_V(fa, 0);
|
||||
ERR_FAIL_COND_V(fa->is_null(), 0);
|
||||
|
||||
return (*fa)->get_buffer((uint8_t *)buf, size);
|
||||
}
|
||||
|
||||
uLong zipio_write(voidpf opaque, voidpf stream, const void *buf, uLong size) {
|
||||
Ref<FileAccess> *fa = reinterpret_cast<Ref<FileAccess> *>(opaque);
|
||||
ERR_FAIL_NULL_V(fa, 0);
|
||||
ERR_FAIL_COND_V(fa->is_null(), 0);
|
||||
|
||||
(*fa)->store_buffer((uint8_t *)buf, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
long zipio_tell(voidpf opaque, voidpf stream) {
|
||||
Ref<FileAccess> *fa = reinterpret_cast<Ref<FileAccess> *>(opaque);
|
||||
ERR_FAIL_NULL_V(fa, 0);
|
||||
ERR_FAIL_COND_V(fa->is_null(), 0);
|
||||
|
||||
return (*fa)->get_position();
|
||||
}
|
||||
|
||||
long zipio_seek(voidpf opaque, voidpf stream, uLong offset, int origin) {
|
||||
Ref<FileAccess> *fa = reinterpret_cast<Ref<FileAccess> *>(opaque);
|
||||
ERR_FAIL_NULL_V(fa, 0);
|
||||
ERR_FAIL_COND_V(fa->is_null(), 0);
|
||||
|
||||
uint64_t pos = offset;
|
||||
switch (origin) {
|
||||
case ZLIB_FILEFUNC_SEEK_CUR:
|
||||
pos = (*fa)->get_position() + offset;
|
||||
break;
|
||||
case ZLIB_FILEFUNC_SEEK_END:
|
||||
pos = (*fa)->get_length() + offset;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
(*fa)->seek(pos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zipio_close(voidpf opaque, voidpf stream) {
|
||||
Ref<FileAccess> *fa = reinterpret_cast<Ref<FileAccess> *>(opaque);
|
||||
ERR_FAIL_NULL_V(fa, 0);
|
||||
ERR_FAIL_COND_V(fa->is_null(), 0);
|
||||
|
||||
fa->unref();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zipio_testerror(voidpf opaque, voidpf stream) {
|
||||
Ref<FileAccess> *fa = reinterpret_cast<Ref<FileAccess> *>(opaque);
|
||||
ERR_FAIL_NULL_V(fa, 1);
|
||||
ERR_FAIL_COND_V(fa->is_null(), 0);
|
||||
|
||||
return (fa->is_valid() && (*fa)->get_error() != OK) ? 1 : 0;
|
||||
}
|
||||
|
||||
voidpf zipio_alloc(voidpf opaque, uInt items, uInt size) {
|
||||
voidpf ptr = memalloc((size_t)items * size);
|
||||
memset(ptr, 0, items * size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void zipio_free(voidpf opaque, voidpf address) {
|
||||
memfree(address);
|
||||
}
|
||||
|
||||
zlib_filefunc_def zipio_create_io(Ref<FileAccess> *p_data) {
|
||||
zlib_filefunc_def io;
|
||||
io.opaque = (void *)p_data;
|
||||
io.zopen_file = zipio_open;
|
||||
io.zread_file = zipio_read;
|
||||
io.zwrite_file = zipio_write;
|
||||
io.ztell_file = zipio_tell;
|
||||
io.zseek_file = zipio_seek;
|
||||
io.zclose_file = zipio_close;
|
||||
io.zerror_file = zipio_testerror;
|
||||
io.alloc_mem = zipio_alloc;
|
||||
io.free_mem = zipio_free;
|
||||
return io;
|
||||
}
|
||||
65
engine/core/io/zip_io.h
Normal file
65
engine/core/io/zip_io.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**************************************************************************/
|
||||
/* zip_io.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ZIP_IO_H
|
||||
#define ZIP_IO_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
// Not directly used in this header, but assumed available in downstream users
|
||||
// like platform/*/export/export.cpp. Could be fixed, but probably better to have
|
||||
// thirdparty includes in as little headers as possible.
|
||||
#include "thirdparty/minizip/unzip.h"
|
||||
#include "thirdparty/minizip/zip.h"
|
||||
|
||||
// Get the current file info and safely convert the full filepath to a String.
|
||||
int godot_unzip_get_current_file_info(unzFile p_zip_file, unz_file_info64 &r_file_info, String &r_filepath);
|
||||
// Try to locate the file in the archive specified by the filepath (works with large paths and Unicode).
|
||||
int godot_unzip_locate_file(unzFile p_zip_file, const String &p_filepath, bool p_case_sensitive = true);
|
||||
|
||||
//
|
||||
|
||||
void *zipio_open(voidpf opaque, const char *p_fname, int mode);
|
||||
uLong zipio_read(voidpf opaque, voidpf stream, void *buf, uLong size);
|
||||
uLong zipio_write(voidpf opaque, voidpf stream, const void *buf, uLong size);
|
||||
|
||||
long zipio_tell(voidpf opaque, voidpf stream);
|
||||
long zipio_seek(voidpf opaque, voidpf stream, uLong offset, int origin);
|
||||
|
||||
int zipio_close(voidpf opaque, voidpf stream);
|
||||
|
||||
int zipio_testerror(voidpf opaque, voidpf stream);
|
||||
|
||||
voidpf zipio_alloc(voidpf opaque, uInt items, uInt size);
|
||||
void zipio_free(voidpf opaque, voidpf address);
|
||||
|
||||
zlib_filefunc_def zipio_create_io(Ref<FileAccess> *p_data);
|
||||
|
||||
#endif // ZIP_IO_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue