Commits

Anonymous committed f42ce8c Draft

[vfs #1] Add ttvfs, miniz, and minihttp sources

  • Participants
  • Parent commits 394c909

Comments (0)

Files changed (36)

File ExternalLibs/minihttp.cpp

+// minihttp.cpp - All functionality required for a minimal TCP/HTTP client packed in one file.
+// Released under the WTFPL (See minihttp.h)
+
+#ifdef _MSC_VER
+#  ifndef _CRT_SECURE_NO_WARNINGS
+#    define _CRT_SECURE_NO_WARNINGS
+#  endif
+#  ifndef _CRT_SECURE_NO_DEPRECATE
+#    define _CRT_SECURE_NO_DEPRECATE
+#  endif
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sstream>
+#include <cctype>
+#include <cerrno>
+#include <algorithm>
+
+#ifdef _WIN32
+#  ifndef _WIN32_WINNT
+#    define _WIN32_WINNT 0x0501
+#  endif
+#  include <winsock2.h>
+#  include <ws2tcpip.h>
+#  define EWOULDBLOCK WSAEWOULDBLOCK
+#  define ETIMEDOUT WSAETIMEDOUT
+#  define ECONNRESET WSAECONNRESET
+#  define ENOTCONN WSAENOTCONN
+#else
+#  include <sys/types.h>
+#  include <unistd.h>
+#  include <fcntl.h>
+#  include <sys/socket.h>
+#  include <netdb.h>
+#  define SOCKET_ERROR (-1)
+#  define INVALID_SOCKET (SOCKET)(~0)
+   typedef intptr_t SOCKET;
+#endif
+
+#include "minihttp.h"
+
+#define SOCKETVALID(s) ((s) != INVALID_SOCKET)
+
+#ifdef _MSC_VER
+#  define STRNICMP _strnicmp
+#else
+#  define STRNICMP strncasecmp
+#endif
+
+#ifdef _DEBUG
+#  define traceprint(...) {printf(__VA_ARGS__);}
+#else
+#  define traceprint(...) {}
+#endif
+
+namespace minihttp {
+
+#define DEFAULT_BUFSIZE 4096
+
+inline int _GetError()
+{
+#ifdef _WIN32
+    return WSAGetLastError();
+#else
+    return errno;
+#endif
+}
+
+inline std::string _GetErrorStr(int e)
+{
+#ifdef _WIN32
+    LPTSTR s;
+    ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, e, 0, (LPTSTR)&s, 0, NULL);
+    std::string ret = s;
+    ::LocalFree(s);
+    return ret;
+#endif
+    return strerror(e);
+}
+
+bool InitNetwork()
+{
+#ifdef _WIN32
+    WSADATA wsadata;
+    if(WSAStartup(MAKEWORD(2,2), &wsadata))
+    {
+        traceprint("WSAStartup ERROR: %s", _GetErrorStr(_GetError()).c_str());
+        return false;
+    }
+#endif
+    return true;
+}
+
+void StopNetwork()
+{
+#ifdef _WIN32
+    WSACleanup();
+#endif
+}
+
+static bool _Resolve(const char *host, unsigned int port, struct sockaddr_in *addr)
+{
+    char port_str[15];
+    sprintf(port_str, "%u", port);
+
+    struct addrinfo hnt, *res = 0;
+    memset(&hnt, 0, sizeof(hnt));
+    hnt.ai_family = AF_INET;
+    hnt.ai_socktype = SOCK_STREAM;
+    if (getaddrinfo(host, port_str, &hnt, &res))
+    {
+        traceprint("RESOLVE ERROR: %s", _GetErrorStr(_GetError()).c_str());
+        return false;
+    }
+    if (res)
+    {
+        if (res->ai_family != AF_INET)
+        {
+            traceprint("RESOLVE WTF: %s", _GetErrorStr(_GetError()).c_str());
+            freeaddrinfo(res);
+            return false;
+        }
+        memcpy(addr, res->ai_addr, res->ai_addrlen);
+        freeaddrinfo(res);
+        return true;
+    }
+    return false;
+}
+
+// FIXME: this does currently not handle links like:
+// http://example.com/index.html#pos
+
+bool SplitURI(const std::string& uri, std::string& host, std::string& file, int& port)
+{
+    const char *p = uri.c_str();
+    const char *sl = strstr(p, "//");
+    unsigned int offs = 0;
+    if(sl)
+    {
+        offs = 7;
+        if(strncmp(p, "http://", offs))
+            return false;
+        p = sl + 2;
+    }
+    sl = strchr(p, '/');
+    if(!sl)
+    {
+        host = p;
+        file = "/";
+    }
+    else
+    {
+        host = uri.substr(offs, sl - p);
+        file = sl;
+    }
+
+    port = -1;
+    size_t colon = host.find(':');
+    if(colon != std::string::npos)
+    {
+        port = atoi(host.c_str() + colon);
+        host.erase(port);
+    }
+
+    return true;
+}
+
+static bool _SetNonBlocking(SOCKET s, bool nonblock)
+{
+    if(!SOCKETVALID(s))
+        return false;
+#ifdef _WIN32
+    ULONG tmp = !!nonblock;
+    if(::ioctlsocket(s, FIONBIO, &tmp) == SOCKET_ERROR)
+        return false;
+#else
+    int tmp = ::fcntl(s, F_GETFL);
+    if(tmp < 0)
+        return false;
+    if(::fcntl(s, F_SETFL, nonblock ? (tmp|O_NONBLOCK) : (tmp|=~O_NONBLOCK)) < 0)
+        return false;
+#endif
+    return true;
+}
+
+TcpSocket::TcpSocket()
+: _s(INVALID_SOCKET), _inbuf(NULL), _inbufSize(0), _recvSize(0),
+  _readptr(NULL), _lastport(0)
+{
+}
+
+TcpSocket::~TcpSocket()
+{
+    close();
+    if(_inbuf)
+        free(_inbuf);
+}
+
+bool TcpSocket::isOpen(void)
+{
+    return SOCKETVALID(_s);
+}
+
+void TcpSocket::close(void)
+{
+    if(!SOCKETVALID(_s))
+        return;
+
+    _OnCloseInternal();
+
+#ifdef _WIN32
+    ::closesocket((SOCKET)_s);
+#else
+    ::close(_s);
+#endif
+    _s = INVALID_SOCKET;
+}
+
+void TcpSocket::_OnCloseInternal()
+{
+    _OnClose();
+}
+
+bool TcpSocket::SetNonBlocking(bool nonblock)
+{
+    _nonblocking = nonblock;
+    return _SetNonBlocking(_s, nonblock);
+}
+
+void TcpSocket::SetBufsizeIn(unsigned int s)
+{
+    if(s < 512)
+        s = 512;
+    if(s != _inbufSize)
+        _inbuf = (char*)realloc(_inbuf, s);
+    _inbufSize = s;
+    _writeSize = s - 1;
+    _readptr = _writeptr = _inbuf;
+}
+
+bool TcpSocket::open(const char *host /* = NULL */, unsigned int port /* = 0 */)
+{
+    if(isOpen())
+    {
+        if( (host && host != _host) || (port && port != _lastport) )
+            close();
+            // ... and continue connecting to new host/port
+        else
+            return true; // still connected, to same host and port.
+    }
+
+    sockaddr_in addr;
+
+    if(host)
+        _host = host;
+    else
+        host = _host.c_str();
+
+    if(port)
+        _lastport = port;
+    else
+    {
+        port = _lastport;
+        if(!port)
+            return false;
+    }
+
+    if(!_Resolve(host, port, &addr))
+    {
+        traceprint("RESOLV ERROR: %s\n", _GetErrorStr(_GetError()).c_str());
+        return false;
+    }
+
+    SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
+
+    if(!SOCKETVALID(s))
+    {
+        traceprint("SOCKET ERROR: %s\n", _GetErrorStr(_GetError()).c_str());
+        return false;
+    }
+
+    if (::connect(s, (sockaddr*)&addr, sizeof(sockaddr)))
+    {
+        traceprint("CONNECT ERROR: %s\n", _GetErrorStr(_GetError()).c_str());
+        return false;
+    }
+
+    _SetNonBlocking(s, _nonblocking); // restore setting if it was set in invalid state. static call because _s is intentionally still invalid here.
+    _s = s; // set the socket handle when we are really sure we are connected, and things are set up
+
+    _OnOpen();
+
+    return true;
+}
+
+bool TcpSocket::SendBytes(const char *str, unsigned int len)
+{
+    if(!SOCKETVALID(_s))
+        return false;
+    //traceprint("SEND: '%s'\n", str);
+    return ::send(_s, str, len, 0) >= 0;
+    // TODO: check _GetError()
+}
+
+void TcpSocket::_ShiftBuffer(void)
+{
+    size_t by = _readptr - _inbuf;
+    memmove(_inbuf, _readptr, by);
+    _readptr = _inbuf;
+    _writeptr = _inbuf + by;
+    _writeSize = _inbufSize - by - 1;
+}
+
+void TcpSocket::_OnData()
+{
+    _OnRecv(_readptr, _recvSize);
+}
+
+bool TcpSocket::update(void)
+{
+   if(!_OnUpdate())
+       return false;
+
+   if(!isOpen())
+       return false;
+
+    if(!_inbuf)
+        SetBufsizeIn(DEFAULT_BUFSIZE);
+
+    int bytes = recv(_s, _writeptr, _writeSize, 0); // last char is used as string terminator
+
+    if(bytes > 0) // we received something
+    {
+        _inbuf[bytes] = 0;
+        _recvSize = bytes;
+
+        // reset pointers for next read
+        _writeSize = _inbufSize - 1;
+        _readptr = _writeptr = _inbuf;
+
+        _OnData();
+        return true;
+    }
+    else if(bytes == 0) // remote has closed the connection
+    {
+        _recvSize = 0;
+        close();
+        return true;
+    }
+    else // whoops, error?
+    {
+        int e = _GetError();
+        switch(e)
+        {
+        case ECONNRESET:
+        case ENOTCONN:
+        case ETIMEDOUT:
+#ifdef _WIN32
+        case WSAECONNABORTED:
+        case WSAESHUTDOWN:
+#endif
+            close();
+            break;
+
+        case EWOULDBLOCK:
+#if defined(EAGAIN) && (EWOULDBLOCK != EAGAIN)
+        case EAGAIN: // linux man pages say this can also happen instead of EWOULDBLOCK
+#endif
+            return false;
+        }
+        traceprint("SOCKET UPDATE ERROR: (%d): %s\n", e, _GetErrorStr(e).c_str());
+    }
+    return true;
+}
+
+
+// ==========================
+// ===== HTTP SPECIFIC ======
+// ==========================
+#ifdef MINIHTTP_SUPPORT_HTTP
+
+static void strToLower(std::string& s)
+{
+    std::transform(s.begin(), s.end(), s.begin(), tolower);
+}
+
+HttpSocket::HttpSocket()
+: TcpSocket(),
+_keep_alive(0), _remaining(0), _chunkedTransfer(false), _mustClose(true), _inProgress(false),
+_followRedir(true), _alwaysHandle(false), _status(0)
+{
+}
+
+HttpSocket::~HttpSocket()
+{
+}
+
+void HttpSocket::_OnOpen()
+{
+    TcpSocket::_OnOpen();
+    _chunkedTransfer = false;
+}
+
+void HttpSocket::_OnCloseInternal()
+{
+    if(!IsRedirecting() || _alwaysHandle)
+        _OnClose();
+}
+
+bool HttpSocket::_OnUpdate()
+{
+    if(!TcpSocket::_OnUpdate())
+        return false;
+
+    if(_inProgress && !_chunkedTransfer && !_remaining && _status)
+        _FinishRequest();
+
+    // initiate transfer if queue is not empty, but the socket somehow forgot to proceed
+    if(_requestQ.size() && !_remaining && !_chunkedTransfer && !_inProgress)
+        _DequeueMore();
+
+    return true;
+}
+
+bool HttpSocket::Download(const std::string& url, void *user /* = NULL */)
+{
+    Request req;
+    req.user = user;
+    SplitURI(url, req.host, req.resource, req.port);
+    if(req.port < 0)
+        req.port = 80;
+    return SendGet(req, false);
+}
+
+bool HttpSocket::SendGet(const std::string what, void *user /* = NULL */)
+{
+    Request req(what, _host, _lastport, user);
+    return SendGet(req, false);
+}
+
+bool HttpSocket::QueueGet(const std::string what, void *user /* = NULL */)
+{
+    Request req(what, _host, _lastport, user);
+    return SendGet(req, true);
+}
+
+bool HttpSocket::SendGet(Request& req, bool enqueue)
+{
+    if(req.host.empty() || !req.port)
+        return false;
+
+    std::stringstream r;
+    const char *crlf = "\r\n";
+    r << "GET " << req.resource << " HTTP/1.1" << crlf;
+    r << "Host: " << req.host << crlf;
+    if(_keep_alive)
+    {
+        r << "Connection: Keep-Alive" << crlf;
+        r << "Keep-Alive: " << _keep_alive << crlf;
+    }
+    else
+        r << "Connection: close" << crlf;
+
+    if(_user_agent.length())
+        r << "User-Agent: " << _user_agent << crlf;
+
+    if(_accept_encoding.length())
+        r << "Accept-Encoding: " << _accept_encoding << crlf;
+
+    r << crlf; // header terminator
+
+    req.header = r.str();
+
+    return _EnqueueOrSend(req, enqueue);
+}
+
+bool HttpSocket::_EnqueueOrSend(const Request& req, bool forceQueue /* = false */)
+{
+    if(_inProgress || forceQueue) // do not send while receiving other data
+    {
+        traceprint("HTTP: Transfer pending; putting into queue. Now %u waiting.\n", (unsigned int)_requestQ.size()); // DEBUG
+        _requestQ.push(req);
+        return true;
+    }
+    // ok, we can send directly
+    if(!_OpenRequest(req))
+        return false;
+    _inProgress = SendBytes(req.header.c_str(), req.header.length());
+    return _inProgress;
+}
+
+// called whenever a request is finished completely and the socket checks for more things to send
+void HttpSocket::_DequeueMore(void)
+{
+    _FinishRequest(); // In case this was not done yet.
+
+    // _inProgress is known to be false here
+    if(_requestQ.size()) // still have other requests queued?
+        if(_EnqueueOrSend(_requestQ.front(), false)) // could we send?
+            _requestQ.pop(); // if so, we are done with this request
+
+    // otherwise, we are done for now. socket is kept alive for future sends. Nothing to do.
+}
+
+bool HttpSocket::_OpenRequest(const Request& req)
+{
+    if(_inProgress)
+    {
+        traceprint("HttpSocket::_OpenRequest(): _inProgress == true, should not be called.");
+        return false;
+    }
+    if(!open(req.host.c_str(), req.port))
+        return false;
+    _inProgress = true;
+    _curRequest = req;
+    _status = 0;
+    return true;
+}
+
+void HttpSocket::_FinishRequest(void)
+{
+    if(_inProgress)
+    {
+        if(!IsRedirecting() || _alwaysHandle)
+            _OnRequestDone(); // notify about finished request
+        _inProgress = false;
+        _hdrs.clear();
+    }
+}
+
+void HttpSocket::_ProcessChunk(void)
+{
+    if(!_chunkedTransfer)
+        return;
+
+    unsigned int chunksize = -1;
+
+    while(true)
+    {
+        // less data required until chunk end than received, means the new chunk starts somewhere in the middle
+        // of the received data block. finish this chunk first.
+        if(_remaining)
+        {
+            if(_remaining <= _recvSize) // it contains the rest of the chunk, including CRLF
+            {
+                _OnRecvInternal(_readptr, _remaining - 2); // implicitly skip CRLF
+                _readptr += _remaining;
+                _recvSize -= _remaining;
+                _remaining = 0; // done with this one.
+                if(!chunksize) // and if chunksize was 0, we are done with all chunks.
+                    break;
+            }
+            else // buffer did not yet arrive completely
+            {
+                _OnRecvInternal(_readptr, _recvSize);
+                _remaining -= _recvSize;
+                _recvSize = 0; // done with the whole buffer, but not with the chunk
+                return; // nothing else to do here
+            }
+        }
+
+        // each chunk identifier ends with CRLF.
+        // if we don't find that, we hit the corner case that the chunk identifier was not fully received.
+        // in that case, adjust the buffer and wait for the rest of the data to be appended
+        char *term = strstr(_readptr, "\r\n");
+        if(!term)
+        {
+            if(_recvSize) // if there is still something queued, move it to the left of the buffer and append on next read
+                _ShiftBuffer();
+            return;
+        }
+        term += 2; // skip CRLF
+        
+        // when we are here, the (next) chunk header was completely received.
+        chunksize = strtoul(_readptr, NULL, 16);
+        _remaining = chunksize + 2; // the http protocol specifies that each chunk has a trailing CRLF
+        _recvSize -= (term - _readptr);
+        _readptr = term;
+    }
+
+    if(!chunksize) // this was the last chunk, no further data expected unless requested
+    {
+        _chunkedTransfer = false;
+        _DequeueMore();
+        if(_recvSize)
+            traceprint("_ProcessChunk: There are %u bytes left in the buffer, huh?\n", _recvSize);
+        if(_mustClose)
+            close();
+    }
+}
+
+void HttpSocket::_ParseHeaderFields(const char *s, size_t size)
+{
+    // Field: Entry data\r\n
+
+    const char *maxs = s + size;
+    const char *colon, *entry;
+    const char *entryEnd = s; // last char of entry data
+    while(s < maxs)
+    {
+        while(isspace(*s))
+        {
+            ++s;
+            if(s >= maxs)
+                return;
+        }
+        colon = strchr(s, ':');
+        if(!colon)
+            return;
+        entryEnd = strchr(colon, '\n');
+        if(!entryEnd)
+            return;
+        while(entryEnd[-1] == '\n' || entryEnd[-1] == '\r')
+            --entryEnd;
+        entry = colon + 1;
+        while(isspace(*entry))
+        {
+            ++entry;
+            if(entry > entryEnd) // Field, but no entry? (Field:   \n\r)
+            {
+                s = entryEnd;
+                continue;
+            }
+        }
+        std::string field(s, colon - s);
+        strToLower(field);
+        _hdrs[field] = std::string(entry, entryEnd - entry);
+        s = entryEnd;
+    }
+}
+
+const char *HttpSocket::Hdr(const char *h) const
+{
+    std::map<std::string, std::string>::const_iterator it = _hdrs.find(h);
+    return it == _hdrs.end() ? NULL : it->second.c_str();
+}
+
+static int safeatoi(const char *s)
+{
+    return s ? atoi(s) : 0;
+}
+
+bool HttpSocket::_HandleStatus()
+{
+    _remaining = _contentLen = safeatoi(Hdr("content-length"));
+
+    const char *encoding = Hdr("transfer-encoding");
+    _chunkedTransfer = encoding && !STRNICMP(encoding, "chunked", 7);
+    
+    const char *conn = Hdr("connection"); // if its not keep-alive, server will close it, so we can too
+    _mustClose = !conn || STRNICMP(conn, "keep-alive", 10);
+
+    if(!(_chunkedTransfer || _contentLen) && _status == 200)
+        traceprint("_ParseHeader: Not chunked transfer and content-length==0, this will go fail");
+
+    switch(_status)
+    {
+        case 200:
+            return true;
+
+        case 301:
+        case 302:
+        case 303:
+        case 307:
+        case 308:
+            if(_followRedir)
+                if(const char *loc = Hdr("location"))
+                {
+                    traceprint("Following HTTP redirect to: %s\n", loc);
+                    Download(loc, _curRequest.user);
+                }
+            return false;
+
+        default:
+            return false;
+    }
+}
+
+bool HttpSocket::IsRedirecting() const
+{
+	switch(_status)
+	{
+		case 301:
+		case 302:
+		case 303:
+		case 307:
+		case 308:
+			return true;
+	}
+	return false;
+}
+
+
+void HttpSocket::_ParseHeader(void)
+{
+    _tmpHdr += _inbuf;
+    const char *hptr = _tmpHdr.c_str();
+
+    if((_recvSize >= 5 || _tmpHdr.size() >= 5) && memcmp("HTTP/", hptr, 5))
+    {
+        traceprint("_ParseHeader: not HTTP stream\n");
+        return;
+    }
+
+    const char *hdrend = strstr(hptr, "\r\n\r\n");
+    if(!hdrend)
+    {
+        traceprint("_ParseHeader: could not find end-of-header marker, or incomplete buf; delaying.\n");
+        return;
+    }
+
+    //traceprint(hptr);
+
+    hptr = strchr(hptr + 5, ' '); // skip "HTTP/", already known
+    if(!hptr)
+        return; // WTF?
+    ++hptr; // number behind first space is the status code
+    _status = atoi(hptr);
+
+    // Default values
+    _chunkedTransfer = false;
+    _contentLen = 0; // yet unknown
+
+    hptr = strstr(hptr, "\r\n");
+    _ParseHeaderFields(hptr + 2, hdrend - hptr);
+
+    // FIXME: return value indicates success.
+    // Bail out on non-success, or at least make it so that _OnRecv() is not called.
+    // (Unless an override bool is given that even non-successful answers get their data delivered!)
+    _HandleStatus();
+
+    // get ready
+    _readptr = strstr(_inbuf, "\r\n\r\n") + 4; // skip double newline. must have been found in hptr earlier.
+    _recvSize -= (_readptr - _inbuf); // skip the header part
+    _tmpHdr.clear();
+}
+
+// generic http header parsing
+void HttpSocket::_OnData(void)
+{
+    if(!(_chunkedTransfer || (_remaining && _recvSize)))
+        _ParseHeader();
+
+    if(_chunkedTransfer)
+    {
+        _ProcessChunk(); // first, try to finish one or more chunks
+    }
+    else if(_remaining && _recvSize) // something remaining? if so, we got a header earlier, but not all data
+    {
+        _remaining -= _recvSize;
+        _OnRecvInternal(_readptr, _recvSize);
+
+        if(int(_remaining) < 0)
+        {
+            traceprint("_OnRecv: _remaining wrap-around, huh??\n");
+            _remaining = 0;
+        }
+        if(!_remaining) // received last block?
+        {
+            if(_mustClose)
+                close();
+            else
+                _DequeueMore();
+        }
+
+        // nothing else to do here.
+    }
+
+    // otherwise, the server sent just the header, with the data following in the next packet
+}
+
+void HttpSocket::_OnClose()
+{
+    if(!ExpectMoreData())
+        _FinishRequest();
+}
+
+void HttpSocket::_OnRecvInternal(char *buf, unsigned int size)
+{
+    if(_status == 200 || _alwaysHandle)
+        _OnRecv(buf, size);
+}
+
+#endif
+
+// ===========================
+// ===== SOCKET SET ==========
+// ===========================
+#ifdef MINIHTTP_SUPPORT_SOCKET_SET
+
+SocketSet::~SocketSet()
+{
+    deleteAll();
+}
+
+void SocketSet::deleteAll(void)
+{
+    for(Store::iterator it = _store.begin(); it != _store.end(); ++it)
+        delete it->first;
+    _store.clear();
+}
+
+bool SocketSet::update(void)
+{
+    bool interesting = false;
+    Store::iterator it = _store.begin();
+    for( ; it != _store.end(); )
+    {
+        TcpSocket *sock =  it->first;
+        SocketSetData& sdata = it->second;
+        interesting = sock->update() || interesting;
+        if(sdata.deleteWhenDone && !sock->isOpen() && !sock->HasPendingTask())
+        {
+            delete sock;
+            _store.erase(it++);
+        }
+        else
+           ++it;
+    }
+    return interesting;
+}
+
+void SocketSet::remove(TcpSocket *s)
+{
+    _store.erase(s);
+}
+
+void SocketSet::add(TcpSocket *s, bool deleteWhenDone /* = true */)
+{
+    s->SetNonBlocking(true);
+    SocketSetData sdata;
+    sdata.deleteWhenDone = deleteWhenDone;
+    _store[s] = sdata;
+}
+
+#endif
+
+
+} // namespace minihttp

