[SabreHelper] Rename logical folder

This commit is contained in:
Matt Nadareski
2016-06-12 20:03:59 -07:00
parent e02233e5d4
commit 8f3ff61cd4
6 changed files with 5 additions and 5 deletions

119
SabreHelper/Tools/CRC32.cs Normal file
View File

@@ -0,0 +1,119 @@
// Copyright (c) Damien Guard. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Originally published at http://damieng.com/blog/2006/08/08/calculating_crc32_in_c_and_net
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace DamienG.Security.Cryptography
{
/// <summary>
/// Implements a 32-bit CRC hash algorithm compatible with Zip etc.
/// </summary>
/// <remarks>
/// Crc32 should only be used for backward compatibility with older file formats
/// and algorithms. It is not secure enough for new applications.
/// If you need to call multiple times for the same data either use the HashAlgorithm
/// interface or remember that the result of one Compute call needs to be ~ (XOR) before
/// being passed in as the seed for the next Compute call.
/// </remarks>
public sealed class Crc32 : HashAlgorithm
{
public const UInt32 DefaultPolynomial = 0xedb88320u;
public const UInt32 DefaultSeed = 0xffffffffu;
static UInt32[] defaultTable;
readonly UInt32 seed;
readonly UInt32[] table;
UInt32 hash;
public Crc32()
: this(DefaultPolynomial, DefaultSeed)
{
}
public Crc32(UInt32 polynomial, UInt32 seed)
{
table = InitializeTable(polynomial);
this.seed = hash = seed;
}
public override void Initialize()
{
hash = seed;
}
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
hash = CalculateHash(table, hash, array, ibStart, cbSize);
}
protected override byte[] HashFinal()
{
var hashBuffer = UInt32ToBigEndianBytes(~hash);
HashValue = hashBuffer;
return hashBuffer;
}
public override int HashSize { get { return 32; } }
public static UInt32 Compute(byte[] buffer)
{
return Compute(DefaultSeed, buffer);
}
public static UInt32 Compute(UInt32 seed, byte[] buffer)
{
return Compute(DefaultPolynomial, seed, buffer);
}
public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer)
{
return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
}
static UInt32[] InitializeTable(UInt32 polynomial)
{
if (polynomial == DefaultPolynomial && defaultTable != null)
return defaultTable;
var createTable = new UInt32[256];
for (var i = 0; i < 256; i++)
{
var entry = (UInt32)i;
for (var j = 0; j < 8; j++)
if ((entry & 1) == 1)
entry = (entry >> 1) ^ polynomial;
else
entry = entry >> 1;
createTable[i] = entry;
}
if (polynomial == DefaultPolynomial)
defaultTable = createTable;
return createTable;
}
static UInt32 CalculateHash(UInt32[] table, UInt32 seed, IList<byte> buffer, int start, int size)
{
var crc = seed;
for (var i = start; i < size - start; i++)
crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff];
return crc;
}
static byte[] UInt32ToBigEndianBytes(UInt32 uint32)
{
var result = BitConverter.GetBytes(uint32);
if (BitConverter.IsLittleEndian)
Array.Reverse(result);
return result;
}
}
}

File diff suppressed because it is too large Load Diff

564
SabreHelper/Tools/Output.cs Normal file
View File

