From c00085893be689c096d0a32acd28ca727e0df34e Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Tue, 6 Apr 2021 17:49:23 -0500 Subject: [PATCH] Implement core Amsi types for antimalware integration --- .../AntiMalware/WinAPI/AmsiFileStream.cs | 113 +++++ .../AntiMalware/WinAPI/AmsiMemoryStream.cs | 114 +++++ IPA.Loader/AntiMalware/WinAPI/Constants.cs | 14 + IPA.Loader/AntiMalware/WinAPI/IAntimalware.cs | 58 +++ IPA.Loader/IPA.Loader.csproj | 5 + IPA.Loader/Loader/LibLoader.cs | 476 +++++++++--------- 6 files changed, 542 insertions(+), 238 deletions(-) create mode 100644 IPA.Loader/AntiMalware/WinAPI/AmsiFileStream.cs create mode 100644 IPA.Loader/AntiMalware/WinAPI/AmsiMemoryStream.cs create mode 100644 IPA.Loader/AntiMalware/WinAPI/Constants.cs create mode 100644 IPA.Loader/AntiMalware/WinAPI/IAntimalware.cs diff --git a/IPA.Loader/AntiMalware/WinAPI/AmsiFileStream.cs b/IPA.Loader/AntiMalware/WinAPI/AmsiFileStream.cs new file mode 100644 index 00000000..a14653ca --- /dev/null +++ b/IPA.Loader/AntiMalware/WinAPI/AmsiFileStream.cs @@ -0,0 +1,113 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace IPA.AntiMalware.WinAPI +{ + internal class AmsiFileStream : IAmsiStream, IDisposable + { + private readonly FileInfo file; + private readonly IntPtr session; + + public AmsiFileStream(FileInfo file, IntPtr session) + { + this.file = file; + this.session = session; + } + + public unsafe void GetAttribute([In] AmsiAttribute attribute, [In] uint dataSize, [Out] byte* buffer, out uint writtenData) + { + switch (attribute) + { + case AmsiAttribute.AppName: + writtenData = WriteWString(AmsiConstants.AppName, dataSize, buffer); + return; + case AmsiAttribute.Session: + *(IntPtr*)buffer = session; + writtenData = (uint)sizeof(IntPtr); + return; + + case AmsiAttribute.ContentName: + writtenData = WriteWString(file.FullName, dataSize, buffer); + return; + + case AmsiAttribute.ContentSize: + *(ulong*)buffer = (ulong)file.Length; + writtenData = sizeof(ulong); + return; + + default: + throw new NotImplementedException(); // return e_notimpl + } + + static unsafe uint WriteWString(string str, uint dataSize, byte* buffer) + { + fixed (char* name = str) + { + return (uint)Encoding.Unicode.GetBytes(name, str.Length, buffer, (int)dataSize); + } + } + } + + private FileStream? stream; + private bool disposedValue; + private readonly byte[] readBuffer = new byte[1024]; + + public unsafe void Read([In] ulong position, [In] uint dataSize, [Out] byte* buffer, out uint readSize) + { + stream ??= file.OpenRead(); + + stream.Position = (long)position; + + var bytesToRead = dataSize; + readSize = 0; + + while (bytesToRead > 0) + { + var bytesRead = stream.Read(readBuffer, 0, (int)Math.Min(readBuffer.Length, bytesToRead)); + if (bytesRead == 0) + { + break; + } + fixed (byte* readBufferPtr = readBuffer) + { + Buffer.MemoryCopy(readBufferPtr, buffer + readSize, dataSize - readSize, bytesRead); + } + bytesToRead -= (uint)bytesRead; + readSize += (uint)bytesRead; + } + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + stream?.Dispose(); + } + + disposedValue = true; + } + } + + // This does not have unmanagd resources, so it doesn't need to exist + // ~AmsiFileStream() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/IPA.Loader/AntiMalware/WinAPI/AmsiMemoryStream.cs b/IPA.Loader/AntiMalware/WinAPI/AmsiMemoryStream.cs new file mode 100644 index 00000000..8aabd050 --- /dev/null +++ b/IPA.Loader/AntiMalware/WinAPI/AmsiMemoryStream.cs @@ -0,0 +1,114 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace IPA.AntiMalware.WinAPI +{ + internal class AmsiMemoryStream : IAmsiStream, IDisposable + { + private readonly string contentName; + private readonly byte[] data; + private readonly GCHandle dataHandle; + + private readonly IntPtr session; + private bool disposedValue; + + public AmsiMemoryStream(string contentName, byte[] data, IntPtr session) + { + this.data = data; + dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned); + this.session = session; + this.contentName = contentName; + } + + public unsafe void GetAttribute([In] AmsiAttribute attribute, [In] uint dataSize, [Out] byte* buffer, out uint writtenData) + { + switch (attribute) + { + case AmsiAttribute.AppName: + writtenData = WriteWString(AmsiConstants.AppName, dataSize, buffer); + return; + case AmsiAttribute.Session: + *(IntPtr*)buffer = session; + writtenData = (uint)sizeof(IntPtr); + return; + + case AmsiAttribute.ContentName: + writtenData = WriteWString(contentName, dataSize, buffer); + return; + + case AmsiAttribute.ContentSize: + *(ulong*)buffer = (ulong)data.Length; + writtenData = sizeof(ulong); + return; + + case AmsiAttribute.ContentAddress: + // because our data is pinned, it can't move while this object exists so we can pass out the fixed address + fixed (byte* dataAddr = data) + { + *(byte**)buffer = dataAddr; + } + writtenData = (uint)sizeof(IntPtr); + return; + + default: + throw new NotImplementedException(); // return e_notimpl + } + + static unsafe uint WriteWString(string str, uint dataSize, byte* buffer) + { + fixed (char* name = str) + { + return (uint)Encoding.Unicode.GetBytes(name, str.Length, buffer, (int)dataSize); + } + } + } + + public unsafe void Read([In] ulong position, [In] uint dataSize, [Out] byte* buffer, out uint readSize) + { + if (position >= (ulong)data.Length) + { + throw new EndOfStreamException(); + } + + fixed (byte* dataPtr = data) + { + var toRead = Math.Min((ulong)data.Length - position, dataSize); + Buffer.MemoryCopy(dataPtr + position, buffer, dataSize, toRead); + readSize = (uint)toRead; + } + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // no managed stae to dispose + } + + dataHandle.Free(); + disposedValue = true; + } + } + + ~AmsiMemoryStream() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/IPA.Loader/AntiMalware/WinAPI/Constants.cs b/IPA.Loader/AntiMalware/WinAPI/Constants.cs new file mode 100644 index 00000000..e841cb6c --- /dev/null +++ b/IPA.Loader/AntiMalware/WinAPI/Constants.cs @@ -0,0 +1,14 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IPA.AntiMalware.WinAPI +{ + internal static class AmsiConstants + { + public static const string AppName = "BSIPA/" + Config.SelfConfig.IPAVersion; + } +} diff --git a/IPA.Loader/AntiMalware/WinAPI/IAntimalware.cs b/IPA.Loader/AntiMalware/WinAPI/IAntimalware.cs new file mode 100644 index 00000000..61b7ca1a --- /dev/null +++ b/IPA.Loader/AntiMalware/WinAPI/IAntimalware.cs @@ -0,0 +1,58 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace IPA.AntiMalware.WinAPI +{ + [Guid("82d29c2e-f062-44e6-b5c9-3d9a2f24a2df")] + [ComVisible(true)] + internal interface IAntimalware + { + void Scan([In] IAmsiStream stream, [Out] out AmsiResult result, [Out] out IAntimalwareProvider provider); + void CloseSession([In] ulong session); + } + + [Guid("3e47f2e5-81d4-4d3b-897f-545096770373")] + [ComVisible(true)] + internal interface IAmsiStream + { + unsafe void GetAttribute([In] AmsiAttribute attribute, [In] uint dataSize, [Out] byte* buffer, out uint writtenData); + unsafe void Read([In] ulong position, [In] uint dataSize, [Out] byte* buffer, out uint readSize); + } + + [Guid("b2cabfe3-fe04-42b1-a5df-08d483d4d125")] + [ComVisible(true)] + internal interface IAntimalwareProvider + { + [return: MarshalAs(UnmanagedType.LPWStr)] string DisplayName(); + + AmsiResult Scan([In] IAmsiStream stream); + void CloseSession([In] ulong session); + } + + internal enum AmsiResult + { + Clean = 0, + NotDetected = 1, + BlockedByAdminStart = 0x4000, + BlockedByAdminEnd = 0x4fff, + Detected = 32768 + } + + internal enum AmsiAttribute + { + AppName = 0, + ContentName = 1, + ContentSize = 2, + ContentAddress = 3, + Session = 4, + RedirectChainSize = 5, + RedirectChainAddress = 6, + AllSize = 7, + AllAddress = 8, + } +} diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj index 3cc88a4f..6d3ff5e4 100644 --- a/IPA.Loader/IPA.Loader.csproj +++ b/IPA.Loader/IPA.Loader.csproj @@ -69,6 +69,11 @@ + + + + + diff --git a/IPA.Loader/Loader/LibLoader.cs b/IPA.Loader/Loader/LibLoader.cs index 1e1ecdd8..3f0a0076 100644 --- a/IPA.Loader/Loader/LibLoader.cs +++ b/IPA.Loader/Loader/LibLoader.cs @@ -1,248 +1,248 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Linq; -using IPA.Logging; -using IPA.Utilities; -using Mono.Cecil; -#if NET3 -using Net3_Proxy; -using Directory = Net3_Proxy.Directory; -using Path = Net3_Proxy.Path; -using File = Net3_Proxy.File; -#endif - -namespace IPA.Loader -{ - internal class CecilLibLoader : BaseAssemblyResolver - { - private static readonly string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name; - private static readonly string CurrentAssemblyPath = Assembly.GetExecutingAssembly().Location; - - public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) - { - LibLoader.SetupAssemblyFilenames(); - - if (name.Name == CurrentAssemblyName) - return AssemblyDefinition.ReadAssembly(CurrentAssemblyPath, parameters); - - if (LibLoader.FilenameLocations.TryGetValue($"{name.Name}.dll", out var path)) - { - if (File.Exists(path)) - return AssemblyDefinition.ReadAssembly(path, parameters); - } - else if (LibLoader.FilenameLocations.TryGetValue($"{name.Name}.{name.Version}.dll", out path)) - { - if (File.Exists(path)) - return AssemblyDefinition.ReadAssembly(path, parameters); - } - - - return base.Resolve(name, parameters); - } - } - - internal static class LibLoader - { - internal static string LibraryPath => Path.Combine(Environment.CurrentDirectory, "Libs"); - internal static string NativeLibraryPath => Path.Combine(LibraryPath, "Native"); - internal static Dictionary FilenameLocations; - - internal static void Configure() - { - SetupAssemblyFilenames(true); - AppDomain.CurrentDomain.AssemblyResolve -= AssemblyLibLoader; - AppDomain.CurrentDomain.AssemblyResolve += AssemblyLibLoader; - } - - internal static void SetupAssemblyFilenames(bool force = false) - { - if (FilenameLocations == null || force) - { - FilenameLocations = new Dictionary(); - +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Linq; +using IPA.Logging; +using IPA.Utilities; +using Mono.Cecil; +#if NET3 +using Net3_Proxy; +using Directory = Net3_Proxy.Directory; +using Path = Net3_Proxy.Path; +using File = Net3_Proxy.File; +#endif + +namespace IPA.Loader +{ + internal class CecilLibLoader : BaseAssemblyResolver + { + private static readonly string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name; + private static readonly string CurrentAssemblyPath = Assembly.GetExecutingAssembly().Location; + + public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) + { + LibLoader.SetupAssemblyFilenames(); + + if (name.Name == CurrentAssemblyName) + return AssemblyDefinition.ReadAssembly(CurrentAssemblyPath, parameters); + + if (LibLoader.FilenameLocations.TryGetValue($"{name.Name}.dll", out var path)) + { + if (File.Exists(path)) + return AssemblyDefinition.ReadAssembly(path, parameters); + } + else if (LibLoader.FilenameLocations.TryGetValue($"{name.Name}.{name.Version}.dll", out path)) + { + if (File.Exists(path)) + return AssemblyDefinition.ReadAssembly(path, parameters); + } + + + return base.Resolve(name, parameters); + } + } + + internal static class LibLoader + { + internal static string LibraryPath => Path.Combine(Environment.CurrentDirectory, "Libs"); + internal static string NativeLibraryPath => Path.Combine(LibraryPath, "Native"); + internal static Dictionary FilenameLocations; + + internal static void Configure() + { + SetupAssemblyFilenames(true); + AppDomain.CurrentDomain.AssemblyResolve -= AssemblyLibLoader; + AppDomain.CurrentDomain.AssemblyResolve += AssemblyLibLoader; + } + + internal static void SetupAssemblyFilenames(bool force = false) + { + if (FilenameLocations == null || force) + { + FilenameLocations = new Dictionary(); + foreach (var fn in TraverseTree(LibraryPath, s => s != NativeLibraryPath)) { - if (FilenameLocations.ContainsKey(fn.Name)) - Log(Logger.Level.Critical, $"Multiple instances of {fn.Name} exist in Libs! Ignoring {fn.FullName}"); + if (FilenameLocations.ContainsKey(fn.Name)) + Log(Logger.Level.Critical, $"Multiple instances of {fn.Name} exist in Libs! Ignoring {fn.FullName}"); else FilenameLocations.Add(fn.Name, fn.FullName); - } - - if (!SetDefaultDllDirectories(LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_SYSTEM32 - | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_APPLICATION_DIR)) - { - var err = new Win32Exception(); - Log(Logger.Level.Critical, $"Error configuring DLL search path"); - Log(Logger.Level.Critical, err); - return; - } - - static void AddDir(string path) - { - var retPtr = AddDllDirectory(path); - if (retPtr == IntPtr.Zero) - { - var err = new Win32Exception(); - Log(Logger.Level.Warning, $"Could not add DLL directory {path}"); - Log(Logger.Level.Warning, err); - } - } - - if (Directory.Exists(NativeLibraryPath)) - { - AddDir(NativeLibraryPath); - _ = TraverseTree(NativeLibraryPath, dir => - { // this is a terrible hack for iterating directories - AddDir(dir); return true; - }).All(f => true); // force it to iterate all - } - - //var unityData = Directory.EnumerateDirectories(Environment.CurrentDirectory, "*_Data").First(); - //AddDir(Path.Combine(unityData, "Plugins")); - - foreach (var dir in Environment.GetEnvironmentVariable("path") - .Split(Path.PathSeparator) + } + + if (!SetDefaultDllDirectories(LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_SYSTEM32 + | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_APPLICATION_DIR)) + { + var err = new Win32Exception(); + Log(Logger.Level.Critical, $"Error configuring DLL search path"); + Log(Logger.Level.Critical, err); + return; + } + + static void AddDir(string path) + { + var retPtr = AddDllDirectory(path); + if (retPtr == IntPtr.Zero) + { + var err = new Win32Exception(); + Log(Logger.Level.Warning, $"Could not add DLL directory {path}"); + Log(Logger.Level.Warning, err); + } + } + + if (Directory.Exists(NativeLibraryPath)) + { + AddDir(NativeLibraryPath); + _ = TraverseTree(NativeLibraryPath, dir => + { // this is a terrible hack for iterating directories + AddDir(dir); return true; + }).All(f => true); // force it to iterate all + } + + //var unityData = Directory.EnumerateDirectories(Environment.CurrentDirectory, "*_Data").First(); + //AddDir(Path.Combine(unityData, "Plugins")); + + foreach (var dir in Environment.GetEnvironmentVariable("path") + .Split(Path.PathSeparator) .Select(Environment.ExpandEnvironmentVariables)) { AddDir(dir); - } - } - } - - public static Assembly AssemblyLibLoader(object source, ResolveEventArgs e) - { - var asmName = new AssemblyName(e.Name); - return LoadLibrary(asmName); - } - - internal static Assembly LoadLibrary(AssemblyName asmName) - { - Log(Logger.Level.Debug, $"Resolving library {asmName}"); - - SetupAssemblyFilenames(); - - var testFile = $"{asmName.Name}.dll"; - Log(Logger.Level.Debug, $"Looking for file {asmName.Name}.dll"); - - if (FilenameLocations.TryGetValue(testFile, out var path)) - { - Log(Logger.Level.Debug, $"Found file {testFile} as {path}"); - if (File.Exists(path)) - return Assembly.LoadFrom(path); - - Log(Logger.Level.Critical, $"but {path} no longer exists!"); - } - else if (FilenameLocations.TryGetValue(testFile = $"{asmName.Name}.{asmName.Version}.dll", out path)) - { - Log(Logger.Level.Debug, $"Found file {testFile} as {path}"); - Log(Logger.Level.Warning, $"File {testFile} should be renamed to just {asmName.Name}.dll"); - if (File.Exists(path)) - return Assembly.LoadFrom(path); - - Log(Logger.Level.Critical, $"but {path} no longer exists!"); - } - - Log(Logger.Level.Critical, $"No library {asmName} found"); - - return null; - } - - internal static void Log(Logger.Level lvl, string message) - { // multiple proxy methods to delay loading of assemblies until it's done - if (Logger.LogCreated) - AssemblyLibLoaderCallLogger(lvl, message); - else - if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0) - Console.WriteLine($"[{lvl}] {message}"); - } - internal static void Log(Logger.Level lvl, Exception message) - { // multiple proxy methods to delay loading of assemblies until it's done - if (Logger.LogCreated) - AssemblyLibLoaderCallLogger(lvl, message); - else - if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0) - Console.WriteLine($"[{lvl}] {message}"); - } - - private static void AssemblyLibLoaderCallLogger(Logger.Level lvl, string message) => Logger.libLoader.Log(lvl, message); - private static void AssemblyLibLoaderCallLogger(Logger.Level lvl, Exception message) => Logger.libLoader.Log(lvl, message); - - // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-tree - private static IEnumerable TraverseTree(string root, Func dirValidator = null) - { - if (dirValidator == null) dirValidator = s => true; - - var dirs = new Stack(32); - - if (!Directory.Exists(root)) - throw new ArgumentException("Directory does not exist", nameof(root)); - dirs.Push(root); - - while (dirs.Count > 0) - { - string currentDir = dirs.Pop(); - string[] subDirs; - try - { - subDirs = Directory.GetDirectories(currentDir); - } - catch (UnauthorizedAccessException) - { continue; } - catch (DirectoryNotFoundException) - { continue; } - - string[] files; - try - { - files = Directory.GetFiles(currentDir); - } - catch (UnauthorizedAccessException) - { continue; } - catch (DirectoryNotFoundException) - { continue; } - - foreach (string str in subDirs) - if (dirValidator(str)) dirs.Push(str); - - foreach (string file in files) - { - FileInfo nextValue; - try - { - nextValue = new FileInfo(file); - } - catch (FileNotFoundException) - { continue; } - - yield return nextValue; - } - } - } - + } + } + } + + public static Assembly AssemblyLibLoader(object source, ResolveEventArgs e) + { + var asmName = new AssemblyName(e.Name); + return LoadLibrary(asmName); + } + + internal static Assembly LoadLibrary(AssemblyName asmName) + { + Log(Logger.Level.Debug, $"Resolving library {asmName}"); + + SetupAssemblyFilenames(); + + var testFile = $"{asmName.Name}.dll"; + Log(Logger.Level.Debug, $"Looking for file {asmName.Name}.dll"); + + if (FilenameLocations.TryGetValue(testFile, out var path)) + { + Log(Logger.Level.Debug, $"Found file {testFile} as {path}"); + if (File.Exists(path)) + return Assembly.LoadFrom(path); + + Log(Logger.Level.Critical, $"but {path} no longer exists!"); + } + else if (FilenameLocations.TryGetValue(testFile = $"{asmName.Name}.{asmName.Version}.dll", out path)) + { + Log(Logger.Level.Debug, $"Found file {testFile} as {path}"); + Log(Logger.Level.Warning, $"File {testFile} should be renamed to just {asmName.Name}.dll"); + if (File.Exists(path)) + return Assembly.LoadFrom(path); + + Log(Logger.Level.Critical, $"but {path} no longer exists!"); + } + + Log(Logger.Level.Critical, $"No library {asmName} found"); + + return null; + } + + internal static void Log(Logger.Level lvl, string message) + { // multiple proxy methods to delay loading of assemblies until it's done + if (Logger.LogCreated) + AssemblyLibLoaderCallLogger(lvl, message); + else + if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0) + Console.WriteLine($"[{lvl}] {message}"); + } + internal static void Log(Logger.Level lvl, Exception message) + { // multiple proxy methods to delay loading of assemblies until it's done + if (Logger.LogCreated) + AssemblyLibLoaderCallLogger(lvl, message); + else + if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0) + Console.WriteLine($"[{lvl}] {message}"); + } + + private static void AssemblyLibLoaderCallLogger(Logger.Level lvl, string message) => Logger.libLoader.Log(lvl, message); + private static void AssemblyLibLoaderCallLogger(Logger.Level lvl, Exception message) => Logger.libLoader.Log(lvl, message); + + // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-tree + private static IEnumerable TraverseTree(string root, Func dirValidator = null) + { + if (dirValidator == null) dirValidator = s => true; + + var dirs = new Stack(32); + + if (!Directory.Exists(root)) + throw new ArgumentException("Directory does not exist", nameof(root)); + dirs.Push(root); + + while (dirs.Count > 0) + { + string currentDir = dirs.Pop(); + string[] subDirs; + try + { + subDirs = Directory.GetDirectories(currentDir); + } + catch (UnauthorizedAccessException) + { continue; } + catch (DirectoryNotFoundException) + { continue; } + + string[] files; + try + { + files = Directory.GetFiles(currentDir); + } + catch (UnauthorizedAccessException) + { continue; } + catch (DirectoryNotFoundException) + { continue; } + + foreach (string str in subDirs) + if (dirValidator(str)) dirs.Push(str); + + foreach (string file in files) + { + FileInfo nextValue; + try + { + nextValue = new FileInfo(file); + } + catch (FileNotFoundException) + { continue; } + + yield return nextValue; + } + } + } + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] -#if NET461 +#if NET461 [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] -#endif - private static extern IntPtr AddDllDirectory(string lpPathName); - - [Flags] - private enum LoadLibraryFlags : uint - { - None = 0, - LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200, - LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000, - LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800, - LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400, - } - +#endif + private static extern IntPtr AddDllDirectory(string lpPathName); + + [Flags] + private enum LoadLibraryFlags : uint + { + None = 0, + LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000, + LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800, + LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400, + } + [DllImport("kernel32.dll", SetLastError = true)] -#if NET461 +#if NET461 [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] -#endif - private static extern bool SetDefaultDllDirectories(LoadLibraryFlags dwFlags); - } -} +#endif + private static extern bool SetDefaultDllDirectories(LoadLibraryFlags dwFlags); + } +}