From 191b5773aed083f586e24a72bcc35b1b236f0ab7 Mon Sep 17 00:00:00 2001 From: Rebecca Wallander Date: Sun, 5 Apr 2026 17:59:24 +0200 Subject: [PATCH] BD support for Redumper image --- Aaru.Core/Image/Convert/Optical.cs | 1 + Aaru.Core/Image/Merge/Optical.cs | 2 + Aaru.Decoders/Bluray/DataFrame.cs | 95 ++++++++ Aaru.Decoders/Bluray/Sector.cs | 80 +++++++ Aaru.Images/Redumper/Bluray.cs | 260 ++++++++++++++++++++++ Aaru.Images/Redumper/Dvd.cs | 309 ++++++++++++++++++++++++++ Aaru.Images/Redumper/Identify.cs | 28 +-- Aaru.Images/Redumper/Ngcw.cs | 4 +- Aaru.Images/Redumper/Properties.cs | 10 +- Aaru.Images/Redumper/Read.cs | 340 ++++++----------------------- Aaru.Images/Redumper/Redumper.cs | 40 ++-- Aaru.Images/Redumper/Verify.cs | 41 +++- 12 files changed, 897 insertions(+), 313 deletions(-) create mode 100644 Aaru.Decoders/Bluray/DataFrame.cs create mode 100644 Aaru.Decoders/Bluray/Sector.cs create mode 100644 Aaru.Images/Redumper/Bluray.cs create mode 100644 Aaru.Images/Redumper/Dvd.cs diff --git a/Aaru.Core/Image/Convert/Optical.cs b/Aaru.Core/Image/Convert/Optical.cs index 802461249..f2cecb964 100644 --- a/Aaru.Core/Image/Convert/Optical.cs +++ b/Aaru.Core/Image/Convert/Optical.cs @@ -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; } diff --git a/Aaru.Core/Image/Merge/Optical.cs b/Aaru.Core/Image/Merge/Optical.cs index 86c368b4e..4895bcf9e 100644 --- a/Aaru.Core/Image/Merge/Optical.cs +++ b/Aaru.Core/Image/Merge/Optical.cs @@ -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; } diff --git a/Aaru.Decoders/Bluray/DataFrame.cs b/Aaru.Decoders/Bluray/DataFrame.cs new file mode 100644 index 000000000..f3e22304e --- /dev/null +++ b/Aaru.Decoders/Bluray/DataFrame.cs @@ -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; + +/// Blu-ray DataFrame: 2048 bytes main_data + 4 bytes EDC (redumper raw .sbram). +public static class DataFrame +{ + /// Total size of one BD DataFrame in a Redumper .sbram file. + public const int Size = 2048 + 4; + + /// Descrambles a 2052-byte frame in place (ISO/IEC 30190 LFSR XOR). + public static void Descramble(Span frame, int lba, bool nintendo) + { + if(frame.Length != Size) throw new ArgumentException(nameof(frame)); + + ushort seed = nintendo ? SeedNintendo(lba) : SeedBluray(lba); + Process(frame, seed); + } + + /// Copies , descrambles the copy, returns whether EDC matches. + public static bool IsValid(ReadOnlySpan 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 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); + } + } + } +} diff --git a/Aaru.Decoders/Bluray/Sector.cs b/Aaru.Decoders/Bluray/Sector.cs new file mode 100644 index 000000000..a518e7c9a --- /dev/null +++ b/Aaru.Decoders/Bluray/Sector.cs @@ -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 src, int size) + { + var pos = 0; + + for(; size > 0; size--) edc = _edcTable[(edc >> 24 ^ src[pos++]) & 0xFF] ^ edc << 8; + + return edc; + } + + /// + /// 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). + /// + /// The descrambled Blu-ray DataFrame + /// True if EDC is correct, False if not + public static bool CheckEdc(byte[] dataFrameDescrambled) + { + if(dataFrameDescrambled is null || dataFrameDescrambled.Length < 2052) return false; + + return ComputeEdc(0, dataFrameDescrambled, 2048) == BigEndianBitConverter.ToUInt32(dataFrameDescrambled, 2048); + } + + /// + /// 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). + /// + /// + /// + /// True if EDC is correct, False if not + 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; + } +} \ No newline at end of file diff --git a/Aaru.Images/Redumper/Bluray.cs b/Aaru.Images/Redumper/Bluray.cs new file mode 100644 index 000000000..1c1cee6a8 --- /dev/null +++ b/Aaru.Images/Redumper/Bluray.cs @@ -0,0 +1,260 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Bluray.cs +// Author(s) : Rebecca Wallander +// +// 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 . +// +// ---------------------------------------------------------------------------- +// 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(); + 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; + } + + + /// Reads one 2052-byte BD DataFrame from .sbram. + 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; + } + + + /// Loads BD sidecars: BCA (SCSI strip, same as device dump), Nintendo heuristic from .physical. + void LoadBdMediaTagSidecars(string basePath) + { + TryBdNintendoFromPhysicalSidecar(basePath); + + LoadScsiSidecar(basePath, ".bca", null, MediaTagType.BD_BCA); + } + + /// + /// 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). + /// + 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; + } + } +} \ No newline at end of file diff --git a/Aaru.Images/Redumper/Dvd.cs b/Aaru.Images/Redumper/Dvd.cs new file mode 100644 index 000000000..05667cc65 --- /dev/null +++ b/Aaru.Images/Redumper/Dvd.cs @@ -0,0 +1,309 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Dvd.cs +// Author(s) : Rebecca Wallander +// +// 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 . +// +// ---------------------------------------------------------------------------- +// 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(); + 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; + } + + /// + /// 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). + /// + 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; + } + + /// Loads DVD sidecar files (.physical, .manufacturer, .bca). + 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); + } + } + } + +} \ No newline at end of file diff --git a/Aaru.Images/Redumper/Identify.cs b/Aaru.Images/Redumper/Identify.cs index 8532661b9..557a8d402 100644 --- a/Aaru.Images/Redumper/Identify.cs +++ b/Aaru.Images/Redumper/Identify.cs @@ -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 /// 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 -} \ No newline at end of file +} diff --git a/Aaru.Images/Redumper/Ngcw.cs b/Aaru.Images/Redumper/Ngcw.cs index b398aef1e..9bfe9bfad 100644 --- a/Aaru.Images/Redumper/Ngcw.cs +++ b/Aaru.Images/Redumper/Ngcw.cs @@ -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; diff --git a/Aaru.Images/Redumper/Properties.cs b/Aaru.Images/Redumper/Properties.cs index 5e926f3bc..ccaff56af 100644 --- a/Aaru.Images/Redumper/Properties.cs +++ b/Aaru.Images/Redumper/Properties.cs @@ -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 ]; /// @@ -94,7 +95,8 @@ public sealed partial class Redumper SectorTagType.DvdSectorIed, SectorTagType.DvdSectorCmi, SectorTagType.DvdSectorTitleKey, - SectorTagType.DvdSectorEdc + SectorTagType.DvdSectorEdc, + SectorTagType.BluRaySectorEdc ]; /// @@ -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, ]; /// diff --git a/Aaru.Images/Redumper/Read.cs b/Aaru.Images/Redumper/Read.cs index d44736e5f..711e62a7c 100644 --- a/Aaru.Images/Redumper/Read.cs +++ b/Aaru.Images/Redumper/Read.cs @@ -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 /// 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(); - 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; } + /// 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 /// 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); } /// - 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 } /// - 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); + } /// - 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 } /// - 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); } /// @@ -351,17 +198,17 @@ public sealed partial class Redumper ReadSector(sectorAddress, false, out buffer, out sectorStatus); /// - 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); /// - 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); /// - 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); /// - 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 /// public List GetSessionTracks(ushort session) => Tracks; -#endregion - - /// - /// 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). - /// - 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 /// Maps a Redumper state byte to an Aaru SectorStatus. 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) }; - /// Loads Redumper sidecar files (.physical, .manufacturer, .bca) as media tags. - 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); - } - } - } - /// - /// 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. /// 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); } -} \ No newline at end of file +} diff --git a/Aaru.Images/Redumper/Redumper.cs b/Aaru.Images/Redumper/Redumper.cs index a03d76511..c607e51df 100644 --- a/Aaru.Images/Redumper/Redumper.cs +++ b/Aaru.Images/Redumper/Redumper.cs @@ -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; /// /// -/// 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 Reed–Solomon 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. /// public sealed partial class Redumper : IOpticalMediaImage { @@ -55,8 +54,8 @@ public sealed partial class Redumper : IOpticalMediaImage /// Size of a DVD sector without parity (ID + CPR_MAI + user data + EDC). const int DVD_SECTOR_SIZE = 2064; - /// DVD user data size. - const int DVD_USER_DATA_SIZE = 2048; + /// DVD/BD user data size. + const int USER_DATA_SIZE = 2048; /// Number of main-data bytes per row in a RecordingFrame. const int ROW_MAIN_DATA_SIZE = 172; @@ -70,15 +69,27 @@ public sealed partial class Redumper : IOpticalMediaImage /// Size of the outer parity block. const int PARITY_OUTER_SIZE = 182; - /// - /// 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). - /// - const int LBA_START = -0x30000; + /// First physical LBA at file offset 0 for DVD .sdram/.state. + const int DVD_LBA_START = -0x30000; + + /// First physical LBA at file offset 0 for BD .sbram/.state. + const int BD_LBA_START = -0x100000; + + /// One BD DataFrame in .sbram: main_data + EDC. + const int BD_DATA_FRAME_SIZE = Decoders.Bluray.DataFrame.Size; /// SCSI READ DVD STRUCTURE parameter list header size (4 bytes). const int SCSI_HEADER_SIZE = 4; + /// Active LBA at index 0; or . + int _lbaStart; + + /// True when the data file is .sbram (Blu-ray DataFrame) instead of .sdram. + bool _isBluRay; + + /// Wii U–style BD: physical structure payload all zeros (see redumper bd_extract_iso). + 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 _mediaTags; byte[] _stateData; - IFilter _sdramFilter; + IFilter _ramFilter; + string _ramPath; long _totalFrames; public Redumper() => _imageInfo = new ImageInfo diff --git a/Aaru.Images/Redumper/Verify.cs b/Aaru.Images/Redumper/Verify.cs index ae6a6c788..43f181371 100644 --- a/Aaru.Images/Redumper/Verify.cs +++ b/Aaru.Images/Redumper/Verify.cs @@ -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 /// 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); + } + + /// Reads scrambled BD frame without going through ReadSectorLong (avoids double descramble). + 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; } /// @@ -93,4 +118,4 @@ public sealed partial class Redumper VerifySectors(sectorAddress, length, out failingLbas, out unknownLbas); #endregion -} \ No newline at end of file +}