This commit is contained in:
Matt Nadareski
2025-11-07 07:40:33 -05:00
parent a281e8325e
commit d2eebd0f00
13 changed files with 580 additions and 606 deletions

View File

@@ -23,7 +23,7 @@ namespace BinaryObjectScanner.Data
}
/// <summary>
/// Cache for all IISOCheck<ISO> types
/// Cache for all IDiskImageCheck<ISO9660> types
/// </summary>
public static IDiskImageCheck<ISO9660>[] ISO9660CheckClasses
{
@@ -103,7 +103,6 @@ namespace BinaryObjectScanner.Data
/// </summary>
private static IContentCheck[]? contentCheckClasses;
/// <summary>
/// Cache for all IISOCheck<ISO9660> types
/// </summary>

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using BinaryObjectScanner.Data;
using BinaryObjectScanner.Interfaces;
using SabreTools.IO.Extensions;
@@ -20,13 +18,13 @@ namespace BinaryObjectScanner.FileType
#region Check Runners
/// <summary>
/// Handle a single file based on all ISO check implementations
/// Handle a single file based on all disk image check implementations
/// </summary>
/// <param name="file">Name of the source file of the ISO, for tracking</param>
/// <param name="file">Name of the source file of the disk image, for tracking</param>
/// <param name="checks">Set of checks to use</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Set of protections in file, empty on error</returns>
protected IDictionary<U, string> RunISOChecks<U>(string file, U[] checks, bool includeDebug)
protected IDictionary<U, string> RunDiskImageChecks<U>(string file, U[] checks, bool includeDebug)
where U : IDiskImageCheck<T>
{
// Create the output dictionary

View File

@@ -25,7 +25,7 @@ namespace BinaryObjectScanner.FileType
// Standard checks
var subProtections
= RunISOChecks(file, StaticChecks.ISO9660CheckClasses, includeDebug);
= RunDiskImageChecks(file, StaticChecks.ISO9660CheckClasses, includeDebug);
protections.Append(file, subProtections.Values);
// If there are no protections
@@ -43,18 +43,18 @@ namespace BinaryObjectScanner.FileType
}
/// <summary>
/// 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)
/// </summary>
public static bool IsPureData(byte[] bytes)
{
// Check if there are three 0x00s in a row. Two seems like pushing it
byte[] containedZeroes = {0x00, 0x00, 0x00};
int index = 0;
for (int i = 0; i < bytes.Length; ++i)
{
if (bytes[i] == containedZeroes[index])
if (bytes[i] == 0x00)
{
if (++index >= containedZeroes.Length)
if (++index >= 3)
return false;
}
else
@@ -68,7 +68,7 @@ namespace BinaryObjectScanner.FileType
// Currently-found worst cases:
// "Y:1BY:1BC" in Redump ID 23339
var strings = bytes.ReadStringsWithEncoding(charLimit: 7, Encoding.ASCII);
Regex rgx = new Regex("[^a-zA-Z0-9 -'!,.]");
var rgx = new Regex("[^a-zA-Z0-9 -'!,.]");
foreach (string str in strings)
{
if (rgx.Replace(str, "").Length > 7)
@@ -78,10 +78,10 @@ namespace BinaryObjectScanner.FileType
return true;
}
// TODO: can these 2 "noteworthy" functions be cached?
/// <summary>
/// Checks whether the Application Use data is "noteworthy" enough to be worth checking for protection.
/// </summary>
/// TODO: can these 2 "noteworthy" functions be cached?
public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd)
{
var applicationUse = pvd.ApplicationUse;
@@ -100,6 +100,7 @@ namespace BinaryObjectScanner.FileType
return false;
else if (Array.TrueForAll(Encoding.ASCII.GetBytes(potentialAppUseString), b => b == 0x20))
return false;
// TODO: Unhandled "norb" mastering that puts stuff everywhere, inconsistently. See RID 103641
// More things will have to go here as more disc authoring softwares are found that do this.
// Redump ID 24478 has a bunch of 0x20 with norb in the middle, some discs have 0x20 that ends in a "/"
@@ -123,6 +124,7 @@ namespace BinaryObjectScanner.FileType
var noteworthyReserved653Bytes = true;
if (Array.TrueForAll(reserved653Bytes, b => b == 0x00))
noteworthyReserved653Bytes = false;
// Unsure if more will be needed
return noteworthyReserved653Bytes;
}

View File

@@ -11,7 +11,7 @@ namespace BinaryObjectScanner.Interfaces
/// Check a path for protections based on file contents
/// </summary>
/// <param name="file">File to check for protection indicators</param>
/// <param name="diskImage"></param>
/// <param name="diskImage">Disk image representing the read-in file</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>String containing any protections found in the file</returns>
string? CheckDiskImage(string file, T diskImage, bool includeDebug);

View File

@@ -43,8 +43,53 @@ namespace BinaryObjectScanner.Protection
// - SETTEC0000SETTEC1111
// - SOFTWARE\SETTEC
// TODO: Are there version numbers?
public class AlphaROM : IExecutableCheck<PortableExecutable>, IDiskImageCheck<ISO9660>
public class AlphaROM : IDiskImageCheck<ISO9660>, IExecutableCheck<PortableExecutable>
{
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
// Checks can be made even easier once UDF support exists, as most (although not all, some early discs like
// redump ID 124111 have no UDF partition) discs have "Settec" slathered over every field UDF lets them.
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
// Alpharom disc check #1: disc has varying (but observed to at least always be larger than 14) length
// string made up of numbers and capital letters.
// TODO: triple-check that length is never below 14
int offset = 0;
var applicationIdentifierString = pvd.ApplicationIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim();
if (applicationIdentifierString == null || applicationIdentifierString.Length < 14)
return null;
if (!Regex.IsMatch(applicationIdentifierString, "^[A-Z0-9]*$"))
return null;
// Alpharom disc check #2: disc has publisher identifier filled with varying amount of data (26-50 bytes
// have been observed) followed by spaces. There's a decent chance this is just a Japanese text string, but
// UTF, Shift-JIS, and EUC-JP all fail to display anything but garbage.
var publisherIdentifier = pvd.PublisherIdentifier;
int firstSpace = Array.FindIndex(publisherIdentifier, b => b == 0x20);
if (firstSpace <= 10 || firstSpace >= 120)
return null;
var publisherData = new byte[firstSpace];
var publisherSpaces = new byte[publisherData.Length - firstSpace];
Array.Copy(publisherIdentifier, 0, publisherData, 0, firstSpace);
Array.Copy(publisherIdentifier, firstSpace, publisherSpaces, 0, publisherData.Length - firstSpace);
if (!Array.TrueForAll(publisherSpaces, b => b == 0x20))
return null;
if (!FileType.ISO9660.IsPureData(publisherData))
return null;
return "AlphaROM";
}
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -88,51 +133,5 @@ namespace BinaryObjectScanner.Protection
return null;
}
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
// Checks can be made even easier once UDF support exists, as most (although not all, some early discs like
// redump ID 124111 have no UDF partition) discs have "Settec" slathered over every field UDF lets them.
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
// Alpharom disc check #1: disc has varying (but observed to at least always be larger than 14) length
// string made up of numbers and capital letters.
// TODO: triple-check that length is never below 14
int offset = 0;
var applicationIdentifierString = pvd.ApplicationIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim();
if (applicationIdentifierString == null || applicationIdentifierString.Length < 14)
return null;
if (!Regex.IsMatch(applicationIdentifierString, "^[A-Z0-9]*$"))
return null;
// Alpharom disc check #2: disc has publisher identifier filled with varying amount of data (26-50 bytes
// have been observed) followed by spaces. There's a decent chance this is just a Japanese text string, but
// UTF, Shift-JIS, and EUC-JP all fail to display anything but garbage.
var publisherIdentifier = pvd.PublisherIdentifier;
int firstSpace = Array.FindIndex(publisherIdentifier, b => b == 0x20);
if (firstSpace <= 10 || firstSpace >= 120)
return null;
var publisherData = new byte[firstSpace];
var publisherSpaces = new byte[publisherData.Length - firstSpace];
Array.Copy(publisherIdentifier, 0, publisherData, 0, firstSpace);
Array.Copy(publisherIdentifier, firstSpace, publisherSpaces, 0, publisherData.Length - firstSpace);
if (!Array.TrueForAll(publisherSpaces, b => b == 0x20))
return null;
if (!FileType.ISO9660.IsPureData(publisherData))
return null;
return "AlphaROM";
}
}
}

