1
// Copyright (C) 2002-2011 Nikolaus Gebhardt
2
// This file is part of the "Irrlicht Engine".
3
// For conditions of distribution and use, see copyright notice in irrlicht.h
5
#include "IrrCompileConfig.h"
7
#include "CFileSystem.h"
9
#include "IWriteFile.h"
10
#include "CZipReader.h"
11
#include "CMountPointReader.h"
12
#include "CPakReader.h"
13
#include "CNPKReader.h"
14
#include "CTarReader.h"
15
#include "CWADReader.h"
16
#include "CFileList.h"
17
#include "CXMLReader.h"
18
#include "CXMLWriter.h"
21
#include "CAttributes.h"
22
#include "CMemoryFile.h"
23
#include "CLimitReadFile.h"
26
#if defined (_IRR_WINDOWS_API_)
27
#if !defined ( _WIN32_WCE )
28
#include <direct.h> // for _chdir
29
#include <io.h> // for _access
33
#if (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_))
38
#include <sys/types.h>
51
CFileSystem::CFileSystem()
54
setDebugName("CFileSystem");
57
setFileListSystem(FILESYSTEM_NATIVE);
58
//! reset current working directory
59
getWorkingDirectory();
61
#ifdef __IRR_COMPILE_WITH_PAK_ARCHIVE_LOADER_
62
ArchiveLoader.push_back(new CArchiveLoaderPAK(this));
65
#ifdef __IRR_COMPILE_WITH_NPK_ARCHIVE_LOADER_
66
ArchiveLoader.push_back(new CArchiveLoaderNPK(this));
69
#ifdef __IRR_COMPILE_WITH_TAR_ARCHIVE_LOADER_
70
ArchiveLoader.push_back(new CArchiveLoaderTAR(this));
73
#ifdef __IRR_COMPILE_WITH_WAD_ARCHIVE_LOADER_
74
ArchiveLoader.push_back(new CArchiveLoaderWAD(this));
77
#ifdef __IRR_COMPILE_WITH_MOUNT_ARCHIVE_LOADER_
78
ArchiveLoader.push_back(new CArchiveLoaderMount(this));
81
#ifdef __IRR_COMPILE_WITH_ZIP_ARCHIVE_LOADER_
82
ArchiveLoader.push_back(new CArchiveLoaderZIP(this));
89
CFileSystem::~CFileSystem()
93
for ( i=0; i < FileArchives.size(); ++i)
95
FileArchives[i]->drop();
98
for ( i=0; i < ArchiveLoader.size(); ++i)
100
ArchiveLoader[i]->drop();
105
//! opens a file for read access
106
IReadFile* CFileSystem::createAndOpenFile(const io::path& filename)
111
for (i=0; i< FileArchives.size(); ++i)
113
file = FileArchives[i]->createAndOpenFile(filename);
118
// Create the file using an absolute path so that it matches
119
// the scheme used by CNullDriver::getTexture().
120
return createReadFile(getAbsolutePath(filename));
124
//! Creates an IReadFile interface for treating memory like a file.
125
IReadFile* CFileSystem::createMemoryReadFile(void* memory, s32 len,
126
const io::path& fileName, bool deleteMemoryWhenDropped)
131
return new CMemoryFile(memory, len, fileName, deleteMemoryWhenDropped);
135
//! Creates an IReadFile interface for reading files inside files
136
IReadFile* CFileSystem::createLimitReadFile(const io::path& fileName,
137
IReadFile* alreadyOpenedFile, long pos, long areaSize)
139
if (!alreadyOpenedFile)
142
return new CLimitReadFile(alreadyOpenedFile, pos, areaSize, fileName);
146
//! Creates an IReadFile interface for treating memory like a file.
147
IWriteFile* CFileSystem::createMemoryWriteFile(void* memory, s32 len,
148
const io::path& fileName, bool deleteMemoryWhenDropped)
153
return new CMemoryFile(memory, len, fileName, deleteMemoryWhenDropped);
157
//! Opens a file for write access.
158
IWriteFile* CFileSystem::createAndWriteFile(const io::path& filename, bool append)
160
return createWriteFile(filename, append);
164
//! Adds an external archive loader to the engine.
165
void CFileSystem::addArchiveLoader(IArchiveLoader* loader)
171
ArchiveLoader.push_back(loader);
174
//! Returns the total number of archive loaders added.
175
u32 CFileSystem::getArchiveLoaderCount() const
177
return ArchiveLoader.size();
180
//! Gets the archive loader by index.
181
IArchiveLoader* CFileSystem::getArchiveLoader(u32 index) const
183
if (index < ArchiveLoader.size())
184
return ArchiveLoader[index];
189
//! move the hirarchy of the filesystem. moves sourceIndex relative up or down
190
bool CFileSystem::moveFileArchive(u32 sourceIndex, s32 relative)
193
const s32 dest = (s32) sourceIndex + relative;
194
const s32 dir = relative < 0 ? -1 : 1;
195
const s32 sourceEnd = ((s32) FileArchives.size() ) - 1;
198
for (s32 s = (s32) sourceIndex;s != dest; s += dir)
200
if (s < 0 || s > sourceEnd || s + dir < 0 || s + dir > sourceEnd)
203
t = FileArchives[s + dir];
204
FileArchives[s + dir] = FileArchives[s];
212
//! Adds an archive to the file system.
213
bool CFileSystem::addFileArchive(const io::path& filename, bool ignoreCase,
214
bool ignorePaths, E_FILE_ARCHIVE_TYPE archiveType,
215
const core::stringc& password)
217
IFileArchive* archive = 0;
220
// see if archive is already added
221
if (changeArchivePassword(filename, password))
226
// do we know what type it should be?
227
if (archiveType == EFAT_UNKNOWN || archiveType == EFAT_FOLDER)
229
// try to load archive based on file name
230
for (i = ArchiveLoader.size()-1; i >=0 ; --i)
232
if (ArchiveLoader[i]->isALoadableFileFormat(filename))
234
archive = ArchiveLoader[i]->createArchive(filename, ignoreCase, ignorePaths);
240
// try to load archive based on content
243
io::IReadFile* file = createAndOpenFile(filename);
246
for (i = ArchiveLoader.size()-1; i >= 0; --i)
249
if (ArchiveLoader[i]->isALoadableFileFormat(file))
252
archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths);
263
// try to open archive based on archive loader type
265
io::IReadFile* file = 0;
267
for (i = ArchiveLoader.size()-1; i >= 0; --i)
269
if (ArchiveLoader[i]->isALoadableFileFormat(archiveType))
271
// attempt to open file
273
file = createAndOpenFile(filename);
278
// attempt to open archive
280
if (ArchiveLoader[i]->isALoadableFileFormat(file))
283
archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths);
290
// couldn't open file
296
// if open, close the file
303
FileArchives.push_back(archive);
305
archive->Password=password;
310
os::Printer::log("Could not create archive for", filename, ELL_ERROR);
313
_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
318
bool CFileSystem::changeArchivePassword(const path& filename, const core::stringc& password)
320
for (s32 idx = 0; idx < (s32)FileArchives.size(); ++idx)
322
// TODO: This should go into a path normalization method
323
// We need to check for directory names with trailing slash and without
324
const path absPath = getAbsolutePath(filename);
325
const path arcPath = FileArchives[idx]->getFileList()->getPath();
326
if ((absPath == arcPath) || ((absPath+_IRR_TEXT("/")) == arcPath))
329
FileArchives[idx]->Password=password;
337
bool CFileSystem::addFileArchive(IReadFile* file, bool ignoreCase, bool ignorePaths,
338
E_FILE_ARCHIVE_TYPE archiveType, const core::stringc& password)
340
if (!file || archiveType == EFAT_FOLDER)
345
if (changeArchivePassword(file->getFileName(), password))
348
IFileArchive* archive = 0;
351
if (archiveType == EFAT_UNKNOWN)
353
// try to load archive based on file name
354
for (i = ArchiveLoader.size()-1; i >=0 ; --i)
356
if (ArchiveLoader[i]->isALoadableFileFormat(file->getFileName()))
358
archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths);
364
// try to load archive based on content
367
for (i = ArchiveLoader.size()-1; i >= 0; --i)
370
if (ArchiveLoader[i]->isALoadableFileFormat(file))
373
archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths);
382
// try to open archive based on archive loader type
383
for (i = ArchiveLoader.size()-1; i >= 0; --i)
385
if (ArchiveLoader[i]->isALoadableFileFormat(archiveType))
387
// attempt to open archive
389
if (ArchiveLoader[i]->isALoadableFileFormat(file))
392
archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths);
402
FileArchives.push_back(archive);
404
archive->Password=password;
409
os::Printer::log("Could not create archive for", file->getFileName(), ELL_ERROR);
417
//! removes an archive from the file system.
418
bool CFileSystem::removeFileArchive(u32 index)
421
if (index < FileArchives.size())
423
FileArchives[index]->drop();
424
FileArchives.erase(index);
427
_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
432
//! removes an archive from the file system.
433
bool CFileSystem::removeFileArchive(const io::path& filename)
435
for (u32 i=0; i < FileArchives.size(); ++i)
437
if (filename == FileArchives[i]->getFileList()->getPath())
438
return removeFileArchive(i);
440
_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
446
u32 CFileSystem::getFileArchiveCount() const
448
return FileArchives.size();
452
IFileArchive* CFileSystem::getFileArchive(u32 index)
454
return index < getFileArchiveCount() ? FileArchives[index] : 0;
458
//! Returns the string of the current working directory
459
const io::path& CFileSystem::getWorkingDirectory()
461
EFileSystemType type = FileSystemType;
463
if (type != FILESYSTEM_NATIVE)
465
type = FILESYSTEM_VIRTUAL;
469
#if defined(_IRR_WINDOWS_CE_PLATFORM_)
470
// does not need this
471
#elif defined(_IRR_WINDOWS_API_)
472
fschar_t tmp[_MAX_PATH];
473
#if defined(_IRR_WCHAR_FILESYSTEM )
474
_wgetcwd(tmp, _MAX_PATH);
475
WorkingDirectory[FILESYSTEM_NATIVE] = tmp;
476
WorkingDirectory[FILESYSTEM_NATIVE].replace(L'\\', L'/');
478
_getcwd(tmp, _MAX_PATH);
479
WorkingDirectory[FILESYSTEM_NATIVE] = tmp;
480
WorkingDirectory[FILESYSTEM_NATIVE].replace('\\', '/');
484
#if (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_))
486
// getting the CWD is rather complex as we do not know the size
487
// so try it until the call was successful
488
// Note that neither the first nor the second parameter may be 0 according to POSIX
490
#if defined(_IRR_WCHAR_FILESYSTEM )
492
wchar_t *tmpPath = new wchar_t[pathSize];
493
while ((pathSize < (1<<16)) && !(wgetcwd(tmpPath,pathSize)))
497
tmpPath = new char[pathSize];
501
WorkingDirectory[FILESYSTEM_NATIVE] = tmpPath;
506
char *tmpPath = new char[pathSize];
507
while ((pathSize < (1<<16)) && !(getcwd(tmpPath,pathSize)))
511
tmpPath = new char[pathSize];
515
WorkingDirectory[FILESYSTEM_NATIVE] = tmpPath;
521
WorkingDirectory[type].validate();
524
return WorkingDirectory[type];
528
//! Changes the current Working Directory to the given string.
529
bool CFileSystem::changeWorkingDirectoryTo(const io::path& newDirectory)
533
if (FileSystemType != FILESYSTEM_NATIVE)
535
WorkingDirectory[FILESYSTEM_VIRTUAL] = newDirectory;
536
// is this empty string constant really intended?
537
flattenFilename(WorkingDirectory[FILESYSTEM_VIRTUAL], _IRR_TEXT(""));
542
WorkingDirectory[FILESYSTEM_NATIVE] = newDirectory;
544
#if defined(_IRR_WINDOWS_CE_PLATFORM_)
546
#elif defined(_MSC_VER)
547
#if defined(_IRR_WCHAR_FILESYSTEM)
548
success=(_wchdir(newDirectory.c_str()) == 0);
550
success=(_chdir(newDirectory.c_str()) == 0);
553
success=(chdir(newDirectory.c_str()) == 0);
561
io::path CFileSystem::getAbsolutePath(const io::path& filename) const
563
#if defined(_IRR_WINDOWS_CE_PLATFORM_)
565
#elif defined(_IRR_WINDOWS_API_)
567
fschar_t fpath[_MAX_PATH];
568
#if defined(_IRR_WCHAR_FILESYSTEM )
569
p = _wfullpath(fpath, filename.c_str(), _MAX_PATH);
570
core::stringw tmp(p);
571
tmp.replace(L'\\', L'/');
573
p = _fullpath(fpath, filename.c_str(), _MAX_PATH);
574
core::stringc tmp(p);
575
tmp.replace('\\', '/');
578
#elif (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_))
582
p = realpath(filename.c_str(), fpath);
585
// content in fpath is unclear at this point
586
if (!fpath[0]) // seems like fpath wasn't altered, use our best guess
588
io::path tmp(filename);
589
return flattenFilename(tmp);
592
return io::path(fpath);
594
if (filename[filename.size()-1]=='/')
595
return io::path(p)+_IRR_TEXT("/");
599
return io::path(filename);
604
//! returns the directory part of a filename, i.e. all until the first
605
//! slash or backslash, excluding it. If no directory path is prefixed, a '.'
607
io::path CFileSystem::getFileDir(const io::path& filename) const
609
// find last forward or backslash
610
s32 lastSlash = filename.findLast('/');
611
const s32 lastBackSlash = filename.findLast('\\');
612
lastSlash = lastSlash > lastBackSlash ? lastSlash : lastBackSlash;
614
if ((u32)lastSlash < filename.size())
615
return filename.subString(0, lastSlash);
617
return _IRR_TEXT(".");
621
//! returns the base part of a filename, i.e. all except for the directory
622
//! part. If no directory path is prefixed, the full name is returned.
623
io::path CFileSystem::getFileBasename(const io::path& filename, bool keepExtension) const
625
// find last forward or backslash
626
s32 lastSlash = filename.findLast('/');
627
const s32 lastBackSlash = filename.findLast('\\');
628
lastSlash = core::max_(lastSlash, lastBackSlash);
630
// get number of chars after last dot
634
// take care to search only after last slash to check only for
635
// dots in the filename
636
end = filename.findLast('.');
637
if (end == -1 || end < lastSlash)
640
end = filename.size()-end;
643
if ((u32)lastSlash < filename.size())
644
return filename.subString(lastSlash+1, filename.size()-lastSlash-1-end);
646
return filename.subString(0, filename.size()-end);
652
//! flatten a path and file name for example: "/you/me/../." becomes "/you"
653
io::path& CFileSystem::flattenFilename(io::path& directory, const io::path& root) const
655
directory.replace('\\', '/');
656
if (directory.lastChar() != '/')
657
directory.append('/');
664
bool lastWasRealDir=false;
666
while ((pos = directory.findNext('/', lastpos)) >= 0)
668
subdir = directory.subString(lastpos, pos - lastpos + 1);
670
if (subdir == _IRR_TEXT("../"))
674
deletePathFromPath(dir, 2);
675
lastWasRealDir=(dir.size()!=0);
680
lastWasRealDir=false;
683
else if (subdir == _IRR_TEXT("/"))
687
else if (subdir != _IRR_TEXT("./"))
700
//! Get the relative filename, relative to the given directory
701
path CFileSystem::getRelativeFilename(const path& filename, const path& directory) const
703
if ( filename.empty() || directory.empty() )
706
io::path path1, file, ext;
707
core::splitFilename(getAbsolutePath(filename), &path1, &file, &ext);
708
io::path path2(getAbsolutePath(directory));
709
core::list<io::path> list1, list2;
710
path1.split(list1, _IRR_TEXT("/\\"), 2);
711
path2.split(list2, _IRR_TEXT("/\\"), 2);
713
core::list<io::path>::ConstIterator it1,it2;
717
#if defined (_IRR_WINDOWS_API_)
718
fschar_t partition1 = 0, partition2 = 0;
719
io::path prefix1, prefix2;
720
if ( it1 != list1.end() )
722
if ( it2 != list2.end() )
724
if ( prefix1.size() > 1 && prefix1[1] == _IRR_TEXT(':') )
725
partition1 = core::locale_lower(prefix1[0]);
726
if ( prefix2.size() > 1 && prefix2[1] == _IRR_TEXT(':') )
727
partition2 = core::locale_lower(prefix2[0]);
729
// must have the same prefix or we can't resolve it to a relative filename
730
if ( partition1 != partition2 )
737
for (; i<list1.size() && i<list2.size()
738
#if defined (_IRR_WINDOWS_API_)
739
&& (io::path(*it1).make_lower()==io::path(*it2).make_lower())
749
for (; i<list2.size(); ++i)
750
path1 += _IRR_TEXT("../");
751
while (it1 != list1.end())
754
path1 += _IRR_TEXT('/');
759
path1 += _IRR_TEXT('.');
766
//! Sets the current file systen type
767
EFileSystemType CFileSystem::setFileListSystem(EFileSystemType listType)
769
EFileSystemType current = FileSystemType;
770
FileSystemType = listType;
775
//! Creates a list of files and directories in the current working directory
776
IFileList* CFileSystem::createFileList()
779
io::path Path = getWorkingDirectory();
780
Path.replace('\\', '/');
781
if (Path.lastChar() != '/')
784
//! Construct from native filesystem
785
if (FileSystemType == FILESYSTEM_NATIVE)
787
// --------------------------------------------
789
#ifdef _IRR_WINDOWS_API_
790
#if !defined ( _WIN32_WCE )
792
r = new CFileList(Path, true, false);
794
struct _tfinddata_t c_file;
797
if( (hFile = _tfindfirst( _T("*"), &c_file )) != -1L )
801
r->addItem(Path + c_file.name, 0, c_file.size, (_A_SUBDIR & c_file.attrib) != 0, 0);
803
while( _tfindnext( hFile, &c_file ) == 0 );
810
//entry.Name = "E:\\";
811
//entry.isDirectory = true;
812
//Files.push_back(entry);
815
// --------------------------------------------
817
#if (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_))
820
r = new CFileList(Path, false, false);
822
r->addItem(Path + _IRR_TEXT(".."), 0, 0, true, 0);
824
//! We use the POSIX compliant methods instead of scandir
825
DIR* dirHandle=opendir(Path.c_str());
828
struct dirent *dirEntry;
829
while ((dirEntry=readdir(dirHandle)))
832
bool isDirectory = false;
834
if((strcmp(dirEntry->d_name, ".")==0) ||
835
(strcmp(dirEntry->d_name, "..")==0))
840
if (stat(dirEntry->d_name, &buf)==0)
843
isDirectory = S_ISDIR(buf.st_mode);
845
#if !defined(_IRR_SOLARIS_PLATFORM_) && !defined(__CYGWIN__)
846
// only available on some systems
849
isDirectory = dirEntry->d_type == DT_DIR;
853
r->addItem(Path + dirEntry->d_name, 0, size, isDirectory, 0);
861
//! create file list for the virtual filesystem
862
r = new CFileList(Path, false, false);
864
//! add relative navigation
869
r->addItem(Path + _IRR_TEXT("."), 0, 0, true, 0);
872
r->addItem(Path + _IRR_TEXT(".."), 0, 0, true, 0);
875
for (u32 i=0; i < FileArchives.size(); ++i)
877
const IFileList *merge = FileArchives[i]->getFileList();
879
for (u32 j=0; j < merge->getFileCount(); ++j)
881
if (core::isInSameDirectory(Path, merge->getFullFileName(j)) == 0)
883
r->addItem(merge->getFullFileName(j), merge->getFileOffset(j), merge->getFileSize(j), merge->isDirectory(j), 0);
894
//! Creates an empty filelist
895
IFileList* CFileSystem::createEmptyFileList(const io::path& path, bool ignoreCase, bool ignorePaths)
897
return new CFileList(path, ignoreCase, ignorePaths);
901
//! determines if a file exists and would be able to be opened.
902
bool CFileSystem::existFile(const io::path& filename) const
904
for (u32 i=0; i < FileArchives.size(); ++i)
905
if (FileArchives[i]->getFileList()->findFile(filename)!=-1)
908
#if defined(_IRR_WINDOWS_CE_PLATFORM_)
909
#if defined(_IRR_WCHAR_FILESYSTEM)
910
HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
912
HANDLE hFile = CreateFileW(core::stringw(filename).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
914
if (hFile == INVALID_HANDLE_VALUE)
922
_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
923
#if defined(_MSC_VER)
924
#if defined(_IRR_WCHAR_FILESYSTEM)
925
return (_waccess(filename.c_str(), 0) != -1);
927
return (_access(filename.c_str(), 0) != -1);
930
return (access(filename.c_str(), F_OK) != -1);
932
return (access(filename.c_str(), 0) != -1);
938
//! Creates a XML Reader from a file.
939
IXMLReader* CFileSystem::createXMLReader(const io::path& filename)
941
IReadFile* file = createAndOpenFile(filename);
945
IXMLReader* reader = createXMLReader(file);
951
//! Creates a XML Reader from a file.
952
IXMLReader* CFileSystem::createXMLReader(IReadFile* file)
957
return createIXMLReader(file);
961
//! Creates a XML Reader from a file.
962
IXMLReaderUTF8* CFileSystem::createXMLReaderUTF8(const io::path& filename)
964
IReadFile* file = createAndOpenFile(filename);
968
IXMLReaderUTF8* reader = createIXMLReaderUTF8(file);
974
//! Creates a XML Reader from a file.
975
IXMLReaderUTF8* CFileSystem::createXMLReaderUTF8(IReadFile* file)
980
return createIXMLReaderUTF8(file);
984
//! Creates a XML Writer from a file.
985
IXMLWriter* CFileSystem::createXMLWriter(const io::path& filename)
987
IWriteFile* file = createAndWriteFile(filename);
988
IXMLWriter* writer = 0;
991
writer = createXMLWriter(file);
998
//! Creates a XML Writer from a file.
999
IXMLWriter* CFileSystem::createXMLWriter(IWriteFile* file)
1001
return new CXMLWriter(file);
1005
//! creates a filesystem which is able to open files from the ordinary file system,
1006
//! and out of zipfiles, which are able to be added to the filesystem.
1007
IFileSystem* createFileSystem()
1009
return new CFileSystem();
1013
//! Creates a new empty collection of attributes, usable for serialization and more.
1014
IAttributes* CFileSystem::createEmptyAttributes(video::IVideoDriver* driver)
1016
return new CAttributes(driver);
1020
} // end namespace irr
1021
} // end namespace io