From 997b7df06f492b518a9257f4cd4aac1a67a5ec3b Mon Sep 17 00:00:00 2001 From: kuba Date: Wed, 30 Sep 2026 10:05:58 +0200 Subject: [PATCH] [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. --- .../Kernel/KernelMemoryCompatExports.cs | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 8c8cb9f..f4b5b19 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -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 _verifiedMountComponents = new(HostFsPath.Comparer); private static readonly ConcurrentDictionary _fullMountRoots = new(StringComparer.Ordinal); - private static readonly ConcurrentDictionary _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);