Skip to content

Commit fd7f794

Browse files
authored
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
1 parent aa1f54d commit fd7f794

File tree

2 files changed

+89
-8
lines changed

2 files changed

+89
-8
lines changed

MinecraftClient/MinecraftClient.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<PrivateAssets>all</PrivateAssets>
3636
</PackageReference>
3737
<PackageReference Include="Microsoft.Windows.Compatibility" Version="5.0.2" />
38+
<PackageReference Include="SingleFileExtractor.Core" Version="1.0.1" />
3839
</ItemGroup>
3940
<ItemGroup>
4041
<Compile Remove="config\ChatBots\AutoLook.cs" />

MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ MIT License
77
using System;
88
using System.Collections.Generic;
99
using System.IO;
10+
using System.IO.MemoryMappedFiles;
1011
using System.Linq;
1112
using System.Reflection;
1213
using Microsoft.CodeAnalysis;
1314
using Microsoft.CodeAnalysis.CSharp;
1415
using Microsoft.CodeAnalysis.Text;
1516
using MinecraftClient;
17+
using SingleFileExtractor.Core;
1618

1719
namespace DynamicRun.Builder
1820
{
19-
internal class Compiler
21+
internal class Compiler
2022
{
2123
public CompileResult Compile(string filepath, string fileName)
2224
{
@@ -58,23 +60,101 @@ private static CSharpCompilation GenerateCode(string sourceCode, string fileName
5860

5961
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
6062

63+
var mods = Assembly.GetEntryAssembly().GetModules();
64+
65+
#pragma warning disable IL3000
66+
// System.Private.CoreLib
67+
var A = typeof(object).Assembly.Location;
68+
// System.Console
69+
var B = typeof(Console).Assembly.Location;
70+
// The path to MinecraftClient.dll
71+
var C = typeof(Program).Assembly.Location;
72+
6173
var references = new List<MetadataReference>
6274
{
63-
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
64-
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
65-
MetadataReference.CreateFromFile(typeof(ChatBot).Assembly.Location)
75+
MetadataReference.CreateFromFile(A),
76+
MetadataReference.CreateFromFile(B)
6677
};
67-
68-
Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList()
69-
.ForEach(a => references.Add(MetadataReference.CreateFromFile(Assembly.Load(a).Location)));
7078

79+
// We're on a Single File Application, so we need to extract the executable to get the assembly.
80+
if (string.IsNullOrEmpty(C))
81+
{
82+
// Create a temporary file to copy the executable to.
83+
var executableDir = System.AppContext.BaseDirectory;
84+
var executablePath = Path.Combine(executableDir, "MinecraftClient.exe");
85+
var tempFileName = Path.GetTempFileName();
86+
if (File.Exists(executablePath))
87+
{
88+
// Copy the executable to a temporary path.
89+
ExecutableReader e = new();
90+
File.Delete(tempFileName);
91+
File.Copy(executablePath, tempFileName);
92+
93+
// Access the contents of the executable.
94+
var viewAccessor = MemoryMappedFile.CreateFromFile(tempFileName, FileMode.Open).CreateViewAccessor();
95+
var manifest = e.ReadManifest(viewAccessor);
96+
var files = manifest.Files;
97+
98+
Stream? assemblyStream;
99+
100+
var assemblyrefs = Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList();
101+
assemblyrefs.Add(new ("MinecraftClient"));
102+
103+
foreach (var refs in assemblyrefs) {
104+
var loadedAssembly = Assembly.Load(refs);
105+
if (string.IsNullOrEmpty(loadedAssembly.Location))
106+
{
107+
// Check if we can access the file from the executable.
108+
var reference = files.FirstOrDefault(x => x.RelativePath.Remove(x.RelativePath.Length - 4) == refs.Name);
109+
var refCount = files.Count(x => x.RelativePath.Remove(x.RelativePath.Length - 4) == refs.Name);
110+
if (refCount > 1)
111+
{
112+
// Safety net for the case where the assembly is referenced multiple times.
113+
// Should not happen normally, but we can make exceptions when it does happen.
114+
throw new InvalidOperationException("Too many references to the same assembly. Assembly name: " + refs.Name);
115+
}
116+
if (reference == null)
117+
{
118+
throw new InvalidOperationException("The executable does not contain a referenced assembly. Assembly name: " + refs.Name);
119+
}
120+
121+
assemblyStream = GetStreamForFileEntry(viewAccessor, reference);
122+
references.Add(MetadataReference.CreateFromStream(assemblyStream));
123+
continue;
124+
}
125+
references.Add(MetadataReference.CreateFromFile(loadedAssembly.Location));
126+
}
127+
128+
// Cleanup.
129+
viewAccessor.Flush();
130+
viewAccessor.Dispose();
131+
}
132+
}
133+
else
134+
{
135+
references.Add(MetadataReference.CreateFromFile(C));
136+
Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList().ForEach(a => references.Add(MetadataReference.CreateFromFile(Assembly.Load(a).Location)));
137+
}
138+
#pragma warning restore IL3000
71139
return CSharpCompilation.Create($"{fileName}.dll",
72-
new[] { parsedSyntaxTree },
140+
new[] { parsedSyntaxTree },
73141
references: references,
74142
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
75143
optimizationLevel: OptimizationLevel.Release,
76144
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
77145
}
146+
147+
private static Stream? GetStreamForFileEntry(MemoryMappedViewAccessor viewAccessor, FileEntry file)
148+
{
149+
var stream = typeof(BundleExtractor).GetMethod("GetStreamForFileEntry", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, new object[] { viewAccessor, file }) as Stream;
150+
151+
if (stream == null)
152+
{
153+
throw new InvalidOperationException("The executable does not contain the assembly. Assembly name: " + file.RelativePath);
154+
}
155+
156+
return stream;
157+
}
78158

79159
internal struct CompileResult {
80160
internal byte[]? Assembly;

0 commit comments

Comments
 (0)