Skip to content

Commit 2a1087e

Browse files
Update oxide resolver in order to properly resolve missing assemblies
1 parent c966abd commit 2a1087e

1 file changed

Lines changed: 190 additions & 51 deletions

File tree

src/Common/OxideResolver.cs

Lines changed: 190 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System.Collections.Immutable;
2+
using System.Reflection.Metadata;
3+
using System.Runtime.CompilerServices;
24
using Microsoft.CodeAnalysis;
35
using Microsoft.Extensions.Logging;
6+
using Oxide.CompilerServices.Types.Compilation;
47
using Oxide.CompilerServices.Types.Configuration;
58

69
namespace Oxide.CompilerServices.Common;
@@ -10,118 +13,254 @@ public class OxideResolver : MetadataReferenceResolver
1013
private readonly ILogger _logger;
1114
private readonly AppConfiguration _appConfiguration;
1215
private readonly string _runtimePath;
13-
private readonly HashSet<PortableExecutableReference> _referenceCache;
16+
private readonly Dictionary<string, AssemblyMetadata> _references;
17+
private readonly HashSet<string>? _mergedAssemblyNames;
1418
public override bool ResolveMissingAssemblies => true;
1519

1620
public OxideResolver(ILogger<OxideResolver> logger, AppConfiguration appConfiguration)
1721
{
1822
_logger = logger;
1923
_appConfiguration = appConfiguration;
2024
_runtimePath = appConfiguration.GetCompilerConfiguration().FrameworkPath;
21-
_referenceCache = new HashSet<PortableExecutableReference>();
25+
_references = new Dictionary<string, AssemblyMetadata>();
26+
_mergedAssemblyNames =
27+
GetMergedAssemblies(Path.Combine(appConfiguration.GetDirectoryConfiguration().Libraries, "Oxide.References.dll"));
2228
}
2329

24-
public override bool Equals(object? other) => other?.Equals(this) ?? false;
30+
public override bool Equals(object? other) => ReferenceEquals(this, other);
2531

26-
public override int GetHashCode() => GetType().GetHashCode();
32+
public override int GetHashCode() => RuntimeHelpers.GetHashCode(this);
2733

2834
public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference, string? baseFilePath,
29-
MetadataReferenceProperties properties)
35+
MetadataReferenceProperties properties) => ImmutableArray<PortableExecutableReference>.Empty;
36+
37+
public override PortableExecutableReference? ResolveMissingAssembly(MetadataReference metadataReference,
38+
AssemblyIdentity assemblyIdentity) => Resolve(metadataReference, assemblyIdentity);
39+
40+
public PortableExecutableReference? GetOrAddReference(string name)
3041
{
31-
_logger.LogInformation("Resolving: {Reference} {BaseFilePath}", reference, baseFilePath);
32-
return ImmutableArray<PortableExecutableReference>.Empty;
33-
}
42+
if (name.Equals("System.Private.CoreLib.dll"))
43+
{
44+
name = "mscorlib.dll";
45+
}
46+
47+
PortableExecutableReference? cachedReference = GetCachedReference(name);
48+
if (cachedReference != null)
49+
{
50+
return cachedReference;
51+
}
52+
53+
string libraryPath = GetLibraryPath(name);
54+
FileInfo fileInfo = new(libraryPath);
3455

35-
public override PortableExecutableReference? ResolveMissingAssembly(MetadataReference metadataReference, AssemblyIdentity assemblyIdentity) =>
36-
Resolve(metadataReference, assemblyIdentity);
56+
_logger.LogDebug("Adding reference {0} from {1}", name, fileInfo.FullName);
57+
58+
if (fileInfo.Exists)
59+
{
60+
_logger.LogDebug("Creating reference from {0}", fileInfo.FullName);
61+
return GetMetadataReferenceFromFile(name, fileInfo.FullName);
62+
}
63+
64+
fileInfo = new FileInfo(Path.Combine(_runtimePath, name));
65+
if (fileInfo.Exists)
66+
{
67+
_logger.LogDebug("Creating reference from {0}", fileInfo.FullName);
68+
return GetMetadataReferenceFromFile(name, fileInfo.FullName);
69+
}
3770

