diff --git a/BuildIRD/BuildIRD.cs b/BuildIRD/BuildIRD.cs index 2e34e8f..2a5a06b 100644 --- a/BuildIRD/BuildIRD.cs +++ b/BuildIRD/BuildIRD.cs @@ -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); } } } diff --git a/LibIRD/IRD.cs b/LibIRD/IRD.cs index b9a84ba..f276ad1 100644 --- a/LibIRD/IRD.cs +++ b/LibIRD/IRD.cs @@ -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 /// /// ISO Rebuild Data /// - /// Generates IRD fields and writes to an IRD file + /// Generates IRD fields and reads/writes IRD files public class IRD { #region Constants + /// + /// Blu-ray ISO sector size in bytes + /// + /// 2048 + private protected const uint SectorSize = 2048; + + /// + /// Size of a blu-ray layer in bytes (BD-25 max size) + /// + /// 12219392 sectors, default PS3 layerbreak value + private protected const long BDLayerSize = 25025314816; + /// /// IRD file signature /// /// "3IRD" private static readonly byte[] Magic = { 0x33, 0x49, 0x52, 0x44 }; - /// - /// Blu-ray ISO sector size in bytes - /// - /// 2048 - private protected static readonly uint SectorSize = 2048; - /// /// MD5 hash of null /// - 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 }; /// /// AES CBC Encryption Key for Data 1 (Disc Key) @@ -91,6 +97,27 @@ namespace LibIRD /// Reserved, usually set to 0x0000 public ushort Attachments { get; set; } = 0x0000; // Default to zero + /// + /// Disc Key + /// + /// 16 bytes + 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 + /// /// D1 key /// @@ -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; + /// + /// D2 key + /// + /// 16 bytes + 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; + /// /// D2 key /// @@ -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 /// private long[] RegionEnd { get; set; } - private protected long Size { get; private set; } - #endregion #region Constructors @@ -301,10 +352,9 @@ namespace LibIRD /// /// Default constructor for internal derived classes only: resulting object not in usable state /// - 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 } /// @@ -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 /// /// Generates Data 1, via AES-128 CBC decryption of a Disc Key @@ -351,7 +402,8 @@ namespace LibIRD /// Byte array containing AES encrypted Disc Key /// /// - private protected void GenerateD1(byte[] key) + /// + 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(); + } + + /// + /// Generates the Disc key, via AES-128 CBC encryption of a Data 1 Key + /// + /// Byte array containing AES decrypted Data 1 Key + /// + /// + /// + 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(); } /// /// Generates Data 2, via AES-128 CBC encryption of a Disc ID /// - /// Byte array containing AES decrypted Disc ID + /// Byte array containing AES decrypted Disc ID /// /// - private protected void GenerateD2(byte[] id) + /// + 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) + /// + /// Generates Disc ID, via AES-128 CBC decryption of a Data 2 Key + /// + /// Byte array containing AES encrypted Data 2 Key + /// + /// + /// + 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(); + } + + /// + /// Generates Data1Key, Data2Key, and PIC from the .getkey.log file + /// + /// Path to the .getkey.log file + /// + /// + /// + 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 /// /// /// - 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 + /// /// Retreives and stores the system version /// @@ -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 } } + /// + /// Decrypts a given sector byte array + /// + /// Byte array to be decrypted + /// + 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(); + } + /// /// Recursively determines file count /// @@ -849,6 +1010,10 @@ namespace LibIRD CountFiles(dirInfo); } + #endregion + + #region IRD File + /// /// Write IRD data to file /// @@ -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(); diff --git a/LibIRD/ReIRD.cs b/LibIRD/ReIRD.cs index 3ff4659..232b7c8 100644 --- a/LibIRD/ReIRD.cs +++ b/LibIRD/ReIRD.cs @@ -51,18 +51,78 @@ namespace LibIRD /// public class ReIRD : IRD { - - #region Constants + #region Properties /// - /// Size of a blu-ray layer in bytes (BD-25 max size) + /// ISO file size /// - /// Can be used as the default PS3 layerbreak value - private const long BDLayerSize = 25025314816; // 12219392 sectors + public long Size { get; private set; } #endregion - #region Property Generation + #region Constructors + + /// + /// Constructor using .getkey.log for Disc Key + /// + /// Path to the ISO + /// Path to the GetKey log file + /// + /// + /// + 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)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)PIC).SequenceEqual(pic)) + throw new InvalidDataException("Unexpected PIC in .getkey.log"); + } + + /// + /// Constructor with optional additional region to generate a specific Disc ID + /// + /// Path to the ISO + /// Disc Key, redump-style (AES encrypted Data 1) + /// Disc Region + /// + /// + 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 /// /// Generates a Disc ID given a size and region, where region is a single byte @@ -87,7 +147,7 @@ namespace LibIRD /// Layer break value, byte at which disc layers are split across /// True to generate a PIC in 3k3y style (0x03 at 115th byte for BD-50 discs) /// - 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; } /// /// Generates the UID field by computing the CRC32 hash of the ISO /// /// Path to the ISO - 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 - - /// - /// Constructor using .getkey.log for Disc Key - /// - /// Path to the ISO - /// Path to the GetKey log file /// /// - /// - 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)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)PIC).SequenceEqual(pic)) - throw new InvalidDataException("Unexpected PIC in .getkey.log"); - } - - /// - /// Constructor with optional additional region to generate a specific Disc ID - /// - /// Path to the ISO - /// Disc Key, redump-style (AES encrypted Data 1) - /// Disc Region - /// - /// - 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); + /// + /// Calculates ISO file size + /// + /// Path to the ISO + /// + /// + 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 diff --git a/PrintParams/PrintParams.cs b/PrintParams/PrintParams.cs index 3948a8c..d030880 100644 --- a/PrintParams/PrintParams.cs +++ b/PrintParams/PrintParams.cs @@ -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