mirror of
https://github.com/SabreTools/MPF.git
synced 2026-04-05 22:01:16 +00:00
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:
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
/*
|
||||
----------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user