Skip to content

Commit

Permalink
Merge pull request #5153 from feuerste/blender_can_read
Browse files Browse the repository at this point in the history
Unify way to check readable blender files.
  • Loading branch information
kimkulling committed Jul 1, 2023
2 parents 42386b8 + 8a65dcc commit e4cac7d
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 57 deletions.
120 changes: 63 additions & 57 deletions code/AssetLib/Blender/BlenderLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = { "<BLENDER", "blender" };

return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
return ParseMagicToken(pFile, pIOHandler).error.empty();
}

// ------------------------------------------------------------------------------------------------
Expand All @@ -142,63 +139,21 @@ void BlenderImporter::SetupProperties(const Importer * /*pImp*/) {
// Imports the given file into the given scene structure.
void BlenderImporter::InternReadFile(const std::string &pFile,
aiScene *pScene, IOSystem *pIOHandler) {
#ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND
std::vector<char> uncompressed;
#endif

FileDatabase file;
std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
if (!stream) {
ThrowException("Could not open file for reading");
}

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<uint8_t>(magic[1]) != 0x8b) {
ThrowException("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) {
ThrowException("Unsupported GZIP compression method");
}

// http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
stream->Seek(0L, aiOrigin_SET);
std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(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<MemoryIOStream>(reinterpret_cast<uint8_t *>(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
StreamOrError streamOrError = ParseMagicToken(pFile, pIOHandler);
if (!streamOrError.error.empty()) {
ThrowException(streamOrError.error);
}
std::shared_ptr<IOStream> stream = std::move(streamOrError.stream);

file.i64bit = (stream->Read(magic, 1, 1), magic[0] == '-');
file.little = (stream->Read(magic, 1, 1), magic[0] == 'v');
char version[4] = { 0 };
file.i64bit = (stream->Read(version, 1, 1), version[0] == '-');
file.little = (stream->Read(version, 1, 1), version[0] == 'v');

stream->Read(magic, 3, 1);
magic[3] = '\0';
stream->Read(version, 3, 1);
version[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", ")");

Expand Down Expand Up @@ -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<IOStream> 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<uint8_t>(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<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream));

size_t total = 0;
Compression compression;
auto uncompressed = std::make_shared<std::vector<char>>();
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<MemoryIOStream>(reinterpret_cast<uint8_t *>(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
13 changes: 13 additions & 0 deletions code/AssetLib/Blender/BlenderLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,19 @@ class BlenderImporter : public BaseImporter, public LogFunctions<BlenderImporter
const Blender::MTex *tex,
Blender::ConversionData &conv_data);

// TODO: Move to a std::variant, once c++17 is supported.
struct StreamOrError {
std::shared_ptr<IOStream> stream;
std::shared_ptr<std::vector<char>> 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,
Expand Down

0 comments on commit e4cac7d

Please sign in to comment.