Compare commits

..

14 Commits

Author SHA1 Message Date
Syoyo Fujita
5a6df34d99 Simplify version comment in tiny_gltf.h
Remove wuffs code(which was accidently adde to `release` branch)
2023-06-04 19:07:00 +09:00
Syoyo Fujita
147a00a601 Prevent duplicated key generation when serializing lights + RapidJSON backend. Fixes #420 2023-06-04 05:45:24 +09:00
Syoyo Fujita
350c296802 Merge pull request #418 from agnat/fix_get_file_size_bug
[Bugfix] Actually invoke the user-supplied function instead of subtracting from a pointer...
2023-04-26 19:28:58 +09:00
David Siegel
cc93e1fd25 Fix: Actually invoke the user-supplied function 2023-04-26 12:13:41 +02:00
Syoyo Fujita
59cc44ad4f Merge pull request #417 from syoyo/filesize-check
Fix to #416
2023-04-23 23:15:07 +09:00
Syoyo Fujita
1a5046e06b Fix MSVC compile failure on AppVeyor CI. 2023-04-23 23:08:41 +09:00
Syoyo Fujita
877d856e71 Format error message.
Add regression test of issue-416.
2023-04-23 21:47:31 +09:00
Syoyo Fujita
b534b6b0d8 Fix syntax. 2023-04-23 21:40:23 +09:00
Syoyo Fujita
ecfd37dee2 - Add GetFileSizeInBytes Filesystem Callback
- Add feature to limit file size for external resources(images, buffers)
- Use strlen to correctly retrieve a string from a string which contains multiple null-characters.
- Return fail when opening a directory(Posix only). Fixes #416
2023-04-23 21:31:30 +09:00
Syoyo Fujita
5c06b7d03b Merge pull request #415 from louwaque/release
Fix serialization of AnimationChannel::target_node when it is zero
2023-04-19 05:17:06 +09:00
Loïc Escales
a75355b018 Fix serialization of AnimationChannel::target_node when it is zero 2023-04-18 21:03:39 +02:00
Syoyo Fujita
a977f7a16f Merge pull request #412 from agnat/add_char_pointer_ctor
Fix #411 by adding a Value(const char *) constructor.
2023-04-10 19:11:35 +09:00
Syoyo Fujita
5a6c55870e Deprecate ubuntu-18.04 image and update CI build configuration. 2023-04-10 18:51:29 +09:00
David Siegel
49caa65177 Fix #411 by adding a Value(const char *) constructor.
Avoid implicit conversion of pointers to bool. Closes #411.
2023-04-08 23:44:53 +02:00
5 changed files with 289 additions and 73 deletions

View File

@@ -4,41 +4,43 @@ on: [push, pull_request]
jobs:
# compile with older gcc4.8
build-gcc48:
# gcc4.8 is too old and ubuntu-18.04 image is not supported in GitHub Actions anymore,
# so disable this build.
## compile with older gcc4.8
#build-gcc48:
runs-on: ubuntu-18.04
name: Build with gcc 4.8
# runs-on: ubuntu-18.04
# name: Build with gcc 4.8
steps:
- name: Checkout
uses: actions/checkout@v1
# steps:
# - name: Checkout
# uses: actions/checkout@v1
- name: Build
run: |
sudo apt-get update
sudo apt-get install -y build-essential
sudo apt-get install -y gcc-4.8 g++-4.8
g++-4.8 -std=c++11 -o loader_example loader_example.cc
# - name: Build
# run: |
# sudo apt-get update
# sudo apt-get install -y build-essential
# sudo apt-get install -y gcc-4.8 g++-4.8
# g++-4.8 -std=c++11 -o loader_example loader_example.cc
- name: NoexceptBuild
run: |
g++-4.8 -DTINYGLTF_NOEXCEPTION -std=c++11 -o loader_example loader_example.cc
# - name: NoexceptBuild
# run: |
# g++-4.8 -DTINYGLTF_NOEXCEPTION -std=c++11 -o loader_example loader_example.cc
- name: RapidjsonBuild
run: |
git clone https://github.com/Tencent/rapidjson
g++-4.8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -o loader_example loader_example.cc
# - name: RapidjsonBuild
# run: |
# git clone https://github.com/Tencent/rapidjson
# g++-4.8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -o loader_example loader_example.cc
# compile with mingw gcc cross
build-mingw-cross:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
name: Build with MinGW gcc cross
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Build
run: |
@@ -133,20 +135,20 @@ jobs:
# Cross-compile for aarch64 linux target
build-cross-aarch64:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
name: Build on cross aarch64
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Build
run: |
sudo apt-get update
sudo apt-get install -y build-essential
sudo apt-get install -y gcc-8-aarch64-linux-gnu g++-8-aarch64-linux-gnu
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
git clone https://github.com/Tencent/rapidjson
aarch64-linux-gnu-g++-8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -g -O0 -o loader_example loader_example.cc
aarch64-linux-gnu-g++ -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -g -O0 -o loader_example loader_example.cc
# macOS clang
build-macos:

