slang-zip-file-system.cpp
#include "slang-zip-file-system.h"
#include "../../slang-com-helper.h"
#include "../../slang-com-ptr.h"
#include "slang-io.h"
#include "slang-string-util.h"
#include "slang-blob.h"
#include "slang-string-slice-pool.h"
#include "slang-uint-set.h"
#include "slang-riff.h"
#include "slang-implicit-directory-collector.h"
#include "../../external/miniz/miniz.h"
#include "../../external/miniz/miniz_common.h"
#include "../../external/miniz/miniz_tdef.h"
#include "../../external/miniz/miniz_tinfl.h"
#include "../../external/miniz/miniz_zip.h"
namespace Slang
{
class ZipFileSystemImpl : public ISlangMutableFileSystem, public IArchiveFileSystem, public ComBaseObject
{
public:
// ISlangUnknown
SLANG_COM_BASE_IUNKNOWN_ALL
// ISlangCastable
virtual SLANG_NO_THROW void* SLANG_MCALL castAs(const Guid& guid) SLANG_OVERRIDE;
// ISlangFileSystem
virtual SLANG_NO_THROW SlangResult SLANG_MCALL loadFile(char const* path, ISlangBlob** outBlob) SLANG_OVERRIDE;
// ISlangFileSystemExt
virtual SLANG_NO_THROW SlangResult SLANG_MCALL getFileUniqueIdentity(const char* path, ISlangBlob** uniqueIdentityOut) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL calcCombinedPath(SlangPathType fromPathType, const char* fromPath, const char* path, ISlangBlob** pathOut) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL getPathType(const char* path, SlangPathType* pathTypeOut) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL getPath(PathKind pathKind, const char* path, ISlangBlob** outPath) SLANG_OVERRIDE;
virtual SLANG_NO_THROW void SLANG_MCALL clearCache() SLANG_OVERRIDE {}
virtual SLANG_NO_THROW SlangResult SLANG_MCALL enumeratePathContents(const char* path, FileSystemContentsCallBack callback, void* userData) SLANG_OVERRIDE;
virtual SLANG_NO_THROW OSPathKind SLANG_MCALL getOSPathKind() SLANG_OVERRIDE { return OSPathKind::None; }
// ISlangModifyableFileSystem
virtual SLANG_NO_THROW SlangResult SLANG_MCALL saveFile(const char* path, const void* data, size_t size) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL saveFileBlob(const char* path, ISlangBlob* dataBlob) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL remove(const char* path) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL createDirectory(const char* path) SLANG_OVERRIDE;
// IArchiveFileSystem
virtual SLANG_NO_THROW SlangResult SLANG_MCALL loadArchive(const void* archive, size_t archiveSizeInBytes) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL storeArchive(bool blobOwnsContent, ISlangBlob** outBlob) SLANG_OVERRIDE;
virtual SLANG_NO_THROW void SLANG_MCALL setCompressionStyle(const CompressionStyle& style) SLANG_OVERRIDE;
ZipFileSystemImpl();
~ZipFileSystemImpl();
protected:
enum class Mode
{
None, // m_archive is not initialized
Read, // m_archive is a reader
ReadWrite, // m_archive is a writer (that can be read from)
};
SlangResult _requireMode(Mode mode);
/// Do the mode change.
SlangResult _requireModeImpl(Mode newMode);
bool _hasArchive() { return m_mode != Mode::None; }
SlangResult _getFixedPath(const char* path, String& outPath);
SlangResult _findEntryIndex(const char* path, mz_uint& outIndex);
SlangResult _findEntryIndexFromFixedPath(const String& fixedPath, mz_uint& outIndex);
SlangResult _copyToAndInitWriter(mz_zip_archive& outWriter);
/// Returns SLANG_E_NOT_FOUND if no directory or contents found
/// terminationState controls when search terminates. If State::Undefined, will enumerate everything.
/// If outContents not set, will just determine if the directory exists
SlangResult _getPathContents(ImplicitDirectoryCollector::State terminationState, ImplicitDirectoryCollector* outCollector);
void _rebuildMap();
/// Returns true if the named item is at the index
UnownedStringSlice _getPathAtIndex(Index index);
void* getInterface(const Guid& guid);
void* getObject(const Guid& guid);
void _initReadWrite(mz_zip_archive& outWriter);
// Maps from a path to an index in the m_archive
StringSliceIndexMap m_pathMap;
// If bit is set (at the archive index) this index has been deleted.
UIntSet m_removedSet;
ScopedAllocation m_data;
mz_uint m_compressionLevel = MZ_BEST_COMPRESSION;
Mode m_mode = Mode::None;
mz_file_read_func m_readFunc;
mz_zip_archive m_archive;
};
void* ZipFileSystemImpl::getInterface(const Guid& guid)
{
if ( guid == ISlangUnknown::getTypeGuid() ||
guid == ISlangCastable::getTypeGuid())
{
return static_cast<ISlangMutableFileSystem*>(this);
}
else if (guid == ISlangFileSystem::getTypeGuid() ||
guid == ISlangFileSystemExt::getTypeGuid() ||
guid == ISlangMutableFileSystem::getTypeGuid())
{
return static_cast<ISlangMutableFileSystem*>(this);
}
else if (guid == IArchiveFileSystem::getTypeGuid())
{
return static_cast<IArchiveFileSystem*>(this);
}
return nullptr;
}
void* ZipFileSystemImpl::getObject(const Guid& guid)
{
SLANG_UNUSED(guid);
return nullptr;
}
void* ZipFileSystemImpl::castAs(const Guid& guid)
{
if (auto ptr = getInterface(guid))
{
return ptr;
}
return getObject(guid);
}
// This is a very awkward hack to make it so we can get a read func, without having to implement all of the tracking etc.
// All this does is create an empty zip, convert into a reader, and then grab the read function
static mz_file_read_func _calcReadFunc()
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
mz_zip_writer_init_heap(&archive, 0, 0);
// Convert to reader
void* buf;
size_t size;
mz_zip_writer_finalize_heap_archive(&archive, &buf, &size);
ScopedAllocation alloc;
alloc.attach(buf, size);
mz_zip_writer_end(&archive);
// Read
mz_zip_zero_struct(&archive);
mz_zip_reader_init_mem(&archive, alloc.getData(), alloc.getSizeInBytes(), 0);
auto readFunc = archive.m_pRead;
mz_zip_end(&archive);
return readFunc;
}
static mz_file_read_func _getReadFunc()
{
static const auto readFunc = _calcReadFunc();
return readFunc;
}
ZipFileSystemImpl::ZipFileSystemImpl():
m_mode(Mode::None)
{
m_readFunc = _getReadFunc();
}
ZipFileSystemImpl::~ZipFileSystemImpl()
{
_requireMode(Mode::None);
}
void ZipFileSystemImpl::_rebuildMap()
{
m_pathMap.clear();
const mz_uint entryCount = mz_zip_reader_get_num_files(&m_archive);
m_removedSet.resizeAndClear(0);
for (mz_uint i = 0; i < entryCount; ++i)
{
mz_zip_archive_file_stat fileStat;
if (!mz_zip_reader_file_stat(&m_archive, mz_uint(i), &fileStat))
{
continue;
}
UnownedStringSlice currentName(fileStat.m_filename);
// Get rid of '/'
currentName = currentName.trim('/');
m_pathMap.add(currentName, Index(i));
}
}
UnownedStringSlice ZipFileSystemImpl::_getPathAtIndex(Index index)
{
SLANG_ASSERT(m_mode != Mode::None);
mz_zip_archive_file_stat fileStat;
// Check it's added at the end
if (!mz_zip_reader_file_stat(&m_archive, mz_uint(index), &fileStat))
{
return UnownedStringSlice();
}
return UnownedStringSlice(fileStat.m_filename).trim('/');
}
void ZipFileSystemImpl::_initReadWrite(mz_zip_archive& outWriter)
{
mz_zip_zero_struct(&outWriter);
mz_zip_writer_init_heap(&outWriter, 0, 0);
outWriter.m_pRead = m_readFunc;
}
SlangResult ZipFileSystemImpl::_copyToAndInitWriter(mz_zip_archive& outWriter)
{
mz_zip_zero_struct(&outWriter);
switch (m_mode)
{
case Mode::None:
{
_initReadWrite(outWriter);
return SLANG_OK;
}
case Mode::Read:
case Mode::ReadWrite:
{
_initReadWrite(outWriter);
const mz_uint entryCount = mz_zip_reader_get_num_files(&m_archive);
for (mz_uint i = 0; i < entryCount; ++i)
{
if (m_removedSet.contains(i))
{
continue;
}
// It's worth noting - it's not clear if this will work, because m_archive might not be a reader, in the miniz docs.
// If it's a writer, it's not clear how to convert a writer to a reader *selectively* which
// we require if we are going to lazily handle removals.
//
// The fix to make this work is the hack that sets the m_reader, such that in effect the writer is both read and write.
// That works because the default writer behavior is a single block of memory for the archive, and that is compatible
// with the reader.
if (! mz_zip_writer_add_from_zip_reader(&outWriter, &m_archive, i))
{
mz_zip_end(&outWriter);
return SLANG_FAIL;
}
}
return SLANG_OK;
}
default: break;
}
return SLANG_FAIL;
}
SlangResult ZipFileSystemImpl::_requireModeImpl(Mode newMode)
{
SLANG_ASSERT(newMode != m_mode);
switch (m_mode)
{
case Mode::None:
{
switch (newMode)
{
case Mode::Read:
{
mz_uint flags = 0;
mz_zip_zero_struct(&m_archive);
mz_zip_reader_init(&m_archive, 0, flags);
break;
}
case Mode::ReadWrite:
{
_initReadWrite(m_archive);
break;
}
default: break;
}
break;
}
case Mode::Read:
{
switch (newMode)
{
case Mode::None:
{
m_data.deallocate();
mz_zip_end(&m_archive);
break;
}
case Mode::ReadWrite:
{
// If nothing is removed, we can just convert
if (m_removedSet.isEmpty())
{
// Convert the reader into the writer
if (!mz_zip_writer_init_from_reader(&m_archive, nullptr))
{
return SLANG_FAIL;
}
// If it's now a writer the memory is owned by the m_archive
m_data.detach();
}
else
{
// Copy into a new writer
mz_zip_archive writer;
SLANG_RETURN_ON_FAIL(_copyToAndInitWriter(writer));
// In the process we have removed anything that was deleted
m_removedSet.clear();
// Don't need the read data anymore
m_data.deallocate();
// Free the current archive
mz_zip_end(&m_archive);
// Make the writer current
m_archive = writer;
break;
}
break;
}
}
break;
}
case Mode::ReadWrite:
{
switch (newMode)
{
case Mode::None:
{
mz_zip_writer_end(&m_archive);
break;
}
case Mode::Read:
{
// If anything has been removed we copy selectively into a new writer, and then convert that
if (!m_removedSet.isEmpty())
{
// There are entries that are deleted... so we need to copy selectively
mz_zip_archive writer;
SLANG_RETURN_ON_FAIL(_copyToAndInitWriter(writer));
// In the process we have removed anything that was deleted
m_removedSet.clear();
// Get rid of the old writer
mz_zip_writer_end(&m_archive);
m_archive = writer;
}
void* buf;
size_t size;
mz_zip_writer_finalize_heap_archive(&m_archive, &buf, &size);
m_data.attach(buf, size);
mz_zip_writer_end(&m_archive);
// Read
mz_zip_zero_struct(&m_archive);
if (!mz_zip_reader_init_mem(&m_archive, m_data.getData(), m_data.getSizeInBytes(), 0))
{
m_data.deallocate();
return SLANG_FAIL;
}
break;
}
default: break;
}
}
}
// Set the new mode
m_mode = newMode;
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::_requireMode(Mode newMode)
{
if (newMode == m_mode)
{
return SLANG_OK;
}
SlangResult res = _requireModeImpl(newMode);
if (SLANG_SUCCEEDED(res))
{
m_mode = newMode;
}
_rebuildMap();
return res;
}
SlangResult ZipFileSystemImpl::_getFixedPath(const char* path, String& outPath)
{
StringBuilder simplifiedPath;
SLANG_RETURN_ON_FAIL(Path::simplify(path, Path::SimplifyStyle::AbsoluteOnlyAndNoRoot, simplifiedPath));
outPath = simplifiedPath;
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::_findEntryIndexFromFixedPath(const String& fixedPath, mz_uint& outIndex)
{
const Index index = m_pathMap.getValue(fixedPath.getUnownedSlice());
// If not in list or deleted - it is removed
if (index < 0 || m_removedSet.contains(index))
{
return SLANG_E_NOT_FOUND;
}
outIndex = mz_uint(index);
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::_findEntryIndex(const char* path, mz_uint& outIndex)
{
String fixedPath;
SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
SLANG_RETURN_ON_FAIL(_findEntryIndexFromFixedPath(fixedPath, outIndex));
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::loadFile(char const* path, ISlangBlob** outBlob)
{
mz_uint index;
SLANG_RETURN_ON_FAIL(_findEntryIndex(path, index));
// Check it's a file
mz_zip_archive_file_stat fileStat;
if (!mz_zip_reader_file_stat(&m_archive, index, &fileStat) || fileStat.m_is_directory)
{
return SLANG_E_NOT_FOUND;
}
ScopedAllocation alloc;
if (!alloc.allocateTerminated(size_t(fileStat.m_uncomp_size)))
{
return SLANG_E_OUT_OF_MEMORY;
}
const mz_uint flags = 0;
// Extract to memory
if (!mz_zip_reader_extract_to_mem(&m_archive, index, alloc.getData(), alloc.getSizeInBytes(), flags))
{
return SLANG_FAIL;
}
*outBlob = RawBlob::moveCreate(alloc).detach();
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::getPathType(const char* path, SlangPathType* outPathType)
{
if (!_hasArchive())
{
return SLANG_E_NOT_FOUND;
}
String fixedPath;
SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
// First look if there is an *explicit* entry - either file or directory
mz_uint index;
if (SLANG_SUCCEEDED(_findEntryIndexFromFixedPath(fixedPath, index)))
{
mz_zip_archive_file_stat fileStat;
if (!mz_zip_reader_file_stat(&m_archive, index, &fileStat))
{
return SLANG_FAIL;
}
*outPathType = fileStat.m_is_directory ? SLANG_PATH_TYPE_DIRECTORY : SLANG_PATH_TYPE_FILE;
return SLANG_OK;
}
else
{
// It could be an *implicit* directory (ie as part of a path). So lets look for that...
ImplicitDirectoryCollector collector(fixedPath);
SLANG_RETURN_ON_FAIL(_getPathContents(ImplicitDirectoryCollector::State::DirectoryExists, &collector));
if (collector.getDirectoryExists())
{
*outPathType = SLANG_PATH_TYPE_DIRECTORY;
return SLANG_OK;
}
}
return SLANG_E_NOT_FOUND;
}
SlangResult ZipFileSystemImpl::getPath(PathKind pathKind, const char* path, ISlangBlob** outPath)
{
switch (pathKind)
{
case PathKind::Display:
case PathKind::Canonical:
{
// Get the fixed path
String fixedPath;
SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
// See if we can find in the zip explicitly
mz_uint index;
if (SLANG_SUCCEEDED(_findEntryIndexFromFixedPath(fixedPath, index)))
{
mz_zip_archive_file_stat fileStat;
if (!mz_zip_reader_file_stat(&m_archive, index, &fileStat))
{
return SLANG_FAIL;
}
// Use the path in the archive itself
*outPath = StringUtil::createStringBlob(fileStat.m_filename).detach();
return SLANG_OK;
}
// Else output the fixed path
*outPath = StringUtil::createStringBlob(fixedPath).detach();
return SLANG_OK;
}
case PathKind::Simplified:
{
*outPath = StringUtil::createStringBlob(Path::simplify(path)).detach();
return SLANG_OK;
}
default: break;
}
return SLANG_E_NOT_AVAILABLE;
}
SlangResult ZipFileSystemImpl::getFileUniqueIdentity(const char* path, ISlangBlob** outUniqueIdentity)
{
return getPath(PathKind::Canonical, path, outUniqueIdentity);
}
SlangResult ZipFileSystemImpl::calcCombinedPath(SlangPathType fromPathType, const char* fromPath, const char* path, ISlangBlob** pathOut)
{
String relPath;
switch (fromPathType)
{
case SLANG_PATH_TYPE_FILE:
{
relPath = Path::combine(Path::getParentDirectory(fromPath), path);
break;
}
case SLANG_PATH_TYPE_DIRECTORY:
{
relPath = Path::combine(fromPath, path);
break;
}
}
*pathOut = StringUtil::createStringBlob(relPath).detach();
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::_getPathContents(ImplicitDirectoryCollector::State terminationState, ImplicitDirectoryCollector* outCollector)
{
if (!_hasArchive())
{
return SLANG_E_NOT_FOUND;
}
// Okay - I want to iterate through all of the entries and look for the ones with this prefix
const Index entryCount = Index(mz_zip_reader_get_num_files(&m_archive));
for (Index i = 0; i < entryCount; ++i)
{
// Skip if it's been deleted.
if (m_removedSet.contains(i))
{
continue;
}
mz_zip_archive_file_stat fileStat;
if (!mz_zip_reader_file_stat(&m_archive, mz_uint(i), &fileStat))
{
continue;
}
UnownedStringSlice currentPath(fileStat.m_filename);
SlangPathType pathType = fileStat.m_is_directory ? SLANG_PATH_TYPE_DIRECTORY : SLANG_PATH_TYPE_FILE;
outCollector->addPath(pathType, currentPath);
// If a termination state is defined, and we reach it, we are done
if (terminationState != ImplicitDirectoryCollector::State::None && outCollector->hasState(terminationState))
{
return SLANG_OK;
}
}
// Check we found the directory at all...
return outCollector->getDirectoryExists() ? SLANG_OK : SLANG_E_NOT_FOUND;
}
SlangResult ZipFileSystemImpl::enumeratePathContents(const char* path, FileSystemContentsCallBack callback, void* userData)
{
if (!_hasArchive())
{
return SLANG_E_NOT_FOUND;
}
String fixedPath;
SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
ImplicitDirectoryCollector collector(fixedPath);
SLANG_RETURN_ON_FAIL(_getPathContents(ImplicitDirectoryCollector::State::None, &collector));
return collector.enumerate(callback, userData);
}
SlangResult ZipFileSystemImpl::saveFileBlob(const char* path, ISlangBlob* dataBlob)
{
if (!dataBlob)
{
return SLANG_E_INVALID_ARG;
}
return saveFile(path, dataBlob->getBufferPointer(), dataBlob->getBufferSize());
}
SlangResult ZipFileSystemImpl::saveFile(const char* path, const void* data, size_t size)
{
String fixedPath;
SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
mz_uint32 index;
if (SLANG_SUCCEEDED(_findEntryIndexFromFixedPath(fixedPath, index)))
{
// Mark as removed
m_removedSet.add(index);
}
// We need to be able to write to the archive
_requireMode(Mode::ReadWrite);
// TODO(JS):
// We may want to check the directory exists that holds the path exists
// Which is easy to do. Without this check it allows directories to come into existence
// when the path to the file is used.
// This behaviour *isn't* strictly the same as the file system, which requires the path
// to a file to exist before it is written.
//
// Not enforcing this allows zips that don't explicitly specify paths - which saves space
// and is simpler.
//
// NOTE! This also means that if a file that produces an implicit path is *removed* that
// the implicit directories are also in effect removed.
// Need to add to the end of the file
const mz_uint32 entryCount = mz_zip_reader_get_num_files(&m_archive);
if (!mz_zip_writer_add_mem(&m_archive, fixedPath.getBuffer(), data, size, m_compressionLevel))
{
return SLANG_FAIL;
}
// Make sure it is added at expended index
SLANG_ASSERT(_getPathAtIndex(entryCount) == fixedPath.getUnownedSlice());
// Set in the map
m_pathMap.add(fixedPath.getUnownedSlice(), entryCount);
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::remove(const char* path)
{
String fixedPath;
SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
mz_uint32 index;
SLANG_RETURN_ON_FAIL(_findEntryIndexFromFixedPath(fixedPath, index));
mz_zip_archive_file_stat fileStat;
if (!mz_zip_reader_file_stat(&m_archive, index, &fileStat))
{
return SLANG_FAIL;
}
if (fileStat.m_is_directory)
{
// Find the directory contents
ImplicitDirectoryCollector collector(fixedPath);
SLANG_RETURN_ON_FAIL(_getPathContents(ImplicitDirectoryCollector::State::HasContent, &collector));
if (collector.hasContent())
{
// If it contains children we can't remove it
return SLANG_FAIL;
}
}
// Mark as removed
m_removedSet.add(index);
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::createDirectory(const char* path)
{
String fixedPath;
SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
// If we find something with this name, we can't create it
mz_uint32 index;
if (SLANG_SUCCEEDED(_findEntryIndexFromFixedPath(fixedPath, index)))
{
return SLANG_FAIL;
}
// Make writable
SLANG_RETURN_ON_FAIL(_requireMode(Mode::ReadWrite));
const mz_uint entryCount = mz_zip_reader_get_num_files(&m_archive);
// The terminating / in the path indicates it's a directory
{
String dirPath(fixedPath);
dirPath.appendChar('/');
if (!mz_zip_writer_add_mem(&m_archive, dirPath.getBuffer(), nullptr, 0, m_compressionLevel))
{
return SLANG_FAIL;
}
}
SLANG_ASSERT(_getPathAtIndex(entryCount) == fixedPath.getUnownedSlice());
// Set the index, that we added at end
m_pathMap.add(fixedPath.getUnownedSlice(), entryCount);
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::storeArchive(bool blobOwnsContent, ISlangBlob** outBlob)
{
// If we have anything deleted in 'Read', we need to convert to 'Write' and then back to read
if (m_mode == Mode::Read && !m_removedSet.isEmpty())
{
_requireMode(Mode::ReadWrite);
}
_requireMode(Mode::Read);
ComPtr<ISlangBlob> blob;
if (blobOwnsContent)
{
// Takes a copy
blob = RawBlob::create(m_data.getData(), Index(m_data.getSizeInBytes()));
}
else
{
// Doesn't take a copy... Must use with care(!)
blob = UnownedRawBlob::create(m_data.getData(), Index(m_data.getSizeInBytes()));
}
*outBlob = blob.detach();
return SLANG_OK;
}
SlangResult ZipFileSystemImpl::loadArchive(const void* archive, size_t archiveSizeInBytes)
{
// Making the mode None empties the archive
SLANG_RETURN_ON_FAIL(_requireMode(Mode::None));
// Store a copy of the archive contents
if (!m_data.set(archive, archiveSizeInBytes))
{
return SLANG_E_OUT_OF_MEMORY;
}
// Initialize archive
mz_zip_zero_struct(&m_archive);
// Read the contents of the archive, and make m_archive own it
if (!mz_zip_reader_init_mem(&m_archive, m_data.getData(), archiveSizeInBytes, 0))
{
return SLANG_FAIL;
}
m_mode = Mode::Read;
// Set up the mapping from paths to indices
_rebuildMap();
return SLANG_OK;
}
void ZipFileSystemImpl::setCompressionStyle(const CompressionStyle& style)
{
switch (style.m_type)
{
case CompressionStyle::Type::BestSpeed: m_compressionLevel = MZ_BEST_SPEED; break;
case CompressionStyle::Type::BestCompression: m_compressionLevel = MZ_BEST_COMPRESSION; break;
case CompressionStyle::Type::Default: m_compressionLevel = MZ_DEFAULT_LEVEL; break;
case CompressionStyle::Type::Level:
{
int level = int(style.m_level * 10.0f + 0.5);
level = (level < 0) ? 0 : level;
level = (level > MZ_UBER_COMPRESSION) ? MZ_UBER_COMPRESSION : level;
m_compressionLevel = level;
break;
}
}
}
/* static */SlangResult ZipFileSystem::create(ComPtr<ISlangMutableFileSystem>& out)
{
out = new ZipFileSystemImpl;
return SLANG_OK;
}
/* static */bool ZipFileSystem::isArchive(const void* data, size_t dataSizeInBytes)
{
if (dataSizeInBytes < sizeof(FourCC))
{
return false;
}
FourCC fourCC = 0;
::memcpy(&fourCC, data, sizeof(FourCC));
// https://en.wikipedia.org/wiki/List_of_file_signatures
switch (fourCC)
{
case SLANG_FOUR_CC(0x50, 0x4B, 0x03, 0x04):
case SLANG_FOUR_CC(0x50, 0x4B, 0x05, 0x06):
case SLANG_FOUR_CC(0x50, 0x4B, 0x07, 0x08):
{
// It's a zip
return true;
}
}
return false;
}
} // namespace Slang