Path: blob/master/Common/File/VFS/SevenZipFileReader.cpp
10523 views
#include <algorithm>1#include <array>2#include <cstdio>3#include <cstdlib>4#include <cstring>5#include <set>67#ifndef _WIN328#include <unistd.h>9#endif1011#define NOMINMAX // 7z includes windows.h for some reason.12#include "ext/lzma-sdk/7z.h"13#include "ext/lzma-sdk/7zCrc.h"14#include "ext/lzma-sdk/7zFile.h"15#include "ext/lzma-sdk/Lzma2Dec.h"16#include "ext/lzma-sdk/LzmaDec.h"1718#include "Common/Data/Encoding/Utf8.h"19#include "Common/Log.h"20#include "Common/StringUtils.h"21#include "Common/File/VFS/SevenZipFileReader.h"2223static constexpr size_t SEVENZIP_LOOKBUF_SIZE = 1 << 14;24static constexpr size_t SEVENZIP_STREAM_LOOKAHEAD = 1 << 18;25static constexpr size_t SEVENZIP_SKIPBUF_SIZE = 1 << 15;2627static constexpr UInt32 SEVENZIP_METHOD_COPY = 0;28static constexpr UInt32 SEVENZIP_METHOD_LZMA = 0x30101;29static constexpr UInt32 SEVENZIP_METHOD_LZMA2 = 0x21;3031static WRes OpenArchiveStreamForRead(CFileInStream *stream, const Path &archivePath) {32#ifdef USE_WINDOWS_FILE33return InFile_OpenW(&stream->file, archivePath.ToWString().c_str());34#else35#if defined(HAVE_LIBRETRO_VFS)36#if PPSSPP_PLATFORM(ANDROID)37if (archivePath.Type() == PathType::CONTENT_URI) {38int fd = File::OpenFD(archivePath, File::OPEN_READ);39if (fd < 0) {40return 1;41}42stream->file.fd = fd;43return 0;44}45#endif46return InFile_Open(&stream->file, archivePath.ToString().c_str());47#else48FILE *file = File::OpenCFile(archivePath, "rb");49if (!file) {50return 1;51}5253#if defined(USE_FOPEN)54stream->file.file = file;55return 0;56#else57const int openedFd = fileno(file);58if (openedFd < 0) {59fclose(file);60return 1;61}6263const int fd = dup(openedFd);64fclose(file);65if (fd < 0) {66return 1;67}6869stream->file.fd = fd;70return 0;71#endif72#endif73#endif74}7576class SevenZipFileReference : public VFSFileReference {77public:78UInt32 index = 0;79};8081class SevenZipOpenFile : public VFSOpenFile {82public:83enum class Mode {84MEMORY,85STREAM_COPY,86STREAM_LZMA,87STREAM_LZMA2,88};8990SevenZipOpenFile() {91LzmaDec_CONSTRUCT(&lzma_);92Lzma2Dec_CONSTRUCT(&lzma2_);93archiveStream_.wres = 0;94File_Construct(&archiveStream_.file);95LookToRead2_INIT(&lookStream_);96}9798~SevenZipOpenFile() override {99Release();100delete[] data;101}102103void Release() {104if (lookStreamBuf_) {105free(lookStreamBuf_);106lookStreamBuf_ = nullptr;107}108if (lzmaAllocated_ && alloc_) {109LzmaDec_Free(&lzma_, alloc_);110lzmaAllocated_ = false;111}112if (lzma2Allocated_ && alloc_) {113Lzma2Dec_Free(&lzma2_, alloc_);114lzma2Allocated_ = false;115}116if (streamOpened_) {117File_Close(&archiveStream_.file);118streamOpened_ = false;119}120}121122Mode mode = Mode::MEMORY;123124uint8_t *data = nullptr;125size_t size = 0;126size_t offset = 0;127128UInt32 fileIndex = 0;129UInt64 bytesToSkip = 0;130UInt64 fileRemaining = 0;131UInt64 packRemaining = 0;132UInt64 folderRemaining = 0;133bool streamError = false;134bool hasFileCRC = false;135UInt32 expectedFileCRC = 0;136UInt32 runningCRC = CRC_INIT_VAL;137bool crcChecked = false;138139ISzAlloc *alloc_ = nullptr;140CFileInStream archiveStream_;141CLookToRead2 lookStream_;142Byte *lookStreamBuf_ = nullptr;143bool streamOpened_ = false;144CLzmaDec lzma_;145CLzma2Dec lzma2_;146bool lzmaAllocated_ = false;147bool lzma2Allocated_ = false;148std::array<uint8_t, SEVENZIP_SKIPBUF_SIZE> skipBuf_{};149};150151static bool InitStreamingOpenFile(152const Path &archivePath,153const CSzArEx &db,154ISzAlloc *alloc,155UInt32 fileIndex,156SevenZipOpenFile *openFile) {157openFile->Release();158openFile->mode = SevenZipOpenFile::Mode::MEMORY;159openFile->alloc_ = alloc;160openFile->fileIndex = fileIndex;161162const UInt32 folderIndex = db.FileToFolder[fileIndex];163if (folderIndex == (UInt32)-1) {164openFile->mode = SevenZipOpenFile::Mode::STREAM_COPY;165openFile->bytesToSkip = 0;166openFile->fileRemaining = 0;167openFile->folderRemaining = 0;168openFile->packRemaining = 0;169openFile->size = 0;170openFile->offset = 0;171return true;172}173174const Byte *folderData = db.db.CodersData + db.db.FoCodersOffsets[folderIndex];175CSzData sd;176sd.Data = folderData;177sd.Size = db.db.FoCodersOffsets[(size_t)folderIndex + 1] - db.db.FoCodersOffsets[folderIndex];178CSzFolder folder;179if (SzGetNextFolderItem(&folder, &sd) != SZ_OK || sd.Size != 0) {180return false;181}182183if (folder.NumCoders != 1 || folder.NumPackStreams != 1 || folder.NumBonds != 0 || folder.PackStreams[0] != 0) {184return false;185}186187const CSzCoderInfo &coder = folder.Coders[0];188if (coder.NumStreams != 1) {189return false;190}191192SevenZipOpenFile::Mode mode = SevenZipOpenFile::Mode::MEMORY;193if (coder.MethodID == SEVENZIP_METHOD_COPY) {194mode = SevenZipOpenFile::Mode::STREAM_COPY;195} else if (coder.MethodID == SEVENZIP_METHOD_LZMA) {196mode = SevenZipOpenFile::Mode::STREAM_LZMA;197} else if (coder.MethodID == SEVENZIP_METHOD_LZMA2) {198mode = SevenZipOpenFile::Mode::STREAM_LZMA2;199} else {200return false;201}202203UInt64 packSize = 0;204const UInt64 *packPositions = db.db.PackPositions + db.db.FoStartPackStreamIndex[folderIndex];205packSize = packPositions[1] - packPositions[0];206207const UInt64 unpackPos = db.UnpackPositions[fileIndex];208const UInt64 folderStartUnpackPos = db.UnpackPositions[db.FolderToFile[folderIndex]];209const UInt64 fileSize64 = db.UnpackPositions[(size_t)fileIndex + 1] - unpackPos;210const UInt64 folderSize64 = SzAr_GetFolderUnpackSize(&db.db, folderIndex);211const UInt64 fileOffsetInFolder = unpackPos - folderStartUnpackPos;212213openFile->size = (size_t)fileSize64;214openFile->offset = 0;215openFile->mode = mode;216openFile->bytesToSkip = fileOffsetInFolder;217openFile->fileRemaining = fileSize64;218openFile->folderRemaining = folderSize64;219openFile->packRemaining = packSize;220openFile->streamError = false;221openFile->runningCRC = CRC_INIT_VAL;222openFile->crcChecked = false;223openFile->hasFileCRC = SzBitWithVals_Check(&db.CRCs, fileIndex);224openFile->expectedFileCRC = openFile->hasFileCRC ? db.CRCs.Vals[fileIndex] : 0;225226if (OpenArchiveStreamForRead(&openFile->archiveStream_, archivePath) != 0) {227return false;228}229openFile->streamOpened_ = true;230231openFile->lookStreamBuf_ = (Byte *)malloc(SEVENZIP_LOOKBUF_SIZE * sizeof(Byte));232if (!openFile->lookStreamBuf_) {233openFile->Release();234return false;235}236237openFile->lookStream_.bufSize = SEVENZIP_LOOKBUF_SIZE;238openFile->lookStream_.buf = openFile->lookStreamBuf_;239openFile->lookStream_.realStream = &openFile->archiveStream_.vt;240FileInStream_CreateVTable(&openFile->archiveStream_);241LookToRead2_CreateVTable(&openFile->lookStream_, False);242LookToRead2_INIT(&openFile->lookStream_);243244const UInt64 streamPos = db.dataPos + packPositions[0];245if (LookInStream_SeekTo(&openFile->lookStream_.vt, streamPos) != SZ_OK) {246openFile->Release();247return false;248}249250const Byte *props = folderData + coder.PropsOffset;251if (mode == SevenZipOpenFile::Mode::STREAM_LZMA) {252if (LzmaDec_Allocate(&openFile->lzma_, props, coder.PropsSize, alloc) != SZ_OK) {253openFile->Release();254return false;255}256openFile->lzmaAllocated_ = true;257LzmaDec_Init(&openFile->lzma_);258} else if (mode == SevenZipOpenFile::Mode::STREAM_LZMA2) {259if (coder.PropsSize != 1 || Lzma2Dec_Allocate(&openFile->lzma2_, props[0], alloc) != SZ_OK) {260openFile->Release();261return false;262}263openFile->lzma2Allocated_ = true;264Lzma2Dec_Init(&openFile->lzma2_);265}266267return true;268}269270static size_t StreamCopyRead(SevenZipOpenFile *openFile, uint8_t *dest, size_t length) {271size_t produced = 0;272while (produced < length && openFile->packRemaining > 0) {273const size_t wanted = (size_t)std::min<UInt64>((UInt64)(length - produced), openFile->packRemaining);274size_t lookahead = wanted;275const void *inBuf = nullptr;276const SRes lookRes = ILookInStream_Look(&openFile->lookStream_.vt, &inBuf, &lookahead);277if (lookRes != SZ_OK || lookahead == 0) {278openFile->streamError = true;279break;280}281282memcpy(dest + produced, inBuf, lookahead);283if (ILookInStream_Skip(&openFile->lookStream_.vt, lookahead) != SZ_OK) {284openFile->streamError = true;285break;286}287288openFile->packRemaining -= lookahead;289produced += lookahead;290}291return produced;292}293294static size_t StreamDecodeRead(SevenZipOpenFile *openFile, uint8_t *dest, size_t length) {295size_t produced = 0;296while (produced < length && openFile->packRemaining > 0) {297size_t lookahead = (size_t)std::min<UInt64>(SEVENZIP_STREAM_LOOKAHEAD, openFile->packRemaining);298const void *inBuf = nullptr;299const SRes lookRes = ILookInStream_Look(&openFile->lookStream_.vt, &inBuf, &lookahead);300if (lookRes != SZ_OK || lookahead == 0) {301openFile->streamError = true;302break;303}304305SizeT inProcessed = (SizeT)lookahead;306SizeT outProcessed = (SizeT)(length - produced);307ELzmaStatus status = LZMA_STATUS_NOT_SPECIFIED;308SRes decodeRes = SZ_ERROR_UNSUPPORTED;309if (openFile->mode == SevenZipOpenFile::Mode::STREAM_LZMA) {310decodeRes = LzmaDec_DecodeToBuf(&openFile->lzma_, dest + produced, &outProcessed, (const Byte *)inBuf, &inProcessed, LZMA_FINISH_ANY, &status);311} else {312decodeRes = Lzma2Dec_DecodeToBuf(&openFile->lzma2_, dest + produced, &outProcessed, (const Byte *)inBuf, &inProcessed, LZMA_FINISH_ANY, &status);313}314315if (decodeRes != SZ_OK) {316openFile->streamError = true;317break;318}319320if (inProcessed > 0) {321if (ILookInStream_Skip(&openFile->lookStream_.vt, inProcessed) != SZ_OK) {322openFile->streamError = true;323break;324}325openFile->packRemaining -= inProcessed;326}327328produced += outProcessed;329if (outProcessed > 0) {330if (openFile->folderRemaining < outProcessed) {331openFile->streamError = true;332break;333}334openFile->folderRemaining -= outProcessed;335}336337if (inProcessed == 0 && outProcessed == 0) {338openFile->streamError = true;339break;340}341342if (status == LZMA_STATUS_FINISHED_WITH_MARK || status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) {343break;344}345}346return produced;347}348349static size_t StreamReadChunk(SevenZipOpenFile *openFile, uint8_t *dest, size_t length) {350if (length == 0 || openFile->streamError) {351return 0;352}353if (openFile->mode == SevenZipOpenFile::Mode::STREAM_COPY) {354return StreamCopyRead(openFile, dest, length);355}356return StreamDecodeRead(openFile, dest, length);357}358359void *SevenZipFileReader::Alloc(ISzAllocPtr, size_t size) {360return size == 0 ? nullptr : malloc(size);361}362363void SevenZipFileReader::Free(ISzAllocPtr, void *address) {364free(address);365}366367SevenZipFileReader::SevenZipFileReader(const Path &archivePath, const std::string &inArchivePath)368: archivePath_(archivePath), inArchivePath_(inArchivePath) {369if (!inArchivePath_.empty() && inArchivePath_.back() != '/') {370inArchivePath_.push_back('/');371}372373archiveStream_.wres = 0;374File_Construct(&archiveStream_.file);375376allocImp_.Alloc = Alloc;377allocImp_.Free = Free;378allocTempImp_.Alloc = Alloc;379allocTempImp_.Free = Free;380381SzArEx_Init(&db_);382LookToRead2_INIT(&lookStream_);383}384385SevenZipFileReader::~SevenZipFileReader() {386std::lock_guard<std::mutex> guard(lock_);387CloseArchive();388}389390SevenZipFileReader *SevenZipFileReader::Create(const Path &archivePath, std::string_view inArchivePath, bool logErrors) {391SevenZipFileReader *reader = new SevenZipFileReader(archivePath, std::string(inArchivePath));392if (!reader->OpenArchive(logErrors)) {393delete reader;394return nullptr;395}396return reader;397}398399bool SevenZipFileReader::OpenArchive(bool logErrors) {400std::lock_guard<std::mutex> guard(lock_);401402if (valid_) {403return true;404}405406static bool crcTableGenerated = false;407if (!crcTableGenerated) {408CrcGenerateTable();409crcTableGenerated = true;410}411412if (OpenArchiveStreamForRead(&archiveStream_, archivePath_) != 0) {413if (logErrors) {414ERROR_LOG(Log::IO, "Failed to open %s as a 7z file", archivePath_.c_str());415}416return false;417}418419lookStreamBuf_ = (Byte *)malloc(SEVENZIP_LOOKBUF_SIZE * sizeof(Byte));420if (!lookStreamBuf_) {421if (logErrors) {422ERROR_LOG(Log::IO, "Failed to allocate 7z look buffer");423}424File_Close(&archiveStream_.file);425return false;426}427428lookStream_.bufSize = SEVENZIP_LOOKBUF_SIZE;429lookStream_.buf = lookStreamBuf_;430lookStream_.realStream = &archiveStream_.vt;431FileInStream_CreateVTable(&archiveStream_);432LookToRead2_CreateVTable(&lookStream_, False);433LookToRead2_INIT(&lookStream_);434435const SRes res = SzArEx_Open(&db_, &lookStream_.vt, &allocImp_, &allocTempImp_);436if (res != SZ_OK) {437if (logErrors) {438ERROR_LOG(Log::IO, "Failed to parse %s as a 7z archive (error %d)", archivePath_.c_str(), res);439}440CloseArchive();441return false;442}443444valid_ = BuildEntryCache();445if (!valid_ && logErrors) {446ERROR_LOG(Log::IO, "Failed to build file table for 7z archive %s", archivePath_.c_str());447}448return valid_;449}450451void SevenZipFileReader::CloseArchive() {452if (cachedBlock_) {453IAlloc_Free(&allocImp_, cachedBlock_);454cachedBlock_ = nullptr;455cachedBlockSize_ = 0;456blockIndex_ = 0xFFFFFFFF;457}458459entries_.clear();460SzArEx_Free(&db_, &allocImp_);461File_Close(&archiveStream_.file);462463if (lookStreamBuf_) {464free(lookStreamBuf_);465lookStreamBuf_ = nullptr;466}467468valid_ = false;469}470471std::string SevenZipFileReader::ReadEntryPath(UInt32 index) const {472const size_t utf16Length = SzArEx_GetFileNameUtf16(&db_, index, nullptr);473if (utf16Length == 0) {474return std::string();475}476477std::vector<UInt16> utf16(utf16Length);478SzArEx_GetFileNameUtf16(&db_, index, utf16.data());479480size_t actualLen = utf16Length;481if (actualLen > 0 && utf16[actualLen - 1] == 0) {482--actualLen;483}484485std::u16string utf16String;486utf16String.reserve(actualLen);487for (size_t i = 0; i < actualLen; ++i) {488utf16String.push_back((char16_t)utf16[i]);489}490491std::string utf8 = ConvertUCS2ToUTF8(utf16String);492std::replace(utf8.begin(), utf8.end(), '\\', '/');493return utf8;494}495496bool SevenZipFileReader::BuildEntryCache() {497entries_.clear();498entries_.reserve(db_.NumFiles);499500for (UInt32 i = 0; i < db_.NumFiles; ++i) {501SevenZipEntry entry{};502entry.path = ReadEntryPath(i);503entry.isDirectory = SzArEx_IsDir(&db_, i) != 0;504entry.size = (uint64_t)SzArEx_GetFileSize(&db_, i);505entries_.push_back(std::move(entry));506}507508return true;509}510511std::string SevenZipFileReader::ResolvePath(std::string_view path) const {512std::string resolved = join(inArchivePath_, path);513std::replace(resolved.begin(), resolved.end(), '\\', '/');514return resolved;515}516517bool SevenZipFileReader::FindEntry(std::string_view path, UInt32 *index, bool *isDirectory) const {518const std::string target = ResolvePath(path);519520for (UInt32 i = 0; i < (UInt32)entries_.size(); ++i) {521const SevenZipEntry &entry = entries_[i];522if (equalsNoCase(entry.path, target)) {523*index = i;524if (isDirectory) {525*isDirectory = entry.isDirectory;526}527return true;528}529}530return false;531}532533uint8_t *SevenZipFileReader::ExtractFile(UInt32 fileIndex, size_t *size) {534std::lock_guard<std::mutex> guard(lock_);535536if (!valid_) {537return nullptr;538}539540size_t offset = 0;541size_t outSizeProcessed = 0;542SRes res = SzArEx_Extract(543&db_,544&lookStream_.vt,545fileIndex,546&blockIndex_,547&cachedBlock_,548&cachedBlockSize_,549&offset,550&outSizeProcessed,551&allocImp_,552&allocTempImp_);553if (res != SZ_OK) {554ERROR_LOG(Log::IO, "Failed extracting '%s' from 7z archive (error %d)", entries_[fileIndex].path.c_str(), res);555return nullptr;556}557558uint8_t *data = new uint8_t[outSizeProcessed + 1];559memcpy(data, cachedBlock_ + offset, outSizeProcessed);560data[outSizeProcessed] = 0;561*size = outSizeProcessed;562return data;563}564565uint8_t *SevenZipFileReader::ReadFile(std::string_view path, size_t *size) {566UInt32 index = 0;567bool isDirectory = false;568if (!FindEntry(path, &index, &isDirectory) || isDirectory) {569return nullptr;570}571return ExtractFile(index, size);572}573574VFSFileReference *SevenZipFileReader::GetFile(std::string_view path) {575UInt32 index = 0;576bool isDirectory = false;577if (!FindEntry(path, &index, &isDirectory) || isDirectory) {578return nullptr;579}580581SevenZipFileReference *ref = new SevenZipFileReference();582ref->index = index;583return ref;584}585586bool SevenZipFileReader::GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) {587SevenZipFileReference *reference = (SevenZipFileReference *)vfsReference;588if (reference->index >= entries_.size()) {589return false;590}591592const SevenZipEntry &entry = entries_[reference->index];593*fileInfo = File::FileInfo{};594fileInfo->isDirectory = entry.isDirectory;595fileInfo->isWritable = false;596fileInfo->exists = true;597fileInfo->size = entry.size;598return true;599}600601void SevenZipFileReader::ReleaseFile(VFSFileReference *vfsReference) {602delete (SevenZipFileReference *)vfsReference;603}604605VFSOpenFile *SevenZipFileReader::OpenFileForRead(VFSFileReference *vfsReference, size_t *size) {606SevenZipFileReference *reference = (SevenZipFileReference *)vfsReference;607if (reference->index >= entries_.size()) {608return nullptr;609}610if (entries_[reference->index].isDirectory) {611return nullptr;612}613614SevenZipOpenFile *openFile = new SevenZipOpenFile();615if (!InitStreamingOpenFile(archivePath_, db_, &allocImp_, reference->index, openFile)) {616// Fallback for unsupported folder graphs (for example BCJ2).617openFile->mode = SevenZipOpenFile::Mode::MEMORY;618openFile->data = ExtractFile(reference->index, &openFile->size);619if (!openFile->data) {620delete openFile;621return nullptr;622}623}624625openFile->offset = 0;626*size = openFile->size;627return openFile;628}629630void SevenZipFileReader::Rewind(VFSOpenFile *vfsOpenFile) {631SevenZipOpenFile *openFile = (SevenZipOpenFile *)vfsOpenFile;632if (openFile->mode != SevenZipOpenFile::Mode::MEMORY) {633if (!InitStreamingOpenFile(archivePath_, db_, &allocImp_, openFile->fileIndex, openFile)) {634openFile->streamError = true;635}636return;637}638openFile->offset = 0;639}640641size_t SevenZipFileReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) {642SevenZipOpenFile *openFile = (SevenZipOpenFile *)vfsOpenFile;643if (openFile->mode != SevenZipOpenFile::Mode::MEMORY) {644size_t produced = 0;645uint8_t *dest = (uint8_t *)buffer;646647while (produced < length && openFile->fileRemaining > 0 && !openFile->streamError) {648if (openFile->bytesToSkip > 0) {649const size_t skipReq = (size_t)std::min<UInt64>(openFile->bytesToSkip, openFile->skipBuf_.size());650const size_t skipped = StreamReadChunk(openFile, openFile->skipBuf_.data(), skipReq);651if (skipped == 0) {652openFile->streamError = true;653break;654}655openFile->bytesToSkip -= skipped;656continue;657}658659const size_t wanted = (size_t)std::min<UInt64>((UInt64)(length - produced), openFile->fileRemaining);660const size_t got = StreamReadChunk(openFile, dest + produced, wanted);661if (got == 0) {662openFile->streamError = true;663break;664}665666if (openFile->hasFileCRC) {667openFile->runningCRC = CrcUpdate(openFile->runningCRC, dest + produced, got);668}669670produced += got;671openFile->fileRemaining -= got;672openFile->offset += got;673}674675if (openFile->fileRemaining == 0 && openFile->hasFileCRC && !openFile->crcChecked) {676openFile->crcChecked = true;677if (CRC_GET_DIGEST(openFile->runningCRC) != openFile->expectedFileCRC) {678openFile->streamError = true;679ERROR_LOG(Log::IO, "CRC mismatch while streaming '%s' from 7z archive", entries_[openFile->fileIndex].path.c_str());680}681}682683return produced;684}685686if (openFile->offset >= openFile->size) {687return 0;688}689690const size_t remaining = openFile->size - openFile->offset;691const size_t toRead = std::min(length, remaining);692memcpy(buffer, openFile->data + openFile->offset, toRead);693openFile->offset += toRead;694return toRead;695}696697void SevenZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) {698delete (SevenZipOpenFile *)vfsOpenFile;699}700701bool SevenZipFileReader::GetFileListing(std::string_view origPath, std::vector<File::FileInfo> *listing, const char *filter) {702std::string path = ResolvePath(origPath);703if (!path.empty() && path.back() != '/') {704path.push_back('/');705}706707std::set<std::string> filters;708std::string tmp;709if (filter) {710while (*filter) {711if (*filter == ':') {712filters.emplace("." + tmp);713tmp.clear();714} else {715tmp.push_back(*filter);716}717filter++;718}719}720if (!tmp.empty()) {721filters.emplace("." + tmp);722}723724std::set<std::string> files;725std::set<std::string> directories;726for (const auto &entry : entries_) {727if (!startsWith(entry.path, path)) {728continue;729}730731if (entry.path.size() == path.size()) {732continue;733}734735std::string_view relative = std::string_view(entry.path).substr(path.size());736size_t slashPos = relative.find('/');737if (slashPos != std::string::npos) {738directories.emplace(std::string(relative.substr(0, slashPos)));739} else if (!entry.isDirectory) {740files.emplace(std::string(relative));741}742}743744listing->clear();745746const std::string relativePath = path.substr(inArchivePath_.size());747listing->reserve(directories.size() + files.size());748749for (const auto &dir : directories) {750File::FileInfo info;751info.name = dir;752info.fullName = Path(relativePath + dir);753info.exists = true;754info.isWritable = false;755info.isDirectory = true;756listing->push_back(info);757}758759for (const auto &file : files) {760File::FileInfo info;761info.name = file;762info.fullName = Path(relativePath + file);763info.exists = true;764info.isWritable = false;765info.isDirectory = false;766if (filter) {767std::string ext = info.fullName.GetFileExtension();768if (filters.find(ext) == filters.end()) {769continue;770}771}772listing->push_back(info);773}774775std::sort(listing->begin(), listing->end());776return !listing->empty();777}778779bool SevenZipFileReader::GetFileInfo(std::string_view path, File::FileInfo *info) {780*info = File::FileInfo{};781info->fullName = Path(path);782info->isWritable = false;783784UInt32 index = 0;785bool isDirectory = false;786if (FindEntry(path, &index, &isDirectory)) {787const SevenZipEntry &entry = entries_[index];788info->exists = true;789info->isDirectory = entry.isDirectory;790info->size = entry.size;791return true;792}793794const std::string base = ResolvePath(path);795const std::string prefix = base.empty() || base.back() == '/' ? base : base + "/";796for (const auto &entry : entries_) {797if (startsWith(entry.path, prefix)) {798info->exists = true;799info->isDirectory = true;800info->size = 0;801return true;802}803}804805info->exists = false;806return false;807}808809810