mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-02-04 00:44:39 +00:00
Implement a command to merge two images into a combined third image.
This commit is contained in:
@@ -1,13 +1,10 @@
|
||||
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve">
|
||||
<s:Boolean
|
||||
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=devices_005Cdumping_005Ccompactdisc/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean
|
||||
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=devices_005Cdumping_005Clinearmemory/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean
|
||||
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=devices_005Cdumping_005Cplaystationportable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean
|
||||
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=devices_005Cdumping_005Csbc/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean
|
||||
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=sidecar/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xml:space="preserve">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=devices_005Cdumping_005Ccompactdisc/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=devices_005Cdumping_005Clinearmemory/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=devices_005Cdumping_005Cplaystationportable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=devices_005Cdumping_005Csbc/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=image_005Cmerge/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=sidecar/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -25,7 +25,7 @@ public partial class Convert
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
if(_decrypt) UpdateStatus?.Invoke("Decrypting encrypted sectors.");
|
||||
if(_decrypt) UpdateStatus?.Invoke(UI.Decrypting_encrypted_sectors);
|
||||
|
||||
// Convert all sectors track by track
|
||||
ErrorNumber errno = ConvertOpticalSectors(inputOptical, outputOptical, useLong);
|
||||
|
||||
47
Aaru.Core/Image/Merge/Block.cs
Normal file
47
Aaru.Core/Image/Merge/Block.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Globalization;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
private (bool success, uint cylinders, uint heads, uint sectors)? ParseGeometry(string geometryString)
|
||||
{
|
||||
// Parses CHS (Cylinder/Head/Sector) geometry string in format "C/H/S" or "C-H-S"
|
||||
// Returns tuple with success flag and parsed values, or null if not specified
|
||||
|
||||
if(geometryString == null) return null;
|
||||
|
||||
string[] geometryPieces = geometryString.Split('/');
|
||||
|
||||
if(geometryPieces.Length == 0) geometryPieces = geometryString.Split('-');
|
||||
|
||||
if(geometryPieces.Length != 3)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Invalid_geometry_specified);
|
||||
|
||||
return (false, 0, 0, 0);
|
||||
}
|
||||
|
||||
if(!uint.TryParse(geometryPieces[0], CultureInfo.InvariantCulture, out uint cylinders) || cylinders == 0)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Invalid_number_of_cylinders_specified);
|
||||
|
||||
return (false, 0, 0, 0);
|
||||
}
|
||||
|
||||
if(!uint.TryParse(geometryPieces[1], CultureInfo.InvariantCulture, out uint heads) || heads == 0)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Invalid_number_of_heads_specified);
|
||||
|
||||
return (false, 0, 0, 0);
|
||||
}
|
||||
|
||||
if(uint.TryParse(geometryPieces[2], CultureInfo.InvariantCulture, out uint sectors) && sectors != 0)
|
||||
return (true, cylinders, heads, sectors);
|
||||
|
||||
StoppingErrorMessage?.Invoke(UI.Invalid_sectors_per_track_specified);
|
||||
|
||||
return (false, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
255
Aaru.Core/Image/Merge/Calculator.cs
Normal file
255
Aaru.Core/Image/Merge/Calculator.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Metadata;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
List<ulong> CalculateSectorsToCopy(IMediaImage primaryImage, IMediaImage secondaryImage, Resume primaryResume,
|
||||
Resume secondaryResume, List<ulong> overrideSectorsList)
|
||||
{
|
||||
List<DumpHardware> primaryTries = (primaryResume != null ? primaryResume.Tries : primaryImage.DumpHardware) ??
|
||||
[
|
||||
new DumpHardware
|
||||
{
|
||||
Extents =
|
||||
[
|
||||
new Extent
|
||||
{
|
||||
Start = 0,
|
||||
End = primaryImage.Info.Sectors - 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
List<DumpHardware> secondaryTries =
|
||||
(secondaryResume != null ? secondaryResume.Tries : secondaryImage.DumpHardware) ??
|
||||
[
|
||||
new DumpHardware
|
||||
{
|
||||
Extents =
|
||||
[
|
||||
new Extent
|
||||
{
|
||||
Start = 0,
|
||||
End = secondaryImage.Info.Sectors - 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Get all sectors that appear in secondaryTries but not in primaryTries
|
||||
var sectorsToCopy = new List<ulong>();
|
||||
|
||||
// Iterate through all extents in secondaryTries
|
||||
foreach(DumpHardware secondaryHardware in secondaryTries)
|
||||
{
|
||||
if(secondaryHardware?.Extents == null) continue;
|
||||
|
||||
foreach(Extent secondaryExtent in secondaryHardware.Extents)
|
||||
{
|
||||
// For each sector in this secondary extent
|
||||
for(ulong sector = secondaryExtent.Start; sector <= secondaryExtent.End; sector++)
|
||||
{
|
||||
// Check if this sector appears in any primary extent
|
||||
var foundInPrimary = false;
|
||||
|
||||
foreach(DumpHardware primaryHardware in primaryTries)
|
||||
{
|
||||
if(primaryHardware?.Extents == null) continue;
|
||||
|
||||
if(primaryHardware.Extents.Any(primaryExtent =>
|
||||
sector >= primaryExtent.Start &&
|
||||
sector <= primaryExtent.End))
|
||||
foundInPrimary = true;
|
||||
|
||||
if(foundInPrimary) break;
|
||||
}
|
||||
|
||||
// If not found in primary, add to result
|
||||
if(!foundInPrimary) sectorsToCopy.Add(sector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sectorsToCopy.AddRange(overrideSectorsList.Where(t => !sectorsToCopy.Contains(t)));
|
||||
|
||||
return sectorsToCopy;
|
||||
}
|
||||
|
||||
List<DumpHardware> CalculateMergedDumpHardware(IMediaImage primaryImage, IMediaImage secondaryImage,
|
||||
Resume primaryResume, Resume secondaryResume,
|
||||
List<ulong> overrideSectorsList)
|
||||
{
|
||||
List<DumpHardware> primaryTries = (primaryResume != null ? primaryResume.Tries : primaryImage.DumpHardware) ??
|
||||
[
|
||||
new DumpHardware
|
||||
{
|
||||
Extents =
|
||||
[
|
||||
new Extent
|
||||
{
|
||||
Start = 0,
|
||||
End = primaryImage.Info.Sectors - 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
List<DumpHardware> secondaryTries =
|
||||
(secondaryResume != null ? secondaryResume.Tries : secondaryImage.DumpHardware) ??
|
||||
[
|
||||
new DumpHardware
|
||||
{
|
||||
Extents =
|
||||
[
|
||||
new Extent
|
||||
{
|
||||
Start = 0,
|
||||
End = secondaryImage.Info.Sectors - 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var mergedHardware = new List<DumpHardware>();
|
||||
|
||||
// Create a mapping of which hardware each sector belongs to
|
||||
var sectorToHardware = new Dictionary<ulong, DumpHardware>();
|
||||
|
||||
// First, build a lookup of which hardware each sector belongs to in primary tries
|
||||
var primarySectorToHardware = new Dictionary<ulong, DumpHardware>();
|
||||
|
||||
foreach(DumpHardware primaryHardware in primaryTries)
|
||||
{
|
||||
if(primaryHardware?.Extents == null) continue;
|
||||
|
||||
foreach(Extent extent in primaryHardware.Extents)
|
||||
{
|
||||
for(ulong sector = extent.Start; sector <= extent.End; sector++)
|
||||
primarySectorToHardware[sector] = primaryHardware;
|
||||
}
|
||||
}
|
||||
|
||||
// Build a lookup of which hardware each sector belongs to in secondary tries
|
||||
var secondarySectorToHardware = new Dictionary<ulong, DumpHardware>();
|
||||
|
||||
foreach(DumpHardware secondaryHardware in secondaryTries)
|
||||
{
|
||||
if(secondaryHardware?.Extents == null) continue;
|
||||
|
||||
foreach(Extent extent in secondaryHardware.Extents)
|
||||
{
|
||||
for(ulong sector = extent.Start; sector <= extent.End; sector++)
|
||||
secondarySectorToHardware[sector] = secondaryHardware;
|
||||
}
|
||||
}
|
||||
|
||||
// Now assign hardware to each sector: use primary hardware, unless sector is in override list
|
||||
foreach((ulong sector, DumpHardware primaryHardware) in primarySectorToHardware)
|
||||
{
|
||||
// If this sector should be overridden, use secondary hardware instead
|
||||
if(overrideSectorsList.Contains(sector))
|
||||
{
|
||||
if(secondarySectorToHardware.TryGetValue(sector, out DumpHardware secondaryHardware))
|
||||
sectorToHardware[sector] = secondaryHardware;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use primary hardware
|
||||
sectorToHardware[sector] = primaryHardware;
|
||||
}
|
||||
}
|
||||
|
||||
// Also add any sectors from override list that weren't in primary
|
||||
foreach(ulong overrideSector in overrideSectorsList)
|
||||
{
|
||||
if(!sectorToHardware.ContainsKey(overrideSector) &&
|
||||
secondarySectorToHardware.TryGetValue(overrideSector, out DumpHardware secondaryHardware))
|
||||
sectorToHardware[overrideSector] = secondaryHardware;
|
||||
}
|
||||
|
||||
// Create extents preserving sector order, grouping contiguous sectors from same hardware
|
||||
var allSectors = sectorToHardware.Keys.Order().ToList();
|
||||
|
||||
if(allSectors.Count == 0) return mergedHardware;
|
||||
|
||||
// Start first extent
|
||||
DumpHardware currentHardware = sectorToHardware[allSectors[0]];
|
||||
ulong extentStart = allSectors[0];
|
||||
ulong extentEnd = allSectors[0];
|
||||
|
||||
for(var i = 1; i < allSectors.Count; i++)
|
||||
{
|
||||
ulong sector = allSectors[i];
|
||||
DumpHardware hw = sectorToHardware[sector];
|
||||
|
||||
// Check if we should continue current extent or start new one
|
||||
if(hw == currentHardware && sector == extentEnd + 1)
|
||||
{
|
||||
// Same hardware and contiguous sector, extend current extent
|
||||
extentEnd = sector;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hardware changed or gap in sectors, save current extent and start new one
|
||||
AddOrUpdateHardware(mergedHardware, currentHardware, extentStart, extentEnd);
|
||||
|
||||
currentHardware = hw;
|
||||
extentStart = sector;
|
||||
extentEnd = sector;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last extent
|
||||
AddOrUpdateHardware(mergedHardware, currentHardware, extentStart, extentEnd);
|
||||
|
||||
return mergedHardware;
|
||||
}
|
||||
|
||||
static void AddOrUpdateHardware(List<DumpHardware> mergedHardware, DumpHardware originalHardware, ulong start,
|
||||
ulong end)
|
||||
{
|
||||
// Check if we already have an entry for this hardware
|
||||
DumpHardware existing = mergedHardware.FirstOrDefault(h => h.Manufacturer == originalHardware.Manufacturer &&
|
||||
h.Model == originalHardware.Model &&
|
||||
h.Revision == originalHardware.Revision &&
|
||||
h.Firmware == originalHardware.Firmware &&
|
||||
h.Serial == originalHardware.Serial);
|
||||
|
||||
if(existing != null)
|
||||
{
|
||||
// Add extent to existing hardware
|
||||
existing.Extents.Add(new Extent
|
||||
{
|
||||
Start = start,
|
||||
End = end
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new hardware entry
|
||||
mergedHardware.Add(new DumpHardware
|
||||
{
|
||||
Manufacturer = originalHardware.Manufacturer,
|
||||
Model = originalHardware.Model,
|
||||
Revision = originalHardware.Revision,
|
||||
Firmware = originalHardware.Firmware,
|
||||
Serial = originalHardware.Serial,
|
||||
Software = originalHardware.Software,
|
||||
Extents =
|
||||
[
|
||||
new Extent
|
||||
{
|
||||
Start = start,
|
||||
End = end
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Aaru.Core/Image/Merge/Capabilities.cs
Normal file
70
Aaru.Core/Image/Merge/Capabilities.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
ErrorNumber ValidateMediaCapabilities(IMediaImage primaryImage, IMediaImage secondaryImage,
|
||||
IWritableImage outputImage, MediaType mediaType)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
if(!outputImage.SupportedMediaTypes.Contains(mediaType))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Output_format_does_not_support_media_type);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
}
|
||||
|
||||
foreach(MediaTagType mediaTag in primaryImage.Info.ReadableMediaTags
|
||||
.Where(mediaTag =>
|
||||
!outputImage.SupportedMediaTags.Contains(mediaTag))
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(string.Format(UI.Media_tag_0_present_in_primary_image_will_be_lost_in_output_format, mediaTag));
|
||||
|
||||
return ErrorNumber.DataWillBeLost;
|
||||
}
|
||||
|
||||
foreach(MediaTagType mediaTag in secondaryImage.Info.ReadableMediaTags
|
||||
.Where(mediaTag =>
|
||||
!primaryImage.Info.ReadableMediaTags
|
||||
.Contains(mediaTag) &&
|
||||
!outputImage.SupportedMediaTags.Contains(mediaTag))
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(string.Format(UI.Media_tag_0_present_in_secondary_image_will_be_lost_in_output_format,
|
||||
mediaTag));
|
||||
|
||||
return ErrorNumber.DataWillBeLost;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber ValidateSectorTags(IMediaImage primaryImage, IWritableImage outputImage, out bool useLong)
|
||||
{
|
||||
useLong = primaryImage.Info.ReadableSectorTags.Count != 0;
|
||||
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
foreach(SectorTagType sectorTag in primaryImage.Info.ReadableSectorTags
|
||||
.Where(sectorTag =>
|
||||
!outputImage.SupportedSectorTags.Contains(sectorTag))
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Output_image_does_not_support_sector_tag_0_data_will_be_lost,
|
||||
sectorTag));
|
||||
|
||||
return ErrorNumber.DataWillBeLost;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
34
Aaru.Core/Image/Merge/Create.cs
Normal file
34
Aaru.Core/Image/Merge/Create.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
private ErrorNumber CreateOutputImage(IMediaImage primaryImage, MediaType mediaType, IWritableImage outputImage,
|
||||
uint negativeSectors, uint overflowSectors)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Invoke_Opening_image_file);
|
||||
|
||||
bool created = outputImage.Create(outputImagePath,
|
||||
mediaType,
|
||||
options,
|
||||
primaryImage.Info.Sectors,
|
||||
negativeSectors,
|
||||
overflowSectors,
|
||||
primaryImage.Info.SectorSize);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
if(created) return ErrorNumber.NoError;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_creating_output_image, outputImage.ErrorMessage));
|
||||
|
||||
return ErrorNumber.CannotCreateFormat;
|
||||
}
|
||||
}
|
||||
632
Aaru.Core/Image/Merge/Edge.cs
Normal file
632
Aaru.Core/Image/Merge/Edge.cs
Normal file
@@ -0,0 +1,632 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
ErrorNumber CopyNegativeSectorsPrimary(bool useLong, IMediaImage primaryImage, IWritableImage outputImage,
|
||||
uint negativeSectors, List<uint> overrideNegativeSectors)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
List<uint> notDumped = [];
|
||||
|
||||
// There's no -0
|
||||
for(uint i = 1; i <= negativeSectors; i++)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
byte[] sector;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_negative_sector_0_of_1, i, negativeSectors),
|
||||
i,
|
||||
negativeSectors);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = primaryImage.ReadSectorLong(i, true, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(sectorStatus == SectorStatus.NotDumped)
|
||||
{
|
||||
notDumped.Add(i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result = outputImage.WriteSectorLong(sector, i, true, sectorStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = primaryImage.ReadSector(i, true, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(sectorStatus == SectorStatus.NotDumped)
|
||||
{
|
||||
notDumped.Add(i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result = outputImage.WriteSector(sector, i, true, sectorStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
if(result) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_negative_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
i));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
foreach(SectorTagType tag in primaryImage.Info.ReadableSectorTags.TakeWhile(_ => useLong)
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
case SectorTagType.CdTrackFlags:
|
||||
case SectorTagType.CdTrackIsrc:
|
||||
case SectorTagType.CdTrackText:
|
||||
// These tags are track tags
|
||||
continue;
|
||||
}
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
for(uint i = 1; i <= negativeSectors; i++)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
if(notDumped.Contains(i)) continue;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tag_1_for_negative_sector_0, i, tag),
|
||||
i,
|
||||
negativeSectors);
|
||||
|
||||
bool result;
|
||||
|
||||
errno = primaryImage.ReadSectorTag(i, true, tag, out byte[] sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputImage.WriteSectorTag(sector, i, true, tag);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
if(result) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_negative_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
i));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
overrideNegativeSectors.AddRange(notDumped.Where(t => !overrideNegativeSectors.Contains(t)));
|
||||
overrideNegativeSectors.Sort();
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
ErrorNumber CopyNegativeSectorsSecondary(bool useLong, IMediaImage secondaryImage, IWritableImage outputImage,
|
||||
List<uint> overrideNegativeSectors)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
List<uint> notDumped = [];
|
||||
var currentCount = 0;
|
||||
int totalCount = overrideNegativeSectors.Count;
|
||||
|
||||
foreach(uint sectorAddress in overrideNegativeSectors)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
byte[] sector;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_negative_sector_0, sectorAddress),
|
||||
currentCount,
|
||||
totalCount);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = secondaryImage.ReadSectorLong(sectorAddress, true, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(sectorStatus == SectorStatus.NotDumped)
|
||||
{
|
||||
notDumped.Add(sectorAddress);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result = outputImage.WriteSectorLong(sector, sectorAddress, true, sectorStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = secondaryImage.ReadSector(sectorAddress, true, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(sectorStatus == SectorStatus.NotDumped)
|
||||
{
|
||||
notDumped.Add(sectorAddress);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result = outputImage.WriteSector(sector, sectorAddress, true, sectorStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
currentCount++;
|
||||
|
||||
if(result) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_negative_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
foreach(SectorTagType tag in secondaryImage.Info.ReadableSectorTags.TakeWhile(_ => useLong)
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
case SectorTagType.CdTrackFlags:
|
||||
case SectorTagType.CdTrackIsrc:
|
||||
case SectorTagType.CdTrackText:
|
||||
// These tags are track tags
|
||||
continue;
|
||||
}
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
currentCount = 0;
|
||||
|
||||
foreach(uint sectorAddress in overrideNegativeSectors)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
if(notDumped.Contains(sectorAddress)) continue;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tag_1_for_negative_sector_0, sectorAddress, tag),
|
||||
currentCount,
|
||||
totalCount);
|
||||
|
||||
bool result;
|
||||
|
||||
errno = secondaryImage.ReadSectorTag(sectorAddress, true, tag, out byte[] sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputImage.WriteSectorTag(sector, sectorAddress, true, tag);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
currentCount++;
|
||||
|
||||
if(result) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_negative_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
ErrorNumber CopyOverflowSectorsPrimary(bool useLong, IMediaImage primaryImage, IWritableImage outputImage,
|
||||
uint overflowSectors, List<ulong> overrideOverflowSectors)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
List<uint> notDumped = [];
|
||||
|
||||
for(uint i = 0; i < overflowSectors; i++)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
byte[] sector;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_overflow_sector_0_of_1, i, overflowSectors),
|
||||
i,
|
||||
overflowSectors);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = primaryImage.ReadSectorLong(primaryImage.Info.Sectors + i, false, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(sectorStatus == SectorStatus.NotDumped)
|
||||
{
|
||||
notDumped.Add(i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result = outputImage.WriteSectorLong(sector, primaryImage.Info.Sectors + i, false, sectorStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = primaryImage.ReadSector(primaryImage.Info.Sectors + i, false, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(sectorStatus == SectorStatus.NotDumped)
|
||||
{
|
||||
notDumped.Add(i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result = outputImage.WriteSector(sector, primaryImage.Info.Sectors + i, false, sectorStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
if(result) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
i));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
foreach(SectorTagType tag in primaryImage.Info.ReadableSectorTags.TakeWhile(_ => useLong)
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
case SectorTagType.CdTrackFlags:
|
||||
case SectorTagType.CdTrackIsrc:
|
||||
case SectorTagType.CdTrackText:
|
||||
// These tags are track tags
|
||||
continue;
|
||||
}
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
for(uint i = 1; i < overflowSectors; i++)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
if(notDumped.Contains(i)) continue;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tag_1_for_overflow_sector_0, i, tag),
|
||||
i,
|
||||
overflowSectors);
|
||||
|
||||
bool result;
|
||||
|
||||
errno = primaryImage.ReadSectorTag(primaryImage.Info.Sectors + i, false, tag, out byte[] sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputImage.WriteSectorTag(sector, primaryImage.Info.Sectors + i, false, tag);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
primaryImage.Info.Sectors + i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
if(result) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
primaryImage.Info.Sectors + i));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
foreach(uint sector in notDumped.Where(t => !overrideOverflowSectors.Contains(primaryImage.Info.Sectors + t)))
|
||||
overrideOverflowSectors.Add(primaryImage.Info.Sectors + sector);
|
||||
|
||||
overrideOverflowSectors.Sort();
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
ErrorNumber CopyOverflowSectorsSecondary(bool useLong, IMediaImage secondaryImage, IWritableImage outputImage,
|
||||
List<ulong> overrideOverflowSectors)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
List<ulong> notDumped = [];
|
||||
|
||||
int overflowSectors = overrideOverflowSectors.Count;
|
||||
var currentSector = 0;
|
||||
|
||||
foreach(ulong sectorAddress in overrideOverflowSectors)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
byte[] sector;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_overflow_sector_0, sectorAddress),
|
||||
currentSector,
|
||||
overflowSectors);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = secondaryImage.ReadSectorLong(sectorAddress, false, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(sectorStatus == SectorStatus.NotDumped)
|
||||
{
|
||||
notDumped.Add(sectorAddress);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result = outputImage.WriteSectorLong(sector, sectorAddress, false, sectorStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = secondaryImage.ReadSector(sectorAddress, false, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(sectorStatus == SectorStatus.NotDumped)
|
||||
{
|
||||
notDumped.Add(sectorAddress);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result = outputImage.WriteSector(sector, sectorAddress, false, sectorStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
currentSector++;
|
||||
|
||||
if(result) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
foreach(SectorTagType tag in secondaryImage.Info.ReadableSectorTags.TakeWhile(_ => useLong)
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
case SectorTagType.CdTrackFlags:
|
||||
case SectorTagType.CdTrackIsrc:
|
||||
case SectorTagType.CdTrackText:
|
||||
// These tags are track tags
|
||||
continue;
|
||||
}
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
currentSector = 0;
|
||||
|
||||
foreach(ulong sectorAddress in overrideOverflowSectors)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
if(notDumped.Contains(sectorAddress)) continue;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tag_1_for_overflow_sector_0, sectorAddress, tag),
|
||||
currentSector,
|
||||
overflowSectors);
|
||||
|
||||
bool result;
|
||||
|
||||
errno = secondaryImage.ReadSectorTag(sectorAddress, false, tag, out byte[] sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputImage.WriteSectorTag(sector, sectorAddress, false, tag);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
currentSector++;
|
||||
|
||||
if(result) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
52
Aaru.Core/Image/Merge/Flux.cs
Normal file
52
Aaru.Core/Image/Merge/Flux.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
// TODO: Should we return error any time?
|
||||
// TODO: Add progress reporting
|
||||
ErrorNumber CopyFlux(IFluxImage inputFlux, IWritableFluxImage outputFlux)
|
||||
{
|
||||
for(ushort track = 0; track < inputFlux.Info.Cylinders; track++)
|
||||
{
|
||||
for(uint head = 0; head < inputFlux.Info.Heads; head++)
|
||||
{
|
||||
ErrorNumber error = inputFlux.SubTrackLength(head, track, out byte subTrackLen);
|
||||
|
||||
if(error != ErrorNumber.NoError) continue;
|
||||
|
||||
for(byte subTrackIndex = 0; subTrackIndex < subTrackLen; subTrackIndex++)
|
||||
{
|
||||
error = inputFlux.CapturesLength(head, track, subTrackIndex, out uint capturesLen);
|
||||
|
||||
if(error != ErrorNumber.NoError) continue;
|
||||
|
||||
for(uint captureIndex = 0; captureIndex < capturesLen; captureIndex++)
|
||||
{
|
||||
inputFlux.ReadFluxCapture(head,
|
||||
track,
|
||||
subTrackIndex,
|
||||
captureIndex,
|
||||
out ulong indexResolution,
|
||||
out ulong dataResolution,
|
||||
out byte[] indexBuffer,
|
||||
out byte[] dataBuffer);
|
||||
|
||||
outputFlux.WriteFluxCapture(indexResolution,
|
||||
dataResolution,
|
||||
indexBuffer,
|
||||
dataBuffer,
|
||||
head,
|
||||
track,
|
||||
subTrackIndex,
|
||||
captureIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
175
Aaru.Core/Image/Merge/Format.cs
Normal file
175
Aaru.Core/Image/Merge/Format.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
(ErrorNumber error, IMediaImage inputFormat) GetInputImage(string imagePath)
|
||||
{
|
||||
// Identify input file filter (determines file type handler)
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
PulseProgress?.Invoke(UI.Identifying_file_filter);
|
||||
|
||||
IFilter inputFilter = PluginRegister.Singleton.GetFilter(imagePath);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
if(inputFilter == null)
|
||||
{
|
||||
AaruLogging.Error(UI.Cannot_open_specified_file);
|
||||
|
||||
return (ErrorNumber.CannotOpenFile, null);
|
||||
}
|
||||
|
||||
// Identify input image format
|
||||
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Identifying_image_format);
|
||||
|
||||
IBaseImage baseImage = ImageFormat.Detect(inputFilter);
|
||||
var inputFormat = baseImage as IMediaImage;
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
|
||||
if(baseImage == null)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Input_image_format_not_identified);
|
||||
|
||||
return (ErrorNumber.UnrecognizedFormat, null);
|
||||
}
|
||||
|
||||
if(inputFormat == null)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Command_not_yet_supported_for_this_image_type);
|
||||
|
||||
return (ErrorNumber.InvalidArgument, null);
|
||||
}
|
||||
|
||||
UpdateStatus?.Invoke(string.Format(UI.Input_image_format_identified_by_0, inputFormat.Name));
|
||||
|
||||
try
|
||||
{
|
||||
// Open the input image file for reading
|
||||
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Invoke_Opening_image_file);
|
||||
|
||||
ErrorNumber opened = inputFormat.Open(inputFilter);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
if(opened != ErrorNumber.NoError)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Unable_to_open_image_format +
|
||||
Environment.NewLine +
|
||||
string.Format(Localization.Core.Error_0, opened));
|
||||
|
||||
return (opened, null);
|
||||
}
|
||||
|
||||
// Get media type and handle obsolete type mappings for backwards compatibility
|
||||
MediaType mediaType = inputFormat.Info.MediaType;
|
||||
|
||||
// Obsolete types
|
||||
#pragma warning disable 612
|
||||
mediaType = mediaType switch
|
||||
{
|
||||
MediaType.SQ1500 => MediaType.SyJet,
|
||||
MediaType.Bernoulli => MediaType.Bernoulli10,
|
||||
MediaType.Bernoulli2 => MediaType.BernoulliBox2_20,
|
||||
_ => inputFormat.Info.MediaType
|
||||
};
|
||||
#pragma warning restore 612
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, UI.Correctly_opened_image_file);
|
||||
|
||||
// Log image statistics for debugging
|
||||
AaruLogging.Debug(MODULE_NAME, UI.Image_without_headers_is_0_bytes, inputFormat.Info.ImageSize);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, UI.Image_has_0_sectors, inputFormat.Info.Sectors);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, UI.Image_identifies_media_type_as_0, mediaType);
|
||||
|
||||
Statistics.AddMediaFormat(inputFormat.Format);
|
||||
Statistics.AddMedia(mediaType, false);
|
||||
Statistics.AddFilter(inputFilter.Name);
|
||||
|
||||
return (ErrorNumber.NoError, inputFormat);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Unable_to_open_image_format +
|
||||
Environment.NewLine +
|
||||
string.Format(Localization.Core.Error_0, ex.Message));
|
||||
|
||||
AaruLogging.Exception(ex, Localization.Core.Error_0, ex.Message);
|
||||
|
||||
return (ErrorNumber.CannotOpenFormat, null);
|
||||
}
|
||||
}
|
||||
|
||||
private IWritableImage FindOutputFormat(PluginRegister plugins, string format, string outputPath)
|
||||
{
|
||||
// Discovers output format plugin by extension, GUID, or name
|
||||
// Searches writable format plugins matching any of three methods:
|
||||
// 1. By file extension (if format not specified)
|
||||
// 2. By plugin GUID (if format is valid GUID)
|
||||
// 3. By plugin name (case-insensitive string match)
|
||||
// Returns null if no match or multiple matches found
|
||||
|
||||
List<IBaseWritableImage> candidates = [];
|
||||
|
||||
// Try extension
|
||||
if(string.IsNullOrEmpty(format))
|
||||
{
|
||||
candidates.AddRange(from plugin in plugins.WritableImages.Values
|
||||
where plugin is not null
|
||||
where plugin.KnownExtensions.Contains(Path.GetExtension(outputPath))
|
||||
select plugin);
|
||||
}
|
||||
|
||||
// Try Id
|
||||
else if(Guid.TryParse(format, CultureInfo.CurrentCulture, out Guid outId))
|
||||
{
|
||||
candidates.AddRange(from plugin in plugins.WritableImages.Values
|
||||
where plugin is not null
|
||||
where plugin.Id == outId
|
||||
select plugin);
|
||||
}
|
||||
|
||||
// Try name
|
||||
else
|
||||
{
|
||||
candidates.AddRange(from plugin in plugins.WritableImages.Values
|
||||
where plugin is not null
|
||||
where plugin.Name.Equals(format, StringComparison.InvariantCultureIgnoreCase)
|
||||
select plugin);
|
||||
}
|
||||
|
||||
switch(candidates.Count)
|
||||
{
|
||||
case 0:
|
||||
StoppingErrorMessage?.Invoke(UI.No_plugin_supports_requested_extension);
|
||||
|
||||
return null;
|
||||
case > 1:
|
||||
StoppingErrorMessage?.Invoke(UI.More_than_one_plugin_supports_requested_extension);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return candidates[0] as IWritableImage;
|
||||
}
|
||||
}
|
||||
531
Aaru.Core/Image/Merge/Merger.cs
Normal file
531
Aaru.Core/Image/Merge/Merger.cs
Normal file
@@ -0,0 +1,531 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Metadata;
|
||||
using Aaru.Localization;
|
||||
using File = System.IO.File;
|
||||
using MediaType = Aaru.CommonTypes.MediaType;
|
||||
using TapeFile = Aaru.CommonTypes.Structs.TapeFile;
|
||||
using TapePartition = Aaru.CommonTypes.Structs.TapePartition;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
[SuppressMessage("Philips Naming", "PH2082:Positive Naming")]
|
||||
public sealed partial class Merger
|
||||
(
|
||||
string primaryImagePath,
|
||||
string secondaryImagePath,
|
||||
string outputImagePath,
|
||||
bool useSecondaryTags,
|
||||
string sectorsFile,
|
||||
bool ignoreMediaType,
|
||||
string comments,
|
||||
int count,
|
||||
string creator,
|
||||
string driveManufacturer,
|
||||
string driveModel,
|
||||
string driveFirmwareRevision,
|
||||
string driveSerialNumber,
|
||||
string format,
|
||||
string mediaBarcode,
|
||||
int lastMediaSequence,
|
||||
string mediaManufacturer,
|
||||
string mediaModel,
|
||||
string mediaPartNumber,
|
||||
int mediaSequence,
|
||||
string mediaSerialNumber,
|
||||
string mediaTitle,
|
||||
Dictionary<string, string> options,
|
||||
string primaryResumeFile,
|
||||
string secondaryResumeFile,
|
||||
string geometry,
|
||||
bool fixSubchannelPosition,
|
||||
bool fixSubchannel,
|
||||
bool fixSubchannelCrc,
|
||||
bool generateSubchannels,
|
||||
bool decrypt,
|
||||
bool ignoreNegativeSectors,
|
||||
bool ignoreOverflowSectors
|
||||
)
|
||||
{
|
||||
const string MODULE_NAME = "Image merger";
|
||||
bool _aborted;
|
||||
readonly PluginRegister _plugins = PluginRegister.Singleton;
|
||||
|
||||
public ErrorNumber Start()
|
||||
{
|
||||
// Validate sector count parameter
|
||||
if(count == 0)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Need_to_specify_more_than_zero_sectors_to_copy_at_once);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
// Parse and validate CHS geometry if specified
|
||||
(bool success, uint cylinders, uint heads, uint sectors)? geometryResult = ParseGeometry(geometry);
|
||||
(uint cylinders, uint heads, uint sectors)? geometryValues = null;
|
||||
|
||||
if(geometryResult is not null)
|
||||
{
|
||||
if(!geometryResult.Value.success) return ErrorNumber.InvalidArgument;
|
||||
|
||||
geometryValues = (geometryResult.Value.cylinders, geometryResult.Value.heads, geometryResult.Value.sectors);
|
||||
}
|
||||
|
||||
// Load resume information from sidecar files
|
||||
|
||||
(bool success, Resume primaryResume) = LoadMetadata(primaryResumeFile);
|
||||
|
||||
if(!success) return ErrorNumber.InvalidArgument;
|
||||
|
||||
(success, Resume secondaryResume) = LoadMetadata(secondaryResumeFile);
|
||||
|
||||
if(!success) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Verify output file doesn't already exist
|
||||
if(File.Exists(outputImagePath))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Output_file_already_exists);
|
||||
|
||||
return ErrorNumber.FileExists;
|
||||
}
|
||||
|
||||
(ErrorNumber errno, IMediaImage primaryImage) = GetInputImage(primaryImagePath);
|
||||
|
||||
if(primaryImage is null) return errno;
|
||||
|
||||
(errno, IMediaImage secondaryImage) = GetInputImage(secondaryImagePath);
|
||||
|
||||
if(secondaryImage is null) return errno;
|
||||
|
||||
// Get media type and handle obsolete type mappings for backwards compatibility
|
||||
MediaType primaryMediaType = primaryImage.Info.MediaType;
|
||||
|
||||
// Obsolete types
|
||||
#pragma warning disable 612
|
||||
primaryMediaType = primaryMediaType switch
|
||||
{
|
||||
MediaType.SQ1500 => MediaType.SyJet,
|
||||
MediaType.Bernoulli => MediaType.Bernoulli10,
|
||||
MediaType.Bernoulli2 => MediaType.BernoulliBox2_20,
|
||||
_ => primaryImage.Info.MediaType
|
||||
};
|
||||
#pragma warning restore 612
|
||||
|
||||
MediaType secondaryMediaType = secondaryImage.Info.MediaType;
|
||||
|
||||
// Obsolete types
|
||||
#pragma warning disable 612
|
||||
secondaryMediaType = secondaryMediaType switch
|
||||
{
|
||||
MediaType.SQ1500 => MediaType.SyJet,
|
||||
MediaType.Bernoulli => MediaType.Bernoulli10,
|
||||
MediaType.Bernoulli2 => MediaType.BernoulliBox2_20,
|
||||
_ => secondaryImage.Info.MediaType
|
||||
};
|
||||
#pragma warning restore 612
|
||||
|
||||
if(!ignoreMediaType && primaryMediaType != secondaryMediaType)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Images_have_different_media_types_cannot_merge);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
// Discover and load output format plugin
|
||||
IWritableImage outputFormat = FindOutputFormat(PluginRegister.Singleton, format, outputImagePath);
|
||||
|
||||
if(outputFormat == null) return ErrorNumber.FormatNotFound;
|
||||
|
||||
UpdateStatus?.Invoke(string.Format(UI.Output_image_format_0, outputFormat.Name));
|
||||
|
||||
if(primaryImage.Info.Sectors != secondaryImage.Info.Sectors)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Images_have_different_number_of_sectors_cannot_merge);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
errno = ValidateMediaCapabilities(primaryImage, secondaryImage, outputFormat, primaryMediaType);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Validate sector tags compatibility between formats
|
||||
errno = ValidateSectorTags(primaryImage, outputFormat, out bool useLong);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Check and setup tape image support if needed
|
||||
var primaryTape = primaryImage as ITapeImage;
|
||||
var secondaryTape = secondaryImage as ITapeImage;
|
||||
var outputTape = outputFormat as IWritableTapeImage;
|
||||
|
||||
errno = ValidateTapeImage(primaryTape, secondaryTape, outputTape);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Parsing_sectors_file);
|
||||
|
||||
(List<ulong> overrideSectorsList, List<uint> overrideNegativeSectorsList) =
|
||||
ParseOverrideSectorsList(sectorsFile);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
if(overrideNegativeSectorsList.Contains(0))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Sectors_file_contains_invalid_sector_number_0_not_continuing);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
uint nominalNegativeSectors = 0;
|
||||
uint nominalOverflowSectors = 0;
|
||||
|
||||
if(!ignoreNegativeSectors)
|
||||
{
|
||||
nominalNegativeSectors = primaryImage.Info.NegativeSectors;
|
||||
|
||||
if(secondaryImage.Info.NegativeSectors > nominalNegativeSectors)
|
||||
nominalNegativeSectors = secondaryImage.Info.NegativeSectors;
|
||||
}
|
||||
|
||||
if(!ignoreOverflowSectors)
|
||||
{
|
||||
nominalOverflowSectors = primaryImage.Info.OverflowSectors;
|
||||
|
||||
if(secondaryImage.Info.OverflowSectors > nominalOverflowSectors)
|
||||
nominalOverflowSectors = secondaryImage.Info.OverflowSectors;
|
||||
}
|
||||
|
||||
// Check if any override sectors is bigger than the biggest overflow sector
|
||||
ulong maxAllowedSector = primaryImage.Info.Sectors - 1 + nominalOverflowSectors;
|
||||
|
||||
if(overrideSectorsList.Count > 0 && overrideSectorsList.Max() > maxAllowedSector)
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(string.Format(UI
|
||||
.Sectors_file_contains_sector_0_which_exceeds_the_maximum_allowed_sector_1_not_continuing,
|
||||
overrideSectorsList.Max(),
|
||||
maxAllowedSector));
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Calculating_sectors_to_merge);
|
||||
|
||||
List<ulong> sectorsToCopyFromSecondImage =
|
||||
CalculateSectorsToCopy(primaryImage, secondaryImage, primaryResume, secondaryResume, overrideSectorsList);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
if(sectorsToCopyFromSecondImage.Count == 0)
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(UI.No_sectors_to_merge__output_image_will_be_identical_to_primary_image_not_continuing);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
errno = SetupTapeImage(primaryTape, secondaryTape, outputTape);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Validate optical media capabilities (sessions, hidden tracks, etc.)
|
||||
if((outputFormat as IWritableOpticalImage)?.OpticalCapabilities.HasFlag(OpticalImageCapabilities
|
||||
.CanStoreSessions) !=
|
||||
true &&
|
||||
(primaryImage as IOpticalMediaImage)?.Sessions?.Count > 1)
|
||||
{
|
||||
// TODO: Disabled until 6.0
|
||||
/*if(!_force)
|
||||
{*/
|
||||
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_sessions);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
/*}
|
||||
|
||||
StoppingErrorMessage?.Invoke("Output format does not support sessions, this will end in a loss of data, continuing...");*/
|
||||
}
|
||||
|
||||
// Check for hidden tracks support in optical media
|
||||
if((outputFormat as IWritableOpticalImage)?.OpticalCapabilities.HasFlag(OpticalImageCapabilities
|
||||
.CanStoreHiddenTracks) !=
|
||||
true &&
|
||||
(primaryImage as IOpticalMediaImage)?.Tracks?.Any(static t => t.Sequence == 0) == true)
|
||||
{
|
||||
// TODO: Disabled until 6.0
|
||||
/*if(!_force)
|
||||
{*/
|
||||
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_hidden_tracks);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
/*}
|
||||
|
||||
StoppingErrorMessage?.Invoke("Output format does not support sessions, this will end in a loss of data, continuing...");*/
|
||||
}
|
||||
|
||||
// Create the output image file with appropriate settings
|
||||
errno = CreateOutputImage(primaryImage,
|
||||
primaryMediaType,
|
||||
outputFormat,
|
||||
nominalNegativeSectors,
|
||||
nominalOverflowSectors);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Set image metadata in the output file
|
||||
errno = SetImageMetadata(primaryImage, secondaryImage, outputFormat);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Prepare metadata and dump hardware information
|
||||
Metadata metadata = primaryImage.AaruMetadata;
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Calculating_merged_dump_hardware_list);
|
||||
|
||||
List<DumpHardware> dumpHardware =
|
||||
CalculateMergedDumpHardware(primaryImage,
|
||||
secondaryImage,
|
||||
primaryResume,
|
||||
secondaryResume,
|
||||
sectorsToCopyFromSecondImage);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
// Convert media tags from input to output format
|
||||
errno = CopyMediaTags(primaryImage, secondaryImage, outputFormat);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
UpdateStatus?.Invoke(string.Format(UI.Copying_0_sectors_from_primary_image, primaryImage.Info.Sectors));
|
||||
|
||||
// Perform the actual data conversion from input to output image
|
||||
if(primaryImage is IOpticalMediaImage primaryOptical &&
|
||||
secondaryImage is IOpticalMediaImage secondaryOptical &&
|
||||
outputFormat is IWritableOpticalImage outputOptical &&
|
||||
primaryOptical.Tracks != null)
|
||||
{
|
||||
errno = CopyOptical(primaryOptical, secondaryOptical, outputOptical, useLong, sectorsToCopyFromSecondImage);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(primaryTape == null || outputTape == null || !primaryTape.IsTape)
|
||||
{
|
||||
(uint cylinders, uint heads, uint sectors) chs =
|
||||
geometryValues != null
|
||||
? (geometryValues.Value.cylinders, geometryValues.Value.heads, geometryValues.Value.sectors)
|
||||
: (primaryImage.Info.Cylinders, primaryImage.Info.Heads, primaryImage.Info.SectorsPerTrack);
|
||||
|
||||
UpdateStatus?.Invoke(string.Format(UI.Setting_geometry_to_0_cylinders_1_heads_and_2_sectors_per_track,
|
||||
chs.cylinders,
|
||||
chs.heads,
|
||||
chs.sectors));
|
||||
|
||||
if(!outputFormat.SetGeometry(chs.cylinders, chs.heads, chs.sectors))
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_setting_geometry_image_may_be_incorrect_continuing,
|
||||
outputFormat.ErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
errno = CopySectorsPrimary(useLong, primaryTape?.IsTape == true, primaryImage, outputFormat);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
errno = CopySectorsTagPrimary(useLong, primaryImage, outputFormat);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
UpdateStatus?.Invoke(string.Format(UI.Will_copy_0_sectors_from_secondary_image,
|
||||
sectorsToCopyFromSecondImage.Count));
|
||||
|
||||
errno = CopySectorsSecondary(useLong,
|
||||
primaryTape?.IsTape == true,
|
||||
primaryImage,
|
||||
outputFormat,
|
||||
sectorsToCopyFromSecondImage);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
errno = CopySectorsTagSecondary(useLong, primaryImage, outputFormat, sectorsToCopyFromSecondImage);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
if(primaryImage is IFluxImage inputFlux && outputFormat is IWritableFluxImage outputFlux)
|
||||
{
|
||||
UpdateStatus?.Invoke(UI.Flux_data_will_be_copied_as_is_from_primary_image);
|
||||
errno = CopyFlux(inputFlux, outputFlux);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
|
||||
if(primaryTape != null && outputTape != null && primaryTape.IsTape)
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
var currentFile = 0;
|
||||
|
||||
foreach(TapeFile tapeFile in primaryTape.Files)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_file_0_of_partition_1,
|
||||
tapeFile.File,
|
||||
tapeFile.Partition),
|
||||
currentFile + 1,
|
||||
primaryTape.Files.Count);
|
||||
|
||||
outputTape.AddFile(tapeFile);
|
||||
currentFile++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
InitProgress?.Invoke();
|
||||
var currentPartition = 0;
|
||||
|
||||
foreach(TapePartition tapePartition in primaryTape.TapePartitions)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tape_partition_0, tapePartition.Number),
|
||||
currentPartition + 1,
|
||||
primaryTape.TapePartitions.Count);
|
||||
|
||||
outputTape.AddPartition(tapePartition);
|
||||
currentPartition++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
if(nominalNegativeSectors > 0)
|
||||
{
|
||||
errno = CopyNegativeSectorsPrimary(useLong,
|
||||
primaryImage,
|
||||
outputFormat,
|
||||
nominalNegativeSectors,
|
||||
overrideNegativeSectorsList);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
errno = CopyNegativeSectorsSecondary(useLong, secondaryImage, outputFormat, overrideNegativeSectorsList);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
|
||||
if(nominalOverflowSectors > 0)
|
||||
{
|
||||
var overrideOverflowSectorsList = overrideSectorsList
|
||||
.Where(sector => sector >= primaryImage.Info.Sectors)
|
||||
.ToList();
|
||||
|
||||
errno = CopyOverflowSectorsPrimary(useLong,
|
||||
primaryImage,
|
||||
outputFormat,
|
||||
nominalOverflowSectors,
|
||||
overrideOverflowSectorsList);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
errno = CopyOverflowSectorsSecondary(useLong, secondaryImage, outputFormat, overrideOverflowSectorsList);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
|
||||
bool ret;
|
||||
|
||||
if(dumpHardware != null && !_aborted)
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
|
||||
PulseProgress?.Invoke(UI.Writing_dump_hardware_list);
|
||||
|
||||
ret = outputFormat.SetDumpHardware(dumpHardware);
|
||||
|
||||
if(ret) UpdateStatus?.Invoke(UI.Written_dump_hardware_list_to_output_image);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
if(metadata != null && !_aborted)
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Writing_metadata);
|
||||
|
||||
ret = outputFormat.SetMetadata(metadata);
|
||||
|
||||
if(ret) UpdateStatus?.Invoke(UI.Written_Aaru_Metadata_to_output_image);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
if(_aborted)
|
||||
{
|
||||
UpdateStatus?.Invoke(UI.Operation_canceled_the_output_file_is_not_correct);
|
||||
|
||||
return ErrorNumber.Canceled;
|
||||
}
|
||||
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Closing_output_image);
|
||||
bool closed = outputFormat.Close();
|
||||
EndProgress?.Invoke();
|
||||
|
||||
if(!closed)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_closing_output_image_Contents_are_not_correct,
|
||||
outputFormat.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
UpdateStatus?.Invoke(UI.Merge_completed_successfully);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Event raised when the progress bar is no longer needed</summary>
|
||||
public event EndProgressHandler EndProgress;
|
||||
|
||||
/// <summary>Event raised when a progress bar is needed</summary>
|
||||
public event InitProgressHandler InitProgress;
|
||||
|
||||
/// <summary>Event raised to report status updates</summary>
|
||||
public event UpdateStatusHandler UpdateStatus;
|
||||
|
||||
/// <summary>Event raised to report a non-fatal error</summary>
|
||||
public event ErrorMessageHandler ErrorMessage;
|
||||
|
||||
/// <summary>Event raised to report a fatal error that stops the dumping operation and should call user's attention</summary>
|
||||
public event ErrorMessageHandler StoppingErrorMessage;
|
||||
|
||||
/// <summary>Event raised to update the values of a determinate progress bar</summary>
|
||||
public event UpdateProgressHandler UpdateProgress;
|
||||
|
||||
/// <summary>Event raised to update the status of an indeterminate progress bar</summary>
|
||||
public event PulseProgressHandler PulseProgress;
|
||||
|
||||
/// <summary>Event raised when the progress bar is no longer needed</summary>
|
||||
public event EndProgressHandler2 EndProgress2;
|
||||
|
||||
/// <summary>Event raised when a progress bar is needed</summary>
|
||||
public event InitProgressHandler2 InitProgress2;
|
||||
|
||||
/// <summary>Event raised to update the values of a determinate progress bar</summary>
|
||||
public event UpdateProgressHandler2 UpdateProgress2;
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
_aborted = true;
|
||||
}
|
||||
}
|
||||
118
Aaru.Core/Image/Merge/Metadata.cs
Normal file
118
Aaru.Core/Image/Merge/Metadata.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Xml.Serialization;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Metadata;
|
||||
using Aaru.Localization;
|
||||
using Aaru.Logging;
|
||||
using Version = Aaru.CommonTypes.Interop.Version;
|
||||
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
private (bool success, Resume resume) LoadMetadata(string resumeFilePath)
|
||||
{
|
||||
Resume resume;
|
||||
|
||||
if(resumeFilePath == null) return (true, null);
|
||||
|
||||
if(File.Exists(resumeFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
if(resumeFilePath.EndsWith(".resume.json", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
var fs = new FileStream(resumeFilePath, FileMode.Open);
|
||||
|
||||
resume =
|
||||
(JsonSerializer.Deserialize(fs, typeof(ResumeJson), ResumeJsonContext.Default) as ResumeJson)
|
||||
?.Resume;
|
||||
|
||||
fs.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bypassed by JSON source generator used above
|
||||
#pragma warning disable IL2026
|
||||
var xs = new XmlSerializer(typeof(Resume));
|
||||
#pragma warning restore IL2026
|
||||
|
||||
var sr = new StreamReader(resumeFilePath);
|
||||
|
||||
// Bypassed by JSON source generator used above
|
||||
#pragma warning disable IL2026
|
||||
resume = (Resume)xs.Deserialize(sr);
|
||||
#pragma warning restore IL2026
|
||||
|
||||
sr.Close();
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Incorrect_resume_file_not_continuing);
|
||||
AaruLogging.Exception(ex, UI.Incorrect_resume_file_not_continuing);
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Could_not_find_resume_file);
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
return (true, resume);
|
||||
}
|
||||
|
||||
ErrorNumber SetImageMetadata(IMediaImage primaryImage, IMediaImage secondaryImage, IWritableImage outputImage)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
var imageInfo = new CommonTypes.Structs.ImageInfo
|
||||
{
|
||||
Application = "Aaru",
|
||||
ApplicationVersion = Version.GetInformationalVersion(),
|
||||
Comments = comments ?? primaryImage.Info.Comments ?? secondaryImage.Info.Comments,
|
||||
Creator = creator ?? primaryImage.Info.Creator ?? secondaryImage.Info.Creator,
|
||||
DriveFirmwareRevision =
|
||||
driveFirmwareRevision ??
|
||||
primaryImage.Info.DriveFirmwareRevision ?? secondaryImage.Info.DriveFirmwareRevision,
|
||||
DriveManufacturer =
|
||||
driveManufacturer ?? primaryImage.Info.DriveManufacturer ?? secondaryImage.Info.DriveManufacturer,
|
||||
DriveModel = driveModel ?? primaryImage.Info.DriveModel ?? secondaryImage.Info.DriveModel,
|
||||
DriveSerialNumber =
|
||||
driveSerialNumber ?? primaryImage.Info.DriveSerialNumber ?? secondaryImage.Info.DriveSerialNumber,
|
||||
LastMediaSequence = lastMediaSequence != 0
|
||||
? lastMediaSequence
|
||||
: primaryImage.Info.LastMediaSequence != 0
|
||||
? primaryImage.Info.LastMediaSequence
|
||||
: secondaryImage.Info.LastMediaSequence,
|
||||
MediaBarcode = mediaBarcode ?? primaryImage.Info.MediaBarcode ?? secondaryImage.Info.MediaBarcode,
|
||||
MediaManufacturer =
|
||||
mediaManufacturer ?? primaryImage.Info.MediaManufacturer ?? secondaryImage.Info.MediaManufacturer,
|
||||
MediaModel = mediaModel ?? primaryImage.Info.MediaModel ?? secondaryImage.Info.MediaModel,
|
||||
MediaPartNumber =
|
||||
mediaPartNumber ?? primaryImage.Info.MediaPartNumber ?? secondaryImage.Info.MediaPartNumber,
|
||||
MediaSequence = mediaSequence != 0
|
||||
? mediaSequence
|
||||
: primaryImage.Info.MediaSequence != 0
|
||||
? primaryImage.Info.MediaSequence
|
||||
: secondaryImage.Info.MediaSequence,
|
||||
MediaSerialNumber =
|
||||
mediaSerialNumber ?? primaryImage.Info.MediaSerialNumber ?? secondaryImage.Info.MediaSerialNumber,
|
||||
MediaTitle = mediaTitle ?? primaryImage.Info.MediaTitle ?? secondaryImage.Info.MediaTitle
|
||||
};
|
||||
|
||||
if(outputImage.SetImageInfo(imageInfo)) return ErrorNumber.NoError;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_setting_metadata_not_continuing,
|
||||
outputImage.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
891
Aaru.Core/Image/Merge/Optical.cs
Normal file
891
Aaru.Core/Image/Merge/Optical.cs
Normal file
@@ -0,0 +1,891 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Core.Media;
|
||||
using Aaru.Decryption.DVD;
|
||||
using Aaru.Devices;
|
||||
using Aaru.Localization;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
ErrorNumber CopyOptical(IOpticalMediaImage primaryOptical, IOpticalMediaImage secondaryOptical,
|
||||
IWritableOpticalImage outputOptical, bool useLong, List<ulong> sectorsToCopyFromSecondImage)
|
||||
{
|
||||
if(!outputOptical.SetTracks(primaryOptical.Tracks))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_sending_tracks_list_to_output_image,
|
||||
outputOptical.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
if(decrypt) UpdateStatus?.Invoke(UI.Decrypting_encrypted_sectors);
|
||||
|
||||
// Convert all sectors track by track
|
||||
ErrorNumber errno = CopyOpticalSectorsPrimary(primaryOptical, outputOptical, useLong);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
Dictionary<byte, string> isrcs = [];
|
||||
Dictionary<byte, byte> trackFlags = [];
|
||||
string mcn = null;
|
||||
HashSet<int> subchannelExtents = [];
|
||||
Dictionary<byte, int> smallestPregapLbaPerTrack = [];
|
||||
var tracks = new Track[primaryOptical.Tracks.Count];
|
||||
|
||||
for(var i = 0; i < tracks.Length; i++)
|
||||
{
|
||||
tracks[i] = new Track
|
||||
{
|
||||
Indexes = [],
|
||||
Description = primaryOptical.Tracks[i].Description,
|
||||
EndSector = primaryOptical.Tracks[i].EndSector,
|
||||
StartSector = primaryOptical.Tracks[i].StartSector,
|
||||
Pregap = primaryOptical.Tracks[i].Pregap,
|
||||
Sequence = primaryOptical.Tracks[i].Sequence,
|
||||
Session = primaryOptical.Tracks[i].Session,
|
||||
BytesPerSector = primaryOptical.Tracks[i].BytesPerSector,
|
||||
RawBytesPerSector = primaryOptical.Tracks[i].RawBytesPerSector,
|
||||
Type = primaryOptical.Tracks[i].Type,
|
||||
SubchannelType = primaryOptical.Tracks[i].SubchannelType
|
||||
};
|
||||
|
||||
foreach(KeyValuePair<ushort, int> idx in primaryOptical.Tracks[i].Indexes)
|
||||
tracks[i].Indexes[idx.Key] = idx.Value;
|
||||
}
|
||||
|
||||
// Gets tracks ISRCs
|
||||
foreach(SectorTagType tag in primaryOptical.Info.ReadableSectorTags
|
||||
.Where(static t => t == SectorTagType.CdTrackIsrc)
|
||||
.Order())
|
||||
{
|
||||
foreach(Track track in tracks)
|
||||
{
|
||||
errno = primaryOptical.ReadSectorTag(track.Sequence, false, tag, out byte[] isrc);
|
||||
|
||||
if(errno != ErrorNumber.NoError) continue;
|
||||
|
||||
isrcs[(byte)track.Sequence] = Encoding.UTF8.GetString(isrc);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets tracks flags
|
||||
foreach(SectorTagType tag in primaryOptical.Info.ReadableSectorTags
|
||||
.Where(static t => t == SectorTagType.CdTrackFlags)
|
||||
.Order())
|
||||
{
|
||||
foreach(Track track in tracks)
|
||||
{
|
||||
errno = primaryOptical.ReadSectorTag(track.Sequence, false, tag, out byte[] flags);
|
||||
|
||||
if(errno != ErrorNumber.NoError) continue;
|
||||
|
||||
trackFlags[(byte)track.Sequence] = flags[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Gets subchannel extents
|
||||
for(ulong s = 0; s < primaryOptical.Info.Sectors; s++)
|
||||
{
|
||||
if(s > int.MaxValue) break;
|
||||
|
||||
subchannelExtents.Add((int)s);
|
||||
}
|
||||
|
||||
errno = CopyOpticalSectorsTagsPrimary(primaryOptical,
|
||||
outputOptical,
|
||||
useLong,
|
||||
isrcs,
|
||||
ref mcn,
|
||||
tracks,
|
||||
subchannelExtents,
|
||||
smallestPregapLbaPerTrack);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Write ISRCs
|
||||
foreach(KeyValuePair<byte, string> isrc in isrcs)
|
||||
{
|
||||
outputOptical.WriteSectorTag(Encoding.UTF8.GetBytes(isrc.Value),
|
||||
isrc.Key,
|
||||
false,
|
||||
SectorTagType.CdTrackIsrc);
|
||||
}
|
||||
|
||||
// Write track flags
|
||||
if(trackFlags.Count > 0)
|
||||
{
|
||||
foreach((byte track, byte flags) in trackFlags)
|
||||
outputOptical.WriteSectorTag([flags], track, false, SectorTagType.CdTrackFlags);
|
||||
}
|
||||
|
||||
// Write MCN
|
||||
if(mcn != null) outputOptical.WriteMediaTag(Encoding.UTF8.GetBytes(mcn), MediaTagType.CD_MCN);
|
||||
|
||||
UpdateStatus?.Invoke(string.Format(UI.Will_copy_0_sectors_from_secondary_image,
|
||||
sectorsToCopyFromSecondImage.Count));
|
||||
|
||||
// Copy sectors from secondary image
|
||||
errno = CopyOpticalSectorsSecondary(secondaryOptical, outputOptical, useLong, sectorsToCopyFromSecondImage);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
errno = CopyOpticalSectorsTagsSecondary(secondaryOptical,
|
||||
outputOptical,
|
||||
useLong,
|
||||
isrcs,
|
||||
ref mcn,
|
||||
tracks,
|
||||
subchannelExtents,
|
||||
smallestPregapLbaPerTrack,
|
||||
sectorsToCopyFromSecondImage);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
|
||||
if(!IsCompactDiscMedia(primaryOptical.Info.MediaType) || !generateSubchannels) return ErrorNumber.NoError;
|
||||
|
||||
// Generate subchannel data
|
||||
CompactDisc.GenerateSubchannels(subchannelExtents,
|
||||
tracks,
|
||||
trackFlags,
|
||||
primaryOptical.Info.Sectors,
|
||||
null,
|
||||
InitProgress,
|
||||
UpdateProgress,
|
||||
EndProgress,
|
||||
outputOptical);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber CopyOpticalSectorsPrimary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
|
||||
bool useLong)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
InitProgress2?.Invoke();
|
||||
byte[] generatedTitleKeys = null;
|
||||
var currentTrack = 0;
|
||||
|
||||
foreach(Track track in inputOptical.Tracks)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_sectors_in_track_0_of_1,
|
||||
currentTrack + 1,
|
||||
inputOptical.Tracks.Count),
|
||||
currentTrack,
|
||||
inputOptical.Tracks.Count);
|
||||
|
||||
ulong doneSectors = 0;
|
||||
ulong trackSectors = track.EndSector - track.StartSector + 1;
|
||||
|
||||
while(doneSectors < trackSectors)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
byte[] sector;
|
||||
|
||||
uint sectorsToDo;
|
||||
|
||||
if(trackSectors - doneSectors >= (ulong)count)
|
||||
sectorsToDo = (uint)count;
|
||||
else
|
||||
sectorsToDo = (uint)(trackSectors - doneSectors);
|
||||
|
||||
UpdateProgress2?.Invoke(string.Format(UI.Copying_sectors_0_to_1_in_track_2,
|
||||
doneSectors + track.StartSector,
|
||||
doneSectors + sectorsToDo + track.StartSector,
|
||||
track.Sequence),
|
||||
(long)doneSectors,
|
||||
(long)trackSectors);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus = SectorStatus.NotDumped;
|
||||
var sectorStatusArray = new SectorStatus[1];
|
||||
ErrorNumber errno;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = sectorsToDo == 1
|
||||
? inputOptical.ReadSectorLong(doneSectors + track.StartSector,
|
||||
false,
|
||||
out sector,
|
||||
out sectorStatus)
|
||||
: inputOptical.ReadSectorsLong(doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
out sector,
|
||||
out sectorStatusArray);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
? outputOptical.WriteSectorLong(sector,
|
||||
doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorStatus)
|
||||
: outputOptical.WriteSectorsLong(sector,
|
||||
doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
sectorStatusArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
if(!result && sector.Length % 2352 != 0)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Input_image_is_not_returning_long_sectors_not_continuing);
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = sectorsToDo == 1
|
||||
? inputOptical.ReadSector(doneSectors + track.StartSector,
|
||||
false,
|
||||
out sector,
|
||||
out sectorStatus)
|
||||
: inputOptical.ReadSectors(doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
out sector,
|
||||
out sectorStatusArray);
|
||||
|
||||
// TODO: Move to generic place when anything but CSS DVDs can be decrypted
|
||||
if(IsDvdMedia(inputOptical.Info.MediaType) && decrypt)
|
||||
{
|
||||
DecryptDvdSector(ref sector,
|
||||
inputOptical,
|
||||
doneSectors + track.StartSector,
|
||||
sectorsToDo,
|
||||
_plugins,
|
||||
ref generatedTitleKeys);
|
||||
}
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
? outputOptical.WriteSector(sector,
|
||||
doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorStatus)
|
||||
: outputOptical.WriteSectors(sector,
|
||||
doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
sectorStatusArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
}
|
||||
|
||||
currentTrack++;
|
||||
}
|
||||
|
||||
EndProgress2?.Invoke();
|
||||
EndProgress?.Invoke();
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber CopyOpticalSectorsSecondary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
|
||||
bool useLong, List<ulong> sectorsToCopy)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
byte[] generatedTitleKeys = null;
|
||||
int howManySectorsToCopy = sectorsToCopy.Count(t => t < inputOptical.Info.Sectors);
|
||||
var howManySectorsCopied = 0;
|
||||
|
||||
foreach(ulong sectorAddress in sectorsToCopy.Where(t => t < inputOptical.Info.Sectors)
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_sector_0, sectorAddress),
|
||||
howManySectorsCopied,
|
||||
howManySectorsToCopy);
|
||||
|
||||
if(_aborted) break;
|
||||
|
||||
byte[] sector;
|
||||
bool result;
|
||||
SectorStatus sectorStatus;
|
||||
ErrorNumber errno;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = inputOptical.ReadSectorLong(sectorAddress, false, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputOptical.WriteSectorLong(sector, sectorAddress, false, sectorStatus);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
if(!result && sector.Length % 2352 != 0)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Input_image_is_not_returning_long_sectors_not_continuing);
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
errno = inputOptical.ReadSector(sectorAddress, false, out sector, out sectorStatus);
|
||||
|
||||
// TODO: Move to generic place when anything but CSS DVDs can be decrypted
|
||||
if(IsDvdMedia(inputOptical.Info.MediaType) && decrypt)
|
||||
DecryptDvdSector(ref sector, inputOptical, sectorAddress, 1, _plugins, ref generatedTitleKeys);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputOptical.WriteSector(sector, sectorAddress, false, sectorStatus);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
howManySectorsCopied++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber CopyOpticalSectorsTagsPrimary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
|
||||
bool useLong, Dictionary<byte, string> isrcs, ref string mcn,
|
||||
Track[] tracks, HashSet<int> subchannelExtents,
|
||||
Dictionary<byte, int> smallestPregapLbaPerTrack)
|
||||
{
|
||||
foreach(SectorTagType tag in inputOptical.Info.ReadableSectorTags.Order()
|
||||
.TakeWhile(_ => useLong)
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
case SectorTagType.DvdSectorCmi:
|
||||
case SectorTagType.DvdSectorTitleKey:
|
||||
case SectorTagType.DvdSectorEdc:
|
||||
case SectorTagType.DvdSectorIed:
|
||||
case SectorTagType.DvdSectorInformation:
|
||||
case SectorTagType.DvdSectorNumber:
|
||||
// This tags are inline in long sector
|
||||
continue;
|
||||
}
|
||||
|
||||
InitProgress?.Invoke();
|
||||
InitProgress2?.Invoke();
|
||||
var currentTrack = 0;
|
||||
|
||||
foreach(Track track in inputOptical.Tracks)
|
||||
{
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tags_in_track_0_of_1,
|
||||
currentTrack + 1,
|
||||
inputOptical.Tracks.Count),
|
||||
currentTrack,
|
||||
inputOptical.Tracks.Count);
|
||||
|
||||
ulong doneSectors = 0;
|
||||
ulong trackSectors = track.EndSector - track.StartSector + 1;
|
||||
byte[] sector;
|
||||
bool result;
|
||||
|
||||
ErrorNumber errno;
|
||||
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.CdTrackFlags:
|
||||
case SectorTagType.CdTrackIsrc:
|
||||
errno = inputOptical.ReadSectorTag(track.Sequence, false, tag, out sector);
|
||||
|
||||
switch(errno)
|
||||
{
|
||||
case ErrorNumber.NoData:
|
||||
|
||||
continue;
|
||||
case ErrorNumber.NoError:
|
||||
result = outputOptical.WriteSectorTag(sector, track.Sequence, false, tag);
|
||||
|
||||
break;
|
||||
default:
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_not_continuing,
|
||||
outputOptical.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_not_continuing,
|
||||
outputOptical.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
while(doneSectors < trackSectors)
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
uint sectorsToDo;
|
||||
|
||||
if(trackSectors - doneSectors >= (ulong)count)
|
||||
sectorsToDo = (uint)count;
|
||||
else
|
||||
sectorsToDo = (uint)(trackSectors - doneSectors);
|
||||
|
||||
UpdateProgress2?.Invoke(string.Format(UI.Copying_tag_3_for_sectors_0_to_1_in_track_2,
|
||||
doneSectors + track.StartSector,
|
||||
doneSectors + sectorsToDo + track.StartSector,
|
||||
track.Sequence,
|
||||
tag),
|
||||
(long)(doneSectors + track.StartSector),
|
||||
(long)(doneSectors + sectorsToDo + track.StartSector));
|
||||
|
||||
if(sectorsToDo == 1)
|
||||
{
|
||||
errno = inputOptical.ReadSectorTag(doneSectors + track.StartSector, false, tag, out sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(tag == SectorTagType.CdSectorSubchannel)
|
||||
{
|
||||
bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw,
|
||||
MmcSubchannel.Raw,
|
||||
sector,
|
||||
doneSectors + track.StartSector,
|
||||
1,
|
||||
null,
|
||||
isrcs,
|
||||
(byte)track.Sequence,
|
||||
ref mcn,
|
||||
tracks,
|
||||
subchannelExtents,
|
||||
fixSubchannelPosition,
|
||||
outputOptical,
|
||||
fixSubchannel,
|
||||
fixSubchannelCrc,
|
||||
null,
|
||||
smallestPregapLbaPerTrack,
|
||||
false,
|
||||
out _);
|
||||
|
||||
if(indexesChanged) outputOptical.SetTracks(tracks.ToList());
|
||||
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = outputOptical.WriteSectorTag(sector,
|
||||
doneSectors + track.StartSector,
|
||||
false,
|
||||
tag);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = inputOptical.ReadSectorsTag(doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
tag,
|
||||
out sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(tag == SectorTagType.CdSectorSubchannel)
|
||||
{
|
||||
bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw,
|
||||
MmcSubchannel.Raw,
|
||||
sector,
|
||||
doneSectors + track.StartSector,
|
||||
sectorsToDo,
|
||||
null,
|
||||
isrcs,
|
||||
(byte)track.Sequence,
|
||||
ref mcn,
|
||||
tracks,
|
||||
subchannelExtents,
|
||||
fixSubchannelPosition,
|
||||
outputOptical,
|
||||
fixSubchannel,
|
||||
fixSubchannelCrc,
|
||||
null,
|
||||
smallestPregapLbaPerTrack,
|
||||
false,
|
||||
out _);
|
||||
|
||||
if(indexesChanged) outputOptical.SetTracks(tracks.ToList());
|
||||
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = outputOptical.WriteSectorsTag(sector,
|
||||
doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
tag);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_for_sector_1_not_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
}
|
||||
|
||||
currentTrack++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
EndProgress2?.Invoke();
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber CopyOpticalSectorsTagsSecondary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
|
||||
bool useLong, Dictionary<byte, string> isrcs, ref string mcn,
|
||||
Track[] tracks, HashSet<int> subchannelExtents,
|
||||
Dictionary<byte, int> smallestPregapLbaPerTrack,
|
||||
List<ulong> sectorsToCopy)
|
||||
{
|
||||
foreach(SectorTagType tag in inputOptical.Info.ReadableSectorTags.Order()
|
||||
.TakeWhile(_ => useLong)
|
||||
.TakeWhile(_ => !_aborted))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
case SectorTagType.DvdSectorCmi:
|
||||
case SectorTagType.DvdSectorTitleKey:
|
||||
case SectorTagType.DvdSectorEdc:
|
||||
case SectorTagType.DvdSectorIed:
|
||||
case SectorTagType.DvdSectorInformation:
|
||||
case SectorTagType.DvdSectorNumber:
|
||||
// This tags are inline in long sector
|
||||
continue;
|
||||
}
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
|
||||
int numberOfSectorsToCopy = sectorsToCopy.Count(t => t < inputOptical.Info.Sectors);
|
||||
var currentSectorIndex = 0;
|
||||
|
||||
foreach(ulong sectorAddress in sectorsToCopy.Where(t => t < inputOptical.Info.Sectors))
|
||||
{
|
||||
if(_aborted) break;
|
||||
|
||||
Track track = inputOptical.Tracks.FirstOrDefault(t => t.StartSector <= sectorAddress &&
|
||||
t.EndSector >= sectorAddress);
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tag_0_for_sector_1, tag, sectorAddress),
|
||||
currentSectorIndex,
|
||||
numberOfSectorsToCopy);
|
||||
|
||||
ErrorNumber errno = inputOptical.ReadSectorTag(sectorAddress, false, tag, out byte[] sector);
|
||||
|
||||
bool result;
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(tag == SectorTagType.CdSectorSubchannel)
|
||||
{
|
||||
bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw,
|
||||
MmcSubchannel.Raw,
|
||||
sector,
|
||||
sectorAddress,
|
||||
1,
|
||||
null,
|
||||
isrcs,
|
||||
(byte)track.Sequence,
|
||||
ref mcn,
|
||||
tracks,
|
||||
subchannelExtents,
|
||||
fixSubchannelPosition,
|
||||
outputOptical,
|
||||
fixSubchannel,
|
||||
fixSubchannelCrc,
|
||||
null,
|
||||
smallestPregapLbaPerTrack,
|
||||
false,
|
||||
out _);
|
||||
|
||||
if(indexesChanged) outputOptical.SetTracks(tracks.ToList());
|
||||
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
result = outputOptical.WriteSectorTag(sector, sectorAddress, false, tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_for_sector_1_not_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
currentSectorIndex++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts DVD sectors using CSS (Content Scramble System) decryption
|
||||
/// Retrieves decryption keys from sector tags or generates them from ISO9660 filesystem
|
||||
/// Only MPEG packets within sectors can be encrypted
|
||||
/// </summary>
|
||||
void DecryptDvdSector(ref byte[] sector, IOpticalMediaImage inputOptical, ulong sectorAddress, uint sectorsToDo,
|
||||
PluginRegister plugins, ref byte[] generatedTitleKeys)
|
||||
{
|
||||
if(_aborted) return;
|
||||
|
||||
// Only sectors which are MPEG packets can be encrypted.
|
||||
if(!Mpeg.ContainsMpegPackets(sector, sectorsToDo)) return;
|
||||
|
||||
byte[] cmi, titleKey;
|
||||
|
||||
if(sectorsToDo == 1)
|
||||
{
|
||||
if(inputOptical.ReadSectorTag(sectorAddress, false, SectorTagType.DvdSectorCmi, out cmi) ==
|
||||
ErrorNumber.NoError &&
|
||||
inputOptical.ReadSectorTag(sectorAddress, false, SectorTagType.DvdTitleKeyDecrypted, out titleKey) ==
|
||||
ErrorNumber.NoError)
|
||||
sector = CSS.DecryptSector(sector, titleKey, cmi);
|
||||
else
|
||||
{
|
||||
if(generatedTitleKeys == null) GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys);
|
||||
|
||||
if(generatedTitleKeys != null)
|
||||
{
|
||||
sector = CSS.DecryptSector(sector,
|
||||
generatedTitleKeys.Skip((int)(5 * sectorAddress)).Take(5).ToArray(),
|
||||
null);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(inputOptical.ReadSectorsTag(sectorAddress, false, sectorsToDo, SectorTagType.DvdSectorCmi, out cmi) ==
|
||||
ErrorNumber.NoError &&
|
||||
inputOptical.ReadSectorsTag(sectorAddress,
|
||||
false,
|
||||
sectorsToDo,
|
||||
SectorTagType.DvdTitleKeyDecrypted,
|
||||
out titleKey) ==
|
||||
ErrorNumber.NoError)
|
||||
sector = CSS.DecryptSector(sector, titleKey, cmi, sectorsToDo);
|
||||
else
|
||||
{
|
||||
if(generatedTitleKeys == null) GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys);
|
||||
|
||||
if(generatedTitleKeys != null)
|
||||
{
|
||||
sector = CSS.DecryptSector(sector,
|
||||
generatedTitleKeys.Skip((int)(5 * sectorAddress))
|
||||
.Take((int)(5 * sectorsToDo))
|
||||
.ToArray(),
|
||||
null,
|
||||
sectorsToDo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates DVD CSS title keys from ISO9660 filesystem
|
||||
/// Used when explicit title keys are not available in sector tags
|
||||
/// Searches for ISO9660 partitions to derive decryption keys
|
||||
/// </summary>
|
||||
void GenerateDvdTitleKeys(IOpticalMediaImage inputOptical, PluginRegister plugins, ref byte[] generatedTitleKeys)
|
||||
{
|
||||
if(_aborted) return;
|
||||
|
||||
List<Partition> partitions = Partitions.GetAll(inputOptical);
|
||||
|
||||
partitions = partitions.FindAll(p =>
|
||||
{
|
||||
Filesystems.Identify(inputOptical, out List<string> idPlugins, p);
|
||||
|
||||
return idPlugins.Contains("iso9660 filesystem");
|
||||
});
|
||||
|
||||
if(!plugins.ReadOnlyFilesystems.TryGetValue("iso9660 filesystem", out IReadOnlyFilesystem rofs)) return;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, UI.Generating_decryption_keys);
|
||||
|
||||
generatedTitleKeys = CSS.GenerateTitleKeys(inputOptical, partitions, inputOptical.Info.Sectors, rofs);
|
||||
}
|
||||
|
||||
static bool IsDvdMedia(MediaType mediaType) =>
|
||||
|
||||
// Checks if media type is any variant of DVD (ROM, R, RDL, PR, PRDL)
|
||||
// Consolidates media type checking logic used throughout conversion process
|
||||
mediaType is MediaType.DVDROM or MediaType.DVDR or MediaType.DVDRDL or MediaType.DVDPR or MediaType.DVDPRDL;
|
||||
|
||||
private static bool IsCompactDiscMedia(MediaType mediaType) =>
|
||||
|
||||
// Checks if media type is any variant of compact disc (CD, CDDA, CDR, CDRW, etc.)
|
||||
// Covers all 45+ CD-based media types including gaming and specialty formats
|
||||
mediaType is MediaType.CD
|
||||
or MediaType.CDDA
|
||||
or MediaType.CDG
|
||||
or MediaType.CDEG
|
||||
or MediaType.CDI
|
||||
or MediaType.CDROM
|
||||
or MediaType.CDROMXA
|
||||
or MediaType.CDPLUS
|
||||
or MediaType.CDMO
|
||||
or MediaType.CDR
|
||||
or MediaType.CDRW
|
||||
or MediaType.CDMRW
|
||||
or MediaType.VCD
|
||||
or MediaType.SVCD
|
||||
or MediaType.PCD
|
||||
or MediaType.DTSCD
|
||||
or MediaType.CDMIDI
|
||||
or MediaType.CDV
|
||||
or MediaType.CDIREADY
|
||||
or MediaType.FMTOWNS
|
||||
or MediaType.PS1CD
|
||||
or MediaType.PS2CD
|
||||
or MediaType.MEGACD
|
||||
or MediaType.SATURNCD
|
||||
or MediaType.GDROM
|
||||
or MediaType.GDR
|
||||
or MediaType.MilCD
|
||||
or MediaType.SuperCDROM2
|
||||
or MediaType.JaguarCD
|
||||
or MediaType.ThreeDO
|
||||
or MediaType.PCFX
|
||||
or MediaType.NeoGeoCD
|
||||
or MediaType.CDTV
|
||||
or MediaType.CD32
|
||||
or MediaType.Playdia
|
||||
or MediaType.Pippin
|
||||
or MediaType.VideoNow
|
||||
or MediaType.VideoNowColor
|
||||
or MediaType.VideoNowXp
|
||||
or MediaType.CVD;
|
||||
}
|
||||
40
Aaru.Core/Image/Merge/Parser.cs
Normal file
40
Aaru.Core/Image/Merge/Parser.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
static (List<ulong> overrideSectors, List<uint> overrideNegativeSectors) ParseOverrideSectorsList(
|
||||
string overrideSectorsListPath)
|
||||
{
|
||||
if(overrideSectorsListPath is null) return ([], []);
|
||||
|
||||
List<ulong> overrideSectors = [];
|
||||
List<uint> overrideNegativeSectors = [];
|
||||
|
||||
StreamReader sr = new(overrideSectorsListPath);
|
||||
|
||||
while(sr.ReadLine() is {} line)
|
||||
{
|
||||
line = line.Trim();
|
||||
|
||||
if(line.Length == 0) continue;
|
||||
|
||||
if(line.StartsWith('-'))
|
||||
{
|
||||
if(long.TryParse(line[1..], CultureInfo.InvariantCulture, out long negativeSector))
|
||||
overrideNegativeSectors.Add((uint)(negativeSector * -1));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ulong.TryParse(line, CultureInfo.InvariantCulture, out ulong sector)) overrideSectors.Add(sector);
|
||||
}
|
||||
}
|
||||
|
||||
sr.Close();
|
||||
|
||||
return (overrideSectors, overrideNegativeSectors);
|
||||
}
|
||||
}
|
||||
325
Aaru.Core/Image/Merge/Sectors.cs
Normal file
325
Aaru.Core/Image/Merge/Sectors.cs
Normal file
@@ -0,0 +1,325 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
ErrorNumber CopySectorsPrimary(bool useLong, bool isTape, IMediaImage primaryImage, IWritableImage outputImage)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
ulong doneSectors = 0;
|
||||
|
||||
while(doneSectors < primaryImage.Info.Sectors)
|
||||
{
|
||||
byte[] sector;
|
||||
|
||||
uint sectorsToDo;
|
||||
|
||||
if(isTape)
|
||||
sectorsToDo = 1;
|
||||
else if(primaryImage.Info.Sectors - doneSectors >= (ulong)count)
|
||||
sectorsToDo = (uint)count;
|
||||
else
|
||||
sectorsToDo = (uint)(primaryImage.Info.Sectors - doneSectors);
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_sectors_0_to_1, doneSectors, doneSectors + sectorsToDo),
|
||||
(long)doneSectors,
|
||||
(long)primaryImage.Info.Sectors);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus = SectorStatus.NotDumped;
|
||||
var sectorStatusArray = new SectorStatus[1];
|
||||
|
||||
ErrorNumber errno;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = sectorsToDo == 1
|
||||
? primaryImage.ReadSectorLong(doneSectors, false, out sector, out sectorStatus)
|
||||
: primaryImage.ReadSectorsLong(doneSectors,
|
||||
false,
|
||||
sectorsToDo,
|
||||
out sector,
|
||||
out sectorStatusArray);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
? outputImage.WriteSectorLong(sector, doneSectors, false, sectorStatus)
|
||||
: outputImage.WriteSectorsLong(sector,
|
||||
doneSectors,
|
||||
false,
|
||||
sectorsToDo,
|
||||
sectorStatusArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = sectorsToDo == 1
|
||||
? primaryImage.ReadSector(doneSectors, false, out sector, out sectorStatus)
|
||||
: primaryImage.ReadSectors(doneSectors,
|
||||
false,
|
||||
sectorsToDo,
|
||||
out sector,
|
||||
out sectorStatusArray);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
? outputImage.WriteSector(sector, doneSectors, false, sectorStatus)
|
||||
: outputImage.WriteSectors(sector, doneSectors, false, sectorsToDo, sectorStatusArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
doneSectors));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber CopySectorsTagPrimary(bool useLong, IMediaImage primaryImage, IWritableImage outputImage)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
foreach(SectorTagType tag in primaryImage.Info.ReadableSectorTags.TakeWhile(_ => useLong))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
ulong doneSectors = 0;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
while(doneSectors < primaryImage.Info.Sectors)
|
||||
{
|
||||
uint sectorsToDo;
|
||||
|
||||
if(primaryImage.Info.Sectors - doneSectors >= (ulong)count)
|
||||
sectorsToDo = (uint)count;
|
||||
else
|
||||
sectorsToDo = (uint)(primaryImage.Info.Sectors - doneSectors);
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tag_2_for_sectors_0_to_1,
|
||||
doneSectors,
|
||||
doneSectors + sectorsToDo,
|
||||
tag),
|
||||
(long)doneSectors,
|
||||
(long)primaryImage.Info.Sectors);
|
||||
|
||||
bool result;
|
||||
|
||||
ErrorNumber errno = sectorsToDo == 1
|
||||
? primaryImage.ReadSectorTag(doneSectors, false, tag, out byte[] sector)
|
||||
: primaryImage.ReadSectorsTag(doneSectors, false, sectorsToDo, tag, out sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
? outputImage.WriteSectorTag(sector, doneSectors, false, tag)
|
||||
: outputImage.WriteSectorsTag(sector, doneSectors, false, sectorsToDo, tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
doneSectors));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber CopySectorsSecondary(bool useLong, bool isTape, IMediaImage secondaryImage, IWritableImage outputImage,
|
||||
List<ulong> sectorsToCopy)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
var doneSectors = 0;
|
||||
int totalSectorsToCopy = sectorsToCopy.Count(t => t < secondaryImage.Info.Sectors);
|
||||
|
||||
foreach(ulong sectorAddress in sectorsToCopy.Where(t => t < secondaryImage.Info.Sectors))
|
||||
{
|
||||
byte[] sector;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_sector_0, sectorAddress), doneSectors, totalSectorsToCopy);
|
||||
|
||||
bool result;
|
||||
|
||||
ErrorNumber errno;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = secondaryImage.ReadSectorLong(sectorAddress, false, out sector, out SectorStatus sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputImage.WriteSectorLong(sector, sectorAddress, false, sectorStatus);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = secondaryImage.ReadSector(sectorAddress, false, out sector, out SectorStatus sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputImage.WriteSector(sector, sectorAddress, false, sectorStatus);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
doneSectors));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
doneSectors++;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber CopySectorsTagSecondary(bool useLong, IMediaImage secondaryImage, IWritableImage outputImage,
|
||||
List<ulong> sectorsToCopy)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
foreach(SectorTagType tag in secondaryImage.Info.ReadableSectorTags.TakeWhile(_ => useLong))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var doneSectors = 0;
|
||||
int sectorsToCopyCount = sectorsToCopy.Count(t => t < secondaryImage.Info.Sectors);
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
foreach(ulong sectorAddress in sectorsToCopy.Where(t => t < secondaryImage.Info.Sectors))
|
||||
{
|
||||
UpdateProgress?.Invoke(string.Format(UI.Copying_tag_0_for_sector_1, tag, sectorAddress),
|
||||
doneSectors,
|
||||
sectorsToCopyCount);
|
||||
|
||||
bool result;
|
||||
|
||||
ErrorNumber errno = secondaryImage.ReadSectorTag(sectorAddress, false, tag, out byte[] sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = outputImage.WriteSectorTag(sector, sectorAddress, false, tag);
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
sectorAddress));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
outputImage.ErrorMessage,
|
||||
sectorAddress));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
doneSectors++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
63
Aaru.Core/Image/Merge/Tags.cs
Normal file
63
Aaru.Core/Image/Merge/Tags.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
using Humanizer;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
ErrorNumber CopyMediaTags(IMediaImage primaryImage, IMediaImage secondaryImage, IWritableImage outputFormat)
|
||||
{
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
foreach(MediaTagType mediaTag in primaryImage.Info.ReadableMediaTags)
|
||||
{
|
||||
PulseProgress?.Invoke(string.Format(UI.Copying_media_tag_0_from_primary_image, mediaTag.Humanize()));
|
||||
|
||||
ErrorNumber errno = primaryImage.ReadMediaTag(mediaTag, out byte[] tag);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_media_tag_not_continuing, errno));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
if(outputFormat?.WriteMediaTag(tag, mediaTag) == true) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_media_tag_not_continuing,
|
||||
outputFormat?.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
foreach(MediaTagType mediaTag in secondaryImage.Info.ReadableMediaTags)
|
||||
{
|
||||
if(!useSecondaryTags && primaryImage.Info.ReadableMediaTags.Contains(mediaTag)) continue;
|
||||
|
||||
PulseProgress?.Invoke(string.Format(UI.Copying_media_tag_0_from_secondary_image, mediaTag.Humanize()));
|
||||
|
||||
ErrorNumber errno = secondaryImage.ReadMediaTag(mediaTag, out byte[] tag);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_media_tag_not_continuing, errno));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
if(outputFormat?.WriteMediaTag(tag, mediaTag) == true) continue;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_media_tag_not_continuing,
|
||||
outputFormat?.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
36
Aaru.Core/Image/Merge/Tape.cs
Normal file
36
Aaru.Core/Image/Merge/Tape.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public sealed partial class Merger
|
||||
{
|
||||
ErrorNumber ValidateTapeImage(ITapeImage primaryTape, ITapeImage secondaryTape, IWritableTapeImage outputTape)
|
||||
{
|
||||
if(_aborted || primaryTape?.IsTape != true && secondaryTape?.IsTape != true || outputTape is not null)
|
||||
return ErrorNumber.NoError;
|
||||
|
||||
StoppingErrorMessage?.Invoke(UI.Input_format_contains_a_tape_image_and_is_not_supported_by_output_format);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
}
|
||||
|
||||
ErrorNumber SetupTapeImage(ITapeImage primaryTape, ITapeImage secondaryTape, IWritableTapeImage outputTape)
|
||||
{
|
||||
if(_aborted || primaryTape?.IsTape != true && secondaryTape?.IsTape != true || outputTape == null)
|
||||
return ErrorNumber.NoError;
|
||||
|
||||
bool ret = outputTape.SetTape();
|
||||
|
||||
// Cannot set image to tape mode
|
||||
if(ret) return ErrorNumber.NoError;
|
||||
|
||||
StoppingErrorMessage?.Invoke(UI.Error_setting_output_image_in_tape_mode +
|
||||
Environment.NewLine +
|
||||
outputTape.ErrorMessage);
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
6
Aaru.Localization/Core.Designer.cs
generated
6
Aaru.Localization/Core.Designer.cs
generated
@@ -6898,5 +6898,11 @@ namespace Aaru.Localization {
|
||||
return ResourceManager.GetString("DVD_2nd_layer_Physical_Format_Information_contained_in_image_WithMarkup", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Media_tag_0_present_in_primary_image_will_be_lost {
|
||||
get {
|
||||
return ResourceManager.GetString("Media_tag_0_present_in_primary_image_will_be_lost", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3499,4 +3499,7 @@ No tiene sentido hacerlo y supondría demasiado esfuerzo para la cinta.</value>
|
||||
<data name="DVD_2nd_layer_Physical_Format_Information_contained_in_image_WithMarkup" xml:space="preserve">
|
||||
<value>[bold][blue]Información del formato físico del DVD (2ª capa) contenida en la imagen:[/][/]</value>
|
||||
</data>
|
||||
<data name="Media_tag_0_present_in_primary_image_will_be_lost" xml:space="preserve">
|
||||
<value>[red]La etiqueta del medio {0} presente en la imagen primaria se perderá en el formato de salida. No se continuará...[/]</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -3520,4 +3520,7 @@ It has no sense to do it, and it will put too much strain on the tape.</value>
|
||||
<data name="DVD_2nd_layer_Physical_Format_Information_contained_in_image_WithMarkup" xml:space="preserve">
|
||||
<value>[bold][blue]DVD 2nd layer Physical Format Information contained in image:[/][/]</value>
|
||||
</data>
|
||||
<data name="Media_tag_0_present_in_primary_image_will_be_lost" xml:space="preserve">
|
||||
<value>[red]Media tag {0} present in primary image will be lost in output format. Not continuing...[/]</value>
|
||||
</data>
|
||||
</root>
|
||||
266
Aaru.Localization/UI.Designer.cs
generated
266
Aaru.Localization/UI.Designer.cs
generated
@@ -10509,5 +10509,271 @@ namespace Aaru.Localization {
|
||||
return ResourceManager.GetString("Use_long_sectors", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Image_Merge_Command_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Image_Merge_Command_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Path_to_the_primary_image_file {
|
||||
get {
|
||||
return ResourceManager.GetString("Path_to_the_primary_image_file", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Path_to_the_secondary_image_file {
|
||||
get {
|
||||
return ResourceManager.GetString("Path_to_the_secondary_image_file", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Path_to_the_output_merged_image_file {
|
||||
get {
|
||||
return ResourceManager.GetString("Path_to_the_output_merged_image_file", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Use_media_tags_from_secondary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("Use_media_tags_from_secondary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string File_containing_list_of_sectors_to_take_from_secondary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("File_containing_list_of_sectors_to_take_from_secondary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Ignore_mismatched_image_media_type {
|
||||
get {
|
||||
return ResourceManager.GetString("Ignore_mismatched_image_media_type", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Resume_file_for_primary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("Resume_file_for_primary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Resume_file_for_secondary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("Resume_file_for_secondary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Media_tag_0_present_in_primary_image_will_be_lost_in_output_format {
|
||||
get {
|
||||
return ResourceManager.GetString("Media_tag_0_present_in_primary_image_will_be_lost_in_output_format", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Media_tag_0_present_in_secondary_image_will_be_lost_in_output_format {
|
||||
get {
|
||||
return ResourceManager.GetString("Media_tag_0_present_in_secondary_image_will_be_lost_in_output_format", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Output_image_does_not_support_sector_tag_0_data_will_be_lost {
|
||||
get {
|
||||
return ResourceManager.GetString("Output_image_does_not_support_sector_tag_0_data_will_be_lost", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_negative_sector_0_of_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_negative_sector_0_of_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_tag_1_for_negative_sector_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_tag_1_for_negative_sector_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_negative_sector_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_negative_sector_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_overflow_sector_0_of_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_overflow_sector_0_of_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_tag_1_for_overflow_sector_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_tag_1_for_overflow_sector_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_overflow_sector_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_overflow_sector_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Images_have_different_media_types_cannot_merge {
|
||||
get {
|
||||
return ResourceManager.GetString("Images_have_different_media_types_cannot_merge", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Images_have_different_number_of_sectors_cannot_merge {
|
||||
get {
|
||||
return ResourceManager.GetString("Images_have_different_number_of_sectors_cannot_merge", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Parsing_sectors_file {
|
||||
get {
|
||||
return ResourceManager.GetString("Parsing_sectors_file", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Sectors_file_contains_invalid_sector_number_0_not_continuing {
|
||||
get {
|
||||
return ResourceManager.GetString("Sectors_file_contains_invalid_sector_number_0_not_continuing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Sectors_file_contains_sector_0_which_exceeds_the_maximum_allowed_sector_1_not_continuing {
|
||||
get {
|
||||
return ResourceManager.GetString("Sectors_file_contains_sector_0_which_exceeds_the_maximum_allowed_sector_1_not_con" +
|
||||
"tinuing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Calculating_sectors_to_merge {
|
||||
get {
|
||||
return ResourceManager.GetString("Calculating_sectors_to_merge", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string No_sectors_to_merge__output_image_will_be_identical_to_primary_image_not_continuing {
|
||||
get {
|
||||
return ResourceManager.GetString("No_sectors_to_merge__output_image_will_be_identical_to_primary_image_not_continui" +
|
||||
"ng", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Calculating_merged_dump_hardware_list {
|
||||
get {
|
||||
return ResourceManager.GetString("Calculating_merged_dump_hardware_list", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_0_sectors_from_primary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_0_sectors_from_primary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Will_copy_0_sectors_from_secondary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("Will_copy_0_sectors_from_secondary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Flux_data_will_be_copied_as_is_from_primary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("Flux_data_will_be_copied_as_is_from_primary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_file_0_of_partition_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_file_0_of_partition_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_tape_partition_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_tape_partition_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Merge_completed_successfully {
|
||||
get {
|
||||
return ResourceManager.GetString("Merge_completed_successfully", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Decrypting_encrypted_sectors {
|
||||
get {
|
||||
return ResourceManager.GetString("Decrypting_encrypted_sectors", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_sectors_in_track_0_of_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_sectors_in_track_0_of_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_sectors_0_to_1_in_track_2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_sectors_0_to_1_in_track_2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Input_image_is_not_returning_long_sectors_not_continuing {
|
||||
get {
|
||||
return ResourceManager.GetString("Input_image_is_not_returning_long_sectors_not_continuing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_sector_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_sector_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_tags_in_track_0_of_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_tags_in_track_0_of_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_tag_3_for_sectors_0_to_1_in_track_2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_tag_3_for_sectors_0_to_1_in_track_2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_tag_0_for_sector_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_tag_0_for_sector_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_sectors_0_to_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_sectors_0_to_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_tag_2_for_sectors_0_to_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_tag_2_for_sectors_0_to_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_media_tag_0_from_primary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_media_tag_0_from_primary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Copying_media_tag_0_from_secondary_image {
|
||||
get {
|
||||
return ResourceManager.GetString("Copying_media_tag_0_from_secondary_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5255,4 +5255,138 @@ Probadores:
|
||||
<data name="Use_long_sectors" xml:space="preserve">
|
||||
<value>Usar sectores largos (con etiquetas).</value>
|
||||
</data>
|
||||
<data name="Image_Merge_Command_Description" xml:space="preserve">
|
||||
<value>Combina dos imágenes en una</value>
|
||||
</data>
|
||||
<data name="Path_to_the_primary_image_file" xml:space="preserve">
|
||||
<value>Ruta al archivo de imagen primario.</value>
|
||||
</data>
|
||||
<data name="Path_to_the_secondary_image_file" xml:space="preserve">
|
||||
<value>Ruta al archivo de imagen secundario.</value>
|
||||
</data>
|
||||
<data name="Path_to_the_output_merged_image_file" xml:space="preserve">
|
||||
<value>Ruta al archivo de imagen combinado.</value>
|
||||
</data>
|
||||
<data name="Use_media_tags_from_secondary_image" xml:space="preserve">
|
||||
<value>Usa las etiquetas del medio de la imagen secundaria (de otra forma cuando estén en ambas imágenes, se usarán las de la primaria).</value>
|
||||
</data>
|
||||
<data name="File_containing_list_of_sectors_to_take_from_secondary_image" xml:space="preserve">
|
||||
<value>Archivo que contiene la lista de sectores a obtener de la imagen secundaria (un sector por línea).</value>
|
||||
</data>
|
||||
<data name="Ignore_mismatched_image_media_type" xml:space="preserve">
|
||||
<value>Ignorar los tipos de medio no coincidentes. La imagen combinada tendrá el tipo de medio de la imagen primaria.</value>
|
||||
</data>
|
||||
<data name="Resume_file_for_primary_image" xml:space="preserve">
|
||||
<value>Fichero de resumen para la imagen primaria</value>
|
||||
</data>
|
||||
<data name="Resume_file_for_secondary_image" xml:space="preserve">
|
||||
<value>Fichero de resumen para la imagen secundaria</value>
|
||||
</data>
|
||||
<data name="Media_tag_0_present_in_primary_image_will_be_lost_in_output_format" xml:space="preserve">
|
||||
<value>[red]La etiqueta del medio [orange3]{0}[/] presente en la imagen primaria se perderá en el formato de salida. No se continuará...[/]</value>
|
||||
</data>
|
||||
<data name="Media_tag_0_present_in_secondary_image_will_be_lost_in_output_format" xml:space="preserve">
|
||||
<value>[red]La etiqueta del medio [orange3]{0}[/] presente en la imagen secundaria se perderá en el formato de salida. No se continuará...[/]</value>
|
||||
</data>
|
||||
<data name="Output_image_does_not_support_sector_tag_0_data_will_be_lost" xml:space="preserve">
|
||||
<value>[red]La imagen de salida no soporta la etiqueta de sector [orange3]{0}[/], se perderán datos. No se continuará...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_negative_sector_0_of_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando sector [lime]-{0}[/] de [violet]-{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_1_for_negative_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando etiqueta [orange3]{1}[/] para el sector [lime]-{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_negative_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando sector [lime]-{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_overflow_sector_0_of_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando sector de sobrecarga [lime]{0}[/] de [violet]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_1_for_overflow_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando etiqueta [orange3]{1}[/] para el sector de sobrecarga [lime]{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_overflow_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando sector de sobrecarga [lime]{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Images_have_different_media_types_cannot_merge" xml:space="preserve">
|
||||
<value>[red]Las imágenes tienen diferentes tipos de medio, no se pueden combinar.[/]</value>
|
||||
</data>
|
||||
<data name="Images_have_different_number_of_sectors_cannot_merge" xml:space="preserve">
|
||||
<value>[red]Las imágenes tienen diferente número de sectores, no se pueden combinar.[/]</value>
|
||||
</data>
|
||||
<data name="Parsing_sectors_file" xml:space="preserve">
|
||||
<value>[slateblue1]Interpretando el fichero de sectores...[/]</value>
|
||||
</data>
|
||||
<data name="Sectors_file_contains_invalid_sector_number_0_not_continuing" xml:space="preserve">
|
||||
<value>[red]El fichero de sectores contiene el sector inválido 0, no se continuará...[/]</value>
|
||||
</data>
|
||||
<data name="Sectors_file_contains_sector_0_which_exceeds_the_maximum_allowed_sector_1_not_continuing"
|
||||
xml:space="preserve">
|
||||
<value>[red]El fichero de sectores contiene el sector [lime]{0}[/] que excede el sector máximo permitido [violet]{1}[/], no se continuará...[/]</value>
|
||||
</data>
|
||||
<data name="Calculating_sectors_to_merge" xml:space="preserve">
|
||||
<value>[slateblue1]Calculando sectores a combinar...[/]</value>
|
||||
</data>
|
||||
<data name="No_sectors_to_merge__output_image_will_be_identical_to_primary_image_not_continuing"
|
||||
xml:space="preserve">
|
||||
<value>[red]No hay sectores a combinar, la imagen de salida sería idéntica a la primaria, no se continuará...[/]</value>
|
||||
</data>
|
||||
<data name="Calculating_merged_dump_hardware_list" xml:space="preserve">
|
||||
<value>[slateblue1]Calculando list de hardware de volcado combinada...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_0_sectors_from_primary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando [teal]{0}[/] sectores de la imagen primaria[/]</value>
|
||||
</data>
|
||||
<data name="Will_copy_0_sectors_from_secondary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Se copiarán [teal]{0}[/] sectores de la imagen secundaria.[/]</value>
|
||||
</data>
|
||||
<data name="Flux_data_will_be_copied_as_is_from_primary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Los datos de flujo se copiarán tal cual de la imagen primaria...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_file_0_of_partition_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando fichero [lime]{0}[/] de partición [teal]{1}[/]...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_tape_partition_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando partición de cinta [teal]{0}[/]...[/]</value>
|
||||
</data>
|
||||
<data name="Merge_completed_successfully" xml:space="preserve">
|
||||
<value>[green]¡Combinación completada con éxito![/]</value>
|
||||
</data>
|
||||
<data name="Decrypting_encrypted_sectors" xml:space="preserve">
|
||||
<value>[green]Desencriptando sectores encriptados.[/]</value>
|
||||
</data>
|
||||
<data name="Copying_sectors_in_track_0_of_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando sectores en pista [teal]{0}[/] de [teal]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_sectors_0_to_1_in_track_2" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando sectores del [lime]{0}[/] al [violet]{1}[/] en la pista [teal]{2}[/][/]</value>
|
||||
</data>
|
||||
<data name="Input_image_is_not_returning_long_sectors_not_continuing" xml:space="preserve">
|
||||
<value>[red]El formato de imagen de entrada no está devolviendo sectores sin procesar, no se continuará...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando sector [lime]{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tags_in_track_0_of_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando etiquetas en la pista [teal]{0}[/] de [teal]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_3_for_sectors_0_to_1_in_track_2" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando etiqueta [orange3]{3}[/] para los sectores del [lime]{0}[/] al [violet]{1}[/] en la pista [teal]{2}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_0_for_sector_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando etiqueta [orange3]{0}[/] para el sector [lime]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_sectors_0_to_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando sectores del [lime]{0}[/] al [violet]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_2_for_sectors_0_to_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando etiqueta [orange3]{2}[/] para los sectores del [lime]{0}[/] al [violet]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_media_tag_0_from_primary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando etiqueta del medio [orange3]{0}[/] de la imagen primaria[/]</value>
|
||||
</data>
|
||||
<data name="Copying_media_tag_0_from_secondary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Copiando etiqueta del medio [orange3]{0}[/] de la imagen secundaria[/]</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -5333,4 +5333,138 @@ Do you want to continue?</value>
|
||||
<data name="Use_long_sectors" xml:space="preserve">
|
||||
<value>Use long sectors (with tags).</value>
|
||||
</data>
|
||||
<data name="Image_Merge_Command_Description" xml:space="preserve">
|
||||
<value>Merges two images into one</value>
|
||||
</data>
|
||||
<data name="Path_to_the_primary_image_file" xml:space="preserve">
|
||||
<value>Path to the primary image file.</value>
|
||||
</data>
|
||||
<data name="Path_to_the_secondary_image_file" xml:space="preserve">
|
||||
<value>Path to the secondary image file.</value>
|
||||
</data>
|
||||
<data name="Path_to_the_output_merged_image_file" xml:space="preserve">
|
||||
<value>Path to the output merged image file.</value>
|
||||
</data>
|
||||
<data name="Use_media_tags_from_secondary_image" xml:space="preserve">
|
||||
<value>Use media tags from secondary image (otherwise when in both images, primary image tags are used).</value>
|
||||
</data>
|
||||
<data name="File_containing_list_of_sectors_to_take_from_secondary_image" xml:space="preserve">
|
||||
<value>File containing list of sectors to take from secondary image (one sector number per line).</value>
|
||||
</data>
|
||||
<data name="Ignore_mismatched_image_media_type" xml:space="preserve">
|
||||
<value>Ignore mismatched image media type. Merged image will still have primary image media type.</value>
|
||||
</data>
|
||||
<data name="Resume_file_for_primary_image" xml:space="preserve">
|
||||
<value>Resume file for primary image</value>
|
||||
</data>
|
||||
<data name="Resume_file_for_secondary_image" xml:space="preserve">
|
||||
<value>Resume file for secondary image</value>
|
||||
</data>
|
||||
<data name="Media_tag_0_present_in_primary_image_will_be_lost_in_output_format" xml:space="preserve">
|
||||
<value>[red]Media tag [orange3]{0}[/] present in primary image will be lost in output format. Not continuing...[/]</value>
|
||||
</data>
|
||||
<data name="Media_tag_0_present_in_secondary_image_will_be_lost_in_output_format" xml:space="preserve">
|
||||
<value>[red]Media tag [orange3]{0}[/] present in secondary image will be lost in output format. Not continuing...[/]</value>
|
||||
</data>
|
||||
<data name="Output_image_does_not_support_sector_tag_0_data_will_be_lost" xml:space="preserve">
|
||||
<value>[red]Output image does not support sector tag [orange3]{0}[/], data will be lost. Not continuing...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_negative_sector_0_of_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copying sector [lime]-{0}[/] of [violet]-{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_1_for_negative_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copying tag [orange3]{1}[/] for sector [lime]-{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_negative_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copying sector [lime]-{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_overflow_sector_0_of_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copying overflow sector [lime]{0}[/] of [violet]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_1_for_overflow_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copying tag [orange3]{1}[/] for overflow sector [lime]{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_overflow_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copying overflow sector [lime]{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Images_have_different_media_types_cannot_merge" xml:space="preserve">
|
||||
<value>[red]Images have different media types, cannot merge.[/]</value>
|
||||
</data>
|
||||
<data name="Images_have_different_number_of_sectors_cannot_merge" xml:space="preserve">
|
||||
<value>[red]Images have different number of sectors, cannot merge.[/]</value>
|
||||
</data>
|
||||
<data name="Parsing_sectors_file" xml:space="preserve">
|
||||
<value>[slateblue1]Parsing sectors file...[/]</value>
|
||||
</data>
|
||||
<data name="Sectors_file_contains_invalid_sector_number_0_not_continuing" xml:space="preserve">
|
||||
<value>[red]Sectors file contains invalid sector number 0, not continuing...[/]</value>
|
||||
</data>
|
||||
<data name="Sectors_file_contains_sector_0_which_exceeds_the_maximum_allowed_sector_1_not_continuing"
|
||||
xml:space="preserve">
|
||||
<value>[red]Sectors file contains sector [lime]{0}[/] which exceeds the maximum allowed sector [violet]{1}[/], not continuing...[/]</value>
|
||||
</data>
|
||||
<data name="Calculating_sectors_to_merge" xml:space="preserve">
|
||||
<value>[slateblue1]Calculating sectors to merge...[/]</value>
|
||||
</data>
|
||||
<data name="No_sectors_to_merge__output_image_will_be_identical_to_primary_image_not_continuing"
|
||||
xml:space="preserve">
|
||||
<value>[red]No sectors to merge, output image will be identical to primary image, not continuing...[/]</value>
|
||||
</data>
|
||||
<data name="Calculating_merged_dump_hardware_list" xml:space="preserve">
|
||||
<value>[slateblue1]Calculating merged dump hardware list...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_0_sectors_from_primary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Copying [teal]{0}[/] sectors from primary image[/]</value>
|
||||
</data>
|
||||
<data name="Will_copy_0_sectors_from_secondary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Will copy [teal]{0}[/] sectors from secondary image.[/]</value>
|
||||
</data>
|
||||
<data name="Flux_data_will_be_copied_as_is_from_primary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Flux data will be copied as-is from primary image...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_file_0_of_partition_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copying file [lime]{0}[/] of partition [teal]{1}[/]...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_tape_partition_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copying tape partition [teal]{0}[/]...[/]</value>
|
||||
</data>
|
||||
<data name="Merge_completed_successfully" xml:space="preserve">
|
||||
<value>[green]Merge completed successfully![/]</value>
|
||||
</data>
|
||||
<data name="Decrypting_encrypted_sectors" xml:space="preserve">
|
||||
<value>[green]Decrypting encrypted sectors.[/]</value>
|
||||
</data>
|
||||
<data name="Copying_sectors_in_track_0_of_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copying sectors in track [teal]{0}[/] of [teal]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_sectors_0_to_1_in_track_2" xml:space="preserve">
|
||||
<value>[slateblue1]Copying sectors [lime]{0}[/] to [violet]{1}[/] in track [teal]{2}[/][/]</value>
|
||||
</data>
|
||||
<data name="Input_image_is_not_returning_long_sectors_not_continuing" xml:space="preserve">
|
||||
<value>[red]Input image is not returning raw sectors, not continuing...[/]</value>
|
||||
</data>
|
||||
<data name="Copying_sector_0" xml:space="preserve">
|
||||
<value>[slateblue1]Copying sector [lime]{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tags_in_track_0_of_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copying tags in track [teal]{0}[/] of [teal]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_3_for_sectors_0_to_1_in_track_2" xml:space="preserve">
|
||||
<value>[slateblue1]Copying tag [orange3]{3}[/] for sectors [lime]{0}[/] to [violet]{1}[/] in track [teal]{2}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_0_for_sector_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copying tag [orange3]{0}[/] for sector [lime]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_sectors_0_to_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copying sectors [lime]{0}[/] to [violet]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_tag_2_for_sectors_0_to_1" xml:space="preserve">
|
||||
<value>[slateblue1]Copying tag [orange3]{2}[/] for sectors [lime]{0}[/] to [violet]{1}[/][/]</value>
|
||||
</data>
|
||||
<data name="Copying_media_tag_0_from_primary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Copying media tag [orange3]{0}[/] from primary image[/]</value>
|
||||
</data>
|
||||
<data name="Copying_media_tag_0_from_secondary_image" xml:space="preserve">
|
||||
<value>[slateblue1]Copying media tag [orange3]{0}[/] from secondary image[/]</value>
|
||||
</data>
|
||||
</root>
|
||||
330
Aaru/Commands/Image/Merge.cs
Normal file
330
Aaru/Commands/Image/Merge.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Core;
|
||||
using Aaru.Core.Image;
|
||||
using Aaru.Localization;
|
||||
using Aaru.Logging;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Aaru.Commands.Image;
|
||||
|
||||
class MergeCommand : AsyncCommand<MergeCommand.Settings>
|
||||
{
|
||||
const string MODULE_NAME = "Merge-image command";
|
||||
static ProgressTask _progressTask1;
|
||||
static ProgressTask _progressTask2;
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
MainClass.PrintCopyright();
|
||||
|
||||
// Initialize subchannel fix flags with cascading dependencies
|
||||
bool fixSubchannel = settings.FixSubchannel;
|
||||
bool fixSubchannelCrc = settings.FixSubchannelCrc;
|
||||
bool fixSubchannelPosition = settings.FixSubchannelPosition;
|
||||
|
||||
if(fixSubchannelCrc) fixSubchannel = true;
|
||||
|
||||
if(fixSubchannel) fixSubchannelPosition = true;
|
||||
|
||||
Statistics.AddCommand("merge-image");
|
||||
|
||||
// Log all command parameters for debugging and auditing
|
||||
Dictionary<string, string> parsedOptions = Options.Parse(settings.Options);
|
||||
LogCommandParameters(settings, fixSubchannelPosition, fixSubchannel, fixSubchannelCrc, parsedOptions);
|
||||
|
||||
var merger = new Merger(settings.PrimaryImagePath,
|
||||
settings.SecondaryImagePath,
|
||||
settings.OutputImagePath,
|
||||
settings.UseSecondaryTags,
|
||||
settings.SectorsFile,
|
||||
settings.IgnoreMediaType,
|
||||
settings.Comments,
|
||||
settings.Count,
|
||||
settings.Creator,
|
||||
settings.DriveManufacturer,
|
||||
settings.DriveModel,
|
||||
settings.DriveFirmwareRevision,
|
||||
settings.DriveSerialNumber,
|
||||
settings.Format,
|
||||
settings.MediaBarcode,
|
||||
settings.LastMediaSequence,
|
||||
settings.MediaManufacturer,
|
||||
settings.MediaModel,
|
||||
settings.MediaPartNumber,
|
||||
settings.MediaSequence,
|
||||
settings.MediaSerialNumber,
|
||||
settings.MediaTitle,
|
||||
parsedOptions,
|
||||
settings.PrimaryResumeFile,
|
||||
settings.SecondaryResumeFile,
|
||||
settings.Geometry,
|
||||
fixSubchannelPosition,
|
||||
fixSubchannel,
|
||||
fixSubchannelCrc,
|
||||
settings.GenerateSubchannels,
|
||||
settings.Decrypt,
|
||||
settings.IgnoreNegativeSectors,
|
||||
settings.IgnoreOverflowSectors);
|
||||
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
|
||||
AnsiConsole.Progress()
|
||||
.AutoClear(true)
|
||||
.HideCompleted(true)
|
||||
.Columns(new ProgressBarColumn(), new PercentageColumn(), new TaskDescriptionColumn())
|
||||
.Start(ctx =>
|
||||
{
|
||||
merger.UpdateStatus += static text => AaruLogging.WriteLine(text);
|
||||
|
||||
merger.ErrorMessage += static text => AaruLogging.Error(text);
|
||||
|
||||
merger.StoppingErrorMessage += static text => AaruLogging.Error(text);
|
||||
|
||||
merger.UpdateProgress += (text, current, maximum) =>
|
||||
{
|
||||
_progressTask1 ??= ctx.AddTask("Progress");
|
||||
_progressTask1.Description = text;
|
||||
_progressTask1.Value = current;
|
||||
_progressTask1.MaxValue = maximum;
|
||||
};
|
||||
|
||||
merger.PulseProgress += text =>
|
||||
{
|
||||
if(_progressTask1 is null)
|
||||
ctx.AddTask(text).IsIndeterminate();
|
||||
else
|
||||
{
|
||||
_progressTask1.Description = text;
|
||||
_progressTask1.IsIndeterminate = true;
|
||||
}
|
||||
};
|
||||
|
||||
merger.InitProgress += () => _progressTask1 = ctx.AddTask("Progress");
|
||||
|
||||
merger.EndProgress += static () =>
|
||||
{
|
||||
_progressTask1?.StopTask();
|
||||
_progressTask1 = null;
|
||||
};
|
||||
|
||||
merger.InitProgress2 += () => _progressTask2 = ctx.AddTask("Progress");
|
||||
|
||||
merger.EndProgress2 += static () =>
|
||||
{
|
||||
_progressTask2?.StopTask();
|
||||
_progressTask2 = null;
|
||||
};
|
||||
|
||||
merger.UpdateProgress2 += (text, current, maximum) =>
|
||||
{
|
||||
_progressTask2 ??= ctx.AddTask("Progress");
|
||||
_progressTask2.Description = text;
|
||||
_progressTask2.Value = current;
|
||||
_progressTask2.MaxValue = maximum;
|
||||
};
|
||||
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
e.Cancel = true;
|
||||
merger.Abort();
|
||||
};
|
||||
|
||||
errno = merger.Start();
|
||||
});
|
||||
|
||||
return (int)errno;
|
||||
}
|
||||
|
||||
private static void LogCommandParameters(Settings settings, bool fixSubchannelPosition, bool fixSubchannel,
|
||||
bool fixSubchannelCrc, Dictionary<string, string> parsedOptions)
|
||||
{
|
||||
// Logs all command-line parameters for debugging and audit trail purposes
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--secondary-tags={0}", settings.UseSecondaryTags);
|
||||
AaruLogging.Debug(MODULE_NAME, "--comments={0}", Markup.Escape(settings.Comments ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--count={0}", settings.Count);
|
||||
AaruLogging.Debug(MODULE_NAME, "--creator={0}", Markup.Escape(settings.Creator ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--debug={0}", settings.Debug);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--drive-manufacturer={0}", Markup.Escape(settings.DriveManufacturer ?? ""));
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--drive-model={0}", Markup.Escape(settings.DriveModel ?? ""));
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--drive-revision={0}", Markup.Escape(settings.DriveFirmwareRevision ?? ""));
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--drive-serial={0}", Markup.Escape(settings.DriveSerialNumber ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--ignore-media-type={0}", settings.IgnoreMediaType);
|
||||
AaruLogging.Debug(MODULE_NAME, "--format={0}", Markup.Escape(settings.Format ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--geometry={0}", Markup.Escape(settings.Geometry ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--media-barcode={0}", Markup.Escape(settings.MediaBarcode ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--media-lastsequence={0}", settings.LastMediaSequence);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--media-manufacturer={0}", Markup.Escape(settings.MediaManufacturer ?? ""));
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--media-model={0}", Markup.Escape(settings.MediaModel ?? ""));
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--media-partnumber={0}", Markup.Escape(settings.MediaPartNumber ?? ""));
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "--media-sequence={0}", settings.MediaSequence);
|
||||
AaruLogging.Debug(MODULE_NAME, "--media-serial={0}", Markup.Escape(settings.MediaSerialNumber ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--media-title={0}", Markup.Escape(settings.MediaTitle ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--options={0}", Markup.Escape(settings.Options ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--secondary-resume={0}", Markup.Escape(settings.SecondaryResumeFile ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--primary-resume={0}", Markup.Escape(settings.PrimaryResumeFile ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--verbose={0}", settings.Verbose);
|
||||
AaruLogging.Debug(MODULE_NAME, "--fix-subchannel-position={0}", fixSubchannelPosition);
|
||||
AaruLogging.Debug(MODULE_NAME, "--fix-subchannel={0}", fixSubchannel);
|
||||
AaruLogging.Debug(MODULE_NAME, "--fix-subchannel-crc={0}", fixSubchannelCrc);
|
||||
AaruLogging.Debug(MODULE_NAME, "--generate-subchannels={0}", settings.GenerateSubchannels);
|
||||
AaruLogging.Debug(MODULE_NAME, "--decrypt={0}", settings.Decrypt);
|
||||
AaruLogging.Debug(MODULE_NAME, "--sectors-file={0}", Markup.Escape(settings.SectorsFile ?? ""));
|
||||
AaruLogging.Debug(MODULE_NAME, "--ignore-negative-sectors={0}", settings.IgnoreNegativeSectors);
|
||||
AaruLogging.Debug(MODULE_NAME, "--ignore-overflow-sectors={0}", settings.IgnoreOverflowSectors);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, UI.Parsed_options);
|
||||
|
||||
foreach(KeyValuePair<string, string> parsedOption in parsedOptions)
|
||||
AaruLogging.Debug(MODULE_NAME, "{0} = {1}", parsedOption.Key, parsedOption.Value);
|
||||
}
|
||||
|
||||
public class Settings : ImageFamily
|
||||
{
|
||||
[LocalizedDescription(nameof(UI.Path_to_the_primary_image_file))]
|
||||
[CommandArgument(0, "<primary-image>")]
|
||||
public string PrimaryImagePath { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Path_to_the_secondary_image_file))]
|
||||
[CommandArgument(1, "<secondary-image>")]
|
||||
public string SecondaryImagePath { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Path_to_the_output_merged_image_file))]
|
||||
[CommandArgument(2, "<output-image>")]
|
||||
public string OutputImagePath { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Use_media_tags_from_secondary_image))]
|
||||
[DefaultValue(false)]
|
||||
[CommandOption("--secondary-tags")]
|
||||
public bool UseSecondaryTags { get; init; }
|
||||
[LocalizedDescription(nameof(UI.File_containing_list_of_sectors_to_take_from_secondary_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--sectors-file")]
|
||||
public string SectorsFile { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Ignore_mismatched_image_media_type))]
|
||||
[DefaultValue(false)]
|
||||
[CommandOption("--ignore-media-type")]
|
||||
public bool IgnoreMediaType { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Image_comments))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--comments")]
|
||||
public string Comments { get; init; }
|
||||
[LocalizedDescription(nameof(UI.How_many_sectors_to_convert_at_once))]
|
||||
[DefaultValue(64)]
|
||||
[CommandOption("-c|--count")]
|
||||
public int Count { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Who_person_created_the_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--creator")]
|
||||
public string Creator { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Manufacturer_of_drive_read_the_media_by_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--drive-manufacturer")]
|
||||
public string DriveManufacturer { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Model_of_drive_used_by_media))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--drive-model")]
|
||||
public string DriveModel { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Firmware_revision_of_drive_read_the_media_by_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--drive-revision")]
|
||||
public string DriveFirmwareRevision { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Serial_number_of_drive_read_the_media_by_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--drive-serial")]
|
||||
public string DriveSerialNumber { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Format_of_the_output_image_as_plugin_name_or_plugin_id))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("-p|--format")]
|
||||
public string Format { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Barcode_of_the_media))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--media-barcode")]
|
||||
public string MediaBarcode { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Last_media_of_sequence_by_image))]
|
||||
[DefaultValue(0)]
|
||||
[CommandOption("--media-lastsequence")]
|
||||
public int LastMediaSequence { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Manufacturer_of_media_by_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--media-manufacturer")]
|
||||
public string MediaManufacturer { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Model_of_media_by_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--media-model")]
|
||||
public string MediaModel { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Part_number_of_media_by_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--media-partnumber")]
|
||||
public string MediaPartNumber { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Number_in_sequence_for_media_by_image))]
|
||||
[DefaultValue(0)]
|
||||
[CommandOption("--media-sequence")]
|
||||
public int MediaSequence { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Serial_number_of_media_by_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--media-serial")]
|
||||
public string MediaSerialNumber { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Title_of_media_represented_by_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--media-title")]
|
||||
public string MediaTitle { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Comma_separated_name_value_pairs_of_image_options))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("-O|--options")]
|
||||
public string Options { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Resume_file_for_primary_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--primary-resume")]
|
||||
public string PrimaryResumeFile { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Resume_file_for_secondary_image))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("--secondary-resume")]
|
||||
public string SecondaryResumeFile { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Force_geometry_help))]
|
||||
[DefaultValue(null)]
|
||||
[CommandOption("-g|--geometry")]
|
||||
public string Geometry { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Fix_subchannel_position_help))]
|
||||
[DefaultValue(true)]
|
||||
[CommandOption("--fix-subchannel-position")]
|
||||
public bool FixSubchannelPosition { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Fix_subchannel_help))]
|
||||
[DefaultValue(false)]
|
||||
[CommandOption("--fix-subchannel")]
|
||||
public bool FixSubchannel { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Fix_subchannel_crc_help))]
|
||||
[DefaultValue(false)]
|
||||
[CommandOption("--fix-subchannel-crc")]
|
||||
public bool FixSubchannelCrc { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Generates_subchannels_help))]
|
||||
[DefaultValue(false)]
|
||||
[CommandOption("--generate-subchannels")]
|
||||
public bool GenerateSubchannels { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Decrypt_sectors_help))]
|
||||
[DefaultValue(false)]
|
||||
[CommandOption("--decrypt")]
|
||||
public bool Decrypt { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Ignore_negative_sectors))]
|
||||
[DefaultValue(false)]
|
||||
[CommandOption("--ignore-negative-sectors")]
|
||||
public bool IgnoreNegativeSectors { get; init; }
|
||||
[LocalizedDescription(nameof(UI.Ignore_overflow_sectors))]
|
||||
[DefaultValue(false)]
|
||||
[CommandOption("--ignore-overflow-sectors")]
|
||||
public bool IgnoreOverflowSectors { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -399,6 +399,9 @@ class MainClass
|
||||
image.AddCommand<VerifyCommand>("verify")
|
||||
.WithAlias("v")
|
||||
.WithDescription(UI.Image_Verify_Command_Description);
|
||||
|
||||
image.AddCommand<MergeCommand>("merge")
|
||||
.WithDescription(UI.Image_Merge_Command_Description);
|
||||
})
|
||||
.WithAlias("i")
|
||||
.WithAlias("img");
|
||||
|
||||
Reference in New Issue
Block a user