private RawTOCEntry EmitRawTOCEntry(ATOCEntry entry)
        {
            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;
            }

            // get ADR & Control from ADR_Control byte
            byte adrc    = Convert.ToByte(entry.ADR_Control);
            var  Control = adrc & 0x0F;
            var  ADR     = adrc >> 4;

            var q = new SubchannelQ
            {
                q_status = SubchannelQ.ComputeStatus(ADR, (EControlQ)(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
            };

            return(new RawTOCEntry {
                QData = q
            });
        }
        /// <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);
        }
Пример #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);
        }
        void RunMednaDisc()
        {
            var disc = new Disc();

            OUT_Disc = disc;

            //create a MednaDisc and give it to the disc for ownership
            var md = new MednaDisc(IN_FromPath);

            disc.DisposableResources.Add(md);

            //"length of disc" for bizhawk's purposes (NOT a robust concept!) is determined by beginning of leadout track
            var m_leadoutTrack = md.TOCTracks[100];
            int nSectors       = (int)m_leadoutTrack.lba;

            //make synth param memos
            disc.SynthParams.MednaDisc = md;

            //this is the sole sector synthesizer we'll need
            var synth = new SS_MednaDisc();

            OUT_Disc.SynthProvider = new SimpleSectorSynthProvider()
            {
                SS = synth
            };

            //ADR (q-Mode) is necessarily 0x01 for a RawTOCEntry
            const int kADR            = 1;
            const int kUnknownControl = 0;

            //mednafen delivers us what is essentially but not exactly (or completely) a TOCRaw.
            //we need to synth RawTOCEntries from this and then turn it into a proper TOCRaw
            //when coming from mednafen, there are 101 entries.
            //entry[0] is placeholder junk, not to be used
            //entry[100] is the leadout track (A0)
            //A1 and A2 are in the form of FirstRecordedTrackNumber and LastRecordedTrackNumber
            for (int i = 1; i < 101; i++)
            {
                var m_te = md.TOCTracks[i];

                //dont add invalid (absent) items
                if (!m_te.Valid)
                {
                    continue;
                }

                var m_ts = new Timestamp((int)m_te.lba + 150);                 //these are supposed to be absolute timestamps

                var q = new SubchannelQ
                {
                    q_status = SubchannelQ.ComputeStatus(kADR, (EControlQ)m_te.control),
                    q_tno    = BCD2.FromDecimal(0), //unknown with mednadisc
                    q_index  = BCD2.FromDecimal(i),
                    min      = BCD2.FromDecimal(0), //unknown with mednadisc
                    sec      = BCD2.FromDecimal(0), //unknown with mednadisc
                    frame    = BCD2.FromDecimal(0), //unknown with mednadisc
                    zero     = 0,                   //unknown with mednadisc
                    ap_min   = BCD2.FromDecimal(m_ts.MIN),
                    ap_sec   = BCD2.FromDecimal(m_ts.SEC),
                    ap_frame = BCD2.FromDecimal(m_ts.FRAC),
                    q_crc    = 0                  //meaningless
                };

                //a special fixup: mednafen's entry 100 is the lead-out track, so change it into the A2 raw toc entry
                if (i == 100)
                {
                    q.q_index.BCDValue = 0xA2;
                }

                disc.RawTOCEntries.Add(new RawTOCEntry {
                    QData = q
                });
            }

            //synth A0 and A1 entries (indicating first and last recorded tracks and also session type)
            var qA0 = new SubchannelQ
            {
                q_status = SubchannelQ.ComputeStatus(kADR, kUnknownControl),
                q_tno    = BCD2.FromDecimal(0), //unknown with mednadisc
                q_index  = BCD2.FromBCD(0xA0),
                min      = BCD2.FromDecimal(0), //unknown with mednadisc
                sec      = BCD2.FromDecimal(0), //unknown with mednadisc
                frame    = BCD2.FromDecimal(0), //unknown with mednadisc
                zero     = 0,                   //unknown with mednadisc
                ap_min   = BCD2.FromDecimal(md.TOC.first_track),
                ap_sec   = BCD2.FromDecimal(md.TOC.disc_type),
                ap_frame = BCD2.FromDecimal(0),
                q_crc    = 0,              //meaningless
            };

            disc.RawTOCEntries.Add(new RawTOCEntry {
                QData = qA0
            });
            var qA1 = new SubchannelQ
            {
                q_status = SubchannelQ.ComputeStatus(kADR, kUnknownControl),
                q_tno    = BCD2.FromDecimal(0), //unknown with mednadisc
                q_index  = BCD2.FromBCD(0xA1),
                min      = BCD2.FromDecimal(0), //unknown with mednadisc
                sec      = BCD2.FromDecimal(0), //unknown with mednadisc
                frame    = BCD2.FromDecimal(0), //unknown with mednadisc
                zero     = 0,                   //unknown with mednadisc
                ap_min   = BCD2.FromDecimal(md.TOC.last_track),
                ap_sec   = BCD2.FromDecimal(0),
                ap_frame = BCD2.FromDecimal(0),
                q_crc    = 0,              //meaningless
            };

            disc.RawTOCEntries.Add(new RawTOCEntry {
                QData = qA1
            });
        }
