From 0ca0a8457e17e66227ed2dcd1d5eb2c6640f6995 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 30 Oct 2025 15:46:55 -0400 Subject: [PATCH 01/36] Initial --- BinaryObjectScanner/Data/StaticChecks.cs | 18 ++++ BinaryObjectScanner/FileType/Executable.cs | 1 - BinaryObjectScanner/FileType/ISO.cs | 103 ++++++++++++++++++++ BinaryObjectScanner/FileType/ISO9660.cs | 47 +++++++++ BinaryObjectScanner/Interfaces/IISOCheck.cs | 19 ++++ 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 BinaryObjectScanner/FileType/ISO.cs create mode 100644 BinaryObjectScanner/FileType/ISO9660.cs create mode 100644 BinaryObjectScanner/Interfaces/IISOCheck.cs diff --git a/BinaryObjectScanner/Data/StaticChecks.cs b/BinaryObjectScanner/Data/StaticChecks.cs index 9b3e2f25..a6b02d1c 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 IISOCheck[] 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 IISOCheck[]? iso9660CheckClasses; + /// /// Cache for all IExecutableCheck types /// 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/ISO.cs b/BinaryObjectScanner/FileType/ISO.cs new file mode 100644 index 00000000..764289a7 --- /dev/null +++ b/BinaryObjectScanner/FileType/ISO.cs @@ -0,0 +1,103 @@ +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 +{ + /// + /// .iso file + /// + public abstract class ISO : DetectableBase + where T : WrapperBase + { + /// + public ISO(T wrapper) : base(wrapper) { } + + #region Check Runners + + /// + /// Handle a single file based on all content check implementations + /// + /// Name of the source file of the stream, for tracking + /// Stream to scan the contents of + /// True to include debug data, false otherwise + /// Set of protections in file, empty on error + protected IDictionary RunContentChecks(string? file, Stream stream, bool includeDebug) + { + // Create the output dictionary + var protections = new CheckDictionary(); + + // If we have an invalid file + if (string.IsNullOrEmpty(file)) + return protections; + else if (!File.Exists(file)) + return protections; + + // Read the file contents + byte[] fileContent = []; + try + { + // If the stream isn't seekable + if (!stream.CanSeek) + return protections; + + stream.Seek(0, SeekOrigin.Begin); + fileContent = stream.ReadBytes((int)stream.Length); + if (fileContent == null) + return protections; + } + catch (Exception ex) + { + if (includeDebug) Console.Error.WriteLine(ex); + return protections; + } + + // Iterate through all checks + StaticChecks.ContentCheckClasses.IterateWithAction(checkClass => + { + // Get the protection for the class, if possible + var protection = checkClass.CheckContents(file!, fileContent, includeDebug); + if (string.IsNullOrEmpty(protection)) + return; + + protections.Append(checkClass, protection); + }); + + return protections; + } + + /// + /// Handle a single file based on all ISO check implementations + /// + /// Name of the source file of the ISO, for tracking + /// ISO to scan + /// 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, T iso, U[] checks, bool includeDebug) + where U : IISOCheck + { + // 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.CheckISO(file, iso, includeDebug); + if (string.IsNullOrEmpty(protection)) + return; + + protections.Append(checkClass, protection); + }); + + return protections; + } + + #endregion + } +} diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs new file mode 100644 index 00000000..0eed96bd --- /dev/null +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.IO; +using BinaryObjectScanner.Data; + +namespace BinaryObjectScanner.FileType +{ + /// + /// ISO9660 + /// + public class ISO9660 : ISO + { + /// + 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(); + + // Only use generic content checks if we're in debug mode + if (includeDebug) + { + var contentProtections = RunContentChecks(file, stream, includeDebug); + protections.Append(file, contentProtections.Values); + } + + // Standard checks + var subProtections + = RunISOChecks(file, _wrapper, 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]); + } + } +} \ No newline at end of file diff --git a/BinaryObjectScanner/Interfaces/IISOCheck.cs b/BinaryObjectScanner/Interfaces/IISOCheck.cs new file mode 100644 index 00000000..0db7f334 --- /dev/null +++ b/BinaryObjectScanner/Interfaces/IISOCheck.cs @@ -0,0 +1,19 @@ +using SabreTools.Serialization.Wrappers; + +namespace BinaryObjectScanner.Interfaces +{ + /// + /// Check an ISO for protection + /// + public interface IISOCheck where T : WrapperBase + { + /// + /// Check a path for protections based on file contents + /// + /// File to check for protection indicators + /// ISO representing the read-in file + /// True to include debug data, false otherwise + /// String containing any protections found in the file + string? CheckISO(string file, T iso, bool includeDebug); + } +} \ No newline at end of file From ca9f667f5fc18fd148ba212ebd16aefc40570ba8 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 30 Oct 2025 17:17:09 -0400 Subject: [PATCH 02/36] Laserlock in --- BinaryObjectScanner/FileType/ISO.cs | 53 +--------------------- BinaryObjectScanner/FileType/ISO9660.cs | 7 --- BinaryObjectScanner/Protection/LaserLok.cs | 53 +++++++++++++++++++++- BinaryObjectScanner/Scanner.cs | 1 + 4 files changed, 54 insertions(+), 60 deletions(-) diff --git a/BinaryObjectScanner/FileType/ISO.cs b/BinaryObjectScanner/FileType/ISO.cs index 764289a7..8768f000 100644 --- a/BinaryObjectScanner/FileType/ISO.cs +++ b/BinaryObjectScanner/FileType/ISO.cs @@ -18,58 +18,7 @@ public abstract class ISO : DetectableBase public ISO(T wrapper) : base(wrapper) { } #region Check Runners - - /// - /// Handle a single file based on all content check implementations - /// - /// Name of the source file of the stream, for tracking - /// Stream to scan the contents of - /// True to include debug data, false otherwise - /// Set of protections in file, empty on error - protected IDictionary RunContentChecks(string? file, Stream stream, bool includeDebug) - { - // Create the output dictionary - var protections = new CheckDictionary(); - - // If we have an invalid file - if (string.IsNullOrEmpty(file)) - return protections; - else if (!File.Exists(file)) - return protections; - - // Read the file contents - byte[] fileContent = []; - try - { - // If the stream isn't seekable - if (!stream.CanSeek) - return protections; - - stream.Seek(0, SeekOrigin.Begin); - fileContent = stream.ReadBytes((int)stream.Length); - if (fileContent == null) - return protections; - } - catch (Exception ex) - { - if (includeDebug) Console.Error.WriteLine(ex); - return protections; - } - - // Iterate through all checks - StaticChecks.ContentCheckClasses.IterateWithAction(checkClass => - { - // Get the protection for the class, if possible - var protection = checkClass.CheckContents(file!, fileContent, includeDebug); - if (string.IsNullOrEmpty(protection)) - return; - - protections.Append(checkClass, protection); - }); - - return protections; - } - + /// /// Handle a single file based on all ISO check implementations /// diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index 0eed96bd..c719d09c 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -18,13 +18,6 @@ public ISO9660(SabreTools.Serialization.Wrappers.ISO9660 wrapper) : base(wrapper // Create the output dictionary var protections = new ProtectionDictionary(); - // Only use generic content checks if we're in debug mode - if (includeDebug) - { - var contentProtections = RunContentChecks(file, stream, includeDebug); - protections.Append(file, contentProtections.Values); - } - // Standard checks var subProtections = RunISOChecks(file, _wrapper, StaticChecks.ISO9660CheckClasses, includeDebug); diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index 1b5ea35e..f41a5be2 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, IISOCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -149,6 +150,56 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } + + public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + { + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + int offset = 0; + + // Goes into any check that checks the application use data + #region AppUseCheck + var applicationUse = pvd.ApplicationUse; + var noteworthyApplicationUse = true; + if (Array.TrueForAll(applicationUse, b => b == 0x00)) + noteworthyApplicationUse = false; + string? potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset); + if (potentialAppUseString != null) + { + if (potentialAppUseString.StartsWith("ImgBurn")) + noteworthyApplicationUse = false; + else if (potentialAppUseString.StartsWith("ULTRAISO")) + noteworthyApplicationUse = false; + // More things will have to go here as more disc authoring softwares are found that do this. + } + #endregion + + // Goes into any check that checks the reserved 653 bytes + #region Reserved653BytesCheck + var reserved653Bytes = pvd.Reserved653Bytes; + var noteworthyReserved653Bytes = true; + if (Array.TrueForAll(reserved653Bytes, b => b == 0x00)) + noteworthyReserved653Bytes = false; + // Unsure if more will be needed + #endregion + + if (noteworthyApplicationUse) + return "None"; //TODO: this might be too unsafe until more App Use strings are known + + if (!noteworthyReserved653Bytes) + return "None"; + + var firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0); + byte[] finalStringBytes = new byte[reserved653Bytes.Length - firstNonZero]; + Array.Copy(reserved653Bytes, firstNonZero, finalStringBytes, 0, finalStringBytes.Length); + string finalString = Encoding.ASCII.GetString(finalStringBytes); //TODO: handle if this data isn't a string + if (finalString.StartsWith("MLSLaserlock")) + return "LaserLock"; + + if (finalString.StartsWith("LaserlockECL")) + return "LaserLock Marathon"; + + return "None"; + } private static string GetBuild(byte[]? sectionContent, bool versionTwo) { 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); From ec76f82a8f58d53b1eab89d379f3327342a03f31 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 30 Oct 2025 19:50:51 -0400 Subject: [PATCH 03/36] This is a better way to read the string --- BinaryObjectScanner/Protection/LaserLok.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index f41a5be2..bbf2d563 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -191,7 +191,10 @@ public List CheckDirectoryPath(string path, List? files) var firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0); byte[] finalStringBytes = new byte[reserved653Bytes.Length - firstNonZero]; Array.Copy(reserved653Bytes, firstNonZero, finalStringBytes, 0, finalStringBytes.Length); - string finalString = Encoding.ASCII.GetString(finalStringBytes); //TODO: handle if this data isn't a string + string? finalString = finalStringBytes.ReadNullTerminatedAnsiString(ref offset); + if (finalString == null) + return null; + if (finalString.StartsWith("MLSLaserlock")) return "LaserLock"; From f1374a7b97f4f6f1f7904b5149dfe220d3a821a9 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 30 Oct 2025 19:52:25 -0400 Subject: [PATCH 04/36] That array copy wasn't needed either --- BinaryObjectScanner/Protection/LaserLok.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index bbf2d563..df60b000 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -189,9 +189,7 @@ public List CheckDirectoryPath(string path, List? files) return "None"; var firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0); - byte[] finalStringBytes = new byte[reserved653Bytes.Length - firstNonZero]; - Array.Copy(reserved653Bytes, firstNonZero, finalStringBytes, 0, finalStringBytes.Length); - string? finalString = finalStringBytes.ReadNullTerminatedAnsiString(ref offset); + string? finalString = reserved653Bytes.ReadNullTerminatedAnsiString(ref firstNonZero); if (finalString == null) return null; From 48718993f7907a489b7cf8ccfcef64b904bf65d3 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 30 Oct 2025 22:26:38 -0400 Subject: [PATCH 05/36] Use static filetype method, rename filetype.iso --- .../FileType/{ISO.cs => DiskImage.cs} | 4 +-- BinaryObjectScanner/FileType/ISO9660.cs | 36 ++++++++++++++++++- BinaryObjectScanner/Protection/LaserLok.cs | 34 +++--------------- 3 files changed, 41 insertions(+), 33 deletions(-) rename BinaryObjectScanner/FileType/{ISO.cs => DiskImage.cs} (93%) diff --git a/BinaryObjectScanner/FileType/ISO.cs b/BinaryObjectScanner/FileType/DiskImage.cs similarity index 93% rename from BinaryObjectScanner/FileType/ISO.cs rename to BinaryObjectScanner/FileType/DiskImage.cs index 8768f000..459042f0 100644 --- a/BinaryObjectScanner/FileType/ISO.cs +++ b/BinaryObjectScanner/FileType/DiskImage.cs @@ -11,11 +11,11 @@ namespace BinaryObjectScanner.FileType /// /// .iso file /// - public abstract class ISO : DetectableBase + public abstract class DiskImage : DetectableBase where T : WrapperBase { /// - public ISO(T wrapper) : base(wrapper) { } + public DiskImage(T wrapper) : base(wrapper) { } #region Check Runners diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index c719d09c..a7b67293 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -1,13 +1,16 @@ +using System; using System.Collections.Generic; using System.IO; using BinaryObjectScanner.Data; +using SabreTools.Data.Models.ISO9660; +using SabreTools.IO.Extensions; namespace BinaryObjectScanner.FileType { /// /// ISO9660 /// - public class ISO9660 : ISO + public class ISO9660 : DiskImage { /// public ISO9660(SabreTools.Serialization.Wrappers.ISO9660 wrapper) : base(wrapper) { } @@ -36,5 +39,36 @@ var subProtections return string.Join(";", [.. protectionList]); } + + // Checks whether the Application Use data is "noteworthy" enough to be worth checking for protection. + public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) + { + int offset = 0; + var applicationUse = pvd.ApplicationUse; + var noteworthyApplicationUse = true; + if (Array.TrueForAll(applicationUse, b => b == 0x00)) + noteworthyApplicationUse = false; + string? potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset); + if (potentialAppUseString != null) // Some image authoring programs add a starting string to AU data + { + if (potentialAppUseString.StartsWith("ImgBurn")) + noteworthyApplicationUse = false; + else if (potentialAppUseString.StartsWith("ULTRAISO")) + noteworthyApplicationUse = false; + // More things will have to go here as more disc authoring softwares are found that do this. + } + return noteworthyApplicationUse; + } + + // 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/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index df60b000..c4af74f7 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -154,40 +154,14 @@ public List CheckDirectoryPath(string path, List? files) public string? CheckISO(string file, ISO9660 iso, bool includeDebug) { var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; - int offset = 0; - // Goes into any check that checks the application use data - #region AppUseCheck - var applicationUse = pvd.ApplicationUse; - var noteworthyApplicationUse = true; - if (Array.TrueForAll(applicationUse, b => b == 0x00)) - noteworthyApplicationUse = false; - string? potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset); - if (potentialAppUseString != null) - { - if (potentialAppUseString.StartsWith("ImgBurn")) - noteworthyApplicationUse = false; - else if (potentialAppUseString.StartsWith("ULTRAISO")) - noteworthyApplicationUse = false; - // More things will have to go here as more disc authoring softwares are found that do this. - } - #endregion - - // Goes into any check that checks the reserved 653 bytes - #region Reserved653BytesCheck - var reserved653Bytes = pvd.Reserved653Bytes; - var noteworthyReserved653Bytes = true; - if (Array.TrueForAll(reserved653Bytes, b => b == 0x00)) - noteworthyReserved653Bytes = false; - // Unsure if more will be needed - #endregion - - if (noteworthyApplicationUse) + if (FileType.ISO9660.NoteworthyApplicationUse(pvd)) return "None"; //TODO: this might be too unsafe until more App Use strings are known - - if (!noteworthyReserved653Bytes) + + if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) return "None"; + var reserved653Bytes = pvd.Reserved653Bytes; var firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0); string? finalString = reserved653Bytes.ReadNullTerminatedAnsiString(ref firstNonZero); if (finalString == null) From 495e22071d98d786a9ae0180d13dd606b41d3a0c Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 01:21:52 -0400 Subject: [PATCH 06/36] Initial Codelok ISO scanning --- BinaryObjectScanner/Protection/CopyLok.cs | 69 ++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/BinaryObjectScanner/Protection/CopyLok.cs b/BinaryObjectScanner/Protection/CopyLok.cs index 972e9871..292dac33 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, IISOCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -34,5 +36,70 @@ public class CopyLok : IExecutableCheck return null; } + + public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + { + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + + if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) + return "None"; + + if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) + return "None"; + + int offset = 0; + + var applicationUse = pvd.ApplicationUse; + var constantValueOne = applicationUse.ReadUInt32LittleEndian(ref offset); + var smallSizeBytes = applicationUse.ReadUInt16LittleEndian(ref offset); + var constantValueTwo = applicationUse.ReadUInt16LittleEndian(ref offset); + var finalSectionOneBytes = applicationUse.ReadUInt32LittleEndian(ref offset); + var zeroByte = applicationUse.ReadByte(ref offset); + var earlyCopyLokBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset); + var pairBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset); + var oneValueBytes = applicationUse.ReadUInt32LittleEndian(ref offset); + var earlyCopyLokBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset); + var pairBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset); + var endingZeroBytes = applicationUse.ReadBytes(ref offset, 483); + + // 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) + { + if (0 == pairBytesOne && 0 == oneValueBytes && 0 == earlyCopyLokBytesTwo && 0 == pairBytesTwo) + 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 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) + { + if (pairBytesTwo != 0x00000000) + return "CopyLok / CodeLok (Pair errors, ~1500)"; + + return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!"; + } + + if (pairBytesOne == 0xF3ED && pairBytesTwo == 0x00000000) + return "CopyLok / CodeLok (Solo errors, ~775)"; + + return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!"; + } } } From 0bb0285b068d1fe8d7350947d0caf9ac6d5fb06b Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 01:31:05 -0400 Subject: [PATCH 07/36] Comments with redump IDs --- BinaryObjectScanner/Protection/CopyLok.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BinaryObjectScanner/Protection/CopyLok.cs b/BinaryObjectScanner/Protection/CopyLok.cs index 292dac33..a1669a3b 100644 --- a/BinaryObjectScanner/Protection/CopyLok.cs +++ b/BinaryObjectScanner/Protection/CopyLok.cs @@ -73,6 +73,7 @@ public class CopyLok : IExecutableCheck, IISOCheck // Check for early variant copylok if (earlyCopyLokBytesOne == 0x00) { + // Redump ID 35908, 56433, 44526 if (0 == pairBytesOne && 0 == oneValueBytes && 0 == earlyCopyLokBytesTwo && 0 == pairBytesTwo) return "CopyLok / CodeLok (Early, ~1850 errors)"; @@ -83,19 +84,21 @@ public class CopyLok : IExecutableCheck, IISOCheck 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 where it's 0x6999 + // 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)"; From 55ebf898aa37df3c1e86753eba45a8fa1b23d768 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 01:34:14 -0400 Subject: [PATCH 08/36] Add redump examples to laserlock --- BinaryObjectScanner/Protection/LaserLok.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index c4af74f7..44fbb926 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -167,12 +167,15 @@ public List CheckDirectoryPath(string path, List? files) 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. return "None"; } From 571e639f49ff30b412b0e51bdbb23399dec8ba05 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 01:46:26 -0400 Subject: [PATCH 09/36] Change for testing --- BinaryObjectScanner/Protection/LaserLok.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index 44fbb926..6519acd0 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -156,10 +156,10 @@ public List CheckDirectoryPath(string path, List? files) var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; if (FileType.ISO9660.NoteworthyApplicationUse(pvd)) - return "None"; //TODO: this might be too unsafe until more App Use strings are known + return "Znone"; //TODO: this might be too unsafe until more App Use strings are known if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) - return "None"; + return "Znone"; var reserved653Bytes = pvd.Reserved653Bytes; var firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0); @@ -176,7 +176,7 @@ public List CheckDirectoryPath(string path, List? files) return "LaserLock Marathon"; // Some discs such as 128068, and also more normal ones, don't seem to have any identifying data. - return "None"; + return "Znone"; } private static string GetBuild(byte[]? sectionContent, bool versionTwo) From 33040b86dac0eab0b2525632f28eb27ead02cd91 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 01:46:58 -0400 Subject: [PATCH 10/36] Small comment --- BinaryObjectScanner/Protection/LaserLok.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index 6519acd0..44db4a7d 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -176,6 +176,7 @@ public List CheckDirectoryPath(string path, List? files) 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 "Znone"; } From 53f96be30c711f67c3992893b7722a2514295c56 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 02:36:52 -0400 Subject: [PATCH 11/36] TAGES --- BinaryObjectScanner/FileType/ISO9660.cs | 21 ++++++++++++++ BinaryObjectScanner/Protection/Tages.cs | 37 ++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index a7b67293..c55442cf 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -39,6 +39,27 @@ var subProtections 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}; + for (int i = 0, index = 0; i < bytes.Length; ++i) + + if (bytes[i] == containedZeroes[index]) { + if (++index >= containedZeroes.Length) { + return false; + } + } + + // Checks if there are strings in the data + // TODO: is this too dangerous? + if (bytes.ReadStringsFrom(charLimit: 3) != null) + return false; + + return true; + } // Checks whether the Application Use data is "noteworthy" enough to be worth checking for protection. public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs index 0ec99056..95234630 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, IISOCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -211,6 +212,40 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } + + public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + { + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + + if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) + return null; + + 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 the last 128. + if (FileType.ISO9660.IsPureData(applicationUse)) + return "TAGES"; + + int offset = 0; + var first384Bytes = applicationUse.ReadBytes(ref offset, 384); + var last128Bytes = applicationUse.ReadBytes(ref offset, 128); + if (Array.TrueForAll(first384Bytes, b => b == 0x00) && FileType.ISO9660.IsPureData(last128Bytes)) + return "TAGES"; + + // Early tages has a 4-byte value at the beginning of the AU data and nothing else. + // Redump ID 35932, 21321, 8776, + offset = 0; + var initialValue = applicationUse.ReadInt32LittleEndian(ref offset); + var last508Bytes = applicationUse.ReadBytes(ref offset, 508); + if (Array.TrueForAll(last508Bytes, b => b == 0x00) && initialValue != 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) { From a07f50ffc0f1e4d4494a3a8c6c9a7bee4448cfae Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 03:37:08 -0400 Subject: [PATCH 12/36] halfway through safedisc --- BinaryObjectScanner/Protection/Macrovision.cs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index 175c34d6..0cf24b51 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, IISOCheck { /// public string? CheckExecutable(string file, NewExecutable exe, bool includeDebug) @@ -242,6 +243,48 @@ public List CheckDirectoryPath(string path, List? files) return null; } + + public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + { + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + + 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 reserved653Bytes = pvd.Reserved653Bytes; + var applicationUse = pvd.ApplicationUse; + + int offset = 0; + var appUsefirst256Bytes = applicationUse.ReadBytes(ref offset, 256); + var appUsemiddle128Bytes = applicationUse.ReadBytes(ref offset, 128); + offset += 64; // Some extra values get added here over time. Check is good enough, easier to skip this. + var appUseFirstUint = applicationUse.ReadUInt16LittleEndian(ref offset); + var appUseFollowingFirstUint = applicationUse.ReadBytes(ref offset, 20); + var appUseSecondUint = applicationUse.ReadUInt32LittleEndian(ref offset); + + // TODO: once ST is fixed, finish this up. read to the end of the AU, then read however many bytes from the + // TODO: start of the reserved, confirm everything, check if reserved ends with enough 0x00 bytes too. + + /*if (Array.TrueForAll(first384Bytes, b => b == 0x00) && FileType.ISO9660.IsPureData(last128Bytes)) + return "TAGES";*/ + + // Early tages has a 4-byte value at the beginning of the AU data and nothing else. + // Redump ID 35932, 21321, 8776, + offset = 0; + var initialValue = applicationUse.ReadInt32LittleEndian(ref offset); + var last508Bytes = applicationUse.ReadBytes(ref offset, 508); + if (Array.TrueForAll(last508Bytes, b => b == 0x00) && initialValue != 0) + return "TAGES (Early)"; + + // The original releases of Moto Racer 3 (31578, 34669) are so early they have seemingly nothing identifiable. + return null; + } /// internal static List MacrovisionCheckDirectoryPath(string path, List? files) From d5dfcaefe77f6714f6729b0a4f1e1fb5e9f1e992 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 15:26:05 -0400 Subject: [PATCH 13/36] Safedisc done --- BinaryObjectScanner/Protection/CopyLok.cs | 12 +++++ BinaryObjectScanner/Protection/LaserLok.cs | 4 ++ BinaryObjectScanner/Protection/Macrovision.cs | 52 ++++++++++++++----- BinaryObjectScanner/Protection/Tages.cs | 10 +++- 4 files changed, 63 insertions(+), 15 deletions(-) diff --git a/BinaryObjectScanner/Protection/CopyLok.cs b/BinaryObjectScanner/Protection/CopyLok.cs index a1669a3b..0bca4dee 100644 --- a/BinaryObjectScanner/Protection/CopyLok.cs +++ b/BinaryObjectScanner/Protection/CopyLok.cs @@ -39,6 +39,8 @@ public class CopyLok : IExecutableCheck, IISOCheck public string? CheckISO(string file, ISO9660 iso, bool includeDebug) { + #region Initial Checks + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) @@ -47,8 +49,12 @@ public class CopyLok : IExecutableCheck, IISOCheck if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) return "None"; + #endregion + int offset = 0; + #region Read Application Use + var applicationUse = pvd.ApplicationUse; var constantValueOne = applicationUse.ReadUInt32LittleEndian(ref offset); var smallSizeBytes = applicationUse.ReadUInt16LittleEndian(ref offset); @@ -62,6 +68,10 @@ public class CopyLok : IExecutableCheck, IISOCheck var 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; @@ -102,6 +112,8 @@ public class CopyLok : IExecutableCheck, IISOCheck 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 44db4a7d..00ad9f4e 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -153,6 +153,8 @@ public List CheckDirectoryPath(string path, List? files) public string? CheckISO(string file, ISO9660 iso, bool includeDebug) { + #region Initial Checks + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; if (FileType.ISO9660.NoteworthyApplicationUse(pvd)) @@ -161,6 +163,8 @@ public List CheckDirectoryPath(string path, List? files) if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) return "Znone"; + #endregion + var reserved653Bytes = pvd.Reserved653Bytes; var firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0); string? finalString = reserved653Bytes.ReadNullTerminatedAnsiString(ref firstNonZero); diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index 0cf24b51..cf58934c 100644 --- a/BinaryObjectScanner/Protection/Macrovision.cs +++ b/BinaryObjectScanner/Protection/Macrovision.cs @@ -246,6 +246,8 @@ public List CheckDirectoryPath(string path, List? files) public string? CheckISO(string file, ISO9660 iso, bool includeDebug) { + #region Initial Checks + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) @@ -257,9 +259,13 @@ public List CheckDirectoryPath(string path, List? files) if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) return null; - var reserved653Bytes = pvd.Reserved653Bytes; + #endregion + var applicationUse = pvd.ApplicationUse; - + var reserved653Bytes = pvd.Reserved653Bytes; + + #region Read Application Use + int offset = 0; var appUsefirst256Bytes = applicationUse.ReadBytes(ref offset, 256); var appUsemiddle128Bytes = applicationUse.ReadBytes(ref offset, 128); @@ -267,23 +273,43 @@ public List CheckDirectoryPath(string path, List? files) var appUseFirstUint = applicationUse.ReadUInt16LittleEndian(ref offset); var appUseFollowingFirstUint = applicationUse.ReadBytes(ref offset, 20); var appUseSecondUint = applicationUse.ReadUInt32LittleEndian(ref offset); + var finalAppUseData = 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 reservedFirst10Bytes = reserved653Bytes.ReadBytes(ref offset, 10); + offset = 96; // TODO: Does it ever go further than this? + var reservedFinal557Bytes = reserved653Bytes.ReadBytes(ref offset, 557); + + #endregion // TODO: once ST is fixed, finish this up. read to the end of the AU, then read however many bytes from the // TODO: start of the reserved, confirm everything, check if reserved ends with enough 0x00 bytes too. - /*if (Array.TrueForAll(first384Bytes, b => b == 0x00) && FileType.ISO9660.IsPureData(last128Bytes)) - return "TAGES";*/ + // The first 256 bytes of application use, and the last 557 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(appUsefirst256Bytes, b => b == 0x00) || !Array.TrueForAll(reservedFinal557Bytes, b => b == 0x00)) + return null; - // Early tages has a 4-byte value at the beginning of the AU data and nothing else. - // Redump ID 35932, 21321, 8776, - offset = 0; - var initialValue = applicationUse.ReadInt32LittleEndian(ref offset); - var last508Bytes = applicationUse.ReadBytes(ref offset, 508); - if (Array.TrueForAll(last508Bytes, b => b == 0x00) && initialValue != 0) - return "TAGES (Early)"; + // All of these sections should be pure data + if (!FileType.ISO9660.IsPureData(appUsemiddle128Bytes) + || !FileType.ISO9660.IsPureData(appUseFollowingFirstUint) + || !FileType.ISO9660.IsPureData(finalAppUseData) + || !FileType.ISO9660.IsPureData(reservedFirst10Bytes)) + 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 (appUseFirstUint > 0xFF || appUseSecondUint > 0xFF) + return null; - // The original releases of Moto Racer 3 (31578, 34669) are so early they have seemingly nothing identifiable. - return null; + return "SafeDisc"; } /// diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs index 95234630..97a52120 100644 --- a/BinaryObjectScanner/Protection/Tages.cs +++ b/BinaryObjectScanner/Protection/Tages.cs @@ -215,17 +215,23 @@ public List CheckDirectoryPath(string path, List? files) public string? CheckISO(string file, ISO9660 iso, bool includeDebug) { + #region Initial Checks + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + // 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 the last 128. + // 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"; @@ -236,7 +242,7 @@ public List CheckDirectoryPath(string path, List? files) return "TAGES"; // Early tages has a 4-byte value at the beginning of the AU data and nothing else. - // Redump ID 35932, 21321, 8776, + // Redump ID 8776, 21321, 35932 offset = 0; var initialValue = applicationUse.ReadInt32LittleEndian(ref offset); var last508Bytes = applicationUse.ReadBytes(ref offset, 508); From b3ba774269c718b236844e38c4626b5803c4d0a8 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 15:35:18 -0400 Subject: [PATCH 14/36] Fix 1 --- BinaryObjectScanner/Protection/Macrovision.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index cf58934c..e318847f 100644 --- a/BinaryObjectScanner/Protection/Macrovision.cs +++ b/BinaryObjectScanner/Protection/Macrovision.cs @@ -283,17 +283,17 @@ public List CheckDirectoryPath(string path, List? files) // Somewhat arbitrary, but going further than 11 seems to exclude some discs. var reservedFirst10Bytes = reserved653Bytes.ReadBytes(ref offset, 10); - offset = 96; // TODO: Does it ever go further than this? - var reservedFinal557Bytes = reserved653Bytes.ReadBytes(ref offset, 557); + offset = 132; // TODO: Does it ever go further than this? + var reservedFinal521Bytes = reserved653Bytes.ReadBytes(ref offset, 521); #endregion // TODO: once ST is fixed, finish this up. read to the end of the AU, then read however many bytes from the // TODO: start of the reserved, confirm everything, check if reserved ends with enough 0x00 bytes too. - // The first 256 bytes of application use, and the last 557 bytes of reserved data, should all be 0x00. + // 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(appUsefirst256Bytes, b => b == 0x00) || !Array.TrueForAll(reservedFinal557Bytes, b => b == 0x00)) + if (!Array.TrueForAll(appUsefirst256Bytes, b => b == 0x00) || !Array.TrueForAll(reservedFinal521Bytes, b => b == 0x00)) return null; // All of these sections should be pure data From 5c42ed5e5db7e12cae9cffe6a8f8fe4fa3bc2dbe Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 16:09:35 -0400 Subject: [PATCH 15/36] Major oversights in puredata fixed --- BinaryObjectScanner/FileType/ISO9660.cs | 20 +++++++++++-------- BinaryObjectScanner/Protection/CopyLok.cs | 4 ++-- BinaryObjectScanner/Protection/Macrovision.cs | 3 --- BinaryObjectScanner/Protection/Tages.cs | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index c55442cf..32d0ac06 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text; using BinaryObjectScanner.Data; using SabreTools.Data.Models.ISO9660; using SabreTools.IO.Extensions; @@ -45,17 +46,20 @@ 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}; - for (int i = 0, index = 0; i < bytes.Length; ++i) - - if (bytes[i] == containedZeroes[index]) { - if (++index >= containedZeroes.Length) { + 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? - if (bytes.ReadStringsFrom(charLimit: 3) != null) + // TODO: is this too dangerous, or too faulty? + if (bytes.ReadStringsWithEncoding(charLimit: 7, Encoding.ASCII).Count > 0) return false; return true; diff --git a/BinaryObjectScanner/Protection/CopyLok.cs b/BinaryObjectScanner/Protection/CopyLok.cs index 0bca4dee..ade49f7f 100644 --- a/BinaryObjectScanner/Protection/CopyLok.cs +++ b/BinaryObjectScanner/Protection/CopyLok.cs @@ -44,10 +44,10 @@ public class CopyLok : IExecutableCheck, IISOCheck var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) - return "None"; + return null; if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) - return "None"; + return null; #endregion diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index e318847f..ea3a2a9b 100644 --- a/BinaryObjectScanner/Protection/Macrovision.cs +++ b/BinaryObjectScanner/Protection/Macrovision.cs @@ -288,9 +288,6 @@ public List CheckDirectoryPath(string path, List? files) #endregion - // TODO: once ST is fixed, finish this up. read to the end of the AU, then read however many bytes from the - // TODO: start of the reserved, confirm everything, check if reserved ends with enough 0x00 bytes too. - // 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(appUsefirst256Bytes, b => b == 0x00) || !Array.TrueForAll(reservedFinal521Bytes, b => b == 0x00)) diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs index 97a52120..b2f16d03 100644 --- a/BinaryObjectScanner/Protection/Tages.cs +++ b/BinaryObjectScanner/Protection/Tages.cs @@ -219,7 +219,7 @@ public List CheckDirectoryPath(string path, List? files) var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; - // There needs to be noteworthy application use data + // There needs to be noteworthy application use data. if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) return null; From c5ef1dae773023a1b4acd930c0a2e1af2b47ddd8 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 20:10:48 -0400 Subject: [PATCH 16/36] Finish SecuROM --- BinaryObjectScanner/FileType/ISO9660.cs | 21 +++- BinaryObjectScanner/Protection/Macrovision.cs | 28 ++--- BinaryObjectScanner/Protection/SecuROM.cs | 104 +++++++++++++++++- BinaryObjectScanner/Protection/Tages.cs | 12 +- 4 files changed, 142 insertions(+), 23 deletions(-) diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index 32d0ac06..03d60dce 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -2,6 +2,7 @@ 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; @@ -59,12 +60,20 @@ public static bool IsPureData(byte[] bytes) // Checks if there are strings in the data // TODO: is this too dangerous, or too faulty? - if (bytes.ReadStringsWithEncoding(charLimit: 7, Encoding.ASCII).Count > 0) - return false; + // 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) { @@ -80,8 +89,16 @@ public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) noteworthyApplicationUse = false; else if (potentialAppUseString.StartsWith("ULTRAISO")) noteworthyApplicationUse = false; + else if (Array.TrueForAll(Encoding.ASCII.GetBytes(potentialAppUseString), b => b == 0x20)) + noteworthyApplicationUse = false; // More things will have to go here as more disc authoring softwares are found that do this. } + + offset = 141; + potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset); + if (potentialAppUseString == "CD-XA001") + noteworthyApplicationUse = false; + return noteworthyApplicationUse; } diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index ea3a2a9b..de631300 100644 --- a/BinaryObjectScanner/Protection/Macrovision.cs +++ b/BinaryObjectScanner/Protection/Macrovision.cs @@ -267,13 +267,13 @@ public List CheckDirectoryPath(string path, List? files) #region Read Application Use int offset = 0; - var appUsefirst256Bytes = applicationUse.ReadBytes(ref offset, 256); - var appUsemiddle128Bytes = applicationUse.ReadBytes(ref offset, 128); + 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. - var appUseFirstUint = applicationUse.ReadUInt16LittleEndian(ref offset); - var appUseFollowingFirstUint = applicationUse.ReadBytes(ref offset, 20); - var appUseSecondUint = applicationUse.ReadUInt32LittleEndian(ref offset); - var finalAppUseData = applicationUse.ReadBytes(ref offset, 38); + var appUseUintOne = applicationUse.ReadUInt16LittleEndian(ref offset); + var appUseDataBytesTwo = applicationUse.ReadBytes(ref offset, 20); + var appUseUintTwo = applicationUse.ReadUInt32LittleEndian(ref offset); + var appUseDataBytesThree = applicationUse.ReadBytes(ref offset, 38); #endregion @@ -282,28 +282,28 @@ public List CheckDirectoryPath(string path, List? files) #region Read Reserved 653 Bytes // Somewhat arbitrary, but going further than 11 seems to exclude some discs. - var reservedFirst10Bytes = reserved653Bytes.ReadBytes(ref offset, 10); + var reservedDataBytes = reserved653Bytes.ReadBytes(ref offset, 10); offset = 132; // TODO: Does it ever go further than this? - var reservedFinal521Bytes = reserved653Bytes.ReadBytes(ref offset, 521); + 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(appUsefirst256Bytes, b => b == 0x00) || !Array.TrueForAll(reservedFinal521Bytes, b => b == 0x00)) + 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(appUsemiddle128Bytes) - || !FileType.ISO9660.IsPureData(appUseFollowingFirstUint) - || !FileType.ISO9660.IsPureData(finalAppUseData) - || !FileType.ISO9660.IsPureData(reservedFirst10Bytes)) + 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 (appUseFirstUint > 0xFF || appUseSecondUint > 0xFF) + if (appUseUintOne > 0xFF || appUseUintTwo > 0xFF) return null; return "SafeDisc"; diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs index 685f7bad..c27dc895 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, IISOCheck { /// /// Matches hash of the Release Control-encrypted executable to known hashes @@ -252,6 +253,107 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } + + public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + { + #region Initial Checks + + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + + // 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)) + { + var 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); + var reservedHundredValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + var reserveDataBytesOne = reserved653Bytes.ReadBytes(ref offset, 80); + var reservedZeroBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12); + var reservedUintOne = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + var reservedUintTwoLow = reserved653Bytes.ReadUInt32LittleEndian(ref offset); // Low value + var reservedZeroBytesThree = reserved653Bytes.ReadBytes(ref offset, 4); + var reservedUintThree = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + var reservedZeroBytesFour = reserved653Bytes.ReadBytes(ref offset, 12); + var reservedUintFour = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + var reservedOneValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + var reservedZeroBytesFive = reserved653Bytes.ReadBytes(ref offset, 4); + var reservedDataBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12); + var reservedLowByteValueOne = reserved653Bytes.ReadByteValue(ref offset); + var reservedLowByteValueTwo = reserved653Bytes.ReadByteValue(ref offset); + var reservedLowByteValueThree = reserved653Bytes.ReadByteValue(ref offset); + var 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; + + // 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; + + if (!FileType.ISO9660.IsPureData(reservedDataBytesTwo) || + !FileType.ISO9660.IsPureData(reservedDataBytesThree)) + return null; + + return "SecuROM"; + } /// /// Try to get the SecuROM v4 version from the overlay, if possible diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs index b2f16d03..68cf71a6 100644 --- a/BinaryObjectScanner/Protection/Tages.cs +++ b/BinaryObjectScanner/Protection/Tages.cs @@ -236,17 +236,17 @@ public List CheckDirectoryPath(string path, List? files) return "TAGES"; int offset = 0; - var first384Bytes = applicationUse.ReadBytes(ref offset, 384); - var last128Bytes = applicationUse.ReadBytes(ref offset, 128); - if (Array.TrueForAll(first384Bytes, b => b == 0x00) && FileType.ISO9660.IsPureData(last128Bytes)) + 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; - var initialValue = applicationUse.ReadInt32LittleEndian(ref offset); - var last508Bytes = applicationUse.ReadBytes(ref offset, 508); - if (Array.TrueForAll(last508Bytes, b => b == 0x00) && initialValue != 0) + var earlyTagesBytes = applicationUse.ReadInt32LittleEndian(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. From 47bd3bfd0dd2cf8abf02b3fd85c86936c235764e Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 21:04:39 -0400 Subject: [PATCH 17/36] ProtectDiSC done --- BinaryObjectScanner/Protection/ProtectDISC.cs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/BinaryObjectScanner/Protection/ProtectDISC.cs b/BinaryObjectScanner/Protection/ProtectDISC.cs index 1f969a27..7829f8a3 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, IISOCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -132,6 +134,36 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } + + public string? CheckISO(string file, ISO9660 iso, 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. + + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + 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) { From cf3f874da9a6fe0742fb7e27843b14935ac40180 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 21:36:52 -0400 Subject: [PATCH 18/36] Alpharom done --- BinaryObjectScanner/Protection/AlphaROM.cs | 51 +++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/BinaryObjectScanner/Protection/AlphaROM.cs b/BinaryObjectScanner/Protection/AlphaROM.cs index deda8920..8e8d68c2 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, IISOCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -84,5 +88,48 @@ public class AlphaROM : IExecutableCheck return null; } + + public string? CheckISO(string file, ISO9660 iso, 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. + + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + + // 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; + + offset = 0; + + // 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; + var 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"; + } } } From 4af8d71eb6300e91ad42e51630385a3db4265bfd Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 22:03:06 -0400 Subject: [PATCH 19/36] Finish StarForce, initial PR review ready --- BinaryObjectScanner/Protection/AlphaROM.cs | 4 +- BinaryObjectScanner/Protection/StarForce.cs | 47 ++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/BinaryObjectScanner/Protection/AlphaROM.cs b/BinaryObjectScanner/Protection/AlphaROM.cs index 8e8d68c2..80930d88 100644 --- a/BinaryObjectScanner/Protection/AlphaROM.cs +++ b/BinaryObjectScanner/Protection/AlphaROM.cs @@ -93,7 +93,7 @@ public class AlphaROM : IExecutableCheck, IISOCheck { // 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. - + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; // Alpharom disc check #1: disc has varying (but observed to at least always be larger than 14) length @@ -115,9 +115,9 @@ public class AlphaROM : IExecutableCheck, IISOCheck var publisherIdentifier = pvd.PublisherIdentifier; var 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); diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index f7a88654..e2030761 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, IISOCheck { // TODO: Bring up to par with PiD. // Known issues: @@ -160,5 +162,48 @@ 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? CheckISO(string file, ISO9660 iso, bool includeDebug) + { + var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + + int offset = 0; + + // StarForce Keyless check #1: the key is stored in the Data Preparer identifier. Length varies, minimum + // length unknown, but it shouldn't be less than 8 at the very least. It's usually 15-24. It's only + // made up of numbers, capital letters, and dashes. + var dataPreparerIdentiferString = pvd.DataPreparerIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim(); + if (dataPreparerIdentiferString == null || dataPreparerIdentiferString.Length < 8) + return null; + + if (!Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9-]*$")) + return null; + + // Starforce Keyless check #2: 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; + + offset = 0; + + var reserved653Bytes = pvd.Reserved653Bytes; + var initialValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + var zeroBytes = reserved653Bytes.ReadBytes(ref offset, 508); + + if (initialValue + 500 < pvd.VolumeSpaceSize || !Array.TrueForAll(zeroBytes, b => b == 0x00)) + return null; + + // It's unfortunately not known to be possible to detect non-keyless StarForce discs. + + // It may be worth 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. + return $"StarForce Keyless - {dataPreparerIdentiferString}"; + } } } From 81f0662b48f688003fc715e81caaba9744b06483 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Fri, 31 Oct 2025 23:51:20 -0400 Subject: [PATCH 20/36] Wait, that would be really bad --- BinaryObjectScanner/Protection/StarForce.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index e2030761..835bcc44 100644 --- a/BinaryObjectScanner/Protection/StarForce.cs +++ b/BinaryObjectScanner/Protection/StarForce.cs @@ -194,7 +194,7 @@ public List CheckDirectoryPath(string path, List? files) var initialValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); var zeroBytes = reserved653Bytes.ReadBytes(ref offset, 508); - if (initialValue + 500 < pvd.VolumeSpaceSize || !Array.TrueForAll(zeroBytes, b => b == 0x00)) + if (initialValue > pvd.VolumeSpaceSize || initialValue + 500 < pvd.VolumeSpaceSize || !Array.TrueForAll(zeroBytes, b => b == 0x00)) return null; // It's unfortunately not known to be possible to detect non-keyless StarForce discs. From fc81ca1eb038ced10cdf7547c04ca1de9a7fc0b8 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 1 Nov 2025 03:05:48 -0400 Subject: [PATCH 21/36] One more for the road. --- BinaryObjectScanner/FileType/ISO9660.cs | 4 ++++ BinaryObjectScanner/Protection/LaserLok.cs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index 03d60dce..54f6803a 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -89,9 +89,13 @@ public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) noteworthyApplicationUse = false; else if (potentialAppUseString.StartsWith("ULTRAISO")) noteworthyApplicationUse = false; + else if (potentialAppUseString.StartsWith("Rimage")) + noteworthyApplicationUse = false; else if (Array.TrueForAll(Encoding.ASCII.GetBytes(potentialAppUseString), b => b == 0x20)) noteworthyApplicationUse = false; // 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; diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index 00ad9f4e..e24dbd79 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -158,10 +158,10 @@ public List CheckDirectoryPath(string path, List? files) var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; if (FileType.ISO9660.NoteworthyApplicationUse(pvd)) - return "Znone"; //TODO: this might be too unsafe until more App Use strings are known + return null; //TODO: this might be too unsafe until more App Use strings are known if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) - return "Znone"; + return null; #endregion @@ -181,7 +181,7 @@ public List CheckDirectoryPath(string path, List? files) // Some discs such as 128068, and also more normal ones, don't seem to have any identifying data. // TODO: list some normal ones - return "Znone"; + return null; } private static string GetBuild(byte[]? sectionContent, bool versionTwo) From e7c2628cebd9b387110b8c4510c1fcc876483bde Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 1 Nov 2025 03:38:05 -0400 Subject: [PATCH 22/36] Small finding --- BinaryObjectScanner/Protection/StarForce.cs | 27 ++++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index 835bcc44..c130ee0b 100644 --- a/BinaryObjectScanner/Protection/StarForce.cs +++ b/BinaryObjectScanner/Protection/StarForce.cs @@ -169,17 +169,7 @@ public List CheckDirectoryPath(string path, List? files) int offset = 0; - // StarForce Keyless check #1: the key is stored in the Data Preparer identifier. Length varies, minimum - // length unknown, but it shouldn't be less than 8 at the very least. It's usually 15-24. It's only - // made up of numbers, capital letters, and dashes. - var dataPreparerIdentiferString = pvd.DataPreparerIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim(); - if (dataPreparerIdentiferString == null || dataPreparerIdentiferString.Length < 8) - return null; - - if (!Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9-]*$")) - return null; - - // Starforce Keyless check #2: the reserved 653 bytes start with a 32-bit LE number that's slightly less + // 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)) @@ -196,8 +186,21 @@ public List CheckDirectoryPath(string path, List? files) if (initialValue > pvd.VolumeSpaceSize || initialValue + 500 < pvd.VolumeSpaceSize || !Array.TrueForAll(zeroBytes, b => b == 0x00)) return null; + + // StarForce Keyless check #2: the key is stored in the Data Preparer identifier. Length varies, minimum + // length unknown, but it shouldn't be less than 8 at the very least. It's usually 15-24. It's only + // made up of numbers, capital letters, and dashes. + + // It turns out that at least a few (i.e. Redump ID 60266) non-keyless StarForce discs still have this + // value here? This check may need to be disabled, but it should hopefully be ok + var dataPreparerIdentiferString = pvd.DataPreparerIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim(); + if (dataPreparerIdentiferString == null || dataPreparerIdentiferString.Length < 8) + return "StarForce"; + + if (!Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9-]*$")) + return "StarForce"; - // It's unfortunately not known to be possible to detect non-keyless StarForce discs. + // It's unfortunately not known to be possible to detect most non-keyless StarForce discs. // It may be worth 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 From 5b914245324e4404e05f60674d6b6fa3e9ad45f2 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 1 Nov 2025 03:51:46 -0400 Subject: [PATCH 23/36] Small fix for finding --- BinaryObjectScanner/Protection/StarForce.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index c130ee0b..cdd8bb1f 100644 --- a/BinaryObjectScanner/Protection/StarForce.cs +++ b/BinaryObjectScanner/Protection/StarForce.cs @@ -167,8 +167,6 @@ public List CheckDirectoryPath(string path, List? files) { var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; - int offset = 0; - // 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. @@ -178,7 +176,7 @@ public List CheckDirectoryPath(string path, List? files) if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd)) return null; - offset = 0; + int offset = 0; var reserved653Bytes = pvd.Reserved653Bytes; var initialValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); @@ -186,6 +184,8 @@ public List CheckDirectoryPath(string path, List? files) 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. Length varies, minimum // length unknown, but it shouldn't be less than 8 at the very least. It's usually 15-24. It's only From d490a83dc9777b142685323ad3eaef8d04968dde Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 1 Nov 2025 05:22:05 -0400 Subject: [PATCH 24/36] Notes on finding --- BinaryObjectScanner/Protection/StarForce.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index cdd8bb1f..dba51d14 100644 --- a/BinaryObjectScanner/Protection/StarForce.cs +++ b/BinaryObjectScanner/Protection/StarForce.cs @@ -191,12 +191,16 @@ public List CheckDirectoryPath(string path, List? files) // length unknown, but it shouldn't be less than 8 at the very least. It's usually 15-24. It's only // made up of numbers, capital letters, and dashes. - // It turns out that at least a few (i.e. Redump ID 60266) non-keyless StarForce discs still have this - // value here? This check may need to be disabled, but it should hopefully be ok + // It turns out that at least a few (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 OK in practice so far. var dataPreparerIdentiferString = pvd.DataPreparerIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim(); if (dataPreparerIdentiferString == null || dataPreparerIdentiferString.Length < 8) return "StarForce"; + // 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. if (!Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9-]*$")) return "StarForce"; From 6ae8b26fd27f63ad560dd90ffaa41f1e1d86afb2 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sun, 2 Nov 2025 02:32:47 -0500 Subject: [PATCH 25/36] Several minor fixes, decisions --- BinaryObjectScanner/FileType/ISO9660.cs | 1 + BinaryObjectScanner/Protection/AlphaROM.cs | 4 +- BinaryObjectScanner/Protection/CopyLok.cs | 4 +- BinaryObjectScanner/Protection/LaserLok.cs | 4 +- BinaryObjectScanner/Protection/Macrovision.cs | 4 +- BinaryObjectScanner/Protection/ProtectDISC.cs | 7 +- BinaryObjectScanner/Protection/SecuROM.cs | 36 +++++++++- BinaryObjectScanner/Protection/StarForce.cs | 65 +++++++++++++------ BinaryObjectScanner/Protection/Tages.cs | 4 +- 9 files changed, 101 insertions(+), 28 deletions(-) diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index 54f6803a..b92571b2 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -93,6 +93,7 @@ public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) noteworthyApplicationUse = false; else if (Array.TrueForAll(Encoding.ASCII.GetBytes(potentialAppUseString), b => b == 0x20)) noteworthyApplicationUse = 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. diff --git a/BinaryObjectScanner/Protection/AlphaROM.cs b/BinaryObjectScanner/Protection/AlphaROM.cs index 80930d88..39731fac 100644 --- a/BinaryObjectScanner/Protection/AlphaROM.cs +++ b/BinaryObjectScanner/Protection/AlphaROM.cs @@ -94,7 +94,9 @@ public class AlphaROM : IExecutableCheck, IISOCheck // 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. - var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + if (iso.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. diff --git a/BinaryObjectScanner/Protection/CopyLok.cs b/BinaryObjectScanner/Protection/CopyLok.cs index ade49f7f..aa1fb16b 100644 --- a/BinaryObjectScanner/Protection/CopyLok.cs +++ b/BinaryObjectScanner/Protection/CopyLok.cs @@ -41,7 +41,9 @@ public class CopyLok : IExecutableCheck, IISOCheck { #region Initial Checks - var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + return null; + if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) return null; diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index e24dbd79..3277b76e 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -155,7 +155,9 @@ public List CheckDirectoryPath(string path, List? files) { #region Initial Checks - var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + if (iso.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 diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index de631300..77d3582c 100644 --- a/BinaryObjectScanner/Protection/Macrovision.cs +++ b/BinaryObjectScanner/Protection/Macrovision.cs @@ -248,7 +248,9 @@ public List CheckDirectoryPath(string path, List? files) { #region Initial Checks - var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + return null; + if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) return null; diff --git a/BinaryObjectScanner/Protection/ProtectDISC.cs b/BinaryObjectScanner/Protection/ProtectDISC.cs index 7829f8a3..ecf7168b 100644 --- a/BinaryObjectScanner/Protection/ProtectDISC.cs +++ b/BinaryObjectScanner/Protection/ProtectDISC.cs @@ -141,11 +141,14 @@ public List CheckDirectoryPath(string path, List? files) // can be checked. Not bothering since this doesn't work for ProtectCD/DVD 6.x discs, which use otherwise // the same check anyways. - var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + if (iso.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 @@ -153,6 +156,7 @@ public List CheckDirectoryPath(string path, List? files) 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 @@ -162,6 +166,7 @@ public List CheckDirectoryPath(string path, List? files) 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+"; } diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs index c27dc895..1b7236f1 100644 --- a/BinaryObjectScanner/Protection/SecuROM.cs +++ b/BinaryObjectScanner/Protection/SecuROM.cs @@ -257,8 +257,9 @@ public List CheckDirectoryPath(string path, List? files) public string? CheckISO(string file, ISO9660 iso, bool includeDebug) { #region Initial Checks - - var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + + if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + return null; // Application Use is too inconsistent to include or exclude @@ -319,6 +320,35 @@ public List CheckDirectoryPath(string path, List? files) || !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. @@ -352,7 +382,7 @@ public List CheckDirectoryPath(string path, List? files) !FileType.ISO9660.IsPureData(reservedDataBytesThree)) return null; - return "SecuROM"; + return "SecuROM 4.8x+"; } /// diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index dba51d14..387bfd64 100644 --- a/BinaryObjectScanner/Protection/StarForce.cs +++ b/BinaryObjectScanner/Protection/StarForce.cs @@ -165,7 +165,9 @@ public List CheckDirectoryPath(string path, List? files) public string? CheckISO(string file, ISO9660 iso, bool includeDebug) { - var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + if (iso.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 @@ -180,37 +182,62 @@ public List CheckDirectoryPath(string path, List? files) var reserved653Bytes = pvd.Reserved653Bytes; var initialValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); - var zeroBytes = reserved653Bytes.ReadBytes(ref offset, 508); + 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. Length varies, minimum - // length unknown, but it shouldn't be less than 8 at the very least. It's usually 15-24. It's only - // made up of numbers, capital letters, and dashes. + // StarForce Keyless check #2: the key is stored in the Data Preparer identifier. - // It turns out that at least a few (i.e. Redump ID 60266, 72531, 87181, 91734, 106732, 105356, 74578, 78200) + // 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 OK in practice so far. + // seems to avoid any false positives in practice so far. var dataPreparerIdentiferString = pvd.DataPreparerIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim(); - if (dataPreparerIdentiferString == null || dataPreparerIdentiferString.Length < 8) + 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 + // it has lowercase letters and spaces, it's caught here. It is genuinely StarForce, so it's not a false // positive. - if (!Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9-]*$")) - return "StarForce"; - - // It's unfortunately not known to be possible to detect most non-keyless StarForce discs. - - // It may be worth 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. - return $"StarForce Keyless - {dataPreparerIdentiferString}"; + 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 68cf71a6..f162e8b8 100644 --- a/BinaryObjectScanner/Protection/Tages.cs +++ b/BinaryObjectScanner/Protection/Tages.cs @@ -217,7 +217,9 @@ public List CheckDirectoryPath(string path, List? files) { #region Initial Checks - var pvd = (PrimaryVolumeDescriptor)iso.VolumeDescriptorSet[0]; + if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + return null; + // There needs to be noteworthy application use data. if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) From 33391fa0c4971bf7e3913c65c7756b5623640c39 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sun, 2 Nov 2025 03:57:41 -0500 Subject: [PATCH 26/36] what do you MEAN it returns true if there are no elements in the array --- BinaryObjectScanner/FileType/ISO9660.cs | 2 +- BinaryObjectScanner/Protection/Tages.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index b92571b2..98039d3e 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -83,7 +83,7 @@ public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) if (Array.TrueForAll(applicationUse, b => b == 0x00)) noteworthyApplicationUse = false; string? potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset); - if (potentialAppUseString != null) // Some image authoring programs add a starting string to AU data + if (potentialAppUseString != null && potentialAppUseString.Length > 0) // Some image authoring programs add a starting string to AU data { if (potentialAppUseString.StartsWith("ImgBurn")) noteworthyApplicationUse = false; diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs index f162e8b8..fca4f8d4 100644 --- a/BinaryObjectScanner/Protection/Tages.cs +++ b/BinaryObjectScanner/Protection/Tages.cs @@ -219,7 +219,6 @@ public List CheckDirectoryPath(string path, List? files) if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; - // There needs to be noteworthy application use data. if (!FileType.ISO9660.NoteworthyApplicationUse(pvd)) From 696016be18c3a066fb00514be4af3f9cf4875f0e Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sun, 2 Nov 2025 04:54:31 -0500 Subject: [PATCH 27/36] Future todo --- BinaryObjectScanner/Protection/SecuROM.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs index 1b7236f1..47ee0e3b 100644 --- a/BinaryObjectScanner/Protection/SecuROM.cs +++ b/BinaryObjectScanner/Protection/SecuROM.cs @@ -378,6 +378,7 @@ public List CheckDirectoryPath(string path, List? files) 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; From e50ea7947b8bad40046c0a49170092ff4fe1abc8 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 6 Nov 2025 08:22:09 -0500 Subject: [PATCH 28/36] Update packages --- BinaryObjectScanner.Test/BinaryObjectScanner.Test.csproj | 6 +++--- BinaryObjectScanner/BinaryObjectScanner.csproj | 6 +++--- ProtectionScan/ProtectionScan.csproj | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) 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/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/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 From 852015c1e9f35e1d395c45e58a70909f901b8611 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 6 Nov 2025 13:28:38 -0500 Subject: [PATCH 29/36] Rebase --- BinaryObjectScanner/Protection/SecuROM.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs index 47ee0e3b..cf94b332 100644 --- a/BinaryObjectScanner/Protection/SecuROM.cs +++ b/BinaryObjectScanner/Protection/SecuROM.cs @@ -324,7 +324,7 @@ public List CheckDirectoryPath(string path, List? files) #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. + // 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 From dfba72645f6b55cb25b4cf2684751b8871684cdc Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 6 Nov 2025 13:49:35 -0500 Subject: [PATCH 30/36] Fix runisochecks --- BinaryObjectScanner/FileType/DiskImage.cs | 7 +++---- BinaryObjectScanner/FileType/ISO9660.cs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/BinaryObjectScanner/FileType/DiskImage.cs b/BinaryObjectScanner/FileType/DiskImage.cs index 459042f0..d789ecff 100644 --- a/BinaryObjectScanner/FileType/DiskImage.cs +++ b/BinaryObjectScanner/FileType/DiskImage.cs @@ -18,16 +18,15 @@ public abstract class DiskImage : DetectableBase 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 - /// ISO to scan /// 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, T iso, U[] checks, bool includeDebug) + protected IDictionary RunISOChecks(string file, U[] checks, bool includeDebug) where U : IISOCheck { // Create the output dictionary @@ -37,7 +36,7 @@ protected IDictionary RunISOChecks(string file, T iso, U[] checks, checks.IterateWithAction(checkClass => { // Get the protection for the class, if possible - var protection = checkClass.CheckISO(file, iso, includeDebug); + var protection = checkClass.CheckISO(file, _wrapper, includeDebug); if (string.IsNullOrEmpty(protection)) return; diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index 98039d3e..1e4c26d0 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -25,7 +25,7 @@ public ISO9660(SabreTools.Serialization.Wrappers.ISO9660 wrapper) : base(wrapper // Standard checks var subProtections - = RunISOChecks(file, _wrapper, StaticChecks.ISO9660CheckClasses, includeDebug); + = RunISOChecks(file, StaticChecks.ISO9660CheckClasses, includeDebug); protections.Append(file, subProtections.Values); // If there are no protections From 50acaa80ad06583a544c86c77901090d24123f3a Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 6 Nov 2025 15:11:54 -0500 Subject: [PATCH 31/36] First round of fixes --- BinaryObjectScanner/Data/StaticChecks.cs | 6 ++-- BinaryObjectScanner/FileType/DiskImage.cs | 6 ++-- BinaryObjectScanner/FileType/ISO9660.cs | 31 +++++++++++-------- .../{IISOCheck.cs => IDiskImageCheck.cs} | 8 ++--- BinaryObjectScanner/Protection/AlphaROM.cs | 11 +++---- BinaryObjectScanner/Protection/CopyLok.cs | 30 +++++++++--------- BinaryObjectScanner/Protection/LaserLok.cs | 10 +++--- BinaryObjectScanner/Protection/Macrovision.cs | 14 ++++----- BinaryObjectScanner/Protection/ProtectDISC.cs | 8 ++--- BinaryObjectScanner/Protection/SecuROM.cs | 30 +++++++++--------- BinaryObjectScanner/Protection/StarForce.cs | 10 +++--- BinaryObjectScanner/Protection/Tages.cs | 10 +++--- 12 files changed, 89 insertions(+), 85 deletions(-) rename BinaryObjectScanner/Interfaces/{IISOCheck.cs => IDiskImageCheck.cs} (68%) diff --git a/BinaryObjectScanner/Data/StaticChecks.cs b/BinaryObjectScanner/Data/StaticChecks.cs index a6b02d1c..2368ef25 100644 --- a/BinaryObjectScanner/Data/StaticChecks.cs +++ b/BinaryObjectScanner/Data/StaticChecks.cs @@ -25,11 +25,11 @@ public static IContentCheck[] ContentCheckClasses /// /// Cache for all IISOCheck types /// - public static IISOCheck[] ISO9660CheckClasses + public static IDiskImageCheck[] ISO9660CheckClasses { get { - iso9660CheckClasses ??= InitCheckClasses>(); + iso9660CheckClasses ??= InitCheckClasses>(); return iso9660CheckClasses; } } @@ -107,7 +107,7 @@ public static IExecutableCheck[] PortableExecutableCheckClas /// /// Cache for all IISOCheck types /// - private static IISOCheck[]? iso9660CheckClasses; + private static IDiskImageCheck[]? iso9660CheckClasses; /// /// Cache for all IExecutableCheck types diff --git a/BinaryObjectScanner/FileType/DiskImage.cs b/BinaryObjectScanner/FileType/DiskImage.cs index d789ecff..9f701b4a 100644 --- a/BinaryObjectScanner/FileType/DiskImage.cs +++ b/BinaryObjectScanner/FileType/DiskImage.cs @@ -9,7 +9,7 @@ namespace BinaryObjectScanner.FileType { /// - /// .iso file + /// Disk image file /// public abstract class DiskImage : DetectableBase where T : WrapperBase @@ -27,7 +27,7 @@ public DiskImage(T wrapper) : base(wrapper) { } /// 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 : IISOCheck + where U : IDiskImageCheck { // Create the output dictionary var protections = new CheckDictionary(); @@ -36,7 +36,7 @@ protected IDictionary RunISOChecks(string file, U[] checks, bool i checks.IterateWithAction(checkClass => { // Get the protection for the class, if possible - var protection = checkClass.CheckISO(file, _wrapper, includeDebug); + var protection = checkClass.CheckDiskImage(file, _wrapper, includeDebug); if (string.IsNullOrEmpty(protection)) return; diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index 1e4c26d0..188c0ec0 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -42,20 +42,25 @@ var subProtections return string.Join(";", [.. protectionList]); } - // 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 (++index >= containedZeroes.Length) return false; } else - index = 0; + { + index = 0; + } } // Checks if there are strings in the data @@ -63,7 +68,7 @@ public static bool IsPureData(byte[] bytes) // 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 -'!?,.]"); + Regex rgx = new Regex("[^a-zA-Z0-9 -'!,.]"); foreach (string str in strings) { if (rgx.Replace(str, "").Length > 7) @@ -77,22 +82,22 @@ public static bool IsPureData(byte[] bytes) // Checks whether the Application Use data is "noteworthy" enough to be worth checking for protection. public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) { - int offset = 0; var applicationUse = pvd.ApplicationUse; - var noteworthyApplicationUse = true; if (Array.TrueForAll(applicationUse, b => b == 0x00)) - noteworthyApplicationUse = false; + 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")) - noteworthyApplicationUse = false; + return false; else if (potentialAppUseString.StartsWith("ULTRAISO")) - noteworthyApplicationUse = false; + return false; else if (potentialAppUseString.StartsWith("Rimage")) - noteworthyApplicationUse = false; + return false; else if (Array.TrueForAll(Encoding.ASCII.GetBytes(potentialAppUseString), b => b == 0x20)) - noteworthyApplicationUse = false; + 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 "/" @@ -102,9 +107,9 @@ public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) offset = 141; potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset); if (potentialAppUseString == "CD-XA001") - noteworthyApplicationUse = false; + return false; - return noteworthyApplicationUse; + return true; } // Checks whether the Reserved 653 Bytes are "noteworthy" enough to be worth checking for protection. diff --git a/BinaryObjectScanner/Interfaces/IISOCheck.cs b/BinaryObjectScanner/Interfaces/IDiskImageCheck.cs similarity index 68% rename from BinaryObjectScanner/Interfaces/IISOCheck.cs rename to BinaryObjectScanner/Interfaces/IDiskImageCheck.cs index 0db7f334..6488a4ee 100644 --- a/BinaryObjectScanner/Interfaces/IISOCheck.cs +++ b/BinaryObjectScanner/Interfaces/IDiskImageCheck.cs @@ -3,17 +3,17 @@ namespace BinaryObjectScanner.Interfaces { /// - /// Check an ISO for protection + /// Check a disk image for protection /// - public interface IISOCheck where T : WrapperBase + public interface IDiskImageCheck where T : WrapperBase { /// /// Check a path for protections based on file contents /// /// File to check for protection indicators - /// ISO representing the read-in file + /// /// True to include debug data, false otherwise /// String containing any protections found in the file - string? CheckISO(string file, T iso, bool includeDebug); + 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 39731fac..7e95bba8 100644 --- a/BinaryObjectScanner/Protection/AlphaROM.cs +++ b/BinaryObjectScanner/Protection/AlphaROM.cs @@ -43,7 +43,7 @@ namespace BinaryObjectScanner.Protection // - SETTEC0000SETTEC1111 // - SOFTWARE\SETTEC // TODO: Are there version numbers? - public class AlphaROM : IExecutableCheck, IISOCheck + public class AlphaROM : IExecutableCheck, IDiskImageCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -88,16 +88,15 @@ public class AlphaROM : IExecutableCheck, IISOCheck return null; } - - public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + /// + 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 (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + 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 @@ -116,7 +115,7 @@ public class AlphaROM : IExecutableCheck, IISOCheck // UTF, Shift-JIS, and EUC-JP all fail to display anything but garbage. var publisherIdentifier = pvd.PublisherIdentifier; - var firstSpace = Array.FindIndex(publisherIdentifier, b => b == 0x20); + int firstSpace = Array.FindIndex(publisherIdentifier, b => b == 0x20); if (firstSpace <= 10 || firstSpace >= 120) return null; diff --git a/BinaryObjectScanner/Protection/CopyLok.cs b/BinaryObjectScanner/Protection/CopyLok.cs index aa1fb16b..38dc67da 100644 --- a/BinaryObjectScanner/Protection/CopyLok.cs +++ b/BinaryObjectScanner/Protection/CopyLok.cs @@ -22,7 +22,7 @@ namespace BinaryObjectScanner.Protection /// /// COPYLOK trademark: https://www.trademarkelite.com/europe/trademark/trademark-detail/000618512/COPYLOK. /// - public class CopyLok : IExecutableCheck, IISOCheck + public class CopyLok : IExecutableCheck, IDiskImageCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -36,12 +36,12 @@ public class CopyLok : IExecutableCheck, IISOCheck return null; } - - public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + /// + public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { #region Initial Checks - if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; @@ -58,16 +58,16 @@ public class CopyLok : IExecutableCheck, IISOCheck #region Read Application Use var applicationUse = pvd.ApplicationUse; - var constantValueOne = applicationUse.ReadUInt32LittleEndian(ref offset); - var smallSizeBytes = applicationUse.ReadUInt16LittleEndian(ref offset); - var constantValueTwo = applicationUse.ReadUInt16LittleEndian(ref offset); - var finalSectionOneBytes = applicationUse.ReadUInt32LittleEndian(ref offset); - var zeroByte = applicationUse.ReadByte(ref offset); - var earlyCopyLokBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset); - var pairBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset); - var oneValueBytes = applicationUse.ReadUInt32LittleEndian(ref offset); - var earlyCopyLokBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset); - var pairBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset); + 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 @@ -86,7 +86,7 @@ public class CopyLok : IExecutableCheck, IISOCheck if (earlyCopyLokBytesOne == 0x00) { // Redump ID 35908, 56433, 44526 - if (0 == pairBytesOne && 0 == oneValueBytes && 0 == earlyCopyLokBytesTwo && 0 == pairBytesTwo) + 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!"; diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index 3277b76e..c27aeef1 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -11,7 +11,7 @@ namespace BinaryObjectScanner.Protection { - public class LaserLok : IExecutableCheck, IPathCheck, IISOCheck + public class LaserLok : IExecutableCheck, IPathCheck, IDiskImageCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -150,12 +150,12 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } - - public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + /// + public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { #region Initial Checks - if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; @@ -168,7 +168,7 @@ public List CheckDirectoryPath(string path, List? files) #endregion var reserved653Bytes = pvd.Reserved653Bytes; - var firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0); + int firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0); string? finalString = reserved653Bytes.ReadNullTerminatedAnsiString(ref firstNonZero); if (finalString == null) return null; diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index 77d3582c..ba37ab01 100644 --- a/BinaryObjectScanner/Protection/Macrovision.cs +++ b/BinaryObjectScanner/Protection/Macrovision.cs @@ -17,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, IISOCheck + public partial class Macrovision : IExecutableCheck, IExecutableCheck, IPathCheck, IDiskImageCheck { /// public string? CheckExecutable(string file, NewExecutable exe, bool includeDebug) @@ -243,12 +243,12 @@ public List CheckDirectoryPath(string path, List? files) return null; } - - public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + /// + public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { #region Initial Checks - if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; @@ -272,9 +272,9 @@ public List CheckDirectoryPath(string path, List? files) 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. - var appUseUintOne = applicationUse.ReadUInt16LittleEndian(ref offset); + ushort appUseUshort = applicationUse.ReadUInt16LittleEndian(ref offset); var appUseDataBytesTwo = applicationUse.ReadBytes(ref offset, 20); - var appUseUintTwo = applicationUse.ReadUInt32LittleEndian(ref offset); + uint appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset); var appUseDataBytesThree = applicationUse.ReadBytes(ref offset, 38); #endregion @@ -305,7 +305,7 @@ public List CheckDirectoryPath(string path, List? files) // 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 (appUseUintOne > 0xFF || appUseUintTwo > 0xFF) + if (appUseUshort > 0xFF || appUseUint > 0xFF) return null; return "SafeDisc"; diff --git a/BinaryObjectScanner/Protection/ProtectDISC.cs b/BinaryObjectScanner/Protection/ProtectDISC.cs index ecf7168b..ecd9b122 100644 --- a/BinaryObjectScanner/Protection/ProtectDISC.cs +++ b/BinaryObjectScanner/Protection/ProtectDISC.cs @@ -13,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, IISOCheck + public class ProtectDISC : IExecutableCheck, IPathCheck, IDiskImageCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -134,14 +134,14 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } - - public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + /// + 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 (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; int offset = 0; diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs index cf94b332..ee18bf89 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, IISOCheck + public class SecuROM : IExecutableCheck, IPathCheck, IDiskImageCheck { /// /// Matches hash of the Release Control-encrypted executable to known hashes @@ -253,12 +253,12 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } - - public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + /// + public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { #region Initial Checks - if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; // Application Use is too inconsistent to include or exclude @@ -279,7 +279,7 @@ public List CheckDirectoryPath(string path, List? files) // Either there's nothing of note, or it's empty other than a 4-byte value at the start. if (FileType.ISO9660.NoteworthyApplicationUse(pvd)) { - var appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset); + uint appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset); var appUseZeroBytes = applicationUse.ReadBytes(ref offset, 508); if (appUseUint == 0 || !Array.TrueForAll(appUseZeroBytes, b => b == 0x00)) @@ -293,22 +293,22 @@ public List CheckDirectoryPath(string path, List? files) #region Read Reserved 653 Bytes var reservedZeroBytesOne = reserved653Bytes.ReadBytes(ref offset, 489); - var reservedHundredValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + uint reservedHundredValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); var reserveDataBytesOne = reserved653Bytes.ReadBytes(ref offset, 80); var reservedZeroBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12); - var reservedUintOne = reserved653Bytes.ReadUInt32LittleEndian(ref offset); - var reservedUintTwoLow = reserved653Bytes.ReadUInt32LittleEndian(ref offset); // Low value + uint reservedUintOne = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + uint reservedUintTwoLow = reserved653Bytes.ReadUInt32LittleEndian(ref offset); // Low value var reservedZeroBytesThree = reserved653Bytes.ReadBytes(ref offset, 4); - var reservedUintThree = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + uint reservedUintThree = reserved653Bytes.ReadUInt32LittleEndian(ref offset); var reservedZeroBytesFour = reserved653Bytes.ReadBytes(ref offset, 12); - var reservedUintFour = reserved653Bytes.ReadUInt32LittleEndian(ref offset); - var reservedOneValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + 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); - var reservedLowByteValueOne = reserved653Bytes.ReadByteValue(ref offset); - var reservedLowByteValueTwo = reserved653Bytes.ReadByteValue(ref offset); - var reservedLowByteValueThree = reserved653Bytes.ReadByteValue(ref offset); - var reservedLowByteValueFour = reserved653Bytes.ReadByteValue(ref offset); + 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 diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index 387bfd64..1a1b78fe 100644 --- a/BinaryObjectScanner/Protection/StarForce.cs +++ b/BinaryObjectScanner/Protection/StarForce.cs @@ -10,7 +10,7 @@ namespace BinaryObjectScanner.Protection { - public class StarForce : IExecutableCheck, IPathCheck, IISOCheck + public class StarForce : IExecutableCheck, IPathCheck, IDiskImageCheck { // TODO: Bring up to par with PiD. // Known issues: @@ -162,10 +162,10 @@ 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? CheckISO(string file, ISO9660 iso, bool includeDebug) + /// + public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { - if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; @@ -181,7 +181,7 @@ public List CheckDirectoryPath(string path, List? files) int offset = 0; var reserved653Bytes = pvd.Reserved653Bytes; - var initialValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset); + 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 diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs index fca4f8d4..1943c722 100644 --- a/BinaryObjectScanner/Protection/Tages.cs +++ b/BinaryObjectScanner/Protection/Tages.cs @@ -10,7 +10,7 @@ namespace BinaryObjectScanner.Protection { - public class TAGES : IExecutableCheck, IPathCheck, IISOCheck + public class TAGES : IExecutableCheck, IPathCheck, IDiskImageCheck { /// public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug) @@ -212,12 +212,12 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } - - public string? CheckISO(string file, ISO9660 iso, bool includeDebug) + /// + public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { #region Initial Checks - if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; // There needs to be noteworthy application use data. @@ -245,7 +245,7 @@ public List CheckDirectoryPath(string path, List? files) // Early tages has a 4-byte value at the beginning of the AU data and nothing else. // Redump ID 8776, 21321, 35932 offset = 0; - var earlyTagesBytes = applicationUse.ReadInt32LittleEndian(ref offset); + 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)"; From 8dc43b94e2cbd2e09a9b80a4269ba423e262ecb5 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 6 Nov 2025 15:23:15 -0500 Subject: [PATCH 32/36] Second round of fixes --- BinaryObjectScanner/FileType/ISO9660.cs | 8 ++++++-- BinaryObjectScanner/Protection/AlphaROM.cs | 3 +-- BinaryObjectScanner/Protection/CopyLok.cs | 1 + BinaryObjectScanner/Protection/LaserLok.cs | 4 ++++ BinaryObjectScanner/Protection/Macrovision.cs | 1 + BinaryObjectScanner/Protection/ProtectDISC.cs | 1 + BinaryObjectScanner/Protection/SecuROM.cs | 1 + BinaryObjectScanner/Protection/StarForce.cs | 1 + BinaryObjectScanner/Protection/Tages.cs | 1 + 9 files changed, 17 insertions(+), 4 deletions(-) diff --git a/BinaryObjectScanner/FileType/ISO9660.cs b/BinaryObjectScanner/FileType/ISO9660.cs index 188c0ec0..1160da73 100644 --- a/BinaryObjectScanner/FileType/ISO9660.cs +++ b/BinaryObjectScanner/FileType/ISO9660.cs @@ -79,7 +79,9 @@ public static bool IsPureData(byte[] bytes) } // TODO: can these 2 "noteworthy" functions be cached? - // Checks whether the Application Use data is "noteworthy" enough to be worth checking for protection. + /// + /// 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; @@ -112,7 +114,9 @@ public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd) return true; } - // Checks whether the Reserved 653 Bytes are "noteworthy" enough to be worth checking for protection. + /// + /// 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; diff --git a/BinaryObjectScanner/Protection/AlphaROM.cs b/BinaryObjectScanner/Protection/AlphaROM.cs index 7e95bba8..691f7e44 100644 --- a/BinaryObjectScanner/Protection/AlphaROM.cs +++ b/BinaryObjectScanner/Protection/AlphaROM.cs @@ -88,6 +88,7 @@ public class AlphaROM : IExecutableCheck, IDiskImageCheck public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { @@ -108,8 +109,6 @@ public class AlphaROM : IExecutableCheck, IDiskImageCheck, IDiskImageCheck public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { diff --git a/BinaryObjectScanner/Protection/LaserLok.cs b/BinaryObjectScanner/Protection/LaserLok.cs index c27aeef1..f66bb10f 100644 --- a/BinaryObjectScanner/Protection/LaserLok.cs +++ b/BinaryObjectScanner/Protection/LaserLok.cs @@ -150,6 +150,7 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } + /// public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { @@ -169,6 +170,9 @@ public List CheckDirectoryPath(string path, List? files) 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; diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index ba37ab01..ac76bc56 100644 --- a/BinaryObjectScanner/Protection/Macrovision.cs +++ b/BinaryObjectScanner/Protection/Macrovision.cs @@ -243,6 +243,7 @@ public List CheckDirectoryPath(string path, List? files) return null; } + /// public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { diff --git a/BinaryObjectScanner/Protection/ProtectDISC.cs b/BinaryObjectScanner/Protection/ProtectDISC.cs index ecd9b122..e3359d1c 100644 --- a/BinaryObjectScanner/Protection/ProtectDISC.cs +++ b/BinaryObjectScanner/Protection/ProtectDISC.cs @@ -134,6 +134,7 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } + /// public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs index ee18bf89..518d787d 100644 --- a/BinaryObjectScanner/Protection/SecuROM.cs +++ b/BinaryObjectScanner/Protection/SecuROM.cs @@ -253,6 +253,7 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } + /// public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index 1a1b78fe..0560a0e7 100644 --- a/BinaryObjectScanner/Protection/StarForce.cs +++ b/BinaryObjectScanner/Protection/StarForce.cs @@ -162,6 +162,7 @@ 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) { diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs index 1943c722..ff028f62 100644 --- a/BinaryObjectScanner/Protection/Tages.cs +++ b/BinaryObjectScanner/Protection/Tages.cs @@ -212,6 +212,7 @@ public List CheckDirectoryPath(string path, List? files) return MatchUtil.GetFirstMatch(path, matchers, any: true); } + /// public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { From cbdd32a160e2abb7e4790680feabd61fd09bf7c6 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 6 Nov 2025 15:41:04 -0500 Subject: [PATCH 33/36] Tests attempt 1 --- .../FileType/DiskImageTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 BinaryObjectScanner.Test/FileType/DiskImageTests.cs 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 From c6947bbe602fb49b97fb82b6c2d253c3b1d3c41d Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 6 Nov 2025 15:52:27 -0500 Subject: [PATCH 34/36] Make checks work --- BinaryObjectScanner/Protection/AlphaROM.cs | 3 +++ BinaryObjectScanner/Protection/CopyLok.cs | 3 +++ BinaryObjectScanner/Protection/LaserLok.cs | 3 +++ BinaryObjectScanner/Protection/Macrovision.cs | 3 +++ BinaryObjectScanner/Protection/ProtectDISC.cs | 3 +++ BinaryObjectScanner/Protection/SecuROM.cs | 3 +++ BinaryObjectScanner/Protection/StarForce.cs | 4 +++- BinaryObjectScanner/Protection/Tages.cs | 3 +++ 8 files changed, 24 insertions(+), 1 deletion(-) diff --git a/BinaryObjectScanner/Protection/AlphaROM.cs b/BinaryObjectScanner/Protection/AlphaROM.cs index 691f7e44..df20d4ae 100644 --- a/BinaryObjectScanner/Protection/AlphaROM.cs +++ b/BinaryObjectScanner/Protection/AlphaROM.cs @@ -95,6 +95,9 @@ public class AlphaROM : IExecutableCheck, IDiskImageCheck, IDiskImageCheck CheckDirectoryPath(string path, List? files) { #region Initial Checks + if (diskImage.VolumeDescriptorSet.Length == 0) + return null; + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; diff --git a/BinaryObjectScanner/Protection/Macrovision.cs b/BinaryObjectScanner/Protection/Macrovision.cs index ac76bc56..cea646ed 100644 --- a/BinaryObjectScanner/Protection/Macrovision.cs +++ b/BinaryObjectScanner/Protection/Macrovision.cs @@ -249,6 +249,9 @@ public List CheckDirectoryPath(string path, List? files) { #region Initial Checks + if (diskImage.VolumeDescriptorSet.Length == 0) + return null; + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; diff --git a/BinaryObjectScanner/Protection/ProtectDISC.cs b/BinaryObjectScanner/Protection/ProtectDISC.cs index e3359d1c..a6a591cf 100644 --- a/BinaryObjectScanner/Protection/ProtectDISC.cs +++ b/BinaryObjectScanner/Protection/ProtectDISC.cs @@ -142,6 +142,9 @@ public List CheckDirectoryPath(string path, List? files) // 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; diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs index 518d787d..952d29f7 100644 --- a/BinaryObjectScanner/Protection/SecuROM.cs +++ b/BinaryObjectScanner/Protection/SecuROM.cs @@ -259,6 +259,9 @@ public List CheckDirectoryPath(string path, List? files) { #region Initial Checks + if (diskImage.VolumeDescriptorSet.Length == 0) + return null; + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; diff --git a/BinaryObjectScanner/Protection/StarForce.cs b/BinaryObjectScanner/Protection/StarForce.cs index 0560a0e7..557581ae 100644 --- a/BinaryObjectScanner/Protection/StarForce.cs +++ b/BinaryObjectScanner/Protection/StarForce.cs @@ -166,9 +166,11 @@ public List CheckDirectoryPath(string path, List? files) /// public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug) { - if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) + 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 diff --git a/BinaryObjectScanner/Protection/Tages.cs b/BinaryObjectScanner/Protection/Tages.cs index ff028f62..9231d2f4 100644 --- a/BinaryObjectScanner/Protection/Tages.cs +++ b/BinaryObjectScanner/Protection/Tages.cs @@ -218,6 +218,9 @@ public List CheckDirectoryPath(string path, List? files) { #region Initial Checks + if (diskImage.VolumeDescriptorSet.Length == 0) + return null; + if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd) return null; From c48f373331e024a80033f585337d3fb40de0acc8 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 6 Nov 2025 16:14:03 -0500 Subject: [PATCH 35/36] Individual test attempt 1 --- .../Protection/AlphaROMTests.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 From a4bd666a17afe487a1b6b94b2f1f2d4194325dae Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Thu, 6 Nov 2025 16:18:30 -0500 Subject: [PATCH 36/36] Final tests --- BinaryObjectScanner.Test/Protection/CopyLokTests.cs | 13 +++++++++++++ .../Protection/LaserLokTests.cs | 13 +++++++++++++ .../Protection/MacrovisionTests.cs | 13 +++++++++++++ .../Protection/ProtectDiscTests.cs | 13 +++++++++++++ BinaryObjectScanner.Test/Protection/SecuROMTests.cs | 13 +++++++++++++ .../Protection/StarForceTests.cs | 13 +++++++++++++ BinaryObjectScanner.Test/Protection/TAGESTests.cs | 13 +++++++++++++ 7 files changed, 91 insertions(+) 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