@@ -63,6 +63,7 @@ | |||
<ItemGroup> | |||
<Compile Include="PatchContext.cs" /> | |||
<Compile Include="Patcher\BackupManager.cs" /> | |||
<Compile Include="Patcher\BackupUnit.cs" /> | |||
<Compile Include="Patcher\Patcher.cs" /> | |||
<Compile Include="Patcher\Virtualizer.cs" /> | |||
<Compile Include="Program.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; | |||
} | |||
} | |||
@@ -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)); | |||
} | |||
private static string GetBackupName(string file) | |||
{ | |||
string backup = file + ".Original"; | |||
if (File.Exists(backup)) | |||
{ | |||
int i = 1; | |||
string backupBase = backup; | |||
while (File.Exists(backup)) | |||
{ | |||
backup = backupBase + i++; | |||
} | |||
} | |||
return backup; | |||
return new DirectoryInfo(context.BackupPath) | |||
.GetDirectories() | |||
.OrderByDescending(p => p.Name) | |||
.Select(p => BackupUnit.FromDirectory(p, context)) | |||
.FirstOrDefault(); | |||
} | |||
public static string FindLatestBackup(string file) | |||
public static bool HasBackup(PatchContext context) | |||
{ | |||
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; | |||
return FindLatestBackup(context) != null; | |||
} | |||
public static bool Restore(string file) | |||
public static bool Restore(PatchContext context) | |||
{ | |||
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; | |||
@@ -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!"); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -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<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) | |||
{ | |||
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); | |||
} | |||
} | |||
@@ -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")] |