Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions Library/DiscUtils.Iso9660/File.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using DiscUtils.Streams;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using LTRData.Extensions.Buffers;

namespace DiscUtils.Iso9660;
Expand All @@ -41,7 +42,7 @@ public File(IsoContext context, ReaderDirEntry dirEntry)
_dirEntry = dirEntry;
}

public virtual byte[] SystemUseData => _dirEntry.Record.SystemUseData;
public virtual byte[] SystemUseData => _dirEntry.RecordExtents[0].SystemUseData;

public UnixFileSystemInfo UnixFileInfo
{
Expand All @@ -54,8 +55,7 @@ public UnixFileSystemInfo UnixFileInfo

var suspRecords = new SuspRecords(_context, SystemUseData);

var pfi =
suspRecords.GetEntry<PosixFileInfoSystemUseEntry>(_context.RockRidgeIdentifier, "PX");
var pfi = suspRecords.GetEntry<PosixFileInfoSystemUseEntry>(_context.RockRidgeIdentifier, "PX");
if (pfi != null)
{
return new UnixFileSystemInfo
Expand Down Expand Up @@ -101,19 +101,35 @@ public FileAttributes FileAttributes
set => throw new NotSupportedException();
}

public long FileLength => _dirEntry.Record.DataLength;
public long FileLength => _dirEntry.RecordExtentsDataLength;

public IBuffer FileContent
{
get
{
var es = new ExtentStream(_context.DataStream, _dirEntry.Record.LocationOfExtent,
_dirEntry.Record.DataLength, _dirEntry.Record.FileUnitSize, _dirEntry.Record.InterleaveGapSize);
return new StreamBuffer(es, Ownership.Dispose);
if (_dirEntry.RecordExtents is [var extent])
{
return new StreamBuffer(
new ExtentStream(_context.DataStream, extent.LocationOfExtent, extent.DataLength, extent.FileUnitSize, extent.InterleaveGapSize),
Ownership.Dispose
);
}

return new StreamBuffer(
new ConcatStream(
Ownership.Dispose,
_dirEntry.RecordExtents.Select(
e => SparseStream.FromStream(
new ExtentStream(_context.DataStream, e.LocationOfExtent, e.DataLength, e.FileUnitSize, e.InterleaveGapSize),
Ownership.Dispose
)
)
),
Ownership.Dispose
);
}
}

public IEnumerable<StreamExtent> EnumerateAllocationExtents()
=> SingleValueEnumerable.Get(new StreamExtent(_dirEntry.Record.LocationOfExtent * IsoUtilities.SectorSize,
_dirEntry.Record.DataLength));
=> _dirEntry.RecordExtents.Select(e => new StreamExtent(e.LocationOfExtent * IsoUtilities.SectorSize, e.DataLength));
}
47 changes: 26 additions & 21 deletions Library/DiscUtils.Iso9660/ReaderDirEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@

