Decrypt and hash files, further cleaning

This commit is contained in:
Deterous
2023-11-10 16:13:56 +13:00
parent db453da1ee
commit 8bebd9a722
4 changed files with 355 additions and 156 deletions
+29 -20
View File
@@ -11,27 +11,15 @@ namespace BuildIRD
{ {
Console.OutputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8;
// Create new reproducible redump-style IRD with a key // Create new reproducible redump-style IRD with a key file
byte[] discKey = new byte[] { 0x1A, 0x2D, 0x88, 0xFC, 0x19, 0x37, 0x27, 0x44, 0x11, 0x5E, 0xE9, 0x83, 0xA0, 0x47, 0xE2, 0xD5 };
string fileName = "./game.iso";
try try
{ {
IRD ird1 = new ReIRD(fileName, discKey); // Read key from .key file
ird1.Write("./test1.ird"); byte[] discKey = File.ReadAllBytes("./game.key");
Console.WriteLine("IRD for " + fileName + " created");
// Create new reproducible redump-style IRD with a GetKey log IRD ird1 = new ReIRD("./game.iso", discKey);
string logPath = "./log.getkey.log"; ird1.Write("./test1.ird");
try Console.WriteLine("IRD created using .key:");
{
IRD ird2 = new ReIRD(fileName, logPath);
ird2.Write("./test2.ird");
Console.WriteLine("IRD for " + fileName + " created");
}
catch (FileNotFoundException)
{
Console.WriteLine("File not found: " + logPath);
}
// Read IRD and print details to console // Read IRD and print details to console
IRD ird = IRD.Read("./test1.ird"); IRD ird = IRD.Read("./test1.ird");
@@ -42,9 +30,30 @@ namespace BuildIRD
Console.WriteLine("Game Version: " + ird.GameVersion); Console.WriteLine("Game Version: " + ird.GameVersion);
Console.WriteLine("App Version: " + ird.AppVersion); Console.WriteLine("App Version: " + ird.AppVersion);
} }
catch (FileNotFoundException) catch (FileNotFoundException e)
{ {
Console.WriteLine("File not found: " + fileName); Console.WriteLine("File not found: " + e.FileName);
}
// Create new reproducible redump-style IRD with a GetKey log
try
{
IRD ird2 = new ReIRD("./game.iso", "./log.getkey.log");
ird2.Write("./test2.ird");
Console.WriteLine("IRD created using .getkey.log:");
// Read IRD and print details to console
IRD ird = IRD.Read("./test2.ird");
Console.WriteLine("IRD Version: " + ird.Version);
Console.WriteLine("Title ID: " + ird.TitleID);
Console.WriteLine("Title: " + ird.Title);
Console.WriteLine("System Version: " + ird.SystemVersion);
Console.WriteLine("Game Version: " + ird.GameVersion);
Console.WriteLine("App Version: " + ird.AppVersion);
}
catch (FileNotFoundException e)
{
Console.WriteLine("File not found: " + e.FileName);
} }
} }
} }
+222 -59
View File
@@ -1,5 +1,5 @@
using DiscUtils.Iso9660; using DiscUtils;
using DiscUtils; using DiscUtils.Iso9660;
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
@@ -12,27 +12,33 @@ namespace LibIRD
/// <summary> /// <summary>
/// ISO Rebuild Data /// ISO Rebuild Data
/// </summary> /// </summary>
/// <remarks>Generates IRD fields and writes to an IRD file</remarks> /// <remarks>Generates IRD fields and reads/writes IRD files</remarks>
public class IRD public class IRD
{ {
#region Constants #region Constants
/// <summary>
/// Blu-ray ISO sector size in bytes
/// </summary>
/// <remarks>2048</remarks>
private protected const uint SectorSize = 2048;
/// <summary>
/// Size of a blu-ray layer in bytes (BD-25 max size)
/// </summary>
/// <remarks>12219392 sectors, default PS3 layerbreak value</remarks>
private protected const long BDLayerSize = 25025314816;
/// <summary> /// <summary>
/// IRD file signature /// IRD file signature
/// </summary> /// </summary>
/// <remarks>"3IRD"</remarks> /// <remarks>"3IRD"</remarks>
private static readonly byte[] Magic = { 0x33, 0x49, 0x52, 0x44 }; private static readonly byte[] Magic = { 0x33, 0x49, 0x52, 0x44 };
/// <summary>
/// Blu-ray ISO sector size in bytes
/// </summary>
/// <remarks>2048</remarks>
private protected static readonly uint SectorSize = 2048;
/// <summary> /// <summary>
/// MD5 hash of null /// MD5 hash of null
/// </summary> /// </summary>
private protected static readonly byte[] NullMD5 = new byte[] { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e }; private static readonly byte[] NullMD5 = new byte[] { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e };
/// <summary> /// <summary>
/// AES CBC Encryption Key for Data 1 (Disc Key) /// AES CBC Encryption Key for Data 1 (Disc Key)
@@ -91,6 +97,27 @@ namespace LibIRD
/// <remarks>Reserved, usually set to 0x0000</remarks> /// <remarks>Reserved, usually set to 0x0000</remarks>
public ushort Attachments { get; set; } = 0x0000; // Default to zero public ushort Attachments { get; set; } = 0x0000; // Default to zero
/// <summary>
/// Disc Key
/// </summary>
/// <remarks>16 bytes</remarks>
public byte[] DiscKey
{
get { return _discKey; }
set
{
if (value != null && value.Length == 16)
{
_discKey = value;
_data1Key = GenerateD1(value);
}
else
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(value));
}
}
private byte[] _discKey;
// TODO: Link Data1Key and Disc Key
/// <summary> /// <summary>
/// D1 key /// D1 key
/// </summary> /// </summary>
@@ -101,13 +128,36 @@ namespace LibIRD
set set
{ {
if (value != null && value.Length == 16) if (value != null && value.Length == 16)
{
_data1Key = value; _data1Key = value;
_discKey = GenerateDiscKey(value);
}
else else
throw new ArgumentException("Data 1 Key must be a byte array of length 16", nameof(value)); throw new ArgumentException("Data 1 Key must be a byte array of length 16", nameof(value));
} }
} }
private byte[] _data1Key; private byte[] _data1Key;
/// <summary>
/// D2 key
/// </summary>
/// <remarks>16 bytes</remarks>
public byte[] DiscID
{
get { return _discID; }
set
{
if (value != null && value.Length == 16)
{
_discID = value;
_data2Key = GenerateD2(value);
}
else
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(value));
}
}
private byte[] _discID;
/// <summary> /// <summary>
/// D2 key /// D2 key
/// </summary> /// </summary>
@@ -118,7 +168,10 @@ namespace LibIRD
set set
{ {
if (value != null && value.Length == 16) if (value != null && value.Length == 16)
{
_data2Key = value; _data2Key = value;
_discID = GenerateDiscID(value);
}
else else
throw new ArgumentException("Data 2 Key must be a byte array of length 16", nameof(value)); throw new ArgumentException("Data 2 Key must be a byte array of length 16", nameof(value));
} }
@@ -247,8 +300,6 @@ namespace LibIRD
/// </summary> /// </summary>
private long[] RegionEnd { get; set; } private long[] RegionEnd { get; set; }
private protected long Size { get; private set; }
#endregion #endregion
#region Constructors #region Constructors
@@ -301,10 +352,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
/// </summary> /// </summary>
private protected IRD(string isoPath) private protected IRD()
{ {
// Assumes that internally derived class will set D1/D2/PIC in its own constructor // Assumes that internally derived class will set fields in its own constructor
GenerateIRD(isoPath);
} }
/// <summary> /// <summary>
@@ -317,6 +367,7 @@ namespace LibIRD
public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC) public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC)
{ {
// Parse ISO, Disc Key, Disc ID, and PIC // Parse ISO, Disc Key, Disc ID, and PIC
DiscKey = discKey;
GenerateD1(discKey); GenerateD1(discKey);
GenerateD2(discID); GenerateD2(discID);
PIC = discPIC; PIC = discPIC;
@@ -343,7 +394,7 @@ namespace LibIRD
#endregion #endregion
#region Methods #region Property Generation
/// <summary> /// <summary>
/// Generates Data 1, via AES-128 CBC decryption of a Disc Key /// Generates Data 1, via AES-128 CBC decryption of a Disc Key
@@ -351,7 +402,8 @@ namespace LibIRD
/// <param name="key">Byte array containing AES encrypted Disc Key</param> /// <param name="key">Byte array containing AES encrypted Disc Key</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
private protected void GenerateD1(byte[] key) /// <exception cref="InvalidOperationException"></exception>
private protected static byte[] GenerateD1(byte[] key)
{ {
// Validate key // Validate key
if (key == null) if (key == null)
@@ -376,22 +428,58 @@ namespace LibIRD
cs.FlushFinalBlock(); cs.FlushFinalBlock();
// Save decrypted key to field // Save decrypted key to field
Data1Key = stream.ToArray(); return stream.ToArray();
}
/// <summary>
/// Generates the Disc key, via AES-128 CBC encryption of a Data 1 Key
/// </summary>
/// <param name="d1">Byte array containing AES decrypted Data 1 Key</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private protected static byte[] GenerateDiscKey(byte[] d1)
{
// Validate key
if (d1 == null)
throw new ArgumentNullException(nameof(d1));
if (d1.Length != 16)
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(d1));
// Setup AES decryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
// Set AES settings
aes.Key = D1AesKey;
aes.IV = D1AesIV;
aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC;
// Perform AES decryption
using MemoryStream stream = new();
using ICryptoTransform enc = aes.CreateEncryptor();
using CryptoStream cs = new(stream, enc, CryptoStreamMode.Write);
cs.Write(d1, 0, 16);
cs.FlushFinalBlock();
// Save decrypted key to field
return stream.ToArray();
} }
/// <summary> /// <summary>
/// Generates Data 2, via AES-128 CBC encryption of a Disc ID /// Generates Data 2, via AES-128 CBC encryption of a Disc ID
/// </summary> /// </summary>
/// <param name="id">Byte array containing AES decrypted Disc ID</param> /// <param name="d2">Byte array containing AES decrypted Disc ID</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
private protected void GenerateD2(byte[] id) /// <exception cref="InvalidOperationException"></exception>
private protected static byte[] GenerateD2(byte[] d2)
{ {
// Validate id // Validate id
if (id == null) if (d2 == null)
throw new ArgumentNullException(nameof(id)); throw new ArgumentNullException(nameof(d2));
if (id.Length != 16) if (d2.Length != 16)
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(id)); throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
// Setup AES encryption // Setup AES encryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings"); using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
@@ -406,14 +494,56 @@ namespace LibIRD
using MemoryStream stream = new(); using MemoryStream stream = new();
using ICryptoTransform enc = aes.CreateEncryptor(); using ICryptoTransform enc = aes.CreateEncryptor();
using CryptoStream cs = new(stream, enc, CryptoStreamMode.Write); using CryptoStream cs = new(stream, enc, CryptoStreamMode.Write);
cs.Write(id, 0, 16); cs.Write(d2, 0, 16);
cs.FlushFinalBlock(); cs.FlushFinalBlock();
// Save encrypted key to field // Save encrypted key to field
Data2Key = stream.ToArray(); return stream.ToArray();
} }
private void ParseGetKeyLog(string getKeyLog) /// <summary>
/// Generates Disc ID, via AES-128 CBC decryption of a Data 2 Key
/// </summary>
/// <param name="d2">Byte array containing AES encrypted Data 2 Key</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private protected static byte[] GenerateDiscID(byte[] d2)
{
// Validate id
if (d2 == null)
throw new ArgumentNullException(nameof(d2));
if (d2.Length != 16)
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
// Setup AES encryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
// Set AES settings
aes.Key = D2AesKey;
aes.IV = D2AesIV;
aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC;
// Perform AES encryption
using MemoryStream stream = new();
using ICryptoTransform dec = aes.CreateDecryptor();
using CryptoStream cs = new(stream, dec, CryptoStreamMode.Write);
cs.Write(d2, 0, 16);
cs.FlushFinalBlock();
// Save encrypted key to field
return stream.ToArray();
}
/// <summary>
/// Generates Data1Key, Data2Key, and PIC from the .getkey.log file
/// </summary>
/// <param name="getKeyLog">Path to the .getkey.log file</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidDataException"></exception>
private protected void ParseGetKeyLog(string getKeyLog)
{ {
// Validate .getkey.log file path // Validate .getkey.log file path
@@ -485,8 +615,8 @@ namespace LibIRD
} }
// Parse DiscKey, DiscID, and PIC // Parse DiscKey, DiscID, and PIC
GenerateD1(discKey); DiscKey = discKey;
GenerateD2(discID); DiscID = discID;
PIC = discPIC; PIC = discPIC;
} }
@@ -497,20 +627,8 @@ namespace LibIRD
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception> /// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidFileSystemException"></exception> /// <exception cref="InvalidFileSystemException"></exception>
private void GenerateIRD(string isoPath) private protected void GenerateIRD(string isoPath)
{ {
// Validate ISO path
if (isoPath == null || isoPath.Length <= 0)
throw new ArgumentNullException(nameof(isoPath));
// Check file exists
var iso = new FileInfo(isoPath);
if (!iso.Exists)
throw new FileNotFoundException(nameof(isoPath));
// Calculate file size
Size = iso.Length;
// Parse ISO file as a file stream // Parse ISO file as a file stream
using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath); using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath);
// Validate ISO file stream // Validate ISO file stream
@@ -562,6 +680,10 @@ namespace LibIRD
Array.Sort(FileKeys, FileHashes); Array.Sort(FileKeys, FileHashes);
} }
#endregion
#region Reading ISO
/// <summary> /// <summary>
/// Retreives and stores the system version /// Retreives and stores the system version
/// </summary> /// </summary>
@@ -782,47 +904,51 @@ namespace LibIRD
if (fileExtents.Length > 1) if (fileExtents.Length > 1)
throw new InvalidFileSystemException("Non-contiguous file detected"); throw new InvalidFileSystemException("Non-contiguous file detected");
long firstByte = fileExtents[0].Start; long firstByte = fileExtents[0].Start;
int firstSector = (int)(firstByte / 2048);
long fileLength = fileExtents[0].Length; long fileLength = fileExtents[0].Length;
FileKeys[FileCount] = firstByte / 2048; FileKeys[FileCount] = firstSector;
// 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] <= firstByte / 2048) if (RegionStart[i] <= firstSector)
{ {
encrypted = i % 2 == 1; encrypted = i % 2 == 1;
break; break;
} }
} }
// Decrypt file if encrypted
if (encrypted)
{
FileHashes[FileCount] = NullMD5;
FileCount++;
continue;
}
// Start reading data from the beginning of the ISO file // Start reading data from the beginning of the ISO file
fs.Seek(firstByte, SeekOrigin.Begin); fs.Seek(firstByte, SeekOrigin.Begin);
byte[] buf = new byte[SectorSize]; byte[] buf = new byte[SectorSize];
int numBytes; int numBytes;
// Read all data before the first data sector // Read all data before the first data sector
MD5 md5 = MD5.Create(); MD5 md5 = MD5.Create();
for (long i = 0; i < (fileLength / SectorSize); i++) for (int i = 0; i < (fileLength / SectorSize); i++)
{ {
numBytes = fs.Read(buf, 0, buf.Length); numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read // Check that an entire sector was read
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
if (encrypted)
buf = DecryptSector(buf, firstSector + i);
// Hash sector
md5.TransformBlock(buf, 0, numBytes, null, 0); md5.TransformBlock(buf, 0, numBytes, null, 0);
} }
// Read remaining partial sector // Read remaining partial sector
if (fileLength % SectorSize != 0) if (fileLength % SectorSize != 0)
{ {
numBytes = fs.Read(buf, 0, (int)(fileLength % SectorSize)); numBytes = fs.Read(buf, 0, buf.Length);
md5.TransformBlock(buf, 0, numBytes, null, 0); // 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 // Finalise and store MD5 hash
@@ -838,6 +964,41 @@ namespace LibIRD
} }
} }
/// <summary>
/// Decrypts a given sector byte array
/// </summary>
/// <param name="sector">Byte array to be decrypted</param>
/// <exception cref="InvalidOperationException"></exception>
private protected byte[] DecryptSector(byte[] sector, int sectorNumber)
{
// Setup AES decryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
// Set AES settings
aes.Key = DiscKey;
aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC;
// Determine Initial Value based on sector number
byte[] iv = new byte[16];
for (int i = 0; i < 16; i++)
{
byte a = (byte)(sectorNumber & 0xFF);
iv[16 - i - 1] = (byte)(sectorNumber & 0xFF);
sectorNumber >>= 8;
}
aes.IV = iv;
// 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>
/// Recursively determines file count /// Recursively determines file count
/// </summary> /// </summary>
@@ -849,6 +1010,10 @@ namespace LibIRD
CountFiles(dirInfo); CountFiles(dirInfo);
} }
#endregion
#region IRD File
/// <summary> /// <summary>
/// Write IRD data to file /// Write IRD data to file
/// </summary> /// </summary>
@@ -1011,13 +1176,11 @@ namespace LibIRD
// Read Header // Read Header
uint headerLength = br.ReadUInt32(); uint headerLength = br.ReadUInt32();
byte[] header = new byte[headerLength]; byte[] header = br.ReadBytes((int)headerLength);
br.Read(header, 0, header.Length);
// Read Footer // Read Footer
uint footerLength = br.ReadUInt32(); uint footerLength = br.ReadUInt32();
byte[] footer = new byte[footerLength]; byte[] footer = br.ReadBytes((int)footerLength);
br.Read(footer, 0, footer.Length);
// Read region hashes // Read region hashes
byte regionCount = br.ReadByte(); byte regionCount = br.ReadByte();
+103 -76
View File
@@ -51,18 +51,78 @@ namespace LibIRD
/// </summary> /// </summary>
public class ReIRD : IRD public class ReIRD : IRD
{ {
#region Properties
#region Constants
/// <summary> /// <summary>
/// Size of a blu-ray layer in bytes (BD-25 max size) /// ISO file size
/// </summary> /// </summary>
/// <remarks>Can be used as the default PS3 layerbreak value</remarks> public long Size { get; private set; }
private const long BDLayerSize = 25025314816; // 12219392 sectors
#endregion #endregion
#region Property Generation #region Constructors
/// <summary>
/// Constructor using .getkey.log for Disc Key
/// </summary>
/// <param name="isoPath">Path to the ISO</param>
/// <param name="getKeyLog">Path to the GetKey log file</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidDataException"></exception>
public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog)
{
// 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);
// Check that GetKey log matches expected Disc ID
//if (!((ReadOnlySpan<byte>)Data2Key).SequenceEqual(d2))
// throw new InvalidDataException("Unexpected Disc ID in .getkey.log");
// Generate Disc PIC
byte[] pic = GeneratePIC(Size);
// Check that GetKey log matches expected PIC
if (!((ReadOnlySpan<byte>)PIC).SequenceEqual(pic))
throw new InvalidDataException("Unexpected PIC in .getkey.log");
}
/// <summary>
/// Constructor with optional additional region to generate a specific Disc ID
/// </summary>
/// <param name="isoPath">Path to the ISO</param>
/// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param>
/// <param name="region">Disc Region</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
public ReIRD(string isoPath, byte[] key, Region region = Region.NONE)
{
// Generate Unique Identifier using ISO CRC32
UID = GenerateUID(isoPath);
// Determine ISO file size
Size = CalculateSize(isoPath);
// Set Disc Key
DiscKey = key;
// Generate Data 2 using Disc ID
DiscID = GenerateID(Size, region);
// Generate Disc PIC
PIC = GeneratePIC(Size);
// Generate IRD fields
GenerateIRD(isoPath);
}
#endregion
#region Methods
/// <summary> /// <summary>
/// Generates a Disc ID given a size and region, where region is a single byte /// Generates a Disc ID given a size and region, where region is a single byte
@@ -87,7 +147,7 @@ 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 void GeneratePIC(long size, long layerbreak = BDLayerSize, bool exactIRD = false) private static byte[] GeneratePIC(long size, long layerbreak = BDLayerSize, bool exactIRD = false)
{ {
// Validate size // Validate size
if (size == 0 || (size % SectorSize) != 0) if (size == 0 || (size % SectorSize) != 0)
@@ -97,13 +157,14 @@ namespace LibIRD
throw new ArgumentException("Layerbreak in bytes must be a positive integer less than the ISO Size", nameof(size)); throw new ArgumentException("Layerbreak in bytes must be a positive integer less than the ISO Size", nameof(size));
// TODO: Generate correct PICs for Hybrid PS3 discs (BD-50 with layerbreak value other than 12219392) // TODO: Generate correct PICs for Hybrid PS3 discs (BD-50 with layerbreak value other than 12219392)
byte[] pic;
if (size > BDLayerSize) // if BD-50 if (size > BDLayerSize) // if BD-50
{ {
// num_sectors + layer_sector_end (0x00100000) + sectors_between_layers (0x01358C00 - 0x00CA73FE) - 3 // num_sectors + layer_sector_end (0x00100000) + sectors_between_layers (0x01358C00 - 0x00CA73FE) - 3
byte[] total_sectors = BitConverter.GetBytes((uint)(size / SectorSize + 8067071)); byte[] total_sectors = BitConverter.GetBytes((uint)(size / SectorSize + 8067071));
// Initial portion of PIC (24 bytes) // Initial portion of PIC (24 bytes)
PIC = new byte[]{ pic = new byte[]{
// [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]
@@ -132,7 +193,7 @@ namespace LibIRD
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
@@ -143,7 +204,7 @@ namespace LibIRD
byte[] end_sector = BitConverter.GetBytes((uint)(size / SectorSize + 1048574)); byte[] end_sector = BitConverter.GetBytes((uint)(size / SectorSize + 1048574));
// Initial portion of PIC (24 bytes) // Initial portion of PIC (24 bytes)
PIC = new byte[]{ 0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x08, 0x00, 0x00, 0x20, 0x00, pic = new byte[]{ 0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x08, 0x00, 0x00, 0x20, 0x00,
0x42, 0x44, 0x4F, 0x01, 0x11, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x44, 0x4F, 0x01, 0x11, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
// Total sectors used on disc (4 bytes) // Total sectors used on disc (4 bytes)
total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0], total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0],
@@ -158,69 +219,17 @@ namespace LibIRD
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
} }
return pic;
} }
/// <summary> /// <summary>
/// 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>
private void GenerateUID(string isoPath)
{
// Compute CRC32 hash
byte[] crc32;
using (FileStream fs = File.OpenRead(isoPath))
{
Crc32 hasher = new();
hasher.Append(fs);
crc32 = hasher.GetCurrentHash();
Array.Reverse(crc32);
}
// Redump ISO CRC32 hash is used as the Unique ID in the reproducible IRD
UID = BitConverter.ToUInt32(crc32, 0);
}
#endregion
#region Constructors
/// <summary>
/// Constructor using .getkey.log for Disc Key
/// </summary>
/// <param name="isoPath">Path to the ISO</param>
/// <param name="getKeyLog">Path to the GetKey log file</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception> /// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidDataException"></exception> private static uint GenerateUID(string isoPath)
public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog)
{
// Generate Unique Identifier using ISO CRC32
GenerateUID(isoPath);
// Generate Data 2 using Disc ID
byte[] d2 = Data2Key;
GenerateD2(GenerateID(Size));
// Check that GetKey log matches expected Disc ID
if (!((ReadOnlySpan<byte>)Data2Key).SequenceEqual(d2))
throw new InvalidDataException("Unexpected Disc ID in .getkey.log");
// Generate Disc PIC
byte[] pic = PIC;
GeneratePIC(Size);
// Check that GetKey log matches expected PIC
if (!((ReadOnlySpan<byte>)PIC).SequenceEqual(pic))
throw new InvalidDataException("Unexpected PIC in .getkey.log");
}
/// <summary>
/// Constructor with optional additional region to generate a specific Disc ID
/// </summary>
/// <param name="isoPath">Path to the ISO</param>
/// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param>
/// <param name="region">Disc Region</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
public ReIRD(string isoPath, byte[] key, Region region = Region.NONE) : base(isoPath)
{ {
// Validate ISO path // Validate ISO path
if (isoPath == null || isoPath.Length <= 0) if (isoPath == null || isoPath.Length <= 0)
@@ -229,22 +238,40 @@ namespace LibIRD
// Check file exists // Check file exists
var iso = new FileInfo(isoPath); var iso = new FileInfo(isoPath);
if (!iso.Exists) if (!iso.Exists)
throw new FileNotFoundException(isoPath); throw new FileNotFoundException(nameof(isoPath));
// Calculate size of ISO // Compute CRC32 hash
long size = iso.Length; byte[] crc32;
using (FileStream fs = File.OpenRead(isoPath))
{
Crc32 hasher = new();
hasher.Append(fs);
crc32 = hasher.GetCurrentHash();
}
// Generate Unique Identifier using ISO CRC32 // Redump ISO CRC32 hash is used as the Unique ID in the reproducible IRD
GenerateUID(isoPath); return BitConverter.ToUInt32(crc32, 0);
}
// Generate Data 1 using Disc Key /// <summary>
GenerateD1(key); /// Calculates ISO file size
/// </summary>
/// <param name="isoPath">Path to the ISO</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
private static long CalculateSize(string isoPath)
{
// Validate ISO path
if (isoPath == null || isoPath.Length <= 0)
throw new ArgumentNullException(nameof(isoPath));
// Generate Data 2 using Disc ID // Check file exists
GenerateD2(GenerateID(size, region)); var iso = new FileInfo(isoPath);
if (!iso.Exists)
throw new FileNotFoundException(nameof(isoPath));
// Generate Disc PIC // Calculate file size
GeneratePIC(size); return iso.Length;
} }
#endregion #endregion
+1 -1
View File
@@ -13,7 +13,7 @@ namespace PrintParams
if (File.Exists(filename)) if (File.Exists(filename))
{ {
ParamSFO paramSFO = new("./PARAM.SFO"); ParamSFO paramSFO = new("./PARAM.SFO");
Console.WriteLine(paramSFO["TITLE_ID"]); Console.WriteLine("PARAM.SFO for: " + paramSFO["TITLE_ID"] + '\n');
paramSFO.Print(); paramSFO.Print();
} }
else else