Example #1
0
        void FromCuePathInternal(string cuePath, CueBinPrefs prefs)
        {
            string cueDir = Path.GetDirectoryName(cuePath);
            var    cue    = new Cue();

            cue.LoadFromPath(cuePath);
            FromCueInternal(cue, cueDir, prefs);
        }
Example #2
0
        public static Disc FromCuePath(string cuePath, CueBinPrefs prefs)
        {
            var ret = new Disc();

            ret.FromCuePathInternal(cuePath, prefs);
            ret.TOC.GeneratePoints();
            ret.PopulateQSubchannel();
            return(ret);
        }
Example #3
0
		public static CueBinPrefs GetCuePrefs()
		{
			var prefs = new CueBinPrefs();
			prefs.AnnotateCue = true; // TODO? checkCueProp_Annotations.Checked;
			prefs.OneBlobPerTrack = false; //TODO? checkCueProp_OneBlobPerTrack.Checked;
			prefs.ReallyDumpBin = false;
			prefs.SingleSession = true;
			prefs.ExtensionAware = true;
			return prefs;
		}
Example #4
0
        public string GenerateCUE_OneBin(CueBinPrefs prefs)
        {
            if (prefs.OneBlobPerTrack) throw new InvalidOperationException("OneBinPerTrack passed to GenerateCUE_OneBin");

            //this generates a single-file cue!!!!!!! dont expect it to generate bin-per-track!
            StringBuilder sb = new StringBuilder();

            foreach (var session in Sessions)
            {
                if (!prefs.SingleSession)
                {
                    //dont want to screw around with sessions for now
                    if (prefs.AnnotateCue) sb.AppendFormat("SESSION {0:D2} (length={1})\n", session.num, session.length_aba);
                    else sb.AppendFormat("SESSION {0:D2}\n", session.num);
                }

                foreach (var track in session.Tracks)
                {
                    ETrackType trackType = track.TrackType;

                    //mutate track type according to our principle of canonicalization
                    if (trackType == ETrackType.Mode1_2048 && prefs.DumpECM)
                        trackType = ETrackType.Mode1_2352;

                    if (prefs.AnnotateCue) sb.AppendFormat("  TRACK {0:D2} {1} (length={2})\n", track.num, Cue.TrackTypeStringForTrackType(trackType), track.length_aba);
                    else sb.AppendFormat("  TRACK {0:D2} {1}\n", track.num, Cue.TrackTypeStringForTrackType(trackType));
                    foreach (var index in track.Indexes)
                    {
                        //cue+bin has an implicit 150 sector pregap which neither the cue nor the bin has any awareness of
                        //except for the baked-in sector addressing.
                        //but, if there is an extra-long pregap, we want to reflect it this way
                        int lba = index.aba - 150;
                        if (lba <= 0 && index.num == 0 && track.num == 1)
                        {
                        }
                        //dont emit index 0 when it is the same as index 1, it is illegal for some reason
                        else if (index.num == 0 && index.aba == track.Indexes[1].aba)
                        {
                            //dont emit index 0 when it is the same as index 1, it confuses some cue parsers
                        }
                        else
                        {
                            sb.AppendFormat("    INDEX {0:D2} {1}\n", index.num, new Timestamp(lba).Value);
                        }
                    }
                }
            }

            return sb.ToString();
        }
Example #5
0
        public static Disc FromCuePath(string cuePath, CueBinPrefs prefs)
        {
            var ret = new Disc();

            ret.FromCuePathInternal(cuePath, prefs);

            ret.Structure.Synthesize_TOCPointsFromSessions();
            ret.Synthesize_SubcodeFromStructure();
            ret.Synthesize_TOCRawFromStructure();

            //try loading SBI. make sure its done after the subcode is synthesized!
            string sbiPath = Path.ChangeExtension(cuePath, "sbi");

            if (File.Exists(sbiPath) && SBI_Format.QuickCheckISSBI(sbiPath))
            {
                var sbi = new SBI_Format().LoadSBIPath(sbiPath);
                ret.ApplySBI(sbi);
            }

            return(ret);
        }
Example #6
0
		bool Dump(CueBin cueBin, string directoryTo, CueBinPrefs prefs)
		{
			ProgressReport pr = new ProgressReport();
			Thread workThread = new Thread(() =>
			{
				cueBin.Dump(directoryTo, prefs, pr);
			});

			ProgressDialog pd = new ProgressDialog(pr);
			pd.Show(this);
			this.Enabled = false;
			workThread.Start();
			for (; ; )
			{
				Application.DoEvents();
				Thread.Sleep(10);
				if (workThread.ThreadState != ThreadState.Running)
					break;
				pd.Update();
			}
			this.Enabled = true;
			pd.Dispose();
			return !pr.CancelSignal;
		}
Example #7
0
		CueBinPrefs GetCuePrefs()
		{
			var prefs = new CueBinPrefs();
			prefs.AnnotateCue = checkCueProp_Annotations.Checked;
			prefs.OneBlobPerTrack = checkCueProp_OneBlobPerTrack.Checked;
			prefs.ReallyDumpBin = false;
			prefs.SingleSession = true;
			return prefs;
		}
