BD support for Redumper image

This commit is contained in:
Rebecca Wallander
2026-04-05 17:59:24 +02:00
parent f72d55a4b8
commit 191b5773ae
12 changed files with 897 additions and 313 deletions

View File

@@ -366,6 +366,7 @@ public partial class Convert
case SectorTagType.DvdSectorIed:
case SectorTagType.DvdSectorInformation:
case SectorTagType.DvdSectorNumber:
case SectorTagType.BluRaySectorEdc:
// This tags are inline in long sector
continue;
}

View File

@@ -435,6 +435,7 @@ public sealed partial class Merger
case SectorTagType.DvdSectorIed:
case SectorTagType.DvdSectorInformation:
case SectorTagType.DvdSectorNumber:
case SectorTagType.BluRaySectorEdc:
// This tags are inline in long sector
continue;
}
@@ -668,6 +669,7 @@ public sealed partial class Merger
case SectorTagType.DvdSectorIed:
case SectorTagType.DvdSectorInformation:
case SectorTagType.DvdSectorNumber:
case SectorTagType.BluRaySectorEdc:
// This tags are inline in long sector
continue;
}

View File

@@ -0,0 +1,95 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : DataFrame.cs
//
// Component : Blu-ray DataFrame (redumper bd::DataFrame) scrambling.
//
// --[ Description ] ----------------------------------------------------------
//
// Descrambling and validity for 2052-byte Blu-ray raw frames per
// ISO/IEC 30190, matching redumper bd_scrambler / bd::DataFrame.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Rebecca Wallander
// ****************************************************************************/
using System;
using Aaru.Decoders.DVD;
using Aaru.Decoders.Bluray;
namespace Aaru.Decoders.Bluray;
/// <summary>Blu-ray DataFrame: 2048 bytes main_data + 4 bytes EDC (redumper raw .sbram).</summary>
public static class DataFrame
{
/// <summary>Total size of one BD DataFrame in a Redumper .sbram file.</summary>
public const int Size = 2048 + 4;
/// <summary>Descrambles a 2052-byte frame in place (ISO/IEC 30190 LFSR XOR).</summary>
public static void Descramble(Span<byte> frame, int lba, bool nintendo)
{
if(frame.Length != Size) throw new ArgumentException(nameof(frame));
ushort seed = nintendo ? SeedNintendo(lba) : SeedBluray(lba);
Process(frame, seed);
}
/// <summary>Copies <paramref name="scrambled" />, descrambles the copy, returns whether EDC matches.</summary>
public static bool IsValid(ReadOnlySpan<byte> scrambled2052, int lba, bool nintendo)
{
if(scrambled2052.Length != Size) return false;
byte[] work = new byte[Size];
scrambled2052.CopyTo(work);
Descramble(work, lba, nintendo);
return Sector.CheckEdc(work);
}
static ushort SeedBluray(int lba)
{
uint psn = (uint)lba + 0x100000;
return (ushort)(psn >> 5);
}
static ushort SeedNintendo(int lba)
{
uint m = (uint)lba & 0xFFFE0;
uint local = m & 0xFFFF;
uint k = local >> 8;
uint slope = (local & 0xFF) << 7;
uint layer = (m >> 16) & 0xF;
uint jump4k = ((local >> 12) & 0x3) << 9;
uint jump32k = (local >> 15) << 12;
uint toggle = (k & 1) << 5;
uint decay = k << 4;
return (ushort)(slope + 1248 + layer + toggle + jump4k + jump32k - decay);
}
static void Process(Span<byte> data, ushort seed)
{
ushort shiftRegister = (ushort)(0x8000 | (seed & 0x7FFF));
for(int i = 0; i < data.Length; i++)
{
data[i] ^= (byte)shiftRegister;
for(int b = 0; b < 8; b++)
{
int lsb = ((shiftRegister >> 15) ^ (shiftRegister >> 14) ^ (shiftRegister >> 12) ^ (shiftRegister >> 3)) & 1;
shiftRegister = (ushort)((shiftRegister << 1) | lsb);
}
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using Aaru.Helpers;
using System.Linq;
using System.Collections.Generic;
namespace Aaru.Decoders.Bluray;
public sealed class Sector
{
static readonly uint[] _edcTable =
[
0x00000000, 0x80000011, 0x80000033, 0x00000022, 0x80000077, 0x00000066, 0x00000044, 0x80000055, 0x800000FF,
0x000000EE, 0x000000CC, 0x800000DD, 0x00000088, 0x80000099, 0x800000BB, 0x000000AA, 0x800001EF, 0x000001FE,
0x000001DC, 0x800001CD, 0x00000198, 0x80000189, 0x800001AB, 0x000001BA, 0x00000110, 0x80000101, 0x80000123,
0x00000132, 0x80000167, 0x00000176, 0x00000154, 0x80000145, 0x800003CF, 0x000003DE, 0x000003FC, 0x800003ED,
0x000003B8, 0x800003A9, 0x8000038B, 0x0000039A, 0x00000330, 0x80000321, 0x80000303, 0x00000312, 0x80000347,
0x00000356, 0x00000374, 0x80000365, 0x00000220, 0x80000231, 0x80000213, 0x00000202, 0x80000257, 0x00000246,
0x00000264, 0x80000275, 0x800002DF, 0x000002CE, 0x000002EC, 0x800002FD, 0x000002A8, 0x800002B9, 0x8000029B,
0x0000028A, 0x8000078F, 0x0000079E, 0x000007BC, 0x800007AD, 0x000007F8, 0x800007E9, 0x800007CB, 0x000007DA,
0x00000770, 0x80000761, 0x80000743, 0x00000752, 0x80000707, 0x00000716, 0x00000734, 0x80000725, 0x00000660,
0x80000671, 0x80000653, 0x00000642, 0x80000617, 0x00000606, 0x00000624, 0x80000635, 0x8000069F, 0x0000068E,
0x000006AC, 0x800006BD, 0x000006E8, 0x800006F9, 0x800006DB, 0x000006CA, 0x00000440, 0x80000451, 0x80000473,
0x00000462, 0x80000437, 0x00000426, 0x00000404, 0x80000415, 0x800004BF, 0x000004AE, 0x0000048C, 0x8000049D,
0x000004C8, 0x800004D9, 0x800004FB, 0x000004EA, 0x800005AF, 0x000005BE, 0x0000059C, 0x8000058D, 0x000005D8,
0x800005C9, 0x800005EB, 0x000005FA, 0x00000550, 0x80000541, 0x80000563, 0x00000572, 0x80000527, 0x00000536,
0x00000514, 0x80000505, 0x80000F0F, 0x00000F1E, 0x00000F3C, 0x80000F2D, 0x00000F78, 0x80000F69, 0x80000F4B,
0x00000F5A, 0x00000FF0, 0x80000FE1, 0x80000FC3, 0x00000FD2, 0x80000F87, 0x00000F96, 0x00000FB4, 0x80000FA5,
0x00000EE0, 0x80000EF1, 0x80000ED3, 0x00000EC2, 0x80000E97, 0x00000E86, 0x00000EA4, 0x80000EB5, 0x80000E1F,
0x00000E0E, 0x00000E2C, 0x80000E3D, 0x00000E68, 0x80000E79, 0x80000E5B, 0x00000E4A, 0x00000CC0, 0x80000CD1,
0x80000CF3, 0x00000CE2, 0x80000CB7, 0x00000CA6, 0x00000C84, 0x80000C95, 0x80000C3F, 0x00000C2E, 0x00000C0C,
0x80000C1D, 0x00000C48, 0x80000C59, 0x80000C7B, 0x00000C6A, 0x80000D2F, 0x00000D3E, 0x00000D1C, 0x80000D0D,
0x00000D58, 0x80000D49, 0x80000D6B, 0x00000D7A, 0x00000DD0, 0x80000DC1, 0x80000DE3, 0x00000DF2, 0x80000DA7,
0x00000DB6, 0x00000D94, 0x80000D85, 0x00000880, 0x80000891, 0x800008B3, 0x000008A2, 0x800008F7, 0x000008E6,
0x000008C4, 0x800008D5, 0x8000087F, 0x0000086E, 0x0000084C, 0x8000085D, 0x00000808, 0x80000819, 0x8000083B,
0x0000082A, 0x8000096F, 0x0000097E, 0x0000095C, 0x8000094D, 0x00000918, 0x80000909, 0x8000092B, 0x0000093A,
0x00000990, 0x80000981, 0x800009A3, 0x000009B2, 0x800009E7, 0x000009F6, 0x000009D4, 0x800009C5, 0x80000B4F,
0x00000B5E, 0x00000B7C, 0x80000B6D, 0x00000B38, 0x80000B29, 0x80000B0B, 0x00000B1A, 0x00000BB0, 0x80000BA1,
0x80000B83, 0x00000B92, 0x80000BC7, 0x00000BD6, 0x00000BF4, 0x80000BE5, 0x00000AA0, 0x80000AB1, 0x80000A93,
0x00000A82, 0x80000AD7, 0x00000AC6, 0x00000AE4, 0x80000AF5, 0x80000A5F, 0x00000A4E, 0x00000A6C, 0x80000A7D,
0x00000A28, 0x80000A39, 0x80000A1B, 0x00000A0A
];
static uint ComputeEdc(uint edc, IReadOnlyList<byte> src, int size)
{
var pos = 0;
for(; size > 0; size--) edc = _edcTable[(edc >> 24 ^ src[pos++]) & 0xFF] ^ edc << 8;
return edc;
}
/// <summary>
/// Validates the EDC on a descrambled Blu-ray DataFrame (2048-byte main_data + 4-byte EDC, big-endian),
/// CRC-32/DVD-ROM-EDC over main_data only (ISO/IEC 30190).
/// </summary>
/// <param name="dataFrameDescrambled">The descrambled Blu-ray DataFrame</param>
/// <returns><c>True</c> if EDC is correct, <c>False</c> if not</returns>
public static bool CheckEdc(byte[] dataFrameDescrambled)
{
if(dataFrameDescrambled is null || dataFrameDescrambled.Length < 2052) return false;
return ComputeEdc(0, dataFrameDescrambled, 2048) == BigEndianBitConverter.ToUInt32(dataFrameDescrambled, 2048);
}
/// <summary>
/// Validates the EDC on a descrambled Blu-ray DataFrame (2048-byte main_data + 4-byte EDC, big-endian),
/// CRC-32/DVD-ROM-EDC over main_data only (ISO/IEC 30190).
/// </summary>
/// <param name="dataFrameDescrambled"></param>
/// <param name="transferLength"></param>
/// <returns><c>True</c> if EDC is correct, <c>False</c> if not</returns>
public static bool CheckEdc(byte[] dataFrameDescrambled, uint transferLength)
{
for(uint i = 0; i < transferLength; i++)
{
if(!CheckEdc(dataFrameDescrambled.Skip((int)(i * 2052)).Take(2052).ToArray())) return false;
}
return true;
}
}

View File

@@ -0,0 +1,260 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Bluray.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disc image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Reads Redumper raw Blu-ray (.sbram + .state) images.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Rebecca Wallander
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Decoders.Bluray;
using Aaru.Helpers;
using Aaru.Logging;
using Partition = Aaru.CommonTypes.Partition;
namespace Aaru.Images;
public sealed partial class Redumper
{
ErrorNumber OpenBd(IFilter imageFilter, string basePath, string sbramPath)
{
_isBluRay = true;
_lbaStart = BD_LBA_START;
_bdNintendo = false;
long stateLength = imageFilter.DataForkLength;
long sbramLength = new FileInfo(sbramPath).Length;
if (sbramLength % BD_DATA_FRAME_SIZE != 0) return ErrorNumber.InvalidArgument;
_totalFrames = sbramLength / BD_DATA_FRAME_SIZE;
if (stateLength != _totalFrames) return ErrorNumber.InvalidArgument;
_imageFilter = imageFilter;
Stream stateStream = imageFilter.GetDataForkStream();
_stateData = new byte[stateLength];
stateStream.Seek(0, SeekOrigin.Begin);
stateStream.EnsureRead(_stateData, 0, (int)stateLength);
_ramFilter = PluginRegister.Singleton.GetFilter(sbramPath);
if (_ramFilter is null) return ErrorNumber.NoSuchFile;
_ramPath = sbramPath;
long negativeLbaCount = Math.Min(-(long)_lbaStart, _totalFrames);
long positiveLbaCount = Math.Max(0, _totalFrames + _lbaStart);
_imageInfo.NegativeSectors = (uint)negativeLbaCount;
_imageInfo.Sectors = (ulong)positiveLbaCount;
_imageInfo.SectorSize = USER_DATA_SIZE;
_imageInfo.ImageSize = _imageInfo.Sectors * USER_DATA_SIZE;
_imageInfo.CreationTime = imageFilter.CreationTime;
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
_imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.Filename);
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
_imageInfo.HasPartitions = true;
_imageInfo.HasSessions = true;
_imageInfo.MediaType = MediaType.BDROM;
_mediaTags = new Dictionary<MediaTagType, byte[]>();
LoadBdMediaTagSidecars(basePath);
_imageInfo.ReadableMediaTags = [.. _mediaTags.Keys];
_imageInfo.ReadableSectorTags = [SectorTagType.BluRaySectorEdc];
Tracks =
[
new Track
{
Sequence = 1,
Session = 1,
Type = TrackType.Data,
StartSector = 0,
EndSector = _imageInfo.Sectors > 0 ? _imageInfo.Sectors - 1 : 0,
Pregap = 0,
FileType = "BINARY",
Filter = _ramFilter,
File = sbramPath,
BytesPerSector = USER_DATA_SIZE,
RawBytesPerSector = BD_DATA_FRAME_SIZE
}
];
Sessions =
[
new Session
{
Sequence = 1,
StartSector = 0,
EndSector = _imageInfo.Sectors > 0 ? _imageInfo.Sectors - 1 : 0,
StartTrack = 1,
EndTrack = 1
}
];
Partitions =
[
new Partition
{
Sequence = 0,
Start = 0,
Length = _imageInfo.Sectors,
Size = _imageInfo.Sectors * _imageInfo.SectorSize,
Offset = 0,
Type = "Blu-ray Data"
}
];
return ErrorNumber.NoError;
}
ErrorNumber ReadSectorForBd(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{
buffer = new byte[USER_DATA_SIZE];
ErrorNumber errno = ReadSectorLongForBd(sectorAddress, negative, out byte[] longBuf, out sectorStatus);
if (errno != ErrorNumber.NoError) return errno;
Array.Copy(longBuf, 0, buffer, 0, USER_DATA_SIZE);
return ErrorNumber.NoError;
}
ErrorNumber ReadSectorLongForBd(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{
buffer = null;
sectorStatus = SectorStatus.NotDumped;
int lba = negative ? -(int)sectorAddress : (int)sectorAddress;
long frameIndex = (long)lba - _lbaStart;
if (frameIndex < 0 || frameIndex >= _totalFrames) return ErrorNumber.OutOfRange;
sectorStatus = MapState(_stateData[frameIndex]);
byte[] frame = ReadBdFrame(frameIndex);
if (frame is null) return ErrorNumber.InvalidArgument;
if (sectorStatus != SectorStatus.Dumped)
{
buffer = frame;
return ErrorNumber.NoError;
}
byte[] work = new byte[BD_DATA_FRAME_SIZE];
Array.Copy(frame, 0, work, 0, BD_DATA_FRAME_SIZE);
DataFrame.Descramble(work, lba, _bdNintendo);
buffer = work;
return ErrorNumber.NoError;
}
/// <summary>Reads one 2052-byte BD DataFrame from .sbram.</summary>
byte[] ReadBdFrame(long frameIndex)
{
Stream stream = _ramFilter.GetDataForkStream();
long offset = frameIndex * BD_DATA_FRAME_SIZE;
if (offset + BD_DATA_FRAME_SIZE > stream.Length) return null;
var frame = new byte[BD_DATA_FRAME_SIZE];
stream.Seek(offset, SeekOrigin.Begin);
stream.EnsureRead(frame, 0, BD_DATA_FRAME_SIZE);
return frame;
}
ErrorNumber ReadSectorsTagForBd(ulong sectorAddress, bool negative, uint length, SectorTagType tag, out byte[] buffer)
{
buffer = null;
if (tag != SectorTagType.BluRaySectorEdc) return ErrorNumber.NotSupported;
buffer = new byte[4 * length];
for (uint i = 0; i < length; i++)
{
ErrorNumber errno = ReadSectorLong(sectorAddress + i, negative, out byte[] sector, out _);
if (errno != ErrorNumber.NoError) return errno;
Array.Copy(sector, 2048, buffer, i * 4, 4);
}
return ErrorNumber.NoError;
}
/// <summary>Loads BD sidecars: BCA (SCSI strip, same as device dump), Nintendo heuristic from .physical.</summary>
void LoadBdMediaTagSidecars(string basePath)
{
TryBdNintendoFromPhysicalSidecar(basePath);
LoadScsiSidecar(basePath, ".bca", null, MediaTagType.BD_BCA);
}
/// <summary>
/// If a single-layer style .physical sidecar exists and the payload (after SCSI header strip) is all zero,
/// treat the dump as Nintendo BD scrambling (redumper bd_extract_iso / dvd_dump).
/// </summary>
void TryBdNintendoFromPhysicalSidecar(string basePath)
{
string[] candidates = [basePath + ".physical", basePath + ".0.physical"];
foreach (string path in candidates)
{
if (!File.Exists(path)) continue;
byte[] data = File.ReadAllBytes(path);
if (data.Length <= SCSI_HEADER_SIZE) continue;
byte[] payload = new byte[data.Length - SCSI_HEADER_SIZE];
Array.Copy(data, SCSI_HEADER_SIZE, payload, 0, payload.Length);
if (payload.Length > 0 && payload.All(b => b == 0)) _bdNintendo = true;
return;
}
}
}

309
Aaru.Images/Redumper/Dvd.cs Normal file
View File

@@ -0,0 +1,309 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Dvd.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disc image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Reads Redumper raw DVD (.sdram + .state) images.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Rebecca Wallander
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Decoders.DVD;
using Aaru.Helpers;
using Aaru.Logging;
using Partition = Aaru.CommonTypes.Partition;
namespace Aaru.Images;
public sealed partial class Redumper
{
ErrorNumber OpenDvd(IFilter imageFilter, string basePath, string sdramPath)
{
_isBluRay = false;
_lbaStart = DVD_LBA_START;
_bdNintendo = false;
long stateLength = imageFilter.DataForkLength;
long sdramLength = new FileInfo(sdramPath).Length;
if (sdramLength % RECORDING_FRAME_SIZE != 0) return ErrorNumber.InvalidArgument;
_totalFrames = sdramLength / RECORDING_FRAME_SIZE;
if (stateLength != _totalFrames) return ErrorNumber.InvalidArgument;
_imageFilter = imageFilter;
Stream stateStream = imageFilter.GetDataForkStream();
_stateData = new byte[stateLength];
stateStream.Seek(0, SeekOrigin.Begin);
stateStream.EnsureRead(_stateData, 0, (int)stateLength);
_ramFilter = PluginRegister.Singleton.GetFilter(sdramPath);
if (_ramFilter is null) return ErrorNumber.NoSuchFile;
_ramPath = sdramPath;
long negativeLbaCount = Math.Min(-_lbaStart, _totalFrames);
long positiveLbaCount = Math.Max(0, _totalFrames + _lbaStart);
_imageInfo.NegativeSectors = (uint)negativeLbaCount;
_imageInfo.Sectors = (ulong)positiveLbaCount;
_imageInfo.SectorSize = USER_DATA_SIZE;
_imageInfo.ImageSize = _imageInfo.Sectors * USER_DATA_SIZE;
_imageInfo.CreationTime = imageFilter.CreationTime;
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
_imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.Filename);
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
_imageInfo.HasPartitions = true;
_imageInfo.HasSessions = true;
_mediaTags = new Dictionary<MediaTagType, byte[]>();
LoadDvdMediaTagSidecars(basePath);
_imageInfo.MediaType = MediaType.DVDROM;
if (_mediaTags.TryGetValue(MediaTagType.DVD_PFI, out byte[] pfi))
{
PFI.PhysicalFormatInformation? decodedPfi = PFI.Decode(pfi, _imageInfo.MediaType);
if (decodedPfi.HasValue)
{
_imageInfo.MediaType = decodedPfi.Value.DiskCategory switch
{
DiskCategory.DVDPR => MediaType.DVDPR,
DiskCategory.DVDPRDL => MediaType.DVDPRDL,
DiskCategory.DVDPRW => MediaType.DVDPRW,
DiskCategory.DVDPRWDL => MediaType.DVDPRWDL,
DiskCategory.DVDR => decodedPfi.Value.PartVersion >= 6 ? MediaType.DVDRDL : MediaType.DVDR,
DiskCategory.DVDRAM => MediaType.DVDRAM,
DiskCategory.DVDRW => decodedPfi.Value.PartVersion >= 15 ? MediaType.DVDRWDL : MediaType.DVDRW,
DiskCategory.Nintendo => decodedPfi.Value.DiscSize == DVDSize.Eighty
? MediaType.GOD
: MediaType.WOD,
_ => MediaType.DVDROM
};
if (decodedPfi.Value.DataAreaEndPSN >= decodedPfi.Value.DataAreaStartPSN)
_ngcwRegularDataSectors =
(ulong)(decodedPfi.Value.DataAreaEndPSN - decodedPfi.Value.DataAreaStartPSN) + 1;
}
}
TryInitializeNgcwAfterOpen();
_imageInfo.ReadableMediaTags = [.. _mediaTags.Keys];
_imageInfo.ReadableSectorTags =
[
SectorTagType.DvdSectorInformation,
SectorTagType.DvdSectorNumber,
SectorTagType.DvdSectorIed,
SectorTagType.DvdSectorCmi,
SectorTagType.DvdSectorTitleKey,
SectorTagType.DvdSectorEdc
];
Tracks =
[
new Track
{
Sequence = 1,
Session = 1,
Type = TrackType.Data,
StartSector = 0,
EndSector = _imageInfo.Sectors > 0 ? _imageInfo.Sectors - 1 : 0,
Pregap = 0,
FileType = "BINARY",
Filter = _ramFilter,
File = sdramPath,
BytesPerSector = USER_DATA_SIZE,
RawBytesPerSector = DVD_SECTOR_SIZE
}
];
Sessions =
[
new Session
{
Sequence = 1,
StartSector = 0,
EndSector = _imageInfo.Sectors > 0 ? _imageInfo.Sectors - 1 : 0,
StartTrack = 1,
EndTrack = 1
}
];
Partitions =
[
new Partition
{
Sequence = 0,
Start = 0,
Length = _imageInfo.Sectors,
Size = _imageInfo.Sectors * _imageInfo.SectorSize,
Offset = 0,
Type = "DVD Data"
}
];
return ErrorNumber.NoError;
}
ErrorNumber ReadSectorForDvd(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{
buffer = new byte[USER_DATA_SIZE];
ErrorNumber err = ReadSectorLongForDvd(sectorAddress, negative, out byte[] long_buffer, out sectorStatus);
if (err != ErrorNumber.NoError) return err;
Array.Copy(long_buffer, Aaru.Decoders.Nintendo.Sector.NintendoMainDataOffset, buffer, 0, USER_DATA_SIZE);
return ErrorNumber.NoError;
}
ErrorNumber ReadSectorLongForDvd(ulong sectorAddress, bool negative, out byte[] buffer,
out SectorStatus sectorStatus)
{
sectorStatus = SectorStatus.Dumped;
// The NGCW logic for DVDs are the same as for Nintendo
return ReadSectorLongForNgcw(sectorAddress, negative, out buffer, out sectorStatus);
}
ErrorNumber ReadSectorsTagForDvd(ulong sectorAddress, bool negative, uint length, SectorTagType tag, out byte[] buffer)
{
buffer = null;
uint sectorOffset;
uint sectorSize;
switch (tag)
{
case SectorTagType.DvdSectorInformation:
sectorOffset = 0;
sectorSize = 1;
break;
case SectorTagType.DvdSectorNumber:
sectorOffset = 1;
sectorSize = 3;
break;
case SectorTagType.DvdSectorIed:
sectorOffset = 4;
sectorSize = 2;
break;
case SectorTagType.DvdSectorCmi:
sectorOffset = 6;
sectorSize = 1;
break;
case SectorTagType.DvdSectorTitleKey:
sectorOffset = 7;
sectorSize = 5;
break;
case SectorTagType.DvdSectorEdc:
sectorOffset = 2060;
sectorSize = 4;
break;
default:
return ErrorNumber.NotSupported;
}
buffer = new byte[sectorSize * length];
for (uint i = 0; i < length; i++)
{
ErrorNumber errno = ReadSectorLong(sectorAddress + i, negative, out byte[] sector, out _);
if (errno != ErrorNumber.NoError) return errno;
Array.Copy(sector, sectorOffset, buffer, i * sectorSize, sectorSize);
}
return ErrorNumber.NoError;
}
/// <summary>
/// Reads one RecordingFrame from the .sdram file and flattens it into a 2064-byte DVD sector
/// by extracting only the 172-byte main_data portion from each of the 12 rows (discarding PI/PO parity).
/// </summary>
byte[] ReadAndFlattenFrame(long frameIndex)
{
Stream stream = _ramFilter.GetDataForkStream();
long offset = frameIndex * RECORDING_FRAME_SIZE;
if (offset + RECORDING_FRAME_SIZE > stream.Length) return null;
var frame = new byte[RECORDING_FRAME_SIZE];
stream.Seek(offset, SeekOrigin.Begin);
stream.EnsureRead(frame, 0, RECORDING_FRAME_SIZE);
var dvdSector = new byte[DVD_SECTOR_SIZE];
int rowStride = ROW_MAIN_DATA_SIZE + ROW_PARITY_INNER_SIZE;
for (int row = 0; row < RECORDING_FRAME_ROWS; row++)
Array.Copy(frame, row * rowStride, dvdSector, row * ROW_MAIN_DATA_SIZE, ROW_MAIN_DATA_SIZE);
return dvdSector;
}
/// <summary>Loads DVD sidecar files (.physical, .manufacturer, .bca).</summary>
void LoadDvdMediaTagSidecars(string basePath)
{
LoadScsiSidecar(basePath, ".physical", ".0.physical", MediaTagType.DVD_PFI);
LoadScsiSidecar(basePath, ".1.physical", null, MediaTagType.DVD_PFI_2ndLayer);
LoadScsiSidecar(basePath, ".manufacturer", ".0.manufacturer", MediaTagType.DVD_DMI);
string bcaPath = basePath + ".bca";
if (File.Exists(bcaPath))
{
byte[] bcaData = File.ReadAllBytes(bcaPath);
if (bcaData.Length > 0)
{
_mediaTags[MediaTagType.DVD_BCA] = bcaData;
AaruLogging.Debug(MODULE_NAME, Localization.Found_media_tag_0, MediaTagType.DVD_BCA);
}
}
}
}

View File

@@ -9,7 +9,7 @@
//
// --[ Description ] ----------------------------------------------------------
//
// Identifies Redumper raw DVD dump images.
// Identifies Redumper raw DVD (.sdram + .state) and Blu-ray (.sbram + .state) dumps.
//
// --[ License ] --------------------------------------------------------------
//
@@ -42,30 +42,30 @@ public sealed partial class Redumper
/// <inheritdoc />
public bool Identify(IFilter imageFilter)
{
string filename = imageFilter.Filename;
string path = imageFilter.Path;
if(string.IsNullOrEmpty(filename)) return false;
if(string.IsNullOrEmpty(path)) return false;
string extension = Path.GetExtension(filename)?.ToLower();
string extension = Path.GetExtension(path)?.ToLower();
if(extension != ".state") return false;
string basePath = filename[..^".state".Length];
string basePath = path[..^".state".Length];
string sdramPath = basePath + ".sdram";
if(!File.Exists(sdramPath)) return false;
string sbramPath = basePath + ".sbram";
long stateLength = imageFilter.DataForkLength;
long sdramLength = new FileInfo(sdramPath).Length;
if(sdramLength == 0 || stateLength == 0) return false;
bool sdramOk = File.Exists(sdramPath) &&
new FileInfo(sdramPath).Length % RECORDING_FRAME_SIZE == 0 &&
stateLength == new FileInfo(sdramPath).Length / RECORDING_FRAME_SIZE;
if(sdramLength % RECORDING_FRAME_SIZE != 0) return false;
bool sbramOk = File.Exists(sbramPath) &&
new FileInfo(sbramPath).Length % BD_DATA_FRAME_SIZE == 0 &&
stateLength == new FileInfo(sbramPath).Length / BD_DATA_FRAME_SIZE;
long frameCount = sdramLength / RECORDING_FRAME_SIZE;
return stateLength == frameCount;
return sdramOk || sbramOk;
}
#endregion
}
}

View File

@@ -49,6 +49,8 @@ public sealed partial class Redumper
{
_nintendoDerivedKey = null;
if(_isBluRay) return;
if(!IsNintendoMediaType(_imageInfo.MediaType)) return;
EnsureNintendoDerivedKeyFromLba0();
@@ -84,7 +86,7 @@ public sealed partial class Redumper
int lba = negative ? -(int)sectorAddress : (int)sectorAddress;
long frameIndex = (long)lba - LBA_START;
long frameIndex = (long)lba - _lbaStart;
if(frameIndex < 0 || frameIndex >= _totalFrames) return ErrorNumber.OutOfRange;

View File

@@ -9,7 +9,7 @@
//
// --[ Description ] ----------------------------------------------------------
//
// Contains properties for Redumper raw DVD dump images.
// Contains properties for Redumper raw DVD and Blu-ray dump images.
//
// --[ License ] --------------------------------------------------------------
//
@@ -83,7 +83,8 @@ public sealed partial class Redumper
MediaTagType.DVD_PFI,
MediaTagType.DVD_PFI_2ndLayer,
MediaTagType.DVD_DMI,
MediaTagType.DVD_BCA
MediaTagType.DVD_BCA,
MediaTagType.BD_BCA
];
/// <inheritdoc />
@@ -94,7 +95,8 @@ public sealed partial class Redumper
SectorTagType.DvdSectorIed,
SectorTagType.DvdSectorCmi,
SectorTagType.DvdSectorTitleKey,
SectorTagType.DvdSectorEdc
SectorTagType.DvdSectorEdc,
SectorTagType.BluRaySectorEdc
];
/// <inheritdoc />
@@ -102,6 +104,8 @@ public sealed partial class Redumper
[
MediaType.DVDROM, MediaType.DVDR, MediaType.DVDRDL, MediaType.DVDRW, MediaType.DVDRWDL, MediaType.DVDRAM,
MediaType.DVDPR, MediaType.DVDPRDL, MediaType.DVDPRW, MediaType.DVDPRWDL, MediaType.GOD, MediaType.WOD,
MediaType.BDROM, MediaType.BDR, MediaType.BDRE, MediaType.BDRXL, MediaType.BDREXL, MediaType.UHDBD,
MediaType.PS3DVD, MediaType.PS3BD, MediaType.PS4BD, MediaType.PS5BD, MediaType.PS2DVD, MediaType.Nuon,
];
/// <inheritdoc />

View File

@@ -9,7 +9,7 @@
//
// --[ Description ] ----------------------------------------------------------
//
// Reads Redumper raw DVD dump images (.sdram + .state).
// Reads Redumper raw DVD (.sdram + .state) and Blu-ray (.sbram + .state) images.
//
// --[ License ] --------------------------------------------------------------
//
@@ -38,7 +38,6 @@ using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Decoders.DVD;
using Aaru.Helpers;
using Aaru.Logging;
using Partition = Aaru.CommonTypes.Partition;
@@ -50,164 +49,58 @@ namespace Aaru.Images;
public sealed partial class Redumper
{
#region IOpticalMediaImage Members
#region IOpticalMediaImage Members
/// <inheritdoc />
public ErrorNumber Open(IFilter imageFilter)
{
string filename = imageFilter.Filename;
string path = imageFilter.Path;
_ngcwRegularDataSectors = 0;
if(string.IsNullOrEmpty(filename)) return ErrorNumber.InvalidArgument;
if (string.IsNullOrEmpty(path)) return ErrorNumber.InvalidArgument;
string basePath = filename[..^".state".Length];
string basePath = path[..^".state".Length];
string sdramPath = basePath + ".sdram";
if(!File.Exists(sdramPath)) return ErrorNumber.NoSuchFile;
string sbramPath = basePath + ".sbram";
long stateLength = imageFilter.DataForkLength;
long sdramLength = new FileInfo(sdramPath).Length;
if(sdramLength % RECORDING_FRAME_SIZE != 0) return ErrorNumber.InvalidArgument;
bool sdramOk = File.Exists(sdramPath) &&
new FileInfo(sdramPath).Length % RECORDING_FRAME_SIZE == 0 &&
stateLength == new FileInfo(sdramPath).Length / RECORDING_FRAME_SIZE;
_totalFrames = sdramLength / RECORDING_FRAME_SIZE;
bool sbramOk = File.Exists(sbramPath) &&
new FileInfo(sbramPath).Length % BD_DATA_FRAME_SIZE == 0 &&
stateLength == new FileInfo(sbramPath).Length / BD_DATA_FRAME_SIZE;
if(stateLength != _totalFrames) return ErrorNumber.InvalidArgument;
_imageFilter = imageFilter;
// Read entire state file into memory (1 byte per frame, manageable size)
Stream stateStream = imageFilter.GetDataForkStream();
_stateData = new byte[stateLength];
stateStream.Seek(0, SeekOrigin.Begin);
stateStream.EnsureRead(_stateData, 0, (int)stateLength);
// Open sdram via filter system
_sdramFilter = PluginRegister.Singleton.GetFilter(sdramPath);
if(_sdramFilter is null) return ErrorNumber.NoSuchFile;
// Compute sector counts
// Frames map to physical LBAs: frame[i] → LBA (LBA_START + i)
// Negative LBAs: LBA_START .. -1, count = min(-LBA_START, _totalFrames)
// Positive LBAs: 0 .. (_totalFrames + LBA_START - 1)
long negativeLbaCount = Math.Min(-LBA_START, _totalFrames);
long positiveLbaCount = Math.Max(0, _totalFrames + LBA_START);
_imageInfo.NegativeSectors = (uint)negativeLbaCount;
_imageInfo.Sectors = (ulong)positiveLbaCount;
_imageInfo.SectorSize = DVD_USER_DATA_SIZE;
_imageInfo.ImageSize = _imageInfo.Sectors * DVD_USER_DATA_SIZE;
_imageInfo.CreationTime = imageFilter.CreationTime;
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
_imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.Filename);
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
_imageInfo.HasPartitions = true;
_imageInfo.HasSessions = true;
// Load media tag sidecars
_mediaTags = new Dictionary<MediaTagType, byte[]>();
LoadMediaTagSidecars(basePath);
// Determine media type from PFI if available
_imageInfo.MediaType = MediaType.DVDROM;
if(_mediaTags.TryGetValue(MediaTagType.DVD_PFI, out byte[] pfi))
// Fall back to DVD if both are valid
if (sdramOk && sbramOk)
{
PFI.PhysicalFormatInformation? decodedPfi = PFI.Decode(pfi, _imageInfo.MediaType);
if(decodedPfi.HasValue)
{
_imageInfo.MediaType = decodedPfi.Value.DiskCategory switch
{
DiskCategory.DVDPR => MediaType.DVDPR,
DiskCategory.DVDPRDL => MediaType.DVDPRDL,
DiskCategory.DVDPRW => MediaType.DVDPRW,
DiskCategory.DVDPRWDL => MediaType.DVDPRWDL,
DiskCategory.DVDR => decodedPfi.Value.PartVersion >= 6 ? MediaType.DVDRDL : MediaType.DVDR,
DiskCategory.DVDRAM => MediaType.DVDRAM,
DiskCategory.DVDRW => decodedPfi.Value.PartVersion >= 15 ? MediaType.DVDRWDL : MediaType.DVDRW,
DiskCategory.Nintendo => decodedPfi.Value.DiscSize == DVDSize.Eighty
? MediaType.GOD
: MediaType.WOD,
_ => MediaType.DVDROM
};
if(decodedPfi.Value.DataAreaEndPSN >= decodedPfi.Value.DataAreaStartPSN)
_ngcwRegularDataSectors =
(ulong)(decodedPfi.Value.DataAreaEndPSN - decodedPfi.Value.DataAreaStartPSN) + 1;
}
AaruLogging.Debug(MODULE_NAME, "Both SDRAM and SBRAM are found, falling back to DVD");
return OpenDvd(imageFilter, basePath, sdramPath);
}
TryInitializeNgcwAfterOpen();
if (sdramOk)
{
AaruLogging.Debug(MODULE_NAME, "SDRAM is found, opening as DVD");
return OpenDvd(imageFilter, basePath, sdramPath);
}
_imageInfo.ReadableMediaTags = [.._mediaTags.Keys];
if (sbramOk)
{
AaruLogging.Debug(MODULE_NAME, "SBRAM is found, opening as Blu-ray");
return OpenBd(imageFilter, basePath, sbramPath);
}
// Sector tags available from DVD RecordingFrame structure
_imageInfo.ReadableSectorTags =
[
SectorTagType.DvdSectorInformation,
SectorTagType.DvdSectorNumber,
SectorTagType.DvdSectorIed,
SectorTagType.DvdSectorCmi,
SectorTagType.DvdSectorTitleKey,
SectorTagType.DvdSectorEdc
];
// Set up single track and session covering positive LBAs
Tracks =
[
new Track
{
Sequence = 1,
Session = 1,
Type = TrackType.Data,
StartSector = 0,
EndSector = _imageInfo.Sectors > 0 ? _imageInfo.Sectors - 1 : 0,
Pregap = 0,
FileType = "BINARY",
Filter = _sdramFilter,
File = sdramPath,
BytesPerSector = DVD_USER_DATA_SIZE,
RawBytesPerSector = DVD_SECTOR_SIZE
}
];
Sessions =
[
new Session
{
Sequence = 1,
StartSector = 0,
EndSector = _imageInfo.Sectors > 0 ? _imageInfo.Sectors - 1 : 0,
StartTrack = 1,
EndTrack = 1
}
];
Partitions =
[
new Partition
{
Sequence = 0,
Start = 0,
Length = _imageInfo.Sectors,
Size = _imageInfo.Sectors * _imageInfo.SectorSize,
Offset = 0,
Type = "DVD Data"
}
];
return ErrorNumber.NoError;
return ErrorNumber.NoSuchFile;
}
/// <inheritdoc />
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer)
{
buffer = null;
if(!_mediaTags.TryGetValue(tag, out byte[] data)) return ErrorNumber.NoData;
if (!_mediaTags.TryGetValue(tag, out byte[] data)) return ErrorNumber.NoData;
buffer = new byte[data.Length];
Array.Copy(data, buffer, data.Length);
@@ -218,33 +111,29 @@ public sealed partial class Redumper
/// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{
buffer = new byte[DVD_USER_DATA_SIZE];
ErrorNumber errno = ReadSectorLong(sectorAddress, negative, out byte[] long_buffer, out sectorStatus);
if(errno != ErrorNumber.NoError) return errno;
if (_isBluRay) return ReadSectorForBd(sectorAddress, negative, out buffer, out sectorStatus);
Array.Copy(long_buffer, Aaru.Decoders.Nintendo.Sector.NintendoMainDataOffset, buffer, 0, DVD_USER_DATA_SIZE);
return ErrorNumber.NoError;
return ReadSectorForDvd(sectorAddress, negative, out buffer, out sectorStatus);
}
/// <inheritdoc />
public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
out SectorStatus[] sectorStatus)
{
buffer = null;
buffer = null;
sectorStatus = null;
buffer = new byte[length * DVD_USER_DATA_SIZE];
buffer = new byte[length * USER_DATA_SIZE];
sectorStatus = new SectorStatus[length];
for(uint i = 0; i < length; i++)
for (uint i = 0; i < length; i++)
{
ulong addr = negative ? sectorAddress - i : sectorAddress + i;
ErrorNumber errno = ReadSector(addr, negative, out byte[] sector, out SectorStatus status);
if(errno != ErrorNumber.NoError) return errno;
if (errno != ErrorNumber.NoError) return errno;
Array.Copy(sector, 0, buffer, i * DVD_USER_DATA_SIZE, DVD_USER_DATA_SIZE);
Array.Copy(sector, 0, buffer, i * USER_DATA_SIZE, USER_DATA_SIZE);
sectorStatus[i] = status;
}
@@ -252,27 +141,33 @@ public sealed partial class Redumper
}
/// <inheritdoc />
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
out SectorStatus sectorStatus) =>
ReadSectorLongForNgcw(sectorAddress, negative, out buffer, out sectorStatus);
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
out SectorStatus sectorStatus)
{
if (_isBluRay) return ReadSectorLongForBd(sectorAddress, negative, out buffer, out sectorStatus);
return ReadSectorLongForNgcw(sectorAddress, negative, out buffer, out sectorStatus);
}
/// <inheritdoc />
public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
out SectorStatus[] sectorStatus)
{
buffer = null;
buffer = null;
sectorStatus = null;
buffer = new byte[length * DVD_SECTOR_SIZE];
int longSize = _isBluRay ? BD_DATA_FRAME_SIZE : DVD_SECTOR_SIZE;
buffer = new byte[length * (uint)longSize];
sectorStatus = new SectorStatus[length];
for(uint i = 0; i < length; i++)
for (uint i = 0; i < length; i++)
{
ErrorNumber errno = ReadSectorLong(sectorAddress + i, negative, out byte[] sector, out SectorStatus status);
if(errno != ErrorNumber.NoError) return errno;
if (errno != ErrorNumber.NoError) return errno;
Array.Copy(sector, 0, buffer, i * DVD_SECTOR_SIZE, DVD_SECTOR_SIZE);
Array.Copy(sector, 0, buffer, i * longSize, longSize);
sectorStatus[i] = status;
}
@@ -288,62 +183,14 @@ public sealed partial class Redumper
}
/// <inheritdoc />
public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag,
public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag,
out byte[] buffer)
{
buffer = null;
uint sectorOffset;
uint sectorSize;
if (_isBluRay) return ReadSectorsTagForBd(sectorAddress, negative, length, tag, out buffer);
switch(tag)
{
case SectorTagType.DvdSectorInformation:
sectorOffset = 0;
sectorSize = 1;
break;
case SectorTagType.DvdSectorNumber:
sectorOffset = 1;
sectorSize = 3;
break;
case SectorTagType.DvdSectorIed:
sectorOffset = 4;
sectorSize = 2;
break;
case SectorTagType.DvdSectorCmi:
sectorOffset = 6;
sectorSize = 1;
break;
case SectorTagType.DvdSectorTitleKey:
sectorOffset = 7;
sectorSize = 5;
break;
case SectorTagType.DvdSectorEdc:
sectorOffset = 2060;
sectorSize = 4;
break;
default:
return ErrorNumber.NotSupported;
}
buffer = new byte[sectorSize * length];
for(uint i = 0; i < length; i++)
{
ErrorNumber errno = ReadSectorLong(sectorAddress + i, negative, out byte[] sector, out _);
if(errno != ErrorNumber.NoError) return errno;
Array.Copy(sector, sectorOffset, buffer, i * sectorSize, sectorSize);
}
return ErrorNumber.NoError;
return ReadSectorsTagForDvd(sectorAddress, negative, length, tag, out buffer);
}
/// <inheritdoc />
@@ -351,17 +198,17 @@ public sealed partial class Redumper
ReadSector(sectorAddress, false, out buffer, out sectorStatus);
/// <inheritdoc />
public ErrorNumber ReadSectorLong(ulong sectorAddress, uint track, out byte[] buffer,
public ErrorNumber ReadSectorLong(ulong sectorAddress, uint track, out byte[] buffer,
out SectorStatus sectorStatus) =>
ReadSectorLong(sectorAddress, false, out buffer, out sectorStatus);
/// <inheritdoc />
public ErrorNumber ReadSectors(ulong sectorAddress, uint length, uint track, out byte[] buffer,
public ErrorNumber ReadSectors(ulong sectorAddress, uint length, uint track, out byte[] buffer,
out SectorStatus[] sectorStatus) =>
ReadSectors(sectorAddress, false, length, out buffer, out sectorStatus);
/// <inheritdoc />
public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, uint track, out byte[] buffer,
public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, uint track, out byte[] buffer,
out SectorStatus[] sectorStatus) =>
ReadSectorsLong(sectorAddress, false, length, out buffer, out sectorStatus);
@@ -370,7 +217,7 @@ public sealed partial class Redumper
ReadSectorTag(sectorAddress, false, tag, out buffer);
/// <inheritdoc />
public ErrorNumber ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag,
public ErrorNumber ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag,
out byte[] buffer) =>
ReadSectorsTag(sectorAddress, false, length, tag, out buffer);
@@ -380,32 +227,7 @@ public sealed partial class Redumper
/// <inheritdoc />
public List<Track> GetSessionTracks(ushort session) => Tracks;
#endregion
/// <summary>
/// Reads one RecordingFrame from the .sdram file and flattens it into a 2064-byte DVD sector
/// by extracting only the 172-byte main_data portion from each of the 12 rows (discarding PI/PO parity).
/// </summary>
byte[] ReadAndFlattenFrame(long frameIndex)
{
Stream stream = _sdramFilter.GetDataForkStream();
long offset = frameIndex * RECORDING_FRAME_SIZE;
if(offset + RECORDING_FRAME_SIZE > stream.Length) return null;
var frame = new byte[RECORDING_FRAME_SIZE];
stream.Seek(offset, SeekOrigin.Begin);
stream.EnsureRead(frame, 0, RECORDING_FRAME_SIZE);
// Flatten: copy 172 main-data bytes from each of the 12 rows into a contiguous 2064-byte buffer
var dvdSector = new byte[DVD_SECTOR_SIZE];
int rowStride = ROW_MAIN_DATA_SIZE + ROW_PARITY_INNER_SIZE;
for(int row = 0; row < RECORDING_FRAME_ROWS; row++)
Array.Copy(frame, row * rowStride, dvdSector, row * ROW_MAIN_DATA_SIZE, ROW_MAIN_DATA_SIZE);
return dvdSector;
}
#endregion
/// <summary>Maps a Redumper state byte to an Aaru SectorStatus.</summary>
static SectorStatus MapState(byte state) =>
@@ -416,59 +238,31 @@ public sealed partial class Redumper
_ => SectorStatus.Dumped // SUCCESS_C2_OFF (2), SUCCESS_SCSI_OFF (3), SUCCESS (4)
};
/// <summary>Loads Redumper sidecar files (.physical, .manufacturer, .bca) as media tags.</summary>
void LoadMediaTagSidecars(string basePath)
{
// PFI layer 0: prefer unindexed, fall back to .0.physical
LoadScsiSidecar(basePath, ".physical", ".0.physical", MediaTagType.DVD_PFI);
// PFI layer 1
LoadScsiSidecar(basePath, ".1.physical", null, MediaTagType.DVD_PFI_2ndLayer);
// DMI layer 0: prefer unindexed, fall back to .0.manufacturer
LoadScsiSidecar(basePath, ".manufacturer", ".0.manufacturer", MediaTagType.DVD_DMI);
// BCA (no SCSI header stripping — redumper writes raw BCA data)
string bcaPath = basePath + ".bca";
if(File.Exists(bcaPath))
{
byte[] bcaData = File.ReadAllBytes(bcaPath);
if(bcaData.Length > 0)
{
_mediaTags[MediaTagType.DVD_BCA] = bcaData;
AaruLogging.Debug(MODULE_NAME, Localization.Found_media_tag_0, MediaTagType.DVD_BCA);
}
}
}
/// <summary>
/// Loads a SCSI READ DVD STRUCTURE response sidecar, strips the 4-byte parameter list header,
/// and stores the 2048-byte payload as a media tag.
/// Loads a SCSI READ DISC/DVD STRUCTURE response sidecar, strips the 4-byte parameter list header,
/// and stores the payload as a media tag.
/// </summary>
void LoadScsiSidecar(string basePath, string primarySuffix, string fallbackSuffix, MediaTagType tag)
{
string path = basePath + primarySuffix;
if(!File.Exists(path))
if (!File.Exists(path))
{
if(fallbackSuffix is null) return;
if (fallbackSuffix is null) return;
path = basePath + fallbackSuffix;
if(!File.Exists(path)) return;
if (!File.Exists(path)) return;
}
byte[] data = File.ReadAllBytes(path);
if(data.Length <= SCSI_HEADER_SIZE) return;
if (data.Length <= SCSI_HEADER_SIZE) return;
// Strip the 4-byte SCSI parameter list header
byte[] payload = new byte[data.Length - SCSI_HEADER_SIZE];
Array.Copy(data, SCSI_HEADER_SIZE, payload, 0, payload.Length);
_mediaTags[tag] = payload;
AaruLogging.Debug(MODULE_NAME, Localization.Found_media_tag_0, tag);
}
}
}

