From 9269f345b00e19b4dfb0dae062324186d7bbf591 Mon Sep 17 00:00:00 2001
From: Anselme <anselme.francois@gmail.com>
Date: Thu, 28 Jul 2016 15:20:16 +0200
Subject: [PATCH] progress on TexturePack and Model serialization, added
 tinydir as included dependency

---
 CONTRIBUTING.md |   2 +
 src/image.cpp   | 150 ++++++++-
 src/image.h     |  10 +-
 src/mesh.cpp    |  86 ++++--
 src/mesh.h      |  21 +-
 src/model.cpp   |  42 +--
 src/model.h     |  22 +-
 src/tinydir.h   | 793 ++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 1027 insertions(+), 99 deletions(-)
 create mode 100644 src/tinydir.h

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1daa3f3..0bec920 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,6 +2,8 @@ The SparrowRenderer has mostly been written by Anselme FRANÇOIS.
 
 Thanks to LodePNG for texture serialization : http://lodev.org/lodepng
 
+Thanks to tinydir for directory navigation : https://github.com/cxong/tinydir
+
 Thanks to GL3W for OpenGL 3.3 core function loading : https://github.com/skaslev/gl3w
 
 Thanks to Dimitri RAGUET for his little fixes, explanations, and optimisations.
\ No newline at end of file
diff --git a/src/image.cpp b/src/image.cpp
index 3116bdf..244b9aa 100644
--- a/src/image.cpp
+++ b/src/image.cpp
@@ -1,6 +1,8 @@
 #include "image.h"
 #include <glm/gtc/noise.hpp>
 #include "lodepng.h"
+#include "tinydir.h"
+#include "texture.h"
 
 std::string getNameFromFilename(const std::string &filename)
 {
@@ -119,18 +121,146 @@ bool Image::save(std::vector<unsigned char> &out)
     return error != 0;
 }
 
-TexturePack::TexturePack(const std::string &filename)
+void TexturePack::addImage(Image* img)
 {
-    m_name = getNameFromFilename(filename);
-    std::FILE *file = std::fopen(filename.c_str(), "r");
-    if(file == NULL)
-        return;
+	TexImage texImg;
+	texImg.name = img->m_name;
+	texImg.img = img;
+	texImg.tex = NULL;
+	if(m_images.count(img->m_name))
+		removeImage(img->m_name);
+	m_images[img->m_name] = texImg;
 }
 
-bool TexturePack::save(const std::string &path)
+void TexturePack::removeImage(const std::string &name)
 {
-    std::string filename = path + '/' + m_name + ".tpk";
-    std::FILE *file = std::fopen(filename.c_str(), "w");
-    if(file == NULL)
-        return false;
+	TexImage &texImg = m_images[name];
+	if(texImg.img != NULL)
+		delete texImg.img;
+	if(texImg.tex != NULL)
+		delete texImg.tex;
+	m_images.erase(name);
+}
+
+void TexturePack::addImage(Texture* tex, const std::string &name, bool downloadFromGL)
+{
+	TexImage texImg;
+	texImg.name = name;
+	texImg.tex = tex;
+	if(downloadFromGL)
+	{
+		texImg.img = tex->getData();
+		texImg.img->m_name = name;
+	}
+	else
+		texImg.img = NULL;
+	if(m_images.count(name))
+		removeImage(name);
+	m_images[name] = texImg;
+}
+
+void TexturePack::initGL()
+{
+	for(std::pair<std::string, TexImage> pair : m_images)
+	{
+        TexImage &texImg = pair.second;
+		if(texImg.img != NULL)
+		{
+			if(texImg.tex != NULL)
+				delete texImg.tex;
+			texImg.tex = new Texture(texImg.img);
+		}
+	}
+}
+
+void TexturePack::initDestroyImages()
+{
+	for(std::pair<std::string, TexImage> pair : m_images)
+	{
+        TexImage &texImg = pair.second;
+		if(texImg.img != NULL)
+		{
+			if(texImg.tex != NULL)
+				delete texImg.tex;
+			texImg.tex = new Texture(texImg.img);
+			delete texImg.img;
+		}
+	}
+}
+
+void TexturePack::getImagesFromGL()
+{
+	for(std::pair<std::string, TexImage> pair : m_images)
+	{
+        TexImage &texImg = pair.second;
+		if(texImg.tex != NULL)
+		{
+			if(texImg.img != NULL)
+				delete texImg.img;
+			texImg.img = texImg.tex->getData();
+		}
+	}
+}
+
+void TexturePack::destroyGL()
+{
+	for(std::pair<std::string, TexImage> pair : m_images)
+	{
+        TexImage &texImg = pair.second;
+		if(texImg.tex != NULL)
+			delete texImg.tex;
+	}
+}
+
+TexturePack::TexturePack(const std::string &folderPath) : TexturePack()
+{
+	m_name = getNameFromFilename(folderPath);
+	tinydir_dir dir;
+	int err = tinydir_open(&dir, folderPath.c_str());
+	if(err == -1)
+	{
+		fprintf(stderr, "can't open directory : \"%s\"\n", folderPath.c_str());
+		return;
+	}
+	int n = dir.n_files;
+	for(int i=0; i<n; ++i)
+	{
+		const tinydir_file &file = dir._files[i];
+		if(std::string(file.extension) == "png")
+		{
+			Image* img = new Image(std::string(file.path));
+			img->m_name = file.name;
+			addImage(img);
+		}
+		else
+			fprintf(stderr, "%s is not a png file, ignoring\n", file.path);
+	}
+}
+
+bool TexturePack::save(const std::string &folderPath)
+{
+	std::string path = folderPath + '/' + m_name;
+	if(!tinydir_create_directory(path.c_str()))
+	{
+        fprintf(stderr, "can't create directory \"%s\"\n", path.c_str());
+        return false;
+	}
+	tinydir_dir dir;
+	int err = tinydir_open(&dir, path.c_str());
+	if(err == -1)
+	{
+		fprintf(stderr, "can't open directory : \"%s\"\n", path.c_str());
+		return false;
+	}
+	// directory is open
+	for(std::pair<std::string, TexImage> pair : m_images)
+	{
+        TexImage &texImg = pair.second;
+        if(texImg.img != NULL)
+        {
+            std::string filename = path + '/' + texImg.name + ".png";
+            if(!texImg.img->save(filename))
+                fprintf(stderr, "can't save image : \"%s\"\n", filename.c_str());
+        }
+	}
 }