Example #8
0
        public CueBin DumpCueBin(string baseName, CueBinPrefs prefs)
        {
            if (Structure.Sessions.Count > 1)
                throw new NotSupportedException("can't dump cue+bin with more than 1 session yet");

            CueBin ret = new CueBin();
            ret.baseName = baseName;
            ret.disc = this;

            if (!prefs.OneBlobPerTrack)
            {
                //this is the preferred mode of dumping things. we will always write full sectors.
                string cue = new CUE_Format().GenerateCUE_OneBin(Structure,prefs);
                var bfd = new CueBin.BinFileDescriptor {name = baseName + ".bin"};
                ret.cue = string.Format("FILE \"{0}\" BINARY\n", bfd.name) + cue;
                ret.bins.Add(bfd);
                bfd.SectorSize = 2352;

                //skip the mandatory track 1 pregap! cue+bin files do not contain it
                for (int i = 150; i < Structure.LengthInSectors; i++)
                {
                    bfd.abas.Add(i);
                    bfd.aba_zeros.Add(false);
                }
            }
            else
            {
                //we build our own cue here (unlike above) because we need to build the cue and the output data at the same time
                StringBuilder sbCue = new StringBuilder();

                for (int i = 0; i < Structure.Sessions[0].Tracks.Count; i++)
                {
                    var track = Structure.Sessions[0].Tracks[i];
                    var bfd = new CueBin.BinFileDescriptor
                        {
                            name = baseName + string.Format(" (Track {0:D2}).bin", track.Number),
                            SectorSize = Cue.BINSectorSizeForTrackType(track.TrackType)
                        };
                    ret.bins.Add(bfd);
                    int aba = 0;

                    //skip the mandatory track 1 pregap! cue+bin files do not contain it
                    if (i == 0) aba = 150;

                    for (; aba < track.LengthInSectors; aba++)
                    {
                        int thisaba = track.Indexes[0].aba + aba;
                        bfd.abas.Add(thisaba);
                        bfd.aba_zeros.Add(false);
                    }
                    sbCue.AppendFormat("FILE \"{0}\" BINARY\n", bfd.name);

                    sbCue.AppendFormat("  TRACK {0:D2} {1}\n", track.Number, Cue.TrackTypeStringForTrackType(track.TrackType));
                    foreach (var index in track.Indexes)
                    {
                        int x = index.aba - track.Indexes[0].aba;
                        if (index.Number == 0 && index.aba == track.Indexes[1].aba)
                        {
                            //dont emit index 0 when it is the same as index 1, it is illegal for some reason
                        }
                        //else if (i==0 && index.num == 0)
                        //{
                        //    //don't generate the first index, it is illogical
                        //}
                        else
                        {
                            //track 1 included the lead-in at the beginning of it. sneak past that.
                            //if (i == 0) x -= 150;
                            sbCue.AppendFormat("    INDEX {0:D2} {1}\n", index.Number, new Timestamp(x).Value);
                        }
                    }
                }

                ret.cue = sbCue.ToString();
            }

            return ret;
        }
Example #9
0
        public static Disc FromCuePath(string cuePath, CueBinPrefs prefs)
        {
            var ret = new Disc();
            ret.FromCuePathInternal(cuePath, prefs);

            ret.Structure.Synthesize_TOCPointsFromSessions();
            ret.Synthesize_SubcodeFromStructure();
            ret.Synthesize_TOCRawFromStructure();

            //try loading SBI. make sure its done after the subcode is synthesized!
            string sbiPath = Path.ChangeExtension(cuePath, "sbi");
            if (File.Exists(sbiPath) && SBI_Format.QuickCheckISSBI(sbiPath))
            {
                var sbi = new SBI_Format().LoadSBIPath(sbiPath);
                ret.ApplySBI(sbi);
            }

            return ret;
        }
Example #10
0
        public void Dump(string directory, CueBinPrefs prefs, ProgressReport progress)
        {
            byte[] subcodeTemp = new byte[96];
            progress.TaskCount = 2;

            progress.Message = "Generating Cue";
            progress.ProgressEstimate = 1;
            progress.ProgressCurrent = 0;
            progress.InfoPresent = true;
            string cuePath = Path.Combine(directory, baseName + ".cue");
            if (prefs.DumpToBitbucket) { }
            else File.WriteAllText(cuePath, cue);

            progress.Message = "Writing bin(s)";
            progress.TaskCurrent = 1;
            progress.ProgressEstimate = bins.Sum(bfd => bfd.abas.Count);
            progress.ProgressCurrent = 0;
            if(!prefs.ReallyDumpBin) return;

            foreach (var bfd in bins)
            {
                int sectorSize = bfd.SectorSize;
                byte[] temp = new byte[2352];
                byte[] empty = new byte[2352];
                string trackBinFile = bfd.name;
                string trackBinPath = Path.Combine(directory, trackBinFile);
                string subQPath = Path.ChangeExtension(trackBinPath, ".sub.q");
                Stream fsSubQ = null;
                Stream fs;
                if(prefs.DumpToBitbucket)
                    fs = Stream.Null;
                else fs = new FileStream(trackBinPath, FileMode.Create, FileAccess.Write, FileShare.None);
                try
                {
                    if (prefs.DumpSubchannelQ)
                        if (prefs.DumpToBitbucket)
                            fsSubQ = Stream.Null;
                        else fsSubQ = new FileStream(subQPath, FileMode.Create, FileAccess.Write, FileShare.None);

                    for (int i = 0; i < bfd.abas.Count; i++)
                    {
                        if (progress.CancelSignal) return;

                        progress.ProgressCurrent++;
                        int aba = bfd.abas[i];
                        if (bfd.aba_zeros[i])
                        {
                            fs.Write(empty, 0, sectorSize);
                        }
                        else
                        {
                            if (sectorSize == 2352)
                                disc.ReadABA_2352(aba, temp, 0);
                            else if (sectorSize == 2048) disc.ReadABA_2048(aba, temp, 0);
                            else throw new InvalidOperationException();
                            fs.Write(temp, 0, sectorSize);

                            //write subQ if necessary
                            if (fsSubQ != null)
                            {
                                disc.Sectors[aba].SubcodeSector.ReadSubcodeDeinterleaved(subcodeTemp, 0);
                                fsSubQ.Write(subcodeTemp, 12, 12);
                            }
                        }
                    }
                }
                finally
                {
                    fs.Dispose();
                    if (fsSubQ != null) fsSubQ.Dispose();
                }
            }
        }
Example #11
0
 //NOT SUPPORTED RIGHT NOW
 //public string CreateRedumpReport()
 //{
 //    if (disc.TOC.Sessions[0].Tracks.Count != bins.Count)
 //        throw new InvalidOperationException("Cannot generate redump report on CueBin lacking OneBinPerTrack property");
 //    StringBuilder sb = new StringBuilder();
 //    for (int i = 0; i < disc.TOC.Sessions[0].Tracks.Count; i++)
 //    {
 //        var track = disc.TOC.Sessions[0].Tracks[i];
 //        var bfd = bins[i];
 //        //dump the track
 //        byte[] dump = new byte[track.length_aba * 2352];
 //        //TODO ????????? post-ABA unknown
 //        //for (int aba = 0; aba < track.length_aba; aba++)
 //        //    disc.ReadLBA_2352(bfd.lbas[lba],dump,lba*2352);
 //        string crc32 = string.Format("{0:X8}", CRC32.Calculate(dump));
 //        string md5 = Util.Hash_MD5(dump, 0, dump.Length);
 //        string sha1 = Util.Hash_SHA1(dump, 0, dump.Length);
 //        int pregap = track.Indexes[1].lba - track.Indexes[0].lba;
 //        Timestamp pregap_ts = new Timestamp(pregap);
 //        Timestamp len_ts = new Timestamp(track.length_lba);
 //        sb.AppendFormat("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}\t{8}\n",
 //            i,
 //            Cue.RedumpTypeStringForTrackType(track.TrackType),
 //            pregap_ts.Value,
 //            len_ts.Value,
 //            track.length_lba,
 //            track.length_lba*Cue.BINSectorSizeForTrackType(track.TrackType),
 //            crc32,
 //            md5,
 //            sha1
 //            );
 //    }
 //    return sb.ToString();
 //}
 public void Dump(string directory, CueBinPrefs prefs)
 {
     ProgressReport pr = new ProgressReport();
     Dump(directory, prefs, pr);
 }
