disc subchannel Q calculation; make TOC a little more useful by adding TOCPoints which are easier to search than nested sessions, tracks, and indices; and change path browser to use a superior folder browser which lets you enter paths into a textbox. I refuse to click to navigate folders
This commit is contained in:
parent
266d81f644
commit
019ad69459
|
@ -176,6 +176,11 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
|
|||
// like this, they probably become available as the bits come off the disc.
|
||||
// but lets get some basic functionality before we go crazy.
|
||||
// Idunno, maybe they do come in a sector at a time.
|
||||
|
||||
//note to vecna: maybe not at the sector level, but at a level > 1 sample and <= 1 sector, samples come out in blocks
|
||||
//due to the way they are jumbled up (seriously, like put into a blender) for error correction purposes.
|
||||
//we may as well assume that the cd audio decoding magic works at the level of one sector, but it isnt one sample.
|
||||
|
||||
if (SectorsLeftToRead == 0)
|
||||
{
|
||||
DataReadInProgress = false;
|
||||
|
@ -491,6 +496,10 @@ throw new Exception("requesting 0 sectors read.............................");
|
|||
|
||||
private void CommandReadSubcodeQ()
|
||||
{
|
||||
//TODO VECNA - i changed this for you but maybe i did it wrong
|
||||
var sectorEntry = disc.ReadSectorEntry(CurrentReadingSector);
|
||||
|
||||
|
||||
DataIn.Clear();
|
||||
|
||||
switch (pce.CDAudio.Mode)
|
||||
|
@ -499,15 +508,19 @@ throw new Exception("requesting 0 sectors read.............................");
|
|||
case CDAudio.CDAudioMode.Paused: DataIn.Enqueue(2); break;
|
||||
case CDAudio.CDAudioMode.Stopped: DataIn.Enqueue(3); break;
|
||||
}
|
||||
DataIn.Enqueue(0); // unused?
|
||||
DataIn.Enqueue((byte)pce.CDAudio.PlayingTrack); // track
|
||||
DataIn.Enqueue(1); // index
|
||||
DataIn.Enqueue(1); // M(rel)
|
||||
DataIn.Enqueue(1); // S(rel)
|
||||
DataIn.Enqueue(1); // F(rel)
|
||||
DataIn.Enqueue(1); // M(abs)
|
||||
DataIn.Enqueue(1); // S(abs)
|
||||
DataIn.Enqueue(1); // F(abs)
|
||||
|
||||
DataIn.Enqueue(sectorEntry.q_status); // unused?
|
||||
|
||||
//DataIn.Enqueue((byte)pce.CDAudio.PlayingTrack); // track //vecna's
|
||||
DataIn.Enqueue(sectorEntry.q_tno.BCDValue); // track //zero's
|
||||
|
||||
DataIn.Enqueue(sectorEntry.q_index.BCDValue); // index
|
||||
DataIn.Enqueue(sectorEntry.q_min.BCDValue); // M(rel)
|
||||
DataIn.Enqueue(sectorEntry.q_sec.BCDValue); // S(rel)
|
||||
DataIn.Enqueue(sectorEntry.q_frame.BCDValue); // F(rel)
|
||||
DataIn.Enqueue(sectorEntry.q_amin.BCDValue); // M(abs)
|
||||
DataIn.Enqueue(sectorEntry.q_asec.BCDValue); // S(abs)
|
||||
DataIn.Enqueue(sectorEntry.q_aframe.BCDValue); // F(abs)
|
||||
SetPhase(BusPhase.DataIn);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
namespace BizHawk
|
||||
{
|
||||
public static class CRC32
|
||||
{
|
||||
public static class CRC32
|
||||
{
|
||||
// Lookup table for speed.
|
||||
private static uint[] CRC32Table;
|
||||
|
||||
static CRC32()
|
||||
{
|
||||
static CRC32()
|
||||
{
|
||||
CRC32Table = new uint[256];
|
||||
for (uint i = 0; i < 256; ++i)
|
||||
{
|
||||
for (uint i = 0; i < 256; ++i)
|
||||
{
|
||||
uint crc = i;
|
||||
for (int j = 8; j > 0; --j)
|
||||
{
|
||||
for (int j = 8; j > 0; --j)
|
||||
{
|
||||
if ((crc & 1) == 1)
|
||||
crc = ((crc >> 1) ^ 0xEDB88320);
|
||||
else
|
||||
else
|
||||
crc >>= 1;
|
||||
}
|
||||
CRC32Table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Calculate(byte[] data)
|
||||
{
|
||||
uint Result = 0xFFFFFFFF;
|
||||
foreach (var b in data)
|
||||
Result = (((Result) >> 8) ^ CRC32Table[b ^ ((Result) & 0xFF)]);
|
||||
return (int)~Result;
|
||||
public static int Calculate(byte[] data)
|
||||
{
|
||||
uint Result = 0xFFFFFFFF;
|
||||
foreach (var b in data)
|
||||
Result = (((Result) >> 8) ^ CRC32Table[b ^ ((Result) & 0xFF)]);
|
||||
return (int) ~Result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -84,8 +84,8 @@ namespace BizHawk.DiscSystem
|
|||
throw new DiscReferenceException(blobPath, ex);
|
||||
}
|
||||
|
||||
blob_length_lba = (int) (blob.Length/blob_sectorsize);
|
||||
blob_leftover = (int) (blob.Length - blob_length_lba*blob_sectorsize);
|
||||
blob_length_lba = (int)(blob.Length / blob_sectorsize);
|
||||
blob_leftover = (int)(blob.Length - blob_length_lba * blob_sectorsize);
|
||||
cue_blob = blob;
|
||||
}
|
||||
else throw new DiscReferenceException(blobPath, new InvalidOperationException("unknown cue file type: " + cue_file.StrFileType));
|
||||
|
@ -149,9 +149,9 @@ namespace BizHawk.DiscSystem
|
|||
if (curr_track == 1)
|
||||
{
|
||||
if (cue_track.PreGap.LBA != 0)
|
||||
throw new InvalidOperationException("not supported: cue files with track 1 pregaps");
|
||||
//but now we add one anyway
|
||||
cue_track.PreGap = new Cue.CueTimestamp(150);
|
||||
throw new InvalidOperationException("not supported (yet): cue files with track 1 pregaps");
|
||||
//but now we add one anyway, because every existing cue+bin seems to implicitly specify this
|
||||
cue_track.PreGap = new Timestamp(150);
|
||||
}
|
||||
|
||||
//check whether a pregap is requested.
|
||||
|
@ -190,7 +190,7 @@ namespace BizHawk.DiscSystem
|
|||
{
|
||||
toc_index.lba = track_disc_pregap_lba - (cue_track.Indexes[1].Timestamp.LBA - cue_track.Indexes[0].Timestamp.LBA);
|
||||
}
|
||||
else toc_index.lba = Sectors.Count;
|
||||
else toc_index.lba = Sectors.Count;
|
||||
|
||||
//calculate length of the index
|
||||
//if it is the last index then we use our calculation from before, otherwise we check the next index
|
||||
|
@ -202,7 +202,7 @@ namespace BizHawk.DiscSystem
|
|||
//emit sectors
|
||||
for (int lba = 0; lba < index_length_lba; lba++)
|
||||
{
|
||||
bool is_last_lba_in_index = (lba == index_length_lba-1);
|
||||
bool is_last_lba_in_index = (lba == index_length_lba - 1);
|
||||
bool is_last_lba_in_track = is_last_lba_in_index && is_last_index;
|
||||
|
||||
switch (cue_track.TrackType)
|
||||
|
@ -220,7 +220,7 @@ namespace BizHawk.DiscSystem
|
|||
Sector_Raw sector_raw = new Sector_Raw();
|
||||
sector_raw.BaseSector = sector_rawblob;
|
||||
//take care to handle final sectors that are too short.
|
||||
if (is_last_lba_in_track && blob_leftover>0)
|
||||
if (is_last_lba_in_track && blob_leftover > 0)
|
||||
{
|
||||
Sector_ZeroPad sector_zeropad = new Sector_ZeroPad();
|
||||
sector_zeropad.BaseSector = sector_rawblob;
|
||||
|
@ -328,7 +328,7 @@ namespace BizHawk.DiscSystem
|
|||
|
||||
public static int BINSectorSizeForTrackType(ETrackType type)
|
||||
{
|
||||
switch(type)
|
||||
switch (type)
|
||||
{
|
||||
case ETrackType.Mode1_2352:
|
||||
case ETrackType.Mode2_2352:
|
||||
|
@ -371,52 +371,16 @@ namespace BizHawk.DiscSystem
|
|||
{
|
||||
public ETrackType TrackType;
|
||||
public int TrackNum;
|
||||
public CueTimestamp PreGap = new CueTimestamp();
|
||||
public CueTimestamp PostGap = new CueTimestamp();
|
||||
public Timestamp PreGap = new Timestamp();
|
||||
public Timestamp PostGap = new Timestamp();
|
||||
public Dictionary<int, CueTrackIndex> Indexes = new Dictionary<int, CueTrackIndex>();
|
||||
}
|
||||
|
||||
public class CueTimestamp
|
||||
{
|
||||
/// <summary>
|
||||
/// creates timestamp of 00:00:00
|
||||
/// </summary>
|
||||
public CueTimestamp()
|
||||
{
|
||||
Value = "00:00:00";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates a timestamp from a string in the form mm:ss:ff
|
||||
/// </summary>
|
||||
public CueTimestamp(string value) {
|
||||
this.Value = value;
|
||||
MIN = int.Parse(value.Substring(0, 2));
|
||||
SEC = int.Parse(value.Substring(3, 2));
|
||||
FRAC = int.Parse(value.Substring(6, 2));
|
||||
LBA = MIN * 60 * 75 + SEC * 75 + FRAC;
|
||||
}
|
||||
public readonly string Value;
|
||||
public readonly int MIN, SEC, FRAC, LBA;
|
||||
|
||||
/// <summary>
|
||||
/// creates timestamp from supplied LBA
|
||||
/// </summary>
|
||||
public CueTimestamp(int LBA)
|
||||
{
|
||||
this.LBA = LBA;
|
||||
MIN = LBA / (60*75);
|
||||
SEC = (LBA / 75)%60;
|
||||
FRAC = LBA % 75;
|
||||
Value = string.Format("{0:D2}:{1:D2}:{2:D2}", MIN, SEC, FRAC);
|
||||
}
|
||||
}
|
||||
|
||||
public class CueTrackIndex
|
||||
{
|
||||
public CueTrackIndex(int num) { IndexNum = num; }
|
||||
public int IndexNum;
|
||||
public CueTimestamp Timestamp;
|
||||
public Timestamp Timestamp;
|
||||
public int ZeroLBA;
|
||||
}
|
||||
|
||||
|
@ -517,11 +481,11 @@ namespace BizHawk.DiscSystem
|
|||
throw new CueBrokenException("malformed index number");
|
||||
if (clp.EOF) throw new CueBrokenException("invalid cue structure (missing index timestamp)");
|
||||
string str_timestamp = clp.ReadToken();
|
||||
if(indexnum <0 || indexnum>99) throw new CueBrokenException("`All index numbers must be between 0 and 99 inclusive.`");
|
||||
if (indexnum < 0 || indexnum > 99) throw new CueBrokenException("`All index numbers must be between 0 and 99 inclusive.`");
|
||||
if (indexnum != 1 && indexnum != last_index_num + 1) throw new CueBrokenException("`The first index must be 0 or 1 with all other indexes being sequential to the first one.`");
|
||||
last_index_num = indexnum;
|
||||
CueTrackIndex cti = new CueTrackIndex(indexnum);
|
||||
cti.Timestamp = new CueTimestamp(str_timestamp);
|
||||
cti.Timestamp = new Timestamp(str_timestamp);
|
||||
cti.IndexNum = indexnum;
|
||||
currTrack.Indexes[indexnum] = cti;
|
||||
break;
|
||||
|
@ -529,13 +493,13 @@ namespace BizHawk.DiscSystem
|
|||
case "PREGAP":
|
||||
if (track_has_pregap) throw new CueBrokenException("`Only one PREGAP command is allowed per track.`");
|
||||
if (currTrack.Indexes.Count > 0) throw new CueBrokenException("`The PREGAP command must appear after a TRACK command, but before any INDEX commands.`");
|
||||
currTrack.PreGap = new CueTimestamp(clp.ReadToken());
|
||||
currTrack.PreGap = new Timestamp(clp.ReadToken());
|
||||
track_has_pregap = true;
|
||||
break;
|
||||
case "POSTGAP":
|
||||
if (track_has_postgap) throw new CueBrokenException("`Only one POSTGAP command is allowed per track.`");
|
||||
track_has_postgap = true;
|
||||
currTrack.PostGap = new CueTimestamp(clp.ReadToken());
|
||||
currTrack.PostGap = new Timestamp(clp.ReadToken());
|
||||
break;
|
||||
case "CATALOG":
|
||||
case "PERFORMER":
|
||||
|
|
|
@ -56,19 +56,55 @@ namespace BizHawk.DiscSystem
|
|||
}
|
||||
}
|
||||
|
||||
//TODO - rename these APIs to ReadSector
|
||||
public partial class Disc
|
||||
{
|
||||
//main API to read a 2352-byte LBA from a disc.
|
||||
//this starts at the beginning of the disc (at the lead-in)
|
||||
//so add 150 to get to get an address in the user data area
|
||||
/// <summary>
|
||||
/// Main API to read a 2352-byte sector from a disc.
|
||||
/// This starts at the beginning of the "userdata" area of the disc (track 1, index 0)
|
||||
/// However, located here is a mandatory pregap of 2 seconds (or more?).
|
||||
/// so you may need to add 150 depending on how your system addresses things to get to the "start" of the first track. (track 1, index 1)
|
||||
/// </summary>
|
||||
public void ReadLBA_2352(int lba, byte[] buffer, int offset)
|
||||
{
|
||||
Sectors[lba].Sector.Read(buffer, offset);
|
||||
}
|
||||
|
||||
//main API to read a 2048-byte LBA from a disc.
|
||||
//this starts at the beginning of the disc (at the lead-in)
|
||||
//so add 150 to get to get an address in the user data area
|
||||
/// <summary>
|
||||
/// Returns a SectorEntry from which you can retrieve various interesting pieces of information about the sector.
|
||||
/// The SectorEntry's interface is not likely to be stable, though, but it may be more convenient.
|
||||
/// </summary>
|
||||
public SectorEntry ReadSectorEntry(int lba)
|
||||
{
|
||||
return Sectors[lba];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified sector's subcode (96 bytes) deinterleaved into the provided buffer.
|
||||
/// P is first 12 bytes, followed by 12 Q bytes, etc.
|
||||
/// I'm not sure what format scsi commands generally return it in.
|
||||
/// It could be this, or RAW (interleaved) which I could also supply when we need it
|
||||
/// </summary>
|
||||
public void ReadSector_Subcode_Deinterleaved(int lba, byte[] buffer, int offset)
|
||||
{
|
||||
Array.Clear(buffer, offset, 96);
|
||||
Sectors[lba].Read_SubchannelQ(buffer, offset + 12);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified sector's subchannel Q (12 bytes) into the provided buffer
|
||||
/// </summary>
|
||||
public void ReadSector_Subchannel_Q(int lba, byte[] buffer, int offset)
|
||||
{
|
||||
Sectors[lba].Read_SubchannelQ(buffer, offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main API to read a 2048-byte sector from a disc.
|
||||
/// This starts at the beginning of the "userdata" area of the disc (track 1, index 0)
|
||||
/// However, located here is a mandatory pregap of 2 seconds (or more?).
|
||||
/// so you may need to add 150 depending on how your system addresses things to get to the "start" of the first track. (track 1, index 1)
|
||||
/// </summary>
|
||||
public void ReadLBA_2048(int lba, byte[] buffer, int offset)
|
||||
{
|
||||
byte[] temp = new byte[2352];
|
||||
|
@ -77,12 +113,13 @@ namespace BizHawk.DiscSystem
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// main API to determine how many LBA sectors are available
|
||||
/// Main API to determine how many sectors are available on the disc.
|
||||
/// This counts from absolute sector 0 to the final sector available.
|
||||
/// </summary>
|
||||
public int LBACount { get { return Sectors.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// indicates whether this disc took significant work to load from the disc (i.e. decoding of ECM or audio data)
|
||||
/// indicates whether this disc took significant work to load from the hard drive (i.e. decoding of ECM or audio data)
|
||||
/// In this case, the user may appreciate a prompt to export the disc so that it won't take so long next time.
|
||||
/// </summary>
|
||||
public bool WasSlowLoad { get; private set; }
|
||||
|
@ -96,7 +133,7 @@ namespace BizHawk.DiscSystem
|
|||
}
|
||||
|
||||
// converts LBA to minute:second:frame format.
|
||||
//TODO - somewhat redundant with CueTimestamp, which is due for refactoring into something not cue-related
|
||||
//TODO - somewhat redundant with Timestamp, which is due for refactoring into something not cue-related
|
||||
public static void ConvertLBAtoMSF(int lba, out byte m, out byte s, out byte f)
|
||||
{
|
||||
m = (byte) (lba / 75 / 60);
|
||||
|
|
|
@ -262,6 +262,49 @@ namespace BizHawk.DiscSystem
|
|||
{
|
||||
public SectorEntry(ISector sec) { this.Sector = sec; }
|
||||
public ISector Sector;
|
||||
|
||||
//todo - add some PARAMETER fields to this, so that the ISector can use them (so that each ISector doesnt have to be constructed also)
|
||||
//also then, maybe this could be a struct
|
||||
|
||||
//q-subchannel stuff. can be returned directly, or built into the entire subcode sector if you want
|
||||
|
||||
/// <summary>
|
||||
/// ADR and CONTROL
|
||||
/// </summary>
|
||||
public byte q_status;
|
||||
|
||||
/// <summary>
|
||||
/// BCD indications of the current track number and index
|
||||
/// </summary>
|
||||
public BCD2 q_tno, q_index;
|
||||
|
||||
/// <summary>
|
||||
/// track-relative timestamp
|
||||
/// </summary>
|
||||
public BCD2 q_min, q_sec, q_frame;
|
||||
/// <summary>
|
||||
/// absolute timestamp
|
||||
/// </summary>
|
||||
public BCD2 q_amin, q_asec, q_aframe;
|
||||
|
||||
public void Read_SubchannelQ(byte[] buffer, int offset)
|
||||
{
|
||||
buffer[offset + 0] = q_status;
|
||||
buffer[offset + 1] = q_tno.BCDValue;
|
||||
buffer[offset + 2] = q_index.BCDValue;
|
||||
buffer[offset + 3] = q_min.BCDValue;
|
||||
buffer[offset + 4] = q_sec.BCDValue;
|
||||
buffer[offset + 5] = q_frame.BCDValue;
|
||||
buffer[offset + 6] = 0;
|
||||
buffer[offset + 7] = q_amin.BCDValue;
|
||||
buffer[offset + 8] = q_asec.BCDValue;
|
||||
buffer[offset + 9] = q_aframe.BCDValue;
|
||||
|
||||
ushort crc16 = CRC16_CCITT.Calculate(buffer, 0, 10);
|
||||
//CRC is stored inverted and big endian
|
||||
buffer[offset + 10] = (byte)(~(crc16 >> 8));
|
||||
buffer[offset + 11] = (byte)(~(crc16));
|
||||
}
|
||||
}
|
||||
|
||||
public List<IBlob> Blobs = new List<IBlob>();
|
||||
|
@ -324,7 +367,8 @@ namespace BizHawk.DiscSystem
|
|||
ret.cue = string.Format("FILE \"{0}\" BINARY\n", bfd.name) + cue;
|
||||
ret.bins.Add(bfd);
|
||||
bfd.SectorSize = 2352;
|
||||
//skip the lead-in!
|
||||
|
||||
//skip the mandatory track 1 pregap! cue+bin files do not contain it
|
||||
for (int i = 150; i < TOC.length_lba; i++)
|
||||
{
|
||||
bfd.lbas.Add(i);
|
||||
|
@ -345,7 +389,7 @@ namespace BizHawk.DiscSystem
|
|||
ret.bins.Add(bfd);
|
||||
int lba=0;
|
||||
|
||||
//skip leadin
|
||||
//skip the mandatory track 1 pregap! cue+bin files do not contain it
|
||||
if (i == 0) lba = 150;
|
||||
|
||||
for (; lba < track.length_lba; lba++)
|
||||
|
@ -360,19 +404,19 @@ namespace BizHawk.DiscSystem
|
|||
foreach (var index in track.Indexes)
|
||||
{
|
||||
int x = index.lba - track.Indexes[0].lba;
|
||||
if (prefs.OmitRedundantIndex0 && index.num == 0 && index.lba == track.Indexes[1].lba)
|
||||
{
|
||||
//dont emit index 0 when it is the same as index 1, it confuses some cue parsers
|
||||
}
|
||||
else if (i==0 && index.num == 0)
|
||||
{
|
||||
//don't generate the first index, it is illogical
|
||||
}
|
||||
else
|
||||
//if (prefs.OmitRedundantIndex0 && index.num == 0 && index.lba == track.Indexes[1].lba)
|
||||
//{
|
||||
// //dont emit index 0 when it is the same as index 1, it confuses some cue parsers
|
||||
//}
|
||||
//else if (i==0 && index.num == 0)
|
||||
//{
|
||||
// //don't generate the first index, it is illogical
|
||||
//}
|
||||
//else
|
||||
{
|
||||
//track 1 included the lead-in at the beginning of it. sneak past that.
|
||||
if (i == 0) x -= 150;
|
||||
sbCue.AppendFormat(" INDEX {0:D2} {1}\n", index.num, new Cue.CueTimestamp(x).Value);
|
||||
//if (i == 0) x -= 150;
|
||||
sbCue.AppendFormat(" INDEX {0:D2} {1}\n", index.num, new Timestamp(x).Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -398,6 +442,8 @@ namespace BizHawk.DiscSystem
|
|||
{
|
||||
var ret = new Disc();
|
||||
ret.FromCuePathInternal(cuePath);
|
||||
ret.TOC.GeneratePoints();
|
||||
ret.PopulateQSubchannel();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -405,8 +451,155 @@ namespace BizHawk.DiscSystem
|
|||
{
|
||||
var ret = new Disc();
|
||||
ret.FromIsoPathInternal(isoPath);
|
||||
ret.TOC.GeneratePoints();
|
||||
ret.PopulateQSubchannel();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates subchannel Q data track for this disc
|
||||
/// </summary>
|
||||
void PopulateQSubchannel()
|
||||
{
|
||||
int lba = 0;
|
||||
int dpIndex = 0;
|
||||
|
||||
while (lba < Sectors.Count)
|
||||
{
|
||||
if (dpIndex < TOC.Points.Count - 1)
|
||||
{
|
||||
if (lba >= TOC.Points[dpIndex + 1].LBA)
|
||||
{
|
||||
dpIndex++;
|
||||
}
|
||||
}
|
||||
var dp = TOC.Points[dpIndex];
|
||||
|
||||
var se = Sectors[lba];
|
||||
|
||||
int control = 0;
|
||||
//choose a control byte depending on whether this is an audio or data track
|
||||
if(dp.Track.TrackType == ETrackType.Audio)
|
||||
control = (int)Q_Control.StereoNoPreEmph;
|
||||
else control = (int)Q_Control.DataUninterrupted;
|
||||
|
||||
//we always use ADR=1 (mode-1 q block)
|
||||
//this could be more sophisticated but it is almost useless for emulation (only useful for catalog/ISRC numbers)
|
||||
int adr = 1;
|
||||
se.q_status = (byte)(adr | (control << 4));
|
||||
se.q_tno = BCD2.FromDecimal(dp.TrackNum);
|
||||
se.q_index = BCD2.FromDecimal(dp.IndexNum);
|
||||
|
||||
int track_relative_lba = lba - dp.Track.Indexes[1].lba;
|
||||
track_relative_lba = Math.Abs(track_relative_lba);
|
||||
Timestamp track_relative_timestamp = new Timestamp(track_relative_lba);
|
||||
se.q_min = BCD2.FromDecimal(track_relative_timestamp.MIN);
|
||||
se.q_sec = BCD2.FromDecimal(track_relative_timestamp.SEC);
|
||||
se.q_frame = BCD2.FromDecimal(track_relative_timestamp.FRAC);
|
||||
Timestamp absolute_timestamp = new Timestamp(lba);
|
||||
se.q_amin = BCD2.FromDecimal(absolute_timestamp.MIN);
|
||||
se.q_asec = BCD2.FromDecimal(absolute_timestamp.SEC);
|
||||
se.q_aframe = BCD2.FromDecimal(absolute_timestamp.FRAC);
|
||||
|
||||
lba++;
|
||||
}
|
||||
}
|
||||
|
||||
static byte IntToBCD(int n)
|
||||
{
|
||||
int ones;
|
||||
int tens = Math.DivRem(n,10,out ones);
|
||||
return (byte)((tens<<4)|ones);
|
||||
}
|
||||
|
||||
private enum Q_Control
|
||||
{
|
||||
StereoNoPreEmph = 0,
|
||||
StereoPreEmph = 1,
|
||||
MonoNoPreemph = 8,
|
||||
MonoPreEmph = 9,
|
||||
DataUninterrupted = 4,
|
||||
DataIncremental = 5,
|
||||
|
||||
CopyProhibitedMask = 0,
|
||||
CopyPermittedMask = 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// encapsulates a 2 digit BCD number as used various places in the CD specs
|
||||
/// </summary>
|
||||
public struct BCD2
|
||||
{
|
||||
/// <summary>
|
||||
/// The raw BCD value. you can't do math on this number! but you may be asked to supply it to a game program.
|
||||
/// The largest number it can logically contain is 99
|
||||
/// </summary>
|
||||
public byte BCDValue;
|
||||
|
||||
/// <summary>
|
||||
/// The derived decimal value. you can do math on this! the largest number it can logically contain is 99.
|
||||
/// </summary>
|
||||
public int DecimalValue
|
||||
{
|
||||
get { return (BCDValue & 0xF) + ((BCDValue >> 4) & 0xF) * 10; }
|
||||
set { BCDValue = IntToBCD(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// makes a BCD2 from a decimal number. don't supply a number > 99 or you might not like the results
|
||||
/// </summary>
|
||||
public static BCD2 FromDecimal(int d)
|
||||
{
|
||||
BCD2 ret = new BCD2();
|
||||
ret.DecimalValue = d;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static byte IntToBCD(int n)
|
||||
{
|
||||
int ones;
|
||||
int tens = Math.DivRem(n, 10, out ones);
|
||||
return (byte)((tens << 4) | ones);
|
||||
}
|
||||
}
|
||||
|
||||
public class Timestamp
|
||||
{
|
||||
/// <summary>
|
||||
/// creates timestamp of 00:00:00
|
||||
/// </summary>
|
||||
public Timestamp()
|
||||
{
|
||||
Value = "00:00:00";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates a timestamp from a string in the form mm:ss:ff
|
||||
/// </summary>
|
||||
public Timestamp(string value)
|
||||
{
|
||||
this.Value = value;
|
||||
MIN = int.Parse(value.Substring(0, 2));
|
||||
SEC = int.Parse(value.Substring(3, 2));
|
||||
FRAC = int.Parse(value.Substring(6, 2));
|
||||
LBA = MIN * 60 * 75 + SEC * 75 + FRAC;
|
||||
}
|
||||
public readonly string Value;
|
||||
public readonly int MIN, SEC, FRAC, LBA;
|
||||
|
||||
/// <summary>
|
||||
/// creates timestamp from supplied LBA
|
||||
/// </summary>
|
||||
public Timestamp(int LBA)
|
||||
{
|
||||
this.LBA = LBA;
|
||||
MIN = LBA / (60 * 75);
|
||||
SEC = (LBA / 75) % 60;
|
||||
FRAC = LBA % 75;
|
||||
Value = string.Format("{0:D2}:{1:D2}:{2:D2}", MIN, SEC, FRAC);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ETrackType
|
||||
|
@ -435,6 +628,11 @@ namespace BizHawk.DiscSystem
|
|||
/// </summary>
|
||||
public bool ReallyDumpBin;
|
||||
|
||||
/// <summary>
|
||||
/// dump a .sub.q along with bins. one day we'll want to dump the entire subcode but really Q is all thats important for debugging most things
|
||||
/// </summary>
|
||||
public bool DumpSubchannelQ;
|
||||
|
||||
/// <summary>
|
||||
/// generate remarks and other annotations to help humans understand whats going on, but which will confuse many cue parsers
|
||||
/// </summary>
|
||||
|
@ -455,11 +653,13 @@ namespace BizHawk.DiscSystem
|
|||
/// </summary>
|
||||
public bool SingleSession;
|
||||
|
||||
/// <summary>
|
||||
/// some cue parsers can't handle redundant Index 0 (equal to Index 1). Such as daemon tools. So, hide those indices.
|
||||
/// Our canonical format craves explicitness so this is defaulted off.
|
||||
/// </summary>
|
||||
public bool OmitRedundantIndex0 = false;
|
||||
//THIS IS WRONG-HEADED. track 1 index 0 must never equal index 1! apparently.
|
||||
|
||||
///// <summary>
|
||||
///// some cue parsers can't handle redundant Index 0 (equal to Index 1). Such as daemon tools. So, hide those indices.
|
||||
///// Our canonical format craves explicitness so this is defaulted off.
|
||||
///// </summary>
|
||||
//public bool OmitRedundantIndex0 = false;
|
||||
|
||||
/// <summary>
|
||||
/// DO NOT CHANGE THIS! All sectors will be written with ECM data. It's a waste of space, but it is exact. (not completely supported yet)
|
||||
|
@ -482,6 +682,8 @@ namespace BizHawk.DiscSystem
|
|||
{
|
||||
public string name;
|
||||
public List<int> lbas = new List<int>();
|
||||
|
||||
//todo - do we really need this? i dont think so...
|
||||
public List<bool> lba_zeros = new List<bool>();
|
||||
public int SectorSize;
|
||||
}
|
||||
|
@ -507,8 +709,8 @@ namespace BizHawk.DiscSystem
|
|||
string sha1 = Util.Hash_SHA1(dump, 0, dump.Length);
|
||||
|
||||
int pregap = track.Indexes[1].lba - track.Indexes[0].lba;
|
||||
Cue.CueTimestamp pregap_ts = new Cue.CueTimestamp(pregap);
|
||||
Cue.CueTimestamp len_ts = new Cue.CueTimestamp(track.length_lba);
|
||||
Timestamp pregap_ts = new Timestamp(pregap);
|
||||
Timestamp len_ts = new Timestamp(track.length_lba);
|
||||
sb.AppendFormat("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}\t{8}\n",
|
||||
i,
|
||||
Cue.RedumpTypeStringForTrackType(track.TrackType),
|
||||
|
@ -532,6 +734,7 @@ namespace BizHawk.DiscSystem
|
|||
|
||||
public void Dump(string directory, CueBinPrefs prefs, ProgressReport progress)
|
||||
{
|
||||
byte[] subQ_temp = new byte[12];
|
||||
progress.TaskCount = 2;
|
||||
|
||||
progress.Message = "Generating Cue";
|
||||
|
@ -545,37 +748,56 @@ namespace BizHawk.DiscSystem
|
|||
progress.TaskCurrent = 1;
|
||||
progress.ProgressEstimate = bins.Sum((bfd) => bfd.lbas.Count);
|
||||
progress.ProgressCurrent = 0;
|
||||
if(prefs.ReallyDumpBin)
|
||||
foreach (var bfd in bins)
|
||||
{
|
||||
int sectorSize = bfd.SectorSize;
|
||||
byte[] temp = new byte[2352];
|
||||
byte[] empty = new byte[2352];
|
||||
string trackBinFile = bfd.name;
|
||||
string trackBinPath = Path.Combine(directory, trackBinFile);
|
||||
using (FileStream fs = new FileStream(trackBinPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
for(int i=0;i<bfd.lbas.Count;i++)
|
||||
{
|
||||
if (progress.CancelSignal) return;
|
||||
if(!prefs.ReallyDumpBin) return;
|
||||
|
||||
progress.ProgressCurrent++;
|
||||
int lba = bfd.lbas[i];
|
||||
if (bfd.lba_zeros[i])
|
||||
foreach (var bfd in bins)
|
||||
{
|
||||
int sectorSize = bfd.SectorSize;
|
||||
byte[] temp = new byte[2352];
|
||||
byte[] empty = new byte[2352];
|
||||
string trackBinFile = bfd.name;
|
||||
string trackBinPath = Path.Combine(directory, trackBinFile);
|
||||
string subQPath = Path.ChangeExtension(trackBinPath, ".sub.q");
|
||||
FileStream fsSubQ = null;
|
||||
FileStream fs = new FileStream(trackBinPath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
try
|
||||
{
|
||||
if (prefs.DumpSubchannelQ)
|
||||
fsSubQ = new FileStream(subQPath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
|
||||
for (int i = 0; i < bfd.lbas.Count; i++)
|
||||
{
|
||||
if (progress.CancelSignal) return;
|
||||
|
||||
progress.ProgressCurrent++;
|
||||
int lba = bfd.lbas[i];
|
||||
if (bfd.lba_zeros[i])
|
||||
{
|
||||
fs.Write(empty, 0, sectorSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sectorSize == 2352)
|
||||
disc.ReadLBA_2352(lba, temp, 0);
|
||||
else if (sectorSize == 2048) disc.ReadLBA_2048(lba, temp, 0);
|
||||
else throw new InvalidOperationException();
|
||||
fs.Write(temp, 0, sectorSize);
|
||||
|
||||
//write subQ if necessary
|
||||
if (fsSubQ != null)
|
||||
{
|
||||
fs.Write(empty, 0, sectorSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sectorSize == 2352)
|
||||
disc.ReadLBA_2352(lba, temp, 0);
|
||||
else if (sectorSize == 2048) disc.ReadLBA_2048(lba, temp, 0);
|
||||
else throw new InvalidOperationException();
|
||||
fs.Write(temp, 0, sectorSize);
|
||||
disc.Sectors[lba].Read_SubchannelQ(subQ_temp, 0);
|
||||
fsSubQ.Write(subQ_temp, 0, 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
fs.Dispose();
|
||||
if (fsSubQ != null) fsSubQ.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,88 @@ namespace BizHawk.DiscSystem
|
|||
|
||||
public class DiscTOC
|
||||
{
|
||||
/// <summary>
|
||||
/// Sessions contained in the disc. Right now support for anything other than 1 session is totally not working
|
||||
/// </summary>
|
||||
public List<Session> Sessions = new List<Session>();
|
||||
|
||||
/// <summary>
|
||||
/// this is an unfinished concept of "TOC Points" which is sometimes more convenient way for organizing the disc contents
|
||||
/// </summary>
|
||||
public List<TOCPoint> Points = new List<TOCPoint>();
|
||||
|
||||
/// <summary>
|
||||
/// Todo - comment about what this actually means
|
||||
/// </summary>
|
||||
public int length_lba;
|
||||
|
||||
/// <summary>
|
||||
/// todo - comment about what this actually means
|
||||
/// </summary>
|
||||
public Timestamp FriendlyLength { get { return new Timestamp(length_lba); } }
|
||||
|
||||
/// <summary>
|
||||
/// seeks the point immediately before (or equal to) this LBA
|
||||
/// </summary>
|
||||
public TOCPoint SeekPoint(int lba)
|
||||
{
|
||||
for(int i=0;i<Points.Count;i++)
|
||||
{
|
||||
TOCPoint tp = Points[i];
|
||||
if (tp.LBA > lba)
|
||||
return Points[i - 1];
|
||||
}
|
||||
return Points[Points.Count - 1];
|
||||
}
|
||||
|
||||
public long BinarySize
|
||||
{
|
||||
get { return length_lba * 2352; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class TOCPoint
|
||||
{
|
||||
public int Num;
|
||||
public int LBA, TrackNum, IndexNum;
|
||||
public Track Track;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generates the Points list from the current TOC
|
||||
/// </summary>
|
||||
public void GeneratePoints()
|
||||
{
|
||||
int num = 0;
|
||||
Points.Clear();
|
||||
foreach (var ses in Sessions)
|
||||
{
|
||||
foreach (var track in ses.Tracks)
|
||||
foreach (var index in track.Indexes)
|
||||
{
|
||||
var tp = new TOCPoint();
|
||||
tp.Num = num++;
|
||||
tp.LBA = index.lba;
|
||||
tp.TrackNum = track.num;
|
||||
tp.IndexNum = index.num;
|
||||
tp.Track = track;
|
||||
Points.Add(tp);
|
||||
}
|
||||
|
||||
var tpLeadout = new TOCPoint();
|
||||
var lastTrack = ses.Tracks[ses.Tracks.Count - 1];
|
||||
tpLeadout.Num = num++;
|
||||
tpLeadout.LBA = lastTrack.Indexes[1].lba + lastTrack.length_lba;
|
||||
tpLeadout.IndexNum = 0;
|
||||
tpLeadout.TrackNum = 100;
|
||||
tpLeadout.Track = null; //no leadout track.. now... or ever?
|
||||
Points.Add(tpLeadout);
|
||||
}
|
||||
}
|
||||
|
||||
public class Session
|
||||
{
|
||||
public int num;
|
||||
|
@ -16,7 +98,7 @@ namespace BizHawk.DiscSystem
|
|||
|
||||
//the length of the session (should be the sum of all track lengths)
|
||||
public int length_lba;
|
||||
public Cue.CueTimestamp FriendlyLength { get { return new Cue.CueTimestamp(length_lba); } }
|
||||
public Timestamp FriendlyLength { get { return new Timestamp(length_lba); } }
|
||||
}
|
||||
|
||||
public class Track
|
||||
|
@ -31,7 +113,7 @@ namespace BizHawk.DiscSystem
|
|||
/// the time before track 1 index 1 is the lead-in and isn't accounted for in any track...
|
||||
/// </summary>
|
||||
public int length_lba;
|
||||
public Cue.CueTimestamp FriendlyLength { get { return new Cue.CueTimestamp(length_lba); } }
|
||||
public Timestamp FriendlyLength { get { return new Timestamp(length_lba); } }
|
||||
}
|
||||
|
||||
public class Index
|
||||
|
@ -43,7 +125,7 @@ namespace BizHawk.DiscSystem
|
|||
//HEY! This is commented out because it is a bad idea.
|
||||
//The length of a section is almost useless, and if you want it, you are probably making an error.
|
||||
//public int length_lba;
|
||||
//public Cue.CueTimestamp FriendlyLength { get { return new Cue.CueTimestamp(length_lba); } }
|
||||
//public Cue.Timestamp FriendlyLength { get { return new Cue.Timestamp(length_lba); } }
|
||||
}
|
||||
|
||||
public string GenerateCUE_OneBin(CueBinPrefs prefs)
|
||||
|
@ -53,7 +135,6 @@ namespace BizHawk.DiscSystem
|
|||
//this generates a single-file cue!!!!!!! dont expect it to generate bin-per-track!
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
bool leadin = true;
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (!prefs.SingleSession)
|
||||
|
@ -66,6 +147,7 @@ namespace BizHawk.DiscSystem
|
|||
foreach (var track in session.Tracks)
|
||||
{
|
||||
ETrackType trackType = track.TrackType;
|
||||
|
||||
//mutate track type according to our principle of canonicalization
|
||||
if (trackType == ETrackType.Mode1_2048 && prefs.DumpECM)
|
||||
trackType = ETrackType.Mode1_2352;
|
||||
|
@ -74,26 +156,17 @@ namespace BizHawk.DiscSystem
|
|||
else sb.AppendFormat(" TRACK {0:D2} {1}\n", track.num, Cue.TrackTypeStringForTrackType(trackType));
|
||||
foreach (var index in track.Indexes)
|
||||
{
|
||||
if (prefs.OmitRedundantIndex0 && index.num == 0 && index.lba == track.Indexes[1].lba)
|
||||
//cue+bin has an implicit 150 sector pregap which neither the cue nor the bin has any awareness of
|
||||
//except for the baked-in sector addressing.
|
||||
//but, if there is an extra-long pregap, we want to reflect it this way
|
||||
int lba = index.lba - 150;
|
||||
if (lba <= 0 && index.num == 0 && track.num == 1)
|
||||
{
|
||||
//dont emit index 0 when it is the same as index 1. it confuses daemon tools.
|
||||
//(make this an option?)
|
||||
}
|
||||
else if (leadin)
|
||||
{
|
||||
//don't generate the first index, it is illogical
|
||||
}
|
||||
else
|
||||
{
|
||||
//subtract leadin. CUE format seems to always imply this exact amount.
|
||||
//however, physical discs could possibly have a slightly longer lead-in.
|
||||
//this could be done with a pregap on track 1 perhaps..
|
||||
//would we handle it here correctly? i think so
|
||||
int lba = index.lba - 150;
|
||||
sb.AppendFormat(" INDEX {0:D2} {1}\n", index.num, new Cue.CueTimestamp(lba).Value);
|
||||
sb.AppendFormat(" INDEX {0:D2} {1}\n", index.num, new Timestamp(lba).Value);
|
||||
}
|
||||
|
||||
leadin = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,14 +174,6 @@ namespace BizHawk.DiscSystem
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
public List<Session> Sessions = new List<Session>();
|
||||
public int length_lba;
|
||||
public Cue.CueTimestamp FriendlyLength { get { return new Cue.CueTimestamp(length_lba); } }
|
||||
|
||||
public long BinarySize
|
||||
{
|
||||
get { return length_lba*2352; }
|
||||
}
|
||||
|
||||
public void AnalyzeLengthsFromIndexLengths()
|
||||
{
|
||||
|
|
|
@ -9,111 +9,66 @@ using System.Collections.Generic;
|
|||
|
||||
namespace BizHawk.DiscSystem
|
||||
{
|
||||
public class SubcodeStream
|
||||
//this has been checked against mednafen's and seems to match
|
||||
//there are a few dozen different ways to do CRC16-CCITT
|
||||
//this table is backwards or something. at any rate its tailored to the needs of the Q subchannel
|
||||
internal static class CRC16_CCITT
|
||||
{
|
||||
Stream source;
|
||||
long offset;
|
||||
public SubcodeStream(Stream source, long offset)
|
||||
{
|
||||
this.source = source;
|
||||
this.offset = offset;
|
||||
cached_decoder = new SubcodePacketDecoder(cached_buffer, 0);
|
||||
}
|
||||
int channel = 0;
|
||||
public char Channel
|
||||
{
|
||||
get { return (char)((7 - channel) + 'p'); }
|
||||
set { channel = SubcodePacketDecoder.NormalizeChannel(value); }
|
||||
}
|
||||
private static ushort[] table = new ushort[256];
|
||||
|
||||
long Position { get; set; }
|
||||
|
||||
int cached_addr = -1;
|
||||
SubcodePacketDecoder cached_decoder = null;
|
||||
byte[] cached_buffer = new byte[24];
|
||||
public int ReadByte()
|
||||
static CRC16_CCITT()
|
||||
{
|
||||
int subcode_addr = (int)Position;
|
||||
int subcode_byte = subcode_addr & 1;
|
||||
subcode_addr /= 2;
|
||||
subcode_addr *= 24;
|
||||
if (subcode_addr != cached_addr)
|
||||
ushort value;
|
||||
ushort temp;
|
||||
for (ushort i = 0; i < 256; ++i)
|
||||
{
|
||||
cached_decoder.Reset();
|
||||
source.Position = offset + subcode_addr;
|
||||
if (source.Read(cached_buffer, 0, 24) != 24)
|
||||
return -1;
|
||||
cached_addr = subcode_addr;
|
||||
value = 0;
|
||||
temp = (ushort)(i << 8);
|
||||
for (byte j = 0; j < 8; ++j)
|
||||
{
|
||||
if (((value ^ temp) & 0x8000) != 0)
|
||||
value = (ushort)((value << 1) ^ 0x1021);
|
||||
else
|
||||
value <<= 1;
|
||||
temp <<= 1;
|
||||
}
|
||||
table[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static ushort Calculate(byte[] data, int offset, int length)
|
||||
{
|
||||
ushort Result = 0;
|
||||
for(int i=0;i<length;i++)
|
||||
{
|
||||
byte b = data[offset + i];
|
||||
int index = (b ^ ((Result >> 8) & 0xFF));
|
||||
Result = (ushort)((Result << 8) ^ table[index]);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class SubcodeDataDecoder
|
||||
{
|
||||
public static void Unpack_Q(byte[] output, int out_ofs, byte[] input, int in_ofs)
|
||||
{
|
||||
for (int i = 0; i < 12; i++)
|
||||
output[out_ofs + i] = 0;
|
||||
for (int i = 0; i < 96; i++)
|
||||
{
|
||||
int bytenum = i >> 3;
|
||||
int bitnum = i & 7;
|
||||
bitnum = 7 - bitnum;
|
||||
int bitval = (byte)((input[in_ofs + i] >> 6) & 1);
|
||||
bitval <<= bitnum;
|
||||
output[out_ofs + bytenum] |= (byte)bitval;
|
||||
}
|
||||
Position = Position + 1;
|
||||
ushort val = cached_decoder.ReadChannel(channel);
|
||||
val >>= (8 * subcode_byte);
|
||||
val &= 0xFF;
|
||||
return (int)val;
|
||||
}
|
||||
}
|
||||
|
||||
class SubcodePacketDecoder
|
||||
{
|
||||
internal static int NormalizeChannel(char channel)
|
||||
{
|
||||
int channum;
|
||||
if (channel >= 'P' && channel <= 'W') channum = channel - 'P';
|
||||
else if (channel >= 'p' && channel <= 'w') channum = (channel - 'p');
|
||||
else throw new InvalidOperationException("invalid channel specified");
|
||||
channum = 7 - channum;
|
||||
return channum;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
cached = false;
|
||||
}
|
||||
byte[] buffer;
|
||||
int offset;
|
||||
public SubcodePacketDecoder(byte[] buffer, int offset)
|
||||
{
|
||||
this.buffer = buffer;
|
||||
this.offset = offset;
|
||||
}
|
||||
byte command { get { return buffer[offset + 0]; } set { buffer[offset + 0] = value; } }
|
||||
byte instruction { get { return buffer[offset + 1]; } set { buffer[offset + 1] = value; } }
|
||||
|
||||
public int parityQ_offset { get { return offset + 2; } }
|
||||
public int data_offset { get { return offset + 4; } }
|
||||
public int parityP_offset { get { return offset + 20; } }
|
||||
|
||||
public byte ReadData(int index)
|
||||
{
|
||||
return buffer[data_offset + index];
|
||||
}
|
||||
|
||||
public ushort ReadChannel(char channel)
|
||||
{
|
||||
return ReadChannel(NormalizeChannel(channel));
|
||||
}
|
||||
|
||||
bool cached;
|
||||
ushort[] decoded_channels = new ushort[8];
|
||||
public ushort ReadChannel(int channum)
|
||||
{
|
||||
if (!cached)
|
||||
{
|
||||
decoded_channels = new ushort[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
decoded_channels[i] = DecodeChannel(i);
|
||||
}
|
||||
return decoded_channels[channum];
|
||||
}
|
||||
|
||||
ushort DecodeChannel(int channum)
|
||||
{
|
||||
int ret = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
ret |= ((ReadData(i) >> channum) & 1) << i;
|
||||
}
|
||||
return (ushort)ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ namespace BizHawk.Emulation.Sound
|
|||
if (track < 1 || track > Disc.TOC.Sessions[0].Tracks.Count)
|
||||
return;
|
||||
|
||||
//note for vecna: you may find that the new "Point" and "SeekPoint" concept in the TOC is more useful than this kind of logic. just something to think about
|
||||
StartLBA = Disc.TOC.Sessions[0].Tracks[track - 1].Indexes[1].lba;
|
||||
EndLBA = StartLBA + Disc.TOC.Sessions[0].Tracks[track - 1].length_lba;
|
||||
PlayingTrack = track;
|
||||
|
@ -65,6 +66,7 @@ namespace BizHawk.Emulation.Sound
|
|||
var tracks = Disc.TOC.Sessions[0].Tracks;
|
||||
bool foundTrack = false;
|
||||
|
||||
//note for vecna: you may find that the new "Point" and "SeekPoint" concept in the TOC is more useful than this kind of logic. just something to think about
|
||||
for (track = 0; track < tracks.Count; track++)
|
||||
{
|
||||
int trackStart = tracks[track].Indexes[0].lba;
|
||||
|
|
|
@ -271,7 +271,7 @@ namespace BizHawk.MultiClient
|
|||
|
||||
private void BrowseFolder(TextBox box, string Name, string System)
|
||||
{
|
||||
FolderBrowserDialog f = new FolderBrowserDialog();
|
||||
FolderBrowserEx f = new FolderBrowserEx();
|
||||
f.Description = "Set the directory for " + Name;
|
||||
f.SelectedPath = PathManager.MakeAbsolutePath(box.Text, System);
|
||||
DialogResult result = f.ShowDialog();
|
||||
|
|
|
@ -85,6 +85,9 @@
|
|||
<Compile Include="7z\SevenZipExtractorAsynchronous.cs" />
|
||||
<Compile Include="7z\SevenZipSfx.cs" />
|
||||
<Compile Include="7z\StreamWrappers.cs" />
|
||||
<Compile Include="FolderBrowserDialogEx.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="InputConfigBase.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using System.ComponentModel;
|
||||
using System.Security.Permissions;
|
||||
|
||||
|
||||
namespace BizHawk
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Component wrapping access to the Browse For Folder common dialog box.
|
||||
/// Call the ShowDialog() method to bring the dialog box up.
|
||||
/// </summary>
|
||||
public sealed class FolderBrowserEx : Component
|
||||
{
|
||||
private static readonly int MAX_PATH = 260;
|
||||
|
||||
// Root node of the tree view.
|
||||
private FolderID startLocation = FolderID.Desktop;
|
||||
|
||||
// Browse info options.
|
||||
private int publicOptions = (int) Win32API.Shell32.BffStyles.RestrictToFilesystem |
|
||||
(int) Win32API.Shell32.BffStyles.RestrictToDomain;
|
||||
|
||||
private int privateOptions = (int)(Win32API.Shell32.BffStyles.NewDialogStyle | Win32API.Shell32.BffStyles.ShowTextBox);
|
||||
|
||||
// Description text to show.
|
||||
public string Description = "Please select a folder below:";
|
||||
|
||||
// Folder chosen by the user.
|
||||
private string directoryPath = String.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Enum of CSIDLs identifying standard shell folders.
|
||||
/// </summary>
|
||||
public enum FolderID
|
||||
{
|
||||
Desktop = 0x0000,
|
||||
Printers = 0x0004,
|
||||
MyDocuments = 0x0005,
|
||||
Favorites = 0x0006,
|
||||
Recent = 0x0008,
|
||||
SendTo = 0x0009,
|
||||
StartMenu = 0x000b,
|
||||
MyComputer = 0x0011,
|
||||
NetworkNeighborhood = 0x0012,
|
||||
Templates = 0x0015,
|
||||
MyPictures = 0x0027,
|
||||
NetAndDialUpConnections = 0x0031,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function that returns the IMalloc interface used by the shell.
|
||||
/// </summary>
|
||||
private static Win32API.IMalloc GetSHMalloc()
|
||||
{
|
||||
Win32API.IMalloc malloc;
|
||||
Win32API.Shell32.SHGetMalloc(out malloc);
|
||||
return malloc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the folder browser dialog box.
|
||||
/// </summary>
|
||||
public DialogResult ShowDialog()
|
||||
{
|
||||
return ShowDialog(null);
|
||||
}
|
||||
|
||||
|
||||
private int callback(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData)
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
IntPtr str = Marshal.StringToHGlobalUni(SelectedPath);
|
||||
Win32.SendMessage(hwnd, (0x400 + 103), 1, str.ToInt32());
|
||||
Marshal.FreeHGlobal(str);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Shows the folder browser dialog box with the specified owner window.
|
||||
/// </summary>
|
||||
public DialogResult ShowDialog(IWin32Window owner)
|
||||
{
|
||||
IntPtr pidlRoot = IntPtr.Zero;
|
||||
|
||||
// Get/find an owner HWND for this dialog.
|
||||
IntPtr hWndOwner;
|
||||
|
||||
if (owner != null)
|
||||
{
|
||||
hWndOwner = owner.Handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
hWndOwner = Win32API.GetActiveWindow();
|
||||
}
|
||||
|
||||
// Get the IDL for the specific startLocation.
|
||||
Win32API.Shell32.SHGetSpecialFolderLocation(hWndOwner, (int) startLocation, out pidlRoot);
|
||||
|
||||
if (pidlRoot == IntPtr.Zero)
|
||||
{
|
||||
return DialogResult.Cancel;
|
||||
}
|
||||
|
||||
int mergedOptions = (int) publicOptions | (int) privateOptions;
|
||||
|
||||
if ((mergedOptions & (int) Win32API.Shell32.BffStyles.NewDialogStyle) != 0)
|
||||
{
|
||||
if (System.Threading.ApartmentState.MTA == Application.OleRequired())
|
||||
mergedOptions = mergedOptions & (~(int) Win32API.Shell32.BffStyles.NewDialogStyle);
|
||||
}
|
||||
|
||||
IntPtr pidlRet = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
// Construct a BROWSEINFO.
|
||||
Win32API.Shell32.BROWSEINFO bi = new Win32API.Shell32.BROWSEINFO();
|
||||
IntPtr buffer = Marshal.AllocHGlobal(MAX_PATH);
|
||||
|
||||
bi.pidlRoot = pidlRoot;
|
||||
bi.hwndOwner = hWndOwner;
|
||||
bi.pszDisplayName = buffer;
|
||||
bi.lpszTitle = Description;
|
||||
bi.ulFlags = mergedOptions;
|
||||
bi.lpfn = new Win32API.Shell32.BFFCALLBACK(callback);
|
||||
// The rest of the fields are initialized to zero by the constructor.
|
||||
// bi.lParam = IntPtr.Zero; bi.iImage = 0;
|
||||
|
||||
// Show the dialog.
|
||||
pidlRet = Win32API.Shell32.SHBrowseForFolder(ref bi);
|
||||
|
||||
// Free the buffer you've allocated on the global heap.
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
|
||||
if (pidlRet == IntPtr.Zero)
|
||||
{
|
||||
// User clicked Cancel.
|
||||
return DialogResult.Cancel;
|
||||
}
|
||||
|
||||
// Then retrieve the path from the IDList.
|
||||
StringBuilder sb = new StringBuilder(MAX_PATH);
|
||||
if (0 == Win32API.Shell32.SHGetPathFromIDList(pidlRet, sb))
|
||||
{
|
||||
return DialogResult.Cancel;
|
||||
}
|
||||
|
||||
// Convert to a string.
|
||||
directoryPath = sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Win32API.IMalloc malloc = GetSHMalloc();
|
||||
malloc.Free(pidlRoot);
|
||||
|
||||
if (pidlRet != IntPtr.Zero)
|
||||
{
|
||||
malloc.Free(pidlRet);
|
||||
}
|
||||
}
|
||||
|
||||
return DialogResult.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function used to set and reset bits in the publicOptions bitfield.
|
||||
/// </summary>
|
||||
private void SetOptionField(int mask, bool turnOn)
|
||||
{
|
||||
if (turnOn)
|
||||
publicOptions |= mask;
|
||||
else
|
||||
publicOptions &= ~mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only return file system directories. If the user selects folders
|
||||
/// that are not part of the file system, the OK button is unavailable.
|
||||
/// </summary>
|
||||
[Category("Navigation")]
|
||||
[Description("Only return file system directories. If the user selects folders " +
|
||||
"that are not part of the file system, the OK button is unavailable.")]
|
||||
[DefaultValue(true)]
|
||||
public bool OnlyFilesystem
|
||||
{
|
||||
get { return (publicOptions & (int) Win32API.Shell32.BffStyles.RestrictToFilesystem) != 0; }
|
||||
set { SetOptionField((int) Win32API.Shell32.BffStyles.RestrictToFilesystem, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Location of the root folder from which to start browsing. Only the specified
|
||||
/// folder and any folders beneath it in the namespace hierarchy appear
|
||||
/// in the dialog box.
|
||||
/// </summary>
|
||||
[Category("Navigation")]
|
||||
[Description("Location of the root folder from which to start browsing. Only the specified " +
|
||||
"folder and any folders beneath it in the namespace hierarchy appear " +
|
||||
"in the dialog box.")]
|
||||
[DefaultValue(typeof (FolderID), "0")]
|
||||
public FolderID StartLocation
|
||||
{
|
||||
get { return startLocation; }
|
||||
set
|
||||
{
|
||||
new UIPermission(UIPermissionWindow.AllWindows).Demand();
|
||||
startLocation = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedPath;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
internal class Win32API
|
||||
{
|
||||
// C# representation of the IMalloc interface.
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
|
||||
Guid("00000002-0000-0000-C000-000000000046")]
|
||||
public interface IMalloc
|
||||
{
|
||||
[PreserveSig]
|
||||
IntPtr Alloc([In] int cb);
|
||||
|
||||
[PreserveSig]
|
||||
IntPtr Realloc([In] IntPtr pv, [In] int cb);
|
||||
|
||||
[PreserveSig]
|
||||
void Free([In] IntPtr pv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetSize([In] IntPtr pv);
|
||||
|
||||
[PreserveSig]
|
||||
int DidAlloc(IntPtr pv);
|
||||
|
||||
[PreserveSig]
|
||||
void HeapMinimize();
|
||||
}
|
||||
|
||||
[DllImport("User32.DLL")]
|
||||
public static extern IntPtr GetActiveWindow();
|
||||
|
||||
public class Shell32
|
||||
{
|
||||
// Styles used in the BROWSEINFO.ulFlags field.
|
||||
[Flags]
|
||||
public enum BffStyles
|
||||
{
|
||||
RestrictToFilesystem = 0x0001, // BIF_RETURNONLYFSDIRS
|
||||
RestrictToDomain = 0x0002, // BIF_DONTGOBELOWDOMAIN
|
||||
RestrictToSubfolders = 0x0008, // BIF_RETURNFSANCESTORS
|
||||
ShowTextBox = 0x0010, // BIF_EDITBOX
|
||||
ValidateSelection = 0x0020, // BIF_VALIDATE
|
||||
NewDialogStyle = 0x0040, // BIF_NEWDIALOGSTYLE
|
||||
BrowseForComputer = 0x1000, // BIF_BROWSEFORCOMPUTER
|
||||
BrowseForPrinter = 0x2000, // BIF_BROWSEFORPRINTER
|
||||
BrowseForEverything = 0x4000, // BIF_BROWSEINCLUDEFILES
|
||||
}
|
||||
|
||||
// Delegate type used in BROWSEINFO.lpfn field.
|
||||
public delegate int BFFCALLBACK(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
public struct BROWSEINFO
|
||||
{
|
||||
public IntPtr hwndOwner;
|
||||
public IntPtr pidlRoot;
|
||||
public IntPtr pszDisplayName;
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public string lpszTitle;
|
||||
public int ulFlags;
|
||||
[MarshalAs(UnmanagedType.FunctionPtr)] public BFFCALLBACK lpfn;
|
||||
public IntPtr lParam;
|
||||
public int iImage;
|
||||
}
|
||||
|
||||
[DllImport("Shell32.DLL")]
|
||||
public static extern int SHGetMalloc(out IMalloc ppMalloc);
|
||||
|
||||
[DllImport("Shell32.DLL")]
|
||||
public static extern int SHGetSpecialFolderLocation(
|
||||
IntPtr hwndOwner, int nFolder, out IntPtr ppidl);
|
||||
|
||||
[DllImport("Shell32.DLL")]
|
||||
public static extern int SHGetPathFromIDList(
|
||||
IntPtr pidl, StringBuilder Path);
|
||||
|
||||
[DllImport("Shell32.DLL", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SHBrowseForFolder(ref BROWSEINFO bi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -244,11 +244,6 @@ namespace BizHawk
|
|||
LVIS_STATEIMAGEMASK = 0xF000,
|
||||
}
|
||||
|
||||
internal struct WindowsFunction {
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -425,7 +420,7 @@ namespace BizHawk
|
|||
Marshal.StructureToPtr(stateItem, ptrItem, true);
|
||||
|
||||
// Send the message to the control window.
|
||||
int result = WindowsFunction.SendMessage(
|
||||
int result = Win32.SendMessage(
|
||||
this.Handle,
|
||||
(int)ListViewMessages.LVM_SETITEMSTATE,
|
||||
index,
|
||||
|
@ -444,7 +439,7 @@ namespace BizHawk
|
|||
|
||||
private void SetVirtualItemCount() {
|
||||
int result;
|
||||
result = WindowsFunction.SendMessage(
|
||||
result = Win32.SendMessage(
|
||||
this.Handle,
|
||||
(int)ListViewMessages.LVM_SETITEMCOUNT,
|
||||
itemCount,
|
||||
|
@ -612,13 +607,13 @@ namespace BizHawk
|
|||
lvhti.pt.x = x;
|
||||
lvhti.pt.y = y;
|
||||
Marshal.StructureToPtr(lvhti, ptrlvhti, true);
|
||||
int z = WindowsFunction.SendMessage(this.Handle, (int)ListViewMessages.LVM_HITTEST, 0, ptrlvhti.ToInt32());
|
||||
int z = Win32.SendMessage(this.Handle, (int)ListViewMessages.LVM_HITTEST, 0, ptrlvhti.ToInt32());
|
||||
Marshal.PtrToStructure(ptrlvhti, lvhti);
|
||||
return z;
|
||||
}
|
||||
|
||||
public void ensureVisible(int index) {
|
||||
WindowsFunction.SendMessage(Handle, (int)ListViewMessages.LVM_ENSUREVISIBLE, index, 1);
|
||||
Win32.SendMessage(Handle, (int)ListViewMessages.LVM_ENSUREVISIBLE, index, 1);
|
||||
}
|
||||
|
||||
public void ensureVisible() {
|
||||
|
|
|
@ -430,6 +430,10 @@ namespace BizHawk
|
|||
|
||||
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -86,21 +86,31 @@ namespace BizHawk
|
|||
//Console.WriteLine(disc.ReadTOC().DebugPrint());
|
||||
//disc.DumpBin_2352("d:\\test.2352");
|
||||
|
||||
////test reading the subcode data. unfortunately we don't have lead-in subcode so we have no TOC
|
||||
//using (FileStream fs = File.OpenRead("c:\\bof4.sub"))
|
||||
//test reading the subcode data. unfortunately we don't have lead-in subcode so we have no TOC
|
||||
//using (FileStream fs = File.OpenRead(@"D:\programs\cdrdao\awakening.bin"))
|
||||
//{
|
||||
// Disc.SubcodeStream stream = new Disc.SubcodeStream(fs, 0);
|
||||
// stream.Channel = 'q';
|
||||
// using (FileStream fsOut = File.OpenWrite("c:\\bof4.sub.q"))
|
||||
// using (FileStream fsOut = File.OpenWrite(@"D:\programs\cdrdao\awakening.sub.q"))
|
||||
// //using (FileStream fsOut = File.OpenWrite(@"D:\programs\cdrdao\data.sub.q"))
|
||||
// {
|
||||
// for (; ; )
|
||||
// int numSectors = (int)fs.Length / (2352 + 96);
|
||||
// for (int i = 0; i < numSectors; i++)
|
||||
// {
|
||||
// int ret = stream.ReadByte();
|
||||
// if (ret == -1) break;
|
||||
// fsOut.WriteByte((byte)ret);
|
||||
// fs.Position = i * (2352 + 96) + 2352;
|
||||
// byte[] tempout = new byte[12];
|
||||
// byte[] tempin = new byte[96];
|
||||
// fs.Read(tempin, 0, 96);
|
||||
// DiscSystem.SubcodeDataDecoder.Unpack_Q(tempout, 0, tempin, 0);
|
||||
// fsOut.Write(tempout, 0, 12);
|
||||
// }
|
||||
|
||||
// //for (; ; )
|
||||
// //{
|
||||
// // int ret = stream.ReadByte();
|
||||
// // if (ret == -1) break;
|
||||
// // fsOut.WriteByte((byte)ret);
|
||||
// //}
|
||||
// }
|
||||
//}
|
||||
//} return;
|
||||
|
||||
//DiscSystem.Disc disc = DiscSystem.Disc.FromCuePath(@"D:\discs\Bomberman_'94_Taikenban_(SCD)(JPN)_-_wav'd\Bomberman '94 Taikenban (SCD)(JPN)_hawked.cue");
|
||||
//DiscSystem.Disc disc = DiscSystem.Disc.FromCuePath(@"D:\discs\Bomberman_'94_Taikenban_(SCD)(JPN)_-_wav'd\Bomberman '94 Taikenban (SCD)(JPN).cue");
|
||||
|
@ -114,16 +124,17 @@ namespace BizHawk
|
|||
//var cueBin = disc.DumpCueBin("Bomberman '94 Taikenban (SCD)(JPN)_hawked", prefs);
|
||||
//cueBin.Dump(@"D:\discs\Bomberman_'94_Taikenban_(SCD)(JPN)_-_wav'd", prefs);
|
||||
|
||||
DiscSystem.Disc disc = DiscSystem.Disc.FromCuePath(@"D:\discs\Angels II - Holy Night (J)[Prototype]\Angels II - Holy Night (J)[Prototype].cue");
|
||||
DiscSystem.Disc disc = DiscSystem.Disc.FromCuePath(@"D:\programs\cdrdao\eac-ripped\Awakening.cue");
|
||||
var prefs = new DiscSystem.CueBinPrefs();
|
||||
prefs.AnnotateCue = false;
|
||||
prefs.OneBlobPerTrack = false;
|
||||
prefs.ReallyDumpBin = true;
|
||||
prefs.OmitRedundantIndex0 = true;
|
||||
prefs.DumpSubchannelQ = true;
|
||||
//prefs.OmitRedundantIndex0 = true;
|
||||
prefs.SingleSession = true;
|
||||
//var cueBin = disc.DumpCueBin("Bomberman '94 Taikenban (SCD)(JPN)_hawked_hawked", prefs);
|
||||
var cueBin = disc.DumpCueBin("test", prefs);
|
||||
cueBin.Dump(@"D:\discs\Angels II - Holy Night (J)[Prototype]", prefs);
|
||||
cueBin.Dump(@"D:\programs\cdrdao\eac-ripped", prefs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ namespace BizHawk
|
|||
prefs.AnnotateCue = checkCueProp_Annotations.Checked;
|
||||
prefs.OneBlobPerTrack = checkCueProp_OneBlobPerTrack.Checked;
|
||||
prefs.ReallyDumpBin = false;
|
||||
prefs.OmitRedundantIndex0 = checkCueProp_OmitRedundantIndex0.Checked;
|
||||
//prefs.OmitRedundantIndex0 = checkCueProp_OmitRedundantIndex0.Checked;
|
||||
prefs.SingleSession = true;
|
||||
return prefs;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue