diff --git a/BinaryObjectScanner/Data/StaticChecks.cs b/BinaryObjectScanner/Data/StaticChecks.cs
index 2368ef25..406119db 100644
--- a/BinaryObjectScanner/Data/StaticChecks.cs
+++ b/BinaryObjectScanner/Data/StaticChecks.cs
@@ -23,7 +23,7 @@ namespace BinaryObjectScanner.Data
}
///
- /// Cache for all IISOCheck types
+ /// Cache for all IDiskImageCheck types
///
public static IDiskImageCheck[] ISO9660CheckClasses
{
@@ -33,7 +33,7 @@ namespace BinaryObjectScanner.Data
return iso9660CheckClasses;
}
}
-
+
///
/// Cache for all IExecutableCheck types
///
@@ -103,12 +103,11 @@ namespace BinaryObjectScanner.Data
///
private static IContentCheck[]? contentCheckClasses;
-
///
/// Cache for all IISOCheck types
///
private static IDiskImageCheck[]? iso9660CheckClasses;
-
+
///
/// Cache for all IExecutableCheck types
///
@@ -169,7 +168,7 @@ namespace BinaryObjectScanner.Data
if (assemblyTypes.Length == 0)
return [];
- // Loop through all types
+ // Loop through all types
List classTypes = [];
foreach (Type? type in assemblyTypes)
{
diff --git a/BinaryObjectScanner/FileType/DiskImage.cs b/BinaryObjectScanner/FileType/DiskImage.cs
index 9f701b4a..a03b3988 100644
--- a/BinaryObjectScanner/FileType/DiskImage.cs
+++ b/BinaryObjectScanner/FileType/DiskImage.cs
@@ -1,6 +1,4 @@
-using System;
using System.Collections.Generic;
-using System.IO;
using BinaryObjectScanner.Data;
using BinaryObjectScanner.Interfaces;
using SabreTools.IO.Extensions;
@@ -20,13 +18,13 @@ namespace BinaryObjectScanner.FileType
#region Check Runners
///
- /// Handle a single file based on all ISO check implementations
+ /// Handle a single file based on all disk image check implementations
///
- /// Name of the source file of the ISO, for tracking
+ /// Name of the source file of the disk image, 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)
+ protected IDictionary RunDiskImageChecks(string file, U[] checks, bool includeDebug)
where U : IDiskImageCheck
{
// Create the output dictionary
diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs
index 1160da73..9e16bbf9 100644
--- a/BinaryObjectScanner/FileType/ISO9660.cs
+++ b/BinaryObjectScanner/FileType/ISO9660.cs
@@ -25,7 +25,7 @@ namespace BinaryObjectScanner.FileType
// Standard checks
var subProtections
- = RunISOChecks(file, StaticChecks.ISO9660CheckClasses, includeDebug);
+ = RunDiskImageChecks(file, StaticChecks.ISO9660CheckClasses, includeDebug);
protections.Append(file, subProtections.Values);
// If there are no protections
@@ -43,51 +43,51 @@ namespace BinaryObjectScanner.FileType
}
///
- /// Checks whether the sequence of bytes is pure data (as in, not empty, not text, just high-entropy data)
+ /// 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)
+ for (int i = 0; i < bytes.Length; ++i)
{
- if (bytes[i] == containedZeroes[index])
+ if (bytes[i] == 0x00)
{
- if (++index >= containedZeroes.Length)
- return false;
+ if (++index >= 3)
+ return false;
}
else
{
- index = 0;
+ 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 -'!,.]");
+ var 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.
///
+ /// TODO: can these 2 "noteworthy" functions be cached?
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
@@ -100,20 +100,21 @@ namespace BinaryObjectScanner.FileType
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;
-
+ if (potentialAppUseString == "CD-XA001")
+ return false;
+
return true;
}
-
+
///
/// Checks whether the Reserved 653 Bytes are "noteworthy" enough to be worth checking for protection.
///
@@ -123,8 +124,9 @@ namespace BinaryObjectScanner.FileType
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
index 6488a4ee..c0d0f532 100644
--- a/BinaryObjectScanner/Interfaces/IDiskImageCheck.cs
+++ b/BinaryObjectScanner/Interfaces/IDiskImageCheck.cs
@@ -11,9 +11,9 @@ namespace BinaryObjectScanner.Interfaces
/// Check a path for protections based on file contents
///
/// File to check for protection indicators
- ///
+ /// Disk image representing the read-in file
/// 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 df20d4ae..43c44047 100644
--- a/BinaryObjectScanner/Protection/AlphaROM.cs
+++ b/BinaryObjectScanner/Protection/AlphaROM.cs
@@ -10,7 +10,7 @@ namespace BinaryObjectScanner.Protection
///
/// Alpha-ROM is a form of copy protection created by SETTEC. It is known to make use of twin sectors as well as region locking.
/// Later forms of Alpha-ROM appear to be digital only, and it's currently unsure what forms of protection the digital only version includes, except that it does make use of region locking.
- /// It seems that Alpha-ROM was used in Visual Novels using certain game engines, most notably RealLive and Siglus (https://forums.fuwanovel.net/topic/20927-cannot-crack-siglus-engine-with-alpharom/).
+ /// It seems that Alpha-ROM was used in Visual Novels using certain game engines, most notably RealLive and Siglus (https://forums.fuwanovel.net/topic/20927-cannot-crack-siglus-engine-with-alpharom/).
/// Not every Siglus engine game uses Alpha-ROM (Source: https://sample9.dmm.co.jp/digital/pcgame/vsat_0263/vsat_0263t.zip {Official trial mirror}).
/// Not every RealLive engine game uses Alpha-ROM (Source: IA item "Kanon_Standard_Edition_Japan").
/// Alpha-ROM also seems to have made use of something called "Alpha-DPS" for non-executable data files (http://www.gonsuke.co.jp/protect.html).
@@ -43,12 +43,57 @@ namespace BinaryObjectScanner.Protection
// - SETTEC0000SETTEC1111
// - SOFTWARE\SETTEC
// TODO: Are there version numbers?
- public class AlphaROM : IExecutableCheck, IDiskImageCheck
+ public class AlphaROM : IDiskImageCheck, IExecutableCheck
{
+ ///
+ 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";
+ }
+
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
- // TODO: Add support for detecting Alpha-ROM found in older games made with the RealLive engine.
+ // TODO: Add support for detecting Alpha-ROM found in older games made with the RealLive engine.
// TODO: Add version detection for Alpha-ROM.
// Get the .data/DATA section strings, if they exist
@@ -88,51 +133,5 @@ namespace BinaryObjectScanner.Protection
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 ce0c6d43..d62e83da 100644
--- a/BinaryObjectScanner/Protection/CopyLok.cs
+++ b/BinaryObjectScanner/Protection/CopyLok.cs
@@ -11,19 +11,99 @@ namespace BinaryObjectScanner.Protection
/// More specifically, it may have been created a spin-off division known as "Panlok" (https://cdmediaworld.com/hardware/cdrom/cd_protections_copylok.shtml).
/// Though it may also be that "PanLok" was an alternate name for the program itself (https://gamecopyworld.com/games/pc_generic_copylok.shtml).
/// There was a PanLok website, but it's unknown what content it had hosted (https://web.archive.org/web/20041215075727/http://www.panlok.com/).
- ///
+ ///
/// CopyLok made use of bad sectors, using an average of 720-750 per disc, and appears to have been a form of ring protection (http://forum.redump.org/topic/29842/issues-dumping-pc-disc-with-code-lock-copy-protection/).
/// At least one disc with CopyLok appears to contain an excerpt of the poem "Jabberwocky" by Lewis Carroll in the raw sector data (http://forum.redump.org/post/54050/#p54050).
/// According to the Readme for poxylok (https://gf.wiretarget.com/copylok.htm), some version of Gangsters 2 may have this protection.
- ///
+ ///
/// Previous versions of BinaryObjectScanner incorrectly reported this DRM as "CodeLock / CodeLok / CopyLok". It was later discovered that due to the similar names, two entirely different DRM were erroneously lumped together.
/// "CodeLock" (in this case actually referring to "Code-Lock") is an entirely separate form of DRM, with the existing check now getting used separately.
/// Also not to be confused with https://en.wikipedia.org/wiki/Rob_Northen_copylock.
- ///
+ ///
/// COPYLOK trademark: https://www.trademarkelite.com/europe/trademark/trademark-detail/000618512/COPYLOK.
///
- public class CopyLok : IExecutableCheck, IDiskImageCheck
+ public class CopyLok : IDiskImageCheck, IExecutableCheck
{
+ ///
+ 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;
+
+ if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
+ return null;
+
+ if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
+ return null;
+
+ 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!";
+ }
+
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -36,91 +116,5 @@ namespace BinaryObjectScanner.Protection
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 c96ddbe6..1b7fc61d 100644
--- a/BinaryObjectScanner/Protection/LaserLok.cs
+++ b/BinaryObjectScanner/Protection/LaserLok.cs
@@ -11,8 +11,44 @@ using SabreTools.Serialization.Wrappers;
namespace BinaryObjectScanner.Protection
{
- public class LaserLok : IExecutableCheck, IPathCheck, IDiskImageCheck
+ public class LaserLok : IDiskImageCheck, IExecutableCheck, IPathCheck
{
+ ///
+ 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;
+
+ 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;
+
+ 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;
+ }
+
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -151,48 +187,6 @@ namespace BinaryObjectScanner.Protection
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 cea646ed..3d35ae25 100644
--- a/BinaryObjectScanner/Protection/Macrovision.cs
+++ b/BinaryObjectScanner/Protection/Macrovision.cs
@@ -13,12 +13,78 @@ namespace BinaryObjectScanner.Protection
///
/// Macrovision was a company that specialized in various forms of DRM. They had an extensive product line, their most infamous product (within this context) being SafeDisc.
/// Due to there being a significant amount of backend tech being shared between various protections, a separate class is needed for generic Macrovision detections.
- ///
+ ///
/// 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, IDiskImageCheck
+ public partial class Macrovision : IDiskImageCheck, IExecutableCheck, IExecutableCheck, IPathCheck
{
+ ///
+ 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;
+
+ 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;
+
+ 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
+
+ #region Read Reserved 653 Bytes
+
+ offset = 0;
+ // 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";
+ }
+
///
public string? CheckExecutable(string file, NewExecutable exe, bool includeDebug)
{
@@ -244,77 +310,6 @@ namespace BinaryObjectScanner.Protection
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)
{
@@ -349,7 +344,7 @@ namespace BinaryObjectScanner.Protection
// One filesize is known to overlap with both SafeDisc and CDS-300, and so is detected separately here.
return firstMatchedString.FileSize() switch
{
- // Found in Redump entries 37832 and 66005.
+ // Found in Redump entries 37832 and 66005.
20 => "SafeDisc 1.00.025-1.41.001",
// Found in Redump entries 30555 and 58573.
@@ -461,7 +456,7 @@ namespace BinaryObjectScanner.Protection
private static string GetSecDrvExecutableVersion(PortableExecutable exe)
{
// Different versions of this driver correspond to different SafeDisc versions.
- // TODO: Check if earlier versions of this driver contain the version string in a less obvious place.
+ // TODO: Check if earlier versions of this driver contain the version string in a less obvious place.
var version = exe.FileVersion;
if (!string.IsNullOrEmpty(version))
{
@@ -672,13 +667,13 @@ namespace BinaryObjectScanner.Protection
// Found in Redump entries 21154, 37982, and 108632.
or "1.20.000"
- // Found in Redump entries 17024/37920.
+ // Found in Redump entries 17024/37920.
or "1.20.001"
// Found in Redump entries 28708, 31526, 43321, 55080, and 75501.
or "1.30.010"
- // Found in Redump entries 9617 and 49552.
+ // Found in Redump entries 9617 and 49552.
or "1.35.000"
// Found in Redump entries 2595 and 30121.
diff --git a/BinaryObjectScanner/Protection/ProtectDISC.cs b/BinaryObjectScanner/Protection/ProtectDISC.cs
index a6a591cf..be46b1ec 100644
--- a/BinaryObjectScanner/Protection/ProtectDISC.cs
+++ b/BinaryObjectScanner/Protection/ProtectDISC.cs
@@ -13,8 +13,46 @@ 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, IDiskImageCheck
+ public class ProtectDISC : IDiskImageCheck, IExecutableCheck, IPathCheck
{
+ ///
+ 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+";
+ }
+
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -135,45 +173,6 @@ namespace BinaryObjectScanner.Protection
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 952d29f7..001beef2 100644
--- a/BinaryObjectScanner/Protection/SecuROM.cs
+++ b/BinaryObjectScanner/Protection/SecuROM.cs
@@ -13,7 +13,7 @@ namespace BinaryObjectScanner.Protection
{
// TODO: Investigate SecuROM for Macintosh
// TODO: Think of a way to detect dfe
- public class SecuROM : IExecutableCheck, IPathCheck, IDiskImageCheck
+ public class SecuROM : IDiskImageCheck, IExecutableCheck, IPathCheck
{
///
/// Matches hash of the Release Control-encrypted executable to known hashes
@@ -24,7 +24,7 @@ namespace BinaryObjectScanner.Protection
{"C6DFF6B08EE126893840E107FD4EC9F6", "Alice - Madness Returns (USA)+(Europe)"},
{"D7703D32B72185358D58448B235BD55E", "Arcania - Gothic 4 (Australia)"}, // International version (English, French, Italian, German, Spanish)
// Arcania - Gothic 4 - Polish(/Hungarian?) - known to most likely exist. Likely matches support site exe.
- {"83CD6225899C08422F860095962287A5", "Arcania - Gothic 4 - Russian (not in redump yet)"},
+ {"83CD6225899C08422F860095962287A5", "Arcania - Gothic 4 - Russian (not in redump yet)"},
// Arcania - Gothic 4 - Chinese - known to most likely exist. Likely matches support site exe.
{"FAF6DD75DDB335101CB77A714793DC28", "Batman - Arkham City - Game of the Year Edition (UK)"},
{"77999579EE4378BDFAC9438CC9CDB44E", "Batman - Arkham City (USA)+(Europe)"},
@@ -103,6 +103,141 @@ namespace BinaryObjectScanner.Protection
{48093043, "deadspace_f.aec"},
};
+ ///
+ 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;
+
+ // 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;
+
+ 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
+
+ #region Read Reserved 653 Bytes
+
+ offset = 0;
+
+ 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)
+ {
+ 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;
+ }
+
+ offset = 0;
+ 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+";
+ }
+
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -254,142 +389,6 @@ namespace BinaryObjectScanner.Protection
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
///
@@ -430,7 +429,7 @@ namespace BinaryObjectScanner.Protection
return null;
// Format the version
- string version = $"{addD.Version}.{addD.Build}";
+ string version = $"{addD.Version}.{addD.Build}";
if (!char.IsNumber(version[0]))
return "(very old, v3 or less)";
@@ -556,7 +555,7 @@ namespace BinaryObjectScanner.Protection
///
private static string? CheckMatroschkaPackage(SecuROMMatroschkaPackage package, bool includeDebug)
{
- // Check for all 0x00 required, as at least one known non-RC matroschka has the field, just empty.
+ // Check for all 0x00 required, as at least one known non-RC matroschka has the field, just empty.
if (package.KeyHexString == null || package.KeyHexString.Trim('\0').Length == 0)
return "SecuROM Matroschka Package";
@@ -626,11 +625,11 @@ namespace BinaryObjectScanner.Protection
// Fallback for PA if none of the above occur, in the case of companies that used their own modified PA
// variants. PiD refers to this as "SecuROM Modified PA Module".
- // Found in Redump entries 111997 (paul.dll) and 56373+56374 (AurParticleSystem.dll). The developers of
+ // Found in Redump entries 111997 (paul.dll) and 56373+56374 (AurParticleSystem.dll). The developers of
// both, Softstar and Aurogon respectively(?), seem to have some connection, and use similar-looking
- // modified PA. It probably has its own name like EA's GAM, but I don't currently know what that would be.
+ // modified PA. It probably has its own name like EA's GAM, but I don't currently know what that would be.
// Regardless, even if these are given their own named variant later, this check should remain in order to
- // catch other modified PA variants (this would have also caught EA GAM, for example) and to match PiD's
+ // catch other modified PA variants (this would have also caught EA GAM, for example) and to match PiD's
// detection abilities.
name = exe.ExportNameTable?.Strings?[0];
diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs
index 557581ae..1ec17df9 100644
--- a/BinaryObjectScanner/Protection/StarForce.cs
+++ b/BinaryObjectScanner/Protection/StarForce.cs
@@ -10,10 +10,91 @@ using SabreTools.Serialization.Wrappers;
namespace BinaryObjectScanner.Protection
{
- public class StarForce : IExecutableCheck, IPathCheck, IDiskImageCheck
+ public class StarForce : IDiskImageCheck, IExecutableCheck, IPathCheck
{
+ ///
+ /// 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},
+ };
+
+ ///
+ 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";
+ }
+
// TODO: Bring up to par with PiD.
- // Known issues:
+ // Known issues:
// "Game.exe" not detected (Redump entry 96137).
// "HR.exe" Themida not detected, doesn't detect "[Builder]" (Is that the default StarForce?) (Redump entry 94805).
// "ChromeEngine3.dll" and "SGP4.dll" not detected, doesn't detect "[FL Disc]" (Redump entry 93098).
@@ -58,9 +139,9 @@ namespace BinaryObjectScanner.Protection
name = exe.TradeName;
- // FrontLine ProActive (digital activation), samples:
- // https://dbox.tools/titles/pc/46450FA4/
- // https://dbox.tools/titles/pc/4F430FA0/
+ // FrontLine ProActive (digital activation), samples:
+ // https://dbox.tools/titles/pc/46450FA4/
+ // https://dbox.tools/titles/pc/4F430FA0/
// https://dbox.tools/titles/pc/53450FA1/
name = exe.TradeName;
if (name.OptionalContains("FL ProActive"))
@@ -162,85 +243,5 @@ namespace BinaryObjectScanner.Protection
// 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 9231d2f4..b6d1081e 100644
--- a/BinaryObjectScanner/Protection/Tages.cs
+++ b/BinaryObjectScanner/Protection/Tages.cs
@@ -10,8 +10,48 @@ using SabreTools.Serialization.Wrappers;
namespace BinaryObjectScanner.Protection
{
- public class TAGES : IExecutableCheck, IPathCheck, IDiskImageCheck
+ public class TAGES : IDiskImageCheck, IExecutableCheck, IPathCheck
{
+ ///
+ 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;
+
+ // 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;
+
+ 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;
+ }
+
///
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -213,51 +253,6 @@ namespace BinaryObjectScanner.Protection
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 02419c39..f72738ff 100644
--- a/BinaryObjectScanner/Scanner.cs
+++ b/BinaryObjectScanner/Scanner.cs
@@ -5,7 +5,6 @@ using BinaryObjectScanner.Data;
using BinaryObjectScanner.Interfaces;
using SabreTools.IO.Extensions;
using SabreTools.Serialization;
-using SabreTools.Serialization.Interfaces;
using SabreTools.Serialization.Wrappers;
namespace BinaryObjectScanner