Files
Aaru/Aaru.Images/CloneCD/Write.cs

632 lines
23 KiB
C#
Raw Normal View History

// /***************************************************************************
2020-02-27 12:31:25 +00:00
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Write.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Writes CloneCD disc images.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
2020-01-03 17:51:30 +00:00
// Copyright © 2011-2020 Natalia Portillo
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
2020-02-27 00:33:26 +00:00
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Structs;
using Aaru.Decoders.CD;
using Schemas;
2020-02-27 00:33:26 +00:00
using TrackType = Aaru.CommonTypes.Enums.TrackType;
2020-02-27 00:33:26 +00:00
namespace Aaru.DiscImages
{
public partial class CloneCd
{
public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors,
2020-02-29 18:03:35 +00:00
uint sectorSize)
{
if(!SupportedMediaTypes.Contains(mediaType))
{
ErrorMessage = $"Unsupport media format {mediaType}";
2020-02-29 18:03:35 +00:00
return false;
}
2020-02-29 18:03:35 +00:00
imageInfo = new ImageInfo
{
MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors
};
try
{
writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path));
descriptorStream = new StreamWriter(path, false, Encoding.ASCII);
2020-02-29 18:03:35 +00:00
dataStream = new FileStream(writingBaseName + ".img", FileMode.OpenOrCreate, FileAccess.ReadWrite,
FileShare.None);
}
catch(IOException e)
{
ErrorMessage = $"Could not create new image file, exception {e.Message}";
2020-02-29 18:03:35 +00:00
return false;
}
imageInfo.MediaType = mediaType;
trackFlags = new Dictionary<byte, byte>();
IsWriting = true;
ErrorMessage = null;
2020-02-29 18:03:35 +00:00
return true;
}
public bool WriteMediaTag(byte[] data, MediaTagType tag)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
2020-02-29 18:03:35 +00:00
return false;
}
switch(tag)
{
case MediaTagType.CD_MCN:
catalog = Encoding.ASCII.GetString(data);
2020-02-29 18:03:35 +00:00
return true;
case MediaTagType.CD_FullTOC:
fulltoc = new byte[data.Length + 2];
Array.Copy(data, 0, fulltoc, 2, data.Length);
fulltoc[0] = (byte)((data.Length & 0xFF00) >> 8);
fulltoc[1] = (byte)(data.Length & 0xFF);
2020-02-29 18:03:35 +00:00
return true;
default:
ErrorMessage = $"Unsupported media tag {tag}";
2020-02-29 18:03:35 +00:00
return false;
}
}
public bool WriteSector(byte[] data, ulong sectorAddress)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
2020-02-29 18:03:35 +00:00
return false;
}
// TODO: Implement ECC generation
ErrorMessage = "This format requires sectors to be raw. Generating ECC is not yet implemented";
2020-02-29 18:03:35 +00:00
return false;
}
public bool WriteSectors(byte[] data, ulong sectorAddress, uint length)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
2020-02-29 18:03:35 +00:00
return false;
}
// TODO: Implement ECC generation
ErrorMessage = "This format requires sectors to be raw. Generating ECC is not yet implemented";
2020-02-29 18:03:35 +00:00
return false;
}
public bool WriteSectorLong(byte[] data, ulong sectorAddress)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
2020-02-29 18:03:35 +00:00
return false;
}
Track track =
Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector &&
sectorAddress <= trk.TrackEndSector);
if(track.TrackSequence == 0)
{
ErrorMessage = $"Can't found track containing {sectorAddress}";
2020-02-29 18:03:35 +00:00
return false;
}
if(data.Length != track.TrackRawBytesPerSector)
{
ErrorMessage = "Incorrect data size";
2020-02-29 18:03:35 +00:00
return false;
}
2020-02-29 18:03:35 +00:00
dataStream.Seek((long)(track.TrackFileOffset + ((sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector)),
SeekOrigin.Begin);
2020-02-29 18:03:35 +00:00
dataStream.Write(data, 0, data.Length);
return true;
}
public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
2020-02-29 18:03:35 +00:00
return false;
}
Track track =
Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector &&
sectorAddress <= trk.TrackEndSector);
if(track.TrackSequence == 0)
{
ErrorMessage = $"Can't found track containing {sectorAddress}";
2020-02-29 18:03:35 +00:00
return false;
}
if(sectorAddress + length > track.TrackEndSector + 1)
{
ErrorMessage = "Can't cross tracks";
2020-02-29 18:03:35 +00:00
return false;
}
if(data.Length % track.TrackRawBytesPerSector != 0)
{
ErrorMessage = "Incorrect data size";
2020-02-29 18:03:35 +00:00
return false;
}
2020-02-29 18:03:35 +00:00
dataStream.Seek((long)(track.TrackFileOffset + ((sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector)),
SeekOrigin.Begin);
2020-02-29 18:03:35 +00:00
dataStream.Write(data, 0, data.Length);
return true;
}
public bool SetTracks(List<Track> tracks)
{
ulong currentDataOffset = 0;
ulong currentSubchannelOffset = 0;
Tracks = new List<Track>();
2020-02-29 18:03:35 +00:00
foreach(Track track in tracks.OrderBy(t => t.TrackSequence))
{
Track newTrack = track;
uint subchannelSize;
2020-02-29 18:03:35 +00:00
switch(track.TrackSubchannelType)
{
case TrackSubchannelType.None:
subchannelSize = 0;
2020-02-29 18:03:35 +00:00
break;
case TrackSubchannelType.Raw:
case TrackSubchannelType.RawInterleaved:
subchannelSize = 96;
2020-02-29 18:03:35 +00:00
break;
default:
ErrorMessage = $"Unsupported subchannel type {track.TrackSubchannelType}";
2020-02-29 18:03:35 +00:00
return false;
}
newTrack.TrackFileOffset = currentDataOffset;
newTrack.TrackSubchannelOffset = currentSubchannelOffset;
currentDataOffset += (ulong)newTrack.TrackRawBytesPerSector *
2020-02-29 18:03:35 +00:00
((newTrack.TrackEndSector - newTrack.TrackStartSector) + 1);
currentSubchannelOffset += subchannelSize * ((newTrack.TrackEndSector - newTrack.TrackStartSector) + 1);
Tracks.Add(newTrack);
}
return true;
}
public bool Close()
{
if(!IsWriting)
{
ErrorMessage = "Image is not opened for writing";
2020-02-29 18:03:35 +00:00
return false;
}
dataStream.Flush();
dataStream.Close();
subStream?.Flush();
subStream?.Close();
FullTOC.CDFullTOC? nullableToc = null;
FullTOC.CDFullTOC toc;
// Easy, just decode the real toc
2020-02-29 18:03:35 +00:00
if(fulltoc != null)
nullableToc = FullTOC.Decode(fulltoc);
// Not easy, create a toc from scratch
if(nullableToc == null)
{
toc = new FullTOC.CDFullTOC();
Dictionary<byte, byte> sessionEndingTrack = new Dictionary<byte, byte>();
toc.FirstCompleteSession = byte.MaxValue;
toc.LastCompleteSession = byte.MinValue;
List<FullTOC.TrackDataDescriptor> trackDescriptors = new List<FullTOC.TrackDataDescriptor>();
byte currentTrack = 0;
foreach(Track track in Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence))
{
if(track.TrackSession < toc.FirstCompleteSession)
toc.FirstCompleteSession = (byte)track.TrackSession;
if(track.TrackSession <= toc.LastCompleteSession)
{
currentTrack = (byte)track.TrackSequence;
2020-02-29 18:03:35 +00:00
continue;
}
2020-02-29 18:03:35 +00:00
if(toc.LastCompleteSession > 0)
sessionEndingTrack.Add(toc.LastCompleteSession, currentTrack);
toc.LastCompleteSession = (byte)track.TrackSession;
}
byte currentSession = 0;
foreach(Track track in Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence))
{
trackFlags.TryGetValue((byte)track.TrackSequence, out byte trackControl);
2020-02-29 18:03:35 +00:00
if(trackControl == 0 &&
track.TrackType != TrackType.Audio)
trackControl = (byte)CdFlags.DataTrack;
// Lead-Out
2020-02-29 18:03:35 +00:00
if(track.TrackSession > currentSession &&
currentSession != 0)
{
(byte minute, byte second, byte frame) leadoutAmsf = LbaToMsf(track.TrackStartSector - 150);
2020-02-29 18:03:35 +00:00
(byte minute, byte second, byte frame) leadoutPmsf =
2020-02-29 18:03:35 +00:00
LbaToMsf(Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).Last().
TrackStartSector);
// Lead-out
trackDescriptors.Add(new FullTOC.TrackDataDescriptor
{
2020-02-29 18:03:35 +00:00
SessionNumber = currentSession, POINT = 0xB0, ADR = 5,
CONTROL = 0,
2020-02-29 18:03:35 +00:00
HOUR = 0, Min = leadoutAmsf.minute, Sec = leadoutAmsf.second,
Frame = leadoutAmsf.frame,
2020-02-29 18:03:35 +00:00
PHOUR = 2, PMIN = leadoutPmsf.minute, PSEC = leadoutPmsf.second,
PFRAME = leadoutPmsf.frame
});
// This seems to be constant? It should not exist on CD-ROM but CloneCD creates them anyway
// Format seems like ATIP, but ATIP should not be as 0xC0 in TOC...
trackDescriptors.Add(new FullTOC.TrackDataDescriptor
{
2020-02-29 18:03:35 +00:00
SessionNumber = currentSession, POINT = 0xC0, ADR = 5, CONTROL = 0,
Min = 128, PMIN = 97, PSEC = 25
});
}
// Lead-in
if(track.TrackSession > currentSession)
{
currentSession = (byte)track.TrackSession;
sessionEndingTrack.TryGetValue(currentSession, out byte endingTrackNumber);
2020-02-29 18:03:35 +00:00
(byte minute, byte second, byte frame) leadinPmsf =
LbaToMsf(Tracks.FirstOrDefault(t => t.TrackSequence == endingTrackNumber).TrackEndSector +
1);
// Starting track
trackDescriptors.Add(new FullTOC.TrackDataDescriptor
{
2020-02-29 18:03:35 +00:00
SessionNumber = currentSession, POINT = 0xA0, ADR = 1, CONTROL = trackControl,
PMIN = (byte)track.TrackSequence
});
// Ending track
trackDescriptors.Add(new FullTOC.TrackDataDescriptor
{
2020-02-29 18:03:35 +00:00
SessionNumber = currentSession, POINT = 0xA1, ADR = 1, CONTROL = trackControl,
PMIN = endingTrackNumber
});
// Lead-out start
trackDescriptors.Add(new FullTOC.TrackDataDescriptor
{
2020-02-29 18:03:35 +00:00
SessionNumber = currentSession, POINT = 0xA2, ADR = 1,
CONTROL = trackControl,
2020-02-29 18:03:35 +00:00
PHOUR = 0, PMIN = leadinPmsf.minute, PSEC = leadinPmsf.second,
PFRAME = leadinPmsf.frame
});
}
(byte minute, byte second, byte frame) pmsf = LbaToMsf(track.TrackStartSector);
// Track
trackDescriptors.Add(new FullTOC.TrackDataDescriptor
{
2020-02-29 18:03:35 +00:00
SessionNumber = (byte)track.TrackSession, POINT = (byte)track.TrackSequence, ADR = 1,
CONTROL = trackControl, PHOUR = 0, PMIN = pmsf.minute,
PSEC = pmsf.second,
PFRAME = pmsf.frame
});
}
toc.TrackDescriptors = trackDescriptors.ToArray();
}
2020-02-29 18:03:35 +00:00
else
toc = nullableToc.Value;
descriptorStream.WriteLine("[CloneCD]");
descriptorStream.WriteLine("Version=2");
descriptorStream.WriteLine("[Disc]");
descriptorStream.WriteLine("TocEntries={0}", toc.TrackDescriptors.Length);
2020-02-29 18:03:35 +00:00
descriptorStream.WriteLine("Sessions={0}", toc.LastCompleteSession);
descriptorStream.WriteLine("DataTracksScrambled=0");
descriptorStream.WriteLine("CDTextLength=0");
2020-02-29 18:03:35 +00:00
if(!string.IsNullOrEmpty(catalog))
descriptorStream.WriteLine("CATALOG={0}", catalog);
for(int i = 1; i <= toc.LastCompleteSession; i++)
{
// TODO: Use first track of session info
descriptorStream.WriteLine("[Session {0}]", i);
descriptorStream.WriteLine("PreGapMode=0");
descriptorStream.WriteLine("PreGapSubC=0");
}
for(int i = 0; i < toc.TrackDescriptors.Length; i++)
{
long alba = MsfToLba((toc.TrackDescriptors[i].Min, toc.TrackDescriptors[i].Sec,
toc.TrackDescriptors[i].Frame));
2020-02-29 18:03:35 +00:00
long plba = MsfToLba((toc.TrackDescriptors[i].PMIN, toc.TrackDescriptors[i].PSEC,
toc.TrackDescriptors[i].PFRAME));
2020-02-29 18:03:35 +00:00
if(alba > 405000)
alba = ((alba - 405000) + 300) * -1;
if(plba > 405000)
plba = ((plba - 405000) + 300) * -1;
2020-02-29 18:03:35 +00:00
descriptorStream.WriteLine("[Entry {0}]", i);
descriptorStream.WriteLine("Session={0}", toc.TrackDescriptors[i].SessionNumber);
descriptorStream.WriteLine("Point=0x{0:x2}", toc.TrackDescriptors[i].POINT);
descriptorStream.WriteLine("ADR=0x{0:x2}", toc.TrackDescriptors[i].ADR);
descriptorStream.WriteLine("Control=0x{0:x2}", toc.TrackDescriptors[i].CONTROL);
2020-02-29 18:03:35 +00:00
descriptorStream.WriteLine("TrackNo={0}", toc.TrackDescriptors[i].TNO);
descriptorStream.WriteLine("AMin={0}", toc.TrackDescriptors[i].Min);
descriptorStream.WriteLine("ASec={0}", toc.TrackDescriptors[i].Sec);
descriptorStream.WriteLine("AFrame={0}", toc.TrackDescriptors[i].Frame);
descriptorStream.WriteLine("ALBA={0}", alba);
descriptorStream.WriteLine("Zero={0}",
((toc.TrackDescriptors[i].HOUR & 0x0F) << 4) +
(toc.TrackDescriptors[i].PHOUR & 0x0F));
2020-02-29 18:03:35 +00:00
descriptorStream.WriteLine("PMin={0}", toc.TrackDescriptors[i].PMIN);
descriptorStream.WriteLine("PSec={0}", toc.TrackDescriptors[i].PSEC);
descriptorStream.WriteLine("PFrame={0}", toc.TrackDescriptors[i].PFRAME);
2020-02-29 18:03:35 +00:00
descriptorStream.WriteLine("PLBA={0}", plba);
}
descriptorStream.Flush();
descriptorStream.Close();
IsWriting = false;
ErrorMessage = "";
2020-02-29 18:03:35 +00:00
return true;
}
2018-12-31 13:17:27 +00:00
public bool SetMetadata(ImageInfo metadata) => true;
public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack)
{
ErrorMessage = "Unsupported feature";
2020-02-29 18:03:35 +00:00
return false;
}
public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
2020-02-29 18:03:35 +00:00
return false;
}
Track track =
Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector &&
sectorAddress <= trk.TrackEndSector);
if(track.TrackSequence == 0)
{
ErrorMessage = $"Can't found track containing {sectorAddress}";
2020-02-29 18:03:35 +00:00
return false;
}
switch(tag)
{
case SectorTagType.CdTrackFlags:
{
if(data.Length != 1)
{
ErrorMessage = "Incorrect data size for track flags";
2020-02-29 18:03:35 +00:00
return false;
}
trackFlags.Add((byte)track.TrackSequence, data[0]);
return true;
}
case SectorTagType.CdSectorSubchannel:
{
if(track.TrackSubchannelType == 0)
{
ErrorMessage =
$"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel";
2020-02-29 18:03:35 +00:00
return false;
}
if(data.Length != 96)
{
ErrorMessage = "Incorrect data size for subchannel";
2020-02-29 18:03:35 +00:00
return false;
}
if(subStream == null)
try
{
subStream = new FileStream(writingBaseName + ".sub", FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.None);
}
catch(IOException e)
{
ErrorMessage = $"Could not create subchannel file, exception {e.Message}";
2020-02-29 18:03:35 +00:00
return false;
}
2020-02-29 18:03:35 +00:00
subStream.Seek((long)(track.TrackSubchannelOffset + ((sectorAddress - track.TrackStartSector) * 96)),
SeekOrigin.Begin);
2020-02-29 18:03:35 +00:00
subStream.Write(data, 0, data.Length);
return true;
}
default:
ErrorMessage = $"Unsupported tag type {tag}";
2020-02-29 18:03:35 +00:00
return false;
}
}
public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag)
{
if(!IsWriting)
{
ErrorMessage = "Tried to write on a non-writable image";
2020-02-29 18:03:35 +00:00
return false;
}
Track track =
Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector &&
sectorAddress <= trk.TrackEndSector);
if(track.TrackSequence == 0)
{
ErrorMessage = $"Can't found track containing {sectorAddress}";
2020-02-29 18:03:35 +00:00
return false;
}
switch(tag)
{
case SectorTagType.CdTrackFlags:
case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag);
case SectorTagType.CdSectorSubchannel:
{
if(track.TrackSubchannelType == 0)
{
ErrorMessage =
$"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel";
2020-02-29 18:03:35 +00:00
return false;
}
if(data.Length % 96 != 0)
{
ErrorMessage = "Incorrect data size for subchannel";
2020-02-29 18:03:35 +00:00
return false;
}
if(subStream == null)
try
{
subStream = new FileStream(writingBaseName + ".sub", FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.None);
}
catch(IOException e)
{
ErrorMessage = $"Could not create subchannel file, exception {e.Message}";
2020-02-29 18:03:35 +00:00
return false;
}
2020-02-29 18:03:35 +00:00
subStream.Seek((long)(track.TrackSubchannelOffset + ((sectorAddress - track.TrackStartSector) * 96)),
SeekOrigin.Begin);
2020-02-29 18:03:35 +00:00
subStream.Write(data, 0, data.Length);
return true;
}
default:
ErrorMessage = $"Unsupported tag type {tag}";
2020-02-29 18:03:35 +00:00
return false;
}
}
2018-12-31 13:17:27 +00:00
public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) => false;
2018-12-31 13:17:27 +00:00
public bool SetCicmMetadata(CICMMetadataType metadata) => false;
}
}