mirror of
https://github.com/SabreTools/MPF.git
synced 2026-02-16 13:55:20 +00:00
871 lines
31 KiB
C#
871 lines
31 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using SabreTools.IO;
|
|
using SabreTools.RedumpLib.Data;
|
|
|
|
namespace MPF.Frontend.Tools
|
|
{
|
|
public static class PhysicalTool
|
|
{
|
|
#region Generic
|
|
|
|
/// <summary>
|
|
/// Get the last modified date for a file from a physical disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <param name="filePath">Relative file path</param>
|
|
/// <returns>Output last modified date in "yyyy-mm-dd" format if possible, null on error</returns>
|
|
public static string? GetFileDate(Drive? drive, string? filePath, bool fixTwoDigitYear = false)
|
|
{
|
|
// 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 the executable name is invalid, we can't do this part
|
|
if (string.IsNullOrEmpty(filePath))
|
|
return null;
|
|
|
|
// Now that we have the EXE name, try to get the fileinfo for it
|
|
string exePath = Path.Combine(drive.Name, filePath);
|
|
if (!File.Exists(exePath))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Get the last modified time
|
|
var fi = new FileInfo(exePath);
|
|
var lastModified = fi.LastWriteTimeUtc;
|
|
int year = lastModified.Year;
|
|
int month = lastModified.Month;
|
|
int day = lastModified.Day;
|
|
|
|
// Fix the Y2K timestamp issue, if required
|
|
if (fixTwoDigitYear)
|
|
year = year >= 1900 && year < 1920 ? 2000 + year % 100 : year;
|
|
|
|
// Format and return the string
|
|
var dt = new DateTime(year, month, day);
|
|
return dt.ToString("yyyy-MM-dd");
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the first numBytes bytes from a disc drive
|
|
/// </summary>
|
|
/// <param name="drive">Drive to get sector from</param>
|
|
/// <param name="numBytes">Number of bytes to read from drive, maximum of one sector (2048 bytes)</param>
|
|
/// <returns>Byte array of first sector of data, null on error</returns>
|
|
public static byte[]? GetFirstBytes(Drive? drive, int numBytes)
|
|
{
|
|
if (drive == null || drive.Letter == null || drive.Letter == '\0')
|
|
return null;
|
|
|
|
// Must read between 1 and 2048 bytes
|
|
if (numBytes < 1)
|
|
return null;
|
|
else if (numBytes > 2048)
|
|
numBytes = 2048;
|
|
|
|
string drivePath = $"\\\\.\\{drive.Letter}:";
|
|
var firstSector = new byte[numBytes];
|
|
try
|
|
{
|
|
// Open the drive as a raw device
|
|
using var driveStream = new FileStream(drivePath, FileMode.Open, FileAccess.Read);
|
|
|
|
// Read the first sector
|
|
int bytesRead = driveStream.Read(firstSector, 0, numBytes);
|
|
if (bytesRead < numBytes)
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
|
|
return firstSector;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region BD-Video
|
|
|
|
/// <summary>
|
|
/// Get if the Bus Encryption Enabled (BEE) flag is set in a path
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Bus encryption enabled status if possible, false otherwise</returns>
|
|
public static bool GetBusEncryptionEnabled(Drive? drive)
|
|
{
|
|
// If there's no drive path, we can't get BEE flag
|
|
if (string.IsNullOrEmpty(drive?.Name))
|
|
return false;
|
|
|
|
// If the folder no longer exists, we can't get exe name
|
|
if (!Directory.Exists(drive!.Name))
|
|
return false;
|
|
|
|
// Get the two possible file paths
|
|
#if NET20 || NET35
|
|
string content000 = Path.Combine(Path.Combine(drive.Name, "AACS"), "Content000.cer");
|
|
string content001 = Path.Combine(Path.Combine(drive.Name, "AACS"), "Content001.cer");
|
|
#else
|
|
string content000 = Path.Combine(drive.Name, "AACS", "Content000.cer");
|
|
string content001 = Path.Combine(drive.Name, "AACS", "Content001.cer");
|
|
#endif
|
|
|
|
try
|
|
{
|
|
// Check the required files
|
|
if (File.Exists(content000) && new FileInfo(content000).Length > 1)
|
|
{
|
|
using var fs = File.OpenRead(content000);
|
|
_ = fs.ReadByte(); // Skip the first byte
|
|
return fs.ReadByte() > 127;
|
|
}
|
|
else if (File.Exists(content001) && new FileInfo(content001).Length > 1)
|
|
{
|
|
using var fs = File.OpenRead(content001);
|
|
_ = fs.ReadByte(); // Skip the first byte
|
|
return fs.ReadByte() > 127;
|
|
}
|
|
|
|
// False if neither file fits the criteria
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PlayStation
|
|
|
|
/// <summary>
|
|
/// Get the EXE name from a PlayStation disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Executable name on success, null otherwise</returns>
|
|
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");
|
|
|
|
try
|
|
{
|
|
// 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)
|
|
{
|
|
// Some games may have the EXE in a subfolder
|
|
string? serial = match.Groups[1].Value;
|
|
return Path.GetFileName(serial);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception, assume SYSTEM.CNF doesn't exist
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the serial from a PlayStation disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Serial on success, null otherwise</returns>
|
|
public static string? GetPlayStationSerial(Drive? drive)
|
|
{
|
|
// Try to get the executable name
|
|
string? exeName = GetPlayStationExecutableName(drive);
|
|
if (string.IsNullOrEmpty(exeName))
|
|
return null;
|
|
|
|
// Handle generic PSX.EXE
|
|
if (exeName == "PSX.EXE")
|
|
return null;
|
|
|
|
// 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
|
|
return exeName
|
|
.Replace('_', '-')
|
|
.Replace(".", string.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the version from a PlayStation 2 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Game version if possible, null on error</returns>
|
|
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
|
|
{
|
|
// 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;
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the internal serial from a PlayStation 3 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Internal disc serial if possible, null on error</returns>
|
|
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
|
|
{
|
|
// Absorb the exception
|
|
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
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the version from a PlayStation 3 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Game version if possible, null on error</returns>
|
|
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
|
|
{
|
|
// Absorb the exception
|
|
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
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the firmware version from a PlayStation 3 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Firmware version if possible, null on error</returns>
|
|
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
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the internal serial from a PlayStation 4 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Internal disc serial if possible, null on error</returns>
|
|
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
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the version from a PlayStation 4 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Game version if possible, null on error</returns>
|
|
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
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the app.pkg info from a PlayStation 4 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>PKG info if possible, null on error</returns>
|
|
public static string? GetPlayStation4PkgInfo(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;
|
|
|
|
// Try parse the app.pkg (multiple if they exist)
|
|
try
|
|
{
|
|
string? pkgInfo = "";
|
|
|
|
string[] appDirs = Directory.GetDirectories(Path.Combine(drive.Name, "app"), "?????????", SearchOption.TopDirectoryOnly);
|
|
|
|
foreach (string dir in appDirs)
|
|
{
|
|
string appPkgPath = Path.Combine(dir, "app.pkg");
|
|
if (!File.Exists(appPkgPath))
|
|
continue;
|
|
|
|
long appPkgSize = new FileInfo(appPkgPath).Length;
|
|
if (appPkgSize < 4096)
|
|
continue;
|
|
|
|
// Read the app.pkg header
|
|
using var fileStream = new FileStream(appPkgPath, FileMode.Open, FileAccess.Read);
|
|
var appPkgHeaderDeserializer = new SabreTools.Serialization.Readers.AppPkgHeader();
|
|
var appPkgHeader = appPkgHeaderDeserializer.Deserialize(fileStream);
|
|
|
|
if (appPkgHeader != null)
|
|
pkgInfo += $"{appPkgHeader.ContentID}{Environment.NewLine}";
|
|
}
|
|
|
|
if (pkgInfo == "")
|
|
return null;
|
|
else
|
|
return pkgInfo;
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the internal serial from a PlayStation 5 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Internal disc serial if possible, null on error</returns>
|
|
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<string>()?.Insert(4, "-");
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// <summary>
|
|
/// Get the version from a PlayStation 5 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Game version if possible, null on error</returns>
|
|
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<string>();
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the params.json file from a drive path, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>JObject representing the JSON on success, null on error</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the params.json file from a filename, if possible
|
|
/// </summary>
|
|
/// <param name="filename">Filename to check</param>
|
|
/// <returns>JObject representing the JSON on success, null on error</returns>
|
|
private static JObject? GetPlayStation5ParamsJsonFromFile(string? filename)
|
|
{
|
|
// If the file doesn't exist
|
|
if (string.IsNullOrEmpty(filename))
|
|
return null;
|
|
if (!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
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the app.pkg info from a PlayStation 5 disc, if possible
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>PKG info if possible, null on error</returns>
|
|
public static string? GetPlayStation5PkgInfo(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;
|
|
|
|
// Try parse the app_sc.pkg (multiple if they exist)
|
|
try
|
|
{
|
|
string? pkgInfo = "";
|
|
|
|
string[] appDirs = Directory.GetDirectories(Path.Combine(drive.Name, "app"), "?????????", SearchOption.TopDirectoryOnly);
|
|
|
|
foreach (string dir in appDirs)
|
|
{
|
|
string appPkgPath = Path.Combine(dir, "app_sc.pkg");
|
|
if (!File.Exists(appPkgPath))
|
|
continue;
|
|
|
|
long appPkgSize = new FileInfo(appPkgPath).Length;
|
|
if (appPkgSize < 4096)
|
|
continue;
|
|
|
|
// Read the app_sc.pkg header
|
|
using var fileStream = new FileStream(appPkgPath, FileMode.Open, FileAccess.Read);
|
|
var appPkgHeaderDeserializer = new SabreTools.Serialization.Readers.AppPkgHeader();
|
|
var appPkgHeader = appPkgHeaderDeserializer.Deserialize(fileStream);
|
|
|
|
if (appPkgHeader != null)
|
|
pkgInfo += $"{appPkgHeader.ContentID}{Environment.NewLine}";
|
|
}
|
|
|
|
if (pkgInfo == "")
|
|
return null;
|
|
else
|
|
return pkgInfo;
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Xbox
|
|
|
|
/// <summary>
|
|
/// Get all filenames for Xbox One and Xbox Series X
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Filenames if possible, null on error</returns>
|
|
public static string? GetXboxFilenames(Drive? drive)
|
|
{
|
|
// If there's no drive path, can't do anything
|
|
if (string.IsNullOrEmpty(drive?.Name))
|
|
return null;
|
|
|
|
// If the folder no longer exists, can't do anything
|
|
if (!Directory.Exists(drive!.Name))
|
|
return null;
|
|
|
|
// Get the MSXC directory path
|
|
string msxc = Path.Combine(drive.Name, "MSXC");
|
|
if (!Directory.Exists(msxc))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
var files = Directory.GetFiles(msxc, "*", SearchOption.TopDirectoryOnly);
|
|
var filenames = Array.ConvertAll(files, Path.GetFileName);
|
|
return string.Join("\n", filenames);
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Title ID(s) for Xbox One and Xbox Series X
|
|
/// </summary>
|
|
/// <param name="drive">Drive to extract information from</param>
|
|
/// <returns>Title ID(s) if possible, null on error</returns>
|
|
public static string? GetXboxTitleID(Drive? drive)
|
|
{
|
|
// If there's no drive path, can't do anything
|
|
if (string.IsNullOrEmpty(drive?.Name))
|
|
return null;
|
|
|
|
// If the folder no longer exists, can't do anything
|
|
if (!Directory.Exists(drive!.Name))
|
|
return null;
|
|
|
|
// Get the catalog.js path
|
|
#if NET20 || NET35
|
|
string catalogjs = Path.Combine(drive.Name, Path.Combine("MSXC", Path.Combine("Metadata", "catalog.js")));
|
|
#else
|
|
string catalogjs = Path.Combine(drive.Name, "MSXC", "Metadata", "catalog.js");
|
|
#endif
|
|
// Check catalog.js exists
|
|
if (!File.Exists(catalogjs))
|
|
return null;
|
|
|
|
// Deserialize catalog.js and extract Title ID(s)
|
|
try
|
|
{
|
|
var catalog = new SabreTools.Serialization.Readers.Catalog().Deserialize(catalogjs);
|
|
if (catalog == null)
|
|
return null;
|
|
if (!string.IsNullOrEmpty(catalog.TitleID))
|
|
return catalog.TitleID;
|
|
if (catalog.Packages == null)
|
|
return null;
|
|
|
|
List<string> titleIDs = [];
|
|
foreach (var package in catalog.Packages)
|
|
{
|
|
if (package?.TitleID != null)
|
|
titleIDs.Add(package.TitleID);
|
|
}
|
|
|
|
return string.Join(", ", [.. titleIDs]);
|
|
}
|
|
catch
|
|
{
|
|
// Absorb the exception
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Sega
|
|
|
|
/// <summary>
|
|
/// Detect the Sega system based on the CD ROM header
|
|
/// </summary>
|
|
/// <param name="drive">Drive to detect system from</param>
|
|
/// <returns>Detected RedumpSystem if detected, null otherwise</returns>
|
|
public static RedumpSystem? DetectSegaSystem(Drive? drive)
|
|
{
|
|
if (drive == null)
|
|
return null;
|
|
|
|
byte[]? firstSector = GetFirstBytes(drive, 0x10);
|
|
if (firstSector == null || firstSector.Length < 0x10)
|
|
return null;
|
|
|
|
string systemType = Encoding.ASCII.GetString(firstSector, 0x00, 0x10);
|
|
|
|
if (systemType.Equals("SEGA SEGASATURN ", StringComparison.Ordinal))
|
|
return RedumpSystem.SegaSaturn;
|
|
else if (systemType.Equals("SEGA SEGAKATANA ", StringComparison.Ordinal))
|
|
return RedumpSystem.SegaDreamcast;
|
|
else if (systemType.Equals("SEGADISCSYSTEM ", StringComparison.Ordinal))
|
|
return RedumpSystem.SegaMegaCDSegaCD;
|
|
else if (systemType.Equals("SEGA MEGA DRIVE ", StringComparison.Ordinal))
|
|
return RedumpSystem.SegaMegaCDSegaCD;
|
|
else if (systemType.Equals("SEGA GENESIS ", StringComparison.Ordinal))
|
|
return RedumpSystem.SegaMegaCDSegaCD;
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Other
|
|
|
|
/// <summary>
|
|
/// Detect a 3DO disc based on the CD ROM header
|
|
/// </summary>
|
|
/// <param name="drive">Drive to detect 3DO disc from</param>
|
|
/// <returns>RedumpSystem.Panasonic3DOInteractiveMultiplayer if detected, null otherwise</returns>
|
|
public static RedumpSystem? Detect3DOSystem(Drive? drive)
|
|
{
|
|
if (drive == null)
|
|
return null;
|
|
|
|
byte[]? firstSector = GetFirstBytes(drive, 0xC0);
|
|
if (firstSector == null || firstSector.Length < 0xC0)
|
|
return null;
|
|
|
|
string systemType = Encoding.ASCII.GetString(firstSector, 0xB0, 0x10);
|
|
|
|
if (systemType.Equals("iamaduckiamaduck", StringComparison.Ordinal))
|
|
return RedumpSystem.Panasonic3DOInteractiveMultiplayer;
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
} |