Files
MPF/Utilities/Validators.cs
Matt Nadareski c1bd30e008 Streamline Callbacks and Ensure MediaType (#78)
* Separate drive speeds; better ensuring of media type

This is the first attempt at separating out the drive speeds into separate categories. At the moment, it seems to be working just fine for CD and DVD.
This also includes some updated code to better ensure that the detected (or selected if the user has changed it) media type is kept between all of the regular changes

* Multiple medium

* Add more drive speed prototyping

* Strip out redundant calls

* Separate drive speeds; better ensuring of media type

This is the first attempt at separating out the drive speeds into separate categories. At the moment, it seems to be working just fine for CD and DVD.
This also includes some updated code to better ensure that the detected (or selected if the user has changed it) media type is kept between all of the regular changes

* Multiple medium

* Add more drive speed prototyping

* Strip out redundant calls

* Streamline merge

* Comment patrol; keep BluRay a first-class citizen

* HD-DVD is supported?!
2018-06-27 23:21:52 -07:00

1338 lines
52 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using IMAPI2;
using DICUI.Data;
using DICUI.External;
namespace DICUI.Utilities
{
public static class Validators
{
/// <summary>
/// Get a list of valid MediaTypes for a given KnownSystem
/// </summary>
/// <param name="sys">KnownSystem value to check</param>
/// <returns>MediaTypes, if possible</returns>
public static List<MediaType?> GetValidMediaTypes(KnownSystem? sys)
{
var types = new List<MediaType?>();
switch (sys)
{
#region Consoles
case KnownSystem.BandaiPlaydiaQuickInteractiveSystem:
types.Add(MediaType.CD);
break;
case KnownSystem.BandaiApplePippin:
types.Add(MediaType.CD);
break;
case KnownSystem.CommodoreAmigaCD32:
types.Add(MediaType.CD);
break;
case KnownSystem.CommodoreAmigaCDTV:
types.Add(MediaType.CD);
break;
case KnownSystem.MattelHyperscan:
types.Add(MediaType.CD);
break;
case KnownSystem.MicrosoftXBOX:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
break;
case KnownSystem.MicrosoftXBOX360XDG2:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
break;
case KnownSystem.MicrosoftXBOX360XDG3:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
break;
case KnownSystem.MicrosoftXBOXOne:
types.Add(MediaType.BluRay);
break;
case KnownSystem.NECPCEngineTurboGrafxCD:
types.Add(MediaType.CD);
break;
case KnownSystem.NECPCFX:
types.Add(MediaType.CD);
break;
case KnownSystem.NintendoGameCube:
types.Add(MediaType.GameCubeGameDisc);
break;
case KnownSystem.NintendoWii:
types.Add(MediaType.WiiOpticalDisc);
break;
case KnownSystem.NintendoWiiU:
types.Add(MediaType.WiiUOpticalDisc);
break;
case KnownSystem.Panasonic3DOInteractiveMultiplayer:
types.Add(MediaType.CD);
break;
case KnownSystem.PhilipsCDi:
types.Add(MediaType.CD);
break;
case KnownSystem.SegaCDMegaCD:
types.Add(MediaType.CD);
break;
case KnownSystem.SegaDreamcast:
types.Add(MediaType.GDROM);
break;
case KnownSystem.SegaSaturn:
types.Add(MediaType.CD);
break;
case KnownSystem.SNKNeoGeoCD:
types.Add(MediaType.CD);
break;
case KnownSystem.SonyPlayStation:
types.Add(MediaType.CD);
break;
case KnownSystem.SonyPlayStation2:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
break;
case KnownSystem.SonyPlayStation3:
types.Add(MediaType.BluRay);
break;
case KnownSystem.SonyPlayStation4:
types.Add(MediaType.BluRay);
break;
case KnownSystem.SonyPlayStationPortable:
types.Add(MediaType.UMD);
break;
case KnownSystem.VMLabsNuon:
types.Add(MediaType.DVD);
break;
case KnownSystem.VTechVFlashVSmilePro:
types.Add(MediaType.CD);
break;
case KnownSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem:
types.Add(MediaType.DVD);
break;
#endregion
#region Computers
case KnownSystem.AcornArchimedes:
types.Add(MediaType.CD);
break;
case KnownSystem.AppleMacintosh:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
types.Add(MediaType.Floppy);
break;
case KnownSystem.CommodoreAmigaCD:
types.Add(MediaType.CD);
break;
case KnownSystem.FujitsuFMTowns:
types.Add(MediaType.CD);
break;
case KnownSystem.IBMPCCompatible:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
types.Add(MediaType.Floppy);
break;
case KnownSystem.NECPC88:
types.Add(MediaType.CD);
break;
case KnownSystem.NECPC98:
types.Add(MediaType.CD);
break;
case KnownSystem.SharpX68000:
types.Add(MediaType.CD);
break;
#endregion
#region Arcade
case KnownSystem.AmigaCUBOCD32:
types.Add(MediaType.CD);
break;
case KnownSystem.AmericanLaserGames3DO:
types.Add(MediaType.CD);
break;
case KnownSystem.Atari3DO:
types.Add(MediaType.CD);
break;
case KnownSystem.Atronic:
types.Add(MediaType.CD);
break;
case KnownSystem.AUSCOMSystem1:
types.Add(MediaType.CD);
break;
case KnownSystem.BallyGameMagic:
types.Add(MediaType.CD);
break;
case KnownSystem.CapcomCPSystemIII:
types.Add(MediaType.CD);
break;
case KnownSystem.GlobalVRVarious:
types.Add(MediaType.CD);
break;
case KnownSystem.GlobalVRVortek:
types.Add(MediaType.CD);
break;
case KnownSystem.GlobalVRVortekV3:
types.Add(MediaType.DVD); // TODO: Confirm
break;
case KnownSystem.ICEPCHardware:
types.Add(MediaType.DVD);
break;
case KnownSystem.IncredibleTechnologiesEagle:
types.Add(MediaType.CD);
break;
case KnownSystem.IncredibleTechnologiesVarious:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
break;
case KnownSystem.KonamiFirebeat:
types.Add(MediaType.CD);
break;
case KnownSystem.KonamiGVSystem:
types.Add(MediaType.CD);
break;
case KnownSystem.KonamiM2:
types.Add(MediaType.CD);
break;
case KnownSystem.KonamiPython:
types.Add(MediaType.DVD); // TODO: Confirm
break;
case KnownSystem.KonamiPython2:
types.Add(MediaType.DVD); // TODO: Confirm
break;
case KnownSystem.KonamiSystem573:
types.Add(MediaType.CD);
break;
case KnownSystem.KonamiTwinkle:
types.Add(MediaType.CD);
break;
case KnownSystem.KonamiVarious:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
break;
case KnownSystem.MeritIndustriesBoardwalk:
types.Add(MediaType.CD); // TODO: Confirm
break;
case KnownSystem.MeritIndustriesMegaTouchAurora:
types.Add(MediaType.CD); // TODO: Confirm
break;
case KnownSystem.MeritIndustriesMegaTouchForce:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
break;
case KnownSystem.MeritIndustriesMegaTouchION:
types.Add(MediaType.CD);
types.Add(MediaType.DVD);
break;
case KnownSystem.MeritIndustriesMegaTouchMaxx:
types.Add(MediaType.DVD);
break;
case KnownSystem.MeritIndustriesMegaTouchXL:
types.Add(MediaType.CD);
break;
case KnownSystem.NamcoCapcomSystem256:
types.Add(MediaType.DVD);
break;
case KnownSystem.NamcoCapcomTaitoSystem246:
types.Add(MediaType.DVD);
break;
case KnownSystem.NamcoSegaNintendoTriforce:
types.Add(MediaType.GDROM);
break;
case KnownSystem.NamcoSystem12:
types.Add(MediaType.CD);
break;
case KnownSystem.NamcoSystem357:
types.Add(MediaType.DVD);
types.Add(MediaType.BluRay);
break;
case KnownSystem.NewJatreCDi:
types.Add(MediaType.CD);
break;
case KnownSystem.NichibutsuHighRateSystem:
types.Add(MediaType.CD);
break;
case KnownSystem.NichibutsuSuperCD:
types.Add(MediaType.CD);
break;
case KnownSystem.NichibutsuXRateSystem:
types.Add(MediaType.DVD);
break;
case KnownSystem.PhotoPlayVarious:
types.Add(MediaType.CD);
break;
case KnownSystem.RawThrillsVarious:
types.Add(MediaType.DVD);
break;
case KnownSystem.SegaChihiro:
types.Add(MediaType.GDROM);
break;
case KnownSystem.SegaEuropaR:
types.Add(MediaType.DVD); // TODO: Confirm
break;
case KnownSystem.SegaLindbergh:
types.Add(MediaType.DVD);
break;
case KnownSystem.SegaNaomi:
types.Add(MediaType.GDROM);
break;
case KnownSystem.SegaNaomi2:
types.Add(MediaType.GDROM);
break;
case KnownSystem.SegaNu:
types.Add(MediaType.DVD);
types.Add(MediaType.BluRay);
break;
case KnownSystem.SegaRingEdge:
types.Add(MediaType.DVD);
break;
case KnownSystem.SegaRingEdge2:
types.Add(MediaType.DVD);
break;
case KnownSystem.SegaRingWide:
types.Add(MediaType.DVD);
break;
case KnownSystem.SegaSTV:
types.Add(MediaType.CD);
break;
case KnownSystem.SegaSystem32:
types.Add(MediaType.CD);
break;
case KnownSystem.SeibuCATSSystem:
types.Add(MediaType.DVD);
break;
case KnownSystem.TABAustriaQuizard:
types.Add(MediaType.CD);
break;
case KnownSystem.TandyMemorexVisualInformationSystem:
types.Add(MediaType.CD);
break;
case KnownSystem.TsunamiTsuMoMultiGameMotionSystem:
types.Add(MediaType.CD);
break;
#endregion
#region Others
case KnownSystem.AudioCD:
types.Add(MediaType.CD);
break;
case KnownSystem.BDVideo:
types.Add(MediaType.BluRay);
break;
case KnownSystem.DVDVideo:
types.Add(MediaType.DVD);
break;
case KnownSystem.EnhancedCD:
types.Add(MediaType.CD);
break;
case KnownSystem.HDDVDVideo:
types.Add(MediaType.HDDVD);
break;
case KnownSystem.PalmOS:
types.Add(MediaType.CD);
break;
case KnownSystem.PhilipsCDiDigitalVideo:
types.Add(MediaType.CD);
break;
case KnownSystem.PhotoCD:
types.Add(MediaType.CD);
break;
case KnownSystem.PlayStationGameSharkUpdates:
types.Add(MediaType.CD);
break;
case KnownSystem.TaoiKTV:
types.Add(MediaType.CD);
break;
case KnownSystem.TomyKissSite:
types.Add(MediaType.CD);
break;
case KnownSystem.VideoCD:
types.Add(MediaType.CD);
break;
#endregion
case KnownSystem.NONE:
default:
types.Add(MediaType.NONE);
break;
}
return types;
}
/// <summary>
/// Create a list of systems matched to their respective enums
/// </summary>
/// <returns>Systems matched to enums, if possible</returns>
/// <remarks>
/// If something has a "string, null" value, it should be assumed that it is a separator
/// </remarks>
/// TODO: Figure out a way that the sections can be generated more automatically
public static OrderedDictionary<string, KnownSystem?> CreateListOfSystems()
{
var systemsDict = new OrderedDictionary<string, KnownSystem?>();
foreach (KnownSystem system in Enum.GetValues(typeof(KnownSystem)))
{
// In the special cases of breaks, we want to add the proper mappings for sections
switch (system)
{
// Consoles section
case KnownSystem.BandaiPlaydiaQuickInteractiveSystem:
systemsDict.Add("---------- Consoles ----------", null);
break;
// Computers section
case KnownSystem.AcornArchimedes:
systemsDict.Add("---------- Computers ----------", null);
break;
// Arcade section
case KnownSystem.AmigaCUBOCD32:
systemsDict.Add("---------- Arcade ----------", null);
break;
// Other section
case KnownSystem.AudioCD:
systemsDict.Add("---------- Others ----------", null);
break;
}
systemsDict.Add(Converters.KnownSystemToString(system), system);
}
return systemsDict;
}
/// <summary>
/// Create a list of active optical drives matched to their volume labels
/// </summary>
/// <returns>Active drives, matched to labels, if possible</returns>
/// <remarks>
/// https://stackoverflow.com/questions/3060796/how-to-distinguish-between-usb-and-floppy-devices?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
/// https://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx
/// </remarks>
public static OrderedDictionary<char, string> CreateListOfDrives()
{
// Get the floppy drives
var floppyDrives = new List<KeyValuePair<char, string>>();
try
{
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
"SELECT * FROM Win32_LogicalDisk");
var collection = searcher.Get();
foreach (ManagementObject queryObj in collection)
{
uint? mediaType = (uint?)queryObj["MediaType"];
if (mediaType != null && ((mediaType > 0 && mediaType < 11) || (mediaType > 12 && mediaType < 22)))
{
char devId = queryObj["DeviceID"].ToString()[0];
floppyDrives.Add(new KeyValuePair<char, string>(devId, UIElements.FloppyDriveString));
}
}
}
catch
{
// No-op
}
// Get the optical disc drives
List<KeyValuePair<char, string>> discDrives = DriveInfo.GetDrives()
.Where(d => d.DriveType == DriveType.CDRom && d.IsReady)
.Select(d => new KeyValuePair<char, string>(d.Name[0], d.VolumeLabel))
.ToList();
// Add the two lists together and order
floppyDrives.AddRange(discDrives);
floppyDrives = floppyDrives.OrderBy(i => i.Key).ToList();
// Add to the ordered dictionary and return
var drivesDict = new OrderedDictionary<char, string>();
foreach (var drive in floppyDrives)
{
drivesDict.Add(drive.Key, drive.Value);
}
return drivesDict;
}
/// <summary>
/// Get the current disc type from drive letter
/// </summary>
/// <param name="driveLetter"></param>
/// <returns></returns>
/// <remarks>
/// https://stackoverflow.com/questions/11420365/detecting-if-disc-is-in-dvd-drive
/// </remarks>
public static MediaType? GetDiscType(char? driveLetter)
{
// Get the DeviceID from the current drive letter
string deviceId = null;
try
{
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
"SELECT * FROM Win32_CDROMDrive WHERE Id = '" + driveLetter + ":\'");
var collection = searcher.Get();
foreach (ManagementObject queryObj in collection)
{
deviceId = (string)queryObj["DeviceID"];
}
}
catch
{
// We don't care what the error was
return null;
}
// If we got no valid device, we don't care and just return
if (deviceId == null)
{
return null;
}
// Get all relevant disc information
MsftDiscMaster2 discMaster = new MsftDiscMaster2();
deviceId = deviceId.ToLower().Replace('\\', '#');
string id = null;
foreach (var disc in discMaster)
{
if (disc.ToString().Contains(deviceId))
{
id = disc.ToString();
}
}
// If we couldn't find the drive, we don't care and return
if (id == null)
{
return null;
}
// Otherwise, we get the media type, if any
MsftDiscRecorder2 recorder = new MsftDiscRecorder2();
recorder.InitializeDiscRecorder(id);
MsftDiscFormat2Data dataWriter = new MsftDiscFormat2Data();
dataWriter.Recorder = recorder;
var media = dataWriter.CurrentPhysicalMediaType;
if (media != IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_UNKNOWN)
{
return Converters.IMAPIDiskTypeToMediaType(media);
}
return null;
}
/// <summary>
/// Get the drive speed of the currently selected drive
/// </summary>
/// <returns>Speed of the drive converted from kbps</returns>
/// <remarks>
/// DIC uses the SCSI_MODE_SENSE command to check this, so does QPXTool (a different one, but still)
/// See if SCSI_MODE_SENSE can be used here
/// Currently, the calculations get something that is technically accurate, but is different than the advertisised
/// capabilities of the drives (according to QPXTool)
/// TransferRate appears to be the CURRENT transfer rate, not the maximum... basically making that flag useless
/// </remarks>
public static int GetDriveSpeed(char driveLetter)
{
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
"SELECT * FROM Win32_CDROMDrive WHERE Id = '" + driveLetter + ":\'");
var collection = searcher.Get();
double? transferRate = -1;
foreach (ManagementObject queryObj in collection)
{
var obj = queryObj["TransferRate"];
transferRate = (double?)queryObj["TransferRate"];
}
// Transfer Rates (kBps)
double cdTransfer = 153.6;
double dvdTransfer = 1385;
double cdTransferTest = ((transferRate ?? -1)) / cdTransfer;
double dvdTransferTest = ((transferRate ?? -1)) / dvdTransfer;
return 0;
}
public unsafe static int GetDriveSpeedEx(char driveLetter, MediaType? mediaType)
{
// Get the DeviceID from the current drive letter
string deviceId = null;
try
{
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
"SELECT * FROM Win32_CDROMDrive WHERE Id = '" + driveLetter + ":\'");
var collection = searcher.Get();
foreach (ManagementObject queryObj in collection)
{
deviceId = (string)queryObj["DeviceID"];
}
}
catch
{
// We don't care what the error was
return -1;
}
// If we got no valid device, we don't care and just return
if (deviceId == null)
{
return -1;
}
// Get all relevant disc information
MsftDiscMaster2 discMaster = new MsftDiscMaster2();
deviceId = deviceId.ToLower().Replace('\\', '#');
string id = null;
foreach (var disc in discMaster)
{
if (disc.ToString().Contains(deviceId))
{
id = disc.ToString();
}
}
// If we couldn't find the drive, we don't care and return
if (id == null)
{
return -1;
}
// Now we initialize the recorder to get disc info
MsftDiscRecorder2 recorder = new MsftDiscRecorder2();
recorder.InitializeDiscRecorder(id);
IDiscRecorder2Ex recorderEx = recorder as IDiscRecorder2Ex;
IMAPI_FEATURE_PAGE_TYPE ifpt = IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_PROFILE_LIST;
switch(mediaType)
{
case MediaType.CD:
ifpt = IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_CD_READ;
break;
case MediaType.DVD:
ifpt = IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_DVD_READ;
break;
case MediaType.HDDVD:
ifpt = IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_HD_DVD_READ;
break;
case MediaType.BluRay:
ifpt = IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_BD_READ;
break;
}
// If we couldn't determine the media type properly, we don't care and return
if (ifpt == IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_PROFILE_LIST)
{
return -1;
}
// Ones that haven't worked:
// recorderEx.GetAdapterDescriptor
// recorderEx.GetDeviceDescriptor
// recorderEx.GetDiscInformation
// recorderEx.GetTrackInformation
// Now we get the requested feature page
// TODO: Figure out structure of returned data
IntPtr featureData = Marshal.AllocHGlobal(32 * sizeof(byte));
recorderEx.GetFeaturePage(
ifpt,
(sbyte)0,
featureData,
out uint byteSize);
byte[] outFeatureArray = new byte[byteSize];
Marshal.Copy(featureData, outFeatureArray, 0, (int)byteSize);
// Now we get the requested mode data
// TODO: Figure out structure of returned data
IntPtr modeData = Marshal.AllocHGlobal(256 * sizeof(byte));
recorderEx.GetModePage(
(IMAPI_MODE_PAGE_TYPE)0x2A,
IMAPI_MODE_PAGE_REQUEST_TYPE.IMAPI_MODE_PAGE_REQUEST_TYPE_CURRENT_VALUES,
modeData,
out uint modeDataSize);
byte[] outModeArray = new byte[modeDataSize];
Marshal.Copy(modeData, outModeArray, 0, (int)modeDataSize);
// Now we send the command to get sense data from the device
// TODO: This seems like the best option, but how is this data structured properly?
byte[] cdbArray = new byte[] { 0x5a, 0x0, 0x2a, 0x00, 0xff, 0x0 };
byte[] senseBuffer = new byte[256];
byte[] buffer = new byte[256];
uint bufferSize = 256;
recorderEx.SendCommandGetDataFromDevice(
ref cdbArray[0],
(uint)6,
senseBuffer,
(uint)60,
out buffer[0],
bufferSize,
out uint BufferFetched);
return -1;
}
/// <summary>
/// Validate that at string would be valid as input to DiscImageCreator
/// </summary>
/// <param name="parameters">String representing all parameters</param>
/// <returns>True if it would be valid, false otherwise</returns>
public static bool ValidateParameters(string parameters)
{
// The string has to be valid by itself first
if (String.IsNullOrWhiteSpace(parameters))
{
return false;
}
// Now split the string into parts for easier validation
// https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes
parameters = parameters.Trim();
List<string> parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+")
.Cast<Match>()
.Select(m => m.Value)
.ToList();
// Determine what the commandline should look like given the first item
int index = -1;
switch (parts[0])
{
case DICCommands.CompactDisc:
case DICCommands.GDROM:
case DICCommands.Swap:
case DICCommands.Data:
if (!IsValidDriveLetter(parts[1]))
{
return false;
}
else if (IsFlag(parts[2]))
{
return false;
}
else if (!IsValidNumber(parts[3], lowerBound: 0, upperBound: 72))
{
return false;
}
if (parts[0] == DICCommands.Swap)
{
if (parts.Count > 5)
{
return false;
}
}
else if (parts[0] == DICCommands.Data || parts[0] == DICCommands.Audio)
{
if (!IsValidNumber(parts[4]) || !IsValidNumber(parts[5]))
{
return false;
}
index = 6;
}
else
{
index = 4;
}
break;
case DICCommands.DigitalVideoDisc:
if (!IsValidDriveLetter(parts[1]))
{
return false;
}
else if (IsFlag(parts[2]))
{
return false;
}
else if (!IsValidNumber(parts[3], lowerBound: 0, upperBound: 72)) // Officially 0-16
{
return false;
}
// Loop through all auxilary flags
for (int i = 4; i < parts.Count; i++)
{
switch (parts[i])
{
case DICFlags.DisableBeep:
case DICFlags.CMI:
case DICFlags.Raw:
// No-op, all of these are single flags
break;
case DICFlags.ForceUnitAccess:
// If the next item doesn't exist, it's good
if (!DoesNextExist(parts, i))
{
break;
}
// If the next item is a flag, it's good
if (IsFlag(parts[i + 1]))
{
break;
}
// If the next item isn't a valid number
else if (!IsValidNumber(parts[i + 1], lowerBound: 0))
{
return false;
}
i++;
break;
default:
return false;
}
}
break;
case DICCommands.BluRay:
case DICCommands.XBOX:
if (!IsValidDriveLetter(parts[1]))
{
return false;
}
else if (IsFlag(parts[2]))
{
return false;
}
// Loop through all auxilary flags
for (int i = 3; i < parts.Count; i++)
{
switch (parts[i])
{
case DICFlags.DisableBeep:
// No-op, this is a single flag
break;
case DICFlags.ForceUnitAccess:
// If the next item doesn't exist, it's good
if (!DoesNextExist(parts, i))
{
break;
}
// If the next item is a flag, it's good
if (IsFlag(parts[i + 1]))
{
break;
}
// If the next item isn't a valid number
else if (!IsValidNumber(parts[i + 1], lowerBound: 0))
{
return false;
}
i++;
break;
default:
return false;
}
}
break;
case DICCommands.Floppy:
if (!IsValidDriveLetter(parts[1]))
{
return false;
}
else if (IsFlag(parts[2]))
{
return false;
}
else if (parts.Count > 3)
{
return false;
}
break;
case DICCommands.Stop:
case DICCommands.Start:
case DICCommands.Eject:
case DICCommands.Close:
case DICCommands.Reset:
case DICCommands.DriveSpeed:
if (!IsValidDriveLetter(parts[1]))
{
return false;
}
else if (parts.Count > 2)
{
return false;
}
break;
case DICCommands.Sub:
case DICCommands.MDS:
if (IsFlag(parts[1]))
{
return false;
}
else if (parts.Count > 2)
{
return false;
}
break;
default:
return false;
}
// Loop through all auxilary flags, if necessary
if (index > 0)
{
for (int i = index; i < parts.Count; i++)
{
switch (parts[i])
{
case DICFlags.DisableBeep:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
break;
case DICFlags.D8Opcode:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
break;
case DICFlags.MCN:
if (parts[0] != DICCommands.CompactDisc)
{
return false;
}
break;
case DICFlags.AMSF:
if (parts[0] != DICCommands.CompactDisc)
{
return false;
}
break;
case DICFlags.Reverse:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.Data)
{
return false;
}
break;
case DICFlags.MultiSession:
if (parts[0] != DICCommands.CompactDisc)
{
return false;
}
break;
case DICFlags.ScanSectorProtect:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.Data)
{
return false;
}
break;
case DICFlags.ScanAntiMod:
if (parts[0] != DICCommands.CompactDisc)
{
return false;
}
break;
case DICFlags.NoFixSubP:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
break;
case DICFlags.NoFixSubQ:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
break;
case DICFlags.NoFixSubRtoW:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
break;
case DICFlags.NoFixSubQLibCrypt:
if (parts[0] != DICCommands.CompactDisc)
{
return false;
}
break;
case DICFlags.NoFixSubQSecuROM:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
break;
case DICFlags.ScanFileProtect:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.Data)
{
return false;
}
// If the next item doesn't exist, it's good
if (!DoesNextExist(parts, i))
{
break;
}
// If the next item is a flag, it's good
if (IsFlag(parts[i + 1]))
{
break;
}
// If the next item isn't a valid number
else if (!IsValidNumber(parts[i + 1], lowerBound: 0))
{
return false;
}
i++;
break;
case DICFlags.ForceUnitAccess:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
// If the next item doesn't exist, it's good
if (!DoesNextExist(parts, i))
{
break;
}
// If the next item is a flag, it's good
if (IsFlag(parts[i + 1]))
{
break;
}
// If the next item isn't a valid number
else if (!IsValidNumber(parts[i + 1], lowerBound: 0))
{
return false;
}
i++;
break;
case DICFlags.AddOffset:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.Audio)
{
return false;
}
// If the next item doesn't exist, it's not good
if (parts.Count == i + 1)
{
return false;
}
// If the next item isn't a valid number
else if (IsValidNumber(parts[i + 1]))
{
return false;
}
break;
case DICFlags.BEOpcode:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
// If the next item doesn't exist, it's good
if (!DoesNextExist(parts, i))
{
break;
}
// If the next item is a flag, it's good
if (IsFlag(parts[i + 1]))
{
break;
}
else if (parts[i + 1] != "raw"
&& (parts[i + 1] != "pack"))
{
return false;
}
i++;
break;
case DICFlags.C2Opcode:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
for (int j = 1; j < 4; j++)
{
// If the next item doesn't exist, it's good
if (!DoesNextExist(parts, i + 1))
{
i++;
break;
}
// If the next item is a flag, it's good
if (IsFlag(parts[i + 1]))
{
i++;
break;
}
// If the next item isn't a valid number
else if (!IsValidNumber(parts[i + 1], lowerBound: 0))
{
return false;
}
i++;
}
break;
case DICFlags.SubchannelReadLevel:
if (parts[0] != DICCommands.CompactDisc
&& parts[0] != DICCommands.GDROM
&& parts[0] != DICCommands.Data
&& parts[0] != DICCommands.Audio)
{
return false;
}
// If the next item doesn't exist, it's good
if (!DoesNextExist(parts, i))
{
break;
}
// If the next item is a flag, it's good
if (IsFlag(parts[i + 1]))
{
break;
}
// If the next item isn't a valid number
else if (!IsValidNumber(parts[3], lowerBound: 0, upperBound: 2))
{
return false;
}
break;
case DICFlags.SeventyFour:
if (parts[0] != DICCommands.Swap)
{
return false;
}
break;
default:
return false;
}
}
}
return true;
}
/// <summary>
/// Returns whether a string is a valid drive letter
/// </summary>
/// <param name="parameter">String value to check</param>
/// <returns>True if it's a valid drive letter, false otherwise</returns>
private static bool IsValidDriveLetter(string parameter)
{
if (!Regex.IsMatch(parameter, @"^[A-Z]:?\\?$"))
{
return false;
}
return true;
}
/// <summary>
/// Returns whether a string is a flag (starts with '/')
/// </summary>
/// <param name="parameter">String value to check</param>
/// <returns>True if it's a flag, false otherwise</returns>
private static bool IsFlag(string parameter)
{
if (parameter.Trim('\"').StartsWith("/"))
{
return true;
}
return false;
}
/// <summary>
/// Returns whether or not the next item exists
/// </summary>
/// <param name="parameters">List of parameters to check against</param>
/// <param name="index">Current index</param>
/// <returns>True if the next item exists, false otherwise</returns>
private static bool DoesNextExist(List<string> parameters, int index)
{
if (index >= parameters.Count - 1)
{
return false;
}
return true;
}
/// <summary>
/// Returns whether a string is a valid number
/// </summary>
/// <param name="parameter">String value to check</param>
/// <param name="lowerBound">Lower bound (>=)</param>
/// <param name="upperBound">Upper bound (<=)</param>
/// <returns>True if it's a valid number, false otherwise</returns>
private static bool IsValidNumber(string parameter, int lowerBound = -1, int upperBound = -1)
{
if (!Int32.TryParse(parameter, out int temp))
{
return false;
}
else if (lowerBound != -1 && temp < lowerBound)
{
return false;
}
else if (upperBound != -1 && temp > upperBound)
{
return false;
}
return true;
}
/// <summary>
/// Determine the base flags to use for checking a commandline
/// </summary>
/// <param name="parameters">Parameters as a string to check</param>
/// <param name="type">Output nullable MediaType containing the found MediaType, if possible</param>
/// <param name="system">Output nullable KnownSystem containing the found KnownSystem, if possible</param>
/// <param name="letter">Output string containing the found drive letter</param>
/// <param name="path">Output string containing the found path</param>
/// <returns>False on error (and all outputs set to null), true otherwise</returns>
public static bool DetermineFlags(string parameters, out MediaType? type, out KnownSystem? system, out string letter, out string path)
{
// Populate all output variables with null
type = null; system = null; letter = null; path = null;
// The string has to be valid by itself first
if (String.IsNullOrWhiteSpace(parameters))
{
return false;
}
// Now split the string into parts for easier validation
// https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes
parameters = parameters.Trim();
List<string> parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+")
.Cast<Match>()
.Select(m => m.Value)
.ToList();
type = Converters.BaseCommmandToMediaType(parts[0]);
system = Converters.BaseCommandToKnownSystem(parts[0]);
// Determine what the commandline should look like given the first item
switch (parts[0])
{
case DICCommands.CompactDisc:
case DICCommands.GDROM:
case DICCommands.Swap:
case DICCommands.Data:
case DICCommands.Audio:
case DICCommands.DigitalVideoDisc:
case DICCommands.BluRay:
case DICCommands.XBOX:
case DICCommands.Floppy:
if (!IsValidDriveLetter(parts[1]))
{
return false;
}
letter = parts[1];
if (IsFlag(parts[2]))
{
return false;
}
path = parts[2].Trim('\"');
// Special case for GameCube/Wii
if (parts.Contains(DICFlags.Raw))
{
type = MediaType.GameCubeGameDisc;
system = KnownSystem.NintendoGameCube;
}
// Special case for PlayStation
else if (parts.Contains(DICFlags.NoFixSubQLibCrypt)
|| parts.Contains(DICFlags.ScanAntiMod))
{
type = MediaType.CD;
system = KnownSystem.SonyPlayStation;
}
// Special case for Saturn
else if (parts.Contains(DICFlags.SeventyFour))
{
type = MediaType.CD;
system = KnownSystem.SegaSaturn;
}
break;
case DICCommands.Stop:
case DICCommands.Start:
case DICCommands.Eject:
case DICCommands.Close:
case DICCommands.Reset:
case DICCommands.DriveSpeed:
if (!IsValidDriveLetter(parts[1]))
{
return false;
}
letter = parts[1];
break;
case DICCommands.Sub:
case DICCommands.MDS:
if (IsFlag(parts[1]))
{
return false;
}
path = parts[1].Trim('\"');
break;
default:
return false;
}
return true;
}
}
}