disc subsystem progress

This commit is contained in:
zeromus 2011-05-08 09:07:46 +00:00
parent a91c8ecbd7
commit 646dd59ad6
12 changed files with 1454 additions and 6 deletions

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}</ProjectGuid>
<OutputType>Library</OutputType>
@ -44,6 +44,8 @@
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
@ -159,6 +161,13 @@
<Compile Include="Consoles\Gameboy\Mappers.cs" />
<Compile Include="Database\CRC32.cs" />
<Compile Include="Database\Database.cs" />
<Compile Include="Disc\CCD_format.cs" />
<Compile Include="Disc\CUE_format.cs" />
<Compile Include="Disc\Disc.cs" />
<Compile Include="Disc\DiscTOC.cs" />
<Compile Include="Disc\ECM.cs" />
<Compile Include="Disc\FFmpeg.cs" />
<Compile Include="Disc\TOC_format.cs" />
<Compile Include="Interfaces\Base Implementations\Game.cs" />
<Compile Include="Interfaces\Base Implementations\IPS.cs" />
<Compile Include="Interfaces\Base Implementations\Movies.cs" />

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
namespace BizHawk.Disc
{
//TBD CCD format
public class CCDFormat
{
}
}

View File

@ -0,0 +1,416 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Disc
{
partial class Disc
{
void FromCuePathInternal(string cuePath)
{
string cueDir = Path.GetDirectoryName(cuePath);
var cue = new Cue();
cue.LoadFromPath(cuePath);
var session = new DiscTOC.Session();
session.num = 1;
TOC.Sessions.Add(session);
int track_counter = 1;
int curr_lba = 0;
foreach (var cue_file in cue.Files)
{
//make a raw file blob for the source binfile
Blob_RawFile blob = new Blob_RawFile();
blob.PhysicalPath = Path.Combine(cueDir, cue_file.Path);
Blobs.Add(blob);
//structural validation
if (cue_file.Tracks.Count < 1) throw new Cue.CueBrokenException("Found a cue file with no tracks");
//structural validation: every track must be the same type with cue/bin (i think)
var trackType = cue_file.Tracks[0].TrackType;
for (int i = 1; i < cue_file.Tracks.Count; i++)
{
if (cue_file.Tracks[i].TrackType != trackType) throw new Cue.CueBrokenException("cue has different track types per datafile (not supported now; maybe never)");
}
//structural validaton: make sure file is a correct size and analyze its length
long flen = new FileInfo(blob.PhysicalPath).Length;
int numlba;
int leftover = 0;
switch (trackType)
{
case Cue.ECueTrackType.Audio:
numlba = (int)(flen / 2352);
leftover = (int)(flen - numlba * 2352);
break;
case Cue.ECueTrackType.Mode1_2352:
case Cue.ECueTrackType.Mode2_2352:
if (flen % 2352 != 0) throw new Cue.CueBrokenException("Found a modeN_2352 cue file that is wrongly-sized");
numlba = (int)(flen / 2352);
break;
case Cue.ECueTrackType.Mode1_2048:
if (flen % 2048 != 0) throw new Cue.CueBrokenException("Found a modeN_2048 cue file that is wrongly-sized");
numlba = (int)(flen / 2048);
break;
default: throw new InvalidOperationException();
}
List<DiscTOC.Track> new_toc_tracks = new List<DiscTOC.Track>();
for (int i = 0; i < cue_file.Tracks.Count; i++)
{
bool last_track = (i == cue_file.Tracks.Count - 1);
var cue_track = cue_file.Tracks[i];
if (cue_track.TrackNum != track_counter) throw new Cue.CueBrokenException("Missing a track in the middle of the cue");
track_counter++;
DiscTOC.Track toc_track = new DiscTOC.Track();
toc_track.num = track_counter;
session.Tracks.Add(toc_track);
new_toc_tracks.Add(toc_track);
//analyze indices
int idx;
for (idx = 0; idx <= 99; idx++)
{
if (!cue_track.Indexes.ContainsKey(idx))
{
if (idx == 0) continue;
if (idx == 1) throw new Cue.CueBrokenException("cue track is missing an index 1");
break;
}
var cue_index = cue_track.Indexes[idx];
//todo - add pregap/postgap from cue?
DiscTOC.Index toc_index = new DiscTOC.Index();
toc_index.num = idx;
toc_track.Indexes.Add(toc_index);
toc_index.lba = cue_index.Timestamp.LBA + curr_lba;
}
//look for extra indices (i.e. gaps)
for (; idx <= 99; idx++)
{
if (cue_track.Indexes.ContainsKey(idx))
throw new Cue.CueBrokenException("cue track is has an index gap");
}
} //track loop
//analyze length of each track and index
DiscTOC.Index last_toc_index = null;
foreach (var toc_track in new_toc_tracks)
{
foreach (var toc_index in toc_track.Indexes)
{
if (last_toc_index != null)
last_toc_index.length_lba = toc_index.lba - last_toc_index.lba;
last_toc_index = toc_index;
}
}
if (last_toc_index != null)
last_toc_index.length_lba = (curr_lba + numlba) - last_toc_index.lba;
//generate the sectors from this file
long curr_src_addr = 0;
for (int i = 0; i < numlba; i++)
{
ISector sector;
switch (trackType)
{
case Cue.ECueTrackType.Audio:
//all 2352 bytes are present
case Cue.ECueTrackType.Mode1_2352:
//2352 bytes are present, containing 2048 bytes of user data as well as ECM
case Cue.ECueTrackType.Mode2_2352:
//2352 bytes are present, containing 2336 bytes of user data, replacing ECM
{
Sector_RawBlob sector_rawblob = new Sector_RawBlob();
sector_rawblob.Blob = blob;
sector_rawblob.Offset = curr_src_addr;
curr_src_addr += 2352;
Sector_Raw sector_raw = new Sector_Raw();
sector_raw.BaseSector = sector_rawblob;
if (i == numlba - 1 && leftover != 0)
{
Sector_ZeroPad sector_zeropad = new Sector_ZeroPad();
sector_zeropad.BaseSector = sector_rawblob;
sector_zeropad.BaseLength = 2352 - leftover;
sector_raw.BaseSector = sector_zeropad;
}
sector = sector_raw;
break;
}
case Cue.ECueTrackType.Mode1_2048:
//2048 bytes are present. ECM needs to be generated to create a full sector
{
var sector_2048 = new Sector_Mode1_2048(i + 150);
sector_2048.Blob = new ECMCacheBlob(blob);
sector_2048.Offset = curr_src_addr;
curr_src_addr += 2048;
sector = sector_2048;
break;
}
default: throw new InvalidOperationException();
}
SectorEntry se = new SectorEntry();
se.Sector = sector;
Sectors.Add(se);
}
curr_lba += numlba;
} //done analyzing cue datafiles
TOC.AnalyzeLengthsFromIndexLengths();
}
}
public class Cue
{
//TODO - export from isobuster and observe the SESSION directive, as well as the MSF directive.
public string DebugPrint()
{
StringBuilder sb = new StringBuilder();
foreach (CueFile cf in Files)
{
sb.AppendFormat("FILE \"{0}\"", cf.Path);
if (cf.Binary) sb.Append(" BINARY");
sb.AppendLine();
foreach (CueTrack ct in cf.Tracks)
{
sb.AppendFormat(" TRACK {0:D2} {1}\n", ct.TrackNum, ct.TrackType.ToString().Replace("_", "/").ToUpper());
foreach (CueTrackIndex cti in ct.Indexes.Values)
{
sb.AppendFormat(" INDEX {0:D2} {1}\n", cti.IndexNum, cti.Timestamp.Value);
}
}
}
return sb.ToString();
}
public class CueFile
{
public string Path;
public bool Binary;
public List<CueTrack> Tracks = new List<CueTrack>();
}
public List<CueFile> Files = new List<CueFile>();
public enum ECueTrackType
{
Mode1_2352,
Mode1_2048,
Mode2_2352,
Audio
}
public class CueTrack
{
public ECueTrackType TrackType;
public int TrackNum;
public Dictionary<int, CueTrackIndex> Indexes = new Dictionary<int, CueTrackIndex>();
}
public class CueTimestamp
{
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;
}
public class CueTrackIndex
{
public int IndexNum;
public CueTimestamp Timestamp;
}
public class CueBrokenException : Exception
{
public CueBrokenException(string why)
: base(why)
{
}
}
public void LoadFromPath(string cuePath)
{
FileInfo fiCue = new FileInfo(cuePath);
if (!fiCue.Exists) throw new FileNotFoundException();
File.ReadAllText(cuePath);
TextReader tr = new StreamReader(cuePath);
CueFile currFile = null;
CueTrack currTrack = null;
int state = 0;
for (; ; )
{
string line = tr.ReadLine();
if (line == null) break;
if (line == "") continue;
line = line.Trim();
var clp = new CueLineParser(line);
string key = clp.ReadToken().ToUpper();
switch (key)
{
case "REM":
break;
case "FILE":
{
currTrack = null;
currFile = new CueFile();
Files.Add(currFile);
currFile.Path = clp.ReadPath().Trim('"');
if (!clp.EOF)
{
string temp = clp.ReadToken().ToUpper();
if (temp == "BINARY")
currFile.Binary = true;
}
break;
}
case "TRACK":
{
if (currFile == null) throw new CueBrokenException("invalid cue structure");
if (clp.EOF) throw new CueBrokenException("invalid cue structure");
string strtracknum = clp.ReadToken();
int tracknum;
if (!int.TryParse(strtracknum, out tracknum))
throw new CueBrokenException("malformed track number");
if (clp.EOF) throw new CueBrokenException("invalid cue structure");
string strtracktype = clp.ReadToken().ToUpper();
currTrack = new CueTrack();
switch (strtracktype)
{
case "MODE1/2352": currTrack.TrackType = ECueTrackType.Mode1_2352; break;
case "MODE1/2048": currTrack.TrackType = ECueTrackType.Mode1_2048; break;
case "MODE2/2352": currTrack.TrackType = ECueTrackType.Mode2_2352; break;
case "AUDIO": currTrack.TrackType = ECueTrackType.Audio; break;
default:
throw new CueBrokenException("unhandled track type");
}
currTrack.TrackNum = tracknum;
currFile.Tracks.Add(currTrack);
break;
}
case "INDEX":
{
if (currTrack == null) throw new CueBrokenException("invalid cue structure");
if (clp.EOF) throw new CueBrokenException("invalid cue structure");
string strindexnum = clp.ReadToken();
int indexnum;
if (!int.TryParse(strindexnum, out indexnum))
throw new CueBrokenException("malformed index number");
if (clp.EOF) throw new CueBrokenException("invalid cue structure (missing index timestamp)");
string str_timestamp = clp.ReadToken();
CueTrackIndex cti = new CueTrackIndex();
cti.Timestamp = new CueTimestamp(str_timestamp); ;
cti.IndexNum = indexnum;
currTrack.Indexes[indexnum] = cti;
break;
}
case "PREGAP":
case "POSTGAP":
throw new CueBrokenException("cue postgap/pregap command not supported yet");
default:
throw new CueBrokenException("unsupported cue command: " + key);
}
}
}
class CueLineParser
{
int index;
string str;
public bool EOF;
public CueLineParser(string line)
{
this.str = line;
}
public string ReadPath() { return ReadToken(true); }
public string ReadToken() { return ReadToken(false); }
public string ReadToken(bool isPath)
{
if (EOF) return null;
int startIndex = index;
bool inToken = false;
bool inQuote = false;
for (; ; )
{
bool done = false;
char c = str[index];
bool isWhiteSpace = (c == ' ' || c == '\t');
if (isWhiteSpace)
{
if (inQuote)
index++;
else
{
if (inToken)
done = true;
else
index++;
}
}
else
{
bool startedQuote = false;
if (!inToken)
{
startIndex = index;
if (isPath && c == '"')
startedQuote = inQuote = true;
inToken = true;
}
switch (str[index])
{
case '"':
index++;
if (inQuote && !startedQuote)
{
done = true;
}
break;
case '\\':
index++;
break;
default:
index++;
break;
}
}
if (index == str.Length)
{
EOF = true;
done = true;
}
if (done) break;
}
return str.Substring(startIndex, index - startIndex);
}
}
}
}

