2016-06-13 23:54:13 -07:00
|
|
|
|
using SabreTools.Helper;
|
2016-06-21 10:59:37 -07:00
|
|
|
|
using SharpCompress.Common;
|
2016-06-13 23:54:13 -07:00
|
|
|
|
using System;
|
2016-04-11 15:09:50 -07:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
2016-06-13 23:54:13 -07:00
|
|
|
|
using System.Linq;
|
2016-05-23 15:57:09 -07:00
|
|
|
|
using System.Text;
|
2016-04-11 17:03:00 -07:00
|
|
|
|
|
2016-04-11 15:09:50 -07:00
|
|
|
|
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 15:09:50 -07:00
|
|
|
|
{
|
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
|
|
|
|
|
2016-05-22 12:45:20 -07:00
|
|
|
|
// User specified inputs
|
2016-04-20 20:41:00 -07:00
|
|
|
|
private List<String> _inputs;
|
2016-06-16 18:57:34 -07:00
|
|
|
|
private Dat _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;
|
2016-05-22 17:38:17 -07:00
|
|
|
|
private bool _enableGzip;
|
2016-09-14 10:25:01 -07:00
|
|
|
|
private bool _addBlanks;
|
|
|
|
|
|
private bool _addDate;
|
2016-08-24 21:19:05 -07:00
|
|
|
|
private bool _nowrite;
|
2016-04-11 21:33:55 -07:00
|
|
|
|
|
|
|
|
|
|
// Other required variables
|
2016-04-20 20:41:00 -07:00
|
|
|
|
private Logger _logger;
|
|
|
|
|
|
|
2016-08-24 21:19:05 -07:00
|
|
|
|
// Public variables
|
|
|
|
|
|
public Dat DatData
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _datdata; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-20 20:41:00 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Create a new DATFromDir object
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="inputs">A List of Strings representing the files and folders to be DATted</param>
|
2016-05-22 12:45:20 -07:00
|
|
|
|
/// <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>
|
2016-05-22 12:45:20 -07:00
|
|
|
|
/// <param name="archivesAsFiles">True if archives should be treated as files, false otherwise</param>
|
2016-08-24 21:19:05 -07:00
|
|
|
|
/// <param name="enableGzip">True if GZIP archives should be treated as files, false otherwise</param>
|
2016-09-14 10:25:01 -07:00
|
|
|
|
/// <param name="addBlanks">True if blank items should be created for empty folders, false otherwise</param>
|
|
|
|
|
|
/// <param name="addDate">True if dates should be archived for all files, false otherwise</param>
|
2016-05-28 15:54:06 -07:00
|
|
|
|
/// <param name="tempDir">Name of the directory to create a temp folder in (blank is current directory)</param>
|
2016-08-24 21:19:05 -07:00
|
|
|
|
/// <param name="nowrite">True if the file should not be written out, false otherwise (default)</param>
|
|
|
|
|
|
/// <param name="logger">Logger object for console and file output</param>
|
2016-09-14 10:25:01 -07:00
|
|
|
|
public DATFromDir(List<String> inputs, Dat datdata, bool noMD5, bool noSHA1, bool bare, bool archivesAsFiles,
|
|
|
|
|
|
bool enableGzip, bool addBlanks, bool addDate, string tempDir, Logger logger, bool nowrite = false)
|
2016-04-20 20:41:00 -07:00
|
|
|
|
{
|
|
|
|
|
|
_inputs = inputs;
|
2016-05-22 12:45:20 -07:00
|
|
|
|
_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;
|
2016-05-22 17:38:17 -07:00
|
|
|
|
_enableGzip = enableGzip;
|
2016-05-28 15:54:06 -07:00
|
|
|
|
_tempDir = tempDir;
|
2016-04-20 20:41:00 -07:00
|
|
|
|
_logger = logger;
|
2016-09-14 10:25:01 -07:00
|
|
|
|
_addBlanks = addBlanks;
|
|
|
|
|
|
_addDate = addDate;
|
2016-08-24 21:19:05 -07:00
|
|
|
|
_nowrite = nowrite;
|
2016-04-20 20:41:00 -07:00
|
|
|
|
}
|
2016-04-11 21:33:55 -07:00
|
|
|
|
|
2016-04-20 20:41:00 -07:00
|
|
|
|
/// <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>
|
2016-05-22 20:42:04 -07:00
|
|
|
|
/// <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()
|
|
|
|
|
|
{
|
2016-05-23 15:57:09 -07:00
|
|
|
|
// Double check to see what it needs to be named
|
2016-08-29 16:33:07 -07:00
|
|
|
|
_basePath = (_inputs.Count > 0 ? (File.Exists(_inputs[0]) ? _inputs[0] : _inputs[0] + Path.DirectorySeparatorChar) : "");
|
2016-05-23 15:57:09 -07:00
|
|
|
|
_basePath = (_basePath != "" ? Path.GetFullPath(_basePath) : "");
|
2016-05-25 11:08:20 -07:00
|
|
|
|
|
|
|
|
|
|
// If the description is defined but not the name, set the name from the description
|
|
|
|
|
|
if (String.IsNullOrEmpty(_datdata.Name) && !String.IsNullOrEmpty(_datdata.Description))
|
|
|
|
|
|
{
|
|
|
|
|
|
_datdata.Name = _datdata.Description;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If the name is defined but not the description, set the description from the name
|
|
|
|
|
|
else if (!String.IsNullOrEmpty(_datdata.Name) && String.IsNullOrEmpty(_datdata.Description))
|
|
|
|
|
|
{
|
|
|
|
|
|
_datdata.Description = _datdata.Name + (_bare ? "" : " (" + _datdata.Date + ")");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If neither the name or description are defined, set them from the automatic values
|
|
|
|
|
|
else if (String.IsNullOrEmpty(_datdata.Name) && String.IsNullOrEmpty(_datdata.Description))
|
2016-05-23 15:57:09 -07:00
|
|
|
|
{
|
|
|
|
|
|
if (_inputs.Count > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
_datdata.Name = Environment.CurrentDirectory.Split(Path.DirectorySeparatorChar).Last();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_basePath.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
|
|
|
|
|
{
|
|
|
|
|
|
_basePath = _basePath.Substring(0, _basePath.Length - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
_datdata.Name = _basePath.Split(Path.DirectorySeparatorChar).Last();
|
|
|
|
|
|
}
|
2016-05-25 11:08:20 -07:00
|
|
|
|
|
|
|
|
|
|
// If the name is still somehow empty, populate it with defaults
|
|
|
|
|
|
_datdata.Name = (String.IsNullOrEmpty(_datdata.Name) ? "Default" : _datdata.Name);
|
|
|
|
|
|
_datdata.Description = _datdata.Name + (_bare ? "" : " (" + _datdata.Date + ")");
|
2016-05-23 15:57:09 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-08-24 21:19:05 -07:00
|
|
|
|
StreamWriter sw;
|
|
|
|
|
|
if (_nowrite)
|
|
|
|
|
|
{
|
|
|
|
|
|
sw = new StreamWriter(new MemoryStream());
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Create and open the output file for writing
|
2016-09-09 15:51:47 -07:00
|
|
|
|
FileStream fs = File.Create(Style.CreateOutfileNames(Environment.CurrentDirectory, _datdata)[_datdata.OutputFormat]);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
sw = new StreamWriter(fs, Encoding.UTF8);
|
|
|
|
|
|
sw.AutoFlush = true;
|
|
|
|
|
|
}
|
2016-05-23 15:57:09 -07:00
|
|
|
|
|
|
|
|
|
|
// Write out the initial file header
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteHeader(sw, _datdata.OutputFormat, _datdata, _logger);
|
2016-04-11 15:09:50 -07:00
|
|
|
|
|
2016-05-17 10:31:57 -07:00
|
|
|
|
// Loop over each of the found paths, if any
|
2016-05-23 15:57:09 -07:00
|
|
|
|
string lastparent = null;
|
2016-05-17 10:31:57 -07:00
|
|
|
|
foreach (string path in _inputs)
|
2016-04-17 19:26:19 -07:00
|
|
|
|
{
|
2016-05-17 10:31:57 -07:00
|
|
|
|
// Set local paths and vars
|
2016-08-29 16:33:07 -07:00
|
|
|
|
_basePath = (File.Exists(path) ? path : path + Path.DirectorySeparatorChar);
|
2016-05-17 10:31:57 -07:00
|
|
|
|
_basePath = Path.GetFullPath(_basePath);
|
2016-04-17 19:26:19 -07:00
|
|
|
|
|
2016-05-17 10:31:57 -07:00
|
|
|
|
// This is where the main loop would go
|
2016-08-29 16:33:07 -07:00
|
|
|
|
if (File.Exists(_basePath))
|
2016-05-17 10:31:57 -07:00
|
|
|
|
{
|
2016-06-13 22:12:00 -07:00
|
|
|
|
lastparent = ProcessPossibleArchive(_basePath, sw, lastparent);
|
2016-05-17 10:31:57 -07:00
|
|
|
|
}
|
|
|
|
|
|
else if (Directory.Exists(_basePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.Log("Folder found: " + _basePath);
|
2016-04-17 19:26:19 -07:00
|
|
|
|
|
2016-05-17 10:31:57 -07:00
|
|
|
|
// 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
|
|
|
|
{
|
2016-06-13 22:12:00 -07:00
|
|
|
|
lastparent = ProcessPossibleArchive(item, sw, lastparent);
|
2016-04-17 19:26:19 -07:00
|
|
|
|
}
|
2016-05-12 11:23:11 -07:00
|
|
|
|
|
2016-05-17 10:31:57 -07:00
|
|
|
|
// Then process each of the subfolders themselves
|
|
|
|
|
|
string basePathBackup = _basePath;
|
|
|
|
|
|
foreach (string item in Directory.EnumerateDirectories(_basePath))
|
|
|
|
|
|
{
|
2016-05-22 12:45:20 -07:00
|
|
|
|
if (_datdata.Type != "SuperDAT")
|
2016-05-17 18:49:13 -07:00
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
_basePath = (File.Exists(item) ? item : item + Path.DirectorySeparatorChar);
|
2016-05-17 18:49:13 -07:00
|
|
|
|
_basePath = Path.GetFullPath(_basePath);
|
|
|
|
|
|
}
|
2016-05-19 10:28:53 -07:00
|
|
|
|
|
|
|
|
|
|
bool items = false;
|
2016-05-17 17:21:31 -07:00
|
|
|
|
foreach (string subitem in Directory.EnumerateFiles(item, "*", SearchOption.AllDirectories))
|
2016-05-12 11:23:11 -07:00
|
|
|
|
{
|
2016-05-19 10:28:53 -07:00
|
|
|
|
items = true;
|
2016-06-13 22:12:00 -07:00
|
|
|
|
lastparent = ProcessPossibleArchive(subitem, sw, lastparent);
|
2016-05-12 11:23:11 -07:00
|
|
|
|
}
|
2016-05-19 10:28:53 -07:00
|
|
|
|
|
2016-09-13 13:04:06 -07:00
|
|
|
|
// Now find all folders that are empty, if we are supposed to
|
2016-09-14 10:25:01 -07:00
|
|
|
|
if (!_datdata.Romba && _addBlanks)
|
2016-05-19 10:28:53 -07:00
|
|
|
|
{
|
2016-06-04 23:07:58 -07:00
|
|
|
|
// If there were no subitems, add a "blank" game to to the set (if not in Romba mode)
|
|
|
|
|
|
if (!items)
|
2016-05-19 10:28:53 -07:00
|
|
|
|
{
|
2016-06-04 23:07:58 -07:00
|
|
|
|
string actualroot = item.Remove(0, basePathBackup.Length);
|
2016-08-29 16:33:07 -07:00
|
|
|
|
Rom rom = new Rom
|
2016-05-19 10:28:53 -07:00
|
|
|
|
{
|
|
|
|
|
|
Name = "null",
|
2016-08-29 13:42:27 -07:00
|
|
|
|
Machine = new Machine
|
2016-08-29 13:41:42 -07:00
|
|
|
|
{
|
|
|
|
|
|
Name = (_datdata.Type == "SuperDAT" ?
|
|
|
|
|
|
(actualroot != "" && !actualroot.StartsWith(Path.DirectorySeparatorChar.ToString()) ?
|
|
|
|
|
|
Path.DirectorySeparatorChar.ToString() :
|
|
|
|
|
|
"") + actualroot :
|
|
|
|
|
|
actualroot),
|
|
|
|
|
|
},
|
2016-08-29 14:43:31 -07:00
|
|
|
|
HashData = new Hash
|
2016-08-29 13:05:32 -07:00
|
|
|
|
{
|
|
|
|
|
|
Size = -1,
|
|
|
|
|
|
CRC = "null",
|
|
|
|
|
|
MD5 = "null",
|
|
|
|
|
|
SHA1 = "null",
|
|
|
|
|
|
},
|
2016-05-19 10:28:53 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-08-29 13:05:32 -07:00
|
|
|
|
string key = rom.HashData.Size + "-" + rom.HashData.CRC;
|
2016-08-29 13:52:13 -07:00
|
|
|
|
if (_datdata.Files.ContainsKey(key))
|
2016-05-19 10:28:53 -07:00
|
|
|
|
{
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files[key].Add(rom);
|
2016-05-19 10:28:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
List<Rom> temp = new List<Rom>();
|
2016-05-19 10:28:53 -07:00
|
|
|
|
temp.Add(rom);
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files.Add(key, temp);
|
2016-05-19 10:28:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-06-04 23:07:58 -07:00
|
|
|
|
|
|
|
|
|
|
// Now scour subdirectories for empties and add those as well (if not in Romba mode)
|
|
|
|
|
|
foreach (string subdir in Directory.EnumerateDirectories(item, "*", SearchOption.AllDirectories))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Directory.EnumerateFiles(subdir, "*", SearchOption.AllDirectories).Count() == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
string actualroot = subdir.Remove(0, basePathBackup.Length);
|
2016-08-29 16:33:07 -07:00
|
|
|
|
Rom rom = new Rom
|
2016-06-04 23:07:58 -07:00
|
|
|
|
{
|
|
|
|
|
|
Name = "null",
|
2016-08-29 13:42:27 -07:00
|
|
|
|
Machine = new Machine
|
2016-08-29 13:41:42 -07:00
|
|
|
|
{
|
|
|
|
|
|
Name = (_datdata.Type == "SuperDAT" ?
|
|
|
|
|
|
(actualroot != "" && !actualroot.StartsWith(Path.DirectorySeparatorChar.ToString()) ?
|
|
|
|
|
|
Path.DirectorySeparatorChar.ToString() :
|
|
|
|
|
|
"") + actualroot :
|
|
|
|
|
|
actualroot),
|
|
|
|
|
|
},
|
2016-08-29 14:43:31 -07:00
|
|
|
|
HashData = new Hash
|
2016-08-29 13:05:32 -07:00
|
|
|
|
{
|
|
|
|
|
|
Size = -1,
|
|
|
|
|
|
CRC = "null",
|
|
|
|
|
|
MD5 = "null",
|
|
|
|
|
|
SHA1 = "null",
|
|
|
|
|
|
},
|
2016-06-04 23:07:58 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-08-29 13:05:32 -07:00
|
|
|
|
string key = rom.HashData.Size + "-" + rom.HashData.CRC;
|
2016-08-29 13:52:13 -07:00
|
|
|
|
if (_datdata.Files.ContainsKey(key))
|
2016-06-04 23:07:58 -07:00
|
|
|
|
{
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files[key].Add(rom);
|
2016-06-04 23:07:58 -07:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
List<Rom> temp = new List<Rom>();
|
2016-06-04 23:07:58 -07:00
|
|
|
|
temp.Add(rom);
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files.Add(key, temp);
|
2016-06-04 23:07:58 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-05-19 10:28:53 -07:00
|
|
|
|
}
|
2016-04-11 23:12:40 -07:00
|
|
|
|
}
|
2016-05-17 10:31:57 -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-11 15:09:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-13 13:04:06 -07:00
|
|
|
|
// Now find all folders that are empty, if we are supposed to
|
2016-09-14 10:25:01 -07:00
|
|
|
|
if (!_datdata.Romba && _addBlanks)
|
2016-04-13 00:44:55 -07:00
|
|
|
|
{
|
2016-08-29 13:52:13 -07:00
|
|
|
|
List<string> keys = _datdata.Files.Keys.ToList();
|
2016-08-24 21:19:05 -07:00
|
|
|
|
foreach (string key in keys)
|
2016-04-13 00:44:55 -07:00
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
List<Rom> roms = _datdata.Files[key];
|
2016-06-01 11:02:52 -07:00
|
|
|
|
for (int i = 0; i < roms.Count; i++)
|
2016-04-13 00:44:55 -07:00
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
Rom rom = roms[i];
|
2016-05-17 10:31:57 -07:00
|
|
|
|
|
2016-06-01 11:02:52 -07:00
|
|
|
|
// If we're in a mode that doesn't allow for actual empty folders, add the blank info
|
2016-09-09 15:51:47 -07:00
|
|
|
|
if ((_datdata.OutputFormat & OutputFormat.SabreDat) == 0 && (_datdata.OutputFormat & OutputFormat.MissFile) == 0)
|
2016-06-01 11:02:52 -07:00
|
|
|
|
{
|
2016-08-29 13:50:55 -07:00
|
|
|
|
rom.Type = ItemType.Rom;
|
2016-06-01 11:02:52 -07:00
|
|
|
|
rom.Name = "-";
|
2016-08-29 13:05:32 -07:00
|
|
|
|
rom.HashData.Size = Constants.SizeZero;
|
|
|
|
|
|
rom.HashData.CRC = Constants.CRCZero;
|
|
|
|
|
|
rom.HashData.MD5 = Constants.MD5Zero;
|
|
|
|
|
|
rom.HashData.SHA1 = Constants.SHA1Zero;
|
2016-06-01 11:02:52 -07:00
|
|
|
|
}
|
2016-05-17 10:31:57 -07:00
|
|
|
|
|
2016-08-24 21:19:05 -07:00
|
|
|
|
if (_nowrite)
|
2016-06-01 11:02:52 -07:00
|
|
|
|
{
|
2016-08-29 13:05:32 -07:00
|
|
|
|
string inkey = rom.HashData.Size + "-" + rom.HashData.CRC;
|
2016-08-29 13:52:13 -07:00
|
|
|
|
if (_datdata.Files.ContainsKey(inkey))
|
2016-08-24 21:19:05 -07:00
|
|
|
|
{
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files[inkey].Add(rom);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
List<Rom> temp = new List<Rom>();
|
2016-08-24 21:19:05 -07:00
|
|
|
|
temp.Add(rom);
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files.Add(inkey, temp);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
2016-06-01 11:02:52 -07:00
|
|
|
|
}
|
2016-08-24 21:19:05 -07:00
|
|
|
|
else
|
2016-06-01 11:02:52 -07:00
|
|
|
|
{
|
2016-08-24 21:19:05 -07:00
|
|
|
|
// If we have a different game and we're not at the start of the list, output the end of last item
|
|
|
|
|
|
int last = 0;
|
2016-08-29 13:42:27 -07:00
|
|
|
|
if (lastparent != null && lastparent.ToLowerInvariant() != rom.Machine.Name.ToLowerInvariant())
|
2016-08-24 21:19:05 -07:00
|
|
|
|
{
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteEndGame(sw, _datdata.OutputFormat, rom, new List<string>(), new List<string>(), lastparent, 0, out last, _logger);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
2016-06-01 11:02:52 -07:00
|
|
|
|
|
2016-08-24 21:19:05 -07:00
|
|
|
|
// If we have a new game, output the beginning of the new item
|
2016-08-29 13:42:27 -07:00
|
|
|
|
if (lastparent == null || lastparent.ToLowerInvariant() != rom.Machine.Name.ToLowerInvariant())
|
2016-08-24 21:19:05 -07:00
|
|
|
|
{
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteStartGame(sw, _datdata.OutputFormat, rom, new List<string>(), lastparent, 0, last, _logger);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Write out the rom data
|
2016-09-09 15:51:47 -07:00
|
|
|
|
if ((_datdata.OutputFormat & OutputFormat.SabreDat) == 0 && (_datdata.OutputFormat & OutputFormat.MissFile) == 0)
|
2016-08-24 21:19:05 -07:00
|
|
|
|
{
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteRomData(sw, _datdata.OutputFormat, rom, lastparent, _datdata, 0, _logger);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
2016-06-01 11:02:52 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-08-29 13:42:27 -07:00
|
|
|
|
lastparent = rom.Machine.Name;
|
2016-05-23 15:57:09 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-05-16 21:52:49 -07:00
|
|
|
|
|
2016-06-01 11:02:52 -07:00
|
|
|
|
// If we had roms but not blanks (and not in Romba mode), create an artifical rom for the purposes of outputting
|
2016-08-29 13:52:13 -07:00
|
|
|
|
if (lastparent != null && _datdata.Files.Count == 0)
|
2016-06-01 11:02:52 -07:00
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
_datdata.Files.Add("temp", new List<Rom>());
|
2016-06-01 11:02:52 -07:00
|
|
|
|
}
|
2016-05-25 11:59:08 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-23 15:57:09 -07:00
|
|
|
|
// Now write the final piece and close the output stream
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteFooter(sw, _datdata.OutputFormat, _datdata, 0, _logger);
|
2016-05-23 15:57:09 -07:00
|
|
|
|
sw.Close();
|
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>
|
2016-05-23 15:57:09 -07:00
|
|
|
|
/// <param name="sw">StreamWriter representing the output file</param>
|
2016-05-25 11:45:34 -07:00
|
|
|
|
/// <param name="lastparent">Name of the last parent rom to make sure that everything is grouped as well as possible</param>
|
|
|
|
|
|
/// <returns>New parent to be used</returns>
|
2016-06-13 22:12:00 -07:00
|
|
|
|
private string ProcessPossibleArchive(string item, StreamWriter sw, string lastparent)
|
2016-04-11 20:41:04 -07:00
|
|
|
|
{
|
2016-05-31 23:34:19 -07:00
|
|
|
|
// Define the temporary directory
|
|
|
|
|
|
string tempdir = (String.IsNullOrEmpty(_tempDir) ? Environment.CurrentDirectory : _tempDir);
|
|
|
|
|
|
tempdir += (tempdir.EndsWith(Path.DirectorySeparatorChar.ToString()) ? "" : Path.DirectorySeparatorChar.ToString());
|
2016-06-20 23:51:58 -07:00
|
|
|
|
tempdir += "__temp__" + Path.DirectorySeparatorChar;
|
2016-05-28 15:54:06 -07:00
|
|
|
|
|
2016-05-22 17:41:52 -07:00
|
|
|
|
// Special case for if we are in Romba mode (all names are supposed to be SHA-1 hashes)
|
2016-05-22 17:38:17 -07:00
|
|
|
|
if (_datdata.Romba)
|
|
|
|
|
|
{
|
2016-08-29 16:52:55 -07:00
|
|
|
|
Rom rom = FileTools.GetTorrentGZFileInfo(item, _logger);
|
2016-05-22 17:38:17 -07:00
|
|
|
|
|
2016-06-13 22:12:00 -07:00
|
|
|
|
// If the rom is valid, write it out
|
|
|
|
|
|
if (rom.Name != null)
|
2016-05-22 17:41:52 -07:00
|
|
|
|
{
|
2016-06-13 22:12:00 -07:00
|
|
|
|
int last = 0;
|
2016-08-24 21:19:05 -07:00
|
|
|
|
|
|
|
|
|
|
if (_nowrite)
|
|
|
|
|
|
{
|
2016-08-29 13:05:32 -07:00
|
|
|
|
string key = rom.HashData.Size + "-" + rom.HashData.CRC;
|
2016-08-29 13:52:13 -07:00
|
|
|
|
if (_datdata.Files.ContainsKey(key))
|
2016-08-24 21:19:05 -07:00
|
|
|
|
{
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files[key].Add(rom);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
List<Rom> temp = new List<Rom>();
|
2016-08-24 21:19:05 -07:00
|
|
|
|
temp.Add(rom);
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files.Add(key, temp);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteStartGame(sw, _datdata.OutputFormat, rom, new List<string>(), "", 0, 0, _logger);
|
|
|
|
|
|
DatTools.WriteRomData(sw, _datdata.OutputFormat, rom, "", _datdata, 0, _logger);
|
|
|
|
|
|
DatTools.WriteEndGame(sw, _datdata.OutputFormat, rom, new List<string>(), new List<string>(), "", 0, out last, _logger);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
2016-05-31 23:34:19 -07:00
|
|
|
|
}
|
2016-06-13 22:12:00 -07:00
|
|
|
|
else
|
2016-05-31 23:34:19 -07:00
|
|
|
|
{
|
2016-06-13 22:12:00 -07:00
|
|
|
|
return string.Empty;
|
2016-05-31 23:34:19 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_logger.User("File added: " + Path.GetFileNameWithoutExtension(item) + Environment.NewLine);
|
2016-08-29 13:42:27 -07:00
|
|
|
|
return rom.Machine.Name;
|
2016-05-22 17:38:17 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-21 11:13:57 -07:00
|
|
|
|
// If both deep hash skip flags are set, do a quickscan
|
2016-06-21 10:59:37 -07:00
|
|
|
|
if (_noMD5 && _noSHA1)
|
|
|
|
|
|
{
|
2016-08-29 16:52:55 -07:00
|
|
|
|
ArchiveType? type = FileTools.GetCurrentArchiveType(item, _logger);
|
2016-06-21 10:59:37 -07:00
|
|
|
|
|
|
|
|
|
|
// If we have an archive, scan it
|
|
|
|
|
|
if (type != null)
|
|
|
|
|
|
{
|
2016-08-29 16:52:55 -07:00
|
|
|
|
List<Rom> extracted = FileTools.GetArchiveFileInfo(item, _logger);
|
2016-08-29 16:33:07 -07:00
|
|
|
|
foreach (Rom rom in extracted)
|
2016-06-21 10:59:37 -07:00
|
|
|
|
{
|
2016-06-21 11:08:13 -07:00
|
|
|
|
lastparent = ProcessFileHelper(item, rom, sw, _basePath,
|
|
|
|
|
|
Path.Combine((Path.GetDirectoryName(Path.GetFullPath(item)) + Path.DirectorySeparatorChar).Remove(0, _basePath.Length) +
|
|
|
|
|
|
Path.GetFileNameWithoutExtension(item)
|
|
|
|
|
|
), _datdata, lastparent);
|
2016-06-21 10:59:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Otherwise, just get the info on the file itself
|
2016-08-29 16:33:07 -07:00
|
|
|
|
else if (!Directory.Exists(item) && File.Exists(item))
|
2016-06-21 10:59:37 -07:00
|
|
|
|
{
|
|
|
|
|
|
lastparent = ProcessFile(item, sw, _basePath, "", _datdata, lastparent);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-06-21 11:08:13 -07:00
|
|
|
|
// Otherwise, attempt to extract the files to the temporary directory
|
2016-06-21 10:59:37 -07:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-08-29 16:52:55 -07:00
|
|
|
|
bool encounteredErrors = FileTools.ExtractArchive(item,
|
2016-06-13 22:12:00 -07:00
|
|
|
|
tempdir,
|
|
|
|
|
|
(_archivesAsFiles ? ArchiveScanLevel.External : ArchiveScanLevel.Internal),
|
|
|
|
|
|
(!_archivesAsFiles && _enableGzip ? ArchiveScanLevel.Internal : ArchiveScanLevel.External),
|
|
|
|
|
|
(_archivesAsFiles ? ArchiveScanLevel.External : ArchiveScanLevel.Internal),
|
|
|
|
|
|
(_archivesAsFiles ? ArchiveScanLevel.External : ArchiveScanLevel.Internal),
|
|
|
|
|
|
_logger);
|
2016-04-11 20:41:04 -07:00
|
|
|
|
|
2016-06-21 10:59:37 -07:00
|
|
|
|
// If the file was an archive and was extracted successfully, check it
|
|
|
|
|
|
if (!encounteredErrors)
|
2016-04-11 20:41:04 -07:00
|
|
|
|
{
|
2016-06-21 10:59:37 -07:00
|
|
|
|
_logger.Log(Path.GetFileName(item) + " treated like an archive");
|
|
|
|
|
|
foreach (string entry in Directory.EnumerateFiles(tempdir, "*", SearchOption.AllDirectories))
|
|
|
|
|
|
{
|
2016-08-24 21:19:05 -07:00
|
|
|
|
string tempbasepath = (Path.GetDirectoryName(Path.GetFullPath(item)) + Path.DirectorySeparatorChar);
|
2016-06-21 10:59:37 -07:00
|
|
|
|
lastparent = ProcessFile(Path.GetFullPath(entry), sw, Path.GetFullPath(tempdir),
|
2016-08-24 21:19:05 -07:00
|
|
|
|
(String.IsNullOrEmpty(tempbasepath)
|
|
|
|
|
|
? ""
|
|
|
|
|
|
: (tempbasepath.Length < _basePath.Length
|
|
|
|
|
|
? tempbasepath
|
|
|
|
|
|
: tempbasepath.Remove(0, _basePath.Length))) +
|
|
|
|
|
|
Path.GetFileNameWithoutExtension(item), _datdata, lastparent);
|
2016-06-21 10:59:37 -07:00
|
|
|
|
}
|
2016-05-17 16:53:02 -07:00
|
|
|
|
|
2016-06-21 10:59:37 -07:00
|
|
|
|
// Clear the temp directory
|
|
|
|
|
|
if (Directory.Exists(tempdir))
|
|
|
|
|
|
{
|
2016-09-01 20:38:41 -07:00
|
|
|
|
FileTools.CleanDirectory(tempdir);
|
2016-06-21 10:59:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Otherwise, just get the info on the file itself
|
2016-08-29 16:33:07 -07:00
|
|
|
|
else if (!Directory.Exists(item) && File.Exists(item))
|
2016-05-17 16:53:02 -07:00
|
|
|
|
{
|
2016-06-21 10:59:37 -07:00
|
|
|
|
lastparent = ProcessFile(item, sw, _basePath, "", _datdata, lastparent);
|
2016-05-17 16:53:02 -07:00
|
|
|
|
}
|
2016-04-11 20:41:04 -07:00
|
|
|
|
}
|
2016-06-21 10:59:37 -07:00
|
|
|
|
|
2016-06-13 22:12:00 -07:00
|
|
|
|
return lastparent;
|
|
|
|
|
|
}
|
2016-04-12 20:34:38 -07:00
|
|
|
|
|
2016-06-13 22:12:00 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Process a single file as a file
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="item">File to be added</param>
|
|
|
|
|
|
/// <param name="sw">StreamWriter representing the output file</param>
|
|
|
|
|
|
/// <param name="basepath">Path the represents the parent directory</param>
|
|
|
|
|
|
/// <param name="parent">Parent game to be used</param>
|
|
|
|
|
|
/// <param name="datdata">DatData object with output information</param>
|
|
|
|
|
|
/// <param name="lastparent">Last known parent game name</param>
|
|
|
|
|
|
/// <returns>New last known parent game name</returns>
|
2016-06-16 18:57:34 -07:00
|
|
|
|
private string ProcessFile(string item, StreamWriter sw, string basepath, string parent, Dat datdata, string lastparent)
|
2016-06-13 22:12:00 -07:00
|
|
|
|
{
|
|
|
|
|
|
_logger.Log(Path.GetFileName(item) + " treated like a file");
|
2016-09-14 10:25:01 -07:00
|
|
|
|
Rom rom = FileTools.GetSingleFileInfo(item, noMD5: _noMD5, noSHA1: _noSHA1, date: _addDate);
|
2016-04-11 20:41:04 -07:00
|
|
|
|
|
2016-06-21 11:08:13 -07:00
|
|
|
|
return ProcessFileHelper(item, rom, sw, basepath, parent, datdata, lastparent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Process a single file as a file (with found Rom data)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="item">File to be added</param>
|
|
|
|
|
|
/// <param name="rom">Rom data to be used to write to file</param>
|
|
|
|
|
|
/// <param name="sw">StreamWriter representing the output file</param>
|
|
|
|
|
|
/// <param name="basepath">Path the represents the parent directory</param>
|
|
|
|
|
|
/// <param name="parent">Parent game to be used</param>
|
|
|
|
|
|
/// <param name="datdata">DatData object with output information</param>
|
|
|
|
|
|
/// <param name="lastparent">Last known parent game name</param>
|
|
|
|
|
|
/// <returns>New last known parent game name</returns>
|
2016-08-29 16:33:07 -07:00
|
|
|
|
private string ProcessFileHelper(string item, Rom rom, StreamWriter sw, string basepath, string parent, Dat datdata, string lastparent)
|
2016-06-21 11:08:13 -07:00
|
|
|
|
{
|
2016-06-13 22:12:00 -07:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (basepath.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
2016-04-11 20:41:04 -07:00
|
|
|
|
{
|
2016-06-13 22:12:00 -07:00
|
|
|
|
basepath = basepath.Substring(0, basepath.Length - 1);
|
|
|
|
|
|
}
|
2016-04-11 20:41:04 -07:00
|
|
|
|
|
2016-06-13 22:12:00 -07:00
|
|
|
|
string actualroot = (item == basepath ? item.Split(Path.DirectorySeparatorChar).Last() : item.Remove(0, basepath.Length).Split(Path.DirectorySeparatorChar)[0]);
|
|
|
|
|
|
if (parent == "")
|
|
|
|
|
|
{
|
|
|
|
|
|
actualroot = (actualroot == "" && datdata.Type != "SuperDAT" ? basepath.Split(Path.DirectorySeparatorChar).Last() : actualroot);
|
|
|
|
|
|
}
|
|
|
|
|
|
string actualitem = (item == basepath ? item : item.Remove(0, basepath.Length + 1));
|
2016-04-12 00:18:55 -07:00
|
|
|
|
|
2016-06-13 22:12:00 -07:00
|
|
|
|
// If we're in SuperDAT mode, make sure the added item is by itself
|
|
|
|
|
|
if (datdata.Type == "SuperDAT")
|
|
|
|
|
|
{
|
|
|
|
|
|
actualroot += (actualroot != "" ? Path.DirectorySeparatorChar.ToString() : "") +
|
|
|
|
|
|
(parent != "" ? parent + Path.DirectorySeparatorChar : "") +
|
|
|
|
|
|
Path.GetDirectoryName(actualitem);
|
|
|
|
|
|
actualroot = actualroot.TrimEnd(Path.DirectorySeparatorChar);
|
|
|
|
|
|
actualitem = Path.GetFileName(actualitem);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (parent != "")
|
|
|
|
|
|
{
|
|
|
|
|
|
actualroot = parent.TrimEnd(Path.DirectorySeparatorChar);
|
|
|
|
|
|
}
|
2016-04-18 13:10:07 -07:00
|
|
|
|
|
2016-06-13 22:12:00 -07:00
|
|
|
|
// Drag and drop is funny
|
|
|
|
|
|
if (actualitem == Path.GetFullPath(actualitem))
|
|
|
|
|
|
{
|
|
|
|
|
|
actualitem = Path.GetFileName(actualitem);
|
|
|
|
|
|
}
|
2016-05-23 15:57:09 -07:00
|
|
|
|
|
2016-06-13 22:12:00 -07:00
|
|
|
|
_logger.Log("Actual item added: " + actualitem);
|
2016-04-11 20:41:04 -07:00
|
|
|
|
|
2016-06-13 22:12:00 -07:00
|
|
|
|
// Update rom information
|
2016-08-29 13:42:27 -07:00
|
|
|
|
rom.Machine = new Machine
|
2016-08-29 13:41:42 -07:00
|
|
|
|
{
|
|
|
|
|
|
Name = (datdata.Type == "SuperDAT" ?
|
2016-08-17 21:01:46 -07:00
|
|
|
|
(actualroot != "" && !actualroot.StartsWith(Path.DirectorySeparatorChar.ToString()) ?
|
2016-06-13 22:12:00 -07:00
|
|
|
|
Path.DirectorySeparatorChar.ToString() :
|
|
|
|
|
|
"") + actualroot :
|
2016-08-29 13:41:42 -07:00
|
|
|
|
actualroot),
|
|
|
|
|
|
};
|
2016-08-29 13:42:27 -07:00
|
|
|
|
rom.Machine.Name = rom.Machine.Name.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString());
|
2016-06-13 22:12:00 -07:00
|
|
|
|
rom.Name = actualitem;
|
2016-05-23 15:57:09 -07:00
|
|
|
|
|
2016-08-24 21:19:05 -07:00
|
|
|
|
if (_nowrite)
|
2016-06-13 22:12:00 -07:00
|
|
|
|
{
|
2016-08-29 13:05:32 -07:00
|
|
|
|
string key = rom.HashData.Size + "-" + rom.HashData.CRC;
|
2016-08-29 13:52:13 -07:00
|
|
|
|
if (_datdata.Files.ContainsKey(key))
|
2016-08-24 21:19:05 -07:00
|
|
|
|
{
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files[key].Add(rom);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-08-29 16:33:07 -07:00
|
|
|
|
List<Rom> temp = new List<Rom>();
|
2016-08-24 21:19:05 -07:00
|
|
|
|
temp.Add(rom);
|
2016-08-29 13:52:13 -07:00
|
|
|
|
_datdata.Files.Add(key, temp);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
2016-04-11 20:41:04 -07:00
|
|
|
|
}
|
2016-08-24 21:19:05 -07:00
|
|
|
|
else
|
2016-04-18 13:10:07 -07:00
|
|
|
|
{
|
2016-08-24 21:19:05 -07:00
|
|
|
|
// If we have a different game and we're not at the start of the list, output the end of last item
|
|
|
|
|
|
int last = 0;
|
2016-08-29 13:42:27 -07:00
|
|
|
|
if (lastparent != null && lastparent.ToLowerInvariant() != rom.Machine.Name.ToLowerInvariant())
|
2016-08-24 21:19:05 -07:00
|
|
|
|
{
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteEndGame(sw, datdata.OutputFormat, rom, new List<string>(), new List<string>(), lastparent, 0, out last, _logger);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we have a new game, output the beginning of the new item
|
2016-08-29 13:42:27 -07:00
|
|
|
|
if (lastparent == null || lastparent.ToLowerInvariant() != rom.Machine.Name.ToLowerInvariant())
|
2016-08-24 21:19:05 -07:00
|
|
|
|
{
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteStartGame(sw, datdata.OutputFormat, rom, new List<string>(), lastparent, 0, last, _logger);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
2016-06-13 22:12:00 -07:00
|
|
|
|
|
2016-08-24 21:19:05 -07:00
|
|
|
|
// Write out the rom data
|
2016-09-09 15:51:47 -07:00
|
|
|
|
DatTools.WriteRomData(sw, datdata.OutputFormat, rom, lastparent, datdata, 0, _logger);
|
2016-08-24 21:19:05 -07:00
|
|
|
|
}
|
2016-06-13 22:12:00 -07:00
|
|
|
|
_logger.User("File added: " + actualitem + Environment.NewLine);
|
|
|
|
|
|
|
2016-08-29 13:42:27 -07:00
|
|
|
|
return rom.Machine.Name;
|
2016-06-13 22:12:00 -07:00
|
|
|
|
}
|
|
|
|
|
|
catch (IOException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.Error(ex.ToString());
|
|
|
|
|
|
return null;
|
2016-04-11 20:41:04 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-11 15:09:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|