public Yabause(CoreComm CoreComm, DiscSystem.Disc CD, object syncSettings) { ServiceProvider = new BasicServiceProvider(this); byte[] bios = CoreComm.CoreFileProvider.GetFirmware("SAT", "J", true, "Saturn BIOS is required."); CoreComm.RomStatusDetails = string.Format("Disk partial hash:{0}", new DiscSystem.DiscHasher(CD).OldHash()); this.CoreComm = CoreComm; this.CD = CD; DiscSectorReader = new DiscSystem.DiscSectorReader(CD); SyncSettings = (SaturnSyncSettings)syncSettings ?? new SaturnSyncSettings(); if (this.SyncSettings.UseGL && glContext == null) { glContext = CoreComm.RequestGLContext(2, 0, false); } ResetCounters(); ActivateGL(); Init(bios); InputCallbackH = new LibYabause.InputCallback(() => InputCallbacks.Call()); LibYabause.libyabause_setinputcallback(InputCallbackH); ConnectTracer(); DriveLightEnabled = true; DeactivateGL(); }
public DobieStation(CoreLoadParameters <object, DobieSyncSettings> lp) : base(lp.Comm, new Configuration { MaxWidth = 640, MaxHeight = 480, DefaultWidth = 640, DefaultHeight = 480, DefaultFpsNumerator = 294912000, DefaultFpsDenominator = 4920115, MaxSamples = 1024, SystemId = "PS2" }) { if (lp.Discs.Count != 1) { throw new InvalidOperationException("Must load a CD or DVD with PS2 core!"); } ControllerDefinition = DualShock; _syncSettings = lp.SyncSettings ?? new DobieSyncSettings(); _syncSettingsActual = lp.SyncSettings ?? new DobieSyncSettings(); _disc = new DiscSectorReader(lp.Discs[0].DiscData); _cdCallback = ReadCd; _core = PreInit <LibDobieStation>(new WaterboxOptions { Filename = "dobie.wbx", SbrkHeapSizeKB = 64 * 1024, SealedHeapSizeKB = 4 * 1024, InvisibleHeapSizeKB = 4 * 1024, PlainHeapSizeKB = 256, MmapHeapSizeKB = 2 * 1024 * 1024, SkipCoreConsistencyCheck = lp.Comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck), SkipMemoryConsistencyCheck = lp.Comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck), }, new[] { _cdCallback }); var bios = lp.Comm.CoreFileProvider.GetFirmware("PS2", "BIOS", true); _exe.AddReadonlyFile(new byte[0x840000], "MEMCARD0"); var worked = _core.Initialize(bios, (ulong)(lp.Discs[0].DiscData.Session1.Tracks[2].LBA - lp.Discs[0].DiscData.Session1.Tracks[1].LBA) * 2048, _cdCallback, _syncSettingsActual.GetNativeSettings() ); if (!worked) { throw new InvalidOperationException("Initialize failed!"); } _exe.RemoveReadonlyFile("MEMCARD0"); PostInit(); _resampler = new SpeexResampler((SpeexResampler.Quality) 6, 480, 441, 48000, 44100, null, this); _serviceProvider.Register <ISoundProvider>(_resampler); }
/// <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 = $"{Path.Combine(path, fileBase)} - Track {track.Number:D2}.mp3"; 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 FFmpegService(); ffmpeg.Run("-f", "s16le", "-ar", "44100", "-ac", "2", "-i", tempfile, "-f", "mp3", "-ab", "192k", mp3Path); } finally { File.Delete(tempfile); } } }
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); }
public ScsiCDBus(PCEngine pce, Disc disc) { this.pce = pce; this.disc = disc; DiscSectorReader = new DiscSectorReader(disc); }
} //Run() 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 { IN_DiscInterface = loadDiscInterface, IN_DiscMountPolicy = new DiscMountPolicy { CUE_PregapContradictionModeA = cmpif != DiscInterface.MednaDisc }, IN_FromPath = infile }; dmj.Run(); srcDisc = dmj.OUT_Disc; var dstDmj = new DiscMountJob { IN_DiscInterface = cmpif, IN_FromPath = infile }; 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(); } } //CompareFile
private PicoDrive(CoreComm comm, GameInfo game, byte[] rom, Disc cd, bool deterministic, SyncSettings syncSettings) : base(comm, new Configuration { MaxSamples = 2048, DefaultWidth = 320, DefaultHeight = 224, MaxWidth = 320, MaxHeight = 480, SystemId = "GEN" }) { var biosg = comm.CoreFileProvider.GetFirmware("32X", "G", false); var biosm = comm.CoreFileProvider.GetFirmware("32X", "M", false); var bioss = comm.CoreFileProvider.GetFirmware("32X", "S", false); var has32xBios = biosg != null && biosm != null && bioss != null; if (deterministic && !has32xBios) { throw new InvalidOperationException("32X BIOS files are required for deterministic mode"); } deterministic |= has32xBios; _syncSettings = syncSettings ?? new SyncSettings(); _core = PreInit <LibPicoDrive>(new PeRunnerOptions { Filename = "picodrive.wbx", SbrkHeapSizeKB = 64, SealedHeapSizeKB = 18 * 1024, InvisibleHeapSizeKB = 1024, MmapHeapSizeKB = 4096, PlainHeapSizeKB = 64, }); if (has32xBios) { _exe.AddReadonlyFile(biosg, "32x.g"); _exe.AddReadonlyFile(biosm, "32x.m"); _exe.AddReadonlyFile(bioss, "32x.s"); Console.WriteLine("Using supplied 32x BIOS files"); } if (cd != null) { _exe.AddReadonlyFile(comm.CoreFileProvider.GetFirmware("GEN", "CD_BIOS_EU", true), "cd.eu"); _exe.AddReadonlyFile(comm.CoreFileProvider.GetFirmware("GEN", "CD_BIOS_US", true), "cd.us"); _exe.AddReadonlyFile(comm.CoreFileProvider.GetFirmware("GEN", "CD_BIOS_JP", true), "cd.jp"); _exe.AddReadonlyFile(gpgx.GPGX.GetCDData(cd), "toc"); _cd = cd; _cdReader = new DiscSectorReader(_cd); _cdcallback = CDRead; _core.SetCDReadCallback(_cdcallback); DriveLightEnabled = true; } else { _exe.AddReadonlyFile(rom, "romfile.md"); } var regionAutoOrder = (LibPicoDrive.Region)( (int)_syncSettings.FirstChoice | (int)_syncSettings.SecondChoice << 4 | (int)_syncSettings.ThirdChoice << 8); if (!_core.Init(cd != null, game["32X"], regionAutoOrder, _syncSettings.RegionOverride)) { throw new InvalidOperationException("Core rejected the file!"); } if (cd != null) { _exe.RemoveReadonlyFile("cd.eu"); _exe.RemoveReadonlyFile("cd.us"); _exe.RemoveReadonlyFile("cd.jp"); _exe.RemoveReadonlyFile("toc"); _core.SetCDReadCallback(null); } else { _exe.RemoveReadonlyFile("romfile.md"); } if (has32xBios) { _exe.RemoveReadonlyFile("32x.g"); _exe.RemoveReadonlyFile("32x.m"); _exe.RemoveReadonlyFile("32x.s"); } PostInit(); ControllerDefinition = PicoDriveController; DeterministicEmulation = deterministic; _core.SetCDReadCallback(_cdcallback); _isPal = _core.IsPal(); VsyncNumerator = _isPal ? 53203424 : 53693175; VsyncDenominator = _isPal ? 3420 * 313 : 3420 * 262; }
public CDAudio(Disc disc, int maxVolume = short.MaxValue) { Disc = disc; DiscSectorReader = new DiscSectorReader(disc); MaxVolume = maxVolume; }
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; }
public static void Extract(Disc disc, string path, string fileBase, Func <bool?> getOverwritePolicy) { var dsr = new DiscSectorReader(disc); var shouldHalt = false; bool?overwriteExisting = null; // true = overwrite, false = skip existing, null = unset var tracks = disc.Session1.Tracks; Parallel.ForEach(tracks, track => { if (shouldHalt) { return; } if (!track.IsAudio) { return; } if (track.NextTrack == null) { return; } int trackLength = track.NextTrack.LBA - track.LBA; var waveData = new byte[trackLength * 2352]; int startLba = track.LBA; lock (disc) for (int sector = 0; sector < trackLength; sector++) { dsr.ReadLBA_2352(startLba + sector, waveData, sector * 2352); } string mp3Path = $"{Path.Combine(path, fileBase)} - Track {track.Number:D2}.mp3"; if (File.Exists(mp3Path)) { overwriteExisting ??= getOverwritePolicy(); switch (overwriteExisting) { case true: // "Yes" -- overwrite File.Delete(mp3Path); break; case false: // "No" -- skip return; case null: // "Cancel" -- halt shouldHalt = true; return; } } string tempfile = Path.GetTempFileName(); try { File.WriteAllBytes(tempfile, waveData); FFmpegService.Run("-f", "s16le", "-ar", "44100", "-ac", "2", "-i", tempfile, "-f", "mp3", "-ab", "192k", mp3Path); } finally { File.Delete(tempfile); } }); }
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); } } }