mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
Implement writing support for DiskCopy 4.2 format, fixes #129
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user