diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 46d57543..e7947ff9 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -35,6 +35,7 @@ all + diff --git a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs index dc83df0a..3bc7c2ae 100644 --- a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs +++ b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs @@ -7,16 +7,18 @@ https://github.com/laurentkempe/DynamicRun/blob/master/LICENSE using System; using System.Collections.Generic; using System.IO; +using System.IO.MemoryMappedFiles; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; using MinecraftClient; +using SingleFileExtractor.Core; namespace DynamicRun.Builder { - internal class Compiler + internal class Compiler { public CompileResult Compile(string filepath, string fileName) { @@ -58,23 +60,101 @@ namespace DynamicRun.Builder var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options); + var mods = Assembly.GetEntryAssembly().GetModules(); + +#pragma warning disable IL3000 + // System.Private.CoreLib + var A = typeof(object).Assembly.Location; + // System.Console + var B = typeof(Console).Assembly.Location; + // The path to MinecraftClient.dll + var C = typeof(Program).Assembly.Location; + var references = new List { - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ChatBot).Assembly.Location) + MetadataReference.CreateFromFile(A), + MetadataReference.CreateFromFile(B) }; - - Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList() - .ForEach(a => references.Add(MetadataReference.CreateFromFile(Assembly.Load(a).Location))); + // We're on a Single File Application, so we need to extract the executable to get the assembly. + if (string.IsNullOrEmpty(C)) + { + // Create a temporary file to copy the executable to. + var executableDir = System.AppContext.BaseDirectory; + var executablePath = Path.Combine(executableDir, "MinecraftClient.exe"); + var tempFileName = Path.GetTempFileName(); + if (File.Exists(executablePath)) + { + // Copy the executable to a temporary path. + ExecutableReader e = new(); + File.Delete(tempFileName); + File.Copy(executablePath, tempFileName); + + // Access the contents of the executable. + var viewAccessor = MemoryMappedFile.CreateFromFile(tempFileName, FileMode.Open).CreateViewAccessor(); + var manifest = e.ReadManifest(viewAccessor); + var files = manifest.Files; + + Stream? assemblyStream; + + var assemblyrefs = Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList(); + assemblyrefs.Add(new ("MinecraftClient")); + + foreach (var refs in assemblyrefs) { + var loadedAssembly = Assembly.Load(refs); + if (string.IsNullOrEmpty(loadedAssembly.Location)) + { + // Check if we can access the file from the executable. + var reference = files.FirstOrDefault(x => x.RelativePath.Remove(x.RelativePath.Length - 4) == refs.Name); + var refCount = files.Count(x => x.RelativePath.Remove(x.RelativePath.Length - 4) == refs.Name); + if (refCount > 1) + { + // Safety net for the case where the assembly is referenced multiple times. + // Should not happen normally, but we can make exceptions when it does happen. + throw new InvalidOperationException("Too many references to the same assembly. Assembly name: " + refs.Name); + } + if (reference == null) + { + throw new InvalidOperationException("The executable does not contain a referenced assembly. Assembly name: " + refs.Name); + } + + assemblyStream = GetStreamForFileEntry(viewAccessor, reference); + references.Add(MetadataReference.CreateFromStream(assemblyStream)); + continue; + } + references.Add(MetadataReference.CreateFromFile(loadedAssembly.Location)); + } + + // Cleanup. + viewAccessor.Flush(); + viewAccessor.Dispose(); + } + } + else + { + references.Add(MetadataReference.CreateFromFile(C)); + Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList().ForEach(a => references.Add(MetadataReference.CreateFromFile(Assembly.Load(a).Location))); + } +#pragma warning restore IL3000 return CSharpCompilation.Create($"{fileName}.dll", - new[] { parsedSyntaxTree }, + new[] { parsedSyntaxTree }, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release, assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default)); } + + private static Stream? GetStreamForFileEntry(MemoryMappedViewAccessor viewAccessor, FileEntry file) + { + var stream = typeof(BundleExtractor).GetMethod("GetStreamForFileEntry", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, new object[] { viewAccessor, file }) as Stream; + + if (stream == null) + { + throw new InvalidOperationException("The executable does not contain the assembly. Assembly name: " + file.RelativePath); + } + + return stream; + } internal struct CompileResult { internal byte[]? Assembly;