Add the UDF models (#108)

The ECMA-167 and UDF structures that the UDF reader fills in, split out
of #100. The only change is that the notes on Volume moved from the
summary into the remarks.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
gmipf
2026-10-26 18:31:46 +01:00
committed by GitHub
parent 33caf06bc1
commit 36a67139d9
29 changed files with 1561 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Allocation Extent Descriptor (4/14.5), continues the allocation descriptors of a File Entry
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class AllocationExtentDescriptor : Descriptor
{
/// <summary>
/// Logical block of the previous allocation extent
/// </summary>
public uint PreviousAllocationExtentLocation { get; set; }
/// <summary>
/// Length of the allocation descriptors in bytes
/// </summary>
public uint LengthOfAllocationDescriptors { get; set; }
/// <summary>
/// Allocation descriptors, of the type given in the owning File Entry's ICB tag flags
/// </summary>
public byte[] AllocationDescriptors { get; set; } = [];
}
}

View File

@@ -0,0 +1,24 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Anchor Volume Descriptor Pointer (3/10.2), recorded at sector 256 (and at the end of the volume)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class AnchorVolumeDescriptorPointer : Descriptor
{
/// <summary>
/// Extent of the Main Volume Descriptor Sequence
/// </summary>
public ExtentDescriptor MainVolumeDescriptorSequenceExtent { get; set; } = new();
/// <summary>
/// Extent of the Reserve Volume Descriptor Sequence
/// </summary>
public ExtentDescriptor ReserveVolumeDescriptorSequenceExtent { get; set; } = new();
/// <summary>
/// Reserved, 480 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[480];
}
}

View File

@@ -0,0 +1,20 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Character Set Specification (charspec, 1/7.2.1)
/// UDF requires CS0 with the information "OSTA Compressed Unicode"
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class CharacterSetSpecification
{
/// <summary>
/// Character set type (0 = CS0)
/// </summary>
public byte CharacterSetType { get; set; }
/// <summary>
/// Character set information, padded with 0x00
/// </summary>
public byte[] CharacterSetInformation { get; set; } = new byte[63];
}
}

View File

@@ -0,0 +1,126 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF / ECMA-167 constant values and arrays
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
/// <see href="http://www.osta.org/specs/pdf/udf260.pdf"/>
public static class Constants
{
#region Volume Recognition Constants
/// <summary>
/// Size of a logical sector on optical media
/// </summary>
public const int SectorSize = 2048;
/// <summary>
/// Sector of the first Volume Structure Descriptor (after the 32KiB System Area)
/// </summary>
public const int VolumeRecognitionSequenceSector = 16;
/// <summary>
/// Sector of the first Anchor Volume Descriptor Pointer
/// </summary>
public const int AnchorVolumeDescriptorPointerSector = 256;
/// <summary>
/// Beginning Extended Area Descriptor identifier, "BEA01"
/// </summary>
public static readonly byte[] BeginningExtendedAreaDescriptor = [0x42, 0x45, 0x41, 0x30, 0x31];
/// <summary>
/// NSR Descriptor identifier for ECMA-167 2nd edition (UDF 1.02 to 1.50), "NSR02"
/// </summary>
public static readonly byte[] NSR02 = [0x4E, 0x53, 0x52, 0x30, 0x32];
/// <summary>
/// NSR Descriptor identifier for ECMA-167 3rd edition (UDF 2.00 and later), "NSR03"
/// </summary>
public static readonly byte[] NSR03 = [0x4E, 0x53, 0x52, 0x30, 0x33];
/// <summary>
/// Terminating Extended Area Descriptor identifier, "TEA01"
/// </summary>
public static readonly byte[] TerminatingExtendedAreaDescriptor = [0x54, 0x45, 0x41, 0x30, 0x31];
/// <summary>
/// ISO 9660 volume descriptor identifier, "CD001" (bridge discs)
/// </summary>
public static readonly byte[] CD001 = [0x43, 0x44, 0x30, 0x30, 0x31];
/// <summary>
/// Boot Descriptor identifier, "BOOT2"
/// </summary>
public static readonly byte[] BOOT2 = [0x42, 0x4F, 0x4F, 0x54, 0x32];
/// <summary>
/// ECMA-168 volume descriptor identifier, "CDW02"
/// </summary>
public static readonly byte[] CDW02 = [0x43, 0x44, 0x57, 0x30, 0x32];
#endregion
#region Partition Map Constants
/// <summary>
/// Type 1 Partition Map type
/// </summary>
public const byte Type1PartitionMap = 1;
/// <summary>
/// Type 2 Partition Map type
/// </summary>
public const byte Type2PartitionMap = 2;
/// <summary>
/// Partition type identifier of a UDF Metadata Partition (UDF 2.50 and later)
/// </summary>
public const string MetadataPartitionIdentifier = "*UDF Metadata Partition";
/// <summary>
/// Partition type identifier of a UDF Sparable Partition
/// </summary>
public const string SparablePartitionIdentifier = "*UDF Sparable Partition";
/// <summary>
/// Partition type identifier of a UDF Virtual Partition
/// </summary>
public const string VirtualPartitionIdentifier = "*UDF Virtual Partition";
#endregion
#region Character Set Constants
/// <summary>
/// OSTA Compressed Unicode, 8 bits per character
/// </summary>
public const byte CompressionId8 = 8;
/// <summary>
/// OSTA Compressed Unicode, 16 bits per character (big-endian)
/// </summary>
public const byte CompressionId16 = 16;
#endregion
#region Allocation Descriptor Constants
/// <summary>
/// Mask of the extent length in an allocation descriptor (the top two bits hold the extent type)
/// </summary>
public const uint ExtentLengthMask = 0x3FFFFFFF;
/// <summary>
/// Shift of the extent type in an allocation descriptor's extent length
/// </summary>
public const int ExtentTypeShift = 30;
/// <summary>
/// Mask of the allocation descriptor type in an ICB tag's flags
/// </summary>
public const ushort AllocationDescriptorTypeMask = 0x0007;
#endregion
}
}

