Skip to content

Commit 44a28cf

Browse files
committed
Basic support for reading Joliet extensions to ISO-9660
1 parent 2e95e83 commit 44a28cf

8 files changed

Lines changed: 109 additions & 19 deletions

TotalImage.IO/FileSystems/ISO/Iso9660FileSystem.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public class Iso9660FileSystem : FileSystem
2626
public IsoPrimaryVolumeDescriptor PrimaryVolumeDescriptor
2727
=> VolumeDescriptors
2828
.OfType<IsoPrimaryVolumeDescriptor>()
29+
.OrderByDescending(e => e.IsJolietVolumeDescriptor)
30+
.ThenByDescending(e => e.Type == IsoVolumeDescriptorType.PrimaryVolumeDescriptor)
2931
.First();
3032

3133
/// <summary>
@@ -57,6 +59,8 @@ public Iso9660FileSystem(Stream containerStream) : base(containerStream)
5759

5860
IsoPrimaryVolumeDescriptor? primaryDescriptor = VolumeDescriptors
5961
.OfType<IsoPrimaryVolumeDescriptor>()
62+
.OrderByDescending(e => e.IsJolietVolumeDescriptor)
63+
.ThenByDescending(e => e.Type == IsoVolumeDescriptorType.PrimaryVolumeDescriptor)
6064
.FirstOrDefault();
6165
if (primaryDescriptor == null)
6266
{

TotalImage.IO/FileSystems/ISO/IsoDirectory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public override IEnumerable<FileSystemObject> EnumerateFileSystemObjects(bool sh
6666
continue;
6767
}
6868

69-
var record = new IsoFileSystemObject(records[nextRecord..(nextRecord + recordLength)]);
69+
var record = new IsoFileSystemObject(records[nextRecord..(nextRecord + recordLength)], fileSystem.PrimaryVolumeDescriptor.IsJolietVolumeDescriptor);
7070

7171
// A record whose identifier is a single zero byte is the current directory
7272
// A record whose identifier is a single one byte is either the parent directory or the root directory if it is the root directory

TotalImage.IO/FileSystems/ISO/IsoFile.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ public override Stream GetStream()
8080
/// </summary>
8181
/// <param name="fso">The file system record</param>
8282
/// <param name="fileSystem">The file system containing the file</param>
83-
/// <param name="parent">The parent directory, if any</param>
84-
public IsoFile(IsoFileSystemObject fso, FileSystem fileSystem, Directory? parent = null) : base(fileSystem, parent)
83+
/// <param name="parent">The parent directory</param>
84+
public IsoFile(IsoFileSystemObject fso, FileSystem fileSystem, Directory parent) : base(fileSystem, parent)
8585
{
8686
Record = fso;
8787
}

TotalImage.IO/FileSystems/ISO/IsoFileSystemObject.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ public class IsoFileSystemObject
8383
/// Create an ISO 9660 directory record
8484
/// </summary>
8585
/// <param name="record">A span containing the directory record</param>
86+
/// <param name="isUnicode">Indicating whether the record should be treated as unicode</param>
8687
/// <exception cref="ArgumentOutOfRangeException">Thrown if the span provided does not cover the entire record</exception>
87-
public IsoFileSystemObject(in ReadOnlySpan<byte> record)
88+
public IsoFileSystemObject(in ReadOnlySpan<byte> record, bool isUnicode)
8889
{
8990
if (record[0] > record.Length)
9091
{
@@ -102,9 +103,22 @@ public IsoFileSystemObject(in ReadOnlySpan<byte> record)
102103
VolumeSequenceNumber = IsoUtilities.ReadUInt16MultiEndian(record[28..32]);
103104
FileIdentifierLength = record[32];
104105

105-
char[] textBuffer = new char[FileIdentifierLength];
106-
Encoding.ASCII.GetChars(record[33..(33 + FileIdentifierLength)], textBuffer);
107-
FileIdentifier = new string(textBuffer);
106+
var nameBytes = record[33..(33 + FileIdentifierLength)];
107+
if (nameBytes.Length == 1 && nameBytes[0] == 0)
108+
{
109+
FileIdentifier = "\u0000";
110+
}
111+
else if (nameBytes.Length == 1 && nameBytes[0] == 1)
112+
{
113+
FileIdentifier = "\u0001";
114+
}
115+
else
116+
{
117+
Encoding encoding = isUnicode ? Encoding.BigEndianUnicode : Encoding.ASCII;
118+
char[] textBuffer = new char[encoding.GetCharCount(nameBytes)];
119+
encoding.GetChars(nameBytes, textBuffer);
120+
FileIdentifier = new string(textBuffer);
121+
}
108122

109123
int systemUseStart = 33 + FileIdentifierLength;
110124
if (systemUseStart % 2 == 1) systemUseStart++; // account for padding field

TotalImage.IO/FileSystems/ISO/IsoPrimaryVolumeDescriptor.cs

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Buffers.Binary;
33
using System.Collections.Immutable;
44
using System.IO;
5+
using System.Linq;
56
using System.Text;
67

78
namespace TotalImage.FileSystems.ISO
@@ -13,6 +14,13 @@ public class IsoPrimaryVolumeDescriptor : IsoVolumeDescriptor
1314
{
1415
private static readonly char[] trimCharacters = new char[] { '\0', ' ' };
1516

17+
private static readonly byte[][] jolietEscapeSequences = new byte[][]
18+
{
19+
new byte[] { 0x25, 0x2f, 0x40 }, // Level 1
20+
new byte[] { 0x25, 0x2f, 0x43 }, // Level 2
21+
new byte[] { 0x25, 0x2f, 0x45 } // Level 3
22+
};
23+
1624
/// <summary>
1725
/// The system that can read the first 16 sectors of the image
1826
/// </summary>
@@ -138,6 +146,11 @@ public class IsoPrimaryVolumeDescriptor : IsoVolumeDescriptor
138146
/// </summary>
139147
public ImmutableArray<byte> ApplicationContent { get; }
140148

149+
/// <summary>
150+
/// Indicates whether the volume descriptor is a Joliet secondary volume descriptor
151+
/// </summary>
152+
public bool IsJolietVolumeDescriptor { get; }
153+
141154
/// <summary>
142155
/// Create an ISO 9660 primary volume descriptor
143156
/// </summary>
@@ -148,12 +161,31 @@ public class IsoPrimaryVolumeDescriptor : IsoVolumeDescriptor
148161
public IsoPrimaryVolumeDescriptor(in ReadOnlySpan<byte> record, in IsoVolumeDescriptorType type, in ImmutableArray<byte> identifier, in byte version)
149162
: base(type, identifier, version)
150163
{
164+
bool isUnicode = false;
165+
if (type == IsoVolumeDescriptorType.SupplementaryVolumeDescriptor)
166+
{
167+
for (int i = 0; i < jolietEscapeSequences.Length; i++)
168+
{
169+
isUnicode = record[88..91].SequenceEqual(jolietEscapeSequences[i].AsSpan());
170+
if (isUnicode)
171+
{
172+
break;
173+
}
174+
}
175+
}
176+
177+
IsJolietVolumeDescriptor = isUnicode;
178+
179+
Encoding encoding = isUnicode
180+
? Encoding.BigEndianUnicode
181+
: Encoding.ASCII;
182+
151183
char[] textBuffer = new char[32];
152184

153-
Encoding.ASCII.GetChars(record[8..40], textBuffer);
185+
encoding.GetChars(record[8..40], textBuffer);
154186
SystemIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
155187

156-
Encoding.ASCII.GetChars(record[40..72], textBuffer);
188+
encoding.GetChars(record[40..72], textBuffer);
157189
VolumeIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
158190

159191
VolumeSpace = IsoUtilities.ReadUInt32MultiEndian(record[80..88]);
@@ -170,31 +202,31 @@ public IsoPrimaryVolumeDescriptor(in ReadOnlySpan<byte> record, in IsoVolumeDesc
170202
MPathTableOffset = BinaryPrimitives.ReadUInt32BigEndian(record[148..152]);
171203
MPathTableOffset = BinaryPrimitives.ReadUInt32BigEndian(record[152..156]);
172204

173-
RootDirectory = new IsoFileSystemObject(record[156..190]);
205+
RootDirectory = new IsoFileSystemObject(record[156..190], isUnicode);
174206

175207
textBuffer = new char[128];
176208

177-
Encoding.ASCII.GetChars(record[190..318], textBuffer);
209+
encoding.GetChars(record[190..318], textBuffer);
178210
VolumeSetIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
179211

180-
Encoding.ASCII.GetChars(record[318..446], textBuffer);
212+
encoding.GetChars(record[318..446], textBuffer);
181213
PublisherIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
182214

183-
Encoding.ASCII.GetChars(record[446..574], textBuffer);
215+
encoding.GetChars(record[446..574], textBuffer);
184216
DataPreparerIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
185217

186-
Encoding.ASCII.GetChars(record[574..702], textBuffer);
218+
encoding.GetChars(record[574..702], textBuffer);
187219
ApplicationIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
188220

189221
textBuffer = new char[37];
190222

191-
Encoding.ASCII.GetChars(record[702..739], textBuffer);
223+
encoding.GetChars(record[702..739], textBuffer);
192224
CopyrightFileIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
193225

194-
Encoding.ASCII.GetChars(record[739..776], textBuffer);
226+
encoding.GetChars(record[739..776], textBuffer);
195227
AbstractFileIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
196228

197-
Encoding.ASCII.GetChars(record[776..813], textBuffer);
229+
encoding.GetChars(record[776..813], textBuffer);
198230
BibliographicFileIdentifier = textBuffer.AsSpan().TrimEnd(trimCharacters).ToString();
199231

200232
textBuffer = new char[17];
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
4+
namespace TotalImage.FileSystems.ISO
5+
{
6+
/// <summary>
7+
/// Represents an ISO 9660 secondary volume descriptor
8+
/// </summary>
9+
public class IsoSecondaryVolumeDescriptor : IsoPrimaryVolumeDescriptor
10+
{
11+
/// <summary>
12+
/// This field specifies certain volume characteristics. Currently no standardised flags.
13+
/// </summary>
14+
public byte VolumeFlags { get; }
15+
16+
/// <summary>
17+
/// One or more escape sequences encoded according to ISO-2022
18+
/// </summary>
19+
public ImmutableArray<byte> EscapeSequences { get; }
20+
21+
/// <summary>
22+
/// Create an ISO 9660 secondary volume descriptor
23+
/// </summary>
24+
/// <param name="record">A span containing the volume descriptor record</param>
25+
/// <param name="type">The type of the volume descriptor</param>
26+
/// <param name="identifier">The volume descriptor identifier</param>
27+
/// <param name="version">The version of the volume descriptor</param>
28+
public IsoSecondaryVolumeDescriptor(in ReadOnlySpan<byte> record, in IsoVolumeDescriptorType type, in ImmutableArray<byte> identifier, in byte version)
29+
: base(record, type, identifier, version)
30+
{
31+
VolumeFlags = record[7];
32+
EscapeSequences = record[88..120].ToArray().ToImmutableArray();
33+
}
34+
}
35+
}

TotalImage.IO/FileSystems/ISO/IsoUtilities.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@ public static class IsoUtilities
2121
throw new ArgumentOutOfRangeException(nameof(date));
2222
}
2323

24+
if (date[0] == '\u0000')
25+
{
26+
return null;
27+
}
28+
2429
bool success = int.TryParse(date[0..4], out int year);
2530
success &= int.TryParse(date[4..6], out int month);
2631
success &= int.TryParse(date[6..8], out int day);
2732
success &= int.TryParse(date[8..10], out int hour);
2833
success &= int.TryParse(date[10..12], out int minute);
2934
success &= int.TryParse(date[12..14], out int second);
30-
success &= int.TryParse(date[14..16], out int hundredths);
35+
int.TryParse(date[14..16], out int hundredths); // not required for successful parsing
3136

3237
if (!success)
3338
{

TotalImage.IO/FileSystems/ISO/IsoVolumeDescriptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public static IsoVolumeDescriptor ReadVolumeDescriptor(in ReadOnlySpan<byte> rec
6868
{
6969
IsoVolumeDescriptorType.PrimaryVolumeDescriptor => new IsoPrimaryVolumeDescriptor(record, type, identifier, version),
7070
IsoVolumeDescriptorType.BootRecord => new IsoBootVolumeDescriptor(record, type, identifier, version),
71-
IsoVolumeDescriptorType.SupplementaryVolumeDescriptor => new IsoUnknownVolumeDescriptor(record, type, identifier, version),
71+
IsoVolumeDescriptorType.SupplementaryVolumeDescriptor => new IsoSecondaryVolumeDescriptor(record, type, identifier, version),
7272
IsoVolumeDescriptorType.VolumePartitionDescriptor => new IsoPartitionVolumeDescriptor(record, type, identifier, version),
7373
IsoVolumeDescriptorType.VolumeDescriptorSetTerminator => new IsoSetTerminatorVolumeDescriptor(type, identifier, version),
7474
_ => new IsoUnknownVolumeDescriptor(record, type, identifier, version),

0 commit comments

Comments
 (0)