diff --git a/BurnOutSharp/Scanner.cs b/BurnOutSharp/Scanner.cs
new file mode 100644
index 00000000..07dd4301
--- /dev/null
+++ b/BurnOutSharp/Scanner.cs
@@ -0,0 +1,585 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BurnOutSharp.FileType;
+using BurnOutSharp.ProtectionType;
+
+namespace BurnOutSharp
+{
+ // TODO: Use the file progress everywhere
+ // TODO: Re-enable direct stream scanning
+ // TODO: Should FileTypes be exposed directly as well so the scans can be exposed easier?
+ public class Scanner
+ {
+ ///
+ /// Optional progress callback during scanning
+ ///
+ public IProgress FileProgress { get; set; } = null;
+
+ ///
+ /// List of paths that will be scanned with this object
+ ///
+ public List Paths { get; set; } = new List();
+
+ ///
+ /// Determines whether the byte position of found protection is included or not
+ ///
+ public bool IncludePosition { get; set; } = false;
+
+ ///
+ /// Determines whether all files are scanned or just executables are
+ ///
+ public bool ScanAllFiles { get; set; } = false;
+
+ ///
+ /// Determines whether archives are decompressed and scanned
+ ///
+ public bool ScanArchives { get; set; } = true;
+
+ ///
+ /// Constructor
+ ///
+ /// Path to create a scanner for
+ /// Optional progress callback
+ public Scanner(string path, IProgress fileProgress = null)
+ {
+ Paths = new List { path };
+ FileProgress = fileProgress;
+ }
+
+ ///
+ /// Scan the list of paths and get all found protections
+ ///
+ /// Dictionary of list of strings representing the found protections
+ public Dictionary> GetProtections()
+ {
+ // If we have no paths, we can't scan
+ if (Paths == null || !Paths.Any())
+ return null;
+
+ // Loop through each path and get the returned values
+ var protections = new Dictionary>();
+ foreach (string path in Paths)
+ {
+ // Directories scan each internal file individually
+ if (Directory.Exists(path))
+ {
+ // Enumerate all files at first for easier access
+ var files = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).ToList();
+
+ // Scan for path-detectable protections
+ var directoryPathProtections = GetPathProtections(path, files);
+ if (directoryPathProtections != null && directoryPathProtections.Any())
+ {
+ foreach (string key in directoryPathProtections.Keys)
+ {
+ if (!protections.ContainsKey(key))
+ protections[key] = new List();
+
+ protections[key].AddRange(directoryPathProtections[key]);
+ }
+ }
+
+ // Scan each file in directory separately
+ foreach (string file in files)
+ {
+ // Scan for path-detectable protections
+ var filePathProtections = GetPathProtections(file);
+ if (filePathProtections != null && filePathProtections.Any())
+ {
+ foreach (string key in filePathProtections.Keys)
+ {
+ if (!protections.ContainsKey(key))
+ protections[key] = new List();
+
+ protections[key].AddRange(filePathProtections[key]);
+ }
+ }
+
+ // Scan for content-detectable protections
+ var fileProtections = GetInternalProtections(file);
+ if (fileProtections != null && fileProtections.Any())
+ {
+ foreach (string key in fileProtections.Keys)
+ {
+ if (!protections.ContainsKey(key))
+ protections[key] = new List();
+
+ protections[key].AddRange(fileProtections[key]);
+ }
+ }
+ }
+ }
+
+ // Scan a single file by itself
+ else if (File.Exists(path))
+ {
+ // Scan for path-detectable protections
+ var filePathProtections = GetPathProtections(path);
+ if (filePathProtections != null && filePathProtections.Any())
+ {
+ foreach (string key in filePathProtections.Keys)
+ {
+ if (!protections.ContainsKey(key))
+ protections[key] = new List();
+
+ protections[key].AddRange(filePathProtections[key]);
+ }
+ }
+
+ // Scan for content-detectable protections
+ var fileProtections = GetInternalProtections(path);
+ if (fileProtections != null && fileProtections.Any())
+ {
+ foreach (string key in fileProtections.Keys)
+ {
+ if (!protections.ContainsKey(key))
+ protections[key] = new List();
+
+ protections[key].AddRange(fileProtections[key]);
+ }
+ }
+ }
+
+ // Throw on an invalid path
+ else
+ {
+ throw new FileNotFoundException($"{path} is not a directory or file, skipping...");
+ }
+ }
+
+ return protections;
+ }
+
+ ///
+ /// Get the path-detectable protections associated with a single path
+ ///
+ /// Path of the directory or file to scan
+ /// Files contained within if the path is a directory
+ /// Dictionary of list of strings representing the found protections
+ public Dictionary> GetPathProtections(string path, List files = null)
+ {
+ List protections = new List();
+ string protection;
+
+ // If we have a directory, get the files in the directory for searching
+ bool isDirectory = false;
+ if (Directory.Exists(path))
+ isDirectory = true;
+
+ // AACS
+ protection = AACS.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Alpha-DVD
+ protection = AlphaDVD.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Bitpool
+ protection = Bitpool.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // ByteShield
+ protection = ByteShield.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Cactus Data Shield
+ protection = CactusDataShield.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // CD-Cops
+ protection = CDCops.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // CD-Lock
+ protection = CDLock.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // CD-Protector
+ protection = CDProtector.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // CD-X
+ protection = CDX.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ /*
+ // CopyKiller
+ protection = CopyKiller.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+ */
+
+ // DiscGuard
+ protection = DiscGuard.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // DVD Crypt
+ protection = DVDCrypt.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // DVD-Movie-PROTECT
+ protection = DVDMoviePROTECT.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // FreeLock
+ protection = FreeLock.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Games for Windows - Live
+ protection = GFWL.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Hexalock AutoLock
+ protection = HexalockAutoLock.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Impulse Reactor
+ protection = ImpulseReactor.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // IndyVCD
+ protection = IndyVCD.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Key2Audio XS
+ protection = Key2AudioXS.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // LaserLock
+ protection = LaserLock.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // MediaCloQ
+ protection = MediaCloQ.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // MediaMax CD3
+ protection = MediaMaxCD3.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Origin
+ protection = Origin.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Protect DVD-Video
+ protection = ProtectDVDVideo.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // SafeCast
+ protection = SafeCast.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // SafeDisc
+ protection = SafeDisc.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // SafeDisc Lite
+ protection = SafeDiscLite.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // SafeLock
+ protection = SafeLock.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // SecuROM
+ protection = SecuROM.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // SmartE
+ protection = SmartE.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // SoftLock
+ protection = SoftLock.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // SolidShield
+ protection = SolidShield.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // StarForce
+ protection = StarForce.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Steam
+ protection = Steam.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // TAGES
+ protection = Tages.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // TZCopyProtector
+ protection = TZCopyProtector.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Uplay
+ protection = Uplay.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // VOB ProtectCD/DVD
+ protection = VOBProtectCDDVD.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Winlock
+ protection = Winlock.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // WTM CD Protect
+ protection = WTMCDProtect.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // XCP
+ protection = XCP.CheckPath(path, files, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Zzxzz
+ protection = Zzxzz.CheckPath(path, isDirectory);
+ if (!string.IsNullOrWhiteSpace(protection))
+ protections.Add(protection);
+
+ // Create and return the dictionary
+ return new Dictionary>
+ {
+ [path] = protections
+ };
+ }
+
+ ///
+ /// Get the content-detectable protections associated with a single path
+ ///
+ /// Path to the file to scan
+ /// Dictionary of list of strings representing the found protections
+ private Dictionary> GetInternalProtections(string file)
+ {
+ // Quick sanity check before continuing
+ if (!File.Exists(file))
+ return null;
+
+ // Initialze the protections found
+ var protections = new Dictionary>();
+
+ // Get the extension for certain checks
+ string extension = Path.GetExtension(file).ToLower().TrimStart('.');
+
+ // Open the file and begin scanning
+ using (FileStream fs = File.OpenRead(file))
+ {
+ // Get the first 16 bytes for matching
+ byte[] magic = new byte[16];
+ try
+ {
+ fs.Read(magic, 0, 16);
+ fs.Seek(-16, SeekOrigin.Current);
+ }
+ catch
+ {
+ // We don't care what the issue was, we can't read or seek the file
+ return null;
+ }
+
+ #region Non-Archive File Types
+
+ // Executable
+ if (ScanAllFiles || Executable.ShouldScan(magic))
+ {
+ var subProtections = Executable.Scan(fs, file, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // Text-based files
+ if (ScanAllFiles || Textfile.ShouldScan(magic, extension))
+ {
+ var subProtections = Executable.Scan(fs, file, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ #endregion
+
+ #region Archive File Types
+
+ // 7-Zip archive
+ if (SevenZip.ShouldScan(magic))
+ {
+ var subProtections = SevenZip.Scan(fs, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // BFPK archive
+ if (BFPK.ShouldScan(magic))
+ {
+ var subProtections = BFPK.Scan(fs, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // BZip2
+ if (BZip2.ShouldScan(magic))
+ {
+ var subProtections = BZip2.Scan(fs, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // GZIP
+ if (GZIP.ShouldScan(magic))
+ {
+ var subProtections = GZIP.Scan(fs, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // InstallShield Cabinet
+ if (file != null && InstallShieldCAB.ShouldScan(magic))
+ {
+ var subProtections = InstallShieldCAB.Scan(file, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // Microsoft Cabinet
+ if (file != null && MicrosoftCAB.ShouldScan(magic))
+ {
+ var subProtections = MicrosoftCAB.Scan(file, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // MSI
+ if (file != null && MSI.ShouldScan(magic))
+ {
+ var subProtections = MSI.Scan(file, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // MPQ archive
+ if (file != null && MPQ.ShouldScan(magic))
+ {
+ var subProtections = MPQ.Scan(file, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // PKZIP archive (and derivatives)
+ if (PKZIP.ShouldScan(magic))
+ {
+ var subProtections = PKZIP.Scan(fs, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // RAR archive
+ if (RAR.ShouldScan(magic))
+ {
+ var subProtections = RAR.Scan(fs, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // Tape Archive
+ if (TapeArchive.ShouldScan(magic))
+ {
+ var subProtections = TapeArchive.Scan(fs, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // Valve archive formats
+ if (file != null && Valve.ShouldScan(magic))
+ {
+ var subProtections = Valve.Scan(file, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ // XZ
+ if (XZ.ShouldScan(magic))
+ {
+ var subProtections = XZ.Scan(fs, IncludePosition);
+ if (!protections.ContainsKey(file))
+ protections[file] = new List();
+
+ protections[file] = subProtections;
+ }
+
+ #endregion
+ }
+
+ return protections;
+ }
+ }
+}