using System;
using System.Buffers;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using DiscUtils.Internal;
using DiscUtils.Streams;
Expand All @@ -35,27 +38,26 @@ internal sealed class ReaderDirEntry : VfsDirEntry
{
private readonly IsoContext _context;
private readonly string _fileName;
private readonly DirectoryRecord _record;
internal readonly List<DirectoryRecord> _records = [];

public ReaderDirEntry(IsoContext context, DirectoryRecord dirRecord)
{
_context = context;
_record = dirRecord;
_fileName = _record.FileIdentifier;
_fileName = dirRecord.FileIdentifier;

var rockRidge = !string.IsNullOrEmpty(_context.RockRidgeIdentifier);

if (context.SuspDetected && _record.SystemUseData != null)
if (context.SuspDetected && dirRecord.SystemUseData != null)
{
SuspRecords = new SuspRecords(_context, _record.SystemUseData);
SuspRecords = new SuspRecords(_context, dirRecord.SystemUseData);
}

if (rockRidge && SuspRecords != null)
{
// The full name is taken from this record, even if it's a child-link record
var nameEntries = SuspRecords.GetEntries(_context.RockRidgeIdentifier, "NM");
var rrName = new StringBuilder();
if (nameEntries != null && nameEntries.Count > 0)
if (nameEntries?.Count > 0)
{
foreach (PosixNameSystemUseEntry nameEntry in nameEntries)
{
Expand All @@ -67,22 +69,20 @@ public ReaderDirEntry(IsoContext context, DirectoryRecord dirRecord)

// If this is a Rock Ridge child link, replace the dir record with that from the 'self' record
// in the child directory.
var clEntry =
SuspRecords.GetEntry<ChildLinkSystemUseEntry>(_context.RockRidgeIdentifier, "CL");
var clEntry = SuspRecords.GetEntry<ChildLinkSystemUseEntry>(_context.RockRidgeIdentifier, "CL");
if (clEntry != null)
{
_context.DataStream.Position = clEntry.ChildDirLocation * _context.VolumeDescriptor.LogicalBlockSize;

var firstSector = ArrayPool<byte>.Shared.Rent(_context.VolumeDescriptor.LogicalBlockSize);
try
{
_context.DataStream.ReadExactly(firstSector, 0,
_context.VolumeDescriptor.LogicalBlockSize);
_context.DataStream.ReadExactly(firstSector, 0, _context.VolumeDescriptor.LogicalBlockSize);

DirectoryRecord.ReadFrom(firstSector, _context.VolumeDescriptor.CharacterEncoding, out _record);
if (_record.SystemUseData != null)
DirectoryRecord.ReadFrom(firstSector, _context.VolumeDescriptor.CharacterEncoding, out dirRecord);
if (dirRecord.SystemUseData != null)
{
SuspRecords = new SuspRecords(_context, _record.SystemUseData);
SuspRecords = new SuspRecords(_context, dirRecord.SystemUseData);
}
}
finally
Expand All @@ -92,9 +92,9 @@ public ReaderDirEntry(IsoContext context, DirectoryRecord dirRecord)
}
}

LastAccessTimeUtc = _record.RecordingDateAndTime;
LastWriteTimeUtc = _record.RecordingDateAndTime;
CreationTimeUtc = _record.RecordingDateAndTime;
LastAccessTimeUtc =dirRecord.RecordingDateAndTime;
LastWriteTimeUtc = dirRecord.RecordingDateAndTime;
CreationTimeUtc = dirRecord.RecordingDateAndTime;

if (rockRidge && SuspRecords != null)
{
Expand All @@ -119,6 +119,8 @@ public ReaderDirEntry(IsoContext context, DirectoryRecord dirRecord)
}
}
}

_records.Add(dirRecord);
}

public override DateTime CreationTimeUtc { get; }
Expand Down Expand Up @@ -147,12 +149,12 @@ public override FileAttributes FileAttributes

attrs |= FileAttributes.ReadOnly;

if ((_record.Flags & FileFlags.Directory) != 0)
if ((_records[0].Flags & FileFlags.Directory) != 0)
{
attrs |= FileAttributes.Directory;
}

if ((_record.Flags & FileFlags.Hidden) != 0)
if ((_records[0].Flags & FileFlags.Hidden) != 0)
{
attrs |= FileAttributes.Hidden;
}
Expand All @@ -167,17 +169,20 @@ public override FileAttributes FileAttributes

public override bool HasVfsTimeInfo => true;

public override bool IsDirectory => (_record.Flags & FileFlags.Directory) != 0;
public override bool IsDirectory => (_records[0].Flags & FileFlags.Directory) != 0;

public override bool IsSymlink => false;

public override DateTime LastAccessTimeUtc { get; }

public override DateTime LastWriteTimeUtc { get; }

public DirectoryRecord Record => _record;
[Obsolete("Please use RecordExtents property instead.")]
public DirectoryRecord Record => _records[0];
public ReadOnlyCollection<DirectoryRecord> RecordExtents => _records.AsReadOnly();
public long RecordExtentsDataLength => _records.Sum(r => r.DataLength);

public SuspRecords SuspRecords { get; }

public override long UniqueCacheId => ((long)_record.LocationOfExtent << 32) | _record.DataLength;
public override long UniqueCacheId => ((long)_records[0].LocationOfExtent << 32) | _records[0].DataLength;
}
20 changes: 16 additions & 4 deletions Library/DiscUtils.Iso9660/ReaderDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ public ReaderDirectory(IsoContext context, ReaderDirEntry dirEntry)
var buffer = ArrayPool<byte>.Shared.Rent(IsoUtilities.SectorSize);
try
{
if (dirEntry.RecordExtents is not [var dirExtent])
{
throw new NotSupportedException("MultiExtent directory entries are not supported");
}

Array.Clear(buffer, 0, buffer.Length);
Stream extent = new ExtentStream(_context.DataStream, dirEntry.Record.LocationOfExtent, uint.MaxValue, 0, 0);
Stream extent = new ExtentStream(_context.DataStream, dirExtent.LocationOfExtent, uint.MaxValue, 0, 0);

_records = new(StringComparer.OrdinalIgnoreCase, entry => entry.FileName);

var totalLength = dirEntry.Record.DataLength;
var totalLength = dirExtent.DataLength;
uint totalRead = 0;
while (totalRead < totalLength)
{
Expand All @@ -73,7 +78,14 @@ public ReaderDirectory(IsoContext context, ReaderDirEntry dirEntry)
}
else
{
_records.Add(childDirEntry);
if (_records.TryGetValue(childDirEntry.FileName, out var existingEntry))
{
existingEntry._records.Add(dr);
}
else
{
_records.Add(childDirEntry);
}
}
}
else if (dr.FileIdentifier == "\0")
Expand All @@ -91,7 +103,7 @@ public ReaderDirectory(IsoContext context, ReaderDirEntry dirEntry)
}
}

