closes https://github.com/assimp/assimp/issues/1729: check for bit flip when unsigned int overflow happens in x-file parsing.

pull/1767/head
Kim Kulling 2018-02-04 22:15:18 +01:00
parent 48d41b4bf9
commit 3685791e0d
2 changed files with 151 additions and 149 deletions

View File

@ -87,59 +87,60 @@ static void dummy_free (void* /*opaque*/, void* address) {
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// Constructor. Creates a data structure out of the XFile given in the memory block. // Constructor. Creates a data structure out of the XFile given in the memory block.
XFileParser::XFileParser( const std::vector<char>& pBuffer) XFileParser::XFileParser( const std::vector<char>& pBuffer)
{ : mMajorVersion( 0 )
mMajorVersion = mMinorVersion = 0; , mMinorVersion( 0 )
mIsBinaryFormat = false; , mIsBinaryFormat( false )
mBinaryNumCount = 0; , mBinaryNumCount( 0 )
P = End = NULL; , mP( nullptr )
mLineNumber = 0; , mEnd( nullptr )
mScene = NULL; , mLineNumber( 0 )
, mScene( nullptr ) {
// vector to store uncompressed file for INFLATE'd X files // vector to store uncompressed file for INFLATE'd X files
std::vector<char> uncompressed; std::vector<char> uncompressed;
// set up memory pointers // set up memory pointers
P = &pBuffer.front(); mP = &pBuffer.front();
End = P + pBuffer.size() - 1; mEnd = mP + pBuffer.size() - 1;
// check header // check header
if( strncmp( P, "xof ", 4) != 0) if ( 0 != strncmp( mP, "xof ", 4 ) ) {
throw DeadlyImportError( "Header mismatch, file is not an XFile."); throw DeadlyImportError( "Header mismatch, file is not an XFile." );
}
// read version. It comes in a four byte format such as "0302" // read version. It comes in a four byte format such as "0302"
mMajorVersion = (unsigned int)(P[4] - 48) * 10 + (unsigned int)(P[5] - 48); mMajorVersion = (unsigned int)(mP[4] - 48) * 10 + (unsigned int)(mP[5] - 48);
mMinorVersion = (unsigned int)(P[6] - 48) * 10 + (unsigned int)(P[7] - 48); mMinorVersion = (unsigned int)(mP[6] - 48) * 10 + (unsigned int)(mP[7] - 48);
bool compressed = false; bool compressed = false;
// txt - pure ASCII text format // txt - pure ASCII text format
if( strncmp( P + 8, "txt ", 4) == 0) if( strncmp( mP + 8, "txt ", 4) == 0)
mIsBinaryFormat = false; mIsBinaryFormat = false;
// bin - Binary format // bin - Binary format
else if( strncmp( P + 8, "bin ", 4) == 0) else if( strncmp( mP + 8, "bin ", 4) == 0)
mIsBinaryFormat = true; mIsBinaryFormat = true;
// tzip - Inflate compressed text format // tzip - Inflate compressed text format
else if( strncmp( P + 8, "tzip", 4) == 0) else if( strncmp( mP + 8, "tzip", 4) == 0)
{ {
mIsBinaryFormat = false; mIsBinaryFormat = false;
compressed = true; compressed = true;
} }
// bzip - Inflate compressed binary format // bzip - Inflate compressed binary format
else if( strncmp( P + 8, "bzip", 4) == 0) else if( strncmp( mP + 8, "bzip", 4) == 0)
{ {
mIsBinaryFormat = true; mIsBinaryFormat = true;
compressed = true; compressed = true;
} }
else ThrowException( format() << "Unsupported xfile format '" << else ThrowException( format() << "Unsupported xfile format '" <<
P[8] << P[9] << P[10] << P[11] << "'"); mP[8] << mP[9] << mP[10] << mP[11] << "'");
// float size // float size
mBinaryFloatSize = (unsigned int)(P[12] - 48) * 1000 mBinaryFloatSize = (unsigned int)(mP[12] - 48) * 1000
+ (unsigned int)(P[13] - 48) * 100 + (unsigned int)(mP[13] - 48) * 100
+ (unsigned int)(P[14] - 48) * 10 + (unsigned int)(mP[14] - 48) * 10
+ (unsigned int)(P[15] - 48); + (unsigned int)(mP[15] - 48);
if( mBinaryFloatSize != 32 && mBinaryFloatSize != 64) if( mBinaryFloatSize != 32 && mBinaryFloatSize != 64)
ThrowException( format() << "Unknown float size " << mBinaryFloatSize << " specified in xfile header." ); ThrowException( format() << "Unknown float size " << mBinaryFloatSize << " specified in xfile header." );
@ -147,7 +148,7 @@ XFileParser::XFileParser( const std::vector<char>& pBuffer)
// The x format specifies size in bits, but we work in bytes // The x format specifies size in bits, but we work in bytes
mBinaryFloatSize /= 8; mBinaryFloatSize /= 8;
P += 16; mP += 16;
// If this is a compressed X file, apply the inflate algorithm to it // If this is a compressed X file, apply the inflate algorithm to it
if (compressed) if (compressed)
@ -186,13 +187,13 @@ XFileParser::XFileParser( const std::vector<char>& pBuffer)
::inflateInit2(&stream, -MAX_WBITS); ::inflateInit2(&stream, -MAX_WBITS);
// skip unknown data (checksum, flags?) // skip unknown data (checksum, flags?)
P += 6; mP += 6;
// First find out how much storage we'll need. Count sections. // First find out how much storage we'll need. Count sections.
const char* P1 = P; const char* P1 = mP;
unsigned int est_out = 0; unsigned int est_out = 0;
while (P1 + 3 < End) while (P1 + 3 < mEnd)
{ {
// read next offset // read next offset
uint16_t ofs = *((uint16_t*)P1); uint16_t ofs = *((uint16_t*)P1);
@ -216,18 +217,18 @@ XFileParser::XFileParser( const std::vector<char>& pBuffer)
// Allocate storage and terminating zero and do the actual uncompressing // Allocate storage and terminating zero and do the actual uncompressing
uncompressed.resize(est_out + 1); uncompressed.resize(est_out + 1);
char* out = &uncompressed.front(); char* out = &uncompressed.front();
while (P + 3 < End) while (mP + 3 < mEnd)
{ {
uint16_t ofs = *((uint16_t*)P); uint16_t ofs = *((uint16_t*)mP);
AI_SWAP2(ofs); AI_SWAP2(ofs);
P += 4; mP += 4;
if (P + ofs > End + 2) { if (mP + ofs > mEnd + 2) {
throw DeadlyImportError("X: Unexpected EOF in compressed chunk"); throw DeadlyImportError("X: Unexpected EOF in compressed chunk");
} }
// push data to the stream // push data to the stream
stream.next_in = (Bytef*)P; stream.next_in = (Bytef*)mP;
stream.avail_in = ofs; stream.avail_in = ofs;
stream.next_out = (Bytef*)out; stream.next_out = (Bytef*)out;
stream.avail_out = MSZIP_BLOCK; stream.avail_out = MSZIP_BLOCK;
@ -242,15 +243,15 @@ XFileParser::XFileParser( const std::vector<char>& pBuffer)
// and advance to the next offset // and advance to the next offset
out += MSZIP_BLOCK - stream.avail_out; out += MSZIP_BLOCK - stream.avail_out;
P += ofs; mP += ofs;
} }
// terminate zlib // terminate zlib
::inflateEnd(&stream); ::inflateEnd(&stream);
// ok, update pointers to point to the uncompressed file data // ok, update pointers to point to the uncompressed file data
P = &uncompressed[0]; mP = &uncompressed[0];
End = out; mEnd = out;
// FIXME: we don't need the compressed data anymore, could release // FIXME: we don't need the compressed data anymore, could release
// it already for better memory usage. Consider breaking const-co. // it already for better memory usage. Consider breaking const-co.
@ -647,8 +648,8 @@ void XFileParser::ParseDataObjectMeshVertexColors( Mesh* pMesh)
if( !mIsBinaryFormat) if( !mIsBinaryFormat)
{ {
FindNextNoneWhiteSpace(); FindNextNoneWhiteSpace();
if( *P == ';' || *P == ',') if( *mP == ';' || *mP == ',')
P++; mP++;
} }
} }
@ -678,8 +679,8 @@ void XFileParser::ParseDataObjectMeshMaterialList( Mesh* pMesh)
// commented out version check, as version 03.03 exported from blender also has 2 semicolons // commented out version check, as version 03.03 exported from blender also has 2 semicolons
if( !mIsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2) if( !mIsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2)
{ {
if(P < End && *P == ';') if(mP < mEnd && *mP == ';')
++P; ++mP;
} }
// if there was only a single material index, replicate it on all faces // if there was only a single material index, replicate it on all faces
@ -1029,12 +1030,12 @@ void XFileParser::TestForSeparator()
return; return;
FindNextNoneWhiteSpace(); FindNextNoneWhiteSpace();
if( P >= End) if( mP >= mEnd)
return; return;
// test and skip // test and skip
if( *P == ';' || *P == ',') if( *mP == ';' || *mP == ',')
P++; mP++;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -1062,46 +1063,54 @@ std::string XFileParser::GetNextToken()
// in binary mode it will only return NAME and STRING token // in binary mode it will only return NAME and STRING token
// and (correctly) skip over other tokens. // and (correctly) skip over other tokens.
if( End - P < 2) return s; if( mEnd - mP < 2) return s;
unsigned int tok = ReadBinWord(); unsigned int tok = ReadBinWord();
unsigned int len; unsigned int len;
// standalone tokens // standalone tokens
switch( tok) switch( tok)
{ {
case 1: case 1: {
// name token // name token
if( End - P < 4) return s; if ( mEnd - mP < 4 ) return s;
len = ReadBinDWord(); len = ReadBinDWord();
if( End - P < int(len)) return s; const int bounds( mEnd - mP );
s = std::string(P, len); const int iLen( len );
P += len; if ( iLen < 0 ) {
return s;
}
if ( bounds < iLen ) {
return s;
}
s = std::string( mP, len );
mP += len;
}
return s; return s;
case 2: case 2:
// string token // string token
if( End - P < 4) return s; if( mEnd - mP < 4) return s;
len = ReadBinDWord(); len = ReadBinDWord();
if( End - P < int(len)) return s; if( mEnd - mP < int(len)) return s;
s = std::string(P, len); s = std::string(mP, len);
P += (len + 2); mP += (len + 2);
return s; return s;
case 3: case 3:
// integer token // integer token
P += 4; mP += 4;
return "<integer>"; return "<integer>";
case 5: case 5:
// GUID token // GUID token
P += 16; mP += 16;
return "<guid>"; return "<guid>";
case 6: case 6:
if( End - P < 4) return s; if( mEnd - mP < 4) return s;
len = ReadBinDWord(); len = ReadBinDWord();
P += (len * 4); mP += (len * 4);
return "<int_list>"; return "<int_list>";
case 7: case 7:
if( End - P < 4) return s; if( mEnd - mP < 4) return s;
len = ReadBinDWord(); len = ReadBinDWord();
P += (len * mBinaryFloatSize); mP += (len * mBinaryFloatSize);
return "<flt_list>"; return "<flt_list>";
case 0x0a: case 0x0a:
return "{"; return "{";
@ -1159,19 +1168,19 @@ std::string XFileParser::GetNextToken()
else else
{ {
FindNextNoneWhiteSpace(); FindNextNoneWhiteSpace();
if( P >= End) if( mP >= mEnd)
return s; return s;
while( (P < End) && !isspace( (unsigned char) *P)) while( (mP < mEnd) && !isspace( (unsigned char) *mP))
{ {
// either keep token delimiters when already holding a token, or return if first valid char // either keep token delimiters when already holding a token, or return if first valid char
if( *P == ';' || *P == '}' || *P == '{' || *P == ',') if( *mP == ';' || *mP == '}' || *mP == '{' || *mP == ',')
{ {
if( !s.size()) if( !s.size())
s.append( P++, 1); s.append( mP++, 1);
break; // stop for delimiter break; // stop for delimiter
} }
s.append( P++, 1); s.append( mP++, 1);
} }
} }
return s; return s;
@ -1186,18 +1195,18 @@ void XFileParser::FindNextNoneWhiteSpace()
bool running = true; bool running = true;
while( running ) while( running )
{ {
while( P < End && isspace( (unsigned char) *P)) while( mP < mEnd && isspace( (unsigned char) *mP))
{ {
if( *P == '\n') if( *mP == '\n')
mLineNumber++; mLineNumber++;
++P; ++mP;
} }
if( P >= End) if( mP >= mEnd)
return; return;
// check if this is a comment // check if this is a comment
if( (P[0] == '/' && P[1] == '/') || P[0] == '#') if( (mP[0] == '/' && mP[1] == '/') || mP[0] == '#')
ReadUntilEndOfLine(); ReadUntilEndOfLine();
else else
break; break;
@ -1214,22 +1223,22 @@ void XFileParser::GetNextTokenAsString( std::string& poString)
} }
FindNextNoneWhiteSpace(); FindNextNoneWhiteSpace();
if( P >= End) if( mP >= mEnd)
ThrowException( "Unexpected end of file while parsing string"); ThrowException( "Unexpected end of file while parsing string");
if( *P != '"') if( *mP != '"')
ThrowException( "Expected quotation mark."); ThrowException( "Expected quotation mark.");
++P; ++mP;
while( P < End && *P != '"') while( mP < mEnd && *mP != '"')
poString.append( P++, 1); poString.append( mP++, 1);
if( P >= End-1) if( mP >= mEnd-1)
ThrowException( "Unexpected end of file while parsing string"); ThrowException( "Unexpected end of file while parsing string");
if( P[1] != ';' || P[0] != '"') if( mP[1] != ';' || mP[0] != '"')
ThrowException( "Expected quotation mark and semicolon at the end of a string."); ThrowException( "Expected quotation mark and semicolon at the end of a string.");
P+=2; mP+=2;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -1238,35 +1247,35 @@ void XFileParser::ReadUntilEndOfLine()
if( mIsBinaryFormat) if( mIsBinaryFormat)
return; return;
while( P < End) while( mP < mEnd)
{ {
if( *P == '\n' || *P == '\r') if( *mP == '\n' || *mP == '\r')
{ {
++P; mLineNumber++; ++mP; mLineNumber++;
return; return;
} }
++P; ++mP;
} }
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
unsigned short XFileParser::ReadBinWord() unsigned short XFileParser::ReadBinWord()
{ {
ai_assert(End - P >= 2); ai_assert(mEnd - mP >= 2);
const unsigned char* q = (const unsigned char*) P; const unsigned char* q = (const unsigned char*) mP;
unsigned short tmp = q[0] | (q[1] << 8); unsigned short tmp = q[0] | (q[1] << 8);
P += 2; mP += 2;
return tmp; return tmp;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
unsigned int XFileParser::ReadBinDWord() unsigned int XFileParser::ReadBinDWord() {
{ ai_assert(mEnd - mP >= 4);
ai_assert(End - P >= 4);
const unsigned char* q = (const unsigned char*) P; const unsigned char* q = (const unsigned char*) mP;
unsigned int tmp = q[0] | (q[1] << 8) | (q[2] << 16) | (q[3] << 24); unsigned int tmp = q[0] | (q[1] << 8) | (q[2] << 16) | (q[3] << 24);
P += 4; mP += 4;
return tmp; return tmp;
} }
@ -1275,20 +1284,20 @@ unsigned int XFileParser::ReadInt()
{ {
if( mIsBinaryFormat) if( mIsBinaryFormat)
{ {
if( mBinaryNumCount == 0 && End - P >= 2) if( mBinaryNumCount == 0 && mEnd - mP >= 2)
{ {
unsigned short tmp = ReadBinWord(); // 0x06 or 0x03 unsigned short tmp = ReadBinWord(); // 0x06 or 0x03
if( tmp == 0x06 && End - P >= 4) // array of ints follows if( tmp == 0x06 && mEnd - mP >= 4) // array of ints follows
mBinaryNumCount = ReadBinDWord(); mBinaryNumCount = ReadBinDWord();
else // single int follows else // single int follows
mBinaryNumCount = 1; mBinaryNumCount = 1;
} }
--mBinaryNumCount; --mBinaryNumCount;
if ( End - P >= 4) { if ( mEnd - mP >= 4) {
return ReadBinDWord(); return ReadBinDWord();
} else { } else {
P = End; mP = mEnd;
return 0; return 0;
} }
} else } else
@ -1299,24 +1308,24 @@ unsigned int XFileParser::ReadInt()
// check preceding minus sign // check preceding minus sign
bool isNegative = false; bool isNegative = false;
if( *P == '-') if( *mP == '-')
{ {
isNegative = true; isNegative = true;
P++; mP++;
} }
// at least one digit expected // at least one digit expected
if( !isdigit( *P)) if( !isdigit( *mP))
ThrowException( "Number expected."); ThrowException( "Number expected.");
// read digits // read digits
unsigned int number = 0; unsigned int number = 0;
while( P < End) while( mP < mEnd)
{ {
if( !isdigit( *P)) if( !isdigit( *mP))
break; break;
number = number * 10 + (*P - 48); number = number * 10 + (*mP - 48);
P++; mP++;
} }
CheckForSeparator(); CheckForSeparator();
@ -1329,10 +1338,10 @@ ai_real XFileParser::ReadFloat()
{ {
if( mIsBinaryFormat) if( mIsBinaryFormat)
{ {
if( mBinaryNumCount == 0 && End - P >= 2) if( mBinaryNumCount == 0 && mEnd - mP >= 2)
{ {
unsigned short tmp = ReadBinWord(); // 0x07 or 0x42 unsigned short tmp = ReadBinWord(); // 0x07 or 0x42
if( tmp == 0x07 && End - P >= 4) // array of floats following if( tmp == 0x07 && mEnd - mP >= 4) // array of floats following
mBinaryNumCount = ReadBinDWord(); mBinaryNumCount = ReadBinDWord();
else // single float following else // single float following
mBinaryNumCount = 1; mBinaryNumCount = 1;
@ -1341,22 +1350,22 @@ ai_real XFileParser::ReadFloat()
--mBinaryNumCount; --mBinaryNumCount;
if( mBinaryFloatSize == 8) if( mBinaryFloatSize == 8)
{ {
if( End - P >= 8) { if( mEnd - mP >= 8) {
ai_real result = (ai_real) (*(double*) P); ai_real result = (ai_real) (*(double*) mP);
P += 8; mP += 8;
return result; return result;
} else { } else {
P = End; mP = mEnd;
return 0; return 0;
} }
} else } else
{ {
if( End - P >= 4) { if( mEnd - mP >= 4) {
ai_real result = *(ai_real*) P; ai_real result = *(ai_real*) mP;
P += 4; mP += 4;
return result; return result;
} else { } else {
P = End; mP = mEnd;
return 0; return 0;
} }
} }
@ -1367,21 +1376,21 @@ ai_real XFileParser::ReadFloat()
// check for various special strings to allow reading files from faulty exporters // check for various special strings to allow reading files from faulty exporters
// I mean you, Blender! // I mean you, Blender!
// Reading is safe because of the terminating zero // Reading is safe because of the terminating zero
if( strncmp( P, "-1.#IND00", 9) == 0 || strncmp( P, "1.#IND00", 8) == 0) if( strncmp( mP, "-1.#IND00", 9) == 0 || strncmp( mP, "1.#IND00", 8) == 0)
{ {
P += 9; mP += 9;
CheckForSeparator(); CheckForSeparator();
return 0.0; return 0.0;
} else } else
if( strncmp( P, "1.#QNAN0", 8) == 0) if( strncmp( mP, "1.#QNAN0", 8) == 0)
{ {
P += 8; mP += 8;
CheckForSeparator(); CheckForSeparator();
return 0.0; return 0.0;
} }
ai_real result = 0.0; ai_real result = 0.0;
P = fast_atoreal_move<ai_real>( P, result); mP = fast_atoreal_move<ai_real>( mP, result);
CheckForSeparator(); CheckForSeparator();

View File

@ -49,10 +49,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <assimp/types.h> #include <assimp/types.h>
namespace Assimp namespace Assimp {
{ namespace XFile {
namespace XFile
{
struct Node; struct Node;
struct Mesh; struct Mesh;
struct Scene; struct Scene;
@ -61,21 +59,20 @@ namespace Assimp
struct AnimBone; struct AnimBone;
} }
/** The XFileParser reads a XFile either in text or binary form and builds a temporary /**
* data structure out of it. * @brief The XFileParser reads a XFile either in text or binary form and builds a temporary
*/ * data structure out of it.
class XFileParser */
{ class XFileParser {
public: public:
/** Constructor. Creates a data structure out of the XFile given in the memory block. /// Constructor. Creates a data structure out of the XFile given in the memory block.
* @param pBuffer Null-terminated memory buffer containing the XFile /// @param pBuffer Null-terminated memory buffer containing the XFile
*/
explicit XFileParser( const std::vector<char>& pBuffer); explicit XFileParser( const std::vector<char>& pBuffer);
/** Destructor. Destroys all imported data along with it */ /// Destructor. Destroys all imported data along with it
~XFileParser(); ~XFileParser();
/** Returns the temporary representation of the imported data */ /// Returns the temporary representation of the imported data.
XFile::Scene* GetImportedData() const { return mScene; } XFile::Scene* GetImportedData() const { return mScene; }
protected: protected:
@ -101,10 +98,10 @@ protected:
//! places pointer to next begin of a token, and ignores comments //! places pointer to next begin of a token, and ignores comments
void FindNextNoneWhiteSpace(); void FindNextNoneWhiteSpace();
//! returns next parseable token. Returns empty string if no token there //! returns next valid token. Returns empty string if no token there
std::string GetNextToken(); std::string GetNextToken();
//! reads header of dataobject including the opening brace. //! reads header of data object including the opening brace.
//! returns false if error happened, and writes name of object //! returns false if error happened, and writes name of object
//! if there is one //! if there is one
void readHeadOfDataObject( std::string* poName = NULL); void readHeadOfDataObject( std::string* poName = NULL);
@ -118,8 +115,8 @@ protected:
//! checks for a separator char, either a ',' or a ';' //! checks for a separator char, either a ',' or a ';'
void CheckForSeparator(); void CheckForSeparator();
/// tests and possibly consumes a separator char, but does nothing if there was no separator /// tests and possibly consumes a separator char, but does nothing if there was no separator
void TestForSeparator(); void TestForSeparator();
//! reads a x file style string //! reads a x file style string
void GetNextTokenAsString( std::string& poString); void GetNextTokenAsString( std::string& poString);
@ -138,27 +135,23 @@ protected:
/** Throws an exception with a line number and the given text. */ /** Throws an exception with a line number and the given text. */
AI_WONT_RETURN void ThrowException( const std::string& pText) AI_WONT_RETURN_SUFFIX; AI_WONT_RETURN void ThrowException( const std::string& pText) AI_WONT_RETURN_SUFFIX;
/** Filters the imported hierarchy for some degenerated cases that some exporters produce. /**
* @param pData The sub-hierarchy to filter * @brief Filters the imported hierarchy for some degenerated cases that some exporters produce.
*/ * @param pData The sub-hierarchy to filter
*/
void FilterHierarchy( XFile::Node* pNode); void FilterHierarchy( XFile::Node* pNode);
protected: protected:
unsigned int mMajorVersion, mMinorVersion; ///< version numbers unsigned int mMajorVersion, mMinorVersion; ///< version numbers
bool mIsBinaryFormat; ///< true if the file is in binary, false if it's in text form bool mIsBinaryFormat; ///< true if the file is in binary, false if it's in text form
unsigned int mBinaryFloatSize; ///< float size in bytes, either 4 or 8 unsigned int mBinaryFloatSize; ///< float size in bytes, either 4 or 8
// counter for number arrays in binary format unsigned int mBinaryNumCount; /// < counter for number arrays in binary format
unsigned int mBinaryNumCount; const char* mP;
const char* mEnd;
const char* P; unsigned int mLineNumber; ///< Line number when reading in text format
const char* End; XFile::Scene* mScene; ///< Imported data
/// Line number when reading in text format
unsigned int mLineNumber;
/// Imported data
XFile::Scene* mScene;
}; };
} } //! ns Assimp
#endif // AI_XFILEPARSER_H_INC #endif // AI_XFILEPARSER_H_INC