2016-11-07 21:31:52 -08:00
using System ;
2016-09-26 17:52:20 -07:00
using System.Collections.Generic ;
2016-06-13 22:12:00 -07:00
using System.Linq ;
2016-08-29 16:55:55 -07:00
using System.Security.Cryptography ;
2016-09-22 17:14:23 -07:00
using System.Xml ;
2016-09-28 12:27:39 -07:00
using System.Xml.Schema ;
2016-10-24 13:51:39 -07:00
2016-10-24 12:58:57 -07:00
using SabreTools.Helper.Data ;
using SabreTools.Helper.Dats ;
2016-10-24 13:42:28 -07:00
using SabreTools.Helper.Skippers ;
2016-10-24 13:51:39 -07:00
2016-10-28 21:49:29 -07:00
#if MONO
2016-10-27 11:35:17 -07:00
using System.IO ;
#else
2016-10-26 22:10:47 -07:00
using Alphaleonis.Win32.Filesystem ;
2016-06-13 20:57:49 -07:00
2016-10-26 22:10:47 -07:00
using BinaryReader = System . IO . BinaryReader ;
using BinaryWriter = System . IO . BinaryWriter ;
using FileStream = System . IO . FileStream ;
using IOException = System . IO . IOException ;
using MemoryStream = System . IO . MemoryStream ;
2016-10-31 13:46:29 -07:00
using PathTooLongException = System . IO . PathTooLongException ;
2016-10-26 22:10:47 -07:00
using SearchOption = System . IO . SearchOption ;
using SeekOrigin = System . IO . SeekOrigin ;
using Stream = System . IO . Stream ;
using StreamReader = System . IO . StreamReader ;
2016-10-30 21:15:33 -07:00
#endif
using NaturalSort ;
using OCRC ;
2016-10-26 22:10:47 -07:00
2016-10-24 12:58:57 -07:00
namespace SabreTools.Helper.Tools
2016-06-13 20:57:49 -07:00
{
2016-10-21 16:25:22 -07:00
public static class FileTools
2016-06-13 20:57:49 -07:00
{
2016-09-22 21:00:18 -07:00
#region File Information
2016-06-15 14:55:06 -07:00
2016-09-26 17:52:20 -07:00
/// <summary>
/// Retrieve a list of files from a directory recursively in proper order
/// </summary>
/// <param name="directory">Directory to parse</param>
/// <param name="infiles">List representing existing files</param>
/// <returns>List with all new files</returns>
public static List < string > RetrieveFiles ( string directory , List < string > infiles )
{
// Take care of the files in the top directory
List < string > toadd = Directory . EnumerateFiles ( directory , "*" , SearchOption . TopDirectoryOnly ) . ToList ( ) ;
2016-10-28 12:59:52 -07:00
toadd . Sort ( new NaturalComparer ( ) ) ;
2016-09-26 17:52:20 -07:00
infiles . AddRange ( toadd ) ;
// Then recurse through and add from the directories
List < string > dirs = Directory . EnumerateDirectories ( directory , "*" , SearchOption . TopDirectoryOnly ) . ToList ( ) ;
2016-10-28 15:13:29 -07:00
dirs = Style . OrderByAlphaNumeric ( dirs , s = > s ) . ToList ( ) ;
2016-09-26 17:52:20 -07:00
foreach ( string dir in dirs )
{
infiles = RetrieveFiles ( dir , infiles ) ;
}
// Return the new list
return infiles ;
}
2016-06-13 22:12:00 -07:00
/// <summary>
2016-09-22 21:00:18 -07:00
/// Get what type of DAT the input file is
2016-06-13 22:12:00 -07:00
/// </summary>
2016-09-22 21:00:18 -07:00
/// <param name="filename">Name of the file to be parsed</param>
2016-10-25 15:02:02 -07:00
/// <returns>The DatFormat corresponding to the DAT</returns>
2016-09-22 21:00:18 -07:00
/// <remarks>There is currently no differentiation between XML and SabreDAT here</remarks>
2016-10-25 15:02:02 -07:00
public static DatFormat GetDatFormat ( string filename , Logger logger )
2016-06-13 22:12:00 -07:00
{
2016-09-22 21:00:18 -07:00
// Limit the output formats based on extension
string ext = Path . GetExtension ( filename ) . ToLowerInvariant ( ) ;
2016-09-26 17:36:25 -07:00
if ( ext . StartsWith ( "." ) )
{
ext = ext . Substring ( 1 ) ;
}
2016-09-28 11:30:06 -07:00
if ( ext ! = "dat" & & ext ! = "md5" & & ext ! = "sfv" & & ext ! = "sha1" & & ext ! = "txt" & & ext ! = "xml" )
2016-08-25 20:03:27 -07:00
{
2016-09-22 21:00:18 -07:00
return 0 ;
2016-06-13 22:12:00 -07:00
}
2016-09-22 21:00:18 -07:00
// Read the input file, if possible
2016-10-31 13:46:29 -07:00
logger . Verbose ( "Attempting to read file to get format: \"" + filename + "\"" ) ;
2016-09-09 13:39:01 -07:00
2016-09-22 21:00:18 -07:00
// Check if file exists
if ( ! File . Exists ( filename ) )
2016-08-18 19:54:37 -07:00
{
2016-09-22 21:00:18 -07:00
logger . Warning ( "File '" + filename + "' could not read from!" ) ;
return 0 ;
2016-08-18 19:54:37 -07:00
}
2016-09-28 11:30:06 -07:00
// Some formats only require the extension to know
if ( ext = = "md5" )
{
2016-10-25 15:02:02 -07:00
return DatFormat . RedumpMD5 ;
2016-09-28 11:30:06 -07:00
}
if ( ext = = "sfv" )
{
2016-10-25 15:02:02 -07:00
return DatFormat . RedumpSFV ;
2016-09-28 11:30:06 -07:00
}
if ( ext = = "sha1" )
{
2016-10-25 15:02:02 -07:00
return DatFormat . RedumpSHA1 ;
2016-09-28 11:30:06 -07:00
}
// For everything else, we need to read it
2016-06-15 14:43:05 -07:00
try
{
2016-09-27 11:26:55 -07:00
// Get the first two lines to check
2016-09-22 21:00:18 -07:00
StreamReader sr = File . OpenText ( filename ) ;
2016-09-27 11:26:55 -07:00
string first = sr . ReadLine ( ) . ToLowerInvariant ( ) ;
string second = sr . ReadLine ( ) . ToLowerInvariant ( ) ;
2016-09-22 21:00:18 -07:00
sr . Dispose ( ) ;
2016-09-27 11:26:55 -07:00
// If we have an XML-based DAT
2016-10-03 09:22:18 -07:00
if ( first . Contains ( "<?xml" ) & & first . Contains ( "?>" ) )
2016-06-17 20:03:07 -07:00
{
2016-09-27 11:26:55 -07:00
if ( second . StartsWith ( "<!doctype datafile" ) )
{
2016-10-25 15:02:02 -07:00
return DatFormat . Logiqx ;
2016-09-27 11:26:55 -07:00
}
else if ( second . StartsWith ( "<!doctype softwarelist" ) )
{
2016-10-25 15:02:02 -07:00
return DatFormat . SoftwareList ;
2016-09-27 11:26:55 -07:00
}
else if ( second . StartsWith ( "<!doctype sabredat" ) )
{
2016-10-25 15:02:02 -07:00
return DatFormat . SabreDat ;
2016-09-27 11:26:55 -07:00
}
2016-10-05 10:24:36 -07:00
else if ( second . StartsWith ( "<dat" ) & & ! second . StartsWith ( "<datafile" ) )
2016-09-27 12:05:29 -07:00
{
2016-10-25 15:02:02 -07:00
return DatFormat . OfflineList ;
2016-09-27 12:05:29 -07:00
}
2016-09-29 17:57:27 -07:00
// Older and non-compliant DATs
else
{
2016-10-25 15:02:02 -07:00
return DatFormat . Logiqx ;
2016-09-29 17:57:27 -07:00
}
2016-06-17 20:03:07 -07:00
}
2016-09-27 11:26:55 -07:00
// If we have an INI-based DAT
2016-09-22 21:00:18 -07:00
else if ( first . Contains ( "[" ) & & first . Contains ( "]" ) )
2016-06-17 20:03:07 -07:00
{
2016-10-25 15:02:02 -07:00
return DatFormat . RomCenter ;
2016-06-17 20:03:07 -07:00
}
2016-09-27 11:26:55 -07:00
// If we have a CMP-based DAT
2016-09-29 20:05:46 -07:00
else if ( first . Contains ( "clrmamepro" ) )
2016-06-17 20:03:07 -07:00
{
2016-10-25 15:02:02 -07:00
return DatFormat . ClrMamePro ;
2016-06-17 20:03:07 -07:00
}
2016-09-29 20:05:46 -07:00
else if ( first . Contains ( "romvault" ) )
2016-09-27 11:26:55 -07:00
{
2016-10-25 15:02:02 -07:00
return DatFormat . ClrMamePro ;
2016-09-27 11:26:55 -07:00
}
2016-09-29 20:05:46 -07:00
else if ( first . Contains ( "doscenter" ) )
2016-09-27 11:26:55 -07:00
{
2016-10-25 15:02:02 -07:00
return DatFormat . DOSCenter ;
2016-09-27 11:26:55 -07:00
}
2016-11-04 11:17:15 -07:00
else if ( first . Contains ( "#Name;Title;Emulator;CloneOf;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra" ) )
{
return DatFormat . AttractMode ;
}
2016-10-02 20:14:24 -07:00
else
{
2016-10-25 15:02:02 -07:00
return DatFormat . ClrMamePro ;
2016-10-02 20:14:24 -07:00
}
2016-06-15 14:43:05 -07:00
}
2016-06-16 12:51:35 -07:00
catch ( Exception )
2016-06-15 14:43:05 -07:00
{
2016-09-22 21:00:18 -07:00
return 0 ;
2016-08-29 12:23:02 -07:00
}
}
2016-09-22 16:16:48 -07:00
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
2016-10-03 15:05:07 -07:00
/// <param name="logger">Logger object for console and file output</param>
2016-09-22 16:16:48 -07:00
/// <param name="noMD5">True if MD5 hashes should not be calculated, false otherwise (default)</param>
/// <param name="noSHA1">True if SHA-1 hashes should not be calcluated, false otherwise (default)</param>
/// <param name="offset">Set a >0 number for getting hash for part of the file, 0 otherwise (default)</param>
2016-09-22 21:00:18 -07:00
/// <param name="date">True if the file Date should be included, false otherwise (default)</param>
2016-10-03 21:16:59 -07:00
/// <param name="header">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
2016-09-22 16:16:48 -07:00
/// <returns>Populated RomData object if success, empty one on error</returns>
2016-10-03 21:16:59 -07:00
public static Rom GetFileInfo ( string input , Logger logger , bool noMD5 = false , bool noSHA1 = false , long offset = 0 , bool date = false , string header = null )
2016-09-22 16:16:48 -07:00
{
2016-09-22 21:00:18 -07:00
// Add safeguard if file doesn't exist
if ( ! File . Exists ( input ) )
2016-09-22 16:16:48 -07:00
{
2016-09-22 21:00:18 -07:00
return new Rom ( ) ;
2016-09-22 16:16:48 -07:00
}
2016-09-22 21:00:18 -07:00
// Get the information from the file stream
2016-10-03 15:05:07 -07:00
Rom rom = new Rom ( ) ;
2016-10-03 21:16:59 -07:00
if ( header ! = null )
2016-10-03 15:05:07 -07:00
{
2016-10-03 21:16:59 -07:00
SkipperRule rule = Skipper . GetMatchingRule ( input , Path . GetFileNameWithoutExtension ( header ) , logger ) ;
2016-10-03 15:05:07 -07:00
// If there's a match, get the new information from the stream
if ( rule . Tests ! = null & & rule . Tests . Count ! = 0 )
{
// Create the input and output streams
MemoryStream outputStream = new MemoryStream ( ) ;
FileStream inputStream = File . OpenRead ( input ) ;
// Transform the stream and get the information from it
2016-10-03 21:16:59 -07:00
rule . TransformStream ( inputStream , outputStream , logger , keepReadOpen : false , keepWriteOpen : true ) ;
2016-10-03 15:29:40 -07:00
rom = GetStreamInfo ( outputStream , outputStream . Length ) ;
2016-10-03 15:05:07 -07:00
// Dispose of the streams
outputStream . Dispose ( ) ;
inputStream . Dispose ( ) ;
}
// Otherwise, just get the info
else
{
2016-10-03 15:29:40 -07:00
rom = GetStreamInfo ( File . OpenRead ( input ) , new FileInfo ( input ) . Length , noMD5 , noSHA1 , offset , false ) ;
2016-10-03 15:05:07 -07:00
}
}
else
{
2016-10-03 15:29:40 -07:00
rom = GetStreamInfo ( File . OpenRead ( input ) , new FileInfo ( input ) . Length , noMD5 , noSHA1 , offset , false ) ;
2016-10-03 15:05:07 -07:00
}
2016-09-22 16:16:48 -07:00
2016-09-22 21:00:18 -07:00
// Add unique data from the file
rom . Name = Path . GetFileName ( input ) ;
rom . Date = ( date ? new FileInfo ( input ) . LastWriteTime . ToString ( "yyyy/MM/dd HH:mm:ss" ) : "" ) ;
2016-09-22 16:16:48 -07:00
return rom ;
}
#endregion
2016-09-01 20:38:41 -07:00
#region File Manipulation
/// <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">String representing bytes to be added to head of file</param>
/// <param name="bytesToAddToTail">String representing bytes to be added to tail of file</param>
public static void AppendBytesToFile ( string input , string output , string bytesToAddToHead , string bytesToAddToTail )
{
// Source: http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa
byte [ ] bytesToAddToHeadArray = new byte [ bytesToAddToHead . Length / 2 ] ;
for ( int i = 0 ; i < bytesToAddToHead . Length ; i + = 2 )
{
bytesToAddToHeadArray [ i / 2 ] = Convert . ToByte ( bytesToAddToHead . Substring ( i , 2 ) , 16 ) ;
}
byte [ ] bytesToAddToTailArray = new byte [ bytesToAddToTail . Length / 2 ] ;
for ( int i = 0 ; i < bytesToAddToTail . Length ; i + = 2 )
{
bytesToAddToTailArray [ i / 2 ] = Convert . ToByte ( bytesToAddToTail . Substring ( i , 2 ) , 16 ) ;
}
AppendBytesToFile ( input , output , bytesToAddToHeadArray , bytesToAddToTailArray ) ;
}
/// <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>
public static void AppendBytesToFile ( string input , string output , byte [ ] bytesToAddToHead , byte [ ] bytesToAddToTail )
{
// If any of the inputs are invalid, skip
if ( ! File . Exists ( input ) )
{
return ;
}
2016-09-22 16:16:48 -07:00
FileStream fsr = File . OpenRead ( input ) ;
FileStream fsw = File . OpenWrite ( output ) ;
2016-09-22 15:36:02 -07:00
2016-09-22 16:16:48 -07:00
AppendBytesToStream ( fsr , fsw , bytesToAddToHead , bytesToAddToTail ) ;
2016-09-22 15:36:02 -07:00
2016-09-22 16:16:48 -07:00
fsr . Dispose ( ) ;
fsw . Dispose ( ) ;
2016-09-01 20:38:41 -07:00
}
2016-10-31 14:26:23 -07:00
/// <summary>
/// Cleans out the temporary directory
/// </summary>
/// <param name="dirname">Name of the directory to clean out</param>
public static void CleanDirectory ( string dirname )
{
foreach ( string file in Directory . EnumerateFiles ( dirname , "*" , SearchOption . TopDirectoryOnly ) )
{
try
{
File . Delete ( file ) ;
}
catch { }
}
foreach ( string dir in Directory . EnumerateDirectories ( dirname , "*" , SearchOption . TopDirectoryOnly ) )
{
try
{
Directory . Delete ( dir , true ) ;
}
catch { }
}
}
2016-09-22 20:42:34 -07: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="logger">Logger object for console and file output</param>
/// <returns>True if the output file was created, false otherwise</returns>
public static bool DetectSkipperAndTransform ( string file , string outDir , Logger logger )
{
// Create the output directory if it doesn't exist
if ( outDir ! = "" & & ! Directory . Exists ( outDir ) )
{
Directory . CreateDirectory ( outDir ) ;
}
logger . User ( "\nGetting skipper information for '" + file + "'" ) ;
2016-09-22 21:32:06 -07:00
// Get the skipper rule that matches the file, if any
2016-10-03 21:16:59 -07:00
SkipperRule rule = Skipper . GetMatchingRule ( file , "" , logger ) ;
2016-09-22 20:42:34 -07:00
2016-09-22 21:32:06 -07:00
// If we have an empty rule, return false
if ( rule . Tests = = null | | rule . Tests . Count = = 0 | | rule . Operation ! = HeaderSkipOperation . None )
2016-09-22 20:42:34 -07:00
{
2016-09-22 21:32:06 -07:00
return false ;
}
2016-09-22 20:42:34 -07:00
2016-09-22 21:32:06 -07:00
logger . User ( "File has a valid copier header" ) ;
2016-09-22 20:42:34 -07:00
2016-09-22 21:32:06 -07:00
// Get the header bytes from the file first
string hstr = string . Empty ;
BinaryReader br = new BinaryReader ( File . OpenRead ( file ) ) ;
2016-09-22 20:42:34 -07:00
2016-09-22 21:32:06 -07:00
// Extract the header as a string for the database
byte [ ] hbin = br . ReadBytes ( ( int ) rule . StartOffset ) ;
for ( int i = 0 ; i < ( int ) rule . StartOffset ; i + + )
{
hstr + = BitConverter . ToString ( new byte [ ] { hbin [ i ] } ) ;
}
br . Dispose ( ) ;
2016-09-22 20:42:34 -07:00
2016-09-22 21:32:06 -07:00
// Apply the rule to the file
string newfile = ( outDir = = "" ? Path . GetFullPath ( file ) + ".new" : Path . Combine ( outDir , Path . GetFileName ( file ) ) ) ;
2016-10-03 21:16:59 -07:00
rule . TransformFile ( file , newfile , logger ) ;
2016-09-22 20:42:34 -07:00
2016-09-22 21:32:06 -07:00
// If the output file doesn't exist, return false
if ( ! File . Exists ( newfile ) )
{
return false ;
2016-09-22 20:42:34 -07:00
}
2016-09-22 21:32:06 -07:00
// Now add the information to the database if it's not already there
2016-10-03 15:29:40 -07:00
Rom rom = GetFileInfo ( newfile , logger ) ;
2016-09-22 21:32:06 -07:00
DatabaseTools . AddHeaderToDatabase ( hstr , rom . SHA1 , rule . SourceFile , logger ) ;
2016-09-22 20:42:34 -07:00
return true ;
}
2016-10-31 13:46:29 -07:00
/// <summary>
/// Retrieve a list of just files from inputs
/// </summary>
/// <param name="inputs">List of strings representing directories and files</param>
/// <param name="maxDegreeOfParallelism">Integer representing the maximum amount of parallelization to be used</param>
/// <param name="logger">Logger object for file and console output</param>
/// <param name="appendparent">True if the parent name should be appended after the special character "¬", false otherwise</param>
/// <returns>List of strings representing just files from the inputs</returns>
public static List < string > GetOnlyFilesFromInputs ( List < string > inputs , int maxDegreeOfParallelism , Logger logger , bool appendparent = false )
{
List < string > outputs = new List < string > ( ) ;
2016-11-03 21:58:29 -07:00
foreach ( string input in inputs )
{
if ( Directory . Exists ( input ) )
2016-10-31 13:46:29 -07:00
{
2016-11-03 21:58:29 -07:00
List < string > files = FileTools . RetrieveFiles ( input , new List < string > ( ) ) ;
// Make sure the files in the directory are ordered correctly
files = Style . OrderByAlphaNumeric ( files , s = > s ) . ToList ( ) ;
foreach ( string file in files )
2016-10-31 13:46:29 -07:00
{
try
{
2016-11-03 22:50:57 -07:00
outputs . Add ( Path . GetFullPath ( file ) + ( appendparent ? "¬" + Path . GetFullPath ( input ) : "" ) ) ;
2016-10-31 13:46:29 -07:00
}
catch ( PathTooLongException )
{
2016-11-03 21:58:29 -07:00
logger . Warning ( "The path for " + file + " was too long" ) ;
2016-10-31 13:46:29 -07:00
}
catch ( Exception ex )
{
logger . Error ( ex . ToString ( ) ) ;
}
}
2016-11-03 21:58:29 -07:00
}
else if ( File . Exists ( input ) )
{
try
{
2016-11-03 22:50:57 -07:00
outputs . Add ( Path . GetFullPath ( input ) + ( appendparent ? "¬" + Path . GetFullPath ( input ) : "" ) ) ;
2016-11-03 21:58:29 -07:00
}
catch ( PathTooLongException )
{
logger . Warning ( "The path for " + input + " was too long" ) ;
}
catch ( Exception ex )
{
logger . Error ( ex . ToString ( ) ) ;
}
}
}
2016-10-31 13:46:29 -07:00
return outputs ;
}
2016-10-31 14:26:23 -07:00
/// <summary>
/// Get the romba path for a file based on the rom's SHA-1
/// </summary>
/// <param name="rom">Rom to get the sha1 from</param>
/// <param name="baseOutDir">Base output folder</param>
/// <returns>Formatted path string to use</returns>
public static string GetRombaPath ( Rom rom , string baseOutDir )
{
string subfolder = Path . Combine ( rom . SHA1 . Substring ( 0 , 2 ) , rom . SHA1 . Substring ( 2 , 2 ) , rom . SHA1 . Substring ( 4 , 2 ) , rom . SHA1 . Substring ( 6 , 2 ) ) ;
return Path . Combine ( baseOutDir , subfolder ) ;
}
/// <summary>
/// Get the XmlTextReader associated with a file, if possible
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="logger">Logger object for console and file output</param>
/// <returns>The XmlTextReader representing the (possibly converted) file, null otherwise</returns>
public static XmlReader GetXmlTextReader ( string filename , Logger logger )
{
logger . Verbose ( "Attempting to read file: \"" + filename + "\"" ) ;
// Check if file exists
if ( ! File . Exists ( filename ) )
{
logger . Warning ( "File '" + filename + "' could not read from!" ) ;
return null ;
}
XmlReader xtr = XmlReader . Create ( filename , new XmlReaderSettings
{
CheckCharacters = false ,
DtdProcessing = DtdProcessing . Ignore ,
IgnoreComments = true ,
IgnoreWhitespace = true ,
ValidationFlags = XmlSchemaValidationFlags . None ,
ValidationType = ValidationType . None ,
} ) ;
return xtr ;
}
/// <summary>
/// Move a file to a named, Romba-style subdirectory
/// </summary>
/// <param name="rom">Rom to get the sha1 from</param>
/// <param name="baseOutDir">Base output folder</param>
/// <param name="filename">Name of the file to be moved</param>
/// <param name="logger">Logger object for file and console output</param>
public static void MoveToRombaFolder ( Rom rom , string baseOutDir , string filename , Logger logger )
{
string outDir = GetRombaPath ( rom , baseOutDir ) ;
if ( ! Directory . Exists ( outDir ) )
{
Directory . CreateDirectory ( outDir ) ;
}
try
{
File . Move ( filename , Path . Combine ( outDir , Path . GetFileName ( filename ) ) ) ;
}
catch ( Exception ex )
{
logger . Warning ( ex . ToString ( ) ) ;
File . Delete ( filename ) ;
}
}
/// <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>
/// <param name="logger">Logger object for console and file output</param>
/// <returns>True if a header was found and appended, false otherwise</returns>
public static bool RestoreHeader ( string file , string outDir , Logger logger )
{
// Create the output directory if it doesn't exist
if ( outDir ! = "" & & ! Directory . Exists ( outDir ) )
{
Directory . CreateDirectory ( outDir ) ;
}
// First, get the SHA-1 hash of the file
Rom rom = GetFileInfo ( file , logger ) ;
// Retrieve a list of all related headers from the database
List < string > headers = DatabaseTools . RetrieveHeadersFromDatabase ( rom . SHA1 , logger ) ;
// 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 + + )
{
logger . User ( "Creating reheadered file: " +
( outDir = = "" ? Path . GetFullPath ( file ) + ".new" : Path . Combine ( outDir , Path . GetFileName ( file ) ) ) + i ) ;
AppendBytesToFile ( file ,
( outDir = = "" ? Path . GetFullPath ( file ) + ".new" : Path . Combine ( outDir , Path . GetFileName ( file ) ) ) + i , headers [ i ] , string . Empty ) ;
logger . User ( "Reheadered file created!" ) ;
}
return true ;
}
2016-09-01 20:38:41 -07:00
#endregion
2016-09-22 16:16:48 -07:00
2016-09-22 21:00:18 -07:00
#region Stream Information
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
2016-10-03 15:05:07 -07:00
/// <param name="size">Size of the input stream</param>
2016-09-22 21:00:18 -07:00
/// <param name="noMD5">True if MD5 hashes should not be calculated, false otherwise (default)</param>
/// <param name="noSHA1">True if SHA-1 hashes should not be calcluated, false otherwise (default)</param>
/// <param name="offset">Set a >0 number for getting hash for part of the file, 0 otherwise (default)</param>
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
/// <returns>Populated RomData object if success, empty one on error</returns>
2016-10-03 15:29:40 -07:00
public static Rom GetStreamInfo ( Stream input , long size , bool noMD5 = false , bool noSHA1 = false , long offset = 0 , bool keepReadOpen = false )
2016-09-22 21:00:18 -07:00
{
Rom rom = new Rom
{
Type = ItemType . Rom ,
2016-10-03 15:05:07 -07:00
Size = size ,
2016-09-22 21:00:18 -07:00
CRC = string . Empty ,
MD5 = string . Empty ,
SHA1 = string . Empty ,
} ;
try
{
// Initialize the hashers
OptimizedCRC crc = new OptimizedCRC ( ) ;
MD5 md5 = MD5 . Create ( ) ;
SHA1 sha1 = SHA1 . Create ( ) ;
// Seek to the starting position, if one is set
2016-10-03 15:25:09 -07:00
if ( offset < 0 )
{
input . Seek ( offset , SeekOrigin . End ) ;
}
else
{
input . Seek ( offset , SeekOrigin . Begin ) ;
}
2016-09-22 21:00:18 -07:00
2016-10-08 23:28:09 -07:00
byte [ ] buffer = new byte [ 8 * 1024 ] ;
2016-09-22 21:00:18 -07:00
int read ;
while ( ( read = input . Read ( buffer , 0 , buffer . Length ) ) > 0 )
{
crc . Update ( buffer , 0 , read ) ;
if ( ! noMD5 )
{
md5 . TransformBlock ( buffer , 0 , read , buffer , 0 ) ;
}
if ( ! noSHA1 )
{
sha1 . TransformBlock ( buffer , 0 , read , buffer , 0 ) ;
}
}
crc . Update ( buffer , 0 , 0 ) ;
rom . CRC = crc . Value . ToString ( "X8" ) . ToLowerInvariant ( ) ;
if ( ! noMD5 )
{
md5 . TransformFinalBlock ( buffer , 0 , 0 ) ;
rom . MD5 = BitConverter . ToString ( md5 . Hash ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
}
if ( ! noSHA1 )
{
sha1 . TransformFinalBlock ( buffer , 0 , 0 ) ;
rom . SHA1 = BitConverter . ToString ( sha1 . Hash ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
}
// Dispose of the hashers
crc . Dispose ( ) ;
md5 . Dispose ( ) ;
sha1 . Dispose ( ) ;
}
catch ( IOException )
{
return new Rom ( ) ;
}
finally
{
if ( ! keepReadOpen )
{
input . Dispose ( ) ;
}
}
return rom ;
}
#endregion
2016-09-22 16:16:48 -07:00
#region Stream Manipulation
/// <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>
public static void AppendBytesToStream ( Stream input , Stream output , byte [ ] bytesToAddToHead , byte [ ] bytesToAddToTail )
{
BinaryReader br = new BinaryReader ( input ) ;
BinaryWriter bw = new BinaryWriter ( output ) ;
if ( bytesToAddToHead . Count ( ) > 0 )
{
bw . Write ( bytesToAddToHead ) ;
}
int bufferSize = 1024 ;
// Now read the file in chunks and write out
byte [ ] buffer = new byte [ bufferSize ] ;
while ( br . BaseStream . Position < = ( br . BaseStream . Length - bufferSize ) )
{
buffer = br . ReadBytes ( bufferSize ) ;
bw . Write ( buffer ) ;
}
// For the final chunk, if any, write out only that number of bytes
int length = ( int ) ( br . BaseStream . Length - br . BaseStream . Position ) ;
buffer = new byte [ length ] ;
buffer = br . ReadBytes ( length ) ;
bw . Write ( buffer ) ;
if ( bytesToAddToTail . Count ( ) > 0 )
{
bw . Write ( bytesToAddToTail ) ;
}
}
#endregion
2016-06-13 20:57:49 -07:00
}
}