diff --git a/.gitignore b/.gitignore index 879c4cd..92ed8ed 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,11 @@ loader_example tests/tester tests/tester_noexcept tests/issue-97.gltf +tests/issue-261.gltf +# unignore +!Makefile +!examples/build-gltf/Makefile +!examples/raytrace/cornellbox_suzanne.obj +!tests/Makefile +!tools/windows/premake5.exe diff --git a/tests/tester.cc b/tests/tester.cc index c8acf43..0d69a83 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -482,3 +482,61 @@ TEST_CASE("expandpath-utf-8", "[pr-226]") { } #endif + +TEST_CASE("empty-bin-buffer", "[issue-382]") { + tinygltf::Model model; + tinygltf::TinyGLTF ctx; + std::string err; + std::string warn; + + tinygltf::Model model_empty; + std::stringstream stream; + bool ret = ctx.WriteGltfSceneToStream(&model_empty, stream, false, true); + REQUIRE(ret == true); + std::string str = stream.str(); + const unsigned char* bytes = (unsigned char*)str.data(); + ret = ctx.LoadBinaryFromMemory(&model, &err, &warn, bytes, str.size()); + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + + err.clear(); + warn.clear(); + + tinygltf::Model model_empty_buffer; + model_empty_buffer.buffers.push_back(tinygltf::Buffer()); + stream = std::stringstream(); + ret = ctx.WriteGltfSceneToStream(&model_empty_buffer, stream, false, true); + REQUIRE(ret == true); + str = stream.str(); + bytes = (unsigned char*)str.data(); + ret = ctx.LoadBinaryFromMemory(&model, &err, &warn, bytes, str.size()); + if (err.empty()) { + std::cerr << "there should have been an error reported" << std::endl; + } + REQUIRE(false == ret); + + err.clear(); + warn.clear(); + + tinygltf::Model model_single_byte_buffer; + tinygltf::Buffer buffer; + buffer.data.push_back(0); + model_single_byte_buffer.buffers.push_back(buffer); + stream = std::stringstream(); + ret = ctx.WriteGltfSceneToStream(&model_single_byte_buffer, stream, false, true); + REQUIRE(ret == true); + str = stream.str(); + { + std::ofstream ofs("tmp.glb"); + ofs.write(str.data(), str.size()); + } + + bytes = (unsigned char*)str.data(); + ret = ctx.LoadBinaryFromMemory(&model_single_byte_buffer, &err, &warn, bytes, str.size()); + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); +} diff --git a/tiny_gltf.h b/tiny_gltf.h index 734c726..fb99d92 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -4113,7 +4113,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, if ((bin_size == 0) || (bin_data == nullptr)) { if (err) { - (*err) += "Invalid binary data in `Buffer'.\n"; + (*err) += "Invalid binary data in `Buffer', or GLB with empty BIN chunk.\n"; } return false; } @@ -6286,15 +6286,15 @@ bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, // // https://github.com/syoyo/tinygltf/issues/372 // Use 64bit uint to avoid integer overflow. - uint64_t json_size = 20ull + uint64_t(chunk0_length); + uint64_t header_and_json_size = 20ull + uint64_t(chunk0_length); - if (json_size > std::numeric_limits::max()) { + if (header_and_json_size > std::numeric_limits::max()) { // Do not allow 4GB or more GLB data. (*err) = "Invalid glTF binary. GLB data exceeds 4GB."; } - if ((json_size > uint64_t(size)) || (chunk0_length < 1) || (length > size) || - (json_size > uint64_t(length)) || + if ((header_and_json_size > uint64_t(size)) || (chunk0_length < 1) || (length > size) || + (header_and_json_size > uint64_t(length)) || (chunk0_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format. if (err) { (*err) = "Invalid glTF binary."; @@ -6305,61 +6305,77 @@ bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, // Padding check // The start and the end of each chunk must be aligned to a 4-byte boundary. // No padding check for chunk0 start since its 4byte-boundary is ensured. - if ((json_size % 4) != 0) { + if ((header_and_json_size % 4) != 0) { if (err) { (*err) = "JSON Chunk end does not aligned to a 4-byte boundary."; } } - // Read Chunk1 info(BIN data) - if ((json_size + 8ull) > uint64_t(length)) { - if (err) { - (*err) = "Insufficient storage space for Chunk1(BIN data)."; + //std::cout << "header_and_json_size = " << header_and_json_size << "\n"; + //std::cout << "length = " << length << "\n"; + + // Chunk1(BIN) data + // The spec says: When the binary buffer is empty or when it is stored by other means, this chunk SHOULD be omitted. + // So when header + JSON data == binary size, Chunk1 is omitted. + if (header_and_json_size == uint64_t(length)) { + + bin_data_ = nullptr; + bin_size_ = 0; + } else { + // Read Chunk1 info(BIN data) + // At least Chunk1 should have 12 bytes(8 bytes(header) + 4 bytes(bin payload could be 1~3 bytes, but need to be aliged to 4 bytes) + if ((header_and_json_size + 12ull) > uint64_t(length)) { + if (err) { + (*err) = "Insufficient storage space for Chunk1(BIN data). At least Chunk1 Must have 4 bytes or more bytes, but got " + std::to_string((header_and_json_size + 12ull) - uint64_t(length)) + ".\n"; + } + return false; } - return false; - } - unsigned int chunk1_length; // 4 bytes - unsigned int chunk1_format; // 4 bytes; - memcpy(&chunk1_length, bytes + json_size, 4); // JSON data length - swap4(&chunk1_length); - memcpy(&chunk1_format, bytes + json_size + 4, 4); - swap4(&chunk1_format); + unsigned int chunk1_length; // 4 bytes + unsigned int chunk1_format; // 4 bytes; + memcpy(&chunk1_length, bytes + header_and_json_size, 4); // JSON data length + swap4(&chunk1_length); + memcpy(&chunk1_format, bytes + header_and_json_size + 4, 4); + swap4(&chunk1_format); - if (chunk1_length < 4) { - // TODO: Do we allow 0byte BIN data? - if (err) { - (*err) = "Insufficient Chunk1(BIN) data size."; + //std::cout << "chunk1_length = " << chunk1_length << "\n"; + + if (chunk1_length < 4) { + if (err) { + (*err) = "Insufficient Chunk1(BIN) data size."; + } + return false; } - return false; - } - if ((chunk1_length % 4) != 0) { - if (err) { - (*err) = "BIN Chunk end does not aligned to a 4-byte boundary."; + if ((chunk1_length % 4) != 0) { + if (err) { + (*err) = "BIN Chunk end does not aligned to a 4-byte boundary."; + } + return false; } - return false; - } - if (uint64_t(chunk1_length) + json_size > uint64_t(length)) { - if (err) { - (*err) = "BIN Chunk data length exceeds the GLB size."; + if (uint64_t(chunk1_length) + header_and_json_size > uint64_t(length)) { + if (err) { + (*err) = "BIN Chunk data length exceeds the GLB size."; + } + return false; } - return false; - } - if (chunk1_format != 0x004e4942) { - if (err) { - (*err) = "Invlid type for chunk1 data."; + if (chunk1_format != 0x004e4942) { + if (err) { + (*err) = "Invlid type for chunk1 data."; + } + return false; } - return false; + + //std::cout << "chunk1_length = " << chunk1_length << "\n"; + + bin_data_ = bytes + header_and_json_size + + 8; // 4 bytes (bin_buffer_length) + 4 bytes(bin_buffer_format) + + bin_size_ = size_t(chunk1_length); } - bin_data_ = bytes + json_size + - 8; // 4 bytes (bin_buffer_length) + 4 bytes(bin_buffer_format) - - bin_size_ = size_t(chunk1_length); - // Extract JSON string. std::string jsonString(reinterpret_cast(&bytes[20]), chunk0_length);