From 432fa4258b1f3f0197e86709f864c53152c277e5 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 13 Oct 2017 21:50:10 +0100 Subject: [PATCH] Added support for the CD-i filesystem described in Green Book. --- .../DiscImageChef.Filesystems.csproj | 2 + .../ISO9660/Consts/CDi.cs | 66 ++++++ DiscImageChef.Filesystems/ISO9660/Info.cs | 28 ++- .../ISO9660/Structs/CDi.cs | 191 ++++++++++++++++++ .../ISO9660/Structs/ISO.cs | 11 + DiscImageChef.Helpers/DateHandlers.cs | 1 + README.md | 1 + 7 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 DiscImageChef.Filesystems/ISO9660/Consts/CDi.cs create mode 100644 DiscImageChef.Filesystems/ISO9660/Structs/CDi.cs diff --git a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj index a81b38adf..7a3ee9c93 100644 --- a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj +++ b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj @@ -164,6 +164,8 @@ + + diff --git a/DiscImageChef.Filesystems/ISO9660/Consts/CDi.cs b/DiscImageChef.Filesystems/ISO9660/Consts/CDi.cs new file mode 100644 index 000000000..69ebbd8f7 --- /dev/null +++ b/DiscImageChef.Filesystems/ISO9660/Consts/CDi.cs @@ -0,0 +1,66 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CDi.cs +// Author(s) : Natalia Portillo +// +// Component : Component +// +// --[ Description ] ---------------------------------------------------------- +// +// Description +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2017 Natalia Portillo +// ****************************************************************************/ +using System; + +namespace DiscImageChef.Filesystems.ISO9660 +{ + public partial class ISO9660 : Filesystem + { + readonly string CdiMagic = "CD-I "; + + [Flags] + enum CdiVolumeFlags + { + // Escapes are not ISO 2375 but ISO 2022 + NotISO2375 = 1, + } + + [Flags] + enum CdiFileFlags : byte + { + Hidden = 0x01, + } + + [Flags] + enum CdiAttributes : ushort + { + OwnerRead = 1 << 0, + OwnerExecute = 1 << 2, + GroupRead = 1 << 4, + GroupExecute = 1 << 6, + OtherRead = 1 << 8, + OtherExecute = 1 << 10, + DigitalAudio = 1 << 14, + Directory = 1 << 15, + } + } +} diff --git a/DiscImageChef.Filesystems/ISO9660/Info.cs b/DiscImageChef.Filesystems/ISO9660/Info.cs index 8b520fa7e..b7fc17e25 100644 --- a/DiscImageChef.Filesystems/ISO9660/Info.cs +++ b/DiscImageChef.Filesystems/ISO9660/Info.cs @@ -73,7 +73,7 @@ namespace DiscImageChef.Filesystems.ISO9660 DicConsole.DebugWriteLine("ISO9660 plugin", "VDMagic = {0}", CurrentEncoding.GetString(VDMagic)); DicConsole.DebugWriteLine("ISO9660 plugin", "HSMagic = {0}", CurrentEncoding.GetString(HSMagic)); - return CurrentEncoding.GetString(VDMagic) == IsoMagic || CurrentEncoding.GetString(HSMagic) == HighSierraMagic; + return CurrentEncoding.GetString(VDMagic) == IsoMagic || CurrentEncoding.GetString(HSMagic) == HighSierraMagic || CurrentEncoding.GetString(VDMagic) == CdiMagic; } public override void GetInformation(ImagePlugins.ImagePlugin imagePlugin, Partition partition, out string information) @@ -93,6 +93,7 @@ namespace DiscImageChef.Filesystems.ISO9660 PrimaryVolumeDescriptor? jolietvd = null; BootRecord? bvd = null; HighSierraPrimaryVolumeDescriptor? hsvd = null; + FileStructureVolumeDescriptor? fsvd = null; ElToritoBootRecord? torito = null; // ISO9660 is designed for 2048 bytes/sector devices @@ -112,6 +113,7 @@ namespace DiscImageChef.Filesystems.ISO9660 int hs_off = 0; if(HighSierra) hs_off = 8; + bool CDi = false; while(true) { @@ -135,13 +137,15 @@ namespace DiscImageChef.Filesystems.ISO9660 Array.Copy(vd_sector, 0x001, VDMagic, 0, 5); Array.Copy(vd_sector, 0x009, HSMagic, 0, 5); - if(CurrentEncoding.GetString(VDMagic) != IsoMagic && CurrentEncoding.GetString(HSMagic) != HighSierraMagic) // Recognized, it is an ISO9660, now check for rest of data. + if(CurrentEncoding.GetString(VDMagic) != IsoMagic && CurrentEncoding.GetString(HSMagic) != HighSierraMagic && CurrentEncoding.GetString(VDMagic) != CdiMagic) // Recognized, it is an ISO9660, now check for rest of data. { if(counter == 0) return; break; } + CDi |= CurrentEncoding.GetString(VDMagic) == CdiMagic; + switch(VDType) { case 0: @@ -176,6 +180,8 @@ namespace DiscImageChef.Filesystems.ISO9660 hsvd = (HighSierraPrimaryVolumeDescriptor)Marshal.PtrToStructure(ptr, typeof(HighSierraPrimaryVolumeDescriptor)); Marshal.FreeHGlobal(ptr); } + else if(CDi) + fsvd = BigEndianMarshal.ByteArrayToStructureBigEndian(vd_sector); else { pvd = new PrimaryVolumeDescriptor(); @@ -221,7 +227,7 @@ namespace DiscImageChef.Filesystems.ISO9660 xmlFSType = new Schemas.FileSystemType(); - if(pvd == null && hsvd == null) + if(pvd == null && hsvd == null && fsvd == null) { information = "ERROR: Could not find primary volume descriptor"; return; @@ -229,6 +235,8 @@ namespace DiscImageChef.Filesystems.ISO9660 if(HighSierra) decodedVD = DecodeVolumeDescriptor(hsvd.Value); + else if(CDi) + decodedVD = DecodeVolumeDescriptor(fsvd.Value); else decodedVD = DecodeVolumeDescriptor(pvd.Value); @@ -267,7 +275,7 @@ namespace DiscImageChef.Filesystems.ISO9660 BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; // Walk thru root directory to see system area extensions in use - while(rootOff + Marshal.SizeOf(typeof(DirectoryRecord)) < root_dir.Length) + while(rootOff + Marshal.SizeOf(typeof(DirectoryRecord)) < root_dir.Length && !CDi) { DirectoryRecord record = new DirectoryRecord(); IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(record)); @@ -472,7 +480,15 @@ namespace DiscImageChef.Filesystems.ISO9660 Decoders.Sega.Saturn.IPBin? Saturn = Decoders.Sega.Saturn.DecodeIPBin(ipbin_sector); Decoders.Sega.Dreamcast.IPBin? Dreamcast = Decoders.Sega.Dreamcast.DecodeIPBin(ipbin_sector); - ISOMetadata.AppendFormat("{0} file system", HighSierra ? "High Sierra Format" : "ISO9660").AppendLine(); + string fsFormat; + if(HighSierra) + fsFormat = "High Sierra Format"; + else if (CDi) + fsFormat = "CD-i"; + else + fsFormat = "ISO9660"; + + ISOMetadata.AppendFormat("{0} file system", fsFormat).AppendLine(); if(XA) ISOMetadata.AppendLine("CD-ROM XA extensions present."); if(Apple) @@ -717,7 +733,7 @@ namespace DiscImageChef.Filesystems.ISO9660 if(refareas.Count > 0) ISOMetadata.Append(suspInformation.ToString()); - xmlFSType.Type = HighSierra ? "High Sierra Format" : "ISO9660"; + xmlFSType.Type = fsFormat; if(jolietvd != null) { diff --git a/DiscImageChef.Filesystems/ISO9660/Structs/CDi.cs b/DiscImageChef.Filesystems/ISO9660/Structs/CDi.cs new file mode 100644 index 000000000..5118199cd --- /dev/null +++ b/DiscImageChef.Filesystems/ISO9660/Structs/CDi.cs @@ -0,0 +1,191 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CDi.cs +// Author(s) : Natalia Portillo +// +// Component : Component +// +// --[ Description ] ---------------------------------------------------------- +// +// Description +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2017 Natalia Portillo +// ****************************************************************************/ +using System.Runtime.InteropServices; +using System.Text; + +namespace DiscImageChef.Filesystems.ISO9660 +{ + public partial class ISO9660 : Filesystem + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct FileStructureVolumeDescriptor + { + public byte type; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public byte[] id; + public byte version; + public CdiVolumeFlags flags; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] system_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] volume_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] + public byte[] reserved1; + public uint volume_space_size; + // Only used in SVDs + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] escape_sequences; + public ushort reserved2; + public ushort volume_set_siz; + public ushort reserved3; + public ushort volume_sequence_number; + public ushort reserved4; + public ushort logical_block_size; + public uint reserved5; + public uint path_table_size; + public ulong reserved6; + public uint type_m_path_table; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 38)] + public byte[] reserved7; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] volume_set_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] publisher_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] preparer_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] application_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] copyright_file_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public byte[] reserved8; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] abstract_file_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public byte[] reserved9; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] bibliographic_file_id; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public byte[] reserved10; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] creation_date; + public byte reserved11; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] modification_date; + public byte reserved12; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] expiration_date; + public byte reserved13; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] effective_date; + public byte reserved14; + public byte file_structure_version; + public byte reserved15; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] + public byte[] application_data; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 653)] + public byte[] reserved16; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CdiDirectoryRecord + { + public byte length; + public byte xattr_len; + public uint reserved1; + public uint start_lbn; + public uint reserved2; + public uint size; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + public byte[] date; + public byte reserved3; + public CdiFileFlags flags; + public ushort file_unit_size; + public ushort reserved4; + public ushort volume_sequence_number; + public byte name_len; + // Followed by name[name_len] and then CdiSystemArea until length arrives + } + + // Follows filename on directory record + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CdiSystemArea + { + public uint owner; + public CdiAttributes attributes; + public ushort reserved1; + public byte file_no; + public byte reserved2; + } + + static DecodedVolumeDescriptor DecodeVolumeDescriptor(FileStructureVolumeDescriptor pvd) + { + DecodedVolumeDescriptor decodedVD = new DecodedVolumeDescriptor(); + + decodedVD.SystemIdentifier = Encoding.ASCII.GetString(pvd.system_id).TrimEnd().Trim(new[] { '\0' }); + decodedVD.VolumeIdentifier = Encoding.ASCII.GetString(pvd.volume_id).TrimEnd().Trim(new[] { '\0' }); + decodedVD.VolumeSetIdentifier = Encoding.ASCII.GetString(pvd.volume_set_id).TrimEnd().Trim(new[] { '\0' }); + decodedVD.PublisherIdentifier = Encoding.ASCII.GetString(pvd.publisher_id).TrimEnd().Trim(new[] { '\0' }); + decodedVD.DataPreparerIdentifier = Encoding.ASCII.GetString(pvd.preparer_id).TrimEnd().Trim(new[] { '\0' }); + decodedVD.ApplicationIdentifier = Encoding.ASCII.GetString(pvd.application_data).TrimEnd().Trim(new[] { '\0' }); + if(pvd.creation_date[0] == '0' || pvd.creation_date[0] == 0x00) + decodedVD.CreationTime = System.DateTime.MinValue; + else + decodedVD.CreationTime = DateHandlers.HighSierraToDateTime(pvd.creation_date); + + if(pvd.modification_date[0] == '0' || pvd.modification_date[0] == 0x00) + { + decodedVD.HasModificationTime = false; + } + else + { + decodedVD.HasModificationTime = true; + decodedVD.ModificationTime = DateHandlers.HighSierraToDateTime(pvd.modification_date); + } + + if(pvd.expiration_date[0] == '0' || pvd.expiration_date[0] == 0x00) + { + decodedVD.HasExpirationTime = false; + } + else + { + decodedVD.HasExpirationTime = true; + decodedVD.ExpirationTime = DateHandlers.HighSierraToDateTime(pvd.expiration_date); + } + + if(pvd.effective_date[0] == '0' || pvd.effective_date[0] == 0x00) + { + decodedVD.HasEffectiveTime = false; + } + else + { + decodedVD.HasEffectiveTime = true; + decodedVD.EffectiveTime = DateHandlers.HighSierraToDateTime(pvd.effective_date); + } + + decodedVD.Blocks = pvd.volume_space_size; + decodedVD.BlockSize = pvd.logical_block_size; + + return decodedVD; + } + } +} diff --git a/DiscImageChef.Filesystems/ISO9660/Structs/ISO.cs b/DiscImageChef.Filesystems/ISO9660/Structs/ISO.cs index 4e461e13d..5c54bd103 100644 --- a/DiscImageChef.Filesystems/ISO9660/Structs/ISO.cs +++ b/DiscImageChef.Filesystems/ISO9660/Structs/ISO.cs @@ -186,6 +186,17 @@ namespace DiscImageChef.Filesystems.ISO9660 public ushort app_use_len_be; } + // There are two tables one in little endian one in big endian + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct PathTableEntry + { + public byte name_len; + public byte xattr_len; + public uint start_lbn; + public ushort parent_dirno; + // Followed by name[name_len] + } + static DecodedVolumeDescriptor DecodeVolumeDescriptor(PrimaryVolumeDescriptor pvd) { DecodedVolumeDescriptor decodedVD = new DecodedVolumeDescriptor(); diff --git a/DiscImageChef.Helpers/DateHandlers.cs b/DiscImageChef.Helpers/DateHandlers.cs index 915f31b27..ee612530b 100644 --- a/DiscImageChef.Helpers/DateHandlers.cs +++ b/DiscImageChef.Helpers/DateHandlers.cs @@ -88,6 +88,7 @@ namespace DiscImageChef return ISO9660ToDateTime(isotime); } + // TODO: Timezone public static DateTime ISO9660ToDateTime(byte[] VDDateTime) { int year, month, day, hour, minute, second, hundredths; diff --git a/README.md b/README.md index a2175662f..f339924af 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ Supported file systems for identification and information only * BSD Fast File System (FFS) / Unix File System (UFS) * BSD Unix File System 2 (UFS2) * BeOS filesystem +* CD-i file system * Coherent UNIX file system * Commodore 1540/1541/1571/1581 filesystems * Cram file system