Framework for reading from IRD files

This commit is contained in:
Deterous
2023-11-04 02:59:48 +13:00
parent 8802ca10da
commit 2618f3ad1c
4 changed files with 169 additions and 70 deletions
+96 -1
View File
@@ -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
/// <summary>
/// Manual constructor
/// </summary>
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;
}
/// <summary>
/// Default constructor for internal derived classes only: resulting object not in usable state
/// </summary>
@@ -315,6 +357,59 @@ namespace LibIRD
Data2Key = stream.ToArray();
}
/// <summary>
/// Read and store IRD data from an existing file
/// </summary>
/// <param name="irdPath">Path to the IRD file</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
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);
}
/// <summary>
/// Write IRD data to file
/// </summary>
-1
View File
@@ -9,7 +9,6 @@
<ItemGroup>
<PackageReference Include="DiscUtils.Core" Version="0.16.13" />
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
<PackageReference Include="DiscUtils.Streams" Version="0.16.13" />
<PackageReference Include="System.IO.Hashing" Version="7.0.0" />
</ItemGroup>
+69 -64
View File
@@ -125,13 +125,41 @@ namespace LibIRD
/// <remarks>Offset to use when reading the footer</remarks>
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
/// <summary>
/// Default generator for reading IRD files rather than ISOs
/// </summary>
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;
}
/// <summary>
/// Constructor for generating values from an ISO file
/// </summary>
@@ -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
/// </summary>
/// <param name="fs"></param>
/// <exception cref="InvalidFileSystemException"></exception>
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
/// <param name="dir"></param>
private void CountFiles(DiscDirectoryInfo dir)
{
foreach (DiscFileInfo _ in dir.GetFiles())
FileCount++;
FileCount += (uint)dir.GetFiles().Length;
foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories())
CountFiles(dirInfo);
}
/// <summary>
/// Determine and store hashes for all files and files within subdirectories recursively
/// Determine byte extents for all files and files within subdirectories recursively
/// </summary>
/// <param name="reader"></param>
/// <param name="path"></param>
private void ProcessFiles(FileStream fs, CDReader reader, DiscDirectoryInfo dir)
/// <param name="dir"></param>
/// <exception cref="InvalidFileSystemException"></exception>
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);
}
}
+4 -4
View File
@@ -47,7 +47,7 @@ namespace LibIRD
#endregion
/// <summary>
/// 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
/// </summary>
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<byte>) PIC).SequenceEqual(pic))
if (!((ReadOnlySpan<byte>)PIC).SequenceEqual(pic))
throw new InvalidDataException("Unexpected PIC in .getkey.log");
}
/// <summary>
/// Constructor with additional region to generate a specific Disc ID
/// 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>