diff --git a/src/LibHac/Common/HResult.cs b/src/LibHac/Common/HResult.cs index 3f0c3439..48fc4404 100644 --- a/src/LibHac/Common/HResult.cs +++ b/src/LibHac/Common/HResult.cs @@ -19,6 +19,7 @@ internal static class HResult public const int ERROR_ALREADY_EXISTS = unchecked((int)0x800700B7); public const int ERROR_DIRECTORY = unchecked((int)0x8007010B); public const int ERROR_SPACES_NOT_ENOUGH_DRIVES = unchecked((int)0x80E7000B); + public const int COR_E_IO = unchecked((int)0x80131620); public static Result HResultToHorizonResult(int hResult) => hResult switch { @@ -35,6 +36,7 @@ internal static class HResult ERROR_ALREADY_EXISTS => ResultFs.PathAlreadyExists.Value, ERROR_DIRECTORY => ResultFs.PathNotFound.Value, ERROR_SPACES_NOT_ENOUGH_DRIVES => ResultFs.UsableSpaceNotEnough.Value, + COR_E_IO => ResultFs.TargetLocked.Value, _ => ResultFs.UnexpectedInLocalFileSystemE.Value }; } diff --git a/src/LibHac/FsSystem/Impl/TargetLockedAvoidance.cs b/src/LibHac/FsSystem/Impl/TargetLockedAvoidance.cs index 840f9b57..0ba4302d 100644 --- a/src/LibHac/FsSystem/Impl/TargetLockedAvoidance.cs +++ b/src/LibHac/FsSystem/Impl/TargetLockedAvoidance.cs @@ -7,7 +7,7 @@ namespace LibHac.FsSystem.Impl; internal static class TargetLockedAvoidance { - private const int RetryCount = 2; + private const int RetryCount = 25; private const int SleepTimeMs = 2; // Allow usage outside of a Horizon context by using standard .NET APIs diff --git a/src/LibHac/FsSystem/LocalFileSystem.cs b/src/LibHac/FsSystem/LocalFileSystem.cs index b1640801..39664ad2 100644 --- a/src/LibHac/FsSystem/LocalFileSystem.cs +++ b/src/LibHac/FsSystem/LocalFileSystem.cs @@ -653,7 +653,28 @@ public class LocalFileSystem : IAttributeFileSystem try { - dir.Delete(recursive); + try + { + dir.Delete(recursive); + } + catch (Exception ex) when (ex.HResult is HResult.COR_E_IO or HResult.ERROR_ACCESS_DENIED) + { + if (recursive) + { + Result rc = DeleteDirectoryRecursivelyWithReadOnly(dir); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + // Try to delete read-only directories by first removing the read-only flag + if (dir.Attributes.HasFlag(FileAttributes.ReadOnly)) + { + dir.Attributes &= ~FileAttributes.ReadOnly; + } + + dir.Delete(false); + } + } } catch (Exception ex) when (ex.HResult < 0) { @@ -670,7 +691,20 @@ public class LocalFileSystem : IAttributeFileSystem try { - file.Delete(); + try + { + file.Delete(); + } + catch (UnauthorizedAccessException ex) when (ex.HResult == HResult.ERROR_ACCESS_DENIED) + { + // Try to delete read-only files by first removing the read-only flag. + if (file.Attributes.HasFlag(FileAttributes.ReadOnly)) + { + file.Attributes &= ~FileAttributes.ReadOnly; + } + + file.Delete(); + } } catch (Exception ex) when (ex.HResult < 0) { @@ -680,6 +714,49 @@ public class LocalFileSystem : IAttributeFileSystem return EnsureDeleted(file); } + private static Result DeleteDirectoryRecursivelyWithReadOnly(DirectoryInfo rootDir) + { + try + { + foreach (FileSystemInfo info in rootDir.EnumerateFileSystemInfos()) + { + if (info is FileInfo file) + { + // Check each file for the read-only flag before deleting. + if (file.Attributes.HasFlag(FileAttributes.ReadOnly)) + { + file.Attributes &= ~FileAttributes.ReadOnly; + } + + file.Delete(); + } + else if (info is DirectoryInfo dir) + { + Result rc = DeleteDirectoryRecursivelyWithReadOnly(dir); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + return ResultFs.UnexpectedInLocalFileSystemF.Log(); + } + } + + // The directory should be empty now. Remove any read-only flag and delete it. + if (rootDir.Attributes.HasFlag(FileAttributes.ReadOnly)) + { + rootDir.Attributes &= ~FileAttributes.ReadOnly; + } + + rootDir.Delete(true); + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return Result.Success; + } + private static Result CreateDirInternal(DirectoryInfo dir, NxFileAttributes attributes) { try