BizHawk/BizHawk.Emulation/Disc/Disc.cs

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;
}
}
}