Implement writing support for DiskCopy 4.2 format, fixes #129

This commit is contained in:
2017-12-28 21:44:25 +00:00
parent feecb2f70a
commit 6f21ffa70c
3 changed files with 502 additions and 159 deletions

View File

@@ -31,6 +31,9 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="Claunia.Encoding, Version=1.4.6570.38892, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Claunia.Encoding.1.4.0\lib\portable40-net40+sl5+win8+wp8\Claunia.Encoding.dll</HintPath>
</Reference>
<Reference Include="SharpCompress, Version=0.19.2.0, Culture=neutral, PublicKeyToken=afb0a02973931d96">
<HintPath>..\packages\SharpCompress.0.19.2\lib\net35\SharpCompress.dll</HintPath>
</Reference>

View File

@@ -34,19 +34,20 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Claunia.Encoding;
using Claunia.RsrcFork;
using DiscImageChef.CommonTypes;
using DiscImageChef.Console;
using DiscImageChef.Filters;
using Encoding = System.Text.Encoding;
using Version = Resources.Version;
namespace DiscImageChef.DiscImages
{
// Checked using several images and strings inside Apple's DiskImages.framework
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class DiskCopy42 : IMediaImage
public class DiskCopy42 : IWritableImage
{
// format byte
/// <summary>3.5", single side, double density, GCR</summary>
@@ -91,6 +92,7 @@ namespace DiscImageChef.DiscImages
const byte kFmtNotStandard = 0x93;
/// <summary>Used incorrectly by Mac OS X with certaing disk images</summary>
const byte kMacOSXFmtByte = 0x00;
const string REGEX_DCPY = @"(?<application>\S+)\s(?<version>\S+)\rData checksum=\$(?<checksum>\S+)$";
/// <summary>Bytes per tag, should be 12</summary>
uint bptag;
@@ -100,16 +102,17 @@ namespace DiscImageChef.DiscImages
IFilter dc42ImageFilter;
/// <summary>Header of opened image</summary>
Dc42Header header;
ImageInfo imageInfo;
ulong sectorsToWrite;
/// <summary>Start of tags in disk image, after data sectors</summary>
uint tagOffset;
bool twiggy;
byte[] twiggyCache;
byte[] twiggyCacheTags;
ImageInfo imageInfo;
public ImageInfo Info => imageInfo;
MemoryStream twiggyDataCache;
MemoryStream twiggyTagCache;
public string Name => "Apple DiskCopy 4.2";
public Guid Id => new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88");
FileStream writingStream;
public DiskCopy42()
{
@@ -138,6 +141,10 @@ namespace DiscImageChef.DiscImages
};
}
public ImageInfo Info => imageInfo;
public string Name => "Apple DiskCopy 4.2";
public Guid Id => new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88");
public bool Identify(IFilter imageFilter)
{
Stream stream = imageFilter.GetDataForkStream();
@@ -226,6 +233,7 @@ namespace DiscImageChef.DiscImages
byte[] buffer = new byte[0x58];
byte[] pString = new byte[64];
stream.Read(buffer, 0, 0x58);
IsWriting = false;
// Incorrect pascal string length, not DC42
if(buffer[0] > 63) return false;
@@ -419,9 +427,12 @@ namespace DiscImageChef.DiscImages
if(i >= 42 && i <= 45) sectorsToCopy = 15;
Array.Copy(data, header.DataSize / 2 + copiedSectors * 512, twiggyCache,
twiggyCache.Length - copiedSectors * 512 - sectorsToCopy * 512, sectorsToCopy * 512);
Array.Copy(tags, header.TagSize / 2 + copiedSectors * bptag, twiggyCacheTags,
twiggyCacheTags.Length - copiedSectors * bptag - sectorsToCopy * bptag,
twiggyCache.Length - copiedSectors * 512 - sectorsToCopy * 512,
sectorsToCopy * 512);
Array.Copy(tags, header.TagSize / 2 + copiedSectors * bptag,
twiggyCacheTags,
twiggyCacheTags.Length - copiedSectors * bptag -
sectorsToCopy * bptag,
sectorsToCopy * bptag);
copiedSectors += sectorsToCopy;
@@ -481,18 +492,13 @@ namespace DiscImageChef.DiscImages
{
string dCpy = StringHandlers.PascalToString(dCpyRsrc.GetResource(dCpyRsrc.GetIds()[0]),
Encoding.GetEncoding("macintosh"));
string dCpyRegEx =
@"(?<application>\S+)\s(?<version>\S+)\rData checksum=\$(?<checksum>\S+)$";
Regex dCpyEx = new Regex(dCpyRegEx);
Regex dCpyEx = new Regex(REGEX_DCPY);
Match dCpyMatch = dCpyEx.Match(dCpy);
if(dCpyMatch.Success)
{
imageInfo.Application = dCpyMatch.Groups["application"].Value;
imageInfo.ApplicationVersion = dCpyMatch.Groups["version"].Value;
// Until MacRoman is implemented
if(imageInfo.Application == "ShrinkWrap?") imageInfo.Application = "ShrinkWrap™";
}
}
}
@@ -603,7 +609,6 @@ namespace DiscImageChef.DiscImages
{
byte[] data = new byte[header.DataSize];
byte[] tags = new byte[header.TagSize];
uint dataChk;
uint tagsChk = 0;
DicConsole.DebugWriteLine("DC42 plugin", "Reading data");
@@ -612,7 +617,7 @@ namespace DiscImageChef.DiscImages
datastream.Read(data, 0, (int)header.DataSize);
DicConsole.DebugWriteLine("DC42 plugin", "Calculating data checksum");
dataChk = DC42CheckSum(data);
uint dataChk = DC42CheckSum(data);
DicConsole.DebugWriteLine("DC42 plugin", "Calculated data checksum = 0x{0:X8}", dataChk);
DicConsole.DebugWriteLine("DC42 plugin", "Stored data checksum = 0x{0:X8}", header.DataChecksum);
@@ -773,6 +778,333 @@ namespace DiscImageChef.DiscImages
throw new FeatureUnsupportedImageException("Feature not supported by image format");
}
public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { };
public IEnumerable<SectorTagType> SupportedSectorTags => new[] {SectorTagType.AppleSectorTag};
public IEnumerable<MediaType> SupportedMediaTypes =>
new[]
{
MediaType.AppleFileWare, MediaType.AppleHD20, MediaType.AppleProfile, MediaType.AppleSonyDS,
MediaType.AppleSonySS, MediaType.AppleWidget, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD,
MediaType.DMF
};
public IEnumerable<(string name, Type type, string description)> SupportedOptions =>
new[] {("macosx", typeof(bool), "Use Mac OS X format byte")};
public IEnumerable<string> KnownExtensions => new[] {".dc42", ".diskcopy42", ".image"};
public bool IsWriting { get; private set; }
public string ErrorMessage { get; private set; }
public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors)
{
header = new Dc42Header();
bool tags = false;
bool macosx;
if(options != null && options.TryGetValue("macosx", out string tmpOption))
bool.TryParse(tmpOption, out macosx);
switch(mediaType)
{
case MediaType.AppleFileWare:
header.FmtByte = kSigmaFmtByteTwiggy;
header.Format = kSigmaFormatTwiggy;
twiggy = true;
tags = true;
twiggyDataCache = new MemoryStream();
twiggyTagCache = new MemoryStream();
// TODO
ErrorMessage = "Twiggy write support not yet implemented";
return false;
case MediaType.AppleHD20:
if(sectors != 39040)
{
ErrorMessage = "Incorrect number of sectors for Apple HD20 image";
return false;
}
header.FmtByte = kFmtNotStandard;
header.Format = kNotStandardFormat;
tags = true;
break;
case MediaType.AppleProfile:
if(sectors != 9728 && sectors != 19456)
{
ErrorMessage = "Incorrect number of sectors for Apple Profile image";
return false;
}
header.FmtByte = kFmtNotStandard;
header.Format = kNotStandardFormat;
tags = true;
break;
case MediaType.AppleSonyDS:
if(sectors != 1600)
{
ErrorMessage = "Incorrect number of sectors for Apple MF2DD image";
return false;
}
header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte800K;
header.Format = kSonyFormat800K;
tags = true;
break;
case MediaType.AppleSonySS:
if(sectors != 800)
{
ErrorMessage = "Incorrect number of sectors for Apple MF1DD image";
return false;
}
header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte400K;
header.Format = kSonyFormat400K;
tags = true;
break;
case MediaType.AppleWidget:
if(sectors != 39040)
{
ErrorMessage = "Incorrect number of sectors for Apple Widget image";
return false;
}
header.FmtByte = kFmtNotStandard;
header.Format = kNotStandardFormat;
tags = true;
break;
case MediaType.DOS_35_DS_DD_9:
if(sectors != 1440)
{
ErrorMessage = "Incorrect number of sectors for MF2DD image";
return false;
}
header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte720K;
header.Format = kSonyFormat720K;
break;
case MediaType.DOS_35_HD:
if(sectors != 2880)
{
ErrorMessage = "Incorrect number of sectors for MF2HD image";
return false;
}
header.Format = kSonyFmtByte1440K;
header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte1440K;
break;
case MediaType.DMF:
if(sectors != 3360)
{
ErrorMessage = "Incorrect number of sectors for DMF image";
return false;
}
header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte1680K;
header.Format = kSonyFormat1680K;
break;
default:
ErrorMessage = $"Unsupport media format {mediaType}";
return false;
}
dataOffset = 0x54;
tagOffset = header.TagSize != 0 ? 0x54 + header.DataSize : 0;
header.DiskName = "-DiscImageChef converted image-";
header.Valid = 1;
header.DataSize = (uint)(sectors * 512);
if(tags) header.TagSize = (uint)(sectors * 12);
sectorsToWrite = sectors;
try { writingStream = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); }
catch(IOException e)
{
ErrorMessage = $"Could not create new image file, exception {e.Message}";
return false;
}
ErrorMessage = null;
return true;
}
public bool WriteMediaTag(byte[] data, MediaTagType tag)
{
ErrorMessage = "Unsupported feature";
return false;
}
public bool WriteSector(byte[] data, ulong sectorAddress)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
return false;
}
if(sectorAddress >= sectorsToWrite)
{
ErrorMessage = "Tried to write past image size";
return false;
}
writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin);
writingStream.Write(data, 0, data.Length);
ErrorMessage = "";
return true;
}
public bool WriteSectors(byte[] data, ulong sectorAddress, uint length)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
return false;
}
if(data.Length != 512)
{
ErrorMessage = "Incorrect data size";
return false;
}
if(data.Length % 512 != 0)
{
ErrorMessage = "Incorrect data size";
return false;
}
if(sectorAddress + length >= sectorsToWrite)
{
ErrorMessage = "Tried to write past image size";
return false;
}
writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin);
writingStream.Write(data, 0, data.Length);
ErrorMessage = "";
return true;
}
public bool WriteSectorLong(byte[] data, ulong sectorAddress)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
return false;
}
if(header.TagSize == 0)
{
ErrorMessage = "Image does not support tags";
return false;
}
if(data.Length != 524)
{
ErrorMessage = "Incorrect data size";
return false;
}
if(sectorAddress >= sectorsToWrite)
{
ErrorMessage = "Tried to write past image size";
return false;
}
writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin);
writingStream.Write(data, 0, 512);
writingStream.Seek((long)(tagOffset + sectorAddress * 12), SeekOrigin.Begin);
writingStream.Write(data, 512, 12);
ErrorMessage = "";
return true;
}
public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
return false;
}
if(header.TagSize == 0)
{
ErrorMessage = "Image does not support tags";
return false;
}
if(data.Length % 524 != 0)
{
ErrorMessage = "Incorrect data size";
return false;
}
if(sectorAddress + length >= sectorsToWrite)
{
ErrorMessage = "Tried to write past image size";
return false;
}
for(uint i = 0; i < length; i++)
{
writingStream.Seek((long)(dataOffset + (sectorAddress + i) * 512), SeekOrigin.Begin);
writingStream.Write(data, (int)(i * 524 + 0), 512);
writingStream.Seek((long)(tagOffset + (sectorAddress + i) * 12),
SeekOrigin.Begin);
writingStream.Write(data, (int)(i * 524 + 512),
12);
}
ErrorMessage = "";
return true;
}
public bool SetTracks(List<Track> tracks)
{
ErrorMessage = "Unsupported feature";
return false;
}
public bool Close()
{
if(!IsWriting)
{
ErrorMessage = "Image is not opened for writing";
return false;
}
writingStream.Seek(0x54, SeekOrigin.Begin);
byte[] data = new byte[header.DataSize];
writingStream.Read(data, 0, (int)header.DataSize);
header.DataChecksum = DC42CheckSum(data);
writingStream.Seek(0x54 + header.DataSize, SeekOrigin.Begin);
data = new byte[header.TagSize];
writingStream.Read(data, 0, (int)header.TagSize);
header.TagChecksum = DC42CheckSum(data);
writingStream.Seek(0, SeekOrigin.Begin);
if(header.DiskName.Length > 63) header.DiskName = header.DiskName.Substring(0, 63);
writingStream.WriteByte((byte)header.DiskName.Length);
Encoding macRoman = new MacRoman();
writingStream.Write(macRoman.GetBytes(header.DiskName), 0, header.DiskName.Length);
writingStream.Seek(64, SeekOrigin.Begin);
writingStream.Write(BigEndianBitConverter.GetBytes(header.DataSize), 0, 4);
writingStream.Write(BigEndianBitConverter.GetBytes(header.TagSize), 0, 4);
writingStream.Write(BigEndianBitConverter.GetBytes(header.DataChecksum), 0, 4);
writingStream.Write(BigEndianBitConverter.GetBytes(header.TagChecksum), 0, 4);
writingStream.WriteByte(header.Format);
writingStream.WriteByte(header.FmtByte);
writingStream.WriteByte(1);
writingStream.WriteByte(0);
writingStream.Flush();
writingStream.Close();
IsWriting = false;
return true;
}
static uint DC42CheckSum(byte[] buffer)
{
uint dc42Chk = 0;
@@ -788,6 +1120,11 @@ namespace DiscImageChef.DiscImages
return dc42Chk;
}
~DiskCopy42()
{
Close();
}
// DiskCopy 4.2 header, big-endian, data-fork, start of file, 84 bytes
struct Dc42Header
{

View File

@@ -45,15 +45,15 @@ namespace DiscImageChef.DiscImages
/// <summary>
/// Gets a list of <see cref="MediaTagType" /> that are supported by the media image format
/// </summary>
List<MediaTagType> SupportedMediaTags { get; }
IEnumerable<MediaTagType> SupportedMediaTags { get; }
/// <summary>
/// Gets a list of <see cref="SectorTagType" /> that are supported by the media image format
/// </summary>
List<SectorTagType> SupportedSectorTags { get; }
IEnumerable<SectorTagType> SupportedSectorTags { get; }
/// <summary>
/// Gets a list of <see cref="MediaType" /> that are supported by the media image format
/// </summary>
List<MediaType> SupportedMediaTypes { get; }
IEnumerable<MediaType> SupportedMediaTypes { get; }
/// <summary>
/// Retrieves a list of options supported by the filesystem, with name, type and description
/// </summary>
@@ -61,7 +61,10 @@ namespace DiscImageChef.DiscImages
/// <summary>
/// Gets a list of known extensions for format auto-chosing
/// </summary>
List<string> KnownExtensions { get; }
IEnumerable<string> KnownExtensions { get; }
bool IsWriting { get; }
string ErrorMessage { get; }
/// <summary>
/// Creates a new image in the specified path, for the specified <see cref="MediaType" />, with the