// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : XGD.cs // Author(s) : Natalia Portillo // // Component : Core algorithms. // // --[ Description ] ---------------------------------------------------------- // // Dumps Xbox Game Discs. // // --[ License ] -------------------------------------------------------------- // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2025 Natalia Portillo // ****************************************************************************/ // ReSharper disable JoinDeclarationAndInitializer using System; using System.Collections.Generic; using System.Linq; using Aaru.CommonTypes; using Aaru.CommonTypes.AaruMetadata; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Extents; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Interop; using Aaru.CommonTypes.Structs.Devices.SCSI; using Aaru.Core.Graphics; using Aaru.Core.Logging; using Aaru.Decoders.DVD; using Aaru.Decoders.SCSI; using Aaru.Decoders.Xbox; using Aaru.Devices; using Aaru.Logging; using Humanizer; using Humanizer.Bytes; using Humanizer.Localisation; using Device = Aaru.Devices.Remote.Device; using Layers = Aaru.CommonTypes.AaruMetadata.Layers; using PlatformID = Aaru.CommonTypes.Interop.PlatformID; using Track = Aaru.CommonTypes.Structs.Track; using TrackType = Aaru.CommonTypes.Enums.TrackType; using Version = Aaru.CommonTypes.Interop.Version; namespace Aaru.Core.Devices.Dumping; /// Implements dumping an Xbox Game Disc using a Kreon drive partial class Dump { /// Dumps an Xbox Game Disc using a Kreon drive /// Media tags as retrieved in MMC layer /// Disc type as detected in MMC layer void Xgd(Dictionary mediaTags, MediaType dskType) { bool sense; const uint blockSize = 2048; uint blocksToRead = 64; double totalDuration = 0; double currentSpeed = 0; double maxSpeed = double.MinValue; double minSpeed = double.MaxValue; if(_outputPlugin is not IWritableImage outputFormat) { StoppingErrorMessage?.Invoke(Localization.Core.Image_is_not_writable_aborting); return; } if(DetectOS.GetRealPlatformID() != PlatformID.Win32NT) { bool isAdmin = _dev is Device remoteDev ? remoteDev.IsAdmin : DetectOS.IsAdmin; if(!isAdmin) { AaruLogging.Error(Localization.Core .Because_of_commands_sent_dumping_XGD_must_be_administrative_Cannot_continue); _dumpLog.WriteLine(Localization.Core.Cannot_dump_XGD_without_administrative_privileges); return; } } if(mediaTags.ContainsKey(MediaTagType.DVD_PFI)) mediaTags.Remove(MediaTagType.DVD_PFI); if(mediaTags.ContainsKey(MediaTagType.DVD_DMI)) mediaTags.Remove(MediaTagType.DVD_DMI); // Drive shall move to lock state when a new disc is inserted. Old kreon versions do not lock correctly so save this sense = _dev.ReadCapacity(out byte[] coldReadCapacity, out byte[] senseBuf, _dev.Timeout, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_disc_capacity); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_disc_capacity); return; } // Drive shall move to lock state when a new disc is inserted. Old kreon versions do not lock correctly so save this sense = _dev.ReadDiscStructure(out byte[] coldPfi, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.PhysicalInformation, 0, 0, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_PFI); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_PFI); return; } UpdateStatus?.Invoke(Localization.Core.Reading_Xbox_Security_Sector); _dumpLog.WriteLine(Localization.Core.Reading_Xbox_Security_Sector); sense = _dev.KreonExtractSs(out byte[] ssBuf, out senseBuf, _dev.Timeout, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_Xbox_Security_Sector_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_Xbox_Security_Sector_not_continuing); return; } _dumpLog.WriteLine(Localization.Core.Decoding_Xbox_Security_Sector); UpdateStatus?.Invoke(Localization.Core.Decoding_Xbox_Security_Sector); SS.SecuritySector? xboxSs = SS.Decode(ssBuf); if(!xboxSs.HasValue) { _dumpLog.WriteLine(Localization.Core.Cannot_decode_Xbox_Security_Sector_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_decode_Xbox_Security_Sector_not_continuing); return; } byte[] tmpBuf = new byte[ssBuf.Length - 4]; Array.Copy(ssBuf, 4, tmpBuf, 0, ssBuf.Length - 4); mediaTags.Add(MediaTagType.Xbox_SecuritySector, tmpBuf); // Get video partition size AaruLogging.Debug(MODULE_NAME, Localization.Core.Getting_video_partition_size); UpdateStatus?.Invoke(Localization.Core.Locking_drive); _dumpLog.WriteLine(Localization.Core.Locking_drive); sense = _dev.KreonLock(out senseBuf, _dev.Timeout, out _); if(sense) { _errorLog?.WriteLine("Kreon lock", _dev.Error, _dev.LastError, senseBuf); _dumpLog.WriteLine(Localization.Core.Cannot_lock_drive_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_lock_drive_not_continuing); return; } UpdateStatus?.Invoke(Localization.Core.Getting_video_partition_size); _dumpLog.WriteLine(Localization.Core.Getting_video_partition_size); sense = _dev.ReadCapacity(out byte[] readBuffer, out senseBuf, _dev.Timeout, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_disc_capacity); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_disc_capacity); return; } ulong totalSize = (ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]) & 0xFFFFFFFF; UpdateStatus?.Invoke(Localization.Core.Reading_Physical_Format_Information); _dumpLog.WriteLine(Localization.Core.Reading_Physical_Format_Information); sense = _dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.PhysicalInformation, 0, 0, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_PFI); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_PFI); return; } tmpBuf = new byte[readBuffer.Length - 4]; Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4); mediaTags.Add(MediaTagType.DVD_PFI, tmpBuf); AaruLogging.Debug(MODULE_NAME, Localization.Core.Video_partition_total_size_0_sectors, totalSize); ulong l0Video = (PFI.Decode(readBuffer, MediaType.DVDROM)?.Layer0EndPSN ?? 0) - (PFI.Decode(readBuffer, MediaType.DVDROM)?.DataAreaStartPSN ?? 0) + 1; ulong l1Video = totalSize - l0Video + 1; UpdateStatus?.Invoke(Localization.Core.Reading_Disc_Manufacturing_Information); _dumpLog.WriteLine(Localization.Core.Reading_Disc_Manufacturing_Information); sense = _dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.DiscManufacturingInformation, 0, 0, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_DMI); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_DMI); return; } tmpBuf = new byte[readBuffer.Length - 4]; Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4); mediaTags.Add(MediaTagType.DVD_DMI, tmpBuf); // Should be a safe value to detect the lock command was ignored, and we're indeed getting the whole size and not the locked one if(totalSize > 300000) { UpdateStatus?.Invoke(Localization.Core.Video_partition_is_too_big_did_lock_work_Trying_cold_values); _dumpLog.WriteLine(Localization.Core.Video_partition_is_too_big_did_lock_work_Trying_cold_values); totalSize = (ulong)((coldReadCapacity[0] << 24) + (coldReadCapacity[1] << 16) + (coldReadCapacity[2] << 8) + coldReadCapacity[3]) & 0xFFFFFFFF; tmpBuf = new byte[coldPfi.Length - 4]; Array.Copy(coldPfi, 4, tmpBuf, 0, coldPfi.Length - 4); mediaTags.Remove(MediaTagType.DVD_PFI); mediaTags.Add(MediaTagType.DVD_PFI, tmpBuf); AaruLogging.Debug(MODULE_NAME, Localization.Core.Video_partition_total_size_0_sectors, totalSize); l0Video = (PFI.Decode(coldPfi, MediaType.DVDROM)?.Layer0EndPSN ?? 0) - (PFI.Decode(coldPfi, MediaType.DVDROM)?.DataAreaStartPSN ?? 0) + 1; l1Video = totalSize - l0Video + 1; if(totalSize > 300000) { _dumpLog.WriteLine(Localization.Core.Cannot_get_video_partition_size_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_video_partition_size_not_continuing); return; } } // Get game partition size AaruLogging.Debug(MODULE_NAME, Localization.Core.Getting_game_partition_size); UpdateStatus?.Invoke(Localization.Core.Unlocking_drive_Xtreme_); _dumpLog.WriteLine(Localization.Core.Unlocking_drive_Xtreme_); sense = _dev.KreonUnlockXtreme(out senseBuf, _dev.Timeout, out _); if(sense) { _errorLog?.WriteLine("Kreon Xtreme unlock", _dev.Error, _dev.LastError, senseBuf); _dumpLog.WriteLine(Localization.Core.Cannot_unlock_drive_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_unlock_drive_not_continuing); return; } UpdateStatus?.Invoke(Localization.Core.Getting_game_partition_size); _dumpLog.WriteLine(Localization.Core.Getting_game_partition_size); sense = _dev.ReadCapacity(out readBuffer, out senseBuf, _dev.Timeout, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_disc_capacity); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_disc_capacity); return; } ulong gameSize = ((ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]) & 0xFFFFFFFF) + 1; AaruLogging.Debug(MODULE_NAME, Localization.Core.Game_partition_total_size_0_sectors, gameSize); // Get middle zone size AaruLogging.Debug(MODULE_NAME, Localization.Core.Getting_middle_zone_size); UpdateStatus?.Invoke(Localization.Core.Unlocking_drive_Wxripper); _dumpLog.WriteLine(Localization.Core.Unlocking_drive_Wxripper); sense = _dev.KreonUnlockWxripper(out senseBuf, _dev.Timeout, out _); if(sense) { _errorLog?.WriteLine("Kreon Wxripper unlock", _dev.Error, _dev.LastError, senseBuf); _dumpLog.WriteLine(Localization.Core.Cannot_unlock_drive_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_unlock_drive_not_continuing); return; } UpdateStatus?.Invoke(Localization.Core.Getting_disc_size); _dumpLog.WriteLine(Localization.Core.Getting_disc_size); sense = _dev.ReadCapacity(out readBuffer, out senseBuf, _dev.Timeout, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_disc_capacity); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_disc_capacity); return; } totalSize = (ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]) & 0xFFFFFFFF; UpdateStatus?.Invoke(Localization.Core.Reading_Physical_Format_Information); _dumpLog.WriteLine(Localization.Core.Reading_Physical_Format_Information); sense = _dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.PhysicalInformation, 0, 0, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_PFI); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_PFI); return; } AaruLogging.Debug(MODULE_NAME, Localization.Core.Unlocked_total_size_0_sectors, totalSize); ulong blocks = totalSize + 1; PFI.PhysicalFormatInformation? wxRipperPfiNullable = PFI.Decode(readBuffer, MediaType.DVDROM); if(wxRipperPfiNullable == null) { _dumpLog.WriteLine(Localization.Core.Cannot_decode_PFI); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_decode_PFI); return; } // Guaranteed to never fall into default PFI.PhysicalFormatInformation wxRipperPfi = wxRipperPfiNullable ?? default(PFI.PhysicalFormatInformation); UpdateStatus?.Invoke(string.Format(Localization.Core.WxRipper_PFI_Data_Area_Start_PSN_0_sectors, wxRipperPfi.DataAreaStartPSN)); UpdateStatus?.Invoke(string.Format(Localization.Core.WxRipper_PFI_Layer_0_End_PSN_0_sectors, wxRipperPfi.Layer0EndPSN)); _dumpLog.WriteLine(string.Format(Localization.Core.WxRipper_PFI_Data_Area_Start_PSN_0_sectors, wxRipperPfi.DataAreaStartPSN)); _dumpLog.WriteLine(string.Format(Localization.Core.WxRipper_PFI_Layer_0_End_PSN_0_sectors, wxRipperPfi.Layer0EndPSN)); ulong middleZone = totalSize - (wxRipperPfi.Layer0EndPSN - wxRipperPfi.DataAreaStartPSN + 1) - gameSize + 1; tmpBuf = new byte[readBuffer.Length - 4]; Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4); mediaTags.Add(MediaTagType.Xbox_PFI, tmpBuf); UpdateStatus?.Invoke(Localization.Core.Reading_Disc_Manufacturing_Information); _dumpLog.WriteLine(Localization.Core.Reading_Disc_Manufacturing_Information); sense = _dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.DiscManufacturingInformation, 0, 0, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_get_DMI); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_DMI); return; } tmpBuf = new byte[readBuffer.Length - 4]; Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4); mediaTags.Add(MediaTagType.Xbox_DMI, tmpBuf); totalSize = l0Video + l1Video + middleZone * 2 + gameSize; ulong layerBreak = l0Video + middleZone + gameSize / 2; UpdateStatus?.Invoke(string.Format(Localization.Core.Video_layer_0_size_0_sectors, l0Video)); UpdateStatus?.Invoke(string.Format(Localization.Core.Video_layer_1_size_0_sectors, l1Video)); UpdateStatus?.Invoke(string.Format(Localization.Core.Middle_zone_size_0_sectors, middleZone)); UpdateStatus?.Invoke(string.Format(Localization.Core.Game_data_size_0_sectors, gameSize)); UpdateStatus?.Invoke(string.Format(Localization.Core.Total_size_0_sectors, totalSize)); UpdateStatus?.Invoke(string.Format(Localization.Core.Real_layer_break_0, layerBreak)); UpdateStatus?.Invoke(""); _dumpLog.WriteLine(Localization.Core.Video_layer_0_size_0_sectors, l0Video); _dumpLog.WriteLine(Localization.Core.Video_layer_1_size_0_sectors, l1Video); _dumpLog.WriteLine(Localization.Core.Middle_zone_size_0_sectors, middleZone); _dumpLog.WriteLine(Localization.Core.Game_data_size_0_sectors, gameSize); _dumpLog.WriteLine(Localization.Core.Total_size_0_sectors, totalSize); _dumpLog.WriteLine(Localization.Core.Real_layer_break_0, layerBreak); bool read12 = !_dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, 0, blockSize, 0, 1, false, _dev.Timeout, out _); if(!read12) { _dumpLog.WriteLine(Localization.Core.Cannot_read_medium_aborting_scan); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_read_medium_aborting_scan); return; } _dumpLog.WriteLine(Localization.Core.Using_SCSI_READ_12_command); UpdateStatus?.Invoke(Localization.Core.Using_SCSI_READ_12_command); // Set speed if(_speedMultiplier >= 0) { if(_speed == 0) { _dumpLog.WriteLine(Localization.Core.Setting_speed_to_MAX); UpdateStatus?.Invoke(Localization.Core.Setting_speed_to_MAX); } else { _dumpLog.WriteLine(string.Format(Localization.Core.Setting_speed_to_0_x, _speed)); UpdateStatus?.Invoke(string.Format(Localization.Core.Setting_speed_to_0_x, _speed)); } _speed *= _speedMultiplier; if(_speed is 0 or > 0xFFFF) _speed = 0xFFFF; _dev.SetCdSpeed(out _, RotationalControl.ClvAndImpureCav, (ushort)_speed, 0, _dev.Timeout, out _); } while(true) { sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, 0, blockSize, 0, blocksToRead, false, _dev.Timeout, out _); if(sense || _dev.Error) blocksToRead /= 2; if(!_dev.Error || blocksToRead == 1) break; } if(_dev.Error) { _dumpLog.WriteLine(Localization.Core.Device_error_0_trying_to_guess_ideal_transfer_length, _dev.LastError); StoppingErrorMessage?.Invoke(string.Format(Localization.Core .Device_error_0_trying_to_guess_ideal_transfer_length, _dev.LastError)); return; } if(_skip < blocksToRead) _skip = blocksToRead; bool ret = true; foreach(MediaTagType tag in mediaTags.Keys.Where(tag => !outputFormat.SupportedMediaTags.Contains(tag))) { ret = false; _dumpLog.WriteLine(string.Format(Localization.Core.Output_format_does_not_support_0, tag)); ErrorMessage?.Invoke(string.Format(Localization.Core.Output_format_does_not_support_0, tag)); } if(!ret) { if(_force) { _dumpLog.WriteLine(Localization.Core.Several_media_tags_not_supported_continuing); ErrorMessage?.Invoke(Localization.Core.Several_media_tags_not_supported_continuing); } else { _dumpLog.WriteLine(Localization.Core.Several_media_tags_not_supported_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Several_media_tags_not_supported_not_continuing); return; } } _dumpLog.WriteLine(Localization.Core.Reading_0_sectors_at_a_time, blocksToRead); UpdateStatus?.Invoke(string.Format(Localization.Core.Reading_0_sectors_at_a_time, blocksToRead)); var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, blockSize, blocksToRead, _private, _dimensions); var ibgLog = new IbgLog(_outputPrefix + ".ibg", 0x0010); ret = outputFormat.Create(_outputPath, dskType, _formatOptions, blocks, blockSize); // Cannot create image if(!ret) { _dumpLog.WriteLine(Localization.Core.Error_creating_output_image_not_continuing); _dumpLog.WriteLine(outputFormat.ErrorMessage); StoppingErrorMessage?.Invoke(Localization.Core.Error_creating_output_image_not_continuing + Environment.NewLine + outputFormat.ErrorMessage); return; } _dumpStopwatch.Restart(); double imageWriteDuration = 0; double cmdDuration = 0; uint saveBlocksToRead = blocksToRead; DumpHardware currentTry = null; ExtentsULong extents = null; ResumeSupport.Process(true, true, totalSize, _dev.Manufacturer, _dev.Model, _dev.Serial, _dev.PlatformId, ref _resume, ref currentTry, ref extents, _dev.FirmwareRevision, _private, _force); if(currentTry == null || extents == null) StoppingErrorMessage?.Invoke(Localization.Core.Could_not_process_resume_file_not_continuing); if(_createGraph) { Spiral.DiscParameters discSpiralParameters = Spiral.DiscParametersFromMediaType(dskType); if(discSpiralParameters is not null) _mediaGraph = new Spiral((int)_dimensions, (int)_dimensions, discSpiralParameters, blocks); else _mediaGraph = new BlockMap((int)_dimensions, (int)_dimensions, blocks); if(_mediaGraph is not null) { foreach(Tuple e in extents.ToArray()) _mediaGraph?.PaintSectorsGood(e.Item1, (uint)(e.Item2 - e.Item1 + 2)); } _mediaGraph?.PaintSectorsBad(_resume.BadBlocks); } (outputFormat as IWritableOpticalImage).SetTracks([ new Track { BytesPerSector = (int)blockSize, EndSector = blocks - 1, Sequence = 1, RawBytesPerSector = (int)blockSize, SubchannelType = TrackSubchannelType.None, Session = 1, Type = TrackType.Data } ]); ulong currentSector = _resume.NextBlock; if(_resume.NextBlock > 0) { UpdateStatus?.Invoke(string.Format(Localization.Core.Resuming_from_block_0, _resume.NextBlock)); _dumpLog.WriteLine(Localization.Core.Resuming_from_block_0, _resume.NextBlock); } bool newTrim = false; _dumpLog.WriteLine(Localization.Core.Reading_game_partition); UpdateStatus?.Invoke(Localization.Core.Reading_game_partition); _speedStopwatch.Reset(); ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for(int e = 0; e <= 16; e++) { if(_aborted) { _resume.NextBlock = currentSector; currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke(Localization.Core.Aborted); _dumpLog.WriteLine(Localization.Core.Aborted); break; } if(currentSector >= blocks) break; ulong extentStart, extentEnd; // Extents if(e < 16) { if(xboxSs.Value.Extents[e].StartPSN <= xboxSs.Value.Layer0EndPSN) extentStart = xboxSs.Value.Extents[e].StartPSN - 0x30000; else { extentStart = (xboxSs.Value.Layer0EndPSN + 1) * 2 - ((xboxSs.Value.Extents[e].StartPSN ^ 0xFFFFFF) + 1) - 0x30000; } if(xboxSs.Value.Extents[e].EndPSN <= xboxSs.Value.Layer0EndPSN) extentEnd = xboxSs.Value.Extents[e].EndPSN - 0x30000; else { extentEnd = (xboxSs.Value.Layer0EndPSN + 1) * 2 - ((xboxSs.Value.Extents[e].EndPSN ^ 0xFFFFFF) + 1) - 0x30000; } } // After last extent else { extentStart = blocks; extentEnd = blocks; } if(currentSector > extentEnd) continue; for(ulong i = currentSector; i < extentStart; i += blocksToRead) { saveBlocksToRead = blocksToRead; if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke(Localization.Core.Aborted); _dumpLog.WriteLine(Localization.Core.Aborted); break; } if(extentStart - i < blocksToRead) blocksToRead = (uint)(extentStart - i); if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed; if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed; UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2, i, blocks, ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()), (long)i, (long)totalSize); _speedStopwatch.Start(); sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)i, blockSize, 0, blocksToRead, false, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); totalDuration += cmdDuration; if(!sense && !_dev.Error) { mhddLog.Write(i, cmdDuration, blocksToRead); ibgLog.Write(i, currentSpeed * 1024); _writeStopwatch.Restart(); outputFormat.WriteSectors(readBuffer, i, blocksToRead); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; extents.Add(i, blocksToRead, true); _mediaGraph?.PaintSectorsGood(i, blocksToRead); } else { _errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf); // TODO: Reset device after X errors if(_stopOnError) return; // TODO: Return more cleanly if(i + _skip > blocks) _skip = (uint)(blocks - i); // Write empty data _writeStopwatch.Restart(); outputFormat.WriteSectors(new byte[blockSize * _skip], i, _skip); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b); AaruLogging.Debug(MODULE_NAME, Localization.Core.READ_error_0, Sense.PrettifySense(senseBuf)); mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip); ibgLog.Write(i, 0); _dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, i); i += _skip - blocksToRead; string[] senseLines = Sense.PrettifySense(senseBuf) .Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries); foreach(string senseLine in senseLines) _dumpLog.WriteLine(senseLine); newTrim = true; } _writeStopwatch.Stop(); blocksToRead = saveBlocksToRead; currentSector = i + 1; _resume.NextBlock = currentSector; sectorSpeedStart += blocksToRead; double elapsed = _speedStopwatch.Elapsed.TotalSeconds; if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue; currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed); sectorSpeedStart = 0; _speedStopwatch.Reset(); } _speedStopwatch.Stop(); for(ulong i = extentStart; i <= extentEnd; i += blocksToRead) { saveBlocksToRead = blocksToRead; if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke(Localization.Core.Aborted); _dumpLog.WriteLine(Localization.Core.Aborted); break; } if(extentEnd - i < blocksToRead) blocksToRead = (uint)(extentEnd - i) + 1; mhddLog.Write(i, cmdDuration, blocksToRead); ibgLog.Write(i, currentSpeed * 1024); // Write empty data _writeStopwatch.Restart(); outputFormat.WriteSectors(new byte[blockSize * blocksToRead], i, blocksToRead); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; blocksToRead = saveBlocksToRead; extents.Add(i, blocksToRead, true); currentSector = i + 1; _resume.NextBlock = currentSector; _mediaGraph?.PaintSectorsGood(i, blocksToRead); } if(!_aborted) currentSector = extentEnd + 1; } _writeStopwatch.Stop(); _resume.BadBlocks = _resume.BadBlocks.Distinct().ToList(); EndProgress?.Invoke(); // Middle Zone D UpdateStatus?.Invoke(Localization.Core.Writing_Middle_Zone_D_empty); _dumpLog.WriteLine(Localization.Core.Writing_Middle_Zone_D_empty); InitProgress?.Invoke(); for(ulong middle = currentSector - blocks - 1; middle < middleZone - 1; middle += blocksToRead) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke(Localization.Core.Aborted); _dumpLog.WriteLine(Localization.Core.Aborted); break; } if(middleZone - 1 - middle < blocksToRead) blocksToRead = (uint)(middleZone - 1 - middle); UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2, middle + currentSector, totalSize, ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()), (long)(middle + currentSector), (long)totalSize); mhddLog.Write(middle + currentSector, cmdDuration, blocksToRead); ibgLog.Write(middle + currentSector, currentSpeed * 1024); // Write empty data _writeStopwatch.Restart(); outputFormat.WriteSectors(new byte[blockSize * blocksToRead], middle + currentSector, blocksToRead); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; extents.Add(currentSector, blocksToRead, true); _writeStopwatch.Stop(); currentSector += blocksToRead; _resume.NextBlock = currentSector; } EndProgress?.Invoke(); blocksToRead = saveBlocksToRead; UpdateStatus?.Invoke(Localization.Core.Locking_drive); _dumpLog.WriteLine(Localization.Core.Locking_drive); sense = _dev.KreonLock(out senseBuf, _dev.Timeout, out _); if(sense) { _dumpLog.WriteLine(Localization.Core.Cannot_lock_drive_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_lock_drive_not_continuing); return; } sense = _dev.ReadCapacity(out readBuffer, out senseBuf, _dev.Timeout, out _); if(sense) { StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_disc_capacity); return; } // Video Layer 1 _dumpLog.WriteLine(Localization.Core.Reading_Video_Layer_1); UpdateStatus?.Invoke(Localization.Core.Reading_Video_Layer_1); InitProgress?.Invoke(); for(ulong l1 = currentSector - blocks - middleZone + l0Video; l1 < l0Video + l1Video; l1 += blocksToRead) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke(Localization.Core.Aborted); _dumpLog.WriteLine(Localization.Core.Aborted); break; } if(l0Video + l1Video - l1 < blocksToRead) blocksToRead = (uint)(l0Video + l1Video - l1); if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed; if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed; UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2, currentSector, totalSize, ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()), (long)currentSector, (long)totalSize); _speedStopwatch.Start(); sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)l1, blockSize, 0, blocksToRead, false, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); totalDuration += cmdDuration; if(!sense && !_dev.Error) { mhddLog.Write(currentSector, cmdDuration, blocksToRead); ibgLog.Write(currentSector, currentSpeed * 1024); _writeStopwatch.Restart(); outputFormat.WriteSectors(readBuffer, currentSector, blocksToRead); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; extents.Add(currentSector, blocksToRead, true); _mediaGraph?.PaintSectorsGood(currentSector, blocksToRead); } else { _errorLog?.WriteLine(currentSector, _dev.Error, _dev.LastError, senseBuf); // TODO: Reset device after X errors if(_stopOnError) return; // TODO: Return more cleanly // Write empty data _writeStopwatch.Restart(); outputFormat.WriteSectors(new byte[blockSize * _skip], currentSector, _skip); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; // TODO: Handle errors in video partition //errored += blocksToRead; //resume.BadBlocks.Add(l1); AaruLogging.Debug(MODULE_NAME, Localization.Core.READ_error_0, Sense.PrettifySense(senseBuf)); mhddLog.Write(l1, cmdDuration < 500 ? 65535 : cmdDuration, _skip); ibgLog.Write(l1, 0); _dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, l1); l1 += _skip - blocksToRead; string[] senseLines = Sense.PrettifySense(senseBuf) .Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries); foreach(string senseLine in senseLines) _dumpLog.WriteLine(senseLine); } _writeStopwatch.Stop(); currentSector += blocksToRead; _resume.NextBlock = currentSector; sectorSpeedStart += blocksToRead; double elapsed = _speedStopwatch.Elapsed.TotalSeconds; if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue; currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed); sectorSpeedStart = 0; _speedStopwatch.Reset(); } EndProgress?.Invoke(); UpdateStatus?.Invoke(Localization.Core.Unlocking_drive_Wxripper); _dumpLog.WriteLine(Localization.Core.Unlocking_drive_Wxripper); sense = _dev.KreonUnlockWxripper(out senseBuf, _dev.Timeout, out _); if(sense) { _errorLog?.WriteLine("Kreon Wxripper unlock", _dev.Error, _dev.LastError, senseBuf); _dumpLog.WriteLine(Localization.Core.Cannot_unlock_drive_not_continuing); StoppingErrorMessage?.Invoke(Localization.Core.Cannot_unlock_drive_not_continuing); return; } sense = _dev.ReadCapacity(out readBuffer, out senseBuf, _dev.Timeout, out _); if(sense) { StoppingErrorMessage?.Invoke(Localization.Core.Cannot_get_disc_capacity); return; } _dumpStopwatch.Stop(); AaruLogging.WriteLine(); mhddLog.Close(); ibgLog.Close(_dev, blocks, blockSize, _dumpStopwatch.Elapsed.TotalSeconds, currentSpeed * 1024, blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000), _devicePath); UpdateStatus?.Invoke(string.Format(Localization.Core.Dump_finished_in_0, _dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second))); UpdateStatus?.Invoke(string.Format(Localization.Core.Average_dump_speed_0, ByteSize.FromBytes(blockSize * (blocks + 1)) .Per(totalDuration.Milliseconds()) .Humanize())); UpdateStatus?.Invoke(string.Format(Localization.Core.Average_write_speed_0, ByteSize.FromBytes(blockSize * (blocks + 1)) .Per(imageWriteDuration.Seconds()) .Humanize())); _dumpLog.WriteLine(string.Format(Localization.Core.Dump_finished_in_0, _dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second))); _dumpLog.WriteLine(string.Format(Localization.Core.Average_dump_speed_0, ByteSize.FromBytes(blockSize * (blocks + 1)) .Per(totalDuration.Milliseconds()) .Humanize())); _dumpLog.WriteLine(string.Format(Localization.Core.Average_write_speed_0, ByteSize.FromBytes(blockSize * (blocks + 1)) .Per(imageWriteDuration.Seconds()) .Humanize())); #region Trimming if(_resume.BadBlocks.Count > 0 && !_aborted && _trim && newTrim) { _trimStopwatch.Restart(); UpdateStatus?.Invoke(Localization.Core.Trimming_skipped_sectors); _dumpLog.WriteLine(Localization.Core.Trimming_skipped_sectors); ulong[] tmpArray = _resume.BadBlocks.ToArray(); InitProgress?.Invoke(); foreach(ulong badSector in tmpArray) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); _dumpLog.WriteLine(Localization.Core.Aborted); break; } PulseProgress?.Invoke(string.Format(Localization.Core.Trimming_sector_0, badSector)); sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)badSector, blockSize, 0, 1, false, _dev.Timeout, out cmdDuration); totalDuration += cmdDuration; if(sense || _dev.Error) { _errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, senseBuf); continue; } _resume.BadBlocks.Remove(badSector); extents.Add(badSector); outputFormat.WriteSector(readBuffer, badSector); _mediaGraph?.PaintSectorGood(badSector); } EndProgress?.Invoke(); _trimStopwatch.Stop(); UpdateStatus?.Invoke(string.Format(Localization.Core.Trimming_finished_in_0, _trimStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second))); _dumpLog.WriteLine(string.Format(Localization.Core.Trimming_finished_in_0, _trimStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second))); } #endregion Trimming #region Error handling if(_resume.BadBlocks.Count > 0 && !_aborted && _retryPasses > 0) { List tmpList = []; foreach(ulong ur in _resume.BadBlocks) { for(ulong i = ur; i < ur + blocksToRead; i++) tmpList.Add(i); } tmpList.Sort(); int pass = 1; bool forward = true; bool runningPersistent = false; _resume.BadBlocks = tmpList; Modes.ModePage? currentModePage = null; byte[] md6; byte[] md10; if(_persistent) { Modes.ModePage_01_MMC pgMmc; sense = _dev.ModeSense6(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout, out _); Modes.DecodedMode? dcMode6 = null; if(!sense) dcMode6 = Modes.DecodeMode6(readBuffer, PeripheralDeviceTypes.MultiMediaDevice); if(sense || dcMode6 is null) { sense = _dev.ModeSense10(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout, out _); if(!sense) { Modes.DecodedMode? dcMode10 = Modes.DecodeMode10(readBuffer, PeripheralDeviceTypes.MultiMediaDevice); if(dcMode10.HasValue) { foreach(Modes.ModePage modePage in dcMode10.Value.Pages.Where(modePage => modePage is { Page: 0x01, Subpage: 0x00 })) currentModePage = modePage; } } } else { if(dcMode6.HasValue) { foreach(Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage => modePage is { Page: 0x01, Subpage: 0x00 })) currentModePage = modePage; } } if(currentModePage == null) { pgMmc = new Modes.ModePage_01_MMC { PS = false, ReadRetryCount = 0x20, Parameter = 0x00 }; currentModePage = new Modes.ModePage { Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc) }; } pgMmc = new Modes.ModePage_01_MMC { PS = false, ReadRetryCount = 255, Parameter = 0x20 }; var md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), Pages = [ new Modes.ModePage { Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc) } ] }; md6 = Modes.EncodeMode6(md, _dev.ScsiType); md10 = Modes.EncodeMode10(md, _dev.ScsiType); UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks); _dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks); sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _); if(sense) sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _); if(sense) { UpdateStatus?.Invoke(Localization.Core .Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading); AaruLogging.Debug(Localization.Core.Error_0, Sense.PrettifySense(senseBuf)); _dumpLog.WriteLine(Localization.Core .Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading); } else runningPersistent = true; } InitProgress?.Invoke(); repeatRetry: ulong[] tmpArray = _resume.BadBlocks.ToArray(); foreach(ulong badSector in tmpArray) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke(Localization.Core.Aborted); _dumpLog.WriteLine(Localization.Core.Aborted); break; } if(forward) { PulseProgress?.Invoke(runningPersistent ? string.Format(Localization.Core .Retrying_sector_0_pass_1_recovering_partial_data_forward, badSector, pass) : string.Format(Localization.Core.Retrying_sector_0_pass_1_forward, badSector, pass)); } else { PulseProgress?.Invoke(runningPersistent ? string.Format(Localization.Core .Retrying_sector_0_pass_1_recovering_partial_data_reverse, badSector, pass) : string.Format(Localization.Core.Retrying_sector_0_pass_1_reverse, badSector, pass)); } sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)badSector, blockSize, 0, 1, false, _dev.Timeout, out cmdDuration); totalDuration += cmdDuration; if(sense || _dev.Error) _errorLog?.WriteLine(currentSector, _dev.Error, _dev.LastError, senseBuf); if(!sense && !_dev.Error) { _resume.BadBlocks.Remove(badSector); extents.Add(badSector); outputFormat.WriteSector(readBuffer, badSector); _mediaGraph?.PaintSectorGood(badSector); UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_block_0_in_pass_1, badSector, pass)); _dumpLog.WriteLine(Localization.Core.Correctly_retried_block_0_in_pass_1, badSector, pass); } else if(runningPersistent) outputFormat.WriteSector(readBuffer, badSector); } if(pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0) { pass++; forward = !forward; _resume.BadBlocks.Sort(); if(!forward) _resume.BadBlocks.Reverse(); goto repeatRetry; } if(runningPersistent && currentModePage.HasValue) { var md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), Pages = [currentModePage.Value] }; md6 = Modes.EncodeMode6(md, _dev.ScsiType); md10 = Modes.EncodeMode10(md, _dev.ScsiType); UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status); _dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status); sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _); if(sense) _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _); } EndProgress?.Invoke(); } #endregion Error handling _resume.BadBlocks.Sort(); currentTry.Extents = ExtentsConverter.ToMetadata(extents); foreach(KeyValuePair tag in mediaTags) { if(tag.Value is null) { AaruLogging.Error(Localization.Core.Error_Tag_type_0_is_null_skipping, tag.Key); continue; } ret = outputFormat.WriteMediaTag(tag.Value, tag.Key); if(ret || _force) continue; // Cannot write tag to image _dumpLog.WriteLine(string.Format(Localization.Core.Cannot_write_tag_0, tag.Key)); StoppingErrorMessage?.Invoke(string.Format(Localization.Core.Cannot_write_tag_0, tag.Key) + Environment.NewLine + outputFormat.ErrorMessage); return; } _resume.BadBlocks.Sort(); foreach(ulong bad in _resume.BadBlocks) _dumpLog.WriteLine(Localization.Core.Sector_0_could_not_be_read, bad); currentTry.Extents = ExtentsConverter.ToMetadata(extents); outputFormat.SetDumpHardware(_resume.Tries); var metadata = new CommonTypes.Structs.ImageInfo { Application = "Aaru", ApplicationVersion = Version.GetVersion() }; if(!outputFormat.SetImageInfo(metadata)) { ErrorMessage?.Invoke(Localization.Core.Error_0_setting_metadata + Environment.NewLine + outputFormat.ErrorMessage); } if(_preSidecar != null) outputFormat.SetMetadata(_preSidecar); _dumpLog.WriteLine(Localization.Core.Closing_output_file); UpdateStatus?.Invoke(Localization.Core.Closing_output_file); _imageCloseStopwatch.Restart(); outputFormat.Close(); _imageCloseStopwatch.Stop(); UpdateStatus?.Invoke(string.Format(Localization.Core.Closed_in_0, _imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second))); _dumpLog.WriteLine(Localization.Core.Closed_in_0, _imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)); if(_aborted) { UpdateStatus?.Invoke(Localization.Core.Aborted); _dumpLog.WriteLine(Localization.Core.Aborted); return; } double totalChkDuration = 0; if(_metadata) { var layers = new Layers { Type = LayerType.OTP, Sectors = [ new Sectors { Value = layerBreak } ] }; WriteOpticalSidecar(blockSize, blocks, dskType, layers, mediaTags, 1, out totalChkDuration, null); } UpdateStatus?.Invoke(""); UpdateStatus?.Invoke(string.Format(Localization.Core .Took_a_total_of_0_1_processing_commands_2_checksumming_3_writing_4_closing, _dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second), totalDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second), totalChkDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second), imageWriteDuration.Seconds().Humanize(minUnit: TimeUnit.Second), _imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second))); UpdateStatus?.Invoke(string.Format(Localization.Core.Average_speed_0, ByteSize.FromBytes(blockSize * (blocks + 1)) .Per(totalDuration.Milliseconds()) .Humanize())); if(maxSpeed > 0) { UpdateStatus?.Invoke(string.Format(Localization.Core.Fastest_speed_burst_0, ByteSize.FromMegabytes(maxSpeed).Per(_oneSecond).Humanize())); } if(minSpeed is > 0 and < double.MaxValue) { UpdateStatus?.Invoke(string.Format(Localization.Core.Slowest_speed_burst_0, ByteSize.FromMegabytes(minSpeed).Per(_oneSecond).Humanize())); } UpdateStatus?.Invoke(string.Format(Localization.Core._0_sectors_could_not_be_read, _resume.BadBlocks.Count)); UpdateStatus?.Invoke(""); Statistics.AddMedia(dskType, true); } }