diff --git a/IPA/IPA.csproj b/IPA/IPA.csproj index a1c46cb..0bd1107 100644 --- a/IPA/IPA.csproj +++ b/IPA/IPA.csproj @@ -63,6 +63,7 @@ + diff --git a/IPA/PatchContext.cs b/IPA/PatchContext.cs index c8a3f17..21150c2 100644 --- a/IPA/PatchContext.cs +++ b/IPA/PatchContext.cs @@ -30,6 +30,7 @@ namespace IPA public string IPARoot { get; private set; } public string ShortcutPath { get; private set; } public string IPA { get; private set; } + public string BackupPath { get; private set; } private PatchContext() { } @@ -39,7 +40,7 @@ namespace IPA context.Args = args; 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.IPA = Assembly.GetExecutingAssembly().Location ?? Path.Combine(context.ProjectRoot, "IPA.exe"); context.LauncherPathSrc = Path.Combine(context.IPARoot, "Launcher.exe"); @@ -50,10 +51,12 @@ namespace IPA context.ManagedPath = Path.Combine(context.DataPathDst, "Managed"); context.EngineFile = Path.Combine(context.ManagedPath, "UnityEngine.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); context.ShortcutPath = Path.Combine(context.ProjectRoot, shortcutName) + ".lnk"; + Directory.CreateDirectory(context.BackupPath); + return context; } } diff --git a/IPA/Patcher/BackupManager.cs b/IPA/Patcher/BackupManager.cs index 1a2ff5e..511a22d 100644 --- a/IPA/Patcher/BackupManager.cs +++ b/IPA/Patcher/BackupManager.cs @@ -9,61 +9,27 @@ namespace IPA.Patcher { 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"; - - 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); + var backup = FindLatestBackup(context); if(backup != null) { - File.Delete(file); - File.Move(backup, file); + backup.Restore(); + backup.Delete(); return true; } return false; diff --git a/IPA/Patcher/BackupUnit.cs b/IPA/Patcher/BackupUnit.cs new file mode 100644 index 0000000..3949072 --- /dev/null +++ b/IPA/Patcher/BackupUnit.cs @@ -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 +{ + /// + /// A unit for backup. WIP. + /// + public class BackupUnit + { + public string Name { get; private set; } + + private DirectoryInfo _BackupPath; + private PatchContext _Context; + private List _Files = new List(); + + + + 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); + } + + /// + /// Adds a file to the list of changed files and backups it. + /// + /// + 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); + } + + /// + /// Reverts the changes made in this unit. + /// + 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!"); + } + } + } + + } +} diff --git a/IPA/Program.cs b/IPA/Program.cs index 4da0f66..3d36efa 100644 --- a/IPA/Program.cs +++ b/IPA/Program.cs @@ -58,11 +58,14 @@ namespace IPA { try { + var backup = new BackupUnit(context); + // Copying Console.WriteLine("Updating files... "); var nativePluginFolder = Path.Combine(context.DataPathDst, "Plugins"); 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!"); @@ -77,7 +80,7 @@ namespace IPA if (!patchedModule.IsPatched) { Console.Write("Patching UnityEngine.dll... "); - BackupManager.MakeBackup(context.EngineFile); + backup.Add(context.EngineFile); patchedModule.Patch(); Console.WriteLine("Done!"); } @@ -87,7 +90,7 @@ namespace IPA if (!virtualizedModule.IsVirtualized) { Console.Write("Virtualizing Assembly-Csharp.dll... "); - BackupManager.MakeBackup(context.AssemblyFile); + backup.Add(context.AssemblyFile); virtualizedModule.Virtualize(); Console.WriteLine("Done!"); } @@ -120,8 +123,8 @@ namespace IPA private static void Revert(PatchContext context) { - Console.Write("Restoring game assembly... "); - if(BackupManager.Restore(context.AssemblyFile)) + Console.Write("Restoring backup... "); + if(BackupManager.Restore(context)) { Console.WriteLine("Done!"); } else @@ -129,16 +132,7 @@ namespace IPA 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)) { Console.WriteLine("Deleting shortcut..."); @@ -209,22 +203,23 @@ namespace IPA yield return to; } - public static void CopyAll(DirectoryInfo source, DirectoryInfo target, Func> interceptor = null) + public static void CopyAll(DirectoryInfo source, DirectoryInfo target, bool aggressive, BackupUnit backup, Func> interceptor = null) { if(interceptor == null) { interceptor = PassThroughInterceptor; } - Directory.CreateDirectory(target.FullName); - // Copy each file into the new directory. foreach (FileInfo fi in source.GetFiles()) { 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); + backup.Add(targetFile); fi.CopyTo(targetFile.FullName, true); } } @@ -233,9 +228,8 @@ namespace IPA // Copy each subdirectory using recursion. foreach (DirectoryInfo diSourceSubDir in source.GetDirectories()) { - DirectoryInfo nextTargetSubDir = - target.CreateSubdirectory(diSourceSubDir.Name); - CopyAll(diSourceSubDir, nextTargetSubDir, interceptor); + DirectoryInfo nextTargetSubDir = new DirectoryInfo(Path.Combine(target.FullName, diSourceSubDir.Name)); + CopyAll(diSourceSubDir, nextTargetSubDir, aggressive, backup, interceptor); } } diff --git a/IPA/Properties/AssemblyInfo.cs b/IPA/Properties/AssemblyInfo.cs index b146dc6..c1dff3e 100644 --- a/IPA/Properties/AssemblyInfo.cs +++ b/IPA/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.0.0.0")] -[assembly: AssemblyFileVersion("3.0.0.0")] +[assembly: AssemblyVersion("3.1.1.0")] +[assembly: AssemblyFileVersion("3.1.1.0")]