Decrypt and hash files, further cleaning
This commit is contained in:
+29
-20
@@ -11,27 +11,15 @@ namespace BuildIRD
|
||||
{
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
// Create new reproducible redump-style IRD with a key
|
||||
byte[] discKey = new byte[] { 0x1A, 0x2D, 0x88, 0xFC, 0x19, 0x37, 0x27, 0x44, 0x11, 0x5E, 0xE9, 0x83, 0xA0, 0x47, 0xE2, 0xD5 };
|
||||
string fileName = "./game.iso";
|
||||
// Create new reproducible redump-style IRD with a key file
|
||||
try
|
||||
{
|
||||
IRD ird1 = new ReIRD(fileName, discKey);
|
||||
ird1.Write("./test1.ird");
|
||||
Console.WriteLine("IRD for " + fileName + " created");
|
||||
// Read key from .key file
|
||||
byte[] discKey = File.ReadAllBytes("./game.key");
|
||||
|
||||
// Create new reproducible redump-style IRD with a GetKey log
|
||||
string logPath = "./log.getkey.log";
|
||||
try
|
||||
{
|
||||
IRD ird2 = new ReIRD(fileName, logPath);
|
||||
ird2.Write("./test2.ird");
|
||||
Console.WriteLine("IRD for " + fileName + " created");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
Console.WriteLine("File not found: " + logPath);
|
||||
}
|
||||
IRD ird1 = new ReIRD("./game.iso", discKey);
|
||||
ird1.Write("./test1.ird");
|
||||
Console.WriteLine("IRD created using .key:");
|
||||
|
||||
// Read IRD and print details to console
|
||||
IRD ird = IRD.Read("./test1.ird");
|
||||
@@ -42,9 +30,30 @@ namespace BuildIRD
|
||||
Console.WriteLine("Game Version: " + ird.GameVersion);
|
||||
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
@@ -1,5 +1,5 @@
|
||||
using DiscUtils.Iso9660;
|
||||
using DiscUtils;
|
||||
using DiscUtils;
|
||||
using DiscUtils.Iso9660;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
@@ -12,27 +12,33 @@ namespace LibIRD
|
||||
/// <summary>
|
||||
/// ISO Rebuild Data
|
||||
/// </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
|
||||
{
|
||||
#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>
|
||||
/// IRD file signature
|
||||
/// </summary>
|
||||
/// <remarks>"3IRD"</remarks>
|
||||
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>
|
||||
/// MD5 hash of null
|
||||
/// </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>
|
||||
/// AES CBC Encryption Key for Data 1 (Disc Key)
|
||||
@@ -91,6 +97,27 @@ namespace LibIRD
|
||||
/// <remarks>Reserved, usually set to 0x0000</remarks>
|
||||
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>
|
||||
/// D1 key
|
||||
/// </summary>
|
||||
@@ -101,13 +128,36 @@ namespace LibIRD
|
||||
set
|
||||
{
|
||||
if (value != null && value.Length == 16)
|
||||
{
|
||||
_data1Key = value;
|
||||
_discKey = GenerateDiscKey(value);
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("Data 1 Key must be a byte array of length 16", nameof(value));
|
||||
}
|
||||
}
|
||||
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>
|
||||
/// D2 key
|
||||
/// </summary>
|
||||
@@ -118,7 +168,10 @@ namespace LibIRD
|
||||
set
|
||||
{
|
||||
if (value != null && value.Length == 16)
|
||||
{
|
||||
_data2Key = value;
|
||||
_discID = GenerateDiscID(value);
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("Data 2 Key must be a byte array of length 16", nameof(value));
|
||||
}
|
||||
@@ -247,8 +300,6 @@ namespace LibIRD
|
||||
/// </summary>
|
||||
private long[] RegionEnd { get; set; }
|
||||
|
||||
private protected long Size { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
@@ -301,10 +352,9 @@ namespace LibIRD
|
||||
/// <summary>
|
||||
/// Default constructor for internal derived classes only: resulting object not in usable state
|
||||
/// </summary>
|
||||
private protected IRD(string isoPath)
|
||||
private protected IRD()
|
||||
{
|
||||
// Assumes that internally derived class will set D1/D2/PIC in its own constructor
|
||||
GenerateIRD(isoPath);
|
||||
// Assumes that internally derived class will set fields in its own constructor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -317,6 +367,7 @@ namespace LibIRD
|
||||
public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC)
|
||||
{
|
||||
// Parse ISO, Disc Key, Disc ID, and PIC
|
||||
DiscKey = discKey;
|
||||
GenerateD1(discKey);
|
||||
GenerateD2(discID);
|
||||
PIC = discPIC;
|
||||
@@ -343,7 +394,7 @@ namespace LibIRD
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
#region Property Generation
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private protected void GenerateD1(byte[] key)
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
private protected static byte[] GenerateD1(byte[] key)
|
||||
{
|
||||
// Validate key
|
||||
if (key == null)
|
||||
@@ -376,22 +428,58 @@ namespace LibIRD
|
||||
cs.FlushFinalBlock();
|
||||
|
||||
// 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>
|
||||
/// Generates Data 2, via AES-128 CBC encryption of a Disc ID
|
||||
/// </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="ArgumentException"></exception>
|
||||
private protected void GenerateD2(byte[] id)
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
private protected static byte[] GenerateD2(byte[] d2)
|
||||
{
|
||||
// Validate id
|
||||
if (id == null)
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
if (id.Length != 16)
|
||||
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(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");
|
||||
@@ -406,14 +494,56 @@ namespace LibIRD
|
||||
using MemoryStream stream = new();
|
||||
using ICryptoTransform enc = aes.CreateEncryptor();
|
||||
using CryptoStream cs = new(stream, enc, CryptoStreamMode.Write);
|
||||
cs.Write(id, 0, 16);
|
||||
cs.Write(d2, 0, 16);
|
||||
cs.FlushFinalBlock();
|
||||
|
||||
// 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
|
||||
@@ -485,8 +615,8 @@ namespace LibIRD
|
||||
}
|
||||
|
||||
// Parse DiscKey, DiscID, and PIC
|
||||
GenerateD1(discKey);
|
||||
GenerateD2(discID);
|
||||
DiscKey = discKey;
|
||||
DiscID = discID;
|
||||
PIC = discPIC;
|
||||
}
|
||||
|
||||
@@ -497,20 +627,8 @@ namespace LibIRD
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="FileNotFoundException"></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
|
||||
using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath);
|
||||
// Validate ISO file stream
|
||||
@@ -562,6 +680,10 @@ namespace LibIRD
|
||||
Array.Sort(FileKeys, FileHashes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reading ISO
|
||||
|
||||
/// <summary>
|
||||
/// Retreives and stores the system version
|
||||
/// </summary>
|
||||
@@ -782,47 +904,51 @@ namespace LibIRD
|
||||
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] = firstByte / 2048;
|
||||
FileKeys[FileCount] = firstSector;
|
||||
|
||||
// Determine whether file is in encrypted or decrypted region
|
||||
bool encrypted = false;
|
||||
for (int i = RegionCount - 1; i > 0; i--)
|
||||
{
|
||||
if (RegionStart[i] <= firstByte / 2048)
|
||||
if (RegionStart[i] <= firstSector)
|
||||
{
|
||||
encrypted = i % 2 == 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt file if encrypted
|
||||
if (encrypted)
|
||||
{
|
||||
FileHashes[FileCount] = NullMD5;
|
||||
FileCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start reading data from the beginning of the ISO file
|
||||
fs.Seek(firstByte, SeekOrigin.Begin);
|
||||
byte[] buf = new byte[SectorSize];
|
||||
int numBytes;
|
||||
// Read all data before the first data sector
|
||||
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);
|
||||
// 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, (int)(fileLength % SectorSize));
|
||||
md5.TransformBlock(buf, 0, numBytes, null, 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
|
||||
@@ -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>
|
||||
/// Recursively determines file count
|
||||
/// </summary>
|
||||
@@ -849,6 +1010,10 @@ namespace LibIRD
|
||||
CountFiles(dirInfo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IRD File
|
||||
|
||||
/// <summary>
|
||||
/// Write IRD data to file
|
||||
/// </summary>
|
||||
@@ -1011,13 +1176,11 @@ namespace LibIRD
|
||||
|
||||
// Read Header
|
||||
uint headerLength = br.ReadUInt32();
|
||||
byte[] header = new byte[headerLength];
|
||||
br.Read(header, 0, header.Length);
|
||||
byte[] header = br.ReadBytes((int)headerLength);
|
||||
|
||||
// Read Footer
|
||||
uint footerLength = br.ReadUInt32();
|
||||
byte[] footer = new byte[footerLength];
|
||||
br.Read(footer, 0, footer.Length);
|
||||
byte[] footer = br.ReadBytes((int)footerLength);
|
||||
|
||||
// Read region hashes
|
||||
byte regionCount = br.ReadByte();
|
||||
|
||||
+103
-76
@@ -51,18 +51,78 @@ namespace LibIRD
|
||||
/// </summary>
|
||||
public class ReIRD : IRD
|
||||
{
|
||||
|
||||
#region Constants
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Size of a blu-ray layer in bytes (BD-25 max size)
|
||||
/// ISO file size
|
||||
/// </summary>
|
||||
/// <remarks>Can be used as the default PS3 layerbreak value</remarks>
|
||||
private const long BDLayerSize = 25025314816; // 12219392 sectors
|
||||
public long Size { get; private set; }
|
||||
|
||||
#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>
|
||||
/// 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="exactIRD">True to generate a PIC in 3k3y style (0x03 at 115th byte for BD-50 discs)</param>
|
||||
/// <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
|
||||
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));
|
||||
|
||||
// TODO: Generate correct PICs for Hybrid PS3 discs (BD-50 with layerbreak value other than 12219392)
|
||||
byte[] pic;
|
||||
if (size > BDLayerSize) // if BD-50
|
||||
{
|
||||
// num_sectors + layer_sector_end (0x00100000) + sectors_between_layers (0x01358C00 - 0x00CA73FE) - 3
|
||||
byte[] total_sectors = BitConverter.GetBytes((uint)(size / SectorSize + 8067071));
|
||||
|
||||
// Initial portion of PIC (24 bytes)
|
||||
PIC = new byte[]{
|
||||
pic = new byte[]{
|
||||
// [4098 bytes] [2x 0x00] ["DI"] [v1] [10units] [DI num]
|
||||
0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x10, 0x00, 0x00, 0x20, 0x00,
|
||||
// ["BDR"] [2 layers]
|
||||
@@ -132,7 +193,7 @@ namespace LibIRD
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
// 3k3y style: 0x03 at last byte
|
||||
if (exactIRD)
|
||||
PIC[114] = 0x03;
|
||||
pic[114] = 0x03;
|
||||
|
||||
}
|
||||
else // if BD-25
|
||||
@@ -143,7 +204,7 @@ namespace LibIRD
|
||||
byte[] end_sector = BitConverter.GetBytes((uint)(size / SectorSize + 1048574));
|
||||
|
||||
// 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,
|
||||
// Total sectors used on disc (4 bytes)
|
||||
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 };
|
||||
}
|
||||
|
||||
return pic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the UID field by computing the CRC32 hash of the ISO
|
||||
/// </summary>
|
||||
/// <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="FileNotFoundException"></exception>
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
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)
|
||||
private static uint GenerateUID(string isoPath)
|
||||
{
|
||||
// Validate ISO path
|
||||
if (isoPath == null || isoPath.Length <= 0)
|
||||
@@ -229,22 +238,40 @@ namespace LibIRD
|
||||
// Check file exists
|
||||
var iso = new FileInfo(isoPath);
|
||||
if (!iso.Exists)
|
||||
throw new FileNotFoundException(isoPath);
|
||||
throw new FileNotFoundException(nameof(isoPath));
|
||||
|
||||
// Calculate size of ISO
|
||||
long size = iso.Length;
|
||||
// Compute CRC32 hash
|
||||
byte[] crc32;
|
||||
using (FileStream fs = File.OpenRead(isoPath))
|
||||
{
|
||||
Crc32 hasher = new();
|
||||
hasher.Append(fs);
|
||||
crc32 = hasher.GetCurrentHash();
|
||||
}
|
||||
|
||||
// Generate Unique Identifier using ISO CRC32
|
||||
GenerateUID(isoPath);
|
||||
// Redump ISO CRC32 hash is used as the Unique ID in the reproducible IRD
|
||||
return BitConverter.ToUInt32(crc32, 0);
|
||||
}
|
||||
|
||||
// Generate Data 1 using Disc Key
|
||||
GenerateD1(key);
|
||||
/// <summary>
|
||||
/// 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
|
||||
GenerateD2(GenerateID(size, region));
|
||||
// Check file exists
|
||||
var iso = new FileInfo(isoPath);
|
||||
if (!iso.Exists)
|
||||
throw new FileNotFoundException(nameof(isoPath));
|
||||
|
||||
// Generate Disc PIC
|
||||
GeneratePIC(size);
|
||||
// Calculate file size
|
||||
return iso.Length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace PrintParams
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
ParamSFO paramSFO = new("./PARAM.SFO");
|
||||
Console.WriteLine(paramSFO["TITLE_ID"]);
|
||||
Console.WriteLine("PARAM.SFO for: " + paramSFO["TITLE_ID"] + '\n');
|
||||
paramSFO.Print();
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user