using System;
using System.Text;
namespace SabreTools.Serialization.Readers.Test
{
///
/// Builds a minimal UDF 1.02 image in memory (2048-byte sectors, one Type 1 partition at sector 260)
///
///
/// Partition blocks:
/// 0 File Set Descriptor
/// 2 root directory File Entry, contents in block 3:
/// parent, "README.TXT" (embedded data), "deleted.txt" (deleted), "Sub" (directory),
/// "Media" (last entry, no padding)
/// 4 "README.TXT" File Entry, data "hello world" embedded
/// 5 "Sub" File Entry, contents in block 6: parent, "data.bin" (16-bit name)
/// 7 "data.bin" File Entry, long_ad to block 8 (2048 bytes), then an Allocation Extent Descriptor
/// in block 10 with a long_ad to block 9 (952 bytes)
/// 8-9 "data.bin" data (byte i = i % 251)
/// 11 "Media" File Entry, one short_ad of 100 bytes allocated but not recorded
///
internal static class UDFImageBuilder
{
public const int SectorSize = 2048;
public const int PartitionStart = 260;
public const int PartitionLength = 40;
public const int Sectors = 300;
public const int DataBinLength = 3000;
///
/// Build the image
///
/// True to break the checksum of the root directory File Entry
/// True to change a byte of the root directory File Entry that only the CRC covers
/// True to record the partition map with length 0 and a map count of 3
/// True to replace the Terminating Descriptor by a Volume Descriptor Pointer back to the start of the sequence
/// Name of the "Sub" directory in the root directory
/// True to add a directory "loop" to "Sub" that points back to the root directory
/// True to record "Media" in a partition block past the end of the image
public static byte[] Build(bool corruptRootEntry = false,
bool corruptRootEntryCrc = false,
bool emptyPartitionMap = false,
bool pointerLoop = false,
string subName = "Sub",
bool subLoop = false,
bool mediaPastEnd = false)
{
var image = new byte[Sectors * SectorSize];
// Volume Recognition Sequence
WriteStructure(image, 16, "BEA01");
WriteStructure(image, 17, "NSR02");
WriteStructure(image, 18, "TEA01");
// Anchor Volume Descriptor Pointer: main and reserve sequence at sector 32, 4 sectors
int avdp = 256 * SectorSize;
WriteUInt32(image, avdp + 16, 4 * SectorSize);
WriteUInt32(image, avdp + 20, 32);
WriteUInt32(image, avdp + 24, 4 * SectorSize);
WriteUInt32(image, avdp + 28, 32);
WriteTag(image, avdp, 2, 256, 512);
// Primary Volume Descriptor
int pvd = 32 * SectorSize;
WriteDString(image, pvd + 24, 32, "TESTVOLUME");
WriteTag(image, pvd, 1, 32, 512);
// Partition Descriptor: partition 0 at sector 260
int pd = 33 * SectorSize;
WriteUInt32(image, pd + 16, 1);
image[pd + 22] = 0;
WriteUInt32(image, pd + 188, PartitionStart);
WriteUInt32(image, pd + 192, mediaPastEnd ? PartitionLength + 20 : PartitionLength);
WriteTag(image, pd, 5, 33, 512);
// Logical Volume Descriptor: block size, domain, File Set Descriptor at block 0, one Type 1 map
int lvd = 34 * SectorSize;
WriteUInt32(image, lvd + 16, 2);
WriteDString(image, lvd + 84, 128, "TESTVOLUME");
WriteUInt32(image, lvd + 212, SectorSize);
WriteAscii(image, lvd + 217, "*OSTA UDF Compliant");
image[lvd + 240] = 0x02;
image[lvd + 241] = 0x01;
WriteLongAd(image, lvd + 248, SectorSize, 0);
WriteUInt32(image, lvd + 264, 6);
WriteUInt32(image, lvd + 268, emptyPartitionMap ? 3 : 1);
image[lvd + 440] = 1;
image[lvd + 441] = (byte)(emptyPartitionMap ? 0 : 6);
image[lvd + 442] = 1;
WriteTag(image, lvd, 6, 34, 446);
// Terminating Descriptor, or a Volume Descriptor Pointer back to the start of the sequence
int td = 35 * SectorSize;
if (pointerLoop)
{
WriteUInt32(image, td + 20, 4 * SectorSize);
WriteUInt32(image, td + 24, 32);
WriteTag(image, td, 3, 35, 512);
}
else
{
WriteTag(image, td, 8, 35, 512);
}
// File Set Descriptor: root directory ICB at block 2
int fsd = Block(0);
WriteDString(image, fsd + 304, 32, "TESTSET");
WriteLongAd(image, fsd + 400, SectorSize, 2);
WriteTag(image, fsd, 256, 0, 512);
// Root directory contents
int root = Block(3);
int length = 0;
length += WriteFid(image, root + length, 3, 0x0A, 2, []);
length += WriteFid(image, root + length, 3, 0x00, 4, Name8("README.TXT"));
length += WriteFid(image, root + length, 3, 0x04, 4, Name8("deleted.txt"));
length += WriteFid(image, root + length, 3, 0x02, 5, Name8(subName));
length += WriteFid(image, root + length, 3, 0x00, 11, Name8("Media"));
WriteFileEntry(image, 2, 4, 0, length, ShortAd(length, 3));
if (corruptRootEntry)
image[Block(2) + 4] ^= 0xFF;
if (corruptRootEntryCrc)
image[Block(2) + 36] ^= 0xFF;
// "README.TXT", embedded
WriteFileEntry(image, 4, 5, 3, 11, Encoding.ASCII.GetBytes("hello world"));
// "Sub" contents
int sub = Block(6);
length = 0;
length += WriteFid(image, sub + length, 6, 0x0A, 2, []);
length += WriteFid(image, sub + length, 6, 0x00, 7, Name16("data.bin"));
if (subLoop)
length += WriteFid(image, sub + length, 6, 0x02, 2, Name8("loop"));
WriteFileEntry(image, 5, 4, 0, length, ShortAd(length, 6));
// "data.bin", long allocation descriptors continued in an Allocation Extent Descriptor
var ads = new byte[32];
WriteLongAd(ads, 0, SectorSize, 8);
WriteLongAd(ads, 16, (3u << 30) | SectorSize, 10);
WriteFileEntry(image, 7, 5, 1, DataBinLength, ads);
for (int i = 0; i < DataBinLength; i++)
{
image[Block(8) + i] = (byte)(i % 251);
}
int aed = Block(10);
WriteUInt32(image, aed + 20, 16);
WriteLongAd(image, aed + 24, DataBinLength - SectorSize, 9);
WriteTag(image, aed, 258, 10, 40);
// "Media", allocated but not recorded, or recorded past the end of the image
if (mediaPastEnd)
WriteFileEntry(image, 11, 5, 0, 100, ShortAd(100, PartitionLength + 10));
else
WriteFileEntry(image, 11, 5, 0, 100, ShortAd((1u << 30) | 100, 0));
return image;
}
///
/// Offset of a partition block in the image
///
public static int Block(int block) => (PartitionStart + block) * SectorSize;
private static void WriteStructure(byte[] image, int sector, string identifier)
{
int offset = sector * SectorSize;
image[offset] = 0;
WriteAscii(image, offset + 1, identifier);
image[offset + 6] = 1;
}
private static int WriteFid(byte[] image, int offset, uint block, byte characteristics, uint icb, byte[] name)
{
image[offset + 16] = 1;
image[offset + 18] = characteristics;
image[offset + 19] = (byte)name.Length;
WriteLongAd(image, offset + 20, SectorSize, icb);
Array.Copy(name, 0, image, offset + 38, name.Length);
int length = (38 + name.Length + 3) & ~3;
WriteTag(image, offset, 257, block, length);
return length;
}
private static void WriteFileEntry(byte[] image, int block, byte fileType, ushort adType, long informationLength, byte[] ads)
{
int offset = Block(block);
image[offset + 16 + 4] = 4;
image[offset + 16 + 11] = fileType;
image[offset + 16 + 18] = (byte)adType;
WriteUInt32(image, offset + 56, (uint)informationLength);
WriteUInt32(image, offset + 172, (uint)ads.Length);
Array.Copy(ads, 0, image, offset + 176, ads.Length);
WriteTag(image, offset, 261, (uint)block, 176 + ads.Length);
}
private static byte[] ShortAd(uint length, uint position)
{
var ad = new byte[8];
WriteUInt32(ad, 0, length);
WriteUInt32(ad, 4, position);
return ad;
}
private static byte[] ShortAd(int length, uint position) => ShortAd((uint)length, position);
private static void WriteLongAd(byte[] data, int offset, uint length, uint block)
{
WriteUInt32(data, offset, length);
WriteUInt32(data, offset + 4, block);
}
private static void WriteLongAd(byte[] data, int offset, int length, uint block)
=> WriteLongAd(data, offset, (uint)length, block);
private static byte[] Name8(string name)
{
var bytes = new byte[name.Length + 1];
bytes[0] = 8;
Encoding.ASCII.GetBytes(name, 0, name.Length, bytes, 1);
return bytes;
}
private static byte[] Name16(string name)
{
var bytes = new byte[(name.Length * 2) + 1];
bytes[0] = 16;
Encoding.BigEndianUnicode.GetBytes(name, 0, name.Length, bytes, 1);
return bytes;
}
private static void WriteDString(byte[] image, int offset, int fieldLength, string value)
{
image[offset] = 8;
WriteAscii(image, offset + 1, value);
image[offset + fieldLength - 1] = (byte)(value.Length + 1);
}
private static void WriteAscii(byte[] image, int offset, string value)
=> Encoding.ASCII.GetBytes(value, 0, value.Length, image, offset);
private static void WriteUInt32(byte[] data, int offset, uint value)
{
data[offset] = (byte)value;
data[offset + 1] = (byte)(value >> 8);
data[offset + 2] = (byte)(value >> 16);
data[offset + 3] = (byte)(value >> 24);
}
private static void WriteUInt32(byte[] data, int offset, int value) => WriteUInt32(data, offset, (uint)value);
///
/// Write a descriptor tag covering the given descriptor length (version 2, CRC, checksum)
///
private static void WriteTag(byte[] image, int offset, ushort identifier, uint location, int length)
{
image[offset] = (byte)identifier;
image[offset + 1] = (byte)(identifier >> 8);
image[offset + 2] = 2;
ushort crc = Crc(image, offset + 16, length - 16);
image[offset + 8] = (byte)crc;
image[offset + 9] = (byte)(crc >> 8);
image[offset + 10] = (byte)(length - 16);
image[offset + 11] = (byte)((length - 16) >> 8);
WriteUInt32(image, offset + 12, location);
byte checksum = 0;
for (int i = 0; i < 16; i++)
{
if (i != 4)
checksum += image[offset + i];
}
image[offset + 4] = checksum;
}
private static ushort Crc(byte[] data, int offset, int length)
{
ushort crc = 0;
for (int i = offset; i < offset + length; i++)
{
crc ^= (ushort)(data[i] << 8);
for (int bit = 0; bit < 8; bit++)
{
crc = (crc & 0x8000) != 0 ? (ushort)((crc << 1) ^ 0x1021) : (ushort)(crc << 1);
}
}
return crc;
}
}
}