Files

593 lines
24 KiB
C#
Raw Permalink Normal View History

2020-10-30 23:56:27 -07:00
using System;
using System.Collections.Concurrent;
2020-10-30 23:56:27 -07:00
using System.Collections.Generic;
using System.IO;
using System.Linq;
2022-12-03 22:47:57 -08:00
using System.Text;
using System.Threading.Tasks;
2020-10-30 23:56:27 -07:00
using BurnOutSharp.FileType;
2022-12-15 00:13:24 -08:00
using BurnOutSharp.Utilities;
using static BurnOutSharp.Utilities.Dictionary;
2020-10-30 23:56:27 -07:00
namespace BurnOutSharp
{
public class Scanner
{
2022-05-01 14:16:53 -07:00
#region Options
2020-10-30 23:56:27 -07:00
/// <summary>
/// Determines whether archives are decompressed and scanned
2020-10-30 23:56:27 -07:00
/// </summary>
public bool ScanArchives { get; private set; }
2020-10-30 23:56:27 -07:00
/// <summary>
/// Determines if content matches are used or not
/// </summary>
public bool ScanContents { get; private set; }
2020-10-30 23:56:27 -07:00
/// <summary>
/// Determines if packers are counted as detected protections or not
2020-10-30 23:56:27 -07:00
/// </summary>
public bool ScanPackers { get; private set; }
2020-10-30 23:56:27 -07:00
2023-01-05 10:46:13 -08:00
/// <summary>
/// Determines if path matches are used or not
/// </summary>
public bool ScanPaths { get; private set; }
2020-10-30 23:56:27 -07:00
/// <summary>
/// Determines if debug information is output or not
2020-10-30 23:56:27 -07:00
/// </summary>
public bool IncludeDebug { get; private set; }
2020-10-30 23:56:27 -07:00
#endregion
/// <summary>
/// Optional progress callback during scanning
/// </summary>
private readonly IProgress<ProtectionProgress> fileProgress;
2022-05-01 14:16:53 -07:00
2020-10-30 23:56:27 -07:00
/// <summary>
/// Constructor
/// </summary>
/// <param name="scanArchives">Enable scanning archive contents</param>
/// <param name="scanContents">Enable including content detections in output</param>
/// <param name="scanPackers">Enable including packers in output</param>
2023-01-05 10:46:13 -08:00
/// <param name="scanPaths">Enable including path detections in output</param>
/// <param name="includeDebug">Enable including debug information</param>
2020-10-30 23:56:27 -07:00
/// <param name="fileProgress">Optional progress callback</param>
public Scanner(bool scanArchives, bool scanContents, bool scanPackers, bool scanPaths, bool includeDebug, IProgress<ProtectionProgress> fileProgress = null)
2020-10-30 23:56:27 -07:00
{
ScanArchives = scanArchives;
ScanContents = scanContents;
ScanPackers = scanPackers;
2023-01-05 10:46:13 -08:00
ScanPaths = scanPaths;
IncludeDebug = includeDebug;
this.fileProgress = fileProgress;
2022-12-03 22:47:57 -08:00
// Register the codepages
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
2020-10-30 23:56:27 -07:00
}
2022-05-01 14:16:53 -07:00
#region Scanning
/// <summary>
/// Scan a single path and get all found protections
/// </summary>
/// <param name="path">Path to scan</param>
/// <returns>Dictionary of list of strings representing the found protections</returns>
public ConcurrentDictionary<string, ConcurrentQueue<string>> GetProtections(string path)
{
return GetProtections(new List<string> { path });
}
2020-10-30 23:56:27 -07:00
/// <summary>
/// Scan the list of paths and get all found protections
/// </summary>
/// <returns>Dictionary of list of strings representing the found protections</returns>
public ConcurrentDictionary<string, ConcurrentQueue<string>> GetProtections(List<string> paths)
2020-10-30 23:56:27 -07:00
{
// If we have no paths, we can't scan
if (paths == null || !paths.Any())
2020-10-30 23:56:27 -07:00
return null;
2021-08-25 20:25:45 -07:00
// Set a starting starting time for debug output
DateTime startTime = DateTime.UtcNow;
2020-10-31 14:00:31 -07:00
// Checkpoint
this.fileProgress?.Report(new ProtectionProgress(null, 0, null));
2020-10-31 14:00:31 -07:00
// Temp variables for reporting
string tempFilePath = Path.GetTempPath();
string tempFilePathWithGuid = Path.Combine(tempFilePath, Guid.NewGuid().ToString());
2020-10-30 23:56:27 -07:00
// Loop through each path and get the returned values
var protections = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
foreach (string path in paths)
2020-10-30 23:56:27 -07:00
{
// 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
2023-01-05 10:46:13 -08:00
if (ScanPaths)
{
var directoryPathProtections = GetDirectoryPathProtections(path, files);
AppendToDictionary(protections, directoryPathProtections);
}
2020-10-30 23:56:27 -07:00
// Scan each file in directory separately
for (int i = 0; i < files.Count; i++)
2020-10-30 23:56:27 -07:00
{
2020-10-31 14:00:31 -07:00
// Get the current file
string file = files.ElementAt(i);
// Get the reportable file name
string reportableFileName = file;
if (reportableFileName.StartsWith(tempFilePath))
reportableFileName = reportableFileName.Substring(tempFilePathWithGuid.Length);
// Checkpoint
this.fileProgress?.Report(new ProtectionProgress(reportableFileName, i / (float)files.Count, "Checking file" + (file != reportableFileName ? " from archive" : string.Empty)));
2020-10-31 14:00:31 -07:00
2020-10-30 23:56:27 -07:00
// Scan for path-detectable protections
2023-01-05 10:46:13 -08:00
if (ScanPaths)
{
var filePathProtections = GetFilePathProtections(file);
AppendToDictionary(protections, filePathProtections);
}
2020-10-30 23:56:27 -07:00
// Scan for content-detectable protections
var fileProtections = GetInternalProtections(file);
if (fileProtections != null && fileProtections.Any())
2020-10-30 23:56:27 -07:00
{
foreach (string key in fileProtections.Keys)
2020-10-30 23:56:27 -07:00
{
if (!protections.ContainsKey(key))
protections[key] = new ConcurrentQueue<string>();
2020-10-30 23:56:27 -07:00
protections[key].AddRange(fileProtections[key]);
2020-10-30 23:56:27 -07:00
}
}
2020-10-31 14:00:31 -07:00
// Checkpoint
protections.TryGetValue(file, out ConcurrentQueue<string> fullProtectionList);
2020-10-31 14:00:31 -07:00
string fullProtection = (fullProtectionList != null && fullProtectionList.Any() ? string.Join(", ", fullProtectionList) : null);
this.fileProgress?.Report(new ProtectionProgress(reportableFileName, (i + 1) / (float)files.Count, fullProtection ?? string.Empty));
2020-10-31 14:00:31 -07:00
}
2020-10-30 23:56:27 -07:00
}
// Scan a single file by itself
else if (File.Exists(path))
{
2020-10-31 14:00:31 -07:00
// Get the reportable file name
string reportableFileName = path;
if (reportableFileName.StartsWith(tempFilePath))
reportableFileName = reportableFileName.Substring(tempFilePathWithGuid.Length);
// Checkpoint
this.fileProgress?.Report(new ProtectionProgress(reportableFileName, 0, "Checking file" + (path != reportableFileName ? " from archive" : string.Empty)));
2020-10-31 14:00:31 -07:00
2020-10-30 23:56:27 -07:00
// Scan for path-detectable protections
2023-01-05 10:46:13 -08:00
if (ScanPaths)
{
var filePathProtections = GetFilePathProtections(path);
AppendToDictionary(protections, filePathProtections);
}
2020-10-30 23:56:27 -07:00
// Scan for content-detectable protections
var fileProtections = GetInternalProtections(path);
if (fileProtections != null && fileProtections.Any())
2020-10-30 23:56:27 -07:00
{
foreach (string key in fileProtections.Keys)
2020-10-30 23:56:27 -07:00
{
if (!protections.ContainsKey(key))
protections[key] = new ConcurrentQueue<string>();
2020-10-30 23:56:27 -07:00
protections[key].AddRange(fileProtections[key]);
2020-10-30 23:56:27 -07:00
}
}
2020-10-31 14:00:31 -07:00
// Checkpoint
protections.TryGetValue(path, out ConcurrentQueue<string> fullProtectionList);
2020-10-31 14:00:31 -07:00
string fullProtection = (fullProtectionList != null && fullProtectionList.Any() ? string.Join(", ", fullProtectionList) : null);
this.fileProgress?.Report(new ProtectionProgress(reportableFileName, 1, fullProtection ?? string.Empty));
2020-10-30 23:56:27 -07:00
}
// Throw on an invalid path
else
{
Console.WriteLine($"{path} is not a directory or file, skipping...");
//throw new FileNotFoundException($"{path} is not a directory or file, skipping...");
2020-10-30 23:56:27 -07:00
}
}
2020-10-31 21:20:16 -07:00
// Clear out any empty keys
2022-12-15 00:13:24 -08:00
ClearEmptyKeys(protections);
2020-10-31 21:20:16 -07:00
2021-08-25 20:25:45 -07:00
// If we're in debug, output the elasped time to console
if (IncludeDebug)
Console.WriteLine($"Time elapsed: {DateTime.UtcNow.Subtract(startTime)}");
2020-10-30 23:56:27 -07:00
return protections;
}
2020-10-30 23:56:27 -07:00
/// <summary>
/// Get the path-detectable protections associated with a single path
/// </summary>
2021-03-19 15:48:53 -07:00
/// <param name="path">Path of the directory to scan</param>
/// <param name="files">Files contained within</param>
2020-10-30 23:56:27 -07:00
/// <returns>Dictionary of list of strings representing the found protections</returns>
private ConcurrentDictionary<string, ConcurrentQueue<string>> GetDirectoryPathProtections(string path, List<string> files)
2020-10-30 23:56:27 -07:00
{
// Create an empty queue for protections
var protections = new ConcurrentQueue<string>();
// Preprocess the list of files
files = files.Select(f => f.Replace('\\', '/')).ToList();
2021-03-19 15:48:53 -07:00
// Iterate through all path checks
2022-05-01 14:46:01 -07:00
Parallel.ForEach(ScanningClasses.PathCheckClasses, pathCheckClass =>
{
ConcurrentQueue<string> protection = pathCheckClass.CheckDirectoryPath(path, files);
if (protection != null)
protections.AddRange(protection);
});
2021-03-19 15:48:53 -07:00
// Create and return the dictionary
return new ConcurrentDictionary<string, ConcurrentQueue<string>>
2021-03-19 15:48:53 -07:00
{
[path] = protections
};
}
/// <summary>
/// Get the path-detectable protections associated with a single path
/// </summary>
/// <param name="path">Path of the file to scan</param>
/// <returns>Dictionary of list of strings representing the found protections</returns>
private ConcurrentDictionary<string, ConcurrentQueue<string>> GetFilePathProtections(string path)
2021-03-19 15:48:53 -07:00
{
// Create an empty queue for protections
var protections = new ConcurrentQueue<string>();
2021-03-19 15:48:53 -07:00
// Iterate through all path checks
2022-05-01 14:46:01 -07:00
Parallel.ForEach(ScanningClasses.PathCheckClasses, pathCheckClass =>
{
string protection = pathCheckClass.CheckFilePath(path.Replace("\\", "/"));
if (!string.IsNullOrWhiteSpace(protection))
protections.Enqueue(protection);
});
2020-10-30 23:56:27 -07:00
// Create and return the dictionary
return new ConcurrentDictionary<string, ConcurrentQueue<string>>
2020-10-30 23:56:27 -07:00
{
[path] = protections
};
}
/// <summary>
/// Get the content-detectable protections associated with a single path
/// </summary>
/// <param name="file">Path to the file to scan</param>
/// <returns>Dictionary of list of strings representing the found protections</returns>
private ConcurrentDictionary<string, ConcurrentQueue<string>> GetInternalProtections(string file)
2020-10-30 23:56:27 -07:00
{
// Quick sanity check before continuing
if (!File.Exists(file))
return null;
2022-05-01 17:06:46 -07:00
// Open the file and begin scanning
try
{
2022-12-22 22:03:32 -08:00
using (FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
2022-05-01 17:06:46 -07:00
{
return GetInternalProtections(file, fs);
}
}
catch (Exception ex)
{
2022-05-15 20:58:27 -07:00
if (IncludeDebug) Console.WriteLine(ex);
2022-05-01 17:06:46 -07:00
var protections = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
2022-12-15 00:13:24 -08:00
AppendToDictionary(protections, file, IncludeDebug ? ex.ToString() : "[Exception opening file, please try again]");
ClearEmptyKeys(protections);
2022-05-01 17:06:46 -07:00
return protections;
}
}
/// <summary>
/// Get the content-detectable protections associated with a single path
/// </summary>
/// <param name="fileName">Name of the source file of the stream, for tracking</param>
/// <param name="stream">Stream to scan the contents of</param>
/// <returns>Dictionary of list of strings representing the found protections</returns>
private ConcurrentDictionary<string, ConcurrentQueue<string>> GetInternalProtections(string fileName, Stream stream)
{
// Quick sanity check before continuing
if (stream == null || !stream.CanRead || !stream.CanSeek)
return null;
2022-03-15 21:30:46 -07:00
// Initialize the protections found
var protections = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
2020-10-30 23:56:27 -07:00
// Get the extension for certain checks
2022-05-01 17:06:46 -07:00
string extension = Path.GetExtension(fileName).ToLower().TrimStart('.');
2020-10-30 23:56:27 -07:00
// Open the file and begin scanning
try
2020-10-30 23:56:27 -07:00
{
2022-05-01 17:06:46 -07:00
// Get the first 16 bytes for matching
byte[] magic = new byte[16];
try
{
stream.Read(magic, 0, 16);
stream.Seek(0, SeekOrigin.Begin);
}
2022-05-15 20:58:27 -07:00
catch (Exception ex)
2020-10-30 23:56:27 -07:00
{
2022-05-15 20:58:27 -07:00
if (IncludeDebug) Console.WriteLine(ex);
2022-05-01 17:06:46 -07:00
return null;
}
// Get the file type either from magic number or extension
2022-12-15 00:13:24 -08:00
SupportedFileType fileType = Tools.Utilities.GetFileType(magic);
2022-12-08 21:46:22 -08:00
if (fileType == SupportedFileType.UNKNOWN)
2022-12-15 00:13:24 -08:00
fileType = Tools.Utilities.GetFileType(extension);
// If we still got unknown, just return null
2022-12-08 21:46:22 -08:00
if (fileType == SupportedFileType.UNKNOWN)
return null;
// Create a scannable for the given file type
2022-12-15 00:13:24 -08:00
var scannable = Tools.Utilities.CreateScannable(fileType);
if (scannable == null)
return null;
2022-05-01 17:06:46 -07:00
#region Non-Archive File Types
// If we're scanning file contents
if (ScanContents)
2022-05-01 17:06:46 -07:00
{
// Executable
if (scannable is Executable)
{
var subProtections = scannable.Scan(this, stream, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
// LDSCRYPT
if (scannable is LDSCRYPT)
{
var subProtections = scannable.Scan(this, stream, fileName);
AppendToDictionary(protections, subProtections);
}
// PLJ
if (scannable is PLJ)
{
var subProtections = scannable.Scan(this, stream, fileName);
AppendToDictionary(protections, subProtections);
}
// SFFS
if (scannable is SFFS)
{
var subProtections = scannable.Scan(this, stream, fileName);
AppendToDictionary(protections, subProtections);
}
// Text-based files
if (scannable is Textfile)
{
var subProtections = scannable.Scan(this, stream, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
}
#endregion
#region Archive File Types
// If we're scanning archives
2022-05-01 17:06:46 -07:00
if (ScanArchives)
{
// 7-Zip archive
if (scannable is SevenZip)
{
var subProtections = scannable.Scan(this, stream, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
// BFPK archive
if (scannable is BFPK)
{
var subProtections = scannable.Scan(this, stream, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2020-10-30 23:56:27 -07:00
2022-12-26 14:52:12 -08:00
// BSP
if (scannable is BSP)
{
var subProtections = scannable.Scan(this, stream, fileName);
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
// BZip2
if (scannable is BZip2)
{
var subProtections = scannable.Scan(this, stream, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2021-07-21 13:40:32 -07:00
}
2022-12-26 14:52:12 -08:00
// GCF
if (scannable is GCF)
{
var subProtections = scannable.Scan(this, stream, fileName);
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
// GZIP
if (scannable is GZIP)
2021-07-21 13:40:32 -07:00
{
var subProtections = scannable.Scan(this, stream, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2020-10-30 23:56:27 -07:00
2022-05-01 17:06:46 -07:00
// InstallShield Archive V3 (Z)
if (fileName != null && scannable is InstallShieldArchiveV3)
{
var subProtections = scannable.Scan(this, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
2020-10-30 23:56:27 -07:00
2022-05-01 17:06:46 -07:00
// InstallShield Cabinet
if (fileName != null && scannable is InstallShieldCAB)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
2022-05-01 17:06:46 -07:00
// Microsoft Cabinet
if (fileName != null && scannable is MicrosoftCAB)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
// Microsoft LZ
if (fileName != null && scannable is MicrosoftLZ)
{
var subProtections = scannable.Scan(this, fileName);
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
// MSI
if (fileName != null && scannable is MSI)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
2022-12-26 14:52:12 -08:00
// MoPaQ archive
if (fileName != null && scannable is MPQ)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
2023-01-05 10:46:13 -08:00
2022-12-26 14:52:12 -08:00
// PAK
if (fileName != null && scannable is PAK)
{
var subProtections = scannable.Scan(this, fileName);
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
// PKZIP archive (and derivatives)
if (scannable is PKZIP)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, stream, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
2022-05-01 17:06:46 -07:00
// RAR archive
if (scannable is RAR)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, stream, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
2022-12-27 01:07:46 -08:00
// SGA
if (scannable is SGA)
{
var subProtections = scannable.Scan(this, stream, fileName);
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
// Tape Archive
if (scannable is TapeArchive)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, stream, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
2022-12-26 14:52:12 -08:00
// VBSP
if (fileName != null && scannable is VBSP)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, fileName);
2022-12-24 15:31:38 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
// VPK
if (fileName != null && scannable is VPK)
{
var subProtections = scannable.Scan(this, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2020-10-30 23:56:27 -07:00
2022-12-26 14:52:12 -08:00
// WAD
if (scannable is WAD)
{
var subProtections = scannable.Scan(this, stream, fileName);
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
2022-05-01 17:06:46 -07:00
// XZ
if (scannable is XZ)
2022-05-01 17:06:46 -07:00
{
var subProtections = scannable.Scan(this, stream, fileName);
2022-12-15 00:13:24 -08:00
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
2022-05-01 17:06:46 -07:00
}
2022-12-26 14:52:12 -08:00
// XZP
if (scannable is XZP)
{
var subProtections = scannable.Scan(this, stream, fileName);
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
}
2022-05-01 17:06:46 -07:00
#endregion
}
2021-09-10 13:59:35 -07:00
catch (Exception ex)
{
2022-05-15 20:58:27 -07:00
if (IncludeDebug) Console.WriteLine(ex);
2022-12-15 00:13:24 -08:00
AppendToDictionary(protections, fileName, IncludeDebug ? ex.ToString() : "[Exception opening file, please try again]");
2020-10-30 23:56:27 -07:00
}
2020-10-31 21:20:16 -07:00
// Clear out any empty keys
2022-12-15 00:13:24 -08:00
ClearEmptyKeys(protections);
2020-10-31 21:20:16 -07:00
2020-10-30 23:56:27 -07:00
return protections;
}
2022-05-01 14:16:53 -07:00
#endregion
2020-10-30 23:56:27 -07:00
}
}