diff --git a/IPA/IPA.csproj b/IPA/IPA.csproj index ad9c6aa..a1c46cb 100644 --- a/IPA/IPA.csproj +++ b/IPA/IPA.csproj @@ -54,17 +54,20 @@ + + + diff --git a/IPA/PatchContext.cs b/IPA/PatchContext.cs new file mode 100644 index 0000000..c5c5461 --- /dev/null +++ b/IPA/PatchContext.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace IPA +{ + public class PatchContext + { + /// + /// Gets the filename of the executable. + /// + public string Executable { get; private set; } + + /// + /// Gets the path to the launcher executable (in the IPA folder) + /// + public string LauncherPathSrc { get; private set; } + public string DataPathSrc { get; private set; } + public string PluginsFolder { get; private set; } + public string ProjectName { get; private set; } + public string DataPathDst { get; private set; } + public string ManagedPath { get; private set; } + public string EngineFile { get; private set; } + public string AssemblyFile { get; private set; } + public string[] Args { get; private set; } + public string ProjectRoot { get; private set; } + public string IPARoot { get; private set; } + public string ShortcutPath { get; private set; } + + private PatchContext() { } + + public static PatchContext Create(String[] args) + { + var context = new PatchContext(); + + context.Args = args; + context.Executable = args[0]; + context.ProjectRoot = Path.GetDirectoryName(context.Executable); + context.IPARoot = Path.Combine(context.ProjectRoot, "IPA"); + context.LauncherPathSrc = Path.Combine(context.IPARoot, "Launcher.exe"); + context.DataPathSrc = Path.Combine(context.IPARoot, "Data"); + context.PluginsFolder = Path.Combine(context.ProjectRoot, "Plugins"); + context.ProjectName = Path.GetFileNameWithoutExtension(context.Executable); + context.DataPathDst = Path.Combine(context.ProjectRoot, context.ProjectName + "_Data"); + context.ManagedPath = Path.Combine(context.DataPathDst, "Managed"); + context.EngineFile = Path.Combine(context.ManagedPath, "UnityEngine.dll"); + context.AssemblyFile = Path.Combine(context.ManagedPath, "Assembly-Csharp.dll"); + + string shortcutName = string.Format("{0} (Patch & Launch)", context.ProjectName); + context.ShortcutPath = Path.Combine(context.ProjectRoot, shortcutName) + ".lnk"; + + return context; + } + } +} diff --git a/IPA/Patcher/BackupManager.cs b/IPA/Patcher/BackupManager.cs index ff83e0d..1a2ff5e 100644 --- a/IPA/Patcher/BackupManager.cs +++ b/IPA/Patcher/BackupManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; namespace IPA.Patcher { @@ -29,5 +30,44 @@ namespace IPA.Patcher 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) + { + File.Delete(file); + File.Move(backup, file); + return true; + } + return false; + } + } } diff --git a/IPA/Program.cs b/IPA/Program.cs index ce7b7b3..7b27295 100644 --- a/IPA/Program.cs +++ b/IPA/Program.cs @@ -1,14 +1,21 @@ using IPA.Patcher; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Forms; namespace IPA { + class Program { + static void Main(string[] args) { if(args.Length < 1 || !args[0].EndsWith(".exe")) @@ -16,61 +23,137 @@ namespace IPA Fail("Drag an (executable) file on the exe!"); } - string launcherSrc = Path.Combine("IPA", "Launcher.exe"); - string dataSrcPath = Path.Combine("IPA", "Data"); - string pluginsFolder = "Plugins"; - string projectName = Path.GetFileNameWithoutExtension(args[0]); - string dataDstPath = Path.Combine(Path.GetDirectoryName(args[0]), projectName + "_Data"); - string managedPath = Path.Combine(dataDstPath, "Managed"); - string engineFile = Path.Combine(managedPath, "UnityEngine.dll"); - string assemblyFile = Path.Combine(managedPath, "Assembly-Csharp.dll"); + try + { + var context = PatchContext.Create(args); + bool isRevert = args.Contains("--revert") || Keyboard.IsKeyDown(Keys.LMenu); + // Sanitizing + Validate(context); + if (isRevert) + { + Revert(context); + } + else + { + Install(context); + StartIfNeedBe(context); + } + } catch(Exception e) + { + Fail(e.Message); + } + } - // Sanitizing - if (!File.Exists(launcherSrc)) Fail("Couldn't find DLLs! Make sure you extracted all contents of the release archive."); - if(!Directory.Exists(dataDstPath) || !File.Exists(engineFile) || !File.Exists(assemblyFile)) + private static void Validate(PatchContext c) + { + if (!File.Exists(c.LauncherPathSrc)) Fail("Couldn't find DLLs! Make sure you extracted all contents of the release archive."); + if (!Directory.Exists(c.DataPathDst) || !File.Exists(c.EngineFile) || !File.Exists(c.AssemblyFile)) { - Fail("Game does not seem to be a Unity project. Could not find the libraries to patch. "); - } + Fail("Game does not seem to be a Unity project. Could not find the libraries to patch."); + } + } + private static void Install(PatchContext context) + { try { // Copying Console.Write("Updating files... "); - CopyAll(new DirectoryInfo(dataSrcPath), new DirectoryInfo(dataDstPath)); + CopyAll(new DirectoryInfo(context.DataPathSrc), new DirectoryInfo(context.DataPathDst)); Console.WriteLine("Successfully updated files!"); - if (!Directory.Exists(pluginsFolder)) + if (!Directory.Exists(context.PluginsFolder)) { Console.WriteLine("Creating plugins folder... "); - Directory.CreateDirectory(pluginsFolder); + Directory.CreateDirectory(context.PluginsFolder); } // Patching - var patchedModule = PatchedModule.Load(engineFile); - if(!patchedModule.IsPatched) + var patchedModule = PatchedModule.Load(context.EngineFile); + if (!patchedModule.IsPatched) { Console.Write("Patching UnityEngine.dll... "); - BackupManager.MakeBackup(engineFile); + BackupManager.MakeBackup(context.EngineFile); patchedModule.Patch(); Console.WriteLine("Done!"); } // Virtualizing - var virtualizedModule = VirtualizedModule.Load(assemblyFile); - if(!virtualizedModule.IsVirtualized) + var virtualizedModule = VirtualizedModule.Load(context.AssemblyFile); + if (!virtualizedModule.IsVirtualized) { Console.Write("Virtualizing Assembly-Csharp.dll... "); - BackupManager.MakeBackup(assemblyFile); + BackupManager.MakeBackup(context.AssemblyFile); virtualizedModule.Virtualize(); Console.WriteLine("Done!"); } - } catch(Exception e) + + // Creating shortcut + if(!File.Exists(context.ShortcutPath)) + { + Console.Write("Creating shortcut... "); + Shortcut.Create(context.ShortcutPath, Assembly.GetExecutingAssembly().Location, Args(context.Executable, "--launch"), context.ProjectRoot, "Launches the game and makes sure it's in a patched state", "", context.Executable); + Console.WriteLine("Created"); + } + } + catch (Exception e) { Fail("Oops! This should not have happened.\n\n" + e); } Console.WriteLine("Finished!"); + + } + + private static void Revert(PatchContext context) + { + Console.Write("Restoring game assembly... "); + if(BackupManager.Restore(context.AssemblyFile)) + { + Console.WriteLine("Done!"); + } else + { + 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..."); + File.Delete(context.ShortcutPath); + } + + Console.WriteLine(""); + Console.WriteLine("--- Done reverting ---"); + + if (!Environment.CommandLine.Contains("--nowait")) + { + Console.WriteLine("\n\n[Press any key to quit]"); + Console.ReadKey(); + } + } + + private static void StartIfNeedBe(PatchContext context) + { + var argList = context.Args.ToList(); + bool launch = argList.Remove("--launch"); + + argList.RemoveAt(0); + + if(launch) + { + Process.Start(context.Executable, Args(argList.ToArray())); + } } public static void CopyAll(DirectoryInfo source, DirectoryInfo target) @@ -108,5 +191,68 @@ namespace IPA } Environment.Exit(1); } + + public static string Args(params string[] args) + { + return string.Join(" ", args.Select(EncodeParameterArgument).ToArray()); + } + + /// + /// Encodes an argument for passing into a program + /// + /// The value that should be received by the program + /// The value which needs to be passed to the program for the original value + /// to come through + public static string EncodeParameterArgument(string original) + { + if (string.IsNullOrEmpty(original)) + return original; + string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0"); + value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\""); + return value; + } + + + public abstract class Keyboard + { + [Flags] + private enum KeyStates + { + None = 0, + Down = 1, + Toggled = 2 + } + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + private static extern short GetKeyState(int keyCode); + + private static KeyStates GetKeyState(Keys key) + { + KeyStates state = KeyStates.None; + + short retVal = GetKeyState((int)key); + + //If the high-order bit is 1, the key is down + //otherwise, it is up. + if ((retVal & 0x8000) == 0x8000) + state |= KeyStates.Down; + + //If the low-order bit is 1, the key is toggled. + if ((retVal & 1) == 1) + state |= KeyStates.Toggled; + + return state; + } + + public static bool IsKeyDown(Keys key) + { + return KeyStates.Down == (GetKeyState(key) & KeyStates.Down); + } + + public static bool IsKeyToggled(Keys key) + { + return KeyStates.Toggled == (GetKeyState(key) & KeyStates.Toggled); + } + } } } diff --git a/IPA/Shortcut.cs b/IPA/Shortcut.cs new file mode 100644 index 0000000..367da55 --- /dev/null +++ b/IPA/Shortcut.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.InteropServices; + +namespace IPA +{ + public class Shortcut + { + private static Type m_type = Type.GetTypeFromProgID("WScript.Shell"); + private static object m_shell = Activator.CreateInstance(m_type); + + [ComImport, TypeLibType((short)0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")] + private interface IWshShortcut + { + [DispId(0)] + string FullName { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0)] get; } + + [DispId(0x3e8)] + string Arguments { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3e8)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3e8)] set; } + + [DispId(0x3e9)] + string Description { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3e9)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3e9)] set; } + + [DispId(0x3ea)] + string Hotkey { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ea)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ea)] set; } + + [DispId(0x3eb)] + string IconLocation { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3eb)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3eb)] set; } + + [DispId(0x3ec)] + string RelativePath { [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ec)] set; } + + [DispId(0x3ed)] + string TargetPath { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ed)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ed)] set; } + + [DispId(0x3ee)] + int WindowStyle { [DispId(0x3ee)] get; [param: In] [DispId(0x3ee)] set; } + + [DispId(0x3ef)] + string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] set; } + + [TypeLibFunc((short)0x40), DispId(0x7d0)] + void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink); + + [DispId(0x7d1)] + void Save(); + } + + public static void Create(string fileName, string targetPath, string arguments, string workingDirectory, string description, string hotkey, string iconPath) + { + IWshShortcut shortcut = (IWshShortcut)m_type.InvokeMember("CreateShortcut", System.Reflection.BindingFlags.InvokeMethod, null, m_shell, new object[] { fileName }); + shortcut.Description = description; + shortcut.Hotkey = hotkey; + shortcut.TargetPath = targetPath; + shortcut.WorkingDirectory = workingDirectory; + shortcut.Arguments = arguments; + if (!string.IsNullOrEmpty(iconPath)) + shortcut.IconLocation = iconPath; + shortcut.Save(); + } + } +} \ No newline at end of file