Files
wagic/JGE/src/zipFS/zfsystem.cpp
wagic.the.homebrew 2e3331a0c0 - Speed improvements to the filesystem (partial fix for issue 767)
- minor speed improvement in the shop (removed a locked texture loading that wasn't necessary)
2011-12-13 15:14:47 +00:00

690 lines
18 KiB
C++

//Important: This file has been modified in order to be integrated in to JGE++
//
// zfsystem.cpp: implementation of the zip file system classes.
//
// Copyright (C) 2004 Tanguy Fautré.
// For conditions of distribution and use,
// see copyright notice in zfsystem.h
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "zfsystem.h"
// Debug
#include "../../include/JLogger.h"
#include "fileio.h" // I/O facilities
#if defined (WIN32)
#include <sys/types.h>
#endif
#include <sys/stat.h>
namespace zip_file_system {
using namespace std;
//////////////////////////////////////////////////////////////////////
// Static variables initialization
//////////////////////////////////////////////////////////////////////
filesystem * izfstream::pDefaultFS = NULL;
string filesystem::CurrentZipName = "";
ifstream filesystem::CurrentZipFile;
filesystem * filesystem::pCurrentFS = NULL;
static const int STORED = 0;
static const int DEFLATED = 8;
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
filesystem::filesystem(const char * BasePath, const char * FileExt, bool DefaultFS)
: m_BasePath(BasePath), m_FileExt(FileExt)
{
using io_facilities::search_iterator;
// Init m_BasePath and be sure the base path finish with a '/' or a '\'
if (! m_BasePath.empty()) {
string::iterator c = m_BasePath.end();
c--;
if ((*c != '/') && (*c != '\\'))
m_BasePath += '/';
}
// Search all *.zip files (or whatever the ZipExt specify as the file extension)
// Search is case insensitive (see fileio.h for details)
// The case insensitive state is mostly because some of the filesystems we support such as FAT32 are case insensitive
// Being case sensitive would lead to weird bugs on these systems.
vector<string> ZipFiles;
for (search_iterator ZSrch = (m_BasePath + "*." + m_FileExt).c_str(); ZSrch != ZSrch.end(); ++ZSrch)
ZipFiles.push_back(ZSrch.Name());
// Open each zip files that have been found, in alphabetic order
sort(ZipFiles.begin(), ZipFiles.end());
for (vector<string>::const_iterator ZipIt = ZipFiles.begin(); ZipIt != ZipFiles.end(); ++ZipIt)
InsertZip(ZipIt->c_str(), ZipIt - ZipFiles.begin());
// Should we make this the default File System for ifile?
if (DefaultFS)
MakeDefault();
}
//////////////////////////////////////////////////////////////////////
// File System Member Functions
//////////////////////////////////////////////////////////////////////
void filesystem::Open(izfstream & File, const char * Filename)
{
// Close the file if it was opened;
File.close();
File.setFS(this);
// Generate the path and see if the file is zipped or not
string FullPath = m_BasePath + Filename;
// File is not zipped
if (FileNotZipped(FullPath.c_str())) {
// Link the izfile object with an opened filebuf
filebuf * FileBuf = new filebuf;
FileBuf->open(FullPath.c_str(), ios::binary | ios::in);
if (FileBuf->is_open()) {
delete File.rdbuf(FileBuf);
File.clear(ios::goodbit);
File.m_FilePath = Filename;
File.m_FullFilePath = FullPath;
File.m_Zipped = false;
}
// File is maybe zipped
} else {
file_info FileInfo;
string ZipPath;
// Check whether the file is zipped, whether the file is a directory and try to open.
if (FindFile(Filename, &FileInfo) && (! FileInfo.m_Directory) && (! ((ZipPath = FindZip(FileInfo.m_PackID)).empty()))) {
// Get the position of the compressed data
if (CurrentZipName.size())
{
if ((pCurrentFS!= this) || (CurrentZipName.compare(ZipPath) != 0))
{
CurrentZipFile.close();
CurrentZipName = "";
pCurrentFS = NULL;
}
}
if (!CurrentZipName.size())
{
CurrentZipName = ZipPath;
string zipName = m_BasePath + CurrentZipName;
CurrentZipFile.open(zipName.c_str(), ios::binary);
pCurrentFS = this;
}
if (!CurrentZipFile) {
CurrentZipName = "";
pCurrentFS = NULL;
return;
}
streamoff DataPos = SkipLFHdr(CurrentZipFile, streamoff(FileInfo.m_Offset));
if (DataPos != streamoff(-1)) {
string zipName = m_BasePath + CurrentZipName;
// Open the file at the right position
((izstream &) File).open(
zipName.c_str(),
streamoff(DataPos),
streamoff(FileInfo.m_CompSize),
FileInfo.m_CompMethod
);
if (File) {
File.m_FilePath = Filename;
File.m_FullFilePath = FullPath;
File.m_Zipped = true;
File.m_UncompSize = FileInfo.m_Size;
File.m_CompSize = FileInfo.m_CompSize;
File.m_Offset = FileInfo.m_Offset;
}
}
}
}
}
bool filesystem::DirExists(const std::string & folderName)
{
//Check in zip
file_info FileInfo;
// Check whether the file is zipped, whether the file is a directory and try to open.
if (FindFile(folderName.c_str(), &FileInfo) && (FileInfo.m_Directory))
return true;
//check real folder
string FullPath = m_BasePath + folderName;
#if defined (WIN32)
struct _stat statBuffer;
if ((_stat(FullPath.c_str(), &statBuffer) >= 0 && // make sure it exists
statBuffer.st_mode & S_IFDIR)) // and it's not a file
return true;
#else
struct stat st;
if (stat(FullPath.c_str(), &st) == 0)
return true;
#endif
//Neither in real folder nor in zip
return false;
}
bool filesystem::FileExists(const std::string & fileName)
{
//Check in zip
file_info FileInfo;
// Check whether the file is zipped, whether the file is a directory and try to open.
if (FindFile(fileName.c_str(), &FileInfo) && (!FileInfo.m_Directory))
return true;
//check real folder
string FullPath = m_BasePath + fileName;
#if defined (WIN32)
struct _stat statBuffer;
if (_stat(FullPath.c_str(), &statBuffer) >= 0) // make sure it exists
return true;
#else
struct stat st;
if (stat(FullPath.c_str(), &st) == 0)
return true;
#endif
//Neither in real folder nor in zip
return false;
}
// Note: this doesn't scan the folders outside of the zip...should we add that here ?
std::vector<std::string>& filesystem::scanfolder(const std::string& folderName, std::vector<std::string>& results)
{
filemap_const_iterator folderPos = m_Files.find(folderName);
if (folderPos == m_Files.end())
return results;
filemap_const_iterator It = folderPos;
string folderNameLC = folderName;
std::transform(folderNameLC.begin(), folderNameLC.end(), folderNameLC.begin(), ::tolower);
size_t length = folderNameLC.length();
while(++It != m_Files.end())
{
string currentFile = (* It).first;
string currentFileLC = currentFile;
std::transform(currentFileLC.begin(), currentFileLC.end(), currentFileLC.begin(), ::tolower);
if (currentFileLC.find(folderNameLC) == 0)
{
string relativePath = currentFile.substr(length);
size_t pos = relativePath.find_first_of("/\\");
//Only add direct children, no recursive browse
if (pos == string::npos || pos == (relativePath.length() - 1))
results.push_back(relativePath);
}
else
{
break;
//We know other files will not belong to that folder because of the order of the map
}
}
return results;
}
//////////////////////////////////////////////////////////////////////
// File System Protected Member Functions
//////////////////////////////////////////////////////////////////////
bool filesystem::FileNotZipped(const char * FilePath) const
{
//return io_facilities::search_iterator(FilePath);
// follow new search_iterator implementation
std::ifstream File(FilePath);
if (! File)
return false;
return true;
}
bool filesystem::FindFile(const char * Filename, file_info * FileInfo) const
{
filemap_const_iterator It = m_Files.find(Filename);
if (It == m_Files.end())
return false; // File not found
* FileInfo = (* It).second;
return true;
}
const string & filesystem::FindZip(size_t PackID) const
{
static const string EmptyString;
zipmap_const_iterator It = m_Zips.find(PackID);
if (It == m_Zips.end())
return EmptyString; // PackID not valid
return (* It).second.m_Filename;
}
void filesystem::InsertZip(const char * Filename, const size_t PackID)
{
zipfile_info ZipInfo;
// Get full path to the zip file and prepare ZipInfo
ZipInfo.m_Filename = Filename;
string ZipPath = m_BasePath + Filename;
// Open zip
LOG(("opening zip:" + ZipPath).c_str());
ifstream File(ZipPath.c_str(), ios::binary);
if (! File)
return;
// Find the start of the central directory
if (! File.seekg(CentralDir(File))) return;
LOG("open zip ok");
// Check every headers within the zip file
file_header FileHdr;
while ((NextHeader(File) == FILE) && (FileHdr.ReadHeader(File))) {
// Include files into Files map
const char * Name = &(* FileHdr.m_Filename.begin());
const unsigned short i = FileHdr.m_FilenameSize - 1;
if (FileHdr.m_FilenameSize != 0) {
m_Files[Name] = file_info(
PackID, // Package ID
FileHdr.m_RelOffset, // "Local File" header offset position
FileHdr.m_UncompSize, // File Size
FileHdr.m_CompSize, // Compressed File Size
FileHdr.m_CompMethod, // Compression Method;
((Name[i] == '/') || (Name[i] == '\\')) // Is a directory?
);
++(ZipInfo.m_NbEntries);
ZipInfo.m_FilesSize += FileHdr.m_UncompSize;
ZipInfo.m_FilesCompSize += FileHdr.m_CompSize;
}
}
File.close();
// Add zip file to Zips data base (only if not empty)
if (ZipInfo.m_NbEntries != 0)
m_Zips[PackID] = ZipInfo;
LOG("--zip file loading DONE");
}
bool filesystem::PreloadZip(const char * Filename, map<string, limited_file_info>& target)
{
zipfile_info ZipInfo;
// Open zip
izfstream File;
File.open(Filename, this);
if (! File)
return false;
// Find the start of the central directory
if (File.Zipped())
{
streamoff realBeginOfFile = SkipLFHdr(CurrentZipFile, File.getOffset());
if (! CurrentZipFile.seekg(CentralDirZipped(CurrentZipFile, realBeginOfFile, File.getCompSize())))
return false;
// Check every headers within the zip file
file_header FileHdr;
while ((NextHeader(CurrentZipFile) == FILE) && (FileHdr.ReadHeader(CurrentZipFile))) {
// Include files into Files map
const char * Name = &(* FileHdr.m_Filename.begin());
const unsigned short i = FileHdr.m_FilenameSize - 1;
if (FileHdr.m_FilenameSize != 0) {
// The zip in zip method only supports stored Zips because of JFileSystem limitations
if ((FileHdr.m_UncompSize != FileHdr.m_CompSize) || FileHdr.m_CompMethod != STORED)
continue;
target[Name] = limited_file_info(
realBeginOfFile + FileHdr.m_RelOffset, // "Local File" header offset position
FileHdr.m_UncompSize // File Size
);
}
}
File.close();
return (target.size() ? true : false);
}
else
{
if (! File.seekg(CentralDir(File)))
return false;
// Check every headers within the zip file
file_header FileHdr;
while ((NextHeader(File) == FILE) && (FileHdr.ReadHeader(File))) {
// Include files into Files map
const char * Name = &(* FileHdr.m_Filename.begin());
const unsigned short i = FileHdr.m_FilenameSize - 1;
if (FileHdr.m_FilenameSize != 0) {
// The zip in zip method only supports stored Zips because of JFileSystem limitations
if ((FileHdr.m_UncompSize != FileHdr.m_CompSize) || FileHdr.m_CompMethod != STORED)
continue;
target[Name] = limited_file_info(
FileHdr.m_RelOffset, // "Local File" header offset position
FileHdr.m_UncompSize // File Size
);
}
}
File.close();
return (target.size() ? true : false);
}
}
//////////////////////////////////////////////////////////////////////
// File System Friend Functions
//////////////////////////////////////////////////////////////////////
ostream & operator << (ostream & Out, const filesystem & FS)
{
size_t NbFiles = 0;
filesystem::zipfile_info AllZipsInfo;
for (filesystem::zipmap_const_iterator It = FS.m_Zips.begin(); It != FS.m_Zips.end(); ++It) {
const filesystem::zipfile_info & ZInfo = (* It).second;
// Print zip filename
Out << setiosflags(ios::left) << setw(32) << "-> \"" + ZInfo.m_Filename + "\"" << resetiosflags(ios::left);
// Print number of entries found in this zip file
Out << " " << setw(5) << ZInfo.m_NbEntries << " files";
// Print the uncompressed size of all included files
Out << " " << setw(7) << ZInfo.m_FilesSize / 1024 << " KB";
// Print the compressed size of all these files
Out << " " << setw(7) << ZInfo.m_FilesCompSize / 1024 << " KB packed" << endl;
++NbFiles;
AllZipsInfo.m_NbEntries += ZInfo.m_NbEntries;
AllZipsInfo.m_FilesSize += ZInfo.m_FilesSize;
AllZipsInfo.m_FilesCompSize += ZInfo.m_FilesCompSize;
}
// Print the general info
Out << "\nTotal: ";
Out << NbFiles << " packs ";
Out << AllZipsInfo.m_NbEntries << " files ";
Out << float(AllZipsInfo.m_FilesSize) / (1024 * 1024) << " MB ";
Out << float(AllZipsInfo.m_FilesCompSize) / (1024 * 1024) << " MB packed." << endl;
return Out;
}
//////////////////////////////////////////////////////////////////////
// "Less Than" Comparaison lt_path_str Member Functions
//////////////////////////////////////////////////////////////////////
bool filesystem::lt_path::operator () (const string & s1, const string & s2) const
{
const char * A = s1.c_str();
const char * B = s2.c_str();
for (size_t i = 0; ; ++i) {
if ((A[i] == '\0') && (B[i] == '\0'))
return false;
// case insensitive and '/' is the same as '\'
if (! (
(A[i] == B[i] + ('a' - 'A')) ||
(A[i] == B[i] - ('a' - 'A')) ||
(A[i] == B[i]) ||
((A[i] == '\\') && (B[i] == '/')) ||
((A[i] == '/') && (B[i] == '\\'))
)) {
if ((A[i] == '\0') || (A[i] < B[i]))
return true;
else
return false;
}
}
}
//////////////////////////////////////////////////////////////////////
// Zip Header Classes Related Member Functions
//////////////////////////////////////////////////////////////////////
streamoff filesystem::CentralDirZipped(std::istream & File, std::streamoff begin, std::size_t size) const
{
using io_facilities::readvar;
std::streamoff eof = begin + size;
// Look for the "end of central dir" header. Start minimum 22 bytes before end.
if (! File.seekg(eof - 22, ios::beg))
return -1;
streamoff EndPos;
streamoff StartPos = File.tellg();
if (StartPos == streamoff(0))
return -1;
if (StartPos <= begin + streamoff(65536))
EndPos = 1;
else
EndPos = StartPos - streamoff(65536);
// Start the scan
do {
unsigned int RawSignature;
if (! readvar(File, RawSignature, 4))
return -1;
eofcd_header Header;
streampos Pos = File.tellg();
// Found a potential "eofcd" header?
if ((RawSignature == ENDOFDIR) && (File.seekg(-4, ios::cur)) && (Header.ReadHeader(File))) {
// Check invariant values (1 disk only)
if ((Header.m_NbDisks == 0) && (0 == Header.m_DirDisk) && (Header.m_LocalEntries == Header.m_TotalEntries)) {
// Check comment ends at eof
if (! File.seekg(eof - 1 , ios::beg))
return -1;
if ((File.tellg() + streamoff(1)) == (Pos + streamoff(Header.m_CommentSize + 22 - 4))) {
// Check the start offset leads to a correct directory/file header;
if (! File.seekg(begin + Header.m_Offset)) return -1;
if (! readvar(File, RawSignature, 4)) return -1;
if (RawSignature == FILE)
return begin + Header.m_Offset;
}
}
}
File.seekg(Pos);
} while ((File.seekg(-5, ios::cur)) && (File.tellg() > EndPos) && (File.tellg() > begin));
return -1;
}
streamoff filesystem::CentralDir(istream & File) const
{
using io_facilities::readvar;
// Look for the "end of central dir" header. Start minimum 22 bytes before end.
if (! File.seekg(-22, ios::end)) return -1;
streamoff EndPos;
streamoff StartPos = File.tellg();
if (StartPos == streamoff(0)) return -1;
if (StartPos <= streamoff(65536))
EndPos = 1;
else
EndPos = StartPos - streamoff(65536);
// Start the scan
do {
unsigned int RawSignature;
if (! readvar(File, RawSignature, 4)) return -1;
eofcd_header Header;
streampos Pos = File.tellg();
// Found a potential "eofcd" header?
if ((RawSignature == ENDOFDIR) && (File.seekg(-4, ios::cur)) && (Header.ReadHeader(File))) {
// Check invariant values (1 disk only)
if ((Header.m_NbDisks == 0) && (0 == Header.m_DirDisk) && (Header.m_LocalEntries == Header.m_TotalEntries)) {
// Check comment ends at eof
if (! File.seekg(-1, ios::end)) return -1;
if ((File.tellg() + streamoff(1)) == (Pos + streamoff(Header.m_CommentSize + 22 - 4))) {
// Check the start offset leads to a correct directory/file header;
if (! File.seekg(Header.m_Offset)) return -1;
if (! readvar(File, RawSignature, 4)) return -1;
if (RawSignature == FILE)
return Header.m_Offset;
}
}
}
File.seekg(Pos);
} while ((File.seekg(-5, ios::cur)) && (File.tellg() > EndPos));
return -1;
}
streamoff filesystem::SkipLFHdr(istream & File, streamoff LFHdrPos)
{
using io_facilities::readvar;
unsigned short NameSize;
unsigned short FieldSize;
unsigned int RawSignature;
// verify it's a local header
if (! File.seekg(LFHdrPos)) return -1;
if (! readvar(File, RawSignature, 4)) return -1;
if (RawSignature != LOCALFILE) return -1;
// Skip and go directly to comment/field size
if (! File.seekg(22, ios::cur)) return -1;
if (! readvar(File, NameSize, 2)) return -1;
if (! readvar(File, FieldSize, 2)) return -1;
// Skip comment and extra field
if (! File.seekg(NameSize + FieldSize, ios::cur)) return -1;
// Now we are at the compressed data position
return (File.tellg());
}
headerid filesystem::NextHeader(istream & File) const
{
using io_facilities::readvar;
unsigned int RawSignature;
if (! readvar(File, RawSignature, 4))
return READERROR;
if (! File.seekg(-4, ios::cur))
return READERROR;
headerid Signature = headerid(RawSignature);
switch (Signature) {
case FILE:
case LOCALFILE:
case ENDOFDIR:
return Signature;
default:
return UNKNOWN;
}
}
} // namespace zip_file_system