Repair incomplete SS files (#950)

* Cleanup SS handling

* Fix SS validity check

* Clean SS for DIC dumps too

* Fix build

* Account for net20

* Account for old dotnet

* Fix tests

* decrypt response table

* Update ProcessingTool.cs

* Repair CCRT

* fix

* fix build

* fix

* cid value is int

* code review

* fix

* fix

* Fix FixSS

* Copy don't move DIC SS

* Abracadabra
This commit is contained in:
Deterous
2026-03-10 22:41:14 +09:00
committed by GitHub
parent 716470e68d
commit da2be77b5c
5 changed files with 397 additions and 193 deletions

View File

@@ -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]

View File

@@ -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:

View File

@@ -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
/// <summary>
/// Get the XGD1 Master ID (XMID) information
/// Get the XGD1 Manufacturing ID (XMID) information
/// </summary>
/// <param name="dmi">DMI.bin file location</param>
/// <returns>String representation of the XGD1 DMI information, empty string on error</returns>
@@ -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;
}
/// <summary>
/// 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
/// </summary>
/// <param name="ssPath">Path to the SS file to check</param>
/// <returns>True if valid but partial SS.bin, false otherwise</returns>
public static bool IsValidPartialSS(string ssPath)
/// <param name="ss">Path to the SS to check</param>
/// <returns>True if SS is repaired and cleaned, false otherwise</returns>
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);
}
/// <summary>
/// Determine if a given SS is valid but contains zeroed challenge responses
/// Determine if a given SS has already been repaired and cleaned
/// </summary>
/// <param name="ss">Byte array of SS sector</param>
/// <returns>True if SS is a valid but partial SS, false otherwise</returns>
public static bool IsValidPartialSS(byte[] ss)
/// <returns>True if SS is repaired and cleaned, false otherwise</returns>
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;
}
/// <summary>
/// Determine if a given SS has already been cleaned
/// </summary>
/// <param name="ss">Byte array of SS sector</param>
/// <returns>True if SS is clean, false otherwise</returns>
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);
}
/// <summary>
/// Clean a rawSS.bin file and write it to a file
/// Repair and clean a rawSS.bin file and write it to a file
/// </summary>
/// <param name="rawSS">Path to the raw SS file to read from</param>
/// <param name="cleanSS">Path to the clean SS file to write to</param>
/// <param name="cleanSS">Path to the fixed SS file to write to</param>
/// <returns>True if successful, false otherwise</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="ss">Byte array of raw SS sector</param>
/// <returns>True if successful, false otherwise</returns>
public static bool CleanSS(byte[] ss)
/// <remarks>Also see ss_sector_range and abgx360</remarks>
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<byte, int> 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;
}
/// <summary>

View File

@@ -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))

View File

@@ -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();
}
}
}
/// <inheritdoc/>
@@ -507,18 +512,18 @@ namespace MPF.Processors
}
/// <summary>
/// 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
/// </summary>
/// <param name="log">Path to XBC log</param>
/// <param name="cleanSS">Path to the clean SS file to read from</param>
/// <param name="rawSS">Path to the raw SS file to write to</param>
/// <param name="currentSS">Path to the provided SS file to read from</param>
/// <param name="rawSS">Path to the recreated SS file to write to</param>
/// <returns>True if successful, false otherwise</returns>
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:
/*
----------------------------------------