Implement "dump-media" command in GUI.

This commit is contained in:
2018-09-30 22:29:52 +01:00
parent 57fa2d11e5
commit e75dee2177
6 changed files with 890 additions and 273 deletions

View File

@@ -1597,6 +1597,8 @@
<e p="Forms" t="Include">
<e p="frmConsole.xeto" t="Include" />
<e p="frmConsole.xeto.cs" t="Include" />
<e p="frmDump.xeto" t="Include" />
<e p="frmDump.xeto.cs" t="Include" />
<e p="frmMain.xeto" t="Include" />
<e p="frmMain.xeto.cs" t="Include" />
</e>

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?><!--
// /***************************************************************************
// The Disc Image Chef
// ============================================================================
//
// Filename : frmMain.xeto
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Main window.
//
// ==[ Description ] ==========================================================
//
// Defines the structure for the main GUI window.
//
// ==[ License ] ==============================================================
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General public License for more details.
//
// You should have received a copy of the GNU General public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ============================================================================
// Copyright © 2011-2018 Natalia Portillo
// ****************************************************************************/
-->
<Form xmlns="http://schema.picoe.ca/eto.forms" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DiscImageChef" ClientSize="600, 450" Padding="10">
<StackLayout Orientation="Vertical" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<Label Text="Output format"/>
<ComboBox ID="cmbFormat" ReadOnly="True" SelectedIndexChanged="OnCmbFormatSelectedIndexChanged"/>
<StackLayout Orientation="Horizontal" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<StackLayoutItem Expand="True">
<TextBox ID="txtDestination" ReadOnly="True"/>
</StackLayoutItem>
<Button ID="btnDestination" Text="Choose..." Click="OnBtnDestinationClick" Enabled="False"/>
</StackLayout>
<CheckBox ID="chkStopOnError" Text="Stop media dump on first error"/>
<CheckBox ID="chkForce" Text="Continue dumping whatever happens"/>
<StackLayout Orientation="Horizontal" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<NumericStepper ID="stpRetries" MinValue="0"/>
<Label Text="Retry passes"/>
</StackLayout>
<CheckBox ID="chkPersistent" Text="Try to recover partial or incorrect data"/>
<CheckBox ID="chkResume" Text="Create/use resume mapfile" CheckedChanged="OnChkResumeCheckedChanged"/>
<CheckBox ID="chkTrack1Pregap" Text="Try to read track 1 pregap"/>
<StackLayout Orientation="Horizontal" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<NumericStepper ID="stpSkipped" MinValue="1"/>
<Label Text="Skipped sectors on error"/>
</StackLayout>
<CheckBox ID="chkSidecar" Text="Create CICM XML metadata sidecar" CheckedChanged="OnChkSidecarCheckedChanged"/>
<CheckBox ID="chkTrim" Text="Trim errors from skipped sectors"/>
<CheckBox ID="chkExistingMetadata" Text="Take metadata from existing CICM XML sidecar"
CheckedChanged="OnChkExistingMetadataCheckedChanged"/>
<Label ID="lblEncoding" Text="Encoding to use on metadata sidecar creation"/>
<ComboBox ID="cmbEncoding" ReadOnly="True"/>
<GroupBox ID="grpOptions" Text="Options" Visible="False"/>
<StackLayout Orientation="Horizontal">
<StackLayoutItem HorizontalAlignment="Right">
<Button ID="btnCancel" Click="OnBtnCancelClick" Text="Cancel"/>
</StackLayoutItem>
<StackLayoutItem HorizontalAlignment="Right">
<Button ID="btnDump" Click="OnBtnDumpClick" Text="Dump"/>
</StackLayoutItem>
</StackLayout>
</StackLayout>
</Form>

View File

