From 2618f3ad1c5d1c3d78f6b712a0a7d3a53e6a9b9e Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Sat, 4 Nov 2023 02:59:48 +1300 Subject: [PATCH] Framework for reading from IRD files --- LibIRD/IRD.cs | 97 ++++++++++++++++++++++++++++++- LibIRD/LibIRD.csproj | 1 - LibIRD/PS3ISO.cs | 133 ++++++++++++++++++++++--------------------- LibIRD/ReIRD.cs | 8 +-- 4 files changed, 169 insertions(+), 70 deletions(-) diff --git a/LibIRD/IRD.cs b/LibIRD/IRD.cs index ee976d0..42a20f7 100644 --- a/LibIRD/IRD.cs +++ b/LibIRD/IRD.cs @@ -108,7 +108,7 @@ namespace LibIRD _data2Key = value; else throw new ArgumentException("Data 2 Key must be a byte array of length 16", nameof(value)); -} + } } private byte[] _data2Key; @@ -137,6 +137,48 @@ namespace LibIRD #region Constructors + /// + /// Manual constructor + /// + private protected IRD( + byte version, + string titleID, + string title, + string sysVersion, + string gameVersion, + string appVersion, + byte[] header, + byte[] footer, + byte[][] regionHashes, + ulong[] fileKeys, + byte[][] fileHashes, + uint uid, + ushort extraConfig, + ushort attachments, + byte[] d1, + byte[] d2, + byte[] pic) + : base( + titleID, + title, + sysVersion, + gameVersion, + appVersion, + header, + footer, + regionHashes, + fileKeys, + fileHashes) + { + Version = version; + UID = uid; + ExtraConfig = extraConfig; + Attachments = attachments; + Data1Key = d1; + Data2Key = d2; + PIC = pic; + } + /// /// Default constructor for internal derived classes only: resulting object not in usable state /// @@ -315,6 +357,59 @@ namespace LibIRD Data2Key = stream.ToArray(); } + /// + /// Read and store IRD data from an existing file + /// + /// Path to the IRD file + /// + /// + public static IRD Read(string irdPath) + { + // Validate irdPath + if (irdPath == null || irdPath.Length <= 0) + throw new ArgumentNullException(nameof(irdPath)); + if (!File.Exists(irdPath)) + throw new FileNotFoundException(nameof(irdPath)); + + byte version = 1; + string titleID = "BCES01234"; + string title = "R"; + string sysVersion = "0.00"; + string gameVersion = "00.00"; + string appVersion = "00.00"; + byte[] header = new byte[1]; + byte[] footer = new byte[1]; + byte[][] regionHashes = new byte[1][]; + regionHashes[0] = new byte[1]; + ulong[] fileKeys = new ulong[1]; + byte[][] fileHashes = new byte[1][]; + fileHashes[0] = new byte[1]; + uint uid = 1; + ushort extraConfig = 1; + ushort attachments = 1; + byte[] d1 = new byte[16]; + byte[] d2 = new byte[16]; + byte[] pic = new byte[115]; + + return new IRD(version, + titleID, + title, + sysVersion, + gameVersion, + appVersion, + header, + footer, + regionHashes, + fileKeys, + fileHashes, + uid, + extraConfig, + attachments, + d1, + d2, + pic); + } + /// /// Write IRD data to file /// diff --git a/LibIRD/LibIRD.csproj b/LibIRD/LibIRD.csproj index 72b691c..f772853 100644 --- a/LibIRD/LibIRD.csproj +++ b/LibIRD/LibIRD.csproj @@ -9,7 +9,6 @@ - diff --git a/LibIRD/PS3ISO.cs b/LibIRD/PS3ISO.cs index 44ab798..8156a9d 100644 --- a/LibIRD/PS3ISO.cs +++ b/LibIRD/PS3ISO.cs @@ -125,13 +125,41 @@ namespace LibIRD /// Offset to use when reading the footer private long UpdateEnd { get; set; } - private long[] RegionStart { get; set; } - private long[] RegionEnd { get; set; } + private long[] FileStart { get; set; } + private long[] FileEnd { get; set; } #endregion #region Constructors + /// + /// Default generator for reading IRD files rather than ISOs + /// + private protected PS3ISO(string titleID, + string title, + string sysVersion, + string gameVersion, + string appVersion, + byte[] header, + byte[] footer, + byte[][] regionHashes, + ulong[] fileKeys, + byte[][] fileHashes) + { + TitleID = titleID; + Title = title; + SystemVersion = sysVersion; + GameVersion = gameVersion; + AppVersion = appVersion; + HeaderLength = (uint)header.Length; + Header = header; + FooterLength = (uint)footer.Length; + Footer = footer; + RegionHashes = regionHashes; + FileKeys = fileKeys; + FileHashes = fileHashes; + } + /// /// Constructor for generating values from an ISO file /// @@ -148,7 +176,7 @@ namespace LibIRD // Check file exists var iso = new FileInfo(isoPath); if (!iso.Exists) - throw new FileNotFoundException(isoPath); + throw new FileNotFoundException(nameof(isoPath)); // TODO: remove these initial values once they're set properly @@ -176,28 +204,35 @@ namespace LibIRD // Determine system update version GetSystemVersion(fs, reader); - // Read the ISO header + // Read and compress the ISO header GetHeader(fs, reader); - // Read the ISO footer + // Read and compress the ISO footer GetFooter(fs); - // Process all regions on ISO - HashRegions(fs); - - // Recursively process all files in ISO + // Recursively count all files in ISO to allocate arrays + DiscDirectoryInfo rootDir = reader.GetDirectoryInfo("\\"); FileCount = 0; - DiscDirectoryInfo dir = reader.GetDirectoryInfo("\\"); - CountFiles(dir); - - // Determine file offsets and hashes + CountFiles(rootDir); + FileStart = new long[FileCount]; + FileEnd = new long[FileCount]; FileKeys = new ulong[FileCount]; FileHashes = new byte[FileCount][]; + + // Determine file offsets and hashes uint fileCount = FileCount; FileCount = 0; - ProcessFiles(fs, reader, dir); + GetFileExtents(rootDir, reader); if (FileCount != fileCount) - throw new InvalidFileSystemException("Unexpected ISO filesystem error"); + throw new InvalidFileSystemException("Unexpected ISO filesystem error: "); + + // Get MD5 hash for all regions and files on ISO + HashData(fs); + for (int i = 0; i < FileCount; i++) + { + FileKeys[i] = (ulong)FileStart[i]; + FileHashes[i] = NullMD5; + } } #endregion @@ -338,7 +373,7 @@ namespace LibIRD /// /// /// - private void HashRegions(FileStream fs) + private void HashData(FileStream fs) { // Determine the number of unencryted regions fs.Seek(0, SeekOrigin.Begin); @@ -350,13 +385,11 @@ namespace LibIRD if (RegionCount <= 0) throw new InvalidFileSystemException("No regions detected in ISO"); RegionHashes = new byte[RegionCount][]; - RegionStart = new long[RegionCount]; - RegionEnd = new long[RegionCount]; // Determine the extent for each region byte[] regionSector = new byte[4]; - long[] startSector = new long[RegionCount]; - long[] endSector = new long[RegionCount]; + long[] regionStart = new long[RegionCount]; + long[] regionEnd = new long[RegionCount]; fs.Seek(8, SeekOrigin.Begin); fs.Read(regionSector, 0, 4); Array.Reverse(regionSector, 0, 4); @@ -364,24 +397,22 @@ namespace LibIRD { // End sector of previous region is start of this region if (i % 2 == 1) - startSector[i] = BitConverter.ToInt32(regionSector) + 1; + regionStart[i] = BitConverter.ToInt32(regionSector) + 1; else - startSector[i] = BitConverter.ToInt32(regionSector); - RegionStart[i] = startSector[i]; + regionStart[i] = BitConverter.ToInt32(regionSector); // Determine end sector offset of this region fs.Read(regionSector, 0, 4); Array.Reverse(regionSector, 0, 4); if (i % 2 == 1) - endSector[i] = BitConverter.ToInt32(regionSector) - 1; + regionEnd[i] = BitConverter.ToInt32(regionSector) - 1; else - endSector[i] = BitConverter.ToInt32(regionSector); - RegionEnd[i] = endSector[i]; + regionEnd[i] = BitConverter.ToInt32(regionSector); } // Remove header from first region - startSector[0] = FirstDataSector; + regionStart[0] = FirstDataSector; // Remove footer from last region - endSector[^1] = (UpdateEnd / SectorSize) - 1; + regionEnd[^1] = (UpdateEnd / SectorSize) - 1; // Determine MD5 hashes for each region using MD5 md5 = MD5.Create(); @@ -389,11 +420,11 @@ namespace LibIRD for (int i = 0; i < RegionCount; i++) { // Start reading data from first sector of region - fs.Seek(SectorSize * startSector[i], SeekOrigin.Begin); + fs.Seek(SectorSize * regionStart[i], SeekOrigin.Begin); // Compute MD5 hash for just the region portion of the ISO file int numBytes; - for (long j = startSector[i]; j <= endSector[i]; j++) + for (long j = regionStart[i]; j <= regionEnd[i]; j++) { // Read one sector at a time numBytes = fs.Read(buf, 0, buf.Length); @@ -416,18 +447,18 @@ namespace LibIRD /// private void CountFiles(DiscDirectoryInfo dir) { - foreach (DiscFileInfo _ in dir.GetFiles()) - FileCount++; + FileCount += (uint)dir.GetFiles().Length; foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories()) CountFiles(dirInfo); } /// - /// Determine and store hashes for all files and files within subdirectories recursively + /// Determine byte extents for all files and files within subdirectories recursively /// /// - /// - private void ProcessFiles(FileStream fs, CDReader reader, DiscDirectoryInfo dir) + /// + /// + private void GetFileExtents(DiscDirectoryInfo dir, CDReader reader) { // Process all files in current directory foreach (DiscFileInfo fileInfo in dir.GetFiles()) @@ -439,41 +470,15 @@ namespace LibIRD throw new InvalidFileSystemException("Unexpected file extent in ISO filestream for " + filePath); if (fileExtents.Length > 1) throw new InvalidFileSystemException("Non-contiguous file detected"); - long firstByte = fileExtents[0].Start; - long fileLength = fileExtents[0].Length; - FileKeys[FileCount] = (ulong)firstByte / 2048; - - // 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++) - { - 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"); - 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); - } - - // Finalise and store MD5 hash - md5.TransformFinalBlock(buf, 0, 0); - FileHashes[FileCount] = md5.Hash; + FileStart[FileCount] = fileExtents[0].Start; + FileEnd[FileCount] = fileExtents[0].Length; FileCount++; } // Recursively process all subfolders of current directory foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories()) { - ProcessFiles(fs, reader, dirInfo); + GetFileExtents(dirInfo, reader); } } diff --git a/LibIRD/ReIRD.cs b/LibIRD/ReIRD.cs index c75b4f3..fb7def0 100644 --- a/LibIRD/ReIRD.cs +++ b/LibIRD/ReIRD.cs @@ -47,7 +47,7 @@ namespace LibIRD #endregion /// - /// Create Reproducible Redump-style IRD file from an ISO, CRC32 and Disc Key + /// Create a reproducible, redump-style IRD file from an ISO and a Disc Key /// public class ReIRD : IRD { @@ -201,7 +201,7 @@ 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; @@ -220,12 +220,12 @@ namespace LibIRD byte[] pic = PIC; GeneratePIC(size); // Check that GetKey log matches expected PIC - if (!((ReadOnlySpan) PIC).SequenceEqual(pic)) + if (!((ReadOnlySpan)PIC).SequenceEqual(pic)) throw new InvalidDataException("Unexpected PIC in .getkey.log"); } /// - /// Constructor with additional region to generate a specific Disc ID + /// Constructor with optional additional region to generate a specific Disc ID /// /// Path to the ISO /// Disc Key, redump-style (AES encrypted Data 1)