using System;
using System.IO;
using System.Windows.Forms;
using Helper.IO;
using Scsi;
using Scsi.Multimedia;
using System.Threading;
using System.Runtime.InteropServices;
namespace ISOBurn
{
public partial class FormMain : Form
{
private MultimediaDevice currentCD;
private static readonly TimeSpan UPDATE_PAUSE = TimeSpan.FromMilliseconds(25);
private static readonly TimeSpan SPEED_UPDATE_PAUSE = TimeSpan.FromMilliseconds(250);
private ProgressInfo lastProgress;
private int lastProgressReportTick;
public FormMain() { this.InitializeComponent(); this.peWriteParams.SelectedObject = new WriteParametersPage(); }
[DllImport("User32.dll")]
private static extern bool SetCursorPos(int X, int Y);
private void RefreshCursor() { SetCursorPos(System.Windows.Forms.Control.MousePosition.X, System.Windows.Forms.Control.MousePosition.Y); }
[DllImport("User32.dll", SetLastError = true)]
private static extern bool EnableWindow(IntPtr hWnd, [MarshalAs(UnmanagedType.Bool)] bool bEnable);
private void btnBurn_Click(object sender, EventArgs e)
{
var drive = (DriveInfo)this.cmbDevice.SelectedItem;
this.currentCD = OpenDrive(drive);
this.gbxOptions.Enabled = false;
this.btnMkISOFS.Enabled = false;
this.cmbDevice.Enabled = false;
this.btnBrowse.Enabled = false;
this.btnBurn.Enabled = false;
this.txtFileName.ReadOnly = true;
this.UseWaitCursor = true;
this.bwCDBurner.RunWorkerAsync(new DoWorkInfo(this.currentCD));
this.btnCancel.Enabled = true;
}
private MultimediaDevice OpenDrive(DriveInfo drive) { return new MultimediaDevice(new Win32ScsiPassThroughInterface(new Win32FileStream(@"\\.\" + drive.Name.TrimEnd(Path.DirectorySeparatorChar), FileAccess.ReadWrite, FileShare.ReadWrite /*Must be specified in order to be dismountable*/, FileMode.Open, FileOptions.None, Win32FileOptions.None).SafeFileHandle, false), false); }
protected override void OnLoad(EventArgs e)
{
var drives = System.IO.DriveInfo.GetDrives();
foreach (var drive in drives)
{
if (drive.DriveType == System.IO.DriveType.CDRom)
{
this.cmbDevice.Items.Add(drive);
if (this.cmbDevice.SelectedItem == null)
{
this.cmbDevice.SelectedItem = drive;
}
}
}
base.OnLoad(e);
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
}
private class DoWorkInfo { public DoWorkInfo(MultimediaDevice device) { this.Device = device; } public MultimediaDevice Device; }
private class ProgressInfo
{
public ProgressInfo(string description, long completed, long total, BurnStage stage)
{
this.Description = description;
this.Completed = completed;
this.Total = total;
this.Stage = stage;
}
public string Description;
public long Completed; public long Total;
public BurnStage Stage;
}
private enum BurnStage { Locking, Dismounting, Flushing, ReadingDiscInformation, SettingParameters, Blanking, Burning, Closing, Unlocking }
private void bwCDBurner_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
//The actual burning occurs here, asynchronously from the UI
this.lastProgressReportTick = Environment.TickCount;
var info = (DoWorkInfo)e.Argument;
using (Stream fileStream = new FileInfo(this.txtFileName.Text).OpenRead())
{
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Locking drive...", 0, 1, BurnStage.Locking));
try { info.Device.Interface.LockVolume(); }
catch (Exception ex) { throw new IOException("Locking the drive failed. Please close any programs that are using the drive.", ex); }
try
{
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Dismounting volume...", 0, 1, BurnStage.Dismounting));
try { info.Device.Interface.DismountVolume(); }
catch { }
IMultimediaDevice iMMD = info.Device;
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Flushing any intermediate data in the cache...", 0, 1, BurnStage.Flushing));
iMMD.Flush();
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Reading disc information...", 0, 1, BurnStage.ReadingDiscInformation));
var discInfo = info.Device.ReadDiscInformation();
var writeParams = (WriteParametersPage)this.peWriteParams.SelectedObject;
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Setting parameters...", 0, 1, BurnStage.SettingParameters));
info.Device.SetWriteParameters(new ModeSelect10Command(false, true), writeParams);
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Seeing if this disc is CD-RW...", 0, 1, BurnStage.ReadingDiscInformation));
SenseData sense;
if (info.Device.CurrentProfile == MultimediaProfile.CDRW)
{
string message;
if (discInfo.DiscStatus == DiscStatus.Finalized | discInfo.DiscStatus == DiscStatus.Other) { message = "Detected an erasable disc that cannot be written to." + Environment.NewLine + "You cannot write to this disc unless you erase it." + Environment.NewLine + "Would you like to erase the disc?"; }
else { message = "It seems like you can write to this disc without erasing it." + Environment.NewLine + "However, erasing is HIGHLY recommended (it may not work correctly otherwise)." + Environment.NewLine + "Would you like to quickly erase it?" + Environment.NewLine + "(Nothing will happen if it is already empty.)"; }
var result = MessageBox.Show(message, "Erase Disc", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
if (result == DialogResult.Cancel) { return; }
if (result == DialogResult.Yes)
{
info.Device.Blank(new BlankCommand(BlankingType.BlankMinimal, true, 0));
int prevTick = Environment.TickCount;
//Get the progress
while ((sense = info.Device.RequestSense()).SenseKey == SenseKey.NotReady && sense.AdditionalSenseCode == AdditionalSenseCode.LogicalUnitNotReady && sense.AdditionalSenseCodeQualifier == (AdditionalSenseCodeQualifier)7)
{
int tick = Environment.TickCount;
if (tick - prevTick > UPDATE_PAUSE.TotalMilliseconds)
{
this.bwCDBurner.ReportProgress((int)(sense.SenseKeySpecific.ProgressIndication.ProgressIndicationFraction * 100), new ProgressInfo("Erasing CD (cannot be canceled)...", sense.SenseKeySpecific.ProgressIndication.ProgressIndication, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Blanking));
prevTick = tick;
}
else { Thread.Sleep(UPDATE_PAUSE); }
}
this.bwCDBurner.ReportProgress(100, new ProgressInfo("CD erased, starting burn...", ProgressIndicationBytes.ProgressIndicationDenominator, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Blanking));
}
}
ushort trackNumber = (ushort)(iMMD.FirstTrackNumber + iMMD.TrackCount - 1); //Last track
bool mustCloseTrack;
info.Device.SetCDSpeed(new SetCDSpeedCommand(ushort.MaxValue, ushort.MaxValue, RotationControl.ConstantLinearVelocity));
using (var track = iMMD.CreateTrack(trackNumber, out mustCloseTrack))
{
int prevTick = Environment.TickCount;
var buffer = new byte[info.Device.BlockSize];
while (fileStream.Position < fileStream.Length && !this.bwCDBurner.CancellationPending)
{
fileStream.Read(buffer, 0, buffer.Length);
track.Write(buffer, 0, buffer.Length);
int tick = Environment.TickCount;
if (tick - prevTick > UPDATE_PAUSE.TotalMilliseconds)
{
this.bwCDBurner.ReportProgress((int)(fileStream.Position * 100 / fileStream.Length), new ProgressInfo("Burning data...", fileStream.Position, fileStream.Length, BurnStage.Burning));
tick = prevTick;
}
}
this.bwCDBurner.ReportProgress(100, new ProgressInfo("Burn completed.", ProgressIndicationBytes.ProgressIndicationDenominator, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Burning));
}
if (mustCloseTrack)
{
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Closing...", 0, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Closing));
info.Device.CloseTrackOrSession(new CloseSessionOrTrackCommand(true, TrackSessionCloseFunction.CloseSessionOrStopBGFormat, trackNumber));
int prevTick = Environment.TickCount;
//Get the progress
while ((sense = info.Device.RequestSense()).SenseKey == SenseKey.NotReady && sense.AdditionalSenseCode == AdditionalSenseCode.LogicalUnitNotReady && sense.AdditionalSenseCodeQualifier == (AdditionalSenseCodeQualifier)7)
{
int tick = Environment.TickCount;
if (tick - prevTick > UPDATE_PAUSE.TotalMilliseconds)
{
this.bwCDBurner.ReportProgress((int)(sense.SenseKeySpecific.ProgressIndication.ProgressIndicationFraction * 100), new ProgressInfo("Closing (cannot be canceled)...", sense.SenseKeySpecific.ProgressIndication.ProgressIndication, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Closing));
prevTick = tick;
}
else { Thread.Sleep(UPDATE_PAUSE); }
}
this.bwCDBurner.ReportProgress(100, new ProgressInfo("Closed.", ProgressIndicationBytes.ProgressIndicationDenominator, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Closing));
}
e.Result = e.Argument;
}
finally
{
try
{
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Dismounting volume...", 1, 1, BurnStage.Dismounting));
info.Device.Interface.DismountVolume();
}
catch { }
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Unlocking drive...", 1, 1, BurnStage.Unlocking));
info.Device.Interface.UnlockVolume();
}
}
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
e.Cancel |= this.bwCDBurner.IsBusy;
base.OnClosing(e);
}
private void bwCDBurner_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
var state = (ProgressInfo)e.UserState;
bool setNewProgress;
var elapsed = TimeSpan.FromMilliseconds(Environment.TickCount - this.lastProgressReportTick);
if (this.lastProgress != null && this.lastProgress.Stage == state.Stage && state.Stage == BurnStage.Burning)
{
double bytesPerSecond = (state.Completed - this.lastProgress.Completed) / elapsed.TotalSeconds;
if (elapsed >= SPEED_UPDATE_PAUSE && !double.IsInfinity(bytesPerSecond) && !double.IsNaN(bytesPerSecond))
{
this.lblInfo.Text = string.Format("{0} ({1:N0} KB/s)", state.Description, bytesPerSecond / 1024);
setNewProgress = true;
}
else { setNewProgress = false; }
}
else
{
this.lblInfo.Text = state.Description;
setNewProgress = true;
}
if (setNewProgress)
{
this.lastProgress = state;
this.lastProgressReportTick = Environment.TickCount;
}
if (state.Total > int.MaxValue) { this.pbMain.Maximum = int.MaxValue; this.pbMain.Value = (int)((double)state.Completed * int.MaxValue / state.Total); }
else { this.pbMain.Maximum = (int)state.Total; this.pbMain.Value = (int)state.Completed; }
}
private void bwCDBurner_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
this.UseWaitCursor = false;
this.currentCD.Dispose();
this.currentCD = null;
if (e.Error != null)
{
MessageBox.Show(string.Format("{0}", e.Error.Message), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else if (!this.btnCancel.Enabled) //meaning this was canceled (e.Canceled and bwCDBurner.CancellationPending don't work for some reason)
{ this.lblInfo.Text = "Burn canceled. You may need to re-insert the CD to refresh Explorer."; }
else
{ this.lblInfo.Text = "Burn successful. You may need to re-insert the CD to refresh Explorer."; }
this.gbxOptions.Enabled = true;
this.pbMain.Value = this.pbMain.Minimum;
this.btnBurn.Enabled = true;
this.btnBrowse.Enabled = true;
this.txtFileName.ReadOnly = false;
this.btnCancel.Enabled = false;
this.btnMkISOFS.Enabled = true;
this.cmbDevice.Enabled = true;
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (this.bwCDBurner.IsBusy)
{
if (MessageBox.Show(this, @"Are you sure you want to cancel the operation?" + Environment.NewLine + "Please note that some operations (such as closing a track or blanking a disc)" + Environment.NewLine + "cannot be canceled and will be completed before stopping.", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1) == DialogResult.Yes)
{
this.bwCDBurner.CancelAsync();
this.btnCancel.Enabled = false;
}
}
}
private void btnBrowse_Click(object sender, EventArgs e)
{
if (this.ofdMain.ShowDialog(this) == DialogResult.OK)
{
this.txtFileName.Text = this.ofdMain.FileName;
}
}
private void miFileExit_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void txtFileName_TextChanged(object sender, EventArgs e)
{
this.btnBurn.Enabled = !string.IsNullOrEmpty(this.txtFileName.Text);
}
private void btnMkISOFS_Click(object sender, EventArgs e)
{
using (var form = new FormMakeImage())
{
var dr = form.ShowDialog(this);
if (dr == DialogResult.OK)
{
this.txtFileName.Text = form.ImageFile.FullName;
}
}
}
}
}