Files
ppsspp/Common/File/VFS/SevenZipFileReader.cpp
T
libretroadmin f1208a7a5a Make threading lock-free
The core and the headless tools no longer use mutexes, condition variables,
spinlocks, std::atomic, std::thread or volatile flags. Threads hand off through
libretro-common's retro_atomic and rthreads plus a few small pieces on top of
them in Common/Thread: Thread, MpscQueue, EventCounter, ParkingLot waits and
ReaderGate. Every wait blocks until it's woken; nothing polls on a timer.

State that only one thread touches just loses its lock. The PSP filesystem,
sceIo and the savedata dialog now belong to the emulation thread: async reads
and writes run when they're queued and complete later in emulated time, and
the IO and savedata threads are gone. The friend finder only talks to the
adhoc server and hands what it gets to the emulation thread. GPU init always
runs on the thread that starts the game.

The Vulkan render and compile queues, pipeline and render pass caches, delete
list and SPIR-V cache, the GL render queue, the software renderer's jit caches,
the file loader caches, the web debugger, UPnP, input, savestates, logging and
the GE debugger all hand off through queues and atomics. The IR jit's spinning
profiler thread goes.

A CPU thread with nothing to run now sleeps until work is queued instead of
spinning. Headless with --debugger waits for a client while stopped, and ends
the run once every client that connected has left. Web server shutdown keeps
servicing the CPU queue until the server has finished.
2026-10-09 04:56:16 +02:00

811 lines
22 KiB
C++