@@ -0,0 +1,564 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
namespace SabreTools.Helper
{
public class Output
{
/// <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>
/// <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
/// - Add a flag for ignoring roms with blank sizes
/// </remarks>
public static bool WriteDatfile(DatData datdata, string outDir, Logger logger, bool norename = true, bool stats = false)
{
// Output initial statistics, for kicks
if (stats)
{
Stats.OutputStats(datdata, logger, (datdata.RomCount + datdata.DiskCount == 0));
}
// Bucket roms by game name and optionally dedupe
SortedDictionary<string, List<RomData>> sortable = DatTools.BucketByGame(datdata.Roms, datdata.MergeRoms, norename, logger);
// Now write out to file
// If it's empty, use the current folder
if (outDir.Trim() == "")
{
outDir = Environment.CurrentDirectory;
}
// Create the output directory if it doesn't already exist
Directory.CreateDirectory(outDir);
// Get the outfile name
string outfile = Style.CreateOutfileName(outDir, datdata);
logger.User("Opening file for writing: " + outfile);
try
{
FileStream fs = File.Create(outfile);
StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
// Write out the header
WriteHeader(sw, datdata, logger);
// Write out each of the machines and roms
int depth = 2, last = -1;
string lastgame = null;
List<string> splitpath = new List<string>();
foreach (List<RomData> roms in sortable.Values)
{
for (int index = 0; index < roms.Count; index++)
{
RomData rom = roms[index];
List<string> newsplit = rom.Game.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.Game.ToLowerInvariant())
{
depth = WriteEndGame(sw, rom, splitpath, newsplit, lastgame, datdata, depth, out last, logger);
}
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.Game.ToLowerInvariant())
{
depth = WriteStartGame(sw, rom, newsplit, lastgame, datdata, depth, last, logger);
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Name == "null" && rom.Size == -1 && rom.CRC == "null" && rom.MD5 == "null" && rom.SHA1 == "null")
{
logger.Log("Empty folder found: " + rom.Game);
// If we're in a mode that doesn't allow for actual empty folders, add the blank info
if (datdata.OutputFormat != OutputFormat.SabreDat && datdata.OutputFormat != OutputFormat.MissFile)
{
rom.Name = "-";
rom.Size = Constants.SizeZero;
rom.CRC = Constants.CRCZero;
rom.MD5 = Constants.MD5Zero;
rom.SHA1 = Constants.SHA1Zero;
}
// Otherwise, set the new path and such, write out, and continue
else
{
splitpath = newsplit;
lastgame = rom.Game;
continue;
}
}
// Now, output the rom data
WriteRomData(sw, rom, lastgame, datdata, depth, logger);
// Set the new data to compare against
splitpath = newsplit;
lastgame = rom.Game;
}
}
// Write the file footer out
WriteFooter(sw, datdata, depth, logger);
logger.Log("File written!" + Environment.NewLine);
sw.Close();
fs.Close();
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="datdata">DatData object representing DAT information</param>
/// <param name="logger">Logger object for file and console output</param>
/// <returns>True if the data was written, false on error</returns>
public static bool WriteHeader(StreamWriter sw, DatData datdata, Logger logger)
{
try
{
string header = "";
switch (datdata.OutputFormat)
{
case OutputFormat.ClrMamePro:
header = "clrmamepro (\n" +
"\tname \"" + datdata.Name + "\"\n" +
"\tdescription \"" + datdata.Description + "\"\n" +
"\tcategory \"" + datdata.Category + "\"\n" +
"\tversion \"" + datdata.Version + "\"\n" +
"\tdate \"" + datdata.Date + "\"\n" +
"\tauthor \"" + datdata.Author + "\"\n" +
"\temail \"" + datdata.Email + "\"\n" +
"\thomepage \"" + datdata.Homepage + "\"\n" +
"\turl \"" + datdata.Url + "\"\n" +
"\tcomment \"" + datdata.Comment + "\"\n" +
(datdata.ForcePacking == ForcePacking.Unzip ? "\tforcezipping no\n" : "") +
")\n";
break;
case OutputFormat.MissFile:
if (datdata.TSV)
{
header = "File Name\tInternal Name\tDescription\tGame Name\tGame Description\tType\t" +
"Rom Name\tDisk Name\tSize\tCRC\tMD5\tSHA1\tNodump\n";
}
break;
case OutputFormat.RomCenter:
header = "[CREDITS]\n" +
"author=" + datdata.Author + "\n" +
"version=" + datdata.Version + "\n" +
"comment=" + datdata.Comment + "\n" +
"[DAT]\n" +
"version=2.50\n" +
"split=" + (datdata.ForceMerging == ForceMerging.Split ? "1" : "0") + "\n" +
"merge=" + (datdata.ForceMerging == ForceMerging.Full ? "1" : "0") + "\n" +
"[EMULATOR]\n" +
"refname=" + datdata.Name + "\n" +
"version=" + datdata.Description + "\n" +
"[GAMES]\n";
break;
case OutputFormat.SabreDat:
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(datdata.Name) + "</name>\n" +
"\t\t<description>" + HttpUtility.HtmlEncode(datdata.Description) + "</description>\n" +
"\t\t<category>" + HttpUtility.HtmlEncode(datdata.Category) + "</category>\n" +
"\t\t<version>" + HttpUtility.HtmlEncode(datdata.Version) + "</version>\n" +
"\t\t<date>" + HttpUtility.HtmlEncode(datdata.Date) + "</date>\n" +
"\t\t<author>" + HttpUtility.HtmlEncode(datdata.Author) + "</author>\n" +
"\t\t<comment>" + HttpUtility.HtmlEncode(datdata.Comment) + "</comment>\n" +
(!String.IsNullOrEmpty(datdata.Type) && datdata.ForcePacking != ForcePacking.Unzip ?
"\t\t<flags>\n" +
(!String.IsNullOrEmpty(datdata.Type) ? "\t\t\t<flag name=\"type\" value=\"" + datdata.Type + "\"/>\n" : "") +
(datdata.ForcePacking == ForcePacking.Unzip ? "\t\t\t<flag name=\"forcepacking\" value=\"unzip\"/>\n" : "") +
"\t\t</flags>\n" : "") +
"\t</header>\n" +
"\t<data>\n";
break;
case OutputFormat.Xml:
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(datdata.Name) + "</name>\n" +
"\t\t<description>" + HttpUtility.HtmlEncode(datdata.Description) + "</description>\n" +
"\t\t<category>" + HttpUtility.HtmlEncode(datdata.Category) + "</category>\n" +
"\t\t<version>" + HttpUtility.HtmlEncode(datdata.Version) + "</version>\n" +
"\t\t<date>" + HttpUtility.HtmlEncode(datdata.Date) + "</date>\n" +
"\t\t<author>" + HttpUtility.HtmlEncode(datdata.Author) + "</author>\n" +
"\t\t<email>" + HttpUtility.HtmlEncode(datdata.Email) + "</email>\n" +
"\t\t<homepage>" + HttpUtility.HtmlEncode(datdata.Homepage) + "</homepage>\n" +
"\t\t<url>" + HttpUtility.HtmlEncode(datdata.Url) + "</url>\n" +
"\t\t<comment>" + HttpUtility.HtmlEncode(datdata.Comment) + "</comment>\n" +
(!String.IsNullOrEmpty(datdata.Type) ? "\t\t<type>" + datdata.Type + "</type>\n" : "") +
(datdata.ForcePacking == ForcePacking.Unzip ? "\t\t<clrmamepro forcepacking=\"unzip\" />\n" : "") +
"\t</header>\n";
break;
}
// Write the header out
sw.Write(header);
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <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="datdata">DatData object representing DAT information</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>
public static int WriteStartGame(StreamWriter sw, RomData rom, List<string> newsplit, string lastgame, DatData datdata, int depth, int last, Logger logger)
{
try
{
// No game should start with a path separator
if (rom.Game.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
rom.Game = rom.Game.Substring(1);
}
string state = "";
switch (datdata.OutputFormat)
{
case OutputFormat.ClrMamePro:
state += "game (\n\tname \"" + rom.Game + "\"\n" +
"\tdescription \"" + rom.Game + "\"\n";
break;
case OutputFormat.SabreDat:
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;
case OutputFormat.Xml:
state += "\t<machine name=\"" + HttpUtility.HtmlEncode(rom.Game) + "\">\n" +
"\t\t<description>" + HttpUtility.HtmlEncode(rom.Game) + "</description>\n";
break;
}
sw.Write(state);
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return depth;
}
return depth;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <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="datdata">DatData object representing DAT information</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>
public static int WriteEndGame(StreamWriter sw, RomData rom, List<string> splitpath, List<string> newsplit, string lastgame, DatData datdata, int depth, out int last, Logger logger)
{
last = 0;
try
{
string state = "";
switch (datdata.OutputFormat)
{
case OutputFormat.ClrMamePro:
state += ")\n";
break;
case OutputFormat.SabreDat:
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;
case OutputFormat.Xml:
state += "\t</machine>\n";
break;
}
sw.Write(state);
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return depth;
}
return depth;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="lastgame">The name of the last game to be output</param>
/// <param name="datdata">DatData object representing DAT information</param>
/// <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>
public static bool WriteRomData(StreamWriter sw, RomData rom, string lastgame, DatData datdata, int depth, Logger logger)
{
try
{
string state = "";
switch (datdata.OutputFormat)
{
case OutputFormat.ClrMamePro:
state += "\t" + rom.Type + " ( name \"" + rom.Name + "\"" +
(rom.Size != -1 ? " size " + rom.Size : "") +
(!String.IsNullOrEmpty(rom.CRC) ? " crc " + rom.CRC.ToLowerInvariant() : "") +
(!String.IsNullOrEmpty(rom.MD5) ? " md5 " + rom.MD5.ToLowerInvariant() : "") +
(!String.IsNullOrEmpty(rom.SHA1) ? " sha1 " + rom.SHA1.ToLowerInvariant() : "") +
(!String.IsNullOrEmpty(rom.Date) ? " date \"" + rom.Date + "\"" : "") +
(rom.Nodump ? " flags nodump" : "") +
" )\n";
break;
case OutputFormat.MissFile:
string pre = datdata.Prefix + (datdata.Quotes || datdata.TSV ? "\"" : "");
string post = (datdata.Quotes || datdata.TSV ? "\"" : "") + datdata.Postfix;
// Check for special strings in prefix and postfix
pre = pre.Replace("%crc%", rom.CRC).Replace("%md5%", rom.MD5).Replace("%sha1%", rom.SHA1).Replace("%size%", rom.Size.ToString());
post = post.Replace("%crc%", rom.CRC).Replace("%md5%", rom.MD5).Replace("%sha1%", rom.SHA1).Replace("%size%", rom.Size.ToString());
// If we're in Romba mode, the state is consistent
if (datdata.Romba)
{
// We can only write out if there's a SHA-1
if (rom.SHA1 != "")
{
string name = rom.SHA1.Substring(0, 2) + "/" + rom.SHA1.Substring(2, 2) + "/" + rom.SHA1.Substring(4, 2) + "/" +
rom.SHA1.Substring(6, 2) + "/" + rom.SHA1 + ".gz";
state += pre + name + post + "\n";
}
}
// If we're in TSV mode, similarly the state is consistent
else if (datdata.TSV)
{
string inline = datdata.FileName + "\t" + datdata.Name + "\t" + datdata.Description + "\t" + rom.Game + "\t" + rom.Game + "\t" +
rom.Type + "\t" + (rom.Type == "rom" ? rom.Name : "") + "\t" + (rom.Type == "disk" ? rom.Name : "") + "\t" + rom.Size + "\t" +
rom.CRC + "\t" + rom.MD5 + "\t" + rom.SHA1 + "\t" + (rom.Nodump ? "Nodump" : "");
state += pre + inline + post + "\n";
}
// Otherwise, use any flags
else
{
string name = (datdata.UseGame ? rom.Game : rom.Name);
if (datdata.RepExt != "")
{
string dir = Path.GetDirectoryName(name);
dir = (dir.EndsWith(Path.DirectorySeparatorChar.ToString()) ? dir : dir + Path.DirectorySeparatorChar);
dir = (dir.StartsWith(Path.DirectorySeparatorChar.ToString()) ? dir.Remove(0, 1) : dir);
name = dir + Path.GetFileNameWithoutExtension(name) + datdata.RepExt;
}
if (datdata.AddExt != "")
{
name += datdata.AddExt;
}
if (!datdata.UseGame && datdata.GameName)
{
name = (rom.Game.EndsWith(Path.DirectorySeparatorChar.ToString()) ? rom.Game : rom.Game + Path.DirectorySeparatorChar) + name;
}
if (datdata.UseGame && rom.Game != lastgame)
{
state += pre + name + post + "\n";
lastgame = rom.Game;
}
else if (!datdata.UseGame)
{
state += pre + name + post + "\n";
}
}
break;
case OutputFormat.RomCenter:
state += "¬¬¬" + HttpUtility.HtmlEncode(rom.Game) +
"¬" + HttpUtility.HtmlEncode(rom.Game) +
"¬" + HttpUtility.HtmlEncode(rom.Name) +
"¬" + rom.CRC.ToLowerInvariant() +
"¬" + (rom.Size != -1 ? rom.Size.ToString() : "") + "¬¬¬\n";
break;
case OutputFormat.SabreDat:
string prefix = "";
for (int i = 0; i < depth; i++)
{
prefix += "\t";
}
state += prefix;
state += "<file type=\"" + rom.Type + "\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\"" +
(rom.Size != -1 ? " size=\"" + rom.Size + "\"" : "") +
(!String.IsNullOrEmpty(rom.CRC) ? " crc=\"" + rom.CRC.ToLowerInvariant() + "\"" : "") +
(!String.IsNullOrEmpty(rom.MD5) ? " md5=\"" + rom.MD5.ToLowerInvariant() + "\"" : "") +
(!String.IsNullOrEmpty(rom.SHA1) ? " sha1=\"" + rom.SHA1.ToLowerInvariant() + "\"" : "") +
(!String.IsNullOrEmpty(rom.Date) ? " date=\"" + rom.Date + "\"" : "") +
(rom.Nodump ? prefix + "/>\n" + prefix + "\t<flags>\n" +
prefix + "\t\t<flag name=\"status\" value=\"nodump\"/>\n" +
prefix + "\t</flags>\n" +
prefix + "</file>\n" :
"/>\n");
break;
case OutputFormat.Xml:
state += "\t\t<" + rom.Type + " name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\"" +
(rom.Size != -1 ? " size=\"" + rom.Size + "\"" : "") +
(!String.IsNullOrEmpty(rom.CRC) ? " crc=\"" + rom.CRC.ToLowerInvariant() + "\"" : "") +
(!String.IsNullOrEmpty(rom.MD5) ? " md5=\"" + rom.MD5.ToLowerInvariant() + "\"" : "") +
(!String.IsNullOrEmpty(rom.SHA1) ? " sha1=\"" + rom.SHA1.ToLowerInvariant() + "\"" : "") +
(!String.IsNullOrEmpty(rom.Date) ? " date=\"" + rom.Date + "\"" : "") +
(rom.Nodump ? " status=\"nodump\"" : "") +
"/>\n";
break;
}
sw.Write(state);
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="datdata">DatData object representing DAT information</param>
/// /// <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>
public static bool WriteFooter(StreamWriter sw, DatData datdata, int depth, Logger logger)
{
try
{
string footer = "";
// If we have roms, output the full footer
if (datdata.Roms != null && datdata.Roms.Count > 0)
{
switch (datdata.OutputFormat)
{
case OutputFormat.ClrMamePro:
footer = ")";
break;
case OutputFormat.SabreDat:
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>";
break;
case OutputFormat.Xml:
footer = "\t</machine>\n</datafile>";
break;
}
}
// Otherwise, output the abbreviated form
else
{
switch (datdata.OutputFormat)
{
case OutputFormat.SabreDat:
case OutputFormat.Xml:
footer = "</datafile>";
break;
}
}
// Write the footer out
sw.Write(footer);
}
catch (Exception ex)
{
logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

147
SabreHelper/Tools/Stats.cs Normal file
View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using SabreTools.Helper;
namespace SabreTools
{
/// <summary>
/// Get statistics on one or more DAT files
/// </summary>
public class Stats
{
// Private instance variables
private List<String> _inputs;
private bool _single;
private Logger _logger;
/// <summary>
/// Create a new UncompressedSize object
/// </summary>
/// <param name="inputs">List of files and folders to parse</param>
/// <param name="single">True if single DAT stats are output, false otherwise</param>
/// <param name="logger">Logger object for file and console output</param>
public Stats(List<String> inputs, bool single, Logger logger)
{
_inputs = inputs;
_single = single;
_logger = logger;
}
/// <summary>
/// Output all requested statistics
/// </summary>
/// <returns>True if output succeeded, false otherwise</returns>
public bool Process()
{
// 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 totalNodump = 0;
/// Now process each of the input files
foreach (string filename in _inputs)
{
_logger.User("Beginning stat collection for '" + filename + "'");
List<String> games = new List<String>();
DatData datdata = new DatData();
datdata = DatTools.Parse(filename, 0, 0, datdata, _logger);
SortedDictionary<string, List<RomData>> newroms = DatTools.BucketByGame(datdata.Roms, false, true, _logger);
// Output single DAT stats (if asked)
if (_single)
{
_logger.User(@"\nFor file '" + filename + @"':
--------------------------------------------------");
OutputStats(datdata, _logger);
}
else
{
_logger.User("\nAdding stats for file '" + filename + "'");
}
// Add single DAT stats to totals
totalSize += datdata.TotalSize;
totalGame += newroms.Count;
totalRom += datdata.RomCount;
totalDisk += datdata.DiskCount;
totalCRC += datdata.CRCCount;
totalMD5 += datdata.MD5Count;
totalSHA1 += datdata.SHA1Count;
totalNodump += datdata.NodumpCount;
}
// Output total DAT stats
if (!_single) { _logger.User(""); }
DatData totaldata = new DatData
{
TotalSize = totalSize,
RomCount = totalRom,
DiskCount = totalDisk,
CRCCount = totalCRC,
MD5Count = totalMD5,
SHA1Count = totalSHA1,
NodumpCount = totalNodump,
};
_logger.User(@"For ALL DATs found
--------------------------------------------------");
OutputStats(totaldata, _logger);
_logger.User(@"
Please check the log folder if the stats scrolled offscreen");
return true;
}
/// <summary>
/// Output the stats in a human-readable format
/// </summary>
/// <param name="datdata">DatData object to read stats from</param>
/// <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>
public static void OutputStats(DatData datdata, Logger logger, bool recalculate = false)
{
if (recalculate)
{
// Wipe out any stats already there
datdata.RomCount = 0;
datdata.DiskCount = 0;
datdata.TotalSize = 0;
datdata.CRCCount = 0;
datdata.MD5Count = 0;
datdata.SHA1Count = 0;
datdata.NodumpCount = 0;
// Loop through and add
foreach (List<RomData> roms in datdata.Roms.Values)
{
foreach (RomData rom in roms)
{
datdata.RomCount += (rom.Type == "rom" ? 1 : 0);
datdata.DiskCount += (rom.Type == "disk" ? 1 : 0);
datdata.TotalSize += rom.Size;
datdata.CRCCount += (String.IsNullOrEmpty(rom.CRC) ? 0 : 1);
datdata.MD5Count += (String.IsNullOrEmpty(rom.MD5) ? 0 : 1);
datdata.SHA1Count += (String.IsNullOrEmpty(rom.SHA1) ? 0 : 1);
datdata.NodumpCount += (rom.Nodump ? 1 : 0);
}
}
}
SortedDictionary<string, List<RomData>> newroms = DatTools.BucketByGame(datdata.Roms, false, true, logger);
logger.User(@" Uncompressed size: " + Style.GetBytesReadable(datdata.TotalSize) + @"
Games found: " + newroms.Count + @"
Roms found: " + datdata.RomCount + @"
Disks found: " + datdata.DiskCount + @"
Roms with CRC: " + datdata.CRCCount + @"
Roms with MD5: " + datdata.MD5Count + @"
Roms with SHA-1: " + datdata.SHA1Count + @"
Roms with Nodump status: " + datdata.NodumpCount + @"
");
}
}
}

256
SabreHelper/Tools/Style.cs Normal file
View File

@@ -0,0 +1,256 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
namespace SabreTools.Helper
{
/// <summary>
/// Include character normalization and replacement mappings
/// </summary>
public class Style
{
/// <summary>
/// Replace accented characters
/// </summary>
/// <param name="input">String to be parsed</param>
/// <returns>String with characters replaced</returns>
public static string NormalizeChars(string input)
{
string[,] charmap = {
{ "Á", "A" }, { "á", "a" },
{ "À", "A" }, { "à", "a" },
{ "Â", "A" }, { "â", "a" },
{ "Ä", "Ae" }, { "ä", "ae" },
{ "Ã", "A" }, { "ã", "a" },
{ "Å", "A" }, { "å", "a" },
{ "Æ", "Ae" }, { "æ", "ae" },
{ "Ç", "C" }, { "ç", "c" },
{ "Ð", "D" }, { "ð", "d" },
{ "É", "E" }, { "é", "e" },
{ "È", "E" }, { "è", "e" },
{ "Ê", "E" }, { "ê", "e" },
{ "Ë", "E" }, { "ë", "e" },
{ "ƒ", "f" },
{ "Í", "I" }, { "í", "i" },
{ "Ì", "I" }, { "ì", "i" },
{ "Î", "I" }, { "î", "i" },
{ "Ï", "I" }, { "ï", "i" },
{ "Ñ", "N" }, { "ñ", "n" },
{ "Ó", "O" }, { "ó", "o" },
{ "Ò", "O" }, { "ò", "o" },
{ "Ô", "O" }, { "ô", "o" },
{ "Ö", "Oe" }, { "ö", "oe" },
{ "Õ", "O" }, { "õ", "o" },
{ "Ø", "O" }, { "ø", "o" },
{ "Š", "S" }, { "š", "s" },
{ "ß", "ss" },
{ "Þ", "B" }, { "þ", "b" },
{ "Ú", "U" }, { "ú", "u" },
{ "Ù", "U" }, { "ù", "u" },
{ "Û", "U" }, { "û", "u" },
{ "Ü", "Ue" }, { "ü", "ue" },
{ "ÿ", "y" },
{ "Ý", "Y" }, { "ý", "y" },
{ "Ž", "Z" }, { "ž", "z" },
};
for (int i = 0; i < charmap.GetLength(0); i++)
{
input = input.Replace(charmap[i, 0], charmap[i, 1]);
}
return input;
}
/// <summary>
/// Replace special characters and patterns
/// </summary>
/// <param name="input">String to be parsed</param>
/// <returns>String with characters replaced</returns>
public static string SearchPattern(string input)
{
string[,] charmap = {
{ @"~", " - " },
{ @"_", " " },
{ @":", " " },
{ @">", ")" },
{ @"<", "(" },
{ @"\|", "-" },
{ "\"", "'" },
{ @"\*", "." },
{ @"\\", "-" },
{ @"/", "-" },
{ @"\?", " " },
{ @"\(([^)(]*)\(([^)]*)\)([^)(]*)\)", " " },
{ @"\(([^)]+)\)", " " },
{ @"\[([^]]+)\]", " " },
{ @"\{([^}]+)\}", " " },
{ @"(ZZZJUNK|ZZZ-UNK-|ZZZ-UNK |zzz unknow |zzz unk |Copy of |[.][a-z]{3}[.][a-z]{3}[.]|[.][a-z]{3}[.])", " " },
{ @" (r|rev|v|ver)\s*[\d\.]+[^\s]*", " " },
{ @"(( )|(\A))(\d{6}|\d{8})(( )|(\Z))", " " },
{ @"(( )|(\A))(\d{1,2})-(\d{1,2})-(\d{4}|\d{2})", " " },
{ @"(( )|(\A))(\d{4}|\d{2})-(\d{1,2})-(\d{1,2})", " " },
{ @"[-]+", "-" },
{ @"\A\s*\)", " " },
{ @"\A\s*(,|-)", " " },
{ @"\s+", " " },
{ @"\s+,", "," },
{ @"\s*(,|-)\s*\Z", " " },
};
for (int i = 0; i < charmap.GetLength(0); i++)
{
input = Regex.Replace(input, charmap[i, 0], charmap[i, 1]);
}
return input;
}
/// <summary>
/// Convert Cyrillic lettering to Latin lettering
/// </summary>
/// <param name="input">String to be parsed</param>
/// <returns>String with characters replaced</returns>
public static string RussianToLatin(string input)
{
string [,] charmap = {
{ "А", "A" }, { "Б", "B" }, { "В", "V" }, { "Г", "G" }, { "Д", "D" },
{ "Е", "E" }, { "Ё", "Yo" }, { "Ж", "Zh" }, { "З", "Z" }, { "И", "I" },
{ "Й", "J" }, { "К", "K" }, { "Л", "L" }, { "М", "M" }, { "Н", "N" },
{ "О", "O" }, { "П", "P" }, { "Р", "R" }, { "С", "S" }, { "Т", "T" },
{ "У", "U" }, { "Ф", "f" }, { "Х", "Kh" }, { "Ц", "Ts" }, { "Ч", "Ch" },
{ "Ш", "Sh" }, { "Щ", "Sch" }, { "Ъ", "" }, { "Ы", "y" }, { "Ь", "" },
{ "Э", "e" }, { "Ю", "yu" }, { "Я", "ya" }, { "а", "a" }, { "б", "b" },
{ "в", "v" }, { "г", "g" }, { "д", "d" }, { "е", "e" }, { "ё", "yo" },
{ "ж", "zh" }, { "з", "z" }, { "и", "i" }, { "й", "j" }, { "к", "k" },
{ "л", "l" }, { "м", "m" }, { "н", "n" }, { "о", "o" }, { "п", "p" },
{ "р", "r" }, { "с", "s" }, { "т", "t" }, { "у", "u" }, { "ф", "f" },
{ "х", "kh" }, { "ц", "ts" }, { "ч", "ch" }, { "ш", "sh" }, { "щ", "sch" },
{ "ъ", "" }, { "ы", "y" }, { "ь", "" }, { "э", "e" }, { "ю", "yu" },
{ "я", "ya" },
};
for (int i = 0; i < charmap.GetLength(0); i++)
{
input = input.Replace(charmap[i, 0], charmap[i, 1]);
}
return input;
}
/// <summary>
/// Generate a proper outfile name based on a DAT and output directory
/// </summary>
/// <param name="outDir">Output directory</param>
/// <param name="datdata">DAT information</param>
/// <returns>String representing the proper name</returns>
public static string CreateOutfileName(string outDir, DatData datdata)
{
// Double check the outdir for the end delim
if (!outDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
outDir += Path.DirectorySeparatorChar;
}
// Get the extension from the output type
string extension = "";
switch(datdata.OutputFormat)
{
case OutputFormat.ClrMamePro:
case OutputFormat.DOSCenter:
case OutputFormat.RomCenter:
extension = ".dat";
break;
case OutputFormat.MissFile:
extension = ".txt";
break;
case OutputFormat.SabreDat:
case OutputFormat.Xml:
extension = ".xml";
break;
}
string filename = (String.IsNullOrEmpty(datdata.FileName) ? datdata.Description : datdata.FileName);
string outfile = outDir + filename + extension;
outfile = (outfile.Contains(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString()) ?
outfile.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString()) :
outfile);
Console.WriteLine(outfile);
return outfile;
}
/// <summary>
/// Returns the human-readable file size for an arbitrary, 64-bit file size
/// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB"
/// </summary>
/// <param name="input"></param>
/// <returns>Human-readable file size</returns>
/// <link>http://www.somacon.com/p576.php</link>
public static string GetBytesReadable(long input)
{
// Get absolute value
long absolute_i = (input < 0 ? -input : input);
// Determine the suffix and readable value
string suffix;
double readable;
if (absolute_i >= 0x1000000000000000) // Exabyte
{
suffix = "EB";
readable = (input >> 50);
}
else if (absolute_i >= 0x4000000000000) // Petabyte
{
suffix = "PB";
readable = (input >> 40);
}
else if (absolute_i >= 0x10000000000) // Terabyte
{
suffix = "TB";
readable = (input >> 30);
}
else if (absolute_i >= 0x40000000) // Gigabyte
{
suffix = "GB";
readable = (input >> 20);
}
else if (absolute_i >= 0x100000) // Megabyte
{
suffix = "MB";
readable = (input >> 10);
}
else if (absolute_i >= 0x400) // Kilobyte
{
suffix = "KB";
readable = input;
}
else
{
return input.ToString("0 B"); // Byte
}
// Divide by 1024 to get fractional value
readable = (readable / 1024);
// Return formatted number with suffix
return readable.ToString("0.### ") + suffix;
}
/// <summary>
/// Converts a string to sentence case.
/// </summary>
/// <param name="input">The string to convert.</param>
/// <returns>A string representing a sentence case string</returns>
/// <remarks>http://stackoverflow.com/questions/3141426/net-method-to-convert-a-string-to-sentence-case</remarks>
public static string SentenceCase(string input)
{
if (input.Length < 1)
{
return input;
}
string sentence = input.ToLower();
return sentence[0].ToString().ToUpper() + sentence.Substring(1);
}
}
}