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