View File

@ -0,0 +1,313 @@
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
{
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(i, temp, 0);
fs.Write(temp, 0, 2352);
}
}
//main api for reading a 2352-byte LBA from a disc
public void ReadLBA(int fad, byte[] buffer, int offset)
{
Sectors[fad].Sector.Read(buffer, offset);
}
//main api for reading the TOC from a disc
public DiscTOC ReadTOC()
{
return TOC;
}
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;
}
}
}

View File

@ -0,0 +1,84 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Disc
{
public class DiscTOC
{
public class Session
{
public int num;
public List<Track> Tracks = new List<Track>();
//the length of the track (should be the sum of all track lengths)
public int length_lba;
}
public class Track
{
public int num;
public List<Index> Indexes = new List<Index>();
//the length of the track (should be the sum of all index lengths)
public int length_lba;
}
public class Index
{
public int num;
public int lba;
//the length of the section
public int length_lba;
}
public static string FormatLBA(int lba)
{
return string.Format("{0} ({1:D2}:{2:D2}:{3:D2})", lba, lba / 60 / 75, (lba / 75) % 60, lba % 75);
}
public string DebugPrint()
{
StringBuilder sb = new StringBuilder();
foreach (var session in Sessions)
{
sb.AppendFormat("SESSION {0:D2} (length={1})\n", session.num, session.length_lba);
foreach (var track in session.Tracks)
{
sb.AppendFormat(" TRACK {0:D2} (length={1})\n", track.num, track.length_lba);
foreach (var index in track.Indexes)
{
sb.AppendFormat(" INDEX {0:D2}: {1}\n", index.num, FormatLBA(index.lba));
}
}
sb.AppendFormat("-EOF-\n");
}
return sb.ToString();
}
public List<Session> Sessions = new List<Session>();
public int length_lba;
public void AnalyzeLengthsFromIndexLengths()
{
foreach (var session in Sessions)
{
foreach (var track in session.Tracks)
{
int track_size = 0;
foreach (var index in track.Indexes)
track_size += index.length_lba;
track.length_lba += track_size;
session.length_lba += track_size;
length_lba += track_size;
}
}
}
}
}

