Example #1
0
        public void Synth(SectorSynthJob job)
        {
            //be lazy, just generate the whole sector unconditionally
            //this is mostly based on mednafen's approach, which was probably finely tailored for PSX
            //heres the comments on the subject:
            //  I'm not trusting that the "control" field for the TOC leadout entry will always be set properly, so | the control fields for the last track entry
            //  and the leadout entry together before extracting the D2 bit.  Audio track->data leadout is fairly benign though maybe noisy(especially if we ever implement
            //  data scrambling properly), but data track->audio leadout could break things in an insidious manner for the more accurate drive emulation code).

            var ses          = job.Disc.Structure.Sessions[SessionNumber];
            int lba_relative = job.LBA - ses.LeadoutTrack.LBA;

            //data is zero

            int ts  = lba_relative;
            int ats = job.LBA;

            const int ADR     = 0x1;         // Q channel data encodes position
            EControlQ control = ses.LeadoutTrack.Control;

            //ehhh? CDI?
            //if(toc.tracks[toc.last_track].valid)
            // control |= toc.tracks[toc.last_track].control & 0x4;
            //else if(toc.disc_type == DISC_TYPE_CD_I)
            // control |= 0x4;
            control |= (EControlQ)(((int)ses.LastInformationTrack.Control) & 4);

            SubchannelQ sq = new SubchannelQ();

            sq.SetStatus(ADR, control);
            sq.q_tno.BCDValue   = 0xAA;
            sq.q_index.BCDValue = 0x01;
            sq.Timestamp        = ts;
            sq.AP_Timestamp     = ats;
            sq.zero             = 0;

            //finally, rely on a gap sector to do the heavy lifting to synthesize this
            CUE.CueTrackType TrackType = CUE.CueTrackType.Audio;
            if (ses.LeadoutTrack.IsData)
            {
                if (job.Disc.TOC.Session1Format == SessionFormat.Type20_CDXA || job.Disc.TOC.Session1Format == SessionFormat.Type10_CDI)
                {
                    TrackType = CUE.CueTrackType.Mode2_2352;
                }
                else
                {
                    TrackType = CUE.CueTrackType.Mode1_2352;
                }
            }

            CUE.SS_Gap ss_gap = new CUE.SS_Gap()
            {
                Policy    = Policy,
                sq        = sq,
                TrackType = TrackType,
                Pause     = true             //?
            };

            ss_gap.Synth(job);
        }
        /// <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(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
                        Blob_RawFile currBlob         = (Blob_RawFile)BlobIndex[currBlobIndex];
                        long         currBlobLength   = currBlob.Length;
                        long         currBlobPosition = sector;
                        if (currBlobPosition == currBlobLength)
                        {
                            currBlobIndex++;
                        }
                        mdfBlob = disc.DisposableResources[currBlobIndex] as 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);
        }
Example #3
0
        /// <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);
        }