Пример #5
0
        /// <summary>
        /// Creates the subcode (really, just subchannel Q) for this disc from its current TOC.
        /// Depends on the TOCPoints existing in the structure
        /// TODO - do we need a fully 0xFF P-subchannel for PSX?
        /// </summary>
        void Synthesize_SubcodeFromStructure()
        {
            int aba     = 0;
            int dpIndex = 0;

            //TODO - from mednafen (on PC-FX chip chan kick)
            //If we're more than 2 seconds(150 sectors) from the real "start" of the track/INDEX 01, and the track is a data track,
            //and the preceding track is an audio track, encode it as audio(by taking the SubQ control field from the preceding

            //NOTE: discs may have subcode which is nonsense or possibly not recoverable from a sensible disc structure.
            //but this function does what it says.

            //SO: heres the main idea of how this works.
            //we have the Structure.Points (whose name we dont like) which is a list of sectors where the tno/index changes.
            //So for each sector, we see if we've advanced to the next point.
            //TODO - check if this is synthesized correctly when producing a structure from a TOCRaw
            while (aba < Sectors.Count)
            {
                if (dpIndex < Structure.Points.Count - 1)
                {
                    while (aba >= Structure.Points[dpIndex + 1].ABA)
                    {
                        dpIndex++;
                    }
                }
                var dp = Structure.Points[dpIndex];


                var se = Sectors[aba];

                EControlQ control = dp.Control;
                bool      pause   = true;
                if (dp.Num != 0)                 //TODO - shouldnt this be IndexNum?
                {
                    pause = false;
                }
                if ((dp.Control & EControlQ.DataUninterrupted) != 0)
                {
                    pause = false;
                }

                int adr = dp.ADR;

                SubchannelQ sq = new SubchannelQ();
                sq.q_status = SubchannelQ.ComputeStatus(adr, control);
                sq.q_tno    = BCD2.FromDecimal(dp.TrackNum).BCDValue;
                sq.q_index  = BCD2.FromDecimal(dp.IndexNum).BCDValue;

                int track_relative_aba = aba - dp.Track.Indexes[1].aba;
                track_relative_aba = Math.Abs(track_relative_aba);
                Timestamp track_relative_timestamp = new Timestamp(track_relative_aba);
                sq.min   = BCD2.FromDecimal(track_relative_timestamp.MIN);
                sq.sec   = BCD2.FromDecimal(track_relative_timestamp.SEC);
                sq.frame = BCD2.FromDecimal(track_relative_timestamp.FRAC);
                sq.zero  = 0;
                Timestamp absolute_timestamp = new Timestamp(aba);
                sq.ap_min   = BCD2.FromDecimal(absolute_timestamp.MIN);
                sq.ap_sec   = BCD2.FromDecimal(absolute_timestamp.SEC);
                sq.ap_frame = BCD2.FromDecimal(absolute_timestamp.FRAC);

                var bss = new BufferedSubcodeSector();
                bss.Synthesize_SubchannelQ(ref sq, true);

                //TEST: need this for psx?
                if (pause)
                {
                    bss.Synthesize_SubchannelP(true);
                }

                se.SubcodeSector = bss;

                aba++;
            }
        }
