Files
SabreTools/DATFromDir/DATFromDir.cs

664 lines
18 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
2016-05-22 23:33:33 -07:00
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
2016-04-12 23:24:08 -07:00
using SabreTools.Helper;
using DamienG.Security.Cryptography;
2016-04-12 23:07:23 -07:00
using SharpCompress.Archive;
using SharpCompress.Common;
using SharpCompress.Reader;
namespace SabreTools
{
2016-04-11 23:49:06 -07:00
/// <summary>
/// Create a DAT file from a specified file, directory, or set thereof
/// </summary>
public class DATFromDir
{
2016-04-11 21:33:55 -07:00
// Path-related variables
2016-04-20 20:41:00 -07:00
private string _basePath;
private string _tempDir;
2016-04-11 21:33:55 -07:00
// User specified inputs
2016-04-20 20:41:00 -07:00
private List<String> _inputs;
private DatData _datdata;
2016-04-20 20:41:00 -07:00
private bool _noMD5;
private bool _noSHA1;
2016-04-21 14:38:11 -07:00
private bool _bare;
2016-04-20 20:41:00 -07:00
private bool _archivesAsFiles;
private bool _enableGzip;
2016-04-11 21:33:55 -07:00
// Other required variables
2016-04-20 20:41:00 -07:00
private Logger _logger;
/// <summary>
/// Create a new DATFromDir object
/// </summary>
/// <param name="inputs">A List of Strings representing the files and folders to be DATted</param>
/// <param name="datdata">DatData object representing the requested output DAT</param>
2016-04-20 20:41:00 -07:00
/// <param name="noMD5">True if MD5 hashes should be skipped over, false otherwise</param>
/// <param name="noSHA1">True if SHA-1 hashes should be skipped over, false otherwise</param>
2016-04-21 14:38:11 -07:00
/// <param name="bare">True if the date should be omitted from the DAT, false otherwise</param>
/// <param name="archivesAsFiles">True if archives should be treated as files, false otherwise</param>
/// <param name="enableGzip">True if GZIP archives should be treated as files, false otherwise</param>>
2016-04-20 20:41:00 -07:00
/// <param name="logger">Logger object for console and file output</param>
public DATFromDir(List<String> inputs, DatData datdata, bool noMD5, bool noSHA1, bool bare, bool archivesAsFiles, bool enableGzip, Logger logger)
2016-04-20 20:41:00 -07:00
{
_inputs = inputs;
_datdata = datdata;
2016-04-20 20:41:00 -07:00
_noMD5 = noMD5;
_noSHA1 = noSHA1;
2016-04-21 14:38:11 -07:00
_bare = bare;
2016-04-20 20:41:00 -07:00
_archivesAsFiles = archivesAsFiles;
_enableGzip = enableGzip;
2016-04-20 20:41:00 -07:00
_logger = logger;
}
2016-04-11 21:33:55 -07:00
2016-04-11 23:49:06 -07:00
/// <summary>
/// Start help or use supplied parameters
/// </summary>
/// <param name="args">String array representing command line parameters</param>
public static void Main(string[] args)
{
2016-04-11 23:12:40 -07:00
Console.Clear();
2016-04-18 16:32:17 -07:00
// Credits take precidence over all
if ((new List<string>(args)).Contains("--credits"))
{
Build.Credits();
return;
}
Logger logger = new Logger(true, "datfromdir.log");
2016-04-20 20:41:00 -07:00
logger.Start();
2016-04-11 21:33:55 -07:00
// First things first, take care of all of the arguments that this could have
bool noMD5 = false, noSHA1 = false, forceunpack = false, archivesAsFiles = false, old = false, superDat = false, bare = false, romba = false, enableGzip = false;
string name = "", desc = "", cat = "", version = "", author = "";
2016-04-11 21:33:55 -07:00
List<string> inputs = new List<string>();
foreach (string arg in args)
{
switch (arg)
{
2016-04-11 23:12:40 -07:00
case "-h":
case "-?":
case "--help":
2016-04-12 23:24:08 -07:00
Build.Help();
2016-04-20 20:41:00 -07:00
logger.Close();
2016-04-11 23:12:40 -07:00
return;
case "-b":
case "--bare":
2016-04-21 14:38:11 -07:00
bare = true;
break;
2016-04-11 21:33:55 -07:00
case "-f":
case "--files":
archivesAsFiles = true;
2016-04-11 21:33:55 -07:00
break;
case "-gz":
case "--gz-files":
enableGzip = true;
break;
case "-m":
case "--noMD5":
noMD5 = true;
break;
2016-04-11 21:33:55 -07:00
case "-o":
case "--old":
2016-04-20 20:41:00 -07:00
old = true;
2016-04-11 21:33:55 -07:00
break;
case "-ro":
case "--romba":
romba = true;
break;
case "-s":
case "--noSHA1":
noSHA1 = true;
break;
case "-sd":
case "--superdat":
2016-04-20 20:41:00 -07:00
superDat = true;
break;
case "-u":
case "--unzip":
forceunpack = true;
break;
2016-04-11 21:33:55 -07:00
default:
if (arg.StartsWith("-n=") || arg.StartsWith("--name="))
{
2016-04-20 20:41:00 -07:00
name = arg.Split('=')[1];
2016-04-11 21:33:55 -07:00
}
else if (arg.StartsWith("-d=") || arg.StartsWith("--desc="))
{
2016-04-20 20:41:00 -07:00
desc = arg.Split('=')[1];
2016-04-11 21:33:55 -07:00
}
else if (arg.StartsWith("-c=") || arg.StartsWith("--cat="))
{
2016-04-20 20:41:00 -07:00
cat = arg.Split('=')[1];
2016-04-11 21:33:55 -07:00
}
2016-04-21 13:32:35 -07:00
else if (arg.StartsWith("-au=") || arg.StartsWith("--author="))
2016-04-12 11:58:10 -07:00
{
2016-04-20 20:41:00 -07:00
author = arg.Split('=')[1];
2016-04-12 11:58:10 -07:00
}
else if (arg.StartsWith("-v=") || arg.StartsWith("--version="))
{
2016-04-20 20:41:00 -07:00
version = arg.Split('=')[1];
2016-04-12 11:58:10 -07:00
}
2016-04-11 21:33:55 -07:00
else
{
inputs.Add(arg);
}
break;
}
}
2016-04-11 23:12:40 -07:00
// If there's no inputs, show the help
if (inputs.Count == 0)
{
2016-04-12 23:24:08 -07:00
Build.Help();
2016-04-20 20:41:00 -07:00
logger.Close();
2016-04-11 23:12:40 -07:00
return;
}
2016-04-11 21:33:55 -07:00
2016-04-18 20:04:38 -07:00
// Output the title
Build.Start("DATFromDir");
2016-04-13 15:00:22 -07:00
// If any of the inputs are not valid, show the help
foreach (string input in inputs)
{
if (!File.Exists(input) && !Directory.Exists(input))
{
2016-04-20 20:41:00 -07:00
logger.Error(input + " is not a valid input!");
2016-04-13 15:00:22 -07:00
Console.WriteLine();
Build.Help();
return;
}
}
2016-04-20 20:41:00 -07:00
// Create a new DATFromDir object and process the inputs
DatData datdata = new DatData
{
Name = name,
Description = desc,
Category = cat,
Version = version,
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Author = author,
ForcePacking = (forceunpack ? ForcePacking.Unzip : ForcePacking.None),
OutputFormat = (old ? OutputFormat.ClrMamePro : OutputFormat.Xml),
Romba = romba,
Type = (superDat ? "SuperDAT" : ""),
Roms = new Dictionary<string, List<RomData>>(),
};
DATFromDir dfd = new DATFromDir(inputs, datdata, noMD5, noSHA1, bare, archivesAsFiles, enableGzip, logger);
2016-04-20 20:41:00 -07:00
bool success = dfd.Start();
// If we failed, show the help
if (!success)
{
Console.WriteLine();
Build.Help();
}
logger.Close();
}
/// <summary>
/// Process the file, folder, or list of some combination into a DAT file
/// </summary>
/// <returns>True if the DAT could be created, false otherwise</returns>
/// <remarks>Try to get the hashing multithreaded (either on a per-hash or per-file level)</remarks>
2016-04-20 20:41:00 -07:00
public bool Start()
{
// Create an output dictionary for all found items
_datdata.Roms = new Dictionary<string, List<RomData>>();
// Loop over each of the found paths, if any
foreach (string path in _inputs)
{
// Set local paths and vars
_basePath = (File.Exists(path) ? path : path + Path.DirectorySeparatorChar);
_basePath = Path.GetFullPath(_basePath);
// This is where the main loop would go
if (File.Exists(_basePath))
{
ProcessFile(_basePath);
}
else if (Directory.Exists(_basePath))
{
_logger.Log("Folder found: " + _basePath);
// Process the files in the base folder first
foreach (string item in Directory.EnumerateFiles(_basePath, "*", SearchOption.TopDirectoryOnly))
2016-04-11 23:12:40 -07:00
{
ProcessFile(item);
}
2016-05-12 11:23:11 -07:00
// Then process each of the subfolders themselves
string basePathBackup = _basePath;
foreach (string item in Directory.EnumerateDirectories(_basePath))
{
if (_datdata.Type != "SuperDAT")
{
_basePath = (File.Exists(item) ? item : item + Path.DirectorySeparatorChar);
_basePath = Path.GetFullPath(_basePath);
}
bool items = false;
foreach (string subitem in Directory.EnumerateFiles(item, "*", SearchOption.AllDirectories))
2016-05-12 11:23:11 -07:00
{
items = true;
ProcessFile(subitem);
2016-05-12 11:23:11 -07:00
}
// If there were no subitems, add a "blank" game to to the set
if (!items)
{
RomData rom = new RomData
{
Name = "null",
Game = item.Remove(0, basePathBackup.Length),
Size = -1,
CRC = "null",
MD5 = "null",
SHA1 = "null",
};
string key = rom.Size + "-" + rom.CRC;
if (_datdata.Roms.ContainsKey(key))
{
_datdata.Roms[key].Add(rom);
}
else
{
List<RomData> temp = new List<RomData>();
temp.Add(rom);
_datdata.Roms.Add(key, temp);
}
}
// Now scour subdirectories for empties and add those as well
foreach (string subdir in Directory.EnumerateDirectories(item, "*", SearchOption.AllDirectories))
{
if (Directory.EnumerateFiles(subdir, "*", SearchOption.AllDirectories).Count() == 0)
{
RomData rom = new RomData
{
Name = "null",
Game = subdir.Remove(0, basePathBackup.Length),
Size = -1,
CRC = "null",
MD5 = "null",
SHA1 = "null",
};
string key = rom.Size + "-" + rom.CRC;
if (_datdata.Roms.ContainsKey(key))
{
_datdata.Roms[key].Add(rom);
}
else
{
List<RomData> temp = new List<RomData>();
temp.Add(rom);
_datdata.Roms.Add(key, temp);
}
}
}
2016-04-11 23:12:40 -07:00
}
_basePath = basePathBackup;
}
// If this somehow skips past the original sensors
else
{
_logger.Error(path + " is not a valid input!");
2016-04-13 14:32:36 -07:00
}
}
2016-04-20 20:41:00 -07:00
// If we found nothing (error state), exit
if (_datdata.Roms.Count == 0)
2016-04-13 14:45:00 -07:00
{
2016-04-20 20:41:00 -07:00
return false;
2016-04-13 14:45:00 -07:00
}
2016-04-12 00:15:16 -07:00
// Double check to see what it needs to be named
if (_datdata.Name == "")
2016-04-13 00:44:55 -07:00
{
2016-04-20 20:41:00 -07:00
if (_inputs.Count > 1)
2016-04-13 00:44:55 -07:00
{
_datdata.Name = Environment.CurrentDirectory.Split(Path.DirectorySeparatorChar).Last();
2016-04-13 00:44:55 -07:00
}
else
{
if (_basePath.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
_basePath = _basePath.Substring(0, _basePath.Length - 1);
}
_datdata.Name = _basePath.Split(Path.DirectorySeparatorChar).Last();
2016-04-13 00:44:55 -07:00
}
}
_datdata.Name = (_datdata.Name == "" ? "Default" : _datdata.Name);
// If we're in a superdat, append the folder name to all game names
if (_datdata.Type == "SuperDAT")
{
List<string> keys = _datdata.Roms.Keys.ToList();
foreach (string key in keys)
{
List<RomData> newroms = new List<RomData>();
foreach (RomData rom in _datdata.Roms[key])
{
RomData newrom = rom;
newrom.Game = _datdata.Name + (newrom.Game != "" &&
!newrom.Game.StartsWith(Path.DirectorySeparatorChar.ToString()) ? Path.DirectorySeparatorChar.ToString() : "") + newrom.Game;
newroms.Add(newrom);
}
_datdata.Roms[key] = newroms;
}
_datdata.Name = _datdata.Name += " - SuperDAT";
}
_datdata.Description = (_datdata.Description == "" ? _datdata.Name + (_bare ? "" : " (" + _datdata.Date + ")") : _datdata.Description);
// Now write it all out as a DAT
Output.WriteDatfile(_datdata, Environment.CurrentDirectory, _logger);
2016-04-20 20:41:00 -07:00
return true;
2016-04-11 23:12:40 -07:00
}
2016-04-11 23:49:06 -07:00
/// <summary>
/// Check a given file for hashes, based on current settings
/// </summary>
/// <param name="item">Filename of the item to be checked</param>
private void ProcessFile(string item)
{
// Special case for if we are in Romba mode (all names are supposed to be SHA-1 hashes)
if (_datdata.Romba)
{
string datum = Path.GetFileNameWithoutExtension(item).ToLowerInvariant();
// Check if the name is the right length
if (!Regex.IsMatch(datum, @"^[0-9a-f]{40}"))
{
_logger.Warning("Non SHA-1 filename found, skipping: '" + datum + "'");
return;
}
RomData rom = new RomData
{
Type = "rom",
Game = datum,
Name = datum,
2016-05-22 21:14:44 -07:00
Size = (new FileInfo(item)).Length,
SHA1 = Path.GetFileNameWithoutExtension(item),
};
string key = datum;
if (_datdata.Roms.ContainsKey(key))
{
_datdata.Roms[key].Add(rom);
}
else
{
List<RomData> temp = new List<RomData>();
temp.Add(rom);
_datdata.Roms.Add(key, temp);
}
2016-05-22 23:33:33 -07:00
_logger.Log("File added: " + Path.GetFileNameWithoutExtension(item) + Environment.NewLine);
return;
}
2016-05-17 16:53:02 -07:00
// Create the temporary output directory
2016-04-11 23:12:40 -07:00
bool encounteredErrors = true;
2016-04-20 20:41:00 -07:00
if (!_archivesAsFiles)
2016-04-11 23:12:40 -07:00
{
2016-05-22 23:33:33 -07:00
IArchive archive = null;
2016-04-12 23:07:23 -07:00
try
{
2016-05-22 23:33:33 -07:00
archive = ArchiveFactory.Open(item);
ArchiveType at = archive.Type;
_logger.Log("Found archive of type: " + at);
2016-05-22 23:33:33 -07:00
if (at == ArchiveType.Zip || at == ArchiveType.SevenZip || at == ArchiveType.Rar)
{
2016-05-17 16:53:02 -07:00
_tempDir = Environment.CurrentDirectory + Path.DirectorySeparatorChar + "temp" + DateTime.Now.ToString("yyyyMMddHHmmss") + Path.DirectorySeparatorChar;
DirectoryInfo di = Directory.CreateDirectory(_tempDir);
IReader reader = archive.ExtractAllEntries();
reader.WriteAllToDirectory(_tempDir, ExtractOptions.ExtractFullPath);
encounteredErrors = false;
}
2016-05-22 23:33:33 -07:00
else if (at == ArchiveType.GZip && _enableGzip)
{
archive.Dispose();
_tempDir = Environment.CurrentDirectory + Path.DirectorySeparatorChar + "temp" + DateTime.Now.ToString("yyyyMMddHHmmss") + Path.DirectorySeparatorChar;
DirectoryInfo di = Directory.CreateDirectory(_tempDir);
using (FileStream itemstream = File.OpenRead(item))
{
using (FileStream outstream = File.Create(_tempDir + Path.GetFileNameWithoutExtension(item)))
{
using (GZipStream gz = new GZipStream(itemstream, CompressionMode.Decompress))
{
gz.CopyTo(outstream);
}
}
}
encounteredErrors = false;
}
archive.Dispose();
2016-04-12 23:07:23 -07:00
}
catch (InvalidOperationException)
{
encounteredErrors = true;
if (archive != null)
{
archive.Dispose();
}
2016-04-12 23:07:23 -07:00
}
2016-04-12 23:39:22 -07:00
catch (Exception ex)
{
_logger.Error(ex.ToString());
encounteredErrors = true;
if (archive != null)
{
archive.Dispose();
}
2016-04-12 23:39:22 -07:00
}
2016-04-11 23:12:40 -07:00
}
// Get a list of files including size and hashes
Crc32 crc = new Crc32();
MD5 md5 = MD5.Create();
SHA1 sha1 = SHA1.Create();
// If the file was an archive and was extracted successfully, check it
if (!encounteredErrors)
{
2016-04-12 23:39:22 -07:00
_logger.Log(Path.GetFileName(item) + " treated like an archive");
2016-04-12 12:04:14 -07:00
foreach (string entry in Directory.EnumerateFiles(_tempDir, "*", SearchOption.AllDirectories))
{
_logger.Log("Found file: " + entry);
string fileCRC = String.Empty;
string fileMD5 = String.Empty;
string fileSHA1 = String.Empty;
try
{
using (FileStream fs = File.Open(entry, FileMode.Open))
{
foreach (byte b in crc.ComputeHash(fs))
{
fileCRC += b.ToString("x2").ToLower();
}
}
2016-04-11 23:12:40 -07:00
if (!_noMD5)
{
2016-04-11 23:12:40 -07:00
using (FileStream fs = File.Open(entry, FileMode.Open))
{
fileMD5 = BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "");
}
}
2016-04-11 23:12:40 -07:00
if (!_noSHA1)
{
2016-04-11 23:12:40 -07:00
using (FileStream fs = File.Open(entry, FileMode.Open))
{
fileSHA1 = BitConverter.ToString(sha1.ComputeHash(fs)).Replace("-", "");
}
}
}
catch (IOException)
{
continue;
}
string actualroot = "";
string actualitem = "";
actualitem = entry.Remove(0, _tempDir.Length);
// If we're in SuperDAT mode, make sure the added item is by itself
if (_datdata.Type == "SuperDAT")
{
actualroot = Path.GetDirectoryName(item.Remove(0, _basePath.Length));
actualroot = (actualroot == "" ? _basePath.Split(Path.DirectorySeparatorChar).Last() : actualroot);
actualroot += Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(item) + Path.DirectorySeparatorChar + Path.GetDirectoryName(actualitem);
actualroot = actualroot.TrimEnd(Path.DirectorySeparatorChar);
actualitem = Path.GetFileName(actualitem);
}
// Otherwise, set the correct root and such
else
{
actualroot = Path.GetFileNameWithoutExtension(item);
actualroot = actualroot.TrimEnd(Path.DirectorySeparatorChar);
}
RomData rom = new RomData
2016-04-11 23:59:32 -07:00
{
2016-04-18 14:05:34 -07:00
Type = "rom",
Game = actualroot,
Name = actualitem,
2016-04-11 23:59:32 -07:00
Size = (new FileInfo(entry)).Length,
CRC = fileCRC,
MD5 = fileMD5,
SHA1 = fileSHA1,
};
string key = rom.Size + "-" + rom.CRC;
if (_datdata.Roms.ContainsKey(key))
{
_datdata.Roms[key].Add(rom);
}
else
{
List<RomData> temp = new List<RomData>();
temp.Add(rom);
_datdata.Roms.Add(key, temp);
}
_logger.User("File added: " + entry + Environment.NewLine);
}
2016-05-17 16:53:02 -07:00
// Delete the temp directory
if (Directory.Exists(_tempDir))
{
Directory.Delete(_tempDir, true);
}
}
// Otherwise, just get the info on the file itself
else if (!Directory.Exists(item) && File.Exists(item))
{
2016-04-12 23:39:22 -07:00
_logger.Log(Path.GetFileName(item) + " treated like a file");
string fileCRC = String.Empty;
string fileMD5 = String.Empty;
string fileSHA1 = String.Empty;
try
{
using (FileStream fs = File.Open(item, FileMode.Open))
{
foreach (byte b in crc.ComputeHash(fs))
{
fileCRC += b.ToString("x2").ToLower();
}
}
2016-04-11 23:12:40 -07:00
if (!_noMD5)
{
2016-04-11 23:12:40 -07:00
using (FileStream fs = File.Open(item, FileMode.Open))
{
fileMD5 = BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "");
}
}
2016-04-11 23:12:40 -07:00
if (!_noSHA1)
{
2016-04-11 23:12:40 -07:00
using (FileStream fs = File.Open(item, FileMode.Open))
{
fileSHA1 = BitConverter.ToString(sha1.ComputeHash(fs)).Replace("-", "");
}
}
2016-04-13 00:44:55 -07:00
if (_basePath.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
_basePath = _basePath.Substring(0, _basePath.Length - 1);
}
string actualroot = (item == _basePath ? item.Split(Path.DirectorySeparatorChar).Last() : item.Remove(0, _basePath.Length).Split(Path.DirectorySeparatorChar)[0]);
actualroot = (actualroot == "" && _datdata.Type != "SuperDAT" ? _basePath.Split(Path.DirectorySeparatorChar).Last() : actualroot);
string actualitem = (item == _basePath ? item : item.Remove(0, _basePath.Length + 1));
// If we're in SuperDAT mode, make sure the added item is by itself
if (_datdata.Type == "SuperDAT")
{
actualroot += (actualroot != "" ? Path.DirectorySeparatorChar.ToString() : "") + Path.GetDirectoryName(actualitem);
actualroot = actualroot.TrimEnd(Path.DirectorySeparatorChar);
actualitem = Path.GetFileName(actualitem);
}
2016-04-12 00:18:55 -07:00
// Drag and drop is funny
if (actualitem == Path.GetFullPath(actualitem))
{
actualitem = Path.GetFileName(actualitem);
}
_logger.Log("Actual item added: " + actualitem);
RomData rom = new RomData
2016-04-11 23:59:32 -07:00
{
2016-04-18 14:05:34 -07:00
Type = "rom",
2016-04-11 23:59:32 -07:00
Game = actualroot,
Name = actualitem,
Size = (new FileInfo(item)).Length,
CRC = fileCRC,
MD5 = fileMD5,
SHA1 = fileSHA1,
};
string key = rom.Size + "-" + rom.CRC;
if (_datdata.Roms.ContainsKey(key))
{
_datdata.Roms[key].Add(rom);
}
else
{
List<RomData> temp = new List<RomData>();
temp.Add(rom);
_datdata.Roms.Add(key, temp);
}
_logger.User("File added: " + actualitem + Environment.NewLine);
}
catch (IOException ex)
{
_logger.Error(ex.ToString());
}
}
}
}
}