Files
Aaru/Aaru.Core/Media/Detection/MMC.cs

2919 lines
119 KiB
C#

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : MMC.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Core.
//
// --[ Description ] ----------------------------------------------------------
//
// Detects media types in MultiMediaCommand devices
//
// --[ 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-2025 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Aaru.Checksums;
using Aaru.CommonTypes;
using Aaru.Decoders.Bluray;
using Aaru.Decoders.CD;
using Aaru.Decoders.DVD;
using Aaru.Decoders.SCSI.MMC;
using Aaru.Decoders.Sega;
using Aaru.Devices;
using Aaru.Helpers;
using Aaru.Logging;
using Sentry;
using DMI = Aaru.Decoders.Xbox.DMI;
using Sector = Aaru.Decoders.CD.Sector;
namespace Aaru.Core.Media.Detection;
/// <summary>Detects media type for MMC class devices</summary>
public static class MMC
{
/// <summary>SHA256 of PlayStation 2 boot sectors, seen in PAL discs</summary>
const string PS2_PAL_HASH = "5d04ff236613e1d8adcf9c201874acd6f6deed1e04306558b86f91cfb626f39d";
/// <summary>SHA256 of PlayStation 2 boot sectors, seen in Japanese, American, Malaysian and Korean discs</summary>
const string PS2_NTSC_HASH = "0bada1426e2c0351b872ef2a9ad2e5a0ac3918f4c53aa53329cb2911a8e16c23";
/// <summary>SHA256 of PlayStation 2 boot sectors, seen in Japanese discs</summary>
const string PS2_JAPANESE_HASH = "b82bffb809070d61fe050b7e1545df53d8f3cc648257cdff7502bc0ba6b38870";
const string MODULE_NAME = "Media detection";
static readonly byte[] _ps3Id = "PlayStation3\0\0\0\0"u8.ToArray();
static readonly byte[] _ps4Id = "PlayStation4\0\0\0\0"u8.ToArray();
static readonly byte[] _ps5Id = "PlayStation5\0\0\0\0"u8.ToArray();
static readonly byte[] _operaId = [0x01, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x01];
// Only present on bootable CDs, but those make more than 99% of all available
static readonly byte[] _fmTownsBootId = [0x49, 0x50, 0x4C, 0x34, 0xEB, 0x55, 0x06];
/// <summary>Present on first two seconds of second track, says "COPYRIGHT BANDAI"</summary>
static readonly byte[] _playdiaCopyright = "COPYRIGHT BANDAI"u8.ToArray();
static readonly byte[] _pcEngineSignature = "PC Engine CD-ROM SYSTEM"u8.ToArray();
static readonly byte[] _pcFxSignature = "PC-FX:Hu_CD-ROM"u8.ToArray();
static readonly byte[] _atariSignature =
"TAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTAIRTARA IPARPVODED TA AEHDAREA RT"u8.ToArray();
/// <summary>This is some kind of header. Every 10 bytes there's an audio byte.</summary>
static readonly byte[] _videoNowColorFrameMarker =
[
0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3,
0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81,
0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7,
0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3,
0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00,
0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3,
0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81,
0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7,
0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3,
0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00,
0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3,
0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81,
0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7, 0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x81, 0xE3, 0xE3, 0xC7,
0xC7, 0x81, 0x81, 0xE3, 0xC7, 0x00, 0x00, 0x00, 0x02, 0x01, 0x04, 0x02, 0x06, 0x03, 0xFF, 0x00, 0x08, 0x04,
0x0A, 0x05, 0x0C, 0x06, 0x0E, 0x07, 0xFF, 0x00, 0x11, 0x08, 0x13, 0x09, 0x15, 0x0A, 0x17, 0x0B, 0xFF, 0x00,
0x19, 0x0C, 0x1B, 0x0D, 0x1D, 0x0E, 0x1F, 0x0F, 0xFF, 0x00, 0x00, 0x28, 0x02, 0x29, 0x04, 0x2A, 0x06, 0x2B,
0xFF, 0x00, 0x08, 0x2C, 0x0A, 0x2D, 0x0C, 0x2E, 0x0E, 0x2F, 0xFF, 0x00, 0x11, 0x30, 0x13, 0x31, 0x15, 0x32,
0x17, 0x33, 0xFF, 0x00, 0x19, 0x34, 0x1B, 0x35, 0x1D, 0x36, 0x1F, 0x37, 0xFF, 0x00, 0x00, 0x38, 0x02, 0x39,
0x04, 0x3A, 0x06, 0x3B, 0xFF, 0x00, 0x08, 0x3C, 0x0A, 0x3D, 0x0C, 0x3E, 0x0E, 0x3F, 0xFF, 0x00, 0x11, 0x40,
0x13, 0x41, 0x15, 0x42, 0x17, 0x43, 0xFF, 0x00, 0x19, 0x44, 0x1B, 0x45, 0x1D, 0x46, 0x1F, 0x47, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00
];
static bool IsData(byte[] sector)
{
if(sector?.Length != 2352) return false;
byte[] syncMark = [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
var testMark = new byte[12];
Array.Copy(sector, 0, testMark, 0, 12);
return syncMark.SequenceEqual(testMark) && (sector[0xF] == 0 || sector[0xF] == 1 || sector[0xF] == 2);
}
static bool IsScrambledData(byte[] sector, int wantedLba, out int offset)
{
offset = 0;
if(sector?.Length != 2352) return false;
byte[] syncMark = [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
var testMark = new byte[12];
for(var i = 0; i <= 2336; i++)
{
Array.Copy(sector, i, testMark, 0, 12);
if(!syncMark.SequenceEqual(testMark) ||
sector[i + 0xF] != 0x60 && sector[i + 0xF] != 0x61 && sector[i + 0xF] != 0x62)
continue;
// De-scramble M and S
int minute = sector[i + 12] ^ 0x01;
int second = sector[i + 13] ^ 0x80;
int frame = sector[i + 14];
// Convert to binary
minute = minute / 16 * 10 + (minute & 0x0F);
second = second / 16 * 10 + (second & 0x0F);
frame = frame / 16 * 10 + (frame & 0x0F);
// Calculate the first found LBA
int lba = minute * 60 * 75 + second * 75 + frame - 150;
// Calculate the difference between the found LBA and the requested one
int diff = wantedLba - lba;
offset = i + 2352 * diff;
return true;
}
return false;
}
static byte[] DescrambleAndFixOffset(byte[] sector, int offsetBytes, int sectorsForOffset)
{
var descrambled = new byte[2352];
int offsetFix = offsetBytes < 0 ? 2352 * sectorsForOffset + offsetBytes : offsetBytes;
Array.Copy(sector, offsetFix, descrambled, 0, 2352);
return Sector.Scramble(descrambled);
}
/// <summary>Checks if the media corresponds to CD-i.</summary>
/// <param name="sector0">Contents of LBA 0, with all headers.</param>
/// <param name="sector16">Contents of LBA 0, with all headers.</param>
/// <returns><c>true</c> if it corresponds to a CD-i, <c>false</c>otherwise.</returns>
static bool IsCdi(byte[] sector0, byte[] sector16)
{
if(sector16?.Length != 2352) return false;
byte[] cdiMark = [0x01, 0x43, 0x44, 0x2D];
bool isData = IsData(sector0);
if(!isData || sector0[0xF] != 2 && sector0[0xF] != 1) return false;
var testMark = new byte[4];
Array.Copy(sector16, 24, testMark, 0, 4);
return cdiMark.SequenceEqual(testMark);
}
static bool IsVideoNowColor(byte[] videoFrame)
{
if(videoFrame is null || videoFrame.Length < _videoNowColorFrameMarker.Length) return false;
var buffer = new byte[_videoNowColorFrameMarker.Length];
for(var framePosition = 0; framePosition + buffer.Length < videoFrame.Length; framePosition++)
{
Array.Copy(videoFrame, framePosition, buffer, 0, buffer.Length);
for(var ab = 9; ab < buffer.Length; ab += 10) buffer[ab] = 0;
if(!_videoNowColorFrameMarker.SequenceEqual(buffer)) continue;
return true;
}
return false;
}
internal static int GetVideoNowColorOffset(byte[] data)
{
var buffer = new byte[_videoNowColorFrameMarker.Length];
for(var framePosition = 0; framePosition + buffer.Length < data.Length; framePosition++)
{
Array.Copy(data, framePosition, buffer, 0, buffer.Length);
for(var ab = 9; ab < buffer.Length; ab += 10) buffer[ab] = 0;
if(!_videoNowColorFrameMarker.SequenceEqual(buffer)) continue;
return 18032 - framePosition;
}
return 0;
}
internal static void DetectDiscType(ref MediaType mediaType, int sessions, FullTOC.CDFullTOC? decodedToc,
Device dev, out bool hiddenTrack, out bool hiddenData,
int firstTrackLastSession, ulong blocks)
{
uint startOfFirstDataTrack = uint.MaxValue;
DI.DiscInformation? blurayDi = null;
bool sense;
byte secondSessionFirstTrack = 0;
byte[] sector0;
byte[] sector1 = null;
byte[] ps2BootSectors = null;
byte[] playdia1 = null;
byte[] playdia2 = null;
byte[] firstDataSectorNotZero = null;
byte[] secondDataSectorNotZero = null;
byte[] firstTrackSecondSession = null;
byte[] firstTrackSecondSessionAudio = null;
byte[] videoNowColorFrame;
hiddenTrack = false;
hiddenData = false;
sense = dev.GetConfiguration(out byte[] cmdBuf, out _, 0, MmcGetConfigurationRt.Current, dev.Timeout, out _);
if(!sense)
{
Features.SeparatedFeatures ftr = Features.Separate(cmdBuf);
AaruLogging.Debug(MODULE_NAME,
Localization.Core.GET_CONFIGURATION_current_profile_is_0,
ftr.CurrentProfile);
mediaType = ftr.CurrentProfile switch
{
0x0001 => MediaType.GENERIC_HDD,
0x0005 => MediaType.CDMO,
0x0008 => MediaType.CD,
0x0009 => MediaType.CDR,
0x000A => MediaType.CDRW,
0x0010 => MediaType.DVDROM,
0x0011 => MediaType.DVDR,
0x0012 => MediaType.DVDRAM,
0x0013 or 0x0014 => MediaType.DVDRW,
0x0015 or 0x0016 => MediaType.DVDRDL,
0x0017 => MediaType.DVDRWDL,
0x0018 => MediaType.DVDDownload,
0x001A => MediaType.DVDPRW,
0x001B => MediaType.DVDPR,
0x0020 => MediaType.DDCD,
0x0021 => MediaType.DDCDR,
0x0022 => MediaType.DDCDRW,
0x002A => MediaType.DVDPRWDL,
0x002B => MediaType.DVDPRDL,
0x0040 => MediaType.BDROM,
0x0041 or 0x0042 => MediaType.BDR,
0x0043 => MediaType.BDRE,
0x0050 => MediaType.HDDVDROM,
0x0051 => MediaType.HDDVDR,
0x0052 => MediaType.HDDVDRAM,
0x0053 => MediaType.HDDVDRW,
0x0058 => MediaType.HDDVDRDL,
0x005A => MediaType.HDDVDRWDL,
_ => mediaType
};
}
if(decodedToc?.TrackDescriptors.Any(static t => t.SessionNumber == 2) == true)
{
secondSessionFirstTrack = decodedToc.Value.TrackDescriptors.Where(static t => t.SessionNumber == 2)
.Min(static t => t.POINT);
}
if(mediaType is MediaType.CD or MediaType.CDROMXA or MediaType.CDI)
{
sense = dev.ReadAtip(out cmdBuf, out _, dev.Timeout, out _);
if(!sense)
{
ATIP.CDATIP atip = ATIP.Decode(cmdBuf);
if(atip != null)
// Only CD-R and CD-RW have ATIP
mediaType = atip.DiscType ? MediaType.CDRW : MediaType.CDR;
}
}
if(mediaType is MediaType.CD or MediaType.CDROMXA)
{
var hasDataTrack = false;
var hasAudioTrack = false;
var allFirstSessionTracksAreAudio = true;
var hasVideoTrack = false;
if(decodedToc.HasValue)
{
FullTOC.TrackDataDescriptor a0Track =
decodedToc.Value.TrackDescriptors.FirstOrDefault(static t => t is { POINT: 0xA0, ADR: 1 });
if(a0Track.POINT == 0xA0)
{
switch(a0Track.PSEC)
{
case 0x10:
AaruLogging.Debug(MODULE_NAME, Localization.Core.TOC_says_disc_type_is_CD_i);
mediaType = MediaType.CDI;
break;
case 0x20:
AaruLogging.Debug(MODULE_NAME, Localization.Core.TOC_says_disc_type_is_CD_ROM_XA);
mediaType = MediaType.CDROMXA;
break;
}
}
foreach(FullTOC.TrackDataDescriptor track in
decodedToc.Value.TrackDescriptors.Where(static t => t.POINT is > 0 and <= 0x99))
{
if(track.TNO == 1 &&
((TocControl)(track.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(track.CONTROL & 0x0D) == TocControl.DataTrackIncremental))
allFirstSessionTracksAreAudio &= firstTrackLastSession != 1;
if((TocControl)(track.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(track.CONTROL & 0x0D) == TocControl.DataTrackIncremental)
{
var startAddress = (uint)(track.PHOUR * 3600 * 75 +
track.PMIN * 60 * 75 +
track.PSEC * 75 +
track.PFRAME -
150);
if(startAddress < startOfFirstDataTrack) startOfFirstDataTrack = startAddress;
hasDataTrack = true;
allFirstSessionTracksAreAudio &= track.POINT >= firstTrackLastSession;
}
else
hasAudioTrack = true;
hasVideoTrack |= track.ADR == 4;
}
}
if(mediaType != MediaType.CDI)
{
switch(hasDataTrack)
{
case true when hasAudioTrack && allFirstSessionTracksAreAudio && sessions == 2:
AaruLogging.Debug(MODULE_NAME,
Localization.Core
.Disc_has_audio_and_data_two_sessions_all_data_second_CD_Plus);
mediaType = MediaType.CDPLUS;
break;
case false when hasAudioTrack && sessions == 1:
AaruLogging.Debug(MODULE_NAME,
Localization.Core.Disc_has_only_audio_in_a_session_CD_Digital_Audio);
mediaType = MediaType.CDDA;
break;
}
if(hasDataTrack && !hasAudioTrack && sessions == 1)
{
AaruLogging.Debug(MODULE_NAME, Localization.Core.Disc_has_only_data_in_a_session_CD_ROM);
mediaType = MediaType.CDROM;
}
if(hasVideoTrack && !hasDataTrack && sessions == 1)
{
AaruLogging.Debug(MODULE_NAME, Localization.Core.Disc_has_video_tracks_CD_Video);
mediaType = MediaType.CDV;
}
}
if(mediaType is MediaType.CD or MediaType.CDROM && hasDataTrack)
{
foreach(uint startAddress in decodedToc.Value.TrackDescriptors
.Where(static t => t.POINT is > 0 and <= 0x99 &&
((TocControl)(t.CONTROL & 0x0D) ==
TocControl.DataTrack ||
(TocControl)(t.CONTROL & 0x0D) ==
TocControl.DataTrackIncremental))
.Select(static track => (uint)(track.PHOUR * 3600 * 75 +
track.PMIN * 60 * 75 +
track.PSEC * 75 +
track.PFRAME -
150) +
16))
{
sense = dev.ReadCd(out cmdBuf,
out _,
startAddress,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(sense || dev.Error) continue;
if(cmdBuf[0] != 0x00 ||
cmdBuf[1] != 0xFF ||
cmdBuf[2] != 0xFF ||
cmdBuf[3] != 0xFF ||
cmdBuf[4] != 0xFF ||
cmdBuf[5] != 0xFF ||
cmdBuf[6] != 0xFF ||
cmdBuf[7] != 0xFF ||
cmdBuf[8] != 0xFF ||
cmdBuf[9] != 0xFF ||
cmdBuf[10] != 0xFF ||
cmdBuf[11] != 0x00 ||
cmdBuf[15] != 0x02)
continue;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Disc_has_a_mode_two_data_track_CD_ROM_XA);
mediaType = MediaType.CDROMXA;
break;
}
}
}
if(secondSessionFirstTrack != 0 &&
decodedToc?.TrackDescriptors.Any(t => t.POINT == secondSessionFirstTrack) == true)
{
FullTOC.TrackDataDescriptor secondSessionFirstTrackTrack =
decodedToc.Value.TrackDescriptors.First(t => t.POINT == secondSessionFirstTrack);
var firstSectorSecondSessionFirstTrack = (uint)(secondSessionFirstTrackTrack.PHOUR * 3600 * 75 +
secondSessionFirstTrackTrack.PMIN * 60 * 75 +
secondSessionFirstTrackTrack.PSEC * 75 +
secondSessionFirstTrackTrack.PFRAME -
150);
sense = dev.ReadCd(out cmdBuf,
out _,
firstSectorSecondSessionFirstTrack,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
firstTrackSecondSession = cmdBuf;
else
{
sense = dev.ReadCd(out cmdBuf,
out _,
firstSectorSecondSessionFirstTrack,
2352,
1,
MmcSectorTypes.Cdda,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error) firstTrackSecondSession = cmdBuf;
}
sense = dev.ReadCd(out cmdBuf,
out _,
firstSectorSecondSessionFirstTrack - 1,
2352,
3,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
firstTrackSecondSessionAudio = cmdBuf;
else
{
sense = dev.ReadCd(out cmdBuf,
out _,
firstSectorSecondSessionFirstTrack - 1,
2352,
3,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error) firstTrackSecondSessionAudio = cmdBuf;
}
}
videoNowColorFrame = new byte[9 * 2352];
for(var i = 0; i < 9; i++)
{
sense = dev.ReadCd(out cmdBuf,
out _,
(uint)i,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(sense || dev.Error)
{
sense = dev.ReadCd(out cmdBuf,
out _,
(uint)i,
2352,
1,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(sense || !dev.Error)
{
videoNowColorFrame = null;
break;
}
}
Array.Copy(cmdBuf, 0, videoNowColorFrame, i * 2352, 2352);
}
FullTOC.TrackDataDescriptor? firstTrack =
decodedToc?.TrackDescriptors.FirstOrDefault(t => t.POINT ==
decodedToc.Value.TrackDescriptors
.Min(static m => m.POINT));
if(firstTrack?.POINT is >= 1 and < 0xA0)
{
var firstTrackSector = (uint)(firstTrack.Value.PHOUR * 3600 * 75 +
firstTrack.Value.PMIN * 60 * 75 +
firstTrack.Value.PSEC * 75 +
firstTrack.Value.PFRAME -
150);
// Check for hidden data before start of track 1
if(firstTrackSector > 0)
{
sense = dev.ReadCd(out sector0,
out _,
0,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!dev.Error && !sense)
{
hiddenTrack = true;
hiddenData = IsData(sector0);
if(hiddenData)
{
sense = dev.ReadCd(out byte[] sector16,
out _,
16,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && IsCdi(sector0, sector16))
{
mediaType = MediaType.CDIREADY;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Disc_has_hidden_CD_i_pregap_CD_i_Ready);
return;
}
}
else
{
hiddenData = IsScrambledData(sector0, 0, out int combinedOffset);
if(hiddenData)
{
int sectorsForOffset = combinedOffset / 2352;
if(sectorsForOffset < 0) sectorsForOffset *= -1;
if(combinedOffset % 2352 != 0) sectorsForOffset++;
var lba0 = 0;
var lba16 = 16;
if(combinedOffset < 0)
{
lba0 -= sectorsForOffset;
lba16 -= sectorsForOffset;
}
sense = dev.ReadCd(out sector0,
out _,
(uint)lba0,
2352,
(uint)sectorsForOffset + 1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
// Drive does not support reading negative sectors?
if(sense && lba0 < 0)
{
dev.ReadCd(out sector0,
out _,
0,
2352,
2,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
sector0 = DescrambleAndFixOffset(sector0, combinedOffset, sectorsForOffset);
}
else
sector0 = DescrambleAndFixOffset(sector0, combinedOffset, sectorsForOffset);
dev.ReadCd(out byte[] sector16,
out _,
(uint)lba16,
2352,
(uint)sectorsForOffset + 1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
sector16 = DescrambleAndFixOffset(sector16, combinedOffset, sectorsForOffset);
if(IsCdi(sector0, sector16))
{
mediaType = MediaType.CDIREADY;
AaruLogging.Debug(MODULE_NAME,
Localization.Core.Disc_has_hidden_CD_i_pregap_CD_i_Ready);
return;
}
}
}
}
}
}
sector0 = null;
switch(mediaType)
{
case MediaType.CD:
case MediaType.CDDA:
case MediaType.CDPLUS:
case MediaType.CDROM:
case MediaType.CDROMXA:
{
sense = dev.ReadCd(out cmdBuf,
out _,
0,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
sector0 = new byte[2048];
Array.Copy(cmdBuf, 16, sector0, 0, 2048);
sense = dev.ReadCd(out cmdBuf,
out _,
1,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
sector1 = new byte[2048];
Array.Copy(cmdBuf, 16, sector1, 0, 2048);
}
sense = dev.ReadCd(out cmdBuf,
out _,
4200,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
playdia1 = new byte[2048];
Array.Copy(cmdBuf, 24, playdia1, 0, 2048);
}
sense = dev.ReadCd(out cmdBuf,
out _,
4201,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
playdia2 = new byte[2048];
Array.Copy(cmdBuf, 24, playdia2, 0, 2048);
}
if(startOfFirstDataTrack != uint.MaxValue)
{
sense = dev.ReadCd(out cmdBuf,
out _,
startOfFirstDataTrack,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
firstDataSectorNotZero = new byte[2048];
Array.Copy(cmdBuf, 16, firstDataSectorNotZero, 0, 2048);
}
sense = dev.ReadCd(out cmdBuf,
out _,
startOfFirstDataTrack + 1,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
secondDataSectorNotZero = new byte[2048];
Array.Copy(cmdBuf, 16, secondDataSectorNotZero, 0, 2048);
}
}
var ps2Ms = new MemoryStream();
for(uint p = 0; p < 12; p++)
{
sense = dev.ReadCd(out cmdBuf,
out _,
p,
2352,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(sense || dev.Error) break;
ps2Ms.Write(cmdBuf, cmdBuf[0x0F] == 0x02 ? 24 : 16, 2048);
}
if(ps2Ms.Length == 0x6000) ps2BootSectors = ps2Ms.ToArray();
}
else
{
sense = dev.ReadCd(out cmdBuf,
out _,
0,
2324,
1,
MmcSectorTypes.Mode2,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
sector0 = new byte[2048];
Array.Copy(cmdBuf, 0, sector0, 0, 2048);
sense = dev.ReadCd(out cmdBuf,
out _,
1,
2324,
1,
MmcSectorTypes.Mode2,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
sector1 = new byte[2048];
Array.Copy(cmdBuf, 1, sector0, 0, 2048);
}
sense = dev.ReadCd(out cmdBuf,
out _,
4200,
2324,
1,
MmcSectorTypes.Mode2,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
playdia1 = new byte[2048];
Array.Copy(cmdBuf, 0, playdia1, 0, 2048);
}
sense = dev.ReadCd(out cmdBuf,
out _,
4201,
2324,
1,
MmcSectorTypes.Mode2,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
playdia2 = new byte[2048];
Array.Copy(cmdBuf, 0, playdia2, 0, 2048);
}
if(startOfFirstDataTrack != uint.MaxValue)
{
sense = dev.ReadCd(out cmdBuf,
out _,
startOfFirstDataTrack,
2324,
1,
MmcSectorTypes.Mode2,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
firstDataSectorNotZero = new byte[2048];
Array.Copy(cmdBuf, 0, firstDataSectorNotZero, 0, 2048);
}
sense = dev.ReadCd(out cmdBuf,
out _,
startOfFirstDataTrack + 1,
2324,
1,
MmcSectorTypes.Mode2,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
secondDataSectorNotZero = new byte[2048];
Array.Copy(cmdBuf, 0, secondDataSectorNotZero, 0, 2048);
}
}
var ps2Ms = new MemoryStream();
for(uint p = 0; p < 12; p++)
{
sense = dev.ReadCd(out cmdBuf,
out _,
p,
2324,
1,
MmcSectorTypes.Mode2,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(sense || dev.Error) break;
ps2Ms.Write(cmdBuf, 0, 2048);
}
if(ps2Ms.Length == 0x6000) ps2BootSectors = ps2Ms.ToArray();
}
else
{
sense = dev.ReadCd(out cmdBuf,
out _,
0,
2048,
1,
MmcSectorTypes.Mode1,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
sector0 = cmdBuf;
sense = dev.ReadCd(out cmdBuf,
out _,
0,
2048,
1,
MmcSectorTypes.Mode1,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error) sector1 = cmdBuf;
sense = dev.ReadCd(out cmdBuf,
out _,
0,
2048,
12,
MmcSectorTypes.Mode1,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error) ps2BootSectors = cmdBuf;
if(startOfFirstDataTrack != uint.MaxValue)
{
sense = dev.ReadCd(out cmdBuf,
out _,
startOfFirstDataTrack,
2048,
1,
MmcSectorTypes.Mode1,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error) firstDataSectorNotZero = cmdBuf;
sense = dev.ReadCd(out cmdBuf,
out _,
startOfFirstDataTrack + 1,
2048,
1,
MmcSectorTypes.Mode1,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
MmcSubchannel.None,
dev.Timeout,
out _);
if(!sense && !dev.Error) secondDataSectorNotZero = cmdBuf;
}
}
else
goto case MediaType.DVDROM;
}
}
break;
}
// TODO: Check for CD-i Ready
case MediaType.CDI:
break;
case MediaType.DVDROM:
case MediaType.HDDVDROM:
case MediaType.BDROM:
case MediaType.UHDBD:
case MediaType.Unknown:
if(mediaType is MediaType.BDROM or MediaType.UHDBD)
{
sense = dev.ReadDiscStructure(out cmdBuf,
out _,
MmcDiscStructureMediaType.Bd,
0,
0,
MmcDiscStructureFormat.DiscInformation,
0,
dev.Timeout,
out _);
if(!sense) blurayDi = DI.Decode(cmdBuf);
}
sense = dev.Read16(out cmdBuf, out _, 0, false, false, false, 0, 2048, 0, 1, false, dev.Timeout, out _);
if(!sense && !dev.Error)
{
sector0 = cmdBuf;
sense = dev.Read16(out cmdBuf,
out _,
0,
false,
false,
false,
1,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(!sense && !dev.Error) sector1 = cmdBuf;
sense = dev.Read16(out cmdBuf,
out _,
0,
false,
false,
false,
0,
2048,
0,
12,
false,
dev.Timeout,
out _);
if(!sense && !dev.Error && cmdBuf.Length == 0x6000) ps2BootSectors = cmdBuf;
}
else
{
sense = dev.Read12(out cmdBuf,
out _,
0,
false,
false,
false,
false,
0,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
sector0 = cmdBuf;
sense = dev.Read12(out cmdBuf,
out _,
0,
false,
false,
false,
false,
1,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(!sense && !dev.Error) sector1 = cmdBuf;
sense = dev.Read12(out cmdBuf,
out _,
0,
false,
false,
false,
false,
0,
2048,
0,
12,
false,
dev.Timeout,
out _);
if(!sense && !dev.Error && cmdBuf.Length == 0x6000) ps2BootSectors = cmdBuf;
}
else
{
sense = dev.Read10(out cmdBuf,
out _,
0,
false,
false,
false,
false,
0,
2048,
0,
1,
dev.Timeout,
out _);
if(!sense && !dev.Error)
{
sector0 = cmdBuf;
sense = dev.Read10(out cmdBuf,
out _,
0,
false,
false,
false,
false,
1,
2048,
0,
1,
dev.Timeout,
out _);
if(!sense && !dev.Error) sector1 = cmdBuf;
sense = dev.Read10(out cmdBuf,
out _,
0,
false,
false,
false,
false,
0,
2048,
0,
12,
dev.Timeout,
out _);
if(!sense && !dev.Error && cmdBuf.Length == 0x6000) ps2BootSectors = cmdBuf;
}
else
{
sense = dev.Read6(out cmdBuf, out _, 0, 2048, 1, dev.Timeout, out _);
if(!sense && !dev.Error)
{
sector0 = cmdBuf;
sense = dev.Read6(out cmdBuf, out _, 1, 2048, 1, dev.Timeout, out _);
if(!sense && !dev.Error) sector1 = cmdBuf;
sense = dev.Read6(out cmdBuf, out _, 0, 2048, 12, dev.Timeout, out _);
if(!sense && !dev.Error && cmdBuf.Length == 0x6000) ps2BootSectors = cmdBuf;
}
}
}
}
if(mediaType == MediaType.DVDROM)
{
sense = dev.ReadDiscStructure(out cmdBuf,
out _,
MmcDiscStructureMediaType.Dvd,
0,
0,
MmcDiscStructureFormat.PhysicalInformation,
0,
dev.Timeout,
out _);
if(!sense)
{
PFI.PhysicalFormatInformation? pfi = PFI.Decode(cmdBuf, mediaType);
if(pfi != null)
{
mediaType = pfi.Value.DiskCategory switch
{
DiskCategory.DVDPR => MediaType.DVDPR,
DiskCategory.DVDPRDL => MediaType.DVDPRDL,
DiskCategory.DVDPRW => MediaType.DVDPRW,
DiskCategory.DVDPRWDL => MediaType.DVDPRWDL,
DiskCategory.DVDR => pfi.Value.PartVersion >= 6
? MediaType.DVDRDL
: MediaType.DVDR,
DiskCategory.DVDRAM => MediaType.DVDRAM,
DiskCategory.DVDRW => pfi.Value.PartVersion >= 15
? MediaType.DVDRWDL
: MediaType.DVDRW,
DiskCategory.HDDVDR => MediaType.HDDVDR,
DiskCategory.HDDVDRAM => MediaType.HDDVDRAM,
DiskCategory.HDDVDROM => MediaType.HDDVDROM,
DiskCategory.HDDVDRW => MediaType.HDDVDRW,
DiskCategory.Nintendo => pfi.Value.DiscSize == DVDSize.Eighty
? MediaType.GOD
: MediaType.WOD,
DiskCategory.UMD => MediaType.UMD,
_ => mediaType
};
}
}
sense = dev.ReadDiscStructure(out cmdBuf,
out _,
MmcDiscStructureMediaType.Dvd,
0,
0,
MmcDiscStructureFormat.DiscManufacturingInformation,
0,
dev.Timeout,
out _);
if(!sense)
{
if(DMI.IsXbox(cmdBuf))
{
AaruLogging.Debug(MODULE_NAME,
"Found Xbox DMI, setting disc type to Xbox Game Disc (XGD).");
mediaType = MediaType.XGD;
return;
}
if(DMI.IsXbox360(cmdBuf))
{
// All XGD3 all have the same number of blocks
if(blocks is 25063 or 4229664 or 4246304) // Wxripper unlock
{
AaruLogging.Debug(MODULE_NAME,
"Found Xbox 360 DMI with {0} blocks, setting disc type to Xbox 360 Game Disc 3 (XGD3).");
mediaType = MediaType.XGD3;
return;
}
AaruLogging.Debug(MODULE_NAME,
"Found Xbox 360 DMI with {0} blocks, setting disc type to Xbox 360 Game Disc 2 (XGD2).");
mediaType = MediaType.XGD2;
return;
}
}
}
break;
// Recordables will be checked for PhotoCD only
case MediaType.CDR:
// Check if ISO9660
sense = dev.Read12(out byte[] isoSector,
out _,
0,
false,
false,
false,
false,
16,
2048,
0,
1,
false,
dev.Timeout,
out _);
// Sector 16 reads, and contains "CD001" magic?
if(sense ||
isoSector[1] != 0x43 ||
isoSector[2] != 0x44 ||
isoSector[3] != 0x30 ||
isoSector[4] != 0x30 ||
isoSector[5] != 0x31)
return;
// From sectors 16 to 31
uint isoSectorPosition = 16;
while(isoSectorPosition < 32)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
isoSectorPosition,
2048,
0,
1,
false,
dev.Timeout,
out _);
// If sector cannot be read, break here
if(sense) break;
// If sector does not contain "CD001" magic, break
if(isoSector[1] != 0x43 ||
isoSector[2] != 0x44 ||
isoSector[3] != 0x30 ||
isoSector[4] != 0x30 ||
isoSector[5] != 0x31)
break;
// If it is PVD or end of descriptor chain, break
if(isoSector[0] == 1 || isoSector[0] == 255) break;
isoSectorPosition++;
}
// If it's not an ISO9660 PVD, return
if(isoSector[0] != 1 ||
isoSector[1] != 0x43 ||
isoSector[2] != 0x44 ||
isoSector[3] != 0x30 ||
isoSector[4] != 0x30 ||
isoSector[5] != 0x31)
return;
var rootStart = BitConverter.ToUInt32(isoSector, 158);
var rootLength = BitConverter.ToUInt32(isoSector, 166);
if(rootStart == 0 || rootLength == 0) return;
rootLength /= 2048;
try
{
using var rootMs = new MemoryStream();
for(uint i = 0; i < rootLength; i++)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
rootStart + i,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
rootMs.Write(isoSector, 0, 2048);
}
isoSector = rootMs.ToArray();
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
return;
}
if(isoSector.Length < 2048) return;
var rootPos = 0;
uint pcdStart = 0;
uint pcdLength = 0;
while(isoSector[rootPos] > 0 &&
rootPos < isoSector.Length &&
rootPos + isoSector[rootPos] <= isoSector.Length)
{
int nameLen = isoSector[rootPos + 32];
var tmpName = new byte[nameLen];
Array.Copy(isoSector, rootPos + 33, tmpName, 0, nameLen);
string name = StringHandlers.CToString(tmpName).ToUpperInvariant();
if(name.EndsWith(";1", StringComparison.InvariantCulture)) name = name[..^2];
if(name == "PHOTO_CD" && (isoSector[rootPos + 25] & 0x02) == 0x02)
{
pcdStart = BitConverter.ToUInt32(isoSector, rootPos + 2);
pcdLength = BitConverter.ToUInt32(isoSector, rootPos + 10) / 2048;
}
rootPos += isoSector[rootPos];
}
if(pcdLength > 0)
{
try
{
using var pcdMs = new MemoryStream();
for(uint i = 0; i < pcdLength; i++)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
pcdStart + i,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
pcdMs.Write(isoSector, 0, 2048);
}
isoSector = pcdMs.ToArray();
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
return;
}
if(isoSector.Length < 2048) return;
for(var pi = 0; pi < pcdLength; pi++)
{
int pcdPos = pi * 2048;
uint infoPos = 0;
while(isoSector[pcdPos] > 0 &&
pcdPos < isoSector.Length &&
pcdPos + isoSector[pcdPos] <= isoSector.Length)
{
int nameLen = isoSector[pcdPos + 32];
var tmpName = new byte[nameLen];
Array.Copy(isoSector, pcdPos + 33, tmpName, 0, nameLen);
string name = StringHandlers.CToString(tmpName).ToUpperInvariant();
if(name.EndsWith(";1", StringComparison.InvariantCulture)) name = name[..^2];
if(name == "INFO.PCD")
{
infoPos = BitConverter.ToUInt32(isoSector, pcdPos + 2);
break;
}
pcdPos += isoSector[pcdPos];
}
if(infoPos > 0)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
infoPos,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
var systemId = new byte[8];
Array.Copy(isoSector, 0, systemId, 0, 8);
string id = StringHandlers.CToString(systemId).TrimEnd();
switch(id)
{
case "PHOTO_CD":
mediaType = MediaType.PCD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Photo_CD_description_file);
return;
}
}
}
}
break;
// Other recordables will not be checked
case MediaType.CDRW:
case MediaType.CDMRW:
case MediaType.DDCDR:
case MediaType.DDCDRW:
case MediaType.DVDR:
case MediaType.DVDRW:
case MediaType.DVDPR:
case MediaType.DVDPRW:
case MediaType.DVDPRWDL:
case MediaType.DVDRDL:
case MediaType.DVDPRDL:
case MediaType.DVDRAM:
case MediaType.DVDRWDL:
case MediaType.DVDDownload:
case MediaType.HDDVDRAM:
case MediaType.HDDVDR:
case MediaType.HDDVDRW:
case MediaType.HDDVDRDL:
case MediaType.HDDVDRWDL:
case MediaType.BDR:
case MediaType.BDRE:
case MediaType.BDRXL:
case MediaType.BDREXL:
return;
}
if(sector0 == null) return;
switch(mediaType)
{
case MediaType.CD:
case MediaType.CDDA:
case MediaType.CDPLUS:
case MediaType.CDROM:
case MediaType.CDROMXA:
// TODO: Pippin requires interpreting Apple Partition Map, reading HFS and checking for Pippin signatures
{
if(CD.DecodeIPBin(sector0).HasValue)
{
mediaType = MediaType.MEGACD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Mega_Sega_CD_IP_BIN);
return;
}
if(Saturn.DecodeIPBin(sector0).HasValue)
{
mediaType = MediaType.SATURNCD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Sega_Saturn_IP_BIN);
return;
}
// Are GDR detectable ???
if(Dreamcast.DecodeIPBin(sector0).HasValue)
{
mediaType = MediaType.GDROM;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Sega_Dreamcast_IP_BIN);
return;
}
if(ps2BootSectors is { Length: 0x6000 })
{
// The decryption key is applied as XOR. As first byte is originally always NULL, it gives us the key :)
byte decryptByte = ps2BootSectors[0];
for(var i = 0; i < 0x6000; i++) ps2BootSectors[i] ^= decryptByte;
string ps2BootSectorsHash = Sha256Context.Data(ps2BootSectors, out _);
AaruLogging.Debug(MODULE_NAME,
Localization.Core.PlayStation_2_boot_sectors_SHA256_0,
ps2BootSectorsHash);
if(ps2BootSectorsHash is PS2_PAL_HASH or PS2_NTSC_HASH or PS2_JAPANESE_HASH)
{
mediaType = MediaType.PS2CD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Sony_PlayStation_2_boot_sectors);
goto hasPs2CdBoot;
}
}
if(sector0 != null)
{
var syncBytes = new byte[7];
Array.Copy(sector0, 0, syncBytes, 0, 7);
if(_operaId.SequenceEqual(syncBytes))
{
mediaType = MediaType.ThreeDO;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Opera_filesystem);
return;
}
if(_fmTownsBootId.SequenceEqual(syncBytes))
{
mediaType = MediaType.FMTOWNS;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_FM_Towns_boot);
return;
}
}
if(playdia1 != null && playdia2 != null)
{
var pd1 = new byte[_playdiaCopyright.Length];
var pd2 = new byte[_playdiaCopyright.Length];
Array.Copy(playdia1, 38, pd1, 0, pd1.Length);
Array.Copy(playdia2, 0, pd2, 0, pd1.Length);
if(_playdiaCopyright.SequenceEqual(pd1) && _playdiaCopyright.SequenceEqual(pd2))
{
mediaType = MediaType.Playdia;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Playdia_copyright);
return;
}
}
if(secondDataSectorNotZero != null)
{
var pce = new byte[_pcEngineSignature.Length];
Array.Copy(secondDataSectorNotZero, 32, pce, 0, pce.Length);
if(_pcEngineSignature.SequenceEqual(pce))
{
mediaType = MediaType.SuperCDROM2;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_PC_Engine_CD_signature);
return;
}
}
if(firstDataSectorNotZero != null)
{
var pcfx = new byte[_pcFxSignature.Length];
Array.Copy(firstDataSectorNotZero, 0, pcfx, 0, pcfx.Length);
if(_pcFxSignature.SequenceEqual(pcfx))
{
mediaType = MediaType.PCFX;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_PC_FX_copyright);
return;
}
}
if(firstTrackSecondSessionAudio != null)
{
var jaguar = new byte[_atariSignature.Length];
for(var i = 0; i + jaguar.Length <= firstTrackSecondSessionAudio.Length; i += 2)
{
Array.Copy(firstTrackSecondSessionAudio, i, jaguar, 0, jaguar.Length);
if(!_atariSignature.SequenceEqual(jaguar)) continue;
mediaType = MediaType.JaguarCD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Atari_signature);
break;
}
}
if(firstTrackSecondSession?.Length >= 2336)
{
var milcd = new byte[2048];
Array.Copy(firstTrackSecondSession, 24, milcd, 0, 2048);
if(Dreamcast.DecodeIPBin(milcd).HasValue)
{
mediaType = MediaType.MilCD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Sega_Dreamcast_IP_BIN_on_second_session);
return;
}
}
// TODO: Detect black and white VideoNow
// TODO: Detect VideoNow XP
if(IsVideoNowColor(videoNowColorFrame))
{
mediaType = MediaType.VideoNowColor;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_VideoNow_Color_frame);
return;
}
// Check CD+G, CD+EG and CD+MIDI
if(mediaType == MediaType.CDDA)
{
sense = dev.ReadCd(out byte[] subBuf,
out _,
150,
96,
8,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
false,
false,
MmcErrorField.None,
MmcSubchannel.Raw,
dev.Timeout,
out _);
if(!sense)
{
var cdg = false;
var cdeg = false;
var cdmidi = false;
for(var i = 0; i < 8; i++)
{
var tmpSub = new byte[96];
Array.Copy(subBuf, i * 96, tmpSub, 0, 96);
DetectRwPackets(tmpSub, out bool cdgPacket, out bool cdegPacket, out bool cdmidiPacket);
if(cdgPacket) cdg = true;
if(cdegPacket) cdeg = true;
if(cdmidiPacket) cdmidi = true;
}
if(cdeg)
{
mediaType = MediaType.CDEG;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_enhanced_graphics_RW_packet);
return;
}
if(cdg)
{
mediaType = MediaType.CDG;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_graphics_RW_packet);
return;
}
if(cdmidi)
{
mediaType = MediaType.CDMIDI;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_MIDI_RW_packet);
return;
}
}
}
// If it has a PS2 boot area it can still be PS1, so check for SYSTEM.CNF below
hasPs2CdBoot:
// Check if ISO9660
sense = dev.Read12(out byte[] isoSector,
out _,
0,
false,
false,
false,
false,
16,
2048,
0,
1,
false,
dev.Timeout,
out _);
// Sector 16 reads, and contains "CD001" magic?
if(sense ||
isoSector[1] != 0x43 ||
isoSector[2] != 0x44 ||
isoSector[3] != 0x30 ||
isoSector[4] != 0x30 ||
isoSector[5] != 0x31)
return;
// From sectors 16 to 31
uint isoSectorPosition = 16;
while(isoSectorPosition < 32)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
isoSectorPosition,
2048,
0,
1,
false,
dev.Timeout,
out _);
// If sector cannot be read, break here
if(sense) break;
// If sector does not contain "CD001" magic, break
if(isoSector[1] != 0x43 ||
isoSector[2] != 0x44 ||
isoSector[3] != 0x30 ||
isoSector[4] != 0x30 ||
isoSector[5] != 0x31)
break;
// If it is PVD or end of descriptor chain, break
if(isoSector[0] == 1 || isoSector[0] == 255) break;
isoSectorPosition++;
}
// If it's not an ISO9660 PVD, return
if(isoSector[0] != 1 ||
isoSector[1] != 0x43 ||
isoSector[2] != 0x44 ||
isoSector[3] != 0x30 ||
isoSector[4] != 0x30 ||
isoSector[5] != 0x31)
return;
var rootStart = BitConverter.ToUInt32(isoSector, 158);
var rootLength = BitConverter.ToUInt32(isoSector, 166);
if(rootStart == 0 || rootLength == 0) return;
rootLength /= 2048;
try
{
using var rootMs = new MemoryStream();
for(uint i = 0; i < rootLength; i++)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
rootStart + i,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
rootMs.Write(isoSector, 0, 2048);
}
isoSector = rootMs.ToArray();
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
return;
}
if(isoSector.Length < 2048) return;
List<string> rootEntries = [];
uint ngcdIplStart = 0;
uint ngcdIplLength = 0;
uint vcdStart = 0;
uint vcdLength = 0;
uint pcdStart = 0;
uint pcdLength = 0;
uint ps1Start = 0;
uint ps1Length = 0;
for(var ri = 0; ri < rootLength; ri++)
{
int rootPos = ri * 2048;
while(rootPos < isoSector.Length &&
isoSector[rootPos] > 0 &&
rootPos + isoSector[rootPos] <= isoSector.Length)
{
int nameLen = isoSector[rootPos + 32];
var tmpName = new byte[nameLen];
Array.Copy(isoSector, rootPos + 33, tmpName, 0, nameLen);
string name = StringHandlers.CToString(tmpName).ToUpperInvariant();
if(name.EndsWith(";1", StringComparison.InvariantCulture)) name = name[..^2];
rootEntries.Add(name);
switch(name)
{
case "IPL.TXT":
ngcdIplStart = BitConverter.ToUInt32(isoSector, rootPos + 2);
ngcdIplLength = BitConverter.ToUInt32(isoSector, rootPos + 10);
break;
case "VCD" when (isoSector[rootPos + 25] & 0x02) == 0x02:
case "SVCD" when (isoSector[rootPos + 25] & 0x02) == 0x02:
case "HQVCD" when (isoSector[rootPos + 25] & 0x02) == 0x02:
vcdStart = BitConverter.ToUInt32(isoSector, rootPos + 2);
vcdLength = BitConverter.ToUInt32(isoSector, rootPos + 10) / 2048;
break;
case "PHOTO_CD" when (isoSector[rootPos + 25] & 0x02) == 0x02:
pcdStart = BitConverter.ToUInt32(isoSector, rootPos + 2);
pcdLength = BitConverter.ToUInt32(isoSector, rootPos + 10) / 2048;
break;
case "SYSTEM.CNF":
ps1Start = BitConverter.ToUInt32(isoSector, rootPos + 2);
ps1Length = BitConverter.ToUInt32(isoSector, rootPos + 10);
break;
}
rootPos += isoSector[rootPos];
}
}
if(rootEntries.Count == 0) return;
if(rootEntries.Contains("CD32.TM"))
{
mediaType = MediaType.CD32;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_CD32_TM_file_in_root);
return;
}
if(rootEntries.Contains("CDTV.TM"))
{
mediaType = MediaType.CDTV;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_CDTV_TM_file_in_root);
return;
}
// "IPL.TXT" length
if(ngcdIplLength > 0)
{
uint ngcdSectors = ngcdIplLength / 2048;
if(ngcdIplLength % 2048 > 0) ngcdSectors++;
string iplTxt;
// Read "IPL.TXT"
try
{
using var ngcdMs = new MemoryStream();
for(uint i = 0; i < ngcdSectors; i++)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
ngcdIplStart + i,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
ngcdMs.Write(isoSector, 0, 2048);
}
isoSector = ngcdMs.ToArray();
iplTxt = Encoding.ASCII.GetString(isoSector);
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
iplTxt = null;
}
// Check "IPL.TXT" lines
if(iplTxt != null)
{
using var sr = new StringReader(iplTxt);
var correctNeoGeoCd = true;
var lineNumber = 0;
while(sr.Peek() > 0)
{
string line = sr.ReadLine();
// End of file
if(line is null || line.Length == 0)
{
if(lineNumber == 0) correctNeoGeoCd = false;
break;
}
// Split line by comma
string[] split = line.Split(',');
// Empty line
if(split.Length == 0) continue;
// More than 3 entries
if(split.Length != 3)
{
if(line[0] < 0x20) break;
correctNeoGeoCd = false;
break;
}
// Split filename
string[] split2 = split[0].Split('.');
// Filename must have two parts, name and extension
if(split2.Length != 2)
{
correctNeoGeoCd = false;
break;
}
// Name must be smaller or equal to 8 characters
if(split2[0].Length > 8)
{
correctNeoGeoCd = false;
break;
}
// Extension must be smaller or equal to 8 characters
if(split2[1].Length > 3)
{
correctNeoGeoCd = false;
break;
}
// Second part must be a single digit
if(split[1].Length != 1 || !byte.TryParse(split[1], out _))
{
correctNeoGeoCd = false;
break;
}
// Third part must be bigger or equal to 1 and smaller or equal to 8
if(split[2].Length is < 1 or > 8)
{
correctNeoGeoCd = false;
break;
}
try
{
_ = Convert.ToUInt32(split[2], 16);
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
correctNeoGeoCd = false;
break;
}
lineNumber++;
}
if(correctNeoGeoCd)
{
mediaType = MediaType.NeoGeoCD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_correct_IPL_TXT_file_in_root);
return;
}
}
}
if(vcdLength > 0)
{
try
{
using var vcdMs = new MemoryStream();
for(uint i = 0; i < vcdLength; i++)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
vcdStart + i,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
vcdMs.Write(isoSector, 0, 2048);
}
isoSector = vcdMs.ToArray();
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
return;
}
if(isoSector.Length < 2048) return;
uint infoPos = 0;
for(var vi = 0; vi < vcdLength; vi++)
{
int vcdPos = vi * 2048;
while(vcdPos < isoSector.Length &&
isoSector[vcdPos] > 0 &&
vcdPos + isoSector[vcdPos] <= isoSector.Length)
{
int nameLen = isoSector[vcdPos + 32];
var tmpName = new byte[nameLen];
Array.Copy(isoSector, vcdPos + 33, tmpName, 0, nameLen);
string name = StringHandlers.CToString(tmpName).ToUpperInvariant();
if(name.EndsWith(";1", StringComparison.InvariantCulture)) name = name[..^2];
if(name is "INFO.VCD" or "INFO.SVD")
{
infoPos = BitConverter.ToUInt32(isoSector, vcdPos + 2);
break;
}
vcdPos += isoSector[vcdPos];
}
}
if(infoPos > 0)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
infoPos,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
var systemId = new byte[8];
Array.Copy(isoSector, 0, systemId, 0, 8);
string id = StringHandlers.CToString(systemId).TrimEnd();
switch(id)
{
case "VIDEO_CD":
mediaType = MediaType.VCD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Video_CD_description_file);
return;
case "SUPERVCD":
mediaType = MediaType.SVCD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Super_Video_CD_description_file);
break;
case "HQ-VCD":
mediaType = MediaType.CVD;
AaruLogging.Debug(MODULE_NAME,
Localization.Core.Found_China_Video_Disc_description_file);
break;
}
}
}
if(pcdLength > 0)
{
try
{
using var pcdMs = new MemoryStream();
for(uint i = 0; i < pcdLength; i++)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
pcdStart + i,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
pcdMs.Write(isoSector, 0, 2048);
}
isoSector = pcdMs.ToArray();
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
return;
}
if(isoSector.Length < 2048) return;
uint infoPos = 0;
for(var pi = 0; pi < pcdLength; pi++)
{
int pcdPos = pi * 2048;
while(pcdPos < isoSector.Length &&
isoSector[pcdPos] > 0 &&
pcdPos + isoSector[pcdPos] <= isoSector.Length)
{
int nameLen = isoSector[pcdPos + 32];
var tmpName = new byte[nameLen];
Array.Copy(isoSector, pcdPos + 33, tmpName, 0, nameLen);
string name = StringHandlers.CToString(tmpName).ToUpperInvariant();
if(name.EndsWith(";1", StringComparison.InvariantCulture)) name = name[..^2];
if(name == "INFO.PCD")
{
infoPos = BitConverter.ToUInt32(isoSector, pcdPos + 2);
break;
}
pcdPos += isoSector[pcdPos];
}
}
if(infoPos > 0)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
infoPos,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
var systemId = new byte[8];
Array.Copy(isoSector, 0, systemId, 0, 8);
string id = StringHandlers.CToString(systemId).TrimEnd();
switch(id)
{
case "PHOTO_CD":
mediaType = MediaType.PCD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Photo_CD_description_file);
return;
}
}
}
// "SYSTEM.CNF" length
if(ps1Length > 0)
{
uint ps1Sectors = ps1Length / 2048;
if(ps1Length % 2048 > 0) ps1Sectors++;
string ps1Txt;
// Read "SYSTEM.CNF"
try
{
using var ps1Ms = new MemoryStream();
for(uint i = 0; i < ps1Sectors; i++)
{
sense = dev.Read12(out isoSector,
out _,
0,
false,
false,
false,
false,
ps1Start + i,
2048,
0,
1,
false,
dev.Timeout,
out _);
if(sense) break;
ps1Ms.Write(isoSector, 0, 2048);
}
isoSector = ps1Ms.ToArray();
ps1Txt = Encoding.ASCII.GetString(isoSector);
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
ps1Txt = null;
}
// Check "SYSTEM.CNF" lines
if(ps1Txt != null)
{
using var sr = new StringReader(ps1Txt);
string ps1BootFile = null;
string ps2BootFile = null;
while(sr.Peek() > 0)
{
string line = sr.ReadLine();
// End of file
if(line is null || line.Length == 0) break;
line = line.Replace(" ", "");
if(line.StartsWith("BOOT=cdrom:", StringComparison.InvariantCultureIgnoreCase))
{
ps1BootFile = line[11..];
if(ps1BootFile.StartsWith('\\')) ps1BootFile = ps1BootFile[1..];
if(ps1BootFile.EndsWith(";1", StringComparison.InvariantCultureIgnoreCase))
ps1BootFile = ps1BootFile[..^2];
break;
}
if(!line.StartsWith("BOOT2=cdrom0:", StringComparison.InvariantCultureIgnoreCase)) continue;
ps2BootFile = line[13..];
if(ps2BootFile.StartsWith('\\')) ps2BootFile = ps2BootFile[1..];
if(ps2BootFile.EndsWith(";1", StringComparison.InvariantCultureIgnoreCase))
ps2BootFile = ps2BootFile[..^2];
break;
}
if(ps1BootFile != null && rootEntries.Contains(ps1BootFile.ToUpperInvariant()))
{
mediaType = MediaType.PS1CD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_correct_SYSTEM_CNF_file_in_root_PS1);
}
if(ps2BootFile != null && rootEntries.Contains(ps2BootFile.ToUpperInvariant()))
{
mediaType = MediaType.PS2CD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_correct_SYSTEM_CNF_file_in_root_PS2);
}
}
}
break;
}
// TODO: Check for CD-i Ready
case MediaType.CDI:
break;
case MediaType.DVDROM:
case MediaType.HDDVDROM:
case MediaType.BDROM:
case MediaType.UHDBD:
case MediaType.Unknown:
// TODO: Nuon requires reading the filesystem, searching for a file called "/NUON/NUON.RUN"
if(ps2BootSectors is { Length: 0x6000 })
{
// The decryption key is applied as XOR. As first byte is originally always NULL, it gives us the key :)
byte decryptByte = ps2BootSectors[0];
for(var i = 0; i < 0x6000; i++) ps2BootSectors[i] ^= decryptByte;
string ps2BootSectorsHash = Sha256Context.Data(ps2BootSectors, out _);
AaruLogging.Debug(MODULE_NAME,
Localization.Core.PlayStation_2_boot_sectors_SHA256_0,
ps2BootSectorsHash);
if(ps2BootSectorsHash is PS2_PAL_HASH or PS2_NTSC_HASH or PS2_JAPANESE_HASH)
{
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Sony_PlayStation_2_boot_sectors_DVD);
mediaType = MediaType.PS2DVD;
}
}
if(sector1 != null)
{
var tmp = new byte[_ps3Id.Length];
Array.Copy(sector1, 0, tmp, 0, tmp.Length);
if(tmp.SequenceEqual(_ps3Id))
{
switch(mediaType)
{
case MediaType.BDROM:
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Sony_PlayStation_3_boot_sectors);
mediaType = MediaType.PS3BD;
break;
case MediaType.DVDROM:
AaruLogging.Debug(MODULE_NAME,
Localization.Core.Found_Sony_PlayStation_3_boot_sectors_DVD);
mediaType = MediaType.PS3DVD;
break;
}
}
tmp = new byte[_ps4Id.Length];
Array.Copy(sector1, 512, tmp, 0, tmp.Length);
if(tmp.SequenceEqual(_ps4Id) && mediaType == MediaType.BDROM)
{
mediaType = MediaType.PS4BD;
AaruLogging.Debug(MODULE_NAME, Localization.Core.Found_Sony_PlayStation_4_boot_sectors);
}
}
if(blurayDi is { Units.Length: > 0 } && blurayDi?.Units[0].DiscTypeIdentifier != null)
{
string blurayType = StringHandlers.CToString(blurayDi?.Units[0].DiscTypeIdentifier);
switch(blurayType)
{
case "BDR":
AaruLogging.Debug(MODULE_NAME, Localization.Core.Blu_ray_type_BDR_setting_disc_type_BDR);
mediaType = MediaType.BDR;
break;
case "XG4":
AaruLogging.Debug(MODULE_NAME, Localization.Core.Blu_ray_type_XG4_setting_disc_type_XGD4);
mediaType = MediaType.XGD4;
break;
// TODO: PS5
case "BDU":
if(sector1 != null)
{
var tmp = new byte[_ps5Id.Length];
Array.Copy(sector1, 1024, tmp, 0, tmp.Length);
if(tmp.SequenceEqual(_ps5Id))
{
mediaType = MediaType.PS5BD;
AaruLogging.Debug(MODULE_NAME,
Localization.Core.Found_Sony_PlayStation_5_boot_sectors);
break;
}
}
AaruLogging.Debug(MODULE_NAME, Localization.Core.Blu_ray_type_BDU_setting_disc_type_UHD);
mediaType = MediaType.UHDBD;
break;
}
}
break;
}
}
static void DetectRwPackets(byte[] subchannel, out bool cdgPacket, out bool cdegPacket, out bool cdmidiPacket)
{
cdgPacket = false;
cdegPacket = false;
cdmidiPacket = false;
var cdSubRwPack1 = new byte[24];
var cdSubRwPack2 = new byte[24];
var cdSubRwPack3 = new byte[24];
var cdSubRwPack4 = new byte[24];
var i = 0;
for(var j = 0; j < 24; j++) cdSubRwPack1[j] = (byte)(subchannel[i++] & 0x3F);
for(var j = 0; j < 24; j++) cdSubRwPack2[j] = (byte)(subchannel[i++] & 0x3F);
for(var j = 0; j < 24; j++) cdSubRwPack3[j] = (byte)(subchannel[i++] & 0x3F);
for(var j = 0; j < 24; j++) cdSubRwPack4[j] = (byte)(subchannel[i++] & 0x3F);
switch(cdSubRwPack1[0])
{
case 0x08:
case 0x09:
cdgPacket = true;
break;
case 0x0A:
cdegPacket = true;
break;
case 0x38:
cdmidiPacket = true;
break;
}
switch(cdSubRwPack2[0])
{
case 0x08:
case 0x09:
cdgPacket = true;
break;
case 0x0A:
cdegPacket = true;
break;
case 0x38:
cdmidiPacket = true;
break;
}
switch(cdSubRwPack3[0])
{
case 0x08:
case 0x09:
cdgPacket = true;
break;
case 0x0A:
cdegPacket = true;
break;
case 0x38:
cdmidiPacket = true;
break;
}
switch(cdSubRwPack4[0])
{
case 0x08:
case 0x09:
cdgPacket = true;
break;
case 0x0A:
cdegPacket = true;
break;
case 0x38:
cdmidiPacket = true;
break;
}
}
}