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] diff --git a/MPF.Processors/DiscImageCreator.cs b/MPF.Processors/DiscImageCreator.cs index f4bfe440c..d7c12f2eb 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; @@ -345,7 +346,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 +361,33 @@ 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 + try + { + if (!File.Exists(xgd1RawSSPath)) + File.Copy(xgd1SSPath, xgd1RawSSPath); + } + catch { } + + // 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 +411,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 +426,33 @@ 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 + try + { + if (!File.Exists(xgd2RawSSPath)) + File.Copy(xgd2SSPath, xgd2RawSSPath); + } + catch { } + + // 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 +834,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/ProcessingTool.cs b/MPF.Processors/ProcessingTool.cs index ed9b51a12..5bee88914 100644 --- a/MPF.Processors/ProcessingTool.cs +++ b/MPF.Processors/ProcessingTool.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; #if NET35_OR_GREATER || NETCOREAPP using System.Linq; #endif +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -965,7 +967,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 @@ -1095,27 +1097,44 @@ 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 + for (int i = 0; i < 207; i++) + { + if (ss[0x661 + i] != ss[0x730 + i]) + 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) + { #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); + if(Array.Exists(checkArr, x => x != 0)) #else - return ss.Skip(32).Take(72).Any(x => x != 0); + if(ss.Skip(32).Take(72).Any(x => x != 0)) #endif + return false; + } + + // 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; } /// - /// Determine if a given SS.bin is valid but contains zeroed challenge responses + /// Determine if a given SS file has already been repaired and cleaned /// - /// Path to the SS file to check - /// True if valid but partial SS.bin, false otherwise - public static bool IsValidPartialSS(string ssPath) + /// 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; @@ -1124,84 +1143,39 @@ public static bool IsValidPartialSS(string ssPath) if (ss.Length != 2048) return false; - return IsValidPartialSS(ss); + return IsFixedSS(ss); } /// - /// 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)) + if (!IsValidSS(ss)) return false; - // Determine challenge table offset, XGD1 is never partial - int ccrt_offset = 0; - if (xgdType == 1) + if (!GetXGDType(ss, out int xgdType)) 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++) + + // XGD1 can't be fixed + if (xgdType == 1) { - 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 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) - return false; - - if (!GetXGDType(ss, out int xgdType)) - return false; - -#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 + 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; @@ -1209,7 +1183,7 @@ public static bool IsCleanSS(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; @@ -1217,7 +1191,7 @@ public static bool IsCleanSS(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; @@ -1225,62 +1199,64 @@ public static bool IsCleanSS(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 + else if (xgdType == 3) { - // Check for a cleaned SSv1 - - 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 (xgdType == 2 && ss[rtOffset + 39] != 0x00) + if (ss[rtOffset + 39] != 0x01) 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] != 0x5B) 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] != 0xB5) 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] != 0x0F) return false; - if (xgdType == 2 && ss[rtOffset + 67] != 0x00) + if (ss[rtOffset + 67] != 0x01) return false; } + else + { + return false; + } - // All angles are as expected, it is clean - return true; + // Check challenge responses (don't write) + return FixSS(ss, false); } /// - /// 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 +1265,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. /// /// Byte array of raw SS sector /// True if successful, false otherwise - public static bool CleanSS(byte[] ss) + /// Also see ss_sector_range and abgx360 + public static bool FixSS(byte[] ss, bool write = true) { // Must be entire sector if (ss.Length != 2048) @@ -1316,6 +1292,10 @@ public static bool CleanSS(byte[] ss) 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]; @@ -1329,37 +1309,215 @@ public static bool CleanSS(byte[] ss) if (xgdType == 3 && !ssv2) return false; - switch (xgdType) + // Must be 21 challenge entries + if (ss[0x660] != 21) + return false; + + // Setup decryptor +#if NET20 + using var aes = new RijndaelManaged(); + aes.BlockSize = 128; +#else + using var aes = Aes.Create(); +#endif + 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; + aes.IV = new byte[16]; + using var decryptor = aes.CreateDecryptor(); + + // Perform decryption + byte[] dcrt = new byte[252]; + bool ct01_found = false; + for (int i = 0; i < 240; i+=16) { - case 1: - // Leave Original Xbox SS.bin unchanged - return true; + decryptor.TransformBlock(ss, 0x304 + i, 16, dcrt, i); + } + Array.Copy(ss, 0x304 + 240, dcrt, 240, 12); + // Rebuild challenge response table + Dictionary cids = []; + 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; + } + // Cannot fix SS with unknown challenge types + else + { + return false; + } + + // Map challenge ID to challenge type + cids.Add(dcrt[i + 1], i); + } + + // Determine challenge table offset + int ccrt_offset = 0; + if (xgdType == 2) + ccrt_offset = 0x200; + else if (xgdType == 3) + ccrt_offset = 0x20; + + // Repair challenge table (challenge entries 22 and 23 are zeroed) + int challenge_count = 0; + for (int i = 0; i < 21; i++) + { + // Offset into SS for the response type + int rOffset = 0x730 + i * 9; + + // Cannot rebuild SS with orphan challenge ID + if (!cids.TryGetValue(ss[rOffset + 1], out int cOffset)) + return false; + + // Validate challenge type with response type + bool angle_challenge = false; + bool other_challenge = false; + switch (dcrt[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] != 5) + 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] != dcrt[cOffset + 4]) + return false; + else + ss[ccrt_offset + i * 9] = dcrt[cOffset + 4]; + if (!write && ss[ccrt_offset + i * 9 + 1] != dcrt[cOffset + 5]) + return false; + else + ss[ccrt_offset + i * 9 + 1] = dcrt[cOffset + 5]; + if (!write && ss[ccrt_offset + i * 9 + 2] != dcrt[cOffset + 6]) + return false; + else + ss[ccrt_offset + i * 9 + 2] = dcrt[cOffset + 6]; + if (!write && ss[ccrt_offset + i * 9 + 3] != dcrt[cOffset + 7]) + return false; + else + ss[ccrt_offset + i * 9 + 3] = dcrt[cOffset + 7]; + + // Set challenge response for non-angle challenges + if (!angle_challenge) + { + if(!write && ss[ccrt_offset + i * 9 + 4] != dcrt[cOffset + 8]) + return false; + else + ss[ccrt_offset + i * 9 + 4] = dcrt[cOffset + 8]; + if(!write && ss[ccrt_offset + i * 9 + 5] != dcrt[cOffset + 9]) + return false; + else + ss[ccrt_offset + i * 9 + 5] = dcrt[cOffset + 9]; + if(!write && ss[ccrt_offset + i * 9 + 6] != dcrt[cOffset + 10]) + return false; + else + ss[ccrt_offset + i * 9 + 6] = dcrt[cOffset + 10]; + if(!write && ss[ccrt_offset + i * 9 + 7] != dcrt[cOffset + 11]) + return false; + else + ss[ccrt_offset + i * 9 + 7] = dcrt[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 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 - return true; + 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 @@ -1381,27 +1539,14 @@ public static bool CleanSS(byte[] ss) ss[102] = 15; // 0x0F ss[103] = 1; // 0x01 } - else - { - 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 - } - - return true; + break; default: // Unknown XGD type return false; } + + return true; } /// diff --git a/MPF.Processors/Redumper.cs b/MPF.Processors/Redumper.cs index 78d21f07b..25a30362c 100644 --- a/MPF.Processors/Redumper.cs +++ b/MPF.Processors/Redumper.cs @@ -290,7 +290,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)) @@ -324,12 +324,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}.fixed.ss")) + { + // Attempt to repair bad .ss file succeeded, hash it + string? ssCrc = HashTool.GetFileHash($"{basePath}.fixed.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..22885a2d7 100644 --- a/MPF.Processors/XboxBackupCreator.cs +++ b/MPF.Processors/XboxBackupCreator.cs @@ -105,33 +105,38 @@ 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; - - // Recreate RawSS.bin + // Ensure a raw SS is saved (recreate from log if needed) RecreateSS(logPath!, ssPath, Path.Combine(outputDirectory, "RawSS.bin")); - // Run ss_sector_range to get repeatable SS hash - ProcessingTool.CleanSS(ssPath, ssPath); - } + if (ProcessingTool.IsValidSS(ssPath)) + { + // Save security sector ranges + string? ranges = ProcessingTool.GetSSRanges(ssPath); + if (!string.IsNullOrEmpty(ranges)) + info.Extras.SecuritySectorRanges = ranges; + } - // 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; + // Repair and clean SS, only hash SS if successful + if (ProcessingTool.FixSS(ssPath, ssPath)) + { + string? ssCrc = HashTool.GetFileHash(ssPath, HashType.CRC32); + if (ssCrc is not null) + info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = ssCrc.ToUpperInvariant(); + } + } } /// @@ -507,18 +512,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; @@ -552,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: /* ----------------------------------------