File ExternalLibs/minihttp.h

+/* This program is free software. It comes without any warranty, to
+* the extent permitted by applicable law. You can redistribute it
+* and/or modify it under the terms of the Do What The Fuck You Want
+* To Public License, Version 2, as published by Sam Hocevar.
+* See http://sam.zoy.org/wtfpl/COPYING for more details. */
+
+#ifndef MINIHTTPSOCKET_H
+#define MINIHTTPSOCKET_H
+
+
+// ---- Compile config -----
+#define MINIHTTP_SUPPORT_HTTP
+#define MINIHTTP_SUPPORT_SOCKET_SET
+// -------------------------
+
+
+
+#include <string>
+
+namespace minihttp
+{
+
+bool InitNetwork();
+void StopNetwork();
+
+bool SplitURI(const std::string& uri, std::string& host, std::string& file, int& port);
+
+
+class TcpSocket
+{
+public:
+    TcpSocket();
+    virtual ~TcpSocket();
+
+    virtual bool HasPendingTask() const { return false; }
+
+    bool open(const char *addr = NULL, unsigned int port = 0);
+    void close();
+    bool update(); // returns true if something interesting happened (incoming data, closed connection, etc)
+
+    bool isOpen(void);
+
+    void SetBufsizeIn(unsigned int s);
+    bool SetNonBlocking(bool nonblock);
+    unsigned int GetBufSize() { return _inbufSize; }
+    const char *GetHost(void) { return _host.c_str(); }
+    bool SendBytes(const char *str, unsigned int len);
+
+protected:
+    virtual void _OnCloseInternal();
+    virtual void _OnData(); // data received callback. Internal, should only be overloaded to call _OnRecv()
+
+    virtual void _OnRecv(char *buf, unsigned int size) = 0;
+    virtual void _OnClose() {}; // close callback
+    virtual void _OnOpen() {} // called when opened
+    virtual bool _OnUpdate() { return true; } // called before reading from the socket
+
+    void _ShiftBuffer();
+
+    char *_inbuf;
+    char *_readptr; // part of inbuf, optionally skipped header
+    char *_writeptr; // passed to recv(). usually equal to _inbuf, but may point inside the buffer in case of a partial transfer.
+
+    unsigned int _inbufSize; // size of internal buffer
+    unsigned int _writeSize; // how many bytes can be written to _writeptr;
+    unsigned int _recvSize; // incoming size, max _inbufSize - 1
+
+    unsigned int _lastport; // port used in last open() call
+
+    bool _nonblocking; // Default true. If false, the current thread is blocked while waiting for input.
+
+    intptr_t _s; // socket handle. really an int, but to be sure its 64 bit compatible as it seems required on windows, we use this.
+
+    std::string _host;
+};
+
+} // end namespace minihttp
+
+
+// ------------------------------------------------------------------------
+
+#ifdef MINIHTTP_SUPPORT_HTTP
+
+#include <map>
+#include <queue>
+
+namespace minihttp
+{
+
+enum HttpCode
+{
+    HTTP_OK = 200,
+    HTTP_NOTFOUND = 404,
+};
+
+struct Request
+{
+    Request() : port(80), user(NULL) {}
+    Request(const std::string& h, const std::string& res, int p = 80, void *u = NULL)
+        : host(h), resource(res), port(80), user(u) {}
+
+    std::string host;
+    std::string header; // set by socket
+    std::string resource;
+    int port;
+    void *user;
+};
+
+class HttpSocket : public TcpSocket
+{
+public:
+
+    HttpSocket();
+    virtual ~HttpSocket();
+
+    virtual bool HasPendingTask() const
+    {
+        return ExpectMoreData() || _requestQ.size();
+    }
+
+    void SetKeepAlive(unsigned int secs) { _keep_alive = secs; }
+    void SetUserAgent(const std::string &s) { _user_agent = s; }
+    void SetAcceptEncoding(const std::string& s) { _accept_encoding = s; }
+    void SetFollowRedirect(bool follow) { _followRedir = follow; }
+    void SetAlwaysHandle(bool h) { _alwaysHandle = h; }
+
+    bool Download(const std::string& url, void *user = NULL);
+    bool SendGet(Request& what, bool enqueue);
+    bool SendGet(const std::string what, void *user = NULL);
+    bool QueueGet(const std::string what, void *user = NULL);
+
+    unsigned int GetRemaining() const { return _remaining; }
+
+    unsigned int GetStatusCode() const { return _status; }
+    unsigned int GetContentLen() const { return _contentLen; }
+    bool ChunkedTransfer() const { return _chunkedTransfer; }
+    bool ExpectMoreData() const { return _remaining || _chunkedTransfer; }
+
+    const Request &GetCurrentRequest() const { return _curRequest; }
+    const char *Hdr(const char *h) const;
+
+    bool IsRedirecting() const;
+
+protected:
+    virtual void _OnCloseInternal();
+    virtual void _OnClose();
+    virtual void _OnData(); // data received callback. Internal, should only be overloaded to call _OnRecv()
+    virtual void _OnRecv(char *buf, unsigned int size) = 0;
+    virtual void _OnOpen(); // called when opene
+    virtual bool _OnUpdate(); // called before reading from the socket
+
+    // new ones:
+    virtual void _OnRequestDone() {}
+
+    void _ProcessChunk();
+    bool _EnqueueOrSend(const Request& req, bool forceQueue = false);
+    void _DequeueMore();
+    bool _OpenRequest(const Request& req);
+    void _ParseHeader();
+    void _ParseHeaderFields(const char *s, size_t size);
+    bool _HandleStatus(); // Returns whether the processed request was successful, or not
+    void _FinishRequest();
+    void _OnRecvInternal(char *buf, unsigned int size);
+
+    std::string _user_agent;
+    std::string _accept_encoding; // Default empty.
+    std::string _tmpHdr; // used to save the http header if the incoming buffer was not large enough
+
+    unsigned int _keep_alive; // http related
+    unsigned int _remaining; // http "Content-Length: X" - already recvd. 0 if ready for next packet.
+                             // For chunked transfer encoding, this holds the remaining size of the current chunk
+    unsigned int _contentLen; // as reported by server
+    unsigned int _status; // http status code, HTTP_OK if things are good
+
+    std::queue<Request> _requestQ;
+    std::map<std::string, std::string> _hdrs; // Maps HTTP header fields to their values
+
+    Request _curRequest;
+
+    bool _inProgress;
+    bool _chunkedTransfer;
+    bool _mustClose; // keep-alive specified, or not
+    bool _followRedir; // Default true. Follow 3xx redirects if this is set.
+    bool _alwaysHandle; // Also deliver to _OnRecv() if a non-success code was received.
+};
+
+} // end namespace minihttp
+
+#endif
+
+// ------------------------------------------------------------------------
+
+#ifdef MINIHTTP_SUPPORT_SOCKET_SET
+
+#include <map>
+
+namespace minihttp
+{
+
+class SocketSet
+{
+public:
+    virtual ~SocketSet();
+    void deleteAll();
+    bool update();
+    void add(TcpSocket *s, bool deleteWhenDone = true);
+    bool has(TcpSocket *s);
+    void remove(TcpSocket *s);
+    inline size_t size() { return _store.size(); }
+
+//protected:
+
+    struct SocketSetData
+    {
+        bool deleteWhenDone;
+        // To be extended
+    };
+    
+    typedef std::map<TcpSocket*, SocketSetData> Store;
+
+    Store _store;
+};
+
+#endif
+
+
+} // end namespace minihttp
+
+
+#endif

