/// <summary> /// Amends the specified target assembly, optionally using assembly amendments defined in the /// specified amendment assemblies. /// </summary> /// <param name="targetAssembly"></param> /// <param name="amendmentAssemblies"></param> internal static void Amend(string targetAssembly, string[] amendmentAssemblies, string[] referenceAssemblies) { // Verify the target assembly exists targetAssembly = Path.GetFullPath(targetAssembly); if (!File.Exists(targetAssembly)) throw new ArgumentException("The specified target assembly, " + targetAssembly + ", does not exist."); // Verify the amendment assemblies exist if (amendmentAssemblies == null) amendmentAssemblies = new string[0]; for (int i = 0; i < amendmentAssemblies.Length; i++) { var path = amendmentAssemblies[i] = Path.GetFullPath(amendmentAssemblies[i]); if (!File.Exists(path)) throw new ArgumentException("The specified amendment assembly, " + path + ", does not exist."); } // Verify that the target has not already been amended var afterthoughtTracker = targetAssembly + ".afterthought"; if (File.Exists(afterthoughtTracker) && File.GetLastWriteTime(targetAssembly) == File.GetLastWriteTime(afterthoughtTracker)) return; // Determine the set of target directories and backup locations var targetWriteTime = File.GetLastWriteTime(targetAssembly); var backupTargetAssembly = targetAssembly + ".backup"; var targetDirectory = Path.GetDirectoryName(targetAssembly); File.Delete(backupTargetAssembly); File.Move(targetAssembly, backupTargetAssembly); // Build up a set of paths with resolving assemblies var referencePaths = new Dictionary<string, string>(); foreach (string path in amendmentAssemblies .Union(referenceAssemblies) .Union(Directory.GetFiles(targetDirectory).Where(p => p.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && p != targetAssembly))) referencePaths[Path.GetFileName(path)] = path; // Register an assembly resolver to look in assembly directories when resolving assemblies AppDomain.CurrentDomain.AssemblyResolve += (s, e) => { var assemblyName = new System.Reflection.AssemblyName(e.Name).Name + ".dll"; string referencePath; if (referencePaths.TryGetValue(assemblyName, out referencePath)) return System.Reflection.Assembly.LoadFrom(referencePath); return null; }; // Get the set of amendments to apply from all of the specified assemblies var assemblies = new System.Reflection.Assembly[] { System.Reflection.Assembly.LoadFrom(backupTargetAssembly) }.Union(amendmentAssemblies.Select(a => System.Reflection.Assembly.LoadFrom(a))); var amendments = AmendmentAttribute.GetAmendments(assemblies.First(), assemblies.Skip(1).ToArray()).ToList(); // Exit immediately if there are no amendments in the target assemblies if (amendments.Count == 0) return; // Amend the target assembly Console.Write("Amending " + Path.GetFileName(targetAssembly)); var start = DateTime.Now; using (var host = new PeReader.DefaultHost()) { foreach (var referencePath in referencePaths.Select(p => Path.GetDirectoryName(p.Value)).Distinct()) host.AddLibPath(referencePath); // Load the target assembly IModule module = host.LoadUnitFrom(backupTargetAssembly) as IModule; if (module == null || module == Dummy.Module || module == Dummy.Assembly) throw new ArgumentException(backupTargetAssembly + " is not a PE file containing a CLR assembly, or an error occurred when loading it."); // Copy the assembly to enable it to be mutated module = new MetadataDeepCopier(host).Copy(module); // Load the debug file if it exists PdbReader pdbReader = null; var pdbFile = Path.Combine(targetDirectory, Path.GetFileNameWithoutExtension(targetAssembly) + ".pdb"); var backupPdbFile = pdbFile + ".backup"; if (File.Exists(pdbFile)) { File.Delete(backupPdbFile); File.Move(pdbFile, backupPdbFile); using (var pdbStream = File.OpenRead(backupPdbFile)) { pdbReader = new PdbReader(pdbStream, host); } } // Amend and persist the target assembly using (pdbReader) { // Create and execute a new assembly amender AssemblyAmender amender = new AssemblyAmender(host, pdbReader, amendments, assemblies); amender.TargetRuntimeVersion = module.TargetRuntimeVersion; module = amender.Visit(module); // Save the amended assembly back to the original directory var localScopeProvider = pdbReader == null ? null : new ILGenerator.LocalScopeProvider(pdbReader); using (var pdbWriter = pdbReader != null ? new PdbWriter(pdbFile, pdbReader) : null) { using (var dllStream = File.Create(targetAssembly)) { PeWriter.WritePeToStream(module, host, dllStream, pdbReader, localScopeProvider, pdbWriter); } } } File.SetLastWriteTime(targetAssembly, targetWriteTime); if (pdbReader != null) File.SetLastWriteTime(pdbFile, targetWriteTime); } Console.WriteLine(" (" + DateTime.Now.Subtract(start).TotalSeconds.ToString("0.000") + " seconds)"); // Set the last write time of the afterthought tracker to match the amended assembly to prevent accidental reamending File.WriteAllText(afterthoughtTracker, ""); File.SetLastWriteTime(afterthoughtTracker, File.GetLastWriteTime(targetAssembly)); }
internal static void Amend(params string[] targets) { // Ensure that at least one target assembly was specified if (targets == null || targets.Length == 0) throw new ArgumentException("At least one target assembly must be specified."); // Ensure that the target assemblies exist for (int i = 0; i < targets.Length; i++) { var path = targets[i] = Path.GetFullPath(targets[i]); if (!File.Exists(path)) throw new ArgumentException("The specified target assembly, " + path + ", does not exist."); } // Determine the set of target directories and backup locations var directories = targets .Select(path => Path.GetDirectoryName(path).ToLower()) .Distinct() .Select(directory => new { SourcePath = directory, BackupPath = Directory.CreateDirectory(Path.Combine(directory, "Backup")).FullName }); // Determine the set of dlls, pdbs, and backup files var assemblies = targets .Select(dllPath => new { DllPath = dllPath, DllBackupPath = Path.Combine(Path.Combine(Path.GetDirectoryName(dllPath), "Backup"), Path.GetFileName(dllPath)), PdbPath = Path.Combine(Path.GetDirectoryName(dllPath), Path.GetFileNameWithoutExtension(dllPath) + ".pdb"), PdbBackupPath = Path.Combine(Path.Combine(Path.GetDirectoryName(dllPath), "Backup"), Path.GetFileNameWithoutExtension(dllPath) + ".pdb") }); // Backup the directories containing the targeted dll and pdb files foreach (var directory in directories) { foreach (var file in Directory.GetFiles(directory.SourcePath)) { if (file.ToLower().EndsWith("exe") || file.ToLower().EndsWith("dll") || file.ToLower().EndsWith("pdb")) { var backupPath = Path.Combine(directory.BackupPath, Path.GetFileName(file)); if (File.Exists(backupPath)) File.SetAttributes(backupPath, File.GetAttributes(backupPath) & ~FileAttributes.ReadOnly); File.Copy(file, backupPath, true); } } } // Register an assembly resolver to look in backup folders when resolving assemblies AppDomain.CurrentDomain.AssemblyResolve += (s, e) => { var assemblyName = new AssemblyName(e.Name).Name + ".dll"; // First see if the assembly is located in one of the target backup directories foreach (var directory in directories) { var dependency = Path.Combine(directory.BackupPath, assemblyName); if (File.Exists(dependency)) return System.Reflection.Assembly.LoadFrom(dependency); } return null; }; // Get the set of amendments to apply from all of the specified assemblies var amendments = AmendmentAttribute.GetAmendments(assemblies.Select(a => System.Reflection.Assembly.LoadFrom(a.DllBackupPath)).ToArray()).ToList(); // Exit immediately if there are no amendments in the target assemblies if (amendments.Count == 0) return; // Process each target assembly individually foreach (var assembly in assemblies) { var assemblyAmendments = amendments.Where(a => a.Type.Assembly.Location == assembly.DllBackupPath).ToArray(); if (assemblyAmendments.Length == 0) continue; Console.Write("Amending " + Path.GetFileName(assembly.DllPath)); var start = DateTime.Now; using (var host = new PeReader.DefaultHost()) { // Load the target assembly IModule module = host.LoadUnitFrom(assembly.DllBackupPath) as IModule; if (module == null || module == Dummy.Module || module == Dummy.Assembly) throw new ArgumentException(assembly.DllBackupPath + " is not a PE file containing a CLR assembly, or an error occurred when loading it."); // Copy the assembly to enable it to be mutated module = new MetadataDeepCopier(host).Copy(module); // Load the debug file if it exists PdbReader pdbReader = null; if (File.Exists(assembly.PdbBackupPath)) { using (var pdbStream = File.OpenRead(assembly.PdbBackupPath)) { pdbReader = new PdbReader(pdbStream, host); } } // Amend and persist the target assembly using (pdbReader) { // Create and execute a new assembly amender AssemblyAmender amender = new AssemblyAmender(host, pdbReader, assemblyAmendments); amender.TargetRuntimeVersion = module.TargetRuntimeVersion; module = amender.Visit(module); // Save the amended assembly back to the original directory using (var pdbWriter = pdbReader != null ? new PdbWriter(assembly.PdbPath, pdbReader) : null) { using (var dllStream = File.Create(assembly.DllPath)) { PeWriter.WritePeToStream(module, host, dllStream, pdbReader, null, pdbWriter); } } } } Console.WriteLine(" (" + DateTime.Now.Subtract(start).TotalSeconds.ToString("0.000") + " seconds)"); } }