View File

@@ -22,41 +22,22 @@ namespace BinaryObjectScanner.Protection
///
/// COPYLOK trademark: https://www.trademarkelite.com/europe/trademark/trademark-detail/000618512/COPYLOK.
/// </summary>
public class CopyLok : IExecutableCheck<PortableExecutable>, IDiskImageCheck<ISO9660>
public class CopyLok : IDiskImageCheck<ISO9660>, IExecutableCheck<PortableExecutable>
{
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
// If there are more than 2 icd-prefixed sections, then we have a match
// Though this is the same name that SafeDisc uses for protected executables, this seems to be a coincidence.
// Found in Redump entries 31557, 31674, 31675, 31708, 38239, 44210, and 53929.
int icdSectionCount = Array.FindAll(exe.SectionNames ?? [], s => s.StartsWith("icd")).Length;
if (icdSectionCount >= 2)
return "CopyLok / CodeLok";
return null;
}
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
#region Initial Checks
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null;
if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
#endregion
int offset = 0;
#region Read Application Use
@@ -122,5 +103,18 @@ namespace BinaryObjectScanner.Protection
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
}
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
// If there are more than 2 icd-prefixed sections, then we have a match
// Though this is the same name that SafeDisc uses for protected executables, this seems to be a coincidence.
// Found in Redump entries 31557, 31674, 31675, 31708, 38239, 44210, and 53929.
int icdSectionCount = Array.FindAll(exe.SectionNames ?? [], s => s.StartsWith("icd")).Length;
if (icdSectionCount >= 2)
return "CopyLok / CodeLok";
return null;
}
}
}

View File

