Remove .NET Framework 4.6.2/4.7.2 (#24)

* Remove < .NET 4.8, general cleanup

* Abstract

* Tango

* Banner

* Scan no more

* Common

* Application

* Access

* Filter-feeder

* Graffiti

* Paint-over

* Law and Order

* XOR-o

* Unused staircase

* Maybe

* Maybe not

* Delete this

* The word is "no"

* Emit

* Improper

* Aye aye

* Fence

* Barrier

* Monkey

* Pail

* Lines
This commit is contained in:
Matt Nadareski
2020-07-15 09:41:59 -07:00
committed by GitHub
parent 1a718a3915
commit 4e406604c2
82 changed files with 8975 additions and 11172 deletions

View File

@@ -0,0 +1,174 @@
using System;
using System.IO;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Big endian reading overloads for BinaryReader
/// </summary>
public static class BinaryReaderExtensions
{
/// <summary>
/// Reads the specified number of bytes from the stream, starting from a specified point in the byte array.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
/// <param name="count">The number of bytes to read.</param>
/// <returns>The number of bytes read into buffer. This might be less than the number of bytes requested if that many bytes are not available, or it might be zero if the end of the stream is reached.</returns>
public static int ReadBigEndian(this BinaryReader reader, byte[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <summary>
/// Reads the specified number of characters from the stream, starting from a specified point in the character array.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
/// <param name="count">The number of characters to read.</param>
/// <returns>The total number of characters read into the buffer. This might be less than the number of characters requested if that many characters are not currently available, or it might be zero if the end of the stream is reached.</returns>
public static int ReadBigEndian(this BinaryReader reader, char[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <summary>
/// Reads the specified number of bytes from the current stream into a byte array and advances the current position by that number of bytes.
/// </summary>
/// <param name="count">The number of bytes to read. This value must be 0 or a non-negative number or an exception will occur.</param>
/// <returns>A byte array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
{
byte[] retval = reader.ReadBytes(count);
Array.Reverse(retval);
return retval;
}
/// <summary>
/// Reads the specified number of characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream.
/// </summary>
/// <param name="count">The number of characters to read. This value must be 0 or a non-negative number or an exception will occur.</param>
/// <returns>A character array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
public static char[] ReadCharsBigEndian(this BinaryReader reader, int count)
{
char[] retval = reader.ReadChars(count);
Array.Reverse(retval);
return retval;
}
/// <summary>
/// Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes.
/// </summary>
/// <returns>A decimal value read from the current stream.</returns>
public static decimal ReadDecimalBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(16);
Array.Reverse(retval);
int i1 = BitConverter.ToInt32(retval, 0);
int i2 = BitConverter.ToInt32(retval, 4);
int i3 = BitConverter.ToInt32(retval, 8);
int i4 = BitConverter.ToInt32(retval, 12);
return new decimal(new int[] { i1, i2, i3, i4 });
}
/// <summary>
/// eads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.
/// </summary>
/// <returns>An 8-byte floating point value read from the current stream.</returns>
public static double ReadDoubleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToDouble(retval, 0);
}
/// <summary>
/// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.
/// </summary>
/// <returns>A 2-byte signed integer read from the current stream.</returns>
public static short ReadInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToInt16(retval, 0);
}
/// <summary>
/// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
/// </summary>
/// <returns>A 4-byte signed integer read from the current stream.</returns>
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToInt32(retval, 0);
}
/// <summary>
/// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.
/// </summary>
/// <returns>An 8-byte signed integer read from the current stream.</returns>
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToInt64(retval, 0);
}
/// <summary>
/// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
/// </summary>
/// <returns>A 4-byte floating point value read from the current stream.</returns>
public static float ReadSingleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToSingle(retval, 0);
}
/// <summary>
/// Reads a 2-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by two bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>A 2-byte unsigned integer read from this stream.</returns>
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToUInt16(retval, 0);
}
/// <summary>
/// Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>A 4-byte unsigned integer read from this stream.</returns>
public static uint ReadUInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToUInt32(retval, 0);
}
/// <summary>
/// Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>An 8-byte unsigned integer read from this stream.</returns>
public static ulong ReadUInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToUInt64(retval, 0);
}
}
}

View File

