Files
SabreTools/SabreTools.Helper/Dats/DatFile.cs

7441 lines
242 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.IO;
2016-04-19 15:41:06 -07:00
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
2016-10-24 13:51:39 -07:00
using SabreTools.Helper.Data;
using SabreTools.Helper.Skippers;
using SabreTools.Helper.Tools;
2016-10-24 13:51:39 -07:00
using NaturalSort;
using SharpCompress.Common;
namespace SabreTools.Helper.Dats
{
public class DatFile : ICloneable
{
#region Private instance variables
// Data common to most DAT types
private string _fileName;
private string _name;
private string _description;
private string _rootDir;
private string _category;
private string _version;
private string _date;
private string _author;
private string _email;
private string _homepage;
private string _url;
private string _comment;
private string _header;
private string _type; // Generally only used for SuperDAT
private ForceMerging _forceMerging;
private ForceNodump _forceNodump;
private ForcePacking _forcePacking;
2016-10-25 15:02:02 -07:00
private DatFormat _datFormat;
private bool _excludeOf;
private bool _mergeRoms;
2016-09-22 18:15:02 -07:00
private SortedDictionary<string, List<DatItem>> _files;
private SortedBy _sortedBy;
// Data specific to the Miss DAT type
private bool _useGame;
private string _prefix;
private string _postfix;
private bool _quotes;
private string _repExt;
private string _addExt;
private bool _remExt;
private bool _gameName;
private bool _romba;
// Statistical data related to the DAT
private long _romCount;
private long _diskCount;
private long _totalSize;
private long _crcCount;
private long _md5Count;
private long _sha1Count;
private long _baddumpCount;
private long _nodumpCount;
#endregion
#region Publicly facing variables
// Data common to most DAT types
public string FileName
{
get { return _fileName; }
set { _fileName = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Description
{
get { return _description; }
set { _description = value; }
}
public string RootDir
{
get { return _rootDir; }
set { _rootDir = value; }
}
public string Category
{
get { return _category; }
set { _category = value; }
}
public string Version
{
get { return _version; }
set { _version = value; }
}
public string Date
{
get { return _date; }
set { _date = value; }
}
public string Author
{
get { return _author; }
set { _author = value; }
}
public string Email
{
get { return _email; }
set { _email = value; }
}
public string Homepage
{
get { return _homepage; }
set { _homepage = value; }
}
public string Url
{
get { return _url; }
set { _url = value; }
}
public string Comment
{
get { return _comment; }
set { _comment = value; }
}
public string Header
{
get { return _header; }
set { _header = value; }
}
public string Type // Generally only used for SuperDAT
{
get { return _type; }
set { _type = value; }
}
public ForceMerging ForceMerging
{
get { return _forceMerging; }
set { _forceMerging = value; }
}
public ForceNodump ForceNodump
{
get { return _forceNodump; }
set { _forceNodump = value; }
}
public ForcePacking ForcePacking
{
get { return _forcePacking; }
set { _forcePacking = value; }
}
2016-10-25 15:02:02 -07:00
public DatFormat DatFormat
{
2016-10-25 15:02:02 -07:00
get { return _datFormat; }
set { _datFormat = value; }
}
public bool ExcludeOf
{
get { return _excludeOf; }
set { _excludeOf = value; }
}
public bool MergeRoms
{
get { return _mergeRoms; }
set { _mergeRoms = value; }
}
2016-09-22 18:15:02 -07:00
public SortedDictionary<string, List<DatItem>> Files
{
get
{
if (_files == null)
{
2016-09-22 18:15:02 -07:00
_files = new SortedDictionary<string, List<DatItem>>();
}
return _files;
}
set
{
_files = value;
}
}
public SortedBy SortedBy
{
get { return _sortedBy; }
set { _sortedBy = value; }
}
// Data specific to the Miss DAT type
public bool UseGame
{
get { return _useGame; }
set { _useGame = value; }
}
public string Prefix
{
get { return _prefix; }
set { _prefix = value; }
}
public string Postfix
{
get { return _postfix; }
set { _postfix = value; }
}
public bool Quotes
{
get { return _quotes; }
set { _quotes = value; }
}
public string RepExt
{
get { return _repExt; }
set { _repExt = value; }
}
public string AddExt
{
get { return _addExt; }
set { _addExt = value; }
}
public bool RemExt
{
get { return _remExt; }
set { _remExt = value; }
}
public bool GameName
{
get { return _gameName; }
set { _gameName = value; }
}
public bool Romba
{
get { return _romba; }
set { _romba = value; }
}
// Statistical data related to the DAT
public long RomCount
{
get { return _romCount; }
set { _romCount = value; }
}
public long DiskCount
{
get { return _diskCount; }
set { _diskCount = value; }
}
public long TotalSize
{
get { return _totalSize; }
set { _totalSize = value; }
}
public long CRCCount
{
get { return _crcCount; }
set { _crcCount = value; }
}
public long MD5Count
{
get { return _md5Count; }
set { _md5Count = value; }
}
public long SHA1Count
{
get { return _sha1Count; }
set { _sha1Count = value; }
}
public long BaddumpCount
{
get { return _baddumpCount; }
set { _baddumpCount = value; }
}
public long NodumpCount
{
get { return _nodumpCount; }
set { _nodumpCount = value; }
}
#endregion
#region Instance Methods
#region Bucketing
/// <summary>
/// Take the arbitrarily sorted Files Dictionary and convert to one sorted by Game
/// </summary>
/// <param name="mergeroms">True if roms should be deduped, false otherwise</param>
/// <param name="norename">True if games should only be compared on game and file name, false if system and source are counted</param>
/// <param name="logger">Logger object for file and console output</param>
/// <param name="output">True if the number of hashes counted is to be output (default), false otherwise</param>
public void BucketByGame(bool mergeroms, bool norename, Logger logger, bool output = true)
{
// If we already have the right sorting, trust it
if (_sortedBy == SortedBy.Game)
{
return;
}
// Set the sorted type
_sortedBy = SortedBy.Game;
SortedDictionary<string, List<DatItem>> sortable = new SortedDictionary<string, List<DatItem>>();
long count = 0;
// If we have a null dict or an empty one, output a new dictionary
if (Files == null || Files.Count == 0)
{
Files = sortable;
}
logger.User("Organizing " + (mergeroms ? "and merging " : "") + "roms by game");
// Process each all of the roms
List<string> keys = Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = Files[key];
// If we're merging the roms, do so
if (mergeroms)
{
roms = DatItem.Merge(roms, logger);
}
// Now add each of the roms to their respective games
foreach (DatItem rom in roms)
{
count++;
string newkey = (norename ? ""
: rom.SystemID.ToString().PadLeft(10, '0')
+ "-"
+ rom.SourceID.ToString().PadLeft(10, '0') + "-")
+ (String.IsNullOrEmpty(rom.Machine.Name)
? "Default"
2016-10-26 14:59:12 -07:00
: rom.Machine.Name);
newkey = HttpUtility.HtmlEncode(newkey);
if (sortable.ContainsKey(newkey))
{
sortable[newkey].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
sortable.Add(newkey, temp);
}
}
}
// Now go through and sort all of the lists
keys = sortable.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> sortedlist = sortable[key];
DatItem.Sort(ref sortedlist, norename);
sortable[key] = sortedlist;
}
// Output the count if told to
if (output)
{
logger.User("A total of " + count + " file hashes will be written out to file");
}
// Now assign the dictionary back
Files = sortable;
}
/// <summary>
/// Take the arbitrarily sorted Files Dictionary and convert to one sorted by Size
/// </summary>
/// <param name="mergeroms">True if roms should be deduped, false otherwise</param>
/// <param name="logger">Logger object for file and console output</param>
/// <param name="output">True if the number of hashes counted is to be output (default), false otherwise</param>
public void BucketBySize(bool mergeroms, Logger logger, bool output = true)
{
// If we already have the right sorting, trust it
if (_sortedBy == SortedBy.Size)
{
return;
}
// Set the sorted type
_sortedBy = SortedBy.Size;
SortedDictionary<string, List<DatItem>> sortable = new SortedDictionary<string, List<DatItem>>();
long count = 0;
// If we have a null dict or an empty one, output a new dictionary
if (Files == null || Files.Count == 0)
{
Files = sortable;
}
logger.User("Organizing " + (mergeroms ? "and merging " : "") + "roms by size");
// Process each all of the roms
List<string> keys = Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = Files[key];
// If we're merging the roms, do so
if (mergeroms)
{
roms = DatItem.Merge(roms, logger);
}
// Now add each of the roms to their respective games
foreach (DatItem rom in roms)
{
count++;
string newkey = (rom.Type == ItemType.Rom ? ((Rom)rom).Size.ToString() : "-1");
if (sortable.ContainsKey(newkey))
{
sortable[newkey].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
sortable.Add(newkey, temp);
}
}
}
// Now go through and sort all of the lists
keys = sortable.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> sortedlist = sortable[key];
DatItem.Sort(ref sortedlist, false);
sortable[key] = sortedlist;
}
// Output the count if told to
if (output)
{
logger.User("A total of " + count + " file hashes will be written out to file");
}
// Now assign the dictionary back
Files = sortable;
}
/// <summary>
/// Take the arbitrarily sorted Files Dictionary and convert to one sorted by CRC
/// </summary>
/// <param name="mergeroms">True if roms should be deduped, false otherwise</param>
/// <param name="logger">Logger object for file and console output</param>
/// <param name="output">True if the number of hashes counted is to be output (default), false otherwise</param>
public void BucketByCRC(bool mergeroms, Logger logger, bool output = true)
{
// If we already have the right sorting, trust it
if (_sortedBy == SortedBy.CRC)
{
return;
}
// Set the sorted type
_sortedBy = SortedBy.CRC;
SortedDictionary<string, List<DatItem>> sortable = new SortedDictionary<string, List<DatItem>>();
long count = 0;
// If we have a null dict or an empty one, output a new dictionary
if (Files == null || Files.Count == 0)
{
Files = sortable;
}
logger.User("Organizing " + (mergeroms ? "and merging " : "") + "roms by CRC");
// Process each all of the roms
List<string> keys = Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = Files[key];
// If we're merging the roms, do so
if (mergeroms)
{
roms = DatItem.Merge(roms, logger);
}
// Now add each of the roms to their respective games
foreach (DatItem rom in roms)
{
count++;
string newkey = (rom.Type == ItemType.Rom ? ((Rom)rom).CRC : Constants.CRCZero);
if (sortable.ContainsKey(newkey))
{
sortable[newkey].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
sortable.Add(newkey, temp);
}
}
}
// Now go through and sort all of the lists
keys = sortable.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> sortedlist = sortable[key];
DatItem.Sort(ref sortedlist, false);
sortable[key] = sortedlist;
}
// Output the count if told to
if (output)
{
logger.User("A total of " + count + " file hashes will be written out to file");
}
// Now assign the dictionary back
Files = sortable;
}
2016-09-22 18:15:02 -07:00
/// <summary>
/// Take the arbitrarily sorted Files Dictionary and convert to one sorted by MD5
2016-09-22 18:15:02 -07:00
/// </summary>
/// <param name="mergeroms">True if roms should be deduped, false otherwise</param>
/// <param name="logger">Logger object for file and console output</param>
/// <param name="output">True if the number of hashes counted is to be output (default), false otherwise</param>
public void BucketByMD5(bool mergeroms, Logger logger, bool output = true)
2016-09-22 18:15:02 -07:00
{
// If we already have the right sorting, trust it
if (_sortedBy == SortedBy.MD5)
{
return;
}
// Set the sorted type
_sortedBy = SortedBy.MD5;
SortedDictionary<string, List<DatItem>> sortable = new SortedDictionary<string, List<DatItem>>();
long count = 0;
// If we have a null dict or an empty one, output a new dictionary
if (Files == null || Files.Count == 0)
{
Files = sortable;
}
logger.User("Organizing " + (mergeroms ? "and merging " : "") + "roms by MD5");
2016-09-22 18:15:02 -07:00
// Process each all of the roms
List<string> keys = Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = Files[key];
// If we're merging the roms, do so
if (mergeroms)
{
roms = DatItem.Merge(roms, logger);
}
// Now add each of the roms to their respective games
foreach (DatItem rom in roms)
{
count++;
string newkey = (rom.Type == ItemType.Rom
? ((Rom)rom).MD5
: (rom.Type == ItemType.Disk
? ((Disk)rom).MD5
: Constants.MD5Zero));
if (sortable.ContainsKey(newkey))
{
sortable[newkey].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
sortable.Add(newkey, temp);
}
}
}
// Now go through and sort all of the lists
keys = sortable.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> sortedlist = sortable[key];
DatItem.Sort(ref sortedlist, false);
sortable[key] = sortedlist;
}
// Output the count if told to
if (output)
{
logger.User("A total of " + count + " file hashes will be written out to file");
}
// Now assign the dictionary back
Files = sortable;
}
/// <summary>
/// Take the arbitrarily sorted Files Dictionary and convert to one sorted by SHA1
/// </summary>
/// <param name="mergeroms">True if roms should be deduped, false otherwise</param>
/// <param name="logger">Logger object for file and console output</param>
/// <param name="output">True if the number of hashes counted is to be output (default), false otherwise</param>
public void BucketBySHA1(bool mergeroms, Logger logger, bool output = true)
{
// If we already have the right sorting, trust it
if (_sortedBy == SortedBy.SHA1)
{
return;
}
// Set the sorted type
_sortedBy = SortedBy.SHA1;
2016-09-22 18:15:02 -07:00
SortedDictionary<string, List<DatItem>> sortable = new SortedDictionary<string, List<DatItem>>();
long count = 0;
// If we have a null dict or an empty one, output a new dictionary
if (Files == null || Files.Count == 0)
{
Files = sortable;
}
logger.User("Organizing " + (mergeroms ? "and merging " : "") + "roms by SHA-1");
2016-09-22 18:15:02 -07:00
// Process each all of the roms
List<string> keys = Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = Files[key];
// If we're merging the roms, do so
if (mergeroms)
{
roms = DatItem.Merge(roms, logger);
}
// Now add each of the roms to their respective games
foreach (DatItem rom in roms)
{
count++;
string newkey = (rom.Type == ItemType.Rom
? ((Rom)rom).SHA1
: (rom.Type == ItemType.Disk
? ((Disk)rom).SHA1
: Constants.MD5Zero));
2016-09-22 18:15:02 -07:00
if (sortable.ContainsKey(newkey))
{
sortable[newkey].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
sortable.Add(newkey, temp);
}
}
}
// Now go through and sort all of the lists
keys = sortable.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> sortedlist = sortable[key];
DatItem.Sort(ref sortedlist, false);
2016-09-22 18:15:02 -07:00
sortable[key] = sortedlist;
}
// Output the count if told to
if (output)
{
logger.User("A total of " + count + " file hashes will be written out to file");
}
// Now assign the dictionary back
Files = sortable;
}
#endregion
#region Cloning Methods
public object Clone()
{
return new DatFile
{
FileName = _fileName,
Name = _name,
Description = _description,
RootDir = _rootDir,
Category = _category,
Version = _version,
Date = _date,
Author = _author,
Email = _email,
Homepage = _homepage,
Url = _url,
Comment = _comment,
Header = _header,
Type = _type,
ForceMerging = _forceMerging,
ForceNodump = _forceNodump,
ForcePacking = _forcePacking,
ExcludeOf = _excludeOf,
2016-10-25 15:02:02 -07:00
DatFormat = _datFormat,
MergeRoms = _mergeRoms,
Files = _files,
SortedBy = _sortedBy,
UseGame = _useGame,
Prefix = _prefix,
Postfix = _postfix,
Quotes = _quotes,
RepExt = _repExt,
AddExt = _addExt,
RemExt = _remExt,
GameName = _gameName,
Romba = _romba,
RomCount = _romCount,
DiskCount = _diskCount,
TotalSize = _totalSize,
CRCCount = _crcCount,
MD5Count = _md5Count,
SHA1Count = _sha1Count,
BaddumpCount = _baddumpCount,
NodumpCount = _nodumpCount,
};
}
public object CloneHeader()
{
return new DatFile
{
FileName = _fileName,
Name = _name,
Description = _description,
RootDir = _rootDir,
Category = _category,
Version = _version,
Date = _date,
Author = _author,
Email = _email,
Homepage = _homepage,
Url = _url,
Comment = _comment,
Header = _header,
Type = _type,
ForceMerging = _forceMerging,
ForceNodump = _forceNodump,
ForcePacking = _forcePacking,
ExcludeOf = _excludeOf,
2016-10-25 15:02:02 -07:00
DatFormat = _datFormat,
MergeRoms = _mergeRoms,
2016-09-22 18:15:02 -07:00
Files = new SortedDictionary<string, List<DatItem>>(),
SortedBy = SortedBy.Default,
UseGame = _useGame,
Prefix = _prefix,
Postfix = _postfix,
Quotes = _quotes,
RepExt = _repExt,
AddExt = _addExt,
RemExt = _remExt,
GameName = _gameName,
Romba = _romba,
};
}
#endregion
2016-09-22 18:15:02 -07:00
#region Converting and Updating
/// <summary>
/// Determine if input files should be merged, diffed, or processed invidually
/// </summary>
2016-09-22 18:15:02 -07:00
/// <param name="inputFileNames">Names of the input files and/or folders</param>
/// <param name="outDir">Optional param for output directory</param>
/// <param name="merge">True if input files should be merged into a single file, false otherwise</param>
/// <param name="diff">Non-zero flag for diffing mode, zero otherwise</param>
/// <param name="inplace">True if the cascade-diffed files should overwrite their inputs, false otherwise</param>
/// <param name="skip">True if the first cascaded diff file should be skipped on output, false otherwise</param>
/// <param name="bare">True if the date should not be appended to the default name, false otherwise [OBSOLETE]</param>
/// <param name="clean">True to clean the game names to WoD standard, false otherwise (default)</param>
/// <param name="softlist">True to allow SL DATs to have game names used instead of descriptions, false otherwise (default)</param>
/// <param name="filter">Filter object to be passed to the DatItem level</param>
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
2016-09-22 18:15:02 -07:00
/// <param name="maxDegreeOfParallelism">Integer representing the maximum amount of parallelization to be used</param>
/// <param name="logger">Logging object for console and file output</param>
public void DetermineUpdateType(List<string> inputFileNames, string outDir, bool merge, DiffMode diff, bool inplace, bool skip,
bool bare, bool clean, bool softlist, Filter filter, bool trim, bool single, string root, int maxDegreeOfParallelism, Logger logger)
{
2016-09-22 18:15:02 -07:00
// If we're in merging or diffing mode, use the full list of inputs
if (merge || diff != 0)
2016-08-26 12:25:31 -07:00
{
2016-09-22 18:15:02 -07:00
// Make sure there are no folders in inputs
List<string> newInputFileNames = new List<string>();
foreach (string input in inputFileNames)
{
if (Directory.Exists(input))
{
List<string> files = FileTools.RetrieveFiles(input, new List<string>());
foreach (string file in files)
2016-09-22 18:15:02 -07:00
{
try
{
newInputFileNames.Add(Path.GetFullPath(file) + "¬" + Path.GetFullPath(input));
}
catch (PathTooLongException)
{
logger.Warning("The path for " + file + " was too long");
}
catch (Exception ex)
{
logger.Error(ex.ToString());
}
}
}
else if (File.Exists(input))
{
try
{
newInputFileNames.Add(Path.GetFullPath(input) + "¬" + Path.GetDirectoryName(Path.GetFullPath(input)));
}
catch (PathTooLongException)
{
logger.Warning("The path for " + input + " was too long");
}
catch (Exception ex)
{
logger.Error(ex.ToString());
}
}
}
2016-08-26 12:25:31 -07:00
2016-09-22 18:15:02 -07:00
// If we're in inverse cascade, reverse the list
if ((diff & DiffMode.ReverseCascade) != 0)
2016-09-22 18:15:02 -07:00
{
newInputFileNames.Reverse();
}
2016-09-22 18:15:02 -07:00
// Create a dictionary of all ROMs from the input DATs
List<DatFile> datHeaders = PopulateUserData(newInputFileNames, inplace, clean, softlist,
outDir, filter, trim, single, root, maxDegreeOfParallelism, logger);
2016-05-26 21:43:28 -07:00
2016-09-22 18:15:02 -07:00
// Modify the Dictionary if necessary and output the results
if (diff != 0 && diff < DiffMode.Cascade)
2016-09-22 18:15:02 -07:00
{
DiffNoCascade(diff, outDir, newInputFileNames, logger);
}
// If we're in cascade and diff, output only cascaded diffs
else if (diff != 0 && diff >= DiffMode.Cascade)
2016-09-22 18:15:02 -07:00
{
DiffCascade(outDir, inplace, newInputFileNames, datHeaders, skip, logger);
}
// Output all entries with user-defined merge
else
{
MergeNoDiff(outDir, newInputFileNames, datHeaders, logger);
}
}
2016-09-22 18:15:02 -07:00
// Otherwise, loop through all of the inputs individually
else
{
Update(inputFileNames, outDir, clean, softlist, filter, trim, single, root, maxDegreeOfParallelism, logger);
2016-09-22 18:15:02 -07:00
}
return;
}
/// <summary>
/// Populate the user DatData object from the input files
/// </summary>
/// <param name="filter">Filter object to be passed to the DatItem level</param>
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
2016-09-22 18:15:02 -07:00
/// <param name="maxDegreeOfParallelism">Integer representing the maximum amount of parallelization to be used</param>
/// <param name="logger">Logging object for console and file output</param>
/// <returns>List of DatData objects representing headers</returns>
private List<DatFile> PopulateUserData(List<string> inputs, bool inplace, bool clean, bool softlist, string outDir,
Filter filter, bool trim, bool single, string root, int maxDegreeOfParallelism, Logger logger)
{
2016-09-22 18:15:02 -07:00
DatFile[] datHeaders = new DatFile[inputs.Count];
DateTime start = DateTime.Now;
logger.User("Processing individual DATs");
2016-09-22 18:15:02 -07:00
Parallel.For(0,
inputs.Count,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
i =>
{
2016-09-22 18:15:02 -07:00
string input = inputs[i];
logger.User("Adding DAT: " + input.Split('¬')[0]);
datHeaders[i] = new DatFile
{
2016-10-25 15:02:02 -07:00
DatFormat = (DatFormat != 0 ? DatFormat : 0),
2016-09-22 18:15:02 -07:00
Files = new SortedDictionary<string, List<DatItem>>(),
MergeRoms = MergeRoms,
};
datHeaders[i].Parse(input.Split('¬')[0], i, 0, filter, trim, single, root, logger, true, clean, softlist);
2016-09-22 18:15:02 -07:00
});
2016-09-22 18:15:02 -07:00
logger.User("Processing complete in " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
logger.User("Populating internal DAT");
Files = new SortedDictionary<string, List<DatItem>>();
for (int i = 0; i < inputs.Count; i++)
{
List<string> keys = datHeaders[i].Files.Keys.ToList();
foreach (string key in keys)
{
2016-09-22 18:15:02 -07:00
if (Files.ContainsKey(key))
{
2016-09-22 18:15:02 -07:00
Files[key].AddRange(datHeaders[i].Files[key]);
}
2016-09-22 18:15:02 -07:00
else
{
2016-09-22 18:15:02 -07:00
Files.Add(key, datHeaders[i].Files[key]);
}
2016-09-22 18:15:02 -07:00
datHeaders[i].Files.Remove(key);
}
datHeaders[i].Files = null;
}
2016-09-22 18:15:02 -07:00
logger.User("Processing and populating complete in " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
2016-09-22 18:15:02 -07:00
return datHeaders.ToList();
}
2016-09-22 18:15:02 -07:00
/// <summary>
/// Output non-cascading diffs
/// </summary>
/// <param name="diff">Non-zero flag for diffing mode, zero otherwise</param>
/// <param name="outDir">Output directory to write the DATs to</param>
/// <param name="inputs">List of inputs to write out from</param>
/// <param name="logger">Logging object for console and file output</param>
public void DiffNoCascade(DiffMode diff, string outDir, List<string> inputs, Logger logger)
{
DateTime start = DateTime.Now;
logger.User("Initializing all output DATs");
2016-09-22 18:15:02 -07:00
// Default vars for use
string post = "";
DatFile outerDiffData = new DatFile();
DatFile dupeData = new DatFile();
2016-09-22 18:15:02 -07:00
// Don't have External dupes
if ((diff & DiffMode.NoDupes) != 0)
{
post = " (No Duplicates)";
outerDiffData = (DatFile)CloneHeader();
outerDiffData.FileName += post;
outerDiffData.Name += post;
outerDiffData.Description += post;
outerDiffData.Files = new SortedDictionary<string, List<DatItem>>();
}
// Have External dupes
if ((diff & DiffMode.Dupes) != 0)
{
post = " (Duplicates)";
dupeData = (DatFile)CloneHeader();
dupeData.FileName += post;
dupeData.Name += post;
dupeData.Description += post;
dupeData.Files = new SortedDictionary<string, List<DatItem>>();
}
// Create a list of DatData objects representing individual output files
List<DatFile> outDats = new List<DatFile>();
// Loop through each of the inputs and get or create a new DatData object
if ((diff & DiffMode.Individuals) != 0)
{
DatFile[] outDatsArray = new DatFile[inputs.Count];
Parallel.For(0, inputs.Count, j =>
{
string innerpost = " (" + Path.GetFileNameWithoutExtension(inputs[j].Split('¬')[0]) + " Only)";
DatFile diffData = (DatFile)CloneHeader();
diffData.FileName += innerpost;
diffData.Name += innerpost;
diffData.Description += innerpost;
diffData.Files = new SortedDictionary<string, List<DatItem>>();
outDatsArray[j] = diffData;
});
outDats = outDatsArray.ToList();
}
logger.User("Initializing complete in " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
// Now, loop through the dictionary and populate the correct DATs
start = DateTime.Now;
logger.User("Populating all output DATs");
List<string> keys = Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = DatItem.Merge(Files[key], logger);
if (roms != null && roms.Count > 0)
{
foreach (DatItem rom in roms)
{
// No duplicates
if ((diff & DiffMode.NoDupes) != 0 || (diff & DiffMode.Individuals) != 0)
{
if ((rom.Dupe & DupeType.Internal) != 0)
2016-09-22 18:15:02 -07:00
{
// Individual DATs that are output
if ((diff & DiffMode.Individuals) != 0)
{
2016-09-22 18:15:02 -07:00
if (outDats[rom.SystemID].Files.ContainsKey(key))
{
outDats[rom.SystemID].Files[key].Add(rom);
}
else
{
List<DatItem> tl = new List<DatItem>();
tl.Add(rom);
outDats[rom.SystemID].Files.Add(key, tl);
}
}
// Merged no-duplicates DAT
if ((diff & DiffMode.NoDupes) != 0)
{
DatItem newrom = rom;
newrom.Machine.Name += " (" + Path.GetFileNameWithoutExtension(inputs[newrom.SystemID].Split('¬')[0]) + ")";
2016-09-22 18:15:02 -07:00
if (outerDiffData.Files.ContainsKey(key))
{
outerDiffData.Files[key].Add(newrom);
}
else
{
List<DatItem> tl = new List<DatItem>();
tl.Add(rom);
outerDiffData.Files.Add(key, tl);
}
}
}
2016-09-22 18:15:02 -07:00
}
// Duplicates only
if ((diff & DiffMode.Dupes) != 0)
{
if ((rom.Dupe & DupeType.External) != 0)
{
2016-09-22 18:15:02 -07:00
DatItem newrom = rom;
newrom.Machine.Name += " (" + Path.GetFileNameWithoutExtension(inputs[newrom.SystemID].Split('¬')[0]) + ")";
2016-09-22 18:15:02 -07:00
if (dupeData.Files.ContainsKey(key))
{
2016-09-22 18:15:02 -07:00
dupeData.Files[key].Add(newrom);
}
2016-09-22 18:15:02 -07:00
else
{
2016-09-22 18:15:02 -07:00
List<DatItem> tl = new List<DatItem>();
tl.Add(rom);
dupeData.Files.Add(key, tl);
}
}
2016-09-22 18:15:02 -07:00
}
}
}
}
logger.User("Populating complete in " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
2016-09-22 18:15:02 -07:00
// Finally, loop through and output each of the DATs
start = DateTime.Now;
logger.User("Outputting all created DATs");
2016-09-22 18:15:02 -07:00
// Output the difflist (a-b)+(b-a) diff
if ((diff & DiffMode.NoDupes) != 0)
{
outerDiffData.WriteToFile(outDir, logger);
}
2016-09-22 18:15:02 -07:00
// Output the (ab) diff
if ((diff & DiffMode.Dupes) != 0)
{
dupeData.WriteToFile(outDir, logger);
}
// Output the individual (a-b) DATs
if ((diff & DiffMode.Individuals) != 0)
{
for (int j = 0; j < inputs.Count; j++)
{
// If we have an output directory set, replace the path
string path = outDir + (Path.GetDirectoryName(inputs[j].Split('¬')[0]).Remove(0, inputs[j].Split('¬')[1].Length));
// If we have more than 0 roms, output
if (outDats[j].Files.Count > 0)
{
outDats[j].WriteToFile(path, logger);
}
2016-09-22 18:15:02 -07:00
}
}
logger.User("Outputting complete in " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
}
2016-09-22 18:15:02 -07:00
/// <summary>
/// Output cascading diffs
/// </summary>
/// <param name="outDir">Output directory to write the DATs to</param>
/// <param name="inplace">True if cascaded diffs are outputted in-place, false otherwise</param>
/// <param name="inputs">List of inputs to write out from</param>
/// <param name="datHeaders">Dat headers used optionally</param>
/// <param name="skip">True if the first cascaded diff file should be skipped on output, false otherwise</param>
/// <param name="logger">Logging object for console and file output</param>
public void DiffCascade(string outDir, bool inplace, List<string> inputs, List<DatFile> datHeaders, bool skip, Logger logger)
{
string post = "";
// Create a list of DatData objects representing output files
List<DatFile> outDats = new List<DatFile>();
// Loop through each of the inputs and get or create a new DatData object
DateTime start = DateTime.Now;
logger.User("Initializing all output DATs");
DatFile[] outDatsArray = new DatFile[inputs.Count];
Parallel.For(0, inputs.Count, j =>
{
string innerpost = " (" + Path.GetFileNameWithoutExtension(inputs[j].Split('¬')[0]) + " Only)";
DatFile diffData;
// If we're in inplace mode, take the appropriate DatData object already stored
if (inplace || !String.IsNullOrEmpty(outDir))
{
diffData = datHeaders[j];
}
2016-09-22 18:15:02 -07:00
else
{
2016-09-22 18:15:02 -07:00
diffData = (DatFile)CloneHeader();
diffData.FileName += post;
diffData.Name += post;
diffData.Description += post;
}
diffData.Files = new SortedDictionary<string, List<DatItem>>();
2016-09-22 18:15:02 -07:00
outDatsArray[j] = diffData;
});
outDats = outDatsArray.ToList();
logger.User("Initializing complete in " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
// Now, loop through the dictionary and populate the correct DATs
start = DateTime.Now;
logger.User("Populating all output DATs");
List<string> keys = Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = DatItem.Merge(Files[key], logger);
if (roms != null && roms.Count > 0)
{
foreach (DatItem rom in roms)
{
2016-09-22 18:15:02 -07:00
// There's odd cases where there are items with System ID < 0. Skip them for now
if (rom.SystemID < 0)
{
2016-09-22 18:15:02 -07:00
logger.Warning("Item found with a <0 SystemID: " + rom.Name);
continue;
}
2016-09-22 18:15:02 -07:00
if (outDats[rom.SystemID].Files.ContainsKey(key))
{
2016-09-22 18:15:02 -07:00
outDats[rom.SystemID].Files[key].Add(rom);
}
else
{
List<DatItem> tl = new List<DatItem>();
tl.Add(rom);
outDats[rom.SystemID].Files.Add(key, tl);
}
}
}
2016-09-22 18:15:02 -07:00
}
logger.User("Populating complete in " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
2016-09-22 18:15:02 -07:00
// Finally, loop through and output each of the DATs
start = DateTime.Now;
logger.User("Outputting all created DATs");
for (int j = (skip ? 1 : 0); j < inputs.Count; j++)
{
// If we have an output directory set, replace the path
string path = "";
if (inplace)
{
2016-09-22 18:15:02 -07:00
path = Path.GetDirectoryName(inputs[j].Split('¬')[0]);
}
else if (!String.IsNullOrEmpty(outDir))
{
path = outDir + (Path.GetDirectoryName(inputs[j].Split('¬')[0]).Remove(0, inputs[j].Split('¬')[1].Length));
}
// If we have more than 0 roms, output
if (outDats[j].Files.Count > 0)
{
outDats[j].WriteToFile(path, logger);
}
}
2016-09-22 18:15:02 -07:00
logger.User("Outputting complete in " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
}
2016-09-22 18:15:02 -07:00
/// <summary>
/// Output user defined merge
/// </summary>
/// <param name="outDir">Output directory to write the DATs to</param>
/// <param name="inputs">List of inputs to write out from</param>
/// <param name="datHeaders">Dat headers used optionally</param>
/// <param name="logger">Logging object for console and file output</param>
public void MergeNoDiff(string outDir, List<string> inputs, List<DatFile> datHeaders, Logger logger)
{
// If we're in SuperDAT mode, prefix all games with their respective DATs
if (Type == "SuperDAT")
{
List<string> keys = Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> newroms = new List<DatItem>();
foreach (DatItem rom in Files[key])
{
DatItem newrom = rom;
string filename = inputs[newrom.SystemID].Split('¬')[0];
string rootpath = inputs[newrom.SystemID].Split('¬')[1];
rootpath += (rootpath == "" ? "" : Path.DirectorySeparatorChar.ToString());
filename = filename.Remove(0, rootpath.Length);
newrom.Machine.Name = Path.GetDirectoryName(filename) + Path.DirectorySeparatorChar
2016-09-22 18:15:02 -07:00
+ Path.GetFileNameWithoutExtension(filename) + Path.DirectorySeparatorChar
+ newrom.Machine.Name;
2016-09-22 18:15:02 -07:00
newroms.Add(newrom);
}
Files[key] = newroms;
}
}
// Output a DAT only if there are roms
if (Files.Count != 0)
{
WriteToFile(outDir, logger);
}
}
/// <summary>
/// Convert, update, and filter a DAT file or set of files using a base
/// </summary>
/// <param name="inputFileNames">Names of the input files and/or folders</param>
/// <param name="outDir">Optional param for output directory</param>
/// <param name="merge">True if input files should be merged into a single file, false otherwise</param>
/// <param name="diff">Non-zero flag for diffing mode, zero otherwise</param>
/// <param name="inplace">True if the cascade-diffed files should overwrite their inputs, false otherwise</param>
/// <param name="skip">True if the first cascaded diff file should be skipped on output, false otherwise</param>
/// <param name="bare">True if the date should not be appended to the default name, false otherwise [OBSOLETE]</param>
/// <param name="clean">True to clean the game names to WoD standard, false otherwise (default)</param>
/// <param name="softlist">True to allow SL DATs to have game names used instead of descriptions, false otherwise (default)</param>
/// <param name="filter">Filter object to be passed to the DatItem level</param>
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="maxDegreeOfParallelism">Integer representing the maximum amount of parallelization to be used</param>
/// <param name="logger">Logging object for console and file output</param>
public void Update(List<string> inputFileNames, string outDir, bool clean, bool softlist, Filter filter,
bool trim, bool single, string root, int maxDegreeOfParallelism, Logger logger)
{
Parallel.ForEach(inputFileNames,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
inputFileName =>
{
// Clean the input string
if (inputFileName != "")
{
inputFileName = Path.GetFullPath(inputFileName);
}
if (File.Exists(inputFileName))
{
DatFile innerDatdata = (DatFile)CloneHeader();
logger.User("Processing \"" + Path.GetFileName(inputFileName) + "\"");
innerDatdata.Parse(inputFileName, 0, 0, filter, trim, single,
root, logger, true, clean, softlist,
keepext: ((innerDatdata.DatFormat & DatFormat.TSV) != 0 || (innerDatdata.DatFormat & DatFormat.CSV) != 0));
// If we have roms, output them
if (innerDatdata.Files.Count != 0)
{
innerDatdata.WriteToFile((outDir == "" ? Path.GetDirectoryName(inputFileName) : outDir), logger, overwrite: (outDir != ""));
}
}
else if (Directory.Exists(inputFileName))
{
inputFileName = Path.GetFullPath(inputFileName) + Path.DirectorySeparatorChar;
Parallel.ForEach(Directory.EnumerateFiles(inputFileName, "*", SearchOption.AllDirectories),
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
file =>
{
logger.User("Processing \"" + Path.GetFullPath(file).Remove(0, inputFileName.Length) + "\"");
DatFile innerDatdata = (DatFile)Clone();
innerDatdata.Files = null;
innerDatdata.Parse(file, 0, 0, filter,
trim, single, root, logger, true, clean, softlist,
keepext: ((innerDatdata.DatFormat & DatFormat.TSV) != 0 || (innerDatdata.DatFormat & DatFormat.CSV) != 0));
// If we have roms, output them
if (innerDatdata.Files != null && innerDatdata.Files.Count != 0)
{
innerDatdata.WriteToFile((outDir == "" ? Path.GetDirectoryName(file) : outDir + Path.GetDirectoryName(file).Remove(0, inputFileName.Length - 1)), logger, overwrite: (outDir != ""));
}
});
}
else
{
logger.Error("I'm sorry but " + inputFileName + " doesn't exist!");
}
});
}
2016-09-22 18:15:02 -07:00
#endregion
2016-10-24 16:14:22 -07:00
#region Parsing
2016-09-22 18:15:02 -07:00
/// <summary>
2016-09-22 18:15:02 -07:00
/// Parse a DAT and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="datdata">The DatData object representing found roms to this point</param>
/// <param name="logger">Logger object for console and/or file output</param>
/// <param name="keep">True if full pathnames are to be kept, false otherwise (default)</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="softlist">True if SL XML names should be kept, false otherwise (default)</param>
/// <param name="keepext">True if original extension should be kept, false otherwise (default)</param>
public void Parse(string filename, int sysid, int srcid, Logger logger, bool keep = false, bool clean = false, bool softlist = false, bool keepext = false)
{
Parse(filename, sysid, srcid, new Filter(), false, false, "", logger, keep, clean, softlist, keepext);
2016-09-22 18:15:02 -07:00
}
/// <summary>
/// Parse a DAT and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="filter">Filter object for passing to the DatItem level</param>
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="logger">Logger object for console and/or file output</param>
2016-09-22 18:15:02 -07:00
/// <param name="keep">True if full pathnames are to be kept, false otherwise (default)</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
2016-09-22 18:15:02 -07:00
/// <param name="softlist">True if SL XML names should be kept, false otherwise (default)</param>
/// <param name="keepext">True if original extension should be kept, false otherwise (default)</param>
public void Parse(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Rom filtering
Filter filter,
// Rom renaming
bool trim,
bool single,
string root,
// Miscellaneous
Logger logger,
2016-09-22 18:15:02 -07:00
bool keep = false,
bool clean = false,
bool softlist = false,
bool keepext = false)
{
2016-09-22 18:15:02 -07:00
// Check the file extension first as a safeguard
string ext = Path.GetExtension(filename).ToLowerInvariant();
if (ext.StartsWith("."))
{
ext = ext.Substring(1);
}
if (ext != "dat" && ext != "md5" && ext != "sfv" && ext != "sha1" && ext != "txt" && ext != "xml")
2016-09-22 18:15:02 -07:00
{
return;
}
2016-09-22 18:15:02 -07:00
// If the output filename isn't set already, get the internal filename
FileName = (String.IsNullOrEmpty(FileName) ? (keepext ? Path.GetFileName(filename) : Path.GetFileNameWithoutExtension(filename)) : FileName);
// If the output type isn't set already, get the internal output type
2016-10-25 15:02:02 -07:00
DatFormat = (DatFormat == 0 ? FileTools.GetDatFormat(filename, logger) : DatFormat);
2016-09-22 18:15:02 -07:00
// Make sure there's a dictionary to read to
if (Files == null)
{
2016-09-22 18:15:02 -07:00
Files = new SortedDictionary<string, List<DatItem>>();
}
2016-09-22 18:15:02 -07:00
// Now parse the correct type of DAT
2016-10-25 15:02:02 -07:00
switch (FileTools.GetDatFormat(filename, logger))
2016-09-22 18:15:02 -07:00
{
2016-10-25 15:02:02 -07:00
case DatFormat.ClrMamePro:
case DatFormat.DOSCenter:
ParseCMP(filename, sysid, srcid, filter, trim, single, root, logger, keep, clean);
2016-09-22 18:15:02 -07:00
break;
2016-10-25 15:02:02 -07:00
case DatFormat.Logiqx:
case DatFormat.OfflineList:
case DatFormat.SabreDat:
case DatFormat.SoftwareList:
ParseGenericXML(filename, sysid, srcid, filter, trim, single, root, logger, keep, clean, softlist);
2016-09-22 18:15:02 -07:00
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RedumpMD5:
ParseRedumpMD5(filename, sysid, srcid, filter, trim, single, root, logger, clean);
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RedumpSFV:
ParseRedumpSFV(filename, sysid, srcid, filter, trim, single, root, logger, clean);
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RedumpSHA1:
ParseRedumpSHA1(filename, sysid, srcid, filter, trim, single, root, logger, clean);
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RomCenter:
ParseRC(filename, sysid, srcid, filter, trim, single, root, logger, clean);
break;
2016-09-22 18:15:02 -07:00
default:
return;
}
}
/// <summary>
2016-09-22 18:15:02 -07:00
/// Parse a ClrMamePro DAT and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="filter">Filter object for passing to the DatItem level</param>
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="logger">Logger object for console and/or file output</param>
/// <param name="keep">True if full pathnames are to be kept, false otherwise (default)</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
2016-09-22 18:15:02 -07:00
private void ParseCMP(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Rom filtering
Filter filter,
// Rom renaming
bool trim,
bool single,
string root,
// Miscellaneous
Logger logger,
bool keep,
2016-09-22 18:15:02 -07:00
bool clean)
{
2016-09-22 18:15:02 -07:00
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(File.OpenRead(filename), enc);
2016-09-22 18:15:02 -07:00
bool block = false, superdat = false;
string blockname = "", tempgamename = "", gamedesc = "", cloneof = "",
romof = "", sampleof = "", year = "", manufacturer = "";
while (!sr.EndOfStream)
{
2016-09-22 18:15:02 -07:00
string line = sr.ReadLine();
// Comments in CMP DATs start with a #
if (line.Trim().StartsWith("#"))
{
2016-09-22 18:15:02 -07:00
continue;
}
// If the line is the header or a game
if (Regex.IsMatch(line, Constants.HeaderPatternCMP))
{
GroupCollection gc = Regex.Match(line, Constants.HeaderPatternCMP).Groups;
if (gc[1].Value == "clrmamepro" || gc[1].Value == "romvault" || gc[1].Value.ToLowerInvariant() == "doscenter")
{
2016-09-22 18:15:02 -07:00
blockname = "header";
}
2016-09-22 18:15:02 -07:00
block = true;
}
2016-09-22 18:15:02 -07:00
// If the line is a rom-like item and we're in a block
else if ((line.Trim().StartsWith("rom (")
|| line.Trim().StartsWith("disk (")
|| line.Trim().StartsWith("file (")
|| (line.Trim().StartsWith("sample") && !line.Trim().StartsWith("sampleof"))
) && block)
{
ItemType temptype = ItemType.Rom;
if (line.Trim().StartsWith("rom ("))
{
temptype = ItemType.Rom;
}
else if (line.Trim().StartsWith("disk ("))
{
temptype = ItemType.Disk;
}
else if (line.Trim().StartsWith("file ("))
{
temptype = ItemType.Rom;
}
else if (line.Trim().StartsWith("sample"))
{
temptype = ItemType.Sample;
}
2016-09-22 18:15:02 -07:00
// Create the proper DatItem based on the type
DatItem item;
switch (temptype)
{
case ItemType.Archive:
item = new Archive();
break;
case ItemType.BiosSet:
item = new BiosSet();
break;
case ItemType.Disk:
item = new Disk();
break;
case ItemType.Release:
item = new Release();
break;
case ItemType.Sample:
item = new Sample();
break;
case ItemType.Rom:
default:
item = new Rom();
break;
}
2016-09-22 18:15:02 -07:00
// Then populate it with information
item.Machine = new Machine
{
Name = tempgamename,
Description = gamedesc,
CloneOf = cloneof,
RomOf = romof,
SampleOf = sampleof,
Manufacturer = manufacturer,
Year = year,
};
2016-09-22 18:15:02 -07:00
item.SystemID = sysid;
item.SourceID = srcid;
// If we have a sample, treat it special
if (temptype == ItemType.Sample)
{
2016-09-22 18:15:02 -07:00
line = line.Trim().Remove(0, 6).Trim().Replace("\"", ""); // Remove "sample" from the input string
item.Name = line;
}
2016-09-22 18:15:02 -07:00
// Otherwise, process the rest of the line
else
{
2016-09-22 18:15:02 -07:00
string[] gc = line.Trim().Split(' ');
// Loop over all attributes and add them if possible
bool quote = false;
string attrib = "", val = "";
for (int i = 2; i < gc.Length; i++)
{
//If the item is empty, we automatically skip it because it's a fluke
if (gc[i].Trim() == String.Empty)
{
2016-09-22 18:15:02 -07:00
continue;
}
// Special cases for standalone item statuses
else if (gc[i] == "baddump" && attrib != "name" && attrib != "status" && attrib != "flags")
2016-05-27 10:47:41 -07:00
{
2016-09-22 18:15:02 -07:00
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.BadDump;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.BadDump;
}
2016-05-27 10:47:41 -07:00
}
else if (gc[i] == "good" && attrib != "name" && attrib != "status" && attrib != "flags")
{
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Good;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Good;
}
}
else if (gc[i] == "nodump" && attrib != "name" && attrib != "status" && attrib != "flags")
2016-05-27 16:59:28 -07:00
{
2016-09-22 18:15:02 -07:00
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Nodump;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Nodump;
}
2016-05-27 16:59:28 -07:00
}
else if (gc[i] == "verified" && attrib != "name" && attrib != "status" && attrib != "flags")
{
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Verified;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Verified;
}
}
2016-10-06 16:14:37 -07:00
// Special cases for DOSCenter DATs only
else if (line.Trim().StartsWith("file ("))
{
// Loop over the specifics
for (int j = i; j < gc.Length; j++)
2016-10-06 16:14:37 -07:00
{
// Names are not quoted, for some stupid reason
if (gc[j] == "name")
2016-10-06 16:14:37 -07:00
{
// Advance to the first part of the name
j++;
item.Name = gc[j];
// Advance to the next item, adding until we find "size"
j++;
while (j < gc.Length && gc[j] != "size" && gc[j] != "date" && gc[j] != "crc")
{
item.Name += " " + gc[j];
j++;
}
2016-10-06 16:14:37 -07:00
}
// Get the size from the next part
else if (gc[j] == "size")
2016-10-06 16:14:37 -07:00
{
j++;
long tempsize = -1;
if (!Int64.TryParse(gc[j], out tempsize))
{
tempsize = 0;
}
((Rom)item).Size = tempsize;
j++;
2016-10-06 16:14:37 -07:00
}
// Get the date from the next part
else if (gc[j] == "date")
{
j++;
((Rom)item).Date = gc[j].Replace("\"", "") + " " + gc[j + 1].Replace("\"", "");
j += 3;
}
2016-10-06 16:14:37 -07:00
// Get the CRC from the next part
else if (gc[j] == "crc")
{
j++;
((Rom)item).CRC = gc[j].Replace("\"", "").ToLowerInvariant();
}
2016-10-06 16:14:37 -07:00
}
}
2016-09-22 18:15:02 -07:00
// Even number of quotes, not in a quote, not in attribute
else if (Regex.Matches(gc[i], "\"").Count % 2 == 0 && !quote && attrib == "")
{
attrib = gc[i].Replace("\"", "");
}
// Even number of quotes, not in a quote, in attribute
else if (Regex.Matches(gc[i], "\"").Count % 2 == 0 && !quote && attrib != "")
{
switch (attrib.ToLowerInvariant())
{
case "name":
item.Name = gc[i].Replace("\"", "");
break;
case "size":
if (item.Type == ItemType.Rom)
{
long size = -1;
if (Int64.TryParse(gc[i].Replace("\"", ""), out size))
{
2016-09-22 18:15:02 -07:00
((Rom)item).Size = size;
}
2016-09-22 18:15:02 -07:00
}
break;
case "crc":
if (item.Type == ItemType.Rom)
{
((Rom)item).CRC = gc[i].Replace("\"", "").ToLowerInvariant();
}
break;
case "md5":
if (item.Type == ItemType.Rom)
{
((Rom)item).MD5 = gc[i].Replace("\"", "").ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).MD5 = gc[i].Replace("\"", "").ToLowerInvariant();
}
break;
case "sha1":
if (item.Type == ItemType.Rom)
{
((Rom)item).SHA1 = gc[i].Replace("\"", "").ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).SHA1 = gc[i].Replace("\"", "").ToLowerInvariant();
}
break;
case "status":
2016-09-22 18:15:02 -07:00
case "flags":
if (gc[i].ToLowerInvariant() == "good")
2016-09-22 18:15:02 -07:00
{
if (item.Type == ItemType.Rom)
{
2016-09-22 18:15:02 -07:00
((Rom)item).ItemStatus = ItemStatus.Good;
}
2016-09-22 18:15:02 -07:00
else if (item.Type == ItemType.Disk)
{
2016-09-22 18:15:02 -07:00
((Disk)item).ItemStatus = ItemStatus.Good;
}
2016-09-22 18:15:02 -07:00
}
else if (gc[i].ToLowerInvariant() == "baddump")
2016-09-22 18:15:02 -07:00
{
if (item.Type == ItemType.Rom)
{
2016-09-22 18:15:02 -07:00
((Rom)item).ItemStatus = ItemStatus.BadDump;
}
2016-09-22 18:15:02 -07:00
else if (item.Type == ItemType.Disk)
{
2016-09-22 18:15:02 -07:00
((Disk)item).ItemStatus = ItemStatus.BadDump;
}
2016-09-22 18:15:02 -07:00
}
else if (gc[i].ToLowerInvariant() == "nodump")
2016-09-22 18:15:02 -07:00
{
if (item.Type == ItemType.Rom)
{
2016-09-22 18:15:02 -07:00
((Rom)item).ItemStatus = ItemStatus.Nodump;
}
2016-09-22 18:15:02 -07:00
else if (item.Type == ItemType.Disk)
{
2016-09-22 18:15:02 -07:00
((Disk)item).ItemStatus = ItemStatus.Nodump;
}
2016-09-22 18:15:02 -07:00
}
else if (gc[i].ToLowerInvariant() == "verified")
2016-09-22 18:15:02 -07:00
{
if (item.Type == ItemType.Rom)
{
2016-09-22 18:15:02 -07:00
((Rom)item).ItemStatus = ItemStatus.Verified;
}
2016-09-22 18:15:02 -07:00
else if (item.Type == ItemType.Disk)
{
2016-09-22 18:15:02 -07:00
((Disk)item).ItemStatus = ItemStatus.Verified;
}
2016-09-22 18:15:02 -07:00
}
break;
case "date":
if (item.Type == ItemType.Rom)
{
((Rom)item).Date = gc[i].Replace("\"", "") + " " + gc[i + 1].Replace("\"", "");
}
i++;
break;
2016-09-22 18:15:02 -07:00
// Special cases for item statuses
case "good":
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Good;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Good;
}
break;
case "baddump":
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.BadDump;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.BadDump;
}
break;
case "nodump":
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Nodump;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Nodump;
}
break;
case "verified":
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Verified;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Verified;
}
break;
}
2016-09-22 18:15:02 -07:00
attrib = "";
}
// Even number of quotes, in a quote, not in attribute
else if (Regex.Matches(gc[i], "\"").Count % 2 == 0 && quote && attrib == "")
{
// Attributes can't have quoted names
}
// Even number of quotes, in a quote, in attribute
else if (Regex.Matches(gc[i], "\"").Count % 2 == 0 && quote && attrib != "")
{
val += " " + gc[i];
}
// Odd number of quotes, not in a quote, not in attribute
else if (Regex.Matches(gc[i], "\"").Count % 2 == 1 && !quote && attrib == "")
{
// Attributes can't have quoted names
}
// Odd number of quotes, not in a quote, in attribute
else if (Regex.Matches(gc[i], "\"").Count % 2 == 1 && !quote && attrib != "")
{
val = gc[i].Replace("\"", "");
quote = true;
}
// Odd number of quotes, in a quote, not in attribute
else if (Regex.Matches(gc[i], "\"").Count % 2 == 1 && quote && attrib == "")
{
quote = false;
}
// Odd number of quotes, in a quote, in attribute
else if (Regex.Matches(gc[i], "\"").Count % 2 == 1 && quote && attrib != "")
{
val += " " + gc[i].Replace("\"", "");
switch (attrib.ToLowerInvariant())
{
case "name":
item.Name = val;
break;
case "size":
if (item.Type == ItemType.Rom)
{
long size = -1;
if (Int64.TryParse(gc[i].Replace("\"", ""), out size))
{
((Rom)item).Size = size;
}
2016-09-22 18:15:02 -07:00
}
break;
case "crc":
if (item.Type == ItemType.Rom)
{
((Rom)item).CRC = gc[i].Replace("\"", "").ToLowerInvariant();
}
break;
case "md5":
if (item.Type == ItemType.Rom)
{
((Rom)item).MD5 = gc[i].Replace("\"", "").ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).MD5 = gc[i].Replace("\"", "").ToLowerInvariant();
}
break;
case "sha1":
if (item.Type == ItemType.Rom)
{
((Rom)item).SHA1 = gc[i].Replace("\"", "").ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).SHA1 = gc[i].Replace("\"", "").ToLowerInvariant();
}
break;
case "status":
2016-09-22 18:15:02 -07:00
case "flags":
if (gc[i].ToLowerInvariant() == "good")
2016-09-22 18:15:02 -07:00
{
if (item.Type == ItemType.Rom)
2016-05-06 10:59:05 -07:00
{
2016-09-22 18:15:02 -07:00
((Rom)item).ItemStatus = ItemStatus.Good;
}
2016-09-22 18:15:02 -07:00
else if (item.Type == ItemType.Disk)
{
2016-09-22 18:15:02 -07:00
((Disk)item).ItemStatus = ItemStatus.Good;
2016-05-06 10:59:05 -07:00
}
2016-09-22 18:15:02 -07:00
}
else if (gc[i].ToLowerInvariant() == "baddump")
2016-09-22 18:15:02 -07:00
{
if (item.Type == ItemType.Rom)
{
2016-09-22 18:15:02 -07:00
((Rom)item).ItemStatus = ItemStatus.BadDump;
}
2016-09-22 18:15:02 -07:00
else if (item.Type == ItemType.Disk)
2016-05-14 19:41:07 -07:00
{
2016-09-22 18:15:02 -07:00
((Disk)item).ItemStatus = ItemStatus.BadDump;
2016-05-14 19:41:07 -07:00
}
2016-09-22 18:15:02 -07:00
}
else if (gc[i].ToLowerInvariant() == "nodump")
2016-09-22 18:15:02 -07:00
{
if (item.Type == ItemType.Rom)
2016-05-14 19:41:07 -07:00
{
2016-09-22 18:15:02 -07:00
((Rom)item).ItemStatus = ItemStatus.Nodump;
2016-05-14 19:41:07 -07:00
}
2016-09-22 18:15:02 -07:00
else if (item.Type == ItemType.Disk)
2016-05-14 19:41:07 -07:00
{
2016-09-22 18:15:02 -07:00
((Disk)item).ItemStatus = ItemStatus.Nodump;
2016-05-14 19:41:07 -07:00
}
2016-09-22 18:15:02 -07:00
}
else if (gc[i].ToLowerInvariant() == "verified")
2016-09-22 18:15:02 -07:00
{
if (item.Type == ItemType.Rom)
{
2016-09-22 18:15:02 -07:00
((Rom)item).ItemStatus = ItemStatus.Verified;
}
2016-09-22 18:15:02 -07:00
else if (item.Type == ItemType.Disk)
{
2016-09-22 18:15:02 -07:00
((Disk)item).ItemStatus = ItemStatus.Verified;
}
2016-09-22 18:15:02 -07:00
}
break;
case "date":
if (item.Type == ItemType.Rom)
{
((Rom)item).Date = gc[i].Replace("\"", "") + " " + gc[i + 1].Replace("\"", "");
}
i++;
break;
}
2016-09-22 18:15:02 -07:00
quote = false;
attrib = "";
val = "";
}
}
}
2016-09-22 18:15:02 -07:00
// Now process and add the rom
string key = "";
ParseAddHelper(item, filter, trim, single, root, clean, logger, out key);
2016-09-22 18:15:02 -07:00
}
// If the line is anything but a rom or disk and we're in a block
else if (Regex.IsMatch(line, Constants.ItemPatternCMP) && block)
{
GroupCollection gc = Regex.Match(line, Constants.ItemPatternCMP).Groups;
2016-09-22 18:15:02 -07:00
if (blockname != "header")
{
string itemval = gc[2].Value.Replace("\"", "");
switch (gc[1].Value)
{
case "name":
tempgamename = (itemval.ToLowerInvariant().EndsWith(".zip") ? itemval.Remove(itemval.Length - 4) : itemval);
break;
case "description":
gamedesc = itemval;
break;
case "romof":
romof = itemval;
break;
case "cloneof":
cloneof = itemval;
break;
case "year":
year = itemval;
break;
case "manufacturer":
manufacturer = itemval;
break;
case "sampleof":
sampleof = itemval;
break;
}
}
else
{
string itemval = gc[2].Value.Replace("\"", "");
2016-10-06 16:14:37 -07:00
if (line.Trim().StartsWith("Name:"))
{
2016-10-06 16:14:37 -07:00
Name = (String.IsNullOrEmpty(Name) ? line.Substring(6) : Name);
superdat = superdat || itemval.Contains(" - SuperDAT");
if (keep && superdat)
{
Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type);
}
2016-10-06 16:14:37 -07:00
continue;
}
2016-09-22 18:15:02 -07:00
switch (gc[1].Value)
{
case "name":
case "Name:":
Name = (String.IsNullOrEmpty(Name) ? itemval : Name);
superdat = superdat || itemval.Contains(" - SuperDAT");
if (keep && superdat)
{
2016-09-22 18:15:02 -07:00
Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type);
}
2016-09-22 18:15:02 -07:00
break;
case "description":
case "Description:":
Description = (String.IsNullOrEmpty(Description) ? itemval : Description);
break;
case "rootdir":
RootDir = (String.IsNullOrEmpty(RootDir) ? itemval : RootDir);
break;
case "category":
Category = (String.IsNullOrEmpty(Category) ? itemval : Category);
break;
case "version":
case "Version:":
Version = (String.IsNullOrEmpty(Version) ? itemval : Version);
break;
case "date":
case "Date:":
Date = (String.IsNullOrEmpty(Date) ? itemval : Date);
break;
case "author":
case "Author:":
Author = (String.IsNullOrEmpty(Author) ? itemval : Author);
break;
case "email":
Email = (String.IsNullOrEmpty(Email) ? itemval : Email);
break;
case "homepage":
case "Homepage:":
Homepage = (String.IsNullOrEmpty(Homepage) ? itemval : Homepage);
break;
case "url":
Url = (String.IsNullOrEmpty(Url) ? itemval : Url);
break;
case "comment":
case "Comment:":
Comment = (String.IsNullOrEmpty(Comment) ? itemval : Comment);
break;
case "header":
Header = (String.IsNullOrEmpty(Header) ? itemval : Header);
break;
case "type":
Type = (String.IsNullOrEmpty(Type) ? itemval : Type);
superdat = superdat || itemval.Contains("SuperDAT");
break;
case "forcemerging":
switch (itemval)
{
2016-09-22 18:15:02 -07:00
case "none":
ForceMerging = ForceMerging.None;
break;
case "split":
ForceMerging = ForceMerging.Split;
break;
case "full":
ForceMerging = ForceMerging.Full;
break;
}
2016-09-22 18:15:02 -07:00
break;
case "forcezipping":
switch (itemval)
{
2016-09-22 18:15:02 -07:00
case "yes":
ForcePacking = ForcePacking.Zip;
break;
case "no":
ForcePacking = ForcePacking.Unzip;
break;
}
2016-09-22 18:15:02 -07:00
break;
case "forcepacking":
switch (itemval)
{
2016-09-22 18:15:02 -07:00
case "zip":
ForcePacking = ForcePacking.Zip;
break;
case "unzip":
ForcePacking = ForcePacking.Unzip;
break;
}
2016-09-22 18:15:02 -07:00
break;
}
}
}
2016-09-22 18:15:02 -07:00
// If we find an end bracket that's not associated with anything else, the block is done
else if (Regex.IsMatch(line, Constants.EndPatternCMP) && block)
{
block = false;
2016-10-10 18:30:48 -07:00
blockname = ""; tempgamename = ""; gamedesc = ""; cloneof = "";
romof = ""; sampleof = ""; year = ""; manufacturer = "";
2016-09-22 18:15:02 -07:00
}
}
2016-09-22 18:15:02 -07:00
sr.Dispose();
}
/// <summary>
2016-09-30 12:15:36 -07:00
/// Parse an XML DAT (Logiqx, OfflineList, SabreDAT, and Software List) and return all found games and roms within
/// </summary>
2016-09-22 18:15:02 -07:00
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="filter">Filter object for passing to the DatItem level</param>
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="logger">Logger object for console and/or file output</param>
2016-09-30 12:15:36 -07:00
/// <param name="keep">True if full pathnames are to be kept, false otherwise (default)</param>
2016-09-22 18:15:02 -07:00
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
2016-09-30 12:15:36 -07:00
/// <param name="softlist">True if SL XML names should be kept, false otherwise (default)</param>
private void ParseGenericXML(
2016-09-22 18:15:02 -07:00
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Rom filtering
Filter filter,
2016-09-22 18:15:02 -07:00
// Rom renaming
bool trim,
bool single,
string root,
// Miscellaneous
Logger logger,
2016-09-30 12:15:36 -07:00
bool keep,
bool clean,
bool softlist)
{
2016-09-30 12:15:36 -07:00
// Prepare all internal variables
XmlReader subreader, headreader, flagreader;
bool superdat = false, empty = true;
string key = "", date = "";
long size = -1;
ItemStatus its = ItemStatus.None;
List<string> parent = new List<string>();
2016-09-22 18:15:02 -07:00
Encoding enc = Style.GetEncoding(filename);
2016-09-30 12:15:36 -07:00
XmlReader xtr = FileTools.GetXmlTextReader(filename, logger);
2016-09-30 12:15:36 -07:00
// If we got a null reader, just return
if (xtr == null)
2016-09-09 10:00:37 -07:00
{
2016-09-30 12:15:36 -07:00
return;
}
2016-09-09 10:00:37 -07:00
2016-09-30 12:15:36 -07:00
// Otherwise, read the file to the end
try
{
xtr.MoveToContent();
while (!xtr.EOF)
{
2016-09-30 12:15:36 -07:00
// If we're ending a folder or game, take care of possibly empty games and removing from the parent
if (xtr.NodeType == XmlNodeType.EndElement && (xtr.Name == "directory" || xtr.Name == "dir"))
{
2016-09-30 12:15:36 -07:00
// If we didn't find any items in the folder, make sure to add the blank rom
if (empty)
2016-09-22 18:15:02 -07:00
{
2016-09-30 12:15:36 -07:00
string tempgame = String.Join("\\", parent);
Rom rom = new Rom("null", tempgame);
// Now process and add the rom
ParseAddHelper(rom, filter, trim, single, root, clean, logger, out key);
2016-09-22 18:15:02 -07:00
}
2016-09-30 12:15:36 -07:00
// Regardless, end the current folder
int parentcount = parent.Count;
if (parentcount == 0)
2016-09-22 18:15:02 -07:00
{
2016-09-30 12:15:36 -07:00
logger.Verbose("Empty parent: " + String.Join("\\", parent));
empty = true;
2016-09-22 18:15:02 -07:00
}
// If we have an end folder element, remove one item from the parent, if possible
if (parentcount > 0)
{
parent.RemoveAt(parent.Count - 1);
if (keep && parentcount > 1)
{
Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type);
superdat = true;
}
}
}
// We only want elements
if (xtr.NodeType != XmlNodeType.Element)
{
xtr.Read();
continue;
}
switch (xtr.Name)
{
// Handle MAME listxml since they're halfway between a SL and a Logiqx XML
case "mame":
if (xtr.GetAttribute("build") != null)
{
Name = (String.IsNullOrEmpty(Name) ? xtr.GetAttribute("build") : Name);
Description = (String.IsNullOrEmpty(Description) ? Name : Name);
}
xtr.Read();
break;
// New software lists have this behavior
case "softwarelist":
if (xtr.GetAttribute("name") != null)
{
Name = (String.IsNullOrEmpty(Name) ? xtr.GetAttribute("name") : Name);
}
if (xtr.GetAttribute("description") != null)
{
Description = (String.IsNullOrEmpty(Description) ? xtr.GetAttribute("description") : Description);
}
if (xtr.GetAttribute("forcemerging") != null)
{
switch (xtr.GetAttribute("forcemerging"))
{
case "split":
ForceMerging = ForceMerging.Split;
break;
case "none":
ForceMerging = ForceMerging.None;
break;
case "full":
ForceMerging = ForceMerging.Full;
break;
}
}
if (xtr.GetAttribute("forceitemStatus") != null)
{
switch (xtr.GetAttribute("forceitemStatus"))
{
case "obsolete":
ForceNodump = ForceNodump.Obsolete;
break;
case "required":
ForceNodump = ForceNodump.Required;
break;
case "ignore":
ForceNodump = ForceNodump.Ignore;
break;
}
}
if (xtr.GetAttribute("forcepacking") != null)
{
switch (xtr.GetAttribute("forcepacking"))
{
case "zip":
ForcePacking = ForcePacking.Zip;
break;
case "unzip":
ForcePacking = ForcePacking.Unzip;
break;
}
}
xtr.Read();
break;
// Handle M1 DATs since they're 99% the same as a SL DAT
case "m1":
Name = (String.IsNullOrEmpty(Name) ? "M1" : Name);
Description = (String.IsNullOrEmpty(Description) ? "M1" : Description);
if (xtr.GetAttribute("version") != null)
{
Version = (String.IsNullOrEmpty(Version) ? xtr.GetAttribute("version") : Version);
}
xtr.Read();
break;
// OfflineList has a different header format
case "configuration":
headreader = xtr.ReadSubtree();
// If there's no subtree to the header, skip it
if (headreader == null)
{
xtr.Skip();
continue;
}
// Otherwise, read what we can from the header
while (!headreader.EOF)
{
// We only want elements
if (headreader.NodeType != XmlNodeType.Element || headreader.Name == "configuration")
{
headreader.Read();
continue;
}
// Get all header items (ONLY OVERWRITE IF THERE'S NO DATA)
string content = "";
switch (headreader.Name.ToLowerInvariant())
{
case "datname":
content = headreader.ReadElementContentAsString(); ;
Name = (String.IsNullOrEmpty(Name) ? content : Name);
superdat = superdat || content.Contains(" - SuperDAT");
if (keep && superdat)
{
Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type);
}
break;
case "datversionurl":
content = headreader.ReadElementContentAsString(); ;
Url = (String.IsNullOrEmpty(Name) ? content : Url);
break;
default:
headreader.Read();
break;
}
}
break;
// We want to process the entire subtree of the header
case "header":
headreader = xtr.ReadSubtree();
// If there's no subtree to the header, skip it
if (headreader == null)
2016-09-22 18:15:02 -07:00
{
xtr.Skip();
continue;
2016-09-22 18:15:02 -07:00
}
// Otherwise, read what we can from the header
while (!headreader.EOF)
2016-09-22 18:15:02 -07:00
{
// We only want elements
if (headreader.NodeType != XmlNodeType.Element || headreader.Name == "header")
{
headreader.Read();
continue;
}
// Get all header items (ONLY OVERWRITE IF THERE'S NO DATA)
string content = "";
switch (headreader.Name)
{
case "name":
content = headreader.ReadElementContentAsString(); ;
Name = (String.IsNullOrEmpty(Name) ? content : Name);
superdat = superdat || content.Contains(" - SuperDAT");
if (keep && superdat)
{
Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type);
}
break;
case "description":
content = headreader.ReadElementContentAsString();
Description = (String.IsNullOrEmpty(Description) ? content : Description);
break;
case "rootdir":
content = headreader.ReadElementContentAsString();
RootDir = (String.IsNullOrEmpty(RootDir) ? content : RootDir);
break;
case "category":
content = headreader.ReadElementContentAsString();
Category = (String.IsNullOrEmpty(Category) ? content : Category);
break;
case "version":
content = headreader.ReadElementContentAsString();
Version = (String.IsNullOrEmpty(Version) ? content : Version);
break;
case "date":
content = headreader.ReadElementContentAsString();
Date = (String.IsNullOrEmpty(Date) ? content.Replace(".", "/") : Date);
break;
case "author":
content = headreader.ReadElementContentAsString();
Author = (String.IsNullOrEmpty(Author) ? content : Author);
// Special cases for SabreDAT
Email = (String.IsNullOrEmpty(Email) && !String.IsNullOrEmpty(headreader.GetAttribute("email")) ?
headreader.GetAttribute("email") : Email);
Homepage = (String.IsNullOrEmpty(Homepage) && !String.IsNullOrEmpty(headreader.GetAttribute("homepage")) ?
headreader.GetAttribute("homepage") : Email);
Url = (String.IsNullOrEmpty(Url) && !String.IsNullOrEmpty(headreader.GetAttribute("url")) ?
headreader.GetAttribute("url") : Email);
break;
case "email":
content = headreader.ReadElementContentAsString();
Email = (String.IsNullOrEmpty(Email) ? content : Email);
break;
case "homepage":
content = headreader.ReadElementContentAsString();
Homepage = (String.IsNullOrEmpty(Homepage) ? content : Homepage);
break;
case "url":
content = headreader.ReadElementContentAsString();
Url = (String.IsNullOrEmpty(Url) ? content : Url);
break;
case "comment":
content = headreader.ReadElementContentAsString();
Comment = (String.IsNullOrEmpty(Comment) ? content : Comment);
break;
case "type":
content = headreader.ReadElementContentAsString();
Type = (String.IsNullOrEmpty(Type) ? content : Type);
superdat = superdat || content.Contains("SuperDAT");
break;
case "clrmamepro":
case "romcenter":
if (headreader.GetAttribute("header") != null)
{
Header = (String.IsNullOrEmpty(Header) ? headreader.GetAttribute("header") : Header);
}
if (headreader.GetAttribute("plugin") != null)
{
Header = (String.IsNullOrEmpty(Header) ? headreader.GetAttribute("plugin") : Header);
}
if (headreader.GetAttribute("forcemerging") != null)
{
switch (headreader.GetAttribute("forcemerging"))
{
case "split":
ForceMerging = ForceMerging.Split;
break;
case "none":
ForceMerging = ForceMerging.None;
break;
case "full":
ForceMerging = ForceMerging.Full;
break;
}
}
if (headreader.GetAttribute("forceitemStatus") != null)
{
switch (headreader.GetAttribute("forceitemStatus"))
{
case "obsolete":
ForceNodump = ForceNodump.Obsolete;
break;
case "required":
ForceNodump = ForceNodump.Required;
break;
case "ignore":
ForceNodump = ForceNodump.Ignore;
break;
}
}
if (headreader.GetAttribute("forcepacking") != null)
{
switch (headreader.GetAttribute("forcepacking"))
{
case "zip":
ForcePacking = ForcePacking.Zip;
break;
case "unzip":
ForcePacking = ForcePacking.Unzip;
break;
}
}
headreader.Read();
break;
case "flags":
flagreader = xtr.ReadSubtree();
// If we somehow have a null flag section, skip it
if (flagreader == null)
{
xtr.Skip();
continue;
}
while (!flagreader.EOF)
{
// We only want elements
if (flagreader.NodeType != XmlNodeType.Element || flagreader.Name == "flags")
{
flagreader.Read();
continue;
}
switch (flagreader.Name)
{
case "flag":
if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null)
2016-09-22 18:15:02 -07:00
{
content = flagreader.GetAttribute("value");
switch (flagreader.GetAttribute("name"))
{
case "type":
Type = (String.IsNullOrEmpty(Type) ? content : Type);
superdat = superdat || content.Contains("SuperDAT");
break;
case "forcemerging":
switch (content)
{
case "split":
ForceMerging = ForceMerging.Split;
break;
case "none":
ForceMerging = ForceMerging.None;
break;
case "full":
ForceMerging = ForceMerging.Full;
break;
}
break;
case "forceitemStatus":
switch (content)
{
case "obsolete":
ForceNodump = ForceNodump.Obsolete;
break;
case "required":
ForceNodump = ForceNodump.Required;
break;
case "ignore":
ForceNodump = ForceNodump.Ignore;
break;
}
break;
case "forcepacking":
switch (content)
{
case "zip":
ForcePacking = ForcePacking.Zip;
break;
case "unzip":
ForcePacking = ForcePacking.Unzip;
break;
}
break;
}
2016-09-22 18:15:02 -07:00
}
flagreader.Read();
break;
default:
flagreader.Read();
break;
}
}
headreader.Skip();
break;
default:
headreader.Read();
break;
}
}
// Skip the header node now that we've processed it
2016-09-22 18:15:02 -07:00
xtr.Skip();
break;
case "machine":
case "game":
case "software":
string temptype = xtr.Name, publisher = "", partname = "", partinterface = "", areaname = "";
bool? supported = null;
long? areasize = null;
List<Tuple<string, string>> infos = new List<Tuple<string, string>>();
List<Tuple<string, string>> features = new List<Tuple<string, string>>();
// We want to process the entire subtree of the game
subreader = xtr.ReadSubtree();
// Safeguard for interesting case of "software" without anything except roms
bool software = false;
// If we have an empty machine, skip it
if (subreader == null)
{
xtr.Skip();
continue;
}
2016-09-27 13:52:40 -07:00
// Otherwise, add what is possible
subreader.MoveToContent();
// Create a new machine
Machine machine = new Machine
{
Name = xtr.GetAttribute("name"),
Description = xtr.GetAttribute("name"),
RomOf = xtr.GetAttribute("romof") ?? "",
CloneOf = xtr.GetAttribute("cloneof") ?? "",
SampleOf = xtr.GetAttribute("sampleof") ?? "",
};
if (subreader.GetAttribute("supported") != null)
{
switch (subreader.GetAttribute("supported"))
{
case "no":
supported = false;
break;
case "yes":
supported = true;
break;
}
}
if (superdat && !keep)
{
string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value;
if (tempout != "")
{
machine.Name = tempout;
}
}
// Get the name of the game from the parent
else if (superdat && keep && parent.Count > 0)
{
machine.Name = String.Join("\\", parent) + "\\" + machine.Name;
}
// Special offline list parts
string ext = "";
string releaseNumber = "";
2016-09-27 13:52:40 -07:00
while (software || !subreader.EOF)
{
software = false;
// We only want elements
if (subreader.NodeType != XmlNodeType.Element)
{
if (subreader.NodeType == XmlNodeType.EndElement && subreader.Name == "part")
{
partname = "";
partinterface = "";
features = new List<Tuple<string, string>>();
}
if (subreader.NodeType == XmlNodeType.EndElement && (subreader.Name == "dataarea" || subreader.Name == "diskarea"))
{
areaname = "";
areasize = null;
}
subreader.Read();
continue;
}
2016-09-27 13:52:40 -07:00
// Get the roms from the machine
switch (subreader.Name)
{
2016-09-30 16:35:47 -07:00
// For OfflineList only
case "title":
machine.Name = subreader.ReadElementContentAsString();
break;
case "releaseNumber":
releaseNumber = subreader.ReadElementContentAsString();
break;
case "romSize":
if (!Int64.TryParse(subreader.ReadElementContentAsString(), out size))
{
size = -1;
}
break;
case "romCRC":
empty = false;
ext = (subreader.GetAttribute("extension") != null ? subreader.GetAttribute("extension") : "");
DatItem olrom = new Rom
{
Name = releaseNumber + " - " + machine.Name + ext,
Size = size,
CRC = subreader.ReadElementContentAsString(),
ItemStatus = ItemStatus.None,
Machine = machine,
};
// Now process and add the rom
ParseAddHelper(olrom, filter, trim, single, root, clean, logger, out key);
break;
2016-09-27 13:52:40 -07:00
// For Software List only
case "publisher":
publisher = subreader.ReadElementContentAsString();
break;
case "info":
infos.Add(Tuple.Create(subreader.GetAttribute("name"), subreader.GetAttribute("value")));
subreader.Read();
break;
case "part":
partname = subreader.GetAttribute("name");
partinterface = subreader.GetAttribute("interface");
subreader.Read();
break;
case "feature":
features.Add(Tuple.Create(subreader.GetAttribute("name"), subreader.GetAttribute("value")));
subreader.Read();
break;
case "dataarea":
case "diskarea":
areaname = subreader.GetAttribute("name");
long areasizetemp = -1;
if (Int64.TryParse(subreader.GetAttribute("size"), out areasizetemp))
{
areasize = areasizetemp;
}
subreader.Read();
break;
// For Logiqx, SabreDAT, and Software List
case "description":
machine.Description = subreader.ReadElementContentAsString();
if (!softlist && temptype == "software")
{
machine.Name = machine.Description.Replace('/', '_').Replace("\"", "''");
}
break;
case "year":
machine.Year = subreader.ReadElementContentAsString();
break;
case "manufacturer":
machine.Manufacturer = subreader.ReadElementContentAsString();
break;
case "release":
empty = false;
bool? defaultrel = null;
if (subreader.GetAttribute("default") != null)
{
if (subreader.GetAttribute("default") == "yes")
{
defaultrel = true;
}
else if (subreader.GetAttribute("default") == "no")
{
defaultrel = false;
}
}
DatItem relrom = new Release
{
Name = subreader.GetAttribute("name"),
Region = subreader.GetAttribute("region"),
Language = subreader.GetAttribute("language"),
Date = date,
Default = defaultrel,
Machine = machine,
Supported = supported,
Publisher = publisher,
Infos = infos,
PartName = partname,
PartInterface = partinterface,
Features = features,
AreaName = areaname,
AreaSize = areasize,
};
// Now process and add the rom
ParseAddHelper(relrom, filter, trim, single, root, clean, logger, out key);
subreader.Read();
break;
case "biosset":
empty = false;
bool? defaultbios = null;
if (subreader.GetAttribute("default") != null)
{
if (subreader.GetAttribute("default") == "yes")
{
defaultbios = true;
}
else if (subreader.GetAttribute("default") == "no")
{
defaultbios = false;
}
}
DatItem biosrom = new BiosSet
{
Name = subreader.GetAttribute("name"),
Description = subreader.GetAttribute("description"),
Default = defaultbios,
Machine = machine,
Supported = supported,
Publisher = publisher,
Infos = infos,
PartName = partname,
PartInterface = partinterface,
Features = features,
AreaName = areaname,
AreaSize = areasize,
SystemID = sysid,
System = filename,
SourceID = srcid,
};
// Now process and add the rom
ParseAddHelper(biosrom, filter, trim, single, root, clean, logger, out key);
2016-09-22 18:15:02 -07:00
subreader.Read();
break;
case "archive":
empty = false;
2016-09-22 18:15:02 -07:00
DatItem archiverom = new Archive
{
Name = subreader.GetAttribute("name"),
Machine = machine,
Supported = supported,
Publisher = publisher,
Infos = infos,
PartName = partname,
PartInterface = partinterface,
Features = features,
AreaName = areaname,
AreaSize = areasize,
SystemID = sysid,
System = filename,
SourceID = srcid,
};
2016-09-22 18:15:02 -07:00
// Now process and add the rom
ParseAddHelper(archiverom, filter, trim, single, root, clean, logger, out key);
2016-09-27 13:52:40 -07:00
subreader.Read();
break;
case "sample":
empty = false;
2016-09-22 18:15:02 -07:00
DatItem samplerom = new Sample
{
Name = subreader.GetAttribute("name"),
Machine = machine,
Supported = supported,
Publisher = publisher,
Infos = infos,
PartName = partname,
PartInterface = partinterface,
Features = features,
AreaName = areaname,
AreaSize = areasize,
SystemID = sysid,
System = filename,
SourceID = srcid,
};
2016-09-22 18:15:02 -07:00
// Now process and add the rom
ParseAddHelper(samplerom, filter, trim, single, root, clean, logger, out key);
subreader.Read();
break;
case "rom":
case "disk":
empty = false;
2016-09-22 18:15:02 -07:00
// If the rom has a status, flag it
its = ItemStatus.None;
if (subreader.GetAttribute("flags") == "good" || subreader.GetAttribute("status") == "good")
{
its = ItemStatus.Good;
}
if (subreader.GetAttribute("flags") == "baddump" || subreader.GetAttribute("status") == "baddump")
{
logger.Verbose("Bad dump detected: " +
(subreader.GetAttribute("name") != null && subreader.GetAttribute("name") != "" ? "\"" + xtr.GetAttribute("name") + "\"" : "ROM NAME NOT FOUND"));
its = ItemStatus.BadDump;
}
if (subreader.GetAttribute("flags") == "nodump" || subreader.GetAttribute("status") == "nodump")
{
logger.Verbose("Nodump detected: " +
(subreader.GetAttribute("name") != null && subreader.GetAttribute("name") != "" ? "\"" + xtr.GetAttribute("name") + "\"" : "ROM NAME NOT FOUND"));
its = ItemStatus.Nodump;
}
if (subreader.GetAttribute("flags") == "verified" || subreader.GetAttribute("status") == "verified")
{
its = ItemStatus.Verified;
}
2016-09-22 18:15:02 -07:00
// If the rom has a Date attached, read it in and then sanitize it
date = "";
if (subreader.GetAttribute("date") != null)
{
DateTime dateTime = DateTime.Now;
if (DateTime.TryParse(subreader.GetAttribute("date"), out dateTime))
{
date = dateTime.ToString();
}
else
{
date = subreader.GetAttribute("date");
}
}
2016-09-22 18:15:02 -07:00
// Take care of hex-sized files
size = -1;
if (subreader.GetAttribute("size") != null && subreader.GetAttribute("size").Contains("0x"))
{
size = Convert.ToInt64(subreader.GetAttribute("size"), 16);
}
else if (subreader.GetAttribute("size") != null)
{
Int64.TryParse(subreader.GetAttribute("size"), out size);
}
2016-09-22 18:15:02 -07:00
// If the rom is continue or ignore, add the size to the previous rom
if (subreader.GetAttribute("loadflag") == "continue" || subreader.GetAttribute("loadflag") == "ignore")
{
int index = Files[key].Count() - 1;
DatItem lastrom = Files[key][index];
if (lastrom.Type == ItemType.Rom)
{
((Rom)lastrom).Size += size;
}
Files[key].RemoveAt(index);
Files[key].Add(lastrom);
subreader.Read();
continue;
}
// If we're in clean mode, sanitize the game name
if (clean)
{
machine.Name = Style.CleanGameName(machine.Name.Split(Path.DirectorySeparatorChar));
}
2016-09-22 18:15:02 -07:00
DatItem inrom;
switch (subreader.Name.ToLowerInvariant())
{
case "disk":
inrom = new Disk
{
Name = subreader.GetAttribute("name"),
MD5 = subreader.GetAttribute("md5")?.ToLowerInvariant(),
SHA1 = subreader.GetAttribute("sha1")?.ToLowerInvariant(),
ItemStatus = its,
Machine = machine,
Supported = supported,
Publisher = publisher,
Infos = infos,
PartName = partname,
PartInterface = partinterface,
Features = features,
AreaName = areaname,
AreaSize = areasize,
SystemID = sysid,
System = filename,
SourceID = srcid,
};
break;
case "rom":
default:
inrom = new Rom
{
Name = subreader.GetAttribute("name"),
Size = size,
CRC = subreader.GetAttribute("crc"),
MD5 = subreader.GetAttribute("md5")?.ToLowerInvariant(),
SHA1 = subreader.GetAttribute("sha1")?.ToLowerInvariant(),
ItemStatus = its,
Date = date,
Machine = machine,
Supported = supported,
Publisher = publisher,
Infos = infos,
PartName = partname,
PartInterface = partinterface,
Features = features,
AreaName = areaname,
AreaSize = areasize,
SystemID = sysid,
System = filename,
SourceID = srcid,
};
break;
}
// Now process and add the rom
ParseAddHelper(inrom, filter, trim, single, root, clean, logger, out key);
subreader.Read();
break;
default:
subreader.Read();
break;
}
}
xtr.Skip();
break;
case "dir":
case "directory":
// Set SuperDAT flag for all SabreDAT inputs, regardless of depth
superdat = true;
if (keep)
{
Type = (Type == "" ? "SuperDAT" : Type);
}
2016-09-22 18:15:02 -07:00
string foldername = (xtr.GetAttribute("name") == null ? "" : xtr.GetAttribute("name"));
if (foldername != "")
{
parent.Add(foldername);
}
2016-09-22 18:15:02 -07:00
xtr.Read();
break;
case "file":
empty = false;
2016-09-22 18:15:02 -07:00
// If the rom is itemStatus, flag it
its = ItemStatus.None;
flagreader = xtr.ReadSubtree();
2016-09-22 18:15:02 -07:00
// If the subtree is empty, skip it
if (flagreader == null)
{
xtr.Skip();
continue;
}
2016-09-22 18:15:02 -07:00
while (!flagreader.EOF)
{
// We only want elements
if (flagreader.NodeType != XmlNodeType.Element || flagreader.Name == "flags")
{
flagreader.Read();
continue;
}
switch (flagreader.Name)
{
case "flag":
case "status":
if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null)
{
string content = flagreader.GetAttribute("value");
switch (flagreader.GetAttribute("name"))
{
case "good":
its = ItemStatus.Good;
break;
case "baddump":
logger.Verbose("Bad dump detected: " + (xtr.GetAttribute("name") != null && xtr.GetAttribute("name") != "" ?
"\"" + xtr.GetAttribute("name") + "\"" : "ROM NAME NOT FOUND"));
its = ItemStatus.BadDump;
break;
case "nodump":
logger.Verbose("Nodump detected: " + (xtr.GetAttribute("name") != null && xtr.GetAttribute("name") != "" ?
"\"" + xtr.GetAttribute("name") + "\"" : "ROM NAME NOT FOUND"));
its = ItemStatus.Nodump;
break;
case "verified":
its = ItemStatus.Verified;
break;
}
}
break;
}
flagreader.Read();
}
2016-09-22 18:15:02 -07:00
// If the rom has a Date attached, read it in and then sanitize it
date = "";
if (xtr.GetAttribute("date") != null)
{
date = DateTime.Parse(xtr.GetAttribute("date")).ToString();
}
// Take care of hex-sized files
size = -1;
if (xtr.GetAttribute("size") != null && xtr.GetAttribute("size").Contains("0x"))
{
size = Convert.ToInt64(xtr.GetAttribute("size"), 16);
}
else if (xtr.GetAttribute("size") != null)
{
Int64.TryParse(xtr.GetAttribute("size"), out size);
}
// If the rom is continue or ignore, add the size to the previous rom
if (xtr.GetAttribute("loadflag") == "continue" || xtr.GetAttribute("loadflag") == "ignore")
{
int index = Files[key].Count() - 1;
DatItem lastrom = Files[key][index];
if (lastrom.Type == ItemType.Rom)
{
((Rom)lastrom).Size += size;
}
Files[key].RemoveAt(index);
Files[key].Add(lastrom);
continue;
}
2016-09-22 18:15:02 -07:00
Machine dir = new Machine();
// Get the name of the game from the parent
dir.Name = String.Join("\\", parent);
dir.Description = dir.Name;
2016-09-22 18:15:02 -07:00
// If we aren't keeping names, trim out the path
if (!keep || !superdat)
{
string tempout = Regex.Match(dir.Name, @".*?\\(.*)").Groups[1].Value;
if (tempout != "")
{
dir.Name = tempout;
}
}
DatItem rom;
switch (xtr.GetAttribute("type").ToLowerInvariant())
{
case "disk":
rom = new Disk
{
Name = xtr.GetAttribute("name"),
MD5 = xtr.GetAttribute("md5")?.ToLowerInvariant(),
SHA1 = xtr.GetAttribute("sha1")?.ToLowerInvariant(),
ItemStatus = its,
Machine = dir,
SystemID = sysid,
System = filename,
SourceID = srcid,
};
break;
case "rom":
default:
rom = new Rom
{
Name = xtr.GetAttribute("name"),
Size = size,
CRC = xtr.GetAttribute("crc")?.ToLowerInvariant(),
MD5 = xtr.GetAttribute("md5")?.ToLowerInvariant(),
SHA1 = xtr.GetAttribute("sha1")?.ToLowerInvariant(),
ItemStatus = its,
Date = date,
Machine = dir,
SystemID = sysid,
System = filename,
SourceID = srcid,
};
break;
}
2016-09-22 18:15:02 -07:00
// Now process and add the rom
ParseAddHelper(rom, filter, trim, single, root, clean, logger, out key);
2016-09-22 18:15:02 -07:00
xtr.Read();
break;
default:
xtr.Read();
break;
}
}
}
2016-09-29 12:57:10 -07:00
catch (Exception ex)
{
2016-09-29 12:57:10 -07:00
logger.Warning(ex.ToString());
// For XML errors, just skip the affected node
2016-09-29 12:55:16 -07:00
xtr?.Read();
}
xtr.Dispose();
}
2016-09-30 12:15:36 -07:00
/// <summary>
/// Parse a Redump MD5 and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="filter">Filter object for passing to the DatItem level</param>
2016-09-30 12:15:36 -07:00
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="logger">Logger object for console and/or file output</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
private void ParseRedumpMD5(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Rom filtering
Filter filter,
2016-09-30 12:15:36 -07:00
// Rom renaming
bool trim,
bool single,
string root,
// Miscellaneous
Logger logger,
bool clean)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(File.OpenRead(filename), enc);
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
Rom rom = new Rom
{
Name = line.Split(' ')[1].Replace("*", String.Empty),
Size = -1,
MD5 = line.Split(' ')[0],
ItemStatus = ItemStatus.None,
Machine = new Machine
{
Name = Path.GetFileNameWithoutExtension(filename),
},
SystemID = sysid,
SourceID = srcid,
};
2016-09-30 12:15:36 -07:00
// Now process and add the rom
string key = "";
ParseAddHelper(rom, filter, trim, single, root, clean, logger, out key);
2016-09-30 12:15:36 -07:00
}
sr.Dispose();
}
/// <summary>
/// Parse a Redump SFV and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="filter">Filter object for passing to the DatItem level</param>
2016-09-30 12:15:36 -07:00
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="logger">Logger object for console and/or file output</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
private void ParseRedumpSFV(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Rom filtering
Filter filter,
2016-09-30 12:15:36 -07:00
// Rom renaming
bool trim,
bool single,
string root,
// Miscellaneous
Logger logger,
bool clean)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(File.OpenRead(filename), enc);
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
Rom rom = new Rom
{
Name = line.Split(' ')[0].Replace("*", String.Empty),
Size = -1,
CRC = line.Split(' ')[1],
ItemStatus = ItemStatus.None,
Machine = new Machine
{
Name = Path.GetFileNameWithoutExtension(filename),
},
SystemID = sysid,
SourceID = srcid,
};
2016-09-30 12:15:36 -07:00
// Now process and add the rom
string key = "";
ParseAddHelper(rom, filter, trim, single, root, clean, logger, out key);
2016-09-30 12:15:36 -07:00
}
sr.Dispose();
}
/// <summary>
/// Parse a Redump SHA-1 and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="filter">Filter object for passing to the DatItem level</param>
2016-09-30 12:15:36 -07:00
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="logger">Logger object for console and/or file output</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
private void ParseRedumpSHA1(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Rom filtering
Filter filter,
2016-09-30 12:15:36 -07:00
// Rom renaming
bool trim,
bool single,
string root,
// Miscellaneous
Logger logger,
bool clean)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(File.OpenRead(filename), enc);
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
Rom rom = new Rom
{
Name = line.Split(' ')[1].Replace("*", String.Empty),
Size = -1,
SHA1 = line.Split(' ')[0],
ItemStatus = ItemStatus.None,
Machine = new Machine
{
Name = Path.GetFileNameWithoutExtension(filename),
},
SystemID = sysid,
SourceID = srcid,
};
2016-09-30 12:15:36 -07:00
// Now process and add the rom
string key = "";
ParseAddHelper(rom, filter, trim, single, root, clean, logger, out key);
2016-09-30 12:15:36 -07:00
}
sr.Dispose();
}
/// <summary>
/// Parse a RomCenter DAT and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="filter">Filter object for passing to the DatItem level</param>
2016-09-30 12:15:36 -07:00
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="logger">Logger object for console and/or file output</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
private void ParseRC(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Rom filtering
Filter filter,
2016-09-30 12:15:36 -07:00
// Rom renaming
bool trim,
bool single,
string root,
// Miscellaneous
Logger logger,
bool clean)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(File.OpenRead(filename), enc);
string blocktype = "";
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
// If the line is the start of the credits section
if (line.ToLowerInvariant().StartsWith("[credits]"))
2016-09-30 12:15:36 -07:00
{
blocktype = "credits";
}
// If the line is the start of the dat section
else if (line.ToLowerInvariant().StartsWith("[dat]"))
2016-09-30 12:15:36 -07:00
{
blocktype = "dat";
}
// If the line is the start of the emulator section
else if (line.ToLowerInvariant().StartsWith("[emulator]"))
2016-09-30 12:15:36 -07:00
{
blocktype = "emulator";
}
// If the line is the start of the game section
else if (line.ToLowerInvariant().StartsWith("[games]"))
2016-09-30 12:15:36 -07:00
{
blocktype = "games";
}
// Otherwise, it's not a section and it's data, so get out all data
else
{
// If we have an author
2016-10-02 20:32:57 -07:00
if (line.ToLowerInvariant().StartsWith("author="))
2016-09-30 12:15:36 -07:00
{
Author = (String.IsNullOrEmpty(Author) ? line.Split('=')[1] : Author);
}
// If we have one of the three version tags
2016-10-02 20:32:57 -07:00
else if (line.ToLowerInvariant().StartsWith("version="))
2016-09-30 12:15:36 -07:00
{
switch (blocktype)
{
case "credits":
Version = (String.IsNullOrEmpty(Version) ? line.Split('=')[1] : Version);
break;
case "emulator":
Description = (String.IsNullOrEmpty(Description) ? line.Split('=')[1] : Description);
break;
}
}
2016-10-21 15:37:34 -07:00
// If we have a URL
else if (line.ToLowerInvariant().StartsWith("url="))
{
Url = (String.IsNullOrEmpty(Url) ? line.Split('=')[1] : Url);
}
2016-09-30 12:15:36 -07:00
// If we have a comment
2016-10-02 20:32:57 -07:00
else if (line.ToLowerInvariant().StartsWith("comment="))
2016-09-30 12:15:36 -07:00
{
Comment = (String.IsNullOrEmpty(Comment) ? line.Split('=')[1] : Comment);
}
// If we have the split flag
2016-10-02 20:32:57 -07:00
else if (line.ToLowerInvariant().StartsWith("split="))
2016-09-30 12:15:36 -07:00
{
int split = 0;
if (Int32.TryParse(line.Split('=')[1], out split))
{
if (split == 1)
{
ForceMerging = ForceMerging.Split;
}
}
}
// If we have the merge tag
2016-10-02 20:32:57 -07:00
else if (line.ToLowerInvariant().StartsWith("merge="))
2016-09-30 12:15:36 -07:00
{
int merge = 0;
if (Int32.TryParse(line.Split('=')[1], out merge))
{
if (merge == 1)
{
ForceMerging = ForceMerging.Full;
}
}
}
// If we have the refname tag
2016-10-02 20:32:57 -07:00
else if (line.ToLowerInvariant().StartsWith("refname="))
2016-09-30 12:15:36 -07:00
{
Name = (String.IsNullOrEmpty(Name) ? line.Split('=')[1] : Name);
}
// If we have a rom
else if (line.StartsWith("¬"))
{
2016-10-02 20:32:57 -07:00
// Some old RC DATs have this behavior
if (line.Contains("¬N¬O"))
{
line = line.Replace("¬N¬O", "") + "¬¬";
}
2016-09-30 12:15:36 -07:00
/*
The rominfo order is as follows:
1 - parent name
2 - parent description
3 - game name
4 - game description
5 - rom name
6 - rom crc
7 - rom size
8 - romof name
9 - merge name
*/
string[] rominfo = line.Split('¬');
// Try getting the size separately
long size = 0;
if (!Int64.TryParse(rominfo[7], out size))
{
size = 0;
}
Rom rom = new Rom
{
Name = rominfo[5],
Size = size,
CRC = rominfo[6],
ItemStatus = ItemStatus.None,
Machine = new Machine
{
Name = rominfo[3],
Description = rominfo[4],
CloneOf = rominfo[1],
RomOf = rominfo[8],
},
SystemID = sysid,
SourceID = srcid,
};
2016-09-30 12:15:36 -07:00
// Now process and add the rom
string key = "";
ParseAddHelper(rom, filter, trim, single, root, clean, logger, out key);
2016-09-30 12:15:36 -07:00
}
}
}
sr.Dispose();
}
/// <summary>
2016-09-22 18:15:02 -07:00
/// Add a rom to the Dat after checking
/// </summary>
2016-09-22 18:15:02 -07:00
/// <param name="item">Item data to check against</param>
/// <param name="filter">Filter object for passing to the DatItem level</param>
2016-09-22 18:15:02 -07:00
/// <param name="trim">True if we are supposed to trim names to NTFS length, false otherwise</param>
/// <param name="single">True if all games should be replaced by '!', false otherwise</param>
/// <param name="root">String representing root directory to compare against for length calculation</param>
/// <param name="logger">Logger object for console and/or file output</param>
private void ParseAddHelper(DatItem item, Filter filter, bool trim, bool single, string root, bool clean, Logger logger, out string key)
{
2016-09-22 18:15:02 -07:00
key = "";
// If there's no name in the rom, we log and skip it
if (item.Name == null)
{
2016-09-22 18:15:02 -07:00
logger.Warning("Rom with no name found! Skipping...");
return;
}
2016-09-22 18:15:02 -07:00
// If we're in cleaning mode, sanitize the game name
item.Machine.Name = (clean ? Style.CleanGameName(item.Machine.Name) : item.Machine.Name);
2016-09-22 18:15:02 -07:00
// If we have a Rom or a Disk, clean the hash data
if (item.Type == ItemType.Rom)
{
Rom itemRom = (Rom)item;
// Sanitize the hashes from null, hex sizes, and "true blank" strings
itemRom.CRC = Style.CleanHashData(itemRom.CRC, Constants.CRCLength);
itemRom.MD5 = Style.CleanHashData(itemRom.MD5, Constants.MD5Length);
itemRom.SHA1 = Style.CleanHashData(itemRom.SHA1, Constants.SHA1Length);
// If we have a rom and it's missing size AND the hashes match a 0-byte file, fill in the rest of the info
if ((itemRom.Size == 0 || itemRom.Size == -1)
&& ((itemRom.CRC == Constants.CRCZero || String.IsNullOrEmpty(itemRom.CRC))
|| itemRom.MD5 == Constants.MD5Zero
|| itemRom.SHA1 == Constants.SHA1Zero))
{
2016-09-22 18:15:02 -07:00
itemRom.Size = Constants.SizeZero;
itemRom.CRC = Constants.CRCZero;
itemRom.MD5 = Constants.MD5Zero;
itemRom.SHA1 = Constants.SHA1Zero;
}
// If the file has no size and it's not the above case, skip and log
else if (itemRom.ItemStatus != ItemStatus.Nodump && (itemRom.Size == 0 || itemRom.Size == -1))
{
logger.Verbose("Incomplete entry for \"" + itemRom.Name + "\" will be output as nodump");
itemRom.ItemStatus = ItemStatus.Nodump;
}
// If the file has a size but aboslutely no hashes, skip and log
else if (itemRom.ItemStatus != ItemStatus.Nodump
&& itemRom.Size > 0
&& String.IsNullOrEmpty(itemRom.CRC)
&& String.IsNullOrEmpty(itemRom.MD5)
&& String.IsNullOrEmpty(itemRom.SHA1))
2016-09-22 18:15:02 -07:00
{
logger.Verbose("Incomplete entry for \"" + itemRom.Name + "\" will be output as nodump");
2016-09-22 18:15:02 -07:00
itemRom.ItemStatus = ItemStatus.Nodump;
}
item = itemRom;
}
else if (item.Type == ItemType.Disk)
{
Disk itemDisk = (Disk)item;
// Sanitize the hashes from null, hex sizes, and "true blank" strings
itemDisk.MD5 = Style.CleanHashData(itemDisk.MD5, Constants.MD5Length);
itemDisk.SHA1 = Style.CleanHashData(itemDisk.SHA1, Constants.SHA1Length);
// If the file has aboslutely no hashes, skip and log
if (itemDisk.ItemStatus != ItemStatus.Nodump
&& String.IsNullOrEmpty(itemDisk.MD5)
&& String.IsNullOrEmpty(itemDisk.SHA1))
{
logger.Verbose("Incomplete entry for \"" + itemDisk.Name + "\" will be output as nodump");
itemDisk.ItemStatus = ItemStatus.Nodump;
}
2016-09-22 18:15:02 -07:00
item = itemDisk;
}
// If the rom passes the filter, include it
if (filter.ItemPasses(item, logger))
2016-09-22 18:15:02 -07:00
{
// If we are in single game mode, rename all games
if (single)
{
item.Machine.Name = "!";
2016-09-22 18:15:02 -07:00
}
// If we are in NTFS trim mode, trim the game name
if (trim)
{
// Windows max name length is 260
int usableLength = 260 - item.Machine.Name.Length - root.Length;
2016-09-22 18:15:02 -07:00
if (item.Name.Length > usableLength)
{
2016-09-22 18:15:02 -07:00
string ext = Path.GetExtension(item.Name);
item.Name = item.Name.Substring(0, usableLength - ext.Length);
item.Name += ext;
}
}
2016-09-22 18:15:02 -07:00
lock (Files)
{
2016-09-22 18:15:02 -07:00
// Get the key and add statistical data
switch (item.Type)
2016-05-23 14:15:09 -07:00
{
2016-09-22 18:15:02 -07:00
case ItemType.Archive:
case ItemType.BiosSet:
case ItemType.Release:
case ItemType.Sample:
key = item.Type.ToString();
break;
case ItemType.Disk:
key = ((Disk)item).MD5;
// Add statistical data
DiskCount += 1;
TotalSize += 0;
MD5Count += (String.IsNullOrEmpty(((Disk)item).MD5) ? 0 : 1);
SHA1Count += (String.IsNullOrEmpty(((Disk)item).SHA1) ? 0 : 1);
BaddumpCount += (((Disk)item).ItemStatus == ItemStatus.BadDump ? 1 : 0);
2016-09-22 18:15:02 -07:00
NodumpCount += (((Disk)item).ItemStatus == ItemStatus.Nodump ? 1 : 0);
break;
case ItemType.Rom:
key = ((Rom)item).Size + "-" + ((Rom)item).CRC;
// Add statistical data
RomCount += 1;
TotalSize += (((Rom)item).ItemStatus == ItemStatus.Nodump ? 0 : ((Rom)item).Size);
CRCCount += (String.IsNullOrEmpty(((Rom)item).CRC) ? 0 : 1);
MD5Count += (String.IsNullOrEmpty(((Rom)item).MD5) ? 0 : 1);
SHA1Count += (String.IsNullOrEmpty(((Rom)item).SHA1) ? 0 : 1);
BaddumpCount += (((Rom)item).ItemStatus == ItemStatus.BadDump ? 1 : 0);
2016-09-22 18:15:02 -07:00
NodumpCount += (((Rom)item).ItemStatus == ItemStatus.Nodump ? 1 : 0);
break;
default:
key = "default";
break;
2016-05-23 14:15:09 -07:00
}
2016-09-22 18:15:02 -07:00
// Add the item to the DAT
if (Files.ContainsKey(key))
{
Files[key].Add(item);
}
else
{
List<DatItem> newvalue = new List<DatItem>();
newvalue.Add(item);
Files.Add(key, newvalue);
}
}
}
2016-05-23 14:15:09 -07:00
}
2016-08-29 17:28:08 -07:00
#endregion
2016-10-24 16:14:22 -07:00
#region Populate DAT from Directory
2016-08-29 17:28:08 -07:00
/// <summary>
2016-10-24 16:14:22 -07:00
/// Create a new Dat from a directory
2016-09-12 15:29:07 -07:00
/// </summary>
2016-10-24 16:14:22 -07:00
/// <param name="basePath">Base folder to be used in creating the DAT</param>
/// <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>
/// <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>
/// <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>
/// <param name="tempDir">Name of the directory to create a temp folder in (blank is current directory)</param>
/// <param name="outDir">Output directory to </param>
/// <param name="copyFiles">True if files should be copied to the temp directory before hashing, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="maxDegreeOfParallelism">Integer representing the maximum amount of parallelization to be used</param>
/// <param name="logger">Logger object for console and file output</param>
public bool PopulateDatFromDir(string basePath, bool noMD5, bool noSHA1, bool bare, bool archivesAsFiles,
bool enableGzip, bool addBlanks, bool addDate, string tempDir, bool copyFiles, string headerToCheckAgainst,
int maxDegreeOfParallelism, Logger logger)
2016-09-12 15:29:07 -07:00
{
2016-10-24 16:14:22 -07:00
// If the description is defined but not the name, set the name from the description
if (String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
Name = Description;
}
2016-10-24 16:14:22 -07:00
// If the name is defined but not the description, set the description from the name
else if (!String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
Description = Name + (bare ? "" : " (" + Date + ")");
}
2016-10-24 16:14:22 -07:00
// If neither the name or description are defined, set them from the automatic values
else if (String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
2016-10-24 16:14:22 -07:00
Name = basePath.Split(Path.DirectorySeparatorChar).Last();
Description = Name + (bare ? "" : " (" + Date + ")");
}
2016-10-24 16:14:22 -07:00
// Process the input
if (Directory.Exists(basePath))
{
2016-10-24 16:14:22 -07:00
logger.Verbose("Folder found: " + basePath);
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
// Process the files in all subfolders
List<string> files = Directory.EnumerateFiles(basePath, "*", SearchOption.AllDirectories).ToList();
Parallel.ForEach(files,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
item =>
2016-09-12 15:29:07 -07:00
{
2016-10-24 16:14:22 -07:00
DFDProcessPossibleArchive(item, basePath, noMD5, noSHA1, bare, archivesAsFiles, enableGzip, addBlanks, addDate,
tempDir, copyFiles, headerToCheckAgainst, maxDegreeOfParallelism, logger);
});
2016-09-12 15:29:07 -07:00
2016-10-24 16:14:22 -07:00
// Now find all folders that are empty, if we are supposed to
if (!Romba && addBlanks)
{
List<string> empties = Directory.EnumerateDirectories(basePath, "*", SearchOption.AllDirectories).ToList();
Parallel.ForEach(empties,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
dir =>
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
if (Directory.EnumerateFiles(dir, "*", SearchOption.TopDirectoryOnly).Count() == 0)
{
2016-10-24 16:14:22 -07:00
// Get the full path for the directory
string fulldir = Path.GetFullPath(dir);
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
// Set the temporary variables
string gamename = "";
string romname = "";
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
// If we have a SuperDAT, we want anything that's not the base path as the game, and the file as the rom
if (Type == "SuperDAT")
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
gamename = fulldir.Remove(0, basePath.Length + 1);
romname = "-";
2016-09-22 18:15:02 -07:00
}
2016-10-24 16:14:22 -07:00
// Otherwise, we want just the top level folder as the game, and the file as everything else
else
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
gamename = fulldir.Remove(0, basePath.Length + 1).Split(Path.DirectorySeparatorChar)[0];
romname = Path.Combine(fulldir.Remove(0, basePath.Length + 1 + gamename.Length), "-");
2016-09-22 18:15:02 -07:00
}
2016-10-24 16:14:22 -07:00
// Sanitize the names
if (gamename.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
gamename = gamename.Substring(1);
}
if (gamename.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
gamename = gamename.Substring(0, gamename.Length - 1);
}
if (romname.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
romname = romname.Substring(1);
}
if (romname.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
romname = romname.Substring(0, romname.Length - 1);
}
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
logger.Verbose("Adding blank empty folder: " + gamename);
Files["null"].Add(new Rom(romname, gamename));
}
});
2016-09-22 18:15:02 -07:00
}
}
2016-10-24 16:14:22 -07:00
else if (File.Exists(basePath))
2016-09-22 18:15:02 -07:00
{
DFDProcessPossibleArchive(basePath, Path.GetDirectoryName(Path.GetDirectoryName(basePath)), noMD5, noSHA1, bare, archivesAsFiles, enableGzip, addBlanks, addDate,
2016-10-24 16:14:22 -07:00
tempDir, copyFiles, headerToCheckAgainst, maxDegreeOfParallelism, logger);
}
2016-10-24 16:14:22 -07:00
// Now that we're done, delete the temp folder (if it's not the default)
logger.User("Cleaning temp folder");
try
{
2016-10-24 16:14:22 -07:00
if (tempDir != Path.GetTempPath())
{
2016-10-24 16:14:22 -07:00
Directory.Delete(tempDir, true);
}
}
2016-10-24 16:14:22 -07:00
catch
{
2016-10-24 16:14:22 -07:00
// Just absorb the error for now
}
return true;
}
/// <summary>
2016-10-24 16:14:22 -07:00
/// Check a given file for hashes, based on current settings
/// </summary>
2016-10-24 16:14:22 -07:00
/// <param name="item">Filename of the item to be checked</param>
/// <param name="basePath">Base folder to be used in creating the DAT</param>
/// <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>
/// <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>
/// <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>
/// <param name="tempDir">Name of the directory to create a temp folder in (blank is current directory)</param>
/// <param name="copyFiles">True if files should be copied to the temp directory before hashing, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="maxDegreeOfParallelism">Integer representing the maximum amount of parallelization to be used</param>
/// <param name="logger">Logger object for console and file output</param>
private void DFDProcessPossibleArchive(string item, string basePath, bool noMD5, bool noSHA1, bool bare, bool archivesAsFiles,
bool enableGzip, bool addBlanks, bool addDate, string tempDir, bool copyFiles, string headerToCheckAgainst,
int maxDegreeOfParallelism, Logger logger)
{
2016-10-24 16:14:22 -07:00
// Define the temporary directory
string tempSubDir = Path.GetFullPath(Path.Combine(tempDir, Path.GetRandomFileName())) + Path.DirectorySeparatorChar;
// Special case for if we are in Romba mode (all names are supposed to be SHA-1 hashes)
if (Romba)
{
2016-10-24 16:14:22 -07:00
Rom rom = ArchiveTools.GetTorrentGZFileInfo(item, logger);
// If the rom is valid, write it out
if (rom.Name != null)
{
2016-10-24 16:14:22 -07:00
// Add the list if it doesn't exist already
string key = rom.Size + "-" + rom.CRC;
2016-10-24 16:14:22 -07:00
lock (Files)
{
if (!Files.ContainsKey(key))
{
Files.Add(key, new List<DatItem>());
}
Files[key].Add(rom);
logger.User("File added: " + Path.GetFileNameWithoutExtension(item) + Environment.NewLine);
}
}
else
{
2016-10-24 16:14:22 -07:00
logger.User("File not added: " + Path.GetFileNameWithoutExtension(item) + Environment.NewLine);
return;
}
2016-09-12 15:29:07 -07:00
2016-10-24 16:14:22 -07:00
return;
}
2016-10-24 16:14:22 -07:00
// If we're copying files, copy it first and get the new filename
string newItem = item;
string newBasePath = basePath;
if (copyFiles)
{
2016-10-24 16:14:22 -07:00
newBasePath = Path.Combine(tempDir, Path.GetRandomFileName());
newItem = Path.GetFullPath(Path.Combine(newBasePath, Path.GetFullPath(item).Remove(0, basePath.Length + 1)));
Directory.CreateDirectory(Path.GetDirectoryName(newItem));
File.Copy(item, newItem, true);
2016-09-22 18:15:02 -07:00
}
2016-09-12 15:29:07 -07:00
2016-10-24 16:14:22 -07:00
// If both deep hash skip flags are set, do a quickscan
if (noMD5 && noSHA1)
{
2016-10-24 16:14:22 -07:00
ArchiveType? type = ArchiveTools.GetCurrentArchiveType(newItem, logger);
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
// If we have an archive, scan it
if (type != null && !archivesAsFiles)
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
List<Rom> extracted = ArchiveTools.GetArchiveFileInfo(newItem, logger);
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
foreach (Rom rom in extracted)
{
DFDProcessFileHelper(newItem,
rom,
basePath,
(Path.GetDirectoryName(Path.GetFullPath(item)) + Path.DirectorySeparatorChar).Remove(0, basePath.Length) + Path.GetFileNameWithoutExtension(item),
logger);
}
}
// Otherwise, just get the info on the file itself
else if (File.Exists(newItem))
{
DFDProcessFile(newItem, "", newBasePath, noMD5, noSHA1, addDate, headerToCheckAgainst, logger);
}
}
// Otherwise, attempt to extract the files to the temporary directory
else
{
ArchiveScanLevel asl = (archivesAsFiles ? ArchiveScanLevel.SevenZipExternal : ArchiveScanLevel.SevenZipInternal)
| (!archivesAsFiles && enableGzip ? ArchiveScanLevel.GZipInternal : ArchiveScanLevel.GZipExternal)
| (archivesAsFiles ? ArchiveScanLevel.RarExternal : ArchiveScanLevel.RarInternal)
| (archivesAsFiles ? ArchiveScanLevel.ZipExternal : ArchiveScanLevel.ZipInternal);
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
bool encounteredErrors = ArchiveTools.ExtractArchive(newItem, tempSubDir, asl, logger);
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
// If the file was an archive and was extracted successfully, check it
if (!encounteredErrors)
{
logger.Verbose(Path.GetFileName(item) + " treated like an archive");
List<string> extracted = Directory.EnumerateFiles(tempSubDir, "*", SearchOption.AllDirectories).ToList();
Parallel.ForEach(extracted,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
entry =>
{
DFDProcessFile(entry,
Path.Combine((Type == "SuperDAT"
? (Path.GetDirectoryName(Path.GetFullPath(item)) + Path.DirectorySeparatorChar).Remove(0, basePath.Length)
: ""),
Path.GetFileNameWithoutExtension(item)),
tempSubDir,
noMD5,
noSHA1,
addDate,
headerToCheckAgainst,
logger);
});
}
// Otherwise, just get the info on the file itself
else if (File.Exists(newItem))
{
DFDProcessFile(newItem, "", newBasePath, noMD5, noSHA1, addDate, headerToCheckAgainst, logger);
2016-09-22 18:15:02 -07:00
}
2016-10-24 16:14:22 -07:00
}
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
// Cue to delete the file if it's a copy
if (copyFiles && item != newItem)
{
try
{
Directory.Delete(newBasePath, true);
}
catch { }
}
2016-10-24 16:14:22 -07:00
// Delete the sub temp directory
if (Directory.Exists(tempSubDir))
{
2016-10-24 16:14:22 -07:00
Directory.Delete(tempSubDir, true);
}
2016-10-24 16:14:22 -07:00
}
2016-09-12 15:29:07 -07:00
2016-10-24 16:14:22 -07:00
/// <summary>
/// Process a single file as a file
/// </summary>
/// <param name="item">File to be added</param>
/// <param name="parent">Parent game to be used</param>
/// <param name="basePath">Path the represents the parent directory</param>
/// <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>
/// <param name="addDate">True if dates should be archived for all files, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="logger">Logger object for console and file output</param>
private void DFDProcessFile(string item, string parent, string basePath, bool noMD5, bool noSHA1, bool addDate, string headerToCheckAgainst, Logger logger)
{
logger.Verbose(Path.GetFileName(item) + " treated like a file");
Rom rom = FileTools.GetFileInfo(item, logger, noMD5: noMD5, noSHA1: noSHA1, date: addDate, header: headerToCheckAgainst);
DFDProcessFileHelper(item, rom, basePath, parent, logger);
2016-09-22 18:15:02 -07:00
}
/// <summary>
2016-10-24 16:14:22 -07:00
/// Process a single file as a file (with found Rom data)
2016-09-22 18:15:02 -07:00
/// </summary>
2016-10-24 16:14:22 -07:00
/// <param name="item">File to be added</param>
/// <param name="item">Rom data to be used to write to file</param>
/// <param name="basepath">Path the represents the parent directory</param>
/// <param name="parent">Parent game to be used</param>
private void DFDProcessFileHelper(string item, DatItem datItem, string basepath, string parent, Logger logger)
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
// If the datItem isn't a Rom or Disk, return
if (datItem.Type != ItemType.Rom && datItem.Type != ItemType.Disk)
{
2016-10-24 16:14:22 -07:00
return;
}
2016-10-24 16:14:22 -07:00
string key = "";
if (datItem.Type == ItemType.Rom)
{
key = ((Rom)datItem).Size + "-" + ((Rom)datItem).CRC;
}
2016-10-24 16:14:22 -07:00
else
{
2016-10-24 16:14:22 -07:00
key = ((Disk)datItem).MD5;
}
2016-10-24 16:14:22 -07:00
// Add the list if it doesn't exist already
lock (Files)
{
if (!Files.ContainsKey(key))
{
Files.Add(key, new List<DatItem>());
}
}
2016-09-22 18:15:02 -07:00
try
{
2016-10-24 16:14:22 -07:00
// If the basepath ends with a directory separator, remove it
if (!basepath.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
basepath += Path.DirectorySeparatorChar.ToString();
}
// Make sure we have the full item path
item = Path.GetFullPath(item);
// Get the data to be added as game and item names
string gamename = "";
string romname = "";
// If the parent is blank, then we have a non-archive file
if (parent == "")
{
// If we have a SuperDAT, we want anything that's not the base path as the game, and the file as the rom
if (Type == "SuperDAT")
{
gamename = Path.GetDirectoryName(item.Remove(0, basepath.Length));
romname = Path.GetFileName(item);
}
2016-10-24 16:14:22 -07:00
// Otherwise, we want just the top level folder as the game, and the file as everything else
else
{
gamename = item.Remove(0, basepath.Length).Split(Path.DirectorySeparatorChar)[0];
romname = item.Remove(0, (Path.Combine(basepath, gamename).Length));
}
}
// Otherwise, we assume that we have an archive
else
2016-09-12 15:29:07 -07:00
{
2016-10-24 16:14:22 -07:00
// If we have a SuperDAT, we want the archive name as the game, and the file as everything else (?)
if (Type == "SuperDAT")
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
gamename = parent;
romname = item.Remove(0, basepath.Length);
}
// Otherwise, we want the archive name as the game, and the file as everything else
else
{
gamename = parent;
romname = item.Remove(0, basepath.Length);
2016-09-22 18:15:02 -07:00
}
2016-09-12 15:29:07 -07:00
}
2016-10-24 16:14:22 -07:00
// Sanitize the names
if (gamename.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
gamename = gamename.Substring(1);
}
if (gamename.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
gamename = gamename.Substring(0, gamename.Length - 1);
}
if (romname.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
romname = romname.Substring(1);
}
if (romname.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
romname = romname.Substring(0, romname.Length - 1);
}
// Update rom information
datItem.Name = romname;
if (datItem.Machine == null)
{
datItem.Machine = new Machine
{
Name = gamename,
Description = gamename,
};
}
2016-09-22 18:15:02 -07:00
else
{
2016-10-24 16:14:22 -07:00
datItem.Machine.Name = gamename;
datItem.Machine.Description = gamename;
}
// Add the file information to the DAT
lock (Files)
{
if (Files.ContainsKey(key))
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
Files[key].Add(datItem);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(datItem);
Files.Add(key, temp);
2016-09-22 18:15:02 -07:00
}
}
2016-09-18 22:52:59 -07:00
2016-10-24 16:14:22 -07:00
logger.User("File added: " + romname + Environment.NewLine);
2016-09-22 18:15:02 -07:00
}
2016-10-24 16:14:22 -07:00
catch (IOException ex)
2016-09-18 22:52:59 -07:00
{
2016-09-22 18:15:02 -07:00
logger.Error(ex.ToString());
2016-10-24 16:14:22 -07:00
return;
2016-09-18 22:52:59 -07:00
}
}
#endregion
#region Rebuilding and Verifying
/// <summary>
/// Process the DAT and find all matches in input files and folders
/// </summary>
/// <param name="inputs">List of input files/folders to check</param>
/// <param name="outDir">Output directory to use to build to</param>
/// <param name="tempDir">Temporary directory for archive extraction</param>
/// <param name="quickScan">True to enable external scanning of archives, false otherwise</param>
/// <param name="date">True if the date from the DAT should be used if available, false otherwise</param>
/// <param name="delete">True if input files should be deleted, false otherwise</param>
/// <param name="inverse">True if the DAT should be used as a filter instead of a template, false otherwise</param>
/// <param name="outputFormat">Output format that files should be written to</param>
/// <param name="romba">True if files should be output in Romba depot folders, false otherwise</param>
/// <param name="archiveScanLevel">ArchiveScanLevel representing the archive handling levels</param>
/// <param name="updateDat">True if the updated DAT should be output, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="logger">Logger object for file and console output</param>
/// <returns>True if rebuilding was a success, false otherwise</returns>
public bool RebuildToOutput(List<string> inputs, string outDir, string tempDir, bool quickScan, bool date,
bool delete, bool inverse, OutputFormat outputFormat, bool romba, ArchiveScanLevel archiveScanLevel, bool updateDat,
string headerToCheckAgainst, int maxDegreeOfParallelism, Logger logger)
{
#region Perform setup
// If the DAT is not populated and inverse is not set, inform the user and quit
if ((Files == null || Files.Count == 0) && !inverse)
{
logger.User("No entries were found to rebuild, exiting...");
return false;
}
// Check that the output directory exists
if (!Directory.Exists(outDir))
{
Directory.CreateDirectory(outDir);
outDir = Path.GetFullPath(outDir);
}
// Check the temp directory
if (String.IsNullOrEmpty(tempDir))
{
tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
}
// Then create or clean the temp directory
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
else
{
FileTools.CleanDirectory(tempDir);
}
// Preload the Skipper list
int listcount = Skipper.List.Count;
#endregion
bool success = true;
DatFile matched = new DatFile();
List<string> files = new List<string>();
#region Retrieve a list of all files
logger.User("Retrieving list all files from input");
DateTime start = DateTime.Now;
// Create a list of just files from inputs
Parallel.ForEach(inputs,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism, },
input => {
if (File.Exists(input))
{
logger.Verbose("File found: '" + input + "'");
files.Add(Path.GetFullPath(input));
}
else if (Directory.Exists(input))
{
logger.Verbose("Directory found: '" + input + "'");
Parallel.ForEach(Directory.EnumerateFiles(input, "*", SearchOption.AllDirectories),
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism, },
file =>
{
logger.Verbose("File found: '" + file + "'");
files.Add(Path.GetFullPath(file));
});
}
else
{
logger.Error("'" + input + "' is not a file or directory!");
}
});
logger.User("Retrieving complete in: " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
#endregion
DatFile current = new DatFile();
Dictionary<string, SkipperRule> fileToSkipperRule = new Dictionary<string, SkipperRule>();
#region Create a dat from input files
logger.User("Getting hash information for all input files");
start = DateTime.Now;
// Now that we have a list of just files, we get a DAT from the input files
Parallel.ForEach(files,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
file =>
{
// Define the temporary directory
string tempSubDir = Path.GetFullPath(Path.Combine(tempDir, Path.GetRandomFileName())) + Path.DirectorySeparatorChar;
// Get the required scanning level for the file
bool shouldExternalProcess = false;
bool shouldInternalProcess = false;
ArchiveTools.GetInternalExternalProcess(file, archiveScanLevel, logger, out shouldExternalProcess, out shouldInternalProcess);
// If we're supposed to scan the file externally
if (shouldExternalProcess)
{
Rom rom = FileTools.GetFileInfo(file, logger, noMD5: quickScan, noSHA1: quickScan, header: headerToCheckAgainst);
rom.Name = Path.GetFullPath(file);
lock (Files)
{
string key = rom.Size + "-" + rom.CRC;
if (current.Files.ContainsKey(key))
{
current.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
current.Files.Add(key, temp);
}
}
// If we had a header, we want the full file information too
if (headerToCheckAgainst != null)
{
rom = FileTools.GetFileInfo(file, logger, noMD5: quickScan, noSHA1: quickScan);
rom.Name = Path.GetFullPath(file);
lock (Files)
{
string key = rom.Size + "-" + rom.CRC;
if (current.Files.ContainsKey(key))
{
current.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
current.Files.Add(key, temp);
}
}
}
}
// If we're supposed to scan the file internally
if (shouldInternalProcess)
{
// If quickscan is set, do so
if (quickScan)
{
List<Rom> extracted = ArchiveTools.GetArchiveFileInfo(file, logger);
foreach (Rom rom in extracted)
{
Rom newrom = rom;
newrom.Machine = new Machine(Path.GetFullPath(file), "");
lock (Files)
{
string key = rom.Size + "-" + rom.CRC;
if (current.Files.ContainsKey(key))
{
current.Files[key].Add(newrom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(newrom);
current.Files.Add(key, temp);
}
}
}
}
// Otherwise, attempt to extract the files to the temporary directory
else
{
bool encounteredErrors = ArchiveTools.ExtractArchive(file, tempSubDir, archiveScanLevel, logger);
// If the file was an archive and was extracted successfully, check it
if (!encounteredErrors)
{
logger.Verbose(Path.GetFileName(file) + " treated like an archive");
List<string> extracted = Directory.EnumerateFiles(tempSubDir, "*", SearchOption.AllDirectories).ToList();
Parallel.ForEach(extracted,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
entry =>
{
Rom rom = FileTools.GetFileInfo(entry, logger, noMD5: quickScan, noSHA1: quickScan, header: headerToCheckAgainst);
rom.Machine = new Machine(Path.GetFullPath(file), "");
lock (Files)
{
string key = rom.Size + "-" + rom.CRC;
if (current.Files.ContainsKey(key))
{
current.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
current.Files.Add(key, temp);
}
}
// If we had a header, we want the full file information too
if (headerToCheckAgainst != null)
{
rom = FileTools.GetFileInfo(file, logger, noMD5: quickScan, noSHA1: quickScan);
rom.Machine = new Machine(Path.GetFullPath(file), "");
lock (Files)
{
string key = rom.Size + "-" + rom.CRC;
if (current.Files.ContainsKey(key))
{
current.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
current.Files.Add(key, temp);
}
}
}
});
}
// Otherwise, just get the info on the file itself
else if (File.Exists(file))
{
Rom rom = FileTools.GetFileInfo(file, logger, noMD5: quickScan, noSHA1: quickScan, header: headerToCheckAgainst);
rom.Name = Path.GetFullPath(file);
lock (Files)
{
string key = rom.Size + "-" + rom.CRC;
if (current.Files.ContainsKey(key))
{
current.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
current.Files.Add(key, temp);
}
}
}
}
}
// Now delete the temp directory
try
{
Directory.Delete(tempSubDir, true);
}
catch { }
});
logger.User("Getting hash information complete in: " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
#endregion
// Create a mapping from destination file to source file
Dictionary<DatItem, DatItem> toFromMap = new Dictionary<DatItem, DatItem>();
#region Find all required files for rebuild
logger.User("Determining files to rebuild");
start = DateTime.Now;
// Order the DATs by hash first to make things easier
logger.User("Sorting input DAT...");
BucketByCRC(false, logger, output: false);
logger.User("Sorting found files...");
current.BucketByCRC(false, logger, output: false);
// Now loop over and find all files that need to be rebuilt
List<string> keys = current.Files.Keys.ToList();
Parallel.ForEach(keys,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
key =>
{
// If we are using the DAT as a filter, treat the files one way
if (inverse)
{
// Check for duplicates
List<DatItem> datItems = current.Files[key];
foreach (Rom rom in datItems)
{
// If the rom has duplicates, we skip it
if (rom.HasDuplicates(this, logger))
{
return;
}
// Otherwise, map the file to itself
try
{
Rom newrom = new Rom
{
Name = rom.Name.Remove(0, Path.GetDirectoryName(rom.Name).Length),
Size = rom.Size,
CRC = rom.CRC,
MD5 = rom.MD5,
SHA1 = rom.SHA1,
Machine = new Machine
{
Name = Path.GetFileNameWithoutExtension(rom.Machine.Name),
},
};
newrom.Name = newrom.Name.Remove(0, (newrom.Name.StartsWith("\\") || newrom.Name.StartsWith("/") ? 1 : 0));
lock (toFromMap)
{
toFromMap.Add(newrom, rom);
}
}
catch { }
}
}
// Otherwise, treat it like a standard rebuild
else
{
// If the input DAT doesn't have the key, then nothing from the current DAT are there
if (!Files.ContainsKey(key))
{
return;
}
// Otherwise, we try to find duplicates
List<DatItem> datItems = current.Files[key];
foreach (Rom rom in datItems)
{
List<DatItem> found = rom.GetDuplicates(this, logger, false);
// Now add all of the duplicates mapped to the current file
foreach (Rom mid in found)
{
try
{
lock (toFromMap)
{
toFromMap.Add(mid, rom);
}
}
catch { }
}
}
}
});
logger.User("Determining complete in: " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
#endregion
// Now bucket the list of keys by game so that we can rebuild properly
SortedDictionary<string, List<DatItem>> keysGroupedByGame = BucketListByGame(toFromMap.Keys.ToList(), false, true, logger, output: false);
#region Rebuild games in order
switch (outputFormat)
{
case OutputFormat.Folder:
logger.User("Rebuilding all files to directory");
break;
case OutputFormat.TapeArchive:
logger.User("Rebuilding all files to TAR");
break;
case OutputFormat.Torrent7Zip:
logger.User("Rebuilding all files to Torrent7Z");
break;
case OutputFormat.TorrentGzip:
logger.User("Rebuilding all files to TorrentGZ");
break;
case OutputFormat.TorrentLrzip:
logger.User("Rebuilding all files to TorrentLRZ");
break;
case OutputFormat.TorrentRar:
logger.User("Rebuilding all files to TorrentRAR");
break;
case OutputFormat.TorrentXZ:
logger.User("Rebuilding all files to TorrentXZ");
break;
case OutputFormat.TorrentZip:
logger.User("Rebuilding all files to TorrentZip");
break;
}
start = DateTime.Now;
// Now loop through the keys and create the correct output items
List<string> games = keysGroupedByGame.Keys.ToList();
Parallel.ForEach(games,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
game =>
{
// Define the temporary directory
string tempSubDir = Path.GetFullPath(Path.Combine(tempDir, Path.GetRandomFileName())) + Path.DirectorySeparatorChar;
// Create an empty list for getting paths for rebuilding
List<string> pathsToFiles = new List<string>();
// Loop through all of the matched items in the game
List<DatItem> itemsInGame = keysGroupedByGame[game];
List<Rom> romsInGame = new List<Rom>();
foreach (Rom rom in itemsInGame)
{
// Get the rom that's mapped to this item
Rom source = (Rom)toFromMap[rom];
// If the file is in an archive, we need to treat it specially
string machinename = source.Machine.Name.ToLowerInvariant();
if (machinename.EndsWith(".7z")
|| machinename.EndsWith(".gz")
|| machinename.EndsWith(".rar")
|| machinename.EndsWith(".zip"))
{
string tempPath = ArchiveTools.ExtractItem(source.Machine.Name, Path.GetFileName(source.Name), tempSubDir, logger);
pathsToFiles.Add(tempPath);
}
// Otherwise, we want to just add the full path
else
{
pathsToFiles.Add(source.Name);
}
// If the size doesn't match, then we add the CRC as a postfix to the file
Rom fi = FileTools.GetFileInfo(pathsToFiles.Last(), logger);
if (fi.Size != source.Size)
{
rom.Name = Path.GetDirectoryName(rom.Name)
+ (String.IsNullOrEmpty(Path.GetDirectoryName(rom.Name)) ? "" : Path.DirectorySeparatorChar.ToString())
+ Path.GetFileNameWithoutExtension(rom.Name)
+ " (" + fi.CRC + ")"
+ Path.GetExtension(rom.Name);
rom.CRC = fi.CRC;
rom.Size = fi.Size;
}
// Now add the rom to the output list
romsInGame.Add(rom);
}
// And now rebuild accordingly
switch (outputFormat)
{
case OutputFormat.Folder:
for (int i = 0; i < romsInGame.Count; i++)
{
string infile = pathsToFiles[i];
Rom outrom = romsInGame[i];
2016-10-25 16:34:50 -07:00
string outfile = Path.Combine(outDir, outrom.Machine.Name, outrom.Name);
// Make sure the output folder is created
Directory.CreateDirectory(Path.GetDirectoryName(outfile));
// Now copy the file over
try
{
File.Copy(infile, outfile);
}
catch { }
}
break;
case OutputFormat.TapeArchive:
2016-10-25 21:20:43 -07:00
ArchiveTools.WriteTAR(pathsToFiles, outDir, romsInGame, logger);
break;
case OutputFormat.Torrent7Zip:
break;
case OutputFormat.TorrentGzip:
for (int i = 0; i < itemsInGame.Count; i++)
{
string infile = pathsToFiles[i];
Rom outrom = romsInGame[i];
ArchiveTools.WriteTorrentGZ(infile, outDir, romba, logger);
}
break;
case OutputFormat.TorrentLrzip:
break;
case OutputFormat.TorrentRar:
break;
case OutputFormat.TorrentXZ:
break;
case OutputFormat.TorrentZip:
ArchiveTools.WriteTorrentZip(pathsToFiles, outDir, romsInGame, logger);
break;
}
// And now clear the temp folder to get rid of any transient files
try
{
Directory.Delete(tempSubDir, true);
}
catch { }
});
logger.User("Rebuilding complete in: " + DateTime.Now.Subtract(start).ToString(@"hh\:mm\:ss\.fffff"));
#endregion
return success;
}
/// <summary>
/// Process the DAT and verify the output directory
/// </summary>
/// <param name="datFile">DAT to use to verify the directory</param>
/// <param name="inputs">List of input directories to compare against</param>
/// <param name="tempDir">Temporary directory for archive extraction</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="logger">Logger object for file and console output</param>
/// <returns>True if verification was a success, false otherwise</returns>
public bool VerifyDirectory(List<string> inputs, string tempDir, string headerToCheckAgainst, Logger logger)
{
// First create or clean the temp directory
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
else
{
FileTools.CleanDirectory(tempDir);
}
bool success = true;
/*
We want the cross section of what's the folder and what's in the DAT. Right now, it just has what's in the DAT that's not in the folder
*/
// Then, loop through and check each of the inputs
logger.User("Processing files:\n");
foreach (string input in inputs)
{
PopulateDatFromDir(input, false /* noMD5 */, false /* noSHA1 */, true /* bare */, false /* archivesAsFiles */,
true /* enableGzip */, false /* addBlanks */, false /* addDate */, tempDir /* tempDir */, false /* copyFiles */,
headerToCheckAgainst, 4 /* maxDegreeOfParallelism */, logger);
}
// Setup the fixdat
DatFile matched = (DatFile)CloneHeader();
matched.Files = new SortedDictionary<string, List<DatItem>>();
matched.FileName = "fixDat_" + matched.FileName;
matched.Name = "fixDat_" + matched.Name;
matched.Description = "fixDat_" + matched.Description;
2016-10-25 15:02:02 -07:00
matched.DatFormat = DatFormat.Logiqx;
// Now that all files are parsed, get only files found in directory
bool found = false;
foreach (List<DatItem> roms in Files.Values)
{
List<DatItem> newroms = DatItem.Merge(roms, logger);
foreach (Rom rom in newroms)
{
if (rom.SourceID == 99)
{
found = true;
string key = rom.Size + "-" + rom.CRC;
if (matched.Files.ContainsKey(key))
{
matched.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
matched.Files.Add(key, temp);
}
}
}
}
// Now output the fixdat to the main folder
if (found)
{
matched.WriteToFile("", logger, stats: true);
}
else
{
logger.User("No fixDat needed");
}
return success;
}
#endregion
2016-10-24 16:14:22 -07:00
#region Splitting
/// <summary>
2016-10-24 16:14:22 -07:00
/// Split a DAT by input extensions
/// </summary>
2016-10-24 16:14:22 -07:00
/// <param name="outDir">Name of the directory to write the DATs out to</param>
/// <param name="basepath">Parent path for replacement</param>
/// <param name="extA">List of extensions to split on (first DAT)</param>
/// <param name="extB">List of extensions to split on (second DAT)</param>
/// <param name="logger">Logger object for console and file writing</param>
/// <returns>True if split succeeded, false otherwise</returns>
public bool SplitByExt(string outDir, string basepath, List<string> extA, List<string> extB, Logger logger)
{
2016-10-24 16:14:22 -07:00
// Make sure all of the extensions have a dot at the beginning
List<string> newExtA = new List<string>();
foreach (string s in extA)
{
2016-10-24 16:14:22 -07:00
newExtA.Add((s.StartsWith(".") ? s : "." + s).ToUpperInvariant());
}
2016-10-24 16:14:22 -07:00
string newExtAString = string.Join(",", newExtA);
2016-10-24 16:14:22 -07:00
List<string> newExtB = new List<string>();
foreach (string s in extB)
{
2016-10-24 16:14:22 -07:00
newExtB.Add((s.StartsWith(".") ? s : "." + s).ToUpperInvariant());
}
2016-10-24 16:14:22 -07:00
string newExtBString = string.Join(",", newExtB);
2016-10-24 16:14:22 -07:00
// Set all of the appropriate outputs for each of the subsets
DatFile datdataA = new DatFile
{
2016-10-24 16:14:22 -07:00
FileName = this.FileName + " (" + newExtAString + ")",
Name = this.Name + " (" + newExtAString + ")",
Description = this.Description + " (" + newExtAString + ")",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Files = new SortedDictionary<string, List<DatItem>>(),
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
};
DatFile datdataB = new DatFile
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
FileName = this.FileName + " (" + newExtBString + ")",
Name = this.Name + " (" + newExtBString + ")",
Description = this.Description + " (" + newExtBString + ")",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Files = new SortedDictionary<string, List<DatItem>>(),
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
};
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
// If roms is empty, return false
if (this.Files.Count == 0)
{
2016-10-24 16:14:22 -07:00
return false;
}
2016-10-24 16:14:22 -07:00
// Now separate the roms accordingly
foreach (string key in this.Files.Keys)
{
2016-10-24 16:14:22 -07:00
foreach (DatItem rom in this.Files[key])
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
if (newExtA.Contains(Path.GetExtension(rom.Name.ToUpperInvariant())))
{
if (datdataA.Files.ContainsKey(key))
{
datdataA.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
datdataA.Files.Add(key, temp);
}
}
else if (newExtB.Contains(Path.GetExtension(rom.Name.ToUpperInvariant())))
{
if (datdataB.Files.ContainsKey(key))
{
datdataB.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
datdataB.Files.Add(key, temp);
}
}
else
{
if (datdataA.Files.ContainsKey(key))
{
datdataA.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
datdataA.Files.Add(key, temp);
}
if (datdataB.Files.ContainsKey(key))
{
datdataB.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
datdataB.Files.Add(key, temp);
}
}
2016-09-22 18:15:02 -07:00
}
}
2016-10-24 16:14:22 -07:00
// Get the output directory
if (outDir != "")
{
2016-10-24 16:14:22 -07:00
outDir = outDir + Path.GetDirectoryName(this.FileName).Remove(0, basepath.Length - 1);
}
else
{
outDir = Path.GetDirectoryName(this.FileName);
}
2016-10-24 16:14:22 -07:00
// Then write out both files
bool success = datdataA.WriteToFile(outDir, logger);
success &= datdataB.WriteToFile(outDir, logger);
return success;
}
/// <summary>
2016-10-24 16:14:22 -07:00
/// Split a DAT by best available hashes
/// </summary>
2016-10-24 16:14:22 -07:00
/// <param name="outDir">Name of the directory to write the DATs out to</param>
/// <param name="basepath">Parent path for replacement</param>
/// <param name="logger">Logger object for console and file writing</param>
/// <returns>True if split succeeded, false otherwise</returns>
public bool SplitByHash(string outDir, string basepath, Logger logger)
{
2016-10-24 16:14:22 -07:00
// Sanitize the basepath to be more predictable
basepath = (basepath.EndsWith(Path.DirectorySeparatorChar.ToString()) ? basepath : basepath + Path.DirectorySeparatorChar);
2016-10-24 16:14:22 -07:00
// Create each of the respective output DATs
logger.User("Creating and populating new DATs");
DatFile itemStatus = new DatFile
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
FileName = this.FileName + " (Nodump)",
Name = this.Name + " (Nodump)",
Description = this.Description + " (Nodump)",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Header = this.Header,
Type = this.Type,
ForceMerging = this.ForceMerging,
ForceNodump = this.ForceNodump,
ForcePacking = this.ForcePacking,
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
MergeRoms = this.MergeRoms,
Files = new SortedDictionary<string, List<DatItem>>(),
};
DatFile sha1 = new DatFile
{
FileName = this.FileName + " (SHA-1)",
Name = this.Name + " (SHA-1)",
Description = this.Description + " (SHA-1)",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Header = this.Header,
Type = this.Type,
ForceMerging = this.ForceMerging,
ForceNodump = this.ForceNodump,
ForcePacking = this.ForcePacking,
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
MergeRoms = this.MergeRoms,
Files = new SortedDictionary<string, List<DatItem>>(),
};
DatFile md5 = new DatFile
{
FileName = this.FileName + " (MD5)",
Name = this.Name + " (MD5)",
Description = this.Description + " (MD5)",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Header = this.Header,
Type = this.Type,
ForceMerging = this.ForceMerging,
ForceNodump = this.ForceNodump,
ForcePacking = this.ForcePacking,
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
MergeRoms = this.MergeRoms,
Files = new SortedDictionary<string, List<DatItem>>(),
};
DatFile crc = new DatFile
{
FileName = this.FileName + " (CRC)",
Name = this.Name + " (CRC)",
Description = this.Description + " (CRC)",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Header = this.Header,
Type = this.Type,
ForceMerging = this.ForceMerging,
ForceNodump = this.ForceNodump,
ForcePacking = this.ForcePacking,
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
MergeRoms = this.MergeRoms,
Files = new SortedDictionary<string, List<DatItem>>(),
};
2016-10-24 16:14:22 -07:00
DatFile other = new DatFile
{
FileName = this.FileName + " (Other)",
Name = this.Name + " (Other)",
Description = this.Description + " (Other)",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Header = this.Header,
Type = this.Type,
ForceMerging = this.ForceMerging,
ForceNodump = this.ForceNodump,
ForcePacking = this.ForcePacking,
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
MergeRoms = this.MergeRoms,
Files = new SortedDictionary<string, List<DatItem>>(),
};
// Now populate each of the DAT objects in turn
List<string> keys = this.Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = this.Files[key];
foreach (DatItem rom in roms)
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
// If the file is not a Rom or Disk, continue
if (rom.Type != ItemType.Disk && rom.Type != ItemType.Rom)
{
continue;
}
2016-10-24 16:14:22 -07:00
// If the file is a itemStatus
if ((rom.Type == ItemType.Rom && ((Rom)rom).ItemStatus == ItemStatus.Nodump)
|| (rom.Type == ItemType.Disk && ((Disk)rom).ItemStatus == ItemStatus.Nodump))
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
if (itemStatus.Files.ContainsKey(key))
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
itemStatus.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
itemStatus.Files.Add(key, temp);
2016-09-22 18:15:02 -07:00
}
}
2016-10-24 16:14:22 -07:00
// If the file has a SHA-1
else if ((rom.Type == ItemType.Rom && !String.IsNullOrEmpty(((Rom)rom).SHA1))
|| (rom.Type == ItemType.Disk && !String.IsNullOrEmpty(((Disk)rom).SHA1)))
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
if (sha1.Files.ContainsKey(key))
{
sha1.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
sha1.Files.Add(key, temp);
}
}
2016-10-24 16:14:22 -07:00
// If the file has no SHA-1 but has an MD5
else if ((rom.Type == ItemType.Rom && !String.IsNullOrEmpty(((Rom)rom).MD5))
|| (rom.Type == ItemType.Disk && !String.IsNullOrEmpty(((Disk)rom).MD5)))
{
if (md5.Files.ContainsKey(key))
{
md5.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
md5.Files.Add(key, temp);
}
}
// If the file has no MD5 but a CRC
else if ((rom.Type == ItemType.Rom && !String.IsNullOrEmpty(((Rom)rom).SHA1))
|| (rom.Type == ItemType.Disk && !String.IsNullOrEmpty(((Disk)rom).SHA1)))
{
if (crc.Files.ContainsKey(key))
{
crc.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
crc.Files.Add(key, temp);
}
}
else
{
if (other.Files.ContainsKey(key))
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
other.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
other.Files.Add(key, temp);
}
}
}
}
2016-10-24 16:14:22 -07:00
// Get the output directory
if (outDir != "")
{
2016-10-24 16:14:22 -07:00
outDir = outDir + Path.GetDirectoryName(this.FileName).Remove(0, basepath.Length - 1);
}
2016-10-24 16:14:22 -07:00
else
{
2016-10-24 16:14:22 -07:00
outDir = Path.GetDirectoryName(this.FileName);
}
2016-10-24 16:14:22 -07:00
// Now, output all of the files to the output directory
logger.User("DAT information created, outputting new files");
bool success = true;
if (itemStatus.Files.Count > 0)
{
2016-10-24 16:14:22 -07:00
success &= itemStatus.WriteToFile(outDir, logger);
}
2016-10-24 16:14:22 -07:00
if (sha1.Files.Count > 0)
{
2016-10-24 16:14:22 -07:00
success &= sha1.WriteToFile(outDir, logger);
}
2016-10-24 16:14:22 -07:00
if (md5.Files.Count > 0)
{
2016-10-24 16:14:22 -07:00
success &= md5.WriteToFile(outDir, logger);
}
2016-10-24 16:14:22 -07:00
if (crc.Files.Count > 0)
{
2016-10-24 16:14:22 -07:00
success &= crc.WriteToFile(outDir, logger);
}
2016-10-24 16:14:22 -07:00
return success;
}
/// <summary>
/// Split a SuperDAT by lowest available directory level
/// </summary>
/// <param name="outDir">Name of the directory to write the DATs out to</param>
/// <param name="basepath">Parent path for replacement</param>
/// <param name="logger">Logger object for console and file writing</param>
/// <returns>True if split succeeded, false otherwise</returns>
public bool SplitByLevel(string outDir, string basepath, Logger logger)
{
// Sanitize the basepath to be more predictable
basepath = (basepath.EndsWith(Path.DirectorySeparatorChar.ToString()) ? basepath : basepath + Path.DirectorySeparatorChar);
// First, organize by games so that we can do the right thing
BucketByGame(false, true, logger, output: false);
// Create a temporary DAT to add things to
DatFile tempDat = (DatFile)CloneHeader();
tempDat.Name = null;
// Then, we loop over the games
foreach (string key in Files.Keys)
{
// Here, the key is the name of the game to be used for comparison
if (tempDat.Name != null && tempDat.Name != Path.GetDirectoryName(key))
{
// Get the path that the file will be written out to
2016-10-26 14:59:12 -07:00
string path = HttpUtility.HtmlDecode(String.IsNullOrEmpty(tempDat.Name)
? outDir
: Path.Combine(outDir, tempDat.Name));
2016-10-26 14:59:12 -07:00
// Now set the new output values
2016-10-26 16:51:46 -07:00
tempDat.FileName = HttpUtility.HtmlDecode(String.IsNullOrEmpty(tempDat.Name)
? FileName
: tempDat.Name.Replace(Path.DirectorySeparatorChar.ToString(), " - ").Replace(Path.AltDirectorySeparatorChar.ToString(), " - "));
tempDat.Description += " (" + tempDat.Name.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-') + ")";
tempDat.Name = Name + " (" + tempDat.Name.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-') + ")";
2016-10-26 14:59:12 -07:00
tempDat.Type = null;
// Write out the temporary DAT to the proper directory
2016-10-26 14:59:12 -07:00
tempDat.WriteToFile(path, logger);
// Reset the DAT for the next items
tempDat = (DatFile)CloneHeader();
tempDat.Name = null;
}
// Clean the input list and set all games to be pathless
List<DatItem> items = Files[key];
items.ForEach(item => item.Machine.Name = Path.GetFileName(item.Machine.Name));
2016-10-26 14:59:12 -07:00
items.ForEach(item => item.Machine.Description = Path.GetFileName(item.Machine.Description));
// Now add the game to the output DAT
if (tempDat.Files.ContainsKey(key))
{
tempDat.Files[key].AddRange(items);
}
else
{
tempDat.Files.Add(key, items);
}
// Then set the DAT name to be the parent directory name
tempDat.Name = Path.GetDirectoryName(key);
}
// Then we write the last DAT out since it would be skipped otherwise
string lastpath = HttpUtility.HtmlDecode(String.IsNullOrEmpty(tempDat.Name)
? outDir
: Path.Combine(outDir, tempDat.Name));
// Now set the new output values
2016-10-26 16:51:46 -07:00
tempDat.FileName = HttpUtility.HtmlDecode(String.IsNullOrEmpty(tempDat.Name)
? FileName
: tempDat.Name.Replace(Path.DirectorySeparatorChar.ToString(), " - ").Replace(Path.AltDirectorySeparatorChar.ToString(), " - "));
tempDat.Description += " (" + tempDat.Name.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-') + ")";
tempDat.Name = Name + " (" + tempDat.Name.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-') + ")";
tempDat.Type = null;
// Write out the temporary DAT to the proper directory
tempDat.WriteToFile(lastpath, logger);
return true;
}
2016-10-24 16:14:22 -07:00
/// <summary>
/// Split a DAT by type of Rom
/// </summary>
/// <param name="outDir">Name of the directory to write the DATs out to</param>
/// <param name="basepath">Parent path for replacement</param>
/// <param name="logger">Logger object for console and file writing</param>
/// <returns>True if split succeeded, false otherwise</returns>
public bool SplitByType(string outDir, string basepath, Logger logger)
{
// Sanitize the basepath to be more predictable
basepath = (basepath.EndsWith(Path.DirectorySeparatorChar.ToString()) ? basepath : basepath + Path.DirectorySeparatorChar);
2016-10-24 16:14:22 -07:00
// Create each of the respective output DATs
logger.User("Creating and populating new DATs");
DatFile romdat = new DatFile
{
FileName = this.FileName + " (ROM)",
Name = this.Name + " (ROM)",
Description = this.Description + " (ROM)",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Header = this.Header,
Type = this.Type,
ForceMerging = this.ForceMerging,
ForceNodump = this.ForceNodump,
ForcePacking = this.ForcePacking,
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
MergeRoms = this.MergeRoms,
Files = new SortedDictionary<string, List<DatItem>>(),
};
DatFile diskdat = new DatFile
{
FileName = this.FileName + " (Disk)",
Name = this.Name + " (Disk)",
Description = this.Description + " (Disk)",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Header = this.Header,
Type = this.Type,
ForceMerging = this.ForceMerging,
ForceNodump = this.ForceNodump,
ForcePacking = this.ForcePacking,
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
MergeRoms = this.MergeRoms,
Files = new SortedDictionary<string, List<DatItem>>(),
};
DatFile sampledat = new DatFile
{
FileName = this.FileName + " (Sample)",
Name = this.Name + " (Sample)",
Description = this.Description + " (Sample)",
Category = this.Category,
Version = this.Version,
Date = this.Date,
Author = this.Author,
Email = this.Email,
Homepage = this.Homepage,
Url = this.Url,
Comment = this.Comment,
Header = this.Header,
Type = this.Type,
ForceMerging = this.ForceMerging,
ForceNodump = this.ForceNodump,
ForcePacking = this.ForcePacking,
2016-10-25 15:02:02 -07:00
DatFormat = this.DatFormat,
2016-10-24 16:14:22 -07:00
MergeRoms = this.MergeRoms,
Files = new SortedDictionary<string, List<DatItem>>(),
};
2016-10-24 16:14:22 -07:00
// Now populate each of the DAT objects in turn
List<string> keys = this.Files.Keys.ToList();
foreach (string key in keys)
{
List<DatItem> roms = this.Files[key];
foreach (DatItem rom in roms)
{
2016-10-24 16:14:22 -07:00
// If the file is a Rom
if (rom.Type == ItemType.Rom)
{
2016-10-24 16:14:22 -07:00
if (romdat.Files.ContainsKey(key))
{
romdat.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
romdat.Files.Add(key, temp);
}
}
2016-10-24 16:14:22 -07:00
// If the file is a Disk
else if (rom.Type == ItemType.Disk)
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
if (diskdat.Files.ContainsKey(key))
{
diskdat.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
diskdat.Files.Add(key, temp);
}
2016-09-22 18:15:02 -07:00
}
2016-10-24 16:14:22 -07:00
// If the file is a Sample
else if (rom.Type == ItemType.Sample)
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
if (sampledat.Files.ContainsKey(key))
{
sampledat.Files[key].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
sampledat.Files.Add(key, temp);
}
2016-09-22 18:15:02 -07:00
}
}
2016-10-24 16:14:22 -07:00
}
2016-10-24 16:14:22 -07:00
// Get the output directory
if (outDir != "")
{
outDir = outDir + Path.GetDirectoryName(this.FileName).Remove(0, basepath.Length - 1);
}
else
{
outDir = Path.GetDirectoryName(this.FileName);
}
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
// Now, output all of the files to the output directory
logger.User("DAT information created, outputting new files");
bool success = true;
if (romdat.Files.Count > 0)
{
success &= romdat.WriteToFile(outDir, logger);
}
2016-10-24 16:14:22 -07:00
if (diskdat.Files.Count > 0)
{
2016-10-24 16:14:22 -07:00
success &= diskdat.WriteToFile(outDir, logger);
2016-09-22 18:15:02 -07:00
}
2016-10-24 16:14:22 -07:00
if (sampledat.Files.Count > 0)
{
success &= sampledat.WriteToFile(outDir, logger);
}
return success;
2016-09-22 18:15:02 -07:00
}
2016-09-22 18:15:02 -07:00
#endregion
2016-09-22 18:15:02 -07:00
#region Statistics
2016-09-22 18:15:02 -07:00
/// <summary>
/// Recalculate the statistics for the Dat
/// </summary>
public void RecalculateStats()
{
// Wipe out any stats already there
RomCount = 0;
DiskCount = 0;
TotalSize = 0;
CRCCount = 0;
MD5Count = 0;
SHA1Count = 0;
BaddumpCount = 0;
2016-09-22 18:15:02 -07:00
NodumpCount = 0;
2016-09-22 18:15:02 -07:00
// If we have a blank Dat in any way, return
if (this == null || Files == null || Files.Count == 0)
{
return;
}
// Loop through and add
foreach (List<DatItem> roms in Files.Values)
{
foreach (Rom rom in roms)
{
RomCount += (rom.Type == ItemType.Rom ? 1 : 0);
DiskCount += (rom.Type == ItemType.Disk ? 1 : 0);
TotalSize += (rom.ItemStatus == ItemStatus.Nodump ? 0 : rom.Size);
CRCCount += (String.IsNullOrEmpty(rom.CRC) ? 0 : 1);
MD5Count += (String.IsNullOrEmpty(rom.MD5) ? 0 : 1);
SHA1Count += (String.IsNullOrEmpty(rom.SHA1) ? 0 : 1);
BaddumpCount += (rom.Type == ItemType.Disk
? (((Disk)rom).ItemStatus == ItemStatus.BadDump ? 1 : 0)
: (rom.Type == ItemType.Rom
? (((Rom)rom).ItemStatus == ItemStatus.BadDump ? 1 : 0)
: 0)
);
NodumpCount += (rom.Type == ItemType.Disk
? (((Disk)rom).ItemStatus == ItemStatus.Nodump ? 1 : 0)
: (rom.Type == ItemType.Rom
? (((Rom)rom).ItemStatus == ItemStatus.Nodump ? 1 : 0)
: 0)
);
2016-09-22 18:15:02 -07:00
}
}
}
/// <summary>
2016-09-22 18:15:02 -07:00
/// Output the stats for the Dat in a human-readable format
/// </summary>
/// <param name="sw">StreamWriter representing the output file or stream for the statistics</param>
2016-10-25 15:02:02 -07:00
/// <param name="statDatFormat">Set the statistics output format to use</param>
2016-09-22 18:15:02 -07:00
/// <param name="logger">Logger object for file and console writing</param>
/// <param name="recalculate">True if numbers should be recalculated for the DAT, false otherwise (default)</param>
/// <param name="game">Number of games to use, -1 means recalculate games (default)</param>
/// <param name="baddumpCol">True if baddumps should be included in output, false otherwise (default)</param>
/// <param name="nodumpCol">True if nodumps should be included in output, false otherwise (default)</param>
2016-10-25 15:02:02 -07:00
public void OutputStats(StreamWriter sw, StatDatFormat statDatFormat, Logger logger, bool recalculate = false, long game = -1, bool baddumpCol = false, bool nodumpCol = false)
{
2016-09-22 18:15:02 -07:00
// If we're supposed to recalculate the statistics, do so
if (recalculate)
{
RecalculateStats();
}
2016-09-22 18:15:02 -07:00
BucketByGame(false, true, logger, false);
if (TotalSize < 0)
{
TotalSize = Int64.MaxValue + TotalSize;
}
// Log the results to screen
string results = @"For '" + FileName + @"':
--------------------------------------------------
Uncompressed size: " + Style.GetBytesReadable(TotalSize) + @"
Games found: " + (game == -1 ? Files.Count : game) + @"
Roms found: " + RomCount + @"
Disks found: " + DiskCount + @"
Roms with CRC: " + CRCCount + @"
Roms with MD5: " + MD5Count + @"
Roms with SHA-1: " + SHA1Count + "\n";
if (baddumpCol)
{
results += " Roms with BadDump status: " + BaddumpCount + "\n";
}
if (nodumpCol)
{
results += " Roms with Nodump status: " + NodumpCount + "\n";
}
logger.User(results);
// Now write it out to file as well
string line = "";
2016-10-25 15:02:02 -07:00
switch (statDatFormat)
{
2016-10-25 15:02:02 -07:00
case StatDatFormat.CSV:
line = "\"" + FileName + "\","
+ "\"" + Style.GetBytesReadable(TotalSize) + "\","
+ "\"" + (game == -1 ? Files.Count : game) + "\","
+ "\"" + RomCount + "\","
+ "\"" + DiskCount + "\","
+ "\"" + CRCCount + "\","
+ "\"" + MD5Count + "\","
+ "\"" + SHA1Count + "\"";
2016-10-24 16:14:22 -07:00
if (baddumpCol)
{
line += ",\"" + BaddumpCount + "\"";
}
if (nodumpCol)
{
line += ",\"" + NodumpCount + "\"";
}
line += "\n";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.HTML:
2016-10-24 16:14:22 -07:00
line = "\t\t\t<tr" + (FileName.StartsWith("DIR: ")
? " class=\"dir\"><td>" + HttpUtility.HtmlEncode(FileName.Remove(0, 5))
: "><td>" + HttpUtility.HtmlEncode(FileName)) + "</td>"
+ "<td align=\"right\">" + Style.GetBytesReadable(TotalSize) + "</td>"
+ "<td align=\"right\">" + (game == -1 ? Files.Count : game) + "</td>"
+ "<td align=\"right\">" + RomCount + "</td>"
+ "<td align=\"right\">" + DiskCount + "</td>"
+ "<td align=\"right\">" + CRCCount + "</td>"
+ "<td align=\"right\">" + MD5Count + "</td>"
+ "<td align=\"right\">" + SHA1Count + "</td>";
if (baddumpCol)
{
line += "<td align=\"right\">" + BaddumpCount + "</td>";
}
if (nodumpCol)
{
line += "<td align=\"right\">" + NodumpCount + "</td>";
}
line += "</tr>\n";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.None:
2016-10-24 16:14:22 -07:00
default:
line = @"'" + FileName + @"':
--------------------------------------------------
Uncompressed size: " + Style.GetBytesReadable(TotalSize) + @"
Games found: " + (game == -1 ? Files.Count : game) + @"
Roms found: " + RomCount + @"
Disks found: " + DiskCount + @"
Roms with CRC: " + CRCCount + @"
Roms with MD5: " + MD5Count + @"
Roms with SHA-1: " + SHA1Count + "\n";
if (baddumpCol)
{
line += " Roms with BadDump status: " + BaddumpCount + "\n";
}
if (nodumpCol)
{
line += " Roms with Nodump status: " + NodumpCount + "\n";
}
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.TSV:
2016-10-24 16:14:22 -07:00
line = "\"" + FileName + "\"\t"
+ "\"" + Style.GetBytesReadable(TotalSize) + "\"\t"
+ "\"" + (game == -1 ? Files.Count : game) + "\"\t"
+ "\"" + RomCount + "\"\t"
+ "\"" + DiskCount + "\"\t"
+ "\"" + CRCCount + "\"\t"
+ "\"" + MD5Count + "\"\t"
+ "\"" + SHA1Count + "\"";
if (baddumpCol)
{
line += "\t\"" + BaddumpCount + "\"";
}
if (nodumpCol)
{
line += "\t\"" + NodumpCount + "\"";
}
line += "\n";
break;
}
// Output the line to the streamwriter
sw.Write(line);
}
#endregion
#region Writing
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datdata">All information for creating the datfile header</param>
/// <param name="outDir">Set the output directory</param>
/// <param name="logger">Logger object for console and/or file output</param>
/// <param name="norename">True if games should only be compared on game and file name (default), false if system and source are counted</param>
/// <param name="stats">True if DAT statistics should be output on write, false otherwise (default)</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <param name="overwrite">True if files should be overwritten (default), false if they should be renamed instead</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
/// <remarks>
/// The following features have been requested for file output:
/// - Have the ability to strip special (non-ASCII) characters from rom information
/// </remarks>
public bool WriteToFile(string outDir, Logger logger, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true)
{
// If there's nothing there, abort
if (Files == null || Files.Count == 0)
{
return false;
}
// If output directory is empty, use the current folder
if (outDir.Trim() == "")
{
outDir = Environment.CurrentDirectory;
}
// Create the output directory if it doesn't already exist
if (!Directory.Exists(outDir))
{
Directory.CreateDirectory(outDir);
}
// If the DAT has no output format, default to XML
2016-10-25 15:02:02 -07:00
if (DatFormat == 0)
2016-10-24 16:14:22 -07:00
{
2016-10-25 15:02:02 -07:00
DatFormat = DatFormat.Logiqx;
2016-10-24 16:14:22 -07:00
}
// Make sure that the three essential fields are filled in
if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
FileName = Name = Description = "Default";
}
else if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
FileName = Name = Description;
}
else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
FileName = Description = Name;
}
else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
FileName = Description;
}
else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
Name = Description = FileName;
}
else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
Name = Description;
}
else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
Description = Name;
}
else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
// Nothing is needed
}
// Output initial statistics, for kicks
if (stats)
{
StreamWriter sw = new StreamWriter(new MemoryStream());
2016-10-25 15:02:02 -07:00
OutputStats(sw, StatDatFormat.None, logger, recalculate: (RomCount + DiskCount == 0), baddumpCol: true, nodumpCol: true);
2016-10-24 16:14:22 -07:00
sw.Dispose();
}
// Bucket roms by game name and optionally dedupe
BucketByGame(MergeRoms, norename, logger);
// Get the outfile name
2016-10-25 15:02:02 -07:00
Dictionary<DatFormat, string> outfiles = Style.CreateOutfileNames(outDir, this, overwrite);
2016-10-24 16:14:22 -07:00
try
{
2016-10-25 15:02:02 -07:00
foreach (DatFormat datFormat in outfiles.Keys)
2016-10-24 16:14:22 -07:00
{
2016-10-25 15:02:02 -07:00
string outfile = outfiles[datFormat];
2016-10-24 16:14:22 -07:00
logger.User("Opening file for writing: " + outfile);
FileStream fs = File.Create(outfile);
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
2016-10-25 15:02:02 -07:00
WriteHeader(sw, datFormat, logger);
2016-10-24 16:14:22 -07:00
// Write out each of the machines and roms
int depth = 2, last = -1;
string lastgame = null;
List<string> splitpath = new List<string>();
// Get a properly sorted set of keys
List<string> keys = Files.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = Files[key];
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.Machine.Name == null)
{
logger.Warning("Null rom found!");
continue;
}
List<string> newsplit = rom.Machine.Name.Split('\\').ToList();
// If we have a different game and we're not at the start of the list, output the end of last item
if (lastgame != null && lastgame.ToLowerInvariant() != rom.Machine.Name.ToLowerInvariant())
{
2016-10-25 15:02:02 -07:00
depth = WriteEndGame(sw, datFormat, rom, splitpath, newsplit, lastgame, depth, out last, logger);
2016-10-24 16:14:22 -07:00
}
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.Machine.Name.ToLowerInvariant())
{
2016-10-25 15:02:02 -07:00
depth = WriteStartGame(sw, datFormat, rom, newsplit, lastgame, depth, last, logger);
2016-10-24 16:14:22 -07:00
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null"
&& ((Rom)rom).MD5 == "null"
&& ((Rom)rom).SHA1 == "null")
{
logger.Verbose("Empty folder found: " + rom.Machine.Name);
// If we're in a mode that doesn't allow for actual empty folders, add the blank info
2016-10-25 15:02:02 -07:00
if (datFormat != DatFormat.CSV
&& datFormat != DatFormat.MissFile
&& datFormat != DatFormat.SabreDat
&& datFormat != DatFormat.TSV)
2016-10-24 16:14:22 -07:00
{
rom.Name = (rom.Name == "null" ? "-" : rom.Name);
((Rom)rom).Size = Constants.SizeZero;
((Rom)rom).CRC = Constants.CRCZero;
((Rom)rom).MD5 = Constants.MD5Zero;
((Rom)rom).SHA1 = Constants.SHA1Zero;
}
2016-10-24 16:14:22 -07:00
// Otherwise, set the new path and such, write out, and continue
else
{
splitpath = newsplit;
lastgame = rom.Machine.Name;
continue;
}
}
2016-10-24 16:14:22 -07:00
// Now, output the rom data
2016-10-25 15:02:02 -07:00
WriteRomData(sw, datFormat, rom, lastgame, depth, logger, ignoreblanks);
2016-10-24 16:14:22 -07:00
// Set the new data to compare against
splitpath = newsplit;
lastgame = rom.Machine.Name;
}
}
2016-10-24 16:14:22 -07:00
// Write the file footer out
2016-10-25 15:02:02 -07:00
WriteFooter(sw, datFormat, depth, logger);
2016-10-24 16:14:22 -07:00
logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return false;
}
2016-10-24 16:14:22 -07:00
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
2016-10-25 15:02:02 -07:00
/// <param name="datFormat">Output format to write to</param>
2016-10-24 16:14:22 -07:00
/// <param name="logger">Logger object for file and console output</param>
/// <returns>True if the data was written, false on error</returns>
2016-10-25 15:02:02 -07:00
private bool WriteHeader(StreamWriter sw, DatFormat datFormat, Logger logger)
2016-10-24 16:14:22 -07:00
{
try
{
string header = "";
2016-10-25 15:02:02 -07:00
switch (datFormat)
2016-10-24 16:14:22 -07:00
{
2016-10-25 15:02:02 -07:00
case DatFormat.ClrMamePro:
2016-10-24 16:14:22 -07:00
header = "clrmamepro (\n" +
"\tname \"" + Name + "\"\n" +
"\tdescription \"" + Description + "\"\n" +
(!String.IsNullOrEmpty(Category) ? "\tcategory \"" + Category + "\"\n" : "") +
"\tversion \"" + Version + "\"\n" +
(!String.IsNullOrEmpty(Date) ? "\tdate \"" + Date + "\"\n" : "") +
"\tauthor \"" + Author + "\"\n" +
(!String.IsNullOrEmpty(Email) ? "\temail \"" + Email + "\"\n" : "") +
(!String.IsNullOrEmpty(Homepage) ? "\thomepage \"" + Homepage + "\"\n" : "") +
(!String.IsNullOrEmpty(Url) ? "\turl \"" + Url + "\"\n" : "") +
(!String.IsNullOrEmpty(Comment) ? "\tcomment \"" + Comment + "\"\n" : "") +
(ForcePacking == ForcePacking.Unzip ? "\tforcezipping no\n" : "") +
(ForcePacking == ForcePacking.Zip ? "\tforcezipping yes\n" : "") +
(ForceMerging == ForceMerging.Full ? "\tforcemerging full\n" : "") +
(ForceMerging == ForceMerging.Split ? "\tforcemerging split\n" : "") +
")\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.CSV:
2016-10-24 16:14:22 -07:00
header = "\"File Name\",\"Internal Name\",\"Description\",\"Game Name\",\"Game Description\",\"Type\",\"" +
"Rom Name\",\"Disk Name\",\"Size\",\"CRC\",\"MD5\",\"SHA1\",\"Nodump\"\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.DOSCenter:
2016-10-24 16:14:22 -07:00
header = "DOSCenter (\n" +
"\tName: " + Name + "\n" +
"\tDescription: " + Description + "\n" +
"\tVersion: " + Version + "\n" +
"\tDate: " + Date + "\n" +
"\tAuthor: " + Author + "\n" +
"\tHomepage: " + Homepage + "\n" +
"\tComment: " + Comment + "\n" +
")\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.Logiqx:
2016-10-24 16:14:22 -07:00
header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE datafile PUBLIC \"-//Logiqx//DTD ROM Management Datafile//EN\" \"http://www.logiqx.com/Dats/datafile.dtd\">\n\n" +
"<datafile>\n" +
"\t<header>\n" +
"\t\t<name>" + HttpUtility.HtmlEncode(Name) + "</name>\n" +
"\t\t<description>" + HttpUtility.HtmlEncode(Description) + "</description>\n" +
(!String.IsNullOrEmpty(RootDir) ? "\t\t<rootdir>" + HttpUtility.HtmlEncode(RootDir) + "</rootdir>\n" : "") +
(!String.IsNullOrEmpty(Category) ? "\t\t<category>" + HttpUtility.HtmlEncode(Category) + "</category>\n" : "") +
"\t\t<version>" + HttpUtility.HtmlEncode(Version) + "</version>\n" +
(!String.IsNullOrEmpty(Date) ? "\t\t<date>" + HttpUtility.HtmlEncode(Date) + "</date>\n" : "") +
"\t\t<author>" + HttpUtility.HtmlEncode(Author) + "</author>\n" +
(!String.IsNullOrEmpty(Email) ? "\t\t<email>" + HttpUtility.HtmlEncode(Email) + "</email>\n" : "") +
(!String.IsNullOrEmpty(Homepage) ? "\t\t<homepage>" + HttpUtility.HtmlEncode(Homepage) + "</homepage>\n" : "") +
(!String.IsNullOrEmpty(Url) ? "\t\t<url>" + HttpUtility.HtmlEncode(Url) + "</url>\n" : "") +
(!String.IsNullOrEmpty(Comment) ? "\t\t<comment>" + HttpUtility.HtmlEncode(Comment) + "</comment>\n" : "") +
(!String.IsNullOrEmpty(Type) ? "\t\t<type>" + HttpUtility.HtmlEncode(Type) + "</type>\n" : "") +
(ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None ?
"\t\t<clrmamepro" +
(ForcePacking == ForcePacking.Unzip ? " forcepacking=\"unzip\"" : "") +
(ForcePacking == ForcePacking.Zip ? " forcepacking=\"zip\"" : "") +
(ForceMerging == ForceMerging.Full ? " forcemerging=\"full\"" : "") +
(ForceMerging == ForceMerging.Split ? " forcemerging=\"split\"" : "") +
(ForceNodump == ForceNodump.Ignore ? " forceitemStatus=\"ignore\"" : "") +
(ForceNodump == ForceNodump.Obsolete ? " forceitemStatus=\"obsolete\"" : "") +
(ForceNodump == ForceNodump.Required ? " forceitemStatus=\"required\"" : "") +
" />\n"
: "") +
"\t</header>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.TSV:
2016-10-24 16:14:22 -07:00
header = "\"File Name\"\t\"Internal Name\"\t\"Description\"\t\"Game Name\"\t\"Game Description\"\t\"Type\"\t\"" +
"Rom Name\"\t\"Disk Name\"\t\"Size\"\t\"CRC\"\t\"MD5\"\t\"SHA1\"\t\"Nodump\"\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.OfflineList:
2016-10-24 16:14:22 -07:00
header = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+ "<dat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"datas.xsd\">\n"
+ "\t<configuration>\n"
+ "\t\t<datName>" + HttpUtility.HtmlEncode(Name) + "</datName>\n"
+ "\t\t<datVersion>" + Files.Count + "</datVersion>\n"
+ "\t\t<system>none</system>\n"
+ "\t\t<screenshotsWidth>240</screenshotsWidth>\n"
+ "\t\t<screenshotsHeight>160</screenshotsHeight>\n"
+ "\t\t<infos>\n"
+ "\t\t\t<title visible=\"false\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<location visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<publisher visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<sourceRom visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<saveType visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<romSize visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<releaseNumber visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<languageNumber visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<comment visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<romCRC visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<im1CRC visible=\"false\" inNamingOption=\"false\" default=\"false\"/>\n"
+ "\t\t\t<im2CRC visible=\"false\" inNamingOption=\"false\" default=\"false\"/>\n"
+ "\t\t\t<languages visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t</infos>\n"
+ "\t\t<canOpen>\n"
+ "\t\t\t<extension>.bin</extension>\n"
+ "\t\t</canOpen>\n"
+ "\t\t<newDat>\n"
+ "\t\t\t<datVersionURL>" + HttpUtility.HtmlEncode(Url) + "</datVersionURL>\n"
+ "\t\t\t<datURL fileName=\"" + HttpUtility.HtmlEncode(FileName) + ".zip\">" + HttpUtility.HtmlEncode(Url) + "</datURL>\n"
+ "\t\t\t<imURL>" + HttpUtility.HtmlEncode(Url) + "</imURL>\n"
+ "\t\t</newDat>\n"
+ "\t\t<search>\n"
+ "\t\t\t<to value=\"location\" default=\"true\" auto=\"true\"/>\n"
+ "\t\t\t<to value=\"romSize\" default=\"true\" auto=\"false\"/>\n"
+ "\t\t\t<to value=\"languages\" default=\"true\" auto=\"true\"/>\n"
+ "\t\t\t<to value=\"saveType\" default=\"false\" auto=\"false\"/>\n"
+ "\t\t\t<to value=\"publisher\" default=\"false\" auto=\"true\"/>\n"
+ "\t\t\t<to value=\"sourceRom\" default=\"false\" auto=\"true\"/>\n"
+ "\t\t</search>\n"
+ "\t\t<romTitle >%u - %n</romTitle>\n"
+ "\t</configuration>\n"
+ "\t<games>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RomCenter:
2016-10-24 16:14:22 -07:00
header = "[CREDITS]\n" +
"author=" + Author + "\n" +
"version=" + Version + "\n" +
"comment=" + Comment + "\n" +
"[DAT]\n" +
"version=2.50\n" +
"split=" + (ForceMerging == ForceMerging.Split ? "1" : "0") + "\n" +
"merge=" + (ForceMerging == ForceMerging.Full ? "1" : "0") + "\n" +
"[EMULATOR]\n" +
"refname=" + Name + "\n" +
"version=" + Description + "\n" +
"[GAMES]\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SabreDat:
2016-10-24 16:14:22 -07:00
header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE sabredat SYSTEM \"newdat.xsd\">\n\n" +
"<datafile>\n" +
"\t<header>\n" +
"\t\t<name>" + HttpUtility.HtmlEncode(Name) + "</name>\n" +
"\t\t<description>" + HttpUtility.HtmlEncode(Description) + "</description>\n" +
(!String.IsNullOrEmpty(RootDir) ? "\t\t<rootdir>" + HttpUtility.HtmlEncode(RootDir) + "</rootdir>\n" : "") +
(!String.IsNullOrEmpty(Category) ? "\t\t<category>" + HttpUtility.HtmlEncode(Category) + "</category>\n" : "") +
"\t\t<version>" + HttpUtility.HtmlEncode(Version) + "</version>\n" +
(!String.IsNullOrEmpty(Date) ? "\t\t<date>" + HttpUtility.HtmlEncode(Date) + "</date>\n" : "") +
"\t\t<author>" + HttpUtility.HtmlEncode(Author) + "</author>\n" +
(!String.IsNullOrEmpty(Comment) ? "\t\t<comment>" + HttpUtility.HtmlEncode(Comment) + "</comment>\n" : "") +
(!String.IsNullOrEmpty(Type) || ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None ?
"\t\t<flags>\n" +
(!String.IsNullOrEmpty(Type) ? "\t\t\t<flag name=\"type\" value=\"" + HttpUtility.HtmlEncode(Type) + "\"/>\n" : "") +
(ForcePacking == ForcePacking.Unzip ? "\t\t\t<flag name=\"forcepacking\" value=\"unzip\"/>\n" : "") +
(ForcePacking == ForcePacking.Zip ? "\t\t\t<flag name=\"forcepacking\" value=\"zip\"/>\n" : "") +
(ForceMerging == ForceMerging.Full ? "\t\t\t<flag name=\"forcemerging\" value=\"full\"/>\n" : "") +
(ForceMerging == ForceMerging.Split ? "\t\t\t<flag name=\"forcemerging\" value=\"split\"/>\n" : "") +
(ForceNodump == ForceNodump.Ignore ? "\t\t\t<flag name=\"forceitemStatus\" value=\"ignore\"/>\n" : "") +
(ForceNodump == ForceNodump.Obsolete ? "\t\t\t<flag name=\"forceitemStatus\" value=\"obsolete\"/>\n" : "") +
(ForceNodump == ForceNodump.Required ? "\t\t\t<flag name=\"forceitemStatus\" value=\"required\"/>\n" : "") +
"\t\t</flags>\n"
: "") +
"\t</header>\n" +
"\t<data>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SoftwareList:
2016-10-24 16:14:22 -07:00
header = "<?xml version=\"1.0\"?>\n" +
"<!DOCTYPE softwarelist SYSTEM \"softwarelist.dtd\">\n\n" +
"<softwarelist name=\"" + HttpUtility.HtmlEncode(Name) + "\"" +
" description=\"" + HttpUtility.HtmlEncode(Description) + "\"" +
(ForcePacking == ForcePacking.Unzip ? " forcepacking=\"unzip\"" : "") +
(ForcePacking == ForcePacking.Zip ? " forcepacking=\"zip\"" : "") +
(ForceMerging == ForceMerging.Full ? " forcemerging=\"full\"" : "") +
(ForceMerging == ForceMerging.Split ? " forcemerging=\"split\"" : "") +
(ForceNodump == ForceNodump.Ignore ? " forceitemStatus=\"ignore\"" : "") +
(ForceNodump == ForceNodump.Obsolete ? " forceitemStatus=\"obsolete\"" : "") +
(ForceNodump == ForceNodump.Required ? " forceitemStatus=\"required\"" : "") +
">\n\n";
break;
}
2016-10-24 16:14:22 -07:00
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return false;
}
2016-10-24 16:14:22 -07:00
return true;
}
2016-09-22 18:15:02 -07:00
/// <summary>
2016-10-24 16:14:22 -07:00
/// Write out Game start using the supplied StreamWriter
2016-09-22 18:15:02 -07:00
/// </summary>
2016-10-24 16:14:22 -07:00
/// <param name="sw">StreamWriter to output to</param>
2016-10-25 15:02:02 -07:00
/// <param name="datFormat">Output format to write to</param>
2016-10-24 16:14:22 -07:00
/// <param name="rom">RomData object to be output</param>
/// <param name="newsplit">Split path representing the parent game (SabreDAT only)</param>
/// <param name="lastgame">The name of the last game to be output</param>
/// <param name="depth">Current depth to output file at (SabreDAT only)</param>
/// <param name="last">Last known depth to cycle back from (SabreDAT only)</param>
2016-09-22 18:15:02 -07:00
/// <param name="logger">Logger object for file and console output</param>
2016-10-24 16:14:22 -07:00
/// <returns>The new depth of the tag</returns>
2016-10-25 15:02:02 -07:00
private int WriteStartGame(StreamWriter sw, DatFormat datFormat, DatItem rom, List<string> newsplit, string lastgame, int depth, int last, Logger logger)
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
try
{
// No game should start with a path separator
if (rom.Machine.Name.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
rom.Machine.Name = rom.Machine.Name.Substring(1);
}
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
string state = "";
2016-10-25 15:02:02 -07:00
switch (datFormat)
2016-10-24 16:14:22 -07:00
{
2016-10-25 15:02:02 -07:00
case DatFormat.ClrMamePro:
2016-10-24 16:14:22 -07:00
state += "game (\n\tname \"" + rom.Machine.Name + "\"\n" +
(ExcludeOf ? "" :
(String.IsNullOrEmpty(rom.Machine.RomOf) ? "" : "\tromof \"" + rom.Machine.RomOf + "\"\n") +
(String.IsNullOrEmpty(rom.Machine.CloneOf) ? "" : "\tcloneof \"" + rom.Machine.CloneOf + "\"\n") +
(String.IsNullOrEmpty(rom.Machine.SampleOf) ? "" : "\tsampleof \"" + rom.Machine.SampleOf + "\"\n")
) +
"\tdescription \"" + (String.IsNullOrEmpty(rom.Machine.Description) ? rom.Machine.Name : rom.Machine.Description) + "\"\n" +
(String.IsNullOrEmpty(rom.Machine.Year) ? "" : "\tyear " + rom.Machine.Year + "\n") +
(String.IsNullOrEmpty(rom.Machine.Manufacturer) ? "" : "\tmanufacturer \"" + rom.Machine.Manufacturer + "\"\n");
break;
2016-10-25 15:02:02 -07:00
case DatFormat.DOSCenter:
2016-10-24 16:14:22 -07:00
state += "game (\n\tname \"" + rom.Machine.Name + ".zip\"\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.Logiqx:
2016-10-24 16:14:22 -07:00
state += "\t<machine name=\"" + HttpUtility.HtmlEncode(rom.Machine.Name) + "\"" +
(rom.Machine.IsBios ? " isbios=\"yes\"" : "") +
(ExcludeOf ? "" :
(String.IsNullOrEmpty(rom.Machine.CloneOf) || (rom.Machine.Name.ToLowerInvariant() == rom.Machine.CloneOf.ToLowerInvariant())
? ""
: " cloneof=\"" + HttpUtility.HtmlEncode(rom.Machine.CloneOf) + "\"") +
(String.IsNullOrEmpty(rom.Machine.RomOf) || (rom.Machine.Name.ToLowerInvariant() == rom.Machine.RomOf.ToLowerInvariant())
? ""
: " romof=\"" + HttpUtility.HtmlEncode(rom.Machine.RomOf) + "\"") +
(String.IsNullOrEmpty(rom.Machine.SampleOf) || (rom.Machine.Name.ToLowerInvariant() == rom.Machine.SampleOf.ToLowerInvariant())
? ""
: " sampleof=\"" + HttpUtility.HtmlEncode(rom.Machine.SampleOf) + "\"")
) +
">\n" +
(String.IsNullOrEmpty(rom.Machine.Comment) ? "" : "\t\t<comment>" + HttpUtility.HtmlEncode(rom.Machine.Comment) + "</comment>\n") +
"\t\t<description>" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.Machine.Description) ? rom.Machine.Name : rom.Machine.Description)) + "</description>\n" +
(String.IsNullOrEmpty(rom.Machine.Year) ? "" : "\t\t<year>" + HttpUtility.HtmlEncode(rom.Machine.Year) + "</year>\n") +
(String.IsNullOrEmpty(rom.Machine.Manufacturer) ? "" : "\t\t<manufacturer>" + HttpUtility.HtmlEncode(rom.Machine.Manufacturer) + "</manufacturer>\n");
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SabreDat:
2016-10-24 16:14:22 -07:00
for (int i = (last == -1 ? 0 : last); i < newsplit.Count; i++)
{
for (int j = 0; j < depth - last + i - (lastgame == null ? 1 : 0); j++)
{
state += "\t";
}
state += "<directory name=\"" + HttpUtility.HtmlEncode(newsplit[i]) + "\" description=\"" +
HttpUtility.HtmlEncode(newsplit[i]) + "\">\n";
}
depth = depth - (last == -1 ? 0 : last) + newsplit.Count;
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SoftwareList:
2016-10-24 16:14:22 -07:00
state += "\t<software name=\"" + HttpUtility.HtmlEncode(rom.Machine.Name) + "\""
+ (rom.Supported != null ? " supported=\"" + (rom.Supported == true ? "yes" : "no") + "\"" : "") +
(ExcludeOf ? "" :
(String.IsNullOrEmpty(rom.Machine.CloneOf) || (rom.Machine.Name.ToLowerInvariant() == rom.Machine.CloneOf.ToLowerInvariant())
? ""
: " cloneof=\"" + HttpUtility.HtmlEncode(rom.Machine.CloneOf) + "\"") +
(String.IsNullOrEmpty(rom.Machine.RomOf) || (rom.Machine.Name.ToLowerInvariant() == rom.Machine.RomOf.ToLowerInvariant())
? ""
: " romof=\"" + HttpUtility.HtmlEncode(rom.Machine.RomOf) + "\"") +
(String.IsNullOrEmpty(rom.Machine.SampleOf) || (rom.Machine.Name.ToLowerInvariant() == rom.Machine.SampleOf.ToLowerInvariant())
? ""
: " sampleof=\"" + HttpUtility.HtmlEncode(rom.Machine.SampleOf) + "\"")
) + ">\n"
+ "\t\t<description>" + HttpUtility.HtmlEncode(rom.Machine.Description) + "</description>\n"
+ (rom.Machine.Year != null ? "\t\t<year>" + HttpUtility.HtmlEncode(rom.Machine.Year) + "</year>\n" : "")
+ (rom.Publisher != null ? "\t\t<publisher>" + HttpUtility.HtmlEncode(rom.Publisher) + "</publisher>\n" : "");
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
foreach (Tuple<string, string> kvp in rom.Infos)
{
state += "\t\t<info name=\"" + HttpUtility.HtmlEncode(kvp.Item1) + "\" value=\"" + HttpUtility.HtmlEncode(kvp.Item2) + "\" />\n";
}
break;
}
2016-09-22 18:15:02 -07:00
2016-10-24 16:14:22 -07:00
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
logger.Error(ex.ToString());
return depth;
2016-09-22 18:15:02 -07:00
}
2016-10-24 16:14:22 -07:00
return depth;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
2016-10-25 15:02:02 -07:00
/// <param name="datFormat">Output format to write to</param>
2016-10-24 16:14:22 -07:00
/// <param name="rom">RomData object to be output</param>
/// <param name="splitpath">Split path representing last kwown parent game (SabreDAT only)</param>
/// <param name="newsplit">Split path representing the parent game (SabreDAT only)</param>
/// <param name="lastgame">The name of the last game to be output</param>
/// <param name="depth">Current depth to output file at (SabreDAT only)</param>
/// <param name="last">Last known depth to cycle back from (SabreDAT only)</param>
/// <param name="logger">Logger object for file and console output</param>
/// <returns>The new depth of the tag</returns>
2016-10-25 15:02:02 -07:00
private int WriteEndGame(StreamWriter sw, DatFormat datFormat, DatItem rom, List<string> splitpath, List<string> newsplit, string lastgame, int depth, out int last, Logger logger)
2016-10-24 16:14:22 -07:00
{
last = 0;
try
2016-09-22 18:15:02 -07:00
{
2016-10-24 16:14:22 -07:00
string state = "";
2016-10-25 15:02:02 -07:00
switch (datFormat)
2016-09-22 18:15:02 -07:00
{
2016-10-25 15:02:02 -07:00
case DatFormat.ClrMamePro:
case DatFormat.DOSCenter:
2016-10-24 16:14:22 -07:00
state += (String.IsNullOrEmpty(rom.Machine.SampleOf) ? "" : "\tsampleof \"" + rom.Machine.SampleOf + "\"\n") + ")\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.Logiqx:
2016-10-24 16:14:22 -07:00
state += "\t</machine>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.OfflineList:
2016-10-24 16:14:22 -07:00
state += "\t\t</game>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SabreDat:
2016-10-24 16:14:22 -07:00
if (splitpath != null)
{
for (int i = 0; i < newsplit.Count && i < splitpath.Count; i++)
{
// Always keep track of the last seen item
last = i;
// If we find a difference, break
if (newsplit[i] != splitpath[i])
{
break;
}
}
// Now that we have the last known position, take down all open folders
for (int i = depth - 1; i > last + 1; i--)
{
// Print out the number of tabs and the end folder
for (int j = 0; j < i; j++)
{
state += "\t";
}
state += "</directory>\n";
}
// Reset the current depth
depth = 2 + last;
}
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SoftwareList:
2016-10-24 16:14:22 -07:00
state += "\t</software>\n\n";
break;
}
2016-10-24 16:14:22 -07:00
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return depth;
}
2016-10-24 16:14:22 -07:00
return depth;
}
/// <summary>
2016-10-24 16:14:22 -07:00
/// Write out RomData using the supplied StreamWriter
/// </summary>
2016-10-24 16:14:22 -07:00
/// <param name="sw">StreamWriter to output to</param>
2016-10-25 15:02:02 -07:00
/// <param name="datFormat">Output format to write to</param>
2016-10-24 16:14:22 -07:00
/// <param name="rom">RomData object to be output</param>
/// <param name="lastgame">The name of the last game to be output</param>
/// <param name="depth">Current depth to output file at (SabreDAT only)</param>
/// <param name="logger">Logger object for file and console output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
2016-10-25 15:02:02 -07:00
private bool WriteRomData(StreamWriter sw, DatFormat datFormat, DatItem rom, string lastgame, int depth, Logger logger, bool ignoreblanks = false)
{
2016-10-24 16:14:22 -07:00
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
2016-10-24 16:14:22 -07:00
return true;
}
2016-10-24 16:14:22 -07:00
try
{
2016-10-24 16:14:22 -07:00
string state = "", name = "", pre = "", post = "";
2016-10-25 15:02:02 -07:00
switch (datFormat)
2016-10-24 16:14:22 -07:00
{
2016-10-25 15:02:02 -07:00
case DatFormat.ClrMamePro:
2016-10-24 16:14:22 -07:00
switch (rom.Type)
{
case ItemType.Archive:
state += "\tarchive ( name\"" + rom.Name + "\""
+ " )\n";
break;
case ItemType.BiosSet:
state += "\tbiosset ( name\"" + rom.Name + "\""
+ (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description \"" + ((BiosSet)rom).Description + "\"" : "")
+ (((BiosSet)rom).Default != null
? "default " + ((BiosSet)rom).Default.ToString().ToLowerInvariant()
: "")
+ " )\n";
break;
case ItemType.Disk:
state += "\tdisk ( name \"" + rom.Name + "\""
+ (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5 " + ((Disk)rom).MD5.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1 " + ((Disk)rom).SHA1.ToLowerInvariant() : "")
+ (((Disk)rom).ItemStatus != ItemStatus.None ? " flags " + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() : "")
+ " )\n";
break;
case ItemType.Release:
state += "\trelease ( name\"" + rom.Name + "\""
+ (!String.IsNullOrEmpty(((Release)rom).Region) ? " region \"" + ((Release)rom).Region + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Language) ? " language \"" + ((Release)rom).Language + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Date) ? " date \"" + ((Release)rom).Date + "\"" : "")
+ (((Release)rom).Default != null
? "default " + ((Release)rom).Default.ToString().ToLowerInvariant()
: "")
+ " )\n";
break;
case ItemType.Rom:
state += "\trom ( name \"" + rom.Name + "\""
+ (((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5 " + ((Rom)rom).MD5.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1 " + ((Rom)rom).SHA1.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date \"" + ((Rom)rom).Date + "\"" : "")
+ (((Rom)rom).ItemStatus != ItemStatus.None ? " flags " + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() : "")
+ " )\n";
break;
case ItemType.Sample:
state += "\tsample ( name\"" + rom.Name + "\""
+ " )\n";
break;
}
2016-10-24 16:14:22 -07:00
break;
2016-10-25 15:02:02 -07:00
case DatFormat.CSV:
2016-10-24 16:14:22 -07:00
// CSV should only output Rom and Disk
if (rom.Type != ItemType.Disk && rom.Type != ItemType.Rom)
{
return true;
}
2016-10-24 16:14:22 -07:00
pre = Prefix + (Quotes ? "\"" : "");
post = (Quotes ? "\"" : "") + Postfix;
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Rom)
{
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%size%", ((Rom)rom).Size.ToString());
post = post
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%size%", ((Rom)rom).Size.ToString());
}
else if (rom.Type == ItemType.Disk)
{
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1);
post = post
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1);
}
if (rom.Type == ItemType.Rom)
{
string inline = "\"" + FileName + "\""
+ ",\"" + Name + "\""
+ ",\"" + Description + "\""
+ ",\"" + rom.Machine.Name + "\""
+ ",\"" + rom.Machine.Description + "\""
+ "," + "\"rom\""
+ ",\"" + rom.Name + "\""
+ "," + "\"\""
+ ",\"" + ((Rom)rom).Size + "\""
+ ",\"" + ((Rom)rom).CRC + "\""
+ ",\"" + ((Rom)rom).MD5 + "\""
+ ",\"" + ((Rom)rom).SHA1 + "\""
+ "," + (((Rom)rom).ItemStatus != ItemStatus.None ? "\"" + ((Rom)rom).ItemStatus.ToString() + "\"" : "\"\"");
state += pre + inline + post + "\n";
}
else if (rom.Type == ItemType.Disk)
{
string inline = "\"" + FileName + "\""
+ ",\"" + Name + "\""
+ ",\"" + Description + "\""
+ ",\"" + rom.Machine.Name + "\""
+ ",\"" + rom.Machine.Description + "\""
+ "," + "\"disk\""
+ "," + "\"\""
+ ",\"" + rom.Name + "\""
+ "," + "\"\""
+ "," + "\"\""
+ ",\"" + ((Disk)rom).MD5 + "\""
+ ",\"" + ((Disk)rom).SHA1 + "\""
+ "," + (((Disk)rom).ItemStatus != ItemStatus.None ? "\"" + ((Disk)rom).ItemStatus.ToString() + "\"" : "\"\"");
state += pre + inline + post + "\n";
}
break;
2016-10-25 15:02:02 -07:00
case DatFormat.DOSCenter:
2016-10-24 16:14:22 -07:00
switch (rom.Type)
{
case ItemType.Archive:
case ItemType.BiosSet:
case ItemType.Disk:
case ItemType.Release:
case ItemType.Sample:
// We don't output these at all
break;
case ItemType.Rom:
state += "\tfile ( name " + ((Rom)rom).Name
+ (((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date " + ((Rom)rom).Date : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "")
+ " )\n";
break;
}
break;
2016-10-25 15:02:02 -07:00
case DatFormat.Logiqx:
2016-10-24 16:14:22 -07:00
switch (rom.Type)
{
case ItemType.Archive:
state += "\t\t<archive name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n";
break;
case ItemType.BiosSet:
state += "\t\t<biosset name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description=\"" + HttpUtility.HtmlEncode(((BiosSet)rom).Description) + "\"" : "")
+ (((BiosSet)rom).Default != null
? ((BiosSet)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n";
break;
case ItemType.Disk:
state += "\t\t<disk name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5=\"" + ((Disk)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1=\"" + ((Disk)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (((Disk)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "")
+ "/>\n";
break;
case ItemType.Release:
state += "\t\t<release name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Release)rom).Region) ? " region=\"" + HttpUtility.HtmlEncode(((Release)rom).Region) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Language) ? " language=\"" + HttpUtility.HtmlEncode(((Release)rom).Language) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Date) ? " date=\"" + HttpUtility.HtmlEncode(((Release)rom).Date) + "\"" : "")
+ (((Release)rom).Default != null
? ((Release)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n";
break;
case ItemType.Rom:
state += "\t\t<rom name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (((Rom)rom).Size != -1 ? " size=\"" + ((Rom)rom).Size + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc=\"" + ((Rom)rom).CRC.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5=\"" + ((Rom)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1=\"" + ((Rom)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date=\"" + ((Rom)rom).Date + "\"" : "")
+ (((Rom)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "")
+ "/>\n";
break;
case ItemType.Sample:
state += "\t\t<file type=\"sample\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n";
break;
}
break;
2016-10-25 15:02:02 -07:00
case DatFormat.MissFile:
2016-10-24 16:14:22 -07:00
// Missfile should only output Rom and Disk
if (rom.Type != ItemType.Disk && rom.Type != ItemType.Rom)
{
return true;
}
2016-10-24 16:14:22 -07:00
pre = Prefix + (Quotes ? "\"" : "");
post = (Quotes ? "\"" : "") + Postfix;
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Rom)
{
2016-10-24 16:14:22 -07:00
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%size%", ((Rom)rom).Size.ToString());
post = post
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%size%", ((Rom)rom).Size.ToString());
}
2016-10-24 16:14:22 -07:00
else if (rom.Type == ItemType.Disk)
{
2016-10-24 16:14:22 -07:00
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1);
post = post
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1);
}
2016-10-24 16:14:22 -07:00
// If we're in Romba mode, the state is consistent
if (Romba)
{
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Rom)
{
// We can only write out if there's a SHA-1
if (((Rom)rom).SHA1 != "")
{
name = ((Rom)rom).SHA1.Substring(0, 2)
+ "/" + ((Rom)rom).SHA1.Substring(2, 2)
+ "/" + ((Rom)rom).SHA1.Substring(4, 2)
+ "/" + ((Rom)rom).SHA1.Substring(6, 2)
+ "/" + ((Rom)rom).SHA1 + ".gz";
state += pre + name + post + "\n";
}
}
else if (rom.Type == ItemType.Disk)
{
// We can only write out if there's a SHA-1
if (((Disk)rom).SHA1 != "")
{
name = ((Disk)rom).SHA1.Substring(0, 2)
+ "/" + ((Disk)rom).SHA1.Substring(2, 2)
+ "/" + ((Disk)rom).SHA1.Substring(4, 2)
+ "/" + ((Disk)rom).SHA1.Substring(6, 2)
+ "/" + ((Disk)rom).SHA1 + ".gz";
state += pre + name + post + "\n";
}
}
}
2016-10-24 16:14:22 -07:00
// Otherwise, use any flags
name = (UseGame ? rom.Machine.Name : rom.Name);
if (RepExt != "" || RemExt)
{
2016-10-24 16:14:22 -07:00
if (RemExt)
{
RepExt = "";
}
string dir = Path.GetDirectoryName(name);
dir = (dir.StartsWith(Path.DirectorySeparatorChar.ToString()) ? dir.Remove(0, 1) : dir);
name = Path.Combine(dir, Path.GetFileNameWithoutExtension(name) + RepExt);
}
2016-10-24 16:14:22 -07:00
if (AddExt != "")
{
2016-10-24 16:14:22 -07:00
name += AddExt;
}
2016-10-24 16:14:22 -07:00
if (!UseGame && GameName)
{
2016-10-24 16:14:22 -07:00
name = Path.Combine(rom.Machine.Name, name);
}
2016-10-24 16:14:22 -07:00
if (UseGame && rom.Machine.Name != lastgame)
{
2016-10-24 16:14:22 -07:00
state += pre + name + post + "\n";
lastgame = rom.Machine.Name;
}
2016-10-24 16:14:22 -07:00
else if (!UseGame)
{
2016-10-24 16:14:22 -07:00
state += pre + name + post + "\n";
}
2016-10-24 16:14:22 -07:00
break;
2016-10-25 15:02:02 -07:00
case DatFormat.OfflineList:
2016-10-24 16:14:22 -07:00
state += "\t\t<game>\n"
+ "\t\t\t<imageNumber>1</imageNumber>\n"
+ "\t\t\t<releaseNumber>1</releaseNumber>\n"
+ "\t\t\t<title>" + HttpUtility.HtmlEncode(rom.Name) + "</title>\n"
+ "\t\t\t<saveType>None</saveType>\n";
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Rom)
{
state += "\t\t\t<romSize>" + ((Rom)rom).Size + "</romSize>\n";
}
2016-10-24 16:14:22 -07:00
state += "\t\t\t<publisher>None</publisher>\n"
+ "\t\t\t<location>0</location>\n"
+ "\t\t\t<sourceRom>None</sourceRom>\n"
+ "\t\t\t<language>0</language>\n";
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Disk)
{
state += "\t\t\t<files>\n"
+ (((Disk)rom).MD5 != null
? "\t\t\t\t<romMD5 extension=\".chd\">" + ((Disk)rom).MD5.ToUpperInvariant() + "</romMD5>\n"
: "\t\t\t\t<romSHA1 extension=\".chd\">" + ((Disk)rom).SHA1.ToUpperInvariant() + "</romSHA1>\n")
+ "\t\t\t</files>\n";
}
else if (rom.Type == ItemType.Rom)
{
string tempext = Path.GetExtension(((Rom)rom).Name);
if (!tempext.StartsWith("."))
{
tempext = "." + tempext;
}
2016-10-24 16:14:22 -07:00
state += "\t\t\t<files>\n"
+ (((Rom)rom).CRC != null
? "\t\t\t\t<romCRC extension=\"" + tempext + "\">" + ((Rom)rom).CRC.ToUpperInvariant() + "</romMD5>\n"
: ((Rom)rom).MD5 != null
? "\t\t\t\t<romMD5 extension=\"" + tempext + "\">" + ((Rom)rom).MD5.ToUpperInvariant() + "</romMD5>\n"
: "\t\t\t\t<romSHA1 extension=\"" + tempext + "\">" + ((Rom)rom).SHA1.ToUpperInvariant() + "</romSHA1>\n")
+ "\t\t\t</files>\n";
}
2016-10-24 16:14:22 -07:00
state += "\t\t\t<im1CRC>00000000</im1CRC>\n"
+ "\t\t\t<im2CRC>00000000</im2CRC>\n"
+ "\t\t\t<comment></comment>\n"
+ "\t\t\t<duplicateID>0</duplicateID>\n"
+ "\t\t</game>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RedumpMD5:
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Rom)
{
state += ((Rom)rom).MD5 + " *" + (GameName ? rom.Machine.Name + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
else if (rom.Type == ItemType.Disk)
{
state += ((Disk)rom).MD5 + " *" + (GameName ? rom.Machine.Name + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RedumpSFV:
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Rom)
{
state += (GameName ? rom.Machine.Name + Path.DirectorySeparatorChar : "") + rom.Name + " " + ((Rom)rom).CRC + "\n";
}
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RedumpSHA1:
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Rom)
{
state += ((Rom)rom).SHA1 + " *" + (GameName ? rom.Machine.Name + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
else if (rom.Type == ItemType.Disk)
{
2016-10-24 16:14:22 -07:00
state += ((Disk)rom).SHA1 + " *" + (GameName ? rom.Machine.Name + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
2016-10-24 16:14:22 -07:00
break;
2016-10-25 15:02:02 -07:00
case DatFormat.RomCenter:
2016-10-24 16:14:22 -07:00
if (rom.Type == ItemType.Rom)
{
2016-10-24 16:14:22 -07:00
state += "¬" + (String.IsNullOrEmpty(rom.Machine.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.Machine.CloneOf)) +
"¬" + (String.IsNullOrEmpty(rom.Machine.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.Machine.CloneOf)) +
"¬" + HttpUtility.HtmlEncode(rom.Machine.Name) +
"¬" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.Machine.Description) ? rom.Machine.Name : rom.Machine.Description)) +
"¬" + HttpUtility.HtmlEncode(rom.Name) +
"¬" + ((Rom)rom).CRC.ToLowerInvariant() +
"¬" + (((Rom)rom).Size != -1 ? ((Rom)rom).Size.ToString() : "") + "¬¬¬\n";
}
2016-10-24 16:14:22 -07:00
else if (rom.Type == ItemType.Disk)
{
2016-10-24 16:14:22 -07:00
state += "¬" + (String.IsNullOrEmpty(rom.Machine.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.Machine.CloneOf)) +
"¬" + (String.IsNullOrEmpty(rom.Machine.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.Machine.CloneOf)) +
"¬" + HttpUtility.HtmlEncode(rom.Machine.Name) +
"¬" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.Machine.Description) ? rom.Machine.Name : rom.Machine.Description)) +
"¬" + HttpUtility.HtmlEncode(rom.Name) +
"¬¬¬¬¬\n";
}
2016-10-24 16:14:22 -07:00
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SabreDat:
2016-10-24 16:14:22 -07:00
string prefix = "";
for (int i = 0; i < depth; i++)
{
2016-10-24 16:14:22 -07:00
prefix += "\t";
}
2016-10-24 16:14:22 -07:00
state += prefix;
switch (rom.Type)
{
2016-10-24 16:14:22 -07:00
case ItemType.Archive:
state += "<file type=\"archive\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n";
break;
case ItemType.BiosSet:
state += "<file type=\"biosset\" name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description=\"" + HttpUtility.HtmlEncode(((BiosSet)rom).Description) + "\"" : "")
+ (((BiosSet)rom).Default != null
? ((BiosSet)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n";
break;
case ItemType.Disk:
state += "<file type=\"disk\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5=\"" + ((Disk)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1=\"" + ((Disk)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (((Disk)rom).ItemStatus != ItemStatus.None ? prefix + "/>\n" + prefix + "\t<flags>\n" +
prefix + "\t\t<flag name=\"status\" value=\"" + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() + "\"/>\n" +
prefix + "\t</flags>\n" +
prefix + "</file>\n" : "/>\n");
break;
case ItemType.Release:
state += "<file type=\"release\" name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Release)rom).Region) ? " region=\"" + HttpUtility.HtmlEncode(((Release)rom).Region) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Language) ? " language=\"" + HttpUtility.HtmlEncode(((Release)rom).Language) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Date) ? " date=\"" + HttpUtility.HtmlEncode(((Release)rom).Date) + "\"" : "")
+ (((Release)rom).Default != null
? ((Release)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n";
break;
case ItemType.Rom:
state += "<file type=\"rom\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (((Rom)rom).Size != -1 ? " size=\"" + ((Rom)rom).Size + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc=\"" + ((Rom)rom).CRC.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5=\"" + ((Rom)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1=\"" + ((Rom)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date=\"" + ((Rom)rom).Date + "\"" : "")
+ (((Rom)rom).ItemStatus != ItemStatus.None ? prefix + "/>\n" + prefix + "\t<flags>\n" +
prefix + "\t\t<flag name=\"status\" value=\"" + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() + "\"/>\n" +
prefix + "\t</flags>\n" +
prefix + "</file>\n" : "/>\n");
break;
case ItemType.Sample:
state += "<file type=\"sample\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n";
break;
}
2016-10-24 16:14:22 -07:00
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SoftwareList:
2016-10-24 16:14:22 -07:00
state += "\t\t<part name=\"" + rom.PartName + "\" interface=\"" + rom.PartInterface + "\">\n";
foreach (Tuple<string, string> kvp in rom.Features)
{
2016-10-24 16:14:22 -07:00
state += "\t\t\t<feature name=\"" + HttpUtility.HtmlEncode(kvp.Item1) + "\" value=\"" + HttpUtility.HtmlEncode(kvp.Item2) + "\"/>\n";
}
2016-10-24 16:14:22 -07:00
switch (rom.Type)
{
2016-10-24 16:14:22 -07:00
case ItemType.Archive:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "archive" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<archive name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
case ItemType.BiosSet:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "biosset" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<biosset name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description=\"" + HttpUtility.HtmlEncode(((BiosSet)rom).Description) + "\"" : "")
+ (((BiosSet)rom).Default != null
? ((BiosSet)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
case ItemType.Disk:
state += "\t\t\t<diskarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "cdrom" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<disk name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5=\"" + ((Disk)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1=\"" + ((Disk)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (((Disk)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "")
+ "/>\n"
+ "\t\t\t</diskarea>\n";
break;
case ItemType.Release:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "release" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<release name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Release)rom).Region) ? " region=\"" + HttpUtility.HtmlEncode(((Release)rom).Region) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Language) ? " language=\"" + HttpUtility.HtmlEncode(((Release)rom).Language) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Date) ? " date=\"" + HttpUtility.HtmlEncode(((Release)rom).Date) + "\"" : "")
+ (((Release)rom).Default != null
? ((Release)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
case ItemType.Rom:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "rom" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<rom name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (((Rom)rom).Size != -1 ? " size=\"" + ((Rom)rom).Size + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc=\"" + ((Rom)rom).CRC.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5=\"" + ((Rom)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1=\"" + ((Rom)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date=\"" + ((Rom)rom).Date + "\"" : "")
+ (((Rom)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "")
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
case ItemType.Sample:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "sample" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<sample type=\"sample\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
}
2016-10-24 16:14:22 -07:00
state += "\t\t</part>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.TSV:
2016-10-24 16:14:22 -07:00
// TSV should only output Rom and Disk
if (rom.Type != ItemType.Disk && rom.Type != ItemType.Rom)
{
2016-10-24 16:14:22 -07:00
return true;
}
2016-10-24 16:14:22 -07:00
pre = Prefix + (Quotes ? "\"" : "");
post = (Quotes ? "\"" : "") + Postfix;
if (rom.Type == ItemType.Rom)
{
2016-10-24 16:14:22 -07:00
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%size%", ((Rom)rom).Size.ToString());
post = post
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%size%", ((Rom)rom).Size.ToString());
}
2016-10-24 16:14:22 -07:00
else if (rom.Type == ItemType.Disk)
{
2016-10-24 16:14:22 -07:00
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1);
post = post
.Replace("%game%", rom.Machine.Name)
.Replace("%name%", rom.Name)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1);
}
if (rom.Type == ItemType.Rom)
{
string inline = "\"" + FileName + "\""
+ "\t\"" + Name + "\""
+ "\t\"" + Description + "\""
+ "\t\"" + rom.Machine.Name + "\""
+ "\t\"" + rom.Machine.Description + "\""
+ "\t" + "\"rom\""
+ "\t\"" + rom.Name + "\""
+ "\t" + "\"\""
+ "\t\"" + ((Rom)rom).Size + "\""
+ "\t\"" + ((Rom)rom).CRC + "\""
+ "\t\"" + ((Rom)rom).MD5 + "\""
+ "\t\"" + ((Rom)rom).SHA1 + "\""
+ "\t" + (((Rom)rom).ItemStatus != ItemStatus.None ? "\"" + ((Rom)rom).ItemStatus.ToString() + "\"" : "\"\"");
state += pre + inline + post + "\n";
}
else if (rom.Type == ItemType.Disk)
{
string inline = "\"" + FileName + "\""
+ "\t\"" + Name + "\""
+ "\t\"" + Description + "\""
+ "\t\"" + rom.Machine.Name + "\""
+ "\t\"" + rom.Machine.Description + "\""
+ "\t" + "\"disk\""
+ "\t" + "\"\""
+ "\t\"" + rom.Name + "\""
+ "\t" + "\"\""
+ "\t" + "\"\""
+ "\t\"" + ((Disk)rom).MD5 + "\""
+ "\t\"" + ((Disk)rom).SHA1 + "\""
+ "\t" + (((Disk)rom).ItemStatus != ItemStatus.None ? "\"" + ((Disk)rom).ItemStatus.ToString() + "\"" : "\"\"");
state += pre + inline + post + "\n";
}
2016-10-24 16:14:22 -07:00
break;
}
2016-10-24 16:14:22 -07:00
sw.Write(state);
sw.Flush();
}
2016-10-24 16:14:22 -07:00
catch (Exception ex)
{
2016-10-24 16:14:22 -07:00
logger.Error(ex.ToString());
return false;
}
2016-10-24 16:14:22 -07:00
return true;
}
/// <summary>
2016-10-24 16:14:22 -07:00
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
2016-10-24 16:14:22 -07:00
/// <param name="sw">StreamWriter to output to</param>
2016-10-25 15:02:02 -07:00
/// <param name="datFormat">Output format to write to</param>
2016-10-24 16:14:22 -07:00
/// <param name="depth">Current depth to output file at (SabreDAT only)</param>
/// <param name="logger">Logger object for file and console output</param>
/// <returns>True if the data was written, false on error</returns>
2016-10-25 15:02:02 -07:00
private bool WriteFooter(StreamWriter sw, DatFormat datFormat, int depth, Logger logger)
{
2016-10-24 16:14:22 -07:00
try
{
2016-10-24 16:14:22 -07:00
string footer = "";
2016-10-24 16:14:22 -07:00
// If we have roms, output the full footer
if (Files != null && Files.Count > 0)
{
2016-10-25 15:02:02 -07:00
switch (datFormat)
{
2016-10-25 15:02:02 -07:00
case DatFormat.ClrMamePro:
case DatFormat.DOSCenter:
2016-10-24 16:14:22 -07:00
footer = ")\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.Logiqx:
2016-10-24 16:14:22 -07:00
footer = "\t</machine>\n</datafile>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.OfflineList:
2016-10-24 16:14:22 -07:00
footer = "\t\t</game>"
+ "\t</games>\n"
+ "\t<gui>\n"
+ "\t\t<images width=\"487\" height=\"162\">\n"
+ "\t\t\t<image x=\"0\" y=\"0\" width=\"240\" height=\"160\"/>\n"
+ "\t\t\t<image x=\"245\" y=\"0\" width=\"240\" height=\"160\"/>\n"
+ "\t\t</images>\n"
+ "\t</gui>\n"
+ "</dat>";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SabreDat:
2016-10-24 16:14:22 -07:00
for (int i = depth - 1; i >= 2; i--)
{
// Print out the number of tabs and the end folder
for (int j = 0; j < i; j++)
{
footer += "\t";
}
footer += "</directory>\n";
}
footer += "\t</data>\n</datafile>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SoftwareList:
2016-10-24 16:14:22 -07:00
footer = "\t</software>\n\n</softwarelist>\n";
break;
}
2016-10-24 16:14:22 -07:00
}
2016-10-24 16:14:22 -07:00
// Otherwise, output the abbreviated form
else
{
2016-10-25 15:02:02 -07:00
switch (datFormat)
{
2016-10-25 15:02:02 -07:00
case DatFormat.Logiqx:
case DatFormat.SabreDat:
2016-10-24 16:14:22 -07:00
footer = "</datafile>\n";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.OfflineList:
2016-10-24 16:14:22 -07:00
footer = "\t</games>\n"
+ "\t<gui>\n"
+ "\t\t<images width=\"487\" height=\"162\">\n"
+ "\t\t\t<image x=\"0\" y=\"0\" width=\"240\" height=\"160\"/>\n"
+ "\t\t\t<image x=\"245\" y=\"0\" width=\"240\" height=\"160\"/>\n"
+ "\t\t</images>\n"
+ "\t</gui>\n"
+ "</dat>";
break;
2016-10-25 15:02:02 -07:00
case DatFormat.SoftwareList:
2016-10-24 16:14:22 -07:00
footer = "</softwarelist>\n";
break;
}
}
2016-10-24 16:14:22 -07:00
// Write the footer out
sw.Write(footer);
sw.Flush();
}
2016-10-24 16:14:22 -07:00
catch (Exception ex)
{
2016-10-24 16:14:22 -07:00
logger.Error(ex.ToString());
return false;
}
2016-10-24 16:14:22 -07:00
return true;
}
#endregion
#endregion // Instance Methods
#region Static Methods
#region Bucketing
/// <summary>
/// Take an arbitrarily ordered List and return a Dictionary sorted by Game
/// </summary>
/// <param name="list">Input unsorted list</param>
/// <param name="mergeroms">True if roms should be deduped, false otherwise</param>
/// <param name="norename">True if games should only be compared on game and file name, false if system and source are counted</param>
/// <param name="logger">Logger object for file and console output</param>
/// <param name="output">True if the number of hashes counted is to be output (default), false otherwise</param>
/// <returns>SortedDictionary bucketed by game name</returns>
public static SortedDictionary<string, List<DatItem>> BucketListByGame(List<DatItem> list, bool mergeroms, bool norename, Logger logger, bool output = true)
{
logger.User("Organizing " + (mergeroms ? "and merging " : "") + "roms for output");
SortedDictionary<string, List<DatItem>> sortable = new SortedDictionary<string, List<DatItem>>();
long count = 0;
// If we have a null dict or an empty one, output a new dictionary
if (list == null || list.Count == 0)
{
2016-10-24 16:14:22 -07:00
return sortable;
}
2016-10-24 16:14:22 -07:00
// If we're merging the roms, do so
if (mergeroms)
{
2016-10-24 16:14:22 -07:00
list = DatItem.Merge(list, logger);
}
2016-10-24 16:14:22 -07:00
// Now add each of the roms to their respective games
foreach (DatItem rom in list)
{
if (rom == null)
{
continue;
}
2016-10-24 16:14:22 -07:00
count++;
string newkey = (norename ? ""
: rom.SystemID.ToString().PadLeft(10, '0')
+ "-"
+ rom.SourceID.ToString().PadLeft(10, '0') + "-")
+ (rom.Machine == null || String.IsNullOrEmpty(rom.Machine.Name)
2016-10-24 16:14:22 -07:00
? "Default"
: rom.Machine.Name.ToLowerInvariant());
newkey = HttpUtility.HtmlEncode(newkey);
if (sortable.ContainsKey(newkey))
{
sortable[newkey].Add(rom);
}
else
{
List<DatItem> temp = new List<DatItem>();
temp.Add(rom);
sortable.Add(newkey, temp);
}
}
2016-10-24 16:14:22 -07:00
return sortable;
}
#endregion
#region Statistics
/// <summary>
/// Output the stats for a list of input dats as files in a human-readable format
/// </summary>
/// <param name="inputs">List of input files and folders</param>
/// <param name="reportName">Name of the output file</param>
/// <param name="single">True if single DAT stats are output, false otherwise</param>
/// <param name="baddumpCol">True if baddumps should be included in output, false otherwise</param>
/// <param name="nodumpCol">True if nodumps should be included in output, false otherwise</param>
2016-10-25 15:02:02 -07:00
/// <param name="statDatFormat" > Set the statistics output format to use</param>
/// <param name="logger">Logger object for file and console output</param>
public static void OutputStats(List<string> inputs, string reportName, bool single, bool baddumpCol,
2016-10-25 15:02:02 -07:00
bool nodumpCol, StatDatFormat statDatFormat, Logger logger)
{
2016-10-25 15:02:02 -07:00
reportName += OutputStatsGetExtension(statDatFormat);
StreamWriter sw = new StreamWriter(File.Open(reportName, FileMode.Create, FileAccess.Write));
// Make sure we have all files
List<Tuple<string, string>> newinputs = new List<Tuple<string, string>>(); // item, basepath
foreach (string input in inputs)
{
if (File.Exists(input))
{
newinputs.Add(Tuple.Create(Path.GetFullPath(input), Path.GetDirectoryName(Path.GetFullPath(input))));
}
if (Directory.Exists(input))
{
foreach (string file in Directory.GetFiles(input, "*", SearchOption.AllDirectories))
{
newinputs.Add(Tuple.Create(Path.GetFullPath(file), Path.GetFullPath(input)));
}
}
}
newinputs = newinputs
.OrderBy(i => Path.GetDirectoryName(i.Item1))
.ThenBy(i => Path.GetFileName(i.Item1))
.ToList();
// Write the header, if any
2016-10-25 15:02:02 -07:00
OutputStatsWriteHeader(sw, statDatFormat, baddumpCol, nodumpCol);
// Init all total variables
long totalSize = 0;
long totalGame = 0;
long totalRom = 0;
long totalDisk = 0;
long totalCRC = 0;
long totalMD5 = 0;
long totalSHA1 = 0;
long totalBaddump = 0;
long totalNodump = 0;
// Init directory-level variables
string lastdir = null;
string basepath = null;
long dirSize = 0;
long dirGame = 0;
long dirRom = 0;
long dirDisk = 0;
long dirCRC = 0;
long dirMD5 = 0;
long dirSHA1 = 0;
long dirBaddump = 0;
long dirNodump = 0;
// Now process each of the input files
foreach (Tuple<string, string> filename in newinputs)
{
// Get the directory for the current file
string thisdir = Path.GetDirectoryName(filename.Item1);
basepath = Path.GetDirectoryName(filename.Item2);
// If we don't have the first file and the directory has changed, show the previous directory stats and reset
if (lastdir != null && thisdir != lastdir)
{
// Output separator if needed
2016-10-25 15:02:02 -07:00
OutputStatsWriteMidSeparator(sw, statDatFormat, baddumpCol, nodumpCol);
DatFile lastdirdat = new DatFile
{
FileName = "DIR: " + HttpUtility.HtmlEncode(lastdir.Remove(0, basepath.Length + (basepath.Length == 0 ? 0 : 1))),
TotalSize = dirSize,
RomCount = dirRom,
DiskCount = dirDisk,
CRCCount = dirCRC,
MD5Count = dirMD5,
SHA1Count = dirSHA1,
BaddumpCount = dirBaddump,
NodumpCount = dirNodump,
};
2016-10-25 15:02:02 -07:00
lastdirdat.OutputStats(sw, statDatFormat, logger, game: dirGame, baddumpCol: baddumpCol, nodumpCol: nodumpCol);
// Write the mid-footer, if any
2016-10-25 15:02:02 -07:00
OutputStatsWriteMidFooter(sw, statDatFormat, baddumpCol, nodumpCol);
// Write the header, if any
2016-10-25 15:02:02 -07:00
OutputStatsWriteMidHeader(sw, statDatFormat, baddumpCol, nodumpCol);
// Reset the directory stats
dirSize = 0;
dirGame = 0;
dirRom = 0;
dirDisk = 0;
dirCRC = 0;
dirMD5 = 0;
dirSHA1 = 0;
dirBaddump = 0;
dirNodump = 0;
}
logger.Verbose("Beginning stat collection for '" + filename.Item1 + "'", false);
List<string> games = new List<string>();
DatFile datdata = new DatFile();
datdata.Parse(filename.Item1, 0, 0, logger);
2016-09-22 18:15:02 -07:00
datdata.BucketByGame(false, true, logger, false);
// Output single DAT stats (if asked)
logger.User("Adding stats for file '" + filename.Item1 + "'\n", false);
if (single)
{
2016-10-25 15:02:02 -07:00
datdata.OutputStats(sw, statDatFormat, logger, baddumpCol: baddumpCol, nodumpCol: nodumpCol);
}
// Add single DAT stats to dir
dirSize += datdata.TotalSize;
dirGame += datdata.Files.Count;
dirRom += datdata.RomCount;
dirDisk += datdata.DiskCount;
dirCRC += datdata.CRCCount;
dirMD5 += datdata.MD5Count;
dirSHA1 += datdata.SHA1Count;
dirBaddump += datdata.BaddumpCount;
dirNodump += datdata.NodumpCount;
// Add single DAT stats to totals
totalSize += datdata.TotalSize;
totalGame += datdata.Files.Count;
totalRom += datdata.RomCount;
totalDisk += datdata.DiskCount;
totalCRC += datdata.CRCCount;
totalMD5 += datdata.MD5Count;
totalSHA1 += datdata.SHA1Count;
totalBaddump += datdata.BaddumpCount;
totalNodump += datdata.NodumpCount;
// Make sure to assign the new directory
lastdir = thisdir;
}
// Output the directory stats one last time
2016-10-25 15:02:02 -07:00
OutputStatsWriteMidSeparator(sw, statDatFormat, baddumpCol, nodumpCol);
if (single)
{
DatFile dirdat = new DatFile
{
FileName = "DIR: " + HttpUtility.HtmlEncode(lastdir.Remove(0, basepath.Length + (basepath.Length == 0 ? 0 : 1))),
TotalSize = dirSize,
RomCount = dirRom,
DiskCount = dirDisk,
CRCCount = dirCRC,
MD5Count = dirMD5,
SHA1Count = dirSHA1,
BaddumpCount = dirBaddump,
NodumpCount = dirNodump,
};
2016-10-25 15:02:02 -07:00
dirdat.OutputStats(sw, statDatFormat, logger, game: dirGame, baddumpCol: baddumpCol, nodumpCol: nodumpCol);
}
// Write the mid-footer, if any
2016-10-25 15:02:02 -07:00
OutputStatsWriteMidFooter(sw, statDatFormat, baddumpCol, nodumpCol);
// Write the header, if any
2016-10-25 15:02:02 -07:00
OutputStatsWriteMidHeader(sw, statDatFormat, baddumpCol, nodumpCol);
// Reset the directory stats
dirSize = 0;
dirGame = 0;
dirRom = 0;
dirDisk = 0;
dirCRC = 0;
dirMD5 = 0;
dirSHA1 = 0;
dirNodump = 0;
// Output total DAT stats
DatFile totaldata = new DatFile
{
2016-09-27 09:32:59 -07:00
FileName = "DIR: All DATs",
TotalSize = totalSize,
RomCount = totalRom,
DiskCount = totalDisk,
CRCCount = totalCRC,
MD5Count = totalMD5,
SHA1Count = totalSHA1,
BaddumpCount = totalBaddump,
NodumpCount = totalNodump,
};
2016-10-25 15:02:02 -07:00
totaldata.OutputStats(sw, statDatFormat, logger, game: totalGame, baddumpCol: baddumpCol, nodumpCol: nodumpCol);
// Output footer if needed
2016-10-25 15:02:02 -07:00
OutputStatsWriteFooter(sw, statDatFormat);
sw.Flush();
sw.Dispose();
logger.User(@"
Please check the log folder if the stats scrolled offscreen", false);
}
/// <summary>
/// Get the proper extension for the stat output format
/// </summary>
2016-10-25 15:02:02 -07:00
/// <param name="statDatFormat">StatDatFormat to get the extension for</param>
/// <returns>File extension with leading period</returns>
2016-10-25 15:02:02 -07:00
private static string OutputStatsGetExtension(StatDatFormat statDatFormat)
{
string reportExtension = "";
2016-10-25 15:02:02 -07:00
switch (statDatFormat)
{
2016-10-25 15:02:02 -07:00
case StatDatFormat.CSV:
reportExtension = ".csv";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.HTML:
reportExtension = ".html";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.None:
default:
reportExtension = ".txt";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.TSV:
reportExtension = ".csv";
break;
}
return reportExtension;
}
/// <summary>
/// Write out the header to the stream, if any exists
/// </summary>
/// <param name="sw">StreamWriter representing the output</param>
2016-10-25 15:02:02 -07:00
/// <param name="statDatFormat">StatDatFormat representing output format</param>
/// <param name="baddumpCol">True if baddumps should be included in output, false otherwise</param>
/// <param name="nodumpCol">True if nodumps should be included in output, false otherwise</param>
2016-10-25 15:02:02 -07:00
private static void OutputStatsWriteHeader(StreamWriter sw, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol)
{
string head = "";
2016-10-25 15:02:02 -07:00
switch (statDatFormat)
{
2016-10-25 15:02:02 -07:00
case StatDatFormat.CSV:
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.HTML:
head = @"<!DOCTYPE html>
<html>
<header>
<title>DAT Statistics Report</title>
2016-09-27 09:32:59 -07:00
<style>
2016-09-27 09:48:48 -07:00
body {
background-color: lightgray;
}
2016-09-27 10:32:12 -07:00
.dir {
color: #0088FF;
2016-09-27 09:32:59 -07:00
}
2016-09-27 09:48:48 -07:00
.right {
align: right;
}
2016-09-27 09:32:59 -07:00
</style>
</header>
2016-09-27 09:48:48 -07:00
<body>
<h2>DAT Statistics Report (" + DateTime.Now.ToShortDateString() + @")</h2>
<table border=""1"" cellpadding=""5"" cellspacing=""0"">
";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.None:
default:
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.TSV:
break;
}
sw.Write(head);
// Now write the mid header for those who need it
2016-10-25 15:02:02 -07:00
OutputStatsWriteMidHeader(sw, statDatFormat, baddumpCol, nodumpCol);
}
/// <summary>
/// Write out the mid-header to the stream, if any exists
/// </summary>
/// <param name="sw">StreamWriter representing the output</param>
2016-10-25 15:02:02 -07:00
/// <param name="statDatFormat">StatDatFormat representing output format</param>
/// <param name="baddumpCol">True if baddumps should be included in output, false otherwise</param>
/// <param name="nodumpCol">True if nodumps should be included in output, false otherwise</param>
2016-10-25 15:02:02 -07:00
private static void OutputStatsWriteMidHeader(StreamWriter sw, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol)
{
string head = "";
2016-10-25 15:02:02 -07:00
switch (statDatFormat)
{
2016-10-25 15:02:02 -07:00
case StatDatFormat.CSV:
head = "\"File Name\",\"Total Size\",\"Games\",\"Roms\",\"Disks\",\"# with CRC\",\"# with MD5\",\"# with SHA-1\""
+ (baddumpCol ? ",\"BadDumps\"" : "") + (nodumpCol ? ",\"Nodumps\"" : "") + "\n";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.HTML:
2016-09-27 09:32:59 -07:00
head = @" <tr bgcolor=""gray""><th>File Name</th><th align=""right"">Total Size</th><th align=""right"">Games</th><th align=""right"">Roms</th>"
2016-09-26 21:01:05 -07:00
+ @"<th align=""right"">Disks</th><th align=""right"">&#35; with CRC</th><th align=""right"">&#35; with MD5</th><th align=""right"">&#35; with SHA-1</th>"
2016-09-27 09:48:48 -07:00
+ (baddumpCol ? "<th class=\".right\">Baddumps</th>" : "") + (nodumpCol ? "<th class=\".right\">Nodumps</th>" : "") + "</tr>\n";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.None:
default:
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.TSV:
head = "\"File Name\"\t\"Total Size\"\t\"Games\"\t\"Roms\"\t\"Disks\"\t\"# with CRC\"\t\"# with MD5\"\t\"# with SHA-1\""
+ (baddumpCol ? "\t\"BadDumps\"" : "") + (nodumpCol ? "\t\"Nodumps\"" : "") + "\n";
break;
}
sw.Write(head);
}
/// <summary>
/// Write out the separator to the stream, if any exists
/// </summary>
/// <param name="sw">StreamWriter representing the output</param>
2016-10-25 15:02:02 -07:00
/// <param name="statDatFormat">StatDatFormat representing output format</param>
/// <param name="baddumpCol">True if baddumps should be included in output, false otherwise</param>
/// <param name="nodumpCol">True if nodumps should be included in output, false otherwise</param>
2016-10-25 15:02:02 -07:00
private static void OutputStatsWriteMidSeparator(StreamWriter sw, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol)
{
string mid = "";
2016-10-25 15:02:02 -07:00
switch (statDatFormat)
{
2016-10-25 15:02:02 -07:00
case StatDatFormat.CSV:
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.HTML:
mid = "<tr><td colspan=\""
+ (baddumpCol && nodumpCol
? "11"
: (baddumpCol ^ nodumpCol
? "10"
: "9")
)
+ "\"></td></tr>\n";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.None:
default:
break;
}
sw.Write(mid);
}
/// <summary>
/// Write out the footer-separator to the stream, if any exists
/// </summary>
/// <param name="sw">StreamWriter representing the output</param>
2016-10-25 15:02:02 -07:00
/// <param name="statDatFormat">StatDatFormat representing output format</param>
/// <param name="baddumpCol">True if baddumps should be included in output, false otherwise</param>
/// <param name="nodumpCol">True if nodumps should be included in output, false otherwise</param>
2016-10-25 15:02:02 -07:00
private static void OutputStatsWriteMidFooter(StreamWriter sw, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol)
{
string end = "";
2016-10-25 15:02:02 -07:00
switch (statDatFormat)
{
2016-10-25 15:02:02 -07:00
case StatDatFormat.CSV:
2016-09-26 15:48:51 -07:00
end = "\n";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.HTML:
end = "<tr border=\"0\"><td colspan=\""
+ (baddumpCol && nodumpCol
? "11"
: (baddumpCol ^ nodumpCol
? "10"
: "9")
)
+ "\"></td></tr>\n";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.None:
default:
2016-09-26 15:48:51 -07:00
end = "\n";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.TSV:
2016-09-26 15:48:51 -07:00
end = "\n";
break;
}
sw.Write(end);
}
/// <summary>
/// Write out the footer to the stream, if any exists
/// </summary>
/// <param name="sw">StreamWriter representing the output</param>
2016-10-25 15:02:02 -07:00
/// <param name="statDatFormat">StatDatFormat representing output format</param>
private static void OutputStatsWriteFooter(StreamWriter sw, StatDatFormat statDatFormat)
{
string end = "";
2016-10-25 15:02:02 -07:00
switch (statDatFormat)
{
2016-10-25 15:02:02 -07:00
case StatDatFormat.CSV:
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.HTML:
end = @" </table>
</body>
</html>
";
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.None:
default:
break;
2016-10-25 15:02:02 -07:00
case StatDatFormat.TSV:
break;
}
sw.Write(end);
}
#endregion
#endregion // Static Methods
}
}