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:
parent
99e3f5ebe2
commit
a90f57afb0
36 changed files with 10047 additions and 0 deletions
841
ExternalLibs/minihttp.cpp
Normal file
841
ExternalLibs/minihttp.cpp
Normal 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
230
ExternalLibs/minihttp.h
Normal 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
|
29
ExternalLibs/ttvfs/CMakeLists.txt
Normal file
29
ExternalLibs/ttvfs/CMakeLists.txt
Normal 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
80
ExternalLibs/ttvfs/VFS.h
Normal 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
|
68
ExternalLibs/ttvfs/VFSArchiveLoader.h
Normal file
68
ExternalLibs/ttvfs/VFSArchiveLoader.h
Normal 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
|
103
ExternalLibs/ttvfs/VFSAtomic.cpp
Normal file
103
ExternalLibs/ttvfs/VFSAtomic.cpp
Normal 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
|
39
ExternalLibs/ttvfs/VFSAtomic.h
Normal file
39
ExternalLibs/ttvfs/VFSAtomic.h
Normal 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
|
32
ExternalLibs/ttvfs/VFSBase.cpp
Normal file
32
ExternalLibs/ttvfs/VFSBase.cpp
Normal 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
|
83
ExternalLibs/ttvfs/VFSBase.h
Normal file
83
ExternalLibs/ttvfs/VFSBase.h
Normal 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
|
101
ExternalLibs/ttvfs/VFSDefines.h
Normal file
101
ExternalLibs/ttvfs/VFSDefines.h
Normal 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
|
441
ExternalLibs/ttvfs/VFSDir.cpp
Normal file
441
ExternalLibs/ttvfs/VFSDir.cpp
Normal 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
213
ExternalLibs/ttvfs/VFSDir.h
Normal 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
|
237
ExternalLibs/ttvfs/VFSFile.cpp
Normal file
237
ExternalLibs/ttvfs/VFSFile.cpp
Normal 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
|
165
ExternalLibs/ttvfs/VFSFile.h
Normal file
165
ExternalLibs/ttvfs/VFSFile.h
Normal 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
|
78
ExternalLibs/ttvfs/VFSFileFuncs.cpp
Normal file
78
ExternalLibs/ttvfs/VFSFileFuncs.cpp
Normal 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
|
15
ExternalLibs/ttvfs/VFSFileFuncs.h
Normal file
15
ExternalLibs/ttvfs/VFSFileFuncs.h
Normal 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
|
394
ExternalLibs/ttvfs/VFSHashmap.h
Normal file
394
ExternalLibs/ttvfs/VFSHashmap.h
Normal 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
|
483
ExternalLibs/ttvfs/VFSHelper.cpp
Normal file
483
ExternalLibs/ttvfs/VFSHelper.cpp
Normal 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
|
191
ExternalLibs/ttvfs/VFSHelper.h
Normal file
191
ExternalLibs/ttvfs/VFSHelper.h
Normal 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
|
57
ExternalLibs/ttvfs/VFSInternal.h
Normal file
57
ExternalLibs/ttvfs/VFSInternal.h
Normal 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
|
121
ExternalLibs/ttvfs/VFSLoader.cpp
Normal file
121
ExternalLibs/ttvfs/VFSLoader.cpp
Normal 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
|
35
ExternalLibs/ttvfs/VFSLoader.h
Normal file
35
ExternalLibs/ttvfs/VFSLoader.h
Normal 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
|
44
ExternalLibs/ttvfs/VFSSelfRefCounter.h
Normal file
44
ExternalLibs/ttvfs/VFSSelfRefCounter.h
Normal 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
|
76
ExternalLibs/ttvfs/VFSSystemPaths.cpp
Normal file
76
ExternalLibs/ttvfs/VFSSystemPaths.cpp
Normal 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
|
20
ExternalLibs/ttvfs/VFSSystemPaths.h
Normal file
20
ExternalLibs/ttvfs/VFSSystemPaths.h
Normal 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
|
504
ExternalLibs/ttvfs/VFSTools.cpp
Normal file
504
ExternalLibs/ttvfs/VFSTools.cpp
Normal 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
|
100
ExternalLibs/ttvfs/VFSTools.h
Normal file
100
ExternalLibs/ttvfs/VFSTools.h
Normal 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
|
15
ExternalLibs/ttvfs_zip/CMakeLists.txt
Normal file
15
ExternalLibs/ttvfs_zip/CMakeLists.txt
Normal 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})
|
107
ExternalLibs/ttvfs_zip/VFSDirZip.cpp
Normal file
107
ExternalLibs/ttvfs_zip/VFSDirZip.cpp
Normal 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
|
32
ExternalLibs/ttvfs_zip/VFSDirZip.h
Normal file
32
ExternalLibs/ttvfs_zip/VFSDirZip.h
Normal 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
|
197
ExternalLibs/ttvfs_zip/VFSFileZip.cpp
Normal file
197
ExternalLibs/ttvfs_zip/VFSFileZip.cpp
Normal 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
|
41
ExternalLibs/ttvfs_zip/VFSFileZip.h
Normal file
41
ExternalLibs/ttvfs_zip/VFSFileZip.h
Normal 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
|
17
ExternalLibs/ttvfs_zip/VFSZipArchiveLoader.cpp
Normal file
17
ExternalLibs/ttvfs_zip/VFSZipArchiveLoader.cpp
Normal 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
|
17
ExternalLibs/ttvfs_zip/VFSZipArchiveLoader.h
Normal file
17
ExternalLibs/ttvfs_zip/VFSZipArchiveLoader.h
Normal 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
|
4834
ExternalLibs/ttvfs_zip/miniz.c
Normal file
4834
ExternalLibs/ttvfs_zip/miniz.c
Normal file
File diff suppressed because it is too large
Load diff
7
ExternalLibs/ttvfs_zip/miniz.h
Normal file
7
ExternalLibs/ttvfs_zip/miniz.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef MINIZ_H
|
||||
#define MINIZ_H
|
||||
|
||||
#define MINIZ_HEADER_FILE_ONLY
|
||||
#include "miniz.c"
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue