Merge pull request #1020 from Asnivor/CDFS
DiscSystem - CDFS classes now consume CD-I discs
This commit is contained in:
commit
3e3a64fffe
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
|
@ -114,5 +115,49 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// (asni 20171013) - Some methods I wrote that have been shoehorned in from another project to speed up development time
|
||||
// If these are offensive in any way, tell me I suck and that I need to do more work with existing methods
|
||||
#region Misc
|
||||
|
||||
/// <summary>
|
||||
/// Returns a byte array of any length
|
||||
/// Not really anything endian going on, but I struggled to find a better place for it
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public byte[] ReadBytes(byte[] buffer, int offset, int length)
|
||||
{
|
||||
return buffer.Skip(offset).Take(length).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an int32 value from any size byte array
|
||||
/// (careful, data may/will be truncated)
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public int ReadIntValue(byte[] buffer, int offset, int length)
|
||||
{
|
||||
var bytes = buffer.Skip(offset).Take(length).ToArray();
|
||||
|
||||
if (swap)
|
||||
Array.Reverse(bytes);
|
||||
|
||||
if (length == 1)
|
||||
return bytes.FirstOrDefault();
|
||||
|
||||
if (length == 2)
|
||||
return BitConverter.ToInt16(bytes, 0);
|
||||
|
||||
int result = BitConverter.ToInt32(bytes, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,21 +48,24 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
s.Seek(this.Offset * ISOFile.SECTOR_SIZE, SeekOrigin.Begin);
|
||||
|
||||
List<ISONodeRecord> records = new List<ISONodeRecord>();
|
||||
|
||||
// Read the directory entries
|
||||
while (s.Position < ((this.Offset * ISOFile.SECTOR_SIZE) + this.Length))
|
||||
|
||||
// Read the directory entries
|
||||
while (s.Position < ((this.Offset * ISOFile.SECTOR_SIZE) + this.Length))
|
||||
{
|
||||
ISONode node;
|
||||
ISONodeRecord record;
|
||||
|
||||
|
||||
// Read the record
|
||||
record = new ISONodeRecord();
|
||||
record.Parse(s);
|
||||
if (ISOFile.Format == ISOFile.ISOFormat.CDInteractive)
|
||||
record.ParseCDInteractive(s);
|
||||
if (ISOFile.Format == ISOFile.ISOFormat.ISO9660)
|
||||
record.ParseISO9660(s);
|
||||
|
||||
|
||||
//zero 24-jun-2013 - improved validity checks
|
||||
//theres nothing here!
|
||||
if (record.Length == 0)
|
||||
//zero 24-jun-2013 - improved validity checks
|
||||
//theres nothing here!
|
||||
if (record.Length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
@ -95,7 +98,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
// Add the node as a child
|
||||
this.Children.Add(record.Name, node);
|
||||
if (!this.Children.ContainsKey(record.Name))
|
||||
this.Children.Add(record.Name, node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,27 +2,51 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is meant to parse disk images as specified by ISO9660.
|
||||
/// Specifically, it should work for most disk images that are created
|
||||
/// by the stanard disk imaging software. This class is by no means
|
||||
/// robust to all variations of ISO9660.
|
||||
/// Also, this class does not currently support the UDF file system.
|
||||
///
|
||||
/// TODO: Add functions to enumerate a directory or visit a file...
|
||||
///
|
||||
/// The information for building class came from three primary sources:
|
||||
/// 1. The ISO9660 wikipedia article:
|
||||
/// http://en.wikipedia.org/wiki/ISO_9660
|
||||
/// 2. ISO9660 Simplified for DOS/Windows
|
||||
/// http://alumnus.caltech.edu/~pje/iso9660.html
|
||||
/// 3. The ISO 9660 File System
|
||||
/// http://users.telenet.be/it3.consultants.bvba/handouts/ISO9960.html
|
||||
/// </summary>
|
||||
public class ISOFile
|
||||
/// <summary>
|
||||
/// This class is meant to parse disk images as specified by:
|
||||
///
|
||||
/// ISO9660
|
||||
/// -------
|
||||
/// It should work for most disk images that are created
|
||||
/// by the stanard disk imaging software. This class is by no means
|
||||
/// robust to all variations of ISO9660.
|
||||
/// Also, this class does not currently support the UDF file system.
|
||||
///
|
||||
/// The information for building class came from three primary sources:
|
||||
/// 1. The ISO9660 wikipedia article:
|
||||
/// http://en.wikipedia.org/wiki/ISO_9660
|
||||
/// 2. ISO9660 Simplified for DOS/Windows
|
||||
/// http://alumnus.caltech.edu/~pje/iso9660.html
|
||||
/// 3. The ISO 9660 File System
|
||||
/// http://users.telenet.be/it3.consultants.bvba/handouts/ISO9960.html
|
||||
///
|
||||
///
|
||||
/// CD-I
|
||||
/// ----
|
||||
/// (asni - 20171013) - Class modified to be able to detect and consume Green
|
||||
/// Book disc images.
|
||||
///
|
||||
/// The implemtation of CD-I in this class adds some (but not all) additional
|
||||
/// properties to the class structures that CD-I brings. This means that
|
||||
/// the same ISO class structures can be returned for both standards.
|
||||
/// These small additions are readily found in ISOVolumeDescriptor.cs
|
||||
///
|
||||
/// ISOFile.cs also now contains a public 'ISOFormat' enum that is set
|
||||
/// during disc parsing.
|
||||
///
|
||||
/// The main reference source for this implementation:
|
||||
/// 1. The CD-I Full Functional Specification (aka Green Book)
|
||||
/// https://www.lscdweb.com/data/downloadables/2/8/cdi_may94_r2.pdf
|
||||
///
|
||||
///
|
||||
/// TODO: Add functions to enumerate a directory or visit a file...
|
||||
///
|
||||
/// </summary>
|
||||
public class ISOFile
|
||||
{
|
||||
#region Constants
|
||||
|
||||
|
@ -31,21 +55,37 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public const int SECTOR_SIZE = 2048;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Public Members
|
||||
#region Static Members
|
||||
|
||||
/// <summary>
|
||||
/// This is a list of all the volume descriptors in the disk image.
|
||||
/// NOTE: The first entry should be the primary volume.
|
||||
/// </summary>
|
||||
public List<ISOVolumeDescriptor> VolumeDescriptors;
|
||||
/// <summary>
|
||||
/// Making this a static for now. Every other way I tried was fairly ineligant (asni)
|
||||
/// </summary>
|
||||
public static ISOFormat Format;
|
||||
|
||||
public static List<CDIPathNode> CDIPathTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Members
|
||||
|
||||
/// <summary>
|
||||
/// This is a list of all the volume descriptors in the disk image.
|
||||
/// NOTE: The first entry should be the primary volume.
|
||||
/// </summary>
|
||||
public List<ISOVolumeDescriptor> VolumeDescriptors;
|
||||
|
||||
/// <summary>
|
||||
/// The Directory that is the root of this file system
|
||||
/// </summary>
|
||||
public ISODirectoryNode Root;
|
||||
|
||||
/// <summary>
|
||||
/// The type of CDFS format detected
|
||||
/// </summary>
|
||||
public ISOFormat CDFSType;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
@ -77,9 +117,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
// Seek through the first volume descriptor
|
||||
s.Seek(startPosition + (SECTOR_SIZE * startSector), SeekOrigin.Begin);
|
||||
|
||||
// Read one of more volume descriptors
|
||||
do
|
||||
|
||||
// Read one of more volume descriptors
|
||||
do
|
||||
{
|
||||
//zero 24-jun-2013 - improved validity checks
|
||||
|
||||
|
@ -87,6 +127,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
bool isValid = desc.Parse(s);
|
||||
if (!isValid) return false;
|
||||
|
||||
this.CDFSType = Format;
|
||||
|
||||
if (desc.IsTerminator())
|
||||
break;
|
||||
else if (desc.Type < 4)
|
||||
|
@ -98,16 +140,17 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
} while (true);
|
||||
|
||||
//zero 24-jun-2013 - well, my very first test iso had 2 volume descriptors.
|
||||
// Check to make sure we only read one volume descriptor
|
||||
// Finding more could be an error with the disk.
|
||||
//if (this.VolumeDescriptors.Count != 1) {
|
||||
// Console.WriteLine("Strange ISO format...");
|
||||
// return;
|
||||
//}
|
||||
//zero 24-jun-2013 - well, my very first test iso had 2 volume descriptors.
|
||||
// Check to make sure we only read one volume descriptor
|
||||
// Finding more could be an error with the disk.
|
||||
//if (this.VolumeDescriptors.Count != 1) {
|
||||
// Console.WriteLine("Strange ISO format...");
|
||||
// return;
|
||||
//}
|
||||
|
||||
//zero 24-jun-2013 - if theres no volume descriptors, we're gonna call this not a cdfs
|
||||
if (VolumeDescriptors.Count == 0) return false;
|
||||
|
||||
//zero 24-jun-2013 - if theres no volume descriptors, we're gonna call this not a cdfs
|
||||
if (VolumeDescriptors.Count == 0) return false;
|
||||
|
||||
// Visit all the directories and get the offset of each directory/file
|
||||
|
||||
|
@ -116,12 +159,68 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
// Create (and visit) the root node
|
||||
this.Root = new ISODirectoryNode(this.VolumeDescriptors[0].RootDirectoryRecord);
|
||||
visitedNodes.Add(this.Root.Offset, this.Root);
|
||||
this.Root.Parse(s, visitedNodes);
|
||||
|
||||
visitedNodes.Add(this.Root.Offset, this.Root);
|
||||
this.Root.Parse(s, visitedNodes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private List<KeyValuePair<string, ISOFileNode>> fileNodes;
|
||||
private List<ISODirectoryNode> dirsParsed;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flat list of all recursed files
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<KeyValuePair<string, ISOFileNode>> EnumerateAllFilesRecursively()
|
||||
{
|
||||
fileNodes = new List<KeyValuePair<string, ISOFileNode>>();
|
||||
dirsParsed = new List<ISODirectoryNode>();
|
||||
|
||||
if (Root.Children == null)
|
||||
return fileNodes;
|
||||
|
||||
// get all folders
|
||||
List<KeyValuePair<string, ISONode>> dirs = (from a in Root.Children
|
||||
where a.Value.GetType() == typeof(ISODirectoryNode)
|
||||
select a).ToList();
|
||||
// iterate through each folder
|
||||
foreach (var d in dirs)
|
||||
{
|
||||
// process all files in this directory (and recursively process files in sub folders
|
||||
ISODirectoryNode idn = d.Value as ISODirectoryNode;
|
||||
if (dirsParsed.Where(a => a == idn).Count() > 0)
|
||||
continue;
|
||||
|
||||
dirsParsed.Add(idn);
|
||||
ProcessDirectoryFiles(idn.Children);
|
||||
}
|
||||
|
||||
return fileNodes.Distinct().ToList();
|
||||
}
|
||||
|
||||
private void ProcessDirectoryFiles(Dictionary<string, ISONode> idn)
|
||||
{
|
||||
foreach (var n in idn)
|
||||
{
|
||||
if (n.Value.GetType() == typeof(ISODirectoryNode))
|
||||
{
|
||||
if (dirsParsed.Where(a => a == n.Value).Count() > 0)
|
||||
continue;
|
||||
|
||||
dirsParsed.Add(n.Value as ISODirectoryNode);
|
||||
ProcessDirectoryFiles((n.Value as ISODirectoryNode).Children);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyValuePair<string, ISOFileNode> f = new KeyValuePair<string, ISOFileNode>(n.Key, n.Value as ISOFileNode);
|
||||
fileNodes.Add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Printing
|
||||
|
@ -135,6 +234,17 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
this.Root.Print(0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Misc
|
||||
|
||||
public enum ISOFormat
|
||||
{
|
||||
Unknown,
|
||||
ISO9660,
|
||||
CDInteractive
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,10 +31,16 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public byte Length;
|
||||
|
||||
/// <summary>
|
||||
/// The file offset of the data for this file/directory (in sectors).
|
||||
/// </summary>
|
||||
public long OffsetOfData;
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public byte ExtendedAttribRecordLength;
|
||||
|
||||
/// <summary>
|
||||
/// The file offset of the data for this file/directory (in sectors).
|
||||
/// </summary>
|
||||
public long OffsetOfData;
|
||||
/// <summary>
|
||||
/// The length of the data for this file/directory (in bytes).
|
||||
/// </summary>
|
||||
|
@ -148,14 +154,19 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
// Put the array into a memory stream and pass to the main parsing function
|
||||
MemoryStream s = new MemoryStream(data);
|
||||
s.Seek(cursor, SeekOrigin.Begin);
|
||||
this.Parse(s);
|
||||
|
||||
if (ISOFile.Format == ISOFile.ISOFormat.ISO9660)
|
||||
this.ParseISO9660(s);
|
||||
|
||||
if (ISOFile.Format == ISOFile.ISOFormat.CDInteractive)
|
||||
this.ParseCDInteractive(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse the node record from the given stream.
|
||||
/// Parse the node record from the given ISO9660 stream.
|
||||
/// </summary>
|
||||
/// <param name="s">The stream to parse from.</param>
|
||||
public void Parse(Stream s)
|
||||
public void ParseISO9660(Stream s)
|
||||
{
|
||||
EndianBitConverter bc = EndianBitConverter.CreateForLittleEndian();
|
||||
long startPosition = s.Position;
|
||||
|
@ -212,6 +223,100 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
s.Seek(startPosition + this.Length, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,21 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
private const int LENGTH_TIME = 17;
|
||||
private const int LENGTH_RESERVED = 512;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
#region Private Properties
|
||||
|
||||
/// <summary>
|
||||
/// The type of this volume description, only 1 and 255 are supported
|
||||
/// </summary>
|
||||
public byte Type;
|
||||
private EndianBitConverter bc = EndianBitConverter.CreateForLittleEndian();
|
||||
private EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The type of this volume description, only 1 and 255 are supported
|
||||
/// </summary>
|
||||
public byte Type;
|
||||
|
||||
/// <summary>
|
||||
/// The system identifier
|
||||
|
@ -66,21 +73,21 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public int PathTableSize;
|
||||
/// <summary>
|
||||
/// Sector offset of the first path table
|
||||
/// (ISO9660 only) Sector offset of the first path table
|
||||
/// </summary>
|
||||
public int OffsetOfFirstLittleEndianPathTable;
|
||||
/// <summary>
|
||||
/// Sector offset of the second path table
|
||||
/// </summary>
|
||||
public int OffsetOfSecondLittleEndianPathTable;
|
||||
/// <summary>
|
||||
/// Sector offset of the first path table
|
||||
/// </summary>
|
||||
public int OffsetOfFirstBigEndianPathTable;
|
||||
/// <summary>
|
||||
/// Sector offset of the second path table
|
||||
/// </summary>
|
||||
public int OffsetOfSecondBigEndianPathTable;
|
||||
/// <summary>
|
||||
/// (ISO9660 only) Sector offset of the second path table
|
||||
/// </summary>
|
||||
public int OffsetOfSecondLittleEndianPathTable;
|
||||
/// <summary>
|
||||
/// (ISO9660 only) Sector offset of the first path table
|
||||
/// </summary>
|
||||
public int OffsetOfFirstBigEndianPathTable;
|
||||
/// <summary>
|
||||
/// (ISO9660 only) Sector offset of the second path table
|
||||
/// </summary>
|
||||
public int OffsetOfSecondBigEndianPathTable;
|
||||
|
||||
/// <summary>
|
||||
/// The root directory record
|
||||
|
@ -134,19 +141,43 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public byte[] EffectiveDateTime;
|
||||
|
||||
/// <summary>
|
||||
/// Extra reserved data
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
/// <summary>
|
||||
/// (ISO9660 only) Extra reserved data
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
// CD-Interactive only
|
||||
|
||||
/// <summary>
|
||||
/// The bits of this field are numbered from 0 to 7 starting with the least significant bit
|
||||
/// BitPosition 0: A value of 0 = the coded character set identifier field specifies only an escape sequence registered according to ISO 2375
|
||||
/// A value of 1 = the coded character set identifier field specifies only an escape sequence NOT registered according to ISO 2375
|
||||
/// BitPostion 1-7: All bits are 0 (reserved for future standardization)
|
||||
/// </summary>
|
||||
public byte VolumeFlags;
|
||||
/// <summary>
|
||||
/// This field specifies one escape sequence according to the International Register of Coded Character Sets to be used with escape
|
||||
/// sequence standards for recording.The ESC character, which is the first character of all sequences, shall be omitted when recording this field
|
||||
/// </summary>
|
||||
public byte[] CodedCharSetIdent;
|
||||
/// <summary>
|
||||
/// The block address of the first block of the system Path Table is kept in this field
|
||||
/// </summary>
|
||||
public int AddressOfPathTable;
|
||||
/// <summary>
|
||||
/// This number is used to indicate the revision number of the file structure standard to which the
|
||||
/// directory search files conform. It is set to one
|
||||
/// </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public ISOVolumeDescriptor()
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public ISOVolumeDescriptor()
|
||||
{
|
||||
// Set everything to the default value
|
||||
this.Type = 0;
|
||||
|
@ -183,6 +214,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
this.EffectiveDateTime = new byte[LENGTH_TIME];
|
||||
|
||||
this.Reserved = new byte[LENGTH_RESERVED];
|
||||
|
||||
// CD-I specific
|
||||
this.VolumeFlags = 0;
|
||||
this.CodedCharSetIdent = new byte[LENGTH_SHORT_IDENTIFIER];
|
||||
this.AddressOfPathTable = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -190,137 +226,391 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
#region Parsing
|
||||
|
||||
/// <summary>
|
||||
/// Parse the volume descriptor header.
|
||||
/// Start parsing 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];
|
||||
// Parse based on format
|
||||
byte[] header = bc.ReadBytes(buffer, 0, ISOFile.SECTOR_SIZE);
|
||||
if (GetISO9660(header))
|
||||
{
|
||||
ParseISO9660(s);
|
||||
return true;
|
||||
}
|
||||
if (GetCDI(header))
|
||||
{
|
||||
ParseCDInteractive(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
//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;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
public void ParseISO9660(Stream s)
|
||||
{
|
||||
long startPosition = s.Position;
|
||||
byte[] buffer = new byte[ISOFile.SECTOR_SIZE];
|
||||
s.Position = startPosition - ISOFile.SECTOR_SIZE;
|
||||
|
||||
#region Type Information
|
||||
// Read the entire structure
|
||||
s.Read(buffer, 0, ISOFile.SECTOR_SIZE);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is the terminator volume descriptor.
|
||||
/// </summary>
|
||||
/// <returns>True if the terminator.</returns>
|
||||
public bool IsTerminator()
|
||||
// Get the type
|
||||
this.Type = buffer[0];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
public void ParseCDInteractive(Stream s)
|
||||
{
|
||||
/* From the Green Book Spec
|
||||
* BP (byte position) obviously is n+1
|
||||
|
||||
BP Size in Bytes Description
|
||||
1 1 Disc Label Record Type
|
||||
2 5 Volume Structure Standard ID
|
||||
7 1 Volume Structure Version number
|
||||
8 1 Volume flags
|
||||
9 32 System identifier
|
||||
41 32 Volume identifier
|
||||
73 12 Reserved
|
||||
85 4 Volume space size
|
||||
89 32 Coded Character Set identifier
|
||||
121 2 Reserved
|
||||
123 2 Number of Volumes in Album
|
||||
125 2 Reserved
|
||||
127 2 Album Set Sequence number
|
||||
129 2 Reserved
|
||||
131 2 Logical Block size
|
||||
133 4 Reserved
|
||||
137 4 Path Table size
|
||||
141 8 Reserved
|
||||
149 4 Address of Path Table
|
||||
153 38 Reserved
|
||||
191 128 Album identifier
|
||||
319 128 Publisher identifier
|
||||
447 128 Data Preparer identifier
|
||||
575 128 Application identifier
|
||||
703 32 Copyright file name
|
||||
735 5 Reserved
|
||||
740 32 Abstract file name
|
||||
772 5 Reserved
|
||||
777 32 Bibliographic file name
|
||||
809 5 Reserved
|
||||
814 16 Creation date and time
|
||||
830 1 Reserved
|
||||
831 16 Modification date and time
|
||||
847 1 Reserved
|
||||
848 16 Expiration date and time
|
||||
864 1 Reserved
|
||||
865 16 Effective date and time
|
||||
881 1 Reserved
|
||||
882 1 File Structure Standard Version number
|
||||
883 1 Reserved
|
||||
884 512 Application use
|
||||
1396 653 Reserved */
|
||||
|
||||
long startPosition = s.Position;
|
||||
byte[] buffer = new byte[ISOFile.SECTOR_SIZE];
|
||||
s.Position = startPosition - ISOFile.SECTOR_SIZE;
|
||||
|
||||
// Read the entire structure
|
||||
s.Read(buffer, 0, ISOFile.SECTOR_SIZE);
|
||||
|
||||
// Get the type
|
||||
this.Type = buffer[0];
|
||||
|
||||
// Handle the primary volume information
|
||||
if (this.Type == 1)
|
||||
{
|
||||
this.VolumeFlags = buffer[7];
|
||||
this.SystemIdentifier = bc.ReadBytes(buffer, 8, LENGTH_SHORT_IDENTIFIER);
|
||||
this.VolumeIdentifier = bc.ReadBytes(buffer, 40, LENGTH_SHORT_IDENTIFIER);
|
||||
this.NumberOfSectors = bcBig.ReadIntValue(buffer, 84, 4);
|
||||
this.CodedCharSetIdent = bc.ReadBytes(buffer, 88, LENGTH_SHORT_IDENTIFIER);
|
||||
this.VolumeSetSize = bcBig.ReadIntValue(buffer, 122, 2);
|
||||
this.VolumeSequenceNumber = bcBig.ReadIntValue(buffer, 126, 2);
|
||||
this.SectorSize = bcBig.ReadIntValue(buffer, 130, 2);
|
||||
this.PathTableSize = bcBig.ReadIntValue(buffer, 136, 4);
|
||||
this.AddressOfPathTable = bcBig.ReadIntValue(buffer, 148, 4);
|
||||
|
||||
this.VolumeSetIdentifier = bc.ReadBytes(buffer, 190, LENGTH_LONG_IDENTIFIER);
|
||||
this.PublisherIdentifier = bc.ReadBytes(buffer, 318, LENGTH_LONG_IDENTIFIER);
|
||||
this.DataPreparerIdentifier = bc.ReadBytes(buffer, 446, LENGTH_LONG_IDENTIFIER);
|
||||
this.ApplicationIdentifier = bc.ReadBytes(buffer, 574, LENGTH_LONG_IDENTIFIER);
|
||||
|
||||
this.CopyrightFileIdentifier = bc.ReadBytes(buffer, 702, LENGTH_SHORT_IDENTIFIER);
|
||||
this.AbstractFileIdentifier = bc.ReadBytes(buffer, 739, LENGTH_SHORT_IDENTIFIER);
|
||||
this.BibliographicalFileIdentifier = bc.ReadBytes(buffer, 776, LENGTH_SHORT_IDENTIFIER);
|
||||
|
||||
this.VolumeCreationDateTime = bc.ReadBytes(buffer, 813, 16);
|
||||
this.LastModifiedDateTime = bc.ReadBytes(buffer, 830, 16);
|
||||
this.ExpirationDateTime = bc.ReadBytes(buffer, 847, 16);
|
||||
this.EffectiveDateTime = bc.ReadBytes(buffer, 864, 16);
|
||||
|
||||
// save current position
|
||||
long pos = s.Position;
|
||||
|
||||
// get path table records
|
||||
s.Position = ISOFile.SECTOR_SIZE * this.AddressOfPathTable;
|
||||
ISOFile.CDIPathTable = CDIPathNode.ParsePathTable(s, this.PathTableSize);
|
||||
|
||||
// read the root dir record
|
||||
s.Position = ISOFile.SECTOR_SIZE * ISOFile.CDIPathTable[0].DirectoryBlockAddress;
|
||||
s.Read(buffer, 0, ISOFile.SECTOR_SIZE);
|
||||
this.RootDirectoryRecord.Parse(buffer, 0);
|
||||
|
||||
// go back to where we were
|
||||
s.Position = pos;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect ISO9660
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <returns></returns>
|
||||
public bool GetISO9660(byte[] buffer)
|
||||
{
|
||||
//zero 24-jun-2013 - validate ISO9660
|
||||
// "CD001" + 0x01
|
||||
if (buffer[1] == 'C' && buffer[2] == 'D' && buffer[3] == '0' && buffer[4] == '0' && buffer[5] == '1' && buffer[6] == 0x01)
|
||||
{
|
||||
ISOFile.Format = ISOFile.ISOFormat.ISO9660;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect CD-I
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <returns></returns>
|
||||
public bool GetCDI(byte[] buffer)
|
||||
{
|
||||
// CD-Interactive
|
||||
if (Encoding.ASCII.GetString(bc.ReadBytes(buffer, 1, 5)).Contains("CD-I"))
|
||||
{
|
||||
ISOFile.Format = ISOFile.ISOFormat.CDInteractive;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type Information
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is the terminator volume descriptor.
|
||||
/// </summary>
|
||||
/// <returns>True if the terminator.</returns>
|
||||
public bool IsTerminator()
|
||||
{
|
||||
return (this.Type == 255);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Directory Path Table entry on a CD-I disc
|
||||
/// </summary>
|
||||
public class CDIPathNode
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The length of the directory name.
|
||||
/// </summary>
|
||||
public byte NameLength;
|
||||
|
||||
/// <summary>
|
||||
/// This is the length of the Extended Attribute record
|
||||
/// </summary>
|
||||
public byte ExtendedAttribRecordLength;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains the beginning logical block number (LBN) of the directory file on disc
|
||||
/// </summary>
|
||||
public int DirectoryBlockAddress;
|
||||
|
||||
/// <summary>
|
||||
/// This is the number (relative to the beginning of the Path Table) of this directory's parent
|
||||
/// </summary>
|
||||
public int ParentDirectoryNumber;
|
||||
|
||||
/// <summary>
|
||||
/// The directory name.
|
||||
/// This variable length field is used to store the actual text representing the name of the directory.
|
||||
/// If the length of the file name is odd, a null padding byte is added to make the size of the Path Table record even.
|
||||
/// The padding byte is not included in the name size field.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
/// <summary>
|
||||
/// Empty Constructor
|
||||
/// </summary>
|
||||
public CDIPathNode()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parsing
|
||||
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue