瀏覽代碼

Merge branch 'master' into adsk-contrib-fix-std-stream-overflow

Kim Kulling 8 年之前
父節點
當前提交
44ad80201c

+ 4 - 0
.travis.sh

@@ -26,6 +26,10 @@ function generate()
         OPTIONS="$OPTIONS -DASSIMP_ASAN=OFF"
         OPTIONS="$OPTIONS -DASSIMP_ASAN=OFF"
     fi
     fi
 
 
+    if [ "$UBSAN" = "ON" ] ; then
+        OPTIONS="$OPTIONS -DASSIMP_UBSAN=ON"
+    fi
+
     cmake -G "Unix Makefiles" $OPTIONS
     cmake -G "Unix Makefiles" $OPTIONS
 }
 }
 
 

+ 3 - 0
.travis.yml

@@ -46,6 +46,9 @@ matrix:
     - os: linux
     - os: linux
       compiler: clang
       compiler: clang
       env: ASAN=ON
       env: ASAN=ON
+    - os: linux
+      compiler: clang
+      env: UBSAN=ON
     - os: linux
     - os: linux
       compiler: clang
       compiler: clang
       env: SHARED_BUILD=ON
       env: SHARED_BUILD=ON

+ 10 - 0
CMakeLists.txt

@@ -86,6 +86,10 @@ OPTION ( ASSIMP_ASAN
   "Enable AddressSanitizer."
   "Enable AddressSanitizer."
   OFF
   OFF
 )
 )
+OPTION ( ASSIMP_UBSAN
+  "Enable Undefined Behavior sanitizer."
+  OFF
+)
 OPTION ( SYSTEM_IRRXML
 OPTION ( SYSTEM_IRRXML
   "Use system installed Irrlicht/IrrXML library."
   "Use system installed Irrlicht/IrrXML library."
   OFF
   OFF
@@ -234,6 +238,12 @@ if (ASSIMP_ASAN)
     SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
     SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
 endif()
 endif()
 
 
+if (ASSIMP_UBSAN)
+    MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
+endif()
+
 INCLUDE (FindPkgMacros)
 INCLUDE (FindPkgMacros)
 INCLUDE (PrecompiledHeader)
 INCLUDE (PrecompiledHeader)
 
 

+ 4 - 2
code/B3DImporter.cpp

@@ -171,7 +171,8 @@ int B3DImporter::ReadByte(){
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 int B3DImporter::ReadInt(){
 int B3DImporter::ReadInt(){
     if( _pos+4<=_buf.size() ){
     if( _pos+4<=_buf.size() ){
-        int n=*(int*)&_buf[_pos];
+        int n;
+        memcpy(&n, &_buf[_pos], 4);
         _pos+=4;
         _pos+=4;
         return n;
         return n;
     }
     }
@@ -182,7 +183,8 @@ int B3DImporter::ReadInt(){
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 float B3DImporter::ReadFloat(){
 float B3DImporter::ReadFloat(){
     if( _pos+4<=_buf.size() ){
     if( _pos+4<=_buf.size() ){
-        float n=*(float*)&_buf[_pos];
+        float n;
+        memcpy(&n, &_buf[_pos], 4);
         _pos+=4;
         _pos+=4;
         return n;
         return n;
     }
     }

+ 4 - 1
code/BlenderDNA.inl

@@ -589,7 +589,10 @@ template <> inline void Structure :: Convert<short>  (short& dest,const FileData
 {
 {
     // automatic rescaling from short to float and vice versa (seems to be used by normals)
     // automatic rescaling from short to float and vice versa (seems to be used by normals)
     if (name == "float") {
     if (name == "float") {
-        dest = static_cast<short>(db.reader->GetF4() * 32767.f);
+        float f = db.reader->GetF4();
+        if ( f > 1.0f )
+            f = 1.0f;
+        dest = static_cast<short>( f * 32767.f);
         //db.reader->IncPtr(-4);
         //db.reader->IncPtr(-4);
         return;
         return;
     }
     }

+ 2 - 1
code/FBXBinaryTokenizer.cpp

@@ -151,7 +151,8 @@ uint32_t ReadWord(const char* input, const char*& cursor, const char* end)
         TokenizeError("cannot ReadWord, out of bounds",input, cursor);
         TokenizeError("cannot ReadWord, out of bounds",input, cursor);
     }
     }
 
 
-    uint32_t word = *reinterpret_cast<const uint32_t*>(cursor);
+    uint32_t word;
+    memcpy(&word, cursor, 4);
     AI_SWAP4(word);
     AI_SWAP4(word);
 
 
     cursor += k_to_read;
     cursor += k_to_read;

+ 1 - 1
code/IFCBoolean.cpp

@@ -272,7 +272,6 @@ bool IntersectsBoundaryProfile(const IfcVector3& e0, const IfcVector3& e1, const
         const IfcVector3& b0 = boundary[i];
         const IfcVector3& b0 = boundary[i];
         const IfcVector3& b1 = boundary[(i + 1) % bcount];
         const IfcVector3& b1 = boundary[(i + 1) % bcount];
         IfcVector3 b = b1 - b0;
         IfcVector3 b = b1 - b0;
-        IfcFloat b_sqlen_inv = 1.0 / b.SquareLength();
 
 
         // segment-segment intersection
         // segment-segment intersection
         // solve b0 + b*s = e0 + e*t for (s,t)
         // solve b0 + b*s = e0 + e*t for (s,t)
@@ -281,6 +280,7 @@ bool IntersectsBoundaryProfile(const IfcVector3& e0, const IfcVector3& e1, const
             // no solutions (parallel lines)
             // no solutions (parallel lines)
             continue;
             continue;
         }
         }
+        IfcFloat b_sqlen_inv = 1.0 / b.SquareLength();
 
 
         const IfcFloat x = b0.x - e0.x;
         const IfcFloat x = b0.x - e0.x;
         const IfcFloat y = b0.y - e0.y;
         const IfcFloat y = b0.y - e0.y;

+ 247 - 14
port/jassimp/jassimp-native/src/jassimp.cpp

@@ -1,7 +1,9 @@
 #include "jassimp.h"
 #include "jassimp.h"
 
 
-#include <assimp/cimport.h>
+#include <assimp/Importer.hpp>
 #include <assimp/scene.h>
 #include <assimp/scene.h>
+#include <assimp/IOStream.hpp>
+#include <assimp/IOSystem.hpp>
 
 
 
 
 #ifdef JNI_LOG
 #ifdef JNI_LOG
@@ -12,9 +14,11 @@
 #define lprintf(...) printf (__VA_ARGS__)
 #define lprintf(...) printf (__VA_ARGS__)
 #endif /* ANDROID */
 #endif /* ANDROID */
 #else
 #else
-#define lprintf
+#define lprintf 
 #endif
 #endif
 
 
+static std::string gLastErrorString;
+
 // Automatically deletes a local ref when it goes out of scope
 // Automatically deletes a local ref when it goes out of scope
 class SmartLocalRef {
 class SmartLocalRef {
 private:
 private:
@@ -270,6 +274,81 @@ static bool callv(JNIEnv *env, jobject object, const char* typeName,
 	return true;
 	return true;
 }
 }
 
 
+static jobject callo(JNIEnv *env, jobject object, const char* typeName, const char* methodName, 
+	const char* signature,/* const*/ jvalue* params)
+{
+	jclass clazz = env->FindClass(typeName);
+	SmartLocalRef clazzRef(env, clazz);
+
+	if (NULL == clazz)
+	{
+		lprintf("could not find class %s\n", typeName);
+		return NULL;
+	}
+
+	jmethodID mid = env->GetMethodID(clazz, methodName, signature);
+
+	if (NULL == mid)
+	{
+		lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName);
+		return NULL;
+	}
+
+	jobject jReturnValue = env->CallObjectMethodA(object, mid, params);
+
+	return jReturnValue;
+}
+
+static int calli(JNIEnv *env, jobject object, const char* typeName, const char* methodName, 
+	const char* signature)
+{
+	jclass clazz = env->FindClass(typeName);
+	SmartLocalRef clazzRef(env, clazz);
+
+	if (NULL == clazz)
+	{
+		lprintf("could not find class %s\n", typeName);
+		return false;
+	}
+
+	jmethodID mid = env->GetMethodID(clazz, methodName, signature);
+
+	if (NULL == mid)
+	{
+		lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName);
+		return false;
+	}
+
+	jint jReturnValue = env->CallIntMethod(object, mid);
+
+	return (int) jReturnValue;
+}
+
+static int callc(JNIEnv *env, jobject object, const char* typeName, const char* methodName, 
+	const char* signature)
+{
+	jclass clazz = env->FindClass(typeName);
+	SmartLocalRef clazzRef(env, clazz);
+
+	if (NULL == clazz)
+	{
+		lprintf("could not find class %s\n", typeName);
+		return false;
+	}
+
+	jmethodID mid = env->GetMethodID(clazz, methodName, signature);
+
+	if (NULL == mid)
+	{
+		lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName);
+		return false;
+	}
+
+	jint jReturnValue = env->CallCharMethod(object, mid);
+
+	return (int) jReturnValue;
+}
+
 
 
 static bool callStaticObject(JNIEnv *env, const char* typeName, const char* methodName, 
 static bool callStaticObject(JNIEnv *env, const char* typeName, const char* methodName, 
 	const char* signature,/* const*/ jvalue* params, jobject& returnValue)
 	const char* signature,/* const*/ jvalue* params, jobject& returnValue)
@@ -359,6 +438,158 @@ static bool copyBufferArray(JNIEnv *env, jobject jMesh, const char* jBufferName,
 	return true;
 	return true;
 }
 }
 
 
+class JavaIOStream : public Assimp::IOStream
+{
+private:	
+	size_t pos;
+	size_t size;
+	char* buffer;
+	jobject jIOStream;
+
+	
+public:
+	JavaIOStream(size_t size, char* buffer, jobject jIOStream) :
+	pos(0),
+	size(size),
+	buffer(buffer),
+	jIOStream(jIOStream)
+	{};
+	
+	
+    ~JavaIOStream(void) 
+    {
+    	free(buffer);
+    }; 
+
+    size_t Read(void* pvBuffer, size_t pSize, size_t pCount)
+    {
+    	const size_t cnt = std::min(pCount,(size - pos)/pSize);
+		const size_t ofs = pSize*cnt;
+	
+	    memcpy(pvBuffer, buffer + pos, ofs);
+	    pos += ofs;
+	
+	    return cnt;
+    };
+    size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) 
+    {
+        return 0;
+    };
+    
+    aiReturn Seek(size_t pOffset, aiOrigin pOrigin)
+    {
+	    if (aiOrigin_SET == pOrigin) {
+	        if (pOffset >= size) {
+	            return AI_FAILURE;
+	        }
+	        pos = pOffset;
+	    }
+	    else if (aiOrigin_END == pOrigin) {
+	        if (pOffset >= size) {
+	            return AI_FAILURE;
+	        }
+	        pos = size-pOffset;
+	    }
+	    else {
+	        if (pOffset + pos >= size) {
+	            return AI_FAILURE;
+	        }
+	        pos += pOffset;
+	    }
+	    return AI_SUCCESS;
+    };
+    
+    size_t Tell(void) const
+    {
+    	return pos;
+    };
+    
+    size_t FileSize() const
+    {
+    	return size;
+    };
+    
+    void Flush() {};
+    
+    
+    jobject javaObject()
+    {
+    	return jIOStream;
+    };
+    
+    
+};
+ 
+
+class JavaIOSystem : public Assimp::IOSystem {
+	private:
+    JNIEnv* mJniEnv;
+	jobject& mJavaIOSystem;
+	
+	public:
+	JavaIOSystem(JNIEnv* env, jobject& javaIOSystem) :
+		mJniEnv(env),
+		mJavaIOSystem(javaIOSystem)
+	{};
+	
+    bool Exists( const char* pFile) const
+    {
+    	jvalue params[1];
+		params[0].l = mJniEnv->NewStringUTF(pFile);
+	    return call(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "exists", "(Ljava/lang/String;)Z", params);
+
+    };
+    char getOsSeparator() const
+    {
+	    return (char) callc(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "getOsSeparator", "()C");
+    };
+    
+    Assimp::IOStream* Open(const char* pFile,const char* pMode = "rb")
+    {
+        jvalue params[2];
+		params[0].l = mJniEnv->NewStringUTF(pFile);
+		params[1].l = mJniEnv->NewStringUTF(pMode);
+		
+		
+	    jobject jStream = callo(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "open", "(Ljava/lang/String;Ljava/lang/String;)Ljassimp/AiIOStream;", params);
+	    if(NULL == jStream)
+	    {
+	    	lprintf("NULL object from AiIOSystem.open\n");
+	    	return NULL;
+	    }
+	    
+	    size_t size = calli(mJniEnv, jStream, "jassimp/AiIOStream", "getFileSize", "()I");
+	    lprintf("Model file size is %d\n", size);
+	    
+	    char* buffer = (char*)malloc(size);
+	    jobject javaBuffer = mJniEnv->NewDirectByteBuffer(buffer, size);
+	    
+	    jvalue readParams[1];
+	    readParams[0].l = javaBuffer;
+	    if(call(mJniEnv, jStream, "jassimp/AiIOStream", "read", "(Ljava/nio/ByteBuffer;)Z", readParams))
+	    {
+	    	return new JavaIOStream(size, buffer, jStream);
+		}
+		else
+		{
+			lprintf("Read failure on AiIOStream.read");
+			free(buffer);
+			return NULL;
+		}
+
+    };
+    void Close( Assimp::IOStream* pFile)
+    {
+    	
+		jvalue params[1];
+		params[0].l = ((JavaIOStream*) pFile)->javaObject();
+		callv(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "close", "(Ljassimp/AiIOStream;)V", params);
+    	delete pFile;
+    };
+    
+
+	
+};
 
 
 
 
 static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene)
 static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene)
@@ -1474,7 +1705,7 @@ JNIEXPORT jint JNICALL Java_jassimp_Jassimp_getlongsize
 JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
 JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
   (JNIEnv *env, jclass jClazz)
   (JNIEnv *env, jclass jClazz)
 {
 {
-	const char *err = aiGetErrorString();
+	const char *err = gLastErrorString.c_str();
 
 
 	if (NULL == err)
 	if (NULL == err)
 	{
 	{
@@ -1486,18 +1717,26 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
 
 
 
 
 JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
 JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
-  (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess)
+  (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess, jobject ioSystem)
 {
 {
 	jobject jScene = NULL; 
 	jobject jScene = NULL; 
 
 
 	/* convert params */
 	/* convert params */
 	const char* cFilename = env->GetStringUTFChars(jFilename, NULL);
 	const char* cFilename = env->GetStringUTFChars(jFilename, NULL);
+	
+    Assimp::Importer imp;
 
 
-
+	
+	if(ioSystem != NULL)
+	{
+		imp.SetIOHandler(new JavaIOSystem(env, ioSystem));		
+		lprintf("Created aiFileIO\n");
+	}
+	
 	lprintf("opening file: %s\n", cFilename);
 	lprintf("opening file: %s\n", cFilename);
 
 
 	/* do import */
 	/* do import */
-	const aiScene *cScene = aiImportFile(cFilename, (unsigned int) postProcess);
+	const aiScene *cScene = imp.ReadFile(cFilename, (unsigned int) postProcess);
 
 
 	if (!cScene)
 	if (!cScene)
 	{
 	{
@@ -1552,19 +1791,13 @@ error:
 		/* thats really a problem because we cannot throw in this case */
 		/* thats really a problem because we cannot throw in this case */
 		env->FatalError("could not throw java.io.IOException");
 		env->FatalError("could not throw java.io.IOException");
 	}
 	}
-
-	env->ThrowNew(exception, aiGetErrorString());
+	gLastErrorString = imp.GetErrorString();
+	env->ThrowNew(exception, gLastErrorString.c_str());
 
 
 	lprintf("problem detected\n");
 	lprintf("problem detected\n");
 	}
 	}
 
 
 end:
 end:
-	/* 
-	 * NOTE: this releases all memory used in the native domain.
-	 * Ensure all data has been passed to java before!
-	 */
-	aiReleaseImport(cScene);
-
 
 
 	/* free params */
 	/* free params */
 	env->ReleaseStringUTFChars(jFilename, cFilename);
 	env->ReleaseStringUTFChars(jFilename, cFilename);

+ 1 - 1
port/jassimp/jassimp-native/src/jassimp.h

@@ -39,7 +39,7 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
  * Signature: (Ljava/lang/String;J)Ljassimp/AiScene;
  * Signature: (Ljava/lang/String;J)Ljassimp/AiScene;
  */
  */
 JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
 JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
-  (JNIEnv *, jclass, jstring, jlong);
+  (JNIEnv *, jclass, jstring, jlong, jobject);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 130 - 0
port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java

@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC)
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *     
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+package jassimp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * IOSystem based on the Java classloader.<p>
+ * 
+ * This IOSystem allows loading models directly from the 
+ * classpath. No extraction to the file system is 
+ * necessary.
+ * 
+ * @author Jesper Smith
+ *
+ */
+public class AiClassLoaderIOSystem implements AiIOSystem<AiInputStreamIOStream>
+{
+   private final Class<?> clazz;
+   private final ClassLoader classLoader;
+  
+   /**
+    * Construct a new AiClassLoaderIOSystem.<p>
+    * 
+    * This constructor uses a ClassLoader to resolve
+    * resources.
+    * 
+    * @param classLoader classLoader to resolve resources.
+    */
+   public AiClassLoaderIOSystem(ClassLoader classLoader) {
+      this.clazz = null;
+      this.classLoader = classLoader;
+   }
+
+   /**
+    * Construct a new AiClassLoaderIOSystem.<p>
+    * 
+    * This constructor uses a Class to resolve
+    * resources.
+    * 
+    * @param class<?> class to resolve resources.
+    */
+   public AiClassLoaderIOSystem(Class<?> clazz) {
+      this.clazz = clazz;
+      this.classLoader = null;
+   }
+   
+
+   @Override
+   public AiInputStreamIOStream open(String filename, String ioMode) {
+      try {
+         
+         InputStream is;
+         
+         if(clazz != null) {
+            is = clazz.getResourceAsStream(filename);
+         }
+         else if (classLoader != null) {
+            is = classLoader.getResourceAsStream(filename);
+         }
+         else {
+            System.err.println("[" + getClass().getSimpleName() + 
+                "] No class or classLoader provided to resolve " + filename);
+            return null;
+         }
+         
+         if(is != null) {
+            return new AiInputStreamIOStream(is);
+         }
+         else {
+            System.err.println("[" + getClass().getSimpleName() + 
+                               "] Cannot find " + filename);
+            return null;
+         }
+      }
+      catch (IOException e) {
+         e.printStackTrace();
+         return null;
+      }
+   }
+
+   @Override
+   public void close(AiInputStreamIOStream file) {
+   }
+
+   @Override
+   public boolean exists(String path)
+   {
+      URL url = null;
+      if(clazz != null) {
+         url = clazz.getResource(path);
+      }
+      else if (classLoader != null) {
+         url = classLoader.getResource(path);
+      }
+
+      
+      if(url == null)
+      {
+         return false;
+      }
+      else
+      {
+         return true;
+      }
+      
+   }
+
+   @Override
+   public char getOsSeparator()
+   {
+      return '/';
+   }
+
+}

+ 55 - 0
port/jassimp/jassimp/src/jassimp/AiIOStream.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC)
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *     
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+package jassimp;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * Interface to allow custom resource loaders for jassimp.<p>
+ *
+ * The design is based on passing the file wholly in memory, 
+ * because Java inputstreams do not have to support seek. <p>
+ * 
+ * Writing files from Java is unsupported.
+ * 
+ * 
+ * @author Jesper Smith
+ *
+ */
+public interface AiIOStream
+{
+
+   /**
+    * Read all data into buffer. <p>
+    * 
+    * The whole stream should be read into the buffer. 
+    * No support is provided for partial reads. 
+    * 
+    * @param buffer Target buffer for the model data
+    * 
+    * @return true if successful, false if an error occurred.
+    */
+   boolean read(ByteBuffer buffer);
+
+   /**
+    * The total size of this stream. <p>
+    *  
+    * @return total size of this stream
+    */
+   int getFileSize();
+
+}

+ 54 - 0
port/jassimp/jassimp/src/jassimp/AiIOSystem.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC)
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *     
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+package jassimp;
+
+public interface AiIOSystem <T extends AiIOStream>
+{
+   /**
+    * 
+    * Open a new file with a given path.
+    * When the access to the file is finished, call close() to release all associated resources
+    * 
+    * @param path Path to the file
+    * @param ioMode file I/O mode. Required are: "wb", "w", "wt", "rb", "r", "rt".
+    * 
+    * @return AiIOStream or null if an error occurred
+    */
+   public T open(String path, String ioMode);
+   
+   
+   /**
+    * Tests for the existence of a file at the given path.
+    *  
+    * @param path path to the file
+    * @return true if there is a file with this path, else false.
+    */
+   public boolean exists(String path);
+
+   /**
+    * Returns the system specific directory separator.<p>
+    * 
+    * @return System specific directory separator
+    */
+   public char getOsSeparator();
+   
+   /**
+    * Closes the given file and releases all resources associated with it.
+    * 
+    * @param file The file instance previously created by Open().
+    */
+   public void close(T file);
+}

+ 102 - 0
port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java

@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC)
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *     
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+package jassimp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.nio.ByteBuffer;
+
+
+/**
+ * Implementation of AiIOStream reading from a InputStream
+ * 
+ * @author Jesper Smith
+ *
+ */
+public class AiInputStreamIOStream implements AiIOStream
+{
+   private final ByteArrayOutputStream os = new ByteArrayOutputStream(); 
+   
+   
+   public AiInputStreamIOStream(URI uri) throws IOException {
+      this(uri.toURL());
+   }
+   
+   public AiInputStreamIOStream(URL url) throws IOException {
+      this(url.openStream());
+   }
+   
+   public AiInputStreamIOStream(InputStream is) throws IOException {
+      int read;
+      byte[] data = new byte[1024];
+      while((read = is.read(data, 0, data.length)) != -1) {
+         os.write(data, 0, read);
+      }
+      os.flush();
+      
+      is.close();
+   }
+   
+   @Override
+   public int getFileSize() {
+      return os.size();
+   }
+   
+   @Override
+   public boolean read(ByteBuffer buffer) {
+     ByteBufferOutputStream bos = new ByteBufferOutputStream(buffer);
+     try
+     {
+        os.writeTo(bos);
+     }
+     catch (IOException e)
+     {
+        e.printStackTrace();
+        return false;
+     }
+     return true;
+   }
+   
+   /**
+    * Internal helper class to copy the contents of an OutputStream
+    * into a ByteBuffer. This avoids a copy.
+    *
+    */
+   private static class ByteBufferOutputStream extends OutputStream {
+
+      private final ByteBuffer buffer;
+      
+      public ByteBufferOutputStream(ByteBuffer buffer) {
+         this.buffer = buffer;
+      }
+      
+      @Override
+      public void write(int b) throws IOException
+      {
+         buffer.put((byte) b);
+      }
+    
+      @Override
+      public void write(byte b[], int off, int len) throws IOException {
+         buffer.put(b, off, len);
+      }
+   }
+}
+

+ 33 - 3
port/jassimp/jassimp/src/jassimp/Jassimp.java

@@ -79,22 +79,52 @@ public final class Jassimp {
         return importFile(filename, EnumSet.noneOf(AiPostProcessSteps.class));
         return importFile(filename, EnumSet.noneOf(AiPostProcessSteps.class));
     }
     }
     
     
+    /**
+     * Imports a file via assimp without post processing.
+     * 
+     * @param filename the file to import
+     * @param ioSystem ioSystem to load files, or null for default
+     * @return the loaded scene
+     * @throws IOException if an error occurs
+     */
+    public static AiScene importFile(String filename, AiIOSystem<?> ioSystem) 
+          throws IOException {
+       
+       return importFile(filename, EnumSet.noneOf(AiPostProcessSteps.class), ioSystem);
+    }
+    
+    
+    /**
+     * Imports a file via assimp.
+     * 
+     * @param filename the file to import
+     * @param postProcessing post processing flags
+     * @return the loaded scene, or null if an error occurred
+     * @throws IOException if an error occurs
+     */
+    public static AiScene importFile(String filename, 
+                                     Set<AiPostProcessSteps> postProcessing) 
+                                           throws IOException {
+        return importFile(filename, postProcessing, null);
+    }
     
     
     /**
     /**
      * Imports a file via assimp.
      * Imports a file via assimp.
      * 
      * 
      * @param filename the file to import
      * @param filename the file to import
      * @param postProcessing post processing flags
      * @param postProcessing post processing flags
+     * @param ioSystem ioSystem to load files, or null for default
      * @return the loaded scene, or null if an error occurred
      * @return the loaded scene, or null if an error occurred
      * @throws IOException if an error occurs
      * @throws IOException if an error occurs
      */
      */
     public static AiScene importFile(String filename, 
     public static AiScene importFile(String filename, 
-            Set<AiPostProcessSteps> postProcessing) throws IOException {
+            Set<AiPostProcessSteps> postProcessing, AiIOSystem<?> ioSystem) 
+                  throws IOException {
         
         
        loadLibrary();
        loadLibrary();
        
        
         return aiImportFile(filename, AiPostProcessSteps.toRawValue(
         return aiImportFile(filename, AiPostProcessSteps.toRawValue(
-                postProcessing));
+                postProcessing), ioSystem);
     }
     }
     
     
     
     
@@ -310,7 +340,7 @@ public final class Jassimp {
      * @throws IOException if an error occurs
      * @throws IOException if an error occurs
      */
      */
     private static native AiScene aiImportFile(String filename, 
     private static native AiScene aiImportFile(String filename, 
-            long postProcessing) throws IOException;
+            long postProcessing, AiIOSystem<?> ioSystem) throws IOException;
     
     
     
     
     /**
     /**

+ 14 - 6
test/unit/utPretransformVertices.cpp

@@ -90,28 +90,36 @@ void PretransformVerticesTest::SetUp()
 
 
     // add 5 empty materials
     // add 5 empty materials
     scene->mMaterials = new aiMaterial*[scene->mNumMaterials = 5];
     scene->mMaterials = new aiMaterial*[scene->mNumMaterials = 5];
-    for (unsigned int i = 0; i < 5;++i)
+    for (unsigned int i = 0; i < 5;++i) {
         scene->mMaterials[i] = new aiMaterial();
         scene->mMaterials[i] = new aiMaterial();
+    }
 
 
     // add 25 test meshes
     // add 25 test meshes
     scene->mMeshes = new aiMesh*[scene->mNumMeshes = 25];
     scene->mMeshes = new aiMesh*[scene->mNumMeshes = 25];
-    for (unsigned int i = 0; i < 25;++i) {
-        aiMesh* mesh = scene->mMeshes[i] = new aiMesh();
+    for ( unsigned int i = 0; i < 25; ++i) {
+        aiMesh* mesh = scene->mMeshes[ i ] = new aiMesh();
 
 
         mesh->mPrimitiveTypes = aiPrimitiveType_POINT;
         mesh->mPrimitiveTypes = aiPrimitiveType_POINT;
         mesh->mFaces = new aiFace[ mesh->mNumFaces = 10+i ];
         mesh->mFaces = new aiFace[ mesh->mNumFaces = 10+i ];
         mesh->mVertices = new aiVector3D[mesh->mNumVertices = mesh->mNumFaces];
         mesh->mVertices = new aiVector3D[mesh->mNumVertices = mesh->mNumFaces];
         for (unsigned int a = 0; a < mesh->mNumFaces; ++a ) {
         for (unsigned int a = 0; a < mesh->mNumFaces; ++a ) {
-            aiFace& f = mesh->mFaces[a];
-            f.mIndices = new unsigned int [f.mNumIndices = 1];
+            aiFace& f = mesh->mFaces[ a ];
+            f.mIndices = new unsigned int [ f.mNumIndices = 1 ];
             f.mIndices[0] = a*3;
             f.mIndices[0] = a*3;
 
 
             mesh->mVertices[a] = aiVector3D((float)i,(float)a,0.f);
             mesh->mVertices[a] = aiVector3D((float)i,(float)a,0.f);
         }
         }
         mesh->mMaterialIndex = i%5;
         mesh->mMaterialIndex = i%5;
 
 
-        if (i % 2)
+        if (i % 2) {
             mesh->mNormals = new aiVector3D[mesh->mNumVertices];
             mesh->mNormals = new aiVector3D[mesh->mNumVertices];
+            for ( unsigned int normalIdx=0; normalIdx<mesh->mNumVertices; ++normalIdx ) {
+                mesh->mNormals[ normalIdx ].x = 1.0f;
+                mesh->mNormals[ normalIdx ].y = 1.0f;
+                mesh->mNormals[ normalIdx ].z = 1.0f;
+                mesh->mNormals[ normalIdx ].Normalize();
+            }
+        }
     }
     }
 
 
     // construct some nodes (1+25)
     // construct some nodes (1+25)