View File

@@ -0,0 +1,14 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// Abstract UDF tagged descriptor, all start with a Descriptor Tag
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public abstract class Descriptor
{
/// <summary>
/// Descriptor tag
/// </summary>
public DescriptorTag DescriptorTag { get; set; } = new();
}
}

View File

@@ -0,0 +1,50 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Descriptor Tag (3/7.2), the first 16 bytes of every tagged descriptor
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class DescriptorTag
{
/// <summary>
/// Type of the descriptor
/// </summary>
public TagIdentifier TagIdentifier { get; set; }
/// <summary>
/// Version of the descriptor, 2 (ECMA-167 2nd edition) or 3 (3rd edition)
/// </summary>
public ushort DescriptorVersion { get; set; }
/// <summary>
/// Sum modulo 256 of bytes 0-3 and 5-15 of the tag
/// </summary>
public byte TagChecksum { get; set; }
/// <summary>
/// Reserved, 0x00
/// </summary>
public byte Reserved { get; set; }
/// <summary>
/// Identification of a set of descriptors
/// </summary>
public ushort TagSerialNumber { get; set; }
/// <summary>
/// CRC-CCITT of the DescriptorCRCLength bytes following the tag
/// </summary>
public ushort DescriptorCRC { get; set; }
/// <summary>
/// Number of bytes after the tag covered by the DescriptorCRC
/// </summary>
public ushort DescriptorCRCLength { get; set; }
/// <summary>
/// Number of the logical sector (volume structures) or logical block (file structures)
/// in which the descriptor is recorded
/// </summary>
public uint TagLocation { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Entity Identifier (regid, 1/7.4)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class EntityIdentifier
{
/// <summary>
/// Flags (bit 0: dirty, bit 1: protected)
/// </summary>
public byte Flags { get; set; }
/// <summary>
/// Identifier, padded with 0x00
/// </summary>
public byte[] Identifier { get; set; } = new byte[23];
/// <summary>
/// Identifier suffix (UDF domain identifiers: revision as a little-endian ushort, then domain flags)
/// </summary>
public byte[] IdentifierSuffix { get; set; } = new byte[8];
}
}

View File

@@ -0,0 +1,306 @@
using System;
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// Enum for DescriptorTag.TagIdentifier
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public enum TagIdentifier : ushort
{
/// <summary>
/// Primary Volume Descriptor (3/10.1)
/// </summary>
PRIMARY_VOLUME_DESCRIPTOR = 1,
/// <summary>
/// Anchor Volume Descriptor Pointer (3/10.2)
/// </summary>
ANCHOR_VOLUME_DESCRIPTOR_POINTER = 2,
/// <summary>
/// Volume Descriptor Pointer (3/10.3)
/// </summary>
VOLUME_DESCRIPTOR_POINTER = 3,
/// <summary>
/// Implementation Use Volume Descriptor (3/10.4)
/// </summary>
IMPLEMENTATION_USE_VOLUME_DESCRIPTOR = 4,
/// <summary>
/// Partition Descriptor (3/10.5)
/// </summary>
PARTITION_DESCRIPTOR = 5,
/// <summary>
/// Logical Volume Descriptor (3/10.6)
/// </summary>
LOGICAL_VOLUME_DESCRIPTOR = 6,
/// <summary>
/// Unallocated Space Descriptor (3/10.8)
/// </summary>
UNALLOCATED_SPACE_DESCRIPTOR = 7,
/// <summary>
/// Terminating Descriptor (3/10.9)
/// </summary>
TERMINATING_DESCRIPTOR = 8,
/// <summary>
/// Logical Volume Integrity Descriptor (3/10.10)
/// </summary>
LOGICAL_VOLUME_INTEGRITY_DESCRIPTOR = 9,
/// <summary>
/// File Set Descriptor (4/14.1)
/// </summary>
FILE_SET_DESCRIPTOR = 256,
/// <summary>
/// File Identifier Descriptor (4/14.4)
/// </summary>
FILE_IDENTIFIER_DESCRIPTOR = 257,
/// <summary>
/// Allocation Extent Descriptor (4/14.5)
/// </summary>
ALLOCATION_EXTENT_DESCRIPTOR = 258,
/// <summary>
/// Indirect Entry (4/14.7)
/// </summary>
INDIRECT_ENTRY = 259,
/// <summary>
/// Terminal Entry (4/14.8)
/// </summary>
TERMINAL_ENTRY = 260,
/// <summary>
/// File Entry (4/14.9)
/// </summary>
FILE_ENTRY = 261,
/// <summary>
/// Extended Attribute Header Descriptor (4/14.10.1)
/// </summary>
EXTENDED_ATTRIBUTE_HEADER_DESCRIPTOR = 262,
/// <summary>
/// Unallocated Space Entry (4/14.11)
/// </summary>
UNALLOCATED_SPACE_ENTRY = 263,
/// <summary>
/// Space Bitmap Descriptor (4/14.12)
/// </summary>
SPACE_BITMAP_DESCRIPTOR = 264,
/// <summary>
/// Partition Integrity Entry (4/14.13)
/// </summary>
PARTITION_INTEGRITY_ENTRY = 265,
/// <summary>
/// Extended File Entry (4/14.17)
/// </summary>
EXTENDED_FILE_ENTRY = 266,
}
/// <summary>
/// Enum for FileIdentifierDescriptor.FileCharacteristics (4/14.4.3)
/// Bits 5-7: Reserved: 0
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
[Flags]
public enum FileCharacteristics : byte
{
/// <summary>
/// No characteristics set
/// </summary>
NONE = 0x00,
/// <summary>
/// The existence of the file shall not be made known to the user (hidden)
/// </summary>
EXISTENCE = 0x01,
/// <summary>
/// The file is a directory
/// </summary>
DIRECTORY = 0x02,
/// <summary>
/// The descriptor shall be ignored (deleted)
/// </summary>
DELETED = 0x04,
/// <summary>
/// The ICB identifies the parent directory
/// </summary>
PARENT = 0x08,
/// <summary>
/// The file contains implementation use data (UDF: stream directory metadata)
/// </summary>
METADATA = 0x10,
}
/// <summary>
/// Enum for ICBTag.FileType (4/14.6.6, UDF 2.3.5.4)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public enum FileType : byte
{
/// <summary>
/// Not specified
/// </summary>
UNSPECIFIED = 0,
/// <summary>
/// Unallocated Space Entry
/// </summary>
UNALLOCATED_SPACE_ENTRY = 1,
/// <summary>
/// Partition Integrity Entry
/// </summary>
PARTITION_INTEGRITY_ENTRY = 2,
/// <summary>
/// Indirect Entry
/// </summary>
INDIRECT_ENTRY = 3,
/// <summary>
/// Directory
/// </summary>
DIRECTORY = 4,
/// <summary>
/// Sequence of bytes (a regular file)
/// </summary>
FILE = 5,
/// <summary>
/// Block special device
/// </summary>
BLOCK_DEVICE = 6,
/// <summary>
/// Character special device
/// </summary>
CHARACTER_DEVICE = 7,
/// <summary>
/// Extended attributes
/// </summary>
EXTENDED_ATTRIBUTES = 8,
/// <summary>
/// FIFO file
/// </summary>
FIFO = 9,
/// <summary>
/// Socket
/// </summary>
SOCKET = 10,
/// <summary>
/// Terminal Entry
/// </summary>
TERMINAL_ENTRY = 11,
/// <summary>
/// Symbolic link
/// </summary>
SYMBOLIC_LINK = 12,
/// <summary>
/// Stream directory
/// </summary>
STREAM_DIRECTORY = 13,
/// <summary>
/// UDF Virtual Allocation Table
/// </summary>
VIRTUAL_ALLOCATION_TABLE = 248,
/// <summary>
/// UDF Real-Time file
/// </summary>
REAL_TIME_FILE = 249,
/// <summary>
/// UDF Metadata File
/// </summary>
METADATA_FILE = 250,
/// <summary>
/// UDF Metadata Mirror File
/// </summary>
METADATA_MIRROR_FILE = 251,
/// <summary>
/// UDF Metadata Bitmap File
/// </summary>
METADATA_BITMAP_FILE = 252,
}
/// <summary>
/// Enum for the allocation descriptor type in ICBTag.Flags (4/14.6.8)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public enum AllocationDescriptorType : byte
{
/// <summary>
/// Short Allocation Descriptors (4/14.14.1)
/// </summary>
SHORT = 0,
/// <summary>
/// Long Allocation Descriptors (4/14.14.2)
/// </summary>
LONG = 1,
/// <summary>
/// Extended Allocation Descriptors (4/14.14.3)
/// </summary>
EXTENDED = 2,
/// <summary>
/// The file data is embedded in the allocation descriptors field
/// </summary>
EMBEDDED = 3,
}
/// <summary>
/// Enum for the extent type in the top two bits of an allocation descriptor's extent length (4/14.14.1.1)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public enum ExtentType : byte
{
/// <summary>
/// Extent recorded and allocated
/// </summary>
RECORDED_AND_ALLOCATED = 0,
/// <summary>
/// Extent not recorded but allocated
/// </summary>
NOT_RECORDED_BUT_ALLOCATED = 1,
/// <summary>
/// Extent not recorded and not allocated
/// </summary>
NOT_RECORDED_NOT_ALLOCATED = 2,
/// <summary>
/// The extent is the next extent of allocation descriptors
/// </summary>
NEXT_EXTENT = 3,
}
}

View File

@@ -0,0 +1,30 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Extended File Entry (4/14.17, UDF 2.00 and later), a File Entry with object size,
/// creation time and a stream directory
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class ExtendedFileEntry : FileEntry
{
/// <summary>
/// Size of the file and its streams in bytes
/// </summary>
public ulong ObjectSize { get; set; }
/// <summary>
/// Creation date and time
/// </summary>
public Timestamp CreationDateAndTime { get; set; } = new();
/// <summary>
/// Reserved, 0x00000000
/// </summary>
public uint Reserved { get; set; }
/// <summary>
/// ICB of the stream directory, length 0 if none
/// </summary>
public LongAllocationDescriptor StreamDirectoryICB { get; set; } = new();
}
}

View File

@@ -0,0 +1,19 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Extent Descriptor (extent_ad, 3/7.1), an extent of logical sectors
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class ExtentDescriptor
{
/// <summary>
/// Length of the extent in bytes
/// </summary>
public uint ExtentLength { get; set; }
/// <summary>
/// Logical sector number of the start of the extent
/// </summary>
public uint ExtentLocation { get; set; }
}
}

View File

@@ -0,0 +1,115 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF File Entry (4/14.9), the ICB describing a file or directory and where its data is recorded
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public class FileEntry : Descriptor
{
/// <summary>
/// ICB tag
/// </summary>
public ICBTag ICBTag { get; set; } = new();
/// <summary>
/// User ID
/// </summary>
public uint Uid { get; set; }
/// <summary>
/// Group ID
/// </summary>
public uint Gid { get; set; }
/// <summary>
/// Permissions
/// </summary>
public uint Permissions { get; set; }
/// <summary>
/// Number of File Identifier Descriptors identifying this ICB
/// </summary>
public ushort FileLinkCount { get; set; }
/// <summary>
/// Record format
/// </summary>
public byte RecordFormat { get; set; }
/// <summary>
/// Record display attributes
/// </summary>
public byte RecordDisplayAttributes { get; set; }
/// <summary>
/// Record length
/// </summary>
public uint RecordLength { get; set; }
/// <summary>
/// Size of the file in bytes
/// </summary>
public ulong InformationLength { get; set; }
/// <summary>
/// Number of recorded logical blocks
/// </summary>
public ulong LogicalBlocksRecorded { get; set; }
/// <summary>
/// Access date and time
/// </summary>
public Timestamp AccessDateAndTime { get; set; } = new();
/// <summary>
/// Modification date and time
/// </summary>
public Timestamp ModificationDateAndTime { get; set; } = new();
/// <summary>
/// Attribute date and time
/// </summary>
public Timestamp AttributeDateAndTime { get; set; } = new();
/// <summary>
/// Checkpoint
/// </summary>
public uint Checkpoint { get; set; }
/// <summary>
/// ICB of the extended attributes, length 0 if none
/// </summary>
public LongAllocationDescriptor ExtendedAttributeICB { get; set; } = new();
/// <summary>
/// Implementation identifier
/// </summary>
public EntityIdentifier ImplementationIdentifier { get; set; } = new();
/// <summary>
/// Unique ID
/// </summary>
public ulong UniqueID { get; set; }
/// <summary>
/// Length of the extended attributes in bytes
/// </summary>
public uint LengthOfExtendedAttributes { get; set; }
/// <summary>
/// Length of the allocation descriptors in bytes
/// </summary>
public uint LengthOfAllocationDescriptors { get; set; }
/// <summary>
/// Extended attributes
/// </summary>
public byte[] ExtendedAttributes { get; set; } = [];
/// <summary>
/// Allocation descriptors of the type given in the ICB tag flags,
/// or the file data itself if the type is AllocationDescriptorType.EMBEDDED
/// </summary>
public byte[] AllocationDescriptors { get; set; } = [];
}
}

