Parse .getkey.log

This commit is contained in:
Deterous
2023-10-30 11:43:01 +13:00
parent 8fec42d243
commit b962c8240a
4 changed files with 245 additions and 35 deletions
+118 -29
View File
@@ -180,12 +180,7 @@ namespace LibIRD
/// <remarks><see cref="FileHashes"/> files, 16-bytes per hash, alternating with each <see cref="FileHashes"/> entry</remarks>
public byte[][] FileHashes { get; private set; }
/// <summary>
/// IRD content CRC
/// </summary>
public uint CRC { get; private set; }
#endregion
#endregion
#region Generate Fields
@@ -275,8 +270,6 @@ namespace LibIRD
#region Constructors
// TODO: Constructor using D1 and D2 directly, rather than Disc Key / Disc ID
/// <summary>
/// Default constructor for internal derived classes only: resulting object not in usable state
/// </summary>
@@ -295,27 +288,122 @@ namespace LibIRD
}
/// <summary>
/// Public Constructor must be called with required fields
/// Constructor that reads required fields from .getkey.log file
/// </summary>
/// <param name="discPath">Path to the ISO</param>
/// <param name="isoPath"></param>
/// <param name="getKeyLog"></param>
public IRD(string isoPath, string getKeyLog)
{
// Parse Disc Key, Disc ID, and PIC from the .getkey.log file
ParseGetKeyLog(getKeyLog);
// Generate the remaining IRD fields from the disc drive or mounted ISO
Generate(isoPath);
}
/// <summary>
/// Constructor with given required fields
/// </summary>
/// <param name="isoPath">Path to the ISO</param>
/// <param name="discKey">Disc Key, byte array of length 16</param>
/// <param name="discID">Disc ID, byte array of length 16</param>
/// <param name="discPIC">Disc PIC, byte array of length 115</param>
public IRD(string discPath, byte[] discKey, byte[] discID, byte[] discPIC)
public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC)
{
// Store IRD fields that cannot be determined from the ISO
// Parse DiscKey, DiscID, and PIC
GenerateD1(discKey);
GenerateD2(discID);
PIC = discPIC;
// Generate the remaining IRD fields from the disc drive or mounted ISO
Generate(discPath);
Generate(isoPath);
}
#endregion
#region Methods
private protected void ParseGetKeyLog(string getKeyLog)
{
// Validate getKeyLog
if (getKeyLog == null || !File.Exists(getKeyLog))
throw new ArgumentNullException(nameof(getKeyLog));
// Read from .getkey.log file
byte[] discKey;
byte[] discID;
byte[] discPIC;
using (var sr = File.OpenText(getKeyLog))
{
string line;
// Determine whether GetKey was successful
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("get_dec_key succeeded!") == false) ;
if (line == null)
throw new InvalidDataException(".getkey.log contains errors");
// Look for Disc Key in log
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_key = ") == false) ;
if (line == null)
throw new InvalidDataException("Could not find Disc Key in .getkey.log");
// Get Disc Key from log
string discKeyStr = line.Substring("disc_key = ".Length);
// Validate Disc Key from log
if (discKeyStr.Length != 32)
throw new InvalidDataException("Unexpected Disc Key in .getkey.log");
// Convert Disc Key to byte array
discKey = Utilities.HexToBytes(discKeyStr);
// Read Disc ID
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_id = ") == false) ;
if (line == null)
throw new InvalidDataException("Could not find Disc ID in .getkey.log");
// Get Disc ID from log
string discIDStr = line.Substring("disc_id = ".Length);
// Validate Disc ID from log
if (discIDStr.Length != 32)
throw new InvalidDataException("Unexpected Disc ID in .getkey.log");
// Replace X's in Disc ID with 00000001
discIDStr = discIDStr.Substring(0, 24) + "00000001";
// Convert Disc ID to byte array
discID = Utilities.HexToBytes(discIDStr);
// Look for PIC in log
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("PIC:") == false) ;
if (line == null)
throw new InvalidDataException("Could not find PIC in .getkey.log");
// Get PIC from log
string discPICStr = "";
for (int i = 0; i < 8; i++)
{
line = sr.ReadLine();
if (line == null)
throw new InvalidDataException("Incomplete PIC in .getkey.log");
discPICStr += line;
}
// Validate PIC from log
if (discPICStr.Length != 256)
throw new InvalidDataException("Unexpected PIC in .getkey.log");
// Convert PIC to byte array
discPIC = Utilities.HexToBytes(discPICStr.Substring(0, 230));
// Check for warnings in .getkey.log
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("WARNING") == false && line.Trim().StartsWith("SUCCESS") == false)
{
string t = line.Trim();
if (t.StartsWith("WARNING"))
throw new InvalidDataException(".getkey.log contains errors");
else if (t.StartsWith("SUCCESS"))
break;
}
}
// Store values
GenerateD1(discKey);
GenerateD2(discID);
PIC = discPIC;
}
/// <summary>
/// Generate IRD fields from a disc drive or mounted ISO
/// </summary>
@@ -330,7 +418,7 @@ namespace LibIRD
// TODO: Code the difficult part (remove these initial values)
TitleID = "ABCD12345";
Title = "Title";
SystemVersion = "01.00";
SystemVersion = "1.00";
GameVersion = "01.00";
AppVersion = "01.00";
HeaderLength = 1;
@@ -341,13 +429,14 @@ namespace LibIRD
Footer[0] = 0x00;
RegionCount = 1;
RegionHashes = new byte[RegionCount][];
RegionHashes[0] = new byte[] { 0x00 };
for (int i = 0; i < RegionCount; i++)
RegionHashes[i] = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
FileCount = 1;
FileKeys = new ulong[FileCount];
FileKeys[0] = 0x00;
FileHashes = new byte[FileCount][];
FileHashes[0] = new byte[] { 0x00 };
FileHashes[0][0] = 0x00;
for (int i = 0; i < FileCount; i++)
FileHashes[i] = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
}
/// <summary>
@@ -357,7 +446,7 @@ namespace LibIRD
/// <exception cref="ArgumentException"></exception>
public void Read(string irdPath)
{
// TODO: Check irdPath is a valid IRD file path
// TODO: Validate irdPath
if (irdPath == null || !File.Exists(irdPath))
throw new ArgumentException("IRD File Path invalid", nameof(irdPath));
@@ -375,13 +464,14 @@ namespace LibIRD
Footer[0] = 0x00;
RegionCount = 1;
RegionHashes = new byte[RegionCount][];
RegionHashes[0] = new byte[] { 0x00 };
for (int i = 0; i < RegionCount; i++)
RegionHashes[i] = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
FileCount = 1;
FileKeys = new ulong[FileCount];
FileKeys[0] = 0x00;
FileHashes = new byte[FileCount][];
FileHashes[0] = new byte[] { 0x00 };
FileHashes[0][0] = 0x00;
for (int i = 0; i < FileCount; i++)
FileHashes[i] = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
}
/// <summary>
@@ -401,7 +491,7 @@ namespace LibIRD
using (GZipStream outStream = new GZipStream(fs, CompressionLevel.Optimal))
{
// Keep track of the little-endian 32-bit "IEEE 802.3" CRC value
Crc32 crcStream = new Crc32();
Crc32 crc32 = new Crc32();
// Write IRD data to stream in order
using (BinaryWriter bw = new BinaryWriter(outStream))
@@ -410,7 +500,7 @@ namespace LibIRD
bw.Write(Magic);
// IRD File Version
bw.Write(_version);
bw.Write(Version);
// PARAM.SFO / TITLE_ID
byte[] titleIDBuf = Encoding.ASCII.GetBytes(TitleID);
@@ -461,7 +551,10 @@ namespace LibIRD
for (int i = 0; i < FileCount; i++)
{
bw.Write(FileKeys[i]);
bw.Write(FileHashes[i], 0, 16);
if (FileHashes[i] == null)
bw.Write(NullMD5);
else
bw.Write(FileHashes[i], 0, 16);
}
// Reserved fields
@@ -484,12 +577,8 @@ namespace LibIRD
if (_version > 7)
bw.Write(UID);
}
// Calculate final CRC32
CRC = BitConverter.ToUInt32(crcStream.GetCurrentHash(), 0);
// Write final CRC32 to stream
outStream.Write(BitConverter.GetBytes(CRC), 0, 4);
outStream.Write(crc32.GetCurrentHash(), 0, 4);
}
}
}
+41 -3
View File
@@ -184,6 +184,44 @@ namespace LibIRD
#region Constructors
/// <summary>
/// Constructor using .getkey.log for Disc Key
/// </summary>
/// <param name="isoPath">Path to the ISO</param>
/// <param name="getKeyLog">Path to the GetKey log file</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidDataException"></exception>
public ReIRD(string isoPath, string getKeyLog)
{
// 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(isoPath);
// Calculate size of ISO
long size = iso.Length;
// Parse .getkey.log for the Disc Key (Data1Key)
ParseGetKeyLog(getKeyLog);
// Generate Data 2 using Disc ID, discarding ID from getKeyLog
GenerateD2(GenerateID(size));
// Generate Disc PIC, discarding PIC from getKeyLog
GeneratePIC(size);
// Generate Unique Identifier using ISO CRC32
GenerateUID(isoPath);
// Generate the IRD hashes
Generate(isoPath);
}
/// <summary>
/// Constructor with required redump-style IRD fields
/// </summary>
@@ -205,9 +243,6 @@ namespace LibIRD
// Calculate size of ISO
long size = iso.Length;
// Generate Unique Identifier using ISO CRC32
GenerateUID(isoPath);
// Generate Data 1 using Disc Key
GenerateD1(key);
@@ -217,6 +252,9 @@ namespace LibIRD
// Generate Disc PIC
GeneratePIC(size);
// Generate Unique Identifier using ISO CRC32
GenerateUID(isoPath);
// Generate the IRD hashes
Generate(isoPath);
}
+81
View File
@@ -0,0 +1,81 @@
using System;
namespace LibIRD
{
internal class Utilities
{
// Helper function to convert a hex string to a byte array
// Source: https://codereview.stackexchange.com/a/53846
private static int HexToInt(char c)
{
switch (c)
{
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
throw new FormatException("Unrecognized hex char " + c);
}
}
private static readonly byte[,] ByteLookup = new byte[,]
{
// low nibble
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
// high nibble
{0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0}
};
internal static byte[] HexToBytes(string input)
{
var result = new byte[(input.Length + 1) >> 1];
int lastcell = result.Length - 1;
int lastchar = input.Length - 1;
// count up in characters, but inside the loop will
// reference from the end of the input/output.
for (int i = 0; i < input.Length; i++)
{
// i >> 1 - (i / 2) gives the result byte offset from the end
// i & 1 - 1 if it is high-nibble, 0 for low-nibble.
result[lastcell - (i >> 1)] |= ByteLookup[i & 1, HexToInt(input[lastchar - i])];
}
return result;
}
}
}