diff --git a/IPA.Tests/IPA.Tests.csproj b/IPA.Tests/IPA.Tests.csproj new file mode 100644 index 00000000..43612e6d --- /dev/null +++ b/IPA.Tests/IPA.Tests.csproj @@ -0,0 +1,93 @@ + + + + + + Debug + AnyCPU + {C66092B0-5C1E-44E9-B524-E0E8E1425379} + Library + Properties + IPA.Tests + IPA.Tests + v4.5.2 + 512 + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll + True + + + ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll + True + + + ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + {14092533-98bb-40a4-9afc-27bb75672a70} + IPA + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/IPA.Tests/ProgramTest.cs b/IPA.Tests/ProgramTest.cs new file mode 100644 index 00000000..c13b6c83 --- /dev/null +++ b/IPA.Tests/ProgramTest.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace IPA.Tests +{ + public class ProgramTest + { + [Theory] + // Unrelated path + [InlineData("test/from.dll", "test/to.dll", "native", false, new string[] { "test/to.dll" })] + + // Flat -> Not-Flat + [InlineData("native/from.dll", "native/to.dll", "native", false, new string[] { "native/x86/to.dll", "native/x86_64/to.dll" })] + + // Flat -> Flat + [InlineData("native/from.dll", "native/to.dll", "native", true, new string[] { "native/to.dll" })] + + // Not-Flat -> Flat + [InlineData("native/x86/from.dll", "native/x86/to.dll", "native", true, new string[] { })] + [InlineData("native/x86_64/from.dll", "native/x86_64/to.dll", "native", true, new string[] { "native/to.dll" })] + + // Not-flat -> Not-Flat + [InlineData("native/x86/from.dll", "native/x86/to.dll", "native", false, new string[] { "native/x86/to.dll" })] + [InlineData("native/x86_64/from.dll", "native/x86_64/to.dll", "native", false, new string[] { "native/x86_64/to.dll" })] + + public void CopiesCorrectly(string from, string to, string nativeFolder, bool isFlat, string[] expected) + { + var outcome = Program.NativePluginInterceptor(new FileInfo(from), new FileInfo(to), new DirectoryInfo(nativeFolder), isFlat).Select(f => f.FullName).ToList(); + + var expectedPaths = expected.Select(e => new FileInfo(e)).Select(f => f.FullName).ToList(); + Assert.Equal(expectedPaths, outcome); + } + } +} diff --git a/IPA.Tests/Properties/AssemblyInfo.cs b/IPA.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..d237d239 --- /dev/null +++ b/IPA.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("IPA.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("IPA.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c66092b0-5c1e-44e9-b524-e0e8e1425379")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/IPA.Tests/ShortcutTest.cs b/IPA.Tests/ShortcutTest.cs new file mode 100644 index 00000000..338b48f4 --- /dev/null +++ b/IPA.Tests/ShortcutTest.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace IPA.Tests +{ + public class ShortcutTest + { + [Fact] + public void CanDealWithEmptyFiles() + { + Shortcut.Create(".lnk", "", "", "", "", "", ""); + } + + [Fact] + public void CanDealWithLongFiles() + { + Shortcut.Create(".lnk", Path.Combine(Path.GetTempPath(), string.Join("_", new string[500])), "", "", "", "", ""); + } + + [Fact] + public void CantDealWithNull() + { + Assert.Throws(() => Shortcut.Create(".lnk", null, "", "", "", "", "")); + } + + [Fact] + public void CanDealWithWeirdCharacters() + { + Shortcut.Create(".lnk", "äöü", "", "", "", "", ""); + } + } +} diff --git a/IPA.Tests/packages.config b/IPA.Tests/packages.config new file mode 100644 index 00000000..221bd9f3 --- /dev/null +++ b/IPA.Tests/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/IPA/IPA.csproj b/IPA/IPA.csproj new file mode 100644 index 00000000..ea60e62b --- /dev/null +++ b/IPA/IPA.csproj @@ -0,0 +1,99 @@ + + + + + Debug + AnyCPU + {14092533-98BB-40A4-9AFC-27BB75672A70} + Exe + Properties + IPA + IPA + v3.5 + 512 + Client + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + none + true + bin\Release\ + TRACE + prompt + 4 + + + favicon.ico + + + + ..\packages\Mono.Cecil.0.9.6.4\lib\net35\Mono.Cecil.dll + True + + + ..\packages\Mono.Cecil.0.9.6.4\lib\net35\Mono.Cecil.Mdb.dll + False + + + ..\packages\Mono.Cecil.0.9.6.4\lib\net35\Mono.Cecil.Pdb.dll + False + + + ..\packages\Mono.Cecil.0.9.6.4\lib\net35\Mono.Cecil.Rocks.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IPA/PatchContext.cs b/IPA/PatchContext.cs new file mode 100644 index 00000000..2ca0e519 --- /dev/null +++ b/IPA/PatchContext.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +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; } + public string IPA { get; private set; } + public string BackupPath { get; private set; } + + private PatchContext() { } + + public static PatchContext Create(String[] args) + { + var context = new PatchContext(); + + context.Args = args; + context.Executable = args[0]; + 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"); + 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, "UnityEngineCore.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 new file mode 100644 index 00000000..c129c397 --- /dev/null +++ b/IPA/Patcher/BackupManager.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace IPA.Patcher +{ + public class BackupManager + { + public static BackupUnit FindLatestBackup(PatchContext context) + { + 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; + } + + public static bool Restore(PatchContext context) + { + var backup = FindLatestBackup(context); + if(backup != null) + { + 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 00000000..70674834 --- /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/Patcher/Patcher.cs b/IPA/Patcher/Patcher.cs new file mode 100644 index 00000000..f9f7d28c --- /dev/null +++ b/IPA/Patcher/Patcher.cs @@ -0,0 +1,99 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace IPA.Patcher +{ + class PatchedModule + { + private static readonly string[] ENTRY_TYPES = { "Input", "Display" }; + + private FileInfo _File; + private ModuleDefinition _Module; + + public static PatchedModule Load(string engineFile) + { + return new PatchedModule(engineFile); + } + + private PatchedModule(string engineFile) + { + _File = new FileInfo(engineFile); + + LoadModules(); + } + + private void LoadModules() + { + var resolver = new DefaultAssemblyResolver(); + resolver.AddSearchDirectory(_File.DirectoryName); + + var parameters = new ReaderParameters + { + AssemblyResolver = resolver, + }; + + _Module = ModuleDefinition.ReadModule(_File.FullName, parameters); + } + + public bool IsPatched + { + get + { + foreach (var @ref in _Module.AssemblyReferences) + { + if (@ref.Name == "IllusionInjector") return true; + } + return false; + } + } + + public void Patch() + { + // First, let's add the reference + var nameReference = new AssemblyNameReference("IllusionInjector", new Version(1, 0, 0, 0)); + var injectorPath = Path.Combine(_File.DirectoryName, "IllusionInjector.dll"); + var injector = ModuleDefinition.ReadModule(injectorPath); + + _Module.AssemblyReferences.Add(nameReference); + int patched = 0; + foreach(var type in FindEntryTypes()) + { + if(PatchType(type, injector)) + { + patched++; + } + } + + if(patched > 0) + { + _Module.Write(_File.FullName); + } else + { + throw new Exception("Could not find any entry type!"); + } + } + + private bool PatchType(TypeDefinition targetType, ModuleDefinition injector) + { + var targetMethod = targetType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic); + if (targetMethod != null) + { + var methodReference = _Module.Import(injector.GetType("IllusionInjector.Injector").Methods.First(m => m.Name == "Inject")); + targetMethod.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Call, methodReference)); + return true; + } + return false; + } + + + private IEnumerable FindEntryTypes() + { + return _Module.GetTypes().Where(m => ENTRY_TYPES.Contains(m.Name)); + } + } +} diff --git a/IPA/Patcher/Virtualizer.cs b/IPA/Patcher/Virtualizer.cs new file mode 100644 index 00000000..c5dab7a2 --- /dev/null +++ b/IPA/Patcher/Virtualizer.cs @@ -0,0 +1,115 @@ +using Mono.Cecil; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace IPA.Patcher +{ + class VirtualizedModule + { + private const string ENTRY_TYPE = "Display"; + + private FileInfo _File; + private ModuleDefinition _Module; + + public static VirtualizedModule Load(string engineFile) + { + return new VirtualizedModule(engineFile); + } + + private VirtualizedModule(string assemblyFile) + { + _File = new FileInfo(assemblyFile); + + LoadModules(); + } + + private void LoadModules() + { + var resolver = new DefaultAssemblyResolver(); + resolver.AddSearchDirectory(_File.DirectoryName); + + var parameters = new ReaderParameters + { + AssemblyResolver = resolver, + }; + + _Module = ModuleDefinition.ReadModule(_File.FullName, parameters); + } + + /// + /// + /// + /// + public void Virtualize() + { + foreach (var type in _Module.Types) + { + VirtualizeType(type); + } + + _Module.Write(_File.FullName); + } + + private void VirtualizeType(TypeDefinition type) + { + if(type.IsSealed) + { + // Unseal + type.IsSealed = false; + } + + if (type.IsInterface) return; + if (type.IsAbstract) return; + + // These two don't seem to work. + if (type.Name == "SceneControl" || type.Name == "ConfigUI") return; + + Console.WriteLine("Virtualizing {0}", type.Name); + // Take care of sub types + foreach (var subType in type.NestedTypes) + { + VirtualizeType(subType); + } + + foreach (var method in type.Methods) + { + if (method.IsManaged + && method.IsIL + && !method.IsStatic + && !method.IsVirtual + && !method.IsAbstract + && !method.IsAddOn + && !method.IsConstructor + && !method.IsSpecialName + && !method.IsGenericInstance + && !method.HasOverrides) + { + method.IsVirtual = true; + method.IsPublic = true; + method.IsPrivate = false; + method.IsNewSlot = true; + method.IsHideBySig = true; + } + } + + foreach (var field in type.Fields) + { + if (field.IsPrivate) field.IsFamily = true; + } + } + + public bool IsVirtualized + { + get + { + var awakeMethods = _Module.GetTypes().SelectMany(t => t.Methods.Where(m => m.Name == "Awake")); + if (awakeMethods.Count() == 0) return false; + + return ((float)awakeMethods.Count(m => m.IsVirtual) / awakeMethods.Count()) > 0.5f; + } + } + } +} diff --git a/IPA/Program.cs b/IPA/Program.cs new file mode 100644 index 00000000..d188f204 --- /dev/null +++ b/IPA/Program.cs @@ -0,0 +1,365 @@ +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 +{ + + public class Program + { + public enum Architecture { + x86, + x64, + Unknown + } + + static void Main(string[] args) + { + if(args.Length < 1 || !args[0].EndsWith(".exe")) + { + Fail("Drag an (executable) file on the exe!"); + } + + 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); + } + } + + 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)) + { + Fail("Game does not seem to be a Unity project. Could not find the libraries to patch."); + } + } + + private static void Install(PatchContext context) + { + 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")); + bool force = !BackupManager.HasBackup(context) || context.Args.Contains("-f") || context.Args.Contains("--force"); + var architecture = DetectArchitecture(context.Executable); + + Console.WriteLine("Architecture: {0}", architecture); + + CopyAll(new DirectoryInfo(context.DataPathSrc), new DirectoryInfo(context.DataPathDst), force, backup, + (from, to) => NativePluginInterceptor(from, to, new DirectoryInfo(nativePluginFolder), isFlat, architecture) ); + + Console.WriteLine("Successfully updated files!"); + + if (!Directory.Exists(context.PluginsFolder)) + { + Console.WriteLine("Creating plugins folder... "); + Directory.CreateDirectory(context.PluginsFolder); + } + + // Patching + var patchedModule = PatchedModule.Load(context.EngineFile); + if (!patchedModule.IsPatched) + { + Console.Write("Patching UnityEngine.dll... "); + backup.Add(context.EngineFile); + patchedModule.Patch(); + Console.WriteLine("Done!"); + } + + // Virtualizing + if (File.Exists(context.AssemblyFile)) + { + var virtualizedModule = VirtualizedModule.Load(context.AssemblyFile); + if (!virtualizedModule.IsVirtualized) + { + Console.Write("Virtualizing Assembly-Csharp.dll... "); + backup.Add(context.AssemblyFile); + virtualizedModule.Virtualize(); + Console.WriteLine("Done!"); + } + } + + // Creating shortcut + if(!File.Exists(context.ShortcutPath)) + { + Console.Write("Creating shortcut to IPA ({0})... ", context.IPA); + try + { + Shortcut.Create( + fileName: context.ShortcutPath, + targetPath: context.IPA, + arguments: Args(context.Executable, "--launch"), + workingDirectory: context.ProjectRoot, + description: "Launches the game and makes sure it's in a patched state", + hotkey: "", + iconPath: context.Executable + ); + Console.WriteLine("Created"); + } catch (Exception e) + { + Console.Error.WriteLine("Failed to create shortcut, but game was patched!"); + } + } + } + catch (Exception e) + { + Fail("Oops! This should not have happened.\n\n" + e); + } + + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("Finished!"); + Console.ResetColor(); + + } + + private static void Revert(PatchContext context) + { + Console.Write("Restoring backup... "); + if(BackupManager.Restore(context)) + { + 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 IEnumerable NativePluginInterceptor(FileInfo from, FileInfo to, DirectoryInfo nativePluginFolder, bool isFlat, Architecture preferredArchitecture) + { + if (to.FullName.StartsWith(nativePluginFolder.FullName)) + { + var relevantBit = to.FullName.Substring(nativePluginFolder.FullName.Length + 1); + // Goes into the plugin folder! + bool isFileFlat = !relevantBit.StartsWith("x86"); + if (isFlat && !isFileFlat) + { + // Flatten structure + bool is64Bit = relevantBit.StartsWith("x86_64"); + if (!is64Bit && preferredArchitecture == Architecture.x86) + { + // 32 bit + yield return new FileInfo(Path.Combine(nativePluginFolder.FullName, relevantBit.Substring("x86".Length + 1))); + } + else if(is64Bit && (preferredArchitecture == Architecture.x64 || preferredArchitecture == Architecture.Unknown)) + { + // 64 bit + yield return new FileInfo(Path.Combine(nativePluginFolder.FullName, relevantBit.Substring("x86_64".Length + 1))); + } else { + // Throw away + yield break; + } + } + else if (!isFlat && isFileFlat) + { + // Deepen structure + yield return new FileInfo(Path.Combine(Path.Combine(nativePluginFolder.FullName, "x86"), relevantBit)); + yield return new FileInfo(Path.Combine(Path.Combine(nativePluginFolder.FullName, "x86_64"), relevantBit)); + } + else + { + yield return to; + } + } + else + { + yield return to; + } + } + private static IEnumerable PassThroughInterceptor(FileInfo from, FileInfo to) + { + yield return to; + } + + public static void CopyAll(DirectoryInfo source, DirectoryInfo target, bool aggressive, BackupUnit backup, Func> interceptor = null) + { + if(interceptor == null) + { + interceptor = PassThroughInterceptor; + } + + // 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 || aggressive) + { + targetFile.Directory.Create(); + + Console.WriteLine(@"Copying {0}", targetFile.FullName); + backup.Add(targetFile); + fi.CopyTo(targetFile.FullName, true); + } + } + } + + // Copy each subdirectory using recursion. + foreach (DirectoryInfo diSourceSubDir in source.GetDirectories()) + { + DirectoryInfo nextTargetSubDir = new DirectoryInfo(Path.Combine(target.FullName, diSourceSubDir.Name)); + CopyAll(diSourceSubDir, nextTargetSubDir, aggressive, backup, interceptor); + } + } + + + static void Fail(string message) + { + Console.Error.Write("ERROR: " + message); + if (!Environment.CommandLine.Contains("--nowait")) + { + Console.WriteLine("\n\n[Press any key to quit]"); + Console.ReadKey(); + } + 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 static Architecture DetectArchitecture(string assembly) + { + using (var reader = new BinaryReader(File.OpenRead(assembly))) + { + var header = reader.ReadUInt16(); + if(header == 0x5a4d) + { + reader.BaseStream.Seek(60, SeekOrigin.Begin); // this location contains the offset for the PE header + var peOffset = reader.ReadUInt32(); + + reader.BaseStream.Seek(peOffset + 4, SeekOrigin.Begin); + var machine = reader.ReadUInt16(); + + if (machine == 0x8664) // IMAGE_FILE_MACHINE_AMD64 + return Architecture.x64; + else if (machine == 0x014c) // IMAGE_FILE_MACHINE_I386 + return Architecture.x86; + else if (machine == 0x0200) // IMAGE_FILE_MACHINE_IA64 + return Architecture.x64; + else + return Architecture.Unknown; + } else + { + // Not a supported binary + return Architecture.Unknown; + } + } + } + + 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/Properties/AssemblyInfo.cs b/IPA/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..01143c29 --- /dev/null +++ b/IPA/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Illusion Plugin Architecture")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Illusion Plugin Architecture")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("14092533-98bb-40a4-9afc-27bb75672a70")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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.1.1.0")] +[assembly: AssemblyFileVersion("3.1.1.0")] diff --git a/IPA/Shortcut.cs b/IPA/Shortcut.cs new file mode 100644 index 00000000..980bdc13 --- /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 diff --git a/IPA/favicon.ico b/IPA/favicon.ico new file mode 100644 index 00000000..ae925ef5 Binary files /dev/null and b/IPA/favicon.ico differ diff --git a/IPA/obj/Debug/IPA.csproj.CoreCompileInputs.cache b/IPA/obj/Debug/IPA.csproj.CoreCompileInputs.cache new file mode 100644 index 00000000..48437bcb --- /dev/null +++ b/IPA/obj/Debug/IPA.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +f7c20aca6b99cd3b62d50a05d9bdca1eb41dca2a diff --git a/IPA/packages.config b/IPA/packages.config new file mode 100644 index 00000000..bc985110 --- /dev/null +++ b/IPA/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/IllusionInjector/Bootstrapper.cs b/IllusionInjector/Bootstrapper.cs new file mode 100644 index 00000000..f84bbe73 --- /dev/null +++ b/IllusionInjector/Bootstrapper.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace IllusionInjector +{ + class Bootstrapper : MonoBehaviour + { + public event Action Destroyed = delegate {}; + + void Awake() + { + if (Environment.CommandLine.Contains("--verbose") && !Screen.fullScreen) + { + Windows.GuiConsole.CreateConsole(); + } + } + + void Start() + { + Destroy(gameObject); + } + void OnDestroy() + { + Destroyed(); + } + } +} diff --git a/IllusionInjector/CompositePlugin.cs b/IllusionInjector/CompositePlugin.cs new file mode 100644 index 00000000..1e4de7f9 --- /dev/null +++ b/IllusionInjector/CompositePlugin.cs @@ -0,0 +1,109 @@ +using IllusionPlugin; +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace IllusionInjector +{ + public class CompositePlugin : IPlugin + { + IEnumerable plugins; + + private delegate void CompositeCall(IPlugin plugin); + + public CompositePlugin(IEnumerable plugins) + { + this.plugins = plugins; + } + + public void OnApplicationStart() + { + Invoke(plugin => plugin.OnApplicationStart()); + } + + public void OnApplicationQuit() + { + Invoke(plugin => plugin.OnApplicationQuit()); + } + + public void OnLevelWasLoaded(int level) + { + foreach (var plugin in plugins) + { + try + { + plugin.OnLevelWasLoaded(level); + } + catch (Exception ex) + { + Console.WriteLine("{0}: {1}", plugin.Name, ex); + } + } + } + + + private void Invoke(CompositeCall callback) + { + foreach (var plugin in plugins) + { + try + { + callback(plugin); + } + catch (Exception ex) + { + Console.WriteLine("{0}: {1}", plugin.Name, ex); + } + } + } + + + + public void OnLevelWasInitialized(int level) + { + foreach (var plugin in plugins) + { + try + { + plugin.OnLevelWasInitialized(level); + } + catch (Exception ex) + { + Console.WriteLine("{0}: {1}", plugin.Name, ex); + } + } + } + + + public void OnUpdate() + { + Invoke(plugin => plugin.OnUpdate()); + } + + public void OnFixedUpdate() + { + Invoke(plugin => plugin.OnFixedUpdate()); + } + + + public string Name + { + get { throw new NotImplementedException(); } + } + + public string Version + { + get { throw new NotImplementedException(); } + } + + public void OnLateUpdate() + { + Invoke(plugin => + { + if (plugin is IEnhancedPlugin) + ((IEnhancedPlugin)plugin).OnLateUpdate(); + }); + } + } +} diff --git a/IllusionInjector/ConsoleWindow.cs b/IllusionInjector/ConsoleWindow.cs new file mode 100644 index 00000000..ff46c0fd --- /dev/null +++ b/IllusionInjector/ConsoleWindow.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections; +using System.Runtime.InteropServices; +using System.IO; +using System.Text; + +namespace Windows +{ + + class GuiConsole + { + public static void CreateConsole() + { + if (hasConsole) + return; + if (oldOut == IntPtr.Zero) + oldOut = GetStdHandle( -11 ); + if (! AllocConsole()) + throw new Exception("AllocConsole() failed"); + conOut = CreateFile( "CONOUT$", 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero ); + if (! SetStdHandle(-11, conOut)) + throw new Exception("SetStdHandle() failed"); + StreamToConsole(); + hasConsole = true; + } + public static void ReleaseConsole() + { + if (! hasConsole) + return; + if (! CloseHandle(conOut)) + throw new Exception("CloseHandle() failed"); + conOut = IntPtr.Zero; + if (! FreeConsole()) + throw new Exception("FreeConsole() failed"); + if (! SetStdHandle(-11, oldOut)) + throw new Exception("SetStdHandle() failed"); + StreamToConsole(); + hasConsole = false; + } + private static void StreamToConsole() + { + Stream cstm = Console.OpenStandardOutput(); + StreamWriter cstw = new StreamWriter( cstm, Encoding.Default ); + cstw.AutoFlush = true; + Console.SetOut( cstw ); + Console.SetError( cstw ); + } + private static bool hasConsole = false; + private static IntPtr conOut; + private static IntPtr oldOut; + [DllImport("kernel32.dll", SetLastError=true)] + private static extern bool AllocConsole(); + [DllImport("kernel32.dll", SetLastError=false)] + private static extern bool FreeConsole(); + [DllImport("kernel32.dll", SetLastError=true)] + private static extern IntPtr GetStdHandle( int nStdHandle ); + [DllImport("kernel32.dll", SetLastError=true)] + private static extern bool SetStdHandle(int nStdHandle, IntPtr hConsoleOutput); + [DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)] + private static extern IntPtr CreateFile( + string fileName, + int desiredAccess, + int shareMode, + IntPtr securityAttributes, + int creationDisposition, + int flagsAndAttributes, + IntPtr templateFile ); + [DllImport("kernel32.dll", ExactSpelling=true, SetLastError=true)] + private static extern bool CloseHandle(IntPtr handle); + } +} \ No newline at end of file diff --git a/IllusionInjector/IllusionInjector.csproj b/IllusionInjector/IllusionInjector.csproj new file mode 100644 index 00000000..6d15b941 --- /dev/null +++ b/IllusionInjector/IllusionInjector.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {D1C61AF5-0D2D-4752-8203-1C6929025F7C} + Library + Properties + IllusionInjector + IllusionInjector + v3.5 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + none + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + ..\Libs\UnityEngine.dll + False + + + + + + + + + + + + + + {e2848bfb-5432-42f4-8ae0-d2ec0cdf2f71} + IllusionPlugin + + + + + \ No newline at end of file diff --git a/IllusionInjector/Injector.cs b/IllusionInjector/Injector.cs new file mode 100644 index 00000000..9138c6a5 --- /dev/null +++ b/IllusionInjector/Injector.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using UnityEngine; + +namespace IllusionInjector +{ + public static class Injector + { + private static bool injected = false; + public static void Inject() + { + if (!injected) + { + injected = true; + var bootstrapper = new GameObject("Bootstrapper").AddComponent(); + bootstrapper.Destroyed += Bootstrapper_Destroyed; + } + } + + private static void Bootstrapper_Destroyed() + { + PluginComponent.Create(); + } + } +} diff --git a/IllusionInjector/PluginComponent.cs b/IllusionInjector/PluginComponent.cs new file mode 100644 index 00000000..0214398b --- /dev/null +++ b/IllusionInjector/PluginComponent.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace IllusionInjector +{ + public class PluginComponent : MonoBehaviour + { + private CompositePlugin plugins; + private bool freshlyLoaded = false; + private bool quitting = false; + + public static PluginComponent Create() + { + return new GameObject("IPA_PluginManager").AddComponent(); + } + + void Awake() + { + DontDestroyOnLoad(gameObject); + + plugins = new CompositePlugin(PluginManager.Plugins); + plugins.OnApplicationStart(); + } + + void Start() + { + OnLevelWasLoaded(Application.loadedLevel); + } + + void Update() + { + if (freshlyLoaded) + { + freshlyLoaded = false; + plugins.OnLevelWasInitialized(Application.loadedLevel); + } + plugins.OnUpdate(); + } + + void LateUpdate() + { + plugins.OnLateUpdate(); + } + + void FixedUpdate() + { + plugins.OnFixedUpdate(); + } + + void OnDestroy() + { + if (!quitting) + { + Create(); + } + } + + void OnApplicationQuit() + { + plugins.OnApplicationQuit(); + + quitting = true; + } + + void OnLevelWasLoaded(int level) + { + plugins.OnLevelWasLoaded(level); + freshlyLoaded = true; + } + + } +} diff --git a/IllusionInjector/PluginManager.cs b/IllusionInjector/PluginManager.cs new file mode 100644 index 00000000..5e7444b4 --- /dev/null +++ b/IllusionInjector/PluginManager.cs @@ -0,0 +1,128 @@ +using IllusionPlugin; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace IllusionInjector +{ + public static class PluginManager + { + private static List _Plugins = null; + + /// + /// Gets the list of loaded plugins and loads them if necessary. + /// + public static IEnumerable Plugins + { + get + { + if(_Plugins == null) + { + LoadPlugins(); + } + return _Plugins; + } + } + + + private static void LoadPlugins() + { + string pluginDirectory = Path.Combine(Environment.CurrentDirectory, "Plugins"); + + // Process.GetCurrentProcess().MainModule crashes the game and Assembly.GetEntryAssembly() is NULL, + // so we need to resort to P/Invoke + string exeName = Path.GetFileNameWithoutExtension(AppInfo.StartupPath); + Console.WriteLine(exeName); + _Plugins = new List(); + + if (!Directory.Exists(pluginDirectory)) return; + + String[] files = Directory.GetFiles(pluginDirectory, "*.dll"); + foreach (var s in files) + { + _Plugins.AddRange(LoadPluginsFromFile(Path.Combine(pluginDirectory, s), exeName)); + } + + + // DEBUG + Console.WriteLine("Running on Unity {0}", UnityEngine.Application.unityVersion); + Console.WriteLine("-----------------------------"); + Console.WriteLine("Loading plugins from {0} and found {1}", pluginDirectory, _Plugins.Count); + Console.WriteLine("-----------------------------"); + foreach (var plugin in _Plugins) + { + + Console.WriteLine(" {0}: {1}", plugin.Name, plugin.Version); + } + Console.WriteLine("-----------------------------"); + } + + private static IEnumerable LoadPluginsFromFile(string file, string exeName) + { + List plugins = new List(); + + if (!File.Exists(file) || !file.EndsWith(".dll", true, null)) + return plugins; + + try + { + Assembly assembly = Assembly.LoadFrom(file); + + foreach (Type t in assembly.GetTypes()) + { + if (t.GetInterface("IPlugin") != null) + { + try + { + + IPlugin pluginInstance = Activator.CreateInstance(t) as IPlugin; + string[] filter = null; + + if (pluginInstance is IEnhancedPlugin) + { + filter = ((IEnhancedPlugin)pluginInstance).Filter; + } + + if(filter == null || Enumerable.Contains(filter, exeName, StringComparer.OrdinalIgnoreCase)) + plugins.Add(pluginInstance); + } + catch (Exception e) + { + Console.WriteLine("[WARN] Could not load plugin {0} in {1}! {2}", t.FullName, Path.GetFileName(file), e); + } + } + } + + } + catch (Exception e) + { + Console.WriteLine("[ERROR] Could not load {0}! {1}", Path.GetFileName(file), e); + } + + return plugins; + } + + public class AppInfo + { + [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false)] + private static extern int GetModuleFileName(HandleRef hModule, StringBuilder buffer, int length); + private static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero); + public static string StartupPath + { + get + { + StringBuilder stringBuilder = new StringBuilder(260); + GetModuleFileName(NullHandleRef, stringBuilder, stringBuilder.Capacity); + return stringBuilder.ToString(); + } + } + } + + } +} diff --git a/IllusionInjector/Properties/AssemblyInfo.cs b/IllusionInjector/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..8f033530 --- /dev/null +++ b/IllusionInjector/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("IllusionInjector")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("IllusionInjector")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("400a540a-d21f-4609-966b-206059b6e73b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache b/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache new file mode 100644 index 00000000..00efea32 --- /dev/null +++ b/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +207669803f9c3f5ead7136a41944fdaa372fd294 diff --git a/LICENSE b/LICENSE index df8472aa..381ba02f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2018 Michael Guedko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2016 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Launcher/Launcher.csproj b/Launcher/Launcher.csproj new file mode 100644 index 00000000..41f8b12d --- /dev/null +++ b/Launcher/Launcher.csproj @@ -0,0 +1,91 @@ + + + + + Debug + AnyCPU + {D1390268-F68B-4A55-B50D-EAD25756C8EF} + WinExe + Properties + Launcher + Launcher + v3.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + syringe.ico + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + \ No newline at end of file diff --git a/Launcher/Program.cs b/Launcher/Program.cs new file mode 100644 index 00000000..711fef76 --- /dev/null +++ b/Launcher/Program.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Windows.Forms; + +namespace Launcher +{ + static class Program + { + private static string[] TABOO_NAMES = { + //"Start", + //"Update", + //"Awake", + //"OnDestroy" + }; + private static string[] ENTRY_TYPES = { "Display" }; + + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + try { + var execPath = Application.ExecutablePath; + var fileName = Path.GetFileNameWithoutExtension(execPath); + if (fileName.IndexOf("VR") == -1 && fileName.IndexOf("_") == -1) + { + Fail("File not named correctly!"); + } + + bool vrMode = fileName.IndexOf("VR") > 0; + string baseName = execPath.Substring(0, vrMode + ? execPath.LastIndexOf("VR") + : execPath.LastIndexOf("_")); + + string executable = baseName + ".exe"; + var file = new FileInfo(executable); + if (file.Exists) + { + var args = Environment.GetCommandLineArgs().ToList(); + if (vrMode) args.Add("--vr"); + EnsureIPA(executable); + StartGame(executable, args.ToArray()); + } + else + { + MessageBox.Show("Could not find: " + file.FullName, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } catch(Exception globalException) { + MessageBox.Show(globalException.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + } + + private static void EnsureIPA(string executable) + { + var processStart = new ProcessStartInfo("IPA.exe", EncodeParameterArgument(executable) + " --nowait"); + processStart.UseShellExecute = false; + processStart.CreateNoWindow = true; + processStart.RedirectStandardError = true; + + var process = Process.Start(processStart); + process.WaitForExit(); + if(process.ExitCode != 0) + { + Fail(process.StandardError.ReadToEnd()); + } + } + + private static void StartGame(string executable, string[] args) + { + var arguments = string.Join(" ", args.ToArray()); + Process.Start(executable, arguments); + } + + private static void Fail(string reason) { + throw new Exception(reason); + } + + /// + /// 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 + private 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; + } + + } +} diff --git a/Launcher/Properties/AssemblyInfo.cs b/Launcher/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..f7570c31 --- /dev/null +++ b/Launcher/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Launcher")] +[assembly: AssemblyDescription("Rename to [EXE]_Patched.exe or [EXE]VR.exe")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Launcher")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d590f676-c2c0-4b80-ae93-6edf274320e6")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Launcher/Properties/Resources.Designer.cs b/Launcher/Properties/Resources.Designer.cs new file mode 100644 index 00000000..f20450db --- /dev/null +++ b/Launcher/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Launcher.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Launcher.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Launcher/Properties/Resources.resx b/Launcher/Properties/Resources.resx new file mode 100644 index 00000000..ffecec85 --- /dev/null +++ b/Launcher/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Launcher/Properties/Settings.Designer.cs b/Launcher/Properties/Settings.Designer.cs new file mode 100644 index 00000000..18739306 --- /dev/null +++ b/Launcher/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Launcher.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Launcher/Properties/Settings.settings b/Launcher/Properties/Settings.settings new file mode 100644 index 00000000..abf36c5d --- /dev/null +++ b/Launcher/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Launcher/Resources.Designer.cs b/Launcher/Resources.Designer.cs new file mode 100644 index 00000000..ce6216de --- /dev/null +++ b/Launcher/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Launcher { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Launcher.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Launcher/Resources.resx b/Launcher/Resources.resx new file mode 100644 index 00000000..4d26a2af --- /dev/null +++ b/Launcher/Resources.resx @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + \ No newline at end of file diff --git a/Launcher/obj/Debug/Launcher.csproj.CoreCompileInputs.cache b/Launcher/obj/Debug/Launcher.csproj.CoreCompileInputs.cache new file mode 100644 index 00000000..67d70831 --- /dev/null +++ b/Launcher/obj/Debug/Launcher.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +7c6dbd91916854c83d27e1fd6e1b9ccedbe983b8 diff --git a/Launcher/packages.config b/Launcher/packages.config new file mode 100644 index 00000000..6f7c8833 --- /dev/null +++ b/Launcher/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Launcher/syringe.ico b/Launcher/syringe.ico new file mode 100644 index 00000000..db70bbab Binary files /dev/null and b/Launcher/syringe.ico differ diff --git a/Libs/UnityEngine.dll b/Libs/UnityEngine.dll new file mode 100644 index 00000000..6a4c68cb Binary files /dev/null and b/Libs/UnityEngine.dll differ