View File

@@ -0,0 +1,49 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF File Identifier Descriptor (4/14.4), a directory entry naming a file or directory and pointing to its ICB
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class FileIdentifierDescriptor : Descriptor
{
/// <summary>
/// File version number
/// </summary>
public ushort FileVersionNumber { get; set; }
/// <summary>
/// File characteristics
/// </summary>
public FileCharacteristics FileCharacteristics { get; set; }
/// <summary>
/// Length of the file identifier in bytes
/// </summary>
public byte LengthOfFileIdentifier { get; set; }
/// <summary>
/// ICB of the file or directory
/// </summary>
public LongAllocationDescriptor ICB { get; set; } = new();
/// <summary>
/// Length of the implementation use in bytes
/// </summary>
public ushort LengthOfImplementationUse { get; set; }
/// <summary>
/// Implementation use
/// </summary>
public byte[] ImplementationUse { get; set; } = [];
/// <summary>
/// File identifier, d-characters (first byte = compression ID), empty for the parent directory
/// </summary>
public byte[] FileIdentifier { get; set; } = [];
/// <summary>
/// Padding to a multiple of 4 bytes
/// </summary>
public byte[] Padding { get; set; } = [];
}
}

View File

@@ -0,0 +1,99 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF File Set Descriptor (4/14.1), the location of the root directory
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class FileSetDescriptor : Descriptor
{
/// <summary>
/// Recording date and time
/// </summary>
public Timestamp RecordingDateAndTime { get; set; } = new();
/// <summary>
/// Interchange level
/// </summary>
public ushort InterchangeLevel { get; set; }
/// <summary>
/// Maximum interchange level
/// </summary>
public ushort MaximumInterchangeLevel { get; set; }
/// <summary>
/// Character set list
/// </summary>
public uint CharacterSetList { get; set; }
/// <summary>
/// Maximum character set list
/// </summary>
public uint MaximumCharacterSetList { get; set; }
/// <summary>
/// File set number
/// </summary>
public uint FileSetNumber { get; set; }
/// <summary>
/// File Set Descriptor number
/// </summary>
public uint FileSetDescriptorNumber { get; set; }
/// <summary>
/// Logical volume identifier character set
/// </summary>
public CharacterSetSpecification LogicalVolumeIdentifierCharacterSet { get; set; } = new();
/// <summary>
/// Logical volume identifier, dstring (last byte = length)
/// </summary>
public byte[] LogicalVolumeIdentifier { get; set; } = new byte[128];
/// <summary>
/// File set character set
/// </summary>
public CharacterSetSpecification FileSetCharacterSet { get; set; } = new();
/// <summary>
/// File set identifier, dstring (last byte = length)
/// </summary>
public byte[] FileSetIdentifier { get; set; } = new byte[32];
/// <summary>
/// Copyright file identifier, dstring (last byte = length)
/// </summary>
public byte[] CopyrightFileIdentifier { get; set; } = new byte[32];
/// <summary>
/// Abstract file identifier, dstring (last byte = length)
/// </summary>
public byte[] AbstractFileIdentifier { get; set; } = new byte[32];
/// <summary>
/// ICB of the root directory
/// </summary>
public LongAllocationDescriptor RootDirectoryICB { get; set; } = new();
/// <summary>
/// Domain identifier
/// </summary>
public EntityIdentifier DomainIdentifier { get; set; } = new();
/// <summary>
/// Next extent of File Set Descriptors, length 0 if none
/// </summary>
public LongAllocationDescriptor NextExtent { get; set; } = new();
/// <summary>
/// ICB of the system stream directory (ECMA-167 3rd edition), length 0 if none
/// </summary>
public LongAllocationDescriptor SystemStreamDirectoryICB { get; set; } = new();
/// <summary>
/// Reserved, 32 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[32];
}
}

