Scripting Hotfix for #2458 (#2460)

* Scripting hotfix

- Fixes #2458
- Change error wording around scripting
- Prevent duplicating the binary for scripting

* Create temporary folder if it doesn't exist
This commit is contained in:
breadbyte 2023-03-28 21:06:56 +08:00 committed by GitHub
parent 2f1da9e8c9
commit 74d29321b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -82,18 +82,35 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder
if (string.IsNullOrEmpty(MinecraftClientDll)) if (string.IsNullOrEmpty(MinecraftClientDll))
{ {
// Create a temporary file to copy the executable to. // Create a temporary file to copy the executable to.
var executableDir = AppContext.BaseDirectory; var executablePath = Environment.ProcessPath;
var executablePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Path.Combine(executableDir, "MinecraftClient.exe") : Path.Combine(executableDir, "MinecraftClient"); var tempPath = Path.Combine(Path.GetTempPath(), "mcc-scripting");
var tempFileName = Path.GetTempFileName(); Directory.CreateDirectory(tempPath);
if (File.Exists(executablePath))
var tempFile = Path.Combine(tempPath, "mcc-executable");
var useExisting = false;
// Check if we already have the executable in the temporary path.
foreach (var file in Directory.EnumerateFiles(tempPath))
{ {
if (file.EndsWith("mcc-executable"))
{
useExisting = true;
break;
}
}
if (!File.Exists(executablePath))
{
throw new FileNotFoundException("[Script Error] Could not locate the current folder of MCC for scripting.");
}
// Copy the executable to a temporary path. // Copy the executable to a temporary path.
ExecutableReader e = new(); if (!useExisting)
File.Delete(tempFileName); File.Copy(executablePath, tempFile);
File.Copy(executablePath, tempFileName);
// Access the contents of the executable. // Access the contents of the executable.
var viewAccessor = MemoryMappedFile.CreateFromFile(tempFileName, FileMode.Open).CreateViewAccessor(); ExecutableReader e = new();
var viewAccessor = MemoryMappedFile.CreateFromFile(tempFile, FileMode.Open).CreateViewAccessor();
var manifest = e.ReadManifest(viewAccessor); var manifest = e.ReadManifest(viewAccessor);
var files = manifest.Files; var files = manifest.Files;
@ -103,29 +120,30 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder
assemblyrefs.Add(new("MinecraftClient")); assemblyrefs.Add(new("MinecraftClient"));
assemblyrefs.Add(new("System.Private.CoreLib")); assemblyrefs.Add(new("System.Private.CoreLib"));
foreach (var refs in assemblyrefs) foreach (var refs in assemblyrefs) {
{
var loadedAssembly = Assembly.Load(refs); var loadedAssembly = Assembly.Load(refs);
if (string.IsNullOrEmpty(loadedAssembly.Location)) if (string.IsNullOrEmpty(loadedAssembly.Location)) {
{
// Check if we can access the file from the executable. // 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 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); var refCount = files.Count(x => x.RelativePath.Remove(x.RelativePath.Length - 4) == refs.Name);
if (refCount > 1) if (refCount > 1) {
{
// Safety net for the case where the assembly is referenced multiple times. // 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. // 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); throw new InvalidOperationException(
"[Script Error] Too many references to the same assembly. Assembly name: " + refs.Name);
} }
if (reference == null)
{ if (reference == null) {
throw new InvalidOperationException("The executable does not contain a referenced assembly. Assembly name: " + refs.Name); throw new InvalidOperationException(
"[Script Error] The executable does not contain a referenced assembly. Assembly name: " + refs.Name);
} }
assemblyStream = GetStreamForFileEntry(viewAccessor, reference); assemblyStream = GetStreamForFileEntry(viewAccessor, reference);
references.Add(MetadataReference.CreateFromStream(assemblyStream!)); references.Add(MetadataReference.CreateFromStream(assemblyStream!));
continue; continue;
} }
references.Add(MetadataReference.CreateFromFile(loadedAssembly.Location)); references.Add(MetadataReference.CreateFromFile(loadedAssembly.Location));
} }
@ -133,7 +151,6 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder
viewAccessor.Flush(); viewAccessor.Flush();
viewAccessor.Dispose(); viewAccessor.Dispose();
} }
}
else else
{ {
references.Add(MetadataReference.CreateFromFile(SystemPrivateCoreLib)); references.Add(MetadataReference.CreateFromFile(SystemPrivateCoreLib));
@ -154,7 +171,7 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder
private static Stream? GetStreamForFileEntry(MemoryMappedViewAccessor viewAccessor, FileEntry file) private static Stream? GetStreamForFileEntry(MemoryMappedViewAccessor viewAccessor, FileEntry file)
{ {
if (typeof(BundleExtractor).GetMethod("GetStreamForFileEntry", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, new object[] { viewAccessor, file }) is not Stream stream) if (typeof(BundleExtractor).GetMethod("GetStreamForFileEntry", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, new object[] { viewAccessor, file }) is not Stream stream)
throw new InvalidOperationException("The executable does not contain the assembly. Assembly name: " + file.RelativePath); throw new InvalidOperationException("[Script Error] The executable does not contain the assembly. Assembly name: " + file.RelativePath);
return stream; return stream;
} }