From ce7512c4917b50e346c208f3d5f352d8e6929f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Oct 2026 16:12:52 +0200 Subject: [PATCH] PathBrowser: List the shares of a network server on Windows Navigating up from a share like \\QUAD\PSP ISO lands on \\QUAD, which FindFirstFile can't list, so it showed as an empty folder. Enumerate the server's disk shares with NetShareEnum instead, skipping admin shares. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/File/DirListing.cpp | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Common/File/DirListing.cpp b/Common/File/DirListing.cpp index 07ae7f7559..3d9cae632d 100644 --- a/Common/File/DirListing.cpp +++ b/Common/File/DirListing.cpp @@ -7,6 +7,10 @@ #if PPSSPP_PLATFORM(UWP) #include #include +#elif defined(_MSC_VER) +#include +#pragma comment(lib, "netapi32.lib") +#define HAVE_NETSHAREENUM #endif #else #include @@ -90,6 +94,57 @@ void ReadFileAttributes(FileInfo *fileInfo, const T &attrs) { } } +#ifdef HAVE_NETSHAREENUM +// Returns the server name if the path is just "//server", which can't be listed like a directory. +static bool GetUNCServerName(const Path &path, std::wstring *server) { + const std::string &str = path.ToString(); + if (str.size() < 3 || str[0] != '/' || str[1] != '/' || str[2] == '.' || str[2] == '?' || str.find('/', 2) != std::string::npos) { + return false; + } + *server = ConvertUTF8ToWString(str.substr(2)); + return true; +} + +// Lists the disk shares of a server as directories, skipping the admin ones like C$ and IPC$. +static bool GetUNCServerShares(const Path &directory, const std::wstring &server, std::vector *files, std::string_view prefix) { + bool success = false; + DWORD_PTR resumeHandle = 0; + NET_API_STATUS status; + do { + SHARE_INFO_1 *shares = nullptr; + DWORD count = 0; + DWORD total = 0; + status = NetShareEnum((LPWSTR)server.c_str(), 1, (LPBYTE *)&shares, MAX_PREFERRED_LENGTH, &count, &total, (LPDWORD)&resumeHandle); + if (status != NERR_Success && status != ERROR_MORE_DATA) { + WARN_LOG(Log::IO, "NetShareEnum failed for %s: %d", directory.c_str(), (int)status); + break; + } + success = true; + for (DWORD i = 0; i < count; i++) { + if ((shares[i].shi1_type & STYPE_MASK) != STYPE_DISKTREE || (shares[i].shi1_type & STYPE_SPECIAL)) { + continue; + } + std::string name = ConvertWStringToUTF8(shares[i].shi1_netname); + if (!startsWith(name, prefix)) { + continue; + } + FileInfo info; + info.name = name; + info.fullName = directory / name; + info.exists = true; + info.isDirectory = true; + info.isWritable = false; + info.size = 0; + files->push_back(info); + } + if (shares) { + NetApiBufferFree(shares); + } + } while (status == ERROR_MORE_DATA); + return success; +} +#endif + #endif bool GetFileInfo(const Path &path, FileInfo * fileInfo) { @@ -283,6 +338,14 @@ bool GetFilesInDir(const Path &directory, std::vector *files, const ch } return files->size(); } +#ifdef HAVE_NETSHAREENUM + std::wstring server; + if (GetUNCServerName(directory, &server)) { + bool success = GetUNCServerShares(directory, server, files, prefix); + std::sort(files->begin(), files->end()); + return success; + } +#endif // Find the first file in the directory. WIN32_FIND_DATA ffd; std::wstring wpath = directory.ToWString();