1 //
2 // BaseAssemblyResolver.cs
3 //
4 // Author:
5 //   Jb Evain (jbevain@gmail.com)
6 //
7 // Copyright (c) 2008 - 2011 Jb Evain
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 
29 using System;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Text;
33 
34 using Mono.Collections.Generic;
35 
36 namespace Mono.Cecil {
37 
AssemblyResolveEventHandler(object sender, AssemblyNameReference reference)38 	public delegate AssemblyDefinition AssemblyResolveEventHandler (object sender, AssemblyNameReference reference);
39 
40 	public sealed class AssemblyResolveEventArgs : EventArgs {
41 
42 		readonly AssemblyNameReference reference;
43 
44 		public AssemblyNameReference AssemblyReference {
45 			get { return reference; }
46 		}
47 
AssemblyResolveEventArgs(AssemblyNameReference reference)48 		public AssemblyResolveEventArgs (AssemblyNameReference reference)
49 		{
50 			this.reference = reference;
51 		}
52 	}
53 
54 #if !SILVERLIGHT && !CF
55 	[Serializable]
56 #endif
57 	public class AssemblyResolutionException : FileNotFoundException {
58 
59 		readonly AssemblyNameReference reference;
60 
61 		public AssemblyNameReference AssemblyReference {
62 			get { return reference; }
63 		}
64 
AssemblyResolutionException(AssemblyNameReference reference)65 		public AssemblyResolutionException (AssemblyNameReference reference)
66 			: base (string.Format ("Failed to resolve assembly: '{0}'", reference))
67 		{
68 			this.reference = reference;
69 		}
70 
71 #if !SILVERLIGHT && !CF
AssemblyResolutionException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)72 		protected AssemblyResolutionException (
73 			System.Runtime.Serialization.SerializationInfo info,
74 			System.Runtime.Serialization.StreamingContext context)
75 			: base (info, context)
76 		{
77 		}
78 #endif
79 	}
80 
81 	public abstract class BaseAssemblyResolver : IAssemblyResolver {
82 
83 		static readonly bool on_mono = Type.GetType ("Mono.Runtime") != null;
84 
85 		readonly Collection<string> directories;
86 
87 #if !SILVERLIGHT && !CF
88 		Collection<string> gac_paths;
89 #endif
90 
AddSearchDirectory(string directory)91 		public void AddSearchDirectory (string directory)
92 		{
93 			directories.Add (directory);
94 		}
95 
RemoveSearchDirectory(string directory)96 		public void RemoveSearchDirectory (string directory)
97 		{
98 			directories.Remove (directory);
99 		}
100 
GetSearchDirectories()101 		public string [] GetSearchDirectories ()
102 		{
103 			var directories = new string [this.directories.size];
104 			Array.Copy (this.directories.items, directories, directories.Length);
105 			return directories;
106 		}
107 
Resolve(string fullName)108 		public virtual AssemblyDefinition Resolve (string fullName)
109 		{
110 			return Resolve (fullName, new ReaderParameters ());
111 		}
112 
Resolve(string fullName, ReaderParameters parameters)113 		public virtual AssemblyDefinition Resolve (string fullName, ReaderParameters parameters)
114 		{
115 			if (fullName == null)
116 				throw new ArgumentNullException ("fullName");
117 
118 			return Resolve (AssemblyNameReference.Parse (fullName), parameters);
119 		}
120 
121 		public event AssemblyResolveEventHandler ResolveFailure;
122 
BaseAssemblyResolver()123 		protected BaseAssemblyResolver ()
124 		{
125 			directories = new Collection<string> (2) { ".", "bin" };
126 		}
127 
GetAssembly(string file, ReaderParameters parameters)128 		AssemblyDefinition GetAssembly (string file, ReaderParameters parameters)
129 		{
130 			if (parameters.AssemblyResolver == null)
131 				parameters.AssemblyResolver = this;
132 
133 			return ModuleDefinition.ReadModule (file, parameters).Assembly;
134 		}
135 
Resolve(AssemblyNameReference name)136 		public virtual AssemblyDefinition Resolve (AssemblyNameReference name)
137 		{
138 			return Resolve (name, new ReaderParameters ());
139 		}
140 
Resolve(AssemblyNameReference name, ReaderParameters parameters)141 		public virtual AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters)
142 		{
143 			if (name == null)
144 				throw new ArgumentNullException ("name");
145 			if (parameters == null)
146 				parameters = new ReaderParameters ();
147 
148 			var assembly = SearchDirectory (name, directories, parameters);
149 			if (assembly != null)
150 				return assembly;
151 
152 #if !SILVERLIGHT && !CF
153 			if (name.IsRetargetable) {
154 				// if the reference is retargetable, zero it
155 				name = new AssemblyNameReference (name.Name, new Version (0, 0, 0, 0)) {
156 					PublicKeyToken = Empty<byte>.Array,
157 				};
158 			}
159 
160 			var framework_dir = Path.GetDirectoryName (typeof (object).Module.FullyQualifiedName);
161 
162 			if (IsZero (name.Version)) {
163 				assembly = SearchDirectory (name, new [] { framework_dir }, parameters);
164 				if (assembly != null)
165 					return assembly;
166 			}
167 
168 			if (name.Name == "mscorlib") {
169 				assembly = GetCorlib (name, parameters);
170 				if (assembly != null)
171 					return assembly;
172 			}
173 
174 			assembly = GetAssemblyInGac (name, parameters);
175 			if (assembly != null)
176 				return assembly;
177 
178 			assembly = SearchDirectory (name, new [] { framework_dir }, parameters);
179 			if (assembly != null)
180 				return assembly;
181 #endif
182 
183 			if (ResolveFailure != null) {
184 				assembly = ResolveFailure (this, name);
185 				if (assembly != null)
186 					return assembly;
187 			}
188 
189 			throw new AssemblyResolutionException (name);
190 		}
191 
SearchDirectory(AssemblyNameReference name, IEnumerable<string> directories, ReaderParameters parameters)192 		AssemblyDefinition SearchDirectory (AssemblyNameReference name, IEnumerable<string> directories, ReaderParameters parameters)
193 		{
194 			var extensions = new [] { ".exe", ".dll" };
195 			foreach (var directory in directories) {
196 				foreach (var extension in extensions) {
197 					string file = Path.Combine (directory, name.Name + extension);
198 					if (File.Exists (file))
199 						return GetAssembly (file, parameters);
200 				}
201 			}
202 
203 			return null;
204 		}
205 
IsZero(Version version)206 		static bool IsZero (Version version)
207 		{
208 			return version == null || (version.Major == 0 && version.Minor == 0 && version.Build == 0 && version.Revision == 0);
209 		}
210 
211 #if !SILVERLIGHT && !CF
GetCorlib(AssemblyNameReference reference, ReaderParameters parameters)212 		AssemblyDefinition GetCorlib (AssemblyNameReference reference, ReaderParameters parameters)
213 		{
214 			var version = reference.Version;
215 			var corlib = typeof (object).Assembly.GetName ();
216 
217 			if (corlib.Version == version || IsZero (version))
218 				return GetAssembly (typeof (object).Module.FullyQualifiedName, parameters);
219 
220 			var path = Directory.GetParent (
221 				Directory.GetParent (
222 					typeof (object).Module.FullyQualifiedName).FullName
223 				).FullName;
224 
225 			if (on_mono) {
226 				if (version.Major == 1)
227 					path = Path.Combine (path, "1.0");
228 				else if (version.Major == 2) {
229 					if (version.MajorRevision == 5)
230 						path = Path.Combine (path, "2.1");
231 					else
232 						path = Path.Combine (path, "2.0");
233 				} else if (version.Major == 4)
234 					path = Path.Combine (path, "4.0");
235 				else
236 					throw new NotSupportedException ("Version not supported: " + version);
237 			} else {
238 				switch (version.Major) {
239 				case 1:
240 					if (version.MajorRevision == 3300)
241 						path = Path.Combine (path, "v1.0.3705");
242 					else
243 						path = Path.Combine (path, "v1.0.5000.0");
244 					break;
245 				case 2:
246 					path = Path.Combine (path, "v2.0.50727");
247 					break;
248 				case 4:
249 					path = Path.Combine (path, "v4.0.30319");
250 					break;
251 				default:
252 					throw new NotSupportedException ("Version not supported: " + version);
253 				}
254 			}
255 
256 			var file = Path.Combine (path, "mscorlib.dll");
257 			if (File.Exists (file))
258 				return GetAssembly (file, parameters);
259 
260 			return null;
261 		}
262 
GetGacPaths()263 		static Collection<string> GetGacPaths ()
264 		{
265 			if (on_mono)
266 				return GetDefaultMonoGacPaths ();
267 
268 			var paths = new Collection<string> (2);
269 			var windir = Environment.GetEnvironmentVariable ("WINDIR");
270 			if (windir == null)
271 				return paths;
272 
273 			paths.Add (Path.Combine (windir, "assembly"));
274 			paths.Add (Path.Combine (windir, Path.Combine ("Microsoft.NET", "assembly")));
275 			return paths;
276 		}
277 
GetDefaultMonoGacPaths()278 		static Collection<string> GetDefaultMonoGacPaths ()
279 		{
280 			var paths = new Collection<string> (1);
281 			var gac = GetCurrentMonoGac ();
282 			if (gac != null)
283 				paths.Add (gac);
284 
285 			var gac_paths_env = Environment.GetEnvironmentVariable ("MONO_GAC_PREFIX");
286 			if (string.IsNullOrEmpty (gac_paths_env))
287 				return paths;
288 
289 			var prefixes = gac_paths_env.Split (Path.PathSeparator);
290 			foreach (var prefix in prefixes) {
291 				if (string.IsNullOrEmpty (prefix))
292 					continue;
293 
294 				var gac_path = Path.Combine (Path.Combine (Path.Combine (prefix, "lib"), "mono"), "gac");
295 				if (Directory.Exists (gac_path) && !paths.Contains (gac))
296 					paths.Add (gac_path);
297 			}
298 
299 			return paths;
300 		}
301 
GetCurrentMonoGac()302 		static string GetCurrentMonoGac ()
303 		{
304 			return Path.Combine (
305 				Directory.GetParent (
306 					Path.GetDirectoryName (typeof (object).Module.FullyQualifiedName)).FullName,
307 				"gac");
308 		}
309 
GetAssemblyInGac(AssemblyNameReference reference, ReaderParameters parameters)310 		AssemblyDefinition GetAssemblyInGac (AssemblyNameReference reference, ReaderParameters parameters)
311 		{
312 			if (reference.PublicKeyToken == null || reference.PublicKeyToken.Length == 0)
313 				return null;
314 
315 			if (gac_paths == null)
316 				gac_paths = GetGacPaths ();
317 
318 			if (on_mono)
319 				return GetAssemblyInMonoGac (reference, parameters);
320 
321 			return GetAssemblyInNetGac (reference, parameters);
322 		}
323 
GetAssemblyInMonoGac(AssemblyNameReference reference, ReaderParameters parameters)324 		AssemblyDefinition GetAssemblyInMonoGac (AssemblyNameReference reference, ReaderParameters parameters)
325 		{
326 			for (int i = 0; i < gac_paths.Count; i++) {
327 				var gac_path = gac_paths [i];
328 				var file = GetAssemblyFile (reference, string.Empty, gac_path);
329 				if (File.Exists (file))
330 					return GetAssembly (file, parameters);
331 			}
332 
333 			return null;
334 		}
335 
GetAssemblyInNetGac(AssemblyNameReference reference, ReaderParameters parameters)336 		AssemblyDefinition GetAssemblyInNetGac (AssemblyNameReference reference, ReaderParameters parameters)
337 		{
338 			var gacs = new [] { "GAC_MSIL", "GAC_32", "GAC_64", "GAC" };
339 			var prefixes = new [] { string.Empty, "v4.0_" };
340 
341 			for (int i = 0; i < 2; i++) {
342 				for (int j = 0; j < gacs.Length; j++) {
343 					var gac = Path.Combine (gac_paths [i], gacs [j]);
344 					var file = GetAssemblyFile (reference, prefixes [i], gac);
345 					if (Directory.Exists (gac) && File.Exists (file))
346 						return GetAssembly (file, parameters);
347 				}
348 			}
349 
350 			return null;
351 		}
352 
GetAssemblyFile(AssemblyNameReference reference, string prefix, string gac)353 		static string GetAssemblyFile (AssemblyNameReference reference, string prefix, string gac)
354 		{
355 			var gac_folder = new StringBuilder ()
356 				.Append (prefix)
357 				.Append (reference.Version)
358 				.Append ("__");
359 
360 			for (int i = 0; i < reference.PublicKeyToken.Length; i++)
361 				gac_folder.Append (reference.PublicKeyToken [i].ToString ("x2"));
362 
363 			return Path.Combine (
364 				Path.Combine (
365 					Path.Combine (gac, reference.Name), gac_folder.ToString ()),
366 				reference.Name + ".dll");
367 		}
368 #endif
369 	}
370 }
371