public override byte[] SystemUseData => Self.Record.SystemUseData;
public override byte[] SystemUseData => Self.RecordExtents[0].SystemUseData;

public IReadOnlyDictionary<string, ReaderDirEntry> AllEntries => _records;

Expand Down
28 changes: 14 additions & 14 deletions Library/DiscUtils.Iso9660/VfsCDReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,41 +305,38 @@ public IEnumerable<Range<long, long>> PathToClusters(string path)
var entry = GetDirectoryEntry(path)
?? throw new FileNotFoundException("File not found", path);

if (entry.Record.FileUnitSize != 0 || entry.Record.InterleaveGapSize != 0)
if (entry.RecordExtents.Any(e => e.FileUnitSize != 0 || e.InterleaveGapSize != 0))
{
throw new NotSupportedException("Non-contiguous extents not supported");
}

return SingleValueEnumerable.Get(
new Range<long, long>(entry.Record.LocationOfExtent,
MathUtilities.Ceil(entry.Record.DataLength, IsoUtilities.SectorSize)));
return entry.RecordExtents.Select(e => new Range<long, long>(e.LocationOfExtent, MathUtilities.Ceil(e.DataLength, IsoUtilities.SectorSize)));
}

public IEnumerable<StreamExtent> PathToExtents(string path)
{
var entry = GetDirectoryEntry(path)
?? throw new FileNotFoundException("File not found", path);

if (entry.Record.FileUnitSize != 0 || entry.Record.InterleaveGapSize != 0)
if (entry.RecordExtents.Any(e => e.FileUnitSize != 0 || e.InterleaveGapSize != 0))
{
throw new NotSupportedException("Non-contiguous extents not supported");
}

return SingleValueEnumerable.Get(
new StreamExtent(entry.Record.LocationOfExtent * IsoUtilities.SectorSize, entry.Record.DataLength));
return entry.RecordExtents.Select(e => new StreamExtent(e.LocationOfExtent * IsoUtilities.SectorSize, e.DataLength));
}

