mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-09-04 08:13:18 +00:00
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:
@@ -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);
|
||||
|
||||
@@ -434,33 +434,23 @@ namespace WandEnhancer.Core
|
||||
throw new Exception("app.asar not found");
|
||||
}
|
||||
|
||||
// Everything past this point mutates the installation. A half-applied patch does
|
||||
// not boot - the fuse is only cleared by the deployed launcher, so a patched
|
||||
// app.asar without it dies with -36861 - so failure has to put the files back.
|
||||
try
|
||||
{
|
||||
_logger("[ENHANCER] Extracting app.asar...", ELogType.Info);
|
||||
AsarExtractor.ExtractAll(_asarPath, _unpackedPath);
|
||||
ExtractSources();
|
||||
PatchAsar();
|
||||
InjectRemotePanelFiles();
|
||||
PackSources();
|
||||
DeployLauncher();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
throw new Exception($"[ENHANCER] Failed to unpack app.asar: {e.Message}", e);
|
||||
RollbackQuietly();
|
||||
throw;
|
||||
}
|
||||
|
||||
PatchAsar();
|
||||
InjectRemotePanelFiles();
|
||||
|
||||
try
|
||||
{
|
||||
new AsarCreator(_unpackedPath, _asarPath, new CreateOptions
|
||||
{
|
||||
Unpack = new Regex(@"^static\\unpacked.*$")
|
||||
}).CreatePackageWithOptions();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"[ENHANCER] Failed to pack app.asar: {e.Message}", e);
|
||||
}
|
||||
|
||||
DeployLauncher();
|
||||
|
||||
// enhancer.json only exists to drive auto-patch. Without it the launcher still
|
||||
// runs Wand (fuse patch only), so drop it when the user opts out.
|
||||
if (_config.AutoApplyAfterUpdate)
|
||||
@@ -475,6 +465,66 @@ namespace WandEnhancer.Core
|
||||
_logger("[ENHANCER] Done!", ELogType.Success);
|
||||
}
|
||||
|
||||
private void ExtractSources()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger("[ENHANCER] Extracting app.asar...", ELogType.Info);
|
||||
AsarExtractor.ExtractAll(_asarPath, _unpackedPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"[ENHANCER] Failed to unpack app.asar: {e.Message}", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void PackSources()
|
||||
{
|
||||
try
|
||||
{
|
||||
new AsarCreator(_unpackedPath, _asarPath, new CreateOptions
|
||||
{
|
||||
Unpack = new Regex(@"^static\\unpacked.*$")
|
||||
}).CreatePackageWithOptions();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"[ENHANCER] Failed to pack app.asar: {e.Message}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Best-effort restore after a failed patch. Never throws: the caller is already
|
||||
/// propagating the real failure and it must not be replaced by a cleanup error.
|
||||
/// </summary>
|
||||
private void RollbackQuietly()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_backupPath))
|
||||
{
|
||||
File.Copy(_backupPath, _asarPath, true);
|
||||
}
|
||||
|
||||
if (Directory.Exists(_unpackedBackupPath))
|
||||
{
|
||||
if (Directory.Exists(_unpackedPath))
|
||||
{
|
||||
Directory.Delete(_unpackedPath, true);
|
||||
}
|
||||
|
||||
AsarSharp.Utils.Extensions.CopyDirectory(_unpackedBackupPath, _unpackedPath);
|
||||
}
|
||||
|
||||
_logger("[ENHANCER] Patch failed - the original Wand files were restored.", ELogType.Warn);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger($"[ENHANCER] Patch failed and the rollback did not finish: {e.Message}. " +
|
||||
"Use Restore before launching Wand.", ELogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public void Restore()
|
||||
{
|
||||
if (!File.Exists(_backupPath) || !Directory.Exists(_unpackedBackupPath))
|
||||
|
||||
Reference in New Issue
Block a user