View File

@@ -197,6 +197,7 @@ if (!ret) {
* `TINYGLTF_USE_RAPIDJSON` : Use RapidJSON as a JSON parser/serializer. RapidJSON files are not included in TinyGLTF repo. Please set an include path to RapidJSON if you enable this feature.
* `TINYGLTF_USE_CPP14` : Use C++14 feature(requires C++14 compiler). This may give better performance than C++11.
## CMake options
You can add tinygltf using `add_subdirectory` feature.

1
tests/issue-416.gltf Normal file
View File

@@ -0,0 +1 @@
{"images":[{"uri":"%!QAAAQAAA5"}],"asset":{"version":""}}

View File

@@ -721,3 +721,39 @@ TEST_CASE("serialize-image-failure", "[issue-394]") {
REQUIRE(false == result);
REQUIRE(os.str().size() == 0);
}
TEST_CASE("filesize-check", "[issue-416]") {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
ctx.SetMaxExternalFileSize(10); // 10 bytes. will fail to load texture image.
bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/Cube/Cube.gltf");
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(false == ret);
}
TEST_CASE("load-issue-416-model", "[issue-416]") {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "issue-416.gltf");
if (!warn.empty()) {
std::cout << "WARN:" << warn << std::endl;
}
if (!err.empty()) {
std::cerr << "ERR:" << err << std::endl;
}
// external file load fails, but reading glTF itself is ok.
REQUIRE(true == ret);
}

View File

