Esempio n. 1
0
 /// <summary>
 /// Builds the application assembly with the specificed Assembly name, and writes it to the supplied output path.
 /// </summary>
 /// <param name="buildInfo">Assembly build information.</param>
 /// <param name="outputPath">The output path of the assembly file.</param>
 public void Build(AssemblyBuildInfo buildInfo, string outputPath)
 {
     // Build the assembly from the (non-editor) CS scripts in the project
     if (BuildAssembly(buildInfo, outputPath))
     {
         PostProcessAssembly(buildInfo, outputPath);
     }
 }
Esempio n. 2
0
        public static void Start()
        {
            var preloaderListener = new PreloaderConsoleListener();

            Logger.Listeners.Add(preloaderListener);

            var entrypointAssemblyPath = Path.ChangeExtension(Process.GetCurrentProcess().MainModule.FileName, "dll");

            Paths.SetExecutablePath(entrypointAssemblyPath);

            TypeLoader.SearchDirectories.Add(Path.GetDirectoryName(entrypointAssemblyPath));

            Logger.Sources.Add(TraceLogSource.CreateSource());

            ChainloaderLogHelper.PrintLogInfo(Log);

            Log.Log(LogLevel.Info, $"CLR runtime version: {Environment.Version}");


            AssemblyBuildInfo executableInfo;

            using (var entrypointAssembly = AssemblyDefinition.ReadAssembly(entrypointAssemblyPath))
                executableInfo = AssemblyBuildInfo.DetermineInfo(entrypointAssembly);

            Log.LogInfo($"Game executable build architecture: {executableInfo}");

            Log.Log(LogLevel.Message, "Preloader started");

            using (var assemblyPatcher = new AssemblyPatcher())
            {
                assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);

                Log.Log(LogLevel.Info,
                        $"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");

                assemblyPatcher.LoadAssemblyDirectories(new[] { Paths.GameRootPath }, new[] { "dll", "exe" });

                Log.Log(LogLevel.Info, $"{assemblyPatcher.PatcherContext.AvailableAssemblies.Count} assemblies discovered");

                assemblyPatcher.PatchAndLoad();
            }

            Log.LogMessage("Preloader finished");

            Logger.Listeners.Remove(preloaderListener);

            var chainloader = new NetChainloader();

            chainloader.Initialize();
            chainloader.Execute();
        }
Esempio n. 3
0
        private void PostProcessAssembly(AssemblyBuildInfo buildInfo, string outputPath)
        {
            var asmName          = buildInfo.Name;
            var readerParameters = new ReaderParameters()
            {
                InMemory    = true,
                ReadSymbols = true
            };
            var asmDef = AssemblyDefinition.ReadAssembly(outputPath, readerParameters);

            asmDef.Name.Name       = asmName;
            asmDef.Name.Version    = new Version(0, buildInfo.Version);
            asmDef.MainModule.Name = asmName + ".dll";

            // Change all assembly references within the main module to point to the new assembly name
            foreach (var asmRef in asmDef.MainModule.AssemblyReferences)
            {
                if (asmRef.Name == BuildConsts.ProjectAssemblyName)
                {
                    asmRef.Name = asmName;
                }
            }

            var methods = GetAllMethodDefinitions(asmDef);

            // Warn if there is no call to ExperienceApp::End() found anywhere in the assembly
            if (!FindExperienceAppEndCall(asmDef, methods))
            {
                throw new Exception("No method call to Liminal.SDK.Core.ExperienceApp::End() was found. Your app will never end. You app will not be approved with a call to End().");
            }

            // Replace UnityEngine.Object::Instantiate method calls with LiminalObject::Instantiate
            // This is required to overcome serialization limitations within Unity when using code from an assembly
            // loaded into an AppDomain at runtime
            ReplaceInstantiateMethods(asmDef, methods);

            // Write assembly back to disk
            //using (var ms = new MemoryStream())
            //{
            //    asmDef.Write(ms, new WriterParameters() { WriteSymbols = true });
            //    //File.WriteAllBytes(outputPath + ".foo", ms.ToArray());
            //    File.WriteAllBytes(outputPath, ms.ToArray());
            //}

            // CJS: This `should` be the same as the block above?!?
            asmDef.Write(outputPath, new WriterParameters()
            {
                WriteSymbols = true
            });
            //asmDef.Write(outputPath + ".foo", new WriterParameters(){WriteSymbols = true});
        }
