context: #region Copyright & License Information
  /*
   * Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation, either version 3 of
   * the License, or (at your option) any later version. For more
   * information, see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  using System.Text.RegularExpressions;
  using ICSharpCode.SharpZipLib.Zip.Compression;
  
  namespace OpenRA.Mods.Common.FileFormats
  {
  	public sealed class InstallShieldCABCompression
  	{
  		const uint MaxFileGroupCount = 71;
  
  		enum CABFlags : ushort
  		{
  			FileSplit = 0x1,
  			FileObfuscated = 0x2,
  			FileCompressed = 0x4,
  			FileInvalid = 0x8,
  		}
  
  		enum LinkFlags : byte
  		{
  			Prev = 0x1,
  			Next = 0x2
  		}
  
  		struct FileGroup
  		{
  			public readonly string Name;
  			public readonly uint FirstFile;
  			public readonly uint LastFile;
  
  			public FileGroup(Stream stream, long offset)
  			{
  				var nameOffset = stream.ReadUInt32();
  				stream.Position += 18;
  				FirstFile = stream.ReadUInt32();
  				LastFile = stream.ReadUInt32();
  
  				var pos = stream.Position;
  				stream.Position = offset + nameOffset;
  				Name = stream.ReadASCIIZ();
  				stream.Position = pos;
  			}
  		}
  
  		struct CabDescriptor
  		{
  			public readonly long FileTableOffset;
  			public readonly uint FileTableSize;
  			public readonly uint FileTableSize2;
  			public readonly uint DirectoryCount;
  
  			public readonly uint FileCount;
  			public readonly long FileTableOffset2;
  
  			public CabDescriptor(Stream stream)
  			{
  				FileTableOffset = stream.ReadUInt32();
  				stream.Position += 4;
  				FileTableSize = stream.ReadUInt32();
  				FileTableSize2 = stream.ReadUInt32();
  				DirectoryCount = stream.ReadUInt32();
  				stream.Position += 8;
  				FileCount = stream.ReadUInt32();
  				FileTableOffset2 = stream.ReadUInt32();
  			}
  		}
  
  		struct DirectoryDescriptor
  		{
  			public readonly string Name;
  
  			public DirectoryDescriptor(Stream stream, long nameTableOffset)
  			{
  				var nameOffset = stream.ReadUInt32();
  				var pos = stream.Position;
  
  				stream.Position = nameTableOffset + nameOffset;
  
  				Name = stream.ReadASCIIZ();
  				stream.Position = pos;
  			}
  		}
  
  		struct FileDescriptor
  		{
  			public readonly uint Index;
  			public readonly CABFlags Flags;
  			public readonly uint ExpandedSize;
  			public readonly uint CompressedSize;
  			public readonly uint DataOffset;
  
  			public readonly byte[] MD5;
  			public readonly uint NameOffset;
  			public readonly ushort DirectoryIndex;
  			public readonly uint LinkToPrevious;
  
  			public readonly uint LinkToNext;
  			public readonly LinkFlags LinkFlags;
  			public readonly ushort Volume;
  			public readonly string Filename;
  
  			public FileDescriptor(Stream stream, uint index, long tableOffset)
  			{
  				Index = index;
  				Flags = (CABFlags)stream.ReadUInt16();
  				ExpandedSize = stream.ReadUInt32();
  				stream.Position += 4;
  				CompressedSize = stream.ReadUInt32();
  
  				stream.Position += 4;
  				DataOffset = stream.ReadUInt32();
  				stream.Position += 4;
  				MD5 = stream.ReadBytes(16);
  
  				stream.Position += 16;
  				NameOffset = stream.ReadUInt32();
  				DirectoryIndex = stream.ReadUInt16();
  				stream.Position += 12;
  				LinkToPrevious = stream.ReadUInt32();
  				LinkToNext = stream.ReadUInt32();
  
  				LinkFlags = (LinkFlags)stream.ReadUInt8();
  				Volume = stream.ReadUInt16();
  
  				var pos = stream.Position;
  				stream.Position = tableOffset + NameOffset;
  				Filename = stream.ReadASCIIZ();
  				stream.Position = pos;
  			}
  		}
  
  		struct CommonHeader
  		{
  			public const long Size = 16;
  			public readonly uint Version;
  			public readonly uint VolumeInfo;
  			public readonly long CabDescriptorOffset;
  			public readonly uint CabDescriptorSize;
  
  			public CommonHeader(Stream stream)
  			{
  				Version = stream.ReadUInt32();
  				VolumeInfo = stream.ReadUInt32();
  				CabDescriptorOffset = stream.ReadUInt32();
  				CabDescriptorSize = stream.ReadUInt32();
  			}
  		}
  
  		struct VolumeHeader
  		{
  			public readonly uint DataOffset;
  			public readonly uint DataOffsetHigh;
  			public readonly uint FirstFileIndex;
  			public readonly uint LastFileIndex;
  
  			public readonly uint FirstFileOffset;
  			public readonly uint FirstFileOffsetHigh;
  			public readonly uint FirstFileSizeExpanded;
  			public readonly uint FirstFileSizeExpandedHigh;
  
  			public readonly uint FirstFileSizeCompressed;
  			public readonly uint FirstFileSizeCompressedHigh;
  			public readonly uint LastFileOffset;
  			public readonly uint LastFileOffsetHigh;
  
  			public readonly uint LastFileSizeExpanded;
  			public readonly uint LastFileSizeExpandedHigh;
  			public readonly uint LastFileSizeCompressed;
  			public readonly uint LastFileSizeCompressedHigh;
  
  			public VolumeHeader(Stream stream)
  			{
  				DataOffset = stream.ReadUInt32();
  				DataOffsetHigh = stream.ReadUInt32();
  
  				FirstFileIndex = stream.ReadUInt32();
  				LastFileIndex = stream.ReadUInt32();
  				FirstFileOffset = stream.ReadUInt32();
  				FirstFileOffsetHigh = stream.ReadUInt32();
  
  				FirstFileSizeExpanded = stream.ReadUInt32();
  				FirstFileSizeExpandedHigh = stream.ReadUInt32();
  				FirstFileSizeCompressed = stream.ReadUInt32();
  				FirstFileSizeCompressedHigh = stream.ReadUInt32();
  
  				LastFileOffset = stream.ReadUInt32();
  				LastFileOffsetHigh = stream.ReadUInt32();
  				LastFileSizeExpanded = stream.ReadUInt32();
  				LastFileSizeExpandedHigh = stream.ReadUInt32();
  
  				LastFileSizeCompressed = stream.ReadUInt32();
  				LastFileSizeCompressedHigh = stream.ReadUInt32();
  			}
  		}
  
  		class CabExtracter
  		{
  			readonly FileDescriptor file;
  			readonly Dictionary<int, Stream> volumes;
  
  			uint remainingInArchive;
  			uint toExtract;
  
  			int currentVolumeID;
  			Stream currentVolume;
  
  			public CabExtracter(FileDescriptor file, Dictionary<int, Stream> volumes)
  			{
  				this.file = file;
  				this.volumes = volumes;
  
  				remainingInArchive = 0;
  				toExtract = file.Flags.HasFlag(CABFlags.FileCompressed) ? file.CompressedSize : file.ExpandedSize;
  
  				SetVolume(file.Volume);
  			}
  
  			public void CopyTo(Stream output, Action<int> onProgress)
  			{
  				if (file.Flags.HasFlag(CABFlags.FileCompressed))
  				{
  					var inf = new Inflater(true);
  					var buffer = new byte[165535];
  					do
  					{
  						var bytesToExtract = currentVolume.ReadUInt16();
  						remainingInArchive -= 2;
  						toExtract -= 2;
  						inf.SetInput(GetBytes(bytesToExtract));
  						toExtract -= bytesToExtract;
  						while (!inf.IsNeedingInput)
  						{
  							if (onProgress != null)
  								onProgress((int)(100 * output.Position / file.ExpandedSize));
  
  							var inflated = inf.Inflate(buffer);
  							output.Write(buffer, 0, inflated);
  						}
  
  						inf.Reset();
  					} while (toExtract > 0);
  				}
  				else
  				{
  					do
  					{
  						if (onProgress != null)
  							onProgress((int)(100 * output.Position / file.ExpandedSize));
  
  						toExtract -= remainingInArchive;
  						output.Write(GetBytes(remainingInArchive), 0, (int)remainingInArchive);
  					} while (toExtract > 0);
  				}
  			}
  
  			public byte[] GetBytes(uint count)
  			{
  				if (count < remainingInArchive)
  				{
  					remainingInArchive -= count;
  					return currentVolume.ReadBytes((int)count);
  				}
  				else
  				{
  					var outArray = new byte[count];
  					var read = currentVolume.Read(outArray, 0, (int)remainingInArchive);
  					if (toExtract > remainingInArchive)
  					{
  						SetVolume(currentVolumeID + 1);
  						remainingInArchive -= (uint)currentVolume.Read(outArray, read, (int)count - read);
  					}
  
  					return outArray;
  				}
  			}
  
  			void SetVolume(int newVolume)
  			{
  				currentVolumeID = newVolume;
  				if (!volumes.TryGetValue(currentVolumeID, out currentVolume))
  					throw new FileNotFoundException("Volume {0} is not available".F(currentVolumeID));
  
  				currentVolume.Position = 0;
  				if (currentVolume.ReadUInt32() != 0x28635349)
  					throw new InvalidDataException("Not an Installshield CAB package");
  
  				uint fileOffset;
  				if (file.Flags.HasFlag(CABFlags.FileSplit))
  				{
  					currentVolume.Position += CommonHeader.Size;
  					var head = new VolumeHeader(currentVolume);
  					if (file.Index == head.LastFileIndex)
  					{
  						if (file.Flags.HasFlag(CABFlags.FileCompressed))
  							remainingInArchive = head.LastFileSizeCompressed;
  						else
  							remainingInArchive = head.LastFileSizeExpanded;
  
  						fileOffset = head.LastFileOffset;
  					}
  					else if (file.Index == head.FirstFileIndex)
  					{
  						if (file.Flags.HasFlag(CABFlags.FileCompressed))
  							remainingInArchive = head.FirstFileSizeCompressed;
  						else
  							remainingInArchive = head.FirstFileSizeExpanded;
  
  						fileOffset = head.FirstFileOffset;
  					}
  					else
  						throw new Exception("Cannot Resolve Remaining Stream");
  				}
  				else
  				{
  					if (file.Flags.HasFlag(CABFlags.FileCompressed))
  						remainingInArchive = file.CompressedSize;
  					else
  						remainingInArchive = file.ExpandedSize;
  
  					fileOffset = file.DataOffset;
  				}
  
  				currentVolume.Position = fileOffset;
  			}
  		}
  
  		readonly Dictionary<string, FileDescriptor> index = new Dictionary<string, FileDescriptor>();
  		readonly Dictionary<int, Stream> volumes;
  
  		public InstallShieldCABCompression(Stream header, Dictionary<int, Stream> volumes)
  		{
  			this.volumes = volumes;
  
  			if (header.ReadUInt32() != 0x28635349)
  				throw new InvalidDataException("Not an Installshield CAB package");
  
  			header.Position += 8;
  			var cabDescriptorOffset = header.ReadUInt32();
  
gt: 			header.Position = cabDescriptorOffset + 12;