| 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.Core.Burn.Bundles |
| 4 | { |
| 5 | using System; |
| 6 | using System.Diagnostics; |
| 7 | using System.IO; |
| 8 | using WixToolset.Data; |
| 9 | using WixToolset.Extensibility.Services; |
| 10 | |
| 11 | /// <summary> |
| 12 | /// Burn PE writer for the WiX toolset. |
| 13 | /// </summary> |
| 14 | /// <remarks>This class encapsulates reading/writing to a stub EXE for |
| 15 | /// creating bundled/chained setup packages.</remarks> |
| 16 | /// <example> |
| 17 | /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid)) |
| 18 | /// { |
| 19 | /// writer.AppendContainer(file1, BurnWriter.Container.UX); |
| 20 | /// writer.AppendContainer(file2, BurnWriter.Container.Attached); |
| 21 | /// } |
| 22 | /// </example> |
| 23 | internal class BurnWriter : BurnCommon |
| 24 | { |
| 25 | private bool disposed; |
| 26 | private BinaryWriter binaryWriter; |
| 27 | private readonly IFileSystem fileSystem; |
| 28 | |
| 29 | private BurnWriter(IMessaging messaging, IFileSystem fileSystem, string fileExe) |
| 30 | : base(messaging, fileExe) |
| 31 | { |
| 32 | this.fileSystem = fileSystem; |
| 33 | } |
| 34 | |
| 35 | /// <summary> |
| 36 | /// Opens a Burn writer. |
| 37 | /// </summary> |
| 38 | /// <param name="messaging">Messaging system.</param> |
| 39 | /// <param name="fileSystem">File system abstraction.</param> |
| 40 | /// <param name="fileExe">Path to file.</param> |
| 41 | /// <returns>Burn writer.</returns> |
| 42 | public static BurnWriter Open(IMessaging messaging, IFileSystem fileSystem, string fileExe) |
| 43 | { |
| 44 | var writer = new BurnWriter(messaging, fileSystem, fileExe); |
| 45 | |
| 46 | using (var binaryReader = new BinaryReader(fileSystem.OpenFile(null, fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))) |
| 47 | { |
| 48 | writer.Initialize(binaryReader); |
| 49 | } |
| 50 | |
| 51 | if (!writer.Invalid) |
| 52 | { |
| 53 | writer.binaryWriter = new BinaryWriter(fileSystem.OpenFile(null, fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete)); |
| 54 | } |
| 55 | |
| 56 | return writer; |
| 57 | } |
| 58 | |
| 59 | /// <summary> |
| 60 | /// Update the ".wixburn" section data. |
| 61 | /// </summary> |
| 62 | /// <param name="stubSize">Size of the stub engine "burn.exe".</param> |
| 63 | /// <param name="bundleId">Unique identifier for this bundle.</param> |
| 64 | /// <returns></returns> |
| 65 | public bool InitializeBundleSectionData(long stubSize, string bundleId) |
| 66 | { |
| 67 | if (this.Invalid) |
| 68 | { |
| 69 | return false; |
| 70 | } |
| 71 | |
| 72 | var bundleGuid = Guid.Parse(bundleId); |
| 73 | |
| 74 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); |
| 75 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); |
| 76 | |
| 77 | this.Messaging.Write(VerboseMessages.BundleGuid(bundleId)); |
| 78 | this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); |
| 79 | this.binaryWriter.Write(bundleGuid.ToByteArray()); |
| 80 | |
| 81 | this.BundleId = bundleGuid; |
| 82 | this.StubSize = (uint)stubSize; |
| 83 | |
| 84 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize); |
| 85 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0); |
| 86 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0); |
| 87 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0); |
| 88 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now. |
| 89 | this.AttachedContainers.Clear(); |
| 90 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0); |
| 91 | for (var i = BURN_SECTION_OFFSET_UXSIZE; i < this.wixburnMaxContainers; i += sizeof(uint)) |
| 92 | { |
| 93 | this.WriteToBurnSectionOffset(i, 0); |
| 94 | } |
| 95 | this.binaryWriter.BaseStream.Flush(); |
| 96 | |
| 97 | this.EngineSize = this.StubSize; |
| 98 | |
| 99 | return true; |
| 100 | } |
| 101 | |
| 102 | /// <summary> |
| 103 | /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. |
| 104 | /// </summary> |
| 105 | /// <param name="sourceLineNumbers">Source line numbers for the container.</param> |
| 106 | /// <param name="fileContainer">File path to append to the current exe.</param> |
| 107 | /// <param name="container">Container section represented by the fileContainer.</param> |
| 108 | /// <returns>true if the container data is successfully appended; false otherwise</returns> |
| 109 | public bool AppendContainer(SourceLineNumber sourceLineNumbers, string fileContainer, BurnCommon.Container container) |
| 110 | { |
| 111 | using (var reader = this.fileSystem.OpenFile(sourceLineNumbers, fileContainer, FileMode.Open, FileAccess.Read, FileShare.Read)) |
| 112 | { |
| 113 | return this.AppendContainer(reader, reader.Length, container); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | /// <summary> |
| 118 | /// Appends the non-UX attached containers from the reader to this bundle. |
| 119 | /// </summary> |
| 120 | /// <param name="reader">The source bundle.</param> |
| 121 | /// <returns>true if the container data is successfully appended; false otherwise.</returns> |
| 122 | public bool ReattachContainers(BurnReader reader) |
| 123 | { |
| 124 | if (this.AttachedContainers.Count == 0 || reader.AttachedContainers.Count < 2) |
| 125 | { |
| 126 | return false; |
| 127 | } |
| 128 | |
| 129 | this.RememberThenResetSignature(); |
| 130 | |
| 131 | var uxContainerSlot = this.AttachedContainers[0]; |
| 132 | this.AttachedContainers.Clear(); |
| 133 | this.AttachedContainers.Add(uxContainerSlot); |
| 134 | |
| 135 | var nextAddress = this.EngineSize; |
| 136 | for (var i = 1; i < reader.AttachedContainers.Count; i++) |
| 137 | { |
| 138 | var slot = reader.AttachedContainers[i]; |
| 139 | |
| 140 | reader.Stream.Seek(nextAddress, SeekOrigin.Begin); |
| 141 | // TODO: verify that the size in the section data is 0 or the same size. |
| 142 | this.AppendContainer(reader.Stream, slot.Size, BurnCommon.Container.Attached); |
| 143 | |
| 144 | nextAddress += slot.Size; |
| 145 | } |
| 146 | |
| 147 | return true; |
| 148 | } |
| 149 | |
| 150 | /// <summary> |
| 151 | /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. |
| 152 | /// </summary> |
| 153 | /// <param name="containerStream">File stream to append to the current exe.</param> |
| 154 | /// <param name="containerSize">Size of container to append.</param> |
| 155 | /// <param name="container">Container section represented by the fileContainer.</param> |
| 156 | /// <returns>true if the container data is successfully appended; false otherwise</returns> |
| 157 | public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container) |
| 158 | { |
| 159 | var containerCount = (uint)this.AttachedContainers.Count; |
| 160 | var burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE + (containerCount * sizeof(uint)); |
| 161 | var containerSlot = new ContainerSlot((uint)containerSize); |
| 162 | |
| 163 | switch (container) |
| 164 | { |
| 165 | case Container.UX: |
| 166 | if (containerCount != 0) |
| 167 | { |
| 168 | Debug.Assert(false); |
| 169 | return false; |
| 170 | } |
| 171 | |
| 172 | this.EngineSize += containerSlot.Size; |
| 173 | break; |
| 174 | |
| 175 | case Container.Attached: |
| 176 | break; |
| 177 | |
| 178 | default: |
| 179 | Debug.Assert(false); |
| 180 | return false; |
| 181 | } |
| 182 | |
| 183 | this.AttachedContainers.Add(containerSlot); |
| 184 | ++containerCount; |
| 185 | return this.AppendContainer(containerStream, containerSlot.Size, burnSectionOffsetSize, containerCount); |
| 186 | } |
| 187 | |
| 188 | public void RememberThenResetSignature() |
| 189 | { |
| 190 | if (this.Invalid) |
| 191 | { |
| 192 | return; |
| 193 | } |
| 194 | |
| 195 | this.OriginalChecksum = this.Checksum; |
| 196 | this.OriginalSignatureOffset = this.SignatureOffset; |
| 197 | this.OriginalSignatureSize = this.SignatureSize; |
| 198 | |
| 199 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum); |
| 200 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset); |
| 201 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize); |
| 202 | |
| 203 | this.Checksum = 0; |
| 204 | this.SignatureOffset = 0; |
| 205 | this.SignatureSize = 0; |
| 206 | |
| 207 | this.WriteToOffset(this.checksumOffset, this.Checksum); |
| 208 | this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset); |
| 209 | this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize); |
| 210 | } |
| 211 | |
| 212 | /// <summary> |
| 213 | /// Dispose object. |
| 214 | /// </summary> |
| 215 | /// <param name="disposing">True when releasing managed objects.</param> |
| 216 | protected override void Dispose(bool disposing) |
| 217 | { |
| 218 | if (!this.disposed) |
| 219 | { |
| 220 | if (disposing && this.binaryWriter != null) |
| 221 | { |
| 222 | this.binaryWriter.Close(); |
| 223 | this.binaryWriter = null; |
| 224 | } |
| 225 | |
| 226 | this.disposed = true; |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | /// <summary> |
| 231 | /// Appends a container to the exe and updates the ".wixburn" section data to point to it. |
| 232 | /// </summary> |
| 233 | /// <param name="containerStream">File stream to append to the current exe.</param> |
| 234 | /// <param name="containerSize">Size of the container.</param> |
| 235 | /// <param name="burnSectionOffsetSize">Offset of size field for this container in ".wixburn" section data.</param> |
| 236 | /// <param name="burnSectionCount">Number of Burn sections.</param> |
| 237 | /// <returns>true if the container data is successfully appended; false otherwise</returns> |
| 238 | private bool AppendContainer(Stream containerStream, uint containerSize, uint burnSectionOffsetSize, uint burnSectionCount) |
| 239 | { |
| 240 | if (this.Invalid) |
| 241 | { |
| 242 | return false; |
| 243 | } |
| 244 | |
| 245 | if (burnSectionOffsetSize > (this.wixburnRawDataSize - sizeof(uint))) |
| 246 | { |
| 247 | this.Invalid = true; |
| 248 | this.Messaging.Write(BurnBackendErrors.TooManyAttachedContainers(this.wixburnMaxContainers)); |
| 249 | return false; |
| 250 | } |
| 251 | |
| 252 | // Update the ".wixburn" section data |
| 253 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount); |
| 254 | this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize); |
| 255 | |
| 256 | // Append the container to the end of the existing bits. |
| 257 | this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); |
| 258 | BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); |
| 259 | this.binaryWriter.BaseStream.Flush(); |
| 260 | |
| 261 | return true; |
| 262 | } |
| 263 | |
| 264 | /// <summary> |
| 265 | /// Writes the value to an offset in the Burn section data. |
| 266 | /// </summary> |
| 267 | /// <param name="offset">Offset in to the Burn section data.</param> |
| 268 | /// <param name="value">Value to write.</param> |
| 269 | private void WriteToBurnSectionOffset(uint offset, uint value) |
| 270 | { |
| 271 | this.WriteToOffset(this.wixburnDataOffset + offset, value); |
| 272 | } |
| 273 | |
| 274 | /// <summary> |
| 275 | /// Writes the value to an offset in the Burn stub. |
| 276 | /// </summary> |
| 277 | /// <param name="offset">Offset in to the Burn stub.</param> |
| 278 | /// <param name="value">Value to write.</param> |
| 279 | private void WriteToOffset(uint offset, uint value) |
| 280 | { |
| 281 | this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin); |
| 282 | this.binaryWriter.Write(value); |
| 283 | } |
| 284 | } |
| 285 | } |