From a2364569605cbae7e1c3e6232c9e0530daccc900 Mon Sep 17 00:00:00 2001 From: kitbyte Date: Sat, 29 Aug 2026 22:02:07 +0300 Subject: [PATCH] feat(patch): report what is blocking a denied copy An access-denied copy now names the destination's attributes, owner and the account running the patcher. --- AsarSharp/Utils/Extensions.cs | 65 ++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/AsarSharp/Utils/Extensions.cs b/AsarSharp/Utils/Extensions.cs index 0b95e09..7b18bb8 100644 --- a/AsarSharp/Utils/Extensions.cs +++ b/AsarSharp/Utils/Extensions.cs @@ -1,6 +1,8 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; using System.Text; namespace AsarSharp.Utils @@ -162,16 +164,71 @@ namespace AsarSharp.Utils public static void CopyOver(string source, string destination) { ClearAttributes(destination); - File.Copy(source, destination, true); + + try + { + File.Copy(source, destination, true); + } + catch (UnauthorizedAccessException e) + { + throw new UnauthorizedAccessException($"{e.Message} {DescribeDenial(destination)}", e); + } + ClearAttributes(destination); } - /// Resets a file to Normal: ReadOnly, Hidden and System all block an overwrite. + /// + /// "Access to the path is denied" names none of the half-dozen things that cause it, and + /// the state is gone by the time anyone reads the report. Attributes were already cleared + /// above, which rules the most common cause out before the message is even written. + /// + private static string DescribeDenial(string destination) + { + if (Directory.Exists(destination)) + { + return "The destination is a directory, not a file."; + } + + if (!File.Exists(destination)) + { + return "The destination does not exist, so the containing folder is refusing new files."; + } + + return $"Attributes {File.GetAttributes(destination)}, owner {DescribeOwner(destination)}, " + + $"running as {Environment.UserName}. A read-only flag, antivirus, folder " + + "permissions or a delete still pending on the file are the usual causes."; + } + + private static string DescribeOwner(string path) + { + try + { + return File.GetAccessControl(path).GetOwner(typeof(NTAccount)).Value; + } + catch (Exception e) when (e is IdentityNotMappedException || e is UnauthorizedAccessException || + e is InvalidOperationException || e is PrivilegeNotHeldException || + e is PlatformNotSupportedException) + { + return "unreadable"; + } + } + + /// + /// Resets a file to Normal: ReadOnly, Hidden and System all block an overwrite. Best + /// effort - a file that denies even this reports it properly through the write that follows. + /// public static void ClearAttributes(string path) { - if (File.Exists(path)) + try { - File.SetAttributes(path, FileAttributes.Normal); + if (File.Exists(path)) + { + File.SetAttributes(path, FileAttributes.Normal); + } + } + catch (Exception e) when (e is UnauthorizedAccessException || e is IOException) + { + // Swallowed so the caller's own failure is the one that surfaces. } }