38-
public PortableExecutableReference? Resolve(MetadataReference metadataReference, AssemblyIdentity assemblyIdentity)
71+
_logger.LogError("Unable to find required dependency {0}", name);
72+
return null;
73+
}
74+
75+
public PortableExecutableReference? GetOrAddReference(CompilerFile compilerFile)
3976
{
40-
string? name = metadataReference.Display;
41-
if (string.IsNullOrWhiteSpace(name))
77+
string name = compilerFile.Name;
78+
79+
_logger.LogDebug("Adding reference {0}", name);
80+
81+
if (IsMergedAssembly(name))
4282
{
43-
return null;
83+
_logger.LogDebug("Assembly {0} is a merged assembly, routing to {1}", name, "Oxide.References.dll");
84+
name = "Oxide.References.dll";
4485
}
4586

46-
PortableExecutableReference? reference = _referenceCache.FirstOrDefault(r =>
47-
Path.GetFileName(r.Display) == name);
87+
PortableExecutableReference? cachedReference = GetCachedReference(name);
88+
if (cachedReference != null)
89+
{
90+
return cachedReference;
91+
}
4892

49-
if (reference != null)
93+
if (ShouldLoadFromFile(compilerFile))
5094
{
51-
return reference;
95+
_logger.LogDebug("Creating reference {0} from file", name);
96+
return GetMetadataReferenceFromFile(name);
5297
}
5398

54-
if (name.Equals("System.Private.CoreLib.dll"))
99+
_logger.LogDebug("Creating reference from image {0}", name);
100+
return GetMetadataReferenceFromImage(compilerFile.Data, name);
101+
}
102+
103+
private PortableExecutableReference? Resolve(MetadataReference metadataReference, AssemblyIdentity assemblyIdentity)
104+
{
105+
_logger.LogDebug("{0} requires {1}", metadataReference.Display, assemblyIdentity.GetDisplayName());
106+
107+
string name = $"{assemblyIdentity.Name}.dll";
108+
if (name == "System.Private.CoreLib.dll")
55109
{
56110
name = "mscorlib.dll";
57111
}
58112

59-
string path = Path.Combine(_appConfiguration.GetDirectoryConfiguration().Libraries, name);
60-
FileInfo fileInfo = new(path);
113+
if (IsMergedAssembly(name))
114+
{
115+
_logger.LogDebug("Assembly {0} is a merged assembly, routing to {1}", name, "Oxide.References.dll");
116+
name = "Oxide.References.dll";
117+
}
118+
119+
PortableExecutableReference? cachedReference = GetCachedReference(name);
120+
if (cachedReference != null)
121+
{
122+
return cachedReference;
123+
}
124+
125+
string libraryPath = GetLibraryPath(name);
126+
FileInfo fileInfo = new(libraryPath);
61127

62-
_logger.LogDebug("Attempting to resolve {0} [{1}] from {2}", name, assemblyIdentity.Version, fileInfo.FullName);
128+
_logger.LogDebug("Resolving {0} from {1}", name, fileInfo.FullName);
63129

64130
if (fileInfo.Exists)
65131
{
66-
reference = MetadataReference.CreateFromFile(fileInfo.FullName);
67-
_referenceCache.Add(reference);
68-
return reference;
132+
_logger.LogDebug("Creating reference from {0}", fileInfo.FullName);
133+
return GetMetadataReferenceFromFile(name, fileInfo.FullName);
69134
}
70135

71136
fileInfo = new FileInfo(Path.Combine(_runtimePath, name));
72-
73137
if (fileInfo.Exists)
74138
{
75-
reference = MetadataReference.CreateFromFile(fileInfo.FullName);
76-
_referenceCache.Add(reference);
77-
return reference;
139+
_logger.LogDebug("Creating reference from {0}", fileInfo.FullName);
140+
return GetMetadataReferenceFromFile(name, fileInfo.FullName);
78141
}
79142

80143
_logger.LogError("Unable to find required dependency {0}", name);
81144
return null;
82145
}
83146

