303 lines
9.8 KiB
C#
303 lines
9.8 KiB
C#
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<IBlob> Blobs = new List<IBlob>();
|
|
public List<SectorEntry> Sectors = new List<SectorEntry>();
|
|
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;
|
|
}
|
|
}
|
|
|
|
} |