diff --git a/src/image.h b/src/image.h
index 0343c19..635bbfd 100644
--- a/src/image.h
+++ b/src/image.h
@@ -42,10 +42,12 @@ private:
         std::string name;
         Texture* tex;
         Image* img;
+		
+		TexImage() : tex(NULL), img(NULL) {}
     };
     
     std::string m_name;
-    std::unordered_map<std::string, Image> m_images;
+    std::unordered_map<std::string, TexImage> m_images;
     bool allocatedImg;
     bool allocatedTex;
 
@@ -69,10 +71,16 @@ public:
      * @brief addImage adds an image to the pack, if an image of this name already exists in the pack, it will be replaced.
      */
     void addImage(Image* img);
+	
+	/**
+     * @brief removeImage removes the image named "name" from the pack if it exists.
+     */
+    void removeImage(const std::string &name);
     
     /**
      * @brief addImage adds an image from an OpenGL texture and names it with the provided name.
      * @param downloadFromGL if this is false, the Image will not be downloaded from the texture, only the texture will be added.
+	 * this does not support cubemap or array textures (only the first face will be added)
      */
     void addImage(Texture* tex, const std::string &name, bool downloadFromGL = true);
     
diff --git a/src/mesh.cpp b/src/mesh.cpp
index ede7baa..c8ab54a 100644
--- a/src/mesh.cpp
+++ b/src/mesh.cpp
@@ -39,6 +39,7 @@ Mesh::Mesh(const std::string &name) :
     isBillboard(false),
     isShadowCaster(true),
     depth(0),
+    m_flags(0),
     primitive_type(GL_TRIANGLES),
     vao(0)
 {
@@ -65,8 +66,39 @@ void Mesh::clearBuffers()
     buffers.clear();
 }
 
