Files
Aaru/Aaru.Core/Media/CompactDisc.cs

1501 lines
54 KiB
C#
Raw Normal View History

2020-07-19 22:01:25 +01:00
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : CompactDisc.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Core algorithms.
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2020 Natalia Portillo
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using Aaru.Checksums;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Core.Logging;
using Aaru.Decoders.CD;
using Aaru.Devices;
2020-07-20 15:43:52 +01:00
using Aaru.Helpers;
namespace Aaru.Core.Media
{
public static class CompactDisc
{
// Return true if indexes have changed
public static bool WriteSubchannelToImage(MmcSubchannel supportedSubchannel, MmcSubchannel desiredSubchannel,
byte[] sub, ulong sectorAddress, uint length, SubchannelLog subLog,
Dictionary<byte, string> isrcs, byte currentTrack, ref string mcn,
Track[] tracks, HashSet<int> subchannelExtents,
bool fixSubchannelPosition, IWritableImage outputPlugin,
bool fixSubchannel, bool fixSubchannelCrc, DumpLog dumpLog,
UpdateStatusHandler updateStatus,
Dictionary<byte, int> smallestPregapLbaPerTrack)
{
if(supportedSubchannel == MmcSubchannel.Q16)
sub = Subchannel.ConvertQToRaw(sub);
if(!fixSubchannelPosition &&
desiredSubchannel != MmcSubchannel.None)
outputPlugin.WriteSectorsTag(sub, sectorAddress, length, SectorTagType.CdSectorSubchannel);
subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)sectorAddress, length, false,
false);
byte[] deSub = Subchannel.Deinterleave(sub);
bool indexesChanged = CheckIndexesFromSubchannel(deSub, isrcs, currentTrack, ref mcn, tracks, dumpLog,
updateStatus, smallestPregapLbaPerTrack);
if(!fixSubchannelPosition ||
desiredSubchannel == MmcSubchannel.None)
return indexesChanged;
int prePos = int.MinValue;
// Check subchannel
for(int subPos = 0; subPos < deSub.Length; subPos += 96)
{
long lba = (long)sectorAddress + (subPos / 96);
bool @fixed = false;
byte[] q = new byte[12];
Array.Copy(deSub, subPos + 12, q, 0, 12);
CRC16CCITTContext.Data(q, 10, out byte[] crc);
bool crcOk = crc[0] == q[10] && crc[1] == q[11];
bool pOk = true;
int pWeight = 0;
bool rwOk = true;
for(int p = subPos; p < subPos + 12; p++)
{
if(deSub[p] != 0 &&
deSub[p] != 255)
pOk = false;
for(int w = 0; w < 8; w++)
if(((deSub[p] >> w) & 1) > 0)
pWeight++;
}
for(int rw = subPos + 24; rw < subPos + 96; rw++)
{
if(deSub[rw] == 0)
continue;
rwOk = false;
break;
}
bool rwPacket = false;
bool cdtextPacket = false;
if(!rwOk)
{
byte[] sectorSub = new byte[96];
Array.Copy(sub, subPos, sectorSub, 0, 96);
DetectRwPackets(sectorSub, out _, out rwPacket, out cdtextPacket);
// TODO: CD+G reed solomon
if(rwPacket && !cdtextPacket)
rwOk = true;
if(cdtextPacket)
rwOk = CheckCdTextPackets(sectorSub);
}
if(!pOk && fixSubchannel)
{
if(pWeight >= 48)
for(int p = subPos; p < subPos + 12; p++)
deSub[p] = 255;
else
for(int p = subPos; p < subPos + 12; p++)
deSub[p] = 0;
pOk = true;
@fixed = true;
subLog.WritePFix(lba);
}
if(!rwOk &&
!rwPacket &&
!cdtextPacket &&
fixSubchannel)
{
for(int rw = subPos + 24; rw < subPos + 96; rw++)
deSub[rw] = 0;
rwOk = true;
@fixed = true;
subLog.WriteRwFix(lba);
}
byte smin, ssec, amin, asec, aframe;
int aPos;
if(!crcOk &&
fixSubchannel &&
subPos > 0 &&
subPos < deSub.Length - 96)
{
isrcs.TryGetValue(currentTrack, out string knownGoodIsrc);
crcOk = FixQSubchannel(deSub, q, subPos, mcn, knownGoodIsrc, fixSubchannelCrc, out bool fixedAdr,
out bool controlFix, out bool fixedZero, out bool fixedTno,
out bool fixedIndex, out bool fixedRelPos, out bool fixedAbsPos,
out bool fixedCrc, out bool fixedMcn, out bool fixedIsrc);
if(crcOk)
{
Array.Copy(q, 0, deSub, subPos + 12, 12);
@fixed = true;
if(fixedAdr)
subLog.WriteQAdrFix(lba);
if(controlFix)
subLog.WriteQCtrlFix(lba);
if(fixedZero)
subLog.WriteQZeroFix(lba);
if(fixedTno)
subLog.WriteQTnoFix(lba);
if(fixedIndex)
subLog.WriteQIndexFix(lba);
if(fixedRelPos)
subLog.WriteQRelPosFix(lba);
if(fixedAbsPos)
subLog.WriteQAbsPosFix(lba);
if(fixedCrc)
subLog.WriteQCrcFix(lba);
if(fixedMcn)
subLog.WriteQMcnFix(lba);
if(fixedIsrc)
subLog.WriteQIsrcFix(lba);
}
}
if(!pOk ||
!crcOk ||
!rwOk)
continue;
aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F));
if((q[0] & 0x3) == 1)
{
amin = (byte)(((q[7] / 16) * 10) + (q[7] & 0x0F));
asec = (byte)(((q[8] / 16) * 10) + (q[8] & 0x0F));
aPos = ((amin * 60 * 75) + (asec * 75) + aframe) - 150;
}
else
{
ulong expectedSectorAddress = sectorAddress + (ulong)(subPos / 96) + 150;
smin = (byte)(expectedSectorAddress / 60 / 75);
expectedSectorAddress -= (ulong)(smin * 60 * 75);
ssec = (byte)(expectedSectorAddress / 75);
aPos = ((smin * 60 * 75) + (ssec * 75) + aframe) - 150;
// Next second
if(aPos < prePos)
aPos += 75;
}
// TODO: Negative sectors
if(aPos < 0)
continue;
prePos = aPos;
byte[] posSub = new byte[96];
Array.Copy(deSub, subPos, posSub, 0, 96);
posSub = Subchannel.Interleave(posSub);
outputPlugin.WriteSectorTag(posSub, (ulong)aPos, SectorTagType.CdSectorSubchannel);
subchannelExtents.Remove(aPos);
if(@fixed)
subLog?.WriteEntry(posSub, supportedSubchannel == MmcSubchannel.Raw, lba, 1, false, true);
}
return indexesChanged;
}
static bool CheckIndexesFromSubchannel(byte[] deSub, Dictionary<byte, string> isrcs, byte currentTrack,
ref string mcn, Track[] tracks, DumpLog dumpLog,
UpdateStatusHandler updateStatus,
Dictionary<byte, int> smallestPregapLbaPerTrack)
{
bool status = false;
// Check subchannel
for(int subPos = 0; subPos < deSub.Length; subPos += 96)
{
byte[] q = new byte[12];
Array.Copy(deSub, subPos + 12, q, 0, 12);
CRC16CCITTContext.Data(q, 10, out byte[] crc);
bool crcOk = crc[0] == q[10] && crc[1] == q[11];
// ISRC
if((q[0] & 0x3) == 3)
{
string isrc = Subchannel.DecodeIsrc(q);
if(isrc == null ||
isrc == "000000000000")
continue;
if(!crcOk)
continue;
if(!isrcs.ContainsKey(currentTrack))
{
dumpLog?.WriteLine($"Found new ISRC {isrc} for track {currentTrack}.");
updateStatus?.Invoke($"Found new ISRC {isrc} for track {currentTrack}.");
}
else if(isrcs[currentTrack] != isrc)
{
dumpLog?.
WriteLine($"ISRC for track {currentTrack} changed from {isrcs[currentTrack]} to {isrc}.");
updateStatus?.
Invoke($"ISRC for track {currentTrack} changed from {isrcs[currentTrack]} to {isrc}.");
}
isrcs[currentTrack] = isrc;
}
else if((q[0] & 0x3) == 2)
{
string newMcn = Subchannel.DecodeMcn(q);
if(newMcn == null ||
newMcn == "0000000000000")
continue;
if(!crcOk)
continue;
if(mcn is null)
{
dumpLog?.WriteLine($"Found new MCN {newMcn}.");
updateStatus?.Invoke($"Found new MCN {newMcn}.");
}
else if(mcn != newMcn)
{
dumpLog?.WriteLine($"MCN changed from {mcn} to {newMcn}.");
updateStatus?.Invoke($"MCN changed from {mcn} to {newMcn}.");
}
mcn = newMcn;
}
else if((q[0] & 0x3) == 1)
{
if(!crcOk)
continue;
byte trackNo = (byte)(((q[1] / 16) * 10) + (q[1] & 0x0F));
for(int i = 0; i < tracks.Length; i++)
{
2020-07-18 21:03:26 +01:00
if(tracks[i].TrackSequence != trackNo)
continue;
// Pregap
2020-07-18 21:03:26 +01:00
if(q[2] == 0 &&
trackNo > 1)
{
byte pmin = (byte)(((q[3] / 16) * 10) + (q[3] & 0x0F));
byte psec = (byte)(((q[4] / 16) * 10) + (q[4] & 0x0F));
byte pframe = (byte)(((q[5] / 16) * 10) + (q[5] & 0x0F));
int qPos = (pmin * 60 * 75) + (psec * 75) + pframe;
if(!smallestPregapLbaPerTrack.ContainsKey(trackNo))
smallestPregapLbaPerTrack[trackNo] = 1;
if(qPos < smallestPregapLbaPerTrack[trackNo])
{
int dif = smallestPregapLbaPerTrack[trackNo] - qPos;
tracks[i].TrackPregap += (ulong)dif;
tracks[i].TrackStartSector -= (ulong)dif;
smallestPregapLbaPerTrack[trackNo] = qPos;
if(i > 0)
tracks[i - 1].TrackEndSector = tracks[i].TrackStartSector - 1;
dumpLog?.
WriteLine($"Pregap for track {trackNo} set to {tracks[i].TrackPregap} sectors.");
updateStatus?.
Invoke($"Pregap for track {trackNo} set to {tracks[i].TrackPregap} sectors.");
status = true;
}
if(tracks[i].TrackPregap >= (ulong)qPos)
continue;
ulong oldPregap = tracks[i].TrackPregap;
tracks[i].TrackPregap = (ulong)qPos;
tracks[i].TrackStartSector -= tracks[i].TrackPregap - oldPregap;
if(i > 0)
tracks[i - 1].TrackEndSector = tracks[i].TrackStartSector - 1;
dumpLog?.WriteLine($"Pregap for track {trackNo} set to {tracks[i].TrackPregap} sectors.");
updateStatus?.Invoke($"Pregap for track {trackNo} set to {tracks[i].TrackPregap} sectors.");
status = true;
continue;
}
2020-07-18 21:03:26 +01:00
if(q[2] == 0)
continue;
byte amin = (byte)(((q[7] / 16) * 10) + (q[7] & 0x0F));
byte asec = (byte)(((q[8] / 16) * 10) + (q[8] & 0x0F));
byte aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F));
int aPos = ((amin * 60 * 75) + (asec * 75) + aframe) - 150;
if(tracks[i].Indexes.ContainsKey(q[2]) &&
aPos >= tracks[i].Indexes[q[2]])
continue;
dumpLog?.WriteLine($"Setting index {q[2]} for track {trackNo} to LBA {aPos}.");
updateStatus?.Invoke($"Setting index {q[2]} for track {trackNo} to LBA {aPos}.");
tracks[i].Indexes[q[2]] = aPos;
status = true;
}
}
}
return status;
}
static void DetectRwPackets(byte[] subchannel, out bool zero, out bool rwPacket, out bool cdtextPacket)
{
zero = false;
rwPacket = false;
cdtextPacket = false;
byte[] cdTextPack1 = new byte[18];
byte[] cdTextPack2 = new byte[18];
byte[] cdTextPack3 = new byte[18];
byte[] cdTextPack4 = new byte[18];
byte[] cdSubRwPack1 = new byte[24];
byte[] cdSubRwPack2 = new byte[24];
byte[] cdSubRwPack3 = new byte[24];
byte[] cdSubRwPack4 = new byte[24];
int i = 0;
for(int j = 0; j < 18; j++)
{
cdTextPack1[j] = (byte)(cdTextPack1[j] | ((subchannel[i++] & 0x3F) << 2));
cdTextPack1[j] = (byte)(cdTextPack1[j++] | ((subchannel[i] & 0xC0) >> 4));
if(j < 18)
cdTextPack1[j] = (byte)(cdTextPack1[j] | ((subchannel[i++] & 0x0F) << 4));
if(j < 18)
cdTextPack1[j] = (byte)(cdTextPack1[j++] | ((subchannel[i] & 0x3C) >> 2));
if(j < 18)
cdTextPack1[j] = (byte)(cdTextPack1[j] | ((subchannel[i++] & 0x03) << 6));
if(j < 18)
cdTextPack1[j] = (byte)(cdTextPack1[j] | (subchannel[i++] & 0x3F));
}
for(int j = 0; j < 18; j++)
{
cdTextPack2[j] = (byte)(cdTextPack2[j] | ((subchannel[i++] & 0x3F) << 2));
cdTextPack2[j] = (byte)(cdTextPack2[j++] | ((subchannel[i] & 0xC0) >> 4));
if(j < 18)
cdTextPack2[j] = (byte)(cdTextPack2[j] | ((subchannel[i++] & 0x0F) << 4));
if(j < 18)
cdTextPack2[j] = (byte)(cdTextPack2[j++] | ((subchannel[i] & 0x3C) >> 2));
if(j < 18)
cdTextPack2[j] = (byte)(cdTextPack2[j] | ((subchannel[i++] & 0x03) << 6));
if(j < 18)
cdTextPack2[j] = (byte)(cdTextPack2[j] | (subchannel[i++] & 0x3F));
}
for(int j = 0; j < 18; j++)
{
cdTextPack3[j] = (byte)(cdTextPack3[j] | ((subchannel[i++] & 0x3F) << 2));
cdTextPack3[j] = (byte)(cdTextPack3[j++] | ((subchannel[i] & 0xC0) >> 4));
if(j < 18)
cdTextPack3[j] = (byte)(cdTextPack3[j] | ((subchannel[i++] & 0x0F) << 4));
if(j < 18)
cdTextPack3[j] = (byte)(cdTextPack3[j++] | ((subchannel[i] & 0x3C) >> 2));
if(j < 18)
cdTextPack3[j] = (byte)(cdTextPack3[j] | ((subchannel[i++] & 0x03) << 6));
if(j < 18)
cdTextPack3[j] = (byte)(cdTextPack3[j] | (subchannel[i++] & 0x3F));
}
for(int j = 0; j < 18; j++)
{
cdTextPack4[j] = (byte)(cdTextPack4[j] | ((subchannel[i++] & 0x3F) << 2));
cdTextPack4[j] = (byte)(cdTextPack4[j++] | ((subchannel[i] & 0xC0) >> 4));
if(j < 18)
cdTextPack4[j] = (byte)(cdTextPack4[j] | ((subchannel[i++] & 0x0F) << 4));
if(j < 18)
cdTextPack4[j] = (byte)(cdTextPack4[j++] | ((subchannel[i] & 0x3C) >> 2));
if(j < 18)
cdTextPack4[j] = (byte)(cdTextPack4[j] | ((subchannel[i++] & 0x03) << 6));
if(j < 18)
cdTextPack4[j] = (byte)(cdTextPack4[j] | (subchannel[i++] & 0x3F));
}
i = 0;
for(int j = 0; j < 24; j++)
cdSubRwPack1[j] = (byte)(subchannel[i++] & 0x3F);
for(int j = 0; j < 24; j++)
cdSubRwPack2[j] = (byte)(subchannel[i++] & 0x3F);
for(int j = 0; j < 24; j++)
cdSubRwPack3[j] = (byte)(subchannel[i++] & 0x3F);
for(int j = 0; j < 24; j++)
cdSubRwPack4[j] = (byte)(subchannel[i++] & 0x3F);
switch(cdSubRwPack1[0])
{
case 0x00:
zero = true;
break;
case 0x08:
case 0x09:
case 0x0A:
case 0x18:
case 0x38:
rwPacket = true;
break;
case 0x14:
cdtextPacket = true;
break;
}
switch(cdSubRwPack2[0])
{
case 0x00:
zero = true;
break;
case 0x08:
case 0x09:
case 0x0A:
case 0x18:
case 0x38:
rwPacket = true;
break;
case 0x14:
cdtextPacket = true;
break;
}
switch(cdSubRwPack3[0])
{
case 0x00:
zero = true;
break;
case 0x08:
case 0x09:
case 0x0A:
case 0x18:
case 0x38:
rwPacket = true;
break;
case 0x14:
cdtextPacket = true;
break;
}
switch(cdSubRwPack4[0])
{
case 0x00:
zero = true;
break;
case 0x08:
case 0x09:
case 0x0A:
case 0x18:
case 0x38:
rwPacket = true;
break;
case 0x14:
cdtextPacket = true;
break;
}
if((cdTextPack1[0] & 0x80) == 0x80)
cdtextPacket = true;
if((cdTextPack2[0] & 0x80) == 0x80)
cdtextPacket = true;
if((cdTextPack3[0] & 0x80) == 0x80)
cdtextPacket = true;
if((cdTextPack4[0] & 0x80) == 0x80)
cdtextPacket = true;
}
static bool CheckCdTextPackets(byte[] subchannel)
{
byte[] cdTextPack1 = new byte[18];
byte[] cdTextPack2 = new byte[18];
byte[] cdTextPack3 = new byte[18];
byte[] cdTextPack4 = new byte[18];
int i = 0;
for(int j = 0; j < 18; j++)
{
cdTextPack1[j] = (byte)(cdTextPack1[j] | ((subchannel[i++] & 0x3F) << 2));
cdTextPack1[j] = (byte)(cdTextPack1[j++] | ((subchannel[i] & 0xC0) >> 4));
if(j < 18)
cdTextPack1[j] = (byte)(cdTextPack1[j] | ((subchannel[i++] & 0x0F) << 4));
if(j < 18)
cdTextPack1[j] = (byte)(cdTextPack1[j++] | ((subchannel[i] & 0x3C) >> 2));
if(j < 18)
cdTextPack1[j] = (byte)(cdTextPack1[j] | ((subchannel[i++] & 0x03) << 6));
if(j < 18)
cdTextPack1[j] = (byte)(cdTextPack1[j] | (subchannel[i++] & 0x3F));
}
for(int j = 0; j < 18; j++)
{
cdTextPack2[j] = (byte)(cdTextPack2[j] | ((subchannel[i++] & 0x3F) << 2));
cdTextPack2[j] = (byte)(cdTextPack2[j++] | ((subchannel[i] & 0xC0) >> 4));
if(j < 18)
cdTextPack2[j] = (byte)(cdTextPack2[j] | ((subchannel[i++] & 0x0F) << 4));
if(j < 18)
cdTextPack2[j] = (byte)(cdTextPack2[j++] | ((subchannel[i] & 0x3C) >> 2));
if(j < 18)
cdTextPack2[j] = (byte)(cdTextPack2[j] | ((subchannel[i++] & 0x03) << 6));
if(j < 18)
cdTextPack2[j] = (byte)(cdTextPack2[j] | (subchannel[i++] & 0x3F));
}
for(int j = 0; j < 18; j++)
{
cdTextPack3[j] = (byte)(cdTextPack3[j] | ((subchannel[i++] & 0x3F) << 2));
cdTextPack3[j] = (byte)(cdTextPack3[j++] | ((subchannel[i] & 0xC0) >> 4));
if(j < 18)
cdTextPack3[j] = (byte)(cdTextPack3[j] | ((subchannel[i++] & 0x0F) << 4));
if(j < 18)
cdTextPack3[j] = (byte)(cdTextPack3[j++] | ((subchannel[i] & 0x3C) >> 2));
if(j < 18)
cdTextPack3[j] = (byte)(cdTextPack3[j] | ((subchannel[i++] & 0x03) << 6));
if(j < 18)
cdTextPack3[j] = (byte)(cdTextPack3[j] | (subchannel[i++] & 0x3F));
}
for(int j = 0; j < 18; j++)
{
cdTextPack4[j] = (byte)(cdTextPack4[j] | ((subchannel[i++] & 0x3F) << 2));
cdTextPack4[j] = (byte)(cdTextPack4[j++] | ((subchannel[i] & 0xC0) >> 4));
if(j < 18)
cdTextPack4[j] = (byte)(cdTextPack4[j] | ((subchannel[i++] & 0x0F) << 4));
if(j < 18)
cdTextPack4[j] = (byte)(cdTextPack4[j++] | ((subchannel[i] & 0x3C) >> 2));
if(j < 18)
cdTextPack4[j] = (byte)(cdTextPack4[j] | ((subchannel[i++] & 0x03) << 6));
if(j < 18)
cdTextPack4[j] = (byte)(cdTextPack4[j] | (subchannel[i++] & 0x3F));
}
bool status = true;
if((cdTextPack1[0] & 0x80) == 0x80)
{
ushort cdTextPack1Crc = BigEndianBitConverter.ToUInt16(cdTextPack1, 16);
byte[] cdTextPack1ForCrc = new byte[16];
Array.Copy(cdTextPack1, 0, cdTextPack1ForCrc, 0, 16);
ushort calculatedCdtp1Crc = CRC16CCITTContext.Calculate(cdTextPack1ForCrc);
if(cdTextPack1Crc != calculatedCdtp1Crc &&
cdTextPack1Crc != 0)
status = false;
}
if((cdTextPack2[0] & 0x80) == 0x80)
{
ushort cdTextPack2Crc = BigEndianBitConverter.ToUInt16(cdTextPack2, 16);
byte[] cdTextPack2ForCrc = new byte[16];
Array.Copy(cdTextPack2, 0, cdTextPack2ForCrc, 0, 16);
ushort calculatedCdtp2Crc = CRC16CCITTContext.Calculate(cdTextPack2ForCrc);
if(cdTextPack2Crc != calculatedCdtp2Crc &&
cdTextPack2Crc != 0)
status = false;
}
if((cdTextPack3[0] & 0x80) == 0x80)
{
ushort cdTextPack3Crc = BigEndianBitConverter.ToUInt16(cdTextPack3, 16);
byte[] cdTextPack3ForCrc = new byte[16];
Array.Copy(cdTextPack3, 0, cdTextPack3ForCrc, 0, 16);
ushort calculatedCdtp3Crc = CRC16CCITTContext.Calculate(cdTextPack3ForCrc);
if(cdTextPack3Crc != calculatedCdtp3Crc &&
cdTextPack3Crc != 0)
status = false;
}
if((cdTextPack4[0] & 0x80) != 0x80)
return status;
ushort cdTextPack4Crc = BigEndianBitConverter.ToUInt16(cdTextPack4, 16);
byte[] cdTextPack4ForCrc = new byte[16];
Array.Copy(cdTextPack4, 0, cdTextPack4ForCrc, 0, 16);
ushort calculatedCdtp4Crc = CRC16CCITTContext.Calculate(cdTextPack4ForCrc);
if(cdTextPack4Crc == calculatedCdtp4Crc ||
cdTextPack4Crc == 0)
return status;
return false;
}
static bool FixQSubchannel(byte[] deSub, byte[] q, int subPos, string mcn, string isrc, bool fixCrc,
out bool fixedAdr, out bool controlFix, out bool fixedZero, out bool fixedTno,
out bool fixedIndex, out bool fixedRelPos, out bool fixedAbsPos, out bool fixedCrc,
out bool fixedMcn, out bool fixedIsrc)
{
byte amin, asec, aframe, pmin, psec, pframe;
byte rmin, rsec, rframe;
int aPos, rPos, pPos, dPos;
controlFix = false;
fixedZero = false;
fixedTno = false;
fixedIndex = false;
fixedRelPos = false;
fixedAbsPos = false;
fixedCrc = false;
fixedMcn = false;
fixedIsrc = false;
byte[] preQ = new byte[12];
byte[] nextQ = new byte[12];
Array.Copy(deSub, (subPos + 12) - 96, preQ, 0, 12);
Array.Copy(deSub, subPos + 12 + 96, nextQ, 0, 12);
bool status;
CRC16CCITTContext.Data(preQ, 10, out byte[] preCrc);
bool preCrcOk = preCrc[0] == preQ[10] && preCrc[1] == preQ[11];
CRC16CCITTContext.Data(nextQ, 10, out byte[] nextCrc);
bool nextCrcOk = nextCrc[0] == nextQ[10] && nextCrc[1] == nextQ[11];
fixedAdr = false;
// Extraneous bits in ADR
if((q[0] & 0xC) != 0)
{
q[0] &= 0xF3;
fixedAdr = true;
}
CRC16CCITTContext.Data(q, 10, out byte[] qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(fixedAdr && status)
return true;
int oldAdr = q[0] & 0x3;
// Try Q-Mode 1
q[0] = (byte)((q[0] & 0xF0) + 1);
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
{
fixedAdr = true;
return true;
}
// Try Q-Mode 2
q[0] = (byte)((q[0] & 0xF0) + 2);
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
{
fixedAdr = true;
return true;
}
// Try Q-Mode 3
q[0] = (byte)((q[0] & 0xF0) + 3);
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
{
fixedAdr = true;
return true;
}
q[0] = (byte)((q[0] & 0xF0) + oldAdr);
oldAdr = q[0];
// Try using previous control
if(preCrcOk && (q[0] & 0xF0) != (preQ[0] & 0xF0))
{
q[0] = (byte)((q[0] & 0x03) + (preQ[0] & 0xF0));
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
{
controlFix = true;
return true;
}
q[0] = (byte)oldAdr;
}
// Try using next control
if(nextCrcOk && (q[0] & 0xF0) != (nextQ[0] & 0xF0))
{
q[0] = (byte)((q[0] & 0x03) + (nextQ[0] & 0xF0));
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
{
controlFix = true;
return true;
}
q[0] = (byte)oldAdr;
}
if(preCrcOk &&
nextCrcOk &&
(nextQ[0] & 0xF0) == (preQ[0] & 0xF0) &&
(q[0] & 0xF0) != (nextQ[0] & 0xF0))
{
q[0] = (byte)((q[0] & 0x03) + (nextQ[0] & 0xF0));
controlFix = true;
}
if((q[0] & 0x3) == 1)
{
// ZERO not zero
if(q[6] != 0)
{
q[6] = 0;
fixedZero = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
if(preCrcOk && nextCrcOk)
{
if(preQ[1] == nextQ[1] &&
preQ[1] != q[1])
{
q[1] = preQ[1];
fixedTno = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
if(preCrcOk && nextCrcOk)
{
if(preQ[2] == nextQ[2] &&
preQ[2] != q[2])
{
q[2] = preQ[2];
fixedIndex = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
amin = (byte)(((q[7] / 16) * 10) + (q[7] & 0x0F));
asec = (byte)(((q[8] / 16) * 10) + (q[8] & 0x0F));
aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F));
aPos = ((amin * 60 * 75) + (asec * 75) + aframe) - 150;
pmin = (byte)(((q[3] / 16) * 10) + (q[3] & 0x0F));
psec = (byte)(((q[4] / 16) * 10) + (q[4] & 0x0F));
pframe = (byte)(((q[5] / 16) * 10) + (q[5] & 0x0F));
pPos = (pmin * 60 * 75) + (psec * 75) + pframe;
// TODO: pregap
// Not pregap
if(q[2] > 0)
{
// Previous was not pregap either
if(preQ[2] > 0 && preCrcOk)
{
rmin = (byte)(((preQ[3] / 16) * 10) + (preQ[3] & 0x0F));
rsec = (byte)(((preQ[4] / 16) * 10) + (preQ[4] & 0x0F));
rframe = (byte)(((preQ[5] / 16) * 10) + (preQ[5] & 0x0F));
rPos = (rmin * 60 * 75) + (rsec * 75) + rframe;
dPos = pPos - rPos;
if(dPos != 1)
{
q[3] = preQ[3];
q[4] = preQ[4];
q[5] = preQ[5];
// BCD add 1, so 0x39 becomes 0x40
if((q[5] & 0xF) == 9)
q[5] += 7;
else
q[5]++;
// 74 frames, so from 0x00 to 0x74, BCD
if(q[5] >= 0x74)
{
// 0 frames
q[5] = 0;
// Add 1 second
if((q[4] & 0xF) == 9)
q[4] += 7;
else
q[4]++;
// 60 seconds, so from 0x00 to 0x59, BCD
if(q[4] >= 0x59)
{
// 0 seconds
q[4] = 0;
// Add 1 minute
q[3]++;
}
}
fixedRelPos = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
// Next is not pregap and we didn't fix relative position with previous
if(nextQ[2] > 0 &&
nextCrcOk &&
!fixedRelPos)
{
rmin = (byte)(((nextQ[3] / 16) * 10) + (nextQ[3] & 0x0F));
rsec = (byte)(((nextQ[4] / 16) * 10) + (nextQ[4] & 0x0F));
rframe = (byte)(((nextQ[5] / 16) * 10) + (nextQ[5] & 0x0F));
rPos = (rmin * 60 * 75) + (rsec * 75) + rframe;
dPos = rPos - pPos;
if(dPos != 1)
{
q[3] = nextQ[3];
q[4] = nextQ[4];
q[5] = nextQ[5];
// If frames is 0
if(q[5] == 0)
{
// If seconds is 0
if(q[4] == 0)
{
// BCD decrease minutes
if((q[3] & 0xF) == 0)
q[3] = (byte)((q[3] & 0xF0) - 0x10);
else
q[3]--;
q[4] = 0x59;
q[5] = 0x73;
}
else
{
// BCD decrease seconds
if((q[4] & 0xF) == 0)
q[4] = (byte)((q[4] & 0xF0) - 0x10);
else
q[4]--;
q[5] = 0x73;
}
}
// BCD decrease frames
else if((q[5] & 0xF) == 0)
q[5] = (byte)((q[5] & 0xF0) - 0x10);
else
q[5]--;
fixedRelPos = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
}
if(preCrcOk)
{
rmin = (byte)(((preQ[7] / 16) * 10) + (preQ[7] & 0x0F));
rsec = (byte)(((preQ[8] / 16) * 10) + (preQ[8] & 0x0F));
rframe = (byte)(((preQ[9] / 16) * 10) + (preQ[9] & 0x0F));
rPos = ((rmin * 60 * 75) + (rsec * 75) + rframe) - 150;
dPos = aPos - rPos;
if(dPos != 1)
{
q[7] = preQ[7];
q[8] = preQ[8];
q[9] = preQ[9];
// BCD add 1, so 0x39 becomes 0x40
if((q[9] & 0xF) == 9)
q[9] += 7;
else
q[9]++;
// 74 frames, so from 0x00 to 0x74, BCD
if(q[9] >= 0x74)
{
// 0 frames
q[9] = 0;
// Add 1 second
if((q[8] & 0xF) == 9)
q[8] += 7;
else
q[8]++;
// 60 seconds, so from 0x00 to 0x59, BCD
if(q[8] >= 0x59)
{
// 0 seconds
q[8] = 0;
// Add 1 minute
q[7]++;
}
}
fixedAbsPos = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
// Next is not pregap and we didn't fix relative position with previous
if(nextQ[2] > 0 &&
nextCrcOk &&
!fixedAbsPos)
{
rmin = (byte)(((nextQ[7] / 16) * 10) + (nextQ[7] & 0x0F));
rsec = (byte)(((nextQ[8] / 16) * 10) + (nextQ[8] & 0x0F));
rframe = (byte)(((nextQ[9] / 16) * 10) + (nextQ[9] & 0x0F));
rPos = ((rmin * 60 * 75) + (rsec * 75) + rframe) - 150;
dPos = rPos - pPos;
if(dPos != 1)
{
q[7] = nextQ[7];
q[8] = nextQ[8];
q[9] = nextQ[9];
// If frames is 0
if(q[9] == 0)
{
// If seconds is 0
if(q[8] == 0)
{
// BCD decrease minutes
if((q[7] & 0xF) == 0)
q[7] = (byte)((q[7] & 0xF0) - 0x10);
else
q[7]--;
q[8] = 0x59;
q[9] = 0x73;
}
else
{
// BCD decrease seconds
if((q[8] & 0xF) == 0)
q[8] = (byte)((q[8] & 0xF0) - 0x10);
else
q[8]--;
q[9] = 0x73;
}
}
// BCD decrease frames
else if((q[9] & 0xF) == 0)
q[9] = (byte)((q[9] & 0xF0) - 0x10);
else
q[9]--;
fixedAbsPos = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
// Game Over
if(!fixCrc || status)
return false;
if(preCrcOk)
{
rmin = (byte)(((preQ[7] / 16) * 10) + (preQ[7] & 0x0F));
rsec = (byte)(((preQ[8] / 16) * 10) + (preQ[8] & 0x0F));
rframe = (byte)(((preQ[9] / 16) * 10) + (preQ[9] & 0x0F));
rPos = ((rmin * 60 * 75) + (rsec * 75) + rframe) - 150;
dPos = aPos - rPos;
bool absOk = dPos == 1;
rmin = (byte)(((preQ[3] / 16) * 10) + (preQ[3] & 0x0F));
rsec = (byte)(((preQ[4] / 16) * 10) + (preQ[4] & 0x0F));
rframe = (byte)(((preQ[5] / 16) * 10) + (preQ[5] & 0x0F));
rPos = (rmin * 60 * 75) + (rsec * 75) + rframe;
dPos = pPos - rPos;
bool relOk = dPos == 1;
if(q[0] != preQ[0] ||
q[1] != preQ[1] ||
q[2] != preQ[2] ||
q[6] != 0 ||
!absOk ||
!relOk)
return false;
CRC16CCITTContext.Data(q, 10, out qCrc);
q[10] = qCrc[0];
q[11] = qCrc[1];
fixedCrc = true;
return true;
}
if(nextCrcOk)
{
rmin = (byte)(((nextQ[7] / 16) * 10) + (nextQ[7] & 0x0F));
rsec = (byte)(((nextQ[8] / 16) * 10) + (nextQ[8] & 0x0F));
rframe = (byte)(((nextQ[9] / 16) * 10) + (nextQ[9] & 0x0F));
rPos = ((rmin * 60 * 75) + (rsec * 75) + rframe) - 150;
dPos = rPos - aPos;
bool absOk = dPos == 1;
rmin = (byte)(((nextQ[3] / 16) * 10) + (nextQ[3] & 0x0F));
rsec = (byte)(((nextQ[4] / 16) * 10) + (nextQ[4] & 0x0F));
rframe = (byte)(((nextQ[5] / 16) * 10) + (nextQ[5] & 0x0F));
rPos = (rmin * 60 * 75) + (rsec * 75) + rframe;
dPos = rPos - pPos;
bool relOk = dPos == 1;
if(q[0] != nextQ[0] ||
q[1] != nextQ[1] ||
q[2] != nextQ[2] ||
q[6] != 0 ||
!absOk ||
!relOk)
return false;
CRC16CCITTContext.Data(q, 10, out qCrc);
q[10] = qCrc[0];
q[11] = qCrc[1];
fixedCrc = true;
return true;
}
// Ok if previous and next are both BAD I won't rewrite the CRC at all
}
else if((q[0] & 0x3) == 2)
{
if(preCrcOk)
{
rframe = (byte)(((preQ[9] / 16) * 10) + (preQ[9] & 0x0F));
aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F));
if(aframe - rframe != 1)
{
q[9] = preQ[9];
if((q[9] & 0xF) == 9)
q[9] += 7;
else
q[9]++;
if(q[9] >= 0x74)
q[9] = 0;
fixedAbsPos = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
else if(nextCrcOk)
{
rframe = (byte)(((nextQ[9] / 16) * 10) + (nextQ[9] & 0x0F));
aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F));
if(aframe - rframe != 1)
{
q[9] = nextQ[9];
if(q[9] == 0)
q[9] = 0x73;
else if((q[9] & 0xF) == 0)
q[9] = (byte)((q[9] & 0xF0) - 0x10);
else
q[9]--;
fixedAbsPos = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
if(mcn != null)
{
q[1] = (byte)((((mcn[0] - 0x30) & 0x0F) * 16) + ((mcn[1] - 0x30) & 0x0F));
q[2] = (byte)((((mcn[2] - 0x30) & 0x0F) * 16) + ((mcn[3] - 0x30) & 0x0F));
q[3] = (byte)((((mcn[4] - 0x30) & 0x0F) * 16) + ((mcn[5] - 0x30) & 0x0F));
q[4] = (byte)((((mcn[6] - 0x30) & 0x0F) * 16) + ((mcn[7] - 0x30) & 0x0F));
q[5] = (byte)((((mcn[8] - 0x30) & 0x0F) * 16) + ((mcn[9] - 0x30) & 0x0F));
q[6] = (byte)((((mcn[10] - 0x30) & 0x0F) * 16) + ((mcn[11] - 0x30) & 0x0F));
q[7] = (byte)(((mcn[12] - 0x30) & 0x0F) * 8);
q[8] = 0;
fixedMcn = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
if(!fixCrc ||
!nextCrcOk ||
!preCrcOk)
return false;
CRC16CCITTContext.Data(q, 10, out qCrc);
q[10] = qCrc[0];
q[11] = qCrc[1];
fixedCrc = true;
return true;
}
else if((q[0] & 0x3) == 3)
{
if(preCrcOk)
{
rframe = (byte)(((preQ[9] / 16) * 10) + (preQ[9] & 0x0F));
aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F));
if(aframe - rframe != 1)
{
q[9] = preQ[9];
if((q[9] & 0xF) == 9)
q[9] += 7;
else
q[9]++;
if(q[9] >= 0x74)
q[9] = 0;
fixedAbsPos = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
else if(nextCrcOk)
{
rframe = (byte)(((nextQ[9] / 16) * 10) + (nextQ[9] & 0x0F));
aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F));
if(aframe - rframe != 1)
{
q[9] = nextQ[9];
if(q[9] == 0)
q[9] = 0x73;
else if((q[9] & 0xF) == 0)
q[9] = (byte)((q[9] & 0xF0) - 0x10);
else
q[9]--;
fixedAbsPos = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
}
if(isrc != null)
{
byte i1 = Subchannel.GetIsrcCode(isrc[0]);
byte i2 = Subchannel.GetIsrcCode(isrc[1]);
byte i3 = Subchannel.GetIsrcCode(isrc[2]);
byte i4 = Subchannel.GetIsrcCode(isrc[3]);
byte i5 = Subchannel.GetIsrcCode(isrc[4]);
q[1] = (byte)((i1 << 2) + ((i2 & 0x30) >> 4));
q[2] = (byte)(((i2 & 0xF) << 4) + (i3 >> 2));
q[3] = (byte)(((i3 & 0x3) << 6) + i4);
q[4] = (byte)(i5 << 2);
q[5] = (byte)((((isrc[5] - 0x30) & 0x0F) * 16) + ((isrc[6] - 0x30) & 0x0F));
q[6] = (byte)((((isrc[7] - 0x30) & 0x0F) * 16) + ((isrc[8] - 0x30) & 0x0F));
q[7] = (byte)((((isrc[9] - 0x30) & 0x0F) * 16) + ((isrc[10] - 0x30) & 0x0F));
q[8] = (byte)(((isrc[11] - 0x30) & 0x0F) * 16);
fixedIsrc = true;
CRC16CCITTContext.Data(q, 10, out qCrc);
status = qCrc[0] == q[10] && qCrc[1] == q[11];
if(status)
return true;
}
if(!fixCrc ||
!nextCrcOk ||
!preCrcOk)
return false;
CRC16CCITTContext.Data(q, 10, out qCrc);
q[10] = qCrc[0];
q[11] = qCrc[1];
fixedCrc = true;
return true;
}
return false;
}
public static void GenerateSubchannels(HashSet<int> subchannelExtents, Track[] tracks,
Dictionary<byte, byte> trackFlags, ulong blocks, SubchannelLog subLog,
DumpLog dumpLog, InitProgressHandler initProgress,
UpdateProgressHandler updateProgress, EndProgressHandler endProgress,
IWritableImage outputPlugin)
{
initProgress?.Invoke();
foreach(int sector in subchannelExtents)
{
Track track = tracks.LastOrDefault(t => (int)t.TrackStartSector <= sector);
byte trkFlags;
byte flags;
ulong trackStart;
ulong pregap;
// Hidden track
if(track.TrackSequence == 0)
{
track = tracks.FirstOrDefault(t => (int)t.TrackSequence == 1);
trackStart = 0;
pregap = track.TrackStartSector;
}
else
{
trackStart = track.TrackStartSector;
pregap = track.TrackPregap;
}
if(!trackFlags.TryGetValue((byte)track.TrackSequence, out trkFlags) &&
track.TrackType != TrackType.Audio)
flags = (byte)CdFlags.DataTrack;
else
flags = trkFlags;
byte index;
if(track.Indexes?.Count > 0)
index = (byte)track.Indexes.LastOrDefault(i => i.Value >= sector).Key;
else
index = 0;
updateProgress?.Invoke($"Generating subchannel for sector {sector}...", sector, (long)blocks);
dumpLog?.WriteLine($"Generating subchannel for sector {sector}.");
byte[] sub = Subchannel.Generate(sector, track.TrackSequence, (int)pregap, (int)trackStart, flags,
index);
outputPlugin.WriteSectorsTag(sub, (ulong)sector, 1, SectorTagType.CdSectorSubchannel);
subLog.WriteEntry(sub, true, sector, 1, true, false);
}
endProgress?.Invoke();
}
}
}