File ExternalLibs/ttvfs/CMakeLists.txt

+
+set(ttvfs_SRC
+    VFS.h
+    VFSArchiveLoader.h
+    VFSAtomic.cpp
+    VFSAtomic.h
+    VFSBase.cpp
+    VFSBase.h
+    VFSDefines.h
+    VFSDir.cpp
+    VFSDir.h
+    VFSFile.cpp
+    VFSFile.h
+    VFSFileFuncs.cpp
+    VFSFileFuncs.h
+    VFSHashmap.h
+    VFSHelper.cpp
+    VFSHelper.h
+    VFSInternal.h
+    VFSLoader.cpp
+    VFSLoader.h
+    VFSSelfRefCounter.h
+    VFSSystemPaths.cpp
+    VFSSystemPaths.h
+    VFSTools.cpp
+    VFSTools.h
+)
+
+add_library(ttvfs ${ttvfs_SRC})

File ExternalLibs/ttvfs/VFS.h

+/* ttvfs -- tiny tree virtual file system
+
+// VFS.h - all the necessary includes to get a basic VFS working
+// Only include externally, not inside the library.
+
+See VFSDefines.h for compile configration.
+
+
+---------[ License ]----------
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+
+#ifndef TTVFS_VFS_H
+#define TTVFS_VFS_H
+
+#include "VFSDefines.h"
+
+VFS_NAMESPACE_START
+bool _checkCompatInternal(bool large, bool nocase, bool hashmap, unsigned int vfspos_size);
+
+/** It is recommended to call this function early in your code
+    and ensure it returns true - if it does not, compiler settings
+    are inconsistent, which may cause otherwise hard to detect problems. */
+inline static bool checkCompat(void)
+{
+#ifdef VFS_LARGEFILE_SUPPORT
+    bool largefile = true;
+#else
+    bool largefile = false;
+#endif
+
+#ifdef VFS_IGNORE_CASE
+    bool nocase = true;
+#else
+    bool nocase = false;
+#endif
+
+#ifdef VFS_USE_HASHMAP
+    bool hashmap = true;
+#else
+    bool hashmap = false;
+#endif
+    return _checkCompatInternal(largefile, nocase, hashmap, sizeof(vfspos));
+}
+VFS_NAMESPACE_END
+
+
+#include <cstring>
+#include <string>
+#include "VFSHelper.h"
+#include "VFSFile.h"
+#include "VFSDir.h"
+#include "VFSSystemPaths.h"
+
+
+// Check to enforce correct including.
+#ifdef VFS_INTERNAL_H
+#error Oops, VFS_INTERNAL_H is defined, someone messed up and included VFSInternal.h wrongly.
+#endif
+
+#endif

