summaryrefslogtreecommitdiff
path: root/src/fs/virtfs
diff options
context:
space:
mode:
Diffstat (limited to 'src/fs/virtfs')
-rw-r--r--src/fs/virtfs/virtdirentry.cpp34
-rw-r--r--src/fs/virtfs/virtdirentry.h41
-rw-r--r--src/fs/virtfs/virtfileprivate.cpp59
-rw-r--r--src/fs/virtfs/virtfileprivate.h50
-rw-r--r--src/fs/virtfs/virtfs.cpp257
-rw-r--r--src/fs/virtfs/virtfsdir.cpp651
-rw-r--r--src/fs/virtfs/virtfsdir.h89
-rw-r--r--src/fs/virtfs/virtfsdir_unittest.cc717
-rw-r--r--src/fs/virtfs/virtfszip.cpp555
-rw-r--r--src/fs/virtfs/virtfszip.h88
-rw-r--r--src/fs/virtfs/virtfszip_unittest.cc745
-rw-r--r--src/fs/virtfs/virtzipentry.cpp38
-rw-r--r--src/fs/virtfs/virtzipentry.h45
-rw-r--r--src/fs/virtfs/zip.cpp291
-rw-r--r--src/fs/virtfs/zip.h42
-rw-r--r--src/fs/virtfs/zip_unittest.cc280
-rw-r--r--src/fs/virtfs/ziplocalheader.cpp39
-rw-r--r--src/fs/virtfs/ziplocalheader.h44
18 files changed, 4065 insertions, 0 deletions
diff --git a/src/fs/virtfs/virtdirentry.cpp b/src/fs/virtfs/virtdirentry.cpp
new file mode 100644
index 000000000..73d2dc175
--- /dev/null
+++ b/src/fs/virtfs/virtdirentry.cpp
@@ -0,0 +1,34 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fs/virtfs/virtdirentry.h"
+
+#include "debug.h"
+
+VirtDirEntry::VirtDirEntry(const std::string &userDir,
+ const std::string &rootDir) :
+ mUserDir(userDir),
+ mRootDir(rootDir)
+{
+}
+
+VirtDirEntry::~VirtDirEntry()
+{
+}
diff --git a/src/fs/virtfs/virtdirentry.h b/src/fs/virtfs/virtdirentry.h
new file mode 100644
index 000000000..b3d3faff6
--- /dev/null
+++ b/src/fs/virtfs/virtdirentry.h
@@ -0,0 +1,41 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTILS_VIRTDIRENTRY_H
+#define UTILS_VIRTDIRENTRY_H
+
+#include <string>
+
+#include "localconsts.h"
+
+struct VirtDirEntry final
+{
+ VirtDirEntry(const std::string &userDir,
+ const std::string &rootDir);
+
+ A_DELETE_COPY(VirtDirEntry)
+
+ ~VirtDirEntry();
+
+ std::string mUserDir;
+ std::string mRootDir;
+};
+
+#endif // UTILS_VIRTDIRENTRY_H
diff --git a/src/fs/virtfs/virtfileprivate.cpp b/src/fs/virtfs/virtfileprivate.cpp
new file mode 100644
index 000000000..06e0418bb
--- /dev/null
+++ b/src/fs/virtfs/virtfileprivate.cpp
@@ -0,0 +1,59 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fs/virtfs/virtfileprivate.h"
+
+#include <unistd.h>
+#include <zlib.h>
+
+#include "debug.h"
+
+VirtFilePrivate::VirtFilePrivate() :
+ mBuf(nullptr),
+ mPos(0U),
+ mSize(0U),
+ mFd(-1)
+{
+}
+
+VirtFilePrivate::VirtFilePrivate(const int fd) :
+ mBuf(nullptr),
+ mPos(0U),
+ mSize(0U),
+ mFd(fd)
+{
+}
+
+VirtFilePrivate::VirtFilePrivate(uint8_t *restrict const buf,
+ const size_t sz) :
+ mBuf(buf),
+ mPos(0U),
+ mSize(sz),
+ mFd(-1)
+{
+}
+
+VirtFilePrivate::~VirtFilePrivate()
+{
+ if (mFd != -1)
+ close(mFd);
+ if (mBuf)
+ delete [] mBuf;
+}
diff --git a/src/fs/virtfs/virtfileprivate.h b/src/fs/virtfs/virtfileprivate.h
new file mode 100644
index 000000000..0af9f66e8
--- /dev/null
+++ b/src/fs/virtfs/virtfileprivate.h
@@ -0,0 +1,50 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTILS_VIRTFILEPRIVATE_H
+#define UTILS_VIRTFILEPRIVATE_H
+
+#include "localconsts.h"
+
+struct VirtFilePrivate final
+{
+ VirtFilePrivate();
+
+ explicit VirtFilePrivate(const int fd);
+
+ VirtFilePrivate(uint8_t *restrict const buf,
+ const size_t sz);
+
+ A_DELETE_COPY(VirtFilePrivate)
+
+ ~VirtFilePrivate();
+
+ // zipfs fields
+ uint8_t *mBuf;
+
+ // zipfs fields
+ size_t mPos;
+ size_t mSize;
+
+ // dirfs fields
+ int mFd;
+};
+
+#endif // UTILS_VIRTFILEPRIVATE_H
diff --git a/src/fs/virtfs/virtfs.cpp b/src/fs/virtfs/virtfs.cpp
new file mode 100644
index 000000000..3d223ccba
--- /dev/null
+++ b/src/fs/virtfs/virtfs.cpp
@@ -0,0 +1,257 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2013-2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fs/virtfs.h"
+
+#include "fs/paths.h"
+#include "fs/virtfile.h"
+#include "fs/virtfsfuncs.h"
+#include "fs/virtlist.h"
+
+#include "fs/virtfs/virtdirentry.h"
+#include "fs/virtfs/virtfsdir.h"
+#include "fs/virtfs/virtfszip.h"
+
+#include "utils/checkutils.h"
+
+#include "debug.h"
+
+const char *dirSeparator = nullptr;
+
+namespace VirtFs
+{
+ void init(const std::string &restrict name)
+ {
+ VirtFsDir::init(name);
+ updateDirSeparator();
+ }
+
+ void updateDirSeparator()
+ {
+#ifdef WIN32
+ dirSeparator = "\\";
+#else // WIN32
+ dirSeparator = "/";
+#endif // WIN32
+ }
+
+ const char *getDirSeparator()
+ {
+ return dirSeparator;
+ }
+
+ const char *getBaseDir()
+ {
+ return VirtFsDir::getBaseDir();
+ }
+
+ const char *getUserDir()
+ {
+ return VirtFsDir::getUserDir();
+ }
+
+ bool exists(const std::string &restrict name)
+ {
+ return VirtFsDir::exists(name) || VirtFsZip::exists(name);
+ }
+
+ VirtList *enumerateFiles(std::string dirName)
+ {
+ VirtList *const list = new VirtList;
+ prepareFsPath(dirName);
+ if (checkPath(dirName) == false)
+ {
+ reportAlways("VirtFs::enumerateFiles invalid path: %s",
+ dirName.c_str());
+ return list;
+ }
+
+ VirtFsDir::enumerateFiles(dirName, list);
+ VirtFsZip::enumerateFiles(dirName, list);
+ return list;
+ }
+
+ bool isDirectory(std::string name)
+ {
+ prepareFsPath(name);
+ if (checkPath(name) == false)
+ {
+ reportAlways("VirtFs::isDirectory invalid path: %s",
+ name.c_str());
+ return false;
+ }
+ return VirtFsDir::isDirectoryInternal(name) ||
+ VirtFsZip::isDirectoryInternal(name);
+ }
+
+ bool isSymbolicLink(const std::string &restrict name)
+ {
+ return VirtFsDir::isSymbolicLink(name);
+ }
+
+ void freeList(VirtList *restrict const handle)
+ {
+ delete handle;
+ }
+
+ VirtFile *openRead(std::string filename)
+ {
+ prepareFsPath(filename);
+ if (checkPath(filename) == false)
+ {
+ reportAlways("VirtFs::openRead invalid path: %s",
+ filename.c_str());
+ return nullptr;
+ }
+ VirtDirEntry *const entry = VirtFsDir::searchEntryByPath(filename);
+ if (entry == nullptr)
+ return VirtFsZip::openReadInternal(filename);
+ return VirtFsDir::openReadDirEntry(entry, filename);
+ }
+
+ VirtFile *openWrite(const std::string &restrict filename)
+ {
+ return VirtFsDir::openWrite(filename);
+ }
+
+ VirtFile *openAppend(const std::string &restrict filename)
+ {
+ return VirtFsDir::openAppend(filename);
+ }
+
+ bool setWriteDir(const std::string &restrict newDir)
+ {
+ return VirtFsDir::setWriteDir(newDir);
+ }
+
+ bool addDirToSearchPath(const std::string &restrict newDir,
+ const Append append)
+ {
+ return VirtFsDir::addToSearchPath(newDir, append);
+ }
+
+ bool removeDirFromSearchPath(const std::string &restrict oldDir)
+ {
+ return VirtFsDir::removeFromSearchPath(oldDir);
+ }
+
+ bool addZipToSearchPath(const std::string &restrict newDir,
+ const Append append)
+ {
+ return VirtFsZip::addToSearchPath(newDir, append);
+ }
+
+ bool removeZipFromSearchPath(const std::string &restrict oldDir)
+ {
+ return VirtFsZip::removeFromSearchPath(oldDir);
+ }
+
+ std::string getRealDir(std::string filename)
+ {
+ prepareFsPath(filename);
+ if (checkPath(filename) == false)
+ {
+ reportAlways("VirtFs::getRealDir invalid path: %s",
+ filename.c_str());
+ return std::string();
+ }
+ VirtDirEntry *const entry = VirtFsDir::searchEntryByPath(filename);
+ if (entry == nullptr)
+ return VirtFsZip::getRealDirInternal(filename);
+ return entry->mUserDir;
+ }
+
+ bool mkdir(const std::string &restrict dirname)
+ {
+ return VirtFsDir::mkdir(dirname);
+ }
+
+ bool remove(const std::string &restrict filename)
+ {
+ return VirtFsDir::remove(filename);
+ }
+
+ bool deinit()
+ {
+ VirtFsDir::deinit();
+ return true;
+ }
+
+ void permitLinks(const bool val)
+ {
+ VirtFsDir::permitLinks(val);
+ }
+
+ const char *getLastError()
+ {
+ return "";
+ }
+
+ int close(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return 0;
+ return file->funcs->close(file);
+ }
+
+ int64_t read(VirtFile *restrict const file,
+ void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount)
+ {
+ return file->funcs->read(file,
+ buffer,
+ objSize,
+ objCount);
+ }
+
+ int64_t write(VirtFile *restrict const file,
+ const void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount)
+ {
+ return file->funcs->write(file,
+ buffer,
+ objSize,
+ objCount);
+ }
+
+ int64_t fileLength(VirtFile *restrict const file)
+ {
+ return file->funcs->fileLength(file);
+ }
+
+ int64_t tell(VirtFile *restrict const file)
+ {
+ return file->funcs->tell(file);
+ }
+
+ int seek(VirtFile *restrict const file,
+ const uint64_t pos)
+ {
+ return file->funcs->seek(file,
+ pos);
+ }
+
+ int eof(VirtFile *restrict const file)
+ {
+ return file->funcs->eof(file);
+ }
+} // namespace VirtFs
diff --git a/src/fs/virtfs/virtfsdir.cpp b/src/fs/virtfs/virtfsdir.cpp
new file mode 100644
index 000000000..13c2f9ba4
--- /dev/null
+++ b/src/fs/virtfs/virtfsdir.cpp
@@ -0,0 +1,651 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2013-2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fs/virtfs/virtfsdir.h"
+
+#include "fs/files.h"
+#include "fs/mkdir.h"
+#include "fs/paths.h"
+#include "fs/virtfs.h"
+#include "fs/virtfile.h"
+#include "fs/virtfsfuncs.h"
+#include "fs/virtlist.h"
+
+#include "fs/virtfs/virtdirentry.h"
+#include "fs/virtfs/virtfileprivate.h"
+
+#include "utils/checkutils.h"
+#include "utils/dtor.h"
+#include "utils/stringutils.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <iostream>
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "debug.h"
+
+extern const char *dirSeparator;
+
+namespace
+{
+ std::vector<VirtDirEntry*> mEntries;
+ std::string mWriteDir;
+ std::string mBaseDir;
+ std::string mUserDir;
+ bool mPermitLinks = false;
+ VirtFsFuncs funcs;
+} // namespace
+
+namespace VirtFsDir
+{
+ namespace
+ {
+ static VirtFile *openFile(std::string filename,
+ const int mode)
+ {
+ prepareFsPath(filename);
+ if (checkPath(filename) == false)
+ {
+ reportAlways("VirtFsDir::openFile invalid path: %s",
+ filename.c_str());
+ return nullptr;
+ }
+ VirtDirEntry *const entry = searchEntryByPath(filename);
+ if (entry == nullptr)
+ return nullptr;
+
+ const std::string path = entry->mRootDir + filename;
+ const int fd = open(path.c_str(),
+ mode,
+ S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ {
+ reportAlways("VirtFsDir::openFile file open error: %s",
+ filename.c_str());
+ return nullptr;
+ }
+ VirtFile *restrict const file = new VirtFile(&funcs);
+ file->mPrivate = new VirtFilePrivate(fd);
+
+ return file;
+ }
+ } // namespace
+
+ VirtFile *openReadDirEntry(VirtDirEntry *const entry,
+ const std::string &filename)
+ {
+ const std::string path = entry->mRootDir + filename;
+ const int fd = open(path.c_str(),
+ O_RDONLY,
+ S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ {
+ reportAlways("VirtFsDir::openReadDirEntry file open error: %s",
+ filename.c_str());
+ return nullptr;
+ }
+ VirtFile *restrict const file = new VirtFile(&funcs);
+ file->mPrivate = new VirtFilePrivate(fd);
+
+ return file;
+ }
+
+ VirtDirEntry *searchEntryByRoot(const std::string &restrict root)
+ {
+ FOR_EACH (std::vector<VirtDirEntry*>::const_iterator, it, mEntries)
+ {
+ if ((*it)->mRootDir == root)
+ return *it;
+ }
+ return nullptr;
+ }
+
+ VirtDirEntry *searchEntryByPath(const std::string &restrict path)
+ {
+ FOR_EACH (std::vector<VirtDirEntry*>::const_iterator, it, mEntries)
+ {
+ VirtDirEntry *const entry = *it;
+ if (Files::existsLocal(entry->mRootDir + path))
+ return entry;
+ }
+ return nullptr;
+ }
+
+ bool addToSearchPathSilent(std::string newDir,
+ const Append append,
+ const SkipError skipError)
+ {
+ prepareFsPath(newDir);
+ if (skipError == SkipError_false &&
+ Files::existsLocal(newDir) == false)
+ {
+ logger->log("VirtFsDir::addToSearchPath directory not exists: %s",
+ newDir.c_str());
+ return false;
+ }
+ if (newDir.find(".zip") != std::string::npos)
+ {
+ reportAlways("Called VirtFsDir::addToSearchPath with zip archive");
+ return false;
+ }
+ std::string rootDir = newDir;
+ if (findLast(rootDir, std::string(dirSeparator)) == false)
+ rootDir += dirSeparator;
+ VirtDirEntry *const entry = VirtFsDir::searchEntryByRoot(rootDir);
+ if (entry != nullptr)
+ {
+ reportAlways("VirtFsDir::addToSearchPath already exists: %s",
+ newDir.c_str());
+ return false;
+ }
+ logger->log("Add virtual directory: " + newDir);
+ if (append == Append_true)
+ {
+ mEntries.push_back(new VirtDirEntry(newDir,
+ rootDir));
+ }
+ else
+ {
+ mEntries.insert(mEntries.begin(),
+ new VirtDirEntry(newDir,
+ rootDir));
+ }
+ return true;
+ }
+
+ bool addToSearchPath(std::string newDir,
+ const Append append)
+ {
+ prepareFsPath(newDir);
+ if (Files::existsLocal(newDir) == false)
+ {
+ reportAlways("VirtFsDir::addToSearchPath directory not exists: %s",
+ newDir.c_str());
+ return false;
+ }
+ if (newDir.find(".zip") != std::string::npos)
+ {
+ reportAlways("Called VirtFsDir::addToSearchPath with zip archive");
+ return false;
+ }
+ std::string rootDir = newDir;
+ if (findLast(rootDir, std::string(dirSeparator)) == false)
+ rootDir += dirSeparator;
+ VirtDirEntry *const entry = VirtFsDir::searchEntryByRoot(rootDir);
+ if (entry != nullptr)
+ {
+ reportAlways("VirtFsDir::addToSearchPath already exists: %s",
+ newDir.c_str());
+ return false;
+ }
+ logger->log("Add virtual directory: " + newDir);
+ if (append == Append_true)
+ {
+ mEntries.push_back(new VirtDirEntry(newDir,
+ rootDir));
+ }
+ else
+ {
+ mEntries.insert(mEntries.begin(),
+ new VirtDirEntry(newDir,
+ rootDir));
+ }
+ return true;
+ }
+
+ bool removeFromSearchPathSilent(std::string oldDir)
+ {
+ prepareFsPath(oldDir);
+ if (oldDir.find(".zip") != std::string::npos)
+ {
+ reportAlways("Called removeFromSearchPath with zip archive");
+ return false;
+ }
+ if (findLast(oldDir, std::string(dirSeparator)) == false)
+ oldDir += dirSeparator;
+ FOR_EACH (std::vector<VirtDirEntry*>::iterator, it, mEntries)
+ {
+ VirtDirEntry *const entry = *it;
+ if (entry->mRootDir == oldDir)
+ {
+ logger->log("Remove virtual directory: " + oldDir);
+ mEntries.erase(it);
+ delete entry;
+ return true;
+ }
+ }
+
+ logger->log("VirtFsDir::removeFromSearchPath not exists: %s",
+ oldDir.c_str());
+ return false;
+ }
+
+ bool removeFromSearchPath(std::string oldDir)
+ {
+ prepareFsPath(oldDir);
+ if (oldDir.find(".zip") != std::string::npos)
+ {
+ reportAlways("Called removeFromSearchPath with zip archive");
+ return false;
+ }
+ if (findLast(oldDir, std::string(dirSeparator)) == false)
+ oldDir += dirSeparator;
+ FOR_EACH (std::vector<VirtDirEntry*>::iterator, it, mEntries)
+ {
+ VirtDirEntry *const entry = *it;
+ if (entry->mRootDir == oldDir)
+ {
+ logger->log("Remove virtual directory: " + oldDir);
+ mEntries.erase(it);
+ delete entry;
+ return true;
+ }
+ }
+
+ reportAlways("VirtFsDir::removeFromSearchPath not exists: %s",
+ oldDir.c_str());
+ return false;
+ }
+
+ std::vector<VirtDirEntry*> &getEntries()
+ {
+ return mEntries;
+ }
+
+ void deinit()
+ {
+ delete_all(mEntries);
+ mEntries.clear();
+ }
+
+#if defined(__native_client__)
+ void init(const std::string &restrict name A_UNUSED)
+ {
+ mBaseDir = "/";
+#elif defined(ANDROID)
+ void init(const std::string &restrict name A_UNUSED)
+ {
+ mBaseDir = getRealPath(".");
+#else // defined(__native_client__)
+
+ void init(const std::string &restrict name)
+ {
+ mBaseDir = getRealPath(getFileDir(name));
+#endif // defined(__native_client__)
+
+ prepareFsPath(mBaseDir);
+ mUserDir = getHomePath();
+ prepareFsPath(mUserDir);
+ initFuncs(&funcs);
+ }
+
+ void initFuncs(VirtFsFuncs *restrict const ptr)
+ {
+ ptr->close = &VirtFsDir::close;
+ ptr->read = &VirtFsDir::read;
+ ptr->write = &VirtFsDir::write;
+ ptr->fileLength = &VirtFsDir::fileLength;
+ ptr->tell = &VirtFsDir::tell;
+ ptr->seek = &VirtFsDir::seek;
+ ptr->eof = &VirtFsDir::eof;
+ }
+
+ const char *getBaseDir()
+ {
+ return mBaseDir.c_str();
+ }
+
+ const char *getUserDir()
+ {
+ return mUserDir.c_str();
+ }
+
+ std::string getRealDir(std::string filename)
+ {
+ prepareFsPath(filename);
+ if (checkPath(filename) == false)
+ {
+ reportAlways("VirtFsDir::exists invalid path: %s",
+ filename.c_str());
+ return std::string();
+ }
+ FOR_EACH (std::vector<VirtDirEntry*>::iterator, it, mEntries)
+ {
+ VirtDirEntry *const entry = *it;
+ const std::string path = entry->mRootDir + filename;
+ if (Files::existsLocal(path))
+ return entry->mUserDir;
+ }
+ return std::string();
+ }
+
+ bool exists(std::string name)
+ {
+ prepareFsPath(name);
+ if (checkPath(name) == false)
+ {
+ reportAlways("VirtFsDir::exists invalid path: %s",
+ name.c_str());
+ return false;
+ }
+ FOR_EACH (std::vector<VirtDirEntry*>::iterator, it, mEntries)
+ {
+ VirtDirEntry *const entry = *it;
+ if (Files::existsLocal(entry->mRootDir + name))
+ return true;
+ }
+ return false;
+ }
+
+ VirtList *enumerateFiles(std::string dirName)
+ {
+ VirtList *const list = new VirtList;
+ prepareFsPath(dirName);
+ if (checkPath(dirName) == false)
+ {
+ reportAlways("VirtFsDir::enumerateFiles invalid path: %s",
+ dirName.c_str());
+ return list;
+ }
+ return enumerateFiles(dirName, list);
+ }
+
+ VirtList *enumerateFiles(const std::string &restrict dirName,
+ VirtList *restrict const list)
+ {
+ StringVect &names = list->names;
+ FOR_EACH (std::vector<VirtDirEntry*>::iterator, it, mEntries)
+ {
+ VirtDirEntry *const entry = *it;
+ StringVect files;
+ std::string path = entry->mRootDir + dirName;
+ if (findLast(path, std::string(dirSeparator)) == false)
+ path += dirSeparator;
+ const struct dirent *next_file = nullptr;
+ DIR *const dir = opendir(path.c_str());
+ if (dir)
+ {
+ while ((next_file = readdir(dir)))
+ {
+ const std::string file = next_file->d_name;
+ if (file == "." || file == "..")
+ continue;
+ if (mPermitLinks == false)
+ {
+ struct stat statbuf;
+ if (lstat(path.c_str(), &statbuf) == 0 &&
+ S_ISLNK(statbuf.st_mode) != 0)
+ {
+ continue;
+ }
+ }
+ bool found(false);
+ FOR_EACH (StringVectCIter, itn, names)
+ {
+ if (*itn == file)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found == false)
+ names.push_back(file);
+ }
+ closedir(dir);
+ }
+ }
+ return list;
+ }
+
+ bool isDirectory(std::string dirName)
+ {
+ prepareFsPath(dirName);
+ if (checkPath(dirName) == false)
+ {
+ reportAlways("VirtFsDir::isDirectory invalid path: %s",
+ dirName.c_str());
+ return false;
+ }
+ return isDirectoryInternal(dirName);
+ }
+
+ bool isDirectoryInternal(const std::string &restrict dirName)
+ {
+ FOR_EACH (std::vector<VirtDirEntry*>::iterator, it, mEntries)
+ {
+ VirtDirEntry *const entry = *it;
+ std::string path = entry->mRootDir + dirName;
+ if (findLast(path, std::string(dirSeparator)) == false)
+ path += dirSeparator;
+
+ struct stat statbuf;
+ if (stat(path.c_str(), &statbuf) == 0 &&
+ S_ISDIR(statbuf.st_mode) != 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool isSymbolicLink(std::string name)
+ {
+ prepareFsPath(name);
+ if (checkPath(name) == false)
+ {
+ reportAlways("VirtFsDir::isSymbolicLink invalid path: %s",
+ name.c_str());
+ return false;
+ }
+ if (mPermitLinks == false)
+ return false;
+
+ struct stat statbuf;
+ return lstat(name.c_str(), &statbuf) == 0 &&
+ S_ISLNK(statbuf.st_mode) != 0;
+ }
+
+ void freeList(VirtList *restrict const handle)
+ {
+ delete handle;
+ }
+
+ VirtFile *openRead(const std::string &restrict filename)
+ {
+ return openFile(filename, O_RDONLY);
+ }
+
+ VirtFile *openWrite(const std::string &restrict filename)
+ {
+ return openFile(filename, O_WRONLY | O_CREAT | O_TRUNC);
+ }
+
+ VirtFile *openAppend(const std::string &restrict filename)
+ {
+ return openFile(filename, O_WRONLY | O_CREAT | O_APPEND);
+ }
+
+ bool setWriteDir(std::string newDir)
+ {
+ prepareFsPath(newDir);
+ mWriteDir = newDir;
+ if (findLast(mWriteDir, std::string(dirSeparator)) == false)
+ mWriteDir += dirSeparator;
+ return true;
+ }
+
+ bool mkdir(std::string dirname)
+ {
+ prepareFsPath(dirname);
+ if (mWriteDir.empty())
+ {
+ reportAlways("VirtFsDir::mkdir write dir is empty");
+ return false;
+ }
+ return mkdir_r((mWriteDir + dirname).c_str()) != -1;
+ }
+
+ bool remove(std::string filename)
+ {
+ prepareFsPath(filename);
+ if (mWriteDir.empty())
+ {
+ reportAlways("VirtFsDir::remove write dir is empty");
+ return false;
+ }
+ return ::remove((mWriteDir + filename).c_str()) != 0;
+ }
+
+ void permitLinks(const bool val)
+ {
+ mPermitLinks = val;
+ }
+
+ const char *getLastError()
+ {
+ return nullptr;
+ }
+
+ int close(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return 0;
+ delete file;
+ return 1;
+ }
+
+ int64_t read(VirtFile *restrict const file,
+ void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount)
+ {
+ if (file == nullptr)
+ return 0;
+ const int fd = file->mPrivate->mFd;
+ if (fd == -1)
+ {
+ reportAlways("VirtFsDir::read file not opened.");
+ return 0;
+ }
+ int max = objSize * objCount;
+ int cnt = ::read(fd, buffer, max);
+ if (cnt <= 0)
+ return cnt;
+ return cnt / objSize;
+ }
+
+ int64_t write(VirtFile *restrict const file,
+ const void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount)
+ {
+ if (file == nullptr)
+ return 0;
+ const int fd = file->mPrivate->mFd;
+ if (fd == -1)
+ {
+ reportAlways("VirtFsDir::write file not opened.");
+ return 0;
+ }
+ int max = objSize * objCount;
+ int cnt = ::write(fd, buffer, max);
+ if (cnt <= 0)
+ return cnt;
+ return cnt / objSize;
+ }
+
+ int64_t fileLength(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return -1;
+ const int fd = file->mPrivate->mFd;
+ if (fd == -1)
+ {
+ reportAlways("VirtFsDir::fileLength file not opened.");
+ return 0;
+ }
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) == -1)
+ {
+ reportAlways("VirtFsDir::fileLength error.");
+ return -1;
+ }
+ return static_cast<int64_t>(statbuf.st_size);
+ }
+
+ int64_t tell(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return -1;
+
+ const int fd = file->mPrivate->mFd;
+ if (fd == -1)
+ {
+ reportAlways("VirtFsDir::tell file not opened.");
+ return 0;
+ }
+ const int64_t pos = lseek(fd, 0, SEEK_CUR);
+ return pos;
+ }
+
+ int seek(VirtFile *restrict const file,
+ const uint64_t pos)
+ {
+ if (file == nullptr)
+ return 0;
+
+ const int fd = file->mPrivate->mFd;
+ if (fd == -1)
+ {
+ reportAlways("VirtFsDir::seek file not opened.");
+ return 0;
+ }
+ const int64_t res = lseek(fd, pos, SEEK_SET);
+ if (res == -1)
+ return 0;
+ return 1;
+ }
+
+ int eof(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return -1;
+
+ const int fd = file->mPrivate->mFd;
+ if (fd == -1)
+ {
+ reportAlways("VirtFsDir::eof file not opened.");
+ return 0;
+ }
+ const int64_t pos = lseek(fd, 0, SEEK_CUR);
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) == -1)
+ {
+ reportAlways("VirtFsDir::fileLength error.");
+ return -1;
+ }
+ const int64_t len = static_cast<int64_t>(statbuf.st_size);
+ return pos < 0 || len < 0 || pos >= len;
+ }
+} // namespace VirtFs
diff --git a/src/fs/virtfs/virtfsdir.h b/src/fs/virtfs/virtfsdir.h
new file mode 100644
index 000000000..75df7fb18
--- /dev/null
+++ b/src/fs/virtfs/virtfsdir.h
@@ -0,0 +1,89 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2013-2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTILS_VIRTFSDIR_H
+#define UTILS_VIRTFSDIR_H
+
+#include "enums/simpletypes/append.h"
+#include "enums/simpletypes/skiperror.h"
+
+#include "localconsts.h"
+
+#include <vector>
+#include <string>
+
+struct VirtDirEntry;
+struct VirtFile;
+struct VirtFsFuncs;
+struct VirtList;
+
+namespace VirtFsDir
+{
+ VirtDirEntry *searchEntryByRoot(const std::string &restrict root);
+ VirtDirEntry *searchEntryByPath(const std::string &restrict path);
+ VirtFile *openReadDirEntry(VirtDirEntry *const entry,
+ const std::string &filename);
+ const char *getBaseDir();
+ const char *getUserDir();
+ bool addToSearchPath(std::string newDir,
+ const Append append);
+ bool addToSearchPathSilent(std::string newDir,
+ const Append append,
+ const SkipError skipError);
+ bool removeFromSearchPath(std::string oldDir);
+ bool removeFromSearchPathSilent(std::string oldDir);
+ void init(const std::string &restrict name);
+ void initFuncs(VirtFsFuncs *restrict const ptr);
+ void deinit();
+ std::vector<VirtDirEntry*> &getEntries();
+ bool exists(std::string name);
+ VirtList *enumerateFiles(std::string dirName) RETURNS_NONNULL;
+ VirtList *enumerateFiles(const std::string &restrict dirName,
+ VirtList *restrict const list) RETURNS_NONNULL;
+ bool isDirectory(std::string dirName);
+ bool isDirectoryInternal(const std::string &restrict dirName);
+ bool isSymbolicLink(std::string name);
+ void freeList(VirtList *restrict const handle);
+ VirtFile *openRead(const std::string &restrict filename);
+ VirtFile *openWrite(const std::string &restrict filename);
+ VirtFile *openAppend(const std::string &restrict filename);
+ bool setWriteDir(std::string newDir);
+ std::string getRealDir(std::string filename);
+ bool mkdir(std::string dirName);
+ bool remove(std::string filename);
+ void permitLinks(const bool val);
+ const char *getLastError();
+ int64_t read(VirtFile *restrict const handle,
+ void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount);
+ int64_t write(VirtFile *restrict const file,
+ const void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount);
+ int close(VirtFile *restrict const file);
+ int64_t fileLength(VirtFile *restrict const file);
+ int64_t tell(VirtFile *restrict const file);
+ int seek(VirtFile *restrict const file,
+ const uint64_t pos);
+ int eof(VirtFile *restrict const file);
+} // namespace VirtFsDir
+
+#endif // UTILS_VIRTFSDIR_H
diff --git a/src/fs/virtfs/virtfsdir_unittest.cc b/src/fs/virtfs/virtfsdir_unittest.cc
new file mode 100644
index 000000000..ad55acf2e
--- /dev/null
+++ b/src/fs/virtfs/virtfsdir_unittest.cc
@@ -0,0 +1,717 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2016-2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "catch.hpp"
+
+#include "fs/virtfs/virtdirentry.h"
+#include "fs/virtfs/virtfsdir.h"
+#include "fs/virtfstools.h"
+#include "fs/virtlist.h"
+
+#include "utils/checkutils.h"
+#include "utils/delete2.h"
+
+#include "debug.h"
+
+TEST_CASE("VirtFsDir getEntries")
+{
+ VirtFsDir::init(".");
+ REQUIRE(VirtFsDir::getEntries().empty());
+ REQUIRE(VirtFsDir::searchEntryByRoot("test") == nullptr);
+ VirtFsDir::deinit();
+}
+
+TEST_CASE("VirtFsDir getBaseDir")
+{
+ VirtFsDir::init(".");
+ REQUIRE(VirtFsDir::getBaseDir() != nullptr);
+ VirtFsDir::deinit();
+}
+
+TEST_CASE("VirtFsDir addToSearchPath")
+{
+ VirtFsDir::init(".");
+ logger = new Logger();
+ SECTION("simple 1")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1",
+ Append_false,
+ SkipError_true));
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir1/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("test/") == nullptr);
+ REQUIRE(VirtFsDir::getEntries().size() == 1);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir1");
+ }
+
+ SECTION("simple 2")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1/",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir1/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("test/") == nullptr);
+ REQUIRE(VirtFsDir::getEntries().size() == 1);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir1/");
+ }
+
+ SECTION("simple 3")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1",
+ Append_false,
+ SkipError_true));
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir2",
+ Append_false,
+ SkipError_true));
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir1/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir2/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("test/") == nullptr);
+ REQUIRE(VirtFsDir::getEntries().size() == 2);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir2/");
+ REQUIRE(VirtFsDir::getEntries()[1]->mRootDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir2");
+ REQUIRE(VirtFsDir::getEntries()[1]->mUserDir == "dir1");
+ }
+
+ SECTION("simple 4")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1\\",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir2",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir1/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir2/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("test/") == nullptr);
+ REQUIRE(VirtFsDir::getEntries().size() == 2);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[1]->mRootDir == "dir2/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[1]->mUserDir == "dir2");
+ }
+
+ SECTION("simple 5")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir2",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir3/test",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir1/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir2/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir3/test/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("test/") == nullptr);
+ REQUIRE(VirtFsDir::getEntries().size() == 3);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir1");
+ REQUIRE(VirtFsDir::getEntries()[1]->mRootDir == "dir2/");
+ REQUIRE(VirtFsDir::getEntries()[1]->mUserDir == "dir2");
+ REQUIRE(VirtFsDir::getEntries()[2]->mRootDir == "dir3/test/");
+ REQUIRE(VirtFsDir::getEntries()[2]->mUserDir == "dir3/test");
+ }
+
+ SECTION("simple 6")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir2",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir3/test",
+ Append_false,
+ SkipError_true));
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir1/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir2/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("dir3/test/") != nullptr);
+ REQUIRE(VirtFsDir::searchEntryByRoot("test/") == nullptr);
+ REQUIRE(VirtFsDir::getEntries().size() == 3);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir3/test/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir3/test");
+ REQUIRE(VirtFsDir::getEntries()[1]->mRootDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[1]->mUserDir == "dir1");
+ REQUIRE(VirtFsDir::getEntries()[2]->mRootDir == "dir2/");
+ REQUIRE(VirtFsDir::getEntries()[2]->mUserDir == "dir2");
+ }
+
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsDir removeFromSearchPath")
+{
+ VirtFsDir::init(".");
+ logger = new Logger();
+
+ SECTION("simple 1")
+ {
+ REQUIRE_THROWS(VirtFsDir::removeFromSearchPath("dir1"));
+ REQUIRE_THROWS(VirtFsDir::removeFromSearchPath("dir1/"));
+ }
+
+ SECTION("simple 2")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1",
+ Append_true,
+ SkipError_true));
+ REQUIRE_THROWS(VirtFsDir::removeFromSearchPath("dir2"));
+ REQUIRE(VirtFsDir::removeFromSearchPath("dir1"));
+ }
+
+ SECTION("simple 3")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir2//dir3",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir3",
+ Append_false,
+ SkipError_true));
+ REQUIRE(VirtFsDir::getEntries().size() == 3);
+ REQUIRE_THROWS(VirtFsDir::removeFromSearchPath("dir2"));
+ REQUIRE(VirtFsDir::removeFromSearchPath("dir1"));
+ REQUIRE(VirtFsDir::getEntries().size() == 2);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir3/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir3");
+ REQUIRE(VirtFsDir::getEntries()[1]->mRootDir == "dir2/dir3/");
+ REQUIRE(VirtFsDir::getEntries()[1]->mUserDir == "dir2/dir3");
+ REQUIRE_THROWS(VirtFsDir::removeFromSearchPath("dir1"));
+ REQUIRE(VirtFsDir::getEntries().size() == 2);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir3/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir3");
+ REQUIRE(VirtFsDir::getEntries()[1]->mRootDir == "dir2/dir3/");
+ REQUIRE(VirtFsDir::getEntries()[1]->mUserDir == "dir2/dir3");
+ REQUIRE(VirtFsDir::removeFromSearchPath("dir2/dir3"));
+ REQUIRE_THROWS(VirtFsDir::removeFromSearchPath("dir2/dir3/"));
+ REQUIRE(VirtFsDir::getEntries().size() == 1);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir3/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir3");
+ }
+
+ SECTION("simple 4")
+ {
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::getEntries().size() == 1);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir1");
+ REQUIRE_THROWS(VirtFsDir::removeFromSearchPath("dir2"));
+ REQUIRE(VirtFsDir::removeFromSearchPath("dir1"));
+ REQUIRE(VirtFsDir::getEntries().size() == 0);
+ REQUIRE(VirtFsDir::addToSearchPathSilent("dir1",
+ Append_true,
+ SkipError_true));
+ REQUIRE(VirtFsDir::getEntries().size() == 1);
+ REQUIRE(VirtFsDir::getEntries()[0]->mRootDir == "dir1/");
+ REQUIRE(VirtFsDir::getEntries()[0]->mUserDir == "dir1");
+ }
+
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsDir exists")
+{
+ VirtFsDir::init(".");
+ logger = new Logger();
+ VirtFsDir::addToSearchPathSilent("data/",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("..\\data",
+ Append_false,
+ SkipError_false);
+
+ REQUIRE(VirtFsDir::exists("test//units.xml") == true);
+ REQUIRE(VirtFsDir::exists("test/\\units123.xml") == false);
+ REQUIRE(VirtFsDir::exists("tesQ/units.xml") == false);
+ REQUIRE(VirtFsDir::exists("units.xml") == false);
+
+ VirtFsDir::addToSearchPathSilent("data//test",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("..//data\\test",
+ Append_false,
+ SkipError_false);
+
+ REQUIRE(VirtFsDir::exists("test\\units.xml") == true);
+ REQUIRE(VirtFsDir::exists("test/units123.xml") == false);
+ REQUIRE(VirtFsDir::exists("tesQ/units.xml") == false);
+ REQUIRE(VirtFsDir::exists("units.xml") == true);
+
+ VirtFsDir::removeFromSearchPathSilent("data/test");
+ VirtFsDir::removeFromSearchPathSilent("../data/test");
+
+ REQUIRE(VirtFsDir::exists("test\\units.xml") == true);
+ REQUIRE(VirtFsDir::exists("test/units123.xml") == false);
+ REQUIRE(VirtFsDir::exists("tesQ/units.xml") == false);
+ REQUIRE(VirtFsDir::exists("units.xml") == false);
+
+ REQUIRE_THROWS(VirtFsDir::exists("test/../units.xml"));
+
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+static void removeTemp(StringVect &restrict list)
+{
+ int cnt = 0;
+ std::sort(list.begin(), list.end());
+
+ FOR_EACH (StringVectIter, it, list)
+ {
+ if (*it != "serverlistplus.xml.part")
+ {
+ logger->log("file: %d %s",
+ cnt,
+ (*it).c_str());
+ cnt ++;
+ }
+ }
+
+ FOR_EACH (StringVectIter, it, list)
+ {
+ if (*it == "serverlistplus.xml.part")
+ {
+ list.erase(it);
+ return;
+ }
+ }
+}
+
+TEST_CASE("VirtFsDir getRealDir")
+{
+ VirtFsDir::init(".");
+ logger = new Logger();
+ REQUIRE(VirtFsDir::getRealDir(".") == "");
+ REQUIRE(VirtFsDir::getRealDir("..") == "");
+ const bool dir1 = VirtFsDir::addToSearchPathSilent("data",
+ Append_false,
+ SkipError_false);
+ REQUIRE((dir1 || VirtFsDir::addToSearchPathSilent("../data",
+ Append_false,
+ SkipError_false)) == true);
+ if (dir1 == true)
+ {
+ REQUIRE(VirtFsDir::getRealDir("test") == "data");
+ REQUIRE(VirtFsDir::getRealDir("test/test.txt") ==
+ "data");
+ REQUIRE(VirtFsDir::getRealDir("test\\test.txt") ==
+ "data");
+ REQUIRE(VirtFsDir::getRealDir("test//test.txt") ==
+ "data");
+ }
+ else
+ {
+ REQUIRE(VirtFsDir::getRealDir("test") == "../data");
+ REQUIRE(VirtFsDir::getRealDir("test/test.txt") ==
+ "../data");
+ REQUIRE(VirtFsDir::getRealDir("test\\test.txt") ==
+ "../data");
+ REQUIRE(VirtFsDir::getRealDir("test//test.txt") ==
+ "../data");
+ }
+ REQUIRE(VirtFsDir::getRealDir("zzz") == "");
+
+ VirtFsDir::addToSearchPathSilent("data/test",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data/test",
+ Append_false,
+ SkipError_false);
+ if (dir1 == true)
+ {
+ REQUIRE(VirtFsDir::getRealDir("test") == "data");
+ REQUIRE(VirtFsDir::getRealDir("test/test.txt") ==
+ "data");
+ REQUIRE(VirtFsDir::getRealDir("test\\test.txt") ==
+ "data");
+ REQUIRE(VirtFsDir::getRealDir("test.txt") ==
+ "data/test");
+ }
+ else
+ {
+ REQUIRE(VirtFsDir::getRealDir("test") == "../data");
+ REQUIRE(VirtFsDir::getRealDir("test/test.txt") ==
+ "../data");
+ REQUIRE(VirtFsDir::getRealDir("test\\test.txt") ==
+ "../data");
+ REQUIRE(VirtFsDir::getRealDir("test.txt") ==
+ "../data/test");
+ }
+ REQUIRE(VirtFsDir::getRealDir("zzz") == "");
+
+ VirtFsDir::removeFromSearchPathSilent("data/test");
+ VirtFsDir::removeFromSearchPathSilent("../data/test");
+
+ if (dir1 == true)
+ {
+ REQUIRE(VirtFsDir::getRealDir("test") == "data");
+ REQUIRE(VirtFsDir::getRealDir("test/test.txt") ==
+ "data");
+ }
+ else
+ {
+ REQUIRE(VirtFsDir::getRealDir("test") == "../data");
+ REQUIRE(VirtFsDir::getRealDir("test/test.txt") ==
+ "../data");
+ }
+ REQUIRE(VirtFsDir::getRealDir("zzz") == "");
+
+ VirtFsDir::removeFromSearchPathSilent("data");
+ VirtFsDir::removeFromSearchPathSilent("../data");
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+static bool inList(VirtList *list,
+ const std::string &name)
+{
+ FOR_EACH (StringVectCIter, it, list->names)
+ {
+ if (*it == name)
+ return true;
+ }
+ return false;
+}
+
+TEST_CASE("VirtFsDir enumerateFiles1")
+{
+ VirtFsDir::init(".");
+ logger = new Logger;
+
+ VirtFsDir::addToSearchPathSilent("data",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data",
+ Append_false,
+ SkipError_false);
+
+ VirtList *list = nullptr;
+
+ const int cnt1 = VirtFsDir::exists("test/test2.txt") ? 27 : 26;
+ const int cnt2 = 27;
+
+ VirtFsDir::permitLinks(false);
+ list = VirtFsDir::enumerateFiles("test");
+ removeTemp(list->names);
+ const size_t sz = list->names.size();
+ REQUIRE(sz == cnt1);
+ VirtFsDir::freeList(list);
+
+ VirtFsDir::permitLinks(true);
+ list = VirtFsDir::enumerateFiles("test/");
+ removeTemp(list->names);
+ REQUIRE(list->names.size() == cnt2);
+ VirtFsDir::freeList(list);
+
+ VirtFsDir::permitLinks(false);
+ list = VirtFsDir::enumerateFiles("test\\");
+ removeTemp(list->names);
+ REQUIRE(list->names.size() == cnt1);
+ VirtFsDir::freeList(list);
+
+ VirtFsDir::removeFromSearchPathSilent("data");
+ VirtFsDir::removeFromSearchPathSilent("../data");
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsDir enumerateFiles2")
+{
+ VirtFsDir::init(".");
+ logger = new Logger;
+
+ VirtFsDir::addToSearchPathSilent("data/test/dir1",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data/test/dir1",
+ Append_false,
+ SkipError_false);
+
+ VirtList *list = nullptr;
+
+ list = VirtFsDir::enumerateFiles("/");
+ const size_t sz = list->names.size();
+ REQUIRE(list->names.size() == 5);
+ REQUIRE(inList(list, "file1.txt"));
+ REQUIRE_FALSE(inList(list, "file2.txt"));
+ VirtFsDir::freeList(list);
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsDir enumerateFiles3")
+{
+ VirtFsDir::init(".");
+ logger = new Logger;
+
+ VirtFsDir::addToSearchPathSilent("data/test/dir1",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data/test/dir1",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("data/test/dir2",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data/test/dir2",
+ Append_false,
+ SkipError_false);
+
+ VirtList *list = nullptr;
+
+ list = VirtFsDir::enumerateFiles("/");
+ const size_t sz = list->names.size();
+ REQUIRE(list->names.size() == 6);
+ REQUIRE(inList(list, "file1.txt"));
+ REQUIRE(inList(list, "file2.txt"));
+ VirtFsDir::freeList(list);
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsDir isDirectory")
+{
+ VirtFsDir::init(".");
+ logger = new Logger();
+ VirtFsDir::addToSearchPathSilent("data",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data",
+ Append_false,
+ SkipError_false);
+
+ REQUIRE(VirtFsDir::isDirectory("test/units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("test/units.xml/") == false);
+ REQUIRE(VirtFsDir::isDirectory("test//units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("test/units123.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("test//units123.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("tesQ/units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("tesQ//units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("test") == true);
+ REQUIRE(VirtFsDir::isDirectory("test/") == true);
+ REQUIRE(VirtFsDir::isDirectory("test//") == true);
+ REQUIRE(VirtFsDir::isDirectory("test/dir1") == true);
+ REQUIRE(VirtFsDir::isDirectory("test//dir1") == true);
+ REQUIRE(VirtFsDir::isDirectory("test//dir1/") == true);
+ REQUIRE(VirtFsDir::isDirectory("test//dir1//") == true);
+ REQUIRE(VirtFsDir::isDirectory("test\\dir1/") == true);
+ REQUIRE(VirtFsDir::isDirectory("test/dir1//") == true);
+ REQUIRE(VirtFsDir::isDirectory("testQ") == false);
+ REQUIRE(VirtFsDir::isDirectory("testQ/") == false);
+ REQUIRE(VirtFsDir::isDirectory("testQ//") == false);
+
+ VirtFsDir::addToSearchPathSilent("data/test",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data/test",
+ Append_false,
+ SkipError_false);
+
+ REQUIRE(VirtFsDir::isDirectory("test/units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("test/units.xml/") == false);
+ REQUIRE(VirtFsDir::isDirectory("test//units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("test/units123.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("tesQ/units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("test") == true);
+ REQUIRE(VirtFsDir::isDirectory("testQ") == false);
+ REQUIRE(VirtFsDir::isDirectory("test/dir1") == true);
+ REQUIRE(VirtFsDir::isDirectory("test\\dir1") == true);
+
+ VirtFsDir::removeFromSearchPathSilent("data/test");
+ VirtFsDir::removeFromSearchPathSilent("../data/test");
+
+ REQUIRE(VirtFsDir::isDirectory("test/units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("test/units123.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("tesQ/units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("units.xml") == false);
+ REQUIRE(VirtFsDir::isDirectory("units.xml/") == false);
+ REQUIRE(VirtFsDir::isDirectory("test") == true);
+ REQUIRE(VirtFsDir::isDirectory("test/") == true);
+ REQUIRE(VirtFsDir::isDirectory("testQ") == false);
+ REQUIRE(VirtFsDir::isDirectory("test/dir1") == true);
+
+ VirtFsDir::removeFromSearchPathSilent("data");
+ VirtFsDir::removeFromSearchPathSilent("../data");
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsDir openRead")
+{
+ VirtFsDir::init(".");
+ logger = new Logger();
+ VirtFsDir::addToSearchPathSilent("data",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data",
+ Append_false,
+ SkipError_false);
+
+ VirtFile *file = nullptr;
+
+ file = VirtFsDir::openRead("test/units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsDir::close(file);
+ file = VirtFsDir::openRead("test\\units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsDir::close(file);
+ file = VirtFsDir::openRead("test/units123.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsDir::openRead("tesQ/units.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsDir::openRead("units.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsDir::openRead("testQ");
+ REQUIRE(file == nullptr);
+
+ VirtFsDir::addToSearchPathSilent("data/test",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data/test",
+ Append_false,
+ SkipError_false);
+
+ file = VirtFsDir::openRead("test/units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsDir::close(file);
+ file = VirtFsDir::openRead("test/units123.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsDir::openRead("tesQ/units.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsDir::openRead("units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsDir::close(file);
+ file = VirtFsDir::openRead("testQ");
+ REQUIRE(file == nullptr);
+
+ VirtFsDir::removeFromSearchPathSilent("data/test");
+ VirtFsDir::removeFromSearchPathSilent("../data/test");
+
+ file = VirtFsDir::openRead("test/units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsDir::close(file);
+ file = VirtFsDir::openRead("test/units123.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsDir::openRead("tesQ/units.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsDir::openRead("units.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsDir::openRead("testQ");
+ REQUIRE(file == nullptr);
+
+ VirtFsDir::removeFromSearchPathSilent("data");
+ VirtFsDir::removeFromSearchPathSilent("../data");
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+
+TEST_CASE("VirtFsDir permitLinks")
+{
+ VirtFsDir::init(".");
+ logger = new Logger();
+ VirtFsDir::addToSearchPathSilent("data",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data",
+ Append_false,
+ SkipError_false);
+
+ const int cnt1 = VirtFsDir::exists("test/test2.txt") ? 25 : 24;
+ const int cnt2 = 25;
+
+ StringVect list;
+ VirtFsDir::permitLinks(false);
+ VirtFsDir::getFiles("test", list);
+ removeTemp(list);
+ const size_t sz = list.size();
+ REQUIRE(sz == cnt1);
+
+ list.clear();
+ VirtFsDir::permitLinks(true);
+ VirtFsDir::getFiles("test", list);
+ removeTemp(list);
+ REQUIRE(list.size() == cnt2);
+
+ list.clear();
+ VirtFsDir::permitLinks(false);
+ VirtFsDir::getFiles("test", list);
+ removeTemp(list);
+ REQUIRE(list.size() == cnt1);
+
+ VirtFsDir::removeFromSearchPathSilent("data");
+ VirtFsDir::removeFromSearchPathSilent("../data");
+ VirtFsDir::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsDir read")
+{
+ VirtFsDir::init(".");
+ logger = new Logger();
+ VirtFsDir::addToSearchPathSilent("data",
+ Append_false,
+ SkipError_false);
+ VirtFsDir::addToSearchPathSilent("../data",
+ Append_false,
+ SkipError_false);
+
+ VirtFile *file = VirtFsDir::openRead("test/test.txt");
+ REQUIRE(file != nullptr);
+ REQUIRE(VirtFsDir::fileLength(file) == 23);
+ const int fileSize = VirtFsDir::fileLength(file);
+
+ void *restrict buffer = calloc(fileSize + 1, 1);
+ REQUIRE(VirtFsDir::read(file, buffer, 1, fileSize) == fileSize);
+ REQUIRE(strcmp(static_cast<char*>(buffer),
+ "test line 1\ntest line 2") == 0);
+ REQUIRE(VirtFsDir::tell(file) == fileSize);
+ REQUIRE(VirtFsDir::eof(file) == true);
+
+ free(buffer);
+ buffer = calloc(fileSize + 1, 1);
+ REQUIRE(VirtFsDir::seek(file, 12) != 0);
+ REQUIRE(VirtFsDir::eof(file) == false);
+ REQUIRE(VirtFsDir::tell(file) == 12);
+ REQUIRE(VirtFsDir::read(file, buffer, 1, 11) == 11);
+ REQUIRE(strcmp(static_cast<char*>(buffer),
+ "test line 2") == 0);
+ REQUIRE(VirtFsDir::eof(file) == true);
+
+ VirtFsDir::close(file);
+ free(buffer);
+
+ VirtFsDir::removeFromSearchPathSilent("data");
+ VirtFsDir::removeFromSearchPathSilent("../data");
+ VirtFsDir::deinit();
+ delete2(logger);
+}
diff --git a/src/fs/virtfs/virtfszip.cpp b/src/fs/virtfs/virtfszip.cpp
new file mode 100644
index 000000000..c4ce6f9e8
--- /dev/null
+++ b/src/fs/virtfs/virtfszip.cpp
@@ -0,0 +1,555 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2013-2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fs/virtfs/virtfszip.h"
+
+#include "fs/files.h"
+#include "fs/paths.h"
+#include "fs/virtfsfuncs.h"
+#include "fs/virtfile.h"
+#include "fs/virtlist.h"
+
+#include "fs/virtfs/virtfileprivate.h"
+#include "fs/virtfs/virtzipentry.h"
+#include "fs/virtfs/zip.h"
+#include "fs/virtfs/ziplocalheader.h"
+
+#include "utils/checkutils.h"
+#include "utils/dtor.h"
+#include "utils/stringutils.h"
+
+#include "debug.h"
+
+extern const char *dirSeparator;
+
+namespace
+{
+ std::vector<VirtZipEntry*> mEntries;
+ VirtFsFuncs funcs;
+} // namespace
+
+namespace VirtFsZip
+{
+ VirtZipEntry *searchEntryByArchive(const std::string &restrict archiveName)
+ {
+ FOR_EACH (std::vector<VirtZipEntry*>::const_iterator, it, mEntries)
+ {
+ if ((*it)->mArchiveName == archiveName)
+ return *it;
+ }
+ return nullptr;
+ }
+
+ ZipLocalHeader *searchHeaderByName(const std::string &restrict filename)
+ {
+ FOR_EACH (std::vector<VirtZipEntry*>::const_iterator, it, mEntries)
+ {
+ VirtZipEntry *const entry = *it;
+ FOR_EACH (std::vector<ZipLocalHeader*>::const_iterator,
+ it2,
+ entry->mHeaders)
+ {
+ if ((*it2)->fileName == filename)
+ return *it2;;
+ }
+ }
+ return nullptr;
+ }
+
+ bool addToSearchPathSilent(std::string newDir,
+ const Append append)
+ {
+ prepareFsPath(newDir);
+ if (Files::existsLocal(newDir) == false)
+ {
+ logger->log("VirtFsZip::addToSearchPath file not exists: %s",
+ newDir.c_str());
+ return false;
+ }
+ if (findLast(newDir, ".zip") == false)
+ {
+ reportAlways("Called VirtFsZip::addToSearchPath without "
+ "zip archive");
+ return false;
+ }
+ VirtZipEntry *entry = VirtFsZip::searchEntryByArchive(newDir);
+ if (entry != nullptr)
+ {
+ reportAlways("VirtFsZip::addToSearchPath already exists: %s",
+ newDir.c_str());
+ return false;
+ }
+ entry = new VirtZipEntry(newDir);
+ if (Zip::readArchiveInfo(entry) == false)
+ {
+ delete entry;
+ return false;
+ }
+
+ logger->log("Add virtual zip: " + newDir);
+ if (append == Append_true)
+ mEntries.push_back(entry);
+ else
+ {
+ mEntries.insert(mEntries.begin(),
+ entry);
+ }
+ return true;
+ }
+
+ bool addToSearchPath(std::string newDir,
+ const Append append)
+ {
+ prepareFsPath(newDir);
+ if (Files::existsLocal(newDir) == false)
+ {
+ reportAlways("VirtFsZip::addToSearchPath directory not exists: %s",
+ newDir.c_str());
+ return false;
+ }
+ if (findLast(newDir, ".zip") == false)
+ {
+ reportAlways("Called VirtFsZip::addToSearchPath without "
+ "zip archive");
+ return false;
+ }
+ VirtZipEntry *entry = VirtFsZip::searchEntryByArchive(newDir);
+ if (entry != nullptr)
+ {
+ reportAlways("VirtFsZip::addToSearchPath already exists: %s",
+ newDir.c_str());
+ return false;
+ }
+ entry = new VirtZipEntry(newDir);
+ if (Zip::readArchiveInfo(entry) == false)
+ {
+ delete entry;
+ return false;
+ }
+
+ logger->log("Add virtual zip: " + newDir);
+ if (append == Append_true)
+ mEntries.push_back(entry);
+ else
+ {
+ mEntries.insert(mEntries.begin(),
+ entry);
+ }
+ return true;
+ }
+
+ bool removeFromSearchPathSilent(std::string oldDir)
+ {
+ prepareFsPath(oldDir);
+ if (findLast(oldDir, ".zip") == false)
+ {
+ reportAlways("Called removeFromSearchPath without zip archive");
+ return false;
+ }
+ FOR_EACH (std::vector<VirtZipEntry*>::iterator, it, mEntries)
+ {
+ VirtZipEntry *const entry = *it;
+ if (entry->mArchiveName == oldDir)
+ {
+ logger->log("Remove virtual zip: " + oldDir);
+ mEntries.erase(it);
+ delete entry;
+ return true;
+ }
+ }
+
+ logger->log("VirtFsZip::removeFromSearchPath not exists: %s",
+ oldDir.c_str());
+ return false;
+ }
+
+ bool removeFromSearchPath(std::string oldDir)
+ {
+ prepareFsPath(oldDir);
+ if (findLast(oldDir, ".zip") == false)
+ {
+ reportAlways("Called removeFromSearchPath without zip archive");
+ return false;
+ }
+ FOR_EACH (std::vector<VirtZipEntry*>::iterator, it, mEntries)
+ {
+ VirtZipEntry *const entry = *it;
+ if (entry->mArchiveName == oldDir)
+ {
+ logger->log("Remove virtual zip: " + oldDir);
+ mEntries.erase(it);
+ delete entry;
+ return true;
+ }
+ }
+
+ reportAlways("VirtFsZip::removeFromSearchPath not exists: %s",
+ oldDir.c_str());
+ return false;
+ }
+
+ std::vector<VirtZipEntry*> &getEntries()
+ {
+ return mEntries;
+ }
+
+ void deinit()
+ {
+ delete_all(mEntries);
+ mEntries.clear();
+ }
+
+ void init()
+ {
+ initFuncs(&funcs);
+ }
+
+ void initFuncs(VirtFsFuncs *restrict const ptr)
+ {
+ ptr->close = &VirtFsZip::close;
+ ptr->read = &VirtFsZip::read;
+ ptr->write = &VirtFsZip::write;
+ ptr->fileLength = &VirtFsZip::fileLength;
+ ptr->tell = &VirtFsZip::tell;
+ ptr->seek = &VirtFsZip::seek;
+ ptr->eof = &VirtFsZip::eof;
+ }
+
+ std::string getRealDir(std::string filename)
+ {
+ prepareFsPath(filename);
+ if (checkPath(filename) == false)
+ {
+ reportAlways("VirtFsZip::exists invalid path: %s",
+ filename.c_str());
+ return std::string();
+ }
+ return getRealDirInternal(filename);
+ }
+
+ std::string getRealDirInternal(const std::string &filename)
+ {
+ ZipLocalHeader *restrict const header = searchHeaderByName(filename);
+ if (header != nullptr)
+ return header->zipEntry->mArchiveName;
+ return std::string();
+ }
+
+ bool exists(std::string name)
+ {
+ prepareFsPath(name);
+ if (checkPath(name) == false)
+ {
+ reportAlways("VirtFsZip::exists invalid path: %s",
+ name.c_str());
+ return false;
+ }
+ ZipLocalHeader *restrict const header = searchHeaderByName(name);
+ if (header != nullptr)
+ return true;
+ return false;
+ }
+
+ VirtList *enumerateFiles(std::string dirName)
+ {
+ VirtList *const list = new VirtList;
+ prepareFsPath(dirName);
+ if (checkPath(dirName) == false)
+ {
+ reportAlways("VirtFsZip::enumerateFiles invalid path: %s",
+ dirName.c_str());
+ return list;
+ }
+ return enumerateFiles(dirName, list);
+ }
+
+ VirtList *enumerateFiles(std::string dirName,
+ VirtList *restrict const list)
+ {
+ if (findLast(dirName, std::string(dirSeparator)) == false)
+ dirName += dirSeparator;
+ StringVect &names = list->names;
+ if (dirName == "/")
+ {
+ FOR_EACH (std::vector<VirtZipEntry*>::const_iterator, it, mEntries)
+ {
+ VirtZipEntry *const entry = *it;
+ FOR_EACH (std::vector<ZipLocalHeader*>::const_iterator,
+ it2,
+ entry->mHeaders)
+ {
+ ZipLocalHeader *const header = *it2;
+ std::string fileName = header->fileName;
+ // skip subdirs from enumeration
+ const size_t idx = fileName.find(dirSeparator);
+ if (idx != std::string::npos)
+ fileName.erase(idx);
+ bool found(false);
+ FOR_EACH (StringVectCIter, itn, names)
+ {
+ if (*itn == fileName)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found == false)
+ names.push_back(fileName);
+ }
+ }
+ }
+ else
+ {
+ FOR_EACH (std::vector<VirtZipEntry*>::const_iterator, it, mEntries)
+ {
+ VirtZipEntry *const entry = *it;
+ FOR_EACH (std::vector<ZipLocalHeader*>::const_iterator,
+ it2,
+ entry->mHeaders)
+ {
+ ZipLocalHeader *const header = *it2;
+ std::string fileName = header->fileName;
+ if (findCutFirst(fileName, dirName) == true)
+ {
+ // skip subdirs from enumeration
+ const size_t idx = fileName.find(dirSeparator);
+ if (idx != std::string::npos)
+ fileName.erase(idx);
+ bool found(false);
+ FOR_EACH (StringVectCIter, itn, names)
+ {
+ if (*itn == fileName)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found == false)
+ names.push_back(fileName);
+ }
+ }
+ }
+ }
+
+ return list;
+ }
+
+ bool isDirectory(std::string dirName)
+ {
+ prepareFsPath(dirName);
+ if (checkPath(dirName) == false)
+ {
+ reportAlways("VirtFsZip::isDirectory invalid path: %s",
+ dirName.c_str());
+ return false;
+ }
+ return isDirectoryInternal(dirName);
+ }
+
+ bool isDirectoryInternal(std::string dirName)
+ {
+ if (findLast(dirName, std::string(dirSeparator)) == false)
+ dirName += dirSeparator;
+ FOR_EACH (std::vector<VirtZipEntry*>::const_iterator, it, mEntries)
+ {
+ VirtZipEntry *const entry = *it;
+ FOR_EACH (std::vector<std::string>::const_iterator,
+ it2,
+ entry->mDirs)
+ {
+ if (*it2 == dirName)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool isSymbolicLink(std::string name)
+ {
+ prepareFsPath(name);
+ if (checkPath(name) == false)
+ {
+ reportAlways("VirtFsZip::isSymbolicLink invalid path: %s",
+ name.c_str());
+ return false;
+ }
+ // look like in zip files can be symlinks, but here they useless
+ return false;
+ }
+
+ void freeList(VirtList *restrict const handle)
+ {
+ delete handle;
+ }
+
+ VirtFile *openRead(std::string filename)
+ {
+ prepareFsPath(filename);
+ if (checkPath(filename) == false)
+ {
+ reportAlways("VirtFsZip::openRead invalid path: %s",
+ filename.c_str());
+ return nullptr;
+ }
+ return openReadInternal(filename);
+ }
+
+ VirtFile *openReadInternal(const std::string &filename)
+ {
+ ZipLocalHeader *restrict const header = searchHeaderByName(filename);
+ if (header != nullptr)
+ {
+ uint8_t *restrict const buf = Zip::readFile(header);
+ if (buf == nullptr)
+ return nullptr;
+ VirtFile *restrict const file = new VirtFile(&funcs);
+ file->mPrivate = new VirtFilePrivate(buf,
+ header->uncompressSize);
+ return file;
+ }
+ return nullptr;
+ }
+
+ VirtFile *openWrite(const std::string &restrict filename A_UNUSED)
+ {
+ return nullptr;
+ }
+
+ VirtFile *openAppend(const std::string &restrict filename A_UNUSED)
+ {
+ return nullptr;
+ }
+
+ bool setWriteDir(const std::string &restrict newDir A_UNUSED)
+ {
+ return false;
+ }
+
+ bool mkdir(const std::string &restrict dirname A_UNUSED)
+ {
+ return false;
+ }
+
+ bool remove(const std::string &restrict filename A_UNUSED)
+ {
+ return false;
+ }
+
+ void permitLinks(const bool val A_UNUSED)
+ {
+ }
+
+ const char *getLastError()
+ {
+ return nullptr;
+ }
+
+ int close(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return 0;
+ delete file;
+ return 1;
+ }
+
+ int64_t read(VirtFile *restrict const file,
+ void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount)
+ {
+ if (file == nullptr ||
+ objSize == 0 ||
+ objCount == 0)
+ {
+ return 0;
+ }
+ if (buffer == nullptr)
+ {
+ reportAlways("VirtFsZip::read buffer is null");
+ return 0;
+ }
+ VirtFilePrivate *restrict const priv = file->mPrivate;
+ const uint32_t pos = priv->mPos;
+ const uint32_t sz = priv->mSize;
+ // if outside of buffer, return
+ if (pos >= sz)
+ return 0;
+ // pointer to start for buffer ready to read
+ const uint8_t *restrict const memPtr = priv->mBuf + pos;
+ // left buffer size from pos to end
+ const uint32_t memSize = sz - pos;
+ // number of objects possible to read
+ uint32_t memCount = memSize / objSize;
+ if (memCount == 0)
+ return 0;
+ // limit number of possible objects to read to objCount
+ if (memCount > objCount)
+ memCount = objCount;
+ // number of bytes to read from buffer
+ const uint32_t memEnd = memCount * objSize;
+ memcpy(buffer, memPtr, memEnd);
+ priv->mPos += memEnd;
+ return memCount;
+ }
+
+ int64_t write(VirtFile *restrict const file A_UNUSED,
+ const void *restrict const buffer A_UNUSED,
+ const uint32_t objSize A_UNUSED,
+ const uint32_t objCount A_UNUSED)
+ {
+ return 0;
+ }
+
+ int64_t fileLength(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return -1;
+
+ return file->mPrivate->mSize;
+ }
+
+ int64_t tell(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return -1;
+
+ return file->mPrivate->mPos;
+ }
+
+ int seek(VirtFile *restrict const file,
+ const uint64_t pos)
+ {
+ if (file == nullptr)
+ return 0;
+
+ if (pos > file->mPrivate->mSize)
+ return 0;
+ file->mPrivate->mPos = pos;
+ return 1;
+ }
+
+ int eof(VirtFile *restrict const file)
+ {
+ if (file == nullptr)
+ return -1;
+
+ return file->mPrivate->mPos >= file->mPrivate->mSize;
+ }
+} // namespace VirtFsZip
diff --git a/src/fs/virtfs/virtfszip.h b/src/fs/virtfs/virtfszip.h
new file mode 100644
index 000000000..c1ed7b05c
--- /dev/null
+++ b/src/fs/virtfs/virtfszip.h
@@ -0,0 +1,88 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2013-2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTILS_VIRTFSZIP_H
+#define UTILS_VIRTFSZIP_H
+
+#include "enums/simpletypes/append.h"
+#include "enums/simpletypes/skiperror.h"
+
+#include "localconsts.h"
+
+#include <vector>
+#include <string>
+
+struct VirtFile;
+struct VirtList;
+struct VirtFsFuncs;
+struct VirtZipEntry;
+struct ZipLocalHeader;
+
+namespace VirtFsZip
+{
+ VirtZipEntry *searchEntryByArchive(const std::string &restrict
+ archiveName);
+ ZipLocalHeader *searchHeaderByName(const std::string &restrict filename);
+ bool addToSearchPath(std::string newDir,
+ const Append append);
+ bool addToSearchPathSilent(std::string newDir,
+ const Append append);
+ bool removeFromSearchPath(std::string oldDir);
+ bool removeFromSearchPathSilent(std::string oldDir);
+ void init();
+ void initFuncs(VirtFsFuncs *restrict const ptr);
+ void deinit();
+ std::vector<VirtZipEntry*> &getEntries();
+ bool exists(std::string name);
+ VirtList *enumerateFiles(std::string dirName) RETURNS_NONNULL;
+ VirtList *enumerateFiles(std::string dirName,
+ VirtList *restrict const list) RETURNS_NONNULL;
+ bool isDirectory(std::string dirName);
+ bool isDirectoryInternal(std::string dirName);
+ bool isSymbolicLink(std::string name);
+ void freeList(VirtList *restrict const handle);
+ VirtFile *openRead(std::string filename);
+ VirtFile *openReadInternal(const std::string &filename);
+ VirtFile *openWrite(const std::string &restrict filename);
+ VirtFile *openAppend(const std::string &restrict filename);
+ bool setWriteDir(const std::string &restrict newDir);
+ std::string getRealDir(std::string filename);
+ std::string getRealDirInternal(const std::string &filename);
+ bool mkdir(const std::string &restrict dirName);
+ bool remove(const std::string &restrict filename);
+ void permitLinks(const bool val);
+ const char *getLastError();
+ int64_t read(VirtFile *restrict const handle,
+ void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount);
+ int64_t write(VirtFile *restrict const file,
+ const void *restrict const buffer,
+ const uint32_t objSize,
+ const uint32_t objCount);
+ int close(VirtFile *restrict const file);
+ int64_t fileLength(VirtFile *restrict const file);
+ int64_t tell(VirtFile *restrict const file);
+ int seek(VirtFile *restrict const file,
+ const uint64_t pos);
+ int eof(VirtFile *restrict const file);
+} // namespace VirtFsZip
+
+#endif // UTILS_VIRTFSZIP_H
diff --git a/src/fs/virtfs/virtfszip_unittest.cc b/src/fs/virtfs/virtfszip_unittest.cc
new file mode 100644
index 000000000..7ac442c78
--- /dev/null
+++ b/src/fs/virtfs/virtfszip_unittest.cc
@@ -0,0 +1,745 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2016-2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "catch.hpp"
+
+#include "fs/files.h"
+#include "fs/virtlist.h"
+
+#include "fs/virtfs/virtfszip.h"
+#include "fs/virtfs/virtzipentry.h"
+
+#include "utils/checkutils.h"
+#include "utils/delete2.h"
+
+#include "debug.h"
+
+TEST_CASE("VirtFsZip getEntries")
+{
+ VirtFsZip::init();
+ REQUIRE(VirtFsZip::getEntries().empty());
+ REQUIRE(VirtFsZip::searchEntryByArchive("test.zip") == nullptr);
+ VirtFsZip::deinit();
+}
+
+TEST_CASE("VirtFsZip addToSearchPath")
+{
+ VirtFsZip::init();
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix("data/test/");
+ std::vector<ZipLocalHeader*> headers;
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ SECTION("simple 1")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "/test.zip",
+ Append_false));
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "file2.zip") == nullptr);
+ REQUIRE(VirtFsZip::getEntries().size() == 1);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test.zip");
+ }
+
+ SECTION("simple 2")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "\\test.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "file2.zip") == nullptr);
+ REQUIRE(VirtFsZip::getEntries().size() == 1);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test.zip");
+ }
+
+ SECTION("simple 3")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_false));
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_false));
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test2.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test3.zip") == nullptr);
+ REQUIRE(VirtFsZip::getEntries().size() == 2);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test2.zip");
+ REQUIRE(VirtFsZip::getEntries()[1]->mArchiveName ==
+ prefix + "test.zip");
+ }
+
+ SECTION("simple 4")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test2.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test3.zip") == nullptr);
+ REQUIRE(VirtFsZip::getEntries().size() == 2);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test.zip");
+ REQUIRE(VirtFsZip::getEntries()[1]->mArchiveName ==
+ prefix + "test2.zip");
+ }
+
+ SECTION("simple 5")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test3.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test2.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test3.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test4.zip") == nullptr);
+ REQUIRE(VirtFsZip::getEntries().size() == 3);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test.zip");
+ REQUIRE(VirtFsZip::getEntries()[1]->mArchiveName ==
+ prefix + "test2.zip");
+ REQUIRE(VirtFsZip::getEntries()[2]->mArchiveName ==
+ prefix + "test3.zip");
+ }
+
+ SECTION("simple 6")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test3.zip",
+ Append_false));
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test2.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test3.zip") != nullptr);
+ REQUIRE(VirtFsZip::searchEntryByArchive(
+ prefix + "test4.zip") == nullptr);
+ REQUIRE(VirtFsZip::getEntries().size() == 3);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test3.zip");
+ REQUIRE(VirtFsZip::getEntries()[1]->mArchiveName ==
+ prefix + "test.zip");
+ REQUIRE(VirtFsZip::getEntries()[2]->mArchiveName ==
+ prefix + "test2.zip");
+ }
+
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip removeFromSearchPath")
+{
+ VirtFsZip::init();
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix("data/test/");
+ std::vector<ZipLocalHeader*> headers;
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ SECTION("simple 1")
+ {
+ REQUIRE_THROWS(VirtFsZip::removeFromSearchPath(
+ prefix + "test123.zip"));
+ }
+
+ SECTION("simple 2")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_true));
+ REQUIRE_THROWS(VirtFsZip::removeFromSearchPath(prefix + "test2.zip"));
+ REQUIRE(VirtFsZip::removeFromSearchPath(prefix + "test.zip"));
+ }
+
+ SECTION("simple 3")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test3.zip",
+ Append_false));
+ REQUIRE(VirtFsZip::getEntries().size() == 3);
+ REQUIRE_THROWS(VirtFsZip::removeFromSearchPath(prefix + "test4.zip"));
+ REQUIRE(VirtFsZip::removeFromSearchPath(prefix + "test.zip"));
+ REQUIRE(VirtFsZip::getEntries().size() == 2);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test3.zip");
+ REQUIRE(VirtFsZip::getEntries()[1]->mArchiveName ==
+ prefix + "test2.zip");
+ REQUIRE_THROWS(VirtFsZip::removeFromSearchPath(prefix + "test.zip"));
+ REQUIRE(VirtFsZip::getEntries().size() == 2);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test3.zip");
+ REQUIRE(VirtFsZip::getEntries()[1]->mArchiveName ==
+ prefix + "test2.zip");
+ REQUIRE(VirtFsZip::removeFromSearchPath(prefix + "//test2.zip"));
+ REQUIRE_THROWS(VirtFsZip::removeFromSearchPath(prefix + "test2.zip"));
+ REQUIRE(VirtFsZip::getEntries().size() == 1);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test3.zip");
+ }
+
+ SECTION("simple 4")
+ {
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "\\test.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::getEntries().size() == 1);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test.zip");
+ REQUIRE_THROWS(VirtFsZip::removeFromSearchPath(prefix + "test2.zip"));
+ REQUIRE(VirtFsZip::removeFromSearchPath(prefix + "\\test.zip"));
+ REQUIRE(VirtFsZip::getEntries().size() == 0);
+ REQUIRE(VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_true));
+ REQUIRE(VirtFsZip::getEntries().size() == 1);
+ REQUIRE(VirtFsZip::getEntries()[0]->mArchiveName ==
+ prefix + "test.zip");
+ }
+
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip exists")
+{
+ VirtFsZip::init();
+ logger = new Logger();
+ VirtFsZip::addToSearchPathSilent("data\\test/test2.zip",
+ Append_false);
+ VirtFsZip::addToSearchPathSilent("../data\\test/test2.zip",
+ Append_false);
+
+ REQUIRE(VirtFsZip::exists("dir2//units.xml") == true);
+ REQUIRE(VirtFsZip::exists("test/units123.xml") == false);
+ REQUIRE(VirtFsZip::exists("tesQ/units.xml") == false);
+ REQUIRE(VirtFsZip::exists("units1.xml") == false);
+ REQUIRE(VirtFsZip::exists("dir/hide.png") == true);
+ REQUIRE(VirtFsZip::exists("dir/brimmedhat.png") == false);
+
+ VirtFsZip::addToSearchPathSilent("data/test/test.zip",
+ Append_false);
+ VirtFsZip::addToSearchPathSilent("../data/test/test.zip",
+ Append_false);
+
+ REQUIRE(VirtFsZip::exists("dir2\\units.xml") == true);
+ REQUIRE(VirtFsZip::exists("test/units123.xml") == false);
+ REQUIRE(VirtFsZip::exists("tesQ/units.xml") == false);
+ REQUIRE(VirtFsZip::exists("units1.xml") == false);
+ REQUIRE(VirtFsZip::exists("dir/hide.png") == true);
+ REQUIRE(VirtFsZip::exists("dir/brimmedhat.png") == true);
+
+ VirtFsZip::removeFromSearchPathSilent("data/test/test2.zip");
+ VirtFsZip::removeFromSearchPathSilent("../data/test/test2.zip");
+
+ REQUIRE(VirtFsZip::exists("dir2//units.xml") == false);
+ REQUIRE(VirtFsZip::exists("test/units123.xml") == false);
+ REQUIRE(VirtFsZip::exists("tesQ/units.xml") == false);
+ REQUIRE(VirtFsZip::exists("units1.xml") == false);
+ REQUIRE(VirtFsZip::exists("dir/\\/hide.png") == true);
+ REQUIRE(VirtFsZip::exists("dir/brimmedhat.png") == true);
+
+ REQUIRE_THROWS(VirtFsZip::exists("test/../units.xml"));
+
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip getRealDir")
+{
+ VirtFsZip::init();
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix("data/test/");
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+ VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_false);
+
+ REQUIRE(VirtFsZip::getRealDir(".") == "");
+ REQUIRE(VirtFsZip::getRealDir("..") == "");
+ REQUIRE(VirtFsZip::getRealDir("test.txt") == prefix + "test2.zip");
+ REQUIRE(VirtFsZip::getRealDir("dir\\dye.png") ==
+ prefix + "test2.zip");
+ REQUIRE(VirtFsZip::getRealDir("zzz") == "");
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_false);
+ REQUIRE(VirtFsZip::getRealDir("dir//dye.png") ==
+ prefix + "test2.zip");
+ REQUIRE(VirtFsZip::getRealDir("dir///hide.png") ==
+ prefix + "test.zip");
+ REQUIRE(VirtFsZip::getRealDir("dir\\\\brimmedhat.png") ==
+ prefix + "test.zip");
+ REQUIRE(VirtFsZip::getRealDir("zzz") == "");
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test.zip");
+
+ REQUIRE(VirtFsZip::getRealDir("dir/brimmedhat.png") == "");
+ REQUIRE(VirtFsZip::getRealDir("test.txt") == prefix + "test2.zip");
+ REQUIRE(VirtFsZip::getRealDir("dir//dye.png") ==
+ prefix + "test2.zip");
+ REQUIRE(VirtFsZip::getRealDir("zzz") == "");
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test2.zip");
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+static bool inList(VirtList *list,
+ const std::string &name)
+{
+ FOR_EACH (StringVectCIter, it, list->names)
+ {
+ if (*it == name)
+ return true;
+ }
+ return false;
+}
+
+TEST_CASE("VirtFsZip enumerateFiles1")
+{
+ VirtFsZip::init();
+ logger = new Logger;
+ std::string name("data/test/test.zip");
+ std::string prefix("data\\test/");
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_false);
+
+ VirtList *list = nullptr;
+
+ list = VirtFsZip::enumerateFiles("dir");
+ REQUIRE(list->names.size() == 2);
+ REQUIRE(inList(list, "brimmedhat.png"));
+ REQUIRE(inList(list, "hide.png"));
+ VirtFsZip::freeList(list);
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test.zip");
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip enumerateFiles2")
+{
+ VirtFsZip::init();
+ logger = new Logger;
+ std::string name("data/test/test.zip");
+ std::string prefix("data//test/");
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_true);
+ VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_true);
+
+ VirtList *list = nullptr;
+
+ list = VirtFsZip::enumerateFiles("dir");
+ FOR_EACH (StringVectCIter, it, list->names)
+ {
+ logger->log("filename: " + *it);
+ }
+
+ REQUIRE(list->names.size() == 5);
+ REQUIRE(inList(list, "brimmedhat.png"));
+ REQUIRE(inList(list, "hide.png"));
+ REQUIRE(inList(list, "1"));
+ REQUIRE(inList(list, "gpl"));
+ REQUIRE(inList(list, "dye.png"));
+ VirtFsZip::freeList(list);
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test.zip");
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test2.zip");
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip enumerateFiles3")
+{
+ VirtFsZip::init();
+ logger = new Logger;
+ std::string name("data/test/test.zip");
+ std::string prefix("data\\test/");
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_false);
+
+ VirtList *list = nullptr;
+
+ list = VirtFsZip::enumerateFiles("/");
+ REQUIRE(list->names.size() == 1);
+ REQUIRE(inList(list, "dir"));
+ VirtFsZip::freeList(list);
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test.zip");
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip enumerateFiles4")
+{
+ VirtFsZip::init();
+ logger = new Logger;
+ std::string name("data/test/test.zip");
+ std::string prefix("data\\test/");
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_false);
+
+ VirtList *list = nullptr;
+
+ list = VirtFsZip::enumerateFiles("/");
+ REQUIRE(list->names.size() == 4);
+ REQUIRE(inList(list, "dir"));
+ REQUIRE(inList(list, "dir2"));
+ REQUIRE(inList(list, "test.txt"));
+ REQUIRE(inList(list, "units.xml"));
+ VirtFsZip::freeList(list);
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test2.zip");
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip isDirectory")
+{
+ VirtFsZip::init();
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix("data/test/");
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_false);
+
+ REQUIRE(VirtFsZip::isDirectory("dir2/units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2/units.xml/") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2//units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2/units123.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2//units123.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("tesQ/units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("tesQ//units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir2/") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir2//") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir/1") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir//1") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir\\1/") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir/1") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir/1/zzz") == false);
+ REQUIRE(VirtFsZip::isDirectory("test/dir1\\") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ/") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ//") == false);
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_false);
+
+ REQUIRE(VirtFsZip::isDirectory("dir2/units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2/units.xml/") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2\\units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2/units123.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2//units123.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("tesQ/units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("tesQ//units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir2/") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir2\\") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir/1") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir//1") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir//1/") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir/1") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir/1/zzz") == false);
+ REQUIRE(VirtFsZip::isDirectory("test/dir1//") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ/") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ//") == false);
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test2.zip");
+
+ REQUIRE(VirtFsZip::isDirectory("dir2/units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2/units.xml/") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2//units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2/units123.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2//units123.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("tesQ/units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("tesQ//units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("units.xml") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir") == true);
+ REQUIRE(VirtFsZip::isDirectory("dir2/") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir2//") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir/1") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir\\1") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir//1/") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir/1") == false);
+ REQUIRE(VirtFsZip::isDirectory("dir/1/zzz") == false);
+ REQUIRE(VirtFsZip::isDirectory("test/dir1//") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ/") == false);
+ REQUIRE(VirtFsZip::isDirectory("testQ//") == false);
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test.zip");
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip openRead")
+{
+ VirtFsZip::init();
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix("data/test/");
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_false);
+
+ VirtFile *file = nullptr;
+
+ file = VirtFsZip::openRead("dir2/units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsZip::close(file);
+ file = VirtFsZip::openRead("dir2\\units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsZip::close(file);
+ file = VirtFsZip::openRead("dir2/units123.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("tesQ/units.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("units.xml1");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("testQ");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("dir/brimmedhat.png");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("dir//brimmedhat.png");
+ REQUIRE(file == nullptr);
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test.zip",
+ Append_false);
+
+ file = VirtFsZip::openRead("dir2/units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsZip::close(file);
+ file = VirtFsZip::openRead("dir2//units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsZip::close(file);
+ file = VirtFsZip::openRead("dir2/units123.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("tesQ/units.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("units.xml1");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("testQ");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("dir/brimmedhat.png");
+ REQUIRE(file != nullptr);
+ VirtFsZip::close(file);
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test.zip");
+
+ file = VirtFsZip::openRead("dir2/units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsZip::close(file);
+ file = VirtFsZip::openRead("dir2\\/\\units.xml");
+ REQUIRE(file != nullptr);
+ VirtFsZip::close(file);
+ file = VirtFsZip::openRead("dir2/units123.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("tesQ/units.xml");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("units.xml1");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("testQ");
+ REQUIRE(file == nullptr);
+ file = VirtFsZip::openRead("dir/brimmedhat.png");
+ REQUIRE(file == nullptr);
+
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test2.zip");
+
+ VirtFsZip::deinit();
+ delete2(logger);
+}
+
+TEST_CASE("VirtFsZip read")
+{
+ VirtFsZip::init();
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix("data/test/");
+ if (Files::existsLocal(name) == false)
+ prefix = "../" + prefix;
+
+ VirtFsZip::addToSearchPathSilent(prefix + "test2.zip",
+ Append_false);
+ VirtFile *file = nullptr;
+ void *restrict buffer = nullptr;
+
+ SECTION("test 1")
+ {
+ file = VirtFsZip::openRead("dir2//test.txt");
+ REQUIRE(file != nullptr);
+ REQUIRE(VirtFsZip::fileLength(file) == 23);
+ const int fileSize = VirtFsZip::fileLength(file);
+
+ buffer = calloc(fileSize + 1, 1);
+ REQUIRE(VirtFsZip::read(file, buffer, 1, fileSize) == fileSize);
+ REQUIRE(strcmp(static_cast<char*>(buffer),
+ "test line 1\ntest line 2") == 0);
+ REQUIRE(VirtFsZip::tell(file) == fileSize);
+ REQUIRE(VirtFsZip::eof(file) == true);
+ }
+
+ SECTION("test 2")
+ {
+ file = VirtFsZip::openRead("dir2\\/test.txt");
+ REQUIRE(file != nullptr);
+ REQUIRE(VirtFsZip::fileLength(file) == 23);
+ const int fileSize = VirtFsZip::fileLength(file);
+
+ buffer = calloc(fileSize + 1, 1);
+ REQUIRE(VirtFsZip::seek(file, 12) != 0);
+ REQUIRE(VirtFsZip::eof(file) == false);
+ REQUIRE(VirtFsZip::tell(file) == 12);
+ REQUIRE(VirtFsZip::read(file, buffer, 1, 11) == 11);
+ REQUIRE(strcmp(static_cast<char*>(buffer),
+ "test line 2") == 0);
+ REQUIRE(VirtFsZip::eof(file) == true);
+ }
+
+ SECTION("test 3")
+ {
+ file = VirtFsZip::openRead("dir2//test.txt");
+ REQUIRE(file != nullptr);
+ const int fileSize = VirtFsZip::fileLength(file);
+
+ buffer = calloc(fileSize + 1, 1);
+ for (int f = 0; f < fileSize; f ++)
+ {
+ REQUIRE(VirtFsZip::seek(file, f) != 0);
+ REQUIRE(VirtFsZip::eof(file) == false);
+ REQUIRE(VirtFsZip::tell(file) == f);
+ }
+ }
+
+ SECTION("test 4")
+ {
+ file = VirtFsZip::openRead("dir2/test.txt");
+ REQUIRE(file != nullptr);
+ const int fileSize = VirtFsZip::fileLength(file);
+ const char *restrict const str = "test line 1\ntest line 2";
+ buffer = calloc(fileSize + 1, 1);
+ for (int f = 0; f < fileSize - 1; f ++)
+ {
+ REQUIRE(VirtFsZip::read(file, buffer, 1, 1) == 1);
+ REQUIRE(static_cast<char*>(buffer)[0] == str[f]);
+ REQUIRE(VirtFsZip::eof(file) == false);
+ REQUIRE(VirtFsZip::tell(file) == f + 1);
+ }
+ REQUIRE(VirtFsZip::read(file, buffer, 1, 1) == 1);
+ REQUIRE(static_cast<char*>(buffer)[0] == str[22]);
+ REQUIRE(VirtFsZip::eof(file) == true);
+ REQUIRE(VirtFsZip::tell(file) == fileSize);
+ }
+
+ SECTION("test 5")
+ {
+ file = VirtFsZip::openRead("dir2\\\\test.txt");
+ REQUIRE(file != nullptr);
+ const int fileSize = VirtFsZip::fileLength(file);
+ const char *restrict const str = "test line 1\ntest line 2";
+ buffer = calloc(fileSize + 1, 1);
+ for (int f = 0; f < fileSize - 1; f += 2)
+ {
+ REQUIRE(VirtFsZip::read(file, buffer, 2, 1) == 1);
+ REQUIRE(static_cast<char*>(buffer)[0] == str[f]);
+ REQUIRE(static_cast<char*>(buffer)[1] == str[f + 1]);
+ REQUIRE(VirtFsZip::eof(file) == false);
+ REQUIRE(VirtFsZip::tell(file) == f + 2);
+ }
+ REQUIRE(VirtFsZip::eof(file) == false);
+ REQUIRE(VirtFsZip::tell(file) == 22);
+ REQUIRE(VirtFsZip::read(file, buffer, 2, 1) == 0);
+ REQUIRE(VirtFsZip::eof(file) == false);
+ }
+
+ SECTION("test 6")
+ {
+ file = VirtFsZip::openRead("dir2//test.txt");
+ REQUIRE(file != nullptr);
+ const int fileSize = VirtFsZip::fileLength(file);
+ const char *restrict const str = "test line 1\ntest line 2";
+ buffer = calloc(fileSize + 1, 1);
+ for (int f = 0; f < fileSize - 1; f += 2)
+ {
+ REQUIRE(VirtFsZip::read(file, buffer, 1, 2) == 2);
+ REQUIRE(static_cast<char*>(buffer)[0] == str[f]);
+ REQUIRE(static_cast<char*>(buffer)[1] == str[f + 1]);
+ REQUIRE(VirtFsZip::eof(file) == false);
+ REQUIRE(VirtFsZip::tell(file) == f + 2);
+ }
+ REQUIRE(VirtFsZip::eof(file) == false);
+ REQUIRE(VirtFsZip::tell(file) == 22);
+ REQUIRE(VirtFsZip::read(file, buffer, 1, 2) == 1);
+ REQUIRE(static_cast<char*>(buffer)[0] == str[22]);
+ REQUIRE(VirtFsZip::eof(file) == true);
+ }
+
+ VirtFsZip::close(file);
+ free(buffer);
+ VirtFsZip::removeFromSearchPathSilent(prefix + "test2.zip");
+ VirtFsZip::deinit();
+ delete2(logger);
+}
diff --git a/src/fs/virtfs/virtzipentry.cpp b/src/fs/virtfs/virtzipentry.cpp
new file mode 100644
index 000000000..bed925d2f
--- /dev/null
+++ b/src/fs/virtfs/virtzipentry.cpp
@@ -0,0 +1,38 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fs/virtfs/virtzipentry.h"
+
+#include "fs/virtfs/ziplocalheader.h"
+
+#include "utils/dtor.h"
+
+#include "debug.h"
+
+VirtZipEntry::VirtZipEntry(const std::string &restrict archiveName) :
+ mArchiveName(archiveName),
+ mHeaders()
+{
+}
+
+VirtZipEntry::~VirtZipEntry()
+{
+ delete_all(mHeaders);
+}
diff --git a/src/fs/virtfs/virtzipentry.h b/src/fs/virtfs/virtzipentry.h
new file mode 100644
index 000000000..fac36cc2d
--- /dev/null
+++ b/src/fs/virtfs/virtzipentry.h
@@ -0,0 +1,45 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTILS_VIRTZIPENTRY_H
+#define UTILS_VIRTZIPENTRY_H
+
+#include <string>
+#include <vector>
+
+#include "localconsts.h"
+
+struct ZipLocalHeader;
+
+struct VirtZipEntry final
+{
+ explicit VirtZipEntry(const std::string &restrict archiveName);
+
+ A_DELETE_COPY(VirtZipEntry)
+
+ ~VirtZipEntry();
+
+ std::string mArchiveName;
+
+ std::vector<ZipLocalHeader*> mHeaders;
+ std::vector<std::string> mDirs;
+};
+
+#endif // UTILS_VIRTZIPENTRY_H
diff --git a/src/fs/virtfs/zip.cpp b/src/fs/virtfs/zip.cpp
new file mode 100644
index 000000000..f27dd05d0
--- /dev/null
+++ b/src/fs/virtfs/zip.cpp
@@ -0,0 +1,291 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fs/virtfs/zip.h"
+
+#include "fs/paths.h"
+
+#include "fs/virtfs/virtzipentry.h"
+#include "fs/virtfs/ziplocalheader.h"
+
+#include "utils/checkutils.h"
+#include "utils/stringutils.h"
+
+#include <iostream>
+#include <unistd.h>
+#include <zlib.h>
+
+#include "debug.h"
+
+#define readVal(val, sz, msg) \
+ cnt = fread(static_cast<void*>(val), 1, sz, arcFile); \
+ if (cnt != sz) \
+ { \
+ reportAlways("Error reading " msg " in file %s", \
+ archiveName.c_str()); \
+ delete header; \
+ delete [] buf; \
+ fclose(arcFile); \
+ return false; \
+ } \
+
+namespace Zip
+{
+ bool readArchiveInfo(VirtZipEntry *const entry)
+ {
+ if (entry == nullptr)
+ {
+ reportAlways("Entry is null.");
+ return false;
+ }
+ const std::string archiveName = entry->mArchiveName;
+ std::vector<ZipLocalHeader*> &restrict headers = entry->mHeaders;
+ std::vector<std::string> &restrict dirs = entry->mDirs;
+ FILE *restrict const arcFile = fopen(archiveName.c_str(),
+ "r");
+ if (arcFile == nullptr)
+ {
+ reportAlways("Can't open zip file %s",
+ archiveName.c_str());
+ return false;
+ }
+ size_t cnt = 0U;
+ uint8_t *const buf = new uint8_t[65535 + 10];
+ uint16_t val16 = 0U;
+ uint16_t method = 0U;
+ ZipLocalHeader *header = nullptr;
+
+ logger->log("Read archive: %s", archiveName.c_str());
+ // format source https://en.wikipedia.org/wiki/Zip_%28file_format%29
+ while (feof(arcFile) == 0)
+ {
+ // file header pointer on 0
+ // read file header signature
+ readVal(buf, 4, "zip file header"); // + 4
+ // pointer on 4
+
+ if (buf[0] == 0x50 &&
+ buf[1] == 0x4B &&
+ buf[2] == 0x03 &&
+ buf[3] == 0x04)
+ { // local file header
+ header = new ZipLocalHeader;
+ header->zipEntry = entry;
+ // skip useless fields
+ fseek(arcFile, 4, SEEK_CUR); // + 4
+ // file header pointer on 8
+ // +++ need add endian specific decoding for method
+ readVal(&method, 2, "compression method") // + 2
+ header->compressed = (method != 0);
+ // file header pointer on 10
+ fseek(arcFile, 8, SEEK_CUR); // + 8
+ // file header pointer on 18
+ readVal(&header->compressSize, 4,
+ "zip compressed size") // + 4
+ // file header pointer on 22
+ // +++ need add endian specific decoding for val32
+ readVal(&header->uncompressSize, 4,
+ "zip uncompressed size") // + 4
+ // file header pointer on 26
+ // +++ need add endian specific decoding for val32
+ readVal(&val16, 2, "file name length") // + 2
+ // file header pointer on 28
+ const uint32_t fileNameLen = CAST_U32(val16);
+ if (fileNameLen > 1000)
+ {
+ reportAlways("Error too long file name in file %s",
+ archiveName.c_str());
+ delete header;
+ delete [] buf;
+ fclose(arcFile);
+ return false;
+ }
+ readVal(&val16, 2, "extra field length") // + 2
+ // file header pointer on 30
+ const uint32_t extraFieldLen = CAST_U32(val16);
+ readVal(buf, fileNameLen, "file name");
+ // file header pointer on 30 + fileNameLen
+ buf[fileNameLen] = 0;
+ header->fileName = std::string(
+ reinterpret_cast<char*>(buf));
+ prepareFsPath(header->fileName);
+ header->dataOffset = ftell(arcFile) + extraFieldLen;
+ fseek(arcFile, extraFieldLen + header->compressSize, SEEK_CUR);
+ // pointer on 30 + fileNameLen + extraFieldLen + compressSize
+ if (findLast(header->fileName, "/") == false)
+ {
+ headers.push_back(header);
+ logger->log(" file name: %s",
+ header->fileName.c_str());
+ logger->log(" compression method: %u",
+ CAST_U32(method));
+ logger->log(" compressed size: %u",
+ header->compressSize);
+ logger->log(" uncompressed size: %u",
+ header->uncompressSize);
+ }
+ else
+ {
+ dirs.push_back(header->fileName);
+ delete header;
+ }
+ }
+ else if (buf[0] == 0x50 &&
+ buf[1] == 0x4B &&
+ buf[2] == 0x01 &&
+ buf[3] == 0x02)
+ { // central directory file header
+ // !!! This is quick way for read zip archives. !!!
+ // !!! It ignore modified files in archive. !!!
+ // ignoring central directory entries
+ break;
+ }
+ else if (buf[0] == 0x50 &&
+ buf[1] == 0x4B &&
+ buf[2] == 0x05 &&
+ buf[3] == 0x06)
+ { // end of central directory
+ // !!! This is quick way for read zip archives. !!!
+ // !!! It ignore modified files in archive. !!!
+ // ignoring end of central directory
+ break;
+ }
+ else
+ {
+ reportAlways("Error in header signature (0x%02x%02x%02x%02x)"
+ " in file %s",
+ buf[0],
+ buf[1],
+ buf[2],
+ buf[3],
+ archiveName.c_str());
+ delete [] buf;
+ fclose(arcFile);
+ return false;
+ }
+ }
+ delete [] buf;
+ fclose(arcFile);
+ return true;
+ }
+
+ void reportZlibError(const std::string &text,
+ const int err)
+ {
+ reportAlways("Zlib error: '%s' in %s",
+ text.c_str(),
+ getZlibError(err).c_str());
+ }
+
+ std::string getZlibError(const int err)
+ {
+ switch (err)
+ {
+ case Z_OK:
+ return std::string();
+ default:
+ return "unknown zlib error";
+ }
+ }
+
+ uint8_t *readCompressedFile(const ZipLocalHeader *restrict const header)
+ {
+ if (header == nullptr)
+ {
+ reportAlways("Zip::readCompressedFile: header is null");
+ return nullptr;
+ }
+ FILE *restrict const arcFile = fopen(
+ header->zipEntry->mArchiveName.c_str(),
+ "r");
+ if (arcFile == nullptr)
+ {
+ reportAlways("Can't open zip file %s",
+ header->zipEntry->mArchiveName.c_str());
+ return nullptr;
+ }
+
+ fseek(arcFile, header->dataOffset, SEEK_SET);
+ const uint32_t compressSize = header->compressSize;
+ uint8_t *const buf = new uint8_t[compressSize];
+ if (fread(static_cast<void*>(buf), 1, compressSize, arcFile) !=
+ compressSize)
+ {
+ reportAlways("Read zip compressed file error from archive: %s",
+ header->zipEntry->mArchiveName.c_str());
+ fclose(arcFile);
+ delete [] buf;
+ return nullptr;
+ }
+ fclose(arcFile);
+ return buf;
+ }
+
+ uint8_t *readFile(const ZipLocalHeader *restrict const header)
+ {
+ if (header == nullptr)
+ {
+ reportAlways("Open zip file error. header is null.");
+ return nullptr;
+ }
+ uint8_t *restrict const in = readCompressedFile(header);
+ if (in == nullptr)
+ return nullptr;
+ if (header->compressed == false)
+ return in; // return as is if data not compressed
+ const size_t outSize = header->uncompressSize;
+ uint8_t *restrict const out = new uint8_t[outSize];
+ if (outSize == 0)
+ return out;
+
+ z_stream strm;
+ strm.zalloc = nullptr;
+ strm.zfree = nullptr;
+ strm.opaque = nullptr;
+ strm.next_in = in;
+ strm.avail_in = header->compressSize;
+ strm.next_out = out;
+ strm.avail_out = outSize;
+
+ int ret = inflateInit2(&strm, -MAX_WBITS);
+ if (ret != Z_OK)
+ {
+ reportZlibError(header->zipEntry->mArchiveName, ret);
+ delete [] in;
+ delete [] out;
+ return nullptr;
+ }
+ ret = inflate(&strm, Z_FINISH);
+// ret = inflate(&strm, Z_SYNC_FLUSH);
+ if (ret != Z_OK &&
+ ret != Z_STREAM_END)
+ {
+ reportZlibError("file decompression error",
+ ret);
+ inflateEnd(&strm);
+ delete [] in;
+ delete [] out;
+ return nullptr;
+ }
+ inflateEnd(&strm);
+ delete [] in;
+ return out;
+ }
+} // namespace Zip
diff --git a/src/fs/virtfs/zip.h b/src/fs/virtfs/zip.h
new file mode 100644
index 000000000..412dbcef9
--- /dev/null
+++ b/src/fs/virtfs/zip.h
@@ -0,0 +1,42 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTILS_ZIP_H
+#define UTILS_ZIP_H
+
+#include "localconsts.h"
+
+#include <string>
+#include <vector>
+
+struct VirtZipEntry;
+struct ZipLocalHeader;
+
+namespace Zip
+{
+ bool readArchiveInfo(VirtZipEntry *const entry);
+ std::string getZlibError(const int err);
+ void reportZlibError(const std::string &text,
+ const int err);
+ uint8_t *readCompressedFile(const ZipLocalHeader *restrict const header);
+ uint8_t *readFile(const ZipLocalHeader *restrict const header);
+} // namespace Zip
+
+#endif // UTILS_ZIP_H
diff --git a/src/fs/virtfs/zip_unittest.cc b/src/fs/virtfs/zip_unittest.cc
new file mode 100644
index 000000000..7ac74f66b
--- /dev/null
+++ b/src/fs/virtfs/zip_unittest.cc
@@ -0,0 +1,280 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "catch.hpp"
+
+#include "logger.h"
+
+#include "fs/files.h"
+
+#include "fs/virtfs/virtzipentry.h"
+#include "fs/virtfs/zip.h"
+#include "fs/virtfs/ziplocalheader.h"
+
+#include "utils/delete2.h"
+
+#include "debug.h"
+
+TEST_CASE("Zip readArchiveInfo")
+{
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix;
+ if (Files::existsLocal(name) == false)
+ prefix = "../";
+
+ SECTION("test.zip")
+ {
+ name = prefix + "data/test/test.zip";
+
+ VirtZipEntry *const entry = new VirtZipEntry(name);
+ std::vector<ZipLocalHeader*> &headers = entry->mHeaders;
+
+ REQUIRE(Zip::readArchiveInfo(entry));
+ REQUIRE(headers.size() == 2);
+ REQUIRE(entry->mArchiveName == name);
+ REQUIRE(headers[0]->fileName == "dir/hide.png");
+ REQUIRE(headers[0]->compressSize == 365);
+ REQUIRE(headers[0]->uncompressSize == 368);
+ REQUIRE(headers[1]->fileName == "dir/brimmedhat.png");
+ REQUIRE(headers[1]->compressSize == 1959);
+ REQUIRE(headers[1]->uncompressSize == 1959);
+
+ delete entry;
+ }
+
+ SECTION("test2.zip")
+ {
+ name = prefix + "data/test/test2.zip";
+
+ VirtZipEntry *const entry = new VirtZipEntry(name);
+ std::vector<ZipLocalHeader*> &headers = entry->mHeaders;
+
+ REQUIRE(Zip::readArchiveInfo(entry));
+ REQUIRE(headers.size() == 11);
+ REQUIRE(entry->mArchiveName == name);
+ REQUIRE(headers[0]->fileName == "test.txt");
+ REQUIRE(headers[0]->compressSize == 17);
+ REQUIRE(headers[0]->uncompressSize == 23);
+
+ REQUIRE(headers[1]->fileName == "dir2/hide.png");
+ REQUIRE(headers[1]->compressSize == 365);
+ REQUIRE(headers[1]->uncompressSize == 368);
+
+ REQUIRE(headers[2]->fileName == "dir2/test.txt");
+ REQUIRE(headers[2]->compressSize == 17);
+ REQUIRE(headers[2]->uncompressSize == 23);
+
+ REQUIRE(headers[3]->fileName == "dir2/paths.xml");
+ REQUIRE(headers[3]->compressSize == 154);
+ REQUIRE(headers[3]->uncompressSize == 185);
+
+ REQUIRE(headers[4]->fileName == "dir2/units.xml");
+ REQUIRE(headers[4]->compressSize == 202);
+ REQUIRE(headers[4]->uncompressSize == 306);
+
+ REQUIRE(headers[5]->fileName == "dir/hide.png");
+ REQUIRE(headers[5]->compressSize == 365);
+ REQUIRE(headers[5]->uncompressSize == 368);
+
+ REQUIRE(headers[6]->fileName == "dir/1/test.txt");
+ REQUIRE(headers[6]->compressSize == 17);
+ REQUIRE(headers[6]->uncompressSize == 23);
+
+ REQUIRE(headers[7]->fileName == "dir/1/file1.txt");
+ REQUIRE(headers[7]->compressSize == 17);
+ REQUIRE(headers[7]->uncompressSize == 23);
+
+ REQUIRE(headers[8]->fileName == "dir/gpl/palette.gpl");
+ REQUIRE(headers[8]->compressSize == 128);
+ REQUIRE(headers[8]->uncompressSize == 213);
+
+ REQUIRE(headers[9]->fileName == "dir/dye.png");
+ REQUIRE(headers[9]->compressSize == 794);
+ REQUIRE(headers[9]->uncompressSize == 794);
+
+ REQUIRE(headers[10]->fileName == "units.xml");
+ REQUIRE(headers[10]->compressSize == 202);
+ REQUIRE(headers[10]->uncompressSize == 306);
+
+ delete entry;
+ }
+
+ SECTION("test3.zip")
+ {
+ name = prefix + "data/test/test3.zip";
+
+ VirtZipEntry *const entry = new VirtZipEntry(name);
+ std::vector<ZipLocalHeader*> &headers = entry->mHeaders;
+
+ REQUIRE(Zip::readArchiveInfo(entry));
+ REQUIRE(headers.size() == 2);
+ REQUIRE(entry->mArchiveName == name);
+ REQUIRE(headers[0]->fileName == "test.txt");
+ REQUIRE(headers[0]->compressSize == 17);
+ REQUIRE(headers[0]->uncompressSize == 23);
+ REQUIRE(headers[1]->fileName == "units.xml");
+ REQUIRE(headers[1]->compressSize == 202);
+ REQUIRE(headers[1]->uncompressSize == 306);
+
+ delete entry;
+ }
+
+ SECTION("test4.zip")
+ {
+ name = prefix + "data/test/test4.zip";
+
+ VirtZipEntry *const entry = new VirtZipEntry(name);
+ std::vector<ZipLocalHeader*> &headers = entry->mHeaders;
+
+ REQUIRE(Zip::readArchiveInfo(entry));
+ REQUIRE(entry->mArchiveName == name);
+ REQUIRE(headers.size() == 0);
+
+ delete entry;
+ }
+
+ delete2(logger);
+}
+
+TEST_CASE("Zip readCompressedFile")
+{
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix;
+ if (Files::existsLocal(name) == false)
+ prefix = "../";
+
+ SECTION("empty")
+ {
+ REQUIRE_THROWS(Zip::readCompressedFile(nullptr));
+ }
+
+ SECTION("test2.zip")
+ {
+ name = prefix + "data/test/test2.zip";
+
+ VirtZipEntry *const entry = new VirtZipEntry(name);
+ std::vector<ZipLocalHeader*> &headers = entry->mHeaders;
+
+ REQUIRE(Zip::readArchiveInfo(entry));
+ REQUIRE(headers.size() == 11);
+ REQUIRE(entry->mArchiveName == name);
+ // test.txt
+ uint8_t *const buf = Zip::readCompressedFile(headers[0]);
+ REQUIRE(buf != nullptr);
+ delete [] buf;
+ delete entry;
+ }
+
+ delete2(logger);
+}
+
+TEST_CASE("Zip readFile")
+{
+ logger = new Logger();
+ std::string name("data/test/test.zip");
+ std::string prefix;
+ if (Files::existsLocal(name) == false)
+ prefix = "../";
+
+ SECTION("empty")
+ {
+ REQUIRE_THROWS(Zip::readFile(nullptr));
+ }
+
+ SECTION("test.zip")
+ {
+ name = prefix + "data/test/test.zip";
+
+ VirtZipEntry *const entry = new VirtZipEntry(name);
+ std::vector<ZipLocalHeader*> &headers = entry->mHeaders;
+
+ REQUIRE(Zip::readArchiveInfo(entry));
+ REQUIRE(headers.size() == 2);
+ REQUIRE(entry->mArchiveName == name);
+ for (int f = 0; f < 2; f ++)
+ {
+ logger->log("test header: %s, %u, %u",
+ headers[f]->fileName.c_str(),
+ headers[f]->compressSize,
+ headers[f]->uncompressSize);
+ uint8_t *const buf = Zip::readFile(headers[f]);
+ REQUIRE(buf != nullptr);
+ delete [] buf;
+ }
+ delete entry;
+ }
+
+ SECTION("test2.zip")
+ {
+ name = prefix + "data/test/test2.zip";
+
+ VirtZipEntry *const entry = new VirtZipEntry(name);
+ std::vector<ZipLocalHeader*> &headers = entry->mHeaders;
+
+ REQUIRE(Zip::readArchiveInfo(entry));
+ REQUIRE(headers.size() == 11);
+ REQUIRE(entry->mArchiveName == name);
+ // test.txt
+ uint8_t *buf = Zip::readFile(headers[0]);
+ REQUIRE(buf != nullptr);
+ const std::string str = std::string(reinterpret_cast<char*>(buf),
+ headers[0]->uncompressSize);
+ REQUIRE(str == "test line 1\ntest line 2");
+ delete [] buf;
+ for (int f = 0; f < 11; f ++)
+ {
+ logger->log("test header: %s, %u, %u",
+ headers[f]->fileName.c_str(),
+ headers[f]->compressSize,
+ headers[f]->uncompressSize);
+ buf = Zip::readFile(headers[f]);
+ REQUIRE(buf != nullptr);
+ delete [] buf;
+ }
+ delete entry;
+ }
+
+ SECTION("test3.zip")
+ {
+ name = prefix + "data/test/test3.zip";
+
+ VirtZipEntry *const entry = new VirtZipEntry(name);
+ std::vector<ZipLocalHeader*> &headers = entry->mHeaders;
+
+ REQUIRE(Zip::readArchiveInfo(entry));
+ REQUIRE(headers.size() == 2);
+ REQUIRE(entry->mArchiveName == name);
+ for (int f = 0; f < 2; f ++)
+ {
+ logger->log("test header: %s, %u, %u",
+ headers[f]->fileName.c_str(),
+ headers[f]->compressSize,
+ headers[f]->uncompressSize);
+ uint8_t *const buf = Zip::readFile(headers[f]);
+ REQUIRE(buf != nullptr);
+ delete [] buf;
+ }
+ delete entry;
+ }
+
+ delete2(logger);
+}
diff --git a/src/fs/virtfs/ziplocalheader.cpp b/src/fs/virtfs/ziplocalheader.cpp
new file mode 100644
index 000000000..1fed2afd8
--- /dev/null
+++ b/src/fs/virtfs/ziplocalheader.cpp
@@ -0,0 +1,39 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fs/virtfs/ziplocalheader.h"
+
+#include "fs/virtfs/virtzipentry.h"
+
+#include "localconsts.h"
+
+#include <string>
+
+#include "debug.h"
+
+ZipLocalHeader::ZipLocalHeader() :
+ fileName(),
+ zipEntry(nullptr),
+ dataOffset(0U),
+ compressSize(0U),
+ uncompressSize(0U),
+ compressed(false)
+{
+}
diff --git a/src/fs/virtfs/ziplocalheader.h b/src/fs/virtfs/ziplocalheader.h
new file mode 100644
index 000000000..f3a1894ce
--- /dev/null
+++ b/src/fs/virtfs/ziplocalheader.h
@@ -0,0 +1,44 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2017 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTILS_ZIPLOCALHEADER_H
+#define UTILS_ZIPLOCALHEADER_H
+
+#include "localconsts.h"
+
+#include <string>
+
+struct VirtZipEntry;
+
+struct ZipLocalHeader final
+{
+ ZipLocalHeader();
+
+ A_DELETE_COPY(ZipLocalHeader)
+
+ std::string fileName;
+ VirtZipEntry *zipEntry;
+ uint32_t dataOffset;
+ uint32_t compressSize;
+ uint32_t uncompressSize;
+ bool compressed;
+};
+
+#endif // UTILS_ZIPLOCALHEADER_H