@@ -0,0 +1,545 @@
using SabreTools.Library.Data;
namespace SabreTools.Library.Tools
{
public static class Converters
{
/// <summary>
/// Get DatFormat value from input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>DatFormat value corresponding to the string</returns>
public static DatFormat AsDatFormat(this string input)
{
switch (input?.Trim().ToLowerInvariant())
{
case "all":
return DatFormat.ALL;
case "am":
case "attractmode":
return DatFormat.AttractMode;
case "cmp":
case "clrmamepro":
return DatFormat.ClrMamePro;
case "csv":
return DatFormat.CSV;
case "dc":
case "doscenter":
return DatFormat.DOSCenter;
case "json":
return DatFormat.Json;
case "lr":
case "listrom":
return DatFormat.Listrom;
case "lx":
case "listxml":
return DatFormat.Listxml;
case "md5":
return DatFormat.RedumpMD5;
case "miss":
case "missfile":
return DatFormat.MissFile;
case "msx":
case "openmsx":
return DatFormat.OpenMSX;
case "ol":
case "offlinelist":
return DatFormat.OfflineList;
case "rc":
case "romcenter":
return DatFormat.RomCenter;
#if NET_FRAMEWORK
case "ripemd160":
return DatFormat.RedumpRIPEMD160;
#endif
case "sd":
case "sabredat":
return DatFormat.SabreDat;
case "sfv":
return DatFormat.RedumpSFV;
case "sha1":
return DatFormat.RedumpSHA1;
case "sha256":
return DatFormat.RedumpSHA256;
case "sha384":
return DatFormat.RedumpSHA384;
case "sha512":
return DatFormat.RedumpSHA512;
case "sl":
case "softwarelist":
return DatFormat.SoftwareList;
case "smdb":
case "everdrive":
return DatFormat.EverdriveSMDB;
case "ssv":
return DatFormat.SSV;
case "tsv":
return DatFormat.TSV;
case "xml":
case "logiqx":
return DatFormat.Logiqx;
default:
return 0x0;
}
}
/// <summary>
/// Get the field associated with each hash type
/// </summary>
public static Field AsField(this Hash hash)
{
switch (hash)
{
case Hash.CRC:
return Field.CRC;
case Hash.MD5:
return Field.MD5;
#if NET_FRAMEWORK
case Hash.RIPEMD160:
return Field.RIPEMD160;
#endif
case Hash.SHA1:
return Field.SHA1;
case Hash.SHA256:
return Field.SHA256;
case Hash.SHA384:
return Field.SHA384;
case Hash.SHA512:
return Field.SHA512;
default:
return Field.NULL;
}
}
/// <summary>
/// Get Field value from input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>Field value corresponding to the string</returns>
public static Field AsField(this string input)
{
switch (input?.ToLowerInvariant())
{
case "areaname":
return Field.AreaName;
case "areasize":
return Field.AreaSize;
case "bios":
return Field.Bios;
case "biosdescription":
case "bios description":
case "biossetdescription":
case "biosset description":
case "bios set description":
return Field.BiosDescription;
case "board":
return Field.Board;
case "cloneof":
return Field.CloneOf;
case "comment":
return Field.Comment;
case "crc":
return Field.CRC;
case "default":
return Field.Default;
case "date":
return Field.Date;
case "description":
return Field.Description;
case "devices":
return Field.Devices;
case "features":
return Field.Features;
case "gamename":
case "machinename":
return Field.MachineName;
case "gametype":
case "machinetype":
return Field.MachineType;
case "index":
return Field.Index;
case "infos":
return Field.Infos;
case "language":
return Field.Language;
case "manufacturer":
return Field.Manufacturer;
case "md5":
return Field.MD5;
case "merge":
return Field.Merge;
case "name":
return Field.Name;
case "offset":
return Field.Offset;
case "optional":
return Field.Optional;
case "partinterface":
return Field.PartInterface;
case "partname":
return Field.PartName;
case "publisher":
return Field.Publisher;
case "rebuildto":
return Field.RebuildTo;
case "region":
return Field.Region;
#if NET_FRAMEWORK
case "ripemd160":
return Field.RIPEMD160;
#endif
case "romof":
return Field.RomOf;
case "runnable":
return Field.Runnable;
case "sampleof":
return Field.SampleOf;
case "sha1":
return Field.SHA1;
case "sha256":
return Field.SHA256;
case "sha384":
return Field.SHA384;
case "sha512":
return Field.SHA512;
case "size":
return Field.Size;
case "slotoptions":
return Field.SlotOptions;
case "sourcefile":
return Field.SourceFile;
case "status":
return Field.Status;
case "supported":
return Field.Supported;
case "writable":
return Field.Writable;
case "year":
return Field.Year;
default:
return Field.NULL;
}
}
/// <summary>
/// Get ForceMerging value from input string
/// </summary>
/// <param name="forcemerge">String to get value from</param>
/// <returns>ForceMerging value corresponding to the string</returns>
public static ForceMerging AsForceMerging(this string forcemerge)
{
#if NET_FRAMEWORK
switch (forcemerge?.ToLowerInvariant())
{
case "split":
return ForceMerging.Split;
case "merged":
return ForceMerging.Merged;
case "nonmerged":
return ForceMerging.NonMerged;
case "full":
return ForceMerging.Full;
case "none":
default:
return ForceMerging.None;
}
#else
return forcemerge?.ToLowerInvariant() switch
{
"split" => ForceMerging.Split,
"merged" => ForceMerging.Merged,
"nonmerged" => ForceMerging.NonMerged,
"full" => ForceMerging.Full,
"none" => ForceMerging.None,
_ => ForceMerging.None,
};
#endif
}
/// <summary>
/// Get ForceNodump value from input string
/// </summary>
/// <param name="forcend">String to get value from</param>
/// <returns>ForceNodump value corresponding to the string</returns>
public static ForceNodump AsForceNodump(this string forcend)
{
#if NET_FRAMEWORK
switch (forcend?.ToLowerInvariant())
{
case "obsolete":
return ForceNodump.Obsolete;
case "required":
return ForceNodump.Required;
case "ignore":
return ForceNodump.Ignore;
case "none":
default:
return ForceNodump.None;
}
#else
return forcend?.ToLowerInvariant() switch
{
"obsolete" => ForceNodump.Obsolete,
"required" => ForceNodump.Required,
"ignore" => ForceNodump.Ignore,
"none" => ForceNodump.None,
_ => ForceNodump.None,
};
#endif
}
/// <summary>
/// Get ForcePacking value from input string
/// </summary>
/// <param name="forcepack">String to get value from</param>
/// <returns>ForcePacking value corresponding to the string</returns>
public static ForcePacking AsForcePacking(this string forcepack)
{
#if NET_FRAMEWORK
switch (forcepack?.ToLowerInvariant())
{
case "yes":
case "zip":
return ForcePacking.Zip;
case "no":
case "unzip":
return ForcePacking.Unzip;
case "none":
default:
return ForcePacking.None;
}
#else
return forcepack?.ToLowerInvariant() switch
{
"yes" => ForcePacking.Zip,
"zip" => ForcePacking.Zip,
"no" => ForcePacking.Unzip,
"unzip" => ForcePacking.Unzip,
"none" => ForcePacking.None,
_ => ForcePacking.None,
};
#endif
}
/// <summary>
/// Get ItemStatus value from input string
/// </summary>
/// <param name="status">String to get value from</param>
/// <returns>ItemStatus value corresponding to the string</returns>
public static ItemStatus AsItemStatus(this string status)
{
#if NET_FRAMEWORK
switch (status?.ToLowerInvariant())
{
case "good":
return ItemStatus.Good;
case "baddump":
return ItemStatus.BadDump;
case "nodump":
case "yes":
return ItemStatus.Nodump;
case "verified":
return ItemStatus.Verified;
case "none":
case "no":
default:
return ItemStatus.None;
}
#else
return status?.ToLowerInvariant() switch
{
"good" => ItemStatus.Good,
"baddump" => ItemStatus.BadDump,
"nodump" => ItemStatus.Nodump,
"yes" => ItemStatus.Nodump,
"verified" => ItemStatus.Verified,
"none" => ItemStatus.None,
"no" => ItemStatus.None,
_ => ItemStatus.None,
};
#endif
}
/// <summary>
/// Get ItemType? value from input string
/// </summary>
/// <param name="itemType">String to get value from</param>
/// <returns>ItemType? value corresponding to the string</returns>
public static ItemType? AsItemType(this string itemType)
{
#if NET_FRAMEWORK
switch (itemType?.ToLowerInvariant())
{
case "archive":
return ItemType.Archive;
case "biosset":
return ItemType.BiosSet;
case "blank":
return ItemType.Blank;
case "disk":
return ItemType.Disk;
case "release":
return ItemType.Release;
case "rom":
return ItemType.Rom;
case "sample":
return ItemType.Sample;
default:
return null;
}
#else
return itemType?.ToLowerInvariant() switch
{
"archive" => ItemType.Archive,
"biosset" => ItemType.BiosSet,
"blank" => ItemType.Blank,
"disk" => ItemType.Disk,
"release" => ItemType.Release,
"rom" => ItemType.Rom,
"sample" => ItemType.Sample,
_ => null,
};
#endif
}
/// <summary>
/// Get MachineType value from input string
/// </summary>
/// <param name="gametype">String to get value from</param>
/// <returns>MachineType value corresponding to the string</returns>
public static MachineType AsMachineType(this string gametype)
{
#if NET_FRAMEWORK
switch (gametype?.ToLowerInvariant())
{
case "bios":
return MachineType.Bios;
case "dev":
case "device":
return MachineType.Device;
case "mech":
case "mechanical":
return MachineType.Mechanical;
case "none":
default:
return MachineType.None;
}
#else
return gametype?.ToLowerInvariant() switch
{
"bios" => MachineType.Bios,
"dev" => MachineType.Device,
"device" => MachineType.Device,
"mech" => MachineType.Mechanical,
"mechanical" => MachineType.Mechanical,
"none" => MachineType.None,
_ => MachineType.None,
};
#endif
}
/// <summary>
/// Get SplitType value from input ForceMerging
/// </summary>
/// <param name="forceMerging">ForceMerging to get value from</param>
/// <returns>SplitType value corresponding to the string</returns>
public static SplitType AsSplitType(this ForceMerging forceMerging)
{
#if NET_FRAMEWORK
switch (forceMerging)
{
case ForceMerging.Split:
return SplitType.Split;
case ForceMerging.Merged:
return SplitType.Merged;
case ForceMerging.NonMerged:
return SplitType.NonMerged;
case ForceMerging.Full:
return SplitType.FullNonMerged;
case ForceMerging.None:
default:
return SplitType.None;
}
#else
return forceMerging switch
{
ForceMerging.Split => SplitType.Split,
ForceMerging.Merged => SplitType.Merged,
ForceMerging.NonMerged => SplitType.NonMerged,
ForceMerging.Full => SplitType.FullNonMerged,
ForceMerging.None => SplitType.None,
_ => SplitType.None,
};
#endif
}
/// <summary>
/// Get StatReportFormat value from input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>StatReportFormat value corresponding to the string</returns>
public static StatReportFormat AsStatReportFormat(this string input)
{
#if NET_FRAMEWORK
switch (input?.Trim().ToLowerInvariant())
{
case "all":
return StatReportFormat.All;
case "csv":
return StatReportFormat.CSV;
case "html":
return StatReportFormat.HTML;
case "ssv":
return StatReportFormat.SSV;
case "text":
return StatReportFormat.Textfile;
case "tsv":
return StatReportFormat.TSV;
default:
return 0x0;
}
#else
return input?.Trim().ToLowerInvariant() switch
{
"all" => StatReportFormat.All,
"csv" => StatReportFormat.CSV,
"html" => StatReportFormat.HTML,
"ssv" => StatReportFormat.SSV,
"text" => StatReportFormat.Textfile,
"tsv" => StatReportFormat.TSV,
_ => 0x0,
};
#endif
}
/// <summary>
/// Get bool? value from input string
/// </summary>
/// <param name="yesno">String to get value from</param>
/// <returns>bool? corresponding to the string</returns>
public static bool? AsYesNo(this string yesno)
{
#if NET_FRAMEWORK
switch (yesno?.ToLowerInvariant())
{
case "yes":
return true;
case "no":
return false;
case "partial":
default:
return null;
}
#else
return yesno?.ToLowerInvariant() switch
{
"yes" => true,
"no" => false,
"partial" => null,
_ => null,
};
#endif
}
}
}