View File

@ -0,0 +1,92 @@
namespace BizHawk.Disc
{
static class ECM
{
static byte[] ecc_f_lut = new byte[256];
static byte[] ecc_b_lut = new byte[256];
static uint[] edc_lut = new uint[256];
static ECM()
{
uint i, j, edc;
for(i = 0; i < 256; i++)
{
j = (uint)((i << 1) ^ (((i & 0x80) != 0) ? 0x11D : 0));
ecc_f_lut[i] = (byte)j;
ecc_b_lut[i ^ j] = (byte)i;
edc = i;
for (j = 0; j < 8; j++)
edc = (edc >> 1) ^ (((edc & 1) != 0) ? 0xD8018001 : 0);
edc_lut[i] = edc;
}
}
static uint edc_partial_computeblock(uint edc, byte[] src, int count)
{
int i = 0;
while (count-- != 0) edc = (edc >> 8) ^ edc_lut[(edc ^ (src[i++])) & 0xFF];
return edc;
}
public static void edc_computeblock(byte[] src, int count, byte[] dest, int dest_offset)
{
uint edc = edc_partial_computeblock(0, src, count);
dest[dest_offset + 0] = (byte)((edc >> 0) & 0xFF);
dest[dest_offset + 1] = (byte)((edc >> 8) & 0xFF);
dest[dest_offset + 2] = (byte)((edc >> 16) & 0xFF);
dest[dest_offset + 3] = (byte)((edc >> 24) & 0xFF);
}
static void ecc_computeblock(byte[] src, int src_offset, uint major_count, uint minor_count,uint major_mult, uint minor_inc, byte[] dest, int dest_offset)
{
uint size = major_count * minor_count;
uint major, minor;
for (major = 0; major < major_count; major++)
{
uint index = (major >> 1) * major_mult + (major & 1);
byte ecc_a = 0;
byte ecc_b = 0;
for (minor = 0; minor < minor_count; minor++)
{
byte temp = src[src_offset+index];
index += minor_inc;
if (index >= size) index -= size;
ecc_a ^= temp;
ecc_b ^= temp;
ecc_a = ecc_f_lut[ecc_a];
//System.Console.WriteLine("{0} {1}", ecc_a, ecc_b);
}
ecc_a = ecc_b_lut[ecc_f_lut[ecc_a] ^ ecc_b];
dest[dest_offset + major] = ecc_a;
dest[dest_offset + major + major_count] = (byte)(ecc_a ^ ecc_b);
}
}
public unsafe static void ecc_generate(byte[] sector, int sector_offset, bool zeroaddress, byte[] dest, int dest_offset)
{
byte address0=0,address1=0,address2=0,address3=0;
byte i;
/* Save the address and zero it out */
if(zeroaddress)
{
address0 = sector[sector_offset + 12 + 0]; sector[sector_offset + 12 + 0] = 0;
address1 = sector[sector_offset + 12 + 1]; sector[sector_offset + 12 + 1] = 0;
address2 = sector[sector_offset + 12 + 2]; sector[sector_offset + 12 + 2] = 0;
address3 = sector[sector_offset + 12 + 3]; sector[sector_offset + 12 + 3] = 0;
}
/* Compute ECC P code */
ecc_computeblock(sector, sector_offset + 0xC, 86, 24, 2, 86, dest, dest_offset);
/* Compute ECC Q code */
ecc_computeblock(sector, sector_offset + 0xC, 52, 43, 86, 88, dest, dest_offset+172);
/* Restore the address */
if (zeroaddress)
{
sector[sector_offset + 12 + 0] = address0;
sector[sector_offset + 12 + 3] = address1;
sector[sector_offset + 12 + 2] = address2;
sector[sector_offset + 12 + 1] = address3;
}
}
}
}

View File

@ -0,0 +1,426 @@
////http://jasonjano.wordpress.com/2010/02/09/a-simple-c-wrapper-for-ffmpeg/
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Web;
//using System.IO;
//using System.Diagnostics;
//using System.Configuration;
//using System.Text.RegularExpressions;
//namespace ffMpeg
//{
// public class Converter
// {
// #region Properties
// public string _ffExe;
// //i.e. temp
// public string WorkingPath;
// #endregion
// #region Constructors
// public Converter()
// {
// Initialize();
// }
// public Converter(string ffmpegExePath)
// {
// _ffExe = ffmpegExePath;
// Initialize();
// }
// #endregion
// #region Initialization
// private void Initialize()
// {
// }
// private string GetWorkingFile()
// {
// //try the stated directory
// if (File.Exists(_ffExe))
// {
// return _ffExe;
// }
// //oops, that didn't work, try the base directory
// if (File.Exists(Path.GetFileName(_ffExe)))
// {
// return Path.GetFileName(_ffExe);
// }
// //well, now we are really unlucky, let's just return null
// return null;
// }
// #endregion
// #region Get the File without creating a file lock
// public static System.Drawing.Image LoadImageFromFile(string fileName)
// {
// System.Drawing.Image theImage = null;
// using (FileStream fileStream = new FileStream(fileName, FileMode.Open,
// FileAccess.Read))
// {
// byte[] img;
// img = new byte[fileStream.Length];
// fileStream.Read(img, 0, img.Length);
// fileStream.Close();
// theImage = System.Drawing.Image.FromStream(new MemoryStream(img));
// img = null;
// }
// GC.Collect();
// return theImage;
// }
// public static MemoryStream LoadMemoryStreamFromFile(string fileName)
// {
// MemoryStream ms = null;
// using (FileStream fileStream = new FileStream(fileName, FileMode.Open,
// FileAccess.Read))
// {
// byte[] fil;
// fil = new byte[fileStream.Length];
// fileStream.Read(fil, 0, fil.Length);
// fileStream.Close();
// ms = new MemoryStream(fil);
// }
// GC.Collect();
// return ms;
// }
// #endregion
// #region Run the process
// private string RunProcess(string Parameters)
// {
// //create a process info
// ProcessStartInfo oInfo = new ProcessStartInfo(this._ffExe, Parameters);
// oInfo.UseShellExecute = false;
// oInfo.CreateNoWindow = true;
// oInfo.RedirectStandardOutput = true;
// oInfo.RedirectStandardError = true;
// //Create the output and streamreader to get the output
// string output = null; StreamReader srOutput = null;
// //try the process
// try
// {
// //run the process
// Process proc = System.Diagnostics.Process.Start(oInfo);
// proc.WaitForExit();
// //get the output
// srOutput = proc.StandardError;
// //now put it in a string
// output = srOutput.ReadToEnd();
// proc.Close();
// }
// catch (Exception)
// {
// output = string.Empty;
// }
// finally
// {
// //now, if we succeded, close out the streamreader
// if (srOutput != null)
// {
// srOutput.Close();
// srOutput.Dispose();
// }
// }
// return output;
// }
// #endregion
// #region GetVideoInfo
// public VideoFile GetVideoInfo(MemoryStream inputFile, string Filename)
// {
// string tempfile = Path.Combine(this.WorkingPath, System.Guid.NewGuid().ToString() + Path.GetExtension(Filename));
// FileStream fs = File.Create(tempfile);
// inputFile.WriteTo(fs);
// fs.Flush();
// fs.Close();
// GC.Collect();
// VideoFile vf = null;
// try
// {
// vf = new VideoFile(tempfile);
// }
// catch (Exception ex)
// {
// throw ex;
// }
// GetVideoInfo(vf);
// try
// {
// File.Delete(tempfile);
// }
// catch (Exception)
// {
// }
// return vf;
// }
// public VideoFile GetVideoInfo(string inputPath)
// {
// VideoFile vf = null;
// try
// {
// vf = new VideoFile(inputPath);
// }
// catch (Exception ex)
// {
// throw ex;
// }
// GetVideoInfo(vf);
// return vf;
// }
// public void GetVideoInfo(VideoFile input)
// {
// //set up the parameters for video info
// string Params = string.Format("-i {0}", input.Path);
// string output = RunProcess(Params);
// input.RawInfo = output;
// //get duration
// Regex re = new Regex("[D|d]uration:.((\\d|:|\\.)*)");
// Match m = re.Match(input.RawInfo);
// if (m.Success)
// {
// string duration = m.Groups[1].Value;
// string[] timepieces = duration.Split(new char[] { ':', '.' });
// if (timepieces.Length == 4)
// {
// input.Duration = new TimeSpan(0, Convert.ToInt16(timepieces[0]), Convert.ToInt16(timepieces[1]), Convert.ToInt16(timepieces[2]), Convert.ToInt16(timepieces[3]));
// }
// }
// //get audio bit rate
// re = new Regex("[B|b]itrate:.((\\d|:)*)");
// m = re.Match(input.RawInfo);
// double kb = 0.0;
// if (m.Success)
// {
// Double.TryParse(m.Groups[1].Value, out kb);
// }
// input.BitRate = kb;
// //get the audio format
// re = new Regex("[A|a]udio:.*");
// m = re.Match(input.RawInfo);
// if (m.Success)
// {
// input.AudioFormat = m.Value;
// }
// //get the video format
// re = new Regex("[V|v]ideo:.*");
// m = re.Match(input.RawInfo);
// if (m.Success)
// {
// input.VideoFormat = m.Value;
// }
// //get the video format
// re = new Regex("(\\d{2,3})x(\\d{2,3})");
// m = re.Match(input.RawInfo);
// if (m.Success)
// {
// int width = 0; int height = 0;
// int.TryParse(m.Groups[1].Value, out width);
// int.TryParse(m.Groups[2].Value, out height);
// input.Width = width;
// input.Height = height;
// }
// input.infoGathered = true;
// }
// #endregion
// #region Convert to FLV
// public OutputPackage ConvertToFLV(MemoryStream inputFile, string Filename)
// {
// string tempfile = Path.Combine(this.WorkingPath, System.Guid.NewGuid().ToString() + Path.GetExtension(Filename));
// FileStream fs = File.Create(tempfile);
// inputFile.WriteTo(fs);
// fs.Flush();
// fs.Close();
// GC.Collect();
// VideoFile vf = null;
// try
// {
// vf = new VideoFile(tempfile);
// }
// catch (Exception ex)
// {
// throw ex;
// }
// OutputPackage oo = ConvertToFLV(vf);
// try
// {
// File.Delete(tempfile);
// }
// catch (Exception)
// {
// }
// return oo;
// }
// public OutputPackage ConvertToFLV(string inputPath)
// {
// VideoFile vf = null;
// try
// {
// vf = new VideoFile(inputPath);
// }
// catch (Exception ex)
// {
// throw ex;
// }
// OutputPackage oo = ConvertToFLV(vf);
// return oo;
// }
// public OutputPackage ConvertToFLV(VideoFile input)
// {
// if (!input.infoGathered)
// {
// GetVideoInfo(input);
// }
// OutputPackage ou = new OutputPackage();
// //set up the parameters for getting a previewimage
// string filename = System.Guid.NewGuid().ToString() + ".jpg";
// int secs;
// //divide the duration in 3 to get a preview image in the middle of the clip
// //instead of a black image from the beginning.
// secs = (int)Math.Round(TimeSpan.FromTicks(input.Duration.Ticks / 3).TotalSeconds, 0);
// string finalpath = Path.Combine(this.WorkingPath, filename);
// string Params = string.Format("-i {0} {1} -vcodec mjpeg -ss {2} -vframes 1 -an -f rawvideo", input.Path, finalpath, secs);
// string output = RunProcess(Params);
// ou.RawOutput = output;
// if (File.Exists(finalpath))
// {
// ou.PreviewImage = LoadImageFromFile(finalpath);
// try
// {
// File.Delete(finalpath);
// }
// catch (Exception) { }
// }
// else
// { //try running again at frame 1 to get something
// Params = string.Format("-i {0} {1} -vcodec mjpeg -ss {2} -vframes 1 -an -f rawvideo", input.Path, finalpath, 1);
// output = RunProcess(Params);
// ou.RawOutput = output;
// if (File.Exists(finalpath))
// {
// ou.PreviewImage = LoadImageFromFile(finalpath);
// try
// {
// File.Delete(finalpath);
// }
// catch (Exception) { }
// }
// }
// finalpath = Path.Combine(this.WorkingPath, filename);
// filename = System.Guid.NewGuid().ToString() + ".flv";
// Params = string.Format("-i {0} -y -ar 22050 -ab 64 -f flv {1}", input.Path, finalpath);
// output = RunProcess(Params);
// if (File.Exists(finalpath))
// {
// ou.VideoStream = LoadMemoryStreamFromFile(finalpath);
// try
// {
// File.Delete(finalpath);
// }
// catch (Exception) { }
// }
// return ou;
// }
// #endregion
// }
// public class VideoFile
// {
// #region Properties
// private string _Path;
// public string Path
// {
// get
// {
// return _Path;
// }
// set
// {
// _Path = value;
// }
// }
// public TimeSpan Duration { get; set; }
// public double BitRate { get; set; }
// public string AudioFormat { get; set; }
// public string VideoFormat { get; set; }
// public int Height { get; set; }
// public int Width { get; set; }
// public string RawInfo { get; set; }
// public bool infoGathered { get; set; }
// #endregion
// #region Constructors
// public VideoFile(string path)
// {
// _Path = path;
// Initialize();
// }
// #endregion
// #region Initialization
// private void Initialize()
// {
// this.infoGathered = false;
// //first make sure we have a value for the video file setting
// if (string.IsNullOrEmpty(_Path))
// {
// throw new Exception("Could not find the location of the video file");
// }
// //Now see if the video file exists
// if (!File.Exists(_Path))
// {
// throw new Exception("The video file " + _Path + " does not exist.");
// }
// }
// #endregion
// }
// public class OutputPackage
// {
// public MemoryStream VideoStream { get; set; }
// public System.Drawing.Image PreviewImage { get; set; }
// public string RawOutput { get; set; }
// public bool Success { get; set; }
// }
//}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
namespace BizHawk.Disc
{
//TBD TOC format
public class TOCFormat
{
}
}

