332 lines
11 KiB
C#
332 lines
11 KiB
C#
using System;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
|
|
namespace BizHawk.Emulation.DiscSystem
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class DiscStructure
|
|
{
|
|
/// <summary>
|
|
/// Right now support for anything other than 1 session is totally not working
|
|
/// </summary>
|
|
public List<Session> Sessions = new List<Session>();
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public List<TOCPoint> Points;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public int LengthInSectors;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public Timestamp FriendlyLength { get { return new Timestamp(LengthInSectors); } }
|
|
|
|
/// <summary>
|
|
/// How many bytes of data in the disc (including lead-in). Disc sectors are really 2352 bytes each, so this is LengthInSectors * 2352
|
|
/// </summary>
|
|
public long BinarySize
|
|
{
|
|
get { return LengthInSectors * 2352; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synthesizes the DiscStructure from RawTOCEntriesJob
|
|
/// TODO - move to attic, not being used
|
|
/// WOULD BE NICE TO USE FOR CUE
|
|
/// </summary>
|
|
public class SynthesizeFromRawTOCEntriesJob
|
|
{
|
|
public IEnumerable<RawTOCEntry> 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// seeks the point immediately before (or equal to) this LBA
|
|
/// </summary>
|
|
public TOCPoint SeekPoint(int lba)
|
|
{
|
|
int aba = lba + 150;
|
|
for(int i=0;i<Points.Count;i++)
|
|
{
|
|
TOCPoint tp = Points[i];
|
|
if (tp.ABA > aba)
|
|
return Points[i - 1];
|
|
}
|
|
return Points[Points.Count - 1];
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 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?
|
|
/// </summary>
|
|
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; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the Points list from the current logical TOC
|
|
/// </summary>
|
|
public void Synthesize_TOCPointsFromSessions()
|
|
{
|
|
Points = new List<TOCPoint>();
|
|
|
|
int num = 0;
|
|
foreach (var ses in Sessions)
|
|
{
|
|
for(int t=0;t<ses.Tracks.Count;t++)
|
|
{
|
|
int tnum = t + 1;
|
|
var track = ses.Tracks[t];
|
|
for(int i=0;i<track.Indexes.Count;i++)
|
|
{
|
|
var index = track.Indexes[i];
|
|
bool repeat = false;
|
|
int aba = index.aba;
|
|
REPEAT:
|
|
var tp = new TOCPoint
|
|
{
|
|
Num = num++,
|
|
ABA = aba,
|
|
TrackNum = track.Number,
|
|
IndexNum = index.Number,
|
|
Track = track,
|
|
//ADR = 1, //It's hard to understand what other value could go here
|
|
Control = track.Control
|
|
};
|
|
|
|
//special case!
|
|
//yellow-book says:
|
|
//pre-gap for "first part of a digital data track not containing user data and encoded as a pause"
|
|
//first interval: at least 75 sectors coded as preceding track
|
|
//second interval: at least 150 sectors coded as user data track.
|
|
//TODO - add pause flag tracking to TOCPoint
|
|
//see mednafen's "TODO: Look into how we're supposed to handle subq control field in the four combinations of track types(data/audio)."
|
|
if (tnum != 1 && i == 0 && track.TrackType != ETrackType.Audio && !repeat)
|
|
{
|
|
//NOTE: we dont implement this exactly the same as mednafen, I think my logic is closer to the docs, but who knows, its complicated
|
|
int distance = track.Indexes[i + 1].aba - track.Indexes[i].aba;
|
|
//well, how do we know to apply this logic?
|
|
//we assume the 150 sector pregap is more important. so if thats all there is, theres no 75 sector pregap like the old track
|
|
//if theres a longer pregap, then we generate weird old track pregap to contain the rest.
|
|
if (distance > 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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public List<Track> Tracks = new List<Track>();
|
|
|
|
//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); } }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public enum ETrackType
|
|
{
|
|
/// <summary>
|
|
/// The track type isn't always known.. it can take this value til its populated
|
|
/// </summary>
|
|
Unknown,
|
|
|
|
/// <summary>
|
|
/// Data track( TOC Q control 0x04 flag set )
|
|
/// </summary>
|
|
Data,
|
|
|
|
/// <summary>
|
|
/// Audio track( TOC Q control 0x04 flag clear )
|
|
/// </summary>
|
|
Audio
|
|
}
|
|
|
|
public class Track
|
|
{
|
|
/// <summary>
|
|
/// The number of the track (1-indexed)
|
|
/// </summary>
|
|
public int Number;
|
|
|
|
/// <summary>
|
|
/// Is this track audio or data?
|
|
/// </summary>
|
|
public ETrackType TrackType;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public int ModeHeuristic;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public EControlQ Control;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
//public int ADR = 1; //??
|
|
|
|
/// <summary>
|
|
/// All the indexes related to the track. These will be 0-Indexed, but they could be non-consecutive.
|
|
/// </summary>
|
|
public List<Index> Indexes = new List<Index>();
|
|
|
|
/// <summary>
|
|
/// 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...
|
|
/// </summary>
|
|
public int LengthInSectors;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public int Start_ABA;
|
|
|
|
/// <summary>
|
|
/// The length as a timestamp (for accessing as a MM:SS:FF)
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
} |