@@ -0,0 +1,534 @@
// /***************************************************************************
// The Disc Image Chef
// ----------------------------------------------------------------------------
//
// Filename : frmMain.xeto.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Main window.
//
// --[ Description ] ----------------------------------------------------------
//
// Implements main GUI window.
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General public License for more details.
//
// You should have received a copy of the GNU General public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2018 Natalia Portillo
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using DiscImageChef.CommonTypes;
using DiscImageChef.CommonTypes.Interfaces;
using DiscImageChef.CommonTypes.Metadata;
using DiscImageChef.Core;
using DiscImageChef.Core.Devices.Dumping;
using DiscImageChef.Core.Logging;
using DiscImageChef.Core.Media.Info;
using DiscImageChef.Devices;
using Eto.Forms;
using Eto.Serialization.Xaml;
using Schemas;
using DeviceInfo = DiscImageChef.Core.Devices.Info.DeviceInfo;
using MediaType = DiscImageChef.CommonTypes.MediaType;
namespace DiscImageChef.Gui.Forms
{
public class frmDump : Form
{
string devicePath;
string outputPrefix;
Resume resume;
CICMMetadataType sidecar;
public frmDump(string devicePath, DeviceInfo deviceInfo, ScsiInfo scsiInfo = null)
{
MediaType mediaType;
XamlReader.Load(this);
// Defaults
chkStopOnError.Checked = false;
chkForce.Checked = false;
chkPersistent.Checked = true;
chkResume.Checked = true;
chkTrack1Pregap.Checked = false;
chkSidecar.Checked = true;
chkTrim.Checked = true;
chkExistingMetadata.Checked = false;
stpRetries.Value = 5;
stpSkipped.Value = 512;
if(scsiInfo != null) mediaType = scsiInfo.MediaType;
else
switch(deviceInfo.Type)
{
case DeviceType.SecureDigital:
mediaType = MediaType.SecureDigital;
break;
case DeviceType.MMC:
mediaType = MediaType.MMC;
break;
default:
if(deviceInfo.IsPcmcia) mediaType = MediaType.PCCardTypeII;
else if(deviceInfo.IsCompactFlash) mediaType = MediaType.CompactFlash;
else mediaType = MediaType.GENERIC_HDD;
break;
}
ObservableCollection<IWritableImage> lstPlugins = new ObservableCollection<IWritableImage>();
PluginBase plugins = GetPluginBase.Instance;
foreach(IWritableImage plugin in
plugins.WritableImages.Values.Where(p => p.SupportedMediaTypes.Contains(mediaType)))
lstPlugins.Add(plugin);
cmbFormat.ItemTextBinding = Binding.Property((IWritableImage p) => p.Name);
cmbFormat.ItemKeyBinding = Binding.Property((IWritableImage p) => p.Id.ToString());
cmbFormat.DataStore = lstPlugins;
List<CommonEncodingInfo> encodings = Encoding
.GetEncodings().Select(info => new CommonEncodingInfo
{
Name = info.Name,
DisplayName =
info.GetEncoding().EncodingName
}).ToList();
encodings.AddRange(Claunia.Encoding.Encoding.GetEncodings()
.Select(info => new CommonEncodingInfo
{
Name = info.Name, DisplayName = info.DisplayName
}));
ObservableCollection<CommonEncodingInfo> lstEncodings = new ObservableCollection<CommonEncodingInfo>();
foreach(CommonEncodingInfo info in encodings.OrderBy(t => t.DisplayName)) lstEncodings.Add(info);
cmbEncoding.ItemTextBinding = Binding.Property((CommonEncodingInfo p) => p.DisplayName);
cmbEncoding.ItemKeyBinding = Binding.Property((CommonEncodingInfo p) => p.Name);
cmbEncoding.DataStore = lstEncodings;
switch(mediaType)
{
case MediaType.CD:
case MediaType.CDDA:
case MediaType.CDG:
case MediaType.CDEG:
case MediaType.CDI:
case MediaType.CDROM:
case MediaType.CDROMXA:
case MediaType.CDPLUS:
case MediaType.CDMO:
case MediaType.CDR:
case MediaType.CDRW:
case MediaType.CDMRW:
case MediaType.VCD:
case MediaType.SVCD:
case MediaType.PCD:
case MediaType.DDCD:
case MediaType.DDCDR:
case MediaType.DDCDRW:
case MediaType.DTSCD:
case MediaType.CDMIDI:
case MediaType.CDV:
case MediaType.CDIREADY:
case MediaType.FMTOWNS:
case MediaType.PS1CD:
case MediaType.PS2CD:
case MediaType.MEGACD:
case MediaType.SATURNCD:
case MediaType.GDROM:
case MediaType.GDR:
case MediaType.MilCD:
case MediaType.SuperCDROM2:
case MediaType.JaguarCD:
case MediaType.ThreeDO:
case MediaType.PCFX:
case MediaType.NeoGeoCD:
case MediaType.CDTV:
case MediaType.CD32:
case MediaType.Playdia:
case MediaType.Pippin:
chkTrack1Pregap.Visible = true;
break;
default:
chkTrack1Pregap.Visible = false;
break;
}
this.devicePath = devicePath;
}
void OnCmbFormatSelectedIndexChanged(object sender, EventArgs e)
{
txtDestination.Text = "";
if(!(cmbFormat.SelectedValue is IWritableImage plugin))
{
grpOptions.Visible = false;
btnDestination.Enabled = false;
return;
}
btnDestination.Enabled = true;
if(!plugin.SupportedOptions.Any())
{
grpOptions.Content = null;
grpOptions.Visible = false;
return;
}
grpOptions.Visible = true;
StackLayout stkOptions = new StackLayout {Orientation = Orientation.Vertical};
foreach((string name, Type type, string description, object @default) option in plugin.SupportedOptions)
switch(option.type.ToString())
{
case "System.Boolean":
CheckBox optBoolean = new CheckBox();
optBoolean.ID = "opt" + option.name;
optBoolean.Text = option.description;
optBoolean.Checked = (bool)option.@default;
stkOptions.Items.Add(optBoolean);
break;
case "System.SByte":
case "System.Int16":
case "System.Int32":
case "System.Int64":
StackLayout stkNumber = new StackLayout();
stkNumber.Orientation = Orientation.Horizontal;
NumericStepper optNumber = new NumericStepper();
optNumber.ID = "opt" + option.name;
optNumber.Value = Convert.ToDouble(option.@default);
stkNumber.Items.Add(optNumber);
Label lblNumber = new Label();
lblNumber.Text = option.description;
stkNumber.Items.Add(lblNumber);
stkOptions.Items.Add(stkNumber);
break;
case "System.Byte":
case "System.UInt16":
case "System.UInt32":
case "System.UInt64":
StackLayout stkUnsigned = new StackLayout();
stkUnsigned.Orientation = Orientation.Horizontal;
NumericStepper optUnsigned = new NumericStepper();
optUnsigned.ID = "opt" + option.name;
optUnsigned.MinValue = 0;
optUnsigned.Value = Convert.ToDouble(option.@default);
stkUnsigned.Items.Add(optUnsigned);
Label lblUnsigned = new Label();
lblUnsigned.Text = option.description;
stkUnsigned.Items.Add(lblUnsigned);
stkOptions.Items.Add(stkUnsigned);
break;
case "System.Single":
case "System.Double":
StackLayout stkFloat = new StackLayout();
stkFloat.Orientation = Orientation.Horizontal;
NumericStepper optFloat = new NumericStepper();
optFloat.ID = "opt" + option.name;
optFloat.DecimalPlaces = 2;
optFloat.Value = Convert.ToDouble(option.@default);
stkFloat.Items.Add(optFloat);
Label lblFloat = new Label();
lblFloat.Text = option.description;
stkFloat.Items.Add(lblFloat);
stkOptions.Items.Add(stkFloat);
break;
case "System.Guid":
// TODO
break;
case "System.String":
StackLayout stkString = new StackLayout();
stkString.Orientation = Orientation.Horizontal;
Label lblString = new Label();
lblString.Text = option.description;
stkString.Items.Add(lblString);
TextBox optString = new TextBox();
optString.ID = "opt" + option.name;
optString.Text = (string)option.@default;
stkString.Items.Add(optString);
stkOptions.Items.Add(stkString);
break;
}
grpOptions.Content = stkOptions;
}
void OnBtnDestinationClick(object sender, EventArgs e)
{
if(!(cmbFormat.SelectedValue is IWritableImage plugin)) return;
SaveFileDialog dlgDestination = new SaveFileDialog {Title = "Choose destination file"};
dlgDestination.Filters.Add(new FileFilter(plugin.Name, plugin.KnownExtensions.ToArray()));
DialogResult result = dlgDestination.ShowDialog(this);
if(result != DialogResult.Ok)
{
txtDestination.Text = "";
outputPrefix = null;
return;
}
if(string.IsNullOrEmpty(Path.GetExtension(dlgDestination.FileName)))
dlgDestination.FileName += plugin.KnownExtensions.First();
txtDestination.Text = dlgDestination.FileName;
outputPrefix = Path.Combine(Path.GetDirectoryName(dlgDestination.FileName),
Path.GetFileNameWithoutExtension(dlgDestination.FileName));
chkResume.Checked = true;
}
void OnChkSidecarCheckedChanged(object sender, EventArgs e)
{
cmbEncoding.Visible = chkSidecar.Checked.Value;
lblEncoding.Visible = chkSidecar.Checked.Value;
}
void OnChkExistingMetadataCheckedChanged(object sender, EventArgs e)
{
if(chkExistingMetadata.Checked == false)
{
sidecar = null;
return;
}
OpenFileDialog dlgMetadata =
new OpenFileDialog {Title = "Choose existing metadata sidecar", CheckFileExists = true};
dlgMetadata.Filters.Add(new FileFilter("CICM XML metadata", ".xml"));
DialogResult result = dlgMetadata.ShowDialog(this);
if(result != DialogResult.Ok)
{
chkExistingMetadata.Checked = false;
return;
}
XmlSerializer sidecarXs = new XmlSerializer(typeof(CICMMetadataType));
try
{
StreamReader sr = new StreamReader(dlgMetadata.FileName);
sidecar = (CICMMetadataType)sidecarXs.Deserialize(sr);
sr.Close();
}
catch
{
MessageBox.Show("Incorrect metadata sidecar file...", MessageBoxType.Error);
chkExistingMetadata.Checked = false;
}
}
void OnChkResumeCheckedChanged(object sender, EventArgs e)
{
if(chkResume.Checked == false) return;
if(outputPrefix != null) CheckResumeFile();
}
void CheckResumeFile()
{
Resume resume = null;
XmlSerializer xs = new XmlSerializer(typeof(Resume));
try
{
StreamReader sr = new StreamReader(outputPrefix + ".resume.xml");
resume = (Resume)xs.Deserialize(sr);
sr.Close();
}
catch
{
MessageBox.Show("Incorrect resume file, cannot use it...", MessageBoxType.Error);
chkResume.Checked = false;
return;
}
if(resume == null || resume.NextBlock <= resume.LastBlock || resume.BadBlocks.Count != 0) return;
MessageBox.Show("Media already dumped correctly, please choose another destination...",
MessageBoxType.Warning);
chkResume.Checked = false;
}
void OnBtnCancelClick(object sender, EventArgs e)
{
Close();
}
void OnBtnDumpClick(object sender, EventArgs e)
{
Device dev;
try
{
dev = new Device(devicePath);
if(dev.Error)
{
MessageBox.Show($"Error {dev.LastError} opening device.", MessageBoxType.Error);
return;
}
}
catch(Exception exception)
{
MessageBox.Show($"Exception {exception.Message} opening device.", MessageBoxType.Error);
return;
}
Statistics.AddDevice(dev);
if(!(cmbFormat.SelectedValue is IWritableImage outputFormat))
{
MessageBox.Show("Cannot open output plugin.", MessageBoxType.Error);
return;
}
Encoding encoding = null;
if(cmbEncoding.SelectedValue is CommonEncodingInfo encodingInfo)
try { encoding = Claunia.Encoding.Encoding.GetEncoding(encodingInfo.Name); }
catch(ArgumentException)
{
MessageBox.Show("Specified encoding is not supported.", MessageBoxType.Error);
return;
}
DumpLog dumpLog = new DumpLog(outputPrefix + ".log", dev);
dumpLog.WriteLine("Output image format: {0}.", outputFormat.Name);
Dictionary<string, string> parsedOptions = new Dictionary<string, string>();
if(grpOptions.Content is StackLayout stkOptions)
foreach(Control option in stkOptions.Children)
{
string value;
switch(option)
{
case CheckBox optBoolean:
value = optBoolean.Checked?.ToString();
break;
case NumericStepper optNumber:
value = optNumber.Value.ToString(CultureInfo.CurrentCulture);
break;
case TextBox optString:
value = optString.Text;
break;
default: continue;
}
string key = option.ID.Substring(3);
parsedOptions.Add(key, value);
}
switch(dev.Type)
{
case DeviceType.ATA:
Ata.Dump(dev, devicePath, outputFormat, (ushort)stpRetries.Value, chkForce.Checked == true,
false, /*options.Raw,*/
chkPersistent.Checked == true, chkStopOnError.Checked == true, ref resume, ref dumpLog,
encoding, outputPrefix, txtDestination.Text, parsedOptions, sidecar,
(uint)stpSkipped.Value, chkExistingMetadata.Checked == false, chkTrim.Checked == false);
break;
case DeviceType.MMC:
case DeviceType.SecureDigital:
SecureDigital.Dump(dev, devicePath, outputFormat, (ushort)stpRetries.Value,
chkForce.Checked == true, false, /*options.Raw,*/
chkPersistent.Checked == true, chkStopOnError.Checked == true, ref resume,
ref dumpLog, encoding, outputPrefix, txtDestination.Text, parsedOptions, sidecar,
(uint)stpSkipped.Value, chkExistingMetadata.Checked == false,
chkTrim.Checked == false);
break;
case DeviceType.NVMe:
NvMe.Dump(dev, devicePath, outputFormat, (ushort)stpRetries.Value, chkForce.Checked == true,
false, /*options.Raw,*/
chkPersistent.Checked == true, chkStopOnError.Checked == true, ref resume, ref dumpLog,
encoding, outputPrefix, txtDestination.Text, parsedOptions, sidecar,
(uint)stpSkipped.Value, chkExistingMetadata.Checked == false, chkTrim.Checked == false);
break;
case DeviceType.ATAPI:
case DeviceType.SCSI:
Scsi.Dump(dev, devicePath, outputFormat, (ushort)stpRetries.Value, chkForce.Checked == true,
false, /*options.Raw,*/
chkPersistent.Checked == true,
chkStopOnError.Checked == true, ref resume, ref dumpLog,
chkTrack1Pregap.Checked == true,
encoding, outputPrefix, txtDestination.Text,
parsedOptions, sidecar, (uint)stpSkipped.Value, chkExistingMetadata.Checked == false,
chkTrim.Checked == false);
break;
default:
dumpLog.WriteLine("Unknown device type.");
dumpLog.Close();
MessageBox.Show("Unknown device type.", MessageBoxType.Error);
return;
}
if(resume != null && chkResume.Checked == true)
{
resume.LastWriteDate = DateTime.UtcNow;
resume.BadBlocks.Sort();
if(File.Exists(outputPrefix + ".resume.xml")) File.Delete(outputPrefix + ".resume.xml");
FileStream fs = new FileStream(outputPrefix + ".resume.xml", FileMode.Create, FileAccess.ReadWrite);
XmlSerializer xs = new XmlSerializer(resume.GetType());
xs.Serialize(fs, resume);
fs.Close();
}
dumpLog.Close();
Statistics.AddCommand("dump-media");
dev.Close();
}
class CommonEncodingInfo
{
public string Name { get; set; }
public string DisplayName { get; set; }
}
#region XAML IDs
ComboBox cmbFormat;
TextBox txtDestination;
Button btnDestination;
CheckBox chkStopOnError;
CheckBox chkForce;
CheckBox chkPersistent;
CheckBox chkResume;
CheckBox chkTrack1Pregap;
CheckBox chkSidecar;
CheckBox chkTrim;
CheckBox chkExistingMetadata;
ComboBox cmbEncoding;
GroupBox grpOptions;
NumericStepper stpRetries;
NumericStepper stpSkipped;
Label lblEncoding;
Button btnCancel;
Button btnDump;
#endregion
}
}

