From 07d8a5d38d9fe18203e87704e3ba3fdabfbee4d5 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:01:25 +0900 Subject: [PATCH 01/17] Cleanup SS handling --- MPF.Processors/ProcessingTool.cs | 186 +++++++++++++--------------- MPF.Processors/Redumper.cs | 13 +- MPF.Processors/XboxBackupCreator.cs | 49 ++++---- 3 files changed, 120 insertions(+), 128 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index ed9b51a12..dfb79a91d 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1095,108 +1095,61 @@ public static bool IsValidSS(byte[] ss) // Must be a valid XGD type if (!GetXGDType(ss, out int xgdType)) return false; + + // Drive entry table must be duplicated exactly + if (!data.Skip(0x661).Take(0x730 - 0x661).SequenceEqual(data.Skip(0x730).Take(0x7FF - 0x730))) + return false; - // Only continue to check SSv2 for XGD3 - if (xgdType != 3) + // Remaining checks are only for Xbox360 SS + if (xgdType == 1) return true; - // Determine if XGD3 SS.bin is SSv1 (Original Kreon) or SSv2 (0800 / Repaired Kreon) + // Determine if XGD3 SS is invalid SSv1 (Original Kreon) or valid SSv2 (0800 / Custom Kreon) + if (xgdType != 3) + { + bool bad_xgd3 = false; #if NET20 - var checkArr = new byte[72]; - Array.Copy(ss, 32, checkArr, 0, 72); - return Array.Exists(checkArr, x => x != 0); + var checkArr = new byte[72]; + Array.Copy(ss, 32, checkArr, 0, 72); + bad_xgd3 = Array.Exists(checkArr, x => x != 0); #else - return ss.Skip(32).Take(72).Any(x => x != 0); + bad_xgd3 = ss.Skip(32).Take(72).Any(x => x != 0); #endif - } - - /// - /// Determine if a given SS.bin is valid but contains zeroed challenge responses - /// - /// Path to the SS file to check - /// True if valid but partial SS.bin, false otherwise - public static bool IsValidPartialSS(string ssPath) - { - if (!File.Exists(ssPath)) - return false; + if (bad_xgd3) + return false; + } - byte[] ss = File.ReadAllBytes(ssPath); - if (ss.Length != 2048) + // Must have correct version and number of CCRT entries + if (ss[0x300] != 2 || ss[0x301] != 21 || ss[0x65F] != 0x02 || ss[0x49E != 0x04]) return false; - - return IsValidPartialSS(ss); + + return true; } /// - /// Determine if a given SS is valid but contains zeroed challenge responses + /// Determine if a given SS has already been repaired and cleaned /// /// Byte array of SS sector - /// True if SS is a valid but partial SS, false otherwise - public static bool IsValidPartialSS(byte[] ss) + /// True if SS is repaired and cleaned, false otherwise + public static bool IsFixedSS(byte[] ss) { - // Check 1 sector long if (ss.Length != 2048) return false; - // Must be a valid XGD type - if (!GetXGDType(ss, out int xgdType)) - return false; - - // Determine challenge table offset, XGD1 is never partial - int ccrt_offset = 0; - if (xgdType == 1) - return false; - else if (xgdType == 2) - ccrt_offset = 0x200; - else if (xgdType == 3) - ccrt_offset = 0x20; - - int[] entry_offsets = [0, 9, 18, 27, 36, 45, 54, 63]; - int[] entry_lengths = [8, 8, 8, 8, 4, 4, 4, 4]; - for (int i = 0; i < entry_offsets.Length; i++) - { - bool emptyResponse = true; - for (int b = 0; b < entry_lengths[i]; b++) - { - if (ss[ccrt_offset + entry_offsets[i] + b] != 0x00) - { - emptyResponse = false; - break; - } - } - - if (emptyResponse) - return true; - } - - return false; - } - - /// - /// Determine if a given SS has already been cleaned - /// - /// Byte array of SS sector - /// True if SS is clean, false otherwise - public static bool IsCleanSS(byte[] ss) - { - if (ss.Length != 2048) + if (!IsValid(ss)) return false; if (!GetXGDType(ss, out int xgdType)) return false; + + // Valid XGD1 is always fixed + if (xgdType == 1) + return true; -#if NET20 - var checkArr = new byte[72]; - Array.Copy(ss, 32, checkArr, 0, 72); - if (xgdType == 3 && Array.Exists(checkArr, x => x != 0)) -#else - if (xgdType == 3 && ss.Skip(32).Take(72).Any(x => x != 0)) -#endif + if (xgdType == 3) { // Check for a cleaned SSv2 - int rtOffset = 0x24; - if (ss[rtOffset + 36] != 0x01) return false; if (ss[rtOffset + 37] != 0x00) @@ -1232,55 +1185,80 @@ public static bool IsCleanSS(byte[] ss) } else { - // Check for a cleaned SSv1 - + // Check for a cleaned XGD2 int rtOffset = 0x204; - if (ss[rtOffset + 36] != 0x01) return false; if (ss[rtOffset + 37] != 0x00) return false; - if (xgdType == 2 && ss[rtOffset + 39] != 0x00) + if (ss[rtOffset + 39] != 0x00) return false; - if (xgdType == 2 && ss[rtOffset + 40] != 0x00) + if (ss[rtOffset + 40] != 0x00) return false; if (ss[rtOffset + 45] != 0x5B) return false; if (ss[rtOffset + 46] != 0x00) return false; - if (xgdType == 2 && ss[rtOffset + 48] != 0x00) + if (ss[rtOffset + 48] != 0x00) return false; - if (xgdType == 2 && ss[rtOffset + 49] != 0x00) + if (ss[rtOffset + 49] != 0x00) return false; if (ss[rtOffset + 54] != 0xB5) return false; if (ss[rtOffset + 55] != 0x00) return false; - if (xgdType == 2 && ss[rtOffset + 57] != 0x00) + if (ss[rtOffset + 57] != 0x00) return false; - if (xgdType == 2 && ss[rtOffset + 58] != 0x00) + if (ss[rtOffset + 58] != 0x00) return false; if (ss[rtOffset + 63] != 0x0F) return false; if (ss[rtOffset + 64] != 0x01) return false; - if (xgdType == 2 && ss[rtOffset + 66] != 0x00) + if (ss[rtOffset + 66] != 0x00) return false; - if (xgdType == 2 && ss[rtOffset + 67] != 0x00) + if (ss[rtOffset + 67] != 0x00) return false; } - // All angles are as expected, it is clean + // Determine challenge table offset + int ccrt_offset = 0; + if (xgdType == 2) + ccrt_offset = 0x200; + else if (xgdType == 3) + ccrt_offset = 0x20; + + // Check for empty challenge responses + int[] entry_offsets = [0, 9, 18, 27, 36, 45, 54, 63]; + int[] entry_lengths = [8, 8, 8, 8, 4, 4, 4, 4]; + for (int i = 0; i < entry_offsets.Length; i++) + { + bool emptyResponse = true; + for (int b = 0; b < entry_lengths[i]; b++) + { + if (ss[ccrt_offset + entry_offsets[i] + b] != 0x00) + { + emptyResponse = false; + break; + } + } + + if (emptyResponse) + return false; + } + + // TODO: Check for correct challenge responses + return true; } /// - /// Clean a rawSS.bin file and write it to a file + /// Repair and clean a rawSS.bin file and write it to a file /// /// Path to the raw SS file to read from - /// Path to the clean SS file to write to + /// Path to the fixed SS file to write to /// True if successful, false otherwise - public static bool CleanSS(string rawSS, string cleanSS) + public static bool FixSS(string rawSS, string fixedSS) { if (!File.Exists(rawSS)) return false; @@ -1289,20 +1267,20 @@ public static bool CleanSS(string rawSS, string cleanSS) if (ss.Length != 2048) return false; - if (!CleanSS(ss)) + if (!FixSS(ss)) return false; - File.WriteAllBytes(cleanSS, ss); + File.WriteAllBytes(fixedSS, ss); return true; } /// - /// Fix a SS sector to its predictable clean form. - /// With help from ss_sector_range + /// Repair and clean a SS sector to its valid, predictable clean form. + /// Note: Also see ss_sector_range and abgx360 /// /// Byte array of raw SS sector /// True if successful, false otherwise - public static bool CleanSS(byte[] ss) + public static bool FixSS(byte[] ss) { // Must be entire sector if (ss.Length != 2048) @@ -1329,11 +1307,12 @@ public static bool CleanSS(byte[] ss) if (xgdType == 3 && !ssv2) return false; + // Clean SS (set fixed angles) switch (xgdType) { case 1: - // Leave Original Xbox SS.bin unchanged - return true; + // Cannot clean XGD1 SS.bin + break; case 2: // Fix standard SSv1 ss.bin @@ -1356,7 +1335,7 @@ public static bool CleanSS(byte[] ss) ss[580] = 1; // 0x01 ss[582] = 0; // 0x00 ss[583] = 0; // 0x00 - return true; + break; case 3: if (ssv2) @@ -1395,13 +1374,16 @@ public static bool CleanSS(byte[] ss) ss[579] = 15; // 0x0F ss[580] = 1; // 0x01 } - - return true; + break; default: // Unknown XGD type return false; } + + // TODO: Repair challenge responses + + return true; } /// diff --git a/MPF.Processors/Redumper.cs b/MPF.Processors/Redumper.cs index ca995c71e..621c39344 100644 --- a/MPF.Processors/Redumper.cs +++ b/MPF.Processors/Redumper.cs @@ -282,7 +282,7 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi if (!File.Exists($"{basePath}.pfi")) RemoveHeaderAndTrim($"{basePath}.physical", $"{basePath}.pfi"); if (!File.Exists($"{basePath}.ss")) - ProcessingTool.CleanSS($"{basePath}.security", $"{basePath}.ss"); + ProcessingTool.FixSS($"{basePath}.security", $"{basePath}.ss"); string xmidString = ProcessingTool.GetXMID($"{basePath}.dmi").Trim('\0'); if (!string.IsNullOrEmpty(xmidString)) @@ -316,12 +316,21 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi string? pfiCrc = HashTool.GetFileHash($"{basePath}.pfi", HashType.CRC32); if (pfiCrc is not null) info.CommonDiscInfo.CommentsSpecialFields[SiteCode.PFIHash] = pfiCrc.ToUpperInvariant(); - if (ProcessingTool.IsValidSS($"{basePath}.ss") && !ProcessingTool.IsValidPartialSS($"{basePath}.ss")) + + // Only record SS hash if it is valid + if (ProcessingTool.IsFixedSS($"{basePath}.ss")) { string? ssCrc = HashTool.GetFileHash($"{basePath}.ss", HashType.CRC32); if (ssCrc is not null) info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = ssCrc.ToUpperInvariant(); } + else if (ProcessingTool.FixSS($"{basePath}.ss", $"{basePath}.ss")) + { + // Attempt to repair bad .ss file succeeded, hash it + string? ssCrc = HashTool.GetFileHash($"{basePath}.ss", HashType.CRC32); + if (ssCrc is not null) + info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = ssCrc.ToUpperInvariant(); + } string? ssRanges = ProcessingTool.GetSSRanges($"{basePath}.ss"); if (!string.IsNullOrEmpty(ssRanges)) diff --git a/MPF.Processors/XboxBackupCreator.cs b/MPF.Processors/XboxBackupCreator.cs index 3d7213202..0a07e0b47 100644 --- a/MPF.Processors/XboxBackupCreator.cs +++ b/MPF.Processors/XboxBackupCreator.cs @@ -105,33 +105,34 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi } #pragma warning restore IDE0010 - // Get the output file paths + // Hash DMI/PFI string dmiPath = Path.Combine(outputDirectory, "DMI.bin"); + if (File.Exists(dmiPath)) + info.CommonDiscInfo.CommentsSpecialFields[SiteCode.DMIHash] = HashTool.GetFileHash(dmiPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; string pfiPath = Path.Combine(outputDirectory, "PFI.bin"); - string ssPath = Path.Combine(outputDirectory, "SS.bin"); + if (File.Exists(pfiPath)) + info.CommonDiscInfo.CommentsSpecialFields[SiteCode.PFIHash] = HashTool.GetFileHash(pfiPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; // Deal with SS.bin + string ssPath = Path.Combine(outputDirectory, "SS.bin"); if (File.Exists(ssPath)) { - // Save security sector ranges - string? ranges = ProcessingTool.GetSSRanges(ssPath); - if (!string.IsNullOrEmpty(ranges)) - info.Extras.SecuritySectorRanges = ranges; + // Ensure a raw SS is saved (recreate from log if needed) + RecreateSS(logPath!, ssPath, Path.Combine(outputDirectory, "RawSS.bin"));. - // Recreate RawSS.bin - RecreateSS(logPath!, ssPath, Path.Combine(outputDirectory, "RawSS.bin")); + if (ProcessingTool.IsValidSS(ssPath)) + { + // Save security sector ranges + string? ranges = ProcessingTool.GetSSRanges(ssPath); + if (!string.IsNullOrEmpty(ranges)) + info.Extras.SecuritySectorRanges = ranges; + } - // Run ss_sector_range to get repeatable SS hash - ProcessingTool.CleanSS(ssPath, ssPath); - } + // Repair and clean SS, only hash SS if successful + if (ProcessingTool.FixSS(ssPath, ssPath)) + info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = HashTool.GetFileHash(ssPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; - // DMI/PFI/SS CRC32 hashes - if (File.Exists(dmiPath)) - info.CommonDiscInfo.CommentsSpecialFields[SiteCode.DMIHash] = HashTool.GetFileHash(dmiPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; - if (File.Exists(pfiPath)) - info.CommonDiscInfo.CommentsSpecialFields[SiteCode.PFIHash] = HashTool.GetFileHash(pfiPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; - if (File.Exists(ssPath)) - info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = HashTool.GetFileHash(ssPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; + } } /// @@ -507,18 +508,18 @@ internal bool GetReadErrors(string? log, out long readErrors) } /// - /// Recreate an SS.bin file from XBC log and write it to a file + /// Recreate a RawSS.bin file from XBC log and write it to a file /// /// Path to XBC log - /// Path to the clean SS file to read from - /// Path to the raw SS file to write to + /// Path to the provided SS file to read from + /// Path to the recreated SS file to write to /// True if successful, false otherwise - private static bool RecreateSS(string log, string cleanSS, string rawSS) + private static bool RecreateSS(string log, string currentSS, string rawSS) { - if (!File.Exists(log) || !File.Exists(cleanSS)) + if (!File.Exists(log) || !File.Exists(currentSS) || File.Exists(rawSS)) return false; - byte[] ss = File.ReadAllBytes(cleanSS); + byte[] ss = File.ReadAllBytes(currentSS); if (ss.Length != 2048) return false; From fbaf6674a9018b11c89731a16fb9a7aae21e8f77 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:09:06 +0900 Subject: [PATCH 02/17] Fix SS validity check --- MPF.Processors/ProcessingTool.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index dfb79a91d..57f0c1bc8 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1105,7 +1105,7 @@ public static bool IsValidSS(byte[] ss) return true; // Determine if XGD3 SS is invalid SSv1 (Original Kreon) or valid SSv2 (0800 / Custom Kreon) - if (xgdType != 3) + if (xgdType == 3) { bool bad_xgd3 = false; #if NET20 @@ -1119,8 +1119,8 @@ public static bool IsValidSS(byte[] ss) return false; } - // Must have correct version and number of CCRT entries - if (ss[0x300] != 2 || ss[0x301] != 21 || ss[0x65F] != 0x02 || ss[0x49E != 0x04]) + // XGD2 must have correct version (2) and number of CCRT entries (21) + if (ss[0x300] != 2 || ss[0x301] != 21 || ss[0x65F] != 2) return false; return true; From 0e9dc60bcc93d1bdb1fe8e793a8afbe3103dc3c7 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:18:11 +0900 Subject: [PATCH 03/17] Clean SS for DIC dumps too --- MPF.Processors/DiscImageCreator.cs | 51 ++++++++++++++++++++++++++--- MPF.Processors/XboxBackupCreator.cs | 8 +++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/MPF.Processors/DiscImageCreator.cs b/MPF.Processors/DiscImageCreator.cs index f4bfe440c..a761faad0 100644 --- a/MPF.Processors/DiscImageCreator.cs +++ b/MPF.Processors/DiscImageCreator.cs @@ -345,7 +345,8 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi { info.CommonDiscInfo.CommentsSpecialFields[SiteCode.DMIHash] = xgd1DMIHash ?? string.Empty; info.CommonDiscInfo.CommentsSpecialFields[SiteCode.PFIHash] = xgd1PFIHash ?? string.Empty; - info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd1SSHash ?? string.Empty; + // Don't put raw SS hash from _suppl.dat / _disc.txt in submission info + //info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd1SSHash ?? string.Empty; } if (GetXGDAuxInfo($"{basePath}_disc.txt", out _, out _, out _, out var xgd1SS)) @@ -359,11 +360,30 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi { info.CommonDiscInfo.CommentsSpecialFields[SiteCode.DMIHash] = xgd1DMIHash ?? string.Empty; info.CommonDiscInfo.CommentsSpecialFields[SiteCode.PFIHash] = xgd1PFIHash ?? string.Empty; - info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd1SSHash ?? string.Empty; + // Don't put raw SS hash from _suppl.dat / _disc.txt in submission info + //info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd1SSHash ?? string.Empty; info.Extras.SecuritySectorRanges = xgd1SS ?? string.Empty; } } + string xgd1SSPath = $"{basePath}_SS.bin"; + string xgd1RawSSPath = $"{basePath}_RawSS.bin"; + if (File.Exists(xgd1SSPath) && ProcessingTool.IsValidSS(xgd1SSPath)) + { + // Save untouched SS + if (!File.Exists(xgd1RawSSPath)) + File.Move(xgd1SSPath, xgd1RawSSPath); + + + // Repair, clean, and validate SS before adding hash to submission info + if (ProcessingTool.FixSS(xgd1SSPath, xgd1SSPath)) + { + string? xgd1SSCrc = HashTool.GetFileHash(xgd1SSPath, HashType.CRC32); + if (xgd1SSCrc is not null) + info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd1SSCrc.ToUpperInvariant(); + } + } + break; case RedumpSystem.MicrosoftXbox360: @@ -387,7 +407,8 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi { info.CommonDiscInfo.CommentsSpecialFields[SiteCode.DMIHash] = xgd23DMIHash ?? string.Empty; info.CommonDiscInfo.CommentsSpecialFields[SiteCode.PFIHash] = xgd23PFIHash ?? string.Empty; - info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd23SSHash ?? string.Empty; + // Don't put raw SS hash from _suppl.dat / _disc.txt in submission info + //info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd23SSHash ?? string.Empty; } if (GetXGDAuxInfo($"{basePath}_disc.txt", out _, out _, out _, out var xgd23SS)) @@ -401,11 +422,30 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi { info.CommonDiscInfo.CommentsSpecialFields[SiteCode.DMIHash] = xgd23DMIHash ?? string.Empty; info.CommonDiscInfo.CommentsSpecialFields[SiteCode.PFIHash] = xgd23PFIHash ?? string.Empty; - info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd23SSHash ?? string.Empty; + // Don't put raw SS hash from _suppl.dat / _disc.txt in submission info + //info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd23SSHash ?? string.Empty; info.Extras.SecuritySectorRanges = xgd23SS ?? string.Empty; } } + string xgd2SSPath = $"{basePath}_SS.bin"; + string xgd2RawSSPath = $"{basePath}_RawSS.bin"; + if (File.Exists(xgd2SSPath) && ProcessingTool.IsValidSS(xgd2SSPath)) + { + // Save untouched SS + if (!File.Exists(xgd2RawSSPath)) + File.Move(xgd2SSPath, xgd2RawSSPath); + + + // Repair, clean, and validate SS before adding hash to submission info + if (ProcessingTool.FixSS(xgd2SSPath, xgd2SSPath)) + { + string? xgd2SSCrc = HashTool.GetFileHash(xgd2SSPath, HashType.CRC32); + if (xgd2SSCrc is not null) + info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = xgd2SSCrc.ToUpperInvariant(); + } + } + break; case RedumpSystem.NamcoSegaNintendoTriforce: @@ -787,6 +827,9 @@ internal override List GetOutputFiles(MediaType? mediaType, string? ? OutputFileFlags.Required | OutputFileFlags.Binary | OutputFileFlags.Zippable : OutputFileFlags.Binary | OutputFileFlags.Zippable, "ss"), + new($"{outputFilename}_RawSS.bin", + OutputFileFlags.Binary | OutputFileFlags.Zippable, + "raw_ss"), ]; case MediaType.HDDVD: diff --git a/MPF.Processors/XboxBackupCreator.cs b/MPF.Processors/XboxBackupCreator.cs index 0a07e0b47..a04e79c23 100644 --- a/MPF.Processors/XboxBackupCreator.cs +++ b/MPF.Processors/XboxBackupCreator.cs @@ -118,7 +118,7 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi if (File.Exists(ssPath)) { // Ensure a raw SS is saved (recreate from log if needed) - RecreateSS(logPath!, ssPath, Path.Combine(outputDirectory, "RawSS.bin"));. + RecreateSS(logPath!, ssPath, Path.Combine(outputDirectory, "RawSS.bin")); if (ProcessingTool.IsValidSS(ssPath)) { @@ -130,7 +130,11 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi // Repair and clean SS, only hash SS if successful if (ProcessingTool.FixSS(ssPath, ssPath)) - info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = HashTool.GetFileHash(ssPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; + { + string? ssCrc = HashTool.GetFileHash(ssPath, HashType.CRC32); + if (ssCrc is not null) + info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = ssCrc.ToUpperInvariant(); + } } } From 67e5401a7c1bc44350be910384248dcdde226e2b Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:27:33 +0900 Subject: [PATCH 04/17] Fix build --- MPF.Processors/DiscImageCreator.cs | 1 + MPF.Processors/ProcessingTool.cs | 21 +++++++++++++++++++-- MPF.Processors/XboxBackupCreator.cs | 5 ----- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/MPF.Processors/DiscImageCreator.cs b/MPF.Processors/DiscImageCreator.cs index a761faad0..ed270532e 100644 --- a/MPF.Processors/DiscImageCreator.cs +++ b/MPF.Processors/DiscImageCreator.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using MPF.Processors.OutputFiles; using SabreTools.Data.Models.Logiqx; +using SabreTools.Hashing; using SabreTools.RedumpLib.Data; #if NET462_OR_GREATER || NETCOREAPP using SharpCompress.Archives; diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index 57f0c1bc8..e28f215f1 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1097,7 +1097,7 @@ public static bool IsValidSS(byte[] ss) return false; // Drive entry table must be duplicated exactly - if (!data.Skip(0x661).Take(0x730 - 0x661).SequenceEqual(data.Skip(0x730).Take(0x7FF - 0x730))) + if (!ss.Skip(0x661).Take(0x730 - 0x661).SequenceEqual(ss.Skip(0x730).Take(0x7FF - 0x730))) return false; // Remaining checks are only for Xbox360 SS @@ -1126,6 +1126,23 @@ public static bool IsValidSS(byte[] ss) return true; } + /// + /// Determine if a given SS file has already been repaired and cleaned + /// + /// Path to the SS to check + /// True if SS is repaired and cleaned, false otherwise + public static bool IsFixedSS(string ssPath) + { + if (!File.Exists(ssPath)) + return false; + + byte[] ss = File.ReadAllBytes(ssPath); + if (ss.Length != 2048) + return false; + + return IsFixedSS(ss); + } + /// /// Determine if a given SS has already been repaired and cleaned /// @@ -1136,7 +1153,7 @@ public static bool IsFixedSS(byte[] ss) if (ss.Length != 2048) return false; - if (!IsValid(ss)) + if (!IsValidSS(ss)) return false; if (!GetXGDType(ss, out int xgdType)) diff --git a/MPF.Processors/XboxBackupCreator.cs b/MPF.Processors/XboxBackupCreator.cs index a04e79c23..6d23a5798 100644 --- a/MPF.Processors/XboxBackupCreator.cs +++ b/MPF.Processors/XboxBackupCreator.cs @@ -557,11 +557,6 @@ private static bool RecreateSS(string log, byte[] ss) if (xgdType == 0) return false; - // Don't recreate an already raw SS - // (but do save to file, so return true) - if (!ProcessingTool.IsCleanSS(ss)) - return true; - // Example replay table: /* ---------------------------------------- From 1cef42aa92565c04279ff7462eb549bad64e470f Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:38:19 +0900 Subject: [PATCH 05/17] Account for net20 --- MPF.Processors/ProcessingTool.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index e28f215f1..0305d6b74 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1097,7 +1097,16 @@ public static bool IsValidSS(byte[] ss) return false; // Drive entry table must be duplicated exactly - if (!ss.Skip(0x661).Take(0x730 - 0x661).SequenceEqual(ss.Skip(0x730).Take(0x7FF - 0x730))) + +#if NET20 + var table1 = new byte[207]; + Array.Copy(ss, 0x661, table1, 0, 207); + var table2 = new byte[207]; + Array.Copy(ss, 0x730, table2, 0, 207); + if (Array.Exists(Enumerable.Range(0, a.Length).ToArray(), i => table1[i] != table2[i])) +#else + if (!ss.AsSpan(0x661, 0x730 - 0x661).SequenceEqual(ss.AsSpan(0x730, 0x7FF - 0x730))) +#endif return false; // Remaining checks are only for Xbox360 SS From 47db8ae41441da8448a52e4ea2ec793fb0f3922d Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:49:26 +0900 Subject: [PATCH 06/17] Account for old dotnet --- MPF.Processors/ProcessingTool.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index 0305d6b74..3e04da8d5 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1097,16 +1097,16 @@ public static bool IsValidSS(byte[] ss) return false; // Drive entry table must be duplicated exactly - -#if NET20 - var table1 = new byte[207]; - Array.Copy(ss, 0x661, table1, 0, 207); - var table2 = new byte[207]; - Array.Copy(ss, 0x730, table2, 0, 207); - if (Array.Exists(Enumerable.Range(0, a.Length).ToArray(), i => table1[i] != table2[i])) -#else - if (!ss.AsSpan(0x661, 0x730 - 0x661).SequenceEqual(ss.AsSpan(0x730, 0x7FF - 0x730))) -#endif + bool mismatch = false; + for (int i = 0; i < 207; i++) + { + if (ss[0x661 + i] != ss[0x730 + i]) + { + mismatch = true; + break; + } + } + if (mismatch) return false; // Remaining checks are only for Xbox360 SS From 9fb4f20a386a7f30992d1fa137fa081e8f5479e0 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:56:28 +0900 Subject: [PATCH 07/17] Fix tests --- MPF.Processors.Test/DiscImageCreatorTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MPF.Processors.Test/DiscImageCreatorTests.cs b/MPF.Processors.Test/DiscImageCreatorTests.cs index abc766cc3..46e1db688 100644 --- a/MPF.Processors.Test/DiscImageCreatorTests.cs +++ b/MPF.Processors.Test/DiscImageCreatorTests.cs @@ -117,7 +117,7 @@ public void GetOutputFiles_DVD_Populated() var processor = new DiscImageCreator(RedumpSystem.IBMPCcompatible); var actual = processor.GetOutputFiles(MediaType.DVD, outputDirectory, outputFilename); - Assert.Equal(16, actual.Count); + Assert.Equal(17, actual.Count); } [Fact] @@ -128,7 +128,7 @@ public void GetOutputFiles_NintendoGameCubeGameDisc_Populated() var processor = new DiscImageCreator(RedumpSystem.IBMPCcompatible); var actual = processor.GetOutputFiles(MediaType.NintendoGameCubeGameDisc, outputDirectory, outputFilename); - Assert.Equal(16, actual.Count); + Assert.Equal(17, actual.Count); } [Fact] @@ -139,7 +139,7 @@ public void GetOutputFiles_NintendoWiiOpticalDisc_Populated() var processor = new DiscImageCreator(RedumpSystem.IBMPCcompatible); var actual = processor.GetOutputFiles(MediaType.NintendoWiiOpticalDisc, outputDirectory, outputFilename); - Assert.Equal(16, actual.Count); + Assert.Equal(17, actual.Count); } [Fact] From dc9f81df8dff71cfd79f831a904f011dde2cd2d2 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:08:51 +0900 Subject: [PATCH 08/17] decrypt response table --- MPF.Processors/ProcessingTool.cs | 153 ++++++++++++++++++++----------- 1 file changed, 100 insertions(+), 53 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index 3e04da8d5..055bedf0f 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -3,6 +3,7 @@ #if NET35_OR_GREATER || NETCOREAPP using System.Linq; #endif +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -1168,7 +1169,7 @@ public static bool IsFixedSS(byte[] ss) if (!GetXGDType(ss, out int xgdType)) return false; - // Valid XGD1 is always fixed + // XGD1 can't be fixed if (xgdType == 1) return true; @@ -1209,7 +1210,7 @@ public static bool IsFixedSS(byte[] ss) if (ss[rtOffset + 67] != 0x01) return false; } - else + else if (xgdType == 2) { // Check for a cleaned XGD2 int rtOffset = 0x204; @@ -1247,33 +1248,8 @@ public static bool IsFixedSS(byte[] ss) return false; } - // Determine challenge table offset - int ccrt_offset = 0; - if (xgdType == 2) - ccrt_offset = 0x200; - else if (xgdType == 3) - ccrt_offset = 0x20; - - // Check for empty challenge responses - int[] entry_offsets = [0, 9, 18, 27, 36, 45, 54, 63]; - int[] entry_lengths = [8, 8, 8, 8, 4, 4, 4, 4]; - for (int i = 0; i < entry_offsets.Length; i++) - { - bool emptyResponse = true; - for (int b = 0; b < entry_lengths[i]; b++) - { - if (ss[ccrt_offset + entry_offsets[i] + b] != 0x00) - { - emptyResponse = false; - break; - } - } - - if (emptyResponse) - return false; - } - - // TODO: Check for correct challenge responses + // Check challenge responses + FixSS(ss, false); return true; } @@ -1306,7 +1282,7 @@ public static bool FixSS(string rawSS, string fixedSS) /// /// Byte array of raw SS sector /// True if successful, false otherwise - public static bool FixSS(byte[] ss) + public static bool FixSS(byte[] ss, bool write = true) { // Must be entire sector if (ss.Length != 2048) @@ -1337,34 +1313,37 @@ public static bool FixSS(byte[] ss) switch (xgdType) { case 1: - // Cannot clean XGD1 SS.bin - break; + // Cannot clean or fix XGD1 SS + return true; case 2: // Fix standard SSv1 ss.bin - ss[552] = 1; // 0x01 - ss[553] = 0; // 0x00 - ss[555] = 0; // 0x00 - ss[556] = 0; // 0x00 - - ss[561] = 91; // 0x5B - ss[562] = 0; // 0x00 - ss[564] = 0; // 0x00 - ss[565] = 0; // 0x00 - - ss[570] = 181; // 0xB5 - ss[571] = 0; // 0x00 - ss[573] = 0; // 0x00 - ss[574] = 0; // 0x00 - - ss[579] = 15; // 0x0F - ss[580] = 1; // 0x01 - ss[582] = 0; // 0x00 - ss[583] = 0; // 0x00 + if (write) + { + ss[552] = 1; // 0x01 + ss[553] = 0; // 0x00 + ss[555] = 0; // 0x00 + ss[556] = 0; // 0x00 + + ss[561] = 91; // 0x5B + ss[562] = 0; // 0x00 + ss[564] = 0; // 0x00 + ss[565] = 0; // 0x00 + + ss[570] = 181; // 0xB5 + ss[571] = 0; // 0x00 + ss[573] = 0; // 0x00 + ss[574] = 0; // 0x00 + + ss[579] = 15; // 0x0F + ss[580] = 1; // 0x01 + ss[582] = 0; // 0x00 + ss[583] = 0; // 0x00 + } break; case 3: - if (ssv2) + if (write && ssv2) { ss[72] = 1; // 0x01 ss[73] = 0; // 0x00 @@ -1386,7 +1365,7 @@ public static bool FixSS(byte[] ss) ss[102] = 15; // 0x0F ss[103] = 1; // 0x01 } - else + else if (write) { ss[552] = 1; // 0x01 ss[553] = 0; // 0x00 @@ -1407,6 +1386,74 @@ public static bool FixSS(byte[] ss) return false; } + // Determine challenge table offset + int ccrt_offset = 0; + if (xgdType == 2) + ccrt_offset = 0x200; + else if (xgdType == 3) + ccrt_offset = 0x20; + + // Setup decryptor +#if NET20 + using RijndaelManaged aes = new RijndaelManaged(); + aes.BlockSize = 128; +#else + using Aes aes = Aes.Create(); +#endif + aes.Key = new byte[] { 0xD1, 0xE3, 0xB3, 0x3A, 0x6C, 0x1E, 0xF7, 0x70, 0x5F, 0x6D, 0xE9, 0x3B, 0xB6, 0xC0, 0xDC, 0x71 }; + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + using ICryptoTransform decryptor = aes.CreateDecryptor(); + + // Perform decryption + byte[] iv = new byte[16]; + byte[] dcrt = new byte[252]; + bool ct01_found = false; + for (int i = 0; i < 240; i+=16) + { + decryptor.TransformBlock(ss, 0x304 + i, 16, dcrt, i); + for (int j = 0; j < 16; j++) + { + dcrt[i + j] ^= iv[j]; + iv[j] = ss[0x304 + i + j]; + } + // Validate challenge type 1 + if (dcrt[i] == 1) + { + // Cannot fix SS with two type 1 challenges + if (ct01_found) + return false; + ct01_found = true; + // Challenge type 1 must match CPR_MAI + int cpr_mai_offset = (xgdType == 3) ? 0xF0 : 0x2D0; + if (dcrt[i + 4] != ss[cpr_mai_offset] || dcrt[i + 5] != ss[cpr_mai_offset + 1] || dcrt[i + 6] != ss[cpr_mai_offset + 2] || dcrt[i + 7] != ss[cpr_mai_offset + 3]) + return false; + } + // Cannot fix unknown challenge types + else if (dcrt[i] != 0xE0 && dcrt[i] != 0x14 && dcrt[i] != 0x15 && dcrt[i] != 0x24 && dcrt[i] != 0x25 && (dcrt[i] & 0xF) != 0xF0) + return false; + } + Array.Copy(ss, 0x304 + 240, dcrt, 240, 12); + + // Check for empty challenge responses + int[] entryOffsets = [0, 9, 18, 27, 36, 45, 54, 63]; + int[] entryLengths = [8, 8, 8, 8, 4, 4, 4, 4]; + for (int i = 0; i < entryOffsets.Length; i++) + { + bool emptyResponse = true; + for (int b = 0; b < entryLengths[i]; b++) + { + if (ss[ccrt_offset + entryOffsets[i] + b] != 0x00) + { + emptyResponse = false; + break; + } + } + + if (emptyResponse) + return false; + } + // TODO: Repair challenge responses return true; From 28d63a1282566f62e9bfbe3b10894fac0e81b363 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:41:52 +0900 Subject: [PATCH 09/17] Update ProcessingTool.cs --- MPF.Processors/ProcessingTool.cs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index 055bedf0f..334ab9cf1 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -966,7 +966,7 @@ public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]? #region Xbox and Xbox 360 /// - /// Get the XGD1 Master ID (XMID) information + /// Get the XGD1 Manufacturing ID (XMID) information /// /// DMI.bin file location /// String representation of the XGD1 DMI information, empty string on error @@ -1248,10 +1248,8 @@ public static bool IsFixedSS(byte[] ss) return false; } - // Check challenge responses - FixSS(ss, false); - - return true; + // Check challenge responses (don't write) + return FixSS(ss, false); } /// @@ -1365,20 +1363,6 @@ public static bool FixSS(byte[] ss, bool write = true) ss[102] = 15; // 0x0F ss[103] = 1; // 0x01 } - else if (write) - { - ss[552] = 1; // 0x01 - ss[553] = 0; // 0x00 - - ss[561] = 91; // 0x5B - ss[562] = 0; // 0x00 - - ss[570] = 181; // 0xB5 - ss[571] = 0; // 0x00 - - ss[579] = 15; // 0x0F - ss[580] = 1; // 0x01 - } break; default: @@ -1386,6 +1370,10 @@ public static bool FixSS(byte[] ss, bool write = true) return false; } + // Must be 21 challenge entries + if (ss[0x660] != 21) + return false; + // Determine challenge table offset int ccrt_offset = 0; if (xgdType == 2) @@ -1417,6 +1405,7 @@ public static bool FixSS(byte[] ss, bool write = true) dcrt[i + j] ^= iv[j]; iv[j] = ss[0x304 + i + j]; } + // Validate challenge type 1 if (dcrt[i] == 1) { @@ -1435,7 +1424,6 @@ public static bool FixSS(byte[] ss, bool write = true) } Array.Copy(ss, 0x304 + 240, dcrt, 240, 12); - // Check for empty challenge responses int[] entryOffsets = [0, 9, 18, 27, 36, 45, 54, 63]; int[] entryLengths = [8, 8, 8, 8, 4, 4, 4, 4]; for (int i = 0; i < entryOffsets.Length; i++) From 7dcb946525953a979e6323f7b48c4857b6a19fa7 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:09:11 +0900 Subject: [PATCH 10/17] Repair CCRT --- MPF.Processors/ProcessingTool.cs | 244 +++++++++++++++++++++---------- 1 file changed, 166 insertions(+), 78 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index 334ab9cf1..16c4fa42e 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1294,6 +1294,10 @@ public static bool FixSS(byte[] ss, bool write = true) if (!GetXGDType(ss, out int xgdType)) return false; + // Cannot fix XGD1 + if (xgdType == 1) + return true; + // Determine if XGD3 SS.bin is SSv1 (Original Kreon) or SSv2 (0800 / Repaired Kreon) #if NET20 var checkArr = new byte[72]; @@ -1307,13 +1311,171 @@ public static bool FixSS(byte[] ss, bool write = true) if (xgdType == 3 && !ssv2) return false; + // Must be 21 challenge entries + if (ss[0x660] != 21) + return false; + + // Setup decryptor +#if NET20 + using RijndaelManaged aes = new RijndaelManaged(); + aes.BlockSize = 128; +#else + using Aes aes = Aes.Create(); +#endif + aes.Key = new byte[] { 0xD1, 0xE3, 0xB3, 0x3A, 0x6C, 0x1E, 0xF7, 0x70, 0x5F, 0x6D, 0xE9, 0x3B, 0xB6, 0xC0, 0xDC, 0x71 }; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + using ICryptoTransform decryptor = aes.CreateDecryptor(); + + // Perform decryption + byte[] iv = new byte[16]; + byte[] dcrt = new byte[252]; + bool ct01_found = false; + for (int i = 0; i < 240; i+=16) + decryptor.TransformBlock(ss, 0x304 + i, 16, dcrt, i); + Array.Copy(ss, 0x304 + 240, dcrt, 240, 12); + + // Rebuild challenge response table + var cids = new Dictionary(); + for (int i = 0; i < dcrt.Length; i+=12) + { + // Validate challenge type 1 + if (dcrt[i] == 1) + { + // Cannot fix SS with two type 1 challenges + if (ct01_found) + return false; + ct01_found = true; + // Challenge type 1 must match CPR_MAI + int cpr_mai_offset = (xgdType == 3) ? 0xF0 : 0x2D0; + if (dcrt[i + 4] != ss[cpr_mai_offset] || dcrt[i + 5] != ss[cpr_mai_offset + 1] || dcrt[i + 6] != ss[cpr_mai_offset + 2] || dcrt[i + 7] != ss[cpr_mai_offset + 3]) + return false; + } + // Check CIDs of known challenges + else if (dcrt[i] == 0x14 || dcrt[i] == 0x15 || dcrt[i] == 0x24 || dcrt[i] == 0x25 || dcrt[i] != 0xE0 || (dcrt[i] & 0xF) != 0xF0) + { + // Cannot fix SS with duplicate Challenge IDs + if (cids.ContainsKey(dcrt[i + 1])) + return false; + cids.Add(dcrt[i + 1], i); + } + // Cannot fix SS with unknown challenge types + else + return false; + } + + // Determine challenge table offset + int ccrt_offset = 0; + if (xgdType == 2) + ccrt_offset = 0x200; + else if (xgdType == 3) + ccrt_offset = 0x20; + + // Repair challenge table + for (int i = 0; i < 23; i++) + { + // Cannot rebuild SS with orphan challenge ID + if (!cids.TryGetValue(ss[0x730 + i * 9 + 1], out byte cOffset)) + return; + + // Validate challenge type with response type + byte rOffset = 0x730 + i * 9; + bool angle_challenge = false; + bool other_challenge = false; + int challenge_count = 0; + switch (ss[cOffset]) + { + case 0x14: + if (ss[rOffset] != 3) + return false; + challenge_count += 1; + // Challenge must be in expected order + if (challenge_count > 5) + return false; + break; + case 0x15: + if (ss[rOffset] != 1) + return false; + challenge_count += 1; + // Challenge must be in expected order + if (challenge_count < 5) + return false; + break; + case 0x24: + if (ss[rOffset] != 7) + return false; + challenge_count += 1; + // Challenge must be in expected order + if (challenge_count < 5 || challenge_count > 8) + return false; + angle_challenge = true; + break; + case 0x25: + if (ss[rOffset] != 9) + return false; + challenge_count += 1; + // Challenge must be in expected order + if (challenge_count < 5 || challenge_count > 8) + return false; + angle_challenge = true; + break; + default: + other_challenge = true; + break; + } + + // Skip other challenges + if (other_challenge) + continue; + + // Set/check challenge data + if (!write && ss[ccrt_offset + i * 9] != ss[cOffset + 4]) + return false; + else + ss[ccrt_offset + i * 9] = ss[cOffset + 4]; + if (!write && ss[ccrt_offset + i * 9 + 1] != ss[cOffset + 5]) + return false + else + ss[ccrt_offset + i * 9 + 1] = ss[cOffset + 5]; + if (!write && ss[ccrt_offset + i * 9 + 2] != ss[cOffset + 6]) + return false + else + ss[ccrt_offset + i * 9 + 2] = ss[cOffset + 6]; + if (!write && ss[ccrt_offset + i * 9 + 3] != ss[cOffset + 7]) + return false + else + ss[ccrt_offset + i * 9 + 2] = ss[cOffset + 7]; + + // Set challenge response for non-angle challenges + if (!angle_challenge) + { + if(!write && ss[ccrt_offset + i * 9 + 4] != ss[cOffset + 8]) + return false; + else + ss[ccrt_offset + i * 9 + 4] = ss[cOffset + 8]; + if(!write && ss[ccrt_offset + i * 9 + 5] != ss[cOffset + 9]) + return false; + else + ss[ccrt_offset + i * 9 + 5] = ss[cOffset + 9]; + if(!write && ss[ccrt_offset + i * 9 + 6] != ss[cOffset + 10]) + return false; + else + ss[ccrt_offset + i * 9 + 6] = ss[cOffset + 10]; + if(!write && ss[ccrt_offset + i * 9 + 7] != ss[cOffset + 11]) + return false; + else + ss[ccrt_offset + i * 9 + 7] = ss[cOffset + 11]; + if(!write && ss[ccrt_offset + i * 9 + 8] != 0) + return false; + else + ss[ccrt_offset + i * 9 + 8] = 0; + } + + } + // Clean SS (set fixed angles) switch (xgdType) { - case 1: - // Cannot clean or fix XGD1 SS - return true; - case 2: // Fix standard SSv1 ss.bin if (write) @@ -1370,80 +1532,6 @@ public static bool FixSS(byte[] ss, bool write = true) return false; } - // Must be 21 challenge entries - if (ss[0x660] != 21) - return false; - - // Determine challenge table offset - int ccrt_offset = 0; - if (xgdType == 2) - ccrt_offset = 0x200; - else if (xgdType == 3) - ccrt_offset = 0x20; - - // Setup decryptor -#if NET20 - using RijndaelManaged aes = new RijndaelManaged(); - aes.BlockSize = 128; -#else - using Aes aes = Aes.Create(); -#endif - aes.Key = new byte[] { 0xD1, 0xE3, 0xB3, 0x3A, 0x6C, 0x1E, 0xF7, 0x70, 0x5F, 0x6D, 0xE9, 0x3B, 0xB6, 0xC0, 0xDC, 0x71 }; - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - using ICryptoTransform decryptor = aes.CreateDecryptor(); - - // Perform decryption - byte[] iv = new byte[16]; - byte[] dcrt = new byte[252]; - bool ct01_found = false; - for (int i = 0; i < 240; i+=16) - { - decryptor.TransformBlock(ss, 0x304 + i, 16, dcrt, i); - for (int j = 0; j < 16; j++) - { - dcrt[i + j] ^= iv[j]; - iv[j] = ss[0x304 + i + j]; - } - - // Validate challenge type 1 - if (dcrt[i] == 1) - { - // Cannot fix SS with two type 1 challenges - if (ct01_found) - return false; - ct01_found = true; - // Challenge type 1 must match CPR_MAI - int cpr_mai_offset = (xgdType == 3) ? 0xF0 : 0x2D0; - if (dcrt[i + 4] != ss[cpr_mai_offset] || dcrt[i + 5] != ss[cpr_mai_offset + 1] || dcrt[i + 6] != ss[cpr_mai_offset + 2] || dcrt[i + 7] != ss[cpr_mai_offset + 3]) - return false; - } - // Cannot fix unknown challenge types - else if (dcrt[i] != 0xE0 && dcrt[i] != 0x14 && dcrt[i] != 0x15 && dcrt[i] != 0x24 && dcrt[i] != 0x25 && (dcrt[i] & 0xF) != 0xF0) - return false; - } - Array.Copy(ss, 0x304 + 240, dcrt, 240, 12); - - int[] entryOffsets = [0, 9, 18, 27, 36, 45, 54, 63]; - int[] entryLengths = [8, 8, 8, 8, 4, 4, 4, 4]; - for (int i = 0; i < entryOffsets.Length; i++) - { - bool emptyResponse = true; - for (int b = 0; b < entryLengths[i]; b++) - { - if (ss[ccrt_offset + entryOffsets[i] + b] != 0x00) - { - emptyResponse = false; - break; - } - } - - if (emptyResponse) - return false; - } - - // TODO: Repair challenge responses - return true; } From 9b184a931bd8eb75574207e6f17fbde230c2a2b0 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:43:04 +0900 Subject: [PATCH 11/17] fix --- MPF.Processors/ProcessingTool.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index 16c4fa42e..daf23cb14 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1434,15 +1434,15 @@ public static bool FixSS(byte[] ss, bool write = true) else ss[ccrt_offset + i * 9] = ss[cOffset + 4]; if (!write && ss[ccrt_offset + i * 9 + 1] != ss[cOffset + 5]) - return false + return false; else ss[ccrt_offset + i * 9 + 1] = ss[cOffset + 5]; if (!write && ss[ccrt_offset + i * 9 + 2] != ss[cOffset + 6]) - return false + return false; else ss[ccrt_offset + i * 9 + 2] = ss[cOffset + 6]; if (!write && ss[ccrt_offset + i * 9 + 3] != ss[cOffset + 7]) - return false + return false; else ss[ccrt_offset + i * 9 + 2] = ss[cOffset + 7]; From 7afd56890422a8ad2a1fb08961308a5d31054fc9 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:33:17 +0900 Subject: [PATCH 12/17] fix build --- MPF.Processors/ProcessingTool.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index daf23cb14..1bf5ce006 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; #if NET35_OR_GREATER || NETCOREAPP using System.Linq; @@ -1376,10 +1377,10 @@ public static bool FixSS(byte[] ss, bool write = true) { // Cannot rebuild SS with orphan challenge ID if (!cids.TryGetValue(ss[0x730 + i * 9 + 1], out byte cOffset)) - return; + return false; // Validate challenge type with response type - byte rOffset = 0x730 + i * 9; + int rOffset = 0x730 + i * 9); bool angle_challenge = false; bool other_challenge = false; int challenge_count = 0; From 624590f50516838c3d5872a7cecf60c8a6a9dbde Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:38:50 +0900 Subject: [PATCH 13/17] fix --- MPF.Processors/ProcessingTool.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index 1bf5ce006..93c213791 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1380,7 +1380,7 @@ public static bool FixSS(byte[] ss, bool write = true) return false; // Validate challenge type with response type - int rOffset = 0x730 + i * 9); + int rOffset = 0x730 + i * 9; bool angle_challenge = false; bool other_challenge = false; int challenge_count = 0; @@ -1471,7 +1471,6 @@ public static bool FixSS(byte[] ss, bool write = true) else ss[ccrt_offset + i * 9 + 8] = 0; } - } // Clean SS (set fixed angles) From d13f4976b1be7032761c55dd66735596d8f0286c Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:44:43 +0900 Subject: [PATCH 14/17] cid value is int --- MPF.Processors/ProcessingTool.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index 93c213791..e6745054e 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1337,7 +1337,7 @@ public static bool FixSS(byte[] ss, bool write = true) Array.Copy(ss, 0x304 + 240, dcrt, 240, 12); // Rebuild challenge response table - var cids = new Dictionary(); + var cids = new Dictionary(); for (int i = 0; i < dcrt.Length; i+=12) { // Validate challenge type 1 @@ -1376,7 +1376,7 @@ public static bool FixSS(byte[] ss, bool write = true) for (int i = 0; i < 23; i++) { // Cannot rebuild SS with orphan challenge ID - if (!cids.TryGetValue(ss[0x730 + i * 9 + 1], out byte cOffset)) + if (!cids.TryGetValue(ss[0x730 + i * 9 + 1], out int cOffset)) return false; // Validate challenge type with response type From 53d2201b8fc65155ce773e73f95570796cb04d5b Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 25 Feb 2026 00:33:37 +0900 Subject: [PATCH 15/17] code review --- MPF.Processors/DiscImageCreator.cs | 16 +++--- MPF.Processors/ProcessingTool.cs | 82 ++++++++++++++++------------- MPF.Processors/XboxBackupCreator.cs | 2 +- 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/MPF.Processors/DiscImageCreator.cs b/MPF.Processors/DiscImageCreator.cs index ed270532e..58d507d2f 100644 --- a/MPF.Processors/DiscImageCreator.cs +++ b/MPF.Processors/DiscImageCreator.cs @@ -372,9 +372,11 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi if (File.Exists(xgd1SSPath) && ProcessingTool.IsValidSS(xgd1SSPath)) { // Save untouched SS - if (!File.Exists(xgd1RawSSPath)) - File.Move(xgd1SSPath, xgd1RawSSPath); - + try + { + if (!File.Exists(xgd1RawSSPath)) + File.Move(xgd1SSPath, xgd1RawSSPath); + } // Repair, clean, and validate SS before adding hash to submission info if (ProcessingTool.FixSS(xgd1SSPath, xgd1SSPath)) @@ -434,9 +436,11 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi if (File.Exists(xgd2SSPath) && ProcessingTool.IsValidSS(xgd2SSPath)) { // Save untouched SS - if (!File.Exists(xgd2RawSSPath)) - File.Move(xgd2SSPath, xgd2RawSSPath); - + try + { + if (!File.Exists(xgd2RawSSPath)) + File.Move(xgd2SSPath, xgd2RawSSPath); + } // Repair, clean, and validate SS before adding hash to submission info if (ProcessingTool.FixSS(xgd2SSPath, xgd2SSPath)) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index e6745054e..d57af7e90 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1099,17 +1099,11 @@ public static bool IsValidSS(byte[] ss) return false; // Drive entry table must be duplicated exactly - bool mismatch = false; for (int i = 0; i < 207; i++) { if (ss[0x661 + i] != ss[0x730 + i]) - { - mismatch = true; - break; - } + return false; } - if (mismatch) - return false; // Remaining checks are only for Xbox360 SS if (xgdType == 1) @@ -1118,13 +1112,12 @@ public static bool IsValidSS(byte[] ss) // Determine if XGD3 SS is invalid SSv1 (Original Kreon) or valid SSv2 (0800 / Custom Kreon) if (xgdType == 3) { - bool bad_xgd3 = false; #if NET20 var checkArr = new byte[72]; Array.Copy(ss, 32, checkArr, 0, 72); - bad_xgd3 = Array.Exists(checkArr, x => x != 0); + if(Array.Exists(checkArr, x => x != 0)) #else - bad_xgd3 = ss.Skip(32).Take(72).Any(x => x != 0); + if(ss.Skip(32).Take(72).Any(x => x != 0)) #endif if (bad_xgd3) return false; @@ -1172,17 +1165,18 @@ public static bool IsFixedSS(byte[] ss) // XGD1 can't be fixed if (xgdType == 1) + { return true; - - if (xgdType == 3) + } + else if (xgdType == 2) { - // Check for a cleaned SSv2 - int rtOffset = 0x24; + // Check for a cleaned XGD2 + int rtOffset = 0x204; if (ss[rtOffset + 36] != 0x01) return false; if (ss[rtOffset + 37] != 0x00) return false; - if (ss[rtOffset + 39] != 0x01) + if (ss[rtOffset + 39] != 0x00) return false; if (ss[rtOffset + 40] != 0x00) return false; @@ -1190,7 +1184,7 @@ public static bool IsFixedSS(byte[] ss) return false; if (ss[rtOffset + 46] != 0x00) return false; - if (ss[rtOffset + 48] != 0x5B) + if (ss[rtOffset + 48] != 0x00) return false; if (ss[rtOffset + 49] != 0x00) return false; @@ -1198,7 +1192,7 @@ public static bool IsFixedSS(byte[] ss) return false; if (ss[rtOffset + 55] != 0x00) return false; - if (ss[rtOffset + 57] != 0xB5) + if (ss[rtOffset + 57] != 0x00) return false; if (ss[rtOffset + 58] != 0x00) return false; @@ -1206,20 +1200,20 @@ public static bool IsFixedSS(byte[] ss) return false; if (ss[rtOffset + 64] != 0x01) return false; - if (ss[rtOffset + 66] != 0x0F) + if (ss[rtOffset + 66] != 0x00) return false; - if (ss[rtOffset + 67] != 0x01) + if (ss[rtOffset + 67] != 0x00) return false; } - else if (xgdType == 2) + else if (xgdType == 3) { - // Check for a cleaned XGD2 - int rtOffset = 0x204; + // Check for a cleaned SSv2 + int rtOffset = 0x24; if (ss[rtOffset + 36] != 0x01) return false; if (ss[rtOffset + 37] != 0x00) return false; - if (ss[rtOffset + 39] != 0x00) + if (ss[rtOffset + 39] != 0x01) return false; if (ss[rtOffset + 40] != 0x00) return false; @@ -1227,7 +1221,7 @@ public static bool IsFixedSS(byte[] ss) return false; if (ss[rtOffset + 46] != 0x00) return false; - if (ss[rtOffset + 48] != 0x00) + if (ss[rtOffset + 48] != 0x5B) return false; if (ss[rtOffset + 49] != 0x00) return false; @@ -1235,7 +1229,7 @@ public static bool IsFixedSS(byte[] ss) return false; if (ss[rtOffset + 55] != 0x00) return false; - if (ss[rtOffset + 57] != 0x00) + if (ss[rtOffset + 57] != 0xB5) return false; if (ss[rtOffset + 58] != 0x00) return false; @@ -1243,11 +1237,15 @@ public static bool IsFixedSS(byte[] ss) return false; if (ss[rtOffset + 64] != 0x01) return false; - if (ss[rtOffset + 66] != 0x00) + if (ss[rtOffset + 66] != 0x0F) return false; - if (ss[rtOffset + 67] != 0x00) + if (ss[rtOffset + 67] != 0x01) return false; } + else + { + return false; + } // Check challenge responses (don't write) return FixSS(ss, false); @@ -1277,10 +1275,10 @@ public static bool FixSS(string rawSS, string fixedSS) /// /// Repair and clean a SS sector to its valid, predictable clean form. - /// Note: Also see ss_sector_range and abgx360 /// /// Byte array of raw SS sector /// True if successful, false otherwise + /// Also see ss_sector_range and abgx360 public static bool FixSS(byte[] ss, bool write = true) { // Must be entire sector @@ -1318,26 +1316,27 @@ public static bool FixSS(byte[] ss, bool write = true) // Setup decryptor #if NET20 - using RijndaelManaged aes = new RijndaelManaged(); + using var aes = new RijndaelManaged(); aes.BlockSize = 128; #else - using Aes aes = Aes.Create(); + using var aes = Aes.Create(); #endif - aes.Key = new byte[] { 0xD1, 0xE3, 0xB3, 0x3A, 0x6C, 0x1E, 0xF7, 0x70, 0x5F, 0x6D, 0xE9, 0x3B, 0xB6, 0xC0, 0xDC, 0x71 }; + aes.Key = [0xD1, 0xE3, 0xB3, 0x3A, 0x6C, 0x1E, 0xF7, 0x70, 0x5F, 0x6D, 0xE9, 0x3B, 0xB6, 0xC0, 0xDC, 0x71]; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; - using ICryptoTransform decryptor = aes.CreateDecryptor(); + using var decryptor = aes.CreateDecryptor(); // Perform decryption - byte[] iv = new byte[16]; byte[] dcrt = new byte[252]; bool ct01_found = false; for (int i = 0; i < 240; i+=16) + { decryptor.TransformBlock(ss, 0x304 + i, 16, dcrt, i); + } Array.Copy(ss, 0x304 + 240, dcrt, 240, 12); // Rebuild challenge response table - var cids = new Dictionary(); + Dictionary cids = []; for (int i = 0; i < dcrt.Length; i+=12) { // Validate challenge type 1 @@ -1346,6 +1345,7 @@ public static bool FixSS(byte[] ss, bool write = true) // Cannot fix SS with two type 1 challenges if (ct01_found) return false; + ct01_found = true; // Challenge type 1 must match CPR_MAI int cpr_mai_offset = (xgdType == 3) ? 0xF0 : 0x2D0; @@ -1362,7 +1362,9 @@ public static bool FixSS(byte[] ss, bool write = true) } // Cannot fix SS with unknown challenge types else + { return false; + } } // Determine challenge table offset @@ -1373,6 +1375,7 @@ public static bool FixSS(byte[] ss, bool write = true) ccrt_offset = 0x20; // Repair challenge table + int challenge_count = 0; for (int i = 0; i < 23; i++) { // Cannot rebuild SS with orphan challenge ID @@ -1383,41 +1386,48 @@ public static bool FixSS(byte[] ss, bool write = true) int rOffset = 0x730 + i * 9; bool angle_challenge = false; bool other_challenge = false; - int challenge_count = 0; switch (ss[cOffset]) { case 0x14: if (ss[rOffset] != 3) return false; + challenge_count += 1; // Challenge must be in expected order if (challenge_count > 5) return false; + break; case 0x15: if (ss[rOffset] != 1) return false; + challenge_count += 1; // Challenge must be in expected order if (challenge_count < 5) return false; + break; case 0x24: if (ss[rOffset] != 7) return false; + challenge_count += 1; // Challenge must be in expected order if (challenge_count < 5 || challenge_count > 8) return false; + angle_challenge = true; break; case 0x25: if (ss[rOffset] != 9) return false; + challenge_count += 1; // Challenge must be in expected order if (challenge_count < 5 || challenge_count > 8) return false; + angle_challenge = true; break; default: @@ -1445,7 +1455,7 @@ public static bool FixSS(byte[] ss, bool write = true) if (!write && ss[ccrt_offset + i * 9 + 3] != ss[cOffset + 7]) return false; else - ss[ccrt_offset + i * 9 + 2] = ss[cOffset + 7]; + ss[ccrt_offset + i * 9 + 3] = ss[cOffset + 7]; // Set challenge response for non-angle challenges if (!angle_challenge) diff --git a/MPF.Processors/XboxBackupCreator.cs b/MPF.Processors/XboxBackupCreator.cs index 6d23a5798..22885a2d7 100644 --- a/MPF.Processors/XboxBackupCreator.cs +++ b/MPF.Processors/XboxBackupCreator.cs @@ -109,6 +109,7 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi string dmiPath = Path.Combine(outputDirectory, "DMI.bin"); if (File.Exists(dmiPath)) info.CommonDiscInfo.CommentsSpecialFields[SiteCode.DMIHash] = HashTool.GetFileHash(dmiPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; + string pfiPath = Path.Combine(outputDirectory, "PFI.bin"); if (File.Exists(pfiPath)) info.CommonDiscInfo.CommentsSpecialFields[SiteCode.PFIHash] = HashTool.GetFileHash(pfiPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty; @@ -135,7 +136,6 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi if (ssCrc is not null) info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = ssCrc.ToUpperInvariant(); } - } } From 364716262a21142931edf21f1af2d04c336309d6 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 25 Feb 2026 00:39:28 +0900 Subject: [PATCH 16/17] fix --- MPF.Processors/DiscImageCreator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MPF.Processors/DiscImageCreator.cs b/MPF.Processors/DiscImageCreator.cs index 58d507d2f..dd68ca6db 100644 --- a/MPF.Processors/DiscImageCreator.cs +++ b/MPF.Processors/DiscImageCreator.cs @@ -377,6 +377,7 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi if (!File.Exists(xgd1RawSSPath)) File.Move(xgd1SSPath, xgd1RawSSPath); } + catch { } // Repair, clean, and validate SS before adding hash to submission info if (ProcessingTool.FixSS(xgd1SSPath, xgd1SSPath)) @@ -441,6 +442,7 @@ public override void GenerateSubmissionInfo(SubmissionInfo info, MediaType? medi if (!File.Exists(xgd2RawSSPath)) File.Move(xgd2SSPath, xgd2RawSSPath); } + catch { } // Repair, clean, and validate SS before adding hash to submission info if (ProcessingTool.FixSS(xgd2SSPath, xgd2SSPath)) From 79cb6ca3bebc94e930de231f3ed8bc31a862ada2 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Wed, 25 Feb 2026 00:44:43 +0900 Subject: [PATCH 17/17] fix --- MPF.Processors/ProcessingTool.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MPF.Processors/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index d57af7e90..dbdc7a78f 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1119,7 +1119,6 @@ public static bool IsValidSS(byte[] ss) #else if(ss.Skip(32).Take(72).Any(x => x != 0)) #endif - if (bad_xgd3) return false; }