File ExternalLibs/ttvfs/VFSArchiveLoader.h

+#ifndef VFS_ARCHIVE_LOADER_H
+#define VFS_ARCHIVE_LOADER_H
+
+#include "VFSDefines.h"
+
+VFS_NAMESPACE_START
+
+class VFSDir;
+class VFSFile;
+class VFSLoader;
+
+// Generic Archive loader interface that is supposed to return a valid VFSDir pointer when it
+// was able to load 'arch' as an archive, and NULL if there was an error or the loader is
+// unable to load that file type.
+// 'asSubdir' - if this is true, the archive will be accessible as a folder (as in "test.zip/file.dat"),
+//              otherwise the files are mounted in place of the archive.
+// 'ldr' - can be set to an external file loader in case the file/folder tree can not be fully generated
+//         at load time.
+// 'opaque' - a POD struct which *must* have a `void (*)(void*, void*, const char*)`
+//            function pointer as first member (at offset 0).
+//            The struct pointer is then passed to that function, along with a pointer to an internal object,
+//            whatever this is. The derived loader knows what this object is - something that this callback
+//            will be interested in modifying, anyways. The rest of the struct can carry the data required to
+//            modify this object.
+//            The const char parameter is a string unique for each loader (to prevent accessing the pointer
+//            in a wrong way by the wrong loader). Example below.
+class VFSArchiveLoader
+{
+public:
+    virtual ~VFSArchiveLoader() {}
+
+    virtual VFSDir *Load(VFSFile *arch, VFSLoader **ldr, void *opaque = NULL) = 0;
+};
+
+/* A possible struct for 'opaque' would be:
+
+struct ExampleLoadParams
+{
+    void (*callback)(void *, void *, const char *);
+    const char *key;
+    unsigned int keylen;
+};
+
+And then the call would look like:
+(Assuming PakFile is an archive file class that represents an archive)
+
+void pakSetup(void *data, void *arch, const char *id)
+{
+    if(strcmp(id, "pak")) // Be sure we're in the right loader.
+        return;
+    ExampleLoadParams *pm = (ExampleLoadParams*)p;
+    PakArchive *pak = (PakArchive*)arch;
+    pak->SetKey(pm->key, pm->keylen);
+}
+
+ExampleLoadParams p;
+p.callback = &pakSetup;
+p.key = "123456";
+p.keylen = 6;
+vfs.AddArchive("data.pak", false, "", &p);
+
+Where p in turn will be passed to the PAK loader.
+
+*/
+
+VFS_NAMESPACE_END
+
+#endif

