1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2025-05-12 12:03:56 +00:00

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

This commit is contained in:
fgenesis 2012-06-01 17:23:19 +02:00
parent 99e3f5ebe2
commit a90f57afb0
36 changed files with 10047 additions and 0 deletions

841
ExternalLibs/minihttp.cpp Normal file
View file

@ -0,0 +1,841 @@
// 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

230
ExternalLibs/minihttp.h Normal file
View file

@ -0,0 +1,230 @@
/* 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

View file

@ -0,0 +1,29 @@
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})

80
ExternalLibs/ttvfs/VFS.h Normal file
View file

@ -0,0 +1,80 @@
/* 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

View file

@ -0,0 +1,68 @@
#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

View file

@ -0,0 +1,103 @@
// 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

View file

@ -0,0 +1,39 @@
// 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

View file

@ -0,0 +1,32 @@
// 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

View file

@ -0,0 +1,83 @@
// 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

View file

@ -0,0 +1,101 @@
// 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

View file

@ -0,0 +1,441 @@
// 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

213
ExternalLibs/ttvfs/VFSDir.h Normal file
View file

@ -0,0 +1,213 @@
// VFSDir.h - basic directory interface + classes
// For conditions of distribution and use, see copyright notice in VFS.h
#ifndef VFSDIR_H
#define VFSDIR_H
#include "VFSBase.h"
#include <map>
#include <cstring>
#ifdef VFS_USE_HASHMAP
# include "VFSHashmap.h"
# include "VFSTools.h"
#endif
VFS_NAMESPACE_START
#ifdef VFS_IGNORE_CASE
# ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable: 4996)
# endif
struct ci_less
{
inline bool operator() (const char *a, const char *b) const
{
return VFS_STRICMP(a, b) < 0;
}
};
struct ci_equal
{
inline bool operator() (const char *a, const char *b) const
{
return !VFS_STRICMP(a, b);
}
};
inline int casecmp(const char *a, const char *b)
{
return VFS_STRICMP(a, b);
}
# ifdef _MSC_VER
# pragma warning(pop)
# endif
#else // VFS_IGNORE_CASE
struct cs_less
{
inline bool operator() (const char *a, const char *b) const
{
return strcmp(a, b) < 0;
}
};
inline int casecmp(const char *a, const char *b)
{
return strcmp(a, b);
}
#endif // VFS_IGNORE_CASE
#ifdef VFS_USE_HASHMAP
struct hashmap_eq
{
inline bool operator() (const char *a, const char *b, size_t h, const VFSBase *itm) const
{
// quick check - instead of just comparing the strings,
// check the hashes first. If they don't match there is no
// need to check the strings at all.
return itm->hash() == h && !casecmp(a, b);
}
};
struct charptr_hash
{
inline size_t operator()(const char *s)
{
// case sensitive or in-sensitive, depending on config
return STRINGHASH(s);
}
};
#endif // VFS_USE_HASHMAP
class VFSDir;
class VFSFile;
typedef void (*FileEnumCallback)(VFSFile *vf, void *user);
typedef void (*DirEnumCallback)(VFSDir *vd, void *user);
class VFSDir : public VFSBase
{
public:
// bitmask
enum EntryFlags
{
NONE = 0,
MOUNTED = 1
};
template<typename T> struct MapEntry
{
MapEntry() {}
MapEntry(T *p, EntryFlags flg = NONE) : ptr(p), flags(flg) {}
inline bool isMounted() { return flags & MOUNTED; }
T *ptr;
EntryFlags flags;
};
// Avoid using std::string as key.
// The file names are known to remain constant during each object's lifetime,
// so just keep the pointers and use an appropriate comparator function.
#ifdef VFS_USE_HASHMAP
// VFS_IGNORE_CASE already handled in hash generation
typedef HashMap<const char *, MapEntry<VFSDir>, charptr_hash, hashmap_eq> Dirs;
typedef HashMap<const char *, MapEntry<VFSFile>, charptr_hash, hashmap_eq> Files;
#else
# ifdef VFS_IGNORE_CASE
typedef std::map<const char *, MapEntry<VFSDir>, ci_less> Dirs;
typedef std::map<const char *, MapEntry<VFSFile>, ci_less> Files;
# else
typedef std::map<const char *, MapEntry<VFSDir>, cs_less> Dirs;
typedef std::map<const char *, MapEntry<VFSFile>, cs_less> Files;
# endif
#endif
VFSDir(const char *fullpath);
virtual ~VFSDir();
/** Enumerate directory with given path. Keeps previously loaded entries.
Returns the amount of files found. */
virtual unsigned int load(bool recursive);
/** Creates a new virtual directory of an internally specified type. */
virtual VFSDir *createNew(const char *dir) const;
/** For debugging. Does never return NULL. */
virtual const char *getType() const { return "VFSDir"; }
/** Can be overloaded if necessary. Called by VFSHelper::ClearGarbage() */
virtual void clearGarbage() {}
/** Can be overloaded to close resources this dir keeps open */
virtual bool close() { return true; }
/** Returns a file for this dir's subtree. Descends if necessary.
Returns NULL if the file is not found. */
VFSFile *getFile(const char *fn);
/** Returns a subdir, descends if necessary. If forceCreate is true,
create directory tree if it does not exist, and return the originally requested
subdir. Otherwise return NULL if not found. */
VFSDir *getDir(const char *subdir, bool forceCreate = false);
/** Recursively drops all files/dirs that were mounted into this directory (and subdirs) */
void clearMounted();
/** Iterate over all files or directories, calling a callback function,
optionally with additional userdata. If safe is true, iterate over a copy.
This is useful if the callback function modifies the tree, e.g.
adds or removes files. */
void forEachFile(FileEnumCallback f, void *user = NULL, bool safe = false);
void forEachDir(DirEnumCallback f, void *user = NULL, bool safe = false);
/* Below is for internal use -- take care if using these externally! */
bool insert(VFSDir *subdir, bool overwrite, EntryFlags flag);
bool merge(VFSDir *dir, bool overwrite, EntryFlags flag);
/** Adds a file directly to this directory, allows any name.
If another file with this name already exists, optionally drop the old one out.
Returns whether the file was actually added. */
bool add(VFSFile *f, bool overwrite, EntryFlags flag);
/** Like add(), but if the file name contains a path, descend the tree to the target dir.
Not-existing subdirs are created on the way. */
bool addRecursive(VFSFile *f, bool overwrite, EntryFlags flag);
protected:
// std::map<const char*,X> or ttvfs::HashMap<const char*, X> stores for files and subdirs.
Files _files;
Dirs _subdirs;
};
typedef VFSDir::Files::iterator FileIter;
typedef VFSDir::Dirs::iterator DirIter;
class VFSDirReal : public VFSDir
{
public:
VFSDirReal(const char *dir);
virtual ~VFSDirReal() {};
virtual unsigned int load(bool recursive);
virtual VFSDir *createNew(const char *dir) const;
virtual const char *getType(void) const { return "VFSDirReal"; }
};
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,237 @@
// VFSFile.cpp - basic file interface + classes
// For conditions of distribution and use, see copyright notice in VFS.h
#include "VFSInternal.h"
#include "VFSFile.h"
#include "VFSTools.h"
#include "VFSFileFuncs.h"
#include <cstdio>
VFS_NAMESPACE_START
VFSFile::VFSFile(const char *name)
: _buf(NULL), _delfunc(NULL)
{
_setName(name);
}
VFSFile::~VFSFile()
{
close();
dropBuf(true);
}
void VFSFile::delBuf(void *mem)
{
deleteHelper(_delfunc, (char*) mem);
}
const void *VFSFile::getBuf(allocator_func alloc /* = NULL */, delete_func del /* = NULL */)
{
assert(!alloc == !del); // either both or none may be defined. Checked extra early to prevent possible errors later.
VFS_GUARD_OPT(this);
if(_buf)
return _buf;
bool op = isopen();
if(!op && !open()) // open with default params if not open
return NULL;
unsigned int s = (unsigned int)size();
_buf = allocHelper(alloc, s + 4); // a bit extra padding
if(!_buf)
return NULL;
_delfunc = del;
vfspos offs;
if(op)
{
vfspos oldpos = getpos();
seek(0);
offs = read(_buf, s);
seek(oldpos);
}
else
{
offs = read(_buf, s);
close();
}
// Might as well be text mode reading, which means less actual bytes than size() said,
// so this can't be done earlier.
memset((char*)_buf + offs, 0, 4);
return _buf;
}
void VFSFile::dropBuf(bool del)
{
VFS_GUARD_OPT(this);
if(del)
delBuf(_buf);
_buf = NULL;
}
VFSFileReal::VFSFileReal(const char *name /* = NULL */)
: VFSFile(name), _fh(NULL), _size(npos), _buf(NULL)
{
}
VFSFileReal::~VFSFileReal()
{
}
bool VFSFileReal::open(const char *mode /* = NULL */)
{
VFS_GUARD_OPT(this);
if(isopen())
close();
dropBuf(true);
_fh = real_fopen(fullname(), mode ? mode : "rb");
if(!_fh)
return false;
real_fseek((FILE*)_fh, 0, SEEK_END);
_size = getpos();
real_fseek((FILE*)_fh, 0, SEEK_SET);
return true;
}
bool VFSFileReal::isopen(void) const
{
VFS_GUARD_OPT(this);
return !!_fh;
}
bool VFSFileReal::iseof(void) const
{
VFS_GUARD_OPT(this);
return !_fh || real_feof((FILE*)_fh);
}
bool VFSFileReal::close(void)
{
VFS_GUARD_OPT(this);
if(_fh)
{
real_fclose((FILE*)_fh);
_fh = NULL;
}
return true;
}
bool VFSFileReal::seek(vfspos pos)
{
VFS_GUARD_OPT(this);
if(!_fh)
return false;
return real_fseek((FILE*)_fh, pos, SEEK_SET) == 0;
}
bool VFSFileReal::seekRel(vfspos offs)
{
VFS_GUARD_OPT(this);
if(!_fh)
return false;
return real_fseek((FILE*)_fh, offs, SEEK_CUR) == 0;
}
bool VFSFileReal::flush(void)
{
VFS_GUARD_OPT(this);
if(!_fh)
return false;
return real_fflush((FILE*)_fh) == 0;
}
vfspos VFSFileReal::getpos(void) const
{
VFS_GUARD_OPT(this);
if(!_fh)
return npos;
return real_ftell((FILE*)_fh);
}
unsigned int VFSFileReal::read(void *dst, unsigned int bytes)
{
VFS_GUARD_OPT(this);
if(!_fh)
return npos;
return real_fread(dst, 1, bytes, (FILE*)_fh);
}
unsigned int VFSFileReal::write(const void *src, unsigned int bytes)
{
VFS_GUARD_OPT(this);
if(!_fh)
return npos;
return real_fwrite(src, 1, bytes, (FILE*)_fh);
}
vfspos VFSFileReal::size(void)
{
VFS_GUARD_OPT(this);
if(_size != npos)
return _size;
open();
close();
// now size is known.
return _size;
}
// ------------- VFSFileMem -----------------------
VFSFileMem::VFSFileMem(const char *name, void *buf, unsigned int size, Mode mode /* = COPY */,
allocator_func alloc /* = NULL */, delete_func delfunc /* = NULL */)
: VFSFile(name), _pos(0), _size(size), _mybuf(mode == TAKE_OVER || mode == COPY)
{
if(mode == COPY)
{
assert(!alloc == !delfunc);
_buf = alloc ? alloc(size+1) : (void*)(new char[size+1]);
memcpy(_buf, buf, size);
((char*)_buf)[size] = 0;
}
else
{
_buf = buf;
}
_delfunc = delfunc;
}
VFSFileMem::~VFSFileMem()
{
if(_mybuf)
VFSFile::dropBuf(true);
}
unsigned int VFSFileMem::read(void *dst, unsigned int bytes)
{
VFS_GUARD_OPT(this);
if(iseof())
return 0;
unsigned int rem = std::min<unsigned int>((unsigned int)(_size - _pos), bytes);
memcpy(dst, (char*)_buf + _pos, rem);
return rem;
}
unsigned int VFSFileMem::write(const void *src, unsigned int bytes)
{
VFS_GUARD_OPT(this);
if(iseof())
return 0;
unsigned int rem = std::min<unsigned int>((unsigned int)(_size - _pos), bytes);
memcpy((char*)_buf + _pos, src, rem);
return rem;
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,165 @@
// VFSFile.h - basic file interface + classes
// For conditions of distribution and use, see copyright notice in VFS.h
#ifndef VFSFILE_H
#define VFSFILE_H
#include "VFSBase.h"
#include <string>
VFS_NAMESPACE_START
/** -- VFSFile basic interface --
* All functions that return bool should return true on success and false on failure.
* If an operation is not necessary or irrelevant (for example, files in memory can't be closed),
* it is useful to return true anyways, because this operation did not fail, technically.
* (Common sense here!)
* An int/vfspos value of 0 indicates failure, except the size/seek/getpos functions, where npos means failure.
* Only the functions required or applicable need to be implemented, for unsupported operations
* the default implementation should be sufficient.
**/
class VFSFile : public VFSBase
{
public:
/** The ctor is expected to set both name() and fullname();
The name must remain static throughout the object's lifetime. */
VFSFile(const char *fn);
virtual ~VFSFile();
/** Open a file.
Mode can be "r", "w", "rb", "rb", and possibly other things that fopen supports.
It is the subclass's choice to support other modes. Default is "rb".
If the file still has a buffer (as returned by getBuf()), it is deleted.
Closes and reopens if already open (even in the same mode). */
virtual bool open(const char *mode = NULL) { return false; }
virtual bool isopen(void) const { return false; }
virtual bool iseof(void) const { return true; }
virtual bool close(void) { return true; }
virtual bool seek(vfspos pos) { return false; }
/** Seek relative to current position. Negative numbers will seek backwards.
(In most cases, the default implementation does not have to be changed) */
virtual bool seekRel(vfspos offs) { VFS_GUARD_OPT(this); return seek(getpos() + offs); }
virtual bool flush(void) { return true; }
/** Current offset in file. Return npos if NA. */
virtual vfspos getpos(void) const { return npos; }
virtual unsigned int read(void *dst, unsigned int bytes) { return 0; }
virtual unsigned int write(const void *src, unsigned int bytes) { return 0; }
/** Return file size. If NA, return npos. If size is not yet known,
open() and close() may be called (with default args) to find out the size.
The file is supposed to be in its old state when the function returns,
that is in the same open state and seek position.
The pointer returned by getBuf() must not change. */
virtual vfspos size(void) { return npos; }
/** Return full file content in memory. Like size(), this may do other operations on the file,
but after the function returns the file is expected to be in the same state it was before.
If the file is not open before the call, it will be opened with default parameters (that is, "rb").
Additional EOL mangling my happen if the file is opened in text mode before (= not binary).
Calls to open() should delete this memory if the file was previously opened in a different mode.
In the default implementation, the returned memory is not guaranteed to be writable without problems,
so don't do it. Don't cast the const away. You have been warned.
Supply your own allocator and deletor functions if required.
NULL means new[] and delete[], respectively.
Either both must be a valid function, or both NULL. Only one of them NULL will cause assertion fail. */
virtual const void *getBuf(allocator_func alloc = NULL, delete_func del = NULL);
/** If del is true, delete internal buffer. If false, unregister internal buffer from the file,
but do not delete. Use free() or an appropriate deletion function later. */
virtual void dropBuf(bool del);
/** Basic RTTI, for debugging purposes */
virtual const char *getType(void) const { return "virtual"; }
// ---- non-virtual part ----
/** Uses the deletion function earlier given to getBuf() to free the given memory,
or delete [] if the function is NULL. Useful if the original function
that was used to allocate the buffer is no longer known. */
void delBuf(void *mem);
protected:
void *_buf;
delete_func _delfunc;
};
class VFSFileReal : public VFSFile
{
public:
VFSFileReal(const char *name);
virtual ~VFSFileReal();
virtual bool open(const char *mode = NULL);
virtual bool isopen(void) const;
virtual bool iseof(void) const;
virtual bool close(void);
virtual bool seek(vfspos pos);
virtual bool seekRel(vfspos offs);
virtual bool flush(void);
virtual vfspos getpos(void) const;
virtual unsigned int read(void *dst, unsigned int bytes);
virtual unsigned int write(const void *src, unsigned int bytes);
virtual vfspos size(void);
virtual const char *getType(void) const { return "disk"; }
inline void *getFP() { return _fh; }
protected:
void *_fh; // FILE*
vfspos _size;
void *_buf;
};
class VFSFileMem : public VFSFile
{
public:
enum Mode
{
COPY, //- Make a copy of the buffer (default action).
REUSE, //- Use the passed-in buffer as is. Requires the pointer
// to remain valid over the life of this object.
TAKE_OVER, //- Take over the passed-in buffer; it will be deleted on object destruction.
};
/* Creates a virtual file from a memory buffer. By default, the memory is copied.
A deletor function can be passed optionally, if its NULL (the default),
delete[] (char*)buf will be used. For malloc()'d memory, pass free. (Only used if mode is TAKE_OVER) */
VFSFileMem(const char *name, void *buf, unsigned int size, Mode m = COPY,
allocator_func alloc = NULL, delete_func delfunc = NULL);
virtual ~VFSFileMem();
virtual bool open(const char *mode = NULL) { return true; }
virtual bool isopen(void) const { return true; } // always open
virtual bool iseof(void) const { VFS_GUARD_OPT(this); return _pos >= _size; }
virtual bool close(void) { return true; } // cant close, but not a problem
virtual bool seek(vfspos pos) { VFS_GUARD_OPT(this); _pos = pos; return true; }
virtual bool seekRel(vfspos offs) { VFS_GUARD_OPT(this); _pos += offs; return true; }
virtual bool flush(void) { return false; } // can't flush, if a successful file write is expected, this IS a problem.
virtual vfspos getpos(void) const { VFS_GUARD_OPT(this); return _pos; }
virtual unsigned int read(void *dst, unsigned int bytes);
virtual unsigned int write(const void *src, unsigned int bytes);
virtual vfspos size(void) { VFS_GUARD_OPT(this); return _size; }
virtual const void *getBuf(allocator_func alloc = NULL, delete_func del = NULL) { VFS_GUARD_OPT(this); return _buf; }
virtual void dropBuf(bool) {} // we can't simply drop the internal buffer, as the file is entirely memory based
virtual const char *getType(void) const { return "mem"; }
protected:
vfspos _pos;
vfspos _size;
bool _mybuf;
};
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,78 @@
#include "VFSFileFuncs.h"
// this is for POSIX - define before including any stdio headers
#ifdef VFS_LARGEFILE_SUPPORT
# ifndef _MSC_VER
# define _FILE_OFFSET_BITS 64
# endif
#endif
#include "VFSInternal.h"
#include <cstdio>
// Compile time assertion to make sure things work as expected
#if defined(VFS_LARGEFILE_SUPPORT) && !defined(_MSC_VER)
static void _dummy_() { switch(0) { case 4: case sizeof(off_t): ; } }
#endif
VFS_NAMESPACE_START
void *real_fopen(const char *name, const char *mode)
{
return fopen(name, mode);
}
int real_fclose(void *fh)
{
return fclose((FILE*)fh);
}
int real_fseek(void *fh, vfspos offset, int origin)
{
#ifdef VFS_LARGEFILE_SUPPORT
# ifdef _MSC_VER
return _fseeki64((FILE*)fh, offset, origin);
# else
return fseeko((FILE*)fh, offset, origin);
# endif
#else
return fseek((FILE*)fh, offset, origin);
#endif
}
vfspos real_ftell(void *fh)
{
#ifdef VFS_LARGEFILE_SUPPORT
# ifdef _MSC_VER
return _ftelli64((FILE*)fh);
# else
return ftello((FILE*)fh);
# endif
#else
return ftell((FILE*)fh);
#endif
}
size_t real_fread(void *ptr, size_t size, size_t count, void *fh)
{
return fread(ptr, size, count, (FILE*)fh);
}
size_t real_fwrite(const void *ptr, size_t size, size_t count, void *fh)
{
return fwrite(ptr, size, count, (FILE*)fh);
}
int real_feof(void *fh)
{
return feof((FILE*)fh);
}
int real_fflush(void *fh)
{
return fflush((FILE*)fh);
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,15 @@
#include "VFSDefines.h"
VFS_NAMESPACE_START
void *real_fopen(const char *name, const char *mode);
int real_fclose(void *fh);
int real_fseek(void *fh, vfspos offset, int origin);
vfspos real_ftell(void *fh);
size_t real_fread(void *ptr, size_t size, size_t count, void *fh);
size_t real_fwrite(const void *ptr, size_t size, size_t count, void *fh);
int real_feof(void *fh);
int real_fflush(void *fh);
VFS_NAMESPACE_END

View file

@ -0,0 +1,394 @@
// VFSHashmap.h - minimalist but fast STL compatible hashmap implementation.
// For conditions of distribution and use, see copyright notice in VFS.h
#ifndef VFS_HASHMAP_H
#define VFS_HASHMAP_H
#include <vector>
#include "VFSDefines.h"
VFS_NAMESPACE_START
template<typename KEY> struct hash_cast
{
inline size_t operator()(const KEY& t) const
{
return (size_t)t;
}
inline size_t operator()(const KEY *t) const
{
return (size_t)*t;
}
};
template<typename KEY, typename T> struct equal
{
inline bool operator()(const KEY& a, const KEY& b, size_t /*hash*/, const T& /*value*/) const
{
return a == b;
}
};
// (http://graphics.stanford.edu/~seander/bithacks.html)
inline size_t nextPowerOf2(size_t v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
v += (v == 0);
return v;
}
template
<
typename KEY,
typename T,
typename HASH,
typename CMP,
typename BucketType
>
class _HashMapBase
{
// ---- supplemental stuff ----
public:
typedef T value_type;
typedef std::size_t size_type;
typedef typename BucketType::iterator ITR;
class iterator : public std::iterator<std::bidirectional_iterator_tag, typename ITR::value_type>
{
public:
typedef typename ITR::value_type value_type;
typedef typename ITR::reference reference;
typedef typename ITR::pointer pointer;
iterator()
: _bucket(-1), _hm(NULL)
{
}
iterator(size_t bucket, const ITR& it, _HashMapBase *hm)
: _bucket(bucket), _it(it), _hm(hm)
{
_nextbucket();
}
iterator(const iterator& it)
: _bucket(it._bucket), _it(it._it), _hm(it._hm)
{
_nextbucket();
}
iterator& operator=(const iterator& o)
{
if(this == &o)
return *this;
_bucket = o._bucket;
_it = o._it;
_hm = o._hm;
return *this;
}
void _nextbucket()
{
while(_bucket+1 < _hm->v.size() && _it == _hm->v[_bucket].end())
_it = _hm->v[++_bucket].begin();
}
void _prevbucket()
{
while(_bucket > 0 && _it == _hm->v[_bucket].begin())
_it = _hm->v[--_bucket].back();
}
iterator& operator++(void) // pre-increment
{
++_it;
_nextbucket();
return *this;
}
iterator operator++(int) // post-increment
{
iterator it = *this;
++*this;
return it;
}
iterator& operator--(void) // pre-decrement
{
--_it;
_prevbucket();
return *this;
}
iterator operator--(int) // post-decrement
{
iterator it = *this;
--*this;
return it;
}
bool operator==(const iterator& o) const
{
return _hm == o._hm && _bucket == o._bucket && _it == o._it;
}
bool operator!=(const iterator& o) const
{
return !(*this == o);
}
bool operator<(const iterator& o) const
{
return _bucket < o._bucket || (_bucket == o._bucket && _it < o._it);
}
bool operator<=(const iterator& o) const
{
return !(*this > o);
}
bool operator>(const iterator& o) const
{
return _bucket > o._bucket || (_bucket == o._bucket && _it > o._it);
}
bool operator>=(const iterator& o) const
{
return !(*this < o);
}
reference operator*()
{
return _it.operator*();
}
pointer operator->()
{
return _it.operator->();
}
size_t _bucket;
ITR _it;
_HashMapBase *_hm;
};
protected:
// ---- Main class start ----
_HashMapBase(size_t buckets, int loadFactor, const CMP& c, const HASH& h)
: v(nextPowerOf2(buckets)), _size(0), _loadFactor(loadFactor), cmp(c), hash(h)
{}
_HashMapBase(const _HashMapBase& o)
: v(o.v), _size(o._size), _loadFactor(o._loadFactor), cmp(o.cmp), hash(o.hash)
{}
public:
_HashMapBase& operator=(const _HashMapBase& o)
{
if(this == &o)
return *this;
v = o.v;
cmp = o.cmp;
hash = o.hash;
_size = o._size;
_loadFactor = o._loadFactor;
}
inline iterator begin()
{
return _MakeIter(0, v[0].begin());
}
inline iterator end()
{
return _MakeIter(v.size()-1, v[v.size()-1].end());
}
iterator find(const KEY& k)
{
size_t h = hash(k);
size_t i = h & (v.size()-1); // assume power of 2
BucketType& b = v[i];
for(typename BucketType::iterator it = b.begin(); it != b.end(); ++it)
if(cmp(k, it->first, h, it->second))
return _MakeIter(i, it);
return end();
}
void erase(const KEY& k)
{
size_t h = hash(k);
BucketType& b = v[h & (v.size()-1)]; // assume power of 2
for(typename BucketType::iterator it = b.begin(); it != b.end(); ++it)
{
if(cmp(k, it->first, h, it->second))
{
b.erase(it);
--_size;
return;
}
}
}
inline iterator erase(const iterator& it)
{
--_size;
return _MakeIter(it._bucket, v[it._bucket].erase(it._it));
}
T& operator[] (const KEY& k)
{
size_t h = hash(k);
size_t i = h & (v.size()-1); // assume power of 2
{
BucketType& b = v[i];
for(typename BucketType::iterator it = b.begin(); it != b.end(); ++it)
if(cmp(k, it->first, h, it->second))
return it->second;
}
++_size;
if(_enlargeIfNecessary())
i = h & (v.size()-1);
v[i].push_back(std::make_pair(k, T()));
return v[i].back().second;
}
inline size_t size() const
{
return _size;
}
/* "Because map containers do not allow for duplicate key values, the insertion operation
checks for each element inserted whether another element exists already in the container
with the same key value, if so, the element is not inserted and its mapped value
is not changed in any way." */ // Oh well.
/*void insert(std::pair<KEY, T>& p)
{
size_t h = hash(p.first);
size_t i = h & (v.size()-1);
{
BucketType& b = v[i]; // assume power of 2
for(typename BucketType::iterator it = b.begin(); it != b.end(); ++it)
if(cmp(p.first, it->first, h, it->second))
return _MakeIter(i, it);
}
++_size;
if(_enlargeIfNecessary())
i = h & (v.size()-1);
v[i].push_back(std::make_pair(k, T()));
return _MakeIter(i, b.end()-1);
}*/ // -- not used in ttvfs currently
private:
inline iterator _MakeIter(size_t bucket, const typename BucketType::iterator& it)
{
return iterator(bucket, it, this);
}
inline bool _enlargeIfNecessary()
{
if(_loadFactor < 0)
return false;
if(_size > v.size() * _loadFactor)
{
_enlarge();
return true;
}
return false;
}
void _enlarge()
{
size_t oldsize = v.size();
v.resize(oldsize * 2);
BucketType cp;
for(size_t i = 0; i < oldsize; ++i)
{
cp.clear();
std::swap(cp, v[i]); // use efficient swap
// v[i] is now empty
// this way can possibly copy elements 2 times, but means less container copying overall
for(typename BucketType::iterator it = cp.begin(); it != cp.end(); ++it)
v[hash(it->first) & (v.size()-1)].push_back(*it); // assume power of 2
}
}
inline void swap(_HashMapBase& hm)
{
if(this == &hm)
return;
std::swap(v, hm.v);
std::swap(_size, hm._size);
std::swap(_loadFactor, hm._loadFactor);
std::swap(hash, hm.hash);
std::swap(cmp, hm.cmp);
}
std::vector<BucketType> v;
size_t _size;
int _loadFactor;
HASH hash; // hash functor
CMP cmp; // compare functor
};
template
<
typename KEY,
typename T,
typename HASH = hash_cast<KEY>,
typename CMP = equal<KEY, T>,
typename BucketType = std::vector<std::pair<KEY,T> >
>
class HashMap : public _HashMapBase<KEY, T, HASH, CMP, BucketType>
{
public:
HashMap(size_t buckets = 16, int loadFactor = 5)
: _HashMapBase<KEY, T, HASH, CMP, BucketType>(buckets, loadFactor, CMP(), HASH())
{
}
};
VFS_NAMESPACE_END
namespace std
{
template
<
typename KEY,
typename T,
typename HASH,
typename CMP,
typename BucketType
>
inline static void swap(VFS_NAMESPACE_IMPL HashMap<KEY, T, HASH, CMP, BucketType>& a,
VFS_NAMESPACE_IMPL HashMap<KEY, T, HASH, CMP, BucketType>& b)
{
a.swap(b);
}
};
#endif

View file

@ -0,0 +1,483 @@
// VFSHelper.cpp - glues it all together and makes use simple
// For conditions of distribution and use, see copyright notice in VFS.h
#include <iostream> // for debug only, see EOF
#include "VFSInternal.h"
#include "VFSHelper.h"
#include "VFSAtomic.h"
#include "VFSTools.h"
#include "VFSDir.h"
#include "VFSFile.h"
#include "VFSLoader.h"
#include "VFSArchiveLoader.h"
#ifdef _DEBUG
# include <cassert>
# define DEBUG_ASSERT(x) assert(x)
#else
# define DEBUG_ASSERT(x)
#endif
VFS_NAMESPACE_START
// predecl is in VFS.h
bool _checkCompatInternal(bool large, bool nocase, bool hashmap, unsigned int vfspos_size)
{
#ifdef VFS_LARGEFILE_SUPPORT
bool largefile_i = true;
#else
bool largefile_i = false;
#endif
#ifdef VFS_IGNORE_CASE
bool nocase_i = true;
#else
bool nocase_i = false;
#endif
#ifdef VFS_USE_HASHMAP
bool hashmap_i = true;
#else
bool hashmap_i = false;
#endif
return (large == largefile_i)
&& (nocase == nocase_i)
&& (hashmap == hashmap_i)
&& (sizeof(vfspos) == vfspos_size);
}
VFSHelper::VFSHelper()
: filesysRoot(NULL), merged(NULL)
{
}
VFSHelper::~VFSHelper()
{
Clear();
}
void VFSHelper::Clear(void)
{
VFS_GUARD_OPT(this);
_cleanup();
if(filesysRoot)
{
filesysRoot->ref--; // this should always delete it...
filesysRoot = NULL; // ...but it may be referenced elsewhere, just in case
}
_ClearMountPoints();
for(LoaderArray::iterator it = loaders.begin(); it != loaders.end(); ++it)
delete *it;
loaders.clear();
for(DirArray::iterator it = trees.begin(); it != trees.end(); ++it)
it->dir->ref--;
trees.clear();
for(ArchiveLoaderArray::iterator it = archLdrs.begin(); it != archLdrs.end(); ++it)
delete *it;
archLdrs.clear();
}
void VFSHelper::_ClearMountPoints(void)
{
for(VFSMountList::iterator it = vlist.begin(); it != vlist.end(); ++it)
it->vdir->ref--;
vlist.clear();
}
void VFSHelper::_cleanup(void)
{
VFS_GUARD_OPT(this); // be extra safe and ensure this is locked
if(merged)
{
merged->ref--;
merged = NULL;
}
}
bool VFSHelper::LoadFileSysRoot(bool recursive)
{
VFS_GUARD_OPT(this);
if(filesysRoot)
return !!filesysRoot->load(recursive);
filesysRoot = new VFSDirReal(".");
if(!filesysRoot->load(recursive))
{
filesysRoot->ref--;
filesysRoot = NULL;
return false;
}
loaders.push_back(new VFSLoaderDisk);
BaseTreeEntry bt;
bt.source = "";
bt.dir = filesysRoot;
trees.push_back(bt);
filesysRoot->ref++;
AddVFSDir(filesysRoot, "");
return true;
}
// TODO: deprecate this
void VFSHelper::Prepare(bool clear /* = true */)
{
VFS_GUARD_OPT(this);
Reload(false, clear, false); // HACK
//for(DirArray::iterator it = trees.begin(); it != trees.end(); ++it)
// merged->getDir((*it)->fullname(), true)->merge(*it);
}
void VFSHelper::Reload(bool fromDisk /* = false */, bool clear /* = false */, bool clearMountPoints /* = false */)
{
VFS_GUARD_OPT(this);
if(clearMountPoints)
_ClearMountPoints();
if(fromDisk && filesysRoot)
LoadFileSysRoot(true);
if(clear)
_cleanup();
if(!merged && trees.size())
{
for(DirArray::iterator it = trees.begin(); it != trees.end(); ++it)
it->dir->clearMounted();
// FIXME: not sure if really correct
merged = trees[0].dir;
merged->ref++;
// FIXME: this is too hogging
//merged->load(true);
}
if(merged)
{
for(VFSMountList::iterator it = vlist.begin(); it != vlist.end(); ++it)
{
//printf("VFS: mount {%s} [%s] -> [%s] (overwrite: %d)\n", it->vdir->getType(), it->vdir->fullname(), it->mountPoint.c_str(), it->overwrite);
GetDir(it->mountPoint.c_str(), true)->merge(it->vdir, it->overwrite, VFSDir::MOUNTED);
}
}
}
bool VFSHelper::Mount(const char *src, const char *dest, bool overwrite /* = true*/)
{
VFS_GUARD_OPT(this);
return AddVFSDir(GetDir(src, false), dest, overwrite);
}
bool VFSHelper::AddVFSDir(VFSDir *dir, const char *subdir /* = NULL */, bool overwrite /* = true */)
{
if(!dir)
return false;
VFS_GUARD_OPT(this);
if(!subdir)
subdir = dir->fullname();
VDirEntry ve(dir, subdir, overwrite);
_StoreMountPoint(ve);
VFSDir *sd = GetDir(subdir, true);
if(!sd) // may be NULL if Prepare() was not called before
return false;
sd->merge(dir, overwrite, VFSDir::MOUNTED); // merge into specified subdir. will be (virtually) created if not existing
return true;
}
bool VFSHelper::Unmount(const char *src, const char *dest)
{
VFSDir *vd = GetDir(src, false);
if(!vd)
return false;
VDirEntry ve(vd, dest, true); // last is dummy
if(!_RemoveMountPoint(ve))
return false;
// FIXME: this could be done more efficiently by just reloading parts of the tree that were involved.
Reload(false, true, false);
//vd->load(true);
//vd->load(false);
/*VFSDir *dstdir = GetDir(dest, false);
if(dstdir)
dstdir->load(false);*/
return true;
}
void VFSHelper::_StoreMountPoint(const VDirEntry& ve)
{
// increase ref already before it will be added
ve.vdir->ref++;
// scan through and ensure only one mount point with the same data is present.
// if present, remove and re-add, this ensures the mount point is at the end of the list
for(VFSMountList::iterator it = vlist.begin(); it != vlist.end(); )
{
const VDirEntry& oe = *it;
if (ve.mountPoint == oe.mountPoint
&& (ve.vdir == oe.vdir || !casecmp(ve.vdir->fullname(), oe.vdir->fullname()))
&& (ve.overwrite || !oe.overwrite) ) // overwrite definitely, or if other does not overwrite
{
it->vdir->ref--;
vlist.erase(it++); // do not break; just in case there are more (fixme?)
}
else
++it;
}
vlist.push_back(ve);
}
bool VFSHelper::_RemoveMountPoint(const VDirEntry& ve)
{
for(VFSMountList::iterator it = vlist.begin(); it != vlist.end(); ++it)
{
const VDirEntry& oe = *it;
if(ve.mountPoint == oe.mountPoint
&& (ve.vdir == oe.vdir || !casecmp(ve.vdir->fullname(), oe.vdir->fullname())) )
{
it->vdir->ref--;
vlist.erase(it);
return true;
}
}
return false;
}
bool VFSHelper::MountExternalPath(const char *path, const char *where /* = "" */, bool loadRec /* = false */, bool overwrite /* = true */)
{
VFS_GUARD_OPT(this);
VFSDirReal *vfs = new VFSDirReal(path);
if(vfs->load(loadRec))
AddVFSDir(vfs, where, overwrite);
return !!--(vfs->ref); // 0 if deleted
}
void VFSHelper::AddLoader(VFSLoader *ldr)
{
VFS_GUARD_OPT(this);
loaders.push_back(ldr);
}
void VFSHelper::AddArchiveLoader(VFSArchiveLoader *ldr)
{
VFS_GUARD_OPT(this);
archLdrs.push_back(ldr);
}
VFSDir *VFSHelper::AddArchive(const char *arch, bool asSubdir /* = true */, const char *subdir /* = NULL */, void *opaque /* = NULL */)
{
VFSFile *af = GetFile(arch);
if(!af)
return NULL;
VFSDir *ad = NULL;
VFSLoader *fileLdr = NULL;
for(ArchiveLoaderArray::iterator it = archLdrs.begin(); it != archLdrs.end(); ++it)
if((ad = (*it)->Load(af, &fileLdr, opaque)))
break;
if(!ad)
return NULL;
if(fileLdr)
loaders.push_back(fileLdr);
BaseTreeEntry bt;
bt.source = arch;
bt.dir = ad;
trees.push_back(bt);
AddVFSDir(ad, subdir, true);
return ad;
}
inline static VFSFile *VFSHelper_GetFileByLoader(VFSLoader *ldr, const char *fn, const char *unmangled, VFSDir *root)
{
if(!ldr)
return NULL;
VFSFile *vf = ldr->Load(fn, unmangled);
if(vf)
{
VFS_GUARD_OPT(vf);
root->addRecursive(vf, true, VFSDir::NONE);
--(vf->ref);
}
return vf;
}
VFSFile *VFSHelper::GetFile(const char *fn)
{
const char *unmangled = fn;
std::string fixed = FixPath(fn);
fn = fixed.c_str();
VFSFile *vf = NULL;
VFS_GUARD_OPT(this);
if(!merged) // Prepare() called?
return NULL;
vf = merged->getFile(fn);
// nothing found? maybe a loader has something.
// if so, add the newly created VFSFile to the tree.
if(!vf)
for(LoaderArray::iterator it = loaders.begin(); it != loaders.end(); ++it)
if((vf = VFSHelper_GetFileByLoader(*it, fn, unmangled, GetDirRoot())))
break;
//printf("VFS: GetFile '%s' -> '%s' (%s:%p)\n", fn, vf ? vf->fullname() : "NULL", vf ? vf->getType() : "?", vf);
return vf;
}
inline static VFSDir *VFSHelper_GetDirByLoader(VFSLoader *ldr, const char *fn, const char *unmangled, VFSDir *root)
{
if(!ldr)
return NULL;
VFSDir *vd = ldr->LoadDir(fn, unmangled);
if(vd)
{
std::string parentname = StripLastPath(fn);
VFS_GUARD_OPT(this);
VFSDir *parent = parentname.empty() ? root : root->getDir(parentname.c_str(), true);
parent->insert(vd, true, VFSDir::NONE);
--(vd->ref); // should delete it
vd = root->getDir(fn); // can't return vd directly because it is cloned on insert+merge, and already deleted here
}
return vd;
}
VFSDir *VFSHelper::GetDir(const char* dn, bool create /* = false */)
{
const char *unmangled = dn;
std::string fixed = FixPath(dn);
dn = fixed.c_str();
VFS_GUARD_OPT(this);
if(!merged)
return NULL;
if(!*dn)
return merged;
VFSDir *vd = merged->getDir(dn);
if(!vd && create)
{
if(!vd)
for(LoaderArray::iterator it = loaders.begin(); it != loaders.end(); ++it)
if((vd = VFSHelper_GetDirByLoader(*it, dn, unmangled, GetDirRoot())))
break;
if(!vd)
vd = merged->getDir(dn, true);
}
//printf("VFS: GetDir '%s' -> '%s' (%s:%p)\n", dn, vd ? vd->fullname() : "NULL", vd ? vd->getType() : "?", vd);
return vd;
}
VFSDir *VFSHelper::GetDirRoot(void)
{
VFS_GUARD_OPT(this);
return merged;
}
VFSDir *VFSHelper::GetBaseTree(const char *path)
{
for(DirArray::iterator it = trees.begin(); it != trees.end(); ++it)
if(!casecmp(it->source.c_str(), path))
return it->dir;
return NULL;
}
VFSDir *VFSHelper::GetMountPoint(const char *path)
{
for(VFSMountList::iterator it = vlist.begin(); it != vlist.end(); ++it)
if(!casecmp(it->mountPoint.c_str(), path))
return it->vdir;
return NULL;
}
void VFSHelper::ClearGarbage(void)
{
for(DirArray::iterator it = trees.begin(); it != trees.end(); ++it)
it->dir->clearGarbage();
}
// DEBUG STUFF
struct _DbgParams
{
_DbgParams(std::ostream& os_, VFSDir *parent_, const std::string& sp_)
: os(os_), parent(parent_), sp(sp_) {}
std::ostream& os;
VFSDir *parent;
const std::string& sp;
};
static void _DumpFile(VFSFile *vf, void *user)
{
_DbgParams& p = *((_DbgParams*)user);
p.os << p.sp << "f|" << vf->name() << " [" << vf->getType() << ", ref " << vf->ref.count() << ", 0x" << vf << "]";
if(strncmp(p.parent->fullname(), vf->fullname(), p.parent->fullnameLen()))
p.os << " <-- {" << vf->fullname() << "} ***********";
p.os << std::endl;
}
static void _DumpTreeRecursive(VFSDir *vd, void *user)
{
_DbgParams& p = *((_DbgParams*)user);
std::string sub = p.sp + " ";
p.os << p.sp << "d|" << vd->name() << " [" << vd->getType() << ", ref " << vd->ref.count() << ", 0x" << vd << "]";
if(p.parent && strncmp(p.parent->fullname(), vd->fullname(), strlen(p.parent->fullname())))
p.os << " <-- {" << vd->fullname() << "} ***********";
p.os << std::endl;
_DbgParams recP(p.os, vd, sub);
vd->forEachDir(_DumpTreeRecursive, &recP);
vd->forEachFile(_DumpFile, &recP);
}
void VFSHelper::debugDumpTree(std::ostream& os, VFSDir *start /* = NULL */)
{
_DbgParams recP(os, NULL, "");
VFSDir *d = start ? start : GetDirRoot();
_DumpTreeRecursive(d, &recP);
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,191 @@
// VFSHelper.h - glues it all together and makes use simple
// For conditions of distribution and use, see copyright notice in VFS.h
#ifndef VFSHELPER_H
#define VFSHELPER_H
#include <vector>
#include <list>
#include <string>
#include <iostream>
#include "VFSAtomic.h"
VFS_NAMESPACE_START
class VFSDir;
class VFSDirReal;
class VFSFile;
class VFSLoader;
class VFSArchiveLoader;
/** VFSHelper - extensible class to simplify working with the VFS tree */
class VFSHelper
{
public:
VFSHelper();
virtual ~VFSHelper();
/** Creates the working tree. Required before any files or directories can be accessed.
Internally, it merges all individual VFS trees into one. If clear is true (default),
an existing merged tree is dropped, and old and previously added files removed.
(This is the recommended setting.)
Mount points and loaders are kept.*/
virtual void Prepare(bool clear = true);
/** Re-merges any files in the tree, and optionally reloads files on disk.
This is useful if files on disk were created or removed, and the tree needs to reflect these changes.
Calls Prepare(clear) internally. */
virtual void Reload(bool fromDisk = false, bool clear = false, bool clearMountPoints = false);
/** Reset an instance to its initial state.
Drops all archives, loaders, archive loaders, mount points, internal trees, ...*/
virtual void Clear(void);
/** Do cleanups from time to time. In base VFSHelper, this is a no-op.
Extensions may wish to override this method do do cleanup jobs. */
virtual void ClearGarbage(void);
/** Load all files from working directory (into an internal tree) */
bool LoadFileSysRoot(bool recursive);
/** Mount a directory in the tree to a different location. Requires a previous call to Prepare().
This can be imagined like a symlink pointing to a different location.
Be careful not to create circles, this might technically work,
but confuses the reference counting, causing memory leaks. */
bool Mount(const char *src, const char *dest, bool overwrite = true);
/** Drops a directory from the tree. Internally, this calls Reload(false),
which is a heavy operation compared to Mount(). Be warned. */
bool Unmount(const char *src, const char *dest);
/** Merges a path into the tree. Requires a previous call to Prepare().
By default the directory is added into the root directory of the merged tree.
Pass NULL to add the directory to its original location,
or any other path to add it to that explicit location.
If loadRec is true, load all subdirs recursively.
It is advised not to use this to re-add parts already in the tree; use Mount() instead.
Rule of thumb: If you called LoadFileSysRoot(), do not use this for subdirs.
Note: Directories mounted with this will return `where` as their full path if it was set.
Use GetMountPoint() to retrieve the underlying VFSDir object. */
bool MountExternalPath(const char *path, const char *where = "", bool loadRec = false, bool overwrite = true);
/** Adds a VFSDir object into the merged tree. If subdir is NULL (the default),
add into the subdir stored in the VFSDir object. The tree will be extended if target dir does not exist.
If overwrite is true (the default), files in the tree will be replaced if already existing.
Requires a previous call to Prepare().
Like with Mount(); be careful not to create cycles. */
bool AddVFSDir(VFSDir *dir, const char *subdir = NULL, bool overwrite = true);
/** Add the contents of an archive file to the tree. By default, the archive can be addressed
like a folder, e.g. "path/to/example.zip/file.txt".
Set asSubdir to false to "unpack" the contents of the archive to the containing folder.
Optionally, the target subdir to mount into can be specified. (See AddVFSDir().)
Returns a pointer to the actual VFSDir object that represents the added archive, or NULL if failed.
The opaque pointer is passed directly to each loader and can contain additional parameters,
such as a password to open the file.
Read the comments in VFSArchiveLoader.h for an explanation how it works. If you have no idea, leave it NULL,
because it can easily cause a crash if not used carefully. */
VFSDir *AddArchive(const char *arch, bool asSubdir = true, const char *subdir = NULL, void *opaque = NULL);
/** Add a loader that can look for files on demand.
It is possible (but not a good idea) to add a loader multiple times. */
void AddLoader(VFSLoader *ldr);
/** Add an archive loader that can open archives of various types.
Whenever an archive file is requested to be opened by AddArchive(),
it is sent through each registered loader until one of them can recognize
the format and open it. An archive loader stays once registered. */
void AddArchiveLoader(VFSArchiveLoader *ldr);
/** Get a file from the merged tree. Requires a previous call to Prepare().
Asks loaders if the file is not in the tree. If found by a loader, the file will be added to the tree.
The returned pointer is reference counted. In case the file pointer is stored elsewhere,
do ptr->ref++, and later ptr->ref--. This is to prevent the VFS tree from deleting the file when cleaning up.
Not necessary if the pointer is just retrieved and used, or temp. stored while the VFS tree is not modified. */
VFSFile *GetFile(const char *fn);
/** Get a directory from the merged tree. If create is true and the directory does not exist,
build the tree structure and return the newly created dir. NULL otherwise.
Requires a previous call to Prepare().
Reference counted, same as GetFile(), look there for more info. */
VFSDir *GetDir(const char* dn, bool create = false);
/** Returns the tree root, which is usually the working directory. */
VFSDir *GetDirRoot(void);
/** Returns one of the root tree sources by their internal name. */
VFSDir *GetBaseTree(const char *path);
/** Returns one of the mount points' base directory
(The one that is normally not acessible) */
VFSDir *GetMountPoint(const char *path);
/** Remove a file or directory from the tree */
//bool Remove(VFSFile *vf);
//bool Remove(VFSDir *dir);
//bool Remove(const char *name); // TODO: CODE ME
inline void lock() { _mtx.Lock(); }
inline void unlock() { _mtx.Unlock(); }
inline Mutex& mutex() const { return _mtx; }
// DEBUG STUFF
void debugDumpTree(std::ostream& os, VFSDir *start = NULL);
protected:
/** Drops the merged tree and allows fully re-creating it.
Overload to do additional cleanup if required. Invoked by Clear() and Prepare(true). */
virtual void _cleanup(void);
struct VDirEntry
{
VDirEntry() : vdir(NULL), overwrite(false) {}
VDirEntry(VFSDir *v, std::string mp, bool ow) : vdir(v), mountPoint(mp), overwrite(ow) {}
VFSDir *vdir;
std::string mountPoint;
bool overwrite;
};
struct BaseTreeEntry
{
std::string source;
VFSDir *dir;
};
typedef std::list<VDirEntry> VFSMountList;
typedef std::vector<VFSLoader*> LoaderArray;
typedef std::vector<VFSArchiveLoader*> ArchiveLoaderArray;
typedef std::vector<BaseTreeEntry> DirArray;
void _StoreMountPoint(const VDirEntry& ve);
bool _RemoveMountPoint(const VDirEntry& ve);
void _ClearMountPoints(void);
// the VFSDirs are merged in their declaration order.
// when merging, files already contained can be overwritten by files merged in later.
VFSDirReal *filesysRoot; // local files on disk (root dir)
// VFSDirs from various sources are stored here, and will be merged into one final tree
// by Prepare().
DirArray trees;
// If files are not in the tree, maybe one of these is able to find it.
LoaderArray loaders;
VFSDir *merged; // contains the merged virtual/actual file system tree
mutable Mutex _mtx;
private:
VFSMountList vlist; // all other trees added later, together with path to mount to
ArchiveLoaderArray archLdrs;
};
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,57 @@
// VFSInternal.h - misc things that are not required to be visible outside of the library.
// For conditions of distribution and use, see copyright notice in VFS.h
// !! this file is supposed to be included ONLY from VFS*.cpp files.
#ifndef VFS_INTERNAL_H
#define VFS_INTERNAL_H
// checks to enforce correct including
#ifdef TTVFS_VFS_H
#error Oops, TTVFS_VFS_H is defined, someone messed up and included VFS.h wrongly.
#endif
#include "VFSDefines.h"
#include <cstdlib>
#include <cstring>
#include <string>
#include <cassert>
VFS_NAMESPACE_START
inline char *allocHelper(allocator_func alloc, size_t size)
{
return alloc ? (char*)alloc(size) : new char[size];
}
inline char *allocHelperExtra(allocator_func alloc, size_t size, size_t extra)
{
char *p = (char*)allocHelper(alloc, size + extra);
memset(p + size, 0, extra);
return p;
}
template <typename T> inline void deleteHelper(delete_func deletor, T *mem)
{
if(deletor)
deletor(mem);
else
delete [] mem;
}
VFS_NAMESPACE_END
#if _MSC_VER
# ifndef _CRT_SECURE_NO_WARNINGS
# define _CRT_SECURE_NO_WARNINGS
# endif
#ifndef _CRT_SECURE_NO_DEPRECATE
# define _CRT_SECURE_NO_DEPRECATE
#endif
# pragma warning(disable: 4355) // 'this' : used in base member initializer list
#endif
#endif

View file

@ -0,0 +1,121 @@
// VFSLoader.cpp - late loading of files not in the tree
// For conditions of distribution and use, see copyright notice in VFS.h
#include "VFSInternal.h"
#include "VFSTools.h"
#include "VFSFile.h"
#include "VFSDir.h"
#include "VFSLoader.h"
VFS_NAMESPACE_START
#if !defined(_WIN32) && defined(VFS_IGNORE_CASE)
#include <dirent.h>
// based on code in PhysicsFS: http://icculus.org/physfs/
static bool locateOneElement(char *buf)
{
char *ptr;
DIR *dirp;
ptr = strrchr(buf, '/'); // find entry at end of path.
//printf("locateOneElem: buf='%s' ptr='%s'\n", ptr, buf);
if (ptr == NULL)
{
dirp = opendir(".");
ptr = buf;
}
else
{
if(ptr != buf) // strip only if not abs path
*ptr = '\0';
//printf("opendir: '%s'\n", buf);
dirp = opendir(buf);
*ptr = '/';
ptr++; // point past dirsep to entry itself.
}
//printf("dirp = %p\n", dirp);
struct dirent *dent;
while ((dent = readdir(dirp)) != NULL)
{
if (strcasecmp(dent->d_name, ptr) == 0)
{
strcpy(ptr, dent->d_name); // found a match. Overwrite with this case.
closedir(dirp);
return true;
}
}
// no match at all...
closedir(dirp);
return false;
}
static bool findFileHarder(char *fn)
{
char *ptr = fn;
bool found = true;
while ((ptr = strchr(ptr + 1, '/')) != 0)
{
*ptr = '\0';
found = locateOneElement(fn);
*ptr = '/'; // restore path separator
if (!found)
return false;
}
// check final element...
found = found && locateOneElement(fn);
//printf("tt: Fixed case '%s' [%s]\n", fn, found ? "found" : "NOT FOUND"); // TEMP
return found;
}
#endif
VFSFile *VFSLoaderDisk::Load(const char *fn, const char * /*ignored*/)
{
if(FileExists(fn))
return new VFSFileReal(fn); // must contain full file name
VFSFileReal *vf = NULL;
#if !defined(_WIN32) && defined(VFS_IGNORE_CASE)
size_t s = strlen(fn);
char *t = (char*)VFS_STACK_ALLOC(s+1);
memcpy(t, fn, s+1); // copy terminating '\0' as well
if(findFileHarder(&t[0])) // fixes the filename on the way
vf = new VFSFileReal(&t[0]);
VFS_STACK_FREE(t);
#endif
return vf;
}
VFSDir *VFSLoaderDisk::LoadDir(const char *fn, const char * /*ignored*/)
{
if(IsDirectory(fn))
return new VFSDirReal(fn); // must contain full file name
VFSDirReal *ret = NULL;
#if !defined(_WIN32) && defined(VFS_IGNORE_CASE)
size_t s = strlen(fn);
char *t = (char*)VFS_STACK_ALLOC(s+1);
memcpy(t, fn, s+1); // copy terminating '\0' as well
if(findFileHarder(&t[0])) // fixes the filename on the way
{
ret = new VFSDirReal(&t[0]);
}
VFS_STACK_FREE(t);
#endif
return ret;
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,35 @@
// VFSLoader.h - late loading of files not in the tree
// For conditions of distribution and use, see copyright notice in VFS.h
#ifndef VFSLOADER_H
#define VFSLOADER_H
#include <cstddef>
#include "VFSDefines.h"
VFS_NAMESPACE_START
class VFSFile;
class VFSDir;
// VFSLoader - to be called if a file is not in the tree.
class VFSLoader
{
public:
virtual ~VFSLoader() {}
virtual VFSFile *Load(const char *fn, const char *unmangled) = 0;
virtual VFSDir *LoadDir(const char *fn, const char *unmangled) { return NULL; }
};
class VFSLoaderDisk : public VFSLoader
{
public:
virtual ~VFSLoaderDisk() {}
virtual VFSFile *Load(const char *fn, const char *unmangled);
virtual VFSDir *LoadDir(const char *fn, const char *unmangled);
};
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,44 @@
#ifndef SELFREFCOUNTER_H
#define SELFREFCOUNTER_H
#include "VFSDefines.h"
#include "VFSAtomic.h"
VFS_NAMESPACE_START
// self must point to the object that holds the counter.
template <class T, bool DELSELF = true> class SelfRefCounter
{
private:
T *self;
volatile int c;
SelfRefCounter(SelfRefCounter& r); // forbid copy constructor
inline unsigned int _deref(void)
{
volatile unsigned int cc = (unsigned int)Atomic_Decr(c); // copy c, in case we get deleted
if(DELSELF && !cc)
{
delete self;
}
return cc;
}
public:
SelfRefCounter(T *p): self(p), c(1) {}
~SelfRefCounter() { /* DEBUG(ASSERT(c <= 1)); */ } // its ok if the last reference calls delete instead of _deref()
inline unsigned int count(void) { return c; }
// post-increment (dummy int)
inline unsigned int operator++(int) { unsigned int cc = c; Atomic_Incr(c); return cc; }
inline unsigned int operator--(int) { unsigned int cc = c; _deref(); return cc; }
// pre-increment
inline unsigned int operator++(void) { return (unsigned int)Atomic_Incr(c); }
inline unsigned int operator--(void) { return _deref(); }
};
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,76 @@
#ifdef _WIN32
# include <shlobj.h>
#endif
#include "VFSSystemPaths.h"
#include "VFSTools.h"
#include "VFSInternal.h"
VFS_NAMESPACE_START
std::string GetUserDir()
{
#ifdef _WIN32
TCHAR szPath[MAX_PATH];
if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, 0, szPath)))
{
return szPath;
}
// Fallback
const char *user = getenv("USERPROFILE");
if(user)
return user;
// Sorry, windoze :(
return "";
#else // Assume POSIX compliance
const char *user = getenv("HOME");
if(user)
return user;
#endif
}
std::string GetAppDir(const char *appname)
{
std::string ret;
#ifdef _WIN32
TCHAR szPath[MAX_PATH];
if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, szPath)))
{
ret = szPath;
}
else
{
// Fallback
const char *user = getenv("APPDATA");
if(user)
ret = user;
else
ret = "."; // Seems we have no other choice
}
return FixPath(ret + '/' + appname);
#else // Assume POSIX compliance
const char *user = getenv("HOME");
if(user)
ret = user;
else
ret = ".";
return FixPath(ret + "/." + appname); // just in case
#endif
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,20 @@
#ifndef VFS_SYSTEM_PATHS_H
#define VFS_SYSTEM_PATHS_H
#include <cstddef>
#include <string>
#include "VFSDefines.h"
VFS_NAMESPACE_START
// Returns the current user's home directory, without terminating '/'
std::string GetUserDir();
// Returns a per-user directory suitable to store application specific data,
// without terminating '/'
std::string GetAppDir(const char *appname);
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,504 @@
// VFSTools.cpp - useful functions and misc stuff
// For conditions of distribution and use, see copyright notice in VFS.h
#include "VFSInternal.h"
#include "VFSFileFuncs.h"
#include <cstdlib>
#include <algorithm>
#include <cctype>
#include <stack>
#include <cstdio>
#include "VFSTools.h"
#if _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <sys/dir.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
#endif
VFS_NAMESPACE_START
std::string stringToLower(std::string s)
{
std::transform(s.begin(), s.end(), s.begin(), tolower);
return s;
}
std::string stringToUpper(std::string s)
{
std::transform(s.begin(), s.end(), s.begin(), toupper);
return s;
}
void makeLowercase(std::string& s)
{
std::transform(s.begin(), s.end(), s.begin(), tolower);
}
void makeUppercase(std::string& s)
{
std::transform(s.begin(), s.end(), s.begin(), toupper);
}
#if !_WIN32
static bool _IsFile(const char *path, dirent *dp)
{
switch(dp->d_type)
{
case DT_DIR:
return false;
case DT_LNK:
{
std::string fullname = path;
fullname += '/';
fullname += dp->d_name;
struct stat statbuf;
if(stat(fullname.c_str(), &statbuf))
return false; // error
return !S_ISDIR(statbuf.st_mode);
}
// TODO: for now, we consider other file types as regular files
default:
;
}
return true;
}
static bool _IsDir(const char *path, dirent *dp)
{
switch(dp->d_type)
{
case DT_DIR:
return true;
case DT_LNK:
{
std::string fullname = path;
fullname += '/';
fullname += dp->d_name;
struct stat statbuf;
if(stat(fullname.c_str(), &statbuf))
return false; // error
return S_ISDIR(statbuf.st_mode);
}
default:
;
}
return false;
}
#endif
// returns list of *plain* file names in given directory,
// without paths, and without anything else
void GetFileList(const char *path, StringList& files)
{
#if !_WIN32
DIR * dirp;
struct dirent * dp;
dirp = opendir(path);
if(dirp)
{
while((dp=readdir(dirp)) != NULL)
{
if (_IsFile(path, dp)) // only add if it is not a directory
{
std::string s(dp->d_name);
files.push_back(s);
}
}
closedir(dirp);
}
# else
WIN32_FIND_DATA fil;
std::string search(path);
MakeSlashTerminated(search);
search += "*";
HANDLE hFil = FindFirstFile(search.c_str(),&fil);
if(hFil != INVALID_HANDLE_VALUE)
{
do
{
if(!(fil.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
std::string s(fil.cFileName);
files.push_back(s);
}
}
while(FindNextFile(hFil, &fil));
FindClose(hFil);
}
# endif
}
// returns a list of directory names in the given directory, *without* the source dir.
// if getting the dir list recursively, all paths are added, except *again* the top source dir beeing queried.
void GetDirList(const char *path, StringList &dirs, bool recursive /* = false */)
{
#if !_WIN32
DIR * dirp;
struct dirent * dp;
dirp = opendir(path);
if(dirp)
{
while((dp = readdir(dirp))) // assignment is intentional
{
if (_IsDir(path, dp)) // only add if it is a directory
{
if(strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0)
{
dirs.push_back(dp->d_name);
if (recursive) // needing a better way to do that
{
std::deque<std::string> newdirs;
GetDirList(dp->d_name, newdirs, true);
std::string d(dp->d_name);
for(std::deque<std::string>::iterator it = newdirs.begin(); it != newdirs.end(); ++it)
dirs.push_back(d + *it);
}
}
}
}
closedir(dirp);
}
#else
std::string search(path);
MakeSlashTerminated(search);
search += "*";
WIN32_FIND_DATA fil;
HANDLE hFil = FindFirstFile(search.c_str(),&fil);
if(hFil != INVALID_HANDLE_VALUE)
{
do
{
if( fil.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
{
if (!strcmp(fil.cFileName, ".") || !strcmp(fil.cFileName, ".."))
continue;
std::string d(fil.cFileName);
dirs.push_back(d);
if (recursive) // need a better way to do that
{
StringList newdirs;
GetDirList(d.c_str(), newdirs, true);
for(std::deque<std::string>::iterator it = newdirs.begin(); it != newdirs.end(); ++it)
dirs.push_back(d + *it);
}
}
}
while(FindNextFile(hFil, &fil));
FindClose(hFil);
}
#endif
}
bool FileExists(const char *fn)
{
#ifdef _WIN32
void *fp = real_fopen(fn, "rb");
if(fp)
{
real_fclose(fp);
return true;
}
return false;
#else
return access(fn, F_OK) == 0;
#endif
}
// must return true if creating the directory was successful, or already exists
bool CreateDir(const char *dir)
{
if(IsDirectory(dir)) // do not try to create if it already exists
return true;
bool result;
# if _WIN32
result = !!::CreateDirectory(dir, NULL);
# else
result = !mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
return result;
}
bool CreateDirRec(const char *dir)
{
if(IsDirectory(dir))
return true;
bool result = true;
StringList li;
StrSplit(dir, "/\\", li, false);
std::string d;
d.reserve(strlen(dir));
bool last;
for(StringList::iterator it = li.begin(); it != li.end(); ++it)
{
d += *it;
last = CreateDir(d.c_str());
result = last && result;
d += '/';
}
return result || last;
}
vfspos GetFileSize(const char* fn)
{
if(!fn || !*fn)
return 0;
void *fp = real_fopen(fn, "rb");
if(!fp)
return 0;
real_fseek(fp, 0, SEEK_END);
vfspos s = real_ftell(fp);
real_fclose(fp);
return s == npos ? 0 : s;
}
std::string FixSlashes(const std::string& s)
{
std::string r;
r.reserve(s.length() + 1);
char last = 0, cur;
for(size_t i = 0; i < s.length(); ++i)
{
cur = s[i];
if(cur == '\\')
cur = '/';
if(last == '/' && cur == '/')
continue;
r += cur;
last = cur;
}
return r;
}
std::string FixPath(const std::string& s)
{
if(s.empty())
return s;
const char *p = s.c_str();
while(p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
p += 2;
if(!*p)
return "";
char end = s[s.length() - 1];
if(end == '/' || end == '\\')
{
std::string r(p);
r.erase(r.length() - 1); // strip trailing '/'
return FixSlashes(r);
}
return FixSlashes(p);
}
bool IsDirectory(const char *s)
{
#if _WIN32
DWORD dwFileAttr = GetFileAttributes(s);
if(dwFileAttr == INVALID_FILE_ATTRIBUTES)
return false;
return !!(dwFileAttr & FILE_ATTRIBUTE_DIRECTORY);
#else
if ( access( s, 0 ) == 0 )
{
struct stat status;
stat( s, &status );
return status.st_mode & S_IFDIR; // FIXME: what about symlinks here?
}
return false;
#endif
}
void MakeSlashTerminated(std::string& s)
{
if(s.length() && s[s.length() - 1] != '/')
s += '/';
}
// extracts the file name from a given path
const char *PathToFileName(const char *str)
{
const char *p = strrchr(str, '/');
return p ? p+1 : str;
}
std::string StripFileExtension(const std::string& s)
{
size_t pos = s.find_last_of('.');
size_t pos2 = s.find_last_of('/');
if(pos != std::string::npos && (pos2 < pos || pos2 == std::string::npos))
return s.substr(0, pos);
return s;
}
std::string StripLastPath(const std::string& s)
{
if(s.empty())
return "";
if(s[s.length() - 1] == '/')
return StripLastPath(s.substr(0, s.length() - 1));
size_t pos = s.find_last_of('/');
if(pos == std::string::npos)
return ""; // nothing remains
return s.substr(0, pos);
}
void GetFileListRecursive(std::string dir, StringList& files, bool withQueriedDir /* = false */)
{
std::stack<std::string> stk;
if(withQueriedDir)
{
stk.push(dir);
while(stk.size())
{
dir = stk.top();
stk.pop();
MakeSlashTerminated(dir);
StringList li;
GetFileList(dir.c_str(), li);
for(std::deque<std::string>::iterator it = li.begin(); it != li.end(); ++it)
files.push_back(dir + *it);
li.clear();
GetDirList(dir.c_str(), li, true);
for(std::deque<std::string>::iterator it = li.begin(); it != li.end(); ++it)
stk.push(dir + *it);
}
}
else
{
std::string topdir = dir;
MakeSlashTerminated(topdir);
stk.push("");
while(stk.size())
{
dir = stk.top();
stk.pop();
MakeSlashTerminated(dir);
StringList li;
dir = topdir + dir;
GetFileList(dir.c_str(), li);
for(std::deque<std::string>::iterator it = li.begin(); it != li.end(); ++it)
files.push_back(dir + *it);
li.clear();
GetDirList(dir.c_str(), li, true);
for(std::deque<std::string>::iterator it = li.begin(); it != li.end(); ++it)
stk.push(dir + *it);
}
}
}
// from http://board.byuu.org/viewtopic.php?f=10&t=1089&start=15
bool WildcardMatch(const char *str, const char *pattern)
{
const char *cp = 0, *mp = 0;
while(*str && *pattern != '*')
{
if(*pattern != *str && *pattern != '?')
return false;
pattern++, str++;
}
while(*str)
{
if(*pattern == '*')
{
if(!*++pattern)
return 1;
mp = pattern;
cp = str + 1;
}
else if(*pattern == *str || *pattern == '?')
{
++pattern;
++str;
}
else
{
pattern = mp;
str = cp++;
}
}
while(*pattern++ == '*');
return !*pattern;
}
// copy strings, mangling newlines to system standard
// windows has 13+10
// *nix has 10
// exotic systems may have 10+13
size_t strnNLcpy(char *dst, const char *src, unsigned int n /* = -1 */)
{
char *olddst = dst;
bool had10 = false, had13 = false;
--n; // reserve 1 for \0 at end
while(*src && n)
{
if((had13 && *src == 10) || (had10 && *src == 13))
{
++src; // last was already mangled
had13 = had10 = false; // processed one CRLF pair
continue;
}
had10 = *src == 10;
had13 = *src == 13;
if(had10 || had13)
{
*dst++ = '\n';
++src;
}
else
*dst++ = *src++;
--n;
}
*dst++ = 0;
return dst - olddst;
}
// Directly appends 'add' to 's', ensuring that 's' is null-terminated.
// Returns the next write position for fastcat (the null byte at the end).
char *fastcat(char *s, const char *add)
{
size_t len = strlen(add);
memcpy(s, add, len);
s += len;
*(s + 1) = 0;
return s;
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,100 @@
// VFSTools.h - useful functions and misc stuff
// For conditions of distribution and use, see copyright notice in VFS.h
#ifndef VFS_TOOLS_H
#define VFS_TOOLS_H
#include <deque>
#include <string>
#include "VFSDefines.h"
VFS_NAMESPACE_START
typedef std::deque<std::string> StringList;
std::string stringToUpper(const std::string& s);
std::string stringToLower(const std::string& s);
void makeUppercase(std::string& s);
void makeLowercase(std::string& s);
void GetFileList(const char *, StringList& files);
void GetDirList(const char *, StringList& dirs, bool recursive = false);
bool FileExists(const char *);
bool IsDirectory(const char *);
bool CreateDir(const char*);
bool CreateDirRec(const char*);
vfspos GetFileSize(const char*);
std::string FixSlashes(const std::string& s);
std::string FixPath(const std::string& s);
const char *PathToFileName(const char *str);
void MakeSlashTerminated(std::string& s);
std::string StripFileExtension(const std::string& s);
std::string StripLastPath(const std::string& s);
void GetFileListRecursive(std::string dir, StringList& files, bool withQueriedDir = false);
bool WildcardMatch(const char *str, const char *pattern);
size_t strnNLcpy(char *dst, const char *src, unsigned int n = -1);
char *fastcat(char *s, const char *add);
template <class T> void StrSplit(const std::string &src, const std::string &sep, T& container, bool keepEmpty = false)
{
std::string s;
for (std::string::const_iterator i = src.begin(); i != src.end(); i++)
{
if (sep.find(*i) != std::string::npos)
{
if (keepEmpty || s.length())
container.push_back(s);
s = "";
}
else
{
s += *i;
}
}
if (keepEmpty || s.length())
container.push_back(s);
}
inline static size_t stringhash(const char *s)
{
size_t h = 0;
for( ; *s; ++s)
{
h += *s;
h += ( h << 10 );
h ^= ( h >> 6 );
}
h += ( h << 3 );
h ^= ( h >> 11 );
h += ( h << 15 );
return h;
}
inline static size_t stringhash_nocase(const char *s)
{
size_t h = 0;
for( ; *s; ++s)
{
h += tolower(*s);
h += ( h << 10 );
h ^= ( h >> 6 );
}
h += ( h << 3 );
h ^= ( h >> 11 );
h += ( h << 15 );
return h;
}
#ifdef VFS_IGNORE_CASE
# define STRINGHASH(s) stringhash_nocase(s)
#else
# define STRINGHASH(s) stringhash(s)
#endif
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,15 @@
set(ttvfs_zip_SRC
VFSDirZip.cpp
VFSDirZip.h
VFSFileZip.cpp
VFSFileZip.h
VFSZipArchiveLoader.cpp
VFSZipArchiveLoader.h
miniz.c
miniz.h
)
include_directories(${TTVFS_INCLUDE_DIRS})
add_library(ttvfs_zip ${ttvfs_zip_SRC})

View file

@ -0,0 +1,107 @@
#include "VFSFileZip.h"
#include "VFSDirZip.h"
#include "VFSTools.h"
#include "VFSInternal.h"
#include "miniz.h"
VFS_NAMESPACE_START
static size_t zip_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
{
VFSFile *vf = (VFSFile*)pOpaque;
mz_int64 cur_ofs = vf->getpos();
if((mz_int64)file_ofs < 0)
return 0;
if(cur_ofs != (mz_int64)file_ofs && !vf->seek((vfspos)file_ofs))
return 0;
return vf->read(pBuf, n);
}
static bool zip_reader_init_vfsfile(mz_zip_archive *pZip, VFSFile *vf, mz_uint32 flags)
{
if(!(pZip || vf))
return false;
vf->open("rb");
mz_uint64 file_size = vf->size();
if(!file_size)
{
vf->close();
return false;
}
pZip->m_pRead = zip_read_func;
pZip->m_pIO_opaque = vf;
if (!mz_zip_reader_init(pZip, file_size, flags))
{
vf->close();
return false;
}
return true;
}
VFSDirZip::VFSDirZip(VFSFile *zf)
: VFSDir(zf->fullname()), _zf(zf)
{
_zf->ref++;
_setOrigin(this);
memset(&_zip, 0, sizeof(_zip));
}
VFSDirZip::~VFSDirZip()
{
close();
_zf->ref--;
}
bool VFSDirZip::close(void)
{
mz_zip_reader_end(&_zip);
_zf->close();
return true;
}
VFSDir *VFSDirZip::createNew(const char *dir) const
{
return VFSDir::createNew(dir); // inside a Zip file; only the base dir can be a real VFSDirZip.
}
unsigned int VFSDirZip::load(bool /*ignored*/)
{
close();
if(!zip_reader_init_vfsfile(&_zip, _zf, 0))
return 0;
unsigned int files = mz_zip_reader_get_num_files(&_zip);
mz_zip_archive_file_stat fs;
for (unsigned int i = 0; i < files; ++i)
{
// FIXME: do we want empty dirs in the tree?
if(mz_zip_reader_is_file_a_directory(&_zip, i))
continue;
if(mz_zip_reader_is_file_encrypted(&_zip, i))
continue;
if(!mz_zip_reader_file_stat(&_zip, i, &fs))
continue;
if(getFile(fs.m_filename))
continue;
VFSFileZip *vf = new VFSFileZip(&_zip);
vf->_setOrigin(this);
memcpy(vf->getZipFileStat(), &fs, sizeof(mz_zip_archive_file_stat));
vf->_init();
addRecursive(vf, true, VFSDir::NONE);
vf->ref--;
}
// Not necessary to keep open all the time, VFSFileZip will re-open the archive if needed
//close();
return files;
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,32 @@
#ifndef VFSDIR_ZIP_H
#define VFSDIR_ZIP_H
#include "VFSDir.h"
#include "miniz.h"
VFS_NAMESPACE_START
class VFSFile;
class VFSDirZip : public VFSDir
{
public:
VFSDirZip(VFSFile *zf);
virtual ~VFSDirZip();
virtual unsigned int load(bool recusive);
virtual VFSDir *createNew(const char *dir) const;
virtual const char *getType() const { return "VFSDirZip"; }
virtual bool close();
inline mz_zip_archive *getZip() { return &_zip; }
protected:
VFSFile *_zf;
mz_zip_archive _zip;
std::string zipfilename;
};
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,197 @@
#include "VFSFileZip.h"
#include "VFSInternal.h"
#include "VFSTools.h"
#include "VFSDir.h"
VFS_NAMESPACE_START
// From miniz.c
//#define MZ_ZIP_MODE_READING 2
static bool zip_reader_reopen_vfsfile(mz_zip_archive *pZip, mz_uint32 flags)
{
if(!(pZip && pZip->m_pIO_opaque && pZip->m_pRead))
return false;
VFSFile *vf = (VFSFile*)pZip->m_pIO_opaque;
if(!vf->isopen())
if(!vf->open("rb"))
return false;
if(pZip->m_zip_mode == MZ_ZIP_MODE_READING)
return true;
mz_uint64 file_size = vf->size();
if(!file_size)
{
vf->close();
return false;
}
if (!mz_zip_reader_init(pZip, file_size, flags))
{
vf->close();
return false;
}
return true;
}
VFSFileZip::VFSFileZip(mz_zip_archive *zip)
: VFSFile(NULL), _fixedStr(NULL), _zip(zip)
{
_mode = "b"; // binary mode by default
_pos = 0;
}
void VFSFileZip::_init()
{
_setName(_zipstat.m_filename);
}
VFSFileZip::~VFSFileZip()
{
dropBuf(true);
}
bool VFSFileZip::open(const char *mode /* = NULL */)
{
VFS_GUARD_OPT(this);
_pos = 0;
if(mode)
{
if(_fixedStr && _mode != mode)
{
delete [] _fixedStr;
_fixedStr = NULL;
}
_mode = mode;
}
return true; // does not have to be opened
}
bool VFSFileZip::isopen(void) const
{
return true; // is always open
}
bool VFSFileZip::iseof(void) const
{
VFS_GUARD_OPT(this);
return _pos >= _zipstat.m_uncomp_size;
}
bool VFSFileZip::close(void)
{
//return flush(); // TODO: write to zip file on close
return true;
}
bool VFSFileZip::seek(vfspos pos)
{
if(pos >= 0xFFFFFFFF) // zip files have uint32 range only
return false;
VFS_GUARD_OPT(this);
_pos = (unsigned int)pos;
return true;
}
bool VFSFileZip::flush(void)
{
// FIXME: use this to actually write to zip file?
return false;
}
vfspos VFSFileZip::getpos(void) const
{
VFS_GUARD_OPT(this);
return _pos;
}
unsigned int VFSFileZip::read(void *dst, unsigned int bytes)
{
VFS_GUARD_OPT(this);
char *mem = (char*)getBuf();
char *startptr = mem + _pos;
char *endptr = mem + size();
bytes = std::min((unsigned int)(endptr - startptr), bytes); // limit in case reading over buffer size
if(_mode.find('b') == std::string::npos)
strnNLcpy((char*)dst, (const char*)startptr, bytes); // non-binary == text mode
else
memcpy(dst, startptr, bytes); // binary copy
_pos += bytes;
return bytes;
}
unsigned int VFSFileZip::write(const void *src, unsigned int bytes)
{
/*VFS_GUARD_OPT(this);
if(getpos() + bytes >= size())
size(getpos() + bytes); // enlarge if necessary
memcpy(_buf + getpos(), src, bytes);
// TODO: implement actually writing to the Zip file.
return bytes;*/
return VFSFile::write(src, bytes);
}
vfspos VFSFileZip::size(void)
{
VFS_GUARD_OPT(this);
return (vfspos)_zipstat.m_uncomp_size;
}
const void *VFSFileZip::getBuf(allocator_func alloc /* = NULL */, delete_func del /* = NULL */)
{
assert(!alloc == !del); // either both or none may be defined. Checked extra early to prevent possible errors later.
VFS_GUARD_OPT(this);
// _fixedStr gets deleted on mode change, so doing this check here is fine
if(_fixedStr)
return _fixedStr;
if(!_buf)
{
size_t sz = (size_t)size();
_buf = allocHelperExtra(alloc, sz, 4);
if(!_buf)
return NULL;
_delfunc = del;
if(!zip_reader_reopen_vfsfile(_zip, 0))
return false; // can happen if the underlying zip file was deleted
if(!mz_zip_reader_extract_to_mem(_zip, _zipstat.m_file_index, _buf, sz, 0))
return false; // this should not happen
if(_mode.find("b") == std::string::npos) // text mode?
{
_fixedStr = allocHelperExtra(alloc, sz, 4);
strnNLcpy(_fixedStr, (const char*)_buf);
// FIXME: is this really correct?
VFSFile::dropBuf(true);
return _fixedStr;
}
}
return _buf;
}
void VFSFileZip::dropBuf(bool del)
{
VFSFile::dropBuf(del);
VFS_GUARD_OPT(this);
if(del)
delBuf(_fixedStr);
_fixedStr = NULL;
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,41 @@
#ifndef VFSFILE_ZIP_H
#define VFSFILE_ZIP_H
#include "VFSFile.h"
#include "miniz.h"
VFS_NAMESPACE_START
class VFSFileZip : public VFSFile
{
public:
VFSFileZip(mz_zip_archive *zip);
virtual ~VFSFileZip();
virtual bool open(const char *mode = NULL);
virtual bool isopen(void) const;
virtual bool iseof(void) const;
virtual bool close(void);
virtual bool seek(vfspos pos);
virtual bool flush(void);
virtual vfspos getpos(void) const;
virtual unsigned int read(void *dst, unsigned int bytes);
virtual unsigned int write(const void *src, unsigned int bytes);
virtual vfspos size(void);
virtual const void *getBuf(allocator_func alloc = NULL, delete_func del = NULL);
virtual void dropBuf(bool del);
virtual const char *getType(void) const { return "Zip"; }
inline mz_zip_archive_file_stat *getZipFileStat(void) { return &_zipstat; }
void _init();
protected:
unsigned int _pos;
std::string _mode;
mz_zip_archive_file_stat _zipstat;
mz_zip_archive *_zip;
char *_fixedStr; // for \n fixed string in text mode. cleared when mode is changed
};
VFS_NAMESPACE_END
#endif

View file

@ -0,0 +1,17 @@
#include "VFSInternal.h"
#include "VFSZipArchiveLoader.h"
#include "VFSDirZip.h"
VFS_NAMESPACE_START
VFSDir *VFSZipArchiveLoader::Load(VFSFile *arch, VFSLoader ** /*unused*/, void * /*unused*/)
{
VFSDirZip *vd = new VFSDirZip(arch);
if(vd->load(true))
return vd;
vd->ref--;
return NULL;
}
VFS_NAMESPACE_END

View file

@ -0,0 +1,17 @@
#ifndef VFS_ZIP_ARCHIVE_LOADER_H
#define VFS_ZIP_ARCHIVE_LOADER_H
#include "VFSArchiveLoader.h"
VFS_NAMESPACE_START
class VFSZipArchiveLoader : public VFSArchiveLoader
{
public:
virtual ~VFSZipArchiveLoader() {}
virtual VFSDir *Load(VFSFile *arch, VFSLoader **ldr, void *opaque = NULL);
};
VFS_NAMESPACE_END
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
#ifndef MINIZ_H
#define MINIZ_H
#define MINIZ_HEADER_FILE_ONLY
#include "miniz.c"
#endif