@@ -11,8 +11,44 @@ using SabreTools.Serialization.Wrappers;
namespace BinaryObjectScanner.Protection
{
public class LaserLok : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
public class LaserLok : IDiskImageCheck<ISO9660>, IExecutableCheck<PortableExecutable>, IPathCheck
{
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null; //TODO: this might be too unsafe until more App Use strings are known
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
var reserved653Bytes = pvd.Reserved653Bytes;
int firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0);
if (firstNonZero < 0)
return null;
string? finalString = reserved653Bytes.ReadNullTerminatedAnsiString(ref firstNonZero);
if (finalString == null)
return null;
// Redump ID 113120
if (finalString.StartsWith("MLSLaserlock"))
return "LaserLock";
// Redump ID 38308, 113341
if (finalString.StartsWith("LaserlockECL"))
return "LaserLock Marathon";
// Some discs such as 128068, and also more normal ones, don't seem to have any identifying data.
// TODO: list some normal ones
return null;
}
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -151,48 +187,6 @@ namespace BinaryObjectScanner.Protection
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
#region Initial Checks
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null; //TODO: this might be too unsafe until more App Use strings are known
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
#endregion
var reserved653Bytes = pvd.Reserved653Bytes;
int firstNonZero = Array.FindIndex(reserved653Bytes, b => b != 0);
if (firstNonZero < 0)
return null;
string? finalString = reserved653Bytes.ReadNullTerminatedAnsiString(ref firstNonZero);
if (finalString == null)
return null;
// Redump ID 113120
if (finalString.StartsWith("MLSLaserlock"))
return "LaserLock";
// Redump ID 38308, 113341
if (finalString.StartsWith("LaserlockECL"))
return "LaserLock Marathon";
// Some discs such as 128068, and also more normal ones, don't seem to have any identifying data.
// TODO: list some normal ones
return null;
}
private static string GetBuild(byte[]? sectionContent, bool versionTwo)
{
if (sectionContent == null)

View File

@@ -17,8 +17,74 @@ namespace BinaryObjectScanner.Protection
/// Macrovision Corporation CD-ROM Unauthorized Copying Study: https://web.archive.org/web/20011005161810/http://www.macrovision.com/solutions/software/cdrom/images/Games_CD-ROM_Study.PDF
/// List of trademarks associated with Marovision: https://tmsearch.uspto.gov/bin/showfield?f=toc&state=4804%3Au8wykd.5.1&p_search=searchss&p_L=50&BackReference=&p_plural=yes&p_s_PARA1=&p_tagrepl%7E%3A=PARA1%24LD&expr=PARA1+AND+PARA2&p_s_PARA2=macrovision&p_tagrepl%7E%3A=PARA2%24ALL&p_op_ALL=AND&a_default=search&a_search=Submit+Query&a_search=Submit+Query
/// </summary>
public partial class Macrovision : IExecutableCheck<NewExecutable>, IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
public partial class Macrovision : IDiskImageCheck<ISO9660>, IExecutableCheck<NewExecutable>, IExecutableCheck<PortableExecutable>, IPathCheck
{
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null;
// Early SafeDisc actually doesn't cross into reserved bytes. Regardless, SafeDisc CD is easy enough to
// identify for obvious other reasons, so there's not much point in potentially running into false positives.
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
var applicationUse = pvd.ApplicationUse;
var reserved653Bytes = pvd.Reserved653Bytes;
#region Read Application Use
int offset = 0;
var appUseZeroBytes = applicationUse.ReadBytes(ref offset, 256);
var appUseDataBytesOne = applicationUse.ReadBytes(ref offset, 128);
offset += 64; // Some extra values get added here over time. Check is good enough, easier to skip this.
ushort appUseUshort = applicationUse.ReadUInt16LittleEndian(ref offset);
var appUseDataBytesTwo = applicationUse.ReadBytes(ref offset, 20);
uint appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset);
var appUseDataBytesThree = applicationUse.ReadBytes(ref offset, 38);
#endregion
#region Read Reserved 653 Bytes
offset = 0;
// Somewhat arbitrary, but going further than 11 seems to exclude some discs.
var reservedDataBytes = reserved653Bytes.ReadBytes(ref offset, 10);
offset = 132; // TODO: Does it ever go further than this?
var reservedZeroBytes = reserved653Bytes.ReadBytes(ref offset, 521);
#endregion
// The first 256 bytes of application use, and the last 521 bytes of reserved data, should all be 0x00.
// It's possible reserved might need to be shortened a bit, but a need for that has not been observed yet.
if (!Array.TrueForAll(appUseZeroBytes, b => b == 0x00) || !Array.TrueForAll(reservedZeroBytes, b => b == 0x00))
return null;
// All of these sections should be pure data
if (!FileType.ISO9660.IsPureData(appUseDataBytesOne)
|| !FileType.ISO9660.IsPureData(appUseDataBytesTwo)
|| !FileType.ISO9660.IsPureData(appUseDataBytesThree)
|| !FileType.ISO9660.IsPureData(reservedDataBytes))
{
return null;
}
// appUseFirstUint has only ever been observed as 0xBB, but no need to be this strict yet. Can be checked
// if it's found that it's needed to, and always viable. appUseSecondUint varies more, but is still always
// under 0xFF so far.
if (appUseUshort > 0xFF || appUseUint > 0xFF)
return null;
return "SafeDisc";
}
/// <inheritdoc/>
public string? CheckExecutable(string file, NewExecutable exe, bool includeDebug)
{
@@ -244,77 +310,6 @@ namespace BinaryObjectScanner.Protection
return null;
}
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
#region Initial Checks
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null;
// Early SafeDisc actually doesn't cross into reserved bytes. Regardless, SafeDisc CD is easy enough to
// identify for obvious other reasons, so there's not much point in potentially running into false positives.
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
#endregion
var applicationUse = pvd.ApplicationUse;
var reserved653Bytes = pvd.Reserved653Bytes;
#region Read Application Use
int offset = 0;
var appUseZeroBytes = applicationUse.ReadBytes(ref offset, 256);
var appUseDataBytesOne = applicationUse.ReadBytes(ref offset, 128);
offset += 64; // Some extra values get added here over time. Check is good enough, easier to skip this.
ushort appUseUshort = applicationUse.ReadUInt16LittleEndian(ref offset);
var appUseDataBytesTwo = applicationUse.ReadBytes(ref offset, 20);
uint appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset);
var appUseDataBytesThree = applicationUse.ReadBytes(ref offset, 38);
#endregion
offset = 0;
#region Read Reserved 653 Bytes
// Somewhat arbitrary, but going further than 11 seems to exclude some discs.
var reservedDataBytes = reserved653Bytes.ReadBytes(ref offset, 10);
offset = 132; // TODO: Does it ever go further than this?
var reservedZeroBytes = reserved653Bytes.ReadBytes(ref offset, 521);
#endregion
// The first 256 bytes of application use, and the last 521 bytes of reserved data, should all be 0x00.
// It's possible reserved might need to be shortened a bit, but a need for that has not been observed yet.
if (!Array.TrueForAll(appUseZeroBytes, b => b == 0x00) || !Array.TrueForAll(reservedZeroBytes, b => b == 0x00))
return null;
// All of these sections should be pure data
if (!FileType.ISO9660.IsPureData(appUseDataBytesOne)
|| !FileType.ISO9660.IsPureData(appUseDataBytesTwo)
|| !FileType.ISO9660.IsPureData(appUseDataBytesThree)
|| !FileType.ISO9660.IsPureData(reservedDataBytes))
return null;
// appUseFirstUint has only ever been observed as 0xBB, but no need to be this strict yet. Can be checked
// if it's found that it's needed to, and always viable. appUseSecondUint varies more, but is still always
// under 0xFF so far.
if (appUseUshort > 0xFF || appUseUint > 0xFF)
return null;
return "SafeDisc";
}
/// <inheritdoc cref="IPathCheck.CheckDirectoryPath(string, List{string})"/>
internal static List<string> MacrovisionCheckDirectoryPath(string path, List<string>? files)
{

View File

@@ -13,8 +13,46 @@ namespace BinaryObjectScanner.Protection
{
// This protection was called VOB ProtectCD / ProtectDVD in versions prior to 6
// ProtectDISC 9/10 checks for the presence of CSS on the disc to run, but don't encrypt any sectors or check for keys. Confirmed in Redump entries 78367 and 110095.
public class ProtectDISC : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
public class ProtectDISC : IDiskImageCheck<ISO9660>, IExecutableCheck<PortableExecutable>, IPathCheck
{
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
// If false positives occur on ProtectDiSC for some reason, there's a bit in the reserved bytes that
// can be checked. Not bothering since this doesn't work for ProtectCD/DVD 6.x discs, which use otherwise
// the same check anyways.
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
int offset = 0;
var copyrightString = pvd.CopyrightFileIdentifier.ReadNullTerminatedAnsiString(ref offset);
if (copyrightString == null || copyrightString.Length < 19)
return null;
copyrightString = copyrightString.Substring(0, 19); // Redump ID 15896 has a trailing space
// Stores some kind of serial in the copyright string, format 0000-XXXX-XXXX-XXXX where it can be numbers or
// capital letters.
if (!Regex.IsMatch(copyrightString, "[0]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}"))
return null;
offset = 0;
// Starting with sometime around 7.5, ProtectDiSC includes a version number string here. Examples include
// 7.5.0.61324 and 9.0.1119. ProtectDiSC versioning is very confusing, so this is not the "actual" version
// number and should not be printed.
// Previous versions just have spaces here, so it doesn't need to be validated beyond that.
var abstractIdentifierString = pvd.AbstractFileIdentifier.ReadNullTerminatedAnsiString(ref offset);
if (abstractIdentifierString == null || abstractIdentifierString.Trim().Length == 0)
return "ProtectDiSC 6-Early 7.x";
return "ProtectDiSC Mid-7.x+";
}
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -135,45 +173,6 @@ namespace BinaryObjectScanner.Protection
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
// If false positives occur on ProtectDiSC for some reason, there's a bit in the reserved bytes that
// can be checked. Not bothering since this doesn't work for ProtectCD/DVD 6.x discs, which use otherwise
// the same check anyways.
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
int offset = 0;
var copyrightString = pvd.CopyrightFileIdentifier.ReadNullTerminatedAnsiString(ref offset);
if (copyrightString == null || copyrightString.Length < 19)
return null;
copyrightString = copyrightString.Substring(0, 19); // Redump ID 15896 has a trailing space
// Stores some kind of serial in the copyright string, format 0000-XXXX-XXXX-XXXX where it can be numbers or
// capital letters.
if (!Regex.IsMatch(copyrightString, "[0]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}"))
return null;
offset = 0;
// Starting with sometime around 7.5, ProtectDiSC includes a version number string here. Examples include
// 7.5.0.61324 and 9.0.1119. ProtectDiSC versioning is very confusing, so this is not the "actual" version
// number and should not be printed.
// Previous versions just have spaces here, so it doesn't need to be validated beyond that.
var abstractIdentifierString = pvd.AbstractFileIdentifier.ReadNullTerminatedAnsiString(ref offset);
if (abstractIdentifierString == null || abstractIdentifierString.Trim().Length == 0)
return "ProtectDiSC 6-Early 7.x";
return "ProtectDiSC Mid-7.x+";
}
private static string GetOldVersion(string matchedString)
{
// Remove unnecessary parts

View File

@@ -13,7 +13,7 @@ namespace BinaryObjectScanner.Protection
{
// TODO: Investigate SecuROM for Macintosh
// TODO: Think of a way to detect dfe
public class SecuROM : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
public class SecuROM : IDiskImageCheck<ISO9660>, IExecutableCheck<PortableExecutable>, IPathCheck
{
/// <summary>
/// Matches hash of the Release Control-encrypted executable to known hashes
@@ -103,6 +103,141 @@ namespace BinaryObjectScanner.Protection
{48093043, "deadspace_f.aec"},
};
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
// Application Use is too inconsistent to include or exclude
// There needs to be noteworthy data in the reserved 653 bytes
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
var applicationUse = pvd.ApplicationUse;
var reserved653Bytes = pvd.Reserved653Bytes;
#region Read Application Use
var offset = 0;
// Either there's nothing of note, or it's empty other than a 4-byte value at the start.
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
{
uint appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset);
var appUseZeroBytes = applicationUse.ReadBytes(ref offset, 508);
if (appUseUint == 0 || !Array.TrueForAll(appUseZeroBytes, b => b == 0x00))
return null;
}
#endregion
#region Read Reserved 653 Bytes
offset = 0;
var reservedZeroBytesOne = reserved653Bytes.ReadBytes(ref offset, 489);
uint reservedHundredValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
var reserveDataBytesOne = reserved653Bytes.ReadBytes(ref offset, 80);
var reservedZeroBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12);
uint reservedUintOne = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
uint reservedUintTwoLow = reserved653Bytes.ReadUInt32LittleEndian(ref offset); // Low value
var reservedZeroBytesThree = reserved653Bytes.ReadBytes(ref offset, 4);
uint reservedUintThree = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
var reservedZeroBytesFour = reserved653Bytes.ReadBytes(ref offset, 12);
uint reservedUintFour = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
uint reservedOneValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
var reservedZeroBytesFive = reserved653Bytes.ReadBytes(ref offset, 4);
var reservedDataBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12);
byte reservedLowByteValueOne = reserved653Bytes.ReadByteValue(ref offset);
byte reservedLowByteValueTwo = reserved653Bytes.ReadByteValue(ref offset);
byte reservedLowByteValueThree = reserved653Bytes.ReadByteValue(ref offset);
byte reservedLowByteValueFour = reserved653Bytes.ReadByteValue(ref offset);
var reservedDataBytesThree = reserved653Bytes.ReadBytes(ref offset, 12);
#endregion
// True for all discs
if (!Array.TrueForAll(reservedZeroBytesOne, b => b == 0x00)
|| !Array.TrueForAll(reservedZeroBytesTwo, b => b == 0x00)
|| !Array.TrueForAll(reservedZeroBytesThree, b => b == 0x00)
|| !Array.TrueForAll(reservedZeroBytesFour, b => b == 0x00)
|| !Array.TrueForAll(reservedZeroBytesFive, b => b == 0x00))
{
return null;
}
#region Early SecuROM Checks
// This duplicates a lot of code. This region is like this because it's still possible to detect early vers,
// but it should be easy to remove this section if it turns out this leads to conflicts or false positives
if (Array.TrueForAll(reserveDataBytesOne, b => b == 0x00)
&& Array.TrueForAll(reservedDataBytesTwo, b => b == 0x00)
&& reservedHundredValue == 0 && reservedOneValue == 0
&& reservedUintOne == 0 && reservedUintTwoLow == 0 && reservedUintThree == 0 && reservedUintFour == 0
&& reservedLowByteValueOne == 0 && reservedLowByteValueTwo == 0 && reservedLowByteValueThree == 0)
{
if (FileType.ISO9660.IsPureData(reservedDataBytesThree))
{
if (reservedLowByteValueFour == 0)
return "SecuROM 3.x-4.6x";
else if (reservedLowByteValueFour < 0x20)
return "SecuROM 4.7x-4.8x";
else
return null;
}
offset = 0;
var earlyFirstFourBytes = reservedDataBytesThree.ReadBytes(ref offset, 4);
var earlyLastEightBytes = reservedDataBytesThree.ReadBytes(ref offset, 8);
if (Array.TrueForAll(earlyFirstFourBytes, b => b == 0x00) && FileType.ISO9660.IsPureData(earlyLastEightBytes))
return "SecuROM 2.x-3.x";
}
#endregion
// If this uint32 is 100, the next 80 bytes should be data. Otherwise, both should only ever be zero.
switch (reservedHundredValue)
{
case 0:
if (!Array.TrueForAll(reserveDataBytesOne, b => b == 0x00))
return null;
break;
case 100:
if (!FileType.ISO9660.IsPureData(reserveDataBytesOne))
return null;
break;
default:
return null;
}
//If you go back to early 4.0 CDs, only the above can be guaranteed to pass. CDs can already be identified via normal
//dumping, though, and (as well as most later CDs) should always pass these remaining checks.
if (reservedUintOne < 0xFFFF || reservedUintTwoLow > 0xFFFF || reservedUintThree < 0xFFFF || reservedUintFour < 0xFFFF)
return null;
if (reservedOneValue != 1)
return null;
if (reservedLowByteValueOne > 0x20 || reservedLowByteValueTwo > 0x20 || reservedLowByteValueThree > 0x20 ||
reservedLowByteValueFour > 0x20)
return null;
// TODO: RID 127715 fails this because the first 8 bytes of reservedDataBytesTwo happen to be "afsCafsC"
if (!FileType.ISO9660.IsPureData(reservedDataBytesTwo)
|| !FileType.ISO9660.IsPureData(reservedDataBytesThree))
return null;
return "SecuROM 4.8x+";
}
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -254,142 +389,6 @@ namespace BinaryObjectScanner.Protection
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
#region Initial Checks
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
// Application Use is too inconsistent to include or exclude
// There needs to be noteworthy data in the reserved 653 bytes
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
#endregion
var applicationUse = pvd.ApplicationUse;
var reserved653Bytes = pvd.Reserved653Bytes;
#region Read Application Use
var offset = 0;
// Either there's nothing of note, or it's empty other than a 4-byte value at the start.
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
{
uint appUseUint = applicationUse.ReadUInt32LittleEndian(ref offset);
var appUseZeroBytes = applicationUse.ReadBytes(ref offset, 508);
if (appUseUint == 0 || !Array.TrueForAll(appUseZeroBytes, b => b == 0x00))
return null;
}
#endregion
offset = 0;
#region Read Reserved 653 Bytes
var reservedZeroBytesOne = reserved653Bytes.ReadBytes(ref offset, 489);
uint reservedHundredValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
var reserveDataBytesOne = reserved653Bytes.ReadBytes(ref offset, 80);
var reservedZeroBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12);
uint reservedUintOne = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
uint reservedUintTwoLow = reserved653Bytes.ReadUInt32LittleEndian(ref offset); // Low value
var reservedZeroBytesThree = reserved653Bytes.ReadBytes(ref offset, 4);
uint reservedUintThree = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
var reservedZeroBytesFour = reserved653Bytes.ReadBytes(ref offset, 12);
uint reservedUintFour = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
uint reservedOneValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
var reservedZeroBytesFive = reserved653Bytes.ReadBytes(ref offset, 4);
var reservedDataBytesTwo = reserved653Bytes.ReadBytes(ref offset, 12);
byte reservedLowByteValueOne = reserved653Bytes.ReadByteValue(ref offset);
byte reservedLowByteValueTwo = reserved653Bytes.ReadByteValue(ref offset);
byte reservedLowByteValueThree = reserved653Bytes.ReadByteValue(ref offset);
byte reservedLowByteValueFour = reserved653Bytes.ReadByteValue(ref offset);
var reservedDataBytesThree = reserved653Bytes.ReadBytes(ref offset, 12);
#endregion
// True for all discs
if (!Array.TrueForAll(reservedZeroBytesOne, b => b == 0x00)
|| !Array.TrueForAll(reservedZeroBytesTwo, b => b == 0x00)
|| !Array.TrueForAll(reservedZeroBytesThree, b => b == 0x00)
|| !Array.TrueForAll(reservedZeroBytesFour, b => b == 0x00)
|| !Array.TrueForAll(reservedZeroBytesFive, b => b == 0x00))
return null;
#region Early SecuROM Checks
// This duplicates a lot of code. This region is like this because it's still possible to detect early vers,
// but it should be easy to remove this section if it turns out this leads to conflicts or false positives
if (Array.TrueForAll(reserveDataBytesOne, b => b == 0x00)
&& Array.TrueForAll(reservedDataBytesTwo, b => b == 0x00)
&& reservedHundredValue == 0 && reservedOneValue == 0
&& reservedUintOne == 0 && reservedUintTwoLow == 0 && reservedUintThree == 0 && reservedUintFour == 0
&& reservedLowByteValueOne == 0 && reservedLowByteValueTwo == 0 && reservedLowByteValueThree == 0)
{
offset = 0;
if (FileType.ISO9660.IsPureData(reservedDataBytesThree))
if ( reservedLowByteValueFour == 0)
return "SecuROM 3.x-4.6x";
else if (reservedLowByteValueFour < 0x20)
return "SecuROM 4.7x-4.8x";
else
return null;
var earlyFirstFourBytes = reservedDataBytesThree.ReadBytes(ref offset, 4);
var earlyLastEightBytes = reservedDataBytesThree.ReadBytes(ref offset, 8);
if (Array.TrueForAll(earlyFirstFourBytes, b => b == 0x00) && FileType.ISO9660.IsPureData(earlyLastEightBytes))
return "SecuROM 2.x-3.x";
}
#endregion
// If this uint32 is 100, the next 80 bytes should be data. Otherwise, both should only ever be zero.
switch(reservedHundredValue)
{
case 0:
if (!Array.TrueForAll(reserveDataBytesOne, b => b == 0x00))
return null;
break;
case 100:
if (!FileType.ISO9660.IsPureData(reserveDataBytesOne))
return null;
break;
default:
return null;
}
//If you go back to early 4.0 CDs, only the above can be guaranteed to pass. CDs can already be identified via normal
//dumping, though, and (as well as most later CDs) should always pass these remaining checks.
if (reservedUintOne < 0xFFFF || reservedUintTwoLow > 0xFFFF || reservedUintThree < 0xFFFF || reservedUintFour < 0xFFFF)
return null;
if (reservedOneValue != 1)
return null;
if (reservedLowByteValueOne > 0x20 || reservedLowByteValueTwo > 0x20 || reservedLowByteValueThree > 0x20 ||
reservedLowByteValueFour > 0x20)
return null;
// TODO: RID 127715 fails this because the first 8 bytes of reservedDataBytesTwo happen to be "afsCafsC"
if (!FileType.ISO9660.IsPureData(reservedDataBytesTwo) ||
!FileType.ISO9660.IsPureData(reservedDataBytesThree))
return null;
return "SecuROM 4.8x+";
}
/// <summary>
/// Try to get the SecuROM v4 version from the overlay, if possible
/// </summary>