Esempio n. 4
0
        private bool BuildAssembly(AssemblyBuildInfo buildInfo, string outputPath)
        {
            // Fetch all the non-editor scripts in the project that should be compiled
            // into the final assembly. The output is essentially the same as Assembly-CSharp.
            var scripts = GetAssemblyScripts();

            if (scripts.Length == 0)
            {
                // No scripts to compile
                // Returning false here indicates that no assembly is required
                return(false);
            }

            var buildSuccess = false;

            var json   = File.ReadAllText(BuildWindowConsts.BuildWindowConfigPath);
            var config = JsonUtility.FromJson <BuildWindowConfig>(json);

            var configAdditionalReferences = config.DefaultAdditionalReferences;

            configAdditionalReferences.AddRange(config.AdditionalReferences);

            foreach (var additionalReference in configAdditionalReferences)
            {
                Debug.Log(additionalReference);
            }

            var additionalReferences = new string[configAdditionalReferences.Count];

            for (var i = 0; i < additionalReferences.Length; i++)
            {
                additionalReferences[i] = GetAssemblyPath(configAdditionalReferences[i]);
            }

            var builder = new AssemblyBuilder(outputPath, scripts)
            {
                buildTargetGroup     = buildInfo.BuildTargetGroup,
                buildTarget          = buildInfo.BuildTarget,
                additionalReferences = additionalReferences,
            };

            // Hook in listeners
            builder.buildStarted  += (path) => Debug.LogFormat("[Liminal.Build] Assembly build started: {0}", buildInfo.Name);
            builder.buildFinished += (path, messages) =>
            {
                var warnings     = messages.Where(m => m.type == CompilerMessageType.Warning);
                var warningCount = warnings.Count();

                var errors     = messages.Where(m => m.type == CompilerMessageType.Error);
                var errorCount = errors.Count();

                Debug.LogFormat("[Liminal.Build] Assembly build finished. Warnings: {0} - Errors: {1} - Output: {2}", warningCount, errorCount, path);

                foreach (var m in warnings)
                {
                    Debug.LogWarning(FormatCompilerMessage(m));
                }

                foreach (var m in errors)
                {
                    Debug.LogError(FormatCompilerMessage(m));
                }

                buildSuccess = (errorCount == 0);
            };

            // Run the build and wait for it to finish
            // Build is run on a separate thread, so we need to wait for its status to change
            builder.Build();
            while (builder.status != AssemblyBuilderStatus.Finished)
            {
                System.Threading.Thread.Sleep(10);
            }

            // Throw if the build failed
            if (!buildSuccess)
            {
                throw new Exception("Assembly build failed");
            }

            return(true);
        }