Example #12
0
        /// <summary>
        /// Generates the CUE file for the provided DiscStructure
        /// </summary>
        public string GenerateCUE_OneBin(DiscStructure structure, CueBinPrefs prefs)
        {
            if (prefs.OneBlobPerTrack)
            {
                throw new InvalidOperationException("OneBinPerTrack passed to GenerateCUE_OneBin");
            }

            //this generates a single-file cue!!!!!!! dont expect it to generate bin-per-track!
            StringBuilder sb = new StringBuilder();

            foreach (var session in structure.Sessions)
            {
                if (!prefs.SingleSession)
                {
                    //dont want to screw around with sessions for now
                    sb.AppendFormat("SESSION {0:D2}\n", session.num);
                    if (prefs.AnnotateCue)
                    {
                        sb.AppendFormat("REM ; session (length={0})\n", session.length_aba);
                    }
                }

                foreach (var track in session.Tracks)
                {
                    ETrackType trackType = track.TrackType;

                    //mutate track type according to our principle of canonicalization
                    if (trackType == ETrackType.Mode1_2048 && prefs.DumpECM)
                    {
                        trackType = ETrackType.Mode1_2352;
                    }

                    sb.AppendFormat("  TRACK {0:D2} {1}\n", track.Number, Cue.TrackTypeStringForTrackType(trackType));
                    if (prefs.AnnotateCue)
                    {
                        sb.AppendFormat("  REM ; track (length={0})\n", track.LengthInSectors);
                    }

                    foreach (var index in track.Indexes)
                    {
                        //cue+bin has an implicit 150 sector pregap which neither the cue nor the bin has any awareness of
                        //except for the baked-in sector addressing.
                        //but, if there is an extra-long pregap, we want to reflect it this way
                        int lba = index.aba - 150;
                        if (lba <= 0 && index.Number == 0 && track.Number == 1)
                        {
                        }
                        //dont emit index 0 when it is the same as index 1, it is illegal for some reason
                        else if (index.Number == 0 && index.aba == track.Indexes[1].aba)
                        {
                            //dont emit index 0 when it is the same as index 1, it confuses some cue parsers
                        }
                        else
                        {
                            sb.AppendFormat("    INDEX {0:D2} {1}\n", index.Number, new Timestamp(lba).Value);
                        }
                    }
                }
            }

            return(sb.ToString());
        }
