fix(patch): stop the deployed launcher inheriting a read-only flag

This commit is contained in:
kitbyte
2026-08-29 21:50:30 +03:00
parent 2cac40558d
commit df9aedcadc
5 changed files with 42 additions and 15 deletions
+2
View File
@@ -152,6 +152,8 @@ namespace AsarSharp.AsarFileSystem
return;
}
// A read-only or hidden archive would fail the swap the same way an overwrite does.
Extensions.ClearAttributes(dest);
// File.Replace swaps in one step, so dest is never observed missing or half-written.
File.Replace(tempPath, dest, null, true);
}
+23 -1
View File
@@ -153,6 +153,28 @@ namespace AsarSharp.Utils
return result;
}
/// <summary>
/// Overwrites <paramref name="destination"/>, clearing attributes on both ends. CopyFile
/// carries the source's ReadOnly flag onto the copy and then refuses to overwrite what it
/// produced, failing with "Access to the path is denied" - so one read-only source (an exe
/// run straight out of a .zip, say) poisons the destination for every later run.
/// </summary>
public static void CopyOver(string source, string destination)
{
ClearAttributes(destination);
File.Copy(source, destination, true);
ClearAttributes(destination);
}
/// <summary>Resets a file to Normal: ReadOnly, Hidden and System all block an overwrite.</summary>
public static void ClearAttributes(string path)
{
if (File.Exists(path))
{
File.SetAttributes(path, FileAttributes.Normal);
}
}
public static void CopyDirectory(string sourceDir, string destinationDir)
{
Directory.CreateDirectory(destinationDir);
@@ -160,7 +182,7 @@ namespace AsarSharp.Utils
foreach (var file in Directory.GetFiles(sourceDir))
{
var destFile = Path.Combine(destinationDir, Path.GetFileName(file));
File.Copy(file, destFile, true);
CopyOver(file, destFile);
}
foreach (var dir in Directory.GetDirectories(sourceDir))
+1
View File
@@ -22,6 +22,7 @@ The newest entry must match the version in `WandEnhancer/Properties/AssemblyInfo
- Fixed the "Buy Pro" banner still showing after a successful patch, and Pro not activating on newer clients.
- Fixed the Enhancer closing itself when any button was pressed. #184
- A failed patch now puts your original Wand files back instead of leaving a half-patched install behind. Packing also builds the archive beside the old one and swaps it in at the end, so a failure can no longer destroy `app.asar`. #221
- Fixed patching and *Restore* both failing with "Access to the path is denied" after the first successful patch. Copying carried the read-only flag from the patcher onto the launcher it installs, and then refused to overwrite what it had written - so running WandEnhancer straight out of the downloaded `.zip`, which Windows marks read-only, broke every later run. #214
- Fixed a half-written backup reporting the installation as patched, which blocked patching and restore at the same time.
- Fixed invalid ASAR integrity metadata produced from short reads, which could yield an archive the client rejects. #170
- Fixed the packer silently dropping files it could not read, for example while Wand was still running.
+14 -11
View File
@@ -277,7 +277,7 @@ namespace WandEnhancer.Core
int copied = 0;
foreach (var file in files.Where(WeModInstalls.IsJavaScriptFile).Distinct(StringComparer.OrdinalIgnoreCase))
{
File.Copy(file, GetAvailableScriptPath(destinationDir, Path.GetFileName(file)));
AsarSharp.Utils.Extensions.CopyOver(file, GetAvailableScriptPath(destinationDir, Path.GetFileName(file)));
copied++;
}
@@ -354,10 +354,10 @@ namespace WandEnhancer.Core
if (File.Exists(stubPath) && !File.Exists(stubBackup))
{
File.Copy(stubPath, stubBackup);
AsarSharp.Utils.Extensions.CopyOver(stubPath, stubBackup);
}
File.Copy(self, stubPath, true);
AsarSharp.Utils.Extensions.CopyOver(self, stubPath);
_logger("[ENHANCER] Launcher deployed to root directory", ELogType.Info);
}
@@ -401,12 +401,12 @@ namespace WandEnhancer.Core
if (!File.Exists(_backupPath))
{
_logger("[ENHANCER] Creating backup...", ELogType.Info);
File.Copy(_asarPath, _backupPath);
AsarSharp.Utils.Extensions.CopyOver(_asarPath, _backupPath);
}
else
{
_logger("[ENHANCER] Backup found, restoring pristine app.asar before patching...", ELogType.Info);
File.Copy(_backupPath, _asarPath, true);
AsarSharp.Utils.Extensions.CopyOver(_backupPath, _asarPath);
}
if (!Directory.Exists(_unpackedBackupPath) && Directory.Exists(_unpackedPath))
@@ -503,7 +503,7 @@ namespace WandEnhancer.Core
{
if (File.Exists(_backupPath))
{
File.Copy(_backupPath, _asarPath, true);
AsarSharp.Utils.Extensions.CopyOver(_backupPath, _asarPath);
}
if (Directory.Exists(_unpackedBackupPath))
@@ -533,7 +533,7 @@ namespace WandEnhancer.Core
}
ProcessTerminator.TryKillProcess(_weModConfig.BrandName);
File.Copy(_backupPath, _asarPath, true);
AsarSharp.Utils.Extensions.CopyOver(_backupPath, _asarPath);
if (Directory.Exists(_unpackedPath))
{
@@ -555,14 +555,17 @@ namespace WandEnhancer.Core
string stubBackup = stubPath + StubBackupSuffix;
if (File.Exists(stubBackup))
{
File.Copy(stubBackup, stubPath, true);
AsarSharp.Utils.Extensions.CopyOver(stubBackup, stubPath);
File.Delete(stubBackup);
}
string autoPatchConfig = Path.Combine(squirrelRoot, Constants.AutoPatchConfigFileName);
if (File.Exists(autoPatchConfig))
foreach (var leftover in new[] { Constants.AutoPatchConfigFileName, LauncherLog.FileName })
{
File.Delete(autoPatchConfig);
string path = Path.Combine(squirrelRoot, leftover);
if (File.Exists(path))
{
File.Delete(path);
}
}
File.Delete(_backupPath);
+2 -3
View File
@@ -58,8 +58,7 @@ namespace WandEnhancer.Core
// Electron dies a second or two after a renderer fails, which is past the detach.
// Watching that window is the only way the exit code reaches the log.
private const int PostDetachWatchMs = 5000;
/// <summary>Electron's exit code when the ASAR integrity fuse rejects the archive.</summary>
private const int AsarIntegrityExitCode = -36861;
private static readonly byte[] Sentinel =
@@ -226,7 +225,7 @@ namespace WandEnhancer.Core
log?.Invoke($"Wand exited right after detach with code {DescribeCode(exitCode)}.", ELogType.Error);
}
/// <summary>Names the exit and exception codes that actually turn up when Wand will not start.</summary>
private static string DescribeCode(int code)
{
switch (code)