diff --git a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
index b7b9fc676..5a7f27db6 100644
--- a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
+++ b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
@@ -71,6 +71,8 @@
+
+
@@ -184,7 +186,7 @@
-
+
diff --git a/DiscImageChef.Filesystems/ISO9660/Consts/Internal.cs b/DiscImageChef.Filesystems/ISO9660/Consts/Internal.cs
new file mode 100644
index 000000000..e0e674838
--- /dev/null
+++ b/DiscImageChef.Filesystems/ISO9660/Consts/Internal.cs
@@ -0,0 +1,53 @@
+// /***************************************************************************
+// The Disc Image Chef
+// ----------------------------------------------------------------------------
+//
+// Filename : AAIP.cs
+// Author(s) : Natalia Portillo
+//
+// Component : ISO9660 filesystem plugin.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// AAIP extensions constants and enumerations.
+//
+// --[ 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-2019 Natalia Portillo
+// ****************************************************************************/
+
+using DiscImageChef.Helpers;
+
+namespace DiscImageChef.Filesystems.ISO9660
+{
+ public partial class ISO9660
+ {
+ static readonly int DirectoryRecordSize = Marshal.SizeOf();
+
+ enum Namespace
+ {
+ Normal,
+ Vms,
+ Joliet,
+ JolietNormal,
+ Rrip,
+ RripNormal,
+ RripJoliet,
+ RripJolietNormal
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscImageChef.Filesystems/ISO9660/Date.cs b/DiscImageChef.Filesystems/ISO9660/Date.cs
new file mode 100644
index 000000000..20810a984
--- /dev/null
+++ b/DiscImageChef.Filesystems/ISO9660/Date.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace DiscImageChef.Filesystems.ISO9660
+{
+ public partial class ISO9660
+ {
+ DateTime DecodeIsoDateTime(byte[] date) => throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/DiscImageChef.Filesystems/ISO9660/Dir.cs b/DiscImageChef.Filesystems/ISO9660/Dir.cs
index cfcf074be..0a431c7a3 100644
--- a/DiscImageChef.Filesystems/ISO9660/Dir.cs
+++ b/DiscImageChef.Filesystems/ISO9660/Dir.cs
@@ -1,11 +1,61 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Text;
using DiscImageChef.CommonTypes.Structs;
+using DiscImageChef.Helpers;
namespace DiscImageChef.Filesystems.ISO9660
{
public partial class ISO9660
{
public Errno ReadDir(string path, out List contents) => throw new NotImplementedException();
+
+ List DecodeCdiDirectory(byte[] data) => throw new NotImplementedException();
+
+ List DecodeHighSierraDirectory(byte[] data) => throw new NotImplementedException();
+
+ // TODO: Implement system area
+ List DecodeIsoDirectory(byte[] data)
+ {
+ List entries = new List();
+ int entryOff = 0;
+
+ while(entryOff + DirectoryRecordSize < data.Length)
+ {
+ DirectoryRecord record =
+ Marshal.ByteArrayToStructureLittleEndian(data, entryOff,
+ Marshal.SizeOf());
+
+ if(record.length == 0) break;
+
+ // Special entries for current and parent directories, skip them
+ if(record.name_len == 1)
+ if(data[entryOff + DirectoryRecordSize] == 0 || data[entryOff + DirectoryRecordSize] == 1)
+ {
+ entryOff += record.length;
+ continue;
+ }
+
+ DecodedDirectoryEntry entry = new DecodedDirectoryEntry();
+
+ entry.Extent = record.extent;
+ entry.Size = record.size;
+ entry.Flags = record.flags;
+ entry.FileUnitSize = record.file_unit_size;
+ entry.Interleave = record.interleave;
+ entry.VolumeSequenceNumber = record.volume_sequence_number;
+ entry.IsoFilename =
+ Encoding.ASCII.GetString(data, entryOff + DirectoryRecordSize, record.name_len);
+ entry.Timestamp = DecodeIsoDateTime(record.date);
+
+ // TODO: Multi-extent files
+ if(entries.All(e => e.IsoFilename != entry.IsoFilename)) entries.Add(entry);
+
+ entryOff += record.length;
+ }
+
+ return entries;
+ }
}
}
\ No newline at end of file
diff --git a/DiscImageChef.Filesystems/ISO9660/ISO9660.cs b/DiscImageChef.Filesystems/ISO9660/ISO9660.cs
index 860b23dc8..bee38803c 100644
--- a/DiscImageChef.Filesystems/ISO9660/ISO9660.cs
+++ b/DiscImageChef.Filesystems/ISO9660/ISO9660.cs
@@ -42,6 +42,8 @@ namespace DiscImageChef.Filesystems.ISO9660
// This is coded following ECMA-119.
public partial class ISO9660 : IReadOnlyFilesystem
{
+ Namespace @namespace;
+
public FileSystemType XmlFsType { get; private set; }
public Encoding Encoding { get; private set; }
public string Name => "ISO9660 Filesystem";
@@ -57,7 +59,11 @@ namespace DiscImageChef.Filesystems.ISO9660
{"normal", "Primary Volume Descriptor, ignoring ;1 suffixes"},
{"vms", "Primary Volume Descriptor, showing version suffixes"},
{"joliet", "Joliet Volume Descriptor"},
- {"joliet+normal", "Joliet with fallback to normal"}
+ {"joliet+normal", "Joliet with fallback to normal"},
+ {"rrip", "Rock Ridge"},
+ {"rrip+normal", "Rock Ridge with fallback to normal"},
+ {"rrip+joliet", "Rock Ridge with fallback to Joliet"},
+ {"rrip+joliet+normal", "Rock Ridge with fallback to Joliet and then to normal (default)"}
};
public Errno ReadLink(string path, out string dest)
diff --git a/DiscImageChef.Filesystems/ISO9660/Structs/Common.cs b/DiscImageChef.Filesystems/ISO9660/Structs/Internal.cs
similarity index 83%
rename from DiscImageChef.Filesystems/ISO9660/Structs/Common.cs
rename to DiscImageChef.Filesystems/ISO9660/Structs/Internal.cs
index dcf60bc3c..ed9932fc9 100644
--- a/DiscImageChef.Filesystems/ISO9660/Structs/Common.cs
+++ b/DiscImageChef.Filesystems/ISO9660/Structs/Internal.cs
@@ -54,5 +54,19 @@ namespace DiscImageChef.Filesystems.ISO9660
public ushort BlockSize;
public uint Blocks;
}
+
+ struct DecodedDirectoryEntry
+ {
+ public uint Extent;
+ public uint Size;
+ public FileFlags Flags;
+ public byte FileUnitSize;
+ public byte Interleave;
+ public ushort VolumeSequenceNumber;
+ public string IsoFilename;
+ public DateTime Timestamp;
+
+ public override string ToString() => IsoFilename;
+ }
}
}
\ No newline at end of file
diff --git a/DiscImageChef.Filesystems/ISO9660/Super.cs b/DiscImageChef.Filesystems/ISO9660/Super.cs
index 5adf9c052..f50f23216 100644
--- a/DiscImageChef.Filesystems/ISO9660/Super.cs
+++ b/DiscImageChef.Filesystems/ISO9660/Super.cs
@@ -4,16 +4,342 @@ using System.Text;
using DiscImageChef.CommonTypes;
using DiscImageChef.CommonTypes.Interfaces;
using DiscImageChef.CommonTypes.Structs;
+using DiscImageChef.Console;
+using DiscImageChef.Decoders.Sega;
+using DiscImageChef.Helpers;
+using Schemas;
namespace DiscImageChef.Filesystems.ISO9660
{
public partial class ISO9660
{
- public Errno Mount(IMediaImage imagePlugin, Partition partition, Encoding encoding,
- Dictionary options, string @namespace) =>
- throw new NotImplementedException();
+ bool debug;
+ IMediaImage image;
+ bool mounted;
+ List rootDirectory;
- public Errno Unmount() => throw new NotImplementedException();
+ public Errno Mount(IMediaImage imagePlugin, Partition partition, Encoding encoding,
+ Dictionary options, string @namespace)
+ {
+ Encoding = encoding ?? Encoding.ASCII;
+ byte[] vdMagic = new byte[5]; // Volume Descriptor magic "CD001"
+ byte[] hsMagic = new byte[5]; // Volume Descriptor magic "CDROM"
+
+ if(options == null) options = GetDefaultOptions();
+ if(options.TryGetValue("debug", out string debugString)) bool.TryParse(debugString, out debug);
+
+ // Default namespace
+ if(@namespace is null) @namespace = "rrip+joliet+normal";
+
+ switch(@namespace.ToLowerInvariant())
+ {
+ case "normal":
+ this.@namespace = Namespace.Normal;
+ break;
+ case "vms":
+ this.@namespace = Namespace.Vms;
+ break;
+ case "joliet":
+ this.@namespace = Namespace.Joliet;
+ break;
+ case "joliet+normal":
+ this.@namespace = Namespace.JolietNormal;
+ break;
+ case "rrip":
+ this.@namespace = Namespace.Rrip;
+ break;
+ case "rrip+normal":
+ this.@namespace = Namespace.RripNormal;
+ break;
+ case "rrip+joliet":
+ this.@namespace = Namespace.RripJoliet;
+ break;
+ case "rrip+joliet+normal":
+ this.@namespace = Namespace.RripJolietNormal;
+ break;
+ default: return Errno.InvalidArgument;
+ }
+
+ PrimaryVolumeDescriptor? pvd = null;
+ PrimaryVolumeDescriptor? jolietvd = null;
+ BootRecord? bvd = null;
+ HighSierraPrimaryVolumeDescriptor? hsvd = null;
+ FileStructureVolumeDescriptor? fsvd = null;
+
+ // ISO9660 is designed for 2048 bytes/sector devices
+ if(imagePlugin.Info.SectorSize < 2048) return Errno.InvalidArgument;
+
+ // ISO9660 Primary Volume Descriptor starts at sector 16, so that's minimal size.
+ if(partition.End < 16) return Errno.InvalidArgument;
+
+ ulong counter = 0;
+
+ byte[] vdSector = imagePlugin.ReadSector(16 + counter + partition.Start);
+ int xaOff = vdSector.Length == 2336 ? 8 : 0;
+ Array.Copy(vdSector, 0x009 + xaOff, hsMagic, 0, 5);
+ bool highSierra = Encoding.GetString(hsMagic) == HIGH_SIERRA_MAGIC;
+ int hsOff = 0;
+ if(highSierra) hsOff = 8;
+ bool cdi = false;
+
+ while(true)
+ {
+ DicConsole.DebugWriteLine("ISO9660 plugin", "Processing VD loop no. {0}", counter);
+ // Seek to Volume Descriptor
+ DicConsole.DebugWriteLine("ISO9660 plugin", "Reading sector {0}", 16 + counter + partition.Start);
+ byte[] vdSectorTmp = imagePlugin.ReadSector(16 + counter + partition.Start);
+ vdSector = new byte[vdSectorTmp.Length - xaOff];
+ Array.Copy(vdSectorTmp, xaOff, vdSector, 0, vdSector.Length);
+
+ byte vdType = vdSector[0 + hsOff]; // Volume Descriptor Type, should be 1 or 2.
+ DicConsole.DebugWriteLine("ISO9660 plugin", "VDType = {0}", vdType);
+
+ if(vdType == 255) // Supposedly we are in the PVD.
+ {
+ if(counter == 0) return Errno.InvalidArgument;
+
+ break;
+ }
+
+ Array.Copy(vdSector, 0x001, vdMagic, 0, 5);
+ Array.Copy(vdSector, 0x009, hsMagic, 0, 5);
+
+ if(Encoding.GetString(vdMagic) != ISO_MAGIC && Encoding.GetString(hsMagic) != HIGH_SIERRA_MAGIC &&
+ Encoding.GetString(vdMagic) != CDI_MAGIC
+ ) // Recognized, it is an ISO9660, now check for rest of data.
+ {
+ if(counter == 0) return Errno.InvalidArgument;
+
+ break;
+ }
+
+ cdi |= Encoding.GetString(vdMagic) == CDI_MAGIC;
+
+ switch(vdType)
+ {
+ case 0:
+ {
+ bvd = Marshal.ByteArrayToStructureLittleEndian(vdSector, hsOff, 2048 - hsOff);
+
+ // TODO: Add boot file to debug root directory
+
+ break;
+ }
+
+ case 1:
+ {
+ if(highSierra)
+ hsvd = Marshal
+ .ByteArrayToStructureLittleEndian(vdSector);
+ else if(cdi)
+ fsvd = Marshal.ByteArrayToStructureBigEndian(vdSector);
+ else pvd = Marshal.ByteArrayToStructureLittleEndian(vdSector);
+
+ break;
+ }
+
+ case 2:
+ {
+ PrimaryVolumeDescriptor svd =
+ Marshal.ByteArrayToStructureLittleEndian(vdSector);
+
+ // Check if this is Joliet
+ if(svd.escape_sequences[0] == '%' && svd.escape_sequences[1] == '/')
+ if(svd.escape_sequences[2] == '@' || svd.escape_sequences[2] == 'C' ||
+ svd.escape_sequences[2] == 'E') jolietvd = svd;
+ else
+ DicConsole.WriteLine("ISO9660 plugin", "Found unknown supplementary volume descriptor");
+
+ break;
+ }
+ }
+
+ counter++;
+ }
+
+ DecodedVolumeDescriptor decodedVd;
+ DecodedVolumeDescriptor decodedJolietVd = new DecodedVolumeDescriptor();
+
+ XmlFsType = new FileSystemType();
+
+ if(pvd == null && hsvd == null && fsvd == null)
+ {
+ DicConsole.ErrorWriteLine("ERROR: Could not find primary volume descriptor");
+ return Errno.InvalidArgument;
+ }
+
+ if(highSierra) decodedVd = DecodeVolumeDescriptor(hsvd.Value);
+ else if(cdi) decodedVd = DecodeVolumeDescriptor(fsvd.Value);
+ else decodedVd = DecodeVolumeDescriptor(pvd.Value);
+
+ if(jolietvd != null) decodedJolietVd = DecodeJolietDescriptor(jolietvd.Value);
+
+ string fsFormat;
+ if(highSierra) fsFormat = "High Sierra Format";
+ else if(cdi)
+ {
+ fsFormat = "CD-i";
+ // TODO: Implement CD-i
+ return Errno.NotImplemented;
+ }
+ else fsFormat = "ISO9660";
+
+ uint rootLocation = 0;
+ uint rootSize = 0;
+
+ // TODO: Read CD-i root directory
+ if(!cdi)
+ {
+ rootLocation = highSierra
+ ? hsvd.Value.root_directory_record.extent
+ : pvd.Value.root_directory_record.extent;
+
+ if(highSierra)
+ {
+ rootSize = hsvd.Value.root_directory_record.size / hsvd.Value.logical_block_size;
+ if(hsvd.Value.root_directory_record.size % hsvd.Value.logical_block_size > 0) rootSize++;
+ }
+ else
+ {
+ rootSize = pvd.Value.root_directory_record.size / pvd.Value.logical_block_size;
+ if(pvd.Value.root_directory_record.size % pvd.Value.logical_block_size > 0) rootSize++;
+ }
+ }
+
+ if(rootLocation + rootSize >= imagePlugin.Info.Sectors) return Errno.InvalidArgument;
+
+ byte[] rootDir = imagePlugin.ReadSectors(rootLocation, rootSize);
+
+ byte[] ipbinSector = imagePlugin.ReadSector(0 + partition.Start);
+ CD.IPBin? segaCd = CD.DecodeIPBin(ipbinSector);
+ Saturn.IPBin? saturn = Saturn.DecodeIPBin(ipbinSector);
+ Dreamcast.IPBin? dreamcast = Dreamcast.DecodeIPBin(ipbinSector);
+
+ // TODO: Add IP.BIN to debug root directory
+ // TODO: Add volume descriptors to debug root directory
+ // TODO: Decode Joliet directory
+
+ rootDirectory = cdi
+ ? DecodeCdiDirectory(rootDir)
+ : highSierra
+ ? DecodeHighSierraDirectory(rootDir)
+ : DecodeIsoDirectory(rootDir);
+
+ XmlFsType.Type = fsFormat;
+
+ if(jolietvd != null && this.@namespace != Namespace.Normal &&
+ this.@namespace != Namespace.Vms &&
+ this.@namespace != Namespace.Rrip && this.@namespace != Namespace.RripNormal)
+ {
+ XmlFsType.VolumeName = decodedJolietVd.VolumeIdentifier;
+
+ if(string.IsNullOrEmpty(decodedJolietVd.SystemIdentifier) ||
+ decodedVd.SystemIdentifier.Length > decodedJolietVd.SystemIdentifier.Length)
+ XmlFsType.SystemIdentifier = decodedVd.SystemIdentifier;
+ else
+ XmlFsType.SystemIdentifier = string.IsNullOrEmpty(decodedJolietVd.SystemIdentifier)
+ ? null
+ : decodedJolietVd.SystemIdentifier;
+
+ if(string.IsNullOrEmpty(decodedJolietVd.VolumeSetIdentifier) || decodedVd.VolumeSetIdentifier.Length >
+ decodedJolietVd.VolumeSetIdentifier.Length)
+ XmlFsType.VolumeSetIdentifier = decodedVd.VolumeSetIdentifier;
+ else
+ XmlFsType.VolumeSetIdentifier = string.IsNullOrEmpty(decodedJolietVd.VolumeSetIdentifier)
+ ? null
+ : decodedJolietVd.VolumeSetIdentifier;
+
+ if(string.IsNullOrEmpty(decodedJolietVd.PublisherIdentifier) || decodedVd.PublisherIdentifier.Length >
+ decodedJolietVd.PublisherIdentifier.Length)
+ XmlFsType.PublisherIdentifier = decodedVd.PublisherIdentifier;
+ else
+ XmlFsType.PublisherIdentifier = string.IsNullOrEmpty(decodedJolietVd.PublisherIdentifier)
+ ? null
+ : decodedJolietVd.PublisherIdentifier;
+
+ if(string.IsNullOrEmpty(decodedJolietVd.DataPreparerIdentifier) ||
+ decodedVd.DataPreparerIdentifier.Length > decodedJolietVd.DataPreparerIdentifier.Length)
+ XmlFsType.DataPreparerIdentifier = decodedVd.DataPreparerIdentifier;
+ else
+ XmlFsType.DataPreparerIdentifier = string.IsNullOrEmpty(decodedJolietVd.DataPreparerIdentifier)
+ ? null
+ : decodedJolietVd.DataPreparerIdentifier;
+
+ if(string.IsNullOrEmpty(decodedJolietVd.ApplicationIdentifier) ||
+ decodedVd.ApplicationIdentifier.Length > decodedJolietVd.ApplicationIdentifier.Length)
+ XmlFsType.ApplicationIdentifier = decodedVd.ApplicationIdentifier;
+ else
+ XmlFsType.ApplicationIdentifier = string.IsNullOrEmpty(decodedJolietVd.ApplicationIdentifier)
+ ? null
+ : decodedJolietVd.ApplicationIdentifier;
+
+ XmlFsType.CreationDate = decodedJolietVd.CreationTime;
+ XmlFsType.CreationDateSpecified = true;
+ if(decodedJolietVd.HasModificationTime)
+ {
+ XmlFsType.ModificationDate = decodedJolietVd.ModificationTime;
+ XmlFsType.ModificationDateSpecified = true;
+ }
+
+ if(decodedJolietVd.HasExpirationTime)
+ {
+ XmlFsType.ExpirationDate = decodedJolietVd.ExpirationTime;
+ XmlFsType.ExpirationDateSpecified = true;
+ }
+
+ if(decodedJolietVd.HasEffectiveTime)
+ {
+ XmlFsType.EffectiveDate = decodedJolietVd.EffectiveTime;
+ XmlFsType.EffectiveDateSpecified = true;
+ }
+ }
+ else
+ {
+ XmlFsType.SystemIdentifier = decodedVd.SystemIdentifier;
+ XmlFsType.VolumeName = decodedVd.VolumeIdentifier;
+ XmlFsType.VolumeSetIdentifier = decodedVd.VolumeSetIdentifier;
+ XmlFsType.PublisherIdentifier = decodedVd.PublisherIdentifier;
+ XmlFsType.DataPreparerIdentifier = decodedVd.DataPreparerIdentifier;
+ XmlFsType.ApplicationIdentifier = decodedVd.ApplicationIdentifier;
+ XmlFsType.CreationDate = decodedVd.CreationTime;
+ XmlFsType.CreationDateSpecified = true;
+ if(decodedVd.HasModificationTime)
+ {
+ XmlFsType.ModificationDate = decodedVd.ModificationTime;
+ XmlFsType.ModificationDateSpecified = true;
+ }
+
+ if(decodedVd.HasExpirationTime)
+ {
+ XmlFsType.ExpirationDate = decodedVd.ExpirationTime;
+ XmlFsType.ExpirationDateSpecified = true;
+ }
+
+ if(decodedVd.HasEffectiveTime)
+ {
+ XmlFsType.EffectiveDate = decodedVd.EffectiveTime;
+ XmlFsType.EffectiveDateSpecified = true;
+ }
+ }
+
+ XmlFsType.Bootable |= bvd != null || segaCd != null || saturn != null || dreamcast != null;
+ XmlFsType.Clusters = decodedVd.Blocks;
+ XmlFsType.ClusterSize = decodedVd.BlockSize;
+
+ image = imagePlugin;
+ mounted = true;
+ return Errno.NoError;
+ }
+
+ public Errno Unmount()
+ {
+ if(!mounted) return Errno.AccessDenied;
+
+ rootDirectory = null;
+ mounted = false;
+
+ return Errno.NoError;
+ }
public Errno StatFs(out FileSystemInfo stat) => throw new NotImplementedException();
}