Files
Aaru/Aaru.Tests/WritableImages/WritableOpticalMediaImageTest.cs

812 lines
40 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Aaru.Checksums;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Core;
using Aaru.Core.Media;
using Aaru.Devices;
using FluentAssertions;
using FluentAssertions.Execution;
using NUnit.Framework;
namespace Aaru.Tests.WritableImages;
public abstract class WritableOpticalMediaImageTest : BaseWritableMediaImageTest
{
const uint SECTORS_TO_READ = 256;
public abstract OpticalImageTestExpected[] Tests { get; }
[OneTimeSetUp]
public void InitTest() => PluginBase.Init();
[Test]
public void Info()
{
Environment.CurrentDirectory = DataFolder;
ErrorNumber errno;
Assert.Multiple(() =>
{
foreach(OpticalImageTestExpected test in Tests)
{
string testFile = test.TestFile;
bool exists = File.Exists(testFile);
Assert.That(exists, string.Format(Localization._0_not_found, testFile));
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
// It arrives here...
if(!exists) continue;
IFilter filter = PluginRegister.Singleton.GetFilter(testFile);
filter.Open(testFile);
var image = Activator.CreateInstance(InputPlugin.GetType()) as IOpticalMediaImage;
Assert.That(image,
Is.Not.Null,
string.Format(Localization.Could_not_instantiate_filesystem_for_0, testFile));
ErrorNumber opened = image.Open(filter);
Assert.That(opened, Is.EqualTo(ErrorNumber.NoError), string.Format(Localization.Open_0, testFile));
if(opened != ErrorNumber.NoError) continue;
using(new AssertionScope())
{
Assert.Multiple(() =>
{
Assert.That(image.Info.Sectors,
Is.EqualTo(test.Sectors),
string.Format(Localization.Sectors_0, testFile));
if(test.SectorSize > 0)
{
Assert.That(image.Info.SectorSize,
Is.EqualTo(test.SectorSize),
string.Format(Localization.Sector_size_0, testFile));
}
Assert.That(image.Info.MediaType,
Is.EqualTo(test.MediaType),
string.Format(Localization.Media_type_0, testFile));
if(image.Info.MetadataMediaType != MetadataMediaType.OpticalDisc) return;
Assert.That(image.Tracks,
Has.Count.EqualTo(test.Tracks.Length),
string.Format(Localization.Tracks_0, testFile));
image.Tracks.Select(static t => t.Session)
.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.Session),
string.Format(Localization.Track_session_0, testFile));
image.Tracks.Select(static t => t.StartSector)
.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.Start),
string.Format(Localization.Track_start_0, testFile));
image.Tracks.Select(static t => t.EndSector)
.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.End),
string.Format(Localization.Track_end_0, testFile));
image.Tracks.Select(static t => t.Pregap)
.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.Pregap),
string.Format(Localization.Track_pregap_0, testFile));
var trackNo = 0;
var flags = new byte?[image.Tracks.Count];
ulong latestEndSector = 0;
foreach(Track currentTrack in image.Tracks)
{
if(currentTrack.EndSector > latestEndSector) latestEndSector = currentTrack.EndSector;
if(image.Info.ReadableSectorTags.Contains(SectorTagType.CdTrackFlags))
{
errno = image.ReadSectorTag(currentTrack.Sequence,
false,
SectorTagType.CdTrackFlags,
out byte[] tmp);
if(errno == ErrorNumber.NoError) flags[trackNo] = tmp[0];
}
trackNo++;
}
flags.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.Flags),
string.Format(Localization.Track_flags_0, testFile));
Assert.That(image.Info.Sectors - 1,
Is.EqualTo(latestEndSector),
string.Format(Localization.Last_sector_for_tracks_is_0_but_it_is_1_for_image,
latestEndSector,
image.Info.Sectors));
});
}
}
});
}
[Test]
public void Convert()
{
Environment.CurrentDirectory = DataFolder;
ErrorNumber errno;
Assert.Multiple(() =>
{
foreach(OpticalImageTestExpected test in Tests)
{
string testFile = test.TestFile;
bool exists = File.Exists(testFile);
Assert.That(exists, string.Format(Localization._0_not_found, testFile));
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
// It arrives here...
if(!exists) continue;
IFilter filter = PluginRegister.Singleton.GetFilter(testFile);
filter.Open(testFile);
var inputFormat = Activator.CreateInstance(InputPlugin.GetType()) as IOpticalMediaImage;
Assert.That(inputFormat,
Is.Not.Null,
string.Format(Localization.Could_not_instantiate_input_plugin_for_0, testFile));
ErrorNumber opened = inputFormat.Open(filter);
Assert.That(opened, Is.EqualTo(ErrorNumber.NoError), string.Format(Localization.Open_0, testFile));
if(opened != ErrorNumber.NoError) continue;
string outputPath = Path.Combine(Path.GetTempPath(), $"{Path.GetRandomFileName()}.{OutputExtension}");
var outputFormat = Activator.CreateInstance(OutputPlugin.GetType()) as IWritableOpticalImage;
Assert.That(outputFormat,
Is.Not.Null,
string.Format(Localization.Could_not_instantiate_output_plugin_for_0, testFile));
Assert.That(outputFormat.SupportedMediaTypes,
Does.Contain(inputFormat.Info.MediaType),
string.Format(Localization.Trying_to_convert_unsupported_media_type_0_for_1,
inputFormat.Info.MediaType,
testFile));
bool useLong = inputFormat.Info.ReadableSectorTags.Except([SectorTagType.CdTrackFlags]).Any();
// TODO: Can be done with LINQ only
foreach(SectorTagType unused in inputFormat.Info.ReadableSectorTags
.Where(sectorTag =>
!outputFormat.SupportedSectorTags
.Contains(sectorTag))
.Where(static sectorTag =>
sectorTag != SectorTagType.CdTrackFlags &&
sectorTag != SectorTagType.CdTrackIsrc &&
sectorTag != SectorTagType.CdSectorSubchannel))
useLong = false;
Assert.That(outputFormat.Create(outputPath,
inputFormat.Info.MediaType,
new Dictionary<string, string>(),
inputFormat.Info.Sectors,
0,
0,
inputFormat.Info.SectorSize),
string.Format(Localization.Error_0_creating_output_image, outputFormat.ErrorMessage));
foreach(MediaTagType mediaTag in inputFormat.Info.ReadableMediaTags.Where(mediaTag =>
outputFormat.SupportedMediaTags.Contains(mediaTag)))
{
if(inputFormat.ReadMediaTag(mediaTag, out byte[] buffer) == ErrorNumber.NoError)
outputFormat.WriteMediaTag(buffer, mediaTag);
}
Assert.That(outputFormat.SetTracks(inputFormat.Tracks),
string.Format(Localization.Error_0_sending_tracks_list_to_output_image,
outputFormat.ErrorMessage));
ulong doneSectors;
foreach(Track track in inputFormat.Tracks)
{
doneSectors = 0;
ulong trackSectors = track.EndSector - track.StartSector + 1;
while(doneSectors < trackSectors)
{
byte[] sector;
uint sectorsToDo;
if(trackSectors - doneSectors >= SECTORS_TO_READ)
sectorsToDo = SECTORS_TO_READ;
else
sectorsToDo = (uint)(trackSectors - doneSectors);
var useNotLong = false;
var result = false;
SectorStatus sectorStatus = SectorStatus.NotDumped;
var sectorStatusArray = new SectorStatus[1];
if(useLong)
{
errno = sectorsToDo == 1
? inputFormat.ReadSectorLong(doneSectors + track.StartSector,
false,
out sector,
out sectorStatus)
: inputFormat.ReadSectorsLong(doneSectors + track.StartSector,
false,
sectorsToDo,
out sector,
out sectorStatusArray);
if(errno == ErrorNumber.NoError)
{
result = sectorsToDo == 1
? outputFormat.WriteSectorLong(sector,
doneSectors + track.StartSector,
false,
sectorStatus)
: outputFormat.WriteSectorsLong(sector,
doneSectors + track.StartSector,
false,
sectorsToDo,
sectorStatusArray);
}
else
result = false;
if(!result && sector.Length % 2352 != 0) useNotLong = true;
}
if(!useLong || useNotLong)
{
errno = sectorsToDo == 1
? inputFormat.ReadSector(doneSectors + track.StartSector,
false,
out sector,
out sectorStatus)
: inputFormat.ReadSectors(doneSectors + track.StartSector,
false,
sectorsToDo,
out sector,
out sectorStatusArray);
Assert.That(errno, Is.EqualTo(ErrorNumber.NoError));
result = sectorsToDo == 1
? outputFormat.WriteSector(sector,
doneSectors + track.StartSector,
false,
sectorStatus)
: outputFormat.WriteSectors(sector,
doneSectors + track.StartSector,
false,
sectorsToDo,
sectorStatusArray);
}
Assert.That(result,
string.Format(Localization.Error_0_writing_sector_1,
outputFormat.ErrorMessage,
doneSectors + track.StartSector));
doneSectors += sectorsToDo;
}
}
Dictionary<byte, string> isrcs = new();
Dictionary<byte, byte> trackFlags = new();
string mcn = null;
HashSet<int> subchannelExtents = [];
Dictionary<byte, int> smallestPregapLbaPerTrack = new();
var tracks = new Track[inputFormat.Tracks.Count];
for(var i = 0; i < tracks.Length; i++)
{
tracks[i] = new Track
{
Indexes = new Dictionary<ushort, int>(),
Description = inputFormat.Tracks[i].Description,
EndSector = inputFormat.Tracks[i].EndSector,
StartSector = inputFormat.Tracks[i].StartSector,
Pregap = inputFormat.Tracks[i].Pregap,
Sequence = inputFormat.Tracks[i].Sequence,
Session = inputFormat.Tracks[i].Session,
BytesPerSector = inputFormat.Tracks[i].BytesPerSector,
RawBytesPerSector = inputFormat.Tracks[i].RawBytesPerSector,
Type = inputFormat.Tracks[i].Type,
SubchannelType = inputFormat.Tracks[i].SubchannelType
};
foreach(KeyValuePair<ushort, int> idx in inputFormat.Tracks[i].Indexes)
tracks[i].Indexes[idx.Key] = idx.Value;
}
foreach(SectorTagType tag in inputFormat.Info.ReadableSectorTags
.Where(static t => t == SectorTagType.CdTrackIsrc)
.Order())
{
foreach(Track track in tracks)
{
errno = inputFormat.ReadSectorTag(track.Sequence, false, tag, out byte[] isrc);
if(errno != ErrorNumber.NoError) continue;
isrcs[(byte)track.Sequence] = Encoding.UTF8.GetString(isrc);
}
}
foreach(SectorTagType tag in inputFormat.Info.ReadableSectorTags
.Where(static t => t == SectorTagType.CdTrackFlags)
.Order())
{
foreach(Track track in tracks)
{
errno = inputFormat.ReadSectorTag(track.Sequence, false, tag, out byte[] flags);
if(errno != ErrorNumber.NoError) continue;
trackFlags[(byte)track.Sequence] = flags[0];
}
}
for(ulong s = 0; s < inputFormat.Info.Sectors; s++)
{
if(s > int.MaxValue) break;
subchannelExtents.Add((int)s);
}
foreach(SectorTagType tag in inputFormat.Info.ReadableSectorTags.Order().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:
// This tags are inline in long sector
continue;
}
if(!outputFormat.SupportedSectorTags.Contains(tag)) continue;
foreach(Track track in inputFormat.Tracks)
{
doneSectors = 0;
ulong trackSectors = track.EndSector - track.StartSector + 1;
byte[] sector;
bool result;
switch(tag)
{
case SectorTagType.CdTrackFlags:
case SectorTagType.CdTrackIsrc:
errno = inputFormat.ReadSectorTag(track.Sequence, false, tag, out sector);
Assert.That(errno,
Is.EqualTo(ErrorNumber.NoError),
string.Format(Localization.Error_0_reading_tag_not_continuing, errno));
result = outputFormat.WriteSectorTag(sector, track.Sequence, false, tag);
Assert.That(result,
string.Format(Localization.Error_0_writing_tag_not_continuing,
outputFormat.ErrorMessage));
continue;
}
while(doneSectors < trackSectors)
{
uint sectorsToDo;
if(trackSectors - doneSectors >= SECTORS_TO_READ)
sectorsToDo = SECTORS_TO_READ;
else
sectorsToDo = (uint)(trackSectors - doneSectors);
if(sectorsToDo == 1)
{
errno = inputFormat.ReadSectorTag(doneSectors + track.StartSector,
false,
tag,
out sector);
Assert.That(errno,
Is.EqualTo(ErrorNumber.NoError),
string.Format(Localization.Error_0_reading_tag_not_continuing, errno));
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,
true,
outputFormat,
true,
true,
null,
smallestPregapLbaPerTrack,
false,
out _);
if(indexesChanged) outputFormat.SetTracks(tracks.ToList());
result = true;
}
else
{
result = outputFormat.WriteSectorTag(sector,
doneSectors + track.StartSector,
false,
tag);
}
}
else
{
errno = inputFormat.ReadSectorsTag(doneSectors + track.StartSector,
false,
sectorsToDo,
tag,
out sector);
Assert.That(errno,
Is.EqualTo(ErrorNumber.NoError),
string.Format(Localization.Error_0_reading_tag_not_continuing, errno));
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,
true,
outputFormat,
true,
true,
null,
smallestPregapLbaPerTrack,
false,
out _);
if(indexesChanged) outputFormat.SetTracks(tracks.ToList());
result = true;
}
else
{
result = outputFormat.WriteSectorsTag(sector,
doneSectors + track.StartSector,
false,
sectorsToDo,
tag);
}
}
Assert.That(result,
string.Format(Localization.Error_0_writing_tag_for_sector_1_not_continuing,
outputFormat.ErrorMessage,
doneSectors + track.StartSector));
doneSectors += sectorsToDo;
}
}
}
foreach(KeyValuePair<byte, string> isrc in isrcs)
{
outputFormat.WriteSectorTag(Encoding.UTF8.GetBytes(isrc.Value),
isrc.Key,
false,
SectorTagType.CdTrackIsrc);
}
if(trackFlags.Count > 0)
{
foreach((byte track, byte flags) in trackFlags)
outputFormat.WriteSectorTag([flags], track, false, SectorTagType.CdTrackFlags);
}
if(mcn != null) outputFormat.WriteMediaTag(Encoding.UTF8.GetBytes(mcn), MediaTagType.CD_MCN);
// TODO: Progress
if(inputFormat.Info.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)
{
CompactDisc.GenerateSubchannels(subchannelExtents,
tracks,
trackFlags,
inputFormat.Info.Sectors,
null,
null,
null,
null,
outputFormat);
}
Assert.That(outputFormat.Close(),
string.Format(Localization.Error_0_closing_output_image_Contents_are_not_correct,
outputFormat.ErrorMessage));
filter = PluginRegister.Singleton.GetFilter(outputPath);
filter.Open(outputPath);
string tmpFolder = Path.GetDirectoryName(outputPath);
Environment.CurrentDirectory = tmpFolder;
var image = Activator.CreateInstance(OutputPlugin.GetType()) as IOpticalMediaImage;
Assert.That(image,
Is.Not.Null,
string.Format(Localization.Could_not_instantiate_output_plugin_for_0, testFile));
opened = image.Open(filter);
Assert.That(opened,
Is.EqualTo(ErrorNumber.NoError),
string.Format(Localization.Open_created_0, testFile));
if(opened != ErrorNumber.NoError) continue;
using(new AssertionScope())
{
Assert.Multiple(() =>
{
Assert.That(image.Info.Sectors,
Is.EqualTo(test.Sectors),
string.Format(Localization.Sectors_output_0, testFile));
if(test.SectorSize > 0)
{
Assert.That(image.Info.SectorSize,
Is.EqualTo(test.SectorSize),
string.Format(Localization.Sector_size_output_0, testFile));
}
Assert.That(image.Tracks,
Has.Count.EqualTo(test.Tracks.Length),
string.Format(Localization.Tracks_output_0, testFile));
image.Tracks.Select(static t => t.Session)
.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.Session),
string.Format(Localization.Track_session_output_0, testFile));
image.Tracks.Select(static t => t.StartSector)
.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.Start),
string.Format(Localization.Track_start_output_0, testFile));
image.Tracks.Select(static t => t.EndSector)
.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.End),
string.Format(Localization.Track_end_output_0, testFile));
image.Tracks.Select(static t => t.Pregap)
.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.Pregap),
string.Format(Localization.Track_pregap_output_0, testFile));
var trackNo = 0;
var flags = new byte?[image.Tracks.Count];
ulong latestEndSector = 0;
foreach(Track currentTrack in image.Tracks)
{
if(currentTrack.EndSector > latestEndSector) latestEndSector = currentTrack.EndSector;
if(image.Info.ReadableSectorTags.Contains(SectorTagType.CdTrackFlags))
{
errno = image.ReadSectorTag(currentTrack.Sequence,
false,
SectorTagType.CdTrackFlags,
out byte[] tmp);
if(errno == ErrorNumber.NoError) flags[trackNo] = tmp[0];
}
trackNo++;
}
flags.Should()
.BeEquivalentTo(test.Tracks.Select(static s => s.Flags),
string.Format(Localization.Track_flags_output_0, testFile));
Assert.That(image.Info.Sectors - 1,
Is.EqualTo(latestEndSector),
string.Format(Localization.Last_sector_for_tracks_is_0_but_it_is_1_for_image_output,
latestEndSector,
image.Info.Sectors));
});
}
Md5Context ctx;
foreach(bool @long in new[]
{
/*false,*/ true
})
{
ctx = new Md5Context();
foreach(Track currentTrack in image.Tracks)
{
ulong sectors = currentTrack.EndSector - currentTrack.StartSector + 1;
doneSectors = 0;
while(doneSectors < sectors)
{
byte[] sector;
if(sectors - doneSectors >= SECTORS_TO_READ)
{
errno = @long
? image.ReadSectorsLong(doneSectors,
SECTORS_TO_READ,
currentTrack.Sequence,
out sector,
out _)
: image.ReadSectors(doneSectors,
SECTORS_TO_READ,
currentTrack.Sequence,
out sector,
out _);
doneSectors += SECTORS_TO_READ;
}
else
{
errno = @long
? image.ReadSectorsLong(doneSectors,
(uint)(sectors - doneSectors),
currentTrack.Sequence,
out sector,
out _)
: image.ReadSectors(doneSectors,
(uint)(sectors - doneSectors),
currentTrack.Sequence,
out sector,
out _);
doneSectors += sectors - doneSectors;
}
Assert.That(errno, Is.EqualTo(ErrorNumber.NoError));
ctx.Update(sector);
}
}
Assert.That(ctx.End(),
Is.EqualTo(@long ? test.LongMd5 : test.Md5),
string.Format("{0}: {1}",
@long ? Localization.Long_hash_output : Localization.Hash_output,
testFile));
}
if(!image.Info.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel)) return;
ctx = new Md5Context();
foreach(Track currentTrack in image.Tracks)
{
ulong sectors = currentTrack.EndSector - currentTrack.StartSector + 1;
doneSectors = 0;
while(doneSectors < sectors)
{
byte[] sector;
if(sectors - doneSectors >= SECTORS_TO_READ)
{
errno = image.ReadSectorsTag(doneSectors,
SECTORS_TO_READ,
currentTrack.Sequence,
SectorTagType.CdSectorSubchannel,
out sector);
doneSectors += SECTORS_TO_READ;
}
else
{
errno = image.ReadSectorsTag(doneSectors,
(uint)(sectors - doneSectors),
currentTrack.Sequence,
SectorTagType.CdSectorSubchannel,
out sector);
doneSectors += sectors - doneSectors;
}
Assert.That(errno, Is.EqualTo(ErrorNumber.NoError));
ctx.Update(sector);
}
}
Assert.That(ctx.End(),
Is.EqualTo(test.SubchannelMd5),
string.Format(Localization.Subchannel_hash_output_0, testFile));
}
});
}
}