Esempio n. 5
0
    public static void Start(string[] args)
    {
        var preloaderListener = new PreloaderConsoleListener();

        Logger.Listeners.Add(preloaderListener);

        if (string.IsNullOrEmpty(ConfigEntrypointExecutable.Value))
        {
            Log.Log(LogLevel.Fatal,
                    $"Entry executable was not set. Please set this in your config before launching the application");
            Program.ReadExit();
            return;
        }

        var executablePath = Path.GetFullPath(ConfigEntrypointExecutable.Value);

        if (!File.Exists(executablePath))
        {
            Log.Log(LogLevel.Fatal, $"Unable to locate executable: {ConfigEntrypointExecutable.Value}");
            Program.ReadExit();
            return;
        }


        Paths.SetExecutablePath(executablePath);
        Program.ResolveDirectories.Add(Paths.GameRootPath);

        foreach (var searchDir in Program.ResolveDirectories)
        {
            TypeLoader.SearchDirectories.Add(searchDir);
        }

        if (PlatformHelper.Is(Platform.Windows))
        {
            AddDllDirectory(Paths.GameRootPath);
            SetDllDirectory(Paths.GameRootPath);
        }


        Logger.Sources.Add(TraceLogSource.CreateSource());

        ChainloaderLogHelper.PrintLogInfo(Log);

        Log.Log(LogLevel.Info, $"CLR runtime version: {Environment.Version}");


        AssemblyBuildInfo executableInfo, launcherInfo;

        using (var executableAssembly = AssemblyDefinition.ReadAssembly(executablePath))
            executableInfo = AssemblyBuildInfo.DetermineInfo(executableAssembly);

        using (var launcherAssembly = AssemblyDefinition.ReadAssembly(typeof(NetPreloader).Assembly.Location))
            launcherInfo = AssemblyBuildInfo.DetermineInfo(launcherAssembly);

        // we don't particularly care about AnyCPU here since the fallback bitness is almost never the case
        if (executableInfo.Is64Bit != launcherInfo.Is64Bit)
        {
            Log.LogError($"Game executable is {(executableInfo.Is64Bit ? "64" : "32")}-bit while BepInEx has been compiled as {(launcherInfo.Is64Bit ? "64" : "32")}-bit. Expect crashes");
        }

        if (executableInfo.NetFrameworkVersion != launcherInfo.NetFrameworkVersion || executableInfo.AssemblyFrameworkType != launcherInfo.AssemblyFrameworkType)
        {
            Log.LogWarning($"Game executable has been compiled as {executableInfo}, while BepInEx has been compiled as {launcherInfo}. There may be issues within the game caused by this");
        }

        Log.LogInfo($"Game executable build architecture: {executableInfo}");
        Log.LogInfo($"BepInEx launcher build architecture: {launcherInfo}");

        Log.Log(LogLevel.Message, "Preloader started");

        Assembly entrypointAssembly;

        using (var assemblyPatcher = new AssemblyPatcher())
        {
            assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);

            Log.Log(LogLevel.Info,
                    $"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");

            assemblyPatcher.LoadAssemblyDirectories(new[] { Paths.GameRootPath }, new[] { "dll", "exe" });

            Log.Log(LogLevel.Info, $"{assemblyPatcher.PatcherContext.AvailableAssemblies.Count} assemblies discovered");

            assemblyPatcher.PatchAndLoad();


            var assemblyName = AssemblyName.GetAssemblyName(executablePath);

            entrypointAssembly =
                assemblyPatcher.PatcherContext.LoadedAssemblies.Values.FirstOrDefault(x => x.FullName ==
                                                                                      assemblyName.FullName);

            foreach (var loadedAssembly in assemblyPatcher.PatcherContext.LoadedAssemblies)
            {
                // TODO: Need full paths for loaded assemblies
                var assemblyPath = Path.Combine(Paths.GameRootPath, loadedAssembly.Key);

                Log.LogDebug($"Registering '{assemblyPath}' as a loaded assembly");
                AssemblyFixes.AssemblyLocations[loadedAssembly.Value.FullName] = assemblyPath;
            }

            if (entrypointAssembly != null)
            {
                Log.LogDebug("Found patched entrypoint assembly! Using it");
            }
            else
            {
                Log.LogDebug("Using entrypoint assembly from disk");
                entrypointAssembly = Assembly.LoadFrom(executablePath);
            }
        }

        Log.LogMessage("Preloader finished");

        Logger.Listeners.Remove(preloaderListener);

        var chainloader = new NetChainloader();

        chainloader.Initialize();
        chainloader.Execute();


        AssemblyFixes.Execute(entrypointAssembly);

        try
        {
            var argList = new List <object>();

            var paramTypes = entrypointAssembly.EntryPoint.GetParameters();

            if (paramTypes.Length == 1 && paramTypes[0].ParameterType == typeof(string[]))
            {
                argList.Add(args);
            }
            else if (paramTypes.Length == 1 && paramTypes[0].ParameterType == typeof(string))
            {
                argList.Add(string.Join(" ", args));
            }
            else if (paramTypes.Length != 0)
            {
                // Only other entrypoint signatures I can think of that .NET supports is Task / Task<int>
                //   async entrypoints. That's a can of worms for another time though

                Log.LogFatal($"Could not figure out how to handle entrypoint method with this signature: {entrypointAssembly.EntryPoint.FullDescription()}");
                return;
            }

            entrypointAssembly.EntryPoint.Invoke(null, argList.ToArray());
        }
        catch (Exception ex)
        {
            Log.LogFatal($"Unhandled exception: {ex}");
        }
    }