View File

@@ -3,7 +3,7 @@ using System.IO;
using System.Collections.Generic;
using SabreTools.Library.Data;
using Mono.Data.Sqlite;
using Microsoft.Data.Sqlite;
namespace SabreTools.Library.Tools
{
@@ -20,8 +20,6 @@ namespace SabreTools.Library.Tools
/// <param name="type">Name of the source skipper file</param>
public static void AddHeaderToDatabase(string header, string SHA1, string source)
{
bool exists = false;
// Ensure the database exists
EnsureDatabase(Constants.HeadererDbSchema, Constants.HeadererFileName, Constants.HeadererConnectionString);
@@ -32,7 +30,7 @@ namespace SabreTools.Library.Tools
string query = $"SELECT * FROM data WHERE sha1='{SHA1}' AND header='{header}'";
SqliteCommand slc = new SqliteCommand(query, dbc);
SqliteDataReader sldr = slc.ExecuteReader();
exists = sldr.HasRows;
bool exists = sldr.HasRows;
if (!exists)
{
@@ -60,7 +58,7 @@ namespace SabreTools.Library.Tools
// Make sure the file exists
if (!File.Exists(db))
SqliteConnection.CreateFile(db);
File.Create(db);
// Open the database connection
SqliteConnection dbc = new SqliteConnection(connectionString);

View File

@@ -0,0 +1,286 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SabreTools.Library.Data;
using NaturalSort;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Extensions to Directory functionality
/// </summary>
public static class DirectoryExtensions
{
/// <summary>
/// Cleans out the temporary directory
/// </summary>
/// <param name="dir">Name of the directory to clean out</param>
public static void Clean(string dir)
{
foreach (string file in Directory.EnumerateFiles(dir, "*", SearchOption.TopDirectoryOnly))
{
FileExtensions.TryDelete(file);
}
foreach (string subdir in Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly))
{
TryDelete(subdir);
}
}
/// <summary>
/// Ensure the output directory is a proper format and can be created
/// </summary>
/// <param name="dir">Directory to check</param>
/// <param name="create">True if the directory should be created, false otherwise (default)</param>
/// <param name="temp">True if this is a temp directory, false otherwise</param>
/// <returns>Full path to the directory</returns>
public static string Ensure(string dir, bool create = false, bool temp = false)
{
// If the output directory is invalid
if (string.IsNullOrWhiteSpace(dir))
{
if (temp)
dir = Path.GetTempPath();
else
dir = Environment.CurrentDirectory;
}
// Get the full path for the output directory
dir = Path.GetFullPath(dir);
// If we're creating the output folder, do so
if (create)
Directory.CreateDirectory(dir);
return dir;
}
/// <summary>
/// Retrieve a list of just directories 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 (default)</param>
/// <returns>List of strings representing just directories from the inputs</returns>
public static List<string> GetDirectoriesOnly(List<string> inputs, bool appendparent = false)
{
List<string> outputs = new List<string>();
foreach (string input in inputs)
{
if (Directory.Exists(input))
{
List<string> directories = GetDirectoriesOrdered(input);
foreach (string dir in directories)
{
try
{
outputs.Add(Path.GetFullPath(dir) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty));
}
catch (PathTooLongException)
{
Globals.Logger.Warning($"The path for '{dir}' was too long");
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
}
}
}
}
return outputs;
}
/// <summary>
/// Retrieve a list of directories from a directory recursively in proper order
/// </summary>
/// <param name="dir">Directory to parse</param>
/// <returns>List with all new files</returns>
private static List<string> GetDirectoriesOrdered(string dir)
{
return GetDirectoriesOrderedHelper(dir, new List<string>());
}
/// <summary>
/// Retrieve a list of directories from a directory recursively in proper order
/// </summary>
/// <param name="dir">Directory to parse</param>
/// <param name="infiles">List representing existing files</param>
/// <returns>List with all new files</returns>
private static List<string> GetDirectoriesOrderedHelper(string dir, List<string> infiles)
{
// Take care of the files in the top directory
List<string> toadd = Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly).ToList();
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
// Then recurse through and add from the directories
foreach (string subDir in toadd)
{
infiles = GetDirectoriesOrderedHelper(subDir, infiles);
}
// Return the new list
return infiles;
}
/// <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 (default)</param>
/// <returns>List of strings representing just files from the inputs</returns>
public static List<string> GetFilesOnly(List<string> inputs, bool appendparent = false)
{
List<string> outputs = new List<string>();
foreach (string input in inputs)
{
if (Directory.Exists(input))
{
List<string> files = GetFilesOrdered(input);
foreach (string file in files)
{
try
{
outputs.Add(Path.GetFullPath(file) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty));
}
catch (PathTooLongException)
{
Globals.Logger.Warning($"The path for '{file}' was too long");
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
}
}
}
else if (File.Exists(input))
{
try
{
outputs.Add(Path.GetFullPath(input) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty));
}
catch (PathTooLongException)
{
Globals.Logger.Warning($"The path for '{input}' was too long");
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
}
}
}
return outputs;
}
/// <summary>
/// Retrieve a list of files from a directory recursively in proper order
/// </summary>
/// <param name="dir">Directory to parse</param>
/// <param name="infiles">List representing existing files</param>
/// <returns>List with all new files</returns>
public static List<string> GetFilesOrdered(string dir)
{
return GetFilesOrderedHelper(dir, new List<string>());
}
/// <summary>
/// Retrieve a list of files from a directory recursively in proper order
/// </summary>
/// <param name="dir">Directory to parse</param>
/// <param name="infiles">List representing existing files</param>
/// <returns>List with all new files</returns>
private static List<string> GetFilesOrderedHelper(string dir, List<string> infiles)
{
// Take care of the files in the top directory
List<string> toadd = Directory.EnumerateFiles(dir, "*", SearchOption.TopDirectoryOnly).ToList();
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
// Then recurse through and add from the directories
List<string> subDirs = Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly).ToList();
subDirs.Sort(new NaturalComparer());
foreach (string subdir in subDirs)
{
infiles = GetFilesOrderedHelper(subdir, infiles);
}
// Return the new list
return infiles;
}
/// <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 List<string> ListEmpty(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)
.ToList();
}
/// <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>
public static bool TryCreateDirectory(string file, bool throwOnError = false)
{
// Now wrap creating the directory
try
{
Directory.CreateDirectory(file);
return true;
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return false;
}
}
/// <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>
public static bool TryDelete(string file, bool throwOnError = false)
{
// Check if the directory exists first
if (!Directory.Exists(file))
return true;
// Now wrap deleting the directory
try
{
Directory.Delete(file, true);
return true;
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return false;
}
}
}
}