+unsigned int Mesh::updateFlags()
+{
+    m_flags = 0;
+
+    if(!indices.empty())
+        m_flags |= 1 << MESH_INDEXED;
+    if(!texCoords.empty())
+        m_flags |= 1 << MESH_TEXTURABLE;
+    if(!instances_offsets.empty())
+        m_flags |= 1 << MESH_INSTANCED;
+
+    if(!positions3D.empty())
+    {
+        m_flags |= 1 << MESH_3D;
+
+        if(!tangents.empty())
+            m_flags |= 1 << MESH_TANGENT_SPACE;
+        if(isDoubleSided)
+            m_flags |= 1 << MESH_DOUBLE_SIDED;
+        if(isBillboard)
+            m_flags |= 1 << MESH_BILLBOARD;
+        if(isShadowCaster)
+            m_flags |= 1 << MESH_SHADOWED;
+    }
+    else
+        m_flags |= 1 << MESH_2D;
+    
+    return m_flags | material->getFlags();
+}
+
 void Mesh::initGL()
 {
+    updateFlags();
     destroyGL();
     
     // create VAO
@@ -197,32 +229,7 @@ void Mesh::clearData()
 
 unsigned int Mesh::getFlags()
 {
-    unsigned int flags = material->getFlags();
-
-    if(!(indices.empty() || buffersId[INDICES_BUFFER] == -1))
-        flags |= 1 << MESH_INDEXED;
-    if(!(texCoords.empty() || buffersId[TEXCOORD_BUFFER] == -1))
-        flags |= 1 << MESH_TEXTURABLE;
-    if(!(instances_offsets.empty() || buffersId[INSTANCE_BUFFER] == -1))
-        flags |= 1 << MESH_INSTANCED;
-
-    if(!(positions3D.empty() || buffersId[POSITION3D_BUFFER] == -1))
-    {
-        flags |= 1 << MESH_3D;
-
-        if((!tangents.empty() || buffersId[TANGENT_BUFFER] == -1))
-            flags |= 1 << MESH_TANGENT_SPACE;
-        if(isDoubleSided)
-            flags |= 1 << MESH_DOUBLE_SIDED;
-        if(isBillboard)
-            flags |= 1 << MESH_BILLBOARD;
-        if(isShadowCaster)
-            flags |= 1 << MESH_SHADOWED;
-    }
-    else
-        flags |= 1 << MESH_2D;
-
-    return flags;
+    return m_flags | material->getFlags();
 }
 
 void Mesh::addRectangle2D(const glm::vec2 &pos, const glm::vec2 &dim, const glm::vec2 &texCoord, const glm::vec2 &texRange, bool indexed)
@@ -249,6 +256,33 @@ void Mesh::addRectangle2D(const glm::vec2 &pos, const glm::vec2 &dim, const glm:
     }
 }
 
+void Mesh::setIsDoubleSided(bool val)
+{
+    isDoubleSided = val;
+    if(val)
+        m_flags |= 1 << MESH_DOUBLE_SIDED;
+    else
+        m_flags &= ~(1 << MESH_DOUBLE_SIDED);
+}
+
+void Mesh::setIsBillboard(bool val)
+{
+    isBillboard = val;
+    if(val)
+        m_flags |= 1 << MESH_BILLBOARD;
+    else
+        m_flags &= ~(1 << MESH_BILLBOARD);
+}
+
+void Mesh::setIsShadowCaster(bool val)
+{
+    isShadowCaster = val;
+    if(val)
+        m_flags |= 1 << MESH_SHADOWED;
+    else
+        m_flags &= ~(1 << MESH_SHADOWED);
+}
+
 struct VertexComparator
 {
     // c'est plutot crade mais j'ai pas trouve d'autre moyen pour le moment
diff --git a/src/mesh.h b/src/mesh.h
index 2242cae..fa05893 100644
--- a/src/mesh.h
+++ b/src/mesh.h
@@ -109,7 +109,8 @@ public:
     void clearData();
 
     /**
-     * @brief getFlags returns the flags that defines the specificities of the mesh and his material
+     * @brief getFlags returns the flags that defines the specificities of the mesh and his material.
+     * @warning the flags are correct only if initGL has been called.
      */
     unsigned int getFlags();
 
@@ -152,7 +153,7 @@ public:
      * @brief setDepth allows to set the depth of a 2D mesh, the depth must be between -1 and 1, -1 being the closest to the camera
      */
     void setDepth(float d) { depth = d; }
-    float getDepth() { return depth; } const
+    float getDepth() const { return depth; }
 
     /*************************************************************/
     /*                    3D MESH PROPERTIES                     */
@@ -161,19 +162,19 @@ public:
     /**
      * @brief setIsDoubleSided allows to enable or disable face culling for this Mesh
      */
-    void setIsDoubleSided(bool val) {isDoubleSided = val;}
+    void setIsDoubleSided(bool val);
 
     /**
      * @brief setIsBillboard allows to enable or disable billboarding,
      * a billboard mesh will always follow the camera orientation
      */
-    void setIsBillboard(bool val) {isBillboard = val;}
+    void setIsBillboard(bool val);
     
     /**
      * @brief setIsShadowCaster allows to enable or disable,
      * rendering of this mesh in the shadowmaps
      */
-    void setIsShadowCaster(bool val) {isShadowCaster = val;}
+    void setIsShadowCaster(bool val);
 
     /*************************************************************/
     /*                      3D MESH TOOLS                        */
@@ -234,7 +235,14 @@ public:
      * default is GL_TRIANGLES
      */
     void setPrimitiveType(GLenum type) {primitive_type = type;}
-
+    
+    /**
+     * @brief updateFlags forces the refresh of the mesh flags,
+     * they will be computed from the CPU-side buffers status
+     * (so don't call it after a call to clearData)
+     */
+    unsigned int updateFlags();
+    
 protected:
     std::string m_name;
     
@@ -243,6 +251,7 @@ protected:
     bool isBillboard;
     bool isShadowCaster;
     float depth;
+    unsigned int m_flags;
 
     GLenum primitive_type;
 
diff --git a/src/model.cpp b/src/model.cpp
index 02b19a0..3f30fb0 100644
--- a/src/model.cpp
+++ b/src/model.cpp
@@ -12,62 +12,32 @@ Model::~Model()
     destroyGL();
     for(Mesh *m : m_meshes)
         delete m;
-}
-/*
-Image* Model::getImage(const std::string &name)
-{
-    for(TextureImg &tex : m_textures)
-    {
-        if(name.compare(tex.name) == 0)
-            return tex.img;
-    }
-    return NULL;
+    setTexturePack(NULL);
 }
 
-Texture* Model::getTexture(const std::string &name)
+void Model::setTexturePack(TexturePack *texPack)
 {
-    for(TextureImg &tex : m_textures)
-    {
-        if(name.compare(tex.name) == 0)
-            return tex.tex;
-    }
-    return NULL;
-}*/
+    if(m_texPack != NULL)
+        delete m_texPack;
+    m_texPack = texPack;
+}
 
 void Model::destroy()
 {
     for(Mesh* m : m_meshes)
         m->clearData();
-    for(TextureImg &tex : m_textures)
-    {
-        delete tex.img;
-        tex.img = NULL;
-    }
 }
 
 void Model::initGL()
 {
     for(Mesh* m : m_meshes)
         m->initGL();
-    for(TextureImg &tex : m_textures)
-    {
-        if(tex.tex == NULL)
-            tex.tex = new Texture(tex.img);
-    }
 }
 
 void Model::destroyGL()
 {
     for(Mesh* m : m_meshes)
         m->destroyGL();
-    for(TextureImg &tex : m_textures)
-    {
-        if(tex.tex != NULL)
-        {
-            delete tex.tex;
-            tex.tex = NULL;
-        }
-    }
 }
 
 bool Model::save(const std::string &filename, const std::string &texturesPath)