View File

@@ -10,8 +10,89 @@ using SabreTools.Serialization.Wrappers;
namespace BinaryObjectScanner.Protection
{
public class StarForce : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
public class StarForce : IDiskImageCheck<ISO9660>, IExecutableCheck<PortableExecutable>, IPathCheck
{
/// <summary>
/// If a StarForce Keyless hash is known to not fit the format, but is in some way a one-off.
/// Key is the SF Keyless Key, value is redump ID
/// </summary>
private static readonly Dictionary<string, uint> UnusualStarforceKeylessKeys = new()
{
{"FYFYILOVEYOU", 60270},
};
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
// Starforce Keyless check #1: the reserved 653 bytes start with a 32-bit LE number that's slightly less
// than the length of the volume size space. The difference varies, it's usually around 10. Check 500 to be
// safe. The rest of the data is all 0x00.
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null;
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
int offset = 0;
var reserved653Bytes = pvd.Reserved653Bytes;
uint initialValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
var zeroBytes = reserved653Bytes.ReadBytes(ref offset, 649);
// It's unfortunately not known to be possible to detect many non-keyless StarForce discs, so some will slip
// through here.
if (initialValue > pvd.VolumeSpaceSize || initialValue + 500 < pvd.VolumeSpaceSize || !Array.TrueForAll(zeroBytes, b => b == 0x00))
return null;
offset = 0;
// StarForce Keyless check #2: the key is stored in the Data Preparer identifier.
// It turns out that some (i.e. Redump ID 60266, 72531, 87181, 91734, 106732, 105356, 74578, 78200)
// non-keyless StarForce discs still have this value here? This check may need to be disabled, but it
// seems to avoid any false positives in practice so far.
var dataPreparerIdentiferString = pvd.DataPreparerIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim();
if (dataPreparerIdentiferString == null || dataPreparerIdentiferString.Length == 0)
return "StarForce";
// It is returning the key, as it tells you what set of DPM your disc corresponds to, and it would also
// help show why a disc might be an alt of another disc (there are at least a decent amount of StarForce
// Keyless alts that would amtch otherwise). Unclear if this is desired by the users of BOS or those
// affected by it.
// Thus far, the StarForce Keyless key is always made up of a number of characters, all either capital letters or
// numbers, sometimes with dashes in between. Thus far, 4 formats have been observed:
// XXXXXXXXXXXXXXXXXXXXXXXXX (25 characters)
// XXXXX-XXXXX-XXXXX-XXXXX-XXXXX (25 characters, plus 4 dashes seperating 5 groups of 5)
// XXXXXXXXXXXXXXXXXXXXXXXXXXXX (28 characters)
// XXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX (28 characters, with 4 dashes)
if (Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{25}$")
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$")
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{28}$")
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{4}-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{6}$"))
{
return $"StarForce Keyless - {dataPreparerIdentiferString}";
}
// Redump ID 60270 is a unique case, there could possibly be more.
if (UnusualStarforceKeylessKeys.ContainsKey(dataPreparerIdentiferString))
return $"StarForce Keyless - {dataPreparerIdentiferString}";
// In case any variants were missed.
if (Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9-]*$"))
return $"StarForce Keyless - {dataPreparerIdentiferString} - Unknown variant, please report to us on GitHub!";
// 34206 reaches this because it's not keyless, and has "WinISO software" as the DPI string. However, since
// it has lowercase letters and spaces, it's caught here. It is genuinely StarForce, so it's not a false
// positive.
return "StarForce";
}
// TODO: Bring up to par with PiD.
// Known issues:
// "Game.exe" not detected (Redump entry 96137).
@@ -162,85 +243,5 @@ namespace BinaryObjectScanner.Protection
// TODO: Determine if there are any file name checks that aren't too generic to use on their own.
return null;
}
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
// Starforce Keyless check #1: the reserved 653 bytes start with a 32-bit LE number that's slightly less
// than the length of the volume size space. The difference varies, it's usually around 10. Check 500 to be
// safe. The rest of the data is all 0x00.
if (FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null;
if (!FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
int offset = 0;
var reserved653Bytes = pvd.Reserved653Bytes;
uint initialValue = reserved653Bytes.ReadUInt32LittleEndian(ref offset);
var zeroBytes = reserved653Bytes.ReadBytes(ref offset, 649);
// It's unfortunately not known to be possible to detect many non-keyless StarForce discs, so some will slip
// through here.
if (initialValue > pvd.VolumeSpaceSize || initialValue + 500 < pvd.VolumeSpaceSize || !Array.TrueForAll(zeroBytes, b => b == 0x00))
return null;
offset = 0;
// StarForce Keyless check #2: the key is stored in the Data Preparer identifier.
// It turns out that some (i.e. Redump ID 60266, 72531, 87181, 91734, 106732, 105356, 74578, 78200)
// non-keyless StarForce discs still have this value here? This check may need to be disabled, but it
// seems to avoid any false positives in practice so far.
var dataPreparerIdentiferString = pvd.DataPreparerIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim();
if (dataPreparerIdentiferString == null || dataPreparerIdentiferString.Length == 0)
return "StarForce";
// It is returning the key, as it tells you what set of DPM your disc corresponds to, and it would also
// help show why a disc might be an alt of another disc (there are at least a decent amount of StarForce
// Keyless alts that would amtch otherwise). Unclear if this is desired by the users of BOS or those
// affected by it.
// Thus far, the StarForce Keyless key is always made up of a number of characters, all either capital letters or
// numbers, sometimes with dashes in between. Thus far, 4 formats have been observed:
// XXXXXXXXXXXXXXXXXXXXXXXXX (25 characters)
// XXXXX-XXXXX-XXXXX-XXXXX-XXXXX (25 characters, plus 4 dashes seperating 5 groups of 5)
// XXXXXXXXXXXXXXXXXXXXXXXXXXXX (28 characters)
// XXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX (28 characters, with 4 dashes)
if (Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{25}$")
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$")
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{28}$")
|| Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9]{4}-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{6}$"))
return $"StarForce Keyless - {dataPreparerIdentiferString}";
// Redump ID 60270 is a unique case, there could possibly be more.
if (UnusualStarforceKeylessKeys.ContainsKey(dataPreparerIdentiferString))
return $"StarForce Keyless - {dataPreparerIdentiferString}";
// In case any variants were missed.
if (Regex.IsMatch(dataPreparerIdentiferString, "^[A-Z0-9-]*$"))
return $"StarForce Keyless - {dataPreparerIdentiferString} - Unknown variant, please report to us on GitHub!";
// 34206 reaches this because it's not keyless, and has "WinISO software" as the DPI string. However, since
// it has lowercase letters and spaces, it's caught here. It is genuinely StarForce, so it's not a false
// positive.
return $"StarForce";
}
/// <summary>
/// If a StarForce Keyless hash is known to not fit the format, but is in some way a one-off.
/// Key is the SF Keyless Key, value is redump ID
/// </summary>
private static readonly Dictionary<string, uint> UnusualStarforceKeylessKeys = new()
{
{"FYFYILOVEYOU", 60270},
};
}
}

