/// <summary>
        /// (Crudely) determines if file is a CourseSmart file
        /// </summary>
        /// <param name="file">Path to file to check</param>
        /// <returns>True if successful; null otherwise</returns>
        public static bool IsCourseSmartFile(string file)
        {
            CourseSmartReader reader = new CourseSmartReader(file);

            byte firstByte = reader.ReadByte();

            if (firstByte == 0 || firstByte > 5)
            {
                // first byte in file must be 1-5
                return(false);
            }

            switch (firstByte)
            {
            case 1:
                // no restrictions, no key/value pairs, just file data
                break;

            case 2:
                // check for width/height/offset
                uint width  = reader.ReadUInt16();
                uint height = reader.ReadUInt16();
                uint offset = reader.ReadUInt16();

                if (width == 0 || height == 0 || offset == 0)
                {
                    // should have some value here
                    return(false);
                }
                break;

            case 3:
                uint numberOfKeyValuePairs = reader.ReadUInt16();
                if (numberOfKeyValuePairs == 0 || numberOfKeyValuePairs > 20)
                {
                    // sanity check on how many key value pairs we will read
                    // should be more than 0 as we need a book name & page #,
                    // but there shouldnt be more than 10ish (most I've seen
                    // is 8, but lets call it 20)
                    return(false);
                }
                break;

            case 4:
            case 5:
                // both of these are error cases, no sense loading them as
                // there shouldnt be anything here
                return(false);
            }

            return(true);
        }
        /// <summary>
        /// (Crudely) determines if file is a CourseSmart file
        /// </summary>
        /// <param name="file">Path to file to check</param>
        /// <returns>True if successful; null otherwise</returns>
        public static bool IsCourseSmartFile(string file)
        {
            CourseSmartReader reader = new CourseSmartReader(file);

            byte firstByte = reader.ReadByte();
            if (firstByte == 0 || firstByte > 5)
            {
                // first byte in file must be 1-5
                return false;
            }

            switch (firstByte)
            {
                case 1:
                    // no restrictions, no key/value pairs, just file data
                    break;
                case 2:
                    // check for width/height/offset 
                    uint width = reader.ReadUInt16();
                    uint height = reader.ReadUInt16();
                    uint offset = reader.ReadUInt16();

                    if (width == 0 || height == 0 || offset == 0)
                    {
                        // should have some value here
                        return false;
                    }
                    break;
                case 3:
                    uint numberOfKeyValuePairs = reader.ReadUInt16();
                    if (numberOfKeyValuePairs == 0 || numberOfKeyValuePairs > 20)
                    {
                        // sanity check on how many key value pairs we will read
                        // should be more than 0 as we need a book name & page #,
                        // but there shouldnt be more than 10ish (most I've seen 
                        // is 8, but lets call it 20)
                        return false;
                    }
                    break;
                case 4:
                case 5:
                    // both of these are error cases, no sense loading them as 
                    // there shouldnt be anything here
                    return false;
            }

            return true;
        }
        /// <summary>
        /// Decryption routine - extracted from offline viewer code
        /// </summary>
        /// <param name="data">Byte List containing encrypted movie data</param>
        private void DecryptMovieData(ref byte[] data)
        {
            // load encrypted data into cs reader
            using (MemoryStream dataStream = new MemoryStream(data))
            using (CourseSmartReader dataReader = new CourseSmartReader(dataStream))
            {
                byte XORKeyLength = dataReader.ReadByte();
                byte SPECIAL = dataReader.ReadByte(); // not 100% on what this value is
                byte swapTableLength = dataReader.ReadByte();

                // read XOR key & swap table
                byte[] XORKey = dataReader.ReadBytes(XORKeyLength);
                byte[] swapTable = dataReader.ReadBytes(swapTableLength);

                // calculate how much movie data remains after reading xor key and swap table
                int movieDataLength = data.Length - (int)dataReader.BaseStream.Position;

                // not sure yet
                int smallerBytesToSwap = movieDataLength / SPECIAL; // smaller
                int largerBytesToSwap = movieDataLength - (smallerBytesToSwap * (SPECIAL - 1)); // larger

                // stage 1 - read remaining data from file and decrypt using XOR key
                List<byte> decryptedDataByteList = new List<byte>();
                for (int i = 0; i < movieDataLength; i++)
                {
                    decryptedDataByteList.Add((byte)(dataReader.ReadByte() ^ XORKey[i % XORKey.Length]));
                }
                byte[] decryptedData = decryptedDataByteList.ToArray();

                // stage 2 - load decrypted data into cs reader & perform byte-swapping to finish decrypting data
                using (MemoryStream decryptedDataStream = new MemoryStream(decryptedData))
                using (CourseSmartReader decryptedDataReader = new CourseSmartReader(decryptedDataStream))
                {
                    for (int i = swapTableLength - 1; i >= 0; i--)
                    {
                        byte swapValue = swapTable[i];
                        // swap is only performed if value from SwapTable is different from current position in table
                        if (swapValue != i)
                        {
                            // not exactly sure what this is about
                            int bytesToSwap1 = (swapValue == (SPECIAL - 1)) ? largerBytesToSwap : smallerBytesToSwap;
                            int bytesToSwap2 = (i == (SPECIAL - 1)) ? largerBytesToSwap : smallerBytesToSwap;
                            int bytesToSwap = Math.Min(bytesToSwap1, bytesToSwap2);

                            int secondSwapPosition = swapValue * smallerBytesToSwap; 
                            int firstSwapPosition = i * smallerBytesToSwap; 

                            // read two sets of bytes to swap
                            decryptedDataReader.BaseStream.Position = firstSwapPosition;
                            byte[] firstSwapBuffer = decryptedDataReader.ReadBytes(bytesToSwap);

                            decryptedDataReader.BaseStream.Position = secondSwapPosition;
                            byte[] secondSwapBuffer = decryptedDataReader.ReadBytes(bytesToSwap);

                            // write bytes back to decrypted data array
                            Buffer.BlockCopy(secondSwapBuffer, 0, decryptedData, firstSwapPosition, bytesToSwap);
                            Buffer.BlockCopy(firstSwapBuffer, 0, decryptedData, secondSwapPosition, bytesToSwap);
                        }
                    }
                }

                // overwrite reference to data with our freshly decrypted movie data
                data = decryptedData;
            }
        }
        /// <summary>
        /// Decryption routine - extracted from offline viewer code
        /// </summary>
        /// <param name="data">Byte List containing encrypted movie data</param>
        private void DecryptMovieData(ref byte[] data)
        {
            // load encrypted data into cs reader
            using (MemoryStream dataStream = new MemoryStream(data))
                using (CourseSmartReader dataReader = new CourseSmartReader(dataStream))
                {
                    byte XORKeyLength    = dataReader.ReadByte();
                    byte SPECIAL         = dataReader.ReadByte(); // not 100% on what this value is
                    byte swapTableLength = dataReader.ReadByte();

                    // read XOR key & swap table
                    byte[] XORKey    = dataReader.ReadBytes(XORKeyLength);
                    byte[] swapTable = dataReader.ReadBytes(swapTableLength);

                    // calculate how much movie data remains after reading xor key and swap table
                    int movieDataLength = data.Length - (int)dataReader.BaseStream.Position;

                    // not sure yet
                    int smallerBytesToSwap = movieDataLength / SPECIAL;                              // smaller
                    int largerBytesToSwap  = movieDataLength - (smallerBytesToSwap * (SPECIAL - 1)); // larger

                    // stage 1 - read remaining data from file and decrypt using XOR key
                    List <byte> decryptedDataByteList = new List <byte>();
                    for (int i = 0; i < movieDataLength; i++)
                    {
                        decryptedDataByteList.Add((byte)(dataReader.ReadByte() ^ XORKey[i % XORKey.Length]));
                    }
                    byte[] decryptedData = decryptedDataByteList.ToArray();

                    // stage 2 - load decrypted data into cs reader & perform byte-swapping to finish decrypting data
                    using (MemoryStream decryptedDataStream = new MemoryStream(decryptedData))
                        using (CourseSmartReader decryptedDataReader = new CourseSmartReader(decryptedDataStream))
                        {
                            for (int i = swapTableLength - 1; i >= 0; i--)
                            {
                                byte swapValue = swapTable[i];
                                // swap is only performed if value from SwapTable is different from current position in table
                                if (swapValue != i)
                                {
                                    // not exactly sure what this is about
                                    int bytesToSwap1 = (swapValue == (SPECIAL - 1)) ? largerBytesToSwap : smallerBytesToSwap;
                                    int bytesToSwap2 = (i == (SPECIAL - 1)) ? largerBytesToSwap : smallerBytesToSwap;
                                    int bytesToSwap  = Math.Min(bytesToSwap1, bytesToSwap2);

                                    int secondSwapPosition = swapValue * smallerBytesToSwap;
                                    int firstSwapPosition  = i * smallerBytesToSwap;

                                    // read two sets of bytes to swap
                                    decryptedDataReader.BaseStream.Position = firstSwapPosition;
                                    byte[] firstSwapBuffer = decryptedDataReader.ReadBytes(bytesToSwap);

                                    decryptedDataReader.BaseStream.Position = secondSwapPosition;
                                    byte[] secondSwapBuffer = decryptedDataReader.ReadBytes(bytesToSwap);

                                    // write bytes back to decrypted data array
                                    Buffer.BlockCopy(secondSwapBuffer, 0, decryptedData, firstSwapPosition, bytesToSwap);
                                    Buffer.BlockCopy(firstSwapBuffer, 0, decryptedData, secondSwapPosition, bytesToSwap);
                                }
                            }
                        }

                    // overwrite reference to data with our freshly decrypted movie data
                    data = decryptedData;
                }
        }