View File

@@ -0,0 +1,15 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF tagged descriptor of a type that is not parsed further
/// (e.g. Implementation Use Volume Descriptor, Unallocated Space Descriptor, Terminating Descriptor)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class GenericDescriptor : Descriptor
{
/// <summary>
/// Contents of the descriptor after the tag, up to the end of the logical sector
/// </summary>
public byte[] Data { get; set; } = [];
}
}

View File

@@ -0,0 +1,49 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Information Control Block Tag (icbtag, 4/14.6)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class ICBTag
{
/// <summary>
/// Prior recorded number of direct entries
/// </summary>
public uint PriorRecordedNumberOfDirectEntries { get; set; }
/// <summary>
/// Strategy type (UDF: 4, or 4096 for write-once media)
/// </summary>
public ushort StrategyType { get; set; }
/// <summary>
/// Strategy parameter, 2 bytes
/// </summary>
public byte[] StrategyParameter { get; set; } = new byte[2];
/// <summary>
/// Maximum number of entries
/// </summary>
public ushort MaximumNumberOfEntries { get; set; }
/// <summary>
/// Reserved, 0x00
/// </summary>
public byte Reserved { get; set; }
/// <summary>
/// File type
/// </summary>
public FileType FileType { get; set; }
/// <summary>
/// Parent ICB location
/// </summary>
public LogicalBlockAddress ParentICBLocation { get; set; } = new();
/// <summary>
/// Flags, the lower 3 bits hold the AllocationDescriptorType
/// </summary>
public ushort Flags { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Recorded Address (lb_addr, 4/7.1), a logical block within a partition
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class LogicalBlockAddress
{
/// <summary>
/// Logical block number, relative to the start of the partition
/// </summary>
public uint LogicalBlockNumber { get; set; }
/// <summary>
/// Index of the partition map in the Logical Volume Descriptor
/// </summary>
public ushort PartitionReferenceNumber { get; set; }
}
}

View File

@@ -0,0 +1,69 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Logical Volume Descriptor (3/10.6), block size, partition maps and the location of the File Set Descriptor
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class LogicalVolumeDescriptor : Descriptor
{
/// <summary>
/// Volume Descriptor Sequence Number, the highest number prevails
/// </summary>
public uint VolumeDescriptorSequenceNumber { get; set; }
/// <summary>
/// Descriptor character set
/// </summary>
public CharacterSetSpecification DescriptorCharacterSet { get; set; } = new();
/// <summary>
/// Logical volume identifier, dstring (last byte = length)
/// </summary>
public byte[] LogicalVolumeIdentifier { get; set; } = new byte[128];
/// <summary>
/// Size of a logical block in bytes
/// </summary>
public uint LogicalBlockSize { get; set; }
/// <summary>
/// Domain identifier ("*OSTA UDF Compliant", suffix holds the UDF revision)
/// </summary>
public EntityIdentifier DomainIdentifier { get; set; } = new();
/// <summary>
/// Logical volume contents use (UDF: extent of the File Set Descriptor)
/// </summary>
public LongAllocationDescriptor LogicalVolumeContentsUse { get; set; } = new();
/// <summary>
/// Length of the partition maps in bytes
/// </summary>
public uint MapTableLength { get; set; }
/// <summary>
/// Number of partition maps
/// </summary>
public uint NumberOfPartitionMaps { get; set; }
/// <summary>
/// Implementation identifier
/// </summary>
public EntityIdentifier ImplementationIdentifier { get; set; } = new();
/// <summary>
/// Implementation use, 128 bytes
/// </summary>
public byte[] ImplementationUse { get; set; } = new byte[128];
/// <summary>
/// Extent of the Logical Volume Integrity Sequence
/// </summary>
public ExtentDescriptor IntegritySequenceExtent { get; set; } = new();
/// <summary>
/// Partition maps, a partition reference number is the index into this array
/// </summary>
public PartitionMap[] PartitionMaps { get; set; } = [];
}
}