84-
public PortableExecutableReference? AddReference(string? name)
147+
private HashSet<string>? GetMergedAssemblies(string path)
85148
{
86-
if (string.IsNullOrWhiteSpace(name))
149+
using AssemblyMetadata metadata = AssemblyMetadata.CreateFromFile(path);
150+
ImmutableArray<ModuleMetadata> metadataModules = metadata.GetModules();
151+
if (metadataModules.Length == 0)
87152
{
88153
return null;
89154
}
90155

91-
PortableExecutableReference? reference = _referenceCache.FirstOrDefault(r =>
92-
Path.GetFileName(r.Display) == name);
156+
ModuleMetadata metadataModule = metadataModules[0];
157+
MetadataReader metadataReader = metadataModule.GetMetadataReader();
158+
159+
AssemblyDefinition assemblyDefinition = metadataReader.GetAssemblyDefinition();
160+
CustomAttributeHandleCollection customAttributes = assemblyDefinition.GetCustomAttributes();
93161

94-
if (reference != null)
162+
HashSet<string> collection = new();
163+
foreach (CustomAttributeHandle customAttributeHandle in customAttributes)
95164
{
96-
return reference;
165+
CustomAttribute customAttribute = metadataReader.GetCustomAttribute(customAttributeHandle);
166+
if (!IsAssemblyMetadataAttribute(metadataReader, customAttribute))
167+
{
168+
continue;
169+
}
170+
171+
BlobReader blobReader = metadataReader.GetBlobReader(customAttribute.Value);
172+
173+
blobReader.ReadUInt16();
174+
175+
string? key = blobReader.ReadSerializedString();
176+
if (key != "Oxide.MergedAssembly")
177+
{
178+
continue;
179+
}
180+
181+
string? value = blobReader.ReadSerializedString();
182+
if (string.IsNullOrEmpty(value))
183+
{
184+
continue;
185+
}
186+
187+
collection.Add($"{value}.dll");
97188
}
98189

99-
if (name.Equals("System.Private.CoreLib.dll"))
190+
return collection;
191+
}
192+
193+
private bool IsAssemblyMetadataAttribute(MetadataReader metadataReader, CustomAttribute customAttribute)
194+
{
195+
EntityHandle constructor = customAttribute.Constructor;
196+
if (constructor.Kind != HandleKind.MemberReference)
100197
{
101-
name = "mscorlib.dll";
198+
return false;
102199
}
103200

104-
string path = Path.Combine(_appConfiguration.GetDirectoryConfiguration().Libraries, name);
105-
FileInfo fileInfo = new(path);
201+
MemberReference memberReference = metadataReader.GetMemberReference((MemberReferenceHandle)constructor);
202+
if (memberReference.Parent.Kind != HandleKind.TypeReference)
203+
{
204+
return false;
205+
}
106206

107-
_logger.LogDebug("Adding reference {0} from {1}", name, fileInfo.FullName);
207+
TypeReference typeRef = metadataReader.GetTypeReference((TypeReferenceHandle)memberReference.Parent);
108208

109-
if (fileInfo.Exists)
209+
return metadataReader.GetString(typeRef.Namespace) == "System.Reflection"
210+
&& metadataReader.GetString(typeRef.Name) == "AssemblyMetadataAttribute";
211+
}
212+
213+
private bool IsMergedAssembly(string name)
214+
{
215+
if (_mergedAssemblyNames != null)
110216
{
111-
reference = MetadataReference.CreateFromFile(fileInfo.FullName);
112-
_referenceCache.Add(reference);
113-
return reference;
217+
return _mergedAssemblyNames.Contains(name);
114218
}
115219

116-
fileInfo = new FileInfo(Path.Combine(_runtimePath, name));
117-
if (fileInfo.Exists)
220+
_logger.LogError("Merged assembly names were not found");
221+
return false;
222+
}
223+
224+
private PortableExecutableReference? GetCachedReference(string name)
225+
{
226+
AssemblyMetadata? reference = _references.GetValueOrDefault(name);
227+
if (reference == null)
118228
{
119-
reference = MetadataReference.CreateFromFile(fileInfo.FullName);
120-
_referenceCache.Add(reference);
121-
return reference;
229+
return null;
122230
}
123231

124-
_logger.LogError("Unable to find required dependency {0}", name);
125-
return null;
232+
_logger.LogDebug("Found cached reference for {0}", name);
233+
return reference.GetReference(filePath: name);
234+
}
235+
236+
private PortableExecutableReference GetMetadataReferenceFromImage(byte[] data, string name)
237+
{
238+
AssemblyMetadata assemblyMetadata = AssemblyMetadata.CreateFromImage(data);
239+
_references.Add(name, assemblyMetadata);
240+
return assemblyMetadata.GetReference(filePath: name);
241+
}
242+
243+
private PortableExecutableReference GetMetadataReferenceFromFile(string name, string? path = null)
244+
{
245+
AssemblyMetadata assemblyMetadata = AssemblyMetadata.CreateFromFile(path ?? name);
246+
_references.Add(name, assemblyMetadata);
247+
return assemblyMetadata.GetReference(filePath: name);
248+
}
249+
250+
private bool ShouldLoadFromFile(CompilerFile compilerFile) =>
251+
File.Exists(compilerFile.Name) && (compilerFile.Data == null || compilerFile.Data.Length == 0);
252+
253+
private string GetLibraryPath(string name) =>
254+
Path.Combine(_appConfiguration.GetDirectoryConfiguration().Libraries, name);
255+
256+
public void Cleanup()
257+
{
258+
_logger.LogDebug("Cleaning up {0} references", _references.Count);
259+
foreach (KeyValuePair<string, AssemblyMetadata> entry in _references)
260+
{
261+
entry.Value.Dispose();
262+
}
263+
264+
_references.Clear();
126265
}
127266
}

0 commit comments

Comments
 (0)