View File

@@ -9,7 +9,7 @@
//
// --[ Description ] ----------------------------------------------------------
//
// Manages Redumper raw DVD dump images (.sdram + .state).
// Manages Redumper raw DVD (.sdram + .state) and Blu-ray (.sbram + .state) dumps.
//
// --[ License ] --------------------------------------------------------------
//
@@ -39,11 +39,10 @@ namespace Aaru.Images;
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IOpticalMediaImage" />
/// <summary>
/// Implements reading Redumper raw DVD dump images (.sdram with .state sidecar).
/// The .sdram file stores scrambled DVD RecordingFrames (2366 bytes each, including
/// inner and outer ReedSolomon parity). The .state file has one byte per frame
/// indicating dump status. The first frame in the file corresponds to physical
/// sector number LBA_START (-0x30000 / -196608).
/// Implements reading Redumper raw DVD (.sdram + .state) and Blu-ray (.sbram + .state) dumps.
/// DVD .sdram stores scrambled RecordingFrames (2366 bytes each). BD .sbram stores
/// scrambled DataFrames (2052 bytes: 2048 user + 4 EDC). The .state file has one byte
/// per frame. DVD LBA at file offset 0 is -0x30000; BD LBA at offset 0 is -0x100000.
/// </summary>
public sealed partial class Redumper : IOpticalMediaImage
{
@@ -55,8 +54,8 @@ public sealed partial class Redumper : IOpticalMediaImage
/// <summary>Size of a DVD sector without parity (ID + CPR_MAI + user data + EDC).</summary>
const int DVD_SECTOR_SIZE = 2064;
/// <summary>DVD user data size.</summary>
const int DVD_USER_DATA_SIZE = 2048;
/// <summary>DVD/BD user data size.</summary>
const int USER_DATA_SIZE = 2048;
/// <summary>Number of main-data bytes per row in a RecordingFrame.</summary>
const int ROW_MAIN_DATA_SIZE = 172;
@@ -70,15 +69,27 @@ public sealed partial class Redumper : IOpticalMediaImage
/// <summary>Size of the outer parity block.</summary>
const int PARITY_OUTER_SIZE = 182;
/// <summary>
/// First physical LBA stored at file offset 0 in the .sdram/.state files.
/// DVD user-data LBA 0 starts at file index -LBA_START (196608).
/// </summary>
const int LBA_START = -0x30000;
/// <summary>First physical LBA at file offset 0 for DVD .sdram/.state.</summary>
const int DVD_LBA_START = -0x30000;
/// <summary>First physical LBA at file offset 0 for BD .sbram/.state.</summary>
const int BD_LBA_START = -0x100000;
/// <summary>One BD DataFrame in .sbram: main_data + EDC.</summary>
const int BD_DATA_FRAME_SIZE = Decoders.Bluray.DataFrame.Size;
/// <summary>SCSI READ DVD STRUCTURE parameter list header size (4 bytes).</summary>
const int SCSI_HEADER_SIZE = 4;
/// <summary>Active LBA at index 0; <see cref="DVD_LBA_START" /> or <see cref="BD_LBA_START" />.</summary>
int _lbaStart;
/// <summary>True when the data file is .sbram (Blu-ray DataFrame) instead of .sdram.</summary>
bool _isBluRay;
/// <summary>Wii Ustyle BD: physical structure payload all zeros (see redumper bd_extract_iso).</summary>
bool _bdNintendo;
readonly Decoders.DVD.Sector _decoding = new();
readonly Decoders.Nintendo.Sector _nintendoDecoder = new();
@@ -90,7 +101,8 @@ public sealed partial class Redumper : IOpticalMediaImage
ImageInfo _imageInfo;
Dictionary<MediaTagType, byte[]> _mediaTags;
byte[] _stateData;
IFilter _sdramFilter;
IFilter _ramFilter;
string _ramPath;
long _totalFrames;
public Redumper() => _imageInfo = new ImageInfo

View File

@@ -9,7 +9,7 @@
//
// --[ Description ] ----------------------------------------------------------
//
// Verifies Redumper raw DVD dump images.
// Verifies Redumper raw DVD and Blu-ray dump images.
//
// --[ License ] --------------------------------------------------------------
//
@@ -31,8 +31,10 @@
// ****************************************************************************/
using System.Collections.Generic;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.Decoders.DVD;
using Aaru.Decoders.Bluray;
using DvdSector = Aaru.Decoders.DVD.Sector;
namespace Aaru.Images;
@@ -43,19 +45,42 @@ public sealed partial class Redumper
/// <inheritdoc />
public bool? VerifySector(ulong sectorAddress)
{
long frameIndex = (long)sectorAddress - LBA_START;
long frameIndex = (long)sectorAddress - _lbaStart;
if(frameIndex < 0 || frameIndex >= _totalFrames) return null;
if(MapState(_stateData[frameIndex]) != SectorStatus.Dumped) return null;
ErrorNumber errno = ReadSectorLong(sectorAddress, false, out byte[] dvdSector, out _);
if(_isBluRay)
{
ErrorNumber errno = ReadBdFrameRawForVerify(sectorAddress, out byte[] raw);
if(errno != ErrorNumber.NoError || dvdSector is null) return null;
if(errno != ErrorNumber.NoError || raw is null) return null;
if(!Sector.CheckIed(dvdSector)) return false;
return DataFrame.IsValid(raw, (int)sectorAddress, _bdNintendo);
}
return Sector.CheckEdc(dvdSector, 1);
ErrorNumber err = ReadSectorLong(sectorAddress, false, out byte[] dvdSector, out _);
if(err != ErrorNumber.NoError || dvdSector is null) return null;
if(!DvdSector.CheckIed(dvdSector)) return false;
return DvdSector.CheckEdc(dvdSector, 1);
}
/// <summary>Reads scrambled BD frame without going through ReadSectorLong (avoids double descramble).</summary>
ErrorNumber ReadBdFrameRawForVerify(ulong sectorAddress, out byte[] raw)
{
raw = null;
long frameIndex = (long)sectorAddress - _lbaStart;
if(frameIndex < 0 || frameIndex >= _totalFrames) return ErrorNumber.OutOfRange;
raw = ReadBdFrame(frameIndex);
return raw is null ? ErrorNumber.InvalidArgument : ErrorNumber.NoError;
}
/// <inheritdoc />
@@ -93,4 +118,4 @@ public sealed partial class Redumper
VerifySectors(sectorAddress, length, out failingLbas, out unknownLbas);
#endregion
}
}