Example #13
0
		void FromCueInternal(Cue cue, string cueDir, CueBinPrefs prefs)
		{
			//TODO - add cue directory to CueBinPrefs???? could make things cleaner...

			var session = new DiscTOC.Session {num = 1};
			TOC.Sessions.Add(session);
			var pregap_sector = new Sector_Zero();

			int curr_track = 1;

			foreach (var cue_file in cue.Files)
			{
				//structural validation
				if (cue_file.Tracks.Count < 1) throw new Cue.CueBrokenException("`You must specify at least one track per file.`");

				string blobPath = Path.Combine(cueDir, cue_file.Path);

				if (CueFileResolver.ContainsKey(cue_file.Path))
					blobPath = CueFileResolver[cue_file.Path];

				int blob_sectorsize = Cue.BINSectorSizeForTrackType(cue_file.Tracks[0].TrackType);
				int blob_length_aba;
				long blob_length_bytes;
				IBlob cue_blob;

				//try any way we can to acquire a file
				if (!File.Exists(blobPath) && prefs.ExtensionAware)
				{
					blobPath = FindAlternateExtensionFile(blobPath, prefs.CaseSensitive, cueDir);
				}

				if (!File.Exists(blobPath))
					throw new DiscReferenceException(blobPath, "");

				//some simple rules to mutate the file type if we received something fishy
				string blobPathExt = Path.GetExtension(blobPath).ToLower();
				if (blobPathExt == ".ape") cue_file.FileType = Cue.CueFileType.Wave;
				if (blobPathExt == ".mp3") cue_file.FileType = Cue.CueFileType.Wave;
				if (blobPathExt == ".mpc") cue_file.FileType = Cue.CueFileType.Wave;
				if (blobPathExt == ".flac") cue_file.FileType = Cue.CueFileType.Wave;
				if (blobPathExt == ".ecm") cue_file.FileType = Cue.CueFileType.ECM;

				if (cue_file.FileType == Cue.CueFileType.Binary || cue_file.FileType == Cue.CueFileType.Unspecified)
				{
					//make a blob for the file
					Blob_RawFile blob = new Blob_RawFile {PhysicalPath = blobPath};
					Blobs.Add(blob);

					blob_length_aba = (int)(blob.Length / blob_sectorsize);
					blob_length_bytes = blob.Length;
					cue_blob = blob;
				}
				else if (cue_file.FileType == Cue.CueFileType.ECM)
				{
					if (!Blob_ECM.IsECM(blobPath))
					{
						throw new DiscReferenceException(blobPath, "an ECM file was specified or detected, but it isn't a valid ECM file. You've got issues. Consult your iso vendor.");
					}
					Blob_ECM blob = new Blob_ECM();
					Blobs.Add(blob);
					blob.Parse(blobPath);
					cue_blob = blob;
					blob_length_aba = (int)(blob.Length / blob_sectorsize);
					blob_length_bytes = blob.Length;
				}
				else if (cue_file.FileType == Cue.CueFileType.Wave)
				{
					Blob_WaveFile blob = new Blob_WaveFile();
					Blobs.Add(blob);

					try
					{
						//check whether we can load the wav directly
						bool loaded = false;
						if (File.Exists(blobPath) && Path.GetExtension(blobPath).ToUpper() == ".WAV")
						{
							try
							{
								blob.Load(blobPath);
								loaded = true;
							}
							catch
							{
							}
						}

						//if that didnt work or wasnt possible, try loading it through ffmpeg
						if (!loaded)
						{
							FFMpeg ffmpeg = new FFMpeg();
							if (!ffmpeg.QueryServiceAvailable())
							{
								throw new DiscReferenceException(blobPath, "No decoding service was available (make sure ffmpeg.exe is available. even though this may be a wav, ffmpeg is used to load oddly formatted wave files. If you object to this, please send us a note and we'll see what we can do. It shouldn't be too hard.)");
							}
							AudioDecoder dec = new AudioDecoder();
							byte[] buf = dec.AcquireWaveData(blobPath);
							blob.Load(new MemoryStream(buf));
							WasSlowLoad = true;
						}
					}
					catch (Exception ex)
					{
						throw new DiscReferenceException(blobPath, ex);
					}

					blob_length_aba = (int)(blob.Length / blob_sectorsize);
					blob_length_bytes = blob.Length;
					cue_blob = blob;
				}
				else throw new Exception("Internal error - Unhandled cue blob type");

				//TODO - make CueTimestamp better, and also make it a struct, and also just make it DiscTimestamp

				//start timekeeping for the blob. every time we hit an index, this will advance
				int blob_timestamp = 0;

				//because we can have different size sectors in a blob, we need to keep a file cursor within the blob
				long blob_cursor = 0;

				//the aba that this cue blob starts on
				int blob_disc_aba_start = Sectors.Count;

				//for each track within the file, create an index 0 if it is missing.
				//also check to make sure there is an index 1
				for (int t = 0; t < cue_file.Tracks.Count; t++)
				{
					var cue_track = cue_file.Tracks[t];
					if (!cue_track.Indexes.ContainsKey(1))
						throw new Cue.CueBrokenException("Track was missing an index 01");
					if (!cue_track.Indexes.ContainsKey(0))
					{
						//index 0 will default to the same as index 1.
						//i am not sure whether it is valid to have two indexes with the same timestamp.
						//we will do this to simplify some processing, but we can purge it in a later pass if we need to.
						var cti = new Cue.CueTrackIndex(0);
						cue_track.Indexes[0] = cti;
						cti.Timestamp = cue_track.Indexes[1].Timestamp;
					}
				}

				//validate that the first index in the file is 00:00:00
				if (cue_file.Tracks[0].Indexes[0].Timestamp.ABA != 0) throw new Cue.CueBrokenException("`The first index of a blob must start at 00:00:00.`");


				//for each track within the file:
				for (int t = 0; t < cue_file.Tracks.Count; t++)
				{
					var cue_track = cue_file.Tracks[t];

					//record the disc ABA that this track started on
					int track_disc_aba_start = Sectors.Count;

					//record the pregap location. it will default to the start of the track unless we supplied a pregap command
					int track_disc_pregap_aba = track_disc_aba_start;

					int blob_track_start = blob_timestamp;

					//once upon a time we had a check here to prevent a single blob from containing variant sector sizes. but we support that now.

					//check integrity of track sequence and setup data structures
					//TODO - check for skipped tracks in cue parser instead
					if (cue_track.TrackNum != curr_track) throw new Cue.CueBrokenException("Found a cue with skipped tracks");
					var toc_track = new DiscTOC.Track();
					toc_track.num = curr_track;
					toc_track.TrackType = cue_track.TrackType;
					session.Tracks.Add(toc_track);

					if (curr_track == 1)
					{
						if (cue_track.PreGap.ABA != 0)
							throw new InvalidOperationException("not supported (yet): cue files with track 1 pregaps");
						//but now we add one anyway, because every existing cue+bin seems to implicitly specify this
						cue_track.PreGap = new Timestamp(150);
					}

					//check whether a pregap is requested.
					//this causes empty sectors to get generated without consuming data from the blob
					if (cue_track.PreGap.ABA > 0)
					{
						for (int i = 0; i < cue_track.PreGap.ABA; i++)
						{
							Sectors.Add(new SectorEntry(pregap_sector));
						}
					}

					//look ahead to the next track's index 1 so we can see how long this track's last index is
					//or, for the last track, use the length of the file
					int track_length_aba;
					if (t == cue_file.Tracks.Count - 1)
						track_length_aba = blob_length_aba - blob_timestamp;
					else track_length_aba = cue_file.Tracks[t + 1].Indexes[1].Timestamp.ABA - blob_timestamp;
					//toc_track.length_aba = track_length_aba; //xxx

					//find out how many indexes we have
					int num_indexes = 0;
					for (num_indexes = 0; num_indexes <= 99; num_indexes++)
						if (!cue_track.Indexes.ContainsKey(num_indexes)) break;

					//for each index, calculate length of index and then emit it
					for (int index = 0; index < num_indexes; index++)
					{
						bool is_last_index = index == num_indexes - 1;

						//install index into hierarchy
						var toc_index = new DiscTOC.Index {num = index};
						toc_track.Indexes.Add(toc_index);
						if (index == 0)
						{
							toc_index.aba = track_disc_pregap_aba - (cue_track.Indexes[1].Timestamp.ABA - cue_track.Indexes[0].Timestamp.ABA);
						}
						else toc_index.aba = Sectors.Count;

						//calculate length of the index
						//if it is the last index then we use our calculation from before, otherwise we check the next index
						int index_length_aba;
						if (is_last_index)
							index_length_aba = track_length_aba - (blob_timestamp - blob_track_start);
						else index_length_aba = cue_track.Indexes[index + 1].Timestamp.ABA - blob_timestamp;

						//emit sectors
						for (int aba = 0; aba < index_length_aba; aba++)
						{
							bool is_last_aba_in_index = (aba == index_length_aba - 1);
							bool is_last_aba_in_track = is_last_aba_in_index && is_last_index;

							switch (cue_track.TrackType)
							{
								case ETrackType.Audio:  //all 2352 bytes are present
								case ETrackType.Mode1_2352: //2352 bytes are present, containing 2048 bytes of user data as well as ECM
								case ETrackType.Mode2_2352: //2352 bytes are present, containing the entirety of a mode2 sector (could be form0,1,2)
									{
										//these cases are all 2352 bytes
										//in all these cases, either no ECM is present or ECM is provided.
										//so we just emit a Sector_Raw
										Sector_RawBlob sector_rawblob = new Sector_RawBlob
											{
												Blob = cue_blob,
												Offset = blob_cursor
											};
										blob_cursor += 2352;
										Sector_Mode1_or_Mode2_2352 sector_raw;
										if(cue_track.TrackType == ETrackType.Mode1_2352)
											sector_raw  = new Sector_Mode1_2352();
										else if (cue_track.TrackType == ETrackType.Audio)
											sector_raw = new Sector_Mode1_2352(); //TODO should probably make a new sector adapter which errors if 2048B are requested
										else if (cue_track.TrackType == ETrackType.Mode2_2352)
											sector_raw = new Sector_Mode2_2352();
										else throw new InvalidOperationException();

										sector_raw.BaseSector = sector_rawblob;

										Sectors.Add(new SectorEntry(sector_raw));
										break;
									}
								case ETrackType.Mode1_2048:
									//2048 bytes are present. ECM needs to be generated to create a full sector
									{
										//ECM needs to know the sector number so we have to record that here
										int curr_disc_aba = Sectors.Count;
										var sector_2048 = new Sector_Mode1_2048(curr_disc_aba + 150)
											{
												Blob = new ECMCacheBlob(cue_blob),
												Offset = blob_cursor
											};
										blob_cursor += 2048;
										Sectors.Add(new SectorEntry(sector_2048));
										break;
									}
							} //switch(TrackType)

							//we've emitted an ABA, so consume it from the blob
							blob_timestamp++;

						} //aba emit loop

					} //index loop

					//check whether a postgap is requested. if it is, we need to generate silent sectors
					for (int i = 0; i < cue_track.PostGap.ABA; i++)
					{
						Sectors.Add(new SectorEntry(pregap_sector));
					}

					//we're done with the track now.
					//record its length:
					toc_track.length_aba = Sectors.Count - toc_track.Indexes[1].aba;
					curr_track++;

					//if we ran off the end of the blob, pad it with zeroes, I guess
					if (blob_cursor > blob_length_bytes)
					{
						//mutate the blob to an encapsulating Blob_ZeroPadAdapter
						Blobs[Blobs.Count - 1] = new Blob_ZeroPadAdapter(Blobs[Blobs.Count - 1], blob_length_bytes, blob_cursor - blob_length_bytes);
					}

				} //track loop
			} //file loop

			//finally, analyze the length of the sessions and the entire disc by summing the lengths of the tracks
			//this is a little more complex than it looks, because the length of a thing is not determined by summing it
			//but rather by the difference in abas between start and end
			TOC.length_aba = 0;
			foreach (var toc_session in TOC.Sessions)
			{
				var firstTrack = toc_session.Tracks[0];

				//track 0, index 0 is actually -150. but cue sheets will never say that
				//firstTrack.Indexes[0].aba -= 150;

				var lastTrack = toc_session.Tracks[toc_session.Tracks.Count - 1];
				session.length_aba = lastTrack.Indexes[1].aba + lastTrack.length_aba - firstTrack.Indexes[0].aba;
				TOC.length_aba += toc_session.length_aba;
			}
		}