View File

@@ -0,0 +1,550 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using SabreTools.Library.Data;
using SabreTools.Library.FileTypes;
using SabreTools.Library.Readers;
using SabreTools.Library.Skippers;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Extensions to File functionality
/// </summary>
public static class FileExtensions
{
/// <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 AppendBytes(string input, string output, byte[] bytesToAddToHead, byte[] bytesToAddToTail)
{
// If any of the inputs are invalid, skip
if (!File.Exists(input))
return;
#if NET_FRAMEWORK
using (FileStream fsr = TryOpenRead(input))
using (FileStream fsw = TryOpenWrite(output))
{
#else
using FileStream fsr = TryOpenRead(input);
using FileStream fsw = TryOpenWrite(output);
#endif
StreamExtensions.AppendBytes(fsr, fsw, bytesToAddToHead, bytesToAddToTail);
#if NET_FRAMEWORK
}
#endif
}
/// <summary>
/// Get what type of DAT the input file is
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <returns>The DatFormat corresponding to the DAT</returns>
public static DatFormat GetDatFormat(this string filename)
{
// Limit the output formats based on extension
if (!PathExtensions.HasValidDatExtension(filename))
return 0;
// Get the extension from the filename
string ext = PathExtensions.GetNormalizedExtension(filename);
// Read the input file, if possible
Globals.Logger.Verbose($"Attempting to read file to get format: {filename}");
// Check if file exists
if (!File.Exists(filename))
{
Globals.Logger.Warning($"File '{filename}' could not read from!");
return 0;
}
// Some formats should only require the extension to know
switch (ext)
{
case "csv":
return DatFormat.CSV;
case "json":
return DatFormat.Json;
case "md5":
return DatFormat.RedumpMD5;
#if NET_FRAMEWORK
case "ripemd160":
return DatFormat.RedumpRIPEMD160;
#endif
case "sfv":
return DatFormat.RedumpSFV;
case "sha1":
return DatFormat.RedumpSHA1;
case "sha256":
return DatFormat.RedumpSHA256;
case "sha384":
return DatFormat.RedumpSHA384;
case "sha512":
return DatFormat.RedumpSHA512;
case "ssv":
return DatFormat.SSV;
case "tsv":
return DatFormat.TSV;
}
// For everything else, we need to read it
try
{
// Get the first two non-whitespace, non-comment lines to check
StreamReader sr = File.OpenText(filename);
string first = sr.ReadLine().ToLowerInvariant();
while (string.IsNullOrWhiteSpace(first) || first.StartsWith("<!--"))
first = sr.ReadLine().ToLowerInvariant();
string second = sr.ReadLine().ToLowerInvariant();
while (string.IsNullOrWhiteSpace(second) || second.StartsWith("<!--"))
second = sr.ReadLine().ToLowerInvariant();
sr.Dispose();
// If we have an XML-based DAT
if (first.Contains("<?xml") && first.Contains("?>"))
{
if (second.StartsWith("<!doctype datafile"))
return DatFormat.Logiqx;
else if (second.StartsWith("<!doctype mame")
|| second.StartsWith("<!doctype m1")
|| second.StartsWith("<mame")
|| second.StartsWith("<m1"))
return DatFormat.Listxml;
else if (second.StartsWith("<!doctype softwaredb"))
return DatFormat.OpenMSX;
else if (second.StartsWith("<!doctype softwarelist"))
return DatFormat.SoftwareList;
else if (second.StartsWith("<!doctype sabredat"))
return DatFormat.SabreDat;
else if ((second.StartsWith("<dat") && !second.StartsWith("<datafile"))
|| second.StartsWith("<?xml-stylesheet"))
return DatFormat.OfflineList;
// Older and non-compliant DATs
else
return DatFormat.Logiqx;
}
// If we have an SMDB (SHA-256, Filename, SHA-1, MD5, CRC32)
else if (Regex.IsMatch(first, @"[0-9a-f]{64}\t.*?\t[0-9a-f]{40}\t[0-9a-f]{32}\t[0-9a-f]{8}"))
return DatFormat.EverdriveSMDB;
// If we have an INI-based DAT
else if (first.Contains("[") && first.Contains("]"))
return DatFormat.RomCenter;
// If we have a listroms DAT
else if (first.StartsWith("roms required for driver"))
return DatFormat.Listrom;
// If we have a CMP-based DAT
else if (first.Contains("clrmamepro"))
return DatFormat.ClrMamePro;
else if (first.Contains("romvault"))
return DatFormat.ClrMamePro;
else if (first.Contains("doscenter"))
return DatFormat.DOSCenter;
else if (first.Contains("#Name;Title;Emulator;CloneOf;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra"))
return DatFormat.AttractMode;
else
return DatFormat.ClrMamePro;
}
catch (Exception)
{
return 0;
}
}
/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
/// Defaults to ASCII when detection of the text file's endianness fails.
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding.</returns>
/// <link>http://stackoverflow.com/questions/3825390/effective-way-to-find-any-files-encoding</link>
public static Encoding GetEncoding(string filename)
{
// Read the BOM
var bom = new byte[4];
FileStream file = FileExtensions.TryOpenRead(filename);
file.Read(bom, 0, 4);
file.Dispose();
// Analyze the BOM
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
return Encoding.Default;
}
/// <summary>
/// Returns the file type of an input file
/// </summary>
/// <param name="input">Input file to check</param>
/// <returns>FileType of inputted file (null on error)</returns>
public static FileType? GetFileType(this string input)
{
FileType? outFileType = null;
// If the file is null, then we have no archive type
if (input == null)
return outFileType;
// First line of defense is going to be the extension, for better or worse
if (!PathExtensions.HasValidArchiveExtension(input))
return outFileType;
// Read the first bytes of the file and get the magic number
try
{
byte[] magic = new byte[8];
BinaryReader br = new BinaryReader(TryOpenRead(input));
magic = br.ReadBytes(8);
br.Dispose();
// Now try to match it to a known signature
if (magic.StartsWith(Constants.SevenZipSignature))
{
outFileType = FileType.SevenZipArchive;
}
else if (magic.StartsWith(Constants.CHDSignature))
{
outFileType = FileType.CHD;
}
else if (magic.StartsWith(Constants.GzSignature))
{
outFileType = FileType.GZipArchive;
}
else if (magic.StartsWith(Constants.LRZipSignature))
{
outFileType = FileType.LRZipArchive;
}
else if (magic.StartsWith(Constants.LZ4Signature)
|| magic.StartsWith(Constants.LZ4SkippableMinSignature)
|| magic.StartsWith(Constants.LZ4SkippableMaxSignature))
{
outFileType = FileType.LZ4Archive;
}
else if (magic.StartsWith(Constants.RarSignature)
|| magic.StartsWith(Constants.RarFiveSignature))
{
outFileType = FileType.RarArchive;
}
else if (magic.StartsWith(Constants.TarSignature)
|| magic.StartsWith(Constants.TarZeroSignature))
{
outFileType = FileType.TapeArchive;
}
else if (magic.StartsWith(Constants.XZSignature))
{
outFileType = FileType.XZArchive;
}
else if (magic.StartsWith(Constants.ZipSignature)
|| magic.StartsWith(Constants.ZipSignatureEmpty)
|| magic.StartsWith(Constants.ZipSignatureSpanned))
{
outFileType = FileType.ZipArchive;
}
else if (magic.StartsWith(Constants.ZPAQSignature))
{
outFileType = FileType.ZPAQArchive;
}
else if (magic.StartsWith(Constants.ZstdSignature))
{
outFileType = FileType.ZstdArchive;
}
}
catch (Exception)
{
// Don't log file open errors
}
return outFileType;
}
/// <summary>
/// Returns if the first byte array starts with the second array
/// </summary>
/// <param name="arr1">First byte array to compare</param>
/// <param name="arr2">Second byte array to compare</param>
/// <param name="exact">True if the input arrays should match exactly, false otherwise (default)</param>
/// <returns>True if the first byte array starts with the second, false otherwise</returns>
private static bool StartsWith(this byte[] arr1, byte[] arr2, bool exact = false)
{
// If we have any invalid inputs, we return false
if (arr1 == null || arr2 == null
|| arr1.Length == 0 || arr2.Length == 0
|| arr2.Length > arr1.Length
|| (exact && arr1.Length != arr2.Length))
{
return false;
}
// Otherwise, loop through and see
for (int i = 0; i < arr2.Length; i++)
{
if (arr1[i] != arr2[i])
return false;
}
return true;
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated (defaults to none)</param>
/// <param name="date">True if the file Date should be included, false otherwise (default)</param>
/// <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>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static BaseFile GetInfo(string input, Hash omitFromScan = 0x0, bool date = false, string header = null, bool chdsAsFiles = true)
{
// Add safeguard if file doesn't exist
if (!File.Exists(input))
return null;
// Get the information from the file stream
BaseFile baseFile;
if (header != null)
{
SkipperRule rule = Skipper.GetMatchingRule(input, Path.GetFileNameWithoutExtension(header));
// 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 = TryOpenRead(input);
// Transform the stream and get the information from it
rule.TransformStream(inputStream, outputStream, keepReadOpen: false, keepWriteOpen: true);
baseFile = outputStream.GetInfo(omitFromScan: omitFromScan, keepReadOpen: false, chdsAsFiles: chdsAsFiles);
// Dispose of the streams
outputStream.Dispose();
inputStream.Dispose();
}
// Otherwise, just get the info
else
{
baseFile = TryOpenRead(input).GetInfo(omitFromScan: omitFromScan, keepReadOpen: false, chdsAsFiles: chdsAsFiles);
}
}
else
{
baseFile = TryOpenRead(input).GetInfo(omitFromScan: omitFromScan, keepReadOpen: false, chdsAsFiles: chdsAsFiles);
}
// Add unique data from the file
baseFile.Filename = Path.GetFileName(input);
baseFile.Date = (date ? new FileInfo(input).LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss") : string.Empty);
return baseFile;
}
/// <summary>
/// Get the IniReader associated with a file, if possible
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="validateRows">True if rows should be in a proper format, false if invalid is okay</param>
/// <returns>The IniReader representing the (possibly converted) file, null otherwise</returns>
public static IniReader GetIniReader(this string filename, bool validateRows)
{
Globals.Logger.Verbose($"Attempting to read file: {filename}");
// Check if file exists
if (!File.Exists(filename))
{
Globals.Logger.Warning($"File '{filename}' could not read from!");
return null;
}
IniReader ir = new IniReader(filename)
{
ValidateRows = validateRows
};
return ir;
}
/// <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>
public static XmlReader GetXmlTextReader(this string filename)
{
Globals.Logger.Verbose($"Attempting to read file: {filename}");
// Check if file exists
if (!File.Exists(filename))
{
Globals.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>
/// 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;
}
}
/// <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>
public static bool TryDelete(string file, bool throwOnError = false)
{
// 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;
}
}
/// <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 an existing 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;
}
}
}
}