View File

@@ -282,7 +282,7 @@ namespace DiscImageChef.Gui.Forms
Values = new[]
{
scsiInfo.MediaType, deviceItem.Values[1],
new pnlScsiInfo(scsiInfo)
new pnlScsiInfo(scsiInfo, (string)deviceItem.Values[1])
}
});
}

View File

@@ -43,6 +43,7 @@ using DiscImageChef.Decoders.SCSI.MMC;
using DiscImageChef.Decoders.SCSI.SSC;
using DiscImageChef.Decoders.Xbox;
using DiscImageChef.Gui.Controls;
using DiscImageChef.Gui.Forms;
using Eto.Drawing;
using Eto.Forms;
using Eto.Serialization.Xaml;
@@ -56,9 +57,10 @@ namespace DiscImageChef.Gui.Panels
{
public class pnlScsiInfo : Panel
{
string devicePath;
ScsiInfo scsiInfo;
public pnlScsiInfo(ScsiInfo scsiInfo)
public pnlScsiInfo(ScsiInfo scsiInfo, string devicePath)
{
XamlReader.Load(this);
@@ -425,6 +427,8 @@ namespace DiscImageChef.Gui.Panels
grpBluraySpareAreaInformation.Visible || grpBlurayPowResources.Visible ||
grpBlurayTrackResources.Visible || btnSaveBlurayRawDfl.Visible ||
btnSaveBlurayPac.Visible;
this.devicePath = devicePath;
}
void SaveElement(byte[] data)
@@ -688,6 +692,9 @@ namespace DiscImageChef.Gui.Panels
scsiInfo.MediaType == MediaType.XGD3) &&
scsiInfo.DeviceInfo.ScsiInquiry?.KreonPresent != true)
MessageBox.Show("Dumping Xbox discs require a Kreon drive.", MessageBoxType.Error);
frmDump dumpForm = new frmDump(devicePath, scsiInfo.DeviceInfo, scsiInfo);
dumpForm.Show();
}
#region XAML controls