using System; using System.Runtime.InteropServices; using System.Reflection; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.IO; using BizHawk.Emulation.DiscSystem; // 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 haven't ruled it out) namespace BizHawk.Client.DiscoHawk { static class Program { static Program() { #if WINDOWS // http://www.codeproject.com/Articles/310675/AppDomain-AssemblyResolve-Event-Tips // this will look in subdirectory "dll" to load pinvoked stuff string dllDir = Path.Combine(GetExeDirectoryAbsolute(), "dll"); SetDllDirectory(dllDir); //in case assembly resolution fails, such as if we moved them into the dll subdirectory, this event handler can reroute to them AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; // but before we even try doing that, whack the MOTW from everything in that directory (that's a dll) // otherwise, some people will have crashes at boot-up due to .net security disliking MOTW. // some people are getting MOTW through a combination of browser used to download BizHawk, and program used to dearchive it WhackAllMOTW(dllDir); #endif } [STAThread] static void Main(string[] args) { SubMain(args); } // NoInlining should keep this code from getting jammed into Main() which would create dependencies on types which haven't been setup by the resolver yet... or something like that [DllImport("user32.dll", SetLastError = true)] public static extern bool ChangeWindowMessageFilterEx(IntPtr hWnd, uint msg, ChangeWindowMessageFilterExAction action, ref CHANGEFILTERSTRUCT changeInfo); private static class Win32 { [DllImport("kernel32.dll")] public static extern IntPtr LoadLibrary(string dllToLoad); [DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); [DllImport("kernel32.dll")] public static extern bool FreeLibrary(IntPtr hModule); } static void SubMain(string[] args) { // MICROSOFT BROKE DRAG AND DROP IN WINDOWS 7. IT DOESN'T WORK ANYMORE // WELL, OBVIOUSLY IT DOES SOMETIMES. I DON'T REMEMBER THE DETAILS OR WHY WE HAD TO DO THIS SHIT #if WINDOWS // BUT THE FUNCTION WE NEED DOESN'T EXIST UNTIL WINDOWS 7, CONVENIENTLY // SO CHECK FOR IT IntPtr lib = Win32.LoadLibrary("user32.dll"); IntPtr proc = Win32.GetProcAddress(lib, "ChangeWindowMessageFilterEx"); if (proc != IntPtr.Zero) { ChangeWindowMessageFilter(WM_DROPFILES, ChangeWindowMessageFilterFlags.Add); ChangeWindowMessageFilter(WM_COPYDATA, ChangeWindowMessageFilterFlags.Add); ChangeWindowMessageFilter(0x0049, ChangeWindowMessageFilterFlags.Add); } Win32.FreeLibrary(lib); #endif var ffmpegPath = Path.Combine(GetExeDirectoryAbsolute(), "ffmpeg.exe"); if (!File.Exists(ffmpegPath)) ffmpegPath = Path.Combine(Path.Combine(GetExeDirectoryAbsolute(), "dll"), "ffmpeg.exe"); FFMpeg.FFMpegPath = ffmpegPath; AudioExtractor.FFmpegPath = ffmpegPath; new DiscoHawk().Run(args); } public static string GetExeDirectoryAbsolute() { var uri = new Uri(Assembly.GetEntryAssembly().GetName().CodeBase); string module = uri.LocalPath + System.Web.HttpUtility.UrlDecode(uri.Fragment); return Path.GetDirectoryName(module); } static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { lock (AppDomain.CurrentDomain) { var asms = AppDomain.CurrentDomain.GetAssemblies(); foreach (var asm in asms) if (asm.FullName == args.Name) return asm; //load missing assemblies by trying to find them in the dll directory string dllName = $"{new AssemblyName(args.Name).Name}.dll"; string directory = Path.Combine(GetExeDirectoryAbsolute(), "dll"); string fname = Path.Combine(directory, dllName); if (!File.Exists(fname)) return null; // it is important that we use LoadFile here and not load from a byte array; otherwise mixed (managed/unmanaged) assemblies can't load return Assembly.LoadFile(fname); } } //declared here instead of a more usual place to avoid dependencies on the more usual place #if WINDOWS [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetDllDirectory(string lpPathName); [DllImport("kernel32.dll", EntryPoint = "DeleteFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] static extern bool DeleteFileW([MarshalAs(UnmanagedType.LPWStr)]string lpFileName); static void RemoveMOTW(string path) { DeleteFileW($"{path}:Zone.Identifier"); } static void WhackAllMOTW(string dllDir) { var todo = new Queue(new[] { new DirectoryInfo(dllDir) }); while (todo.Count > 0) { var di = todo.Dequeue(); foreach (var diSub in di.GetDirectories()) todo.Enqueue(diSub); foreach (var fi in di.GetFiles("*.dll")) RemoveMOTW(fi.FullName); foreach (var fi in di.GetFiles("*.exe")) RemoveMOTW(fi.FullName); } } #endif private const UInt32 WM_DROPFILES = 0x0233; private const UInt32 WM_COPYDATA = 0x004A; [DllImport("user32")] public static extern bool ChangeWindowMessageFilter(uint msg, ChangeWindowMessageFilterFlags flags); public enum ChangeWindowMessageFilterFlags : uint { Add = 1, Remove = 2 } public enum MessageFilterInfo : uint { None = 0, AlreadyAllowed = 1, AlreadyDisAllowed = 2, AllowedHigher = 3 } public enum ChangeWindowMessageFilterExAction : uint { Reset = 0, Allow = 1, DisAllow = 2 } [StructLayout(LayoutKind.Sequential)] public struct CHANGEFILTERSTRUCT { public uint size; public MessageFilterInfo info; } } class DiscoHawk { static List FindCuesRecurse(string dir) { List ret = new List(); Queue dpTodo = new Queue(); dpTodo.Enqueue(dir); for (; ; ) { string dpCurr; if (dpTodo.Count == 0) break; dpCurr = dpTodo.Dequeue(); foreach(var fi in new DirectoryInfo(dpCurr).GetFiles("*.cue")) { ret.Add(fi.FullName); } Parallel.ForEach(new DirectoryInfo(dpCurr).GetDirectories(), (di) => { lock(dpTodo) dpTodo.Enqueue(di.FullName); }); } return ret; } public void Run(string[] args) { if (args.Length == 0) { var dialog = new MainDiscoForm(); dialog.ShowDialog(); return; } bool scanCues = false; string dirArg = null; string infile = null; var loadDiscInterface = DiscInterface.BizHawk; var compareDiscInterfaces = new List (); bool hawk = false; int idx = 0; while (idx < args.Length) { string a = args[idx++]; string au = a.ToUpperInvariant(); if (au == "LOAD") loadDiscInterface = (DiscInterface)Enum.Parse(typeof(DiscInterface), args[idx++], true); else if (au == "COMPARE") compareDiscInterfaces.Add((DiscInterface)Enum.Parse(typeof(DiscInterface), args[idx++], true)); else if (au == "HAWK") hawk = true; else if (au == "CUEDIR") { dirArg = args[idx++]; scanCues = true; } else infile = a; } if (hawk) { if (infile == null) { return; } // TODO - write it out var dmj = new DiscMountJob { IN_DiscInterface = loadDiscInterface, IN_FromPath = infile }; dmj.Run(); } bool verbose = true; if (scanCues) { verbose = false; var todo = FindCuesRecurse(dirArg); var po = new ParallelOptions(); var cts = new CancellationTokenSource(); po.CancellationToken = cts.Token; po.MaxDegreeOfParallelism = 1; if(po.MaxDegreeOfParallelism < 0) po.MaxDegreeOfParallelism = 1; object olock = new object(); int ctr=0; bool blocked = false; try { Parallel.ForEach(todo, po, (fp) => { lock (olock) { ctr++; int strlen = todo.Count.ToString().Length; string fmt = string.Format("{{0,{0}}}/{{1,{0}}} {{2}}", strlen); Console.WriteLine(fmt, ctr, todo.Count, Path.GetFileNameWithoutExtension(fp)); } if(!blocked) foreach (var cmpif in compareDiscInterfaces) { var sw = new StringWriter(); bool success = CompareFile(fp, loadDiscInterface, cmpif, verbose, cts, sw); if (!success) { lock (Console.Out) Console.Out.Write(sw.ToString()); cts.Cancel(); return; } } }); } catch (AggregateException ae) { Console.WriteLine(ae.ToString()); } catch (OperationCanceledException oce) { Console.WriteLine(oce.ToString()); } Console.WriteLine("--TERMINATED--"); return; } if (compareDiscInterfaces.Count != 0) { var sw = new StringWriter(); foreach (var cmpif in compareDiscInterfaces) { CompareFile(infile, loadDiscInterface, cmpif, verbose, null, sw); } sw.Flush(); string results = sw.ToString(); var cr = new ComparisonResults { textBox1 = { Text = results } }; cr.ShowDialog(); } } //Run() static bool CompareFile(string infile, DiscInterface loadDiscInterface, DiscInterface cmpif, bool verbose, CancellationTokenSource cancelToken, StringWriter sw) { Disc srcDisc = null, dstDisc = null; try { bool success = false; sw.WriteLine("BEGIN COMPARE: {0}\nSRC {1} vs DST {2}", infile, loadDiscInterface, cmpif); //reload the original disc, with new policies as needed var dmj = new DiscMountJob { IN_DiscInterface = loadDiscInterface, IN_FromPath = infile }; if (cmpif == DiscInterface.MednaDisc) { dmj.IN_DiscMountPolicy.CUE_PregapContradictionModeA = false; } dmj.Run(); srcDisc = dmj.OUT_Disc; var dstDmj = new DiscMountJob { IN_DiscInterface = cmpif, IN_FromPath = infile }; dstDmj.Run(); dstDisc = dstDmj.OUT_Disc; var srcDsr = new DiscSectorReader(srcDisc); var dstDsr = new DiscSectorReader(dstDisc); var srcToc = srcDisc.TOC; var dstToc = dstDisc.TOC; var srcDataBuf = new byte[2448]; var dstDataBuf = new byte[2448]; void SwDumpTocOne(DiscTOC.TOCItem item) { if (!item.Exists) { sw.Write("(---missing---)"); } else { sw.Write("({0:X2} - {1})", (byte) item.Control, item.LBA); } } void SwDumpToc(int index) { sw.Write("SRC TOC#{0,3} ", index); SwDumpTocOne(srcToc.TOCItems[index]); sw.WriteLine(); sw.Write("DST TOC#{0,3} ", index); SwDumpTocOne(dstToc.TOCItems[index]); sw.WriteLine(); } //verify sector count if (srcDisc.Session1.LeadoutLBA != dstDisc.Session1.LeadoutLBA) { sw.Write("LeadoutTrack.LBA {0} vs {1}\n", srcDisc.Session1.LeadoutTrack.LBA, dstDisc.Session1.LeadoutTrack.LBA); goto SKIPPO; } //verify TOC match if (srcDisc.TOC.FirstRecordedTrackNumber != dstDisc.TOC.FirstRecordedTrackNumber || srcDisc.TOC.LastRecordedTrackNumber != dstDisc.TOC.LastRecordedTrackNumber) { sw.WriteLine("Mismatch of RecordedTrackNumbers: {0}-{1} vs {2}-{3}", srcDisc.TOC.FirstRecordedTrackNumber, srcDisc.TOC.LastRecordedTrackNumber, dstDisc.TOC.FirstRecordedTrackNumber, dstDisc.TOC.LastRecordedTrackNumber ); goto SKIPPO; } bool badToc = false; for (int t = 0; t < 101; t++) { if (srcToc.TOCItems[t].Exists != dstToc.TOCItems[t].Exists || srcToc.TOCItems[t].Control != dstToc.TOCItems[t].Control || srcToc.TOCItems[t].LBA != dstToc.TOCItems[t].LBA ) { sw.WriteLine("Mismatch in TOCItem"); SwDumpToc(t); badToc = true; } } if (badToc) goto SKIPPO; void SwDumpChunkOne(string comment, int lba, byte[] buf, int addr, int count) { sw.Write("{0} - ", comment); for (int i = 0; i < count; i++) { if (i + addr >= buf.Length) continue; sw.Write("{0:X2}{1}", buf[addr + i], (i == count - 1) ? " " : " "); } sw.WriteLine(); } int[] offenders = new int[12]; void SwDumpChunk(int lba, int dispAddr, int addr, int count, int numOffenders) { var hashedOffenders = new HashSet(); for (int i = 0; i < numOffenders; i++) { hashedOffenders.Add(offenders[i]); } sw.Write(" "); for (int i = 0; i < count; i++) { sw.Write((hashedOffenders.Contains(dispAddr + i)) ? "vvv " : " "); } sw.WriteLine(); sw.Write(" "); for (int i = 0; i < count; i++) { sw.Write("{0:X3} ", dispAddr + i, (i == count - 1) ? " " : " "); } sw.WriteLine(); sw.Write(" "); sw.Write(new string('-', count * 4)); sw.WriteLine(); SwDumpChunkOne($"SRC #{lba,6} ({new Timestamp(lba)})", lba, srcDataBuf, addr, count); SwDumpChunkOne($"DST #{lba,6} ({new Timestamp(lba)})", lba, dstDataBuf, addr, count); } //verify each sector contents int nSectors = srcDisc.Session1.LeadoutLBA; for (int lba = -150; lba < nSectors; lba++) { if (verbose) if (lba % 1000 == 0) Console.WriteLine("LBA {0} of {1}", lba, nSectors); if (cancelToken != null) if (cancelToken.Token.IsCancellationRequested) return false; srcDsr.ReadLBA_2448(lba, srcDataBuf, 0); dstDsr.ReadLBA_2448(lba, dstDataBuf, 0); //check the header for (int b = 0; b < 16; b++) { if (srcDataBuf[b] != dstDataBuf[b]) { sw.WriteLine("Mismatch in sector header at byte {0}", b); offenders[0] = b; SwDumpChunk(lba, 0, 0, 16, 1); goto SKIPPO; } } // check userData for (int b = 16; b < 2352; b++) { if (srcDataBuf[b] != dstDataBuf[b]) { sw.Write("LBA {0} mismatch at userdata byte {1}; terminating sector cmp\n", lba, b); goto SKIPPO; } } // check subChannels for (int c = 0, b = 2352; c < 8; c++) { int numOffenders = 0; for (int e = 0; e < 12; e++, b++) { if (srcDataBuf[b] != dstDataBuf[b]) { offenders[numOffenders++] = e; } } if (numOffenders != 0) { sw.Write("LBA {0} mismatch(es) at subchannel {1}; terminating sector cmp\n", lba, (char)('P' + c)); SwDumpChunk(lba, 0, 2352 + c * 12, 12, numOffenders); goto SKIPPO; } } } success = true; SKIPPO: sw.WriteLine("END COMPARE"); sw.WriteLine("-----------------------------"); return success; } finally { if (srcDisc != null) srcDisc.Dispose(); if (dstDisc != null) dstDisc.Dispose(); } } //CompareFile } //class DiscoHawk #if false /// code to test ECM static class Test { public static void Shuffle(this IList list, Random rng) { int n = list.Count; while (n > 1) { n--; int k = rng.Next(n + 1); T value = list[k]; list[k] = list[n]; list[n] = value; } } public static void TestMain() { var plaindisc = Disc.LoadAutomagic("d:\\ecmtest\\test.cue"); var ecmdisc = Disc.LoadAutomagic("d:\\ecmtest\\ecmtest.cue"); // var prefs = new CueBinPrefs // { // AnnotateCue = false, // OneBlobPerTrack = false, // ReallyDumpBin = true, // SingleSession = true, // DumpToBitbucket = true // }; // var dump = ecmdisc.DumpCueBin("test", prefs); // dump.Dump("test", prefs); // var prefs = new CueBinPrefs // { // AnnotateCue = false, // OneBlobPerTrack = false, // ReallyDumpBin = true, // SingleSession = true // }; // var dump = ecmdisc.DumpCueBin("test", prefs); // dump.Dump(@"D:\ecmtest\myout", prefs); int seed = 102; for (; ; ) { Console.WriteLine("running seed {0}", seed); Random r = new Random(seed); seed++; byte[] chunkbuf_corlet = new byte[2352 * 20]; byte[] chunkbuf_mine = new byte[2352 * 20]; // int length = (ecmdisc._Sectors.Count - 150) * 2352; // API has changed var length = 0; int counter = 0; List> testChunks = new List>(); while (counter < length) { int chunk = r.Next(1, 2352 * 20); if (r.Next(20) == 0) chunk /= 100; if (r.Next(40) == 0) chunk = 0; if (counter + chunk > length) chunk = length - counter; testChunks.Add(new Tuple(counter, chunk)); counter += chunk; } testChunks.Shuffle(r); for (int t = 0; t < testChunks.Count; t++) { //Console.WriteLine("skank"); var item = testChunks[t]; //Console.WriteLine("chunk {0} of {3} is {1} bytes @ {2:X8}", t, item.Item2, item.Item1, testChunks.Count); // plaindisc.ReadLBA_2352_Flat(item.Item1, chunkbuf_corlet, 0, item.Item2); // API has changed // ecmdisc.ReadLBA_2352_Flat(item.Item1, chunkbuf_mine, 0, item.Item2); // API has changed for (int i = 0; i < item.Item2; i++) if (chunkbuf_corlet[i] != chunkbuf_mine[i]) { Debug.Assert(false); } } } } } #endif }