diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index e2b21affbb..b5710082a9 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -3,7 +3,7 @@ Debug AnyCPU - 9.0.21022 + 9.0.30729 2.0 {197D4314-8A9F-49BA-977D-54ACEFAEB6BA} Library @@ -44,6 +44,8 @@ 3.5 + + 3.5 @@ -159,6 +161,13 @@ + + + + + + + diff --git a/BizHawk.Emulation/Disc/CCD_format.cs b/BizHawk.Emulation/Disc/CCD_format.cs new file mode 100644 index 0000000000..10f40fc5d4 --- /dev/null +++ b/BizHawk.Emulation/Disc/CCD_format.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Disc +{ + //TBD CCD format + public class CCDFormat + { + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Disc/CUE_format.cs b/BizHawk.Emulation/Disc/CUE_format.cs new file mode 100644 index 0000000000..b2ad669526 --- /dev/null +++ b/BizHawk.Emulation/Disc/CUE_format.cs @@ -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 new_toc_tracks = new List(); + 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 Tracks = new List(); + } + + public List Files = new List(); + + public enum ECueTrackType + { + Mode1_2352, + Mode1_2048, + Mode2_2352, + Audio + } + + public class CueTrack + { + public ECueTrackType TrackType; + public int TrackNum; + public Dictionary Indexes = new Dictionary(); + } + + 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); + } + + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Disc/Disc.cs b/BizHawk.Emulation/Disc/Disc.cs new file mode 100644 index 0000000000..ae1c65a383 --- /dev/null +++ b/BizHawk.Emulation/Disc/Disc.cs @@ -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 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(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; + } + } + +} \ No newline at end of file diff --git a/BizHawk.Emulation/Disc/DiscTOC.cs b/BizHawk.Emulation/Disc/DiscTOC.cs new file mode 100644 index 0000000000..475a8157f0 --- /dev/null +++ b/BizHawk.Emulation/Disc/DiscTOC.cs @@ -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 Tracks = new List(); + + //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 Indexes = new List(); + + //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 Sessions = new List(); + 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; + } + } + } + } + +} \ No newline at end of file diff --git a/BizHawk.Emulation/Disc/ECM.cs b/BizHawk.Emulation/Disc/ECM.cs new file mode 100644 index 0000000000..f8027b0f20 --- /dev/null +++ b/BizHawk.Emulation/Disc/ECM.cs @@ -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; + } + } + } + +} \ No newline at end of file diff --git a/BizHawk.Emulation/Disc/FFmpeg.cs b/BizHawk.Emulation/Disc/FFmpeg.cs new file mode 100644 index 0000000000..bbd84257ff --- /dev/null +++ b/BizHawk.Emulation/Disc/FFmpeg.cs @@ -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; } +// } +//} diff --git a/BizHawk.Emulation/Disc/TOC_format.cs b/BizHawk.Emulation/Disc/TOC_format.cs new file mode 100644 index 0000000000..befd865dc7 --- /dev/null +++ b/BizHawk.Emulation/Disc/TOC_format.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Disc +{ + //TBD TOC format + public class TOCFormat + { + } +} \ No newline at end of file diff --git a/BizHawk.MultiClient/Config.cs b/BizHawk.MultiClient/Config.cs index e0a213d0c4..0016c94998 100644 --- a/BizHawk.MultiClient/Config.cs +++ b/BizHawk.MultiClient/Config.cs @@ -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; diff --git a/BizHawk.sln b/BizHawk.sln index 06187e112b..71b4354095 100644 --- a/BizHawk.sln +++ b/BizHawk.sln @@ -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 diff --git a/DiscoHawk/DiscoHawk.cs b/DiscoHawk/DiscoHawk.cs new file mode 100644 index 0000000000..8237006ba5 --- /dev/null +++ b/DiscoHawk/DiscoHawk.cs @@ -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"); + + + } + } + +} \ No newline at end of file diff --git a/DiscoHawk/DiscoHawk.csproj b/DiscoHawk/DiscoHawk.csproj new file mode 100644 index 0000000000..4ecd138f4f --- /dev/null +++ b/DiscoHawk/DiscoHawk.csproj @@ -0,0 +1,56 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {C4366030-6D03-424B-AE53-F4F43BB217C3} + Exe + Properties + DiscoHawk + DiscoHawk + v3.5 + 512 + + + true + full + false + ..\BizHawk.MultiClient\output\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {197D4314-8A9F-49BA-977D-54ACEFAEB6BA} + BizHawk.Emulation + + + {EE135301-08B3-4EFC-A61C-1C53E1C65CB9} + BizHawk.Util + + + + + + \ No newline at end of file