19 Commits
Author SHA1 Message Date
Deterous 6edb959e49 Bump IRDKit to v0.6.1 2024-02-27 13:24:08 +09:00
Deterous 96b36fcabe Add rename functionality 2024-02-27 12:49:14 +09:00
Deterous 9d03dbb7b1 Perform hashing in one read pass 2024-02-20 16:37:15 +09:00
Deterous c7b49d0c80 Initial work towards one-pass hashing 2024-02-19 21:38:06 +09:00
Deterous 5ce81f9663 Bump version 2024-02-18 17:28:21 +09:00
Deterous f74220efea Read ISO in larger chunks 2024-02-18 16:59:04 +09:00
Deterous 6e529f0050 Reuse ISO hash if searching redump 2024-02-16 11:11:51 +09:00
Deterous b862057123 Only print warning for non-contiguous files 2024-02-11 12:39:13 +09:00
Deterous cf19596ea7 Alphabetical order, key file path 2024-02-08 09:38:46 +09:00
Deterous 6af41a9bb3 Include dependencies in nupkg 2024-02-07 15:43:23 +09:00
Deterous 89c23622bd Remove DiscUtils as a public dependency 2024-02-07 14:41:14 +09:00
Deterous 8ab6a9d694 Fix file hash for split files with irregular filesize 2024-02-07 09:32:56 +09:00
Deterous bc3df52e75 Cleanup 2024-02-05 09:20:59 +09:00
Deterous 23f8dce272 Bump version, provide build script 2024-02-04 12:09:11 +09:00
Deterous 8e45432aab Throw error for BD-Video hybrid discs without layerbreak 2024-02-03 23:15:13 +09:00
Deterous e9bb0ae00a Allow for output folder argument, bump version 2024-02-01 10:55:23 +09:00
Deterous 4116c56b7d Add verbose flag to IRDKit, improve printing 2024-02-01 10:31:07 +09:00
Deterous e30ac0961a Finalise diff function, bump version 2024-02-01 00:52:35 +09:00
Deterous 1ef9f06e35 Improve diff 2024-01-31 23:41:46 +09:00
11 changed files with 916 additions and 405 deletions
+8 -9
View File
@@ -3,19 +3,18 @@
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<PackAsTool>true</PackAsTool> <TargetName>irdkit</TargetName>
<ToolCommandName>irdkit</ToolCommandName> <AssemblyName>irdkit</AssemblyName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<TargetFrameworks>net6.0;net7.0;net8.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>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.3.0</Version> <Version>0.6.1</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) Deterous 2023</Copyright> <Copyright>Copyright (c) Deterous 2023-2024</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Deterous/LibIRD</RepositoryUrl> <RepositoryUrl>https://github.com/Deterous/LibIRD</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
@@ -28,11 +27,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LibIRD\LibIRD.csproj" /> <ProjectReference Include="..\LibIRD\LibIRD.csproj" PrivateAssets="All" />
<ProjectReference Include="..\DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj" PrivateAssets="All" />
<ProjectReference Include="..\DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj" PrivateAssets="All" />
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.2" /> <PackageReference Include="SabreTools.RedumpLib" Version="1.3.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+417 -129
View File
@@ -11,6 +11,7 @@ using System.IO.Hashing;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Xml.Linq;
namespace IRDKit namespace IRDKit
{ {
@@ -24,14 +25,14 @@ namespace IRDKit
[Verb("create", HelpText = "Create an IRD from an ISO")] [Verb("create", HelpText = "Create an IRD from an ISO")]
public class CreateOptions public class CreateOptions
{ {
[Value(0, Required = true, HelpText = "Path to an ISO file, or directory of ISO files")] [Value(0, Required = true, HelpText = "Path to ISO file(s), or directory of ISO files")]
public IEnumerable<string> ISOPath { get; set; } public IEnumerable<string> ISOPath { get; set; }
[Option('o', "output", HelpText = "Path to the IRD file to be created (will overwrite)")] [Option('o', "output", HelpText = "Path to the IRD file to be created (will overwrite)")]
public string IRDPath { get; set; } public string IRDPath { get; set; }
[Option('b', "layerbreak", HelpText = "Layerbreak value in bytes (use with BD-Video hybrid discs). Default: 12219392")] [Option('b', "layerbreak", HelpText = "Layerbreak value in bytes (use with BD-Video hybrid discs). Default: 12219392")]
public long? Layerbreak { get; set; } public long? Layerbreak { get; set; }
[Option('k', "key", HelpText = "Hexadecimal representation of the disc key")] [Option('k', "key", HelpText = "Hexadecimal representation of the disc key")]
public string Key { get; set; } public string Key { get; set; }
@@ -44,6 +45,9 @@ namespace IRDKit
[Option('r', "recurse", HelpText = "Recurse through all subdirectories and generate IRDs for all ISOs")] [Option('r', "recurse", HelpText = "Recurse through all subdirectories and generate IRDs for all ISOs")]
public bool Recurse { get; set; } public bool Recurse { get; set; }
[Option('v', "verbose", HelpText = "Print more information during IRD creation")]
public bool Verbose { get; set; }
} }
/// <summary> /// <summary>
@@ -52,7 +56,7 @@ namespace IRDKit
[Verb("info", HelpText = "Print information from an IRD or ISO")] [Verb("info", HelpText = "Print information from an IRD or ISO")]
public class InfoOptions public class InfoOptions
{ {
[Value(0, Required = true, HelpText = "Path to an IRD or ISO file, or directory of IRD and/or ISO files")] [Value(0, Required = true, HelpText = "Path to IRD/ISO file(s), or directory of IRD/ISO files")]
public IEnumerable<string> InPath { get; set; } public IEnumerable<string> InPath { get; set; }
[Option('o', "output", HelpText = "Path to the text or json file to be created (will overwrite)")] [Option('o', "output", HelpText = "Path to the text or json file to be created (will overwrite)")]
@@ -65,9 +69,9 @@ namespace IRDKit
public bool Recurse { get; set; } public bool Recurse { get; set; }
} }
/// <summary> /// <summary>
/// IRD diff command /// IRD diff command
/// </summary> /// </summary>
[Verb("diff", HelpText = "Compare two IRDs and print their differences")] [Verb("diff", HelpText = "Compare two IRDs and print their differences")]
public class DiffOptions public class DiffOptions
{ {
@@ -81,6 +85,31 @@ namespace IRDKit
public string OutPath { get; set; } public string OutPath { get; set; }
} }
/// <summary>
/// IRD rename command
/// </summary>
[Verb("rename", HelpText = "Rename one or more IRD files according to the redump PS3 DAT")]
public class RenameOptions
{
[Value(0, Required = true, HelpText = "Path to IRD file(s), or directory of IRD files")]
public IEnumerable<string> IRDPath { get; set; }
[Option('d', "datfile", Required = true, HelpText = "Path to the redump PS3 Datfile")]
public string DATPath { get; set; }
[Option('s', "serial", HelpText = "Appends disc serial to new IRD filename")]
public bool Serial { get; set; }
[Option('c', "crc", HelpText = "Appends ISO CRC to new IRD filename")]
public bool CRC { get; set; }
[Option('r', "recurse", HelpText = "Recurse through all subdirectories and rename all IRDs")]
public bool Recurse { get; set; }
[Option('v', "verbose", HelpText = "Print more information about the renaming")]
public bool Verbose { get; set; }
}
#endregion #endregion
#region Program #region Program
@@ -95,7 +124,7 @@ namespace IRDKit
Console.OutputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8;
// Parse arguments // Parse arguments
var result = Parser.Default.ParseArguments<CreateOptions, InfoOptions, DiffOptions>(args).WithParsed(Run); var result = Parser.Default.ParseArguments<CreateOptions, InfoOptions, DiffOptions, RenameOptions>(args).WithParsed(Run);
} }
/// <summary> /// <summary>
@@ -111,12 +140,17 @@ namespace IRDKit
case CreateOptions opt: case CreateOptions opt:
// Validate ISO paths // Validate ISO paths
ArgumentNullException.ThrowIfNull(opt.ISOPath); if (opt.ISOPath == null || !opt.ISOPath.Any())
{
Console.Error.WriteLine("Provide a valid ISO path to create an IRD");
return;
}
foreach (string isoPath in opt.ISOPath) foreach (string isoPath in opt.ISOPath)
{ {
// Validate ISO path // Validate ISO path
ArgumentNullException.ThrowIfNull(isoPath); if (string.IsNullOrEmpty(isoPath))
continue;
// If directory, search for all ISOs in current directory // If directory, search for all ISOs in current directory
if (Directory.Exists(isoPath)) if (Directory.Exists(isoPath))
@@ -125,47 +159,59 @@ namespace IRDKit
IEnumerable<string> isoFiles; IEnumerable<string> isoFiles;
if (opt.Recurse) if (opt.Recurse)
{ {
if (isoPath == ".") if (opt.Verbose && isoPath == ".")
Console.WriteLine($"Recursively searching for ISOs in current directory"); Console.WriteLine($"Recursively searching for ISOs in current directory");
else else if (opt.Verbose)
Console.WriteLine($"Recursively searching for ISOs in {isoPath}"); Console.WriteLine($"Recursively searching for ISOs in {isoPath}");
isoFiles = Directory.EnumerateFiles(isoPath, "*.iso", SearchOption.AllDirectories); isoFiles = Directory.EnumerateFiles(isoPath, "*.iso", SearchOption.AllDirectories);
} }
else else
{ {
if (isoPath == ".") if (opt.Verbose && isoPath == ".")
Console.WriteLine($"Searching for ISOs in current directory"); Console.WriteLine($"Searching for ISOs in current directory");
else else if (opt.Verbose)
Console.WriteLine($"Searching for ISOs in {isoPath}"); Console.WriteLine($"Searching for ISOs in {isoPath}");
isoFiles = Directory.EnumerateFiles(isoPath, "*.iso", SearchOption.TopDirectoryOnly); isoFiles = Directory.EnumerateFiles(isoPath, "*.iso", SearchOption.TopDirectoryOnly);
} }
// Warn if no files are found // Warn if no files are found
if (!isoFiles.Any()) if (!isoFiles.Any())
Console.WriteLine("No ISOs found (ensure .iso extension)"); {
if (opt.Recurse)
Console.Error.WriteLine($"No ISOs found in {isoPath} (ensure .iso extension)");
else
Console.Error.WriteLine($"No ISOs found in {isoPath} (ensure .iso extension, or try use -r)");
continue;
}
// Determine output IRD folder
string outputPath = opt.IRDPath;
if (File.Exists(opt.IRDPath))
outputPath = Path.GetDirectoryName(opt.IRDPath);
// Create an IRD file for all ISO files found // Create an IRD file for all ISO files found
foreach (string file in isoFiles) foreach (string file in isoFiles.OrderBy(x => x))
ISO2IRD(file); ISO2IRD(file, irdPath: outputPath, keyPath: opt.KeyFile, verbose: opt.Verbose);
} }
else else
{ {
// Check that given file exists // Check that given file exists
if (!File.Exists(isoPath)) throw new ArgumentException("Not a valid file or directory"); if (!File.Exists(isoPath))
{
Console.Error.WriteLine($"ISO not found: {isoPath}");
continue;
}
// Save to given output path, if only 1 IRD is being created string irdPath;
if (opt.ISOPath.Count() == 1 && opt.IRDPath != null && opt.IRDPath != "") // Save to given output path and filename, if only 1 IRD is being created
{ if (opt.ISOPath.Count() == 1)
string irdPath = ISO2IRD(isoPath, opt.IRDPath, opt.Key, opt.KeyFile, opt.GetKeyLog, opt.Layerbreak); irdPath = ISO2IRD(isoPath, irdPath: opt.IRDPath, hexKey: opt.Key, keyPath: opt.KeyFile, getKeyLog: opt.GetKeyLog, layerbreak: opt.Layerbreak, verbose: opt.Verbose);
if (irdPath != null) // Save to given output path, if more than 1 IRD is being created
Console.WriteLine($"IRD saved to {irdPath}");
}
else else
{ irdPath = ISO2IRD(isoPath, irdPath: Path.GetDirectoryName(opt.IRDPath), verbose: opt.Verbose);
string irdPath = ISO2IRD(isoPath, null, opt.Key, opt.KeyFile, opt.GetKeyLog, opt.Layerbreak);
if (irdPath != null) if (irdPath != null)
Console.WriteLine($"IRD saved to {irdPath}"); Console.WriteLine($"IRD saved to {irdPath}");
}
} }
} }
@@ -175,7 +221,11 @@ namespace IRDKit
case InfoOptions opt: case InfoOptions opt:
// Validate required parameter // Validate required parameter
ArgumentNullException.ThrowIfNull(opt.InPath); if (opt.InPath == null || !opt.InPath.Any())
{
Console.Error.WriteLine("Provide a valid ISO or IRD path to print info about");
return;
}
// Clear the output file path if it exists // Clear the output file path if it exists
if (opt.OutPath != null && opt.OutPath != "") if (opt.OutPath != null && opt.OutPath != "")
@@ -184,7 +234,8 @@ namespace IRDKit
foreach (string filePath in opt.InPath) foreach (string filePath in opt.InPath)
{ {
// Validate path // Validate path
ArgumentNullException.ThrowIfNull(filePath); if (string.IsNullOrEmpty(filePath))
continue;
// If directory, search for all ISOs in current directory // If directory, search for all ISOs in current directory
if (Directory.Exists(filePath)) if (Directory.Exists(filePath))
@@ -213,7 +264,10 @@ namespace IRDKit
// Warn if no files are found // Warn if no files are found
if (!isoFiles.Any() && !irdFiles.Any()) if (!isoFiles.Any() && !irdFiles.Any())
Console.WriteLine("No IRDs or ISOs found (ensure .ird and .iso extensions)"); {
Console.Error.WriteLine("No IRDs or ISOs found (ensure .ird and .iso extensions)");
return;
}
// Open JSON object // Open JSON object
if (opt.Json) if (opt.Json)
@@ -245,7 +299,7 @@ namespace IRDKit
{ {
// Not a valid ISO file despite extension, assume file is an IRD // Not a valid ISO file despite extension, assume file is an IRD
if (!opt.Json) if (!opt.Json)
Console.WriteLine($"{file} is not a valid ISO file\n"); Console.Error.WriteLine($"{file} is not a valid ISO file\n");
} }
} }
@@ -264,7 +318,11 @@ namespace IRDKit
else else
{ {
// Check that given file exists // Check that given file exists
if (!File.Exists(filePath)) throw new ArgumentException($"{filePath} is not a valid file or directory"); if (!File.Exists(filePath))
{
Console.Error.WriteLine($"{filePath} is not a valid file");
continue;
}
// Print info from given file // Print info from given file
PrintInfo(filePath, opt.Json, true, opt.OutPath); PrintInfo(filePath, opt.Json, true, opt.OutPath);
@@ -279,11 +337,12 @@ namespace IRDKit
// Process options from a `diff` command // Process options from a `diff` command
case DiffOptions opt: case DiffOptions opt:
// Validate required parameter // Validate required parameters
ArgumentNullException.ThrowIfNull(opt.InPath1); if (opt.InPath1 == null || opt.InPath2 == null || !File.Exists(opt.InPath1) || !File.Exists(opt.InPath2))
ArgumentNullException.ThrowIfNull(opt.InPath2); {
if (!File.Exists(opt.InPath1)) throw new ArgumentException($"{opt.InPath1} is not a valid file or directory"); Console.Error.WriteLine("Provide two paths to IRDs to compare");
if (!File.Exists(opt.InPath2)) throw new ArgumentException($"{opt.InPath2} is not a valid file or directory"); return;
}
// Clear the output file path if it exists // Clear the output file path if it exists
if (opt.OutPath != null && opt.OutPath != "") if (opt.OutPath != null && opt.OutPath != "")
@@ -297,6 +356,99 @@ namespace IRDKit
break; break;
// Process options from a 'rename' command
case RenameOptions opt:
// Validate required parameters
if (opt.IRDPath == null || !opt.IRDPath.Any())
{
Console.Error.WriteLine("Provide a valid IRD path to rename");
return;
}
// Read DAT file
XDocument datfile = DatParser(opt.DATPath);
if (datfile == null)
{
Console.Error.WriteLine("Unable to parse DAT file");
return;
}
foreach (string irdPath in opt.IRDPath)
{
// Validate IRD path
if (string.IsNullOrEmpty(irdPath))
continue;
// If directory, search for all ISOs in current directory
if (Directory.Exists(irdPath))
{
// If recurse option enabled, search recursively
IEnumerable<string> irdFiles;
if (opt.Recurse)
{
if (opt.Verbose && irdPath == ".")
Console.WriteLine($"Recursively renaming IRDs in current directory");
else if (opt.Verbose)
Console.WriteLine($"Recursively renaming IRDs in {irdPath}");
irdFiles = Directory.EnumerateFiles(irdPath, "*.ird", SearchOption.AllDirectories);
}
else
{
if (opt.Verbose && irdPath == ".")
Console.WriteLine($"Renaming IRDs in current directory");
else if (opt.Verbose)
Console.WriteLine($"Renaming IRDs in {irdPath}");
irdFiles = Directory.EnumerateFiles(irdPath, "*.ird", SearchOption.TopDirectoryOnly);
}
// Warn if no files are found
if (!irdFiles.Any())
{
if (opt.Recurse)
Console.Error.WriteLine($"No IRDs found in {irdPath} (ensure .ird extension)");
else
Console.Error.WriteLine($"No IRDs found in {irdPath} (ensure .ird extension, or try use -r)");
continue;
}
// Rename all IRD files found
foreach (string file in irdFiles.OrderBy(x => x))
{
try
{
RenameIRD(file, datfile, serial: opt.Serial, crc: opt.CRC, verbose: opt.Verbose);
}
catch (Exception e)
{
Console.Error.WriteLine(e);
}
}
}
else
{
// Check that given file exists
if (!File.Exists(irdPath))
{
Console.Error.WriteLine($"IRD not found: {irdPath}");
continue;
}
// Rename provided IRD path
try
{
RenameIRD(irdPath, datfile, serial: opt.Serial, crc: opt.CRC, verbose: opt.Verbose);
}
catch (Exception e)
{
Console.Error.WriteLine(e);
}
}
}
break;
// Unknown command // Unknown command
default: default:
break; break;
@@ -355,9 +507,9 @@ namespace IRDKit
if (json) if (json)
return; return;
if (isISO) if (isISO)
Console.WriteLine($"{inPath} is not a valid ISO file\n"); Console.Error.WriteLine($"{inPath} is not a valid ISO file\n");
else else
Console.WriteLine($"{inPath} is not a valid IRD file\n"); Console.Error.WriteLine($"{inPath} is not a valid IRD file\n");
} }
} }
@@ -372,10 +524,13 @@ namespace IRDKit
public static void PrintISO(string isoPath, bool json, bool single = true, string outPath = null) public static void PrintISO(string isoPath, bool json, bool single = true, string outPath = null)
{ {
// Open ISO file for reading // Open ISO file for reading
using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath); using FileStream fs = new(isoPath, FileMode.Open, FileAccess.Read);
// Validate ISO file stream // Validate ISO file stream
if (!CDReader.Detect(fs)) if (fs == null || !CDReader.Detect(fs))
throw new InvalidFileSystemException($"{isoPath} is not a valid ISO file"); {
Console.Error.WriteLine($"{isoPath} is not a valid ISO file");
return;
}
// Create new ISO reader // Create new ISO reader
using CDReader reader = new(fs, true, true); using CDReader reader = new(fs, true, true);
@@ -412,7 +567,7 @@ namespace IRDKit
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
if (!json) if (!json)
Console.WriteLine($"{isoPath} is not a valid PS3 ISO file\n"); Console.Error.WriteLine($"{isoPath} is not a valid PS3 ISO file\n");
return; return;
} }
@@ -435,7 +590,7 @@ namespace IRDKit
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
if (!json) if (!json)
Console.WriteLine($"\\PS3_GAME\\PARAM.SFO not found in {isoPath}\n"); Console.Error.WriteLine($"\\PS3_GAME\\PARAM.SFO not found in {isoPath}\n");
} }
// End JSON object // End JSON object
@@ -469,7 +624,7 @@ namespace IRDKit
// Check they are different IRDs // Check they are different IRDs
if (Path.GetFullPath(irdPath1) == Path.GetFullPath(irdPath2)) if (Path.GetFullPath(irdPath1) == Path.GetFullPath(irdPath2))
{ {
Console.WriteLine("Provide two different IRDs for a diff"); Console.Error.WriteLine("Provide two different IRDs for a diff");
return; return;
} }
@@ -480,45 +635,69 @@ namespace IRDKit
// Build a formatted diff // Build a formatted diff
StringBuilder printText = new(); StringBuilder printText = new();
// Print any version difference
if (IRD1.Version != IRD2.Version) if (IRD1.Version != IRD2.Version)
printText.AppendLine($"Version: {IRD1.Version} vs {IRD2.Version}"); printText.AppendLine($"Version: {IRD1.Version} vs {IRD2.Version}");
// Print any title ID difference
if (IRD1.TitleID != IRD2.TitleID) if (IRD1.TitleID != IRD2.TitleID)
printText.AppendLine($"TitleID: {IRD1.TitleID} vs {IRD2.TitleID}"); printText.AppendLine($"TitleID: {IRD1.TitleID} vs {IRD2.TitleID}");
// Print any title difference
if (IRD1.Title != IRD2.Title) if (IRD1.Title != IRD2.Title)
printText.AppendLine($"Title: {IRD1.Title} vs {IRD2.Title}"); printText.AppendLine($"Title: \"{IRD1.Title}\" vs \"{IRD2.Title}\"");
// Print any system version difference
if (IRD1.SystemVersion != IRD2.SystemVersion) if (IRD1.SystemVersion != IRD2.SystemVersion)
printText.AppendLine($"PUP Version: {IRD1.SystemVersion} vs {IRD2.SystemVersion}"); printText.AppendLine($"PUP Version: {IRD1.SystemVersion} vs {IRD2.SystemVersion}");
// Print any disc version difference
if (IRD1.DiscVersion != IRD2.DiscVersion) if (IRD1.DiscVersion != IRD2.DiscVersion)
printText.AppendLine($"Disc Version: {IRD1.DiscVersion} vs {IRD2.DiscVersion}"); printText.AppendLine($"Disc Version: {IRD1.DiscVersion} vs {IRD2.DiscVersion}");
// Print any app version difference
if (IRD1.AppVersion != IRD2.AppVersion) if (IRD1.AppVersion != IRD2.AppVersion)
printText.AppendLine($"App Version: {IRD1.AppVersion} vs {IRD2.AppVersion}"); printText.AppendLine($"App Version: {IRD1.AppVersion} vs {IRD2.AppVersion}");
// Un-gzip the headers to compare them
byte[] header1 = Decompress(IRD1.Header); byte[] header1 = Decompress(IRD1.Header);
byte[] header2 = Decompress(IRD2.Header); byte[] header2 = Decompress(IRD2.Header);
// Print the difference in header length, if not 0
if (header1.Length != header2.Length) if (header1.Length != header2.Length)
printText.AppendLine($"Header Length: {header1.Length} vs {header2.Length}"); printText.AppendLine($"Header Length: {header1.Length} vs {header2.Length}");
if (!header1.SequenceEqual(header2)) // Print number of bytes that the headers differ by, if not 0
printText.AppendLine($"Header: Differs"); int headerDiff;
if (header1.Length < header2.Length)
headerDiff = header2.Length - header1.Length + header1.Where((x, i) => x != header2[i]).Count();
else
headerDiff = header1.Length - header2.Length + header2.Where((x, i) => x != header1[i]).Count();
if (headerDiff != 0)
printText.AppendLine($"Header: Differs by {headerDiff} bytes");
// Un-gzip the footers to compare them
byte[] footer1 = Decompress(IRD1.Footer); byte[] footer1 = Decompress(IRD1.Footer);
byte[] footer2 = Decompress(IRD2.Footer); byte[] footer2 = Decompress(IRD2.Footer);
// Print the difference in footer length, if not 0
if (footer1.Length != footer2.Length) if (footer1.Length != footer2.Length)
printText.AppendLine($"Footer Length: {footer1.Length} vs {footer2.Length}"); printText.AppendLine($"Footer Length: {footer1.Length} vs {footer2.Length}");
if (!footer1.SequenceEqual(footer2)) // Print number of bytes that the footers differ by, if not 0
printText.AppendLine($"Footer: Differs"); int footerDiff;
if (footer1.Length < footer2.Length)
footerDiff = footer2.Length - footer1.Length + footer1.Where((x, i) => x != footer2[i]).Count();
else
footerDiff = footer1.Length - footer2.Length + footer2.Where((x, i) => x != footer1[i]).Count();
if (footerDiff != 0)
printText.AppendLine($"Footer: Differs by {footerDiff} bytes");
// Print the difference in number of regions, if not 0
if (IRD1.RegionCount != IRD2.RegionCount) if (IRD1.RegionCount != IRD2.RegionCount)
printText.AppendLine($"Region Count: {IRD1.RegionCount} vs {IRD2.RegionCount}"); printText.AppendLine($"Region Count: {IRD1.RegionCount} vs {IRD2.RegionCount}");
// Print any differences in region hashes
int regionCount = IRD2.RegionCount < IRD1.RegionCount ? IRD2.RegionCount : IRD1.RegionCount; int regionCount = IRD2.RegionCount < IRD1.RegionCount ? IRD2.RegionCount : IRD1.RegionCount;
if (regionCount > IRD1.RegionHashes.Length) if (regionCount > IRD1.RegionHashes.Length)
regionCount = IRD1.RegionHashes.Length; regionCount = IRD1.RegionHashes.Length;
@@ -530,44 +709,58 @@ namespace IRDKit
printText.AppendLine($"Region {i} Hash: {Convert.ToHexString(IRD1.RegionHashes[i])} vs {Convert.ToHexString(IRD2.RegionHashes[i])}"); printText.AppendLine($"Region {i} Hash: {Convert.ToHexString(IRD1.RegionHashes[i])} vs {Convert.ToHexString(IRD2.RegionHashes[i])}");
} }
// Print the difference in number of files, if not 0
if (IRD1.FileCount != IRD2.FileCount) if (IRD1.FileCount != IRD2.FileCount)
printText.AppendLine($"File Count: {IRD1.FileCount} vs {IRD2.FileCount}"); printText.AppendLine($"File Count: {IRD1.FileCount} vs {IRD2.FileCount}");
int fileCount = IRD2.FileCount < IRD1.FileCount ? (int)IRD2.FileCount : (int)IRD1.FileCount; // Print the mismatch file hashes, for each file offset at which they differ
if (fileCount > IRD1.FileKeys.Length) List<long> missingOffsets1 = [];
fileCount = IRD1.FileKeys.Length; List<long> missingOffsets2 = [];
if (fileCount > IRD2.FileKeys.Length) for (int i = 0; i < IRD1.FileKeys.Length; i++)
fileCount = IRD2.FileKeys.Length;
if (fileCount > IRD1.FileHashes.Length)
fileCount = IRD1.FileHashes.Length;
if (fileCount > IRD2.FileHashes.Length)
fileCount = IRD2.FileHashes.Length;
for (int i = 0; i < fileCount; i++)
{ {
if (IRD1.FileKeys[i] != IRD2.FileKeys[i]) int j = Array.FindIndex(IRD2.FileKeys, element => element == IRD1.FileKeys[i]);
printText.AppendLine($"File {i} Offset: {IRD1.FileKeys[i]} vs {IRD2.FileKeys[i]}"); if (j == -1)
if (!IRD1.FileHashes[i].SequenceEqual(IRD2.FileHashes[i])) missingOffsets2.Add(IRD1.FileKeys[i]);
printText.AppendLine($"File {i} Hash: {Convert.ToHexString(IRD1.FileHashes[i])} vs {Convert.ToHexString(IRD2.FileHashes[i])}"); if (j != -1 && !IRD1.FileHashes[i].SequenceEqual(IRD2.FileHashes[j]))
printText.AppendLine($"File Hash at Offset {IRD1.FileKeys[i]}: {Convert.ToHexString(IRD1.FileHashes[i])} vs {Convert.ToHexString(IRD2.FileHashes[j])}");
} }
for (int i = 0; i < IRD2.FileKeys.Length; i++)
{
int j = Array.FindIndex(IRD1.FileKeys, element => element == IRD2.FileKeys[i]);
if (j == -1)
missingOffsets1.Add(IRD2.FileKeys[i]);
}
// Print the file offsets that differ
if (missingOffsets1.Count > 0)
printText.AppendLine($"File Offsets not Present in {irdPath1}: {string.Join(", ", missingOffsets1)}");
if (missingOffsets2.Count > 0)
printText.AppendLine($"File Offsets not Present in {irdPath2}: {string.Join(", ", missingOffsets2)}");
// Print any extra config data difference
if (IRD1.ExtraConfig != IRD2.ExtraConfig) if (IRD1.ExtraConfig != IRD2.ExtraConfig)
printText.AppendLine($"Extra Config: {IRD1.ExtraConfig:X4} vs {IRD2.ExtraConfig:X4}"); printText.AppendLine($"Extra Config: {IRD1.ExtraConfig:X4} vs {IRD2.ExtraConfig:X4}");
// Print any attachments data difference
if (IRD1.Attachments != IRD2.Attachments) if (IRD1.Attachments != IRD2.Attachments)
printText.AppendLine($"Attachments: {IRD1.Attachments:X4} vs {IRD2.Attachments:X4}"); printText.AppendLine($"Attachments: {IRD1.Attachments:X4} vs {IRD2.Attachments:X4}");
// Print any unique ID difference
if (IRD1.UID != IRD2.UID) if (IRD1.UID != IRD2.UID)
printText.AppendLine($"Unique ID: {IRD1.UID:X8} vs {IRD2.UID:X8}"); printText.AppendLine($"Unique ID: {IRD1.UID:X8} vs {IRD2.UID:X8}");
// Print any data 1 key difference
if (!IRD1.Data1Key.SequenceEqual(IRD2.Data1Key)) if (!IRD1.Data1Key.SequenceEqual(IRD2.Data1Key))
printText.AppendLine($"Data 1 Key: {Convert.ToHexString(IRD1.Data1Key)} vs {Convert.ToHexString(IRD2.Data1Key)}"); printText.AppendLine($"Data 1 Key: {Convert.ToHexString(IRD1.Data1Key)} vs {Convert.ToHexString(IRD2.Data1Key)}");
// Print any data 2 key difference
if (!IRD1.Data2Key.SequenceEqual(IRD2.Data2Key)) if (!IRD1.Data2Key.SequenceEqual(IRD2.Data2Key))
printText.AppendLine($"Data 2 Key: {Convert.ToHexString(IRD1.Data2Key)} vs {Convert.ToHexString(IRD2.Data2Key)}"); printText.AppendLine($"Data 2 Key: {Convert.ToHexString(IRD1.Data2Key)} vs {Convert.ToHexString(IRD2.Data2Key)}");
// Print any PIC difference
if (!IRD1.PIC.SequenceEqual(IRD2.PIC)) if (!IRD1.PIC.SequenceEqual(IRD2.PIC))
printText.AppendLine($"PIC: {Convert.ToHexString(IRD1.PIC)} vs {Convert.ToHexString(IRD2.PIC)}"); printText.AppendLine($"PIC: {Convert.ToHexString(IRD1.PIC)} vs {Convert.ToHexString(IRD2.PIC)}");
// Write formatted string to file if output path provided, otherwise to console
if (outPath != null) if (outPath != null)
File.AppendAllText(outPath, printText.ToString()); File.AppendAllText(outPath, printText.ToString());
else else
@@ -583,17 +776,32 @@ namespace IRDKit
/// <param name="keyPath">Disc key file (overridden by hex string if present)</param> /// <param name="keyPath">Disc key file (overridden by hex string if present)</param>
/// <param name="getKeyLog">GetKey log file (overridden by disc key or key file if present)</param> /// <param name="getKeyLog">GetKey log file (overridden by disc key or key file if present)</param>
/// <param name="layerbreak">Layerbreak value of disc</param> /// <param name="layerbreak">Layerbreak value of disc</param>
public static string ISO2IRD(string isoPath, string irdPath = null, string hexKey = null, string keyPath = null, string getKeyLog = null, long? layerbreak = null) public static string ISO2IRD(string isoPath, string irdPath = null, string hexKey = null, string keyPath = null, string getKeyLog = null, long? layerbreak = null, bool verbose = false)
{ {
// Check file exists // Check file exists
FileInfo iso = new(isoPath); FileInfo iso;
try
{
iso = new(isoPath);
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message + ", failed to create IRD");
return null;
}
if (!iso.Exists) if (!iso.Exists)
{ {
Console.WriteLine($"{nameof(isoPath)} is not a valid file or directory"); Console.Error.WriteLine($"{nameof(isoPath)} is not a valid file or directory");
return null; return null;
} }
// Determine IRD path if only folder given
if (Directory.Exists(irdPath))
irdPath = Path.Combine(irdPath, Path.GetFileName(Path.ChangeExtension(isoPath, ".ird")));
// Determine IRD path if none given // Determine IRD path if none given
if (irdPath == string.Empty)
irdPath = Path.GetFileName(Path.ChangeExtension(isoPath, ".ird"));
irdPath ??= Path.ChangeExtension(isoPath, ".ird"); irdPath ??= Path.ChangeExtension(isoPath, ".ird");
// Create new reproducible redump-style IRD with a given hex key // Create new reproducible redump-style IRD with a given hex key
@@ -604,21 +812,20 @@ namespace IRDKit
// Get disc key from hex string // Get disc key from hex string
byte[] discKey = Convert.FromHexString(hexKey); byte[] discKey = Convert.FromHexString(hexKey);
if (discKey == null || discKey.Length != 16) if (discKey == null || discKey.Length != 16)
throw new ArgumentException(hexKey); Console.Error.WriteLine($"{hexKey} is not a valid key, detecting key automatically...");
else
Console.WriteLine($"Creating {irdPath} with Key: {hexKey}"); {
IRD ird1 = new ReIRD(isoPath, discKey, layerbreak); Console.WriteLine($"Creating {irdPath} with Key: {hexKey}");
ird1.Write(irdPath); IRD ird1 = new ReIRD(isoPath, discKey, layerbreak);
ird1.Print(); ird1.Write(irdPath);
return irdPath; if (verbose)
ird1.Print();
return irdPath;
}
} }
catch (ArgumentException) catch (Exception e)
{ {
Console.Error.WriteLine($"{hexKey} is not a valid key, detecting key automatically..."); Console.Error.WriteLine(e.Message + ", failed to create IRD");
}
catch (FileNotFoundException)
{
Console.Error.WriteLine("File not found, failed to create IRD");
return null; return null;
} }
} }
@@ -626,23 +833,30 @@ namespace IRDKit
// Create new reproducible redump-style IRD with a given key file // Create new reproducible redump-style IRD with a given key file
if (keyPath != null) if (keyPath != null)
{ {
// Read key from .key file
byte[] discKey = File.ReadAllBytes(keyPath);
try try
{ {
IRD ird2 = new ReIRD(isoPath, discKey, layerbreak); // If key directory was given, append filename and .key
Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(discKey)}"); if (Directory.Exists(keyPath))
ird2.Write(irdPath); {
ird2.Print(); keyPath = Path.Combine(keyPath, Path.ChangeExtension(Path.GetFileName(isoPath), ".key"));
return irdPath; }
// Read key from .key file
byte[] discKey = File.ReadAllBytes(keyPath);
if (discKey == null || discKey.Length != 16)
Console.Error.WriteLine($"{hexKey} is not a valid key, detecting key automatically...");
else
{
Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(discKey)}");
IRD ird1 = new ReIRD(isoPath, discKey, layerbreak);
ird1.Write(irdPath);
if (verbose)
ird1.Print();
return irdPath;
}
} }
catch (ArgumentException) catch (Exception e)
{ {
Console.Error.WriteLine($"{Convert.ToHexString(discKey)} is not a valid key, detecting key automatically..."); Console.Error.WriteLine(e.Message + ", failed to create IRD");
}
catch (FileNotFoundException)
{
Console.Error.WriteLine("File not found, failed to create IRD");
return null; return null;
} }
} }
@@ -653,14 +867,15 @@ namespace IRDKit
try try
{ {
Console.WriteLine($"Creating {irdPath} with key from: {getKeyLog}"); Console.WriteLine($"Creating {irdPath} with key from: {getKeyLog}");
IRD ird3 = new ReIRD(isoPath, getKeyLog); IRD ird1 = new ReIRD(isoPath, getKeyLog);
ird3.Write(irdPath); ird1.Write(irdPath);
ird3.Print(); if (verbose)
ird1.Print();
return irdPath; return irdPath;
} }
catch (FileNotFoundException) catch (Exception e)
{ {
Console.Error.WriteLine("File not found, failed to create IRD"); Console.Error.WriteLine(e.Message + ", failed to create IRD");
return null; return null;
} }
} }
@@ -676,21 +891,20 @@ namespace IRDKit
// Read key from .key file // Read key from .key file
byte[] discKey = File.ReadAllBytes(keyfilePath); byte[] discKey = File.ReadAllBytes(keyfilePath);
if (discKey == null || discKey.Length != 16) if (discKey == null || discKey.Length != 16)
throw new ArgumentException(keyfilePath); Console.Error.WriteLine($"{hexKey} is not a valid key, detecting key automatically...");
else
Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(discKey)}"); {
IRD ird2 = new ReIRD(isoPath, discKey, layerbreak); Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(discKey)}");
ird2.Write(irdPath); IRD ird1 = new ReIRD(isoPath, discKey, layerbreak);
ird2.Print(); ird1.Write(irdPath);
return irdPath; if (verbose)
ird1.Print();
return irdPath;
}
} }
catch (ArgumentException) catch (Exception e)
{ {
Console.Error.WriteLine("Given key file not valid, detecting key automatically..."); Console.Error.WriteLine(e.Message + ", failed to create IRD");
}
catch (FileNotFoundException)
{
Console.Error.WriteLine("File not found, failed to create IRD");
return null; return null;
} }
} }
@@ -704,28 +918,32 @@ namespace IRDKit
try try
{ {
Console.WriteLine($"Creating {irdPath} with key from: {logfilePath}"); Console.WriteLine($"Creating {irdPath} with key from: {logfilePath}");
IRD ird3 = new ReIRD(isoPath, logfilePath); IRD ird1 = new ReIRD(isoPath, logfilePath);
ird3.Write(irdPath); ird1.Write(irdPath);
ird3.Print(); if (verbose)
ird1.Print();
return irdPath; return irdPath;
} }
catch (FileNotFoundException) catch (Exception e)
{ {
Console.Error.WriteLine("File not found, failed to create IRD"); Console.Error.WriteLine(e.Message + ", failed to create IRD");
return null; return null;
} }
} }
// No key provided, try get key from redump.org // No key provided, try get key from redump.org
Console.WriteLine("No key provided... Searching for key on redump.org"); if (verbose)
Console.WriteLine("No key provided... Searching for key on redump.org");
// Compute CRC32 hash // Compute CRC32 hash
byte[] crc32; byte[] crc32;
uint crc32UInt;
using (FileStream fs = File.OpenRead(isoPath)) using (FileStream fs = File.OpenRead(isoPath))
{ {
Crc32 hasher = new(); Crc32 hasher = new();
hasher.Append(fs); hasher.Append(fs);
crc32 = hasher.GetCurrentHash(); crc32 = hasher.GetCurrentHash();
crc32UInt = BitConverter.ToUInt32(crc32);
// Change endianness // Change endianness
Array.Reverse(crc32); Array.Reverse(crc32);
} }
@@ -737,12 +955,12 @@ namespace IRDKit
int id; int id;
if (ids.Count == 0) if (ids.Count == 0)
{ {
Console.WriteLine("ISO not found in redump, cannot automatically retreive key"); Console.Error.WriteLine("ISO not found in redump and no valid key provided, cannot create IRD");
return null; return null;
} }
else if (ids.Count > 1) else if (ids.Count > 1)
{ {
// Compute SHA1 hash // More than one result for the CRC32 hash, compute SHA1 hash instead
byte[] sha1; byte[] sha1;
using (FileStream fs = File.OpenRead(isoPath)) using (FileStream fs = File.OpenRead(isoPath))
{ {
@@ -755,18 +973,19 @@ namespace IRDKit
List<int> ids2 = redump.CheckSingleSitePage("http://redump.org/discs/system/ps3/quicksearch/" + sha1_hash).ConfigureAwait(false).GetAwaiter().GetResult(); List<int> ids2 = redump.CheckSingleSitePage("http://redump.org/discs/system/ps3/quicksearch/" + sha1_hash).ConfigureAwait(false).GetAwaiter().GetResult();
if (ids2.Count == 0) if (ids2.Count == 0)
{ {
Console.WriteLine("ISO not found in redump, cannot automatically retreive key"); Console.Error.WriteLine("ISO not found in redump and no valid key provided, cannot create IRD");
return null; return null;
} }
else if (ids2.Count > 1) else if (ids2.Count > 1)
{ {
Console.WriteLine("Cannot automatically get key from redump. Please search redump.org and run again with -k"); Console.Error.WriteLine("Cannot automatically get key from redump. Please search redump.org and run again with -k");
return null; return null;
} }
id = ids2[0]; id = ids2[0];
} }
else else
{ {
// One result found, assume it is the PS3 ISO
id = ids[0]; id = ids[0];
} }
@@ -774,15 +993,84 @@ namespace IRDKit
byte[] key = redump.GetByteArrayAsync($"http://redump.org/disc/{id}/key").ConfigureAwait(false).GetAwaiter().GetResult(); byte[] key = redump.GetByteArrayAsync($"http://redump.org/disc/{id}/key").ConfigureAwait(false).GetAwaiter().GetResult();
if (key.Length != 16) if (key.Length != 16)
{ {
Console.WriteLine("Invalid key obtained from redump"); Console.Error.WriteLine("Invalid key obtained from redump and no valid key provided, cannot create IRD");
return null;
} }
// Create IRD with key from redump // Create IRD with key from redump
Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(key)}"); Console.WriteLine($"Creating {irdPath} with Key from redump.org: {Convert.ToHexString(key)}");
IRD ird = new ReIRD(isoPath, key, layerbreak); try
ird.Write(irdPath); {
ird.Print(); IRD ird = new ReIRD(isoPath, key, layerbreak, crc32UInt);
return irdPath; ird.Write(irdPath);
if (verbose)
ird.Print();
return irdPath;
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message + ", failed to create IRD");
return null;
}
}
public static XDocument DatParser(string datpath = null)
{
try
{
if (!File.Exists(datpath))
return null;
return XDocument.Load(datpath);
}
catch
{
return null;
}
}
public static string GetDatFilename(IRD ird, XDocument datfile)
{
string crc32 = ird.UID.ToString("X8").ToLower();
XElement node = datfile.Root.Elements("game").Where(e => e.Element("rom").Attribute("crc").Value == crc32).FirstOrDefault() ?? throw new ArgumentException("Cannot find ISO in redump DAT");
return node.Attribute("name").Value;
}
public static void RenameIRD(string irdPath, XDocument datfile, bool serial = false, bool crc = false, bool verbose = false)
{
IRD ird = IRD.Read(irdPath);
if (ird.ExtraConfig != 0x0001)
throw new ArgumentException($"{irdPath} is not a redump-style IRD");
string filename = GetDatFilename(ird, datfile);
if (filename == null)
throw new ArgumentException($"Cannot determine DAT filename for {irdPath}");
if (serial)
filename += $" [{ird.TitleID[..4]}-{ird.TitleID[4..9]}]";
if (crc)
filename += $" [{ird.UID:X8}]";
// Rename irdPath to filename
string directory = Path.GetDirectoryName(Path.GetFullPath(filename));
string filepath;
if (!string.IsNullOrEmpty(directory))
filepath = Path.Combine(Path.GetDirectoryName(irdPath), filename + ".ird");
else
filepath = filename + ".ird";
// Rename IRD to new name
if (irdPath != filepath)
{
if (verbose)
Console.WriteLine($"Renaming {Path.GetFileName(irdPath)} to {Path.GetFileName(filepath)}");
File.Move(irdPath, filepath);
}
else
{
if (verbose)
Console.WriteLine($"Skipping {Path.GetFileName(irdPath)}, already named correctly");
}
} }
#endregion #endregion
+40 -6
View File
@@ -1,7 +1,41 @@
## How to use IRDKit # 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 is a tool that allows direct use of LibIRD functionality from the command line interface.
``` For full help instructions, run `irdkit help`
irdkit game.iso game.ird
``` ## Creating ISOs
For detailed usage, run `irdkit --help`
For all options, run `irdkit help create`
To create an IRD from an ISO, run `irdkit create game.iso`
Multiple ISOs can be processed at once with `irdkit create game1.iso game2.iso`
Or a whole directory of ISOs can be processed with `irdkit create ./ISO`, or recursively with `irdkit create -r ./ISO`
The IRD will be created in the same folder as the ISO, with the same filename.
A different IRD path and/or filename can be defined with `-o` or `--output=`
### Key
By default, IRDs will be created by pulling keys from redump.org
A key can be manually provided with `-k` or `--key=`
A key file can be provided with `-f game.key` or `--key-file=`
A key from GetKey log file can be used with `-l game.getkey.log` or `--getkey-log=`
### PIC
By default, a PIC will be generated assuming a default layerbreak
A layerbreak value can be provided with `-b` or `--layerbreak=`
A PIC from a GetKey log file can be used with `-l game.getkey.log` or `--getkey-log=`
## Printing info
For all options, run `irdkit help info`
To print info about an ISO or IRD file, run `irdkit info game.iso` or `irdkit info game.ird`
The info can be printed to a file, e.g. `-o out.txt` or `--output=`
The info can be formatted as a JSON with `-j` or `--json`
## Comparing IRDs
For all options, run `irdkit help diff`
To compare two IRDs, run `irdkit diff game1.ird game2.ird`
The comparison can be printed to a file, e.g. `-o out.txt` or `--output=`
+319 -148
View File
@@ -2,6 +2,7 @@
using DiscUtils.Iso9660; using DiscUtils.Iso9660;
using DiscUtils.Streams; using DiscUtils.Streams;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.IO.Hashing; using System.IO.Hashing;
@@ -117,7 +118,6 @@ namespace LibIRD
} }
} }
private byte[] _discKey; private byte[] _discKey;
// TODO: Link Data1Key and Disc Key
/// <summary> /// <summary>
/// D1 key /// D1 key
@@ -271,7 +271,7 @@ namespace LibIRD
/// <summary> /// <summary>
/// MD5 hashes for all decrypted files in the image /// MD5 hashes for all decrypted files in the image
/// </summary> /// </summary>
/// <remarks><see cref="FileHashes"/> files, 16-bytes per hash, alternating with each <see cref="FileHashes"/> entry</remarks> /// <remarks><see cref="FileCount"/> files, 16-bytes per hash, alternating with each <see cref="FileHashes"/> entry</remarks>
public byte[][] FileHashes { get; private set; } public byte[][] FileHashes { get; private set; }
/// <summary> /// <summary>
@@ -301,6 +301,11 @@ namespace LibIRD
/// </summary> /// </summary>
private long[] RegionEnd { get; set; } private long[] RegionEnd { get; set; }
/// <summary>
/// File extents to hash
/// </summary>
private Range<long, long>[][] FileExtents { get; set; }
#endregion #endregion
#region Constructors #region Constructors
@@ -352,11 +357,9 @@ namespace LibIRD
/// <summary> /// <summary>
/// Default constructor for internal derived classes only: resulting object not in usable state /// Default constructor for internal derived classes only: resulting object not in usable state
/// Assumes that internally derived class will set fields in its own constructor
/// </summary> /// </summary>
private protected IRD() private protected IRD() { }
{
// Assumes that internally derived class will set fields in its own constructor
}
/// <summary> /// <summary>
/// Constructor with given required fields /// Constructor with given required fields
@@ -406,7 +409,6 @@ namespace LibIRD
private protected static byte[] GenerateD1(byte[] key) private protected static byte[] GenerateD1(byte[] key)
{ {
// Validate key // Validate key
ArgumentNullException.ThrowIfNull(key, 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));
@@ -440,7 +442,6 @@ namespace LibIRD
private protected static byte[] GenerateDiscKey(byte[] d1) private protected static byte[] GenerateDiscKey(byte[] d1)
{ {
// Validate key // Validate key
ArgumentNullException.ThrowIfNull(d1, 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));
@@ -474,7 +475,6 @@ namespace LibIRD
private protected static byte[] GenerateD2(byte[] d2) private protected static byte[] GenerateD2(byte[] d2)
{ {
// Validate id // Validate id
ArgumentNullException.ThrowIfNull(d2, 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
@@ -507,7 +507,6 @@ namespace LibIRD
private protected static byte[] GenerateDiscID(byte[] d2) private protected static byte[] GenerateDiscID(byte[] d2)
{ {
// Validate id // Validate id
ArgumentNullException.ThrowIfNull(d2 , 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));
@@ -540,9 +539,7 @@ namespace LibIRD
/// <exception cref="InvalidDataException"></exception> /// <exception cref="InvalidDataException"></exception>
private protected void ParseGetKeyLog(string getKeyLog) private protected void ParseGetKeyLog(string getKeyLog)
{ {
// Validate .getkey.log file path // Validate .getkey.log file path
ArgumentNullException.ThrowIfNull(getKeyLog, nameof(getKeyLog));
if (!File.Exists(getKeyLog)) if (!File.Exists(getKeyLog))
throw new FileNotFoundException(nameof(getKeyLog)); throw new FileNotFoundException(nameof(getKeyLog));
@@ -625,7 +622,7 @@ namespace LibIRD
private protected void GenerateIRD(string isoPath, bool redump = false) 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, FileShare.Read, 4096, FileOptions.SequentialScan) ?? throw new FileNotFoundException(isoPath);
// Validate ISO file stream // Validate ISO file stream
if (!CDReader.Detect(fs)) if (!CDReader.Detect(fs))
throw new InvalidFileSystemException("Not a valid ISO file"); throw new InvalidFileSystemException("Not a valid ISO file");
@@ -640,7 +637,7 @@ namespace LibIRD
ExtraConfig |= 0x01; ExtraConfig |= 0x01;
// Redump-style IRDs use fields from PS3_DISC.SFB // Redump-style IRDs use fields from PS3_DISC.SFB
using DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_DISC.SFB", FileMode.Open, FileAccess.Read); using SparseStream s = reader.OpenFile("PS3_DISC.SFB", FileMode.Open, FileAccess.Read);
// Parse PS3_DISC.SFB file // Parse PS3_DISC.SFB file
PS3_DiscSFB ps3_DiscSFB = new(s); PS3_DiscSFB ps3_DiscSFB = new(s);
@@ -658,7 +655,7 @@ namespace LibIRD
} }
// 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 (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);
@@ -704,30 +701,40 @@ namespace LibIRD
// Read and compress the ISO footer // Read and compress the ISO footer
GetFooter(fs); GetFooter(fs);
// Process all regions on ISO // Get info region info from ISO
HashRegions(fs); GetRegions(fs);
// TODO: Speed up program by hashing regions and files at the same time (read from filesystem only once)
// Recursively count all files in ISO to allocate file arrays // Recursively count all files in ISO to allocate file arrays
DiscDirectoryInfo rootDir = reader.GetDirectoryInfo("\\"); DiscDirectoryInfo rootDir = reader.GetDirectoryInfo("\\");
FileCount = 0; FileCount = 0;
CountFiles(rootDir); CountFiles(rootDir);
FileKeys = new long[FileCount];
FileHashes = new byte[FileCount][];
// Determine file offsets and hashes // Pre-allocate arrays and reset file count
FileKeys = new long[FileCount];
FileExtents = new Range<long, long>[FileCount][];
uint fileCount = FileCount; uint fileCount = FileCount;
FileCount = 0; FileCount = 0;
HashFiles(fs, reader, rootDir);
// Determine file offsets
GetFiles(fs, reader, rootDir);
// Resize arrays if non-contiguous files were detected
if (FileCount != fileCount) if (FileCount != fileCount)
{ {
Console.WriteLine($"Likely contains non-contiguous files: detected {FileCount} out of {fileCount} expected files");
long[] tempFileKeys = FileKeys; long[] tempFileKeys = FileKeys;
Array.Resize(ref tempFileKeys, (int)FileCount); Array.Resize(ref tempFileKeys, (int)FileCount);
FileKeys = tempFileKeys; FileKeys = tempFileKeys;
Range<long, long>[][] tempFileExtents = FileExtents;
Array.Resize(ref tempFileExtents, (int)FileCount);
FileExtents = tempFileExtents;
} }
Array.Sort(FileKeys, FileHashes); // Sort files by offset
Array.Sort(FileKeys, FileExtents);
// Calculate CRC32 hash of ISO only if generating a redump IRD and the UID is not already set
RegionHashes = new byte[RegionCount][];
FileHashes = new byte[FileCount][];
HashISO(fs, redump && UID == 0x00000000);
} }
#endregion #endregion
@@ -739,12 +746,12 @@ namespace LibIRD
/// </summary> /// </summary>
/// <remarks>PS3UPDAT.PUP update file version number</remarks> /// <remarks>PS3UPDAT.PUP update file version number</remarks>
/// <param name="fs">ISO filestream</param> /// <param name="fs">ISO filestream</param>
/// <param name="reader"></param> /// <param name="reader">CDReader</param>
/// <exception cref="InvalidFileSystemException"></exception> /// <exception cref="InvalidFileSystemException"></exception>
private void GetSystemVersion(FileStream fs, CDReader reader) private void GetSystemVersion(FileStream fs, CDReader reader)
{ {
// Determine PUP file offset via cluster // Determine PUP file offset via cluster
DiscUtils.Streams.Range<long, long>[] updateClusters = reader.PathToClusters("\\PS3_UPDATE\\PS3UPDAT.PUP"); Range<long, long>[] updateClusters = reader.PathToClusters("\\PS3_UPDATE\\PS3UPDAT.PUP");
if (updateClusters == null && updateClusters.Length == 0 && updateClusters[0] == null) if (updateClusters == null && updateClusters.Length == 0 && updateClusters[0] == null)
throw new InvalidFileSystemException("Invalid file extents for PS3UPDAT.PUP"); throw new InvalidFileSystemException("Invalid file extents for PS3UPDAT.PUP");
@@ -782,12 +789,12 @@ namespace LibIRD
/// Retreives and stores the header /// Retreives and stores the header
/// </summary> /// </summary>
/// <param name="fs">ISO filestream</param> /// <param name="fs">ISO filestream</param>
/// <param name="reader"></param> /// <param name="reader">CDReader</param>
/// <exception cref="InvalidFileSystemException"></exception> /// <exception cref="InvalidFileSystemException"></exception>
private void GetHeader(FileStream fs, CDReader reader) private void GetHeader(FileStream fs, CDReader reader)
{ {
// Determine the extent of the header via cluster (Sector 0 to first data sector) // Determine the extent of the header via cluster (Sector 0 to first data sector)
DiscUtils.Streams.Range<long, long>[] sfbClusters = reader.PathToClusters("\\PS3_DISC.SFB"); Range<long, long>[] sfbClusters = reader.PathToClusters("\\PS3_DISC.SFB");
if (sfbClusters == null && sfbClusters.Length == 0 && sfbClusters[0] == null) if (sfbClusters == null && sfbClusters.Length == 0 && sfbClusters[0] == null)
throw new InvalidFileSystemException("Invalid file extents for PS3_DISC.SFB"); throw new InvalidFileSystemException("Invalid file extents for PS3_DISC.SFB");
// End of header is at beginning of first byte of dedicated cluster // End of header is at beginning of first byte of dedicated cluster
@@ -795,7 +802,11 @@ namespace LibIRD
// Begin a GZip stream to write header to // Begin a GZip stream to write header to
using MemoryStream headerStream = new(); using MemoryStream headerStream = new();
#if NET6_0_OR_GREATER
using (GZipStream gzs = new(headerStream, CompressionLevel.SmallestSize)) using (GZipStream gzs = new(headerStream, CompressionLevel.SmallestSize))
#else
using (GZipStream gzs = new(headerStream, CompressionLevel.Optimal))
#endif
{ {
// Start reading data from the beginning of the ISO file // Start reading data from the beginning of the ISO file
fs.Seek(0, SeekOrigin.Begin); fs.Seek(0, SeekOrigin.Begin);
@@ -823,7 +834,11 @@ namespace LibIRD
{ {
// Begin a GZip stream to write footer to // Begin a GZip stream to write footer to
using MemoryStream footerStream = new(); using MemoryStream footerStream = new();
#if NET6_0_OR_GREATER
using (GZipStream gzs = new(footerStream, CompressionLevel.SmallestSize)) using (GZipStream gzs = new(footerStream, CompressionLevel.SmallestSize))
#else
using (GZipStream gzs = new(footerStream, CompressionLevel.Optimal))
#endif
{ {
// Start reading data from after last file (PS3UPDAT.PUP) // Start reading data from after last file (PS3UPDAT.PUP)
fs.Seek(UpdateEnd, SeekOrigin.Begin); fs.Seek(UpdateEnd, SeekOrigin.Begin);
@@ -844,11 +859,11 @@ namespace LibIRD
} }
/// <summary> /// <summary>
/// Determines and stores the hashes for each disc region /// Retreives and stores the Region extents
/// </summary> /// </summary>
/// <param name="fs"></param> /// <param name="fs">ISO filestream</param>
/// <exception cref="InvalidFileSystemException"></exception> /// <exception cref="InvalidFileSystemException"></exception>
private void HashRegions(FileStream fs) private void GetRegions(FileStream fs)
{ {
// Determine the number of unencryted regions // Determine the number of unencryted regions
fs.Seek(0, SeekOrigin.Begin); fs.Seek(0, SeekOrigin.Begin);
@@ -859,7 +874,6 @@ namespace LibIRD
RegionCount = (byte)(2 * ((uint)decRegionCount[3]) - 1); RegionCount = (byte)(2 * ((uint)decRegionCount[3]) - 1);
if (RegionCount <= 0) if (RegionCount <= 0)
throw new InvalidFileSystemException("No regions detected in ISO"); throw new InvalidFileSystemException("No regions detected in ISO");
RegionHashes = new byte[RegionCount][];
RegionStart = new long[RegionCount]; RegionStart = new long[RegionCount];
RegionEnd = new long[RegionCount]; RegionEnd = new long[RegionCount];
@@ -888,40 +902,15 @@ namespace LibIRD
RegionStart[0] = FirstDataSector; RegionStart[0] = FirstDataSector;
// Remove footer from last region // Remove footer from last region
RegionEnd[^1] = (UpdateEnd / SectorSize) - 1; RegionEnd[^1] = (UpdateEnd / SectorSize) - 1;
// Determine MD5 hashes for each region
using MD5 md5 = MD5.Create();
byte[] buf = new byte[SectorSize];
for (int i = 0; i < RegionCount; i++)
{
// Start reading data from first sector of region
fs.Seek(SectorSize * RegionStart[i], SeekOrigin.Begin);
// Compute MD5 hash for just the region portion of the ISO file
int numBytes;
for (long j = RegionStart[i]; j <= RegionEnd[i]; j++)
{
// Read one sector at a time
numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read
if (numBytes < buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Process MD5 sum one sector at a time
md5.TransformBlock(buf, 0, buf.Length, null, 0);
}
// Compute and store MD5 hash of region
md5.TransformFinalBlock(buf, 0, 0);
RegionHashes[i] = md5.Hash;
}
} }
/// <summary> /// <summary>
/// Determine and store hashes for all files and files within subdirectories recursively /// Determine and store file extents for all files and files within subdirectories recursively
/// </summary> /// </summary>
/// <param name="reader"></param> /// <param name="fs">ISO filestream</param>
/// <param name="path"></param> /// <param name="reader">CDReader</param>
private void HashFiles(FileStream fs, CDReader reader, DiscDirectoryInfo dir) /// <param name="dir">Folder to search for files within</param>
private void GetFiles(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())
@@ -929,124 +918,103 @@ namespace LibIRD
string filePath = fileInfo.FullName; string filePath = fileInfo.FullName;
// Determine the extents of the file via clusters // Determine the extents of the file via clusters
DiscUtils.Streams.Range<long, long>[] fileClusters = reader.PathToClusters(filePath); Range<long, long>[] fileExtent = reader.PathToClusters(filePath);
// If invalid clusters were returned, we can't hash this file // If invalid clusters were returned, we can't hash this file
if (fileClusters == null && fileClusters.Length == 0) if (fileExtent == null && fileExtent.Length == 0)
throw new InvalidFileSystemException($"Unexpected file extents for {filePath}"); throw new InvalidFileSystemException($"Unexpected file extents for {filePath}");
// Determine smallest file offset as first sector // Determine smallest file offset as first sector
long smallestOffset = fileClusters[0].Offset; long smallestOffset = fileExtent[0].Offset;
for (int i = 1; i < fileClusters.Length; i++) bool nonContiguous = false;
for (int i = 1; i < fileExtent.Length; i++)
{ {
if (fileClusters[i] == null) if (fileExtent[i] == null)
throw new InvalidFileSystemException($"Unexpected file extents for {filePath}"); throw new InvalidFileSystemException($"Unexpected file extents for {filePath}");
if (fileClusters[i].Offset < smallestOffset) if (fileExtent[i].Offset * SectorSize != fileExtent[i - 1].Offset * SectorSize + fileExtent[i - 1].Count)
smallestOffset = fileClusters[i].Offset; nonContiguous = true;
if (fileExtent[i].Offset < smallestOffset)
smallestOffset = fileExtent[i].Offset;
} }
int firstSector = (int)(smallestOffset);
// If already encountered file offset, skip this file // If already encountered file offset, skip this file
if (Array.Exists(FileKeys, element => element == firstSector)) if (Array.Exists(FileKeys, element => element == smallestOffset))
continue; continue;
else if (nonContiguous)
Console.WriteLine($"Non-contiguous file found: {filePath}");
// Add file offset to keys // Add file offset to keys and extents to extents
FileKeys[FileCount] = firstSector; FileKeys[FileCount] = smallestOffset;
FileExtents[FileCount] = fileExtent;
// Determine whether file is in encrypted or decrypted region
bool encrypted = false;
for (int i = RegionCount - 1; i > 0; i--)
{
if (RegionStart[i] <= firstSector)
{
encrypted = i % 2 == 1;
break;
}
}
// Hash each non-contiguous portion of the ISO file
byte[] buf = new byte[SectorSize];
MD5 md5 = MD5.Create();
for (int i = 0; i < fileClusters.Length; i++)
{
// Start reading data from the beginning of the ISO file
fs.Seek(fileClusters[i].Offset * SectorSize, SeekOrigin.Begin);
int numBytes;
// Read all data before the first data sector
for (int j = 0; j < (fileClusters[i].Count / SectorSize); j++)
{
numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read
if (numBytes < buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Decrypt sector if necessary
if (encrypted)
buf = DecryptSector(buf, firstSector + j);
// Hash sector
md5.TransformBlock(buf, 0, numBytes, null, 0);
}
// Read remaining partial sector
if (fileClusters[i].Count % SectorSize != 0)
{
numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read
if (numBytes < buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Decrypt partial sector if necessary
if (encrypted)
buf = DecryptSector(buf, firstSector + (int)(fileClusters[i].Count / SectorSize));
// Hash partial sector
md5.TransformBlock(buf, 0, (int)(fileClusters[i].Count % SectorSize), null, 0);
}
}
// Finalise and store MD5 hash
md5.TransformFinalBlock(buf, 0, 0);
FileHashes[FileCount] = md5.Hash;
FileCount++; FileCount++;
} }
// 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())
{ {
HashFiles(fs, reader, dirInfo); GetFiles(fs, reader, dirInfo);
} }
} }
/// <summary> /// <summary>
/// Decrypts a given sector byte array /// Decrypts a given byte array of sector(s)
/// </summary> /// </summary>
/// <param name="sector">Byte array to be decrypted</param> /// <param name="buffer">Byte array to be decrypted</param>
/// <param name="sectorNumber">Sector number of first sector being decrypted</param>
/// <param name="offset">Number of sectors to skip</param>
/// <param name="count">Number of sectors to decrypt, beginning from offset</param>
/// <exception cref="InvalidOperationException"></exception> /// <exception cref="InvalidOperationException"></exception>
private protected byte[] DecryptSector(byte[] sector, int sectorNumber) private protected void DecryptSectors(ref byte[] buffer, int sectorNumber, int offset = 0, int? count = null)
{ {
ArgumentNullException.ThrowIfNull(buffer);
if (buffer.Length == 0 || buffer.Length % SectorSize != 0)
throw new ArgumentException("Encrypted buffer must be multiple of SectorSize");
if (offset < 0 || offset >= buffer.Length / SectorSize)
throw new ArgumentException("Offset sector must be within buffer");
count ??= (int)(buffer.Length / SectorSize) - offset;
if (count < 0 || count > (buffer.Length / SectorSize) - offset)
throw new ArgumentException("Number of sectors must be within buffer");
// Setup AES decryption // Setup AES decryption
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");
// Set AES settings // Set AES settings
aes.Key = DiscKey; aes.Key = DiscKey;
aes.Padding = PaddingMode.None; aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC; aes.Mode = CipherMode.CBC;
// Determine Initial Value based on sector number // Convert offset and count to number of bytes
byte[] iv = new byte[16]; offset *= (int)SectorSize;
for (int i = 0; i < 16; i++) count *= (int)SectorSize;
// Decrypt buffer one sector at a time
for (int i = offset; i < offset + count; i += (int)SectorSize)
{ {
byte a = (byte)(sectorNumber & 0xFF); // Determine AES Initial Value based on sector number
iv[16 - i - 1] = (byte)(sectorNumber & 0xFF); byte[] iv = new byte[16];
sectorNumber >>= 8; int tempNum = sectorNumber;
for (int j = 0; j < 16; j++)
{
iv[16 - j - 1] = (byte)(tempNum & 0xFF);
tempNum >>= 8;
}
aes.IV = iv;
// Perform AES decryption
using MemoryStream stream = new();
using CryptoStream cs = new(stream, aes.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(buffer, i, (int)SectorSize);
cs.FlushFinalBlock();
// Write decrypted sector to output
stream.ToArray().CopyTo(buffer, i);
sectorNumber++;
} }
aes.IV = iv; return;
// Perform AES decryption
using MemoryStream stream = new();
using ICryptoTransform dec = aes.CreateDecryptor();
using CryptoStream cs = new(stream, dec, CryptoStreamMode.Write);
cs.Write(sector, 0, sector.Length);
cs.FlushFinalBlock();
return stream.ToArray();
} }
/// <summary> /// <summary>
@@ -1060,6 +1028,200 @@ namespace LibIRD
CountFiles(dirInfo); CountFiles(dirInfo);
} }
/// <summary>
/// Calculate hashes for all region and file extents
/// </summary>
/// <param name="fs">ISO filestream</param>
/// <param name="redump">True if also calculating CRC32 of entire ISO</param>
private void HashISO(FileStream fs, bool redump)
{
// Initialise CRC32 ISO hasher, only used if making redump-style IRD
byte[] crc32;
Crc32 isoHasher = new();
// Initialise MD5 region hashes
List<int> regions = [];
MD5[] regionMD5 = new MD5[RegionCount];
for (int i = 0; i < RegionCount; i++)
{
regions.Add(i);
regionMD5[i] = MD5.Create();
}
// Initialise MD5 file hashes
List<int> files = [];
MD5[] fileMD5 = new MD5[FileCount];
for (int i = 0; i < FileCount; i++)
{
files.Add(i);
fileMD5[i] = MD5.Create();
}
// Start hashing from beginning of ISO
long currentSector = 0;
fs.Seek(currentSector, SeekOrigin.Begin);
// Read from ISO, 1024 sectors at a time
int bufSectors = 1024;
byte[] buf = new byte[bufSectors * SectorSize];
while (true)
{
// Attempt to read a full buffer
bufSectors = 1024;
int numBytes = fs.Read(buf, 0, buf.Length);
// If end of ISO reached, stop reading
if (numBytes == 0)
{
// If making redump-style IRD, save CRC32 hash to UID field
if (redump)
{
crc32 = isoHasher.GetCurrentHash();
UID = BitConverter.ToUInt32(crc32, 0);
}
return;
}
// Keep trying to read to fill buffer, remove once partial buffer hashing is supported
if (numBytes != buf.Length)
{
while (numBytes % SectorSize != 0)
{
int newNumBytes = fs.Read(buf, numBytes, buf.Length - numBytes);
numBytes += newNumBytes;
// If end of ISO reached, trim buffer and hash
if (newNumBytes == 0 && numBytes % SectorSize != 0)
{
//numBytes -= numBytes % (int)SectorSize;
Console.Error.WriteLine("ERROR: ISO filestream ended early");
break;
}
}
// Only hash portion of buffer
bufSectors = numBytes / (int)SectorSize;
if (bufSectors == 0)
Console.Error.WriteLine("ERROR: Trailing partial sector in ISO filestream");
if (numBytes > buf.Length)
throw new InvalidFileSystemException("ERROR: Read more bytes than buffer size???");
}
// Hash ISO
if (redump)
isoHasher.Append(new ReadOnlySpan<byte>(buf, 0, numBytes));
// Hash regions
List<int> regionsEnded = [];
foreach (int i in regions)
{
// Stop hashing regions if current region has not yet started (assumes regions are ordered)
if (RegionStart[i] > currentSector + bufSectors)
break;
// Skip region if it has already ended
//if (RegionEnd[i] < currentSector)
// continue;
// Check if region has ended in this buffer [We know: Start is not in the future, Ending is not in the past]
if (RegionEnd[i] < currentSector + bufSectors)
{
// Determine start byte, if region is entirely within the buffer
int startByte = RegionStart[i] > currentSector ? (int)(SectorSize * (RegionStart[i] - currentSector)) : 0;
// Determine end byte
int endByte = (int)(SectorSize * (RegionEnd[i] - currentSector + 1));
// Close region hash
regionMD5[i].TransformFinalBlock(buf, startByte, endByte - startByte);
RegionHashes[i] = regionMD5[i].Hash;
regionMD5[i].Clear();
regionsEnded.Add(i);
}
// Check if region has already begun
else if (RegionStart[i] <= currentSector)
{
// Hash buffer
regionMD5[i].TransformBlock(buf, 0, (int)SectorSize * bufSectors, null, 0);
}
// Region Start is in this buffer, ending is in the future
else
{
// Hash partial buffer
int regionStart = (int)(SectorSize * (RegionStart[i] - currentSector));
regionMD5[i].TransformBlock(buf, regionStart, (int)SectorSize * bufSectors - regionStart, null, 0);
}
}
if (regionsEnded.Count > 0)
regions.RemoveAll(item => regionsEnded.Contains(item));
// Decrypt any encrypted sectors of buffer
for (int i = 1; i < RegionCount; i += 2)
{
// If the current encrypted region is within the buffer
if (RegionStart[i] < currentSector + bufSectors
&& RegionEnd[i] >= currentSector)
{
// First sector to decrypt from
int encOffset = 0;
// Don't decrypt initial sectors if the encrypted region starts within this buffer
if (RegionStart[i] > currentSector)
encOffset = (int)(RegionStart[i] - currentSector);
// Number of sectors to decrypt
int encCount = bufSectors - encOffset;
// Don't decrypt last sectors if the encrypted region ends within this buffer
if (RegionEnd[i] < currentSector + bufSectors)
encCount -= (int)(currentSector + bufSectors - RegionEnd[i] + 1);
// Decrypt encrypted sectors
DecryptSectors(ref buf, (int)currentSector + encOffset, encOffset, encCount);
}
}
// Hash files
List<int> filesEnded = [];
foreach (int i in files)
{
// Stop hashing files if current file has not yet started (assumes FileKeys are sorted)
if (FileKeys[i] > currentSector + bufSectors)
break;
// Hash each file extent for each file
for (int j = 0; j < FileExtents[i].Length; j++)
{
// Skip hashing file extent if it has not yet started or already ended
if (FileExtents[i][j].Offset > currentSector + bufSectors
|| SectorSize* FileExtents[i][j].Offset + FileExtents[i][j].Count < SectorSize * currentSector)
continue;
// Determine first file byte location in buffer
int startByte = FileExtents[i][j].Offset > currentSector ? (int)(SectorSize * (FileExtents[i][j].Offset - currentSector)) : 0;
// Determine last file byte location in buffer
int endByte = (int)(FileExtents[i][j].Count - SectorSize * (currentSector - FileExtents[i][j].Offset));
// Don't hash more than the buffer size
endByte = endByte < bufSectors * (int)SectorSize ? endByte : bufSectors * (int)SectorSize;
// Hash portion of buffer that file exists in
fileMD5[i].TransformBlock(buf, startByte, endByte - startByte, null, 0);
}
// Check if current file has ended in this buffer (assumes last extent contains last byte)
long lastByte = SectorSize * FileExtents[i][^1].Offset + FileExtents[i][^1].Count;
if (lastByte < SectorSize * (currentSector + bufSectors)
&& lastByte > SectorSize * currentSector)
{
// Close file hash
fileMD5[i].TransformFinalBlock(buf, 0, 0);
FileHashes[i] = fileMD5[i].Hash;
fileMD5[i].Clear();
filesEnded.Add(i);
}
}
if (filesEnded.Count > 0)
files.RemoveAll(item => filesEnded.Contains(item));
currentSector += bufSectors;
}
}
#endregion #endregion
#region IRD File #region IRD File
@@ -1127,7 +1289,12 @@ namespace LibIRD
// Hashes for each region // Hashes for each region
for (int i = 0; i < RegionCount; i++) for (int i = 0; i < RegionCount; i++)
bw.Write(RegionHashes[i], 0, 16); {
if (RegionHashes[i] == null)
bw.Write(NullMD5);
else
bw.Write(RegionHashes[i], 0, 16);
}
// Number of files hashed // Number of files hashed
bw.Write(FileCount); bw.Write(FileCount);
@@ -1175,7 +1342,11 @@ namespace LibIRD
// Create the IRD file stream // Create the IRD file stream
using FileStream fs = new(irdPath, FileMode.Create, FileAccess.Write); using FileStream fs = new(irdPath, FileMode.Create, FileAccess.Write);
// Create a GZipped IRD file stream // Create a GZipped IRD file stream
#if NET6_0_OR_GREATER
using GZipStream gzStream = new(fs, CompressionLevel.SmallestSize); using GZipStream gzStream = new(fs, CompressionLevel.SmallestSize);
#else
using GZipStream gzStream = new(fs, CompressionLevel.Optimal);
#endif
// Write entire gzipped IRD stream to file // Write entire gzipped IRD stream to file
stream.Position = 0; stream.Position = 0;
stream.CopyTo(gzStream); stream.CopyTo(gzStream);
@@ -1242,7 +1413,7 @@ namespace LibIRD
uint fileCount = br.ReadUInt32(); uint fileCount = br.ReadUInt32();
long[] fileKeys = new long[fileCount]; long[] fileKeys = new long[fileCount];
byte[][] fileHashes = new byte[fileCount][]; byte[][] fileHashes = new byte[fileCount][];
for (int i = 0; i < fileCount; i++) for (int i = 0; i < fileCount; i++)
{ {
fileKeys[i] = br.ReadInt64(); fileKeys[i] = br.ReadInt64();
fileHashes[i] = br.ReadBytes(16); fileHashes[i] = br.ReadBytes(16);
+16 -6
View File
@@ -2,16 +2,17 @@
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net6.0;net7.0;net8.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>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.3.0</Version> <Version>0.6.0</Version>
<PackageOutputPath>../nupkg</PackageOutputPath>
<!-- 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) Deterous 2024</Copyright> <Copyright>Copyright (c) Deterous 2023-2024</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Deterous/LibIRD/</RepositoryUrl> <RepositoryUrl>https://github.com/Deterous/LibIRD/</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
@@ -25,10 +26,19 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj" PrivateAssets="All" />
<ProjectReference Include="..\DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj" PrivateAssets="All" />
<ProjectReference Include="..\DiscUtils\Library\DiscUtils.Streams\DiscUtils.Streams.csproj" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Streams\DiscUtils.Streams.csproj" PrivateAssets="All" />
<PackageReference Include="System.IO.Hashing" Version="8.0.0" /> <PackageReference Include="System.IO.Hashing" Version="8.0.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="BuildOnlySettings;ResolveReferences">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')-&gt;WithMetadataValue('PrivateAssets', 'All'))" />
</ItemGroup>
</Target>
</Project> </Project>
+7 -6
View File
@@ -27,18 +27,14 @@ namespace LibIRD
/// A field within the PS3_DISC.SFB file /// A field within the PS3_DISC.SFB file
/// </summary> /// </summary>
/// <remarks>string Key, string Value</remarks> /// <remarks>string Key, string Value</remarks>
public Dictionary<string, string> Field { get; private set; } public Dictionary<string, string> Field { get; private set; }
/// <summary> /// <summary>
/// Constructor using a PARAM.SFO file path /// Constructor using a PARAM.SFO file path
/// </summary> /// </summary>
/// <param name="sfbPath">Full file path to the PS3_DISC.SFB file</param> /// <param name="sfbPath">Full file path to the PS3_DISC.SFB file</param>
/// <exception cref="ArgumentNullException"></exception>
public PS3_DiscSFB(string sfbPath) public PS3_DiscSFB(string sfbPath)
{ {
// Validate file path
ArgumentNullException.ThrowIfNull(sfbPath, nameof(sfbPath));
// Read file as a stream, and parse file // Read file as a stream, and parse file
using FileStream fs = new(sfbPath, FileMode.Open, FileAccess.Read); using FileStream fs = new(sfbPath, FileMode.Open, FileAccess.Read);
Parse(fs); Parse(fs);
@@ -135,6 +131,11 @@ namespace LibIRD
} }
} }
/// <summary>
/// Define JSON options once
/// </summary>
private readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true };
/// <summary> /// <summary>
/// Prints parameters extracted from PS3_DISC.SFB to a json object /// Prints parameters extracted from PS3_DISC.SFB to a json object
/// </summary> /// </summary>
@@ -142,7 +143,7 @@ namespace LibIRD
public void PrintJson(string jsonPath = null) public void PrintJson(string jsonPath = null)
{ {
// Serialise PS3_Disc.SFB data to a JSON object // Serialise PS3_Disc.SFB data to a JSON object
string json = JsonSerializer.Serialize(Field, new JsonSerializerOptions { WriteIndented = true }); string json = JsonSerializer.Serialize(Field, JsonOpts);
// If no path given, output to console // If no path given, output to console
if (jsonPath == null) if (jsonPath == null)
+10 -10
View File
@@ -43,13 +43,8 @@ namespace LibIRD
/// 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 file</param> /// <param name="sfoPath">Full file path to the PARAM.SFO file</param>
/// <exception cref="ArgumentNullException"></exception>
public ParamSFO(string sfoPath) public ParamSFO(string sfoPath)
{ {
// Validate file path
if (sfoPath == null || sfoPath.Length <= 0)
throw new ArgumentNullException(nameof(sfoPath));
// Read file as a stream, and parse file // Read file as a stream, and parse file
using FileStream fs = new(sfoPath, FileMode.Open, FileAccess.Read); using FileStream fs = new(sfoPath, FileMode.Open, FileAccess.Read);
Parse(fs); Parse(fs);
@@ -103,7 +98,7 @@ namespace LibIRD
- keyOffset[i]; - keyOffset[i];
// Read ith key name // Read ith key name
string key = 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 + dataOffset[i]; sfoStream.Position = dataTableStart + dataOffset[i];
@@ -112,13 +107,13 @@ namespace LibIRD
Field[key] = dataFormat[i] switch Field[key] = dataFormat[i] switch
{ {
// Non-null-terminated UTF-8 String // Non-null-terminated UTF-8 String
0x0004 => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])), 0x0004 => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])),
// Null-terminated UTF-8 String // Null-terminated UTF-8 String
0x0204 => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])).TrimEnd('\0'), 0x0204 => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])).TrimEnd('\0'),
// Integer // Integer
0x0404 => br.ReadInt32().ToString(), 0x0404 => br.ReadInt32().ToString(),
// Unknown data format, assume null-terminated string // Unknown data format, assume null-terminated string
_ => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])).TrimEnd('\0'), _ => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])).TrimEnd('\0'),
}; };
} }
} }
@@ -159,6 +154,11 @@ namespace LibIRD
} }
} }
/// <summary>
/// Define JSON options once
/// </summary>
private readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true };
/// <summary> /// <summary>
/// Prints parameters extracted from PARAM.SFO to a json object /// Prints parameters extracted from PARAM.SFO to a json object
/// </summary> /// </summary>
@@ -166,7 +166,7 @@ namespace LibIRD
public void PrintJson(string jsonPath = null) public void PrintJson(string jsonPath = null)
{ {
// Serialise PS3_Disc.SFB data to a JSON object // Serialise PS3_Disc.SFB data to a JSON object
string json = JsonSerializer.Serialize(Field, new JsonSerializerOptions { WriteIndented = true }); string json = JsonSerializer.Serialize(Field, JsonOpts);
// If no path given, output to console // If no path given, output to console
if (jsonPath == null) if (jsonPath == null)
+79 -87
View File
@@ -1,4 +1,5 @@
using System; using DiscUtils.Iso9660;
using System;
using System.IO; using System.IO;
using System.IO.Hashing; using System.IO.Hashing;
@@ -51,15 +52,6 @@ namespace LibIRD
/// </summary> /// </summary>
public class ReIRD : IRD public class ReIRD : IRD
{ {
#region Properties
/// <summary>
/// ISO file size
/// </summary>
private long Size { get; set; }
#endregion
#region Constructors #region Constructors
/// <summary> /// <summary>
@@ -67,26 +59,7 @@ 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>
/// <param name="layerbreak">Layerbreak value, in sectors</param> public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog, true) { }
/// <exception cref="InvalidDataException"></exception>
public ReIRD(string isoPath, string getKeyLog, long? layerbreak = null) : base(isoPath, getKeyLog, true)
{
// Generate Unique Identifier using ISO CRC32
UID = GenerateUID(isoPath);
// Determine ISO file size
Size = CalculateSize(isoPath);
// Generate Data 2 using Disc ID
DiscID = GenerateID(Size);
// Generate Disc PIC
byte[] pic = GeneratePIC(Size, layerbreak * SectorSize);
// Check that GetKey log matches expected PIC
if (!((ReadOnlySpan<byte>)PIC).SequenceEqual(pic))
throw new InvalidDataException("Unexpected PIC in .getkey.log");
}
/// <summary> /// <summary>
/// Constructor with optional additional region to generate a specific Disc ID /// Constructor with optional additional region to generate a specific Disc ID
@@ -94,23 +67,24 @@ namespace LibIRD
/// <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="layerbreak">Layerbreak value, in sectors</param>
/// <param name="uid">Unique ID, crc32 hash of ISO</param>
/// <param name="region">Disc Region</param> /// <param name="region">Disc Region</param>
public ReIRD(string isoPath, byte[] key, long? layerbreak = null, Region region = Region.NONE) : base() public ReIRD(string isoPath, byte[] key, long? layerbreak = null, uint? uid = null, Region region = Region.NONE) : base()
{ {
// Generate Unique Identifier using ISO CRC32 // If the ISO CRC32 is provided, use it as the Unique ID
UID = GenerateUID(isoPath); UID = uid == null ? 0x00000000 : (uint)uid;
// Determine ISO file size // Determine ISO file size
Size = CalculateSize(isoPath); long size = CalculateSize(isoPath);
// Set Disc Key // Set Disc Key
DiscKey = key; DiscKey = key;
// Generate Data 2 using Disc ID // Generate Data 2 using Disc ID
DiscID = GenerateID(Size, region); DiscID = GenerateID(size, region);
// Generate Disc PIC // Generate Disc PIC
PIC = GeneratePIC(Size, layerbreak * SectorSize); PIC = GeneratePIC(isoPath, size, layerbreak * SectorSize);
// Generate IRD fields // Generate IRD fields
GenerateIRD(isoPath, true); GenerateIRD(isoPath, true);
@@ -143,12 +117,13 @@ 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 = null, bool exactIRD = false) private static byte[] GeneratePIC(string isoPath, 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 provided layerbreak
if (layerbreak != null) if (layerbreak != null)
{ {
if (layerbreak <= 0 || (layerbreak >= size)) if (layerbreak <= 0 || (layerbreak >= size))
@@ -156,70 +131,78 @@ namespace LibIRD
if (layerbreak >= 2 * BDLayerSize || layerbreak % SectorSize != 0) if (layerbreak >= 2 * BDLayerSize || layerbreak % SectorSize != 0)
throw new ArgumentException("Unexpected layerbreak value", nameof(size)); throw new ArgumentException("Unexpected layerbreak value", nameof(size));
} }
// If layerbreak value was not set, assume it is a non-hybrid disc with default layerbreak else if (size > BDLayerSize)
long layer_break = layerbreak ?? BDLayerSize; {
// If no layerbreak provided, ensure ISO is not BD-Video hybrid
using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath);
CDReader reader = new(fs, true, true);
if (reader.DirectoryExists("\\BDMV"))
throw new ArgumentException("Layerbreak must be provided for BD-Video hybrid discs");
// Assume disc has default layerbreak
layerbreak = BDLayerSize;
}
// Generate the PIC based on the size and layerbreak of the ISO // 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
{ {
// Layer 0 start sector = 0x01000000 // Layer 0 start sector = 0x01000000
long l0_start_sector = 1048576; long l0_start_sector = 1048576;
// Layer 0 end sector = start sector + layerbreak - 2 // Layer 0 end sector = start sector + layerbreak - 2
long l0_end_sector = (layer_break / SectorSize) + l0_start_sector - 2; long l0_end_sector = ((long)layerbreak / SectorSize) + l0_start_sector - 2;
// Convert end sector location to hex values for PIC // Convert end sector location to hex values for PIC
byte[] l0es = [(byte)((l0_end_sector >> 24) & 0xFF), byte[] l0es = [(byte)((l0_end_sector >> 24) & 0xFF),
(byte)((l0_end_sector >> 16) & 0xFF), (byte)((l0_end_sector >> 16) & 0xFF),
(byte)((l0_end_sector >> 8) & 0xFF), (byte)((l0_end_sector >> 8) & 0xFF),
(byte)((l0_end_sector >> 0) & 0xFF)]; (byte)((l0_end_sector >> 0) & 0xFF)];
// Layer 1 start sector = end of disc (0x01EFFFFE) - layerbreak + 2 // Layer 1 start sector = end of disc (0x01EFFFFE) - layerbreak + 2
long l1_start_sector = 32505854 - (layer_break / SectorSize) + 2; long l1_start_sector = 32505854 - ((long)layerbreak! / SectorSize) + 2;
// Convert start of start sector location to hex values for PIC // Convert start of start sector location to hex values for PIC
byte[] l1ss = [(byte)((l1_start_sector >> 24) & 0xFF), byte[] l1ss = [(byte)((l1_start_sector >> 24) & 0xFF),
(byte)((l1_start_sector >> 16) & 0xFF), (byte)((l1_start_sector >> 16) & 0xFF),
(byte)((l1_start_sector >> 8) & 0xFF), (byte)((l1_start_sector >> 8) & 0xFF),
(byte)((l1_start_sector >> 0) & 0xFF)]; (byte)((l1_start_sector >> 0) & 0xFF)];
// Total sectors used = num_sectors + Layer 0 start + sectors_between_layers (usually 0x01358C00 - 0x00CA73FE - 3) // 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); long total_sectors = (size / SectorSize) + l0_start_sector + (l1_start_sector - l0_end_sector - 3);
byte[] ts = BitConverter.GetBytes((uint) total_sectors); byte[] ts = BitConverter.GetBytes((uint)total_sectors);
// Define the PIC // Define the PIC
pic = [ pic = [
// Initial portion of PIC (24 bytes) // Initial portion of PIC (24 bytes)
// [4098 bytes] [2x 0x00] ["DI"] [v1] [10units] [DI num] // [4098 bytes] [2x 0x00] ["DI"] [v1] [10units] [DI num]
0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x10, 0x00, 0x00, 0x20, 0x00, 0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x10, 0x00, 0x00, 0x20, 0x00,
// ["BDR"] [2 layers] // ["BDR"] [2 layers]
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
// Total sectors used on disc (4 bytes) // Total sectors used on disc (4 bytes)
ts[3], ts[2], ts[1], ts[0], ts[3], ts[2], ts[1], ts[0],
// 1st Layer sector start location (4 bytes) // 1st Layer sector start location (4 bytes)
0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
// 1st Layer sector end location (4 bytes), 0x00CA73FE for default BD layerbreak of 12219392 // 1st Layer sector end location (4 bytes), 0x00CA73FE for default BD layerbreak of 12219392
l0es[0], l0es[1], l0es[2], l0es[3], l0es[0], l0es[1], l0es[2], l0es[3],
// 32 bytes of zeros // 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,
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 // Initial portion of PIC again, for 2nd layer
// ["DI"] [v1] [11unit][DI num] // ["DI"] [v1] [11unit][DI num]
0x44, 0x49, 0x01, 0x11, 0x00, 0x01, 0x20, 0x00, 0x44, 0x49, 0x01, 0x11, 0x00, 0x01, 0x20, 0x00,
// ["BDR"] [2 layers] // ["BDR"] [2 layers]
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
// Total sectors used on disc // Total sectors used on disc
ts[3], ts[2], ts[1], ts[0], ts[3], ts[2], ts[1], ts[0],
// 2nd Layer sector start location, 0x01358C00 for default BD layerbreak of 12219392 // 2nd Layer sector start location, 0x01358C00 for default BD layerbreak of 12219392
l1ss[0], l1ss[1], l1ss[2], l1ss[3], l1ss[0], l1ss[1], l1ss[2], l1ss[3],
// 2nd Layer sector end location // 2nd Layer sector end location
0x01, 0xEF, 0xFF, 0xFE, 0x01, 0xEF, 0xFF, 0xFE,
// Remaining 32 bytes are zeroes // Remaining 32 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 ];
// 3k3y style: 0x03 at last byte // 3k3y style: 0x03 at last byte
if (exactIRD) if (exactIRD)
pic[114] = 0x03; 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
@@ -253,15 +236,19 @@ namespace LibIRD
/// Generates the UID field by computing the CRC32 hash of the ISO /// Generates the UID field by computing the CRC32 hash of the ISO
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception> /// <exception cref="FileNotFoundException"></exception>
private static uint GenerateUID(string isoPath) private static uint GenerateUID(string isoPath)
{ {
// Validate ISO path
ArgumentNullException.ThrowIfNull(isoPath, nameof(isoPath));
// Check file exists // Check file exists
var iso = new FileInfo(isoPath); FileInfo iso;
try
{
iso = new FileInfo(isoPath);
}
catch (Exception e)
{
throw new ArgumentException("Invalid ISO Path: " + e.Message);
}
if (!iso.Exists) if (!iso.Exists)
throw new FileNotFoundException(nameof(isoPath)); throw new FileNotFoundException(nameof(isoPath));
@@ -282,15 +269,20 @@ namespace LibIRD
/// Calculates ISO file size /// Calculates ISO file size
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception>
/// <exception cref="FileNotFoundException"></exception> /// <exception cref="FileNotFoundException"></exception>
private static long CalculateSize(string isoPath) private static long CalculateSize(string isoPath)
{ {
// Validate ISO path
ArgumentNullException.ThrowIfNull(isoPath, nameof(isoPath));
// Check file exists // Check file exists
var iso = new FileInfo(isoPath); FileInfo iso;
try
{
iso = new FileInfo(isoPath);
}
catch (Exception e)
{
throw new ArgumentException("Invalid ISO Path: " + e.Message);
}
if (!iso.Exists) if (!iso.Exists)
throw new FileNotFoundException(nameof(isoPath)); throw new FileNotFoundException(nameof(isoPath));
+1
View File
@@ -13,6 +13,7 @@ IRDKit is a tool that allows direct use of LibIRD functionality from a command l
- Printing info about all ISOs and IRDs in a folder: `irdkit info .` - Printing info about all ISOs and IRDs in a folder: `irdkit info .`
- Creating an IRD from an ISO: `irdkit create game.iso` - Creating an IRD from an ISO: `irdkit create game.iso`
- Finding differences between two IRDs: `irdkit diff game1.ird game2.ird` - Finding differences between two IRDs: `irdkit diff game1.ird game2.ird`
For detailed usage, read more [here](IRDKit). For detailed usage, read more [here](IRDKit).
## Using the LibIRD library ## Using the LibIRD library
+15
View File
@@ -0,0 +1,15 @@
dotnet publish -c Release -f net8.0 -r win-x86 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r win-x64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r win-arm64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r linux-x64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r linux-arm64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r osx-x64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r osx-arm64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/win-x86/publish/irdkit.exe" -Destination "./irdkit-win-x86.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/win-x64/publish/irdkit.exe" -Destination "./irdkit-win-x64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/win-arm64/publish/irdkit.exe" -Destination "./irdkit-win-arm64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/linux-x64/publish/irdkit" -Destination "./irdkit-linux-x64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/linux-arm64/publish/irdkit" -Destination "./irdkit-linux-arm64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/osx-x64/publish/irdkit" -Destination "./irdkit-osx-x64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/osx-arm64/publish/irdkit" -Destination "./irdkit-osx-arm64.zip" -CompressionLevel "Optimal"