Many cleanups in disc system
Multisession support! (resolves #3400) The support is a bit lackluster given some constraints of our environment (e.g. the lack of a readable leadin entirely), but they don't matter for now
This commit is contained in:
parent
1ca1f4a397
commit
9d6a732cb4
|
@ -266,6 +266,10 @@ namespace BizHawk.Client.Common
|
|||
game.System = VSystemID.Raw.PCE;
|
||||
break;
|
||||
|
||||
case DiscType.JaguarCD:
|
||||
game.System = VSystemID.Raw.Jaguar;
|
||||
break;
|
||||
|
||||
case DiscType.Amiga:
|
||||
throw NoCoreForSystem(VSystemID.Raw.Amiga);
|
||||
case DiscType.CDi:
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace BizHawk.Emulation.Cores.Atari.Jaguar
|
|||
|
||||
var bios = Zstd.DecompressZstdStream(new MemoryStream(brev.Value)).ToArray();
|
||||
|
||||
var settings = new LibVirtualJaguar.Settings()
|
||||
var settings = new LibVirtualJaguar.Settings
|
||||
{
|
||||
NTSC = _syncSettings.NTSC,
|
||||
UseBIOS = !_syncSettings.SkipBIOS,
|
||||
|
@ -78,23 +78,9 @@ namespace BizHawk.Emulation.Cores.Atari.Jaguar
|
|||
|
||||
if (lp.Discs.Count > 0)
|
||||
{
|
||||
#if false
|
||||
_cd = lp.Discs[0].DiscData;
|
||||
_cdReader = new(_cd);
|
||||
#else
|
||||
if (lp.Discs.Count == 1)
|
||||
{
|
||||
throw new InvalidOperationException("Jaguar CD currently requires each session split into separate discs");
|
||||
}
|
||||
|
||||
_cd = new Disc[lp.Discs.Count];
|
||||
_cdReader = new DiscSectorReader[lp.Discs.Count];
|
||||
for (int i = 0; i < lp.Discs.Count; i++)
|
||||
{
|
||||
_cd[i] = lp.Discs[i].DiscData;
|
||||
_cdReader[i] = new(lp.Discs[i].DiscData);
|
||||
}
|
||||
#endif
|
||||
_core.SetCdCallbacks(_cdTocCallback, _cdReadCallback);
|
||||
|
||||
_saveRamSize = _syncSettings.UseMemoryTrack ? 0x20000 : 0;
|
||||
|
@ -259,24 +245,23 @@ namespace BizHawk.Emulation.Cores.Atari.Jaguar
|
|||
|
||||
private readonly LibVirtualJaguar.CDTOCCallback _cdTocCallback;
|
||||
private readonly LibVirtualJaguar.CDReadCallback _cdReadCallback;
|
||||
|
||||
// uh oh, we don't actually have multisession disc support, so...
|
||||
// TODO: get rid of this hack once we have proper multisession disc support
|
||||
#if false
|
||||
|
||||
private readonly Disc _cd;
|
||||
private readonly DiscSectorReader _cdReader;
|
||||
private readonly byte[] _buf2352 = new byte[2352];
|
||||
|
||||
private void CDTOCCallback(IntPtr dst)
|
||||
{
|
||||
var lastLeadOutTs = new Timestamp(_cd.TOC.LeadoutLBA + 150);
|
||||
var nsessions = _cd.Sessions.Count - 1;
|
||||
var lastLeadOutTs = new Timestamp(_cd.Sessions[nsessions].LeadoutLBA + 150);
|
||||
|
||||
var toc = new LibVirtualJaguar.TOC
|
||||
{
|
||||
Padding0 = 0,
|
||||
Padding1 = 0,
|
||||
NumSessions = (byte)(_cd.Structure.Sessions.Count - 1),
|
||||
NumSessions = (byte)nsessions,
|
||||
MinTrack = (byte)_cd.TOC.FirstRecordedTrackNumber,
|
||||
MaxTrack = (byte)_cd.TOC.LastRecordedTrackNumber,
|
||||
MaxTrack = (byte)_cd.Sessions[nsessions].TOC.LastRecordedTrackNumber,
|
||||
LastLeadOutMins = lastLeadOutTs.MIN,
|
||||
LastLeadOutSecs = lastLeadOutTs.SEC,
|
||||
LastLeadOutFrames = lastLeadOutTs.FRAC,
|
||||
|
@ -284,86 +269,24 @@ namespace BizHawk.Emulation.Cores.Atari.Jaguar
|
|||
};
|
||||
|
||||
var trackNum = 0;
|
||||
for (int i = 1; i < _cd.Structure.Sessions.Count; i++)
|
||||
for (var i = 1; i < _cd.Sessions.Count; i++)
|
||||
{
|
||||
var session = _cd.Structure.Sessions[i];
|
||||
for (int j = 1; j < session.InformationTrackCount; j++)
|
||||
{
|
||||
var track = session.Tracks[trackNum];
|
||||
toc.Tracks[i].TrackNum = (byte)track.Number;
|
||||
var ts = new Timestamp(track.LBA + 150);
|
||||
toc.Tracks[i].StartMins = ts.MIN;
|
||||
toc.Tracks[i].StartSecs = ts.SEC;
|
||||
toc.Tracks[i].StartFrames = ts.FRAC;
|
||||
toc.Tracks[i].SessionNum = (byte)(i - 1);
|
||||
var durTs = new Timestamp(track.NextTrack.LBA - track.LBA);
|
||||
toc.Tracks[i].DurMins = durTs.MIN;
|
||||
toc.Tracks[i].DurSecs = durTs.SEC;
|
||||
toc.Tracks[i].DurFrames = durTs.FRAC;
|
||||
trackNum++;
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.StructureToPtr(toc, dst, false);
|
||||
}
|
||||
|
||||
private void CDReadCallback(int lba, IntPtr dst)
|
||||
{
|
||||
var buf = new byte[2352];
|
||||
_cdReader.ReadLBA_2352(lba, buf, 0);
|
||||
Marshal.Copy(buf, 0, dst, 2352);
|
||||
DriveLightOn = true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
private readonly Disc[] _cd;
|
||||
private readonly DiscSectorReader[] _cdReader;
|
||||
private int[] _cdLbaOffsets;
|
||||
|
||||
private void CDTOCCallback(IntPtr dst)
|
||||
{
|
||||
var lastLeadOutTs = new Timestamp(_cd.Sum(c => c.TOC.LeadoutLBA) + _cd.Length * 150);
|
||||
|
||||
var toc = new LibVirtualJaguar.TOC
|
||||
{
|
||||
Padding0 = 0,
|
||||
Padding1 = 0,
|
||||
NumSessions = (byte)_cd.Length,
|
||||
MinTrack = (byte)_cd[0].TOC.FirstRecordedTrackNumber,
|
||||
MaxTrack = (byte)(_cd[0].TOC.FirstRecordedTrackNumber + _cd.Sum(c => c.Session1.InformationTrackCount - c.TOC.FirstRecordedTrackNumber)),
|
||||
LastLeadOutMins = lastLeadOutTs.MIN,
|
||||
LastLeadOutSecs = lastLeadOutTs.SEC,
|
||||
LastLeadOutFrames = lastLeadOutTs.FRAC,
|
||||
Tracks = new LibVirtualJaguar.TOC.Track[127],
|
||||
};
|
||||
|
||||
var trackNum = 0;
|
||||
var lbaOffset = 0;
|
||||
var trackOffset = 0;
|
||||
_cdLbaOffsets = new int[_cd.Length];
|
||||
for (int i = 0; i < _cd.Length; i++)
|
||||
{
|
||||
var session = _cd[i].Session1;
|
||||
for (int j = 0; j < session.InformationTrackCount; j++)
|
||||
var session = _cd.Sessions[i];
|
||||
for (var j = 0; j < session.InformationTrackCount; j++)
|
||||
{
|
||||
var track = session.Tracks[j + 1];
|
||||
toc.Tracks[trackNum].TrackNum = (byte)(trackOffset + track.Number);
|
||||
var ts = new Timestamp(lbaOffset + track.LBA + 150);
|
||||
toc.Tracks[trackNum].TrackNum = (byte)track.Number;
|
||||
var ts = new Timestamp(track.LBA + 150);
|
||||
toc.Tracks[trackNum].StartMins = ts.MIN;
|
||||
toc.Tracks[trackNum].StartSecs = ts.SEC;
|
||||
toc.Tracks[trackNum].StartFrames = ts.FRAC;
|
||||
toc.Tracks[trackNum].SessionNum = (byte)i;
|
||||
toc.Tracks[trackNum].SessionNum = (byte)(i - 1);
|
||||
var durTs = new Timestamp(track.NextTrack.LBA - track.LBA);
|
||||
toc.Tracks[trackNum].DurMins = durTs.MIN;
|
||||
toc.Tracks[trackNum].DurSecs = durTs.SEC;
|
||||
toc.Tracks[trackNum].DurFrames = durTs.FRAC;
|
||||
trackNum++;
|
||||
}
|
||||
|
||||
trackOffset += session.InformationTrackCount;
|
||||
lbaOffset += session.LeadoutTrack.LBA - session.FirstInformationTrack.LBA + 150;
|
||||
_cdLbaOffsets[i] = lbaOffset;
|
||||
}
|
||||
|
||||
Marshal.StructureToPtr(toc, dst, false);
|
||||
|
@ -371,19 +294,9 @@ namespace BizHawk.Emulation.Cores.Atari.Jaguar
|
|||
|
||||
private void CDReadCallback(int lba, IntPtr dst)
|
||||
{
|
||||
var buf = new byte[2352];
|
||||
for (int i = 0; i < _cdReader.Length; i++)
|
||||
{
|
||||
if (lba < _cdLbaOffsets[i])
|
||||
{
|
||||
_cdReader[i].ReadLBA_2352(lba - (i == 0 ? 0 : _cdLbaOffsets[i - 1]), buf, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.Copy(buf, 0, dst, 2352);
|
||||
_cdReader.ReadLBA_2352(lba, _buf2352, 0);
|
||||
Marshal.Copy(_buf2352, 0, dst, 2352);
|
||||
DriveLightOn = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -420,7 +420,7 @@ namespace BizHawk.Emulation.Cores.Sony.PSX
|
|||
|
||||
private int ShockDisc_ReadTOC(IntPtr opaque, OctoshockDll.ShockTOC* read_target, OctoshockDll.ShockTOCTrack* tracks101)
|
||||
{
|
||||
read_target->disc_type = (byte)Disc.TOC.Session1Format;
|
||||
read_target->disc_type = (byte)Disc.TOC.SessionFormat;
|
||||
read_target->first_track = (byte)Disc.TOC.FirstRecordedTrackNumber; //i _think_ that's what is meant here
|
||||
read_target->last_track = (byte)Disc.TOC.LastRecordedTrackNumber; //i _think_ that's what is meant here
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
// everything that's not commented, we're sure about
|
||||
t.FirstTrack = tin.FirstRecordedTrackNumber;
|
||||
t.LastTrack = tin.LastRecordedTrackNumber;
|
||||
t.DiskType = (int)tin.Session1Format;
|
||||
t.DiskType = (int)tin.SessionFormat;
|
||||
for (int i = 0; i < 101; i++)
|
||||
{
|
||||
t.Tracks[i].Adr = tin.TOCItems[i].Exists ? 1 : 0; // ????
|
||||
|
|
|
@ -29,34 +29,27 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DiscStructure corresponding to the TOCRaw
|
||||
/// This is a 1-indexed list of sessions (session 1 is at [1])
|
||||
/// To prevent duplicate Add(null) calls around the code, we'll have it already have [0] with null
|
||||
/// So the first Add() call will put a session at [1], the second will put a session at [2], and so on
|
||||
/// </summary>
|
||||
public DiscStructure Structure;
|
||||
public readonly IList<DiscSession> Sessions = new List<DiscSession> { null };
|
||||
|
||||
/// <summary>
|
||||
/// DiscStructure.Session 1 of the disc, since that's all that's needed most of the time.
|
||||
/// Session 1 of the disc, since that's all that's needed most of the time.
|
||||
/// </summary>
|
||||
public DiscStructure.Session Session1 => Structure.Sessions[1];
|
||||
public DiscSession Session1 => Sessions[1];
|
||||
|
||||
/// <summary>
|
||||
/// The DiscTOC corresponding to Session1.
|
||||
/// </summary>
|
||||
public DiscTOC TOC => Session1.TOC;
|
||||
|
||||
/// <summary>
|
||||
/// The name of a disc. Loosely based on the filename. Just for informational purposes.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// The DiscTOCRaw corresponding to the RawTOCEntries.
|
||||
/// TODO - there's one of these for every session, so... having one here doesn't make sense
|
||||
/// so...
|
||||
/// TODO - remove me
|
||||
/// </summary>
|
||||
public DiscTOC TOC;
|
||||
|
||||
/// <summary>
|
||||
/// The raw TOC entries found in the lead-in track.
|
||||
/// These aren't very useful, but theyre one of the most lowest-level data structures from which other TOC-related stuff is derived
|
||||
/// </summary>
|
||||
public List<RawTOCEntry> RawTOCEntries = new List<RawTOCEntry>();
|
||||
|
||||
/// <summary>
|
||||
/// Free-form optional memos about the disc
|
||||
/// </summary>
|
||||
|
@ -110,7 +103,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// The sectors on the disc. Don't use this directly! Use the SectorSynthProvider instead.
|
||||
/// TODO - eliminate this entirely and do entirely with the delegate (much faster disc loading... but massively annoying architecture inside-out logic)
|
||||
/// </summary>
|
||||
internal List<ISectorSynthJob2448> _Sectors = new List<ISectorSynthJob2448>();
|
||||
internal List<ISectorSynthJob2448> _Sectors = new();
|
||||
|
||||
/// <summary>
|
||||
/// ISectorSynthProvider instance for the disc. May be daisy-chained
|
||||
|
|
|
@ -1,27 +1,7 @@
|
|||
//Copyright (c) 2012 BizHawk team
|
||||
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
//this software and associated documentation files (the "Software"), to deal in
|
||||
//the Software without restriction, including without limitation the rights to
|
||||
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
//of the Software, and to permit persons to whom the Software is furnished to do
|
||||
//so, subject to the following conditions:
|
||||
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
//ECM File Format reading support
|
||||
//ECM File Format reading support
|
||||
|
||||
//TODO - make a background thread to validate the EDC. be sure to terminate thread when the Blob disposes
|
||||
//remember: may need another stream for that. the IBlob architecture doesn-t demand multi-threading support
|
||||
//remember: may need another stream for that. the IBlob architecture doesn't demand multi-threading support
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
@ -65,7 +45,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// an index of blocks within the ECM file, for random-access.
|
||||
/// itll be sorted by logical ordering, so you can binary search for the address you want
|
||||
/// </summary>
|
||||
private readonly List<IndexEntry> Index = new List<IndexEntry>();
|
||||
private readonly List<IndexEntry> Index = new();
|
||||
|
||||
/// <summary>
|
||||
/// the ECMfile-provided EDC integrity checksum. not being used right now
|
||||
|
@ -76,21 +56,21 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
public void Load(string path)
|
||||
{
|
||||
stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
stream = new(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
//skip header
|
||||
stream.Seek(4, SeekOrigin.Current);
|
||||
|
||||
long logOffset = 0;
|
||||
for (; ; )
|
||||
while (true)
|
||||
{
|
||||
//read block count. this format is really stupid. maybe its good for detecting non-ecm files or something.
|
||||
int b = stream.ReadByte();
|
||||
var b = stream.ReadByte();
|
||||
if (b == -1) MisformedException();
|
||||
int bytes = 1;
|
||||
int T = b & 3;
|
||||
var bytes = 1;
|
||||
var T = b & 3;
|
||||
long N = (b >> 2) & 0x1F;
|
||||
int nbits = 5;
|
||||
var nbits = 5;
|
||||
while (b.Bit(7))
|
||||
{
|
||||
if (bytes == 5) MisformedException(); //if we're gonna need a 6th byte, this file is broken
|
||||
|
@ -109,31 +89,32 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
if (N >= 0x100000000)
|
||||
MisformedException();
|
||||
|
||||
uint todo = (uint)N + 1;
|
||||
var todo = (uint)N + 1;
|
||||
|
||||
Index.Add(new IndexEntry(type: T, number: todo, ecmOffset: stream.Position, logicalOffset: logOffset));
|
||||
|
||||
if (T == 0)
|
||||
switch (T)
|
||||
{
|
||||
stream.Seek(todo, SeekOrigin.Current);
|
||||
logOffset += todo;
|
||||
case 0:
|
||||
stream.Seek(todo, SeekOrigin.Current);
|
||||
logOffset += todo;
|
||||
break;
|
||||
case 1:
|
||||
stream.Seek(todo * (2048 + 3), SeekOrigin.Current);
|
||||
logOffset += todo * 2352;
|
||||
break;
|
||||
case 2:
|
||||
stream.Seek(todo * 2052, SeekOrigin.Current);
|
||||
logOffset += todo * 2336;
|
||||
break;
|
||||
case 3:
|
||||
stream.Seek(todo * 2328, SeekOrigin.Current);
|
||||
logOffset += todo * 2336;
|
||||
break;
|
||||
default:
|
||||
MisformedException();
|
||||
break;
|
||||
}
|
||||
else if (T == 1)
|
||||
{
|
||||
stream.Seek(todo * (2048 + 3), SeekOrigin.Current);
|
||||
logOffset += todo * 2352;
|
||||
}
|
||||
else if (T == 2)
|
||||
{
|
||||
stream.Seek(todo * 2052, SeekOrigin.Current);
|
||||
logOffset += todo * 2336;
|
||||
}
|
||||
else if (T == 3)
|
||||
{
|
||||
stream.Seek(todo * 2328, SeekOrigin.Current);
|
||||
logOffset += todo * 2336;
|
||||
}
|
||||
else MisformedException();
|
||||
}
|
||||
|
||||
//TODO - endian bug. need an endian-independent binary reader with good license (miscutils is apache license)
|
||||
|
@ -144,24 +125,19 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
Length = logOffset;
|
||||
}
|
||||
|
||||
private void MisformedException()
|
||||
private static void MisformedException()
|
||||
{
|
||||
throw new InvalidOperationException("Mis-formed ECM file");
|
||||
}
|
||||
|
||||
public static bool IsECM(string path)
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
int e = fs.ReadByte();
|
||||
int c = fs.ReadByte();
|
||||
int m = fs.ReadByte();
|
||||
int o = fs.ReadByte();
|
||||
if (e != 'E' || c != 'C' || m != 'M' || o != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var e = fs.ReadByte();
|
||||
var c = fs.ReadByte();
|
||||
var m = fs.ReadByte();
|
||||
var o = fs.ReadByte();
|
||||
return e == 'E' && c == 'C' && m == 'M' && o == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -170,9 +146,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
private int FindInIndex(long offset, int LastReadIndex)
|
||||
{
|
||||
//try to avoid searching the index. check the last index we we used.
|
||||
for(int i=0;i<2;i++) //try 2 times
|
||||
for (var i = 0; i < 2; i++) //try 2 times
|
||||
{
|
||||
IndexEntry last = Index[LastReadIndex];
|
||||
var last = Index[LastReadIndex];
|
||||
if (LastReadIndex == Index.Count - 1)
|
||||
{
|
||||
//byte_pos would have to be after the last entry
|
||||
|
@ -183,7 +159,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
else
|
||||
{
|
||||
IndexEntry next = Index[LastReadIndex + 1];
|
||||
var next = Index[LastReadIndex + 1];
|
||||
if (offset >= last.LogicalOffset && offset < next.LogicalOffset)
|
||||
{
|
||||
return LastReadIndex;
|
||||
|
@ -195,18 +171,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
//Console.WriteLine("binary searched"); //use this to check for mistaken LastReadIndex logic resulting in binary searches during sequential access
|
||||
int listIndex = Index.LowerBoundBinarySearch(idx => idx.LogicalOffset, offset);
|
||||
var listIndex = Index.LowerBoundBinarySearch(idx => idx.LogicalOffset, offset);
|
||||
System.Diagnostics.Debug.Assert(listIndex < Index.Count);
|
||||
//Console.WriteLine("byte_pos {0:X8} using index #{1} at offset {2:X8}", offset, listIndex, Index[listIndex].LogicalOffset);
|
||||
|
||||
return listIndex;
|
||||
}
|
||||
|
||||
private void Reconstruct(byte[] secbuf, int type)
|
||||
private static void Reconstruct(byte[] secbuf, int type)
|
||||
{
|
||||
//sync
|
||||
secbuf[0] = 0;
|
||||
for (int i = 1; i <= 10; i++)
|
||||
for (var i = 1; i <= 10; i++)
|
||||
secbuf[i] = 0xFF;
|
||||
secbuf[11] = 0x00;
|
||||
|
||||
|
@ -217,7 +193,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//mode 1
|
||||
secbuf[15] = 0x01;
|
||||
//reserved
|
||||
for (int i = 0x814; i <= 0x81B; i++)
|
||||
for (var i = 0x814; i <= 0x81B; i++)
|
||||
secbuf[i] = 0x00;
|
||||
break;
|
||||
|
||||
|
@ -257,7 +233,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public int Read(long byte_pos, byte[] buffer, int offset, int _count)
|
||||
{
|
||||
long remain = _count;
|
||||
int completed = 0;
|
||||
var completed = 0;
|
||||
|
||||
//we take advantage of the fact that we pretty much always read one sector at a time.
|
||||
//this would be really inefficient if we only read one byte at a time.
|
||||
|
@ -265,18 +241,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
while (remain > 0)
|
||||
{
|
||||
int listIndex = FindInIndex(byte_pos, Read_LastIndex);
|
||||
var listIndex = FindInIndex(byte_pos, Read_LastIndex);
|
||||
|
||||
IndexEntry ie = Index[listIndex];
|
||||
var ie = Index[listIndex];
|
||||
Read_LastIndex = listIndex;
|
||||
|
||||
if (ie.Type == 0)
|
||||
{
|
||||
//type 0 is special: its just a raw blob. so all we need to do is read straight out of the stream
|
||||
long blockOffset = byte_pos - ie.LogicalOffset;
|
||||
long bytesRemainInBlock = ie.Number - blockOffset;
|
||||
var blockOffset = byte_pos - ie.LogicalOffset;
|
||||
var bytesRemainInBlock = ie.Number - blockOffset;
|
||||
|
||||
long todo = remain;
|
||||
var todo = remain;
|
||||
if (bytesRemainInBlock < todo)
|
||||
todo = bytesRemainInBlock;
|
||||
|
||||
|
@ -288,7 +264,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
toRead = int.MaxValue;
|
||||
else toRead = (int)todo;
|
||||
|
||||
int done = stream.Read(buffer, offset, toRead);
|
||||
var done = stream.Read(buffer, offset, toRead);
|
||||
if (done != toRead)
|
||||
return completed;
|
||||
|
||||
|
@ -298,31 +274,38 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
offset += done;
|
||||
byte_pos += done;
|
||||
}
|
||||
|
||||
//done reading the raw block; go back to check for another block
|
||||
continue;
|
||||
} //if(type 0)
|
||||
}
|
||||
else
|
||||
{
|
||||
//these are sector-based types. they have similar handling.
|
||||
|
||||
long blockOffset = byte_pos - ie.LogicalOffset;
|
||||
var blockOffset = byte_pos - ie.LogicalOffset;
|
||||
|
||||
//figure out which sector within the block we're in
|
||||
int outSecSize;
|
||||
int inSecSize;
|
||||
int outSecOffset;
|
||||
if (ie.Type == 1) { outSecSize = 2352; inSecSize = 2048; outSecOffset = 0; }
|
||||
else if (ie.Type == 2) { outSecSize = 2336; inSecSize = 2052; outSecOffset = 16; }
|
||||
else if (ie.Type == 3) { outSecSize = 2336; inSecSize = 2328; outSecOffset = 16; }
|
||||
else throw new InvalidOperationException();
|
||||
switch (ie.Type)
|
||||
{
|
||||
case 1:
|
||||
outSecSize = 2352; inSecSize = 2048; outSecOffset = 0;
|
||||
break;
|
||||
case 2:
|
||||
outSecSize = 2336; inSecSize = 2052; outSecOffset = 16;
|
||||
break;
|
||||
case 3:
|
||||
outSecSize = 2336; inSecSize = 2328; outSecOffset = 16;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
long secNumberInBlock = blockOffset / outSecSize;
|
||||
long secOffsetInEcm = secNumberInBlock * outSecSize;
|
||||
long bytesAskedIntoSector = blockOffset % outSecSize;
|
||||
long bytesRemainInSector = outSecSize - bytesAskedIntoSector;
|
||||
var secNumberInBlock = blockOffset / outSecSize;
|
||||
var secOffsetInEcm = secNumberInBlock * outSecSize;
|
||||
var bytesAskedIntoSector = blockOffset % outSecSize;
|
||||
var bytesRemainInSector = outSecSize - bytesAskedIntoSector;
|
||||
|
||||
long todo = remain;
|
||||
var todo = remain;
|
||||
if (bytesRemainInSector < todo)
|
||||
todo = bytesRemainInSector;
|
||||
|
||||
|
@ -353,16 +336,15 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//sector is decoded to 2352 bytes. Handling doesnt depend much on type from here
|
||||
|
||||
Array.Copy(Read_SectorBuf, (int)bytesAskedIntoSector + outSecOffset, buffer, offset, todo);
|
||||
int done = (int)todo;
|
||||
var done = (int)todo;
|
||||
|
||||
offset += done;
|
||||
completed += done;
|
||||
remain -= done;
|
||||
byte_pos += done;
|
||||
|
||||
} //not type 0
|
||||
|
||||
} // while(Remain)
|
||||
}
|
||||
}
|
||||
|
||||
return completed;
|
||||
}
|
||||
|
|
|
@ -10,21 +10,22 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
set
|
||||
{
|
||||
physicalPath = value;
|
||||
length = new FileInfo(physicalPath).Length;
|
||||
Length = new FileInfo(physicalPath).Length;
|
||||
}
|
||||
}
|
||||
|
||||
private string physicalPath;
|
||||
private long length;
|
||||
|
||||
public readonly long Offset = 0;
|
||||
|
||||
private BufferedStream fs;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
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.
|
||||
|
@ -34,12 +35,13 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//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;
|
||||
fs ??= new BufferedStream(new FileStream(physicalPath, FileMode.Open, FileAccess.Read, FileShare.Read), buffersize);
|
||||
long target = byte_pos + Offset;
|
||||
fs ??= new(new FileStream(physicalPath, FileMode.Open, FileAccess.Read, FileShare.Read), buffersize);
|
||||
var target = byte_pos + Offset;
|
||||
if (fs.Position != target)
|
||||
fs.Position = target;
|
||||
return fs.Read(buffer, offset, count);
|
||||
}
|
||||
public long Length => length;
|
||||
|
||||
public long Length { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//acquire the start of the data chunk
|
||||
var dataChunk = (RiffMaster.RiffSubchunk) dataChunks[0];
|
||||
waveDataStreamPos = dataChunk.Position;
|
||||
mDataLength = dataChunk.Length;
|
||||
Length = dataChunk.Length;
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
|
@ -92,8 +92,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
private RiffMaster RiffSource;
|
||||
private long waveDataStreamPos;
|
||||
private long mDataLength;
|
||||
public long Length => mDataLength;
|
||||
public long Length { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
private readonly IBlob srcBlob;
|
||||
private readonly long srcBlobLength;
|
||||
|
||||
public Blob_ZeroPadAdapter(IBlob srcBlob, long srcBlobLength)
|
||||
{
|
||||
this.srcBlob = srcBlob;
|
||||
|
@ -16,15 +17,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
public int Read(long byte_pos, byte[] buffer, int offset, int count)
|
||||
{
|
||||
int todo = count;
|
||||
long end = byte_pos + todo;
|
||||
var todo = count;
|
||||
var end = byte_pos + todo;
|
||||
if (end > srcBlobLength)
|
||||
{
|
||||
long temp = (int)(srcBlobLength - byte_pos);
|
||||
if (temp > int.MaxValue)
|
||||
throw new InvalidOperationException();
|
||||
todo = (int)temp;
|
||||
|
||||
todo = checked((int)(srcBlobLength - byte_pos));
|
||||
//zero-fill the unused part (just for safety's sake)
|
||||
Array.Clear(buffer, offset + todo, count - todo);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Linq;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
|
@ -19,7 +19,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
public void WriteFile(string fname)
|
||||
{
|
||||
using FileStream fs = new FileStream(fname, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
using FileStream fs = new(fname, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
WriteStream(fs);
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
protected static void WriteTag(BinaryWriter bw, string tag)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (var i = 0; i < 4; i++)
|
||||
bw.Write(tag[i]);
|
||||
bw.Flush();
|
||||
}
|
||||
|
@ -74,9 +74,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public long Position;
|
||||
public uint Length;
|
||||
public Stream Source;
|
||||
|
||||
public override void WriteStream(Stream s)
|
||||
{
|
||||
BinaryWriter bw = new BinaryWriter(s);
|
||||
BinaryWriter bw = new(s);
|
||||
WriteTag(bw, tag);
|
||||
bw.Write(Length);
|
||||
bw.Flush();
|
||||
|
@ -88,6 +89,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
if (Length % 2 != 0)
|
||||
s.WriteByte(0);
|
||||
}
|
||||
|
||||
public override long GetVolume()
|
||||
{
|
||||
long ret = Length;
|
||||
|
@ -97,8 +99,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
public byte[] ReadAll()
|
||||
{
|
||||
int msSize = (int)Math.Min((long)int.MaxValue, Length);
|
||||
MemoryStream ms = new MemoryStream(msSize);
|
||||
var msSize = (int)Math.Min((long)int.MaxValue, Length);
|
||||
MemoryStream ms = new(msSize);
|
||||
Source.Position = Position;
|
||||
Util.CopyStream(Source, ms, Length);
|
||||
return ms.ToArray();
|
||||
|
@ -118,28 +120,30 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
public enum FORMAT_TAG : ushort
|
||||
{
|
||||
WAVE_FORMAT_UNKNOWN = (0x0000),
|
||||
WAVE_FORMAT_PCM = (0x0001),
|
||||
WAVE_FORMAT_ADPCM = (0x0002),
|
||||
WAVE_FORMAT_ALAW = (0x0006),
|
||||
WAVE_FORMAT_MULAW = (0x0007),
|
||||
WAVE_FORMAT_OKI_ADPCM = (0x0010),
|
||||
WAVE_FORMAT_DIGISTD = (0x0015),
|
||||
WAVE_FORMAT_DIGIFIX = (0x0016),
|
||||
IBM_FORMAT_MULAW = (0x0101),
|
||||
IBM_FORMAT_ALAW = (0x0102),
|
||||
IBM_FORMAT_ADPCM = (0x0103),
|
||||
WAVE_FORMAT_UNKNOWN = 0x0000,
|
||||
WAVE_FORMAT_PCM = 0x0001,
|
||||
WAVE_FORMAT_ADPCM = 0x0002,
|
||||
WAVE_FORMAT_ALAW = 0x0006,
|
||||
WAVE_FORMAT_MULAW = 0x0007,
|
||||
WAVE_FORMAT_OKI_ADPCM = 0x0010,
|
||||
WAVE_FORMAT_DIGISTD = 0x0015,
|
||||
WAVE_FORMAT_DIGIFIX = 0x0016,
|
||||
IBM_FORMAT_MULAW = 0x0101,
|
||||
IBM_FORMAT_ALAW = 0x0102,
|
||||
IBM_FORMAT_ADPCM = 0x0103,
|
||||
}
|
||||
|
||||
public FORMAT_TAG format_tag;
|
||||
public ushort channels;
|
||||
public uint samplesPerSec;
|
||||
public uint avgBytesPerSec;
|
||||
public ushort blockAlign;
|
||||
public ushort bitsPerSample;
|
||||
|
||||
public RiffSubchunk_fmt(RiffSubchunk origin)
|
||||
{
|
||||
tag = "fmt ";
|
||||
BinaryReader br = new BinaryReader(new MemoryStream(origin.ReadAll()));
|
||||
BinaryReader br = new(new MemoryStream(origin.ReadAll()));
|
||||
format_tag = (FORMAT_TAG)br.ReadUInt16();
|
||||
channels = br.ReadUInt16();
|
||||
samplesPerSec = br.ReadUInt32();
|
||||
|
@ -147,6 +151,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
blockAlign = br.ReadUInt16();
|
||||
bitsPerSample = br.ReadUInt16();
|
||||
}
|
||||
|
||||
public override void WriteStream(Stream s)
|
||||
{
|
||||
Flush();
|
||||
|
@ -155,8 +160,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
private void Flush()
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
BinaryWriter bw = new BinaryWriter(ms);
|
||||
MemoryStream ms = new();
|
||||
BinaryWriter bw = new(ms);
|
||||
bw.Write((ushort)format_tag);
|
||||
bw.Write(channels);
|
||||
bw.Write(samplesPerSec);
|
||||
|
@ -180,12 +185,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
public RiffChunk GetSubchunk(string tag, string type)
|
||||
{
|
||||
foreach (RiffChunk rc in subchunks)
|
||||
foreach (var rc in subchunks.Where(rc => rc.tag == tag))
|
||||
{
|
||||
if (rc.tag != tag) continue;
|
||||
if (type == null) return rc;
|
||||
if (rc is RiffContainer cont && cont.type == type) return cont;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -193,28 +198,28 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
tag = "LIST";
|
||||
}
|
||||
|
||||
public string type;
|
||||
public List<RiffChunk> subchunks = new List<RiffChunk>();
|
||||
public List<RiffChunk> subchunks = new();
|
||||
|
||||
public override void WriteStream(Stream s)
|
||||
{
|
||||
BinaryWriter bw = new BinaryWriter(s);
|
||||
BinaryWriter bw = new(s);
|
||||
WriteTag(bw, tag);
|
||||
long size = GetVolume();
|
||||
var size = GetVolume();
|
||||
if (size > uint.MaxValue) throw new FormatException("File too big to write out");
|
||||
bw.Write((uint)size);
|
||||
WriteTag(bw, type);
|
||||
bw.Flush();
|
||||
foreach (RiffChunk rc in subchunks)
|
||||
foreach (var rc in subchunks)
|
||||
rc.WriteStream(s);
|
||||
if (size % 2 != 0)
|
||||
s.WriteByte(0);
|
||||
}
|
||||
|
||||
public override long GetVolume()
|
||||
{
|
||||
long len = 4;
|
||||
foreach (RiffChunk rc in subchunks)
|
||||
len += rc.GetVolume() + 8;
|
||||
return len;
|
||||
return 4 + subchunks.Sum(rc => rc.GetVolume() + 8);
|
||||
}
|
||||
|
||||
public override RiffChunk Morph()
|
||||
|
@ -237,10 +242,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
subchunks = rc.subchunks;
|
||||
type = "INFO";
|
||||
foreach (RiffChunk chunk in subchunks)
|
||||
foreach (var chunk in subchunks)
|
||||
{
|
||||
RiffSubchunk rsc = chunk as RiffSubchunk;
|
||||
if (chunk == null) throw new FormatException("Invalid subchunk of INFO list"); //TODO is this supposed to be a check on `rsc` (i.e. as a type check)? --yoshi
|
||||
if (chunk is not RiffSubchunk rsc) throw new FormatException("Invalid subchunk of INFO list");
|
||||
dictionary[rsc.tag] = System.Text.Encoding.ASCII.GetString(rsc.ReadAll());
|
||||
}
|
||||
}
|
||||
|
@ -250,12 +254,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
subchunks.Clear();
|
||||
foreach (var (subchunkTag, s) in dictionary)
|
||||
{
|
||||
RiffSubchunk rs = new RiffSubchunk
|
||||
{
|
||||
tag = subchunkTag,
|
||||
Source = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(s)),
|
||||
Position = 0
|
||||
};
|
||||
var rs = new RiffSubchunk
|
||||
{
|
||||
tag = subchunkTag,
|
||||
Source = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(s)),
|
||||
Position = 0
|
||||
};
|
||||
rs.Length = (uint)rs.Source.Length;
|
||||
subchunks.Add(rs);
|
||||
}
|
||||
|
@ -275,38 +279,38 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
public RiffContainer riff;
|
||||
|
||||
private long readCounter;
|
||||
|
||||
private RiffChunk ReadChunk(BinaryReader br)
|
||||
{
|
||||
RiffChunk ret;
|
||||
string tag = ReadTag(br); readCounter += 4;
|
||||
uint size = br.ReadUInt32(); readCounter += 4;
|
||||
var tag = ReadTag(br); readCounter += 4;
|
||||
var size = br.ReadUInt32(); readCounter += 4;
|
||||
if (size > int.MaxValue)
|
||||
throw new FormatException("chunk too big");
|
||||
if (tag == "RIFF" || tag == "LIST")
|
||||
if (tag is "RIFF" or "LIST")
|
||||
{
|
||||
RiffContainer rc = new RiffContainer
|
||||
{
|
||||
tag = tag,
|
||||
type = ReadTag(br)
|
||||
};
|
||||
var rc = new RiffContainer
|
||||
{
|
||||
tag = tag,
|
||||
type = ReadTag(br)
|
||||
};
|
||||
|
||||
readCounter += 4;
|
||||
long readEnd = readCounter - 4 + size;
|
||||
var readEnd = readCounter - 4 + size;
|
||||
while (readEnd > readCounter)
|
||||
rc.subchunks.Add(ReadChunk(br));
|
||||
ret = rc.Morph();
|
||||
}
|
||||
else
|
||||
{
|
||||
RiffSubchunk rsc = new RiffSubchunk
|
||||
{
|
||||
tag = tag,
|
||||
Source = br.BaseStream,
|
||||
Position = br.BaseStream.Position,
|
||||
Length = size
|
||||
};
|
||||
var rsc = new RiffSubchunk
|
||||
{
|
||||
tag = tag,
|
||||
Source = br.BaseStream,
|
||||
Position = br.BaseStream.Position,
|
||||
Length = size
|
||||
};
|
||||
readCounter += size;
|
||||
br.BaseStream.Position += size;
|
||||
ret = rsc.Morph();
|
||||
|
@ -316,8 +320,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
br.ReadByte();
|
||||
readCounter += 1;
|
||||
}
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void WriteStream(Stream s)
|
||||
|
@ -332,8 +336,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
Dispose();
|
||||
BaseStream = s;
|
||||
readCounter = 0;
|
||||
BinaryReader br = new BinaryReader(s);
|
||||
RiffChunk chunk = ReadChunk(br);
|
||||
BinaryReader br = new(s);
|
||||
var chunk = ReadChunk(br);
|
||||
if (chunk.tag != "RIFF") throw new FormatException("can't recognize riff chunk");
|
||||
riff = (RiffContainer)chunk;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
using System.IO;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Linq;
|
||||
using BizHawk.Common;
|
||||
|
||||
//check out ccd2iso linux program?
|
||||
|
@ -74,7 +74,6 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </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 that's why CCD puts it here
|
||||
/// </summary>
|
||||
|
@ -160,41 +159,39 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public CCDParseException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
private class CCDSection : Dictionary<string,int>
|
||||
private class CCDSection : Dictionary<string, int>
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public int FetchOrDefault(int def, string key)
|
||||
{
|
||||
TryGetValue(key, out def);
|
||||
return def;
|
||||
}
|
||||
=> TryGetValue(key, out var val) ? val : def;
|
||||
|
||||
/// <exception cref="CCDParseException"><paramref name="key"/> not found in <see langword="this"/></exception>
|
||||
public int FetchOrFail(string key)
|
||||
{
|
||||
if(!TryGetValue(key, out var ret))
|
||||
if (!TryGetValue(key, out var ret))
|
||||
throw new CCDParseException($"Malformed or unexpected CCD format: missing required [Entry] key: {key}");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CCDSection> ParseSections(Stream stream)
|
||||
private static List<CCDSection> ParseSections(Stream stream)
|
||||
{
|
||||
List<CCDSection> sections = new List<CCDSection>();
|
||||
var 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);
|
||||
var sr = new StreamReader(stream);
|
||||
CCDSection currSection = null;
|
||||
for (; ; )
|
||||
while (true)
|
||||
{
|
||||
var line = sr.ReadLine();
|
||||
if (line == null) break;
|
||||
if (line == "") continue;
|
||||
if (line is null) break;
|
||||
if (line == string.Empty) continue;
|
||||
if (line.StartsWith("["))
|
||||
{
|
||||
currSection = new CCDSection
|
||||
currSection = new()
|
||||
{
|
||||
Name = line.Trim('[', ']').ToUpper()
|
||||
};
|
||||
|
@ -202,6 +199,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
else
|
||||
{
|
||||
if (currSection is null)
|
||||
throw new CCDParseException("Malformed or unexpected CCD format: started without [");
|
||||
var parts = line.Split('=');
|
||||
if (parts.Length != 2)
|
||||
throw new CCDParseException("Malformed or unexpected CCD format: parsing item into two parts");
|
||||
|
@ -219,17 +218,21 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
val = int.Parse(parts[1]);
|
||||
currSection[parts[0].ToUpper()] = val;
|
||||
}
|
||||
} //loop until lines exhausted
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
private int PreParseIntegrityCheck(List<CCDSection> sections)
|
||||
private static int PreParseIntegrityCheck(IReadOnlyList<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");
|
||||
switch (sections.Count)
|
||||
{
|
||||
case 0:
|
||||
throw new CCDParseException("Malformed CCD format: no sections");
|
||||
//we need at least a CloneCD and Disc section
|
||||
case < 2:
|
||||
throw new CCDParseException("Malformed CCD format: insufficient sections");
|
||||
}
|
||||
|
||||
var ccdSection = sections[0];
|
||||
if (ccdSection.Name != "CLONECD")
|
||||
|
@ -238,35 +241,36 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
if (!ccdSection.TryGetValue("VERSION", out var version))
|
||||
throw new CCDParseException("Malformed CCD format: missing version in CloneCD section");
|
||||
|
||||
if(sections[1].Name != "DISC")
|
||||
if (sections[1].Name != "DISC")
|
||||
throw new CCDParseException("Malformed CCD format: section[1] isn't [Disc]");
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
/// <exception cref="CCDParseException">parsed <see cref="CCDFile.DataTracksScrambled"/> is <c>1</c>, parsed session number is not <c>1</c>, or malformed entry</exception>
|
||||
public CCDFile ParseFrom(Stream stream)
|
||||
public static CCDFile ParseFrom(Stream stream)
|
||||
{
|
||||
CCDFile ccdf = new CCDFile();
|
||||
var 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
|
||||
var nTocEntries = discSection["TOCENTRIES"]; //its conceivable that this could be missing
|
||||
var 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: {nameof(ccdf.DataTracksScrambled)}=1 not supported. Please report this, so we can understand what it means.");
|
||||
if (ccdf.DataTracksScrambled == 1)
|
||||
throw new CCDParseException($"Malformed CCD format: {nameof(ccdf.DataTracksScrambled)}=1 not supported. Please report this, so we can understand what it means.");
|
||||
|
||||
for (int i = 2; i < sections.Count; i++)
|
||||
for (var 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);
|
||||
var sesnum = int.Parse(section.Name.Split(' ')[1]);
|
||||
var session = new CCDSession(sesnum);
|
||||
ccdf.Sessions.Add(session);
|
||||
if (sesnum != ccdf.Sessions.Count)
|
||||
throw new CCDParseException("Malformed CCD format: wrong session number in sequence");
|
||||
|
@ -275,8 +279,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
else if (section.Name.StartsWith("ENTRY"))
|
||||
{
|
||||
int entryNum = int.Parse(section.Name.Split(' ')[1]);
|
||||
CCDTocEntry entry = new CCDTocEntry(entryNum);
|
||||
var entryNum = int.Parse(section.Name.Split(' ')[1]);
|
||||
var entry = new CCDTocEntry(entryNum);
|
||||
ccdf.TOCEntries.Add(entry);
|
||||
|
||||
entry.Session = section.FetchOrFail("SESSION");
|
||||
|
@ -299,14 +303,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
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);
|
||||
var entryNum = int.Parse(section.Name.Split(' ')[1]);
|
||||
var track = new CCDTrack(entryNum);
|
||||
ccdf.Tracks.Add(track);
|
||||
ccdf.TracksByNumber[entryNum] = track;
|
||||
foreach (var (k, v) in section)
|
||||
|
@ -315,7 +316,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
else if (k.StartsWith("INDEX")) track.Indexes[int.Parse(k.Split(' ')[1])] = v;
|
||||
}
|
||||
}
|
||||
} //sections loop
|
||||
}
|
||||
|
||||
return ccdf;
|
||||
}
|
||||
|
@ -345,7 +346,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
CCDFile ccdf;
|
||||
using (var infCCD = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
ccdf = new CCD_Format().ParseFrom(infCCD);
|
||||
ccdf = ParseFrom(infCCD);
|
||||
|
||||
ret.ParsedCCDFile = ccdf;
|
||||
|
||||
|
@ -370,70 +371,76 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
sw.WriteLine("Version=3");
|
||||
sw.WriteLine();
|
||||
sw.WriteLine("[Disc]");
|
||||
sw.WriteLine("TocEntries={0}", disc.RawTOCEntries.Count);
|
||||
sw.WriteLine("Sessions=1");
|
||||
sw.WriteLine("TocEntries={0}", disc.Sessions.Sum(s => s?.RawTOCEntries.Count ?? 0));
|
||||
sw.WriteLine("Sessions={0}", disc.Sessions.Count - 1);
|
||||
sw.WriteLine("DataTracksScrambled=0");
|
||||
sw.WriteLine("CDTextLength=0"); //not supported anyway
|
||||
sw.WriteLine();
|
||||
sw.WriteLine("[Session 1]");
|
||||
sw.WriteLine("PreGapMode=2");
|
||||
sw.WriteLine("PreGapSubC=1");
|
||||
sw.WriteLine();
|
||||
for (int i = 0; i < disc.RawTOCEntries.Count; i++)
|
||||
for (var i = 1; i < disc.Sessions.Count; i++)
|
||||
{
|
||||
var entry = disc.RawTOCEntries[i];
|
||||
|
||||
//ehhh something's wrong with how I track these
|
||||
int point = entry.QData.q_index.DecimalValue;
|
||||
if (point == 100) point = 0xA0;
|
||||
if (point == 101) point = 0xA1;
|
||||
if (point == 102) point = 0xA2;
|
||||
|
||||
sw.WriteLine("[Entry {0}]", i);
|
||||
sw.WriteLine("Session=1");
|
||||
sw.WriteLine("Point=0x{0:x2}", point);
|
||||
sw.WriteLine("ADR=0x{0:x2}", entry.QData.ADR);
|
||||
sw.WriteLine("Control=0x{0:x2}", (int)entry.QData.CONTROL);
|
||||
sw.WriteLine("TrackNo={0}", entry.QData.q_tno.DecimalValue);
|
||||
sw.WriteLine("AMin={0}", entry.QData.min.DecimalValue);
|
||||
sw.WriteLine("ASec={0}", entry.QData.sec.DecimalValue);
|
||||
sw.WriteLine("AFrame={0}", entry.QData.frame.DecimalValue);
|
||||
sw.WriteLine("ALBA={0}", entry.QData.Timestamp - 150); //remember to adapt the absolute MSF to an LBA (this field is redundant...)
|
||||
sw.WriteLine("Zero={0}", entry.QData.zero);
|
||||
sw.WriteLine("PMin={0}", entry.QData.ap_min.DecimalValue);
|
||||
sw.WriteLine("PSec={0}", entry.QData.ap_sec.DecimalValue);
|
||||
sw.WriteLine("PFrame={0}", entry.QData.ap_frame.DecimalValue);
|
||||
sw.WriteLine("PLBA={0}", entry.QData.AP_Timestamp - 150); //remember to adapt the absolute MSF to an LBA (this field is redundant...)
|
||||
var session = disc.Sessions[i];
|
||||
|
||||
sw.WriteLine("[Session {0}]", i);
|
||||
sw.WriteLine("PreGapMode=2");
|
||||
sw.WriteLine("PreGapSubC=1");
|
||||
sw.WriteLine();
|
||||
}
|
||||
|
||||
for (var j = 0; j < session.RawTOCEntries.Count; j++)
|
||||
{
|
||||
var entry = session.RawTOCEntries[j];
|
||||
|
||||
//this is nonsense, really. the whole CCD track list shouldn't be needed.
|
||||
//but in order to make a high quality CCD which can be inspected by various other tools, we need it
|
||||
//now, regarding the indexes.. theyre truly useless. having indexes written out with the tracks is bad news.
|
||||
//index information is only truly stored in subQ
|
||||
for (int tnum = 1; tnum <= disc.Session1.LastInformationTrack.Number; tnum++)
|
||||
{
|
||||
var track = disc.Session1.Tracks[tnum];
|
||||
sw.WriteLine("[TRACK {0}]", track.Number);
|
||||
sw.WriteLine("MODE={0}", track.Mode);
|
||||
//indexes are BS, don't write them. but we certainly need an index 1
|
||||
sw.WriteLine("INDEX 1={0}", track.LBA);
|
||||
sw.WriteLine();
|
||||
//ehhh something's wrong with how I track these
|
||||
var point = entry.QData.q_index.DecimalValue;
|
||||
if (point == 100) point = 0xA0;
|
||||
if (point == 101) point = 0xA1;
|
||||
if (point == 102) point = 0xA2;
|
||||
|
||||
sw.WriteLine("[Entry {0}]", j);
|
||||
sw.WriteLine("Session={0}", i);
|
||||
sw.WriteLine("Point=0x{0:x2}", point);
|
||||
sw.WriteLine("ADR=0x{0:x2}", entry.QData.ADR);
|
||||
sw.WriteLine("Control=0x{0:x2}", (int)entry.QData.CONTROL);
|
||||
sw.WriteLine("TrackNo={0}", entry.QData.q_tno.DecimalValue);
|
||||
sw.WriteLine("AMin={0}", entry.QData.min.DecimalValue);
|
||||
sw.WriteLine("ASec={0}", entry.QData.sec.DecimalValue);
|
||||
sw.WriteLine("AFrame={0}", entry.QData.frame.DecimalValue);
|
||||
sw.WriteLine("ALBA={0}", entry.QData.Timestamp - 150); //remember to adapt the absolute MSF to an LBA (this field is redundant...)
|
||||
sw.WriteLine("Zero={0}", entry.QData.zero);
|
||||
sw.WriteLine("PMin={0}", entry.QData.ap_min.DecimalValue);
|
||||
sw.WriteLine("PSec={0}", entry.QData.ap_sec.DecimalValue);
|
||||
sw.WriteLine("PFrame={0}", entry.QData.ap_frame.DecimalValue);
|
||||
sw.WriteLine("PLBA={0}", entry.QData.AP_Timestamp - 150); //remember to adapt the absolute MSF to an LBA (this field is redundant...)
|
||||
sw.WriteLine();
|
||||
}
|
||||
|
||||
//this is nonsense, really. the whole CCD track list shouldn't be needed.
|
||||
//but in order to make a high quality CCD which can be inspected by various other tools, we need it
|
||||
//now, regarding the indexes.. theyre truly useless. having indexes written out with the tracks is bad news.
|
||||
//index information is only truly stored in subQ
|
||||
for (var tnum = session.FirstInformationTrack.Number; tnum <= session.LastInformationTrack.Number; tnum++)
|
||||
{
|
||||
var track = session.Tracks[tnum];
|
||||
sw.WriteLine("[TRACK {0}]", track.Number);
|
||||
sw.WriteLine("MODE={0}", track.Mode);
|
||||
//indexes are BS, don't write them. but we certainly need an index 1
|
||||
sw.WriteLine("INDEX 1={0}", track.LBA);
|
||||
sw.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO - actually re-add
|
||||
//dump the img and sub
|
||||
//TODO - acquire disk size first
|
||||
string imgPath = Path.ChangeExtension(path, ".img");
|
||||
string subPath = Path.ChangeExtension(path, ".sub");
|
||||
var imgPath = Path.ChangeExtension(path, ".img");
|
||||
var subPath = Path.ChangeExtension(path, ".sub");
|
||||
var buf2448 = new byte[2448];
|
||||
DiscSectorReader dsr = new DiscSectorReader(disc);
|
||||
var dsr = new DiscSectorReader(disc);
|
||||
|
||||
using var imgFile = File.OpenWrite(imgPath);
|
||||
using var subFile = File.OpenWrite(subPath);
|
||||
int nLBA = disc.Session1.LeadoutLBA;
|
||||
for (int lba = 0; lba < nLBA; lba++)
|
||||
var nLBA = disc.Sessions[disc.Sessions.Count - 1].LeadoutLBA;
|
||||
for (var lba = 0; lba < nLBA; lba++)
|
||||
{
|
||||
dsr.ReadLBA_2448(lba, buf2448, 0);
|
||||
imgFile.Write(buf2448, 0, 2352);
|
||||
|
@ -446,8 +453,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public void Synth(SectorSynthJob job)
|
||||
{
|
||||
//CCD is always containing everything we'd need (unless a .sub is missing?) so don't worry about flags
|
||||
var imgBlob = job.Disc.DisposableResources[0] as IBlob;
|
||||
var subBlob = job.Disc.DisposableResources[1] as IBlob;
|
||||
var imgBlob = (IBlob) job.Disc.DisposableResources[0];
|
||||
var subBlob = (IBlob) job.Disc.DisposableResources[1];
|
||||
//Read_2442(job.LBA, job.DestBuffer2448, job.DestOffset);
|
||||
|
||||
//read the IMG data if needed
|
||||
|
@ -473,16 +480,16 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
/// <exception cref="CCDParseException">file <paramref name="ccdPath"/> not found, nonexistent IMG file, nonexistent SUB file, IMG or SUB file not multiple of <c>2352 B</c>, or IMG and SUB files differ in length</exception>
|
||||
public Disc LoadCCDToDisc(string ccdPath, DiscMountPolicy IN_DiscMountPolicy)
|
||||
public static Disc LoadCCDToDisc(string ccdPath, DiscMountPolicy IN_DiscMountPolicy)
|
||||
{
|
||||
var loadResults = LoadCCDPath(ccdPath);
|
||||
if (!loadResults.Valid)
|
||||
throw loadResults.FailureException;
|
||||
|
||||
Disc disc = new Disc();
|
||||
var disc = new Disc();
|
||||
|
||||
IBlob imgBlob = null, subBlob = null;
|
||||
long imgLen = -1, subLen;
|
||||
IBlob imgBlob = null;
|
||||
long imgLen = -1;
|
||||
|
||||
//mount the IMG file
|
||||
//first check for a .ecm in place of the img
|
||||
|
@ -512,39 +519,49 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
//mount the SUB file
|
||||
if (!File.Exists(loadResults.SubPath)) throw new CCDParseException("Malformed CCD format: nonexistent SUB file!");
|
||||
var subFile = new Blob_RawFile() { PhysicalPath = loadResults.SubPath };
|
||||
subBlob = subFile;
|
||||
disc.DisposableResources.Add(subBlob);
|
||||
subLen = subFile.Length;
|
||||
var subFile = new Blob_RawFile { PhysicalPath = loadResults.SubPath };
|
||||
disc.DisposableResources.Add(subFile);
|
||||
var subLen = subFile.Length;
|
||||
|
||||
//quick integrity check of file sizes
|
||||
if (imgLen % 2352 != 0) throw new CCDParseException("Malformed CCD format: IMG file length not multiple of 2352");
|
||||
int NumImgSectors = (int)(imgLen / 2352);
|
||||
var NumImgSectors = (int)(imgLen / 2352);
|
||||
if (subLen != NumImgSectors * 96) throw new CCDParseException("Malformed CCD format: SUB file length not matching IMG");
|
||||
|
||||
var ccdf = loadResults.ParsedCCDFile;
|
||||
|
||||
//the only instance of a sector synthesizer we'll need
|
||||
SS_CCD synth = new SS_CCD();
|
||||
var synth = new SS_CCD();
|
||||
|
||||
// create the initial session
|
||||
var curSession = 1;
|
||||
disc.Sessions.Add(new() { Number = curSession });
|
||||
|
||||
//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>();
|
||||
foreach (var entry in ccdf.TOCEntries)
|
||||
foreach (var entry in ccdf.TOCEntries.OrderBy(te => te.Session))
|
||||
{
|
||||
BCD2 tno, ino;
|
||||
if (entry.Session != curSession)
|
||||
{
|
||||
if (entry.Session != curSession + 1)
|
||||
throw new CCDParseException("Malformed CCD format: Session incremented more than one");
|
||||
curSession = entry.Session;
|
||||
disc.Sessions.Add(new() { Number = curSession });
|
||||
}
|
||||
|
||||
//this should actually be zero. im not sure if this is stored as BCD2 or not
|
||||
tno = BCD2.FromDecimal(entry.TrackNo);
|
||||
|
||||
var tno = BCD2.FromDecimal(entry.TrackNo);
|
||||
|
||||
//these are special values.. I think, taken from this:
|
||||
//http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html
|
||||
//the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD.
|
||||
//Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing.
|
||||
ino = BCD2.FromDecimal(entry.Point);
|
||||
if (entry.Point == 0xA0) ino.BCDValue = 0xA0;
|
||||
else if (entry.Point == 0xA1) ino.BCDValue = 0xA1;
|
||||
else if (entry.Point == 0xA2) ino.BCDValue = 0xA2;
|
||||
var ino = BCD2.FromDecimal(entry.Point);
|
||||
ino.BCDValue = entry.Point switch
|
||||
{
|
||||
0xA0 or 0xA1 or 0xA2 => (byte)entry.Point,
|
||||
_ => ino.BCDValue
|
||||
};
|
||||
|
||||
var q = new SubchannelQ
|
||||
{
|
||||
|
@ -561,28 +578,30 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
q_crc = 0, //meaningless
|
||||
};
|
||||
|
||||
disc.RawTOCEntries.Add(new RawTOCEntry { QData = q });
|
||||
disc.Sessions[curSession].RawTOCEntries.Add(new() { QData = q });
|
||||
}
|
||||
|
||||
//analyze the RAWTocEntries to figure out what type of track track 1 is
|
||||
var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job(disc.RawTOCEntries);
|
||||
var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job(disc.Session1.RawTOCEntries);
|
||||
tocSynth.Run();
|
||||
|
||||
//Add sectors for the mandatory track 1 pregap, which isn't stored in the CCD file
|
||||
//We reuse some CUE code for this.
|
||||
//If we load other formats later we might should abstract this even further (to a synthesizer job)
|
||||
//It can't really be abstracted from cue files though due to the necessity of merging this with other track 1 pregaps
|
||||
CUE.CueTrackType pregapTrackType = CUE.CueTrackType.Audio;
|
||||
var pregapTrackType = CUE.CueTrackType.Audio;
|
||||
if (tocSynth.Result.TOCItems[1].IsData)
|
||||
{
|
||||
if (tocSynth.Result.Session1Format == SessionFormat.Type20_CDXA)
|
||||
pregapTrackType = CUE.CueTrackType.Mode2_2352;
|
||||
else if (tocSynth.Result.Session1Format == SessionFormat.Type10_CDI)
|
||||
pregapTrackType = CUE.CueTrackType.CDI_2352;
|
||||
else if (tocSynth.Result.Session1Format == SessionFormat.Type00_CDROM_CDDA)
|
||||
pregapTrackType = CUE.CueTrackType.Mode1_2352;
|
||||
pregapTrackType = tocSynth.Result.SessionFormat switch
|
||||
{
|
||||
SessionFormat.Type20_CDXA => CUE.CueTrackType.Mode2_2352,
|
||||
SessionFormat.Type10_CDI => CUE.CueTrackType.CDI_2352,
|
||||
SessionFormat.Type00_CDROM_CDDA => CUE.CueTrackType.Mode1_2352,
|
||||
_ => pregapTrackType
|
||||
};
|
||||
}
|
||||
for (int i = 0; i < 150; i++)
|
||||
|
||||
for (var i = 0; i < 150; i++)
|
||||
{
|
||||
var ss_gap = new CUE.SS_Gap()
|
||||
{
|
||||
|
@ -591,14 +610,14 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
};
|
||||
disc._Sectors.Add(ss_gap);
|
||||
|
||||
int qRelMSF = i - 150;
|
||||
var qRelMSF = i - 150;
|
||||
|
||||
//tweak relMSF due to ambiguity/contradiction in yellowbook docs
|
||||
if (!IN_DiscMountPolicy.CUE_PregapContradictionModeA)
|
||||
qRelMSF++;
|
||||
|
||||
//setup subQ
|
||||
byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
|
||||
const byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
|
||||
ss_gap.sq.SetStatus(ADR, tocSynth.Result.TOCItems[1].Control);
|
||||
ss_gap.sq.q_tno = BCD2.FromDecimal(1);
|
||||
ss_gap.sq.q_index = BCD2.FromDecimal(0);
|
||||
|
@ -612,17 +631,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//build the sectors:
|
||||
//set up as many sectors as we have img/sub for, even if the TOC doesnt reference them
|
||||
//(the TOC is unreliable, and the Track records are redundant)
|
||||
for (int i = 0; i < NumImgSectors; i++)
|
||||
for (var i = 0; i < NumImgSectors; i++)
|
||||
{
|
||||
disc._Sectors.Add(synth);
|
||||
}
|
||||
|
||||
return disc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} //class CCD_Format
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
FileMSF = fileMSF;
|
||||
}
|
||||
|
||||
public override readonly string ToString() => $"I#{Number:D2} {FileMSF}";
|
||||
public override string ToString() => $"I#{Number:D2} {FileMSF}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -79,7 +79,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
}
|
||||
|
||||
internal class CompiledDiscInfo
|
||||
internal class CompiledSessionInfo
|
||||
{
|
||||
public int FirstRecordedTrackNumber, LastRecordedTrackNumber;
|
||||
public SessionFormat SessionFormat;
|
||||
|
@ -89,6 +89,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
{
|
||||
public int BlobIndex;
|
||||
public int Number;
|
||||
public int Session;
|
||||
|
||||
/// <summary>
|
||||
/// A track that's final in a file gets its length from the length of the file; other tracks lengths are determined from the succeeding track
|
||||
|
@ -101,7 +102,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
/// </summary>
|
||||
public bool IsFirstInFile;
|
||||
|
||||
public CompiledCDText CDTextData = new CompiledCDText();
|
||||
public readonly CompiledCDText CDTextData = new();
|
||||
public Timestamp PregapLength, PostgapLength;
|
||||
public CueTrackFlags Flags = CueTrackFlags.None;
|
||||
public CueTrackType TrackType = CueTrackType.Unknown;
|
||||
|
@ -132,9 +133,9 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// output: high level disc info
|
||||
/// output: high level session info (most of the time, this only has 1 session)
|
||||
/// </summary>
|
||||
public CompiledDiscInfo OUT_CompiledDiscInfo { get; private set; }
|
||||
public List<CompiledSessionInfo> OUT_CompiledSessionInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// output: CD-Text set at the global level (before any track commands)
|
||||
|
@ -160,37 +161,33 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
/// </summary>
|
||||
public int OUT_LoadTime { get; private set; }
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
private CompiledCDText curr_cdtext;
|
||||
private int curr_blobIndex = -1;
|
||||
private CompiledCueTrack curr_track = null;
|
||||
private CompiledCueFile curr_file = null;
|
||||
private bool discinfo_session1Format_determined = false;
|
||||
private bool curr_fileHasTrack = false;
|
||||
private int curr_session = 1;
|
||||
private CompiledCueTrack curr_track;
|
||||
private CompiledCueFile curr_file;
|
||||
private bool sessionFormatDetermined;
|
||||
private bool curr_fileHasTrack;
|
||||
|
||||
private void UpdateDiscInfo(CUE_File.Command.TRACK trackCommand)
|
||||
{
|
||||
if (OUT_CompiledDiscInfo.FirstRecordedTrackNumber == 0)
|
||||
OUT_CompiledDiscInfo.FirstRecordedTrackNumber = trackCommand.Number;
|
||||
OUT_CompiledDiscInfo.LastRecordedTrackNumber = trackCommand.Number;
|
||||
if (!discinfo_session1Format_determined)
|
||||
var sessionInfo = OUT_CompiledSessionInfo[curr_session];
|
||||
if (sessionInfo.FirstRecordedTrackNumber == 0)
|
||||
sessionInfo.FirstRecordedTrackNumber = trackCommand.Number;
|
||||
sessionInfo.LastRecordedTrackNumber = trackCommand.Number;
|
||||
if (!sessionFormatDetermined)
|
||||
{
|
||||
switch (trackCommand.Type)
|
||||
{
|
||||
case CueTrackType.Mode2_2336:
|
||||
case CueTrackType.Mode2_2352:
|
||||
OUT_CompiledDiscInfo.SessionFormat = SessionFormat.Type20_CDXA;
|
||||
discinfo_session1Format_determined = true;
|
||||
sessionInfo.SessionFormat = SessionFormat.Type20_CDXA;
|
||||
sessionFormatDetermined = true;
|
||||
break;
|
||||
|
||||
case CueTrackType.CDI_2336:
|
||||
case CueTrackType.CDI_2352:
|
||||
OUT_CompiledDiscInfo.SessionFormat = SessionFormat.Type10_CDI;
|
||||
discinfo_session1Format_determined = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
sessionInfo.SessionFormat = SessionFormat.Type10_CDI;
|
||||
sessionFormatDetermined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +217,6 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
//TODO - smart audio file resolving only for AUDIO types. not BINARY or MOTOROLA or AIFF or ECM or what have you
|
||||
|
||||
var options = Resolver.Resolve(f.Path);
|
||||
string choice = null;
|
||||
if (options.Count == 0)
|
||||
{
|
||||
Error($"Couldn't resolve referenced cue file: {f.Path} ; you can commonly repair the cue file yourself, or a file might be missing");
|
||||
|
@ -228,12 +224,10 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
OUT_CompiledCueFiles.Add(null);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
choice = options[0];
|
||||
if (options.Count > 1)
|
||||
Warn($"Multiple options resolving referenced cue file; choosing: {Path.GetFileName(choice)}");
|
||||
}
|
||||
|
||||
var choice = options[0];
|
||||
if (options.Count > 1)
|
||||
Warn($"Multiple options resolving referenced cue file; choosing: {Path.GetFileName(choice)}");
|
||||
|
||||
var cfi = new CompiledCueFile();
|
||||
curr_file = cfi;
|
||||
|
@ -244,43 +238,53 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
//determine the CueFileInfo's type, based on extension and extra checking
|
||||
//TODO - once we reorganize the file ID stuff, do legit checks here (this is completely redundant with the fileID system
|
||||
//TODO - decode vs stream vs unpossible policies in input policies object (including ffmpeg availability-checking callback (results can be cached))
|
||||
string blobPathExt = Path.GetExtension(choice).ToUpperInvariant();
|
||||
if (blobPathExt == ".BIN" || blobPathExt == ".IMG") cfi.Type = CompiledCueFileType.BIN;
|
||||
else if (blobPathExt == ".ISO") cfi.Type = CompiledCueFileType.BIN;
|
||||
else if (blobPathExt == ".WAV")
|
||||
var blobPathExt = Path.GetExtension(choice).ToUpperInvariant();
|
||||
switch (blobPathExt)
|
||||
{
|
||||
//quickly, check the format. turn it to DecodeAudio if it can't be supported
|
||||
//TODO - fix exception-throwing inside
|
||||
//TODO - verify stream-disposing semantics
|
||||
var fs = File.OpenRead(choice);
|
||||
using var blob = new Blob_WaveFile();
|
||||
try
|
||||
case ".BIN" or ".IMG" or ".RAW":
|
||||
case ".ISO":
|
||||
cfi.Type = CompiledCueFileType.BIN;
|
||||
break;
|
||||
case ".WAV":
|
||||
{
|
||||
blob.Load(fs);
|
||||
cfi.Type = CompiledCueFileType.WAVE;
|
||||
//quickly, check the format. turn it to DecodeAudio if it can't be supported
|
||||
//TODO - fix exception-throwing inside
|
||||
//TODO - verify stream-disposing semantics
|
||||
var fs = File.OpenRead(choice);
|
||||
using var blob = new Blob_WaveFile();
|
||||
try
|
||||
{
|
||||
blob.Load(fs);
|
||||
cfi.Type = CompiledCueFileType.WAVE;
|
||||
}
|
||||
catch
|
||||
{
|
||||
cfi.Type = CompiledCueFileType.DecodeAudio;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
case ".APE":
|
||||
case ".MP3":
|
||||
case ".MPC":
|
||||
case ".FLAC":
|
||||
cfi.Type = CompiledCueFileType.DecodeAudio;
|
||||
}
|
||||
}
|
||||
else if (blobPathExt == ".APE") cfi.Type = CompiledCueFileType.DecodeAudio;
|
||||
else if (blobPathExt == ".MP3") cfi.Type = CompiledCueFileType.DecodeAudio;
|
||||
else if (blobPathExt == ".MPC") cfi.Type = CompiledCueFileType.DecodeAudio;
|
||||
else if (blobPathExt == ".FLAC") cfi.Type = CompiledCueFileType.DecodeAudio;
|
||||
else if (blobPathExt == ".ECM")
|
||||
{
|
||||
cfi.Type = CompiledCueFileType.ECM;
|
||||
if (!Blob_ECM.IsECM(choice))
|
||||
break;
|
||||
case ".ECM":
|
||||
{
|
||||
Error($"an ECM file was specified or detected, but it isn't a valid ECM file: {Path.GetFileName(choice)}");
|
||||
cfi.Type = CompiledCueFileType.Unknown;
|
||||
cfi.Type = CompiledCueFileType.ECM;
|
||||
if (!Blob_ECM.IsECM(choice))
|
||||
{
|
||||
Error($"an ECM file was specified or detected, but it isn't a valid ECM file: {Path.GetFileName(choice)}");
|
||||
cfi.Type = CompiledCueFileType.Unknown;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error($"Unknown cue file type. Since it's likely an unsupported compression, this is an error: {Path.GetFileName(choice)}");
|
||||
cfi.Type = CompiledCueFileType.Unknown;
|
||||
default:
|
||||
Error($"Unknown cue file type. Since it's likely an unsupported compression, this is an error: {Path.GetFileName(choice)}");
|
||||
cfi.Type = CompiledCueFileType.Unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
//TODO - check for mismatches between track types and file types, or is that best done when interpreting the commands?
|
||||
|
@ -289,7 +293,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
private void CreateTrack1Pregap()
|
||||
{
|
||||
if (OUT_CompiledCueTracks[1].PregapLength.Sector is not (0 or 150)) Error("Track 1 specified an illegal pregap. It's being ignored and replaced with a 00:02:00 pregap");
|
||||
OUT_CompiledCueTracks[1].PregapLength = new Timestamp(150);
|
||||
OUT_CompiledCueTracks[1].PregapLength = new(150);
|
||||
}
|
||||
|
||||
private void FinalAnalysis()
|
||||
|
@ -304,21 +308,23 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
//we could check the format of the wav file here, though
|
||||
|
||||
//score the cost of loading the file
|
||||
bool needsCodec = false;
|
||||
var needsCodec = false;
|
||||
OUT_LoadTime = 0;
|
||||
foreach (var cfi in OUT_CompiledCueFiles)
|
||||
foreach (var cfi in OUT_CompiledCueFiles.Where(cfi => cfi is not null))
|
||||
{
|
||||
if (cfi == null)
|
||||
continue;
|
||||
if (cfi.Type == CompiledCueFileType.DecodeAudio)
|
||||
switch (cfi.Type)
|
||||
{
|
||||
needsCodec = true;
|
||||
OUT_LoadTime = Math.Max(OUT_LoadTime, 10);
|
||||
case CompiledCueFileType.DecodeAudio:
|
||||
needsCodec = true;
|
||||
OUT_LoadTime = Math.Max(OUT_LoadTime, 10);
|
||||
break;
|
||||
case CompiledCueFileType.SeekAudio:
|
||||
needsCodec = true;
|
||||
break;
|
||||
case CompiledCueFileType.ECM:
|
||||
OUT_LoadTime = Math.Max(OUT_LoadTime, 1);
|
||||
break;
|
||||
}
|
||||
if (cfi.Type == CompiledCueFileType.SeekAudio)
|
||||
needsCodec = true;
|
||||
if (cfi.Type == CompiledCueFileType.ECM)
|
||||
OUT_LoadTime = Math.Max(OUT_LoadTime, 1);
|
||||
}
|
||||
|
||||
//check whether processing was available
|
||||
|
@ -328,7 +334,6 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private void CloseTrack()
|
||||
{
|
||||
if (curr_track == null)
|
||||
|
@ -341,10 +346,10 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
//this is the kind of thing I sought to solve originally by 'interpreting' the file, but it seems easy enough to handle this way
|
||||
//my carlin.cue tests this but test cases shouldn't be hard to find
|
||||
var fileMSF = curr_track.IsFirstInFile
|
||||
? new Timestamp(0)
|
||||
? new(0)
|
||||
: curr_track.Indexes[0].FileMSF; // else, same MSF as index 1 will make it effectively nonexistent
|
||||
|
||||
curr_track.Indexes.Insert(0, new CompiledCueIndex(0, fileMSF));
|
||||
curr_track.Indexes.Insert(0, new(0, fileMSF));
|
||||
}
|
||||
|
||||
OUT_CompiledCueTracks.Add(curr_track);
|
||||
|
@ -354,18 +359,19 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
private void OpenTrack(CUE_File.Command.TRACK trackCommand)
|
||||
{
|
||||
//assert that a file is open
|
||||
if(curr_file == null)
|
||||
if (curr_file == null)
|
||||
{
|
||||
Error("Track command encountered with no active file");
|
||||
throw new DiscJobAbortException();
|
||||
}
|
||||
|
||||
curr_track = new CompiledCueTrack();
|
||||
curr_track = new();
|
||||
|
||||
//spill cdtext data into this track
|
||||
curr_cdtext = curr_track.CDTextData;
|
||||
|
||||
curr_track.BlobIndex = curr_blobIndex;
|
||||
curr_track.Session = curr_session;
|
||||
curr_track.Number = trackCommand.Number;
|
||||
curr_track.TrackType = trackCommand.Type;
|
||||
|
||||
|
@ -383,31 +389,31 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
private void AddIndex(CUE_File.Command.INDEX indexCommand)
|
||||
{
|
||||
curr_track.Indexes.Add(new CompiledCueIndex(indexCommand.Number, indexCommand.Timestamp));
|
||||
curr_track.Indexes.Add(new(indexCommand.Number, indexCommand.Timestamp));
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
//in params
|
||||
var cue = IN_CueFile;
|
||||
|
||||
//output state
|
||||
OUT_GlobalCDText = new CompiledCDText();
|
||||
OUT_CompiledDiscInfo = new CompiledDiscInfo();
|
||||
OUT_CompiledCueFiles = new List<CompiledCueFile>();
|
||||
OUT_CompiledCueTracks = new List<CompiledCueTrack>();
|
||||
OUT_GlobalCDText = new();
|
||||
OUT_CompiledCueFiles = new();
|
||||
OUT_CompiledCueTracks = new();
|
||||
|
||||
//add a track 0, for addressing convenience.
|
||||
//note: for future work, track 0 may need emulation (accessible by very negative LBA--the TOC is stored there)
|
||||
var track0 = new CompiledCueTrack() {
|
||||
var track0 = new CompiledCueTrack
|
||||
{
|
||||
Number = 0,
|
||||
};
|
||||
OUT_CompiledCueTracks.Add(track0);
|
||||
|
||||
// similarly, session 0 is added as a null entry, with session 1 added in for the actual first entry
|
||||
OUT_CompiledSessionInfo = new() { null, new() };
|
||||
|
||||
//global cd text will acquire the cdtext commands set before track commands
|
||||
curr_cdtext = OUT_GlobalCDText;
|
||||
|
||||
foreach (var cmd in cue.Commands) switch (cmd)
|
||||
foreach (var cmd in IN_CueFile.Commands) switch (cmd)
|
||||
{
|
||||
case CUE_File.Command.CATALOG:
|
||||
case CUE_File.Command.CDTEXTFILE:
|
||||
|
@ -435,6 +441,16 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
if (curr_track == null) Warn("Ignoring invalid flag commands outside of a track command");
|
||||
else curr_track.Flags |= flagsCmd.Flags; // take care to |= it here, so the data flag doesn't get cleared
|
||||
break;
|
||||
case CUE_File.Command.SESSION session:
|
||||
if (session.Number == curr_session) break; // this may occur for SESSION 1 at the beginning, so we'll silence warnings from this
|
||||
if (session.Number != curr_session + 1) Warn("Ignoring non-sequential session commands"); // TODO: should this be allowed? doesn't make sense here...
|
||||
else
|
||||
{
|
||||
curr_session = session.Number;
|
||||
OUT_CompiledSessionInfo.Add(new());
|
||||
sessionFormatDetermined = false;
|
||||
}
|
||||
break;
|
||||
case CUE_File.Command.TRACK trackCmd:
|
||||
CloseTrack();
|
||||
OpenTrack(trackCmd);
|
||||
|
@ -466,9 +482,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
FinalAnalysis();
|
||||
|
||||
FinishLog();
|
||||
|
||||
} //Run()
|
||||
}
|
||||
|
||||
} //class CompileCueJob
|
||||
|
||||
} //namespace BizHawk.Emulation.DiscSystem
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
//http://digitalx.org/cue-sheet/index.html "all cue sheet information is a straight 1:1 copy from the cdrwin helpfile"
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem.CUE
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public CATALOG(string value) => Value = value;
|
||||
|
||||
public override readonly string ToString() => $"CATALOG: {Value}";
|
||||
public readonly override string ToString() => $"CATALOG: {Value}";
|
||||
}
|
||||
|
||||
public readonly struct CDTEXTFILE : Command
|
||||
|
@ -28,7 +28,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public CDTEXTFILE(string path) => Path = path;
|
||||
|
||||
public override readonly string ToString() => $"CDTEXTFILE: {Path}";
|
||||
public override string ToString() => $"CDTEXTFILE: {Path}";
|
||||
}
|
||||
|
||||
public readonly struct FILE : Command
|
||||
|
@ -43,7 +43,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
Type = type;
|
||||
}
|
||||
|
||||
public override readonly string ToString() => $"FILE ({Type}): {Path}";
|
||||
public override string ToString() => $"FILE ({Type}): {Path}";
|
||||
}
|
||||
|
||||
public readonly struct FLAGS : Command
|
||||
|
@ -52,7 +52,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public FLAGS(CueTrackFlags flags) => Flags = flags;
|
||||
|
||||
public override readonly string ToString() => $"FLAGS {Flags}";
|
||||
public override string ToString() => $"FLAGS {Flags}";
|
||||
}
|
||||
|
||||
public readonly struct INDEX : Command
|
||||
|
@ -67,7 +67,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
Timestamp = timestamp;
|
||||
}
|
||||
|
||||
public override readonly string ToString() => $"INDEX {Number,2} {Timestamp}";
|
||||
public override string ToString() => $"INDEX {Number,2} {Timestamp}";
|
||||
}
|
||||
|
||||
public readonly struct ISRC : Command
|
||||
|
@ -76,7 +76,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public ISRC(string value) => Value = value;
|
||||
|
||||
public override readonly string ToString() => $"ISRC: {Value}";
|
||||
public override string ToString() => $"ISRC: {Value}";
|
||||
}
|
||||
|
||||
public readonly struct PERFORMER : Command
|
||||
|
@ -85,7 +85,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public PERFORMER(string value) => Value = value;
|
||||
|
||||
public override readonly string ToString() => $"PERFORMER: {Value}";
|
||||
public override string ToString() => $"PERFORMER: {Value}";
|
||||
}
|
||||
|
||||
public readonly struct POSTGAP : Command
|
||||
|
@ -94,7 +94,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public POSTGAP(Timestamp length) => Length = length;
|
||||
|
||||
public override readonly string ToString() => $"POSTGAP: {Length}";
|
||||
public override string ToString() => $"POSTGAP: {Length}";
|
||||
}
|
||||
|
||||
public readonly struct PREGAP : Command
|
||||
|
@ -103,7 +103,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public PREGAP(Timestamp length) => Length = length;
|
||||
|
||||
public override readonly string ToString() => $"PREGAP: {Length}";
|
||||
public override string ToString() => $"PREGAP: {Length}";
|
||||
}
|
||||
|
||||
public readonly struct REM : Command
|
||||
|
@ -112,7 +112,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public REM(string value) => Value = value;
|
||||
|
||||
public override readonly string ToString() => $"REM: {Value}";
|
||||
public override string ToString() => $"REM: {Value}";
|
||||
}
|
||||
|
||||
public readonly struct COMMENT : Command
|
||||
|
@ -121,7 +121,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public COMMENT(string value) => Value = value;
|
||||
|
||||
public override readonly string ToString() => $"COMMENT: {Value}";
|
||||
public override string ToString() => $"COMMENT: {Value}";
|
||||
}
|
||||
|
||||
public readonly struct SONGWRITER : Command
|
||||
|
@ -130,7 +130,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public SONGWRITER(string value) => Value = value;
|
||||
|
||||
public override readonly string ToString() => $"SONGWRITER: {Value}";
|
||||
public override string ToString() => $"SONGWRITER: {Value}";
|
||||
}
|
||||
|
||||
public readonly struct TITLE : Command
|
||||
|
@ -139,7 +139,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public TITLE(string value) => Value = value;
|
||||
|
||||
public override readonly string ToString() => $"TITLE: {Value}";
|
||||
public override string ToString() => $"TITLE: {Value}";
|
||||
}
|
||||
|
||||
public readonly struct TRACK : Command
|
||||
|
@ -154,7 +154,18 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
Type = type;
|
||||
}
|
||||
|
||||
public override readonly string ToString() => $"TRACK {Number,2} ({Type})";
|
||||
public override string ToString() => $"TRACK {Number,2} ({Type})";
|
||||
}
|
||||
|
||||
// This doesn't exist officially, rather it is derived from special REM comments
|
||||
// Consider this an "extension" perhaps?
|
||||
public readonly struct SESSION : Command
|
||||
{
|
||||
public readonly int Number;
|
||||
|
||||
public SESSION(int number) => Number = number;
|
||||
|
||||
public override string ToString() => $"SESSION {Number}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,6 +187,6 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
/// <summary>
|
||||
/// Stuff other than the commands, global for the whole disc
|
||||
/// </summary>
|
||||
public DiscInfo GlobalDiscInfo = new DiscInfo();
|
||||
public readonly DiscInfo GlobalDiscInfo = new();
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem.CUE
|
||||
|
@ -72,19 +71,17 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
|
||||
private List<BlobInfo> BlobInfos;
|
||||
private readonly List<TrackInfo> TrackInfos = new List<TrackInfo>();
|
||||
|
||||
private readonly List<TrackInfo> TrackInfos = new();
|
||||
|
||||
private void MountBlobs()
|
||||
{
|
||||
IBlob file_blob = null;
|
||||
|
||||
BlobInfos = new List<BlobInfo>();
|
||||
BlobInfos = new();
|
||||
foreach (var ccf in IN_CompileJob.OUT_CompiledCueFiles)
|
||||
{
|
||||
var bi = new BlobInfo();
|
||||
BlobInfos.Add(bi);
|
||||
|
||||
IBlob file_blob;
|
||||
switch (ccf.Type)
|
||||
{
|
||||
case CompiledCueFileType.BIN:
|
||||
|
@ -118,8 +115,8 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
{
|
||||
throw new DiscReferenceException(ccf.FullPath, "No decoding service was available (make sure ffmpeg.exe is available. Even though this may be a wav, ffmpeg is used to load oddly formatted wave files. If you object to this, please send us a note and we'll see what we can do. It shouldn't be too hard.)");
|
||||
}
|
||||
AudioDecoder dec = new AudioDecoder();
|
||||
byte[] buf = dec.AcquireWaveData(ccf.FullPath);
|
||||
AudioDecoder dec = new();
|
||||
var buf = dec.AcquireWaveData(ccf.FullPath);
|
||||
var blob = new Blob_WaveFile();
|
||||
OUT_Disc.DisposableResources.Add(file_blob = blob);
|
||||
blob.Load(new MemoryStream(buf));
|
||||
|
@ -128,23 +125,20 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
} //switch(file type)
|
||||
}
|
||||
|
||||
//wrap all the blobs with zero padding
|
||||
bi.Blob = new Blob_ZeroPadAdapter(file_blob, bi.Length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void AnalyzeTracks()
|
||||
{
|
||||
var compiledTracks = IN_CompileJob.OUT_CompiledCueTracks;
|
||||
|
||||
for(int t=0;t<compiledTracks.Count;t++)
|
||||
foreach (var cct in compiledTracks)
|
||||
{
|
||||
var cct = compiledTracks[t];
|
||||
|
||||
TrackInfos.Add(new TrackInfo(cct));
|
||||
TrackInfos.Add(new(cct));
|
||||
|
||||
//OH NO! CANT DO THIS!
|
||||
//need to read sectors from file to reliably know its ending size.
|
||||
|
@ -158,11 +152,13 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
}
|
||||
|
||||
private DiscSession CurrentSession = new() { Number = 1 };
|
||||
|
||||
private void EmitRawTOCEntry(CompiledCueTrack cct)
|
||||
{
|
||||
SubchannelQ toc_sq = default;
|
||||
//absent some kind of policy for how to set it, this is a safe assumption:
|
||||
byte toc_ADR = 1;
|
||||
const byte toc_ADR = 1;
|
||||
toc_sq.SetStatus(toc_ADR, (EControlQ)(int)cct.Flags);
|
||||
toc_sq.q_tno.BCDValue = 0; //kind of a little weird here.. the track number becomes the 'point' and put in the index instead. 0 is the track number here.
|
||||
toc_sq.q_index = BCD2.FromDecimal(cct.Number);
|
||||
|
@ -171,20 +167,33 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
toc_sq.sec = BCD2.FromDecimal(0);
|
||||
toc_sq.frame = BCD2.FromDecimal(0);
|
||||
toc_sq.AP_Timestamp = OUT_Disc._Sectors.Count;
|
||||
OUT_Disc.RawTOCEntries.Add(new RawTOCEntry { QData = toc_sq });
|
||||
CurrentSession.RawTOCEntries.Add(new() { QData = toc_sq });
|
||||
}
|
||||
|
||||
private void CloseSession()
|
||||
{
|
||||
//add RawTOCEntries A0 A1 A2 to round out the TOC
|
||||
var sessionInfo = IN_CompileJob.OUT_CompiledSessionInfo[CurrentSession.Number];
|
||||
var TOCMiscInfo = new Synthesize_A0A1A2_Job(
|
||||
firstRecordedTrackNumber: sessionInfo.FirstRecordedTrackNumber,
|
||||
lastRecordedTrackNumber: sessionInfo.LastRecordedTrackNumber,
|
||||
sessionFormat: sessionInfo.SessionFormat,
|
||||
leadoutTimestamp: OUT_Disc._Sectors.Count);
|
||||
|
||||
TOCMiscInfo.Run(CurrentSession.RawTOCEntries);
|
||||
OUT_Disc.Sessions.Add(CurrentSession);
|
||||
CurrentSession = null;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
//params
|
||||
var compiled = IN_CompileJob;
|
||||
var context = compiled.IN_CueContext;
|
||||
OUT_Disc = new Disc();
|
||||
var context = IN_CompileJob.IN_CueContext;
|
||||
OUT_Disc = new();
|
||||
|
||||
//generation state
|
||||
int curr_index;
|
||||
int curr_blobIndex = -1;
|
||||
int curr_blobMSF = -1;
|
||||
var curr_blobIndex = -1;
|
||||
var curr_blobMSF = -1;
|
||||
BlobInfo curr_blobInfo = null;
|
||||
long curr_blobOffset = -1;
|
||||
|
||||
|
@ -197,20 +206,38 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
//loop from track 1 to 99
|
||||
//(track 0 isnt handled yet, that's way distant work)
|
||||
for (int t = 1; t < TrackInfos.Count; t++)
|
||||
for (var t = 1; t < TrackInfos.Count; t++)
|
||||
{
|
||||
TrackInfo ti = TrackInfos[t];
|
||||
CompiledCueTrack cct = ti.CompiledCueTrack;
|
||||
var ti = TrackInfos[t];
|
||||
var cct = ti.CompiledCueTrack;
|
||||
|
||||
if (CurrentSession.Number != cct.Session)
|
||||
{
|
||||
if (cct.Session != CurrentSession.Number + 1)
|
||||
{
|
||||
// error in case a session isn't just incrementing
|
||||
// maybe we can allow this, but that ends up being complicated here
|
||||
// not like any actual rip is going to have nonsequential sessions
|
||||
// edit: actually this is enforced parsing side, so this is probably silly here
|
||||
throw new InvalidOperationException("Track session change was not incremental");
|
||||
}
|
||||
|
||||
CloseSession();
|
||||
CurrentSession = new()
|
||||
{
|
||||
Number = cct.Session
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------
|
||||
//setup track pregap processing
|
||||
//per "Example 05" on digitalx.org, pregap can come from index specification and pregap command
|
||||
int specifiedPregapLength = cct.PregapLength.Sector;
|
||||
int impliedPregapLength = cct.Indexes[1].FileMSF.Sector - cct.Indexes[0].FileMSF.Sector;
|
||||
int totalPregapLength = specifiedPregapLength + impliedPregapLength;
|
||||
var specifiedPregapLength = cct.PregapLength.Sector;
|
||||
var impliedPregapLength = cct.Indexes[1].FileMSF.Sector - cct.Indexes[0].FileMSF.Sector;
|
||||
var totalPregapLength = specifiedPregapLength + impliedPregapLength;
|
||||
|
||||
//from now on we'll track relative timestamp and increment it continually
|
||||
int relMSF = -totalPregapLength;
|
||||
var relMSF = -totalPregapLength;
|
||||
|
||||
//read more at policies declaration
|
||||
//if (!context.DiscMountPolicy.CUE_PauseContradictionModeA)
|
||||
|
@ -231,11 +258,11 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
|
||||
//work until the next track is reached, or the end of the current file is reached, depending on the track type
|
||||
curr_index = 0;
|
||||
for (; ; )
|
||||
var curr_index = 0;
|
||||
while (true)
|
||||
{
|
||||
bool trackDone = false;
|
||||
bool generateGap = false;
|
||||
var trackDone = false;
|
||||
var generateGap = false;
|
||||
|
||||
if (specifiedPregapLength > 0)
|
||||
{
|
||||
|
@ -246,7 +273,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
else
|
||||
{
|
||||
//if burning through the file, select the appropriate index by inspecting the next index and seeing if we've reached it
|
||||
for (; ; )
|
||||
while (true)
|
||||
{
|
||||
if (curr_index == cct.Indexes.Count - 1)
|
||||
break;
|
||||
|
@ -265,8 +292,8 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
//select the track type for the subQ
|
||||
//it's obviously the same as the main track type usually, but during a pregap it can be different
|
||||
TrackInfo qTrack = ti;
|
||||
int qRelMSF = relMSF;
|
||||
var qTrack = ti;
|
||||
var qRelMSF = relMSF;
|
||||
if (curr_index == 0)
|
||||
{
|
||||
//tweak relMSF due to ambiguity/contradiction in yellowbook docs
|
||||
|
@ -287,14 +314,14 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
|
||||
//generate the right kind of sector synth for this track
|
||||
SS_Base ss = null;
|
||||
SS_Base ss;
|
||||
if (generateGap)
|
||||
{
|
||||
ss = new SS_Gap { TrackType = qTrack.CompiledCueTrack.TrackType };
|
||||
}
|
||||
else
|
||||
{
|
||||
int sectorSize = int.MaxValue;
|
||||
int sectorSize;
|
||||
switch (qTrack.CompiledCueTrack.TrackType)
|
||||
{
|
||||
case CueTrackType.Audio:
|
||||
|
@ -315,7 +342,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
throw new InvalidOperationException($"Not supported: {cct.TrackType}");
|
||||
}
|
||||
|
||||
ss.Blob = curr_blobInfo.Blob;
|
||||
ss.Blob = curr_blobInfo!.Blob;
|
||||
ss.BlobOffset = curr_blobOffset;
|
||||
curr_blobOffset += sectorSize;
|
||||
curr_blobMSF++;
|
||||
|
@ -324,7 +351,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
ss.Policy = context.DiscMountPolicy;
|
||||
|
||||
//setup subQ
|
||||
byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
|
||||
const byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
|
||||
ss.sq.SetStatus(ADR, (EControlQ)(int)qTrack.CompiledCueTrack.Flags);
|
||||
ss.sq.q_tno = BCD2.FromDecimal(cct.Number);
|
||||
ss.sq.q_index = BCD2.FromDecimal(curr_index);
|
||||
|
@ -341,7 +368,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
if (cct.IsFinalInFile)
|
||||
{
|
||||
//sometimes, break when the file is exhausted
|
||||
if (curr_blobOffset >= curr_blobInfo.Length)
|
||||
if (curr_blobOffset >= curr_blobInfo!.Length)
|
||||
trackDone = true;
|
||||
}
|
||||
else
|
||||
|
@ -358,16 +385,16 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
//---------------------------------
|
||||
//gen postgap sectors
|
||||
int specifiedPostgapLength = cct.PostgapLength.Sector;
|
||||
for (int s = 0; s < specifiedPostgapLength; s++)
|
||||
var specifiedPostgapLength = cct.PostgapLength.Sector;
|
||||
for (var s = 0; s < specifiedPostgapLength; s++)
|
||||
{
|
||||
var ss = new SS_Gap
|
||||
{
|
||||
TrackType = cct.TrackType // TODO - old track type in some < -150 cases?
|
||||
TrackType = cct.TrackType // TODO - old track type in some < -150 cases?
|
||||
};
|
||||
|
||||
//-subq-
|
||||
byte ADR = 1;
|
||||
const byte ADR = 1;
|
||||
ss.sq.SetStatus(ADR, (EControlQ)(int)cct.Flags);
|
||||
ss.sq.q_tno = BCD2.FromDecimal(cct.Number);
|
||||
ss.sq.q_index = BCD2.FromDecimal(curr_index);
|
||||
|
@ -381,18 +408,9 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
OUT_Disc._Sectors.Add(ss);
|
||||
relMSF++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} //end track loop
|
||||
|
||||
|
||||
//add RawTOCEntries A0 A1 A2 to round out the TOC
|
||||
var TOCMiscInfo = new Synthesize_A0A1A2_Job(
|
||||
firstRecordedTrackNumber: IN_CompileJob.OUT_CompiledDiscInfo.FirstRecordedTrackNumber,
|
||||
lastRecordedTrackNumber: IN_CompileJob.OUT_CompiledDiscInfo.LastRecordedTrackNumber,
|
||||
session1Format: IN_CompileJob.OUT_CompiledDiscInfo.SessionFormat,
|
||||
leadoutTimestamp: OUT_Disc._Sectors.Count);
|
||||
TOCMiscInfo.Run(OUT_Disc.RawTOCEntries);
|
||||
CloseSession();
|
||||
|
||||
//TODO - generate leadout, or delegates at least
|
||||
|
||||
|
@ -400,8 +418,6 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
//OUT_Disc.Structure.Synthesize_TOCPointsFromSessions();
|
||||
|
||||
//FinishLog();
|
||||
|
||||
} //Run()
|
||||
} //class LoadCueJob
|
||||
} //namespace BizHawk.Emulation.DiscSystem
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,8 +45,8 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
public string ReadToken() { return ReadToken(Mode.Normal); }
|
||||
public string ReadLine()
|
||||
{
|
||||
int len = str.Length;
|
||||
string ret = str.Substring(index, len - index);
|
||||
var len = str.Length;
|
||||
var ret = str.Substring(index, len - index);
|
||||
index = len;
|
||||
EOF = true;
|
||||
return ret;
|
||||
|
@ -61,16 +61,16 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
{
|
||||
if (EOF) return null;
|
||||
|
||||
bool isPath = mode == Mode.Quotable;
|
||||
var isPath = mode == Mode.Quotable;
|
||||
|
||||
int startIndex = index;
|
||||
bool inToken = false;
|
||||
bool inQuote = false;
|
||||
var startIndex = index;
|
||||
var inToken = false;
|
||||
var inQuote = false;
|
||||
for (; ; )
|
||||
{
|
||||
bool done = false;
|
||||
char c = str[index];
|
||||
bool isWhiteSpace = (c == ' ' || c == '\t');
|
||||
var done = false;
|
||||
var c = str[index];
|
||||
var isWhiteSpace = c is ' ' or '\t';
|
||||
|
||||
if (isWhiteSpace)
|
||||
{
|
||||
|
@ -86,7 +86,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
else
|
||||
{
|
||||
bool startedQuote = false;
|
||||
var startedQuote = false;
|
||||
if (!inToken)
|
||||
{
|
||||
startIndex = index;
|
||||
|
@ -120,7 +120,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
if (done) break;
|
||||
}
|
||||
|
||||
string ret = str.Substring(startIndex, index - startIndex);
|
||||
var ret = str.Substring(startIndex, index - startIndex);
|
||||
|
||||
if (mode == Mode.Quotable)
|
||||
ret = ret.Trim('"');
|
||||
|
@ -129,28 +129,26 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
}
|
||||
|
||||
private void LoadFromString(ParseCueJob job)
|
||||
private void LoadFromString()
|
||||
{
|
||||
string cueString = job.IN_CueString;
|
||||
TextReader tr = new StringReader(cueString);
|
||||
TextReader tr = new StringReader(IN_CueString);
|
||||
|
||||
for (; ; )
|
||||
while (true)
|
||||
{
|
||||
job.CurrentLine++;
|
||||
string line = tr.ReadLine();
|
||||
if (line == null) break;
|
||||
line = line.Trim();
|
||||
if (line == "") continue;
|
||||
CurrentLine++;
|
||||
var line = tr.ReadLine()?.Trim();
|
||||
if (line is null) break;
|
||||
if (line == string.Empty) continue;
|
||||
var clp = new CueLineParser(line);
|
||||
|
||||
string key = clp.ReadToken().ToUpperInvariant();
|
||||
var key = clp.ReadToken().ToUpperInvariant();
|
||||
|
||||
//remove nonsense at beginning
|
||||
if (!IN_Strict)
|
||||
{
|
||||
while (key.Length > 0)
|
||||
{
|
||||
char c = key[0];
|
||||
var c = key[0];
|
||||
if(c == ';') break;
|
||||
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) break;
|
||||
key = key.Substring(1);
|
||||
|
@ -167,22 +165,22 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
else switch (key)
|
||||
{
|
||||
default:
|
||||
job.Warn($"Unknown command: {key}");
|
||||
Warn($"Unknown command: {key}");
|
||||
break;
|
||||
|
||||
case "CATALOG":
|
||||
if (OUT_CueFile.GlobalDiscInfo.Catalog != null)
|
||||
job.Warn("Multiple CATALOG commands detected. Subsequent ones are ignored.");
|
||||
Warn("Multiple CATALOG commands detected. Subsequent ones are ignored.");
|
||||
else if (clp.EOF)
|
||||
job.Warn("Ignoring empty CATALOG command");
|
||||
Warn("Ignoring empty CATALOG command");
|
||||
else OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.Catalog = new CUE_File.Command.CATALOG(clp.ReadToken()));
|
||||
break;
|
||||
|
||||
case "CDTEXTFILE":
|
||||
if (OUT_CueFile.GlobalDiscInfo.CDTextFile != null)
|
||||
job.Warn("Multiple CDTEXTFILE commands detected. Subsequent ones are ignored.");
|
||||
Warn("Multiple CDTEXTFILE commands detected. Subsequent ones are ignored.");
|
||||
else if (clp.EOF)
|
||||
job.Warn("Ignoring empty CDTEXTFILE command");
|
||||
Warn("Ignoring empty CDTEXTFILE command");
|
||||
else OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.CDTextFile = new CUE_File.Command.CDTEXTFILE(clp.ReadPath()));
|
||||
break;
|
||||
|
||||
|
@ -192,7 +190,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
CueFileType ft;
|
||||
if (clp.EOF)
|
||||
{
|
||||
job.Error("FILE command is missing file type.");
|
||||
Error("FILE command is missing file type.");
|
||||
ft = CueFileType.Unspecified;
|
||||
}
|
||||
else
|
||||
|
@ -201,7 +199,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
switch (strType)
|
||||
{
|
||||
default:
|
||||
job.Error($"Unknown FILE type: {strType}");
|
||||
Error($"Unknown FILE type: {strType}");
|
||||
ft = CueFileType.Unspecified;
|
||||
break;
|
||||
case "BINARY": ft = CueFileType.BINARY; break;
|
||||
|
@ -225,7 +223,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
{
|
||||
case "DATA":
|
||||
default:
|
||||
job.Warn($"Unknown FLAG: {flag}");
|
||||
Warn($"Unknown FLAG: {flag}");
|
||||
break;
|
||||
case "DCP": flags |= CueTrackFlags.DCP; break;
|
||||
case "4CH": flags |= CueTrackFlags._4CH; break;
|
||||
|
@ -234,7 +232,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
}
|
||||
}
|
||||
if (flags == CueTrackFlags.None)
|
||||
job.Warn("Empty FLAG command");
|
||||
Warn("Empty FLAG command");
|
||||
OUT_CueFile.Commands.Add(new CUE_File.Command.FLAGS(flags));
|
||||
}
|
||||
break;
|
||||
|
@ -243,16 +241,16 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
{
|
||||
if (clp.EOF)
|
||||
{
|
||||
job.Error("Incomplete INDEX command");
|
||||
Error("Incomplete INDEX command");
|
||||
break;
|
||||
}
|
||||
string strindexnum = clp.ReadToken();
|
||||
var strindexnum = clp.ReadToken();
|
||||
if (!int.TryParse(strindexnum, out var indexnum) || indexnum < 0 || indexnum > 99)
|
||||
{
|
||||
job.Error($"Invalid INDEX number: {strindexnum}");
|
||||
Error($"Invalid INDEX number: {strindexnum}");
|
||||
break;
|
||||
}
|
||||
string str_timestamp = clp.ReadToken();
|
||||
var str_timestamp = clp.ReadToken();
|
||||
var ts = new Timestamp(str_timestamp);
|
||||
if (!ts.Valid && !IN_Strict)
|
||||
{
|
||||
|
@ -263,7 +261,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
if (!ts.Valid)
|
||||
{
|
||||
if (IN_Strict)
|
||||
job.Error($"Invalid INDEX timestamp: {str_timestamp}");
|
||||
Error($"Invalid INDEX timestamp: {str_timestamp}");
|
||||
break;
|
||||
}
|
||||
OUT_CueFile.Commands.Add(new CUE_File.Command.INDEX(indexnum, ts));
|
||||
|
@ -272,14 +270,14 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
case "ISRC":
|
||||
if (OUT_CueFile.GlobalDiscInfo.ISRC != null)
|
||||
job.Warn("Multiple ISRC commands detected. Subsequent ones are ignored.");
|
||||
Warn("Multiple ISRC commands detected. Subsequent ones are ignored.");
|
||||
else if (clp.EOF)
|
||||
job.Warn("Ignoring empty ISRC command");
|
||||
Warn("Ignoring empty ISRC command");
|
||||
else
|
||||
{
|
||||
var isrc = clp.ReadToken();
|
||||
if (isrc.Length != 12)
|
||||
job.Warn($"Invalid ISRC code ignored: {isrc}");
|
||||
Warn($"Invalid ISRC code ignored: {isrc}");
|
||||
else
|
||||
{
|
||||
OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.ISRC = new CUE_File.Command.ISRC(isrc));
|
||||
|
@ -297,7 +295,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
var str_msf = clp.ReadToken();
|
||||
var msf = new Timestamp(str_msf);
|
||||
if (!msf.Valid)
|
||||
job.Error($"Ignoring {{0}} with invalid length MSF: {str_msf}", key);
|
||||
Error($"Ignoring {{0}} with invalid length MSF: {str_msf}", key);
|
||||
else
|
||||
{
|
||||
if (key == "POSTGAP")
|
||||
|
@ -309,8 +307,21 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
break;
|
||||
|
||||
case "REM":
|
||||
OUT_CueFile.Commands.Add(new CUE_File.Command.REM(clp.ReadLine()));
|
||||
break;
|
||||
{
|
||||
var comment = clp.ReadLine();
|
||||
// cues don't support multiple sessions themselves, but it is common for rips to put SESSION # in REM fields
|
||||
// so, if we have such a REM, we'll check if the comment starts with SESSION, and interpret that as a session "command"
|
||||
var trimmed = comment.Trim();
|
||||
if (trimmed.ToUpperInvariant().StartsWith("SESSION ") && int.TryParse(trimmed.Substring(8), out var number) && number > 0)
|
||||
{
|
||||
OUT_CueFile.Commands.Add(new CUE_File.Command.SESSION(number));
|
||||
break;
|
||||
}
|
||||
|
||||
OUT_CueFile.Commands.Add(new CUE_File.Command.REM(comment));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case "SONGWRITER":
|
||||
OUT_CueFile.Commands.Add(new CUE_File.Command.SONGWRITER(clp.ReadPath() ?? ""));
|
||||
|
@ -324,14 +335,14 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
{
|
||||
if (clp.EOF)
|
||||
{
|
||||
job.Error("Incomplete TRACK command");
|
||||
Error("Incomplete TRACK command");
|
||||
break;
|
||||
}
|
||||
|
||||
string str_tracknum = clp.ReadToken();
|
||||
if (!int.TryParse(str_tracknum, out int tracknum) || tracknum < 1 || tracknum > 99)
|
||||
var str_tracknum = clp.ReadToken();
|
||||
if (!int.TryParse(str_tracknum, out var tracknum) || tracknum is < 1 or > 99)
|
||||
{
|
||||
job.Error($"Invalid TRACK number: {str_tracknum}");
|
||||
Error($"Invalid TRACK number: {str_tracknum}");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -342,7 +353,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
switch (str_trackType.ToUpperInvariant())
|
||||
{
|
||||
default:
|
||||
job.Error($"Unknown TRACK type: {str_trackType}");
|
||||
Error($"Unknown TRACK type: {str_trackType}");
|
||||
tt = CueTrackType.Unknown;
|
||||
break;
|
||||
case "AUDIO": tt = CueTrackType.Audio; break;
|
||||
|
@ -368,21 +379,18 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
//add a comment
|
||||
OUT_CueFile.Commands.Add(new CUE_File.Command.COMMENT(remainder));
|
||||
}
|
||||
else job.Warn($"Unknown text at end of line after processing command: {key}");
|
||||
else Warn($"Unknown text at end of line after processing command: {key}");
|
||||
}
|
||||
|
||||
} //end cue parsing loop
|
||||
|
||||
job.FinishLog();
|
||||
} //LoadFromString
|
||||
FinishLog();
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
OUT_CueFile = new CUE_File();
|
||||
LoadFromString(this);
|
||||
OUT_CueFile = new();
|
||||
LoadFromString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} //namespace
|
||||
}
|
|
@ -96,7 +96,7 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
Array.Clear(job.DestBuffer2448, job.DestOffset, 2352);
|
||||
|
||||
byte mode = 255;
|
||||
int form = -1;
|
||||
var form = -1;
|
||||
switch (TrackType)
|
||||
{
|
||||
case CueTrackType.Audio:
|
||||
|
@ -135,14 +135,17 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
SynthUtils.SectorHeader(job.DestBuffer2448, job.DestOffset + 0, job.LBA, mode);
|
||||
}
|
||||
|
||||
if (mode == 1)
|
||||
switch (mode)
|
||||
{
|
||||
if ((job.Parts & ESectorSynthPart.ECMAny) != 0)
|
||||
SynthUtils.ECM_Mode1(job.DestBuffer2448, job.DestOffset + 0, job.LBA);
|
||||
}
|
||||
if (mode == 2 && form == 2)
|
||||
{
|
||||
SynthUtils.EDC_Mode2_Form2(job.DestBuffer2448, job.DestOffset);
|
||||
case 1:
|
||||
{
|
||||
if ((job.Parts & ESectorSynthPart.ECMAny) != 0)
|
||||
SynthUtils.ECM_Mode1(job.DestBuffer2448, job.DestOffset + 0, job.LBA);
|
||||
break;
|
||||
}
|
||||
case 2 when form == 2:
|
||||
SynthUtils.EDC_Mode2_Form2(job.DestBuffer2448, job.DestOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
SynthSubchannelAsNeed(job);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Linq;
|
||||
using BizHawk.Common.PathExtensions;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem.CUE
|
||||
|
@ -45,17 +45,17 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
{
|
||||
IsHardcodedResolve = true;
|
||||
fisBaseDir = new MyFileInfo[hardcodes.Count];
|
||||
int i = 0;
|
||||
var i = 0;
|
||||
foreach (var kvp in hardcodes)
|
||||
{
|
||||
fisBaseDir[i++] = new MyFileInfo { FullName = kvp.Key, FileInfo = new FileInfo(kvp.Value) };
|
||||
fisBaseDir[i++] = new() { FullName = kvp.Key, FileInfo = new(kvp.Value) };
|
||||
}
|
||||
}
|
||||
|
||||
private MyFileInfo[] MyFileInfosFromFileInfos(FileInfo[] fis)
|
||||
{
|
||||
var myfis = new MyFileInfo[fis.Length];
|
||||
for (int i = 0; i < fis.Length; i++)
|
||||
for (var i = 0; i < fis.Length; i++)
|
||||
{
|
||||
myfis[i].FileInfo = fis[i];
|
||||
myfis[i].FullName = fis[i].FullName;
|
||||
|
@ -97,33 +97,31 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
//it's a little unclear whether we should go for a whitelist or a blacklist here.
|
||||
//there's similar numbers of cases either way.
|
||||
//perhaps we could code both (and prefer choices from the whitelist)
|
||||
if (ext == ".cue" || ext == ".sbi" || ext == ".ccd" || ext == ".sub")
|
||||
if (ext is ".cue" or ".sbi" or ".ccd" or ".sub")
|
||||
continue;
|
||||
|
||||
//continuing the bad plan: forbid archives (always a wrong choice, not supported anyway)
|
||||
//we should have a list prioritized by extension and score that way
|
||||
if (ext == ".7z" || ext == ".rar" || ext == ".zip" || ext == ".bz2" || ext == ".gz")
|
||||
if (ext is ".7z" or ".rar" or ".zip" or ".bz2" or ".gz")
|
||||
continue;
|
||||
|
||||
string fragment = Path.GetFileNameWithoutExtension(fi.FullName);
|
||||
var fragment = Path.GetFileNameWithoutExtension(fi.FullName);
|
||||
//match files with differing extensions
|
||||
int cmp = string.Compare(fragment, targetFragment, !caseSensitive);
|
||||
var cmp = string.Compare(fragment, targetFragment, !caseSensitive);
|
||||
if (cmp != 0)
|
||||
//match files with another extension added on (likely to be mygame.bin.ecm)
|
||||
cmp = string.Compare(fragment, targetFile, !caseSensitive);
|
||||
if (cmp == 0)
|
||||
{
|
||||
//take care to add an exact match at the beginning
|
||||
if (fi.FullName.ToLowerInvariant() == Path.Combine(baseDir,path).ToLowerInvariant())
|
||||
if (fi.FullName.ToLowerInvariant() == Path.Combine(baseDir, path).ToLowerInvariant())
|
||||
results.Insert(0, fi.FileInfo);
|
||||
else
|
||||
results.Add(fi.FileInfo);
|
||||
}
|
||||
}
|
||||
var ret = new List<string>();
|
||||
foreach (var fi in results)
|
||||
ret.Add(fi.FullName);
|
||||
return ret;
|
||||
|
||||
return results.Select(fi => fi.FullName).ToList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,18 +7,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
public static M3U_File Read(StreamReader sr)
|
||||
{
|
||||
M3U_File ret = new M3U_File();
|
||||
var ret = new M3U_File();
|
||||
return !ret.Parse(sr) ? null : ret;
|
||||
}
|
||||
|
||||
private bool Parse(StreamReader sr)
|
||||
{
|
||||
bool ext = false;
|
||||
int runtime = -1;
|
||||
var ext = false;
|
||||
var runtime = -1;
|
||||
string title = null;
|
||||
for (; ; )
|
||||
while (true)
|
||||
{
|
||||
string line = sr.ReadLine();
|
||||
var line = sr.ReadLine();
|
||||
if (line == null)
|
||||
break;
|
||||
if (line.StartsWith("#"))
|
||||
|
@ -34,7 +34,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
if (!ext) continue;
|
||||
|
||||
line = line.Substring(8);
|
||||
int cidx = line.IndexOf(',');
|
||||
var cidx = line.IndexOf(',');
|
||||
|
||||
//don't know what to do with this, but its a comment, so ignore it
|
||||
if (cidx == -1)
|
||||
|
@ -47,21 +47,20 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//just a comment. ignore it
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
var e = new Entry
|
||||
{
|
||||
var e = new Entry {
|
||||
Path = line,
|
||||
Runtime = runtime,
|
||||
Title = title
|
||||
};
|
||||
Entries.Add(e);
|
||||
runtime = -1;
|
||||
title = null;
|
||||
}
|
||||
} //parse loop
|
||||
Path = line,
|
||||
Runtime = runtime,
|
||||
Title = title
|
||||
};
|
||||
Entries.Add(e);
|
||||
runtime = -1;
|
||||
title = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
} //Parse()
|
||||
}
|
||||
|
||||
public readonly IList<Entry> Entries = new List<Entry>();
|
||||
|
||||
|
@ -90,8 +89,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public int Runtime;
|
||||
}
|
||||
|
||||
} //class M3U_File
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// <summary>
|
||||
/// MDS Header
|
||||
/// </summary>
|
||||
public AHeader Header = new AHeader();
|
||||
public AHeader Header = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of MDS session blocks
|
||||
|
@ -47,13 +47,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// <summary>
|
||||
/// Current parsed session objects
|
||||
/// </summary>
|
||||
public List<Session> ParsedSession = new List<Session>();
|
||||
public List<Session> ParsedSession = new();
|
||||
|
||||
/// <summary>
|
||||
/// Calculated MDS TOC entries (still to be parsed into BizHawk)
|
||||
/// </summary>
|
||||
public readonly IList<ATOCEntry> TOCEntries = new List<ATOCEntry>();
|
||||
|
||||
}
|
||||
|
||||
public class AHeader
|
||||
|
@ -113,10 +112,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public AHeader Parse(Stream stream)
|
||||
{
|
||||
EndianBitConverter bc = EndianBitConverter.CreateForLittleEndian();
|
||||
EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();
|
||||
var bc = EndianBitConverter.CreateForLittleEndian();
|
||||
|
||||
byte[] header = new byte[88];
|
||||
var header = new byte[88];
|
||||
stream.Read(header, 0, 88);
|
||||
|
||||
this.Signature = Encoding.ASCII.GetString(header.Take(16).ToArray());
|
||||
|
@ -195,18 +193,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// <summary>
|
||||
/// Track extra block
|
||||
/// </summary>
|
||||
public ATrackExtra ExtraBlock = new ATrackExtra();
|
||||
public readonly ATrackExtra ExtraBlock = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of footer(filename) blocks for this track
|
||||
/// </summary>
|
||||
public List<AFooter> FooterBlocks = new List<AFooter>();
|
||||
public List<AFooter> FooterBlocks = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of the calculated full paths to this track's image file
|
||||
/// The MDS file itself may contain a filename, or just an *.extension
|
||||
/// </summary>
|
||||
public List<string> ImageFileNamePaths = new List<string>();
|
||||
public List<string> ImageFileNamePaths = new();
|
||||
|
||||
public int BlobIndex;
|
||||
}
|
||||
|
@ -281,22 +279,21 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// List of the calculated full paths to this track's image file
|
||||
/// The MDS file itself may contain a filename, or just an *.extension
|
||||
/// </summary>
|
||||
public List<string> ImageFileNamePaths = new List<string>();
|
||||
public List<string> ImageFileNamePaths = new();
|
||||
|
||||
/// <summary>
|
||||
/// Track extra block
|
||||
/// </summary>
|
||||
public ATrackExtra ExtraBlock = new ATrackExtra();
|
||||
public ATrackExtra ExtraBlock = new();
|
||||
|
||||
public int BlobIndex;
|
||||
}
|
||||
|
||||
/// <exception cref="MDSParseException">header is malformed or identifies file as MDS 2.x, or any track has a DVD mode</exception>
|
||||
public AFile Parse(FileStream stream)
|
||||
public static AFile Parse(FileStream stream)
|
||||
{
|
||||
EndianBitConverter bc = EndianBitConverter.CreateForLittleEndian();
|
||||
EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();
|
||||
bool isDvd = false;
|
||||
var bc = EndianBitConverter.CreateForLittleEndian();
|
||||
var isDvd = false;
|
||||
|
||||
var aFile = new AFile { MDSPath = stream.Name };
|
||||
|
||||
|
@ -317,12 +314,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
// parse sessions
|
||||
Dictionary<int, ASession> aSessions = new Dictionary<int, ASession>();
|
||||
var aSessions = new Dictionary<int, ASession>();
|
||||
|
||||
stream.Seek(aFile.Header.SessionOffset, SeekOrigin.Begin);
|
||||
for (int se = 0; se < aFile.Header.SessionCount; se++)
|
||||
for (var se = 0; se < aFile.Header.SessionCount; se++)
|
||||
{
|
||||
byte[] sessionHeader = new byte[24];
|
||||
var sessionHeader = new byte[24];
|
||||
stream.Read(sessionHeader, 0, 24);
|
||||
//sessionHeader.Reverse().ToArray();
|
||||
|
||||
|
@ -345,21 +342,19 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
long footerOffset = 0;
|
||||
|
||||
// parse track blocks
|
||||
Dictionary<int, ATrack> aTracks = new Dictionary<int, ATrack>();
|
||||
var aTracks = new Dictionary<int, ATrack>();
|
||||
|
||||
// iterate through each session block
|
||||
foreach (ASession session in aSessions.Values)
|
||||
foreach (var session in aSessions.Values)
|
||||
{
|
||||
stream.Seek(session.TrackOffset, SeekOrigin.Begin);
|
||||
//Dictionary<int, ATrack> sessionToc = new Dictionary<int, ATrack>();
|
||||
|
||||
// iterate through every block specified in each session
|
||||
for (int bl = 0; bl < session.AllBlocks; bl++)
|
||||
for (var bl = 0; bl < session.AllBlocks; bl++)
|
||||
{
|
||||
byte[] trackHeader;
|
||||
ATrack track = new ATrack();
|
||||
|
||||
trackHeader = new byte[80];
|
||||
var trackHeader = new byte[80];
|
||||
var track = new ATrack();
|
||||
|
||||
stream.Read(trackHeader, 0, 80);
|
||||
|
||||
|
@ -387,37 +382,36 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
isDvd = true;
|
||||
throw new MDSParseException("DVD Detected. Not currently supported!");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// check for track extra block - this can probably be handled in a separate loop,
|
||||
// but I'll just store the current stream position then seek forward to the extra block for this track
|
||||
long currPos = stream.Position;
|
||||
var currPos = stream.Position;
|
||||
|
||||
// Only CDs have extra blocks - for DVDs ExtraOffset = track length
|
||||
if (track.ExtraOffset > 0 && !isDvd)
|
||||
{
|
||||
byte[] extHeader = new byte[8];
|
||||
var extHeader = new byte[8];
|
||||
stream.Seek(track.ExtraOffset, SeekOrigin.Begin);
|
||||
stream.Read(extHeader, 0, 8);
|
||||
track.ExtraBlock.Pregap = bc.ToInt32(extHeader.Take(4).ToArray());
|
||||
track.ExtraBlock.Sectors = bc.ToInt32(extHeader.Skip(4).Take(4).ToArray());
|
||||
stream.Seek(currPos, SeekOrigin.Begin);
|
||||
}
|
||||
else if (isDvd == true)
|
||||
else if (isDvd)
|
||||
{
|
||||
track.ExtraBlock.Sectors = track.ExtraOffset;
|
||||
}
|
||||
|
||||
// read the footer/filename block for this track
|
||||
currPos = stream.Position;
|
||||
long numOfFilenames = track.Files;
|
||||
var numOfFilenames = track.Files;
|
||||
for (long fi = 1; fi <= numOfFilenames; fi++)
|
||||
{
|
||||
// skip leadin/out info tracks
|
||||
if (track.FooterOffset == 0)
|
||||
continue;
|
||||
|
||||
byte[] foot = new byte[16];
|
||||
var foot = new byte[16];
|
||||
stream.Seek(track.FooterOffset, SeekOrigin.Begin);
|
||||
stream.Read(foot, 0, 16);
|
||||
|
||||
|
@ -430,7 +424,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
track.FooterBlocks = track.FooterBlocks.Distinct().ToList();
|
||||
|
||||
// parse the filename string
|
||||
string fileName = "*.mdf";
|
||||
var fileName = "*.mdf";
|
||||
if (f.FilenameOffset > 0)
|
||||
{
|
||||
// filename offset is present
|
||||
|
@ -467,7 +461,6 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
else
|
||||
fileName = Encoding.Default.GetString(fname).TrimEnd('\0');
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// assume an MDF file with the same name as the MDS
|
||||
|
@ -502,10 +495,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
|
||||
// build custom session object
|
||||
aFile.ParsedSession = new List<Session>();
|
||||
aFile.ParsedSession = new();
|
||||
foreach (var s in aSessions.Values)
|
||||
{
|
||||
Session session = new Session();
|
||||
var session = new Session();
|
||||
|
||||
if (!aTracks.TryGetValue(s.FirstTrack, out var startTrack))
|
||||
{
|
||||
|
@ -532,7 +525,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
.Where(a => se.StartTrack <= a.TrackNo && a.TrackNo <= se.EndTrack)
|
||||
.OrderBy(a => a.TrackNo))
|
||||
{
|
||||
aFile.TOCEntries.Add(new ATOCEntry(t.Point)
|
||||
aFile.TOCEntries.Add(new(t.Point)
|
||||
{
|
||||
ADR_Control = t.ADR_Control,
|
||||
AFrame = t.AFrame,
|
||||
|
@ -595,7 +588,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
AFile mdsf;
|
||||
using (var infMDS = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
mdsf = new MDS_Format().Parse(infMDS);
|
||||
mdsf = Parse(infMDS);
|
||||
|
||||
ret.ParsedMDSFile = mdsf;
|
||||
|
||||
|
@ -610,11 +603,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
/// <exception cref="MDSParseException">path reference no longer points to file</exception>
|
||||
private Dictionary<int, IBlob> MountBlobs(AFile mdsf, Disc disc)
|
||||
private static Dictionary<int, IBlob> MountBlobs(AFile mdsf, Disc disc)
|
||||
{
|
||||
Dictionary<int, IBlob> BlobIndex = new Dictionary<int, IBlob>();
|
||||
var BlobIndex = new Dictionary<int, IBlob>();
|
||||
|
||||
int count = 0;
|
||||
var count = 0;
|
||||
foreach (var track in mdsf.Tracks)
|
||||
{
|
||||
foreach (var file in track.ImageFileNamePaths.Distinct())
|
||||
|
@ -622,18 +615,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
if (!File.Exists(file))
|
||||
throw new MDSParseException($"Malformed MDS format: nonexistent image file: {file}");
|
||||
|
||||
IBlob mdfBlob = null;
|
||||
long mdfLen = -1;
|
||||
|
||||
//mount the file
|
||||
if (mdfBlob == null)
|
||||
{
|
||||
var mdfFile = new Blob_RawFile() { PhysicalPath = file };
|
||||
mdfLen = mdfFile.Length;
|
||||
mdfBlob = mdfFile;
|
||||
}
|
||||
var mdfBlob = new Blob_RawFile { PhysicalPath = file };
|
||||
|
||||
bool dupe = false;
|
||||
var dupe = false;
|
||||
foreach (var re in disc.DisposableResources)
|
||||
{
|
||||
if (re.ToString() == mdfBlob.ToString())
|
||||
|
@ -652,24 +637,24 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
return BlobIndex;
|
||||
}
|
||||
|
||||
private RawTOCEntry EmitRawTOCEntry(ATOCEntry entry)
|
||||
private static RawTOCEntry EmitRawTOCEntry(ATOCEntry entry)
|
||||
{
|
||||
BCD2 tno, ino;
|
||||
|
||||
//this should actually be zero. im not sure if this is stored as BCD2 or not
|
||||
tno = BCD2.FromDecimal(entry.TrackNo);
|
||||
var tno = BCD2.FromDecimal(entry.TrackNo);
|
||||
|
||||
//these are special values.. I think, taken from this:
|
||||
//http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html
|
||||
//the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD.
|
||||
//Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing.
|
||||
ino = BCD2.FromDecimal(entry.Point);
|
||||
if (entry.Point == 0xA0) ino.BCDValue = 0xA0;
|
||||
else if (entry.Point == 0xA1) ino.BCDValue = 0xA1;
|
||||
else if (entry.Point == 0xA2) ino.BCDValue = 0xA2;
|
||||
var ino = BCD2.FromDecimal(entry.Point);
|
||||
ino.BCDValue = entry.Point switch
|
||||
{
|
||||
0xA0 or 0xA1 or 0xA2 => (byte)entry.Point,
|
||||
_ => ino.BCDValue
|
||||
};
|
||||
|
||||
// get ADR & Control from ADR_Control byte
|
||||
byte adrc = Convert.ToByte(entry.ADR_Control);
|
||||
var adrc = Convert.ToByte(entry.ADR_Control);
|
||||
var Control = adrc & 0x0F;
|
||||
var ADR = adrc >> 4;
|
||||
|
||||
|
@ -688,49 +673,56 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
q_crc = 0, //meaningless
|
||||
};
|
||||
|
||||
return new RawTOCEntry { QData = q };
|
||||
return new() { QData = q };
|
||||
}
|
||||
|
||||
/// <exception cref="MDSParseException">no file found at <paramref name="mdsPath"/> or BLOB error</exception>
|
||||
public Disc LoadMDSToDisc(string mdsPath, DiscMountPolicy IN_DiscMountPolicy)
|
||||
public static Disc LoadMDSToDisc(string mdsPath, DiscMountPolicy IN_DiscMountPolicy)
|
||||
{
|
||||
var loadResults = LoadMDSPath(mdsPath);
|
||||
if (!loadResults.Valid)
|
||||
throw loadResults.FailureException;
|
||||
|
||||
Disc disc = new Disc();
|
||||
var disc = new Disc();
|
||||
|
||||
// load all blobs
|
||||
Dictionary<int, IBlob> BlobIndex = MountBlobs(loadResults.ParsedMDSFile, disc);
|
||||
var BlobIndex = MountBlobs(loadResults.ParsedMDSFile, disc);
|
||||
|
||||
var mdsf = loadResults.ParsedMDSFile;
|
||||
|
||||
//generate DiscTOCRaw items from the ones specified in the MDS file
|
||||
disc.RawTOCEntries = new List<RawTOCEntry>();
|
||||
var curSession = 1;
|
||||
disc.Sessions.Add(new() { Number = curSession });
|
||||
foreach (var entry in mdsf.TOCEntries)
|
||||
{
|
||||
disc.RawTOCEntries.Add(EmitRawTOCEntry(entry));
|
||||
if (entry.Session != curSession)
|
||||
{
|
||||
if (entry.Session != curSession + 1)
|
||||
throw new MDSParseException("Session incremented more than one!");
|
||||
curSession = entry.Session;
|
||||
disc.Sessions.Add(new() { Number = curSession });
|
||||
}
|
||||
|
||||
disc.Sessions[curSession].RawTOCEntries.Add(EmitRawTOCEntry(entry));
|
||||
}
|
||||
|
||||
//analyze the RAWTocEntries to figure out what type of track track 1 is
|
||||
var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job(disc.RawTOCEntries);
|
||||
var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job(disc.Session1.RawTOCEntries);
|
||||
tocSynth.Run();
|
||||
|
||||
// now build the sectors
|
||||
int currBlobIndex = 0;
|
||||
var currBlobIndex = 0;
|
||||
foreach (var session in mdsf.ParsedSession)
|
||||
{
|
||||
for (int i = session.StartTrack; i <= session.EndTrack; i++)
|
||||
for (var i = session.StartTrack; i <= session.EndTrack; i++)
|
||||
{
|
||||
int relMSF = -1;
|
||||
var relMSF = -1;
|
||||
|
||||
var track = mdsf.TOCEntries.FirstOrDefault(t => t.Point == i);
|
||||
if (track == null) break;
|
||||
|
||||
// ignore the info entries
|
||||
if (track.Point == 0xA0 ||
|
||||
track.Point == 0xA1 ||
|
||||
track.Point == 0xA2)
|
||||
if (track.Point is 0xA0 or 0xA1 or 0xA2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -758,23 +750,22 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
string bString = tBlobs.First();
|
||||
#endif
|
||||
|
||||
IBlob mdfBlob = null;
|
||||
|
||||
// check for track pregap and create if necessary
|
||||
// this is specified in the track extras block
|
||||
if (track.ExtraBlock.Pregap > 0)
|
||||
{
|
||||
CUE.CueTrackType pregapTrackType = CUE.CueTrackType.Audio;
|
||||
var pregapTrackType = CUE.CueTrackType.Audio;
|
||||
if (tocSynth.Result.TOCItems[1].IsData)
|
||||
{
|
||||
if (tocSynth.Result.Session1Format == SessionFormat.Type20_CDXA)
|
||||
pregapTrackType = CUE.CueTrackType.Mode2_2352;
|
||||
else if (tocSynth.Result.Session1Format == SessionFormat.Type10_CDI)
|
||||
pregapTrackType = CUE.CueTrackType.CDI_2352;
|
||||
else if (tocSynth.Result.Session1Format == SessionFormat.Type00_CDROM_CDDA)
|
||||
pregapTrackType = CUE.CueTrackType.Mode1_2352;
|
||||
pregapTrackType = tocSynth.Result.SessionFormat switch
|
||||
{
|
||||
SessionFormat.Type20_CDXA => CUE.CueTrackType.Mode2_2352,
|
||||
SessionFormat.Type10_CDI => CUE.CueTrackType.CDI_2352,
|
||||
SessionFormat.Type00_CDROM_CDDA => CUE.CueTrackType.Mode1_2352,
|
||||
_ => pregapTrackType
|
||||
};
|
||||
}
|
||||
for (int pre = 0; pre < track.ExtraBlock.Pregap; pre++)
|
||||
for (var pre = 0; pre < track.ExtraBlock.Pregap; pre++)
|
||||
{
|
||||
relMSF++;
|
||||
|
||||
|
@ -785,14 +776,14 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
};
|
||||
disc._Sectors.Add(ss_gap);
|
||||
|
||||
int qRelMSF = pre - Convert.ToInt32(track.ExtraBlock.Pregap);
|
||||
var qRelMSF = pre - Convert.ToInt32(track.ExtraBlock.Pregap);
|
||||
|
||||
//tweak relMSF due to ambiguity/contradiction in yellowbook docs
|
||||
if (!IN_DiscMountPolicy.CUE_PregapContradictionModeA)
|
||||
qRelMSF++;
|
||||
|
||||
//setup subQ
|
||||
byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
|
||||
const byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
|
||||
ss_gap.sq.SetStatus(ADR, tocSynth.Result.TOCItems[1].Control);
|
||||
ss_gap.sq.q_tno = BCD2.FromDecimal(1);
|
||||
ss_gap.sq.q_index = BCD2.FromDecimal(0);
|
||||
|
@ -804,22 +795,19 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
// pregap processing completed
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// create track sectors
|
||||
long currBlobOffset = track.TrackOffset;
|
||||
for (long sector = session.StartSector; sector <= session.EndSector; sector++)
|
||||
var currBlobOffset = track.TrackOffset;
|
||||
for (var sector = session.StartSector; sector <= session.EndSector; sector++)
|
||||
{
|
||||
CUE.SS_Base sBase = null;
|
||||
CUE.SS_Base sBase;
|
||||
|
||||
// get the current blob from the BlobIndex
|
||||
Blob_RawFile currBlob = (Blob_RawFile) BlobIndex[currBlobIndex];
|
||||
long currBlobLength = currBlob.Length;
|
||||
long currBlobPosition = sector;
|
||||
if (currBlobPosition == currBlobLength)
|
||||
var currBlob = (Blob_RawFile) BlobIndex[currBlobIndex];
|
||||
var currBlobLength = currBlob.Length;
|
||||
if (sector == currBlobLength)
|
||||
currBlobIndex++;
|
||||
mdfBlob = disc.DisposableResources[currBlobIndex] as Blob_RawFile;
|
||||
var mdfBlob = (IBlob) disc.DisposableResources[currBlobIndex];
|
||||
|
||||
//int userSector = 2048;
|
||||
switch (track.SectorSize)
|
||||
|
@ -851,22 +839,23 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
// add subchannel data
|
||||
relMSF++;
|
||||
BCD2 tno, ino;
|
||||
|
||||
#if false
|
||||
//this should actually be zero. im not sure if this is stored as BCD2 or not
|
||||
tno = BCD2.FromDecimal(track.TrackNo);
|
||||
|
||||
var tno = BCD2.FromDecimal(track.TrackNo);
|
||||
#endif
|
||||
//these are special values.. I think, taken from this:
|
||||
//http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html
|
||||
//the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD.
|
||||
//Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing.
|
||||
ino = BCD2.FromDecimal(track.Point);
|
||||
if (track.Point == 0xA0) ino.BCDValue = 0xA0;
|
||||
else if (track.Point == 0xA1) ino.BCDValue = 0xA1;
|
||||
else if (track.Point == 0xA2) ino.BCDValue = 0xA2;
|
||||
var ino = BCD2.FromDecimal(track.Point);
|
||||
ino.BCDValue = track.Point switch
|
||||
{
|
||||
0xA0 or 0xA1 or 0xA2 => (byte)track.Point,
|
||||
_ => ino.BCDValue
|
||||
};
|
||||
|
||||
// get ADR & Control from ADR_Control byte
|
||||
byte adrc = Convert.ToByte(track.ADR_Control);
|
||||
var adrc = Convert.ToByte(track.ADR_Control);
|
||||
var Control = adrc & 0x0F;
|
||||
var ADR = adrc >> 4;
|
||||
|
||||
|
@ -882,15 +871,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
sBase.sq = q;
|
||||
|
||||
disc._Sectors.Add(sBase);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return disc;
|
||||
}
|
||||
|
||||
} //class MDS_Format
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -34,16 +34,10 @@ namespace BizHawk.Emulation.DiscSystem.SBI
|
|||
/// </summary>
|
||||
public static bool QuickCheckISSBI(string path)
|
||||
{
|
||||
using (var fs = File.OpenRead(path))
|
||||
{
|
||||
BinaryReader br = new BinaryReader(fs);
|
||||
string sig = br.ReadStringFixedUtf8(4);
|
||||
if (sig != "SBI\0")
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
using var fs = File.OpenRead(path);
|
||||
var br = new BinaryReader(fs);
|
||||
var sig = br.ReadStringFixedUtf8(4);
|
||||
return sig == "SBI\0";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -26,7 +26,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//but it will help detect dumps with mangled TOCs which are all too common
|
||||
|
||||
CRC32 crc = new();
|
||||
byte[] buffer2352 = new byte[2352];
|
||||
var buffer2352 = new byte[2352];
|
||||
|
||||
var dsr = new DiscSectorReader(disc)
|
||||
{
|
||||
|
@ -36,19 +36,20 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//hash the TOC
|
||||
static void AddAsBytesTo(CRC32 crc32, int i)
|
||||
=> crc32.Add(BitConverter.GetBytes(i));
|
||||
AddAsBytesTo(crc, (int)disc.TOC.Session1Format);
|
||||
|
||||
AddAsBytesTo(crc, (int)disc.TOC.SessionFormat);
|
||||
AddAsBytesTo(crc, disc.TOC.FirstRecordedTrackNumber);
|
||||
AddAsBytesTo(crc, disc.TOC.LastRecordedTrackNumber);
|
||||
for (int i = 1; i <= 100; i++)
|
||||
for (var i = 1; i <= 100; i++)
|
||||
{
|
||||
//if (disc.TOC.TOCItems[i].Exists) Console.WriteLine("{0:X8} {1:X2} {2:X2} {3:X8}", crc.Current, (int)disc.TOC.TOCItems[i].Control, disc.TOC.TOCItems[i].Exists ? 1 : 0, disc.TOC.TOCItems[i].LBATimestamp.Sector); //a little debugging
|
||||
AddAsBytesTo(crc, (int)disc.TOC.TOCItems[i].Control);
|
||||
AddAsBytesTo(crc, disc.TOC.TOCItems[i].Exists ? 1 : 0);
|
||||
AddAsBytesTo(crc, (int)disc.TOC.TOCItems[i].LBA);
|
||||
AddAsBytesTo(crc, disc.TOC.TOCItems[i].LBA);
|
||||
}
|
||||
|
||||
//hash first 26 sectors
|
||||
for (int i = 0; i < 26; i++)
|
||||
for (var i = 0; i < 26; i++)
|
||||
{
|
||||
dsr.ReadLBA_2352(i, buffer2352, 0);
|
||||
crc.Add(buffer2352);
|
||||
|
@ -63,7 +64,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public uint Calculate_PSX_RedumpHash()
|
||||
{
|
||||
CRC32 crc = new();
|
||||
byte[] buffer2352 = new byte[2352];
|
||||
var buffer2352 = new byte[2352];
|
||||
|
||||
var dsr = new DiscSectorReader(disc)
|
||||
{
|
||||
|
@ -72,7 +73,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
|
||||
//read all sectors for redump hash
|
||||
for (int i = 0; i < disc.Session1.LeadoutLBA; i++)
|
||||
for (var i = 0; i < disc.Session1.LeadoutLBA; i++)
|
||||
{
|
||||
dsr.ReadLBA_2352(i, buffer2352, 0);
|
||||
crc.Add(buffer2352);
|
||||
|
@ -86,15 +87,15 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//TODO - this is a very platform-specific thing. hashing the TOC may be faster and be just as effective. so, rename it appropriately
|
||||
public string OldHash()
|
||||
{
|
||||
byte[] buffer = new byte[512 * 2352];
|
||||
DiscSectorReader dsr = new DiscSectorReader(disc);
|
||||
var buffer = new byte[512 * 2352];
|
||||
var dsr = new DiscSectorReader(disc);
|
||||
foreach (var track in disc.Session1.Tracks)
|
||||
{
|
||||
if (track.IsAudio)
|
||||
continue;
|
||||
|
||||
int lba_len = Math.Min(track.NextTrack.LBA, 512);
|
||||
for (int s = 0; s < 512 && s < lba_len; s++)
|
||||
var lba_len = Math.Min(track.NextTrack.LBA, 512);
|
||||
for (var s = 0; s < 512 && s < lba_len; s++)
|
||||
dsr.ReadLBA_2352(track.LBA + s, buffer, s * 2352);
|
||||
|
||||
return MD5Checksum.ComputeDigestHex(buffer.AsSpan(start: 0, length: lba_len * 2352));
|
||||
|
|
|
@ -107,6 +107,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// Yes, that one
|
||||
/// </summary>
|
||||
SonyPS2,
|
||||
|
||||
/// <summary>
|
||||
/// Atari Jaguar CD
|
||||
/// </summary>
|
||||
JaguarCD,
|
||||
}
|
||||
|
||||
public class DiscIdentifier
|
||||
|
@ -114,7 +119,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public DiscIdentifier(Disc disc)
|
||||
{
|
||||
_disc = disc;
|
||||
_dsr = new DiscSectorReader(disc)
|
||||
_dsr = new(disc)
|
||||
{
|
||||
// the first check for mode 0 should be sufficient for blocking attempts to read audio sectors
|
||||
// but github #928 had a data track with an audio sector
|
||||
|
@ -125,7 +130,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
private readonly Disc _disc;
|
||||
private readonly DiscSectorReader _dsr;
|
||||
private readonly Dictionary<int, byte[]> _sectorCache = new Dictionary<int, byte[]>();
|
||||
private readonly Dictionary<int, byte[]> _sectorCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to determine the type of the disc.
|
||||
|
@ -133,6 +138,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public DiscType DetectDiscType()
|
||||
{
|
||||
// Jaguar CDs are infact audio CDs, so they get bumped to the top of detection here
|
||||
if (DetectJaguarCD())
|
||||
return DiscType.JaguarCD;
|
||||
|
||||
// PCFX & TurboCD sometimes (if not alltimes) have audio on track 1 - run these before the AudioDisc detection (asni)
|
||||
if (DetectPCFX())
|
||||
return DiscType.PCFX;
|
||||
|
@ -177,11 +186,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
return DiscType.Wii;
|
||||
|
||||
var discView = EDiscStreamView.DiscStreamView_Mode1_2048;
|
||||
if (_disc.TOC.Session1Format == SessionFormat.Type20_CDXA)
|
||||
if (_disc.TOC.SessionFormat == SessionFormat.Type20_CDXA)
|
||||
discView = EDiscStreamView.DiscStreamView_Mode2_Form1_2048;
|
||||
|
||||
var iso = new ISOFile();
|
||||
bool isIso = iso.Parse(new DiscStream(_disc, discView, 0));
|
||||
var isIso = iso.Parse(new DiscStream(_disc, discView, 0));
|
||||
|
||||
if (!isIso)
|
||||
{
|
||||
|
@ -228,7 +237,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
if (sysId == "ASAHI-CDV")
|
||||
return DiscType.Playdia;
|
||||
|
||||
if (sysId == "CDTV" || sysId == "AMIGA"
|
||||
if (sysId is "CDTV" or "AMIGA"
|
||||
|| iso.Root.Children.Keys.Any(k => k.ToLowerInvariant().Contains("cd32")))
|
||||
{
|
||||
return DiscType.Amiga;
|
||||
|
@ -273,7 +282,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
private bool DetectPCFX()
|
||||
{
|
||||
var toc = _disc.TOC;
|
||||
for (int t = toc.FirstRecordedTrackNumber;
|
||||
for (var t = toc.FirstRecordedTrackNumber;
|
||||
t <= toc.LastRecordedTrackNumber;
|
||||
t++)
|
||||
{
|
||||
|
@ -302,16 +311,14 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
if (!StringAt("CD001", 0x1, 0x10))
|
||||
return false;
|
||||
|
||||
byte[] sector20 = ReadDataSectorCached(20);
|
||||
var sector20 = ReadDataSectorCached(20);
|
||||
var zecrc = CRC32.Calculate(sector20);
|
||||
|
||||
//known_crcs
|
||||
if (zecrc == 0xd7b47c06) return true; // AV Tanjou
|
||||
if (zecrc == 0x86aec522) return true; // Bishoujo Jyanshi [...]
|
||||
if (zecrc == 0xc8d1b5ef) return true; // CD Bishoujo [...]
|
||||
if (zecrc == 0x0bdbde64) return true; // CD Pachisuro [...]
|
||||
|
||||
return false;
|
||||
return zecrc is 0xd7b47c06 // AV Tanjou
|
||||
or 0x86aec522 // Bishoujo Jyanshi [...]
|
||||
or 0xc8d1b5ef // CD Bishoujo [...]
|
||||
or 0x0bdbde64; // CD Pachisuro [...]
|
||||
}
|
||||
|
||||
//asni 20171011 - this ONLY works if a valid cuefile/ccd is passed into DiscIdentifier.
|
||||
|
@ -320,7 +327,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
private bool DetectTurboCD()
|
||||
{
|
||||
var toc = _disc.TOC;
|
||||
for (int t = toc.FirstRecordedTrackNumber;
|
||||
for (var t = toc.FirstRecordedTrackNumber;
|
||||
t <= toc.LastRecordedTrackNumber;
|
||||
t++)
|
||||
{
|
||||
|
@ -335,7 +342,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
private bool Detect3DO()
|
||||
{
|
||||
var toc = _disc.TOC;
|
||||
for (int t = toc.FirstRecordedTrackNumber;
|
||||
for (var t = toc.FirstRecordedTrackNumber;
|
||||
t <= toc.LastRecordedTrackNumber;
|
||||
t++)
|
||||
{
|
||||
|
@ -349,7 +356,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//asni - slightly longer running than the others due to its brute-force nature. Should run later in the method
|
||||
private bool DetectDreamcast()
|
||||
{
|
||||
for (int i = 0; i < 1000; i++)
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
if (SectorContains("segakatana", i))
|
||||
return true;
|
||||
|
@ -367,8 +374,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
var data = ReadDataSectorCached(0);
|
||||
if (data == null) return false;
|
||||
byte[] magic = data.Skip(28).Take(4).ToArray();
|
||||
string hexString = "";
|
||||
var magic = data.Skip(28).Take(4).ToArray();
|
||||
var hexString = "";
|
||||
foreach (var b in magic)
|
||||
hexString += b.ToString("X2");
|
||||
|
||||
|
@ -379,14 +386,31 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
var data = ReadDataSectorCached(0);
|
||||
if (data == null) return false;
|
||||
byte[] magic = data.Skip(24).Take(4).ToArray();
|
||||
string hexString = "";
|
||||
var magic = data.Skip(24).Take(4).ToArray();
|
||||
var hexString = "";
|
||||
foreach (var b in magic)
|
||||
hexString += b.ToString("X2");
|
||||
|
||||
return hexString == "5D1C9EA3";
|
||||
}
|
||||
|
||||
private bool DetectJaguarCD()
|
||||
{
|
||||
// Atari Jaguar CDs are multisession discs which are encoded like audio CDs
|
||||
// The first track of the second session is the boot track, which should have "ATARI APPROVED DATA HEADER ATRI" somewhere
|
||||
// Although be wary, many dumps are byteswapped, so it might be "TARA IPARPVODED TA AEHDAREA RT.I" (. being a wildcard here)
|
||||
// The core will fixup byteswapped dumps internally
|
||||
if (_disc.Sessions.Count > 2 && !_disc.Sessions[2].TOC.TOCItems[_disc.Sessions[2].TOC.FirstRecordedTrackNumber].IsData)
|
||||
{
|
||||
var data = new byte[2352];
|
||||
_dsr.ReadLBA_2352(_disc.Sessions[2].Tracks[1].LBA, data, 0);
|
||||
var s = Encoding.ASCII.GetString(data);
|
||||
return s.Contains("ATARI APPROVED DATA HEADER ATRI") || s.Contains("TARA IPARPVODED TA AEHDAREA RT");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private byte[] ReadDataSectorCached(int lba)
|
||||
{
|
||||
//read it if we don't have it cached
|
||||
|
@ -395,7 +419,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
if (!_sectorCache.TryGetValue(lba, out var data))
|
||||
{
|
||||
data = new byte[2048];
|
||||
int read = _dsr.ReadLBA_2048(lba, data, 0);
|
||||
var read = _dsr.ReadLBA_2048(lba, data, 0);
|
||||
if (read != 2048)
|
||||
return null;
|
||||
_sectorCache[lba] = data;
|
||||
|
@ -407,8 +431,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
var data = ReadDataSectorCached(lba);
|
||||
if (data == null) return false;
|
||||
byte[] cmp = Encoding.ASCII.GetBytes(s);
|
||||
byte[] cmp2 = new byte[cmp.Length];
|
||||
var cmp = Encoding.ASCII.GetBytes(s);
|
||||
var cmp2 = new byte[cmp.Length];
|
||||
Buffer.BlockCopy(data, offset, cmp2, 0, cmp.Length);
|
||||
return cmp.SequenceEqual(cmp2);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
//"length of disc" for BizHawk's purposes (NOT a robust concept!) is determined by beginning of leadout track
|
||||
var m_leadoutTrack = md.TOCTracks[100];
|
||||
int nSectors = (int)m_leadoutTrack.lba;
|
||||
var nSectors = (int)m_leadoutTrack.lba;
|
||||
|
||||
//make synth param memos
|
||||
disc.SynthParams.MednaDisc = md;
|
||||
|
@ -41,13 +41,16 @@
|
|||
const int kADR = 1;
|
||||
const int kUnknownControl = 0;
|
||||
|
||||
// mednadisc only supports 1 session I think?
|
||||
disc.Sessions.Add(new() { Number = 1 });
|
||||
|
||||
//mednafen delivers us what is essentially but not exactly (or completely) a TOCRaw.
|
||||
//we need to synth RawTOCEntries from this and then turn it into a proper TOCRaw
|
||||
//when coming from mednafen, there are 101 entries.
|
||||
//entry[0] is placeholder junk, not to be used
|
||||
//entry[100] is the leadout track (A0)
|
||||
//A1 and A2 are in the form of FirstRecordedTrackNumber and LastRecordedTrackNumber
|
||||
for (int i = 1; i < 101; i++)
|
||||
for (var i = 1; i < 101; i++)
|
||||
{
|
||||
var m_te = md.TOCTracks[i];
|
||||
|
||||
|
@ -78,7 +81,7 @@
|
|||
q.q_index.BCDValue = 0xA2;
|
||||
}
|
||||
|
||||
disc.RawTOCEntries.Add(new RawTOCEntry { QData = q });
|
||||
disc.Session1.RawTOCEntries.Add(new() { QData = q });
|
||||
}
|
||||
|
||||
// synth A0 and A1 entries (indicating first and last recorded tracks and also session type)
|
||||
|
@ -96,7 +99,7 @@
|
|||
ap_frame = BCD2.FromDecimal(0),
|
||||
q_crc = 0, //meaningless
|
||||
};
|
||||
disc.RawTOCEntries.Add(new RawTOCEntry { QData = qA0 });
|
||||
disc.Session1.RawTOCEntries.Add(new() { QData = qA0 });
|
||||
var qA1 = new SubchannelQ
|
||||
{
|
||||
q_status = SubchannelQ.ComputeStatus(kADR, kUnknownControl),
|
||||
|
@ -111,7 +114,7 @@
|
|||
ap_frame = BCD2.FromDecimal(0),
|
||||
q_crc = 0, //meaningless
|
||||
};
|
||||
disc.RawTOCEntries.Add(new RawTOCEntry { QData = qA1 });
|
||||
disc.Session1.RawTOCEntries.Add(new() { QData = qA1 });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using System.Linq;
|
||||
using BizHawk.Common.PathExtensions;
|
||||
using BizHawk.Emulation.DiscSystem.CUE;
|
||||
|
||||
|
@ -80,27 +80,32 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
OUT_Disc.Name = Path.GetFileName(IN_FromPath);
|
||||
|
||||
//generate toc and structure:
|
||||
//1. TOCRaw from RawTOCEntries
|
||||
var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job(OUT_Disc.RawTOCEntries);
|
||||
tocSynth.Run();
|
||||
OUT_Disc.TOC = tocSynth.Result;
|
||||
//2. Structure from TOCRaw
|
||||
var structureSynth = new Synthesize_DiscStructure_From_DiscTOC_Job(OUT_Disc, OUT_Disc.TOC);
|
||||
structureSynth.Run();
|
||||
OUT_Disc.Structure = structureSynth.Result;
|
||||
|
||||
//generate toc and session tracks:
|
||||
for (var i = 1; i < OUT_Disc.Sessions.Count; i++)
|
||||
{
|
||||
var session = OUT_Disc.Sessions[i];
|
||||
//1. TOC from RawTOCEntries
|
||||
var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job(session.RawTOCEntries);
|
||||
tocSynth.Run();
|
||||
session.TOC = tocSynth.Result;
|
||||
//2. DiscTracks from TOC
|
||||
var tracksSynth = new Synthesize_DiscTracks_From_DiscTOC_Job(OUT_Disc, session);
|
||||
tracksSynth.Run();
|
||||
}
|
||||
|
||||
//insert a synth provider to take care of the leadout track
|
||||
//currently, we let mednafen take care of its own leadout track (we'll make that controllable later)
|
||||
//TODO: This currently doesn't work well with multisessions (only the last session can have a leadout read with the current model)
|
||||
//(although note only VirtualJaguar currently deals with multisession discs and it doesn't care about the leadout so far)
|
||||
if (IN_DiscInterface != DiscInterface.MednaDisc)
|
||||
{
|
||||
var ss_leadout = new SS_Leadout
|
||||
{
|
||||
SessionNumber = 1,
|
||||
SessionNumber = OUT_Disc.Sessions.Count - 1,
|
||||
Policy = IN_DiscMountPolicy
|
||||
};
|
||||
Func<int, bool> condition = (int lba) => lba >= OUT_Disc.Session1.LeadoutLBA;
|
||||
new ConditionalSectorSynthProvider().Install(OUT_Disc, condition, ss_leadout);
|
||||
bool Condition(int lba) => lba >= OUT_Disc.Sessions[OUT_Disc.Sessions.Count - 1].LeadoutLBA;
|
||||
new ConditionalSectorSynthProvider().Install(OUT_Disc, Condition, ss_leadout);
|
||||
}
|
||||
|
||||
//apply SBI if it exists
|
||||
|
@ -186,7 +191,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
switch (ext.ToLowerInvariant())
|
||||
{
|
||||
case ".ccd":
|
||||
OUT_Disc = new CCD_Format().LoadCCDToDisc(IN_FromPath, IN_DiscMountPolicy);
|
||||
OUT_Disc = CCD_Format.LoadCCDToDisc(IN_FromPath, IN_DiscMountPolicy);
|
||||
break;
|
||||
case ".cue":
|
||||
LoadCue(dir, File.ReadAllText(IN_FromPath));
|
||||
|
@ -204,7 +209,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
INDEX 01 00:00:00");
|
||||
break;
|
||||
case ".mds":
|
||||
OUT_Disc = new MDS_Format().LoadMDSToDisc(IN_FromPath, IN_DiscMountPolicy);
|
||||
OUT_Disc = MDS_Format.LoadMDSToDisc(IN_FromPath, IN_DiscMountPolicy);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
public class DiscSession
|
||||
{
|
||||
//Notable omission:
|
||||
//Length of the session
|
||||
//How should this be defined? It's even harder than determining a track length
|
||||
|
||||
/// <summary>
|
||||
/// The DiscTOC corresponding to the RawTOCEntries.
|
||||
/// </summary>
|
||||
public DiscTOC TOC;
|
||||
|
||||
/// <summary>
|
||||
/// The raw TOC entries found in the lead-in track.
|
||||
/// These aren't very useful, but they're one of the most lowest-level data structures from which other TOC-related stuff is derived
|
||||
/// </summary>
|
||||
public readonly List<RawTOCEntry> RawTOCEntries = new();
|
||||
|
||||
/// <summary>
|
||||
/// The LBA of the session's leadout. In other words, for all intents and purposes, the end of the session
|
||||
/// </summary>
|
||||
public int LeadoutLBA => LeadoutTrack.LBA;
|
||||
|
||||
/// <summary>
|
||||
/// The session number
|
||||
/// </summary>
|
||||
public int Number;
|
||||
|
||||
/// <summary>
|
||||
/// The number of user information tracks in the session.
|
||||
/// This excludes the lead-in and lead-out tracks
|
||||
/// Use this instead of Tracks.Count
|
||||
/// </summary>
|
||||
public int InformationTrackCount => Tracks.Count - 2;
|
||||
|
||||
/// <summary>
|
||||
/// All the tracks in the session.. but... Tracks[0] is the lead-in track. Tracks[1] should be "Track 1". So beware of this.
|
||||
/// For a disc with "3 tracks", Tracks.Count will be 5: it includes that lead-in track as well as the leadout track.
|
||||
/// Perhaps we should turn this into a special collection type with no Count or Length, or a method to GetTrack()
|
||||
/// </summary>
|
||||
public readonly IList<DiscTrack> Tracks = new List<DiscTrack>();
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the first information track (Track 1)
|
||||
/// The raw TOC may have specified something different; it's not clear how this discrepancy is handled.
|
||||
/// </summary>
|
||||
public DiscTrack FirstInformationTrack => Tracks[1];
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the last information track on the disc.
|
||||
/// The raw TOC may have specified something different; it's not clear how this discrepancy is handled.
|
||||
/// </summary>
|
||||
public DiscTrack LastInformationTrack => Tracks[InformationTrackCount];
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the lead-out track.
|
||||
/// Effectively, the end of the user area of the disc.
|
||||
/// </summary>
|
||||
public DiscTrack LeadoutTrack => Tracks[Tracks.Count - 1];
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the lead-in track
|
||||
/// </summary>
|
||||
public DiscTrack LeadinTrack => Tracks[0];
|
||||
|
||||
/// <summary>
|
||||
/// Determines which track of the session is at the specified LBA.
|
||||
/// </summary>
|
||||
public DiscTrack SeekTrack(int lba)
|
||||
{
|
||||
var ses = this;
|
||||
|
||||
for (var i = 1; i < Tracks.Count; i++)
|
||||
{
|
||||
var track = ses.Tracks[i];
|
||||
//funny logic here: if the current track's LBA is > the requested track number, it means the previous track is the one we wanted
|
||||
if (track.LBA > lba)
|
||||
return ses.Tracks[i - 1];
|
||||
}
|
||||
return ses.LeadoutTrack;
|
||||
}
|
||||
#if false
|
||||
public class Index
|
||||
{
|
||||
public int Number;
|
||||
public int LBA;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -66,7 +66,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
SectorSize = 2048;
|
||||
Disc = disc;
|
||||
NumSectors = disc.Session1.LeadoutLBA;
|
||||
dsr = new DiscSectorReader(disc);
|
||||
dsr = new(disc);
|
||||
|
||||
//following the provided view
|
||||
switch (view)
|
||||
|
@ -114,18 +114,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//TODO - I'm not sure everything in here makes sense right now..
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
long remainInDisc = Length - currPosition;
|
||||
var remainInDisc = Length - currPosition;
|
||||
if (count > remainInDisc)
|
||||
count = (int)Math.Min(remainInDisc, int.MaxValue);
|
||||
|
||||
int remain = count;
|
||||
int readed = 0;
|
||||
var remain = count;
|
||||
var readed = 0;
|
||||
while (remain > 0)
|
||||
{
|
||||
int lba = (int)(currPosition / SectorSize);
|
||||
int lba_within = (int)(currPosition % SectorSize);
|
||||
int todo = remain;
|
||||
int remains_in_lba = SectorSize - lba_within;
|
||||
var lba = (int)(currPosition / SectorSize);
|
||||
var lba_within = (int)(currPosition % SectorSize);
|
||||
var todo = remain;
|
||||
var remains_in_lba = SectorSize - lba_within;
|
||||
if (remains_in_lba < todo)
|
||||
todo = remains_in_lba;
|
||||
if (cachedSector != lba)
|
||||
|
|
|
@ -1,178 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains structural information for the disc broken down into c# data structures for easy interrogation.
|
||||
/// This represents a best-effort interpretation of the raw disc image.
|
||||
/// NOTE: Since this ended up really just having the list of sessions.. maybe it isn't needed and can just float on up into Disc
|
||||
/// </summary>
|
||||
public class DiscStructure
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a 1-indexed list of sessions (session 1 is at [1])
|
||||
/// Support for multiple sessions is thoroughly not working yet
|
||||
/// </summary>
|
||||
public readonly IList<Session> Sessions = new List<Session>();
|
||||
|
||||
public class Session
|
||||
{
|
||||
//Notable omission:
|
||||
//Length of the session
|
||||
//How should this be defined? It's even harder than determining a track length
|
||||
|
||||
/// <summary>
|
||||
/// The LBA of the session's leadout. In other words, for all intents and purposes, the end of the session
|
||||
/// </summary>
|
||||
public int LeadoutLBA => LeadoutTrack.LBA;
|
||||
|
||||
/// <summary>
|
||||
/// The session number
|
||||
/// </summary>
|
||||
public int Number;
|
||||
|
||||
/// <summary>
|
||||
/// The number of user information tracks in the session.
|
||||
/// This excludes the lead-in and lead-out tracks
|
||||
/// Use this instead of Tracks.Count
|
||||
/// </summary>
|
||||
public int InformationTrackCount => Tracks.Count - 2;
|
||||
|
||||
/// <summary>
|
||||
/// All the tracks in the session.. but... Tracks[0] is the lead-in track. Tracks[1] should be "Track 1". So beware of this.
|
||||
/// For a disc with "3 tracks", Tracks.Count will be 5: it includes that lead-in track as well as the leadout track.
|
||||
/// Perhaps we should turn this into a special collection type with no Count or Length, or a method to GetTrack()
|
||||
/// </summary>
|
||||
public readonly IList<Track> Tracks = new List<Track>();
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the first information track (Track 1)
|
||||
/// The raw TOC may have specified something different; it's not clear how this discrepancy is handled.
|
||||
/// </summary>
|
||||
public Track FirstInformationTrack => Tracks[1];
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the last information track on the disc.
|
||||
/// The raw TOC may have specified something different; it's not clear how this discrepancy is handled.
|
||||
/// </summary>
|
||||
public Track LastInformationTrack => Tracks[InformationTrackCount];
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the lead-out track.
|
||||
/// Effectively, the end of the user area of the disc.
|
||||
/// </summary>
|
||||
public Track LeadoutTrack => Tracks[Tracks.Count - 1];
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the lead-in track
|
||||
/// </summary>
|
||||
public Track LeadinTrack => Tracks[0];
|
||||
|
||||
/// <summary>
|
||||
/// Determines which track of the session is at the specified LBA.
|
||||
/// </summary>
|
||||
public Track SeekTrack(int lba)
|
||||
{
|
||||
var ses = this;
|
||||
|
||||
for (int i = 1; i < Tracks.Count; i++)
|
||||
{
|
||||
var track = ses.Tracks[i];
|
||||
//funny logic here: if the current track's LBA is > the requested track number, it means the previous track is the one we wanted
|
||||
if (track.LBA > lba)
|
||||
return ses.Tracks[i - 1];
|
||||
}
|
||||
return ses.LeadoutTrack;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Type of a track as specified in the TOC Q-Subchannel data from the control flags.
|
||||
/// Could also be 4-Channel Audio, but we'll handle that later if needed
|
||||
/// </summary>
|
||||
public enum ETrackType
|
||||
{
|
||||
/// <summary>
|
||||
/// The track type isn't always known.. it can take this value til its populated
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Data track( TOC Q control 0x04 flag set )
|
||||
/// </summary>
|
||||
Data,
|
||||
|
||||
/// <summary>
|
||||
/// Audio track( TOC Q control 0x04 flag clear )
|
||||
/// </summary>
|
||||
Audio
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a Track.
|
||||
/// </summary>
|
||||
public class Track
|
||||
{
|
||||
//Notable omission:
|
||||
//a list of Indices. It's difficult to reliably construct it.
|
||||
//Notably, mednafen can't readily produce it.
|
||||
//Indices may need scanning sector by sector.
|
||||
//It's unlikely that any software would be needing indices anyway.
|
||||
//We should add another index scanning service if that's ever needed.
|
||||
//(note: a CCD should contain indices, but it's not clear whether it's required. logically it shouldn't be)
|
||||
//Notable omission:
|
||||
//Length of the track.
|
||||
//How should this be defined? Between which indices? It's really hard.
|
||||
|
||||
//These omissions could be handled by ReadStructure() policies which permit the scanning of the entire disc.
|
||||
//After that, they could be cached in here.
|
||||
|
||||
/// <summary>
|
||||
/// The number of the track (1-indexed)
|
||||
/// </summary>
|
||||
public int Number;
|
||||
|
||||
/// <summary>
|
||||
/// The Mode of the track (0 is Audio, 1 and 2 are data)
|
||||
/// This is heuristically determined.
|
||||
/// Actual sector contents may vary
|
||||
/// </summary>
|
||||
public int Mode;
|
||||
|
||||
/// <summary>
|
||||
/// Is this track a Data track?
|
||||
/// </summary>
|
||||
public bool IsData => !IsAudio;
|
||||
|
||||
/// <summary>
|
||||
/// Is this track an Audio track?
|
||||
/// </summary>
|
||||
public bool IsAudio => Mode == 0;
|
||||
|
||||
/// <summary>
|
||||
/// The 'control' properties of the track expected to be found in the track's subQ.
|
||||
/// However, this is what's indicated by the disc TOC.
|
||||
/// Actual sector contents may vary.
|
||||
/// </summary>
|
||||
public EControlQ Control;
|
||||
|
||||
/// <summary>
|
||||
/// The starting LBA of the track (index 1).
|
||||
/// </summary>
|
||||
public int LBA;
|
||||
|
||||
/// <summary>
|
||||
/// The next track in the session. null for the leadout track of a session.
|
||||
/// </summary>
|
||||
public Track NextTrack;
|
||||
}
|
||||
|
||||
public class Index
|
||||
{
|
||||
public int Number;
|
||||
public int LBA;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -18,7 +18,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// <summary>
|
||||
/// The TOC specifies the format of the session, so here it is.
|
||||
/// </summary>
|
||||
public SessionFormat Session1Format = SessionFormat.None;
|
||||
public SessionFormat SessionFormat = SessionFormat.None;
|
||||
|
||||
/// <summary>
|
||||
/// Information about a single track in the TOC
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a Track.
|
||||
/// </summary>
|
||||
public class DiscTrack
|
||||
{
|
||||
//Notable omission:
|
||||
//a list of Indices. It's difficult to reliably construct it.
|
||||
//Notably, mednafen can't readily produce it.
|
||||
//Indices may need scanning sector by sector.
|
||||
//It's unlikely that any software would be needing indices anyway.
|
||||
//We should add another index scanning service if that's ever needed.
|
||||
//(note: a CCD should contain indices, but it's not clear whether it's required. logically it shouldn't be)
|
||||
//Notable omission:
|
||||
//Length of the track.
|
||||
//How should this be defined? Between which indices? It's really hard.
|
||||
|
||||
//These omissions could be handled by ReadStructure() policies which permit the scanning of the entire disc.
|
||||
//After that, they could be cached in here.
|
||||
|
||||
/// <summary>
|
||||
/// The number of the track (1-indexed)
|
||||
/// </summary>
|
||||
public int Number;
|
||||
|
||||
/// <summary>
|
||||
/// The Mode of the track (0 is Audio, 1 and 2 are data)
|
||||
/// This is heuristically determined.
|
||||
/// Actual sector contents may vary
|
||||
/// </summary>
|
||||
public int Mode;
|
||||
|
||||
/// <summary>
|
||||
/// Is this track a Data track?
|
||||
/// </summary>
|
||||
public bool IsData => !IsAudio;
|
||||
|
||||
/// <summary>
|
||||
/// Is this track an Audio track?
|
||||
/// </summary>
|
||||
public bool IsAudio => Mode == 0;
|
||||
|
||||
/// <summary>
|
||||
/// The 'control' properties of the track expected to be found in the track's subQ.
|
||||
/// However, this is what's indicated by the disc TOC.
|
||||
/// Actual sector contents may vary.
|
||||
/// </summary>
|
||||
public EControlQ Control;
|
||||
|
||||
/// <summary>
|
||||
/// The starting LBA of the track (index 1).
|
||||
/// </summary>
|
||||
public int LBA;
|
||||
|
||||
/// <summary>
|
||||
/// The next track in the session. null for the leadout track of a session.
|
||||
/// </summary>
|
||||
public DiscTrack NextTrack;
|
||||
|
||||
/// <summary>
|
||||
/// The Type of a track as specified in the TOC Q-Subchannel data from the control flags.
|
||||
/// Could also be 4-Channel Audio, but we'll handle that later if needed
|
||||
/// </summary>
|
||||
public enum ETrackType
|
||||
{
|
||||
/// <summary>
|
||||
/// The track type isn't always known.. it can take this value til its populated
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Data track( TOC Q control 0x04 flag set )
|
||||
/// </summary>
|
||||
Data,
|
||||
|
||||
/// <summary>
|
||||
/// Audio track( TOC Q control 0x04 flag clear )
|
||||
/// </summary>
|
||||
Audio
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,14 +48,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// makes a BCD2 from a decimal number. don't supply a number > 99 or you might not like the results
|
||||
/// </summary>
|
||||
public static BCD2 FromDecimal(int d)
|
||||
{
|
||||
return new BCD2 { DecimalValue = d };
|
||||
}
|
||||
=> new() { DecimalValue = d };
|
||||
|
||||
public static BCD2 FromBCD(byte b)
|
||||
{
|
||||
return new BCD2 { BCDValue = b };
|
||||
}
|
||||
=> new() { BCDValue = b };
|
||||
|
||||
public static int BCDToInt(byte n)
|
||||
{
|
||||
|
@ -65,29 +61,25 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
public static byte IntToBCD(int n)
|
||||
{
|
||||
int tens = Math.DivRem(n, 10, out var ones);
|
||||
var tens = Math.DivRem(n, 10, out var ones);
|
||||
return (byte)((tens << 4) | ones);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return BCDValue.ToString("X2");
|
||||
}
|
||||
=> BCDValue.ToString("X2");
|
||||
}
|
||||
|
||||
public static class MSF
|
||||
{
|
||||
public static int ToInt(int m, int s, int f)
|
||||
{
|
||||
return m * 60 * 75 + s * 75 + f;
|
||||
}
|
||||
=> m * 60 * 75 + s * 75 + f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// todo - rename to MSF? It can specify durations, so maybe it should be not suggestive of timestamp
|
||||
/// TODO - can we maybe use BCD2 in here
|
||||
/// </summary>
|
||||
public struct Timestamp
|
||||
public readonly struct Timestamp
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the string is a legit MSF. It's strict.
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
private static byte IntToBCD(int n)
|
||||
{
|
||||
int tens = Math.DivRem(n, 10, out var ones);
|
||||
var tens = Math.DivRem(n, 10, out var ones);
|
||||
return (byte)((tens << 4) | ones);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public static int BCD_Byte(this int val)
|
||||
{
|
||||
byte ret = (byte)(val % 10);
|
||||
var ret = (byte)(val % 10);
|
||||
ret += (byte)(16 * (val / 10));
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,4 @@
|
|||
//Copyright (c) 2012 BizHawk team
|
||||
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
//this software and associated documentation files (the "Software"), to deal in
|
||||
//the Software without restriction, including without limitation the rights to
|
||||
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
//of the Software, and to permit persons to whom the Software is furnished to do
|
||||
//so, subject to the following conditions:
|
||||
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
//CD-ROM ECC/EDC related algorithms
|
||||
//CD-ROM ECC/EDC related algorithms
|
||||
|
||||
//todo - ecm sometimes sets the sector address to 0 before computing the ECC. i cant find any documentation to support this.
|
||||
//seems to only take effect for cd-xa (mode 2, form 1). need to ask about this or test further on a cd-xa test disc
|
||||
|
@ -70,11 +50,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
const uint edc_poly = 0x8001801B;
|
||||
|
||||
//generate the CRC table
|
||||
uint reverse_edc_poly = BitReverse.Reverse32(edc_poly);
|
||||
var reverse_edc_poly = BitReverse.Reverse32(edc_poly);
|
||||
for (uint i = 0; i < 256; ++i)
|
||||
{
|
||||
uint crc = i;
|
||||
for (int j = 8; j > 0; --j)
|
||||
var crc = i;
|
||||
for (var j = 8; j > 0; --j)
|
||||
{
|
||||
if ((crc & 1) == 1)
|
||||
crc = ((crc >> 1) ^ reverse_edc_poly);
|
||||
|
@ -91,10 +71,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
private static void Prep_ECC()
|
||||
{
|
||||
//create a table implementing f(i) = i*2
|
||||
for (int i = 0; i < 256; i++)
|
||||
for (var i = 0; i < 256; i++)
|
||||
{
|
||||
int n = i * 2;
|
||||
int b = n & 0xFF;
|
||||
var n = i * 2;
|
||||
var b = n & 0xFF;
|
||||
if (n > 0xFF) b ^= 0x1D; //primitive polynomial x^8 + x^4 + x^3 + x^2 + 1 -> 0x11D
|
||||
mul2tab[i] = (byte)b;
|
||||
}
|
||||
|
@ -109,11 +89,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
// System.Diagnostics.Debug.Assert(mul2tab[i] == mul2tab_B[i]);
|
||||
|
||||
//create a table implementing f(i) = i/3
|
||||
for (int i = 0; i < 256; i++)
|
||||
for (var i = 0; i < 256; i++)
|
||||
{
|
||||
byte x1 = (byte)i;
|
||||
byte x2 = mul2tab[i];
|
||||
byte x3 = (byte)(x2 ^ x1); //2x + x = 3x
|
||||
var x1 = (byte)i;
|
||||
var x2 = mul2tab[i];
|
||||
var x3 = (byte)(x2 ^ x1); //2x + x = 3x
|
||||
//instead of dividing 1/3 we write the table backwards since its the inverse of multiplying by 3
|
||||
//this idea was taken from Corlett's techniques; I know not from whence they came.
|
||||
div3tab[x3] = x1;
|
||||
|
@ -173,10 +153,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
byte pow_accum = 0;
|
||||
byte add_accum = 0;
|
||||
for (int i = 0; i < todo; i++)
|
||||
for (var i = 0; i < todo; i++)
|
||||
{
|
||||
addr_offset %= (1118 * 2); //modulo addressing is irrelevant for P-parity calculation but comes into play for Q-parity
|
||||
byte d = data[base_offset + addr_offset];
|
||||
var d = data[base_offset + addr_offset];
|
||||
addr_offset += addr_add;
|
||||
add_accum ^= d;
|
||||
pow_accum ^= d;
|
||||
|
@ -205,18 +185,16 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public static uint EDC_Calc(byte[] data, int offset, int length)
|
||||
{
|
||||
uint crc = 0;
|
||||
for (int i = 0; i < length; i++)
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
byte b = data[offset + i];
|
||||
int entry = ((int)crc ^ b) & 0xFF;
|
||||
var b = data[offset + i];
|
||||
var entry = ((int)crc ^ b) & 0xFF;
|
||||
crc = edc_table[entry] ^ (crc >> 8);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// returns the address from a sector. useful for saving it before zeroing it for ECC calculations
|
||||
/// </summary>
|
||||
|
@ -241,8 +219,6 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//sector[sector_offset + 12 + 3] = (byte)((address >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// populates a sector with valid ECC information.
|
||||
/// it is safe to supply the same array for sector and dest.
|
||||
|
@ -250,7 +226,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public static void ECC_Populate(byte[] src, int src_offset, byte[] dest, int dest_offset, bool zeroSectorAddress)
|
||||
{
|
||||
//save the old sector address, so we can restore it later. SOMETIMES ECC is supposed to be calculated without it? see TODO
|
||||
uint address = GetSectorAddress(src, src_offset);
|
||||
var address = GetSectorAddress(src, src_offset);
|
||||
if (zeroSectorAddress) SetSectorAddress(src, src_offset, 0);
|
||||
|
||||
//all further work takes place relative to offset 12 in the sector
|
||||
|
@ -259,9 +235,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
//calculate P parity for 86 columns (twice 43 word-columns)
|
||||
byte parity0, parity1;
|
||||
for (int col = 0; col < 86; col++)
|
||||
for (var col = 0; col < 86; col++)
|
||||
{
|
||||
int offset = col;
|
||||
var offset = col;
|
||||
CalcECC(src, src_offset, offset, 86, 24, out parity0, out parity1);
|
||||
//store the parities in the sector; theyre read for the Q parity calculations
|
||||
dest[dest_offset + 1032 * 2 + col] = parity0;
|
||||
|
@ -270,11 +246,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
//calculate Q parity for 52 diagonals (twice 26 word-diagonals)
|
||||
//modulo addressing is taken care of in CalcECC
|
||||
for (int d = 0; d < 26; d++)
|
||||
for (var d = 0; d < 26; d++)
|
||||
{
|
||||
for (int w = 0; w < 2; w++)
|
||||
for (var w = 0; w < 2; w++)
|
||||
{
|
||||
int offset = d * 86 + w;
|
||||
var offset = d * 86 + w;
|
||||
CalcECC(src, src_offset, offset, 88, 43, out parity0, out parity1);
|
||||
//store the parities in the sector; that's where theyve got to go anyway
|
||||
dest[dest_offset + 1118 * 2 + d * 2 + w] = parity0;
|
||||
|
@ -332,6 +308,5 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
} //static class FFUtil
|
||||
#endif
|
||||
} //static class ECM
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
|
||||
//this has been checked against mednafen's and seems to match
|
||||
//there are a few dozen different ways to do CRC16-CCITT
|
||||
//this table is backwards or something. at any rate its tailored to the needs of the Q subchannel
|
||||
|
@ -13,7 +12,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
for (ushort i = 0; i < 256; ++i)
|
||||
{
|
||||
ushort value = 0;
|
||||
ushort temp = (ushort)(i << 8);
|
||||
var temp = (ushort)(i << 8);
|
||||
for (byte j = 0; j < 8; ++j)
|
||||
{
|
||||
if (((value ^ temp) & 0x8000) != 0)
|
||||
|
@ -29,15 +28,13 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public static ushort Calculate(byte[] data, int offset, int length)
|
||||
{
|
||||
ushort Result = 0;
|
||||
for (int i = 0; i < length; i++)
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
byte b = data[offset + i];
|
||||
int index = (b ^ ((Result >> 8) & 0xFF));
|
||||
var b = data[offset + i];
|
||||
var index = (b ^ ((Result >> 8) & 0xFF));
|
||||
Result = (ushort)((Result << 8) ^ table[index]);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -13,17 +13,17 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
//save this, it's small, and we'll want it for disc processing a/b checks
|
||||
disc.Memos["sbi"] = sbi;
|
||||
|
||||
DiscSectorReader dsr = new DiscSectorReader(disc);
|
||||
var dsr = new DiscSectorReader(disc);
|
||||
|
||||
int n = sbi.ABAs.Count;
|
||||
int b = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
var n = sbi.ABAs.Count;
|
||||
var b = 0;
|
||||
for (var i = 0; i < n; i++)
|
||||
{
|
||||
int lba = sbi.ABAs[i] - 150;
|
||||
var lba = sbi.ABAs[i] - 150;
|
||||
|
||||
//create a synthesizer which can return the patched data
|
||||
var ss_patchq = new SS_PatchQ { Original = disc._Sectors[lba + 150] };
|
||||
byte[] subQbuf = ss_patchq.Buffer_SubQ;
|
||||
var subQbuf = ss_patchq.Buffer_SubQ;
|
||||
|
||||
//read the old subcode
|
||||
dsr.ReadLBA_SubQ(lba, subQbuf, 0);
|
||||
|
@ -32,11 +32,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
disc._Sectors[lba + 150] = ss_patchq;
|
||||
|
||||
//apply SBI patch
|
||||
for (int j = 0; j < 12; j++)
|
||||
for (var j = 0; j < 12; j++)
|
||||
{
|
||||
short patch = sbi.subq[b++];
|
||||
var patch = sbi.subq[b++];
|
||||
if (patch == -1) continue;
|
||||
else subQbuf[j] = (byte)patch;
|
||||
subQbuf[j] = (byte)patch;
|
||||
}
|
||||
|
||||
//Apply mednafen hacks
|
||||
|
|
|
@ -24,13 +24,13 @@ namespace BizHawk.Emulation.DiscSystem.SBI
|
|||
public override void Run()
|
||||
{
|
||||
using var fs = File.OpenRead(IN_Path);
|
||||
BinaryReader br = new BinaryReader(fs);
|
||||
string sig = br.ReadStringFixedUtf8(4);
|
||||
var br = new BinaryReader(fs);
|
||||
var sig = br.ReadStringFixedUtf8(4);
|
||||
if (sig != "SBI\0")
|
||||
throw new SBIParseException("Missing magic number");
|
||||
|
||||
SubQPatchData ret = new SubQPatchData();
|
||||
List<short> bytes = new List<short>();
|
||||
var ret = new SubQPatchData();
|
||||
var bytes = new List<short>();
|
||||
|
||||
//read records until done
|
||||
for (; ; )
|
||||
|
@ -50,20 +50,20 @@ namespace BizHawk.Emulation.DiscSystem.SBI
|
|||
{
|
||||
case 1: //Q0..Q9
|
||||
if (fs.Position + 10 > fs.Length) throw new SBIParseException("Broken record");
|
||||
for (int i = 0; i <= 9; i++) bytes.Add(br.ReadByte());
|
||||
for (int i = 10; i <= 11; i++) bytes.Add(-1);
|
||||
for (var i = 0; i <= 9; i++) bytes.Add(br.ReadByte());
|
||||
for (var i = 10; i <= 11; i++) bytes.Add(-1);
|
||||
break;
|
||||
case 2: //Q3..Q5
|
||||
if (fs.Position + 3 > fs.Length) throw new SBIParseException("Broken record");
|
||||
for (int i = 0; i <= 2; i++) bytes.Add(-1);
|
||||
for (int i = 3; i <= 5; i++) bytes.Add(br.ReadByte());
|
||||
for (int i = 6; i <= 11; i++) bytes.Add(-1);
|
||||
for (var i = 0; i <= 2; i++) bytes.Add(-1);
|
||||
for (var i = 3; i <= 5; i++) bytes.Add(br.ReadByte());
|
||||
for (var i = 6; i <= 11; i++) bytes.Add(-1);
|
||||
break;
|
||||
case 3: //Q7..Q9
|
||||
if (fs.Position + 3 > fs.Length) throw new SBIParseException("Broken record");
|
||||
for (int i = 0; i <= 6; i++) bytes.Add(-1);
|
||||
for (int i = 7; i <= 9; i++) bytes.Add(br.ReadByte());
|
||||
for (int i = 10; i <= 11; i++) bytes.Add(-1);
|
||||
for (var i = 0; i <= 6; i++) bytes.Add(-1);
|
||||
for (var i = 7; i <= 9; i++) bytes.Add(br.ReadByte());
|
||||
for (var i = 10; i <= 11; i++) bytes.Add(-1);
|
||||
break;
|
||||
default:
|
||||
throw new SBIParseException("Broken record");
|
||||
|
|
|
@ -15,28 +15,28 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
private readonly int IN_LastRecordedTrackNumber;
|
||||
|
||||
private readonly SessionFormat IN_Session1Format;
|
||||
private readonly SessionFormat IN_SessionFormat;
|
||||
|
||||
private readonly int IN_LeadoutTimestamp;
|
||||
|
||||
/// <param name="firstRecordedTrackNumber">"First Recorded Track Number" value for TOC (usually 1)</param>
|
||||
/// <param name="lastRecordedTrackNumber">"Last Recorded Track Number" value for TOC</param>
|
||||
/// <param name="session1Format">The session format for this TOC</param>
|
||||
/// <param name="sessionFormat">The session format for this TOC</param>
|
||||
/// <param name="leadoutTimestamp">The absolute timestamp of the lead-out track</param>
|
||||
public Synthesize_A0A1A2_Job(
|
||||
int firstRecordedTrackNumber,
|
||||
int lastRecordedTrackNumber,
|
||||
SessionFormat session1Format,
|
||||
SessionFormat sessionFormat,
|
||||
int leadoutTimestamp)
|
||||
{
|
||||
IN_FirstRecordedTrackNumber = firstRecordedTrackNumber;
|
||||
IN_LastRecordedTrackNumber = lastRecordedTrackNumber;
|
||||
IN_Session1Format = session1Format;
|
||||
IN_SessionFormat = sessionFormat;
|
||||
IN_LeadoutTimestamp = leadoutTimestamp;
|
||||
}
|
||||
|
||||
/// <summary>appends the new entries to the provided list</summary>
|
||||
/// <exception cref="InvalidOperationException"><see cref="IN_Session1Format"/> is <see cref="SessionFormat.None"/> or a non-member</exception>
|
||||
/// <exception cref="InvalidOperationException"><see cref="IN_SessionFormat"/> is <see cref="SessionFormat.None"/> or a non-member</exception>
|
||||
public void Run(List<RawTOCEntry> entries)
|
||||
{
|
||||
//NOTE: entries are inserted at the beginning due to observations of CCD indicating they might need to be that way
|
||||
|
@ -46,24 +46,24 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
//ADR (q-Mode) is necessarily 0x01 for a RawTOCEntry
|
||||
const int kADR = 1;
|
||||
const int kUnknownControl = 0;
|
||||
const EControlQ kUnknownControl = 0;
|
||||
|
||||
sq.SetStatus(kADR, (EControlQ)kUnknownControl);
|
||||
sq.SetStatus(kADR, kUnknownControl);
|
||||
|
||||
//first recorded track number:
|
||||
sq.q_index.BCDValue = 0xA0;
|
||||
sq.ap_min.DecimalValue = IN_FirstRecordedTrackNumber;
|
||||
switch(IN_Session1Format)
|
||||
sq.ap_sec.DecimalValue = IN_SessionFormat switch
|
||||
{
|
||||
//TODO these probably shouldn't be decimal values
|
||||
case SessionFormat.Type00_CDROM_CDDA: sq.ap_sec.DecimalValue = 0x00; break;
|
||||
case SessionFormat.Type10_CDI: sq.ap_sec.DecimalValue = 0x10; break;
|
||||
case SessionFormat.Type20_CDXA: sq.ap_sec.DecimalValue = 0x20; break;
|
||||
default: throw new InvalidOperationException("Invalid Session1Format");
|
||||
}
|
||||
SessionFormat.Type00_CDROM_CDDA => 0x00,
|
||||
SessionFormat.Type10_CDI => 0x10,
|
||||
SessionFormat.Type20_CDXA => 0x20,
|
||||
_ => throw new InvalidOperationException("Invalid SessionFormat")
|
||||
};
|
||||
sq.ap_frame.DecimalValue = 0;
|
||||
|
||||
entries.Insert(0, new RawTOCEntry { QData = sq });
|
||||
entries.Insert(0, new() { QData = sq });
|
||||
|
||||
//last recorded track number:
|
||||
sq.q_index.BCDValue = 0xA1;
|
||||
|
@ -71,13 +71,13 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
sq.ap_sec.DecimalValue = 0;
|
||||
sq.ap_frame.DecimalValue = 0;
|
||||
|
||||
entries.Insert(1, new RawTOCEntry { QData = sq });
|
||||
entries.Insert(1, new() { QData = sq });
|
||||
|
||||
//leadout:
|
||||
sq.q_index.BCDValue = 0xA2;
|
||||
sq.AP_Timestamp = IN_LeadoutTimestamp;
|
||||
|
||||
entries.Insert(2, new RawTOCEntry { QData = sq });
|
||||
entries.Insert(2, new() { QData = sq });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
internal class Synthesize_DiscStructure_From_DiscTOC_Job
|
||||
{
|
||||
private readonly Disc IN_Disc;
|
||||
|
||||
private readonly DiscTOC TOCRaw;
|
||||
|
||||
public Synthesize_DiscStructure_From_DiscTOC_Job(Disc disc, DiscTOC tocRaw)
|
||||
{
|
||||
IN_Disc = disc;
|
||||
TOCRaw = tocRaw;
|
||||
}
|
||||
|
||||
public DiscStructure Result { get; private set; }
|
||||
|
||||
/// <exception cref="InvalidOperationException">first track of <see cref="TOCRaw"/> is not <c>1</c></exception>
|
||||
public void Run()
|
||||
{
|
||||
var dsr = new DiscSectorReader(IN_Disc) { Policy = { DeterministicClearBuffer = false } };
|
||||
|
||||
Result = new DiscStructure();
|
||||
var session = new DiscStructure.Session();
|
||||
Result.Sessions.Add(null); //placeholder session for reindexing
|
||||
Result.Sessions.Add(session);
|
||||
|
||||
session.Number = 1;
|
||||
|
||||
if (TOCRaw.FirstRecordedTrackNumber != 1)
|
||||
throw new InvalidOperationException($"Unsupported: {nameof(TOCRaw.FirstRecordedTrackNumber)} != 1");
|
||||
|
||||
//add a lead-in track
|
||||
session.Tracks.Add(new DiscStructure.Track
|
||||
{
|
||||
Number = 0,
|
||||
Control = EControlQ.None, //we'll set this later
|
||||
LBA = -new Timestamp(99,99,99).Sector //obvious garbage
|
||||
});
|
||||
|
||||
int ntracks = TOCRaw.LastRecordedTrackNumber - TOCRaw.FirstRecordedTrackNumber + 1;
|
||||
for (int i = 0; i < ntracks; i++)
|
||||
{
|
||||
var item = TOCRaw.TOCItems[i + 1];
|
||||
var track = new DiscStructure.Track
|
||||
{
|
||||
Number = i + 1,
|
||||
Control = item.Control,
|
||||
LBA = item.LBA
|
||||
};
|
||||
session.Tracks.Add(track);
|
||||
|
||||
if (!item.IsData)
|
||||
track.Mode = 0;
|
||||
else
|
||||
{
|
||||
//determine the mode by a hardcoded heuristic: check mode of first sector
|
||||
track.Mode = dsr.ReadLBA_Mode(track.LBA);
|
||||
}
|
||||
|
||||
//determine track length according to... how? It isn't clear.
|
||||
//Let's not do this until it's needed.
|
||||
//if (i == ntracks - 1)
|
||||
// track.Length = TOCRaw.LeadoutLBA.Sector - track.LBA;
|
||||
//else track.Length = (TOCRaw.TOCItems[i + 2].LBATimestamp.Sector - track.LBA);
|
||||
}
|
||||
|
||||
//add lead-out track
|
||||
session.Tracks.Add(new DiscStructure.Track
|
||||
{
|
||||
Number = 0xA0, //right?
|
||||
//kind of a guess, but not completely
|
||||
Control = session.Tracks[session.Tracks.Count -1 ].Control,
|
||||
Mode = session.Tracks[session.Tracks.Count - 1].Mode,
|
||||
LBA = TOCRaw.LeadoutLBA
|
||||
});
|
||||
|
||||
//link track list
|
||||
for (int i = 0; i < session.Tracks.Count - 1; i++)
|
||||
{
|
||||
session.Tracks[i].NextTrack = session.Tracks[i + 1];
|
||||
}
|
||||
|
||||
//fix lead-in track type
|
||||
//guesses:
|
||||
session.Tracks[0].Control = session.Tracks[1].Control;
|
||||
session.Tracks[0].Mode = session.Tracks[1].Mode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,71 +22,88 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
public void Run()
|
||||
{
|
||||
var job = this;
|
||||
DiscTOC ret = new DiscTOC();
|
||||
Result = new();
|
||||
|
||||
//this is a dummy, for convenience in array indexing, so that track 1 is at array index 1
|
||||
ret.TOCItems[0].LBA = 0; //arguably could be -150, but let's not just yet
|
||||
ret.TOCItems[0].Control = 0;
|
||||
ret.TOCItems[0].Exists = false;
|
||||
Result.TOCItems[0].LBA = 0; //arguably could be -150, but let's not just yet
|
||||
Result.TOCItems[0].Control = 0;
|
||||
Result.TOCItems[0].Exists = false;
|
||||
|
||||
//just in case this doesn't get set...
|
||||
ret.FirstRecordedTrackNumber = 0;
|
||||
ret.LastRecordedTrackNumber = 0;
|
||||
Result.FirstRecordedTrackNumber = 0;
|
||||
Result.LastRecordedTrackNumber = 0;
|
||||
|
||||
int maxFoundTrack = 0;
|
||||
var maxFoundTrack = 0;
|
||||
|
||||
foreach (var te in job.Entries)
|
||||
foreach (var te in Entries)
|
||||
{
|
||||
var q = te.QData;
|
||||
var point = q.q_index.DecimalValue;
|
||||
|
||||
//see ECMD-394 page 5-14 for info about point = 0xA0, 0xA1, 0xA2
|
||||
|
||||
if (point == 0x00)
|
||||
job.Log.Add("unexpected POINT=00 in lead-in Q-channel");
|
||||
else if (point == 255)
|
||||
throw new InvalidOperationException("point == 255");
|
||||
else if (point <= 99)
|
||||
switch (point)
|
||||
{
|
||||
maxFoundTrack = Math.Max(maxFoundTrack, point);
|
||||
ret.TOCItems[point].LBA = q.AP_Timestamp - 150; //RawTOCEntries contained an absolute time
|
||||
ret.TOCItems[point].Control = q.CONTROL;
|
||||
ret.TOCItems[point].Exists = true;
|
||||
}
|
||||
else if (point == 100) //0xA0 bcd
|
||||
{
|
||||
ret.FirstRecordedTrackNumber = q.ap_min.DecimalValue;
|
||||
if (q.ap_frame.DecimalValue != 0) job.Log.Add("PFRAME should be 0 for POINT=0xA0");
|
||||
if (q.ap_sec.DecimalValue == 0x00) ret.Session1Format = SessionFormat.Type00_CDROM_CDDA;
|
||||
else if (q.ap_sec.DecimalValue == 0x10) ret.Session1Format = SessionFormat.Type10_CDI;
|
||||
else if (q.ap_sec.DecimalValue == 0x20) ret.Session1Format = SessionFormat.Type20_CDXA;
|
||||
else job.Log.Add("Unrecognized session format: PSEC should be one of {0x00,0x10,0x20} for POINT=0xA0");
|
||||
}
|
||||
else if (point == 101) //0xA1 bcd
|
||||
{
|
||||
ret.LastRecordedTrackNumber = q.ap_min.DecimalValue;
|
||||
if (q.ap_sec.DecimalValue != 0) job.Log.Add("PSEC should be 0 for POINT=0xA1");
|
||||
if (q.ap_frame.DecimalValue != 0) job.Log.Add("PFRAME should be 0 for POINT=0xA1");
|
||||
}
|
||||
else if (point == 102) //0xA2 bcd
|
||||
{
|
||||
ret.TOCItems[100].LBA = q.AP_Timestamp - 150; //RawTOCEntries contained an absolute time
|
||||
ret.TOCItems[100].Control = 0; //not clear what this should be
|
||||
ret.TOCItems[100].Exists = true;
|
||||
case 0x00:
|
||||
Log.Add("unexpected POINT=00 in lead-in Q-channel");
|
||||
break;
|
||||
case 255:
|
||||
throw new InvalidOperationException("point == 255");
|
||||
case <= 99:
|
||||
maxFoundTrack = Math.Max(maxFoundTrack, point);
|
||||
Result.TOCItems[point].LBA = q.AP_Timestamp - 150; //RawTOCEntries contained an absolute time
|
||||
Result.TOCItems[point].Control = q.CONTROL;
|
||||
Result.TOCItems[point].Exists = true;
|
||||
break;
|
||||
//0xA0 bcd
|
||||
case 100:
|
||||
{
|
||||
Result.FirstRecordedTrackNumber = q.ap_min.DecimalValue;
|
||||
if (q.ap_frame.DecimalValue != 0) Log.Add("PFRAME should be 0 for POINT=0xA0");
|
||||
switch (q.ap_sec.DecimalValue)
|
||||
{
|
||||
case 0x00:
|
||||
Result.SessionFormat = SessionFormat.Type00_CDROM_CDDA;
|
||||
break;
|
||||
case 0x10:
|
||||
Result.SessionFormat = SessionFormat.Type10_CDI;
|
||||
break;
|
||||
case 0x20:
|
||||
Result.SessionFormat = SessionFormat.Type20_CDXA;
|
||||
break;
|
||||
default:
|
||||
Log.Add("Unrecognized session format: PSEC should be one of {0x00,0x10,0x20} for POINT=0xA0");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
//0xA1 bcd
|
||||
case 101:
|
||||
{
|
||||
Result.LastRecordedTrackNumber = q.ap_min.DecimalValue;
|
||||
if (q.ap_sec.DecimalValue != 0) Log.Add("PSEC should be 0 for POINT=0xA1");
|
||||
if (q.ap_frame.DecimalValue != 0) Log.Add("PFRAME should be 0 for POINT=0xA1");
|
||||
break;
|
||||
}
|
||||
//0xA2 bcd
|
||||
case 102:
|
||||
Result.TOCItems[100].LBA = q.AP_Timestamp - 150; //RawTOCEntries contained an absolute time
|
||||
Result.TOCItems[100].Control = 0; //not clear what this should be
|
||||
Result.TOCItems[100].Exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//this is speculative:
|
||||
//well, nothing to be done here..
|
||||
// if (ret.FirstRecordedTrackNumber == -1) { }
|
||||
if (ret.LastRecordedTrackNumber == -1) { ret.LastRecordedTrackNumber = maxFoundTrack; }
|
||||
if (ret.Session1Format == SessionFormat.None) ret.Session1Format = SessionFormat.Type00_CDROM_CDDA;
|
||||
if (Result.LastRecordedTrackNumber == -1) { Result.LastRecordedTrackNumber = maxFoundTrack; }
|
||||
if (Result.SessionFormat == SessionFormat.None) Result.SessionFormat = SessionFormat.Type00_CDROM_CDDA;
|
||||
|
||||
//if (!ret.LeadoutTimestamp.Valid) {
|
||||
//if (!Result.LeadoutTimestamp.Valid) {
|
||||
// //we're DOOMED. we cant know the length of the last track without this....
|
||||
//}
|
||||
job.Result = ret;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
internal class Synthesize_DiscTracks_From_DiscTOC_Job
|
||||
{
|
||||
private readonly Disc IN_Disc;
|
||||
|
||||
private readonly DiscSession IN_Session;
|
||||
|
||||
private DiscTOC TOCRaw => IN_Session.TOC;
|
||||
private IList<DiscTrack> Tracks => IN_Session.Tracks;
|
||||
|
||||
public Synthesize_DiscTracks_From_DiscTOC_Job(Disc disc, DiscSession session)
|
||||
{
|
||||
IN_Disc = disc;
|
||||
IN_Session = session;
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidOperationException">first track of <see cref="TOCRaw"/> is not <c>1</c></exception>
|
||||
public void Run()
|
||||
{
|
||||
var dsr = new DiscSectorReader(IN_Disc) { Policy = { DeterministicClearBuffer = false } };
|
||||
|
||||
//add a lead-in track
|
||||
Tracks.Add(new()
|
||||
{
|
||||
Number = 0,
|
||||
Control = EControlQ.None, //we'll set this later
|
||||
LBA = -new Timestamp(99,99,99).Sector //obvious garbage
|
||||
});
|
||||
|
||||
for (var i = TOCRaw.FirstRecordedTrackNumber; i <= TOCRaw.LastRecordedTrackNumber; i++)
|
||||
{
|
||||
var item = TOCRaw.TOCItems[i];
|
||||
var track = new DiscTrack
|
||||
{
|
||||
Number = i,
|
||||
Control = item.Control,
|
||||
LBA = item.LBA
|
||||
};
|
||||
Tracks.Add(track);
|
||||
|
||||
if (!item.IsData)
|
||||
track.Mode = 0;
|
||||
else
|
||||
{
|
||||
//determine the mode by a hardcoded heuristic: check mode of first sector
|
||||
track.Mode = dsr.ReadLBA_Mode(track.LBA);
|
||||
}
|
||||
|
||||
//determine track length according to... how? It isn't clear.
|
||||
//Let's not do this until it's needed.
|
||||
//if (i == ntracks - 1)
|
||||
// track.Length = TOCRaw.LeadoutLBA.Sector - track.LBA;
|
||||
//else track.Length = (TOCRaw.TOCItems[i + 2].LBATimestamp.Sector - track.LBA);
|
||||
}
|
||||
|
||||
//add lead-out track
|
||||
Tracks.Add(new()
|
||||
{
|
||||
Number = 0xA0, //right?
|
||||
//kind of a guess, but not completely
|
||||
Control = Tracks[Tracks.Count - 1].Control,
|
||||
Mode = Tracks[Tracks.Count - 1].Mode,
|
||||
LBA = TOCRaw.LeadoutLBA
|
||||
});
|
||||
|
||||
//link track list
|
||||
for (var i = 0; i < Tracks.Count - 1; i++)
|
||||
{
|
||||
Tracks[i].NextTrack = Tracks[i + 1];
|
||||
}
|
||||
|
||||
//fix lead-in track type
|
||||
//guesses:
|
||||
Tracks[0].Control = Tracks[TOCRaw.FirstRecordedTrackNumber].Control;
|
||||
Tracks[0].Mode = Tracks[TOCRaw.FirstRecordedTrackNumber].Mode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,22 +29,22 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
//leadout flags.. let's set them the same as the last track.
|
||||
//THIS IS NOT EXACTLY THE SAME WAY MEDNAFEN DOES IT
|
||||
EControlQ leadoutFlags = lastTrackTOCItem.Control;
|
||||
var leadoutFlags = lastTrackTOCItem.Control;
|
||||
|
||||
//TODO - needs to be encoded as a certain mode (mode 2 form 2 for psx... i guess...)
|
||||
|
||||
for (int i = 0; i < Length; i++)
|
||||
for (var i = 0; i < Length; i++)
|
||||
{
|
||||
//var se = new SectorEntry(sz);
|
||||
//Disc.Sectors.Add(se);
|
||||
SubchannelQ sq = default;
|
||||
|
||||
int track_relative_msf = i;
|
||||
var track_relative_msf = i;
|
||||
sq.min = BCD2.FromDecimal(new Timestamp(track_relative_msf).MIN);
|
||||
sq.sec = BCD2.FromDecimal(new Timestamp(track_relative_msf).SEC);
|
||||
sq.frame = BCD2.FromDecimal(new Timestamp(track_relative_msf).FRAC);
|
||||
|
||||
int absolute_msf = i + leadoutTs;
|
||||
var absolute_msf = i + leadoutTs;
|
||||
sq.ap_min = BCD2.FromDecimal(new Timestamp(absolute_msf + 150).MIN);
|
||||
sq.ap_sec = BCD2.FromDecimal(new Timestamp(absolute_msf + 150).SEC);
|
||||
sq.ap_frame = BCD2.FromDecimal(new Timestamp(absolute_msf + 150).FRAC);
|
||||
|
@ -52,7 +52,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
sq.q_tno.DecimalValue = 0xAA; //special value for leadout
|
||||
sq.q_index.DecimalValue = 1;
|
||||
|
||||
byte ADR = 1;
|
||||
const byte ADR = 1;
|
||||
sq.SetStatus(ADR, leadoutFlags);
|
||||
|
||||
//TODO - actually stash the subQ
|
||||
|
|
|
@ -127,12 +127,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
internal class ArraySectorSynthProvider : ISectorSynthProvider
|
||||
{
|
||||
public List<ISectorSynthJob2448> Sectors = new List<ISectorSynthJob2448>();
|
||||
public List<ISectorSynthJob2448> Sectors = new();
|
||||
public int FirstLBA;
|
||||
|
||||
public ISectorSynthJob2448 Get(int lba)
|
||||
{
|
||||
int index = lba - FirstLBA;
|
||||
var index = lba - FirstLBA;
|
||||
if (index < 0) return null;
|
||||
return index >= Sectors.Count ? null : Sectors[index];
|
||||
}
|
||||
|
@ -156,6 +156,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
private Func<int,bool> Condition;
|
||||
private ISectorSynthJob2448 Patch;
|
||||
private ISectorSynthProvider Parent;
|
||||
|
||||
public void Install(Disc disc, Func<int, bool> condition, ISectorSynthJob2448 patch)
|
||||
{
|
||||
Parent = disc.SynthProvider;
|
||||
|
@ -163,6 +164,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
Condition = condition;
|
||||
Patch = patch;
|
||||
}
|
||||
|
||||
public ISectorSynthJob2448 Get(int lba)
|
||||
{
|
||||
return Condition(lba) ? Patch : Parent.Get(lba);
|
||||
|
@ -195,7 +197,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
internal class SS_PatchQ : ISectorSynthJob2448
|
||||
{
|
||||
public ISectorSynthJob2448 Original;
|
||||
public byte[] Buffer_SubQ = new byte[12];
|
||||
public readonly byte[] Buffer_SubQ = new byte[12];
|
||||
|
||||
public void Synth(SectorSynthJob job)
|
||||
{
|
||||
Original.Synth(job);
|
||||
|
@ -204,7 +207,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
return;
|
||||
|
||||
//apply patched subQ
|
||||
for (int i = 0; i < 12; i++)
|
||||
for (var i = 0; i < 12; i++)
|
||||
job.DestBuffer2448[2352 + 12 + i] = Buffer_SubQ[i];
|
||||
}
|
||||
}
|
||||
|
@ -223,16 +226,16 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
// and the leadout entry together before extracting the D2 bit. Audio track->data leadout is fairly benign though maybe noisy(especially if we ever implement
|
||||
// data scrambling properly), but data track->audio leadout could break things in an insidious manner for the more accurate drive emulation code).
|
||||
|
||||
var ses = job.Disc.Structure.Sessions[SessionNumber];
|
||||
int lba_relative = job.LBA - ses.LeadoutTrack.LBA;
|
||||
var ses = job.Disc.Sessions[SessionNumber];
|
||||
var lba_relative = job.LBA - ses.LeadoutTrack.LBA;
|
||||
|
||||
//data is zero
|
||||
|
||||
int ts = lba_relative;
|
||||
int ats = job.LBA;
|
||||
var ts = lba_relative;
|
||||
var ats = job.LBA;
|
||||
|
||||
const int ADR = 0x1; // Q channel data encodes position
|
||||
EControlQ control = ses.LeadoutTrack.Control;
|
||||
var control = ses.LeadoutTrack.Control;
|
||||
|
||||
//ehhh? CDI?
|
||||
// if(toc.tracks[toc.last_track].valid) control |= toc.tracks[toc.last_track].control & 0x4;
|
||||
|
@ -248,16 +251,16 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
sq.zero = 0;
|
||||
|
||||
//finally, rely on a gap sector to do the heavy lifting to synthesize this
|
||||
CUE.CueTrackType TrackType = CUE.CueTrackType.Audio;
|
||||
var TrackType = CUE.CueTrackType.Audio;
|
||||
if (ses.LeadoutTrack.IsData)
|
||||
{
|
||||
if (job.Disc.TOC.Session1Format == SessionFormat.Type20_CDXA || job.Disc.TOC.Session1Format == SessionFormat.Type10_CDI)
|
||||
if (job.Disc.TOC.SessionFormat is SessionFormat.Type20_CDXA or SessionFormat.Type10_CDI)
|
||||
TrackType = CUE.CueTrackType.Mode2_2352;
|
||||
else
|
||||
TrackType = CUE.CueTrackType.Mode1_2352;
|
||||
}
|
||||
|
||||
CUE.SS_Gap ss_gap = new CUE.SS_Gap()
|
||||
var ss_gap = new CUE.SS_Gap
|
||||
{
|
||||
Policy = Policy,
|
||||
sq = sq,
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// <param name="offset">location within buffer of Q subchannel</param>
|
||||
public static ushort SubQ_SynthChecksum(byte[] buf12, int offset)
|
||||
{
|
||||
ushort crc16 = CRC16_CCITT.Calculate(buf12, offset, 10);
|
||||
var crc16 = CRC16_CCITT.Calculate(buf12, offset, 10);
|
||||
|
||||
//CRC is stored inverted and big endian
|
||||
buf12[offset + 10] = (byte)(~(crc16 >> 8));
|
||||
|
@ -51,8 +51,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public static void SubP(byte[] buffer12, int offset, bool pause)
|
||||
{
|
||||
byte val = (byte)(pause ? 0xFF : 0x00);
|
||||
for (int i = 0; i < 12; i++)
|
||||
var val = (byte)(pause ? 0xFF : 0x00);
|
||||
for (var i = 0; i < 12; i++)
|
||||
buffer12[offset + i] = val;
|
||||
}
|
||||
|
||||
|
@ -62,9 +62,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public static void SectorHeader(byte[] buffer16, int offset, int LBA, byte mode)
|
||||
{
|
||||
buffer16[offset + 0] = 0x00;
|
||||
for (int i = 1; i < 11; i++) buffer16[offset + i] = 0xFF;
|
||||
for (var i = 1; i < 11; i++) buffer16[offset + i] = 0xFF;
|
||||
buffer16[offset + 11] = 0x00;
|
||||
Timestamp ts = new Timestamp(LBA + 150);
|
||||
var ts = new Timestamp(LBA + 150);
|
||||
buffer16[offset + 12] = BCD2.IntToBCD(ts.MIN);
|
||||
buffer16[offset + 13] = BCD2.IntToBCD(ts.SEC);
|
||||
buffer16[offset + 14] = BCD2.IntToBCD(ts.FRAC);
|
||||
|
@ -76,7 +76,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public static void EDC_Mode2_Form1(byte[] buf2352, int offset)
|
||||
{
|
||||
uint edc = ECM.EDC_Calc(buf2352, offset + 16, 2048 + 8);
|
||||
var edc = ECM.EDC_Calc(buf2352, offset + 16, 2048 + 8);
|
||||
ECM.PokeUint(buf2352, offset + 2072, edc);
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public static void EDC_Mode2_Form2(byte[] buf2352, int offset)
|
||||
{
|
||||
uint edc = ECM.EDC_Calc(buf2352, offset + 16, 2324 + 8);
|
||||
var edc = ECM.EDC_Calc(buf2352, offset + 16, 2324 + 8);
|
||||
ECM.PokeUint(buf2352, offset + 2348, edc);
|
||||
}
|
||||
|
||||
|
@ -97,11 +97,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public static void ECM_Mode1(byte[] buf2352, int offset, int LBA)
|
||||
{
|
||||
//EDC
|
||||
uint edc = ECM.EDC_Calc(buf2352, offset, 2064);
|
||||
var edc = ECM.EDC_Calc(buf2352, offset, 2064);
|
||||
ECM.PokeUint(buf2352, offset + 2064, edc);
|
||||
|
||||
//reserved, zero
|
||||
for (int i = 0; i < 8; i++) buf2352[offset + 2068 + i] = 0;
|
||||
for (var i = 0; i < 8; i++) buf2352[offset + 2068 + i] = 0;
|
||||
|
||||
//ECC
|
||||
ECM.ECC_Populate(buf2352, offset, buf2352, offset, false);
|
||||
|
@ -113,13 +113,13 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public static void InterleaveSubcode(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index)
|
||||
{
|
||||
for (int d = 0; d < 12; d++)
|
||||
for (var d = 0; d < 12; d++)
|
||||
{
|
||||
for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++)
|
||||
for (var bitpoodle = 0; bitpoodle < 8; bitpoodle++)
|
||||
{
|
||||
int rawb = 0;
|
||||
var rawb = 0;
|
||||
|
||||
for (int ch = 0; ch < 8; ch++)
|
||||
for (var ch = 0; ch < 8; ch++)
|
||||
{
|
||||
rawb |= ((in_buf[ch * 12 + d + in_buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch);
|
||||
}
|
||||
|
@ -134,12 +134,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public static void DeinterleaveSubcode(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index)
|
||||
{
|
||||
for (int i = 0; i < 96; i++)
|
||||
for (var i = 0; i < 96; i++)
|
||||
out_buf[i] = 0;
|
||||
|
||||
for (int ch = 0; ch < 8; ch++)
|
||||
for (var ch = 0; ch < 8; ch++)
|
||||
{
|
||||
for (int i = 0; i < 96; i++)
|
||||
for (var i = 0; i < 96; i++)
|
||||
{
|
||||
out_buf[(ch * 12) + (i >> 3) + out_buf_index] |= (byte)(((in_buf[i + in_buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7)));
|
||||
}
|
||||
|
@ -151,18 +151,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public static unsafe void InterleaveSubcodeInplace(byte[] buf, int buf_index)
|
||||
{
|
||||
byte* out_buf = stackalloc byte[96];
|
||||
var out_buf = stackalloc byte[96];
|
||||
|
||||
for (int i = 0; i < 96; i++)
|
||||
for (var i = 0; i < 96; i++)
|
||||
out_buf[i] = 0;
|
||||
|
||||
for (int d = 0; d < 12; d++)
|
||||
for (var d = 0; d < 12; d++)
|
||||
{
|
||||
for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++)
|
||||
for (var bitpoodle = 0; bitpoodle < 8; bitpoodle++)
|
||||
{
|
||||
int rawb = 0;
|
||||
var rawb = 0;
|
||||
|
||||
for (int ch = 0; ch < 8; ch++)
|
||||
for (var ch = 0; ch < 8; ch++)
|
||||
{
|
||||
rawb |= ((buf[ch * 12 + d + buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch);
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 96; i++)
|
||||
for (var i = 0; i < 96; i++)
|
||||
buf[i + buf_index] = out_buf[i];
|
||||
}
|
||||
|
||||
|
@ -179,20 +179,20 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// </summary>
|
||||
public static unsafe void DeinterleaveSubcodeInplace(byte[] buf, int buf_index)
|
||||
{
|
||||
byte* out_buf = stackalloc byte[96];
|
||||
var out_buf = stackalloc byte[96];
|
||||
|
||||
for (int i = 0; i < 96; i++)
|
||||
for (var i = 0; i < 96; i++)
|
||||
out_buf[i] = 0;
|
||||
|
||||
for (int ch = 0; ch < 8; ch++)
|
||||
for (var ch = 0; ch < 8; ch++)
|
||||
{
|
||||
for (int i = 0; i < 96; i++)
|
||||
for (var i = 0; i < 96; i++)
|
||||
{
|
||||
out_buf[(ch * 12) + (i >> 3)] |= (byte)(((buf[i + buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7)));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 96; i++)
|
||||
for (var i = 0; i < 96; i++)
|
||||
buf[i + buf_index] = out_buf[i];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue