Revert "Move some private helper methods to local methods in DiscSystem"

This reverts commit 6f813edbdb.
This commit is contained in:
zeromus 2020-05-26 12:51:33 -05:00
parent 7244231cc4
commit 5a6072cdf0
10 changed files with 302 additions and 186 deletions

View File

@ -91,7 +91,7 @@ namespace BizHawk.Emulation.DiscSystem
}
#endif
static MednaDisc()
private static void CheckLibrary()
{
var lib = OSTailoredCode.LinkedLibManager.LoadOrZero("mednadisc.dll");
_IsLibraryAvailable = lib != IntPtr.Zero
@ -99,6 +99,11 @@ namespace BizHawk.Emulation.DiscSystem
if (lib != IntPtr.Zero) OSTailoredCode.LinkedLibManager.FreeByPtr(lib);
}
static MednaDisc()
{
CheckLibrary();
}
private static bool _IsLibraryAvailable;
public static bool IsLibraryAvailable => _IsLibraryAvailable;

View File

@ -146,33 +146,19 @@ namespace BizHawk.Emulation.DiscSystem
return true;
}
private List<KeyValuePair<string, ISOFileNode>> fileNodes;
private List<ISODirectoryNode> dirsParsed;
/// <summary>
/// Returns a flat list of all recursed files
/// </summary>
public List<KeyValuePair<string, ISOFileNode>> EnumerateAllFilesRecursively()
{
var fileNodes = new List<KeyValuePair<string, ISOFileNode>>();
fileNodes = new List<KeyValuePair<string, ISOFileNode>>();
if (Root.Children == null) return fileNodes;
var dirsParsed = new List<ISODirectoryNode>();
void ProcessDirectoryFiles(Dictionary<string, ISONode> idn)
{
foreach (var n in idn)
{
if (n.Value is ISODirectoryNode subdirNode)
{
if (dirsParsed.Contains(subdirNode)) continue;
dirsParsed.Add(subdirNode);
ProcessDirectoryFiles(subdirNode.Children);
}
else
{
fileNodes.Add(new KeyValuePair<string, ISOFileNode>(n.Key, n.Value as ISOFileNode));
}
}
}
dirsParsed = new List<ISODirectoryNode>();
foreach (var idn in Root.Children.Values.OfType<ISODirectoryNode>()) // iterate through each folder
{
// process all files in this directory (and recursively process files in subfolders)
@ -181,6 +167,24 @@ namespace BizHawk.Emulation.DiscSystem
ProcessDirectoryFiles(idn.Children);
}
return fileNodes.Distinct().ToList();
}
private void ProcessDirectoryFiles(Dictionary<string, ISONode> idn)
{
foreach (var n in idn)
{
if (n.Value is ISODirectoryNode subdirNode)
{
if (dirsParsed.Contains(subdirNode)) continue;
dirsParsed.Add(subdirNode);
ProcessDirectoryFiles(subdirNode.Children);
}
else
{
KeyValuePair<string, ISOFileNode> f = new KeyValuePair<string, ISOFileNode>(n.Key, n.Value as ISOFileNode);
fileNodes.Add(f);
}
}
}
/// <summary>

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
@ -16,6 +17,11 @@ namespace BizHawk.Emulation.DiscSystem
public bool IsAudio;
}
private static string[] Escape(IEnumerable<string> args)
{
return args.Select(s => s.Contains(" ") ? $"\"{s}\"" : s).ToArray();
}
//note: accepts . or : in the stream stream/substream separator in the stream ID format, since that changed at some point in FFMPEG history
//if someone has a better idea how to make the determination of whether an audio stream is available, I'm all ears
private static readonly Regex rxHasAudio = new Regex(@"Stream \#(\d*(\.|\:)\d*)\: Audio", RegexOptions.Compiled);
@ -49,9 +55,9 @@ namespace BizHawk.Emulation.DiscSystem
public int ExitCode;
}
public static RunResults Run(params string[] args)
public RunResults Run(params string[] args)
{
args = args.Select(s => s.Contains(" ") ? $"\"{s}\"" : s).ToArray();
args = Escape(args);
StringBuilder sbCmdline = new StringBuilder();
for (int i = 0; i < args.Length; i++)
{
@ -100,7 +106,7 @@ namespace BizHawk.Emulation.DiscSystem
}
}
internal static class AudioDecoder
internal class AudioDecoder
{
[Serializable]
public class AudioDecoder_Exception : Exception
@ -111,17 +117,50 @@ namespace BizHawk.Emulation.DiscSystem
}
}
/// <exception cref="AudioDecoder_Exception">could not find source audio for <paramref name="audioPath"/></exception>
public static byte[] AcquireWaveData(string audioPath)
public AudioDecoder()
{
// find audio at a path similar to the provided path (i.e. finds Track01.mp3 for Track01.wav)
// TODO isn't this redundant with CueFileResolver?
var basePath = Path.GetFileNameWithoutExtension(audioPath);
var files = new DirectoryInfo(Path.GetDirectoryName(audioPath)).GetFiles().Select(fi => fi.FullName).ToList();
var found = files.Where(f => string.Equals(f, audioPath, StringComparison.InvariantCulture)) // first, look for the file type we actually asked for
.Concat(files.Where(f => string.Equals(Path.GetFileNameWithoutExtension(f), basePath, StringComparison.InvariantCulture))) // then, look for any other type
.FirstOrDefault(f => new FFMpeg().QueryAudio(f).IsAudio); // ignore false positives
return new FFMpeg().DecodeAudio(found ?? throw new AudioDecoder_Exception($"Could not find source audio for: {Path.GetFileName(audioPath)}"));
}
private bool CheckForAudio(string path)
{
FFMpeg ffmpeg = new FFMpeg();
var qa = ffmpeg.QueryAudio(path);
return qa.IsAudio;
}
/// <summary>
/// finds audio at a path similar to the provided path (i.e. finds Track01.mp3 for Track01.wav)
/// TODO - isnt this redundant with CueFileResolver?
/// </summary>
private string FindAudio(string audioPath)
{
string basePath = Path.GetFileNameWithoutExtension(audioPath);
//look for potential candidates
var di = new DirectoryInfo(Path.GetDirectoryName(audioPath));
var fis = di.GetFiles();
//first, look for the file type we actually asked for
foreach (var fi in fis)
{
if (fi.FullName.ToUpper() == audioPath.ToUpper())
if (CheckForAudio(fi.FullName))
return fi.FullName;
}
//then look for any other type
foreach (var fi in fis)
{
if (Path.GetFileNameWithoutExtension(fi.FullName).ToUpper() == basePath.ToUpper())
{
if (CheckForAudio(fi.FullName))
{
return fi.FullName;
}
}
}
return null;
}
/// <exception cref="AudioDecoder_Exception">could not find source audio for <paramref name="audioPath"/></exception>
public byte[] AcquireWaveData(string audioPath) => new FFMpeg()
.DecodeAudio(FindAudio(audioPath) ?? throw new AudioDecoder_Exception($"Could not find source audio for: {Path.GetFileName(audioPath)}"));
}
}

View File

@ -67,8 +67,6 @@ namespace BizHawk.Emulation.DiscSystem
public void Load(string path)
{
const string MISFORMED_EXCEPTION_MESSAGE = "Mis-formed ECM file";
stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
//skip header
@ -79,17 +77,17 @@ namespace BizHawk.Emulation.DiscSystem
{
//read block count. this format is really stupid. maybe its good for detecting non-ecm files or something.
int b = stream.ReadByte();
if (b == -1) throw new InvalidOperationException(MISFORMED_EXCEPTION_MESSAGE);
if (b == -1) MisformedException();
int bytes = 1;
int T = b & 3;
long N = (b >> 2) & 0x1F;
int nbits = 5;
while (b.Bit(7))
{
if (bytes == 5) throw new InvalidOperationException(MISFORMED_EXCEPTION_MESSAGE); //if we're gonna need a 6th byte, this file is broken
if (bytes == 5) MisformedException(); //if we're gonna need a 6th byte, this file is broken
b = stream.ReadByte();
bytes++;
if (b == -1) throw new InvalidOperationException(MISFORMED_EXCEPTION_MESSAGE);
if (b == -1) MisformedException();
N |= (long)(b & 0x7F) << nbits;
nbits += 7;
}
@ -100,7 +98,7 @@ namespace BizHawk.Emulation.DiscSystem
//the 0x80000000 business is confusing, but this is almost positively an error
if (N >= 0x100000000)
throw new InvalidOperationException(MISFORMED_EXCEPTION_MESSAGE);
MisformedException();
uint todo = (uint)N + 1;
@ -134,7 +132,7 @@ namespace BizHawk.Emulation.DiscSystem
stream.Seek(todo * 2328, SeekOrigin.Current);
logOffset += todo * 2336;
}
else throw new InvalidOperationException(MISFORMED_EXCEPTION_MESSAGE);
else MisformedException();
}
//TODO - endian bug. need an endian-independent binary reader with good license (miscutils is apache license)
@ -145,6 +143,11 @@ namespace BizHawk.Emulation.DiscSystem
Length = logOffset;
}
private 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))

