- fixed -Werror=misleading-indentation warning - fixed -Werror=nonnull-compare warning on xll and QT builds - PNG_LIBPNG_VER backward compatibily changes - giflib backward compatibily changes - resolved some undefs for X11 build - silenced some small compile warnings in JGE Makefile - added -DTIXML_USE_STL to remaining build types (PSP had it added) - fixed x11 Boost incompatibility issue part of #548 - reworked PrecompiledHeader.h platforms used
646 lines
16 KiB
C++
646 lines
16 KiB
C++
/* JFileSystem centralizes all access to resources in JGE.
|
|
It allows to have files for the game split in two subfolders, a "system" subfolder (read only) and a "user" subfolder (read/write)
|
|
Additionally, these two subfolders can have some of their resources in zip file (see zfsystem.h).
|
|
Zip files can contain duplicates of the same file, the one that will eventually be used is the one is the latest zip file (by alphabetical order)
|
|
|
|
Read access priority:
|
|
User folder, real file
|
|
User folder, zip file
|
|
System folder, real file
|
|
System folder, zip file
|
|
|
|
Write access:
|
|
User folder, real file
|
|
|
|
User folder is the only one that is really needed to guarantee both read and write access, the system folder is not necessary but provides a nice way to distinguish
|
|
The content that users should not be touching.
|
|
*/
|
|
|
|
#if defined (ANDROID)
|
|
#include "PrecompiledHeader.h"
|
|
#endif //ANDROID
|
|
|
|
#if defined (LINUX)
|
|
#include "../../projects/mtg/include/PrecompiledHeader.h"
|
|
#endif //LINUX
|
|
|
|
|
|
#ifdef WIN32
|
|
#pragma warning(disable : 4786)
|
|
#include <direct.h>
|
|
#define MAKEDIR(name) _mkdir(name)
|
|
#else
|
|
#include <sys/stat.h>
|
|
#define MAKEDIR(name) mkdir(name, 0777)
|
|
#endif
|
|
|
|
#include "../include/JGE.h"
|
|
#include "../include/JFileSystem.h"
|
|
#include "../include/JLogger.h"
|
|
#include <dirent.h>
|
|
|
|
#ifdef QT_CONFIG
|
|
#include <QDir>
|
|
#endif
|
|
|
|
/*
|
|
#ifdef IOS
|
|
#include <Foundation/Foundation.h>
|
|
#endif
|
|
*/
|
|
|
|
JFileSystem* JFileSystem::mInstance = NULL;
|
|
|
|
JZipCache::JZipCache()
|
|
{}
|
|
|
|
JZipCache::~JZipCache()
|
|
{
|
|
dir.clear();
|
|
}
|
|
|
|
void JFileSystem::Pause()
|
|
{
|
|
filesystem::closeTempFiles();
|
|
}
|
|
|
|
void JFileSystem::preloadZip(const string& filename)
|
|
{
|
|
map<string,JZipCache *>::iterator it = mZipCache.find(filename);
|
|
if (it != mZipCache.end()) return;
|
|
|
|
//random number of files stored in the cache.
|
|
// This is based on the idea that an average filepath (in Wagic) for image is 37 characters (=bytes) + 8 bytes to store the fileinfo = 45 bytes,
|
|
//so 4500 files represent roughly 200kB, an "ok" size for the PSP
|
|
if (mZipCachedElementsCount > 4500)
|
|
{
|
|
clearZipCache();
|
|
}
|
|
|
|
JZipCache * cache = new JZipCache();
|
|
mZipCache[filename] = cache;
|
|
|
|
if (!mZipAvailable || !mZipFile) {
|
|
AttachZipFile(filename);
|
|
if (!mZipAvailable || !mZipFile) return;
|
|
}
|
|
|
|
if ((mUserFS->PreloadZip(filename.c_str(), cache->dir) || (mSystemFS && mSystemFS->PreloadZip(filename.c_str(), cache->dir))))
|
|
{
|
|
mZipCachedElementsCount+= cache->dir.size();
|
|
}
|
|
else
|
|
{
|
|
DetachZipFile();
|
|
}
|
|
}
|
|
|
|
|
|
void JFileSystem::init(const string & userPath, const string & systemPath)
|
|
{
|
|
Destroy();
|
|
mInstance = new JFileSystem(userPath, systemPath);
|
|
}
|
|
|
|
JFileSystem* JFileSystem::GetInstance()
|
|
{
|
|
if (!mInstance)
|
|
{
|
|
#ifdef RESPATH
|
|
init( RESPATH"/");
|
|
#else
|
|
init("Res/");
|
|
#endif
|
|
}
|
|
return mInstance;
|
|
}
|
|
|
|
// Tries to set the system and user paths.
|
|
// On some OSes, the parameters get overriden by hardcoded values
|
|
JFileSystem::JFileSystem(const string & _userPath, const string & _systemPath)
|
|
|
|
{
|
|
string systemPath = _systemPath;
|
|
string userPath = _userPath;
|
|
|
|
#ifdef IOS
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES);
|
|
NSString *documentsDirectory = [paths objectAtIndex:0];
|
|
|
|
userPath = [[documentsDirectory stringByAppendingString: @"/User/"] cStringUsingEncoding:1];
|
|
systemPath = [[documentsDirectory stringByAppendingString: @"/Res/"] cStringUsingEncoding:1];
|
|
|
|
#elif defined (ANDROID)
|
|
userPath = JGE::GetInstance()->getFileSystemLocation();
|
|
systemPath = "";
|
|
|
|
DebugTrace("User path " << userPath);
|
|
#elif defined (QT_CONFIG)
|
|
|
|
QDir sysDir(RESDIR);
|
|
QDir dir(QDir::homePath());
|
|
dir.mkdir(USERDIR);
|
|
dir.cd(USERDIR);
|
|
|
|
userPath = QDir::toNativeSeparators(dir.absolutePath()).toStdString();
|
|
systemPath = QDir::toNativeSeparators(sysDir.absolutePath()).toStdString();
|
|
|
|
DebugTrace("User path " << userPath);
|
|
DebugTrace("System path " << systemPath);
|
|
DebugTrace("Current path " << QDir::currentPath().toStdString());
|
|
#else
|
|
//Find the Res.txt file and matching Res folders for backwards compatibility
|
|
ifstream mfile("Res.txt");
|
|
string resPath;
|
|
if (mfile)
|
|
{
|
|
bool found = false;
|
|
while (!found && std::getline(mfile, resPath))
|
|
{
|
|
if (!resPath.size())
|
|
continue;
|
|
|
|
if (resPath[resPath.size() - 1] == '\r')
|
|
resPath.erase(resPath.size() - 1); //Handle DOS files
|
|
string testfile = resPath;
|
|
testfile.append("graphics/simon.dat");
|
|
ifstream file(testfile.c_str());
|
|
if (file)
|
|
{
|
|
userPath = resPath;
|
|
systemPath = "";
|
|
found = true;
|
|
file.close();
|
|
}
|
|
}
|
|
mfile.close();
|
|
}
|
|
#endif
|
|
|
|
// Make sure the base paths finish with a '/' or a '\'
|
|
if (! userPath.empty()) {
|
|
string::iterator c = userPath.end();//userPath.at(userPath.size()-1);
|
|
c--;
|
|
if ((*c != '/') && (*c != '\\'))
|
|
userPath += '/';
|
|
}
|
|
|
|
if (! systemPath.empty()) {
|
|
string::iterator c = systemPath.end();//systemPath.at(systemPath.size()-1);
|
|
c--;
|
|
if ((*c != '/') && (*c != '\\'))
|
|
systemPath += '/';
|
|
}
|
|
|
|
mUserFSPath = userPath;
|
|
MAKEDIR(userPath.c_str());
|
|
|
|
mSystemFSPath = systemPath;
|
|
|
|
mUserFS = new filesystem(userPath.c_str());
|
|
mSystemFS = (mSystemFSPath.size() && (mSystemFSPath.compare(mUserFSPath) != 0)) ? new filesystem(systemPath.c_str()) : NULL;
|
|
|
|
mZipAvailable = false;
|
|
mZipCachedElementsCount = 0;
|
|
mPassword = NULL;
|
|
mFileSize = 0;
|
|
mCurrentFileInZip = NULL;
|
|
|
|
};
|
|
|
|
void JFileSystem::Destroy()
|
|
{
|
|
if (mInstance)
|
|
{
|
|
delete mInstance;
|
|
mInstance = NULL;
|
|
}
|
|
}
|
|
|
|
bool JFileSystem::DirExists(const string& strDirname)
|
|
{
|
|
return (mSystemFS && mSystemFS->DirExists(strDirname)) || mUserFS->DirExists(strDirname);
|
|
}
|
|
|
|
bool JFileSystem::FileExists(const string& strFilename)
|
|
{
|
|
if (strFilename.length() < 1 ) return false;
|
|
|
|
return (mSystemFS && mSystemFS->FileExists(strFilename)) || mUserFS->FileExists(strFilename);
|
|
}
|
|
|
|
bool JFileSystem::MakeDir(const string & dir)
|
|
{
|
|
string fullDir = mUserFSPath + dir;
|
|
MAKEDIR(fullDir.c_str());
|
|
return true;
|
|
}
|
|
|
|
JFileSystem::~JFileSystem()
|
|
{
|
|
clearZipCache();
|
|
filesystem::closeTempFiles();
|
|
SAFE_DELETE(mUserFS);
|
|
SAFE_DELETE(mSystemFS);
|
|
}
|
|
|
|
|
|
void JFileSystem::clearZipCache()
|
|
{
|
|
DetachZipFile();
|
|
|
|
map<string,JZipCache *>::iterator it;
|
|
for (it = mZipCache.begin(); it != mZipCache.end(); ++it){
|
|
delete(it->second);
|
|
}
|
|
mZipCache.clear();
|
|
mZipCachedElementsCount = 0;
|
|
}
|
|
|
|
bool JFileSystem::AttachZipFile(const string &zipfile, char *password /* = NULL */)
|
|
{
|
|
if (mZipAvailable && mZipFile.is_open())
|
|
{
|
|
if (mZipFileName != zipfile)
|
|
DetachZipFile(); // close the previous zip file
|
|
else
|
|
return true;
|
|
}
|
|
|
|
mZipFileName = zipfile;
|
|
mPassword = password;
|
|
|
|
openForRead(mZipFile, mZipFileName);
|
|
|
|
if (!mZipFile)
|
|
return false;
|
|
|
|
|
|
//A hack for a zip inside a zip: instead we open the zip containing it
|
|
if (mZipFile.Zipped())
|
|
{
|
|
mZipFile.close();
|
|
assert(filesystem::getCurrentFS());
|
|
mZipFile.open(filesystem::getCurrentZipName().c_str(), filesystem::getCurrentFS());
|
|
assert(mZipFile);
|
|
}
|
|
mZipAvailable = true;
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
void JFileSystem::DetachZipFile()
|
|
{
|
|
if (mZipFile)
|
|
{
|
|
mZipFile.close();
|
|
}
|
|
mCurrentFileInZip = NULL;
|
|
mZipAvailable = false;
|
|
}
|
|
|
|
bool JFileSystem::openForRead(izfstream & File, const string & FilePath) {
|
|
|
|
File.open(FilePath.c_str(), mUserFS);
|
|
if (File)
|
|
return true;
|
|
|
|
if(!mSystemFS)
|
|
return false;
|
|
|
|
File.open(FilePath.c_str(), mSystemFS);
|
|
if (File)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool JFileSystem::readIntoString(const string & FilePath, string & target)
|
|
{
|
|
izfstream file;
|
|
if (!openForRead(file, FilePath))
|
|
return false;
|
|
|
|
int fileSize = GetFileSize(file);
|
|
|
|
#ifndef __MINGW32__
|
|
try {
|
|
#endif
|
|
target.resize((std::string::size_type) fileSize);
|
|
#ifndef __MINGW32__
|
|
} catch (bad_alloc&) {
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
|
|
if (fileSize)
|
|
file.read(&target[0], fileSize);
|
|
|
|
file.close();
|
|
return true;
|
|
}
|
|
|
|
bool JFileSystem::openForWrite(ofstream & File, const string & FilePath, ios_base::openmode mode)
|
|
{
|
|
string filename = mUserFSPath;
|
|
filename.append(FilePath);
|
|
|
|
#if defined(ANDROID)
|
|
DebugTrace("ANDROID");
|
|
std::vector<string> dirs;
|
|
string path = filename.substr( 0, filename.find_last_of( '/' ) + 1 );
|
|
|
|
// put it into list
|
|
dirs.push_back(path);
|
|
|
|
//make list of directories that need to be created
|
|
do
|
|
{
|
|
path = path.substr( 0, path.find_last_of( '/', path.size() - 2 ) + 1 );
|
|
dirs.push_back(path);
|
|
|
|
} while (path.compare(mUserFSPath) != 0);
|
|
|
|
// remove mUserFSPath from list
|
|
dirs.pop_back();
|
|
|
|
// create missing directories
|
|
for (std::vector<string>::reverse_iterator it = dirs.rbegin(); it != dirs.rend(); ++it)
|
|
{
|
|
if(!DirExists(*it))
|
|
{
|
|
MAKEDIR((*it).c_str());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
File.open(filename.c_str(), mode);
|
|
|
|
if (File)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JFileSystem::OpenFile(const string &filename)
|
|
{
|
|
mCurrentFileInZip = NULL;
|
|
|
|
if (!mZipAvailable || !mZipFile)
|
|
return openForRead(mFile, filename);
|
|
|
|
preloadZip(mZipFileName);
|
|
map<string,JZipCache *>::iterator it = mZipCache.find(mZipFileName);
|
|
if (it == mZipCache.end())
|
|
{
|
|
//DetachZipFile();
|
|
//return OpenFile(filename);
|
|
return openForRead(mFile, filename);
|
|
}
|
|
JZipCache * zc = it->second;
|
|
map<string, filesystem::limited_file_info>::iterator it2 = zc->dir.find(filename);
|
|
if (it2 == zc->dir.end())
|
|
{
|
|
/*DetachZipFile();
|
|
return OpenFile(filename); */
|
|
return openForRead(mFile, filename);
|
|
}
|
|
|
|
mCurrentFileInZip = &(it2->second);
|
|
mFileSize = it2->second.m_Size;
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
void JFileSystem::CloseFile()
|
|
{
|
|
if (mZipAvailable && mZipFile)
|
|
{
|
|
mCurrentFileInZip = NULL;
|
|
}
|
|
|
|
if (mFile)
|
|
mFile.close();
|
|
}
|
|
|
|
//returns 0 if less than "size" bits were read
|
|
int JFileSystem::ReadFile(void *buffer, int size)
|
|
{
|
|
if (mCurrentFileInZip)
|
|
{
|
|
assert(mZipFile);
|
|
if((size_t)size > mCurrentFileInZip->m_Size) //only support "store" method for zip inside zips
|
|
return 0;
|
|
std::streamoff offset = filesystem::SkipLFHdr(mZipFile, mCurrentFileInZip->m_Offset);
|
|
if (!mZipFile.seekg(offset))
|
|
return 0;
|
|
mZipFile.read((char *) buffer, size);
|
|
//TODO what if can't read
|
|
return size;
|
|
}
|
|
|
|
if (!mFile)
|
|
return 0;
|
|
|
|
assert(!mFile.Zipped() || (size_t)size <= mFile.getUncompSize());
|
|
mFile.read((char *)buffer, size);
|
|
if (mFile.eof())
|
|
return 0;
|
|
return size;
|
|
}
|
|
|
|
std::vector<std::string>& JFileSystem::scanRealFolder(const std::string& folderName, std::vector<std::string>& results)
|
|
{
|
|
DIR *dip = opendir(folderName.c_str());
|
|
if (!dip)
|
|
return results;
|
|
|
|
while (struct dirent * dit = readdir(dip))
|
|
{
|
|
results.push_back(dit->d_name);
|
|
}
|
|
|
|
closedir(dip);
|
|
|
|
return results;
|
|
}
|
|
|
|
std::vector<std::string>& JFileSystem::scanfolder(const std::string& _folderName, std::vector<std::string>& results)
|
|
{
|
|
if (!_folderName.size())
|
|
return results;
|
|
|
|
map<string, bool> seen;
|
|
|
|
|
|
string folderName = _folderName;
|
|
if (folderName[folderName.length() - 1] != '/')
|
|
folderName.append("/");
|
|
|
|
//we scan zips first, then normal folders.
|
|
// This is to avoid duplicate folders coming from the real folder
|
|
// (a folder "foo" in the zip comes out as "foo/", while on the real FS it comes out as "foo")
|
|
|
|
//user zips
|
|
{
|
|
//Scan the zip filesystem
|
|
std::vector<std::string> userZips;
|
|
mUserFS->scanfolder(folderName, userZips);
|
|
|
|
for (size_t i = 0; i < userZips.size(); ++i)
|
|
seen[userZips[i]] = true;
|
|
}
|
|
|
|
//system zips
|
|
if (mSystemFS)
|
|
{
|
|
//Scan the zip filesystem
|
|
std::vector<std::string> systemZips;
|
|
mSystemFS->scanfolder(folderName, systemZips);
|
|
|
|
for (size_t i = 0; i < systemZips.size(); ++i)
|
|
seen[systemZips[i]] = true;
|
|
|
|
}
|
|
|
|
//user real files
|
|
{
|
|
//scan the real files
|
|
std::vector<std::string> userReal;
|
|
string realFolderName = mUserFSPath;
|
|
realFolderName.append(folderName);
|
|
scanRealFolder(realFolderName, userReal);
|
|
|
|
for (size_t i = 0; i < userReal.size(); ++i)
|
|
{
|
|
string asFolder = userReal[i] + "/";
|
|
if (seen.find(asFolder) == seen.end())
|
|
seen[userReal[i]] = true;
|
|
}
|
|
}
|
|
|
|
//system real files
|
|
if (mSystemFS)
|
|
{
|
|
//scan the real files
|
|
std::vector<std::string> systemReal;
|
|
string realFolderName = mSystemFSPath;
|
|
realFolderName.append(folderName);
|
|
scanRealFolder(realFolderName, systemReal);
|
|
|
|
|
|
for (size_t i = 0; i < systemReal.size(); ++i)
|
|
{
|
|
string asFolder = systemReal[i] + "/";
|
|
if (seen.find(asFolder) == seen.end())
|
|
seen[systemReal[i]] = true;
|
|
}
|
|
}
|
|
|
|
for(map<string,bool>::iterator it = seen.begin(); it != seen.end(); ++it)
|
|
{
|
|
results.push_back(it->first);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
std::vector<std::string> JFileSystem::scanfolder(const std::string& folderName)
|
|
{
|
|
std::vector<std::string> result;
|
|
return scanfolder(folderName, result);
|
|
}
|
|
|
|
int JFileSystem::GetFileSize()
|
|
{
|
|
if (mCurrentFileInZip)
|
|
return mFileSize;
|
|
|
|
return GetFileSize(mFile);
|
|
}
|
|
|
|
bool JFileSystem::Rename(string _from, string _to)
|
|
{
|
|
string from = mUserFSPath + _from;
|
|
string to = mUserFSPath + _to;
|
|
std::remove(to.c_str());
|
|
return (rename(from.c_str(), to.c_str()) == 0);
|
|
}
|
|
|
|
bool JFileSystem::Remove(string aFile)
|
|
{
|
|
string toRemove = mUserFSPath + aFile;
|
|
return (std::remove(toRemove.c_str()) == 0);
|
|
}
|
|
|
|
int JFileSystem::GetFileSize(izfstream & file)
|
|
{
|
|
if (!file)
|
|
return 0;
|
|
|
|
if (file.Zipped())
|
|
{
|
|
//There's a bug in zipped version that prevents from sending a correct filesize with the "standard" seek method
|
|
//The hack below only works for the "stored" version I think...
|
|
return file.getUncompSize();
|
|
}
|
|
|
|
file.seekg (0, ios::end);
|
|
int length = (int) file.tellg();
|
|
file.seekg (0, ios::beg);
|
|
return length;
|
|
|
|
}
|
|
|
|
// AVOID Using This function!!!
|
|
/*
|
|
This function is deprecated, but some code is still using it
|
|
It used to give a pathname to a file in the file system.
|
|
Now with the support of zip resources, a pathname does not make sense anymore
|
|
However some of our code still relies on "physical" files not being in zip.
|
|
So this call is now super heavy: it checks where the file is, and if it's in a zip, it extracts
|
|
it to the user Filesystem, assuming that whoever called this needs to access the file through its pathname later on.
|
|
|
|
As a result, this function isvery inefficient and shouldn't be used in the general case.
|
|
*/
|
|
string JFileSystem::GetResourceFile(string filename)
|
|
{
|
|
izfstream temp;
|
|
bool result = openForRead(temp, filename);
|
|
|
|
if (!temp || !result)
|
|
return "";
|
|
|
|
if (!temp.Zipped())
|
|
{
|
|
string result = temp.FullFilePath();
|
|
temp.close();
|
|
return result;
|
|
}
|
|
|
|
// File is inside a zip archive,
|
|
//we copy it to the user FS
|
|
string destFile = mUserFSPath + filename;
|
|
ofstream dest;
|
|
if (openForWrite(dest, filename, ios_base::binary))
|
|
{
|
|
// allocate memory:
|
|
size_t length = temp.getUncompSize();
|
|
char * buffer = new char [length];
|
|
|
|
// read data as a block:
|
|
temp.read(buffer, length);
|
|
temp.close();
|
|
|
|
dest.write (buffer,length);
|
|
delete[] buffer;
|
|
dest.close();
|
|
return destFile;
|
|
}
|
|
return "";
|
|
}
|