/// <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); } }
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(); }
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}); }
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); }
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}"); } }