View File

@ -38,6 +38,9 @@ namespace BizHawk.Emulation.DiscSystem
BaseStream = null;
}
private static string ReadTag(BinaryReader br) =>
string.Concat(br.ReadChar(), br.ReadChar(), br.ReadChar(), br.ReadChar());
protected static void WriteTag(BinaryWriter bw, string tag)
{
for (int i = 0; i < 4; i++)
@ -275,9 +278,7 @@ namespace BizHawk.Emulation.DiscSystem
private long readCounter;
private RiffChunk ReadChunk(BinaryReader br)
{
static string ReadTag(BinaryReader br) => string.Concat(br.ReadChar(), br.ReadChar(), br.ReadChar(), br.ReadChar());
{
RiffChunk ret;
string tag = ReadTag(br); readCounter += 4;
uint size = br.ReadUInt32(); readCounter += 4;

View File

@ -214,18 +214,31 @@ namespace BizHawk.Emulation.DiscSystem
return sections;
}
private int PreParseIntegrityCheck(List<CCDSection> sections)
{
if (sections.Count == 0) throw new CCDParseException("Malformed CCD format: no sections");
//we need at least a CloneCD and Disc section
if (sections.Count < 2) throw new CCDParseException("Malformed CCD format: insufficient sections");
var ccdSection = sections[0];
if (ccdSection.Name != "CLONECD")
throw new CCDParseException("Malformed CCD format: confusing first section name");
if (!ccdSection.ContainsKey("VERSION"))
throw new CCDParseException("Malformed CCD format: missing version in CloneCD section");
if(sections[1].Name != "DISC")
throw new CCDParseException("Malformed CCD format: section[1] isn't [Disc]");
int version = ccdSection["VERSION"];
return version;
}
/// <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)
{
static int PreParseIntegrityCheck(IReadOnlyList<CCDSection> sections)
{
if (sections.Count == 0) throw new CCDParseException("Malformed CCD format: no sections");
if (sections.Count < 2) throw new CCDParseException("Malformed CCD format: insufficient sections"); //we need at least a CloneCD and Disc section
if (sections[0].Name != "CLONECD") throw new CCDParseException("Malformed CCD format: confusing first section name");
if (sections[1].Name != "DISC") throw new CCDParseException("Malformed CCD format: section[1] isn't [Disc]");
return sections[0].TryGetValue("VERSION", out var version) ? version : throw new CCDParseException("Malformed CCD format: missing version in CloneCD section");
}
CCDFile ccdf = new CCDFile();
var sections = ParseSections(stream);

View File

@ -107,7 +107,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.)");
}
byte[] buf = AudioDecoder.AcquireWaveData(ccf.FullPath);
AudioDecoder dec = new AudioDecoder();
byte[] buf = dec.AcquireWaveData(ccf.FullPath);
var blob = new Disc.Blob_WaveFile();
OUT_Disc.DisposableResources.Add(file_blob = blob);
blob.Load(new MemoryStream(buf));

View File

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Common.BufferExtensions;
//disc type identification logic
namespace BizHawk.Emulation.DiscSystem
@ -121,79 +119,6 @@ namespace BizHawk.Emulation.DiscSystem
/// </summary>
public DiscType DetectDiscType()
{
// this is a reasonable approach to identify Saturn
bool DetectSegaSaturn() => StringAt("SEGA SEGASATURN", 0);
// probably wrong
bool DetectMegaCD() => StringAt("SEGADISCSYSTEM", 0) || StringAt("SEGADISCSYSTEM", 16);
bool DetectPSX() => StringAt(" Licensed by ", 0, 4)
&& (
StringAt("Sony Computer Entertainment Euro", 32, 4)
|| StringAt("Sony Computer Entertainment Inc.", 32, 4)
|| StringAt("Sony Computer Entertainment Amer", 32, 4)
|| StringAt("Sony Computer Entertainment of A", 32, 4)
);
bool DetectPCFX()
{
var toc = _disc.TOC;
for (var t = toc.FirstRecordedTrackNumber; t <= toc.LastRecordedTrackNumber; t++)
{
var track = _disc.TOC.TOCItems[t];
//asni - this search is less specific - turns out there are discs where 'Hu:' is not present
if (track.IsData && SectorContains("pc-fx", track.LBA)) return true;
}
return false;
}
// asni 20171011 - this ONLY works if a valid cuefile/ccd is passed into DiscIdentifier.
// if an .iso is presented, the internally manufactured cue data does not work - possibly something to do with track 01 being Audio.
// Not tested, but presumably PCFX has the same issue
bool DetectTurboCD()
{
var toc = _disc.TOC;
for (int t = toc.FirstRecordedTrackNumber; t <= toc.LastRecordedTrackNumber; t++)
{
var track = _disc.TOC.TOCItems[t];
// asni - pcfx games also contain the 'PC Engine' string
if (track.IsData && SectorContains("pc engine", track.LBA + 1) && !SectorContains("pc-fx", track.LBA + 1)) return true;
}
return false;
}
bool Detect3DO()
{
var toc = _disc.TOC;
for (var t = toc.FirstRecordedTrackNumber; t <= toc.LastRecordedTrackNumber; t++)
{
var track = _disc.TOC.TOCItems[t];
if (track.IsData && SectorContains("iamaduckiamaduck", track.LBA)) return true;
}
return false;
}
// asni - slightly longer-running than the others due to its brute-force nature. Should be run later in the method
bool DetectDreamcast()
{
for (var i = 0; i < 1000; i++) if (SectorContains("segakatana", i)) return true;
return false;
}
bool DetectCDi() => StringAt("CD-RTOS", 8, 16);
bool DetectGameCube()
{
var data = ReadSectorCached(0);
return data != null && data.Skip(28).Take(4).ToArray().BytesToHexString() == "C2339F3D";
}
bool DetectWii()
{
var data = ReadSectorCached(0);
return data != null && data.Skip(24).Take(4).ToArray().BytesToHexString() == "5D1C9EA3";
}
// PCFX & TurboCD sometimes (if not alltimes) have audio on track 1 - run these before the AudioDisc detection (asni)
if (DetectPCFX())
return DiscType.PCFX;
@ -294,6 +219,120 @@ namespace BizHawk.Emulation.DiscSystem
return DiscType.UnknownFormat;
}
/// <summary>
/// This is reasonable approach to ID saturn.
/// </summary>
private bool DetectSegaSaturn()
{
return StringAt("SEGA SEGASATURN", 0);
}
/// <summary>
/// probably wrong
/// </summary>
private bool DetectMegaCD()
{
return StringAt("SEGADISCSYSTEM", 0) || StringAt("SEGADISCSYSTEM", 16);
}
private bool DetectPSX()
{
if (!StringAt(" Licensed by ", 0, 4)) return false;
return (StringAt("Sony Computer Entertainment Euro", 32, 4)
|| StringAt("Sony Computer Entertainment Inc.", 32, 4)
|| StringAt("Sony Computer Entertainment Amer", 32, 4)
|| StringAt("Sony Computer Entertainment of A", 32, 4)
);
}
private bool DetectPCFX()
{
var toc = _disc.TOC;
for (int t = toc.FirstRecordedTrackNumber;
t <= toc.LastRecordedTrackNumber;
t++)
{
var track = _disc.TOC.TOCItems[t];
//asni - this search is less specific - turns out there are discs where 'Hu:' is not present
if (track.IsData && SectorContains("pc-fx", track.LBA))
return true;
}
return false;
}
//asni 20171011 - this ONLY works if a valid cuefile/ccd is passed into DiscIdentifier.
//if an .iso is presented, the internally manufactured cue data does not work - possibly something to do with
//track 01 being Audio. Not tested, but presumably PCFX has the same issue
private bool DetectTurboCD()
{
var toc = _disc.TOC;
for (int t = toc.FirstRecordedTrackNumber;
t <= toc.LastRecordedTrackNumber;
t++)
{
var track = _disc.TOC.TOCItems[t];
//asni - pcfx games also contain the 'PC Engine' string
if ((track.IsData && SectorContains("pc engine", track.LBA + 1) && !SectorContains("pc-fx", track.LBA + 1)))
return true;
}
return false;
}
private bool Detect3DO()
{
var toc = _disc.TOC;
for (int t = toc.FirstRecordedTrackNumber;
t <= toc.LastRecordedTrackNumber;
t++)
{
var track = _disc.TOC.TOCItems[t];
if (track.IsData && SectorContains("iamaduckiamaduck", track.LBA))
return true;
}
return false;
}
//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++)
{
if (SectorContains("segakatana", i))
return true;
}
return false;
}
private bool DetectCDi()
{
return StringAt("CD-RTOS", 8, 16);
}
private bool DetectGameCube()
{
var data = ReadSectorCached(0);
if (data == null) return false;
byte[] magic = data.Skip(28).Take(4).ToArray();
string hexString = "";
foreach (var b in magic)
hexString += b.ToString("X2");
return hexString == "C2339F3D";
}
private bool DetectWii()
{
var data = ReadSectorCached(0);
if (data == null) return false;
byte[] magic = data.Skip(24).Take(4).ToArray();
string hexString = "";
foreach (var b in magic)
hexString += b.ToString("X2");
return hexString == "5D1C9EA3";
}
private byte[] ReadSectorCached(int lba)
{
//read it if we don't have it cached

View File

@ -126,6 +126,44 @@ namespace BizHawk.Emulation.DiscSystem
return 2448;
}
private int ReadLBA_2048_Mode1(int lba, byte[] buffer, int offset)
{
//we can read the 2048 bytes directly
var sector = disc.SynthProvider.Get(lba);
if (sector == null) return 0;
PrepareBuffer(buffer, offset, 2048);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.User2048;
sector.Synth(job);
Buffer.BlockCopy(buf2442, 16, buffer, offset, 2048);
return 2048;
}
private int ReadLBA_2048_Mode2_Form1(int lba, byte[] buffer, int offset)
{
//we can read the 2048 bytes directly but we have to get them from the mode 2 data
var sector = disc.SynthProvider.Get(lba);
if (sector == null) return 0;
PrepareBuffer(buffer, offset, 2048);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.User2336;
sector.Synth(job);
Buffer.BlockCopy(buf2442, 24, buffer, offset, 2048);
return 2048;
}
/// <summary>
/// Reads 12 bytes of subQ data from a sector.
/// This is necessarily deinterleaved.
@ -158,48 +196,10 @@ namespace BizHawk.Emulation.DiscSystem
/// <exception cref="InvalidOperationException"></exception>
public int ReadLBA_2048(int lba, byte[] buffer, int offset)
{
int ReadLBA_2048_Mode1()
{
//we can read the 2048 bytes directly
var sector = disc.SynthProvider.Get(lba);
if (sector == null) return 0;
PrepareBuffer(buffer, offset, 2048);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.User2048;
sector.Synth(job);
Buffer.BlockCopy(buf2442, 16, buffer, offset, 2048);
return 2048;
}
int ReadLBA_2048_Mode2_Form1()
{
//we can read the 2048 bytes directly but we have to get them from the mode 2 data
var sector = disc.SynthProvider.Get(lba);
if (sector == null) return 0;
PrepareBuffer(buffer, offset, 2048);
PrepareJob(lba);
job.DestBuffer2448 = buf2442;
job.DestOffset = 0;
job.Parts = ESectorSynthPart.User2336;
sector.Synth(job);
Buffer.BlockCopy(buf2442, 24, buffer, offset, 2048);
return 2048;
}
if (Policy.UserData2048Mode == DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode1)
return ReadLBA_2048_Mode1();
return ReadLBA_2048_Mode1(lba, buffer, offset);
else if (Policy.UserData2048Mode == DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode2_Form1)
return ReadLBA_2048_Mode2_Form1();
return ReadLBA_2048_Mode2_Form1(lba, buffer, offset);
else
{
//we need to determine the type of the sector.

View File

@ -215,29 +215,40 @@ namespace BizHawk.Emulation.DiscSystem
return crc;
}
/// <summary>
/// returns the address from a sector. useful for saving it before zeroing it for ECC calculations
/// </summary>
private static uint GetSectorAddress(byte[] sector, int sector_offset)
{
return (uint)(
(sector[sector_offset + 12 + 0] << 0)
| (sector[sector_offset + 12 + 1] << 8)
| (sector[sector_offset + 12 + 2] << 16)
);
//| (sector[sector_offset + 12 + 3] << 24));
}
/// <summary>
/// sets the address for a sector. useful for restoring it after zeroing it for ECC calculations
/// </summary>
private static void SetSectorAddress(byte[] sector, int sector_offset, uint address)
{
sector[sector_offset + 12 + 0] = (byte)((address >> 0) & 0xFF);
sector[sector_offset + 12 + 1] = (byte)((address >> 8) & 0xFF);
sector[sector_offset + 12 + 2] = (byte)((address >> 16) & 0xFF);
//sector[sector_offset + 12 + 3] = (byte)((address >> 24) & 0xFF);
}
/// <summary>
/// populates a sector with valid ECC information.
/// it is safe to supply the same array for sector and dest.
/// </summary>
public static void ECC_Populate(byte[] src, int src_offset, byte[] dest, int dest_offset, bool zeroSectorAddress)
{
// returns the address from a sector. useful for saving it before zeroing it for ECC calculations
static uint GetSectorAddress(byte[] sector, int sector_offset) => (uint)(
(sector[sector_offset + 12 + 0] << 0)
| (sector[sector_offset + 12 + 1] << 8)
| (sector[sector_offset + 12 + 2] << 16)
// | (sector[sector_offset + 12 + 3] << 24)
);
// sets the address for a sector. useful for restoring it after zeroing it for ECC calculations
static void SetSectorAddress(byte[] sector, int sector_offset, uint address)
{
sector[sector_offset + 12 + 0] = (byte)((address >> 0) & 0xFF);
sector[sector_offset + 12 + 1] = (byte)((address >> 8) & 0xFF);
sector[sector_offset + 12 + 2] = (byte)((address >> 16) & 0xFF);
// sector[sector_offset + 12 + 3] = (byte)((address >> 24) & 0xFF);
}
//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);
if (zeroSectorAddress) SetSectorAddress(src, src_offset, 0);