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; + } + } +}