2018-09-13 20:03:07 +01:00
|
|
|
// /***************************************************************************
|
|
|
|
|
// The Disc Image Chef
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// 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/>.
|
|
|
|
|
//
|
|
|
|
|
// ----------------------------------------------------------------------------
|
2020-01-03 17:51:30 +00:00
|
|
|
// Copyright © 2011-2020 Natalia Portillo
|
2018-09-13 20:03:07 +01:00
|
|
|
// ****************************************************************************/
|
|
|
|
|
|
|
|
|
|
using System;
|
2020-01-01 18:06:31 +00:00
|
|
|
using System.IO;
|
2018-09-13 20:03:07 +01:00
|
|
|
using System.Linq;
|
2020-02-27 00:33:26 +00:00
|
|
|
using Aaru.Checksums;
|
|
|
|
|
using Aaru.CommonTypes;
|
|
|
|
|
using Aaru.Console;
|
|
|
|
|
using Aaru.Decoders.CD;
|
|
|
|
|
using Aaru.Decoders.Sega;
|
|
|
|
|
using Aaru.Devices;
|
2020-01-19 19:04:34 +00:00
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
// ReSharper disable JoinDeclarationAndInitializer
|
2018-09-13 20:03:07 +01:00
|
|
|
|
2020-02-27 00:33:26 +00:00
|
|
|
namespace Aaru.Core.Media.Detection
|
2018-09-13 20:03:07 +01:00
|
|
|
{
|
|
|
|
|
public static class MMC
|
|
|
|
|
{
|
2020-01-01 18:06:31 +00:00
|
|
|
/// <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";
|
|
|
|
|
|
|
|
|
|
static readonly byte[] _ps3Id =
|
|
|
|
|
{
|
|
|
|
|
0x50, 0x6C, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x33, 0x00, 0x00, 0x00, 0x00
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static readonly byte[] _ps4Id =
|
|
|
|
|
{
|
|
|
|
|
0x50, 0x6C, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x34, 0x00, 0x00, 0x00, 0x00
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 =
|
|
|
|
|
{
|
|
|
|
|
0x43, 0x4F, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x42, 0x41, 0x4E, 0x44, 0x41, 0x49
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static readonly byte[] _pcEngineSignature =
|
|
|
|
|
{
|
|
|
|
|
0x50, 0x43, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x43, 0x44, 0x2D, 0x52, 0x4F, 0x4D, 0x20, 0x53,
|
|
|
|
|
0x59, 0x53, 0x54, 0x45, 0x4D
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static readonly byte[] _pcFxSignature =
|
|
|
|
|
{
|
|
|
|
|
0x50, 0x43, 0x2D, 0x46, 0x58, 0x3A, 0x48, 0x75, 0x5F, 0x43, 0x44, 0x2D, 0x52, 0x4F, 0x4D
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static readonly byte[] _atariSignature =
|
|
|
|
|
{
|
|
|
|
|
0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41,
|
|
|
|
|
0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52,
|
|
|
|
|
0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41,
|
|
|
|
|
0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x49, 0x52, 0x54, 0x41, 0x52, 0x41, 0x20, 0x49, 0x50, 0x41,
|
|
|
|
|
0x52, 0x50, 0x56, 0x4F, 0x44, 0x45, 0x44, 0x20, 0x54, 0x41, 0x20, 0x41, 0x45, 0x48, 0x44, 0x41, 0x52, 0x45,
|
|
|
|
|
0x41, 0x20, 0x52, 0x54
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-26 01:24:57 +00:00
|
|
|
/// <summary>This is some kind of header. Every 10 bytes there's an audio byte.</summary>
|
2020-01-01 18:06:31 +00:00
|
|
|
static readonly byte[] _videoNowColorFrameMarker =
|
2019-02-12 00:56:02 +00:00
|
|
|
{
|
2019-12-26 01:24:57 +00:00
|
|
|
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
|
2019-02-12 00:56:02 +00:00
|
|
|
};
|
|
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
static bool IsData(byte[] sector)
|
2018-09-13 20:03:07 +01:00
|
|
|
{
|
2020-01-01 18:06:31 +00:00
|
|
|
if(sector?.Length != 2352)
|
2019-12-26 01:24:57 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
byte[] syncMark =
|
|
|
|
|
{
|
|
|
|
|
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
byte[] testMark = new byte[12];
|
|
|
|
|
Array.Copy(sector, 0, testMark, 0, 12);
|
|
|
|
|
|
|
|
|
|
return syncMark.SequenceEqual(testMark) && (sector[0xF] == 0 || sector[0xF] == 1 || sector[0xF] == 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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;
|
|
|
|
|
|
2019-12-26 01:24:57 +00:00
|
|
|
byte[] cdiMark =
|
|
|
|
|
{
|
|
|
|
|
0x01, 0x43, 0x44, 0x2D
|
|
|
|
|
};
|
2018-09-13 20:03:07 +01:00
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
bool isData = IsData(sector0);
|
2018-09-13 20:03:07 +01:00
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
if(!isData ||
|
2019-12-26 01:24:57 +00:00
|
|
|
sector0[0xF] != 2)
|
|
|
|
|
return false;
|
2018-09-13 20:03:07 +01:00
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
byte[] testMark = new byte[4];
|
2018-09-13 20:03:07 +01:00
|
|
|
Array.Copy(sector16, 24, testMark, 0, 4);
|
2019-12-26 01:24:57 +00:00
|
|
|
|
2018-09-13 20:03:07 +01:00
|
|
|
return cdiMark.SequenceEqual(testMark);
|
|
|
|
|
}
|
2019-02-12 00:56:02 +00:00
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
static bool IsVideoNowColor(byte[] videoFrame)
|
2019-02-12 00:56:02 +00:00
|
|
|
{
|
2019-12-26 01:24:57 +00:00
|
|
|
if(videoFrame is null ||
|
2020-01-01 18:06:31 +00:00
|
|
|
videoFrame.Length < _videoNowColorFrameMarker.Length)
|
2019-12-26 01:24:57 +00:00
|
|
|
return false;
|
2019-02-12 00:56:02 +00:00
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
byte[] buffer = new byte[_videoNowColorFrameMarker.Length];
|
2019-12-26 01:24:57 +00:00
|
|
|
|
2019-02-12 00:56:02 +00:00
|
|
|
for(int framePosition = 0; framePosition + buffer.Length < videoFrame.Length; framePosition++)
|
|
|
|
|
{
|
|
|
|
|
Array.Copy(videoFrame, framePosition, buffer, 0, buffer.Length);
|
|
|
|
|
|
2019-12-26 01:24:57 +00:00
|
|
|
for(int ab = 9; ab < buffer.Length; ab += 10)
|
|
|
|
|
buffer[ab] = 0;
|
2019-02-12 00:56:02 +00:00
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
if(!_videoNowColorFrameMarker.SequenceEqual(buffer))
|
2019-12-26 01:24:57 +00:00
|
|
|
continue;
|
2019-02-12 00:56:02 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-12-26 01:24:57 +00:00
|
|
|
|
|
|
|
|
public static int GetVideoNowColorOffset(byte[] data)
|
|
|
|
|
{
|
2020-01-01 18:06:31 +00:00
|
|
|
byte[] buffer = new byte[_videoNowColorFrameMarker.Length];
|
2019-12-26 01:24:57 +00:00
|
|
|
|
|
|
|
|
for(int framePosition = 0; framePosition + buffer.Length < data.Length; framePosition++)
|
|
|
|
|
{
|
|
|
|
|
Array.Copy(data, framePosition, buffer, 0, buffer.Length);
|
|
|
|
|
|
|
|
|
|
for(int ab = 9; ab < buffer.Length; ab += 10)
|
|
|
|
|
buffer[ab] = 0;
|
|
|
|
|
|
2020-01-01 18:06:31 +00:00
|
|
|
if(!_videoNowColorFrameMarker.SequenceEqual(buffer))
|
2019-12-26 01:24:57 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
return 18032 - framePosition;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2020-01-01 18:06:31 +00:00
|
|
|
|
|
|
|
|
public static void DetectDiscType(ref MediaType mediaType, int sessions, FullTOC.CDFullTOC? decodedToc,
|
|
|
|
|
Device dev, out bool hiddenTrack, out bool hiddenData,
|
|
|
|
|
int firstTrackLastSession)
|
|
|
|
|
{
|
|
|
|
|
uint startOfFirstDataTrack = uint.MaxValue;
|
|
|
|
|
byte[] cmdBuf;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
if(decodedToc.HasValue)
|
|
|
|
|
if(decodedToc.Value.TrackDescriptors.Any(t => t.SessionNumber == 2))
|
|
|
|
|
secondSessionFirstTrack =
|
|
|
|
|
decodedToc.Value.TrackDescriptors.Where(t => t.SessionNumber == 2).Min(t => t.POINT);
|
|
|
|
|
|
|
|
|
|
if(mediaType == MediaType.CD ||
|
|
|
|
|
mediaType == MediaType.CDROMXA)
|
|
|
|
|
{
|
|
|
|
|
bool hasDataTrack = false;
|
|
|
|
|
bool hasAudioTrack = false;
|
|
|
|
|
bool allFirstSessionTracksAreAudio = true;
|
|
|
|
|
bool hasVideoTrack = false;
|
|
|
|
|
|
|
|
|
|
if(decodedToc.HasValue)
|
|
|
|
|
foreach(FullTOC.TrackDataDescriptor track in decodedToc.Value.TrackDescriptors)
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
uint 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(hasDataTrack &&
|
|
|
|
|
hasAudioTrack &&
|
|
|
|
|
allFirstSessionTracksAreAudio &&
|
|
|
|
|
sessions == 2)
|
|
|
|
|
mediaType = MediaType.CDPLUS;
|
|
|
|
|
|
|
|
|
|
if(!hasDataTrack &&
|
|
|
|
|
hasAudioTrack &&
|
|
|
|
|
sessions == 1)
|
|
|
|
|
mediaType = MediaType.CDDA;
|
|
|
|
|
|
|
|
|
|
if(hasDataTrack &&
|
|
|
|
|
!hasAudioTrack &&
|
|
|
|
|
sessions == 1)
|
|
|
|
|
mediaType = MediaType.CDROM;
|
|
|
|
|
|
|
|
|
|
if(hasVideoTrack &&
|
|
|
|
|
!hasDataTrack &&
|
|
|
|
|
sessions == 1)
|
|
|
|
|
mediaType = MediaType.CDV;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(secondSessionFirstTrack != 0 &&
|
|
|
|
|
decodedToc.HasValue &&
|
|
|
|
|
decodedToc.Value.TrackDescriptors.Any(t => t.POINT == secondSessionFirstTrack))
|
|
|
|
|
{
|
|
|
|
|
FullTOC.TrackDataDescriptor secondSessionFirstTrackTrack =
|
|
|
|
|
decodedToc.Value.TrackDescriptors.First(t => t.POINT == secondSessionFirstTrack);
|
|
|
|
|
|
|
|
|
|
uint 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.None, 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, true, MmcHeaderCodes.None, true, true,
|
|
|
|
|
MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
if(!sense &&
|
|
|
|
|
!dev.Error)
|
|
|
|
|
firstTrackSecondSessionAudio = cmdBuf;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
videoNowColorFrame = new byte[9 * 2352];
|
|
|
|
|
|
|
|
|
|
for(int 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, true,
|
|
|
|
|
MmcHeaderCodes.None, true, true, MmcErrorField.None, MmcSubchannel.None,
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
if(sense || !dev.Error)
|
|
|
|
|
{
|
|
|
|
|
videoNowColorFrame = null;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Array.Copy(cmdBuf, 0, videoNowColorFrame, i * 2352, 2352);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(decodedToc.HasValue)
|
|
|
|
|
{
|
2020-01-19 19:04:34 +00:00
|
|
|
FullTOC.TrackDataDescriptor firstTrack =
|
|
|
|
|
decodedToc.Value.TrackDescriptors.FirstOrDefault(t => t.POINT == 1);
|
2020-01-01 18:06:31 +00:00
|
|
|
|
2020-01-19 19:04:34 +00:00
|
|
|
if(firstTrack.POINT == 1)
|
2020-01-01 18:06:31 +00:00
|
|
|
{
|
2020-01-19 19:04:34 +00:00
|
|
|
uint firstTrackSector = (uint)(((firstTrack.PHOUR * 3600 * 75) + (firstTrack.PMIN * 60 * 75) +
|
|
|
|
|
(firstTrack.PSEC * 75) + firstTrack.PFRAME) - 150);
|
2020-01-01 18:06:31 +00:00
|
|
|
|
2020-01-19 19:04:34 +00:00
|
|
|
// Check for hidden data before start of track 1
|
|
|
|
|
if(firstTrackSector > 0)
|
2020-01-01 18:06:31 +00:00
|
|
|
{
|
2020-01-19 19:04:34 +00:00
|
|
|
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 _);
|
2020-01-01 18:06:31 +00:00
|
|
|
|
2020-01-19 19:04:34 +00:00
|
|
|
if(!dev.Error &&
|
|
|
|
|
!sense)
|
2020-01-01 18:06:31 +00:00
|
|
|
{
|
2020-01-19 19:04:34 +00:00
|
|
|
hiddenTrack = true;
|
|
|
|
|
|
|
|
|
|
hiddenData = IsData(sector0);
|
2020-01-01 18:06:31 +00:00
|
|
|
|
2020-01-19 19:04:34 +00:00
|
|
|
if(hiddenData)
|
2020-01-01 18:06:31 +00:00
|
|
|
{
|
2020-01-19 19:04:34 +00:00
|
|
|
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(IsCdi(sector0, sector16))
|
|
|
|
|
{
|
|
|
|
|
mediaType = MediaType.CDIREADY;
|
|
|
|
|
}
|
2020-01-01 18:06:31 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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, true,
|
|
|
|
|
MmcHeaderCodes.None, true, true, 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, true,
|
|
|
|
|
MmcHeaderCodes.None, true, true, 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,
|
|
|
|
|
true, MmcHeaderCodes.None, true, true, 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,
|
|
|
|
|
true, MmcHeaderCodes.None, true, true, 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, true, MmcHeaderCodes.None, true,
|
|
|
|
|
true, 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, true, MmcHeaderCodes.None, true,
|
|
|
|
|
true, 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,
|
|
|
|
|
true, MmcHeaderCodes.None, true, true, 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, true,
|
|
|
|
|
MmcHeaderCodes.None, true, true, 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,
|
|
|
|
|
true, MmcHeaderCodes.None, true, true, 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,
|
|
|
|
|
true, MmcHeaderCodes.None, true, true, 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, true, MmcHeaderCodes.None,
|
|
|
|
|
true, true, 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, true, MmcHeaderCodes.None,
|
|
|
|
|
true, true, 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.Unknown:
|
|
|
|
|
sense = dev.Read16(out cmdBuf, out _, 0, false, true, false, 0, 2048, 0, 1, false, dev.Timeout,
|
|
|
|
|
out _);
|
|
|
|
|
|
|
|
|
|
if(!sense &&
|
|
|
|
|
!dev.Error)
|
|
|
|
|
{
|
|
|
|
|
sector0 = cmdBuf;
|
|
|
|
|
|
|
|
|
|
sense = dev.Read16(out cmdBuf, out _, 0, false, true, false, 1, 2048, 0, 1, false, dev.Timeout,
|
|
|
|
|
out _);
|
|
|
|
|
|
|
|
|
|
if(!sense &&
|
|
|
|
|
!dev.Error)
|
|
|
|
|
sector1 = cmdBuf;
|
|
|
|
|
|
|
|
|
|
sense = dev.Read16(out cmdBuf, out _, 0, false, true, 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, true, false, false, 0, 2048, 0, 1, false,
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
if(!sense &&
|
|
|
|
|
!dev.Error)
|
|
|
|
|
{
|
|
|
|
|
sector0 = cmdBuf;
|
|
|
|
|
|
|
|
|
|
sense = dev.Read12(out cmdBuf, out _, 0, false, true, false, false, 1, 2048, 0, 1, false,
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
if(!sense &&
|
|
|
|
|
!dev.Error)
|
|
|
|
|
sector1 = cmdBuf;
|
|
|
|
|
|
|
|
|
|
sense = dev.Read12(out cmdBuf, out _, 0, false, true, 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, true, false, false, 0, 2048, 0, 1,
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
if(!sense &&
|
|
|
|
|
!dev.Error)
|
|
|
|
|
{
|
|
|
|
|
sector0 = cmdBuf;
|
|
|
|
|
|
|
|
|
|
sense = dev.Read10(out cmdBuf, out _, 0, false, true, false, false, 1, 2048, 0, 1,
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
if(!sense &&
|
|
|
|
|
!dev.Error)
|
|
|
|
|
sector1 = cmdBuf;
|
|
|
|
|
|
|
|
|
|
sense = dev.Read10(out cmdBuf, out _, 0, false, true, 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Recordables will not be checked
|
|
|
|
|
case MediaType.CDR:
|
|
|
|
|
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: CDTV requires reading the filesystem, searching for a file called "/CDTV.TM"
|
|
|
|
|
// TODO: CD32 requires reading the filesystem, searching for a file called "/CD32.TM"
|
|
|
|
|
// TODO: Neo-Geo CD requires reading the filesystem and checking that the file "/IPL.TXT" is correct
|
|
|
|
|
// TODO: Pippin requires interpreting Apple Partition Map, reading HFS and checking for Pippin signatures
|
|
|
|
|
{
|
|
|
|
|
if(CD.DecodeIPBin(sector0).HasValue)
|
|
|
|
|
{
|
|
|
|
|
mediaType = MediaType.MEGACD;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(Saturn.DecodeIPBin(sector0).HasValue)
|
|
|
|
|
mediaType = MediaType.SATURNCD;
|
|
|
|
|
|
|
|
|
|
// Are GDR detectable ???
|
|
|
|
|
if(Dreamcast.DecodeIPBin(sector0).HasValue)
|
|
|
|
|
mediaType = MediaType.GDROM;
|
|
|
|
|
|
|
|
|
|
if(ps2BootSectors != null &&
|
|
|
|
|
ps2BootSectors.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(int i = 0; i < 0x6000; i++)
|
|
|
|
|
ps2BootSectors[i] ^= decryptByte;
|
|
|
|
|
|
|
|
|
|
string ps2BootSectorsHash = Sha256Context.Data(ps2BootSectors, out _);
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("Media-info Command", "PlayStation 2 boot sectors SHA256: {0}",
|
|
|
|
|
ps2BootSectorsHash);
|
|
|
|
|
|
|
|
|
|
if(ps2BootSectorsHash == PS2_PAL_HASH ||
|
|
|
|
|
ps2BootSectorsHash == PS2_NTSC_HASH ||
|
|
|
|
|
ps2BootSectorsHash == PS2_JAPANESE_HASH)
|
|
|
|
|
mediaType = MediaType.PS2CD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(sector0 != null)
|
|
|
|
|
{
|
|
|
|
|
byte[] syncBytes = new byte[7];
|
|
|
|
|
Array.Copy(sector0, 0, syncBytes, 0, 7);
|
|
|
|
|
|
|
|
|
|
if(_operaId.SequenceEqual(syncBytes))
|
|
|
|
|
mediaType = MediaType.ThreeDO;
|
|
|
|
|
|
|
|
|
|
if(_fmTownsBootId.SequenceEqual(syncBytes))
|
|
|
|
|
mediaType = MediaType.FMTOWNS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(playdia1 != null &&
|
|
|
|
|
playdia2 != null)
|
|
|
|
|
{
|
|
|
|
|
byte[] pd1 = new byte[_playdiaCopyright.Length];
|
|
|
|
|
byte[] 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(secondDataSectorNotZero != null)
|
|
|
|
|
{
|
|
|
|
|
byte[] pce = new byte[_pcEngineSignature.Length];
|
|
|
|
|
Array.Copy(secondDataSectorNotZero, 32, pce, 0, pce.Length);
|
|
|
|
|
|
|
|
|
|
if(_pcEngineSignature.SequenceEqual(pce))
|
|
|
|
|
mediaType = MediaType.SuperCDROM2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(firstDataSectorNotZero != null)
|
|
|
|
|
{
|
|
|
|
|
byte[] pcfx = new byte[_pcFxSignature.Length];
|
|
|
|
|
Array.Copy(firstDataSectorNotZero, 0, pcfx, 0, pcfx.Length);
|
|
|
|
|
|
|
|
|
|
if(_pcFxSignature.SequenceEqual(pcfx))
|
|
|
|
|
mediaType = MediaType.PCFX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(firstTrackSecondSessionAudio != null)
|
|
|
|
|
{
|
|
|
|
|
byte[] jaguar = new byte[_atariSignature.Length];
|
|
|
|
|
|
|
|
|
|
for(int 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;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(firstTrackSecondSession != null)
|
|
|
|
|
if(firstTrackSecondSession.Length >= 2336)
|
|
|
|
|
{
|
|
|
|
|
byte[] milcd = new byte[2048];
|
|
|
|
|
Array.Copy(firstTrackSecondSession, 24, milcd, 0, 2048);
|
|
|
|
|
|
|
|
|
|
if(Dreamcast.DecodeIPBin(milcd).HasValue)
|
|
|
|
|
mediaType = MediaType.MilCD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Detect black and white VideoNow
|
|
|
|
|
// TODO: Detect VideoNow XP
|
|
|
|
|
if(IsVideoNowColor(videoNowColorFrame))
|
|
|
|
|
mediaType = MediaType.VideoNowColor;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Check for CD-i Ready
|
|
|
|
|
case MediaType.CDI: break;
|
|
|
|
|
case MediaType.DVDROM:
|
|
|
|
|
case MediaType.HDDVDROM:
|
|
|
|
|
case MediaType.BDROM:
|
|
|
|
|
case MediaType.Unknown:
|
|
|
|
|
// TODO: Nuon requires reading the filesystem, searching for a file called "/NUON/NUON.RUN"
|
|
|
|
|
if(ps2BootSectors != null &&
|
|
|
|
|
ps2BootSectors.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(int i = 0; i < 0x6000; i++)
|
|
|
|
|
ps2BootSectors[i] ^= decryptByte;
|
|
|
|
|
|
|
|
|
|
string ps2BootSectorsHash = Sha256Context.Data(ps2BootSectors, out _);
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("Media-info Command", "PlayStation 2 boot sectors SHA256: {0}",
|
|
|
|
|
ps2BootSectorsHash);
|
|
|
|
|
|
|
|
|
|
if(ps2BootSectorsHash == PS2_PAL_HASH ||
|
|
|
|
|
ps2BootSectorsHash == PS2_NTSC_HASH ||
|
|
|
|
|
ps2BootSectorsHash == PS2_JAPANESE_HASH)
|
|
|
|
|
mediaType = MediaType.PS2DVD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(sector1 != null)
|
|
|
|
|
{
|
|
|
|
|
byte[] tmp = new byte[_ps3Id.Length];
|
|
|
|
|
Array.Copy(sector1, 0, tmp, 0, tmp.Length);
|
|
|
|
|
|
|
|
|
|
if(tmp.SequenceEqual(_ps3Id))
|
|
|
|
|
switch(mediaType)
|
|
|
|
|
{
|
|
|
|
|
case MediaType.BDROM:
|
|
|
|
|
mediaType = MediaType.PS3BD;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case MediaType.DVDROM:
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Identify discs that require reading tracks (PC-FX, PlayStation, Sega, etc)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-13 20:03:07 +01:00
|
|
|
}
|
|
|
|
|
}
|