From b962c8240a92c4717ef1037cdb7c9444fdcc266f Mon Sep 17 00:00:00 2001
From: Deterous <138427222+Deterous@users.noreply.github.com>
Date: Mon, 30 Oct 2023 11:43:01 +1300
Subject: [PATCH] Parse .getkey.log
---
BuildIRD/BuildIRD.cs | 8 ++-
LibIRD/IRD.cs | 147 ++++++++++++++++++++++++++++++++++---------
LibIRD/ReIRD.cs | 44 ++++++++++++-
LibIRD/Utilities.cs | 81 ++++++++++++++++++++++++
4 files changed, 245 insertions(+), 35 deletions(-)
create mode 100644 LibIRD/Utilities.cs
diff --git a/BuildIRD/BuildIRD.cs b/BuildIRD/BuildIRD.cs
index ccb4d6d..7a5038e 100644
--- a/BuildIRD/BuildIRD.cs
+++ b/BuildIRD/BuildIRD.cs
@@ -8,9 +8,11 @@ namespace BuildIRD
static void Main()
{
byte[] discKey = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
- IRD ird = new ReIRD("./game.iso", discKey);
- ird.Write("./test.ird");
- Console.WriteLine(ird.HeaderLength);
+ IRD ird1 = new ReIRD("./game.iso", discKey);
+ IRD ird2 = new ReIRD("./game.iso", "./log.getkey.log");
+ ird1.Write("./test1.ird");
+ ird2.Write("./test2.ird");
+ Console.WriteLine(ird1.Title);
}
}
}
diff --git a/LibIRD/IRD.cs b/LibIRD/IRD.cs
index 96064bb..e4628a6 100644
--- a/LibIRD/IRD.cs
+++ b/LibIRD/IRD.cs
@@ -180,12 +180,7 @@ namespace LibIRD
/// files, 16-bytes per hash, alternating with each entry
public byte[][] FileHashes { get; private set; }
- ///
- /// IRD content CRC
- ///
- 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
-
///
/// Default constructor for internal derived classes only: resulting object not in usable state
///
@@ -295,27 +288,122 @@ namespace LibIRD
}
///
- /// Public Constructor must be called with required fields
+ /// Constructor that reads required fields from .getkey.log file
///
- /// Path to the ISO
+ ///
+ ///
+ 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);
+ }
+
+ ///
+ /// Constructor with given required fields
+ ///
+ /// Path to the ISO
/// Disc Key, byte array of length 16
/// Disc ID, byte array of length 16
/// Disc PIC, byte array of length 115
- 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;
+ }
+
///
/// Generate IRD fields from a disc drive or mounted ISO
///
@@ -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 };
}
///
@@ -357,7 +446,7 @@ namespace LibIRD
///
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 };
}
///
@@ -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);
}
}
}
diff --git a/LibIRD/ReIRD.cs b/LibIRD/ReIRD.cs
index 1c890ac..c2c0152 100644
--- a/LibIRD/ReIRD.cs
+++ b/LibIRD/ReIRD.cs
@@ -184,6 +184,44 @@ namespace LibIRD
#region Constructors
+ ///
+ /// Constructor using .getkey.log for Disc Key
+ ///
+ /// Path to the ISO
+ /// Path to the GetKey log file
+ ///
+ ///
+ ///
+ 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);
+ }
+
///
/// Constructor with required redump-style IRD fields
///
@@ -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);
}
diff --git a/LibIRD/Utilities.cs b/LibIRD/Utilities.cs
new file mode 100644
index 0000000..c4d28f6
--- /dev/null
+++ b/LibIRD/Utilities.cs
@@ -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;
+ }
+ }
+}