context: #region Copyright & License Information
  /*
   * Copyright 2007-2019 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.IO;
  
  namespace OpenRA.Mods.Common.FileFormats
  {
  	public class VqaReader
  	{
  		public readonly ushort Frames;
  		public readonly byte Framerate;
  		public readonly ushort Width;
  		public readonly ushort Height;
  
  		Stream stream;
  		int currentFrame;
  		ushort numColors;
  		ushort blockWidth;
  		ushort blockHeight;
  		byte chunkBufferParts;
  		int2 blocks;
  		uint[] offsets;
  		uint[] palette;
  		uint videoFlags; // if 0x10 is set the video is a 16 bit hq video (ts and later)
  		int sampleRate;
  		int sampleBits;
  		int audioChannels;
  
  		// Stores a list of subpixels, referenced by the VPTZ chunk
  		byte[] cbf;
  		byte[] cbp;
  		byte[] cbfBuffer;
  
  		// Buffer for loading file subchunks, the maximum chunk size of a file is not defined
  		// and the header definition for the size of the biggest chunks (color data) isn't accurate.
  		// But 256k is large enough for all TS videos(< 200k).
  		byte[] fileBuffer = new byte[256000];
  		int maxCbfzSize = 256000;
  		int vtprSize = 0;
  		int currentChunkBuffer = 0;
  		int chunkBufferOffset = 0;
  
  		// Top half contains block info, bottom half contains references to cbf array
  		byte[] origData;
  
  		// Final frame output
  		uint[,] frameData;
  		byte[] audioData;		// audio for this frame: 22050Hz 16bit mono pcm, uncompressed.
  		bool hasAudio;
  
  		public byte[] AudioData { get { return audioData; } }
  		public int CurrentFrame { get { return currentFrame; } }
  		public int SampleRate { get { return sampleRate; } }
  		public int SampleBits { get { return sampleBits; } }
  		public int AudioChannels { get { return audioChannels; } }
  		public bool HasAudio { get { return hasAudio; } }
  
  		public VqaReader(Stream stream)
  		{
  			this.stream = stream;
  
  			// Decode FORM chunk
  			if (stream.ReadASCII(4) != "FORM")
  				throw new InvalidDataException("Invalid vqa (invalid FORM section)");
  			/*var length = */stream.ReadUInt32();
  
  			if (stream.ReadASCII(8) != "WVQAVQHD")
  				throw new InvalidDataException("Invalid vqa (not WVQAVQHD)");
  			/*var length2 = */stream.ReadUInt32();
  
  			/*var version = */stream.ReadUInt16();
  			videoFlags = stream.ReadUInt16();
  			Frames = stream.ReadUInt16();
  			Width = stream.ReadUInt16();
  			Height = stream.ReadUInt16();
  
  			blockWidth = stream.ReadUInt8();
  			blockHeight = stream.ReadUInt8();
  			Framerate = stream.ReadUInt8();
  			chunkBufferParts = stream.ReadUInt8();
  			blocks = new int2(Width / blockWidth, Height / blockHeight);
  
  			numColors = stream.ReadUInt16();
  			/*var maxBlocks = */stream.ReadUInt16();
  			/*var unknown1 = */stream.ReadUInt16();
  			/*var unknown2 = */stream.ReadUInt32();
  
  			// Audio
  			sampleRate = stream.ReadUInt16();
  			audioChannels = stream.ReadByte();
  			sampleBits = stream.ReadByte();
  
  			/*var unknown3 =*/stream.ReadUInt32();
  			/*var unknown4 =*/stream.ReadUInt16();
  			/*maxCbfzSize =*/stream.ReadUInt32(); // Unreliable
  
  			/*var unknown5 =*/stream.ReadUInt32();
  
  			var frameSize = Exts.NextPowerOf2(Math.Max(Width, Height));
  
  			if (IsHqVqa)
  			{
  				cbfBuffer = new byte[maxCbfzSize];
  				cbf = new byte[maxCbfzSize * 3];
  				origData = new byte[maxCbfzSize];
  			}
  			else
  			{
  				cbfBuffer = new byte[Width * Height];
  				cbf = new byte[Width * Height];
  				cbp = new byte[Width * Height];
  				origData = new byte[2 * blocks.X * blocks.Y];
  			}
  
  			palette = new uint[numColors];
  			frameData = new uint[frameSize, frameSize];
  			var type = stream.ReadASCII(4);
  			while (type != "FINF")
  			{
  				// Sub type is a file tag
  				if (type[3] == 'F')
  				{
  					var jmp = int2.Swap(stream.ReadUInt32());
  					stream.Seek(jmp, SeekOrigin.Current);
  					type = stream.ReadASCII(4);
  				}
  				else
  					throw new NotSupportedException("Vqa uses unknown Subtype: {0}".F(type));
  			}
  
  			/*var length = */stream.ReadUInt16();
  			/*var unknown4 = */stream.ReadUInt16();
  
  			// Frame offsets
  			offsets = new uint[Frames];
  			for (var i = 0; i < Frames; i++)
  			{
  				offsets[i] = stream.ReadUInt32();
  				if (offsets[i] > 0x40000000)
  					offsets[i] -= 0x40000000;
  				offsets[i] <<= 1;
  			}
  
  			CollectAudioData();
  
  			Reset();
  		}
  
  		public void Reset()
  		{
  			currentFrame = chunkBufferOffset = currentChunkBuffer = 0;
  			LoadFrame();
  		}
  
  		void CollectAudioData()
  		{
  			var audio1 = new MemoryStream(); // left channel / mono
  			var audio2 = new MemoryStream(); // right channel
  			var adpcmIndex = 0;
  			var compressed = false;
  			for (var i = 0; i < Frames; i++)
  			{
  				stream.Seek(offsets[i], SeekOrigin.Begin);
  				var end = (i < Frames - 1) ? offsets[i + 1] : stream.Length;
  
  				while (stream.Position < end)
  				{
  					var type = stream.ReadASCII(4);
  					if (type == "SN2J")
  					{
  						var jmp = int2.Swap(stream.ReadUInt32());
  						stream.Seek(jmp, SeekOrigin.Current);
  						type = stream.ReadASCII(4);
  					}
  
  					var length = int2.Swap(stream.ReadUInt32());
  
  					switch (type)
  					{
  						case "SND0":
  						case "SND2":
  							if (audioChannels == 0)
  								throw new NotSupportedException();
  							else if (audioChannels == 1)
  							{
  								var rawAudio = stream.ReadBytes((int)length);
  								audio1.WriteArray(rawAudio);
  							}
  							else
  							{
  								var rawAudio = stream.ReadBytes((int)length / 2);
  								audio1.WriteArray(rawAudio);
  								rawAudio = stream.ReadBytes((int)length / 2);
  								audio2.WriteArray(rawAudio);
  								if (length % 2 != 0)
  									stream.ReadBytes(2);
  							}
  
  							compressed = type == "SND2";
  							break;
  						default:
  							if (length + stream.Position > stream.Length)
  								throw new NotSupportedException("Vqa uses unknown Subtype: {0}".F(type));
  							stream.ReadBytes((int)length);
  							break;
  					}
  
  					// Chunks are aligned on even bytes; advance by a byte if the next one is null
  					if (stream.Peek() == 0) stream.ReadByte();
  				}
  			}
  
  			if (audioChannels == 1)
  				audioData = compressed ? ImaAdpcmReader.LoadImaAdpcmSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
  			else
  			{
  				byte[] leftData, rightData;
  				if (!compressed)
  				{
  
gt: 					leftData = audio1.ToArray();