Exemple #1
0
        /*
         *      BP  Size in bytes   Description
         *      1   1               Name size
         *      2   1               Extended Attribute record length
         *      3   4               Directory block address
         *      7   2               Parent Directory number
         *      9   n               Directory file name
         */

        public static List <CDIPathNode> ParsePathTable(Stream s, int PathTableSize)
        {
            EndianBitConverter bc    = EndianBitConverter.CreateForLittleEndian();
            EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();

            byte[] buffer = new byte[ISOFile.SECTOR_SIZE];

            // Read the entire structure
            s.Read(buffer, 0, ISOFile.SECTOR_SIZE);

            int startCursor = 0;

            List <CDIPathNode> pathNodes = new List <CDIPathNode>();

            int pad = 0;

            do
            {
                CDIPathNode node = new CDIPathNode();
                byte[]      data = bc.ReadBytes(buffer, startCursor, ISOFile.SECTOR_SIZE - startCursor);
                node.NameLength = data[0];

                node.ExtendedAttribRecordLength = data[1];
                node.DirectoryBlockAddress      = bcBig.ReadIntValue(data, 2, 4);
                node.ParentDirectoryNumber      = bcBig.ReadIntValue(data, 6, 2);
                node.Name = Encoding.ASCII.GetString(bc.ReadBytes(data, 8, data[0]));

                // if nameLength is odd a padding byte must be added

                if (node.NameLength % 2 != 0)
                {
                    pad = 1;
                }

                pathNodes.Add(node);

                startCursor += node.NameLength + 8;
            } while (startCursor < PathTableSize + pad);


            return(pathNodes);
        }
            /// <summary>
            /// Parse mds stream for the header
            /// </summary>
            public AHeader Parse(Stream stream)
            {
                EndianBitConverter bc    = EndianBitConverter.CreateForLittleEndian();
                EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();

                byte[] header = new byte[88];
                stream.Read(header, 0, 88);

                this.Signature       = Encoding.ASCII.GetString(header.Take(16).ToArray());
                this.Version         = header.Skip(16).Take(2).ToArray();
                this.Medium          = bc.ToInt16(header.Skip(18).Take(2).ToArray());
                this.SessionCount    = bc.ToInt16(header.Skip(20).Take(2).ToArray());
                this.BCALength       = bc.ToInt16(header.Skip(26).Take(2).ToArray());
                this.BCAOffset       = bc.ToInt32(header.Skip(36).Take(4).ToArray());
                this.StructureOffset = bc.ToInt32(header.Skip(64).Take(4).ToArray());
                this.SessionOffset   = bc.ToInt32(header.Skip(80).Take(4).ToArray());
                this.DPMOffset       = bc.ToInt32(header.Skip(84).Take(4).ToArray());

                return(this);
            }
        /// <exception cref="MDSParseException">header is malformed or identifies file as MDS 2.x, or any track has a DVD mode</exception>
        public AFile Parse(FileStream stream)
        {
            EndianBitConverter bc    = EndianBitConverter.CreateForLittleEndian();
            EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();
            bool isDvd = false;

            var aFile = new AFile {
                MDSPath = stream.Name
            };

            stream.Seek(0, SeekOrigin.Begin);

            // check whether the header in the mds file is long enough
            if (stream.Length < 88)
            {
                throw new MDSParseException("Malformed MDS format: The descriptor file does not appear to be long enough.");
            }

            // parse header
            aFile.Header = aFile.Header.Parse(stream);

            // check version to make sure this is only v1.x
            // currently NO support for version 2.x

            if (aFile.Header.Version[0] > 1)
            {
                throw new MDSParseException($"MDS Parse Error: Only MDS version 1.x is supported!\nDetected version: {aFile.Header.Version[0]}.{aFile.Header.Version[1]}");
            }

            // parse sessions
            Dictionary <int, ASession> aSessions = new Dictionary <int, ASession>();

            stream.Seek(aFile.Header.SessionOffset, SeekOrigin.Begin);
            for (int se = 0; se < aFile.Header.SessionCount; se++)
            {
                byte[] sessionHeader = new byte[24];
                stream.Read(sessionHeader, 0, 24);
                //sessionHeader.Reverse().ToArray();

                var session = new ASession
                {
                    SessionStart   = bc.ToInt32(sessionHeader.Take(4).ToArray()),
                    SessionEnd     = bc.ToInt32(sessionHeader.Skip(4).Take(4).ToArray()),
                    SessionNumber  = bc.ToInt16(sessionHeader.Skip(8).Take(2).ToArray()),
                    AllBlocks      = sessionHeader[10],
                    NonTrackBlocks = sessionHeader[11],
                    FirstTrack     = bc.ToInt16(sessionHeader.Skip(12).Take(2).ToArray()),
                    LastTrack      = bc.ToInt16(sessionHeader.Skip(14).Take(2).ToArray()),
                    TrackOffset    = bc.ToInt32(sessionHeader.Skip(20).Take(4).ToArray())
                };

                //mdsf.Sessions.Add(session);
                aSessions.Add(session.SessionNumber, session);
            }

            long footerOffset = 0;

            // parse track blocks
            Dictionary <int, ATrack> aTracks = new Dictionary <int, ATrack>();

            // iterate through each session block
            foreach (ASession session in aSessions.Values)
            {
                stream.Seek(session.TrackOffset, SeekOrigin.Begin);
                //Dictionary<int, ATrack> sessionToc = new Dictionary<int, ATrack>();

                // iterate through every block specified in each session
                for (int bl = 0; bl < session.AllBlocks; bl++)
                {
                    byte[] trackHeader;
                    ATrack track = new ATrack();

                    trackHeader = new byte[80];

                    stream.Read(trackHeader, 0, 80);

                    track.Mode         = trackHeader[0];
                    track.SubMode      = trackHeader[1];
                    track.ADR_Control  = trackHeader[2];
                    track.TrackNo      = trackHeader[3];
                    track.Point        = trackHeader[4];
                    track.AMin         = trackHeader[5];
                    track.ASec         = trackHeader[6];
                    track.AFrame       = trackHeader[7];
                    track.Zero         = trackHeader[8];
                    track.PMin         = trackHeader[9];
                    track.PSec         = trackHeader[10];
                    track.PFrame       = trackHeader[11];
                    track.ExtraOffset  = bc.ToInt32(trackHeader.Skip(12).Take(4).ToArray());
                    track.SectorSize   = bc.ToInt16(trackHeader.Skip(16).Take(2).ToArray());
                    track.PLBA         = bc.ToInt32(trackHeader.Skip(36).Take(4).ToArray());
                    track.StartOffset  = BitConverter.ToUInt64(trackHeader.Skip(40).Take(8).ToArray(), 0);
                    track.Files        = bc.ToInt32(trackHeader.Skip(48).Take(4).ToArray());
                    track.FooterOffset = bc.ToInt32(trackHeader.Skip(52).Take(4).ToArray());

                    if (track.Mode == 0x02)
                    {
                        isDvd = true;
                        throw new MDSParseException("DVD Detected. Not currently supported!");
                    }


                    // check for track extra block - this can probably be handled in a separate loop,
                    // but I'll just store the current stream position then seek forward to the extra block for this track
                    long currPos = stream.Position;

                    // Only CDs have extra blocks - for DVDs ExtraOffset = track length
                    if (track.ExtraOffset > 0 && !isDvd)
                    {
                        byte[] extHeader = new byte[8];
                        stream.Seek(track.ExtraOffset, SeekOrigin.Begin);
                        stream.Read(extHeader, 0, 8);
                        track.ExtraBlock.Pregap  = bc.ToInt32(extHeader.Take(4).ToArray());
                        track.ExtraBlock.Sectors = bc.ToInt32(extHeader.Skip(4).Take(4).ToArray());
                        stream.Seek(currPos, SeekOrigin.Begin);
                    }
                    else if (isDvd == true)
                    {
                        track.ExtraBlock.Sectors = track.ExtraOffset;
                    }

                    // read the footer/filename block for this track
                    currPos = stream.Position;
                    long numOfFilenames = track.Files;
                    for (long fi = 1; fi <= numOfFilenames; fi++)
                    {
                        // skip leadin/out info tracks
                        if (track.FooterOffset == 0)
                        {
                            continue;
                        }

                        byte[] foot = new byte[16];
                        stream.Seek(track.FooterOffset, SeekOrigin.Begin);
                        stream.Read(foot, 0, 16);

                        var f = new AFooter
                        {
                            FilenameOffset = bc.ToInt32(foot.Take(4).ToArray()),
                            WideChar       = bc.ToInt32(foot.Skip(4).Take(4).ToArray())
                        };
                        track.FooterBlocks.Add(f);
                        track.FooterBlocks = track.FooterBlocks.Distinct().ToList();

                        // parse the filename string
                        string fileName = "*.mdf";
                        if (f.FilenameOffset > 0)
                        {
                            // filename offset is present
                            stream.Seek(f.FilenameOffset, SeekOrigin.Begin);
                            byte[] fname;

                            if (numOfFilenames == 1)
                            {
                                if (aFile.Header.DPMOffset == 0)
                                {
                                    // filename is in the remaining space to EOF
                                    fname = new byte[stream.Length - stream.Position];
                                }
                                else
                                {
                                    // filename is in the remaining space to EOF + dpm offset
                                    fname = new byte[aFile.Header.DPMOffset - stream.Position];
                                }
                            }

                            else
                            {
                                // looks like each filename string is 6 bytes with a trailing \0
                                fname = new byte[6];
                            }


                            // read the filename
                            stream.Read(fname, 0, fname.Length);

                            // if widechar is 1 filename is stored using 16-bit, otherwise 8-bit is used
                            if (f.WideChar == 1)
                            {
                                fileName = Encoding.Unicode.GetString(fname).TrimEnd('\0');
                            }
                            else
                            {
                                fileName = Encoding.Default.GetString(fname).TrimEnd('\0');
                            }
                        }

                        else
                        {
                            // assume an MDF file with the same name as the MDS
                        }

                        string dir = Path.GetDirectoryName(aFile.MDSPath);

                        if (f.FilenameOffset == 0 ||
                            string.Compare(fileName, "*.mdf", StringComparison.InvariantCultureIgnoreCase) == 0)
                        {
                            fileName = $@"{dir}\{Path.GetFileNameWithoutExtension(aFile.MDSPath)}.mdf";
                        }
                        else
                        {
                            fileName = $@"{dir}\{fileName}";
                        }

                        track.ImageFileNamePaths.Add(fileName);
                        track.ImageFileNamePaths = track.ImageFileNamePaths.Distinct().ToList();
                    }

                    stream.Position = currPos;


                    aTracks.Add(track.Point, track);
                    aFile.Tracks.Add(track);

                    if (footerOffset == 0)
                    {
                        footerOffset = track.FooterOffset;
                    }
                }
            }


            // build custom session object
            aFile.ParsedSession = new List <Session>();
            foreach (var s in aSessions.Values)
            {
                Session session = new Session();

                if (!aTracks.TryGetValue(s.FirstTrack, out var startTrack))
                {
                    break;
                }

                if (!aTracks.TryGetValue(s.LastTrack, out var endTrack))
                {
                    break;
                }

                session.StartSector     = startTrack.PLBA;
                session.StartTrack      = s.FirstTrack;
                session.SessionSequence = s.SessionNumber;
                session.EndSector       = endTrack.PLBA + endTrack.ExtraBlock.Sectors - 1;
                session.EndTrack        = s.LastTrack;

                aFile.ParsedSession.Add(session);
            }

            // now build the TOC object
            foreach (var se in aFile.ParsedSession)
            {
                foreach (var t in aTracks.Values
                         .Where(a => se.StartTrack <= a.TrackNo && a.TrackNo <= se.EndTrack)
                         .OrderBy(a => a.TrackNo))
                {
                    aFile.TOCEntries.Add(new ATOCEntry(t.Point)
                    {
                        ADR_Control        = t.ADR_Control,
                        AFrame             = t.AFrame,
                        AMin               = t.AMin,
                        ASec               = t.ASec,
                        BlobIndex          = t.BlobIndex,
                        EntryNum           = t.TrackNo,
                        ExtraBlock         = t.ExtraBlock,
                        ImageFileNamePaths = t.ImageFileNamePaths,
                        PFrame             = t.PFrame,
                        PLBA               = Convert.ToInt32(t.PLBA),
                        PMin               = t.PMin,
                        Point              = t.Point,
                        PSec               = t.PSec,
                        SectorSize         = t.SectorSize,
                        Session            = se.SessionSequence,
                        TrackOffset        = Convert.ToInt64(t.StartOffset),
                        Zero               = t.Zero
                    });
                }
            }

            return(aFile);
        }
        /// <summary>
        /// Parse the node record from the given CD-I stream.
        /// </summary>
        /// <param name="s">The stream to parse from.</param>
        public void ParseCDInteractive(Stream s)
        {
            /*
             * BP      Size in bytes   Description
             * 1       1               Record length
             * 2       1               Extended Attribute record length
             * 3       4               Reserved
             * 7       4               File beginning LBN
             * 11      4               Reserved
             * 15      4               File size
             * 19      6               Creation date
             * 25      1               Reserved
             * 26      1               File flags
             * 27      2               Interleave
             * 29      2               Reserved
             * 31      2               Album Set Sequence number
             * 33      1               File name size
             * 34      (n)             File name
             * 34+n    4               Owner ID
             * 38+n    2               Attributes
             * 40+n    2               Reserved
             * 42+n    1               File number
             * 43+n    1               Reserved
             *      43+n            Total
             */

            EndianBitConverter bc    = EndianBitConverter.CreateForLittleEndian();
            EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();
            long startPosition       = s.Position;

            byte[] buffer = new byte[ISOFile.SECTOR_SIZE];

            // Read the entire structure
            s.Read(buffer, 0, ISOFile.SECTOR_SIZE);
            s.Position -= ISOFile.SECTOR_SIZE;

            // Get the record length
            this.Length = buffer[0];

            // extended attribute record length
            this.ExtendedAttribRecordLength = buffer[1];

            // Read Data Offset
            this.OffsetOfData = bcBig.ReadIntValue(buffer, 6, 4);

            // Read Data Length
            this.LengthOfData = bcBig.ReadIntValue(buffer, 14, 4);

            // Read the time
            var ti = bc.ReadBytes(buffer, 18, 6);

            this.Year   = ti[0];
            this.Month  = ti[1];
            this.Day    = ti[2];
            this.Hour   = ti[3];
            this.Minute = ti[4];
            this.Second = ti[5];

            // read interleave - still to do

            // read album (volume) set sequence number (we are ignoring this)

            // Read the name length
            this.NameLength = buffer[32];

            // Read the file/directory name
            var name = bc.ReadBytes(buffer, 33, this.NameLength);

            if (this.NameLength == 1 && (name[0] == 0 || name[0] == 1))
            {
                if (name[0] == 0)
                {
                    this.Name = ISONodeRecord.CURRENT_DIRECTORY;
                }
                else
                {
                    this.Name = ISONodeRecord.PARENT_DIRECTORY;
                }
            }
            else
            {
                this.Name = ASCIIEncoding.ASCII.GetString(name, 0, this.NameLength);
            }

            // skip ownerID for now

            // read the flags - only really interested in the directory attribute (bit 15)
            // (confusingly these are called 'attributes' in CD-I. the CD-I 'File Flags' entry is something else entirely)
            this.Flags = buffer[37 + this.NameLength];

            // skip filenumber
            //this.FileNumber = buffer[41 + this.NameLength];

            // Seek to end
            s.Seek(startPosition + this.Length, SeekOrigin.Begin);
        }
        /// <summary>
        /// Parse the node record from the given ISO9660 stream.
        /// </summary>
        /// <param name="s">The stream to parse from.</param>
        public void ParseISO9660(Stream s)
        {
            EndianBitConverter bc = EndianBitConverter.CreateForLittleEndian();
            long startPosition    = s.Position;

            byte[] buffer = new byte[ISOFile.SECTOR_SIZE];

            // Get the length
            s.Read(buffer, 0, 1);
            this.Length = buffer[0];

            //the number of sectors in the attribute record
            s.Read(buffer, 0, 1);

            // Read Data Offset
            s.Read(buffer, 0, 8);
            this.OffsetOfData = (long)bc.ToInt32(buffer);

            // Read Data Length
            s.Read(buffer, 0, 8);
            this.LengthOfData = (long)bc.ToInt32(buffer);

            // Read the time and flags
            s.Read(buffer, 0, 8);
            this.Year           = buffer[0];
            this.Month          = buffer[1];
            this.Day            = buffer[2];
            this.Hour           = buffer[3];
            this.Minute         = buffer[4];
            this.Second         = buffer[5];
            this.TimeZoneOffset = buffer[6];

            this.Flags = buffer[7];

            s.Read(buffer, 0, 6);

            // Read the name length
            s.Read(buffer, 0, 1);
            this.NameLength = buffer[0];

            // Read the directory name
            s.Read(buffer, 0, this.NameLength);
            if (this.NameLength == 1 && (buffer[0] == 0 || buffer[0] == 1))
            {
                if (buffer[0] == 0)
                {
                    this.Name = ISONodeRecord.CURRENT_DIRECTORY;
                }
                else
                {
                    this.Name = ISONodeRecord.PARENT_DIRECTORY;
                }
            }
            else
            {
                this.Name = ASCIIEncoding.ASCII.GetString(buffer, 0, this.NameLength);
            }

            // Seek to end
            s.Seek(startPosition + this.Length, SeekOrigin.Begin);
        }
        /// <summary>
        /// Parse the volume descriptor header.
        /// </summary>
        /// <param name="s">The stream to parse from.</param>
        public bool Parse(Stream s)
        {
            EndianBitConverter bc    = EndianBitConverter.CreateForLittleEndian();
            EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();
            long startPosition       = s.Position;

            byte[] buffer = new byte[ISOFile.SECTOR_SIZE];

            // Read the entire structure
            s.Read(buffer, 0, ISOFile.SECTOR_SIZE);

            // Get the type
            this.Type = buffer[0];

            //zero 24-jun-2013 - validate
            //  "CD001" + 0x01
            if (buffer[1] == 'C' && buffer[2] == 'D' && buffer[3] == '0' && buffer[4] == '0' && buffer[5] == '1' && buffer[6] == 0x01)
            {
                //it seems to be a valid volume descriptor
            }
            else
            {
                return(false);
            }

            // Handle the primary volume information
            if (this.Type == 1)
            {
                int cursor = 8;
                // Get the system identifier
                Array.Copy(buffer, cursor,
                           this.SystemIdentifier, 0, LENGTH_SHORT_IDENTIFIER);
                cursor += LENGTH_SHORT_IDENTIFIER;

                // Get the volume identifier
                Array.Copy(buffer, cursor,
                           this.VolumeIdentifier, 0, LENGTH_SHORT_IDENTIFIER);
                cursor += LENGTH_SHORT_IDENTIFIER;

                cursor += 8;

                // Get the total number of sectors
                this.NumberOfSectors = bc.ToInt32(buffer, cursor);
                cursor += 8;

                cursor += 32;

                this.VolumeSetSize        = bc.ToInt16(buffer, cursor);
                cursor                   += 4;
                this.VolumeSequenceNumber = bc.ToInt16(buffer, cursor);
                cursor                   += 4;
                this.SectorSize           = bc.ToInt16(buffer, cursor);
                cursor                   += 4;

                this.PathTableSize = bc.ToInt32(buffer, cursor);
                cursor            += 8;
                this.OffsetOfFirstLittleEndianPathTable = bc.ToInt32(buffer, cursor);
                cursor += 4;
                this.OffsetOfSecondLittleEndianPathTable = bc.ToInt32(buffer, cursor);
                cursor += 4;
                this.OffsetOfFirstLittleEndianPathTable = bcBig.ToInt32(buffer, cursor);
                cursor += 4;
                this.OffsetOfSecondLittleEndianPathTable = bcBig.ToInt32(buffer, cursor);
                cursor += 4;

                this.RootDirectoryRecord.Parse(buffer, cursor);
                cursor += LENGTH_ROOT_DIRECTORY_RECORD;

                Array.Copy(buffer, cursor,
                           this.VolumeSetIdentifier, 0, LENGTH_LONG_IDENTIFIER);
                cursor += LENGTH_LONG_IDENTIFIER;
                Array.Copy(buffer, cursor,
                           this.PublisherIdentifier, 0, LENGTH_LONG_IDENTIFIER);
                cursor += LENGTH_LONG_IDENTIFIER;
                Array.Copy(buffer, cursor,
                           this.DataPreparerIdentifier, 0, LENGTH_LONG_IDENTIFIER);
                cursor += LENGTH_LONG_IDENTIFIER;
                Array.Copy(buffer, cursor,
                           this.ApplicationIdentifier, 0, LENGTH_LONG_IDENTIFIER);
                cursor += LENGTH_LONG_IDENTIFIER;

                Array.Copy(buffer, cursor,
                           this.CopyrightFileIdentifier, 0, LENGTH_IDENTIFIER);
                cursor += LENGTH_IDENTIFIER;
                Array.Copy(buffer, cursor,
                           this.AbstractFileIdentifier, 0, LENGTH_IDENTIFIER);
                cursor += LENGTH_IDENTIFIER;
                Array.Copy(buffer, cursor,
                           this.BibliographicalFileIdentifier, 0, LENGTH_IDENTIFIER);
                cursor += LENGTH_IDENTIFIER;

                Array.Copy(buffer, cursor,
                           this.VolumeCreationDateTime, 0, LENGTH_TIME);
                cursor += LENGTH_TIME;
                Array.Copy(buffer, cursor,
                           this.LastModifiedDateTime, 0, LENGTH_TIME);
                cursor += LENGTH_TIME;
                Array.Copy(buffer, cursor,
                           this.ExpirationDateTime, 0, LENGTH_TIME);
                cursor += LENGTH_TIME;
                Array.Copy(buffer, cursor,
                           this.EffectiveDateTime, 0, LENGTH_TIME);
                cursor += LENGTH_TIME;

                cursor += 1;

                cursor += 1;

                Array.Copy(buffer, cursor,
                           this.Reserved, 0, LENGTH_RESERVED);
                cursor += LENGTH_RESERVED;
            }

            return(true);
        }