Implement better backup mechanism.

This commit is contained in:
Eusth 2017-02-22 00:34:07 +01:00
parent 1f298a4857
commit 06c85a93a5
6 changed files with 167 additions and 75 deletions

View file

@ -63,6 +63,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="PatchContext.cs" /> <Compile Include="PatchContext.cs" />
<Compile Include="Patcher\BackupManager.cs" /> <Compile Include="Patcher\BackupManager.cs" />
<Compile Include="Patcher\BackupUnit.cs" />
<Compile Include="Patcher\Patcher.cs" /> <Compile Include="Patcher\Patcher.cs" />
<Compile Include="Patcher\Virtualizer.cs" /> <Compile Include="Patcher\Virtualizer.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />

View file

@ -30,6 +30,7 @@ namespace IPA
public string IPARoot { get; private set; } public string IPARoot { get; private set; }
public string ShortcutPath { get; private set; } public string ShortcutPath { get; private set; }
public string IPA { get; private set; } public string IPA { get; private set; }
public string BackupPath { get; private set; }
private PatchContext() { } private PatchContext() { }
@ -39,7 +40,7 @@ namespace IPA
context.Args = args; context.Args = args;
context.Executable = args[0]; context.Executable = args[0];
context.ProjectRoot = Path.GetDirectoryName(context.Executable); context.ProjectRoot = new FileInfo(context.Executable).Directory.FullName;
context.IPARoot = Path.Combine(context.ProjectRoot, "IPA"); context.IPARoot = Path.Combine(context.ProjectRoot, "IPA");
context.IPA = Assembly.GetExecutingAssembly().Location ?? Path.Combine(context.ProjectRoot, "IPA.exe"); context.IPA = Assembly.GetExecutingAssembly().Location ?? Path.Combine(context.ProjectRoot, "IPA.exe");
context.LauncherPathSrc = Path.Combine(context.IPARoot, "Launcher.exe"); context.LauncherPathSrc = Path.Combine(context.IPARoot, "Launcher.exe");
@ -50,10 +51,12 @@ namespace IPA
context.ManagedPath = Path.Combine(context.DataPathDst, "Managed"); context.ManagedPath = Path.Combine(context.DataPathDst, "Managed");
context.EngineFile = Path.Combine(context.ManagedPath, "UnityEngine.dll"); context.EngineFile = Path.Combine(context.ManagedPath, "UnityEngine.dll");
context.AssemblyFile = Path.Combine(context.ManagedPath, "Assembly-Csharp.dll"); context.AssemblyFile = Path.Combine(context.ManagedPath, "Assembly-Csharp.dll");
context.BackupPath = Path.Combine(Path.Combine(context.IPARoot, "Backups"), context.ProjectName);
string shortcutName = string.Format("{0} (Patch & Launch)", context.ProjectName); string shortcutName = string.Format("{0} (Patch & Launch)", context.ProjectName);
context.ShortcutPath = Path.Combine(context.ProjectRoot, shortcutName) + ".lnk"; context.ShortcutPath = Path.Combine(context.ProjectRoot, shortcutName) + ".lnk";
Directory.CreateDirectory(context.BackupPath);
return context; return context;
} }
} }

View file

