mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-02-03 21:29:23 +00:00
Cleanup
This commit is contained in:
@@ -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
|
||||
{
|
||||
@@ -33,7 +33,7 @@ namespace BinaryObjectScanner.Data
|
||||
return iso9660CheckClasses;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Cache for all IExecutableCheck<LinearExecutable> types
|
||||
/// </summary>
|
||||
@@ -103,12 +103,11 @@ namespace BinaryObjectScanner.Data
|
||||
/// </summary>
|
||||
private static IContentCheck[]? contentCheckClasses;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Cache for all IISOCheck<ISO9660> types
|
||||
/// </summary>
|
||||
private static IDiskImageCheck<ISO9660>[]? iso9660CheckClasses;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Cache for all IExecutableCheck<LinearExecutable> types
|
||||
/// </summary>
|
||||
@@ -169,7 +168,7 @@ namespace BinaryObjectScanner.Data
|
||||
if (assemblyTypes.Length == 0)
|
||||
return [];
|
||||
|
||||
// Loop through all types
|
||||
// Loop through all types
|
||||
List<T> classTypes = [];
|
||||
foreach (Type? type in assemblyTypes)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace BinaryObjectScanner.FileType
|
||||
|
||||
// Standard checks
|
||||
var subProtections
|
||||
= RunISOChecks(file, StaticChecks.ISO9660CheckClasses, includeDebug);
|
||||
= RunDiskImageChecks(file, StaticChecks.ISO9660CheckClasses, includeDebug);
|
||||
protections.Append(file, subProtections.Values);
|
||||
|
||||
// If there are no protections
|
||||
@@ -43,51 +43,51 @@ namespace BinaryObjectScanner.FileType
|
||||
}
|
||||
|
||||
/// <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)
|
||||
for (int i = 0; i < bytes.Length; ++i)
|
||||
{
|
||||
if (bytes[i] == containedZeroes[index])
|
||||
if (bytes[i] == 0x00)
|
||||
{
|
||||
if (++index >= containedZeroes.Length)
|
||||
return false;
|
||||
if (++index >= 3)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = 0;
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks if there are strings in the data
|
||||
// TODO: is this too dangerous, or too faulty?
|
||||
// Currently-found worst cases:
|
||||
// "Y:1BY:1BC" in Redump ID 23339
|
||||
var strings = bytes.ReadStringsWithEncoding(charLimit: 7, Encoding.ASCII);
|
||||
Regex rgx = new Regex("[^a-zA-Z0-9 -'!,.]");
|
||||
var rgx = new Regex("[^a-zA-Z0-9 -'!,.]");
|
||||
foreach (string str in strings)
|
||||
{
|
||||
if (rgx.Replace(str, "").Length > 7)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: can these 2 "noteworthy" functions be cached?
|
||||
|
||||
/// <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;
|
||||
if (Array.TrueForAll(applicationUse, b => b == 0x00))
|
||||
return false;
|
||||
|
||||
|
||||
int offset = 0;
|
||||
string? potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset);
|
||||
if (potentialAppUseString != null && potentialAppUseString.Length > 0) // Some image authoring programs add a starting string to AU data
|
||||
@@ -100,20 +100,21 @@ namespace BinaryObjectScanner.FileType
|
||||
return false;
|
||||
else if (Array.TrueForAll(Encoding.ASCII.GetBytes(potentialAppUseString), b => b == 0x20))
|
||||
return false;
|
||||
|
||||
// TODO: Unhandled "norb" mastering that puts stuff everywhere, inconsistently. See RID 103641
|
||||
// More things will have to go here as more disc authoring softwares are found that do this.
|
||||
// Redump ID 24478 has a bunch of 0x20 with norb in the middle, some discs have 0x20 that ends in a "/"
|
||||
// character. If these are found to be causing issues they can be added.
|
||||
}
|
||||
|
||||
|
||||
offset = 141;
|
||||
potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset);
|
||||
if (potentialAppUseString == "CD-XA001")
|
||||
return false;
|
||||
|
||||
if (potentialAppUseString == "CD-XA001")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the Reserved 653 Bytes are "noteworthy" enough to be worth checking for protection.
|
||||
/// </summary>
|
||||
@@ -123,8 +124,9 @@ namespace BinaryObjectScanner.FileType
|
||||
var noteworthyReserved653Bytes = true;
|
||||
if (Array.TrueForAll(reserved653Bytes, b => b == 0x00))
|
||||
noteworthyReserved653Bytes = false;
|
||||
|
||||
// Unsure if more will be needed
|
||||
return noteworthyReserved653Bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace BinaryObjectScanner.Protection
|
||||
/// <summary>
|
||||
/// Alpha-ROM is a form of copy protection created by SETTEC. It is known to make use of twin sectors as well as region locking.
|
||||
/// Later forms of Alpha-ROM appear to be digital only, and it's currently unsure what forms of protection the digital only version includes, except that it does make use of region locking.
|
||||
/// It seems that Alpha-ROM was used in Visual Novels using certain game engines, most notably RealLive and Siglus (https://forums.fuwanovel.net/topic/20927-cannot-crack-siglus-engine-with-alpharom/).
|
||||
/// It seems that Alpha-ROM was used in Visual Novels using certain game engines, most notably RealLive and Siglus (https://forums.fuwanovel.net/topic/20927-cannot-crack-siglus-engine-with-alpharom/).
|
||||
/// Not every Siglus engine game uses Alpha-ROM (Source: https://sample9.dmm.co.jp/digital/pcgame/vsat_0263/vsat_0263t.zip {Official trial mirror}).
|
||||
/// Not every RealLive engine game uses Alpha-ROM (Source: IA item "Kanon_Standard_Edition_Japan").
|
||||
/// Alpha-ROM also seems to have made use of something called "Alpha-DPS" for non-executable data files (http://www.gonsuke.co.jp/protect.html).
|
||||
@@ -43,12 +43,57 @@ namespace BinaryObjectScanner.Protection
|
||||
// - SETTEC0000SETTEC1111
|
||||
// - SOFTWARE\SETTEC
|
||||
// TODO: Are there version numbers?
|
||||
public class AlphaROM : IExecutableCheck<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)
|
||||
{
|
||||
// TODO: Add support for detecting Alpha-ROM found in older games made with the RealLive engine.
|
||||
// TODO: Add support for detecting Alpha-ROM found in older games made with the RealLive engine.
|
||||
// TODO: Add version detection for Alpha-ROM.
|
||||
|
||||
// Get the .data/DATA section strings, if they exist
|
||||
@@ -88,51 +133,5 @@ namespace BinaryObjectScanner.Protection
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,19 +11,99 @@ namespace BinaryObjectScanner.Protection
|
||||
/// More specifically, it may have been created a spin-off division known as "Panlok" (https://cdmediaworld.com/hardware/cdrom/cd_protections_copylok.shtml).
|
||||
/// Though it may also be that "PanLok" was an alternate name for the program itself (https://gamecopyworld.com/games/pc_generic_copylok.shtml).
|
||||
/// There was a PanLok website, but it's unknown what content it had hosted (https://web.archive.org/web/20041215075727/http://www.panlok.com/).
|
||||
///
|
||||
///
|
||||
/// CopyLok made use of bad sectors, using an average of 720-750 per disc, and appears to have been a form of ring protection (http://forum.redump.org/topic/29842/issues-dumping-pc-disc-with-code-lock-copy-protection/).
|
||||
/// At least one disc with CopyLok appears to contain an excerpt of the poem "Jabberwocky" by Lewis Carroll in the raw sector data (http://forum.redump.org/post/54050/#p54050).
|
||||
/// According to the Readme for poxylok (https://gf.wiretarget.com/copylok.htm), some version of Gangsters 2 may have this protection.
|
||||
///
|
||||
///
|
||||
/// Previous versions of BinaryObjectScanner incorrectly reported this DRM as "CodeLock / CodeLok / CopyLok". It was later discovered that due to the similar names, two entirely different DRM were erroneously lumped together.
|
||||
/// "CodeLock" (in this case actually referring to "Code-Lock") is an entirely separate form of DRM, with the existing check now getting used separately.
|
||||
/// Also not to be confused with https://en.wikipedia.org/wiki/Rob_Northen_copylock.
|
||||
///
|
||||
///
|
||||
/// COPYLOK trademark: https://www.trademarkelite.com/europe/trademark/trademark-detail/000618512/COPYLOK.
|
||||
/// </summary>
|
||||
public class CopyLok : IExecutableCheck<PortableExecutable>, IDiskImageCheck<ISO9660>
|
||||
public class CopyLok : IDiskImageCheck<ISO9660>, IExecutableCheck<PortableExecutable>
|
||||
{
|
||||
/// <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;
|
||||
|
||||
if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
|
||||
return null;
|
||||
|
||||
int offset = 0;
|
||||
|
||||
#region Read Application Use
|
||||
|
||||
var applicationUse = pvd.ApplicationUse;
|
||||
uint constantValueOne = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
ushort smallSizeBytes = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
ushort constantValueTwo = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
uint finalSectionOneBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
byte zeroByte = applicationUse.ReadByte(ref offset);
|
||||
ushort earlyCopyLokBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
ushort pairBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
uint oneValueBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
uint earlyCopyLokBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
uint pairBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
var endingZeroBytes = applicationUse.ReadBytes(ref offset, 483);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Main Checks
|
||||
|
||||
// Early return if the rest of the AU data isn't 0x00
|
||||
if (!Array.TrueForAll(endingZeroBytes, b => b == 0x00))
|
||||
return null;
|
||||
|
||||
// Check first currently-observed constant value
|
||||
if (constantValueOne != 0x4ED38AE1)
|
||||
return null;
|
||||
|
||||
// Check for early variant copylok
|
||||
if (earlyCopyLokBytesOne == 0x00)
|
||||
{
|
||||
// Redump ID 35908, 56433, 44526
|
||||
if (pairBytesOne == 0 && oneValueBytes == 0 && earlyCopyLokBytesTwo == 0 && pairBytesTwo == 0)
|
||||
return "CopyLok / CodeLok (Early, ~1850 errors)";
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
|
||||
// Check remaining currently-observed constant values
|
||||
if (constantValueTwo != 0x4ED3 || zeroByte != 0x00 || earlyCopyLokBytesOne != 0x0C76 || oneValueBytes != 0x00000001)
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
|
||||
// Always 0xD1AD, except in Redump ID 71985 (the only sample) where it's 0x6999
|
||||
// Update number be more accurate if more samples are acquired.
|
||||
if (smallSizeBytes < 0xADD1)
|
||||
return "CopyLok / CodeLok (Less errors, ~255)";
|
||||
|
||||
if (pairBytesOne == 0x9425)
|
||||
{
|
||||
// Redump ID 37860, 37881, 38239, 100685, 108375
|
||||
if (pairBytesTwo != 0x00000000)
|
||||
return "CopyLok / CodeLok (Pair errors, ~1500)";
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
|
||||
// Redump ID 31557, 44210, 49087, 72183, 31675
|
||||
if (pairBytesOne == 0xF3ED && pairBytesTwo == 0x00000000)
|
||||
return "CopyLok / CodeLok (Solo errors, ~775)";
|
||||
|
||||
#endregion
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
|
||||
{
|
||||
@@ -36,91 +116,5 @@ namespace BinaryObjectScanner.Protection
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? CheckDiskImage(string file, ISO9660 diskImage, bool includeDebug)
|
||||
{
|
||||
#region Initial Checks
|
||||
|
||||
if (diskImage.VolumeDescriptorSet.Length == 0)
|
||||
return null;
|
||||
|
||||
if (diskImage.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
|
||||
return null;
|
||||
|
||||
|
||||
if (!FileType.ISO9660.NoteworthyApplicationUse(pvd))
|
||||
return null;
|
||||
|
||||
if (FileType.ISO9660.NoteworthyReserved653Bytes(pvd))
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
|
||||
int offset = 0;
|
||||
|
||||
#region Read Application Use
|
||||
|
||||
var applicationUse = pvd.ApplicationUse;
|
||||
uint constantValueOne = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
ushort smallSizeBytes = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
ushort constantValueTwo = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
uint finalSectionOneBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
byte zeroByte = applicationUse.ReadByte(ref offset);
|
||||
ushort earlyCopyLokBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
ushort pairBytesOne = applicationUse.ReadUInt16LittleEndian(ref offset);
|
||||
uint oneValueBytes = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
uint earlyCopyLokBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
uint pairBytesTwo = applicationUse.ReadUInt32LittleEndian(ref offset);
|
||||
var endingZeroBytes = applicationUse.ReadBytes(ref offset, 483);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Main Checks
|
||||
|
||||
// Early return if the rest of the AU data isn't 0x00
|
||||
if (!Array.TrueForAll(endingZeroBytes, b => b == 0x00))
|
||||
return null;
|
||||
|
||||
// Check first currently-observed constant value
|
||||
if (constantValueOne != 0x4ED38AE1)
|
||||
return null;
|
||||
|
||||
// Check for early variant copylok
|
||||
if (earlyCopyLokBytesOne == 0x00)
|
||||
{
|
||||
// Redump ID 35908, 56433, 44526
|
||||
if (pairBytesOne == 0 && oneValueBytes == 0 && earlyCopyLokBytesTwo == 0 && pairBytesTwo == 0)
|
||||
return "CopyLok / CodeLok (Early, ~1850 errors)";
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
|
||||
// Check remaining currently-observed constant values
|
||||
if (constantValueTwo != 0x4ED3 || zeroByte != 0x00 || earlyCopyLokBytesOne != 0x0C76 || oneValueBytes != 0x00000001)
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
|
||||
// Always 0xD1AD, except in Redump ID 71985 (the only sample) where it's 0x6999
|
||||
// Update number be more accurate if more samples are acquired.
|
||||
if (smallSizeBytes < 0xADD1)
|
||||
return "CopyLok / CodeLok (Less errors, ~255)";
|
||||
|
||||
if (pairBytesOne == 0x9425)
|
||||
{
|
||||
// Redump ID 37860, 37881, 38239, 100685, 108375
|
||||
if (pairBytesTwo != 0x00000000)
|
||||
return "CopyLok / CodeLok (Pair errors, ~1500)";
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
|
||||
// Redump ID 31557, 44210, 49087, 72183, 31675
|
||||
if (pairBytesOne == 0xF3ED && pairBytesTwo == 0x00000000)
|
||||
return "CopyLok / CodeLok (Solo errors, ~775)";
|
||||
|
||||
#endregion
|
||||
|
||||
return "CopyLok / CodeLok - Unknown variant, please report to us on GitHub!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -13,12 +13,78 @@ namespace BinaryObjectScanner.Protection
|
||||
/// <summary>
|
||||
/// Macrovision was a company that specialized in various forms of DRM. They had an extensive product line, their most infamous product (within this context) being SafeDisc.
|
||||
/// Due to there being a significant amount of backend tech being shared between various protections, a separate class is needed for generic Macrovision detections.
|
||||
///
|
||||
///
|
||||
/// Macrovision Corporation CD-ROM Unauthorized Copying Study: https://web.archive.org/web/20011005161810/http://www.macrovision.com/solutions/software/cdrom/images/Games_CD-ROM_Study.PDF
|
||||
/// List of trademarks associated with Marovision: https://tmsearch.uspto.gov/bin/showfield?f=toc&state=4804%3Au8wykd.5.1&p_search=searchss&p_L=50&BackReference=&p_plural=yes&p_s_PARA1=&p_tagrepl%7E%3A=PARA1%24LD&expr=PARA1+AND+PARA2&p_s_PARA2=macrovision&p_tagrepl%7E%3A=PARA2%24ALL&p_op_ALL=AND&a_default=search&a_search=Submit+Query&a_search=Submit+Query
|
||||
/// </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)
|
||||
{
|
||||
@@ -349,7 +344,7 @@ namespace BinaryObjectScanner.Protection
|
||||
// One filesize is known to overlap with both SafeDisc and CDS-300, and so is detected separately here.
|
||||
return firstMatchedString.FileSize() switch
|
||||
{
|
||||
// Found in Redump entries 37832 and 66005.
|
||||
// Found in Redump entries 37832 and 66005.
|
||||
20 => "SafeDisc 1.00.025-1.41.001",
|
||||
|
||||
// Found in Redump entries 30555 and 58573.
|
||||
@@ -461,7 +456,7 @@ namespace BinaryObjectScanner.Protection
|
||||
private static string GetSecDrvExecutableVersion(PortableExecutable exe)
|
||||
{
|
||||
// Different versions of this driver correspond to different SafeDisc versions.
|
||||
// TODO: Check if earlier versions of this driver contain the version string in a less obvious place.
|
||||
// TODO: Check if earlier versions of this driver contain the version string in a less obvious place.
|
||||
var version = exe.FileVersion;
|
||||
if (!string.IsNullOrEmpty(version))
|
||||
{
|
||||
@@ -672,13 +667,13 @@ namespace BinaryObjectScanner.Protection
|
||||
// Found in Redump entries 21154, 37982, and 108632.
|
||||
or "1.20.000"
|
||||
|
||||
// Found in Redump entries 17024/37920.
|
||||
// Found in Redump entries 17024/37920.
|
||||
or "1.20.001"
|
||||
|
||||
// Found in Redump entries 28708, 31526, 43321, 55080, and 75501.
|
||||
or "1.30.010"
|
||||
|
||||
// Found in Redump entries 9617 and 49552.
|
||||
// Found in Redump entries 9617 and 49552.
|
||||
or "1.35.000"
|
||||
|
||||
// Found in Redump entries 2595 and 30121.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -24,7 +24,7 @@ namespace BinaryObjectScanner.Protection
|
||||
{"C6DFF6B08EE126893840E107FD4EC9F6", "Alice - Madness Returns (USA)+(Europe)"},
|
||||
{"D7703D32B72185358D58448B235BD55E", "Arcania - Gothic 4 (Australia)"}, // International version (English, French, Italian, German, Spanish)
|
||||
// Arcania - Gothic 4 - Polish(/Hungarian?) - known to most likely exist. Likely matches support site exe.
|
||||
{"83CD6225899C08422F860095962287A5", "Arcania - Gothic 4 - Russian (not in redump yet)"},
|
||||
{"83CD6225899C08422F860095962287A5", "Arcania - Gothic 4 - Russian (not in redump yet)"},
|
||||
// Arcania - Gothic 4 - Chinese - known to most likely exist. Likely matches support site exe.
|
||||
{"FAF6DD75DDB335101CB77A714793DC28", "Batman - Arkham City - Game of the Year Edition (UK)"},
|
||||
{"77999579EE4378BDFAC9438CC9CDB44E", "Batman - Arkham City (USA)+(Europe)"},
|
||||
@@ -103,6 +103,141 @@ namespace BinaryObjectScanner.Protection
|
||||
{48093043, "deadspace_f.aec"},
|
||||
};
|
||||
|
||||
/// <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>
|
||||
@@ -430,7 +429,7 @@ namespace BinaryObjectScanner.Protection
|
||||
return null;
|
||||
|
||||
// Format the version
|
||||
string version = $"{addD.Version}.{addD.Build}";
|
||||
string version = $"{addD.Version}.{addD.Build}";
|
||||
if (!char.IsNumber(version[0]))
|
||||
return "(very old, v3 or less)";
|
||||
|
||||
@@ -556,7 +555,7 @@ namespace BinaryObjectScanner.Protection
|
||||
/// </summary>
|
||||
private static string? CheckMatroschkaPackage(SecuROMMatroschkaPackage package, bool includeDebug)
|
||||
{
|
||||
// Check for all 0x00 required, as at least one known non-RC matroschka has the field, just empty.
|
||||
// Check for all 0x00 required, as at least one known non-RC matroschka has the field, just empty.
|
||||
if (package.KeyHexString == null || package.KeyHexString.Trim('\0').Length == 0)
|
||||
return "SecuROM Matroschka Package";
|
||||
|
||||
@@ -626,11 +625,11 @@ namespace BinaryObjectScanner.Protection
|
||||
|
||||
// Fallback for PA if none of the above occur, in the case of companies that used their own modified PA
|
||||
// variants. PiD refers to this as "SecuROM Modified PA Module".
|
||||
// Found in Redump entries 111997 (paul.dll) and 56373+56374 (AurParticleSystem.dll). The developers of
|
||||
// Found in Redump entries 111997 (paul.dll) and 56373+56374 (AurParticleSystem.dll). The developers of
|
||||
// both, Softstar and Aurogon respectively(?), seem to have some connection, and use similar-looking
|
||||
// modified PA. It probably has its own name like EA's GAM, but I don't currently know what that would be.
|
||||
// modified PA. It probably has its own name like EA's GAM, but I don't currently know what that would be.
|
||||
// Regardless, even if these are given their own named variant later, this check should remain in order to
|
||||
// catch other modified PA variants (this would have also caught EA GAM, for example) and to match PiD's
|
||||
// catch other modified PA variants (this would have also caught EA GAM, for example) and to match PiD's
|
||||
// detection abilities.
|
||||
|
||||
name = exe.ExportNameTable?.Strings?[0];
|
||||
|
||||
@@ -10,10 +10,91 @@ 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:
|
||||
// Known issues:
|
||||
// "Game.exe" not detected (Redump entry 96137).
|
||||
// "HR.exe" Themida not detected, doesn't detect "[Builder]" (Is that the default StarForce?) (Redump entry 94805).
|
||||
// "ChromeEngine3.dll" and "SGP4.dll" not detected, doesn't detect "[FL Disc]" (Redump entry 93098).
|
||||
@@ -58,9 +139,9 @@ namespace BinaryObjectScanner.Protection
|
||||
|
||||
name = exe.TradeName;
|
||||
|
||||
// FrontLine ProActive (digital activation), samples:
|
||||
// https://dbox.tools/titles/pc/46450FA4/
|
||||
// https://dbox.tools/titles/pc/4F430FA0/
|
||||
// FrontLine ProActive (digital activation), samples:
|
||||
// https://dbox.tools/titles/pc/46450FA4/
|
||||
// https://dbox.tools/titles/pc/4F430FA0/
|
||||
// https://dbox.tools/titles/pc/53450FA1/
|
||||
name = exe.TradeName;
|
||||
if (name.OptionalContains("FL ProActive"))
|
||||
@@ -162,85 +243,5 @@ namespace BinaryObjectScanner.Protection
|
||||
// TODO: Determine if there are any file name checks that aren't too generic to use on their own.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <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},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user