Example #4
0
        public void Run()
        {
            //params
            var compiled = IN_CompileJob;
            var context  = compiled.IN_CueContext;

            OUT_Disc = new Disc();

            //generation state
            int      curr_index;
            int      curr_blobIndex  = -1;
            int      curr_blobMSF    = -1;
            BlobInfo curr_blobInfo   = null;
            long     curr_blobOffset = -1;

            //mount all input files
            MountBlobs();

            //unhappily, we cannot determine the length of all the tracks without knowing the length of the files
            //now that the files are mounted, we can figure the track lengths
            AnalyzeTracks();

            //loop from track 1 to 99
            //(track 0 isnt handled yet, that's way distant work)
            for (int t = 1; t < TrackInfos.Count; t++)
            {
                TrackInfo        ti  = TrackInfos[t];
                CompiledCueTrack cct = ti.CompiledCueTrack;

                //---------------------------------
                //setup track pregap processing
                //per "Example 05" on digitalx.org, pregap can come from index specification and pregap command
                int specifiedPregapLength = cct.PregapLength.Sector;
                int impliedPregapLength   = cct.Indexes[1].FileMSF.Sector - cct.Indexes[0].FileMSF.Sector;
                int totalPregapLength     = specifiedPregapLength + impliedPregapLength;

                //from now on we'll track relative timestamp and increment it continually
                int relMSF = -totalPregapLength;

                //read more at policies declaration
                //if (!context.DiscMountPolicy.CUE_PauseContradictionModeA)
                //  relMSF += 1;
                //---------------------------------


                //---------------------------------
                //generate sectors for this track.

                //advance to the next file if needed
                if (curr_blobIndex != cct.BlobIndex)
                {
                    curr_blobIndex  = cct.BlobIndex;
                    curr_blobOffset = 0;
                    curr_blobMSF    = 0;
                    curr_blobInfo   = BlobInfos[curr_blobIndex];
                }

                //work until the next track is reached, or the end of the current file is reached, depending on the track type
                curr_index = 0;
                for (; ;)
                {
                    bool trackDone   = false;
                    bool generateGap = false;

                    if (specifiedPregapLength > 0)
                    {
                        //if burning through a specified pregap, count it down
                        generateGap = true;
                        specifiedPregapLength--;
                    }
                    else
                    {
                        //if burning through the file, select the appropriate index by inspecting the next index and seeing if we've reached it
                        for (; ;)
                        {
                            if (curr_index == cct.Indexes.Count - 1)
                            {
                                break;
                            }
                            if (curr_blobMSF >= cct.Indexes[curr_index + 1].FileMSF.Sector)
                            {
                                curr_index++;
                                if (curr_index == 1)
                                {
                                    //WE ARE NOW AT INDEX 1: generate the RawTOCEntry for this track
                                    EmitRawTOCEntry(cct);
                                }
                            }
                            else
                            {
                                break;
                            }
                        }
                    }

                    //select the track type for the subQ
                    //it's obviously the same as the main track type usually, but during a pregap it can be different
                    TrackInfo qTrack  = ti;
                    int       qRelMSF = relMSF;
                    if (curr_index == 0)
                    {
                        //tweak relMSF due to ambiguity/contradiction in yellowbook docs
                        if (!context.DiscMountPolicy.CUE_PregapContradictionModeA)
                        {
                            qRelMSF++;
                        }

                        //[IEC10149] says there's two "intervals" of a pregap.
                        //mednafen's pseudocode interpretation of this:
                        //if this is a data track and the previous track was not data, the last 150 sectors of the pregap match this track and the earlier sectors (at least 75) math the previous track
                        //I agree, so let's do it that way
                        if (t != 1 && cct.TrackType != CueTrackType.Audio && TrackInfos[t - 1].CompiledCueTrack.TrackType == CueTrackType.Audio)
                        {
                            if (relMSF < -150)
                            {
                                qTrack = TrackInfos[t - 1];
                            }
                        }
                    }

                    //generate the right kind of sector synth for this track
                    SS_Base ss = null;
                    if (generateGap)
                    {
                        var ss_gap = new SS_Gap();
                        ss_gap.TrackType = qTrack.CompiledCueTrack.TrackType;
                        ss = ss_gap;
                    }
                    else
                    {
                        int sectorSize = int.MaxValue;
                        switch (qTrack.CompiledCueTrack.TrackType)
                        {
                        case CueTrackType.Audio:
                        case CueTrackType.CDI_2352:
                        case CueTrackType.Mode1_2352:
                        case CueTrackType.Mode2_2352:
                            ss         = new SS_2352();
                            sectorSize = 2352;
                            break;

                        case CueTrackType.Mode1_2048:
                            ss         = new SS_Mode1_2048();
                            sectorSize = 2048;
                            break;

                        default:
                        case CueTrackType.Mode2_2336:
                            throw new InvalidOperationException($"Not supported: {cct.TrackType}");
                        }

                        ss.Blob          = curr_blobInfo.Blob;
                        ss.BlobOffset    = curr_blobOffset;
                        curr_blobOffset += sectorSize;
                        curr_blobMSF++;
                    }

                    ss.Policy = context.DiscMountPolicy;

                    //setup subQ
                    byte ADR = 1;                     //absent some kind of policy for how to set it, this is a safe assumption:
                    ss.sq.SetStatus(ADR, (EControlQ)(int)qTrack.CompiledCueTrack.Flags);
                    ss.sq.q_tno        = BCD2.FromDecimal(cct.Number);
                    ss.sq.q_index      = BCD2.FromDecimal(curr_index);
                    ss.sq.AP_Timestamp = OUT_Disc._Sectors.Count;
                    ss.sq.Timestamp    = qRelMSF;

                    //setup subP
                    if (curr_index == 0)
                    {
                        ss.Pause = true;
                    }

                    OUT_Disc._Sectors.Add(ss);
                    relMSF++;

                    if (cct.IsFinalInFile)
                    {
                        //sometimes, break when the file is exhausted
                        if (curr_blobOffset >= curr_blobInfo.Length)
                        {
                            trackDone = true;
                        }
                    }
                    else
                    {
                        //other times, break when the track is done
                        //(this check is safe because it's not the final track overall if it's not the final track in a file)
                        if (curr_blobMSF >= TrackInfos[t + 1].CompiledCueTrack.Indexes[0].FileMSF.Sector)
                        {
                            trackDone = true;
                        }
                    }

                    if (trackDone)
                    {
                        break;
                    }
                }

                //---------------------------------
                //gen postgap sectors
                int specifiedPostgapLength = cct.PostgapLength.Sector;
                for (int s = 0; s < specifiedPostgapLength; s++)
                {
                    var ss = new SS_Gap();
                    ss.TrackType = cct.TrackType;                     //TODO - old track type in some < -150 cases?

                    //-subq-
                    byte ADR = 1;
                    ss.sq.SetStatus(ADR, (EControlQ)(int)cct.Flags);
                    ss.sq.q_tno        = BCD2.FromDecimal(cct.Number);
                    ss.sq.q_index      = BCD2.FromDecimal(curr_index);
                    ss.sq.AP_Timestamp = OUT_Disc._Sectors.Count;
                    ss.sq.Timestamp    = relMSF;

                    //-subP-
                    //always paused--is this good enough?
                    ss.Pause = true;

                    OUT_Disc._Sectors.Add(ss);
                    relMSF++;
                }
            }             //end track loop


            //add RawTOCEntries A0 A1 A2 to round out the TOC
            var TOCMiscInfo = new Synthesize_A0A1A2_Job {
                IN_FirstRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.FirstRecordedTrackNumber,
                IN_LastRecordedTrackNumber  = IN_CompileJob.OUT_CompiledDiscInfo.LastRecordedTrackNumber,
                IN_Session1Format           = IN_CompileJob.OUT_CompiledDiscInfo.SessionFormat,
                IN_LeadoutTimestamp         = OUT_Disc._Sectors.Count
            };

            TOCMiscInfo.Run(OUT_Disc.RawTOCEntries);

            //TODO - generate leadout, or delegates at least

            //blech, old crap, maybe
            //OUT_Disc.Structure.Synthesize_TOCPointsFromSessions();

            //FinishLog();
        } //Run()
