Support writing logically block addressable tapes in dicformat.

This commit is contained in:
2019-05-01 22:51:30 +01:00
parent 9ec6787175
commit 9dd03546a1
6 changed files with 216 additions and 15 deletions

View File

@@ -122,6 +122,7 @@ namespace DiscImageChef.DiscImages
List<IndexEntry> index;
/// <summary>If set to <c>true</c>, the DDT entries are in-memory.</summary>
bool inMemoryDdt;
bool isTape;
ulong lastWrittenBlock;
/// <summary>LZMA stream.</summary>
LzmaStream lzmaBlockStream;
@@ -154,6 +155,7 @@ namespace DiscImageChef.DiscImages
byte[] structureBytes;
/// <summary>Cache for pointer for marshaling structures.</summary>
IntPtr structurePointer;
Dictionary<ulong, ulong> tapeDdt;
/// <summary>Cache of CompactDisc track's flags</summary>
Dictionary<byte, byte> trackFlags;
/// <summary>Cache of CompactDisc track's ISRC</summary>

View File

@@ -239,8 +239,10 @@ namespace DiscImageChef.DiscImages
ParentBlock = 0x50524E54,
/// <summary>Block containing an array of hardware used to create the image</summary>
DumpHardwareBlock = 0x2A504D44,
/// <summary>TODO: Block containing list of files for a tape image</summary>
TapeFileBlock = 0x454C4654
/// <summary>Block containing list of files for a tape image</summary>
TapeFileBlock = 0x454C4654,
/// <summary>Block containing list of partitions for a tape image</summary>
TapePartitionBlock = 0x54425054
}
enum ChecksumAlgorithm : byte

View File

@@ -107,8 +107,7 @@ namespace DiscImageChef.DiscImages
string[] separated = identify.Model.Split(' ');
if(separated.Length == 1)
if(string.IsNullOrWhiteSpace(imageInfo.DriveModel))
imageInfo.DriveModel = separated[0];
if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) imageInfo.DriveModel = separated[0];
else
{
if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer))
@@ -249,7 +248,8 @@ namespace DiscImageChef.DiscImages
{
if(inMemoryDdt)
{
userDataDdt[sectorAddress] = pointer;
if(isTape) tapeDdt[sectorAddress] = pointer;
else userDataDdt[sectorAddress] = pointer;
return;
}

View File

@@ -86,7 +86,7 @@ namespace DiscImageChef.DiscImages
/// <summary>CRC64-ECMA of the compressed DDT</summary>
public ulong cmpCrc64;
/// <summary>CRC64-ECMA of the uncompressed DDT</summary>
public ulong crc64;
public readonly ulong crc64;
}
/// <summary>Header for the index, followed by entries</summary>
@@ -314,5 +314,81 @@ namespace DiscImageChef.DiscImages
/// <summary>Length in bytes of checksum that follows this structure</summary>
public uint length;
}
/// <summary>
/// Tape file block, contains a list of all files in a tape
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TapeFileHeader
{
/// <summary>Identifier, <see cref="BlockType.TapeFileBlock" /></summary>
public BlockType identifier;
/// <summary>How many entries follow this header</summary>
public uint entries;
/// <summary>Size of the whole block, not including this header, in bytes</summary>
public ulong length;
/// <summary>CRC64-ECMA of the block</summary>
public ulong crc64;
}
/// <summary>
/// Tape file entry
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TapeFileEntry
{
/// <summary>
/// File number
/// </summary>
public uint File;
/// <summary>
/// Partition number
/// </summary>
public readonly byte Partition;
/// <summary>
/// First block, inclusive, of the file
/// </summary>
public ulong FirstBlock;
/// <summary>
/// Last block, inclusive, of the file
/// </summary>
public ulong LastBlock;
}
/// <summary>
/// Tape partition block, contains a list of all partitions in a tape
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TapePartitionHeader
{
/// <summary>Identifier, <see cref="BlockType.TapePartitionBlock" /></summary>
public BlockType identifier;
/// <summary>How many entries follow this header</summary>
public byte entries;
/// <summary>Size of the whole block, not including this header, in bytes</summary>
public ulong length;
/// <summary>CRC64-ECMA of the block</summary>
public ulong crc64;
}
/// <summary>
/// Tape partition entry
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TapePartitionEntry
{
/// <summary>
/// Partition number
/// </summary>
public byte Number;
/// <summary>
/// First block, inclusive, of the partition
/// </summary>
public ulong FirstBlock;
/// <summary>
/// Last block, inclusive, of the partition
/// </summary>
public ulong LastBlock;
}
}
}

View File

@@ -30,22 +30,46 @@
// Copyright © 2011-2019 Natalia Portillo
// ****************************************************************************/
using System;
using System.Collections.Generic;
using DiscImageChef.CommonTypes.Interfaces;
using System.Linq;
using DiscImageChef.CommonTypes.Structs;
namespace DiscImageChef.DiscImages
{
public partial class DiscImageChef
{
public List<TapeFile> Files { get; }
List<TapePartition> ITapeImage.Partitions { get; }
public List<TapeFile> Files { get; private set; }
public List<TapePartition> TapePartitions { get; private set; }
public bool AddFile(TapeFile file) => throw new NotImplementedException();
public bool AddFile(TapeFile file)
{
if(Files.Any(f => f.File == file.File))
{
TapeFile removeMe = Files.FirstOrDefault(f => f.File == file.File);
Files.Remove(removeMe);
}
public bool AddPartition(TapePartition partition) => throw new NotImplementedException();
Files.Add(file);
return true;
}
public bool SetTape() => throw new NotImplementedException();
public bool AddPartition(TapePartition partition)
{
if(TapePartitions.Any(f => f.Number == partition.Number))
{
TapePartition removeMe = TapePartitions.FirstOrDefault(f => f.Number == partition.Number);
TapePartitions.Remove(removeMe);
}
TapePartitions.Add(partition);
return true;
}
public bool SetTape()
{
Files = new List<TapeFile>();
TapePartitions = new List<TapePartition>();
return isTape = true;
}
}
}

