From 82d3f47432246910d2ef636370264c4935d15a42 Mon Sep 17 00:00:00 2001 From: Marco Feuerstein Date: Thu, 29 Jun 2023 15:10:28 +0200 Subject: [PATCH] Unify way to check readable blender files. --- code/AssetLib/Blender/BlenderLoader.cpp | 120 +++++++++++++----------- code/AssetLib/Blender/BlenderLoader.h | 13 +++ 2 files changed, 76 insertions(+), 57 deletions(-) diff --git a/code/AssetLib/Blender/BlenderLoader.cpp b/code/AssetLib/Blender/BlenderLoader.cpp index 269c90b96..5c6e7bc5b 100644 --- a/code/AssetLib/Blender/BlenderLoader.cpp +++ b/code/AssetLib/Blender/BlenderLoader.cpp @@ -115,15 +115,12 @@ BlenderImporter::~BlenderImporter() { delete modifier_cache; } -static const char * const Tokens[] = { "BLENDER" }; +static const char Token[] = "BLENDER"; // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { - // note: this won't catch compressed files - static const char *tokens[] = { " uncompressed; -#endif - FileDatabase file; - std::shared_ptr stream(pIOHandler->Open(pFile, "rb")); - if (!stream) { - ThrowException("Could not open file for reading"); + StreamOrError streamOrError = ParseMagicToken(pFile, pIOHandler); + if (!streamOrError.error.empty()) { + ThrowException(streamOrError.error); } + std::shared_ptr stream = std::move(streamOrError.stream); - char magic[8] = { 0 }; - stream->Read(magic, 7, 1); - if (strcmp(magic, Tokens[0])) { - // Check for presence of the gzip header. If yes, assume it is a - // compressed blend file and try uncompressing it, else fail. This is to - // avoid uncompressing random files which our loader might end up with. -#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND - ThrowException("BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?"); -#else - if (magic[0] != 0x1f || static_cast(magic[1]) != 0x8b) { - ThrowException("BLENDER magic bytes are missing, couldn't find GZIP header either"); - } + char version[4] = { 0 }; + file.i64bit = (stream->Read(version, 1, 1), version[0] == '-'); + file.little = (stream->Read(version, 1, 1), version[0] == 'v'); - LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file"); - if (magic[2] != 8) { - ThrowException("Unsupported GZIP compression method"); - } + stream->Read(version, 3, 1); + version[3] = '\0'; - // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer - stream->Seek(0L, aiOrigin_SET); - std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(stream)); - - size_t total = 0; - Compression compression; - if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) { - total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), uncompressed); - compression.close(); - } - - // replace the input stream with a memory stream - stream = std::make_shared(reinterpret_cast(uncompressed.data()), total); - - // .. and retry - stream->Read(magic, 7, 1); - if (strcmp(magic, "BLENDER")) { - ThrowException("Found no BLENDER magic word in decompressed GZIP file"); - } -#endif - } - - file.i64bit = (stream->Read(magic, 1, 1), magic[0] == '-'); - file.little = (stream->Read(magic, 1, 1), magic[0] == 'v'); - - stream->Read(magic, 3, 1); - magic[3] = '\0'; - - LogInfo("Blender version is ", magic[0], ".", magic + 1, + LogInfo("Blender version is ", version[0], ".", version + 1, " (64bit: ", file.i64bit ? "true" : "false", ", little endian: ", file.little ? "true" : "false", ")"); @@ -1338,4 +1293,55 @@ aiNode *BlenderImporter::ConvertNode(const Scene &in, const Object *obj, Convers return node.release(); } +BlenderImporter::StreamOrError BlenderImporter::ParseMagicToken(const std::string &pFile, IOSystem *pIOHandler) const { + std::shared_ptr stream(pIOHandler->Open(pFile, "rb")); + if (stream == nullptr) { + return {{}, {}, "Could not open file for reading"}; + } + + char magic[8] = { 0 }; + stream->Read(magic, 7, 1); + if (strcmp(magic, Token) == 0) { + return {stream, {}, {}}; + } + + // Check for presence of the gzip header. If yes, assume it is a + // compressed blend file and try uncompressing it, else fail. This is to + // avoid uncompressing random files which our loader might end up with. +#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND + return {{}, {}, "BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?"}; +#else + if (magic[0] != 0x1f || static_cast(magic[1]) != 0x8b) { + return {{}, {}, "BLENDER magic bytes are missing, couldn't find GZIP header either"}; + } + + LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file"); + if (magic[2] != 8) { + return {{}, {}, "Unsupported GZIP compression method"}; + } + + // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer + stream->Seek(0L, aiOrigin_SET); + std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(stream)); + + size_t total = 0; + Compression compression; + auto uncompressed = std::make_shared>(); + if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) { + total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), *uncompressed); + compression.close(); + } + + // replace the input stream with a memory stream + stream = std::make_shared(reinterpret_cast(uncompressed->data()), total); + + // .. and retry + stream->Read(magic, 7, 1); + if (strcmp(magic, Token) == 0) { + return {stream, uncompressed, {}}; + } + return {{}, {}, "Found no BLENDER magic word in decompressed GZIP file"}; +#endif +} + #endif // ASSIMP_BUILD_NO_BLEND_IMPORTER diff --git a/code/AssetLib/Blender/BlenderLoader.h b/code/AssetLib/Blender/BlenderLoader.h index b29ee5941..2bdc24ae2 100644 --- a/code/AssetLib/Blender/BlenderLoader.h +++ b/code/AssetLib/Blender/BlenderLoader.h @@ -180,6 +180,19 @@ private: const Blender::MTex *tex, Blender::ConversionData &conv_data); + // TODO: Move to a std::variant, once c++17 is supported. + struct StreamOrError { + std::shared_ptr stream; + std::shared_ptr> input; + std::string error; + }; + + // Returns either a stream (and optional input data for the stream) or + // an error if it can't parse the magic token. + StreamOrError ParseMagicToken( + const std::string &pFile, + IOSystem *pIOHandler) const; + private: // static stuff, mostly logging and error reporting. // -------------------- static void CheckActualType(const Blender::ElemBase *dt,