Files
gmipf 1663345195 Add a UDF reader and wrapper (#100)
* Add a UDF reader

Reads the Volume Recognition Sequence, the Anchor Volume Descriptor
Pointer (sector 256, then the end of the volume), the Main or Reserve
Volume Descriptor Sequence, the File Set Descriptor and the directory
hierarchy. Type 1 and Metadata Partitions are mapped, Virtual and
Sparable partitions are not.

File Entries, Allocation Extent Descriptors and directories are read in
ascending sector order with wrap-around, adjacent sectors merged into
one request. FindFileEntry follows one path and reads only the
directories on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add a UDF wrapper

Prints the volume and file set structures and the directory hierarchy,
and extracts the files. CreateDiscImageWrapper tries UDF after ISO 9660
for a stream at offset 0, so discs with both file systems still open as
ISO 9660.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Treat the stream position as the start of the UDF volume

The UDF reader addresses logical sectors from the start of the stream,
so the wrapper hands it a view that starts at the current position and
keeps that offset, like the other wrappers. CreateDiscImageWrapper no
longer skips UDF for a stream that is not at offset 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review comments on the UDF code

- Move the descriptor tag check to UDFExtensions, with tests
- Parse Volume Structure Descriptors in their own method
- Use the existing extensions for numbers, the partition type identifier
  and printing entity identifiers
- Use Array.Exists in IsSafeName
- Make Extract non-virtual and remove the printing test
- Use collection expressions and string interpolation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Parse File Identifier Descriptors in their own method

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add a TODO to use the CRC from Hashing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:18:23 +01:00

113 lines
3.8 KiB
C#

using System.Collections.Generic;
using System.IO;
using SabreTools.Data.Models.UDF;
using SabreTools.IO;
namespace SabreTools.Wrappers
{
public partial class UDF : WrapperBase<Volume>
{
#region Descriptive Properties
/// <inheritdoc/>
public override string DescriptionString => "Universal Disk Format (UDF) Volume";
#endregion
#region Extension Properties
/// <inheritdoc cref="Volume.VolumeRecognitionSequence"/>
public VolumeStructureDescriptor[] VolumeRecognitionSequence => Model.VolumeRecognitionSequence;
/// <inheritdoc cref="Volume.VolumeDescriptorSequence"/>
public Descriptor[] VolumeDescriptorSequence => Model.VolumeDescriptorSequence;
/// <inheritdoc cref="Volume.FileSetDescriptor"/>
public FileSetDescriptor? FileSetDescriptor => Model.FileSetDescriptor;
/// <inheritdoc cref="Volume.FileEntries"/>
public Dictionary<long, FileEntry> FileEntries => Model.FileEntries;
/// <inheritdoc cref="Volume.Directories"/>
public Dictionary<long, FileIdentifierDescriptor[]> Directories => Model.Directories;
#endregion
#region Constructors
/// <inheritdoc/>
public UDF(Volume model, byte[] data) : base(model, data) { }
/// <inheritdoc/>
public UDF(Volume model, byte[] data, int offset) : base(model, data, offset) { }
/// <inheritdoc/>
public UDF(Volume model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
/// <inheritdoc/>
public UDF(Volume model, Stream data) : base(model, data) { }
/// <inheritdoc/>
public UDF(Volume model, Stream data, long offset) : base(model, data, offset) { }
/// <inheritdoc/>
public UDF(Volume model, Stream data, long offset, long length) : base(model, data, offset, length) { }
#endregion
#region Static Constructors
/// <summary>
/// Create a UDF Volume from a byte array and offset
/// </summary>
/// <param name="data">Byte array representing the UDF Volume</param>
/// <param name="offset">Offset within the array to parse</param>
/// <returns>A UDF Volume wrapper on success, null on failure</returns>
public static UDF? Create(byte[]? data, int offset)
{
// If the data is invalid
if (data is null || data.Length == 0)
return null;
// If the offset is out of bounds
if (offset < 0 || offset >= data.Length)
return null;
// Create a memory stream and use that
var dataStream = new MemoryStream(data, offset, data.Length - offset);
return Create(dataStream);
}
/// <summary>
/// Create a UDF Volume from a Stream
/// </summary>
/// <param name="data">Stream representing the UDF Volume</param>
/// <returns>A UDF Volume wrapper on success, null on failure</returns>
public static UDF? Create(Stream? data)
{
// If the data is invalid
if (data is null || !data.CanRead || !data.CanSeek)
return null;
try
{
// Cache the current offset
long currentOffset = data.Position;
// The reader addresses logical sectors from the start of the stream, so it reads a view that starts here
var model = new Serialization.Readers.UDF().Deserialize(new ReadOnlyViewStream(data, currentOffset));
if (model is null)
return null;
return new UDF(model, data, currentOffset);
}
catch
{
return null;
}
}
#endregion
}
}