Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0ca0a84
Initial
HeroponRikiBestest Oct 30, 2025
ca9f667
Laserlock in
HeroponRikiBestest Oct 30, 2025
ec76f82
This is a better way to read the string
HeroponRikiBestest Oct 30, 2025
f1374a7
That array copy wasn't needed either
HeroponRikiBestest Oct 30, 2025
4871899
Use static filetype method, rename filetype.iso
HeroponRikiBestest Oct 31, 2025
495e220
Initial Codelok ISO scanning
HeroponRikiBestest Oct 31, 2025
0bb0285
Comments with redump IDs
HeroponRikiBestest Oct 31, 2025
55ebf89
Add redump examples to laserlock
HeroponRikiBestest Oct 31, 2025
571e639
Change for testing
HeroponRikiBestest Oct 31, 2025
33040b8
Small comment
HeroponRikiBestest Oct 31, 2025
53f96be
TAGES
HeroponRikiBestest Oct 31, 2025
a07f50f
halfway through safedisc
HeroponRikiBestest Oct 31, 2025
d5dfcae
Safedisc done
HeroponRikiBestest Oct 31, 2025
b3ba774
Fix 1
HeroponRikiBestest Oct 31, 2025
5c42ed5
Major oversights in puredata fixed
HeroponRikiBestest Oct 31, 2025
c5ef1da
Finish SecuROM
HeroponRikiBestest Nov 1, 2025
47bd3bf
ProtectDiSC done
HeroponRikiBestest Nov 1, 2025
cf3f874
Alpharom done
HeroponRikiBestest Nov 1, 2025
4af8d71
Finish StarForce, initial PR review ready
HeroponRikiBestest Nov 1, 2025
81f0662
Wait, that would be really bad
HeroponRikiBestest Nov 1, 2025
fc81ca1
One more for the road.
HeroponRikiBestest Nov 1, 2025
e7c2628
Small finding
HeroponRikiBestest Nov 1, 2025
5b91424
Small fix for finding
HeroponRikiBestest Nov 1, 2025
d490a83
Notes on finding
HeroponRikiBestest Nov 1, 2025
6ae8b26
Several minor fixes, decisions
HeroponRikiBestest Nov 2, 2025
33391fa
what do you MEAN it returns true if there are no elements in the array
HeroponRikiBestest Nov 2, 2025
696016b
Future todo
HeroponRikiBestest Nov 2, 2025
e50ea79
Update packages
mnadareski Nov 6, 2025
852015c
Rebase
HeroponRikiBestest Nov 6, 2025
dfba726
Fix runisochecks
HeroponRikiBestest Nov 6, 2025
50acaa8
First round of fixes
HeroponRikiBestest Nov 6, 2025
8dc43b9
Second round of fixes
HeroponRikiBestest Nov 6, 2025
cbdd32a
Tests attempt 1
HeroponRikiBestest Nov 6, 2025
c6947bb
Make checks work
HeroponRikiBestest Nov 6, 2025
c48f373
Individual test attempt 1
HeroponRikiBestest Nov 6, 2025
a4bd666
Final tests
HeroponRikiBestest Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions BinaryObjectScanner.Test/BinaryObjectScanner.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="SabreTools.Serialization" Version="[2.0.2]" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="SabreTools.Serialization" Version="[2.1.0]" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
6 changes: 3 additions & 3 deletions BinaryObjectScanner/BinaryObjectScanner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@
<PackageReference Include="GrindCore.SharpCompress" Version="0.40.4-alpha" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))" />
<PackageReference Include="SabreTools.Hashing" Version="[1.5.1]" />
<PackageReference Include="SabreTools.IO" Version="[1.7.6]" />
<PackageReference Include="SabreTools.Serialization" Version="[2.0.2]" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.9" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
<PackageReference Include="SabreTools.IO" Version="[1.8.0]" />
<PackageReference Include="SabreTools.Serialization" Version="[2.1.0]" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.10" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions BinaryObjectScanner/Data/StaticChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ public static IContentCheck[] ContentCheckClasses
}
}

/// <summary>
/// Cache for all IISOCheck<ISO> types
/// </summary>
public static IISOCheck<ISO9660>[] ISO9660CheckClasses
{
get
{
iso9660CheckClasses ??= InitCheckClasses<IISOCheck<ISO9660>>();
return iso9660CheckClasses;
}
}

/// <summary>
/// Cache for all IExecutableCheck<LinearExecutable> types
/// </summary>
Expand Down Expand Up @@ -91,6 +103,12 @@ public static IExecutableCheck<PortableExecutable>[] PortableExecutableCheckClas
/// </summary>
private static IContentCheck[]? contentCheckClasses;


/// <summary>
/// Cache for all IISOCheck<ISO9660> types
/// </summary>
private static IISOCheck<ISO9660>[]? iso9660CheckClasses;

