mirror of
https://github.com/AquariaOSE/Aquaria.git
synced 2025-07-05 15:34:48 +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
Add a link
Reference in a new issue