View File

@ -69,7 +69,8 @@
public string LuaPath = ".\\Lua";
public string WatchPath = ".";
public string AVIPath = ".";
public string FFMpegPath = "ffmpeg.exe";
// General Client Settings
public int TargetZoomFactor = 2;

View File

@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Util", "BizHawk.Uti
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.MultiClient", "BizHawk.MultiClient\BizHawk.MultiClient.csproj", "{DD448B37-BA3F-4544-9754-5406E8094723}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoHawk", "DiscoHawk\DiscoHawk.csproj", "{C4366030-6D03-424B-AE53-F4F43BB217C3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -25,10 +27,10 @@ Global
{DD448B37-BA3F-4544-9754-5406E8094723}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD448B37-BA3F-4544-9754-5406E8094723}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD448B37-BA3F-4544-9754-5406E8094723}.Release|Any CPU.Build.0 = Release|Any CPU
{C43F8BCA-CB5F-47CF-A0BD-B32ED49423BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C43F8BCA-CB5F-47CF-A0BD-B32ED49423BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C43F8BCA-CB5F-47CF-A0BD-B32ED49423BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C43F8BCA-CB5F-47CF-A0BD-B32ED49423BA}.Release|Any CPU.Build.0 = Release|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

29
DiscoHawk/DiscoHawk.cs Normal file
View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace BizHawk
{
class DiscoHawk
{
static void Main(string[] args)
{
new DiscoHawk().Run(args);
}
void Run(string[] args)
{
//string testfile = @"d:\Radiant Silvergun (J)\Radiant Silvergun (J).cue";
//var disc = Disc.Disc.FromCuePath(testfile);
//string testfile = @"r:\isos\memtest86-3.2.iso";
//var disc = Disc.Disc.FromIsoPath(testfile);
//Console.WriteLine(disc.ReadTOC().DebugPrint());
//disc.DumpBin_2352("d:\\test.2352");
}
}
}

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{C4366030-6D03-424B-AE53-F4F43BB217C3}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>DiscoHawk</RootNamespace>
<AssemblyName>DiscoHawk</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\BizHawk.MultiClient\output\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<ItemGroup>
<ProjectReference Include="..\BizHawk.Emulation\BizHawk.Emulation.csproj">
<Project>{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}</Project>
<Name>BizHawk.Emulation</Name>
</ProjectReference>
<ProjectReference Include="..\BizHawk.Util\BizHawk.Util.csproj">
<Project>{EE135301-08B3-4EFC-A61C-1C53E1C65CB9}</Project>
<Name>BizHawk.Util</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="DiscoHawk.cs" />
</ItemGroup>
</Project>