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
+222 -59
View File
@@ -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
View File
@@ -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