using System.Collections.Generic;
using System.Text;
using SabreTools.Data.Extensions;
using SabreTools.Data.Models.UDF;
using SabreTools.Text.Extensions;
namespace SabreTools.Wrappers
{
public partial class UDF : IPrintable
{
#if NETCOREAPP
///
public string ExportJSON(bool recursive) => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
#else
///
public string ExportJSON(bool recursive) => Newtonsoft.Json.JsonConvert.SerializeObject(Model, _jsonSerializerOptions);
#endif
///
public void PrintInformation(StringBuilder builder, bool recursive)
{
builder.AppendLine("UDF Information:");
builder.AppendLine("-------------------------");
builder.AppendLine();
Print(builder, Model.VolumeRecognitionSequence);
Print(builder, Model.AnchorVolumeDescriptorPointer);
Print(builder, Model.VolumeDescriptorSequence);
Print(builder, Model.FileSetDescriptor);
if (Model.FileSetDescriptor is not null)
{
builder.AppendLine(" Directory Hierarchy:");
builder.AppendLine(" -------------------------");
long root = Model.FileSetDescriptor.RootDirectoryICB.ExtentLocation.GetKey();
PrintDirectory(builder, root, "\\", new HashSet { root });
builder.AppendLine();
}
}
private static void Print(StringBuilder builder, VolumeStructureDescriptor[] sequence)
{
builder.AppendLine(" Volume Recognition Sequence:");
builder.AppendLine(" -------------------------");
if (sequence.Length == 0)
{
builder.AppendLine(" No volume structure descriptors");
builder.AppendLine();
return;
}
for (int i = 0; i < sequence.Length; i++)
{
var vsd = sequence[i];
builder.AppendLine($" Volume Structure Descriptor {i}:");
builder.AppendLine(vsd.StructureType, " Structure Type");
builder.AppendLine(Encoding.ASCII.GetString(vsd.StandardIdentifier), " Standard Identifier");
builder.AppendLine(vsd.StructureVersion, " Structure Version");
}
builder.AppendLine();
}
private static void Print(StringBuilder builder, AnchorVolumeDescriptorPointer? anchor)
{
builder.AppendLine(" Anchor Volume Descriptor Pointer:");
builder.AppendLine(" -------------------------");
if (anchor is null)
{
builder.AppendLine(" No anchor volume descriptor pointer");
builder.AppendLine();
return;
}
Print(builder, anchor.DescriptorTag, " ");
Print(builder, anchor.MainVolumeDescriptorSequenceExtent, " Main Volume Descriptor Sequence");
Print(builder, anchor.ReserveVolumeDescriptorSequenceExtent, " Reserve Volume Descriptor Sequence");
builder.AppendLine();
}
private static void Print(StringBuilder builder, Descriptor[] sequence)
{
builder.AppendLine(" Volume Descriptor Sequence:");
builder.AppendLine(" -------------------------");
if (sequence.Length == 0)
{
builder.AppendLine(" No volume descriptors");
builder.AppendLine();
return;
}
foreach (var descriptor in sequence)
{
builder.AppendLine($" {descriptor.DescriptorTag.TagIdentifier} (Sector {descriptor.DescriptorTag.TagLocation}):");
Print(builder, descriptor.DescriptorTag, " ");
switch (descriptor)
{
case PrimaryVolumeDescriptor pvd:
builder.AppendLine(pvd.VolumeDescriptorSequenceNumber, " Volume Descriptor Sequence Number");
builder.AppendLine(pvd.PrimaryVolumeDescriptorNumber, " Primary Volume Descriptor Number");
builder.AppendLine(pvd.VolumeIdentifier.DecodeDString(), " Volume Identifier");
builder.AppendLine(pvd.VolumeSequenceNumber, " Volume Sequence Number");
builder.AppendLine(pvd.MaximumVolumeSequenceNumber, " Maximum Volume Sequence Number");
builder.AppendLine(pvd.InterchangeLevel, " Interchange Level");
builder.AppendLine(pvd.MaximumInterchangeLevel, " Maximum Interchange Level");
builder.AppendLine(pvd.VolumeSetIdentifier.DecodeDString(), " Volume Set Identifier");
Print(builder, pvd.ApplicationIdentifier, " Application Identifier");
builder.AppendLine(Format(pvd.RecordingDateAndTime), " Recording Date and Time");
Print(builder, pvd.ImplementationIdentifier, " Implementation Identifier");
builder.AppendLine(pvd.Flags, " Flags");
break;
case PartitionDescriptor pd:
builder.AppendLine(pd.VolumeDescriptorSequenceNumber, " Volume Descriptor Sequence Number");
builder.AppendLine(pd.PartitionFlags, " Partition Flags");
builder.AppendLine(pd.PartitionNumber, " Partition Number");
Print(builder, pd.PartitionContents, " Partition Contents");
builder.AppendLine(pd.AccessType, " Access Type");
builder.AppendLine(pd.PartitionStartingLocation, " Partition Starting Location");
builder.AppendLine(pd.PartitionLength, " Partition Length");
Print(builder, pd.ImplementationIdentifier, " Implementation Identifier");
break;
case LogicalVolumeDescriptor lvd:
builder.AppendLine(lvd.VolumeDescriptorSequenceNumber, " Volume Descriptor Sequence Number");
builder.AppendLine(lvd.LogicalVolumeIdentifier.DecodeDString(), " Logical Volume Identifier");
builder.AppendLine(lvd.LogicalBlockSize, " Logical Block Size");
Print(builder, lvd.DomainIdentifier, " Domain Identifier");
Print(builder, lvd.LogicalVolumeContentsUse, " File Set Descriptor Extent");
builder.AppendLine(lvd.MapTableLength, " Map Table Length");
builder.AppendLine(lvd.NumberOfPartitionMaps, " Number of Partition Maps");
Print(builder, lvd.ImplementationIdentifier, " Implementation Identifier");
Print(builder, lvd.IntegritySequenceExtent, " Integrity Sequence Extent");
for (int i = 0; i < lvd.PartitionMaps.Length; i++)
{
Print(builder, lvd.PartitionMaps[i], i);
}
break;
case VolumeDescriptorPointer vdp:
builder.AppendLine(vdp.VolumeDescriptorSequenceNumber, " Volume Descriptor Sequence Number");
Print(builder, vdp.NextVolumeDescriptorSequenceExtent, " Next Volume Descriptor Sequence");
break;
default:
// Other descriptors are not parsed further
break;
}
}
builder.AppendLine();
}
private static void Print(StringBuilder builder, PartitionMap map, int index)
{
builder.AppendLine($" Partition Map {index} (Partition Reference Number {index}):");
builder.AppendLine(map.PartitionMapType, " Partition Map Type");
builder.AppendLine(map.PartitionMapLength, " Partition Map Length");
switch (map)
{
case Type1PartitionMap type1:
builder.AppendLine(type1.VolumeSequenceNumber, " Volume Sequence Number");
builder.AppendLine(type1.PartitionNumber, " Partition Number");
break;
case MetadataPartitionMap metadata:
Print(builder, metadata.PartitionTypeIdentifier, " Partition Type Identifier");
builder.AppendLine(metadata.VolumeSequenceNumber, " Volume Sequence Number");
builder.AppendLine(metadata.PartitionNumber, " Partition Number");
builder.AppendLine(metadata.MetadataFileLocation, " Metadata File Location");
builder.AppendLine(metadata.MetadataMirrorFileLocation, " Metadata Mirror File Location");
builder.AppendLine(metadata.MetadataBitmapFileLocation, " Metadata Bitmap File Location");
builder.AppendLine(metadata.AllocationUnitSize, " Allocation Unit Size");
builder.AppendLine(metadata.AlignmentUnitSize, " Alignment Unit Size");
builder.AppendLine(metadata.Flags, " Flags");
break;
case Type2PartitionMap type2:
Print(builder, type2.PartitionTypeIdentifier, " Partition Type Identifier");
builder.AppendLine(type2.VolumeSequenceNumber, " Volume Sequence Number");
builder.AppendLine(type2.PartitionNumber, " Partition Number");
builder.AppendLine(type2.PartitionTypeData, " Partition Type Data");
break;
default:
// Unknown partition map types are not parsed further
break;
}
}
private static void Print(StringBuilder builder, FileSetDescriptor? fsd)
{
builder.AppendLine(" File Set Descriptor:");
builder.AppendLine(" -------------------------");
if (fsd is null)
{
builder.AppendLine(" No file set descriptor");
builder.AppendLine();
return;
}
Print(builder, fsd.DescriptorTag, " ");
builder.AppendLine(Format(fsd.RecordingDateAndTime), " Recording Date and Time");
builder.AppendLine(fsd.InterchangeLevel, " Interchange Level");
builder.AppendLine(fsd.MaximumInterchangeLevel, " Maximum Interchange Level");
builder.AppendLine(fsd.FileSetNumber, " File Set Number");
builder.AppendLine(fsd.FileSetDescriptorNumber, " File Set Descriptor Number");
builder.AppendLine(fsd.LogicalVolumeIdentifier.DecodeDString(), " Logical Volume Identifier");
builder.AppendLine(fsd.FileSetIdentifier.DecodeDString(), " File Set Identifier");
builder.AppendLine(fsd.CopyrightFileIdentifier.DecodeDString(), " Copyright File Identifier");
builder.AppendLine(fsd.AbstractFileIdentifier.DecodeDString(), " Abstract File Identifier");
Print(builder, fsd.RootDirectoryICB, " Root Directory ICB");
Print(builder, fsd.DomainIdentifier, " Domain Identifier");
Print(builder, fsd.NextExtent, " Next Extent");
Print(builder, fsd.SystemStreamDirectoryICB, " System Stream Directory ICB");
builder.AppendLine();
}
private void PrintDirectory(StringBuilder builder, long key, string path, HashSet visited)
{
if (!Model.Directories.TryGetValue(key, out var fids))
{
builder.AppendLine($" {path} (directory not read)");
return;
}
foreach (var fid in fids)
{
if ((fid.FileCharacteristics & (FileCharacteristics.DELETED | FileCharacteristics.PARENT)) != 0)
continue;
string name = fid.FileIdentifier.DecodeDCharacters() ?? "(unknown encoding)";
string childPath = path + name;
long childKey = fid.ICB.ExtentLocation.GetKey();
string hidden = (fid.FileCharacteristics & FileCharacteristics.EXISTENCE) != 0 ? ", hidden" : string.Empty;
if (!Model.FileEntries.TryGetValue(childKey, out var entry))
{
builder.AppendLine($" {childPath} (File Entry not read{hidden})");
continue;
}
string location = $"partition {fid.ICB.ExtentLocation.PartitionReferenceNumber}, block {fid.ICB.ExtentLocation.LogicalBlockNumber}";
if (entry.ICBTag.FileType == FileType.DIRECTORY)
{
builder.AppendLine($" {childPath}\\ ({location}{hidden})");
if (visited.Add(childKey))
PrintDirectory(builder, childKey, $"{childPath}\\", visited);
}
else
{
builder.AppendLine($" {childPath} ({entry.InformationLength} bytes, {entry.ICBTag.FileType}, {location}{hidden})");
}
}
}
private static void Print(StringBuilder builder, DescriptorTag tag, string indent)
{
builder.AppendLine((ushort)tag.TagIdentifier, $"{indent}Tag Identifier");
builder.AppendLine(tag.DescriptorVersion, $"{indent}Descriptor Version");
builder.AppendLine(tag.TagSerialNumber, $"{indent}Tag Serial Number");
builder.AppendLine(tag.DescriptorCRCLength, $"{indent}Descriptor CRC Length");
builder.AppendLine(tag.TagLocation, $"{indent}Tag Location");
}
private static void Print(StringBuilder builder, ExtentDescriptor extent, string label)
=> builder.AppendLine($"sector {extent.ExtentLocation}, {extent.ExtentLength} bytes", label);
private static void Print(StringBuilder builder, LongAllocationDescriptor ad, string label)
{
uint length = UDFExtensions.GetExtentLength(ad.ExtentLength);
builder.AppendLine($"partition {ad.ExtentLocation.PartitionReferenceNumber}, block {ad.ExtentLocation.LogicalBlockNumber}, {length} bytes", label);
}
private static void Print(StringBuilder builder, EntityIdentifier identifier, string label)
{
builder.AppendLine(identifier.Identifier, label, Encoding.ASCII);
builder.AppendLine(identifier.Flags, $"{label} Flags");
builder.AppendLine(identifier.IdentifierSuffix, $"{label} Suffix");
}
private static string Format(Timestamp timestamp)
{
if (timestamp.Year == 0 && timestamp.Month == 0 && timestamp.Day == 0)
return "Not specified";
// Lower 12 bits: signed offset from UTC in minutes, -2047 = not specified
int offset = timestamp.TypeAndTimezone & 0x0FFF;
if ((offset & 0x0800) != 0)
offset -= 0x1000;
string zone = offset == -2047 ? string.Empty : $" UTC{(offset < 0 ? "-" : "+")}{System.Math.Abs(offset) / 60:D2}:{System.Math.Abs(offset) % 60:D2}";
return $"{timestamp.Year:D4}-{timestamp.Month:D2}-{timestamp.Day:D2} {timestamp.Hour:D2}:{timestamp.Minute:D2}:{timestamp.Second:D2}.{timestamp.Centiseconds:D2}{zone}";
}
}
}