From bd3484cb3d0ff332789bbaea0e982a970b6f4a61 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 5 Jun 2020 14:15:06 -0700 Subject: [PATCH] Better common methods --- DICUI.Library/Aaru/Parameters.cs | 31 ++ DICUI.Library/Data/BaseParameters.cs | 258 ++++++++++++++ DICUI.Library/DiscImageCreator/Parameters.cs | 332 +++---------------- DICUI.Library/Utilities/IniFile.cs | 234 +++++++++++++ DICUI.Library/Utilities/IniParse.cs | 91 ----- DICUI.Library/Utilities/Validators.cs | 25 +- 6 files changed, 580 insertions(+), 391 deletions(-) create mode 100644 DICUI.Library/Utilities/IniFile.cs delete mode 100644 DICUI.Library/Utilities/IniParse.cs diff --git a/DICUI.Library/Aaru/Parameters.cs b/DICUI.Library/Aaru/Parameters.cs index 7a9d3129..f79148bf 100644 --- a/DICUI.Library/Aaru/Parameters.cs +++ b/DICUI.Library/Aaru/Parameters.cs @@ -1396,7 +1396,38 @@ namespace DICUI.Aaru switch (system) { + case KnownSystem.KonamiPython2: + if (GetPlaystationExecutableInfo(drive?.Letter, out Region? pythonTwoRegion, out string pythonTwoDate)) + { + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion; + info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate; + } + info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? ""; + break; + + case KnownSystem.SonyPlayStation: + if (GetPlaystationExecutableInfo(drive?.Letter, out Region? playstationRegion, out string playstationDate)) + { + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion; + info.CommonDiscInfo.EXEDateBuildDate = playstationDate; + } + + break; + + case KnownSystem.SonyPlayStation2: + if (GetPlaystationExecutableInfo(drive?.Letter, out Region? playstationTwoRegion, out string playstationTwoDate)) + { + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion; + info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate; + } + + info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? ""; + break; + + case KnownSystem.SonyPlayStation4: + info.VersionAndEditions.Version = GetPlayStation4Version(drive?.Letter) ?? ""; + break; } } diff --git a/DICUI.Library/Data/BaseParameters.cs b/DICUI.Library/Data/BaseParameters.cs index 4c5178e5..5cda0967 100644 --- a/DICUI.Library/Data/BaseParameters.cs +++ b/DICUI.Library/Data/BaseParameters.cs @@ -224,6 +224,8 @@ namespace DICUI.Data { } } + #region Parameter Parsing + /// /// Returns whether or not the selected item exists /// @@ -378,5 +380,261 @@ namespace DICUI.Data return true; } + + #endregion + + #region Common Information Extraction + + /// + /// 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 } } \ No newline at end of file diff --git a/DICUI.Library/DiscImageCreator/Parameters.cs b/DICUI.Library/DiscImageCreator/Parameters.cs index 27e0a733..ed03e1aa 100644 --- a/DICUI.Library/DiscImageCreator/Parameters.cs +++ b/DICUI.Library/DiscImageCreator/Parameters.cs @@ -1653,7 +1653,7 @@ namespace DICUI.DiscImageCreator break; case KnownSystem.MicrosoftXBOX: - if (GetXBOXAuxInfo(basePath + "_disc.txt", out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver)) + if (GetXgdAuxInfo(basePath + "_disc.txt", out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver)) { info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {dmihash ?? ""}\n" + $"{Template.XBOXPFIHash}: {pfihash ?? ""}\n" + @@ -1662,7 +1662,7 @@ namespace DICUI.DiscImageCreator info.Extras.SecuritySectorRanges = ss ?? ""; } - if (GetXBOXDMIInfo(Path.Combine(outputDirectory, "DMI.bin"), out string serial, out string version, out Region? region)) + if (GetXboxDMIInfo(Path.Combine(outputDirectory, "DMI.bin"), out string serial, out string version, out Region? region)) { info.CommonDiscInfo.Serial = serial ?? ""; info.VersionAndEditions.Version = version ?? ""; @@ -1672,7 +1672,7 @@ namespace DICUI.DiscImageCreator break; case KnownSystem.MicrosoftXBOX360: - if (GetXBOXAuxInfo(basePath + "_disc.txt", out string dmi360hash, out string pfi360hash, out string ss360hash, out string ss360, out string ssver360)) + if (GetXgdAuxInfo(basePath + "_disc.txt", out string dmi360hash, out string pfi360hash, out string ss360hash, out string ss360, out string ssver360)) { info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {dmi360hash ?? ""}\n" + $"{Template.XBOXPFIHash}: {pfi360hash ?? ""}\n" + @@ -1681,7 +1681,7 @@ namespace DICUI.DiscImageCreator info.Extras.SecuritySectorRanges = ss360 ?? ""; } - if (GetXBOX360DMIInfo(Path.Combine(outputDirectory, "DMI.bin"), out string serial360, out string version360, out Region? region360)) + if (GetXbox360DMIInfo(Path.Combine(outputDirectory, "DMI.bin"), out string serial360, out string version360, out Region? region360)) { info.CommonDiscInfo.Serial = serial360 ?? ""; info.VersionAndEditions.Version = version360 ?? ""; @@ -1970,7 +1970,7 @@ namespace DICUI.DiscImageCreator private void SetBaseCommand(KnownSystem? system, MediaType? type) { // If we have an invalid combination, we should BaseCommand = null - if (!Utilities.Validators.GetValidMediaTypes(system).Contains(type)) + if (!Validators.GetValidMediaTypes(system).Contains(type)) { BaseCommand = Command.NONE; return; @@ -2410,195 +2410,6 @@ namespace DICUI.DiscImageCreator } } - /// - /// 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 - /// - private 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 keyValuePairs = IniParse.ParseIniFile(systemCnfPath); - string bootValue = string.Empty; - - // PlayStation uses "BOOT" as the key - if (keyValuePairs.ContainsKey("boot")) - bootValue = keyValuePairs["boot"]; - - // PlayStation 2 uses "BOOT2" as the key - if (keyValuePairs.ContainsKey("boot2")) - bootValue = keyValuePairs["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; - - // Standardized "S" serials - if (exeName.StartsWith("S")) - { - // string publisher = exeName[0] + exeName[1]; - // char secondRegion = exeName[3]; - switch (exeName[2]) - { - case 'A': - region = Region.Asia; - break; - case 'C': - region = Region.China; - break; - case 'E': - region = Region.Europe; - break; - case 'J': - region = Region.JapanKorea; - break; - case 'K': - region = Region.Korea; - break; - case 'P': - region = Region.Japan; - break; - case 'U': - region = Region.USA; - break; - } - } - - // Special cases - else if (exeName.StartsWith("PAPX")) - { - region = Region.Japan; - } - else if (exeName.StartsWith("PABX")) - { - // Region appears entirely random - } - else if (exeName.StartsWith("PCBX")) - { - region = Region.Japan; - } - else if (exeName.StartsWith("PDBX")) - { - // Single disc known, Japan - } - else if (exeName.StartsWith("PEBX")) - { - // Single disc known, Europe - } - - // 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 - private 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 keyValuePairs = IniParse.ParseIniFile(systemCnfPath); - if (keyValuePairs.ContainsKey("ver")) - return keyValuePairs["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 - private 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; - } - } - /// /// Get the PVD from the input file, if possible /// @@ -2835,26 +2646,6 @@ namespace DICUI.DiscImageCreator } } - /// - /// Determine the category based on the UMDImageCreator string - /// - /// String representing the category - /// Category, if possible - private 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; - } - } - /// /// Get the write offset from the input file, if possible /// @@ -2864,9 +2655,7 @@ namespace DICUI.DiscImageCreator { // If the file doesn't exist, we can't get info from it if (!File.Exists(disc)) - { return null; - } using (StreamReader sr = File.OpenText(disc)) { @@ -2890,11 +2679,11 @@ namespace DICUI.DiscImageCreator } /// - /// Get the XBOX/360 auxiliary info from the outputted files, if possible + /// Get the XGD auxiliary info from the outputted files, if possible /// /// _disc.txt file location /// True on successful extraction of info, false otherwise - private static bool GetXBOXAuxInfo(string disc, out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver) + private static bool GetXgdAuxInfo(string disc, out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver) { dmihash = null; pfihash = null; sshash = null; ss = null; ssver = null; @@ -2906,46 +2695,45 @@ namespace DICUI.DiscImageCreator { try { - // Fast forward to the Security Sector version and read it - while (!sr.ReadLine().Trim().StartsWith("CPR_MAI Key")) ; - ssver = sr.ReadLine().Trim().Split(' ')[4]; // "Version of challenge table: " - - // Fast forward to the Security Sector Ranges - while (!sr.ReadLine().Trim().StartsWith("Number of security sector ranges:")) ; - - // Now that we're at the ranges, read each line in and concatenate - Regex layerRegex = new Regex(@"Layer [01].*, startLBA-endLBA:\s*(\d+)-\s*(\d+)"); - string line = sr.ReadLine().Trim(); - while (!line.StartsWith("========== Unlock 2 state(wxripper) ==========")) + while(!sr.EndOfStream) { - // If we have a recognized line format, parse it - if (line.StartsWith("Layer ")) + string line = sr.ReadLine().Trim(); + + // Security Sector version + if (line.StartsWith("Version of challenge table")) { - var match = layerRegex.Match(line); - ss += $"{match.Groups[1]}-{match.Groups[2]}\n"; + ssver = line.Split(' ')[4]; // "Version of challenge table: " } - line = sr.ReadLine().Trim(); - } + // Security Sector ranges + else if (line.StartsWith("Number of security sector ranges:")) + { + Regex layerRegex = new Regex(@"Layer [01].*, startLBA-endLBA:\s*(\d+)-\s*(\d+)"); - // Fast forward to the aux hashes - while (!line.StartsWith(" - /// Get the XOX serial info from the DMI.bin file, if possible + /// Get the Xbox serial info from the DMI.bin file, if possible /// /// DMI.bin file location /// True on successful extraction of info, false otherwise - private static bool GetXBOXDMIInfo(string dmi, out string serial, out string version, out Region? region) + private static bool GetXboxDMIInfo(string dmi, out string serial, out string version, out Region? region) { serial = null; version = null; region = Region.World; @@ -2979,7 +2767,7 @@ namespace DICUI.DiscImageCreator serial = $"{str[0]}{str[1]}-{str[2]}{str[3]}{str[4]}"; version = $"1.{str[5]}{str[6]}"; - region = GetXBOXRegion(str[7]); + region = GetXgdRegion(str[7]); return true; } catch @@ -2990,13 +2778,13 @@ namespace DICUI.DiscImageCreator } /// - /// Get the XBOX 360 serial info from the DMI.bin file, if possible + /// Get the Xbox 360 serial info from the DMI.bin file, if possible /// /// DMI.bin file location /// True on successful extraction of info, false otherwise - private static bool GetXBOX360DMIInfo(string dmi, out string serial, out string version, out Region? region) + private static bool GetXbox360DMIInfo(string dmi, out string serial, out string version, out Region? region) { - serial = null; version = null; region = null; + serial = null; version = null; region = Region.World; if (!File.Exists(dmi)) return false; @@ -3010,7 +2798,7 @@ namespace DICUI.DiscImageCreator serial = $"{str[0]}{str[1]}-{str[2]}{str[3]}{str[4]}{str[5]}"; version = $"1.{str[6]}{str[7]}"; - region = GetXBOXRegion(str[8]); + region = GetXgdRegion(str[8]); // str[9], str[10], str[11] - unknown purpose // str[12], str[13] - disc <12> of <13> return true; @@ -3022,34 +2810,6 @@ namespace DICUI.DiscImageCreator } } - /// - /// Determine the region based on the XBOX serial character - /// - /// Character denoting the region - /// Region, if possible - private static Region? GetXBOXRegion(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 } } diff --git a/DICUI.Library/Utilities/IniFile.cs b/DICUI.Library/Utilities/IniFile.cs new file mode 100644 index 00000000..ac2f4120 --- /dev/null +++ b/DICUI.Library/Utilities/IniFile.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace DICUI.Utilities +{ + public class IniFile + { + private Dictionary _keyValuePairs; + + public string this[string key] + { + get + { + if (_keyValuePairs == null) + _keyValuePairs = new Dictionary(); + + key = key.ToLowerInvariant(); + if (_keyValuePairs.ContainsKey(key)) + return _keyValuePairs[key]; + + return null; + } + set + { + if (_keyValuePairs == null) + _keyValuePairs = new Dictionary(); + + key = key.ToLowerInvariant(); + _keyValuePairs[key] = value; + } + } + + /// + /// Create an empty INI file + /// + public IniFile() + { + _keyValuePairs = new Dictionary(); + } + + /// + /// Populate an INI file from path + /// + public IniFile(string path) + { + this.Parse(path); + } + + /// + /// Populate an INI file from stream + /// + public IniFile(Stream stream) + { + this.Parse(stream); + } + + /// + /// Check if the key exists in the INI file + /// + public bool ContainsKey(string key) + { + return _keyValuePairs.ContainsKey(key.ToLowerInvariant()); + } + + /// + /// Add or update a key and value to the INI file + /// + public void AddOrUpdate(string key, string value) + { + _keyValuePairs[key.ToLowerInvariant()] = value; + } + + /// + /// Remove a key from the INI file + /// + public void Remove(string key) + { + _keyValuePairs.Remove(key.ToLowerInvariant()); + } + + /// + /// Read an INI file based on the path + /// + public bool Parse(string path) + { + // If we don't have a file, we can't read it + if (!File.Exists(path)) + return false; + + using (var fileStream = File.OpenRead(path)) + { + return Parse(fileStream); + } + } + + /// + /// Read an INI file from a stream + /// + public bool Parse(Stream stream) + { + // If the stream is invalid or unreadable, we can't process it + if (stream == null || !stream.CanRead || stream.Position >= stream.Length - 1) + return false; + + // Keys are case-insensitive by default + try + { + using (StreamReader sr = new StreamReader(stream)) + { + string section = string.Empty; + while (!sr.EndOfStream) + { + string line = sr.ReadLine().Trim(); + + // Comments start with ';' + if (line.StartsWith(";")) + { + // No-op, we don't process comments + } + + // Section titles are surrounded by square brackets + else if (line.StartsWith("[")) + { + section = line.TrimStart('[').TrimEnd(']'); + } + + // Valid INI lines are in the format key=value + else if (line.Contains("=")) + { + // Split the line by '=' for key-value pairs + string[] data = line.Split('='); + + // If the value field contains an '=', we need to put them back in + string key = data[0].Trim(); + string value = string.Join("=", data.Skip(1)).Trim(); + + // Section names are prepended to the key with a '.' separating + if (!string.IsNullOrEmpty(section)) + key = $"{section}.{key}"; + + // Set or overwrite keys in the returned dictionary + _keyValuePairs[key.ToLowerInvariant()] = value; + } + + // All other lines are ignored + } + } + } + catch + { + // We don't care what the error was, just catch and return + return false; + } + + return true; + } + + /// + /// Write an INI file to a path + /// + public bool Write(string path) + { + // If we don't have a valid dictionary with values, we can't write out + if (_keyValuePairs == null || _keyValuePairs.Count == 0) + return false; + + using (var fileStream = File.OpenWrite(path)) + { + return Write(fileStream); + } + } + + /// + /// Write an INI file to a stream + /// + public bool Write(Stream stream) + { + // If we don't have a valid dictionary with values, we can't write out + if (_keyValuePairs == null || _keyValuePairs.Count == 0) + return false; + + // If the stream is invalid or unwritable, we can't output to it + if (stream == null || !stream.CanWrite || stream.Position >= stream.Length - 1) + return false; + + try + { + using (StreamWriter sw = new StreamWriter(stream)) + { + // Order the dictionary by keys to link sections together + var orderedKeyValuePairs = _keyValuePairs.OrderBy(kvp => kvp.Key); + + string section = string.Empty; + foreach (var keyValuePair in orderedKeyValuePairs) + { + // Extract the key and value + string key = keyValuePair.Key; + string value = keyValuePair.Value; + + // We assume '.' is a section name separator + if (key.Contains('.')) + { + // Split the key by '.' + string[] data = keyValuePair.Key.Split('.'); + + // If the key contains an '.', we need to put them back in + string newSection = data[0].Trim(); + key = string.Join(".", data.Skip(1)).Trim(); + + // If we have a new section, write it out + if (!string.Equals(newSection, section, StringComparison.OrdinalIgnoreCase)) + { + sw.WriteLine($"[{newSection}]"); + section = newSection; + } + } + + // Now write out the key and value in a standardized way + sw.WriteLine($"{key}={value}"); + } + } + } + catch + { + // We don't care what the error was, just catch and return + return false; + } + + return true; + } + } +} diff --git a/DICUI.Library/Utilities/IniParse.cs b/DICUI.Library/Utilities/IniParse.cs deleted file mode 100644 index 66c69ce5..00000000 --- a/DICUI.Library/Utilities/IniParse.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace DICUI.Utilities -{ - public static class IniParse - { - /// - /// Read an INI file based on the path - /// - /// - /// - public static Dictionary ParseIniFile(string path) - { - // If we don't have a file, we can't read it - if (!File.Exists(path)) - return new Dictionary(); - - using (var fileStream = File.OpenRead(path)) - { - return ParseIniFile(fileStream); - } - } - - /// - /// Read an INI file from a stream - /// - /// - /// - public static Dictionary ParseIniFile(Stream stream) - { - // If the stream is invalid or unreadable, we can't process it - if (stream == null || !stream.CanRead || stream.Position >= stream.Length - 1) - return new Dictionary(); - - // Keys are case-insensitive by default - var keyValuePairs = new Dictionary(); - try - { - using (StreamReader sr = new StreamReader(stream)) - { - string section = string.Empty; - while (!sr.EndOfStream) - { - string line = sr.ReadLine().Trim(); - - // Comments start with ';' - if (line.StartsWith(";")) - { - // No-op, we don't process comments - } - - // Section titles are surrounded by square brackets - else if (line.StartsWith("[")) - { - section = line.TrimStart('[').TrimEnd(']'); - } - - // Valid INI lines are in the format key=value - else if (line.Contains("=")) - { - // Split the line by '=' for key-value pairs - string[] data = line.Split('='); - - // If the value field contains an '=', we need to put them back in - string key = data[0].Trim(); - string value = string.Join("=", data.Skip(1)).Trim(); - - // Section names are prepended to the key with a '.' separating - if (!string.IsNullOrEmpty(section)) - key = $"{section}.{key}"; - - // Set or overwrite keys in the returned dictionary - keyValuePairs[key.ToLowerInvariant()] = value; - } - - // All other lines are ignored - } - } - } - catch - { - // We don't care what the error was, just catch and return - return new Dictionary(); - } - - return keyValuePairs; - } - } -} diff --git a/DICUI.Library/Utilities/Validators.cs b/DICUI.Library/Utilities/Validators.cs index 3c728e87..7f98dd3d 100644 --- a/DICUI.Library/Utilities/Validators.cs +++ b/DICUI.Library/Utilities/Validators.cs @@ -918,22 +918,19 @@ namespace DICUI.Utilities } // Sony PlayStation and Sony PlayStation 2 - if (File.Exists(Path.Combine(drivePath, "SYSTEM.CNF"))) + string psxExePath = Path.Combine(drivePath, "PSX.EXE"); + string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF"); + if (File.Exists(systemCnfPath)) { // Check for either BOOT or BOOT2 - using (StreamReader reader = File.OpenText(Path.Combine(drivePath, "SYSTEM.CNF"))) - { - while (!reader.EndOfStream) - { - string line = reader.ReadLine(); - if (line.Contains("BOOT2")) - return KnownSystem.SonyPlayStation2; - else if (line.Contains("BOOT")) - return KnownSystem.SonyPlayStation; - } - } - - // If we have a weird disc, just assume PS1 + var systemCnf = new IniFile(systemCnfPath); + if (systemCnf.ContainsKey("BOOT")) + return KnownSystem.SonyPlayStation; + else if (systemCnf.ContainsKey("BOOT2")) + return KnownSystem.SonyPlayStation2; + } + else if (File.Exists(psxExePath)) + { return KnownSystem.SonyPlayStation; }