@@ -54,17 +54,20 @@ | |||
</Reference> | |||
<Reference Include="System" /> | |||
<Reference Include="System.Core" /> | |||
<Reference Include="System.Windows.Forms" /> | |||
<Reference Include="System.Xml.Linq" /> | |||
<Reference Include="System.Data.DataSetExtensions" /> | |||
<Reference Include="System.Data" /> | |||
<Reference Include="System.Xml" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Include="PatchContext.cs" /> | |||
<Compile Include="Patcher\BackupManager.cs" /> | |||
<Compile Include="Patcher\Patcher.cs" /> | |||
<Compile Include="Patcher\Virtualizer.cs" /> | |||
<Compile Include="Program.cs" /> | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
<Compile Include="Shortcut.cs" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="packages.config" /> | |||
@@ -0,0 +1,57 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
namespace IPA | |||
{ | |||
public class PatchContext | |||
{ | |||
/// <summary> | |||
/// Gets the filename of the executable. | |||
/// </summary> | |||
public string Executable { get; private set; } | |||
/// <summary> | |||
/// Gets the path to the launcher executable (in the IPA folder) | |||
/// </summary> | |||
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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
/// <summary> | |||
/// Encodes an argument for passing into a program | |||
/// </summary> | |||
/// <param name="original">The value that should be received by the program</param> | |||
/// <returns>The value which needs to be passed to the program for the original value | |||
/// to come through</returns> | |||
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); | |||
} | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |