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
2017-05-04 02:41:11 -07:00
using SabreTools.Library.Data ;
using SabreTools.Library.Dats ;
using SabreTools.Library.External ;
using SabreTools.Library.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 ;
2017-03-15 20:07:28 -07:00
using FileAccess = System . IO . FileAccess ;
using FileMode = System . IO . FileMode ;
using FileShare = System . IO . FileShare ;
2016-10-26 22:10:47 -07:00
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
2017-05-04 02:41:11 -07:00
namespace SabreTools.Library.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-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>
2017-03-01 21:26:27 -08:00
public static DatFormat GetDatFormat ( string filename )
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 ) ;
}
2017-02-27 00:01:24 -08:00
if ( ext ! = "csv" & & ext ! = "dat" & & ext ! = "md5" & & ext ! = "sfv" & & ext ! = "sha1"
& & ext ! = "sha384" & & ext ! = "sha512" & & ext ! = "tsv" & & 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
2017-03-01 21:26:27 -08:00
Globals . 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
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Warning ( "File '" + filename + "' could not read from!" ) ;
2016-09-22 21:00:18 -07:00
return 0 ;
2016-08-18 19:54:37 -07:00
}
2017-05-10 15:13:49 -07:00
// Some formats should only require the extension to know
2016-09-28 11:30:06 -07:00
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
}
2017-02-23 20:26:32 -08:00
if ( ext = = "sha256" )
{
return DatFormat . RedumpSHA256 ;
}
2017-02-27 00:01:24 -08:00
if ( ext = = "sha384" )
{
return DatFormat . RedumpSHA384 ;
}
if ( ext = = "sha512" )
{
return DatFormat . RedumpSHA512 ;
}
2017-05-10 15:13:49 -07:00
if ( ext = = "csv" )
{
return DatFormat . CSV ;
}
if ( ext = = "tsv" )
{
return DatFormat . TSV ;
}
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 ;
2017-02-23 20:26:32 -08:00
}
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
}
}
2017-03-14 20:36:16 -07:00
/// <summary>
/// Get all empty folders within a root folder
/// </summary>
/// <param name="root">Root directory to parse</param>
/// <returns>IEumerable containing all directories that are empty, an empty enumerable if the root is empty, null otherwise</returns>
public static IEnumerable < string > GetEmptyDirectories ( string root )
{
// Check if the root exists first
if ( ! Directory . Exists ( root ) )
{
return null ;
}
// If it does and it is empty, return a blank enumerable
if ( Directory . EnumerateFileSystemEntries ( root , "*" , SearchOption . AllDirectories ) . Count ( ) = = 0 )
{
return new List < string > ( ) ;
}
// Otherwise, get the complete list
return Directory . EnumerateDirectories ( root , "*" , SearchOption . AllDirectories )
. Where ( dir = > Directory . EnumerateFileSystemEntries ( dir , "*" , SearchOption . AllDirectories ) . Count ( ) = = 0 ) ;
}
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>
2017-02-26 23:05:31 -08:00
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated (defaults to none)</param>
2016-09-22 16:16:48 -07:00
/// <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>
2017-03-01 21:26:27 -08:00
public static Rom GetFileInfo ( string input , Hash omitFromScan = 0x0 ,
2017-02-26 23:05:31 -08:00
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
{
2017-03-01 21:26:27 -08:00
SkipperRule rule = Skipper . GetMatchingRule ( input , Path . GetFileNameWithoutExtension ( header ) ) ;
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 ( ) ;
2017-03-15 20:07:28 -07:00
FileStream inputStream = FileTools . TryOpenRead ( input ) ;
2016-10-03 15:05:07 -07:00
// Transform the stream and get the information from it
2017-03-01 21:26:27 -08:00
rule . TransformStream ( inputStream , outputStream , keepReadOpen : false , keepWriteOpen : true ) ;
2017-03-15 13:43:38 -07:00
rom = GetStreamInfo ( outputStream , outputStream . Length , omitFromScan : omitFromScan , keepReadOpen : false ) ;
2016-10-03 15:05:07 -07:00
// Dispose of the streams
outputStream . Dispose ( ) ;
inputStream . Dispose ( ) ;
}
// Otherwise, just get the info
else
{
2017-03-15 13:43:38 -07:00
long length = new FileInfo ( input ) . Length ;
2017-03-15 20:07:28 -07:00
rom = GetStreamInfo ( TryOpenRead ( input ) , length , omitFromScan , offset , false ) ;
2016-10-03 15:05:07 -07:00
}
}
else
{
2017-03-15 13:43:38 -07:00
long length = new FileInfo ( input ) . Length ;
2017-03-15 20:07:28 -07:00
rom = GetStreamInfo ( TryOpenRead ( input ) , length , omitFromScan , 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 ) ;
2017-01-27 16:53:29 -08:00
rom . Date = ( date ? new FileInfo ( input ) . LastWriteTime . ToString ( "yyyy/MM/dd HH:mm:ss" ) : "" ) ;
2016-09-22 16:16:48 -07:00
return rom ;
}
2017-03-14 20:36:16 -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 ( ) ;
toadd . Sort ( new NaturalComparer ( ) ) ;
infiles . AddRange ( toadd ) ;
// Then recurse through and add from the directories
List < string > dirs = Directory . EnumerateDirectories ( directory , "*" , SearchOption . TopDirectoryOnly ) . ToList ( ) ;
dirs = Style . OrderByAlphaNumeric ( dirs , s = > s ) . ToList ( ) ;
foreach ( string dir in dirs )
{
infiles = RetrieveFiles ( dir , infiles ) ;
}
// Return the new list
return infiles ;
}
2016-09-22 16:16:48 -07:00
#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 ;
}
2017-03-15 20:07:28 -07:00
FileStream fsr = TryOpenRead ( input ) ;
FileStream fsw = TryOpenWrite ( 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 ) )
{
2017-03-15 20:07:28 -07:00
TryDeleteFile ( file ) ;
2016-10-31 14:26:23 -07:00
}
foreach ( string dir in Directory . EnumerateDirectories ( dirname , "*" , SearchOption . TopDirectoryOnly ) )
{
2017-03-15 20:07:28 -07:00
TryDeleteDirectory ( dir ) ;
2016-10-31 14:26:23 -07:00
}
}
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>
/// <returns>True if the output file was created, false otherwise</returns>
2017-03-01 21:26:27 -08:00
public static bool DetectSkipperAndTransform ( string file , string outDir )
2016-09-22 20:42:34 -07:00
{
// Create the output directory if it doesn't exist
2017-01-27 16:53:29 -08:00
if ( outDir ! = "" & & ! Directory . Exists ( outDir ) )
2016-09-22 20:42:34 -07:00
{
Directory . CreateDirectory ( outDir ) ;
}
2017-03-01 21:26:27 -08:00
Globals . Logger . User ( "\nGetting skipper information for '" + file + "'" ) ;
2016-09-22 20:42:34 -07:00
2016-09-22 21:32:06 -07:00
// Get the skipper rule that matches the file, if any
2017-03-01 21:26:27 -08:00
SkipperRule rule = Skipper . GetMatchingRule ( file , "" ) ;
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
2017-03-01 21:26:27 -08:00
Globals . 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 ;
2017-03-15 20:07:28 -07:00
BinaryReader br = new BinaryReader ( TryOpenRead ( 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
2017-01-27 16:53:29 -08:00
string newfile = ( outDir = = "" ? Path . GetFullPath ( file ) + ".new" : Path . Combine ( outDir , Path . GetFileName ( file ) ) ) ;
2017-03-01 21:26:27 -08:00
rule . TransformFile ( file , newfile ) ;
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
2017-03-01 21:26:27 -08:00
Rom rom = GetFileInfo ( newfile ) ;
DatabaseTools . AddHeaderToDatabase ( hstr , rom . SHA1 , rule . SourceFile ) ;
2016-09-22 21:32:06 -07:00
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="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>
2017-03-01 21:26:27 -08:00
public static List < string > GetOnlyFilesFromInputs ( List < string > inputs , bool appendparent = false )
2016-10-31 13:46:29 -07:00
{
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
{
2017-01-27 16:53:29 -08:00
outputs . Add ( Path . GetFullPath ( file ) + ( appendparent ? "¬" + Path . GetFullPath ( input ) : "" ) ) ;
2016-10-31 13:46:29 -07:00
}
catch ( PathTooLongException )
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Warning ( "The path for " + file + " was too long" ) ;
2016-10-31 13:46:29 -07:00
}
catch ( Exception ex )
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Error ( ex . ToString ( ) ) ;
2016-10-31 13:46:29 -07:00
}
}
2016-11-03 21:58:29 -07:00
}
else if ( File . Exists ( input ) )
{
try
{
2017-01-27 16:53:29 -08:00
outputs . Add ( Path . GetFullPath ( input ) + ( appendparent ? "¬" + Path . GetFullPath ( input ) : "" ) ) ;
2016-11-03 21:58:29 -07:00
}
catch ( PathTooLongException )
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Warning ( "The path for " + input + " was too long" ) ;
2016-11-03 21:58:29 -07:00
}
catch ( Exception ex )
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Error ( ex . ToString ( ) ) ;
2016-11-03 21:58:29 -07:00
}
}
}
2016-10-31 13:46:29 -07:00
return outputs ;
}
2016-10-31 14:26:23 -07:00
/// <summary>
/// Get the XmlTextReader associated with a file, if possible
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <returns>The XmlTextReader representing the (possibly converted) file, null otherwise</returns>
2017-03-01 21:26:27 -08:00
public static XmlReader GetXmlTextReader ( string filename )
2016-10-31 14:26:23 -07:00
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Verbose ( "Attempting to read file: \"" + filename + "\"" ) ;
2016-10-31 14:26:23 -07:00
// Check if file exists
if ( ! File . Exists ( filename ) )
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Warning ( "File '" + filename + "' could not read from!" ) ;
2016-10-31 14:26:23 -07:00
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>
/// 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>
2017-03-01 21:26:27 -08:00
public static bool RestoreHeader ( string file , string outDir )
2016-10-31 14:26:23 -07:00
{
// Create the output directory if it doesn't exist
2017-01-27 16:53:29 -08:00
if ( outDir ! = "" & & ! Directory . Exists ( outDir ) )
2016-10-31 14:26:23 -07:00
{
Directory . CreateDirectory ( outDir ) ;
}
// First, get the SHA-1 hash of the file
2017-03-01 21:26:27 -08:00
Rom rom = GetFileInfo ( file ) ;
2016-10-31 14:26:23 -07:00
// Retrieve a list of all related headers from the database
2017-03-01 21:26:27 -08:00
List < string > headers = DatabaseTools . RetrieveHeadersFromDatabase ( rom . SHA1 ) ;
2016-10-31 14:26:23 -07: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 + + )
{
2017-03-01 21:26:27 -08:00
Globals . Logger . User ( "Creating reheadered file: " +
2017-01-27 16:53:29 -08:00
( outDir = = "" ? Path . GetFullPath ( file ) + ".new" : Path . Combine ( outDir , Path . GetFileName ( file ) ) ) + i ) ;
2016-10-31 14:26:23 -07:00
AppendBytesToFile ( file ,
2017-01-27 16:53:29 -08:00
( outDir = = "" ? Path . GetFullPath ( file ) + ".new" : Path . Combine ( outDir , Path . GetFileName ( file ) ) ) + i , headers [ i ] , string . Empty ) ;
2017-03-01 21:26:27 -08:00
Globals . Logger . User ( "Reheadered file created!" ) ;
2016-10-31 14:26:23 -07:00
}
return true ;
}
2017-03-15 20:07:28 -07:00
/// <summary>
/// Try to create a file for write, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to create</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>An opened stream representing the file on success, null otherwise</returns>
public static FileStream TryCreate ( string file , bool throwOnError = false )
{
// Now wrap opening the file
try
{
return File . Open ( file , FileMode . Create , FileAccess . Write , FileShare . ReadWrite ) ;
}
catch ( Exception ex )
{
if ( throwOnError )
{
throw ex ;
}
else
{
return null ;
}
}
}
2017-03-15 14:44:44 -07:00
/// <summary>
/// Try to safely delete a directory, optionally throwing the error
/// </summary>
/// <param name="file">Name of the directory to delete</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>True if the file didn't exist or could be deleted, false otherwise</returns>
2017-03-15 20:07:28 -07:00
public static bool TryDeleteDirectory ( string file , bool throwOnError = false )
2017-03-15 14:44:44 -07:00
{
// Check if the file exists first
if ( ! Directory . Exists ( file ) )
{
return true ;
}
// Now wrap deleting the file
try
{
Directory . Delete ( file , true ) ;
return true ;
}
catch ( Exception ex )
{
if ( throwOnError )
{
throw ex ;
}
else
{
return false ;
}
}
}
/// <summary>
/// Try to safely delete a file, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to delete</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>True if the file didn't exist or could be deleted, false otherwise</returns>
2017-03-15 20:07:28 -07:00
public static bool TryDeleteFile ( string file , bool throwOnError = false )
2017-03-15 14:44:44 -07:00
{
// Check if the file exists first
if ( ! File . Exists ( file ) )
{
return true ;
}
// Now wrap deleting the file
try
{
File . Delete ( file ) ;
return true ;
}
catch ( Exception ex )
{
if ( throwOnError )
{
throw ex ;
}
else
{
return false ;
}
}
}
2017-03-15 20:07:28 -07:00
/// <summary>
/// Try to open a file for read, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to open</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>An opened stream representing the file on success, null otherwise</returns>
public static FileStream TryOpenRead ( string file , bool throwOnError = false )
{
// Check if the file exists first
if ( ! File . Exists ( file ) )
{
return null ;
}
// Now wrap opening the file
try
{
return File . Open ( file , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
}
catch ( Exception ex )
{
if ( throwOnError )
{
throw ex ;
}
else
{
return null ;
}
}
}
/// <summary>
/// Try to open a file for read/write, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to open</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>An opened stream representing the file on success, null otherwise</returns>
public static FileStream TryOpenReadWrite ( string file , bool throwOnError = false )
{
// Check if the file exists first
if ( ! File . Exists ( file ) )
{
return null ;
}
// Now wrap opening the file
try
{
return File . Open ( file , FileMode . Open , FileAccess . ReadWrite , FileShare . ReadWrite ) ;
}
catch ( Exception ex )
{
if ( throwOnError )
{
throw ex ;
}
else
{
return null ;
}
}
}
/// <summary>
/// Try to open a file for write, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to open</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>An opened stream representing the file on success, null otherwise</returns>
public static FileStream TryOpenWrite ( string file , bool throwOnError = false )
{
// Check if the file exists first
if ( ! File . Exists ( file ) )
{
return null ;
}
// Now wrap opening the file
try
{
return File . Open ( file , FileMode . Open , FileAccess . Write , FileShare . ReadWrite ) ;
}
catch ( Exception ex )
{
if ( throwOnError )
{
throw ex ;
}
else
{
return null ;
}
}
}
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>
2017-02-26 23:05:31 -08:00
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated (defaults to none)</param>
2016-09-22 21:00:18 -07:00
/// <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>
2017-02-26 23:05:31 -08:00
public static Rom GetStreamInfo ( Stream input , long size , Hash omitFromScan = 0x0 ,
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 ,
2017-02-23 14:23:41 -08:00
SHA256 = string . Empty ,
2017-03-15 13:43:38 -07:00
SHA384 = string . Empty ,
SHA512 = string . Empty ,
2016-09-22 21:00:18 -07:00
} ;
try
{
// Initialize the hashers
OptimizedCRC crc = new OptimizedCRC ( ) ;
MD5 md5 = MD5 . Create ( ) ;
SHA1 sha1 = SHA1 . Create ( ) ;
2017-02-23 14:23:41 -08:00
SHA256 sha256 = SHA256 . Create ( ) ;
2017-02-26 23:12:20 -08:00
SHA384 sha384 = SHA384 . Create ( ) ;
SHA512 sha512 = SHA512 . Create ( ) ;
2017-03-05 20:55:22 -08:00
xxHash xxHash = new xxHash ( ) ;
xxHash . Init ( ) ;
2016-09-22 21:00:18 -07:00
// 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 ) ;
2017-02-26 23:05:31 -08:00
if ( ( omitFromScan & Hash . MD5 ) = = 0 )
2016-09-22 21:00:18 -07:00
{
md5 . TransformBlock ( buffer , 0 , read , buffer , 0 ) ;
}
2017-02-26 23:05:31 -08:00
if ( ( omitFromScan & Hash . SHA1 ) = = 0 )
2016-09-22 21:00:18 -07:00
{
sha1 . TransformBlock ( buffer , 0 , read , buffer , 0 ) ;
}
2017-02-26 23:05:31 -08:00
if ( ( omitFromScan & Hash . SHA256 ) = = 0 )
2017-02-23 14:23:41 -08:00
{
sha256 . TransformBlock ( buffer , 0 , read , buffer , 0 ) ;
}
2017-02-26 23:12:20 -08:00
if ( ( omitFromScan & Hash . SHA384 ) = = 0 )
{
sha384 . TransformBlock ( buffer , 0 , read , buffer , 0 ) ;
}
if ( ( omitFromScan & Hash . SHA512 ) = = 0 )
{
sha512 . TransformBlock ( buffer , 0 , read , buffer , 0 ) ;
}
2017-03-05 20:55:22 -08:00
if ( ( omitFromScan & Hash . xxHash ) = = 0 )
{
xxHash . Update ( buffer , read ) ;
}
2016-09-22 21:00:18 -07:00
}
crc . Update ( buffer , 0 , 0 ) ;
rom . CRC = crc . Value . ToString ( "X8" ) . ToLowerInvariant ( ) ;
2017-02-26 23:05:31 -08:00
if ( ( omitFromScan & Hash . MD5 ) = = 0 )
2016-09-22 21:00:18 -07:00
{
md5 . TransformFinalBlock ( buffer , 0 , 0 ) ;
2017-01-27 16:53:29 -08:00
rom . MD5 = BitConverter . ToString ( md5 . Hash ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
2016-09-22 21:00:18 -07:00
}
2017-02-26 23:05:31 -08:00
if ( ( omitFromScan & Hash . SHA1 ) = = 0 )
2016-09-22 21:00:18 -07:00
{
sha1 . TransformFinalBlock ( buffer , 0 , 0 ) ;
2017-01-27 16:53:29 -08:00
rom . SHA1 = BitConverter . ToString ( sha1 . Hash ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
2016-09-22 21:00:18 -07:00
}
2017-02-26 23:05:31 -08:00
if ( ( omitFromScan & Hash . SHA256 ) = = 0 )
2017-02-23 14:23:41 -08:00
{
sha256 . TransformFinalBlock ( buffer , 0 , 0 ) ;
rom . SHA256 = BitConverter . ToString ( sha256 . Hash ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
}
2017-02-26 23:12:20 -08:00
if ( ( omitFromScan & Hash . SHA384 ) = = 0 )
{
sha384 . TransformFinalBlock ( buffer , 0 , 0 ) ;
rom . SHA384 = BitConverter . ToString ( sha384 . Hash ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
}
if ( ( omitFromScan & Hash . SHA512 ) = = 0 )
{
sha512 . TransformFinalBlock ( buffer , 0 , 0 ) ;
rom . SHA512 = BitConverter . ToString ( sha512 . Hash ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
}
2017-03-05 20:55:22 -08:00
if ( ( omitFromScan & Hash . xxHash ) = = 0 )
{
2017-03-13 21:34:27 -07:00
//rom.xxHash = xxHash.Digest().ToString("X8").ToLowerInvariant();
2017-03-05 20:55:22 -08:00
}
2016-09-22 21:00:18 -07:00
// Dispose of the hashers
crc . Dispose ( ) ;
md5 . Dispose ( ) ;
sha1 . Dispose ( ) ;
2017-02-23 14:23:41 -08:00
sha256 . Dispose ( ) ;
2017-02-26 23:12:20 -08:00
sha384 . Dispose ( ) ;
sha512 . Dispose ( ) ;
2016-09-22 21:00:18 -07:00
}
catch ( IOException )
{
return new Rom ( ) ;
}
finally
{
2017-04-14 21:51:41 -07:00
// Seek to the beginning of the stream
input . Seek ( 0 , SeekOrigin . Begin ) ;
2016-09-22 21:00:18 -07:00
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
}
}