View File

@@ -0,0 +1,25 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Long Allocation Descriptor (long_ad, 4/14.14.2), an extent within any partition of the logical volume
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class LongAllocationDescriptor
{
/// <summary>
/// Length of the extent in bytes (lower 30 bits) and the ExtentType (upper 2 bits)
/// A length of 0 terminates a sequence of allocation descriptors
/// </summary>
public uint ExtentLength { get; set; }
/// <summary>
/// Logical block address of the start of the extent
/// </summary>
public LogicalBlockAddress ExtentLocation { get; set; } = new();
/// <summary>
/// Implementation use (UDF: flags and unique ID bits)
/// </summary>
public byte[] ImplementationUse { get; set; } = new byte[6];
}
}

View File

@@ -0,0 +1,45 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Metadata Partition Map (UDF 2.2.10, UDF 2.50 and later), a partition whose logical blocks
/// are the contents of the Metadata File recorded in the underlying partition
/// </summary>
/// <see href="http://www.osta.org/specs/pdf/udf260.pdf"/>
public sealed class MetadataPartitionMap : Type2PartitionMap
{
/// <summary>
/// Logical block of the Metadata File's File Entry in the underlying partition
/// </summary>
public uint MetadataFileLocation { get; set; }
/// <summary>
/// Logical block of the Metadata Mirror File's File Entry in the underlying partition
/// </summary>
public uint MetadataMirrorFileLocation { get; set; }
/// <summary>
/// Logical block of the Metadata Bitmap File's File Entry, 0xFFFFFFFF if not recorded
/// </summary>
public uint MetadataBitmapFileLocation { get; set; }
/// <summary>
/// Allocation unit size in logical blocks
/// </summary>
public uint AllocationUnitSize { get; set; }
/// <summary>
/// Alignment unit size in logical blocks
/// </summary>
public ushort AlignmentUnitSize { get; set; }
/// <summary>
/// Flags (bit 0: duplicate metadata flag)
/// </summary>
public byte Flags { get; set; }
/// <summary>
/// Reserved, 5 bytes of 0x00
/// </summary>
public byte[] Reserved2 { get; set; } = new byte[5];
}
}

View File

@@ -0,0 +1,64 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Partition Descriptor (3/10.5), the physical location of a partition
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class PartitionDescriptor : Descriptor
{
/// <summary>
/// Volume Descriptor Sequence Number, the highest number prevails
/// </summary>
public uint VolumeDescriptorSequenceNumber { get; set; }
/// <summary>
/// Partition flags (bit 0: space is allocated)
/// </summary>
public ushort PartitionFlags { get; set; }
/// <summary>
/// Partition number, referenced by the partition maps
/// </summary>
public ushort PartitionNumber { get; set; }
/// <summary>
/// Partition contents ("+NSR02" or "+NSR03" for UDF)
/// </summary>
public EntityIdentifier PartitionContents { get; set; } = new();
/// <summary>
/// Partition contents use (Partition Header Descriptor for NSR), 128 bytes
/// </summary>
public byte[] PartitionContentsUse { get; set; } = new byte[128];
/// <summary>
/// Access type (1 = read-only, 2 = write-once, 3 = rewritable, 4 = overwritable)
/// </summary>
public uint AccessType { get; set; }
/// <summary>
/// Logical sector number of the first sector of the partition
/// </summary>
public uint PartitionStartingLocation { get; set; }
/// <summary>
/// Length of the partition in logical sectors
/// </summary>
public uint PartitionLength { get; set; }
/// <summary>
/// Implementation identifier
/// </summary>
public EntityIdentifier ImplementationIdentifier { get; set; } = new();
/// <summary>
/// Implementation use, 128 bytes
/// </summary>
public byte[] ImplementationUse { get; set; } = new byte[128];
/// <summary>
/// Reserved, 156 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[156];
}
}

