From b794ec8f9608852f04721e6e308c446998df93aa Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Sat, 11 Nov 2023 19:50:50 +1300 Subject: [PATCH] Parse PS3_DISC.SFB for redump-style IRDs --- LibIRD/IRD.cs | 50 ++++++++++++--- LibIRD/PS3_DiscSFB.cs | 127 +++++++++++++++++++++++++++++++++++++ LibIRD/ParamSFO.cs | 11 ++-- LibIRD/ReIRD.cs | 4 +- PrintParams/PrintParams.cs | 19 ++++-- 5 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 LibIRD/PS3_DiscSFB.cs diff --git a/LibIRD/IRD.cs b/LibIRD/IRD.cs index dcee529..f5d3092 100644 --- a/LibIRD/IRD.cs +++ b/LibIRD/IRD.cs @@ -364,7 +364,8 @@ namespace LibIRD /// Disc Key, byte array of length 16 /// Disc ID, byte array of length 16 /// Disc PIC, byte array of length 115 - public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC) + /// True if redump-style IRD + public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC, bool redump = false) { // Parse ISO, Disc Key, Disc ID, and PIC DiscKey = discKey; @@ -373,7 +374,7 @@ namespace LibIRD PIC = discPIC; // Generate IRD files from ISO - GenerateIRD(isoPath); + GenerateIRD(isoPath, redump); } /// @@ -381,15 +382,14 @@ namespace LibIRD /// /// Path to the ISO /// Path to the .getkey.log file - /// - /// - public IRD(string isoPath, string getKeyLog) + /// True if redump-style IRD + public IRD(string isoPath, string getKeyLog, bool redump = false) { // Parse .getkey.log file ParseGetKeyLog(getKeyLog); // Generate IRD files from ISO - GenerateIRD(isoPath); + GenerateIRD(isoPath, redump); } #endregion @@ -624,10 +624,11 @@ namespace LibIRD /// Constructor for generating values from an ISO file /// /// Path to the ISO + /// True if redump-style IRD /// /// /// - private protected void GenerateIRD(string isoPath) + private protected void GenerateIRD(string isoPath, bool redump = false) { // Parse ISO file as a file stream using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath); @@ -638,15 +639,46 @@ namespace LibIRD // New ISO Reader from DiscUtils CDReader reader = new(fs, true, true); + // Redump-style IRDs set the lowest bit of ExtraConfig to 1 + ExtraConfig |= 0x01; + + // If generating redump-style IRD, read PS3 Metadata from PS3_DISC.SFB + if (redump) + { + using DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_DISC.SFB", FileMode.Open, FileAccess.Read); + + // Parse PS3_DISC.SFB file + PS3_DiscSFB ps3_DiscSFB = new(s); + + // Redump-style IRDs use the TITLE ID + if (ps3_DiscSFB.Field.ContainsKey("TITLE_ID") && ps3_DiscSFB.Field["TITLE_ID"].Length == 10 && ps3_DiscSFB.Field["TITLE_ID"][4] == '-') + { + // Use the TITLE_ID from PS3_DISC.SFB + TitleID = string.Concat(ps3_DiscSFB.Field["TITLE_ID"].AsSpan(0, 4), ps3_DiscSFB.Field["TITLE_ID"].AsSpan(5, 5)); + } + else + { + // Valid Title ID not found in PS3_DISC.SFB, use the one in PS3_GAME/PARAM.SFO + redump = false; + } + + // If the version field is present, this is a multi-game disc + // Redump-style IRDs use the VERSION field from PS3_DISC.SFB instead of VERSION from PARAM.SFO + if (ps3_DiscSFB.Field.ContainsKey("VERSION")) + GameVersion = ps3_DiscSFB.Field["VERSION"]; + } + // Read PS3 Metadata from PARAM.SFO using (DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_GAME\\PARAM.SFO", FileMode.Open, FileAccess.Read)) { // Parse PARAM.SFO file ParamSFO paramSFO = new(s); // Store required values for IRD - TitleID = paramSFO["TITLE_ID"]; + if (!redump) + TitleID = paramSFO["TITLE_ID"]; Title = paramSFO["TITLE"]; - GameVersion = paramSFO["VERSION"]; + if (GameVersion == null) + GameVersion = paramSFO["VERSION"]; AppVersion = paramSFO["APP_VER"]; } diff --git a/LibIRD/PS3_DiscSFB.cs b/LibIRD/PS3_DiscSFB.cs new file mode 100644 index 0000000..bfe60fa --- /dev/null +++ b/LibIRD/PS3_DiscSFB.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace LibIRD +{ + /// + /// PS3_DISC.SFB file parsing + /// + public class PS3_DiscSFB + { + /// + /// PS3_DISC.SFB file signature + /// + /// { 0x2E, 0x53, 0x46, 0x42 } + public static readonly string Magic = ".SFB"; + + /// + /// PS3_DISC.SFB file version + /// + /// Typically v1, { 0x00, 0x01 } + public ushort Version { get; private set; } + + /// + /// A field within the PS3_DISC.SFB file + /// + public Dictionary Field { get; private set; } + + /// + /// Constructor using a PARAM.SFO file path + /// + /// Full file path to the PS3_DISC.SFB file + /// + public PS3_DiscSFB(string sfbPath) + { + // Validate file path + if (sfbPath == null || sfbPath.Length <= 0) + throw new ArgumentNullException(nameof(sfbPath)); + + // Read file as a stream, and parse file + using FileStream fs = new(sfbPath, FileMode.Open, FileAccess.Read); + Parse(fs); + } + + /// + /// Parse PS3_DISC.SFB from stream + /// + /// SFB file stream + /// + public PS3_DiscSFB(Stream sfbStream) + { + // Parse file stream + Parse(sfbStream); + } + + /// + /// Read fields from PS3_DISC.SFB + /// + /// File stream for PS3_DISC.SFB + /// + private void Parse(Stream sfbStream) + { + // Read binary stream + using BinaryReader br = new(sfbStream); + + // Check file signature is correct + string magic = Encoding.ASCII.GetString(br.ReadBytes(4)); + if (magic != PS3_DiscSFB.Magic) + throw new FileLoadException("Unexpected PS3_DISC.SFB file"); + + // Read SFB file version + byte[] buf = br.ReadBytes(2); + Array.Reverse(buf); + Version = BitConverter.ToUInt16(buf); + + // Process all field headers + sfbStream.Seek(0x20, SeekOrigin.Begin); + string field = Encoding.ASCII.GetString(br.ReadBytes(0x10)).Trim('\0'); + Field = new Dictionary { }; + while (field != null && field != "") + { + // Find location of value + buf = br.ReadBytes(4); + Array.Reverse(buf); + int offset = BitConverter.ToInt32(buf, 0); + buf = br.ReadBytes(4); + Array.Reverse(buf); + int length = BitConverter.ToInt32(buf, 0); + + // Access and store value + long pos = sfbStream.Position; + sfbStream.Seek(offset, SeekOrigin.Begin); + Field[field] = Encoding.ASCII.GetString(br.ReadBytes(length)).Trim('\0'); + sfbStream.Seek(pos + 8, SeekOrigin.Begin); + + // Attempt to read new field + field = Encoding.ASCII.GetString(br.ReadBytes(0x10)).Trim('\0'); + } + } + + /// + /// Prints formatted parameters extracted from PS3_DISC.SFB to console + /// + public void Print() + { + // Build string from parameters + StringBuilder print = new(); + print.AppendLine("PS3_DISC.SFB Contents:"); + print.AppendLine("======================"); + + // Loop through all parameters in PARAM.SFO + foreach (KeyValuePair field in Field) + { + print.Append(field.Key); + print.Append(": "); + print.AppendLine(field.Value); + } + + // Ensure UTF-8 will display properly + Console.OutputEncoding = Encoding.UTF8; + + // Print formatted string + Console.Write(print); + } + } +} diff --git a/LibIRD/ParamSFO.cs b/LibIRD/ParamSFO.cs index fed4dbb..25ea5c2 100644 --- a/LibIRD/ParamSFO.cs +++ b/LibIRD/ParamSFO.cs @@ -18,7 +18,7 @@ namespace LibIRD /// /// PARAM.SFO file version /// - /// Typically { 0x01 0x01 0x00 0x00 } (v1.1) + /// Typically { 0x01, 0x01, 0x00, 0x00 } (v1.1) public uint Version { get; private set; } /// @@ -117,7 +117,7 @@ namespace LibIRD /// /// Constructor using a PARAM.SFO file path /// - /// Full file path to the PARAM.SFO + /// Full file path to the PARAM.SFO file /// public ParamSFO(string sfoPath) { @@ -143,7 +143,7 @@ namespace LibIRD // Check file signature is correct string magic = Encoding.ASCII.GetString(br.ReadBytes(4)); if (magic != ParamSFO.Magic) - throw new FileLoadException("Not a valid PARAM.SFO file"); + throw new FileLoadException("Unexpected PARAM.SFO file"); // Parse header Version = br.ReadUInt32(); @@ -222,15 +222,14 @@ namespace LibIRD switch (Params[i].DataFormat) { case 0x0404: - print.Append(Params[i].IntValue); - print.AppendLine(); + print.Append(Params[i].IntValue +Environment.NewLine); break; default: print.AppendLine(Params[i].StringValue); break; } - print.Append('\n'); } + print.Append(Environment.NewLine); // Ensure UTF-8 will display properly Console.OutputEncoding = Encoding.UTF8; diff --git a/LibIRD/ReIRD.cs b/LibIRD/ReIRD.cs index 2da95f7..ab11d64 100644 --- a/LibIRD/ReIRD.cs +++ b/LibIRD/ReIRD.cs @@ -70,7 +70,7 @@ namespace LibIRD /// /// /// - public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog) + public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog, true) { // Generate Unique Identifier using ISO CRC32 UID = GenerateUID(isoPath); @@ -117,7 +117,7 @@ namespace LibIRD PIC = GeneratePIC(Size); // Generate IRD fields - GenerateIRD(isoPath); + GenerateIRD(isoPath, true); } #endregion diff --git a/PrintParams/PrintParams.cs b/PrintParams/PrintParams.cs index d030880..f53b146 100644 --- a/PrintParams/PrintParams.cs +++ b/PrintParams/PrintParams.cs @@ -8,12 +8,23 @@ namespace PrintParams { static void Main() { - string filename = "./PARAM.SFO"; - + string filename = "./PS3_DISC.SFB"; if (File.Exists(filename)) { - ParamSFO paramSFO = new("./PARAM.SFO"); - Console.WriteLine("PARAM.SFO for: " + paramSFO["TITLE_ID"] + '\n'); + PS3_DiscSFB ps3_DiscSFB = new(filename); + Console.WriteLine("PS3_DISC.SFB for: " + ps3_DiscSFB.Field["TITLE_ID"]); + ps3_DiscSFB.Print(); + } + else + { + Console.WriteLine(filename + " not found"); + } + + filename = "./PARAM.SFO"; + if (File.Exists(filename)) + { + ParamSFO paramSFO = new(filename); + Console.WriteLine("PARAM.SFO for: " + paramSFO["TITLE_ID"]); paramSFO.Print(); } else