Пример #6
0
        /// <summary>
        /// Loads a CCD at the specified path to a Disc object
        /// </summary>
        public Disc LoadCCDToDisc(string ccdPath)
        {
            var loadResults = LoadCCDPath(ccdPath);

            if (!loadResults.Valid)
            {
                throw loadResults.FailureException;
            }

            Disc disc = new Disc();

            var ccdf    = loadResults.ParsedCCDFile;
            var imgBlob = new Disc.Blob_RawFile()
            {
                PhysicalPath = loadResults.ImgPath
            };
            var subBlob = new Disc.Blob_RawFile()
            {
                PhysicalPath = loadResults.SubPath
            };

            disc.Blobs.Add(imgBlob);
            disc.Blobs.Add(subBlob);

            //generate DiscTOCRaw items from the ones specified in the CCD file
            //TODO - range validate these (too many truncations to byte)
            disc.RawTOCEntries = new List <RawTOCEntry>();
            BufferedSubcodeSector bss = new BufferedSubcodeSector();

            foreach (var entry in ccdf.TOCEntries)
            {
                var q = new SubchannelQ
                {
                    q_status = SubchannelQ.ComputeStatus(entry.ADR, (EControlQ)(entry.Control & 0xF)),
                    q_tno    = (byte)entry.TrackNo,
                    q_index  = (byte)entry.Point,
                    min      = BCD2.FromDecimal(entry.AMin),
                    sec      = BCD2.FromDecimal(entry.ASec),
                    frame    = BCD2.FromDecimal(entry.AFrame),
                    zero     = (byte)entry.Zero,
                    ap_min   = BCD2.FromDecimal(entry.PMin),
                    ap_sec   = BCD2.FromDecimal(entry.PSec),
                    ap_frame = BCD2.FromDecimal(entry.PFrame),
                };

                //CRC cant be calculated til we've got all the fields setup
                q.q_crc = bss.Synthesize_SubchannelQ(ref q, true);

                disc.RawTOCEntries.Add(new RawTOCEntry {
                    QData = q
                });
            }

            //generate the toc from the entries
            var tocSynth = new DiscTOCRaw.SynthesizeFromRawTOCEntriesJob()
            {
                Entries = disc.RawTOCEntries
            };

            tocSynth.Run();
            disc.TOCRaw = tocSynth.Result;

            //synthesize DiscStructure
            var structureSynth = new DiscStructure.SynthesizeFromDiscTOCRawJob()
            {
                TOCRaw = disc.TOCRaw
            };

            structureSynth.Run();
            disc.Structure = structureSynth.Result;

            //I *think* implicitly there is an index 0.. at.. i dunno, 0 maybe, for track 1
            {
                var dsi0 = new DiscStructure.Index();
                dsi0.LBA    = 0;
                dsi0.Number = 0;
                disc.Structure.Sessions[0].Tracks[0].Indexes.Add(dsi0);
            }

            //now, how to get the track types for the DiscStructure?
            //1. the CCD tells us (somehow the reader has judged)
            //2. scan it out of the Q subchannel
            //lets choose1.
            //TODO - better consider how to handle the situation where we have havent received all the [TRACK] items we need
            foreach (var st in disc.Structure.Sessions[0].Tracks)
            {
                var ccdt = ccdf.TracksByNumber[st.Number];
                switch (ccdt.Mode)
                {
                case 0:
                    st.TrackType = ETrackType.Audio;                             //for CCD, this means audio, apparently.
                    break;

                case 1:
                    st.TrackType = ETrackType.Mode1_2352;
                    break;

                case 2:
                    st.TrackType = ETrackType.Mode2_2352;
                    break;

                default:
                    throw new InvalidOperationException("Unsupported CCD mode");
                }

                //add indexes for this track
                foreach (var ccdi in ccdt.Indexes)
                {
                    var dsi = new DiscStructure.Index();
                    //if (ccdi.Key == 0) continue;
                    dsi.LBA    = ccdi.Value;
                    dsi.Number = ccdi.Key;
                    st.Indexes.Add(dsi);
                }
            }

            //add sectors for the lead-in, which isn't stored in the CCD file, I think
            //TODO - synthesize lead-in sectors from TOC, if the lead-in isn't available.
            //need a test case for that though.
            var leadin_sector_zero  = new Sector_Zero();
            var leadin_subcode_zero = new ZeroSubcodeSector();

            for (int i = 0; i < 150; i++)
            {
                var se = new SectorEntry(leadin_sector_zero);
                disc.Sectors.Add(se);
                se.SubcodeSector = leadin_subcode_zero;
            }

            //build the sectors:
            //set up as many sectors as we have img/sub for, even if the TOC doesnt reference them (TOC is unreliable, although the tracks should have covered it all)
            for (int i = 0; i < loadResults.NumImgSectors; i++)
            {
                var isec = new Sector_RawBlob();
                isec.Offset = ((long)i) * 2352;
                isec.Blob   = imgBlob;

                var se = new SectorEntry(isec);
                disc.Sectors.Add(se);

                var scsec = new BlobSubcodeSectorPreDeinterleaved();
                scsec.Offset     = ((long)i) * 96;
                scsec.Blob       = subBlob;
                se.SubcodeSector = scsec;
            }

            return(disc);
        }