Scripting Hotfix for .NET (#2061)

* fix scripting being broken

scripting got broken due to being compiled as a single file application.

* move invoke to a helper function

also move MinecraftClient assembly fetch into the main assembly fetch loop

* Downgrade version of SingleFileExtractor

1.1.0 changed their internal API, so we use 1.0.1 to reduce the amount of reflection we need to do

* add exception messages
This commit is contained in:
breadbyte 2022-08-16 00:30:56 +08:00 committed by GitHub
parent aa1f54d0d8
commit fd7f79402f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 89 additions and 8 deletions

View file

@ -35,6 +35,7 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.Compatibility" Version="5.0.2" />
<PackageReference Include="SingleFileExtractor.Core" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Remove="config\ChatBots\AutoLook.cs" />

View file

@ -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>
{
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;