diff --git a/BinaryObjectScanner.Test/BinaryObjectScanner.Test.csproj b/BinaryObjectScanner.Test/BinaryObjectScanner.Test.csproj
index b009ea13..f8de8ec1 100644
--- a/BinaryObjectScanner.Test/BinaryObjectScanner.Test.csproj
+++ b/BinaryObjectScanner.Test/BinaryObjectScanner.Test.csproj
@@ -16,10 +16,10 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
+
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/BinaryObjectScanner.Test/FileType/DiskImageTests.cs b/BinaryObjectScanner.Test/FileType/DiskImageTests.cs
new file mode 100644
index 00000000..8fa8b956
--- /dev/null
+++ b/BinaryObjectScanner.Test/FileType/DiskImageTests.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BinaryObjectScanner.Test/Protection/AlphaROMTests.cs b/BinaryObjectScanner.Test/Protection/AlphaROMTests.cs
index 09ebe17f..e43e04af 100644
--- a/BinaryObjectScanner.Test/Protection/AlphaROMTests.cs
+++ b/BinaryObjectScanner.Test/Protection/AlphaROMTests.cs
@@ -18,5 +18,18 @@ public void CheckPortableExecutableTest()
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);
+ }
}
}
\ No newline at end of file
diff --git a/BinaryObjectScanner.Test/Protection/CopyLokTests.cs b/BinaryObjectScanner.Test/Protection/CopyLokTests.cs
index 1fd1cd9b..a1ed7165 100644
--- a/BinaryObjectScanner.Test/Protection/CopyLokTests.cs
+++ b/BinaryObjectScanner.Test/Protection/CopyLokTests.cs
@@ -18,5 +18,18 @@ public void CheckPortableExecutableTest()
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);
+ }
}
}
\ No newline at end of file
diff --git a/BinaryObjectScanner.Test/Protection/LaserLokTests.cs b/BinaryObjectScanner.Test/Protection/LaserLokTests.cs
index 2f83e0fe..e324b595 100644
--- a/BinaryObjectScanner.Test/Protection/LaserLokTests.cs
+++ b/BinaryObjectScanner.Test/Protection/LaserLokTests.cs
@@ -40,5 +40,18 @@ public void CheckFilePathTest()
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);
+ }
}
}
\ No newline at end of file
diff --git a/BinaryObjectScanner.Test/Protection/MacrovisionTests.cs b/BinaryObjectScanner.Test/Protection/MacrovisionTests.cs
index 81f18d6d..5b9ab0ec 100644
--- a/BinaryObjectScanner.Test/Protection/MacrovisionTests.cs
+++ b/BinaryObjectScanner.Test/Protection/MacrovisionTests.cs
@@ -53,5 +53,18 @@ public void CheckFilePathTest()
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);
+ }
}
}
\ No newline at end of file
diff --git a/BinaryObjectScanner.Test/Protection/ProtectDiscTests.cs b/BinaryObjectScanner.Test/Protection/ProtectDiscTests.cs
index acc140eb..31c2a5b6 100644
--- a/BinaryObjectScanner.Test/Protection/ProtectDiscTests.cs
+++ b/BinaryObjectScanner.Test/Protection/ProtectDiscTests.cs
@@ -40,5 +40,18 @@ public void CheckFilePathTest()
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);
+ }
}
}
\ No newline at end of file
diff --git a/BinaryObjectScanner.Test/Protection/SecuROMTests.cs b/BinaryObjectScanner.Test/Protection/SecuROMTests.cs
index f2e3c716..834b4166 100644
--- a/BinaryObjectScanner.Test/Protection/SecuROMTests.cs
+++ b/BinaryObjectScanner.Test/Protection/SecuROMTests.cs
@@ -40,5 +40,18 @@ public void CheckFilePathTest()
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);
+ }
}
}
\ No newline at end of file
diff --git a/BinaryObjectScanner.Test/Protection/StarForceTests.cs b/BinaryObjectScanner.Test/Protection/StarForceTests.cs
index 3f0afa4d..518071ae 100644
--- a/BinaryObjectScanner.Test/Protection/StarForceTests.cs
+++ b/BinaryObjectScanner.Test/Protection/StarForceTests.cs
@@ -40,5 +40,18 @@ public void CheckFilePathTest()
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);
+ }
}
}
\ No newline at end of file
diff --git a/BinaryObjectScanner.Test/Protection/TAGESTests.cs b/BinaryObjectScanner.Test/Protection/TAGESTests.cs
index b5866503..49c004a5 100644
--- a/BinaryObjectScanner.Test/Protection/TAGESTests.cs
+++ b/BinaryObjectScanner.Test/Protection/TAGESTests.cs
@@ -40,5 +40,18 @@ public void CheckFilePathTest()
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);
+ }
}
}
\ No newline at end of file
diff --git a/BinaryObjectScanner/BinaryObjectScanner.csproj b/BinaryObjectScanner/BinaryObjectScanner.csproj
index ae28bd85..b37dd513 100644
--- a/BinaryObjectScanner/BinaryObjectScanner.csproj
+++ b/BinaryObjectScanner/BinaryObjectScanner.csproj
@@ -49,9 +49,9 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/BinaryObjectScanner/Data/StaticChecks.cs b/BinaryObjectScanner/Data/StaticChecks.cs
index 9b3e2f25..2368ef25 100644
--- a/BinaryObjectScanner/Data/StaticChecks.cs
+++ b/BinaryObjectScanner/Data/StaticChecks.cs
@@ -22,6 +22,18 @@ public static IContentCheck[] ContentCheckClasses
}
}
+ ///
+ /// Cache for all IISOCheck types
+ ///
+ public static IDiskImageCheck[] ISO9660CheckClasses
+ {
+ get
+ {
+ iso9660CheckClasses ??= InitCheckClasses>();
+ return iso9660CheckClasses;
+ }
+ }
+
///
/// Cache for all IExecutableCheck types
///
@@ -91,6 +103,12 @@ public static IExecutableCheck[] PortableExecutableCheckClas
///
private static IContentCheck[]? contentCheckClasses;
+
+ ///
+ /// Cache for all IISOCheck types
+ ///
+ private static IDiskImageCheck[]? iso9660CheckClasses;
+
///
/// Cache for all IExecutableCheck types
///
diff --git a/BinaryObjectScanner/FileType/DiskImage.cs b/BinaryObjectScanner/FileType/DiskImage.cs
new file mode 100644
index 00000000..9f701b4a
--- /dev/null
+++ b/BinaryObjectScanner/FileType/DiskImage.cs
@@ -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
+{
+ ///
+ /// Disk image file
+ ///
+ public abstract class DiskImage : DetectableBase
+ where T : WrapperBase
+ {
+ ///
+ public DiskImage(T wrapper) : base(wrapper) { }
+
+ #region Check Runners
+
+ ///
+ /// Handle a single file based on all ISO check implementations
+ ///
+ /// Name of the source file of the ISO, for tracking
+ /// Set of checks to use
+ /// True to include debug data, false otherwise
+ /// Set of protections in file, empty on error
+ protected IDictionary RunISOChecks(string file, U[] checks, bool includeDebug)
+ where U : IDiskImageCheck
+ {
+ // Create the output dictionary
+ var protections = new CheckDictionary();
+
+ // 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
+ }
+}
diff --git a/BinaryObjectScanner/FileType/Executable.cs b/BinaryObjectScanner/FileType/Executable.cs
index f549c75a..ab2acb96 100644
--- a/BinaryObjectScanner/FileType/Executable.cs
+++ b/BinaryObjectScanner/FileType/Executable.cs
@@ -76,7 +76,6 @@ protected IDictionary RunContentChecks(string? file, Stre
/// Name of the source file of the executable, for tracking
/// Executable to scan
/// Set of checks to use
- /// Scanner for handling recursive protections
/// True to include debug data, false otherwise
/// Set of protections in file, empty on error
protected IDictionary RunExecutableChecks(string file, T exe, U[] checks, bool includeDebug)
diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs
new file mode 100644
index 00000000..1160da73
--- /dev/null
+++ b/BinaryObjectScanner/FileType/ISO9660.cs
@@ -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
+{
+ ///
+ /// ISO9660
+ ///
+ public class ISO9660 : DiskImage
+ {
+ ///
+ public ISO9660(SabreTools.Serialization.Wrappers.ISO9660 wrapper) : base(wrapper) { }
+
+ ///
+ 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();
+ foreach (string key in protections.Keys)
+ {
+ protectionList.AddRange(protections[key]);
+ }
+
+ return string.Join(";", [.. protectionList]);
+ }
+
+ ///
+ /// Checks whether the sequence of bytes is pure data (as in, not empty, not text, just high-entropy data)
+ ///
+ 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?
+ ///
+ /// Checks whether the Application Use data is "noteworthy" enough to be worth checking for protection.
+ ///
+ 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;
+ }
+
+ ///
+ /// Checks whether the Reserved 653 Bytes are "noteworthy" enough to be worth checking for protection.
+ ///
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BinaryObjectScanner/Interfaces/IDiskImageCheck.cs b/BinaryObjectScanner/Interfaces/IDiskImageCheck.cs
new file mode 100644
index 00000000..6488a4ee
--- /dev/null
+++ b/BinaryObjectScanner/Interfaces/IDiskImageCheck.cs
@@ -0,0 +1,19 @@
+using SabreTools.Serialization.Wrappers;
+
+namespace BinaryObjectScanner.Interfaces
+{
+ ///
+ /// Check a disk image for protection
+ ///
+ public interface IDiskImageCheck where T : WrapperBase
+ {
+ ///
+ /// Check a path for protections based on file contents
+ ///
+ /// File to check for protection indicators
+ ///
+ /// True to include debug data, false otherwise
+ /// String containing any protections found in the file
+ string? CheckDiskImage(string file, T diskImage, bool includeDebug);
+ }
+}
\ No newline at end of file
diff --git a/BinaryObjectScanner/Protection/AlphaROM.cs b/BinaryObjectScanner/Protection/AlphaROM.cs
index deda8920..df20d4ae 100644
--- a/BinaryObjectScanner/Protection/AlphaROM.cs
+++ b/BinaryObjectScanner/Protection/AlphaROM.cs
@@ -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
+ public class AlphaROM : IExecutableCheck, IDiskImageCheck
{
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
@@ -84,5 +88,51 @@ public class AlphaROM : IExecutableCheck
return null;
}
+
+ ///
+ 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";
+ }
}
}
diff --git a/BinaryObjectScanner/Protection/CopyLok.cs b/BinaryObjectScanner/Protection/CopyLok.cs
index 972e9871..ce0c6d43 100644
--- a/BinaryObjectScanner/Protection/CopyLok.cs
+++ b/BinaryObjectScanner/Protection/CopyLok.cs
@@ -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.
///
- public class CopyLok : IExecutableCheck
+ public class CopyLok : IExecutableCheck, IDiskImageCheck
{
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
@@ -34,5 +36,91 @@ public class CopyLok : IExecutableCheck
return null;
}
+
+ ///
+ 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!";
+ }
}
}
diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs
index 1b5ea35e..c96ddbe6 100644
--- a/BinaryObjectScanner/Protection/LaserLok.cs
+++ b/BinaryObjectScanner/Protection/LaserLok.cs
@@ -3,6 +3,7 @@
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 @@
namespace BinaryObjectScanner.Protection
{
- public class LaserLok : IExecutableCheck, IPathCheck
+ public class LaserLok : IExecutableCheck, IPathCheck, IDiskImageCheck
{
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
@@ -150,6 +151,48 @@ public List CheckDirectoryPath(string path, List? files)
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
+ ///
+ 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)
diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs
index 175c34d6..cea646ed 100644
--- a/BinaryObjectScanner/Protection/Macrovision.cs
+++ b/BinaryObjectScanner/Protection/Macrovision.cs
@@ -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
///
- public partial class Macrovision : IExecutableCheck, IExecutableCheck, IPathCheck
+ public partial class Macrovision : IExecutableCheck, IExecutableCheck, IPathCheck, IDiskImageCheck
{
///
public string? CheckExecutable(string file, NewExecutable exe, bool includeDebug)
@@ -243,6 +244,77 @@ public List CheckDirectoryPath(string path, List? files)
return null;
}
+ ///
+ 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";
+ }
+
///
internal static List MacrovisionCheckDirectoryPath(string path, List? files)
{
diff --git a/BinaryObjectScanner/Protection/ProtectDISC.cs b/BinaryObjectScanner/Protection/ProtectDISC.cs
index 1f969a27..a6a591cf 100644
--- a/BinaryObjectScanner/Protection/ProtectDISC.cs
+++ b/BinaryObjectScanner/Protection/ProtectDISC.cs
@@ -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, IPathCheck
+ public class ProtectDISC : IExecutableCheck, IPathCheck, IDiskImageCheck
{
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
@@ -133,6 +135,45 @@ public List CheckDirectoryPath(string path, List? files)
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
+ ///
+ 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
diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs
index 685f7bad..952d29f7 100644
--- a/BinaryObjectScanner/Protection/SecuROM.cs
+++ b/BinaryObjectScanner/Protection/SecuROM.cs
@@ -2,6 +2,7 @@
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, IPathCheck
+ public class SecuROM : IExecutableCheck, IPathCheck, IDiskImageCheck
{
///
/// Matches hash of the Release Control-encrypted executable to known hashes
@@ -253,6 +254,142 @@ public List CheckDirectoryPath(string path, List? files)
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
+ ///
+ 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+";
+ }
+
///
/// Try to get the SecuROM v4 version from the overlay, if possible
///
diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs
index f7a88654..557581ae 100644
--- a/BinaryObjectScanner/Protection/StarForce.cs
+++ b/BinaryObjectScanner/Protection/StarForce.cs
@@ -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 @@
namespace BinaryObjectScanner.Protection
{
- public class StarForce : IExecutableCheck, IPathCheck
+ public class StarForce : IExecutableCheck, IPathCheck, IDiskImageCheck
{
// TODO: Bring up to par with PiD.
// Known issues:
@@ -160,5 +162,85 @@ public List CheckDirectoryPath(string path, List? files)
// TODO: Determine if there are any file name checks that aren't too generic to use on their own.
return null;
}
+
+ ///
+ 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";
+ }
+
+ ///
+ /// 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
+ ///
+ private static readonly Dictionary UnusualStarforceKeylessKeys = new()
+ {
+ {"FYFYILOVEYOU", 60270},
+ };
}
}
diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs
index 0ec99056..9231d2f4 100644
--- a/BinaryObjectScanner/Protection/Tages.cs
+++ b/BinaryObjectScanner/Protection/Tages.cs
@@ -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 @@
namespace BinaryObjectScanner.Protection
{
- public class TAGES : IExecutableCheck, IPathCheck
+ public class TAGES : IExecutableCheck, IPathCheck, IDiskImageCheck
{
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
@@ -212,6 +213,51 @@ public List CheckDirectoryPath(string path, List? files)
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
+ ///
+ 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
diff --git a/BinaryObjectScanner/Scanner.cs b/BinaryObjectScanner/Scanner.cs
index 137b7340..02419c39 100644
--- a/BinaryObjectScanner/Scanner.cs
+++ b/BinaryObjectScanner/Scanner.cs
@@ -478,6 +478,7 @@ private static List PerformPathCheck(IPathCheck impl, string? path, List
{
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);
diff --git a/ProtectionScan/ProtectionScan.csproj b/ProtectionScan/ProtectionScan.csproj
index c870b64d..3da7baee 100644
--- a/ProtectionScan/ProtectionScan.csproj
+++ b/ProtectionScan/ProtectionScan.csproj
@@ -33,7 +33,7 @@
-
+
\ No newline at end of file