File ExternalLibs/ttvfs/VFSAtomic.cpp

+// VFSAtomic.cpp - atomic operations and thread locking
+// For conditions of distribution and use, see copyright notice in VFS.h
+
+/** --- Atomic operations and thread safety ---
+  * You may want to add your own implementation if thread safety is needed.
+  * If not, just leave everything like it is.
+
+  * If you are on windows, Interlocked[In/De]crement is faster than
+    explicit mutex locking for integer operations.
+
+  * TODO: The actual locking that is done in the tree when VFS_THREADSAFE is defined
+    is rather crude for the time beeing; a somewhat more efficient ReadWriteLock
+    implementation would be nice to have, someday.
+
+  * If you can, leave VFS_THREADSAFE undefined and do the locking externally,
+    it will probably have much better performance than if each and every operation
+    does a lock and unlock call.
+    (For a rather I/O based library this should not really make a difference, anyway.
+    But don't say you haven't been warned :) )
+*/
+
+#include "VFSInternal.h"
+#include "VFSAtomic.h"
+
+// for Interlocked[In/De]crement, if required
+#if defined(_WIN32) && defined(VFS_THREADSAFE)
+#    define WIN32_LEAN_AND_MEAN
+#    include <windows.h>
+#endif
+
+VFS_NAMESPACE_START
+
+#ifdef VFS_THREADSAFE
+static Mutex mtx;
+#endif
+
+int Atomic_Incr(volatile int &i)
+{
+#ifdef VFS_THREADSAFE
+#   ifdef _WIN32
+        volatile LONG* dp = (volatile LONG*) &i;
+        return InterlockedIncrement( dp );
+#   else
+        Guard g(mtx);
+#   endif
+#endif
+    return ++i;
+}
+
+int Atomic_Decr(volatile int &i)
+{
+#ifdef VFS_THREADSAFE
+#   ifdef _WIN32
+        volatile LONG* dp = (volatile LONG*) &i;
+        return InterlockedDecrement( dp );
+#   else
+        Guard g(mtx);
+#   endif
+#endif
+    return --i;
+}
+
+/* Implement your Mutex class here.
+   Important: The mutex must be re-entrant/recursive,
+   means it must be possible to lock it from the same thread multiple times.
+*/
+Mutex::Mutex()
+{
+    // implement your own if needed. Remove the trap below when you are done.
+    // This is to prevent people from defining VFS_THREADSAFE and expecting everything to work just like that :)
+#ifdef VFS_THREADSAFE
+#error VFSAtomic: Hey, you forgot to implement the mutex class, cant guarantee thread safety! Either undef VFS_THREADSAFE or read the docs and get your hands dirty.
+#endif
+}
+
+Mutex::~Mutex()
+{
+    // implement your own if needed
+}
+
+void Mutex::Lock(void)
+{
+    // implement your own if needed
+}
+
+void Mutex::Unlock(void)
+{
+    // implement your own if needed
+}
+
+Guard::Guard(Mutex& m)
+: _m(m)
+{
+    _m.Lock();
+}
+
+Guard::~Guard()
+{
+    _m.Unlock();
+}
+
+
+VFS_NAMESPACE_END

