2020-10-30 23:56:27 -07:00
using System ;
2021-07-18 09:44:23 -07:00
using System.Collections.Concurrent ;
2020-10-30 23:56:27 -07:00
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2021-07-18 09:44:23 -07:00
using System.Threading.Tasks ;
2020-10-30 23:56:27 -07:00
using BurnOutSharp.FileType ;
2021-08-25 15:09:42 -07:00
using BurnOutSharp.Tools ;
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>
2022-05-01 14:11:09 -07:00
/// Determines whether archives are decompressed and scanned
2020-10-30 23:56:27 -07:00
/// </summary>
2022-05-01 14:11:09 -07:00
public bool ScanArchives { get ; private set ; }
2020-10-30 23:56:27 -07:00
/// <summary>
2022-05-01 14:11:09 -07:00
/// Determines if packers are counted as detected protections or not
2020-10-30 23:56:27 -07:00
/// </summary>
2022-05-01 14:11:09 -07:00
public bool ScanPackers { get ; private set ; }
2020-10-30 23:56:27 -07:00
/// <summary>
2022-05-01 14:11:09 -07:00
/// Determines if debug information is output or not
2020-10-30 23:56:27 -07:00
/// </summary>
2022-05-01 14:11:09 -07:00
public bool IncludeDebug { get ; private set ; }
2020-10-30 23:56:27 -07:00
2022-05-01 16:59:03 -07:00
#endregion
2020-10-31 14:46:08 -07:00
/// <summary>
2022-05-01 14:11:09 -07:00
/// Optional progress callback during scanning
2020-10-31 14:46:08 -07:00
/// </summary>
2022-05-01 16:59:03 -07:00
private readonly IProgress < ProtectionProgress > fileProgress ;
2022-05-01 14:16:53 -07:00
2020-10-30 23:56:27 -07:00
/// <summary>
/// Constructor
/// </summary>
2022-05-01 14:11:09 -07:00
/// <param name="scanArchives">Enable scanning archive contents</param>
/// <param name="scanPackers">Enable including packers 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>
2022-05-01 14:11:09 -07:00
public Scanner ( bool scanArchives , bool scanPackers , bool includeDebug , IProgress < ProtectionProgress > fileProgress = null )
2020-10-30 23:56:27 -07:00
{
2022-05-01 14:11:09 -07:00
ScanArchives = scanArchives ;
ScanPackers = scanPackers ;
IncludeDebug = includeDebug ;
2022-05-01 16:59:03 -07:00
this . fileProgress = fileProgress ;
2020-10-30 23:56:27 -07:00
}
2022-05-01 14:16:53 -07:00
#region Scanning
2020-10-31 00:06:41 -07:00
/// <summary>
2020-10-31 13:20:54 -07:00
/// Scan a single path and get all found protections
2020-10-31 00:06:41 -07:00
/// </summary>
2020-10-31 13:20:54 -07:00
/// <param name="path">Path to scan</param>
/// <returns>Dictionary of list of strings representing the found protections</returns>
2021-07-18 09:44:23 -07:00
public ConcurrentDictionary < string , ConcurrentQueue < string > > GetProtections ( string path )
2020-10-31 00:06:41 -07:00
{
2020-10-31 13:20:54 -07:00
return GetProtections ( new List < string > { path } ) ;
2020-10-31 00:06:41 -07:00
}
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>
2021-07-18 09:44:23 -07:00
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
2020-10-31 13:20:54 -07:00
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
2022-05-01 16:59:03 -07:00
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
2021-07-18 09:44:23 -07:00
var protections = new ConcurrentDictionary < string , ConcurrentQueue < string > > ( ) ;
2020-10-31 13:20:54 -07:00
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
2021-03-19 15:48:53 -07:00
var directoryPathProtections = GetDirectoryPathProtections ( path , files ) ;
2020-10-31 14:00:31 -07:00
Utilities . AppendToDictionary ( protections , directoryPathProtections ) ;
2020-10-30 23:56:27 -07:00
// Scan each file in directory separately
2021-03-22 21:25:14 -07:00
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
2022-05-01 16:59:03 -07:00
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
2021-03-19 15:48:53 -07:00
var filePathProtections = GetFilePathProtections ( file ) ;
2020-10-31 14:00:31 -07:00
Utilities . 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 ( ) )
{
foreach ( string key in fileProtections . Keys )
{
if ( ! protections . ContainsKey ( key ) )
2021-07-18 09:44:23 -07:00
protections [ key ] = new ConcurrentQueue < string > ( ) ;
2020-10-30 23:56:27 -07:00
protections [ key ] . AddRange ( fileProtections [ key ] ) ;
}
}
2020-10-31 14:00:31 -07:00
// Checkpoint
2021-07-18 09:44:23 -07:00
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 ) ;
2022-05-01 16:59:03 -07:00
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
2022-05-01 16:59:03 -07:00
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
2021-03-19 15:48:53 -07:00
var filePathProtections = GetFilePathProtections ( path ) ;
2020-10-31 14:00:31 -07:00
Utilities . 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 ( ) )
{
foreach ( string key in fileProtections . Keys )
{
if ( ! protections . ContainsKey ( key ) )
2021-07-18 09:44:23 -07:00
protections [ key ] = new ConcurrentQueue < string > ( ) ;
2020-10-30 23:56:27 -07:00
protections [ key ] . AddRange ( fileProtections [ key ] ) ;
}
}
2020-10-31 14:00:31 -07:00
// Checkpoint
2021-07-18 09:44:23 -07:00
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 ) ;
2022-05-01 16:59:03 -07:00
this . fileProgress ? . Report ( new ProtectionProgress ( reportableFileName , 1 , fullProtection ? ? string . Empty ) ) ;
2020-10-30 23:56:27 -07:00
}
// Throw on an invalid path
else
{
2020-10-31 14:14:35 -07:00
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
Utilities . ClearEmptyKeys ( protections ) ;
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 ;
}
2021-03-19 15:56:07 -07:00
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>
2021-07-18 09:44:23 -07:00
private ConcurrentDictionary < string , ConcurrentQueue < string > > GetDirectoryPathProtections ( string path , List < string > files )
2020-10-30 23:56:27 -07:00
{
2021-07-18 09:44:23 -07:00
// Create an empty queue for protections
var protections = new ConcurrentQueue < string > ( ) ;
2021-03-02 15:10:52 -08:00
2021-07-17 22:31:29 -07:00
// 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 = >
2021-03-02 15:10:52 -08:00
{
2021-07-18 09:44:23 -07:00
ConcurrentQueue < string > protection = pathCheckClass . CheckDirectoryPath ( path , files ) ;
2021-03-23 13:35:12 -07:00
if ( protection ! = null )
protections . AddRange ( protection ) ;
2021-07-18 09:44:23 -07:00
} ) ;
2021-03-02 15:10:52 -08:00
2021-03-19 15:48:53 -07:00
// Create and return the dictionary
2021-07-18 09:44:23 -07:00
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>
2021-07-18 09:44:23 -07:00
private ConcurrentDictionary < string , ConcurrentQueue < string > > GetFilePathProtections ( string path )
2021-03-19 15:48:53 -07:00
{
2021-07-18 09:44:23 -07:00
// Create an empty queue for protections
var protections = new ConcurrentQueue < string > ( ) ;
2021-03-19 15:48:53 -07:00
2021-03-02 15:10:52 -08:00
// Iterate through all path checks
2022-05-01 14:46:01 -07:00
Parallel . ForEach ( ScanningClasses . PathCheckClasses , pathCheckClass = >
2021-03-02 15:10:52 -08:00
{
2021-07-17 22:31:29 -07:00
string protection = pathCheckClass . CheckFilePath ( path . Replace ( "\\" , "/" ) ) ;
2021-03-02 15:10:52 -08:00
if ( ! string . IsNullOrWhiteSpace ( protection ) )
2021-07-18 09:44:23 -07:00
protections . Enqueue ( protection ) ;
} ) ;
2020-10-30 23:56:27 -07:00
// Create and return the dictionary
2021-07-18 09:44:23 -07:00
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>
2021-07-18 09:44:23 -07:00
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
{
using ( FileStream fs = File . OpenRead ( file ) )
{
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 > > ( ) ;
Utilities . AppendToDictionary ( protections , file , "[Exception opening file, please try again]" ) ;
Utilities . ClearEmptyKeys ( protections ) ;
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
2021-07-18 09:44:23 -07:00
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
2021-09-01 22:22:14 -07:00
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 ;
}
#region Non - Archive File Types
// Executable
if ( new Executable ( ) . ShouldScan ( magic ) )
{
var subProtections = new Executable ( ) . Scan ( this , stream , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
// Text-based files
if ( new Textfile ( ) . ShouldScan ( magic , extension ) )
{
var subProtections = new Textfile ( ) . Scan ( this , stream , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
#endregion
#region Archive File Types
// If we're scanning archives, we have a few to try out
if ( ScanArchives )
{
// 7-Zip archive
if ( new SevenZip ( ) . ShouldScan ( magic ) )
2020-10-31 00:06:41 -07:00
{
2022-05-01 17:06:46 -07:00
var subProtections = new SevenZip ( ) . Scan ( this , stream , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
2020-10-31 00:06:41 -07:00
}
2022-05-01 17:06:46 -07:00
// BFPK archive
if ( new BFPK ( ) . ShouldScan ( magic ) )
2020-10-31 00:06:41 -07:00
{
2022-05-01 17:06:46 -07:00
var subProtections = new BFPK ( ) . Scan ( this , stream , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
2020-10-31 00:06:41 -07:00
}
2020-10-30 23:56:27 -07:00
2022-05-01 17:06:46 -07:00
// BZip2
if ( new BZip2 ( ) . ShouldScan ( magic ) )
2020-10-31 00:06:41 -07:00
{
2022-05-01 17:06:46 -07:00
var subProtections = new BZip2 ( ) . Scan ( this , stream , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
2020-10-31 14:00:31 -07:00
Utilities . AppendToDictionary ( protections , subProtections ) ;
2021-07-21 13:40:32 -07:00
}
2022-05-01 17:06:46 -07:00
// GZIP
if ( new GZIP ( ) . ShouldScan ( magic ) )
2021-07-21 13:40:32 -07:00
{
2022-05-01 17:06:46 -07:00
var subProtections = new GZIP ( ) . Scan ( this , stream , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
2021-07-21 13:40:32 -07:00
Utilities . AppendToDictionary ( protections , subProtections ) ;
2020-10-31 00:06:41 -07:00
}
2020-10-30 23:56:27 -07:00
2022-05-01 17:06:46 -07:00
// InstallShield Archive V3 (Z)
if ( fileName ! = null & & new InstallShieldArchiveV3 ( ) . ShouldScan ( magic ) )
2020-10-31 00:06:41 -07:00
{
2022-05-01 17:06:46 -07:00
var subProtections = new InstallShieldArchiveV3 ( ) . Scan ( this , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2020-10-30 23:56:27 -07:00
2022-05-01 17:06:46 -07:00
// InstallShield Cabinet
if ( fileName ! = null & & new InstallShieldCAB ( ) . ShouldScan ( magic ) )
{
var subProtections = new InstallShieldCAB ( ) . Scan ( this , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2021-09-01 22:22:14 -07:00
2022-05-01 17:06:46 -07:00
// Microsoft Cabinet
if ( fileName ! = null & & new MicrosoftCAB ( ) . ShouldScan ( magic ) )
{
var subProtections = new MicrosoftCAB ( ) . Scan ( this , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2021-09-01 22:22:14 -07:00
2022-05-01 17:06:46 -07:00
// MSI
if ( fileName ! = null & & new MSI ( ) . ShouldScan ( magic ) )
{
var subProtections = new MSI ( ) . Scan ( this , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2021-09-01 22:22:14 -07:00
2022-05-01 17:06:46 -07:00
// MPQ archive
if ( fileName ! = null & & new MPQ ( ) . ShouldScan ( magic ) )
{
var subProtections = new MPQ ( ) . Scan ( this , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2021-09-01 22:22:14 -07:00
2022-05-01 17:06:46 -07:00
// PKZIP archive (and derivatives)
if ( new PKZIP ( ) . ShouldScan ( magic ) )
{
var subProtections = new PKZIP ( ) . Scan ( this , stream , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2021-09-01 22:22:14 -07:00
2022-05-01 17:06:46 -07:00
// RAR archive
if ( new RAR ( ) . ShouldScan ( magic ) )
{
var subProtections = new RAR ( ) . Scan ( this , stream , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2021-09-01 22:22:14 -07:00
2022-05-01 17:06:46 -07:00
// Tape Archive
if ( new TapeArchive ( ) . ShouldScan ( magic ) )
{
var subProtections = new TapeArchive ( ) . Scan ( this , stream , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2021-09-01 22:22:14 -07:00
2022-05-01 17:06:46 -07:00
// Valve archive formats
if ( fileName ! = null & & new Valve ( ) . ShouldScan ( magic ) )
{
var subProtections = new Valve ( ) . Scan ( this , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
2020-10-31 00:06:41 -07:00
}
2020-10-30 23:56:27 -07:00
2022-05-01 17:06:46 -07:00
// XZ
if ( new XZ ( ) . ShouldScan ( magic ) )
{
var subProtections = new XZ ( ) . Scan ( this , stream , fileName ) ;
Utilities . PrependToKeys ( subProtections , fileName ) ;
Utilities . AppendToDictionary ( protections , subProtections ) ;
}
2021-09-01 22:22:14 -07:00
}
2022-05-01 17:06:46 -07:00
#endregion
2021-09-01 22:22:14 -07:00
}
2021-09-10 13:59:35 -07:00
catch ( Exception ex )
2021-09-01 22:22:14 -07:00
{
2022-05-15 20:58:27 -07:00
if ( IncludeDebug ) Console . WriteLine ( ex ) ;
2022-05-01 17:06:46 -07:00
Utilities . AppendToDictionary ( protections , fileName , "[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
Utilities . ClearEmptyKeys ( protections ) ;
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
}
}