2011-05-08 09:07:46 +00:00
using System ;
2011-08-07 03:21:03 +00:00
using System.Linq ;
2011-05-08 09:07:46 +00:00
using System.Text ;
using System.IO ;
using System.Collections.Generic ;
//http://www.pctechguide.com/iso-9660-data-format-for-cds-cd-roms-cd-rs-and-cd-rws
//http://linux.die.net/man/1/cue2toc
2011-08-07 03:21:03 +00:00
//http://cdemu.sourceforge.net/project.php#sf
2011-05-08 09:07:46 +00:00
//apparently cdrdao is the ultimate linux tool for doing this stuff but it doesnt support DAO96 (or other DAO modes) that would be necessary to extract P-Q subchannels
//(cdrdao only supports R-W)
2011-06-20 09:09:21 +00:00
//here is a featureset list of windows cd burning programs (useful for cuesheet compatibility info)
//http://www.dcsoft.com/cue_mastering_progs.htm
2011-05-08 09:07:46 +00:00
//good
//http://linux-sxs.org/bedtime/cdapi.html
//http://en.wikipedia.org/wiki/Track_%28CD%29
//http://docs.google.com/viewer?a=v&q=cache:imNKye05zIEJ:www.13thmonkey.org/documentation/SCSI/mmc-r10a.pdf+q+subchannel+TOC+format&hl=en&gl=us&pid=bl&srcid=ADGEEShtYqlluBX2lgxTL3pVsXwk6lKMIqSmyuUCX4RJ3DntaNq5vI2pCvtkyze-fumj7vvrmap6g1kOg5uAVC0IxwU_MRhC5FB0c_PQ2BlZQXDD7P3GeNaAjDeomelKaIODrhwOoFNb&sig=AHIEtbRXljAcFjeBn3rMb6tauHWjSNMYrw
//r:\consoles\~docs\yellowbook
//http://digitalx.org/cue-sheet/examples/
/ /
//"qemu cdrom emulator"
//http://www.koders.com/c/fid7171440DEC7C18B932715D671DEE03743111A95A.aspx
//less good
//http://www.cyberciti.biz/faq/getting-volume-information-from-cds-iso-images/
//http://www.cims.nyu.edu/cgi-systems/man.cgi?section=7I&topic=cdio
//ideas:
/ *
* do some stuff asynchronously . for example , decoding mp3 sectors .
* keep a list of ' blobs ' ( giant bins or decoded wavs likely ) which can reference the disk
* keep a list of sectors and the blob / offset from which they pull - - also whether the sector is available
* if it is not available and something requests it then it will have to block while that sector gets generated
* perhaps the blobs know how to resolve themselves and the requested sector can be immediately resolved ( priority boost )
* mp3 blobs should be hashed and dropped in % TEMP % as a wav decode
* /
//here is an MIT licensed C mp3 decoder
//http://core.fluendo.com/gstreamer/src/gst-fluendo-mp3/
/ * information on saturn TOC and session data structures is on pdf page 58 of System Library User ' s Manual ;
* as seen in yabause , there are 1000 u32s in this format :
* Ctrl [ 4 bit ] Adr [ 4 bit ] StartFrameAddressFAD [ 24 bit ] ( nonexisting tracks are 0xFFFFFFFF )
* Followed by Fist Track Information , Last Track Information . .
* Ctrl [ 4 bit ] Adr [ 4 bit ] FirstTrackNumber / LastTrackNumber [ 8 bit ] and then some stuff I dont understand
* . . and Read Out Information :
* Ctrl [ 4 bit ] Adr [ 4 bit ] ReadOutStartFrameAddress [ 24 bit ]
*
* Also there is some stuff about FAD of sessions .
* This should be generated by the saturn core , but we need to make sure we pass down enough information to do it
* /
//2048 bytes packed into 2352:
//12 bytes sync(00 ff ff ff ff ff ff ff ff ff ff 00)
//3 bytes sector address (min+A0),sec,frac //does this correspond to ccd `point` field in the TOC entries?
2013-06-25 08:31:48 +00:00
//sector mode byte (0: silence; 1: 2048Byte mode (EDC,ECC,CIRC), 2: mode2 (could be 2336[vanilla mode2], 2048[xa mode2 form1], 2324[xa mode2 form2])
2011-05-08 09:07:46 +00:00
//cue sheets may use mode1_2048 (and the error coding needs to be regenerated to get accurate raw data) or mode1_2352 (the entire sector is present)
//audio is a different mode, seems to be just 2352 bytes with no sync, header or error correction. i guess the CIRC error correction is still there
2013-11-03 23:45:44 +00:00
namespace BizHawk.Emulation.DiscSystem
2011-05-08 09:07:46 +00:00
{
2011-09-04 06:15:41 +00:00
public partial class Disc : IDisposable
2011-05-08 09:07:46 +00:00
{
public interface ISector
{
2013-06-25 08:31:48 +00:00
/// <summary>
/// reads the entire sector, raw
/// </summary>
2013-08-08 19:31:08 +00:00
int Read_2352 ( byte [ ] buffer , int offset ) ;
2013-06-25 08:31:48 +00:00
/// <summary>
/// reads 2048 bytes of userdata.. precisely what this means isnt always 100% certain (for instance mode2 form 0 has 2336 bytes of userdata instead of 2048)..
/// ..but its certain enough for this to be useful
/// </summary>
int Read_2048 ( byte [ ] buffer , int offset ) ;
2011-05-08 09:07:46 +00:00
}
2012-11-17 22:16:09 +00:00
/// <summary>
2013-06-25 08:31:48 +00:00
/// Presently, an IBlob doesn't need to work multithreadedly. It's quite an onerous demand.
/// This should probably be managed by the Disc class somehow, or by the user making another Disc.
2012-11-17 22:16:09 +00:00
/// </summary>
2011-09-04 06:15:41 +00:00
public interface IBlob : IDisposable
2011-05-08 09:07:46 +00:00
{
2012-11-17 22:16:09 +00:00
/// <summary>
/// what a weird parameter order. normally the dest buffer would be first. weird.
/// </summary>
2013-06-25 08:31:48 +00:00
/// <param name="byte_pos">location in the blob to read from</param>
/// <param name="buffer">destination buffer for read data</param>
/// <param name="offset">offset into destination buffer</param>
/// <param name="count">amount to read</param>
2011-05-08 09:07:46 +00:00
int Read ( long byte_pos , byte [ ] buffer , int offset , int count ) ;
}
2013-11-15 00:49:19 +00:00
public sealed class Blob_ZeroPadAdapter : IBlob
2013-08-08 19:26:37 +00:00
{
public Blob_ZeroPadAdapter ( IBlob baseBlob , long padFrom , long padLen )
{
this . baseBlob = baseBlob ;
this . padFrom = padFrom ;
this . padLen = padLen ;
}
public int Read ( long byte_pos , byte [ ] buffer , int offset , int count )
{
throw new NotImplementedException ( "Blob_ZeroPadAdapter hasnt been tested yet! please report this!" ) ;
//something about this seems unnecessarily complex, but... i dunno.
2013-09-14 19:34:14 +00:00
/ *
2013-08-08 19:26:37 +00:00
//figure out how much remains until the zero-padding begins
long remain = byte_pos - padFrom ;
int todo ;
if ( remain < count )
todo = ( int ) remain ;
else todo = count ;
//read up until the zero-padding
int totalRead = 0 ;
int readed = baseBlob . Read ( byte_pos , buffer , offset , todo ) ;
totalRead + = readed ;
offset + = todo ;
//if we didnt read enough, we certainly shouldnt try to read any more
if ( readed < todo )
return readed ;
//if that was all we needed, then we're done
count - = todo ;
if ( count = = 0 )
return totalRead ;
//if we need more, it must come from zero-padding
remain = padLen ;
if ( remain < count )
todo = ( int ) remain ;
else todo = count ;
Array . Clear ( buffer , offset , todo ) ;
totalRead + = todo ;
return totalRead ;
2013-09-14 19:34:14 +00:00
* /
2013-08-08 19:26:37 +00:00
}
public void Dispose ( )
{
baseBlob . Dispose ( ) ;
}
2013-11-15 00:49:19 +00:00
private readonly IBlob baseBlob ;
private long padFrom ;
private long padLen ;
2013-08-08 19:26:37 +00:00
}
2013-11-15 00:49:19 +00:00
private class Blob_RawFile : IBlob
2011-05-08 09:07:46 +00:00
{
2011-06-20 09:09:21 +00:00
public string PhysicalPath {
2013-11-15 00:49:19 +00:00
get
{
return physicalPath ;
}
2011-06-20 09:09:21 +00:00
set
{
physicalPath = value ;
length = new FileInfo ( physicalPath ) . Length ;
}
}
string physicalPath ;
long length ;
2012-06-15 19:21:46 +00:00
public long Offset = 0 ;
2011-05-08 09:07:46 +00:00
2011-06-20 09:09:21 +00:00
BufferedStream fs ;
2011-05-08 09:07:46 +00:00
public void Dispose ( )
{
if ( fs ! = null )
{
fs . Dispose ( ) ;
fs = null ;
}
}
public int Read ( long byte_pos , byte [ ] buffer , int offset , int count )
{
2011-08-06 10:43:05 +00:00
//use quite a large buffer, because normally we will be reading these sequentially but in small chunks.
//this enhances performance considerably
2011-06-20 09:09:21 +00:00
const int buffersize = 2352 * 75 * 2 ;
2011-05-08 09:07:46 +00:00
if ( fs = = null )
2011-06-20 09:09:21 +00:00
fs = new BufferedStream ( new FileStream ( physicalPath , FileMode . Open , FileAccess . Read , FileShare . Read ) , buffersize ) ;
2011-05-08 09:07:46 +00:00
long target = byte_pos + Offset ;
if ( fs . Position ! = target )
fs . Position = target ;
return fs . Read ( buffer , offset , count ) ;
}
2011-06-20 09:09:21 +00:00
public long Length
{
get
{
return length ;
}
}
2011-05-08 09:07:46 +00:00
}
2013-06-25 08:31:48 +00:00
/// <summary>
/// this ISector is dumb and only knows how to drag chunks off a source blob
/// </summary>
2011-05-08 09:07:46 +00:00
class Sector_RawBlob : ISector
{
public IBlob Blob ;
public long Offset ;
2013-08-08 19:31:08 +00:00
public int Read_2352 ( byte [ ] buffer , int offset )
2011-05-08 09:07:46 +00:00
{
return Blob . Read ( Offset , buffer , offset , 2352 ) ;
}
2013-06-25 08:31:48 +00:00
public int Read_2048 ( byte [ ] buffer , int offset )
{
return Blob . Read ( Offset , buffer , offset , 2048 ) ;
}
2011-05-08 09:07:46 +00:00
}
2013-06-25 08:31:48 +00:00
/// <summary>
/// this ISector always returns zeroes
/// </summary>
2011-06-20 09:09:21 +00:00
class Sector_Zero : ISector
{
2013-08-08 19:31:08 +00:00
public int Read_2352 ( byte [ ] buffer , int offset )
2011-06-20 09:09:21 +00:00
{
2013-06-25 08:31:48 +00:00
Array . Clear ( buffer , 0 , 2352 ) ;
2011-06-20 09:09:21 +00:00
return 2352 ;
}
2013-06-25 08:31:48 +00:00
public int Read_2048 ( byte [ ] buffer , int offset )
{
Array . Clear ( buffer , 0 , 2048 ) ;
return 2048 ;
}
2011-06-20 09:09:21 +00:00
}
2013-08-08 19:26:37 +00:00
// ------ replaced by Blob_ZeroPadAdapter
///// <summary>
///// this ISector adapts another ISector by always returning zeroes
///// TODO I dont like the way this works. I think blobs should get adapted instead to zero-pad to a certain length.
///// </summary>
//class Sector_ZeroPad : ISector
//{
// public ISector BaseSector;
// public int BaseLength;
// public int Read(byte[] buffer, int offset)
// {
// return _Read(buffer, offset, 2352);
// }
// public int Read_2048(byte[] buffer, int offset)
// {
// return _Read(buffer, offset, 2352);
// }
// int _Read(byte[] buffer, int offset, int amount)
// {
// int read = BaseSector.Read(buffer, offset);
// if(read < BaseLength) return read;
// for (int i = BaseLength; i < amount; i++)
// buffer[offset + i] = 0;
// return amount;
// }
//}
2011-05-08 09:07:46 +00:00
2013-06-25 08:31:48 +00:00
abstract class Sector_Mode1_or_Mode2_2352 : ISector
2011-05-08 09:07:46 +00:00
{
public ISector BaseSector ;
2013-08-08 19:31:08 +00:00
public abstract int Read_2352 ( byte [ ] buffer , int offset ) ;
2013-06-25 08:31:48 +00:00
public abstract int Read_2048 ( byte [ ] buffer , int offset ) ;
}
/// <summary>
/// This ISector is a raw MODE1 sector
/// </summary>
class Sector_Mode1_2352 : Sector_Mode1_or_Mode2_2352
{
2013-08-08 19:31:08 +00:00
public override int Read_2352 ( byte [ ] buffer , int offset )
2013-06-25 08:31:48 +00:00
{
2013-08-08 19:31:08 +00:00
return BaseSector . Read_2352 ( buffer , offset ) ;
2013-06-25 08:31:48 +00:00
}
public override int Read_2048 ( byte [ ] buffer , int offset )
{
//to get 2048 bytes out of this sector type, start 16 bytes in
2013-08-08 19:31:08 +00:00
int ret = BaseSector . Read_2352 ( TempSector , 0 ) ;
2013-06-25 08:31:48 +00:00
Buffer . BlockCopy ( TempSector , 16 , buffer , offset , 2048 ) ;
System . Diagnostics . Debug . Assert ( buffer ! = TempSector ) ;
return 2048 ;
}
[ThreadStatic]
static byte [ ] TempSector = new byte [ 2352 ] ;
}
/// <summary>
/// this ISector is a raw MODE2 sector. could be form 0,1,2... who can say? supposedly:
/// To tell the different Mode 2s apart you have to examine bytes 16-23 of the sector (the first 8 bytes of Mode Data).
/// If bytes 16-19 are not the same as 20-23, then it is Mode 2. If they are equal and bit 5 is on (0x20), then it is Mode 2 Form 2. Otherwise it is Mode 2 Form 1.
/// ...but we're not using this information in any way
/// </summary>
class Sector_Mode2_2352 : Sector_Mode1_or_Mode2_2352
{
2013-08-08 19:31:08 +00:00
public override int Read_2352 ( byte [ ] buffer , int offset )
2011-05-08 09:07:46 +00:00
{
2013-08-08 19:31:08 +00:00
return BaseSector . Read_2352 ( buffer , offset ) ;
2011-05-08 09:07:46 +00:00
}
2013-06-25 08:31:48 +00:00
public override int Read_2048 ( byte [ ] buffer , int offset )
{
//to get 2048 bytes out of this sector type, start 24 bytes in
2013-08-08 19:31:08 +00:00
int ret = BaseSector . Read_2352 ( TempSector , 0 ) ;
2013-06-25 08:31:48 +00:00
Buffer . BlockCopy ( TempSector , 24 , buffer , offset , 2048 ) ;
System . Diagnostics . Debug . Assert ( buffer ! = TempSector ) ;
return 2048 ;
}
[ThreadStatic]
static byte [ ] TempSector = new byte [ 2352 ] ;
2011-05-08 09:07:46 +00:00
}
2013-11-15 00:49:19 +00:00
private static byte BCD_Byte ( byte val )
2011-05-08 09:07:46 +00:00
{
byte ret = ( byte ) ( val % 10 ) ;
ret + = ( byte ) ( 16 * ( val / 10 ) ) ;
return ret ;
}
//a blob that also has an ECM cache associated with it. maybe one day.
class ECMCacheBlob
{
public ECMCacheBlob ( IBlob blob )
{
BaseBlob = blob ;
}
public IBlob BaseBlob ;
}
2013-06-25 08:31:48 +00:00
/// <summary>
/// this ISector is a MODE1 sector that is generating itself from an underlying MODE1/2048 userdata piece
/// </summary>
2011-05-08 09:07:46 +00:00
class Sector_Mode1_2048 : ISector
{
2011-08-15 10:43:36 +00:00
public Sector_Mode1_2048 ( int ABA )
2011-05-08 09:07:46 +00:00
{
2011-08-15 10:43:36 +00:00
byte aba_min = ( byte ) ( ABA / 60 / 75 ) ;
byte aba_sec = ( byte ) ( ( ABA / 75 ) % 60 ) ;
byte aba_frac = ( byte ) ( ABA % 75 ) ;
bcd_aba_min = BCD_Byte ( aba_min ) ;
bcd_aba_sec = BCD_Byte ( aba_sec ) ;
bcd_aba_frac = BCD_Byte ( aba_frac ) ;
2011-05-08 09:07:46 +00:00
}
2011-08-15 10:43:36 +00:00
byte bcd_aba_min , bcd_aba_sec , bcd_aba_frac ;
2011-05-08 09:07:46 +00:00
public ECMCacheBlob Blob ;
public long Offset ;
byte [ ] extra_data ;
bool has_extra_data ;
2013-06-25 08:31:48 +00:00
public int Read_2048 ( byte [ ] buffer , int offset )
{
2013-08-08 19:26:37 +00:00
//this is easy. we only have 2048 bytes, and 2048 bytes were requested
2013-06-25 08:31:48 +00:00
return Blob . BaseBlob . Read ( Offset , buffer , offset , 2048 ) ;
}
2013-08-08 19:31:08 +00:00
public int Read_2352 ( byte [ ] buffer , int offset )
2011-05-08 09:07:46 +00:00
{
//user data
int read = Blob . BaseBlob . Read ( Offset , buffer , offset + 16 , 2048 ) ;
//if we read the 2048 physical bytes OK, then return the complete sector
if ( read = = 2048 & & has_extra_data )
{
Buffer . BlockCopy ( extra_data , 0 , buffer , offset , 16 ) ;
Buffer . BlockCopy ( extra_data , 16 , buffer , offset + 2064 , 4 + 8 + 172 + 104 ) ;
return 2352 ;
}
//sync
buffer [ offset + 0 ] = 0x00 ; buffer [ offset + 1 ] = 0xFF ; buffer [ offset + 2 ] = 0xFF ; buffer [ offset + 3 ] = 0xFF ;
buffer [ offset + 4 ] = 0xFF ; buffer [ offset + 5 ] = 0xFF ; buffer [ offset + 6 ] = 0xFF ; buffer [ offset + 7 ] = 0xFF ;
buffer [ offset + 8 ] = 0xFF ; buffer [ offset + 9 ] = 0xFF ; buffer [ offset + 10 ] = 0xFF ; buffer [ offset + 11 ] = 0x00 ;
//sector address
2011-08-15 10:43:36 +00:00
buffer [ offset + 12 ] = bcd_aba_min ;
buffer [ offset + 13 ] = bcd_aba_sec ;
buffer [ offset + 14 ] = bcd_aba_frac ;
2011-05-08 09:07:46 +00:00
//mode 1
buffer [ offset + 15 ] = 1 ;
2012-11-15 08:03:23 +00:00
//calculate EDC and poke into the sector
2012-11-17 22:16:09 +00:00
uint edc = ECM . EDC_Calc ( buffer , offset , 2064 ) ;
ECM . PokeUint ( buffer , 2064 , edc ) ;
2011-05-08 09:07:46 +00:00
//intermediate
for ( int i = 0 ; i < 8 ; i + + ) buffer [ offset + 2068 + i ] = 0 ;
//ECC
2012-11-15 08:03:23 +00:00
ECM . ECC_Populate ( buffer , offset , buffer , offset , false ) ;
//VALIDATION - check our homemade algorithms against code derived from ECM
////EDC
//GPL_ECM.edc_validateblock(buffer, 2064, buffer, offset + 2064);
////ECC
//GPL_ECM.ecc_validate(buffer, offset, false);
2011-05-08 09:07:46 +00:00
//if we read the 2048 physical bytes OK, then return the complete sector
if ( read = = 2048 )
{
extra_data = new byte [ 16 + 4 + 8 + 172 + 104 ] ;
Buffer . BlockCopy ( buffer , 0 , extra_data , 0 , 16 ) ;
Buffer . BlockCopy ( buffer , 2064 , extra_data , 16 , 4 + 8 + 172 + 104 ) ;
has_extra_data = true ;
return 2352 ;
}
//otherwise, return a smaller value to indicate an error
else return read ;
}
}
//this is a physical 2352 byte sector.
public class SectorEntry
{
2013-11-04 03:12:50 +00:00
public SectorEntry ( ISector sec ) { Sector = sec ; }
2011-05-08 09:07:46 +00:00
public ISector Sector ;
2011-08-14 23:13:31 +00:00
//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 ) ) ;
}
2011-05-08 09:07:46 +00:00
}
public List < IBlob > Blobs = new List < IBlob > ( ) ;
public List < SectorEntry > Sectors = new List < SectorEntry > ( ) ;
public DiscTOC TOC = new DiscTOC ( ) ;
2011-09-04 06:15:41 +00:00
public void Dispose ( )
{
foreach ( var blob in Blobs )
{
blob . Dispose ( ) ;
}
}
2011-05-08 09:07:46 +00:00
void FromIsoPathInternal ( string isoPath )
{
2013-06-25 08:31:48 +00:00
//make a fake cue file to represent this iso file
2013-11-15 00:49:19 +00:00
const string isoCueWrapper = @ "
2013-06-25 08:31:48 +00:00
FILE "" xarp . barp . marp . farp "" BINARY
TRACK 01 MODE1 / 2048
INDEX 01 00 : 00 : 00
";
2013-11-15 00:49:19 +00:00
string cueDir = String . Empty ;
2013-06-25 08:31:48 +00:00
var cue = new Cue ( ) ;
CueFileResolver [ "xarp.barp.marp.farp" ] = isoPath ;
cue . LoadFromString ( isoCueWrapper ) ;
FromCueInternal ( cue , cueDir , new CueBinPrefs ( ) ) ;
//var session = new DiscTOC.Session();
//session.num = 1;
//TOC.Sessions.Add(session);
//var track = new DiscTOC.Track();
//track.num = 1;
//session.Tracks.Add(track);
//var index = new DiscTOC.Index();
//index.num = 0;
//track.Indexes.Add(index);
//index = new DiscTOC.Index();
//index.num = 1;
//track.Indexes.Add(index);
//var fiIso = new FileInfo(isoPath);
//Blob_RawFile blob = new Blob_RawFile();
//blob.PhysicalPath = fiIso.FullName;
//Blobs.Add(blob);
//int num_aba = (int)(fiIso.Length / 2048);
//track.length_aba = num_aba;
//if (fiIso.Length % 2048 != 0)
// throw new InvalidOperationException("invalid iso file (size not multiple of 2048)");
////TODO - handle this with Final Fantasy 9 cd1.iso
//var ecmCacheBlob = new ECMCacheBlob(blob);
//for (int i = 0; i < num_aba; i++)
//{
// Sector_Mode1_2048 sector = new Sector_Mode1_2048(i+150);
// sector.Blob = ecmCacheBlob;
// sector.Offset = i * 2048;
// Sectors.Add(new SectorEntry(sector));
//}
//TOC.AnalyzeLengthsFromIndexLengths();
2011-05-08 09:07:46 +00:00
}
2011-06-20 09:09:21 +00:00
public CueBin DumpCueBin ( string baseName , CueBinPrefs prefs )
{
if ( TOC . Sessions . Count > 1 )
throw new NotSupportedException ( "can't dump cue+bin with more than 1 session yet" ) ;
CueBin ret = new CueBin ( ) ;
ret . baseName = baseName ;
ret . disc = this ;
2011-08-07 03:21:03 +00:00
if ( ! prefs . OneBlobPerTrack )
2011-06-20 09:09:21 +00:00
{
2011-08-06 21:40:52 +00:00
//this is the preferred mode of dumping things. we will always write full sectors.
2011-08-06 22:32:52 +00:00
string cue = TOC . GenerateCUE_OneBin ( prefs ) ;
2013-11-15 00:49:19 +00:00
var bfd = new CueBin . BinFileDescriptor { name = baseName + ".bin" } ;
2011-06-20 09:09:21 +00:00
ret . cue = string . Format ( "FILE \"{0}\" BINARY\n" , bfd . name ) + cue ;
ret . bins . Add ( bfd ) ;
2011-08-06 10:43:05 +00:00
bfd . SectorSize = 2352 ;
2011-08-14 23:13:31 +00:00
//skip the mandatory track 1 pregap! cue+bin files do not contain it
2011-08-15 10:43:36 +00:00
for ( int i = 150 ; i < TOC . length_aba ; i + + )
2011-06-20 09:09:21 +00:00
{
2011-08-15 10:43:36 +00:00
bfd . abas . Add ( i ) ;
bfd . aba_zeros . Add ( false ) ;
2011-06-20 09:09:21 +00:00
}
}
else
{
2011-08-06 22:32:52 +00:00
//we build our own cue here (unlike above) because we need to build the cue and the output dat aat the same time
2011-06-20 09:09:21 +00:00
StringBuilder sbCue = new StringBuilder ( ) ;
for ( int i = 0 ; i < TOC . Sessions [ 0 ] . Tracks . Count ; i + + )
{
var track = TOC . Sessions [ 0 ] . Tracks [ i ] ;
2013-11-15 00:49:19 +00:00
var bfd = new CueBin . BinFileDescriptor
{
name = baseName + string . Format ( " (Track {0:D2}).bin" , track . num ) ,
SectorSize = Cue . BINSectorSizeForTrackType ( track . TrackType )
} ;
2011-06-20 09:09:21 +00:00
ret . bins . Add ( bfd ) ;
2011-08-15 10:43:36 +00:00
int aba = 0 ;
2011-06-20 09:09:21 +00:00
2011-08-14 23:13:31 +00:00
//skip the mandatory track 1 pregap! cue+bin files do not contain it
2011-08-15 10:43:36 +00:00
if ( i = = 0 ) aba = 150 ;
2011-08-06 22:32:52 +00:00
2011-08-15 10:43:36 +00:00
for ( ; aba < track . length_aba ; aba + + )
2011-06-20 09:09:21 +00:00
{
2011-08-15 10:43:36 +00:00
int thisaba = track . Indexes [ 0 ] . aba + aba ;
bfd . abas . Add ( thisaba ) ;
bfd . aba_zeros . Add ( false ) ;
2011-06-20 09:09:21 +00:00
}
sbCue . AppendFormat ( "FILE \"{0}\" BINARY\n" , bfd . name ) ;
sbCue . AppendFormat ( " TRACK {0:D2} {1}\n" , track . num , Cue . TrackTypeStringForTrackType ( track . TrackType ) ) ;
foreach ( var index in track . Indexes )
{
2011-08-15 10:43:36 +00:00
int x = index . aba - track . Indexes [ 0 ] . aba ;
2011-08-28 06:31:31 +00:00
if ( index . num = = 0 & & index . aba = = track . Indexes [ 1 ] . aba )
{
//dont emit index 0 when it is the same as index 1, it is illegal for some reason
}
2011-08-14 23:13:31 +00:00
//else if (i==0 && index.num == 0)
//{
// //don't generate the first index, it is illogical
//}
2011-08-28 06:31:31 +00:00
else
2011-08-06 22:32:52 +00:00
{
//track 1 included the lead-in at the beginning of it. sneak past that.
2011-08-14 23:13:31 +00:00
//if (i == 0) x -= 150;
sbCue . AppendFormat ( " INDEX {0:D2} {1}\n" , index . num , new Timestamp ( x ) . Value ) ;
2011-08-06 22:32:52 +00:00
}
2011-06-20 09:09:21 +00:00
}
}
ret . cue = sbCue . ToString ( ) ;
}
return ret ;
}
2011-08-15 10:43:36 +00:00
/// <summary>
/// NOT USED RIGHT NOW. AMBIGUOUS, ANYWAY.
/// "bin" is an ill-defined concept.
/// </summary>
[Obsolete]
void DumpBin_2352 ( string binPath )
2011-05-08 09:07:46 +00:00
{
byte [ ] temp = new byte [ 2352 ] ;
2011-08-15 10:43:36 +00:00
//a cue's bin probably doesn't contain the first 150 sectors, so skip it
using ( FileStream fs = new FileStream ( binPath , FileMode . Create , FileAccess . Write , FileShare . None ) )
for ( int i = 150 ; i < Sectors . Count ; i + + )
2011-05-08 09:07:46 +00:00
{
2011-08-15 10:43:36 +00:00
ReadLBA_2352 ( i , temp , 0 ) ;
2011-05-08 09:07:46 +00:00
fs . Write ( temp , 0 , 2352 ) ;
}
}
2012-01-21 21:23:19 +00:00
public static Disc FromCuePath ( string cuePath , CueBinPrefs prefs )
2011-05-08 09:07:46 +00:00
{
var ret = new Disc ( ) ;
2012-01-21 21:23:19 +00:00
ret . FromCuePathInternal ( cuePath , prefs ) ;
2011-08-14 23:13:31 +00:00
ret . TOC . GeneratePoints ( ) ;
ret . PopulateQSubchannel ( ) ;
2011-05-08 09:07:46 +00:00
return ret ;
}
2011-09-04 06:15:41 +00:00
/// <summary>
/// THIS HASNT BEEN TESTED IN A LONG TIME. DOES IT WORK?
/// </summary>
2011-05-08 09:07:46 +00:00
public static Disc FromIsoPath ( string isoPath )
{
var ret = new Disc ( ) ;
ret . FromIsoPathInternal ( isoPath ) ;
2011-08-14 23:13:31 +00:00
ret . TOC . GeneratePoints ( ) ;
ret . PopulateQSubchannel ( ) ;
2011-05-08 09:07:46 +00:00
return ret ;
}
2011-08-14 23:13:31 +00:00
/// <summary>
/// creates subchannel Q data track for this disc
/// </summary>
void PopulateQSubchannel ( )
{
2011-08-15 10:43:36 +00:00
int aba = 0 ;
2011-08-14 23:13:31 +00:00
int dpIndex = 0 ;
2011-08-15 10:43:36 +00:00
while ( aba < Sectors . Count )
2011-08-14 23:13:31 +00:00
{
if ( dpIndex < TOC . Points . Count - 1 )
{
2011-08-15 10:43:36 +00:00
if ( aba > = TOC . Points [ dpIndex + 1 ] . ABA )
2011-08-14 23:13:31 +00:00
{
dpIndex + + ;
}
}
var dp = TOC . Points [ dpIndex ] ;
2011-08-15 10:43:36 +00:00
var se = Sectors [ aba ] ;
2011-08-14 23:13:31 +00:00
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 ) ;
2011-08-15 10:43:36 +00:00
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 ) ;
2011-08-14 23:13:31 +00:00
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 ) ;
2011-08-15 10:43:36 +00:00
Timestamp absolute_timestamp = new Timestamp ( aba ) ;
2011-08-14 23:13:31 +00:00
se . q_amin = BCD2 . FromDecimal ( absolute_timestamp . MIN ) ;
se . q_asec = BCD2 . FromDecimal ( absolute_timestamp . SEC ) ;
se . q_aframe = BCD2 . FromDecimal ( absolute_timestamp . FRAC ) ;
2011-08-15 10:43:36 +00:00
aba + + ;
2011-08-14 23:13:31 +00:00
}
}
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 )
{
2013-11-15 00:49:19 +00:00
return new BCD2 { DecimalValue = d } ;
2011-08-14 23:13:31 +00:00
}
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 )
{
2013-11-15 00:49:19 +00:00
Value = value ;
2011-08-14 23:13:31 +00:00
MIN = int . Parse ( value . Substring ( 0 , 2 ) ) ;
SEC = int . Parse ( value . Substring ( 3 , 2 ) ) ;
FRAC = int . Parse ( value . Substring ( 6 , 2 ) ) ;
2011-08-15 10:43:36 +00:00
ABA = MIN * 60 * 75 + SEC * 75 + FRAC ;
2011-08-14 23:13:31 +00:00
}
public readonly string Value ;
2011-08-15 10:43:36 +00:00
public readonly int MIN , SEC , FRAC , ABA ;
2011-08-14 23:13:31 +00:00
/// <summary>
2011-08-15 10:43:36 +00:00
/// creates timestamp from supplied ABA
2011-08-14 23:13:31 +00:00
/// </summary>
2011-08-15 10:43:36 +00:00
public Timestamp ( int ABA )
2011-08-14 23:13:31 +00:00
{
2011-08-15 10:43:36 +00:00
this . ABA = ABA ;
MIN = ABA / ( 60 * 75 ) ;
SEC = ( ABA / 75 ) % 60 ;
FRAC = ABA % 75 ;
2011-08-14 23:13:31 +00:00
Value = string . Format ( "{0:D2}:{1:D2}:{2:D2}" , MIN , SEC , FRAC ) ;
}
2011-05-08 09:07:46 +00:00
}
2011-06-20 09:09:21 +00:00
public enum ETrackType
{
Mode1_2352 ,
Mode1_2048 ,
Mode2_2352 ,
Audio
}
public class CueBinPrefs
{
/// <summary>
2011-08-07 03:21:03 +00:00
/// Controls general operations: should the output be split into several blobs, or just use one?
2011-06-20 09:09:21 +00:00
/// </summary>
2011-08-07 03:21:03 +00:00
public bool OneBlobPerTrack ;
2011-06-20 09:09:21 +00:00
2011-08-06 22:32:52 +00:00
/// <summary>
/// NOT SUPPORTED YET (just here as a reminder) If choosing OneBinPerTrack, you may wish to write wave files for audio tracks.
/// </summary>
//public bool DumpWaveFiles;
2011-06-20 09:09:21 +00:00
/// <summary>
/// turn this on to dump bins instead of just cues
/// </summary>
public bool ReallyDumpBin ;
2012-11-17 22:16:09 +00:00
/// <summary>
/// Dump bins to bitbucket instead of disk
/// </summary>
public bool DumpToBitbucket ;
2011-08-14 23:13:31 +00:00
/// <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 ;
2011-06-20 09:09:21 +00:00
/// <summary>
/// generate remarks and other annotations to help humans understand whats going on, but which will confuse many cue parsers
/// </summary>
public bool AnnotateCue ;
/// <summary>
2011-08-06 22:32:52 +00:00
/// EVIL: in theory this would attempt to generate pregap commands to save disc space, but I think this is a bad idea.
/// it would also be useful for OneBinPerTrack mode in making wave files.
/// HOWEVER - by the time we've loaded things up into our canonical format, we don't know which 'pregaps' are safe for turning back into pregaps
/// Because they might sometimes contain data (gapless audio discs). So we would have to inspect a series of sectors to look for silence.
/// And even still, the ECC information might be important. So, forget it.
/// NEVER USE OR IMPLEMENT THIS
2011-06-20 09:09:21 +00:00
/// </summary>
2011-08-06 22:32:52 +00:00
//public bool PreferPregapCommand = false;
2011-06-20 09:09:21 +00:00
/// <summary>
/// some cue parsers cant handle sessions. better not emit a session command then. multi-session discs will then be broken
/// </summary>
public bool SingleSession ;
2011-08-06 10:43:05 +00:00
2012-01-21 21:23:19 +00:00
/// <summary>
/// enables various extension-aware behaviours.
/// enables auto-search for files with the same name but differing extension.
/// enables auto-detection of situations where cue blobfiles are indicating the wrong type in the cuefile
/// </summary>
public bool ExtensionAware = false ;
/// <summary>
/// whenever we have a choice, use case sensitivity in searching for files
/// </summary>
public bool CaseSensitive = false ;
2011-08-14 23:13:31 +00:00
2011-08-06 10:43:05 +00:00
/// <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)
/// </summary>
public bool DumpECM = true ;
2011-06-20 09:09:21 +00:00
}
/// <summary>
/// Encapsulates an in-memory cue+bin (complete cuesheet and a little registry of files)
/// it will be based on a disc (fro mwhich it can read sectors to avoid burning through extra memory)
/// TODO - we must merge this with whatever reads in cue+bin
/// </summary>
public class CueBin
{
public string cue ;
public string baseName ;
public Disc disc ;
public class BinFileDescriptor
{
public string name ;
2011-08-15 10:43:36 +00:00
public List < int > abas = new List < int > ( ) ;
2011-08-14 23:13:31 +00:00
//todo - do we really need this? i dont think so...
2011-08-15 10:43:36 +00:00
public List < bool > aba_zeros = new List < bool > ( ) ;
2011-08-06 10:43:05 +00:00
public int SectorSize ;
2011-06-20 09:09:21 +00:00
}
public List < BinFileDescriptor > bins = new List < BinFileDescriptor > ( ) ;
2011-08-15 10:43:36 +00:00
//NOT SUPPORTED RIGHT NOW
//public string CreateRedumpReport()
//{
// if (disc.TOC.Sessions[0].Tracks.Count != bins.Count)
// throw new InvalidOperationException("Cannot generate redump report on CueBin lacking OneBinPerTrack property");
// StringBuilder sb = new StringBuilder();
// for (int i = 0; i < disc.TOC.Sessions[0].Tracks.Count; i++)
// {
// var track = disc.TOC.Sessions[0].Tracks[i];
// var bfd = bins[i];
2011-06-20 09:09:21 +00:00
2011-08-15 10:43:36 +00:00
// //dump the track
// byte[] dump = new byte[track.length_aba * 2352];
// //TODO ????????? post-ABA unknown
// //for (int aba = 0; aba < track.length_aba; aba++)
// // disc.ReadLBA_2352(bfd.lbas[lba],dump,lba*2352);
// string crc32 = string.Format("{0:X8}", CRC32.Calculate(dump));
// string md5 = Util.Hash_MD5(dump, 0, dump.Length);
// string sha1 = Util.Hash_SHA1(dump, 0, dump.Length);
// int pregap = track.Indexes[1].lba - track.Indexes[0].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),
// pregap_ts.Value,
// len_ts.Value,
// track.length_lba,
// track.length_lba*Cue.BINSectorSizeForTrackType(track.TrackType),
// crc32,
// md5,
// sha1
// );
// }
// return sb.ToString();
//}
2011-06-20 09:09:21 +00:00
public void Dump ( string directory , CueBinPrefs prefs )
{
2011-08-07 03:21:03 +00:00
ProgressReport pr = new ProgressReport ( ) ;
Dump ( directory , prefs , pr ) ;
}
public void Dump ( string directory , CueBinPrefs prefs , ProgressReport progress )
{
2011-08-14 23:13:31 +00:00
byte [ ] subQ_temp = new byte [ 12 ] ;
2011-08-07 03:21:03 +00:00
progress . TaskCount = 2 ;
progress . Message = "Generating Cue" ;
2011-08-07 04:00:06 +00:00
progress . ProgressEstimate = 1 ;
progress . ProgressCurrent = 0 ;
progress . InfoPresent = true ;
2011-06-20 09:09:21 +00:00
string cuePath = Path . Combine ( directory , baseName + ".cue" ) ;
2012-11-17 22:16:09 +00:00
if ( prefs . DumpToBitbucket ) { }
else File . WriteAllText ( cuePath , cue ) ;
2011-08-07 03:21:03 +00:00
progress . Message = "Writing bin(s)" ;
progress . TaskCurrent = 1 ;
2013-11-04 03:12:50 +00:00
progress . ProgressEstimate = bins . Sum ( bfd = > bfd . abas . Count ) ;
2011-08-07 03:21:03 +00:00
progress . ProgressCurrent = 0 ;
2011-08-14 23:13:31 +00:00
if ( ! prefs . ReallyDumpBin ) return ;
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" ) ;
2012-11-17 22:16:09 +00:00
Stream fsSubQ = null ;
Stream fs ;
if ( prefs . DumpToBitbucket )
fs = Stream . Null ;
else fs = new FileStream ( trackBinPath , FileMode . Create , FileAccess . Write , FileShare . None ) ;
2011-08-14 23:13:31 +00:00
try
2011-06-20 09:09:21 +00:00
{
2011-08-14 23:13:31 +00:00
if ( prefs . DumpSubchannelQ )
2012-11-17 22:16:09 +00:00
if ( prefs . DumpToBitbucket )
fsSubQ = Stream . Null ;
else fsSubQ = new FileStream ( subQPath , FileMode . Create , FileAccess . Write , FileShare . None ) ;
2011-08-14 23:13:31 +00:00
2011-08-15 10:43:36 +00:00
for ( int i = 0 ; i < bfd . abas . Count ; i + + )
2011-06-20 09:09:21 +00:00
{
2011-08-14 23:13:31 +00:00
if ( progress . CancelSignal ) return ;
2011-08-07 03:21:03 +00:00
2011-08-14 23:13:31 +00:00
progress . ProgressCurrent + + ;
2011-08-15 10:43:36 +00:00
int aba = bfd . abas [ i ] ;
if ( bfd . aba_zeros [ i ] )
2011-08-14 23:13:31 +00:00
{
fs . Write ( empty , 0 , sectorSize ) ;
}
else
{
if ( sectorSize = = 2352 )
2011-08-15 10:43:36 +00:00
disc . ReadABA_2352 ( aba , temp , 0 ) ;
else if ( sectorSize = = 2048 ) disc . ReadABA_2048 ( aba , temp , 0 ) ;
2011-08-14 23:13:31 +00:00
else throw new InvalidOperationException ( ) ;
fs . Write ( temp , 0 , sectorSize ) ;
//write subQ if necessary
if ( fsSubQ ! = null )
2011-06-20 09:09:21 +00:00
{
2011-08-15 10:43:36 +00:00
disc . Sectors [ aba ] . Read_SubchannelQ ( subQ_temp , 0 ) ;
2011-08-14 23:13:31 +00:00
fsSubQ . Write ( subQ_temp , 0 , 12 ) ;
2011-06-20 09:09:21 +00:00
}
}
}
}
2011-08-14 23:13:31 +00:00
finally
{
fs . Dispose ( ) ;
if ( fsSubQ ! = null ) fsSubQ . Dispose ( ) ;
}
}
2011-06-20 09:09:21 +00:00
}
}
2011-05-08 09:07:46 +00:00
}