diff --git a/CHANGELIST.md b/CHANGELIST.md index 183ba2ee..62fc5009 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -4,6 +4,7 @@ - Add Check flags for protection scan extras - Add comments around default options object - Fix usings ordering in ItemHelper +- Add physical drive extensions to new tool ### 3.2.1 (2024-08-05) diff --git a/MPF.Frontend/Drive.cs b/MPF.Frontend/Drive.cs index ff2dbe8b..d7747e7c 100644 --- a/MPF.Frontend/Drive.cs +++ b/MPF.Frontend/Drive.cs @@ -1,17 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Text.RegularExpressions; #if NET462_OR_GREATER || NETCOREAPP using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Generic; #endif -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using MPF.Processors; -using SabreTools.IO; using SabreTools.RedumpLib.Data; namespace MPF.Frontend @@ -236,463 +229,6 @@ namespace MPF.Frontend #endregion - #region Information Extraction - - /// - /// Get the EXE name from a PlayStation disc, if possible - /// - /// Executable name on success, null otherwise - public string? GetPlayStationExecutableName() - { - // If there's no drive path, we can't get exe name - if (string.IsNullOrEmpty(Name)) - return null; - - // If the folder no longer exists, we can't get exe name - if (!Directory.Exists(Name)) - return null; - - // Get the two paths that we will need to check - string psxExePath = Path.Combine(Name, "PSX.EXE"); - string systemCnfPath = Path.Combine(Name, "SYSTEM.CNF"); - - // 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.?:\\?(.*)", RegexOptions.Compiled); - if (match.Groups.Count > 1) - { - string? serial = match.Groups[1].Value; - - // Some games may have the EXE in a subfolder - serial = Path.GetFileName(serial); - - return serial; - } - } - - // If the SYSTEM.CNF value can't be found, try PSX.EXE - if (File.Exists(psxExePath)) - return "PSX.EXE"; - - // If neither can be found, we return null - return null; - } - - /// - /// Get the EXE date from a PlayStation disc, if possible - /// - /// 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 - public bool GetPlayStationExecutableInfo(out string? serial, out Region? region, out string? date) - { - serial = null; region = null; date = null; - - // If there's no drive path, we can't do this part - if (string.IsNullOrEmpty(Name)) - return false; - - // If the folder no longer exists, we can't do this part - if (!Directory.Exists(Name)) - return false; - - // Get the executable name - string? exeName = GetPlayStationExecutableName(); - - // If no executable found, we can't do this part - if (exeName == null) - return false; - - // EXE name may have a trailing `;` after - // EXE name should always be in all caps - exeName = exeName - .Split(';')[0] - .ToUpperInvariant(); - - // Serial is most of the EXE name normalized - serial = exeName - .Replace('_', '-') - .Replace(".", string.Empty); - - // Get the region, if possible - region = ProcessingTool.GetPlayStationRegion(exeName); - - // Now that we have the EXE name, try to get the fileinfo for it - string exePath = Path.Combine(Name, exeName); - if (!File.Exists(exePath)) - return false; - - // Fix the Y2K timestamp issue - var fi = new FileInfo(exePath); - var 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 - /// - /// Game version if possible, null on error - public string? GetPlayStation2Version() - { - // If there's no drive path, we can't do this part - if (string.IsNullOrEmpty(Name)) - return null; - - // If the folder no longer exists, we can't do this part - if (!Directory.Exists(Name)) - return null; - - // Get the SYSTEM.CNF path to check - string systemCnfPath = Path.Combine(Name, "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 internal serial from a PlayStation 3 disc, if possible - /// - /// Internal disc serial if possible, null on error - public string? GetPlayStation3Serial() - { - // If there's no drive path, we can't do this part - if (string.IsNullOrEmpty(Name)) - return null; - - // If the folder no longer exists, we can't do this part - if (!Directory.Exists(Name)) - return null; - - // Attempt to use PS3_DISC.SFB - string sfbPath = Path.Combine(Name, "PS3_DISC.SFB"); - if (File.Exists(sfbPath)) - { - try - { - using var br = new BinaryReader(File.OpenRead(sfbPath)); - br.BaseStream.Seek(0x220, SeekOrigin.Begin); - return new string(br.ReadChars(0x10)).TrimEnd('\0'); - } - catch - { - // We don't care what the error was - return null; - } - } - - // Attempt to use PARAM.SFO -#if NET20 || NET35 - string sfoPath = Path.Combine(Path.Combine(Name, "PS3_GAME"), "PARAM.SFO"); -#else - string sfoPath = Path.Combine(Name, "PS3_GAME", "PARAM.SFO"); -#endif - if (File.Exists(sfoPath)) - { - try - { - using var br = new BinaryReader(File.OpenRead(sfoPath)); - br.BaseStream.Seek(-0x18, SeekOrigin.End); - return new string(br.ReadChars(9)).TrimEnd('\0').Insert(4, "-"); - } - catch - { - // We don't care what the error was - return null; - } - } - - return null; - } - - /// - /// Get the version from a PlayStation 3 disc, if possible - /// - /// Game version if possible, null on error - public string? GetPlayStation3Version() - { - // If there's no drive path, we can't do this part - if (string.IsNullOrEmpty(Name)) - return null; - - // If the folder no longer exists, we can't do this part - if (!Directory.Exists(Name)) - return null; - - // Attempt to use PS3_DISC.SFB - string sfbPath = Path.Combine(Name, "PS3_DISC.SFB"); - if (File.Exists(sfbPath)) - { - try - { - using var br = new BinaryReader(File.OpenRead(sfbPath)); - br.BaseStream.Seek(0x230, SeekOrigin.Begin); - var discVersion = new string(br.ReadChars(0x10)).TrimEnd('\0'); - if (!string.IsNullOrEmpty(discVersion)) - return discVersion; - } - catch - { - // We don't care what the error was - return null; - } - } - - // Attempt to use PARAM.SFO -#if NET20 || NET35 - string sfoPath = Path.Combine(Path.Combine(Name, "PS3_GAME"), "PARAM.SFO"); -#else - string sfoPath = Path.Combine(Name, "PS3_GAME", "PARAM.SFO"); -#endif - if (File.Exists(sfoPath)) - { - try - { - using var br = new BinaryReader(File.OpenRead(sfoPath)); - br.BaseStream.Seek(-0x08, SeekOrigin.End); - return new string(br.ReadChars(5)).TrimEnd('\0'); - } - catch - { - // We don't care what the error was - return null; - } - } - - return null; - } - - /// - /// Get the firmware version from a PlayStation 3 disc, if possible - /// - /// Firmware version if possible, null on error - public string? GetPlayStation3FirmwareVersion() - { - // If there's no drive path, we can't do this part - if (string.IsNullOrEmpty(Name)) - return null; - - // If the folder no longer exists, we can't do this part - if (!Directory.Exists(Name)) - return null; - - // Attempt to read from /PS3_UPDATE/PS3UPDAT.PUP -#if NET20 || NET35 - string pupPath = Path.Combine(Path.Combine(Name, "PS3_UPDATE"), "PS3UPDAT.PUP"); -#else - string pupPath = Path.Combine(Name, "PS3_UPDATE", "PS3UPDAT.PUP"); -#endif - if (!File.Exists(pupPath)) - return null; - - try - { - using var br = new BinaryReader(File.OpenRead(pupPath)); - br.BaseStream.Seek(0x3E, SeekOrigin.Begin); - byte[] buf = new byte[2]; - br.Read(buf, 0, 2); - Array.Reverse(buf); - short location = BitConverter.ToInt16(buf, 0); - br.BaseStream.Seek(location, SeekOrigin.Begin); - return new string(br.ReadChars(4)); - } - catch - { - // We don't care what the error was - return null; - } - } - - /// - /// Get the internal serial from a PlayStation 4 disc, if possible - /// - /// Internal disc serial if possible, null on error - public string? GetPlayStation4Serial() - { - // If there's no drive path, we can't do this part - if (string.IsNullOrEmpty(Name)) - return null; - - // If the folder no longer exists, we can't do this part - if (!Directory.Exists(Name)) - return null; - - // If we can't find param.sfo, we don't have a PlayStation 4 disc -#if NET20 || NET35 - string paramSfoPath = Path.Combine(Path.Combine(Name, "bd"), "param.sfo"); -#else - string paramSfoPath = Path.Combine(Name, "bd", "param.sfo"); -#endif - if (!File.Exists(paramSfoPath)) - return null; - - // Let's try reading param.sfo to find the serial at the end of the file - try - { - using var br = new BinaryReader(File.OpenRead(paramSfoPath)); - br.BaseStream.Seek(-0x14, SeekOrigin.End); - return new string(br.ReadChars(9)).Insert(4, "-"); - } - catch - { - // We don't care what the error was - return null; - } - } - - /// - /// Get the version from a PlayStation 4 disc, if possible - /// - /// Game version if possible, null on error - public string? GetPlayStation4Version() - { - // If there's no drive path, we can't do this part - if (string.IsNullOrEmpty(Name)) - return null; - - // If the folder no longer exists, we can't do this part - if (!Directory.Exists(Name)) - return null; - - // If we can't find param.sfo, we don't have a PlayStation 4 disc -#if NET20 || NET35 - string paramSfoPath = Path.Combine(Path.Combine(Name, "bd"), "param.sfo"); -#else - string paramSfoPath = Path.Combine(Name, "bd", "param.sfo"); -#endif - if (!File.Exists(paramSfoPath)) - return null; - - // Let's try reading param.sfo to find the version at the end of the file - try - { - using var 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 internal serial from a PlayStation 5 disc, if possible - /// - /// Internal disc serial if possible, null on error - public string? GetPlayStation5Serial() - { - // Attempt to get the param.json file - var json = GetPlayStation5ParamsJsonFromDrive(); - if (json == null) - return null; - - try - { - return json["disc"]?[0]?["masterDataId"]?.Value()?.Insert(4, "-"); - } - catch - { - // We don't care what the error was - return null; - } - } - - // - /// Get the version from a PlayStation 5 disc, if possible - /// - /// Game version if possible, null on error - public string? GetPlayStation5Version() - { - // Attempt to get the param.json file - var json = GetPlayStation5ParamsJsonFromDrive(); - if (json == null) - return null; - - try - { - return json["masterVersion"]?.Value(); - } - catch - { - // We don't care what the error was - return null; - } - } - - /// - /// Get the params.json file from a drive path, if possible - /// - /// JObject representing the JSON on success, null on error - private JObject? GetPlayStation5ParamsJsonFromDrive() - { - // If there's no drive path, we can't do this part - if (string.IsNullOrEmpty(Name)) - return null; - - // If the folder no longer exists, we can't do this part - if (!Directory.Exists(Name)) - return null; - - // If we can't find param.json, we don't have a PlayStation 5 disc -#if NET20 || NET35 - string paramJsonPath = Path.Combine(Path.Combine(Name, "bd"), "param.json"); -#else - string paramJsonPath = Path.Combine(Name, "bd", "param.json"); -#endif - return GetPlayStation5ParamsJsonFromFile(paramJsonPath); - } - - /// - /// Get the params.json file from a filename, if possible - /// - /// Filename to check - /// JObject representing the JSON on success, null on error - private static JObject? GetPlayStation5ParamsJsonFromFile(string? filename) - { - // If the file doesn't exist - if (string.IsNullOrEmpty(filename) || !File.Exists(filename)) - return null; - - // Let's try reading param.json to find the version in the unencrypted JSON - try - { - using var br = new BinaryReader(File.OpenRead(filename)); - br.BaseStream.Seek(0x800, SeekOrigin.Begin); - byte[] jsonBytes = br.ReadBytes((int)(br.BaseStream.Length - 0x800)); - return JsonConvert.DeserializeObject(Encoding.ASCII.GetString(jsonBytes)) as JObject; - } - catch - { - // We don't care what the error was - return null; - } - } - - #endregion - #region Helpers /// diff --git a/MPF.Frontend/Tools/PhysicalTool.cs b/MPF.Frontend/Tools/PhysicalTool.cs new file mode 100644 index 00000000..772ae110 --- /dev/null +++ b/MPF.Frontend/Tools/PhysicalTool.cs @@ -0,0 +1,483 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using MPF.Processors; +using SabreTools.IO; +using SabreTools.RedumpLib.Data; + +namespace MPF.Frontend.Tools +{ + public static class PhysicalTool + { + #region Information Extraction + + /// + /// Get the EXE name from a PlayStation disc, if possible + /// + /// Drive to extract information from + /// Executable name on success, null otherwise + public static string? GetPlayStationExecutableName(Drive? drive) + { + // If there's no drive path, we can't get exe name + if (string.IsNullOrEmpty(drive?.Name)) + return null; + + // If the folder no longer exists, we can't get exe name + if (!Directory.Exists(drive.Name)) + return null; + + // Get the two paths that we will need to check + string psxExePath = Path.Combine(drive.Name, "PSX.EXE"); + string systemCnfPath = Path.Combine(drive.Name, "SYSTEM.CNF"); + + // 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.?:\\?(.*)", RegexOptions.Compiled); + if (match.Groups.Count > 1) + { + string? serial = match.Groups[1].Value; + + // Some games may have the EXE in a subfolder + serial = Path.GetFileName(serial); + + return serial; + } + } + + // If the SYSTEM.CNF value can't be found, try PSX.EXE + if (File.Exists(psxExePath)) + return "PSX.EXE"; + + // If neither can be found, we return null + return null; + } + + /// + /// Get the EXE date from a PlayStation disc, if possible + /// + /// Drive to extract information from + /// 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 + public static bool GetPlayStationExecutableInfo(Drive? drive, out string? serial, out Region? region, out string? date) + { + serial = null; region = null; date = null; + + // If there's no drive path, we can't do this part + if (string.IsNullOrEmpty(drive?.Name)) + return false; + + // If the folder no longer exists, we can't do this part + if (!Directory.Exists(drive.Name)) + return false; + + // Get the executable name + string? exeName = GetPlayStationExecutableName(drive); + + // If no executable found, we can't do this part + if (exeName == null) + return false; + + // EXE name may have a trailing `;` after + // EXE name should always be in all caps + exeName = exeName + .Split(';')[0] + .ToUpperInvariant(); + + // Serial is most of the EXE name normalized + serial = exeName + .Replace('_', '-') + .Replace(".", string.Empty); + + // Get the region, if possible + region = ProcessingTool.GetPlayStationRegion(exeName); + + // Now that we have the EXE name, try to get the fileinfo for it + string exePath = Path.Combine(drive.Name, exeName); + if (!File.Exists(exePath)) + return false; + + // Fix the Y2K timestamp issue + var fi = new FileInfo(exePath); + var 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 to extract information from + /// Game version if possible, null on error + public static string? GetPlayStation2Version(Drive? drive) + { + // If there's no drive path, we can't do this part + if (string.IsNullOrEmpty(drive?.Name)) + return null; + + // If the folder no longer exists, we can't do this part + if (!Directory.Exists(drive.Name)) + return null; + + // Get the SYSTEM.CNF path to check + string systemCnfPath = Path.Combine(drive.Name, "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 internal serial from a PlayStation 3 disc, if possible + /// + /// Drive to extract information from + /// Internal disc serial if possible, null on error + public static string? GetPlayStation3Serial(Drive? drive) + { + // If there's no drive path, we can't do this part + if (string.IsNullOrEmpty(drive?.Name)) + return null; + + // If the folder no longer exists, we can't do this part + if (!Directory.Exists(drive.Name)) + return null; + + // Attempt to use PS3_DISC.SFB + string sfbPath = Path.Combine(drive.Name, "PS3_DISC.SFB"); + if (File.Exists(sfbPath)) + { + try + { + using var br = new BinaryReader(File.OpenRead(sfbPath)); + br.BaseStream.Seek(0x220, SeekOrigin.Begin); + return new string(br.ReadChars(0x10)).TrimEnd('\0'); + } + catch + { + // We don't care what the error was + return null; + } + } + + // Attempt to use PARAM.SFO +#if NET20 || NET35 + string sfoPath = Path.Combine(Path.Combine(drive.Name, "PS3_GAME"), "PARAM.SFO"); +#else + string sfoPath = Path.Combine(drive.Name, "PS3_GAME", "PARAM.SFO"); +#endif + if (File.Exists(sfoPath)) + { + try + { + using var br = new BinaryReader(File.OpenRead(sfoPath)); + br.BaseStream.Seek(-0x18, SeekOrigin.End); + return new string(br.ReadChars(9)).TrimEnd('\0').Insert(4, "-"); + } + catch + { + // We don't care what the error was + return null; + } + } + + return null; + } + + /// + /// Get the version from a PlayStation 3 disc, if possible + /// + /// Drive to extract information from + /// Game version if possible, null on error + public static string? GetPlayStation3Version(Drive? drive) + { + // If there's no drive path, we can't do this part + if (string.IsNullOrEmpty(drive?.Name)) + return null; + + // If the folder no longer exists, we can't do this part + if (!Directory.Exists(drive.Name)) + return null; + + // Attempt to use PS3_DISC.SFB + string sfbPath = Path.Combine(drive.Name, "PS3_DISC.SFB"); + if (File.Exists(sfbPath)) + { + try + { + using var br = new BinaryReader(File.OpenRead(sfbPath)); + br.BaseStream.Seek(0x230, SeekOrigin.Begin); + var discVersion = new string(br.ReadChars(0x10)).TrimEnd('\0'); + if (!string.IsNullOrEmpty(discVersion)) + return discVersion; + } + catch + { + // We don't care what the error was + return null; + } + } + + // Attempt to use PARAM.SFO +#if NET20 || NET35 + string sfoPath = Path.Combine(Path.Combine(drive.Name, "PS3_GAME"), "PARAM.SFO"); +#else + string sfoPath = Path.Combine(drive.Name, "PS3_GAME", "PARAM.SFO"); +#endif + if (File.Exists(sfoPath)) + { + try + { + using var br = new BinaryReader(File.OpenRead(sfoPath)); + br.BaseStream.Seek(-0x08, SeekOrigin.End); + return new string(br.ReadChars(5)).TrimEnd('\0'); + } + catch + { + // We don't care what the error was + return null; + } + } + + return null; + } + + /// + /// Get the firmware version from a PlayStation 3 disc, if possible + /// + /// Drive to extract information from + /// Firmware version if possible, null on error + public static string? GetPlayStation3FirmwareVersion(Drive? drive) + { + // If there's no drive path, we can't do this part + if (string.IsNullOrEmpty(drive?.Name)) + return null; + + // If the folder no longer exists, we can't do this part + if (!Directory.Exists(drive.Name)) + return null; + + // Attempt to read from /PS3_UPDATE/PS3UPDAT.PUP +#if NET20 || NET35 + string pupPath = Path.Combine(Path.Combine(drive.Name, "PS3_UPDATE"), "PS3UPDAT.PUP"); +#else + string pupPath = Path.Combine(drive.Name, "PS3_UPDATE", "PS3UPDAT.PUP"); +#endif + if (!File.Exists(pupPath)) + return null; + + try + { + using var br = new BinaryReader(File.OpenRead(pupPath)); + br.BaseStream.Seek(0x3E, SeekOrigin.Begin); + byte[] buf = new byte[2]; + br.Read(buf, 0, 2); + Array.Reverse(buf); + short location = BitConverter.ToInt16(buf, 0); + br.BaseStream.Seek(location, SeekOrigin.Begin); + return new string(br.ReadChars(4)); + } + catch + { + // We don't care what the error was + return null; + } + } + + /// + /// Get the internal serial from a PlayStation 4 disc, if possible + /// + /// Drive to extract information from + /// Internal disc serial if possible, null on error + public static string? GetPlayStation4Serial(Drive? drive) + { + // If there's no drive path, we can't do this part + if (string.IsNullOrEmpty(drive?.Name)) + return null; + + // If the folder no longer exists, we can't do this part + if (!Directory.Exists(drive.Name)) + return null; + + // If we can't find param.sfo, we don't have a PlayStation 4 disc +#if NET20 || NET35 + string paramSfoPath = Path.Combine(Path.Combine(drive.Name, "bd"), "param.sfo"); +#else + string paramSfoPath = Path.Combine(drive.Name, "bd", "param.sfo"); +#endif + if (!File.Exists(paramSfoPath)) + return null; + + // Let's try reading param.sfo to find the serial at the end of the file + try + { + using var br = new BinaryReader(File.OpenRead(paramSfoPath)); + br.BaseStream.Seek(-0x14, SeekOrigin.End); + return new string(br.ReadChars(9)).Insert(4, "-"); + } + catch + { + // We don't care what the error was + return null; + } + } + + /// + /// Get the version from a PlayStation 4 disc, if possible + /// + /// Drive to extract information from + /// Game version if possible, null on error + public static string? GetPlayStation4Version(Drive? drive) + { + // If there's no drive path, we can't do this part + if (string.IsNullOrEmpty(drive?.Name)) + return null; + + // If the folder no longer exists, we can't do this part + if (!Directory.Exists(drive.Name)) + return null; + + // If we can't find param.sfo, we don't have a PlayStation 4 disc +#if NET20 || NET35 + string paramSfoPath = Path.Combine(Path.Combine(drive.Name, "bd"), "param.sfo"); +#else + string paramSfoPath = Path.Combine(drive.Name, "bd", "param.sfo"); +#endif + if (!File.Exists(paramSfoPath)) + return null; + + // Let's try reading param.sfo to find the version at the end of the file + try + { + using var 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 internal serial from a PlayStation 5 disc, if possible + /// + /// Drive to extract information from + /// Internal disc serial if possible, null on error + public static string? GetPlayStation5Serial(Drive? drive) + { + // Attempt to get the param.json file + var json = GetPlayStation5ParamsJsonFromDrive(drive); + if (json == null) + return null; + + try + { + return json["disc"]?[0]?["masterDataId"]?.Value()?.Insert(4, "-"); + } + catch + { + // We don't care what the error was + return null; + } + } + + // + /// Get the version from a PlayStation 5 disc, if possible + /// + /// Drive to extract information from + /// Game version if possible, null on error + public static string? GetPlayStation5Version(Drive? drive) + { + // Attempt to get the param.json file + var json = GetPlayStation5ParamsJsonFromDrive(drive); + if (json == null) + return null; + + try + { + return json["masterVersion"]?.Value(); + } + catch + { + // We don't care what the error was + return null; + } + } + + /// + /// Get the params.json file from a drive path, if possible + /// + /// Drive to extract information from + /// JObject representing the JSON on success, null on error + private static JObject? GetPlayStation5ParamsJsonFromDrive(Drive? drive) + { + // If there's no drive path, we can't do this part + if (string.IsNullOrEmpty(drive?.Name)) + return null; + + // If the folder no longer exists, we can't do this part + if (!Directory.Exists(drive.Name)) + return null; + + // If we can't find param.json, we don't have a PlayStation 5 disc +#if NET20 || NET35 + string paramJsonPath = Path.Combine(Path.Combine(drive.Name, "bd"), "param.json"); +#else + string paramJsonPath = Path.Combine(drive.Name, "bd", "param.json"); +#endif + return GetPlayStation5ParamsJsonFromFile(paramJsonPath); + } + + /// + /// Get the params.json file from a filename, if possible + /// + /// Filename to check + /// JObject representing the JSON on success, null on error + private static JObject? GetPlayStation5ParamsJsonFromFile(string? filename) + { + // If the file doesn't exist + if (string.IsNullOrEmpty(filename) || !File.Exists(filename)) + return null; + + // Let's try reading param.json to find the version in the unencrypted JSON + try + { + using var br = new BinaryReader(File.OpenRead(filename)); + br.BaseStream.Seek(0x800, SeekOrigin.Begin); + byte[] jsonBytes = br.ReadBytes((int)(br.BaseStream.Length - 0x800)); + return JsonConvert.DeserializeObject(Encoding.ASCII.GetString(jsonBytes)) as JObject; + } + catch + { + // We don't care what the error was + return null; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/MPF.Frontend/Tools/SubmissionGenerator.cs b/MPF.Frontend/Tools/SubmissionGenerator.cs index 14f18e18..7a6204e4 100644 --- a/MPF.Frontend/Tools/SubmissionGenerator.cs +++ b/MPF.Frontend/Tools/SubmissionGenerator.cs @@ -681,12 +681,12 @@ namespace MPF.Frontend.Tools case RedumpSystem.KonamiPython2: // TODO: Remove this hack when DIC supports build date output if (isDiscImageCreator) - info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName()); + info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", PhysicalTool.GetPlayStationExecutableName(drive)); if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? kp2Exe) && !string.IsNullOrEmpty(kp2Exe)) info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(kp2Exe); - if (drive?.GetPlayStationExecutableInfo(out var kp2Serial, out Region? kp2Region, out var kp2Date) == true) + if (PhysicalTool.GetPlayStationExecutableInfo(drive, out var kp2Serial, out Region? kp2Region, out var kp2Date) == true) { if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value)) info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = kp2Serial ?? string.Empty; @@ -696,7 +696,7 @@ namespace MPF.Frontend.Tools } if (string.IsNullOrEmpty(info.VersionAndEditions!.Version)) - info.VersionAndEditions!.Version = drive?.GetPlayStation2Version() ?? string.Empty; + info.VersionAndEditions!.Version = PhysicalTool.GetPlayStation2Version(drive) ?? string.Empty; break; @@ -795,12 +795,12 @@ namespace MPF.Frontend.Tools case RedumpSystem.SonyPlayStation: // TODO: Remove this hack when DIC supports build date output if (isDiscImageCreator) - info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName(), psx: true); + info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", PhysicalTool.GetPlayStationExecutableName(drive), psx: true); if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? psxExe) && !string.IsNullOrEmpty(psxExe)) info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(psxExe); - if (drive?.GetPlayStationExecutableInfo(out var psxSerial, out Region? psxRegion, out var psxDate) == true) + if (PhysicalTool.GetPlayStationExecutableInfo(drive, out var psxSerial, out Region? psxRegion, out var psxDate) == true) { if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value)) info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = psxSerial ?? string.Empty; @@ -816,12 +816,12 @@ namespace MPF.Frontend.Tools // TODO: Remove this hack when DIC supports build date output if (isDiscImageCreator) - info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName()); + info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", PhysicalTool.GetPlayStationExecutableName(drive)); if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps2Exe) && !string.IsNullOrEmpty(ps2Exe)) info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(ps2Exe); - if (drive?.GetPlayStationExecutableInfo(out var ps2Serial, out Region? ps2Region, out var ps2Date) == true) + if (PhysicalTool.GetPlayStationExecutableInfo(drive, out var ps2Serial, out Region? ps2Region, out var ps2Date) == true) { if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value)) info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps2Serial ?? string.Empty; @@ -831,7 +831,7 @@ namespace MPF.Frontend.Tools } if (string.IsNullOrEmpty(info.VersionAndEditions!.Version)) - info.VersionAndEditions!.Version = drive?.GetPlayStation2Version() ?? string.Empty; + info.VersionAndEditions!.Version = PhysicalTool.GetPlayStation2Version(drive) ?? string.Empty; break; @@ -840,14 +840,14 @@ namespace MPF.Frontend.Tools info.Extras.DiscID ??= addPlaceholders ? RequiredValue : string.Empty; if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps3Serial) || string.IsNullOrEmpty(ps3Serial)) - info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation3Serial() ?? string.Empty; + info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = PhysicalTool.GetPlayStation3Serial(drive) ?? string.Empty; if (string.IsNullOrEmpty(info.VersionAndEditions!.Version)) - info.VersionAndEditions!.Version = drive?.GetPlayStation3Version() ?? string.Empty; + info.VersionAndEditions!.Version = PhysicalTool.GetPlayStation3Version(drive) ?? string.Empty; if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.Patches, out string? ps3Firmware) || string.IsNullOrEmpty(ps3Firmware)) { - string? firmwareVersion = drive?.GetPlayStation3FirmwareVersion(); + string? firmwareVersion = PhysicalTool.GetPlayStation3FirmwareVersion(drive); if (firmwareVersion != null) info.CommonDiscInfo!.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}"; } @@ -856,19 +856,19 @@ namespace MPF.Frontend.Tools case RedumpSystem.SonyPlayStation4: if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps4Serial) || string.IsNullOrEmpty(ps4Serial)) - info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation4Serial() ?? string.Empty; + info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = PhysicalTool.GetPlayStation4Serial(drive) ?? string.Empty; if (string.IsNullOrEmpty(info.VersionAndEditions!.Version)) - info.VersionAndEditions!.Version = drive?.GetPlayStation4Version() ?? string.Empty; + info.VersionAndEditions!.Version = PhysicalTool.GetPlayStation4Version(drive) ?? string.Empty; break; case RedumpSystem.SonyPlayStation5: if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps5Serial) || string.IsNullOrEmpty(ps5Serial)) - info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation5Serial() ?? string.Empty; + info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = PhysicalTool.GetPlayStation5Serial(drive) ?? string.Empty; if (string.IsNullOrEmpty(info.VersionAndEditions!.Version)) - info.VersionAndEditions!.Version = drive?.GetPlayStation5Version() ?? string.Empty; + info.VersionAndEditions!.Version = PhysicalTool.GetPlayStation5Version(drive) ?? string.Empty; break; diff --git a/MPF.Frontend/ViewModels/MainViewModel.cs b/MPF.Frontend/ViewModels/MainViewModel.cs index 1390fedd..2e09e97e 100644 --- a/MPF.Frontend/ViewModels/MainViewModel.cs +++ b/MPF.Frontend/ViewModels/MainViewModel.cs @@ -1917,7 +1917,7 @@ namespace MPF.Frontend.ViewModels { case RedumpSystem.SonyPlayStation: case RedumpSystem.SonyPlayStation2: - drive.GetPlayStationExecutableInfo(out string? serial, out _, out _); + PhysicalTool.GetPlayStationExecutableInfo(drive, out string? serial, out _, out _); volumeLabel = serial ?? "track"; break;