// (c) Giora Tamir (giora@gtamir.com), 2005 using System; using System.IO; using System.Runtime.Serialization; namespace Nikse.SubtitleEdit.Core.ContainerFormats { #region RiffParserException [Serializable] public class RiffParserException : ApplicationException { public RiffParserException() { } public RiffParserException(string message) : base(message) { } public RiffParserException(string message, Exception inner) : base(message, inner) { } public RiffParserException(SerializationInfo info, StreamingContext context) : base(info, context) { } } #endregion RiffParserException public class RiffParser : IDisposable { #region CONSTANTS public const int DWordSize = 4; public const int TwoDWordSize = 8; public const string Riff4Cc = "RIFF"; public const string Rifx4Cc = "RIFX"; public const string List4Cc = "LIST"; // Known file types public static readonly int CkidAvi = ToFourCc("AVI "); public static readonly int CkidWav = ToFourCc("WAVE"); public static readonly int CkidRmid = ToFourCc("RMID"); #endregion CONSTANTS #region private members private string _fileName; private string _shortName; private long _fileSize; private int _dataSize; private FileStream _stream; private int _fileRiff; private int _fileType; #endregion private members #region Delegates /// /// Method to be called when a list element is found /// /// /// public delegate void ProcessListElement(RiffParser rp, int fourCcType, int length); /// /// Method to be called when a chunk element is found /// /// /// /// public delegate void ProcessChunkElement(RiffParser rp, int fourCcType, int unpaddedLength, int paddedLength); #endregion Delegates #region public Members /// /// RIFF data segment size /// public int DataSize { get { return _dataSize; } } /// /// Current file name /// public string FileName { get { return _fileName; } } /// /// Current short (name only) file name /// public string ShortName { get { return _shortName; } } /// /// Return the general file type (RIFF or RIFX); /// public int FileRiff { get { return _fileRiff; } } /// /// Return the specific file type (AVI/WAV...) /// public int FileType { get { return _fileType; } } #endregion public Members /// /// Determine if the file is a valid RIFF file /// /// File to examine /// True if file is a RIFF file public bool TryOpenFile(string filename) { // Sanity check if (null != _stream) { return false; } bool errorOccured = false; // Opening a new file try { FileInfo fi = new FileInfo(filename); _fileName = fi.FullName; _shortName = fi.Name; _fileSize = fi.Length; // Read the RIFF header _stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); int fourCc; int datasize; int fileType; ReadTwoInts(out fourCc, out datasize); ReadOneInt(out fileType); _fileRiff = fourCc; _fileType = fileType; // Check for a valid RIFF header string riff = FromFourCc(fourCc); if (riff != Riff4Cc && riff != Rifx4Cc) { // Not a valid RIFF file errorOccured = true; return false; } // Good header. Check size _dataSize = datasize; if (_fileSize < _dataSize + TwoDWordSize) { // Truncated file errorOccured = true; return false; } } catch { errorOccured = true; } finally { if (errorOccured && (null != _stream)) { _stream.Close(); _stream.Dispose(); _stream = null; } } return !errorOccured; } /// /// Read the next RIFF element invoking the correct delegate. /// Returns true if an element can be read /// /// Reference to number of bytes left in the current list /// Method to invoke if a chunk is found /// Method to invoke if a list is found /// public bool ReadElement(ref int bytesleft, ProcessChunkElement chunk, ProcessListElement list) { // Are we done? if (TwoDWordSize > bytesleft) { return false; } // We have enough bytes, read int fourCc; int size; ReadTwoInts(out fourCc, out size); // Reduce bytes left bytesleft -= TwoDWordSize; // Do we have enough bytes? if (bytesleft < size) { // Skip the bad data and throw an exception SkipData(bytesleft); bytesleft = 0; throw new RiffParserException("Element size mismatch for element " + FromFourCc(fourCc) + " need " + size + " but have only " + bytesleft); } // Examine the element, is it a list or a chunk string type = FromFourCc(fourCc); if (type == List4Cc) { // We have a list ReadOneInt(out fourCc); if (null == list) { SkipData(size - 4); } else { // Invoke the list method list(this, fourCc, size - 4); } // Adjust size bytesleft -= size; } else { // Calculated padded size - padded to WORD boundary int paddedSize = size; if (0 != (size & 1)) { ++paddedSize; } if (null == chunk) { SkipData(paddedSize); } else { chunk(this, fourCc, size, paddedSize); } // Adjust size bytesleft -= paddedSize; } return true; } #region Stream access /// /// Non-thread-safe method to read two ints from the stream /// /// Output FourCC int /// Output chunk/list size public void ReadTwoInts(out int fourCc, out int size) { try { var buffer = new byte[TwoDWordSize]; int readsize = _stream.Read(buffer, 0, TwoDWordSize); if (TwoDWordSize != readsize) { throw new RiffParserException("Unable to read. Corrupt RIFF file " + FileName); } fourCc = RiffDecodeHeader.GetInt(buffer, 0); size = RiffDecodeHeader.GetInt(buffer, 4); } catch (Exception ex) { throw new RiffParserException("Problem accessing RIFF file " + FileName, ex); } } /// /// Non-thread-safe read a single int from the stream /// /// Output int public void ReadOneInt(out int fourCc) { try { var buffer = new byte[DWordSize]; int readsize = _stream.Read(buffer, 0, DWordSize); if (DWordSize != readsize) { throw new RiffParserException("Unable to read. Corrupt RIFF file " + FileName); } fourCc = RiffDecodeHeader.GetInt(buffer, 0); } catch (Exception ex) { throw new RiffParserException("Problem accessing RIFF file " + FileName, ex); } } /// /// Skip the specified number of bytes /// /// Number of bytes to skip public void SkipData(int skipBytes) { try { _stream.Seek(skipBytes, SeekOrigin.Current); } catch (Exception ex) { throw new RiffParserException("Problem seeking in file " + FileName, ex); } } /// /// Read the specified length into the byte array at the specified /// offset in the array /// /// Array of bytes to read into /// Offset in the array to start from /// Number of bytes to read /// Number of bytes actually read public int ReadData(Byte[] data, int offset, int length) { try { return _stream.Read(data, offset, length); } catch (Exception ex) { throw new RiffParserException("Problem reading data in file " + FileName, ex); } } /// /// Close the RIFF file /// public void CloseFile() { if (null != _stream) { _stream.Close(); _stream = null; } } #endregion Stream access #region FourCC conversion methods public static string FromFourCc(int fourCc) { var chars = new char[4]; chars[0] = (char)(fourCc & 0xFF); chars[1] = (char)((fourCc >> 8) & 0xFF); chars[2] = (char)((fourCc >> 16) & 0xFF); chars[3] = (char)((fourCc >> 24) & 0xFF); return new string(chars); } public static int ToFourCc(string fourCc) { if (fourCc.Length != 4) { throw new Exception("FourCC strings must be 4 characters long " + fourCc); } int result = fourCc[3] << 24 | fourCc[2] << 16 | fourCc[1] << 8 | fourCc[0]; return result; } public static int ToFourCc(char[] fourCc) { if (fourCc.Length != 4) { throw new Exception("FourCC char arrays must be 4 characters long " + new string(fourCc)); } int result = fourCc[3] << 24 | fourCc[2] << 16 | fourCc[1] << 8 | fourCc[0]; return result; } public static int ToFourCc(char c0, char c1, char c2, char c3) { int result = c3 << 24 | c2 << 16 | c1 << 8 | c0; return result; } #endregion FourCC conversion methods public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (_stream != null) { _stream.Dispose(); _stream = null; } } } } }