Files
SabreTools/Headerer/Features/Extract.cs

154 lines
5.9 KiB
C#
Raw Normal View History

2020-12-08 14:53:49 -08:00
using System.IO;
using System.Collections.Generic;
2024-03-04 23:56:05 -05:00
using Microsoft.Data.Sqlite;
using SabreTools.Core.Tools;
2020-12-08 14:53:49 -08:00
using SabreTools.FileTypes;
2024-03-04 23:56:05 -05:00
using SabreTools.Hashing;
2020-12-07 13:57:26 -08:00
using SabreTools.Help;
2020-12-07 15:08:57 -08:00
using SabreTools.IO;
2020-12-08 14:53:49 -08:00
using SabreTools.Skippers;
2020-07-31 23:17:12 -07:00
namespace Headerer.Features
2020-07-31 23:17:12 -07:00
{
internal class Extract : BaseFeature
{
public const string Value = "Extract";
public Extract()
{
Name = Value;
2024-07-18 01:06:40 -04:00
Flags.AddRange(["ex", "extract"]);
2020-07-31 23:17:12 -07:00
Description = "Extract and remove copier headers";
2020-12-07 13:57:26 -08:00
_featureType = ParameterType.Flag;
2020-09-04 23:17:27 -07:00
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.
2020-07-31 23:17:12 -07:00
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";
2021-02-03 10:09:40 -08:00
// Common Features
AddCommonFeatures();
2021-02-03 10:09:40 -08:00
2020-07-31 23:17:12 -07:00
AddFeature(OutputDirStringInput);
AddFeature(NoStoreHeaderFlag);
}
public override bool ProcessFeatures(Dictionary<string, Feature?> features)
2020-07-31 23:17:12 -07:00
{
2021-03-19 20:52:11 -07:00
// If the base fails, just fail out
if (!base.ProcessFeatures(features))
return false;
2020-07-31 23:17:12 -07:00
// Get feature flags
bool nostore = GetBoolean(features, NoStoreHeaderValue);
// Get only files from the inputs
2020-12-10 22:16:53 -08:00
List<ParentablePath> files = PathTool.GetFilesOnly(Inputs);
2020-07-31 23:17:12 -07:00
foreach (ParentablePath file in files)
{
2020-12-08 14:53:49 -08:00
DetectTransformStore(file.CurrentPath, OutputDir, nostore);
2020-07-31 23:17:12 -07:00
}
2021-03-19 20:52:11 -07:00
return true;
2020-07-31 23:17:12 -07:00
}
2020-12-08 14:53:49 -08:00
/// <summary>
/// Detect header skipper compliance and create an output 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>
/// <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)
2020-12-08 14:53:49 -08:00
{
2021-02-09 21:22:56 -08:00
// Create the output directory if it doesn't exist
if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
2020-12-08 14:53:49 -08:00
logger.User($"\nGetting skipper information for '{file}'");
// Get the skipper rule that matches the file, if any
2020-12-10 21:29:17 -08:00
SkipperMatch.Init();
Rule rule = SkipperMatch.GetMatchingRule(file, string.Empty);
2020-12-08 14:53:49 -08:00
// If we have an empty rule, return false
if (rule.Tests == null || rule.Tests.Length == 0 || rule.Operation != HeaderSkipOperation.None)
2020-12-08 14:53:49 -08:00
return false;
logger.User("File has a valid copier header");
// Get the header bytes from the file first
string hstr;
try
{
// Extract the header as a string for the database
using var fs = File.OpenRead(file);
int startOffset = int.Parse(rule.StartOffset ?? "0");
byte[] hbin = new byte[startOffset];
fs.Read(hbin, 0, startOffset);
hstr = TextHelper.ByteArrayToString(hbin)!;
2020-12-08 14:53:49 -08:00
}
catch
{
return false;
}
// Apply the rule to the file
string newfile = string.IsNullOrWhiteSpace(outDir) ? Path.GetFullPath(file) + ".new" : Path.Combine(outDir, Path.GetFileName(file));
2020-12-08 14:53:49 -08:00
rule.TransformFile(file, newfile);
// If the output file doesn't exist, return false
if (!File.Exists(newfile))
return false;
// Now add the information to the database if it's not already there
if (!nostore)
{
BaseFile? baseFile = BaseFile.GetInfo(newfile, hashes: [HashType.SHA1], asFiles: TreatAsFile.NonArchive);
AddHeaderToDatabase(hstr, TextHelper.ByteArrayToString(baseFile!.SHA1)!, rule.SourceFile!);
2020-12-08 14:53:49 -08:00
}
return true;
}
/// <summary>
/// Add a header to the database
/// </summary>
/// <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)
{
// Ensure the database exists
2020-12-09 14:33:47 -08:00
EnsureDatabase();
2020-12-08 14:53:49 -08:00
// Open the database connection
SqliteConnection dbc = new(HeadererConnectionString);
2020-12-08 14:53:49 -08:00
dbc.Open();
string query = $"SELECT * FROM data WHERE sha1='{SHA1}' AND header='{header}'";
SqliteCommand slc = new(query, dbc);
2020-12-08 14:53:49 -08:00
SqliteDataReader sldr = slc.ExecuteReader();
bool exists = sldr.HasRows;
if (!exists)
{
query = $"INSERT INTO data (sha1, header, type) VALUES ('{SHA1}', '{header}', '{source}')";
slc = new SqliteCommand(query, dbc);
logger.Verbose($"Result of inserting header: {slc.ExecuteNonQuery()}");
}
// Dispose of database objects
slc.Dispose();
sldr.Dispose();
dbc.Dispose();
}
2020-07-31 23:17:12 -07:00
}
}