View File

@@ -0,0 +1,19 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// Abstract UDF Partition Map (3/10.7)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public abstract class PartitionMap
{
/// <summary>
/// Partition map type (1 or 2)
/// </summary>
public byte PartitionMapType { get; set; }
/// <summary>
/// Length of the partition map in bytes
/// </summary>
public byte PartitionMapLength { get; set; }
}
}

View File

@@ -0,0 +1,114 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Primary Volume Descriptor (3/10.1)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class PrimaryVolumeDescriptor : Descriptor
{
/// <summary>
/// Volume Descriptor Sequence Number, the highest number prevails
/// </summary>
public uint VolumeDescriptorSequenceNumber { get; set; }
/// <summary>
/// Primary Volume Descriptor Number
/// </summary>
public uint PrimaryVolumeDescriptorNumber { get; set; }
/// <summary>
/// Volume identifier, dstring (last byte = length)
/// </summary>
public byte[] VolumeIdentifier { get; set; } = new byte[32];
/// <summary>
/// Volume sequence number
/// </summary>
public ushort VolumeSequenceNumber { get; set; }
/// <summary>
/// Maximum volume sequence number
/// </summary>
public ushort MaximumVolumeSequenceNumber { get; set; }
/// <summary>
/// Interchange level
/// </summary>
public ushort InterchangeLevel { get; set; }
/// <summary>
/// Maximum interchange level
/// </summary>
public ushort MaximumInterchangeLevel { get; set; }
/// <summary>
/// Character set list
/// </summary>
public uint CharacterSetList { get; set; }
/// <summary>
/// Maximum character set list
/// </summary>
public uint MaximumCharacterSetList { get; set; }
/// <summary>
/// Volume set identifier, dstring (last byte = length)
/// </summary>
public byte[] VolumeSetIdentifier { get; set; } = new byte[128];
/// <summary>
/// Descriptor character set
/// </summary>
public CharacterSetSpecification DescriptorCharacterSet { get; set; } = new();
/// <summary>
/// Explanatory character set
/// </summary>
public CharacterSetSpecification ExplanatoryCharacterSet { get; set; } = new();
/// <summary>
/// Extent of the volume abstract
/// </summary>
public ExtentDescriptor VolumeAbstract { get; set; } = new();
/// <summary>
/// Extent of the volume copyright notice
/// </summary>
public ExtentDescriptor VolumeCopyrightNotice { get; set; } = new();
/// <summary>
/// Application identifier
/// </summary>
public EntityIdentifier ApplicationIdentifier { get; set; } = new();
/// <summary>
/// Recording date and time
/// </summary>
public Timestamp RecordingDateAndTime { get; set; } = new();
/// <summary>
/// Implementation identifier
/// </summary>
public EntityIdentifier ImplementationIdentifier { get; set; } = new();
/// <summary>
/// Implementation use, 64 bytes
/// </summary>
public byte[] ImplementationUse { get; set; } = new byte[64];
/// <summary>
/// Predecessor Volume Descriptor Sequence location
/// </summary>
public uint PredecessorVolumeDescriptorSequenceLocation { get; set; }
/// <summary>
/// Flags (bit 0: volume set identification is common to all volumes of the set)
/// </summary>
public ushort Flags { get; set; }
/// <summary>
/// Reserved, 22 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[22];
}
}

View File

@@ -0,0 +1,20 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Short Allocation Descriptor (short_ad, 4/14.14.1), an extent within the partition of the descriptor
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class ShortAllocationDescriptor
{
/// <summary>
/// Length of the extent in bytes (lower 30 bits) and the ExtentType (upper 2 bits)
/// A length of 0 terminates a sequence of allocation descriptors
/// </summary>
public uint ExtentLength { get; set; }
/// <summary>
/// Logical block number of the start of the extent
/// </summary>
public uint ExtentPosition { get; set; }
}
}

View File

