using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; using DICUI.Utilities; namespace DICUI.Data { public abstract class BaseParameters { /// /// Path to the executable /// public string ExecutablePath { get; set; } /// /// Program that this set of parameters represents /// public InternalProgram InternalProgram { get; set; } /// /// Process to track external program /// private Process process; /// /// Populate a Parameters object from a param string /// /// String possibly representing a set of parameters public BaseParameters(string parameters) { // If any parameters are not valid, wipe out everything if (!ValidateAndSetParameters(parameters)) { ResetValues(); } } /// /// Generate parameters based on a set of known inputs /// /// KnownSystem value to use /// MediaType value to use /// Drive letter to use /// Filename to use /// Drive speed to use /// Enable paranoid mode (safer dumping) /// Enable quiet mode (no beeps) /// User-defined reread count public BaseParameters(KnownSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, bool paranoid, bool quietMode, int retryCount) { SetDefaultParameters(system, type, driveLetter, filename, driveSpeed, paranoid, retryCount); } /// /// Blindly generate a parameter string based on the inputs /// /// Correctly formatted parameter string, null on error public abstract string GenerateParameters(); /// /// Get the input path from the implementation /// /// String representing the path, null on error public abstract string InputPath(); /// /// Get the output path from the implementation /// /// String representing the path, null on error public abstract string OutputPath(); /// /// Get the processing speed from the implementation /// /// int? representing the speed, null on error public abstract int? GetSpeed(); /// /// Set the processing speed int the implementation /// /// int? representing the speed public abstract void SetSpeed(int? speed); /// /// Get the MediaType from the current set of parameters /// /// MediaType value if successful, null on error public abstract MediaType? GetMediaType(); /// /// Gets if the current command is considered a dumping command or not /// /// True if it's a dumping command, false otherwise public abstract bool IsDumpingCommand(); /// /// Returns if the current Parameter object is valid /// /// public bool IsValid() { return GenerateParameters() != null; } /// /// Reset all special variables to have default values /// protected abstract void ResetValues(); /// /// Set default parameters for a given system and media type /// /// KnownSystem value to use /// MediaType value to use /// Drive letter to use /// Filename to use /// Drive speed to use /// Enable paranoid mode (safer dumping) /// User-defined reread count protected abstract void SetDefaultParameters( KnownSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, bool paranoid, int retryCount); /// /// Scan a possible parameter string and populate whatever possible /// /// String possibly representing parameters /// protected abstract bool ValidateAndSetParameters(string parameters); /// /// Validate if all required output files exist /// /// Base filename and path to use for checking /// KnownSystem type representing the media /// MediaType type representing the media /// public abstract bool CheckAllOutputFilesExist(string basePath, KnownSystem? system, MediaType? type); /// /// Generate a SubmissionInfo for the output files /// /// Base submission info to fill in specifics for /// Base filename and path to use for checking /// KnownSystem type representing the media /// MediaType type representing the media /// Drive representing the disc to get information from public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, string basePath, KnownSystem? system, MediaType? type, Drive drive); /// /// Run internal program /// public void ExecuteInternalProgram() { process = new Process() { StartInfo = new ProcessStartInfo() { FileName = ExecutablePath, Arguments = GenerateParameters() ?? "", }, }; process.Start(); process.WaitForExit(); } /// /// Run internal program async with an input set of parameters /// /// /// Standard output from commandline window public async Task ExecuteInternalProgram(BaseParameters parameters) { Process childProcess; string output = await Task.Run(() => { childProcess = new Process() { StartInfo = new ProcessStartInfo() { FileName = parameters.ExecutablePath, Arguments = parameters.GenerateParameters(), CreateNoWindow = true, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, }, }; childProcess.Start(); childProcess.WaitForExit(1000); // Just in case, we want to push a button 5 times to clear any errors for (int i = 0; i < 5; i++) childProcess.StandardInput.WriteLine("Y"); string stdout = childProcess.StandardOutput.ReadToEnd(); childProcess.Dispose(); return stdout; }); return output; } /// /// Cancel an in-progress dumping process /// public void KillInternalProgram() { try { if (process != null && !process.HasExited) process.Kill(); } catch { } } #region Parameter Parsing /// /// Returns whether or not the selected item exists /// /// List of parameters to check against /// Current index /// True if the next item exists, false otherwise protected static bool DoesExist(List parameters, int index) { if (index >= parameters.Count) return false; return true; } /// /// Get the full lines from the input file, if possible /// /// file location /// True if should read as binary, false otherwise (default) /// Full text of the file, null on error protected static string GetFullFile(string filename, bool binary = false) { // If the file doesn't exist, we can't get info from it if (!File.Exists(filename)) return null; // If we're reading as binary if (binary) { string hex = string.Empty; using (BinaryReader br = new BinaryReader(File.OpenRead(filename))) { while (br.BaseStream.Position < br.BaseStream.Length) { hex += Convert.ToString(br.ReadByte(), 16); } } return hex; } return string.Join("\n", File.ReadAllLines(filename)); } /// /// Returns whether a string is a flag (starts with '/') /// /// String value to check /// True if it's a flag, false otherwise protected static bool IsFlag(string parameter) { if (parameter.Trim('\"').StartsWith("/")) return true; return false; } /// /// Returns whether a string is a valid drive letter /// /// String value to check /// True if it's a valid drive letter, false otherwise protected static bool IsValidDriveLetter(string parameter) { if (!Regex.IsMatch(parameter, @"^[A-Z]:?\\?$")) return false; return true; } /// /// Returns whether a string is a valid bool /// /// String value to check /// True if it's a valid bool, false otherwise protected static bool IsValidBool(string parameter) { return bool.TryParse(parameter, out bool temp); } /// /// Returns whether a string is a valid byte /// /// String value to check /// Lower bound (>=) /// Upper bound (<=) /// True if it's a valid byte, false otherwise protected static bool IsValidInt8(string parameter, sbyte lowerBound = -1, sbyte upperBound = -1) { if (!sbyte.TryParse(parameter, out sbyte temp)) return false; else if (lowerBound != -1 && temp < lowerBound) return false; else if (upperBound != -1 && temp > upperBound) return false; return true; } /// /// Returns whether a string is a valid Int16 /// /// String value to check /// Lower bound (>=) /// Upper bound (<=) /// True if it's a valid Int16, false otherwise protected static bool IsValidInt16(string parameter, short lowerBound = -1, short upperBound = -1) { if (!short.TryParse(parameter, out short temp)) return false; else if (lowerBound != -1 && temp < lowerBound) return false; else if (upperBound != -1 && temp > upperBound) return false; return true; } /// /// Returns whether a string is a valid Int32 /// /// String value to check /// Lower bound (>=) /// Upper bound (<=) /// True if it's a valid Int32, false otherwise protected static bool IsValidInt32(string parameter, int lowerBound = -1, int upperBound = -1) { if (!int.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; } /// /// Returns whether a string is a valid Int64 /// /// String value to check /// Lower bound (>=) /// Upper bound (<=) /// True if it's a valid Int64, false otherwise protected static bool IsValidInt64(string parameter, long lowerBound = -1, long upperBound = -1) { if (!long.TryParse(parameter, out long temp)) return false; else if (lowerBound != -1 && temp < lowerBound) return false; else if (upperBound != -1 && temp > upperBound) return false; return true; } #endregion #region Common Information Extraction /// /// Get the split values for ISO-based media /// /// String representing the combined hash data /// True if extraction was successful, false otherwise protected static bool GetISOHashValues(string hashData, out long size, out string crc32, out string md5, out string sha1) { size = -1; crc32 = null; md5 = null; sha1 = null; if (string.IsNullOrWhiteSpace(hashData)) return false; Regex hashreg = new Regex(@" /// Get the existance of an anti-modchip string from a PlayStation disc, if possible /// /// Drive letter to use to check /// Anti-modchip existance if possible, false on error protected static bool GetPlayStationAntiModchipDetected(char? driveLetter) { // If there's no drive letter, we can't do this part if (driveLetter == null) return false; // If the folder no longer exists, we can't do this part string drivePath = driveLetter + ":\\"; if (!Directory.Exists(drivePath)) return false; string enAntiModString = " SOFTWARE TERMINATED\nCONSOLE MAY HAVE BEEN MODIFIED\n CALL 1-888-780-7690"; string jpAntiModString = "強制終了しました。\n本体が改造されている\nおそれがあります。"; // Scan through each file to check for the anti-modchip strings foreach (string path in Directory.EnumerateFiles(drivePath, "*", SearchOption.AllDirectories)) { try { // TODO: This is a memory hog string fileContents = File.ReadAllText(path); if (fileContents.Contains(enAntiModString) || fileContents.Contains(jpAntiModString)) return true; } catch { // No-op, we don't care what the error was } } return false; } /// /// Get the EXE date from a PlayStation disc, if possible /// /// Drive letter to use to check /// Output region, if possible /// Output EXE date in "yyyy-mm-dd" format if possible, null on error /// protected static bool GetPlayStationExecutableInfo(char? driveLetter, out Region? region, out string date) { region = null; date = null; // If there's no drive letter, we can't do this part if (driveLetter == null) return false; // If the folder no longer exists, we can't do this part string drivePath = driveLetter + ":\\"; if (!Directory.Exists(drivePath)) return false; // Get the two paths that we will need to check string psxExePath = Path.Combine(drivePath, "PSX.EXE"); string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF"); // Try both of the common paths that contain information string exeName = null; // Read the CNF file as an INI file var systemCnf = new IniFile(systemCnfPath); string bootValue = string.Empty; // PlayStation uses "BOOT" as the key if (systemCnf.ContainsKey("BOOT")) bootValue = systemCnf["BOOT"]; // PlayStation 2 uses "BOOT2" as the key if (systemCnf.ContainsKey("BOOT2")) bootValue = systemCnf["BOOT2"]; // If we had any boot value, parse it and get the executable name if (!string.IsNullOrEmpty(bootValue)) { var match = Regex.Match(bootValue, @"cdrom.?:\\?(.*)"); if (match != null && match.Groups.Count > 1) { exeName = match.Groups[1].Value; exeName = exeName.Split(';')[0]; } } // If the SYSTEM.CNF value can't be found, try PSX.EXE if (string.IsNullOrWhiteSpace(exeName) && File.Exists(psxExePath)) exeName = "PSX.EXE"; // If neither can be found, we return false if (string.IsNullOrWhiteSpace(exeName)) return false; // Get the region, if possible region = GetPlayStationRegion(exeName); // Now that we have the EXE name, try to get the fileinfo for it string exePath = Path.Combine(drivePath, exeName); if (!File.Exists(exePath)) return false; // Fix the Y2K timestamp issue FileInfo fi = new FileInfo(exePath); DateTime dt = new DateTime(fi.LastWriteTimeUtc.Year >= 1900 && fi.LastWriteTimeUtc.Year < 1920 ? 2000 + fi.LastWriteTimeUtc.Year % 100 : fi.LastWriteTimeUtc.Year, fi.LastWriteTimeUtc.Month, fi.LastWriteTimeUtc.Day); date = dt.ToString("yyyy-MM-dd"); return true; } /// /// Get the version from a PlayStation 2 disc, if possible /// /// Drive letter to use to check /// Game version if possible, null on error protected static string GetPlayStation2Version(char? driveLetter) { // If there's no drive letter, we can't do this part if (driveLetter == null) return null; // If the folder no longer exists, we can't do this part string drivePath = driveLetter + ":\\"; if (!Directory.Exists(drivePath)) return null; // Get the SYSTEM.CNF path to check string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF"); // Try to parse the SYSTEM.CNF file var systemCnf = new IniFile(systemCnfPath); if (systemCnf.ContainsKey("VER")) return systemCnf["VER"]; // If "VER" can't be found, we can't do much return null; } /// /// Get the version from a PlayStation 4 disc, if possible /// /// Drive letter to use to check /// Game version if possible, null on error protected static string GetPlayStation4Version(char? driveLetter) { // If there's no drive letter, we can't do this part if (driveLetter == null) return null; // If the folder no longer exists, we can't do this part string drivePath = driveLetter + ":\\"; if (!Directory.Exists(drivePath)) return null; // If we can't find param.sfo, we don't have a PlayStation 4 disc string paramSfoPath = Path.Combine(drivePath, "bd", "param.sfo"); if (!File.Exists(paramSfoPath)) return null; // Let's try reading param.sfo to find the version at the end of the file try { using (BinaryReader br = new BinaryReader(File.OpenRead(paramSfoPath))) { br.BaseStream.Seek(-0x08, SeekOrigin.End); return new string(br.ReadChars(5)); } } catch { // We don't care what the error was return null; } } #endregion #region Category Extraction /// /// Determine the category based on the UMDImageCreator string /// /// String representing the category /// Category, if possible protected static Category? GetUMDCategory(string category) { switch (category) { case "GAME": return Category.Games; case "VIDEO": return Category.Video; case "AUDIO": return Category.Audio; default: return null; } } #endregion #region Region Extraction /// /// Determine the region based on the PlayStation serial code /// /// PlayStation serial code /// Region mapped from name, if possible protected static Region? GetPlayStationRegion(string serial) { // Standardized "S" serials if (serial.StartsWith("S")) { // string publisher = serial[0] + serial[1]; // char secondRegion = serial[3]; switch (serial[2]) { case 'A': return Region.Asia; case 'C': return Region.China; case 'E': return Region.Europe; case 'J': return Region.JapanKorea; case 'K': return Region.Korea; case 'P': return Region.Japan; case 'U': return Region.USA; } } // Japan-only special serial else if (serial.StartsWith("PAPX")) return Region.Japan; // Region appears entirely random else if (serial.StartsWith("PABX")) return null; // Japan-only special serial else if (serial.StartsWith("PCBX")) return Region.Japan; // Single disc known, Japan else if (serial.StartsWith("PDBX")) return Region.Japan; // Single disc known, Europe else if (serial.StartsWith("PEBX")) return Region.Europe; return null; } /// /// Determine the region based on the XGD serial character /// /// Character denoting the region /// Region, if possible protected static Region? GetXgdRegion(char region) { switch (region) { case 'W': return Region.World; case 'A': return Region.USA; case 'J': return Region.JapanAsia; case 'E': return Region.Europe; case 'K': return Region.USAJapan; case 'L': return Region.USAEurope; case 'H': return Region.JapanEurope; default: return null; } } #endregion } }