9 Commits
Author SHA1 Message Date
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
8 changed files with 194 additions and 138 deletions
+5 -5
View File
@@ -9,7 +9,7 @@
<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.4.0</Version> <Version>0.5.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Deterous</Authors> <Authors>Deterous</Authors>
@@ -27,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>
+25 -14
View File
@@ -31,7 +31,7 @@ namespace IRDKit
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; }
@@ -68,9 +68,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
{ {
@@ -159,11 +159,13 @@ namespace IRDKit
} }
// Determine output IRD folder // Determine output IRD folder
string outputPath = Path.GetDirectoryName(opt.IRDPath); 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, irdPath: outputPath, verbose: opt.Verbose); ISO2IRD(file, irdPath: outputPath, keyPath: opt.KeyFile, verbose: opt.Verbose);
} }
else else
{ {
@@ -610,7 +612,9 @@ namespace IRDKit
missingOffsets1.Add(IRD2.FileKeys[i]); missingOffsets1.Add(IRD2.FileKeys[i]);
} }
// Print the file offsets that differ // Print the file offsets that differ
printText.AppendLine($"File Offsets not Present in {irdPath1}: {string.Join(", ", missingOffsets1)}"); 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)}"); printText.AppendLine($"File Offsets not Present in {irdPath2}: {string.Join(", ", missingOffsets2)}");
// Print any extra config data difference // Print any extra config data difference
@@ -717,14 +721,19 @@ namespace IRDKit
{ {
try try
{ {
// If key directory was given, append filename and .key
if (Directory.Exists(keyPath))
{
keyPath = Path.Combine(keyPath, Path.ChangeExtension(Path.GetFileName(isoPath), ".key"));
}
// Read key from .key file // Read key from .key file
byte[] discKey = File.ReadAllBytes(keyPath); byte[] discKey = File.ReadAllBytes(keyPath);
if (discKey == null || discKey.Length != 16) if (discKey == null || discKey.Length != 16)
Console.Error.WriteLine($"{hexKey} is not a valid key, detecting key automatically..."); Console.Error.WriteLine($"{hexKey} is not a valid key, detecting key automatically...");
else else
{ {
IRD ird1 = new ReIRD(isoPath, discKey, layerbreak);
Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(discKey)}"); Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(discKey)}");
IRD ird1 = new ReIRD(isoPath, discKey, layerbreak);
ird1.Write(irdPath); ird1.Write(irdPath);
if (verbose) if (verbose)
ird1.Print(); ird1.Print();
@@ -776,13 +785,13 @@ namespace IRDKit
try try
{ {
// Read key from .key file // Read key from .key file
byte[] discKey = File.ReadAllBytes(keyPath); byte[] discKey = File.ReadAllBytes(keyfilePath);
if (discKey == null || discKey.Length != 16) if (discKey == null || discKey.Length != 16)
Console.Error.WriteLine($"{hexKey} is not a valid key, detecting key automatically..."); Console.Error.WriteLine($"{hexKey} is not a valid key, detecting key automatically...");
else else
{ {
IRD ird1 = new ReIRD(isoPath, discKey, layerbreak);
Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(discKey)}"); Console.WriteLine($"Creating {irdPath} with Key: {Convert.ToHexString(discKey)}");
IRD ird1 = new ReIRD(isoPath, discKey, layerbreak);
ird1.Write(irdPath); ird1.Write(irdPath);
if (verbose) if (verbose)
ird1.Print(); ird1.Print();
@@ -809,8 +818,8 @@ namespace IRDKit
// Found .getkey.log file, check it is valid // Found .getkey.log file, check it is valid
try try
{ {
Console.WriteLine($"Creating {irdPath} with key from: {getKeyLog}"); Console.WriteLine($"Creating {irdPath} with key from: {logfilePath}");
IRD ird1 = new ReIRD(isoPath, getKeyLog); IRD ird1 = new ReIRD(isoPath, logfilePath);
ird1.Write(irdPath); ird1.Write(irdPath);
if (verbose) if (verbose)
ird1.Print(); ird1.Print();
@@ -834,11 +843,13 @@ namespace IRDKit
// 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);
} }
@@ -896,7 +907,7 @@ namespace IRDKit
Console.WriteLine($"Creating {irdPath} with Key from redump.org: {Convert.ToHexString(key)}"); Console.WriteLine($"Creating {irdPath} with Key from redump.org: {Convert.ToHexString(key)}");
try try
{ {
IRD ird = new ReIRD(isoPath, key, layerbreak); IRD ird = new ReIRD(isoPath, key, layerbreak, crc32UInt);
ird.Write(irdPath); ird.Write(irdPath);
if (verbose) if (verbose)
ird.Print(); ird.Print();
+90 -57
View File
@@ -270,7 +270,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>
@@ -715,7 +715,6 @@ namespace LibIRD
HashFiles(fs, reader, rootDir); HashFiles(fs, reader, rootDir);
if (FileCount != fileCount) if (FileCount != fileCount)
{ {
Console.WriteLine($"{isoPath} contains split 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;
@@ -891,8 +890,8 @@ namespace LibIRD
RegionEnd[^1] = (UpdateEnd / SectorSize) - 1; RegionEnd[^1] = (UpdateEnd / SectorSize) - 1;
// Determine MD5 hashes for each region // Determine MD5 hashes for each region
using MD5 md5 = MD5.Create(); int bufSectors = 1024;
byte[] buf = new byte[SectorSize]; byte[] buf = new byte[bufSectors * SectorSize];
for (int i = 0; i < RegionCount; i++) for (int i = 0; i < RegionCount; i++)
{ {
// Start reading data from first sector of region // Start reading data from first sector of region
@@ -900,19 +899,32 @@ namespace LibIRD
// Compute MD5 hash for just the region portion of the ISO file // Compute MD5 hash for just the region portion of the ISO file
int numBytes; int numBytes;
for (long j = RegionStart[i]; j <= RegionEnd[i]; j++) using MD5 md5 = MD5.Create();
int regionSectors = (int)(RegionEnd[i] - RegionStart[i]) + 1;
for (int j = bufSectors; j <= regionSectors; j += bufSectors)
{ {
// Read one sector at a time // Read into buffer
numBytes = fs.Read(buf, 0, buf.Length); numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read // TODO: Process partial buffer if non-zero is returned
if (numBytes < buf.Length) if (numBytes < buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly"); throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Process MD5 sum one sector at a time
// Process MD5 sum
md5.TransformBlock(buf, 0, buf.Length, null, 0); md5.TransformBlock(buf, 0, buf.Length, null, 0);
} }
// Read any remaining sectors
int bufRemainder = (int)(SectorSize * (regionSectors % bufSectors));
if (bufRemainder != 0)
{
numBytes = fs.Read(buf, 0, bufRemainder);
// TODO: Process partial buffer if non-zero is returned
if (numBytes < bufRemainder)
throw new InvalidFileSystemException("Disc region ended unexpectedly");
}
// Compute and store MD5 hash of region // Compute and store MD5 hash of region
md5.TransformFinalBlock(buf, 0, 0); md5.TransformFinalBlock(buf, 0, bufRemainder);
RegionHashes[i] = md5.Hash; RegionHashes[i] = md5.Hash;
} }
} }
@@ -938,70 +950,80 @@ namespace LibIRD
// Determine smallest file offset as first sector // Determine smallest file offset as first sector
long smallestOffset = fileClusters[0].Offset; long smallestOffset = fileClusters[0].Offset;
bool nonContiguous = false;
for (int i = 1; i < fileClusters.Length; i++) for (int i = 1; i < fileClusters.Length; i++)
{ {
if (fileClusters[i] == null) if (fileClusters[i] == null)
throw new InvalidFileSystemException($"Unexpected file extents for {filePath}"); throw new InvalidFileSystemException($"Unexpected file extents for {filePath}");
if (fileClusters[i].Offset * SectorSize != fileClusters[i - 1].Offset * SectorSize + fileClusters[i - 1].Count)
nonContiguous = true;
if (fileClusters[i].Offset < smallestOffset) if (fileClusters[i].Offset < smallestOffset)
smallestOffset = fileClusters[i].Offset; smallestOffset = fileClusters[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)
if (fileClusters.Length > 1) Console.WriteLine($"Non-contiguous file found: {filePath}");
Console.WriteLine($"Split file detected: {filePath}");
// Add file offset to keys // Add file offset to keys
FileKeys[FileCount] = firstSector; FileKeys[FileCount] = smallestOffset;
// Determine whether file is in encrypted or decrypted region // Determine whether file is in encrypted or decrypted region
bool encrypted = false; bool encrypted = false;
for (int i = RegionCount - 1; i > 0; i--) for (int i = RegionCount - 1; i > 0; i--)
{ {
if (RegionStart[i] <= firstSector) if (RegionStart[i] <= smallestOffset)
{ {
encrypted = i % 2 == 1; encrypted = i % 2 == 1;
break; break;
} }
} }
// Hash each non-contiguous portion of the ISO file // Read one sector at a time for small files
byte[] buf = new byte[SectorSize]; int bufSectors = (fileClusters[0].Count < 1024 * SectorSize) ? 1 : 1024;
MD5 md5 = MD5.Create(); byte[] buf = new byte[bufSectors * SectorSize];
// Hash each non-contiguous portion of the file
using MD5 md5 = MD5.Create();
for (int i = 0; i < fileClusters.Length; i++) for (int i = 0; i < fileClusters.Length; i++)
{ {
// Start reading data from the beginning of the ISO file // Start reading data from the beginning of the current extent
fs.Seek(fileClusters[i].Offset * SectorSize, SeekOrigin.Begin); fs.Seek(fileClusters[i].Offset * SectorSize, SeekOrigin.Begin);
int numBytes; int numBytes;
// Read all data before the first data sector // Read file in buffers
for (int j = 0; j < (fileClusters[i].Count / SectorSize); j++) long fileSectors = fileClusters[i].Count / SectorSize;
for (int j = 0; j <= fileSectors - bufSectors; j += bufSectors)
{ {
numBytes = fs.Read(buf, 0, buf.Length); numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read // TODO: Process partial buffer if non-zero is returned
if (numBytes < buf.Length) if (numBytes != buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly"); throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Decrypt sector if necessary
// Decrypt buffer if necessary
if (encrypted) if (encrypted)
buf = DecryptSector(buf, firstSector + j); DecryptSectors(ref buf, (int)fileClusters[i].Offset + j);
// Hash sector // Hash sector
md5.TransformBlock(buf, 0, numBytes, null, 0); md5.TransformBlock(buf, 0, buf.Length, null, 0);
} }
// Read remaining partial sector // Read remaining partial buffer
if (fileClusters[i].Count % SectorSize != 0) if ((fileClusters[i].Count % buf.Length) > 0)
{ {
numBytes = fs.Read(buf, 0, buf.Length); numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read // TODO: Process partial buffer if non-zero is returned
if (numBytes < buf.Length) if (numBytes != buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly"); throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Decrypt partial sector if necessary
// Decrypt buffer if necessary
if (encrypted) if (encrypted)
buf = DecryptSector(buf, firstSector + (int)(fileClusters[i].Count / SectorSize)); DecryptSectors(ref buf, (int)fileClusters[i].Offset + bufSectors * (int)(fileClusters[i].Count / buf.Length));
// Hash partial sector
md5.TransformBlock(buf, 0, (int)(fileClusters[i].Count % SectorSize), null, 0); // Hash partial buffer
md5.TransformBlock(buf, 0, (int)(fileClusters[i].Count % buf.Length), null, 0);
} }
} }
@@ -1019,38 +1041,49 @@ namespace LibIRD
} }
/// <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="offset">Number of bytes to decrypt</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)
{ {
ArgumentNullException.ThrowIfNull(buffer);
if (buffer.Length == 0 || buffer.Length % SectorSize != 0)
throw new ArgumentException("Encrypted buffer must be multiple of SectorSize");
// 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 // Decrypt buffer one sector at a time
byte[] iv = new byte[16]; for (int i = 0; i < buffer.Length; i += (int)SectorSize)
for (int i = 0; i < 16; i++)
{ {
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>
@@ -1064,7 +1097,7 @@ namespace LibIRD
CountFiles(dirInfo); CountFiles(dirInfo);
} }
#endregion #endregion
#region IRD File #region IRD File
@@ -1250,7 +1283,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);
+13 -4
View File
@@ -6,7 +6,7 @@
<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.4.0</Version> <Version>0.5.0</Version>
<PackageOutputPath>../nupkg</PackageOutputPath> <PackageOutputPath>../nupkg</PackageOutputPath>
<!-- Package Properties --> <!-- Package Properties -->
@@ -26,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 -2
View File
@@ -27,7 +27,7 @@ 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
@@ -131,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>
@@ -138,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)
+40 -42
View File
@@ -2,7 +2,6 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Hashing; using System.IO.Hashing;
using System.Security;
namespace LibIRD namespace LibIRD
{ {
@@ -60,7 +59,6 @@ namespace LibIRD
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <param name="getKeyLog">Path to the GetKey log file</param> /// <param name="getKeyLog">Path to the GetKey log file</param>
/// <exception cref="InvalidDataException"></exception>
public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog, true) public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog, true)
{ {
// Generate Unique Identifier using ISO CRC32 // Generate Unique Identifier using ISO CRC32
@@ -73,11 +71,12 @@ 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 // Generate Unique Identifier using ISO CRC32
UID = GenerateUID(isoPath); UID = uid == null ? GenerateUID(isoPath) : (uint)uid;
// Determine ISO file size // Determine ISO file size
long size = CalculateSize(isoPath); long size = CalculateSize(isoPath);
@@ -152,62 +151,62 @@ namespace LibIRD
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 = ((long)layerbreak / 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 - ((long)layerbreak! / 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
@@ -241,7 +240,6 @@ 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)
{ {
@@ -275,7 +273,7 @@ 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)
{ {