mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Further disassociate Headerer
This commit is contained in:
43
Headerer/Database.cs
Normal file
43
Headerer/Database.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using SabreTools.IO;
|
||||
|
||||
namespace Headerer
|
||||
{
|
||||
internal static class Database
|
||||
{
|
||||
#region Constants
|
||||
|
||||
public static string HeadererFileName = Path.Combine(PathTool.GetRuntimeDirectory(), "Headerer.sqlite");
|
||||
public static string HeadererConnectionString = $"Data Source={HeadererFileName};";
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the database exists and has the proper schema
|
||||
/// </summary>
|
||||
public static void EnsureDatabase()
|
||||
{
|
||||
// Make sure the file exists
|
||||
if (!File.Exists(HeadererFileName))
|
||||
File.Create(HeadererFileName);
|
||||
|
||||
// Open the database connection
|
||||
SqliteConnection dbc = new(HeadererConnectionString);
|
||||
dbc.Open();
|
||||
|
||||
// Make sure the database has the correct schema
|
||||
string query = @"
|
||||
CREATE TABLE IF NOT EXISTS data (
|
||||
'sha1' TEXT NOT NULL,
|
||||
'header' TEXT NOT NULL,
|
||||
'type' TEXT NOT NULL,
|
||||
PRIMARY KEY (sha1, header, type)
|
||||
)";
|
||||
SqliteCommand slc = new(query, dbc);
|
||||
slc.ExecuteNonQuery();
|
||||
slc.Dispose();
|
||||
dbc.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,14 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Help;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Skippers;
|
||||
|
||||
namespace Headerer.Features
|
||||
namespace Headerer
|
||||
{
|
||||
internal class Extract : BaseFeature
|
||||
internal static class Extract
|
||||
{
|
||||
public const string Value = "Extract";
|
||||
|
||||
public Extract()
|
||||
{
|
||||
Name = Value;
|
||||
Flags.AddRange(["ex", "extract"]);
|
||||
Description = "Extract and remove copier headers";
|
||||
_featureType = ParameterType.Flag;
|
||||
LongDescription = @"This will detect, store, and remove copier headers from a file or folder of files. The headers are backed up and collated by the hash of the unheadered file. Files are then output without the detected copier header alongside the originals with the suffix .new. No input files are altered in the process. Only uncompressed files will be processed.
|
||||
|
||||
The following systems have headers that this program can work with:
|
||||
- Atari 7800
|
||||
- Atari Lynx
|
||||
- Commodore PSID Music
|
||||
- NEC PC - Engine / TurboGrafx 16
|
||||
- Nintendo Famicom / Nintendo Entertainment System
|
||||
- Nintendo Famicom Disk System
|
||||
- Nintendo Super Famicom / Super Nintendo Entertainment System
|
||||
- Nintendo Super Famicom / Super Nintendo Entertainment System SPC";
|
||||
|
||||
// Common Features
|
||||
AddCommonFeatures();
|
||||
|
||||
AddFeature(OutputDirStringInput);
|
||||
AddFeature(NoStoreHeaderFlag);
|
||||
}
|
||||
|
||||
public override bool ProcessFeatures(Dictionary<string, Feature?> features)
|
||||
{
|
||||
// If the base fails, just fail out
|
||||
if (!base.ProcessFeatures(features))
|
||||
return false;
|
||||
|
||||
// Get feature flags
|
||||
bool nostore = GetBoolean(features, NoStoreHeaderValue);
|
||||
|
||||
// Get only files from the inputs
|
||||
List<ParentablePath> files = PathTool.GetFilesOnly(Inputs);
|
||||
foreach (ParentablePath file in files)
|
||||
{
|
||||
DetectTransformStore(file.CurrentPath, OutputDir, nostore);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect header skipper compliance and create an output file
|
||||
/// </summary>
|
||||
@@ -64,13 +16,13 @@ The following systems have headers that this program can work with:
|
||||
/// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param>
|
||||
/// <param name="nostore">True if headers should not be stored in the database, false otherwise</param>
|
||||
/// <returns>True if the output file was created, false otherwise</returns>
|
||||
private bool DetectTransformStore(string file, string? outDir, bool nostore)
|
||||
public static bool DetectTransformStore(string file, string? outDir, bool nostore)
|
||||
{
|
||||
// Create the output directory if it doesn't exist
|
||||
if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir))
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
logger.User($"\nGetting skipper information for '{file}'");
|
||||
Console.WriteLine($"\nGetting skipper information for '{file}'");
|
||||
|
||||
// Get the skipper rule that matches the file, if any
|
||||
SkipperMatch.Init();
|
||||
@@ -80,7 +32,7 @@ The following systems have headers that this program can work with:
|
||||
if (rule.Tests == null || rule.Tests.Length == 0 || rule.Operation != HeaderSkipOperation.None)
|
||||
return false;
|
||||
|
||||
logger.User("File has a valid copier header");
|
||||
Console.WriteLine("File has a valid copier header");
|
||||
|
||||
// Get the header bytes from the file first
|
||||
string hstr;
|
||||
@@ -122,17 +74,17 @@ The following systems have headers that this program can work with:
|
||||
/// <param name="header">String representing the header bytes</param>
|
||||
/// <param name="SHA1">SHA-1 of the deheadered file</param>
|
||||
/// <param name="type">Name of the source skipper file</param>
|
||||
private void AddHeaderToDatabase(string header, string SHA1, string source)
|
||||
private static void AddHeaderToDatabase(string header, string SHA1, string source)
|
||||
{
|
||||
// Ensure the database exists
|
||||
EnsureDatabase();
|
||||
Database.EnsureDatabase();
|
||||
|
||||
// Open the database connection
|
||||
SqliteConnection dbc = new(HeadererConnectionString);
|
||||
SqliteConnection dbc = new(Database.HeadererConnectionString);
|
||||
dbc.Open();
|
||||
|
||||
string query = $"SELECT * FROM data WHERE sha1='{SHA1}' AND header='{header}'";
|
||||
SqliteCommand slc = new(query, dbc);
|
||||
var slc = new SqliteCommand(query, dbc);
|
||||
SqliteDataReader sldr = slc.ExecuteReader();
|
||||
bool exists = sldr.HasRows;
|
||||
|
||||
@@ -140,7 +92,7 @@ The following systems have headers that this program can work with:
|
||||
{
|
||||
query = $"INSERT INTO data (sha1, header, type) VALUES ('{SHA1}', '{header}', '{source}')";
|
||||
slc = new SqliteCommand(query, dbc);
|
||||
logger.Verbose($"Result of inserting header: {slc.ExecuteNonQuery()}");
|
||||
Console.WriteLine($"Result of inserting header: {slc.ExecuteNonQuery()}"); // TODO: Gate behind debug flag
|
||||
}
|
||||
|
||||
// Dispose of database objects
|
||||
10
Headerer/Feature.cs
Normal file
10
Headerer/Feature.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Headerer
|
||||
{
|
||||
internal enum Feature
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
Extract,
|
||||
Restore,
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using SabreTools.Core.Tools;
|
||||
using SabreTools.Help;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Logging;
|
||||
|
||||
namespace Headerer.Features
|
||||
{
|
||||
internal class BaseFeature : TopLevel
|
||||
{
|
||||
#region Logging
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
protected Logger logger = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
public static string HeadererFileName = Path.Combine(PathTool.GetRuntimeDirectory(), "Headerer.sqlite");
|
||||
public static string HeadererConnectionString = $"Data Source={HeadererFileName};";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Features
|
||||
|
||||
#region Flag features
|
||||
|
||||
internal const string NoStoreHeaderValue = "no-store-header";
|
||||
internal static Feature NoStoreHeaderFlag
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Feature(
|
||||
NoStoreHeaderValue,
|
||||
new List<string>() { "-nsh", "--no-store-header" },
|
||||
"Don't store the extracted header",
|
||||
ParameterType.Flag,
|
||||
longDescription: "By default, all headers that are removed from files are backed up in the database. This flag allows users to skip that step entirely, avoiding caching the headers at all.");
|
||||
}
|
||||
}
|
||||
|
||||
internal const string ScriptValue = "script";
|
||||
internal static Feature ScriptFlag
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Feature(
|
||||
ScriptValue,
|
||||
new List<string>() { "-sc", "--script" },
|
||||
"Enable script mode (no clear screen)",
|
||||
ParameterType.Flag,
|
||||
"For times when SabreTools is being used in a scripted environment, the user may not want the screen to be cleared every time that it is called. This flag allows the user to skip clearing the screen on run just like if the console was being redirected.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region String features
|
||||
|
||||
internal const string LogLevelStringValue = "log-level";
|
||||
internal static Feature LogLevelStringInput
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Feature(
|
||||
LogLevelStringValue,
|
||||
new List<string>() { "-ll", "--log-level" },
|
||||
"Set the lowest log level for output",
|
||||
ParameterType.String,
|
||||
longDescription: @"Set the lowest log level for output.
|
||||
Possible values are: Verbose, User, Warning, Error");
|
||||
}
|
||||
}
|
||||
|
||||
internal const string OutputDirStringValue = "output-dir";
|
||||
internal static Feature OutputDirStringInput
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Feature(
|
||||
OutputDirStringValue,
|
||||
new List<string>() { "-out", "--output-dir" },
|
||||
"Set output directory",
|
||||
ParameterType.String,
|
||||
longDescription: "This sets an output folder to be used when the files are created. If a path is not defined, the runtime directory is used instead.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Lowest log level for output
|
||||
/// </summary>
|
||||
public LogLevel LogLevel { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output directory
|
||||
/// </summary>
|
||||
protected string? OutputDir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if scripting mode is enabled
|
||||
/// </summary>
|
||||
public bool ScriptMode { get; protected set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Add Feature Groups
|
||||
|
||||
/// <summary>
|
||||
/// Add common features
|
||||
/// </summary>
|
||||
protected void AddCommonFeatures()
|
||||
{
|
||||
AddFeature(ScriptFlag);
|
||||
AddFeature(LogLevelStringInput);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override bool ProcessFeatures(Dictionary<string, Feature?> features)
|
||||
{
|
||||
LogLevel = GetString(features, LogLevelStringValue).AsLogLevel();
|
||||
OutputDir = GetString(features, OutputDirStringValue)?.Trim('"');
|
||||
ScriptMode = GetBoolean(features, ScriptValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Protected Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the database exists and has the proper schema
|
||||
/// </summary>
|
||||
protected static void EnsureDatabase()
|
||||
{
|
||||
// Make sure the file exists
|
||||
if (!File.Exists(HeadererFileName))
|
||||
File.Create(HeadererFileName);
|
||||
|
||||
// Open the database connection
|
||||
SqliteConnection dbc = new(HeadererConnectionString);
|
||||
dbc.Open();
|
||||
|
||||
// Make sure the database has the correct schema
|
||||
string query = @"
|
||||
CREATE TABLE IF NOT EXISTS data (
|
||||
'sha1' TEXT NOT NULL,
|
||||
'header' TEXT NOT NULL,
|
||||
'type' TEXT NOT NULL,
|
||||
PRIMARY KEY (sha1, header, type)
|
||||
)";
|
||||
SqliteCommand slc = new(query, dbc);
|
||||
slc.ExecuteNonQuery();
|
||||
slc.Dispose();
|
||||
dbc.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using SabreTools.Help;
|
||||
|
||||
namespace Headerer.Features
|
||||
{
|
||||
internal class DisplayHelp : BaseFeature
|
||||
{
|
||||
public const string Value = "Help";
|
||||
|
||||
public DisplayHelp()
|
||||
{
|
||||
Name = Value;
|
||||
Flags.AddRange(["?", "h", "help"]);
|
||||
Description = "Show this help";
|
||||
_featureType = ParameterType.Flag;
|
||||
LongDescription = "Built-in to most of the programs is a basic help text.";
|
||||
}
|
||||
|
||||
public override bool ProcessArgs(string[] args, FeatureSet help)
|
||||
{
|
||||
// If we had something else after help
|
||||
if (args.Length > 1)
|
||||
{
|
||||
help.OutputIndividualFeature(args[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, show generic help
|
||||
else
|
||||
{
|
||||
help.OutputGenericHelp();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using SabreTools.Help;
|
||||
|
||||
namespace Headerer.Features
|
||||
{
|
||||
internal class DisplayHelpDetailed : BaseFeature
|
||||
{
|
||||
public const string Value = "Help (Detailed)";
|
||||
|
||||
public DisplayHelpDetailed()
|
||||
{
|
||||
Name = Value;
|
||||
Flags.AddRange(["??", "hd", "help-detailed"]);
|
||||
Description = "Show this detailed help";
|
||||
_featureType = ParameterType.Flag;
|
||||
LongDescription = "Display a detailed help text to the screen.";
|
||||
}
|
||||
|
||||
public override bool ProcessArgs(string[] args, FeatureSet help)
|
||||
{
|
||||
// If we had something else after help
|
||||
if (args.Length > 1)
|
||||
{
|
||||
help.OutputIndividualFeature(args[1], includeLongDescription: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, show generic help
|
||||
else
|
||||
{
|
||||
help.OutputAllHelp();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,11 +35,6 @@
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.Core\SabreTools.Core.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Help\SabreTools.Help.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.10" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.2.2" />
|
||||
|
||||
129
Headerer/Options.cs
Normal file
129
Headerer/Options.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Headerer
|
||||
{
|
||||
internal sealed class Options
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Set of input paths to use for operations
|
||||
/// </summary>
|
||||
public List<string> InputPaths { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Represents the feature being called
|
||||
/// </summary>
|
||||
public Feature Feature { get; private set; } = Feature.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// Optional output directory
|
||||
/// </summary>
|
||||
public string? OutputDir { get; private set; }
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Disable storing copier headers on extract
|
||||
/// </summary>
|
||||
public bool NoStoreHeader { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Parse commandline arguments into an Options object
|
||||
/// </summary>
|
||||
public static Options? ParseOptions(string[] args)
|
||||
{
|
||||
// If we have invalid arguments
|
||||
if (args == null || args.Length == 0)
|
||||
return null;
|
||||
|
||||
// Create an Options object
|
||||
var options = new Options();
|
||||
|
||||
// Get the first argument as a feature flag
|
||||
string featureName = args[0];
|
||||
switch (featureName)
|
||||
{
|
||||
case "ex":
|
||||
case "extract":
|
||||
options.Feature = Feature.Extract;
|
||||
break;
|
||||
|
||||
case "re":
|
||||
case "restore":
|
||||
options.Feature = Feature.Restore;
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"{featureName} is not a recognized feature");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse the options and paths
|
||||
int index = 1;
|
||||
for (; index < args.Length; index++)
|
||||
{
|
||||
string arg = args[index];
|
||||
switch (arg)
|
||||
{
|
||||
case "-o":
|
||||
case "--outdir":
|
||||
options.OutputDir = index + 1 < args.Length ? args[++index] : string.Empty;
|
||||
break;
|
||||
|
||||
#region Extraction
|
||||
|
||||
case "-nsh":
|
||||
case "--no-store-header":
|
||||
options.NoStoreHeader = true;
|
||||
break;
|
||||
|
||||
#endregion
|
||||
|
||||
default:
|
||||
options.InputPaths.Add(arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate we have any input paths to work on
|
||||
if (options.InputPaths.Count == 0)
|
||||
{
|
||||
Console.WriteLine("At least one path is required!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display help text
|
||||
/// </summary>
|
||||
/// <param name="err">Additional error text to display, can be null to ignore</param>
|
||||
public static void DisplayHelp(string? err = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(err))
|
||||
Console.WriteLine($"Error: {err}");
|
||||
|
||||
Console.WriteLine("Headerer - Remove, store, and restore copier headers");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Headerer.exe <features> <options> file|directory ...");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Features:");
|
||||
Console.WriteLine("ex, extract Extract and remove copier headers");
|
||||
Console.WriteLine("re, restore Restore header to file based on SHA-1");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Common options:");
|
||||
Console.WriteLine("-?, -h, --help Display this help text and quit");
|
||||
Console.WriteLine("-o, --outdir [PATH] Set output directory");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Extraction options:");
|
||||
Console.WriteLine("-nsh, --no-store-header Set output path for extraction (required)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,179 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Headerer.Features;
|
||||
using SabreTools.Core;
|
||||
using SabreTools.Help;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Logging;
|
||||
|
||||
namespace Headerer
|
||||
namespace Headerer
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
#region Static Variables
|
||||
|
||||
/// <summary>
|
||||
/// Help object that determines available functionality
|
||||
/// </summary>
|
||||
private static FeatureSet? _help;
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
private static readonly Logger logger = new();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Entry point for the SabreTools application
|
||||
/// </summary>
|
||||
/// <param name="args">String array representing command line parameters</param>
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// Perform initial setup and verification
|
||||
LoggerImpl.SetFilename(Path.Combine(PathTool.GetRuntimeDirectory(), "logs", "headerer.log"), true);
|
||||
LoggerImpl.AppendPrefix = true;
|
||||
LoggerImpl.LowestLogLevel = LogLevel.VERBOSE;
|
||||
LoggerImpl.ThrowOnError = false;
|
||||
LoggerImpl.Start();
|
||||
|
||||
// Create a new Help object for this program
|
||||
_help = RetrieveHelp();
|
||||
|
||||
// Credits take precidence over all
|
||||
if (new List<string>(args).Contains("--credits"))
|
||||
// Validate the arguments
|
||||
if (args == null || args.Length == 0)
|
||||
{
|
||||
FeatureSet.OutputCredits();
|
||||
LoggerImpl.Close();
|
||||
Options.DisplayHelp("One input file path required");
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's no arguments, show help
|
||||
if (args.Length == 0)
|
||||
// Get the options from the arguments
|
||||
var options = Options.ParseOptions(args);
|
||||
|
||||
// If we have an invalid state
|
||||
if (options == null)
|
||||
{
|
||||
_help.OutputGenericHelp();
|
||||
LoggerImpl.Close();
|
||||
Options.DisplayHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the first argument as a feature flag
|
||||
string featureName = args[0];
|
||||
|
||||
// Verify that the flag is valid
|
||||
if (!_help.TopLevelFlag(featureName))
|
||||
// Loop through the input paths
|
||||
foreach (string inputPath in options.InputPaths)
|
||||
{
|
||||
logger.User($"'{featureName}' is not valid feature flag");
|
||||
_help.OutputIndividualFeature(featureName);
|
||||
LoggerImpl.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the proper name for the feature
|
||||
featureName = _help.GetFeatureName(featureName);
|
||||
|
||||
// Get the associated feature
|
||||
BaseFeature feature = (_help[featureName] as BaseFeature)!;
|
||||
|
||||
// If we had the help feature first
|
||||
if (featureName == DisplayHelp.Value || featureName == DisplayHelpDetailed.Value)
|
||||
// TODO: Do something with the output success flags
|
||||
switch (options.Feature)
|
||||
{
|
||||
feature.ProcessArgs(args, _help);
|
||||
LoggerImpl.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Now verify that all other flags are valid
|
||||
if (!feature.ProcessArgs(args, _help))
|
||||
{
|
||||
LoggerImpl.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the new log level based on settings
|
||||
LoggerImpl.LowestLogLevel = feature.LogLevel;
|
||||
|
||||
// If output is being redirected or we are in script mode, don't allow clear screens
|
||||
if (!Console.IsOutputRedirected && feature.ScriptMode)
|
||||
{
|
||||
Console.Clear();
|
||||
Globals.SetConsoleHeader("Headerer");
|
||||
}
|
||||
|
||||
// Now process the current feature
|
||||
Dictionary<string, Feature?> features = _help.GetEnabledFeatures();
|
||||
bool success = false;
|
||||
switch (featureName)
|
||||
{
|
||||
// No-op as these should be caught
|
||||
case DisplayHelp.Value:
|
||||
case DisplayHelpDetailed.Value:
|
||||
case Feature.Extract:
|
||||
_ = Extract.DetectTransformStore(inputPath, options.OutputDir, options.NoStoreHeader);
|
||||
break;
|
||||
|
||||
// Require input verification
|
||||
case Extract.Value:
|
||||
case Restore.Value:
|
||||
VerifyInputs(feature.Inputs, feature);
|
||||
success = feature.ProcessFeatures(features);
|
||||
break;
|
||||
|
||||
// If nothing is set, show the help
|
||||
default:
|
||||
_help.OutputGenericHelp();
|
||||
case Feature.Restore:
|
||||
_ = Restore.RestoreHeader(inputPath, options.OutputDir);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the feature failed, output help
|
||||
if (!success)
|
||||
{
|
||||
logger.Error("An error occurred during processing!");
|
||||
_help.OutputIndividualFeature(featureName);
|
||||
}
|
||||
|
||||
LoggerImpl.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a Help object for this program
|
||||
/// </summary>
|
||||
/// <returns>Populated Help object</returns>
|
||||
private static FeatureSet RetrieveHelp()
|
||||
{
|
||||
// Create and add the header to the Help object
|
||||
string barrier = "-----------------------------------------";
|
||||
List<string> helpHeader =
|
||||
[
|
||||
"Headerer - Remove, store, and restore copier headers",
|
||||
barrier,
|
||||
"Usage: Headerer [option] [flags] [filename|dirname] ...",
|
||||
string.Empty
|
||||
];
|
||||
|
||||
// Create the base help object with header
|
||||
var help = new FeatureSet(helpHeader);
|
||||
|
||||
// Add all of the features
|
||||
help.Add(new DisplayHelp());
|
||||
help.Add(new DisplayHelpDetailed());
|
||||
help.Add(new Extract());
|
||||
help.Add(new Restore());
|
||||
|
||||
return help;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that there are inputs, show help otherwise
|
||||
/// </summary>
|
||||
/// <param name="inputs">List of inputs</param>
|
||||
/// <param name="feature">Name of the current feature</param>
|
||||
private static void VerifyInputs(List<string> inputs, BaseFeature feature)
|
||||
{
|
||||
if (inputs.Count == 0)
|
||||
{
|
||||
logger.Error("This feature requires at least one input");
|
||||
_help?.OutputIndividualFeature(feature.Name);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,21 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Help;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace Headerer.Features
|
||||
namespace Headerer
|
||||
{
|
||||
internal class Restore : BaseFeature
|
||||
internal static class Restore
|
||||
{
|
||||
public const string Value = "Restore";
|
||||
|
||||
public Restore()
|
||||
{
|
||||
Name = Value;
|
||||
Flags.AddRange(["re", "restore"]);
|
||||
Description = "Restore header to file based on SHA-1";
|
||||
_featureType = ParameterType.Flag;
|
||||
LongDescription = @"This will make use of stored copier headers and reapply them to files if they match the included hash. More than one header can be applied to a file, so they will be output to new files, suffixed with .newX, where X is a number. No input files are altered in the process. Only uncompressed files will be processed.
|
||||
|
||||
The following systems have headers that this program can work with:
|
||||
- Atari 7800
|
||||
- Atari Lynx
|
||||
- Commodore PSID Music
|
||||
- NEC PC - Engine / TurboGrafx 16
|
||||
- Nintendo Famicom / Nintendo Entertainment System
|
||||
- Nintendo Famicom Disk System
|
||||
- Nintendo Super Famicom / Super Nintendo Entertainment System
|
||||
- Nintendo Super Famicom / Super Nintendo Entertainment System SPC";
|
||||
|
||||
// Common Features
|
||||
AddCommonFeatures();
|
||||
|
||||
AddFeature(OutputDirStringInput);
|
||||
}
|
||||
|
||||
public override bool ProcessFeatures(Dictionary<string, Feature?> features)
|
||||
{
|
||||
// If the base fails, just fail out
|
||||
if (!base.ProcessFeatures(features))
|
||||
return false;
|
||||
|
||||
// Get only files from the inputs
|
||||
List<ParentablePath> files = PathTool.GetFilesOnly(Inputs);
|
||||
foreach (ParentablePath file in files)
|
||||
{
|
||||
RestoreHeader(file.CurrentPath, OutputDir);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect and replace header(s) to the given file
|
||||
/// </summary>
|
||||
/// <param name="file">Name of the file to be parsed</param>
|
||||
/// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param>
|
||||
/// <returns>True if a header was found and appended, false otherwise</returns>
|
||||
public bool RestoreHeader(string file, string? outDir)
|
||||
public static bool RestoreHeader(string file, string? outDir)
|
||||
{
|
||||
// Create the output directory if it doesn't exist
|
||||
if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir))
|
||||
@@ -78,9 +35,9 @@ The following systems have headers that this program can work with:
|
||||
for (int i = 0; i < headers.Count; i++)
|
||||
{
|
||||
string outputFile = (string.IsNullOrWhiteSpace(outDir) ? $"{Path.GetFullPath(file)}.new" : Path.Combine(outDir, Path.GetFileName(file))) + i;
|
||||
logger.User($"Creating reheadered file: {outputFile}");
|
||||
Console.WriteLine($"Creating reheadered file: {outputFile}");
|
||||
AppendBytes(file, outputFile, ByteArrayExtensions.StringToByteArray(headers[i]), null);
|
||||
logger.User("Reheadered file created!");
|
||||
Console.WriteLine("Reheadered file created!");
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -91,33 +48,33 @@ The following systems have headers that this program can work with:
|
||||
/// </summary>
|
||||
/// <param name="SHA1">SHA-1 of the deheadered file</param>
|
||||
/// <returns>List of strings representing the headers to add</returns>
|
||||
private List<string> RetrieveHeadersFromDatabase(string SHA1)
|
||||
private static List<string> RetrieveHeadersFromDatabase(string SHA1)
|
||||
{
|
||||
// Ensure the database exists
|
||||
EnsureDatabase();
|
||||
Database.EnsureDatabase();
|
||||
|
||||
// Open the database connection
|
||||
SqliteConnection dbc = new SqliteConnection(HeadererConnectionString);
|
||||
var dbc = new SqliteConnection(Database.HeadererConnectionString);
|
||||
dbc.Open();
|
||||
|
||||
// Create the output list of headers
|
||||
List<string> headers = [];
|
||||
|
||||
string query = $"SELECT header, type FROM data WHERE sha1='{SHA1}'";
|
||||
SqliteCommand slc = new SqliteCommand(query, dbc);
|
||||
var slc = new SqliteCommand(query, dbc);
|
||||
SqliteDataReader sldr = slc.ExecuteReader();
|
||||
|
||||
if (sldr.HasRows)
|
||||
{
|
||||
while (sldr.Read())
|
||||
{
|
||||
logger.Verbose($"Found match with rom type '{sldr.GetString(1)}'");
|
||||
Console.WriteLine($"Found match with rom type '{sldr.GetString(1)}'"); // TODO: Gate behind debug flag
|
||||
headers.Add(sldr.GetString(0));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warning("No matching header could be found!");
|
||||
Console.Error.WriteLine("No matching header could be found!");
|
||||
}
|
||||
|
||||
// Dispose of database objects
|
||||
Reference in New Issue
Block a user