big discsystem refactors and preliminary CCD handling. not to be considered stable, but ill start supporting it.
This commit is contained in:
parent
d8a204572d
commit
29b217b587
|
@ -203,9 +203,16 @@ namespace BizHawk.Client.Common
|
|||
try
|
||||
{
|
||||
var ext = file.Extension.ToLower();
|
||||
if (ext == ".iso" || ext == ".cue")
|
||||
if (ext == ".iso" || ext == ".cue" || ext == ".ccd")
|
||||
{
|
||||
var disc = ext == ".iso" ? Disc.FromIsoPath(path) : Disc.FromCuePath(path, new CueBinPrefs());
|
||||
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);
|
||||
|
||||
var hash = disc.GetHash();
|
||||
game = Database.CheckDatabase(hash);
|
||||
if (game == null)
|
||||
|
|
|
@ -17,18 +17,18 @@ namespace BizHawk.Client.DiscoHawk
|
|||
public static void Extract(Disc disc, string path, string filebase)
|
||||
{
|
||||
bool confirmed = false;
|
||||
var tracks = disc.TOC.Sessions[0].Tracks;
|
||||
var tracks = disc.Structure.Sessions[0].Tracks;
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (track.TrackType != ETrackType.Audio)
|
||||
continue;
|
||||
|
||||
var waveData = new byte[track.length_aba * 2352];
|
||||
var waveData = new byte[track.LengthInSectors * 2352];
|
||||
int startLba = track.Indexes[1].LBA;
|
||||
for (int sector = 0; sector < track.length_aba; sector++)
|
||||
for (int sector = 0; sector < track.LengthInSectors; sector++)
|
||||
disc.ReadLBA_2352(startLba + sector, waveData, sector * 2352);
|
||||
|
||||
string mp3Path = string.Format("{0} - Track {1:D2}.mp3", Path.Combine(path, filebase), track.num);
|
||||
string mp3Path = string.Format("{0} - Track {1:D2}.mp3", Path.Combine(path, filebase), track.Number);
|
||||
if (File.Exists(mp3Path))
|
||||
{
|
||||
if (!confirmed)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{C4366030-6D03-424B-AE53-F4F43BB217C3}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>BizHawk.Client.DiscoHawk</RootNamespace>
|
||||
<AssemblyName>DiscoHawk</AssemblyName>
|
||||
|
@ -153,6 +153,10 @@
|
|||
<Content Include="discohawk.ico" />
|
||||
</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.DiscSystem\BizHawk.Emulation.DiscSystem.csproj">
|
||||
<Project>{f51946ea-827f-4d82-b841-1f2f6d060312}</Project>
|
||||
<Name>BizHawk.Emulation.DiscSystem</Name>
|
||||
|
|
|
@ -178,6 +178,10 @@ namespace BizHawk.Client.DiscoHawk
|
|||
dialog.ShowDialog();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//test stuff...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,11 +76,11 @@ namespace BizHawk.Client.DiscoHawk
|
|||
Disc disc = discRecord.Disc;
|
||||
boundDiscRecord = discRecord;
|
||||
|
||||
DiscTOC toc = disc.ReadTOC();
|
||||
DiscStructure toc = disc.ReadStructure();
|
||||
boundDisc = disc;
|
||||
lblSessions.Text = toc.Sessions.Count.ToString();
|
||||
lblTracks.Text = toc.Sessions.Sum((ses) => ses.Tracks.Count).ToString();
|
||||
lblSectors.Text = string.Format("{0} ({1})", toc.length_aba, toc.FriendlyLength.Value);
|
||||
lblSectors.Text = string.Format("{0} ({1})", toc.LengthInSectors, toc.FriendlyLength.Value);
|
||||
lblSize.Text = string.Format("{0:0.00} MB", toc.BinarySize / 1024.0 / 1024.0);
|
||||
btnExportCue.Enabled = true;
|
||||
UpdateCue();
|
||||
|
|
|
@ -9,6 +9,8 @@ using System.Windows.Forms;
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.StringExtensions;
|
||||
using BizHawk.Emulation.DiscSystem;
|
||||
|
||||
namespace BizHawk.Client.DiscoHawk
|
||||
|
@ -60,8 +62,10 @@ namespace BizHawk.Client.DiscoHawk
|
|||
Disc disc = null;
|
||||
if (ext == ".ISO")
|
||||
disc = Disc.FromIsoPath(file);
|
||||
else if(ext == ".CUE")
|
||||
else if (ext == ".CUE")
|
||||
disc = Disc.FromCuePath(file, prefs);
|
||||
else if (ext == ".CCD")
|
||||
disc = Disc.FromCCDPath(file);
|
||||
string baseName = Path.GetFileNameWithoutExtension(file);
|
||||
baseName += "_hawked";
|
||||
prefs.ReallyDumpBin = true;
|
||||
|
@ -116,7 +120,8 @@ namespace BizHawk.Client.DiscoHawk
|
|||
if (files == null) return new List<string>();
|
||||
foreach (string str in files)
|
||||
{
|
||||
if (Path.GetExtension(str).ToUpper() != ".CUE" && Path.GetExtension(str).ToUpper() != ".ISO")
|
||||
string ext = Path.GetExtension(str).ToUpper();
|
||||
if(!ext.In(new string[]{".CUE",".ISO",".CCD"}))
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
|
|
@ -1875,20 +1875,20 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (VersionInfo.DeveloperBuild)
|
||||
{
|
||||
ofd.Filter = FormatFilter(
|
||||
"Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.cue;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;%ARCH%",
|
||||
"Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;%ARCH%",
|
||||
"Music Files", "*.psf;*.sid",
|
||||
"Disc Images", "*.cue",
|
||||
"Disc Images", "*.cue;*.ccd",
|
||||
"NES", "*.nes;*.fds;%ARCH%",
|
||||
"Super NES", "*.smc;*.sfc;*.xml;%ARCH%",
|
||||
"Master System", "*.sms;*.gg;*.sg;%ARCH%",
|
||||
"PC Engine", "*.pce;*.sgx;*.cue;%ARCH%",
|
||||
"PC Engine", "*.pce;*.sgx;*.cue;*.ccd;%ARCH%",
|
||||
"TI-83", "*.rom;%ARCH%",
|
||||
"Archive Files", "%ARCH%",
|
||||
"Savestate", "*.state",
|
||||
"Atari 2600", "*.a26;*.bin;%ARCH%",
|
||||
"Atari 7800", "*.a78;*.bin;%ARCH%",
|
||||
"Atari Lynx", "*.lnx;%ARCH%",
|
||||
"Genesis", "*.gen;*.smd;*.bin;*.md;*.cue;%ARCH%",
|
||||
"Genesis", "*.gen;*.smd;*.bin;*.md;*.cue;*.ccd;%ARCH%",
|
||||
"Gameboy", "*.gb;*.gbc;*.sgb;%ARCH%",
|
||||
"Gameboy Advance", "*.gba;%ARCH%",
|
||||
"Colecovision", "*.col;%ARCH%",
|
||||
|
@ -1904,15 +1904,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
else
|
||||
{
|
||||
ofd.Filter = FormatFilter(
|
||||
"Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.gb;*.gbc;*.gba;*.pce;*.sgx;*.bin;*.smd;*.gen;*.md;*.smc;*.sfc;*.a26;*.a78;*.lnx;*.col;*.rom;*.cue;*.sgb;*.z64;*.v64;*.n64;*.ws;*.wsc;*.xml;%ARCH%",
|
||||
"Disc Images", "*.cue",
|
||||
"Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.gb;*.gbc;*.gba;*.pce;*.sgx;*.bin;*.smd;*.gen;*.md;*.smc;*.sfc;*.a26;*.a78;*.lnx;*.col;*.rom;*.cue;*.ccd;*.sgb;*.z64;*.v64;*.n64;*.ws;*.wsc;*.xml;%ARCH%",
|
||||
"Disc Images", "*.cue;*.ccd",
|
||||
"NES", "*.nes;*.fds;%ARCH%",
|
||||
"Super NES", "*.smc;*.sfc;*.xml;%ARCH%",
|
||||
"Nintendo 64", "*.z64;*.v64;*.n64",
|
||||
"Gameboy", "*.gb;*.gbc;*.sgb;%ARCH%",
|
||||
"Gameboy Advance", "*.gba;%ARCH%",
|
||||
"Master System", "*.sms;*.gg;*.sg;%ARCH%",
|
||||
"PC Engine", "*.pce;*.sgx;*.cue;%ARCH%",
|
||||
"PC Engine", "*.pce;*.sgx;*.cue;*.ccd;%ARCH%",
|
||||
"Atari 2600", "*.a26;%ARCH%",
|
||||
"Atari 7800", "*.a78;%ARCH%",
|
||||
"Atari Lynx", "*.lnx;%ARCH%",
|
||||
|
@ -1920,7 +1920,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
"TI-83", "*.rom;%ARCH%",
|
||||
"Archive Files", "%ARCH%",
|
||||
"Savestate", "*.state",
|
||||
"Genesis", "*.gen;*.md;*.smd;*.bin;*.cue;%ARCH%",
|
||||
"Genesis", "*.gen;*.md;*.smd;*.bin;*.cue;*.ccd;%ARCH%",
|
||||
"WonderSwan", "*.ws;*.wsc;%ARCH%",
|
||||
"All Files", "*.*");
|
||||
}
|
||||
|
|
|
@ -65,6 +65,10 @@
|
|||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>..\References\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\References\OpenTK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
|
|
|
@ -147,6 +147,8 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
|
||||
PCEngine pce;
|
||||
public Disc disc;
|
||||
SubcodeReader subcodeReader;
|
||||
SubchannelQ subchannelQ;
|
||||
int audioStartLBA;
|
||||
int audioEndLBA;
|
||||
|
||||
|
@ -154,6 +156,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
{
|
||||
this.pce = pce;
|
||||
this.disc = disc;
|
||||
subcodeReader = new SubcodeReader(disc);
|
||||
}
|
||||
|
||||
public void Think()
|
||||
|
@ -418,7 +421,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
|
||||
case 0x80: // Set start offset in track units
|
||||
byte trackNo = CommandBuffer[2].BCDtoBin();
|
||||
audioStartLBA = disc.TOC.Sessions[0].Tracks[trackNo - 1].Indexes[1].aba - 150;
|
||||
audioStartLBA = disc.Structure.Sessions[0].Tracks[trackNo - 1].Indexes[1].aba - 150;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -453,10 +456,10 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
|
||||
case 0x80: // Set end offset in track units
|
||||
byte trackNo = CommandBuffer[2].BCDtoBin();
|
||||
if (trackNo - 1 >= disc.TOC.Sessions[0].Tracks.Count)
|
||||
if (trackNo - 1 >= disc.Structure.Sessions[0].Tracks.Count)
|
||||
audioEndLBA = disc.LBACount;
|
||||
else
|
||||
audioEndLBA = disc.TOC.Sessions[0].Tracks[trackNo - 1].Indexes[1].aba - 150;
|
||||
audioEndLBA = disc.Structure.Sessions[0].Tracks[trackNo - 1].Indexes[1].aba - 150;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -493,7 +496,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
void CommandReadSubcodeQ()
|
||||
{
|
||||
bool playing = pce.CDAudio.Mode != CDAudio.CDAudioMode_Stopped;
|
||||
var sectorEntry = disc.ReadLBA_SectorEntry(playing ? pce.CDAudio.CurrentSector : CurrentReadingSector);
|
||||
int sectorNum = playing ? pce.CDAudio.CurrentSector : CurrentReadingSector;
|
||||
|
||||
DataIn.Clear();
|
||||
|
||||
|
@ -504,15 +507,16 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
case CDAudio.CDAudioMode_Stopped: DataIn.Enqueue(3); break;
|
||||
}
|
||||
|
||||
DataIn.Enqueue(sectorEntry.q_status); // I do not know what status is
|
||||
DataIn.Enqueue(sectorEntry.q_tno.BCDValue); // track
|
||||
DataIn.Enqueue(sectorEntry.q_index.BCDValue); // index
|
||||
DataIn.Enqueue(sectorEntry.q_min.BCDValue); // M(rel)
|
||||
DataIn.Enqueue(sectorEntry.q_sec.BCDValue); // S(rel)
|
||||
DataIn.Enqueue(sectorEntry.q_frame.BCDValue); // F(rel)
|
||||
DataIn.Enqueue(sectorEntry.q_amin.BCDValue); // M(abs)
|
||||
DataIn.Enqueue(sectorEntry.q_asec.BCDValue); // S(abs)
|
||||
DataIn.Enqueue(sectorEntry.q_aframe.BCDValue); // F(abs)
|
||||
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
|
||||
DataIn.Enqueue(subchannelQ.min.BCDValue); // M(rel)
|
||||
DataIn.Enqueue(subchannelQ.sec.BCDValue); // S(rel)
|
||||
DataIn.Enqueue(subchannelQ.frame.BCDValue); // F(rel)
|
||||
DataIn.Enqueue(subchannelQ.ap_min.BCDValue); // M(abs)
|
||||
DataIn.Enqueue(subchannelQ.ap_sec.BCDValue); // S(abs)
|
||||
DataIn.Enqueue(subchannelQ.ap_frame.BCDValue); // F(abs)
|
||||
|
||||
SetPhase(BusPhase_DataIn);
|
||||
}
|
||||
|
@ -525,7 +529,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
{
|
||||
DataIn.Clear();
|
||||
DataIn.Enqueue(0x01);
|
||||
DataIn.Enqueue(((byte)disc.TOC.Sessions[0].Tracks.Count).BinToBCD());
|
||||
DataIn.Enqueue(((byte)disc.Structure.Sessions[0].Tracks.Count).BinToBCD());
|
||||
SetPhase(BusPhase_DataIn);
|
||||
break;
|
||||
}
|
||||
|
@ -546,7 +550,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
case 2: // Return starting position of specified track in MSF format
|
||||
{
|
||||
int track = CommandBuffer[2].BCDtoBin();
|
||||
var tracks = disc.TOC.Sessions[0].Tracks;
|
||||
var tracks = disc.Structure.Sessions[0].Tracks;
|
||||
if (CommandBuffer[2] > 0x99)
|
||||
throw new Exception("invalid track number BCD request... is something I need to handle?");
|
||||
if (track == 0) track = 1;
|
||||
|
@ -556,7 +560,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
int lbaPos;
|
||||
|
||||
if (track > tracks.Count)
|
||||
lbaPos = disc.TOC.Sessions[0].length_aba - 150;
|
||||
lbaPos = disc.Structure.Sessions[0].length_aba - 150;
|
||||
else
|
||||
lbaPos = tracks[track].Indexes[1].aba - 150;
|
||||
|
||||
|
@ -568,7 +572,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
DataIn.Enqueue(s.BinToBCD());
|
||||
DataIn.Enqueue(f.BinToBCD());
|
||||
|
||||
if (track > tracks.Count || disc.TOC.Sessions[0].Tracks[track].TrackType == ETrackType.Audio)
|
||||
if (track > tracks.Count || disc.Structure.Sessions[0].Tracks[track].TrackType == ETrackType.Audio)
|
||||
DataIn.Enqueue(0);
|
||||
else
|
||||
DataIn.Enqueue(4);
|
||||
|
|
|
@ -627,7 +627,7 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn
|
|||
{
|
||||
// this stuff from yabause's cdbase.c. don't ask me to explain it
|
||||
|
||||
var TOC = CD.ReadTOC();
|
||||
var TOC = CD.ReadStructure();
|
||||
int[] rTOC = new int[102];
|
||||
var ses = TOC.Sessions[0];
|
||||
int ntrk = ses.Tracks.Count;
|
||||
|
|
|
@ -316,7 +316,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
|
|||
|
||||
ret.readcallback = cd_callback_handle = new LibGPGX.cd_read_cb(CDRead);
|
||||
|
||||
var ses = CD.TOC.Sessions[0];
|
||||
var ses = CD.Structure.Sessions[0];
|
||||
int ntrack = ses.Tracks.Count;
|
||||
|
||||
// bet you a dollar this is all wrong
|
||||
|
@ -325,7 +325,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
|
|||
if (i < ntrack)
|
||||
{
|
||||
ret.tracks[i].start = ses.Tracks[i].Indexes[1].aba - 150;
|
||||
ret.tracks[i].end = ses.Tracks[i].length_aba + ret.tracks[i].start;
|
||||
ret.tracks[i].end = ses.Tracks[i].LengthInSectors + ret.tracks[i].start;
|
||||
if (i == ntrack - 1)
|
||||
{
|
||||
ret.end = ret.tracks[i].end;
|
||||
|
|
|
@ -46,7 +46,10 @@
|
|||
<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" />
|
||||
|
@ -62,12 +65,16 @@
|
|||
<Compile Include="Disc.API.cs" />
|
||||
<Compile Include="Disc.cs" />
|
||||
<Compile Include="Disc.ID.cs" />
|
||||
<Compile Include="DiscTOC.cs" />
|
||||
<Compile Include="DiscTypes.cs" />
|
||||
<Compile Include="DiscUtils.cs" />
|
||||
<Compile Include="ECM.cs" />
|
||||
<Compile Include="GPL_ECM.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\svnrev.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>
|
||||
|
@ -80,6 +87,10 @@
|
|||
<Name>BizHawk.Emulation.Common</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="docs\notes.txt" />
|
||||
<Content Include="docs\todo.txt" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>"$(SolutionDir)subwcrev.bat" "$(ProjectDir)"</PreBuildEvent>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
partial class Disc
|
||||
{
|
||||
internal 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;
|
||||
|
||||
BufferedStream fs;
|
||||
public void Dispose()
|
||||
{
|
||||
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
|
||||
|
||||
//NOTE: wouldnt very large buffering create stuttering? this would depend on how it's implemented.
|
||||
//really, we need a smarter asynchronous read-ahead buffer. that requires substantially more engineering, some kind of 'DiscUniverse' of carefully managed threads and such.
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,9 @@ 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?
|
||||
/// </summary>
|
||||
class Blob_WaveFile : IBlob
|
||||
{
|
||||
[Serializable]
|
||||
|
@ -19,7 +22,53 @@ 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;
|
||||
|
||||
BufferedStream fs;
|
||||
public void Dispose()
|
||||
{
|
||||
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 void Load(byte[] waveData)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Presently, an IBlob doesn't need to work multithreadedly. It's quite an onerous demand.
|
||||
/// This should probably be managed by the Disc class somehow, or by the user making another Disc.
|
||||
/// </summary>
|
||||
public interface IBlob : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// what a weird parameter order. normally the dest buffer would be first. weird.
|
||||
/// </summary>
|
||||
/// <param name="byte_pos">location in the blob to read from</param>
|
||||
/// <param name="buffer">destination buffer for read data</param>
|
||||
/// <param name="offset">offset into destination buffer</param>
|
||||
/// <param name="count">amount to read</param>
|
||||
int Read(long byte_pos, byte[] buffer, int offset, int count);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,509 @@
|
|||
namespace BizHawk.Emulation.DiscSystem
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//check out ccd2iso linux program?
|
||||
//https://wiki.archlinux.org/index.php/CD_Burning#TOC.2FCUE.2FBIN_for_mixed-mode_disks advice here
|
||||
//also referencing mednafen sources
|
||||
|
||||
//TODO - copy mednafen sanity checking
|
||||
//TODO - check subcode vs TOC (warnings if different)
|
||||
//TODO - check TOC vs tracks (warnings if different)
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
//TBD CCD format
|
||||
//check out ccd2iso linux program?
|
||||
//https://wiki.archlinux.org/index.php/CD_Burning#TOC.2FCUE.2FBIN_for_mixed-mode_disks advice here
|
||||
public class CCDFormat
|
||||
public class CCD_Format
|
||||
{
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents a CCD file, faithfully. Minimal interpretation of the data happens.
|
||||
/// Currently the [TRACK] sections aren't parsed, though.
|
||||
/// </summary>
|
||||
public class CCDFile
|
||||
{
|
||||
/// <summary>
|
||||
/// which version CCD file this came from. We hope it shouldn't affect the semantics of anything else in here, but just in case..
|
||||
/// </summary>
|
||||
public int Version;
|
||||
|
||||
/// <summary>
|
||||
/// this is probably a 0 or 1 bool
|
||||
/// </summary>
|
||||
public int DataTracksScrambled;
|
||||
|
||||
/// <summary>
|
||||
/// ???
|
||||
/// </summary>
|
||||
public int CDTextLength;
|
||||
|
||||
/// <summary>
|
||||
/// The [Session] sections
|
||||
/// </summary>
|
||||
public List<CCDSession> Sessions = new List<CCDSession>();
|
||||
|
||||
/// <summary>
|
||||
/// The [Entry] sctions
|
||||
/// </summary>
|
||||
public List<CCDTocEntry> TOCEntries = new List<CCDTocEntry>();
|
||||
|
||||
/// <summary>
|
||||
/// The [TRACK] sections
|
||||
/// </summary>
|
||||
public List<CCDTrack> Tracks = new List<CCDTrack>();
|
||||
|
||||
/// <summary>
|
||||
/// The [TRACK] sections, indexed by number
|
||||
/// </summary>
|
||||
public Dictionary<int, CCDTrack> TracksByNumber = new Dictionary<int, CCDTrack>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an [Entry] section from a CCD file
|
||||
/// </summary>
|
||||
public class CCDTocEntry
|
||||
{
|
||||
public CCDTocEntry(int entryNum)
|
||||
{
|
||||
EntryNum = entryNum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// these should be 0-indexed
|
||||
/// </summary>
|
||||
public int EntryNum;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// the CCD specifies this, but it isnt in the actual disc data as such, it is encoded some other (likely difficult to extract) way and thats why CCD puts it here
|
||||
/// </summary>
|
||||
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.
|
||||
/// </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.
|
||||
/// </summary>
|
||||
public int PLBA;
|
||||
|
||||
//these correspond pretty directly to values in the Q subchannel fields
|
||||
public int Control;
|
||||
public int ADR;
|
||||
public int TrackNo;
|
||||
public int Point;
|
||||
public int AMin;
|
||||
public int ASec;
|
||||
public int AFrame;
|
||||
public int Zero;
|
||||
public int PMin;
|
||||
public int PSec;
|
||||
public int PFrame;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a [Track] section from a CCD file
|
||||
/// </summary>
|
||||
public class CCDTrack
|
||||
{
|
||||
public CCDTrack(int number)
|
||||
{
|
||||
this.Number = number;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// note: this is 1-indexed
|
||||
/// </summary>
|
||||
public int Number;
|
||||
|
||||
/// <summary>
|
||||
/// The specified data mode
|
||||
/// </summary>
|
||||
public int Mode;
|
||||
|
||||
/// <summary>
|
||||
/// The indexes specified for the track (these are 0-indexed)
|
||||
/// </summary>
|
||||
public Dictionary<int, int> Indexes = new Dictionary<int, int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a [Session] section from a CCD file
|
||||
/// </summary>
|
||||
public class CCDSession
|
||||
{
|
||||
public CCDSession(int number)
|
||||
{
|
||||
this.Number = number;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// note: this is 1-indexed.
|
||||
/// </summary>
|
||||
public int Number;
|
||||
|
||||
//Not sure what the default should be.. ive only seen mode=2
|
||||
public int PregapMode;
|
||||
|
||||
/// <summary>
|
||||
/// this is probably a 0 or 1 bool
|
||||
/// </summary>
|
||||
public int PregapSubcode;
|
||||
}
|
||||
|
||||
public class CCDParseException : Exception
|
||||
{
|
||||
public CCDParseException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
class CCDSection : Dictionary<string,int>
|
||||
{
|
||||
public string Name;
|
||||
public int FetchOrDefault(int def, string key)
|
||||
{
|
||||
TryGetValue(key, out def);
|
||||
return def;
|
||||
}
|
||||
public int FetchOrFail(string key)
|
||||
{
|
||||
int ret;
|
||||
if(!TryGetValue(key, out ret))
|
||||
throw new CCDParseException("Malformed or unexpected CCD format: missing required [Entry] key: " + key);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
List<CCDSection> ParseSections(Stream stream)
|
||||
{
|
||||
List<CCDSection> sections = new List<CCDSection>();
|
||||
|
||||
//TODO - do we need to attempt to parse out the version tag in a first pass?
|
||||
//im doing this from a version 3 example
|
||||
|
||||
StreamReader sr = new StreamReader(stream);
|
||||
CCDSection currSection = null;
|
||||
for (; ; )
|
||||
{
|
||||
var line = sr.ReadLine();
|
||||
if (line == null) break;
|
||||
if (line == "") continue;
|
||||
if (line.StartsWith("["))
|
||||
{
|
||||
currSection = new CCDSection();
|
||||
currSection.Name = line.Trim('[', ']').ToUpper();
|
||||
sections.Add(currSection);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parts = line.Split('=');
|
||||
if (parts.Length != 2)
|
||||
throw new CCDParseException("Malformed or unexpected CCD format: parsing item into two parts");
|
||||
int val;
|
||||
if (parts[1].StartsWith("0x") || parts[1].StartsWith("0X"))
|
||||
val = int.Parse(parts[1].Substring(2), NumberStyles.HexNumber);
|
||||
else val = int.Parse(parts[1]);
|
||||
currSection[parts[0].ToUpper()] = val;
|
||||
}
|
||||
} //loop until lines exhausted
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
int PreParseIntegrityCheck(List<CCDSection> sections)
|
||||
{
|
||||
if (sections.Count == 0) throw new CCDParseException("Malformed CCD format: no sections");
|
||||
|
||||
//we need at least a CloneCD and Disc section
|
||||
if (sections.Count < 2) throw new CCDParseException("Malformed CCD format: insufficient sections");
|
||||
|
||||
var ccdSection = sections[0];
|
||||
if (ccdSection.Name != "CLONECD")
|
||||
throw new CCDParseException("Malformed CCD format: confusing first section name");
|
||||
|
||||
if (!ccdSection.ContainsKey("VERSION"))
|
||||
throw new CCDParseException("Malformed CCD format: missing version in CloneCD section");
|
||||
|
||||
if(sections[1].Name != "DISC")
|
||||
throw new CCDParseException("Malformed CCD format: section[1] isn't [Disc]");
|
||||
|
||||
int version = ccdSection["VERSION"];
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a CCD file contained in the provided stream
|
||||
/// </summary>
|
||||
public CCDFile ParseFrom(Stream stream)
|
||||
{
|
||||
CCDFile ccdf = new CCDFile();
|
||||
|
||||
var sections = ParseSections(stream);
|
||||
ccdf.Version = PreParseIntegrityCheck(sections);
|
||||
|
||||
var discSection = sections[1];
|
||||
int nTocEntries = discSection["TOCENTRIES"]; //its conceivable that this could be missing
|
||||
int nSessions = discSection["SESSIONS"]; //its conceivable that this could be missing
|
||||
ccdf.DataTracksScrambled = discSection.FetchOrDefault(0, "DATATRACKSSCRAMBLED");
|
||||
ccdf.CDTextLength = discSection.FetchOrDefault(0, "CDTEXTLENGTH");
|
||||
|
||||
if (ccdf.DataTracksScrambled==1) throw new CCDParseException("Malformed CCD format: DataTracksScrambled=1 not supported. Please report this, so we can understand what it means.");
|
||||
|
||||
for (int i = 2; i < sections.Count; i++)
|
||||
{
|
||||
var section = sections[i];
|
||||
if (section.Name.StartsWith("SESSION"))
|
||||
{
|
||||
int sesnum = int.Parse(section.Name.Split(' ')[1]);
|
||||
CCDSession session = new CCDSession(sesnum);
|
||||
ccdf.Sessions.Add(session);
|
||||
if (sesnum != ccdf.Sessions.Count)
|
||||
throw new CCDParseException("Malformed CCD format: wrong session number in sequence");
|
||||
session.PregapMode = section.FetchOrDefault(0, "PREGAPMODE");
|
||||
session.PregapSubcode = section.FetchOrDefault(0, "PREGAPSUBC");
|
||||
}
|
||||
else if (section.Name.StartsWith("ENTRY"))
|
||||
{
|
||||
int entryNum = int.Parse(section.Name.Split(' ')[1]);
|
||||
CCDTocEntry entry = new CCDTocEntry(entryNum);
|
||||
ccdf.TOCEntries.Add(entry);
|
||||
|
||||
entry.Session = section.FetchOrFail("SESSION");
|
||||
entry.Point = section.FetchOrFail("POINT");
|
||||
entry.ADR = section.FetchOrFail("ADR");
|
||||
entry.Control = section.FetchOrFail("CONTROL");
|
||||
entry.TrackNo = section.FetchOrFail("TRACKNO");
|
||||
entry.AMin = section.FetchOrFail("AMIN");
|
||||
entry.ASec = section.FetchOrFail("ASEC");
|
||||
entry.AFrame = section.FetchOrFail("AFRAME");
|
||||
entry.ALBA = section.FetchOrFail("ALBA");
|
||||
entry.Zero = section.FetchOrFail("ZERO");
|
||||
entry.PMin = section.FetchOrFail("PMIN");
|
||||
entry.PSec = section.FetchOrFail("PSEC");
|
||||
entry.PFrame = section.FetchOrFail("PFRAME");
|
||||
entry.PLBA = section.FetchOrFail("PLBA");
|
||||
|
||||
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)
|
||||
throw new CCDParseException("Warning: inconsistency in CCD PLBA vs computed P MSF");
|
||||
|
||||
if(entry.Session != 1)
|
||||
throw new CCDParseException("Malformed CCD format: not yet supporting multi-session files");
|
||||
}
|
||||
else if (section.Name.StartsWith("TRACK"))
|
||||
{
|
||||
int entryNum = int.Parse(section.Name.Split(' ')[1]);
|
||||
CCDTrack track = new CCDTrack(entryNum);
|
||||
ccdf.Tracks.Add(track);
|
||||
ccdf.TracksByNumber[entryNum] = track;
|
||||
foreach (var kvp in section)
|
||||
{
|
||||
if (kvp.Key == "MODE")
|
||||
track.Mode = kvp.Value;
|
||||
if (kvp.Key.StartsWith("INDEX"))
|
||||
{
|
||||
int inum = int.Parse(kvp.Key.Split(' ')[1]);
|
||||
track.Indexes[inum] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} //sections loop
|
||||
|
||||
return ccdf;
|
||||
}
|
||||
|
||||
public class LoadResults
|
||||
{
|
||||
public List<RawTOCEntry> RawTOCEntries;
|
||||
public CCDFile ParsedCCDFile;
|
||||
public bool Valid;
|
||||
public Exception FailureException;
|
||||
public string ImgPath;
|
||||
public string SubPath;
|
||||
public string CcdPath;
|
||||
public int NumImgSectors;
|
||||
}
|
||||
|
||||
public static LoadResults LoadCCDPath(string path)
|
||||
{
|
||||
LoadResults ret = new LoadResults();
|
||||
ret.CcdPath = path;
|
||||
ret.ImgPath = Path.ChangeExtension(path, ".img");
|
||||
ret.SubPath = Path.ChangeExtension(path, ".sub");
|
||||
try
|
||||
{
|
||||
if(!File.Exists(path)) throw new CCDParseException("Malformed CCD format: nonexistent CCD file!");
|
||||
if (!File.Exists(ret.ImgPath)) throw new CCDParseException("Malformed CCD format: nonexistent IMG file!");
|
||||
if (!File.Exists(ret.SubPath)) throw new CCDParseException("Malformed CCD format: nonexistent SUB file!");
|
||||
|
||||
//quick check of .img and .sub sizes
|
||||
long imgLen = new FileInfo(ret.ImgPath).Length;
|
||||
long subLen = new FileInfo(ret.SubPath).Length;
|
||||
if(imgLen % 2352 != 0) throw new CCDParseException("Malformed CCD format: IMG file length not multiple of 2352");
|
||||
ret.NumImgSectors = (int)(imgLen / 2352);
|
||||
if (subLen != ret.NumImgSectors * 96) throw new CCDParseException("Malformed CCD format: SUB file length not matching IMG");
|
||||
|
||||
CCDFile ccdf;
|
||||
using (var infCCD = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
ccdf = new CCD_Format().ParseFrom(infCCD);
|
||||
|
||||
ret.ParsedCCDFile = ccdf;
|
||||
|
||||
ret.Valid = true;
|
||||
}
|
||||
catch (CCDParseException ex)
|
||||
{
|
||||
ret.FailureException = ex;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a CCD at the specified path to a Disc object
|
||||
/// </summary>
|
||||
public Disc LoadCCDToDisc(string ccdPath)
|
||||
{
|
||||
var loadResults = LoadCCDPath(ccdPath);
|
||||
if (!loadResults.Valid)
|
||||
throw loadResults.FailureException;
|
||||
|
||||
Disc disc = new Disc();
|
||||
|
||||
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);
|
||||
|
||||
//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)
|
||||
{
|
||||
var q = new SubchannelQ
|
||||
{
|
||||
q_status = SubchannelQ.ComputeStatus(entry.ADR, (EControlQ)(entry.Control & 0xF)),
|
||||
q_tno = (byte)entry.TrackNo,
|
||||
q_index = (byte)entry.Point,
|
||||
min = BCD2.FromDecimal(entry.AMin),
|
||||
sec = BCD2.FromDecimal(entry.ASec),
|
||||
frame = BCD2.FromDecimal(entry.AFrame),
|
||||
zero = (byte)entry.Zero,
|
||||
ap_min = BCD2.FromDecimal(entry.PMin),
|
||||
ap_sec = BCD2.FromDecimal(entry.PSec),
|
||||
ap_frame = BCD2.FromDecimal(entry.PFrame),
|
||||
};
|
||||
|
||||
//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();
|
||||
for (int i = 0; i < 150; i++)
|
||||
{
|
||||
var se = new SectorEntry(leadin_sector_zero);
|
||||
disc.Sectors.Add(se);
|
||||
se.SubcodeSector = leadin_subcode_zero;
|
||||
}
|
||||
|
||||
//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)
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -48,11 +48,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
public void PlayTrack(int track)
|
||||
{
|
||||
if (track < 1 || track > Disc.TOC.Sessions[0].Tracks.Count)
|
||||
if (track < 1 || track > Disc.Structure.Sessions[0].Tracks.Count)
|
||||
return;
|
||||
|
||||
StartLBA = Disc.TOC.Sessions[0].Tracks[track - 1].Indexes[1].aba - 150;
|
||||
EndLBA = StartLBA + Disc.TOC.Sessions[0].Tracks[track - 1].length_aba;
|
||||
StartLBA = Disc.Structure.Sessions[0].Tracks[track - 1].Indexes[1].aba - 150;
|
||||
EndLBA = StartLBA + Disc.Structure.Sessions[0].Tracks[track - 1].LengthInSectors;
|
||||
PlayingTrack = track;
|
||||
CurrentSector = StartLBA;
|
||||
SectorOffset = 0;
|
||||
|
@ -64,12 +64,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
public void PlayStartingAtLba(int lba)
|
||||
{
|
||||
var point = Disc.TOC.SeekPoint(lba);
|
||||
var point = Disc.Structure.SeekPoint(lba);
|
||||
if (point == null || point.Track == null) return;
|
||||
|
||||
PlayingTrack = point.TrackNum;
|
||||
StartLBA = lba;
|
||||
EndLBA = point.Track.Indexes[1].aba + point.Track.length_aba - 150;
|
||||
EndLBA = point.Track.Indexes[1].aba + point.Track.LengthInSectors - 150;
|
||||
|
||||
CurrentSector = StartLBA;
|
||||
SectorOffset = 0;
|
||||
|
|
|
@ -7,6 +7,64 @@ using System.Collections.Generic;
|
|||
|
||||
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>
|
||||
|
@ -53,8 +111,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
//TODO - add cue directory to CueBinPrefs???? could make things cleaner...
|
||||
|
||||
var session = new DiscTOC.Session {num = 1};
|
||||
TOC.Sessions.Add(session);
|
||||
Structure = new DiscStructure();
|
||||
var session = new DiscStructure.Session {num = 1};
|
||||
Structure.Sessions.Add(session);
|
||||
var pregap_sector = new Sector_Zero();
|
||||
|
||||
int curr_track = 1;
|
||||
|
@ -190,7 +249,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
//validate that the first index in the file is 00:00:00
|
||||
if (cue_file.Tracks[0].Indexes[0].Timestamp.ABA != 0) throw new Cue.CueBrokenException("`The first index of a blob must start at 00:00:00.`");
|
||||
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:
|
||||
|
@ -211,14 +270,19 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//check integrity of track sequence and setup data structures
|
||||
//TODO - check for skipped tracks in cue parser instead
|
||||
if (cue_track.TrackNum != curr_track) throw new Cue.CueBrokenException("Found a cue with skipped tracks");
|
||||
var toc_track = new DiscTOC.Track();
|
||||
toc_track.num = curr_track;
|
||||
toc_track.TrackType = cue_track.TrackType;
|
||||
var toc_track = new DiscStructure.Track();
|
||||
session.Tracks.Add(toc_track);
|
||||
toc_track.Number = curr_track;
|
||||
toc_track.TrackType = cue_track.TrackType;
|
||||
|
||||
//choose a Control value based on
|
||||
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.ABA != 0)
|
||||
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);
|
||||
|
@ -226,9 +290,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
//check whether a pregap is requested.
|
||||
//this causes empty sectors to get generated without consuming data from the blob
|
||||
if (cue_track.PreGap.ABA > 0)
|
||||
if (cue_track.PreGap.Sector > 0)
|
||||
{
|
||||
for (int i = 0; i < cue_track.PreGap.ABA; i++)
|
||||
for (int i = 0; i < cue_track.PreGap.Sector; i++)
|
||||
{
|
||||
Sectors.Add(new SectorEntry(pregap_sector));
|
||||
}
|
||||
|
@ -239,7 +303,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
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.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
|
||||
|
@ -253,11 +317,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
bool is_last_index = index == num_indexes - 1;
|
||||
|
||||
//install index into hierarchy
|
||||
var toc_index = new DiscTOC.Index {num = index};
|
||||
var toc_index = new DiscStructure.Index {Number = index};
|
||||
toc_track.Indexes.Add(toc_index);
|
||||
if (index == 0)
|
||||
{
|
||||
toc_index.aba = track_disc_pregap_aba - (cue_track.Indexes[1].Timestamp.ABA - cue_track.Indexes[0].Timestamp.ABA);
|
||||
toc_index.aba = track_disc_pregap_aba - (cue_track.Indexes[1].Timestamp.Sector - cue_track.Indexes[0].Timestamp.Sector);
|
||||
}
|
||||
else toc_index.aba = Sectors.Count;
|
||||
|
||||
|
@ -266,7 +330,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
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.ABA - blob_timestamp;
|
||||
else index_length_aba = cue_track.Indexes[index + 1].Timestamp.Sector - blob_timestamp;
|
||||
|
||||
//emit sectors
|
||||
for (int aba = 0; aba < index_length_aba; aba++)
|
||||
|
@ -327,14 +391,14 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
} //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.ABA; i++)
|
||||
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.length_aba = Sectors.Count - toc_track.Indexes[1].aba;
|
||||
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
|
||||
|
@ -350,8 +414,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//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
|
||||
TOC.length_aba = 0;
|
||||
foreach (var toc_session in TOC.Sessions)
|
||||
Structure.LengthInSectors = 0;
|
||||
foreach (var toc_session in Structure.Sessions)
|
||||
{
|
||||
var firstTrack = toc_session.Tracks[0];
|
||||
|
||||
|
@ -359,8 +423,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//firstTrack.Indexes[0].aba -= 150;
|
||||
|
||||
var lastTrack = toc_session.Tracks[toc_session.Tracks.Count - 1];
|
||||
session.length_aba = lastTrack.Indexes[1].aba + lastTrack.length_aba - firstTrack.Indexes[0].aba;
|
||||
TOC.length_aba += toc_session.length_aba;
|
||||
session.length_aba = lastTrack.Indexes[1].aba + lastTrack.LengthInSectors - firstTrack.Indexes[0].aba;
|
||||
Structure.LengthInSectors += toc_session.length_aba;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -469,6 +533,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
public CueTrackIndex(int num) { IndexNum = num; }
|
||||
public int IndexNum;
|
||||
|
||||
/// <summary>
|
||||
/// Is this an ABA or a LBA? please say.
|
||||
/// </summary>
|
||||
public Timestamp Timestamp;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,44 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
|
@ -140,8 +178,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
sealed public partial class Disc
|
||||
{
|
||||
/// <summary>
|
||||
/// Main API to read a 2352-byte sector from a disc.
|
||||
/// This starts after the mandatory pregap of 2 seconds (but what happens if there is more more?).
|
||||
/// 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)
|
||||
{
|
||||
|
@ -149,26 +186,31 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main API to read a 2048-byte sector from a disc.
|
||||
/// This starts after the mandatory pregap of 2 seconds (but what happens if there is more more?).
|
||||
/// 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 disc address space
|
||||
/// 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)
|
||||
|
@ -180,7 +222,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// reads logical data from a flat disc address space
|
||||
/// 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)
|
||||
|
@ -191,7 +233,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
READLBA_Flat_Implementation(disc_offset, buffer, offset, length, (a, b, c) => ReadLBA_2048(a, b, c), secsize, lba_buf, ref sectorHint);
|
||||
}
|
||||
|
||||
public 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)
|
||||
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)
|
||||
|
@ -221,25 +263,6 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
return Sectors[lba + 150];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified LBA's subcode (96 bytes) deinterleaved into the provided buffer.
|
||||
/// P is first 12 bytes, followed by 12 Q bytes, etc.
|
||||
/// I'm not sure what format scsi commands generally return it in.
|
||||
/// It could be this, or RAW (interleaved) which I could also supply when we need it
|
||||
/// </summary>
|
||||
public void ReadLBA_Subcode_Deinterleaved(int lba, byte[] buffer, int offset)
|
||||
{
|
||||
Array.Clear(buffer, offset, 96);
|
||||
Sectors[lba + 150].Read_SubchannelQ(buffer, offset + 12);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified LBA's subchannel Q (12 bytes) into the provided buffer
|
||||
/// </summary>
|
||||
public void ReadLBA_Subchannel_Q(int lba, byte[] buffer, int offset)
|
||||
{
|
||||
Sectors[lba + 150].Read_SubchannelQ(buffer, offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main API to determine how many LBAs are available on the disc.
|
||||
|
@ -260,11 +283,13 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public bool WasSlowLoad { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// main api for reading the TOC from a disc
|
||||
/// 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 DiscTOC ReadTOC()
|
||||
public DiscStructure ReadStructure()
|
||||
{
|
||||
return TOC;
|
||||
return Structure;
|
||||
}
|
||||
|
||||
// converts LBA to minute:second:frame format.
|
||||
|
@ -285,16 +310,17 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
// 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 TOC.Sessions[0].Tracks)
|
||||
foreach (var track in Structure.Sessions[0].Tracks)
|
||||
{
|
||||
if (track.TrackType == ETrackType.Audio)
|
||||
continue;
|
||||
|
||||
int lba_len = Math.Min(track.length_aba, 512);
|
||||
for (int s = 0; s < 512 && s < track.length_aba; s++)
|
||||
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);
|
||||
|
|
|
@ -66,411 +66,34 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
public partial class Disc : IDisposable
|
||||
{
|
||||
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>
|
||||
/// The raw TOC entries found in the lead-in track.
|
||||
/// </summary>
|
||||
public List<RawTOCEntry> RawTOCEntries = new List<RawTOCEntry>();
|
||||
|
||||
/// <summary>
|
||||
/// Presently, an IBlob doesn't need to work multithreadedly. It's quite an onerous demand.
|
||||
/// This should probably be managed by the Disc class somehow, or by the user making another Disc.
|
||||
/// The DiscTOCRaw corresponding to the RawTOCEntries
|
||||
/// </summary>
|
||||
public interface IBlob : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// what a weird parameter order. normally the dest buffer would be first. weird.
|
||||
/// </summary>
|
||||
/// <param name="byte_pos">location in the blob to read from</param>
|
||||
/// <param name="buffer">destination buffer for read data</param>
|
||||
/// <param name="offset">offset into destination buffer</param>
|
||||
/// <param name="count">amount to read</param>
|
||||
int Read(long byte_pos, byte[] buffer, int offset, int count);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
BufferedStream fs;
|
||||
public void Dispose()
|
||||
{
|
||||
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 DiscTOCRaw TOCRaw;
|
||||
|
||||
/// <summary>
|
||||
/// this ISector is dumb and only knows how to drag chunks off a source blob
|
||||
/// The DiscStructure corresponding the the TOCRaw
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
return Blob.Read(Offset, buffer, offset, 2048);
|
||||
}
|
||||
}
|
||||
public DiscStructure Structure;
|
||||
|
||||
/// <summary>
|
||||
/// this ISector always returns zeroes
|
||||
/// The blobs mounted by this disc for supplying binary content
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------ replaced by Blob_ZeroPadAdapter
|
||||
///// <summary>
|
||||
///// this ISector adapts another ISector by always returning zeroes
|
||||
///// TODO I dont like the way this works. I think blobs should get adapted instead to zero-pad to a certain length.
|
||||
///// </summary>
|
||||
//class Sector_ZeroPad : ISector
|
||||
//{
|
||||
// public ISector BaseSector;
|
||||
// public int BaseLength;
|
||||
// public int Read(byte[] buffer, int offset)
|
||||
// {
|
||||
// return _Read(buffer, offset, 2352);
|
||||
// }
|
||||
// public int Read_2048(byte[] buffer, int offset)
|
||||
// {
|
||||
// return _Read(buffer, offset, 2352);
|
||||
// }
|
||||
// int _Read(byte[] buffer, int offset, int amount)
|
||||
// {
|
||||
// int read = BaseSector.Read(buffer, offset);
|
||||
// if(read < BaseLength) return read;
|
||||
// for (int i = BaseLength; i < amount; i++)
|
||||
// buffer[offset + i] = 0;
|
||||
// return amount;
|
||||
// }
|
||||
//}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
private static byte BCD_Byte(byte val)
|
||||
{
|
||||
byte ret = (byte)(val % 10);
|
||||
ret += (byte)(16 * (val / 10));
|
||||
return ret;
|
||||
}
|
||||
|
||||
//a blob that also has an ECM cache associated with it. maybe one day.
|
||||
class ECMCacheBlob
|
||||
{
|
||||
public ECMCacheBlob(IBlob blob)
|
||||
{
|
||||
BaseBlob = blob;
|
||||
}
|
||||
public IBlob BaseBlob;
|
||||
}
|
||||
|
||||
/// <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 = BCD_Byte(aba_min);
|
||||
bcd_aba_sec = BCD_Byte(aba_sec);
|
||||
bcd_aba_frac = BCD_Byte(aba_frac);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//this is a physical 2352 byte sector.
|
||||
public class SectorEntry
|
||||
{
|
||||
public SectorEntry(ISector sec) { Sector = sec; }
|
||||
public ISector Sector;
|
||||
|
||||
//todo - add some PARAMETER fields to this, 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
|
||||
|
||||
//q-subchannel stuff. can be returned directly, or built into the entire subcode sector if you want
|
||||
|
||||
/// <summary>
|
||||
/// ADR and CONTROL
|
||||
/// </summary>
|
||||
public byte q_status;
|
||||
|
||||
/// <summary>
|
||||
/// BCD indications of the current track number and index
|
||||
/// </summary>
|
||||
public BCD2 q_tno, q_index;
|
||||
|
||||
/// <summary>
|
||||
/// track-relative timestamp
|
||||
/// </summary>
|
||||
public BCD2 q_min, q_sec, q_frame;
|
||||
/// <summary>
|
||||
/// absolute timestamp
|
||||
/// </summary>
|
||||
public BCD2 q_amin, q_asec, q_aframe;
|
||||
|
||||
public void Read_SubchannelQ(byte[] buffer, int offset)
|
||||
{
|
||||
buffer[offset + 0] = q_status;
|
||||
buffer[offset + 1] = q_tno.BCDValue;
|
||||
buffer[offset + 2] = q_index.BCDValue;
|
||||
buffer[offset + 3] = q_min.BCDValue;
|
||||
buffer[offset + 4] = q_sec.BCDValue;
|
||||
buffer[offset + 5] = q_frame.BCDValue;
|
||||
buffer[offset + 6] = 0;
|
||||
buffer[offset + 7] = q_amin.BCDValue;
|
||||
buffer[offset + 8] = q_asec.BCDValue;
|
||||
buffer[offset + 9] = q_aframe.BCDValue;
|
||||
|
||||
ushort crc16 = CRC16_CCITT.Calculate(buffer, 0, 10);
|
||||
//CRC is stored inverted and big endian
|
||||
buffer[offset + 10] = (byte)(~(crc16 >> 8));
|
||||
buffer[offset + 11] = (byte)(~(crc16));
|
||||
}
|
||||
}
|
||||
|
||||
public List<IBlob> Blobs = new List<IBlob>();
|
||||
|
||||
/// <summary>
|
||||
/// The sectors on the disc
|
||||
/// </summary>
|
||||
public List<SectorEntry> Sectors = new List<SectorEntry>();
|
||||
public DiscTOC TOC = new DiscTOC();
|
||||
|
||||
public Disc()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -494,46 +117,12 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
CueFileResolver["xarp.barp.marp.farp"] = isoPath;
|
||||
cue.LoadFromString(isoCueWrapper);
|
||||
FromCueInternal(cue, cueDir, new CueBinPrefs());
|
||||
|
||||
//var session = new DiscTOC.Session();
|
||||
//session.num = 1;
|
||||
//TOC.Sessions.Add(session);
|
||||
//var track = new DiscTOC.Track();
|
||||
//track.num = 1;
|
||||
//session.Tracks.Add(track);
|
||||
//var index = new DiscTOC.Index();
|
||||
//index.num = 0;
|
||||
//track.Indexes.Add(index);
|
||||
//index = new DiscTOC.Index();
|
||||
//index.num = 1;
|
||||
//track.Indexes.Add(index);
|
||||
|
||||
//var fiIso = new FileInfo(isoPath);
|
||||
//Blob_RawFile blob = new Blob_RawFile();
|
||||
//blob.PhysicalPath = fiIso.FullName;
|
||||
//Blobs.Add(blob);
|
||||
//int num_aba = (int)(fiIso.Length / 2048);
|
||||
//track.length_aba = num_aba;
|
||||
//if (fiIso.Length % 2048 != 0)
|
||||
// throw new InvalidOperationException("invalid iso file (size not multiple of 2048)");
|
||||
////TODO - handle this with Final Fantasy 9 cd1.iso
|
||||
|
||||
//var ecmCacheBlob = new ECMCacheBlob(blob);
|
||||
//for (int i = 0; i < num_aba; i++)
|
||||
//{
|
||||
// Sector_Mode1_2048 sector = new Sector_Mode1_2048(i+150);
|
||||
// sector.Blob = ecmCacheBlob;
|
||||
// sector.Offset = i * 2048;
|
||||
// Sectors.Add(new SectorEntry(sector));
|
||||
//}
|
||||
|
||||
//TOC.AnalyzeLengthsFromIndexLengths();
|
||||
}
|
||||
|
||||
|
||||
public CueBin DumpCueBin(string baseName, CueBinPrefs prefs)
|
||||
{
|
||||
if (TOC.Sessions.Count > 1)
|
||||
if (Structure.Sessions.Count > 1)
|
||||
throw new NotSupportedException("can't dump cue+bin with more than 1 session yet");
|
||||
|
||||
CueBin ret = new CueBin();
|
||||
|
@ -543,14 +132,14 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
if (!prefs.OneBlobPerTrack)
|
||||
{
|
||||
//this is the preferred mode of dumping things. we will always write full sectors.
|
||||
string cue = TOC.GenerateCUE_OneBin(prefs);
|
||||
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 < TOC.length_aba; i++)
|
||||
for (int i = 150; i < Structure.LengthInSectors; i++)
|
||||
{
|
||||
bfd.abas.Add(i);
|
||||
bfd.aba_zeros.Add(false);
|
||||
|
@ -558,15 +147,15 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
}
|
||||
else
|
||||
{
|
||||
//we build our own cue here (unlike above) because we need to build the cue and the output dat aat the same time
|
||||
//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 < TOC.Sessions[0].Tracks.Count; i++)
|
||||
for (int i = 0; i < Structure.Sessions[0].Tracks.Count; i++)
|
||||
{
|
||||
var track = TOC.Sessions[0].Tracks[i];
|
||||
var track = Structure.Sessions[0].Tracks[i];
|
||||
var bfd = new CueBin.BinFileDescriptor
|
||||
{
|
||||
name = baseName + string.Format(" (Track {0:D2}).bin", track.num),
|
||||
name = baseName + string.Format(" (Track {0:D2}).bin", track.Number),
|
||||
SectorSize = Cue.BINSectorSizeForTrackType(track.TrackType)
|
||||
};
|
||||
ret.bins.Add(bfd);
|
||||
|
@ -575,7 +164,7 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
//skip the mandatory track 1 pregap! cue+bin files do not contain it
|
||||
if (i == 0) aba = 150;
|
||||
|
||||
for (; aba < track.length_aba; aba++)
|
||||
for (; aba < track.LengthInSectors; aba++)
|
||||
{
|
||||
int thisaba = track.Indexes[0].aba + aba;
|
||||
bfd.abas.Add(thisaba);
|
||||
|
@ -583,11 +172,11 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
}
|
||||
sbCue.AppendFormat("FILE \"{0}\" BINARY\n", bfd.name);
|
||||
|
||||
sbCue.AppendFormat(" TRACK {0:D2} {1}\n", track.num, Cue.TrackTypeStringForTrackType(track.TrackType));
|
||||
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.num == 0 && index.aba == track.Indexes[1].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
|
||||
}
|
||||
|
@ -599,7 +188,7 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
{
|
||||
//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.num, new Timestamp(x).Value);
|
||||
sbCue.AppendFormat(" INDEX {0:D2} {1}\n", index.Number, new Timestamp(x).Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -610,32 +199,22 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NOT USED RIGHT NOW. AMBIGUOUS, ANYWAY.
|
||||
/// "bin" is an ill-defined concept.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
void DumpBin_2352(string binPath)
|
||||
{
|
||||
byte[] temp = new byte[2352];
|
||||
//a cue's bin probably doesn't contain the first 150 sectors, so skip it
|
||||
using (FileStream fs = new FileStream(binPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
for (int i = 150; i < Sectors.Count; i++)
|
||||
{
|
||||
ReadLBA_2352(i, temp, 0);
|
||||
fs.Write(temp, 0, 2352);
|
||||
}
|
||||
}
|
||||
|
||||
public static Disc FromCuePath(string cuePath, CueBinPrefs prefs)
|
||||
{
|
||||
var ret = new Disc();
|
||||
ret.FromCuePathInternal(cuePath, prefs);
|
||||
ret.TOC.GeneratePoints();
|
||||
ret.PopulateQSubchannel();
|
||||
ret.Structure.Synthesize_TOCPointsFromSessions();
|
||||
ret.Synthesize_SubcodeFromCurrentTOC();
|
||||
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>
|
||||
|
@ -643,55 +222,59 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
{
|
||||
var ret = new Disc();
|
||||
ret.FromIsoPathInternal(isoPath);
|
||||
ret.TOC.GeneratePoints();
|
||||
ret.PopulateQSubchannel();
|
||||
ret.Structure.Synthesize_TOCPointsFromSessions();
|
||||
ret.Synthesize_SubcodeFromCurrentTOC();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates subchannel Q data track for this disc
|
||||
/// Creates the subcode (really, just subchannel Q) for this disc from its current TOC.
|
||||
/// Depends on the TOCPoints existing in the structure
|
||||
/// </summary>
|
||||
void PopulateQSubchannel()
|
||||
void Synthesize_SubcodeFromCurrentTOC()
|
||||
{
|
||||
int aba = 0;
|
||||
int dpIndex = 0;
|
||||
|
||||
while (aba < Sectors.Count)
|
||||
{
|
||||
if (dpIndex < TOC.Points.Count - 1)
|
||||
if (dpIndex < Structure.Points.Count - 1)
|
||||
{
|
||||
if (aba >= TOC.Points[dpIndex + 1].ABA)
|
||||
if (aba >= Structure.Points[dpIndex + 1].ABA)
|
||||
{
|
||||
dpIndex++;
|
||||
}
|
||||
}
|
||||
var dp = TOC.Points[dpIndex];
|
||||
var dp = Structure.Points[dpIndex];
|
||||
|
||||
var se = Sectors[aba];
|
||||
|
||||
int control = 0;
|
||||
//choose a control byte depending on whether this is an audio or data track
|
||||
if(dp.Track.TrackType == ETrackType.Audio)
|
||||
control = (int)Q_Control.StereoNoPreEmph;
|
||||
else control = (int)Q_Control.DataUninterrupted;
|
||||
|
||||
EControlQ control = EControlQ.None;
|
||||
|
||||
//we always use ADR=1 (mode-1 q block)
|
||||
//this could be more sophisticated but it is almost useless for emulation (only useful for catalog/ISRC numbers)
|
||||
int adr = 1;
|
||||
se.q_status = (byte)(adr | (control << 4));
|
||||
se.q_tno = BCD2.FromDecimal(dp.TrackNum);
|
||||
se.q_index = BCD2.FromDecimal(dp.IndexNum);
|
||||
|
||||
SubchannelQ sq = new SubchannelQ();
|
||||
sq.q_status = SubchannelQ.ComputeStatus(adr, control);
|
||||
sq.q_tno = (byte)dp.TrackNum;
|
||||
sq.q_index = (byte)dp.IndexNum;
|
||||
|
||||
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);
|
||||
se.q_min = BCD2.FromDecimal(track_relative_timestamp.MIN);
|
||||
se.q_sec = BCD2.FromDecimal(track_relative_timestamp.SEC);
|
||||
se.q_frame = BCD2.FromDecimal(track_relative_timestamp.FRAC);
|
||||
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);
|
||||
se.q_amin = BCD2.FromDecimal(absolute_timestamp.MIN);
|
||||
se.q_asec = BCD2.FromDecimal(absolute_timestamp.SEC);
|
||||
se.q_aframe = BCD2.FromDecimal(absolute_timestamp.FRAC);
|
||||
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);
|
||||
se.SubcodeSector = bss;
|
||||
|
||||
aba++;
|
||||
}
|
||||
|
@ -703,19 +286,6 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
int tens = Math.DivRem(n,10,out ones);
|
||||
return (byte)((tens<<4)|ones);
|
||||
}
|
||||
|
||||
private enum Q_Control
|
||||
{
|
||||
StereoNoPreEmph = 0,
|
||||
StereoPreEmph = 1,
|
||||
MonoNoPreemph = 8,
|
||||
MonoPreEmph = 9,
|
||||
DataUninterrupted = 4,
|
||||
DataIncremental = 5,
|
||||
|
||||
CopyProhibitedMask = 0,
|
||||
CopyPermittedMask = 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -755,52 +325,95 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
}
|
||||
}
|
||||
|
||||
public class Timestamp
|
||||
public struct Timestamp
|
||||
{
|
||||
/// <summary>
|
||||
/// creates timestamp of 00:00:00
|
||||
/// </summary>
|
||||
public Timestamp()
|
||||
{
|
||||
Value = "00:00:00";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates a timestamp from a string in the form mm:ss:ff
|
||||
/// </summary>
|
||||
public Timestamp(string value)
|
||||
{
|
||||
Value = 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));
|
||||
ABA = MIN * 60 * 75 + SEC * 75 + FRAC;
|
||||
Sector = MIN * 60 * 75 + SEC * 75 + FRAC;
|
||||
_value = null;
|
||||
}
|
||||
public readonly string Value;
|
||||
public readonly int MIN, SEC, FRAC, ABA;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// creates timestamp from supplied ABA
|
||||
/// creates timestamp from supplies MSF
|
||||
/// </summary>
|
||||
public Timestamp(int ABA)
|
||||
public Timestamp(int m, int s, int f)
|
||||
{
|
||||
this.ABA = ABA;
|
||||
MIN = ABA / (60 * 75);
|
||||
SEC = (ABA / 75) % 60;
|
||||
FRAC = ABA % 75;
|
||||
Value = string.Format("{0:D2}:{1:D2}:{2:D2}", MIN, SEC, FRAC);
|
||||
MIN = m;
|
||||
SEC = s;
|
||||
FRAC = f;
|
||||
Sector = MIN * 60 * 75 + SEC * 75 + FRAC;
|
||||
_value = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates timestamp from supplied SectorNumber
|
||||
/// </summary>
|
||||
public Timestamp(int SectorNumber)
|
||||
{
|
||||
this.Sector = SectorNumber;
|
||||
MIN = SectorNumber / (60 * 75);
|
||||
SEC = (SectorNumber / 75) % 60;
|
||||
FRAC = SectorNumber % 75;
|
||||
_value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
@ -935,7 +548,7 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
|
||||
public void Dump(string directory, CueBinPrefs prefs, ProgressReport progress)
|
||||
{
|
||||
byte[] subQ_temp = new byte[12];
|
||||
byte[] subcodeTemp = new byte[96];
|
||||
progress.TaskCount = 2;
|
||||
|
||||
progress.Message = "Generating Cue";
|
||||
|
@ -993,8 +606,8 @@ FILE ""xarp.barp.marp.farp"" BINARY
|
|||
//write subQ if necessary
|
||||
if (fsSubQ != null)
|
||||
{
|
||||
disc.Sectors[aba].Read_SubchannelQ(subQ_temp, 0);
|
||||
fsSubQ.Write(subQ_temp, 0, 12);
|
||||
disc.Sectors[aba].SubcodeSector.ReadSubcodeDeinterleaved(subcodeTemp, 0);
|
||||
fsSubQ.Write(subcodeTemp, 12, 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,18 +4,32 @@ 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.
|
||||
/// </summary>
|
||||
public class DiscTOC
|
||||
{
|
||||
/// <summary>
|
||||
/// Sessions contained in the disc. Right now support for anything other than 1 session is totally not working
|
||||
/// Right now support for anything other than 1 session is totally not working
|
||||
/// </summary>
|
||||
public List<Session> Sessions = new List<Session>();
|
||||
|
||||
/// <summary>
|
||||
/// this is an unfinished concept of "TOC Points" which is sometimes more convenient way for organizing the disc contents
|
||||
/// List of Points described by the TOC
|
||||
/// </summary>
|
||||
public List<TOCPoint> Points = new List<TOCPoint>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Todo - comment about what this actually means
|
||||
/// TODO - this is redundant with Sectors.Count
|
||||
|
@ -66,11 +80,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generates the Points list from the current TOC
|
||||
/// Generates the Points list from the current logical TOC
|
||||
/// </summary>
|
||||
public void GeneratePoints()
|
||||
public void SynthesizeTOCPointsFromSessions()
|
||||
{
|
||||
int num = 0;
|
||||
Points.Clear();
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a TOC entry discovered in the Q subchannel data of the lead-in track.
|
||||
/// 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
|
||||
{
|
||||
public SubchannelQ QData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main unit of organization for reading data from the disc. Represents one physical disc sector.
|
||||
/// </summary>
|
||||
public class SectorEntry
|
||||
{
|
||||
public SectorEntry(ISector sec) { Sector = sec; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the --whatsitcalled-- normal data for the sector with this
|
||||
/// </summary>
|
||||
public ISector Sector;
|
||||
|
||||
/// <summary>
|
||||
/// Access the subcode data for the sector
|
||||
/// </summary>
|
||||
public ISubcodeSector SubcodeSector;
|
||||
|
||||
//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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
public static class DiscUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// converts the given byte to a BCD value
|
||||
/// </summary>
|
||||
public static byte BCD_Byte(this byte val)
|
||||
{
|
||||
byte ret = (byte)(val % 10);
|
||||
ret += (byte)(16 * (val / 10));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
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)
|
||||
{
|
||||
return Blob.Read(Offset, 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,215 @@
|
|||
//a decent little subcode reference
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads subcode from an internally-managed buffer
|
||||
/// </summary>
|
||||
class BufferedSubcodeSector : ISubcodeSector
|
||||
{
|
||||
/// <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 byte[] SubcodeDeinterleaved = new byte[96];
|
||||
}
|
||||
|
||||
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
|
||||
/// </summary>
|
||||
public byte q_status;
|
||||
|
||||
/// <summary>
|
||||
/// normal track: BCD indications of the current track number
|
||||
/// leadin track: should be 0
|
||||
/// </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
|
||||
|
@ -43,6 +250,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
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++)
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
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
|
||||
/// </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 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)
|
||||
{
|
||||
foreach (var track in ses.Tracks)
|
||||
foreach (var index in track.Indexes)
|
||||
{
|
||||
var tp = new TOCPoint
|
||||
{
|
||||
Num = num++,
|
||||
ABA = index.aba,
|
||||
TrackNum = track.Number,
|
||||
IndexNum = index.Number,
|
||||
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.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;
|
||||
|
||||
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>
|
||||
/// 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
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
MIT LICENSE
|
||||
|
||||
https://code.google.com/p/iso-parser/
|
|
@ -1,9 +1,7 @@
|
|||
//lets leave some notes here until we've rewritten every possible thing -- error recovery, ECM file format, ISO filesystem, etc.
|
||||
|
||||
//here is a braindump of urls and stuff for future reference
|
||||
//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
|
||||
//we used this code
|
||||
//https://code.google.com/p/iso-parser/
|
||||
|
||||
//more gpl edc/ecc
|
||||
//http://code.ohloh.net/file?fid=BEZeY2fWALJKXTgY3Oe9J988ubQ&cid=Xkpw3SKt7K8&s=edc%20ecc%20&pp=0&fl=C&ff=1&filterChecked=true&mp=1&ml=1&me=1&md=1&browser=Default#L13
|
||||
|
@ -33,3 +31,8 @@
|
|||
//http://www.cs.utsa.edu/~wagner/laws/FFM.html
|
||||
//http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/reedsolomon/reed_solomon_codes.html
|
||||
//http://en.wikipedia.org/wiki/Finite_field_arithmetic
|
||||
|
||||
//here is a braindump of urls and stuff for future reference
|
||||
//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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
. consider normalizing everything to use -150 for lead-in crappola ?
|
Loading…
Reference in New Issue