From 35aff7eaf97056d0074a1519937ec86a96df15f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 1 May 2023 23:20:38 +0200 Subject: [PATCH 1/4] Fix logic in ZipFileReader file listing --- Common/File/VFS/ZipFileReader.cpp | 21 ++++++++++++--------- Common/StringUtils.h | 4 ++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 8935fafe5513..c789ff899fc1 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -132,26 +132,29 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vector &files, std::set &directories) { size_t pathlen = strlen(path); - if (path[pathlen - 1] == '/') - pathlen--; + if (pathlen == 1 && path[0] == '/') { + // Root. We simply use a zero length string. + pathlen = 0; + } std::lock_guard guard(lock_); int numFiles = zip_get_num_files(zip_file_); for (int i = 0; i < numFiles; i++) { const char* name = zip_get_name(zip_file_, i, 0); if (!name) - continue; + continue; // shouldn't happen, I think if (!memcmp(name, path, pathlen)) { - // The prefix is right. Let's see if this is a file or path. const char *slashPos = strchr(name + pathlen + 1, '/'); if (slashPos != 0) { - // A directory. - std::string dirName = std::string(name + pathlen + 1, slashPos - (name + pathlen + 1)); + // A directory. Let's pick off the only part we care about. + int offset = pathlen; + std::string dirName = std::string(name + offset, slashPos - (name + offset)); directories.insert(dirName); - } else if (name[pathlen] == '/') { - const char *fn = name + pathlen + 1; + } else { + // It's a file. + const char *fn = name + pathlen; files.insert(std::string(fn)); - } // else, it was a file with the same prefix as the path. like langregion.ini next to lang/. + } } } } diff --git a/Common/StringUtils.h b/Common/StringUtils.h index ea73b7b2f63a..5e68baec6ed2 100644 --- a/Common/StringUtils.h +++ b/Common/StringUtils.h @@ -61,6 +61,10 @@ inline bool endsWithNoCase(const std::string &str, const std::string &what) { return strncasecmp(str.c_str() + offset, what.c_str(), what.size()) == 0; } +inline bool equalsNoCase(const std::string &str, const char *what) { + return strcasecmp(str.c_str(), what) == 0; +} + void DataToHexString(const uint8_t *data, size_t size, std::string *output); void DataToHexString(int indent, uint32_t startAddr, const uint8_t* data, size_t size, std::string* output); From d10fae7274851b936b3a0389e92a51a2ca3321ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 1 May 2023 23:20:54 +0200 Subject: [PATCH 2/4] Scan the root of loaded texture packs to find all the hash-named files. --- GPU/Common/TextureReplacer.cpp | 77 +++++++++++++++++++++++----------- GPU/Common/TextureReplacer.h | 2 +- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index d133105cc60f..090f6a5e290c 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -140,7 +140,7 @@ bool TextureReplacer::LoadIni() { bool iniLoaded = ini.LoadFromVFS(*dir, INI_FILENAME); if (iniLoaded) { - if (!LoadIniValues(ini)) { + if (!LoadIniValues(ini, dir)) { delete dir; return false; } @@ -160,7 +160,7 @@ bool TextureReplacer::LoadIni() { } INFO_LOG(G3D, "Loading extra texture ini: %s", overrideFilename.c_str()); - if (!LoadIniValues(overrideIni, true)) { + if (!LoadIniValues(overrideIni, dir, true)) { delete dir; return false; } @@ -195,7 +195,7 @@ bool TextureReplacer::LoadIni() { return true; } -bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { +bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverride) { auto options = ini.GetOrCreateSection("options"); std::string hash; options->Get("hash", &hash, ""); @@ -231,13 +231,14 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { } bool filenameWarning = false; + + std::map> filenameMap; + if (ini.HasSection("hashes")) { auto hashes = ini.GetOrCreateSection("hashes")->ToMap(); // Format: hashname = filename.png bool checkFilenames = g_Config.bSaveNewTextures && !g_Config.bIgnoreTextureFilenames && !vfsIsZip_; - std::map> filenameMap; - for (const auto &item : hashes) { ReplacementCacheKey key(0, 0); int level = 0; // sscanf might fail to pluck the level, but that's ok, we default to 0. sscanf doesn't write to non-matched outputs. @@ -256,31 +257,57 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { ERROR_LOG(G3D, "Unsupported syntax under [hashes]: %s", item.first.c_str()); } } + } - // Now, translate the filenameMap to the final aliasMap. - for (auto &pair : filenameMap) { - std::string alias; - int mipIndex = 0; - for (auto &level : pair.second) { - if (level.first == mipIndex) { - alias += level.second + "|"; - mipIndex++; - } else { - WARN_LOG(G3D, "Non-sequential mip index %d, breaking. filenames=%s", level.first, level.second.c_str()); - break; - } + // Scan the root of the texture folder/zip and preinitialize the hash map. + std::vector filesInRoot; + dir->GetFileListing("/", &filesInRoot, nullptr); + for (auto file : filesInRoot) { + if (file.isDirectory) + continue; + if (file.name.empty() || file.name[0] == '.') + continue; + Path path(file.name); + std::string ext = path.GetFileExtension(); + + std::string hash = file.name.substr(0, file.name.size() - ext.size()); + if (!((hash.size() >= 26 && hash.size() <= 27 && hash[24] == '_') || hash.size() == 24)) { + continue; + } + // OK, it's hash-like enough to try to parse it into the map. + if (equalsNoCase(ext, ".ktx2") || equalsNoCase(ext, ".png") || equalsNoCase(ext, ".dds")) { + ReplacementCacheKey key(0, 0); + int level = 0; // sscanf might fail to pluck the level, but that's ok, we default to 0. sscanf doesn't write to non-matched outputs. + if (sscanf(hash.c_str(), "%16llx%8x_%d", &key.cachekey, &key.hash, &level) >= 1) { + INFO_LOG(G3D, "hash-like file in root, adding: %s", file.name.c_str()); + filenameMap[key][level] = file.name; } - if (alias == "|") { - alias = ""; // marker for no replacement + } + } + + // Now, translate the filenameMap to the final aliasMap. + for (auto &pair : filenameMap) { + std::string alias; + int mipIndex = 0; + for (auto &level : pair.second) { + if (level.first == mipIndex) { + alias += level.second + "|"; + mipIndex++; + } else { + WARN_LOG(G3D, "Non-sequential mip index %d, breaking. filenames=%s", level.first, level.second.c_str()); + break; } - // Replace any '\' with '/', to be safe and consistent. Since these are from the ini file, we do this on all platforms. - for (auto &c : alias) { - if (c == '\\') { - c = '/'; - } + } + if (alias == "|") { + alias = ""; // marker for no replacement + } + // Replace any '\' with '/', to be safe and consistent. Since these are from the ini file, we do this on all platforms. + for (auto &c : alias) { + if (c == '\\') { + c = '/'; } - aliases_[pair.first] = alias; } + aliases_[pair.first] = alias; } if (filenameWarning) { diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index 0f22a5b3a53c..cbdf50953063 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -128,7 +128,7 @@ class TextureReplacer { bool FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering); bool LoadIni(); - bool LoadIniValues(IniFile &ini, bool isOverride = false); + bool LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverride = false); void ParseHashRange(const std::string &key, const std::string &value); void ParseFiltering(const std::string &key, const std::string &value); void ParseReduceHashRange(const std::string& key, const std::string& value); From ee7e8d7c06f5490839db814a2854f03a1da45556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 2 May 2023 11:35:45 +0200 Subject: [PATCH 3/4] Add a unit test, fix listing zip directories --- CMakeLists.txt | 1 + Common/File/VFS/VFS.h | 1 + Common/File/VFS/ZipFileReader.cpp | 75 ++++++++++++++--------- Common/File/VFS/ZipFileReader.h | 6 +- GPU/Common/TextureReplacer.cpp | 44 +++++++------- android/jni/Android.mk | 1 + source_assets/ziptest.zip | Bin 0 -> 1955 bytes unittest/TestVFS.cpp | 94 +++++++++++++++++++++++++++++ unittest/UnitTest.cpp | 2 + unittest/UnitTests.vcxproj | 1 + unittest/UnitTests.vcxproj.filters | 1 + 11 files changed, 176 insertions(+), 50 deletions(-) create mode 100644 source_assets/ziptest.zip create mode 100644 unittest/TestVFS.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 27a63a02ce0f..6063ede1c6e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2481,6 +2481,7 @@ if(UNITTEST) unittest/TestIRPassSimplify.cpp unittest/TestX64Emitter.cpp unittest/TestVertexJit.cpp + unittest/TestVFS.cpp unittest/TestRiscVEmitter.cpp unittest/TestSoftwareGPUJit.cpp unittest/TestThreadManager.cpp diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index 5d585e309e3d..a3dc73e2e3b7 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -37,6 +37,7 @@ class VFSInterface { public: virtual ~VFSInterface() {} virtual uint8_t *ReadFile(const char *path, size_t *size) = 0; + // If listing already contains files, it'll be cleared. virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = nullptr) = 0; }; diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index c789ff899fc1..6f784ee6dea3 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -38,10 +38,13 @@ ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath, return nullptr; } - ZipFileReader *reader = new ZipFileReader(); - reader->zip_file_ = zip_file; - truncate_cpy(reader->inZipPath_, inZipPath); - return reader; + // The inZipPath is supposed to be a folder, and internally in this class, we suffix + // folder paths with '/', matching how the zip library works. + std::string path = inZipPath; + if (!path.empty() && path.back() != '/') { + path.push_back('/'); + } + return new ZipFileReader(zip_file, path); } ZipFileReader::~ZipFileReader() { @@ -50,16 +53,15 @@ ZipFileReader::~ZipFileReader() { } uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) { - char temp_path[2048]; - snprintf(temp_path, sizeof(temp_path), "%s%s", inZipPath_, path); + std::string temp_path = inZipPath_ + path; std::lock_guard guard(lock_); // Figure out the file size first. struct zip_stat zstat; - zip_stat(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat); - zip_file *file = zip_fopen(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED); + zip_stat(zip_file_, temp_path.c_str(), ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat); + zip_file *file = zip_fopen(zip_file_, temp_path.c_str(), ZIP_FL_NOCASE | ZIP_FL_UNCHANGED); if (!file) { - ERROR_LOG(IO, "Error opening %s from ZIP", temp_path); + ERROR_LOG(IO, "Error opening %s from ZIP", temp_path.c_str()); return 0; } uint8_t *contents = new uint8_t[zstat.size + 1]; @@ -72,8 +74,10 @@ uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) { } bool ZipFileReader::GetFileListing(const char *orig_path, std::vector *listing, const char *filter = 0) { - char path[2048]; - snprintf(path, sizeof(path), "%s%s", inZipPath_, orig_path); + std::string path = std::string(inZipPath_) + orig_path; + if (!path.empty() && path.back() != '/') { + path.push_back('/'); + } std::set filters; std::string tmp; @@ -95,17 +99,27 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vector files; std::set directories; - GetZipListings(path, files, directories); + bool success = GetZipListings(path, files, directories); + if (!success) { + // This means that no file prefix matched the path. + return false; + } + + listing->clear(); + + INFO_LOG(SYSTEM, "Listing %s", orig_path); for (auto diter = directories.begin(); diter != directories.end(); ++diter) { File::FileInfo info; info.name = *diter; // Remove the "inzip" part of the fullname. - info.fullName = Path(std::string(path).substr(strlen(inZipPath_))) / *diter; + std::string relativePath = std::string(path).substr(inZipPath_.size()); + info.fullName = Path(relativePath + *diter); info.exists = true; info.isWritable = false; info.isDirectory = true; + INFO_LOG(SYSTEM, "Found file: %s (%s)", info.name.c_str(), info.fullName.c_str()); listing->push_back(info); } @@ -113,7 +127,8 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vectorpush_back(info); } @@ -130,39 +146,44 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vector &files, std::set &directories) { - size_t pathlen = strlen(path); - if (pathlen == 1 && path[0] == '/') { - // Root. We simply use a zero length string. - pathlen = 0; - } +// path here is from the root, so inZipPath needs to already be added. +bool ZipFileReader::GetZipListings(const std::string &path, std::set &files, std::set &directories) { + _dbg_assert_(path.empty() || path.back() == '/'); std::lock_guard guard(lock_); int numFiles = zip_get_num_files(zip_file_); + bool anyPrefixMatched = false; for (int i = 0; i < numFiles; i++) { const char* name = zip_get_name(zip_file_, i, 0); if (!name) continue; // shouldn't happen, I think - if (!memcmp(name, path, pathlen)) { - const char *slashPos = strchr(name + pathlen + 1, '/'); + if (startsWith(name, path)) { + if (strlen(name) == path.size()) { + // Don't want to return the same folder. + continue; + } + const char *slashPos = strchr(name + path.size(), '/'); if (slashPos != 0) { + anyPrefixMatched = true; // A directory. Let's pick off the only part we care about. - int offset = pathlen; + size_t offset = path.size(); std::string dirName = std::string(name + offset, slashPos - (name + offset)); + // We might get a lot of these if the tree is deep. The std::set deduplicates. directories.insert(dirName); } else { + anyPrefixMatched = true; // It's a file. - const char *fn = name + pathlen; + const char *fn = name + path.size(); files.insert(std::string(fn)); } } } + return anyPrefixMatched; } bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) { struct zip_stat zstat; - char temp_path[1024]; - snprintf(temp_path, sizeof(temp_path), "%s%s", inZipPath_, path); + std::string temp_path = inZipPath_ + path; // Clear some things to start. info->isDirectory = false; @@ -171,7 +192,7 @@ bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) { { std::lock_guard guard(lock_); - if (0 != zip_stat(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat)) { + if (0 != zip_stat(zip_file_, temp_path.c_str(), ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat)) { // ZIP files do not have real directories, so we'll end up here if we // try to stat one. For now that's fine. info->exists = false; diff --git a/Common/File/VFS/ZipFileReader.h b/Common/File/VFS/ZipFileReader.h index 44a3292ede6f..30c61ccc76bf 100644 --- a/Common/File/VFS/ZipFileReader.h +++ b/Common/File/VFS/ZipFileReader.h @@ -40,9 +40,11 @@ class ZipFileReader : public VFSBackend { } private: - void GetZipListings(const char *path, std::set &files, std::set &directories); + ZipFileReader(zip *zip_file, const std::string &inZipPath) : zip_file_(zip_file), inZipPath_(inZipPath) {} + // Path has to be either an empty string, or a string ending with a /. + bool GetZipListings(const std::string &path, std::set &files, std::set &directories); zip *zip_file_ = nullptr; std::mutex lock_; - char inZipPath_[256]; + std::string inZipPath_; }; diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 090f6a5e290c..6b72e879f1a6 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -160,7 +160,7 @@ bool TextureReplacer::LoadIni() { } INFO_LOG(G3D, "Loading extra texture ini: %s", overrideFilename.c_str()); - if (!LoadIniValues(overrideIni, dir, true)) { + if (!LoadIniValues(overrideIni, nullptr, true)) { delete dir; return false; } @@ -261,26 +261,28 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri // Scan the root of the texture folder/zip and preinitialize the hash map. std::vector filesInRoot; - dir->GetFileListing("/", &filesInRoot, nullptr); - for (auto file : filesInRoot) { - if (file.isDirectory) - continue; - if (file.name.empty() || file.name[0] == '.') - continue; - Path path(file.name); - std::string ext = path.GetFileExtension(); - - std::string hash = file.name.substr(0, file.name.size() - ext.size()); - if (!((hash.size() >= 26 && hash.size() <= 27 && hash[24] == '_') || hash.size() == 24)) { - continue; - } - // OK, it's hash-like enough to try to parse it into the map. - if (equalsNoCase(ext, ".ktx2") || equalsNoCase(ext, ".png") || equalsNoCase(ext, ".dds")) { - ReplacementCacheKey key(0, 0); - int level = 0; // sscanf might fail to pluck the level, but that's ok, we default to 0. sscanf doesn't write to non-matched outputs. - if (sscanf(hash.c_str(), "%16llx%8x_%d", &key.cachekey, &key.hash, &level) >= 1) { - INFO_LOG(G3D, "hash-like file in root, adding: %s", file.name.c_str()); - filenameMap[key][level] = file.name; + if (dir) { + dir->GetFileListing("", &filesInRoot, nullptr); + for (auto file : filesInRoot) { + if (file.isDirectory) + continue; + if (file.name.empty() || file.name[0] == '.') + continue; + Path path(file.name); + std::string ext = path.GetFileExtension(); + + std::string hash = file.name.substr(0, file.name.size() - ext.size()); + if (!((hash.size() >= 26 && hash.size() <= 27 && hash[24] == '_') || hash.size() == 24)) { + continue; + } + // OK, it's hash-like enough to try to parse it into the map. + if (equalsNoCase(ext, ".ktx2") || equalsNoCase(ext, ".png") || equalsNoCase(ext, ".dds") || equalsNoCase(ext, ".zim")) { + ReplacementCacheKey key(0, 0); + int level = 0; // sscanf might fail to pluck the level, but that's ok, we default to 0. sscanf doesn't write to non-matched outputs. + if (sscanf(hash.c_str(), "%16llx%8x_%d", &key.cachekey, &key.hash, &level) >= 1) { + // INFO_LOG(G3D, "hash-like file in root, adding: %s", file.name.c_str()); + filenameMap[key][level] = file.name; + } } } } diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 38301fb43621..64005a3f42db 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -811,6 +811,7 @@ ifeq ($(UNITTEST),1) $(SRC)/unittest/TestSoftwareGPUJit.cpp \ $(SRC)/unittest/TestThreadManager.cpp \ $(SRC)/unittest/TestVertexJit.cpp \ + $(SRC)/unittest/TestVFS.cpp \ $(TESTARMEMITTER_FILE) \ $(SRC)/unittest/UnitTest.cpp diff --git a/source_assets/ziptest.zip b/source_assets/ziptest.zip new file mode 100644 index 0000000000000000000000000000000000000000..57e117dc0ac16ab6119e1eea5bb273dce9236199 GIT binary patch literal 1955 zcmai#&ubG=5Xax9t%llKq1Fl>8j5J3V!Y@<6tO*68pJI^gi49K?GhtpW3$^JMJ+-t zBK`|ra?cuJ}`7LwqH=T=q-Z>fG3FBl| zSx}!5R4&|X40+8$jHFyr0e3c%ax;*Ws|g1IpA@$Sz z{I1Wo3=1mxz>{GKnV1TzLfEs3TMt`>mAyi-e&ZLLeI*jQi#5G)=CV> zHZ^+ykAgE^2b780*eO!-jwKF@%24vs+TE{~wo~7aoW|c&*?efJ@-fhAI3OyYn(v)N zIc;bDR7SYG`JhwzaW3yVfRSi>9VGg^l1VN()NR2+FZ2vlY6)D;_>zBC{GzI&$L@09Z zsr;$Ft^nn=9lSDoG458YMWW@gh!4ZN@QI73B;h;?u`=#fyt_{(d}h0Xf<(y%gu-delFrx!Wiw2OP5LsJM7D`~=0g VwX+?2pyQ$&Ui&+QSO5tj{s9BFvyuP+ literal 0 HcmV?d00001 diff --git a/unittest/TestVFS.cpp b/unittest/TestVFS.cpp new file mode 100644 index 000000000000..8eb57886d0a3 --- /dev/null +++ b/unittest/TestVFS.cpp @@ -0,0 +1,94 @@ +#include +#include + +#include "Common/Log.h" +#include "Common/File/VFS/ZipFileReader.h" + +#include "UnitTest.h" + +static bool CheckContainsDir(const std::vector &listing, const char *name) { + for (auto &file : listing) { + if (file.name == name && file.isDirectory) { + return true; + } + } + return false; +} + +static bool CheckContainsFile(const std::vector &listing, const char *name) { + for (auto &file : listing) { + if (file.name == name && !file.isDirectory) { + return true; + } + } + return false; +} + +// ziptest.zip file structure: +// +// ziptest/ +// data/ +// a/ +// in_a.txt +// b/ +// in_b.txt +// argh.txt +// big.txt +// lang/ +// en_us.txt +// sv_se.txt +// langregion.txt +// in_root.txt + +// TODO: Also test the filter. +bool TestZipFile() { + // First, check things relative to root, with an empty internal path. + Path zipPath = Path("../source_assets/ziptest.zip"); + if (!File::Exists(zipPath)) { + zipPath = Path("source_assets/ziptest.zip"); + } + + ZipFileReader *dir = ZipFileReader::Create(zipPath, "", true); + EXPECT_TRUE(dir != nullptr); + + std::vector listing; + EXPECT_TRUE(dir->GetFileListing("", &listing, nullptr)); + EXPECT_EQ_INT(listing.size(), 2); + EXPECT_TRUE(CheckContainsDir(listing, "ziptest")); + EXPECT_TRUE(CheckContainsFile(listing, "in_root.txt")); + EXPECT_FALSE(dir->GetFileListing("ziptestwrong", &listing, nullptr)); + + // Next, do a file listing in a directory, but keep the root. + EXPECT_TRUE(dir->GetFileListing("ziptest", &listing, nullptr)); + EXPECT_EQ_INT(listing.size(), 3); + EXPECT_TRUE(CheckContainsDir(listing, "data")); + EXPECT_TRUE(CheckContainsDir(listing, "lang")); + EXPECT_TRUE(CheckContainsFile(listing, "langregion.txt")); + delete dir; + + // Next, we'll destroy the reader and create a new one based in a subdirectory. + dir = ZipFileReader::Create(zipPath, "ziptest/data", true); + EXPECT_TRUE(dir != nullptr); + EXPECT_TRUE(dir->GetFileListing("", &listing, nullptr)); + EXPECT_EQ_INT(listing.size(), 4); + EXPECT_TRUE(CheckContainsDir(listing, "a")); + EXPECT_TRUE(CheckContainsDir(listing, "b")); + EXPECT_TRUE(CheckContainsFile(listing, "argh.txt")); + EXPECT_TRUE(CheckContainsFile(listing, "big.txt")); + + EXPECT_TRUE(dir->GetFileListing("a", &listing, nullptr)); + EXPECT_TRUE(CheckContainsFile(listing, "in_a.txt")); + EXPECT_EQ_INT(listing.size(), 1); + EXPECT_TRUE(dir->GetFileListing("b", &listing, nullptr)); + EXPECT_TRUE(CheckContainsFile(listing, "in_b.txt")); + EXPECT_EQ_INT(listing.size(), 1); + delete dir; + + return true; +} + +bool TestVFS() { + if (!TestZipFile()) + return false; + return true; +} diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 605ba0bfb85b..0c41d6302abd 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -930,6 +930,7 @@ bool TestShaderGenerators(); bool TestSoftwareGPUJit(); bool TestIRPassSimplify(); bool TestThreadManager(); +bool TestVFS(); TestItem availableTests[] = { #if PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86) @@ -968,6 +969,7 @@ TestItem availableTests[] = { TEST_ITEM(DepthMath), TEST_ITEM(InputMapping), TEST_ITEM(EscapeMenuString), + TEST_ITEM(VFS), }; int main(int argc, const char *argv[]) { diff --git a/unittest/UnitTests.vcxproj b/unittest/UnitTests.vcxproj index 473429fa92f7..687314e43dbb 100644 --- a/unittest/UnitTests.vcxproj +++ b/unittest/UnitTests.vcxproj @@ -397,6 +397,7 @@ + true diff --git a/unittest/UnitTests.vcxproj.filters b/unittest/UnitTests.vcxproj.filters index 14344efb6a43..eb084afaf0a7 100644 --- a/unittest/UnitTests.vcxproj.filters +++ b/unittest/UnitTests.vcxproj.filters @@ -16,6 +16,7 @@ + From 919979eeceec3b8fc850171989c7c2e256031691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 2 May 2023 11:40:50 +0200 Subject: [PATCH 4/4] Disable excessive logging --- Common/File/VFS/ZipFileReader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 6f784ee6dea3..c934a6e3d1b0 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -119,7 +119,7 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vectorpush_back(info); } @@ -138,7 +138,7 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vectorpush_back(info); }