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 ;
2020-12-08 16:37:08 -08:00
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-07-31 23:17:12 -07:00
2024-10-23 22:04:52 -04:00
namespace Headerer.Features
2020-07-31 23:17:12 -07:00
{
internal class Restore : BaseFeature
{
public const string Value = "Restore" ;
public Restore ( )
{
Name = Value ;
2024-07-18 01:06:40 -04:00
Flags . AddRange ( [ "re" , "restore" ] ) ;
2020-07-31 23:17:12 -07:00
Description = "Restore header to file based on SHA-1" ;
2020-12-07 13:57:26 -08:00
_featureType = ParameterType . Flag ;
2020-09-04 23:17:27 -07:00
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.
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:48:23 -08:00
// Common Features
AddCommonFeatures ( ) ;
2024-03-06 00:53:32 -05:00
2020-07-31 23:17:12 -07:00
AddFeature ( OutputDirStringInput ) ;
}
2024-03-05 20:26:38 -05:00
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 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
RestoreHeader ( file . CurrentPath , OutputDir ) ;
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
}
2024-03-06 00:53:32 -05:00
2020-12-08 14:53:49 -08:00
/// <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>
2024-03-06 00:53:32 -05:00
public bool RestoreHeader ( string file , string? outDir )
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
// First, get the SHA-1 hash of the file
2024-03-06 00:53:32 -05:00
BaseFile ? baseFile = BaseFile . GetInfo ( file , hashes : [ HashType . SHA1 ] , asFiles : TreatAsFile . NonArchive ) ;
2020-12-08 14:53:49 -08:00
// Retrieve a list of all related headers from the database
2024-03-06 00:53:32 -05:00
List < string > headers = RetrieveHeadersFromDatabase ( TextHelper . ByteArrayToString ( baseFile ! . SHA1 ) ! ) ;
2020-12-08 14:53:49 -08:00
// If we have nothing retrieved, we return false
if ( headers . Count = = 0 )
return false ;
// Now loop through and create the reheadered files, if possible
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}" ) ;
2023-08-14 13:36:37 -04:00
AppendBytes ( file , outputFile , TextHelper . StringToByteArray ( headers [ i ] ) , null ) ;
2020-12-08 14:53:49 -08:00
logger . User ( "Reheadered file created!" ) ;
}
return true ;
}
/// <summary>
/// Retrieve headers from the database
/// </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 )
{
// 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
2024-03-06 00:53:32 -05:00
SqliteConnection dbc = new SqliteConnection ( HeadererConnectionString ) ;
2020-12-08 14:53:49 -08:00
dbc . Open ( ) ;
// Create the output list of headers
2024-03-06 00:53:32 -05:00
List < string > headers = [ ] ;
2020-12-08 14:53:49 -08:00
string query = $"SELECT header, type FROM data WHERE sha1='{SHA1}'" ;
2024-03-06 00:53:32 -05:00
SqliteCommand slc = new SqliteCommand ( query , dbc ) ;
2020-12-08 14:53:49 -08:00
SqliteDataReader sldr = slc . ExecuteReader ( ) ;
if ( sldr . HasRows )
{
while ( sldr . Read ( ) )
{
logger . Verbose ( $"Found match with rom type '{sldr.GetString(1)}'" ) ;
headers . Add ( sldr . GetString ( 0 ) ) ;
}
}
else
{
logger . Warning ( "No matching header could be found!" ) ;
}
// Dispose of database objects
slc . Dispose ( ) ;
sldr . Dispose ( ) ;
dbc . Dispose ( ) ;
return headers ;
}
2024-03-06 00:53:32 -05:00
2020-12-08 14:53:49 -08:00
/// <summary>
/// Add an aribtrary number of bytes to the inputted file
/// </summary>
/// <param name="input">File to be appended to</param>
/// <param name="output">Outputted file</param>
/// <param name="bytesToAddToHead">Bytes to be added to head of file</param>
/// <param name="bytesToAddToTail">Bytes to be added to tail of file</param>
2024-03-06 00:53:32 -05:00
private static void AppendBytes ( string input , string output , byte [ ] ? bytesToAddToHead , byte [ ] ? bytesToAddToTail )
2020-12-08 14:53:49 -08:00
{
// If any of the inputs are invalid, skip
if ( ! File . Exists ( input ) )
return ;
using FileStream fsr = File . OpenRead ( input ) ;
using FileStream fsw = File . OpenWrite ( output ) ;
2023-04-19 16:39:58 -04:00
2020-12-14 15:22:14 -08:00
AppendBytes ( fsr , fsw , bytesToAddToHead , bytesToAddToTail ) ;
2020-12-08 14:53:49 -08:00
}
/// <summary>
/// Add an aribtrary number of bytes to the inputted stream
/// </summary>
/// <param name="input">Stream to be appended to</param>
/// <param name="output">Outputted stream</param>
/// <param name="bytesToAddToHead">Bytes to be added to head of stream</param>
/// <param name="bytesToAddToTail">Bytes to be added to tail of stream</param>
2024-03-06 00:53:32 -05:00
private static void AppendBytes ( Stream input , Stream output , byte [ ] ? bytesToAddToHead , byte [ ] ? bytesToAddToTail )
2020-12-08 14:53:49 -08:00
{
// Write out prepended bytes
if ( bytesToAddToHead ! = null & & bytesToAddToHead . Length > 0 )
output . Write ( bytesToAddToHead , 0 , bytesToAddToHead . Length ) ;
// Now copy the existing file over
input . CopyTo ( output ) ;
// Write out appended bytes
if ( bytesToAddToTail ! = null & & bytesToAddToTail . Length > 0 )
output . Write ( bytesToAddToTail , 0 , bytesToAddToTail . Length ) ;
}
2020-07-31 23:17:12 -07:00
}
}