using System; using System.Text; using System.Collections.Generic; namespace BizHawk.Emulation.DiscSystem { /// /// Contains structural information for the disc broken down into c# data structures for easy interrogation. /// This represents a best-effort interpretation of the raw disc image. /// You cannot assume a disc can be round-tripped into its original format through this (especially if it came from a format more detailed) /// TODO - index 0s arent populated /// IDEA: Make this be generated by a module which is aware of the 'firmware' being emulated, so firmware-specific rules can be applied. /// public class DiscStructure { /// /// Right now support for anything other than 1 session is totally not working /// public List Sessions = new List(); /// /// List of Points described by the TOC. /// TODO - this is kind of garbage. /// TODO - rename this /// TODO - generate it during loading of ccd/cue /// TODO - is this 98% redundant with the Tracks list? I think so. Maybe it can encode more detail, but the DiscStructure is lossy anyway /// Really, what it is, is a series of points where the tno/index change. Kind of an agenda. /// Maybe I should rename it something different, or at least comment it /// Or, you could look at this as a kind of compressed disc map /// NOTE: While this class is actually pretty useless for robust stuff, this list of TOCPoints could be considered robust once fully evaluated /// public List Points; /// /// How many sectors in the disc, including the 150 lead-in sectors, up to the end of the last track (before the lead-out track) /// TODO - does anyone ever need this as the ABA Count? Rename it LBACount or ABACount /// public int LengthInSectors; /// /// Length (including lead-in) of the disc as a timestamp /// TODO - does anyone ever need this as the ABA Count? Rename it LBACount or ABACount /// public Timestamp FriendlyLength { get { return new Timestamp(LengthInSectors); } } /// /// How many bytes of data in the disc (including lead-in). Disc sectors are really 2352 bytes each, so this is LengthInSectors * 2352 /// public long BinarySize { get { return LengthInSectors * 2352; } } /// /// Synthesizes the DiscStructure from RawTOCEntriesJob /// TODO - move to attic, not being used /// WOULD BE NICE TO USE FOR CUE /// public class SynthesizeFromRawTOCEntriesJob { public IEnumerable Entries; public DiscStructure Result; public void Run() { Result = new DiscStructure(); var session = new Session(); Result.Sessions.Add(session); //TODO - are these necessarily in order? foreach (var te in Entries) { int pt = te.QData.q_index.DecimalValue; int lba = te.QData.Timestamp.Sector; var bcd2 = new BCD2 { BCDValue = (byte)pt }; if (bcd2.DecimalValue > 99) //A0 A1 A2 leadout and crap continue; var track = new Track { Start_ABA = lba, Number = pt }; track.Indexes.Add(new Index()); //dummy index 0 track.Indexes.Add(new Index() { Number = 1, LBA = lba }); session.Tracks.Add(track); } } } /// /// seeks the point immediately before (or equal to) this LBA /// public TOCPoint SeekPoint(int lba) { int aba = lba + 150; for(int i=0;i aba) return Points[i - 1]; } return Points[Points.Count - 1]; } /// /// Uhm... I'm back to thinking this is a good idea. It's a pretty lean log of the shape of the disc and a good thing to generate a DiscStructure from (and maybe avoid using the DiscStructure altogether) /// Rename it to DiscMapEntry? /// public class TOCPoint { public int Num; public int ABA, TrackNum, IndexNum; public Track Track; public int ADR; //meh... public EControlQ Control; public int LBA { get { return ABA - 150; } } } /// /// Generates the Points list from the current logical TOC /// public void Synthesize_TOCPointsFromSessions() { Points = new List(); int num = 0; foreach (var ses in Sessions) { for(int t=0;t 150) { int weirdPregapSize = distance - 150; //need a new point. fix the old one tp.ADR = Points[Points.Count - 1].ADR; tp.Control = Points[Points.Count - 1].Control; Points.Add(tp); aba += weirdPregapSize; repeat = true; goto REPEAT; } } Points.Add(tp); } } var tpLeadout = new TOCPoint(); var lastTrack = ses.Tracks[ses.Tracks.Count - 1]; tpLeadout.Num = num++; tpLeadout.ABA = lastTrack.Indexes[1].aba + lastTrack.LengthInSectors; tpLeadout.IndexNum = 0; tpLeadout.TrackNum = 100; tpLeadout.Track = null; //no leadout track.. now... or ever? Points.Add(tpLeadout); } } public class Session { public int num; /// /// All the tracks in the session.. but... Tracks[0] should be "Track 1". So beware of this. /// SO SUCKY!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /// We might should keep this organized as a dictionary as well. /// public List Tracks = new List(); //the length of the session (should be the sum of all track lengths) public int length_aba; public Timestamp FriendlyLength { get { return new Timestamp(length_aba); } } } /// /// The Type of a track as specified in the TOC Q-Subchannel data from the control flags. /// Could also be 4-Channel Audio, but we'll handle that later if needed /// public enum ETrackType { /// /// The track type isn't always known.. it can take this value til its populated /// Unknown, /// /// Data track( TOC Q control 0x04 flag set ) /// Data, /// /// Audio track( TOC Q control 0x04 flag clear ) /// Audio } public class Track { /// /// The number of the track (1-indexed) /// public int Number; /// /// Is this track audio or data? /// public ETrackType TrackType; /// /// The mode of a track. /// 0 Will be audio, 1 and 2 will be data. /// XA sub-forms are expected to be variable within a track /// This is named as a Heuristic because it is a very ill-defined concept. /// There's virtually nothing that tells us what the Mode of a track is. You just have to guess. /// By contrast, the Control field is implied (and maybe specified) to be consistent and match the TOC /// public int ModeHeuristic; /// /// The 'control' properties of the track indicated by the subchannel Q. /// While in principle these could vary during the track, illegally (or maybe legally according to weird redbook rules) /// they normally don't; they're useful for describing what type of contents the track is. /// public EControlQ Control; /// /// Well, it seems a track can have an ADR property (used to fill the subchannel Q). This is delivered from a CCD file but may have to be guessed from /// //public int ADR = 1; //?? /// /// All the indexes related to the track. These will be 0-Indexed, but they could be non-consecutive. /// public List Indexes = new List(); /// /// a track logically starts at index 1. /// so this is the length from this index 1 to the next index 1 (or the end of the disc) /// the time before track 1 index 1 is the lead-in [edit: IS IT?] and isn't accounted for in any track... /// public int LengthInSectors; /// /// The beginning ABA of the track (index 1). This isn't well-supported, yet /// WHAT? IS THIS NOT AN ABA SOMETIMES? /// IS IT THE INDEX 0 OF THE TRACK? THATS FUCKED UP. COMPARE TO TOCRAW ENTRIES. IT SHOULD BE MATCHING THAT /// HEY??? SHOULD THIS EVEN BE HERE? YOURE SUPPOSED TO USE THE INDEXES INSTEAD. /// WELL, IF WE KEEP THIS THE MEANING SHOULD BE SAME AS INDEX[1].LBA (or ABA) SO BE SURE TO WRITE THAT COMMENT HERE /// public int Start_ABA; /// /// The length as a timestamp (for accessing as a MM:SS:FF) /// public Timestamp FriendlyLength { get { return new Timestamp(LengthInSectors); } } } public class Index { public int Number; public int aba; public int LBA { get { return aba - 150; } set { aba = value + 150; } } //the length of the section //HEY! This is commented out because it is a bad idea. //The length of a `section`? (what's a section?) is almost useless, and if you want it, you are probably making an error. //public int length_lba; //public Cue.Timestamp FriendlyLength { get { return new Cue.Timestamp(length_lba); } } } public void AnalyzeLengthsFromIndexLengths() { //this is a little more complex than it looks, because the length of a thing is not determined by summing it //but rather by the difference in lbas between start and end LengthInSectors = 0; foreach (var session in Sessions) { var firstTrack = session.Tracks[0]; var lastTrack = session.Tracks[session.Tracks.Count - 1]; session.length_aba = lastTrack.Indexes[0].aba + lastTrack.LengthInSectors - firstTrack.Indexes[0].aba; LengthInSectors += session.length_aba; } } } }