public long GetAllocatedClustersCount(string path)
{
var entry = GetDirectoryEntry(path)
?? throw new FileNotFoundException("File not found", path);

if (entry.Record.FileUnitSize != 0 || entry.Record.InterleaveGapSize != 0)
if (entry.RecordExtents.Any(e => e.FileUnitSize != 0 || e.InterleaveGapSize != 0))
{
throw new NotSupportedException("Non-contiguous extents not supported");
}

return entry.Record.DataLength / IsoUtilities.SectorSize;
return entry.RecordExtentsDataLength / IsoUtilities.SectorSize;
}

public ClusterMap BuildClusterMap()
Expand Down Expand Up @@ -371,16 +368,19 @@ public ClusterMap BuildClusterMap()
fileIdToPaths[entry.UniqueCacheId] = Array.AsReadOnly(newPaths);
}

if (entry.Record.FileUnitSize != 0 || entry.Record.InterleaveGapSize != 0)
if (entry.RecordExtents.Any(e => e.FileUnitSize != 0 || e.InterleaveGapSize != 0))
{
throw new NotSupportedException("Non-contiguous extents not supported");
}

long clusters = MathUtilities.Ceil(entry.Record.DataLength, IsoUtilities.SectorSize);
for (long i = 0; i < clusters; ++i)
foreach (var e in entry.RecordExtents)
{
clusterToRole[i + entry.Record.LocationOfExtent] = ClusterRoles.DataFile;
clusterToFileId[i + entry.Record.LocationOfExtent] = entry.UniqueCacheId;
long clusters = MathUtilities.Ceil(e.DataLength, IsoUtilities.SectorSize);
for (long i = 0; i < clusters; ++i)
{
clusterToRole[i + e.LocationOfExtent] = ClusterRoles.DataFile;
clusterToFileId[i + e.LocationOfExtent] = entry.UniqueCacheId;
}
}
});

Expand Down
Binary file not shown.
36 changes: 30 additions & 6 deletions Tests/LibraryTests/Iso9660/SampleDataTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DiscUtils.Iso9660;
using LibraryTests.Utilities;
using Xunit;
Expand All @@ -11,17 +12,40 @@ public class SampleDataTests
[Fact]
public void AppleTestZip()
{
using var iso = Helpers.Helpers.LoadTestDataFileFromGZipFile("Iso9660", "apple-test.iso.gz");
using var iso = Helpers.Helpers.LoadTestDataFileFromGZipFile(nameof(Iso9660), "apple-test.iso.gz");
using var cr = new CDReader(iso, false);

var dir = cr.GetDirectoryInfo("sub-directory");
Assert.NotNull(dir);
Assert.Equal("sub-directory", dir.Name);

var file = dir.GetFiles("apple-test.txt");
Assert.Single(file);
Assert.Equal(21, file.First().Length);
Assert.Equal("apple-test.txt", file.First().Name);
Assert.Equal(dir, file.First().Directory);
var files = dir.GetFiles("apple-test.txt").ToList();
Assert.Single(files);
Assert.Equal(21, files.First().Length);
Assert.Equal("apple-test.txt", files.First().Name);
Assert.Equal(dir, files.First().Directory);
}

[Fact]
public void MultiExtentFiles()
{
using var iso = Helpers.Helpers.LoadTestDataFileFromGZipFile(nameof(Iso9660), "multiextent.iso_header.gz");
using var cr = new CDReader(iso, joliet: true, hideVersions: true);

const string pathToMultiextentFiles = @"\PS3_GAME\USRDIR\Resource\Common";
var fsEntries = cr.GetFileSystemEntries(pathToMultiextentFiles).ToList();
Assert.Equal(11, fsEntries.Count);

var dir = cr.GetDirectoryInfo(pathToMultiextentFiles);
var files = dir.GetFiles().ToList();
Assert.Equal(10, files.Count);

var misc0 = files.First(f => f.Name is "Misc0.FPK");
var misc1 = files.First(f => f.Name is "Misc1.FPK");
Assert.Equal(1464404972, misc0.Length);
Assert.Equal(1585521232, misc1.Length);

var meClusters = cr.PathToClusters(misc0.FullName).ToList();
Assert.Equal(2, meClusters.Count);
}
}