/* * 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 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); }