Framework for reading from IRD files
This commit is contained in:
+96
-1
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user