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

414 lines
17 KiB
C#
Raw Normal View History

2020-03-11 21:56:55 +00:00
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : CompactDisc.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Core.
//
// --[ Description ] ----------------------------------------------------------
//
// Retrieves information from CompactDisc media.
//
// --[ 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/>.
//
// ----------------------------------------------------------------------------
2024-12-19 10:45:18 +00:00
// Copyright © 2011-2025 Natalia Portillo
2020-03-11 21:56:55 +00:00
// ****************************************************************************/
using System;
2020-01-06 22:29:01 +00:00
using System.Diagnostics.CodeAnalysis;
using System.Linq;
2020-02-27 00:33:26 +00:00
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Structs;
2020-02-29 18:03:35 +00:00
using Aaru.Core.Media.Detection;
2020-02-27 00:33:26 +00:00
using Aaru.Database.Models;
using Aaru.Decoders.CD;
using Aaru.Devices;
using Device = Aaru.Database.Models.Device;
namespace Aaru.Core.Media.Info;
2022-03-06 13:29:38 +00:00
/// <summary>Core operations for retrieving information about CD based media</summary>
public static class CompactDisc
{
2022-03-06 13:29:38 +00:00
/// <summary>Gets the offset bytes from a Compact Disc</summary>
/// <param name="cdOffset">Offset entry from database</param>
/// <param name="dbDev">Device entry from database</param>
/// <param name="debug">Debug</param>
/// <param name="dev">Opened device</param>
/// <param name="dskType">Detected disk type</param>
/// <param name="dumpLog">Dump log if applicable</param>
/// <param name="tracks">Disc track list</param>
/// <param name="updateStatus">UpdateStatus event</param>
/// <param name="driveOffset">Drive offset</param>
/// <param name="combinedOffset">Combined offset</param>
/// <param name="supportsPlextorReadCdDa">Set to <c>true</c> if drive supports PLEXTOR READ CD-DA vendor command</param>
/// <returns><c>true</c> if offset could be found, <c>false</c> otherwise</returns>
[SuppressMessage("ReSharper", "TooWideLocalVariableScope")]
public static void GetOffset(CdOffset cdOffset, Device dbDev, bool debug, Aaru.Devices.Device dev,
MediaType dskType, Track[] tracks, UpdateStatusHandler updateStatus,
2023-10-03 22:57:50 +01:00
out int? driveOffset, out int? combinedOffset, out bool supportsPlextorReadCdDa)
{
2022-03-06 13:29:38 +00:00
byte[] cmdBuf;
bool sense;
int minute;
int second;
int frame;
byte[] sectorSync;
byte[] tmpBuf;
int lba;
int diff;
Track dataTrack = default;
Track audioTrack = default;
bool offsetFound = false;
2022-03-06 13:29:38 +00:00
const uint sectorSize = 2352;
driveOffset = cdOffset?.Offset * 4;
combinedOffset = null;
supportsPlextorReadCdDa = false;
if(dskType != MediaType.VideoNowColor)
{
2022-03-06 13:29:38 +00:00
if(tracks.Any(t => t.Type != TrackType.Audio))
{
2022-03-06 13:29:38 +00:00
dataTrack = tracks.FirstOrDefault(t => t.Type != TrackType.Audio);
2022-03-06 13:29:38 +00:00
if(dataTrack != null)
{
// Build sync
2024-05-01 04:39:38 +01:00
sectorSync = [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
2022-03-06 13:29:38 +00:00
tmpBuf = new byte[sectorSync.Length];
2022-03-06 13:29:38 +00:00
// Ensure to be out of the pregap, or multi-session discs give funny values
uint wantedLba = (uint)(dataTrack.StartSector + 151);
2022-03-06 13:29:38 +00:00
// Plextor READ CDDA
if(dbDev?.ATAPI?.RemovableMedias?.Any(d => d.SupportsPlextorReadCDDA == true) == true ||
dbDev?.SCSI?.RemovableMedias?.Any(d => d.SupportsPlextorReadCDDA == true) == true ||
dev.Manufacturer.Equals("plextor", StringComparison.InvariantCultureIgnoreCase))
2022-03-06 13:29:38 +00:00
{
2024-05-01 04:05:22 +01:00
sense = dev.PlextorReadCdDa(out cmdBuf,
out _,
wantedLba,
sectorSize,
3,
PlextorSubchannel.None,
dev.Timeout,
out _);
2022-03-06 13:29:38 +00:00
if(!sense && !dev.Error)
{
2022-03-06 13:29:38 +00:00
supportsPlextorReadCdDa = true;
for(int i = 0; i < cmdBuf.Length - sectorSync.Length; i++)
{
2022-03-06 13:29:38 +00:00
Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length);
2024-05-01 04:05:22 +01:00
if(!tmpBuf.SequenceEqual(sectorSync)) continue;
2022-03-06 13:29:38 +00:00
// De-scramble M and S
minute = cmdBuf[i + 12] ^ 0x01;
second = cmdBuf[i + 13] ^ 0x80;
frame = cmdBuf[i + 14];
2022-03-06 13:29:38 +00:00
// Convert to binary
2023-10-03 22:57:50 +01:00
minute = minute / 16 * 10 + (minute & 0x0F);
second = second / 16 * 10 + (second & 0x0F);
frame = frame / 16 * 10 + (frame & 0x0F);
2022-03-06 13:29:38 +00:00
// Calculate the first found LBA
2023-10-03 22:57:50 +01:00
lba = minute * 60 * 75 + second * 75 + frame - 150;
2022-03-06 13:29:38 +00:00
// Calculate the difference between the found LBA and the requested one
diff = (int)wantedLba - lba;
2023-10-03 22:57:50 +01:00
combinedOffset = i + 2352 * diff;
2022-03-06 13:29:38 +00:00
offsetFound = true;
2022-03-06 13:29:38 +00:00
break;
}
}
2022-03-06 13:29:38 +00:00
}
2022-03-06 13:29:38 +00:00
if(!offsetFound &&
(debug ||
dbDev?.ATAPI?.RemovableMedias?.Any(d => d.CanReadCdScrambled == true) == true ||
2022-03-07 07:36:44 +00:00
dbDev?.SCSI?.RemovableMedias?.Any(d => d.CanReadCdScrambled == true) == true ||
dbDev?.SCSI?.MultiMediaDevice?.TestedMedia?.Any(d => d.CanReadCdScrambled == true) == true ||
dev.Manufacturer.Equals("hl-dt-st", StringComparison.InvariantCultureIgnoreCase)))
2022-03-06 13:29:38 +00:00
{
2024-05-01 04:05:22 +01:00
sense = dev.ReadCd(out cmdBuf,
out _,
wantedLba,
sectorSize,
3,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
2022-03-06 13:29:38 +00:00
{
// Clear cache
for(int i = 0; i < 63; i++)
2022-03-06 13:29:38 +00:00
{
2024-05-01 04:05:22 +01:00
sense = dev.ReadCd(out _,
out _,
(uint)(wantedLba + 3 + 16 * i),
sectorSize,
16,
MmcSectorTypes.AllTypes,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
2022-03-07 07:36:44 +00:00
out _);
2022-03-06 13:29:38 +00:00
2024-05-01 04:05:22 +01:00
if(sense || dev.Error) break;
2022-03-06 13:29:38 +00:00
}
2024-05-01 04:05:22 +01:00
dev.ReadCd(out cmdBuf,
out _,
wantedLba,
sectorSize,
3,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
for(int i = 0; i < cmdBuf.Length - sectorSync.Length; i++)
2022-03-06 13:29:38 +00:00
{
Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length);
2024-05-01 04:05:22 +01:00
if(!tmpBuf.SequenceEqual(sectorSync)) continue;
2022-03-06 13:29:38 +00:00
// De-scramble M and S
minute = cmdBuf[i + 12] ^ 0x01;
second = cmdBuf[i + 13] ^ 0x80;
frame = cmdBuf[i + 14];
2022-03-06 13:29:38 +00:00
// Convert to binary
2023-10-03 22:57:50 +01:00
minute = minute / 16 * 10 + (minute & 0x0F);
second = second / 16 * 10 + (second & 0x0F);
frame = frame / 16 * 10 + (frame & 0x0F);
2022-03-06 13:29:38 +00:00
// Calculate the first found LBA
2023-10-03 22:57:50 +01:00
lba = minute * 60 * 75 + second * 75 + frame - 150;
2022-03-06 13:29:38 +00:00
// Calculate the difference between the found LBA and the requested one
diff = (int)wantedLba - lba;
2023-10-03 22:57:50 +01:00
combinedOffset = i + 2352 * diff;
2022-03-06 13:29:38 +00:00
offsetFound = true;
break;
}
}
}
}
2022-03-06 13:29:38 +00:00
}
2024-05-01 04:05:22 +01:00
if(offsetFound) return;
2020-01-06 22:29:01 +00:00
2022-03-06 13:29:38 +00:00
// Try to get another the offset some other way, we need an audio track just after a data track, same session
for(int i = 1; i < tracks.Length; i++)
2022-03-06 13:29:38 +00:00
{
2024-05-01 04:05:22 +01:00
if(tracks[i - 1].Type == TrackType.Audio || tracks[i].Type != TrackType.Audio) continue;
2022-03-06 13:29:38 +00:00
dataTrack = tracks[i - 1];
audioTrack = tracks[i];
2022-03-06 13:29:38 +00:00
break;
}
2024-05-01 04:05:22 +01:00
if(dataTrack is null || audioTrack is null) return;
2022-03-06 13:29:38 +00:00
// Found them
2024-05-01 04:05:22 +01:00
sense = dev.ReadCd(out cmdBuf,
out _,
(uint)audioTrack.StartSector,
sectorSize,
3,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(sense || dev.Error) return;
2022-03-06 13:29:38 +00:00
dataTrack.EndSector += 150;
2022-03-06 13:29:38 +00:00
// Calculate MSF
2023-10-03 22:57:50 +01:00
minute = (int)dataTrack.EndSector / 4500;
second = ((int)dataTrack.EndSector - minute * 4500) / 75;
frame = (int)dataTrack.EndSector - minute * 4500 - second * 75;
2022-03-06 13:29:38 +00:00
dataTrack.EndSector -= 150;
2022-03-06 13:29:38 +00:00
// Convert to BCD
2023-10-03 22:57:50 +01:00
minute = (minute / 10 << 4) + minute % 10;
second = (second / 10 << 4) + second % 10;
frame = (frame / 10 << 4) + frame % 10;
2022-03-06 13:29:38 +00:00
// Scramble M and S
minute ^= 0x01;
second ^= 0x80;
2022-03-06 13:29:38 +00:00
// Build sync
2024-05-01 04:39:38 +01:00
sectorSync =
[
2022-03-06 13:29:38 +00:00
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, (byte)minute, (byte)second,
(byte)frame
2024-05-01 04:39:38 +01:00
];
2022-03-06 13:29:38 +00:00
tmpBuf = new byte[sectorSync.Length];
for(int i = 0; i < cmdBuf.Length - sectorSync.Length; i++)
2022-03-06 13:29:38 +00:00
{
Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length);
2024-05-01 04:05:22 +01:00
if(!tmpBuf.SequenceEqual(sectorSync)) continue;
2022-03-06 13:29:38 +00:00
combinedOffset = i + 2352;
offsetFound = true;
2022-03-06 13:29:38 +00:00
break;
}
2024-05-01 04:05:22 +01:00
if(offsetFound || audioTrack.Pregap <= 0) return;
sense = dev.ReadCd(out byte[] dataBuf,
out _,
(uint)dataTrack.EndSector,
sectorSize,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(sense || dev.Error) return;
for(int i = 0; i < dataBuf.Length; i++) dataBuf[i] ^= Sector.ScrambleTable[i];
for(int i = 0; i < 2352; i++)
2022-03-06 13:29:38 +00:00
{
byte[] dataSide = new byte[2352 - i];
byte[] audioSide = new byte[2352 - i];
2023-10-03 22:57:50 +01:00
Array.Copy(dataBuf, i, dataSide, 0, dataSide.Length);
Array.Copy(cmdBuf, 0, audioSide, 0, audioSide.Length);
2024-05-01 04:05:22 +01:00
if(!dataSide.SequenceEqual(audioSide)) continue;
2022-03-06 13:29:38 +00:00
combinedOffset = audioSide.Length;
2022-03-06 13:29:38 +00:00
break;
}
2022-03-06 13:29:38 +00:00
}
else
{
byte[] videoNowColorFrame = new byte[9 * sectorSize];
2024-05-01 04:05:22 +01:00
sense = dev.ReadCd(out cmdBuf,
out _,
0,
sectorSize,
9,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
2022-03-06 13:29:38 +00:00
if(sense || dev.Error)
{
2024-05-01 04:05:22 +01:00
sense = dev.ReadCd(out cmdBuf,
out _,
0,
sectorSize,
9,
MmcSectorTypes.Cdda,
false,
false,
true,
MmcHeaderCodes.None,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
2022-03-07 07:36:44 +00:00
out _);
2024-05-01 04:05:22 +01:00
if(sense || dev.Error) videoNowColorFrame = null;
2022-03-06 13:29:38 +00:00
}
2022-03-06 13:29:38 +00:00
if(videoNowColorFrame is null)
updateStatus?.Invoke(Localization.Core.Could_not_find_VideoNow_Color_frame_offset);
2022-03-06 13:29:38 +00:00
else
{
combinedOffset = MMC.GetVideoNowColorOffset(videoNowColorFrame);
updateStatus?.Invoke(string.Format(Localization.Core.VideoNow_Color_frame_is_offset_0_bytes,
combinedOffset));
}
}
}
}