Merge branch 'discsys'

Conflicts:
	BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
	BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGXDynamic.cs
	libmupen64plus/mupen64plus-win32-deps
This commit is contained in:
zeromus 2015-07-11 14:01:44 -05:00
commit a7ae3c0cfc
134 changed files with 42243 additions and 11549 deletions

View File

@ -237,12 +237,7 @@ namespace BizHawk.Client.Common
Disc disc = null;
string discPath = e.Path;
string discExt = Path.GetExtension(discPath).ToLower();
if (discExt == ".iso")
disc = Disc.FromIsoPath(discPath);
if (discExt == ".cue")
disc = Disc.FromCuePath(discPath, new CueBinPrefs());
if (discExt == ".ccd")
disc = Disc.FromCCDPath(discPath);
disc = Disc.LoadAutomagic(discPath);
if(disc == null)
throw new InvalidOperationException("Can't load one of the files specified in the M3U");
discNames.Add(Path.GetFileNameWithoutExtension(discPath));
@ -260,22 +255,34 @@ namespace BizHawk.Client.Common
throw new InvalidOperationException("Can't load CD files from archives!");
}
Disc disc = null;
if(ext == ".iso")
disc = Disc.FromIsoPath(path);
if(ext == ".cue")
disc = Disc.FromCuePath(path, new CueBinPrefs());
if (ext == ".ccd")
disc = Disc.FromCCDPath(path);
string discHash = null;
var hash = disc.GetHash();
game = Database.CheckDatabase(hash);
//--- load the disc in a context which will let us abort if it's going to take too long
var discMountJob = new DiscMountJob { IN_FromPath = path };
discMountJob.IN_SlowLoadAbortThreshold = 8;
discMountJob.Run();
if (discMountJob.OUT_SlowLoadAborted)
{
System.Windows.Forms.MessageBox.Show("This disc would take too long to load. Run it through discohawk first, or find a new rip because this one is probably junk");
return false;
}
var disc = discMountJob.OUT_Disc;
//-----------
//TODO - use more sophisticated IDer
var discType = new DiscIdentifier(disc).DetectDiscType();
if (discType == DiscType.SonyPSX)
discHash = new DiscHasher(disc).Calculate_PSX_BizIDHash().ToString("X8");
else discHash = new DiscHasher(disc).OldHash();
game = Database.CheckDatabase(discHash);
if (game == null)
{
// try to use our wizard methods
game = new GameInfo { Name = Path.GetFileNameWithoutExtension(file.Name), Hash = hash };
game = new GameInfo { Name = Path.GetFileNameWithoutExtension(file.Name), Hash = discHash };
switch (disc.DetectDiscType())
switch (new DiscIdentifier(disc).DetectDiscType())
{
case DiscType.SegaSaturn:
game.System = "SAT";
@ -313,7 +320,18 @@ namespace BizHawk.Client.Common
break;
case "PSX":
nextEmulator = new Octoshock(nextComm, new List<Disc>(new[]{disc}), new List<string>(new[]{Path.GetFileNameWithoutExtension(path)}), null, GetCoreSettings<Octoshock>(), GetCoreSyncSettings<Octoshock>());
nextEmulator.CoreComm.RomStatusDetails = "PSX etc.";
if (game.IsRomStatusBad())
nextEmulator.CoreComm.RomStatusDetails = "Disc could not be identified as known-good. Look for a better rip.";
else
{
StringWriter sw = new StringWriter();
sw.WriteLine("Disc was identified (99.99% confidently) as known good.");
sw.WriteLine("Nonetheless it could be an unrecognized romhack or patched version.");
sw.WriteLine("Ideal hash for entire disc is: CRC32:{0:X8}", game.GetStringValue("dh"));
sw.WriteLine("The file you loaded hasn't been hashed entirely (it would take too long)");
sw.WriteLine("Check it in the PSX menu (eventually) and compare to this or check each .bin file individually against redump.org");
nextEmulator.CoreComm.RomStatusDetails = sw.ToString();
}
break;
case "PCE":
case "PCECD":

View File

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}</ProjectGuid>
<OutputType>WinExe</OutputType>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BizHawk.Client.DBMan</RootNamespace>
<AssemblyName>BizHawk.Client.DBMan</AssemblyName>
@ -19,18 +22,20 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\output\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>TRACE;DEBUG;WINDOWS</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\output\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<DefineConstants>TRACE;WINDOWS</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<ItemGroup>
<Reference Include="CSharp-SQLite">
@ -40,7 +45,10 @@
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.XML" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="DB.cs" />
@ -53,6 +61,8 @@
<Compile Include="DirectoryScan.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="DiscHash.cs" />
<Compile Include="PsxDBJob.cs" />
<Compile Include="RomHasher.cs" />
<EmbeddedResource Include="DBMan_MainForm.resx">
<DependentUpon>DBMan_MainForm.cs</DependentUpon>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,123 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using BizHawk.Common;
using BizHawk.Emulation.DiscSystem;
namespace BizHawk.Client.DBMan
{
class DiscHash
{
static List<string> FindExtensionsRecurse(string dir, string extUppercaseWithDot)
{
List<string> ret = new List<string>();
Queue<string> dpTodo = new Queue<string>();
dpTodo.Enqueue(dir);
for (; ; )
{
string dpCurr;
if (dpTodo.Count == 0)
break;
dpCurr = dpTodo.Dequeue();
Parallel.ForEach(new DirectoryInfo(dpCurr).GetFiles(), (fi) =>
{
if (fi.Extension.ToUpperInvariant() == extUppercaseWithDot)
lock (ret)
ret.Add(fi.FullName);
});
Parallel.ForEach(new DirectoryInfo(dpCurr).GetDirectories(), (di) =>
{
lock (dpTodo)
dpTodo.Enqueue(di.FullName);
});
}
return ret;
}
public void Run(string[] args)
{
string indir = null;
string dpTemp = null;
string fpOutfile = null;
for (int i = 0; ; )
{
if (i == args.Length) break;
var arg = args[i++];
if (arg == "--indir")
indir = args[i++];
if (arg == "--tempdir")
dpTemp = args[i++];
if (arg == "--outfile")
fpOutfile = args[i++];
}
using (var outf = new StreamWriter(fpOutfile))
{
Dictionary<uint, string> FoundHashes = new Dictionary<uint, string>();
object olock = new object();
var todo = FindExtensionsRecurse(indir, ".CUE");
int progress = 0;
//loop over games (parallel doesnt work well when reading tons of data over the network, as we are here to do the complete redump hash)
var po = new ParallelOptions();
//po.MaxDegreeOfParallelism = Environment.ProcessorCount - 1;
po.MaxDegreeOfParallelism = 1;
Parallel.ForEach(todo, po, (fiCue) =>
{
string name = Path.GetFileNameWithoutExtension(fiCue);
lock (olock)
{
if (done.Contains(name))
{
progress++;
return;
}
}
//now look for the cue file
using (var disc = Disc.LoadAutomagic(fiCue))
{
var hasher = new DiscHasher(disc);
uint bizHashId = hasher.Calculate_PSX_BizIDHash();
uint redumpHash = hasher.Calculate_PSX_RedumpHash();
lock (olock)
{
progress++;
Console.WriteLine("{0}/{1} [{2:X8}] {3}", progress, todo.Count, bizHashId, Path.GetFileNameWithoutExtension(fiCue));
outf.WriteLine("bizhash:{0:X8} datahash:{1:X8} //{2}", bizHashId, redumpHash, name);
if (FoundHashes.ContainsKey(bizHashId))
{
Console.WriteLine("--> COLLISION WITH: {0}", FoundHashes[bizHashId]);
outf.WriteLine("--> COLLISION WITH: {0}", FoundHashes[bizHashId]);
}
else
FoundHashes[bizHashId] = name;
Console.Out.Flush();
outf.Flush();
}
}
}); //major loop
} //using(outfile)
} //MyRun()
} //class PsxRedump
}

View File

@ -1,14 +1,126 @@
using System;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Collections.Generic;
using Community.CsharpSqlite.SQLiteClient;
using System.IO;
namespace BizHawk.Client.DBMan
{
internal static class Program
{
[STAThread]
static void Main()
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 = System.IO.Path.Combine(GetExeDirectoryAbsolute(), "dll");
SetDllDirectory(dllDir);
//but before we even try doing that, whack the MOTW from everything in that directory (thats 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);
//in case assembly resolution fails, such as if we moved them into the dll subdiretory, this event handler can reroute to them
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
#endif
}
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/unamanged) 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");
}
//for debugging purposes, this is provided. when we're satisfied everyone understands whats going on, we'll get rid of this
[DllImportAttribute("kernel32.dll", EntryPoint = "CreateFileW")]
public static extern System.IntPtr CreateFileW([InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName, int dwDesiredAccess, int dwShareMode, [InAttribute()] int lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, [InAttribute()] int hTemplateFile);
static void ApplyMOTW(string path)
{
int generic_write = 0x40000000;
int file_share_write = 2;
int create_always = 2;
var adsHandle = CreateFileW(path + ":Zone.Identifier", generic_write, file_share_write, 0, create_always, 0, 0);
using (var sfh = new Microsoft.Win32.SafeHandles.SafeFileHandle(adsHandle, true))
{
var adsStream = new System.IO.FileStream(sfh, FileAccess.Write);
StreamWriter sw = new StreamWriter(adsStream);
sw.Write("[ZoneTransfer]\r\nZoneId=3");
sw.Flush();
adsStream.Close();
}
}
static void WhackAllMOTW(string dllDir)
{
var todo = new Queue<DirectoryInfo>(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
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0 && args[0] == "--dischash")
{
new DiscHash().Run(args.Skip(1).ToArray());
return;
}
if (args.Length > 0 && args[0] == "--psxdb")
{
new PsxDBJob().Run(args.Skip(1).ToArray());
return;
}
//if (args.Length > 0 && args[0] == "--disccmp")
//{
// new DiscCmp().Run(args.Skip(1).ToArray());
// return;
//}
try
{
InitDB();

View File

@ -0,0 +1,127 @@
using System;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Common;
using BizHawk.Emulation.DiscSystem;
namespace BizHawk.Client.DBMan
{
class PsxDBJob
{
class HashRecord
{
public string name, bizhash, datahash;
public bool matched;
}
public void Run(string[] args)
{
string fpHash = null, fpRedump = null, fpOutfile = null;
for (int i = 0; ; )
{
if (i == args.Length) break;
var arg = args[i++];
if (arg == "--hashes")
fpHash = args[i++];
if (arg == "--redump")
fpRedump = args[i++];
if (arg == "--outfile")
fpOutfile = args[i++];
}
var hashes = new Dictionary<string, HashRecord>();
Console.WriteLine("Loading redump data");
RedumpPSX rdpsx = new RedumpPSX();
rdpsx.Load(fpRedump);
Console.WriteLine("Loading hash data");
var splitSlashes = new string[]{"//"};
foreach (var line in File.ReadAllLines(fpHash))
{
var parts = line.Split(splitSlashes, StringSplitOptions.None);
var hr = new HashRecord()
{
name = parts[1],
bizhash = parts[0].Substring(8, 8),
datahash = parts[0].Substring(26, 8),
};
hashes[hr.datahash] = hr;
}
Console.WriteLine("merging");
foreach (var rr in rdpsx.Records)
{
HashRecord hr;
if (!hashes.TryGetValue(rr.crc, out hr))
continue;
hr.matched = true;
//correct name to redump current
hr.name = rr.name;
}
Console.WriteLine("writing results");
using (var outf = new StreamWriter(fpOutfile))
{
foreach (var hr in hashes.Values)
{
if (!hr.matched)
continue;
outf.WriteLine("{0}\tG\t{1}\tPSX\t\tdh={2}", hr.bizhash, hr.name, hr.datahash);
}
}
}
}
class RedumpPSX
{
public class RedumpRecord
{
public string name;
public string crc;
}
public List<RedumpRecord> Records = new List<RedumpRecord>();
public void Load(string datpath)
{
var xd = XDocument.Load(datpath);
Dictionary<uint, string> knownHashes = new Dictionary<uint, string>();
var games = xd.Root.Descendants("game").ToArray();
for(int i=0;i<games.Length;i++)
{
var game = games[i];
if (i % 100 == 0)
Console.WriteLine("{0}/{1}", i, games.Length);
var name = game.Attribute("name").Value;
BizHawk.Emulation.DiscSystem.DiscHasher.SpecialCRC32 spec_crc_calc = new Emulation.DiscSystem.DiscHasher.SpecialCRC32();
spec_crc_calc.Current = 0;
foreach (var rom in game.Elements("rom"))
{
var ext = Path.GetExtension(rom.Attribute("name").Value).ToLower();
if (ext == ".cue") continue;
uint onecrc = uint.Parse(rom.Attribute("crc").Value, NumberStyles.HexNumber);
int size = int.Parse(rom.Attribute("size").Value);
spec_crc_calc.Incorporate(onecrc, size);
}
//Console.WriteLine("{0:X8}", spec_crc_calc.Current);
Records.Add(new RedumpRecord()
{
name = name,
crc = spec_crc_calc.Current.ToString("X8")
});
}
}
}
}

View File

@ -178,9 +178,10 @@ namespace BizHawk.Client.DBMan
try
{
string ext = new FileInfo(file).Extension.ToLowerInvariant();
using (var disc = ext == ".iso" ? Disc.FromIsoPath(file) : Disc.FromCuePath(file, new CueBinPrefs()))
using (var disc = Disc.LoadAutomagic(file))
{
return disc.GetHash();
var hasher = new DiscHasher(disc);
return hasher.OldHash();
}
}
catch

View File

@ -118,14 +118,14 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="richTextBox1.Text" xml:space="preserve">
<value>DiscoHawk converts bolloxed-up crusty disc images to totally tidy cue+bin.
<value>DiscoHawk converts bolloxed-up crusty disc images to totally tidy CCD.
DiscoHawk is part of the BizHawk project ( http://code.google.com/p/bizhawk ).
BizHawk is a .net-based multi-system emulator brought to you by some of the rerecording emulator principals. We wrote our own cue parsing/generating code to be able to handle any kind of junk we threw at it. Instead of trapping it in the emulator, we liberated it in the form of this tool, to be useful in other environments.
To use, drag a .cue into the MAGIC area. DiscoHawk will dump a newly cleaned up cue+bin pair to the same directory as the original .cue, and call it _hawked.
To use, drag a disc (.cue, .iso, .ccd) into the top area. DiscoHawk will dump a newly cleaned up CCD file set to the same directory as the original disc image, and call it _hawked.
This is beta software. You are invited to report problems to our bug tracker or IRC. Problems consist of: crusty disc images that crash DiscoHawk or that cause DiscoHawk to produce a _hawked.cue which fails to serve your particular purposes (which we will need to be informed of, in case we are outputting wrongly.)</value>
This is beta software. You are invited to report problems to our bug tracker or IRC. Problems consist of: crusty disc images that crash DiscoHawk or that cause DiscoHawk to produce a _hawked.ccd which fails to serve your particular purposes (which we will need to be informed of, in case we are outputting wrongly.)</value>
</data>
</root>

View File

@ -16,17 +16,20 @@ namespace BizHawk.Client.DiscoHawk
public static void Extract(Disc disc, string path, string filebase)
{
var dsr = new DiscSectorReader(disc);
bool confirmed = false;
var tracks = disc.Structure.Sessions[0].Tracks;
var tracks = disc.Session1.Tracks;
foreach (var track in tracks)
{
if (track.TrackType != ETrackType.Audio)
if (!track.IsAudio)
continue;
var waveData = new byte[track.LengthInSectors * 2352];
int startLba = track.Indexes[1].LBA;
for (int sector = 0; sector < track.LengthInSectors; sector++)
disc.ReadLBA_2352(startLba + sector, waveData, sector * 2352);
int trackLength = track.NextTrack.LBA - track.LBA;
var waveData = new byte[trackLength * 2352];
int startLba = track.LBA;
for (int sector = 0; sector < trackLength; sector++)
dsr.ReadLBA_2352(startLba + sector, waveData, sector * 2352);
string mp3Path = string.Format("{0} - Track {1:D2}.mp3", Path.Combine(path, filebase), track.Number);
if (File.Exists(mp3Path))

View File

@ -44,6 +44,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<PlatformTarget>x86</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
@ -87,26 +88,19 @@
<DependentUpon>About.cs</DependentUpon>
</Compile>
<Compile Include="AudioExtractor.cs" />
<Compile Include="DiscoHawk.cs" />
<Compile Include="DiscoHawkDialog.cs">
<Compile Include="ComparisonResults.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="DiscoHawkDialog.Designer.cs">
<DependentUpon>DiscoHawkDialog.cs</DependentUpon>
<Compile Include="ComparisonResults.Designer.cs">
<DependentUpon>ComparisonResults.cs</DependentUpon>
</Compile>
<Compile Include="DiscoHawk.cs" />
<Compile Include="MainDiscoForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainDiscoForm.Designer.cs">
<DependentUpon>MainDiscoForm.cs</DependentUpon>
</Compile>
<Compile Include="MednadiscTester.cs" />
<Compile Include="ProgressDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="ProgressDialog.Designer.cs">
<DependentUpon>ProgressDialog.cs</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@ -124,16 +118,13 @@
<EmbeddedResource Include="About.resx">
<DependentUpon>About.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="DiscoHawkDialog.resx">
<DependentUpon>DiscoHawkDialog.cs</DependentUpon>
<EmbeddedResource Include="ComparisonResults.resx">
<DependentUpon>ComparisonResults.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="MainDiscoForm.resx">
<DependentUpon>MainDiscoForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="ProgressDialog.resx">
<DependentUpon>ProgressDialog.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">

View File

@ -0,0 +1,119 @@
namespace BizHawk.Client.DiscoHawk
{
partial class ComparisonResults
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.RichTextBox();
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.tabPage3 = new System.Windows.Forms.TabPage();
this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Dock = System.Windows.Forms.DockStyle.Fill;
this.textBox1.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.textBox1.Location = new System.Drawing.Point(3, 3);
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.Size = new System.Drawing.Size(757, 394);
this.textBox1.TabIndex = 1;
this.textBox1.Text = "";
//
// tabControl1
//
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Controls.Add(this.tabPage3);
this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl1.Location = new System.Drawing.Point(0, 0);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(771, 426);
this.tabControl1.TabIndex = 2;
//
// tabPage1
//
this.tabPage1.Controls.Add(this.textBox1);
this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
this.tabPage1.Size = new System.Drawing.Size(763, 400);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "Log";
this.tabPage1.UseVisualStyleBackColor = true;
//
// tabPage2
//
this.tabPage2.Location = new System.Drawing.Point(4, 22);
this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
this.tabPage2.Size = new System.Drawing.Size(763, 400);
this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "SRC Context";
this.tabPage2.UseVisualStyleBackColor = true;
//
// tabPage3
//
this.tabPage3.Location = new System.Drawing.Point(4, 22);
this.tabPage3.Name = "tabPage3";
this.tabPage3.Padding = new System.Windows.Forms.Padding(3);
this.tabPage3.Size = new System.Drawing.Size(763, 400);
this.tabPage3.TabIndex = 2;
this.tabPage3.Text = "DST Context";
this.tabPage3.UseVisualStyleBackColor = true;
//
// ComparisonResults
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(771, 426);
this.Controls.Add(this.tabControl1);
this.Name = "ComparisonResults";
this.Text = "ComparisonResults";
this.tabControl1.ResumeLayout(false);
this.tabPage1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
public System.Windows.Forms.RichTextBox textBox1;
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.TabPage tabPage3;
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace BizHawk.Client.DiscoHawk
{
public partial class ComparisonResults : Form
{
public ComparisonResults()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -4,6 +4,8 @@ using System.Diagnostics;
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;
@ -184,28 +186,335 @@ namespace BizHawk.Client.DiscoHawk
class DiscoHawk
{
public void Run(string[] args)
static List<string> FindCuesRecurse(string dir)
{
bool gui = true;
foreach (var arg in args)
List<string> ret = new List<string>();
Queue<string> dpTodo = new Queue<string>();
dpTodo.Enqueue(dir);
for (; ; )
{
if (arg.ToUpper() == "COMMAND") gui = false;
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);
});
}
if (gui)
return ret;
}
public void Run(string[] args)
{
if (args.Length == 0)
{
var dialog = new MainDiscoForm();
dialog.ShowDialog();
return;
}
else
bool scanCues = false;
string dirArg = null;
string infile = null;
var loadDiscInterface = DiscInterface.BizHawk;
var compareDiscInterfaces = new List<DiscInterface> ();
bool hawk = false;
int idx = 0;
while (idx < args.Length)
{
//test stuff...
MednadiscTester tester = new MednadiscTester();
tester.TestDirectory(@"c:\isos\psx");
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();
//var disc = dmj.OUT_Disc;
}
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;
//po.MaxDegreeOfParallelism = System.Environment.ProcessorCount - 2; //I'm disk or network bound, no sense hammering this
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 (fp.Contains("Break Out"))
// blocked = false;
}
if(!blocked)
foreach (var cmpif in compareDiscInterfaces)
{
StringWriter 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)
{
StringWriter 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();
cr.textBox1.Text = results;
cr.ShowDialog();
}
} //Run()
static bool CompareFile(string infile, DiscInterface loadDiscInterface, DiscInterface cmpif, bool verbose, CancellationTokenSource cancelToken, StringWriter sw)
{
Disc src_disc = null, dst_disc = 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();
src_disc = dmj.OUT_Disc;
var dst_dmj = new DiscMountJob { IN_DiscInterface = cmpif, IN_FromPath = infile };
dst_dmj.Run();
dst_disc = dst_dmj.OUT_Disc;
var src_dsr = new DiscSectorReader(src_disc);
var dst_dsr = new DiscSectorReader(dst_disc);
var src_toc = src_disc.TOC;
var dst_toc = dst_disc.TOC;
var src_databuf = new byte[2448];
var dst_databuf = new byte[2448];
Action<DiscTOC.TOCItem> sw_dump_toc_one = (item) =>
{
if (!item.Exists)
sw.Write("(---missing---)");
else
sw.Write("({0:X2} - {1})", (byte)item.Control, item.LBATimestamp);
};
Action<int> sw_dump_toc = (index) =>
{
sw.Write("SRC TOC#{0,3} ", index); sw_dump_toc_one(src_toc.TOCItems[index]); sw.WriteLine();
sw.Write("DST TOC#{0,3} ", index); sw_dump_toc_one(dst_toc.TOCItems[index]); sw.WriteLine();
};
//verify sector count
if (src_disc.Session1.LeadoutLBA != dst_disc.Session1.LeadoutLBA)
{
sw.Write("LeadoutTrack.LBA {0} vs {1}\n", src_disc.Session1.LeadoutTrack.LBA, dst_disc.Session1.LeadoutTrack.LBA);
goto SKIPPO;
}
//verify TOC match
if (src_disc.TOC.FirstRecordedTrackNumber != dst_disc.TOC.FirstRecordedTrackNumber
|| src_disc.TOC.LastRecordedTrackNumber != dst_disc.TOC.LastRecordedTrackNumber)
{
sw.WriteLine("Mismatch of RecordedTrackNumbers: {0}-{1} vs {2}-{3}",
src_disc.TOC.FirstRecordedTrackNumber, src_disc.TOC.LastRecordedTrackNumber,
dst_disc.TOC.FirstRecordedTrackNumber, dst_disc.TOC.LastRecordedTrackNumber
);
goto SKIPPO;
}
bool badToc = false;
for (int t = 0; t < 101; t++)
{
if (src_toc.TOCItems[t].Exists != dst_toc.TOCItems[t].Exists
|| src_toc.TOCItems[t].Control != dst_toc.TOCItems[t].Control
|| src_toc.TOCItems[t].LBATimestamp.Sector != dst_toc.TOCItems[t].LBATimestamp.Sector
)
{
sw.WriteLine("Mismatch in TOCItem");
sw_dump_toc(t);
badToc = true;
}
}
if (badToc)
goto SKIPPO;
Action<string, int, byte[], int, int> sw_dump_chunk_one = (comment, lba, buf, addr, 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];
Action<int, int, int, int, int> sw_dump_chunk = (lba, dispaddr, addr, count, numoffenders) =>
{
var hashedOffenders = new HashSet<int>();
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();
sw_dump_chunk_one(string.Format("SRC #{0,6} ({1})", lba, new Timestamp(lba)), lba, src_databuf, addr, count);
sw_dump_chunk_one(string.Format("DST #{0,6} ({1})", lba, new Timestamp(lba)), lba, dst_databuf, addr, count);
};
//verify each sector contents
int nSectors = src_disc.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;
src_dsr.ReadLBA_2448(lba, src_databuf, 0);
dst_dsr.ReadLBA_2448(lba, dst_databuf, 0);
//check the header
for (int b = 0; b < 16; b++)
{
if (src_databuf[b] != dst_databuf[b])
{
sw.WriteLine("Mismatch in sector header at byte {0}", b);
offenders[0] = b;
sw_dump_chunk(lba, 0, 0, 16, 1);
goto SKIPPO;
}
}
//check userdata
for (int b = 16; b < 2352; b++)
{
if (src_databuf[b] != dst_databuf[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 (src_databuf[b] != dst_databuf[b])
{
offenders[numOffenders++] = e;
}
}
if (numOffenders != 0)
{
sw.Write("LBA {0} mismatch(es) at subchannel {1}; terminating sector cmp\n", lba, (char)('P' + c));
sw_dump_chunk(lba, 0, 2352 + c * 12, 12, numOffenders);
goto SKIPPO;
}
}
}
success = true;
SKIPPO:
sw.WriteLine("END COMPARE");
sw.WriteLine("-----------------------------");
return success;
}
finally
{
if (src_disc != null)
src_disc.Dispose();
if (dst_disc != null)
dst_disc.Dispose();
}
} //CompareFile
} //class DiscoHawk
}

View File

@ -98,16 +98,6 @@ namespace BizHawk.Client.DiscoHawk
txtCuePreview.Text = cueBin.cue.Replace("\n", "\r\n"); ;
}
CueBinPrefs GetCuePrefs()
{
var prefs = new CueBinPrefs();
prefs.AnnotateCue = checkCueProp_Annotations.Checked;
prefs.OneBlobPerTrack = checkCueProp_OneBlobPerTrack.Checked;
prefs.ReallyDumpBin = false;
prefs.SingleSession = true;
return prefs;
}
private void btnPresetCanonical_Click(object sender, EventArgs e)
{
PresetCanonical();
@ -185,30 +175,30 @@ namespace BizHawk.Client.DiscoHawk
return ret;
}
bool Dump(CueBin cueBin, string directoryTo, CueBinPrefs prefs)
{
ProgressReport pr = new ProgressReport();
Thread workThread = new Thread(() =>
{
cueBin.Dump(directoryTo, prefs, pr);
});
//bool Dump(CueBin cueBin, string directoryTo, CueBinPrefs prefs)
//{
// ProgressReport pr = new ProgressReport();
// Thread workThread = new Thread(() =>
// {
// cueBin.Dump(directoryTo, prefs, pr);
// });
ProgressDialog pd = new ProgressDialog(pr);
pd.Show(this);
this.Enabled = false;
workThread.Start();
for (; ; )
{
Application.DoEvents();
Thread.Sleep(10);
if (workThread.ThreadState != ThreadState.Running)
break;
pd.Update();
}
this.Enabled = true;
pd.Dispose();
return !pr.CancelSignal;
}
// ProgressDialog pd = new ProgressDialog(pr);
// pd.Show(this);
// this.Enabled = false;
// workThread.Start();
// for (; ; )
// {
// Application.DoEvents();
// Thread.Sleep(10);
// if (workThread.ThreadState != ThreadState.Running)
// break;
// pd.Update();
// }
// this.Enabled = true;
// pd.Dispose();
// return !pr.CancelSignal;
//}
private void btnExportCue_Click(object sender, EventArgs e)
{

View File

@ -28,6 +28,8 @@
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem("BizHawk");
System.Windows.Forms.ListViewItem listViewItem2 = new System.Windows.Forms.ListViewItem("Mednafen");
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainDiscoForm));
this.ExitButton = new System.Windows.Forms.Button();
this.lblMagicDragArea = new System.Windows.Forms.Panel();
@ -35,13 +37,27 @@
this.lblMp3ExtractMagicArea = new System.Windows.Forms.Panel();
this.label2 = new System.Windows.Forms.Label();
this.btnAbout = new System.Windows.Forms.Button();
this.radioButton1 = new System.Windows.Forms.RadioButton();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.label4 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.radioButton2 = new System.Windows.Forms.RadioButton();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.checkEnableOutput = new System.Windows.Forms.CheckBox();
this.radioButton4 = new System.Windows.Forms.RadioButton();
this.label6 = new System.Windows.Forms.Label();
this.label7 = new System.Windows.Forms.Label();
this.lvCompareTargets = new System.Windows.Forms.ListView();
this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.lblMagicDragArea.SuspendLayout();
this.lblMp3ExtractMagicArea.SuspendLayout();
this.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.SuspendLayout();
//
// ExitButton
//
this.ExitButton.Location = new System.Drawing.Point(146, 245);
this.ExitButton.Location = new System.Drawing.Point(411, 401);
this.ExitButton.Name = "ExitButton";
this.ExitButton.Size = new System.Drawing.Size(75, 23);
this.ExitButton.TabIndex = 0;
@ -54,7 +70,7 @@
this.lblMagicDragArea.AllowDrop = true;
this.lblMagicDragArea.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.lblMagicDragArea.Controls.Add(this.label1);
this.lblMagicDragArea.Location = new System.Drawing.Point(21, 12);
this.lblMagicDragArea.Location = new System.Drawing.Point(286, 31);
this.lblMagicDragArea.Name = "lblMagicDragArea";
this.lblMagicDragArea.Size = new System.Drawing.Size(200, 100);
this.lblMagicDragArea.TabIndex = 1;
@ -63,19 +79,18 @@
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(47, 10);
this.label1.Location = new System.Drawing.Point(17, 25);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(106, 13);
this.label1.Size = new System.Drawing.Size(166, 47);
this.label1.TabIndex = 0;
this.label1.Text = "Drag here for MAGIC";
this.label1.Text = "Drag here to HAWK your disc - dump it out as a clean CCD";
//
// lblMp3ExtractMagicArea
//
this.lblMp3ExtractMagicArea.AllowDrop = true;
this.lblMp3ExtractMagicArea.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.lblMp3ExtractMagicArea.Controls.Add(this.label2);
this.lblMp3ExtractMagicArea.Location = new System.Drawing.Point(21, 127);
this.lblMp3ExtractMagicArea.Location = new System.Drawing.Point(286, 146);
this.lblMp3ExtractMagicArea.Name = "lblMp3ExtractMagicArea";
this.lblMp3ExtractMagicArea.Size = new System.Drawing.Size(200, 100);
this.lblMp3ExtractMagicArea.TabIndex = 2;
@ -84,16 +99,15 @@
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(17, 25);
this.label2.Location = new System.Drawing.Point(20, 25);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(151, 39);
this.label2.Size = new System.Drawing.Size(163, 39);
this.label2.TabIndex = 0;
this.label2.Text = "Drag cue here to extract audio\ntracks to MP3 for listening\npleasure!";
this.label2.Text = "Drag a disc here to extract the audio tracks to MP3";
//
// btnAbout
//
this.btnAbout.Location = new System.Drawing.Point(21, 245);
this.btnAbout.Location = new System.Drawing.Point(319, 401);
this.btnAbout.Name = "btnAbout";
this.btnAbout.Size = new System.Drawing.Size(75, 23);
this.btnAbout.TabIndex = 3;
@ -101,11 +115,139 @@
this.btnAbout.UseVisualStyleBackColor = true;
this.btnAbout.Click += new System.EventHandler(this.btnAbout_Click);
//
// radioButton1
//
this.radioButton1.AutoSize = true;
this.radioButton1.Checked = true;
this.radioButton1.Location = new System.Drawing.Point(6, 19);
this.radioButton1.Name = "radioButton1";
this.radioButton1.Size = new System.Drawing.Size(67, 17);
this.radioButton1.TabIndex = 4;
this.radioButton1.TabStop = true;
this.radioButton1.Text = "BizHawk";
this.radioButton1.UseVisualStyleBackColor = true;
//
// groupBox1
//
this.groupBox1.Controls.Add(this.label4);
this.groupBox1.Controls.Add(this.label3);
this.groupBox1.Controls.Add(this.radioButton2);
this.groupBox1.Controls.Add(this.radioButton1);
this.groupBox1.Location = new System.Drawing.Point(12, 12);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(253, 206);
this.groupBox1.TabIndex = 5;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Disc Reading Engine";
//
// label4
//
this.label4.Location = new System.Drawing.Point(20, 95);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(216, 43);
this.label4.TabIndex = 8;
this.label4.Text = "- Doesn\'t support audio decoding yet\r\n(even though Mednafen proper can do it)\r\n- " +
"Loads ISO, CUE, and CCD";
//
// label3
//
this.label3.Location = new System.Drawing.Point(20, 39);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(216, 33);
this.label3.TabIndex = 7;
this.label3.Text = "- Uses FFMPEG for audio decoding\r\n- Loads ISO, CUE, and CCD";
//
// radioButton2
//
this.radioButton2.AutoSize = true;
this.radioButton2.Location = new System.Drawing.Point(6, 75);
this.radioButton2.Name = "radioButton2";
this.radioButton2.Size = new System.Drawing.Size(73, 17);
this.radioButton2.TabIndex = 5;
this.radioButton2.Text = "Mednafen";
this.radioButton2.UseVisualStyleBackColor = true;
//
// groupBox2
//
this.groupBox2.Controls.Add(this.checkEnableOutput);
this.groupBox2.Controls.Add(this.radioButton4);
this.groupBox2.Location = new System.Drawing.Point(12, 224);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(253, 69);
this.groupBox2.TabIndex = 6;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Output Format";
//
// checkEnableOutput
//
this.checkEnableOutput.AutoSize = true;
this.checkEnableOutput.Checked = true;
this.checkEnableOutput.CheckState = System.Windows.Forms.CheckState.Checked;
this.checkEnableOutput.Location = new System.Drawing.Point(177, 19);
this.checkEnableOutput.Name = "checkEnableOutput";
this.checkEnableOutput.Size = new System.Drawing.Size(59, 17);
this.checkEnableOutput.TabIndex = 7;
this.checkEnableOutput.Text = "Enable";
this.checkEnableOutput.UseVisualStyleBackColor = true;
//
// radioButton4
//
this.radioButton4.AutoSize = true;
this.radioButton4.Checked = true;
this.radioButton4.Location = new System.Drawing.Point(12, 19);
this.radioButton4.Name = "radioButton4";
this.radioButton4.Size = new System.Drawing.Size(47, 17);
this.radioButton4.TabIndex = 5;
this.radioButton4.TabStop = true;
this.radioButton4.Text = "CCD";
this.radioButton4.UseVisualStyleBackColor = true;
//
// label6
//
this.label6.AutoSize = true;
this.label6.Location = new System.Drawing.Point(9, 305);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(111, 13);
this.label6.TabIndex = 2;
this.label6.Text = "Compare Reading To:";
//
// label7
//
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(343, 12);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(70, 13);
this.label7.TabIndex = 10;
this.label7.Text = "- Operations -";
//
// lvCompareTargets
//
this.lvCompareTargets.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.columnHeader1});
this.lvCompareTargets.FullRowSelect = true;
this.lvCompareTargets.GridLines = true;
this.lvCompareTargets.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
this.lvCompareTargets.HideSelection = false;
this.lvCompareTargets.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
listViewItem1,
listViewItem2});
this.lvCompareTargets.Location = new System.Drawing.Point(12, 321);
this.lvCompareTargets.Name = "lvCompareTargets";
this.lvCompareTargets.Size = new System.Drawing.Size(121, 97);
this.lvCompareTargets.TabIndex = 11;
this.lvCompareTargets.UseCompatibleStateImageBehavior = false;
this.lvCompareTargets.View = System.Windows.Forms.View.Details;
//
// MainDiscoForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(247, 285);
this.ClientSize = new System.Drawing.Size(510, 436);
this.Controls.Add(this.lvCompareTargets);
this.Controls.Add(this.label6);
this.Controls.Add(this.label7);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.btnAbout);
this.Controls.Add(this.lblMp3ExtractMagicArea);
this.Controls.Add(this.lblMagicDragArea);
@ -118,10 +260,13 @@
this.Text = "DiscoHawk";
this.Load += new System.EventHandler(this.MainDiscoForm_Load);
this.lblMagicDragArea.ResumeLayout(false);
this.lblMagicDragArea.PerformLayout();
this.lblMp3ExtractMagicArea.ResumeLayout(false);
this.lblMp3ExtractMagicArea.PerformLayout();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
@ -133,5 +278,17 @@
private System.Windows.Forms.Panel lblMp3ExtractMagicArea;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button btnAbout;
private System.Windows.Forms.RadioButton radioButton1;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.RadioButton radioButton2;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.RadioButton radioButton4;
private System.Windows.Forms.ListView lvCompareTargets;
private System.Windows.Forms.ColumnHeader columnHeader1;
private System.Windows.Forms.CheckBox checkEnableOutput;
}
}

View File

@ -30,7 +30,7 @@ namespace BizHawk.Client.DiscoHawk
private void MainDiscoForm_Load(object sender, EventArgs e)
{
lvCompareTargets.Columns[0].Width = lvCompareTargets.ClientSize.Width;
}
private void ExitButton_Click(object sender, EventArgs e)
@ -38,40 +38,22 @@ namespace BizHawk.Client.DiscoHawk
this.Close();
}
public static CueBinPrefs GetCuePrefs()
{
var prefs = new CueBinPrefs();
prefs.AnnotateCue = true; // TODO? checkCueProp_Annotations.Checked;
prefs.OneBlobPerTrack = false; //TODO? checkCueProp_OneBlobPerTrack.Checked;
prefs.ReallyDumpBin = false;
prefs.SingleSession = true;
prefs.ExtensionAware = true;
return prefs;
}
private void lblMagicDragArea_DragDrop(object sender, DragEventArgs e)
{
List<string> files = validateDrop(e.Data);
if (files.Count == 0) return;
try
{
this.Cursor = Cursors.WaitCursor;
foreach (var file in files)
{
var prefs = GetCuePrefs();
string ext = Path.GetExtension(file).ToUpper();
Disc disc = null;
if (ext == ".ISO")
disc = Disc.FromIsoPath(file);
else if (ext == ".CUE")
disc = Disc.FromCuePath(file, prefs);
else if (ext == ".CCD")
disc = Disc.FromCCDPath(file);
var disc = Disc.LoadAutomagic(file);
string baseName = Path.GetFileNameWithoutExtension(file);
baseName += "_hawked";
prefs.ReallyDumpBin = true;
var cueBin = disc.DumpCueBin(baseName, GetCuePrefs());
Dump(cueBin, Path.GetDirectoryName(file), prefs);
string outfile = Path.Combine(Path.GetDirectoryName(file), baseName) + ".ccd";
CCD_Format.Dump(disc, outfile);
}
this.Cursor = Cursors.Default;
}
catch (Exception ex)
{
@ -80,30 +62,30 @@ namespace BizHawk.Client.DiscoHawk
}
}
bool Dump(CueBin cueBin, string directoryTo, CueBinPrefs prefs)
{
ProgressReport pr = new ProgressReport();
Thread workThread = new Thread(() =>
{
cueBin.Dump(directoryTo, prefs, pr);
});
//bool Dump(CueBin cueBin, string directoryTo, CueBinPrefs prefs)
//{
// ProgressReport pr = new ProgressReport();
// Thread workThread = new Thread(() =>
// {
// cueBin.Dump(directoryTo, prefs, pr);
// });
ProgressDialog pd = new ProgressDialog(pr);
pd.Show(this);
this.Enabled = false;
workThread.Start();
for (; ; )
{
Application.DoEvents();
Thread.Sleep(10);
if (workThread.ThreadState != ThreadState.Running)
break;
pd.Update();
}
this.Enabled = true;
pd.Dispose();
return !pr.CancelSignal;
}
// ProgressDialog pd = new ProgressDialog(pr);
// pd.Show(this);
// this.Enabled = false;
// workThread.Start();
// for (; ; )
// {
// Application.DoEvents();
// Thread.Sleep(10);
// if (workThread.ThreadState != ThreadState.Running)
// break;
// pd.Update();
// }
// this.Enabled = true;
// pd.Dispose();
// return !pr.CancelSignal;
//}
private void lblMagicDragArea_DragEnter(object sender, DragEventArgs e)
{
@ -132,22 +114,23 @@ namespace BizHawk.Client.DiscoHawk
private void lblMp3ExtractMagicArea_DragDrop(object sender, DragEventArgs e)
{
var files = validateDrop(e.Data);
if (files.Count == 0) return;
foreach (var file in files)
{
using (var disc = Disc.FromCuePath(file, new CueBinPrefs()))
{
var path = Path.GetDirectoryName(file);
var filename = Path.GetFileNameWithoutExtension(file);
AudioExtractor.Extract(disc, path, filename);
}
}
// var files = validateDrop(e.Data);
// if (files.Count == 0) return;
// foreach (var file in files)
// {
// using (var disc = Disc.FromCuePath(file, new CueBinPrefs()))
// {
// var path = Path.GetDirectoryName(file);
// var filename = Path.GetFileNameWithoutExtension(file);
// AudioExtractor.Extract(disc, path, filename);
// }
// }
}
private void btnAbout_Click(object sender, EventArgs e)
{
new About().ShowDialog();
}
}
}

View File

@ -1,121 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using BizHawk.Emulation.DiscSystem;
class MednadiscTester
{
[DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mednadisc_LoadCD(string path);
[DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mednadisc_ReadSector(IntPtr disc, int lba, byte[] buf2448);
[DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void mednadisc_CloseCD(IntPtr disc);
public class QuickSubcodeReader
{
public QuickSubcodeReader(byte[] buffer)
{
this.buffer = buffer;
}
public void ReadLBA_SubchannelQ(int offset, ref SubchannelQ sq)
{
sq.q_status = buffer[offset + 0];
sq.q_tno = buffer[offset + 1];
sq.q_index = buffer[offset + 2];
sq.min.BCDValue = buffer[offset + 3];
sq.sec.BCDValue = buffer[offset + 4];
sq.frame.BCDValue = buffer[offset + 5];
//nothing in byte[6]
sq.ap_min.BCDValue = buffer[offset + 7];
sq.ap_sec.BCDValue = buffer[offset + 8];
sq.ap_frame.BCDValue = buffer[offset + 9];
//CRC is stored inverted and big endian.. so... do the opposite
byte hibyte = (byte)(~buffer[offset + 10]);
byte lobyte = (byte)(~buffer[offset + 11]);
sq.q_crc = (ushort)((hibyte << 8) | lobyte);
}
byte[] buffer;
}
public void TestDirectory(string dpTarget)
{
foreach (var fi in new DirectoryInfo(dpTarget).GetFiles())
{
if (fi.Extension.ToLower() == ".cue") { }
else if (fi.Extension.ToLower() == ".ccd") { }
else continue;
NewTest(fi.FullName);
}
}
static bool NewTest(string path)
{
bool ret = false;
Disc disc;
if (Path.GetExtension(path).ToLower() == ".cue") disc = Disc.FromCuePath(path, new CueBinPrefs());
else disc = Disc.FromCCDPath(path);
IntPtr mednadisc = mednadisc_LoadCD(path);
//TODO - test leadout a bit, or determine length some superior way
//TODO - check length against mednadisc
int nSectors = (int)(disc.Structure.BinarySize / 2352) - 150;
var subbuf = new byte[96];
var discbuf = new byte[2352 + 96];
var monkeybuf = new byte[2352 + 96];
var disc_qbuf = new byte[96];
var monkey_qbuf = new byte[96];
for (int i = 0; i < nSectors; i++)
{
mednadisc_ReadSector(mednadisc, i, monkeybuf);
disc.ReadLBA_2352(i, discbuf, 0);
disc.ReadLBA_SectorEntry(i).SubcodeSector.ReadSubcodeDeinterleaved(subbuf, 0);
SubcodeUtils.Interleave(subbuf, 0, discbuf, 2352);
//remove P
for (int q = 2352; q < 2352 + 96; q++)
{
discbuf[q] &= 0x7F;
monkeybuf[q] &= 0x7F;
}
for (int q = 0; q < 2352 + 96; q++)
{
if (discbuf[q] != monkeybuf[q])
{
Console.WriteLine("MISMATCH: " + Path.GetFileName(path));
//decode Q subchannels for manual investigation
SubcodeUtils.Deinterleave(discbuf, 2352, disc_qbuf, 0);
var asr = new QuickSubcodeReader(disc_qbuf);
SubchannelQ disc_q = new SubchannelQ();
asr.ReadLBA_SubchannelQ(12, ref disc_q);
SubcodeUtils.Deinterleave(monkeybuf, 2352, monkey_qbuf, 0);
asr = new QuickSubcodeReader(monkey_qbuf);
SubchannelQ monkey_q = new SubchannelQ();
asr.ReadLBA_SubchannelQ(12, ref monkey_q);
goto END;
}
}
}
ret = true;
END:
disc.Dispose();
mednadisc_CloseCD(mednadisc);
return ret;
}
}

View File

@ -0,0 +1,234 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Security.Cryptography;
namespace BizHawk.Common.BufferExtensions
{
public static class BufferExtensions
{
public static void SaveAsHex(this byte[] buffer, TextWriter writer)
{
foreach (var b in buffer)
{
writer.Write("{0:X2}", b);
}
writer.WriteLine();
}
public unsafe static void SaveAsHexFast(this byte[] buffer, TextWriter writer)
{
char* table = Util.HexConvPtr;
if (buffer.Length > 0)
{
int len = buffer.Length;
fixed (byte* src = &buffer[0])
for (int i = 0; i < len; i++)
{
writer.Write(table[src[i] >> 4]);
writer.Write(table[src[i] & 15]);
}
}
writer.WriteLine();
}
public static void SaveAsHex(this byte[] buffer, TextWriter writer, int length)
{
for (int i = 0; i < length; i++)
{
writer.Write("{0:X2}", buffer[i]);
}
writer.WriteLine();
}
public static void SaveAsHex(this short[] buffer, TextWriter writer)
{
foreach (var b in buffer)
{
writer.Write("{0:X4}", b);
}
writer.WriteLine();
}
public static void SaveAsHex(this ushort[] buffer, TextWriter writer)
{
foreach (var b in buffer)
{
writer.Write("{0:X4}", b);
}
writer.WriteLine();
}
public static void SaveAsHex(this int[] buffer, TextWriter writer)
{
foreach (int b in buffer)
{
writer.Write("{0:X8}", b);
}
writer.WriteLine();
}
public static void SaveAsHex(this uint[] buffer, TextWriter writer)
{
foreach (var b in buffer)
{
writer.Write("{0:X8}", b);
}
writer.WriteLine();
}
public static void ReadFromHex(this byte[] buffer, string hex)
{
if (hex.Length % 2 != 0)
{
throw new Exception("Hex value string does not appear to be properly formatted.");
}
for (int i = 0; i < buffer.Length && i * 2 < hex.Length; i++)
{
var bytehex = "" + hex[i * 2] + hex[i * 2 + 1];
buffer[i] = byte.Parse(bytehex, NumberStyles.HexNumber);
}
}
public static unsafe void ReadFromHexFast(this byte[] buffer, string hex)
{
if (buffer.Length * 2 != hex.Length)
{
throw new Exception("Data size mismatch");
}
int count = buffer.Length;
fixed (byte* _dst = buffer)
fixed (char* _src = hex)
{
byte* dst = _dst;
char* src = _src;
while (count > 0)
{
// in my tests, replacing Hex2Int() with a 256 entry LUT slowed things down slightly
*dst++ = (byte)(Hex2Int(*src++) << 4 | Hex2Int(*src++));
count--;
}
}
}
public static void ReadFromHex(this short[] buffer, string hex)
{
if (hex.Length % 4 != 0)
{
throw new Exception("Hex value string does not appear to be properly formatted.");
}
for (int i = 0; i < buffer.Length && i * 4 < hex.Length; i++)
{
var shorthex = "" + hex[i * 4] + hex[(i * 4) + 1] + hex[(i * 4) + 2] + hex[(i * 4) + 3];
buffer[i] = short.Parse(shorthex, NumberStyles.HexNumber);
}
}
public static void ReadFromHex(this ushort[] buffer, string hex)
{
if (hex.Length % 4 != 0)
{
throw new Exception("Hex value string does not appear to be properly formatted.");
}
for (int i = 0; i < buffer.Length && i * 4 < hex.Length; i++)
{
var ushorthex = "" + hex[i * 4] + hex[(i * 4) + 1] + hex[(i * 4) + 2] + hex[(i * 4) + 3];
buffer[i] = ushort.Parse(ushorthex, NumberStyles.HexNumber);
}
}
public static void ReadFromHex(this int[] buffer, string hex)
{
if (hex.Length % 8 != 0)
{
throw new Exception("Hex value string does not appear to be properly formatted.");
}
for (int i = 0; i < buffer.Length && i * 8 < hex.Length; i++)
{
//string inthex = "" + hex[i * 8] + hex[(i * 8) + 1] + hex[(i * 4) + 2] + hex[(i * 4) + 3] + hex[(i*4
var inthex = hex.Substring(i * 8, 8);
buffer[i] = int.Parse(inthex, NumberStyles.HexNumber);
}
}
/// <summary>
/// Converts bytes to an uppercase string of hex numbers in upper case without any spacing or anything
/// </summary>
public static string BytesToHexString(this byte[] bytes)
{
var sb = new StringBuilder();
foreach (var b in bytes)
{
sb.AppendFormat("{0:X2}", b);
}
return sb.ToString();
}
public static bool FindBytes(this byte[] array, byte[] pattern)
{
var fidx = 0;
int result = Array.FindIndex(array, 0, array.Length, (byte b) =>
{
fidx = (b == pattern[fidx]) ? fidx + 1 : 0;
return (fidx == pattern.Length);
});
return (result >= pattern.Length - 1);
}
public static string HashMD5(this byte[] data, int offset, int len)
{
using (var md5 = MD5.Create())
{
md5.ComputeHash(data, offset, len);
return md5.Hash.BytesToHexString();
}
}
public static string HashMD5(this byte[] data)
{
return HashMD5(data, 0, data.Length);
}
public static string HashSHA1(this byte[] data, int offset, int len)
{
using (var sha1 = SHA1.Create())
{
sha1.ComputeHash(data, offset, len);
return sha1.Hash.BytesToHexString();
}
}
public static string HashSHA1(this byte[] data)
{
return HashSHA1(data, 0, data.Length);
}
#region Helpers
private static int Hex2Int(char c)
{
if (c <= '9')
{
return c - '0';
}
if (c <= 'F')
{
return c - '7';
}
return c - 'W';
}
#endregion
}
}

View File

@ -94,6 +94,7 @@
<Compile Include="Interfaces\IVideoProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceInjector.cs" />
<Compile Include="Sound\CDAudio.cs" />
<Compile Include="Sound\HuC6280PSG.cs" />
<Compile Include="Sound\MMC5Audio.cs" />
<Compile Include="Sound\SN76489.cs" />
@ -117,6 +118,10 @@
<Project>{866f8d13-0678-4ff9-80a4-a3993fd4d8a3}</Project>
<Name>BizHawk.Common</Name>
</ProjectReference>
<ProjectReference Include="..\BizHawk.Emulation.DiscSystem\BizHawk.Emulation.DiscSystem.csproj">
<Project>{F51946EA-827F-4D82-B841-1F2F6D060312}</Project>
<Name>BizHawk.Emulation.DiscSystem</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>

View File

@ -101,6 +101,11 @@ namespace BizHawk.Emulation.Common
return int.Parse(Options[option]);
}
public string GetStringValue(string option)
{
return Options[option];
}
public int GetHexValue(string option)
{
return int.Parse(Options[option], NumberStyles.HexNumber);

View File

@ -2,13 +2,14 @@
using System.IO;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.DiscSystem;
// The state of the cd player is quantized to the frame level.
// This isn't ideal. But life's too short.
// I decided not to let the perfect be the enemy of the good.
// It can always be refactored. It's at least deterministic.
namespace BizHawk.Emulation.DiscSystem
namespace BizHawk.Emulation.Common.Components
{
public sealed class CDAudio : ISoundProvider
{
@ -24,6 +25,7 @@ namespace BizHawk.Emulation.DiscSystem
public Action CallbackAction = delegate { };
public Disc Disc;
public DiscSectorReader DiscSectorReader;
public byte Mode = CDAudioMode_Stopped;
public byte PlayMode = PlaybackMode_LoopOnCompletion;
@ -43,16 +45,20 @@ namespace BizHawk.Emulation.DiscSystem
public CDAudio(Disc disc, int maxVolume = short.MaxValue)
{
Disc = disc;
DiscSectorReader = new DiscSectorReader(disc);
MaxVolume = maxVolume;
}
public void PlayTrack(int track)
{
if (track < 1 || track > Disc.Structure.Sessions[0].Tracks.Count)
if (track < 1 || track > Disc.Session1.InformationTrackCount)
return;
StartLBA = Disc.Structure.Sessions[0].Tracks[track - 1].Indexes[1].aba - 150;
EndLBA = StartLBA + Disc.Structure.Sessions[0].Tracks[track - 1].LengthInSectors;
StartLBA = Disc.Session1.Tracks[track].LBA;
//play until the beginning of the next track (?)
EndLBA = Disc.Session1.Tracks[track + 1].LBA;
PlayingTrack = track;
CurrentSector = StartLBA;
SectorOffset = 0;
@ -64,12 +70,14 @@ namespace BizHawk.Emulation.DiscSystem
public void PlayStartingAtLba(int lba)
{
var point = Disc.Structure.SeekPoint(lba);
if (point == null || point.Track == null) return;
var track = Disc.Session1.SeekTrack(lba);
if (track == null) return;
PlayingTrack = point.TrackNum;
PlayingTrack = track.Number;
StartLBA = lba;
EndLBA = point.Track.Indexes[1].aba + point.Track.LengthInSectors - 150;
//play until the beginning of the next track (?)
EndLBA = Disc.Session1.Tracks[track.Number + 1].LBA;
CurrentSector = StartLBA;
SectorOffset = 0;
@ -121,10 +129,10 @@ namespace BizHawk.Emulation.DiscSystem
{
if (CachedSector != CurrentSector)
{
if (CurrentSector >= Disc.LBACount)
if (CurrentSector >= Disc.Session1.LeadoutLBA)
Array.Clear(SectorCache, 0, 2352); // request reading past end of available disc
else
Disc.ReadLBA_2352(CurrentSector, SectorCache, 0);
DiscSectorReader.ReadLBA_2352(CurrentSector, SectorCache, 0);
CachedSector = CurrentSector;
}
}

View File

@ -59,9 +59,6 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<ItemGroup>
<Reference Include="ELFSharp">
<HintPath>..\References\ELFSharp.dll</HintPath>
</Reference>
<Reference Include="EMU7800">
<HintPath>..\References\EMU7800.dll</HintPath>
</Reference>
@ -141,7 +138,7 @@
<DependentUpon>AppleII.cs</DependentUpon>
</Compile>
<Compile Include="Computers\AppleII\AppleII.ISettable.cs">
<DependentUpon>AppleII.cs</DependentUpon>
<DependentUpon>AppleII.cs</DependentUpon>
</Compile>
<Compile Include="Computers\AppleII\AppleII.IStatable.cs">
<DependentUpon>AppleII.cs</DependentUpon>
@ -338,7 +335,7 @@
<Compile Include="Consoles\Intellivision\Intellicart.cs" />
<Compile Include="Consoles\Intellivision\Intellivision.cs" />
<Compile Include="Consoles\Intellivision\Intellivision.IEmulator.cs">
<DependentUpon>Intellivision.cs</DependentUpon>
<DependentUpon>Intellivision.cs</DependentUpon>
</Compile>
<Compile Include="Consoles\Intellivision\Intellivision.MemoryMap.cs">
<DependentUpon>Intellivision.cs</DependentUpon>
@ -737,10 +734,7 @@
<Compile Include="Consoles\Sega\gpgx\GenDbgHlp.cs" />
<Compile Include="Consoles\Sega\gpgx\GPGX.cs" />
<Compile Include="Consoles\Sega\gpgx\GPGXControlConverter.cs" />
<Compile Include="Consoles\Sega\gpgx\GPGXControlConverterDynamic.cs" />
<Compile Include="Consoles\Sega\gpgx\GPGXDynamic.cs" />
<Compile Include="Consoles\Sega\gpgx\LibGPGX.cs" />
<Compile Include="Consoles\Sega\gpgx\LibGPGXDynamic.cs" />
<Compile Include="Consoles\Sega\Saturn\FilePiping.cs" />
<Compile Include="Consoles\Sega\Saturn\LibYabause.cs" />
<Compile Include="Consoles\Sega\Saturn\Yabause.cs" />
@ -833,7 +827,6 @@
<Compile Include="CPUs\Z80\Registers.cs" />
<Compile Include="CPUs\Z80\Tables.cs" />
<Compile Include="CPUs\Z80\Z80A.cs" />
<Compile Include="ElfRunner.cs" />
<Compile Include="FileID.cs" />
<Compile Include="LibRetro.cs" />
<Compile Include="LibRetroEmulator.cs" />
@ -845,7 +838,6 @@
<Compile Include="Consoles\PC Engine\VDC.cs" />
<Compile Include="Consoles\PC Engine\VDC.Render.cs" />
<Compile Include="Consoles\PC Engine\VPC.cs" />
<Compile Include="MemoryBlock.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Consoles\Sega\Genesis\Genesis.cs" />
<Compile Include="Consoles\Sega\Genesis\GenVDP.cs" />

View File

@ -147,7 +147,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
Init(game, rom);
// the default RomStatusDetails don't do anything with Disc
CoreComm.RomStatusDetails = string.Format("{0}\r\nDisk partial hash:{1}", game.Name, disc.GetHash());
CoreComm.RomStatusDetails = string.Format("{0}\r\nDisk partial hash:{1}", game.Name, new DiscSystem.DiscHasher(disc).OldHash());
SetControllerButtons();
}

View File

@ -5,6 +5,7 @@ using System.Globalization;
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Common.Components;
using BizHawk.Emulation.DiscSystem;
namespace BizHawk.Emulation.Cores.PCEngine
@ -147,7 +148,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
PCEngine pce;
public Disc disc;
SubcodeReader subcodeReader;
DiscSectorReader DiscSectorReader;
SubchannelQ subchannelQ;
int audioStartLBA;
int audioEndLBA;
@ -156,7 +157,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
{
this.pce = pce;
this.disc = disc;
subcodeReader = new SubcodeReader(disc);
DiscSectorReader = new DiscSectorReader(disc);
}
public void Think()
@ -175,7 +176,8 @@ namespace BizHawk.Emulation.Cores.PCEngine
if (DataIn.Count == 0)
{
// read in a sector and shove it in the queue
disc.ReadLBA_2048(CurrentReadingSector, DataIn.GetBuffer(), 0);
DiscSystem.DiscSectorReader dsr = new DiscSectorReader(disc); //TODO - cache reader
dsr.ReadLBA_2048(CurrentReadingSector, DataIn.GetBuffer(), 0);
DataIn.SignalBufferFilled(2048);
CurrentReadingSector++;
SectorsLeftToRead--;
@ -412,16 +414,16 @@ namespace BizHawk.Emulation.Cores.PCEngine
audioStartLBA = (CommandBuffer[3] << 16) | (CommandBuffer[4] << 8) | CommandBuffer[5];
break;
case 0x40: // Set start offset in MSF units
case 0x40: // Set start offset in absolute MSF units
byte m = CommandBuffer[2].BCDtoBin();
byte s = CommandBuffer[3].BCDtoBin();
byte f = CommandBuffer[4].BCDtoBin();
audioStartLBA = Disc.ConvertMSFtoLBA(m, s, f);
audioStartLBA = DiscUtils.Convert_AMSF_To_LBA(m, s, f);
break;
case 0x80: // Set start offset in track units
byte trackNo = CommandBuffer[2].BCDtoBin();
audioStartLBA = disc.Structure.Sessions[0].Tracks[trackNo - 1].Indexes[1].aba - 150;
audioStartLBA = disc.Session1.Tracks[trackNo].LBA;
break;
}
@ -447,19 +449,19 @@ namespace BizHawk.Emulation.Cores.PCEngine
audioEndLBA = (CommandBuffer[3] << 16) | (CommandBuffer[4] << 8) | CommandBuffer[5];
break;
case 0x40: // Set end offset in MSF units
case 0x40: // Set end offset in absolute MSF units
byte m = CommandBuffer[2].BCDtoBin();
byte s = CommandBuffer[3].BCDtoBin();
byte f = CommandBuffer[4].BCDtoBin();
audioEndLBA = Disc.ConvertMSFtoLBA(m, s, f);
audioEndLBA = DiscUtils.Convert_AMSF_To_LBA(m, s, f);
break;
case 0x80: // Set end offset in track units
byte trackNo = CommandBuffer[2].BCDtoBin();
if (trackNo - 1 >= disc.Structure.Sessions[0].Tracks.Count)
audioEndLBA = disc.LBACount;
if (trackNo - 1 >= disc.Session1.Tracks.Count)
audioEndLBA = disc.Session1.LeadoutLBA;
else
audioEndLBA = disc.Structure.Sessions[0].Tracks[trackNo - 1].Indexes[1].aba - 150;
audioEndLBA = disc.Session1.Tracks[trackNo].LBA;
break;
}
@ -507,10 +509,10 @@ namespace BizHawk.Emulation.Cores.PCEngine
case CDAudio.CDAudioMode_Stopped: DataIn.Enqueue(3); break;
}
subcodeReader.ReadLBA_SubchannelQ(sectorNum, ref subchannelQ);
DataIn.Enqueue(subchannelQ.q_status); // I do not know what status is
DataIn.Enqueue(subchannelQ.q_tno); // track
DataIn.Enqueue(subchannelQ.q_index); // index
DiscSectorReader.ReadLBA_SubQ(sectorNum, out subchannelQ);
DataIn.Enqueue(subchannelQ.q_status); //status (control and q-mode; control is useful to know if it's a data or audio track)
DataIn.Enqueue(subchannelQ.q_tno.BCDValue); // track //zero 03-jul-2015 - did I adapt this right>
DataIn.Enqueue(subchannelQ.q_index.BCDValue); // index //zero 03-jul-2015 - did I adapt this right>
DataIn.Enqueue(subchannelQ.min.BCDValue); // M(rel)
DataIn.Enqueue(subchannelQ.sec.BCDValue); // S(rel)
DataIn.Enqueue(subchannelQ.frame.BCDValue); // F(rel)
@ -529,16 +531,17 @@ namespace BizHawk.Emulation.Cores.PCEngine
{
DataIn.Clear();
DataIn.Enqueue(0x01);
DataIn.Enqueue(((byte)disc.Structure.Sessions[0].Tracks.Count).BinToBCD());
DataIn.Enqueue(((byte)disc.Session1.Tracks.Count).BinToBCD());
SetPhase(BusPhase_DataIn);
break;
}
case 1: // return total disc length in minutes/seconds/frames
{
int totalLbaLength = disc.LBACount;
//zero 07-jul-2015 - I may have broken this
int totalLbaLength = disc.Session1.LeadoutLBA;
byte m, s, f;
Disc.ConvertLBAtoMSF(totalLbaLength, out m, out s, out f);
DiscUtils.Convert_LBA_To_AMSF(totalLbaLength + 150, out m, out s, out f);
DataIn.Clear();
DataIn.Enqueue(m.BinToBCD());
@ -547,32 +550,30 @@ namespace BizHawk.Emulation.Cores.PCEngine
SetPhase(BusPhase_DataIn);
break;
}
case 2: // Return starting position of specified track in MSF format
case 2: // Return starting position of specified track in MSF format. TODO - did zero adapt this right? track indexing might be off
{
int track = CommandBuffer[2].BCDtoBin();
var tracks = disc.Structure.Sessions[0].Tracks;
var tracks = disc.Session1.Tracks;
if (CommandBuffer[2] > 0x99)
throw new Exception("invalid track number BCD request... is something I need to handle?");
if (track == 0) track = 1;
track--;
int lbaPos;
if (track > tracks.Count)
lbaPos = disc.Structure.Sessions[0].length_aba - 150;
if (track > disc.Session1.InformationTrackCount)
lbaPos = disc.Session1.LeadoutLBA; //zero 03-jul-2015 - did I adapt this right?
else
lbaPos = tracks[track].Indexes[1].aba - 150;
lbaPos = tracks[track].LBA;
byte m, s, f;
Disc.ConvertLBAtoMSF(lbaPos, out m, out s, out f);
DiscUtils.Convert_LBA_To_AMSF(lbaPos, out m, out s, out f);
DataIn.Clear();
DataIn.Enqueue(m.BinToBCD());
DataIn.Enqueue(s.BinToBCD());
DataIn.Enqueue(f.BinToBCD());
if (track > tracks.Count || disc.Structure.Sessions[0].Tracks[track].TrackType == ETrackType.Audio)
if (track > tracks.Count || disc.Session1.Tracks[track].IsAudio)
DataIn.Enqueue(0);
else
DataIn.Enqueue(4);

View File

@ -41,6 +41,7 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn
static Yabause AttachedCore = null;
GCHandle VideoHandle;
Disc CD;
DiscSectorReader DiscSectorReader;
GCHandle SoundHandle;
bool Disposed = false;
@ -59,9 +60,10 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn
{
ServiceProvider = new BasicServiceProvider(this);
byte[] bios = CoreComm.CoreFileProvider.GetFirmware("SAT", "J", true, "Saturn BIOS is required.");
CoreComm.RomStatusDetails = string.Format("Disk partial hash:{0}", CD.GetHash());
CoreComm.RomStatusDetails = string.Format("Disk partial hash:{0}", new DiscSystem.DiscHasher(CD).OldHash());
this.CoreComm = CoreComm;
this.CD = CD;
DiscSectorReader = new DiscSystem.DiscSectorReader(CD);
SyncSettings = (SaturnSyncSettings)syncSettings ?? new SaturnSyncSettings();
@ -390,31 +392,25 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn
int CD_ReadTOC(IntPtr dest)
{
// this stuff from yabause's cdbase.c. don't ask me to explain it
//TODO - we could just get this out of the actual TOC, it's the same thing
var TOC = CD.ReadStructure();
int[] rTOC = new int[102];
var ses = TOC.Sessions[0];
var ses = CD.Session1;
int ntrk = ses.Tracks.Count;
for (int i = 0; i < 99; i++)
for (int i = 1; i <= 99; i++)
{
if (i < ntrk)
{
var trk = ses.Tracks[i];
uint t = (uint)trk.Indexes[1].aba;
uint t = (uint)trk.LBA + 150;
if(trk.IsAudio)
t |= 0x01000000;
else
t |= 0x41000000;
switch (trk.TrackType)
{
case DiscSystem.ETrackType.Audio:
t |= 0x01000000;
break;
case DiscSystem.ETrackType.Mode1_2048:
case DiscSystem.ETrackType.Mode1_2352:
case DiscSystem.ETrackType.Mode2_2352:
t |= 0x41000000;
break;
}
rTOC[i] = (int)t;
}
else
@ -425,7 +421,8 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn
rTOC[99] = (int)(rTOC[0] & 0xff000000 | 0x010000);
rTOC[100] = (int)(rTOC[ntrk - 1] & 0xff000000 | (uint)(ntrk << 16));
rTOC[101] = (int)(rTOC[ntrk - 1] & 0xff000000 | (uint)(ses.length_aba));
rTOC[101] = (int)(rTOC[ntrk - 1] & 0xff000000 | (uint)(CD.TOC.LeadoutLBA.Sector)); //zero 03-jul-2014 - maybe off by 150
Marshal.Copy(rTOC, 0, dest, 102);
return 408;
@ -442,7 +439,8 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn
byte[] data = new byte[2352];
try
{
CD.ReadABA_2352(FAD, data, 0);
//CD.ReadABA_2352(FAD, data, 0);
DiscSectorReader.ReadLBA_2352(FAD - 150, data, 0); //zero 21-jun-2015 - did I adapt this right?
}
catch (Exception e)
{

View File

@ -31,6 +31,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
static GPGX AttachedCore = null;
DiscSystem.Disc CD;
DiscSystem.DiscSectorReader DiscSectorReader;
byte[] romfile;
bool drivelight;
@ -91,6 +92,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
this.romfile = rom;
this.CD = CD;
this.DiscSectorReader = new DiscSystem.DiscSectorReader(CD);
LibGPGX.INPUT_SYSTEM system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_NONE;
LibGPGX.INPUT_SYSTEM system_b = LibGPGX.INPUT_SYSTEM.SYSTEM_NONE;
@ -289,9 +291,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
if (audio)
{
byte[] data = new byte[2352];
if (lba < CD.LBACount)
if (lba < CD.Session1.LeadoutLBA)
{
CD.ReadLBA_2352(lba, data, 0);
DiscSectorReader.ReadLBA_2352(lba, data, 0);
}
else
{
@ -304,7 +306,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
else
{
byte[] data = new byte[2048];
CD.ReadLBA_2048(lba, data, 0);
DiscSectorReader.ReadLBA_2048(lba, data, 0);
Marshal.Copy(data, 0, dest, 2048);
drivelight = true;
}
@ -319,16 +321,17 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
ret.readcallback = cd_callback_handle = new LibGPGX.cd_read_cb(CDRead);
var ses = CD.Structure.Sessions[0];
var ses = CD.Session1;
int ntrack = ses.Tracks.Count;
// bet you a dollar this is all wrong
//zero 07-jul-2015 - throws a dollar in the pile, since he probably messed it up worse
for (int i = 0; i < LibGPGX.CD_MAX_TRACKS; i++)
{
if (i < ntrack)
{
ret.tracks[i].start = ses.Tracks[i].Indexes[1].aba - 150;
ret.tracks[i].end = ses.Tracks[i].LengthInSectors + ret.tracks[i].start;
ret.tracks[i].start = ses.Tracks[i].LBA;
ret.tracks[i].end = ses.Tracks[i + 1].LBA;
if (i == ntrack - 1)
{
ret.end = ret.tracks[i].end;

View File

@ -142,7 +142,7 @@ namespace BizHawk.Emulation.Cores.Sony.PSX
cbReadTOC = ShockDisc_ReadTOC;
cbReadLBA = ShockDisc_ReadLBA2448;
this.cbActivity = cbActivity;
OctoshockDll.shock_CreateDisc(out OctoshockHandle, IntPtr.Zero, disc.LBACount, cbReadTOC, cbReadLBA, true);
OctoshockDll.shock_CreateDisc(out OctoshockHandle, IntPtr.Zero, disc.Session1.LeadoutLBA, cbReadTOC, cbReadLBA, true);
}
OctoshockDll.ShockDisc_ReadTOC cbReadTOC;
@ -160,16 +160,16 @@ namespace BizHawk.Emulation.Cores.Sony.PSX
int ShockDisc_ReadTOC(IntPtr opaque, OctoshockDll.ShockTOC* read_target, OctoshockDll.ShockTOCTrack* tracks101)
{
read_target->disc_type = 1; //hardcoded in octoshock
read_target->first_track = (byte)Disc.TOCRaw.FirstRecordedTrackNumber; //i _think_ thats what is meant here
read_target->last_track = (byte)Disc.TOCRaw.LastRecordedTrackNumber; //i _think_ thats what is meant here
read_target->disc_type = (byte)Disc.TOC.Session1Format;
read_target->first_track = (byte)Disc.TOC.FirstRecordedTrackNumber; //i _think_ thats what is meant here
read_target->last_track = (byte)Disc.TOC.LastRecordedTrackNumber; //i _think_ thats what is meant here
tracks101[0].lba = tracks101[0].adr = tracks101[0].control = 0;
for (int i = 1; i < 100; i++)
{
var item = Disc.TOCRaw.TOCItems[i];
tracks101[i].adr = 1; //not sure what this is
var item = Disc.TOC.TOCItems[i];
tracks101[i].adr = (byte)(item.Exists ? 1 : 0);
tracks101[i].lba = (uint)item.LBATimestamp.Sector;
tracks101[i].control = (byte)item.Control;
}
@ -177,14 +177,7 @@ namespace BizHawk.Emulation.Cores.Sony.PSX
////the lead-out track is to be synthesized
tracks101[read_target->last_track + 1].adr = 1;
tracks101[read_target->last_track + 1].control = 0;
tracks101[read_target->last_track + 1].lba = (uint)Disc.TOCRaw.LeadoutTimestamp.Sector;
////laaaame
//tracks101[read_target->last_track + 1].lba =
// (uint)(
// Disc.Structure.Sessions[0].Tracks[read_target->last_track - 1].Start_ABA //AUGH. see comment in Start_ABA
// + Disc.Structure.Sessions[0].Tracks[read_target->last_track - 1].LengthInSectors
// - 150
// );
tracks101[read_target->last_track + 1].lba = (uint)Disc.TOC.LeadoutLBA.Sector;
//element 100 is to be copied as the lead-out track
tracks101[100] = tracks101[read_target->last_track + 1];
@ -192,7 +185,7 @@ namespace BizHawk.Emulation.Cores.Sony.PSX
return OctoshockDll.SHOCK_OK;
}
byte[] SectorBuffer = new byte[2352];
byte[] SectorBuffer = new byte[2448];
int ShockDisc_ReadLBA2448(IntPtr opaque, int lba, void* dst)
{
@ -205,17 +198,17 @@ namespace BizHawk.Emulation.Cores.Sony.PSX
if (subcodeLog) Console.Write("{0}|", lba);
else if (readLog) Console.WriteLine("Read Sector: " + lba);
Disc.ReadLBA_2352(lba, SectorBuffer, 0);
Marshal.Copy(SectorBuffer, 0, new IntPtr(dst), 2352);
Disc.ReadLBA_SectorEntry(lba).SubcodeSector.ReadSubcodeDeinterleaved(SectorBuffer, 0);
Marshal.Copy(SectorBuffer, 0, new IntPtr((byte*)dst + 2352), 96);
//todo - cache reader
DiscSystem.DiscSectorReader dsr = new DiscSystem.DiscSectorReader(Disc);
dsr.ReadLBA_2448(lba, SectorBuffer, 0);
Marshal.Copy(SectorBuffer, 0, new IntPtr(dst), 2448);
if (subcodeLog)
{
for (int i = 0; i < 24; i++)
Console.Write("{0:X2}", *((byte*)dst + 2352 + i));
Console.WriteLine();
}
//if (subcodeLog)
//{
// for (int i = 0; i < 24; i++)
// Console.Write("{0:X2}", *((byte*)dst + 2352 + i));
// Console.WriteLine();
//}
return OctoshockDll.SHOCK_OK;
}

View File

@ -218,8 +218,9 @@ namespace BizHawk.Emulation.Cores
FileIDResults IdentifyDisc(IdentifyJob job)
{
var discIdentifier = new DiscSystem.DiscIdentifier(job.Disc);
//DiscSystem could use some newer approaches from this file (instead of parsing ISO filesystem... maybe?)
switch (job.Disc.DetectDiscType())
switch (discIdentifier.DetectDiscType())
{
case DiscSystem.DiscType.SegaSaturn:
return new FileIDResults(new FileIDResult(FileIDType.Saturn, 100));
@ -585,6 +586,9 @@ namespace BizHawk.Emulation.Cores
//well... guess it's a disc.
//since it's just a bin, we dont need the user to provide a DiscSystem disc.
//lets just analyze this as best we can.
//of course, it's a lot of redundant logic with the discsystem disc checker.
//but you kind of need different approaches when loading a hugely unstructured bin file
//discsystem won't really even be happy mounting a .bin anyway, and we don't want it to be
//for PSX, we have a magic word to look for.
//it's at 0x24E0 with a mode2 (2352 byte) track 1.
@ -600,13 +604,13 @@ namespace BizHawk.Emulation.Cores
return ret;
}
//it's not proven that this is reliable. this is actually part of the mode1 CDFS header. perhaps it's mobile?
//it's not proven that this is reliable. this is actually part of the Mode 1 CDFS header. perhaps it's mobile?
//if it's mobile, we'll need to mount it as an ISO file here via discsystem
if (CheckMagic(job.Stream, SimpleMagics.PSP))
return new FileIDResult(FileIDType.PSP, 95);
//if this was an ISO, we might discover the magic word at offset 0...
//if it was a mode2/2352 bin, we might discover it at offset 16 (after the sync)
//if it was a mode2/2352 bin, we might discover it at offset 16 (after the data sector header)
if(CheckMagic(job.Stream, SimpleMagics.SEGASATURN,0))
return new FileIDResult(FileIDType.Saturn, job.Extension == "ISO" ? 95 : 90);
if (CheckMagic(job.Stream, SimpleMagics.SEGASATURN, 16))

View File

@ -0,0 +1,164 @@
using System;
using System.Text;
using System.IO;
using System.Globalization;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// A resource representing a disc opened and mounted through the MednaDisc component.
/// Does not attempt to virtually present the disc as a BizHawk disc - that will be the
/// responsibility of the user of this code
/// </summary>
public unsafe class MednaDisc : IDisposable
{
public MednaDisc(string pathToDisc)
{
if (!IsLibraryAvailable)
throw new InvalidOperationException("MednaDisc library is not available!");
handle = mednadisc_LoadCD(pathToDisc);
if (handle == IntPtr.Zero)
throw new InvalidOperationException("Failed to load MednaDisc: " + pathToDisc);
//read the mednafen toc
TOCTracks = new MednadiscTOCTrack[101];
fixed (MednadiscTOCTrack* _tracks = &TOCTracks[0])
fixed(MednadiscTOC* _toc = &TOC)
mednadisc_ReadTOC(handle, _toc, _tracks);
//leave the disc open until this is disposed so we can read sectors from it
}
IntPtr handle;
public MednadiscTOC TOC;
public MednadiscTOCTrack[] TOCTracks;
[ThreadStatic] static byte[] buf2442 = new byte[2448];
[ThreadStatic] static byte[] buf96 = new byte[96];
public void Read_2442(int LBA, byte[] buffer, int offset)
{
//read directly into the target buffer
fixed(byte* pBuffer = &buffer[0])
mednadisc_ReadSector(handle, LBA, pBuffer + offset);
}
//public void ReadSubcodeDeinterleaved(int LBA, byte[] buffer, int offset)
//{
// fixed (byte* pBuffer = buf2442)
// mednadisc_ReadSector(handle, LBA, pBuffer);
// SubcodeUtils.Deinterleave(buf2442, 2352, buffer, offset);
//}
//public void ReadSubcodeChannel(int LBA, int number, byte[] buffer, int offset)
//{
// fixed (byte* pBuffer = buf2442)
// mednadisc_ReadSector(handle, LBA, pBuffer);
// SubcodeUtils.Deinterleave(buf2442, 2352, buf96, 0);
// for (int i = 0; i < 12; i++)
// buffer[offset + i] = buf96[number * 12 + i];
//}
//public void Read_2352(int LBA, byte[] buffer, int offset)
//{
// fixed (byte* pBuffer = buf2442)
// mednadisc_ReadSector(handle, LBA, pBuffer);
// Buffer.BlockCopy(buf2442, 0, buffer, offset, 2352);
//}
//public void Read_2048(int LBA, byte[] buffer, int offset)
//{
// //this depends on CD-XA mode and such. so we need to read the mode bytes
// //HEY!!!!!! SHOULD THIS BE DONE BASED ON THE CLAIMED TRACK TYPE, OR ON WHATS IN THE SECTOR?
// //this is kind of a function of the CD reader.. it's not clear how this function should work.
// //YIKES!!!!!!!!!!!!!!
// //well, we need to scrutinize it for CCD files anyway, so...
// //this sucks.
// fixed (byte* pBuffer = buf2442)
// mednadisc_ReadSector(handle, LBA, pBuffer);
// byte mode = buf2442[15];
// if (mode == 1)
// Buffer.BlockCopy(buf2442, 16, buffer, offset, 2048);
// else
// Buffer.BlockCopy(buf2442, 24, buffer, offset, 2048); //PSX assumptions about CD-XA.. BAD BAD BAD
//}
static void CheckLibrary()
{
IntPtr lib = LoadLibrary("mednadisc.dll");
if (lib == IntPtr.Zero)
{
_IsLibraryAvailable = false;
return;
}
IntPtr addr = GetProcAddress(lib, "mednadisc_LoadCD");
FreeLibrary(lib);
if (addr == IntPtr.Zero)
{
_IsLibraryAvailable = false;
}
_IsLibraryAvailable = true;
}
static MednaDisc()
{
CheckLibrary();
}
static bool _IsLibraryAvailable;
public static bool IsLibraryAvailable { get { return _IsLibraryAvailable; } }
public void Dispose()
{
if(handle == IntPtr.Zero) return;
mednadisc_CloseCD(handle);
}
[StructLayout(LayoutKind.Sequential)]
public struct MednadiscTOC
{
public byte first_track;
public byte last_track;
public byte disc_type;
};
[StructLayout(LayoutKind.Explicit)]
public struct MednadiscTOCTrack
{
[FieldOffset(0)] public byte adr;
[FieldOffset(1)] public byte control;
[FieldOffset(4)] public uint lba;
//can't be a bool due to marshalling...
[FieldOffset(8)] public byte _validByte;
public bool Valid { get { return _validByte != 0; } }
};
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
static extern bool FreeLibrary(IntPtr hModule);
[DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mednadisc_LoadCD(string path);
[DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mednadisc_ReadSector(IntPtr disc, int lba, byte* buf2448);
[DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void mednadisc_CloseCD(IntPtr disc);
[DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void mednadisc_ReadTOC(IntPtr disc, MednadiscTOC* toc, MednadiscTOCTrack* tracks101);
}
}

View File

@ -50,53 +50,70 @@
<Compile Include="..\Version\VersionInfo.cs">
<Link>VersionInfo.cs</Link>
</Compile>
<Compile Include="Blobs\Blob_ECM.cs" />
<Compile Include="Blobs\Blob_RawFile.cs" />
<Compile Include="Blobs\Blob_WaveFile.cs" />
<Compile Include="Blobs\Blob_ZeroPadAdapter.cs" />
<Compile Include="Blobs\IBlob.cs" />
<Compile Include="Blobs\RiffMaster.cs" />
<Compile Include="CCD_format.cs" />
<Compile Include="CDAudio.cs" />
<Compile Include="cdfs\EndianBitConverter.cs" />
<Compile Include="cdfs\ISODirectoryNode.cs" />
<Compile Include="cdfs\ISOFile.cs" />
<Compile Include="cdfs\ISOFileNode.cs" />
<Compile Include="cdfs\ISONode.cs" />
<Compile Include="cdfs\ISONodeRecord.cs" />
<Compile Include="cdfs\ISOVolumeDescriptor.cs" />
<Compile Include="CUE_format.cs" />
<Compile Include="Decoding.cs" />
<Compile Include="Disc.API.cs" />
<Compile Include="API_MednaDisc.cs" />
<Compile Include="CDFS\EndianBitConverter.cs" />
<Compile Include="CDFS\ISODirectoryNode.cs" />
<Compile Include="CDFS\ISOFile.cs" />
<Compile Include="CDFS\ISOFileNode.cs" />
<Compile Include="CDFS\ISONode.cs" />
<Compile Include="CDFS\ISONodeRecord.cs" />
<Compile Include="CDFS\ISOVolumeDescriptor.cs" />
<Compile Include="Disc.cs" />
<Compile Include="Disc.ID.cs" />
<Compile Include="DiscDecoding.cs" />
<Compile Include="DiscExceptions.cs" />
<Compile Include="DiscFormats\Blobs\Blob_ECM.cs" />
<Compile Include="DiscFormats\Blobs\Blob_RawFile.cs" />
<Compile Include="DiscFormats\Blobs\Blob_WaveFile.cs" />
<Compile Include="DiscFormats\Blobs\Blob_ZeroPadAdapter.cs" />
<Compile Include="DiscFormats\Blobs\IBlob.cs" />
<Compile Include="DiscFormats\Blobs\RiffMaster.cs" />
<Compile Include="DiscFormats\CCD_format.cs" />
<Compile Include="DiscFormats\CUE\CueFileResolver.cs" />
<Compile Include="DiscFormats\CUE\CUE_Compile.cs" />
<Compile Include="DiscFormats\CUE\CUE_Context.cs" />
<Compile Include="DiscFormats\CUE\CUE_Load.cs" />
<Compile Include="DiscFormats\CUE\CUE_Parse.cs" />
<Compile Include="DiscFormats\CUE\CUE_Synths.cs" />
<Compile Include="DiscFormats\M3U_file.cs" />
<Compile Include="DiscFormats\SBI_format.cs" />
<Compile Include="DiscFormats\TOC_format.cs" />
<Compile Include="DiscHasher.cs" />
<Compile Include="DiscIdentifier.cs" />
<Compile Include="DiscJob.cs" />
<Compile Include="DiscMountJob.cs" />
<Compile Include="DiscMountJob.MednaDisc.cs" />
<Compile Include="DiscMountPolicy.cs" />
<Compile Include="DiscSectorReader.cs" />
<Compile Include="DiscStream.cs" />
<Compile Include="DiscStructure.cs" />
<Compile Include="DiscSubQ.cs" />
<Compile Include="DiscTOC.cs" />
<Compile Include="DiscTypes.cs" />
<Compile Include="DiscUtils.cs" />
<Compile Include="ECM.cs" />
<Compile Include="GPL_ECM.cs" />
<Compile Include="M3U_file.cs" />
<Compile Include="Internal\Algorithms\ECM.cs" />
<Compile Include="Internal\Algorithms\GPL_ECM.cs" />
<Compile Include="Internal\Algorithms\SubQ_CRC.cs" />
<Compile Include="Internal\Jobs\ApplySBIJob.cs" />
<Compile Include="Internal\Jobs\LoadSBIJob.cs" />
<Compile Include="Internal\Jobs\Synthesize_A0A1A2_Job.cs" />
<Compile Include="Internal\Jobs\Synthesize_DiscStructure_From_DiscTOC_Job.cs" />
<Compile Include="Internal\Jobs\Synthesize_DiscTOC_From_RawTOCEntries_Job.cs" />
<Compile Include="Internal\Jobs\Synthesize_Leadout_Job.cs" />
<Compile Include="Internal\SectorSynth.cs" />
<Compile Include="Internal\SynthUtils.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SBI_format.cs" />
<Compile Include="SectorInterfaces.cs" />
<Compile Include="Subcode.cs" />
<Compile Include="TOC\DiscStructure.cs" />
<Compile Include="TOC\TOCRaw.cs" />
<Compile Include="TOC_format.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BizHawk.Common\BizHawk.Common.csproj">
<Project>{866f8d13-0678-4ff9-80a4-a3993fd4d8a3}</Project>
<Name>BizHawk.Common</Name>
</ProjectReference>
<ProjectReference Include="..\BizHawk.Emulation.Common\BizHawk.Emulation.Common.csproj">
<Project>{E1A23168-B571-411C-B360-2229E7225E0E}</Project>
<Name>BizHawk.Emulation.Common</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="CDFS\origin.txt" />
<Content Include="docs\notes.txt" />
<Content Include="docs\todo.txt" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>

View File

@ -1,70 +0,0 @@
using System;
using System.IO;
namespace BizHawk.Emulation.DiscSystem
{
public partial class Disc : IDisposable
{
public sealed class Blob_ZeroPadAdapter : IBlob
{
public Blob_ZeroPadAdapter(IBlob baseBlob, long padFrom, long padLen)
{
this.baseBlob = baseBlob;
this.padFrom = padFrom;
this.padLen = padLen;
}
public int Read(long byte_pos, byte[] buffer, int offset, int count)
{
//I have an ff9 disc which can demo this
throw new NotImplementedException("Blob_ZeroPadAdapter hasnt been tested yet! please report this!");
//something about this seems unnecessarily complex, but... i dunno.
/*
//figure out how much remains until the zero-padding begins
long remain = byte_pos - padFrom;
int todo;
if (remain < count)
todo = (int)remain;
else todo = count;
//read up until the zero-padding
int totalRead = 0;
int readed = baseBlob.Read(byte_pos, buffer, offset, todo);
totalRead += readed;
offset += todo;
//if we didnt read enough, we certainly shouldnt try to read any more
if (readed < todo)
return readed;
//if that was all we needed, then we're done
count -= todo;
if (count == 0)
return totalRead;
//if we need more, it must come from zero-padding
remain = padLen;
if (remain < count)
todo = (int)remain;
else todo = count;
Array.Clear(buffer, offset, todo);
totalRead += todo;
return totalRead;
*/
}
public void Dispose()
{
baseBlob.Dispose();
}
private readonly IBlob baseBlob;
private long padFrom;
private long padLen;
}
}
}

View File

@ -1,783 +0,0 @@
using System;
using System.Text;
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.Emulation.DiscSystem
{
public class CUE_Format
{
/// <summary>
/// Generates the CUE file for the provided DiscStructure
/// </summary>
public string GenerateCUE_OneBin(DiscStructure structure, CueBinPrefs prefs)
{
if (prefs.OneBlobPerTrack) throw new InvalidOperationException("OneBinPerTrack passed to GenerateCUE_OneBin");
//this generates a single-file cue!!!!!!! dont expect it to generate bin-per-track!
StringBuilder sb = new StringBuilder();
foreach (var session in structure.Sessions)
{
if (!prefs.SingleSession)
{
//dont want to screw around with sessions for now
sb.AppendFormat("SESSION {0:D2}\n", session.num);
if (prefs.AnnotateCue) sb.AppendFormat("REM ; session (length={0})\n", session.length_aba);
}
foreach (var track in session.Tracks)
{
ETrackType trackType = track.TrackType;
//mutate track type according to our principle of canonicalization
if (trackType == ETrackType.Mode1_2048 && prefs.DumpECM)
trackType = ETrackType.Mode1_2352;
sb.AppendFormat(" TRACK {0:D2} {1}\n", track.Number, Cue.TrackTypeStringForTrackType(trackType));
if (prefs.AnnotateCue) sb.AppendFormat(" REM ; track (length={0})\n", track.LengthInSectors);
foreach (var index in track.Indexes)
{
//cue+bin has an implicit 150 sector pregap which neither the cue nor the bin has any awareness of
//except for the baked-in sector addressing.
//but, if there is an extra-long pregap, we want to reflect it this way
int lba = index.aba - 150;
if (lba <= 0 && index.Number == 0 && track.Number == 1)
{
}
//dont emit index 0 when it is the same as index 1, it is illegal for some reason
else if (index.Number == 0 && index.aba == track.Indexes[1].aba)
{
//dont emit index 0 when it is the same as index 1, it confuses some cue parsers
}
else
{
sb.AppendFormat(" INDEX {0:D2} {1}\n", index.Number, new Timestamp(lba).Value);
}
}
}
}
return sb.ToString();
}
}
partial class Disc
{
/// <summary>
/// finds a file in the same directory with an extension alternate to the supplied one.
/// If two are found, an exception is thrown (later, we may have heuristics to try to acquire the desired content)
/// TODO - this whole concept could be turned into a gigantic FileResolver class and be way more powerful
/// </summary>
string FindAlternateExtensionFile(string path, bool caseSensitive, string baseDir)
{
string targetFile = Path.GetFileName(path);
string targetFragment = Path.GetFileNameWithoutExtension(path);
var di = new FileInfo(path).Directory;
//if the directory doesnt exist, it may be because it was a full path or something. try an alternate base directory
if (!di.Exists)
di = new DirectoryInfo(baseDir);
var results = new List<FileInfo>();
foreach (var fi in di.GetFiles())
{
//dont acquire cue files...
if (Path.GetExtension(fi.FullName).ToLower() == ".cue")
continue;
string fragment = Path.GetFileNameWithoutExtension(fi.FullName);
//match files with differing extensions
int cmp = string.Compare(fragment, targetFragment, !caseSensitive);
if(cmp != 0)
//match files with another extension added on (likely to be mygame.bin.ecm)
cmp = string.Compare(fragment, targetFile, !caseSensitive);
if (cmp == 0)
results.Add(fi);
}
if(results.Count == 0) throw new DiscReferenceException(path, "Cannot find the specified file");
if (results.Count > 1) throw new DiscReferenceException(path, "Cannot choose between multiple options");
return results[0].FullName;
}
//cue files can get their data from other sources using this
readonly Dictionary<string, string> CueFileResolver = new Dictionary<string, string>();
void FromCueInternal(Cue cue, string cueDir, CueBinPrefs prefs)
{
//TODO - add cue directory to CueBinPrefs???? could make things cleaner...
Structure = new DiscStructure();
var session = new DiscStructure.Session {num = 1};
Structure.Sessions.Add(session);
var pregap_sector = new Sector_Zero();
int curr_track = 1;
foreach (var cue_file in cue.Files)
{
//structural validation
if (cue_file.Tracks.Count < 1) throw new Cue.CueBrokenException("`You must specify at least one track per file.`");
string blobPath = Path.Combine(cueDir, cue_file.Path);
if (CueFileResolver.ContainsKey(cue_file.Path))
blobPath = CueFileResolver[cue_file.Path];
int blob_sectorsize = Cue.BINSectorSizeForTrackType(cue_file.Tracks[0].TrackType);
int blob_length_aba;
long blob_length_bytes;
IBlob cue_blob;
//try any way we can to acquire a file
if (!File.Exists(blobPath) && prefs.ExtensionAware)
{
blobPath = FindAlternateExtensionFile(blobPath, prefs.CaseSensitive, cueDir);
}
if (!File.Exists(blobPath))
throw new DiscReferenceException(blobPath, "");
//some simple rules to mutate the file type if we received something fishy
string blobPathExt = Path.GetExtension(blobPath).ToLower();
if (blobPathExt == ".ape") cue_file.FileType = Cue.CueFileType.Wave;
if (blobPathExt == ".mp3") cue_file.FileType = Cue.CueFileType.Wave;
if (blobPathExt == ".mpc") cue_file.FileType = Cue.CueFileType.Wave;
if (blobPathExt == ".flac") cue_file.FileType = Cue.CueFileType.Wave;
if (blobPathExt == ".ecm") cue_file.FileType = Cue.CueFileType.ECM;
if (cue_file.FileType == Cue.CueFileType.Binary || cue_file.FileType == Cue.CueFileType.Unspecified)
{
//make a blob for the file
Blob_RawFile blob = new Blob_RawFile {PhysicalPath = blobPath};
Blobs.Add(blob);
blob_length_aba = (int)(blob.Length / blob_sectorsize);
blob_length_bytes = blob.Length;
cue_blob = blob;
}
else if (cue_file.FileType == Cue.CueFileType.ECM)
{
if (!Blob_ECM.IsECM(blobPath))
{
throw new DiscReferenceException(blobPath, "an ECM file was specified or detected, but it isn't a valid ECM file. You've got issues. Consult your iso vendor.");
}
Blob_ECM blob = new Blob_ECM();
Blobs.Add(blob);
blob.Parse(blobPath);
cue_blob = blob;
blob_length_aba = (int)(blob.Length / blob_sectorsize);
blob_length_bytes = blob.Length;
}
else if (cue_file.FileType == Cue.CueFileType.Wave)
{
Blob_WaveFile blob = new Blob_WaveFile();
Blobs.Add(blob);
try
{
//check whether we can load the wav directly
bool loaded = false;
if (File.Exists(blobPath) && Path.GetExtension(blobPath).ToUpper() == ".WAV")
{
try
{
blob.Load(blobPath);
loaded = true;
}
catch
{
}
}
//if that didnt work or wasnt possible, try loading it through ffmpeg
if (!loaded)
{
FFMpeg ffmpeg = new FFMpeg();
if (!ffmpeg.QueryServiceAvailable())
{
throw new DiscReferenceException(blobPath, "No decoding service was available (make sure ffmpeg.exe is available. even though this may be a wav, ffmpeg is used to load oddly formatted wave files. If you object to this, please send us a note and we'll see what we can do. It shouldn't be too hard.)");
}
AudioDecoder dec = new AudioDecoder();
byte[] buf = dec.AcquireWaveData(blobPath);
blob.Load(new MemoryStream(buf));
WasSlowLoad = true;
}
}
catch (Exception ex)
{
throw new DiscReferenceException(blobPath, ex);
}
blob_length_aba = (int)(blob.Length / blob_sectorsize);
blob_length_bytes = blob.Length;
cue_blob = blob;
}
else throw new Exception("Internal error - Unhandled cue blob type");
//TODO - make CueTimestamp better, and also make it a struct, and also just make it DiscTimestamp
//start timekeeping for the blob. every time we hit an index, this will advance
int blob_timestamp = 0;
//because we can have different size sectors in a blob, we need to keep a file cursor within the blob
long blob_cursor = 0;
//the aba that this cue blob starts on
int blob_disc_aba_start = Sectors.Count;
//this is a bit dodgy.. lets fixup the indices so we have something for index 0
//TODO - I WISH WE DIDNT HAVE TO DO THIS. WE SHOULDNT PAY SO MUCH ATTENTION TO THE INTEGRITY OF THE INDEXES
Timestamp blob_ts = new Timestamp(0);
for (int t = 0; t < cue_file.Tracks.Count; t++)
{
var cue_track = cue_file.Tracks[t];
if (!cue_track.Indexes.ContainsKey(1))
throw new Cue.CueBrokenException("Track was missing an index 01");
for (int i = 0; i <= 99; i++)
{
if (cue_track.Indexes.ContainsKey(i))
{
blob_ts = cue_track.Indexes[i].Timestamp;
}
else if (i == 0)
{
var cti = new Cue.CueTrackIndex(0);
cue_track.Indexes[0] = cti;
cti.Timestamp = blob_ts;
}
}
}
//validate that the first index in the file is 00:00:00
//"The first index of a file must start at 00:00:00"
//zero 20-dec-2014 - NOTE - index 0 is OK. we've seen files that 'start' at non-zero but thats only with index 1 -- an index 0 was explicitly listed at time 0
if (cue_file.Tracks[0].Indexes[0].Timestamp.Sector != 0) throw new Cue.CueBrokenException("`The first index of a blob must start at 00:00:00.`");
//for each track within the file:
for (int t = 0; t < cue_file.Tracks.Count; t++)
{
var cue_track = cue_file.Tracks[t];
//record the disc ABA that this track started on
int track_disc_aba_start = Sectors.Count;
//record the pregap location. it will default to the start of the track unless we supplied a pregap command
int track_disc_pregap_aba = track_disc_aba_start;
int blob_track_start = blob_timestamp;
//once upon a time we had a check here to prevent a single blob from containing variant sector sizes. but we support that now.
//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 DiscStructure.Track();
session.Tracks.Add(toc_track);
toc_track.Number = curr_track;
toc_track.TrackType = cue_track.TrackType;
toc_track.ADR = 1; //safe assumption. CUE can't store this.
//choose a Control value based on track type and other flags from cue
//TODO - this might need to be controlled by cue loading prefs
toc_track.Control = cue_track.Control;
if (toc_track.TrackType == ETrackType.Audio)
toc_track.Control |= EControlQ.StereoNoPreEmph;
else toc_track.Control |= EControlQ.DataUninterrupted;
if (curr_track == 1)
{
if (cue_track.PreGap.Sector != 0)
throw new InvalidOperationException("not supported (yet): cue files with track 1 pregaps");
//but now we add one anyway, because every existing cue+bin seems to implicitly specify this
cue_track.PreGap = new Timestamp(150);
}
//check whether a pregap is requested.
//this causes empty sectors to get generated without consuming data from the blob
if (cue_track.PreGap.Sector > 0)
{
for (int i = 0; i < cue_track.PreGap.Sector; i++)
{
Sectors.Add(new SectorEntry(pregap_sector));
}
}
//look ahead to the next track's index 1 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_aba;
if (t == cue_file.Tracks.Count - 1)
track_length_aba = blob_length_aba - blob_timestamp;
else track_length_aba = cue_file.Tracks[t + 1].Indexes[1].Timestamp.Sector - blob_timestamp;
//toc_track.length_aba = track_length_aba; //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 DiscStructure.Index {Number = index};
toc_track.Indexes.Add(toc_index);
if (index == 0)
{
//zero 18-dec-2014 - uhhhh cant make sense of this.
//toc_index.aba = track_disc_pregap_aba - (cue_track.Indexes[1].Timestamp.Sector - cue_track.Indexes[0].Timestamp.Sector);
toc_index.aba = track_disc_pregap_aba;
}
else toc_index.aba = Sectors.Count;
//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_aba;
if (is_last_index)
index_length_aba = track_length_aba - (blob_timestamp - blob_track_start);
else index_length_aba = cue_track.Indexes[index + 1].Timestamp.Sector - blob_timestamp;
//emit sectors
for (int aba = 0; aba < index_length_aba; aba++)
{
bool is_last_aba_in_index = (aba == index_length_aba - 1);
bool is_last_aba_in_track = is_last_aba_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 the entirety of a mode2 sector (could be form0,1,2)
{
//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
{
Blob = cue_blob,
Offset = blob_cursor
};
blob_cursor += 2352;
Sector_Mode1_or_Mode2_2352 sector_raw;
if(cue_track.TrackType == ETrackType.Mode1_2352)
sector_raw = new Sector_Mode1_2352();
else if (cue_track.TrackType == ETrackType.Audio)
sector_raw = new Sector_Mode1_2352(); //TODO should probably make a new sector adapter which errors if 2048B are requested
else if (cue_track.TrackType == ETrackType.Mode2_2352)
sector_raw = new Sector_Mode2_2352();
else throw new InvalidOperationException();
sector_raw.BaseSector = sector_rawblob;
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_aba = Sectors.Count;
var sector_2048 = new Sector_Mode1_2048(curr_disc_aba + 150)
{
Blob = new ECMCacheBlob(cue_blob),
Offset = blob_cursor
};
blob_cursor += 2048;
Sectors.Add(new SectorEntry(sector_2048));
break;
}
} //switch(TrackType)
//we've emitted an ABA, so consume it from the blob
blob_timestamp++;
} //aba 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.Sector; i++)
{
Sectors.Add(new SectorEntry(pregap_sector));
}
//we're done with the track now.
//record its length:
toc_track.LengthInSectors = Sectors.Count - toc_track.Indexes[1].aba;
curr_track++;
//if we ran off the end of the blob, pad it with zeroes, I guess
if (blob_cursor > blob_length_bytes)
{
//mutate the blob to an encapsulating Blob_ZeroPadAdapter
Blobs[Blobs.Count - 1] = new Blob_ZeroPadAdapter(Blobs[Blobs.Count - 1], blob_length_bytes, blob_cursor - blob_length_bytes);
}
} //track loop
} //file loop
//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 abas between start and end
//EDIT - or is the above nonsense? it should be the amount of data present, full stop.
Structure.LengthInSectors = 0;
foreach (var toc_session in Structure.Sessions)
{
var firstTrack = toc_session.Tracks[0];
//track 0, index 0 is actually -150. but cue sheets will never say that
//firstTrack.Indexes[0].aba -= 150;
var lastTrack = toc_session.Tracks[toc_session.Tracks.Count - 1];
session.length_aba = lastTrack.Indexes[1].aba + lastTrack.LengthInSectors - firstTrack.Indexes[0].aba;
Structure.LengthInSectors += toc_session.length_aba;
}
}
void FromCuePathInternal(string cuePath, CueBinPrefs prefs)
{
string cueDir = Path.GetDirectoryName(cuePath);
var cue = new Cue();
cue.LoadFromPath(cuePath);
FromCueInternal(cue, cueDir, prefs);
}
}
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.FileType == CueFileType.Binary) sb.Append(" BINARY");
if (cf.FileType == CueFileType.Wave) sb.Append(" WAVE");
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 enum CueFileType
{
Unspecified, Binary, Wave, ECM
}
public class CueFile
{
public string Path;
public List<CueTrack> Tracks = new List<CueTrack>();
public CueFileType FileType = CueFileType.Unspecified;
public string StrFileType;
}
public List<CueFile> Files = new List<CueFile>();
public static int BINSectorSizeForTrackType(ETrackType type)
{
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 EControlQ Control;
public ETrackType TrackType;
public int TrackNum;
public Timestamp PreGap = new Timestamp();
public Timestamp PostGap = new Timestamp();
public Dictionary<int, CueTrackIndex> Indexes = new Dictionary<int, CueTrackIndex>();
}
public class CueTrackIndex
{
public CueTrackIndex(int num) { IndexNum = num; }
public int IndexNum;
/// <summary>
/// Is this an ABA or a LBA? please say.
/// </summary>
public Timestamp Timestamp;
}
[Serializable]
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();
string cueString = File.ReadAllText(cuePath);
LoadFromString(cueString);
}
public void LoadFromString(string cueString)
{
TextReader tr = new StringReader(cueString);
bool track_has_pregap = false;
bool track_has_postgap = false;
int last_index_num = -1;
CueFile currFile = null;
CueTrack currTrack = null;
for (; ; )
{
string line = tr.ReadLine();
if (line == null) break;
line = line.Trim();
if (line == "") continue;
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();
switch (temp)
{
case "BINARY":
currFile.FileType = CueFileType.Binary;
break;
case "WAVE":
case "MP3":
currFile.FileType = CueFileType.Wave;
break;
}
currFile.StrFileType = temp;
}
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");
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 = 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();
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)
{
Timestamp = new Timestamp(str_timestamp), 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 Timestamp(clp.ReadToken());
track_has_pregap = true;
break;
case "POSTGAP":
if (track_has_postgap) throw new CueBrokenException("`Only one POSTGAP command is allowed per track.`");
track_has_postgap = true;
currTrack.PostGap = new Timestamp(clp.ReadToken());
break;
case "CATALOG":
case "PERFORMER":
case "SONGWRITER":
case "TITLE":
case "ISRC":
case "FLAGS":
//TODO - keep these for later?
//known flags:
//FLAGS DCP
{
var flags = clp.ReadToken();
if (flags == "DCP")
{
currTrack.Control |= EControlQ.CopyPermittedMask;
} else throw new CueBrokenException("Unknown flags: " + flags);
}
break;
default:
throw new CueBrokenException("unsupported cue command: " + key);
}
} //end cue parsing loop
}
class CueLineParser
{
int index;
string str;
public bool EOF;
public CueLineParser(string line)
{
str = line;
}
public string ReadPath() { return ReadToken(true); }
public string ReadToken() { return ReadToken(false); }
public string ReadToken(bool isPath)
{
if (EOF) return null;
int startIndex = index;
bool inToken = false;
bool inQuote = false;
for (; ; )
{
bool done = false;
char c = str[index];
bool isWhiteSpace = (c == ' ' || c == '\t');
if (isWhiteSpace)
{
if (inQuote)
index++;
else
{
if (inToken)
done = true;
else
index++;
}
}
else
{
bool startedQuote = false;
if (!inToken)
{
startIndex = index;
if (isPath && c == '"')
startedQuote = inQuote = true;
inToken = true;
}
switch (str[index])
{
case '"':
index++;
if (inQuote && !startedQuote)
{
done = true;
}
break;
case '\\':
index++;
break;
default:
index++;
break;
}
}
if (index == str.Length)
{
EOF = true;
done = true;
}
if (done) break;
}
return str.Substring(startIndex, index - startIndex);
}
}
}
}

View File

@ -1,331 +0,0 @@
using System;
using System.Collections.Generic;
using BizHawk.Common.BufferExtensions;
//main apis for emulator core routine use
namespace BizHawk.Emulation.DiscSystem
{
[Serializable]
public class DiscReferenceException : Exception
{
public DiscReferenceException(string fname, Exception inner)
: base(string.Format("A disc attempted to reference a file which could not be accessed or loaded: {0}", fname),inner)
{
}
public DiscReferenceException(string fname, string extrainfo)
: base(string.Format("A disc attempted to reference a file which could not be accessed or loaded:\n\n{0}\n\n{1}", fname, extrainfo))
{
}
}
public class ProgressReport
{
public string Message;
public bool InfoPresent;
public double ProgressEstimate;
public double ProgressCurrent;
public int TaskCount;
public int TaskCurrent;
public bool CancelSignal;
}
public class DiscHopper
{
public Disc CurrentDisc;
public Queue<Disc> Queue = new Queue<Disc>();
public void Enqueue(Disc disc)
{
Queue.Enqueue(disc);
}
public void Next()
{
if (Queue.Count != 0) Queue.Dequeue();
}
public void Eject()
{
CurrentDisc = null;
}
public void Insert()
{
if (Queue.Count > 0)
CurrentDisc = Queue.Peek();
}
public void Clear()
{
CurrentDisc = null;
Queue.Clear();
}
}
/// <summary>
/// Simplifies access to the subcode data in a disc
/// </summary>
public class SubcodeReader
{
public SubcodeReader(Disc disc)
{
this.disc = disc;
}
public void ReadLBA_SubchannelQ(int lba, ref SubchannelQ sq)
{
var se = disc.ReadLBA_SectorEntry(lba);
se.SubcodeSector.ReadSubcodeChannel(1, buffer, 0);
int offset = 0;
sq.q_status = buffer[offset + 0];
sq.q_tno = buffer[offset + 1];
sq.q_index = buffer[offset + 2];
sq.min.BCDValue = buffer[offset + 3];
sq.sec.BCDValue = buffer[offset + 4];
sq.frame.BCDValue = buffer[offset + 5];
//nothing in byte[6]
sq.ap_min.BCDValue = buffer[offset + 7];
sq.ap_sec.BCDValue = buffer[offset + 8];
sq.ap_frame.BCDValue = buffer[offset + 9];
//CRC is stored inverted and big endian.. so... do the opposite
byte hibyte = (byte)(~buffer[offset + 10]);
byte lobyte = (byte)(~buffer[offset + 11]);
sq.q_crc = (ushort)((hibyte << 8) | lobyte);
}
Disc disc;
byte[] buffer = new byte[96];
}
/// <summary>
/// Allows you to stream data off a disc
/// </summary>
public class DiscStream : System.IO.Stream
{
int SectorSize;
int NumSectors;
Disc Disc;
long currPosition;
int cachedSector;
byte[] cachedSectorBuffer;
public static DiscStream Open_LBA_2048(Disc disc)
{
var ret = new DiscStream();
ret._Open_LBA_2048(disc);
return ret;
}
void _Open_LBA_2048(Disc disc)
{
SectorSize = 2048;
Disc = disc;
NumSectors = disc.LBACount;
currPosition = 0;
cachedSector = -1;
cachedSectorBuffer = new byte[SectorSize];
}
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return true; } }
public override bool CanWrite { get { return false; } }
public override void Flush() { throw new NotImplementedException(); }
public override long Length { get { return NumSectors * SectorSize; } }
public override long Position
{
get { return currPosition; }
set
{
currPosition = value;
//invalidate the cached sector..
//as a later optimization, we could actually intelligently decide if this is necessary
cachedSector = -1;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
long remain = Length - currPosition;
if (count > remain)
count = (int)Math.Min(remain,int.MaxValue);
Disc.READLBA_Flat_Implementation(currPosition, buffer, offset, count, (a, b, c) => Disc.ReadLBA_2048(a, b, c), SectorSize, cachedSectorBuffer, ref cachedSector);
currPosition += count;
return count;
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
switch (origin)
{
case System.IO.SeekOrigin.Begin: Position = offset; break;
case System.IO.SeekOrigin.Current: Position += offset; break;
case System.IO.SeekOrigin.End: Position = Length - offset; break;
}
return Position;
}
public override void SetLength(long value) { throw new NotImplementedException(); }
public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); }
}
sealed public partial class Disc
{
/// <summary>
/// Main API to read a 2352-byte sector from a disc, identified by LBA.
/// </summary>
public void ReadLBA_2352(int lba, byte[] buffer, int offset)
{
ReadABA_2352(lba + 150, buffer, offset);
}
/// <summary>
/// Main API to read a 2048-byte sector from a disc, identified by LBA.
/// </summary>
public void ReadLBA_2048(int lba, byte[] buffer, int offset)
{
ReadABA_2048(lba + 150, buffer, offset);
}
/// <summary>
/// Main API to read a 2352-byte sector from a disc, identified by ABA
/// </summary>
public void ReadABA_2352(int aba, byte[] buffer, int offset)
{
Sectors[aba].Sector.Read_2352(buffer, offset);
}
/// <summary>
/// Main API to read a 2048-byte sector from a disc, identified by ABA
/// </summary>
public void ReadABA_2048(int aba, byte[] buffer, int offset)
{
Sectors[aba].Sector.Read_2048(buffer, offset);
}
/// <summary>
/// reads logical data from a flat (logical) disc address space. disc_offset=0 represents LBA=0.
/// useful for plucking data from a known location on the disc
/// </summary>
public void ReadLBA_2352_Flat(long disc_offset, byte[] buffer, int offset, int length)
{
const int secsize = 2352;
byte[] lba_buf = new byte[secsize];
int sectorHint = -1;
READLBA_Flat_Implementation(disc_offset, buffer, offset, length, (a, b, c) => ReadLBA_2352(a, b, c), secsize, lba_buf, ref sectorHint);
}
/// <summary>
/// reads logical data from a flat (absolute) disc address space. disc_offset=0 represents LBA=0.
/// useful for plucking data from a known location on the disc
/// </summary>
public void ReadLBA_2048_Flat(long disc_offset, byte[] buffer, int offset, int length)
{
const int secsize = 2048;
byte[] lba_buf = new byte[secsize];
int sectorHint = -1;
READLBA_Flat_Implementation(disc_offset, buffer, offset, length, (a, b, c) => ReadLBA_2048(a, b, c), secsize, lba_buf, ref sectorHint);
}
internal void READLBA_Flat_Implementation(long disc_offset, byte[] buffer, int offset, int length, Action<int, byte[], int> sectorReader, int sectorSize, byte[] sectorBuf, ref int sectorBufferHint)
{
//hint is the sector number which is already read. to avoid repeatedly reading the sector from the disc in case of several small reads, so that sectorBuf can be used as a sector cache
while (length > 0)
{
int lba = (int)(disc_offset / sectorSize);
int lba_within = (int)(disc_offset % sectorSize);
int todo = length;
int remains_in_lba = sectorSize - lba_within;
if (remains_in_lba < todo)
todo = remains_in_lba;
if(sectorBufferHint != lba)
sectorReader(lba, sectorBuf, 0);
sectorBufferHint = lba;
Array.Copy(sectorBuf, lba_within, buffer, offset, todo);
offset += todo;
length -= todo;
disc_offset += todo;
}
}
/// <summary>
/// Returns a SectorEntry from which you can retrieve various interesting pieces of information about the sector.
/// The SectorEntry's interface is not likely to be stable, though, but it may be more convenient.
/// </summary>
public SectorEntry ReadLBA_SectorEntry(int lba)
{
return Sectors[lba + 150];
}
/// <summary>
/// Main API to determine how many LBAs are available on the disc.
/// This counts from LBA 0 to the final sector available.
/// </summary>
public int LBACount { get { return ABACount - 150; } }
/// <summary>
/// Main API to determine how many ABAs (sectors) are available on the disc.
/// This counts from ABA 0 to the final sector available.
/// </summary>
public int ABACount { get { return Sectors.Count; } }
/// <summary>
/// indicates whether this disc took significant work to load from the hard drive (i.e. decoding of ECM or audio data)
/// In this case, the user may appreciate a prompt to export the disc so that it won't take so long next time.
/// </summary>
public bool WasSlowLoad { get; private set; }
/// <summary>
/// main api for reading the structure from a disc.
/// TODO - this is weak sauce. Why this one method to read something which is nothing but returning a structure? Lame.
/// Either get rid of this, or rethink the disc API wrapper concept
/// </summary>
public DiscStructure ReadStructure()
{
return Structure;
}
// converts LBA to minute:second:frame format.
//TODO - somewhat redundant with Timestamp, which is due for refactoring into something not cue-related
public static void ConvertLBAtoMSF(int lba, out byte m, out byte s, out byte f)
{
lba += 150;
m = (byte)(lba / 75 / 60);
s = (byte)((lba - (m * 75 * 60)) / 75);
f = (byte)(lba - (m * 75 * 60) - (s * 75));
}
// converts MSF to LBA offset
public static int ConvertMSFtoLBA(byte m, byte s, byte f)
{
return f + (s * 75) + (m * 75 * 60) - 150;
}
// gets an identifying hash. hashes the first 512 sectors of
// the first data track on the disc.
//TODO - this is a very platform-specific thing. hashing the TOC may be faster and be just as effective. so, rename it appropriately
public string GetHash()
{
byte[] buffer = new byte[512 * 2352];
foreach (var track in Structure.Sessions[0].Tracks)
{
if (track.TrackType == ETrackType.Audio)
continue;
int lba_len = Math.Min(track.LengthInSectors, 512);
for (int s = 0; s < 512 && s < track.LengthInSectors; s++)
ReadABA_2352(track.Indexes[1].aba + s, buffer, s * 2352);
return buffer.HashMD5(0, lba_len * 2352);
}
return "no data track found";
}
}
}

View File

@ -4,713 +4,100 @@ using System.Text;
using System.IO;
using System.Collections.Generic;
//http://www.pctechguide.com/iso-9660-data-format-for-cds-cd-roms-cd-rs-and-cd-rws
//http://linux.die.net/man/1/cue2toc
//ARCHITECTURE NOTE:
//No provisions are made for caching synthesized data for later accelerated use.
//This is because, in the worst case that might result in synthesizing an entire disc in memory.
//Instead, users should be advised to `hawk` the disc first for most rapid access so that synthesis won't be necessary and speed will be maximized.
//This will result in a completely flattened CCD where everything comes right off the hard drive
//Our choice here might be an unwise decision for disc ID and miscellaneous purposes but it's best for gaming and stream-converting (hawking and hashing)
//http://cdemu.sourceforge.net/project.php#sf
//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
//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: mode2 (could be 2336[vanilla mode2], 2048[xa mode2 form1], 2324[xa mode2 form2])
//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)
//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
//TODO: in principle, we could mount audio to decode only on an as-needed basis
//this might result in hiccups during emulation, though, so it should be an option.
//This would imply either decode-length processing (scan file without decoding) or decoding and discarding the data.
//We should probably have some richer policy specifications for this kind of thing, but it's not a high priority. Main workflow is still discohawking.
//Alternate policies would probably be associated with copious warnings (examples: ? ? ?)
namespace BizHawk.Emulation.DiscSystem
{
public partial class Disc : IDisposable
{
/// <summary>
/// Free-form optional memos about the disc
/// Automagically loads a disc, without any fine-tuned control at all
/// </summary>
public Dictionary<string, object> Memos = new Dictionary<string, object>();
public static Disc LoadAutomagic(string path)
{
var job = new DiscMountJob { IN_FromPath = path };
//job.IN_DiscInterface = DiscInterface.MednaDisc; //TEST
job.Run();
return job.OUT_Disc;
}
/// <summary>
/// The raw TOC entries found in the lead-in track.
/// </summary>
public List<RawTOCEntry> RawTOCEntries = new List<RawTOCEntry>();
/// <summary>
/// The DiscTOCRaw corresponding to the RawTOCEntries
/// </summary>
public DiscTOCRaw TOCRaw;
/// <summary>
/// The DiscStructure corresponding the the TOCRaw
/// The DiscStructure corresponding to the TOCRaw
/// </summary>
public DiscStructure Structure;
/// <summary>
/// The blobs mounted by this disc for supplying binary content
/// DiscStructure.Session 1 of the disc, since that's all thats needed most of the time.
/// </summary>
public List<IBlob> Blobs = new List<IBlob>();
public DiscStructure.Session Session1 { get { return Structure.Sessions[1]; } }
/// <summary>
/// The sectors on the disc
/// The DiscTOCRaw corresponding to the RawTOCEntries.
/// TODO - there's one of these for every session, so... having one here doesnt make sense
/// so...
/// TODO - remove me
/// </summary>
public List<SectorEntry> Sectors = new List<SectorEntry>();
public DiscTOC TOC;
public Disc()
{
}
/// <summary>
/// The raw TOC entries found in the lead-in track.
/// These aren't very useful, but theyre one of the most lowest-level data structures from which other TOC-related stuff is derived
/// </summary>
public List<RawTOCEntry> RawTOCEntries = new List<RawTOCEntry>();
/// <summary>
/// Free-form optional memos about the disc
/// </summary>
public Dictionary<string, object> Memos = new Dictionary<string, object>();
public void Dispose()
{
foreach (var blob in Blobs)
foreach (var res in DisposableResources)
{
blob.Dispose();
}
}
void FromIsoPathInternal(string isoPath)
{
//make a fake cue file to represent this iso file
const string isoCueWrapper = @"
FILE ""xarp.barp.marp.farp"" BINARY
TRACK 01 MODE1/2048
INDEX 01 00:00:00
";
string cueDir = String.Empty;
var cue = new Cue();
CueFileResolver["xarp.barp.marp.farp"] = isoPath;
cue.LoadFromString(isoCueWrapper);
FromCueInternal(cue, cueDir, new CueBinPrefs());
}
public CueBin DumpCueBin(string baseName, CueBinPrefs prefs)
{
if (Structure.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.OneBlobPerTrack)
{
//this is the preferred mode of dumping things. we will always write full sectors.
string cue = new CUE_Format().GenerateCUE_OneBin(Structure,prefs);
var bfd = new CueBin.BinFileDescriptor {name = baseName + ".bin"};
ret.cue = string.Format("FILE \"{0}\" BINARY\n", bfd.name) + cue;
ret.bins.Add(bfd);
bfd.SectorSize = 2352;
//skip the mandatory track 1 pregap! cue+bin files do not contain it
for (int i = 150; i < Structure.LengthInSectors; i++)
{
bfd.abas.Add(i);
bfd.aba_zeros.Add(false);
}
}
else
{
//we build our own cue here (unlike above) because we need to build the cue and the output data at the same time
StringBuilder sbCue = new StringBuilder();
for (int i = 0; i < Structure.Sessions[0].Tracks.Count; i++)
{
var track = Structure.Sessions[0].Tracks[i];
var bfd = new CueBin.BinFileDescriptor
{
name = baseName + string.Format(" (Track {0:D2}).bin", track.Number),
SectorSize = Cue.BINSectorSizeForTrackType(track.TrackType)
};
ret.bins.Add(bfd);
int aba = 0;
//skip the mandatory track 1 pregap! cue+bin files do not contain it
if (i == 0) aba = 150;
for (; aba < track.LengthInSectors; aba++)
{
int thisaba = track.Indexes[0].aba + aba;
bfd.abas.Add(thisaba);
bfd.aba_zeros.Add(false);
}
sbCue.AppendFormat("FILE \"{0}\" BINARY\n", bfd.name);
sbCue.AppendFormat(" TRACK {0:D2} {1}\n", track.Number, Cue.TrackTypeStringForTrackType(track.TrackType));
foreach (var index in track.Indexes)
{
int x = index.aba - track.Indexes[0].aba;
if (index.Number == 0 && index.aba == track.Indexes[1].aba)
{
//dont emit index 0 when it is the same as index 1, it is illegal for some reason
}
//else if (i==0 && index.num == 0)
//{
// //don't generate the first index, it is illogical
//}
else
{
//track 1 included the lead-in at the beginning of it. sneak past that.
//if (i == 0) x -= 150;
sbCue.AppendFormat(" INDEX {0:D2} {1}\n", index.Number, new Timestamp(x).Value);
}
}
}
ret.cue = sbCue.ToString();
}
return ret;
}
public static Disc FromCuePath(string cuePath, CueBinPrefs prefs)
{
var ret = new Disc();
ret.FromCuePathInternal(cuePath, prefs);
ret.Structure.Synthesize_TOCPointsFromSessions();
ret.Synthesize_SubcodeFromStructure();
ret.Synthesize_TOCRawFromStructure();
//try loading SBI. make sure its done after the subcode is synthesized!
string sbiPath = Path.ChangeExtension(cuePath, "sbi");
if (File.Exists(sbiPath) && SBI_Format.QuickCheckISSBI(sbiPath))
{
var sbi = new SBI_Format().LoadSBIPath(sbiPath);
ret.ApplySBI(sbi);
}
return ret;
}
public static Disc FromCCDPath(string ccdPath)
{
CCD_Format ccdLoader = new CCD_Format();
return ccdLoader.LoadCCDToDisc(ccdPath);
}
/// <summary>
/// THIS HASNT BEEN TESTED IN A LONG TIME. DOES IT WORK?
/// </summary>
public static Disc FromIsoPath(string isoPath)
{
var ret = new Disc();
ret.FromIsoPathInternal(isoPath);
ret.Structure.Synthesize_TOCPointsFromSessions();
ret.Synthesize_SubcodeFromStructure();
return ret;
}
/// <summary>
/// Synthesizes a crudely estimated TOCRaw from the disc structure.
/// </summary>
public void Synthesize_TOCRawFromStructure()
{
TOCRaw = new DiscTOCRaw();
TOCRaw.FirstRecordedTrackNumber = 1;
TOCRaw.LastRecordedTrackNumber = Structure.Sessions[0].Tracks.Count;
int lastEnd = 0;
for (int i = 0; i < Structure.Sessions[0].Tracks.Count; i++)
{
var track = Structure.Sessions[0].Tracks[i];
TOCRaw.TOCItems[i + 1].Control = track.Control;
TOCRaw.TOCItems[i + 1].Exists = true;
//TOCRaw.TOCItems[i + 1].LBATimestamp = new Timestamp(track.Start_ABA - 150); //AUGH. see comment in Start_ABA
//TOCRaw.TOCItems[i + 1].LBATimestamp = new Timestamp(track.Indexes[1].LBA); //ZOUNDS!
//TOCRaw.TOCItems[i + 1].LBATimestamp = new Timestamp(track.Indexes[1].LBA + 150); //WHATEVER, I DONT KNOW. MAKES IT MATCH THE CCD, BUT THERES MORE PROBLEMS
TOCRaw.TOCItems[i + 1].LBATimestamp = new Timestamp(track.Indexes[1].LBA); //WHAT?? WE NEED THIS AFTER ALL! ZOUNDS MEANS, THERE WAS JUST SOME OTHER BUG
lastEnd = track.LengthInSectors + track.Indexes[1].LBA;
}
TOCRaw.LeadoutTimestamp = new Timestamp(lastEnd);
}
/// <summary>
/// applies an SBI file to the disc
/// </summary>
public void ApplySBI(SBI_Format.SBIFile sbi)
{
//save this, it's small, and we'll want it for disc processing a/b checks
Memos["sbi"] = sbi;
int n = sbi.ABAs.Count;
byte[] subcode = new byte[96];
int b=0;
for (int i = 0; i < n; i++)
{
int aba = sbi.ABAs[i];
var oldSubcode = this.Sectors[aba].SubcodeSector;
oldSubcode.ReadSubcodeDeinterleaved(subcode, 0);
for (int j = 0; j < 12; j++)
{
short patch = sbi.subq[b++];
if (patch == -1) continue;
else subcode[12 + j] = (byte)patch;
}
var bss = new BufferedSubcodeSector();
Sectors[aba].SubcodeSector = BufferedSubcodeSector.CloneFromBytesDeinterleaved(subcode);
res.Dispose();
}
}
/// <summary>
/// Creates the subcode (really, just subchannel Q) for this disc from its current TOC.
/// Depends on the TOCPoints existing in the structure
/// TODO - do we need a fully 0xFF P-subchannel for PSX?
/// The DiscMountPolicy used to mount the disc. Consider this read-only.
/// NOT SURE WE NEED THIS
/// </summary>
void Synthesize_SubcodeFromStructure()
{
int aba = 0;
int dpIndex = 0;
//public DiscMountPolicy DiscMountPolicy;
//TODO - from mednafen (on PC-FX chip chan kick)
//If we're more than 2 seconds(150 sectors) from the real "start" of the track/INDEX 01, and the track is a data track,
//and the preceding track is an audio track, encode it as audio(by taking the SubQ control field from the preceding
//NOTE: discs may have subcode which is nonsense or possibly not recoverable from a sensible disc structure.
//but this function does what it says.
//SO: heres the main idea of how this works.
//we have the Structure.Points (whose name we dont like) which is a list of sectors where the tno/index changes.
//So for each sector, we see if we've advanced to the next point.
//TODO - check if this is synthesized correctly when producing a structure from a TOCRaw
while (aba < Sectors.Count)
{
if (dpIndex < Structure.Points.Count - 1)
{
while (aba >= Structure.Points[dpIndex + 1].ABA)
{
dpIndex++;
}
}
var dp = Structure.Points[dpIndex];
var se = Sectors[aba];
EControlQ control = dp.Control;
bool pause = true;
if (dp.Num != 0) //TODO - shouldnt this be IndexNum?
pause = false;
if ((dp.Control & EControlQ.DataUninterrupted)!=0)
pause = false;
int adr = dp.ADR;
SubchannelQ sq = new SubchannelQ();
sq.q_status = SubchannelQ.ComputeStatus(adr, control);
sq.q_tno = BCD2.FromDecimal(dp.TrackNum).BCDValue;
sq.q_index = BCD2.FromDecimal(dp.IndexNum).BCDValue;
int track_relative_aba = aba - dp.Track.Indexes[1].aba;
track_relative_aba = Math.Abs(track_relative_aba);
Timestamp track_relative_timestamp = new Timestamp(track_relative_aba);
sq.min = BCD2.FromDecimal(track_relative_timestamp.MIN);
sq.sec = BCD2.FromDecimal(track_relative_timestamp.SEC);
sq.frame = BCD2.FromDecimal(track_relative_timestamp.FRAC);
sq.zero = 0;
Timestamp absolute_timestamp = new Timestamp(aba);
sq.ap_min = BCD2.FromDecimal(absolute_timestamp.MIN);
sq.ap_sec = BCD2.FromDecimal(absolute_timestamp.SEC);
sq.ap_frame = BCD2.FromDecimal(absolute_timestamp.FRAC);
var bss = new BufferedSubcodeSector();
bss.Synthesize_SubchannelQ(ref sq, true);
//TEST: need this for psx?
if(pause) bss.Synthesize_SubchannelP(true);
se.SubcodeSector = bss;
aba++;
}
}
static byte IntToBCD(int n)
{
int ones;
int tens = Math.DivRem(n,10,out ones);
return (byte)((tens<<4)|ones);
}
}
/// <summary>
/// encapsulates a 2 digit BCD number as used various places in the CD specs
/// </summary>
public struct BCD2
{
/// <summary>
/// The raw BCD value. you can't do math on this number! but you may be asked to supply it to a game program.
/// The largest number it can logically contain is 99
/// </summary>
public byte BCDValue;
//----------------------------------------------------------------------------
/// <summary>
/// The derived decimal value. you can do math on this! the largest number it can logically contain is 99.
/// Disposable resources (blobs, mostly) referenced by this disc
/// </summary>
public int DecimalValue
{
get { return (BCDValue & 0xF) + ((BCDValue >> 4) & 0xF) * 10; }
set { BCDValue = IntToBCD(value); }
}
internal List<IDisposable> DisposableResources = new List<IDisposable>();
/// <summary>
/// makes a BCD2 from a decimal number. don't supply a number > 99 or you might not like the results
/// The sectors on the disc
/// TODO - replace with delegate (much faster disc loading, support of reading of arbitrary lead-out and lead-in sectors)
/// </summary>
public static BCD2 FromDecimal(int d)
{
return new BCD2 {DecimalValue = d};
}
public static int BCDToInt(byte n)
{
var bcd = new BCD2();
bcd.BCDValue = n;
return bcd.DecimalValue;
}
public static byte IntToBCD(int n)
{
int ones;
int tens = Math.DivRem(n, 10, out ones);
return (byte)((tens << 4) | ones);
}
}
public struct Timestamp
{
/// <summary>
/// creates a timestamp from a string in the form mm:ss:ff
/// </summary>
public Timestamp(string value)
{
//TODO - could be performance-improved
MIN = int.Parse(value.Substring(0, 2));
SEC = int.Parse(value.Substring(3, 2));
FRAC = int.Parse(value.Substring(6, 2));
Sector = MIN * 60 * 75 + SEC * 75 + FRAC;
_value = null;
}
public readonly int MIN, SEC, FRAC, Sector;
public string Value
{
get
{
if (_value != null) return _value;
return _value = string.Format("{0:D2}:{1:D2}:{2:D2}", MIN, SEC, FRAC);
}
}
string _value;
internal List<ISectorSynthJob2448> Sectors = new List<ISectorSynthJob2448>();
/// <summary>
/// creates timestamp from supplies MSF
/// Parameters set during disc loading which can be referenced by the sector synthesizers
/// </summary>
public Timestamp(int m, int s, int f)
{
MIN = m;
SEC = s;
FRAC = f;
Sector = MIN * 60 * 75 + SEC * 75 + FRAC;
_value = null;
}
internal SectorSynthParams SynthParams = new SectorSynthParams();
/// <summary>
/// creates timestamp from supplied SectorNumber
/// Forbid public construction
/// </summary>
public Timestamp(int SectorNumber)
{
this.Sector = SectorNumber;
MIN = SectorNumber / (60 * 75);
SEC = (SectorNumber / 75) % 60;
FRAC = SectorNumber % 75;
_value = null;
}
}
internal Disc()
{}
/// <summary>
/// The type of a Track, not strictly (for now) adhering to the realistic values, but also including information for ourselves about what source the data came from.
/// We should make that not the case.
/// TODO - let CUE have its own "track type" enum, since cue tracktypes arent strictly corresponding to "real" track types, whatever those are.
/// </summary>
public enum ETrackType
{
/// <summary>
/// The track type isn't always known.. it can take this value til its populated
/// </summary>
Unknown,
/// <summary>
/// CD-ROM (yellowbook) specification - it is a Mode1 track, and we have all 2352 bytes for the sector
/// </summary>
Mode1_2352,
/// <summary>
/// CD-ROM (yellowbook) specification - it is a Mode1 track, but originally we only had 2048 bytes for the sector.
/// This means, for various purposes, we need to synthesize additional data
/// </summary>
Mode1_2048,
/// <summary>
/// CD-ROM (yellowbook) specification - it is a Mode2 track.
/// </summary>
Mode2_2352,
/// <summary>
/// CD-DA (redbook) specification.. nominally. In fact, it's just 2352 raw PCM bytes per sector, and that concept isnt directly spelled out in redbook.
/// </summary>
Audio
}
/// <summary>
/// TODO - this is garbage. It's half input related, and half output related. This needs to be split up.
/// </summary>
public class CueBinPrefs
{
/// <summary>
/// Controls general operations: should the output be split into several blobs, or just use one?
/// </summary>
public bool OneBlobPerTrack;
/// <summary>
/// NOT SUPPORTED YET (just here as a reminder) If choosing OneBinPerTrack, you may wish to write wave files for audio tracks.
/// </summary>
//public bool DumpWaveFiles;
/// <summary>
/// turn this on to dump bins instead of just cues
/// </summary>
public bool ReallyDumpBin;
/// <summary>
/// Dump bins to bitbucket instead of disk
/// </summary>
public bool DumpToBitbucket;
/// <summary>
/// dump a .sub.q along with bins. one day we'll want to dump the entire subcode but really Q is all thats important for debugging most things
/// </summary>
public bool DumpSubchannelQ;
/// <summary>
/// generate remarks and other annotations to help humans understand whats going on, but which will confuse many cue parsers
/// </summary>
public bool AnnotateCue;
/// <summary>
/// EVIL: in theory this would attempt to generate pregap commands to save disc space, but I think this is a bad idea.
/// it would also be useful for OneBinPerTrack mode in making wave files.
/// HOWEVER - by the time we've loaded things up into our canonical format, we don't know which 'pregaps' are safe for turning back into pregaps
/// Because they might sometimes contain data (gapless audio discs). So we would have to inspect a series of sectors to look for silence.
/// And even still, the ECC information might be important. So, forget it.
/// NEVER USE OR IMPLEMENT THIS
/// </summary>
//public bool PreferPregapCommand = false;
/// <summary>
/// some cue parsers cant handle sessions. better not emit a session command then. multi-session discs will then be broken
/// </summary>
public bool SingleSession;
/// <summary>
/// enables various extension-aware behaviours.
/// enables auto-search for files with the same name but differing extension.
/// enables auto-detection of situations where cue blobfiles are indicating the wrong type in the cuefile
/// </summary>
public bool ExtensionAware = false;
/// <summary>
/// whenever we have a choice, use case sensitivity in searching for files
/// </summary>
public bool CaseSensitive = false;
/// <summary>
/// DO NOT CHANGE THIS! All sectors will be written with ECM data. It's a waste of space, but it is exact. (not completely supported yet)
/// </summary>
public bool DumpECM = true;
}
/// <summary>
/// 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
/// </summary>
public class CueBin
{
public string cue;
public string baseName;
public Disc disc;
public class BinFileDescriptor
{
public string name;
public List<int> abas = new List<int>();
//todo - do we really need this? i dont think so...
public List<bool> aba_zeros = new List<bool>();
public int SectorSize;
}
public List<BinFileDescriptor> bins = new List<BinFileDescriptor>();
//NOT SUPPORTED RIGHT NOW
//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_aba * 2352];
// //TODO ????????? post-ABA unknown
// //for (int aba = 0; aba < track.length_aba; aba++)
// // 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;
// Timestamp pregap_ts = new Timestamp(pregap);
// Timestamp len_ts = new Timestamp(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)
{
ProgressReport pr = new ProgressReport();
Dump(directory, prefs, pr);
}
public void Dump(string directory, CueBinPrefs prefs, ProgressReport progress)
{
byte[] subcodeTemp = new byte[96];
progress.TaskCount = 2;
progress.Message = "Generating Cue";
progress.ProgressEstimate = 1;
progress.ProgressCurrent = 0;
progress.InfoPresent = true;
string cuePath = Path.Combine(directory, baseName + ".cue");
if (prefs.DumpToBitbucket) { }
else File.WriteAllText(cuePath, cue);
progress.Message = "Writing bin(s)";
progress.TaskCurrent = 1;
progress.ProgressEstimate = bins.Sum(bfd => bfd.abas.Count);
progress.ProgressCurrent = 0;
if(!prefs.ReallyDumpBin) return;
foreach (var bfd in bins)
{
int sectorSize = bfd.SectorSize;
byte[] temp = new byte[2352];
byte[] empty = new byte[2352];
string trackBinFile = bfd.name;
string trackBinPath = Path.Combine(directory, trackBinFile);
string subQPath = Path.ChangeExtension(trackBinPath, ".sub.q");
Stream fsSubQ = null;
Stream fs;
if(prefs.DumpToBitbucket)
fs = Stream.Null;
else fs = new FileStream(trackBinPath, FileMode.Create, FileAccess.Write, FileShare.None);
try
{
if (prefs.DumpSubchannelQ)
if (prefs.DumpToBitbucket)
fsSubQ = Stream.Null;
else fsSubQ = new FileStream(subQPath, FileMode.Create, FileAccess.Write, FileShare.None);
for (int i = 0; i < bfd.abas.Count; i++)
{
if (progress.CancelSignal) return;
progress.ProgressCurrent++;
int aba = bfd.abas[i];
if (bfd.aba_zeros[i])
{
fs.Write(empty, 0, sectorSize);
}
else
{
if (sectorSize == 2352)
disc.ReadABA_2352(aba, temp, 0);
else if (sectorSize == 2048) disc.ReadABA_2048(aba, temp, 0);
else throw new InvalidOperationException();
fs.Write(temp, 0, sectorSize);
//write subQ if necessary
if (fsSubQ != null)
{
disc.Sectors[aba].SubcodeSector.ReadSubcodeDeinterleaved(subcodeTemp, 0);
fsSubQ.Write(subcodeTemp, 12, 12);
}
}
}
}
finally
{
fs.Dispose();
if (fsSubQ != null) fsSubQ.Dispose();
}
}
}
}
}

View File

@ -117,6 +117,7 @@ namespace BizHawk.Emulation.DiscSystem
/// <summary>
/// finds audio at a path similar to the provided path (i.e. finds Track01.mp3 for Track01.wav)
/// TODO - isnt this redundant with CueFileResolver?
/// </summary>
string FindAudio(string audioPath)
{

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using BizHawk.Common.BufferExtensions;
//some old junk
namespace BizHawk.Emulation.DiscSystem
{
[Serializable]
public class DiscReferenceException : Exception
{
public DiscReferenceException(string fname, Exception inner)
: base(string.Format("A disc attempted to reference a file which could not be accessed or loaded: {0}", fname), inner)
{
}
public DiscReferenceException(string fname, string extrainfo)
: base(string.Format("A disc attempted to reference a file which could not be accessed or loaded:\n\n{0}\n\n{1}", fname, extrainfo))
{
}
}
}

View File

@ -34,7 +34,7 @@ namespace BizHawk.Emulation.DiscSystem
{
sealed partial class Disc
{
private class Blob_ECM : IBlob
internal class Blob_ECM : IBlob
{
FileStream stream;
@ -66,7 +66,7 @@ namespace BizHawk.Emulation.DiscSystem
public long Length;
public void Parse(string path)
public void Load(string path)
{
stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
@ -136,7 +136,8 @@ namespace BizHawk.Emulation.DiscSystem
else MisformedException();
}
//TODO - endian bug. need endian-independent binary reader with good license
//TODO - endian bug. need an endian-independent binary reader with good license (miscutils is apache license)
//extension methods on binary reader wont suffice, we need something that lets you control the endianness used for reading. a complete replacement.
var br = new BinaryReader(stream);
EDC = br.ReadInt32();

View File

@ -7,9 +7,10 @@ namespace BizHawk.Emulation.DiscSystem
partial class Disc
{
/// <summary>
/// TODO - dont we need to modify RiffMaster to be able to stream from the disk instead of loading everything at parse time?
/// TODO - doublecheck that riffmaster is not filling memory at load-time but reading through to the disk
/// TODO - clarify stream disposing semantics
/// </summary>
class Blob_WaveFile : IBlob
internal class Blob_WaveFile : IBlob
{
[Serializable]
public class Blob_WaveFile_Exception : Exception
@ -22,53 +23,56 @@ namespace BizHawk.Emulation.DiscSystem
public Blob_WaveFile()
{
} private class Blob_RawFile : IBlob
{
public string PhysicalPath {
get
{
return physicalPath;
}
set
{
physicalPath = value;
length = new FileInfo(physicalPath).Length;
}
}
string physicalPath;
long length;
public long Offset = 0;
private class Blob_RawFile : IBlob
{
public string PhysicalPath
{
get
{
return physicalPath;
}
set
{
physicalPath = value;
length = new FileInfo(physicalPath).Length;
}
}
string physicalPath;
long length;
BufferedStream fs;
public void Dispose()
{
if (fs != null)
public long Offset = 0;
BufferedStream fs;
public void Dispose()
{
fs.Dispose();
fs = null;
if (fs != null)
{
fs.Dispose();
fs = null;
}
}
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 but in small chunks.
//this enhances performance considerably
const int buffersize = 2352 * 75 * 2;
if (fs == null)
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;
}
}
}
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 but in small chunks.
//this enhances performance considerably
const int buffersize = 2352 * 75 * 2;
if (fs == null)
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;
}
}
}
public void Load(byte[] waveData)
{
@ -123,7 +127,7 @@ namespace BizHawk.Emulation.DiscSystem
waveDataStreamPos = dataChunk.Position;
mDataLength = dataChunk.Length;
}
catch
catch(Exception)
{
Dispose();
throw;

View File

@ -0,0 +1,47 @@
using System;
using System.IO;
//I have an ff9 disc which is truncated
namespace BizHawk.Emulation.DiscSystem
{
public partial class Disc : IDisposable
{
internal sealed class Blob_ZeroPadAdapter : IBlob
{
IBlob srcBlob;
long srcBlobLength;
public Blob_ZeroPadAdapter(IBlob srcBlob, long srcBlobLength)
{
this.srcBlob = srcBlob;
this.srcBlobLength = srcBlobLength;
}
public int Read(long byte_pos, byte[] buffer, int offset, int count)
{
int todo = count;
long end = byte_pos + todo;
if (end > srcBlobLength)
{
long temp = (int)(srcBlobLength - byte_pos);
if (temp > int.MaxValue)
throw new InvalidOperationException();
todo = (int)temp;
//zero-fill the unused part (just for safety's sake)
Array.Clear(buffer, offset + todo, count - todo);
}
srcBlob.Read(byte_pos, buffer, offset, todo);
//since it's zero padded, this never fails and always reads the requested amount
return count;
}
public void Dispose()
{
}
}
}
}

View File

@ -11,6 +11,7 @@ namespace BizHawk.Emulation.DiscSystem
/// References to large blobs remain mostly on disk in the file which RiffMaster keeps a reference too. Dispose it to close the file.
/// You can modify blobs however you want and write the file back out to a new path, if youre careful (that was the original point of this)
/// Please be sure to test round-tripping when you make any changes. This architecture is a bit tricky to use, but it works if youre careful.
/// TODO - clarify stream disposing semantics
/// </summary>
class RiffMaster : IDisposable
{
@ -281,7 +282,7 @@ namespace BizHawk.Emulation.DiscSystem
private long readCounter;
private RiffChunk ReadChunk(BinaryReader br)
{
{
RiffChunk ret;
string tag = ReadTag(br); readCounter += 4;
uint size = br.ReadUInt32(); readCounter += 4;
@ -311,6 +312,7 @@ namespace BizHawk.Emulation.DiscSystem
Length = size
};
readCounter += size;
br.BaseStream.Position += size;
ret = rsc.Morph();
}
if (size % 2 != 0)

View File

@ -80,16 +80,17 @@ namespace BizHawk.Emulation.DiscSystem
public int Session;
/// <summary>
/// this seems just to be the LBA corresponding to AMIN:ASEC:AFRAME. It's not stored on the disc, and it's redundant.
/// this seems just to be the LBA corresponding to AMIN:ASEC:AFRAME (give or take 150). It's not stored on the disc, and it's redundant.
/// </summary>
public int ALBA;
/// <summary>
/// this seems just to be the LBA corresponding to PMIN:PSEC:PFRAME. It's not stored on the disc, and it's redundant.
/// this seems just to be the LBA corresponding to PMIN:PSEC:PFRAME (give or take 150). It's not stored on the disc, and it's redundant.
/// </summary>
public int PLBA;
//these correspond pretty directly to values in the Q subchannel fields
//NOTE: they're specified as absolute MSF. That means, they're 2 seconds off from what they should be when viewed as final TOC values
public int Control;
public int ADR;
public int TrackNo;
@ -119,7 +120,7 @@ namespace BizHawk.Emulation.DiscSystem
public int Number;
/// <summary>
/// The specified data mode
/// The specified data mode.
/// </summary>
public int Mode;
@ -285,6 +286,7 @@ namespace BizHawk.Emulation.DiscSystem
entry.PFrame = section.FetchOrFail("PFRAME");
entry.PLBA = section.FetchOrFail("PLBA");
//note: LBA 0 is Ansolute MSF 00:02:00
if (new Timestamp(entry.AMin, entry.ASec, entry.AFrame).Sector != entry.ALBA + 150)
throw new CCDParseException("Warning: inconsistency in CCD ALBA vs computed A MSF");
if (new Timestamp(entry.PMin, entry.PSec, entry.PFrame).Sector != entry.PLBA + 150)
@ -362,6 +364,124 @@ namespace BizHawk.Emulation.DiscSystem
return ret;
}
public static void Dump(Disc disc, string path)
{
using (var sw = new StreamWriter(path))
{
//NOTE: IsoBuster requires the A0,A1,A2 RawTocEntries to be first or else it can't do anything with the tracks
//if we ever get them in a different order, we'll have to re-order them here
sw.WriteLine("[CloneCD]");
sw.WriteLine("Version=3");
sw.WriteLine();
sw.WriteLine("[Disc]");
sw.WriteLine("TocEntries={0}", disc.RawTOCEntries.Count);
sw.WriteLine("Sessions=1");
sw.WriteLine("DataTracksScrambled=0");
sw.WriteLine("CDTextLength=0"); //not supported anyway
sw.WriteLine();
sw.WriteLine("[Session 1]");
sw.WriteLine("PreGapMode=2");
sw.WriteLine("PreGapSubC=1");
sw.WriteLine();
for (int i = 0; i < disc.RawTOCEntries.Count; i++)
{
var entry = disc.RawTOCEntries[i];
//ehhh something's wrong with how I track these
int point = entry.QData.q_index.DecimalValue;
if (point == 100) point = 0xA0;
if (point == 101) point = 0xA1;
if (point == 102) point = 0xA2;
sw.WriteLine("[Entry {0}]", i);
sw.WriteLine("Session=1");
sw.WriteLine("Point=0x{0:x2}", point);
sw.WriteLine("ADR=0x{0:x2}", entry.QData.ADR);
sw.WriteLine("Control=0x{0:x2}", (int)entry.QData.CONTROL);
sw.WriteLine("TrackNo={0}", entry.QData.q_tno.DecimalValue);
sw.WriteLine("AMin={0}", entry.QData.min.DecimalValue);
sw.WriteLine("ASec={0}", entry.QData.sec.DecimalValue);
sw.WriteLine("AFrame={0}", entry.QData.frame.DecimalValue);
sw.WriteLine("ALBA={0}", entry.QData.Timestamp.Sector - 150); //remember to adapt the absolute MSF to an LBA (this field is redundant...)
sw.WriteLine("Zero={0}", entry.QData.zero);
sw.WriteLine("PMin={0}", entry.QData.ap_min.DecimalValue);
sw.WriteLine("PSec={0}", entry.QData.ap_sec.DecimalValue);
sw.WriteLine("PFrame={0}", entry.QData.ap_frame.DecimalValue);
sw.WriteLine("PLBA={0}", entry.QData.AP_Timestamp.Sector - 150); //remember to adapt the absolute MSF to an LBA (this field is redundant...)
sw.WriteLine();
}
//this is nonsense, really. the whole CCD track list shouldn't be needed.
//but in order to make a high quality CCD which can be inspected by various other tools, we need it
//now, regarding the indexes.. theyre truly useless. having indexes written out with the tracks is bad news.
//index information is only truly stored in subQ
for (int tnum = 1; tnum <= disc.Session1.LastInformationTrack.Number; tnum++)
{
var track = disc.Session1.Tracks[tnum];
sw.WriteLine("[TRACK {0}]", track.Number);
sw.WriteLine("MODE={0}", track.Mode);
//indexes are BS, dont write them. but we certainly need an index 1
sw.WriteLine("INDEX 1={0}", track.LBA);
sw.WriteLine();
}
}
//TODO - actually re-add
//dump the img and sub
//TODO - acquire disk size first
string imgPath = Path.ChangeExtension(path, ".img");
string subPath = Path.ChangeExtension(path, ".sub");
var buf2448 = new byte[2448];
DiscSectorReader dsr = new DiscSectorReader(disc);
using (var imgFile = File.OpenWrite(imgPath))
using (var subFile = File.OpenWrite(subPath))
{
int nLBA = disc.Session1.LeadoutLBA;
for (int lba = 0; lba < nLBA; lba++)
{
dsr.ReadLBA_2448(lba, buf2448, 0);
imgFile.Write(buf2448, 0, 2352);
subFile.Write(buf2448, 2352, 96);
}
}
}
class SS_CCD : ISectorSynthJob2448
{
public void Synth(SectorSynthJob job)
{
//CCD is always containing everything we'd need (unless a .sub is missing?) so don't about flags
var imgBlob = job.Disc.DisposableResources[0] as Disc.Blob_RawFile;
var subBlob = job.Disc.DisposableResources[1] as Disc.Blob_RawFile;
//Read_2442(job.LBA, job.DestBuffer2448, job.DestOffset);
//read the IMG data if needed
if ((job.Parts & ESectorSynthPart.UserAny) != 0)
{
long ofs = job.LBA * 2352;
imgBlob.Read(ofs, job.DestBuffer2448, 0, 2352);
}
//if subcode is needed, read it
if ((job.Parts & (ESectorSynthPart.SubcodeAny)) != 0)
{
long ofs = job.LBA * 96;
subBlob.Read(ofs, job.DestBuffer2448, 2352, 96);
//subcode comes to us deinterleved; we may still need to interleave it
if ((job.Parts & (ESectorSynthPart.SubcodeDeinterleave)) == 0)
{
SynthUtils.InterleaveSubcodeInplace(job.DestBuffer2448, job.DestOffset + 2352);
}
}
}
}
/// <summary>
/// Loads a CCD at the specified path to a Disc object
/// </summary>
@ -373,23 +493,40 @@ namespace BizHawk.Emulation.DiscSystem
Disc disc = new Disc();
//mount the IMG and SUB files
var ccdf = loadResults.ParsedCCDFile;
var imgBlob = new Disc.Blob_RawFile() { PhysicalPath = loadResults.ImgPath };
var subBlob = new Disc.Blob_RawFile() { PhysicalPath = loadResults.SubPath };
disc.Blobs.Add(imgBlob);
disc.Blobs.Add(subBlob);
disc.DisposableResources.Add(imgBlob);
disc.DisposableResources.Add(subBlob);
//the only instance of a sector synthesizer we'll need
SS_CCD synth = new SS_CCD();
//generate DiscTOCRaw items from the ones specified in the CCD file
//TODO - range validate these (too many truncations to byte)
disc.RawTOCEntries = new List<RawTOCEntry>();
BufferedSubcodeSector bss = new BufferedSubcodeSector();
foreach (var entry in ccdf.TOCEntries)
{
BCD2 tno, ino;
//this should actually be zero. im not sure if this is stored as BCD2 or not
tno = BCD2.FromDecimal(entry.TrackNo);
//these are special values.. I think, taken from this:
//http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html
//the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD.
//Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing.
ino = BCD2.FromDecimal(entry.Point);
if (entry.Point == 0xA0) ino.BCDValue = 0xA0;
else if (entry.Point == 0xA1) ino.BCDValue = 0xA1;
else if (entry.Point == 0xA2) ino.BCDValue = 0xA2;
var q = new SubchannelQ
{
q_status = SubchannelQ.ComputeStatus(entry.ADR, (EControlQ)(entry.Control & 0xF)),
q_tno = (byte)entry.TrackNo,
q_index = (byte)entry.Point,
q_tno = tno,
q_index = ino,
min = BCD2.FromDecimal(entry.AMin),
sec = BCD2.FromDecimal(entry.ASec),
frame = BCD2.FromDecimal(entry.AFrame),
@ -397,111 +534,32 @@ namespace BizHawk.Emulation.DiscSystem
ap_min = BCD2.FromDecimal(entry.PMin),
ap_sec = BCD2.FromDecimal(entry.PSec),
ap_frame = BCD2.FromDecimal(entry.PFrame),
q_crc = 0, //meainingless
};
//CRC cant be calculated til we've got all the fields setup
q.q_crc = bss.Synthesize_SubchannelQ(ref q, true);
disc.RawTOCEntries.Add(new RawTOCEntry { QData = q });
}
//generate the toc from the entries
var tocSynth = new DiscTOCRaw.SynthesizeFromRawTOCEntriesJob() { Entries = disc.RawTOCEntries };
tocSynth.Run();
disc.TOCRaw = tocSynth.Result;
//synthesize DiscStructure
var structureSynth = new DiscStructure.SynthesizeFromDiscTOCRawJob() { TOCRaw = disc.TOCRaw };
structureSynth.Run();
disc.Structure = structureSynth.Result;
//I *think* implicitly there is an index 0.. at.. i dunno, 0 maybe, for track 1
{
var dsi0 = new DiscStructure.Index();
dsi0.LBA = 0;
dsi0.Number = 0;
disc.Structure.Sessions[0].Tracks[0].Indexes.Add(dsi0);
}
//now, how to get the track types for the DiscStructure?
//1. the CCD tells us (somehow the reader has judged)
//2. scan it out of the Q subchannel
//lets choose1.
//TODO - better consider how to handle the situation where we have havent received all the [TRACK] items we need
foreach (var st in disc.Structure.Sessions[0].Tracks)
{
var ccdt = ccdf.TracksByNumber[st.Number];
switch (ccdt.Mode)
{
case 0:
st.TrackType = ETrackType.Audio; //for CCD, this means audio, apparently.
break;
case 1:
st.TrackType = ETrackType.Mode1_2352;
break;
case 2:
st.TrackType = ETrackType.Mode2_2352;
break;
default:
throw new InvalidOperationException("Unsupported CCD mode");
}
//add indexes for this track
foreach (var ccdi in ccdt.Indexes)
{
var dsi = new DiscStructure.Index();
//if (ccdi.Key == 0) continue;
dsi.LBA = ccdi.Value;
dsi.Number = ccdi.Key;
st.Indexes.Add(dsi);
}
}
//add sectors for the lead-in, which isn't stored in the CCD file, I think
//TODO - synthesize lead-in sectors from TOC, if the lead-in isn't available.
//need a test case for that though.
var leadin_sector_zero = new Sector_Zero();
var leadin_subcode_zero = new ZeroSubcodeSector();
//add sectors for the mandatory track 1 pregap, which isn't stored in the CCD file
//TODO - THIS IS JUNK. MORE CORRECTLY SYNTHESIZE IT
for (int i = 0; i < 150; i++)
{
var se = new SectorEntry(leadin_sector_zero);
disc.Sectors.Add(se);
se.SubcodeSector = leadin_subcode_zero;
//TODO - YIKES!
disc.Sectors.Add(null);
}
//build the sectors:
//set up as many sectors as we have img/sub for, even if the TOC doesnt reference them (TOC is unreliable, although the tracks should have covered it all)
//set up as many sectors as we have img/sub for, even if the TOC doesnt reference them
//(TOC is unreliable, although the tracks should have covered it all)
for (int i = 0; i < loadResults.NumImgSectors; i++)
{
var isec = new Sector_RawBlob();
isec.Offset = ((long)i) * 2352;
isec.Blob = imgBlob;
var se = new SectorEntry(isec);
disc.Sectors.Add(se);
var scsec = new BlobSubcodeSectorPreDeinterleaved();
scsec.Offset = ((long)i) * 96;
scsec.Blob = subBlob;
se.SubcodeSector = scsec;
disc.Sectors.Add(synth);
}
return disc;
}
public void Dump(Disc disc, string ccdPath)
{
//TODO!!!!!!!
StringWriter sw = new StringWriter();
sw.WriteLine("[CloneCD]");
sw.WriteLine("Version=3");
sw.WriteLine("[Disc]");
//sw.WriteLine("TocEntries={0}",disc.TOCRaw.TOCItems
sw.WriteLine("Sessions=1");
sw.WriteLine("DataTracksScrambled=0");
sw.WriteLine("CDTextLength=0");
}
} //class CCD_Format
}

View File

@ -0,0 +1,489 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
//this would be a good place for structural validation
//after this step, we won't want to have to do stuff like that (it will gunk up already sticky code)
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Context
{
internal class CompiledCDText
{
public string Songwriter;
public string Performer;
public string Title;
public string ISRC;
}
internal class CompiledCueIndex
{
public int Number;
/// <summary>
/// this is annoying, it should just be an integer
/// </summary>
public Timestamp FileMSF;
public override string ToString()
{
return string.Format("I#{0:D2} {1}", Number, FileMSF);
}
}
/// <summary>
/// What type of file we're looking at.. each one would require a different ingestion handler
/// </summary>
public enum CompiledCueFileType
{
Unknown,
/// <summary>
/// a raw BIN that can be mounted directly
/// </summary>
BIN,
/// <summary>
/// a raw WAV that can be mounted directly
/// </summary>
WAVE,
/// <summary>
/// an ECM file that can be mounted directly (once the index is generated)
/// </summary>
ECM,
/// <summary>
/// An encoded audio file which can be seeked on the fly, therefore roughly mounted on the fly
/// THIS ISN'T SUPPORTED YET
/// </summary>
SeekAudio,
/// <summary>
/// An encoded audio file which can't be seeked on the fly. It must be decoded to a temp buffer, or pre-discohawked
/// </summary>
DecodeAudio,
}
internal class CompiledCueFile
{
public string FullPath;
public CompiledCueFileType Type;
public override string ToString()
{
return string.Format("{0}: {1}", Type, Path.GetFileName(FullPath));
}
}
internal class CompiledDiscInfo
{
public int FirstRecordedTrackNumber, LastRecordedTrackNumber;
public SessionFormat SessionFormat;
}
internal class CompiledCueTrack
{
public int BlobIndex;
public int Number;
/// <summary>
/// A track that's final in a file gets its length from the length of the file; other tracks lengths are determined from the succeeding track
/// </summary>
public bool IsFinalInFile;
/// <summary>
/// A track that's first in a file has an implicit index 0 at 00:00:00
/// Otherwise it has an implicit index 0 at the placement of the index 1
/// </summary>
public bool IsFirstInFile;
public CompiledCDText CDTextData = new CompiledCDText();
public Timestamp PregapLength, PostgapLength;
public CueFile.TrackFlags Flags = CueFile.TrackFlags.None;
public CueFile.TrackType TrackType = CueFile.TrackType.Unknown;
public List<CompiledCueIndex> Indexes = new List<CompiledCueIndex>();
public override string ToString()
{
var idx = Indexes.Find((i) => i.Number == 1);
if (idx == null)
return string.Format("T#{0:D2} NO INDEX 1", Number);
else
{
var indexlist = string.Join("|", Indexes);
return string.Format("T#{0:D2} {1}:{2} ({3})", Number, BlobIndex, idx.FileMSF, indexlist);
}
}
}
internal class CompileCueJob : DiscJob
{
/// <summary>
/// input: the CueFile to analyze
/// </summary>
public CueFile IN_CueFile;
/// <summary>
/// The context used for this compiling job
/// TODO - rename something like context
/// </summary>
public CUE_Context IN_CueFormat;
/// <summary>
/// output: high level disc info
/// </summary>
public CompiledDiscInfo OUT_CompiledDiscInfo;
/// <summary>
/// output: CD-Text set at the global level (before any track commands)
/// </summary>
public CompiledCDText OUT_GlobalCDText;
/// <summary>
/// output: The compiled file info
/// </summary>
public List<CompiledCueFile> OUT_CompiledCueFiles;
/// <summary>
/// output: The compiled track info
/// </summary>
public List<CompiledCueTrack> OUT_CompiledCueTracks;
/// <summary>
/// output: An integer between 0 and 10 indicating how costly it will be to load this disc completely.
/// Activites like decoding non-seekable media will increase the load time.
/// 0 - Requires no noticeable time
/// 1 - Requires minimal processing (indexing ECM)
/// 10 - Requires ages, decoding audio data, etc.
/// </summary>
public int OUT_LoadTime;
//-----------------------------------------------------------------
CompiledCDText curr_cdtext;
int curr_blobIndex = -1;
CompiledCueTrack curr_track = null;
CompiledCueFile curr_file = null;
bool discinfo_session1Format_determined = false;
bool curr_fileHasTrack = false;
void UpdateDiscInfo(CueFile.Command.TRACK trackCommand)
{
if (OUT_CompiledDiscInfo.FirstRecordedTrackNumber == 0)
OUT_CompiledDiscInfo.FirstRecordedTrackNumber = trackCommand.Number;
OUT_CompiledDiscInfo.LastRecordedTrackNumber = trackCommand.Number;
if (!discinfo_session1Format_determined)
{
switch (trackCommand.Type)
{
case CueFile.TrackType.Mode2_2336:
case CueFile.TrackType.Mode2_2352:
OUT_CompiledDiscInfo.SessionFormat = SessionFormat.Type20_CDXA;
discinfo_session1Format_determined = true;
break;
case CueFile.TrackType.CDI_2336:
case CueFile.TrackType.CDI_2352:
OUT_CompiledDiscInfo.SessionFormat = SessionFormat.Type10_CDI;
discinfo_session1Format_determined = true;
break;
default:
break;
}
}
}
void CloseFile()
{
if (curr_track != null)
{
//flag this track as the final one in the file
curr_track.IsFinalInFile = true;
}
curr_file = null;
}
void OpenFile(CueFile.Command.FILE f)
{
if (curr_file != null)
CloseFile();
curr_blobIndex++;
curr_fileHasTrack = false;
var Resolver = IN_CueFormat.Resolver;
//TODO - smart audio file resolving only for AUDIO types. not BINARY or MOTOROLA or AIFF or ECM or what have you
var options = Resolver.Resolve(f.Path);
string choice = null;
if (options.Count == 0)
{
Error("Couldn't resolve referenced cue file: " + f.Path);
return;
}
else
{
choice = options[0];
if (options.Count > 1)
Warn("Multiple options resolving referenced cue file; choosing: " + Path.GetFileName(choice));
}
var cfi = new CompiledCueFile();
OUT_CompiledCueFiles.Add(cfi);
cfi.FullPath = choice;
//determine the CueFileInfo's type, based on extension and extra checking
//TODO - once we reorganize the file ID stuff, do legit checks here (this is completely redundant with the fileID system
//TODO - decode vs stream vs unpossible policies in input policies object (including ffmpeg availability-checking callback (results can be cached))
string blobPathExt = Path.GetExtension(choice).ToUpperInvariant();
if (blobPathExt == ".BIN" || blobPathExt == ".IMG") cfi.Type = CompiledCueFileType.BIN;
else if (blobPathExt == ".ISO") cfi.Type = CompiledCueFileType.BIN;
else if (blobPathExt == ".WAV")
{
//quickly, check the format. turn it to DecodeAudio if it can't be supported
//TODO - fix exception-throwing inside
//TODO - verify stream-disposing semantics
var fs = File.OpenRead(choice);
using (var blob = new Disc.Blob_WaveFile())
{
try
{
blob.Load(fs);
cfi.Type = CompiledCueFileType.WAVE;
}
catch
{
cfi.Type = CompiledCueFileType.DecodeAudio;
}
}
}
else if (blobPathExt == ".APE") cfi.Type = CompiledCueFileType.DecodeAudio;
else if (blobPathExt == ".MP3") cfi.Type = CompiledCueFileType.DecodeAudio;
else if (blobPathExt == ".MPC") cfi.Type = CompiledCueFileType.DecodeAudio;
else if (blobPathExt == ".FLAC") cfi.Type = CompiledCueFileType.DecodeAudio;
else if (blobPathExt == ".ECM")
{
cfi.Type = CompiledCueFileType.ECM;
if (!Disc.Blob_ECM.IsECM(choice))
{
Error("an ECM file was specified or detected, but it isn't a valid ECM file: " + Path.GetFileName(choice));
cfi.Type = CompiledCueFileType.Unknown;
}
}
else
{
Error("Unknown cue file type. Since it's likely an unsupported compression, this is an error: ", Path.GetFileName(choice));
cfi.Type = CompiledCueFileType.Unknown;
}
//TODO - check for mismatches between track types and file types, or is that best done when interpreting the commands?
}
void CreateTrack1Pregap()
{
if (OUT_CompiledCueTracks[1].PregapLength.Sector == 0) { }
else if (OUT_CompiledCueTracks[1].PregapLength.Sector == 150) { }
else
{
Error("Track 1 specified an illegal pregap. It's being ignored and replaced with a 00:02:00 pregap");
}
OUT_CompiledCueTracks[1].PregapLength = new Timestamp(150);
}
void FinalAnalysis()
{
//some quick checks:
if (OUT_CompiledCueFiles.Count == 0)
Error("Cue file doesn't specify any input files!");
//we can't reliably analyze the length of files here, because we might have to be decoding to get lengths (VBR mp3s)
//So, it's not really worth the trouble. We'll cope with lengths later
//we could check the format of the wav file here, though
//score the cost of loading the file
bool needsCodec = false;
OUT_LoadTime = 0;
foreach (var cfi in OUT_CompiledCueFiles)
{
if (cfi.Type == CompiledCueFileType.DecodeAudio)
{
needsCodec = true;
OUT_LoadTime = Math.Max(OUT_LoadTime, 10);
}
if (cfi.Type == CompiledCueFileType.SeekAudio)
needsCodec = true;
if (cfi.Type == CompiledCueFileType.ECM)
OUT_LoadTime = Math.Max(OUT_LoadTime, 1);
}
//check whether processing was available
if (needsCodec)
{
FFMpeg ffmpeg = new FFMpeg();
if (!ffmpeg.QueryServiceAvailable())
Warn("Decoding service will be required for further processing, but is not available");
}
}
void CloseTrack()
{
if (curr_track == null)
return;
//normalize: if an index 0 is missing, add it here
if (curr_track.Indexes[0].Number != 0)
{
var index0 = new CompiledCueIndex();
var index1 = curr_track.Indexes[0];
index0.Number = 0;
index0.FileMSF = index1.FileMSF; //same MSF as index 1 will make it effectively nonexistent
//well now, if it's the first in the file, an implicit index will take its value from 00:00:00 in the file
//this is the kind of thing I sought to solve originally by 'interpreting' the file, but it seems easy enough to handle this way
//my carlin.cue tests this but test cases shouldnt be hard to find
if (curr_track.IsFirstInFile)
index0.FileMSF = new Timestamp(0);
curr_track.Indexes.Insert(0, index0);
}
OUT_CompiledCueTracks.Add(curr_track);
curr_track = null;
}
void OpenTrack(CueFile.Command.TRACK trackCommand)
{
curr_track = new CompiledCueTrack();
//spill cdtext data into this track
curr_cdtext = curr_track.CDTextData;
curr_track.BlobIndex = curr_blobIndex;
curr_track.Number = trackCommand.Number;
curr_track.TrackType = trackCommand.Type;
//default flags
if (curr_track.TrackType != CueFile.TrackType.Audio)
curr_track.Flags = CueFile.TrackFlags.DATA;
if (!curr_fileHasTrack)
{
curr_fileHasTrack = curr_track.IsFirstInFile = true;
}
UpdateDiscInfo(trackCommand);
}
void AddIndex(CueFile.Command.INDEX indexCommand)
{
var newindex = new CompiledCueIndex();
newindex.FileMSF = indexCommand.Timestamp;
newindex.Number = indexCommand.Number;
curr_track.Indexes.Add(newindex);
}
public void Run()
{
//in params
var cue = IN_CueFile;
//output state
OUT_GlobalCDText = new CompiledCDText();
OUT_CompiledDiscInfo = new CompiledDiscInfo();
OUT_CompiledCueFiles = new List<CompiledCueFile>();
OUT_CompiledCueTracks = new List<CompiledCueTrack>();
//add a track 0, for addressing convenience.
//note: for future work, track 0 may need emulation (accessible by very negative LBA--the TOC is stored there)
var track0 = new CompiledCueTrack() {
Number = 0,
};
OUT_CompiledCueTracks.Add(track0);
//global cd text will acquire the cdtext commands set before track commands
curr_cdtext = OUT_GlobalCDText;
for (int i = 0; i < cue.Commands.Count; i++)
{
var cmd = cue.Commands[i];
//these commands get dealt with globally. nothing to be done here
//(but in the future we need to accumulate them into the compile pass output)
if (cmd is CueFile.Command.CATALOG || cmd is CueFile.Command.CDTEXTFILE) continue;
//nothing to be done for comments
if (cmd is CueFile.Command.REM) continue;
if (cmd is CueFile.Command.COMMENT) continue;
//CD-text and related
if (cmd is CueFile.Command.PERFORMER) curr_cdtext.Performer = (cmd as CueFile.Command.PERFORMER).Value;
if (cmd is CueFile.Command.SONGWRITER) curr_cdtext.Songwriter = (cmd as CueFile.Command.SONGWRITER).Value;
if (cmd is CueFile.Command.TITLE) curr_cdtext.Title = (cmd as CueFile.Command.TITLE).Value;
if (cmd is CueFile.Command.ISRC) curr_cdtext.ISRC = (cmd as CueFile.Command.ISRC).Value;
//flags can only be set when a track command is running
if (cmd is CueFile.Command.FLAGS)
{
if (curr_track == null)
Warn("Ignoring invalid flag commands outside of a track command");
else
//take care to |= it here, so the data flag doesn't get cleared
curr_track.Flags |= (cmd as CueFile.Command.FLAGS).Flags;
}
if (cmd is CueFile.Command.TRACK)
{
CloseTrack();
OpenTrack(cmd as CueFile.Command.TRACK);
}
if (cmd is CueFile.Command.FILE)
{
CloseFile();
OpenFile(cmd as CueFile.Command.FILE);
}
if (cmd is CueFile.Command.INDEX)
{
//todo - validate no postgap specified
AddIndex(cmd as CueFile.Command.INDEX);
}
if (cmd is CueFile.Command.PREGAP)
{
//validate track open
//validate no indexes
curr_track.PregapLength = (cmd as CueFile.Command.PREGAP).Length;
}
if (cmd is CueFile.Command.POSTGAP)
{
curr_track.PostgapLength = (cmd as CueFile.Command.POSTGAP).Length;
}
}
//it's a bit odd to close the file before closing the track, but...
//we need to be sure to CloseFile first to make sure the track is marked as the final one in the file
CloseFile();
CloseTrack();
CreateTrack1Pregap();
FinalAnalysis();
} //Run()
} //class CompileCueJob
} //partial class CUE_Format2
} //namespace BizHawk.Emulation.DiscSystem

View File

@ -0,0 +1,23 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
//http://digitalx.org/cue-sheet/index.html "all cue sheet information is a straight 1:1 copy from the cdrwin helpfile"
namespace BizHawk.Emulation.DiscSystem
{
public partial class CUE_Context
{
/// <summary>
/// The CueFileResolver to be used by this instance
/// </summary>
public CueFileResolver Resolver;
/// <summary>
/// The DiscMountPolicy to be applied to this context
/// </summary>
public DiscMountPolicy DiscMountPolicy;
}
}

View File

@ -0,0 +1,403 @@
//TODO:
//"The first index of a file must start at 00:00:00" - if this isnt the case, we'll be doing nonsense for sure. so catch it
//Recover the idea of TOCPoints maybe, as it's a more flexible way of generating the structure.
//TODO
//check for flags changing after a PREGAP is processed. the PREGAP can't correctly run if the flags aren't set
//IN GENERAL: validate more pedantically (but that code gets in the way majorly)
// - perhaps isolate validation checks into a different pass distinct from a Burn pass
//NEW IDEA:
//a cue file is a compressed representation of a more verbose format which is easier to understand
//most fundamentally, it is organized with TRACK and INDEX commands alternating.
//these should be flattened into individual records with CURRTRACK and CURRINDEX fields.
//more generally, it's organized with 'register' settings and INDEX commands alternating.
//whenever an INDEX command is received from the cue file, individual flattened records are written with the current 'register' settings
//and an incrementing timestamp until the INDEX command appears (or the EOF happens)
//PREGAP commands are special : at the moment it is received, emit flat records with a different pregap structure
//POSTGAP commands are special : TBD
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Context
{
/// <summary>
/// Loads a cue file into a Disc.
/// For this job, virtually all nonsense input is treated as errors, but the process will try to recover as best it can.
/// The user should still reject any jobs which generated errors
/// </summary>
internal class LoadCueJob : DiscJob
{
/// <summary>
/// The results of the compile job, a prerequisite for this
/// </summary>
public CompileCueJob IN_CompileJob;
/// <summary>
/// The resulting disc
/// </summary>
public Disc OUT_Disc;
private enum BurnType
{
Normal, Pregap, Postgap
}
class BlobInfo
{
public IBlob Blob;
public long Length;
}
//not sure if we need this...
class TrackInfo
{
public int Length;
public CompiledCueTrack CompiledCueTrack;
}
List<BlobInfo> BlobInfos;
List<TrackInfo> TrackInfos = new List<TrackInfo>();
void MountBlobs()
{
IBlob file_blob = null;
BlobInfos = new List<BlobInfo>();
foreach (var ccf in IN_CompileJob.OUT_CompiledCueFiles)
{
var bi = new BlobInfo();
BlobInfos.Add(bi);
switch (ccf.Type)
{
case CompiledCueFileType.BIN:
case CompiledCueFileType.Unknown:
{
//raw files:
var blob = new Disc.Blob_RawFile { PhysicalPath = ccf.FullPath };
OUT_Disc.DisposableResources.Add(file_blob = blob);
bi.Length = blob.Length;
break;
}
case CompiledCueFileType.ECM:
{
var blob = new Disc.Blob_ECM();
OUT_Disc.DisposableResources.Add(file_blob = blob);
blob.Load(ccf.FullPath);
bi.Length = blob.Length;
break;
}
case CompiledCueFileType.WAVE:
{
var blob = new Disc.Blob_WaveFile();
OUT_Disc.DisposableResources.Add(file_blob = blob);
blob.Load(ccf.FullPath);
bi.Length = blob.Length;
break;
}
case CompiledCueFileType.DecodeAudio:
{
FFMpeg ffmpeg = new FFMpeg();
if (!ffmpeg.QueryServiceAvailable())
{
throw new DiscReferenceException(ccf.FullPath, "No decoding service was available (make sure ffmpeg.exe is available. even though this may be a wav, ffmpeg is used to load oddly formatted wave files. If you object to this, please send us a note and we'll see what we can do. It shouldn't be too hard.)");
}
AudioDecoder dec = new AudioDecoder();
byte[] buf = dec.AcquireWaveData(ccf.FullPath);
var blob = new Disc.Blob_WaveFile();
OUT_Disc.DisposableResources.Add(file_blob = blob);
blob.Load(new MemoryStream(buf));
bi.Length = buf.Length;
break;
}
default:
throw new InvalidOperationException();
} //switch(file type)
//wrap all the blobs with zero padding
bi.Blob = new Disc.Blob_ZeroPadAdapter(file_blob, bi.Length);
}
}
void AnalyzeTracks()
{
var compiledTracks = IN_CompileJob.OUT_CompiledCueTracks;
for(int t=0;t<compiledTracks.Count;t++)
{
var cct = compiledTracks[t];
var ti = new TrackInfo() { CompiledCueTrack = cct };
TrackInfos.Add(ti);
//OH NO! CANT DO THIS!
//need to read sectors from file to reliably know its ending size.
//could determine it from file mode.
//do we really need this?
//if (cct.IsFinalInFile)
//{
// //length is determined from length of file
//}
}
}
void EmitRawTOCEntry(CompiledCueTrack cct)
{
SubchannelQ toc_sq = new SubchannelQ();
//absent some kind of policy for how to set it, this is a safe assumption:
byte toc_ADR = 1;
toc_sq.SetStatus(toc_ADR, (EControlQ)(int)cct.Flags);
toc_sq.q_tno.BCDValue = 0; //kind of a little weird here.. the track number becomes the 'point' and put in the index instead. 0 is the track number here.
toc_sq.q_index = BCD2.FromDecimal(cct.Number);
//not too sure about these yet
toc_sq.min = BCD2.FromDecimal(0);
toc_sq.sec = BCD2.FromDecimal(0);
toc_sq.frame = BCD2.FromDecimal(0);
toc_sq.AP_Timestamp = new Timestamp(OUT_Disc.Sectors.Count);
OUT_Disc.RawTOCEntries.Add(new RawTOCEntry { QData = toc_sq });
}
public void Run()
{
//params
var compiled = IN_CompileJob;
var context = compiled.IN_CueFormat;
OUT_Disc = new Disc();
//generation state
int curr_index;
int curr_blobIndex = -1;
int curr_blobMSF = -1;
BlobInfo curr_blobInfo = null;
long curr_blobOffset = -1;
//mount all input files
MountBlobs();
//unhappily, we cannot determine the length of all the tracks without knowing the length of the files
//now that the files are mounted, we can figure the track lengths
AnalyzeTracks();
//loop from track 1 to 99
//(track 0 isnt handled yet, that's way distant work)
for (int t = 1; t < TrackInfos.Count; t++)
{
TrackInfo ti = TrackInfos[t];
CompiledCueTrack cct = ti.CompiledCueTrack;
//---------------------------------
//setup track pregap processing
//per "Example 05" on digitalx.org, pregap can come from index specification and pregap command
int specifiedPregapLength = cct.PregapLength.Sector;
int impliedPregapLength = cct.Indexes[1].FileMSF.Sector - cct.Indexes[0].FileMSF.Sector;
int totalPregapLength = specifiedPregapLength + impliedPregapLength;
//from now on we'll track relative timestamp and increment it continually
int relMSF = -totalPregapLength;
//read more at policies declaration
//if (!context.DiscMountPolicy.CUE_PauseContradictionModeA)
// relMSF += 1;
//---------------------------------
//---------------------------------
//generate sectors for this track.
//advance to the next file if needed
if (curr_blobIndex != cct.BlobIndex)
{
curr_blobIndex = cct.BlobIndex;
curr_blobOffset = 0;
curr_blobMSF = 0;
curr_blobInfo = BlobInfos[curr_blobIndex];
}
//work until the next track is reached, or the end of the current file is reached, depending on the track type
curr_index = 0;
for (; ; )
{
bool trackDone = false;
bool generateGap = false;
if (specifiedPregapLength > 0)
{
//if burning through a specified pregap, count it down
generateGap = true;
specifiedPregapLength--;
}
else
{
//if burning through the file, select the appropriate index by inspecting the next index and seeing if we've reached it
for (; ; )
{
if (curr_index == cct.Indexes.Count - 1)
break;
if (curr_blobMSF >= cct.Indexes[curr_index + 1].FileMSF.Sector)
{
curr_index++;
if (curr_index == 1)
{
//WE ARE NOW AT INDEX 1: generate the RawTOCEntry for this track
EmitRawTOCEntry(cct);
}
}
else break;
}
}
//select the track type for the subQ
//it's obviously the same as the main track type usually, but during a pregap it can be different
TrackInfo qTrack = ti;
int qRelMSF = relMSF;
if (curr_index == 0)
{
//tweak relMSF due to ambiguity/contradiction in yellowbook docs
if (!context.DiscMountPolicy.CUE_PregapContradictionModeA)
qRelMSF++;
//[IEC10149] says there's two "intervals" of a pregap.
//mednafen's pseudocode interpretation of this:
//if this is a data track and the previous track was not data, the last 150 sectors of the pregap match this track and the earlier sectors (at least 75) math the previous track
//I agree, so let's do it that way
if (t != 1 && cct.TrackType != CueFile.TrackType.Audio && TrackInfos[t - 1].CompiledCueTrack.TrackType == CueFile.TrackType.Audio)
{
if (relMSF < -150)
{
qTrack = TrackInfos[t - 1];
}
}
}
//generate the right kind of sector synth for this track
SS_Base ss = null;
if (generateGap)
{
var ss_gap = new SS_Gap();
ss_gap.TrackType = qTrack.CompiledCueTrack.TrackType;
ss = ss_gap;
}
else
{
int sectorSize = int.MaxValue;
switch (qTrack.CompiledCueTrack.TrackType)
{
case CueFile.TrackType.Audio:
case CueFile.TrackType.CDI_2352:
case CueFile.TrackType.Mode1_2352:
case CueFile.TrackType.Mode2_2352:
ss = new SS_2352();
sectorSize = 2352;
break;
case CueFile.TrackType.Mode1_2048:
ss = new SS_Mode1_2048();
sectorSize = 2048;
break;
default:
case CueFile.TrackType.Mode2_2336:
throw new InvalidOperationException("Not supported: " + cct.TrackType);
}
ss.Blob = curr_blobInfo.Blob;
ss.BlobOffset = curr_blobOffset;
curr_blobOffset += sectorSize;
curr_blobMSF++;
}
ss.Policy = context.DiscMountPolicy;
//setup subQ
byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
ss.sq.SetStatus(ADR, (EControlQ)(int)qTrack.CompiledCueTrack.Flags);
ss.sq.q_tno = BCD2.FromDecimal(cct.Number);
ss.sq.q_index = BCD2.FromDecimal(curr_index);
ss.sq.AP_Timestamp = new Timestamp(OUT_Disc.Sectors.Count);
ss.sq.Timestamp = new Timestamp(qRelMSF);
//setup subP
if (curr_index == 0)
ss.Pause = true;
OUT_Disc.Sectors.Add(ss);
relMSF++;
if (cct.IsFinalInFile)
{
//sometimes, break when the file is exhausted
if (curr_blobOffset >= curr_blobInfo.Length)
trackDone = true;
}
else
{
//other times, break when the track is done
//(this check is safe because it's not the final track overall if it's not the final track in a file)
if (curr_blobMSF >= TrackInfos[t + 1].CompiledCueTrack.Indexes[0].FileMSF.Sector)
trackDone = true;
}
if (trackDone)
break;
}
//---------------------------------
//gen postgap sectors
int specifiedPostgapLength = cct.PostgapLength.Sector;
for (int s = 0; s < specifiedPostgapLength; s++)
{
var ss = new SS_Gap();
ss.TrackType = cct.TrackType; //TODO - old track type in some < -150 cases?
//-subq-
byte ADR = 1;
ss.sq.SetStatus(ADR, (EControlQ)(int)cct.Flags);
ss.sq.q_tno = BCD2.FromDecimal(cct.Number);
ss.sq.q_index = BCD2.FromDecimal(curr_index);
ss.sq.AP_Timestamp = new Timestamp(OUT_Disc.Sectors.Count);
ss.sq.Timestamp = new Timestamp(relMSF);
//-subP-
//always paused--is this good enough?
ss.Pause = true;
OUT_Disc.Sectors.Add(ss);
relMSF++;
}
} //end track loop
//add RawTOCEntries A0 A1 A2 to round out the TOC
var TOCMiscInfo = new Synthesize_A0A1A2_Job {
IN_FirstRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.FirstRecordedTrackNumber,
IN_LastRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.LastRecordedTrackNumber,
IN_Session1Format = IN_CompileJob.OUT_CompiledDiscInfo.SessionFormat,
IN_LeadoutTimestamp = new Timestamp(OUT_Disc.Sectors.Count) //do we need a +150?
};
TOCMiscInfo.Run(OUT_Disc.RawTOCEntries);
//TODO - generate leadout, or delegates at least
//blech, old crap, maybe
//OUT_Disc.Structure.Synthesize_TOCPointsFromSessions();
//FinishLog();
} //Run()
} //class LoadCueJob
} //partial class CUE_Format2
} //namespace BizHawk.Emulation.DiscSystem

View File

@ -0,0 +1,453 @@
//TODO - object initialization syntax cleanup
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
//http://digitalx.org/cue-sheet/index.html "all cue sheet information is a straight 1:1 copy from the cdrwin helpfile"
//http://www.gnu.org/software/libcdio/libcdio.html#Sectors
//this is actually a great reference. they use LSN instead of LBA.. maybe a good idea for us
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Context
{
public class ParseCueJob : DiscJob
{
/// <summary>
/// input: the cue string to parse
/// </summary>
public string IN_CueString;
/// <summary>
/// output: the resulting minimally-processed cue file
/// </summary>
public CueFile OUT_CueFile;
}
/// <summary>
/// Represents the contents of a cue file
/// </summary>
public class CueFile
{
// (here are all the commands we can encounter)
public static class Command
{
//TODO - record line number origin of command? Kind of nice but inessential
public class CATALOG { public string Value; public override string ToString() { return string.Format("CATALOG: {0}", Value); } }
public class CDTEXTFILE { public string Path; public override string ToString() { return string.Format("CDTEXTFILE: {0}", Path); } }
public class FILE { public string Path; public FileType Type; public override string ToString() { return string.Format("FILE ({0}): {1}", Type, Path); } }
public class FLAGS { public TrackFlags Flags; public override string ToString() { return string.Format("FLAGS {0}", Flags); } }
public class INDEX { public int Number; public Timestamp Timestamp; public override string ToString() { return string.Format("INDEX {0,2} {1}", Number, Timestamp); } }
public class ISRC { public string Value; public override string ToString() { return string.Format("ISRC: {0}", Value); } }
public class PERFORMER { public string Value; public override string ToString() { return string.Format("PERFORMER: {0}", Value); } }
public class POSTGAP { public Timestamp Length; public override string ToString() { return string.Format("POSTGAP: {0}", Length); } }
public class PREGAP { public Timestamp Length; public override string ToString() { return string.Format("PREGAP: {0}", Length); } }
public class REM { public string Value; public override string ToString() { return string.Format("REM: {0}", Value); } }
public class COMMENT { public string Value; public override string ToString() { return string.Format("COMMENT: {0}", Value); } }
public class SONGWRITER { public string Value; public override string ToString() { return string.Format("SONGWRITER: {0}", Value); } }
public class TITLE { public string Value; public override string ToString() { return string.Format("TITLE: {0}", Value); } }
public class TRACK { public int Number; public TrackType Type; public override string ToString() { return string.Format("TRACK {0,2} ({1})", Number, Type); } }
}
/// <summary>
/// Stuff other than the commands, global for the whole disc
/// </summary>
public class DiscInfo
{
public Command.CATALOG Catalog;
public Command.ISRC ISRC;
public Command.CDTEXTFILE CDTextFile;
}
/// <summary>
/// The sequential list of commands parsed out of the cue file
/// </summary>
public List<object> Commands = new List<object>();
/// <summary>
/// Stuff other than the commands, global for the whole disc
/// </summary>
public DiscInfo GlobalDiscInfo = new DiscInfo();
[Flags]
public enum TrackFlags
{
None = 0,
PRE = 1, //Pre-emphasis enabled (audio tracks only)
DCP = 2, //Digital copy permitted
DATA = 4, //Set automatically by cue-processing equipment, here for completeness
_4CH = 8, //Four channel audio
SCMS = 64, //Serial copy management system (not supported by all recorders) (??)
}
//All audio files (WAVE, AIFF, and MP3) must be in 44.1KHz 16-bit stereo format.
//BUT NOTE: MP3 can be VBR and the length can't be known without decoding the whole thing.
//But, some ideas:
//1. we could operate ffmpeg differently to retrieve the length, which maybe it can do without having to decode the entire thing
//2. we could retrieve it from an ID3 if present.
//3. as a last resort, since MP3 is the annoying case usually, we could include my c# mp3 parser and sum the length (test the performance, this might be reasonably fast on par with ECM parsing)
//NOTE: once deciding the length, we would have to stick with it! samples would have to be discarded or inserted to make the track work out
//but we COULD effectively achieve stream-loading mp3 discs, with enough work.
public enum FileType
{
Unspecified,
BINARY, //Intel binary file (least significant byte first)
MOTOROLA, //Motorola binary file (most significant byte first)
AIFF, //Audio AIFF file
WAVE, //Audio WAVE file
MP3, //Audio MP3 file
}
public enum TrackType
{
Unknown,
Audio, //Audio/Music (2352)
CDG, //Karaoke CD+G (2448)
Mode1_2048, //CDROM Mode1 Data (cooked)
Mode1_2352, //CDROM Mode1 Data (raw)
Mode2_2336, //CDROM-XA Mode2 Data (could contain form 1 or form 2)
Mode2_2352, //CDROM-XA Mode2 Data (but there's no reason to distinguish this from Mode1_2352 other than to alert us that the entire session should be XA
CDI_2336, //CDI Mode2 Data
CDI_2352 //CDI Mode2 Data
}
class CueLineParser
{
int index;
string str;
public bool EOF;
public CueLineParser(string line)
{
str = line;
}
public string ReadPath() { return ReadToken(Mode.Quotable); }
public string ReadToken() { return ReadToken(Mode.Normal); }
public string ReadLine()
{
int len = str.Length;
string ret = str.Substring(index, len - index);
index = len;
EOF = true;
return ret;
}
enum Mode
{
Normal, Quotable
}
string ReadToken(Mode mode)
{
if (EOF) return null;
bool isPath = mode == Mode.Quotable;
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;
}
string ret = str.Substring(startIndex, index - startIndex);
if (mode == Mode.Quotable)
ret = ret.Trim('"');
return ret;
}
}
internal void LoadFromString(ParseCueJob job)
{
string cueString = job.IN_CueString;
TextReader tr = new StringReader(cueString);
for (; ; )
{
job.CurrentLine++;
string line = tr.ReadLine();
if (line == null) break;
line = line.Trim();
if (line == "") continue;
var clp = new CueLineParser(line);
string key = clp.ReadToken().ToUpperInvariant();
if (key.StartsWith(";"))
{
clp.EOF = true;
Commands.Add(new Command.COMMENT() { Value = line });
}
else switch (key)
{
default:
job.Warn("Unknown command: " + key);
break;
case "CATALOG":
if (GlobalDiscInfo.Catalog != null)
job.Warn("Multiple CATALOG commands detected. Subsequent ones are ignored.");
else if (clp.EOF)
job.Warn("Ignoring empty CATALOG command");
else Commands.Add(GlobalDiscInfo.Catalog = new Command.CATALOG() { Value = clp.ReadToken() });
break;
case "CDTEXTFILE":
if (GlobalDiscInfo.CDTextFile != null)
job.Warn("Multiple CDTEXTFILE commands detected. Subsequent ones are ignored.");
else if (clp.EOF)
job.Warn("Ignoring empty CDTEXTFILE command");
else Commands.Add(GlobalDiscInfo.CDTextFile = new Command.CDTEXTFILE() { Path = clp.ReadPath() });
break;
case "FILE":
{
var path = clp.ReadPath();
FileType ft;
if (clp.EOF)
{
job.Error("FILE command is missing file type.");
ft = FileType.Unspecified;
}
else
{
var strType = clp.ReadToken().ToUpperInvariant();
switch (strType)
{
default:
job.Error("Unknown FILE type: " + strType);
ft = FileType.Unspecified;
break;
case "BINARY": ft = FileType.BINARY; break;
case "MOTOROLA": ft = FileType.MOTOROLA; break;
case "BINARAIFF": ft = FileType.AIFF; break;
case "WAVE": ft = FileType.WAVE; break;
case "MP3": ft = FileType.MP3; break;
}
}
Commands.Add(new Command.FILE() { Path = path, Type = ft });
}
break;
case "FLAGS":
{
var cmd = new Command.FLAGS();
Commands.Add(cmd);
while (!clp.EOF)
{
var flag = clp.ReadToken().ToUpperInvariant();
switch (flag)
{
case "DATA":
default:
job.Warn("Unknown FLAG: " + flag);
break;
case "DCP": cmd.Flags |= TrackFlags.DCP; break;
case "4CH": cmd.Flags |= TrackFlags._4CH; break;
case "PRE": cmd.Flags |= TrackFlags.PRE; break;
case "SCMS": cmd.Flags |= TrackFlags.SCMS; break;
}
}
if (cmd.Flags == TrackFlags.None)
job.Warn("Empty FLAG command");
}
break;
case "INDEX":
{
if (clp.EOF)
{
job.Error("Incomplete INDEX command");
break;
}
string strindexnum = clp.ReadToken();
int indexnum;
if (!int.TryParse(strindexnum, out indexnum) || indexnum < 0 || indexnum > 99)
{
job.Error("Invalid INDEX number: " + strindexnum);
break;
}
string str_timestamp = clp.ReadToken();
var ts = new Timestamp(str_timestamp);
if (!ts.Valid)
{
job.Error("Invalid INDEX timestamp: " + str_timestamp);
break;
}
Commands.Add(new Command.INDEX() { Number = indexnum, Timestamp = ts });
}
break;
case "ISRC":
if (GlobalDiscInfo.ISRC != null)
job.Warn("Multiple ISRC commands detected. Subsequent ones are ignored.");
else if (clp.EOF)
job.Warn("Ignoring empty ISRC command");
else
{
var isrc = clp.ReadToken();
if (isrc.Length != 12)
job.Warn("Invalid ISRC code ignored: " + isrc);
else
{
Commands.Add(new Command.ISRC() { Value = isrc });
}
}
break;
case "PERFORMER":
Commands.Add(new Command.PERFORMER() { Value = clp.ReadPath() ?? "" });
break;
case "POSTGAP":
case "PREGAP":
{
var str_msf = clp.ReadToken();
var msf = new Timestamp(str_msf);
if (!msf.Valid)
job.Error("Ignoring {0} with invalid length MSF: " + str_msf, key);
else
{
if (key == "POSTGAP")
Commands.Add(new Command.POSTGAP() { Length = msf });
else
Commands.Add(new Command.PREGAP() { Length = msf });
}
}
break;
case "REM":
Commands.Add(new Command.REM() { Value = clp.ReadLine() });
break;
case "SONGWRITER":
Commands.Add(new Command.SONGWRITER() { Value = clp.ReadPath() ?? "" });
break;
case "TITLE":
Commands.Add(new Command.TITLE() { Value = clp.ReadPath() ?? "" });
break;
case "TRACK":
{
if (clp.EOF)
{
job.Error("Incomplete TRACK command");
break;
}
string str_tracknum = clp.ReadToken();
int tracknum;
if (!int.TryParse(str_tracknum, out tracknum) || tracknum < 1 || tracknum > 99)
{
job.Error("Invalid TRACK number: " + str_tracknum);
break;
}
//TODO - check sequentiality? maybe as a warning
TrackType tt;
var str_trackType = clp.ReadToken();
switch (str_trackType.ToUpperInvariant())
{
default:
job.Error("Unknown TRACK type: " + str_trackType);
tt = TrackType.Unknown;
break;
case "AUDIO": tt = TrackType.Audio; break;
case "CDG": tt = TrackType.CDG; break;
case "MODE1/2048": tt = TrackType.Mode1_2048; break;
case "MODE1/2352": tt = TrackType.Mode1_2352; break;
case "MODE2/2336": tt = TrackType.Mode2_2336; break;
case "MODE2/2352": tt = TrackType.Mode2_2352; break;
case "CDI/2336": tt = TrackType.CDI_2336; break;
case "CDI/2352": tt = TrackType.CDI_2352; break;
}
Commands.Add(new Command.TRACK() { Number = tracknum, Type = tt });
}
break;
}
if (!clp.EOF)
{
var remainder = clp.ReadLine();
if (remainder.TrimStart().StartsWith(";"))
{
//add a comment
Commands.Add(new Command.COMMENT() { Value = remainder });
}
else job.Warn("Unknown text at end of line after processing command: " + key);
}
} //end cue parsing loop
job.FinishLog();
} //LoadFromString
}
/// <summary>
/// Performs minimum parse processing on a cue file
/// </summary>
public void ParseCueFile(ParseCueJob job)
{
job.OUT_CueFile = new CueFile();
job.OUT_CueFile.LoadFromString(job);
}
} //partial class
} //namespace

View File

@ -0,0 +1,156 @@
using System;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Context
{
abstract class SS_Base : ISectorSynthJob2448
{
public IBlob Blob;
public long BlobOffset;
public DiscMountPolicy Policy;
//subQ data
public SubchannelQ sq;
//subP data
public bool Pause;
public abstract void Synth(SectorSynthJob job);
protected void SynthSubchannelAsNeed(SectorSynthJob job)
{
//synth P if needed
if ((job.Parts & ESectorSynthPart.SubchannelP) != 0)
{
SynthUtils.SubP(job.DestBuffer2448, job.DestOffset + 2352, Pause);
}
//synth Q if needed
//TODO - why not already have it serialized? Into a disc resource, even.
if ((job.Parts & ESectorSynthPart.SubchannelQ) != 0)
{
SynthUtils.SubQ_Serialize(job.DestBuffer2448, job.DestOffset + 2352 + 12, ref sq);
}
//clear R-W if needed
if ((job.Parts & ESectorSynthPart.Subchannel_RSTUVW) != 0)
{
Array.Clear(job.DestBuffer2448, job.DestOffset + 2352 + 12 + 12, (12 * 6));
}
//subcode has been generated deinterleaved; we may still need to interleave it
if((job.Parts & ESectorSynthPart.SubcodeAny) != 0)
if ((job.Parts & (ESectorSynthPart.SubcodeDeinterleave)) == 0)
{
SynthUtils.InterleaveSubcodeInplace(job.DestBuffer2448, job.DestOffset + 2352);
}
}
}
/// <summary>
/// Represents a Mode1 2048-byte sector
/// </summary>
class SS_Mode1_2048 : SS_Base
{
public override void Synth(SectorSynthJob job)
{
//read the sector user data
if((job.Parts & ESectorSynthPart.User2048) != 0)
Blob.Read(BlobOffset, job.DestBuffer2448, job.DestOffset + 16, 2048);
if ((job.Parts & ESectorSynthPart.Header16) != 0)
SynthUtils.SectorHeader(job.DestBuffer2448, job.DestOffset + 0, job.LBA, 1);
if ((job.Parts & ESectorSynthPart.ECMAny) != 0)
SynthUtils.ECM_Mode1(job.DestBuffer2448, job.DestOffset + 0, job.LBA);
SynthSubchannelAsNeed(job);
}
}
/// <summary>
/// Represents a 2352-byte sector of any sort
/// </summary>
class SS_2352 : SS_Base
{
public override void Synth(SectorSynthJob job)
{
//read the sector user data
Blob.Read(BlobOffset, job.DestBuffer2448, job.DestOffset, 2352);
//if subcode is needed, synthesize it
SynthSubchannelAsNeed(job);
}
}
class SS_Gap : SS_Base
{
public CueFile.TrackType TrackType;
public override void Synth(SectorSynthJob job)
{
//this isn't fully analyzed/optimized
Array.Clear(job.DestBuffer2448, job.DestOffset, 2352);
byte mode = 255;
int form = -1;
switch (TrackType)
{
case CueFile.TrackType.Audio:
mode = 0;
break;
case CueFile.TrackType.CDI_2352:
case CueFile.TrackType.Mode1_2352:
mode = 1;
break;
case CueFile.TrackType.Mode2_2352:
mode = 2;
if (Policy.CUE_PregapMode2_As_XA_Form2)
{
job.DestBuffer2448[job.DestOffset + 12 + 6] = 0x20;
job.DestBuffer2448[job.DestOffset + 12 + 10] = 0x20;
}
form = 2; //no other choice right now really
break;
case CueFile.TrackType.Mode1_2048:
mode = 1;
Pause = true;
break;
case CueFile.TrackType.Mode2_2336:
default:
throw new InvalidOperationException("Not supported: " + TrackType);
}
//audio has no sector header but the others do
if (mode != 0)
{
if ((job.Parts & ESectorSynthPart.Header16) != 0)
SynthUtils.SectorHeader(job.DestBuffer2448, job.DestOffset + 0, job.LBA, mode);
}
if (mode == 1)
{
if ((job.Parts & ESectorSynthPart.ECMAny) != 0)
SynthUtils.ECM_Mode1(job.DestBuffer2448, job.DestOffset + 0, job.LBA);
}
if (mode == 2 && form == 2)
{
SynthUtils.EDC_Mode2_Form2(job.DestBuffer2448, job.DestOffset);
}
SynthSubchannelAsNeed(job);
}
}
}
}

View File

@ -0,0 +1,124 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Context
{
/// <summary>
/// The CUE module user's hook for controlling how cue member file paths get resolved
/// </summary>
public class CueFileResolver
{
public bool caseSensitive = false;
public bool IsHardcodedResolve { get; private set; }
string baseDir;
/// <summary>
/// Retrieving the FullName from a FileInfo can be slow (and probably other operations), so this will cache all the needed values
/// TODO - could we treat it like an actual cache and only fill the FullName if it's null?
/// </summary>
struct MyFileInfo
{
public string FullName;
public FileInfo FileInfo;
}
DirectoryInfo diBasedir;
MyFileInfo[] fisBaseDir;
/// <summary>
/// sets the base directory and caches the list of files in the directory
/// </summary>
public void SetBaseDirectory(string baseDir)
{
this.baseDir = baseDir;
diBasedir = new DirectoryInfo(baseDir);
//list all files, so we dont scan repeatedly.
fisBaseDir = MyFileInfosFromFileInfos(diBasedir.GetFiles());
}
/// <summary>
/// TODO - doesnt seem like we're using this...
/// </summary>
public void SetHardcodeResolve(IDictionary<string, string> hardcodes)
{
IsHardcodedResolve = true;
fisBaseDir = new MyFileInfo[hardcodes.Count];
int i = 0;
foreach (var kvp in hardcodes)
{
fisBaseDir[i++] = new MyFileInfo { FullName = kvp.Key, FileInfo = new FileInfo(kvp.Value) };
}
}
MyFileInfo[] MyFileInfosFromFileInfos(FileInfo[] fis)
{
var myfis = new MyFileInfo[fis.Length];
for (int i = 0; i < fis.Length; i++)
{
myfis[i].FileInfo = fis[i];
myfis[i].FullName = fis[i].FullName;
}
return myfis;
}
/// <summary>
/// Performs cue-intelligent logic to acquire a file requested by the cue.
/// Returns the resulting full path(s).
/// If there are multiple options, it returns them all
/// </summary>
public List<string> Resolve(string path)
{
string targetFile = Path.GetFileName(path);
string targetFragment = Path.GetFileNameWithoutExtension(path);
DirectoryInfo di = null;
MyFileInfo[] fileInfos;
if (!string.IsNullOrEmpty(Path.GetDirectoryName(path)))
{
di = new FileInfo(path).Directory;
//fileInfos = di.GetFiles(Path.GetFileNameWithoutExtension(path)); //does this work?
fileInfos = MyFileInfosFromFileInfos(di.GetFiles()); //we (probably) have to enumerate all the files to do a search anyway, so might as well do this
//TODO - dont do the search until a resolve fails
}
else
{
di = diBasedir;
fileInfos = fisBaseDir;
}
var results = new List<FileInfo>();
foreach (var fi in fileInfos)
{
var ext = Path.GetExtension(fi.FullName).ToLowerInvariant();
//some choices are always bad: (we're looking for things like .bin and .wav)
//it's a little unclear whether we should go for a whitelist or a blacklist here.
//there's similar numbers of cases either way.
//perhaps we could code both (and prefer choices from the whitelist)
if (ext == ".cue" || ext == ".sbi" || ext == ".ccd" || ext == ".sub")
continue;
string fragment = Path.GetFileNameWithoutExtension(fi.FullName);
//match files with differing extensions
int cmp = string.Compare(fragment, targetFragment, !caseSensitive);
if (cmp != 0)
//match files with another extension added on (likely to be mygame.bin.ecm)
cmp = string.Compare(fragment, targetFile, !caseSensitive);
if (cmp == 0)
results.Add(fi.FileInfo);
}
var ret = new List<string>();
foreach (var fi in results)
ret.Add(fi.FullName);
return ret;
}
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using BizHawk.Common.IOExtensions;
namespace BizHawk.Emulation.DiscSystem.SBI
{
public class SBIParseException : Exception
{
public SBIParseException(string message) : base(message) { }
}
/// <summary>
/// The interpreted contents of an SBI file
/// </summary>
public class SubQPatchData
{
/// <summary>
/// a list of patched ABAs
/// </summary>
public List<int> ABAs = new List<int>();
/// <summary>
/// 12 values (Q subchannel data) for every patched ABA; -1 means unpatched
/// </summary>
public short[] subq;
}
public static class SBIFormat
{
/// <summary>
/// Does a cursory check to see if the file looks like an SBI
/// </summary>
public static bool QuickCheckISSBI(string path)
{
using (var fs = File.OpenRead(path))
{
BinaryReader br = new BinaryReader(fs);
string sig = br.ReadStringFixedAscii(4);
if (sig != "SBI\0")
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,246 @@
using System;
using BizHawk.Common.BufferExtensions;
namespace BizHawk.Emulation.DiscSystem
{
public class DiscHasher
{
public DiscHasher(Disc disc)
{
this.disc = disc;
}
Disc disc;
/// <summary>
/// calculates the hash for quick PSX Disc identification
/// </summary>
public uint Calculate_PSX_BizIDHash()
{
//notes about the hash:
//"Arc the Lad II (J) 1.0 and 1.1 conflict up to 25 sectors (so use 26)
//Tekken 3 (Europe) (Alt) and Tekken 3 (Europe) conflict in track 2 and 3 unfortunately, not sure what to do about this yet
//the TOC isn't needed!
//but it will help detect dumps with mangled TOCs which are all too common
//
//a possibly special CRC32 is used to help us match redump's DB elsewhere
SpecialCRC32 crc = new SpecialCRC32();
byte[] buffer2352 = new byte[2352];
var dsr = new DiscSectorReader(disc);
dsr.Policy.DeterministicClearBuffer = false; //live dangerously
//hash the TOC
crc.Add((int)disc.TOC.Session1Format);
crc.Add(disc.TOC.FirstRecordedTrackNumber);
crc.Add(disc.TOC.LastRecordedTrackNumber);
for (int i = 1; i <= 100; i++)
{
//if (disc.TOC.TOCItems[i].Exists) Console.WriteLine("{0:X8} {1:X2} {2:X2} {3:X8}", crc.Current, (int)disc.TOC.TOCItems[i].Control, disc.TOC.TOCItems[i].Exists ? 1 : 0, disc.TOC.TOCItems[i].LBATimestamp.Sector); //a little debugging
crc.Add((int)disc.TOC.TOCItems[i].Control);
crc.Add(disc.TOC.TOCItems[i].Exists ? 1 : 0);
crc.Add((int)disc.TOC.TOCItems[i].LBATimestamp.Sector);
}
//hash first 26 sectors
for (int i = 0; i < 26; i++)
{
dsr.ReadLBA_2352(i, buffer2352, 0);
crc.Add(buffer2352, 0, 2352);
}
return crc.Result;
}
/// <summary>
/// calculates the complete disc hash for matching to a redump
/// </summary>
public uint Calculate_PSX_RedumpHash()
{
//a special CRC32 is used to help us match redump's DB
SpecialCRC32 crc = new SpecialCRC32();
byte[] buffer2352 = new byte[2352];
var dsr = new DiscSectorReader(disc);
dsr.Policy.DeterministicClearBuffer = false; //live dangerously
//read all sectors for redump hash
for (int i = 0; i < disc.Session1.LeadoutLBA; i++)
{
dsr.ReadLBA_2352(i, buffer2352, 0);
crc.Add(buffer2352, 0, 2352);
}
return crc.Result;
}
// gets an identifying hash. hashes the first 512 sectors of
// the first data track on the disc.
//TODO - this is a very platform-specific thing. hashing the TOC may be faster and be just as effective. so, rename it appropriately
public string OldHash()
{
byte[] buffer = new byte[512 * 2352];
DiscSectorReader dsr = new DiscSectorReader(disc);
foreach (var track in disc.Session1.Tracks)
{
if (track.IsAudio)
continue;
int lba_len = Math.Min(track.NextTrack.LBA, 512);
for (int s = 0; s < 512 && s < lba_len; s++)
dsr.ReadLBA_2352(track.LBA + s, buffer, s * 2352);
return buffer.HashMD5(0, lba_len * 2352);
}
return "no data track found";
}
/// <summary>
/// A stateful special CRC32 calculator
/// This may be absolutely standard and not special at all. I don't know, there were some differences between it and other CRC code I found in bizhawk
/// </summary>
public class SpecialCRC32
{
private static readonly uint[] CRC32Table;
static SpecialCRC32()
{
CRC32Table = new uint[256];
for (uint i = 0; i < 256; ++i)
{
uint crc = i;
for (int j = 8; j > 0; --j)
{
if ((crc & 1) == 1)
crc = ((crc >> 1) ^ 0xEDB88320);
else
crc >>= 1;
}
CRC32Table[i] = crc;
}
}
uint current = 0xFFFFFFFF;
public unsafe void Add(byte[] data, int offset, int size)
{
if (offset + size > data.Length)
throw new ArgumentOutOfRangeException();
if (offset < 0)
throw new ArgumentOutOfRangeException();
fixed (byte* pData = data)
for (int i = 0; i < size; i++)
{
byte b = pData[offset + i];
current = CRC32Table[(current ^ b) & 0xFF] ^ (current >> 8);
}
}
byte[] smallbuf = new byte[8];
public void Add(int data)
{
smallbuf[0] = (byte)((data) & 0xFF);
smallbuf[1] = (byte)((data >> 8) & 0xFF);
smallbuf[2] = (byte)((data >> 16) & 0xFF);
smallbuf[3] = (byte)((data >> 24) & 0xFF);
Add(smallbuf, 0, 4);
}
/// <summary>
/// The negated output (the typical result of the CRC calculation)
/// </summary>
public uint Result { get { return current ^ 0xFFFFFFFF; } }
/// <summary>
/// The raw non-negated output
/// </summary>
public uint Current { get { return current; } set { current = value; } }
uint gf2_matrix_times(uint[] mat, uint vec)
{
int matIdx = 0;
uint sum = 0;
while (vec != 0)
{
if ((vec & 1) != 0)
sum ^= mat[matIdx];
vec >>= 1;
matIdx++;
}
return sum;
}
void gf2_matrix_square(uint[] square, uint[] mat)
{
int n;
for (n = 0; n < 32; n++)
square[n] = gf2_matrix_times(mat, mat[n]);
}
/// <summary>
/// Incorporates a pre-calculated CRC with the given length by combining crcs
/// It's a bit flaky, so be careful, but it works
/// </summary>
public void Incorporate(uint crc, int len)
{
current = crc32_combine(current, crc, len);
}
//tables used by crc32_combine
uint[] even, odd;
//algorithm from zlib's crc32_combine. read http://www.leapsecond.com/tools/crcomb.c for more
uint crc32_combine(uint crc1, uint crc2, int len2)
{
if (even == null) even = new uint[32]; // even-power-of-two zeros operator
if (odd == null) odd = new uint[32]; // odd-power-of-two zeros operator
// degenerate case
if (len2 == 0)
return crc1;
// put operator for one zero bit in odd
odd[0] = 0xedb88320; //CRC-32 polynomial
uint row = 1;
for (int n = 1; n < 32; n++)
{
odd[n] = row;
row <<= 1;
}
//put operator for two zero bits in even
gf2_matrix_square(even, odd);
//put operator for four zero bits in odd
gf2_matrix_square(odd, even);
//apply len2 zeros to crc1 (first square will put the operator for one zero byte, eight zero bits, in even)
do
{
//apply zeros operator for this bit of len2
gf2_matrix_square(even, odd);
if ((len2 & 1) != 0)
crc1 = gf2_matrix_times(even, crc1);
len2 >>= 1;
//if no more bits set, then done
if (len2 == 0)
break;
//another iteration of the loop with odd and even swapped
gf2_matrix_square(odd, even);
if ((len2 & 1) != 0)
crc1 = gf2_matrix_times(odd, crc1);
len2 >>= 1;
//if no more bits set, then done
} while (len2 != 0);
//return combined crc
crc1 ^= crc2;
return crc1;
}
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
//disc type identification logic
@ -42,8 +43,18 @@ namespace BizHawk.Emulation.DiscSystem
MegaCD
}
sealed public partial class Disc
public class DiscIdentifier
{
public DiscIdentifier(Disc disc)
{
this.disc = disc;
dsr = new DiscSectorReader(disc);
}
Disc disc;
DiscSectorReader dsr;
Dictionary<int, byte[]> sectorCache = new Dictionary<int,byte[]>();
/// <summary>
/// Attempts to determine the type of the disc.
/// In the future, we might return a struct or a class with more detailed information
@ -61,9 +72,10 @@ namespace BizHawk.Emulation.DiscSystem
//we dont know how to detect TurboCD.
//an emulator frontend will likely just guess TurboCD if the disc is UnknownFormat
//(we can also have a gameDB!)
var iso = new ISOFile();
bool isIso = iso.Parse(DiscStream.Open_LBA_2048(this));
bool isIso = iso.Parse(new DiscStream(disc, EDiscStreamView.DiscStreamView_Mode1_2048, 0));
if (isIso)
{
@ -108,8 +120,17 @@ namespace BizHawk.Emulation.DiscSystem
private bool StringAt(string s, int n, int lba = 0)
{
byte[] data = new byte[2048];
ReadLBA_2048(lba, data, 0);
//read it if we dont have it cached
//we wont be caching very much here, it's no big deal
//identification is not something we want to take a long time
byte[] data;
if (!sectorCache.TryGetValue(lba, out data))
{
data = new byte[2048];
dsr.ReadLBA_2048(lba, data, 0);
sectorCache[lba] = data;
}
byte[] cmp = System.Text.Encoding.ASCII.GetBytes(s);
byte[] cmp2 = new byte[cmp.Length];
Buffer.BlockCopy(data, n, cmp2, 0, cmp.Length);

View File

@ -0,0 +1,58 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Returns ERRORS for things which will can't be processed any further. Processing of this job will continue though to try to collect the maximum amount of feedback.
/// Returns WARNINGS for things which will are irregular or erroneous but later jobs might be able to handle, or which can be worked around by configuration assumptions.
/// TODO - make IDisposable so I don't have to remember to Finish() it?
/// </summary>
public class DiscJob
{
internal int CurrentLine = -1;
internal StringWriter swLog = new StringWriter();
internal void Warn(string format, params object[] args) { Log("WARN ", format, args); }
internal void Error(string format, params object[] args) { OUT_ErrorLevel = true; Log("ERROR", format, args); }
internal void Log(string level, string format, params object[] args)
{
var msg = string.Format(format, args);
if (CurrentLine == -1)
swLog.WriteLine("{0}: {1}", level, msg);
else
swLog.WriteLine("[{0,3}] {1}: {2}", CurrentLine, level, msg);
}
/// <summary>
/// Whether there were any errors
/// </summary>
public bool OUT_ErrorLevel = false;
/// <summary>
/// output: log transcript of the job
/// </summary>
public string OUT_Log;
/// <summary>
/// Finishes logging. Flushes the output and closes the logging mechanism
/// </summary>
internal void FinishLog()
{
OUT_Log = swLog.ToString();
swLog.Close();
}
/// <summary>
/// Concatenates the log results from the provided job into this log
/// </summary>
public void ConcatenateJobLog(DiscJob job)
{
OUT_ErrorLevel |= job.OUT_ErrorLevel;
swLog.Write(job.OUT_Log);
}
}
}

View File

@ -0,0 +1,135 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class DiscMountJob
{
class SS_MednaDisc : ISectorSynthJob2448
{
public void Synth(SectorSynthJob job)
{
//mednafen is always synthesizing everything, no need to worry about flags.. mostly./
job.Params.MednaDisc.Read_2442(job.LBA, job.DestBuffer2448, job.DestOffset);
//we may still need to deinterleave it if subcode was requested and it needs deinterleaving
if ((job.Parts & (ESectorSynthPart.SubcodeDeinterleave | ESectorSynthPart.SubcodeAny)) != 0)
{
SynthUtils.DeinterleaveSubcodeInplace(job.DestBuffer2448, 2352);
}
}
}
void RunMednaDisc()
{
var disc = new Disc();
OUT_Disc = disc;
//create a MednaDisc and give it to the disc for ownership
var md = new MednaDisc(IN_FromPath);
disc.DisposableResources.Add(md);
//"length of disc" for bizhawk's purposes (NOT a robust concept!) is determined by beginning of leadout track
var m_leadoutTrack = md.TOCTracks[100];
int nSectors = (int)m_leadoutTrack.lba;
//make synth param memos
disc.SynthParams.MednaDisc = md;
//this is the sole sector synthesizer we'll need
var synth = new SS_MednaDisc();
//make sector interfaces:
for (int i = 0; i < 150; i++)
{
disc.Sectors.Add(synth);
}
//2. actual sectors
for (int i = 0; i < nSectors; i++)
{
disc.Sectors.Add(synth);
}
//ADR (q-Mode) is necessarily 0x01 for a RawTOCEntry
const int kADR = 1;
const int kUnknownControl = 0;
//mednafen delivers us what is essentially but not exactly (or completely) a TOCRaw.
//we need to synth RawTOCEntries from this and then turn it into a proper TOCRaw
//when coming from mednafen, there are 101 entries.
//entry[0] is placeholder junk, not to be used
//entry[100] is the leadout track (A0)
//A1 and A2 are in the form of FirstRecordedTrackNumber and LastRecordedTrackNumber
for (int i = 1; i < 101; i++)
{
var m_te = md.TOCTracks[i];
//dont add invalid (absent) items
if (!m_te.Valid)
continue;
var m_ts = new Timestamp((int)m_te.lba + 150); //these are supposed to be absolute timestamps
var q = new SubchannelQ
{
q_status = SubchannelQ.ComputeStatus(kADR, (EControlQ)m_te.control),
q_tno = BCD2.FromDecimal(0), //unknown with mednadisc
q_index = BCD2.FromDecimal(i),
min = BCD2.FromDecimal(0), //unknown with mednadisc
sec = BCD2.FromDecimal(0), //unknown with mednadisc
frame = BCD2.FromDecimal(0), //unknown with mednadisc
zero = 0, //unknown with mednadisc
ap_min = BCD2.FromDecimal(m_ts.MIN),
ap_sec = BCD2.FromDecimal(m_ts.SEC),
ap_frame = BCD2.FromDecimal(m_ts.FRAC),
q_crc = 0 //meaningless
};
//a special fixup: mednafen's entry 100 is the lead-out track, so change it into the A2 raw toc entry
if (i == 100)
{
q.q_index.BCDValue = 0xA2;
}
disc.RawTOCEntries.Add(new RawTOCEntry { QData = q });
}
//synth A0 and A1 entries (indicating first and last recorded tracks and also session type)
var qA0 = new SubchannelQ
{
q_status = SubchannelQ.ComputeStatus(kADR, kUnknownControl),
q_tno = BCD2.FromDecimal(0), //unknown with mednadisc
q_index = BCD2.FromBCD(0xA0),
min = BCD2.FromDecimal(0), //unknown with mednadisc
sec = BCD2.FromDecimal(0), //unknown with mednadisc
frame = BCD2.FromDecimal(0), //unknown with mednadisc
zero = 0, //unknown with mednadisc
ap_min = BCD2.FromDecimal(md.TOC.first_track),
ap_sec = BCD2.FromDecimal(md.TOC.disc_type),
ap_frame = BCD2.FromDecimal(0),
q_crc = 0, //meaningless
};
disc.RawTOCEntries.Add(new RawTOCEntry { QData = qA0 });
var qA1 = new SubchannelQ
{
q_status = SubchannelQ.ComputeStatus(kADR, kUnknownControl),
q_tno = BCD2.FromDecimal(0), //unknown with mednadisc
q_index = BCD2.FromBCD(0xA1),
min = BCD2.FromDecimal(0), //unknown with mednadisc
sec = BCD2.FromDecimal(0), //unknown with mednadisc
frame = BCD2.FromDecimal(0), //unknown with mednadisc
zero = 0, //unknown with mednadisc
ap_min = BCD2.FromDecimal(md.TOC.last_track),
ap_sec = BCD2.FromDecimal(0),
ap_frame = BCD2.FromDecimal(0),
q_crc = 0, //meaningless
};
disc.RawTOCEntries.Add(new RawTOCEntry { QData = qA1 });
}
}
}

View File

@ -0,0 +1,170 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// A Job interface for mounting discs.
/// This is publicly exposed because it's the main point of control for fine-tuning disc loading options.
/// This would typically be used to load discs.
/// </summary>
public partial class DiscMountJob : DiscJob
{
/// <summary>
/// The filename to be loaded
/// </summary>
public string IN_FromPath;
/// <summary>
/// Slow-loading cues won't finish loading if this threshold is exceeded.
/// Set to 10 to always load a cue
/// </summary>
public int IN_SlowLoadAbortThreshold = 10;
/// <summary>
/// Cryptic policies to be used when mounting the disc.
/// </summary>
public DiscMountPolicy IN_DiscMountPolicy = new DiscMountPolicy();
/// <summary>
/// The interface to be used for loading the disc.
/// Usually you'll want DiscInterface.BizHawk, but others can be used for A/B testing
/// </summary>
public DiscInterface IN_DiscInterface = DiscInterface.BizHawk;
/// <summary>
/// The resulting disc
/// </summary>
public Disc OUT_Disc;
/// <summary>
/// Whether a mount operation was aborted due to being too slow
/// </summary>
public bool OUT_SlowLoadAborted;
public void Run()
{
switch (IN_DiscInterface)
{
case DiscInterface.LibMirage:
throw new NotSupportedException("LibMirage not supported yet");
case DiscInterface.BizHawk:
RunBizHawk();
break;
case DiscInterface.MednaDisc:
RunMednaDisc();
break;
}
if (OUT_Disc != null)
{
//generate toc and structure:
//1. TOCRaw from RawTOCEntries
var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job() { Entries = OUT_Disc.RawTOCEntries };
tocSynth.Run();
OUT_Disc.TOC = tocSynth.Result;
//2. Structure frmo TOCRaw
var structureSynth = new Synthesize_DiscStructure_From_DiscTOC_Job() { IN_Disc = OUT_Disc, TOCRaw = OUT_Disc.TOC };
structureSynth.Run();
OUT_Disc.Structure = structureSynth.Result;
}
FinishLog();
}
void RunBizHawk()
{
string infile = IN_FromPath;
string cue_content = null;
var cfr = new CUE_Context.CueFileResolver();
RERUN:
var ext = Path.GetExtension(infile).ToLowerInvariant();
if (ext == ".iso")
{
//make a fake cue file to represent this iso file and rerun it as a cue
string filebase = Path.GetFileName(infile);
cue_content = string.Format(@"
FILE ""{0}"" BINARY
TRACK 01 MODE1/2048
INDEX 01 00:00:00",
filebase);
infile = Path.ChangeExtension(infile, ".cue");
goto RERUN;
}
if (ext == ".cue")
{
//TODO - make sure code is designed so no matter what happens, a disc is disposed in case of errors.
//perhaps the CUE_Format2 (once renamed to something like Context) can handle that
var cuePath = IN_FromPath;
var cue2 = new CUE_Context();
cue2.DiscMountPolicy = IN_DiscMountPolicy;
cue2.Resolver = cfr;
if (!cfr.IsHardcodedResolve) cfr.SetBaseDirectory(Path.GetDirectoryName(infile));
//parse the cue file
var parseJob = new CUE_Context.ParseCueJob();
if (cue_content == null)
cue_content = File.ReadAllText(cuePath);
parseJob.IN_CueString = cue_content;
cue2.ParseCueFile(parseJob);
//TODO - need better handling of log output
if (!string.IsNullOrEmpty(parseJob.OUT_Log)) Console.WriteLine(parseJob.OUT_Log);
ConcatenateJobLog(parseJob);
//compile the cue file:
//includes this work: resolve required bin files and find out what it's gonna take to load the cue
var compileJob = new CUE_Context.CompileCueJob();
compileJob.IN_CueFormat = cue2;
compileJob.IN_CueFile = parseJob.OUT_CueFile;
compileJob.Run();
//TODO - need better handling of log output
if (!string.IsNullOrEmpty(compileJob.OUT_Log)) Console.WriteLine(compileJob.OUT_Log);
ConcatenateJobLog(compileJob);
//check slow loading threshold
if (compileJob.OUT_LoadTime >= IN_SlowLoadAbortThreshold)
{
Warn("Loading terminated due to slow load threshold");
OUT_SlowLoadAborted = true;
goto DONE;
}
//actually load it all up
var loadJob = new CUE_Context.LoadCueJob();
loadJob.IN_CompileJob = compileJob;
loadJob.Run();
//TODO - need better handling of log output
if (!string.IsNullOrEmpty(loadJob.OUT_Log)) Console.WriteLine(loadJob.OUT_Log);
ConcatenateJobLog(loadJob);
OUT_Disc = loadJob.OUT_Disc;
//OUT_Disc.DiscMountPolicy = IN_DiscMountPolicy; //NOT SURE WE NEED THIS (only makes sense for cue probably)
//apply SBI if it exists (TODO - for formats other than cue?)
var sbiPath = Path.ChangeExtension(IN_FromPath, ".sbi");
if (File.Exists(sbiPath) && SBI.SBIFormat.QuickCheckISSBI(sbiPath))
{
var loadSbiJob = new SBI.LoadSBIJob() { IN_Path = sbiPath };
loadSbiJob.Run();
var applySbiJob = new ApplySBIJob();
applySbiJob.Run(OUT_Disc, loadSbiJob.OUT_Data, IN_DiscMountPolicy.SBI_As_Mednafen);
}
}
else if (ext == ".ccd")
{
CCD_Format ccdLoader = new CCD_Format();
OUT_Disc = ccdLoader.LoadCCDToDisc(IN_FromPath);
}
DONE: ;
}
}
}

View File

@ -0,0 +1,44 @@
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// General disc policies to be logically applied at mounting time. The choices are irreversible once a disc is loaded.
/// Maybe these are only for CUEs, but maybe not. Not sure yet.
/// Could put caching policies here too (cached ecm calculations, etc.)
/// </summary>
public class DiscMountPolicy
{
/// <summary>
/// "At the beginning of a Pause (i.e. Index = 00) the relative time is
/// --A-- set to the duration of the Pause.
/// During the Pause this relative time decreases and
/// --B-- equals zero in the last Section"
/// This is a contradiction.
/// By choosing true, mode A is selected, and the final sector of the pause is -1.
/// (I like this better. Defaulting until proven otherwise [write test case here])
/// By choosing false, mode B is selected, and the final sector of the pause is 0.
/// (Mednafen does it this way)
/// Discs (including PSX) exist using A, or B, or possibly (reference please) neither.
/// </summary>
public bool CUE_PregapContradictionModeA = true;
/// <summary>
/// Mednafen sets mode2 pregap sectors as XA Form2 sectors.
/// This is almost surely not right in every case.
/// </summary>
public bool CUE_PregapMode2_As_XA_Form2 = true;
/// <summary>
/// Mednafen loads SBI files oddly
/// </summary>
public bool SBI_As_Mednafen = true;
public void SetForPSX()
{
CUE_PregapContradictionModeA = false;
CUE_PregapMode2_As_XA_Form2 = true;
SBI_As_Mednafen = true;
}
}
}

View File

@ -0,0 +1,292 @@
using System;
using System.Collections.Generic;
using BizHawk.Common.BufferExtensions;
namespace BizHawk.Emulation.DiscSystem
{
public class DiscSectorReaderPolicy
{
/// <summary>
/// Different methods that can be used to get 2048 byte sectors
/// </summary>
public enum EUserData2048Mode
{
/// <summary>
/// The contents of the sector should be inspected (mode and form) and 2048 bytes returned accordingly
/// </summary>
InspectSector,
/// <summary>
/// Read it as mode 1
/// </summary>
AssumeMode1,
/// <summary>
/// Read it as mode 2 (form 1)
/// </summary>
AssumeMode2_Form1,
}
/// <summary>
/// The method used to get 2048 byte sectors
/// </summary>
public EUserData2048Mode UserData2048Mode = EUserData2048Mode.InspectSector;
/// <summary>
/// Throw exceptions if 2048 byte data can't be read
/// </summary>
public bool ThrowExceptions2048 = true;
/// <summary>
/// Indicates whether subcode should be delivered deinterleaved. It isn't stored that way on actual discs. But it is in .sub files.
/// This defaults to true because it's most likely higher-performing, and it's rarely ever wanted interleaved.
/// </summary>
public bool DeinterleavedSubcode = true;
/// <summary>
/// Indicates whether the output buffer should be cleared before returning any data.
/// This will unfortunately involve clearing sections you didn't ask for, and clearing sections about to be filled with data from the disc.
/// It is a waste of performance, but it will ensure reliability.
/// </summary>
public bool DeterministicClearBuffer = true;
}
/// <summary>
/// Main entry point for reading sectors from a disc.
/// This is not a multi-thread capable interface.
/// </summary>
public class DiscSectorReader
{
public DiscSectorReaderPolicy Policy = new DiscSectorReaderPolicy();
Disc disc;
public DiscSectorReader(Disc disc)
{
this.disc = disc;
}
void PrepareJob(int lba)
{
job.LBA = lba;
job.Params = disc.SynthParams;
job.Disc = disc;
}
void PrepareBuffer(byte[] buffer, int offset, int size)
{
if (Policy.DeterministicClearBuffer) Array.Clear(buffer, offset, size);
}
/// <summary>
/// Reads a full 2352 bytes of user data from a sector
/// </summary>
public int ReadLBA_2352(int lba, byte[] buffer, int offset)
{
var sector = disc.Sectors[lba + 150];
PrepareBuffer(buffer, offset, 2352);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.User2352;
job.Disc = disc;
//this can't include subcode, so it's senseless to handle it here
//if (Policy.DeinterleavedSubcode) job.Parts |= ESectorSynthPart.SubcodeDeinterleave;
sector.Synth(job);
Buffer.BlockCopy(buf2442, 0, buffer, offset, 2352);
return 2352;
}
/// <summary>
/// Reads the absolutely complete 2448 byte sector including all the user data and subcode
/// </summary>
public int ReadLBA_2448(int lba, byte[] buffer, int offset)
{
var sector = disc.Sectors[lba + 150];
PrepareBuffer(buffer, offset, 2352);
PrepareJob(lba);
job.DestBuffer2448 = buffer; //go straight to the caller's buffer
job.DestOffset = offset; //go straight to the caller's buffer
job.Parts = ESectorSynthPart.Complete2448;
if (Policy.DeinterleavedSubcode)
job.Parts |= ESectorSynthPart.SubcodeDeinterleave;
sector.Synth(job);
//we went straight to the caller's buffer, so no need to copy
return 2442;
}
int ReadLBA_2048_Mode1(int lba, byte[] buffer, int offset)
{
//we can read the 2048 bytes directly
var sector = disc.Sectors[lba + 150];
PrepareBuffer(buffer, offset, 2352);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.User2048;
sector.Synth(job);
Buffer.BlockCopy(buf2442, 16, buffer, offset, 2048);
return 2048;
}
int ReadLBA_2048_Mode2_Form1(int lba, byte[] buffer, int offset)
{
//we can read the 2048 bytes directly but we have to get them from the mode 2 data
var sector = disc.Sectors[lba + 150];
PrepareBuffer(buffer, offset, 2352);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.User2336;
sector.Synth(job);
Buffer.BlockCopy(buf2442, 24, buffer, offset, 2048);
return 2048;
}
/// <summary>
/// Reads 12 bytes of subQ data from a sector.
/// This is necessarily deinterleaved.
/// </summary>
public int ReadLBA_SubQ(int lba, byte[] buffer, int offset)
{
var sector = disc.Sectors[lba + 150];
PrepareBuffer(buffer, offset, 12);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.SubchannelQ | ESectorSynthPart.SubcodeDeinterleave;
sector.Synth(job);
Buffer.BlockCopy(buf2442, 2352 + 12, buffer, offset, 12);
return 12;
}
/// <summary>
/// reads 2048 bytes of user data from a sector.
/// This is only valid for Mode 1 and XA Mode 2 (Form 1) sectors.
/// Attempting it on any other sectors is ill-defined.
/// If any console is trying to do that, we'll have to add a policy for it, or handle it in the console.
/// (We can add a method to this API that checks the type of a sector to make that easier)
/// </summary>
public int ReadLBA_2048(int lba, byte[] buffer, int offset)
{
if (Policy.UserData2048Mode == DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode1)
return ReadLBA_2048_Mode1(lba, buffer, offset);
else if (Policy.UserData2048Mode == DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode2_Form1)
return ReadLBA_2048_Mode2_Form1(lba, buffer, offset);
else
{
//we need to determine the type of the sector.
//in no case do we need the ECC so build special flags here
var sector = disc.Sectors[lba + 150];
PrepareBuffer(buffer, offset, 2048);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.Header16 | ESectorSynthPart.User2048 | ESectorSynthPart.EDC12;
sector.Synth(job);
//now the inspection, based on the mode
byte mode = buf2442[15];
if (mode == 1)
{
Buffer.BlockCopy(buf2442, 16, buffer, offset, 2048);
return 2048;
}
else if (mode == 2)
{
//greenbook pg II-22
//we're going to do a sanity check here.. we're not sure what happens if we try to read 2048 bytes from a form-2 2324 byte sector
//we could handle it by policy but for now the policy is exception
byte submodeByte = buf2442[18];
int form = ((submodeByte >> 5) & 1) + 1;
if (form == 2)
{
if (Policy.ThrowExceptions2048)
throw new InvalidOperationException("Unsupported scenario: reading 2048 bytes from a Mode2 Form 2 sector");
else return 0;
}
//otherwise it's OK
Buffer.BlockCopy(buf2442, 24, buffer, offset, 2048);
return 2048;
}
else
{
if (Policy.ThrowExceptions2048)
throw new InvalidOperationException("Unsupported scenario: reading 2048 bytes from an unhandled sector type");
else return 0;
}
}
}
/// <summary>
/// Reads 12 bytes of subQ data from a sector and stores it unpacked into the provided struct
/// TODO - make use of deserialize code elsewhere
/// </summary>
public void ReadLBA_SubQ(int lba, out SubchannelQ sq)
{
ReadLBA_SubQ(lba, buf12, 0);
sq.q_status = buf12[0];
sq.q_tno.BCDValue = buf12[1];
sq.q_index.BCDValue = buf12[2];
sq.min.BCDValue = buf12[3];
sq.sec.BCDValue = buf12[4];
sq.frame.BCDValue = buf12[5];
sq.zero = buf12[6];
sq.ap_min.BCDValue = buf12[7];
sq.ap_sec.BCDValue = buf12[8];
sq.ap_frame.BCDValue = buf12[9];
//CRC is stored inverted and big endian.. so... do the opposite
byte hibyte = (byte)(~buf12[10]);
byte lobyte = (byte)(~buf12[11]);
sq.q_crc = (ushort)((hibyte << 8) | lobyte);
}
/// <summary>
/// Reads the mode field from a sector
/// If this is an audio sector, the results will be nonsense.
/// </summary>
public int ReadLBA_Mode(int lba)
{
var sector = disc.Sectors[lba + 150];
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.Header16;
job.Disc = disc;
sector.Synth(job);
return buf2442[15];
}
//lets not try to these as a sector cache. it gets too complicated. its just a temporary variable.
byte[] buf2442 = new byte[2448];
byte[] buf12 = new byte[12];
SectorSynthJob job = new SectorSynthJob();
}
}

View File

@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using BizHawk.Common.BufferExtensions;
namespace BizHawk.Emulation.DiscSystem
{
public enum EDiscStreamView
{
/// <summary>
/// views the disc as Mode 0 (aka Audio)
/// </summary>
DiscStreamView_Mode0_2352,
/// <summary>
/// views the disc as audio (aka Mode 0)
/// </summary>
DiscStreamView_Audio_2352 = DiscStreamView_Mode0_2352,
/// <summary>
/// views the disc as Mode 1
/// </summary>
DiscStreamView_Mode1_2048,
/// <summary>
/// views the disc as Mode 2
/// </summary>
DiscStreamView_Mode2_2336,
/// <summary>
/// views the disc as Mode 2 Form 1
/// </summary>
DiscStreamView_Mode2_Form1_2048,
/// <summary>
/// views the disc as Mode 2 Form 2
/// </summary>
DiscStreamView_Mode2_Form2_2324,
}
/// <summary>
/// Allows you to stream data off a disc.
/// For future work: depending on the View you select, it may not be seekable (in other words, it would need to read sequentially)
///
/// OLD COMMENTS:
/// Allows you to stream data off a disc
/// NOTE - it's probably been commented elsewhere, but this is possibly a bad idea! Turn it into views instead,
/// and the exact behaviour depends on the requested access level (raw or logical) and then what type of sectors are getting used
/// NOTE - actually even THAT is probably a bad idea. sector types can change on the fly.
/// this class promises something it can't deliver. (it's only being used to scan an ISO disc)
/// Well, we could make code that is full of red flags and warnings like "if this ISNT a 2048 byte sector ISO disc, then this wont work"
///
/// TODO - Receive some information about the track that this stream is modeling, and have the stream return EOF at the end of the track?
/// </summary>
public class DiscStream : System.IO.Stream
{
int SectorSize;
int NumSectors;
Disc Disc;
long currPosition;
byte[] cachedSectorBuffer;
int cachedSector;
DiscSectorReader dsr;
public DiscStream(Disc disc, EDiscStreamView view, int from_lba)
{
if (view != EDiscStreamView.DiscStreamView_Mode1_2048)
throw new NotSupportedException("disc streams of not mode 1 are currently unsupported");
SectorSize = 2048;
Disc = disc;
NumSectors = disc.Session1.LeadoutLBA;
dsr = new DiscSectorReader(disc);
//following the provided view
dsr.Policy.UserData2048Mode = DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode1;
currPosition = from_lba * SectorSize;
cachedSector = -1;
cachedSectorBuffer = new byte[SectorSize];
}
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return true; } }
public override bool CanWrite { get { return false; } }
public override void Flush() { throw new NotImplementedException(); }
public override long Length { get { return NumSectors * SectorSize; } }
public override long Position
{
get { return currPosition; }
set
{
currPosition = value;
//invalidate the cached sector..
//as a later optimization, we could actually intelligently decide if this is necessary
cachedSector = -1;
}
}
internal void READLBA_Flat_Implementation(long disc_offset, byte[] buffer, int offset, int length, Action<int, byte[], int> sectorReader, int sectorSize, byte[] sectorBuf, ref int sectorBufferHint)
{
//hint is the sector number which is already read. to avoid repeatedly reading the sector from the disc in case of several small reads, so that sectorBuf can be used as a sector cache
}
//TODO - I'm not sure everything in here makes sense right now..
public override int Read(byte[] buffer, int offset, int count)
{
long remainInDisc = Length - currPosition;
if (count > remainInDisc)
count = (int)Math.Min(remainInDisc, int.MaxValue);
int remain = count;
int readed = 0;
while (remain > 0)
{
int lba = (int)(currPosition / SectorSize);
int lba_within = (int)(currPosition % SectorSize);
int todo = remain;
int remains_in_lba = SectorSize - lba_within;
if (remains_in_lba < todo)
todo = remains_in_lba;
if (cachedSector != lba)
{
dsr.ReadLBA_2048(lba, cachedSectorBuffer, 0);
cachedSector = lba;
}
Array.Copy(cachedSectorBuffer, lba_within, buffer, offset, todo);
offset += todo;
remain -= todo;
currPosition += todo;
readed += todo;
}
return readed;
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
switch (origin)
{
case System.IO.SeekOrigin.Begin: Position = offset; break;
case System.IO.SeekOrigin.Current: Position += offset; break;
case System.IO.SeekOrigin.End: Position = Length - offset; break;
}
return Position;
}
public override void SetLength(long value) { throw new NotImplementedException(); }
public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); }
}
}

View File

@ -0,0 +1,175 @@
using System;
using System.Text;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Contains structural information for the disc broken down into c# data structures for easy interrogation.
/// This represents a best-effort interpretation of the raw disc image.
/// NOTE: Since this ended up really just having the list of sessions.. maybe it isn't needed and can just float on up into Disc
/// </summary>
public class DiscStructure
{
/// <summary>
/// This is a 0-indexed list of sessions (session 1 is at [0])
/// Support for multiple sessions is thoroughly not working yet
/// TODO - make re-index me with a null session 0
/// </summary>
public List<Session> Sessions = new List<Session>();
public class Session
{
//Notable omission:
//Length of the session
//How should this be defined? It's even harder than determining a track length
/// <summary>
/// The LBA of the session's leadout. In other words, for all intents and purposes, the end of the session
/// </summary>
public int LeadoutLBA { get { return LeadoutTrack.LBA; } }
/// <summary>
/// The session number
/// </summary>
public int Number;
/// <summary>
/// The number of user information tracks in the session.
/// This excludes track 0 and the lead-out track.
/// Use this instead of Tracks.Count
/// </summary>
public int InformationTrackCount { get { return Tracks.Count - 2; } }
/// <summary>
/// All the tracks in the session.. but... Tracks[0] is the lead-in track placeholder. Tracks[1] should be "Track 1". So beware of this.
/// For a disc with "3 tracks", Tracks.Count will be 5: it includes that lead-in track as well as the leadout track.
/// Perhaps we should turn this into a special collection type with no Count or Length, or a method to GetTrack()
/// </summary>
public List<Track> Tracks = new List<Track>();
/// <summary>
/// A reference to the first information track (Track 1)
/// </summary>
public Track FirstInformationTrack { get { return Tracks[1]; } }
/// <summary>
/// A reference to the first information track (Track 1)
/// </summary>
public Track LastInformationTrack { get { return Tracks[InformationTrackCount]; } }
/// <summary>
/// A reference to the lead-out track.
/// Effectively, the end of the user area of the disc.
/// </summary>
public Track LeadoutTrack { get { return Tracks[Tracks.Count - 1]; } }
/// <summary>
/// Determines which track of the session is at the specified LBA.
/// Returns null if it's before track 1
/// </summary>
public Track SeekTrack(int lba)
{
var ses = this;
//take care with this loop bounds:
for (int i = 1; i <= ses.InformationTrackCount; i++)
{
var track = ses.Tracks[i];
if (track.LBA > lba)
return (i == 1) ? null : ses.Tracks[i];
}
return ses.Tracks[ses.Tracks.Count];
}
}
/// <summary>
/// The Type of a track as specified in the TOC Q-Subchannel data from the control flags.
/// Could also be 4-Channel Audio, but we'll handle that later if needed
/// </summary>
public enum ETrackType
{
/// <summary>
/// The track type isn't always known.. it can take this value til its populated
/// </summary>
Unknown,
/// <summary>
/// Data track( TOC Q control 0x04 flag set )
/// </summary>
Data,
/// <summary>
/// Audio track( TOC Q control 0x04 flag clear )
/// </summary>
Audio
}
/// <summary>
/// Information about a Track.
/// </summary>
public class Track
{
//Notable omission:
//a list of Indices. It's difficult to reliably construct it.
//Notably, mednafen can't readily produce it.
//Indices may need scanning sector by sector.
//It's unlikely that any software would be needing indices anyway.
//We should add another index scanning service if that's ever needed.
//(note: a CCD should contain indices, but it's not clear whether it's required. logically it shouldnt be)
//Notable omission:
//Length of the track.
//How should this be defined? Between which indices? It's really hard.
//These omissions could be handled by ReadStructure() policies which permit the scanning of the entire disc.
//After that, they could be cached in here.
/// <summary>
/// The number of the track (1-indexed)
/// </summary>
public int Number;
/// <summary>
/// The Mode of the track (0 is Audio, 1 and 2 are data)
/// This is heuristically determined.
/// Actual sector contents may vary
/// </summary>
public int Mode;
/// <summary>
/// Is this track a Data track?
/// </summary>
public bool IsData { get { return !IsAudio; } }
/// <summary>
/// Is this track an Audio track?
/// </summary>
public bool IsAudio { get { return Mode == 0; } }
/// <summary>
/// The 'control' properties of the track indicated by the subchannel Q.
/// This is as indicated by the disc TOC.
/// Actual sector contents may vary.
/// </summary>
public EControlQ Control;
/// <summary>
/// The starting LBA of the track (index 1).
/// </summary>
public int LBA;
/// <summary>
/// The next track in the session. null for the leadout track of a session.
/// </summary>
public Track NextTrack;
}
public class Index
{
public int Number;
public int LBA;
}
}
}

View File

@ -0,0 +1,135 @@
using System;
//TODO - call on unmanaged code in mednadisc if available to do deinterleaving faster. be sure to benchmark it though..
//a decent little subcode reference
//http://www.jbum.com/cdg_revealed.html
//NOTES: the 'subchannel Q' stuff here has a lot to do with the q-Mode 1. q-Mode 2 is different,
//and q-Mode 1 technically is defined a little differently in the lead-in area, although the fields align so the datastructures can be reused
//Q subchannel basic structure: (quick ref: https://en.wikipedia.org/wiki/Compact_Disc_subcode)
//Byte 1: (aka `status`)
// q-Control: 4 bits (i.e. flags)
// q-Mode: 4 bits (aka ADR; WHY is this called ADR?)
//q-Data: other stuff depending on q-Mode and type of track
//q-CRC: CRC of preceding
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Control bit flags for the Q Subchannel.
/// </summary>
[Flags]
public enum EControlQ
{
None = 0,
PRE = 1, //Pre-emphasis enabled (audio tracks only)
DCP = 2, //Digital copy permitted
DATA = 4, //set for data tracks, clear for audio tracks
_4CH = 8, //Four channel audio
}
/// <summary>
/// Why did I make this a struct? I thought there might be a shitton of these and I was trying to cut down on object creation churn during disc-loading.
/// But I ended up mostly just having a shitton of byte[] for each buffer (I could improve that later to possibly reference a blob on top of a memorystream)
/// So, I should probably change that.
/// </summary>
public struct SubchannelQ
{
/// <summary>
/// ADR and CONTROL
/// TODO - make BCD2? PROBABLY NOT. I DONT KNOW.
/// </summary>
public byte q_status;
/// <summary>
/// normal track: BCD indication of the current track number
/// leadin track: should be 0
/// </summary>
public BCD2 q_tno;
/// <summary>
/// normal track: BCD indication of the current index
/// leadin track: 'POINT' field used to ID the TOC entry #
/// </summary>
public BCD2 q_index;
/// <summary>
/// These are the initial set of timestamps. Meaning varies:
/// check yellowbook 22.3.3 and 22.3.4
/// leadin track: unknown
/// user information track: relative timestamp
/// leadout: relative timestamp
/// TODO - why are these BCD2? having things in BCD2 is freaking annoying, I should only make them BCD2 when serializing into a subchannel Q buffer
/// EDIT - elsewhere I rambled "why not BCD2?". geh. need to make a final organized approach
/// </summary>
public BCD2 min, sec, frame;
/// <summary>
/// This is supposed to be zero.. but CCD format stores it, so maybe it's useful for copy protection or something
/// </summary>
public byte zero;
/// <summary>
/// These are the second set of timestamps. Meaning varies:
/// check yellowbook 22.3.3 and 22.3.4
/// leadin track q-mode 1: TOC entry, absolute MSF of track
/// user information track: absolute timestamp
/// leadout: absolute timestamp
/// </summary>
public BCD2 ap_min, ap_sec, ap_frame;
/// <summary>
/// Don't assume this CRC is correct, in the case of some copy protections it is intended to be wrong.
/// Furthermore, it is meaningless (and in BizHawk, unpopulated) for a TOC Entry
/// (since an invalid CRC on a [theyre redundantly/duplicately stored] toc entry would cause it to get discarded in favor of another one with a correct CRC)
/// </summary>
public ushort q_crc;
/// <summary>
/// Retrieves the initial set of timestamps (min,sec,frac) as a convenient Timestamp
/// </summary>
public Timestamp Timestamp
{
get { return new Timestamp(min.DecimalValue, sec.DecimalValue, frame.DecimalValue); }
set { min.DecimalValue = value.MIN; sec.DecimalValue = value.SEC; frame.DecimalValue = value.FRAC; }
}
/// <summary>
/// Retrieves the second set of timestamps (ap_min, ap_sec, ap_frac) as a convenient Timestamp.
/// </summary>
public Timestamp AP_Timestamp {
get { return new Timestamp(ap_min.DecimalValue, ap_sec.DecimalValue, ap_frame.DecimalValue); }
set { ap_min.DecimalValue = value.MIN; ap_sec.DecimalValue = value.SEC; ap_frame.DecimalValue = value.FRAC; }
}
/// <summary>
/// sets the status byte from the provided adr/qmode and control values
/// </summary>
public void SetStatus(byte adr_or_qmode, EControlQ control)
{
q_status = ComputeStatus(adr_or_qmode, control);
}
/// <summary>
/// computes a status byte from the provided adr/qmode and control values
/// </summary>
public static byte ComputeStatus(int adr_or_qmode, EControlQ control)
{
return (byte)(adr_or_qmode | (((int)control) << 4));
}
/// <summary>
/// Retrives the ADR field of the q_status member (low 4 bits)
/// </summary>
public int ADR { get { return q_status & 0xF; } }
/// <summary>
/// Retrieves the CONTROL field of the q_status member (high 4 bits)
/// </summary>
public EControlQ CONTROL { get { return (EControlQ)((q_status >> 4) & 0xF); } }
}
}

View File

@ -1,226 +1,71 @@
using System;
using System;
using System.Text;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// The TOC contains information directly describing the structure of the disc, as well as some more logically structured information derived from that.
/// Additionally, this class contains methods to synchronize between them.
/// This is a bit weird.. Perhaps the different views of the data should be seaprated.
///
/// One final caveat - I think the TOC should be independent for independent sessions.. the concept of multi-sessioning is so thoroughly ill-supported here, it will require radical renovation ever to support.
///
/// Sessions -> Tracks : These are the highest-level data structures of the disc. We should probably rename this to something like DiscStructure, and save DiscTOC for the actual TOC.
/// TOCPoint : These are the basic logical unit of data stored in the TOC. They're basically bookmarks for tracks.
/// TOCEntry : These are the basic unit of data in the rawest view of the TOC. They're stored in the lead-in Q subchannel, and there are multiple redundant copies, and they store several different types of information.
/// Represents our best guess at what a disc drive firmware will receive by reading the TOC from the lead-in track, modeled after CCD contents and mednafen/PSX needs.
/// </summary>
public class DiscTOC
{
/// <summary>
/// Right now support for anything other than 1 session is totally not working
/// The TOC specifies the first recorded track number, independently of whatever may actually be recorded
/// </summary>
public List<Session> Sessions = new List<Session>();
public int FirstRecordedTrackNumber = -1;
/// <summary>
/// List of Points described by the TOC
/// The TOC specifies the last recorded track number, independently of whatever may actually be recorded
/// </summary>
public List<TOCPoint> Points = new List<TOCPoint>();
public int LastRecordedTrackNumber = -1;
/// <summary>
/// Todo - comment about what this actually means
/// TODO - this is redundant with Sectors.Count
/// The TOC specifies the format of the session, so here it is.
/// </summary>
public int length_aba;
public SessionFormat Session1Format = SessionFormat.None;
/// <summary>
/// todo - comment about what this actually means
/// Information about a single track in the TOC
/// </summary>
public Timestamp FriendlyLength { get { return new Timestamp(length_aba); } }
/// <summary>
/// todo - comment about what this actually means
/// </summary>
public long BinarySize
public struct TOCItem
{
get { return length_aba * 2352; }
}
/// <summary>
/// seeks the point immediately before (or equal to) this LBA
/// </summary>
public TOCPoint SeekPoint(int lba)
{
int aba = lba + 150;
for(int i=0;i<Points.Count;i++)
{
TOCPoint tp = Points[i];
if (tp.ABA > aba)
return Points[i - 1];
}
return Points[Points.Count - 1];
}
/// <summary>
///
/// </summary>
public class TOCPoint
{
public int Num;
public int ABA, TrackNum, IndexNum;
public Track Track;
public int LBA
{
get { return ABA - 150; }
}
}
/// <summary>
/// Generates the Points list from the current logical TOC
/// </summary>
public void SynthesizeTOCPointsFromSessions()
{
int num = 0;
Points.Clear();
foreach (var ses in Sessions)
{
foreach (var track in ses.Tracks)
foreach (var index in track.Indexes)
{
var tp = new TOCPoint
{
Num = num++,
ABA = index.aba,
TrackNum = track.num,
IndexNum = index.num,
Track = track
};
Points.Add(tp);
}
var tpLeadout = new TOCPoint();
var lastTrack = ses.Tracks[ses.Tracks.Count - 1];
tpLeadout.Num = num++;
tpLeadout.ABA = lastTrack.Indexes[1].aba + lastTrack.length_aba;
tpLeadout.IndexNum = 0;
tpLeadout.TrackNum = 100;
tpLeadout.Track = null; //no leadout track.. now... or ever?
Points.Add(tpLeadout);
}
}
public class Session
{
public int num;
public List<Track> Tracks = new List<Track>();
//the length of the session (should be the sum of all track lengths)
public int length_aba;
public Timestamp FriendlyLength { get { return new Timestamp(length_aba); } }
}
public class Track
{
public ETrackType TrackType;
public int num;
public List<Index> Indexes = new List<Index>();
/// <summary>
/// [IEC10149] "the control field used in the information track"
/// the raw TOC entries do have a control field which is supposed to match what's found in the track.
/// Determining whether a track contains audio or data is very important.
/// A track mode can't be safely determined from reading sectors from the actual track if it's an audio track (there's no sector header with a mode byte)
/// </summary>
public EControlQ Control;
/// <summary>
/// a track logically starts at index 1.
/// so this is the length from this index 1 to the next index 1 (or the end of the disc)
/// the time before track 1 index 1 is the lead-in and isn't accounted for in any track...
/// Whether the Control indicates that this is data
/// </summary>
public int length_aba;
public Timestamp FriendlyLength { get { return new Timestamp(length_aba); } }
public bool IsData { get { return (Control & EControlQ.DATA) != 0; } }
/// <summary>
/// The location of the track (Index 1)
/// </summary>
public Timestamp LBATimestamp;
/// <summary>
/// Whether this entry exists (since the table is 101 entries long always)
/// </summary>
public bool Exists;
}
public class Index
{
public int num;
public int aba;
/// <summary>
/// This is a convenient format for storing the TOC (taken from mednafen)
/// Index 0 is empty, so that track 1 is in index 1.
/// Index 100 is the Lead-out track
/// </summary>
public TOCItem[] TOCItems = new TOCItem[101];
public int LBA
{
get { return aba - 150; }
}
//the length of the section
//HEY! This is commented out because it is a bad idea.
//The length of a section is almost useless, and if you want it, you are probably making an error.
//public int length_lba;
//public Cue.Timestamp FriendlyLength { get { return new Cue.Timestamp(length_lba); } }
}
public string GenerateCUE_OneBin(CueBinPrefs prefs)
{
if (prefs.OneBlobPerTrack) throw new InvalidOperationException("OneBinPerTrack passed to GenerateCUE_OneBin");
//this generates a single-file cue!!!!!!! dont expect it to generate bin-per-track!
StringBuilder sb = new StringBuilder();
foreach (var session in Sessions)
{
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_aba);
else sb.AppendFormat("SESSION {0:D2}\n", session.num);
}
foreach (var track in session.Tracks)
{
ETrackType trackType = track.TrackType;
//mutate track type according to our principle of canonicalization
if (trackType == ETrackType.Mode1_2048 && prefs.DumpECM)
trackType = ETrackType.Mode1_2352;
if (prefs.AnnotateCue) sb.AppendFormat(" TRACK {0:D2} {1} (length={2})\n", track.num, Cue.TrackTypeStringForTrackType(trackType), track.length_aba);
else sb.AppendFormat(" TRACK {0:D2} {1}\n", track.num, Cue.TrackTypeStringForTrackType(trackType));
foreach (var index in track.Indexes)
{
//cue+bin has an implicit 150 sector pregap which neither the cue nor the bin has any awareness of
//except for the baked-in sector addressing.
//but, if there is an extra-long pregap, we want to reflect it this way
int lba = index.aba - 150;
if (lba <= 0 && index.num == 0 && track.num == 1)
{
}
//dont emit index 0 when it is the same as index 1, it is illegal for some reason
else if (index.num == 0 && index.aba == track.Indexes[1].aba)
{
//dont emit index 0 when it is the same as index 1, it confuses some cue parsers
}
else
{
sb.AppendFormat(" INDEX {0:D2} {1}\n", index.num, new Timestamp(lba).Value);
}
}
}
}
return sb.ToString();
}
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_aba = 0;
foreach (var session in Sessions)
{
var firstTrack = session.Tracks[0];
var lastTrack = session.Tracks[session.Tracks.Count - 1];
session.length_aba = lastTrack.Indexes[0].aba + lastTrack.length_aba - firstTrack.Indexes[0].aba;
length_aba += session.length_aba;
}
}
/// <summary>
/// The timestamp of the leadout track. In other words, the end of the user area.
/// </summary>
public Timestamp LeadoutLBA { get { return TOCItems[100].LBATimestamp; } }
}
}

View File

@ -7,7 +7,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Represents a TOC entry discovered in the Q subchannel data of the lead-in track.
/// Represents a TOC entry discovered in the Q subchannel data of the lead-in track by the reader. These are stored redundantly.
/// It isn't clear whether we need anything other than the SubchannelQ data, so I abstracted this in case we need it.
/// </summary>
public class RawTOCEntry
@ -15,24 +15,165 @@ namespace BizHawk.Emulation.DiscSystem
public SubchannelQ QData;
}
/// <summary>
/// Main unit of organization for reading data from the disc. Represents one physical disc sector.
/// </summary>
public class SectorEntry
public enum DiscInterface
{
public SectorEntry(ISector sec) { Sector = sec; }
BizHawk, MednaDisc, LibMirage
}
public enum SessionFormat
{
None = -1,
Type00_CDROM_CDDA = 0x00,
Type10_CDI = 0x10,
Type20_CDXA = 0x20
}
/// <summary>
/// encapsulates a 2 digit BCD number as used various places in the CD specs
/// </summary>
public struct BCD2
{
/// <summary>
/// The raw BCD value. you can't do math on this number! but you may be asked to supply it to a game program.
/// The largest number it can logically contain is 99
/// </summary>
public byte BCDValue;
/// <summary>
/// Access the --whatsitcalled-- normal data for the sector with this
/// The derived decimal value. you can do math on this! the largest number it can logically contain is 99.
/// </summary>
public ISector Sector;
public int DecimalValue
{
get { return (BCDValue & 0xF) + ((BCDValue >> 4) & 0xF) * 10; }
set { BCDValue = IntToBCD(value); }
}
/// <summary>
/// Access the subcode data for the sector
/// makes a BCD2 from a decimal number. don't supply a number > 99 or you might not like the results
/// </summary>
public ISubcodeSector SubcodeSector;
public static BCD2 FromDecimal(int d)
{
return new BCD2 { DecimalValue = d };
}
//todo - add a PARAMETER fields to this (a long, maybe) so that the ISector can use them (so that each ISector doesnt have to be constructed also)
//also then, maybe this could be a struct
public static BCD2 FromBCD(byte b)
{
return new BCD2 { BCDValue = b };
}
public static int BCDToInt(byte n)
{
var bcd = new BCD2();
bcd.BCDValue = n;
return bcd.DecimalValue;
}
public static byte IntToBCD(int n)
{
int ones;
int tens = Math.DivRem(n, 10, out ones);
return (byte)((tens << 4) | ones);
}
public override string ToString()
{
return BCDValue.ToString("X2");
}
}
/// <summary>
/// todo - rename to MSF? It can specify durations, so maybe it should be not suggestive of timestamp
/// TODO - can we maybe use BCD2 in here
/// </summary>
public struct Timestamp
{
/// <summary>
/// Checks if the string is a legit MSF. It's strict.
/// </summary>
public static bool IsMatch(string str)
{
return new Timestamp(str).Valid;
}
/// <summary>
/// creates a timestamp from a string in the form mm:ss:ff
/// </summary>
public Timestamp(string str)
{
if (str.Length != 8) goto BOGUS;
if (str[0] < '0' || str[0] > '9') goto BOGUS;
if (str[1] < '0' || str[1] > '9') goto BOGUS;
if (str[2] != ':') goto BOGUS;
if (str[3] < '0' || str[3] > '9') goto BOGUS;
if (str[4] < '0' || str[4] > '9') goto BOGUS;
if (str[5] != ':') goto BOGUS;
if (str[6] < '0' || str[6] > '9') goto BOGUS;
if (str[7] < '0' || str[7] > '9') goto BOGUS;
MIN = (byte)((str[0] - '0') * 10 + (str[1] - '0'));
SEC = (byte)((str[3] - '0') * 10 + (str[4] - '0'));
FRAC = (byte)((str[6] - '0') * 10 + (str[7] - '0'));
Valid = true;
Negative = false;
return;
BOGUS:
MIN = SEC = FRAC = 0;
Valid = false;
Negative = false;
return;
}
/// <summary>
/// The string representation of the MSF
/// </summary>
public string Value
{
get
{
if (!Valid) return "--:--:--";
return string.Format("{0}{1:D2}:{2:D2}:{3:D2}", Negative ? '-' : '+', MIN, SEC, FRAC);
}
}
public readonly byte MIN, SEC, FRAC;
public readonly bool Valid, Negative;
/// <summary>
/// The fully multiplied out flat-address Sector number
/// </summary>
public int Sector { get { return MIN * 60 * 75 + SEC * 75 + FRAC; } }
/// <summary>
/// creates timestamp from the supplied MSF
/// </summary>
public Timestamp(int m, int s, int f)
{
MIN = (byte)m;
SEC = (byte)s;
FRAC = (byte)f;
Valid = true;
Negative = false;
}
/// <summary>
/// creates timestamp from supplied SectorNumber
/// </summary>
public Timestamp(int SectorNumber)
{
if (SectorNumber < 0)
{
SectorNumber = -SectorNumber;
Negative = true;
}
else Negative = false;
MIN = (byte)(SectorNumber / (60 * 75));
SEC = (byte)((SectorNumber / 75) % 60);
FRAC = (byte)(SectorNumber % 75);
Valid = true;
}
public override string ToString()
{
return Value;
}
}
}

View File

@ -1,16 +1,43 @@
using System;
namespace BizHawk.Emulation.DiscSystem
{
public static class DiscUtils
{
static byte IntToBCD(int n)
{
int ones;
int tens = Math.DivRem(n, 10, out ones);
return (byte)((tens << 4) | ones);
}
/// <summary>
/// converts the given byte to a BCD value
/// converts the given int to a BCD value
/// </summary>
public static byte BCD_Byte(this byte val)
public static int BCD_Byte(this int val)
{
byte ret = (byte)(val % 10);
ret += (byte)(16 * (val / 10));
return ret;
}
/// <summary>
/// converts an LBA to AMSF absolute minute:second:frame format.
/// </summary>
public static void Convert_LBA_To_AMSF(int lba, out byte m, out byte s, out byte f)
{
lba += 150; //dont do this anymore
m = (byte)(lba / 75 / 60);
s = (byte)((lba - (m * 75 * 60)) / 75);
f = (byte)(lba - (m * 75 * 60) - (s * 75));
}
// converts MSF to LBA offset
public static int Convert_AMSF_To_LBA(byte m, byte s, byte f)
{
return f + (s * 75) + (m * 75 * 60) - 150;
}
}
}

View File

@ -27,6 +27,7 @@
//it turns out mame's cdrom.c uses pretty much this exact thing, naming the tables mul2tab->ecclow and div3tab->ecchigh
//Corlett's ECM uses our same fundamental approach as well.
//I can't figure out what winUAE is doing.
//Martin writes these algorithms in his own inimitable format. We should try transcribing them here for testing.
using BizHawk.Common;
@ -225,7 +226,8 @@ namespace BizHawk.Emulation.DiscSystem
(sector[sector_offset + 12 + 0] << 0)
| (sector[sector_offset + 12 + 1] << 8)
| (sector[sector_offset + 12 + 2] << 16)
| (sector[sector_offset + 12 + 3] << 24));
);
//| (sector[sector_offset + 12 + 3] << 24));
}
/// <summary>
@ -236,7 +238,7 @@ namespace BizHawk.Emulation.DiscSystem
sector[sector_offset + 12 + 0] = (byte)((address >> 0) & 0xFF);
sector[sector_offset + 12 + 1] = (byte)((address >> 8) & 0xFF);
sector[sector_offset + 12 + 2] = (byte)((address >> 16) & 0xFF);
sector[sector_offset + 12 + 3] = (byte)((address >> 24) & 0xFF);
//sector[sector_offset + 12 + 3] = (byte)((address >> 24) & 0xFF);
}

View File

@ -0,0 +1,43 @@
namespace BizHawk.Emulation.DiscSystem
{
//this has been checked against mednafen's and seems to match
//there are a few dozen different ways to do CRC16-CCITT
//this table is backwards or something. at any rate its tailored to the needs of the Q subchannel
internal static class CRC16_CCITT
{
private static readonly ushort[] table = new ushort[256];
static CRC16_CCITT()
{
for (ushort i = 0; i < 256; ++i)
{
ushort value = 0;
ushort temp = (ushort)(i << 8);
for (byte j = 0; j < 8; ++j)
{
if (((value ^ temp) & 0x8000) != 0)
value = (ushort)((value << 1) ^ 0x1021);
else
value <<= 1;
temp <<= 1;
}
table[i] = value;
}
}
public static ushort Calculate(byte[] data, int offset, int length)
{
ushort Result = 0;
for (int i = 0; i < length; i++)
{
byte b = data[offset + i];
int index = (b ^ ((Result >> 8) & 0xFF));
Result = (ushort)((Result << 8) ^ table[index]);
}
return Result;
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
//TODO - generate correct Q subchannel CRC
namespace BizHawk.Emulation.DiscSystem
{
class ApplySBIJob
{
/// <summary>
/// applies an SBI file to the disc
/// </summary>
public void Run(Disc disc, SBI.SubQPatchData sbi, bool asMednafen)
{
//TODO - could implement as a blob, to avoid allocating so many byte buffers
//save this, it's small, and we'll want it for disc processing a/b checks
disc.Memos["sbi"] = sbi;
DiscSectorReader dsr = new DiscSectorReader(disc);
int n = sbi.ABAs.Count;
int b = 0;
for (int i = 0; i < n; i++)
{
int lba = sbi.ABAs[i] - 150;
//create a synthesizer which can return the patched data
var ss_patchq = new SS_PatchQ() { Original = disc.Sectors[lba + 150] };
byte[] subQbuf = ss_patchq.Buffer_SubQ;
//read the old subcode
dsr.ReadLBA_SubQ(lba, subQbuf, 0);
//insert patch
disc.Sectors[lba + 150] = ss_patchq;
//apply SBI patch
for (int j = 0; j < 12; j++)
{
short patch = sbi.subq[b++];
if (patch == -1) continue;
else subQbuf[j] = (byte)patch;
}
//Apply mednafen hacks
//The reasoning here is that we know we expect these sectors to have a wrong checksum. therefore, generate a checksum, and make it wrong
//However, this seems senseless to me. The whole point of the SBI data is that it stores the patches needed to generate an acceptable subQ, right?
if (asMednafen)
{
SynthUtils.SubQ_SynthChecksum(subQbuf, 0);
subQbuf[10] ^= 0xFF;
subQbuf[11] ^= 0xFF;
}
}
}
}
}

View File

@ -1,60 +1,37 @@
using System;
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using BizHawk.Common.IOExtensions;
namespace BizHawk.Emulation.DiscSystem
namespace BizHawk.Emulation.DiscSystem.SBI
{
public class SBI_Format
/// <summary>
/// Loads SBI files into an internal representation.
/// </summary>
class LoadSBIJob : DiscJob
{
public class SBIParseException : Exception
{
public SBIParseException(string message) : base(message) { }
}
public class SBIFile
{
/// <summary>
/// a list of patched ABAs
/// </summary>
public List<int> ABAs = new List<int>();
/// <summary>
/// 12 values (Q subchannel data) for every patched ABA; -1 means unpatched
/// </summary>
public short[] subq;
}
/// <summary>
/// The file to be loaded
/// </summary>
public string IN_Path;
/// <summary>
/// Does a cursory check to see if the file looks like an SBI
/// The resulting interpreted data
/// </summary>
public static bool QuickCheckISSBI(string path)
{
using (var fs = File.OpenRead(path))
{
BinaryReader br = new BinaryReader(fs);
string sig = br.ReadStringFixedAscii(4);
if (sig != "SBI\0")
return false;
}
return true;
}
public SubQPatchData OUT_Data;
/// <summary>
/// Loads an SBI file from the specified path
/// </summary>
public SBIFile LoadSBIPath(string path)
public void Run()
{
using(var fs = File.OpenRead(path))
using (var fs = File.OpenRead(IN_Path))
{
BinaryReader br = new BinaryReader(fs);
string sig = br.ReadStringFixedAscii(4);
if (sig != "SBI\0")
throw new SBIParseException("Missing magic number");
SBIFile ret = new SBIFile();
SubQPatchData ret = new SubQPatchData();
List<short> bytes = new List<short>();
//read records until done
@ -64,7 +41,7 @@ namespace BizHawk.Emulation.DiscSystem
if (fs.Position == fs.Length)
break;
if (fs.Position+4 > fs.Length) throw new SBIParseException("Broken record");
if (fs.Position + 4 > fs.Length) throw new SBIParseException("Broken record");
var m = BCD2.BCDToInt(br.ReadByte());
var s = BCD2.BCDToInt(br.ReadByte());
var f = BCD2.BCDToInt(br.ReadByte());
@ -94,10 +71,10 @@ namespace BizHawk.Emulation.DiscSystem
throw new SBIParseException("Broken record");
}
}
ret.subq = bytes.ToArray();
return ret;
ret.subq = bytes.ToArray();
OUT_Data = ret;
}
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
//TODO - generate correct Q subchannel CRC
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Synthesizes RawTCOEntry A0 A1 A2 from the provided information.
/// This might be reused by formats other than CUE later, so it isn't directly associated with that
/// </summary>
class Synthesize_A0A1A2_Job
{
/// <summary>
/// "First Recorded Track Number" value for TOC (usually 1)
/// </summary>
public int IN_FirstRecordedTrackNumber;
/// <summary>
/// "Last Recorded Track Number" value for TOC
/// </summary>
public int IN_LastRecordedTrackNumber;
/// <summary>
/// The absolute timestamp of the lead-out track
/// </summary>
public Timestamp IN_LeadoutTimestamp;
/// <summary>
/// The session format for this TOC
/// </summary>
public SessionFormat IN_Session1Format;
/// <summary>
/// Appends the new entries to the provided list
/// </summary>
public void Run(List<RawTOCEntry> entries)
{
//NOTE: entries are inserted at the beginning due to observations of CCD indicating they might need to be that way
//Since I'm being asked to synthesize them here, I guess I can put them in whatever order I want, can't I?
SubchannelQ sq = new SubchannelQ();
//ADR (q-Mode) is necessarily 0x01 for a RawTOCEntry
const int kADR = 1;
const int kUnknownControl = 0;
sq.SetStatus(kADR, (EControlQ)kUnknownControl);
//first recorded track number:
sq.q_index.BCDValue = 0xA0;
sq.ap_min.DecimalValue = IN_FirstRecordedTrackNumber;
switch(IN_Session1Format)
{
//TODO these probably shouldn't be decimal values
case SessionFormat.Type00_CDROM_CDDA: sq.ap_sec.DecimalValue = 0x00; break;
case SessionFormat.Type10_CDI: sq.ap_sec.DecimalValue = 0x10; break;
case SessionFormat.Type20_CDXA: sq.ap_sec.DecimalValue = 0x20; break;
default: throw new InvalidOperationException("Invalid Session1Format");
}
sq.ap_frame.DecimalValue = 0;
entries.Insert(0, new RawTOCEntry { QData = sq });
//last recorded track number:
sq.q_index.BCDValue = 0xA1;
sq.ap_min.DecimalValue = IN_LastRecordedTrackNumber;
sq.ap_sec.DecimalValue = 0;
sq.ap_frame.DecimalValue = 0;
entries.Insert(1, new RawTOCEntry { QData = sq });
//leadout:
sq.q_index.BCDValue = 0xA2;
sq.AP_Timestamp = IN_LeadoutTimestamp;
entries.Insert(2, new RawTOCEntry { QData = sq });
}
}
}

View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
class Synthesize_DiscStructure_From_DiscTOC_Job
{
public Disc IN_Disc;
public DiscTOC TOCRaw;
public DiscStructure Result;
public void Run()
{
var dsr = new DiscSectorReader(IN_Disc);
dsr.Policy.DeterministicClearBuffer = false;
Result = new DiscStructure();
var session = new DiscStructure.Session();
Result.Sessions.Add(null); //placeholder session for reindexing
Result.Sessions.Add(session);
session.Number = 1;
if (TOCRaw.FirstRecordedTrackNumber != 1)
throw new InvalidOperationException("Unsupported: FirstRecordedTrackNumber != 1");
//add a lead-in track
session.Tracks.Add(new DiscStructure.Track()
{
Number = 0,
Control = EControlQ.None, //TODO - not accurate (take from track 1?)
LBA = -150 //TODO - not accurate
});
int ntracks = TOCRaw.LastRecordedTrackNumber - TOCRaw.FirstRecordedTrackNumber + 1;
for (int i = 0; i < ntracks; i++)
{
var item = TOCRaw.TOCItems[i + 1];
var track = new DiscStructure.Track()
{
Number = i + 1,
Control = item.Control,
LBA = item.LBATimestamp.Sector
};
session.Tracks.Add(track);
if (!item.IsData)
track.Mode = 0;
else
{
//determine the mode by a hardcoded heuristic: check mode of first sector
track.Mode = dsr.ReadLBA_Mode(track.LBA);
}
//determine track length according to... how? It isn't clear.
//Let's not do this until it's needed.
//if (i == ntracks - 1)
// track.Length = TOCRaw.LeadoutLBA.Sector - track.LBA;
//else track.Length = (TOCRaw.TOCItems[i + 2].LBATimestamp.Sector - track.LBA);
}
//add lead-out track
session.Tracks.Add(new DiscStructure.Track()
{
Number = 0xA0, //right?
Control = EControlQ.None, //TODO - not accurate (take from track 1?)
LBA = TOCRaw.LeadoutLBA.Sector
});
//link track list
for (int i = 0; i < session.Tracks.Count - 1; i++)
{
session.Tracks[i].NextTrack = session.Tracks[i + 1];
}
}
}
}

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Synthesizes the TOC from a set of raw entries.
/// When a disc drive firmware reads the lead-in area, it builds this TOC from finding q-mode 1 sectors in the Q subchannel of the lead-in area.
/// Question: I guess it must ignore q-mode != 1? what else would it do with it?
/// </summary>
class Synthesize_DiscTOC_From_RawTOCEntries_Job
{
public IEnumerable<RawTOCEntry> Entries;
public List<string> Log = new List<string>();
public DiscTOC Result;
public void Run()
{
var job = this;
DiscTOC ret = new DiscTOC();
//this is a dummy, for convenience in array indexing, so that track 1 is at array index 1
ret.TOCItems[0].LBATimestamp = new Timestamp(0); //arguably could be -150, but let's not just yet
ret.TOCItems[0].Control = 0;
ret.TOCItems[0].Exists = false;
//just in case this doesnt get set...
ret.FirstRecordedTrackNumber = 0;
ret.LastRecordedTrackNumber = 0;
int maxFoundTrack = 0;
foreach (var te in job.Entries)
{
var q = te.QData;
var point = q.q_index.DecimalValue;
//see ECMD-394 page 5-14 for info about point = 0xA0, 0xA1, 0xA2
if (point == 0x00)
job.Log.Add("unexpected POINT=00 in lead-in Q-channel");
else if (point == 255)
throw new InvalidOperationException("point == 255");
else if (point <= 99)
{
maxFoundTrack = Math.Max(maxFoundTrack, point);
ret.TOCItems[point].LBATimestamp = new Timestamp(q.AP_Timestamp.Sector - 150); //RawTOCEntries contained an absolute time
ret.TOCItems[point].Control = q.CONTROL;
ret.TOCItems[point].Exists = true;
}
else if (point == 100) //0xA0 bcd
{
ret.FirstRecordedTrackNumber = q.ap_min.DecimalValue;
if (q.ap_frame.DecimalValue != 0) job.Log.Add("PFRAME should be 0 for POINT=0xA0");
if (q.ap_sec.DecimalValue == 0x00) ret.Session1Format = SessionFormat.Type00_CDROM_CDDA;
else if (q.ap_sec.DecimalValue == 0x10) ret.Session1Format = SessionFormat.Type10_CDI;
else if (q.ap_sec.DecimalValue == 0x20) ret.Session1Format = SessionFormat.Type20_CDXA;
else job.Log.Add("Unrecognized session format: PSEC should be one of {0x00,0x10,0x20} for POINT=0xA0");
}
else if (point == 101) //0xA1 bcd
{
ret.LastRecordedTrackNumber = q.ap_min.DecimalValue;
if (q.ap_sec.DecimalValue != 0) job.Log.Add("PSEC should be 0 for POINT=0xA1");
if (q.ap_frame.DecimalValue != 0) job.Log.Add("PFRAME should be 0 for POINT=0xA1");
}
else if (point == 102) //0xA2 bcd
{
ret.TOCItems[100].LBATimestamp = new Timestamp(q.AP_Timestamp.Sector - 150); //RawTOCEntries contained an absolute time
ret.TOCItems[100].Control = 0; //not clear what this should be
ret.TOCItems[100].Exists = true;
}
}
//this is speculative:
//well, nothing to be done here..
if (ret.FirstRecordedTrackNumber == -1) { }
if (ret.LastRecordedTrackNumber == -1) { ret.LastRecordedTrackNumber = maxFoundTrack; }
if (ret.Session1Format == SessionFormat.None) ret.Session1Format = SessionFormat.Type00_CDROM_CDDA;
//if (!ret.LeadoutTimestamp.Valid) {
// //we're DOOMED. we cant know the length of the last track without this....
//}
job.Result = ret;
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
//TODO - generate correct Q subchannel CRC
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// generates lead-out sectors according to very crude approximations
/// TODO - this isnt being used right now
/// </summary>
class Synthesize_LeadoutJob
{
public int Length;
public Disc Disc;
public void Run()
{
//TODO: encode_mode2_form2_sector
var leadoutTs = Disc.TOC.LeadoutLBA;
var lastTrackTOCItem = Disc.TOC.TOCItems[Disc.TOC.LastRecordedTrackNumber]; //NOTE: in case LastRecordedTrackNumber is al ie, this will malfunction
//leadout flags.. let's set them the same as the last track.
//THIS IS NOT EXACTLY THE SAME WAY MEDNAFEN DOES IT
EControlQ leadoutFlags = lastTrackTOCItem.Control;
//TODO - needs to be encoded as a certain mode (mode 2 form 2 for psx... i guess...)
for (int i = 0; i < Length; i++)
{
//var se = new SectorEntry(sz);
//Disc.Sectors.Add(se);
SubchannelQ sq = new SubchannelQ();
int track_relative_msf = i;
sq.min = BCD2.FromDecimal(new Timestamp(track_relative_msf).MIN);
sq.sec = BCD2.FromDecimal(new Timestamp(track_relative_msf).SEC);
sq.frame = BCD2.FromDecimal(new Timestamp(track_relative_msf).FRAC);
int absolute_msf = i + leadoutTs.Sector;
sq.ap_min = BCD2.FromDecimal(new Timestamp(absolute_msf + 150).MIN);
sq.ap_sec = BCD2.FromDecimal(new Timestamp(absolute_msf + 150).SEC);
sq.ap_frame = BCD2.FromDecimal(new Timestamp(absolute_msf + 150).FRAC);
sq.q_tno.DecimalValue = 0xAA; //special value for leadout
sq.q_index.DecimalValue = 1;
byte ADR = 1;
sq.SetStatus(ADR, leadoutFlags);
//TODO - actually stash the subQ
}
}
}
}

View File

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Indicates which part of a sector are needing to be synthesized.
/// Sector synthesis may create too much data, but this is a hint as to what's needed
/// TODO - add a flag indicating whether clearing has happened
/// TODO - add output to the job indicating whether interleaving has happened. let the sector reader be responsible
/// </summary>
[Flags] enum ESectorSynthPart
{
/// <summary>
/// The data sector header is required. There's no header for audio tracks/sectors.
/// </summary>
Header16 = 1,
/// <summary>
/// The main 2048 user data bytes are required
/// </summary>
User2048 = 2,
/// <summary>
/// The 276 bytes of error correction are required
/// </summary>
ECC276 = 4,
/// <summary>
/// The 12 bytes preceding the ECC section are required (usually EDC and zero but also userdata sometimes)
/// </summary>
EDC12 = 8,
/// <summary>
/// The entire possible 276+12=288 bytes of ECM data is required (ECC276|EDC12)
/// </summary>
ECM288Complete = (ECC276 | EDC12),
/// <summary>
/// An alias for ECM288Complete
/// </summary>
ECMAny = ECM288Complete,
/// <summary>
/// A mode2 userdata section is required: the main 2048 user bytes AND the ECC and EDC areas
/// </summary>
User2336 = (User2048 | ECM288Complete),
/// <summary>
/// The complete sector userdata (2352 bytes) is required
/// </summary>
UserComplete = 15,
/// <summary>
/// An alias for UserComplete
/// </summary>
UserAny = UserComplete,
/// <summary>
/// An alias for UserComplete
/// </summary>
User2352 = UserComplete,
/// <summary>
/// SubP is required
/// </summary>
SubchannelP = 16,
/// <summary>
/// SubQ is required
/// </summary>
SubchannelQ = 32,
/// <summary>
/// Subchannels R-W (all except for P and Q)
/// </summary>
Subchannel_RSTUVW = (64|128|256|512|1024|2048),
/// <summary>
/// Complete subcode is required
/// </summary>
SubcodeComplete = (SubchannelP | SubchannelQ | Subchannel_RSTUVW),
/// <summary>
/// Any of the subcode might be required (just another way of writing SubcodeComplete)
/// </summary>
SubcodeAny = SubcodeComplete,
/// <summary>
/// The subcode should be deinterleaved
/// </summary>
SubcodeDeinterleave = 4096,
/// <summary>
/// The 100% complete sector is required including 2352 bytes of userdata and 96 bytes of subcode
/// </summary>
Complete2448 = SubcodeComplete | User2352,
}
interface ISectorSynthJob2448
{
void Synth(SectorSynthJob job);
}
/// <summary>
/// Not a proper job? maybe with additional flags, it could be
/// </summary>
class SectorSynthJob
{
public int LBA;
public ESectorSynthPart Parts;
public byte[] DestBuffer2448;
public int DestOffset;
public SectorSynthParams Params;
public Disc Disc;
}
/// <summary>
/// Generic parameters for sector synthesis.
/// To cut down on resource utilization, these can be stored in a disc and are tightly coupled to
/// the SectorSynths that have been setup for it
/// </summary>
struct SectorSynthParams
{
public long[] BlobOffsets;
public MednaDisc MednaDisc;
}
class SS_PatchQ : ISectorSynthJob2448
{
public ISectorSynthJob2448 Original;
public byte[] Buffer_SubQ = new byte[12];
public void Synth(SectorSynthJob job)
{
Original.Synth(job);
if ((job.Parts & ESectorSynthPart.SubchannelQ) == 0)
return;
//apply patched subQ
for (int i = 0; i < 12; i++)
job.DestBuffer2448[2352 + 12 + i] = Buffer_SubQ[i];
}
}
}

View File

@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace BizHawk.Emulation.DiscSystem
{
static class SynthUtils
{
/// <summary>
/// Calculates the checksum of the provided Q subchannel buffer and emplaces it
/// </summary>
/// <param name="buffer">12 byte Q subchannel buffer: input and output buffer for operation</param>
/// <param name="offset">location within buffer of Q subchannel</param>
public static ushort SubQ_SynthChecksum(byte[] buf12, int offset)
{
ushort crc16 = CRC16_CCITT.Calculate(buf12, offset, 10);
//CRC is stored inverted and big endian
buf12[offset + 10] = (byte)(~(crc16 >> 8));
buf12[offset + 11] = (byte)(~(crc16));
return crc16;
}
/// <summary>
/// Caclulates the checksum of the provided Q subchannel buffer
/// </summary>
public static ushort SubQ_CalcChecksum(byte[] buf12, int offset)
{
return CRC16_CCITT.Calculate(buf12, offset, 10);
}
/// <summary>
/// Serializes the provided SubchannelQ structure into a buffer
/// Returns the crc, calculated or otherwise.
/// </summary>
public static ushort SubQ_Serialize(byte[] buf12, int offset, ref SubchannelQ sq)
{
buf12[offset + 0] = sq.q_status;
buf12[offset + 1] = sq.q_tno.BCDValue;
buf12[offset + 2] = sq.q_index.BCDValue;
buf12[offset + 3] = sq.min.BCDValue;
buf12[offset + 4] = sq.sec.BCDValue;
buf12[offset + 5] = sq.frame.BCDValue;
buf12[offset + 6] = sq.zero;
buf12[offset + 7] = sq.ap_min.BCDValue;
buf12[offset + 8] = sq.ap_sec.BCDValue;
buf12[offset + 9] = sq.ap_frame.BCDValue;
return SubQ_SynthChecksum(buf12, offset);
}
/// <summary>
/// Synthesizes the typical subP data into the provided buffer depending on the indicated pause flag
/// </summary>
public static void SubP(byte[] buffer12, int offset, bool pause)
{
byte val = (byte)(pause ? 0xFF : 0x00);
for (int i = 0; i < 12; i++)
buffer12[offset + i] = val;
}
/// <summary>
/// Synthesizes a data sector header
/// </summary>
public static void SectorHeader(byte[] buffer16, int offset, int LBA, byte mode)
{
buffer16[offset + 0] = 0x00;
for (int i = 1; i < 11; i++) buffer16[offset + i] = 0xFF;
buffer16[offset + 11] = 0x00;
Timestamp ts = new Timestamp(LBA + 150);
buffer16[offset + 12] = BCD2.IntToBCD(ts.MIN);
buffer16[offset + 13] = BCD2.IntToBCD(ts.SEC);
buffer16[offset + 14] = BCD2.IntToBCD(ts.FRAC);
buffer16[offset + 15] = mode;
}
/// <summary>
/// Synthesizes the EDC checksum for a Mode 2 Form 1 data sector (and puts it in place)
/// </summary>
public static void EDC_Mode2_Form1(byte[] buf2352, int offset)
{
uint edc = ECM.EDC_Calc(buf2352, offset + 16, 2048 + 8);
ECM.PokeUint(buf2352, offset + 2072, edc);
}
/// <summary>
/// Synthesizes the EDC checksum for a Mode 2 Form 2 data sector (and puts it in place)
/// </summary>
public static void EDC_Mode2_Form2(byte[] buf2352, int offset)
{
uint edc = ECM.EDC_Calc(buf2352, offset + 16, 2324 + 8);
ECM.PokeUint(buf2352, offset + 2348, edc);
}
/// <summary>
/// Synthesizes the complete ECM data (EDC + ECC) for a Mode 1 data sector (and puts it in place)
/// Make sure everything else in the sector userdata is done before calling this
/// </summary>
public static void ECM_Mode1(byte[] buf2352, int offset, int LBA)
{
//EDC
uint edc = ECM.EDC_Calc(buf2352, offset, 2064);
ECM.PokeUint(buf2352, offset + 2064, edc);
//reserved, zero
for (int i = 0; i < 8; i++) buf2352[offset + 2068 + i] = 0;
//ECC
ECM.ECC_Populate(buf2352, offset, buf2352, offset, false);
}
/// <summary>
/// Converts the useful (but unrealistic) deinterleaved subchannel data into the useless (but realistic) interleaved format.
/// in_buf and out_buf should not overlap
/// </summary>
public static void InterleaveSubcode(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index)
{
for (int d = 0; d < 12; d++)
{
for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++)
{
int rawb = 0;
for (int ch = 0; ch < 8; ch++)
{
rawb |= ((in_buf[ch * 12 + d + in_buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch);
}
out_buf[(d << 3) + bitpoodle + out_buf_index] = (byte)rawb;
}
}
}
/// <summary>
/// Converts the useless (but realistic) interleaved subchannel data into a useful (but unrealistic) deinterleaved format.
/// in_buf and out_buf should not overlap
/// </summary>
public static void DeinterleaveSubcode(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index)
{
for (int i = 0; i < 96; i++)
out_buf[i] = 0;
for (int ch = 0; ch < 8; ch++)
{
for (int i = 0; i < 96; i++)
{
out_buf[(ch * 12) + (i >> 3) + out_buf_index] |= (byte)(((in_buf[i + in_buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7)));
}
}
}
/// <summary>
/// Converts the useful (but unrealistic) deinterleaved data into the useless (but realistic) interleaved subchannel format.
/// </summary>
public unsafe static void InterleaveSubcodeInplace(byte[] buf, int buf_index)
{
byte* out_buf = stackalloc byte[96];
for (int i = 0; i < 96; i++)
out_buf[i] = 0;
for (int d = 0; d < 12; d++)
{
for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++)
{
int rawb = 0;
for (int ch = 0; ch < 8; ch++)
{
rawb |= ((buf[ch * 12 + d + buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch);
}
out_buf[(d << 3) + bitpoodle] = (byte)rawb;
}
}
for (int i = 0; i < 96; i++)
buf[i + buf_index] = out_buf[i];
}
/// <summary>
/// Converts the useless (but realistic) interleaved subchannel data into a useful (but unrealistic) deinterleaved format.
/// </summary>
public unsafe static void DeinterleaveSubcodeInplace(byte[] buf, int buf_index)
{
byte* out_buf = stackalloc byte[96];
for (int i = 0; i < 96; i++)
out_buf[i] = 0;
for (int ch = 0; ch < 8; ch++)
{
for (int i = 0; i < 96; i++)
{
out_buf[(ch * 12) + (i >> 3)] |= (byte)(((buf[i + buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7)));
}
}
for (int i = 0; i < 96; i++)
buf[i + buf_index] = out_buf[i];
}
}
}

View File

@ -1,210 +0,0 @@
using System;
using System.IO;
namespace BizHawk.Emulation.DiscSystem
{
public interface ISector
{
/// <summary>
/// reads the entire sector, raw
/// </summary>
int Read_2352(byte[] buffer, int offset);
/// <summary>
/// reads 2048 bytes of userdata.. precisely what this means isnt always 100% certain (for instance mode2 form 0 has 2336 bytes of userdata instead of 2048)..
/// ..but its certain enough for this to be useful
/// </summary>
int Read_2048(byte[] buffer, int offset);
}
/// <summary>
/// this ISector is dumb and only knows how to drag chunks off a source blob
/// </summary>
public class Sector_RawBlob : ISector
{
public IBlob Blob;
public long Offset;
public int Read_2352(byte[] buffer, int offset)
{
return Blob.Read(Offset, buffer, offset, 2352);
}
public int Read_2048(byte[] buffer, int offset)
{
//this depends on CD-XA mode and such. so we need to read the mode bytes
//HEY!!!!!! SHOULD THIS BE DONE BASED ON THE CLAIMED TRACK TYPE, OR ON WHATS IN THE SECTOR?
//this is kind of a function of the CD reader.. it's not clear how this function should work.
//YIKES!!!!!!!!!!!!!!
//well, we need to scrutinize it for CCD files anyway, so...
//this sucks.
Blob.Read(Offset + 16, buffer, 0, 1);
byte mode = buffer[0];
if(mode == 1)
return Blob.Read(Offset + 16, buffer, offset, 2048);
else
return Blob.Read(Offset + 24, buffer, offset, 2048);
}
}
/// <summary>
/// this ISector always returns zeroes
/// </summary>
class Sector_Zero : ISector
{
public int Read_2352(byte[] buffer, int offset)
{
Array.Clear(buffer, 0, 2352);
return 2352;
}
public int Read_2048(byte[] buffer, int offset)
{
Array.Clear(buffer, 0, 2048);
return 2048;
}
}
abstract class Sector_Mode1_or_Mode2_2352 : ISector
{
public ISector BaseSector;
public abstract int Read_2352(byte[] buffer, int offset);
public abstract int Read_2048(byte[] buffer, int offset);
}
/// <summary>
/// This ISector is a raw MODE1 sector
/// </summary>
class Sector_Mode1_2352 : Sector_Mode1_or_Mode2_2352
{
public override int Read_2352(byte[] buffer, int offset)
{
return BaseSector.Read_2352(buffer, offset);
}
public override int Read_2048(byte[] buffer, int offset)
{
//to get 2048 bytes out of this sector type, start 16 bytes in
int ret = BaseSector.Read_2352(TempSector, 0);
Buffer.BlockCopy(TempSector, 16, buffer, offset, 2048);
System.Diagnostics.Debug.Assert(buffer != TempSector);
return 2048;
}
[ThreadStatic]
static byte[] TempSector = new byte[2352];
}
/// <summary>
/// this ISector is a raw MODE2 sector. could be form 0,1,2... who can say? supposedly:
/// To tell the different Mode 2s apart you have to examine bytes 16-23 of the sector (the first 8 bytes of Mode Data).
/// If bytes 16-19 are not the same as 20-23, then it is Mode 2. If they are equal and bit 5 is on (0x20), then it is Mode 2 Form 2. Otherwise it is Mode 2 Form 1.
/// ...but we're not using this information in any way
/// </summary>
class Sector_Mode2_2352 : Sector_Mode1_or_Mode2_2352
{
public override int Read_2352(byte[] buffer, int offset)
{
return BaseSector.Read_2352(buffer, offset);
}
public override int Read_2048(byte[] buffer, int offset)
{
//to get 2048 bytes out of this sector type, start 24 bytes in
int ret = BaseSector.Read_2352(TempSector, 0);
Buffer.BlockCopy(TempSector, 24, buffer, offset, 2048);
System.Diagnostics.Debug.Assert(buffer != TempSector);
return 2048;
}
[ThreadStatic]
static byte[] TempSector = new byte[2352];
}
//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;
}
/// <summary>
/// this ISector is a MODE1 sector that is generating itself from an underlying MODE1/2048 userdata piece
/// </summary>
class Sector_Mode1_2048 : ISector
{
public Sector_Mode1_2048(int ABA)
{
byte aba_min = (byte)(ABA / 60 / 75);
byte aba_sec = (byte)((ABA / 75) % 60);
byte aba_frac = (byte)(ABA % 75);
bcd_aba_min = aba_min.BCD_Byte();
bcd_aba_sec = aba_sec.BCD_Byte();
bcd_aba_frac = aba_frac.BCD_Byte();
}
byte bcd_aba_min, bcd_aba_sec, bcd_aba_frac;
public ECMCacheBlob Blob;
public long Offset;
byte[] extra_data;
bool has_extra_data;
public int Read_2048(byte[] buffer, int offset)
{
//this is easy. we only have 2048 bytes, and 2048 bytes were requested
return Blob.BaseBlob.Read(Offset, buffer, offset, 2048);
}
public int Read_2352(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_aba_min;
buffer[offset + 13] = bcd_aba_sec;
buffer[offset + 14] = bcd_aba_frac;
//mode 1
buffer[offset + 15] = 1;
//calculate EDC and poke into the sector
uint edc = ECM.EDC_Calc(buffer, offset, 2064);
ECM.PokeUint(buffer, 2064, edc);
//intermediate
for (int i = 0; i < 8; i++) buffer[offset + 2068 + i] = 0;
//ECC
ECM.ECC_Populate(buffer, offset, buffer, offset, false);
//VALIDATION - check our homemade algorithms against code derived from ECM
////EDC
//GPL_ECM.edc_validateblock(buffer, 2064, buffer, offset + 2064);
////ECC
//GPL_ECM.ecc_validate(buffer, offset, false);
//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;
}
}
}

View File

@ -1,330 +0,0 @@
using System;
//a decent little subcode reference
//http://www.jbum.com/cdg_revealed.html
namespace BizHawk.Emulation.DiscSystem
{
public interface ISubcodeSector
{
/// <summary>
/// reads 96 bytes of subcode data (deinterleaved) for this sector into the supplied buffer
/// </summary>
void ReadSubcodeDeinterleaved(byte[] buffer, int offset);
/// <summary>
/// Reads just one of the channels. p=0, q=1, etc.
/// </summary>
void ReadSubcodeChannel(int number, byte[] buffer, int offset);
}
public static class SubcodeUtils
{
public static void Interleave(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index)
{
for (int d = 0; d < 12; d++)
{
for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++)
{
int rawb = 0;
for (int ch = 0; ch < 8; ch++)
{
rawb |= ((in_buf[ch * 12 + d + in_buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch);
}
out_buf[(d << 3) + bitpoodle + out_buf_index] = (byte)rawb;
}
}
}
public static void Deinterleave(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index)
{
for (int i = 0; i < 96; i++)
out_buf[i] = 0;
for (int ch = 0; ch < 8; ch++)
{
for (int i = 0; i < 96; i++)
{
out_buf[(ch * 12) + (i >> 3) + out_buf_index] |= (byte)(((in_buf[i + in_buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7)));
}
}
}
}
/// <summary>
/// Reads subcode from an internally-managed buffer
/// </summary>
class BufferedSubcodeSector : ISubcodeSector
{
public void Synthesize_SubchannelP(bool pause)
{
byte val = pause ? (byte)0xFF : (byte)0x00;
for (int i = 0; i < 12; i++)
SubcodeDeinterleaved[i] = val;
}
/// <summary>
/// Fills this subcode buffer with subchannel Q data. calculates the required CRC, as well.
/// Returns the crc, calculated or otherwise.
/// </summary>
public ushort Synthesize_SubchannelQ(ref SubchannelQ sq, bool calculateCRC)
{
int offset = 12; //Q subchannel begins after P, 12 bytes in
SubcodeDeinterleaved[offset + 0] = sq.q_status;
SubcodeDeinterleaved[offset + 1] = sq.q_tno;
SubcodeDeinterleaved[offset + 2] = sq.q_index;
SubcodeDeinterleaved[offset + 3] = sq.min.BCDValue;
SubcodeDeinterleaved[offset + 4] = sq.sec.BCDValue;
SubcodeDeinterleaved[offset + 5] = sq.frame.BCDValue;
SubcodeDeinterleaved[offset + 6] = sq.zero;
SubcodeDeinterleaved[offset + 7] = sq.ap_min.BCDValue;
SubcodeDeinterleaved[offset + 8] = sq.ap_sec.BCDValue;
SubcodeDeinterleaved[offset + 9] = sq.ap_frame.BCDValue;
ushort crc16;
if (calculateCRC)
crc16 = CRC16_CCITT.Calculate(SubcodeDeinterleaved, offset, 10);
else crc16 = sq.q_crc;
//CRC is stored inverted and big endian
SubcodeDeinterleaved[offset + 10] = (byte)(~(crc16 >> 8));
SubcodeDeinterleaved[offset + 11] = (byte)(~(crc16));
return crc16;
}
public void ReadSubcodeDeinterleaved(byte[] buffer, int offset)
{
Buffer.BlockCopy(SubcodeDeinterleaved, 0, buffer, offset, 96);
}
public void ReadSubcodeChannel(int number, byte[] buffer, int offset)
{
Buffer.BlockCopy(SubcodeDeinterleaved, number * 12, buffer, offset, 12);
}
public BufferedSubcodeSector()
{
SubcodeDeinterleaved = new byte[96];
}
public static BufferedSubcodeSector CloneFromBytesDeinterleaved(byte[] buffer)
{
var ret = new BufferedSubcodeSector();
Buffer.BlockCopy(buffer, 0, ret.SubcodeDeinterleaved, 0, 96);
return ret;
}
public byte[] SubcodeDeinterleaved;
}
public class ZeroSubcodeSector : ISubcodeSector
{
public void ReadSubcodeDeinterleaved(byte[] buffer, int offset)
{
for (int i = 0; i < 96; i++) buffer[i + offset] = 0;
}
public void ReadSubcodeChannel(int number, byte[] buffer, int offset)
{
for (int i = 0; i < 12; i++)
buffer[i + offset] = 0;
}
}
/// <summary>
/// Reads subcode data from a blob, assuming it was already stored in deinterleaved format
/// </summary>
public class BlobSubcodeSectorPreDeinterleaved : ISubcodeSector
{
public void ReadSubcodeDeinterleaved(byte[] buffer, int offset)
{
Blob.Read(Offset, buffer, offset, 96);
}
public void ReadSubcodeChannel(int number, byte[] buffer, int offset)
{
Blob.Read(Offset + number * 12, buffer, offset, 12);
}
public IBlob Blob;
public long Offset;
}
/// <summary>
/// Control bit flags for the Q Subchannel
/// </summary>
[Flags]
public enum EControlQ
{
None = 0,
StereoNoPreEmph = 0,
StereoPreEmph = 1,
MonoNoPreemph = 8,
MonoPreEmph = 9,
DataUninterrupted = 4,
DataIncremental = 5,
CopyProhibitedMask = 0,
CopyPermittedMask = 2,
}
/// <summary>
/// Why did I make this a struct? I thought there might be a shitton of these and I was trying to cut down on object creation churn during disc-loading.
/// But I ended up mostly just having a shitton of byte[] for each buffer (I could improve that later to possibly reference a blob on top of a memorystream)
/// So, I should probably change that.
/// </summary>
public struct SubchannelQ
{
/// <summary>
/// ADR and CONTROL
/// TODO - make BCD2?
/// </summary>
public byte q_status;
/// <summary>
/// normal track: BCD indications of the current track number
/// leadin track: should be 0
/// TODO - make BCD2?
/// </summary>
public byte q_tno;
/// <summary>
/// normal track: BCD indications of the current index
/// leadin track: 'POINT' field used to ID the TOC entry #
/// </summary>
public byte q_index;
/// <summary>
/// These are the initial set of timestamps. Meaning varies:
/// check yellowbook 22.3.3 and 22.3.4
/// normal track: relative timestamp
/// leadin track: unknown
/// </summary>
public BCD2 min, sec, frame;
/// <summary>
/// This is supposed to be zero.. but CCD format stores it, so maybe it's useful for copy protection or something
/// </summary>
public byte zero;
/// <summary>
/// These are the second set of timestamps. Meaning varies:
/// check yellowbook 22.3.3 and 22.3.4
/// normal track: absolute timestamp
/// leadin track: timestamp of toc entry
/// </summary>
public BCD2 ap_min, ap_sec, ap_frame;
/// <summary>
/// The CRC. This is the actual CRC value as would be calculated from our library (it is inverted and written big endian to the disc)
/// Don't assume this CRC is correct-- If this SubchannelQ was read from a dumped disc, the CRC might be wrong.
/// CCD doesnt specify this for TOC entries, so it will be wrong. It may or may not be right for data track sectors from a CCD file.
/// Or we may have computed this SubchannelQ data and generated the correct CRC at that time.
/// </summary>
public ushort q_crc;
/// <summary>
/// Retrieves the initial set of timestamps (min,sec,frac) as a convenient Timestamp
/// </summary>
public Timestamp Timestamp { get { return new Timestamp(min.DecimalValue, sec.DecimalValue, frame.DecimalValue); } }
/// <summary>
/// Retrieves the second set of timestamps (ap_min, ap_sec, ap_frac) as a convenient Timestamp
/// </summary>
public Timestamp AP_Timestamp { get { return new Timestamp(ap_min.DecimalValue, ap_sec.DecimalValue, ap_frame.DecimalValue); } }
/// <summary>
/// sets the status byte from the provided adr and control values
/// </summary>
public void SetStatus(byte adr, EControlQ control)
{
q_status = ComputeStatus(adr, control);
}
/// <summary>
/// computes a status byte from the provided adr and control values
/// </summary>
public static byte ComputeStatus(int adr, EControlQ control)
{
return (byte)(adr | (((int)control) << 4));
}
/// <summary>
/// Retrives the ADR field of the q_status member (low 4 bits)
/// </summary>
public int ADR { get { return q_status & 0xF; } }
/// <summary>
/// Retrieves the CONTROL field of the q_status member (high 4 bits)
/// </summary>
public EControlQ CONTROL { get { return (EControlQ)((q_status >> 4) & 0xF); } }
}
//this has been checked against mednafen's and seems to match
//there are a few dozen different ways to do CRC16-CCITT
//this table is backwards or something. at any rate its tailored to the needs of the Q subchannel
internal static class CRC16_CCITT
{
private static readonly ushort[] table = new ushort[256];
static CRC16_CCITT()
{
for (ushort i = 0; i < 256; ++i)
{
ushort value = 0;
ushort temp = (ushort)(i << 8);
for (byte j = 0; j < 8; ++j)
{
if (((value ^ temp) & 0x8000) != 0)
value = (ushort)((value << 1) ^ 0x1021);
else
value <<= 1;
temp <<= 1;
}
table[i] = value;
}
}
public static ushort Calculate(byte[] data, int offset, int length)
{
ushort Result = 0;
for(int i=0;i<length;i++)
{
byte b = data[offset + i];
int index = (b ^ ((Result >> 8) & 0xFF));
Result = (ushort)((Result << 8) ^ table[index]);
}
return Result;
}
}
public class SubcodeDataDecoder
{
/// <summary>
/// This seems to deinterleave Q from a subcode buffer? Not sure.. it isn't getting used anywhere right now, as you can see.
/// </summary>
public static void Unpack_Q(byte[] output, int out_ofs, byte[] input, int in_ofs)
{
for (int i = 0; i < 12; i++)
output[out_ofs + i] = 0;
for (int i = 0; i < 96; i++)
{
int bytenum = i >> 3;
int bitnum = i & 7;
bitnum = 7 - bitnum;
int bitval = (byte)((input[in_ofs + i] >> 6) & 1);
bitval <<= bitnum;
output[out_ofs + bytenum] |= (byte)bitval;
}
}
}
}

View File

@ -1,295 +0,0 @@
using System;
using System.Text;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Contains structural information for the disc broken down into c# data structures for easy interrogation.
/// This represents a best-effort interpretation of the raw disc image.
/// You cannot assume a disc can be round-tripped into its original format through this (especially if it came from a format more detailed)
/// </summary>
public class DiscStructure
{
/// <summary>
/// Right now support for anything other than 1 session is totally not working
/// </summary>
public List<Session> Sessions = new List<Session>();
/// <summary>
/// List of Points described by the TOC.
/// TODO - this is kind of garbage, but... I was using it for Synthesize_SubcodeFromStructure() :/
/// Really, what it is, is a series of points where the tno/index change. Kind of an agenda. And thats how that function uses it.
/// Maybe I should rename it something different, or at least comment it
/// Or, you could look at this as a kind of compressed disc map
/// </summary>
public List<TOCPoint> Points;
/// <summary>
/// How many sectors in the disc, including the 150 lead-in sectors
/// </summary>
public int LengthInSectors;
/// <summary>
/// Length (including lead-in) of the disc as a timestamp
/// </summary>
public Timestamp FriendlyLength { get { return new Timestamp(LengthInSectors); } }
/// <summary>
/// How many bytes of data in the disc (including lead-in). Disc sectors are really 2352 bytes each, so this is LengthInSectors * 2352
/// </summary>
public long BinarySize
{
get { return LengthInSectors * 2352; }
}
/// <summary>
/// Synthesizes the DiscStructure from a DiscTOCRaw
/// </summary>
public class SynthesizeFromDiscTOCRawJob
{
public DiscTOCRaw TOCRaw;
public DiscStructure Result;
public void Run()
{
Result = new DiscStructure();
Result.Sessions.Add(new Session());
Track lastTrack = null;
for (int tnum = TOCRaw.FirstRecordedTrackNumber; tnum <= TOCRaw.LastRecordedTrackNumber; tnum++)
{
var ti = TOCRaw.TOCItems[tnum];
var track = new Track();
Result.Sessions[0].Tracks.Add(track);
track.Number = tnum;
track.Control = ti.Control;
track.TrackType = ETrackType.Unknown; //not known yet
track.Start_ABA = ti.LBATimestamp.Sector + 150;
if (lastTrack != null)
{
lastTrack.LengthInSectors = track.Start_ABA - lastTrack.Start_ABA;
}
lastTrack = track;
}
if(lastTrack != null)
lastTrack.LengthInSectors = (TOCRaw.LeadoutTimestamp.Sector + 150) - lastTrack.Start_ABA;
//total length of the disc is counted up to the leadout, including the lead-in.
//i guess supporting storing the leadout is future work.
Result.LengthInSectors = TOCRaw.LeadoutTimestamp.Sector;
}
}
/// <summary>
/// seeks the point immediately before (or equal to) this LBA
/// </summary>
public TOCPoint SeekPoint(int lba)
{
int aba = lba + 150;
for(int i=0;i<Points.Count;i++)
{
TOCPoint tp = Points[i];
if (tp.ABA > aba)
return Points[i - 1];
}
return Points[Points.Count - 1];
}
/// <summary>
///
/// </summary>
public class TOCPoint
{
public int Num;
public int ABA, TrackNum, IndexNum;
public Track Track;
public int ADR;
public EControlQ Control;
public int LBA
{
get { return ABA - 150; }
}
}
/// <summary>
/// Generates the Points list from the current logical TOC
/// </summary>
public void Synthesize_TOCPointsFromSessions()
{
Points = new List<TOCPoint>();
int num = 0;
foreach (var ses in Sessions)
{
for(int t=0;t<ses.Tracks.Count;t++)
{
int tnum = t + 1;
var track = ses.Tracks[t];
for(int i=0;i<track.Indexes.Count;i++)
{
var index = track.Indexes[i];
bool repeat = false;
int aba = index.aba;
REPEAT:
var tp = new TOCPoint
{
Num = num++,
ABA = aba,
TrackNum = track.Number,
IndexNum = index.Number,
Track = track,
ADR = track.ADR,
Control = track.Control
};
//special case!
//yellow-book says:
//pre-gap for "first part of a digital data track not containing user data and encoded as a pause"
//first interval: at least 75 sectors coded as preceding track
//second interval: at least 150 sectors coded as user data track.
//TODO - add pause flag tracking to TOCPoint
//see mednafen's "TODO: Look into how we're supposed to handle subq control field in the four combinations of track types(data/audio)."
if (tnum != 1 && i == 0 && track.TrackType != ETrackType.Audio && !repeat)
{
//NOTE: we dont implement this exactly the same as mednafen, I think my logic is closer to the docs, but who knows, its complicated
int distance = track.Indexes[i + 1].aba - track.Indexes[i].aba;
//well, how do we know to apply this logic?
//we assume the 150 sector pregap is more important. so if thats all there is, theres no 75 sector pregap like the old track
//if theres a longer pregap, then we generate weird old track pregap to contain the rest.
if (distance > 150)
{
int weirdPregapSize = distance - 150;
//need a new point. fix the old one
tp.ADR = Points[Points.Count - 1].ADR;
tp.Control = Points[Points.Count - 1].Control;
Points.Add(tp);
aba += weirdPregapSize;
repeat = true;
goto REPEAT;
}
}
Points.Add(tp);
}
}
var tpLeadout = new TOCPoint();
var lastTrack = ses.Tracks[ses.Tracks.Count - 1];
tpLeadout.Num = num++;
tpLeadout.ABA = lastTrack.Indexes[1].aba + lastTrack.LengthInSectors;
tpLeadout.IndexNum = 0;
tpLeadout.TrackNum = 100;
tpLeadout.Track = null; //no leadout track.. now... or ever?
Points.Add(tpLeadout);
}
}
public class Session
{
public int num;
/// <summary>
/// All the tracks in the session.. but... Tracks[0] should be "Track 1". So beware of this.
/// We might should keep this organized as a dictionary as well.
/// </summary>
public List<Track> Tracks = new List<Track>();
//the length of the session (should be the sum of all track lengths)
public int length_aba;
public Timestamp FriendlyLength { get { return new Timestamp(length_aba); } }
}
public class Track
{
/// <summary>
/// The number of the track (1-indexed)
/// </summary>
public int Number;
/// <summary>
/// Conceptual track type, not necessarily stored this way anywhere
/// </summary>
public ETrackType TrackType;
/// <summary>
/// The 'control' properties of the track indicated by the subchannel Q.
/// While in principle these could vary during the track, illegally (or maybe legally according to weird redbook rules)
/// they normally don't; they're useful for describing what type of contents the track is.
/// </summary>
public EControlQ Control;
/// <summary>
/// Well, it seems a track can have an ADR property (used to fill the subchannel Q). This is delivered from a CCD file but may have to be guessed from
/// </summary>
public int ADR = 1;
/// <summary>
/// All the indexes related to the track. These will be 0-Indexed, but they could be non-consecutive.
/// </summary>
public List<Index> Indexes = new List<Index>();
/// <summary>
/// a track logically starts at index 1.
/// so this is the length from this index 1 to the next index 1 (or the end of the disc)
/// the time before track 1 index 1 is the lead-in and isn't accounted for in any track...
/// </summary>
public int LengthInSectors;
/// <summary>
/// The beginning ABA of the track (index 1). This isn't well-supported, yet
/// WHAT? IS THIS NOT AN ABA SOMETIMES?
/// IS IT THE INDEX 0 OF THE TRACK? THATS FUCKED UP. COMPARE TO TOCRAW ENTRIES. IT SHOULD BE MATCHING THAT
/// HEY??? SHOULD THIS EVEN BE HERE? YOURE SUPPOSED TO USE THE INDEXES INSTEAD.
/// WELL, IF WE KEEP THIS THE MEANING SHOULD BE SAME AS INDEX[1].LBA (or ABA) SO BE SURE TO WRITE THAT COMMENT HERE
/// </summary>
public int Start_ABA;
/// <summary>
/// The length as a timestamp (for accessing as a MM:SS:FF)
/// </summary>
public Timestamp FriendlyLength { get { return new Timestamp(LengthInSectors); } }
}
public class Index
{
public int Number;
public int aba;
public int LBA
{
get { return aba - 150; }
set { aba = value + 150; }
}
//the length of the section
//HEY! This is commented out because it is a bad idea.
//The length of a `section`? (what's a section?) is almost useless, and if you want it, you are probably making an error.
//public int length_lba;
//public Cue.Timestamp FriendlyLength { get { return new Cue.Timestamp(length_lba); } }
}
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
LengthInSectors = 0;
foreach (var session in Sessions)
{
var firstTrack = session.Tracks[0];
var lastTrack = session.Tracks[session.Tracks.Count - 1];
session.length_aba = lastTrack.Indexes[0].aba + lastTrack.LengthInSectors - firstTrack.Indexes[0].aba;
LengthInSectors += session.length_aba;
}
}
}
}

View File

@ -1,120 +0,0 @@
using System;
using System.Text;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Represents our best guess at what a disc drive firmware will receive by reading the TOC from the lead-in track, modeled after CCD contents and mednafen/PSX needs.
/// </summary>
public class DiscTOCRaw
{
/// <summary>
/// Synthesizes the TOC from a set of raw entries.
/// When a disc drive firmware reads the lead-in area, it builds this TOC from finding ADR=1 (mode=1) sectors in the Q subchannel of the lead-in area.
/// I don't think this lead-in area Q subchannel is stored in a CCD .sub file.
/// The disc drive firmware will discover other mode sectors in the lead-in area, and it will register those in separate data structures.
/// </summary>
public class SynthesizeFromRawTOCEntriesJob
{
public IEnumerable<RawTOCEntry> Entries;
public List<string> Log = new List<string>();
public DiscTOCRaw Result;
public void Run()
{
SynthesizeFromRawTOCEntriesJob job = this;
DiscTOCRaw ret = new DiscTOCRaw();
//just in case this doesnt get set...
ret.FirstRecordedTrackNumber = 0;
ret.LastRecordedTrackNumber = 0;
int maxFoundTrack = 0;
foreach (var te in job.Entries)
{
var q = te.QData;
int point = q.q_index;
//see ECMD-394 page 5-14 for info about point = 0xA0, 0xA1, 0xA2
if (point == 0x00)
job.Log.Add("unexpected POINT=00 in lead-in Q-channel");
else if (point <= 99)
{
maxFoundTrack = Math.Max(maxFoundTrack, point);
ret.TOCItems[point].LBATimestamp = q.AP_Timestamp;
ret.TOCItems[point].Control = q.CONTROL;
ret.TOCItems[point].Exists = true;
}
else if (point == 0xA0)
{
ret.FirstRecordedTrackNumber = q.ap_min.DecimalValue;
if (q.ap_frame.DecimalValue != 0) job.Log.Add("PFRAME should be 0 for POINT=0xA0");
if (q.ap_sec.DecimalValue == 0x00) ret.Session1Format = DiscTOCRaw.SessionFormat.Type00_CDROM_CDDA;
else if (q.ap_sec.DecimalValue == 0x10) ret.Session1Format = DiscTOCRaw.SessionFormat.Type10_CDI;
else if (q.ap_sec.DecimalValue == 0x20) ret.Session1Format = DiscTOCRaw.SessionFormat.Type20_CDXA;
else job.Log.Add("Unrecognized session format: PSEC should be one of {0x00,0x10,0x20} for POINT=0xA0");
}
else if (point == 0xA1)
{
ret.LastRecordedTrackNumber = q.ap_min.DecimalValue;
if (q.ap_sec.DecimalValue != 0) job.Log.Add("PSEC should be 0 for POINT=0xA1");
if (q.ap_frame.DecimalValue != 0) job.Log.Add("PFRAME should be 0 for POINT=0xA1");
}
else if (point == 0xA2)
{
ret.LeadoutTimestamp = q.AP_Timestamp;
}
}
//this is speculative:
//well, nothing to be done here..
if (ret.FirstRecordedTrackNumber == 0) { }
if (ret.LastRecordedTrackNumber == 0) { ret.LastRecordedTrackNumber = maxFoundTrack; }
job.Result = ret;
}
}
public enum SessionFormat
{
Type00_CDROM_CDDA,
Type10_CDI,
Type20_CDXA
}
/// <summary>
/// The TOC specifies the first recorded track number, independently of whatever may actually be recorded (its unclear what happens if theres a mismatch)
/// </summary>
public int FirstRecordedTrackNumber;
/// <summary>
/// The TOC specifies the last recorded track number, independently of whatever may actually be recorded (its unclear what happens if theres a mismatch)
/// </summary>
public int LastRecordedTrackNumber;
/// <summary>
/// The TOC specifies the format of the session, so here it is.
/// </summary>
public SessionFormat Session1Format = SessionFormat.Type00_CDROM_CDDA;
public struct TOCItem
{
public EControlQ Control;
public Timestamp LBATimestamp;
public bool Exists;
}
/// <summary>
/// I think it likely that a firmware would just have a buffer for 100 of these. There can never be more than 100 and it might not match the 0xA0 and 0xA1 -specified values
/// Also #0 is illegal and is always empty.
/// </summary>
public TOCItem[] TOCItems = new TOCItem[100];
/// <summary>
/// POINT=0xA2 specifies this
/// </summary>
public Timestamp LeadoutTimestamp;
}
}

View File

@ -1,4 +1,20 @@
//lets leave some notes here until we've rewritten every possible thing -- error recovery, ECM file format, ISO filesystem, etc.
//OLD ideas:
/*
* do some stuff asynchronously. for example, decoding mp3 sectors.
* 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
*/
//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: mode2 (could be 2336[vanilla mode2], 2048[xa mode2 form1], 2324[xa mode2 form2])
//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)
//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
//we used this code
//https://code.google.com/p/iso-parser/
@ -36,3 +52,49 @@
//check this for some iso stuff but seems like it ripped off corlett's code
//http://lioneditor.googlecode.com/svn/trunk/utils/isopatcherv05/src/
//http://code.ohloh.net/file?fid=185llKM04w3QCqwC2MdFgtUiQ94&cid=yPMRq_HKxUg&s=ecc_computeblock%28pSector%20%2B%200xC%2C%2052%2C%2043%2C%2086%2C%2088%2C%20pSector%20%2B%200x8C8%29&mp=1&ml=1&me=1&md=1&browser=Default#L106
//https://books.google.com/books?id=caF_AAAAQBAJ&lpg=PA124&ots=OA9Ttj9CHZ&dq=disc%20TOC%20point%20A2&pg=PA124
//http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html
//http://www.pctechguide.com/iso-9660-data-format-for-cds-cd-roms-cd-rs-and-cd-rws
//http://linux.die.net/man/1/cue2toc
//http://cdemu.sourceforge.net/project.php#sf
//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 links
//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
//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
//some other docs
//http://www.emutalk.net/threads/54428-Reference-for-8-byte-sub-header-used-in-CDROM-XA references http://ccsun.nchu.edu.tw/~imtech/cou...act%20Disc.pdf which is pretty cool
//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
*/

View File

@ -1 +0,0 @@
. consider normalizing everything to use -150 for lead-in crappola ?

View File

@ -1,5 +1,5 @@

Microsoft Visual Studio Solution File, Format Version 11.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Version", "Version\Version.csproj", "{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}"
EndProject
@ -56,85 +56,187 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Bizware.BizwareGL.S
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Client.MultiHawk", "BizHawk.Client.MultiHawk\BizHawk.Client.MultiHawk.csproj", "{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mednadisc", "psx\mednadisc\bizhawk\mednadisc.vcxproj", "{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|Win32 = Debug|Win32
Debug|x86 = Debug|x86
Release|Mixed Platforms = Release|Mixed Platforms
Release|Win32 = Release|Win32
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Debug|Mixed Platforms.Build.0 = Debug|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Debug|Win32.ActiveCfg = Debug|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Debug|x86.ActiveCfg = Debug|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Debug|x86.Build.0 = Debug|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Release|Mixed Platforms.ActiveCfg = Release|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Release|Mixed Platforms.Build.0 = Release|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Release|Win32.ActiveCfg = Release|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Release|x86.ActiveCfg = Release|x86
{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}.Release|x86.Build.0 = Release|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Debug|Mixed Platforms.Build.0 = Debug|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Debug|Win32.ActiveCfg = Debug|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Debug|x86.ActiveCfg = Debug|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Debug|x86.Build.0 = Debug|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Release|Mixed Platforms.ActiveCfg = Release|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Release|Mixed Platforms.Build.0 = Release|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Release|Win32.ActiveCfg = Release|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Release|x86.ActiveCfg = Release|x86
{24A0AA3C-B25F-4197-B23D-476D6462DBA0}.Release|x86.Build.0 = Release|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Debug|Mixed Platforms.Build.0 = Debug|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Debug|Win32.ActiveCfg = Debug|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Debug|x86.ActiveCfg = Debug|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Debug|x86.Build.0 = Debug|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Release|Mixed Platforms.ActiveCfg = Release|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Release|Mixed Platforms.Build.0 = Release|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Release|Win32.ActiveCfg = Release|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Release|x86.ActiveCfg = Release|x86
{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}.Release|x86.Build.0 = Release|x86
{DD448B37-BA3F-4544-9754-5406E8094723}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{DD448B37-BA3F-4544-9754-5406E8094723}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{DD448B37-BA3F-4544-9754-5406E8094723}.Debug|Win32.ActiveCfg = Debug|Any CPU
{DD448B37-BA3F-4544-9754-5406E8094723}.Debug|x86.ActiveCfg = Debug|x86
{DD448B37-BA3F-4544-9754-5406E8094723}.Debug|x86.Build.0 = Debug|x86
{DD448B37-BA3F-4544-9754-5406E8094723}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{DD448B37-BA3F-4544-9754-5406E8094723}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{DD448B37-BA3F-4544-9754-5406E8094723}.Release|Win32.ActiveCfg = Release|Any CPU
{DD448B37-BA3F-4544-9754-5406E8094723}.Release|x86.ActiveCfg = Release|x86
{DD448B37-BA3F-4544-9754-5406E8094723}.Release|x86.Build.0 = Release|x86
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Debug|Win32.ActiveCfg = Debug|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Debug|x86.ActiveCfg = Debug|x86
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Debug|x86.Build.0 = Debug|x86
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Release|Win32.ActiveCfg = Release|Any CPU
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Release|x86.ActiveCfg = Release|x86
{C4366030-6D03-424B-AE53-F4F43BB217C3}.Release|x86.Build.0 = Release|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Debug|Mixed Platforms.Build.0 = Debug|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Debug|Win32.ActiveCfg = Debug|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Debug|x86.ActiveCfg = Debug|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Debug|x86.Build.0 = Debug|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Release|Mixed Platforms.ActiveCfg = Release|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Release|Mixed Platforms.Build.0 = Release|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Release|Win32.ActiveCfg = Release|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Release|x86.ActiveCfg = Release|x86
{F51946EA-827F-4D82-B841-1F2F6D060312}.Release|x86.Build.0 = Release|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Debug|Mixed Platforms.Build.0 = Debug|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Debug|Win32.ActiveCfg = Debug|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Debug|x86.ActiveCfg = Debug|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Debug|x86.Build.0 = Debug|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Release|Mixed Platforms.ActiveCfg = Release|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Release|Mixed Platforms.Build.0 = Release|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Release|Win32.ActiveCfg = Release|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Release|x86.ActiveCfg = Release|x86
{E1A23168-B571-411C-B360-2229E7225E0E}.Release|x86.Build.0 = Release|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Debug|Mixed Platforms.Build.0 = Debug|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Debug|Win32.ActiveCfg = Debug|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Debug|x86.ActiveCfg = Debug|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Debug|x86.Build.0 = Debug|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Release|Mixed Platforms.ActiveCfg = Release|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Release|Mixed Platforms.Build.0 = Release|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Release|Win32.ActiveCfg = Release|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Release|x86.ActiveCfg = Release|x86
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}.Release|x86.Build.0 = Release|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|Mixed Platforms.Build.0 = Debug|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|Win32.ActiveCfg = Debug|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|x86.ActiveCfg = Debug|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|x86.Build.0 = Debug|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Mixed Platforms.ActiveCfg = Release|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Mixed Platforms.Build.0 = Release|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Win32.ActiveCfg = Release|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|x86.ActiveCfg = Release|x86
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|x86.Build.0 = Release|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|Mixed Platforms.Build.0 = Debug|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|Win32.ActiveCfg = Debug|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|x86.ActiveCfg = Debug|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Debug|x86.Build.0 = Debug|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|Mixed Platforms.ActiveCfg = Release|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|Mixed Platforms.Build.0 = Release|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|Win32.ActiveCfg = Release|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|x86.ActiveCfg = Release|x86
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE}.Release|x86.Build.0 = Release|x86
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Debug|Win32.ActiveCfg = Debug|Any CPU
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Debug|x86.ActiveCfg = Debug|x86
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Debug|x86.Build.0 = Debug|x86
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Release|Win32.ActiveCfg = Release|Any CPU
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Release|x86.ActiveCfg = Release|x86
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9}.Release|x86.Build.0 = Release|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Debug|Mixed Platforms.Build.0 = Debug|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Debug|Win32.ActiveCfg = Debug|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Debug|x86.ActiveCfg = Debug|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Debug|x86.Build.0 = Debug|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Release|Mixed Platforms.ActiveCfg = Release|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Release|Mixed Platforms.Build.0 = Release|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Release|Win32.ActiveCfg = Release|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Release|x86.ActiveCfg = Release|x86
{337CA23E-65E7-44E1-9411-97EE08BB8116}.Release|x86.Build.0 = Release|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Debug|Mixed Platforms.Build.0 = Debug|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Debug|Win32.ActiveCfg = Debug|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Debug|x86.ActiveCfg = Debug|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Debug|x86.Build.0 = Debug|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Release|Mixed Platforms.ActiveCfg = Release|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Release|Mixed Platforms.Build.0 = Release|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Release|Win32.ActiveCfg = Release|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Release|x86.ActiveCfg = Release|x86
{E6B436B1-A3CD-4C9A-8F76-5D7154726884}.Release|x86.Build.0 = Release|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Debug|Mixed Platforms.Build.0 = Debug|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Debug|Win32.ActiveCfg = Debug|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Debug|x86.ActiveCfg = Debug|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Debug|x86.Build.0 = Debug|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Release|Mixed Platforms.ActiveCfg = Release|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Release|Mixed Platforms.Build.0 = Release|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Release|Win32.ActiveCfg = Release|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Release|x86.ActiveCfg = Release|x86
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18}.Release|x86.Build.0 = Release|x86
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Debug|Mixed Platforms.Build.0 = Debug|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Debug|Win32.ActiveCfg = Debug|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Debug|Win32.Build.0 = Debug|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Debug|x86.ActiveCfg = Debug|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Debug|x86.Build.0 = Debug|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Release|Mixed Platforms.ActiveCfg = Release|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Release|Mixed Platforms.Build.0 = Release|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Release|Win32.ActiveCfg = Release|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Release|Win32.Build.0 = Release|Win32
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Release|x86.ActiveCfg = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{24A0AA3C-B25F-4197-B23D-476D6462DBA0} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
{DD448B37-BA3F-4544-9754-5406E8094723} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
{C4366030-6D03-424B-AE53-F4F43BB217C3} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
{F51946EA-827F-4D82-B841-1F2F6D060312} = {3627C08B-3E43-4224-9DA4-40BD69495FBC}
{24A0AA3C-B25F-4197-B23D-476D6462DBA0} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
{E1A23168-B571-411C-B360-2229E7225E0E} = {3627C08B-3E43-4224-9DA4-40BD69495FBC}
{F51946EA-827F-4D82-B841-1F2F6D060312} = {3627C08B-3E43-4224-9DA4-40BD69495FBC}
{197D4314-8A9F-49BA-977D-54ACEFAEB6BA} = {3627C08B-3E43-4224-9DA4-40BD69495FBC}
{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC} = {3627C08B-3E43-4224-9DA4-40BD69495FBC}
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465} = {0540A9A6-977E-466D-8BD3-1D8590BD5282}
{5160CFB1-5389-47C1-B7F6-8A0DC97641EE} = {0540A9A6-977E-466D-8BD3-1D8590BD5282}
{2D2890A8-C338-4439-AD8B-CB9EE85A94F9} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
{337CA23E-65E7-44E1-9411-97EE08BB8116} = {0540A9A6-977E-466D-8BD3-1D8590BD5282}
{E6B436B1-A3CD-4C9A-8F76-5D7154726884} = {0540A9A6-977E-466D-8BD3-1D8590BD5282}
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = BizHawk.Client.EmuHawk\BizHawk.Client.EmuHawk.csproj

BIN
output/dll/mednadisc.dll Normal file

Binary file not shown.

View File

@ -24,6 +24,7 @@
#include gamedb_gba.txt
#include gamedb_lynx.txt
#include gamedb_appleII.txt
#include gamedb_psx.txt
; ************ TI-83 ************

7008
output/gamedb/gamedb_psx.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ EW_EXPORT void* mednadisc_LoadCD(const char* fname)
{
CDAccess* disc = NULL;
try {
disc = cdaccess_open_image(fname,false);
disc = CDAccess_Open(fname,false);
}
catch(MDFN_Error &) {
return NULL;
@ -53,16 +53,20 @@ EW_EXPORT void mednadisc_ReadTOC(MednaDisc* md, JustTOC* justToc, CDUtility::TOC
memcpy(tracks101,toc.tracks,sizeof(toc.tracks));
}
//NOTE: the subcode will come out interleaved.
//Don't try changing this unless youre REALLY bored. It's convoluted.
//If you do, make sure you have three states: must_interleave, must_deinterleaved and dontcare
EW_EXPORT int32 mednadisc_ReadSector(MednaDisc* md, int lba, void* buf2448)
{
CDAccess* disc = md->disc;
CDUtility::TOC &toc = md->toc;
try
{
//EDIT: this is handled now by the individual readers
//if it's at the lead-out track or beyond, synthesize it as a lead-out sector
if(lba >= (int32)toc.tracks[100].lba)
synth_leadout_sector_lba(0x02, toc, lba, (uint8*)buf2448);
else
//if(lba >= (int32)toc.tracks[100].lba)
// synth_leadout_sector_lba(0x02, toc, lba, (uint8*)buf2448);
//else
disc->Read_Raw_Sector((uint8*)buf2448,lba);
}
catch(MDFN_Error &) {

View File

@ -1,248 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="psx">
<UniqueIdentifier>{00f73db4-1182-4bf7-b891-66bf860d3742}</UniqueIdentifier>
</Filter>
<Filter Include="emuware">
<UniqueIdentifier>{f69cc8f2-7480-44d6-9a32-9dca789d2bf6}</UniqueIdentifier>
</Filter>
<Filter Include="cdrom">
<UniqueIdentifier>{57a8e6ec-9225-410d-b38f-ba209abae070}</UniqueIdentifier>
</Filter>
<Filter Include="psx\input">
<UniqueIdentifier>{76abb796-5411-4d33-b3e0-f1f3873f138e}</UniqueIdentifier>
</Filter>
<Filter Include="emuware\msvc">
<UniqueIdentifier>{cb700979-4dce-4b10-8521-3ab71a313271}</UniqueIdentifier>
</Filter>
<Filter Include="video">
<UniqueIdentifier>{d1f71901-17a5-441a-8b4f-f7da34a057c1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\psx\cdc.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\psx.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\dis.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\gte.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\spu.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\Stream.cpp" />
<ClCompile Include="..\psx\frontio.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\cpu.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\dma.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\irq.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\mdec.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\sio.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\timer.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\endian.cpp" />
<ClCompile Include="..\psx\input\dualshock.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\psx\input\gamepad.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\psx\input\guncon.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\psx\input\justifier.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\psx\input\memcard.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\psx\input\mouse.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\psx\input\multitap.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\psx\input\negcon.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\psx\input\dualanalog.cpp">
<Filter>psx\input</Filter>
</ClCompile>
<ClCompile Include="..\octoshock.cpp" />
<ClCompile Include="..\emuware\emuware.cpp">
<Filter>emuware</Filter>
</ClCompile>
<ClCompile Include="..\video\surface.cpp">
<Filter>video</Filter>
</ClCompile>
<ClCompile Include="..\video\Deinterlacer.cpp">
<Filter>video</Filter>
</ClCompile>
<ClCompile Include="..\emuware\EW_state.cpp">
<Filter>emuware</Filter>
</ClCompile>
<ClCompile Include="..\psx\gpu.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\gpu_line.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\gpu_polygon.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\psx\gpu_sprite.cpp">
<Filter>psx</Filter>
</ClCompile>
<ClCompile Include="..\cdrom\CDUtility.cpp">
<Filter>cdrom</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\psx\cdc.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\psx.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\emuware\emuware.h">
<Filter>emuware</Filter>
</ClInclude>
<ClInclude Include="..\psx\dis.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\gte.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\math_ops.h" />
<ClInclude Include="..\git.h" />
<ClInclude Include="..\octoshock.h" />
<ClInclude Include="..\psx\spu.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\endian.h" />
<ClInclude Include="..\Stream.h" />
<ClInclude Include="..\error.h" />
<ClInclude Include="..\psx\frontio.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\cpu.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\dma.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\irq.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\mdec.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\sio.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\timer.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\dualanalog.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\dualshock.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\gamepad.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\guncon.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\justifier.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\memcard.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\mouse.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\multitap.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\psx\input\negcon.h">
<Filter>psx\input</Filter>
</ClInclude>
<ClInclude Include="..\emuware\msvc\inttypes.h">
<Filter>emuware\msvc</Filter>
</ClInclude>
<ClInclude Include="..\emuware\msvc\stdint.h">
<Filter>emuware\msvc</Filter>
</ClInclude>
<ClInclude Include="..\video\surface.h">
<Filter>video</Filter>
</ClInclude>
<ClInclude Include="..\video\Deinterlacer.h">
<Filter>video</Filter>
</ClInclude>
<ClInclude Include="..\emuware\EW_state.h">
<Filter>emuware</Filter>
</ClInclude>
<ClInclude Include="..\cdrom\SimpleFIFO.h">
<Filter>cdrom</Filter>
</ClInclude>
<ClInclude Include="..\psx\gpu.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\psx\masmem.h">
<Filter>psx</Filter>
</ClInclude>
<ClInclude Include="..\cdrom\CDUtility.h">
<Filter>cdrom</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\psx\spu_fir_table.inc">
<Filter>psx</Filter>
</None>
<None Include="..\psx\spu_reverb.inc">
<Filter>psx</Filter>
</None>
<None Include="..\psx\gpu_command_table.inc">
<Filter>psx</Filter>
</None>
<None Include="..\psx\gpu_common.inc">
<Filter>psx</Filter>
</None>
<None Include="..\psx\gpu_line.inc">
<Filter>psx</Filter>
</None>
<None Include="..\psx\gpu_polygon.inc">
<Filter>psx</Filter>
</None>
<None Include="..\psx\gpu_sprite.inc">
<Filter>psx</Filter>
</None>
<None Include="..\psx\cpu_computedgoto.inc">
<Filter>psx</Filter>
</None>
<None Include="..\psx\cpu_bigswitch.inc">
<Filter>psx</Filter>
</None>
</ItemGroup>
</Project>

View File

@ -11,10 +11,10 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\cdrom\audioreader.cpp" />
<ClCompile Include="..\cdrom\CDAccess.cpp" />
<ClCompile Include="..\cdrom\CDAccess_CCD.cpp" />
<ClCompile Include="..\cdrom\CDAccess_Image.cpp" />
<ClCompile Include="..\cdrom\CDAFReader.cpp" />
<ClCompile Include="..\cdrom\cdromif.cpp" />
<ClCompile Include="..\cdrom\CDUtility.cpp" />
<ClCompile Include="..\cdrom\crc32.cpp" />
@ -30,12 +30,15 @@
<ClCompile Include="..\MemoryStream.cpp" />
<ClCompile Include="..\Stream.cpp" />
<ClCompile Include="..\string\trim.cpp" />
<ClCompile Include="..\trio\trio.c" />
<ClCompile Include="..\trio\trionan.c" />
<ClCompile Include="..\trio\triostr.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\cdrom\audioreader.h" />
<ClInclude Include="..\cdrom\CDAccess.h" />
<ClInclude Include="..\cdrom\CDAccess_CCD.h" />
<ClInclude Include="..\cdrom\CDAccess_Image.h" />
<ClInclude Include="..\cdrom\CDAFReader.h" />
<ClInclude Include="..\cdrom\cdromif.h" />
<ClInclude Include="..\cdrom\CDUtility.h" />
<ClInclude Include="..\cdrom\dvdisaster.h" />
@ -51,6 +54,7 @@
<ClInclude Include="..\MemoryStream.h" />
<ClInclude Include="..\Stream.h" />
<ClInclude Include="..\string\trim.h" />
<ClInclude Include="..\trio\trio.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}</ProjectGuid>
@ -81,7 +85,7 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(ProjectDir)\..\..\..\output\dll\</OutDir>
<OutDir>$(ProjectDir)..\..\..\output\dll\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
@ -92,7 +96,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>EW_EXPORT;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;OCTOSHOCK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>TRIO_PUBLIC=;TRIO_PRIVATE=static;EW_EXPORT;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;OCTOSHOCK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../emuware/msvc;..</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
@ -113,7 +117,7 @@
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;EW_EXPORT;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;TRIO_PUBLIC=;TRIO_PRIVATE=static;EW_EXPORT;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>

View File

@ -10,6 +10,9 @@
<Filter Include="string">
<UniqueIdentifier>{798fa5bd-6381-487a-99d2-35a15a6da439}</UniqueIdentifier>
</Filter>
<Filter Include="trio">
<UniqueIdentifier>{a43930f5-41a5-4b2b-92ef-bd90f9716127}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\cdrom\CDAccess_Image.cpp">
@ -51,10 +54,19 @@
<Filter>string</Filter>
</ClCompile>
<ClCompile Include="..\general.cpp" />
<ClCompile Include="..\cdrom\audioreader.cpp">
<ClCompile Include="..\Mednadisc.cpp" />
<ClCompile Include="..\trio\trio.c">
<Filter>trio</Filter>
</ClCompile>
<ClCompile Include="..\cdrom\CDAFReader.cpp">
<Filter>cdrom</Filter>
</ClCompile>
<ClCompile Include="..\Mednadisc.cpp" />
<ClCompile Include="..\trio\trionan.c">
<Filter>trio</Filter>
</ClCompile>
<ClCompile Include="..\trio\triostr.c">
<Filter>trio</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\cdrom\CDAccess_Image.h">
@ -96,9 +108,12 @@
<Filter>string</Filter>
</ClInclude>
<ClInclude Include="..\general.h" />
<ClInclude Include="..\cdrom\audioreader.h">
<ClInclude Include="..\Mednadisc.h" />
<ClInclude Include="..\trio\trio.h">
<Filter>trio</Filter>
</ClInclude>
<ClInclude Include="..\cdrom\CDAFReader.h">
<Filter>cdrom</Filter>
</ClInclude>
<ClInclude Include="..\Mednadisc.h" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,83 @@
/* Mednafen - Multi-system Emulator
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// CDAFR_Open(), and CDAFReader, will NOT take "ownership" of the Stream object(IE it won't ever delete it). Though it does assume it has exclusive access
// to it for as long as the CDAFReader object exists.
// Don't allow exceptions to propagate into the vorbis/musepack/etc. libraries, as it could easily leave the state of the library's decoder "object" in an
// inconsistent state, which would cause all sorts of unfun when we try to destroy it while handling the exception farther up.
#include "emuware/emuware.h"
#include "CDAFReader.h"
#include "CDAFReader_Vorbis.h"
#include "CDAFReader_MPC.h"
#ifdef HAVE_LIBSNDFILE
#include "CDAFReader_SF.h"
#endif
CDAFReader::CDAFReader() : LastReadPos(0)
{
}
CDAFReader::~CDAFReader()
{
}
CDAFReader* CDAFR_Null_Open(Stream* fp)
{
return NULL;
}
CDAFReader *CDAFR_Open(Stream *fp)
{
static CDAFReader* (* const OpenFuncs[])(Stream* fp) =
{
#ifdef HAVE_MPC
CDAFR_MPC_Open,
#endif
#ifdef HAVE_VORBIS
CDAFR_Vorbis_Open, // Must come before CDAFR_SF_Open
#endif
#ifdef HAVE_LIBSNDFILE
CDAFR_SF_Open,
#endif
CDAFR_Null_Open
};
for(int idx=0;idx<ARRAY_SIZE(OpenFuncs);idx++)
{
auto f = OpenFuncs[idx];
try
{
fp->rewind();
return f(fp);
}
catch(int i)
{
}
}
return(NULL);
}

Some files were not shown because too many files have changed in this diff Show More