From 4b0d796bd2d06d9d096ce017e5968549219acead Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:05:22 +1300 Subject: [PATCH] Refactor, IRDKit can take directories --- IRDKit/IRDKit.csproj | 8 +- IRDKit/Program.cs | 343 ++++++++++++++++++++++++++++-------------- LibIRD/IRD.cs | 112 ++++++++++---- LibIRD/LibIRD.csproj | 5 +- LibIRD/PS3_DiscSFB.cs | 62 ++++++-- LibIRD/ParamSFO.cs | 224 ++++++++++----------------- LibIRD/ReIRD.cs | 42 +++--- README.md | 4 + 8 files changed, 474 insertions(+), 326 deletions(-) diff --git a/IRDKit/IRDKit.csproj b/IRDKit/IRDKit.csproj index 886cee6..1523d93 100644 --- a/IRDKit/IRDKit.csproj +++ b/IRDKit/IRDKit.csproj @@ -9,7 +9,8 @@ net6.0;net7.0;net8.0 win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64 latest - 0.1.0 + true + 0.2.0 Deterous @@ -23,14 +24,15 @@ - + + - + diff --git a/IRDKit/Program.cs b/IRDKit/Program.cs index abf0c69..e2f0d6d 100644 --- a/IRDKit/Program.cs +++ b/IRDKit/Program.cs @@ -2,203 +2,322 @@ using DiscUtils; using DiscUtils.Iso9660; using LibIRD; +using SabreTools.RedumpLib.Web; using System; using System.Collections.Generic; using System.IO; using System.IO.Hashing; +using System.Security.Cryptography; using System.Text; namespace IRDKit { internal class Program { - // IRD Creation + /// + /// IRD Creation Verb + /// [Verb("create", HelpText = "Create an IRD from an ISO")] public class CreateOptions { [Value(0, Required = true, HelpText = "Path to an ISO file, or directory of ISO files")] public string ISOPath { get; set; } - [Value(1, HelpText = "Path to the IRD file to be created")] + [Value(1, Required = false, HelpText = "Path to the IRD file to be created")] public string IRDPath { get; set; } - [Option('r', "recurse", HelpText = "Recurse through all subdirectories and generate IRDs for all ISOs")] - public bool Recurse { get; set; } + [Option('b', "layerbreak", HelpText = "Layerbreak value in bytes (define for BD-Video hybrid discs). Default: 12219392")] + public long? Layerbreak { get; set; } [Option('k', "key", HelpText = "Hexadecimal representation of the disc key")] public string Key { get; set; } - [Option("key-file", HelpText = "Path to a redump .key file")] - public string KeyFile { get; set; } - [Option('l', "getkey-log", HelpText = "Path to a .getkey.log file")] public string GetKeyLog { get; set; } + + [Option('f', "key-file", HelpText = "Path to a redump .key file")] + public string KeyFile { get; set; } + + [Option('r', "recurse", HelpText = "Recurse through all subdirectories and generate IRDs for all ISOs")] + public bool Recurse { get; set; } } - // IRD Info + /// + /// IRD or ISO information verb + /// [Verb("info", HelpText = "Print information from an IRD or ISO")] public class InfoOptions { [Value(0, Required = true, HelpText = "Path to the IRD or ISO file to be printed")] - public string Path { get; set; } + public string InPath { get; set; } + + [Value(1, Required = false, HelpText = "Path to the text or json file to be created")] + public string OutPath { get; set; } + + [Option('j', "json", HelpText = "Print IRD or ISO information as a JSON object")] + public bool Json { get; set; } } + /// + /// Parse command line arguments + /// + /// Command line arguments public static void Main(string[] args) { - // Parse command line arguments - var result = Parser.Default.ParseArguments(args) - .WithParsed(Run) - .WithNotParsed(HandleParseError); - } - - private static void HandleParseError(IEnumerable errs) - { - if (errs.IsVersion()) - { - // --version - } - else if (errs.IsHelp()) - { - // --help - } - else - { - // Parsing error - Console.WriteLine("Parsing error"); - } - return; + var result = Parser.Default.ParseArguments(args).WithParsed(Run); } + /// + /// Perform + /// + /// + /// + /// private static void Run(object obj) { switch (obj) { - case CreateOptions c: + case CreateOptions opt: Console.OutputEncoding = Encoding.UTF8; - // Create new reproducible redump-style IRD with a given hex key - if (c.Key != null) - { - try - { - // Get disc key from hex string - byte[] discKey = Convert.FromHexString(c.Key); - - IRD ird1 = new ReIRD(c.ISOPath, discKey); - ird1.Write(c.IRDPath ?? Path.GetFileNameWithoutExtension(c.ISOPath) + ".ird"); - ird1.Print(); - } - catch (FileNotFoundException) - { - Console.Error.WriteLine("File not found"); - } - break; - } - - // Create new reproducible redump-style IRD with a given key file - if (c.KeyFile != null) - { - try - { - // Read key from .key file - byte[] discKey = File.ReadAllBytes(c.KeyFile); - - IRD ird1 = new ReIRD(c.ISOPath, discKey); - ird1.Write(c.IRDPath ?? Path.GetFileNameWithoutExtension(c.ISOPath) + ".ird"); - ird1.Print(); - } - catch (FileNotFoundException) - { - Console.Error.WriteLine("File not found"); - } - break; - } - - // Create new reproducible redump-style IRD with a given GetKey log - if (c.GetKeyLog != null) - { - try - { - IRD ird2 = new ReIRD(c.ISOPath, c.GetKeyLog); - ird2.Write(c.IRDPath ?? Path.GetFileNameWithoutExtension(c.ISOPath) + ".ird"); - ird2.Print(); - } - catch (FileNotFoundException) - { - Console.Error.WriteLine("File not found"); - } - break; - } - - // No key provided, try get key from redump.org - // Validate ISO path - ArgumentNullException.ThrowIfNull(c.ISOPath); + ArgumentNullException.ThrowIfNull(opt.ISOPath); - // Check file exists - var iso = new FileInfo(c.ISOPath); - if (!iso.Exists) - throw new FileNotFoundException(nameof(c.ISOPath)); - - // Compute CRC32 hash - byte[] crc32; - using (FileStream fs = File.OpenRead(c.ISOPath)) + // If directory, search for all ISOs in current directory + if (Directory.Exists(opt.ISOPath)) { - Crc32 hasher = new(); - hasher.Append(fs); - crc32 = hasher.GetCurrentHash(); - Array.Reverse(crc32); - Console.WriteLine("Automatic key retrieval not yet implemented, search redump.org for: " + Convert.ToHexString(crc32)); + // If recurse option enabled, search recursively + IEnumerable isoFiles; + if (opt.Recurse) + { + Console.WriteLine($"Recursively searching for ISOs in {opt.ISOPath}"); + isoFiles = Directory.EnumerateFiles(opt.ISOPath, "*.iso", SearchOption.AllDirectories); + } + else + { + Console.WriteLine($"Searching for ISOs in {opt.ISOPath}"); + isoFiles = Directory.EnumerateFiles(opt.ISOPath, "*.iso", SearchOption.TopDirectoryOnly); + } + // Create an IRD file for all ISO files found + foreach (string file in isoFiles) + ProcessISO(opt, file); + break; } - break; + // Create a single IRD from an ISO + if (File.Exists(opt.ISOPath)) + { + ProcessISO(opt, opt.ISOPath, opt.IRDPath); + break; + } - case InfoOptions info: - string filetype = Path.GetExtension(info.Path); + throw new ArgumentException("Not a valid ISO file or directory"); + + case InfoOptions opt: + string filetype = Path.GetExtension(opt.InPath); if (String.Compare(filetype, ".iso", StringComparison.OrdinalIgnoreCase) == 0) { // Open ISO file for reading - using FileStream fs = new FileStream(info.Path, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(info.Path); + using FileStream fs = new FileStream(opt.InPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(opt.InPath); // Validate ISO file stream if (!CDReader.Detect(fs)) throw new InvalidFileSystemException("Not a valid ISO file"); // Create new ISO reader CDReader reader = new(fs, true, true); + File.WriteAllText(opt.OutPath, "{\n"); + + // Write PS3_DISC.SFB info using (DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_DISC.SFB", FileMode.Open, FileAccess.Read)) { try { PS3_DiscSFB ps3_DiscSFB = new(s); - ps3_DiscSFB.Print(); + if (opt.Json) + { + File.AppendAllText(opt.OutPath, "\"PS3_DISC.SFB\": "); + ps3_DiscSFB.PrintJson(opt.OutPath); + File.AppendAllText(opt.OutPath, ","); + } + else + ps3_DiscSFB.Print(opt.OutPath); } catch { Console.WriteLine("PS3_DISC.SFB not found"); } } - + // Write PARAM.SFO info using (DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_GAME\\PARAM.SFO", FileMode.Open, FileAccess.Read)) { try { ParamSFO paramSFO = new(s); - paramSFO.Print(); + if (opt.Json) + { + File.AppendAllText(opt.OutPath, "\n\"PARAM.SFO\": "); + paramSFO.PrintJson(opt.OutPath); + } + else + paramSFO.Print(opt.OutPath); } catch { - Console.WriteLine("./PARAM.SFO not found"); + Console.WriteLine("PS3_GAME\\PARAM.SFO not found"); } } + + File.AppendAllText(opt.OutPath, "\n}"); } - else // Assume it is an IRD file + else { - IRD.Read(info.Path).Print(); + // Assume it is an IRD file + if (opt.Json) + IRD.Read(opt.InPath).PrintJson(opt.OutPath); + else + IRD.Read(opt.InPath).Print(opt.OutPath); } + break; } } + + public static void ProcessISO(CreateOptions opt, string isoPath, string irdPath = null) + { + // Check file exists + var iso = new FileInfo(isoPath); + if (!iso.Exists) + { + Console.WriteLine($"{nameof(isoPath)} is not a valid File or Directory"); + return; + } + Console.WriteLine($"Reading {isoPath}"); + + // Create new reproducible redump-style IRD with a given hex key + if (opt.Key != null) + { + try + { + // Get disc key from hex string + byte[] discKey = Convert.FromHexString(opt.Key); + + Console.WriteLine($"Creating reproducible, redump-style IRD with Key: {opt.Key}"); + IRD ird1 = new ReIRD(isoPath, discKey, opt.Layerbreak); + ird1.Write(irdPath ?? Path.GetFileNameWithoutExtension(isoPath) + ".ird"); + ird1.Print(); + } + catch (FileNotFoundException) + { + Console.Error.WriteLine("File not found"); + } + return; + } + + // Create new reproducible redump-style IRD with a given key file + if (opt.KeyFile != null) + { + try + { + // Read key from .key file + byte[] discKey = File.ReadAllBytes(opt.KeyFile); + + Console.WriteLine($"Creating reproducible, redump-style IRD with Key: {Convert.ToHexString(discKey)}"); + IRD ird2 = new ReIRD(isoPath, discKey, opt.Layerbreak); + ird2.Write(irdPath ?? Path.GetFileNameWithoutExtension(isoPath) + ".ird"); + ird2.Print(); + } + catch (FileNotFoundException) + { + Console.Error.WriteLine("File not found"); + } + return; + } + + // Create new reproducible redump-style IRD with a given GetKey log + if (opt.GetKeyLog != null) + { + try + { + Console.WriteLine($"Creating reproducible, redump-style IRD with key from: {opt.GetKeyLog}"); + IRD ird3 = new ReIRD(isoPath, opt.GetKeyLog); + ird3.Write(irdPath ?? Path.GetFileNameWithoutExtension(isoPath) + ".ird"); + ird3.Print(); + } + catch (FileNotFoundException) + { + Console.Error.WriteLine("File not found"); + } + return; + } + + // No key provided, try get key from redump.org + Console.WriteLine("No key provided... Searching for key on redump.org..."); + + // Compute CRC32 hash + byte[] crc32; + using (FileStream fs = File.OpenRead(isoPath)) + { + Crc32 hasher = new(); + hasher.Append(fs); + crc32 = hasher.GetCurrentHash(); + // Change endianness + Array.Reverse(crc32); + } + string crc32_hash = Convert.ToHexString(crc32).ToLower(); + + // Search for ISO on redump.org + RedumpHttpClient redump = new(); + List ids = redump.CheckSingleSitePage("http://redump.org/discs/system/ps3/quicksearch/" + crc32_hash).ConfigureAwait(false).GetAwaiter().GetResult(); + int id; + if (ids.Count == 0) + { + Console.WriteLine("ISO not found in redump, cannot automatically retreive key."); + return; + } + else if (ids.Count > 1) + { + // Compute SHA1 hash + byte[] sha1; + using (FileStream fs = File.OpenRead(isoPath)) + { + SHA1 hasher = SHA1.Create(); + sha1 = hasher.ComputeHash(fs); + } + string sha1_hash = Convert.ToHexString(sha1).ToLower(); + + // Search redump.org for SHA1 hash + List ids2 = redump.CheckSingleSitePage("http://redump.org/discs/system/ps3/quicksearch/" + sha1_hash).ConfigureAwait(false).GetAwaiter().GetResult(); + if (ids2.Count == 0) + { + Console.WriteLine("ISO not found in redump, cannot automatically retreive key."); + return; + } + else if (ids2.Count > 1) + { + Console.WriteLine("Cannot automatically get key from redump. Please search redump.org and run again with -k"); + return; + } + id = ids2[0]; + } + else + { + id = ids[0]; + } + + // Download key file from redump.org + byte[] key = redump.GetByteArrayAsync($"http://redump.org/disc/{id}/key").ConfigureAwait(false).GetAwaiter().GetResult(); + if (key.Length != 16) + { + Console.WriteLine("Invalid key obtained from redump"); + } + + // Create IRD with key from redump + Console.WriteLine($"Creating reproducible, redump-style IRD with Key: {Convert.ToHexString(key)}"); + IRD ird = new ReIRD(isoPath, key, opt.Layerbreak); + ird.Write(irdPath ?? Path.GetFileNameWithoutExtension(isoPath) + ".ird"); + ird.Print(); + } } } \ No newline at end of file diff --git a/LibIRD/IRD.cs b/LibIRD/IRD.cs index 1999342..d010891 100644 --- a/LibIRD/IRD.cs +++ b/LibIRD/IRD.cs @@ -6,6 +6,7 @@ using System.IO.Compression; using System.IO.Hashing; using System.Security.Cryptography; using System.Text; +using System.Text.Json; namespace LibIRD { @@ -663,11 +664,11 @@ namespace LibIRD // Parse PARAM.SFO file ParamSFO paramSFO = new(s); // If PS3_DISC.SFB did not set TitleID, use PARAM.SFO TITLE_ID - TitleID ??= paramSFO["TITLE_ID"]; - Title = paramSFO["TITLE"]; + TitleID ??= paramSFO.Field["TITLE_ID"]; + Title = paramSFO.Field["TITLE"]; // If PS3_DISC.SFB did not set DiscVersion, use PARAM.SFO VERSION - DiscVersion ??= paramSFO["VERSION"]; - AppVersion = paramSFO["APP_VER"]; + DiscVersion ??= paramSFO.Field["VERSION"]; + AppVersion = paramSFO.Field["APP_VER"]; } // Determine system update version @@ -1266,38 +1267,93 @@ namespace LibIRD /// /// Prints IRD fields to console /// - public void Print() + /// Optional path to save file to + public void Print(string printPath = null) { // Build string from parameters - StringBuilder print = new(); - print.AppendLine("IRD Contents:"); - print.AppendLine("============="); + StringBuilder printText = new(); + printText.AppendLine("IRD Contents:"); + printText.AppendLine("============="); // Append IRD fields to string builder - print.AppendLine($"Magic: {Encoding.ASCII.GetString(Magic)}"); - print.AppendLine($"IRD Version: {Version}"); - print.AppendLine($"Title ID: {TitleID}"); - print.AppendLine($"Title: {Title}"); - print.AppendLine($"PUP Version: {SystemVersion}"); - print.AppendLine($"Disc Version: {DiscVersion}"); - print.AppendLine($"App Version: {AppVersion}"); - print.AppendLine($"Regions: {RegionCount}"); - print.AppendLine($"Files: {FileCount}"); + printText.AppendLine($"Magic: {Encoding.ASCII.GetString(Magic)}"); + printText.AppendLine($"IRD Version: {Version}"); + printText.AppendLine($"Title ID: {TitleID}"); + printText.AppendLine($"Title: {Title}"); + printText.AppendLine($"PUP Version: {SystemVersion}"); + printText.AppendLine($"Disc Version: {DiscVersion}"); + printText.AppendLine($"App Version: {AppVersion}"); + printText.AppendLine($"Regions: {RegionCount}"); + printText.AppendLine($"Files: {FileCount}"); if (ExtraConfig != 0x0000) - print.AppendLine($"Extra Config: {ExtraConfig:X4}"); + printText.AppendLine($"Extra Config: {ExtraConfig:X4}"); if (Attachments != 0x0000) - print.AppendLine($"Attachments: {Attachments:X4}"); - print.AppendLine($"Unique ID: {UID:X8}"); - print.AppendLine($"Data 1 Key: {Convert.ToHexString(Data1Key)}"); - print.AppendLine($"Data 2 Key: {Convert.ToHexString(Data2Key)}"); - print.AppendLine($"PIC: {Convert.ToHexString(PIC)}"); - print.AppendLine(); + printText.AppendLine($"Attachments: {Attachments:X4}"); + printText.AppendLine($"Unique ID: {UID:X8}"); + printText.AppendLine($"Data 1 Key: {Convert.ToHexString(Data1Key)}"); + printText.AppendLine($"Data 2 Key: {Convert.ToHexString(Data2Key)}"); + printText.AppendLine($"PIC: {Convert.ToHexString(PIC)}"); + printText.AppendLine(); - // Ensure UTF-8 will display properly - Console.OutputEncoding = Encoding.UTF8; + if (printPath == null) + { + // Ensure UTF-8 will display properly + Console.OutputEncoding = Encoding.UTF8; - // Print formatted string - Console.Write(print); + // Print formatted string + Console.Write(printText); + } + else + { + File.WriteAllText(printPath, printText.ToString()); + } + } + + + /// + /// Prints IRD fields to a json object + /// + /// Optionally print to json file + public void PrintJson(string jsonPath = null) + { + // Build string from parameters + StringBuilder json = new(); + + // Append IRD fields to string builder + json.AppendLine("{"); + json.AppendLine($" \"Magic\": \"{Encoding.ASCII.GetString(Magic)}\","); + json.AppendLine($" \"IRD Version\": \"{Version}\","); + json.AppendLine($" \"Title ID\": \"{TitleID}\","); + json.AppendLine($" \"Title\": \"{Title}\","); + json.AppendLine($" \"PUP Version\": \"{SystemVersion}\","); + json.AppendLine($" \"Disc Version\": \"{DiscVersion}\","); + json.AppendLine($" \"App Version\": \"{AppVersion}\","); + json.AppendLine($" \"Regions\": \"{RegionCount}\","); + json.AppendLine($" \"Files\": \"{FileCount}\","); + if (ExtraConfig != 0x0000) + json.AppendLine($" \"Extra Config\": \"{ExtraConfig:X4}\","); + if (Attachments != 0x0000) + json.AppendLine($" \"Attachments\": \"{Attachments:X4}\","); + json.AppendLine($" \"Unique ID\": \"{UID:X8}"); + json.AppendLine($" \"Data 1 Key\": \"{Convert.ToHexString(Data1Key)}\","); + json.AppendLine($" \"Data 2 Key\": \"{Convert.ToHexString(Data2Key)}\","); + json.AppendLine($" \"PIC\": \"{Convert.ToHexString(PIC)}\""); + json.AppendLine("}"); + + // If no path given, output to console + if (jsonPath == null) + { + // Ensure UTF-8 will display properly in console + Console.OutputEncoding = Encoding.UTF8; + + // Print formatted string to console + Console.Write(json); + } + else + { + // Write to path + File.AppendAllText(jsonPath, json.ToString()); + } } #endregion diff --git a/LibIRD/LibIRD.csproj b/LibIRD/LibIRD.csproj index d46aebe..d9f6355 100644 --- a/LibIRD/LibIRD.csproj +++ b/LibIRD/LibIRD.csproj @@ -5,14 +5,15 @@ net6.0;net7.0;net8.0 win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64 latest - 0.1.0 + true + 0.2.0 Deterous Library for ISO Rebuild Data Copyright (c) Deterous 2023 README.md - https://github.com/Deterous/LibIRD + https://github.com/Deterous/LibIRD/ git ps3 iso ird redump GPL-3.0-only diff --git a/LibIRD/PS3_DiscSFB.cs b/LibIRD/PS3_DiscSFB.cs index 223bf57..d86cf66 100644 --- a/LibIRD/PS3_DiscSFB.cs +++ b/LibIRD/PS3_DiscSFB.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Text; +using System.Text.Json; namespace LibIRD { @@ -25,6 +26,7 @@ namespace LibIRD /// /// A field within the PS3_DISC.SFB file /// + /// string Key, string Value public Dictionary Field { get; private set; } /// @@ -35,8 +37,7 @@ namespace LibIRD public PS3_DiscSFB(string sfbPath) { // Validate file path - if (sfbPath == null || sfbPath.Length <= 0) - throw new ArgumentNullException(nameof(sfbPath)); + ArgumentNullException.ThrowIfNull(sfbPath, nameof(sfbPath)); // Read file as a stream, and parse file using FileStream fs = new(sfbPath, FileMode.Open, FileAccess.Read); @@ -47,7 +48,6 @@ namespace LibIRD /// Parse PS3_DISC.SFB from stream /// /// SFB file stream - /// public PS3_DiscSFB(Stream sfbStream) { // Parse file stream @@ -102,27 +102,59 @@ namespace LibIRD /// /// Prints formatted parameters extracted from PS3_DISC.SFB to console /// - public void Print() + /// Optionally print to text file + public void Print(string printPath = null) { // Build string from parameters - StringBuilder print = new(); - print.AppendLine("PS3_DISC.SFB Contents:"); - print.AppendLine("======================"); + StringBuilder printText = new(); + printText.AppendLine("PS3_DISC.SFB Contents:"); + printText.AppendLine("======================"); // Loop through all parameters in PARAM.SFO foreach (KeyValuePair field in Field) + printText.AppendLine(field.Key + ": " + field.Value); + // Blank line + printText.Append(Environment.NewLine); + + // If no path given, print to console + if (printPath == null) { - print.Append(field.Key); - print.Append(": "); - print.AppendLine(field.Value); + // Ensure UTF-8 will display properly in console + Console.OutputEncoding = Encoding.UTF8; + + // Print formatted string to console + Console.Write(printText); } - print.Append(Environment.NewLine); + else + { + // Write data to file + File.AppendAllText(printPath, printText.ToString()); + } + } - // Ensure UTF-8 will display properly - Console.OutputEncoding = Encoding.UTF8; + /// + /// Prints parameters extracted from PS3_DISC.SFB to a json object + /// + /// Optionally print to json file + public void PrintJson(string jsonPath = null) + { + // Serialise PS3_Disc.SFB data to a JSON object + string json = JsonSerializer.Serialize(Field, new JsonSerializerOptions { WriteIndented = true }); - // Print formatted string - Console.Write(print); + // If no path given, output to console + if (jsonPath == null) + { + // Ensure UTF-8 will display properly in console + Console.OutputEncoding = Encoding.UTF8; + + // Print formatted string to console + Console.Write(json); + } + else + { + // Write to path + File.AppendAllText(jsonPath, json); + } } } } diff --git a/LibIRD/ParamSFO.cs b/LibIRD/ParamSFO.cs index 9d45d28..722be2d 100644 --- a/LibIRD/ParamSFO.cs +++ b/LibIRD/ParamSFO.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text; +using System.Text.Json; namespace LibIRD { @@ -22,91 +24,10 @@ namespace LibIRD public uint Version { get; private set; } /// - /// The location of the first byte of the Key Table + /// A field within the PS3_DISC.SFB file /// - public uint KeyTableStart { get; private set; } - - /// - /// The location of the first byte of the Data Table - /// - public uint DataTableStart { get; private set; } - - /// - /// The number of parameters in the table - /// - public uint ParamCount { get; private set; } - - /// - /// Parameter, a single entry in parameter table - /// - public class Param - { - /// - /// Offset of key, relative to KeyTableStart - /// - public ushort KeyOffset { get; internal set; } - - /// - /// Format of parameter - /// - /// 0x0400 is string, 0x0404 is uint - public ushort DataFormat { get; internal set; } - - /// - /// Number of bytes used for parameter - /// - public uint DataLength { get; internal set; } - - /// - /// Total number of bytes for parameter - /// - /// DataTotal - DataLength is padding of 0x00 - public uint DataTotal { get; internal set; } - - /// - /// offset of parameter, relative to DataTableStart - /// - public uint DataOffset { get; internal set; } - - /// - /// The name of the parameter - /// - public string Name { get; internal set; } - - /// - /// The value of the parameter, if it is a string - /// - public string StringValue { get; internal set; } = null; - - /// - /// The value of the parameter, if it is a UInt32 - /// - public int IntValue { get; internal set; } = 0; - } - - /// - /// The parameters in the table - /// - /// Params in the table - public Param[] Params { get; private set; } - - /// - /// String index overloading, gets string value of given key - /// - /// Parameter to be retreived - /// The string value of the given key - public string this[string key] - { - get - { - int index = Array.FindIndex(Params, param => param.Name == key); - if (index == -1) - return null; - if (Params[index].DataFormat == 0x0404) - return Params[index].IntValue.ToString(); - return Params[index].StringValue; - } - } + /// string Key, string Value + public Dictionary Field { get; private set; } /// /// Constructor using a PARAM.SFO file stream @@ -151,95 +72,114 @@ namespace LibIRD // Parse header Version = br.ReadUInt32(); - KeyTableStart = br.ReadUInt32(); - DataTableStart = br.ReadUInt32(); - ParamCount = br.ReadUInt32(); + uint keyTableStart = br.ReadUInt32(); + uint dataTableStart = br.ReadUInt32(); + uint paramCount = br.ReadUInt32(); // Parse parameter metadata - Params = new Param[ParamCount]; - for (int i = 0; i < ParamCount; i++) + ushort[] keyOffset = new ushort[paramCount]; + uint[] dataFormat = new uint[paramCount]; + uint[] dataLength = new uint[paramCount]; + uint[] dataTotal = new uint[paramCount]; + uint[] dataOffset = new uint[paramCount]; + for (int i = 0; i < paramCount; i++) { - Params[i] = new Param - { - KeyOffset = br.ReadUInt16(), - DataFormat = br.ReadUInt16(), - DataLength = br.ReadUInt32(), - DataTotal = br.ReadUInt32(), - DataOffset = br.ReadUInt32() - }; + keyOffset[i] = br.ReadUInt16(); + dataFormat[i] = br.ReadUInt16(); + dataLength[i] = br.ReadUInt32(); + dataTotal[i] = br.ReadUInt32(); + dataOffset[i] = br.ReadUInt32(); } // Parse parameters - for (int i = 0; i < ParamCount; i++) + Field = []; + for (int i = 0; i < paramCount; i++) { // Move stream to ith key - sfoStream.Position = KeyTableStart + Params[i].KeyOffset; + sfoStream.Position = keyTableStart + keyOffset[i]; // Determine ith key length - uint keyLen = ((i == ParamCount - 1) ? DataTableStart - KeyTableStart : Params[i + 1].KeyOffset) - - Params[i].KeyOffset; + uint keyLen = ((i == paramCount - 1) ? dataTableStart - keyTableStart : keyOffset[i + 1]) + - keyOffset[i]; // Read ith key name - Params[i].Name = Encoding.ASCII.GetString(br.ReadBytes((int) keyLen)).TrimEnd('\0'); + string key = Encoding.ASCII.GetString(br.ReadBytes((int) keyLen)).TrimEnd('\0'); // Move stream to ith data - sfoStream.Position = DataTableStart + Params[i].DataOffset; + sfoStream.Position = dataTableStart + dataOffset[i]; // Read ith data, based on data format - switch (Params[i].DataFormat) + Field[key] = dataFormat[i] switch { - case 0x0400: // Non-null-terminated UTF-8 String - Params[i].StringValue = Encoding.UTF8.GetString(br.ReadBytes((int)Params[i].DataLength)); - break; - case 0x0402: // Null-terminated UTF-8 String - Params[i].StringValue = Encoding.UTF8.GetString(br.ReadBytes((int)Params[i].DataLength)).TrimEnd('\0'); - break; - case 0x0404: // Integer - //if (Params[i].DataLength != 4) - //throw new ArgumentException("Integer parameter not 4 bytes?"); - Params[i].IntValue = br.ReadInt32(); - break; - default: // Unknown data format, assume null-terminated string - Params[i].StringValue = Encoding.UTF8.GetString(br.ReadBytes((int)Params[i].DataLength)).TrimEnd('\0'); - break; - } + // Non-null-terminated UTF-8 String + 0x0004 => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])), + // Null-terminated UTF-8 String + 0x0204 => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])).TrimEnd('\0'), + // Integer + 0x0404 => br.ReadInt32().ToString(), + // Unknown data format, assume null-terminated string + _ => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])).TrimEnd('\0'), + }; } } /// /// Prints formatted parameters extracted from PARAM.SFO to console /// - public void Print() + /// Optionally print to text file + public void Print(string printPath = null) { // Build string from parameters - StringBuilder print = new(); - print.AppendLine("PARAM.SFO Contents:"); - print.AppendLine("===================="); + StringBuilder printText = new(); + printText.AppendLine("PARAM.SFO Contents:"); + printText.AppendLine("===================="); // Loop through all parameters in PARAM.SFO - for (int i = 0; i < ParamCount; i++) + foreach (KeyValuePair field in Field) + printText.AppendLine(field.Key + ": " + field.Value); + // Blank line + printText.Append(Environment.NewLine); + + // If no path given, print to console + if (printPath == null) { - print.Append(Params[i].Name); - print.Append(' '); - for (int j = Params[i].Name.Length; j < 20; j++) - print.Append(' '); - switch (Params[i].DataFormat) - { - case 0x0404: - print.Append(Params[i].IntValue +Environment.NewLine); - break; - default: - print.AppendLine(Params[i].StringValue); - break; - } + // Ensure UTF-8 will display properly in console + Console.OutputEncoding = Encoding.UTF8; + + // Print formatted string to console + Console.Write(printText); } - print.Append(Environment.NewLine); + else + { + // Write data to file + File.AppendAllText(printPath, printText.ToString()); + } + } - // Ensure UTF-8 will display properly - Console.OutputEncoding = Encoding.UTF8; + /// + /// Prints parameters extracted from PARAM.SFO to a json object + /// + /// Optionally print to json file + public void PrintJson(string jsonPath = null) + { + // Serialise PS3_Disc.SFB data to a JSON object + string json = JsonSerializer.Serialize(Field, new JsonSerializerOptions { WriteIndented = true }); + + // If no path given, output to console + if (jsonPath == null) + { + // Ensure UTF-8 will display properly in console + Console.OutputEncoding = Encoding.UTF8; + + // Print formatted string to console + Console.Write(json); + } + else + { + // Write to path + File.AppendAllText(jsonPath, json); + } - // Print formatted string - Console.Write(print); } } } diff --git a/LibIRD/ReIRD.cs b/LibIRD/ReIRD.cs index 56821c2..a6d2b8c 100644 --- a/LibIRD/ReIRD.cs +++ b/LibIRD/ReIRD.cs @@ -67,10 +67,9 @@ namespace LibIRD /// /// Path to the ISO /// Path to the GetKey log file - /// - /// + /// Layerbreak value, in sectors /// - public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog, true) + public ReIRD(string isoPath, string getKeyLog, long? layerbreak = null) : base(isoPath, getKeyLog, true) { // Generate Unique Identifier using ISO CRC32 UID = GenerateUID(isoPath); @@ -80,12 +79,10 @@ namespace LibIRD // Generate Data 2 using Disc ID DiscID = GenerateID(Size); - // Check that GetKey log matches expected Disc ID - //if (!((ReadOnlySpan)Data2Key).SequenceEqual(d2)) - // throw new InvalidDataException("Unexpected Disc ID in .getkey.log"); // Generate Disc PIC - byte[] pic = GeneratePIC(Size); + byte[] pic = GeneratePIC(Size, layerbreak * SectorSize); + // Check that GetKey log matches expected PIC if (!((ReadOnlySpan)PIC).SequenceEqual(pic)) throw new InvalidDataException("Unexpected PIC in .getkey.log"); @@ -96,10 +93,9 @@ namespace LibIRD /// /// Path to the ISO /// Disc Key, redump-style (AES encrypted Data 1) + /// Layerbreak value, in sectors /// Disc Region - /// - /// - public ReIRD(string isoPath, byte[] key, Region region = Region.NONE) + public ReIRD(string isoPath, byte[] key, long? layerbreak = null, Region region = Region.NONE) { // Generate Unique Identifier using ISO CRC32 UID = GenerateUID(isoPath); @@ -114,7 +110,7 @@ namespace LibIRD DiscID = GenerateID(Size, region); // Generate Disc PIC - PIC = GeneratePIC(Size); + PIC = GeneratePIC(Size, layerbreak * SectorSize); // Generate IRD fields GenerateIRD(isoPath, true); @@ -144,7 +140,7 @@ namespace LibIRD /// Generates the PIC data for a given ISO size in bytes /// /// Total ISO size in number of bytes - /// Layer break value, byte at which disc layers are split across + /// Layer break value, byte at which disc layers are split across /// True to generate a PIC in 3k3y style (0x03 at 115th byte for BD-50 discs) /// private static byte[] GeneratePIC(long size, long? layerbreak = null, bool exactIRD = false) @@ -171,20 +167,20 @@ namespace LibIRD long l0_start_sector = 1048576; // Layer 0 end sector = start sector + layerbreak - 2 - long l0_end_sector = layer_break + l0_start_sector - 2; + long l0_end_sector = (layer_break / SectorSize) + l0_start_sector - 2; // Convert end sector location to hex values for PIC byte[] l0es = [(byte)((l0_end_sector >> 24) & 0xFF), - (byte)((l0_end_sector >> 16) & 0xFF), - (byte)((l0_end_sector >> 8) & 0xFF), - (byte)((l0_end_sector >> 0) & 0xFF)]; + (byte)((l0_end_sector >> 16) & 0xFF), + (byte)((l0_end_sector >> 8) & 0xFF), + (byte)((l0_end_sector >> 0) & 0xFF)]; // Layer 1 start sector = end of disc (0x01EFFFFE) - layerbreak + 2 - long l1_start_sector = 32505854 - layer_break + 2; + long l1_start_sector = 32505854 - (layer_break / SectorSize) + 2; // Convert start of start sector location to hex values for PIC byte[] l1ss = [(byte)((l1_start_sector >> 24) & 0xFF), - (byte)((l1_start_sector >> 16) & 0xFF), - (byte)((l1_start_sector >> 8) & 0xFF), - (byte)((l1_start_sector >> 0) & 0xFF)]; + (byte)((l1_start_sector >> 16) & 0xFF), + (byte)((l1_start_sector >> 8) & 0xFF), + (byte)((l1_start_sector >> 0) & 0xFF)]; // Total sectors used = num_sectors + Layer 0 start + sectors_between_layers (usually 0x01358C00 - 0x00CA73FE - 3) long total_sectors = (size / SectorSize) + l0_start_sector + (l1_start_sector - l0_end_sector - 3); @@ -262,8 +258,7 @@ namespace LibIRD private static uint GenerateUID(string isoPath) { // Validate ISO path - if (isoPath == null || isoPath.Length <= 0) - throw new ArgumentNullException(nameof(isoPath)); + ArgumentNullException.ThrowIfNull(isoPath, nameof(isoPath)); // Check file exists var iso = new FileInfo(isoPath); @@ -292,8 +287,7 @@ namespace LibIRD private static long CalculateSize(string isoPath) { // Validate ISO path - if (isoPath == null || isoPath.Length <= 0) - throw new ArgumentNullException(nameof(isoPath)); + ArgumentNullException.ThrowIfNull(isoPath, nameof(isoPath)); // Check file exists var iso = new FileInfo(isoPath); diff --git a/README.md b/README.md index 34ed42d..2a8ec42 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,7 @@ For detailed usage, read more [here](IRDKit). ## Using the LibIRD library LibIRD was originally made for creating reproducible, redump-style IRDs when dumping PS3 discs with [MPF](https://github.com/SabreTools/MPF). If you wish to integrate LibIRD into your own application, read the examples [here](LibIRD). + +## Limitations + +Currently, LibIRD does not produce correct IRDs for ISOs with non-contiguous files. Therefore, do not consider this library "stable" until v1.0.0 is released. \ No newline at end of file