10 Commits
Author SHA1 Message Date
Deterous 4b0d796bd2 Refactor, IRDKit can take directories 2023-11-30 11:05:22 +13:00
Deterous 11ed114507 Hash ISO for searching redump 2023-11-25 03:11:24 +13:00
Deterous 357a8df11b IRDKit minimum functionality 2023-11-25 02:39:47 +13:00
Deterous 652cd4aa52 Generate PIC for hybrid discs using layerbreak 2023-11-25 02:08:29 +13:00
Deterous 43e8517bf0 Refactor for .NET8 2023-11-24 21:46:16 +13:00
Deterous f86a89c04a Move printing into IRDKit 2023-11-16 11:58:27 +13:00
Deterous e665391e47 Start work on CLI tool 2023-11-16 11:45:21 +13:00
Deterous b794ec8f96 Parse PS3_DISC.SFB for redump-style IRDs 2023-11-11 19:50:50 +13:00
Deterous 5a9cce39ae Update README 2023-11-10 22:16:44 +13:00
Deterous 2fcf5fcae3 Print IRD fields, add a READEME 2023-11-10 22:09:00 +13:00
15 changed files with 951 additions and 389 deletions
-60
View File
@@ -1,60 +0,0 @@
using LibIRD;
using System;
using System.IO;
using System.Text;
namespace BuildIRD
{
internal class Program
{
static void Main()
{
Console.OutputEncoding = Encoding.UTF8;
// Create new reproducible redump-style IRD with a key file
try
{
// Read key from .key file
byte[] discKey = File.ReadAllBytes("./game.key");
IRD ird1 = new ReIRD("./game.iso", discKey);
ird1.Write("./test1.ird");
Console.WriteLine("IRD created using .key:");
// Read IRD and print details to console
IRD ird = IRD.Read("./test1.ird");
Console.WriteLine("IRD Version: " + ird.Version);
Console.WriteLine("Title ID: " + ird.TitleID);
Console.WriteLine("Title: " + ird.Title);
Console.WriteLine("System Version: " + ird.SystemVersion);
Console.WriteLine("Game Version: " + ird.GameVersion);
Console.WriteLine("App Version: " + ird.AppVersion);
}
catch (FileNotFoundException e)
{
Console.WriteLine("File not found: " + e.FileName);
}
// Create new reproducible redump-style IRD with a GetKey log
try
{
IRD ird2 = new ReIRD("./game.iso", "./log.getkey.log");
ird2.Write("./test2.ird");
Console.WriteLine("IRD created using .getkey.log:");
// Read IRD and print details to console
IRD ird = IRD.Read("./test2.ird");
Console.WriteLine("IRD Version: " + ird.Version);
Console.WriteLine("Title ID: " + ird.TitleID);
Console.WriteLine("Title: " + ird.Title);
Console.WriteLine("System Version: " + ird.SystemVersion);
Console.WriteLine("Game Version: " + ird.GameVersion);
Console.WriteLine("App Version: " + ird.AppVersion);
}
catch (FileNotFoundException e)
{
Console.WriteLine("File not found: " + e.FileName);
}
}
}
}
-14
View File
@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Version>0.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LibIRD\LibIRD.csproj" />
</ItemGroup>
</Project>
+38
View File
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<OutputType>Exe</OutputType>
<PackAsTool>true</PackAsTool>
<ToolCommandName>irdkit</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.2.0</Version>
<!-- Package Properties -->
<Authors>Deterous</Authors>
<Description>Library for ISO Rebuild Data</Description>
<Copyright>Copyright (c) Deterous 2023</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Deterous/LibIRD</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>ps3 iso ird redump</PackageTags>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="./README.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LibIRD\LibIRD.csproj" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="DiscUtils.Core" Version="0.16.13" />
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.1" />
</ItemGroup>
</Project>
+323
View File
@@ -0,0 +1,323 @@
using CommandLine;
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
{
/// <summary>
/// IRD Creation Verb
/// </summary>
[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, Required = false, HelpText = "Path to the IRD file to be created")]
public string IRDPath { 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('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; }
}
/// <summary>
/// IRD or ISO information verb
/// </summary>
[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 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; }
}
/// <summary>
/// Parse command line arguments
/// </summary>
/// <param name="args">Command line arguments</param>
public static void Main(string[] args)
{
var result = Parser.Default.ParseArguments<CreateOptions, InfoOptions>(args).WithParsed(Run);
}
/// <summary>
/// Perform
/// </summary>
/// <param name="obj"></param>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidFileSystemException"></exception>
private static void Run(object obj)
{
switch (obj)
{
case CreateOptions opt:
Console.OutputEncoding = Encoding.UTF8;
// Validate ISO path
ArgumentNullException.ThrowIfNull(opt.ISOPath);
// If directory, search for all ISOs in current directory
if (Directory.Exists(opt.ISOPath))
{
// If recurse option enabled, search recursively
IEnumerable<string> 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;
}
// Create a single IRD from an ISO
if (File.Exists(opt.ISOPath))
{
ProcessISO(opt, opt.ISOPath, opt.IRDPath);
break;
}
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(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);
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);
if (opt.Json)
{
File.AppendAllText(opt.OutPath, "\n\"PARAM.SFO\": ");
paramSFO.PrintJson(opt.OutPath);
}
else
paramSFO.Print(opt.OutPath);
}
catch
{
Console.WriteLine("PS3_GAME\\PARAM.SFO not found");
}
}
File.AppendAllText(opt.OutPath, "\n}");
}
else
{
// 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<int> 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<int> 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();
}
}
}
+7
View File
@@ -0,0 +1,7 @@
## How to use IRDKit
IRDKit is a tool that allows direct use of LibIRD functionality from the command line interface. The basic usage is:
```
irdkit game.iso game.ird
```
For detailed usage, run `irdkit --help`
+5 -11
View File
@@ -5,9 +5,7 @@ VisualStudioVersion = 17.7.34202.233
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibIRD", "LibIRD\LibIRD.csproj", "{4A80C34B-D4C5-4536-BEAE-46218BC09980}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibIRD", "LibIRD\LibIRD.csproj", "{4A80C34B-D4C5-4536-BEAE-46218BC09980}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildIRD", "BuildIRD\BuildIRD.csproj", "{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRDKit", "IRDKit\IRDKit.csproj", "{9060E3AF-E4FB-436F-93A1-5C47389CB88E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintParams", "PrintParams\PrintParams.csproj", "{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -19,14 +17,10 @@ Global
{4A80C34B-D4C5-4536-BEAE-46218BC09980}.Debug|Any CPU.Build.0 = Debug|Any CPU {4A80C34B-D4C5-4536-BEAE-46218BC09980}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A80C34B-D4C5-4536-BEAE-46218BC09980}.Release|Any CPU.ActiveCfg = Release|Any CPU {4A80C34B-D4C5-4536-BEAE-46218BC09980}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A80C34B-D4C5-4536-BEAE-46218BC09980}.Release|Any CPU.Build.0 = Release|Any CPU {4A80C34B-D4C5-4536-BEAE-46218BC09980}.Release|Any CPU.Build.0 = Release|Any CPU
{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Debug|Any CPU.Build.0 = Debug|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Release|Any CPU.ActiveCfg = Release|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Release|Any CPU.Build.0 = Release|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.Build.0 = Release|Any CPU
{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
+157 -43
View File
@@ -6,6 +6,7 @@ using System.IO.Compression;
using System.IO.Hashing; using System.IO.Hashing;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json;
namespace LibIRD namespace LibIRD
{ {
@@ -33,32 +34,32 @@ namespace LibIRD
/// IRD file signature /// IRD file signature
/// </summary> /// </summary>
/// <remarks>"3IRD"</remarks> /// <remarks>"3IRD"</remarks>
private static readonly byte[] Magic = { 0x33, 0x49, 0x52, 0x44 }; private static readonly byte[] Magic = [0x33, 0x49, 0x52, 0x44];
/// <summary> /// <summary>
/// MD5 hash of null /// MD5 hash of null
/// </summary> /// </summary>
private static readonly byte[] NullMD5 = new byte[] { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e }; private static readonly byte[] NullMD5 = [0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e];
/// <summary> /// <summary>
/// AES CBC Encryption Key for Data 1 (Disc Key) /// AES CBC Encryption Key for Data 1 (Disc Key)
/// </summary> /// </summary>
private static readonly byte[] D1AesKey = { 0x38, 0x0B, 0xCF, 0x0B, 0x53, 0x45, 0x5B, 0x3C, 0x78, 0x17, 0xAB, 0x4F, 0xA3, 0xBA, 0x90, 0xED }; private static readonly byte[] D1AesKey = [0x38, 0x0B, 0xCF, 0x0B, 0x53, 0x45, 0x5B, 0x3C, 0x78, 0x17, 0xAB, 0x4F, 0xA3, 0xBA, 0x90, 0xED];
/// <summary> /// <summary>
/// AES CBC Initial Value for Data 1 (Disc Key) /// AES CBC Initial Value for Data 1 (Disc Key)
/// </summary> /// </summary>
private static readonly byte[] D1AesIV = { 0x69, 0x47, 0x47, 0x72, 0xAF, 0x6F, 0xDA, 0xB3, 0x42, 0x74, 0x3A, 0xEF, 0xAA, 0x18, 0x62, 0x87 }; private static readonly byte[] D1AesIV = [0x69, 0x47, 0x47, 0x72, 0xAF, 0x6F, 0xDA, 0xB3, 0x42, 0x74, 0x3A, 0xEF, 0xAA, 0x18, 0x62, 0x87];
/// <summary> /// <summary>
/// AES CBC Encryption Key for Data 2 (Disc ID) /// AES CBC Encryption Key for Data 2 (Disc ID)
/// </summary> /// </summary>
private static readonly byte[] D2AesKey = { 0x7C, 0xDD, 0x0E, 0x02, 0x07, 0x6E, 0xFE, 0x45, 0x99, 0xB1, 0xB8, 0x2C, 0x35, 0x99, 0x19, 0xB3 }; private static readonly byte[] D2AesKey = [0x7C, 0xDD, 0x0E, 0x02, 0x07, 0x6E, 0xFE, 0x45, 0x99, 0xB1, 0xB8, 0x2C, 0x35, 0x99, 0x19, 0xB3];
/// <summary> /// <summary>
/// AES CBC Initial Value for Data 2 (Disc ID) /// AES CBC Initial Value for Data 2 (Disc ID)
/// </summary> /// </summary>
private static readonly byte[] D2AesIV = { 0x22, 0x26, 0x92, 0x8D, 0x44, 0x03, 0x2F, 0x43, 0x6A, 0xFD, 0x26, 0x7E, 0x74, 0x8B, 0x23, 0x93 }; private static readonly byte[] D2AesIV = [0x22, 0x26, 0x92, 0x8D, 0x44, 0x03, 0x2F, 0x43, 0x6A, 0xFD, 0x26, 0x7E, 0x74, 0x8B, 0x23, 0x93];
#endregion #endregion
@@ -88,7 +89,7 @@ namespace LibIRD
/// <summary> /// <summary>
/// Extra Config /// Extra Config
/// </summary> /// </summary>
/// <remarks>Reserved, usually set to 0x0000</remarks> /// <remarks>Usually set to 0x0000, set to 0x0001 for redump-style IRDs</remarks>
public ushort ExtraConfig { get; set; } = 0x0000; // Default to zero public ushort ExtraConfig { get; set; } = 0x0000; // Default to zero
/// <summary> /// <summary>
@@ -217,7 +218,7 @@ namespace LibIRD
/// The same value stored in PARAM.SFO / VERSION /// The same value stored in PARAM.SFO / VERSION
/// </summary> /// </summary>
/// <remarks>5 bytes, ASCII, e.g. "01.20"</remarks> /// <remarks>5 bytes, ASCII, e.g. "01.20"</remarks>
public string GameVersion { get; private set; } public string DiscVersion { get; private set; }
/// <summary> /// <summary>
/// The same value stored in PARAM.SFO / APP_VER /// The same value stored in PARAM.SFO / APP_VER
@@ -312,7 +313,7 @@ namespace LibIRD
string titleID, string titleID,
string title, string title,
string sysVersion, string sysVersion,
string gameVersion, string discVersion,
string appVersion, string appVersion,
byte[] header, byte[] header,
byte[] footer, byte[] footer,
@@ -329,7 +330,7 @@ namespace LibIRD
TitleID = titleID; TitleID = titleID;
Title = title; Title = title;
SystemVersion = sysVersion; SystemVersion = sysVersion;
GameVersion = gameVersion; DiscVersion = discVersion;
AppVersion = appVersion; AppVersion = appVersion;
HeaderLength = (uint)header.Length; HeaderLength = (uint)header.Length;
Header = header; Header = header;
@@ -364,7 +365,8 @@ namespace LibIRD
/// <param name="discKey">Disc Key, byte array of length 16</param> /// <param name="discKey">Disc Key, byte array of length 16</param>
/// <param name="discID">Disc ID, byte array of length 16</param> /// <param name="discID">Disc ID, byte array of length 16</param>
/// <param name="discPIC">Disc PIC, byte array of length 115</param> /// <param name="discPIC">Disc PIC, byte array of length 115</param>
public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC) /// <param name="redump">True if redump-style IRD</param>
public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC, bool redump = false)
{ {
// Parse ISO, Disc Key, Disc ID, and PIC // Parse ISO, Disc Key, Disc ID, and PIC
DiscKey = discKey; DiscKey = discKey;
@@ -373,7 +375,7 @@ namespace LibIRD
PIC = discPIC; PIC = discPIC;
// Generate IRD files from ISO // Generate IRD files from ISO
GenerateIRD(isoPath); GenerateIRD(isoPath, redump);
} }
/// <summary> /// <summary>
@@ -381,15 +383,14 @@ namespace LibIRD
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <param name="getKeyLog">Path to the .getkey.log file</param> /// <param name="getKeyLog">Path to the .getkey.log file</param>
/// <exception cref="ArgumentNullException"></exception> /// <param name="redump">True if redump-style IRD</param>
/// <exception cref="InvalidDataException"></exception> public IRD(string isoPath, string getKeyLog, bool redump = false)
public IRD(string isoPath, string getKeyLog)
{ {
// Parse .getkey.log file // Parse .getkey.log file
ParseGetKeyLog(getKeyLog); ParseGetKeyLog(getKeyLog);
// Generate IRD files from ISO // Generate IRD files from ISO
GenerateIRD(isoPath); GenerateIRD(isoPath, redump);
} }
#endregion #endregion
@@ -406,8 +407,7 @@ namespace LibIRD
private protected static byte[] GenerateD1(byte[] key) private protected static byte[] GenerateD1(byte[] key)
{ {
// Validate key // Validate key
if (key == null) ArgumentNullException.ThrowIfNull(key, nameof(key));
throw new ArgumentNullException(nameof(key));
if (key.Length != 16) if (key.Length != 16)
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(key)); throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(key));
@@ -441,8 +441,7 @@ namespace LibIRD
private protected static byte[] GenerateDiscKey(byte[] d1) private protected static byte[] GenerateDiscKey(byte[] d1)
{ {
// Validate key // Validate key
if (d1 == null) ArgumentNullException.ThrowIfNull(d1, nameof(d1));
throw new ArgumentNullException(nameof(d1));
if (d1.Length != 16) if (d1.Length != 16)
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(d1)); throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(d1));
@@ -476,10 +475,8 @@ namespace LibIRD
private protected static byte[] GenerateD2(byte[] d2) private protected static byte[] GenerateD2(byte[] d2)
{ {
// Validate id // Validate id
if (d2 == null) ArgumentNullException.ThrowIfNull(d2, nameof(d2));
throw new ArgumentNullException(nameof(d2)); if (d2.Length != 16) throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
if (d2.Length != 16)
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
// Setup AES encryption // Setup AES encryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings"); using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
@@ -511,8 +508,7 @@ namespace LibIRD
private protected static byte[] GenerateDiscID(byte[] d2) private protected static byte[] GenerateDiscID(byte[] d2)
{ {
// Validate id // Validate id
if (d2 == null) ArgumentNullException.ThrowIfNull(d2 , nameof(d2));
throw new ArgumentNullException(nameof(d2));
if (d2.Length != 16) if (d2.Length != 16)
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2)); throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
@@ -547,8 +543,7 @@ namespace LibIRD
{ {
// Validate .getkey.log file path // Validate .getkey.log file path
if (getKeyLog == null) ArgumentNullException.ThrowIfNull(getKeyLog, nameof(getKeyLog));
throw new ArgumentNullException(nameof(getKeyLog));
if (!File.Exists(getKeyLog)) if (!File.Exists(getKeyLog))
throw new FileNotFoundException(nameof(getKeyLog)); throw new FileNotFoundException(nameof(getKeyLog));
@@ -624,10 +619,11 @@ namespace LibIRD
/// Constructor for generating values from an ISO file /// Constructor for generating values from an ISO file
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <param name="redump">True if redump-style IRD</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception> /// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidFileSystemException"></exception> /// <exception cref="InvalidFileSystemException"></exception>
private protected void GenerateIRD(string isoPath) private protected void GenerateIRD(string isoPath, bool redump = false)
{ {
// Parse ISO file as a file stream // Parse ISO file as a file stream
using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath); using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath);
@@ -638,16 +634,41 @@ namespace LibIRD
// New ISO Reader from DiscUtils // New ISO Reader from DiscUtils
CDReader reader = new(fs, true, true); CDReader reader = new(fs, true, true);
// If generating redump-style IRD
if (redump)
{
// Redump-style IRDs set the lowest bit of ExtraConfig to 1
ExtraConfig |= 0x01;
// Redump-style IRDs use fields from PS3_DISC.SFB
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);
bool title_id_found = ps3_DiscSFB.Field.TryGetValue("TITLE_ID", out string title_id);
// If a valid TITLE_ID field is present, remove the hyphen to fit into standard IRD file
if (title_id_found && title_id.Length == 10 && title_id[4] == '-')
TitleID = string.Concat(title_id.AsSpan(0, 4), title_id.AsSpan(5, 5));
// 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
bool version_found = ps3_DiscSFB.Field.TryGetValue("VERSION", out string disc_version);
if (version_found)
DiscVersion = disc_version;
}
// Read PS3 Metadata from PARAM.SFO // Read PS3 Metadata from PARAM.SFO
using (DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_GAME\\PARAM.SFO", FileMode.Open, FileAccess.Read)) using (DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_GAME\\PARAM.SFO", FileMode.Open, FileAccess.Read))
{ {
// Parse PARAM.SFO file // Parse PARAM.SFO file
ParamSFO paramSFO = new(s); ParamSFO paramSFO = new(s);
// Store required values for IRD // If PS3_DISC.SFB did not set TitleID, use PARAM.SFO TITLE_ID
TitleID = paramSFO["TITLE_ID"]; TitleID ??= paramSFO.Field["TITLE_ID"];
Title = paramSFO["TITLE"]; Title = paramSFO.Field["TITLE"];
GameVersion = paramSFO["VERSION"]; // If PS3_DISC.SFB did not set DiscVersion, use PARAM.SFO VERSION
AppVersion = paramSFO["APP_VER"]; DiscVersion ??= paramSFO.Field["VERSION"];
AppVersion = paramSFO.Field["APP_VER"];
} }
// Determine system update version // Determine system update version
@@ -674,7 +695,7 @@ namespace LibIRD
// Determine file offsets and hashes // Determine file offsets and hashes
uint fileCount = FileCount; uint fileCount = FileCount;
FileCount = 0; FileCount = 0;
ProcessFiles(fs, reader, rootDir); HashFiles(fs, reader, rootDir);
if (FileCount != fileCount) if (FileCount != fileCount)
throw new InvalidFileSystemException("Unexpected ISO filesystem error: "); throw new InvalidFileSystemException("Unexpected ISO filesystem error: ");
Array.Sort(FileKeys, FileHashes); Array.Sort(FileKeys, FileHashes);
@@ -891,12 +912,13 @@ namespace LibIRD
/// </summary> /// </summary>
/// <param name="reader"></param> /// <param name="reader"></param>
/// <param name="path"></param> /// <param name="path"></param>
private void ProcessFiles(FileStream fs, CDReader reader, DiscDirectoryInfo dir) private void HashFiles(FileStream fs, CDReader reader, DiscDirectoryInfo dir)
{ {
// Process all files in current directory // Process all files in current directory
foreach (DiscFileInfo fileInfo in dir.GetFiles()) foreach (DiscFileInfo fileInfo in dir.GetFiles())
{ {
string filePath = fileInfo.FullName; string filePath = fileInfo.FullName;
// Try get the first sector from the file extents instead // Try get the first sector from the file extents instead
DiscUtils.Streams.StreamExtent[] fileExtents = reader.PathToExtents(filePath); DiscUtils.Streams.StreamExtent[] fileExtents = reader.PathToExtents(filePath);
if (fileExtents == null || fileExtents.Length <= 0) if (fileExtents == null || fileExtents.Length <= 0)
@@ -960,7 +982,7 @@ namespace LibIRD
// Recursively process all subfolders of current directory // Recursively process all subfolders of current directory
foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories()) foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories())
{ {
ProcessFiles(fs, reader, dirInfo); HashFiles(fs, reader, dirInfo);
} }
} }
@@ -1049,10 +1071,10 @@ namespace LibIRD
bw.Write(systemVersionBuf, 0, 4); bw.Write(systemVersionBuf, 0, 4);
// PARAM.SFO / VERSION // PARAM.SFO / VERSION
byte[] buf = Encoding.ASCII.GetBytes(GameVersion); byte[] buf = Encoding.ASCII.GetBytes(DiscVersion);
byte[] gameVersionBuf = new byte[5]; byte[] discVersionBuf = new byte[5];
Array.Copy(buf, 0, gameVersionBuf, 0, buf.Length); Array.Copy(buf, 0, discVersionBuf, 0, buf.Length);
bw.Write(gameVersionBuf, 0, 5); bw.Write(discVersionBuf, 0, 5);
// PARAM.SFO / APP_VER // PARAM.SFO / APP_VER
buf = Encoding.ASCII.GetBytes(AppVersion); buf = Encoding.ASCII.GetBytes(AppVersion);
@@ -1166,7 +1188,7 @@ namespace LibIRD
// Read System, Game, and App Version // Read System, Game, and App Version
string sysVersion = Encoding.ASCII.GetString(br.ReadBytes(4)); string sysVersion = Encoding.ASCII.GetString(br.ReadBytes(4));
string gameVersion = Encoding.ASCII.GetString(br.ReadBytes(5)); string discVersion = Encoding.ASCII.GetString(br.ReadBytes(5));
string appVersion = Encoding.ASCII.GetString(br.ReadBytes(5)); string appVersion = Encoding.ASCII.GetString(br.ReadBytes(5));
// Read UID (for Version 7) // Read UID (for Version 7)
@@ -1227,7 +1249,7 @@ namespace LibIRD
titleID, titleID,
title, title,
sysVersion, sysVersion,
gameVersion, discVersion,
appVersion, appVersion,
header, header,
footer, footer,
@@ -1242,6 +1264,98 @@ namespace LibIRD
uid); uid);
} }
/// <summary>
/// Prints IRD fields to console
/// </summary>
/// <param name="printPath">Optional path to save file to</param>
public void Print(string printPath = null)
{
// Build string from parameters
StringBuilder printText = new();
printText.AppendLine("IRD Contents:");
printText.AppendLine("=============");
// Append IRD fields to string builder
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)
printText.AppendLine($"Extra Config: {ExtraConfig:X4}");
if (Attachments != 0x0000)
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();
if (printPath == null)
{
// Ensure UTF-8 will display properly
Console.OutputEncoding = Encoding.UTF8;
// Print formatted string
Console.Write(printText);
}
else
{
File.WriteAllText(printPath, printText.ToString());
}
}
/// <summary>
/// Prints IRD fields to a json object
/// </summary>
/// <param name="jsonPath">Optionally print to json file</param>
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 #endregion
} }
} }
+13 -6
View File
@@ -2,25 +2,32 @@
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net6.0;net7.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<Version>0.1.0</Version> <LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.2.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Deterous</Authors> <Authors>Deterous</Authors>
<Description>Library for ISO Rebuild Data</Description> <Description>Library for ISO Rebuild Data</Description>
<Copyright>Copyright (c)2023 Deterous</Copyright> <Copyright>Copyright (c) Deterous 2023</Copyright>
<RepositoryUrl>https://github.com/Deterous/LibIRD</RepositoryUrl> <PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Deterous/LibIRD/</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageTags>ps3 iso ird</PackageTags> <PackageTags>ps3 iso ird redump</PackageTags>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="./README.md" Pack="true" PackagePath=""/>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DiscUtils.Core" Version="0.16.13" /> <PackageReference Include="DiscUtils.Core" Version="0.16.13" />
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" /> <PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
<PackageReference Include="DiscUtils.Streams" Version="0.16.13" /> <PackageReference Include="DiscUtils.Streams" Version="0.16.13" />
<PackageReference Include="System.IO.Hashing" Version="7.0.0" /> <PackageReference Include="System.IO.Hashing" Version="8.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+160
View File
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
namespace LibIRD
{
/// <summary>
/// PS3_DISC.SFB file parsing
/// </summary>
public class PS3_DiscSFB
{
/// <summary>
/// PS3_DISC.SFB file signature
/// </summary>
/// <remarks>{ 0x2E, 0x53, 0x46, 0x42 }</remarks>
public static readonly string Magic = ".SFB";
/// <summary>
/// PS3_DISC.SFB file version
/// </summary>
/// <remarks>Typically v1, { 0x00, 0x01 }</remarks>
public ushort Version { get; private set; }
/// <summary>
/// A field within the PS3_DISC.SFB file
/// </summary>
/// <remarks>string Key, string Value</remarks>
public Dictionary<string, string> Field { get; private set; }
/// <summary>
/// Constructor using a PARAM.SFO file path
/// </summary>
/// <param name="sfbPath">Full file path to the PS3_DISC.SFB file</param>
/// <exception cref="ArgumentNullException"></exception>
public PS3_DiscSFB(string sfbPath)
{
// Validate file path
ArgumentNullException.ThrowIfNull(sfbPath, nameof(sfbPath));
// Read file as a stream, and parse file
using FileStream fs = new(sfbPath, FileMode.Open, FileAccess.Read);
Parse(fs);
}
/// <summary>
/// Parse PS3_DISC.SFB from stream
/// </summary>
/// <param name="sfbStream">SFB file stream</param>
public PS3_DiscSFB(Stream sfbStream)
{
// Parse file stream
Parse(sfbStream);
}
/// <summary>
/// Read fields from PS3_DISC.SFB
/// </summary>
/// <param name="sfbStream">File stream for PS3_DISC.SFB</param>
/// <exception cref="FileLoadException"></exception>
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 = [];
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');
}
}
/// <summary>
/// Prints formatted parameters extracted from PS3_DISC.SFB to console
/// </summary>
/// <param name="printPath">Optionally print to text file</param>
public void Print(string printPath = null)
{
// Build string from parameters
StringBuilder printText = new();
printText.AppendLine("PS3_DISC.SFB Contents:");
printText.AppendLine("======================");
// Loop through all parameters in PARAM.SFO
foreach (KeyValuePair<string, string> 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)
{
// Ensure UTF-8 will display properly in console
Console.OutputEncoding = Encoding.UTF8;
// Print formatted string to console
Console.Write(printText);
}
else
{
// Write data to file
File.AppendAllText(printPath, printText.ToString());
}
}
/// <summary>
/// Prints parameters extracted from PS3_DISC.SFB to a json object
/// </summary>
/// <param name="jsonPath">Optionally print to json file</param>
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);
}
}
}
}
+86 -140
View File
@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.Json;
namespace LibIRD namespace LibIRD
{ {
@@ -18,91 +20,14 @@ namespace LibIRD
/// <summary> /// <summary>
/// PARAM.SFO file version /// PARAM.SFO file version
/// </summary> /// </summary>
/// <remarks>Typically { 0x01 0x01 0x00 0x00 } (v1.1)</remarks> /// <remarks>Typically { 0x01, 0x01, 0x00, 0x00 } (v1.1)</remarks>
public uint Version { get; private set; } public uint Version { get; private set; }
/// <summary> /// <summary>
/// The location of the first byte of the Key Table /// A field within the PS3_DISC.SFB file
/// </summary> /// </summary>
public uint KeyTableStart { get; private set; } /// <remarks>string Key, string Value</remarks>
public Dictionary<string, string> Field { get; private set; }
/// <summary>
/// The location of the first byte of the Data Table
/// </summary>
public uint DataTableStart { get; private set; }
/// <summary>
/// The number of parameters in the table
/// </summary>
public uint ParamCount { get; private set; }
/// <summary>
/// Parameter, a single entry in parameter table
/// </summary>
public class Param
{
/// <summary>
/// Offset of key, relative to KeyTableStart
/// </summary>
public ushort KeyOffset { get; internal set; }
/// <summary>
/// Format of parameter
/// </summary>
/// <remarks>0x0400 is string, 0x0404 is uint</remarks>
public ushort DataFormat { get; internal set; }
/// <summary>
/// Number of bytes used for parameter
/// </summary>
public uint DataLength { get; internal set; }
/// <summary>
/// Total number of bytes for parameter
/// </summary>
/// <remarks>DataTotal - DataLength is padding of 0x00</remarks>
public uint DataTotal { get; internal set; }
/// <summary>
/// offset of parameter, relative to DataTableStart
/// </summary>
public uint DataOffset { get; internal set; }
/// <summary>
/// The name of the parameter
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// The value of the parameter, if it is a string
/// </summary>
public string StringValue { get; internal set; } = null;
/// <summary>
/// The value of the parameter, if it is a UInt32
/// </summary>
public int IntValue { get; internal set; } = 0;
}
/// <summary>
/// The parameters in the table
/// </summary>
/// <remarks><see cref="ParamCount"/> Params in the table</remarks>
public Param[] Params { get; private set; }
/// <summary>
/// String index overloading, gets string value of given key
/// </summary>
/// <param name="key">Parameter to be retreived</param>
/// <returns>The string value of the given key</returns>
public string this[string key]
{
get
{
int index = Array.FindIndex(Params, param => param.Name == key);
return Params[index].StringValue;
}
}
/// <summary> /// <summary>
/// Constructor using a PARAM.SFO file stream /// Constructor using a PARAM.SFO file stream
@@ -117,7 +42,7 @@ namespace LibIRD
/// <summary> /// <summary>
/// Constructor using a PARAM.SFO file path /// Constructor using a PARAM.SFO file path
/// </summary> /// </summary>
/// <param name="sfoPath">Full file path to the PARAM.SFO</param> /// <param name="sfoPath">Full file path to the PARAM.SFO file</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public ParamSFO(string sfoPath) public ParamSFO(string sfoPath)
{ {
@@ -143,97 +68,118 @@ namespace LibIRD
// Check file signature is correct // Check file signature is correct
string magic = Encoding.ASCII.GetString(br.ReadBytes(4)); string magic = Encoding.ASCII.GetString(br.ReadBytes(4));
if (magic != ParamSFO.Magic) if (magic != ParamSFO.Magic)
throw new FileLoadException("Not a valid PARAM.SFO file"); throw new FileLoadException("Unexpected PARAM.SFO file");
// Parse header // Parse header
Version = br.ReadUInt32(); Version = br.ReadUInt32();
KeyTableStart = br.ReadUInt32(); uint keyTableStart = br.ReadUInt32();
DataTableStart = br.ReadUInt32(); uint dataTableStart = br.ReadUInt32();
ParamCount = br.ReadUInt32(); uint paramCount = br.ReadUInt32();
// Parse parameter metadata // Parse parameter metadata
Params = new Param[ParamCount]; ushort[] keyOffset = new ushort[paramCount];
for (int i = 0; i < ParamCount; i++) 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[i] = br.ReadUInt16();
{ dataFormat[i] = br.ReadUInt16();
KeyOffset = br.ReadUInt16(), dataLength[i] = br.ReadUInt32();
DataFormat = br.ReadUInt16(), dataTotal[i] = br.ReadUInt32();
DataLength = br.ReadUInt32(), dataOffset[i] = br.ReadUInt32();
DataTotal = br.ReadUInt32(),
DataOffset = br.ReadUInt32()
};
} }
// Parse parameters // Parse parameters
for (int i = 0; i < ParamCount; i++) Field = [];
for (int i = 0; i < paramCount; i++)
{ {
// Move stream to ith key // Move stream to ith key
sfoStream.Position = KeyTableStart + Params[i].KeyOffset; sfoStream.Position = keyTableStart + keyOffset[i];
// Determine ith key length // Determine ith key length
uint keyLen = ((i == ParamCount - 1) ? DataTableStart - KeyTableStart : Params[i + 1].KeyOffset) uint keyLen = ((i == paramCount - 1) ? dataTableStart - keyTableStart : keyOffset[i + 1])
- Params[i].KeyOffset; - keyOffset[i];
// Read ith key name // 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 // Move stream to ith data
sfoStream.Position = DataTableStart + Params[i].DataOffset; sfoStream.Position = dataTableStart + dataOffset[i];
// Read ith data, based on data format // Read ith data, based on data format
switch (Params[i].DataFormat) Field[key] = dataFormat[i] switch
{ {
case 0x0400: // Non-null-terminated UTF-8 String // Non-null-terminated UTF-8 String
Params[i].StringValue = Encoding.UTF8.GetString(br.ReadBytes((int)Params[i].DataLength)); 0x0004 => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])),
break; // Null-terminated UTF-8 String
case 0x0402: // Null-terminated UTF-8 String 0x0204 => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])).TrimEnd('\0'),
Params[i].StringValue = Encoding.UTF8.GetString(br.ReadBytes((int)Params[i].DataLength)).TrimEnd('\0'); // Integer
break; 0x0404 => br.ReadInt32().ToString(),
case 0x0404: // Integer // Unknown data format, assume null-terminated string
//if (Params[i].DataLength != 4) _ => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])).TrimEnd('\0'),
//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;
}
} }
} }
/// <summary> /// <summary>
/// Prints formatted parameters extracted from PARAM.SFO to console /// Prints formatted parameters extracted from PARAM.SFO to console
/// </summary> /// </summary>
public void Print() /// <param name="printPath">Optionally print to text file</param>
public void Print(string printPath = null)
{ {
// Build string from parameters // Build string from parameters
StringBuilder print = new("PARAM.SFO Contents:\n====================\n"); StringBuilder printText = new();
printText.AppendLine("PARAM.SFO Contents:");
printText.AppendLine("====================");
// Loop through all parameters in PARAM.SFO // Loop through all parameters in PARAM.SFO
for (int i = 0; i < ParamCount; i++) foreach (KeyValuePair<string, string> 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); // Ensure UTF-8 will display properly in console
print.Append(' '); Console.OutputEncoding = Encoding.UTF8;
for (int j = Params[i].Name.Length; j < 20; j++)
print.Append(' '); // Print formatted string to console
switch (Params[i].DataFormat) Console.Write(printText);
{ }
case 0x0404: else
print.Append(Params[i].IntValue); {
break; // Write data to file
default: File.AppendAllText(printPath, printText.ToString());
print.Append(Params[i].StringValue); }
break; }
}
print.Append('\n'); /// <summary>
/// Prints parameters extracted from PARAM.SFO to a json object
/// </summary>
/// <param name="jsonPath">Optionally print to json file</param>
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);
} }
// Ensure UTF-8 will display properly
Console.OutputEncoding = Encoding.UTF8;
// Print formatted string
Console.Write(print);
} }
} }
} }
+40
View File
@@ -0,0 +1,40 @@
## How to use 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, the following are some examples.
### Reproducible, redump-style IRDs
The standard way of generating a reproducible, redump-style IRD is with a redump ISO file and a redump key file (e.g. http://redump.org/disc/28721/key/):
```cs
byte[] discKey = File.ReadAllBytes("./game.key");
IRD ird = new ReIRD("./game.iso", discKey);
```
Alternatively, the disc key can be extracted from a [GetKey](https://archive.org/download/GetKeyR2GameOS.7z/GetKey-r2-GameOS.7z) log file obtained from a PS3 (or when dumping using [ManaGunZ](https://github.com/Zarh/ManaGunZ/))
```cs
IRD ird = new ReIRD("./game.iso", "./game.getkey.log");
```
### Custom IRDs
Functionality is also provided for creating IRDs with a custom disc key, disc ID, and PIC:
```cs
byte[] discKey = new byte[16];
byte[] discID = new byte[16];
byte[] PIC = new byte[115];
// Set vars to desired values
IRD ird = new IRD("./game.iso", discKey, discID, discPIC);
```
As before, a GetKey log file can also be used with an ISO to create a custom IRD, with the disc key, disc ID, and PIC all being extracted from the log file (rather than just the key for redump-style IRDs).
```cs
IRD ird = new IRD("./game.iso", "./game.getkey.log");
```
Finally, an existing IRD file can be read to create an IRD:
```cs
IRD ird = IRD.Read("./game.ird")
```
An IRD can be then be tweaked, its fields printed, and written to a new IRD:
```cs
ird.UID = 0x9F1A51D8;
ird.Print();
ird.Write("game2.ird");
```
+99 -75
View File
@@ -56,7 +56,7 @@ namespace LibIRD
/// <summary> /// <summary>
/// ISO file size /// ISO file size
/// </summary> /// </summary>
public long Size { get; private set; } private long Size { get; set; }
#endregion #endregion
@@ -67,10 +67,9 @@ namespace LibIRD
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <param name="getKeyLog">Path to the GetKey log file</param> /// <param name="getKeyLog">Path to the GetKey log file</param>
/// <exception cref="ArgumentNullException"></exception> /// <param name="layerbreak">Layerbreak value, in sectors</param>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidDataException"></exception> /// <exception cref="InvalidDataException"></exception>
public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog) public ReIRD(string isoPath, string getKeyLog, long? layerbreak = null) : base(isoPath, getKeyLog, true)
{ {
// Generate Unique Identifier using ISO CRC32 // Generate Unique Identifier using ISO CRC32
UID = GenerateUID(isoPath); UID = GenerateUID(isoPath);
@@ -80,12 +79,10 @@ namespace LibIRD
// Generate Data 2 using Disc ID // Generate Data 2 using Disc ID
DiscID = GenerateID(Size); DiscID = GenerateID(Size);
// Check that GetKey log matches expected Disc ID
//if (!((ReadOnlySpan<byte>)Data2Key).SequenceEqual(d2))
// throw new InvalidDataException("Unexpected Disc ID in .getkey.log");
// Generate Disc PIC // Generate Disc PIC
byte[] pic = GeneratePIC(Size); byte[] pic = GeneratePIC(Size, layerbreak * SectorSize);
// Check that GetKey log matches expected PIC // Check that GetKey log matches expected PIC
if (!((ReadOnlySpan<byte>)PIC).SequenceEqual(pic)) if (!((ReadOnlySpan<byte>)PIC).SequenceEqual(pic))
throw new InvalidDataException("Unexpected PIC in .getkey.log"); throw new InvalidDataException("Unexpected PIC in .getkey.log");
@@ -96,10 +93,9 @@ namespace LibIRD
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param> /// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param>
/// <param name="layerbreak">Layerbreak value, in sectors</param>
/// <param name="region">Disc Region</param> /// <param name="region">Disc Region</param>
/// <exception cref="ArgumentNullException"></exception> public ReIRD(string isoPath, byte[] key, long? layerbreak = null, Region region = Region.NONE)
/// <exception cref="FileNotFoundException"></exception>
public ReIRD(string isoPath, byte[] key, Region region = Region.NONE)
{ {
// Generate Unique Identifier using ISO CRC32 // Generate Unique Identifier using ISO CRC32
UID = GenerateUID(isoPath); UID = GenerateUID(isoPath);
@@ -114,10 +110,10 @@ namespace LibIRD
DiscID = GenerateID(Size, region); DiscID = GenerateID(Size, region);
// Generate Disc PIC // Generate Disc PIC
PIC = GeneratePIC(Size); PIC = GeneratePIC(Size, layerbreak * SectorSize);
// Generate IRD fields // Generate IRD fields
GenerateIRD(isoPath); GenerateIRD(isoPath, true);
} }
#endregion #endregion
@@ -131,12 +127,12 @@ namespace LibIRD
private static byte[] GenerateID(long size, Region region = Region.NONE) private static byte[] GenerateID(long size, Region region = Region.NONE)
{ {
if (size > BDLayerSize) // if BD-50, Disc ID is fixed if (size > BDLayerSize) // if BD-50, Disc ID is fixed
return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, return [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
else // else if BD-25, Disc ID has a byte referring to disc region else // else if BD-25, Disc ID has a byte referring to disc region
{ {
return new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, return [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x00, 0x02, 0x00, (byte)region, 0x00, 0x00, 0x00, 0x01 }; 0x00, 0x02, 0x00, (byte)region, 0x00, 0x00, 0x00, 0x01 ];
} }
} }
@@ -147,55 +143,83 @@ namespace LibIRD
/// <param name="layerbreak">Layer break value, byte at which disc layers are split across</param> /// <param name="layerbreak">Layer break value, byte at which disc layers are split across</param>
/// <param name="exactIRD">True to generate a PIC in 3k3y style (0x03 at 115th byte for BD-50 discs)</param> /// <param name="exactIRD">True to generate a PIC in 3k3y style (0x03 at 115th byte for BD-50 discs)</param>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
private static byte[] GeneratePIC(long size, long layerbreak = BDLayerSize, bool exactIRD = false) private static byte[] GeneratePIC(long size, long? layerbreak = null, bool exactIRD = false)
{ {
// Validate size // Validate size
if (size == 0 || (size % SectorSize) != 0) if (size <= 0 || (size % SectorSize) != 0)
throw new ArgumentException("ISO Size in bytes must be a positive integer multiple of 2048", nameof(size)); throw new ArgumentException("ISO Size in bytes must be a positive integer multiple of 2048", nameof(size));
// Validate layerbreak // Validate layerbreak
if (layerbreak == 0 || (layerbreak != BDLayerSize && layerbreak >= size)) if (layerbreak != null)
throw new ArgumentException("Layerbreak in bytes must be a positive integer less than the ISO Size", nameof(size)); {
if (layerbreak <= 0 || (layerbreak >= size))
throw new ArgumentException("Layerbreak in bytes must be a positive integer less than the ISO Size", nameof(size));
if (layerbreak >= 2 * BDLayerSize || layerbreak % SectorSize != 0)
throw new ArgumentException("Unexpected layerbreak value", nameof(size));
}
// If layerbreak value was not set, assume it is a non-hybrid disc with default layerbreak
long layer_break = layerbreak ?? BDLayerSize;
// TODO: Generate correct PICs for Hybrid PS3 discs (BD-50 with layerbreak value other than 12219392) // Generate the PIC based on the size and layerbreak of the ISO
byte[] pic; byte[] pic;
if (size > BDLayerSize) // if BD-50 if (size > BDLayerSize) // if BD-50
{ {
// num_sectors + layer_sector_end (0x00100000) + sectors_between_layers (0x01358C00 - 0x00CA73FE) - 3 // Layer 0 start sector = 0x01000000
byte[] total_sectors = BitConverter.GetBytes((uint)(size / SectorSize + 8067071)); long l0_start_sector = 1048576;
// Layer 0 end sector = start sector + layerbreak - 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)];
// Initial portion of PIC (24 bytes) // Layer 1 start sector = end of disc (0x01EFFFFE) - layerbreak + 2
pic = new byte[]{ long l1_start_sector = 32505854 - (layer_break / SectorSize) + 2;
// [4098 bytes] [2x 0x00] ["DI"] [v1] [10units] [DI num] // Convert start of start sector location to hex values for PIC
0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x10, 0x00, 0x00, 0x20, 0x00, byte[] l1ss = [(byte)((l1_start_sector >> 24) & 0xFF),
// ["BDR"] [2 layers] (byte)((l1_start_sector >> 16) & 0xFF),
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)((l1_start_sector >> 8) & 0xFF),
// Total sectors used on disc (4 bytes) (byte)((l1_start_sector >> 0) & 0xFF)];
total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0],
// 1st Layer sector start location (4 bytes) // Total sectors used = num_sectors + Layer 0 start + sectors_between_layers (usually 0x01358C00 - 0x00CA73FE - 3)
0x00, 0x10, 0x00, 0x00, long total_sectors = (size / SectorSize) + l0_start_sector + (l1_start_sector - l0_end_sector - 3);
// 1st Layer sector end location (4 bytes) byte[] ts = BitConverter.GetBytes((uint) total_sectors);
0x00, 0xCA, 0x73, 0xFE,
// 32 bytes of zeros
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Initial portion of PIC again, for 2nd layer
// ["DI"] [v1] [11unit][DI num]
0x44, 0x49, 0x01, 0x11, 0x00, 0x01, 0x20, 0x00,
// ["BDR"] [2 layers]
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
// Total sectors used on disc
total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0],
// 2nd Layer sector start location
0x01, 0x35, 0x8C, 0x00,
// 2nd Layer sector end location
0x01, 0xEF, 0xFF, 0xFE,
// Remaining 32 bytes are zeroes
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
// 3k3y style: 0x03 at last byte
if (exactIRD)
pic[114] = 0x03;
} // Define the PIC
pic = [
// Initial portion of PIC (24 bytes)
// [4098 bytes] [2x 0x00] ["DI"] [v1] [10units] [DI num]
0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x10, 0x00, 0x00, 0x20, 0x00,
// ["BDR"] [2 layers]
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
// Total sectors used on disc (4 bytes)
ts[3], ts[2], ts[1], ts[0],
// 1st Layer sector start location (4 bytes)
0x00, 0x10, 0x00, 0x00,
// 1st Layer sector end location (4 bytes), 0x00CA73FE for default BD layerbreak of 12219392
l0es[0], l0es[1], l0es[2], l0es[3],
// 32 bytes of zeros
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Initial portion of PIC again, for 2nd layer
// ["DI"] [v1] [11unit][DI num]
0x44, 0x49, 0x01, 0x11, 0x00, 0x01, 0x20, 0x00,
// ["BDR"] [2 layers]
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
// Total sectors used on disc
ts[3], ts[2], ts[1], ts[0],
// 2nd Layer sector start location, 0x01358C00 for default BD layerbreak of 12219392
l1ss[0], l1ss[1], l1ss[2], l1ss[3],
// 2nd Layer sector end location
0x01, 0xEF, 0xFF, 0xFE,
// Remaining 32 bytes are zeroes
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
// 3k3y style: 0x03 at last byte
if (exactIRD)
pic[114] = 0x03;
}
else // if BD-25 else // if BD-25
{ {
// Total sectors used on disc: num_sectors + layer_sector_end (0x00100000) - 1 // Total sectors used on disc: num_sectors + layer_sector_end (0x00100000) - 1
@@ -203,21 +227,23 @@ namespace LibIRD
// Layer sector end location: num_sectors + layer_sector_end (0x00100000) - 2 // Layer sector end location: num_sectors + layer_sector_end (0x00100000) - 2
byte[] end_sector = BitConverter.GetBytes((uint)(size / SectorSize + 1048574)); byte[] end_sector = BitConverter.GetBytes((uint)(size / SectorSize + 1048574));
// Initial portion of PIC (24 bytes) // Define the PIC
pic = new byte[]{ 0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x08, 0x00, 0x00, 0x20, 0x00, pic = [
0x42, 0x44, 0x4F, 0x01, 0x11, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Initial portion of PIC (24 bytes)
// Total sectors used on disc (4 bytes) 0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x08, 0x00, 0x00, 0x20, 0x00,
total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0], 0x42, 0x44, 0x4F, 0x01, 0x11, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
// Layer sector start location (4 bytes) // Total sectors used on disc (4 bytes)
0x00, 0x10, 0x00, 0x00, total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0],
// Layer sector end location (4 bytes) // Layer sector start location (4 bytes)
end_sector[3], end_sector[2], end_sector[1], end_sector[0], 0x00, 0x10, 0x00, 0x00,
// Remaining 79 bytes are zeroes // Layer sector end location (4 bytes)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, end_sector[3], end_sector[2], end_sector[1], end_sector[0],
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Remaining 79 bytes are zeroes
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
} }
return pic; return pic;
@@ -232,8 +258,7 @@ namespace LibIRD
private static uint GenerateUID(string isoPath) private static uint GenerateUID(string isoPath)
{ {
// Validate ISO path // Validate ISO path
if (isoPath == null || isoPath.Length <= 0) ArgumentNullException.ThrowIfNull(isoPath, nameof(isoPath));
throw new ArgumentNullException(nameof(isoPath));
// Check file exists // Check file exists
var iso = new FileInfo(isoPath); var iso = new FileInfo(isoPath);
@@ -262,8 +287,7 @@ namespace LibIRD
private static long CalculateSize(string isoPath) private static long CalculateSize(string isoPath)
{ {
// Validate ISO path // Validate ISO path
if (isoPath == null || isoPath.Length <= 0) ArgumentNullException.ThrowIfNull(isoPath, nameof(isoPath));
throw new ArgumentNullException(nameof(isoPath));
// Check file exists // Check file exists
var iso = new FileInfo(isoPath); var iso = new FileInfo(isoPath);
-25
View File
@@ -1,25 +0,0 @@
using LibIRD;
using System;
using System.IO;
namespace PrintParams
{
internal class Program
{
static void Main()
{
string filename = "./PARAM.SFO";
if (File.Exists(filename))
{
ParamSFO paramSFO = new("./PARAM.SFO");
Console.WriteLine("PARAM.SFO for: " + paramSFO["TITLE_ID"] + '\n');
paramSFO.Print();
}
else
{
Console.WriteLine(filename + " not found");
}
}
}
}
-14
View File
@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Version>0.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LibIRD\LibIRD.csproj" />
</ItemGroup>
</Project>
+23 -1
View File
@@ -1 +1,23 @@
# WIP # Library for ISO Rebuild Data (LibIRD)
.NET library for generating, writing, and reading IRD files. Functionality is provided for deterministically generating IRDs such that a redump ISO and key have a 1-to-1 correspondence with an IRD file.
## What is an IRD?
IRD files contain a summary of what data is on a PlayStation 3 disc. It can be used to rebuild ISOs from files (JB folders). IRD files are also useful for decrypting ISOs after they have been dumped on a PC blu-ray drive, such as for the purposes of legally emulating your PS3 games.
## How to use IRDKit
IRDKit is a tool that allows direct use of LibIRD functionality from the command line interface. The basic usage is:
```
irdkit create game.iso
```
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.