fix(patch): roll back the installation when patching fails

Patching mutated the install in place with no recovery, so a failure left a
partly patched Wand behind: rewritten bundles in app.asar.unpacked, or a patched
app.asar with no launcher deployed, which cannot clear the fuse and fails with
-36861. Restore both backups on any failure and rethrow the original error.

Pack the archive into a sibling file and swap it in at the end. Writing straight
into app.asar truncated it when the stream opened, so a failed pack destroyed
the archive it was replacing.
This commit is contained in:
kitbyte
2026-08-29 18:44:24 +03:00
parent 8b83750bf2
commit 9bcb4bb991
2 changed files with 119 additions and 22 deletions
+48 -1
View File
@@ -128,7 +128,54 @@ namespace AsarSharp.AsarFileSystem
var buf = new byte[StreamBufferSize];
var blockBuf = new byte[4 * 1024 * 1024]; // shared across all files — avoids 4MB alloc per file
using (var fs = new FileStream(dest, FileMode.Create, FileAccess.Write, FileShare.None, StreamBufferSize, FileOptions.SequentialScan))
// Build beside the target and swap at the end. Writing straight into dest truncates
// it on open, so any failure mid-write left the caller with a destroyed archive.
string tempPath = dest + ".building";
try
{
WriteArchive(tempPath, dest, fileSystem, lists, serializerSettings,
headerPickle, sizePickle, sizePickleSize, buf, blockBuf);
ReplaceFile(tempPath, dest);
}
catch
{
TryDelete(tempPath);
throw;
}
}
private static void ReplaceFile(string tempPath, string dest)
{
if (!File.Exists(dest))
{
File.Move(tempPath, dest);
return;
}
// File.Replace swaps in one step, so dest is never observed missing or half-written.
File.Replace(tempPath, dest, null, true);
}
private static void TryDelete(string path)
{
try
{
if (File.Exists(path))
{
File.Delete(path);
}
}
catch (Exception e) when (e is IOException || e is UnauthorizedAccessException)
{
// Leftover build file only wastes space; the real failure is already propagating.
}
}
private static void WriteArchive(string archivePath, string dest, Filesystem fileSystem,
FilesystemFilesAndLinks lists, JsonSerializerSettings serializerSettings,
Pickle headerPickle, Pickle sizePickle, int sizePickleSize, byte[] buf, byte[] blockBuf)
{
using (var fs = new FileStream(archivePath, FileMode.Create, FileAccess.Write, FileShare.None, StreamBufferSize, FileOptions.SequentialScan))
{
sizePickle.WriteTo(fs);
headerPickle.WriteTo(fs);