File ExternalLibs/ttvfs/VFSAtomic.h

+// VFSAtomic.h - atomic operations and thread locking
+// For conditions of distribution and use, see copyright notice in VFS.h
+
+#ifndef VFS_ATOMIC_H
+#define VFS_ATOMIC_H
+
+#include "VFSDefines.h"
+
+VFS_NAMESPACE_START
+
+int Atomic_Incr(volatile int &i);
+int Atomic_Decr(volatile int &i);
+
+// generic Mutex class, needs to be reentrant/recursive.
+class Mutex
+{
+public:
+    Mutex();
+    ~Mutex();
+    void Lock();
+    void Unlock();
+
+protected:
+    // add own stuff if needed
+};
+
+class Guard
+{
+public:
+    Guard(Mutex& m);
+    ~Guard();
+
+protected:
+    Mutex& _m;
+};
+
+VFS_NAMESPACE_END
+
+#endif

File ExternalLibs/ttvfs/VFSBase.cpp

+// VFSBase.cpp - common code for VFSDir and VFSFile
+// For conditions of distribution and use, see copyright notice in VFS.h
+
+#include "VFSBase.h"
+#include "VFSInternal.h"
+#include "VFSTools.h"
+
+VFS_NAMESPACE_START
+
+VFSBase::VFSBase()
+: ref(this)
+#ifdef VFS_USE_HASHMAP
+  , _hash(0)
+#endif
+  , _origin(NULL)
+{
+}
+
+// call this only with a lock held!
+void VFSBase::_setName(const char *n)
+{
+    if(!n)
+        return;
+    _fullname = FixPath(n);
+    _name = PathToFileName(_fullname.c_str());
+#ifdef VFS_USE_HASHMAP
+    _hash = STRINGHASH(_name);
+#endif
+}
+
+
+VFS_NAMESPACE_END

File ExternalLibs/ttvfs/VFSBase.h

+// VFSBase.h - bass class for VFSDir and VFSFile
+// For conditions of distribution and use, see copyright notice in VFS.h
+
+#ifndef VFS_BASE_H
+#define VFS_BASE_H
+
+#include <string>
+#include "VFSDefines.h"
+#include "VFSSelfRefCounter.h"
+
+VFS_NAMESPACE_START
+
+// Used internally. No special properties, just holds some common code.
+class VFSBase
+{
+public:
+    virtual ~VFSBase() {}
+
+    /** Returns the plain file name. Never NULL. */
+    inline const char *name() const { VFS_GUARD_OPT(this); return _name; }
+
+    /** Returns the file name with full path. Never NULL. */
+    inline const char *fullname() const { VFS_GUARD_OPT(this); return _fullname.c_str(); }
+
+    /** To avoid strlen() */
+    inline size_t fullnameLen() const { VFS_GUARD_OPT(this); return _fullname.length(); }
+    // We know that mem addr of _name > _fullname:
+    // _fullname: "abc/def/ghi/hjk.txt" (length = 19)
+    // _name:                 "hjk.txt" <-- want that length
+    // ptr diff: 12
+    // so in total: 19 - 12 == 7
+    inline size_t nameLen() const { VFS_GUARD_OPT(this); return _fullname.length() - (_name - _fullname.c_str()); }
+
+    /** Basic RTTI, for debugging purposes */
+    virtual const char *getType() const { return "<BASE>"; }
+
+    /** Can be overloaded to close resources this object keeps open */
+    virtual bool close() { return true; }
+
+    /** Returns an object this object depends on. (used internally, by extensions) */
+    inline VFSBase *getOrigin() const { return _origin; }
+
+    inline void lock() const { _mtx.Lock(); }
+    inline void unlock() const { _mtx.Unlock(); }
+    inline Mutex& mutex() const { return _mtx; }
+
+#ifdef VFS_USE_HASHMAP
+    inline size_t hash() const { return _hash; }
+#endif
+
+    // For internal use
+    inline void _setOrigin(VFSBase *origin) { _origin = origin; }
+
+protected:
+    VFSBase();
+    void _setName(const char *n);
+
+private:
+
+#ifdef VFS_USE_HASHMAP
+    size_t _hash;
+#endif
+
+    const char *_name; // must point to an address constant during object lifetime (like _fullname.c_str() + N)
+                       // (not necessary to have an additional string copy here, just wastes memory)
+    std::string _fullname;
+
+    mutable Mutex _mtx;
+
+    VFSBase *_origin; // May store a pointer if necessary. NOT ref-counted, because this would create cycles in almost all cases.
+
+public:
+
+    /** Reference count, if the pointer to this file is stored somewhere it is advisable to increase
+    (ref++) it. If it reaches 0, this file is deleted automatically. */
+    SelfRefCounter<VFSBase> ref;
+};
+
+
+
+VFS_NAMESPACE_END
+
+#endif

File ExternalLibs/ttvfs/VFSDefines.h

+// VFSDefines.h - compile config and basic setup
+// For conditions of distribution and use, see copyright notice in VFS.h
+
+#ifndef VFS_DEFINES_H
+#define VFS_DEFINES_H
+
+/* --- Config section -- modify as needed --- */
+
+// choose a namespace name, or comment out to disable namespacing completely (not recommended)
+#define VFS_NAMESPACE ttvfs
+
+// Define this to allow dealing with files > 4 GB, using non-standard functions.
+// This may or may not work with your platform/compiler, good luck.
+//#define VFS_LARGEFILE_SUPPORT
+
+// Define this to make all operations case insensitive.
+// Windows systems generally don't care much, but for Linux and Mac this can be used
+// to get the same behavior as on windows.
+// Additionally, this achieves full case insensitivity within the library,
+// if the the same files are accessed multiple times by the program, but with not-uniform case.
+// (no sane programmer should do this, anyway).
+// However, on non-windows systems this will decrease performance when checking for files
+// on disk (see VFSLoader.cpp).
+#define VFS_IGNORE_CASE
+
+// Define this to make all VFSFile, VFSDir, VFSHelper operations thread-safe.
+// If you do, do not forget to add your own implementation to VFSAtomic.cpp/.h !
+// If this is not defined, you can still do manual locking if you know what you're doing,
+// performance matters, and you implemented actual locking into the Mutex class.
+// If no Mutex implementation is provided, its operations are no-ops, beware!
+// Note: This adds a *lot* of overhead. Better ensure thread safety yourself, externally. Really!
+//#define VFS_THREADSAFE
+
+// By default, ttvfs uses a std::map to store stuff.
+// Uncomment the line below to use an (experimental!) hashmap.
+// With std::map, iterating over entries will always deliver them in sorted order.
+// The hashmap will deliver entries in random order, but lookup will be much faster
+// (especially if VFS_IGNORE_CASE is set!)
+//#define VFS_USE_HASHMAP
+
+// These are used for small, temporary memory allocations that can remain on the stack.
+// If alloca is available, this is the preferred way.
+#include <stdlib.h>
+#ifdef _WIN32
+#  include <malloc.h> // MSVC/MinGW still need this for alloca. Seems to be windows-specific failure
+#endif
+#define VFS_STACK_ALLOC(size) alloca(size)
+#define VFS_STACK_FREE(ptr)   /* no need to free anything here */
+// Fail-safe:
+//#define VFS_STACK_ALLOC(size) malloc(size)
+//#define VFS_STACK_FREE(ptr)  free(ptr)
+
+/* --- End of config section --- */
+
+
+#ifdef VFS_NAMESPACE
+#    define VFS_NAMESPACE_START namespace VFS_NAMESPACE {
+#    define VFS_NAMESPACE_END }
+#    define VFS_NAMESPACE_IMPL VFS_NAMESPACE::
+#else
+#    define VFS_NAMESPACE_START
+#    define VFS_NAMESPACE_END
+#    define VFS_NAMESPACE_IMPL
+#endif
+
+VFS_NAMESPACE_START
+
+#ifdef VFS_LARGEFILE_SUPPORT
+#    if defined(_MSC_VER)
+         typedef __int64           vfspos;
+#    else
+         typedef long long         vfspos;
+#    endif
+#else
+    typedef unsigned int           vfspos;
+#endif
+
+// simple guard wrapper, works also if VFS_THREADSAFE is not defined
+#define VFS_GUARD(obj) VFS_NAMESPACE_IMPL Guard __vfs_stack_guard((obj)->mutex())
+
+// defines for optional auto-locking; only if VFS_THREADSAFE is defined
+#ifdef VFS_THREADSAFE
+#    define VFS_GUARD_OPT(obj) VFS_GUARD(obj)
+#else
+#    define VFS_GUARD_OPT(obj)
+#endif
+
+#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
+#    define VFS_STRICMP stricmp
+#else
+#    define VFS_STRICMP strcasecmp
+#endif
+
+static const vfspos npos = vfspos(-1);
+
+typedef void *(*allocator_func)(size_t);
+typedef void (*delete_func)(void*);
+
+VFS_NAMESPACE_END
+
+#endif