@@ -25,32 +25,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Version:
// - v2.8.1 Missed serialization texture sampler name fixed. PR#399.
// - v2.8.0 Add URICallbacks for custom URI handling in Buffer and Image. PR#397.
// - v2.7.0 Change WriteImageDataFunction user callback function signature. PR#393.
// - v2.6.3 Fix GLB file with empty BIN chunk was not handled. PR#382 and PR#383.
// - v2.6.2 Fix out-of-bounds access of accessors. PR#379.
// - v2.6.1 Better GLB validation check when loading.
// - v2.6.0 Support serializing sparse accessor(Thanks to @fynv).
// Disable expanding file path for security(no use of awkward `wordexp` anymore).
// - v2.5.0 Add SetPreserveImageChannels() option to load image data as is.
// - v2.4.3 Fix null object output when material has all default
// parameters.
// - v2.4.2 Decode percent-encoded URI.
// - v2.4.1 Fix some glTF object class does not have `extensions` and/or
// `extras` property.
// - v2.4.0 Experimental RapidJSON and C++14 support(Thanks to @jrkoone).
// - v2.3.1 Set default value of minFilter and magFilter in Sampler to -1.
// - v2.3.0 Modified Material representation according to glTF 2.0 schema
// (and introduced TextureInfo class)
// Change the behavior of `Value::IsNumber`. It return true either the
// value is int or real.
// - v2.2.0 Add loading 16bit PNG support. Add Sparse accessor support(Thanks
// to @Ybalrid)
// - v2.1.0 Add draco compression.
// - v2.0.1 Add comparison feature(Thanks to @Selmar).
// - v2.0.0 glTF 2.0!.
// Version: - v2.8.10
// See https://github.com/syoyo/tinygltf/releases for release history.
//
// Tiny glTF loader is using following third party libraries:
//
@@ -301,6 +277,9 @@ class Value {
}
explicit Value(std::string &&s)
: type_(STRING_TYPE), string_value_(std::move(s)) {}
explicit Value(const char *s) : type_(STRING_TYPE) {
string_value_ = s;
}
explicit Value(const unsigned char *p, size_t n) : type_(BINARY_TYPE) {
binary_value_.resize(n);
memcpy(binary_value_.data(), p, n);
@@ -1301,6 +1280,12 @@ typedef bool (*WriteWholeFileFunction)(std::string *, const std::string &,
const std::vector<unsigned char> &,
void *);
///
/// GetFileSizeFunction type. Signature for custom filesystem callbacks.
///
typedef bool (*GetFileSizeFunction)(size_t *filesize_out, std::string *err, const std::string &abs_filename,
void *userdata);
///
/// A structure containing all required filesystem callbacks and a pointer to
/// their user data.
@@ -1310,6 +1295,7 @@ struct FsCallbacks {
ExpandFilePathFunction ExpandFilePath;
ReadWholeFileFunction ReadWholeFile;
WriteWholeFileFunction WriteWholeFile;
GetFileSizeFunction GetFileSizeInBytes; // To avoid GetFileSize Win32 API, add `InBytes` suffix.
void *user_data; // An argument that is passed to all fs callbacks
};
@@ -1333,6 +1319,9 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
bool WriteWholeFile(std::string *err, const std::string &filepath,
const std::vector<unsigned char> &contents, void *);
bool GetFileSizeInBytes(size_t *filesize_out, std::string *err, const std::string &filepath,
void *);
#endif
///
@@ -1472,6 +1461,19 @@ class TinyGLTF {
preserve_image_channels_ = onoff;
}
///
/// Set maximum allowed external file size in bytes.
/// Default: 2GB
/// Only effective for built-in ReadWholeFileFunction FS function.
///
void SetMaxExternalFileSize(size_t max_bytes) {
max_external_file_size_ = max_bytes;
}
size_t GetMaxExternalFileSize() const {
return max_external_file_size_;
}
bool GetPreserveImageChannels() const { return preserve_image_channels_; }
private:
@@ -1496,6 +1498,8 @@ class TinyGLTF {
bool preserve_image_channels_ = false; /// Default false(expand channels to
/// RGBA) for backward compatibility.
size_t max_external_file_size_{size_t((std::numeric_limits<int32_t>::max)())}; // Default 2GB
// Warning & error messages
std::string warn_;
std::string err_;
@@ -1503,11 +1507,11 @@ class TinyGLTF {
FsCallbacks fs = {
#ifndef TINYGLTF_NO_FS
&tinygltf::FileExists, &tinygltf::ExpandFilePath,
&tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile,
&tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile, &tinygltf::GetFileSizeInBytes,
nullptr // Fs callback user data
#else
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr // Fs callback user data
#endif
@@ -1554,6 +1558,7 @@ class TinyGLTF {
#ifndef TINYGLTF_NO_FS
#include <cstdio>
#include <fstream>
#include <sys/stat.h> // for is_directory check
#endif
#include <sstream>
@@ -2107,9 +2112,19 @@ static std::string FindFile(const std::vector<std::string> &paths,
return std::string();
}
// https://github.com/syoyo/tinygltf/issues/416
// Use strlen() since std::string's size/length reports the number of elements in the buffer, not the length of string(null-terminated)
// strip null-character in the middle of string.
size_t slength = strlen(filepath.c_str());
if (slength == 0) {
return std::string();
}
std::string cleaned_filepath = std::string(filepath.c_str());
for (size_t i = 0; i < paths.size(); i++) {
std::string absPath =
fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data);
fs->ExpandFilePath(JoinPath(paths[i], cleaned_filepath), fs->user_data);
if (fs->FileExists(absPath, fs->user_data)) {
return absPath;
}
@@ -2355,7 +2370,7 @@ bool URIDecode(const std::string &in_uri, std::string *out_uri,
static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
std::string *warn, const std::string &filename,
const std::string &basedir, bool required,
size_t reqBytes, bool checkSize, FsCallbacks *fs) {
size_t reqBytes, bool checkSize, size_t maxFileSize, FsCallbacks *fs) {
if (fs == nullptr || fs->FileExists == nullptr ||
fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) {
// This is a developer error, assert() ?
@@ -2381,6 +2396,29 @@ static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
return false;
}
// Check file size
if (fs->GetFileSizeInBytes) {
size_t file_size{0};
std::string _err;
bool ok = fs->GetFileSizeInBytes(&file_size, &_err, filepath, fs->user_data);
if (!ok) {
if (_err.size()) {
if (failMsgOut) {
(*failMsgOut) += "Getting file size failed : " + filename + ", err = " + _err + "\n";
}
}
return false;
}
if (file_size > maxFileSize) {
if (failMsgOut) {
(*failMsgOut) += "File size " + std::to_string(file_size) + " exceeds maximum allowed file size " + std::to_string(maxFileSize) + " : " + filepath + "\n";
}
return false;
}
}
std::vector<unsigned char> buf;
std::string fileReadErr;
bool fileRead =
@@ -2687,12 +2725,23 @@ bool FileExists(const std::string &abs_filename, void *) {
#else
#ifdef _WIN32
#if defined(_MSC_VER) || defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
// First check if a file is a directory.
DWORD result = GetFileAttributesW(UTF8ToWchar(abs_filename).c_str());
if (result == INVALID_FILE_ATTRIBUTES) {
return false;
}
if (result & FILE_ATTRIBUTE_DIRECTORY) {
return false;
}
FILE *fp = nullptr;
errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb");
if (err != 0) {
return false;
}
#else
// TODO: is_directory check
FILE *fp = nullptr;
errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
if (err != 0) {
@@ -2701,6 +2750,14 @@ bool FileExists(const std::string &abs_filename, void *) {
#endif
#else
struct stat sb;
if (stat(abs_filename.c_str(), &sb)) {
return false;
}
if (S_ISDIR(sb.st_mode)) {
return false;
}
FILE *fp = fopen(abs_filename.c_str(), "rb");
#endif
if (fp) {
@@ -2777,6 +2834,100 @@ std::string ExpandFilePath(const std::string &filepath, void *) {
#endif
}
bool GetFileSizeInBytes(size_t *filesize_out, std::string *err,
const std::string &filepath, void *userdata) {
(void)userdata;
#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
if (asset_manager) {
AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(),
AASSET_MODE_STREAMING);
if (!asset) {
if (err) {
(*err) += "File open error : " + filepath + "\n";
}
return false;
}
size_t size = AAsset_getLength(asset);
if (size == 0) {
if (err) {
(*err) += "Invalid file size : " + filepath +
" (does the path point to a directory?)";
}
return false;
}
return true;
} else {
if (err) {
(*err) += "No asset manager specified : " + filepath + "\n";
}
return false;
}
#else
#ifdef _WIN32
#if defined(__GLIBCXX__) // mingw
int file_descriptor =
_wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY);
__gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
std::istream f(&wfile_buf);
#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION)
// For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept
// `wchar_t *`
std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary);
#else
// Unknown compiler/runtime
std::ifstream f(filepath.c_str(), std::ifstream::binary);
#endif
#else
std::ifstream f(filepath.c_str(), std::ifstream::binary);
#endif
if (!f) {
if (err) {
(*err) += "File open error : " + filepath + "\n";
}
return false;
}
// For directory(and pipe?), peek() will fail(Posix gnustl/libc++ only)
int buf = f.peek();
if (!f) {
if (err) {
(*err) += "File read error. Maybe empty file or invalid file : " + filepath + "\n";
}
return false;
}
f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
//std::cout << "sz = " << sz << "\n";
f.seekg(0, f.beg);
if (int64_t(sz) < 0) {
if (err) {
(*err) += "Invalid file size : " + filepath +
" (does the path point to a directory?)";
}
return false;
} else if (sz == 0) {
if (err) {
(*err) += "File is empty : " + filepath + "\n";
}
return false;
} else if (sz >= (std::numeric_limits<std::streamoff>::max)()) {
if (err) {
(*err) += "Invalid file size : " + filepath + "\n";
}
return false;
}
(*filesize_out) = sz;
return true;
#endif
}
bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
const std::string &filepath, void *) {
#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
@@ -2832,8 +2983,19 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
return false;
}
// For directory(and pipe?), peek() will fail(Posix gnustl/libc++ only)
int buf = f.peek();
if (!f) {
if (err) {
(*err) += "File read error. Maybe empty file or invalid file : " + filepath + "\n";
}
return false;
}
f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
//std::cout << "sz = " << sz << "\n";
f.seekg(0, f.beg);
if (int64_t(sz) < 0) {
@@ -2847,6 +3009,11 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
(*err) += "File is empty : " + filepath + "\n";
}
return false;
} else if (sz >= (std::numeric_limits<std::streamoff>::max)()) {
if (err) {
(*err) += "Invalid file size : " + filepath + "\n";
}
return false;
}
out->resize(sz);
@@ -3873,7 +4040,7 @@ static bool ParseAsset(Asset *asset, std::string *err, const detail::json &o,
static bool ParseImage(Image *image, const int image_idx, std::string *err,
std::string *warn, const detail::json &o,
bool store_original_json_for_extras_and_extensions,
const std::string &basedir, FsCallbacks *fs,
const std::string &basedir, const size_t max_file_size, FsCallbacks *fs,
const URICallbacks *uri_cb,
LoadImageDataFunction *LoadImageData = nullptr,
void *load_image_user_data = nullptr) {
@@ -3973,8 +4140,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) {
if (err) {
(*err) += "Failed to decode 'uri' for image[" +
std::to_string(image_idx) + "] name = [" + image->name +
"]\n";
std::to_string(image_idx) + "] name = \"" + image->name +
"\"\n";
}
return false;
}
@@ -3989,8 +4156,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
if (!uri_cb->decode(uri, &decoded_uri, uri_cb->user_data)) {
if (warn) {
(*warn) += "Failed to decode 'uri' for image[" +
std::to_string(image_idx) + "] name = [" + image->name +
"]\n";
std::to_string(image_idx) + "] name = \"" + image->name +
"\"\n";
}
// Image loading failure is not critical to overall gltf loading.
@@ -3999,11 +4166,11 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir,
/* required */ false, /* required bytes */ 0,
/* checksize */ false, fs)) {
/* checksize */ false, /* max file size */ max_file_size, fs)) {
if (warn) {
(*warn) += "Failed to load external 'uri' for image[" +
std::to_string(image_idx) + "] name = [" + image->name +
"]\n";
std::to_string(image_idx) + "] name = \"" + decoded_uri +
"\"\n";
}
// If the image cannot be loaded, keep uri as image->uri.
return true;
@@ -4012,8 +4179,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
if (img.empty()) {
if (warn) {
(*warn) += "Image data is empty for image[" +
std::to_string(image_idx) + "] name = [" + image->name +
"] \n";
std::to_string(image_idx) + "] name = \"" + image->name +
"\" \n";
}
return false;
}
@@ -4176,7 +4343,7 @@ static bool ParseOcclusionTextureInfo(
static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o,
bool store_original_json_for_extras_and_extensions,
FsCallbacks *fs, const URICallbacks *uri_cb,
const std::string &basedir, bool is_binary = false,
const std::string &basedir, const size_t max_buffer_size, bool is_binary = false,
const unsigned char *bin_data = nullptr,
size_t bin_size = 0) {
size_t byteLength;
@@ -4228,7 +4395,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o,
}
if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr,
decoded_uri, basedir, /* required */ true,
byteLength, /* checkSize */ true, fs)) {
byteLength, /* checkSize */ true, /* max_file_size */max_buffer_size, fs)) {
return false;
}
}
@@ -4276,7 +4443,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o,
}
if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri,
basedir, /* required */ true, byteLength,
/* checkSize */ true, fs)) {
/* checkSize */ true, /* max file size */max_buffer_size, fs)) {
return false;
}
}
@@ -5811,7 +5978,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
Buffer buffer;
if (!ParseBuffer(&buffer, err, o,
store_original_json_for_extras_and_extensions_, &fs,
&uri_cb, base_dir, is_binary_, bin_data_, bin_size_)) {
&uri_cb, base_dir, max_external_file_size_, is_binary_, bin_data_, bin_size_)) {
return false;
}
@@ -6082,7 +6249,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
Image image;
if (!ParseImage(&image, idx, err, warn, o,
store_original_json_for_extras_and_extensions_, base_dir,
&fs, &uri_cb, &this->LoadImageData,
max_external_file_size_, &fs, &uri_cb, &this->LoadImageData,
load_image_user_data)) {
return false;
}
@@ -6576,7 +6743,16 @@ void JsonAddMember(detail::json &o, const char *key, detail::json &&value) {
if (!o.IsObject()) {
o.SetObject();
}
o.AddMember(detail::json(key, detail::GetAllocator()), std::move(value), detail::GetAllocator());
// Issue 420.
// AddMember may create duplicated key, so use [] API when a key already exists.
// https://github.com/Tencent/rapidjson/issues/771#issuecomment-254386863
detail::json_const_iterator it;
if (detail::FindMember(o, key, it)) {
o[key] = std::move(value); // replace
} else {
o.AddMember(detail::json(key, detail::GetAllocator()), std::move(value), detail::GetAllocator());
}
#else
o[key] = std::move(value);
#endif
@@ -6967,7 +7143,7 @@ static void SerializeGltfAnimationChannel(const AnimationChannel &channel,
{
detail::json target;
if (channel.target_node > 0) {
if (channel.target_node >= 0) {
SerializeNumberProperty("node", channel.target_node, target);
}