305 lines
6.5 KiB
C++
305 lines
6.5 KiB
C++
// Copyright (C) 2009 Gaz Davidson
|
|
// This file is part of the "Irrlicht Engine".
|
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
|
|
#include "CTarReader.h"
|
|
#include "CFileList.h"
|
|
#include "CLimitReadFile.h"
|
|
#include "os.h"
|
|
#include "coreutil.h"
|
|
#include "errno.h"
|
|
|
|
#include "IrrCompileConfig.h"
|
|
|
|
namespace irr
|
|
{
|
|
namespace io
|
|
{
|
|
|
|
//! Constructor
|
|
CArchiveLoaderTAR::CArchiveLoaderTAR(io::IFileSystem* fs)
|
|
: FileSystem(fs)
|
|
{
|
|
#ifdef _DEBUG
|
|
setDebugName("CArchiveLoaderTAR");
|
|
#endif
|
|
}
|
|
|
|
|
|
//! returns true if the file maybe is able to be loaded by this class
|
|
bool CArchiveLoaderTAR::isALoadableFileFormat(const core::string<c16>& filename) const
|
|
{
|
|
return core::hasFileExtension(filename, "tar");
|
|
}
|
|
|
|
|
|
//! Creates an archive from the filename
|
|
/** \param file File handle to check.
|
|
\return Pointer to newly created archive, or 0 upon error. */
|
|
IFileArchive* CArchiveLoaderTAR::createArchive(const core::string<c16>& filename, bool ignoreCase, bool ignorePaths) const
|
|
{
|
|
IFileArchive *archive = 0;
|
|
io::IReadFile* file = FileSystem->createAndOpenFile(filename);
|
|
|
|
if (file)
|
|
{
|
|
archive = createArchive(file, ignoreCase, ignorePaths);
|
|
file->drop();
|
|
}
|
|
|
|
return archive;
|
|
}
|
|
|
|
|
|
//! creates/loads an archive from the file.
|
|
//! \return Pointer to the created archive. Returns 0 if loading failed.
|
|
IFileArchive* CArchiveLoaderTAR::createArchive(io::IReadFile* file, bool ignoreCase, bool ignorePaths) const
|
|
{
|
|
IFileArchive *archive = 0;
|
|
if (file)
|
|
{
|
|
file->seek(0);
|
|
archive = new CTarReader(file, ignoreCase, ignorePaths);
|
|
}
|
|
return archive;
|
|
}
|
|
|
|
//! Check if the file might be loaded by this class
|
|
/** Check might look into the file.
|
|
\param file File handle to check.
|
|
\return True if file seems to be loadable. */
|
|
bool CArchiveLoaderTAR::isALoadableFileFormat(io::IReadFile* file) const
|
|
{
|
|
// TAR files consist of blocks of 512 bytes
|
|
// if it isn't a multiple of 512 then it's not a TAR file.
|
|
if (file->getSize() % 512)
|
|
return false;
|
|
|
|
file->seek(0);
|
|
|
|
// read header of first file
|
|
STarHeader fHead;
|
|
file->read(&fHead, sizeof(STarHeader));
|
|
|
|
u32 checksum = 0;
|
|
sscanf(fHead.Checksum, "%o", &checksum);
|
|
|
|
// verify checksum
|
|
|
|
// some old TAR writers assume that chars are signed, others assume unsigned
|
|
// USTAR archives have a longer header, old TAR archives end after linkname
|
|
|
|
u32 checksum1=0;
|
|
s32 checksum2=0;
|
|
|
|
// remember to blank the checksum field!
|
|
memset(fHead.Checksum, ' ', 8);
|
|
|
|
// old header
|
|
for (u8* p = (u8*)(&fHead); p < (u8*)(&fHead.Magic[0]); ++p)
|
|
{
|
|
checksum1 += *p;
|
|
checksum2 += c8(*p);
|
|
}
|
|
|
|
if (!strcmp(fHead.Magic, "star"))
|
|
{
|
|
for (u8* p = (u8*)(&fHead.Magic[0]); p < (u8*)(&fHead) + sizeof(fHead); ++p)
|
|
{
|
|
checksum1 += *p;
|
|
checksum2 += c8(*p);
|
|
}
|
|
}
|
|
return checksum1 == checksum || checksum2 == (s32)checksum;
|
|
}
|
|
|
|
/*
|
|
TAR Archive
|
|
*/
|
|
CTarReader::CTarReader(IReadFile* file, bool ignoreCase, bool ignorePaths)
|
|
: File(file), IgnoreCase(ignoreCase), IgnorePaths(ignorePaths)
|
|
{
|
|
#ifdef _DEBUG
|
|
setDebugName("CTarReader");
|
|
#endif
|
|
|
|
if (File)
|
|
{
|
|
File->grab();
|
|
|
|
Base = File->getFileName();
|
|
Base.replace('\\', '/');
|
|
|
|
// fill the file list
|
|
populateFileList();
|
|
}
|
|
}
|
|
|
|
|
|
CTarReader::~CTarReader()
|
|
{
|
|
if (File)
|
|
File->drop();
|
|
|
|
}
|
|
|
|
|
|
u32 CTarReader::populateFileList()
|
|
{
|
|
STarHeader fHead;
|
|
FileList.clear();
|
|
|
|
u32 pos = 0;
|
|
while ( s32(pos + sizeof(STarHeader)) < File->getSize())
|
|
{
|
|
// seek to next file header
|
|
File->seek(pos);
|
|
|
|
// read the header
|
|
File->read(&fHead, sizeof(fHead));
|
|
|
|
// only add standard files for now
|
|
if (fHead.Link == ETLI_REGULAR_FILE || ETLI_REGULAR_FILE_OLD)
|
|
{
|
|
STARArchiveEntry entry;
|
|
|
|
core::string<c16> fullPath = L"";
|
|
fullPath.reserve(255);
|
|
|
|
// USTAR archives have a filename prefix
|
|
// may not be null terminated, copy carefully!
|
|
if (!strcmp(fHead.Magic, "ustar"))
|
|
{
|
|
c8* np = fHead.FileNamePrefix;
|
|
while(*np && (np - fHead.FileNamePrefix) < 155)
|
|
fullPath.append(*np);
|
|
np++;
|
|
}
|
|
|
|
// append the file name
|
|
c8* np = fHead.FileName;
|
|
while(*np && (np - fHead.FileName) < 100)
|
|
{
|
|
fullPath.append(*np);
|
|
np++;
|
|
}
|
|
|
|
fullPath.replace('\\', '/');
|
|
const s32 lastSlash = fullPath.findLast('/');
|
|
|
|
if (IgnoreCase)
|
|
fullPath.make_lower();
|
|
|
|
if (lastSlash == -1)
|
|
{
|
|
entry.path = "";
|
|
entry.simpleFileName = fullPath;
|
|
}
|
|
else
|
|
{
|
|
entry.path = fullPath.subString(0, lastSlash);
|
|
if (IgnorePaths)
|
|
entry.simpleFileName = &fullPath[lastSlash+1];
|
|
else
|
|
entry.simpleFileName = fullPath;
|
|
}
|
|
|
|
// get size
|
|
core::stringc sSize = "";
|
|
sSize.reserve(12);
|
|
np = fHead.Size;
|
|
while(*np && (np - fHead.Size) < 12)
|
|
{
|
|
sSize.append(*np);
|
|
np++;
|
|
}
|
|
|
|
entry.size=strtoul(sSize.c_str(), NULL, 8);
|
|
if (errno==ERANGE)
|
|
os::Printer::log("File too large", fullPath, ELL_WARNING);
|
|
|
|
// save start position
|
|
entry.startPos = pos + 512;
|
|
|
|
// move to next file header block
|
|
pos = entry.startPos + (entry.size / 512) * 512 +
|
|
((entry.size % 512) ? 512 : 0);
|
|
|
|
// add file to list
|
|
FileList.push_back(entry);
|
|
}
|
|
else
|
|
{
|
|
// move to next block
|
|
pos += 512;
|
|
}
|
|
|
|
}
|
|
|
|
FileList.sort();
|
|
|
|
return FileList.size();
|
|
}
|
|
|
|
//! opens a file by file name
|
|
IReadFile* CTarReader::createAndOpenFile(const core::string<c16>& filename)
|
|
{
|
|
const s32 index = findFile(filename);
|
|
|
|
if (index != -1)
|
|
return createAndOpenFile(index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//! opens a file by index
|
|
IReadFile* CTarReader::createAndOpenFile(u32 index)
|
|
{
|
|
if (index < FileList.size())
|
|
return createLimitReadFile(FileList[index].simpleFileName, File, FileList[index].startPos, FileList[index].size);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
//! returns count of files in archive
|
|
u32 CTarReader::getFileCount() const
|
|
{
|
|
return FileList.size();
|
|
}
|
|
|
|
|
|
//! returns data of file
|
|
const IFileArchiveEntry* CTarReader::getFileInfo(u32 index)
|
|
{
|
|
return &FileList[index];
|
|
}
|
|
|
|
|
|
//! return the id of the file Archive
|
|
const core::string<c16>& CTarReader::getArchiveName()
|
|
{
|
|
return Base;
|
|
}
|
|
|
|
|
|
//! returns fileindex
|
|
s32 CTarReader::findFile(const core::string<c16>& simpleFilename)
|
|
{
|
|
STARArchiveEntry entry;
|
|
entry.simpleFileName = simpleFilename;
|
|
|
|
if (IgnoreCase)
|
|
entry.simpleFileName.make_lower();
|
|
|
|
if (IgnorePaths)
|
|
core::deletePathFromFilename(entry.simpleFileName);
|
|
|
|
return FileList.binary_search(entry);
|
|
}
|
|
|
|
} // end namespace io
|
|
} // end namespace irr
|
|
|