11using System . Collections . Immutable ;
2+ using System . Reflection . Metadata ;
3+ using System . Runtime . CompilerServices ;
24using Microsoft . CodeAnalysis ;
35using Microsoft . Extensions . Logging ;
6+ using Oxide . CompilerServices . Types . Compilation ;
47using Oxide . CompilerServices . Types . Configuration ;
58
69namespace 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