@ -9,61 +9,27 @@ namespace IPA.Patcher
{ {
public class BackupManager public class BackupManager
{ {
public static void MakeBackup(string file) public static BackupUnit FindLatestBackup(PatchContext context)
{ {
File.Copy(file, GetBackupName(file)); return new DirectoryInfo(context.BackupPath)
.GetDirectories()
.OrderByDescending(p => p.Name)
.Select(p => BackupUnit.FromDirectory(p, context))
.FirstOrDefault();
}
public static bool HasBackup(PatchContext context)
{
return FindLatestBackup(context) != null;
} }
private static string GetBackupName(string file) public static bool Restore(PatchContext context)
{ {
string backup = file + ".Original"; var backup = FindLatestBackup(context);
if (File.Exists(backup))
{
int i = 1;
string backupBase = backup;
while (File.Exists(backup))
{
backup = backupBase + i++;
}
}
return backup;
}
public static string FindLatestBackup(string file)
{
var directory = Path.GetDirectoryName(file);
var filename = Path.GetFileName(file);
var regex = new Regex(String.Format(@"^{0}\.Original\d*$", Regex.Escape(filename)));
var extractNumRegex = new Regex(@"\d+$");
string latestFile = null;
int latestNum = -1;
foreach(var f in Directory.GetFiles(directory))
{
if(regex.IsMatch(Path.GetFileName(f)))
{
var match = extractNumRegex.Match(f);
int number = match.Success ? int.Parse(match.Value) : 0;
if(number > latestNum)
{
latestNum = number;
latestFile = f;
}
}
}
return latestFile;
}
public static bool Restore(string file)
{
var backup = FindLatestBackup(file);
if(backup != null) if(backup != null)
{ {
File.Delete(file); backup.Restore();
File.Move(backup, file); backup.Delete();
return true; return true;
} }
return false; return false;

128
IPA/Patcher/BackupUnit.cs Normal file
View file

@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
namespace IPA.Patcher
{
/// <summary>
/// A unit for backup. WIP.
/// </summary>
public class BackupUnit
{
public string Name { get; private set; }
private DirectoryInfo _BackupPath;
private PatchContext _Context;
private List<string> _Files = new List<string>();
public BackupUnit(PatchContext context) : this(context, DateTime.Now.ToString("yyyy-MM-dd_h-mm-ss"))
{
}
private BackupUnit(PatchContext context, string name)
{
Name = name;
_Context = context;
_BackupPath = new DirectoryInfo(Path.Combine(_Context.BackupPath, Name));
}
public static BackupUnit FromDirectory(DirectoryInfo directory, PatchContext context)
{
var unit = new BackupUnit(context, directory.Name);
// Parse directory
foreach(var file in directory.GetFiles("*", SearchOption.AllDirectories)) {
var relativePath = file.FullName.Substring(directory.FullName.Length + 1);
unit._Files.Add(relativePath);
}
return unit;
}
public void Add(string file)
{
Add(new FileInfo(file));
}
internal void Delete()
{
_BackupPath.Delete(true);
}
/// <summary>
/// Adds a file to the list of changed files and backups it.
/// </summary>
/// <param name="path"></param>
public void Add(FileInfo file)
{
if(!file.FullName.StartsWith(_Context.ProjectRoot))
{
Console.Error.WriteLine("Invalid file path for backup! {0}", file);
return;
}
var relativePath = file.FullName.Substring(_Context.ProjectRoot.Length + 1);
var backupPath = new FileInfo(Path.Combine(_BackupPath.FullName, relativePath));
if(_Files.Contains(relativePath))
{
Console.WriteLine("Skipping backup of {0}", relativePath);
return;
}
// Copy over
backupPath.Directory.Create();
if (file.Exists)
{
file.CopyTo(backupPath.FullName);
} else
{
// Make empty file
backupPath.Create().Close();
}
// Add to list
_Files.Add(relativePath);
}
/// <summary>
/// Reverts the changes made in this unit.
/// </summary>
public void Restore()
{
foreach(var relativePath in _Files)
{
Console.WriteLine("Restoring {0}", relativePath);
// Original version
var backupFile = new FileInfo(Path.Combine(_BackupPath.FullName, relativePath));
var target = new FileInfo(Path.Combine(_Context.ProjectRoot, relativePath));
if (backupFile.Exists)
{
if (backupFile.Length > 0)
{
Console.WriteLine(" {0} => {1}", backupFile.FullName, target.FullName);
target.Directory.Create();
backupFile.CopyTo(target.FullName, true);
} else
{
Console.WriteLine(" x {0}", target.FullName);
if(target.Exists)
{
target.Delete();
}
}
} else {
Console.Error.WriteLine("Backup not found!");
}
}
}
}
}

View file

@ -58,11 +58,14 @@ namespace IPA
{ {
try try
{ {
var backup = new BackupUnit(context);
// Copying // Copying
Console.WriteLine("Updating files... "); Console.WriteLine("Updating files... ");
var nativePluginFolder = Path.Combine(context.DataPathDst, "Plugins"); var nativePluginFolder = Path.Combine(context.DataPathDst, "Plugins");
bool isFlat = Directory.Exists(nativePluginFolder) && Directory.GetFiles(nativePluginFolder).Any(f => f.EndsWith(".dll")); bool isFlat = Directory.Exists(nativePluginFolder) && Directory.GetFiles(nativePluginFolder).Any(f => f.EndsWith(".dll"));
CopyAll(new DirectoryInfo(context.DataPathSrc), new DirectoryInfo(context.DataPathDst), (from, to) => NativePluginInterceptor(from, to, new DirectoryInfo(nativePluginFolder), isFlat) ); bool force = !BackupManager.HasBackup(context) || context.Args.Contains("-f") || context.Args.Contains("--force");
CopyAll(new DirectoryInfo(context.DataPathSrc), new DirectoryInfo(context.DataPathDst), force, backup, (from, to) => NativePluginInterceptor(from, to, new DirectoryInfo(nativePluginFolder), isFlat) );
Console.WriteLine("Successfully updated files!"); Console.WriteLine("Successfully updated files!");
@ -77,7 +80,7 @@ namespace IPA
if (!patchedModule.IsPatched) if (!patchedModule.IsPatched)
{ {
Console.Write("Patching UnityEngine.dll... "); Console.Write("Patching UnityEngine.dll... ");
BackupManager.MakeBackup(context.EngineFile); backup.Add(context.EngineFile);
patchedModule.Patch(); patchedModule.Patch();
Console.WriteLine("Done!"); Console.WriteLine("Done!");
} }
@ -87,7 +90,7 @@ namespace IPA
if (!virtualizedModule.IsVirtualized) if (!virtualizedModule.IsVirtualized)
{ {
Console.Write("Virtualizing Assembly-Csharp.dll... "); Console.Write("Virtualizing Assembly-Csharp.dll... ");
BackupManager.MakeBackup(context.AssemblyFile); backup.Add(context.AssemblyFile);
virtualizedModule.Virtualize(); virtualizedModule.Virtualize();
Console.WriteLine("Done!"); Console.WriteLine("Done!");
} }
@ -120,8 +123,8 @@ namespace IPA
private static void Revert(PatchContext context) private static void Revert(PatchContext context)
{ {
Console.Write("Restoring game assembly... "); Console.Write("Restoring backup... ");
if(BackupManager.Restore(context.AssemblyFile)) if(BackupManager.Restore(context))
{ {
Console.WriteLine("Done!"); Console.WriteLine("Done!");
} else } else
@ -129,16 +132,7 @@ namespace IPA
Console.WriteLine("Already vanilla!"); Console.WriteLine("Already vanilla!");
} }
Console.Write("Restoring unity engine... ");
if(BackupManager.Restore(context.EngineFile))
{
Console.WriteLine("Done!");
}
else
{
Console.WriteLine("Already vanilla!");
}
if (File.Exists(context.ShortcutPath)) if (File.Exists(context.ShortcutPath))
{ {
Console.WriteLine("Deleting shortcut..."); Console.WriteLine("Deleting shortcut...");
@ -209,22 +203,23 @@ namespace IPA
yield return to; yield return to;
} }
public static void CopyAll(DirectoryInfo source, DirectoryInfo target, Func<FileInfo, FileInfo, IEnumerable<FileInfo>> interceptor = null) public static void CopyAll(DirectoryInfo source, DirectoryInfo target, bool aggressive, BackupUnit backup, Func<FileInfo, FileInfo, IEnumerable<FileInfo>> interceptor = null)
{ {
if(interceptor == null) if(interceptor == null)
{ {
interceptor = PassThroughInterceptor; interceptor = PassThroughInterceptor;
} }
Directory.CreateDirectory(target.FullName);
// Copy each file into the new directory. // Copy each file into the new directory.
foreach (FileInfo fi in source.GetFiles()) foreach (FileInfo fi in source.GetFiles())
{ {
foreach(var targetFile in interceptor(fi, new FileInfo(Path.Combine(target.FullName, fi.Name)))) { foreach(var targetFile in interceptor(fi, new FileInfo(Path.Combine(target.FullName, fi.Name)))) {
if (!targetFile.Exists || targetFile.LastWriteTimeUtc < fi.LastWriteTimeUtc) if (!targetFile.Exists || targetFile.LastWriteTimeUtc < fi.LastWriteTimeUtc || aggressive)
{ {
targetFile.Directory.Create();
Console.WriteLine(@"Copying {0}", targetFile.FullName); Console.WriteLine(@"Copying {0}", targetFile.FullName);
backup.Add(targetFile);
fi.CopyTo(targetFile.FullName, true); fi.CopyTo(targetFile.FullName, true);
} }
} }
@ -233,9 +228,8 @@ namespace IPA
// Copy each subdirectory using recursion. // Copy each subdirectory using recursion.
foreach (DirectoryInfo diSourceSubDir in source.GetDirectories()) foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
{ {
DirectoryInfo nextTargetSubDir = DirectoryInfo nextTargetSubDir = new DirectoryInfo(Path.Combine(target.FullName, diSourceSubDir.Name));
target.CreateSubdirectory(diSourceSubDir.Name); CopyAll(diSourceSubDir, nextTargetSubDir, aggressive, backup, interceptor);
CopyAll(diSourceSubDir, nextTargetSubDir, interceptor);
} }
} }

View file

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.0.0.0")] [assembly: AssemblyVersion("3.1.1.0")]
[assembly: AssemblyFileVersion("3.0.0.0")] [assembly: AssemblyFileVersion("3.1.1.0")]