mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-04-24 23:30:07 +00:00
Initial ISO Protection Detection (#389)
* Initial * Laserlock in * This is a better way to read the string * That array copy wasn't needed either * Use static filetype method, rename filetype.iso * Initial Codelok ISO scanning * Comments with redump IDs * Add redump examples to laserlock * Change for testing * Small comment * TAGES * halfway through safedisc * Safedisc done * Fix 1 * Major oversights in puredata fixed * Finish SecuROM * ProtectDiSC done * Alpharom done * Finish StarForce, initial PR review ready * Wait, that would be really bad * One more for the road. * Small finding * Small fix for finding * Notes on finding * Several minor fixes, decisions * what do you MEAN it returns true if there are no elements in the array * Future todo * Update packages * Rebase * Fix runisochecks * First round of fixes * Second round of fixes * Tests attempt 1 * Make checks work * Individual test attempt 1 * Final tests --------- Co-authored-by: Matt Nadareski <mnadareski@outlook.com>
This commit is contained in:
committed by
GitHub
parent
9f5b292035
commit
a281e8325e
34
BinaryObjectScanner.Test/FileType/DiskImageTests.cs
Normal file
34
BinaryObjectScanner.Test/FileType/DiskImageTests.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.IO;
|
||||
using BinaryObjectScanner.FileType;
|
||||
using Xunit;
|
||||
|
||||
namespace BinaryObjectScanner.Test.FileType
|
||||
{
|
||||
public class DiskImageTests
|
||||
{
|
||||
private static readonly SabreTools.Serialization.Wrappers.ISO9660 wrapper
|
||||
= new(new SabreTools.Data.Models.ISO9660.Volume(), new MemoryStream(new byte[1024]));
|
||||
|
||||
[Fact]
|
||||
public void DetectFile_EmptyString_Null()
|
||||
{
|
||||
string file = string.Empty;
|
||||
var detectable = new ISO9660(wrapper);
|
||||
|
||||
string? actual = detectable.Detect(file, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetectStream_EmptyStream_Empty()
|
||||
{
|
||||
Stream? stream = new MemoryStream();
|
||||
string file = string.Empty;
|
||||
var detectable = new ISO9660(wrapper);
|
||||
|
||||
string? actual = detectable.Detect(stream, file, includeDebug: false);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,5 +18,18 @@ namespace BinaryObjectScanner.Test.Protection
|
||||
string? actual = checker.CheckExecutable(file, exe, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckDiskImageTest()
|
||||
{
|
||||
string file = "filename";
|
||||
SabreTools.Data.Models.ISO9660.Volume model = new();
|
||||
Stream source = new MemoryStream(new byte[1024]);
|
||||
SabreTools.Serialization.Wrappers.ISO9660 iso = new(model, source);
|
||||
|
||||
var checker = new AlphaROM();
|
||||
string? actual = checker.CheckDiskImage(file, iso, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,5 +18,18 @@ namespace BinaryObjectScanner.Test.Protection
|
||||
string? actual = checker.CheckExecutable(file, exe, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckDiskImageTest()
|
||||
{
|
||||
string file = "filename";
|
||||
SabreTools.Data.Models.ISO9660.Volume model = new();
|
||||
Stream source = new MemoryStream(new byte[1024]);
|
||||
SabreTools.Serialization.Wrappers.ISO9660 iso = new(model, source);
|
||||
|
||||
var checker = new CopyLok();
|
||||
string? actual = checker.CheckDiskImage(file, iso, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,18 @@ namespace BinaryObjectScanner.Test.Protection
|
||||
string? actual = checker.CheckFilePath(path);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckDiskImageTest()
|
||||
{
|
||||
string file = "filename";
|
||||
SabreTools.Data.Models.ISO9660.Volume model = new();
|
||||
Stream source = new MemoryStream(new byte[1024]);
|
||||
SabreTools.Serialization.Wrappers.ISO9660 iso = new(model, source);
|
||||
|
||||
var checker = new LaserLok();
|
||||
string? actual = checker.CheckDiskImage(file, iso, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,5 +53,18 @@ namespace BinaryObjectScanner.Test.Protection
|
||||
string? actual = checker.CheckFilePath(path);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckDiskImageTest()
|
||||
{
|
||||
string file = "filename";
|
||||
SabreTools.Data.Models.ISO9660.Volume model = new();
|
||||
Stream source = new MemoryStream(new byte[1024]);
|
||||
SabreTools.Serialization.Wrappers.ISO9660 iso = new(model, source);
|
||||
|
||||
var checker = new Macrovision();
|
||||
string? actual = checker.CheckDiskImage(file, iso, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,18 @@ namespace BinaryObjectScanner.Test.Protection
|
||||
string? actual = checker.CheckFilePath(path);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckDiskImageTest()
|
||||
{
|
||||
string file = "filename";
|
||||
SabreTools.Data.Models.ISO9660.Volume model = new();
|
||||
Stream source = new MemoryStream(new byte[1024]);
|
||||
SabreTools.Serialization.Wrappers.ISO9660 iso = new(model, source);
|
||||
|
||||
var checker = new ProtectDISC();
|
||||
string? actual = checker.CheckDiskImage(file, iso, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,18 @@ namespace BinaryObjectScanner.Test.Protection
|
||||
string? actual = checker.CheckFilePath(path);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckDiskImageTest()
|
||||
{
|
||||
string file = "filename";
|
||||
SabreTools.Data.Models.ISO9660.Volume model = new();
|
||||
Stream source = new MemoryStream(new byte[1024]);
|
||||
SabreTools.Serialization.Wrappers.ISO9660 iso = new(model, source);
|
||||
|
||||
var checker = new SecuROM();
|
||||
string? actual = checker.CheckDiskImage(file, iso, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,18 @@ namespace BinaryObjectScanner.Test.Protection
|
||||
string? actual = checker.CheckFilePath(path);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckDiskImageTest()
|
||||
{
|
||||
string file = "filename";
|
||||
SabreTools.Data.Models.ISO9660.Volume model = new();
|
||||
Stream source = new MemoryStream(new byte[1024]);
|
||||
SabreTools.Serialization.Wrappers.ISO9660 iso = new(model, source);
|
||||
|
||||
var checker = new StarForce();
|
||||
string? actual = checker.CheckDiskImage(file, iso, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,18 @@ namespace BinaryObjectScanner.Test.Protection
|
||||
string? actual = checker.CheckFilePath(path);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckDiskImageTest()
|
||||
{
|
||||
string file = "filename";
|
||||
SabreTools.Data.Models.ISO9660.Volume model = new();
|
||||
Stream source = new MemoryStream(new byte[1024]);
|
||||
SabreTools.Serialization.Wrappers.ISO9660 iso = new(model, source);
|
||||
|
||||
var checker = new TAGES();
|
||||
string? actual = checker.CheckDiskImage(file, iso, includeDebug: false);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,18 @@ namespace BinaryObjectScanner.Data
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache for all IISOCheck<ISO> types
|
||||
/// </summary>
|
||||
public static IDiskImageCheck<ISO9660>[] ISO9660CheckClasses
|
||||
{
|
||||
get
|
||||
{
|
||||
iso9660CheckClasses ??= InitCheckClasses<IDiskImageCheck<ISO9660>>();
|
||||
return iso9660CheckClasses;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache for all IExecutableCheck<LinearExecutable> types
|
||||
/// </summary>
|
||||
@@ -91,6 +103,12 @@ namespace BinaryObjectScanner.Data
|
||||
/// </summary>
|
||||
private static IContentCheck[]? contentCheckClasses;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Cache for all IISOCheck<ISO9660> types
|
||||
/// </summary>
|
||||
private static IDiskImageCheck<ISO9660>[]? iso9660CheckClasses;
|
||||
|
||||
/// <summary>
|
||||
/// Cache for all IExecutableCheck<LinearExecutable> types
|
||||
/// </summary>
|
||||
|
||||
51
BinaryObjectScanner/FileType/DiskImage.cs
Normal file
51
BinaryObjectScanner/FileType/DiskImage.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using BinaryObjectScanner.Data;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace BinaryObjectScanner.FileType
|
||||
{
|
||||
/// <summary>
|
||||
/// Disk image file
|
||||
/// </summary>
|
||||
public abstract class DiskImage<T> : DetectableBase<T>
|
||||
where T : WrapperBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DiskImage(T wrapper) : base(wrapper) { }
|
||||
|
||||
#region Check Runners
|
||||
|
||||
/// <summary>
|
||||
/// Handle a single file based on all ISO check implementations
|
||||
/// </summary>
|
||||
/// <param name="file">Name of the source file of the ISO, for tracking</param>
|
||||
/// <param name="checks">Set of checks to use</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>Set of protections in file, empty on error</returns>
|
||||
protected IDictionary<U, string> RunISOChecks<U>(string file, U[] checks, bool includeDebug)
|
||||
where U : IDiskImageCheck<T>
|
||||
{
|
||||
// Create the output dictionary
|
||||
var protections = new CheckDictionary<U>();
|
||||
|
||||
// Iterate through all checks
|
||||
checks.IterateWithAction(checkClass =>
|
||||
{
|
||||
// Get the protection for the class, if possible
|
||||
var protection = checkClass.CheckDiskImage(file, _wrapper, includeDebug);
|
||||
if (string.IsNullOrEmpty(protection))
|
||||
return;
|
||||
|
||||
protections.Append(checkClass, protection);
|
||||
});
|
||||
|
||||
return protections;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
130
BinaryObjectScanner/FileType/ISO9660.cs
Normal file
130
BinaryObjectScanner/FileType/ISO9660.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using BinaryObjectScanner.Data;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace BinaryObjectScanner.FileType
|
||||
{
|
||||
/// <summary>
|
||||
/// ISO9660
|
||||
/// </summary>
|
||||
public class ISO9660 : DiskImage<SabreTools.Serialization.Wrappers.ISO9660>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ISO9660(SabreTools.Serialization.Wrappers.ISO9660 wrapper) : base(wrapper) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? Detect(Stream stream, string file, bool includeDebug)
|
||||
{
|
||||
// Create the output dictionary
|
||||
var protections = new ProtectionDictionary();
|
||||
|
||||
// Standard checks
|
||||
var subProtections
|
||||
= RunISOChecks(file, StaticChecks.ISO9660CheckClasses, includeDebug);
|
||||
protections.Append(file, subProtections.Values);
|
||||
|
||||
// If there are no protections
|
||||
if (protections.Count == 0)
|
||||
return null;
|
||||
|
||||
// Create the internal list
|
||||
var protectionList = new List<string>();
|
||||
foreach (string key in protections.Keys)
|
||||
{
|
||||
protectionList.AddRange(protections[key]);
|
||||
}
|
||||
|
||||
return string.Join(";", [.. protectionList]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the sequence of bytes is pure data (as in, not empty, not text, just high-entropy data)
|
||||
/// </summary>
|
||||
public static bool IsPureData(byte[] bytes)
|
||||
{
|
||||
// Check if there are three 0x00s in a row. Two seems like pushing it
|
||||
byte[] containedZeroes = {0x00, 0x00, 0x00};
|
||||
int index = 0;
|
||||
for (int i = 0; i < bytes.Length; ++i)
|
||||
{
|
||||
if (bytes[i] == containedZeroes[index])
|
||||
{
|
||||
if (++index >= containedZeroes.Length)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if there are strings in the data
|
||||
// TODO: is this too dangerous, or too faulty?
|
||||
// Currently-found worst cases:
|
||||
// "Y:1BY:1BC" in Redump ID 23339
|
||||
var strings = bytes.ReadStringsWithEncoding(charLimit: 7, Encoding.ASCII);
|
||||
Regex rgx = new Regex("[^a-zA-Z0-9 -'!,.]");
|
||||
foreach (string str in strings)
|
||||
{
|
||||
if (rgx.Replace(str, "").Length > 7)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: can these 2 "noteworthy" functions be cached?
|
||||
/// <summary>
|
||||
/// Checks whether the Application Use data is "noteworthy" enough to be worth checking for protection.
|
||||
/// </summary>
|
||||
public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd)
|
||||
{
|
||||
var applicationUse = pvd.ApplicationUse;
|
||||
if (Array.TrueForAll(applicationUse, b => b == 0x00))
|
||||
return false;
|
||||
|
||||
int offset = 0;
|
||||
string? potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset);
|
||||
if (potentialAppUseString != null && potentialAppUseString.Length > 0) // Some image authoring programs add a starting string to AU data
|
||||
{
|
||||
if (potentialAppUseString.StartsWith("ImgBurn"))
|
||||
return false;
|
||||
else if (potentialAppUseString.StartsWith("ULTRAISO"))
|
||||
return false;
|
||||
else if (potentialAppUseString.StartsWith("Rimage"))
|
||||
return false;
|
||||
else if (Array.TrueForAll(Encoding.ASCII.GetBytes(potentialAppUseString), b => b == 0x20))
|
||||
return false;
|
||||
// TODO: Unhandled "norb" mastering that puts stuff everywhere, inconsistently. See RID 103641
|
||||
// More things will have to go here as more disc authoring softwares are found that do this.
|
||||
// Redump ID 24478 has a bunch of 0x20 with norb in the middle, some discs have 0x20 that ends in a "/"
|
||||
// character. If these are found to be causing issues they can be added.
|
||||
}
|
||||
|
||||
offset = 141;
|
||||
potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset);
|
||||
if (potentialAppUseString == "CD-XA001")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the Reserved 653 Bytes are "noteworthy" enough to be worth checking for protection.
|
||||
/// </summary>
|
||||
public static bool NoteworthyReserved653Bytes(PrimaryVolumeDescriptor pvd)
|
||||
{
|
||||
var reserved653Bytes = pvd.Reserved653Bytes;
|
||||
var noteworthyReserved653Bytes = true;
|
||||
if (Array.TrueForAll(reserved653Bytes, b => b == 0x00))
|
||||
noteworthyReserved653Bytes = false;
|
||||
// Unsure if more will be needed
|
||||
return noteworthyReserved653Bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
BinaryObjectScanner/Interfaces/IDiskImageCheck.cs
Normal file
19
BinaryObjectScanner/Interfaces/IDiskImageCheck.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace BinaryObjectScanner.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Check a disk image for protection
|
||||
/// </summary>
|
||||
public interface IDiskImageCheck<T> where T : WrapperBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Check a path for protections based on file contents
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for protection indicators</param>
|
||||
/// <param name="diskImage"></param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>String containing any protections found in the file</returns>
|
||||
string? CheckDiskImage(string file, T diskImage, bool includeDebug);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace BinaryObjectScanner.Protection
|
||||
@@ -39,7 +43,7 @@ namespace BinaryObjectScanner.Protection
|
||||
// - SETTEC0000SETTEC1111
|
||||
// - SOFTWARE\SETTEC
|
||||
// TODO: Are there version numbers?
|
||||
public class AlphaROM : IExecutableCheck<PortableExecutable>
|
||||
public class AlphaROM : IExecutableCheck<PortableExecutable>, IDiskImageCheck<ISO9660>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
|
||||
@@ -84,5 +88,51 @@ namespace BinaryObjectScanner.Protection
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
// Checks can be made even easier once UDF support exists, as most (although not all, some early discs like
|
||||
// redump ID 124111 have no UDF partition) discs have "Settec" slathered over every field UDF lets them.
|
||||
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
// Alpharom disc check #1: disc has varying (but observed to at least always be larger than 14) length
|
||||
// string made up of numbers and capital letters.
|
||||
// TODO: triple-check that length is never below 14
|
||||
int offset = 0;
|
||||
var applicationIdentifierString = pvd.ApplicationIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim();
|
||||
if (applicationIdentifierString == null || applicationIdentifierString.Length < 14)
|
||||
return null;
|
||||
|
||||
if (!Regex.IsMatch(applicationIdentifierString, "^[A-Z0-9]*$"))
|
||||
return null;
|
||||
|
||||
// Alpharom disc check #2: disc has publisher identifier filled with varying amount of data (26-50 bytes
|
||||
// have been observed) followed by spaces. There's a decent chance this is just a Japanese text string, but
|
||||
// UTF, Shift-JIS, and EUC-JP all fail to display anything but garbage.
|
||||
|
||||
var publisherIdentifier = pvd.PublisherIdentifier;
|
||||
int firstSpace = Array.FindIndex(publisherIdentifier, b => b == 0x20);
|
||||
if (firstSpace <= 10 || firstSpace >= 120)
|
||||
return null;
|
||||
|
||||
var publisherData = new byte[firstSpace];
|
||||
var publisherSpaces = new byte[publisherData.Length - firstSpace];
|
||||
Array.Copy(publisherIdentifier, 0, publisherData, 0, firstSpace);
|
||||
Array.Copy(publisherIdentifier, firstSpace, publisherSpaces, 0, publisherData.Length - firstSpace);
|
||||
|
||||
if (!Array.TrueForAll(publisherSpaces, b => b == 0x20))
|
||||
return null;
|
||||
|
||||
if (!FileType.ISO9660.IsPureData(publisherData))
|
||||
return null;
|
||||
|
||||
return "AlphaROM";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace BinaryObjectScanner.Protection
|
||||
@@ -20,7 +22,7 @@ namespace BinaryObjectScanner.Protection
|
||||
///
|
||||
/// COPYLOK trademark: https://www.trademarkelite.com/europe/trademark/trademark-detail/000618512/COPYLOK.
|
||||
/// </summary>
|
||||
public class CopyLok : IExecutableCheck<PortableExecutable>
|
||||
public class CopyLok : IExecutableCheck<PortableExecutable>, IDiskImageCheck<ISO9660>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
|
||||
@@ -34,5 +36,91 @@ namespace BinaryObjectScanner.Protection
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
#region Initial Checks
|
||||
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
|
||||
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
|
||||
return null;
|
||||
|
||||
if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
|
||||
int offset = 0;
|
||||
|
||||
#region Read Application Use
|
||||
|
||||
var applicationUse = pvd.ApplicationUse;
|
||||
uint constantValueOne = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
ushort smallSizeBytes = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
ushort constantValueTwo = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
uint finalSectionOneBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
byte zeroByte = applicationUse.ReadByte(ref offset);
|
||||
ushort earlyCopyLokBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
ushort pairBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
uint oneValueBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
uint earlyCopyLokBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
uint pairBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
var endingZeroBytes = applicationUse.ReadBytes(ref offset, 483);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Main Checks
|
||||
|
||||
// Early return if the rest of the AU data isn't 0x00
|
||||
if (!Array.TrueForAll(endingZeroBytes, b => b == 0x00))
|
||||
return null;
|
||||
|
||||
// Check first currently-observed constant value
|
||||
if (constantValueOne != 0x4ED38AE1)
|
||||
return null;
|
||||
|
||||
// Check for early variant copylok
|
||||
if (earlyCopyLokBytesOne == 0x00)
|
||||
{
|
||||
// Redump ID 35908, 56433, 44526
|
||||
if (pairBytesOne == 0 && oneValueBytes == 0 && earlyCopyLokBytesTwo == 0 && pairBytesTwo == 0)
|
||||
return "CopyLok / CodeLok (Early, ~1850 errors)";
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
|
||||
// Check remaining currently-observed constant values
|
||||
if (constantValueTwo != 0x4ED3 || zeroByte != 0x00 || earlyCopyLokBytesOne != 0x0C76 || oneValueBytes != 0x00000001)
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
|
||||
// Always 0xD1AD, except in Redump ID 71985 (the only sample) where it's 0x6999
|
||||
// Update number be more accurate if more samples are acquired.
|
||||
if (smallSizeBytes < 0xADD1)
|
||||
return "CopyLok / CodeLok (Less errors, ~255)";
|
||||
|
||||
if (pairBytesOne == 0x9425)
|
||||
{
|
||||
// Redump ID 37860, 37881, 38239, 100685, 108375
|
||||
if (pairBytesTwo != 0x00000000)
|
||||
return "CopyLok / CodeLok (Pair errors, ~1500)";
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
|
||||
// Redump ID 31557, 44210, 49087, 72183, 31675
|
||||
if (pairBytesOne == 0xF3ED && pairBytesTwo == 0x00000000)
|
||||
return "CopyLok / CodeLok (Solo errors, ~775)";
|
||||
|
||||
#endregion
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Matching;
|
||||
@@ -10,7 +11,7 @@ using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace BinaryObjectScanner.Protection
|
||||
{
|
||||
public class LaserLok : IExecutableCheck<PortableExecutable>, IPathCheck
|
||||
public class LaserLok : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
|
||||
@@ -150,6 +151,48 @@ namespace BinaryObjectScanner.Protection
|
||||
return MatchUtil.GetFirstMatch(path, matchers, any: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
#region Initial Checks
|
||||
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
|
||||
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
|
||||
return null; //TODO: this might be too unsafe until more App Use strings are known
|
||||
|
||||
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
|
||||
var reserved653Bytes = pvd.Reserved653Bytes;
|
||||
int firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0);
|
||||
if (firstNonZero < 0)
|
||||
return null;
|
||||
|
||||
string? finalString = reserved653Bytes.ReadNullTerminatedAnsiString(ref firstNonZero);
|
||||
if (finalString == null)
|
||||
return null;
|
||||
|
||||
// Redump ID 113120
|
||||
if (finalString.StartsWith("MLSLaserlock"))
|
||||
return "LaserLock";
|
||||
|
||||
// Redump ID 38308, 113341
|
||||
if (finalString.StartsWith("LaserlockECL"))
|
||||
return "LaserLock Marathon";
|
||||
|
||||
// Some discs such as 128068, and also more normal ones, don't seem to have any identifying data.
|
||||
// TODO: list some normal ones
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetBuild(byte[]? sectionContent, bool versionTwo)
|
||||
{
|
||||
if (sectionContent == null)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Matching;
|
||||
@@ -16,7 +17,7 @@ namespace BinaryObjectScanner.Protection
|
||||
/// Macrovision Corporation CD-ROM Unauthorized Copying Study: https://web.archive.org/web/20011005161810/http://www.macrovision.com/solutions/software/cdrom/images/Games_CD-ROM_Study.PDF
|
||||
/// List of trademarks associated with Marovision: https://tmsearch.uspto.gov/bin/showfield?f=toc&state=4804%3Au8wykd.5.1&p_search=searchss&p_L=50&BackReference=&p_plural=yes&p_s_PARA1=&p_tagrepl%7E%3A=PARA1%24LD&expr=PARA1+AND+PARA2&p_s_PARA2=macrovision&p_tagrepl%7E%3A=PARA2%24ALL&p_op_ALL=AND&a_default=search&a_search=Submit+Query&a_search=Submit+Query
|
||||
/// </summary>
|
||||
public partial class Macrovision : IExecutableCheck<NewExecutable>, IExecutableCheck<PortableExecutable>, IPathCheck
|
||||
public partial class Macrovision : IExecutableCheck<NewExecutable>, IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string? CheckExecutable(string file, NewExecutable exe, bool includeDebug)
|
||||
@@ -243,6 +244,77 @@ namespace BinaryObjectScanner.Protection
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
#region Initial Checks
|
||||
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
|
||||
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
|
||||
return null;
|
||||
|
||||
// Early SafeDisc actually doesn't cross into reserved bytes. Regardless, SafeDisc CD is easy enough to
|
||||
// identify for obvious other reasons, so there's not much point in potentially running into false positives.
|
||||
|
||||
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
|
||||
var applicationUse = pvd.ApplicationUse;
|
||||
var reserved653Bytes = pvd.Reserved653Bytes;
|
||||
|
||||
#region Read Application Use
|
||||
|
||||
int offset = 0;
|
||||
var appUseZeroBytes = applicationUse.ReadBytes(ref offset, 256);
|
||||
var appUseDataBytesOne = applicationUse.ReadBytes(ref offset, 128);
|
||||
offset += 64; // Some extra values get added here over time. Check is good enough, easier to skip this.
|
||||
ushort appUseUshort = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
var appUseDataBytesTwo = applicationUse.ReadBytes(ref offset, 20);
|
||||
uint appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
var appUseDataBytesThree = applicationUse.ReadBytes(ref offset, 38);
|
||||
|
||||
#endregion
|
||||
|
||||
offset = 0;
|
||||
|
||||
#region Read Reserved 653 Bytes
|
||||
|
||||
// Somewhat arbitrary, but going further than 11 seems to exclude some discs.
|
||||
var reservedDataBytes = reserved653Bytes.ReadBytes(ref offset, 10);
|
||||
offset = 132; // TODO: Does it ever go further than this?
|
||||
var reservedZeroBytes = reserved653Bytes.ReadBytes(ref offset, 521);
|
||||
|
||||
#endregion
|
||||
|
||||
// The first 256 bytes of application use, and the last 521 bytes of reserved data, should all be 0x00.
|
||||
// It's possible reserved might need to be shortened a bit, but a need for that has not been observed yet.
|
||||
if (!Array.TrueForAll(appUseZeroBytes, b => b == 0x00) || !Array.TrueForAll(reservedZeroBytes, b => b == 0x00))
|
||||
return null;
|
||||
|
||||
// All of these sections should be pure data
|
||||
if (!FileType.ISO9660.IsPureData(appUseDataBytesOne)
|
||||
|| !FileType.ISO9660.IsPureData(appUseDataBytesTwo)
|
||||
|| !FileType.ISO9660.IsPureData(appUseDataBytesThree)
|
||||
|| !FileType.ISO9660.IsPureData(reservedDataBytes))
|
||||
return null;
|
||||
|
||||
// appUseFirstUint has only ever been observed as 0xBB, but no need to be this strict yet. Can be checked
|
||||
// if it's found that it's needed to, and always viable. appUseSecondUint varies more, but is still always
|
||||
// under 0xFF so far.
|
||||
if (appUseUshort > 0xFF || appUseUint > 0xFF)
|
||||
return null;
|
||||
|
||||
return "SafeDisc";
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IPathCheck.CheckDirectoryPath(string, List{string})"/>
|
||||
internal static List<string> MacrovisionCheckDirectoryPath(string path, List<string>? files)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Matching;
|
||||
@@ -11,7 +13,7 @@ namespace BinaryObjectScanner.Protection
|
||||
{
|
||||
// This protection was called VOB ProtectCD / ProtectDVD in versions prior to 6
|
||||
// ProtectDISC 9/10 checks for the presence of CSS on the disc to run, but don't encrypt any sectors or check for keys. Confirmed in Redump entries 78367 and 110095.
|
||||
public class ProtectDISC : IExecutableCheck<PortableExecutable>, IPathCheck
|
||||
public class ProtectDISC : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
|
||||
@@ -133,6 +135,45 @@ namespace BinaryObjectScanner.Protection
|
||||
return MatchUtil.GetFirstMatch(path, matchers, any: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
// If false positives occur on ProtectDiSC for some reason, there's a bit in the reserved bytes that
|
||||
// can be checked. Not bothering since this doesn't work for ProtectCD/DVD 6.x discs, which use otherwise
|
||||
// the same check anyways.
|
||||
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
int offset = 0;
|
||||
var copyrightString = pvd.CopyrightFileIdentifier.ReadNullTerminatedAnsiString(ref offset);
|
||||
if (copyrightString == null || copyrightString.Length < 19)
|
||||
return null;
|
||||
|
||||
copyrightString = copyrightString.Substring(0, 19); // Redump ID 15896 has a trailing space
|
||||
|
||||
// Stores some kind of serial in the copyright string, format 0000-XXXX-XXXX-XXXX where it can be numbers or
|
||||
// capital letters.
|
||||
|
||||
if (!Regex.IsMatch(copyrightString, "[0]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}"))
|
||||
return null;
|
||||
|
||||
offset = 0;
|
||||
|
||||
// Starting with sometime around 7.5, ProtectDiSC includes a version number string here. Examples include
|
||||
// 7.5.0.61324 and 9.0.1119. ProtectDiSC versioning is very confusing, so this is not the "actual" version
|
||||
// number and should not be printed.
|
||||
// Previous versions just have spaces here, so it doesn't need to be validated beyond that.
|
||||
var abstractIdentifierString = pvd.AbstractFileIdentifier.ReadNullTerminatedAnsiString(ref offset);
|
||||
if (abstractIdentifierString == null || abstractIdentifierString.Trim().Length == 0)
|
||||
return "ProtectDiSC 6-Early 7.x";
|
||||
|
||||
return "ProtectDiSC Mid-7.x+";
|
||||
}
|
||||
|
||||
private static string GetOldVersion(string matchedString)
|
||||
{
|
||||
// Remove unnecessary parts
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Matching;
|
||||
@@ -12,7 +13,7 @@ namespace BinaryObjectScanner.Protection
|
||||
{
|
||||
// TODO: Investigate SecuROM for Macintosh
|
||||
// TODO: Think of a way to detect dfe
|
||||
public class SecuROM : IExecutableCheck<PortableExecutable>, IPathCheck
|
||||
public class SecuROM : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
|
||||
{
|
||||
/// <summary>
|
||||
/// Matches hash of the Release Control-encrypted executable to known hashes
|
||||
@@ -253,6 +254,142 @@ namespace BinaryObjectScanner.Protection
|
||||
return MatchUtil.GetFirstMatch(path, matchers, any: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
#region Initial Checks
|
||||
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
// Application Use is too inconsistent to include or exclude
|
||||
|
||||
// There needs to be noteworthy data in the reserved 653 bytes
|
||||
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
|
||||
var applicationUse = pvd.ApplicationUse;
|
||||
var reserved653Bytes = pvd.Reserved653Bytes;
|
||||
|
||||
#region Read Application Use
|
||||
|
||||
var offset = 0;
|
||||
|
||||
// Either there's nothing of note, or it's empty other than a 4-byte value at the start.
|
||||
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
|
||||
{
|
||||
uint appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
var appUseZeroBytes = applicationUse.ReadBytes(ref offset, 508);
|
||||
|
||||
if (appUseUint == 0 || !Array.TrueForAll(appUseZeroBytes, b => b == 0x00))
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
offset = 0;
|
||||
|
||||
#region Read Reserved 653 Bytes
|
||||
|
||||
var reservedZeroBytesOne = reserved653Bytes.ReadBytes(ref offset, 489);
|
||||
uint reservedHundredValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
|
||||
var reserveDataBytesOne = reserved653Bytes.ReadBytes(ref offset, 80);
|
||||
var reservedZeroBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12);
|
||||
uint reservedUintOne = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
|
||||
uint reservedUintTwoLow = reserved653Bytes.ReadUInt32LittleEndian(ref offset); // Low value
|
||||
var reservedZeroBytesThree = reserved653Bytes.ReadBytes(ref offset, 4);
|
||||
uint reservedUintThree = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
|
||||
var reservedZeroBytesFour = reserved653Bytes.ReadBytes(ref offset, 12);
|
||||
uint reservedUintFour = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
|
||||
uint reservedOneValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
|
||||
var reservedZeroBytesFive = reserved653Bytes.ReadBytes(ref offset, 4);
|
||||
var reservedDataBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12);
|
||||
byte reservedLowByteValueOne = reserved653Bytes.ReadByteValue(ref offset);
|
||||
byte reservedLowByteValueTwo = reserved653Bytes.ReadByteValue(ref offset);
|
||||
byte reservedLowByteValueThree = reserved653Bytes.ReadByteValue(ref offset);
|
||||
byte reservedLowByteValueFour = reserved653Bytes.ReadByteValue(ref offset);
|
||||
var reservedDataBytesThree = reserved653Bytes.ReadBytes(ref offset, 12);
|
||||
|
||||
#endregion
|
||||
|
||||
// True for all discs
|
||||
if (!Array.TrueForAll(reservedZeroBytesOne, b => b == 0x00)
|
||||
|| !Array.TrueForAll(reservedZeroBytesTwo, b => b == 0x00)
|
||||
|| !Array.TrueForAll(reservedZeroBytesThree, b => b == 0x00)
|
||||
|| !Array.TrueForAll(reservedZeroBytesFour, b => b == 0x00)
|
||||
|| !Array.TrueForAll(reservedZeroBytesFive, b => b == 0x00))
|
||||
return null;
|
||||
|
||||
#region Early SecuROM Checks
|
||||
|
||||
// This duplicates a lot of code. This region is like this because it's still possible to detect early vers,
|
||||
// but it should be easy to remove this section if it turns out this leads to conflicts or false positives
|
||||
if (Array.TrueForAll(reserveDataBytesOne, b => b == 0x00)
|
||||
&& Array.TrueForAll(reservedDataBytesTwo, b => b == 0x00)
|
||||
&& reservedHundredValue == 0 && reservedOneValue == 0
|
||||
&& reservedUintOne == 0 && reservedUintTwoLow == 0 && reservedUintThree == 0 && reservedUintFour == 0
|
||||
&& reservedLowByteValueOne == 0 && reservedLowByteValueTwo == 0 && reservedLowByteValueThree == 0)
|
||||
{
|
||||
offset = 0;
|
||||
|
||||
if (FileType.ISO9660.IsPureData(reservedDataBytesThree))
|
||||
if ( reservedLowByteValueFour == 0)
|
||||
return "SecuROM 3.x-4.6x";
|
||||
else if (reservedLowByteValueFour < 0x20)
|
||||
return "SecuROM 4.7x-4.8x";
|
||||
else
|
||||
return null;
|
||||
|
||||
var earlyFirstFourBytes = reservedDataBytesThree.ReadBytes(ref offset, 4);
|
||||
var earlyLastEightBytes = reservedDataBytesThree.ReadBytes(ref offset, 8);
|
||||
|
||||
if (Array.TrueForAll(earlyFirstFourBytes, b => b == 0x00) && FileType.ISO9660.IsPureData(earlyLastEightBytes))
|
||||
return "SecuROM 2.x-3.x";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// If this uint32 is 100, the next 80 bytes should be data. Otherwise, both should only ever be zero.
|
||||
|
||||
switch(reservedHundredValue)
|
||||
{
|
||||
case 0:
|
||||
if (!Array.TrueForAll(reserveDataBytesOne, b => b == 0x00))
|
||||
return null;
|
||||
break;
|
||||
case 100:
|
||||
if (!FileType.ISO9660.IsPureData(reserveDataBytesOne))
|
||||
return null;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
//If you go back to early 4.0 CDs, only the above can be guaranteed to pass. CDs can already be identified via normal
|
||||
//dumping, though, and (as well as most later CDs) should always pass these remaining checks.
|
||||
if (reservedUintOne < 0xFFFF || reservedUintTwoLow > 0xFFFF || reservedUintThree < 0xFFFF || reservedUintFour < 0xFFFF)
|
||||
return null;
|
||||
|
||||
if (reservedOneValue != 1)
|
||||
return null;
|
||||
|
||||
if (reservedLowByteValueOne > 0x20 || reservedLowByteValueTwo > 0x20 || reservedLowByteValueThree > 0x20 ||
|
||||
reservedLowByteValueFour > 0x20)
|
||||
return null;
|
||||
|
||||
// TODO: RID 127715 fails this because the first 8 bytes of reservedDataBytesTwo happen to be "afsCafsC"
|
||||
if (!FileType.ISO9660.IsPureData(reservedDataBytesTwo) ||
|
||||
!FileType.ISO9660.IsPureData(reservedDataBytesThree))
|
||||
return null;
|
||||
|
||||
return "SecuROM 4.8x+";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the SecuROM v4 version from the overlay, if possible
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Matching;
|
||||
@@ -8,7 +10,7 @@ using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace BinaryObjectScanner.Protection
|
||||
{
|
||||
public class StarForce : IExecutableCheck<PortableExecutable>, IPathCheck
|
||||
public class StarForce : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
|
||||
{
|
||||
// TODO: Bring up to par with PiD.
|
||||
// Known issues:
|
||||
@@ -160,5 +162,85 @@ namespace BinaryObjectScanner.Protection
|
||||
// TODO: Determine if there are any file name checks that aren't too generic to use on their own.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
// Starforce Keyless check #1: the reserved 653 bytes start with a 32-bit LE number that's slightly less
|
||||
// than the length of the volume size space. The difference varies, it's usually around 10. Check 500 to be
|
||||
// safe. The rest of the data is all 0x00.
|
||||
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
|
||||
return null;
|
||||
|
||||
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
|
||||
return null;
|
||||
|
||||
int offset = 0;
|
||||
|
||||
var reserved653Bytes = pvd.Reserved653Bytes;
|
||||
uint initialValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
|
||||
var zeroBytes = reserved653Bytes.ReadBytes(ref offset, 649);
|
||||
|
||||
// It's unfortunately not known to be possible to detect many non-keyless StarForce discs, so some will slip
|
||||
// through here.
|
||||
if (initialValue > pvd.VolumeSpaceSize || initialValue + 500 < pvd.VolumeSpaceSize || !Array.TrueForAll(zeroBytes, b => b == 0x00))
|
||||
return null;
|
||||
|
||||
offset = 0;
|
||||
|
||||
// StarForce Keyless check #2: the key is stored in the Data Preparer identifier.
|
||||
|
||||
// It turns out that some (i.e. Redump ID 60266, 72531, 87181, 91734, 106732, 105356, 74578, 78200)
|
||||
// non-keyless StarForce discs still have this value here? This check may need to be disabled, but it
|
||||
// seems to avoid any false positives in practice so far.
|
||||
var dataPreparerIdentiferString = pvd.DataPreparerIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim();
|
||||
if (dataPreparerIdentiferString == null || dataPreparerIdentiferString.Length == 0)
|
||||
return "StarForce";
|
||||
|
||||
// It is returning the key, as it tells you what set of DPM your disc corresponds to, and it would also
|
||||
// help show why a disc might be an alt of another disc (there are at least a decent amount of StarForce
|
||||
// Keyless alts that would amtch otherwise). Unclear if this is desired by the users of BOS or those
|
||||
// affected by it.
|
||||
|
||||
// Thus far, the StarForce Keyless key is always made up of a number of characters, all either capital letters or
|
||||
// numbers, sometimes with dashes in between. Thus far, 4 formats have been observed:
|
||||
// XXXXXXXXXXXXXXXXXXXXXXXXX (25 characters)
|
||||
// XXXXX-XXXXX-XXXXX-XXXXX-XXXXX (25 characters, plus 4 dashes seperating 5 groups of 5)
|
||||
// XXXXXXXXXXXXXXXXXXXXXXXXXXXX (28 characters)
|
||||
// XXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX (28 characters, with 4 dashes)
|
||||
if (Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{25}$")
|
||||
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$")
|
||||
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{28}$")
|
||||
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{4}-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{6}$"))
|
||||
return $"StarForce Keyless - {dataPreparerIdentiferString}";
|
||||
|
||||
// Redump ID 60270 is a unique case, there could possibly be more.
|
||||
if (UnusualStarforceKeylessKeys.ContainsKey(dataPreparerIdentiferString))
|
||||
return $"StarForce Keyless - {dataPreparerIdentiferString}";
|
||||
|
||||
// In case any variants were missed.
|
||||
if (Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9-]*$"))
|
||||
return $"StarForce Keyless - {dataPreparerIdentiferString} - Unknown variant, please report to us on GitHub!";
|
||||
|
||||
// 34206 reaches this because it's not keyless, and has "WinISO software" as the DPI string. However, since
|
||||
// it has lowercase letters and spaces, it's caught here. It is genuinely StarForce, so it's not a false
|
||||
// positive.
|
||||
return $"StarForce";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a StarForce Keyless hash is known to not fit the format, but is in some way a one-off.
|
||||
/// Key is the SF Keyless Key, value is redump ID
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, uint> UnusualStarforceKeylessKeys = new()
|
||||
{
|
||||
{"FYFYILOVEYOU", 60270},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using BinaryObjectScanner.Interfaces;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Matching;
|
||||
@@ -9,7 +10,7 @@ using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace BinaryObjectScanner.Protection
|
||||
{
|
||||
public class TAGES : IExecutableCheck<PortableExecutable>, IPathCheck
|
||||
public class TAGES : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
|
||||
@@ -212,6 +213,51 @@ namespace BinaryObjectScanner.Protection
|
||||
return MatchUtil.GetFirstMatch(path, matchers, any: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
#region Initial Checks
|
||||
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
// There needs to be noteworthy application use data.
|
||||
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
|
||||
return null;
|
||||
|
||||
// There should not be noteworthy data in the reserved 653 bytes
|
||||
if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
|
||||
var applicationUse = pvd.ApplicationUse;
|
||||
|
||||
// Non-early tages either has all 512 bytes of the AU data full of data, or only the last 128.
|
||||
if (FileType.ISO9660.IsPureData(applicationUse))
|
||||
return "TAGES";
|
||||
|
||||
int offset = 0;
|
||||
var optionalZeroBytes = applicationUse.ReadBytes(ref offset, 384);
|
||||
var tagesBytes = applicationUse.ReadBytes(ref offset, 128);
|
||||
if (Array.TrueForAll(optionalZeroBytes, b => b == 0x00) && FileType.ISO9660.IsPureData(tagesBytes))
|
||||
return "TAGES";
|
||||
|
||||
// Early tages has a 4-byte value at the beginning of the AU data and nothing else.
|
||||
// Redump ID 8776, 21321, 35932
|
||||
offset = 0;
|
||||
uint earlyTagesBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
var zeroBytes = applicationUse.ReadBytes(ref offset, 508);
|
||||
if (Array.TrueForAll(zeroBytes, b => b == 0x00) && earlyTagesBytes != 0)
|
||||
return "TAGES (Early)";
|
||||
|
||||
// The original releases of Moto Racer 3 (31578, 34669) are so early they have seemingly nothing identifiable.
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetVersion(PortableExecutable exe)
|
||||
{
|
||||
// Check the internal versions
|
||||
|
||||
@@ -478,6 +478,7 @@ namespace BinaryObjectScanner
|
||||
{
|
||||
case AACSMediaKeyBlock obj: return new FileType.AACSMediaKeyBlock(obj);
|
||||
case BDPlusSVM obj: return new FileType.BDPlusSVM(obj);
|
||||
case ISO9660 obj: return new FileType.ISO9660(obj);
|
||||
case LDSCRYPT obj: return new FileType.LDSCRYPT(obj);
|
||||
case LinearExecutable obj: return new FileType.LinearExecutable(obj);
|
||||
case MSDOS obj: return new FileType.MSDOS(obj);
|
||||
|
||||
Reference in New Issue
Block a user