Framework for reading from IRD files
This commit is contained in:
+96
-1
@@ -108,7 +108,7 @@ namespace LibIRD
|
|||||||
_data2Key = value;
|
_data2Key = value;
|
||||||
else
|
else
|
||||||
throw new ArgumentException("Data 2 Key must be a byte array of length 16", nameof(value));
|
throw new ArgumentException("Data 2 Key must be a byte array of length 16", nameof(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private byte[] _data2Key;
|
private byte[] _data2Key;
|
||||||
|
|
||||||
@@ -137,6 +137,48 @@ namespace LibIRD
|
|||||||
|
|
||||||
#region Constructors
|
#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>
|
/// <summary>
|
||||||
/// Default constructor for internal derived classes only: resulting object not in usable state
|
/// Default constructor for internal derived classes only: resulting object not in usable state
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -315,6 +357,59 @@ namespace LibIRD
|
|||||||
Data2Key = stream.ToArray();
|
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>
|
/// <summary>
|
||||||
/// Write IRD data to file
|
/// Write IRD data to file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DiscUtils.Core" Version="0.16.13" />
|
<PackageReference Include="DiscUtils.Core" Version="0.16.13" />
|
||||||
<PackageReference Include="DiscUtils.Iso9660" 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" />
|
<PackageReference Include="System.IO.Hashing" Version="7.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
+69
-64
@@ -125,13 +125,41 @@ namespace LibIRD
|
|||||||
/// <remarks>Offset to use when reading the footer</remarks>
|
/// <remarks>Offset to use when reading the footer</remarks>
|
||||||
private long UpdateEnd { get; set; }
|
private long UpdateEnd { get; set; }
|
||||||
|
|
||||||
private long[] RegionStart { get; set; }
|
private long[] FileStart { get; set; }
|
||||||
private long[] RegionEnd { get; set; }
|
private long[] FileEnd { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#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>
|
/// <summary>
|
||||||
/// Constructor for generating values from an ISO file
|
/// Constructor for generating values from an ISO file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -148,7 +176,7 @@ namespace LibIRD
|
|||||||
// Check file exists
|
// Check file exists
|
||||||
var iso = new FileInfo(isoPath);
|
var iso = new FileInfo(isoPath);
|
||||||
if (!iso.Exists)
|
if (!iso.Exists)
|
||||||
throw new FileNotFoundException(isoPath);
|
throw new FileNotFoundException(nameof(isoPath));
|
||||||
|
|
||||||
// TODO: remove these initial values once they're set properly
|
// TODO: remove these initial values once they're set properly
|
||||||
|
|
||||||
@@ -176,28 +204,35 @@ namespace LibIRD
|
|||||||
// Determine system update version
|
// Determine system update version
|
||||||
GetSystemVersion(fs, reader);
|
GetSystemVersion(fs, reader);
|
||||||
|
|
||||||
// Read the ISO header
|
// Read and compress the ISO header
|
||||||
GetHeader(fs, reader);
|
GetHeader(fs, reader);
|
||||||
|
|
||||||
// Read the ISO footer
|
// Read and compress the ISO footer
|
||||||
GetFooter(fs);
|
GetFooter(fs);
|
||||||
|
|
||||||
// Process all regions on ISO
|
// Recursively count all files in ISO to allocate arrays
|
||||||
HashRegions(fs);
|
DiscDirectoryInfo rootDir = reader.GetDirectoryInfo("\\");
|
||||||
|
|
||||||
// Recursively process all files in ISO
|
|
||||||
FileCount = 0;
|
FileCount = 0;
|
||||||
DiscDirectoryInfo dir = reader.GetDirectoryInfo("\\");
|
CountFiles(rootDir);
|
||||||
CountFiles(dir);
|
FileStart = new long[FileCount];
|
||||||
|
FileEnd = new long[FileCount];
|
||||||
// Determine file offsets and hashes
|
|
||||||
FileKeys = new ulong[FileCount];
|
FileKeys = new ulong[FileCount];
|
||||||
FileHashes = new byte[FileCount][];
|
FileHashes = new byte[FileCount][];
|
||||||
|
|
||||||
|
// Determine file offsets and hashes
|
||||||
uint fileCount = FileCount;
|
uint fileCount = FileCount;
|
||||||
FileCount = 0;
|
FileCount = 0;
|
||||||
ProcessFiles(fs, reader, dir);
|
GetFileExtents(rootDir, reader);
|
||||||
if (FileCount != fileCount)
|
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
|
#endregion
|
||||||
@@ -338,7 +373,7 @@ namespace LibIRD
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fs"></param>
|
/// <param name="fs"></param>
|
||||||
/// <exception cref="InvalidFileSystemException"></exception>
|
/// <exception cref="InvalidFileSystemException"></exception>
|
||||||
private void HashRegions(FileStream fs)
|
private void HashData(FileStream fs)
|
||||||
{
|
{
|
||||||
// Determine the number of unencryted regions
|
// Determine the number of unencryted regions
|
||||||
fs.Seek(0, SeekOrigin.Begin);
|
fs.Seek(0, SeekOrigin.Begin);
|
||||||
@@ -350,13 +385,11 @@ namespace LibIRD
|
|||||||
if (RegionCount <= 0)
|
if (RegionCount <= 0)
|
||||||
throw new InvalidFileSystemException("No regions detected in ISO");
|
throw new InvalidFileSystemException("No regions detected in ISO");
|
||||||
RegionHashes = new byte[RegionCount][];
|
RegionHashes = new byte[RegionCount][];
|
||||||
RegionStart = new long[RegionCount];
|
|
||||||
RegionEnd = new long[RegionCount];
|
|
||||||
|
|
||||||
// Determine the extent for each region
|
// Determine the extent for each region
|
||||||
byte[] regionSector = new byte[4];
|
byte[] regionSector = new byte[4];
|
||||||
long[] startSector = new long[RegionCount];
|
long[] regionStart = new long[RegionCount];
|
||||||
long[] endSector = new long[RegionCount];
|
long[] regionEnd = new long[RegionCount];
|
||||||
fs.Seek(8, SeekOrigin.Begin);
|
fs.Seek(8, SeekOrigin.Begin);
|
||||||
fs.Read(regionSector, 0, 4);
|
fs.Read(regionSector, 0, 4);
|
||||||
Array.Reverse(regionSector, 0, 4);
|
Array.Reverse(regionSector, 0, 4);
|
||||||
@@ -364,24 +397,22 @@ namespace LibIRD
|
|||||||
{
|
{
|
||||||
// End sector of previous region is start of this region
|
// End sector of previous region is start of this region
|
||||||
if (i % 2 == 1)
|
if (i % 2 == 1)
|
||||||
startSector[i] = BitConverter.ToInt32(regionSector) + 1;
|
regionStart[i] = BitConverter.ToInt32(regionSector) + 1;
|
||||||
else
|
else
|
||||||
startSector[i] = BitConverter.ToInt32(regionSector);
|
regionStart[i] = BitConverter.ToInt32(regionSector);
|
||||||
RegionStart[i] = startSector[i];
|
|
||||||
// Determine end sector offset of this region
|
// Determine end sector offset of this region
|
||||||
fs.Read(regionSector, 0, 4);
|
fs.Read(regionSector, 0, 4);
|
||||||
Array.Reverse(regionSector, 0, 4);
|
Array.Reverse(regionSector, 0, 4);
|
||||||
if (i % 2 == 1)
|
if (i % 2 == 1)
|
||||||
endSector[i] = BitConverter.ToInt32(regionSector) - 1;
|
regionEnd[i] = BitConverter.ToInt32(regionSector) - 1;
|
||||||
else
|
else
|
||||||
endSector[i] = BitConverter.ToInt32(regionSector);
|
regionEnd[i] = BitConverter.ToInt32(regionSector);
|
||||||
RegionEnd[i] = endSector[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove header from first region
|
// Remove header from first region
|
||||||
startSector[0] = FirstDataSector;
|
regionStart[0] = FirstDataSector;
|
||||||
// Remove footer from last region
|
// Remove footer from last region
|
||||||
endSector[^1] = (UpdateEnd / SectorSize) - 1;
|
regionEnd[^1] = (UpdateEnd / SectorSize) - 1;
|
||||||
|
|
||||||
// Determine MD5 hashes for each region
|
// Determine MD5 hashes for each region
|
||||||
using MD5 md5 = MD5.Create();
|
using MD5 md5 = MD5.Create();
|
||||||
@@ -389,11 +420,11 @@ namespace LibIRD
|
|||||||
for (int i = 0; i < RegionCount; i++)
|
for (int i = 0; i < RegionCount; i++)
|
||||||
{
|
{
|
||||||
// Start reading data from first sector of region
|
// 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
|
// Compute MD5 hash for just the region portion of the ISO file
|
||||||
int numBytes;
|
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
|
// Read one sector at a time
|
||||||
numBytes = fs.Read(buf, 0, buf.Length);
|
numBytes = fs.Read(buf, 0, buf.Length);
|
||||||
@@ -416,18 +447,18 @@ namespace LibIRD
|
|||||||
/// <param name="dir"></param>
|
/// <param name="dir"></param>
|
||||||
private void CountFiles(DiscDirectoryInfo dir)
|
private void CountFiles(DiscDirectoryInfo dir)
|
||||||
{
|
{
|
||||||
foreach (DiscFileInfo _ in dir.GetFiles())
|
FileCount += (uint)dir.GetFiles().Length;
|
||||||
FileCount++;
|
|
||||||
foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories())
|
foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories())
|
||||||
CountFiles(dirInfo);
|
CountFiles(dirInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="reader"></param>
|
/// <param name="reader"></param>
|
||||||
/// <param name="path"></param>
|
/// <param name="dir"></param>
|
||||||
private void ProcessFiles(FileStream fs, CDReader reader, DiscDirectoryInfo dir)
|
/// <exception cref="InvalidFileSystemException"></exception>
|
||||||
|
private void GetFileExtents(DiscDirectoryInfo dir, CDReader reader)
|
||||||
{
|
{
|
||||||
// Process all files in current directory
|
// Process all files in current directory
|
||||||
foreach (DiscFileInfo fileInfo in dir.GetFiles())
|
foreach (DiscFileInfo fileInfo in dir.GetFiles())
|
||||||
@@ -439,41 +470,15 @@ namespace LibIRD
|
|||||||
throw new InvalidFileSystemException("Unexpected file extent in ISO filestream for " + filePath);
|
throw new InvalidFileSystemException("Unexpected file extent in ISO filestream for " + filePath);
|
||||||
if (fileExtents.Length > 1)
|
if (fileExtents.Length > 1)
|
||||||
throw new InvalidFileSystemException("Non-contiguous file detected");
|
throw new InvalidFileSystemException("Non-contiguous file detected");
|
||||||
long firstByte = fileExtents[0].Start;
|
FileStart[FileCount] = fileExtents[0].Start;
|
||||||
long fileLength = fileExtents[0].Length;
|
FileEnd[FileCount] = 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;
|
|
||||||
FileCount++;
|
FileCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively process all subfolders of current directory
|
// Recursively process all subfolders of current directory
|
||||||
foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories())
|
foreach (DiscDirectoryInfo dirInfo in dir.GetDirectories())
|
||||||
{
|
{
|
||||||
ProcessFiles(fs, reader, dirInfo);
|
GetFileExtents(dirInfo, reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -47,7 +47,7 @@ namespace LibIRD
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class ReIRD : IRD
|
public class ReIRD : IRD
|
||||||
{
|
{
|
||||||
@@ -201,7 +201,7 @@ namespace LibIRD
|
|||||||
// Check file exists
|
// Check file exists
|
||||||
var iso = new FileInfo(isoPath);
|
var iso = new FileInfo(isoPath);
|
||||||
if (!iso.Exists)
|
if (!iso.Exists)
|
||||||
throw new FileNotFoundException(isoPath);
|
throw new FileNotFoundException(nameof(isoPath));
|
||||||
|
|
||||||
// Calculate size of ISO
|
// Calculate size of ISO
|
||||||
long size = iso.Length;
|
long size = iso.Length;
|
||||||
@@ -220,12 +220,12 @@ namespace LibIRD
|
|||||||
byte[] pic = PIC;
|
byte[] pic = PIC;
|
||||||
GeneratePIC(size);
|
GeneratePIC(size);
|
||||||
// Check that GetKey log matches expected PIC
|
// 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");
|
throw new InvalidDataException("Unexpected PIC in .getkey.log");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor with additional region to generate a specific Disc ID
|
/// Constructor with optional additional region to generate a specific Disc ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="isoPath">Path to the ISO</param>
|
/// <param name="isoPath">Path to the ISO</param>
|
||||||
/// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param>
|
/// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param>
|
||||||
|
|||||||
Reference in New Issue
Block a user