View File

@@ -10,8 +10,48 @@ using SabreTools.Serialization.Wrappers;
namespace BinaryObjectScanner.Protection
{
public class TAGES : IExecutableCheck<PortableExecutable>, IPathCheck, IDiskImageCheck<ISO9660>
public class TAGES : IDiskImageCheck<ISO9660>, IExecutableCheck<PortableExecutable>, IPathCheck
{
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
// There needs to be noteworthy application use data.
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null;
// There should not be noteworthy data in the reserved 653 bytes
if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
var applicationUse = pvd.ApplicationUse;
// Non-early tages either has all 512 bytes of the AU data full of data, or only the last 128.
if (FileType.ISO9660.IsPureData(applicationUse))
return "TAGES";
int offset = 0;
var optionalZeroBytes = applicationUse.ReadBytes(ref offset, 384);
var tagesBytes = applicationUse.ReadBytes(ref offset, 128);
if (Array.TrueForAll(optionalZeroBytes, b => b == 0x00) && FileType.ISO9660.IsPureData(tagesBytes))
return "TAGES";
// Early tages has a 4-byte value at the beginning of the AU data and nothing else.
// Redump ID 8776, 21321, 35932
offset = 0;
uint earlyTagesBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
var zeroBytes = applicationUse.ReadBytes(ref offset, 508);
if (Array.TrueForAll(zeroBytes, b => b == 0x00) && earlyTagesBytes != 0)
return "TAGES (Early)";
// The original releases of Moto Racer 3 (31578, 34669) are so early they have seemingly nothing identifiable.
return null;
}
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
@@ -213,51 +253,6 @@ namespace BinaryObjectScanner.Protection
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
/// <inheritdoc/>
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
{
#region Initial Checks
if (diskImage.VolumeDescriptorSet.Length == 0)
return null;
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;
// There needs to be noteworthy application use data.
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
return null;
// There should not be noteworthy data in the reserved 653 bytes
if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
return null;
#endregion
var applicationUse = pvd.ApplicationUse;
// Non-early tages either has all 512 bytes of the AU data full of data, or only the last 128.
if (FileType.ISO9660.IsPureData(applicationUse))
return "TAGES";
int offset = 0;
var optionalZeroBytes = applicationUse.ReadBytes(ref offset, 384);
var tagesBytes = applicationUse.ReadBytes(ref offset, 128);
if (Array.TrueForAll(optionalZeroBytes, b => b == 0x00) && FileType.ISO9660.IsPureData(tagesBytes))
return "TAGES";
// Early tages has a 4-byte value at the beginning of the AU data and nothing else.
// Redump ID 8776, 21321, 35932
offset = 0;
uint earlyTagesBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
var zeroBytes = applicationUse.ReadBytes(ref offset, 508);
if (Array.TrueForAll(zeroBytes, b => b == 0x00) && earlyTagesBytes != 0)
return "TAGES (Early)";
// The original releases of Moto Racer 3 (31578, 34669) are so early they have seemingly nothing identifiable.
return null;
}
private static string GetVersion(PortableExecutable exe)
{
// Check the internal versions

View File

@@ -5,7 +5,6 @@ using BinaryObjectScanner.Data;
using BinaryObjectScanner.Interfaces;
using SabreTools.IO.Extensions;
using SabreTools.Serialization;
using SabreTools.Serialization.Interfaces;
using SabreTools.Serialization.Wrappers;
namespace BinaryObjectScanner