using System; using System.Text; using System.Text.RegularExpressions; using System.IO; using System.Collections.Generic; //TODO - reading big files across the network is slow due to the small sector read sizes. make some kind of read-ahead thing //TODO - add lead-in generation (so we can clarify the LBA addresses and have a place to put subcode perhaps) //the bin dumper will need to start at LBA 150 //http://www.pctechguide.com/iso-9660-data-format-for-cds-cd-roms-cd-rs-and-cd-rws //http://linux.die.net/man/1/cue2toc //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) //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[4bit] Adr[4bit] StartFrameAddressFAD[24bit] (nonexisting tracks are 0xFFFFFFFF) * Followed by Fist Track Information, Last Track Information.. * Ctrl[4bit] Adr[4bit] FirstTrackNumber/LastTrackNumber[8bit] and then some stuff I dont understand * ..and Read Out Information: * Ctrl[4bit] Adr[4bit] ReadOutStartFrameAddress[24bit] * * 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? //sector mode byte (0: silence; 1: 2048Byte mode (EDC,ECC,CIRC), 2: 2352Byte mode (CIRC only) //user data: 2336 bytes //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) //mode2_2352 is the only kind of mode2, by necessity //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 namespace BizHawk.Disc { public partial class Disc { //TODO - separate these into Read_2352 and Read_2048 (optimizations can be made by ISector implementors depending on what is requested) //(for example, avoiding the 2048 byte sector creating the ECC data and then immediately discarding it) public interface ISector { int Read(byte[] buffer, int offset); } public interface IBlob { int Read(long byte_pos, byte[] buffer, int offset, int count); void Dispose(); } class Blob_RawFile : IBlob { public string PhysicalPath; public long Offset; FileStream fs; public void Dispose() { if (fs != null) { fs.Dispose(); fs = null; } } public int Read(long byte_pos, byte[] buffer, int offset, int count) { if (fs == null) fs = new FileStream(PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.Read); long target = byte_pos + Offset; if(fs.Position != target) fs.Position = target; return fs.Read(buffer, offset, count); } } class Sector_RawSilence : ISector { public int Read(byte[] buffer, int offset) { Array.Clear(buffer, 0, 2352); return 2352; } } class Sector_RawBlob : ISector { public IBlob Blob; public long Offset; public int Read(byte[] buffer, int offset) { return Blob.Read(Offset, buffer, offset, 2352); } } class Sector_ZeroPad : ISector { public ISector BaseSector; public int BaseLength; public int Read(byte[] buffer, int offset) { int read = BaseSector.Read(buffer, offset); if(read < BaseLength) return read; for (int i = BaseLength; i < 2352; i++) buffer[offset + i] = 0; return 2352; } } class Sector_Raw : ISector { public ISector BaseSector; public int Read(byte[] buffer, int offset) { return BaseSector.Read(buffer, offset); } } protected static byte BCD_Byte(byte val) { 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; } class Sector_Mode1_2048 : ISector { public Sector_Mode1_2048(int LBA) { byte lba_min = (byte)(LBA / 60 / 75); byte lba_sec = (byte)((LBA / 75) % 60); byte lba_frac = (byte)(LBA % 75); bcd_lba_min = BCD_Byte(lba_min); bcd_lba_sec = BCD_Byte(lba_sec); bcd_lba_frac = BCD_Byte(lba_frac); } byte bcd_lba_min, bcd_lba_sec, bcd_lba_frac; public ECMCacheBlob Blob; public long Offset; byte[] extra_data; bool has_extra_data; public int Read(byte[] buffer, int offset) { //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 buffer[offset + 12] = bcd_lba_min; buffer[offset + 13] = bcd_lba_sec; buffer[offset + 14] = bcd_lba_frac; //mode 1 buffer[offset + 15] = 1; //EDC ECM.edc_computeblock(buffer, offset+2064, buffer, offset+2064); //intermediate for (int i = 0; i < 8; i++) buffer[offset + 2068 + i] = 0; //ECC ECM.ecc_generate(buffer, offset, false, buffer, offset+2076); //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 { public ISector Sector; } public List Blobs = new List(); public List Sectors = new List(); public DiscTOC TOC = new DiscTOC(); void FromIsoPathInternal(string isoPath) { 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_lba = (int)(fiIso.Length / 2048); index.length_lba = num_lba; if (fiIso.Length % 2048 != 0) throw new InvalidOperationException("invalid iso file (size not multiple of 2048)"); var ecmCacheBlob = new ECMCacheBlob(blob); for (int i = 0; i < num_lba; i++) { Sector_Mode1_2048 sector = new Sector_Mode1_2048(i+150); sector.Blob = ecmCacheBlob; sector.Offset = i * 2048; SectorEntry se = new SectorEntry(); se.Sector = sector; Sectors.Add(se); } TOC.AnalyzeLengthsFromIndexLengths(); } public void DumpBin_2352(string binPath) { byte[] temp = new byte[2352]; using(FileStream fs = new FileStream(binPath,FileMode.Create,FileAccess.Write,FileShare.None)) for (int i = 0; i < Sectors.Count; i++) { ReadLBA_2352(i, temp, 0); fs.Write(temp, 0, 2352); } } public static Disc FromCuePath(string cuePath) { var ret = new Disc(); ret.FromCuePathInternal(cuePath); return ret; } public static Disc FromIsoPath(string isoPath) { var ret = new Disc(); ret.FromIsoPathInternal(isoPath); return ret; } } }