diff --git a/src/model.h b/src/model.h
index 60666d6..89e4455 100644
--- a/src/model.h
+++ b/src/model.h
@@ -11,39 +11,21 @@ class TexturePack;
 class Model
 {
 protected:
-    /*struct TextureImg
-    {
-        std::string name;
-        Texture* tex;
-        Image* img;
-        
-        TextureImg(const std::string n, Image* i) :
-            name(n), tex(NULL), img(i) {}
-    };*/
-    
     TexturePack *m_texPack;
     std::vector<Mesh*> m_meshes;
-    
-    //std::vector<TextureImg> m_textures;
 
 public:
     Model();
     
     ~Model();
     
+    void setTexturePack(TexturePack *texPack);
+    
     /**
      * @brief addMesh adds a mesh to the mesh list
      */
     void addMesh(Mesh *mesh) { m_meshes.push_back(mesh); }
     
-    /**
-     * @brief addImage adds an image to the texture pack
-     */
-    /*void addImage(const std::string &name, Image* img)
-    {
-        m_textures.push_back(TextureImg(name, img));
-    }*/
-    
     /**
      * @brief getMeshes returns the mesh array
      */
diff --git a/src/tinydir.h b/src/tinydir.h
new file mode 100644
index 0000000..6a5cf0f
--- /dev/null
+++ b/src/tinydir.h
@@ -0,0 +1,793 @@
+/*
+Copyright (c) 2013-2016, tinydir authors:
+- Cong Xu
+- Lautis Sun
+- Baudouin Feildel
+- Andargor <andargor@yahoo.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ this file has been slightly modified by EpicSparrow to add the tinydir_create_directory function
+ */
+
+#ifndef TINYDIR_H
+#define TINYDIR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if ((defined _UNICODE) && !(defined UNICODE))
+#define UNICODE
+#endif
+
+#if ((defined UNICODE) && !(defined _UNICODE))
+#define _UNICODE
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _MSC_VER
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include <tchar.h>
+# pragma warning(push)
+# pragma warning (disable : 4996)
+#else
+# include <dirent.h>
+# include <libgen.h>
+# include <sys/stat.h>
+# include <stddef.h>
+#endif
+#ifdef __MINGW32__
+# include <direct.h>
+# include <tchar.h>
+#endif
+
+bool tinydir_create_directory(const char* path)
+{
+   
+   int nError = 0;
+   #if defined(_WIN32)
+       nError = _mkdir(path); // can be used on Windows
+   #else 
+       mode_t nMode = 0733; // UNIX style permissions
+       nError = mkdir(path, nMode); // can be used on non-Windows
+   #endif
+   return nError == 0;
+}
+
+/* types */
+
+/* Windows UNICODE wide character support */
+#if defined _MSC_VER || defined __MINGW32__
+#define _tinydir_char_t TCHAR
+#define TINYDIR_STRING(s) _TEXT(s)
+#define _tinydir_strlen _tcslen
+#define _tinydir_strcpy _tcscpy
+#define _tinydir_strcat _tcscat
+#define _tinydir_strcmp _tcscmp
+#define _tinydir_strrchr _tcsrchr
+#define _tinydir_strncmp _tcsncmp
+#else
+#define _tinydir_char_t char
+#define TINYDIR_STRING(s) s
+#define _tinydir_strlen strlen
+#define _tinydir_strcpy strcpy
+#define _tinydir_strcat strcat
+#define _tinydir_strcmp strcmp
+#define _tinydir_strrchr strrchr
+#define _tinydir_strncmp strncmp
+#endif
+
+#define _TINYDIR_PATH_MAX 4096
+#ifdef _MSC_VER
+/* extra chars for the "\\*" mask */
+# define _TINYDIR_PATH_EXTRA 2
+#else
+# define _TINYDIR_PATH_EXTRA 0
+#endif
+#define _TINYDIR_FILENAME_MAX 256
+
+#ifdef _MSC_VER
+# define _TINYDIR_FUNC static __inline
+#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
+# define _TINYDIR_FUNC static __inline__
+#else
+# define _TINYDIR_FUNC static inline
+#endif
+
+/* readdir_r is a POSIX-only function, and may not be available under various
+ * environments/settings, e.g. MinGW. Use readdir fallback */
+#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\
+	_POSIX_SOURCE
+# define _TINYDIR_HAS_READDIR_R
+#endif
+#if _POSIX_C_SOURCE >= 200112L
+# define _TINYDIR_HAS_FPATHCONF
+# include <unistd.h>
+#endif
+#if _BSD_SOURCE || _SVID_SOURCE || \
+	(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
+# define _TINYDIR_HAS_DIRFD
+# include <sys/types.h>
+#endif
+#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
+	defined _PC_NAME_MAX
+# define _TINYDIR_USE_FPATHCONF
+#endif
+#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
+	!(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
+# define _TINYDIR_USE_READDIR
+#endif
+
+/* MINGW32 has two versions of dirent, ASCII and UNICODE*/
+#ifndef _MSC_VER
+#if (defined __MINGW32__) && (defined _UNICODE)
+#define _TINYDIR_DIR _WDIR
+#define _tinydir_dirent _wdirent
+#define _tinydir_opendir _wopendir
+#define _tinydir_readdir _wreaddir
+#define _tinydir_closedir _wclosedir
+#else
+#define _TINYDIR_DIR DIR
+#define _tinydir_dirent dirent
+#define _tinydir_opendir opendir
+#define _tinydir_readdir readdir
+#define _tinydir_closedir closedir
+#endif
+#endif
+
+/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */
+#if    defined(_TINYDIR_MALLOC) &&  defined(_TINYDIR_FREE)
+#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE)
+#else
+#error "Either define both alloc and free or none of them!"
+#endif
+
+#if !defined(_TINYDIR_MALLOC)
+	#define _TINYDIR_MALLOC(_size) malloc(_size)
+	#define _TINYDIR_FREE(_ptr)    free(_ptr)
+#endif /* !defined(_TINYDIR_MALLOC) */
+
+typedef struct tinydir_file
+{
+	_tinydir_char_t path[_TINYDIR_PATH_MAX];
+	_tinydir_char_t name[_TINYDIR_FILENAME_MAX];
+	_tinydir_char_t *extension;
+	int is_dir;
+	int is_reg;
+
+#ifndef _MSC_VER
+#ifdef __MINGW32__
+	struct _stat _s;
+#else
+	struct stat _s;
+#endif
+#endif
+} tinydir_file;
+
+typedef struct tinydir_dir
+{
+	_tinydir_char_t path[_TINYDIR_PATH_MAX];
+	int has_next;
+	size_t n_files;
+
+	tinydir_file *_files;
+#ifdef _MSC_VER
+	HANDLE _h;
+	WIN32_FIND_DATA _f;
+#else
+	_TINYDIR_DIR *_d;
+	struct _tinydir_dirent *_e;
+#ifndef _TINYDIR_USE_READDIR
+	struct _tinydir_dirent *_ep;
+#endif
+#endif
+} tinydir_dir;
+
+
+/* declarations */
+
+_TINYDIR_FUNC
+int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+void tinydir_close(tinydir_dir *dir);
+
+_TINYDIR_FUNC
+int tinydir_next(tinydir_dir *dir);
+_TINYDIR_FUNC
+int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
+_TINYDIR_FUNC
+int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
+_TINYDIR_FUNC
+int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
+
+_TINYDIR_FUNC
+void _tinydir_get_ext(tinydir_file *file);
+_TINYDIR_FUNC
+int _tinydir_file_cmp(const void *a, const void *b);
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+_TINYDIR_FUNC
+size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp);
+#endif
+#endif
+
+
+/* definitions*/
+
+_TINYDIR_FUNC
+int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path)
+{
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+	int error;
+	int size;	/* using int size */
+#endif
+#else
+	_tinydir_char_t path_buf[_TINYDIR_PATH_MAX];
+#endif
+	_tinydir_char_t *pathp;
+
+	if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+	if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
+	{
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+
+	/* initialise dir */
+	dir->_files = NULL;
+#ifdef _MSC_VER
+	dir->_h = INVALID_HANDLE_VALUE;
+#else
+	dir->_d = NULL;
+#ifndef _TINYDIR_USE_READDIR
+	dir->_ep = NULL;
+#endif
+#endif
+	tinydir_close(dir);
+
+	_tinydir_strcpy(dir->path, path);
+	/* Remove trailing slashes */
+	pathp = &dir->path[_tinydir_strlen(dir->path) - 1];
+	while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/')))
+	{
+		*pathp = TINYDIR_STRING('\0');
+		pathp++;
+	}
+#ifdef _MSC_VER
+	_tinydir_strcpy(path_buf, dir->path);
+	_tinydir_strcat(path_buf, TINYDIR_STRING("\\*"));
+	dir->_h = FindFirstFile(path_buf, &dir->_f);
+	if (dir->_h == INVALID_HANDLE_VALUE)
+	{
+		errno = ENOENT;
+#else
+	dir->_d = _tinydir_opendir(path);
+	if (dir->_d == NULL)
+	{
+#endif
+		goto bail;
+	}
+
+	/* read first file */
+	dir->has_next = 1;
+#ifndef _MSC_VER
+#ifdef _TINYDIR_USE_READDIR
+	dir->_e = _tinydir_readdir(dir->_d);
+#else
+	/* allocate dirent buffer for readdir_r */
+	size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */
+	if (size == -1) return -1;
+	dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size);
+	if (dir->_ep == NULL) return -1;
+
+	error = readdir_r(dir->_d, dir->_ep, &dir->_e);
+	if (error != 0) return -1;
+#endif
+	if (dir->_e == NULL)
+	{
+		dir->has_next = 0;
+	}
+#endif
+
+	return 0;
+
+bail:
+	tinydir_close(dir);
+	return -1;
+}
+
+_TINYDIR_FUNC
+int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path)
+{
+	/* Count the number of files first, to pre-allocate the files array */
+	size_t n_files = 0;
+	if (tinydir_open(dir, path) == -1)
+	{
+		return -1;
+	}
+	while (dir->has_next)
+	{
+		n_files++;
+		if (tinydir_next(dir) == -1)
+		{
+			goto bail;
+		}
+	}
+	tinydir_close(dir);
+
+	if (tinydir_open(dir, path) == -1)
+	{
+		return -1;
+	}
+
+	dir->n_files = 0;
+	dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files);
+	if (dir->_files == NULL)
+	{
+		goto bail;
+	}
+	while (dir->has_next)
+	{
+		tinydir_file *p_file;
+		dir->n_files++;
+
+		p_file = &dir->_files[dir->n_files - 1];
+		if (tinydir_readfile(dir, p_file) == -1)
+		{
+			goto bail;
+		}
+
+		if (tinydir_next(dir) == -1)
+		{
+			goto bail;
+		}
+
+		/* Just in case the number of files has changed between the first and
+		second reads, terminate without writing into unallocated memory */
+		if (dir->n_files == n_files)
+		{
+			break;
+		}
+	}
+
+	qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp);
+
+	return 0;
+
+bail:
+	tinydir_close(dir);
+	return -1;
+}
+
+_TINYDIR_FUNC
+void tinydir_close(tinydir_dir *dir)
+{
+	if (dir == NULL)
+	{
+		return;
+	}
+
+	memset(dir->path, 0, sizeof(dir->path));
+	dir->has_next = 0;
+	dir->n_files = 0;
+	_TINYDIR_FREE(dir->_files);
+	dir->_files = NULL;
+#ifdef _MSC_VER
+	if (dir->_h != INVALID_HANDLE_VALUE)
+	{
+		FindClose(dir->_h);
+	}
+	dir->_h = INVALID_HANDLE_VALUE;
+#else
+	if (dir->_d)
+	{
+		_tinydir_closedir(dir->_d);
+	}
+	dir->_d = NULL;
+	dir->_e = NULL;
+#ifndef _TINYDIR_USE_READDIR
+	_TINYDIR_FREE(dir->_ep);
+	dir->_ep = NULL;
+#endif
+#endif
+}
+
+_TINYDIR_FUNC
+int tinydir_next(tinydir_dir *dir)
+{
+	if (dir == NULL)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+	if (!dir->has_next)
+	{
+		errno = ENOENT;
+		return -1;
+	}
+
+#ifdef _MSC_VER
+	if (FindNextFile(dir->_h, &dir->_f) == 0)
+#else
+#ifdef _TINYDIR_USE_READDIR
+	dir->_e = _tinydir_readdir(dir->_d);
+#else
+	if (dir->_ep == NULL)
+	{
+		return -1;
+	}
+	if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0)
+	{
+		return -1;
+	}
+#endif
+	if (dir->_e == NULL)
+#endif
+	{
+		dir->has_next = 0;
+#ifdef _MSC_VER
+		if (GetLastError() != ERROR_SUCCESS &&
+			GetLastError() != ERROR_NO_MORE_FILES)
+		{
+			tinydir_close(dir);
+			errno = EIO;
+			return -1;
+		}
+#endif
+	}
+
+	return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file)
+{
+	if (dir == NULL || file == NULL)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+#ifdef _MSC_VER
+	if (dir->_h == INVALID_HANDLE_VALUE)
+#else
+	if (dir->_e == NULL)
+#endif
+	{
+		errno = ENOENT;
+		return -1;
+	}
+	if (_tinydir_strlen(dir->path) +
+		_tinydir_strlen(
+#ifdef _MSC_VER
+			dir->_f.cFileName
+#else
+			dir->_e->d_name
+#endif
+		) + 1 + _TINYDIR_PATH_EXTRA >=
+		_TINYDIR_PATH_MAX)
+	{
+		/* the path for the file will be too long */
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+	if (_tinydir_strlen(
+#ifdef _MSC_VER
+			dir->_f.cFileName
+#else
+			dir->_e->d_name
+#endif
+		) >= _TINYDIR_FILENAME_MAX)
+	{
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+
+	_tinydir_strcpy(file->path, dir->path);
+	_tinydir_strcat(file->path, TINYDIR_STRING("/"));
+	_tinydir_strcpy(file->name,
+#ifdef _MSC_VER
+		dir->_f.cFileName
+#else
+		dir->_e->d_name
+#endif
+	);
+	_tinydir_strcat(file->path, file->name);
+#ifndef _MSC_VER
+#ifdef __MINGW32__
+	if (_tstat(
+#else
+	if (stat(
+#endif
+		file->path, &file->_s) == -1)
+	{
+		return -1;
+	}
+#endif
+	_tinydir_get_ext(file);
+
+	file->is_dir =
+#ifdef _MSC_VER
+		!!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+#else
+		S_ISDIR(file->_s.st_mode);
+#endif
+	file->is_reg =
+#ifdef _MSC_VER
+		!!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
+		(
+			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) &&
+			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
+			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) &&
+#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM
+			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) &&
+#endif
+#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA
+			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) &&
+#endif
+			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) &&
+			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY));
+#else
+		S_ISREG(file->_s.st_mode);
+#endif
+
+	return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i)
+{
+	if (dir == NULL || file == NULL)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+	if (i >= dir->n_files)
+	{
+		errno = ENOENT;
+		return -1;
+	}
+
+	memcpy(file, &dir->_files[i], sizeof(tinydir_file));
+	_tinydir_get_ext(file);
+
+	return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_open_subdir_n(tinydir_dir *dir, size_t i)
+{
+	_tinydir_char_t path[_TINYDIR_PATH_MAX];
+	if (dir == NULL)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+	if (i >= dir->n_files || !dir->_files[i].is_dir)
+	{
+		errno = ENOENT;
+		return -1;
+	}
+
+	_tinydir_strcpy(path, dir->_files[i].path);
+	tinydir_close(dir);
+	if (tinydir_open_sorted(dir, path) == -1)
+	{
+		return -1;
+	}
+
+	return 0;
+}
+
+/* Open a single file given its path */
+_TINYDIR_FUNC
+int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path)
+{
+	tinydir_dir dir;
+	int result = 0;
+	int found = 0;
+	_tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX];
+	_tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX];
+	_tinydir_char_t *dir_name;
+	_tinydir_char_t *base_name;
+#if (defined _MSC_VER || defined __MINGW32__)
+	_tinydir_char_t drive_buf[_TINYDIR_PATH_MAX];
+	_tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX];
+#endif
+
+	if (file == NULL || path == NULL || _tinydir_strlen(path) == 0)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+	if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
+	{
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+
+	/* Get the parent path */
+#if (defined _MSC_VER || defined __MINGW32__)
+#if ((defined _MSC_VER) && (_MSC_VER >= 1400))
+		_tsplitpath_s(
+			path,
+			drive_buf, sizeof drive_buf,
+			dir_name_buf, sizeof dir_name_buf,
+			file_name_buf, sizeof file_name_buf,
+			ext_buf, sizeof ext_buf);
+#else
+		_tsplitpath(
+			path,
+			drive_buf,
+			dir_name_buf,
+			file_name_buf,
+			ext_buf);
+#endif
+	if (errno)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+	/* Emulate the behavior of dirname by returning "." for dir name if it's
+	empty */
+#if ((defined _MSC_VER || defined __MINGW32__) && (defined UNICODE))
+	if (drive_buf[0] == '\0' && drive_buf[1] == '\0' && dir_name_buf[0] == '\0' && dir_name_buf[1] == '\0')
+#else
+	if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0')
+#endif
+	{
+		_tinydir_strcpy(dir_name_buf, TINYDIR_STRING("."));
+	}
+	/* Concatenate the drive letter and dir name to form full dir name */
+	_tinydir_strcat(drive_buf, dir_name_buf);
+	dir_name = drive_buf;
+	/* Concatenate the file name and extension to form base name */
+	_tinydir_strcat(file_name_buf, ext_buf);
+	base_name = file_name_buf;
+#else
+	_tinydir_strcpy(dir_name_buf, path);
+	dir_name = dirname(dir_name_buf);
+	_tinydir_strcpy(file_name_buf, path);
+	base_name =basename(file_name_buf);
+#endif
+
+	/* Open the parent directory */
+	if (tinydir_open(&dir, dir_name) == -1)
+	{
+		return -1;
+	}
+
+	/* Read through the parent directory and look for the file */
+	while (dir.has_next)
+	{
+		if (tinydir_readfile(&dir, file) == -1)
+		{
+			result = -1;
+			goto bail;
+		}
+		if (_tinydir_strcmp(file->name, base_name) == 0)
+		{
+			/* File found */
+			found = 1;
+			break;
+		}
+		tinydir_next(&dir);
+	}
+	if (!found)
+	{
+		result = -1;
+		errno = ENOENT;
+	}
+
+bail:
+	tinydir_close(&dir);
+	return result;
+}
+
+_TINYDIR_FUNC
+void _tinydir_get_ext(tinydir_file *file)
+{
+	_tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.'));
+	if (period == NULL)
+	{
+		file->extension = &(file->name[_tinydir_strlen(file->name)]);
+	}
+	else
+	{
+		file->extension = period + 1;
+	}
+}
+
+_TINYDIR_FUNC
+int _tinydir_file_cmp(const void *a, const void *b)
+{
+	const tinydir_file *fa = (const tinydir_file *)a;
+	const tinydir_file *fb = (const tinydir_file *)b;
+	if (fa->is_dir != fb->is_dir)
+	{
+		return -(fa->is_dir - fb->is_dir);
+	}
+	return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX);
+}
+
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+/*
+The following authored by Ben Hutchings <ben@decadent.org.uk>
+from https://womble.decadent.org.uk/readdir_r-advisory.html
+*/
+/* Calculate the required buffer size (in bytes) for directory      *
+* entries read from the given directory handle.  Return -1 if this  *
+* this cannot be done.                                              *
+*                                                                   *
+* This code does not trust values of NAME_MAX that are less than    *
+* 255, since some systems (including at least HP-UX) incorrectly    *
+* define it to be a smaller value.                                  */
+_TINYDIR_FUNC
+size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp)
+{
+	long name_max;
+	size_t name_end;
+	/* parameter may be unused */
+	(void)dirp;
+
+#if defined _TINYDIR_USE_FPATHCONF
+	name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
+	if (name_max == -1)
+#if defined(NAME_MAX)
+		name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
+#else
+		return (size_t)(-1);
+#endif
+#elif defined(NAME_MAX)
+ 	name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
+#else
+#error "buffer size for readdir_r cannot be determined"
+#endif
+	name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1;
+	return (name_end > sizeof(struct _tinydir_dirent) ?
+		name_end : sizeof(struct _tinydir_dirent));
+}
+#endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+# if defined (_MSC_VER)
+# pragma warning(pop)
+# endif
+
+#endif