diff options
Diffstat (limited to 'src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs')
-rw-r--r-- | src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs new file mode 100644 index 00000000..76ff79b3 --- /dev/null +++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs | |||
@@ -0,0 +1,711 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Tools.MakeSfxCA | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Security; | ||
9 | using System.Text; | ||
10 | using System.Reflection; | ||
11 | using Compression; | ||
12 | using Compression.Cab; | ||
13 | using Resources; | ||
14 | using ResourceCollection = Resources.ResourceCollection; | ||
15 | |||
16 | /// <summary> | ||
17 | /// Command-line tool for building self-extracting custom action packages. | ||
18 | /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's | ||
19 | /// entry-points and file version to look like the CA module. | ||
20 | /// </summary> | ||
21 | public static class MakeSfxCA | ||
22 | { | ||
23 | private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll"; | ||
24 | |||
25 | private static TextWriter log; | ||
26 | |||
27 | /// <summary> | ||
28 | /// Prints usage text for the tool. | ||
29 | /// </summary> | ||
30 | /// <param name="w">Console text writer.</param> | ||
31 | public static void Usage(TextWriter w) | ||
32 | { | ||
33 | w.WriteLine("Deployment Tools Foundation custom action packager version {0}", | ||
34 | Assembly.GetExecutingAssembly().GetName().Version); | ||
35 | w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved."); | ||
36 | w.WriteLine(); | ||
37 | w.WriteLine("Usage: MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]"); | ||
38 | w.WriteLine(); | ||
39 | w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package."); | ||
40 | w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY); | ||
41 | w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config"); | ||
42 | } | ||
43 | |||
44 | /// <summary> | ||
45 | /// Runs the MakeSfxCA command-line tool. | ||
46 | /// </summary> | ||
47 | /// <param name="args">Command-line arguments.</param> | ||
48 | /// <returns>0 on success, nonzero on failure.</returns> | ||
49 | public static int Main(string[] args) | ||
50 | { | ||
51 | if (args.Length < 3) | ||
52 | { | ||
53 | Usage(Console.Out); | ||
54 | return 1; | ||
55 | } | ||
56 | |||
57 | var output = args[0]; | ||
58 | var sfxDll = args[1]; | ||
59 | var inputs = new string[args.Length - 2]; | ||
60 | Array.Copy(args, 2, inputs, 0, inputs.Length); | ||
61 | |||
62 | try | ||
63 | { | ||
64 | Build(output, sfxDll, inputs, Console.Out); | ||
65 | return 0; | ||
66 | } | ||
67 | catch (ArgumentException ex) | ||
68 | { | ||
69 | Console.Error.WriteLine("Error: Invalid argument: " + ex.Message); | ||
70 | return 1; | ||
71 | } | ||
72 | catch (FileNotFoundException ex) | ||
73 | { | ||
74 | Console.Error.WriteLine("Error: Cannot find file: " + ex.Message); | ||
75 | return 1; | ||
76 | } | ||
77 | catch (Exception ex) | ||
78 | { | ||
79 | Console.Error.WriteLine("Error: Unexpected error: " + ex); | ||
80 | return 1; | ||
81 | } | ||
82 | } | ||
83 | |||
84 | /// <summary> | ||
85 | /// Packages up all the inputs to the output location. | ||
86 | /// </summary> | ||
87 | /// <exception cref="Exception">Various exceptions are thrown | ||
88 | /// if things go wrong.</exception> | ||
89 | public static void Build(string output, string sfxDll, IList<string> inputs, TextWriter log) | ||
90 | { | ||
91 | MakeSfxCA.log = log; | ||
92 | |||
93 | if (string.IsNullOrEmpty(output)) | ||
94 | { | ||
95 | throw new ArgumentNullException("output"); | ||
96 | } | ||
97 | |||
98 | if (string.IsNullOrEmpty(sfxDll)) | ||
99 | { | ||
100 | throw new ArgumentNullException("sfxDll"); | ||
101 | } | ||
102 | |||
103 | if (inputs == null || inputs.Count == 0) | ||
104 | { | ||
105 | throw new ArgumentNullException("inputs"); | ||
106 | } | ||
107 | |||
108 | if (!File.Exists(sfxDll)) | ||
109 | { | ||
110 | throw new FileNotFoundException(sfxDll); | ||
111 | } | ||
112 | |||
113 | var customActionAssembly = inputs[0]; | ||
114 | if (!File.Exists(customActionAssembly)) | ||
115 | { | ||
116 | throw new FileNotFoundException(customActionAssembly); | ||
117 | } | ||
118 | |||
119 | inputs = MakeSfxCA.SplitList(inputs); | ||
120 | |||
121 | var inputsMap = MakeSfxCA.GetPackFileMap(inputs); | ||
122 | |||
123 | var foundWIAssembly = false; | ||
124 | foreach (var input in inputsMap.Keys) | ||
125 | { | ||
126 | if (string.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY, | ||
127 | StringComparison.OrdinalIgnoreCase) == 0) | ||
128 | { | ||
129 | foundWIAssembly = true; | ||
130 | } | ||
131 | } | ||
132 | |||
133 | if (!foundWIAssembly) | ||
134 | { | ||
135 | throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY + | ||
136 | " must be included in the list of support files. " + | ||
137 | "If using the MSBuild targets, make sure the assembly reference " + | ||
138 | "has the Private (Copy Local) flag set."); | ||
139 | } | ||
140 | |||
141 | MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly)); | ||
142 | |||
143 | var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly); | ||
144 | var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly); | ||
145 | |||
146 | if (entryPoints.Count == 0 && uiClass == null) | ||
147 | { | ||
148 | throw new ArgumentException( | ||
149 | "No CA or UI entry points found in module: " + customActionAssembly); | ||
150 | } | ||
151 | else if (entryPoints.Count > 0 && uiClass != null) | ||
152 | { | ||
153 | throw new NotSupportedException( | ||
154 | "CA and UI entry points cannot be in the same assembly: " + customActionAssembly); | ||
155 | } | ||
156 | |||
157 | var dir = Path.GetDirectoryName(output); | ||
158 | if (dir.Length > 0 && !Directory.Exists(dir)) | ||
159 | { | ||
160 | Directory.CreateDirectory(dir); | ||
161 | } | ||
162 | |||
163 | using (Stream outputStream = File.Create(output)) | ||
164 | { | ||
165 | MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass); | ||
166 | } | ||
167 | |||
168 | MakeSfxCA.CopyVersionResource(customActionAssembly, output); | ||
169 | |||
170 | MakeSfxCA.PackInputFiles(output, inputsMap); | ||
171 | |||
172 | log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName); | ||
173 | } | ||
174 | |||
175 | /// <summary> | ||
176 | /// Splits any list items delimited by semicolons into separate items. | ||
177 | /// </summary> | ||
178 | /// <param name="list">Read-only input list.</param> | ||
179 | /// <returns>New list with resulting split items.</returns> | ||
180 | private static IList<string> SplitList(IList<string> list) | ||
181 | { | ||
182 | var newList = new List<string>(list.Count); | ||
183 | |||
184 | foreach (var item in list) | ||
185 | { | ||
186 | if (!string.IsNullOrEmpty(item)) | ||
187 | { | ||
188 | foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) | ||
189 | { | ||
190 | newList.Add(splitItem); | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | |||
195 | return newList; | ||
196 | } | ||
197 | |||
198 | /// <summary> | ||
199 | /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection. | ||
200 | /// </summary> | ||
201 | /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param> | ||
202 | /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param> | ||
203 | /// <remarks> | ||
204 | /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them | ||
205 | /// to the list of input files if found. | ||
206 | /// </remarks> | ||
207 | private static void ResolveDependentAssemblies(IDictionary<string, string> inputFiles, string inputDir) | ||
208 | { | ||
209 | AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) | ||
210 | { | ||
211 | AssemblyName resolveName = new AssemblyName(args.Name); | ||
212 | Assembly assembly = null; | ||
213 | |||
214 | // First, try to find the assembly in the list of input files. | ||
215 | foreach (var inputFile in inputFiles.Values) | ||
216 | { | ||
217 | var inputName = Path.GetFileNameWithoutExtension(inputFile); | ||
218 | var inputExtension = Path.GetExtension(inputFile); | ||
219 | if (string.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) && | ||
220 | (string.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) || | ||
221 | string.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase))) | ||
222 | { | ||
223 | assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile); | ||
224 | |||
225 | if (assembly != null) | ||
226 | { | ||
227 | break; | ||
228 | } | ||
229 | } | ||
230 | } | ||
231 | |||
232 | // Second, try to find the assembly in the input directory. | ||
233 | if (assembly == null && inputDir != null) | ||
234 | { | ||
235 | string assemblyPath = null; | ||
236 | if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll")) | ||
237 | { | ||
238 | assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll"; | ||
239 | } | ||
240 | else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe")) | ||
241 | { | ||
242 | assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe"; | ||
243 | } | ||
244 | |||
245 | if (assemblyPath != null) | ||
246 | { | ||
247 | assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath); | ||
248 | |||
249 | if (assembly != null) | ||
250 | { | ||
251 | // Add this detected dependency to the list of files to be packed. | ||
252 | inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath); | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | |||
257 | // Third, try to load the assembly from the GAC. | ||
258 | if (assembly == null) | ||
259 | { | ||
260 | try | ||
261 | { | ||
262 | assembly = Assembly.ReflectionOnlyLoad(args.Name); | ||
263 | } | ||
264 | catch (FileNotFoundException) | ||
265 | { | ||
266 | } | ||
267 | } | ||
268 | |||
269 | if (assembly != null) | ||
270 | { | ||
271 | if (string.Equals(assembly.GetName().ToString(), resolveName.ToString())) | ||
272 | { | ||
273 | log.WriteLine(" Loaded dependent assembly: " + assembly.Location); | ||
274 | return assembly; | ||
275 | } | ||
276 | |||
277 | log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location); | ||
278 | log.WriteLine(" Loaded assembly : " + assembly.GetName()); | ||
279 | log.WriteLine(" Reference assembly: " + resolveName); | ||
280 | } | ||
281 | else | ||
282 | { | ||
283 | log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName); | ||
284 | } | ||
285 | |||
286 | return null; | ||
287 | }; | ||
288 | } | ||
289 | |||
290 | /// <summary> | ||
291 | /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails. | ||
292 | /// </summary> | ||
293 | /// <param name="assemblyPath">Path of the assembly file to laod.</param> | ||
294 | /// <returns>Loaded assembly, or null if the load failed.</returns> | ||
295 | private static Assembly TryLoadDependentAssembly(string assemblyPath) | ||
296 | { | ||
297 | Assembly assembly = null; | ||
298 | try | ||
299 | { | ||
300 | assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath); | ||
301 | } | ||
302 | catch (IOException ex) | ||
303 | { | ||
304 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
305 | } | ||
306 | catch (BadImageFormatException ex) | ||
307 | { | ||
308 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
309 | } | ||
310 | catch (SecurityException ex) | ||
311 | { | ||
312 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
313 | } | ||
314 | |||
315 | return assembly; | ||
316 | } | ||
317 | |||
318 | /// <summary> | ||
319 | /// Searches the types in the input assembly for a type that implements IEmbeddedUI. | ||
320 | /// </summary> | ||
321 | /// <param name="module"></param> | ||
322 | /// <returns></returns> | ||
323 | private static string FindEmbeddedUIClass(string module) | ||
324 | { | ||
325 | log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module)); | ||
326 | |||
327 | string uiClass = null; | ||
328 | |||
329 | var assembly = Assembly.ReflectionOnlyLoadFrom(module); | ||
330 | |||
331 | foreach (var type in assembly.GetExportedTypes()) | ||
332 | { | ||
333 | if (!type.IsAbstract) | ||
334 | { | ||
335 | foreach (var interfaceType in type.GetInterfaces()) | ||
336 | { | ||
337 | if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI") | ||
338 | { | ||
339 | if (uiClass == null) | ||
340 | { | ||
341 | uiClass = assembly.GetName().Name + "!" + type.FullName; | ||
342 | } | ||
343 | else | ||
344 | { | ||
345 | throw new ArgumentException("Multiple IEmbeddedUI implementations found."); | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | |||
352 | return uiClass; | ||
353 | } | ||
354 | |||
355 | /// <summary> | ||
356 | /// Reflects on an input CA module to locate custom action entry-points. | ||
357 | /// </summary> | ||
358 | /// <param name="module">Assembly module with CA entry-points.</param> | ||
359 | /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns> | ||
360 | private static IDictionary<string, string> FindEntryPoints(string module) | ||
361 | { | ||
362 | log.WriteLine("Searching for custom action entry points " + | ||
363 | "in {0}", Path.GetFileName(module)); | ||
364 | |||
365 | var entryPoints = new Dictionary<string, string>(); | ||
366 | |||
367 | var assembly = Assembly.ReflectionOnlyLoadFrom(module); | ||
368 | |||
369 | foreach (var type in assembly.GetExportedTypes()) | ||
370 | { | ||
371 | foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) | ||
372 | { | ||
373 | var entryPointName = MakeSfxCA.GetEntryPoint(method); | ||
374 | if (entryPointName != null) | ||
375 | { | ||
376 | var entryPointPath = string.Format( | ||
377 | "{0}!{1}.{2}", | ||
378 | Path.GetFileNameWithoutExtension(module), | ||
379 | type.FullName, | ||
380 | method.Name); | ||
381 | entryPoints.Add(entryPointName, entryPointPath); | ||
382 | |||
383 | log.WriteLine(" {0}={1}", entryPointName, entryPointPath); | ||
384 | } | ||
385 | } | ||
386 | } | ||
387 | |||
388 | return entryPoints; | ||
389 | } | ||
390 | |||
391 | /// <summary> | ||
392 | /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method. | ||
393 | /// </summary> | ||
394 | /// <param name="method">A public static method.</param> | ||
395 | /// <returns>Entrypoint name for the method as specified by the custom action attribute or just the method name, | ||
396 | /// or null if the method is not a custom action method.</returns> | ||
397 | private static string GetEntryPoint(MethodInfo method) | ||
398 | { | ||
399 | IList<CustomAttributeData> attributes; | ||
400 | try | ||
401 | { | ||
402 | attributes = CustomAttributeData.GetCustomAttributes(method); | ||
403 | } | ||
404 | catch (FileLoadException) | ||
405 | { | ||
406 | // Already logged load failures in the assembly-resolve-handler. | ||
407 | return null; | ||
408 | } | ||
409 | |||
410 | foreach (CustomAttributeData attribute in attributes) | ||
411 | { | ||
412 | if (attribute.ToString().StartsWith( | ||
413 | "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(", | ||
414 | StringComparison.Ordinal)) | ||
415 | { | ||
416 | string entryPointName = null; | ||
417 | foreach (var argument in attribute.ConstructorArguments) | ||
418 | { | ||
419 | // The entry point name is the first positional argument, if specified. | ||
420 | entryPointName = (string) argument.Value; | ||
421 | break; | ||
422 | } | ||
423 | |||
424 | if (string.IsNullOrEmpty(entryPointName)) | ||
425 | { | ||
426 | entryPointName = method.Name; | ||
427 | } | ||
428 | |||
429 | return entryPointName; | ||
430 | } | ||
431 | } | ||
432 | |||
433 | return null; | ||
434 | } | ||
435 | |||
436 | /// <summary> | ||
437 | /// Counts the number of template entrypoints in SfxCA.dll. | ||
438 | /// </summary> | ||
439 | /// <remarks> | ||
440 | /// Depending on the requirements, SfxCA.dll might be built with | ||
441 | /// more entrypoints than the default. | ||
442 | /// </remarks> | ||
443 | private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat) | ||
444 | { | ||
445 | for (var count = 0; ; count++) | ||
446 | { | ||
447 | var templateName = string.Format(entryPointFormat, count); | ||
448 | var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); | ||
449 | |||
450 | var nameOffset = FindBytes(fileBytes, templateAsciiBytes); | ||
451 | if (nameOffset < 0) | ||
452 | { | ||
453 | return count; | ||
454 | } | ||
455 | } | ||
456 | } | ||
457 | |||
458 | /// <summary> | ||
459 | /// Writes a modified version of SfxCA.dll to the output stream, | ||
460 | /// with the template entry-points mapped to the CA entry-points. | ||
461 | /// </summary> | ||
462 | /// <remarks> | ||
463 | /// To avoid having to recompile SfxCA.dll for every different set of CAs, | ||
464 | /// this method looks for a preset number of template entry-points in the | ||
465 | /// binary file and overwrites their entrypoint name and string data with | ||
466 | /// CA-specific values. | ||
467 | /// </remarks> | ||
468 | private static void WriteEntryModule( | ||
469 | string sfxDll, Stream outputStream, IDictionary<string, string> entryPoints, string uiClass) | ||
470 | { | ||
471 | log.WriteLine("Modifying SfxCA.dll stub"); | ||
472 | |||
473 | byte[] fileBytes; | ||
474 | using (var readStream = File.OpenRead(sfxDll)) | ||
475 | { | ||
476 | fileBytes = new byte[(int) readStream.Length]; | ||
477 | readStream.Read(fileBytes, 0, fileBytes.Length); | ||
478 | } | ||
479 | |||
480 | const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}"; | ||
481 | const int MAX_ENTRYPOINT_NAME = 72; | ||
482 | const int MAX_ENTRYPOINT_PATH = 160; | ||
483 | //var emptyBytes = new byte[0]; | ||
484 | |||
485 | var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT); | ||
486 | |||
487 | if (slotCount == 0) | ||
488 | { | ||
489 | throw new ArgumentException("Invalid SfxCA.dll file."); | ||
490 | } | ||
491 | |||
492 | if (entryPoints.Count > slotCount) | ||
493 | { | ||
494 | throw new ArgumentException(string.Format( | ||
495 | "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " + | ||
496 | "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.", | ||
497 | entryPoints.Count, slotCount)); | ||
498 | } | ||
499 | |||
500 | var slotSort = new string[slotCount]; | ||
501 | for (var i = 0; i < slotCount - entryPoints.Count; i++) | ||
502 | { | ||
503 | slotSort[i] = string.Empty; | ||
504 | } | ||
505 | |||
506 | entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count); | ||
507 | Array.Sort<string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal); | ||
508 | |||
509 | for (var i = 0; ; i++) | ||
510 | { | ||
511 | var templateName = string.Format(ENTRYPOINT_FORMAT, i); | ||
512 | var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); | ||
513 | var templateUniBytes = Encoding.Unicode.GetBytes(templateName); | ||
514 | |||
515 | var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes); | ||
516 | if (nameOffset < 0) | ||
517 | { | ||
518 | break; | ||
519 | } | ||
520 | |||
521 | var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes); | ||
522 | if (pathOffset < 0) | ||
523 | { | ||
524 | break; | ||
525 | } | ||
526 | |||
527 | var entryPointName = slotSort[i]; | ||
528 | var entryPointPath = entryPointName.Length > 0 ? | ||
529 | entryPoints[entryPointName] : string.Empty; | ||
530 | |||
531 | if (entryPointName.Length > MAX_ENTRYPOINT_NAME) | ||
532 | { | ||
533 | throw new ArgumentException(string.Format( | ||
534 | "Entry point name exceeds limit of {0} characters: {1}", | ||
535 | MAX_ENTRYPOINT_NAME, | ||
536 | entryPointName)); | ||
537 | } | ||
538 | |||
539 | if (entryPointPath.Length > MAX_ENTRYPOINT_PATH) | ||
540 | { | ||
541 | throw new ArgumentException(string.Format( | ||
542 | "Entry point path exceeds limit of {0} characters: {1}", | ||
543 | MAX_ENTRYPOINT_PATH, | ||
544 | entryPointPath)); | ||
545 | } | ||
546 | |||
547 | var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName); | ||
548 | var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath); | ||
549 | |||
550 | MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes); | ||
551 | MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes); | ||
552 | } | ||
553 | |||
554 | if (entryPoints.Count == 0 && uiClass != null) | ||
555 | { | ||
556 | // Remove the zzz prefix from exported EmbeddedUI entry-points. | ||
557 | foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" }) | ||
558 | { | ||
559 | var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export); | ||
560 | |||
561 | var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes); | ||
562 | if (exportOffset < 0) | ||
563 | { | ||
564 | throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export); | ||
565 | } | ||
566 | |||
567 | var replaceNameBytes = Encoding.ASCII.GetBytes(export); | ||
568 | MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes); | ||
569 | } | ||
570 | |||
571 | if (uiClass.Length > MAX_ENTRYPOINT_PATH) | ||
572 | { | ||
573 | throw new ArgumentException(string.Format( | ||
574 | "UI class full name exceeds limit of {0} characters: {1}", | ||
575 | MAX_ENTRYPOINT_PATH, | ||
576 | uiClass)); | ||
577 | } | ||
578 | |||
579 | var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName"); | ||
580 | var replaceBytes = Encoding.Unicode.GetBytes(uiClass); | ||
581 | |||
582 | // Fill in the embedded UI implementor class so the proxy knows which one to load. | ||
583 | var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes); | ||
584 | if (replaceOffset >= 0) | ||
585 | { | ||
586 | MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes); | ||
587 | } | ||
588 | } | ||
589 | |||
590 | outputStream.Write(fileBytes, 0, fileBytes.Length); | ||
591 | } | ||
592 | |||
593 | /// <summary> | ||
594 | /// Searches for a sub-array of bytes within a larger array of bytes. | ||
595 | /// </summary> | ||
596 | private static int FindBytes(byte[] source, byte[] find) | ||
597 | { | ||
598 | for (var i = 0; i < source.Length; i++) | ||
599 | { | ||
600 | int j; | ||
601 | for (j = 0; j < find.Length; j++) | ||
602 | { | ||
603 | if (source[i + j] != find[j]) | ||
604 | { | ||
605 | break; | ||
606 | } | ||
607 | } | ||
608 | |||
609 | if (j == find.Length) | ||
610 | { | ||
611 | return i; | ||
612 | } | ||
613 | } | ||
614 | |||
615 | return -1; | ||
616 | } | ||
617 | |||
618 | /// <summary> | ||
619 | /// Replaces a range of bytes with new bytes, padding any extra part | ||
620 | /// of the range with zeroes. | ||
621 | /// </summary> | ||
622 | private static void ReplaceBytes( | ||
623 | byte[] source, int offset, int length, byte[] replace) | ||
624 | { | ||
625 | for (var i = 0; i < length; i++) | ||
626 | { | ||
627 | if (i < replace.Length) | ||
628 | { | ||
629 | source[offset + i] = replace[i]; | ||
630 | } | ||
631 | else | ||
632 | { | ||
633 | source[offset + i] = 0; | ||
634 | } | ||
635 | } | ||
636 | } | ||
637 | |||
638 | /// <summary> | ||
639 | /// Print the name of one file as it is being packed into the cab. | ||
640 | /// </summary> | ||
641 | private static void PackProgress(object source, ArchiveProgressEventArgs e) | ||
642 | { | ||
643 | if (e.ProgressType == ArchiveProgressType.StartFile && log != null) | ||
644 | { | ||
645 | log.WriteLine(" {0}", e.CurrentFileName); | ||
646 | } | ||
647 | } | ||
648 | |||
649 | /// <summary> | ||
650 | /// Gets a mapping from filenames as they will be in the cab to filenames | ||
651 | /// as they are currently on disk. | ||
652 | /// </summary> | ||
653 | /// <remarks> | ||
654 | /// By default, all files will be placed in the root of the cab. But inputs may | ||
655 | /// optionally include an alternate inside-cab file path before an equals sign. | ||
656 | /// </remarks> | ||
657 | private static IDictionary<string, string> GetPackFileMap(IList<string> inputs) | ||
658 | { | ||
659 | var fileMap = new Dictionary<string, string>(); | ||
660 | foreach (var inputFile in inputs) | ||
661 | { | ||
662 | if (inputFile.IndexOf('=') > 0) | ||
663 | { | ||
664 | var parse = inputFile.Split('='); | ||
665 | if (!fileMap.ContainsKey(parse[0])) | ||
666 | { | ||
667 | fileMap.Add(parse[0], parse[1]); | ||
668 | } | ||
669 | } | ||
670 | else | ||
671 | { | ||
672 | var fileName = Path.GetFileName(inputFile); | ||
673 | if (!fileMap.ContainsKey(fileName)) | ||
674 | { | ||
675 | fileMap.Add(fileName, inputFile); | ||
676 | } | ||
677 | } | ||
678 | } | ||
679 | return fileMap; | ||
680 | } | ||
681 | |||
682 | /// <summary> | ||
683 | /// Packs the input files into a cab that is appended to the | ||
684 | /// output SfxCA.dll. | ||
685 | /// </summary> | ||
686 | private static void PackInputFiles(string outputFile, IDictionary<string, string> fileMap) | ||
687 | { | ||
688 | log.WriteLine("Packaging files"); | ||
689 | |||
690 | var cabInfo = new CabInfo(outputFile); | ||
691 | cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress); | ||
692 | } | ||
693 | |||
694 | /// <summary> | ||
695 | /// Copies the version resource information from the CA module to | ||
696 | /// the CA package. This gives the package the file version and | ||
697 | /// description of the CA module, instead of the version and | ||
698 | /// description of SfxCA.dll. | ||
699 | /// </summary> | ||
700 | private static void CopyVersionResource(string sourceFile, string destFile) | ||
701 | { | ||
702 | log.WriteLine("Copying file version info from {0} to {1}", | ||
703 | sourceFile, destFile); | ||
704 | |||
705 | var rc = new ResourceCollection(); | ||
706 | rc.Find(sourceFile, ResourceType.Version); | ||
707 | rc.Load(sourceFile); | ||
708 | rc.Save(destFile); | ||
709 | } | ||
710 | } | ||
711 | } | ||