Example #5
0
        public void Run()
        {
            //params
            var compiled = IN_CompileJob;
            var context = compiled.IN_CueContext;
            OUT_Disc = new Disc();

            //generation state
            int curr_index;
            int curr_blobIndex = -1;
            int curr_blobMSF = -1;
            BlobInfo curr_blobInfo = null;
            long curr_blobOffset = -1;

            //mount all input files
            MountBlobs();

            //unhappily, we cannot determine the length of all the tracks without knowing the length of the files
            //now that the files are mounted, we can figure the track lengths
            AnalyzeTracks();

            //loop from track 1 to 99
            //(track 0 isnt handled yet, that's way distant work)
            for (int t = 1; t < TrackInfos.Count; t++)
            {
                TrackInfo ti = TrackInfos[t];
                CompiledCueTrack cct = ti.CompiledCueTrack;

                //---------------------------------
                //setup track pregap processing
                //per "Example 05" on digitalx.org, pregap can come from index specification and pregap command
                int specifiedPregapLength = cct.PregapLength.Sector;
                int impliedPregapLength = cct.Indexes[1].FileMSF.Sector - cct.Indexes[0].FileMSF.Sector;
                int totalPregapLength = specifiedPregapLength + impliedPregapLength;

                //from now on we'll track relative timestamp and increment it continually
                int relMSF = -totalPregapLength;

                //read more at policies declaration
                //if (!context.DiscMountPolicy.CUE_PauseContradictionModeA)
                //  relMSF += 1;
                //---------------------------------

                //---------------------------------
                //generate sectors for this track.

                //advance to the next file if needed
                if (curr_blobIndex != cct.BlobIndex)
                {
                    curr_blobIndex = cct.BlobIndex;
                    curr_blobOffset = 0;
                    curr_blobMSF = 0;
                    curr_blobInfo = BlobInfos[curr_blobIndex];
                }

                //work until the next track is reached, or the end of the current file is reached, depending on the track type
                curr_index = 0;
                for (; ; )
                {
                    bool trackDone = false;
                    bool generateGap = false;

                    if (specifiedPregapLength > 0)
                    {
                        //if burning through a specified pregap, count it down
                        generateGap = true;
                        specifiedPregapLength--;
                    }
                    else
                    {
                        //if burning through the file, select the appropriate index by inspecting the next index and seeing if we've reached it
                        for (; ; )
                        {
                            if (curr_index == cct.Indexes.Count - 1)
                                break;
                            if (curr_blobMSF >= cct.Indexes[curr_index + 1].FileMSF.Sector)
                            {
                                curr_index++;
                                if (curr_index == 1)
                                {
                                    //WE ARE NOW AT INDEX 1: generate the RawTOCEntry for this track
                                    EmitRawTOCEntry(cct);
                                }
                            }
                            else break;
                        }
                    }

                    //select the track type for the subQ
                    //it's obviously the same as the main track type usually, but during a pregap it can be different
                    TrackInfo qTrack = ti;
                    int qRelMSF = relMSF;
                    if (curr_index == 0)
                    {
                        //tweak relMSF due to ambiguity/contradiction in yellowbook docs
                        if (!context.DiscMountPolicy.CUE_PregapContradictionModeA)
                            qRelMSF++;

                        //[IEC10149] says there's two "intervals" of a pregap.
                        //mednafen's pseudocode interpretation of this:
                        //if this is a data track and the previous track was not data, the last 150 sectors of the pregap match this track and the earlier sectors (at least 75) math the previous track
                        //I agree, so let's do it that way
                        if (t != 1 && cct.TrackType != CueTrackType.Audio && TrackInfos[t - 1].CompiledCueTrack.TrackType == CueTrackType.Audio)
                        {
                            if (relMSF < -150)
                            {
                                qTrack = TrackInfos[t - 1];
                            }
                        }
                    }

                    //generate the right kind of sector synth for this track
                    SS_Base ss = null;
                    if (generateGap)
                    {
                        var ss_gap = new SS_Gap();
                        ss_gap.TrackType = qTrack.CompiledCueTrack.TrackType;
                        ss = ss_gap;
                    }
                    else
                    {
                        int sectorSize = int.MaxValue;
                        switch (qTrack.CompiledCueTrack.TrackType)
                        {
                            case CueTrackType.Audio:
                            case CueTrackType.CDI_2352:
                            case CueTrackType.Mode1_2352:
                            case CueTrackType.Mode2_2352:
                                ss = new SS_2352();
                                sectorSize = 2352;
                                break;

                            case CueTrackType.Mode1_2048:
                                ss = new SS_Mode1_2048();
                                sectorSize = 2048;
                                break;

                            default:
                            case CueTrackType.Mode2_2336:
                                throw new InvalidOperationException("Not supported: " + cct.TrackType);
                        }

                        ss.Blob = curr_blobInfo.Blob;
                        ss.BlobOffset = curr_blobOffset;
                        curr_blobOffset += sectorSize;
                        curr_blobMSF++;
                    }

                    ss.Policy = context.DiscMountPolicy;

                    //setup subQ
                    byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
                    ss.sq.SetStatus(ADR, (EControlQ)(int)qTrack.CompiledCueTrack.Flags);
                    ss.sq.q_tno = BCD2.FromDecimal(cct.Number);
                    ss.sq.q_index = BCD2.FromDecimal(curr_index);
                    ss.sq.AP_Timestamp = new Timestamp(OUT_Disc.Sectors.Count);
                    ss.sq.Timestamp = new Timestamp(qRelMSF);

                    //setup subP
                    if (curr_index == 0)
                        ss.Pause = true;

                    OUT_Disc.Sectors.Add(ss);
                    relMSF++;

                    if (cct.IsFinalInFile)
                    {
                        //sometimes, break when the file is exhausted
                        if (curr_blobOffset >= curr_blobInfo.Length)
                            trackDone = true;
                    }
                    else
                    {
                        //other times, break when the track is done
                        //(this check is safe because it's not the final track overall if it's not the final track in a file)
                        if (curr_blobMSF >= TrackInfos[t + 1].CompiledCueTrack.Indexes[0].FileMSF.Sector)
                            trackDone = true;
                    }

                    if (trackDone)
                        break;
                }

                //---------------------------------
                //gen postgap sectors
                int specifiedPostgapLength = cct.PostgapLength.Sector;
                for (int s = 0; s < specifiedPostgapLength; s++)
                {
                    var ss = new SS_Gap();
                    ss.TrackType = cct.TrackType; //TODO - old track type in some < -150 cases?

                    //-subq-
                    byte ADR = 1;
                    ss.sq.SetStatus(ADR, (EControlQ)(int)cct.Flags);
                    ss.sq.q_tno = BCD2.FromDecimal(cct.Number);
                    ss.sq.q_index = BCD2.FromDecimal(curr_index);
                    ss.sq.AP_Timestamp = new Timestamp(OUT_Disc.Sectors.Count);
                    ss.sq.Timestamp = new Timestamp(relMSF);

                    //-subP-
                    //always paused--is this good enough?
                    ss.Pause = true;

                    OUT_Disc.Sectors.Add(ss);
                    relMSF++;
                }

            } //end track loop

            //add RawTOCEntries A0 A1 A2 to round out the TOC
            var TOCMiscInfo = new Synthesize_A0A1A2_Job {
                IN_FirstRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.FirstRecordedTrackNumber,
                IN_LastRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.LastRecordedTrackNumber,
                IN_Session1Format = IN_CompileJob.OUT_CompiledDiscInfo.SessionFormat,
                IN_LeadoutTimestamp = new Timestamp(OUT_Disc.Sectors.Count) //do we need a +150?
            };
            TOCMiscInfo.Run(OUT_Disc.RawTOCEntries);

            //TODO - generate leadout, or delegates at least

            //blech, old crap, maybe
            //OUT_Disc.Structure.Synthesize_TOCPointsFromSessions();

            //FinishLog();
        }
