[Kernel] Take APR file sizes from the mount check's stat instead of listing directories

Resolving an APR path already stats every component to rule out links out of the mount.
The same stat now records the file's size, so size lookup no longer lists the file's whole
directory. Listing stats every entry: Demon's Souls keeps up to 7,390 files per asset
directory on a slow disk, and each first lookup in one paid for all of them.

FileInfo reports a missing component as -1 instead of throwing; it still ends the check.
This commit is contained in:
kuba 2026-09-30 10:05:58 +02:00
parent 467877d046
commit 997b7df06f
1 changed files with 16 additions and 23 deletions

View File

@ -126,7 +126,6 @@ public static partial class KernelMemoryCompatExports
// every asset path at startup; re-reading each directory's attributes costs seconds.
private static readonly ConcurrentDictionary<string, byte> _verifiedMountComponents = new(HostFsPath.Comparer);
private static readonly ConcurrentDictionary<string, string> _fullMountRoots = new(StringComparer.Ordinal);
private static readonly ConcurrentDictionary<string, byte> _aprScannedDirectories = new(HostFsPath.Comparer);
private static long _nextFileDescriptor = 2;
private static string _applicationTitleId = "UNKNOWN";
@ -5173,11 +5172,26 @@ public static partial class KernelMemoryCompatExports
try
{
if ((File.GetAttributes(current) & FileAttributes.ReparsePoint) != 0)
// One lstat answers both questions a resolved asset path asks: whether the
// component is a link, and (for the file itself) its size for APR resolution.
var component = new FileInfo(current);
var attributes = component.Attributes;
if ((int)attributes == -1)
{
// Component does not exist yet (create path); nothing to follow.
break;
}
if ((attributes & FileAttributes.ReparsePoint) != 0)
{
return true;
}
if ((attributes & FileAttributes.Directory) == 0)
{
_aprFileSizeCache.TryAdd(current, component.Length < 0 ? 0UL : unchecked((ulong)component.Length));
}
_verifiedMountComponents.TryAdd(current, 0);
}
catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException)
@ -6902,27 +6916,6 @@ public static partial class KernelMemoryCompatExports
return false;
}
// One directory listing returns every file's size; titles resolve whole asset
// directories at startup, so this replaces one host query per file.
if (Path.GetDirectoryName(cachePath) is { } directory && _aprScannedDirectories.TryAdd(directory, 0))
{
try
{
foreach (var file in new DirectoryInfo(directory).EnumerateFiles())
{
_aprFileSizeCache.TryAdd(file.FullName, file.Length < 0 ? 0UL : unchecked((ulong)file.Length));
}
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or System.Security.SecurityException)
{
}
if (_aprFileSizeCache.TryGetValue(cachePath, out size))
{
return true;
}
}
try
{
var fileInfo = new FileInfo(cachePath);