using System.Text; using System.IO; namespace BizHawk.Emulation.DiscSystem { /// /// Class to represent the file/directory information read from the disk. /// public class ISONodeRecord { #region Constants /// /// String representing the current directory entry /// public const string CURRENT_DIRECTORY = "."; /// /// String representing the parent directory entry /// public const string PARENT_DIRECTORY = ".."; #endregion #region Public Properties /// /// The length of the record in bytes. /// public byte Length; /// /// This is the number of blocks at the beginning of the file reserved for extended attribute information /// The format of the extended attribute record is not defined and is reserved for application use /// public byte ExtendedAttribRecordLength; /// /// The file offset of the data for this file/directory (in sectors). /// public long OffsetOfData; /// /// The length of the data for this file/directory (in bytes). /// public long LengthOfData; /// /// The file/directory creation year since 1900. /// public byte Year; /// /// The file/directory creation month. /// public byte Month; /// /// The file/directory creation day. /// public byte Day; /// /// The file/directory creation hour. /// public byte Hour; /// /// The file/directory creation minute. /// public byte Minute; /// /// The file/directory creation second. /// public byte Second; /// /// The file time offset from GMT. /// public byte TimeZoneOffset; /// /// Flags representing the attributes of this file/directory. /// public byte Flags; /// /// The length of the file/directory name. /// public byte NameLength; /// /// The file/directory name. /// public string Name; #endregion #region Construction /// /// Constructor /// public ISONodeRecord() { // Set initial values this.Length = 0; this.OffsetOfData = 0; this.LengthOfData = 0; this.Year = 0; this.Month = 0; this.Day = 0; this.Hour = 0; this.Minute = 0; this.Second = 0; this.TimeZoneOffset = 0; this.Flags = 0; this.NameLength = 0; this.Name = null; } #endregion #region File/Directory Methods /// /// Return true if the record represents a file. /// /// True if a file. public bool IsFile() { return ((this.Flags >> 1) & 0x01) == 0; } /// /// Return true if the record represents a directory. /// /// True if a directory. public bool IsDirectory() { return ((this.Flags >> 1) & 0x01) == 1; } #endregion #region Parsing /// /// Parse the record from an array and offset. /// /// The array to parse from. /// The offset to start parsing at. public void Parse(byte[] data, int cursor) { // Put the array into a memory stream and pass to the main parsing function MemoryStream s = new MemoryStream(data); s.Seek(cursor, SeekOrigin.Begin); if (ISOFile.Format == ISOFile.ISOFormat.ISO9660) this.ParseISO9660(s); if (ISOFile.Format == ISOFile.ISOFormat.CDInteractive) this.ParseCDInteractive(s); } /// /// Parse the node record from the given ISO9660 stream. /// /// The stream to parse from. 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); } /// /// Parse the node record from the given CD-I stream. /// /// The stream to parse from. 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); } #endregion } }