diff --git a/MPF.Processors.Test/DiscImageCreatorTests.cs b/MPF.Processors.Test/DiscImageCreatorTests.cs
index abc766cc..46e1db68 100644
--- a/MPF.Processors.Test/DiscImageCreatorTests.cs
+++ b/MPF.Processors.Test/DiscImageCreatorTests.cs
@@ -117,7 +117,7 @@ namespace MPF.Processors.Test
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 @@ namespace MPF.Processors.Test
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 @@ namespace MPF.Processors.Test
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 f4bfe440..d7c12f2e 100644
--- a/MPF.Processors/DiscImageCreator.cs
+++ b/MPF.Processors/DiscImageCreator.cs
@@ -7,6 +7,7 @@ using System.Linq;
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 @@ namespace MPF.Processors
{
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 @@ namespace MPF.Processors
{
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 @@ namespace MPF.Processors
{
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 @@ namespace MPF.Processors
{
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 @@ namespace MPF.Processors
? 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 ed9b51a1..5bee8891 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 @@ namespace MPF.Processors
#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 @@ namespace MPF.Processors
// 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,79 +1143,71 @@ namespace MPF.Processors
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 (!GetXGDType(ss, out int xgdType))
+ return false;
+
+ // XGD1 can't be fixed
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 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 XGD2
+ int rtOffset = 0x204;
+ if (ss[rtOffset + 36] != 0x01)
+ return false;
+ if (ss[rtOffset + 37] != 0x00)
+ return false;
+ if (ss[rtOffset + 39] != 0x00)
+ return false;
+ if (ss[rtOffset + 40] != 0x00)
+ return false;
+ if (ss[rtOffset + 45] != 0x5B)
+ return false;
+ if (ss[rtOffset + 46] != 0x00)
+ return false;
+ if (ss[rtOffset + 48] != 0x00)
+ return false;
+ if (ss[rtOffset + 49] != 0x00)
+ return false;
+ if (ss[rtOffset + 54] != 0xB5)
+ return false;
+ if (ss[rtOffset + 55] != 0x00)
+ return false;
+ if (ss[rtOffset + 57] != 0x00)
+ return false;
+ if (ss[rtOffset + 58] != 0x00)
+ return false;
+ if (ss[rtOffset + 63] != 0x0F)
+ return false;
+ if (ss[rtOffset + 64] != 0x01)
+ return false;
+ if (ss[rtOffset + 66] != 0x00)
+ return false;
+ if (ss[rtOffset + 67] != 0x00)
+ return false;
+ }
+ else 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 +1243,20 @@ namespace MPF.Processors
}
else
{
- // Check for a cleaned SSv1
-
- int rtOffset = 0x204;
-
- if (ss[rtOffset + 36] != 0x01)
- return false;
- if (ss[rtOffset + 37] != 0x00)
- return false;
- if (xgdType == 2 && ss[rtOffset + 39] != 0x00)
- return false;
- if (xgdType == 2 && 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)
- return false;
- if (xgdType == 2 && 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)
- return false;
- if (xgdType == 2 && 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)
- return false;
- if (xgdType == 2 && ss[rtOffset + 67] != 0x00)
- return false;
+ 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 @@ namespace MPF.Processors
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 @@ namespace MPF.Processors
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 @@ namespace MPF.Processors
if (xgdType == 3 && !ssv2)
return false;
+ // 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)
+ {
+ 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 1:
- // Leave Original Xbox SS.bin unchanged
- 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
+ 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[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[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;
+ 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 @@ namespace MPF.Processors
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 78d21f07..25a30362 100644
--- a/MPF.Processors/Redumper.cs
+++ b/MPF.Processors/Redumper.cs
@@ -290,7 +290,7 @@ namespace MPF.Processors
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 @@ namespace MPF.Processors
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 3d721320..22885a2d 100644
--- a/MPF.Processors/XboxBackupCreator.cs
+++ b/MPF.Processors/XboxBackupCreator.cs
@@ -105,33 +105,38 @@ namespace MPF.Processors
}
#pragma warning restore IDE0010
- // Get the output file paths
+ // Hash DMI/PFI
string dmiPath = Path.Combine(outputDirectory, "DMI.bin");
- string pfiPath = Path.Combine(outputDirectory, "PFI.bin");
- string ssPath = Path.Combine(outputDirectory, "SS.bin");
-
- // Deal with 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
- RecreateSS(logPath!, ssPath, Path.Combine(outputDirectory, "RawSS.bin"));
-
- // Run ss_sector_range to get repeatable SS hash
- ProcessingTool.CleanSS(ssPath, ssPath);
- }
-
- // DMI/PFI/SS CRC32 hashes
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;
+
+ // Deal with SS.bin
+ string ssPath = Path.Combine(outputDirectory, "SS.bin");
if (File.Exists(ssPath))
- info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SSHash] = HashTool.GetFileHash(ssPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
+ {
+ // Ensure a raw SS is saved (recreate from log if needed)
+ 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;
+ }
+
+ // 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 @@ namespace MPF.Processors
}
///
- /// 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 @@ namespace MPF.Processors
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:
/*
----------------------------------------