diff --git a/CHANGELIST.md b/CHANGELIST.md index 39312659..2b1f40e2 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -9,6 +9,7 @@ - Update RedumpLib and related - Update BinaryObjectScanner to 3.1.11 - Remove now-unused Hash enum +- Use IO implementation of IniFile ### 3.1.8 (2024-05-09) diff --git a/MPF.Core/Data/Drive.cs b/MPF.Core/Data/Drive.cs index 50d562ab..10ddb20c 100644 --- a/MPF.Core/Data/Drive.cs +++ b/MPF.Core/Data/Drive.cs @@ -7,6 +7,7 @@ using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Generic; #endif using MPF.Core.Converters; +using SabreTools.IO; using SabreTools.RedumpLib.Data; namespace MPF.Core.Data diff --git a/MPF.Core/Data/IniFile.cs b/MPF.Core/Data/IniFile.cs deleted file mode 100644 index 8735cc70..00000000 --- a/MPF.Core/Data/IniFile.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace MPF.Core.Data -{ - public class IniFile : IDictionary - { - private Dictionary _keyValuePairs = []; - - public string this[string key] - { - get - { - _keyValuePairs ??= []; - - key = key.ToLowerInvariant(); - if (_keyValuePairs.TryGetValue(key, out string? val)) - return val; - - return string.Empty; - } - set - { - _keyValuePairs ??= []; - - key = key.ToLowerInvariant(); - _keyValuePairs[key] = value; - } - } - - /// - /// Create an empty INI file - /// - public IniFile() - { - } - - /// - /// 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); - } - - /// - /// 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 var sr = new StreamReader(stream); - string section = string.Empty; - while (!sr.EndOfStream) - { - var line = sr.ReadLine()?.Trim(); - - // Empty lines are skipped - if (string.IsNullOrEmpty(line)) - { - // No-op, we don't process empty lines - } - - // Comments start with ';' - else 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).ToArray()).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 - { - // Order the dictionary by keys to link sections together - using var sw = new StreamWriter(stream); - 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).ToArray()).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; - } - - #region IDictionary Impelementations - - public ICollection Keys => ((IDictionary)_keyValuePairs).Keys; - - public ICollection Values => ((IDictionary)_keyValuePairs).Values; - - public int Count => ((ICollection>)_keyValuePairs).Count; - - public bool IsReadOnly => ((ICollection>)_keyValuePairs).IsReadOnly; - - public void Add(string key, string value) - { - ((IDictionary)_keyValuePairs).Add(key.ToLowerInvariant(), value); - } - - bool IDictionary.Remove(string key) - { - return ((IDictionary)_keyValuePairs).Remove(key.ToLowerInvariant()); - } - - public bool TryGetValue(string key, out string value) - { - bool result = ((IDictionary)_keyValuePairs).TryGetValue(key.ToLowerInvariant(), out var temp); - value = temp ?? string.Empty; - return result; - } - - public void Add(KeyValuePair item) - { - var newItem = new KeyValuePair(item.Key.ToLowerInvariant(), item.Value); - ((ICollection>)_keyValuePairs).Add(newItem); - } - - public void Clear() - { - ((ICollection>)_keyValuePairs).Clear(); - } - - public bool Contains(KeyValuePair item) - { - var newItem = new KeyValuePair(item.Key.ToLowerInvariant(), item.Value); - return ((ICollection>)_keyValuePairs).Contains(newItem); - } - - public bool ContainsKey(string key) - { - return _keyValuePairs.ContainsKey(key.ToLowerInvariant()); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((ICollection>)_keyValuePairs).CopyTo(array, arrayIndex); - } - - public bool Remove(KeyValuePair item) - { - var newItem = new KeyValuePair(item.Key.ToLowerInvariant(), item.Value); - return ((ICollection>)_keyValuePairs).Remove(newItem); - } - - public IEnumerator> GetEnumerator() - { - return ((IEnumerable>)_keyValuePairs).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_keyValuePairs).GetEnumerator(); - } - - #endregion - } -} diff --git a/MPF.Core/InfoTool.cs b/MPF.Core/InfoTool.cs index 35a5adc4..7c5b7a90 100644 --- a/MPF.Core/InfoTool.cs +++ b/MPF.Core/InfoTool.cs @@ -14,6 +14,7 @@ using MPF.Core.Modules; using MPF.Core.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using SabreTools.IO; using SabreTools.Models.PIC; using SabreTools.RedumpLib.Data; using Formatting = Newtonsoft.Json.Formatting; @@ -384,6 +385,11 @@ namespace MPF.Core return di.Units[0]?.Body?.DiscTypeIdentifier; } + /// + /// Get the EXE name from a PlayStation disc, if possible + /// + /// Drive letter to use to check + /// Executable name on success, null otherwise internal static string? GetPlayStationExecutableName(char? driveLetter) { // If there's no drive letter, we can't get exe name @@ -395,6 +401,11 @@ namespace MPF.Core return GetPlayStationExecutableName(drivePath); } + /// + /// Get the EXE name from a PlayStation disc, if possible + /// + /// Drive path to use to check + /// Executable name on success, null otherwise internal static string? GetPlayStationExecutableName(string? drivePath) { // If there's no drive path, we can't get exe name @@ -411,7 +422,7 @@ namespace MPF.Core // Read the CNF file as an INI file var systemCnf = new IniFile(systemCnfPath); - string bootValue = string.Empty; + string? bootValue = string.Empty; // PlayStation uses "BOOT" as the key if (systemCnf.ContainsKey("BOOT")) @@ -451,7 +462,7 @@ namespace MPF.Core /// Internal disc serial, if possible /// Output region, if possible /// Output EXE date in "yyyy-mm-dd" format if possible, null on error - /// + /// True if information could be determined, false otherwise internal static bool GetPlayStationExecutableInfo(char? driveLetter, out string? serial, out Region? region, out string? date) { serial = null; region = null; date = null; @@ -472,7 +483,7 @@ namespace MPF.Core /// Internal disc serial, if possible /// Output region, if possible /// Output EXE date in "yyyy-mm-dd" format if possible, null on error - /// + /// True if information could be determined, false otherwise internal static bool GetPlayStationExecutableInfo(string? drivePath, out string? serial, out Region? region, out string? date) { serial = null; region = null; date = null; @@ -1003,7 +1014,7 @@ namespace MPF.Core } } -#endregion + #endregion #region Category Extraction