diff --git a/BizHawk.Emulation/Disc/CUE_format.cs b/BizHawk.Emulation/Disc/CUE_format.cs index b2ad669526..94e4238a1b 100644 --- a/BizHawk.Emulation/Disc/CUE_format.cs +++ b/BizHawk.Emulation/Disc/CUE_format.cs @@ -4,6 +4,8 @@ using System.Text.RegularExpressions; using System.IO; using System.Collections.Generic; +//this rule is not supported correctly: `The first track number can be greater than one, but all track numbers after the first must be sequential.` + namespace BizHawk.Disc { partial class Disc @@ -17,157 +19,202 @@ namespace BizHawk.Disc var session = new DiscTOC.Session(); session.num = 1; TOC.Sessions.Add(session); + var pregap_sector = new Sector_Zero(); + + int curr_track = 1; - int track_counter = 1; - int curr_lba = 0; foreach (var cue_file in cue.Files) { - //make a raw file blob for the source binfile + //structural validation + if (cue_file.Tracks.Count < 1) throw new Cue.CueBrokenException("`You must specify at least one track per file.`"); + + int blob_sectorsize = Cue.BINSectorSizeForTrackType(cue_file.Tracks[0].TrackType); + + //make a blob for the file 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"); + int blob_length_lba = (int)(blob.Length / blob_sectorsize); + int blob_leftover = (int)(blob.Length - blob_length_lba * blob_sectorsize); - //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++) + //TODO - make CueTimestamp better, and also make it a struct, and also just make it DiscTimestamp + //TODO - wav handling + //TODO - mp3 decode + + //start timekeeping for the blob. every time we hit an index, this will advance + int blob_timestamp = 0; + + //for each track within the file, create an index 0 if it is missing. + //also check to make sure there is an index 1 + for (int t = 0; t < cue_file.Tracks.Count; t++) { - if (cue_file.Tracks[i].TrackType != trackType) throw new Cue.CueBrokenException("cue has different track types per datafile (not supported now; maybe never)"); + var cue_track = cue_file.Tracks[t]; + if (!cue_track.Indexes.ContainsKey(1)) + throw new Cue.CueBrokenException("Track was missing an index 01"); + if (!cue_track.Indexes.ContainsKey(0)) + { + //index 0 will default to the same as index 1. + //i am not sure whether it is valid to have two indexes with the same timestamp. + //we will do this to simplify some processing, but we can purge it in a later pass if we need to. + var cti = new Cue.CueTrackIndex(0); + cue_track.Indexes[0] = cti; + cti.Timestamp = cue_track.Indexes[1].Timestamp; + } } - //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) + //validate that the first index in the file is 00:00:00 + if (cue_file.Tracks[0].Indexes[0].Timestamp.LBA != 0) throw new Cue.CueBrokenException("`The first index of a file must start at 00:00:00.`"); + + + //for each track within the file: + for (int t = 0; t < cue_file.Tracks.Count; t++) { - case Cue.ECueTrackType.Audio: - numlba = (int)(flen / 2352); - leftover = (int)(flen - numlba * 2352); - break; + var cue_track = cue_file.Tracks[t]; - 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; + //record the disc LBA that this sector started on + int track_disc_lba_start = Sectors.Count; - default: throw new InvalidOperationException(); - } + //record the pregap location. it will default to the start of the track unless we supplied a pregap command + int track_disc_pregap_lba = track_disc_lba_start; - 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); + //enforce a rule of our own: every track within the file must have the same sector size + //we do know that files can change between track types within a file, but we're not sure what to do if the sector size changes + if (Cue.BINSectorSizeForTrackType(cue_track.TrackType) != blob_sectorsize) throw new Cue.CueBrokenException("Found different sector sizes within a cue file. We don't know how to handle that."); - 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; + //check integrity of track sequence and setup data structures + //TODO - check for skipped tracks in cue parser instead + if (cue_track.TrackNum != curr_track) throw new Cue.CueBrokenException("Found a cue with skipped tracks"); + var toc_track = new DiscTOC.Track(); + toc_track.num = curr_track; + toc_track.TrackType = cue_track.TrackType; session.Tracks.Add(toc_track); - new_toc_tracks.Add(toc_track); - //analyze indices - int idx; - for (idx = 0; idx <= 99; idx++) + //check whether a pregap is requested. + //when this happens for the first track in a file, some empty sectors are generated + //when it happens for any other track, its just another way of specifying index 0 LBA + if (cue_track.PreGap.LBA > 0) { - 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? + if (t == 0) + for (int i = 0; i < cue_track.PreGap.LBA; i++) + { + Sectors.Add(new SectorEntry(pregap_sector)); + } + else track_disc_pregap_lba -= cue_track.PreGap.LBA; + } - DiscTOC.Index toc_index = new DiscTOC.Index(); - toc_index.num = idx; + //look ahead to the next track's index 0 so we can see how long this track's last index is + //or, for the last track, use the length of the file + int track_length_lba; + if (t == cue_file.Tracks.Count - 1) + track_length_lba = blob_length_lba - blob_timestamp; + else track_length_lba = cue_file.Tracks[t + 1].Indexes[0].Timestamp.LBA - blob_timestamp; + //toc_track.length_lba = track_length_lba; //xxx + + //find out how many indexes we have + int num_indexes = 0; + for (num_indexes = 0; num_indexes <= 99; num_indexes++) + if (!cue_track.Indexes.ContainsKey(num_indexes)) break; + + //for each index, calculate length of index and then emit it + for (int index = 0; index < num_indexes; index++) + { + bool is_last_index = index == num_indexes - 1; + + //install index into hierarchy + var toc_index = new DiscTOC.Index(); + toc_index.num = index; toc_track.Indexes.Add(toc_index); - toc_index.lba = cue_index.Timestamp.LBA + curr_lba; + if (index == 0) toc_index.lba = track_disc_pregap_lba; + else toc_index.lba = Sectors.Count; + + //toc_index.lba += 150; //TODO - consider whether to add 150 here + + //calculate length of the index + //if it is the last index then we use our calculation from before, otherwise we check the next index + int index_length_lba; + if (is_last_index) + index_length_lba = track_disc_lba_start + track_length_lba - blob_timestamp; + else index_length_lba = cue_track.Indexes[index + 1].Timestamp.LBA - blob_timestamp; + + //emit sectors + for (int lba = 0; lba < index_length_lba; lba++) + { + bool is_last_lba_in_index = (lba == index_length_lba-1); + bool is_last_lba_in_track = is_last_lba_in_index && is_last_index; + + switch (cue_track.TrackType) + { + case ETrackType.Audio: //all 2352 bytes are present + case ETrackType.Mode1_2352: //2352 bytes are present, containing 2048 bytes of user data as well as ECM + case ETrackType.Mode2_2352: //2352 bytes are present, containing 2336 bytes of user data, with no ECM + { + //these cases are all 2352 bytes + //in all these cases, either no ECM is present or ECM is provided. + //so we just emit a Sector_Raw + Sector_RawBlob sector_rawblob = new Sector_RawBlob(); + sector_rawblob.Blob = blob; + sector_rawblob.Offset = (long)blob_timestamp * 2352; + Sector_Raw sector_raw = new Sector_Raw(); + sector_raw.BaseSector = sector_rawblob; + //take care to handle final sectors that are too short. + if (is_last_lba_in_track && blob_leftover>0) + { + Sector_ZeroPad sector_zeropad = new Sector_ZeroPad(); + sector_zeropad.BaseSector = sector_rawblob; + sector_zeropad.BaseLength = 2352 - blob_leftover; + sector_raw.BaseSector = sector_zeropad; + Sectors.Add(new SectorEntry(sector_raw)); + } + Sectors.Add(new SectorEntry(sector_raw)); + break; + } + case ETrackType.Mode1_2048: + //2048 bytes are present. ECM needs to be generated to create a full sector + { + //ECM needs to know the sector number so we have to record that here + int curr_disc_lba = Sectors.Count; + var sector_2048 = new Sector_Mode1_2048(curr_disc_lba + 150); + sector_2048.Blob = new ECMCacheBlob(blob); + sector_2048.Offset = (long)blob_timestamp * 2048; + if (blob_leftover > 0) throw new Cue.CueBrokenException("TODO - Incomplete 2048 byte/sector bin files (iso files) not yet supported."); + break; + } + } //switch(TrackType) + + //we've emitted an LBA, so consume it from the blob + blob_timestamp++; + + } //lba emit loop + + } //index loop + + //check whether a postgap is requested. if it is, we need to generate silent sectors + for (int i = 0; i < cue_track.PostGap.LBA; i++) + { + Sectors.Add(new SectorEntry(pregap_sector)); } - //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"); - } + //we're done with the track now. + //record its length: + toc_track.length_lba = Sectors.Count - track_disc_lba_start; + curr_track++; + } //track loop + } //file 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(); + //finally, analyze the length of the sessions and the entire disc by summing the lengths of the tracks + //this is a little more complex than it looks, because the length of a thing is not determined by summing it + //but rather by the difference in lbas between start and end + TOC.length_lba = 0; + foreach (var toc_session in TOC.Sessions) + { + var firstTrack = toc_session.Tracks[0]; + var lastTrack = toc_session.Tracks[toc_session.Tracks.Count - 1]; + session.length_lba = lastTrack.Indexes[1].lba + lastTrack.length_lba - firstTrack.Indexes[0].lba; + TOC.length_lba += toc_session.length_lba; + } } } @@ -205,23 +252,69 @@ namespace BizHawk.Disc public List Files = new List(); - public enum ECueTrackType + public static int BINSectorSizeForTrackType(ETrackType type) { - Mode1_2352, - Mode1_2048, - Mode2_2352, - Audio + switch(type) + { + case ETrackType.Mode1_2352: + case ETrackType.Mode2_2352: + case ETrackType.Audio: + return 2352; + case ETrackType.Mode1_2048: + return 2048; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public static string TrackTypeStringForTrackType(ETrackType type) + { + switch (type) + { + case ETrackType.Mode1_2352: return "MODE1/2352"; + case ETrackType.Mode2_2352: return "MODE2/2352"; + case ETrackType.Audio: return "AUDIO"; + case ETrackType.Mode1_2048: return "MODE1/2048"; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public static string RedumpTypeStringForTrackType(ETrackType type) + { + switch (type) + { + case ETrackType.Mode1_2352: return "Data/Mode 1"; + case ETrackType.Mode1_2048: throw new InvalidOperationException("guh dunno what to put here"); + case ETrackType.Mode2_2352: return "Data/Mode 2"; + case ETrackType.Audio: return "Audio"; + default: + throw new ArgumentOutOfRangeException(); + } } public class CueTrack { - public ECueTrackType TrackType; + public ETrackType TrackType; public int TrackNum; + public CueTimestamp PreGap = new CueTimestamp(); + public CueTimestamp PostGap = new CueTimestamp(); public Dictionary Indexes = new Dictionary(); } public class CueTimestamp { + /// + /// creates timestamp of 00:00:00 + /// + public CueTimestamp() + { + Value = "00:00:00"; + } + + /// + /// creates a timestamp from a string in the form mm:ss:ff + /// public CueTimestamp(string value) { this.Value = value; MIN = int.Parse(value.Substring(0, 2)); @@ -231,12 +324,26 @@ namespace BizHawk.Disc } public readonly string Value; public readonly int MIN, SEC, FRAC, LBA; + + /// + /// creates timestamp from supplied LBA + /// + public CueTimestamp(int LBA) + { + this.LBA = LBA; + MIN = LBA / (60*75); + SEC = (LBA / 75)%60; + FRAC = LBA % 75; + Value = string.Format("{0:D2}:{1:D2}:{2:D2}", MIN, SEC, FRAC); + } } public class CueTrackIndex { + public CueTrackIndex(int num) { IndexNum = num; } public int IndexNum; public CueTimestamp Timestamp; + public int ZeroLBA; } public class CueBrokenException : Exception @@ -254,6 +361,9 @@ namespace BizHawk.Disc File.ReadAllText(cuePath); TextReader tr = new StreamReader(cuePath); + bool track_has_pregap = false; + bool track_has_postgap = false; + int last_index_num = -1; CueFile currFile = null; CueTrack currTrack = null; int state = 0; @@ -294,44 +404,60 @@ namespace BizHawk.Disc if (!int.TryParse(strtracknum, out tracknum)) throw new CueBrokenException("malformed track number"); if (clp.EOF) throw new CueBrokenException("invalid cue structure"); + if (tracknum < 0 || tracknum > 99) throw new CueBrokenException("`All track numbers must be between 1 and 99 inclusive.`"); 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; + case "MODE1/2352": currTrack.TrackType = ETrackType.Mode1_2352; break; + case "MODE1/2048": currTrack.TrackType = ETrackType.Mode1_2048; break; + case "MODE2/2352": currTrack.TrackType = ETrackType.Mode2_2352; break; + case "AUDIO": currTrack.TrackType = ETrackType.Audio; break; default: throw new CueBrokenException("unhandled track type"); } currTrack.TrackNum = tracknum; currFile.Tracks.Add(currTrack); + track_has_pregap = false; + track_has_postgap = false; + last_index_num = -1; break; } case "INDEX": { if (currTrack == null) throw new CueBrokenException("invalid cue structure"); if (clp.EOF) throw new CueBrokenException("invalid cue structure"); + if (track_has_postgap) throw new CueBrokenException("`The POSTGAP command must appear after all INDEX commands for the current track.`"); 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); ; + if(indexnum <0 || indexnum>99) throw new CueBrokenException("`All index numbers must be between 0 and 99 inclusive.`"); + if (indexnum != 1 && indexnum != last_index_num + 1) throw new CueBrokenException("`The first index must be 0 or 1 with all other indexes being sequential to the first one.`"); + last_index_num = indexnum; + CueTrackIndex cti = new CueTrackIndex(indexnum); + cti.Timestamp = new CueTimestamp(str_timestamp); cti.IndexNum = indexnum; currTrack.Indexes[indexnum] = cti; break; } case "PREGAP": + if (track_has_pregap) throw new CueBrokenException("`Only one PREGAP command is allowed per track.`"); + if (currTrack.Indexes.Count > 0) throw new CueBrokenException("`The PREGAP command must appear after a TRACK command, but before any INDEX commands.`"); + currTrack.PreGap = new CueTimestamp(clp.ReadToken()); + track_has_pregap = true; + break; case "POSTGAP": - throw new CueBrokenException("cue postgap/pregap command not supported yet"); + if (track_has_pregap) throw new CueBrokenException("`Only one POSTGAP command is allowed per track.`"); + track_has_postgap = true; + currTrack.PostGap = new CueTimestamp(clp.ReadToken()); + break; default: throw new CueBrokenException("unsupported cue command: " + key); } - } + } //end cue parsing loop } diff --git a/BizHawk.Emulation/Disc/Disc.cs b/BizHawk.Emulation/Disc/Disc.cs index 26cbc9abe3..ecf0e58287 100644 --- a/BizHawk.Emulation/Disc/Disc.cs +++ b/BizHawk.Emulation/Disc/Disc.cs @@ -14,6 +14,9 @@ using System.Collections.Generic; //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) +//here is a featureset list of windows cd burning programs (useful for cuesheet compatibility info) +//http://www.dcsoft.com/cue_mastering_progs.htm + //good //http://linux-sxs.org/bedtime/cdapi.html //http://en.wikipedia.org/wiki/Track_%28CD%29 @@ -65,7 +68,6 @@ using System.Collections.Generic; 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) @@ -83,10 +85,20 @@ namespace BizHawk.Disc class Blob_RawFile : IBlob { - public string PhysicalPath; + public string PhysicalPath { + get { return physicalPath; } + set + { + physicalPath = value; + length = new FileInfo(physicalPath).Length; + } + } + string physicalPath; + long length; + public long Offset; - FileStream fs; + BufferedStream fs; public void Dispose() { if (fs != null) @@ -97,13 +109,22 @@ namespace BizHawk.Disc } public int Read(long byte_pos, byte[] buffer, int offset, int count) { + //use quite a large buffer, because normally we will be reading these sequentially + const int buffersize = 2352 * 75 * 2; if (fs == null) - fs = new FileStream(PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.Read); + fs = new BufferedStream(new FileStream(physicalPath, FileMode.Open, FileAccess.Read, FileShare.Read), buffersize); long target = byte_pos + Offset; if(fs.Position != target) fs.Position = target; return fs.Read(buffer, offset, count); } + public long Length + { + get + { + return length; + } + } } class Sector_RawSilence : ISector @@ -125,6 +146,16 @@ namespace BizHawk.Disc } } + class Sector_Zero : ISector + { + public int Read(byte[] buffer, int offset) + { + for (int i = 0; i < 2352; i++) + buffer[offset + i] = 0; + return 2352; + } + } + class Sector_ZeroPad : ISector { public ISector BaseSector; @@ -229,6 +260,7 @@ namespace BizHawk.Disc //this is a physical 2352 byte sector. public class SectorEntry { + public SectorEntry(ISector sec) { this.Sector = sec; } public ISector Sector; } @@ -256,7 +288,7 @@ namespace BizHawk.Disc blob.PhysicalPath = fiIso.FullName; Blobs.Add(blob); int num_lba = (int)(fiIso.Length / 2048); - index.length_lba = num_lba; + //index.length_lba = num_lba; if (fiIso.Length % 2048 != 0) throw new InvalidOperationException("invalid iso file (size not multiple of 2048)"); @@ -266,21 +298,76 @@ namespace BizHawk.Disc 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); + Sectors.Add(new SectorEntry(sector)); } TOC.AnalyzeLengthsFromIndexLengths(); } + + public CueBin DumpCueBin(string baseName, CueBinPrefs prefs) + { + if (TOC.Sessions.Count > 1) + throw new NotSupportedException("can't dump cue+bin with more than 1 session yet"); + + CueBin ret = new CueBin(); + ret.baseName = baseName; + ret.disc = this; + + if (!prefs.OneBinPerTrack) + { + string cue = TOC.GenerateCUE(prefs); + var bfd = new CueBin.BinFileDescriptor(); + bfd.name = baseName + ".bin"; + ret.cue = string.Format("FILE \"{0}\" BINARY\n", bfd.name) + cue; + ret.bins.Add(bfd); + for (int i = 0; i < TOC.length_lba; i++) + { + bfd.lbas.Add(i+150); + bfd.lba_zeros.Add(false); + } + } + else + { + StringBuilder sbCue = new StringBuilder(); + + for (int i = 0; i < TOC.Sessions[0].Tracks.Count; i++) + { + var track = TOC.Sessions[0].Tracks[i]; + var bfd = new CueBin.BinFileDescriptor(); + bfd.name = baseName + string.Format(" (Track {0:D2}).bin", track.num); + ret.bins.Add(bfd); + int lba=0; + + for (; lba < track.length_lba; lba++) + { + int thislba = track.Indexes[0].lba + lba; + bfd.lbas.Add(thislba + 150); + bfd.lba_zeros.Add(false); + } + sbCue.AppendFormat("FILE \"{0}\" BINARY\n", bfd.name); + + sbCue.AppendFormat(" TRACK {0:D2} {1}\n", track.num, Cue.TrackTypeStringForTrackType(track.TrackType)); + foreach (var index in track.Indexes) + { + int x = index.lba - track.Indexes[0].lba; + sbCue.AppendFormat(" INDEX {0:D2} {1}\n", index.num, new Cue.CueTimestamp(x).Value); + } + } + + ret.cue = sbCue.ToString(); + } + + return ret; + } + 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); + ReadLBA_2352(150+i, temp, 0); fs.Write(temp, 0, 2352); } } @@ -300,4 +387,129 @@ namespace BizHawk.Disc } } + public enum ETrackType + { + Mode1_2352, + Mode1_2048, + Mode2_2352, + Audio + } + + + public class CueBinPrefs + { + /// + /// Controls general operations: should the output be split into several bins, or just use one? + /// + public bool OneBinPerTrack; + + /// + /// turn this on to dump bins instead of just cues + /// + public bool ReallyDumpBin; + + /// + /// generate remarks and other annotations to help humans understand whats going on, but which will confuse many cue parsers + /// + public bool AnnotateCue; + + /// + /// you may find that some cue parsers are upset by index 00 + /// if thats the case, then we can emit pregaps instead. + /// you might also want to use this to save disk space (without pregap commands, the pregap must be stored as empty sectors) + /// + public bool PreferPregapCommand = false; + + /// + /// some cue parsers cant handle sessions. better not emit a session command then. multi-session discs will then be broken + /// + public bool SingleSession; + } + + /// + /// Encapsulates an in-memory cue+bin (complete cuesheet and a little registry of files) + /// it will be based on a disc (fro mwhich it can read sectors to avoid burning through extra memory) + /// TODO - we must merge this with whatever reads in cue+bin + /// + public class CueBin + { + public string cue; + public string baseName; + public Disc disc; + + public class BinFileDescriptor + { + public string name; + public List lbas = new List(); + public List lba_zeros = new List(); + } + + public List bins = new List(); + + public string CreateRedumpReport() + { + if (disc.TOC.Sessions[0].Tracks.Count != bins.Count) + throw new InvalidOperationException("Cannot generate redump report on CueBin lacking OneBinPerTrack property"); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < disc.TOC.Sessions[0].Tracks.Count; i++) + { + var track = disc.TOC.Sessions[0].Tracks[i]; + var bfd = bins[i]; + + //dump the track + byte[] dump = new byte[track.length_lba * 2352]; + for (int lba = 0; lba < track.length_lba; lba++) + disc.ReadLBA_2352(bfd.lbas[lba],dump,lba*2352); + string crc32 = string.Format("{0:X8}", CRC32.Calculate(dump)); + string md5 = Util.Hash_MD5(dump, 0, dump.Length); + string sha1 = Util.Hash_SHA1(dump, 0, dump.Length); + + int pregap = track.Indexes[1].lba - track.Indexes[0].lba; + Cue.CueTimestamp pregap_ts = new Cue.CueTimestamp(pregap); + Cue.CueTimestamp len_ts = new Cue.CueTimestamp(track.length_lba); + sb.AppendFormat("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}\t{8}\n", + i, + Cue.RedumpTypeStringForTrackType(track.TrackType), + pregap_ts.Value, + len_ts.Value, + track.length_lba, + track.length_lba*Cue.BINSectorSizeForTrackType(track.TrackType), + crc32, + md5, + sha1 + ); + } + return sb.ToString(); + } + + public void Dump(string directory, CueBinPrefs prefs) + { + string cuePath = Path.Combine(directory, baseName + ".cue"); + File.WriteAllText(cuePath, cue); + if(prefs.ReallyDumpBin) + foreach (var bfd in bins) + { + byte[] temp = new byte[2352]; + byte[] empty = new byte[2352]; + string trackBinFile = bfd.name; + string trackBinPath = Path.Combine(directory, trackBinFile); + using (FileStream fs = new FileStream(trackBinPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + for(int i=0;i Indexes = new List(); //the length of the track (should be the sum of all index lengths) public int length_lba; + public Cue.CueTimestamp FriendlyLength { get { return new Cue.CueTimestamp(length_lba); } } } public class Index @@ -33,29 +36,33 @@ namespace BizHawk.Disc public int lba; //the length of the section - public int length_lba; + //public int length_lba; + //public Cue.CueTimestamp FriendlyLength { get { return new Cue.CueTimestamp(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() + public string GenerateCUE(CueBinPrefs prefs) { StringBuilder sb = new StringBuilder(); foreach (var session in Sessions) { - sb.AppendFormat("SESSION {0:D2} (length={1})\n", session.num, session.length_lba); + if (!prefs.SingleSession) + { + //dont want to screw around with sessions for now + if (prefs.AnnotateCue) sb.AppendFormat("SESSION {0:D2} (length={1})\n", session.num, session.length_lba); + else sb.AppendFormat("SESSION {0:D2}\n", session.num); + } foreach (var track in session.Tracks) { - sb.AppendFormat(" TRACK {0:D2} (length={1})\n", track.num, track.length_lba); + if (prefs.AnnotateCue) sb.AppendFormat(" TRACK {0:D2} {1} (length={2})\n", track.num, Cue.TrackTypeStringForTrackType(track.TrackType), track.length_lba); + else sb.AppendFormat(" TRACK {0:D2} {1}\n", track.num, Cue.TrackTypeStringForTrackType(track.TrackType)); foreach (var index in track.Indexes) { - sb.AppendFormat(" INDEX {0:D2}: {1}\n", index.num, FormatLBA(index.lba)); + //if (prefs.PreferPregapCommand && index.num == 0) + // sb.AppendFormat(" PREGAP {0}\n", new Cue.CueTimestamp(index.length_lba).Value); + //else + sb.AppendFormat(" INDEX {0:D2} {1}\n", index.num, new Cue.CueTimestamp(index.lba).Value); } } - sb.AppendFormat("-EOF-\n"); } return sb.ToString(); @@ -66,17 +73,15 @@ namespace BizHawk.Disc public void AnalyzeLengthsFromIndexLengths() { + //this is a little more complex than it looks, because the length of a thing is not determined by summing it + //but rather by the difference in lbas between start and end + length_lba = 0; 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; - } + var firstTrack = session.Tracks[0]; + var lastTrack = session.Tracks[session.Tracks.Count - 1]; + session.length_lba = lastTrack.Indexes[0].lba + lastTrack.length_lba - firstTrack.Indexes[0].lba; + length_lba += session.length_lba; } } } diff --git a/BizHawk.Emulation/Disc/ECM.cs b/BizHawk.Emulation/Disc/ECM.cs index f8027b0f20..500fbf3488 100644 --- a/BizHawk.Emulation/Disc/ECM.cs +++ b/BizHawk.Emulation/Disc/ECM.cs @@ -65,7 +65,7 @@ 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; + //byte i; /* Save the address and zero it out */ if(zeroaddress) { diff --git a/BizHawk.Emulation/Disc/FFmpeg.cs b/BizHawk.Emulation/Disc/FFmpeg.cs index bbd84257ff..1a500ce165 100644 --- a/BizHawk.Emulation/Disc/FFmpeg.cs +++ b/BizHawk.Emulation/Disc/FFmpeg.cs @@ -1,426 +1,373 @@ -////http://jasonjano.wordpress.com/2010/02/09/a-simple-c-wrapper-for-ffmpeg/ +//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; +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; +namespace ffMpeg +{ + public class Converter + { + public static string _ffExe; + public string WorkingPath; //i.e. temp -// //i.e. temp -// public string WorkingPath; + public Converter() + { + Initialize(); + } -// #endregion + #region Initialization + private void Initialize() + { + } -// #region Constructors -// public Converter() -// { -// Initialize(); -// } -// public Converter(string ffmpegExePath) -// { -// _ffExe = ffmpegExePath; -// Initialize(); -// } -// #endregion + private string GetWorkingFile() + { + //try the stated directory + if (File.Exists(_ffExe)) + { + return _ffExe; + } -// #region Initialization -// private void Initialize() -// { -// } + //oops, that didn't work, try the base directory + if (File.Exists(Path.GetFileName(_ffExe))) + { + return Path.GetFileName(_ffExe); + } -// private string GetWorkingFile() -// { -// //try the stated directory -// if (File.Exists(_ffExe)) -// { -// return _ffExe; -// } + //well, now we are really unlucky, let's just return null + return null; + } + #endregion -// //oops, that didn't work, try the base directory -// if (File.Exists(Path.GetFileName(_ffExe))) -// { -// return Path.GetFileName(_ffExe); -// } + #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; + } -// //well, now we are really unlucky, let's just return null -// return null; -// } -// #endregion + 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 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; -// } + #region Run the process + private string RunProcess(string Parameters) + { + //create a process info + ProcessStartInfo oInfo = new ProcessStartInfo(_ffExe, Parameters); + oInfo.UseShellExecute = false; + oInfo.CreateNoWindow = true; + oInfo.RedirectStandardOutput = true; + oInfo.RedirectStandardError = true; -// 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 + //Create the output and streamreader to get the output + string output = null; StreamReader srOutput = null; -// #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; + //try the process + try + { + //run the process + Process proc = System.Diagnostics.Process.Start(oInfo); -// //Create the output and streamreader to get the output -// string output = null; StreamReader srOutput = null; + proc.WaitForExit(); -// //try the process -// try -// { -// //run the process -// Process proc = System.Diagnostics.Process.Start(oInfo); + //get the output + srOutput = proc.StandardError; -// proc.WaitForExit(); + //now put it in a string + output = srOutput.ReadToEnd(); -// //get the output -// srOutput = proc.StandardError; + 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 -// //now put it in a string -// output = srOutput.ReadToEnd(); + #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(); -// 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 + VideoFile vf = null; + try + { + vf = new VideoFile(tempfile); + } + catch (Exception ex) + { + throw ex; + } -// #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(); + GetVideoInfo(vf); -// VideoFile vf = null; -// try -// { -// vf = new VideoFile(tempfile); -// } -// catch (Exception ex) -// { -// throw ex; -// } + try + { + File.Delete(tempfile); + } + catch (Exception) + { -// 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); -// 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; + 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 duration -// Regex re = new Regex("[D|d]uration:.((\\d|:|\\.)*)"); -// Match m = re.Match(input.RawInfo); + //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; -// 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 the audio format + re = new Regex("[A|a]udio:.*"); + m = re.Match(input.RawInfo); + if (m.Success) + { + input.AudioFormat = m.Value; + } -// //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 video format + re = new Regex("[V|v]ideo:.*"); + m = re.Match(input.RawInfo); + if (m.Success) + { + input.VideoFormat = m.Value; + } -// //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("(\\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 -// //get the video format -// re = new Regex("[V|v]ideo:.*"); -// m = re.Match(input.RawInfo); -// if (m.Success) -// { -// input.VideoFormat = m.Value; -// } + #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(); -// //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 + VideoFile vf = null; + try + { + vf = new VideoFile(tempfile); + } + catch (Exception ex) + { + throw ex; + } -// #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(); + OutputPackage oo = ConvertToFLV(vf); -// VideoFile vf = null; -// try -// { -// vf = new VideoFile(tempfile); -// } -// catch (Exception ex) -// { -// throw ex; -// } + try + { + File.Delete(tempfile); + } + catch (Exception) + { -// 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(); -// return oo; -// } -// public OutputPackage ConvertToFLV(string inputPath) -// { -// VideoFile vf = null; -// try -// { -// vf = new VideoFile(inputPath); -// } -// catch (Exception ex) -// { -// throw ex; -// } + string filename = System.Guid.NewGuid().ToString() + ".flv"; + string finalpath = Path.Combine(this.WorkingPath, filename); + string Params = string.Format("-i \"{0}\" -y -ar 22050 -ab 64 -f flv \"{1}\"", input.Path, finalpath); + string output = RunProcess(Params); -// OutputPackage oo = ConvertToFLV(vf); -// return oo; -// } -// public OutputPackage ConvertToFLV(VideoFile input) -// { -// if (!input.infoGathered) -// { -// GetVideoInfo(input); -// } -// OutputPackage ou = new OutputPackage(); + if (File.Exists(finalpath)) + { + ou.VideoStream = LoadMemoryStreamFromFile(finalpath); + try + { + File.Delete(finalpath); + } + catch (Exception) { } + } + return ou; + } + #endregion + } -// //set up the parameters for getting a previewimage -// string filename = System.Guid.NewGuid().ToString() + ".jpg"; -// int secs; + public class VideoFile + { + #region Properties + private string _Path; + public string Path + { + get + { + return _Path; + } + set + { + _Path = value; + } + } -// //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); + 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 -// 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); + #region Constructors + public VideoFile(string path) + { + _Path = path; + Initialize(); + } + #endregion -// ou.RawOutput = output; + #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"); + } -// 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); + //Now see if the video file exists + if (!File.Exists(_Path)) + { + throw new Exception("The video file " + _Path + " does not exist."); + } + } + #endregion + } -// 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; } -// } -//} + 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.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index 096adfe80b..ff3e835ba0 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -73,14 +73,14 @@ namespace BizHawk.MultiClient //we could background thread this later instead if we wanted to be real clever NES.BootGodDB.GetDatabaseBytes = () => { - using (HawkFile NesCartFile = new HawkFile(PathManager.GetExePathAbsolute() + "\\NesCarts.7z").BindFirst()) + using (HawkFile NesCartFile = new HawkFile(PathManager.GetExeDirectoryAbsolute() + "\\NesCarts.7z").BindFirst()) return Util.ReadAllBytes(NesCartFile.GetStream()); }; Global.MainForm = this; Global.CoreInputComm = new CoreInputComm(); SyncCoreInputComm(); - Database.LoadDatabase(PathManager.GetExePathAbsolute() + "\\gamedb.txt"); + Database.LoadDatabase(PathManager.GetExeDirectoryAbsolute() + "\\gamedb.txt"); SyncPresentationMode(); diff --git a/DiscoHawk/DiscoHawk.cs b/DiscoHawk/DiscoHawk.cs index cb0d449129..64a7dc6ae2 100644 --- a/DiscoHawk/DiscoHawk.cs +++ b/DiscoHawk/DiscoHawk.cs @@ -2,6 +2,17 @@ using System.Collections.Generic; using System.IO; +//cue format preferences notes + +//pcejin - +//does not like session commands +//it can handle binpercue +//it seems not to be able to handle binpertrack, or maybe i am doing something wrong (still havent ruled it out) + +//isobuster - +//does not like onebinpertrack images with index 00 + + namespace BizHawk { class DiscoHawk @@ -13,8 +24,41 @@ namespace BizHawk void Run(string[] args) { - //string testfile = @"d:\Radiant Silvergun (J)\Radiant Silvergun (J).cue"; - //var disc = Disc.Disc.FromCuePath(testfile); + //string exedir = BizHawk.MultiClient.PathManager.GetExeDirectoryAbsolute(); + //ffMpeg.Converter._ffExe = Path.Combine(exedir, "ffmpeg.exe"); + + ////cue+bin+mp3 tests + //var conv = new ffMpeg.Converter(); + ////conv.WorkingPath = Environment.GetEnvironmentVariable("TEMP"); + ////var vf = conv.GetVideoInfo(@"D:\isos\scd\persia\Prince of Persia 01.mp3"); + //var o = conv.ConvertToFLV(@"D:\isos\scd\persia\Prince of Persia 01.mp3"); + + ////-i mp3file.mp3 -f wav outfile.wav + + + //Disc.CueBin munged; + //Disc.CueBinPrefs prefs = new Disc.CueBinPrefs(); + //prefs.SingleSession = true; + //Disc.Disc disc; + + //string testfile = @"D:\isos\pcecd\cosmicfantasy2\Cosmic Fantasy II [U][CD][WTG990301][Telenet Japan][1992][PCE][thx-1138-darkwater].cue"; + //disc = Disc.Disc.FromCuePath(testfile); + //prefs.ReallyDumpBin = true; + //prefs.AnnotateCue = false; + //prefs.OneBinPerTrack = true; + //prefs.PreferPregapCommand = false; + //munged = disc.DumpCueBin("test", prefs); + //munged.Dump("d:\\test", prefs); + //File.WriteAllText("d:\\test\\redump.txt", munged.CreateRedumpReport()); + + //try roundtripping back to one file + //disc = Disc.Disc.FromCuePath("d:\\test\\test.cue"); + //prefs.ReallyDumpBin = false; + //prefs.OneBinPerTrack = false; + //prefs.PreferPregapCommand = true; + //munged = disc.DumpCueBin("one", prefs); + //munged.Dump("d:\\test", prefs); + //string testfile = @"r:\isos\memtest86-3.2.iso"; //var disc = Disc.Disc.FromIsoPath(testfile); @@ -22,7 +66,7 @@ namespace BizHawk //Console.WriteLine(disc.ReadTOC().DebugPrint()); //disc.DumpBin_2352("d:\\test.2352"); - //test reading the subcode data. unfortunately we don't have lead-in subcode so we have no TOC + ////test reading the subcode data. unfortunately we don't have lead-in subcode so we have no TOC //using (FileStream fs = File.OpenRead("c:\\bof4.sub")) //{ // Disc.SubcodeStream stream = new Disc.SubcodeStream(fs, 0); diff --git a/DiscoHawk/DiscoHawk.csproj b/DiscoHawk/DiscoHawk.csproj index 4ecd138f4f..f8dbb92add 100644 --- a/DiscoHawk/DiscoHawk.csproj +++ b/DiscoHawk/DiscoHawk.csproj @@ -45,6 +45,10 @@ {197D4314-8A9F-49BA-977D-54ACEFAEB6BA} BizHawk.Emulation + + {DD448B37-BA3F-4544-9754-5406E8094723} + BizHawk.MultiClient + {EE135301-08B3-4EFC-A61C-1C53E1C65CB9} BizHawk.Util