View File

@@ -688,7 +688,11 @@ namespace DiscImageChef.DiscImages
inMemoryDdt = sectors <= maxDdtSize * 1024 * 1024 / sizeof(ulong);
// If in memory, easy
if(inMemoryDdt) userDataDdt = new ulong[sectors];
if(inMemoryDdt)
{
if(isTape) tapeDdt = new Dictionary<ulong, ulong>();
else userDataDdt = new ulong[sectors];
}
// If not, create the block, add to index, and enlarge the file to allow the DDT to exist on-disk
else
{
@@ -798,7 +802,7 @@ namespace DiscImageChef.DiscImages
return false;
}
if(sectorAddress >= Info.Sectors)
if(sectorAddress >= Info.Sectors && !isTape)
{
ErrorMessage = "Tried to write past image size";
return false;
@@ -1854,6 +1858,96 @@ namespace DiscImageChef.DiscImages
}
}
if(isTape)
{
ulong latestBlock = tapeDdt.Max(b => b.Key);
userDataDdt = new ulong[latestBlock + 1];
foreach(KeyValuePair<ulong, ulong> block in tapeDdt) userDataDdt[block.Key] = block.Value;
inMemoryDdt = true;
tapeDdt.Clear();
idxEntry = new IndexEntry
{
blockType = BlockType.TapePartitionBlock,
dataType = DataType.UserData,
offset = (ulong)imageStream.Position
};
DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tape partitions to position {0}",
idxEntry.offset);
TapePartitionEntry[] tapePartitionEntries = new TapePartitionEntry[TapePartitions.Count];
for(int t = 0; t < TapePartitions.Count; t++)
{
tapePartitionEntries[t] = new TapePartitionEntry();
tapePartitionEntries[t].Number = TapePartitions[t].Number;
tapePartitionEntries[t].FirstBlock = TapePartitions[t].FirstBlock;
tapePartitionEntries[t].LastBlock = TapePartitions[t].LastBlock;
}
byte[] tapePartitionEntriesData =
MemoryMarshal.Cast<TapePartitionEntry, byte>(tapePartitionEntries).ToArray();
TapePartitionHeader tapePartitionHeader = new TapePartitionHeader();
tapePartitionHeader.identifier = BlockType.TapePartitionBlock;
tapePartitionHeader.entries = (byte)tapePartitionEntries.Length;
tapePartitionHeader.length = (ulong)tapePartitionEntriesData.Length;
crc64 = new Crc64Context();
crc64.Update(tapePartitionEntriesData);
tapePartitionHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0);
structureBytes = new byte[Marshal.SizeOf<TapePartitionHeader>()];
MemoryMarshal.Write(structureBytes, ref tapePartitionHeader);
imageStream.Write(structureBytes, 0, structureBytes.Length);
structureBytes = null;
imageStream.Write(tapePartitionEntriesData, 0, tapePartitionEntriesData.Length);
index.RemoveAll(t => t.blockType == BlockType.TapePartitionBlock && t.dataType == DataType.UserData);
index.Add(idxEntry);
idxEntry = new IndexEntry
{
blockType = BlockType.TapeFileBlock,
dataType = DataType.UserData,
offset = (ulong)imageStream.Position
};
DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tape files to position {0}",
idxEntry.offset);
TapeFileEntry[] tapeFileEntries = new TapeFileEntry[Files.Count];
for(int t = 0; t < Files.Count; t++)
tapeFileEntries[t] = new TapeFileEntry
{
File = Files[t].File, FirstBlock = Files[t].FirstBlock, LastBlock = Files[t].LastBlock
};
byte[] tapeFileEntriesData = MemoryMarshal.Cast<TapeFileEntry, byte>(tapeFileEntries).ToArray();
TapeFileHeader tapeFileHeader = new TapeFileHeader
{
identifier = BlockType.TapeFileBlock,
entries = (uint)tapeFileEntries.Length,
length = (ulong)tapeFileEntriesData.Length
};
crc64 = new Crc64Context();
crc64.Update(tapeFileEntriesData);
tapeFileHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0);
structureBytes = new byte[Marshal.SizeOf<TapeFileHeader>()];
MemoryMarshal.Write(structureBytes, ref tapeFileHeader);
imageStream.Write(structureBytes, 0, structureBytes.Length);
structureBytes = null;
imageStream.Write(tapeFileEntriesData, 0, tapeFileEntriesData.Length);
index.RemoveAll(t => t.blockType == BlockType.TapeFileBlock && t.dataType == DataType.UserData);
index.Add(idxEntry);
}
// If the DDT is in-memory, write it to disk
if(inMemoryDdt)
{
@@ -2949,11 +3043,13 @@ namespace DiscImageChef.DiscImages
return true;
}
case SectorTagType.CdTrackIsrc:
{
if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data));
return true;
}
case SectorTagType.CdSectorSubchannel:
{
if(data.Length != 96)
@@ -2968,6 +3064,7 @@ namespace DiscImageChef.DiscImages
return true;
}
default:
ErrorMessage = $"Don't know how to write sector tag type {tag}";
return false;