Example #14
0
		void FromCuePathInternal(string cuePath, CueBinPrefs prefs)
		{
			string cueDir = Path.GetDirectoryName(cuePath);
			var cue = new Cue();
			cue.LoadFromPath(cuePath);
			FromCueInternal(cue, cueDir, prefs);
		}
Example #15
0
		public static Disc FromCuePath(string cuePath, CueBinPrefs prefs)
		{
			var ret = new Disc();
			ret.FromCuePathInternal(cuePath, prefs);
			ret.TOC.GeneratePoints();
			ret.PopulateQSubchannel();
			return ret;
		}
Example #16
0
        void FromCueInternal(Cue cue, string cueDir, CueBinPrefs prefs)
        {
            //TODO - add cue directory to CueBinPrefs???? could make things cleaner...

            var session = new DiscTOC.Session {
                num = 1
            };

            TOC.Sessions.Add(session);
            var pregap_sector = new Sector_Zero();

            int curr_track = 1;

            foreach (var cue_file in cue.Files)
            {
                //structural validation
                if (cue_file.Tracks.Count < 1)
                {
                    throw new Cue.CueBrokenException("`You must specify at least one track per file.`");
                }

                string blobPath = Path.Combine(cueDir, cue_file.Path);

                if (CueFileResolver.ContainsKey(cue_file.Path))
                {
                    blobPath = CueFileResolver[cue_file.Path];
                }

                int   blob_sectorsize = Cue.BINSectorSizeForTrackType(cue_file.Tracks[0].TrackType);
                int   blob_length_aba;
                long  blob_length_bytes;
                IBlob cue_blob;

                //try any way we can to acquire a file
                if (!File.Exists(blobPath) && prefs.ExtensionAware)
                {
                    blobPath = FindAlternateExtensionFile(blobPath, prefs.CaseSensitive, cueDir);
                }

                if (!File.Exists(blobPath))
                {
                    throw new DiscReferenceException(blobPath, "");
                }

                //some simple rules to mutate the file type if we received something fishy
                string blobPathExt = Path.GetExtension(blobPath).ToLower();
                if (blobPathExt == ".ape")
                {
                    cue_file.FileType = Cue.CueFileType.Wave;
                }
                if (blobPathExt == ".mp3")
                {
                    cue_file.FileType = Cue.CueFileType.Wave;
                }
                if (blobPathExt == ".mpc")
                {
                    cue_file.FileType = Cue.CueFileType.Wave;
                }
                if (blobPathExt == ".flac")
                {
                    cue_file.FileType = Cue.CueFileType.Wave;
                }
                if (blobPathExt == ".ecm")
                {
                    cue_file.FileType = Cue.CueFileType.ECM;
                }

                if (cue_file.FileType == Cue.CueFileType.Binary || cue_file.FileType == Cue.CueFileType.Unspecified)
                {
                    //make a blob for the file
                    Blob_RawFile blob = new Blob_RawFile {
                        PhysicalPath = blobPath
                    };
                    Blobs.Add(blob);

                    blob_length_aba   = (int)(blob.Length / blob_sectorsize);
                    blob_length_bytes = blob.Length;
                    cue_blob          = blob;
                }
                else if (cue_file.FileType == Cue.CueFileType.ECM)
                {
                    if (!Blob_ECM.IsECM(blobPath))
                    {
                        throw new DiscReferenceException(blobPath, "an ECM file was specified or detected, but it isn't a valid ECM file. You've got issues. Consult your iso vendor.");
                    }
                    Blob_ECM blob = new Blob_ECM();
                    Blobs.Add(blob);
                    blob.Parse(blobPath);
                    cue_blob          = blob;
                    blob_length_aba   = (int)(blob.Length / blob_sectorsize);
                    blob_length_bytes = blob.Length;
                }
                else if (cue_file.FileType == Cue.CueFileType.Wave)
                {
                    Blob_WaveFile blob = new Blob_WaveFile();
                    Blobs.Add(blob);

                    try
                    {
                        //check whether we can load the wav directly
                        bool loaded = false;
                        if (File.Exists(blobPath) && Path.GetExtension(blobPath).ToUpper() == ".WAV")
                        {
                            try
                            {
                                blob.Load(blobPath);
                                loaded = true;
                            }
                            catch
                            {
                            }
                        }

                        //if that didnt work or wasnt possible, try loading it through ffmpeg
                        if (!loaded)
                        {
                            FFMpeg ffmpeg = new FFMpeg();
                            if (!ffmpeg.QueryServiceAvailable())
                            {
                                throw new DiscReferenceException(blobPath, "No decoding service was available (make sure ffmpeg.exe is available. even though this may be a wav, ffmpeg is used to load oddly formatted wave files. If you object to this, please send us a note and we'll see what we can do. It shouldn't be too hard.)");
                            }
                            AudioDecoder dec = new AudioDecoder();
                            byte[]       buf = dec.AcquireWaveData(blobPath);
                            blob.Load(new MemoryStream(buf));
                            WasSlowLoad = true;
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new DiscReferenceException(blobPath, ex);
                    }

                    blob_length_aba   = (int)(blob.Length / blob_sectorsize);
                    blob_length_bytes = blob.Length;
                    cue_blob          = blob;
                }
                else
                {
                    throw new Exception("Internal error - Unhandled cue blob type");
                }

                //TODO - make CueTimestamp better, and also make it a struct, and also just make it DiscTimestamp

                //start timekeeping for the blob. every time we hit an index, this will advance
                int blob_timestamp = 0;

                //because we can have different size sectors in a blob, we need to keep a file cursor within the blob
                long blob_cursor = 0;

                //the aba that this cue blob starts on
                int blob_disc_aba_start = Sectors.Count;

                //for each track within the file, create an index 0 if it is missing.
                //also check to make sure there is an index 1
                for (int t = 0; t < cue_file.Tracks.Count; t++)
                {
                    var cue_track = cue_file.Tracks[t];
                    if (!cue_track.Indexes.ContainsKey(1))
                    {
                        throw new Cue.CueBrokenException("Track was missing an index 01");
                    }
                    if (!cue_track.Indexes.ContainsKey(0))
                    {
                        //index 0 will default to the same as index 1.
                        //i am not sure whether it is valid to have two indexes with the same timestamp.
                        //we will do this to simplify some processing, but we can purge it in a later pass if we need to.
                        var cti = new Cue.CueTrackIndex(0);
                        cue_track.Indexes[0] = cti;
                        cti.Timestamp        = cue_track.Indexes[1].Timestamp;
                    }
                }

                //validate that the first index in the file is 00:00:00
                if (cue_file.Tracks[0].Indexes[0].Timestamp.ABA != 0)
                {
                    throw new Cue.CueBrokenException("`The first index of a blob must start at 00:00:00.`");
                }


                //for each track within the file:
                for (int t = 0; t < cue_file.Tracks.Count; t++)
                {
                    var cue_track = cue_file.Tracks[t];

                    //record the disc ABA that this track started on
                    int track_disc_aba_start = Sectors.Count;

                    //record the pregap location. it will default to the start of the track unless we supplied a pregap command
                    int track_disc_pregap_aba = track_disc_aba_start;

                    int blob_track_start = blob_timestamp;

                    //once upon a time we had a check here to prevent a single blob from containing variant sector sizes. but we support that now.

                    //check integrity of track sequence and setup data structures
                    //TODO - check for skipped tracks in cue parser instead
                    if (cue_track.TrackNum != curr_track)
                    {
                        throw new Cue.CueBrokenException("Found a cue with skipped tracks");
                    }
                    var toc_track = new DiscTOC.Track();
                    toc_track.num       = curr_track;
                    toc_track.TrackType = cue_track.TrackType;
                    session.Tracks.Add(toc_track);

                    if (curr_track == 1)
                    {
                        if (cue_track.PreGap.ABA != 0)
                        {
                            throw new InvalidOperationException("not supported (yet): cue files with track 1 pregaps");
                        }
                        //but now we add one anyway, because every existing cue+bin seems to implicitly specify this
                        cue_track.PreGap = new Timestamp(150);
                    }

                    //check whether a pregap is requested.
                    //this causes empty sectors to get generated without consuming data from the blob
                    if (cue_track.PreGap.ABA > 0)
                    {
                        for (int i = 0; i < cue_track.PreGap.ABA; i++)
                        {
                            Sectors.Add(new SectorEntry(pregap_sector));
                        }
                    }

                    //look ahead to the next track's index 1 so we can see how long this track's last index is
                    //or, for the last track, use the length of the file
                    int track_length_aba;
                    if (t == cue_file.Tracks.Count - 1)
                    {
                        track_length_aba = blob_length_aba - blob_timestamp;
                    }
                    else
                    {
                        track_length_aba = cue_file.Tracks[t + 1].Indexes[1].Timestamp.ABA - blob_timestamp;
                    }
                    //toc_track.length_aba = track_length_aba; //xxx

                    //find out how many indexes we have
                    int num_indexes = 0;
                    for (num_indexes = 0; num_indexes <= 99; num_indexes++)
                    {
                        if (!cue_track.Indexes.ContainsKey(num_indexes))
                        {
                            break;
                        }
                    }

                    //for each index, calculate length of index and then emit it
                    for (int index = 0; index < num_indexes; index++)
                    {
                        bool is_last_index = index == num_indexes - 1;

                        //install index into hierarchy
                        var toc_index = new DiscTOC.Index {
                            num = index
                        };
                        toc_track.Indexes.Add(toc_index);
                        if (index == 0)
                        {
                            toc_index.aba = track_disc_pregap_aba - (cue_track.Indexes[1].Timestamp.ABA - cue_track.Indexes[0].Timestamp.ABA);
                        }
                        else
                        {
                            toc_index.aba = Sectors.Count;
                        }

                        //calculate length of the index
                        //if it is the last index then we use our calculation from before, otherwise we check the next index
                        int index_length_aba;
                        if (is_last_index)
                        {
                            index_length_aba = track_length_aba - (blob_timestamp - blob_track_start);
                        }
                        else
                        {
                            index_length_aba = cue_track.Indexes[index + 1].Timestamp.ABA - blob_timestamp;
                        }

                        //emit sectors
                        for (int aba = 0; aba < index_length_aba; aba++)
                        {
                            bool is_last_aba_in_index = (aba == index_length_aba - 1);
                            bool is_last_aba_in_track = is_last_aba_in_index && is_last_index;

                            switch (cue_track.TrackType)
                            {
                            case ETrackType.Audio:                                      //all 2352 bytes are present
                            case ETrackType.Mode1_2352:                                 //2352 bytes are present, containing 2048 bytes of user data as well as ECM
                            case ETrackType.Mode2_2352:                                 //2352 bytes are present, containing the entirety of a mode2 sector (could be form0,1,2)
                            {
                                //these cases are all 2352 bytes
                                //in all these cases, either no ECM is present or ECM is provided.
                                //so we just emit a Sector_Raw
                                Sector_RawBlob sector_rawblob = new Sector_RawBlob
                                {
                                    Blob   = cue_blob,
                                    Offset = blob_cursor
                                };
                                blob_cursor += 2352;
                                Sector_Mode1_or_Mode2_2352 sector_raw;
                                if (cue_track.TrackType == ETrackType.Mode1_2352)
                                {
                                    sector_raw = new Sector_Mode1_2352();
                                }
                                else if (cue_track.TrackType == ETrackType.Audio)
                                {
                                    sector_raw = new Sector_Mode1_2352();                                                     //TODO should probably make a new sector adapter which errors if 2048B are requested
                                }
                                else if (cue_track.TrackType == ETrackType.Mode2_2352)
                                {
                                    sector_raw = new Sector_Mode2_2352();
                                }
                                else
                                {
                                    throw new InvalidOperationException();
                                }

                                sector_raw.BaseSector = sector_rawblob;

                                Sectors.Add(new SectorEntry(sector_raw));
                                break;
                            }

                            case ETrackType.Mode1_2048:
                                //2048 bytes are present. ECM needs to be generated to create a full sector
                            {
                                //ECM needs to know the sector number so we have to record that here
                                int curr_disc_aba = Sectors.Count;
                                var sector_2048   = new Sector_Mode1_2048(curr_disc_aba + 150)
                                {
                                    Blob   = new ECMCacheBlob(cue_blob),
                                    Offset = blob_cursor
                                };
                                blob_cursor += 2048;
                                Sectors.Add(new SectorEntry(sector_2048));
                                break;
                            }
                            }                             //switch(TrackType)

                            //we've emitted an ABA, so consume it from the blob
                            blob_timestamp++;
                        }                 //aba emit loop
                    }                     //index loop

                    //check whether a postgap is requested. if it is, we need to generate silent sectors
                    for (int i = 0; i < cue_track.PostGap.ABA; i++)
                    {
                        Sectors.Add(new SectorEntry(pregap_sector));
                    }

                    //we're done with the track now.
                    //record its length:
                    toc_track.length_aba = Sectors.Count - toc_track.Indexes[1].aba;
                    curr_track++;

                    //if we ran off the end of the blob, pad it with zeroes, I guess
                    if (blob_cursor > blob_length_bytes)
                    {
                        //mutate the blob to an encapsulating Blob_ZeroPadAdapter
                        Blobs[Blobs.Count - 1] = new Blob_ZeroPadAdapter(Blobs[Blobs.Count - 1], blob_length_bytes, blob_cursor - blob_length_bytes);
                    }
                }         //track loop
            }             //file loop

            //finally, analyze the length of the sessions and the entire disc by summing the lengths of the tracks
            //this is a little more complex than it looks, because the length of a thing is not determined by summing it
            //but rather by the difference in abas between start and end
            TOC.length_aba = 0;
            foreach (var toc_session in TOC.Sessions)
            {
                var firstTrack = toc_session.Tracks[0];

                //track 0, index 0 is actually -150. but cue sheets will never say that
                //firstTrack.Indexes[0].aba -= 150;

                var lastTrack = toc_session.Tracks[toc_session.Tracks.Count - 1];
                session.length_aba = lastTrack.Indexes[1].aba + lastTrack.length_aba - firstTrack.Indexes[0].aba;
                TOC.length_aba    += toc_session.length_aba;
            }
        }
Example #17
0
        public CueBin DumpCueBin(string baseName, CueBinPrefs prefs)
        {
            if (Structure.Sessions.Count > 1)
            {
                throw new NotSupportedException("can't dump cue+bin with more than 1 session yet");
            }

            CueBin ret = new CueBin();

            ret.baseName = baseName;
            ret.disc     = this;

            if (!prefs.OneBlobPerTrack)
            {
                //this is the preferred mode of dumping things. we will always write full sectors.
                string cue = new CUE_Format().GenerateCUE_OneBin(Structure, prefs);
                var    bfd = new CueBin.BinFileDescriptor {
                    name = baseName + ".bin"
                };
                ret.cue = string.Format("FILE \"{0}\" BINARY\n", bfd.name) + cue;
                ret.bins.Add(bfd);
                bfd.SectorSize = 2352;

                //skip the mandatory track 1 pregap! cue+bin files do not contain it
                for (int i = 150; i < Structure.LengthInSectors; i++)
                {
                    bfd.abas.Add(i);
                    bfd.aba_zeros.Add(false);
                }
            }
            else
            {
                //we build our own cue here (unlike above) because we need to build the cue and the output data at the same time
                StringBuilder sbCue = new StringBuilder();

                for (int i = 0; i < Structure.Sessions[0].Tracks.Count; i++)
                {
                    var track = Structure.Sessions[0].Tracks[i];
                    var bfd   = new CueBin.BinFileDescriptor
                    {
                        name       = baseName + string.Format(" (Track {0:D2}).bin", track.Number),
                        SectorSize = Cue.BINSectorSizeForTrackType(track.TrackType)
                    };
                    ret.bins.Add(bfd);
                    int aba = 0;

                    //skip the mandatory track 1 pregap! cue+bin files do not contain it
                    if (i == 0)
                    {
                        aba = 150;
                    }

                    for (; aba < track.LengthInSectors; aba++)
                    {
                        int thisaba = track.Indexes[0].aba + aba;
                        bfd.abas.Add(thisaba);
                        bfd.aba_zeros.Add(false);
                    }
                    sbCue.AppendFormat("FILE \"{0}\" BINARY\n", bfd.name);

                    sbCue.AppendFormat("  TRACK {0:D2} {1}\n", track.Number, Cue.TrackTypeStringForTrackType(track.TrackType));
                    foreach (var index in track.Indexes)
                    {
                        int x = index.aba - track.Indexes[0].aba;
                        if (index.Number == 0 && index.aba == track.Indexes[1].aba)
                        {
                            //dont emit index 0 when it is the same as index 1, it is illegal for some reason
                        }
                        //else if (i==0 && index.num == 0)
                        //{
                        //    //don't generate the first index, it is illogical
                        //}
                        else
                        {
                            //track 1 included the lead-in at the beginning of it. sneak past that.
                            //if (i == 0) x -= 150;
                            sbCue.AppendFormat("    INDEX {0:D2} {1}\n", index.Number, new Timestamp(x).Value);
                        }
                    }
                }

                ret.cue = sbCue.ToString();
            }

            return(ret);
        }
Example #18
0
        public void Dump(string directory, CueBinPrefs prefs, ProgressReport progress)
        {
            byte[] subcodeTemp = new byte[96];
            progress.TaskCount = 2;

            progress.Message          = "Generating Cue";
            progress.ProgressEstimate = 1;
            progress.ProgressCurrent  = 0;
            progress.InfoPresent      = true;
            string cuePath = Path.Combine(directory, baseName + ".cue");

            if (prefs.DumpToBitbucket)
            {
            }
            else
            {
                File.WriteAllText(cuePath, cue);
            }

            progress.Message          = "Writing bin(s)";
            progress.TaskCurrent      = 1;
            progress.ProgressEstimate = bins.Sum(bfd => bfd.abas.Count);
            progress.ProgressCurrent  = 0;
            if (!prefs.ReallyDumpBin)
            {
                return;
            }

            foreach (var bfd in bins)
            {
                int    sectorSize   = bfd.SectorSize;
                byte[] temp         = new byte[2352];
                byte[] empty        = new byte[2352];
                string trackBinFile = bfd.name;
                string trackBinPath = Path.Combine(directory, trackBinFile);
                string subQPath     = Path.ChangeExtension(trackBinPath, ".sub.q");
                Stream fsSubQ       = null;
                Stream fs;
                if (prefs.DumpToBitbucket)
                {
                    fs = Stream.Null;
                }
                else
                {
                    fs = new FileStream(trackBinPath, FileMode.Create, FileAccess.Write, FileShare.None);
                }
                try
                {
                    if (prefs.DumpSubchannelQ)
                    {
                        if (prefs.DumpToBitbucket)
                        {
                            fsSubQ = Stream.Null;
                        }
                        else
                        {
                            fsSubQ = new FileStream(subQPath, FileMode.Create, FileAccess.Write, FileShare.None);
                        }
                    }

                    for (int i = 0; i < bfd.abas.Count; i++)
                    {
                        if (progress.CancelSignal)
                        {
                            return;
                        }

                        progress.ProgressCurrent++;
                        int aba = bfd.abas[i];
                        if (bfd.aba_zeros[i])
                        {
                            fs.Write(empty, 0, sectorSize);
                        }
                        else
                        {
                            if (sectorSize == 2352)
                            {
                                disc.ReadABA_2352(aba, temp, 0);
                            }
                            else if (sectorSize == 2048)
                            {
                                disc.ReadABA_2048(aba, temp, 0);
                            }
                            else
                            {
                                throw new InvalidOperationException();
                            }
                            fs.Write(temp, 0, sectorSize);

                            //write subQ if necessary
                            if (fsSubQ != null)
                            {
                                disc.Sectors[aba].SubcodeSector.ReadSubcodeDeinterleaved(subcodeTemp, 0);
                                fsSubQ.Write(subcodeTemp, 12, 12);
                            }
                        }
                    }
                }
                finally
                {
                    fs.Dispose();
                    if (fsSubQ != null)
                    {
                        fsSubQ.Dispose();
                    }
                }
            }
        }
Example #19
0
        //NOT SUPPORTED RIGHT NOW
        //public string CreateRedumpReport()
        //{
        //    if (disc.TOC.Sessions[0].Tracks.Count != bins.Count)
        //        throw new InvalidOperationException("Cannot generate redump report on CueBin lacking OneBinPerTrack property");
        //    StringBuilder sb = new StringBuilder();
        //    for (int i = 0; i < disc.TOC.Sessions[0].Tracks.Count; i++)
        //    {
        //        var track = disc.TOC.Sessions[0].Tracks[i];
        //        var bfd = bins[i];

        //        //dump the track
        //        byte[] dump = new byte[track.length_aba * 2352];
        //        //TODO ????????? post-ABA unknown
        //        //for (int aba = 0; aba < track.length_aba; aba++)
        //        //    disc.ReadLBA_2352(bfd.lbas[lba],dump,lba*2352);
        //        string crc32 = string.Format("{0:X8}", CRC32.Calculate(dump));
        //        string md5 = Util.Hash_MD5(dump, 0, dump.Length);
        //        string sha1 = Util.Hash_SHA1(dump, 0, dump.Length);

        //        int pregap = track.Indexes[1].lba - track.Indexes[0].lba;
        //        Timestamp pregap_ts = new Timestamp(pregap);
        //        Timestamp len_ts = new Timestamp(track.length_lba);
        //        sb.AppendFormat("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}\t{8}\n",
        //            i,
        //            Cue.RedumpTypeStringForTrackType(track.TrackType),
        //            pregap_ts.Value,
        //            len_ts.Value,
        //            track.length_lba,
        //            track.length_lba*Cue.BINSectorSizeForTrackType(track.TrackType),
        //            crc32,
        //            md5,
        //            sha1
        //            );
        //    }
        //    return sb.ToString();
        //}

        public void Dump(string directory, CueBinPrefs prefs)
        {
            ProgressReport pr = new ProgressReport();

            Dump(directory, prefs, pr);
        }