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