#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <set>
#ifndef _WIN32
#include <unistd.h>
#endif
#define NOMINMAX // 7z includes windows.h for some reason.
#include "ext/lzma-sdk/7z.h"
#include "ext/lzma-sdk/7zCrc.h"
#include "ext/lzma-sdk/7zFile.h"
#include "ext/lzma-sdk/Lzma2Dec.h"
#include "ext/lzma-sdk/LzmaDec.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
#include "Common/File/VFS/SevenZipFileReader.h"
#include "Common/Thread/ParkingLot.h"
static constexpr size_t SEVENZIP_LOOKBUF_SIZE = 1 << 14;
static constexpr size_t SEVENZIP_STREAM_LOOKAHEAD = 1 << 18;
static constexpr size_t SEVENZIP_SKIPBUF_SIZE = 1 << 15;
static constexpr UInt32 SEVENZIP_METHOD_COPY = 0;
static constexpr UInt32 SEVENZIP_METHOD_LZMA = 0x30101;
static constexpr UInt32 SEVENZIP_METHOD_LZMA2 = 0x21;
static WRes OpenArchiveStreamForRead(CFileInStream *stream, const Path &archivePath) {
#ifdef USE_WINDOWS_FILE
return InFile_OpenW(&stream->file, archivePath.ToWString().c_str());
#else
#if defined(HAVE_LIBRETRO_VFS)
#if PPSSPP_PLATFORM(ANDROID)
if (archivePath.Type() == PathType::CONTENT_URI) {
int fd = File::OpenFD(archivePath, File::OPEN_READ);
if (fd < 0) {
return 1;
}
stream->file.fd = fd;
return 0;
}
#endif
return InFile_Open(&stream->file, archivePath.ToString().c_str());
#else
FILE *file = File::OpenCFile(archivePath, "rb");
if (!file) {
return 1;
}
#if defined(USE_FOPEN)
stream->file.file = file;
return 0;
#else
const int openedFd = fileno(file);
if (openedFd < 0) {
fclose(file);
return 1;
}
const int fd = dup(openedFd);
fclose(file);
if (fd < 0) {
return 1;
}
stream->file.fd = fd;
return 0;
#endif
#endif
#endif
}
class SevenZipFileReference : public VFSFileReference {
public:
UInt32 index = 0;
};
class SevenZipOpenFile : public VFSOpenFile {
public:
enum class Mode {
MEMORY,
STREAM_COPY,
STREAM_LZMA,
STREAM_LZMA2,
};
SevenZipOpenFile() {
LzmaDec_CONSTRUCT(&lzma_);
Lzma2Dec_CONSTRUCT(&lzma2_);
archiveStream_.wres = 0;
File_Construct(&archiveStream_.file);
LookToRead2_INIT(&lookStream_);
}
~SevenZipOpenFile() override {
Release();
delete[] data;
}
void Release() {
if (lookStreamBuf_) {
free(lookStreamBuf_);
lookStreamBuf_ = nullptr;
}
if (lzmaAllocated_ && alloc_) {
LzmaDec_Free(&lzma_, alloc_);
lzmaAllocated_ = false;
}
if (lzma2Allocated_ && alloc_) {
Lzma2Dec_Free(&lzma2_, alloc_);
lzma2Allocated_ = false;
}
if (streamOpened_) {
File_Close(&archiveStream_.file);
streamOpened_ = false;
}
}
Mode mode = Mode::MEMORY;
uint8_t *data = nullptr;
size_t size = 0;
size_t offset = 0;
UInt32 fileIndex = 0;
UInt64 bytesToSkip = 0;
UInt64 fileRemaining = 0;
UInt64 packRemaining = 0;
UInt64 folderRemaining = 0;
bool streamError = false;
bool hasFileCRC = false;
UInt32 expectedFileCRC = 0;
UInt32 runningCRC = CRC_INIT_VAL;
bool crcChecked = false;
ISzAlloc *alloc_ = nullptr;
CFileInStream archiveStream_;
CLookToRead2 lookStream_;
Byte *lookStreamBuf_ = nullptr;
bool streamOpened_ = false;
CLzmaDec lzma_;
CLzma2Dec lzma2_;
bool lzmaAllocated_ = false;
bool lzma2Allocated_ = false;
std::array<uint8_t, SEVENZIP_SKIPBUF_SIZE> skipBuf_{};
};
static bool InitStreamingOpenFile(
const Path &archivePath,
const CSzArEx &db,
ISzAlloc *alloc,
UInt32 fileIndex,
SevenZipOpenFile *openFile) {
openFile->Release();
openFile->mode = SevenZipOpenFile::Mode::MEMORY;
openFile->alloc_ = alloc;
openFile->fileIndex = fileIndex;
const UInt32 folderIndex = db.FileToFolder[fileIndex];
if (folderIndex == (UInt32)-1) {
openFile->mode = SevenZipOpenFile::Mode::STREAM_COPY;
openFile->bytesToSkip = 0;
openFile->fileRemaining = 0;
openFile->folderRemaining = 0;
openFile->packRemaining = 0;
openFile->size = 0;
openFile->offset = 0;
return true;
}
const Byte *folderData = db.db.CodersData + db.db.FoCodersOffsets[folderIndex];
CSzData sd;
sd.Data = folderData;
sd.Size = db.db.FoCodersOffsets[(size_t)folderIndex + 1] - db.db.FoCodersOffsets[folderIndex];
CSzFolder folder;
if (SzGetNextFolderItem(&folder, &sd) != SZ_OK || sd.Size != 0) {
return false;
}
if (folder.NumCoders != 1 || folder.NumPackStreams != 1 || folder.NumBonds != 0 || folder.PackStreams[0] != 0) {
return false;
}
const CSzCoderInfo &coder = folder.Coders[0];
if (coder.NumStreams != 1) {
return false;
}
SevenZipOpenFile::Mode mode = SevenZipOpenFile::Mode::MEMORY;
if (coder.MethodID == SEVENZIP_METHOD_COPY) {
mode = SevenZipOpenFile::Mode::STREAM_COPY;
} else if (coder.MethodID == SEVENZIP_METHOD_LZMA) {
mode = SevenZipOpenFile::Mode::STREAM_LZMA;
} else if (coder.MethodID == SEVENZIP_METHOD_LZMA2) {
mode = SevenZipOpenFile::Mode::STREAM_LZMA2;
} else {
return false;
}
UInt64 packSize = 0;
const UInt64 *packPositions = db.db.PackPositions + db.db.FoStartPackStreamIndex[folderIndex];
packSize = packPositions[1] - packPositions[0];
const UInt64 unpackPos = db.UnpackPositions[fileIndex];
const UInt64 folderStartUnpackPos = db.UnpackPositions[db.FolderToFile[folderIndex]];
const UInt64 fileSize64 = db.UnpackPositions[(size_t)fileIndex + 1] - unpackPos;
const UInt64 folderSize64 = SzAr_GetFolderUnpackSize(&db.db, folderIndex);
const UInt64 fileOffsetInFolder = unpackPos - folderStartUnpackPos;
openFile->size = (size_t)fileSize64;
openFile->offset = 0;
openFile->mode = mode;
openFile->bytesToSkip = fileOffsetInFolder;
openFile->fileRemaining = fileSize64;
openFile->folderRemaining = folderSize64;
openFile->packRemaining = packSize;
openFile->streamError = false;
openFile->runningCRC = CRC_INIT_VAL;
openFile->crcChecked = false;
openFile->hasFileCRC = SzBitWithVals_Check(&db.CRCs, fileIndex);
openFile->expectedFileCRC = openFile->hasFileCRC ? db.CRCs.Vals[fileIndex] : 0;
if (OpenArchiveStreamForRead(&openFile->archiveStream_, archivePath) != 0) {
return false;
}
openFile->streamOpened_ = true;
openFile->lookStreamBuf_ = (Byte *)malloc(SEVENZIP_LOOKBUF_SIZE * sizeof(Byte));
if (!openFile->lookStreamBuf_) {
openFile->Release();
return false;
}
openFile->lookStream_.bufSize = SEVENZIP_LOOKBUF_SIZE;
openFile->lookStream_.buf = openFile->lookStreamBuf_;
openFile->lookStream_.realStream = &openFile->archiveStream_.vt;
FileInStream_CreateVTable(&openFile->archiveStream_);
LookToRead2_CreateVTable(&openFile->lookStream_, False);
LookToRead2_INIT(&openFile->lookStream_);
const UInt64 streamPos = db.dataPos + packPositions[0];
if (LookInStream_SeekTo(&openFile->lookStream_.vt, streamPos) != SZ_OK) {
openFile->Release();
return false;
}
const Byte *props = folderData + coder.PropsOffset;
if (mode == SevenZipOpenFile::Mode::STREAM_LZMA) {
if (LzmaDec_Allocate(&openFile->lzma_, props, coder.PropsSize, alloc) != SZ_OK) {
openFile->Release();
return false;
}
openFile->lzmaAllocated_ = true;
LzmaDec_Init(&openFile->lzma_);
} else if (mode == SevenZipOpenFile::Mode::STREAM_LZMA2) {
if (coder.PropsSize != 1 || Lzma2Dec_Allocate(&openFile->lzma2_, props[0], alloc) != SZ_OK) {
openFile->Release();
return false;
}
openFile->lzma2Allocated_ = true;
Lzma2Dec_Init(&openFile->lzma2_);
}
return true;
}
static size_t StreamCopyRead(SevenZipOpenFile *openFile, uint8_t *dest, size_t length) {
size_t produced = 0;
while (produced < length && openFile->packRemaining > 0) {
const size_t wanted = (size_t)std::min<UInt64>((UInt64)(length - produced), openFile->packRemaining);
size_t lookahead = wanted;
const void *inBuf = nullptr;
const SRes lookRes = ILookInStream_Look(&openFile->lookStream_.vt, &inBuf, &lookahead);
if (lookRes != SZ_OK || lookahead == 0) {
openFile->streamError = true;
break;
}
memcpy(dest + produced, inBuf, lookahead);
if (ILookInStream_Skip(&openFile->lookStream_.vt, lookahead) != SZ_OK) {
openFile->streamError = true;
break;
}
openFile->packRemaining -= lookahead;
produced += lookahead;
}
return produced;
}
static size_t StreamDecodeRead(SevenZipOpenFile *openFile, uint8_t *dest, size_t length) {
size_t produced = 0;
while (produced < length && openFile->packRemaining > 0) {
size_t lookahead = (size_t)std::min<UInt64>(SEVENZIP_STREAM_LOOKAHEAD, openFile->packRemaining);
const void *inBuf = nullptr;
const SRes lookRes = ILookInStream_Look(&openFile->lookStream_.vt, &inBuf, &lookahead);
if (lookRes != SZ_OK || lookahead == 0) {
openFile->streamError = true;
break;
}
SizeT inProcessed = (SizeT)lookahead;
SizeT outProcessed = (SizeT)(length - produced);
ELzmaStatus status = LZMA_STATUS_NOT_SPECIFIED;
SRes decodeRes = SZ_ERROR_UNSUPPORTED;
if (openFile->mode == SevenZipOpenFile::Mode::STREAM_LZMA) {
decodeRes = LzmaDec_DecodeToBuf(&openFile->lzma_, dest + produced, &outProcessed, (const Byte *)inBuf, &inProcessed, LZMA_FINISH_ANY, &status);
} else {
decodeRes = Lzma2Dec_DecodeToBuf(&openFile->lzma2_, dest + produced, &outProcessed, (const Byte *)inBuf, &inProcessed, LZMA_FINISH_ANY, &status);
}
if (decodeRes != SZ_OK) {
openFile->streamError = true;
break;
}
if (inProcessed > 0) {
if (ILookInStream_Skip(&openFile->lookStream_.vt, inProcessed) != SZ_OK) {
openFile->streamError = true;
break;
}
openFile->packRemaining -= inProcessed;
}
produced += outProcessed;
if (outProcessed > 0) {
if (openFile->folderRemaining < outProcessed) {
openFile->streamError = true;
break;
}
openFile->folderRemaining -= outProcessed;
}
if (inProcessed == 0 && outProcessed == 0) {
openFile->streamError = true;
break;
}
if (status == LZMA_STATUS_FINISHED_WITH_MARK || status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) {
break;
}
}
return produced;
}
static size_t StreamReadChunk(SevenZipOpenFile *openFile, uint8_t *dest, size_t length) {
if (length == 0 || openFile->streamError) {
return 0;
}
if (openFile->mode == SevenZipOpenFile::Mode::STREAM_COPY) {
return StreamCopyRead(openFile, dest, length);
}
return StreamDecodeRead(openFile, dest, length);
}
void *SevenZipFileReader::Alloc(ISzAllocPtr, size_t size) {
return size == 0 ? nullptr : malloc(size);
}
void SevenZipFileReader::Free(ISzAllocPtr, void *address) {
free(address);
}
SevenZipFileReader::SevenZipFileReader(const Path &archivePath, const std::string &inArchivePath)
: archivePath_(archivePath), inArchivePath_(inArchivePath) {
if (!inArchivePath_.empty() && inArchivePath_.back() != '/') {
inArchivePath_.push_back('/');
}
archiveStream_.wres = 0;
File_Construct(&archiveStream_.file);
allocImp_.Alloc = Alloc;
allocImp_.Free = Free;
allocTempImp_.Alloc = Alloc;
allocTempImp_.Free = Free;
SzArEx_Init(&db_);
LookToRead2_INIT(&lookStream_);
}
SevenZipFileReader::~SevenZipFileReader() {
CloseArchive();
}
SevenZipFileReader *SevenZipFileReader::Create(const Path &archivePath, std::string_view inArchivePath, bool logErrors) {
SevenZipFileReader *reader = new SevenZipFileReader(archivePath, std::string(inArchivePath));
if (!reader->OpenArchive(logErrors)) {
delete reader;
return nullptr;
}
return reader;
}
bool SevenZipFileReader::OpenArchive(bool logErrors) {
if (valid_) {
return true;
}
// The CRC table is process-wide: 0 = not built, 1 = building, 2 = ready.
static retro_atomic_int_t crcTableState{ 0 };
if (retro_atomic_cas_int(&crcTableState, 0, 1)) {
CrcGenerateTable();
retro_atomic_store_release_int(&crcTableState, 2);
ParkingLotNotify(&crcTableState);
} else {
ParkingLotWait(&crcTableState, [] {
return retro_atomic_load_acquire_int(&crcTableState) == 2;
});
}
if (OpenArchiveStreamForRead(&archiveStream_, archivePath_) != 0) {
if (logErrors) {
ERROR_LOG(Log::IO, "Failed to open %s as a 7z file", archivePath_.c_str());
}
return false;
}
lookStreamBuf_ = (Byte *)malloc(SEVENZIP_LOOKBUF_SIZE * sizeof(Byte));
if (!lookStreamBuf_) {
if (logErrors) {
ERROR_LOG(Log::IO, "Failed to allocate 7z look buffer");
}
File_Close(&archiveStream_.file);
return false;
}
lookStream_.bufSize = SEVENZIP_LOOKBUF_SIZE;
lookStream_.buf = lookStreamBuf_;
lookStream_.realStream = &archiveStream_.vt;
FileInStream_CreateVTable(&archiveStream_);
LookToRead2_CreateVTable(&lookStream_, False);
LookToRead2_INIT(&lookStream_);
const SRes res = SzArEx_Open(&db_, &lookStream_.vt, &allocImp_, &allocTempImp_);
if (res != SZ_OK) {
if (logErrors) {
ERROR_LOG(Log::IO, "Failed to parse %s as a 7z archive (error %d)", archivePath_.c_str(), res);
}
CloseArchive();
return false;
}
valid_ = BuildEntryCache();
if (!valid_ && logErrors) {
ERROR_LOG(Log::IO, "Failed to build file table for 7z archive %s", archivePath_.c_str());
}
return valid_;
}
void SevenZipFileReader::CloseArchive() {
if (cachedBlock_) {
IAlloc_Free(&allocImp_, cachedBlock_);
cachedBlock_ = nullptr;
cachedBlockSize_ = 0;
blockIndex_ = 0xFFFFFFFF;
}
entries_.clear();
SzArEx_Free(&db_, &allocImp_);
File_Close(&archiveStream_.file);
if (lookStreamBuf_) {
free(lookStreamBuf_);
lookStreamBuf_ = nullptr;
}
valid_ = false;
}
std::string SevenZipFileReader::ReadEntryPath(UInt32 index) const {
const size_t utf16Length = SzArEx_GetFileNameUtf16(&db_, index, nullptr);
if (utf16Length == 0) {
return std::string();
}
std::vector<UInt16> utf16(utf16Length);
SzArEx_GetFileNameUtf16(&db_, index, utf16.data());
size_t actualLen = utf16Length;
if (actualLen > 0 && utf16[actualLen - 1] == 0) {
--actualLen;
}
std::u16string utf16String;
utf16String.reserve(actualLen);
for (size_t i = 0; i < actualLen; ++i) {
utf16String.push_back((char16_t)utf16[i]);
}
std::string utf8 = ConvertUCS2ToUTF8(utf16String);
std::replace(utf8.begin(), utf8.end(), '\\', '/');
return utf8;
}
bool SevenZipFileReader::BuildEntryCache() {
entries_.clear();
entries_.reserve(db_.NumFiles);
for (UInt32 i = 0; i < db_.NumFiles; ++i) {
SevenZipEntry entry{};
entry.path = ReadEntryPath(i);
entry.isDirectory = SzArEx_IsDir(&db_, i) != 0;
entry.size = (uint64_t)SzArEx_GetFileSize(&db_, i);
entries_.push_back(std::move(entry));
}
return true;
}
std::string SevenZipFileReader::ResolvePath(std::string_view path) const {
std::string resolved = join(inArchivePath_, path);
std::replace(resolved.begin(), resolved.end(), '\\', '/');
return resolved;
}
bool SevenZipFileReader::FindEntry(std::string_view path, UInt32 *index, bool *isDirectory) const {
const std::string target = ResolvePath(path);
for (UInt32 i = 0; i < (UInt32)entries_.size(); ++i) {
const SevenZipEntry &entry = entries_[i];
if (equalsNoCase(entry.path, target)) {
*index = i;
if (isDirectory) {
*isDirectory = entry.isDirectory;
}
return true;
}
}
return false;
}
uint8_t *SevenZipFileReader::ExtractFile(UInt32 fileIndex, size_t *size) {
if (!valid_) {
return nullptr;
}
size_t offset = 0;
size_t outSizeProcessed = 0;
SRes res = SzArEx_Extract(
&db_,
&lookStream_.vt,
fileIndex,
&blockIndex_,
&cachedBlock_,
&cachedBlockSize_,
&offset,
&outSizeProcessed,
&allocImp_,
&allocTempImp_);
if (res != SZ_OK) {
ERROR_LOG(Log::IO, "Failed extracting '%s' from 7z archive (error %d)", entries_[fileIndex].path.c_str(), res);
return nullptr;
}
uint8_t *data = new uint8_t[outSizeProcessed + 1];
memcpy(data, cachedBlock_ + offset, outSizeProcessed);
data[outSizeProcessed] = 0;
*size = outSizeProcessed;
return data;
}
uint8_t *SevenZipFileReader::ReadFile(std::string_view path, size_t *size) {
UInt32 index = 0;
bool isDirectory = false;
if (!FindEntry(path, &index, &isDirectory) || isDirectory) {
return nullptr;
}
return ExtractFile(index, size);
}
VFSFileReference *SevenZipFileReader::GetFile(std::string_view path) {
UInt32 index = 0;
bool isDirectory = false;
if (!FindEntry(path, &index, &isDirectory) || isDirectory) {
return nullptr;
}
SevenZipFileReference *ref = new SevenZipFileReference();
ref->index = index;
return ref;
}
bool SevenZipFileReader::GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) {
SevenZipFileReference *reference = (SevenZipFileReference *)vfsReference;
if (reference->index >= entries_.size()) {
return false;
}
const SevenZipEntry &entry = entries_[reference->index];
*fileInfo = File::FileInfo{};
fileInfo->isDirectory = entry.isDirectory;
fileInfo->isWritable = false;
fileInfo->exists = true;
fileInfo->size = entry.size;
return true;
}
void SevenZipFileReader::ReleaseFile(VFSFileReference *vfsReference) {
delete (SevenZipFileReference *)vfsReference;
}
VFSOpenFile *SevenZipFileReader::OpenFileForRead(VFSFileReference *vfsReference, size_t *size) {
SevenZipFileReference *reference = (SevenZipFileReference *)vfsReference;
if (reference->index >= entries_.size()) {
return nullptr;
}
if (entries_[reference->index].isDirectory) {
return nullptr;
}
SevenZipOpenFile *openFile = new SevenZipOpenFile();
if (!InitStreamingOpenFile(archivePath_, db_, &allocImp_, reference->index, openFile)) {
// Fallback for unsupported folder graphs (for example BCJ2).
openFile->mode = SevenZipOpenFile::Mode::MEMORY;
openFile->data = ExtractFile(reference->index, &openFile->size);
if (!openFile->data) {
delete openFile;
return nullptr;
}
}
openFile->offset = 0;
*size = openFile->size;
return openFile;
}
void SevenZipFileReader::Rewind(VFSOpenFile *vfsOpenFile) {
SevenZipOpenFile *openFile = (SevenZipOpenFile *)vfsOpenFile;
if (openFile->mode != SevenZipOpenFile::Mode::MEMORY) {
if (!InitStreamingOpenFile(archivePath_, db_, &allocImp_, openFile->fileIndex, openFile)) {
openFile->streamError = true;
}
return;
}
openFile->offset = 0;
}
size_t SevenZipFileReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) {
SevenZipOpenFile *openFile = (SevenZipOpenFile *)vfsOpenFile;
if (openFile->mode != SevenZipOpenFile::Mode::MEMORY) {
size_t produced = 0;
uint8_t *dest = (uint8_t *)buffer;
while (produced < length && openFile->fileRemaining > 0 && !openFile->streamError) {
if (openFile->bytesToSkip > 0) {
const size_t skipReq = (size_t)std::min<UInt64>(openFile->bytesToSkip, openFile->skipBuf_.size());
const size_t skipped = StreamReadChunk(openFile, openFile->skipBuf_.data(), skipReq);
if (skipped == 0) {
openFile->streamError = true;
break;
}
openFile->bytesToSkip -= skipped;
continue;
}
const size_t wanted = (size_t)std::min<UInt64>((UInt64)(length - produced), openFile->fileRemaining);
const size_t got = StreamReadChunk(openFile, dest + produced, wanted);
if (got == 0) {
openFile->streamError = true;
break;
}
if (openFile->hasFileCRC) {
openFile->runningCRC = CrcUpdate(openFile->runningCRC, dest + produced, got);
}
produced += got;
openFile->fileRemaining -= got;
openFile->offset += got;
}
if (openFile->fileRemaining == 0 && openFile->hasFileCRC && !openFile->crcChecked) {
openFile->crcChecked = true;
if (CRC_GET_DIGEST(openFile->runningCRC) != openFile->expectedFileCRC) {
openFile->streamError = true;
ERROR_LOG(Log::IO, "CRC mismatch while streaming '%s' from 7z archive", entries_[openFile->fileIndex].path.c_str());
}
}
return produced;
}
if (openFile->offset >= openFile->size) {
return 0;
}
const size_t remaining = openFile->size - openFile->offset;
const size_t toRead = std::min(length, remaining);
memcpy(buffer, openFile->data + openFile->offset, toRead);
openFile->offset += toRead;
return toRead;
}
void SevenZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) {
delete (SevenZipOpenFile *)vfsOpenFile;
}
bool SevenZipFileReader::GetFileListing(std::string_view origPath, std::vector<File::FileInfo> *listing, const char *filter) {
std::string path = ResolvePath(origPath);
if (!path.empty() && path.back() != '/') {
path.push_back('/');
}
std::set<std::string> filters;
std::string tmp;
if (filter) {
while (*filter) {
if (*filter == ':') {
filters.emplace("." + tmp);
tmp.clear();
} else {
tmp.push_back(*filter);
}
filter++;
}
}
if (!tmp.empty()) {
filters.emplace("." + tmp);
}
std::set<std::string> files;
std::set<std::string> directories;
for (const auto &entry : entries_) {
if (!startsWith(entry.path, path)) {
continue;
}
if (entry.path.size() == path.size()) {
continue;
}
std::string_view relative = std::string_view(entry.path).substr(path.size());
size_t slashPos = relative.find('/');
if (slashPos != std::string::npos) {
directories.emplace(std::string(relative.substr(0, slashPos)));
} else if (!entry.isDirectory) {
files.emplace(std::string(relative));
}
}
listing->clear();
const std::string relativePath = path.substr(inArchivePath_.size());
listing->reserve(directories.size() + files.size());
for (const auto &dir : directories) {
File::FileInfo info;
info.name = dir;
info.fullName = Path(relativePath + dir);
info.exists = true;
info.isWritable = false;
info.isDirectory = true;
listing->push_back(info);
}
for (const auto &file : files) {
File::FileInfo info;
info.name = file;
info.fullName = Path(relativePath + file);
info.exists = true;
info.isWritable = false;
info.isDirectory = false;
if (filter) {
std::string ext = info.fullName.GetFileExtension();
if (filters.find(ext) == filters.end()) {
continue;
}
}
listing->push_back(info);
}
std::sort(listing->begin(), listing->end());
return !listing->empty();
}
bool SevenZipFileReader::GetFileInfo(std::string_view path, File::FileInfo *info) {
*info = File::FileInfo{};
info->fullName = Path(path);
info->isWritable = false;
UInt32 index = 0;
bool isDirectory = false;
if (FindEntry(path, &index, &isDirectory)) {
const SevenZipEntry &entry = entries_[index];
info->exists = true;
info->isDirectory = entry.isDirectory;
info->size = entry.size;
return true;
}
const std::string base = ResolvePath(path);
const std::string prefix = base.empty() || base.back() == '/' ? base : base + "/";
for (const auto &entry : entries_) {
if (startsWith(entry.path, prefix)) {
info->exists = true;
info->isDirectory = true;
info->size = 0;
return true;
}
}
info->exists = false;
return false;
}