File ExternalLibs/ttvfs/VFSDir.cpp

+// VFSDir.cpp - basic directory interface + classes
+// For conditions of distribution and use, see copyright notice in VFS.h
+
+#include <set>
+
+#include "VFSInternal.h"
+#include "VFSTools.h"
+#include "VFSFile.h"
+#include "VFSDir.h"
+
+VFS_NAMESPACE_START
+
+
+VFSDir::VFSDir(const char *fullpath)
+{
+    _setName(fullpath);
+}
+
+VFSDir::~VFSDir()
+{
+    for(Files::iterator it = _files.begin(); it != _files.end(); ++it)
+        it->second.ptr->ref--;
+    for(Dirs::iterator it = _subdirs.begin(); it != _subdirs.end(); ++it)
+        it->second.ptr->ref--;
+}
+
+unsigned int VFSDir::load(bool recursive)
+{
+    return 0;
+}
+
+VFSDir *VFSDir::createNew(const char *dir) const
+{
+    VFSDir *vd = new VFSDir(dir);
+    vd->_setOrigin(getOrigin());
+    return vd;
+}
+
+bool VFSDir::add(VFSFile *f, bool overwrite, EntryFlags flag)
+{
+    if(!f)
+        return false;
+
+    VFS_GUARD_OPT(this);
+
+    Files::iterator it = _files.find(f->name());
+    
+    if(it != _files.end())
+    {
+        if(overwrite)
+        {
+            VFSFile *oldf = it->second.ptr;
+            if(oldf == f)
+                return false;
+
+            _files.erase(it);
+            oldf->ref--;
+        }
+        else
+            return false;
+    }
+
+    f->ref++;
+    _files[f->name()] = MapEntry<VFSFile>(f, flag);
+    return true;
+}
+
+bool VFSDir::addRecursive(VFSFile *f, bool overwrite, EntryFlags flag)
+{
+    if(!f)
+        return false;
+
+    VFS_GUARD_OPT(this);
+
+    // figure out directory from full file name
+    VFSDir *vdir;
+    size_t prefixLen = f->fullnameLen() - f->nameLen();
+    if(prefixLen)
+    {
+        char *dirname = (char*)VFS_STACK_ALLOC(prefixLen);
+        --prefixLen; // -1 to strip the trailing '/'. That's the position where to put the terminating null byte.
+        memcpy(dirname, f->fullname(), prefixLen); // copy trailing null byte
+        dirname[prefixLen] = 0;
+        vdir = getDir(dirname, true);
+        VFS_STACK_FREE(dirname);
+    }
+    else
+        vdir = this;
+
+    return vdir->add(f, true, flag);
+}
+
+bool VFSDir::merge(VFSDir *dir, bool overwrite, EntryFlags flag)
+{
+    if(!dir)
+        return false;
+    if(dir == this)
+        return true; // nothing to do then
+
+    bool result = false;
+    VFS_GUARD_OPT(this);
+
+    for(Files::iterator it = dir->_files.begin(); it != dir->_files.end(); ++it)
+        result = add(it->second.ptr, overwrite, flag) || result;
+
+    for(Dirs::iterator it = dir->_subdirs.begin(); it != dir->_subdirs.end(); ++it)
+        result = insert(it->second.ptr, overwrite, flag) || result;
+    return result;
+}
+
+bool VFSDir::insert(VFSDir *subdir, bool overwrite, EntryFlags flag)
+{
+    if(!subdir)
+        return false;
+
+    VFS_GUARD_OPT(this);
+
+    // With load() cleaning up the tree, it is ok to add subsequent VFSDirs directly.
+    // This will be very useful at some point when files can be mounted into a directory
+    // belonging to an archive, and this way adding files to the archive.
+    Dirs::iterator it = _subdirs.find(subdir->name());
+    if(it == _subdirs.end())
+    {
+        subdir->ref++;
+        _subdirs[subdir->name()] = MapEntry<VFSDir>(subdir, flag);
+        return true;
+    }
+    else
+    {
+        it->second.ptr->merge(subdir, overwrite, flag);
+        return false;
+    }
+}
+
+VFSFile *VFSDir::getFile(const char *fn)
+{
+    while(fn[0] == '.' && fn[1] == '/') // skip ./
+        fn += 2;
+
+    char *slashpos = (char *)strchr(fn, '/');
+
+    // if there is a '/' in the string, descend into subdir and continue there
+    if(slashpos)
+    {
+        // "" (the empty directory name) is allowed, so we can't return 'this' when hitting an empty string the first time.
+        // This whole mess is required for absolute unix-style paths ("/home/foo/..."),
+        // which, to integrate into the tree properly, sit in the root directory's ""-subdir.
+        // FIXME: Bad paths (double-slashes and the like) need to be normalized elsewhere, currently!
+
+        size_t len = strlen(fn);
+        char *dup = (char*)VFS_STACK_ALLOC(len + 1);
+        memcpy(dup, fn, len + 1); // also copy the null-byte
+        slashpos = dup + (slashpos - fn); // use direct offset, not to have to recount again the first time
+        VFSDir *subdir = this;
+        const char *ptr = dup;
+        Dirs::iterator it;
+        VFS_GUARD_OPT(this);
+
+        goto pos_known;
+        do
+        {
+            ptr = slashpos + 1;
+            while(ptr[0] == '.' && ptr[1] == '/') // skip ./
+                ptr += 2;
+            slashpos = (char *)strchr(ptr, '/');
+            if(!slashpos)
+                break;
+
+        pos_known:
+            *slashpos = 0;
+            it = subdir->_subdirs.find(ptr);
+            if(it != subdir->_subdirs.end())
+                subdir = it->second.ptr; // found it
+            else
+                subdir = NULL; // bail out
+        }
+        while(subdir);
+        VFS_STACK_FREE(dup);
+        if(!subdir)
+            return NULL;
+        // restore pointer into original string, by offset
+        ptr = fn + (ptr - dup);
+
+        Files::iterator ft = subdir->_files.find(ptr);
+        return ft != subdir->_files.end() ? ft->second.ptr : NULL;
+    }
+
+    // no subdir? file must be in this dir now.
+    VFS_GUARD_OPT(this);
+    Files::iterator it = _files.find(fn);
+    return it != _files.end() ? it->second.ptr : NULL;
+}
+
+VFSDir *VFSDir::getDir(const char *subdir, bool forceCreate /* = false */)
+{
+    if(!subdir[0] || (subdir[0] == '.' && (!subdir[1] || subdir[1] == '/'))) // empty string or "." or "./" ? use this.
+        return this;
+
+    VFSDir *ret = NULL;
+    char *slashpos = (char *)strchr(subdir, '/');
+
+    // if there is a '/' in the string, descend into subdir and continue there
+    if(slashpos)
+    {
+        // from a/b/c, cut out the a, without the trailing '/'.
+        const char *sub = slashpos + 1;
+        size_t copysize = slashpos - subdir;
+        char * const t = (char*)VFS_STACK_ALLOC(copysize + 1);
+        memcpy(t, subdir, copysize);
+        t[copysize] = 0;
+        VFS_GUARD_OPT(this);
+        Dirs::iterator it = _subdirs.find(t);
+        if(it != _subdirs.end())
+        {
+            ret = it->second.ptr->getDir(sub, forceCreate); // descend into subdirs
+        }
+        else if(forceCreate)
+        {
+            // -> newname = fullname() + '/' + t
+            size_t fullLen = fullnameLen();
+            VFSDir *ins;
+            if(fullLen)
+            {
+                char * const newname = (char*)VFS_STACK_ALLOC(fullLen + copysize + 2);
+                char *ptr = newname;
+                memcpy(ptr, fullname(), fullLen);
+                ptr += fullLen;
+                *ptr++ = '/';
+                memcpy(ptr, t, copysize);
+                ptr[copysize] = 0;
+                ins = createNew(newname);
+                VFS_STACK_FREE(newname);
+            }
+            else
+                ins = createNew(t);
+
+            _subdirs[ins->name()] = ins;
+            ret = ins->getDir(sub, true); // create remaining structure
+        }
+    }
+    else
+    {
+        VFS_GUARD_OPT(this);
+        Dirs::iterator it = _subdirs.find(subdir);
+        if(it != _subdirs.end())
+            ret = it->second.ptr;
+        else if(forceCreate)
+        {
+            size_t fullLen = fullnameLen();
+            if(fullLen)
+            {
+                // -> newname = fullname() + '/' + subdir
+                size_t subdirLen = strlen(subdir);
+                char * const newname = (char*)VFS_STACK_ALLOC(fullLen + subdirLen + 2);
+                char *ptr = newname;
+                memcpy(ptr, fullname(), fullLen);
+                ptr += fullLen;
+                *ptr++ = '/';
+                memcpy(ptr, subdir, subdirLen);
+                ptr[subdirLen] = 0;
+
+                ret = createNew(newname);
+                VFS_STACK_FREE(newname);
+            }
+            else
+            {
+                ret = createNew(subdir);
+            }
+
+            _subdirs[ret->name()] = ret;
+        }
+    }
+
+    return ret;
+}
+
+void VFSDir::clearMounted()
+{
+    for(FileIter it = _files.begin(); it != _files.end(); )
+    {
+        MapEntry<VFSFile>& e = it->second;
+        if(e.isMounted())
+        {
+            e.ptr->ref--;
+            _files.erase(it++);
+        }
+        else
+            ++it;
+    }
+    for(DirIter it = _subdirs.begin(); it != _subdirs.end(); )
+    {
+        MapEntry<VFSDir>& e = it->second;
+        if(e.isMounted())
+        {
+            e.ptr->ref--;
+            _subdirs.erase(it++);
+        }
+        else
+        {
+            it->second.ptr->clearMounted();
+            ++it;
+        }
+    }
+}
+
+template<typename T> static void iterIncref(T *b, void*) { ++(b->ref); }
+template<typename T> static void iterDecref(T *b, void*) { --(b->ref); }
+
+static void _iterDirs(VFSDir::Dirs &m, DirEnumCallback f, void *user)
+{
+    for(DirIter it = m.begin(); it != m.end(); ++it)
+        f(it->second.ptr, user);
+}
+
+void VFSDir::forEachDir(DirEnumCallback f, void *user /* = NULL */, bool safe /* = false */)
+{
+    VFS_GUARD_OPT(this);
+    if(safe)
+    {
+        Dirs cp = _subdirs;
+        _iterDirs(cp, iterIncref<VFSDir>, NULL);
+        _iterDirs(cp, f, user);
+        _iterDirs(cp, iterDecref<VFSDir>, NULL);
+    }
+    else
+        _iterDirs(_subdirs, f, user);
+}
+
+static void _iterFiles(VFSDir::Files &m, FileEnumCallback f, void *user)
+{
+    for(FileIter it = m.begin(); it != m.end(); ++it)
+        f(it->second.ptr, user);
+}
+
+void VFSDir::forEachFile(FileEnumCallback f, void *user /* = NULL */, bool safe /* = false */)
+{
+    VFS_GUARD_OPT(this);
+    if(safe)
+    {
+        Files cp = _files;
+        _iterFiles(cp, iterIncref<VFSFile>, NULL);
+        _iterFiles(cp, f, user);
+        _iterFiles(cp, iterDecref<VFSFile>, NULL);
+    }
+    else
+        _iterFiles(_files, f, user);
+}
+
+
+// ----- VFSDirReal start here -----
+
+
+VFSDirReal::VFSDirReal(const char *dir) : VFSDir(dir)
+{
+}
+
+VFSDir *VFSDirReal::createNew(const char *dir) const
+{
+    return new VFSDirReal(dir);
+}
+
+unsigned int VFSDirReal::load(bool recursive)
+{
+    VFS_GUARD_OPT(this);
+
+    Files remainF;
+    Dirs remainD;
+
+    remainF.swap(_files);
+    remainD.swap(_subdirs);
+
+    // _files, _subdirs now empty
+
+    StringList li;
+    GetFileList(fullname(), li);
+    for(StringList::iterator it = li.begin(); it != li.end(); ++it)
+    {
+        // file was already present, move over and erase
+        FileIter fi = remainF.find(it->c_str());
+        if(fi != remainF.end())
+        {
+            _files[fi->first] = fi->second;
+            remainF.erase(fi);
+            continue;
+        }
+
+        // TODO: use stack alloc
+        std::string tmp = fullname();
+        tmp += '/';
+        tmp += *it;
+        VFSFileReal *f = new VFSFileReal(tmp.c_str());
+        _files[f->name()] = f;
+    }
+    unsigned int sum = li.size();
+
+    li.clear();
+    GetDirList(fullname(), li, false);
+    for(std::deque<std::string>::iterator it = li.begin(); it != li.end(); ++it)
+    {
+        // subdir was already present, move over and erase
+        DirIter fi = remainD.find(it->c_str());
+        if(fi != remainD.end())
+        {
+            if(recursive)
+                sum += fi->second.ptr->load(true);
+            ++sum;
+
+            _subdirs[fi->first] = fi->second;
+            remainD.erase(fi);
+            continue;
+        }
+
+        // TODO: use stack alloc
+        std::string full(fullname());
+        full += '/';
+        full += *it; // GetDirList() always returns relative paths
+
+        VFSDir *d = createNew(full.c_str());
+        if(recursive)
+            sum += d->load(true);
+        ++sum;
+        _subdirs[d->name()] = d;
+    }
+
+    // clean up & remove no longer existing files & dirs,
+    // and move over entries mounted here.
+    for(FileIter it = remainF.begin(); it != remainF.end(); ++it)
+        if(it->second.isMounted())
+            _files[it->first] = it->second;
+        else
+            it->second.ptr->ref--;
+    for(DirIter it = remainD.begin(); it != remainD.end(); ++it)
+        if(it->second.isMounted())
+            _subdirs[it->first] = it->second;
+        else
+            it->second.ptr->ref--;
+
+    return sum;
+}
+
+VFS_NAMESPACE_END

File ExternalLibs/ttvfs/VFSDir.h

+// VFSDir.h - basic directory interface + classes
+// For conditions of distribution and use, see copyright notice in VFS.h
+
+#ifndef VFSDIR_H