38 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
Deterous 563b05b44f Bump version 2024-01-31 20:24:34 +09:00
Deterous 07a3a3a7f1 Correct number of files for non-contiguous ISOs 2024-01-31 20:13:42 +09:00
Deterous df9bb8c7f2 Diff command for comparing IRDs 2024-01-31 16:56:35 +09:00
Deterous 1a74e8fed0 Better path handling 2024-01-31 15:03:37 +09:00
Deterous e6f6d1f41a Hash non-contiguous files 2024-01-31 14:44:35 +09:00
Deterous 2fe56e994e Add DiscUtils submodule 2024-01-30 18:19:56 +09:00
Deterous e910312d6e Fix param parsing 2023-12-23 08:56:13 +13:00
Deterous 26bde9d753 Simplify conditionals 2023-12-23 00:07:02 +13:00
Deterous c1fa640e0b Don't assume PARAM.SFO fields exist 2023-12-22 23:53:39 +13:00
Deterous ec6caca3e9 Output file path flag 2023-12-06 13:26:05 +13:00
Deterous 8ccbc8430c Print filename for ISO info, remove trailing comma for JSON 2023-12-06 12:56:31 +13:00
Deterous a036481560 Proper printing 2023-12-05 00:02:09 +13:00
Deterous ba7bacc11c Print all data to file 2023-12-04 23:51:07 +13:00
Deterous 8b82ec3d4e Catch filenotfound errors for non-PS3 ISOs 2023-12-04 23:45:04 +13:00
Deterous 0443ba5b9f Fix info command when not given outpath 2023-12-04 23:40:46 +13:00
Deterous 493c92220a Allow info command to operate in directory 2023-12-04 23:35:52 +13:00
Deterous 602394a182 Automatically detect key files with same name 2023-11-30 13:37:27 +13:00
Deterous 338f7a00b7 Fix issue with manual IRD creation 2023-11-30 12:51:15 +13:00
Deterous 7f9fd1c9d6 Fix info printing 2023-11-30 12:36:24 +13:00
13 changed files with 1548 additions and 484 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "DiscUtils"]
path = DiscUtils
url = https://github.com/Deterous/DiscUtils
Submodule
+1
Submodule DiscUtils added at 299030c20b
+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.2.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" PrivateAssets="All" />
<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="DiscUtils.Core" Version="0.16.13" /> <PackageReference Include="SabreTools.RedumpLib" Version="1.3.3" />
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+921 -148
View File
File diff suppressed because it is too large Load Diff
+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=`
+26
View File
@@ -7,6 +7,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibIRD", "LibIRD\LibIRD.csp
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRDKit", "IRDKit\IRDKit.csproj", "{9060E3AF-E4FB-436F-93A1-5C47389CB88E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRDKit", "IRDKit\IRDKit.csproj", "{9060E3AF-E4FB-436F-93A1-5C47389CB88E}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A8BB1F9F-FC8E-471C-9647-6FFF91D63BA7}"
ProjectSection(SolutionItems) = preProject
.gitattributes = .gitattributes
.gitignore = .gitignore
LICENSE.txt = LICENSE.txt
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscUtils.Core", "DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj", "{9E71F5CD-887D-4A61-8460-9F7E8F110B46}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscUtils.Iso9660", "DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj", "{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscUtils.Streams", "DiscUtils\Library\DiscUtils.Streams\DiscUtils.Streams.csproj", "{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -21,6 +35,18 @@ Global
{9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Debug|Any CPU.Build.0 = Debug|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.Build.0 = Release|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.Build.0 = Release|Any CPU
{9E71F5CD-887D-4A61-8460-9F7E8F110B46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E71F5CD-887D-4A61-8460-9F7E8F110B46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E71F5CD-887D-4A61-8460-9F7E8F110B46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E71F5CD-887D-4A61-8460-9F7E8F110B46}.Release|Any CPU.Build.0 = Release|Any CPU
{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}.Release|Any CPU.Build.0 = Release|Any CPU
{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
+399 -194
View File
@@ -1,12 +1,13 @@
using DiscUtils; using DiscUtils;
using DiscUtils.Iso9660; using DiscUtils.Iso9660;
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;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json;
namespace LibIRD namespace LibIRD
{ {
@@ -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
@@ -365,13 +368,12 @@ namespace LibIRD
/// <param name="discKey">Disc Key, byte array of length 16</param> /// <param name="discKey">Disc Key, byte array of length 16</param>
/// <param name="discID">Disc ID, byte array of length 16</param> /// <param name="discID">Disc ID, byte array of length 16</param>
/// <param name="discPIC">Disc PIC, byte array of length 115</param> /// <param name="discPIC">Disc PIC, byte array of length 115</param>
/// <param name="redump">True if redump-style IRD</param> /// <param name="redump">True if redump-style IRD (default: false)</param>
public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC, bool redump = false) public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC, bool redump = false)
{ {
// Parse ISO, Disc Key, Disc ID, and PIC // Parse ISO, Disc Key, Disc ID, and PIC
DiscKey = discKey; DiscKey = discKey;
GenerateD1(discKey); DiscID = discID;
GenerateD2(discID);
PIC = discPIC; PIC = discPIC;
// Generate IRD files from ISO // Generate IRD files from ISO
@@ -407,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));
@@ -441,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));
@@ -475,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
@@ -508,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));
@@ -541,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));
@@ -626,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");
@@ -641,34 +637,59 @@ 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);
bool title_id_found = ps3_DiscSFB.Field.TryGetValue("TITLE_ID", out string title_id); bool titleIDFound = ps3_DiscSFB.Field.TryGetValue("TITLE_ID", out string titleID);
// If a valid TITLE_ID field is present, remove the hyphen to fit into standard IRD file // If a valid TITLE_ID field is present, remove the hyphen to fit into standard IRD file
if (title_id_found && title_id.Length == 10 && title_id[4] == '-') if (titleIDFound && titleID.Length == 10 && titleID[4] == '-')
TitleID = string.Concat(title_id.AsSpan(0, 4), title_id.AsSpan(5, 5)); TitleID = string.Concat(titleID.AsSpan(0, 4), titleID.AsSpan(5, 5));
// If the version field is present, this is a multi-game disc // If the version field is present, this is a multi-game disc
// Redump-style IRDs use the VERSION field from PS3_DISC.SFB instead of VERSION from PARAM.SFO // Redump-style IRDs use the VERSION field from PS3_DISC.SFB instead of VERSION from PARAM.SFO
bool version_found = ps3_DiscSFB.Field.TryGetValue("VERSION", out string disc_version); bool discVersionFound = ps3_DiscSFB.Field.TryGetValue("VERSION", out string discVersion);
if (version_found) if (discVersionFound)
DiscVersion = disc_version; DiscVersion = discVersion;
} }
// 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);
// If PS3_DISC.SFB did not set TitleID, use PARAM.SFO TITLE_ID // If PS3_DISC.SFB did not set TitleID, use PARAM.SFO TITLE_ID
TitleID ??= paramSFO.Field["TITLE_ID"]; if (TitleID == null)
Title = paramSFO.Field["TITLE"]; {
// If PS3_DISC.SFB did not set DiscVersion, use PARAM.SFO VERSION bool titleIDFound = paramSFO.Field.TryGetValue("TITLE_ID", out string titleID);
DiscVersion ??= paramSFO.Field["VERSION"]; if (titleIDFound)
AppVersion = paramSFO.Field["APP_VER"]; TitleID = titleID.Length == 9 ? titleID : titleID.PadRight(9, '\0')[..9];
else
TitleID = "\0\0\0\0\0\0\0\0\0";
}
// Try use Title from PARAM.SFO
bool titleFound = paramSFO.Field.TryGetValue("TITLE", out string title);
Title = titleFound ? title : String.Empty;
// If PS3_DISC.SFB did not set DiscVersion, try use PARAM.SFO VERSION
if (DiscVersion == null)
{
bool discVersionFound = paramSFO.Field.TryGetValue("VERSION", out string discVersion);
if (discVersionFound)
DiscVersion = discVersion.Length == 5 ? discVersion : discVersion.PadRight(5, '\0')[..5];
else
DiscVersion = "\0\0\0\0\0";
}
// Try use App Version from PARAM.SFO
bool appVersionFound = paramSFO.Field.TryGetValue("APP_VER", out string appVersion);
if (appVersionFound)
AppVersion = appVersion.Length == 5 ? appVersion : appVersion.PadRight(5, '\0')[..5];
else
AppVersion = "\0\0\0\0\0";
} }
// Determine system update version // Determine system update version
@@ -680,25 +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)
throw new InvalidFileSystemException("Unexpected ISO filesystem error: "); {
Array.Sort(FileKeys, FileHashes); long[] tempFileKeys = FileKeys;
Array.Resize(ref tempFileKeys, (int)FileCount);
FileKeys = tempFileKeys;
Range<long, long>[][] tempFileExtents = FileExtents;
Array.Resize(ref tempFileExtents, (int)FileCount);
FileExtents = tempFileExtents;
}
// 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
@@ -710,38 +746,27 @@ 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) if (updateClusters == null && updateClusters.Length == 0 && updateClusters[0] == null)
{ throw new InvalidFileSystemException("Invalid file extents for PS3UPDAT.PUP");
// File too small for dedicated cluster, try get the offset from the file extents instead
DiscUtils.Streams.StreamExtent[] updateExtents = reader.PathToExtents("\\PS3_UPDATE\\PS3UPDAT.PUP"); // PS3UPDAT.PUP file begins at first byte of dedicated cluster
if (updateExtents == null || updateExtents.Length <= 0) UpdateOffset = SectorSize * updateClusters[0].Offset;
throw new InvalidFileSystemException("Unexpected PS3UPDAT.PUP file extent in ISO filestream"); // Update file ends at the last byte of the last cluster
// PS3UPDAT.PUP file begins at start of first extent UpdateEnd = SectorSize * updateClusters[^1].Offset + updateClusters[^1].Count;
UpdateOffset = updateExtents[0].Start;
// Update file ends at the last extent plus its length
UpdateEnd = updateExtents[^1].Start + updateExtents[^1].Length;
}
else
{
// PS3UPDAT.PUP file begins at first byte of dedicated cluster
UpdateOffset = updateClusters[0] != null ? SectorSize * updateClusters[0].Offset : 0;
// Update file ends at the last byte of the last cluster
UpdateEnd = SectorSize * (updateClusters[^1].Offset + updateClusters[^1].Count);
}
// Check PUP file Magic // Check PUP file Magic
fs.Seek(UpdateOffset, SeekOrigin.Begin); fs.Seek(UpdateOffset, SeekOrigin.Begin);
byte[] pupMagic = new byte[5]; byte[] pupMagic = new byte[5];
fs.Read(pupMagic, 0, pupMagic.Length); fs.Read(pupMagic, 0, pupMagic.Length);
// If magic is incorrect, set version to "0000" (unknown) // If magic is incorrect, set version to all nulls, "\0\0\0\0" (unknown)
if (Encoding.ASCII.GetString(pupMagic) != "SCEUF") if (Encoding.ASCII.GetString(pupMagic) != "SCEUF")
SystemVersion = "0000"; SystemVersion = "\0\0\0\0";
else else
{ {
// Determine location of version string // Determine location of version string
@@ -764,29 +789,24 @@ 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) if (sfbClusters == null && sfbClusters.Length == 0 && sfbClusters[0] == null)
{ throw new InvalidFileSystemException("Invalid file extents for PS3_DISC.SFB");
// File too small for dedicated cluster, try get the first sector from the file extents instead // End of header is at beginning of first byte of dedicated cluster
DiscUtils.Streams.StreamExtent[] sfbExtents = reader.PathToExtents("\\PS3_DISC.SFB"); FirstDataSector = sfbClusters[0].Offset;
if (sfbExtents == null || sfbExtents.Length <= 0)
throw new InvalidFileSystemException("Unexpected PS3UPDAT.PUP file extent in ISO filestream");
FirstDataSector = sfbExtents[0].Start;
}
else
{
// End of header is at beginning of first byte of dedicated cluster
FirstDataSector = sfbClusters[0] != null ? sfbClusters[0].Offset : 0;
}
// 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);
@@ -814,9 +834,13 @@ 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 // Start reading data from after last file (PS3UPDAT.PUP)
fs.Seek(UpdateEnd, SeekOrigin.Begin); fs.Seek(UpdateEnd, SeekOrigin.Begin);
byte[] buf = new byte[SectorSize]; byte[] buf = new byte[SectorSize];
int numBytes = (int)SectorSize; int numBytes = (int)SectorSize;
@@ -835,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);
@@ -850,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];
@@ -879,146 +902,119 @@ 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())
{ {
string filePath = fileInfo.FullName; string filePath = fileInfo.FullName;
// Try get the first sector from the file extents instead // Determine the extents of the file via clusters
DiscUtils.Streams.StreamExtent[] fileExtents = reader.PathToExtents(filePath); Range<long, long>[] fileExtent = reader.PathToClusters(filePath);
if (fileExtents == null || fileExtents.Length <= 0)
throw new InvalidFileSystemException("Unexpected file extent in ISO filestream for " + filePath);
if (fileExtents.Length > 1)
throw new InvalidFileSystemException("Non-contiguous file detected");
long firstByte = fileExtents[0].Start;
int firstSector = (int)(firstByte / 2048);
long fileLength = fileExtents[0].Length;
FileKeys[FileCount] = firstSector;
// Determine whether file is in encrypted or decrypted region // If invalid clusters were returned, we can't hash this file
bool encrypted = false; if (fileExtent == null && fileExtent.Length == 0)
for (int i = RegionCount - 1; i > 0; i--) throw new InvalidFileSystemException($"Unexpected file extents for {filePath}");
// Determine smallest file offset as first sector
long smallestOffset = fileExtent[0].Offset;
bool nonContiguous = false;
for (int i = 1; i < fileExtent.Length; i++)
{ {
if (RegionStart[i] <= firstSector) if (fileExtent[i] == null)
{ throw new InvalidFileSystemException($"Unexpected file extents for {filePath}");
encrypted = i % 2 == 1;
break; if (fileExtent[i].Offset * SectorSize != fileExtent[i - 1].Offset * SectorSize + fileExtent[i - 1].Count)
} nonContiguous = true;
if (fileExtent[i].Offset < smallestOffset)
smallestOffset = fileExtent[i].Offset;
} }
// Start reading data from the beginning of the ISO file // If already encountered file offset, skip this file
fs.Seek(firstByte, SeekOrigin.Begin); if (Array.Exists(FileKeys, element => element == smallestOffset))
byte[] buf = new byte[SectorSize]; continue;
int numBytes; else if (nonContiguous)
// Read all data before the first data sector Console.WriteLine($"Non-contiguous file found: {filePath}");
MD5 md5 = MD5.Create();
for (int i = 0; i < (fileLength / SectorSize); i++)
{
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 + i);
// Hash sector
md5.TransformBlock(buf, 0, numBytes, null, 0);
}
// Read remaining partial sector
if (fileLength % 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)(fileLength / SectorSize));
// Hash partial sector
md5.TransformBlock(buf, 0, (int)(fileLength % SectorSize), null, 0);
}
// Finalise and store MD5 hash // Add file offset to keys and extents to extents
md5.TransformFinalBlock(buf, 0, 0); FileKeys[FileCount] = smallestOffset;
FileHashes[FileCount] = md5.Hash; FileExtents[FileCount] = fileExtent;
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>
@@ -1032,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
@@ -1099,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);
@@ -1147,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);
@@ -1214,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);
@@ -1239,7 +1438,7 @@ namespace LibIRD
// Read UID (for Version 8 onwards) // Read UID (for Version 8 onwards)
if (version > 7) if (version > 7)
uid = br.ReadUInt16(); uid = br.ReadUInt32();
// Read and CRC32 hash // Read and CRC32 hash
byte[] crc = br.ReadBytes(4); byte[] crc = br.ReadBytes(4);
@@ -1268,11 +1467,14 @@ namespace LibIRD
/// Prints IRD fields to console /// Prints IRD fields to console
/// </summary> /// </summary>
/// <param name="printPath">Optional path to save file to</param> /// <param name="printPath">Optional path to save file to</param>
public void Print(string printPath = null) public void Print(string printPath = null, string irdName = null)
{ {
// Build string from parameters // Build string from parameters
StringBuilder printText = new(); StringBuilder printText = new();
printText.AppendLine("IRD Contents:"); if (irdName == null)
printText.AppendLine("IRD Contents:");
else
printText.AppendLine($"IRD Contents: {irdName}");
printText.AppendLine("============="); printText.AppendLine("=============");
// Append IRD fields to string builder // Append IRD fields to string builder
@@ -1305,7 +1507,7 @@ namespace LibIRD
} }
else else
{ {
File.WriteAllText(printPath, printText.ToString()); File.AppendAllText(printPath, printText.ToString());
} }
} }
@@ -1314,7 +1516,7 @@ namespace LibIRD
/// Prints IRD fields to a json object /// Prints IRD fields to a json object
/// </summary> /// </summary>
/// <param name="jsonPath">Optionally print to json file</param> /// <param name="jsonPath">Optionally print to json file</param>
public void PrintJson(string jsonPath = null) public void PrintJson(string jsonPath = null, bool single = true)
{ {
// Build string from parameters // Build string from parameters
StringBuilder json = new(); StringBuilder json = new();
@@ -1334,11 +1536,14 @@ namespace LibIRD
json.AppendLine($" \"Extra Config\": \"{ExtraConfig:X4}\","); json.AppendLine($" \"Extra Config\": \"{ExtraConfig:X4}\",");
if (Attachments != 0x0000) if (Attachments != 0x0000)
json.AppendLine($" \"Attachments\": \"{Attachments:X4}\","); json.AppendLine($" \"Attachments\": \"{Attachments:X4}\",");
json.AppendLine($" \"Unique ID\": \"{UID:X8}"); json.AppendLine($" \"Unique ID\": \"{UID:X8}\",");
json.AppendLine($" \"Data 1 Key\": \"{Convert.ToHexString(Data1Key)}\","); json.AppendLine($" \"Data 1 Key\": \"{Convert.ToHexString(Data1Key)}\",");
json.AppendLine($" \"Data 2 Key\": \"{Convert.ToHexString(Data2Key)}\","); json.AppendLine($" \"Data 2 Key\": \"{Convert.ToHexString(Data2Key)}\",");
json.AppendLine($" \"PIC\": \"{Convert.ToHexString(PIC)}\""); json.AppendLine($" \"PIC\": \"{Convert.ToHexString(PIC)}\"");
json.AppendLine("}"); if (single)
json.AppendLine("}");
else
json.AppendLine("},");
// If no path given, output to console // If no path given, output to console
if (jsonPath == null) if (jsonPath == null)
+18 -7
View File
@@ -2,32 +2,43 @@
<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.2.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 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>
<PackageTags>ps3 iso ird redump</PackageTags> <PackageTags>ps3 iso ird redump</PackageTags>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="./README.md" Pack="true" PackagePath=""/> <None Include="./README.md" Pack="true" PackagePath="" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DiscUtils.Core" Version="0.16.13" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj" PrivateAssets="All" />
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj" PrivateAssets="All" />
<PackageReference Include="DiscUtils.Streams" Version="0.16.13" /> <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>
+12 -8
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);
@@ -103,11 +99,14 @@ namespace LibIRD
/// Prints formatted parameters extracted from PS3_DISC.SFB to console /// Prints formatted parameters extracted from PS3_DISC.SFB to console
/// </summary> /// </summary>
/// <param name="printPath">Optionally print to text file</param> /// <param name="printPath">Optionally print to text file</param>
public void Print(string printPath = null) public void Print(string printPath = null, string isoName = null)
{ {
// Build string from parameters // Build string from parameters
StringBuilder printText = new(); StringBuilder printText = new();
printText.AppendLine("PS3_DISC.SFB Contents:"); if (isoName != null)
printText.AppendLine($"PS3_DISC.SFB Contents: {isoName}");
else
printText.AppendLine("PS3_DISC.SFB Contents:");
printText.AppendLine("======================"); printText.AppendLine("======================");
// Loop through all parameters in PARAM.SFO // Loop through all parameters in PARAM.SFO
@@ -132,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>
@@ -139,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)
+16 -13
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'),
}; };
} }
} }
@@ -127,12 +122,15 @@ namespace LibIRD
/// Prints formatted parameters extracted from PARAM.SFO to console /// Prints formatted parameters extracted from PARAM.SFO to console
/// </summary> /// </summary>
/// <param name="printPath">Optionally print to text file</param> /// <param name="printPath">Optionally print to text file</param>
public void Print(string printPath = null) public void Print(string printPath = null, string isoName = null)
{ {
// Build string from parameters // Build string from parameters
StringBuilder printText = new(); StringBuilder printText = new();
printText.AppendLine("PARAM.SFO Contents:"); if (isoName != null)
printText.AppendLine("===================="); printText.AppendLine($"PARAM.SFO Contents: {isoName}");
else
printText.AppendLine("PARAM.SFO Contents:");
printText.AppendLine("===================");
// Loop through all parameters in PARAM.SFO // Loop through all parameters in PARAM.SFO
foreach (KeyValuePair<string, string> field in Field) foreach (KeyValuePair<string, string> field in Field)
@@ -156,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>
@@ -163,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) 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));
+6 -8
View File
@@ -8,16 +8,14 @@ IRD files contain a summary of what data is on a PlayStation 3 disc. It can be u
## 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 a command line interface. The basic usage is:
``` - Printing info about an ISO: `irdkit info game.iso`
irdkit create game.iso - Printing info about all ISOs and IRDs in a folder: `irdkit info .`
``` - Creating an IRD from an ISO: `irdkit create game.iso`
- 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
LibIRD was originally made for creating reproducible, redump-style IRDs when dumping PS3 discs with [MPF](https://github.com/SabreTools/MPF). If you wish to integrate LibIRD into your own application, read the examples [here](LibIRD). LibIRD was originally made for creating reproducible, redump-style IRDs when dumping PS3 discs with [MPF](https://github.com/SabreTools/MPF). If you wish to integrate LibIRD into your own application, read the examples [here](LibIRD).
## Limitations
Currently, LibIRD does not produce correct IRDs for ISOs with non-contiguous files. Therefore, do not consider this library "stable" until v1.0.0 is released.
+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"