/// <summary>
/// Cache for all IExecutableCheck<LinearExecutable> types
/// </summary>
Expand Down
52 changes: 52 additions & 0 deletions BinaryObjectScanner/FileType/DiskImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.IO;
using BinaryObjectScanner.Data;
using BinaryObjectScanner.Interfaces;
using SabreTools.IO.Extensions;
using SabreTools.Serialization.Wrappers;

namespace BinaryObjectScanner.FileType
{
/// <summary>
/// .iso file
/// </summary>
public abstract class DiskImage<T> : DetectableBase<T>
where T : WrapperBase
{
/// <inheritdoc/>
public DiskImage(T wrapper) : base(wrapper) { }

#region Check Runners

/// <summary>
/// Handle a single file based on all ISO check implementations
/// </summary>
/// <param name="file">Name of the source file of the ISO, for tracking</param>
/// <param name="iso">ISO to scan</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, T iso, U[] checks, bool includeDebug)
where U : IISOCheck<T>
{
// Create the output dictionary
var protections = new CheckDictionary<U>();

// Iterate through all checks
checks.IterateWithAction(checkClass =>
{
// Get the protection for the class, if possible
var protection = checkClass.CheckISO(file, iso, includeDebug);
if (string.IsNullOrEmpty(protection))
return;

protections.Append(checkClass, protection);
});

return protections;
}

#endregion
}
}
1 change: 0 additions & 1 deletion BinaryObjectScanner/FileType/Executable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ protected IDictionary<IContentCheck, string> RunContentChecks(string? file, Stre
/// <param name="file">Name of the source file of the executable, for tracking</param>
/// <param name="exe">Executable to scan</param>
/// <param name="checks">Set of checks to use</param>
/// <param name="scanner">Scanner for handling recursive protections</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> RunExecutableChecks<U>(string file, T exe, U[] checks, bool includeDebug)
Expand Down
121 changes: 121 additions & 0 deletions BinaryObjectScanner/FileType/ISO9660.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using BinaryObjectScanner.Data;
using SabreTools.Data.Models.ISO9660;
using SabreTools.IO.Extensions;

namespace BinaryObjectScanner.FileType
{
/// <summary>
/// ISO9660
/// </summary>
public class ISO9660 : DiskImage<SabreTools.Serialization.Wrappers.ISO9660>
{
/// <inheritdoc/>
public ISO9660(SabreTools.Serialization.Wrappers.ISO9660 wrapper) : base(wrapper) { }

/// <inheritdoc/>
public override string? Detect(Stream stream, string file, bool includeDebug)
{
// Create the output dictionary
var protections = new ProtectionDictionary();

// Standard checks
var subProtections
= RunISOChecks(file, _wrapper, StaticChecks.ISO9660CheckClasses, includeDebug);
protections.Append(file, subProtections.Values);

// If there are no protections
if (protections.Count == 0)
return null;

// Create the internal list
var protectionList = new List<string>();
foreach (string key in protections.Keys)
{
protectionList.AddRange(protections[key]);
}

return string.Join(";", [.. protectionList]);
}

// Checks whether the sequence of bytes is pure data (as in, not empty, not text, just high-entropy data)
public static bool IsPureData(byte[] bytes)
{
// Check if there are three 0x00s in a row. Two seems like pushing it
byte[] containedZeroes = {0x00, 0x00, 0x00};
int index = 0;
for (int i = 0; i < bytes.Length; ++i) {
if (bytes[i] == containedZeroes[index])
{
if (++index >= containedZeroes.Length)
return false;
}
else
index = 0;
}

// Checks if there are strings in the data
// TODO: is this too dangerous, or too faulty?
// Currently-found worst cases:
// "Y:1BY:1BC" in Redump ID 23339
var strings = bytes.ReadStringsWithEncoding(charLimit: 7, Encoding.ASCII);
Regex rgx = new Regex("[^a-zA-Z0-9 -'!?,.]");
foreach (string str in strings)
{
if (rgx.Replace(str, "").Length > 7)
return false;
}

return true;
}

// TODO: can these 2 "noteworthy" functions be cached?
// Checks whether the Application Use data is "noteworthy" enough to be worth checking for protection.
public static bool NoteworthyApplicationUse(PrimaryVolumeDescriptor pvd)
{
int offset = 0;
var applicationUse = pvd.ApplicationUse;
var noteworthyApplicationUse = true;
if (Array.TrueForAll(applicationUse, b => b == 0x00))
noteworthyApplicationUse = false;
string? potentialAppUseString = applicationUse.ReadNullTerminatedAnsiString(ref offset);
if (potentialAppUseString != null && potentialAppUseString.Length > 0) // Some image authoring programs add a starting string to AU data
{
if (potentialAppUseString.StartsWith("ImgBurn"))
noteworthyApplicationUse = false;
else if (potentialAppUseString.StartsWith("ULTRAISO"))
noteworthyApplicationUse = false;
else if (potentialAppUseString.StartsWith("Rimage"))
noteworthyApplicationUse = false;
else if (Array.TrueForAll(Encoding.ASCII.GetBytes(potentialAppUseString), b => b == 0x20))
noteworthyApplicationUse = false;
// 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")
noteworthyApplicationUse = false;

return noteworthyApplicationUse;
}

// Checks whether the Reserved 653 Bytes are "noteworthy" enough to be worth checking for protection.
public static bool NoteworthyReserved653Bytes(PrimaryVolumeDescriptor pvd)
{
var reserved653Bytes = pvd.Reserved653Bytes;
var noteworthyReserved653Bytes = true;
if (Array.TrueForAll(reserved653Bytes, b => b == 0x00))
noteworthyReserved653Bytes = false;
// Unsure if more will be needed
return noteworthyReserved653Bytes;
}
}
}
19 changes: 19 additions & 0 deletions BinaryObjectScanner/Interfaces/IISOCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using SabreTools.Serialization.Wrappers;

namespace BinaryObjectScanner.Interfaces
{
/// <summary>
/// Check an ISO for protection
/// </summary>
public interface IISOCheck<T> where T : WrapperBase
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat unrelated, but I need to figure out what preferential order an image check would fall compared to executable, content, and path checks. That likely won't be able to be handled in this PR, but if you have suggestions, I'm open to them. The current recommended order is in one of the documentation files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea, honestly; I wasn't even really aware there was an order.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an example from my documentation: https://github.com/SabreTools/BinaryObjectScanner/blob/master/Coding%20Guide.md#code-organization

Roughly it's: Content, Executable, Path in that order with Executable being split between the 4 supported types in the order from the link above. Tacking the new interface and implementation at the end is fine for now, but I don't know if that's going to be a reasonable general order. It doesn't have to be decided now, just a musing thought.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really mind where it is, as long as it happens.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the pattern (and for the sake of at least minimal documentation), I think it will end up being the second interface in order. You do not need to worry about making this change as a part of this PR. I will handle it afterwards once the logic is all set.

{
/// <summary>
/// Check a path for protections based on file contents
/// </summary>
/// <param name="file">File to check for protection indicators</param>
/// <param name="iso">ISO 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? CheckISO(string file, T iso, bool includeDebug);
}
}
53 changes: 51 additions & 2 deletions BinaryObjectScanner/Protection/AlphaROM.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using BinaryObjectScanner.Interfaces;
using System;
using System.Text.RegularExpressions;
using BinaryObjectScanner.Interfaces;
using SabreTools.Data.Models.ISO9660;
using SabreTools.IO.Extensions;
using SabreTools.Serialization.Wrappers;

namespace BinaryObjectScanner.Protection
Expand Down Expand Up @@ -39,7 +43,7 @@ namespace BinaryObjectScanner.Protection
// - SETTEC0000SETTEC1111
// - SOFTWARE\SETTEC
// TODO: Are there version numbers?
public class AlphaROM : IExecutableCheck<PortableExecutable>
public class AlphaROM : IExecutableCheck<PortableExecutable>, IISOCheck<ISO9660>
{
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
Expand Down Expand Up @@ -84,5 +88,50 @@ public class AlphaROM : IExecutableCheck<PortableExecutable>

return null;
}

public string? CheckISO(string file, ISO9660 iso, bool includeDebug)
{
// Checks can be made even easier once UDF support exists, as most (although not all, some early discs like
// redump ID 124111 have no UDF partition) discs have "Settec" slathered over every field UDF lets them.

if (iso.VolumeDescriptorSet[0] is not PrimaryVolumeDescriptor pvd)
return null;


// Alpharom disc check #1: disc has varying (but observed to at least always be larger than 14) length
// string made up of numbers and capital letters.
// TODO: triple-check that length is never below 14
int offset = 0;
var applicationIdentifierString = pvd.ApplicationIdentifier.ReadNullTerminatedAnsiString(ref offset)?.Trim();
if (applicationIdentifierString == null || applicationIdentifierString.Length < 14)
return null;

if (!Regex.IsMatch(applicationIdentifierString, "^[A-Z0-9]*$"))
return null;

offset = 0;

// Alpharom disc check #2: disc has publisher identifier filled with varying amount of data (26-50 bytes
// have been observed) followed by spaces. There's a decent chance this is just a Japanese text string, but
// UTF, Shift-JIS, and EUC-JP all fail to display anything but garbage.

var publisherIdentifier = pvd.PublisherIdentifier;
var firstSpace = Array.FindIndex(publisherIdentifier, b => b == 0x20);
if (firstSpace <= 10 || firstSpace >= 120)
return null;

var publisherData = new byte[firstSpace];
var publisherSpaces = new byte[publisherData.Length - firstSpace];
Array.Copy(publisherIdentifier, 0, publisherData, 0, firstSpace);
Array.Copy(publisherIdentifier, firstSpace, publisherSpaces, 0, publisherData.Length - firstSpace);

if (!Array.TrueForAll(publisherSpaces, b => b == 0x20))
return null;

if (!FileType.ISO9660.IsPureData(publisherData))
return null;

return "AlphaROM";
}
}
}
Loading