Example #6
0
        public void Synth(SectorSynthJob job)
        {
            //be lazy, just generate the whole sector unconditionally
            //this is mostly based on mednafen's approach, which was probably finely tailored for PSX
            //heres the comments on the subject:
            //  I'm not trusting that the "control" field for the TOC leadout entry will always be set properly, so | the control fields for the last track entry
            //  and the leadout entry together before extracting the D2 bit.  Audio track->data leadout is fairly benign though maybe noisy(especially if we ever implement
            //  data scrambling properly), but data track->audio leadout could break things in an insidious manner for the more accurate drive emulation code).

            var ses = job.Disc.Structure.Sessions[SessionNumber];
            int lba_relative = job.LBA - ses.LeadoutTrack.LBA;

            //data is zero

            int ts = lba_relative;
            int ats = job.LBA;

            const int ADR = 0x1; // Q channel data encodes position
            EControlQ control = ses.LeadoutTrack.Control;

            //ehhh? CDI?
             //if(toc.tracks[toc.last_track].valid)
             // control |= toc.tracks[toc.last_track].control & 0x4;
             //else if(toc.disc_type == DISC_TYPE_CD_I)
             // control |= 0x4;
            control |= (EControlQ)(((int)ses.LastInformationTrack.Control) & 4);

            SubchannelQ sq = new SubchannelQ();
            sq.SetStatus(ADR, control);
            sq.q_tno.BCDValue = 0xAA;
            sq.q_index.BCDValue = 0x01;
            sq.Timestamp = ts;
            sq.AP_Timestamp = ats;
            sq.zero = 0;

            //finally, rely on a gap sector to do the heavy lifting to synthesize this
            CUE.CueTrackType TrackType = CUE.CueTrackType.Audio;
            if (ses.LeadoutTrack.IsData)
            {
                if (job.Disc.TOC.Session1Format == SessionFormat.Type20_CDXA || job.Disc.TOC.Session1Format == SessionFormat.Type10_CDI)
                    TrackType = CUE.CueTrackType.Mode2_2352;
                else
                    TrackType = CUE.CueTrackType.Mode1_2352;
            }

            CUE.SS_Gap ss_gap = new CUE.SS_Gap()
            {
                Policy = Policy,
                sq = sq,
                TrackType = TrackType,
                Pause = true //?
            };

            ss_gap.Synth(job);
        }