using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using SabreTools.Helper;
using DamienG.Security.Cryptography;
using SharpCompress.Archive;
using SharpCompress.Common;
using SharpCompress.Reader;
namespace SabreTools
{
///
/// Create a DAT file from a specified file, directory, or set thereof
///
public class DATFromDir
{
// Path-related variables
private static string _basePath;
private static string _tempDir;
// Extraction and listing related variables
private static List _roms;
// User specified variables
private static bool _noMD5;
private static bool _noSHA1;
private static bool _noDate;
private static bool _forceunzip;
private static bool _allfiles;
private static bool _old;
private static string _name;
private static string _desc;
private static string _cat;
private static string _version;
private static string _author;
// Other required variables
private static string _date = DateTime.Now.ToString("yyyy-MM-dd");
private static Logger _logger;
///
/// Start help or use supplied parameters
///
/// String array representing command line parameters
public static void Main(string[] args)
{
Console.Clear();
Console.Title = "DATFromDir " + Build.Version;
_logger = new Logger(false, "datfromdir.log");
_logger.Start();
Remapping.CreateArchiveFormats();
// First things first, take care of all of the arguments that this could have
_noMD5 = false; _noSHA1 = false; _forceunzip = false; _allfiles = false; _old = false;
_name = ""; _desc = ""; _cat = ""; _version = ""; _author = "";
List inputs = new List();
foreach (string arg in args)
{
switch (arg)
{
case "-h":
case "-?":
case "--help":
Build.Help();
_logger.Close();
return;
case "-m":
case "--noMD5":
_noMD5 = true;
break;
case "-s":
case "--noSHA1":
_noSHA1 = true;
break;
case "-b":
case "--bare":
_noDate = true;
break;
case "-u":
case "--unzip":
_forceunzip = true;
break;
case "-f":
case "--files":
_allfiles = true;
break;
case "-o":
case "--old":
_old = true;
break;
default:
if (arg.StartsWith("-n=") || arg.StartsWith("--name="))
{
_name = arg.Split('=')[1];
}
else if (arg.StartsWith("-d=") || arg.StartsWith("--desc="))
{
_desc = arg.Split('=')[1];
}
else if (arg.StartsWith("-c=") || arg.StartsWith("--cat="))
{
_cat = arg.Split('=')[1];
}
else if (arg.StartsWith("-a=") || arg.StartsWith("--author="))
{
_author = arg.Split('=')[1];
}
else if (arg.StartsWith("-v=") || arg.StartsWith("--version="))
{
_version = arg.Split('=')[1];
}
else
{
inputs.Add(arg);
}
break;
}
}
// If there's no inputs, show the help
if (inputs.Count == 0)
{
Build.Help();
_logger.Close();
return;
}
// Create an output array for all found items
_roms = new List();
// Loop over each of the found paths, if any
foreach (string path in inputs)
{
// Set local paths and vars
_tempDir = Environment.CurrentDirectory + Path.DirectorySeparatorChar + "temp" + DateTime.Now.ToString("yyyyMMddHHmmss") + Path.DirectorySeparatorChar;
_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
{
_logger.Log("Folder found: " + _basePath);
foreach (string item in Directory.EnumerateFiles(_basePath, "*", SearchOption.AllDirectories))
{
ProcessFile(item);
}
}
}
// Order the roms by name of parent, then name of rom
_roms.Sort(delegate (RomData A, RomData B)
{
if (A.Game == B.Game)
{
if (A.Name == B.Name)
{
return (int)(A.Size - B.Size);
}
return String.Compare(A.Name, B.Name);
}
return String.Compare(A.Game, B.Game);
});
//TODO: So, this below section is a pretty much one for one copy of code that is written in generate
// this means that in the future, "writing to DAT" will be abstracted out to the DLL so that any
// properly formatted data can be passed in and it will get written as necessary. That would open
// the possibiliites for different ways to generate a DAT from multiple things
// Double check to see what it needs to be named
if (_name == "")
{
if (inputs.Count > 1)
{
_name = Environment.CurrentDirectory.Split(Path.DirectorySeparatorChar).Last();
Console.WriteLine(_name);
}
else
{
if (_basePath.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
_basePath = _basePath.Substring(0, _basePath.Length - 1);
}
_name = _basePath.Split(Path.DirectorySeparatorChar).Last();
}
}
_name = (_name == "" ? "Default" : _name);
_desc = (_desc == "" ? _name + (_noDate ? "" : " (" + _date + ")") : _desc);
// Now write it all out as a DAT
try
{
FileStream fs = File.Create(_desc + ".xml");
StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
string header_old = "clrmamepro (\n" +
"\tname \"" + _name + "\"\n" +
"\tdescription \"" + _desc + "\"\n" +
"\tversion \"" + _version + "\"\n" +
"\tcomment \"\"\n" +
"\tauthor \"" + _author + "\"\n" +
(_forceunzip ? "\tforcezipping no\n" : "") +
")\n";
string header = "\n" +
"\n\n" +
"\t\n" +
"\t\t\n" +
"\t\t\t" + HttpUtility.HtmlEncode(_name) + "\n" +
"\t\t\t" + HttpUtility.HtmlEncode(_desc) + "\n" +
"\t\t\t" + HttpUtility.HtmlEncode(_cat) + "\n" +
"\t\t\t" + HttpUtility.HtmlEncode(_version) + "\n" +
"\t\t\t" + _date + "\n" +
"\t\t\t" + HttpUtility.HtmlEncode(_author) + "\n" +
(_forceunzip ? "\t\t\t\n" : "") +
"\t\t\n";
// Write the header out
sw.Write((_old ? header_old : header));
// Write out each of the machines and roms
string lastgame = "";
foreach (RomData rom in _roms)
{
string state = "";
if (lastgame != "" && lastgame != rom.Game)
{
state += (_old ? ")\n" : "\t\n");
}
if (lastgame != rom.Game)
{
state += (_old ? "game (\n\tname \"" + rom.Game + "\"\n" +
"\tdescription \"" + rom.Game + "\"\n" :
"\t\n" +
"\t\t" + HttpUtility.HtmlEncode(rom.Game) + "\n");
}
if (_old)
{
state += "\trom ( name \"" + rom.Name + "\"" +
(rom.Size != 0 ? " size " + rom.Size : "") +
(rom.CRC != "" ? " crc " + rom.CRC.ToLowerInvariant() : "") +
(rom.MD5 != "" ? " md5 " + rom.MD5.ToLowerInvariant() : "") +
(rom.SHA1 != "" ? " sha1 " + rom.SHA1.ToLowerInvariant() : "") +
" )\n";
}
else
{
state += "\t\t\n";
}
lastgame = rom.Game;
sw.Write(state);
}
sw.Write((_old ? ")" : "\t\n"));
_logger.Log("File written!" + Environment.NewLine);
sw.Close();
fs.Close();
}
catch (Exception ex)
{
_logger.Error(ex.ToString());
}
_logger.Close();
}
///
/// Check a given file for hashes, based on current settings
///
/// Filename of the item to be checked
private static void ProcessFile (string item)
{
// Create the temporary output directory
DirectoryInfo di = Directory.CreateDirectory(_tempDir);
bool encounteredErrors = true;
if (!_allfiles && Remapping.ArchiveFormats.Contains(Path.GetExtension(item).ToLowerInvariant()))
{
try
{
IArchive archive = ArchiveFactory.Open(item);
_logger.Log("Found archive of type: " + archive.Type);
IReader reader = archive.ExtractAllEntries();
reader.WriteAllToDirectory(_tempDir, ExtractOptions.ExtractFullPath);
encounteredErrors = false;
}
catch (InvalidOperationException)
{
encounteredErrors = true;
}
catch (Exception ex)
{
_logger.Error(ex.ToString());
encounteredErrors = true;
}
}
// 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)
{
_logger.Log(Path.GetFileName(item) + " treated like an archive");
foreach (string entry in Directory.EnumerateFiles(_tempDir, "*", SearchOption.AllDirectories))
{
_logger.Log("\tFound 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();
}
}
if (!_noMD5)
{
using (FileStream fs = File.Open(entry, FileMode.Open))
{
fileMD5 = BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "");
}
}
if (!_noSHA1)
{
using (FileStream fs = File.Open(entry, FileMode.Open))
{
fileSHA1 = BitConverter.ToString(sha1.ComputeHash(fs)).Replace("-", "");
}
}
}
catch (IOException)
{
continue;
}
_roms.Add(new RomData
{
Game = Path.GetFileNameWithoutExtension(item),
Name = entry.Remove(0, _tempDir.Length),
Size = (new FileInfo(entry)).Length,
CRC = fileCRC,
MD5 = fileMD5,
SHA1 = fileSHA1,
});
_logger.Log("File added" + Environment.NewLine);
}
}
// Otherwise, just get the info on the file itself
else if (!Directory.Exists(item) && File.Exists(item))
{
_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();
}
}
if (!_noMD5)
{
using (FileStream fs = File.Open(item, FileMode.Open))
{
fileMD5 = BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "");
}
}
if (!_noSHA1)
{
using (FileStream fs = File.Open(item, FileMode.Open))
{
fileSHA1 = BitConverter.ToString(sha1.ComputeHash(fs)).Replace("-", "");
}
}
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 == "" ? _basePath.Split(Path.DirectorySeparatorChar).Last() : actualroot);
string actualitem = (item == _basePath ? item : item.Remove(0, _basePath.Length + 1)); //.Remove(0, actualroot.Length)
// Drag and drop is funny
if (actualitem == Path.GetFullPath(actualitem))
{
actualitem = Path.GetFileName(actualitem);
}
_roms.Add(new RomData
{
Game = actualroot,
Name = actualitem,
Size = (new FileInfo(item)).Length,
CRC = fileCRC,
MD5 = fileMD5,
SHA1 = fileSHA1,
});
_logger.Log("File added" + Environment.NewLine);
}
catch (IOException) { }
}
// Delete the temp directory
if (Directory.Exists(_tempDir))
{
di.Delete(true);
}
}
}
}