public DiscStream(Disc disc, EDiscStreamView view, int from_lba) { SectorSize = 2048; Disc = disc; NumSectors = disc.Session1.LeadoutLBA; dsr = new DiscSectorReader(disc); //following the provided view switch (view) { case EDiscStreamView.DiscStreamView_Mode1_2048: dsr.Policy.UserData2048Mode = DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode1; break; case EDiscStreamView.DiscStreamView_Mode2_Form1_2048: dsr.Policy.UserData2048Mode = DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode2_Form1; break; default: throw new NotSupportedException("Unsupported EDiscStreamView"); } currPosition = from_lba * SectorSize; cachedSector = -1; cachedSectorBuffer = new byte[SectorSize]; }
public Yabause(CoreComm CoreComm, DiscSystem.Disc CD, object SyncSettings) { byte[] bios = CoreComm.CoreFileProvider.GetFirmware("SAT", "J", true, "Saturn BIOS is required."); CoreComm.RomStatusDetails = string.Format("Disk partial hash:{0}", CD.GetHash()); this.CoreComm = CoreComm; this.CD = CD; this.SyncSettings = (SaturnSyncSettings)SyncSettings ?? new SaturnSyncSettings(); if (this.SyncSettings.UseGL && glContext == null) { glContext = CoreComm.RequestGLContext(); } ResetCounters(); ActivateGL(); Init(bios); InputCallbackH = new LibYabause.InputCallback(() => CoreComm.InputCallback.Call()); LibYabause.libyabause_setinputcallback(InputCallbackH); CoreComm.UsesDriveLed = true; DeactivateGL(); }
public DiscIdentifier(Disc disc) { this.disc = disc; dsr = new DiscSectorReader(disc); //the first check for mode 0 should be sufficient for blocking attempts to read audio sectors, so dont do this //dsr.Policy.ThrowExceptions2048 = false; }
void _Open_LBA_2048(Disc disc) { SectorSize = 2048; Disc = disc; NumSectors = disc.LBACount; currPosition = 0; cachedSector = -1; cachedSectorBuffer = new byte[SectorSize]; }
void BindDisc(DiscRecord discRecord) { Disc disc = discRecord.Disc; boundDiscRecord = discRecord; DiscStructure toc = disc.ReadStructure(); boundDisc = disc; lblSessions.Text = toc.Sessions.Count.ToString(); lblTracks.Text = toc.Sessions.Sum((ses) => ses.Tracks.Count).ToString(); lblSectors.Text = string.Format("{0} ({1})", toc.LengthInSectors, toc.FriendlyLength.Value); lblSize.Text = string.Format("{0:0.00} MB", toc.BinarySize / 1024.0 / 1024.0); btnExportCue.Enabled = true; UpdateCue(); }
public DiscStream(Disc disc, EDiscStreamView view, int from_lba) { if (view != EDiscStreamView.DiscStreamView_Mode1_2048) throw new NotSupportedException("disc streams of not mode 1 are currently unsupported"); SectorSize = 2048; Disc = disc; NumSectors = disc.Session1.LeadoutLBA; dsr = new DiscSectorReader(disc); //following the provided view dsr.Policy.UserData2048Mode = DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode1; currPosition = from_lba * SectorSize; cachedSector = -1; cachedSectorBuffer = new byte[SectorSize]; }
/// <summary> /// applies an SBI file to the disc /// </summary> public void Run(Disc disc, SBI.SubQPatchData sbi, bool asMednafen) { //TODO - could implement as a blob, to avoid allocating so many byte buffers //save this, it's small, and we'll want it for disc processing a/b checks disc.Memos["sbi"] = sbi; DiscSectorReader dsr = new DiscSectorReader(disc); int n = sbi.ABAs.Count; int b = 0; for (int i = 0; i < n; i++) { int lba = sbi.ABAs[i] - 150; //create a synthesizer which can return the patched data var ss_patchq = new SS_PatchQ() { Original = disc.Sectors[lba + 150] }; byte[] subQbuf = ss_patchq.Buffer_SubQ; //read the old subcode dsr.ReadLBA_SubQ(lba, subQbuf, 0); //insert patch disc.Sectors[lba + 150] = ss_patchq; //apply SBI patch for (int j = 0; j < 12; j++) { short patch = sbi.subq[b++]; if (patch == -1) continue; else subQbuf[j] = (byte)patch; } //Apply mednafen hacks //The reasoning here is that we know we expect these sectors to have a wrong checksum. therefore, generate a checksum, and make it wrong //However, this seems senseless to me. The whole point of the SBI data is that it stores the patches needed to generate an acceptable subQ, right? if (asMednafen) { SynthUtils.SubQ_SynthChecksum(subQbuf, 0); subQbuf[10] ^= 0xFF; subQbuf[11] ^= 0xFF; } } }
public static void Extract(Disc disc, string path, string filebase) { var dsr = new DiscSectorReader(disc); bool confirmed = false; var tracks = disc.Session1.Tracks; foreach (var track in tracks) { if (!track.IsAudio) continue; int trackLength = track.NextTrack.LBA - track.LBA; var waveData = new byte[trackLength * 2352]; int startLba = track.LBA; for (int sector = 0; sector < trackLength; sector++) dsr.ReadLBA_2352(startLba + sector, waveData, sector * 2352); string mp3Path = string.Format("{0} - Track {1:D2}.mp3", Path.Combine(path, filebase), track.Number); if (File.Exists(mp3Path)) { if (!confirmed) { var dr = MessageBox.Show("This file already exists. Do you want extraction to proceed overwriting files, or cancel the entire operation immediately?", "File already exists", MessageBoxButtons.OKCancel); if (dr == DialogResult.Cancel) return; confirmed = true; } File.Delete(mp3Path); } string tempfile = Path.GetTempFileName(); try { File.WriteAllBytes(tempfile, waveData); var ffmpeg = new FFMpeg(); ffmpeg.Run("-f", "s16le", "-ar", "44100", "-ac", "2", "-i", tempfile, "-f", "mp3", "-ab", "192k", mp3Path); } finally { File.Delete(tempfile); } } }
public CDAudio(Disc disc, int maxVolume = short.MaxValue) { Disc = disc; MaxVolume = maxVolume; }
public static DiscStream Open_LBA_2048(Disc disc) { var ret = new DiscStream(); ret._Open_LBA_2048(disc); return ret; }
public DiscSectorReader(Disc disc) { this.disc = disc; }
/// <summary> /// THIS HASNT BEEN TESTED IN A LONG TIME. DOES IT WORK? /// </summary> public static Disc FromIsoPath(string isoPath) { var ret = new Disc(); ret.FromIsoPathInternal(isoPath); ret.Structure.Synthesize_TOCPointsFromSessions(); ret.Synthesize_SubcodeFromStructure(); return ret; }
public DiscIdentifier(Disc disc) { this.disc = disc; dsr = new DiscSectorReader(disc); }
void RunMednaDisc() { var disc = new Disc(); OUT_Disc = disc; //create a MednaDisc and give it to the disc for ownership var md = new MednaDisc(IN_FromPath); disc.DisposableResources.Add(md); //"length of disc" for bizhawk's purposes (NOT a robust concept!) is determined by beginning of leadout track var m_leadoutTrack = md.TOCTracks[100]; int nSectors = (int)m_leadoutTrack.lba; //make synth param memos disc.SynthParams.MednaDisc = md; //this is the sole sector synthesizer we'll need var synth = new SS_MednaDisc(); OUT_Disc.SynthProvider = new SimpleSectorSynthProvider() { SS = synth }; //ADR (q-Mode) is necessarily 0x01 for a RawTOCEntry const int kADR = 1; const int kUnknownControl = 0; //mednafen delivers us what is essentially but not exactly (or completely) a TOCRaw. //we need to synth RawTOCEntries from this and then turn it into a proper TOCRaw //when coming from mednafen, there are 101 entries. //entry[0] is placeholder junk, not to be used //entry[100] is the leadout track (A0) //A1 and A2 are in the form of FirstRecordedTrackNumber and LastRecordedTrackNumber for (int i = 1; i < 101; i++) { var m_te = md.TOCTracks[i]; //dont add invalid (absent) items if (!m_te.Valid) continue; var m_ts = new Timestamp((int)m_te.lba + 150); //these are supposed to be absolute timestamps var q = new SubchannelQ { q_status = SubchannelQ.ComputeStatus(kADR, (EControlQ)m_te.control), q_tno = BCD2.FromDecimal(0), //unknown with mednadisc q_index = BCD2.FromDecimal(i), min = BCD2.FromDecimal(0), //unknown with mednadisc sec = BCD2.FromDecimal(0), //unknown with mednadisc frame = BCD2.FromDecimal(0), //unknown with mednadisc zero = 0, //unknown with mednadisc ap_min = BCD2.FromDecimal(m_ts.MIN), ap_sec = BCD2.FromDecimal(m_ts.SEC), ap_frame = BCD2.FromDecimal(m_ts.FRAC), q_crc = 0 //meaningless }; //a special fixup: mednafen's entry 100 is the lead-out track, so change it into the A2 raw toc entry if (i == 100) { q.q_index.BCDValue = 0xA2; } disc.RawTOCEntries.Add(new RawTOCEntry { QData = q }); } //synth A0 and A1 entries (indicating first and last recorded tracks and also session type) var qA0 = new SubchannelQ { q_status = SubchannelQ.ComputeStatus(kADR, kUnknownControl), q_tno = BCD2.FromDecimal(0), //unknown with mednadisc q_index = BCD2.FromBCD(0xA0), min = BCD2.FromDecimal(0), //unknown with mednadisc sec = BCD2.FromDecimal(0), //unknown with mednadisc frame = BCD2.FromDecimal(0), //unknown with mednadisc zero = 0, //unknown with mednadisc ap_min = BCD2.FromDecimal(md.TOC.first_track), ap_sec = BCD2.FromDecimal(md.TOC.disc_type), ap_frame = BCD2.FromDecimal(0), q_crc = 0, //meaningless }; disc.RawTOCEntries.Add(new RawTOCEntry { QData = qA0 }); var qA1 = new SubchannelQ { q_status = SubchannelQ.ComputeStatus(kADR, kUnknownControl), q_tno = BCD2.FromDecimal(0), //unknown with mednadisc q_index = BCD2.FromBCD(0xA1), min = BCD2.FromDecimal(0), //unknown with mednadisc sec = BCD2.FromDecimal(0), //unknown with mednadisc frame = BCD2.FromDecimal(0), //unknown with mednadisc zero = 0, //unknown with mednadisc ap_min = BCD2.FromDecimal(md.TOC.last_track), ap_sec = BCD2.FromDecimal(0), ap_frame = BCD2.FromDecimal(0), q_crc = 0, //meaningless }; disc.RawTOCEntries.Add(new RawTOCEntry { QData = qA1 }); }
public ScsiCDBus(PCEngine pce, Disc disc) { this.pce = pce; this.disc = disc; }
public DiscHasher(Disc disc) { this.disc = disc; }
/// <exception cref="MDSParseException">no file found at <paramref name="mdsPath"/> or BLOB error</exception> public Disc LoadMDSToDisc(string mdsPath, DiscMountPolicy IN_DiscMountPolicy) { var loadResults = LoadMDSPath(mdsPath); if (!loadResults.Valid) { throw loadResults.FailureException; } Disc disc = new Disc(); // load all blobs Dictionary <int, IBlob> BlobIndex = MountBlobs(loadResults.ParsedMDSFile, disc); var mdsf = loadResults.ParsedMDSFile; //generate DiscTOCRaw items from the ones specified in the MDS file disc.RawTOCEntries = new List <RawTOCEntry>(); foreach (var entry in mdsf.TOCEntries) { disc.RawTOCEntries.Add(EmitRawTOCEntry(entry)); } //analyze the RAWTocEntries to figure out what type of track track 1 is var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job() { Entries = disc.RawTOCEntries }; tocSynth.Run(); // now build the sectors int currBlobIndex = 0; foreach (var session in mdsf.ParsedSession) { for (int i = session.StartTrack; i <= session.EndTrack; i++) { int relMSF = -1; var track = mdsf.TOCEntries.FirstOrDefault(t => t.Point == i); if (track == null) { break; } // ignore the info entries if (track.Point == 0xA0 || track.Point == 0xA1 || track.Point == 0xA2) { continue; } // get the blob(s) for this track // it's probably a safe assumption that there will be only one blob per track, but I'm still not 100% sure on this var tr = mdsf.TOCEntries.FirstOrDefault(a => a.Point == i) ?? throw new MDSParseException("BLOB Error!"); #if true if (tr.ImageFileNamePaths.Count == 0) { throw new MDSParseException("BLOB Error!"); } #else // this is the worst use of lists and LINQ I've seen in this god-forsaken codebase, I hope for all our sakes that it's not a workaround for some race condition --yoshi List <string> blobstrings = new List <string>(); foreach (var t in tr.ImageFileNamePaths) { if (!blobstrings.Contains(t)) { blobstrings.Add(t); } } var tBlobs = (from a in tr.ImageFileNamePaths select a).ToList(); if (tBlobs.Count < 1) { throw new MDSParseException("BLOB Error!"); } // is the currBlob valid for this track, or do we need to increment? string bString = tBlobs.First(); #endif IBlob mdfBlob = null; // check for track pregap and create if necessary // this is specified in the track extras block if (track.ExtraBlock.Pregap > 0) { CUE.CueTrackType pregapTrackType = CUE.CueTrackType.Audio; if (tocSynth.Result.TOCItems[1].IsData) { if (tocSynth.Result.Session1Format == SessionFormat.Type20_CDXA) { pregapTrackType = CUE.CueTrackType.Mode2_2352; } else if (tocSynth.Result.Session1Format == SessionFormat.Type10_CDI) { pregapTrackType = CUE.CueTrackType.CDI_2352; } else if (tocSynth.Result.Session1Format == SessionFormat.Type00_CDROM_CDDA) { pregapTrackType = CUE.CueTrackType.Mode1_2352; } } for (int pre = 0; pre < track.ExtraBlock.Pregap; pre++) { relMSF++; var ss_gap = new CUE.SS_Gap() { Policy = IN_DiscMountPolicy, TrackType = pregapTrackType }; disc._Sectors.Add(ss_gap); int qRelMSF = pre - Convert.ToInt32(track.ExtraBlock.Pregap); //tweak relMSF due to ambiguity/contradiction in yellowbook docs if (!IN_DiscMountPolicy.CUE_PregapContradictionModeA) { qRelMSF++; } //setup subQ byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption: ss_gap.sq.SetStatus(ADR, tocSynth.Result.TOCItems[1].Control); ss_gap.sq.q_tno = BCD2.FromDecimal(1); ss_gap.sq.q_index = BCD2.FromDecimal(0); ss_gap.sq.AP_Timestamp = pre; ss_gap.sq.Timestamp = qRelMSF; //setup subP ss_gap.Pause = true; } // pregap processing completed } // create track sectors long currBlobOffset = track.TrackOffset; for (long sector = session.StartSector; sector <= session.EndSector; sector++) { CUE.SS_Base sBase = null; // get the current blob from the BlobIndex Disc.Blob_RawFile currBlob = (Disc.Blob_RawFile)BlobIndex[currBlobIndex]; long currBlobLength = currBlob.Length; long currBlobPosition = sector; if (currBlobPosition == currBlobLength) { currBlobIndex++; } mdfBlob = disc.DisposableResources[currBlobIndex] as Disc.Blob_RawFile; //int userSector = 2048; switch (track.SectorSize) { case 2448: sBase = new CUE.SS_2352() { Policy = IN_DiscMountPolicy }; //userSector = 2352; break; case 2048: default: sBase = new CUE.SS_Mode1_2048() { Policy = IN_DiscMountPolicy }; //userSector = 2048; break; //throw new Exception($"Not supported: Sector Size {track.SectorSize}"); } // configure blob sBase.Blob = mdfBlob; sBase.BlobOffset = currBlobOffset; currBlobOffset += track.SectorSize; // userSector; // add subchannel data relMSF++; BCD2 tno, ino; //this should actually be zero. im not sure if this is stored as BCD2 or not tno = BCD2.FromDecimal(track.TrackNo); //these are special values.. I think, taken from this: //http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html //the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD. //Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing. ino = BCD2.FromDecimal(track.Point); if (track.Point == 0xA0) { ino.BCDValue = 0xA0; } else if (track.Point == 0xA1) { ino.BCDValue = 0xA1; } else if (track.Point == 0xA2) { ino.BCDValue = 0xA2; } // get ADR & Control from ADR_Control byte byte adrc = Convert.ToByte(track.ADR_Control); var Control = adrc & 0x0F; var ADR = adrc >> 4; var q = new SubchannelQ { q_status = SubchannelQ.ComputeStatus(ADR, (EControlQ)(Control & 0xF)), q_tno = BCD2.FromDecimal(track.Point), q_index = ino, AP_Timestamp = disc._Sectors.Count, Timestamp = relMSF - Convert.ToInt32(track.ExtraBlock.Pregap) }; sBase.sq = q; disc._Sectors.Add(sBase); } } } return(disc); }
/// <exception cref="CCDParseException">file <paramref name="ccdPath"/> not found, nonexistent IMG file, nonexistent SUB file, IMG or SUB file not multiple of <c>2352 B</c>, or IMG and SUB files differ in length</exception> public Disc LoadCCDToDisc(string ccdPath, DiscMountPolicy IN_DiscMountPolicy) { var loadResults = LoadCCDPath(ccdPath); if (!loadResults.Valid) { throw loadResults.FailureException; } Disc disc = new Disc(); IBlob imgBlob = null, subBlob = null; long imgLen = -1, subLen; //mount the IMG file //first check for a .ecm in place of the img var imgPath = loadResults.ImgPath; if (!File.Exists(imgPath)) { var ecmPath = Path.ChangeExtension(imgPath, ".img.ecm"); if (File.Exists(ecmPath)) { if (Disc.Blob_ECM.IsECM(ecmPath)) { var ecm = new Disc.Blob_ECM(); ecm.Load(ecmPath); imgBlob = ecm; imgLen = ecm.Length; } } } if (imgBlob == null) { if (!File.Exists(loadResults.ImgPath)) { throw new CCDParseException("Malformed CCD format: nonexistent IMG file!"); } var imgFile = new Disc.Blob_RawFile() { PhysicalPath = loadResults.ImgPath }; imgLen = imgFile.Length; imgBlob = imgFile; } disc.DisposableResources.Add(imgBlob); //mount the SUB file if (!File.Exists(loadResults.SubPath)) { throw new CCDParseException("Malformed CCD format: nonexistent SUB file!"); } var subFile = new Disc.Blob_RawFile() { PhysicalPath = loadResults.SubPath }; subBlob = subFile; disc.DisposableResources.Add(subBlob); subLen = subFile.Length; //quick integrity check of file sizes if (imgLen % 2352 != 0) { throw new CCDParseException("Malformed CCD format: IMG file length not multiple of 2352"); } int NumImgSectors = (int)(imgLen / 2352); if (subLen != NumImgSectors * 96) { throw new CCDParseException("Malformed CCD format: SUB file length not matching IMG"); } var ccdf = loadResults.ParsedCCDFile; //the only instance of a sector synthesizer we'll need SS_CCD synth = new SS_CCD(); //generate DiscTOCRaw items from the ones specified in the CCD file //TODO - range validate these (too many truncations to byte) disc.RawTOCEntries = new List <RawTOCEntry>(); foreach (var entry in ccdf.TOCEntries) { BCD2 tno, ino; //this should actually be zero. im not sure if this is stored as BCD2 or not tno = BCD2.FromDecimal(entry.TrackNo); //these are special values.. I think, taken from this: //http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html //the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD. //Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing. ino = BCD2.FromDecimal(entry.Point); if (entry.Point == 0xA0) { ino.BCDValue = 0xA0; } else if (entry.Point == 0xA1) { ino.BCDValue = 0xA1; } else if (entry.Point == 0xA2) { ino.BCDValue = 0xA2; } var q = new SubchannelQ { q_status = SubchannelQ.ComputeStatus(entry.ADR, (EControlQ)(entry.Control & 0xF)), q_tno = tno, q_index = ino, min = BCD2.FromDecimal(entry.AMin), sec = BCD2.FromDecimal(entry.ASec), frame = BCD2.FromDecimal(entry.AFrame), zero = (byte)entry.Zero, ap_min = BCD2.FromDecimal(entry.PMin), ap_sec = BCD2.FromDecimal(entry.PSec), ap_frame = BCD2.FromDecimal(entry.PFrame), q_crc = 0, //meaningless }; disc.RawTOCEntries.Add(new RawTOCEntry { QData = q }); } //analyze the RAWTocEntries to figure out what type of track track 1 is var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job() { Entries = disc.RawTOCEntries }; tocSynth.Run(); //Add sectors for the mandatory track 1 pregap, which isn't stored in the CCD file //We reuse some CUE code for this. //If we load other formats later we might should abstract this even further (to a synthesizer job) //It can't really be abstracted from cue files though due to the necessity of merging this with other track 1 pregaps CUE.CueTrackType pregapTrackType = CUE.CueTrackType.Audio; if (tocSynth.Result.TOCItems[1].IsData) { if (tocSynth.Result.Session1Format == SessionFormat.Type20_CDXA) { pregapTrackType = CUE.CueTrackType.Mode2_2352; } else if (tocSynth.Result.Session1Format == SessionFormat.Type10_CDI) { pregapTrackType = CUE.CueTrackType.CDI_2352; } else if (tocSynth.Result.Session1Format == SessionFormat.Type00_CDROM_CDDA) { pregapTrackType = CUE.CueTrackType.Mode1_2352; } } for (int i = 0; i < 150; i++) { var ss_gap = new CUE.SS_Gap() { Policy = IN_DiscMountPolicy, TrackType = pregapTrackType }; disc._Sectors.Add(ss_gap); int qRelMSF = i - 150; //tweak relMSF due to ambiguity/contradiction in yellowbook docs if (!IN_DiscMountPolicy.CUE_PregapContradictionModeA) { qRelMSF++; } //setup subQ byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption: ss_gap.sq.SetStatus(ADR, tocSynth.Result.TOCItems[1].Control); ss_gap.sq.q_tno = BCD2.FromDecimal(1); ss_gap.sq.q_index = BCD2.FromDecimal(0); ss_gap.sq.AP_Timestamp = i; ss_gap.sq.Timestamp = qRelMSF; //setup subP ss_gap.Pause = true; } //build the sectors: //set up as many sectors as we have img/sub for, even if the TOC doesnt reference them //(the TOC is unreliable, and the Track records are redundant) for (int i = 0; i < NumImgSectors; i++) { disc._Sectors.Add(synth); } return(disc); }
public static void Dump(Disc disc, string path) { using (var sw = new StreamWriter(path)) { //NOTE: IsoBuster requires the A0,A1,A2 RawTocEntries to be first or else it can't do anything with the tracks //if we ever get them in a different order, we'll have to re-order them here sw.WriteLine("[CloneCD]"); sw.WriteLine("Version=3"); sw.WriteLine(); sw.WriteLine("[Disc]"); sw.WriteLine("TocEntries={0}", disc.RawTOCEntries.Count); sw.WriteLine("Sessions=1"); sw.WriteLine("DataTracksScrambled=0"); sw.WriteLine("CDTextLength=0"); //not supported anyway sw.WriteLine(); sw.WriteLine("[Session 1]"); sw.WriteLine("PreGapMode=2"); sw.WriteLine("PreGapSubC=1"); sw.WriteLine(); for (int i = 0; i < disc.RawTOCEntries.Count; i++) { var entry = disc.RawTOCEntries[i]; //ehhh something's wrong with how I track these int point = entry.QData.q_index.DecimalValue; if (point == 100) { point = 0xA0; } if (point == 101) { point = 0xA1; } if (point == 102) { point = 0xA2; } sw.WriteLine("[Entry {0}]", i); sw.WriteLine("Session=1"); sw.WriteLine("Point=0x{0:x2}", point); sw.WriteLine("ADR=0x{0:x2}", entry.QData.ADR); sw.WriteLine("Control=0x{0:x2}", (int)entry.QData.CONTROL); sw.WriteLine("TrackNo={0}", entry.QData.q_tno.DecimalValue); sw.WriteLine("AMin={0}", entry.QData.min.DecimalValue); sw.WriteLine("ASec={0}", entry.QData.sec.DecimalValue); sw.WriteLine("AFrame={0}", entry.QData.frame.DecimalValue); sw.WriteLine("ALBA={0}", entry.QData.Timestamp - 150); //remember to adapt the absolute MSF to an LBA (this field is redundant...) sw.WriteLine("Zero={0}", entry.QData.zero); sw.WriteLine("PMin={0}", entry.QData.ap_min.DecimalValue); sw.WriteLine("PSec={0}", entry.QData.ap_sec.DecimalValue); sw.WriteLine("PFrame={0}", entry.QData.ap_frame.DecimalValue); sw.WriteLine("PLBA={0}", entry.QData.AP_Timestamp - 150); //remember to adapt the absolute MSF to an LBA (this field is redundant...) sw.WriteLine(); } //this is nonsense, really. the whole CCD track list shouldn't be needed. //but in order to make a high quality CCD which can be inspected by various other tools, we need it //now, regarding the indexes.. theyre truly useless. having indexes written out with the tracks is bad news. //index information is only truly stored in subQ for (int tnum = 1; tnum <= disc.Session1.LastInformationTrack.Number; tnum++) { var track = disc.Session1.Tracks[tnum]; sw.WriteLine("[TRACK {0}]", track.Number); sw.WriteLine("MODE={0}", track.Mode); //indexes are BS, dont write them. but we certainly need an index 1 sw.WriteLine("INDEX 1={0}", track.LBA); sw.WriteLine(); } } //TODO - actually re-add //dump the img and sub //TODO - acquire disk size first string imgPath = Path.ChangeExtension(path, ".img"); string subPath = Path.ChangeExtension(path, ".sub"); var buf2448 = new byte[2448]; DiscSectorReader dsr = new DiscSectorReader(disc); using (var imgFile = File.OpenWrite(imgPath)) using (var subFile = File.OpenWrite(subPath)) { int nLBA = disc.Session1.LeadoutLBA; for (int lba = 0; lba < nLBA; lba++) { dsr.ReadLBA_2448(lba, buf2448, 0); imgFile.Write(buf2448, 0, 2352); subFile.Write(buf2448, 2352, 96); } } }
void RunBizHawk() { string infile = IN_FromPath; string cue_content = null; var cfr = new CueFileResolver(); RERUN: var ext = Path.GetExtension(infile).ToLowerInvariant(); if (ext == ".iso") { //make a fake cue file to represent this iso file and rerun it as a cue string filebase = Path.GetFileName(infile); cue_content = string.Format(@" FILE ""{0}"" BINARY TRACK 01 MODE1/2048 INDEX 01 00:00:00", filebase); infile = Path.ChangeExtension(infile, ".cue"); goto RERUN; } if (ext == ".cue") { //TODO - make sure code is designed so no matter what happens, a disc is disposed in case of errors. //perhaps the CUE_Format2 (once renamed to something like Context) can handle that var cuePath = IN_FromPath; var cueContext = new CUE_Context(); cueContext.DiscMountPolicy = IN_DiscMountPolicy; cueContext.Resolver = cfr; if (!cfr.IsHardcodedResolve) cfr.SetBaseDirectory(Path.GetDirectoryName(infile)); //parse the cue file var parseJob = new ParseCueJob(); if (cue_content == null) cue_content = File.ReadAllText(cuePath); parseJob.IN_CueString = cue_content; parseJob.Run(parseJob); //TODO - need better handling of log output if (!string.IsNullOrEmpty(parseJob.OUT_Log)) Console.WriteLine(parseJob.OUT_Log); ConcatenateJobLog(parseJob); //compile the cue file: //includes this work: resolve required bin files and find out what it's gonna take to load the cue var compileJob = new CompileCueJob(); compileJob.IN_CueContext = cueContext; compileJob.IN_CueFile = parseJob.OUT_CueFile; compileJob.Run(); //TODO - need better handling of log output if (!string.IsNullOrEmpty(compileJob.OUT_Log)) Console.WriteLine(compileJob.OUT_Log); ConcatenateJobLog(compileJob); //check slow loading threshold if (compileJob.OUT_LoadTime >= IN_SlowLoadAbortThreshold) { Warn("Loading terminated due to slow load threshold"); OUT_SlowLoadAborted = true; goto DONE; } //actually load it all up var loadJob = new LoadCueJob(); loadJob.IN_CompileJob = compileJob; loadJob.Run(); //TODO - need better handling of log output if (!string.IsNullOrEmpty(loadJob.OUT_Log)) Console.WriteLine(loadJob.OUT_Log); ConcatenateJobLog(loadJob); OUT_Disc = loadJob.OUT_Disc; //OUT_Disc.DiscMountPolicy = IN_DiscMountPolicy; //NOT SURE WE NEED THIS (only makes sense for cue probably) //apply SBI if it exists (TODO - for formats other than cue?) var sbiPath = Path.ChangeExtension(IN_FromPath, ".sbi"); if (File.Exists(sbiPath) && SBI.SBIFormat.QuickCheckISSBI(sbiPath)) { var loadSbiJob = new SBI.LoadSBIJob() { IN_Path = sbiPath }; loadSbiJob.Run(); var applySbiJob = new ApplySBIJob(); applySbiJob.Run(OUT_Disc, loadSbiJob.OUT_Data, IN_DiscMountPolicy.SBI_As_Mednafen); } } else if (ext == ".ccd") { CCD_Format ccdLoader = new CCD_Format(); OUT_Disc = ccdLoader.LoadCCDToDisc(IN_FromPath, IN_DiscMountPolicy); } DONE: ; }
public ScsiCDBus(PCEngine pce, Disc disc) { this.pce = pce; this.disc = disc; subcodeReader = new SubcodeReader(disc); }
/// <summary> /// Loads a CCD at the specified path to a Disc object /// </summary> public Disc LoadCCDToDisc(string ccdPath) { var loadResults = LoadCCDPath(ccdPath); if (!loadResults.Valid) { throw loadResults.FailureException; } Disc disc = new Disc(); var ccdf = loadResults.ParsedCCDFile; var imgBlob = new Disc.Blob_RawFile() { PhysicalPath = loadResults.ImgPath }; var subBlob = new Disc.Blob_RawFile() { PhysicalPath = loadResults.SubPath }; disc.Blobs.Add(imgBlob); disc.Blobs.Add(subBlob); //generate DiscTOCRaw items from the ones specified in the CCD file //TODO - range validate these (too many truncations to byte) disc.RawTOCEntries = new List <RawTOCEntry>(); BufferedSubcodeSector bss = new BufferedSubcodeSector(); foreach (var entry in ccdf.TOCEntries) { var q = new SubchannelQ { q_status = SubchannelQ.ComputeStatus(entry.ADR, (EControlQ)(entry.Control & 0xF)), q_tno = (byte)entry.TrackNo, q_index = (byte)entry.Point, min = BCD2.FromDecimal(entry.AMin), sec = BCD2.FromDecimal(entry.ASec), frame = BCD2.FromDecimal(entry.AFrame), zero = (byte)entry.Zero, ap_min = BCD2.FromDecimal(entry.PMin), ap_sec = BCD2.FromDecimal(entry.PSec), ap_frame = BCD2.FromDecimal(entry.PFrame), }; //CRC cant be calculated til we've got all the fields setup q.q_crc = bss.Synthesize_SubchannelQ(ref q, true); disc.RawTOCEntries.Add(new RawTOCEntry { QData = q }); } //generate the toc from the entries var tocSynth = new DiscTOCRaw.SynthesizeFromRawTOCEntriesJob() { Entries = disc.RawTOCEntries }; tocSynth.Run(); disc.TOCRaw = tocSynth.Result; //synthesize DiscStructure var structureSynth = new DiscStructure.SynthesizeFromDiscTOCRawJob() { TOCRaw = disc.TOCRaw }; structureSynth.Run(); disc.Structure = structureSynth.Result; //I *think* implicitly there is an index 0.. at.. i dunno, 0 maybe, for track 1 { var dsi0 = new DiscStructure.Index(); dsi0.LBA = 0; dsi0.Number = 0; disc.Structure.Sessions[0].Tracks[0].Indexes.Add(dsi0); } //now, how to get the track types for the DiscStructure? //1. the CCD tells us (somehow the reader has judged) //2. scan it out of the Q subchannel //lets choose1. //TODO - better consider how to handle the situation where we have havent received all the [TRACK] items we need foreach (var st in disc.Structure.Sessions[0].Tracks) { var ccdt = ccdf.TracksByNumber[st.Number]; switch (ccdt.Mode) { case 0: st.TrackType = ETrackType.Audio; //for CCD, this means audio, apparently. break; case 1: st.TrackType = ETrackType.Mode1_2352; break; case 2: st.TrackType = ETrackType.Mode2_2352; break; default: throw new InvalidOperationException("Unsupported CCD mode"); } //add indexes for this track foreach (var ccdi in ccdt.Indexes) { var dsi = new DiscStructure.Index(); //if (ccdi.Key == 0) continue; dsi.LBA = ccdi.Value; dsi.Number = ccdi.Key; st.Indexes.Add(dsi); } } //add sectors for the lead-in, which isn't stored in the CCD file, I think //TODO - synthesize lead-in sectors from TOC, if the lead-in isn't available. //need a test case for that though. var leadin_sector_zero = new Sector_Zero(); var leadin_subcode_zero = new ZeroSubcodeSector(); for (int i = 0; i < 150; i++) { var se = new SectorEntry(leadin_sector_zero); disc.Sectors.Add(se); se.SubcodeSector = leadin_subcode_zero; } //build the sectors: //set up as many sectors as we have img/sub for, even if the TOC doesnt reference them (TOC is unreliable, although the tracks should have covered it all) for (int i = 0; i < loadResults.NumImgSectors; i++) { var isec = new Sector_RawBlob(); isec.Offset = ((long)i) * 2352; isec.Blob = imgBlob; var se = new SectorEntry(isec); disc.Sectors.Add(se); var scsec = new BlobSubcodeSectorPreDeinterleaved(); scsec.Offset = ((long)i) * 96; scsec.Blob = subBlob; se.SubcodeSector = scsec; } return(disc); }
public static Disc FromCuePath(string cuePath, CueBinPrefs prefs) { var ret = new Disc(); ret.FromCuePathInternal(cuePath, prefs); ret.TOC.GeneratePoints(); ret.PopulateQSubchannel(); return ret; }
public void Install(Disc disc, Func<int, bool> condition, ISectorSynthJob2448 patch) { Parent = disc.SynthProvider; disc.SynthProvider = this; Condition = condition; Patch = patch; }
public CDAudio(Disc disc, int maxVolume = short.MaxValue) { Disc = disc; DiscSectorReader = new DiscSectorReader(disc); MaxVolume = maxVolume; }
public PCEngine(CoreComm comm, GameInfo game, Disc disc, object Settings, object syncSettings) { CoreComm = comm; CoreComm.CpuTraceAvailable = true; CoreComm.UsesDriveLed = true; systemid = "PCECD"; Type = NecSystemType.TurboCD; this.disc = disc; this._settings = (PCESettings)Settings ?? new PCESettings(); _syncSettings = (PCESyncSettings)syncSettings ?? new PCESyncSettings(); GameInfo biosInfo; byte[] rom = CoreComm.CoreFileProvider.GetFirmwareWithGameInfo("PCECD", "Bios", true, out biosInfo, "PCE-CD System Card not found. Please check the BIOS settings in Config->Firmwares."); if (biosInfo.Status == RomStatus.BadDump) { CoreComm.ShowMessage( "The PCE-CD System Card you have selected is known to be a bad dump. This may cause problems playing PCE-CD games.\n\n" + "It is recommended that you find a good dump of the system card. Sorry to be the bearer of bad news!"); } else if (biosInfo.NotInDatabase) { CoreComm.ShowMessage( "The PCE-CD System Card you have selected is not recognized in our database. That might mean it's a bad dump, or isn't the correct rom."); } else if (biosInfo["BIOS"] == false) { //zeromus says: someone please write a note about how this could possibly happen. //it seems like this is a relic of using gameDB for storing whether something is a bios? firmwareDB should be handling it now. CoreComm.ShowMessage( "The PCE-CD System Card you have selected is not a BIOS image. You may have selected the wrong rom. FYI-Please report this to developers, I don't think this error message should happen."); } if (biosInfo["SuperSysCard"]) { game.AddOption("SuperSysCard"); } if (game["NeedSuperSysCard"] && game["SuperSysCard"] == false) { CoreComm.ShowMessage( "This game requires a version 3.0 System card and won't run with the system card you've selected. Try selecting a 3.0 System Card in the firmware configuration."); throw new Exception(); } game.FirmwareHash = rom.HashSHA1(); Init(game, rom); // the default RomStatusDetails don't do anything with Disc CoreComm.RomStatusDetails = string.Format("{0}\r\nDisk partial hash:{1}", game.Name, disc.GetHash()); SetControllerButtons(); }
public void Clear() { CurrentDisc = null; Queue.Clear(); }
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; }
public void Enqueue(Disc disc) { Queue.Enqueue(disc); }
/// <summary> /// THIS HASNT BEEN TESTED IN A LONG TIME. DOES IT WORK? /// </summary> public static Disc FromIsoPath(string isoPath) { var ret = new Disc(); ret.FromIsoPathInternal(isoPath); ret.TOC.GeneratePoints(); ret.PopulateQSubchannel(); return ret; }
void RunBizHawk() { string infile = IN_FromPath; string cue_content = null; var cfr = new CueFileResolver(); RERUN: var ext = Path.GetExtension(infile).ToLowerInvariant(); if (ext == ".iso") { //make a fake cue file to represent this iso file and rerun it as a cue string filebase = Path.GetFileName(infile); cue_content = $@" FILE ""{filebase}"" BINARY TRACK 01 MODE1/2048 INDEX 01 00:00:00" ; infile = Path.ChangeExtension(infile, ".cue"); goto RERUN; } if (ext == ".cue") { //TODO - major renovation of error handling needed //TODO - make sure code is designed so no matter what happens, a disc is disposed in case of errors. //perhaps the CUE_Format2 (once renamed to something like Context) can handle that var cuePath = IN_FromPath; var cueContext = new CUE_Context(); cueContext.DiscMountPolicy = IN_DiscMountPolicy; cueContext.Resolver = cfr; if (!cfr.IsHardcodedResolve) { cfr.SetBaseDirectory(Path.GetDirectoryName(infile)); } //parse the cue file var parseJob = new ParseCueJob(); if (cue_content == null) { cue_content = File.ReadAllText(cuePath); } parseJob.IN_CueString = cue_content; bool okParse = true; try { parseJob.Run(parseJob); } catch (DiscJobAbortException) { okParse = false; parseJob.FinishLog(); } if (!string.IsNullOrEmpty(parseJob.OUT_Log)) { Console.WriteLine(parseJob.OUT_Log); } ConcatenateJobLog(parseJob); if (!okParse) { goto DONE; } //compile the cue file: //includes this work: resolve required bin files and find out what it's gonna take to load the cue var compileJob = new CompileCueJob(); compileJob.IN_CueContext = cueContext; compileJob.IN_CueFile = parseJob.OUT_CueFile; bool okCompile = true; try { compileJob.Run(); } catch (DiscJobAbortException) { okCompile = false; compileJob.FinishLog(); } if (!string.IsNullOrEmpty(compileJob.OUT_Log)) { Console.WriteLine(compileJob.OUT_Log); } ConcatenateJobLog(compileJob); if (!okCompile || compileJob.OUT_ErrorLevel) { goto DONE; } //check slow loading threshold if (compileJob.OUT_LoadTime > IN_SlowLoadAbortThreshold) { Warn("Loading terminated due to slow load threshold"); OUT_SlowLoadAborted = true; goto DONE; } //actually load it all up var loadJob = new LoadCueJob(); loadJob.IN_CompileJob = compileJob; loadJob.Run(); //TODO - need better handling of log output if (!string.IsNullOrEmpty(loadJob.OUT_Log)) { Console.WriteLine(loadJob.OUT_Log); } ConcatenateJobLog(loadJob); OUT_Disc = loadJob.OUT_Disc; //OUT_Disc.DiscMountPolicy = IN_DiscMountPolicy; //NOT SURE WE NEED THIS (only makes sense for cue probably) } else if (ext == ".ccd") { CCD_Format ccdLoader = new CCD_Format(); OUT_Disc = ccdLoader.LoadCCDToDisc(IN_FromPath, IN_DiscMountPolicy); } else if (ext == ".mds") { MDS_Format mdsLoader = new MDS_Format(); OUT_Disc = mdsLoader.LoadMDSToDisc(IN_FromPath, IN_DiscMountPolicy); } DONE: //setup the lowest level synth provider if (OUT_Disc != null) { var sssp = new ArraySectorSynthProvider() { Sectors = OUT_Disc._Sectors, FirstLBA = -150 }; OUT_Disc.SynthProvider = sssp; } }
public ScsiCDBus(PCEngine pce, Disc disc) { this.pce = pce; this.disc = disc; DiscSectorReader = new DiscSectorReader(disc); }
public void Eject() { CurrentDisc = null; }
private static bool CompareFile(string infile, DiscInterface loadDiscInterface, DiscInterface cmpif, bool verbose, CancellationTokenSource cancelToken, StringWriter sw) { Disc srcDisc = null, dstDisc = null; try { bool success = false; sw.WriteLine("BEGIN COMPARE: {0}\nSRC {1} vs DST {2}", infile, loadDiscInterface, cmpif); //reload the original disc, with new policies as needed var dmj = new DiscMountJob( fromPath: infile, discMountPolicy: new DiscMountPolicy { CUE_PregapContradictionModeA = cmpif != DiscInterface.MednaDisc }, discInterface: loadDiscInterface); dmj.Run(); srcDisc = dmj.OUT_Disc; var dstDmj = new DiscMountJob(fromPath: infile, discInterface: cmpif); dstDmj.Run(); dstDisc = dstDmj.OUT_Disc; var srcDsr = new DiscSectorReader(srcDisc); var dstDsr = new DiscSectorReader(dstDisc); var srcToc = srcDisc.TOC; var dstToc = dstDisc.TOC; var srcDataBuf = new byte[2448]; var dstDataBuf = new byte[2448]; void SwDumpTocOne(DiscTOC.TOCItem item) { if (!item.Exists) { sw.Write("(---missing---)"); } else { sw.Write("({0:X2} - {1})", (byte)item.Control, item.LBA); } } void SwDumpToc(int index) { sw.Write("SRC TOC#{0,3} ", index); SwDumpTocOne(srcToc.TOCItems[index]); sw.WriteLine(); sw.Write("DST TOC#{0,3} ", index); SwDumpTocOne(dstToc.TOCItems[index]); sw.WriteLine(); } //verify sector count if (srcDisc.Session1.LeadoutLBA != dstDisc.Session1.LeadoutLBA) { sw.Write("LeadoutTrack.LBA {0} vs {1}\n", srcDisc.Session1.LeadoutTrack.LBA, dstDisc.Session1.LeadoutTrack.LBA); goto SKIPPO; } //verify TOC match if (srcDisc.TOC.FirstRecordedTrackNumber != dstDisc.TOC.FirstRecordedTrackNumber || srcDisc.TOC.LastRecordedTrackNumber != dstDisc.TOC.LastRecordedTrackNumber) { sw.WriteLine("Mismatch of RecordedTrackNumbers: {0}-{1} vs {2}-{3}", srcDisc.TOC.FirstRecordedTrackNumber, srcDisc.TOC.LastRecordedTrackNumber, dstDisc.TOC.FirstRecordedTrackNumber, dstDisc.TOC.LastRecordedTrackNumber ); goto SKIPPO; } bool badToc = false; for (int t = 0; t < 101; t++) { if (srcToc.TOCItems[t].Exists != dstToc.TOCItems[t].Exists || srcToc.TOCItems[t].Control != dstToc.TOCItems[t].Control || srcToc.TOCItems[t].LBA != dstToc.TOCItems[t].LBA ) { sw.WriteLine("Mismatch in TOCItem"); SwDumpToc(t); badToc = true; } } if (badToc) { goto SKIPPO; } void SwDumpChunkOne(string comment, int lba, byte[] buf, int addr, int count) { sw.Write("{0} - ", comment); for (int i = 0; i < count; i++) { if (i + addr >= buf.Length) { continue; } sw.Write("{0:X2}{1}", buf[addr + i], (i == count - 1) ? " " : " "); } sw.WriteLine(); } int[] offenders = new int[12]; void SwDumpChunk(int lba, int dispAddr, int addr, int count, int numOffenders) { var hashedOffenders = new HashSet <int>(); for (int i = 0; i < numOffenders; i++) { hashedOffenders.Add(offenders[i]); } sw.Write(" "); for (int i = 0; i < count; i++) { sw.Write((hashedOffenders.Contains(dispAddr + i)) ? "vvv " : " "); } sw.WriteLine(); sw.Write(" "); for (int i = 0; i < count; i++) { sw.Write("{0:X3} ", dispAddr + i, (i == count - 1) ? " " : " "); } sw.WriteLine(); sw.Write(" "); sw.Write(new string('-', count * 4)); sw.WriteLine(); SwDumpChunkOne($"SRC #{lba,6} ({new Timestamp(lba)})", lba, srcDataBuf, addr, count); SwDumpChunkOne($"DST #{lba,6} ({new Timestamp(lba)})", lba, dstDataBuf, addr, count); } //verify each sector contents int nSectors = srcDisc.Session1.LeadoutLBA; for (int lba = -150; lba < nSectors; lba++) { if (verbose) { if (lba % 1000 == 0) { Console.WriteLine("LBA {0} of {1}", lba, nSectors); } } if (cancelToken != null) { if (cancelToken.Token.IsCancellationRequested) { return(false); } } srcDsr.ReadLBA_2448(lba, srcDataBuf, 0); dstDsr.ReadLBA_2448(lba, dstDataBuf, 0); //check the header for (int b = 0; b < 16; b++) { if (srcDataBuf[b] != dstDataBuf[b]) { sw.WriteLine("Mismatch in sector header at byte {0}", b); offenders[0] = b; SwDumpChunk(lba, 0, 0, 16, 1); goto SKIPPO; } } // check userData for (int b = 16; b < 2352; b++) { if (srcDataBuf[b] != dstDataBuf[b]) { sw.Write("LBA {0} mismatch at userdata byte {1}; terminating sector cmp\n", lba, b); goto SKIPPO; } } // check subChannels for (int c = 0, b = 2352; c < 8; c++) { int numOffenders = 0; for (int e = 0; e < 12; e++, b++) { if (srcDataBuf[b] != dstDataBuf[b]) { offenders[numOffenders++] = e; } } if (numOffenders != 0) { sw.Write("LBA {0} mismatch(es) at subchannel {1}; terminating sector cmp\n", lba, (char)('P' + c)); SwDumpChunk(lba, 0, 2352 + c * 12, 12, numOffenders); goto SKIPPO; } } } success = true; SKIPPO: sw.WriteLine("END COMPARE"); sw.WriteLine("-----------------------------"); return(success); } finally { srcDisc?.Dispose(); dstDisc?.Dispose(); } }
public void Insert() { if (Queue.Count > 0) CurrentDisc = Queue.Peek(); }
public SubcodeReader(Disc disc) { this.disc = disc; }
public static void RunWithArgs(string[] args, Action <string> showComparisonResultsCallback) { bool scanCues = false; string dirArg = null; string infile = null; var loadDiscInterface = DiscInterface.BizHawk; var compareDiscInterfaces = new List <DiscInterface>(); bool hawk = false; bool music = false; bool overwrite = false; int idx = 0; while (idx < args.Length) { string a = args[idx++]; string au = a.ToUpperInvariant(); if (au == "LOAD") { loadDiscInterface = (DiscInterface)Enum.Parse(typeof(DiscInterface), args[idx++], true); } else if (au == "COMPARE") { compareDiscInterfaces.Add((DiscInterface)Enum.Parse(typeof(DiscInterface), args[idx++], true)); } else if (au == "HAWK") { hawk = true; } else if (au == "CUEDIR") { dirArg = args[idx++]; scanCues = true; } else if (au is "MUSIC") { music = true; } else if (au is "OVERWRITE") { overwrite = true; } else { infile = a; } } if (hawk) { if (infile == null) { return; } HawkAndWriteFile( inputPath: infile, errorCallback: err => Console.WriteLine($"failed to convert {infile}:\n{err}"), discInterface: loadDiscInterface); } if (music) { if (infile is null) { return; } using var disc = Disc.LoadAutomagic(infile); var path = Path.GetDirectoryName(infile); var filename = Path.GetFileNameWithoutExtension(infile); bool?CheckOverwrite(string mp3Path) { if (overwrite) { return(true); // overwrite } Console.WriteLine($"{mp3Path} already exists. Remove existing output files, or retry with the extra argument \"OVERWRITE\"."); return(null); // cancel } AudioExtractor.Extract(disc, path, filename, CheckOverwrite); } bool verbose = true; if (scanCues) { verbose = false; var todo = FindCuesRecurse(dirArg); var po = new ParallelOptions(); var cts = new CancellationTokenSource(); po.CancellationToken = cts.Token; po.MaxDegreeOfParallelism = 1; if (po.MaxDegreeOfParallelism < 0) { po.MaxDegreeOfParallelism = 1; } object olock = new object(); int ctr = 0; bool blocked = false; try { Parallel.ForEach(todo, po, (fp) => { lock (olock) { ctr++; int strlen = todo.Count.ToString().Length; string fmt = string.Format("{{0,{0}}}/{{1,{0}}} {{2}}", strlen); Console.WriteLine(fmt, ctr, todo.Count, Path.GetFileNameWithoutExtension(fp)); } if (!blocked) { foreach (var cmpif in compareDiscInterfaces) { var sw = new StringWriter(); bool success = CompareFile(fp, loadDiscInterface, cmpif, verbose, cts, sw); if (!success) { lock (Console.Out) Console.Out.Write(sw.ToString()); cts.Cancel(); return; } } } }); } catch (AggregateException ae) { Console.WriteLine(ae.ToString()); } catch (OperationCanceledException oce) { Console.WriteLine(oce.ToString()); } Console.WriteLine("--TERMINATED--"); return; } if (compareDiscInterfaces.Count != 0) { var sw = new StringWriter(); foreach (var cmpif in compareDiscInterfaces) { CompareFile(infile, loadDiscInterface, cmpif, verbose, null, sw); } sw.Flush(); string results = sw.ToString(); showComparisonResultsCallback(results); } }