diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs
new file mode 100644
index 00000000..3c89bcc2
--- /dev/null
+++ b/ProtectionScan/Features/MainFeature.cs
@@ -0,0 +1,185 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using BinaryObjectScanner;
+using SabreTools.CommandLine;
+using SabreTools.CommandLine.Inputs;
+
+namespace ProtectionScan.Features
+{
+ internal sealed class MainFeature : Feature
+ {
+ #region Feature Definition
+
+ public const string DisplayName = "main";
+
+ /// Flags are unused
+ private static readonly string[] _flags = [];
+
+ /// Description is unused
+ private const string _description = "";
+
+ #endregion
+
+ #region Inputs
+
+ private const string _debugName = "debug";
+ internal readonly FlagInput DebugInput = new(_debugName, ["-d", "--debug"], "Enable debug mode");
+
+ private const string _noArchivesName = "no-archives";
+ internal readonly FlagInput NoArchivesInput = new(_noArchivesName, ["-na", "--no-archives"], "Disable scanning archives");
+
+ private const string _noContentsName = "no-contents";
+ internal readonly FlagInput NoContentsInput = new(_noContentsName, ["-nc", "--no-contents"], "Disable scanning for content checks");
+
+ private const string _noPathsName = "no-paths";
+ internal readonly FlagInput NoPathsInput = new(_noPathsName, ["-np", "--no-paths"], "Disable scanning for path checks");
+
+ private const string _noSubdirsName = "no-subdirs";
+ internal readonly FlagInput NoSubdirsInput = new(_noSubdirsName, ["-ns", "--no-subdirs"], "Disable scanning subdirectories");
+
+ #endregion
+
+ public MainFeature()
+ : base(DisplayName, _flags, _description)
+ {
+ RequiresInputs = true;
+
+ Add(DebugInput);
+ Add(NoContentsInput);
+ Add(NoArchivesInput);
+ Add(NoPathsInput);
+ Add(NoSubdirsInput);
+ }
+
+ ///
+ public override bool Execute()
+ {
+ // Create progress indicator
+ var fileProgress = new Progress();
+ fileProgress.ProgressChanged += Changed;
+
+ // Create scanner for all paths
+ var scanner = new Scanner(
+ !GetBoolean(_noArchivesName),
+ !GetBoolean(_noContentsName),
+ !GetBoolean(_noPathsName),
+ !GetBoolean(_noSubdirsName),
+ !GetBoolean(_debugName),
+ fileProgress);
+
+ // Loop through the input paths
+ for (int i = 0; i < Inputs.Count; i++)
+ {
+ string arg = Inputs[i];
+ GetAndWriteProtections(scanner, arg);
+ }
+
+ return true;
+ }
+
+ ///
+ public override bool VerifyInputs() => Inputs.Count > 0;
+
+ ///
+ /// Protection progress changed handler
+ ///
+ private static void Changed(object? source, ProtectionProgress value)
+ {
+ string prefix = string.Empty;
+ for (int i = 0; i < value.Depth; i++)
+ {
+ prefix += "--> ";
+ }
+
+ Console.WriteLine($"{prefix}{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}");
+ }
+
+ ///
+ /// Wrapper to get and log protections for a single path
+ ///
+ /// Scanner object to use
+ /// File or directory path
+ private static void GetAndWriteProtections(Scanner scanner, string path)
+ {
+ // Normalize by getting the full path
+ path = Path.GetFullPath(path);
+
+ // An invalid path can't be scanned
+ if (!Directory.Exists(path) && !File.Exists(path))
+ {
+ Console.WriteLine($"{path} does not exist, skipping...");
+ return;
+ }
+
+ try
+ {
+ var protections = scanner.GetProtections(path);
+ WriteProtectionResultFile(path, protections);
+ }
+ catch (Exception ex)
+ {
+ try
+ {
+ using var sw = new StreamWriter(File.OpenWrite($"exception-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.txt"));
+ sw.WriteLine(ex);
+ }
+ catch
+ {
+ Console.WriteLine("Could not open exception log file for writing. See original message below:");
+ Console.WriteLine(ex);
+ }
+ }
+ }
+
+ ///
+ /// Write the protection results from a single path to file, if possible
+ ///
+ /// File or directory path
+ /// Dictionary of protections found, if any
+ private static void WriteProtectionResultFile(string path, Dictionary> protections)
+ {
+ if (protections == null)
+ {
+ Console.WriteLine($"No protections found for {path}");
+ return;
+ }
+
+ // Attempt to open a protection file for writing
+ StreamWriter? sw = null;
+ try
+ {
+ sw = new StreamWriter(File.OpenWrite($"protection-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.txt"));
+ }
+ catch
+ {
+ Console.WriteLine("Could not open protection log file for writing. Only a console log will be provided.");
+ }
+
+ // Sort the keys for consistent output
+ string[] keys = [.. protections.Keys];
+ Array.Sort(keys);
+
+ // Loop over all keys
+ foreach (string key in keys)
+ {
+ // Skip over files with no protection
+ var value = protections[key];
+ if (value.Count == 0)
+ continue;
+
+ // Sort the detected protections for consistent output
+ string[] fileProtections = [.. value];
+ Array.Sort(fileProtections);
+
+ // Format and output the line
+ string line = $"{key}: {string.Join(", ", fileProtections)}";
+ Console.WriteLine(line);
+ sw?.WriteLine(line);
+ }
+
+ // Dispose of the writer
+ sw?.Dispose();
+ }
+ }
+}
diff --git a/ProtectionScan/Program.cs b/ProtectionScan/Program.cs
index 752fa139..8154ed82 100644
--- a/ProtectionScan/Program.cs
+++ b/ProtectionScan/Program.cs
@@ -1,38 +1,23 @@
using System;
using System.Collections.Generic;
-using System.IO;
-using BinaryObjectScanner;
+using ProtectionScan.Features;
using SabreTools.CommandLine;
-using SabreTools.CommandLine.Inputs;
+using SabreTools.CommandLine.Features;
namespace ProtectionScan
{
- class Program
+ public static class Program
{
- #region Constants
-
- private const string _debugName = "debug";
- private const string _helpName = "help";
- private const string _noArchivesName = "no-archives";
- private const string _noContentsName = "no-contents";
- private const string _noPathsName = "no-paths";
- private const string _noSubdirsName = "no-subdirs";
-
- #endregion
-
- static void Main(string[] args)
+ public static void Main(string[] args)
{
#if NET462_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
// Register the codepages
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
#endif
- // Create progress indicator
- var fileProgress = new Progress();
- fileProgress.ProgressChanged += Changed;
-
// Create the command set
- var commandSet = CreateCommands();
+ var mainFeature = new MainFeature();
+ var commandSet = CreateCommands(mainFeature);
// If we have no args, show the help and quit
if (args == null || args.Length == 0)
@@ -41,47 +26,39 @@ namespace ProtectionScan
return;
}
- // Loop through and process the options
- int firstFileIndex = 0;
- for (; firstFileIndex < args.Length; firstFileIndex++)
- {
- string arg = args[firstFileIndex];
+ // Cache the first argument and starting index
+ string featureName = args[0];
- var input = commandSet.GetTopLevel(arg);
- if (input == null)
+ // Try processing the standalone arguments
+ var topLevel = commandSet.GetTopLevel(featureName);
+ switch (topLevel)
+ {
+ // Standalone Options
+ case Help help: help.ProcessArgs(args, 0, commandSet); return;
+
+ // Default Behavior
+ default:
+ if (!mainFeature.ProcessArgs(args, 0))
+ {
+ commandSet.OutputAllHelp();
+ return;
+ }
+ else if (!mainFeature.VerifyInputs())
+ {
+ Console.Error.WriteLine("At least one input is required");
+ commandSet.OutputAllHelp();
+ return;
+ }
+
+ mainFeature.Execute();
break;
-
- input.ProcessInput(args, ref firstFileIndex);
- }
-
- // If help was specified
- if (commandSet.GetBoolean(_helpName))
- {
- commandSet.OutputAllHelp();
- return;
- }
-
- // Create scanner for all paths
- var scanner = new Scanner(
- !commandSet.GetBoolean(_noArchivesName),
- !commandSet.GetBoolean(_noContentsName),
- !commandSet.GetBoolean(_noPathsName),
- !commandSet.GetBoolean(_noSubdirsName),
- !commandSet.GetBoolean(_debugName),
- fileProgress);
-
- // Loop through the input paths
- for (int i = firstFileIndex; i < args.Length; i++)
- {
- string arg = args[i];
- GetAndWriteProtections(scanner, arg);
}
}
///
/// Create the command set for the program
///
- private static CommandSet CreateCommands()
+ private static CommandSet CreateCommands(MainFeature mainFeature)
{
List header = [
"Protection Scanner",
@@ -92,115 +69,14 @@ namespace ProtectionScan
var commandSet = new CommandSet(header);
- commandSet.Add(new FlagInput(_helpName, ["-?", "-h", "--help"], "Display this help text"));
- commandSet.Add(new FlagInput(_debugName, ["-d", "--debug"], "Enable debug mode"));
- commandSet.Add(new FlagInput(_noContentsName, ["-nc", "--no-contents"], "Disable scanning for content checks"));
- commandSet.Add(new FlagInput(_noArchivesName, ["-na", "--no-archives"], "Disable scanning archives"));
- commandSet.Add(new FlagInput(_noPathsName, ["-np", "--no-paths"], "Disable scanning for path checks"));
- commandSet.Add(new FlagInput(_noSubdirsName, ["-ns", "--no-subdirs"], "Disable scanning subdirectories"));
+ commandSet.Add(new Help(["-?", "-h", "--help"]));
+ commandSet.Add(mainFeature.DebugInput);
+ commandSet.Add(mainFeature.NoContentsInput);
+ commandSet.Add(mainFeature.NoArchivesInput);
+ commandSet.Add(mainFeature.NoPathsInput);
+ commandSet.Add(mainFeature.NoSubdirsInput);
return commandSet;
}
-
- ///
- /// Wrapper to get and log protections for a single path
- ///
- /// Scanner object to use
- /// File or directory path
- private static void GetAndWriteProtections(Scanner scanner, string path)
- {
- // Normalize by getting the full path
- path = Path.GetFullPath(path);
-
- // An invalid path can't be scanned
- if (!Directory.Exists(path) && !File.Exists(path))
- {
- Console.WriteLine($"{path} does not exist, skipping...");
- return;
- }
-
- try
- {
- var protections = scanner.GetProtections(path);
- WriteProtectionResultFile(path, protections);
- }
- catch (Exception ex)
- {
- try
- {
- using var sw = new StreamWriter(File.OpenWrite($"exception-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.txt"));
- sw.WriteLine(ex);
- }
- catch
- {
- Console.WriteLine("Could not open exception log file for writing. See original message below:");
- Console.WriteLine(ex);
- }
- }
- }
-
- ///
- /// Write the protection results from a single path to file, if possible
- ///
- /// File or directory path
- /// Dictionary of protections found, if any
- private static void WriteProtectionResultFile(string path, Dictionary> protections)
- {
- if (protections == null)
- {
- Console.WriteLine($"No protections found for {path}");
- return;
- }
-
- // Attempt to open a protection file for writing
- StreamWriter? sw = null;
- try
- {
- sw = new StreamWriter(File.OpenWrite($"protection-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.txt"));
- }
- catch
- {
- Console.WriteLine("Could not open protection log file for writing. Only a console log will be provided.");
- }
-
- // Sort the keys for consistent output
- string[] keys = [.. protections.Keys];
- Array.Sort(keys);
-
- // Loop over all keys
- foreach (string key in keys)
- {
- // Skip over files with no protection
- var value = protections[key];
- if (value.Count == 0)
- continue;
-
- // Sort the detected protections for consistent output
- string[] fileProtections = [.. value];
- Array.Sort(fileProtections);
-
- // Format and output the line
- string line = $"{key}: {string.Join(", ", fileProtections)}";
- Console.WriteLine(line);
- sw?.WriteLine(line);
- }
-
- // Dispose of the writer
- sw?.Dispose();
- }
-
- ///
- /// Protection progress changed handler
- ///
- private static void Changed(object? source, ProtectionProgress value)
- {
- string prefix = string.Empty;
- for (int i = 0; i < value.Depth; i++)
- {
- prefix += "--> ";
- }
-
- Console.WriteLine($"{prefix}{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}");
- }
}
}