/// <exception cref="NotSupportedException"><paramref name="view"/> is not <see cref="DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode1"/> or <see cref="DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode2_Form1"/></exception> 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 {nameof(EDiscStreamView)}"); } currPosition = from_lba * SectorSize; cachedSector = -1; cachedSectorBuffer = new byte[SectorSize]; }
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 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; }
public DiscIdentifier(Disc disc) { _disc = disc; _dsr = new DiscSectorReader(disc); //the first check for mode 0 should be sufficient for blocking attempts to read audio sectors //but github #928 had a data track with an audio sector //so let's be careful here.. we're just trying to ID things, not be robust _dsr.Policy.ThrowExceptions2048 = false; }
/// <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; } } }
/// <summary> /// calculates the hash for quick PSX Disc identification /// </summary> public uint Calculate_PSX_BizIDHash() { //notes about the hash: //"Arc the Lad II (J) 1.0 and 1.1 conflict up to 25 sectors (so use 26) //Tekken 3 (Europe) (Alt) and Tekken 3 (Europe) conflict in track 2 and 3 unfortunately, not sure what to do about this yet //the TOC isn't needed! //but it will help detect dumps with mangled TOCs which are all too common CRC32 crc = new(); byte[] buffer2352 = new byte[2352]; var dsr = new DiscSectorReader(disc) { Policy = { DeterministicClearBuffer = false } // live dangerously };
// gets an identifying hash. hashes the first 512 sectors of // the first data track on the disc. //TODO - this is a very platform-specific thing. hashing the TOC may be faster and be just as effective. so, rename it appropriately public string OldHash() { byte[] buffer = new byte[512 * 2352]; DiscSectorReader dsr = new DiscSectorReader(disc); foreach (var track in disc.Session1.Tracks) { if (track.IsAudio) continue; int lba_len = Math.Min(track.NextTrack.LBA, 512); for (int s = 0; s < 512 && s < lba_len; s++) dsr.ReadLBA_2352(track.LBA + s, buffer, s * 2352); return buffer.HashMD5(0, lba_len * 2352); } return "no data track found"; }
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> /// calculates the complete disc hash for matching to a redump /// </summary> public uint Calculate_PSX_RedumpHash() { //a special CRC32 is used to help us match redump's DB SpecialCRC32 crc = new SpecialCRC32(); byte[] buffer2352 = new byte[2352]; var dsr = new DiscSectorReader(disc); dsr.Policy.DeterministicClearBuffer = false; //live dangerously //read all sectors for redump hash for (int i = 0; i < disc.Session1.LeadoutLBA; i++) { dsr.ReadLBA_2352(i, buffer2352, 0); crc.Add(buffer2352, 0, 2352); } return crc.Result; }
/// <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); } } }
/// <summary> /// Easily extracts a mode1 sector range (suitable for extracting ISO FS data files) /// </summary> public byte[] Easy_Extract_Mode1(int lba_start, int lba_count, int byteLength = -1) { int totsize = lba_count * 2048; byte[] ret = new byte[totsize]; var dsr = new DiscSectorReader(this); dsr.Policy.DeterministicClearBuffer = false; for (int i = 0; i < lba_count; i++) { dsr.ReadLBA_2048(lba_start + i, ret, i * 2048); } if (byteLength != -1 && byteLength != totsize) { byte[] newret = new byte[byteLength]; Array.Copy(ret, newret, byteLength); return(newret); } return(ret); }
/// <summary> /// calculates the complete disc hash for matching to a redump /// </summary> public uint Calculate_PSX_RedumpHash() { //a special CRC32 is used to help us match redump's DB SpecialCRC32 crc = new SpecialCRC32(); byte[] buffer2352 = new byte[2352]; var dsr = new DiscSectorReader(disc); dsr.Policy.DeterministicClearBuffer = false; //live dangerously //read all sectors for redump hash for (int i = 0; i < disc.Session1.LeadoutLBA; i++) { dsr.ReadLBA_2352(i, buffer2352, 0); crc.Add(buffer2352, 0, 2352); } return(crc.Result); }
/// <summary> /// calculates the hash for quick PSX Disc identification /// </summary> public uint Calculate_PSX_BizIDHash() { //notes about the hash: //"Arc the Lad II (J) 1.0 and 1.1 conflict up to 25 sectors (so use 26) //Tekken 3 (Europe) (Alt) and Tekken 3 (Europe) conflict in track 2 and 3 unfortunately, not sure what to do about this yet //the TOC isn't needed! //but it will help detect dumps with mangled TOCs which are all too common // //a possibly special CRC32 is used to help us match redump's DB elsewhere SpecialCRC32 crc = new SpecialCRC32(); byte[] buffer2352 = new byte[2352]; var dsr = new DiscSectorReader(disc) { Policy = { DeterministicClearBuffer = false } // live dangerously }; //hash the TOC crc.Add((int)disc.TOC.Session1Format); crc.Add(disc.TOC.FirstRecordedTrackNumber); crc.Add(disc.TOC.LastRecordedTrackNumber); for (int i = 1; i <= 100; i++) { //if (disc.TOC.TOCItems[i].Exists) Console.WriteLine("{0:X8} {1:X2} {2:X2} {3:X8}", crc.Current, (int)disc.TOC.TOCItems[i].Control, disc.TOC.TOCItems[i].Exists ? 1 : 0, disc.TOC.TOCItems[i].LBATimestamp.Sector); //a little debugging crc.Add((int)disc.TOC.TOCItems[i].Control); crc.Add(disc.TOC.TOCItems[i].Exists ? 1 : 0); crc.Add((int)disc.TOC.TOCItems[i].LBA); } //hash first 26 sectors for (int i = 0; i < 26; i++) { dsr.ReadLBA_2352(i, buffer2352, 0); crc.Add(buffer2352, 0, 2352); } return(crc.Result); }
// gets an identifying hash. hashes the first 512 sectors of // the first data track on the disc. //TODO - this is a very platform-specific thing. hashing the TOC may be faster and be just as effective. so, rename it appropriately public string OldHash() { byte[] buffer = new byte[512 * 2352]; DiscSectorReader dsr = new DiscSectorReader(disc); foreach (var track in disc.Session1.Tracks) { if (track.IsAudio) { continue; } int lba_len = Math.Min(track.NextTrack.LBA, 512); for (int s = 0; s < 512 && s < lba_len; s++) { dsr.ReadLBA_2352(track.LBA + s, buffer, s * 2352); } return(buffer.HashMD5(0, lba_len * 2352)); } return("no data track found"); }
/// <summary> /// calculates the hash for quick PSX Disc identification /// </summary> public uint Calculate_PSX_BizIDHash() { //notes about the hash: //"Arc the Lad II (J) 1.0 and 1.1 conflict up to 25 sectors (so use 26) //Tekken 3 (Europe) (Alt) and Tekken 3 (Europe) conflict in track 2 and 3 unfortunately, not sure what to do about this yet //the TOC isn't needed! //but it will help detect dumps with mangled TOCs which are all too common // //a possibly special CRC32 is used to help us match redump's DB elsewhere SpecialCRC32 crc = new SpecialCRC32(); byte[] buffer2352 = new byte[2352]; var dsr = new DiscSectorReader(disc); dsr.Policy.DeterministicClearBuffer = false; //live dangerously //hash the TOC crc.Add((int)disc.TOC.Session1Format); crc.Add(disc.TOC.FirstRecordedTrackNumber); crc.Add(disc.TOC.LastRecordedTrackNumber); for (int i = 1; i <= 100; i++) { //if (disc.TOC.TOCItems[i].Exists) Console.WriteLine("{0:X8} {1:X2} {2:X2} {3:X8}", crc.Current, (int)disc.TOC.TOCItems[i].Control, disc.TOC.TOCItems[i].Exists ? 1 : 0, disc.TOC.TOCItems[i].LBATimestamp.Sector); //a little debugging crc.Add((int)disc.TOC.TOCItems[i].Control); crc.Add(disc.TOC.TOCItems[i].Exists ? 1 : 0); crc.Add((int)disc.TOC.TOCItems[i].LBA); } //hash first 26 sectors for (int i = 0; i < 26; i++) { dsr.ReadLBA_2352(i, buffer2352, 0); crc.Add(buffer2352, 0, 2352); } return crc.Result; }
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, don't 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); } }
public CDAudio(Disc disc, int maxVolume = short.MaxValue) { Disc = disc; DiscSectorReader = new DiscSectorReader(disc); MaxVolume = maxVolume; }
/// <exception cref="InvalidOperationException">first track of <see cref="TOCRaw"/> is not <c>1</c></exception> public void Run() { var dsr = new DiscSectorReader(IN_Disc) { Policy = { DeterministicClearBuffer = false } }; Result = new DiscStructure(); var session = new DiscStructure.Session(); Result.Sessions.Add(null); //placeholder session for reindexing Result.Sessions.Add(session); session.Number = 1; if (TOCRaw.FirstRecordedTrackNumber != 1) { throw new InvalidOperationException($"Unsupported: {nameof(TOCRaw.FirstRecordedTrackNumber)} != 1"); } //add a lead-in track session.Tracks.Add(new DiscStructure.Track { Number = 0, Control = EControlQ.None, //we'll set this later LBA = -new Timestamp(99, 99, 99).Sector //obvious garbage }); int ntracks = TOCRaw.LastRecordedTrackNumber - TOCRaw.FirstRecordedTrackNumber + 1; for (int i = 0; i < ntracks; i++) { var item = TOCRaw.TOCItems[i + 1]; var track = new DiscStructure.Track { Number = i + 1, Control = item.Control, LBA = item.LBA }; session.Tracks.Add(track); if (!item.IsData) { track.Mode = 0; } else { //determine the mode by a hardcoded heuristic: check mode of first sector track.Mode = dsr.ReadLBA_Mode(track.LBA); } //determine track length according to... how? It isn't clear. //Let's not do this until it's needed. //if (i == ntracks - 1) // track.Length = TOCRaw.LeadoutLBA.Sector - track.LBA; //else track.Length = (TOCRaw.TOCItems[i + 2].LBATimestamp.Sector - track.LBA); } //add lead-out track session.Tracks.Add(new DiscStructure.Track { Number = 0xA0, //right? //kind of a guess, but not completely Control = session.Tracks[session.Tracks.Count - 1].Control, Mode = session.Tracks[session.Tracks.Count - 1].Mode, LBA = TOCRaw.LeadoutLBA }); //link track list for (int i = 0; i < session.Tracks.Count - 1; i++) { session.Tracks[i].NextTrack = session.Tracks[i + 1]; } //fix lead-in track type //guesses: session.Tracks[0].Control = session.Tracks[1].Control; session.Tracks[0].Mode = session.Tracks[1].Mode; }
public void Think() { if (RST) { ResetDevice(); return; } if (DataReadInProgress && pce.Cpu.TotalExecutedCycles > DataReadWaitTimer) { if (SectorsLeftToRead > 0) pce.DriveLightOn = true; if (DataIn.Count == 0) { // read in a sector and shove it in the queue DiscSystem.DiscSectorReader dsr = new DiscSectorReader(disc); //TODO - cache reader dsr.ReadLBA_2048(CurrentReadingSector, DataIn.GetBuffer(), 0); DataIn.SignalBufferFilled(2048); CurrentReadingSector++; SectorsLeftToRead--; pce.IntDataTransferReady = true; // If more sectors, should set the next think-clock to however long it takes to read 1 sector // but I dont. I dont think transfers actually happen sector by sector // like this, they probably become available as the bits come off the disc. // but lets get some basic functionality before we go crazy. // Idunno, maybe they do come in a sector at a time. //note to vecna: maybe not at the sector level, but at a level > 1 sample and <= 1 sector, samples come out in blocks //due to the way they are jumbled up (seriously, like put into a blender) for error correction purposes. //we may as well assume that the cd audio decoding magic works at the level of one sector, but it isnt one sample. if (SectorsLeftToRead == 0) { DataReadInProgress = false; DataTransferWasDone = true; } SetPhase(BusPhase_DataIn); } } do { signalsChanged = false; busPhaseChanged = false; if (SEL && !BSY) { SetPhase(BusPhase_Command); } else if (ATN && !REQ && !ACK) { SetPhase(BusPhase_MessageOut); } else switch (Phase) { case BusPhase_Command: ThinkCommandPhase(); break; case BusPhase_DataIn: ThinkDataInPhase(); break; case BusPhase_DataOut: ThinkDataOutPhase(); break; case BusPhase_MessageIn: ThinkMessageInPhase(); break; case BusPhase_MessageOut: ThinkMessageOutPhase(); break; case BusPhase_Status: ThinkStatusPhase(); break; default: break; } } while (signalsChanged || busPhaseChanged); }
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 DiscIdentifier(Disc disc) { this.disc = disc; dsr = new DiscSectorReader(disc); }
public ScsiCDBus(PCEngine pce, Disc disc) { this.pce = pce; this.disc = disc; DiscSectorReader = new DiscSectorReader(disc); }
public void Run() { var dsr = new DiscSectorReader(IN_Disc); dsr.Policy.DeterministicClearBuffer = false; Result = new DiscStructure(); var session = new DiscStructure.Session(); Result.Sessions.Add(null); //placeholder session for reindexing Result.Sessions.Add(session); session.Number = 1; if (TOCRaw.FirstRecordedTrackNumber != 1) throw new InvalidOperationException("Unsupported: FirstRecordedTrackNumber != 1"); //add a lead-in track session.Tracks.Add(new DiscStructure.Track() { Number = 0, Control = EControlQ.None, //we'll set this later LBA = -new Timestamp(99,99,99).Sector //obvious garbage }); int ntracks = TOCRaw.LastRecordedTrackNumber - TOCRaw.FirstRecordedTrackNumber + 1; for (int i = 0; i < ntracks; i++) { var item = TOCRaw.TOCItems[i + 1]; var track = new DiscStructure.Track() { Number = i + 1, Control = item.Control, LBA = item.LBA }; session.Tracks.Add(track); if (!item.IsData) track.Mode = 0; else { //determine the mode by a hardcoded heuristic: check mode of first sector track.Mode = dsr.ReadLBA_Mode(track.LBA); } //determine track length according to... how? It isn't clear. //Let's not do this until it's needed. //if (i == ntracks - 1) // track.Length = TOCRaw.LeadoutLBA.Sector - track.LBA; //else track.Length = (TOCRaw.TOCItems[i + 2].LBATimestamp.Sector - track.LBA); } //add lead-out track session.Tracks.Add(new DiscStructure.Track() { Number = 0xA0, //right? //kind of a guess, but not completely Control = session.Tracks[session.Tracks.Count -1 ].Control, Mode = session.Tracks[session.Tracks.Count - 1].Mode, LBA = TOCRaw.LeadoutLBA }); //link track list for (int i = 0; i < session.Tracks.Count - 1; i++) { session.Tracks[i].NextTrack = session.Tracks[i + 1]; } //fix lead-in track type //guesses: session.Tracks[0].Control = session.Tracks[1].Control; session.Tracks[0].Mode = session.Tracks[1].Mode; }
static bool CompareFile(string infile, DiscInterface loadDiscInterface, DiscInterface cmpif, bool verbose, CancellationTokenSource cancelToken, StringWriter sw) { Disc src_disc = null, dst_disc = 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 { IN_DiscInterface = loadDiscInterface, IN_FromPath = infile }; if (cmpif == DiscInterface.MednaDisc) { dmj.IN_DiscMountPolicy.CUE_PregapContradictionModeA = false; } dmj.Run(); src_disc = dmj.OUT_Disc; var dst_dmj = new DiscMountJob { IN_DiscInterface = cmpif, IN_FromPath = infile }; dst_dmj.Run(); dst_disc = dst_dmj.OUT_Disc; var src_dsr = new DiscSectorReader(src_disc); var dst_dsr = new DiscSectorReader(dst_disc); var src_toc = src_disc.TOC; var dst_toc = dst_disc.TOC; var src_databuf = new byte[2448]; var dst_databuf = new byte[2448]; Action<DiscTOC.TOCItem> sw_dump_toc_one = (item) => { if (!item.Exists) sw.Write("(---missing---)"); else sw.Write("({0:X2} - {1})", (byte)item.Control, item.LBA); }; Action<int> sw_dump_toc = (index) => { sw.Write("SRC TOC#{0,3} ", index); sw_dump_toc_one(src_toc.TOCItems[index]); sw.WriteLine(); sw.Write("DST TOC#{0,3} ", index); sw_dump_toc_one(dst_toc.TOCItems[index]); sw.WriteLine(); }; //verify sector count if (src_disc.Session1.LeadoutLBA != dst_disc.Session1.LeadoutLBA) { sw.Write("LeadoutTrack.LBA {0} vs {1}\n", src_disc.Session1.LeadoutTrack.LBA, dst_disc.Session1.LeadoutTrack.LBA); goto SKIPPO; } //verify TOC match if (src_disc.TOC.FirstRecordedTrackNumber != dst_disc.TOC.FirstRecordedTrackNumber || src_disc.TOC.LastRecordedTrackNumber != dst_disc.TOC.LastRecordedTrackNumber) { sw.WriteLine("Mismatch of RecordedTrackNumbers: {0}-{1} vs {2}-{3}", src_disc.TOC.FirstRecordedTrackNumber, src_disc.TOC.LastRecordedTrackNumber, dst_disc.TOC.FirstRecordedTrackNumber, dst_disc.TOC.LastRecordedTrackNumber ); goto SKIPPO; } bool badToc = false; for (int t = 0; t < 101; t++) { if (src_toc.TOCItems[t].Exists != dst_toc.TOCItems[t].Exists || src_toc.TOCItems[t].Control != dst_toc.TOCItems[t].Control || src_toc.TOCItems[t].LBA != dst_toc.TOCItems[t].LBA ) { sw.WriteLine("Mismatch in TOCItem"); sw_dump_toc(t); badToc = true; } } if (badToc) goto SKIPPO; Action<string, int, byte[], int, int> sw_dump_chunk_one = (comment, lba, buf, addr, 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]; Action<int, int, int, int, int> sw_dump_chunk = (lba, dispaddr, addr, count, 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(); sw_dump_chunk_one(string.Format("SRC #{0,6} ({1})", lba, new Timestamp(lba)), lba, src_databuf, addr, count); sw_dump_chunk_one(string.Format("DST #{0,6} ({1})", lba, new Timestamp(lba)), lba, dst_databuf, addr, count); }; //verify each sector contents int nSectors = src_disc.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; src_dsr.ReadLBA_2448(lba, src_databuf, 0); dst_dsr.ReadLBA_2448(lba, dst_databuf, 0); //check the header for (int b = 0; b < 16; b++) { if (src_databuf[b] != dst_databuf[b]) { sw.WriteLine("Mismatch in sector header at byte {0}", b); offenders[0] = b; sw_dump_chunk(lba, 0, 0, 16, 1); goto SKIPPO; } } //check userdata for (int b = 16; b < 2352; b++) { if (src_databuf[b] != dst_databuf[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 (src_databuf[b] != dst_databuf[b]) { offenders[numOffenders++] = e; } } if (numOffenders != 0) { sw.Write("LBA {0} mismatch(es) at subchannel {1}; terminating sector cmp\n", lba, (char)('P' + c)); sw_dump_chunk(lba, 0, 2352 + c * 12, 12, numOffenders); goto SKIPPO; } } } success = true; SKIPPO: sw.WriteLine("END COMPARE"); sw.WriteLine("-----------------------------"); return success; } finally { if (src_disc != null) src_disc.Dispose(); if (dst_disc != null) dst_disc.Dispose(); } }