@@ -0,0 +1,60 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Timestamp (1/7.3)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class Timestamp
{
/// <summary>
/// Type (upper 4 bits, 1 = local time) and time zone offset from UTC in minutes
/// (lower 12 bits, signed, -2047 = not specified)
/// </summary>
public ushort TypeAndTimezone { get; set; }
/// <summary>
/// Year, 1 to 9999
/// </summary>
public short Year { get; set; }
/// <summary>
/// Month, 1 to 12
/// </summary>
public byte Month { get; set; }
/// <summary>
/// Day, 1 to 31
/// </summary>
public byte Day { get; set; }
/// <summary>
/// Hour, 0 to 23
/// </summary>
public byte Hour { get; set; }
/// <summary>
/// Minute, 0 to 59
/// </summary>
public byte Minute { get; set; }
/// <summary>
/// Second, 0 to 59
/// </summary>
public byte Second { get; set; }
/// <summary>
/// Centiseconds, 0 to 99
/// </summary>
public byte Centiseconds { get; set; }
/// <summary>
/// Hundreds of microseconds, 0 to 99
/// </summary>
public byte HundredsOfMicroseconds { get; set; }
/// <summary>
/// Microseconds, 0 to 99
/// </summary>
public byte Microseconds { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Type 1 Partition Map (3/10.7.2), logical blocks map 1:1 onto a Partition Descriptor's sectors
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class Type1PartitionMap : PartitionMap
{
/// <summary>
/// Volume sequence number of the volume containing the partition
/// </summary>
public ushort VolumeSequenceNumber { get; set; }
/// <summary>
/// Partition number of the Partition Descriptor
/// </summary>
public ushort PartitionNumber { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Type 2 Partition Map (3/10.7.3, UDF 2.2.8-2.2.10), a partition identified by its partition type identifier
/// (e.g. "*UDF Virtual Partition", "*UDF Sparable Partition", "*UDF Metadata Partition")
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
/// <see href="http://www.osta.org/specs/pdf/udf260.pdf"/>
public class Type2PartitionMap : PartitionMap
{
/// <summary>
/// Reserved, 2 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[2];
/// <summary>
/// Partition type identifier
/// </summary>
public EntityIdentifier PartitionTypeIdentifier { get; set; } = new();
/// <summary>
/// Volume sequence number of the volume containing the partition
/// </summary>
public ushort VolumeSequenceNumber { get; set; }
/// <summary>
/// Partition number of the underlying Partition Descriptor
/// </summary>
public ushort PartitionNumber { get; set; }
/// <summary>
/// Partition type specific data after the partition number, 24 bytes
/// Empty for partition types that are parsed further (MetadataPartitionMap)
/// </summary>
public byte[] PartitionTypeData { get; set; } = [];
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// A volume (or disc image) with a UDF / ECMA-167 file system
/// </summary>
/// <remarks>
/// Logical sectors and logical blocks are 2048 bytes on optical media
/// File system structures are addressed by logical block address (partition reference number + logical block);
/// dictionaries are keyed by ((long)PartitionReferenceNumber &lt;&lt; 32) | LogicalBlockNumber
/// </remarks>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
/// <see href="http://www.osta.org/specs/pdf/udf260.pdf"/>
public sealed class Volume
{
/// <summary>
/// Volume Recognition Sequence starting at sector 16
/// </summary>
public VolumeStructureDescriptor[] VolumeRecognitionSequence { get; set; } = [];
/// <summary>
/// Anchor Volume Descriptor Pointer
/// </summary>
public AnchorVolumeDescriptorPointer? AnchorVolumeDescriptorPointer { get; set; }
/// <summary>
/// Volume Descriptor Sequence (Primary, Partition, Logical Volume, ... up to the Terminating Descriptor)
/// </summary>
public Descriptor[] VolumeDescriptorSequence { get; set; } = [];
/// <summary>
/// File Set Descriptor
/// </summary>
public FileSetDescriptor? FileSetDescriptor { get; set; }
/// <summary>
/// File Entries (and Extended File Entries) by logical block address
/// </summary>
public Dictionary<long, FileEntry> FileEntries { get; set; } = [];
/// <summary>
/// Allocation Extent Descriptors by logical block address
/// </summary>
public Dictionary<long, AllocationExtentDescriptor> AllocationExtentDescriptors { get; set; } = [];
/// <summary>
/// File Identifier Descriptors of each directory, by logical block address of the directory's File Entry
/// </summary>
public Dictionary<long, FileIdentifierDescriptor[]> Directories { get; set; } = [];
}
}

View File

@@ -0,0 +1,24 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Volume Descriptor Pointer (3/10.3), continues the Volume Descriptor Sequence in another extent
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class VolumeDescriptorPointer : Descriptor
{
/// <summary>
/// Volume Descriptor Sequence Number
/// </summary>
public uint VolumeDescriptorSequenceNumber { get; set; }
/// <summary>
/// Extent of the next part of the Volume Descriptor Sequence
/// </summary>
public ExtentDescriptor NextVolumeDescriptorSequenceExtent { get; set; } = new();
/// <summary>
/// Reserved, 484 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[484];
}
}

View File

@@ -0,0 +1,30 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Volume Structure Descriptor (2/9.1), one logical sector of the Volume Recognition Sequence
/// starting at sector 16, e.g. "BEA01", "NSR02"/"NSR03", "TEA01", or "CD001" on bridge discs
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class VolumeStructureDescriptor
{
/// <summary>
/// Structure type
/// </summary>
public byte StructureType { get; set; }
/// <summary>
/// Standard identifier, 5 bytes
/// </summary>
public byte[] StandardIdentifier { get; set; } = new byte[5];
/// <summary>
/// Structure version
/// </summary>
public byte StructureVersion { get; set; }
/// <summary>
/// Structure data, the rest of the logical sector
/// </summary>
public byte[] StructureData { get; set; } = [];
}
}