View File

@@ -54,7 +54,7 @@ namespace SabreTools.Library.Tools
/// </summary>
public void Stop()
{
Globals.Logger.User($"{_subject} completed in {DateTime.Now.Subtract(_startTime).ToString("hh:mm:ss.fffff")}");
Globals.Logger.User($"{_subject} completed in {DateTime.Now.Subtract(_startTime):G}");
}
}
}

View File

@@ -13,17 +13,17 @@ namespace SabreTools.Library.Tools
public class Logger
{
// Private instance variables
private bool _tofile;
private readonly bool _tofile;
private bool _warnings;
private bool _errors;
private string _filename;
private LogLevel _filter;
private readonly string _filename;
private readonly LogLevel _filter;
private DateTime _start;
private StreamWriter _log;
private object _lock = new object(); // This is used during multithreaded logging
private readonly object _lock = new object(); // This is used during multithreaded logging
// Private required variables
private string _basepath = Path.Combine(Globals.ExeDir, "logs") + Path.DirectorySeparatorChar;
private readonly string _basepath = Path.Combine(Globals.ExeDir, "logs") + Path.DirectorySeparatorChar;
/// <summary>
/// Initialize a console-only logger object
@@ -50,7 +50,7 @@ namespace SabreTools.Library.Tools
_tofile = tofile;
_warnings = false;
_errors = false;
_filename = $"{Path.GetFileNameWithoutExtension(filename)} ({DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss")}).{Utilities.GetExtension(filename)}";
_filename = $"{Path.GetFileNameWithoutExtension(filename)} ({DateTime.Now:yyyy-MM-dd HH-mm-ss}).{PathExtensions.GetNormalizedExtension(filename)}";
_filter = filter;
if (!Directory.Exists(_basepath))
@@ -71,11 +71,13 @@ namespace SabreTools.Library.Tools
try
{
FileStream logfile = Utilities.TryCreate(Path.Combine(_basepath, _filename));
_log = new StreamWriter(logfile, Encoding.UTF8, (int)(4 * Constants.KibiByte), true);
_log.AutoFlush = true;
FileStream logfile = FileExtensions.TryCreate(Path.Combine(_basepath, _filename));
_log = new StreamWriter(logfile, Encoding.UTF8, (int)(4 * Constants.KibiByte), true)
{
AutoFlush = true
};
_log.WriteLine($"Logging started {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
_log.WriteLine($"Logging started {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_log.WriteLine($"Command run: {Globals.CommandLineArgs}");
}
catch
@@ -104,7 +106,7 @@ namespace SabreTools.Library.Tools
TimeSpan span = DateTime.Now.Subtract(_start);
// Special case for multi-day runs
string total = string.Empty;
string total;
if (span >= TimeSpan.FromDays(1))
total = span.ToString(@"d\:hh\:mm\:ss");
else
@@ -118,7 +120,7 @@ namespace SabreTools.Library.Tools
try
{
_log.WriteLine($"Logging ended {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
_log.WriteLine($"Logging ended {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_log.WriteLine($"Total runtime: {total}");
Console.WriteLine($"Total runtime: {total}");
_log.Close();

View File

@@ -0,0 +1,223 @@
using System;
using System.IO;
using SabreTools.Library.Data;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Extensions to Path functionality
/// </summary>
public static class PathExtensions
{
/// <summary>
/// Get the extension from the path, if possible
/// </summary>
/// <param name="path">Path to get extension from</param>
/// <returns>Extension, if possible</returns>
public static string GetNormalizedExtension(string path)
{
// Check null or empty first
if (string.IsNullOrWhiteSpace(path))
return null;
// Get the extension from the path, if possible
string ext = Path.GetExtension(path)?.ToLowerInvariant();
// Check if the extension is null or empty
if (string.IsNullOrWhiteSpace(ext))
return null;
// Make sure that extensions are valid
ext = ext.TrimStart('.');
return ext;
}
/// <summary>
/// Get the proper filename (with subpath) from the file and parent combination
/// </summary>
/// <param name="path">Input combined path to use</param>
/// <param name="sanitize">True if path separators should be converted to '-', false otherwise</param>
/// <returns>Subpath for the file</returns>
public static string GetNormalizedFileName(string path, bool sanitize)
{
// Check that we have a combined path first
if (!path.Contains("¬"))
{
string filename = Path.GetFileName(path);
if (sanitize)
filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-');
return filename;
}
// First separate out the parts
string child = path.Split('¬')[0];
string parent = path.Split('¬')[1];
// If the parts are the same, return the filename from the first part
if (string.Equals(child, parent, StringComparison.Ordinal))
{
string filename = Path.GetFileName(child);
if (sanitize)
filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-');
return filename;
}
// Otherwise, remove the parent from the child and return the remainder
else
{
string filename = child.Remove(0, parent.Length + 1);
if (sanitize)
filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-');
return filename;
}
}
/// <summary>
/// Get the proper output path for a given input file and output directory
/// </summary>
/// <param name="outDir">Output directory to use</param>
/// <param name="inputpath">Input path to create output for</param>
/// <param name="inplace">True if the output file should go to the same input folder, false otherwise</param>
/// <returns>Complete output path</returns>
public static string GetOutputPath(string outDir, string inputpath, bool inplace)
{
// First, we need to ensure the output directory
outDir = DirectoryExtensions.Ensure(outDir);
// Check if we have a split path or not
bool splitpath = inputpath.Contains("¬");
// If we have a split path, we need to treat the input separately
if (splitpath)
{
string[] split = inputpath.Split('¬');
// If we have an inplace output, use the directory name from the input path
if (inplace)
{
outDir = Path.GetDirectoryName(split[0]);
}
// TODO: Should this be the default? Always create a subfolder if a folder is found?
// If we are processing a path that is coming from a directory and we are outputting to the current directory, we want to get the subfolder to write to
else if (split[0].Length != split[1].Length && outDir == Environment.CurrentDirectory)
{
outDir = Path.GetDirectoryName(Path.Combine(outDir, split[0].Remove(0, Path.GetDirectoryName(split[1]).Length + 1)));
}
// If we are processing a path that is coming from a directory, we want to get the subfolder to write to
else if (split[0].Length != split[1].Length)
{
outDir = Path.GetDirectoryName(Path.Combine(outDir, split[0].Remove(0, split[1].Length + 1)));
}
// If we are processing a single file from the root of a directory, we just use the output directory
else
{
// No-op
}
}
// Otherwise, assume the input path is just a filename
else
{
// If we have an inplace output, use the directory name from the input path
if (inplace)
{
outDir = Path.GetDirectoryName(inputpath);
}
// Otherwise, just use the supplied output directory
else
{
// No-op
}
}
// Finally, return the output directory
return outDir;
}
/// <summary>
/// Get a proper romba sub path
/// </summary>
/// <param name="hash">SHA-1 hash to get the path for</param>
/// <returns>Subfolder path for the given hash</returns>
public static string GetRombaPath(string hash)
{
// If the hash isn't the right size, then we return null
if (hash.Length != Constants.SHA1Length) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
return null;
return Path.Combine(hash.Substring(0, 2), hash.Substring(2, 2), hash.Substring(4, 2), hash.Substring(6, 2), hash + ".gz");
}
/// <summary>
/// Get if the given path has a valid DAT extension
/// </summary>
/// <param name="path">Path to check</param>
/// <returns>True if the extension is valid, false otherwise</returns>
public static bool HasValidArchiveExtension(string path)
{
// Get the extension from the path, if possible
string ext = PathExtensions.GetNormalizedExtension(path);
// Check against the list of known archive extensions
switch (ext)
{
case "7z":
case "gz":
case "lzma":
case "rar":
case "rev":
case "r00":
case "r01":
case "tar":
case "tgz":
case "tlz":
case "zip":
case "zipx":
return true;
default:
return false;
}
}
/// <summary>
/// Get if the given path has a valid DAT extension
/// </summary>
/// <param name="path">Path to check</param>
/// <returns>True if the extension is valid, false otherwise</returns>
public static bool HasValidDatExtension(string path)
{
// Get the extension from the path, if possible
string ext = GetNormalizedExtension(path);
// Check against the list of known DAT extensions
switch (ext)
{
case "csv":
case "dat":
case "json":
case "md5":
case "ripemd160":
case "sfv":
case "sha1":
case "sha256":
case "sha384":
case "sha512":
case "ssv":
case "tsv":
case "txt":
case "xml":
return true;
default:
return false;
}
}
}
}

View File

@@ -0,0 +1,413 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SabreTools.Library.Data;
namespace SabreTools.Library.Tools
{
public static class Sanitizer
{
/// <summary>
/// Get a sanitized Date from an input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>Date as a string, if possible</returns>
public static string CleanDate(string input)
{
string date = string.Empty;
if (input != null)
{
if (DateTime.TryParse(input, out DateTime dateTime))
date = dateTime.ToString();
else
date = input;
}
return date;
}
/// <summary>
/// Clean a game (or rom) name to the WoD standard
/// </summary>
/// <param name="game">Name of the game to be cleaned</param>
/// <returns>The cleaned name</returns>
public static string CleanGameName(string game)
{
///Run the name through the filters to make sure that it's correct
game = NormalizeChars(game);
game = RussianToLatin(game);
game = SearchPattern(game);
game = new Regex(@"(([[(].*[\)\]] )?([^([]+))").Match(game).Groups[1].Value;
game = game.TrimStart().TrimEnd();
return game;
}
/// <summary>
/// Clean a game (or rom) name to the WoD standard
/// </summary>
/// <param name="game">Array representing the path to be cleaned</param>
/// <returns>The cleaned name</returns>
public static string CleanGameName(string[] game)
{
#if NET_FRAMEWORK
game[game.Length - 1] = CleanGameName(game.Last());
#else
game[^1] = CleanGameName(game[^1]);
#endif
string outgame = string.Join(Path.DirectorySeparatorChar.ToString(), game);
outgame = outgame.TrimStart().TrimEnd();
return outgame;
}
/// <summary>
/// Clean a CRC32 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanCRC32(string hash)
{
return CleanHashData(hash, Constants.CRCLength);
}
/// <summary>
/// Clean a MD5 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanMD5(string hash)
{
return CleanHashData(hash, Constants.MD5Length);
}
#if NET_FRAMEWORK
/// <summary>
/// Clean a RIPEMD160 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanRIPEMD160(string hash)
{
return CleanHashData(hash, Constants.RIPEMD160Length);
}
#endif
/// <summary>
/// Clean a SHA1 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanSHA1(string hash)
{
return CleanHashData(hash, Constants.SHA1Length);
}
/// <summary>
/// Clean a SHA256 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanSHA256(string hash)
{
return CleanHashData(hash, Constants.SHA256Length);
}
/// <summary>
/// Clean a SHA384 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanSHA384(string hash)
{
return CleanHashData(hash, Constants.SHA384Length);
}
/// <summary>
/// Clean a SHA512 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanSHA512(string hash)
{
return CleanHashData(hash, Constants.SHA512Length);
}
/// <summary>
/// Clean a hash string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <param name="padding">Amount of characters to pad to</param>
/// <returns>Cleaned string</returns>
private static string CleanHashData(string hash, int padding)
{
// If we have a known blank hash, return blank
if (string.IsNullOrWhiteSpace(hash) || hash == "-" || hash == "_")
return string.Empty;
// Check to see if it's a "hex" hash
hash = hash.Trim().Replace("0x", string.Empty);
// If we have a blank hash now, return blank
if (string.IsNullOrWhiteSpace(hash))
return string.Empty;
// If the hash shorter than the required length, pad it
if (hash.Length < padding)
hash = hash.PadLeft(padding, '0');
// If the hash is longer than the required length, it's invalid
else if (hash.Length > padding)
return string.Empty;
// Now normalize the hash
hash = hash.ToLowerInvariant();
// Otherwise, make sure that every character is a proper match
for (int i = 0; i < hash.Length; i++)
{
if ((hash[i] < '0' || hash[i] > '9') && (hash[i] < 'a' || hash[i] > 'f'))
{
hash = string.Empty;
break;
}
}
return hash;
}
/// <summary>
/// Clean a hash string from a Listrom DAT
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanListromHashData(string hash)
{
if (hash.StartsWith("CRC"))
return hash.Substring(4, 8).ToLowerInvariant();
else if (hash.StartsWith("SHA1"))
return hash.Substring(5, 40).ToLowerInvariant();
return hash;
}
/// <summary>
/// Get a sanitized size from an input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>Size as a long, if possible</returns>
public static long CleanSize(string input)
{
long size = -1;
if (input != null && input.Contains("0x"))
size = Convert.ToInt64(input, 16);
else if (input != null)
Int64.TryParse(input, out size);
return size;
}
/// <summary>
/// Remove all chars that are considered path unsafe
/// </summary>
/// <param name="s">Input string to clean</param>
/// <returns>Cleaned string</returns>
public static string RemovePathUnsafeCharacters(string s)
{
List<char> invalidPath = Path.GetInvalidPathChars().ToList();
return new string(s.Where(c => !invalidPath.Contains(c)).ToArray());
}
/// <summary>
/// Remove all unicode-specific chars from a string
/// </summary>
/// <param name="s">Input string to clean</param>
/// <returns>Cleaned string</returns>
public static string RemoveUnicodeCharacters(string s)
{
return new string(s.Where(c => c <= 255).ToArray());
}
/// <summary>
/// Replace accented characters
/// </summary>
/// <param name="input">String to be parsed</param>
/// <returns>String with characters replaced</returns>
private static string NormalizeChars(string input)
{
string[,] charmap = {
{ "Á", "A" }, { "á", "a" },
{ "À", "A" }, { "à", "a" },
{ "Â", "A" }, { "â", "a" },
{ "Ä", "Ae" }, { "ä", "ae" },
{ "Ã", "A" }, { "ã", "a" },
{ "Å", "A" }, { "å", "a" },
{ "Æ", "Ae" }, { "æ", "ae" },
{ "Ç", "C" }, { "ç", "c" },
{ "Ð", "D" }, { "ð", "d" },
{ "É", "E" }, { "é", "e" },
{ "È", "E" }, { "è", "e" },
{ "Ê", "E" }, { "ê", "e" },
{ "Ë", "E" }, { "ë", "e" },
{ "ƒ", "f" },
{ "Í", "I" }, { "í", "i" },
{ "Ì", "I" }, { "ì", "i" },
{ "Î", "I" }, { "î", "i" },
{ "Ï", "I" }, { "ï", "i" },
{ "Ñ", "N" }, { "ñ", "n" },
{ "Ó", "O" }, { "ó", "o" },
{ "Ò", "O" }, { "ò", "o" },
{ "Ô", "O" }, { "ô", "o" },
{ "Ö", "Oe" }, { "ö", "oe" },
{ "Õ", "O" }, { "õ", "o" },
{ "Ø", "O" }, { "ø", "o" },
{ "Š", "S" }, { "š", "s" },
{ "ß", "ss" },
{ "Þ", "B" }, { "þ", "b" },
{ "Ú", "U" }, { "ú", "u" },
{ "Ù", "U" }, { "ù", "u" },
{ "Û", "U" }, { "û", "u" },
{ "Ü", "Ue" }, { "ü", "ue" },
{ "ÿ", "y" },
{ "Ý", "Y" }, { "ý", "y" },
{ "Ž", "Z" }, { "ž", "z" },
};
for (int i = 0; i < charmap.GetLength(0); i++)
{
input = input.Replace(charmap[i, 0], charmap[i, 1]);
}
return input;
}
/// <summary>
/// Convert Cyrillic lettering to Latin lettering
/// </summary>
/// <param name="input">String to be parsed</param>
/// <returns>String with characters replaced</returns>
private static string RussianToLatin(string input)
{
string[,] charmap = {
{ "А", "A" }, { "Б", "B" }, { "В", "V" }, { "Г", "G" }, { "Д", "D" },
{ "Е", "E" }, { "Ё", "Yo" }, { "Ж", "Zh" }, { "З", "Z" }, { "И", "I" },
{ "Й", "J" }, { "К", "K" }, { "Л", "L" }, { "М", "M" }, { "Н", "N" },
{ "О", "O" }, { "П", "P" }, { "Р", "R" }, { "С", "S" }, { "Т", "T" },
{ "У", "U" }, { "Ф", "f" }, { "Х", "Kh" }, { "Ц", "Ts" }, { "Ч", "Ch" },
{ "Ш", "Sh" }, { "Щ", "Sch" }, { "Ъ", string.Empty }, { "Ы", "y" }, { "Ь", string.Empty },
{ "Э", "e" }, { "Ю", "yu" }, { "Я", "ya" }, { "а", "a" }, { "б", "b" },
{ "в", "v" }, { "г", "g" }, { "д", "d" }, { "е", "e" }, { "ё", "yo" },
{ "ж", "zh" }, { "з", "z" }, { "и", "i" }, { "й", "j" }, { "к", "k" },
{ "л", "l" }, { "м", "m" }, { "н", "n" }, { "о", "o" }, { "п", "p" },
{ "р", "r" }, { "с", "s" }, { "т", "t" }, { "у", "u" }, { "ф", "f" },
{ "х", "kh" }, { "ц", "ts" }, { "ч", "ch" }, { "ш", "sh" }, { "щ", "sch" },
{ "ъ", string.Empty }, { "ы", "y" }, { "ь", string.Empty }, { "э", "e" }, { "ю", "yu" },
{ "я", "ya" },
};
for (int i = 0; i < charmap.GetLength(0); i++)
{
input = input.Replace(charmap[i, 0], charmap[i, 1]);
}
return input;
}
/// <summary>
/// Replace special characters and patterns
/// </summary>
/// <param name="input">String to be parsed</param>
/// <returns>String with characters replaced</returns>
private static string SearchPattern(string input)
{
string[,] charmap = {
{ @"~", " - " },
{ @"_", " " },
{ @":", " " },
{ @">", ")" },
{ @"<", "(" },
{ @"\|", "-" },
{ "\"", "'" },
{ @"\*", "." },
{ @"\\", "-" },
{ @"/", "-" },
{ @"\?", " " },
{ @"\(([^)(]*)\(([^)]*)\)([^)(]*)\)", " " },
{ @"\(([^)]+)\)", " " },
{ @"\[([^]]+)\]", " " },
{ @"\{([^}]+)\}", " " },
{ @"(ZZZJUNK|ZZZ-UNK-|ZZZ-UNK |zzz unknow |zzz unk |Copy of |[.][a-z]{3}[.][a-z]{3}[.]|[.][a-z]{3}[.])", " " },
{ @" (r|rev|v|ver)\s*[\d\.]+[^\s]*", " " },
{ @"(( )|(\A))(\d{6}|\d{8})(( )|(\Z))", " " },
{ @"(( )|(\A))(\d{1,2})-(\d{1,2})-(\d{4}|\d{2})", " " },
{ @"(( )|(\A))(\d{4}|\d{2})-(\d{1,2})-(\d{1,2})", " " },
{ @"[-]+", "-" },
{ @"\A\s*\)", " " },
{ @"\A\s*(,|-)", " " },
{ @"\s+", " " },
{ @"\s+,", "," },
{ @"\s*(,|-)\s*\Z", " " },
};
for (int i = 0; i < charmap.GetLength(0); i++)
{
input = Regex.Replace(input, charmap[i, 0], charmap[i, 1]);
}
return input;
}
/// <summary>
/// Get the multiplier to be used with the size given
/// </summary>
/// <param name="sizestring">String with possible size with extension</param>
/// <returns>Tuple of multiplier to use on final size and fixed size string</returns>
public static long ToSize(string sizestring)
{
// If the string is null or empty, we return -1
if (string.IsNullOrWhiteSpace(sizestring))
return -1;
// Make sure the string is in lower case
sizestring = sizestring.ToLowerInvariant();
// Get any trailing size identifiers
long multiplier = 1;
if (sizestring.EndsWith("k") || sizestring.EndsWith("kb"))
multiplier = Constants.KiloByte;
else if (sizestring.EndsWith("ki") || sizestring.EndsWith("kib"))
multiplier = Constants.KibiByte;
else if (sizestring.EndsWith("m") || sizestring.EndsWith("mb"))
multiplier = Constants.MegaByte;
else if (sizestring.EndsWith("mi") || sizestring.EndsWith("mib"))
multiplier = Constants.MibiByte;
else if (sizestring.EndsWith("g") || sizestring.EndsWith("gb"))
multiplier = Constants.GigaByte;
else if (sizestring.EndsWith("gi") || sizestring.EndsWith("gib"))
multiplier = Constants.GibiByte;
else if (sizestring.EndsWith("t") || sizestring.EndsWith("tb"))
multiplier = Constants.TeraByte;
else if (sizestring.EndsWith("ti") || sizestring.EndsWith("tib"))
multiplier = Constants.TibiByte;
else if (sizestring.EndsWith("p") || sizestring.EndsWith("pb"))
multiplier = Constants.PetaByte;
else if (sizestring.EndsWith("pi") || sizestring.EndsWith("pib"))
multiplier = Constants.PibiByte;
// Remove any trailing identifiers
sizestring = sizestring.TrimEnd(new char[] { 'k', 'm', 'g', 't', 'p', 'i', 'b', ' ' });
// Now try to get the size from the string
if (!Int64.TryParse(sizestring, out long size))
size = -1;
else
size *= multiplier;
return size;
}
}
}

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SabreTools.Library.Data;
using SabreTools.Library.FileTypes;
using Compress.ThreadReaders;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Extensions to Stream functionality
/// </summary>
public static class StreamExtensions
{
/// <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 AppendBytes(Stream input, Stream output, byte[] bytesToAddToHead, byte[] bytesToAddToTail)
{
// Write out prepended bytes
if (bytesToAddToHead != null && bytesToAddToHead.Count() > 0)
output.Write(bytesToAddToHead, 0, bytesToAddToHead.Length);
// Now copy the existing file over
input.CopyTo(output);
// Write out appended bytes
if (bytesToAddToTail != null && bytesToAddToTail.Count() > 0)
output.Write(bytesToAddToTail, 0, bytesToAddToTail.Length);
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="size">Size of the input stream</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated (defaults to none)</param>
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static BaseFile GetInfo(this Stream input, long size = -1, Hash omitFromScan = 0x0, bool keepReadOpen = false, bool chdsAsFiles = true)
{
return GetInfoAsync(input, size, omitFromScan, keepReadOpen, chdsAsFiles).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="size">Size of the input stream</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated (defaults to none)</param>
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static async Task<BaseFile> GetInfoAsync(Stream input, long size = -1, Hash omitFromScan = 0x0, bool keepReadOpen = false, bool chdsAsFiles = true)
{
// If we want to automatically set the size
if (size == -1)
size = input.Length;
// We first check to see if it's a CHD if we have to
if (!chdsAsFiles)
{
var chd = CHDFile.Create(input);
input.SeekIfPossible();
// If we found a valid CHD
if (chd != null)
{
if (!keepReadOpen)
input.Dispose();
return chd;
}
}
BaseFile rom = new BaseFile()
{
Size = size,
};
try
{
// Get a list of hashers to run over the buffer
List<Hasher> hashers = new List<Hasher>()
{
new Hasher(Hash.CRC),
new Hasher(Hash.MD5),
#if NET_FRAMEWORK
new Hasher(Hash.RIPEMD160),
#endif
new Hasher(Hash.SHA1),
new Hasher(Hash.SHA256),
new Hasher(Hash.SHA384),
new Hasher(Hash.SHA512),
};
// Initialize the hashing helpers
var loadBuffer = new ThreadLoadBuffer(input);
int buffersize = 3 * 1024 * 1024;
byte[] buffer0 = new byte[buffersize];
byte[] buffer1 = new byte[buffersize];
/*
Please note that some of the following code is adapted from
RomVault. This is a modified version of how RomVault does
threaded hashing. As such, some of the terminology and code
is the same, though variable names and comments may have
been tweaked to better fit this code base.
*/
// Pre load the first buffer
int next = size > buffersize ? buffersize : (int)size;
input.Read(buffer0, 0, next);
int current = next;
size -= next;
bool bufferSelect = true;
while (current > 0)
{
// Trigger the buffer load on the second buffer
next = size > buffersize ? buffersize : (int)size;
if (next > 0)
loadBuffer.Trigger(bufferSelect ? buffer1 : buffer0, next);
byte[] buffer = bufferSelect ? buffer0 : buffer1;
// Run hashes in parallel
await Task.WhenAll(hashers.Select(h => h.Process(buffer, current)));
// Wait for the load buffer worker, if needed
if (next > 0)
loadBuffer.Wait();
// Setup for the next hashing step
current = next;
size -= next;
bufferSelect = !bufferSelect;
}
// Finalize all hashing helpers
loadBuffer.Finish();
await Task.WhenAll(hashers.Select(h => h.Finalize()));
// Get the results
if (!omitFromScan.HasFlag(Hash.CRC))
rom.CRC = hashers.First(h => h.HashType == Hash.CRC).GetHash();
if (!omitFromScan.HasFlag(Hash.MD5))
rom.MD5 = hashers.First(h => h.HashType == Hash.MD5).GetHash();
#if NET_FRAMEWORK
if (!omitFromScan.HasFlag(Hash.RIPEMD160))
rom.RIPEMD160 = hashers.First(h => h.HashType == Hash.RIPEMD160).GetHash();
#endif
if (!omitFromScan.HasFlag(Hash.SHA1))
rom.SHA1 = hashers.First(h => h.HashType == Hash.SHA1).GetHash();
if (!omitFromScan.HasFlag(Hash.SHA256))
rom.SHA256 = hashers.First(h => h.HashType == Hash.SHA256).GetHash();
if (!omitFromScan.HasFlag(Hash.SHA384))
rom.SHA384 = hashers.First(h => h.HashType == Hash.SHA384).GetHash();
if (!omitFromScan.HasFlag(Hash.SHA512))
rom.SHA512 = hashers.First(h => h.HashType == Hash.SHA512).GetHash();
// Dispose of the hashers
loadBuffer.Dispose();
hashers.ForEach(h => h.Dispose());
}
catch (IOException)
{
return new BaseFile();
}
finally
{
if (!keepReadOpen)
input.Dispose();
else
input.SeekIfPossible();
}
return rom;
}
/// <summary>
/// Seek to a specific point in the stream, if possible
/// </summary>
/// <param name="input">Input stream to try seeking on</param>
/// <param name="offset">Optional offset to seek to</param>
private static long SeekIfPossible(this Stream input, long offset = 0)
{
if (input.CanSeek)
{
try
{
if (offset < 0)
return input.Seek(offset, SeekOrigin.End);
else if (offset >= 0)
return input.Seek(offset, SeekOrigin.Begin);
}
catch (NotSupportedException)
{
Globals.Logger.Verbose("Stream does not support seeking to starting offset. Stream position not changed");
}
catch (NotImplementedException)
{
Globals.Logger.Warning("Stream does not support seeking to starting offset. Stream position not changed");
}
}
return input.Position;
}
}
}

File diff suppressed because it is too large Load Diff