diff --git a/Core/DirectoryLink.cs b/Core/DirectoryLink.cs index a2b8820bf..10f4e8dcd 100644 --- a/Core/DirectoryLink.cs +++ b/Core/DirectoryLink.cs @@ -90,12 +90,42 @@ public static bool TryGetTarget(string link, out string target) return !string.IsNullOrEmpty(target); } + public static void Remove(string link) + { + if (Platform.IsWindows) + { + if (Directory.Exists(link)) + { + var h = CreateFile(link, GenericWrite, FileShare.Write, IntPtr.Zero, + FileMode.Open, BackupSemantics | OpenReparsePoint, IntPtr.Zero); + if (!h.IsInvalid) + { + var junctionInfo = ReparseDataBuffer.Empty(); + if (!DeviceIoControl(h, FSCTL_DELETE_REPARSE_POINT, + ref junctionInfo, 8, + null, 0, + out _, IntPtr.Zero)) + { + throw new Kraken($"Failed to remove junction at {link}: {Marshal.GetLastWin32Error()}"); + } + h.Close(); + Directory.Delete(link); + } + } + } + else + { + File.Delete(link); + } + } + private const uint GenericWrite = 0x40000000u; private const uint BackupSemantics = 0x02000000u; private const uint OpenReparsePoint = 0x00200000u; - private const uint FSCTL_SET_REPARSE_POINT = 0x000900A4u; private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003u; + private const uint FSCTL_SET_REPARSE_POINT = 0x000900A4u; private const uint FSCTL_GET_REPARSE_POINT = 0x000900A8u; + private const uint FSCTL_DELETE_REPARSE_POINT = 0x000900ACu; [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern bool DeviceIoControl(SafeFileHandle hDevice, @@ -130,6 +160,20 @@ private struct ReparseDataBuffer [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8184)] public string PathBuffer; + public static ReparseDataBuffer Empty() + { + return new ReparseDataBuffer + { + ReparseTag = IO_REPARSE_TAG_MOUNT_POINT, + ReparseDataLength = 0, + SubstituteNameOffset = 0, + SubstituteNameLength = 0, + PrintNameOffset = 0, + PrintNameLength = 0, + PathBuffer = "", + }; + } + public static ReparseDataBuffer FromPath(string target, out int byteCount) { var fullTarget = $@"\??\{Path.GetFullPath(target)}"; diff --git a/Tests/Core/Utilities.cs b/Tests/Core/Utilities.cs index 106abd0a9..03984b2c8 100644 --- a/Tests/Core/Utilities.cs +++ b/Tests/Core/Utilities.cs @@ -61,6 +61,8 @@ public void CopyDirectory_WithSymlinks_MakesSymlinks() var fi3 = new FileInfo(Path.Combine(tempDir, "KSP-0.25", "GameData", "README.md")); Assert.IsFalse(fi3.Attributes.HasFlag(FileAttributes.ReparsePoint), "KSP-0.25/GameData/README.md should not be a symlink"); + + DirectoryLink.Remove(fi2.FullName); } [Test]