From 6b38cdda8c516184c0d7791be0f6eb2c0856f702 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Date: Mon, 12 Oct 2026 01:53:45 +0530 Subject: [PATCH] fix(libkernel): report the target size of symbolic links on Windows _wstat64, the FindFirstFileExW listing and std::filesystem::file_size report the size of a file symbolic link itself (0). stat therefore gave 0 for linked files, and APR resolved them as empty, so Unity treated a linked globalgamemanagers as empty and never read it. stat takes the size from the handle it already opens, which follows the link. The APR listing skips reparse points, and names missing from it are sized through stat. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/Apr/src/Apr.cpp | 9 ++++-- .../prx/libkernel/File/src/NativeStat.cpp | 1 + core/libs/tests/GuestAmpr.cpp | 30 +++++++++++++++++++ core/libs/tests/GuestFilesystem.cpp | 7 +++-- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index 6910a43eb..631f5553e 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -77,7 +77,7 @@ bool _fileSize(const std::filesystem::path& path, std::uint64_t& bytes) { const HANDLE find = FindFirstFileExW((path.parent_path() / L"*").c_str(), FindExInfoBasic, &entry, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH); if (find != INVALID_HANDLE_VALUE) { do { - if ((entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) continue; + if ((entry.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) != 0) continue; const auto size = (static_cast(entry.nFileSizeHigh) << 32u) | entry.nFileSizeLow; sizes.emplace(_foldCase(std::filesystem::path(entry.cFileName).string()), size); } while (FindNextFileW(find, &entry)); @@ -101,8 +101,11 @@ bool _fileSize(const std::filesystem::path& path, std::uint64_t& bytes) { } // Not in the listing (created since, or an unusual name): ask the file system directly. std::error_code error; - bytes = std::filesystem::file_size(path, error); - return !error; + if (!std::filesystem::is_regular_file(path, error)) return false; + FileStat stat{}; + File::FillFileStat(path, &stat); + bytes = static_cast(stat.st_size); + return true; } int _fail(int guestErrno) { diff --git a/core/libs/prx/libkernel/File/src/NativeStat.cpp b/core/libs/prx/libkernel/File/src/NativeStat.cpp index 5454db47d..4495fb59f 100644 --- a/core/libs/prx/libkernel/File/src/NativeStat.cpp +++ b/core/libs/prx/libkernel/File/src/NativeStat.cpp @@ -68,6 +68,7 @@ static void CopyNativeStat(const NativeStat& st, FileStat* sb) { sb->st_mode = static_cast(st.st_mode); sb->st_size = static_cast(st.st_size); #ifdef _WIN32 + if (st.hasIdentity) sb->st_size = static_cast((static_cast(st.identity.nFileSizeHigh) << 32u) | st.identity.nFileSizeLow); sb->st_dev = static_cast(st.st_dev); sb->st_ino = static_cast(st.st_ino); sb->st_nlink = static_cast(st.st_nlink); diff --git a/core/libs/tests/GuestAmpr.cpp b/core/libs/tests/GuestAmpr.cpp index 3b366edd4..37bc4c9df 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -9,12 +9,16 @@ #include #include #include +#include #include #include #include #include #include #include +#ifdef _WIN32 +#include +#endif extern "C" { int APS5_VABI sceAmprCommandBufferConstructor(Apr::CommandBufferObject*); @@ -671,6 +675,31 @@ void TestMalformedCommandSizes() { Require(rejected); } +void TestResolveSymbolicLink() { + const std::filesystem::path directory = "ampr_resolve_links"; + std::filesystem::remove_all(directory); + Require(std::filesystem::create_directory(directory)); + { std::ofstream file(directory / "target.bin", std::ios::binary); file << "linked data"; } + const auto link = directory / "link.bin"; +#ifdef _WIN32 + if (CreateSymbolicLinkW(link.c_str(), L"target.bin", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0) { + Require(GetLastError() == ERROR_PRIVILEGE_NOT_HELD); + std::fprintf(stderr, "APR symbolic link check skipped: creating symbolic links needs Developer Mode or administrator rights\n"); + std::filesystem::remove_all(directory); + return; + } +#else + std::filesystem::create_symlink("target.bin", link); +#endif + const std::string linkPath = link.generic_string(); + const char* path = linkPath.c_str(); + std::uint32_t id = 0; + std::uint64_t size = 0; + std::uint32_t failed = 0; + Require(sceKernelAprResolveFilepathsToIdsAndFileSizes(&path, 1, &id, &size, &failed) == 0 && size == 11); + std::filesystem::remove_all(directory); +} + void TestGatherScatter() { const char* path = "ampr_gather_scatter.bin"; { @@ -1230,6 +1259,7 @@ int main() { TestZeroFilledBuffer(); TestResolveFailures(); TestMalformedCommandSizes(); + TestResolveSymbolicLink(); TestGatherScatter(); TestAmmRangesAboveHintedMaps(); TestAmm(); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index f759e12d3..f531c7ba4 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -428,7 +428,7 @@ int main() { std::filesystem::create_symlink("present.txt", link); Require(lstat_nid_postfix(link.string().c_str(), &linkStatus) == 0); Require((linkStatus.st_mode & 0170000) == 0120000 && linkStatus.st_size == 11); - Require(stat_nid_postfix(link.string().c_str(), &status) == 0 && (status.st_mode & 0170000) == 0100000); + Require(stat_nid_postfix(link.string().c_str(), &status) == 0 && (status.st_mode & 0170000) == 0100000 && status.st_size == 5); const auto dangling = root / "dangling"; std::filesystem::create_symlink("missing.txt", dangling); Require(lstat_nid_postfix(dangling.string().c_str(), &linkStatus) == 0 && (linkStatus.st_mode & 0170000) == 0120000); @@ -443,7 +443,7 @@ int main() { Require(unlink_nid_postfix(link.string().c_str()) == 0 && unlink_nid_postfix(dangling.string().c_str()) == 0); #else const auto fileLink = root / "file-link"; - if (CreateSymbolicLinkW(fileLink.c_str(), present.c_str(), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0) { + if (CreateSymbolicLinkW(fileLink.c_str(), L"present.txt", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0) { Require(GetLastError() == ERROR_PRIVILEGE_NOT_HELD); std::fprintf(stderr, "Windows lstat link checks skipped: creating symbolic links needs Developer Mode or administrator rights\n"); } else { @@ -451,7 +451,7 @@ int main() { Require(std::filesystem::create_directory(targetDirectory)); const auto directoryLink = root / "directory-link"; const auto danglingLink = root / "dangling-link"; - Require(CreateSymbolicLinkW(directoryLink.c_str(), targetDirectory.c_str(), + Require(CreateSymbolicLinkW(directoryLink.c_str(), L"target-directory", SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); Require(CreateSymbolicLinkW(danglingLink.c_str(), missing.c_str(), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); const auto checkUnsupportedLink = [](const std::filesystem::path& link) { @@ -470,6 +470,7 @@ int main() { const bool statusUnchanged = std::all_of(bytes, bytes + sizeof(untouched), [](unsigned char value) { return value == 0x5a; }); return rejected && errorUnchanged && statusUnchanged; }; + Require(stat_nid_postfix(fileLink.string().c_str(), &status) == 0 && status.st_size == 5); Require(checkUnsupportedLink(fileLink)); Require(checkUnsupportedLink(directoryLink)); Require(checkUnsupportedLink(danglingLink));