diff --git a/FileEmulationFramework.Lib/Utilities/Native.cs b/FileEmulationFramework.Lib/Utilities/Native.cs index 1ac9a94..2f27cf7 100644 --- a/FileEmulationFramework.Lib/Utilities/Native.cs +++ b/FileEmulationFramework.Lib/Utilities/Native.cs @@ -11,6 +11,11 @@ namespace FileEmulationFramework.Lib.Utilities; [ExcludeFromCodeCoverage(Justification = "Definitions for Interop.")] public static class Native { + /// + /// The value of a handle that is invalid + /// + public static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + /// /// /// Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded. diff --git a/FileEmulationFramework/FileAccessServer.cs b/FileEmulationFramework/FileAccessServer.cs index 9297053..68807af 100644 --- a/FileEmulationFramework/FileAccessServer.cs +++ b/FileEmulationFramework/FileAccessServer.cs @@ -32,7 +32,7 @@ public static unsafe class FileAccessServer private static List Emulators { get; } = new(); private static readonly ConcurrentDictionary HandleToInfoMap = new(); - private static readonly ConcurrentDictionary PathToVirtualFileMap = new(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary PathToVirtualFileMap = new(StringComparer.OrdinalIgnoreCase); private static readonly ConcurrentDictionary PathToHandleMap = new(StringComparer.OrdinalIgnoreCase); private static readonly object ThreadLock = new(); @@ -148,44 +148,11 @@ private static int QueryFullAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FI var path = Strings.TrimWindowsPrefixes(attributes->ObjectName); - SafeFileHandle? safeHandle = null; - - if (!PathToHandleMap.TryGetValue(path, out var hfile) || !HandleToInfoMap.TryGetValue(hfile, out var info)) - { - // We haven't tried to create an emulated file for this yet, try it - if (!PathToVirtualFileMap.TryGetValue(path, out info)) - { - // Prevent recursion - PathToVirtualFileMap[path] = null; - - // There is a virtual file but no handle exists for it, we need to make one - try - { - safeHandle = File.OpenHandle(path); - } - catch (Exception e) - { - // If we couldn't make the handle this probably isn't a file we can emulate - return result; - } - - hfile = safeHandle.DangerousGetHandle(); - - // We tried to make one but failed, this file isn't emulated - if (!HandleToInfoMap.TryGetValue(hfile, out info)) - { - safeHandle.Close(); - return result; - } - } - - // We've tried to create an emulated file for this before but failed, this file isn't emulated - if (info == null) - return result; - } + if (!TryGetFileInfoFromPath(path, out var hfile, out var info, out var newFileHandle)) + return result; var oldSize = information->EndOfFile; - var newSize = info.File.GetFileSize(hfile, info); + var newSize = info!.File.GetFileSize(hfile, info); if (newSize != -1) information->EndOfFile = newSize; @@ -200,8 +167,7 @@ private static int QueryFullAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FI } // Clean up if we needed to make a new handle - if (safeHandle != null) - safeHandle.Close(); + newFileHandle?.Close(); return result; } @@ -212,16 +178,19 @@ private static int QueryAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FILE_B var result = _getFileAttributesHook.OriginalFunction.Value.Invoke(attributes, information); var path = Strings.TrimWindowsPrefixes(attributes->ObjectName); - if (!PathToHandleMap.TryGetValue(path, out var hfile) || !HandleToInfoMap.TryGetValue(hfile, out var info)) + if (!TryGetFileInfoFromPath(path, out var hfile, out var info, out var newFileHandle)) return result; - if (info.File.TryGetLastWriteTime(hfile, info, out var newWriteTime)) + if (info!.File.TryGetLastWriteTime(hfile, info, out var newWriteTime)) { var oldWriteTime = information->LastWriteTime.ToDateTime(); information->LastWriteTime = new LARGE_INTEGER(newWriteTime!.Value.ToFileTimeUtc()); _logger.Info("[FileAccessServer] File Last Write Override | Old: {0}, New: {1} | {2}", oldWriteTime, newWriteTime, path); } + + // Clean up if we needed to make a new handle + newFileHandle?.Close(); return result; } @@ -323,7 +292,7 @@ private static int NtCreateFileImpl(IntPtr* handle, FileAccess access, OBJECT_AT _logger.Debug("[FileAccessServer] Accessing: {0}, {1}, Route: {2}", hndl, newFilePath, _currentRoute.FullPath); // Try Accept New File (virtual override) - if (PathToVirtualFileMap.TryGetValue(newFilePath, out var fileInfo) && fileInfo != null) + if (PathToVirtualFileMap.TryGetValue(newFilePath, out var fileInfo)) { // Reuse of emulated file (backed by stream) is safe because file access is single threaded. lock (ThreadLock) @@ -343,7 +312,8 @@ private static int NtCreateFileImpl(IntPtr* handle, FileAccess access, OBJECT_AT lock (ThreadLock) { - HandleToInfoMap[hndl] = new(newFilePath, 0, emulatedFile); + fileInfo = new(newFilePath, 0, emulatedFile); + HandleToInfoMap[hndl] = fileInfo; PathToHandleMap[newFilePath] = hndl; } return ntStatus; @@ -357,6 +327,50 @@ private static int NtCreateFileImpl(IntPtr* handle, FileAccess access, OBJECT_AT } } + /// + /// Tries to get the FileInformation for an emulated file based on its path. + /// If a file at the specified path has never been created before this will attempt to create it, making the emulated file. + /// + /// The path to the file to try and get information for + /// The handle to the emulated file if there was one + /// The FileInformation for the emulated file if there is one, null otherwise + /// A safe handle to the new file that was created, if one had to be created. Make sure to close this when you are done with the file info! + /// True if the file information for an emulated file with the specified path was found, false otherwise. + private static bool TryGetFileInfoFromPath(string path, out IntPtr hfile, out FileInformation? fileInfo, out SafeFileHandle? newFileHandle) + { + newFileHandle = null; + fileInfo = null; + + // We haven't tried to create an emulated file for this yet, try it + if (!PathToHandleMap.TryGetValue(path, out hfile) || (hfile != INVALID_HANDLE_VALUE && !HandleToInfoMap.TryGetValue(hfile, out fileInfo))) + { + // Prevent recursion + PathToHandleMap[path] = INVALID_HANDLE_VALUE; + + // There is a virtual file but no handle exists for it, we need to make one + try + { + newFileHandle = File.OpenHandle(path); + } + catch (Exception e) + { + // If we couldn't make the handle this probably isn't a file we can emulate + return false; + } + + hfile = newFileHandle.DangerousGetHandle(); + + // We tried to make one but failed, this file isn't emulated + if (!HandleToInfoMap.TryGetValue(hfile, out fileInfo)) + { + newFileHandle.Close(); + return false; + } + } + + return fileInfo != null; + } + /// /// Determines if the given handle refers to a directory. /// diff --git a/Submodules/Atlus-Script-Tools b/Submodules/Atlus-Script-Tools index fce608b..0a74cf0 160000 --- a/Submodules/Atlus-Script-Tools +++ b/Submodules/Atlus-Script-Tools @@ -1 +1 @@ -Subproject commit fce608b6238f645b4f7bf6c7a5d70c0305d2499b +Subproject commit 0a74cf05de30118ba60e9b1ff7b6cd61677085ed