Remove .NET Framework 4.6.2/4.7.2 (#24)

* Remove < .NET 4.8, general cleanup

* Abstract

* Tango

* Banner

* Scan no more

* Common

* Application

* Access

* Filter-feeder

* Graffiti

* Paint-over

* Law and Order

* XOR-o

* Unused staircase

* Maybe

* Maybe not

* Delete this

* The word is "no"

* Emit

* Improper

* Aye aye

* Fence

* Barrier

* Monkey

* Pail

* Lines
This commit is contained in:
Matt Nadareski
2020-07-15 09:41:59 -07:00
committed by GitHub
parent 1a718a3915
commit 4e406604c2
82 changed files with 8975 additions and 11172 deletions

Binary file not shown.

View File

@@ -21,7 +21,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public AttractMode(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -29,25 +29,19 @@ namespace SabreTools.Library.DatFiles
/// Parse an AttractMode DAT and return all found games 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Open a file reader
Encoding enc = Utilities.GetEncoding(filename);
StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc);
Encoding enc = FileExtensions.GetEncoding(filename);
StreamReader sr = new StreamReader(FileExtensions.TryOpenRead(filename), enc);
sr.ReadLine(); // Skip the first line since it's the header
while (!sr.EndOfStream)
@@ -83,7 +77,9 @@ namespace SabreTools.Library.DatFiles
Size = Constants.SizeZero,
CRC = Constants.CRCZero,
MD5 = Constants.MD5Zero,
#if NET_FRAMEWORK
RIPEMD160 = Constants.RIPEMD160Zero,
#endif
SHA1 = Constants.SHA1Zero,
ItemStatus = ItemStatus.None,
@@ -93,10 +89,13 @@ namespace SabreTools.Library.DatFiles
Year = gameinfo[4],
Manufacturer = gameinfo[5],
Comment = gameinfo[15],
IndexId = indexId,
IndexSource = filename,
};
// Now process and add the rom
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
}
sr.Dispose();
@@ -113,7 +112,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -122,10 +121,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false));
svw.Quotes = false;
svw.Separator = ';';
svw.VerifyFieldCount = true;
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false))
{
Quotes = false,
Separator = ';',
VerifyFieldCount = true
};
// Write out the header
WriteHeader(svw);
@@ -254,23 +255,23 @@ namespace SabreTools.Library.DatFiles
string[] fields = new string[]
{
datItem.GetField(Field.MachineName, ExcludeFields),
datItem.GetField(Field.Description, ExcludeFields),
FileName,
datItem.GetField(Field.CloneOf, ExcludeFields),
datItem.GetField(Field.Year, ExcludeFields),
datItem.GetField(Field.Manufacturer, ExcludeFields),
string.Empty, // datItem.GetField(Field.Category, ExcludeFields)
string.Empty, // datItem.GetField(Field.Players, ExcludeFields)
string.Empty, // datItem.GetField(Field.Rotation, ExcludeFields)
string.Empty, // datItem.GetField(Field.Control, ExcludeFields)
string.Empty, // datItem.GetField(Field.Status, ExcludeFields)
string.Empty, // datItem.GetField(Field.DisplayCount, ExcludeFields)
string.Empty, // datItem.GetField(Field.DisplayType, ExcludeFields)
string.Empty, // datItem.GetField(Field.AltRomname, ExcludeFields)
string.Empty, // datItem.GetField(Field.AltTitle, ExcludeFields)
datItem.GetField(Field.Comment, ExcludeFields),
string.Empty, // datItem.GetField(Field.Buttons, ExcludeFields)
datItem.GetField(Field.MachineName, DatHeader.ExcludeFields),
datItem.GetField(Field.Description, DatHeader.ExcludeFields),
DatHeader.FileName,
datItem.GetField(Field.CloneOf, DatHeader.ExcludeFields),
datItem.GetField(Field.Year, DatHeader.ExcludeFields),
datItem.GetField(Field.Manufacturer, DatHeader.ExcludeFields),
string.Empty, // datItem.GetField(Field.Category, DatHeader.ExcludeFields)
string.Empty, // datItem.GetField(Field.Players, DatHeader.ExcludeFields)
string.Empty, // datItem.GetField(Field.Rotation, DatHeader.ExcludeFields)
string.Empty, // datItem.GetField(Field.Control, DatHeader.ExcludeFields)
string.Empty, // datItem.GetField(Field.Status, DatHeader.ExcludeFields)
string.Empty, // datItem.GetField(Field.DisplayCount, DatHeader.ExcludeFields)
string.Empty, // datItem.GetField(Field.DisplayType, DatHeader.ExcludeFields)
string.Empty, // datItem.GetField(Field.AltRomname, DatHeader.ExcludeFields)
string.Empty, // datItem.GetField(Field.AltTitle, DatHeader.ExcludeFields)
datItem.GetField(Field.Comment, DatHeader.ExcludeFields),
string.Empty, // datItem.GetField(Field.Buttons, DatHeader.ExcludeFields)
};
svw.WriteValues(fields);

View File

@@ -5,10 +5,10 @@ using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
using SabreTools.Library.Readers;
using SabreTools.Library.Tools;
using SabreTools.Library.Writers;
using NaturalSort;
using SabreTools.Library.Readers;
namespace SabreTools.Library.DatFiles
{
@@ -22,7 +22,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public ClrMamePro(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -30,26 +30,22 @@ namespace SabreTools.Library.DatFiles
/// 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Open a file reader
Encoding enc = Utilities.GetEncoding(filename);
ClrMameProReader cmpr = new ClrMameProReader(Utilities.TryOpenRead(filename), enc);
cmpr.DosCenter = false;
Encoding enc = FileExtensions.GetEncoding(filename);
ClrMameProReader cmpr = new ClrMameProReader(FileExtensions.TryOpenRead(filename), enc)
{
DosCenter = false
};
while (!cmpr.EndOfStream)
{
@@ -72,10 +68,10 @@ namespace SabreTools.Library.DatFiles
case "set": // Used by the most ancient DATs
case "game": // Used by most CMP DATs
case "machine": // Possibly used by MAME CMP DATs
ReadSet(cmpr, false, filename, sysid, srcid, clean, remUnicode);
ReadSet(cmpr, false, filename, indexId);
break;
case "resource": // Used by some other DATs to denote a BIOS set
ReadSet(cmpr, true, filename, sysid, srcid, clean, remUnicode);
ReadSet(cmpr, true, filename, indexId);
break;
default:
@@ -123,63 +119,63 @@ namespace SabreTools.Library.DatFiles
switch (itemKey)
{
case "name":
Name = (string.IsNullOrWhiteSpace(Name) ? itemVal : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? itemVal : DatHeader.Name);
superdat = superdat || itemVal.Contains(" - SuperDAT");
if (keep && superdat)
Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? "SuperDAT" : DatHeader.Type);
break;
case "description":
Description = (string.IsNullOrWhiteSpace(Description) ? itemVal : Description);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? itemVal : DatHeader.Description);
break;
case "rootdir":
RootDir = (string.IsNullOrWhiteSpace(RootDir) ? itemVal : RootDir);
DatHeader.RootDir = (string.IsNullOrWhiteSpace(DatHeader.RootDir) ? itemVal : DatHeader.RootDir);
break;
case "category":
Category = (string.IsNullOrWhiteSpace(Category) ? itemVal : Category);
DatHeader.Category = (string.IsNullOrWhiteSpace(DatHeader.Category) ? itemVal : DatHeader.Category);
break;
case "version":
Version = (string.IsNullOrWhiteSpace(Version) ? itemVal : Version);
DatHeader.Version = (string.IsNullOrWhiteSpace(DatHeader.Version) ? itemVal : DatHeader.Version);
break;
case "date":
Date = (string.IsNullOrWhiteSpace(Date) ? itemVal : Date);
DatHeader.Date = (string.IsNullOrWhiteSpace(DatHeader.Date) ? itemVal : DatHeader.Date);
break;
case "author":
Author = (string.IsNullOrWhiteSpace(Author) ? itemVal : Author);
DatHeader.Author = (string.IsNullOrWhiteSpace(DatHeader.Author) ? itemVal : DatHeader.Author);
break;
case "email":
Email = (string.IsNullOrWhiteSpace(Email) ? itemVal : Email);
DatHeader.Email = (string.IsNullOrWhiteSpace(DatHeader.Email) ? itemVal : DatHeader.Email);
break;
case "homepage":
Homepage = (string.IsNullOrWhiteSpace(Homepage) ? itemVal : Homepage);
DatHeader.Homepage = (string.IsNullOrWhiteSpace(DatHeader.Homepage) ? itemVal : DatHeader.Homepage);
break;
case "url":
Url = (string.IsNullOrWhiteSpace(Url) ? itemVal : Url);
DatHeader.Url = (string.IsNullOrWhiteSpace(DatHeader.Url) ? itemVal : DatHeader.Url);
break;
case "comment":
Comment = (string.IsNullOrWhiteSpace(Comment) ? itemVal : Comment);
DatHeader.Comment = (string.IsNullOrWhiteSpace(DatHeader.Comment) ? itemVal : DatHeader.Comment);
break;
case "header":
Header = (string.IsNullOrWhiteSpace(Header) ? itemVal : Header);
DatHeader.Header = (string.IsNullOrWhiteSpace(DatHeader.Header) ? itemVal : DatHeader.Header);
break;
case "type":
Type = (string.IsNullOrWhiteSpace(Type) ? itemVal : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? itemVal : DatHeader.Type);
superdat = superdat || itemVal.Contains("SuperDAT");
break;
case "forcemerging":
if (ForceMerging == ForceMerging.None)
ForceMerging = Utilities.GetForceMerging(itemVal);
if (DatHeader.ForceMerging == ForceMerging.None)
DatHeader.ForceMerging = itemVal.AsForceMerging();
break;
case "forcezipping":
if (ForcePacking == ForcePacking.None)
ForcePacking = Utilities.GetForcePacking(itemVal);
if (DatHeader.ForcePacking == ForcePacking.None)
DatHeader.ForcePacking = itemVal.AsForcePacking();
break;
case "forcepacking":
if (ForcePacking == ForcePacking.None)
ForcePacking = Utilities.GetForcePacking(itemVal);
if (DatHeader.ForcePacking == ForcePacking.None)
DatHeader.ForcePacking = itemVal.AsForcePacking();
break;
}
@@ -192,22 +188,14 @@ namespace SabreTools.Library.DatFiles
/// <param name="cmpr">ClrMameProReader to use to parse the header</param>
/// <param name="resource">True if the item is a resource (bios), false otherwise</param>
/// <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="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadSet(
ClrMameProReader cmpr,
bool resource,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool clean,
bool remUnicode)
int indexId)
{
// Prepare all internal variables
bool containsItems = false;
@@ -297,14 +285,13 @@ namespace SabreTools.Library.DatFiles
}
// Create the proper DatItem based on the type
DatItem item = Utilities.GetDatItem(itemType);
DatItem item = DatItem.Create(itemType);
// Then populate it with information
item.CopyMachineInformation(machine);
item.SystemID = sysid;
item.System = filename;
item.SourceID = srcid;
item.IndexId = indexId;
item.IndexSource = filename;
// Loop through all of the attributes
foreach (var kvp in cmpr.Internal)
@@ -335,53 +322,55 @@ namespace SabreTools.Library.DatFiles
break;
case "crc":
if (item.ItemType == ItemType.Rom)
(item as Rom).CRC = Utilities.CleanHashData(attrVal, Constants.CRCLength);
(item as Rom).CRC = attrVal;
break;
case "md5":
if (item.ItemType == ItemType.Rom)
(item as Rom).MD5 = Utilities.CleanHashData(attrVal, Constants.MD5Length);
(item as Rom).MD5 = attrVal;
else if (item.ItemType == ItemType.Disk)
((Disk)item).MD5 = Utilities.CleanHashData(attrVal, Constants.MD5Length);
((Disk)item).MD5 = attrVal;
break;
#if NET_FRAMEWORK
case "ripemd160":
if (item.ItemType == ItemType.Rom)
(item as Rom).RIPEMD160 = Utilities.CleanHashData(attrVal, Constants.RIPEMD160Length);
(item as Rom).RIPEMD160 = attrVal;
else if (item.ItemType == ItemType.Disk)
((Disk)item).RIPEMD160 = Utilities.CleanHashData(attrVal, Constants.RIPEMD160Length);
((Disk)item).RIPEMD160 = attrVal;
break;
#endif
case "sha1":
if (item.ItemType == ItemType.Rom)
(item as Rom).SHA1 = Utilities.CleanHashData(attrVal, Constants.SHA1Length);
(item as Rom).SHA1 = attrVal;
else if (item.ItemType == ItemType.Disk)
((Disk)item).SHA1 = Utilities.CleanHashData(attrVal, Constants.SHA1Length);
((Disk)item).SHA1 = attrVal;
break;
case "sha256":
if (item.ItemType == ItemType.Rom)
((Rom)item).SHA256 = Utilities.CleanHashData(attrVal, Constants.SHA256Length);
((Rom)item).SHA256 = attrVal;
else if (item.ItemType == ItemType.Disk)
((Disk)item).SHA256 = Utilities.CleanHashData(attrVal, Constants.SHA256Length);
((Disk)item).SHA256 = attrVal;
break;
case "sha384":
if (item.ItemType == ItemType.Rom)
((Rom)item).SHA384 = Utilities.CleanHashData(attrVal, Constants.SHA384Length);
((Rom)item).SHA384 = attrVal;
else if (item.ItemType == ItemType.Disk)
((Disk)item).SHA384 = Utilities.CleanHashData(attrVal, Constants.SHA384Length);
((Disk)item).SHA384 = attrVal;
break;
case "sha512":
if (item.ItemType == ItemType.Rom)
((Rom)item).SHA512 = Utilities.CleanHashData(attrVal, Constants.SHA512Length);
((Rom)item).SHA512 = attrVal;
else if (item.ItemType == ItemType.Disk)
((Disk)item).SHA512 = Utilities.CleanHashData(attrVal, Constants.SHA512Length);
((Disk)item).SHA512 = attrVal;
break;
case "status":
ItemStatus tempFlagStatus = Utilities.GetItemStatus(attrVal);
ItemStatus tempFlagStatus = attrVal.AsItemStatus();
if (item.ItemType == ItemType.Rom)
((Rom)item).ItemStatus = tempFlagStatus;
else if (item.ItemType == ItemType.Disk)
@@ -397,9 +386,9 @@ namespace SabreTools.Library.DatFiles
break;
case "default":
if (item.ItemType == ItemType.BiosSet)
((BiosSet)item).Default = Utilities.GetYesNo(attrVal.ToLowerInvariant());
((BiosSet)item).Default = attrVal.ToLowerInvariant().AsYesNo();
else if (item.ItemType == ItemType.Release)
((Release)item).Default = Utilities.GetYesNo(attrVal.ToLowerInvariant());
((Release)item).Default = attrVal.ToLowerInvariant().AsYesNo();
break;
case "description":
@@ -421,7 +410,7 @@ namespace SabreTools.Library.DatFiles
}
// Now process and add the rom
ParseAddHelper(item, clean, remUnicode);
ParseAddHelper(item);
}
}
@@ -430,15 +419,14 @@ namespace SabreTools.Library.DatFiles
{
Blank blank = new Blank()
{
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
blank.CopyMachineInformation(machine);
// Now process and add the rom
ParseAddHelper(blank, clean, remUnicode);
ParseAddHelper(blank);
}
}
@@ -453,7 +441,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -462,8 +450,10 @@ namespace SabreTools.Library.DatFiles
return false;
}
ClrMameProWriter cmpw = new ClrMameProWriter(fs, new UTF8Encoding(false));
cmpw.Quotes = true;
ClrMameProWriter cmpw = new ClrMameProWriter(fs, new UTF8Encoding(false))
{
Quotes = true
};
// Write out the header
WriteHeader(cmpw);
@@ -513,7 +503,9 @@ namespace SabreTools.Library.DatFiles
((Rom)rom).Size = Constants.SizeZero;
((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null;
((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null;
#if NET_FRAMEWORK
((Rom)rom).RIPEMD160 = ((Rom)rom).RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null;
#endif
((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null;
((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null;
((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null;
@@ -555,24 +547,24 @@ namespace SabreTools.Library.DatFiles
{
cmpw.WriteStartElement("clrmamepro");
cmpw.WriteStandalone("name", Name);
cmpw.WriteStandalone("description", Description);
if (!string.IsNullOrWhiteSpace(Category))
cmpw.WriteStandalone("category", Category);
cmpw.WriteStandalone("version", Version);
if (!string.IsNullOrWhiteSpace(Date))
cmpw.WriteStandalone("date", Date);
cmpw.WriteStandalone("author", Author);
if (!string.IsNullOrWhiteSpace(Email))
cmpw.WriteStandalone("email", Email);
if (!string.IsNullOrWhiteSpace(Homepage))
cmpw.WriteStandalone("homepage", Homepage);
if (!string.IsNullOrWhiteSpace(Url))
cmpw.WriteStandalone("url", Url);
if (!string.IsNullOrWhiteSpace(Comment))
cmpw.WriteStandalone("comment", Comment);
switch (ForcePacking)
cmpw.WriteStandalone("name", DatHeader.Name);
cmpw.WriteStandalone("description", DatHeader.Description);
if (!string.IsNullOrWhiteSpace(DatHeader.Category))
cmpw.WriteStandalone("category", DatHeader.Category);
cmpw.WriteStandalone("version", DatHeader.Version);
if (!string.IsNullOrWhiteSpace(DatHeader.Date))
cmpw.WriteStandalone("date", DatHeader.Date);
cmpw.WriteStandalone("author", DatHeader.Author);
if (!string.IsNullOrWhiteSpace(DatHeader.Email))
cmpw.WriteStandalone("email", DatHeader.Email);
if (!string.IsNullOrWhiteSpace(DatHeader.Homepage))
cmpw.WriteStandalone("homepage", DatHeader.Homepage);
if (!string.IsNullOrWhiteSpace(DatHeader.Url))
cmpw.WriteStandalone("url", DatHeader.Url);
if (!string.IsNullOrWhiteSpace(DatHeader.Comment))
cmpw.WriteStandalone("comment", DatHeader.Comment);
switch (DatHeader.ForcePacking)
{
case ForcePacking.Unzip:
cmpw.WriteStandalone("forcezipping", "no", false);
@@ -582,7 +574,7 @@ namespace SabreTools.Library.DatFiles
break;
}
switch (ForceMerging)
switch (DatHeader.ForceMerging)
{
case ForceMerging.Full:
cmpw.WriteStandalone("forcemerging", "full", false);
@@ -627,20 +619,20 @@ namespace SabreTools.Library.DatFiles
// Build the state based on excluded fields
cmpw.WriteStartElement(datItem.MachineType == MachineType.Bios ? "resource" : "game");
cmpw.WriteStandalone("name", datItem.GetField(Field.MachineName, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RomOf, ExcludeFields)))
cmpw.WriteStandalone("name", datItem.GetField(Field.MachineName, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RomOf, DatHeader.ExcludeFields)))
cmpw.WriteStandalone("romof", datItem.RomOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, DatHeader.ExcludeFields)))
cmpw.WriteStandalone("cloneof", datItem.CloneOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, DatHeader.ExcludeFields)))
cmpw.WriteStandalone("sampleof", datItem.SampleOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, DatHeader.ExcludeFields)))
cmpw.WriteStandalone("description", datItem.MachineDescription);
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, ExcludeFields)))
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, DatHeader.ExcludeFields)))
cmpw.WriteStandalone("description", datItem.MachineName);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, DatHeader.ExcludeFields)))
cmpw.WriteStandalone("year", datItem.Year);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Manufacturer, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Manufacturer, DatHeader.ExcludeFields)))
cmpw.WriteStandalone("manufacturer", datItem.Manufacturer);
cmpw.Flush();
@@ -665,7 +657,7 @@ namespace SabreTools.Library.DatFiles
try
{
// Build the state based on excluded fields
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, DatHeader.ExcludeFields)))
cmpw.WriteStandalone("sampleof", datItem.SampleOf);
// End game
@@ -706,17 +698,17 @@ namespace SabreTools.Library.DatFiles
{
case ItemType.Archive:
cmpw.WriteStartElement("archive");
cmpw.WriteAttributeString("name", datItem.GetField(Field.Name, ExcludeFields));
cmpw.WriteAttributeString("name", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
cmpw.WriteEndElement();
break;
case ItemType.BiosSet:
var biosSet = datItem as BiosSet;
cmpw.WriteStartElement("biosset");
cmpw.WriteAttributeString("name", biosSet.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(biosSet.GetField(Field.BiosDescription, ExcludeFields)))
cmpw.WriteAttributeString("name", biosSet.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(biosSet.GetField(Field.BiosDescription, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("description", biosSet.Description);
if (!ExcludeFields[(int)Field.Default] && biosSet.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && biosSet.Default != null)
cmpw.WriteAttributeString("default", biosSet.Default.ToString().ToLowerInvariant());
cmpw.WriteEndElement();
break;
@@ -724,20 +716,22 @@ namespace SabreTools.Library.DatFiles
case ItemType.Disk:
var disk = datItem as Disk;
cmpw.WriteStartElement("disk");
cmpw.WriteAttributeString("name", disk.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
cmpw.WriteAttributeString("name", disk.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("md5", disk.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("ripemd160", disk.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("sha1", disk.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("sha256", disk.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("sha384", disk.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("sha512", disk.SHA512.ToLowerInvariant());
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
cmpw.WriteAttributeString("flags", disk.ItemStatus.ToString().ToLowerInvariant());
cmpw.WriteEndElement();
break;
@@ -745,14 +739,14 @@ namespace SabreTools.Library.DatFiles
case ItemType.Release:
var release = datItem as Release;
cmpw.WriteStartElement("release");
cmpw.WriteAttributeString("name", release.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, ExcludeFields)))
cmpw.WriteAttributeString("name", release.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("region", release.Region);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Language, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Language, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("language", release.Language);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("date", release.Date);
if (!ExcludeFields[(int)Field.Default] && release.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && release.Default != null)
cmpw.WriteAttributeString("default", release.Default.ToString().ToLowerInvariant());
cmpw.WriteEndElement();
break;
@@ -760,33 +754,35 @@ namespace SabreTools.Library.DatFiles
case ItemType.Rom:
var rom = datItem as Rom;
cmpw.WriteStartElement("rom");
cmpw.WriteAttributeString("name", rom.GetField(Field.Name, ExcludeFields));
if (!ExcludeFields[(int)Field.Size] && rom.Size != -1)
cmpw.WriteAttributeString("name", rom.GetField(Field.Name, DatHeader.ExcludeFields));
if (!DatHeader.ExcludeFields[(int)Field.Size] && rom.Size != -1)
cmpw.WriteAttributeString("size", rom.Size.ToString());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("crc", rom.CRC.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("md5", rom.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("ripemd160", rom.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("sha1", rom.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("sha256", rom.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("sha384", rom.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("sha512", rom.SHA512.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("date", rom.Date);
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
cmpw.WriteAttributeString("flags", rom.ItemStatus.ToString().ToLowerInvariant());
cmpw.WriteEndElement();
break;
case ItemType.Sample:
cmpw.WriteStartElement("sample");
cmpw.WriteAttributeString("name", datItem.GetField(Field.Name, ExcludeFields));
cmpw.WriteAttributeString("name", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
cmpw.WriteEndElement();
break;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.Library.Data;
using Newtonsoft.Json;
@@ -231,7 +233,6 @@ namespace SabreTools.Library.DatFiles
/// <summary>
/// Clone the current header
/// </summary>
/// <returns></returns>
public object Clone()
{
return new DatHeader()
@@ -273,6 +274,520 @@ namespace SabreTools.Library.DatFiles
};
}
/// <summary>
/// Clone the standard parts of the current header
/// </summary>
public DatHeader CloneStandard()
{
return new DatHeader()
{
FileName = this.FileName,
Name = this.Name,
Description = this.Description,
RootDir = this.RootDir,
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,
DatFormat = this.DatFormat,
ExcludeFields = this.ExcludeFields,
OneRom = this.OneRom,
KeepEmptyGames = this.KeepEmptyGames,
SceneDateStrip = this.SceneDateStrip,
DedupeRoms = this.DedupeRoms,
StripHash = this.StripHash,
};
}
/// <summary>
/// Clone the filtering parts of the current header
/// </summary>
public DatHeader CloneFiltering()
{
return new DatHeader()
{
DatFormat = this.DatFormat,
ExcludeFields = this.ExcludeFields,
OneRom = this.OneRom,
KeepEmptyGames = this.KeepEmptyGames,
SceneDateStrip = this.SceneDateStrip,
DedupeRoms = this.DedupeRoms,
StripHash = this.StripHash,
UseRomName = this.UseRomName,
Prefix = this.Prefix,
Postfix = this.Postfix,
Quotes = this.Quotes,
ReplaceExtension = this.ReplaceExtension,
AddExtension = this.AddExtension,
RemoveExtension = this.RemoveExtension,
GameName = this.GameName,
Romba = this.Romba,
};
}
/// <summary>
/// Overwrite local values from another DatHeader if not default
/// </summary>
/// <param name="datHeader">DatHeader to get the values from</param>
public void ConditionalCopy(DatHeader datHeader)
{
if (!string.IsNullOrWhiteSpace(datHeader.FileName))
this.FileName = datHeader.FileName;
if (!string.IsNullOrWhiteSpace(datHeader.Name))
this.Name = datHeader.Name;
if (!string.IsNullOrWhiteSpace(datHeader.Description))
this.Description = datHeader.Description;
if (!string.IsNullOrWhiteSpace(datHeader.RootDir))
this.RootDir = datHeader.RootDir;
if (!string.IsNullOrWhiteSpace(datHeader.Category))
this.Category = datHeader.Category;
if (!string.IsNullOrWhiteSpace(datHeader.Version))
this.Version = datHeader.Version;
if (!string.IsNullOrWhiteSpace(datHeader.Date))
this.Date = datHeader.Date;
if (!string.IsNullOrWhiteSpace(datHeader.Author))
this.Author = datHeader.Author;
if (!string.IsNullOrWhiteSpace(datHeader.Email))
this.Email = datHeader.Email;
if (!string.IsNullOrWhiteSpace(datHeader.Homepage))
this.Homepage = datHeader.Homepage;
if (!string.IsNullOrWhiteSpace(datHeader.Url))
this.Url = datHeader.Url;
if (!string.IsNullOrWhiteSpace(datHeader.Comment))
this.Comment = datHeader.Comment;
if (!string.IsNullOrWhiteSpace(datHeader.Header))
this.Header = datHeader.Header;
if (!string.IsNullOrWhiteSpace(datHeader.Type))
this.Type = datHeader.Type;
if (datHeader.ForceMerging != ForceMerging.None)
this.ForceMerging = datHeader.ForceMerging;
if (datHeader.ForceNodump != ForceNodump.None)
this.ForceNodump = datHeader.ForceNodump;
if (datHeader.ForcePacking != ForcePacking.None)
this.ForcePacking = datHeader.ForcePacking;
if (datHeader.DatFormat != 0x00)
this.DatFormat = datHeader.DatFormat;
if (datHeader.ExcludeFields != null)
this.ExcludeFields = datHeader.ExcludeFields;
this.OneRom = datHeader.OneRom;
this.KeepEmptyGames = datHeader.KeepEmptyGames;
this.SceneDateStrip = datHeader.SceneDateStrip;
this.DedupeRoms = datHeader.DedupeRoms;
//this.StripHash = datHeader.StripHash;
if (!string.IsNullOrWhiteSpace(datHeader.Prefix))
this.Prefix = datHeader.Prefix;
if (!string.IsNullOrWhiteSpace(datHeader.Postfix))
this.Postfix = datHeader.Postfix;
if (!string.IsNullOrWhiteSpace(datHeader.AddExtension))
this.AddExtension = datHeader.AddExtension;
if (!string.IsNullOrWhiteSpace(datHeader.ReplaceExtension))
this.ReplaceExtension = datHeader.ReplaceExtension;
this.RemoveExtension = datHeader.RemoveExtension;
this.Romba = datHeader.Romba;
this.GameName = datHeader.GameName;
this.Quotes = datHeader.Quotes;
this.UseRomName = datHeader.UseRomName;
}
#endregion
#region Writing
/// <summary>
/// Generate a proper outfile name based on a DAT and output directory
/// </summary>
/// <param name="outDir">Output directory</param>
/// <param name="overwrite">True if we ignore existing files (default), false otherwise</param>
/// <returns>Dictionary of output formats mapped to file names</returns>
public Dictionary<DatFormat, string> CreateOutFileNames(string outDir, bool overwrite = true)
{
// Create the output dictionary
Dictionary<DatFormat, string> outfileNames = new Dictionary<DatFormat, string>();
// Double check the outDir for the end delim
if (!outDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
outDir += Path.DirectorySeparatorChar;
// Get all used extensions
List<string> usedExtensions = new List<string>();
// Get the extensions from the output type
// TODO: Can the system of adding be more automated?
#region .csv
// CSV
if (DatFormat.HasFlag(DatFormat.CSV))
{
outfileNames.Add(DatFormat.CSV, CreateOutFileNamesHelper(outDir, ".csv", overwrite));
usedExtensions.Add(".csv");
};
#endregion
#region .dat
// ClrMamePro
if (DatFormat.HasFlag(DatFormat.ClrMamePro))
{
outfileNames.Add(DatFormat.ClrMamePro, CreateOutFileNamesHelper(outDir, ".dat", overwrite));
usedExtensions.Add(".dat");
};
// RomCenter
if (DatFormat.HasFlag(DatFormat.RomCenter))
{
if (usedExtensions.Contains(".dat"))
{
outfileNames.Add(DatFormat.RomCenter, CreateOutFileNamesHelper(outDir, ".rc.dat", overwrite));
usedExtensions.Add(".rc.dat");
}
else
{
outfileNames.Add(DatFormat.RomCenter, CreateOutFileNamesHelper(outDir, ".dat", overwrite));
usedExtensions.Add(".dat");
}
}
// DOSCenter
if (DatFormat.HasFlag(DatFormat.DOSCenter))
{
if (usedExtensions.Contains(".dat"))
{
outfileNames.Add(DatFormat.DOSCenter, CreateOutFileNamesHelper(outDir, ".dc.dat", overwrite));
usedExtensions.Add(".dc.dat");
}
else
{
outfileNames.Add(DatFormat.DOSCenter, CreateOutFileNamesHelper(outDir, ".dat", overwrite));
usedExtensions.Add(".dat");
}
}
#endregion
#region .json
// JSON
if (DatFormat.HasFlag(DatFormat.Json))
{
outfileNames.Add(DatFormat.Json, CreateOutFileNamesHelper(outDir, ".json", overwrite));
usedExtensions.Add(".json");
}
#endregion
#region .md5
// Redump MD5
if (DatFormat.HasFlag(DatFormat.RedumpMD5))
{
outfileNames.Add(DatFormat.RedumpMD5, CreateOutFileNamesHelper(outDir, ".md5", overwrite));
usedExtensions.Add(".md5");
};
#endregion
#if NET_FRAMEWORK
#region .ripemd160
// Redump RIPEMD160
if (DatFormat.HasFlag(DatFormat.RedumpRIPEMD160))
{
outfileNames.Add(DatFormat.RedumpRIPEMD160, CreateOutFileNamesHelper(outDir, ".ripemd160", overwrite));
usedExtensions.Add(".ripemd160");
};
#endregion
#endif
#region .sfv
// Redump SFV
if (DatFormat.HasFlag(DatFormat.RedumpSFV))
{
outfileNames.Add(DatFormat.RedumpSFV, CreateOutFileNamesHelper(outDir, ".sfv", overwrite));
usedExtensions.Add(".sfv");
};
#endregion
#region .sha1
// Redump SHA-1
if (DatFormat.HasFlag(DatFormat.RedumpSHA1))
{
outfileNames.Add(DatFormat.RedumpSHA1, CreateOutFileNamesHelper(outDir, ".sha1", overwrite));
usedExtensions.Add(".sha1");
};
#endregion
#region .sha256
// Redump SHA-256
if (DatFormat.HasFlag(DatFormat.RedumpSHA256))
{
outfileNames.Add(DatFormat.RedumpSHA256, CreateOutFileNamesHelper(outDir, ".sha256", overwrite));
usedExtensions.Add(".sha256");
};
#endregion
#region .sha384
// Redump SHA-384
if (DatFormat.HasFlag(DatFormat.RedumpSHA384))
{
outfileNames.Add(DatFormat.RedumpSHA384, CreateOutFileNamesHelper(outDir, ".sha384", overwrite));
usedExtensions.Add(".sha384");
};
#endregion
#region .sha512
// Redump SHA-512
if (DatFormat.HasFlag(DatFormat.RedumpSHA512))
{
outfileNames.Add(DatFormat.RedumpSHA512, CreateOutFileNamesHelper(outDir, ".sha512", overwrite));
usedExtensions.Add(".sha512");
};
#endregion
#region .ssv
// SSV
if (DatFormat.HasFlag(DatFormat.SSV))
{
outfileNames.Add(DatFormat.SSV, CreateOutFileNamesHelper(outDir, ".ssv", overwrite));
usedExtensions.Add(".ssv");
};
#endregion
#region .tsv
// TSV
if (DatFormat.HasFlag(DatFormat.TSV))
{
outfileNames.Add(DatFormat.TSV, CreateOutFileNamesHelper(outDir, ".tsv", overwrite));
usedExtensions.Add(".tsv");
};
#endregion
#region .txt
// AttractMode
if (DatFormat.HasFlag(DatFormat.AttractMode))
{
outfileNames.Add(DatFormat.AttractMode, CreateOutFileNamesHelper(outDir, ".txt", overwrite));
usedExtensions.Add(".txt");
}
// MAME Listroms
if (DatFormat.HasFlag(DatFormat.Listrom))
{
if (usedExtensions.Contains(".txt"))
{
outfileNames.Add(DatFormat.Listrom, CreateOutFileNamesHelper(outDir, ".lr.txt", overwrite));
usedExtensions.Add(".lr.txt");
}
else
{
outfileNames.Add(DatFormat.Listrom, CreateOutFileNamesHelper(outDir, ".txt", overwrite));
usedExtensions.Add(".txt");
}
}
// Missfile
if (DatFormat.HasFlag(DatFormat.MissFile))
{
if (usedExtensions.Contains(".txt"))
{
outfileNames.Add(DatFormat.MissFile, CreateOutFileNamesHelper(outDir, ".miss.txt", overwrite));
usedExtensions.Add(".miss.txt");
}
else
{
outfileNames.Add(DatFormat.MissFile, CreateOutFileNamesHelper(outDir, ".txt", overwrite));
usedExtensions.Add(".txt");
}
}
// Everdrive SMDB
if (DatFormat.HasFlag(DatFormat.EverdriveSMDB))
{
if (usedExtensions.Contains(".txt"))
{
outfileNames.Add(DatFormat.EverdriveSMDB, CreateOutFileNamesHelper(outDir, ".smdb.txt", overwrite));
usedExtensions.Add(".smdb.txt");
}
else
{
outfileNames.Add(DatFormat.EverdriveSMDB, CreateOutFileNamesHelper(outDir, ".txt", overwrite));
usedExtensions.Add(".txt");
}
}
#endregion
#region .xml
// Logiqx XML
if (DatFormat.HasFlag(DatFormat.Logiqx))
{
outfileNames.Add(DatFormat.Logiqx, CreateOutFileNamesHelper(outDir, ".xml", overwrite));
}
if (DatFormat.HasFlag(DatFormat.LogiqxDeprecated))
{
outfileNames.Add(DatFormat.LogiqxDeprecated, CreateOutFileNamesHelper(outDir, ".xml", overwrite));
}
// SabreDAT
if (DatFormat.HasFlag(DatFormat.SabreDat))
{
if (usedExtensions.Contains(".xml"))
{
outfileNames.Add(DatFormat.SabreDat, CreateOutFileNamesHelper(outDir, ".sd.xml", overwrite));
usedExtensions.Add(".sd.xml");
}
else
{
outfileNames.Add(DatFormat.SabreDat, CreateOutFileNamesHelper(outDir, ".xml", overwrite));
usedExtensions.Add(".xml");
}
}
// Software List
if (DatFormat.HasFlag(DatFormat.SoftwareList))
{
if (usedExtensions.Contains(".xml"))
{
outfileNames.Add(DatFormat.SoftwareList, CreateOutFileNamesHelper(outDir, ".sl.xml", overwrite));
usedExtensions.Add(".sl.xml");
}
else
{
outfileNames.Add(DatFormat.SoftwareList, CreateOutFileNamesHelper(outDir, ".xml", overwrite));
usedExtensions.Add(".xml");
}
}
// MAME Listxml
if (DatFormat.HasFlag(DatFormat.Listxml))
{
if (usedExtensions.Contains(".xml"))
{
outfileNames.Add(DatFormat.Listxml, CreateOutFileNamesHelper(outDir, ".mame.xml", overwrite));
usedExtensions.Add(".mame.xml");
}
else
{
outfileNames.Add(DatFormat.Listxml, CreateOutFileNamesHelper(outDir, ".xml", overwrite));
usedExtensions.Add(".xml");
}
}
// OfflineList
if (DatFormat.HasFlag(DatFormat.OfflineList))
{
if (usedExtensions.Contains(".xml"))
{
outfileNames.Add(DatFormat.OfflineList, CreateOutFileNamesHelper(outDir, ".ol.xml", overwrite));
usedExtensions.Add(".ol.xml");
}
else
{
outfileNames.Add(DatFormat.OfflineList, CreateOutFileNamesHelper(outDir, ".xml", overwrite));
usedExtensions.Add(".xml");
}
}
// openMSX
if (DatFormat.HasFlag(DatFormat.OpenMSX))
{
if (usedExtensions.Contains(".xml"))
{
outfileNames.Add(DatFormat.OpenMSX, CreateOutFileNamesHelper(outDir, ".msx.xml", overwrite));
usedExtensions.Add(".msx.xml");
}
else
{
outfileNames.Add(DatFormat.OpenMSX, CreateOutFileNamesHelper(outDir, ".xml", overwrite));
usedExtensions.Add(".xml");
}
}
#endregion
return outfileNames;
}
/// <summary>
/// Help generating the outfile name
/// </summary>
/// <param name="outDir">Output directory</param>
/// <param name="extension">Extension to use for the file</param>
/// <param name="overwrite">True if we ignore existing files, false otherwise</param>
/// <returns>String containing the new filename</returns>
private string CreateOutFileNamesHelper(string outDir, string extension, bool overwrite)
{
string filename = (string.IsNullOrWhiteSpace(FileName) ? Description : FileName);
filename = Path.GetFileNameWithoutExtension(filename);
string outfile = $"{outDir}{filename}{extension}";
outfile = outfile.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", Path.DirectorySeparatorChar.ToString());
if (!overwrite)
{
int i = 1;
while (File.Exists(outfile))
{
outfile = $"{outDir}{filename}_{i}{extension}";
outfile = outfile.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", Path.DirectorySeparatorChar.ToString());
i++;
}
}
return outfile;
}
#endregion
#endregion // Instance Methods

View File

@@ -1,5 +1,12 @@
using SabreTools.Library.Data;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
using SabreTools.Library.Reports;
using SabreTools.Library.Tools;
namespace SabreTools.Library.DatFiles
{
@@ -24,6 +31,11 @@ namespace SabreTools.Library.DatFiles
/// </summary>
public StatReportFormat ReportFormat { get; set; } = StatReportFormat.None;
/// <summary>
/// Statistics output file name
/// </summary>
public string ReportName { get; set; } = "report";
/// <summary>
/// Overall item count
/// </summary>
@@ -80,10 +92,12 @@ namespace SabreTools.Library.DatFiles
/// </summary>
public long MD5Count { get; set; } = 0;
#if NET_FRAMEWORK
/// <summary>
/// Number of items with a RIPEMD160 hash
/// </summary>
public long RIPEMD160Count { get; set; } = 0;
#endif
/// <summary>
/// Number of items with a SHA-1 hash
@@ -129,6 +143,8 @@ namespace SabreTools.Library.DatFiles
#region Instance Methods
#region Updates
/// <summary>
/// Add to the statistics given a DatItem
/// </summary>
@@ -155,7 +171,9 @@ namespace SabreTools.Library.DatFiles
if (((Disk)item).ItemStatus != ItemStatus.Nodump)
{
this.MD5Count += (string.IsNullOrWhiteSpace(((Disk)item).MD5) ? 0 : 1);
#if NET_FRAMEWORK
this.RIPEMD160Count += (string.IsNullOrWhiteSpace(((Disk)item).RIPEMD160) ? 0 : 1);
#endif
this.SHA1Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA1) ? 0 : 1);
this.SHA256Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA256) ? 0 : 1);
this.SHA384Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA384) ? 0 : 1);
@@ -177,7 +195,9 @@ namespace SabreTools.Library.DatFiles
this.TotalSize += ((Rom)item).Size;
this.CRCCount += (string.IsNullOrWhiteSpace(((Rom)item).CRC) ? 0 : 1);
this.MD5Count += (string.IsNullOrWhiteSpace(((Rom)item).MD5) ? 0 : 1);
#if NET_FRAMEWORK
this.RIPEMD160Count += (string.IsNullOrWhiteSpace(((Rom)item).RIPEMD160) ? 0 : 1);
#endif
this.SHA1Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA1) ? 0 : 1);
this.SHA256Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA256) ? 0 : 1);
this.SHA384Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA384) ? 0 : 1);
@@ -218,7 +238,9 @@ namespace SabreTools.Library.DatFiles
// Individual hash counts
this.CRCCount += stats.CRCCount;
this.MD5Count += stats.MD5Count;
#if NET_FRAMEWORK
this.RIPEMD160Count += stats.RIPEMD160Count;
#endif
this.SHA1Count += stats.SHA1Count;
this.SHA256Count += stats.SHA256Count;
this.SHA384Count += stats.SHA384Count;
@@ -229,7 +251,43 @@ namespace SabreTools.Library.DatFiles
this.GoodCount += stats.GoodCount;
this.NodumpCount += stats.NodumpCount;
this.VerifiedCount += stats.VerifiedCount;
}
}
/// <summary>
/// Get the highest-order BucketedBy value that represents the statistics
/// </summary>
public BucketedBy GetBestAvailable()
{
// If all items are supposed to have a SHA-512, we sort by that
if (RomCount + DiskCount - NodumpCount == SHA512Count)
return BucketedBy.SHA512;
// If all items are supposed to have a SHA-384, we sort by that
else if (RomCount + DiskCount - NodumpCount == SHA384Count)
return BucketedBy.SHA384;
// If all items are supposed to have a SHA-256, we sort by that
else if (RomCount + DiskCount - NodumpCount == SHA256Count)
return BucketedBy.SHA256;
// If all items are supposed to have a SHA-1, we sort by that
else if (RomCount + DiskCount - NodumpCount == SHA1Count)
return BucketedBy.SHA1;
#if NET_FRAMEWORK
// If all items are supposed to have a RIPEMD160, we sort by that
else if (RomCount + DiskCount - NodumpCount == RIPEMD160Count)
return BucketedBy.RIPEMD160;
#endif
// If all items are supposed to have a MD5, we sort by that
else if (RomCount + DiskCount - NodumpCount == MD5Count)
return BucketedBy.MD5;
// Otherwise, we sort by CRC
else
return BucketedBy.CRC;
}
/// <summary>
/// Remove from the statistics given a DatItem
@@ -257,7 +315,9 @@ namespace SabreTools.Library.DatFiles
if (((Disk)item).ItemStatus != ItemStatus.Nodump)
{
this.MD5Count -= (string.IsNullOrWhiteSpace(((Disk)item).MD5) ? 0 : 1);
#if NET_FRAMEWORK
this.RIPEMD160Count -= (string.IsNullOrWhiteSpace(((Disk)item).RIPEMD160) ? 0 : 1);
#endif
this.SHA1Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA1) ? 0 : 1);
this.SHA256Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA256) ? 0 : 1);
this.SHA384Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA384) ? 0 : 1);
@@ -279,7 +339,9 @@ namespace SabreTools.Library.DatFiles
this.TotalSize -= ((Rom)item).Size;
this.CRCCount -= (string.IsNullOrWhiteSpace(((Rom)item).CRC) ? 0 : 1);
this.MD5Count -= (string.IsNullOrWhiteSpace(((Rom)item).MD5) ? 0 : 1);
#if NET_FRAMEWORK
this.RIPEMD160Count -= (string.IsNullOrWhiteSpace(((Rom)item).RIPEMD160) ? 0 : 1);
#endif
this.SHA1Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA1) ? 0 : 1);
this.SHA256Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA256) ? 0 : 1);
this.SHA384Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA384) ? 0 : 1);
@@ -318,7 +380,9 @@ namespace SabreTools.Library.DatFiles
this.CRCCount = 0;
this.MD5Count = 0;
#if NET_FRAMEWORK
this.RIPEMD160Count = 0;
#endif
this.SHA1Count = 0;
this.SHA256Count = 0;
this.SHA384Count = 0;
@@ -330,6 +394,210 @@ namespace SabreTools.Library.DatFiles
this.VerifiedCount = 0;
}
#endregion
#region Writing
/// <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>
/// <param name="statDatFormat" > Set the statistics output format to use</param>
public static void OutputStats(List<string> inputs, string reportName, string outDir, bool single,
bool baddumpCol, bool nodumpCol, StatReportFormat statDatFormat)
{
// If there's no output format, set the default
if (statDatFormat == StatReportFormat.None)
statDatFormat = StatReportFormat.Textfile;
// Get the proper output file name
if (string.IsNullOrWhiteSpace(reportName))
reportName = "report";
// Get the proper output directory name
outDir = DirectoryExtensions.Ensure(outDir);
// Get the dictionary of desired output report names
Dictionary<StatReportFormat, string> outputs = DatStats.CreateOutStatsNames(outDir, statDatFormat, reportName);
// Make sure we have all files and then order them
List<string> files = DirectoryExtensions.GetFilesOnly(inputs);
files = files
.OrderBy(i => Path.GetDirectoryName(i))
.ThenBy(i => Path.GetFileName(i))
.ToList();
// Get all of the writers that we need
List<BaseReport> reports = outputs.Select(kvp => BaseReport.Create(kvp.Key, kvp.Value, baddumpCol, nodumpCol)).ToList();
// Write the header, if any
reports.ForEach(report => report.WriteHeader());
// Init all total variables
DatStats totalStats = new DatStats();
// Init directory-level variables
string lastdir = null;
string basepath = null;
DatStats dirStats = new DatStats();
// Now process each of the input files
foreach (string file in files)
{
// Get the directory for the current file
string thisdir = Path.GetDirectoryName(file);
basepath = Path.GetDirectoryName(Path.GetDirectoryName(file));
// 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
reports.ForEach(report => report.WriteMidSeparator());
DatFile lastdirdat = DatFile.Create();
reports.ForEach(report => report.ReplaceStatistics($"DIR: {WebUtility.HtmlEncode(lastdir)}", dirStats.GameCount, dirStats));
reports.ForEach(report => report.Write());
// Write the mid-footer, if any
reports.ForEach(report => report.WriteFooterSeparator());
// Write the header, if any
reports.ForEach(report => report.WriteMidHeader());
// Reset the directory stats
dirStats.Reset();
}
Globals.Logger.Verbose($"Beginning stat collection for '{file}'", false);
List<string> games = new List<string>();
DatFile datdata = DatFile.CreateAndParse(file);
datdata.BucketBy(BucketedBy.Game, DedupeType.None, norename: true);
// Output single DAT stats (if asked)
Globals.Logger.User($"Adding stats for file '{file}'\n", false);
if (single)
{
reports.ForEach(report => report.ReplaceStatistics(datdata.DatHeader.FileName, datdata.Keys.Count, datdata.DatStats));
reports.ForEach(report => report.Write());
}
// Add single DAT stats to dir
dirStats.AddStats(datdata.DatStats);
dirStats.GameCount += datdata.Keys.Count();
// Add single DAT stats to totals
totalStats.AddStats(datdata.DatStats);
totalStats.GameCount += datdata.Keys.Count();
// Make sure to assign the new directory
lastdir = thisdir;
}
// Output the directory stats one last time
reports.ForEach(report => report.WriteMidSeparator());
if (single)
{
reports.ForEach(report => report.ReplaceStatistics($"DIR: {WebUtility.HtmlEncode(lastdir)}", dirStats.GameCount, dirStats));
reports.ForEach(report => report.Write());
}
// Write the mid-footer, if any
reports.ForEach(report => report.WriteFooterSeparator());
// Write the header, if any
reports.ForEach(report => report.WriteMidHeader());
// Reset the directory stats
dirStats.Reset();
// Output total DAT stats
reports.ForEach(report => report.ReplaceStatistics("DIR: All DATs", totalStats.GameCount, totalStats));
reports.ForEach(report => report.Write());
// Output footer if needed
reports.ForEach(report => report.WriteFooter());
Globals.Logger.User(@"
Please check the log folder if the stats scrolled offscreen", false);
}
/// <summary>
/// Get the proper extension for the stat output format
/// </summary>
/// <param name="outDir">Output path to use</param>
/// <param name="statDatFormat">StatDatFormat to get the extension for</param>
/// <param name="reportName">Name of the input file to use</param>
/// <returns>Dictionary of output formats mapped to file names</returns>
private static Dictionary<StatReportFormat, string> CreateOutStatsNames(string outDir, StatReportFormat statDatFormat, string reportName, bool overwrite = true)
{
Dictionary<StatReportFormat, string> output = new Dictionary<StatReportFormat, string>();
// First try to create the output directory if we need to
if (!Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
// Double check the outDir for the end delim
if (!outDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
outDir += Path.DirectorySeparatorChar;
// For each output format, get the appropriate stream writer
if (statDatFormat.HasFlag(StatReportFormat.None))
output.Add(StatReportFormat.None, CreateOutStatsNamesHelper(outDir, ".null", reportName, overwrite));
if (statDatFormat.HasFlag(StatReportFormat.Textfile))
output.Add(StatReportFormat.Textfile, CreateOutStatsNamesHelper(outDir, ".txt", reportName, overwrite));
if (statDatFormat.HasFlag(StatReportFormat.CSV))
output.Add(StatReportFormat.CSV, CreateOutStatsNamesHelper(outDir, ".csv", reportName, overwrite));
if (statDatFormat.HasFlag(StatReportFormat.HTML))
output.Add(StatReportFormat.HTML, CreateOutStatsNamesHelper(outDir, ".html", reportName, overwrite));
if (statDatFormat.HasFlag(StatReportFormat.SSV))
output.Add(StatReportFormat.SSV, CreateOutStatsNamesHelper(outDir, ".ssv", reportName, overwrite));
if (statDatFormat.HasFlag(StatReportFormat.TSV))
output.Add(StatReportFormat.TSV, CreateOutStatsNamesHelper(outDir, ".tsv", reportName, overwrite));
return output;
}
/// <summary>
/// Help generating the outstats name
/// </summary>
/// <param name="outDir">Output directory</param>
/// <param name="extension">Extension to use for the file</param>
/// <param name="reportName">Name of the input file to use</param>
/// <param name="overwrite">True if we ignore existing files, false otherwise</param>
/// <returns>String containing the new filename</returns>
private static string CreateOutStatsNamesHelper(string outDir, string extension, string reportName, bool overwrite)
{
string outfile = outDir + reportName + extension;
outfile = outfile.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", Path.DirectorySeparatorChar.ToString());
if (!overwrite)
{
int i = 1;
while (File.Exists(outfile))
{
outfile = $"{outDir}{reportName}_{i}{extension}";
outfile = outfile.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", Path.DirectorySeparatorChar.ToString());
i++;
}
}
return outfile;
}
#endregion
#endregion // Instance Methods
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
@@ -24,7 +23,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public DosCenter(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -32,26 +31,22 @@ namespace SabreTools.Library.DatFiles
/// Parse a DOSCenter 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Open a file reader
Encoding enc = Utilities.GetEncoding(filename);
ClrMameProReader cmpr = new ClrMameProReader(Utilities.TryOpenRead(filename), enc);
cmpr.DosCenter = true;
Encoding enc = FileExtensions.GetEncoding(filename);
ClrMameProReader cmpr = new ClrMameProReader(FileExtensions.TryOpenRead(filename), enc)
{
DosCenter = true
};
while (!cmpr.EndOfStream)
{
@@ -71,7 +66,7 @@ namespace SabreTools.Library.DatFiles
// Sets
case "game":
ReadGame(cmpr, filename, sysid, srcid, clean, remUnicode);
ReadGame(cmpr, filename, indexId);
break;
default:
@@ -116,25 +111,25 @@ namespace SabreTools.Library.DatFiles
switch (itemKey)
{
case "name":
Name = (string.IsNullOrWhiteSpace(Name) ? itemVal : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? itemVal : DatHeader.Name);
break;
case "description":
Description = (string.IsNullOrWhiteSpace(Description) ? itemVal : Description);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? itemVal : DatHeader.Description);
break;
case "dersion":
Version = (string.IsNullOrWhiteSpace(Version) ? itemVal : Version);
DatHeader.Version = (string.IsNullOrWhiteSpace(DatHeader.Version) ? itemVal : DatHeader.Version);
break;
case "date":
Date = (string.IsNullOrWhiteSpace(Date) ? itemVal : Date);
DatHeader.Date = (string.IsNullOrWhiteSpace(DatHeader.Date) ? itemVal : DatHeader.Date);
break;
case "author":
Author = (string.IsNullOrWhiteSpace(Author) ? itemVal : Author);
DatHeader.Author = (string.IsNullOrWhiteSpace(DatHeader.Author) ? itemVal : DatHeader.Author);
break;
case "homepage":
Homepage = (string.IsNullOrWhiteSpace(Homepage) ? itemVal : Homepage);
DatHeader.Homepage = (string.IsNullOrWhiteSpace(DatHeader.Homepage) ? itemVal : DatHeader.Homepage);
break;
case "comment":
Comment = (string.IsNullOrWhiteSpace(Comment) ? itemVal : Comment);
DatHeader.Comment = (string.IsNullOrWhiteSpace(DatHeader.Comment) ? itemVal : DatHeader.Comment);
break;
}
}
@@ -145,21 +140,13 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="cmpr">ClrMameProReader to use to parse the header</param>
/// <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="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadGame(
ClrMameProReader cmpr,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool clean,
bool remUnicode)
int indexId)
{
// Prepare all internal variables
bool containsItems = false;
@@ -208,14 +195,13 @@ namespace SabreTools.Library.DatFiles
containsItems = true;
// Create the proper DatItem based on the type
Rom item = Utilities.GetDatItem(ItemType.Rom) as Rom;
Rom item = DatItem.Create(ItemType.Rom) as Rom;
// Then populate it with information
item.CopyMachineInformation(machine);
item.SystemID = sysid;
item.System = filename;
item.SourceID = srcid;
item.IndexId = indexId;
item.IndexSource = filename;
// Loop through all of the attributes
foreach (var kvp in cmpr.Internal)
@@ -243,7 +229,7 @@ namespace SabreTools.Library.DatFiles
break;
case "crc":
item.CRC = Utilities.CleanHashData(attrVal, Constants.CRCLength);
item.CRC = attrVal;
break;
case "date":
item.Date = attrVal;
@@ -252,7 +238,7 @@ namespace SabreTools.Library.DatFiles
}
// Now process and add the rom
ParseAddHelper(item, clean, remUnicode);
ParseAddHelper(item);
}
}
@@ -261,15 +247,14 @@ namespace SabreTools.Library.DatFiles
{
Blank blank = new Blank()
{
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
blank.CopyMachineInformation(machine);
// Now process and add the rom
ParseAddHelper(blank, clean, remUnicode);
ParseAddHelper(blank);
}
}
@@ -285,7 +270,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -294,8 +279,10 @@ namespace SabreTools.Library.DatFiles
return false;
}
ClrMameProWriter cmpw = new ClrMameProWriter(fs, new UTF8Encoding(false));
cmpw.Quotes = false;
ClrMameProWriter cmpw = new ClrMameProWriter(fs, new UTF8Encoding(false))
{
Quotes = false
};
// Write out the header
WriteHeader(cmpw);
@@ -329,7 +316,7 @@ namespace SabreTools.Library.DatFiles
// 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.MachineName.ToLowerInvariant())
WriteEndGame(cmpw, rom);
WriteEndGame(cmpw);
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
@@ -346,7 +333,9 @@ namespace SabreTools.Library.DatFiles
((Rom)rom).Size = Constants.SizeZero;
((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null;
((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null;
#if NET_FRAMEWORK
((Rom)rom).RIPEMD160 = ((Rom)rom).RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null;
#endif
((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null;
((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null;
((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null;
@@ -387,13 +376,13 @@ namespace SabreTools.Library.DatFiles
try
{
cmpw.WriteStartElement("DOSCenter");
cmpw.WriteStandalone("Name:", Name, false);
cmpw.WriteStandalone("Description:", Description, false);
cmpw.WriteStandalone("Version:", Version, false);
cmpw.WriteStandalone("Date:", Date, false);
cmpw.WriteStandalone("Author:", Author, false);
cmpw.WriteStandalone("Homepage:", Homepage, false);
cmpw.WriteStandalone("Comment:", Comment, false);
cmpw.WriteStandalone("Name:", DatHeader.Name, false);
cmpw.WriteStandalone("Description:", DatHeader.Description, false);
cmpw.WriteStandalone("Version:", DatHeader.Version, false);
cmpw.WriteStandalone("Date:", DatHeader.Date, false);
cmpw.WriteStandalone("Author:", DatHeader.Author, false);
cmpw.WriteStandalone("Homepage:", DatHeader.Homepage, false);
cmpw.WriteStandalone("Comment:", DatHeader.Comment, false);
cmpw.WriteEndElement();
cmpw.Flush();
@@ -422,7 +411,7 @@ namespace SabreTools.Library.DatFiles
// Build the state based on excluded fields
cmpw.WriteStartElement("game");
cmpw.WriteStandalone("name", $"{datItem.GetField(Field.MachineName, ExcludeFields)}.zip", true);
cmpw.WriteStandalone("name", $"{datItem.GetField(Field.MachineName, DatHeader.ExcludeFields)}.zip", true);
cmpw.Flush();
}
@@ -439,9 +428,8 @@ namespace SabreTools.Library.DatFiles
/// Write out Game end using the supplied StreamWriter
/// </summary>
/// <param name="cmpw">ClrMameProWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private bool WriteEndGame(ClrMameProWriter cmpw, DatItem datItem)
private bool WriteEndGame(ClrMameProWriter cmpw)
{
try
{
@@ -483,12 +471,12 @@ namespace SabreTools.Library.DatFiles
case ItemType.Rom:
var rom = datItem as Rom;
cmpw.WriteStartElement("file");
cmpw.WriteAttributeString("name", datItem.GetField(Field.Name, ExcludeFields));
if (!ExcludeFields[(int)Field.Size] && rom.Size != -1)
cmpw.WriteAttributeString("name", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
if (!DatHeader.ExcludeFields[(int)Field.Size] && rom.Size != -1)
cmpw.WriteAttributeString("size", rom.Size.ToString());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("date", rom.Date);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, DatHeader.ExcludeFields)))
cmpw.WriteAttributeString("crc", rom.CRC.ToLowerInvariant());
cmpw.WriteEndElement();
break;

View File

@@ -21,7 +21,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public EverdriveSMDB(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -29,25 +29,19 @@ namespace SabreTools.Library.DatFiles
/// Parse an Everdrive SMDB file and return all found games 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Open a file reader
Encoding enc = Utilities.GetEncoding(filename);
StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc);
Encoding enc = FileExtensions.GetEncoding(filename);
StreamReader sr = new StreamReader(FileExtensions.TryOpenRead(filename), enc);
while (!sr.EndOfStream)
{
@@ -69,18 +63,21 @@ namespace SabreTools.Library.DatFiles
{
Name = gameinfo[1].Substring(fullname[0].Length + 1),
Size = -1, // No size provided, but we don't want the size being 0
CRC = Utilities.CleanHashData(gameinfo[4], Constants.CRCLength),
MD5 = Utilities.CleanHashData(gameinfo[3], Constants.MD5Length),
SHA1 = Utilities.CleanHashData(gameinfo[2], Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(gameinfo[0], Constants.SHA256Length),
CRC = gameinfo[4],
MD5 = gameinfo[3],
SHA1 = gameinfo[2],
SHA256 = gameinfo[0],
ItemStatus = ItemStatus.None,
MachineName = fullname[0],
MachineDescription = fullname[0],
IndexId = indexId,
IndexSource = filename,
};
// Now process and add the rom
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
}
sr.Dispose();
@@ -97,7 +94,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -106,10 +103,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false));
svw.Quotes = false;
svw.Separator = '\t';
svw.VerifyFieldCount = true;
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false))
{
Quotes = false,
Separator = '\t',
VerifyFieldCount = true
};
// Get a properly sorted set of keys
List<string> keys = Keys;
@@ -189,12 +188,12 @@ namespace SabreTools.Library.DatFiles
string[] fields = new string[]
{
rom.GetField(Field.SHA256, ExcludeFields),
$"{rom.GetField(Field.MachineName, ExcludeFields)}/",
rom.GetField(Field.Name, ExcludeFields),
rom.GetField(Field.SHA1, ExcludeFields),
rom.GetField(Field.MD5, ExcludeFields),
rom.GetField(Field.CRC, ExcludeFields),
rom.GetField(Field.SHA256, DatHeader.ExcludeFields),
$"{rom.GetField(Field.MachineName, DatHeader.ExcludeFields)}/",
rom.GetField(Field.Name, DatHeader.ExcludeFields),
rom.GetField(Field.SHA1, DatHeader.ExcludeFields),
rom.GetField(Field.MD5, DatHeader.ExcludeFields),
rom.GetField(Field.CRC, DatHeader.ExcludeFields),
};
svw.WriteValues(fields);

View File

@@ -1,9 +1,14 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
using SabreTools.Library.Tools;
namespace SabreTools.Library.DatFiles
{
@@ -15,16 +20,37 @@ namespace SabreTools.Library.DatFiles
{
#region Pubically facing variables
#region Machine Filters
/// <summary>
/// Include or exclude machine names
/// </summary>
public FilterItem<string> MachineName { get; set; } = new FilterItem<string>();
/// <summary>
/// Include romof and cloneof when filtering machine names
/// </summary>
public FilterItem<bool> IncludeOfInGame { get; set; } = new FilterItem<bool>() { Neutral = false };
/// <summary>
/// Include or exclude machine descriptions
/// </summary>
public FilterItem<string> MachineDescription { get; set; } = new FilterItem<string>();
/// <summary>
/// Include or exclude machine types
/// </summary>
public FilterItem<MachineType> MachineTypes { get; set; } = new FilterItem<MachineType>() { Positive = MachineType.NULL, Negative = MachineType.NULL };
/// <summary>
/// Include or exclude items with the "Runnable" tag
/// </summary>
public FilterItem<bool?> Runnable { get; set; } = new FilterItem<bool?>() { Neutral = null };
#endregion
#region DatItem Filters
/// <summary>
/// Include or exclude item names
/// </summary>
@@ -35,6 +61,12 @@ namespace SabreTools.Library.DatFiles
/// </summary>
public FilterItem<string> ItemTypes { get; set; } = new FilterItem<string>();
/// <summary>
/// Include or exclude item sizes
/// </summary>
/// <remarks>Positive means "Greater than or equal", Negative means "Less than or equal", Neutral means "Equal"</remarks>
public FilterItem<long> Size { get; set; } = new FilterItem<long>() { Positive = -1, Negative = -1, Neutral = -1 };
/// <summary>
/// Include or exclude CRC32 hashes
/// </summary>
@@ -45,10 +77,12 @@ namespace SabreTools.Library.DatFiles
/// </summary>
public FilterItem<string> MD5 { get; set; } = new FilterItem<string>();
#if NET_FRAMEWORK
/// <summary>
/// Include or exclude RIPEMD160 hashes
/// </summary>
public FilterItem<string> RIPEMD160 { get; set; } = new FilterItem<string>();
#endif
/// <summary>
/// Include or exclude SHA-1 hashes
@@ -75,26 +109,29 @@ namespace SabreTools.Library.DatFiles
/// </summary>
public FilterItem<ItemStatus> ItemStatuses { get; set; } = new FilterItem<ItemStatus>() { Positive = ItemStatus.NULL, Negative = ItemStatus.NULL };
/// <summary>
/// Include or exclude machine types
/// </summary>
public FilterItem<MachineType> MachineTypes { get; set; } = new FilterItem<MachineType>() { Positive = MachineType.NULL, Negative = MachineType.NULL };
#endregion
#region Manipulation Filters
/// <summary>
/// Include or exclude item sizes
/// Clean all names to WoD standards
/// </summary>
/// <remarks>Positive means "Greater than or equal", Negative means "Less than or equal", Neutral means "Equal"</remarks>
public FilterItem<long> Size { get; set; } = new FilterItem<long>() { Positive = -1, Negative = -1, Neutral = -1 };
public FilterItem<bool> Clean { get; set; } = new FilterItem<bool>() { Neutral = false };
/// <summary>
/// Include romof and cloneof when filtering machine names
/// Set Machine Description from Machine Name
/// </summary>
public FilterItem<bool> IncludeOfInGame { get; set; } = new FilterItem<bool>() { Neutral = false };
public FilterItem<bool> DescriptionAsName { get; set; } = new FilterItem<bool>() { Neutral = false };
/// <summary>
/// Include or exclude items with the "Runnable" tag
/// Internally split a DatFile
/// </summary>
public FilterItem<bool?> Runnable { get; set; } = new FilterItem<bool?>() { Neutral = null };
public FilterItem<SplitType> InternalSplit { get; set; } = new FilterItem<SplitType>() { Neutral = SplitType.None };
/// <summary>
/// Remove all unicode characters
/// </summary>
public FilterItem<bool> RemoveUnicode { get; set; } = new FilterItem<bool>() { Neutral = false };
/// <summary>
/// Change all machine names to "!"
@@ -111,6 +148,8 @@ namespace SabreTools.Library.DatFiles
/// </summary>
public FilterItem<string> Root { get; set; } = new FilterItem<string>() { Neutral = null };
#endregion
#endregion // Pubically facing variables
#region Instance methods
@@ -118,9 +157,10 @@ namespace SabreTools.Library.DatFiles
/// <summary>
/// Filter a DatFile using the inputs
/// </summary>
/// <param name="datFile"></param>
/// <param name="datFile">DatFile to filter</param>
/// <param name="useTags">True if DatFile tags override splitting, false otherwise</param>
/// <returns>True if the DatFile was filtered, false on error</returns>
public bool FilterDatFile(DatFile datFile)
public bool FilterDatFile(DatFile datFile, bool useTags)
{
try
{
@@ -136,6 +176,21 @@ namespace SabreTools.Library.DatFiles
// If the rom passes the filter, include it
if (ItemPasses(item))
{
// If we're stripping unicode characters, do so from all relevant things
if (this.RemoveUnicode.Neutral)
{
item.Name = Sanitizer.RemoveUnicodeCharacters(item.Name);
item.MachineName = Sanitizer.RemoveUnicodeCharacters(item.MachineName);
item.MachineDescription = Sanitizer.RemoveUnicodeCharacters(item.MachineDescription);
}
// If we're in cleaning mode, do so from all relevant things
if (this.Clean.Neutral)
{
item.MachineName = Sanitizer.CleanGameName(item.MachineName);
item.MachineDescription = Sanitizer.CleanGameName(item.MachineDescription);
}
// If we are in single game mode, rename all games
if (this.Single.Neutral)
item.MachineName = "!";
@@ -164,6 +219,38 @@ namespace SabreTools.Library.DatFiles
datFile.Remove(key);
datFile.AddRange(key, newitems);
}
// Process description to machine name
if (this.DescriptionAsName.Neutral)
MachineDescriptionToName(datFile);
// If we are using tags from the DAT, set the proper input for split type unless overridden
if (useTags && this.InternalSplit.Neutral == SplitType.None)
this.InternalSplit.Neutral = datFile.DatHeader.ForceMerging.AsSplitType();
// Run internal splitting
ProcessSplitType(datFile, this.InternalSplit.Neutral);
// We remove any blanks, if we aren't supposed to have any
if (!datFile.DatHeader.KeepEmptyGames)
{
foreach (string key in datFile.Keys)
{
List<DatItem> items = datFile[key];
List<DatItem> newitems = items.Where(i => i.ItemType != ItemType.Blank).ToList();
datFile.Remove(key);
datFile.AddRange(key, newitems);
}
}
// If we are removing scene dates, do that now
if (datFile.DatHeader.SceneDateStrip)
StripSceneDatesFromItems(datFile);
// Run the one rom per game logic, if required
if (datFile.DatHeader.OneRom)
OneRomPerGame(datFile);
}
catch (Exception ex)
{
@@ -226,11 +313,13 @@ namespace SabreTools.Library.DatFiles
if (this.MD5.MatchesNegativeSet(rom.MD5) == true)
return false;
#if NET_FRAMEWORK
// Filter on RIPEMD160
if (this.RIPEMD160.MatchesPositiveSet(rom.RIPEMD160) == false)
return false;
if (this.RIPEMD160.MatchesNegativeSet(rom.RIPEMD160) == true)
return false;
#endif
// Filter on SHA-1
if (this.SHA1.MatchesPositiveSet(rom.SHA1) == false)
@@ -272,11 +361,13 @@ namespace SabreTools.Library.DatFiles
if (this.MD5.MatchesNegativeSet(rom.MD5) == true)
return false;
#if NET_FRAMEWORK
// Filter on RIPEMD160
if (this.RIPEMD160.MatchesPositiveSet(rom.RIPEMD160) == false)
return false;
if (this.RIPEMD160.MatchesNegativeSet(rom.RIPEMD160) == true)
return false;
#endif
// Filter on SHA-1
if (this.SHA1.MatchesPositiveSet(rom.SHA1) == false)
@@ -346,6 +437,642 @@ namespace SabreTools.Library.DatFiles
return true;
}
#region Internal Splitting/Merging
/// <summary>
/// Process items according to SplitType
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// <param name="splitType">SplitType to implement</param>
private void ProcessSplitType(DatFile datFile, SplitType splitType)
{
// Now we pre-process the DAT with the splitting/merging mode
switch (splitType)
{
case SplitType.None:
// No-op
break;
case SplitType.DeviceNonMerged:
CreateDeviceNonMergedSets(datFile, DedupeType.None);
break;
case SplitType.FullNonMerged:
CreateFullyNonMergedSets(datFile, DedupeType.None);
break;
case SplitType.NonMerged:
CreateNonMergedSets(datFile, DedupeType.None);
break;
case SplitType.Merged:
CreateMergedSets(datFile, DedupeType.None);
break;
case SplitType.Split:
CreateSplitSets(datFile, DedupeType.None);
break;
}
}
/// <summary>
/// Use cdevice_ref tags to get full non-merged sets and remove parenting tags
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// <param name="mergeroms">Dedupe type to be used</param>
private void CreateDeviceNonMergedSets(DatFile datFile, DedupeType mergeroms)
{
Globals.Logger.User("Creating device non-merged sets from the DAT");
// For sake of ease, the first thing we want to do is bucket by game
datFile.BucketBy(BucketedBy.Game, mergeroms, norename: true);
// Now we want to loop through all of the games and set the correct information
while (AddRomsFromDevices(datFile, false, false)) ;
while (AddRomsFromDevices(datFile, true, false)) ;
// Then, remove the romof and cloneof tags so it's not picked up by the manager
RemoveTagsFromChild(datFile);
}
/// <summary>
/// Use cloneof tags to create non-merged sets and remove the tags plus using the device_ref tags to get full sets
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// <param name="mergeroms">Dedupe type to be used</param>
private void CreateFullyNonMergedSets(DatFile datFile, DedupeType mergeroms)
{
Globals.Logger.User("Creating fully non-merged sets from the DAT");
// For sake of ease, the first thing we want to do is sort by game
datFile.BucketBy(BucketedBy.Game, mergeroms, norename: true);
// Now we want to loop through all of the games and set the correct information
while (AddRomsFromDevices(datFile, true, true)) ;
AddRomsFromDevices(datFile, false, true);
AddRomsFromParent(datFile);
// Now that we have looped through the cloneof tags, we loop through the romof tags
AddRomsFromBios(datFile);
// Then, remove the romof and cloneof tags so it's not picked up by the manager
RemoveTagsFromChild(datFile);
}
/// <summary>
/// Use cloneof tags to create merged sets and remove the tags
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// <param name="mergeroms">Dedupe type to be used</param>
private void CreateMergedSets(DatFile datFile, DedupeType mergeroms)
{
Globals.Logger.User("Creating merged sets from the DAT");
// For sake of ease, the first thing we want to do is sort by game
datFile.BucketBy(BucketedBy.Game, mergeroms, norename: true);
// Now we want to loop through all of the games and set the correct information
AddRomsFromChildren(datFile);
// Now that we have looped through the cloneof tags, we loop through the romof tags
RemoveBiosRomsFromChild(datFile, false);
RemoveBiosRomsFromChild(datFile, true);
// Finally, remove the romof and cloneof tags so it's not picked up by the manager
RemoveTagsFromChild(datFile);
}
/// <summary>
/// Use cloneof tags to create non-merged sets and remove the tags
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// <param name="mergeroms">Dedupe type to be used</param>
private void CreateNonMergedSets(DatFile datFile, DedupeType mergeroms)
{
Globals.Logger.User("Creating non-merged sets from the DAT");
// For sake of ease, the first thing we want to do is sort by game
datFile.BucketBy(BucketedBy.Game, mergeroms, norename: true);
// Now we want to loop through all of the games and set the correct information
AddRomsFromParent(datFile);
// Now that we have looped through the cloneof tags, we loop through the romof tags
RemoveBiosRomsFromChild(datFile, false);
RemoveBiosRomsFromChild(datFile, true);
// Finally, remove the romof and cloneof tags so it's not picked up by the manager
RemoveTagsFromChild(datFile);
}
/// <summary>
/// Use cloneof and romof tags to create split sets and remove the tags
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// <param name="mergeroms">Dedupe type to be used</param>
private void CreateSplitSets(DatFile datFile, DedupeType mergeroms)
{
Globals.Logger.User("Creating split sets from the DAT");
// For sake of ease, the first thing we want to do is sort by game
datFile.BucketBy(BucketedBy.Game, mergeroms, norename: true);
// Now we want to loop through all of the games and set the correct information
RemoveRomsFromChild(datFile);
// Now that we have looped through the cloneof tags, we loop through the romof tags
RemoveBiosRomsFromChild(datFile, false);
RemoveBiosRomsFromChild(datFile, true);
// Finally, remove the romof and cloneof tags so it's not picked up by the manager
RemoveTagsFromChild(datFile);
}
/// <summary>
/// Use romof tags to add roms to the children
/// </summary>
/// <param name="datFile">DatFile to filter</param>
private void AddRomsFromBios(DatFile datFile)
{
List<string> games = datFile.Keys;
foreach (string game in games)
{
// If the game has no items in it, we want to continue
if (datFile[game].Count == 0)
continue;
// Determine if the game has a parent or not
string parent = null;
if (!string.IsNullOrWhiteSpace(datFile[game][0].RomOf))
parent = datFile[game][0].RomOf;
// If the parent doesnt exist, we want to continue
if (string.IsNullOrWhiteSpace(parent))
continue;
// If the parent doesn't have any items, we want to continue
if (datFile[parent].Count == 0)
continue;
// If the parent exists and has items, we copy the items from the parent to the current game
DatItem copyFrom = datFile[game][0];
List<DatItem> parentItems = datFile[parent];
foreach (DatItem item in parentItems)
{
DatItem datItem = (DatItem)item.Clone();
datItem.CopyMachineInformation(copyFrom);
if (datFile[game].Where(i => i.Name == datItem.Name).Count() == 0 && !datFile[game].Contains(datItem))
datFile.Add(game, datItem);
}
}
}
/// <summary>
/// Use device_ref and optionally slotoption tags to add roms to the children
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// <param name="dev">True if only child device sets are touched, false for non-device sets (default)</param>
/// <param name="slotoptions">True if slotoptions tags are used as well, false otherwise</param>
private bool AddRomsFromDevices(DatFile datFile, bool dev = false, bool slotoptions = false)
{
bool foundnew = false;
List<string> games = datFile.Keys;
foreach (string game in games)
{
// If the game doesn't have items, we continue
if (datFile[game] == null || datFile[game].Count == 0)
continue;
// If the game (is/is not) a bios, we want to continue
if (dev ^ (datFile[game][0].MachineType.HasFlag(MachineType.Device)))
continue;
// If the game has no devices, we continue
if (datFile[game][0].Devices == null
|| datFile[game][0].Devices.Count == 0
|| (slotoptions && datFile[game][0].SlotOptions == null)
|| (slotoptions && datFile[game][0].SlotOptions.Count == 0))
{
continue;
}
// Determine if the game has any devices or not
List<string> devices = datFile[game][0].Devices;
List<string> newdevs = new List<string>();
foreach (string device in devices)
{
// If the device doesn't exist then we continue
if (datFile[device].Count == 0)
continue;
// Otherwise, copy the items from the device to the current game
DatItem copyFrom = datFile[game][0];
List<DatItem> devItems = datFile[device];
foreach (DatItem item in devItems)
{
DatItem datItem = (DatItem)item.Clone();
newdevs.AddRange(datItem.Devices ?? new List<string>());
datItem.CopyMachineInformation(copyFrom);
if (datFile[game].Where(i => i.Name.ToLowerInvariant() == datItem.Name.ToLowerInvariant()).Count() == 0)
{
foundnew = true;
datFile.Add(game, datItem);
}
}
}
// Now that every device is accounted for, add the new list of devices, if they don't already exist
foreach (string device in newdevs)
{
if (!datFile[game][0].Devices.Contains(device))
datFile[game][0].Devices.Add(device);
}
// If we're checking slotoptions too
if (slotoptions)
{
// Determine if the game has any slotoptions or not
List<string> slotopts = datFile[game][0].SlotOptions;
List<string> newslotopts = new List<string>();
foreach (string slotopt in slotopts)
{
// If the slotoption doesn't exist then we continue
if (datFile[slotopt].Count == 0)
continue;
// Otherwise, copy the items from the slotoption to the current game
DatItem copyFrom = datFile[game][0];
List<DatItem> slotItems = datFile[slotopt];
foreach (DatItem item in slotItems)
{
DatItem datItem = (DatItem)item.Clone();
newslotopts.AddRange(datItem.SlotOptions ?? new List<string>());
datItem.CopyMachineInformation(copyFrom);
if (datFile[game].Where(i => i.Name.ToLowerInvariant() == datItem.Name.ToLowerInvariant()).Count() == 0)
{
foundnew = true;
datFile.Add(game, datItem);
}
}
}
// Now that every slotoption is accounted for, add the new list of slotoptions, if they don't already exist
foreach (string slotopt in newslotopts)
{
if (!datFile[game][0].SlotOptions.Contains(slotopt))
datFile[game][0].SlotOptions.Add(slotopt);
}
}
}
return foundnew;
}
/// <summary>
/// Use cloneof tags to add roms to the children, setting the new romof tag in the process
/// </summary>
/// <param name="datFile">DatFile to filter</param>
private void AddRomsFromParent(DatFile datFile)
{
List<string> games = datFile.Keys;
foreach (string game in games)
{
// If the game has no items in it, we want to continue
if (datFile[game].Count == 0)
continue;
// Determine if the game has a parent or not
string parent = null;
if (!string.IsNullOrWhiteSpace(datFile[game][0].CloneOf))
parent = datFile[game][0].CloneOf;
// If the parent doesnt exist, we want to continue
if (string.IsNullOrWhiteSpace(parent))
continue;
// If the parent doesn't have any items, we want to continue
if (datFile[parent].Count == 0)
continue;
// If the parent exists and has items, we copy the items from the parent to the current game
DatItem copyFrom = datFile[game][0];
List<DatItem> parentItems = datFile[parent];
foreach (DatItem item in parentItems)
{
DatItem datItem = (DatItem)item.Clone();
datItem.CopyMachineInformation(copyFrom);
if (datFile[game].Where(i => i.Name.ToLowerInvariant() == datItem.Name.ToLowerInvariant()).Count() == 0
&& !datFile[game].Contains(datItem))
{
datFile.Add(game, datItem);
}
}
// Now we want to get the parent romof tag and put it in each of the items
List<DatItem> items = datFile[game];
string romof = datFile[parent][0].RomOf;
foreach (DatItem item in items)
{
item.RomOf = romof;
}
}
}
/// <summary>
/// Use cloneof tags to add roms to the parents, removing the child sets in the process
/// </summary>
/// <param name="datFile">DatFile to filter</param>
private void AddRomsFromChildren(DatFile datFile)
{
List<string> games = datFile.Keys;
foreach (string game in games)
{
// If the game has no items in it, we want to continue
if (datFile[game].Count == 0)
continue;
// Determine if the game has a parent or not
string parent = null;
if (!string.IsNullOrWhiteSpace(datFile[game][0].CloneOf))
parent = datFile[game][0].CloneOf;
// If there is no parent, then we continue
if (string.IsNullOrWhiteSpace(parent))
continue;
// Otherwise, move the items from the current game to a subfolder of the parent game
DatItem copyFrom = datFile[parent].Count == 0 ? new Rom { MachineName = parent, MachineDescription = parent } : datFile[parent][0];
List<DatItem> items = datFile[game];
foreach (DatItem item in items)
{
// If the disk doesn't have a valid merge tag OR the merged file doesn't exist in the parent, then add it
if (item.ItemType == ItemType.Disk && (((Disk)item).MergeTag == null || !datFile[parent].Select(i => i.Name).Contains(((Disk)item).MergeTag)))
{
item.CopyMachineInformation(copyFrom);
datFile.Add(parent, item);
}
// Otherwise, if the parent doesn't already contain the non-disk (or a merge-equivalent), add it
else if (item.ItemType != ItemType.Disk && !datFile[parent].Contains(item))
{
// Rename the child so it's in a subfolder
item.Name = $"{item.MachineName}\\{item.Name}";
// Update the machine to be the new parent
item.CopyMachineInformation(copyFrom);
// Add the rom to the parent set
datFile.Add(parent, item);
}
}
// Then, remove the old game so it's not picked up by the writer
datFile.Remove(game);
}
}
/// <summary>
/// Remove all BIOS and device sets
/// </summary>
/// <param name="datFile">DatFile to filter</param>
private void RemoveBiosAndDeviceSets(DatFile datFile)
{
List<string> games = datFile.Keys;
foreach (string game in games)
{
if (datFile[game].Count > 0
&& (datFile[game][0].MachineType.HasFlag(MachineType.Bios)
|| datFile[game][0].MachineType.HasFlag(MachineType.Device)))
{
datFile.Remove(game);
}
}
}
/// <summary>
/// Use romof tags to remove bios roms from children
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// <param name="bios">True if only child Bios sets are touched, false for non-bios sets (default)</param>
private void RemoveBiosRomsFromChild(DatFile datFile, bool bios = false)
{
// Loop through the romof tags
List<string> games = datFile.Keys;
foreach (string game in games)
{
// If the game has no items in it, we want to continue
if (datFile[game].Count == 0)
continue;
// If the game (is/is not) a bios, we want to continue
if (bios ^ datFile[game][0].MachineType.HasFlag(MachineType.Bios))
continue;
// Determine if the game has a parent or not
string parent = null;
if (!string.IsNullOrWhiteSpace(datFile[game][0].RomOf))
parent = datFile[game][0].RomOf;
// If the parent doesnt exist, we want to continue
if (string.IsNullOrWhiteSpace(parent))
continue;
// If the parent doesn't have any items, we want to continue
if (datFile[parent].Count == 0)
continue;
// If the parent exists and has items, we remove the items that are in the parent from the current game
List<DatItem> parentItems = datFile[parent];
foreach (DatItem item in parentItems)
{
DatItem datItem = (DatItem)item.Clone();
while (datFile[game].Contains(datItem))
{
datFile.Remove(game, datItem);
}
}
}
}
/// <summary>
/// Use cloneof tags to remove roms from the children
/// </summary>
/// <param name="datFile">DatFile to filter</param>
private void RemoveRomsFromChild(DatFile datFile)
{
List<string> games = datFile.Keys;
foreach (string game in games)
{
// If the game has no items in it, we want to continue
if (datFile[game].Count == 0)
continue;
// Determine if the game has a parent or not
string parent = null;
if (!string.IsNullOrWhiteSpace(datFile[game][0].CloneOf))
parent = datFile[game][0].CloneOf;
// If the parent doesnt exist, we want to continue
if (string.IsNullOrWhiteSpace(parent))
continue;
// If the parent doesn't have any items, we want to continue
if (datFile[parent].Count == 0)
continue;
// If the parent exists and has items, we remove the parent items from the current game
List<DatItem> parentItems = datFile[parent];
foreach (DatItem item in parentItems)
{
DatItem datItem = (DatItem)item.Clone();
while (datFile[game].Contains(datItem))
{
datFile.Remove(game, datItem);
}
}
// Now we want to get the parent romof tag and put it in each of the remaining items
List<DatItem> items = datFile[game];
string romof = datFile[parent][0].RomOf;
foreach (DatItem item in items)
{
item.RomOf = romof;
}
}
}
/// <summary>
/// Remove all romof and cloneof tags from all games
/// </summary>
/// <param name="datFile">DatFile to filter</param>
private void RemoveTagsFromChild(DatFile datFile)
{
List<string> games = datFile.Keys;
foreach (string game in games)
{
List<DatItem> items = datFile[game];
foreach (DatItem item in items)
{
item.CloneOf = null;
item.RomOf = null;
}
}
}
#endregion
#region Manipulation
/// <summary>
/// Use game descriptions as names in the DAT, updating cloneof/romof/sampleof
/// </summary>
/// <param name="datFile">DatFile to filter</param>
private void MachineDescriptionToName(DatFile datFile)
{
try
{
// First we want to get a mapping for all games to description
ConcurrentDictionary<string, string> mapping = new ConcurrentDictionary<string, string>();
List<string> keys = datFile.Keys;
Parallel.ForEach(keys, Globals.ParallelOptions, key =>
{
List<DatItem> items = datFile[key];
foreach (DatItem item in items)
{
// If the key mapping doesn't exist, add it
mapping.TryAdd(item.MachineName, item.MachineDescription.Replace('/', '_').Replace("\"", "''").Replace(":", " -"));
}
});
// Now we loop through every item and update accordingly
keys = datFile.Keys;
Parallel.ForEach(keys, Globals.ParallelOptions, key =>
{
List<DatItem> items = datFile[key];
List<DatItem> newItems = new List<DatItem>();
foreach (DatItem item in items)
{
// Update machine name
if (!string.IsNullOrWhiteSpace(item.MachineName) && mapping.ContainsKey(item.MachineName))
item.MachineName = mapping[item.MachineName];
// Update cloneof
if (!string.IsNullOrWhiteSpace(item.CloneOf) && mapping.ContainsKey(item.CloneOf))
item.CloneOf = mapping[item.CloneOf];
// Update romof
if (!string.IsNullOrWhiteSpace(item.RomOf) && mapping.ContainsKey(item.RomOf))
item.RomOf = mapping[item.RomOf];
// Update sampleof
if (!string.IsNullOrWhiteSpace(item.SampleOf) && mapping.ContainsKey(item.SampleOf))
item.SampleOf = mapping[item.SampleOf];
// Add the new item to the output list
newItems.Add(item);
}
// Replace the old list of roms with the new one
datFile.Remove(key);
datFile.AddRange(key, newItems);
});
}
catch (Exception ex)
{
Globals.Logger.Warning(ex.ToString());
}
}
/// <summary>
/// Ensure that all roms are in their own game (or at least try to ensure)
/// </summary>
/// <param name="datFile">DatFile to filter</param>
/// TODO: This is incorrect for the actual 1G1R logic... this is actually just silly
private void OneRomPerGame(DatFile datFile)
{
// For each rom, we want to update the game to be "<game name>/<rom name>"
Parallel.ForEach(datFile.Keys, Globals.ParallelOptions, key =>
{
List<DatItem> items = datFile[key];
for (int i = 0; i < items.Count; i++)
{
string[] splitname = items[i].Name.Split('.');
items[i].MachineName += $"/{string.Join(".", splitname.Take(splitname.Length > 1 ? splitname.Length - 1 : 1))}";
}
});
}
/// <summary>
/// Strip the dates from the beginning of scene-style set names
/// </summary>
/// <param name="datFile">DatFile to filter</param>
private void StripSceneDatesFromItems(DatFile datFile)
{
// Output the logging statement
Globals.Logger.User("Stripping scene-style dates");
// Set the regex pattern to use
string pattern = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)";
// Now process all of the roms
List<string> keys = datFile.Keys;
Parallel.ForEach(keys, Globals.ParallelOptions, key =>
{
List<DatItem> items = datFile[key];
for (int j = 0; j < items.Count; j++)
{
DatItem item = items[j];
if (Regex.IsMatch(item.MachineName, pattern))
item.MachineName = Regex.Replace(item.MachineName, pattern, "$2");
if (Regex.IsMatch(item.MachineDescription, pattern))
item.MachineDescription = Regex.Replace(item.MachineDescription, pattern, "$2");
items[j] = item;
}
datFile.Remove(key);
datFile.AddRange(key, items);
});
}
#endregion
#endregion // Instance Methods
}
}

View File

@@ -25,7 +25,7 @@ namespace SabreTools.Library.DatFiles
/// <param name="datFile">Parent DatFile to copy from</param>
/// <param name="hash">Type of hash that is associated with this DAT</param>
public Hashfile(DatFile datFile, Hash hash)
: base(datFile, cloneHeader: false)
: base(datFile)
{
_hash = hash;
}
@@ -34,25 +34,19 @@ namespace SabreTools.Library.DatFiles
/// Parse a hashfile or 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Open a file reader
Encoding enc = Utilities.GetEncoding(filename);
StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc);
Encoding enc = FileExtensions.GetEncoding(filename);
StreamReader sr = new StreamReader(FileExtensions.TryOpenRead(filename), enc);
while (!sr.EndOfStream)
{
@@ -64,7 +58,7 @@ namespace SabreTools.Library.DatFiles
string hash = string.Empty;
// If we have CRC, then it's an SFV file and the name is first are
if ((_hash & Hash.CRC) != 0)
if (_hash.HasFlag(Hash.CRC))
{
name = split[0].Replace("*", String.Empty);
hash = split[1];
@@ -80,23 +74,25 @@ namespace SabreTools.Library.DatFiles
{
Name = name,
Size = -1,
CRC = ((_hash & Hash.CRC) != 0 ? Utilities.CleanHashData(hash, Constants.CRCLength) : null),
MD5 = ((_hash & Hash.MD5) != 0 ? Utilities.CleanHashData(hash, Constants.MD5Length) : null),
RIPEMD160 = ((_hash & Hash.RIPEMD160) != 0 ? Utilities.CleanHashData(hash, Constants.RIPEMD160Length) : null),
SHA1 = ((_hash & Hash.SHA1) != 0 ? Utilities.CleanHashData(hash, Constants.SHA1Length) : null),
SHA256 = ((_hash & Hash.SHA256) != 0 ? Utilities.CleanHashData(hash, Constants.SHA256Length) : null),
SHA384 = ((_hash & Hash.SHA384) != 0 ? Utilities.CleanHashData(hash, Constants.SHA384Length) : null),
SHA512 = ((_hash & Hash.SHA512) != 0 ? Utilities.CleanHashData(hash, Constants.SHA512Length) : null),
CRC = (_hash.HasFlag(Hash.CRC) ? hash : null),
MD5 = (_hash.HasFlag(Hash.MD5) ? hash : null),
#if NET_FRAMEWORK
RIPEMD160 = (_hash.HasFlag(Hash.RIPEMD160) ? hash : null),
#endif
SHA1 = (_hash.HasFlag(Hash.SHA1) ? hash : null),
SHA256 = (_hash.HasFlag(Hash.SHA256) ? hash : null),
SHA384 = (_hash.HasFlag(Hash.SHA384) ? hash : null),
SHA512 = (_hash.HasFlag(Hash.SHA512) ? hash : null),
ItemStatus = ItemStatus.None,
MachineName = Path.GetFileNameWithoutExtension(filename),
SystemID = sysid,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
// Now process and add the rom
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
}
sr.Dispose();
@@ -113,7 +109,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -122,10 +118,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false));
svw.Quotes = false;
svw.Separator = ' ';
svw.VerifyFieldCount = true;
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false))
{
Quotes = false,
Separator = ' ',
VerifyFieldCount = true
};
// Get a properly sorted set of keys
List<string> keys = Keys;
@@ -200,40 +198,42 @@ namespace SabreTools.Library.DatFiles
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = string.Empty;
if (GameName)
fields[0] = $"{rom.GetField(Field.MachineName, ExcludeFields)}{Path.DirectorySeparatorChar}";
fields[0] += rom.GetField(Field.Name, ExcludeFields);
fields[1] = rom.GetField(Field.CRC, ExcludeFields);
if (DatHeader.GameName)
fields[0] = $"{rom.GetField(Field.MachineName, DatHeader.ExcludeFields)}{Path.DirectorySeparatorChar}";
fields[0] += rom.GetField(Field.Name, DatHeader.ExcludeFields);
fields[1] = rom.GetField(Field.CRC, DatHeader.ExcludeFields);
break;
}
break;
case Hash.MD5:
#if NET_FRAMEWORK
case Hash.RIPEMD160:
#endif
case Hash.SHA1:
case Hash.SHA256:
case Hash.SHA384:
case Hash.SHA512:
Field hashField = Utilities.GetFieldFromHash(_hash);
Field hashField = _hash.AsField();
switch (datItem.ItemType)
{
case ItemType.Disk:
var disk = datItem as Disk;
fields[0] = disk.GetField(hashField, ExcludeFields);
fields[0] = disk.GetField(hashField, DatHeader.ExcludeFields);
fields[1] = string.Empty;
if (GameName)
fields[1] = $"{disk.GetField(Field.MachineName, ExcludeFields)}{Path.DirectorySeparatorChar}";
fields[1] += disk.GetField(Field.Name, ExcludeFields);
if (DatHeader.GameName)
fields[1] = $"{disk.GetField(Field.MachineName, DatHeader.ExcludeFields)}{Path.DirectorySeparatorChar}";
fields[1] += disk.GetField(Field.Name, DatHeader.ExcludeFields);
break;
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = rom.GetField(hashField, ExcludeFields);
fields[0] = rom.GetField(hashField, DatHeader.ExcludeFields);
fields[1] = string.Empty;
if (GameName)
fields[1] = $"{rom.GetField(Field.MachineName, ExcludeFields)}{Path.DirectorySeparatorChar}";
fields[1] += rom.GetField(Field.Name, ExcludeFields);
if (DatHeader.GameName)
fields[1] = $"{rom.GetField(Field.MachineName, DatHeader.ExcludeFields)}{Path.DirectorySeparatorChar}";
fields[1] += rom.GetField(Field.Name, DatHeader.ExcludeFields);
break;
}
break;

View File

@@ -21,7 +21,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Json(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -29,25 +29,18 @@ namespace SabreTools.Library.DatFiles
/// Parse a Logiqx XML 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Prepare all internal variables
Encoding enc = Utilities.GetEncoding(filename);
StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), new UTF8Encoding(false));
StreamReader sr = new StreamReader(FileExtensions.TryOpenRead(filename), new UTF8Encoding(false));
JsonTextReader jtr = new JsonTextReader(sr);
// If we got a null reader, just return
@@ -77,7 +70,7 @@ namespace SabreTools.Library.DatFiles
// Machine array
case "machines":
ReadMachines(sr, jtr, clean, remUnicode);
ReadMachines(sr, jtr, filename, indexId);
jtr.Read();
break;
@@ -124,96 +117,96 @@ namespace SabreTools.Library.DatFiles
}
// Get all header items (ONLY OVERWRITE IF THERE'S NO DATA)
string content = string.Empty;
string content;
switch (jtr.Value)
{
case "name":
content = jtr.ReadAsString();
Name = (string.IsNullOrWhiteSpace(Name) ? content : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? content : DatHeader.Name);
superdat = superdat || content.Contains(" - SuperDAT");
if (keep && superdat)
{
Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? "SuperDAT" : DatHeader.Type);
}
break;
case "description":
content = jtr.ReadAsString();
Description = (string.IsNullOrWhiteSpace(Description) ? content : Description);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? content : DatHeader.Description);
break;
case "rootdir": // This is exclusive to TruRip XML
content = jtr.ReadAsString();
RootDir = (string.IsNullOrWhiteSpace(RootDir) ? content : RootDir);
DatHeader.RootDir = (string.IsNullOrWhiteSpace(DatHeader.RootDir) ? content : DatHeader.RootDir);
break;
case "category":
content = jtr.ReadAsString();
Category = (string.IsNullOrWhiteSpace(Category) ? content : Category);
DatHeader.Category = (string.IsNullOrWhiteSpace(DatHeader.Category) ? content : DatHeader.Category);
break;
case "version":
content = jtr.ReadAsString();
Version = (string.IsNullOrWhiteSpace(Version) ? content : Version);
DatHeader.Version = (string.IsNullOrWhiteSpace(DatHeader.Version) ? content : DatHeader.Version);
break;
case "date":
content = jtr.ReadAsString();
Date = (string.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date);
DatHeader.Date = (string.IsNullOrWhiteSpace(DatHeader.Date) ? content.Replace(".", "/") : DatHeader.Date);
break;
case "author":
content = jtr.ReadAsString();
Author = (string.IsNullOrWhiteSpace(Author) ? content : Author);
DatHeader.Author = (string.IsNullOrWhiteSpace(DatHeader.Author) ? content : DatHeader.Author);
break;
case "email":
content = jtr.ReadAsString();
Email = (string.IsNullOrWhiteSpace(Email) ? content : Email);
DatHeader.Email = (string.IsNullOrWhiteSpace(DatHeader.Email) ? content : DatHeader.Email);
break;
case "homepage":
content = jtr.ReadAsString();
Homepage = (string.IsNullOrWhiteSpace(Homepage) ? content : Homepage);
DatHeader.Homepage = (string.IsNullOrWhiteSpace(DatHeader.Homepage) ? content : DatHeader.Homepage);
break;
case "url":
content = jtr.ReadAsString();
Url = (string.IsNullOrWhiteSpace(Url) ? content : Url);
DatHeader.Url = (string.IsNullOrWhiteSpace(DatHeader.Url) ? content : DatHeader.Url);
break;
case "comment":
content = jtr.ReadAsString();
Comment = (string.IsNullOrWhiteSpace(Comment) ? content : Comment);
DatHeader.Comment = (string.IsNullOrWhiteSpace(DatHeader.Comment) ? content : DatHeader.Comment);
break;
case "type": // This is exclusive to TruRip XML
content = jtr.ReadAsString();
Type = (string.IsNullOrWhiteSpace(Type) ? content : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? content : DatHeader.Type);
superdat = superdat || content.Contains("SuperDAT");
break;
case "forcemerging":
if (ForceMerging == ForceMerging.None)
ForceMerging = Utilities.GetForceMerging(jtr.ReadAsString());
if (DatHeader.ForceMerging == ForceMerging.None)
DatHeader.ForceMerging = jtr.ReadAsString().AsForceMerging();
break;
case "forcepacking":
if (ForcePacking == ForcePacking.None)
ForcePacking = Utilities.GetForcePacking(jtr.ReadAsString());
if (DatHeader.ForcePacking == ForcePacking.None)
DatHeader.ForcePacking = jtr.ReadAsString().AsForcePacking();
break;
case "forcenodump":
if (ForceNodump == ForceNodump.None)
ForceNodump = Utilities.GetForceNodump(jtr.ReadAsString());
if (DatHeader.ForceNodump == ForceNodump.None)
DatHeader.ForceNodump = jtr.ReadAsString().AsForceNodump();
break;
case "header":
content = jtr.ReadAsString();
Header = (string.IsNullOrWhiteSpace(Header) ? content : Header);
DatHeader.Header = (string.IsNullOrWhiteSpace(DatHeader.Header) ? content : DatHeader.Header);
break;
default:
@@ -229,15 +222,15 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="sr">StreamReader to use to parse the header</param>
/// <param name="itr">JsonTextReader to use to parse the machine</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadMachines(
StreamReader sr,
JsonTextReader jtr,
// Miscellaneous
bool clean,
bool remUnicode)
// Standard Dat parsing
string filename,
int indexId)
{
// If the reader is invalid, skip
if (jtr == null)
@@ -257,7 +250,7 @@ namespace SabreTools.Library.DatFiles
continue;
}
ReadMachine(sr, jtr, clean, remUnicode);
ReadMachine(sr, jtr, filename, indexId);
jtr.Read();
}
}
@@ -267,15 +260,15 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="sr">StreamReader to use to parse the header</param>
/// <param name="itr">JsonTextReader to use to parse the machine</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadMachine(
StreamReader sr,
JsonTextReader jtr,
// Miscellaneous
bool clean,
bool remUnicode)
// Standard Dat parsing
string filename,
int indexId)
{
// If we have an empty machine, skip it
if (jtr == null)
@@ -337,19 +330,7 @@ namespace SabreTools.Library.DatFiles
break;
case "supported":
string supported = jtr.ReadAsString();
switch (supported)
{
case "yes":
machine.Supported = true;
break;
case "no":
machine.Supported = false;
break;
case "partial":
machine.Supported = null;
break;
}
machine.Supported = jtr.ReadAsString().AsYesNo();
break;
case "sourcefile":
@@ -357,16 +338,7 @@ namespace SabreTools.Library.DatFiles
break;
case "runnable":
string runnable = jtr.ReadAsString();
switch (runnable)
{
case "yes":
machine.Runnable = true;
break;
case "no":
machine.Runnable = false;
break;
}
machine.Runnable = jtr.ReadAsString().AsYesNo();
break;
case "board":
@@ -438,7 +410,7 @@ namespace SabreTools.Library.DatFiles
break;
case "items":
ReadItems(sr, jtr, clean, remUnicode, machine);
ReadItems(sr, jtr, filename, indexId, machine);
break;
default:
@@ -453,16 +425,19 @@ namespace SabreTools.Library.DatFiles
/// Read item array information
/// </summary>
/// <param name="sr">StreamReader to use to parse the header</param>
/// <param name="itr">JsonTextReader to use to parse the machine</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="jtr">JsonTextReader to use to parse the machine</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
/// <param name="machine">Machine information to add to the parsed items</param>
private void ReadItems(
StreamReader sr,
JsonTextReader jtr,
// Standard Dat parsing
string filename,
int indexId,
// Miscellaneous
bool clean,
bool remUnicode,
Machine machine)
{
// If the reader is invalid, skip
@@ -483,7 +458,7 @@ namespace SabreTools.Library.DatFiles
continue;
}
ReadItem(sr, jtr, clean, remUnicode, machine);
ReadItem(sr, jtr, filename, indexId, machine);
jtr.Read();
}
}
@@ -492,16 +467,19 @@ namespace SabreTools.Library.DatFiles
/// Read item information
/// </summary>
/// <param name="sr">StreamReader to use to parse the header</param>
/// <param name="itr">JsonTextReader to use to parse the machine</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="jtr">JsonTextReader to use to parse the machine</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
/// <param name="machine">Machine information to add to the parsed items</param>
private void ReadItem(
StreamReader sr,
JsonTextReader jtr,
// Standard Dat parsing
string filename,
int indexId,
// Miscellaneous
bool clean,
bool remUnicode,
Machine machine)
{
// If we have an empty machine, skip it
@@ -547,8 +525,10 @@ namespace SabreTools.Library.DatFiles
if (itemType == null)
return;
DatItem datItem = Utilities.GetDatItem(itemType.Value);
DatItem datItem = DatItem.Create(itemType.Value);
datItem.CopyMachineInformation(machine);
datItem.IndexId = indexId;
datItem.IndexSource = filename;
datItem.Name = name;
datItem.PartName = partName;
@@ -565,7 +545,9 @@ namespace SabreTools.Library.DatFiles
else if (itemType == ItemType.Disk)
{
(datItem as Disk).MD5 = md5;
#if NET_FRAMEWORK
(datItem as Disk).RIPEMD160 = ripemd160;
#endif
(datItem as Disk).SHA1 = sha1;
(datItem as Disk).SHA256 = sha256;
(datItem as Disk).SHA384 = sha384;
@@ -590,7 +572,9 @@ namespace SabreTools.Library.DatFiles
(datItem as Rom).Size = size;
(datItem as Rom).CRC = crc;
(datItem as Rom).MD5 = md5;
#if NET_FRAMEWORK
(datItem as Rom).RIPEMD160 = ripemd160;
#endif
(datItem as Rom).SHA1 = sha1;
(datItem as Rom).SHA256 = sha256;
(datItem as Rom).SHA384 = sha384;
@@ -603,7 +587,7 @@ namespace SabreTools.Library.DatFiles
(datItem as Rom).Optional = optional;
}
ParseAddHelper(datItem, clean, remUnicode);
ParseAddHelper(datItem);
return;
}
@@ -618,7 +602,7 @@ namespace SabreTools.Library.DatFiles
switch (jtr.Value)
{
case "type":
itemType = Utilities.GetItemType(jtr.ReadAsString());
itemType = jtr.ReadAsString().AsItemType();
break;
case "name":
@@ -731,7 +715,7 @@ namespace SabreTools.Library.DatFiles
break;
case "status":
itemStatus = Utilities.GetItemStatus(jtr.ReadAsString());
itemStatus = jtr.ReadAsString().AsItemStatus();
break;
case "optional":
@@ -765,7 +749,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -775,10 +759,12 @@ namespace SabreTools.Library.DatFiles
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false));
JsonTextWriter jtw = new JsonTextWriter(sw);
jtw.Formatting = Formatting.Indented;
jtw.IndentChar = '\t';
jtw.Indentation = 1;
JsonTextWriter jtw = new JsonTextWriter(sw)
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(jtw);
@@ -827,7 +813,9 @@ namespace SabreTools.Library.DatFiles
((Rom)rom).Size = Constants.SizeZero;
((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null;
((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null;
#if NET_FRAMEWORK
((Rom)rom).RIPEMD160 = ((Rom)rom).RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null;
#endif
((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null;
((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null;
((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null;
@@ -873,57 +861,57 @@ namespace SabreTools.Library.DatFiles
jtw.WriteStartObject();
jtw.WritePropertyName("name");
jtw.WriteValue(Name);
jtw.WriteValue(DatHeader.Name);
jtw.WritePropertyName("description");
jtw.WriteValue(Description);
if (!string.IsNullOrWhiteSpace(RootDir))
jtw.WriteValue(DatHeader.Description);
if (!string.IsNullOrWhiteSpace(DatHeader.RootDir))
{
jtw.WritePropertyName("rootdir");
jtw.WriteValue(RootDir);
jtw.WriteValue(DatHeader.RootDir);
}
if (!string.IsNullOrWhiteSpace(Category))
if (!string.IsNullOrWhiteSpace(DatHeader.Category))
{
jtw.WritePropertyName("category");
jtw.WriteValue(Category);
jtw.WriteValue(DatHeader.Category);
}
jtw.WritePropertyName("version");
jtw.WriteValue(Version);
if (!string.IsNullOrWhiteSpace(Date))
jtw.WriteValue(DatHeader.Version);
if (!string.IsNullOrWhiteSpace(DatHeader.Date))
{
jtw.WritePropertyName("date");
jtw.WriteValue(Date);
jtw.WriteValue(DatHeader.Date);
}
jtw.WritePropertyName("author");
jtw.WriteValue(Author);
if (!string.IsNullOrWhiteSpace(Email))
jtw.WriteValue(DatHeader.Author);
if (!string.IsNullOrWhiteSpace(DatHeader.Email))
{
jtw.WritePropertyName("email");
jtw.WriteValue(Email);
jtw.WriteValue(DatHeader.Email);
}
if (!string.IsNullOrWhiteSpace(Homepage))
if (!string.IsNullOrWhiteSpace(DatHeader.Homepage))
{
jtw.WritePropertyName("homepage");
jtw.WriteValue(Homepage);
jtw.WriteValue(DatHeader.Homepage);
}
if (!string.IsNullOrWhiteSpace(Url))
if (!string.IsNullOrWhiteSpace(DatHeader.Url))
{
jtw.WritePropertyName("date");
jtw.WriteValue(Url);
jtw.WriteValue(DatHeader.Url);
}
if (!string.IsNullOrWhiteSpace(Comment))
if (!string.IsNullOrWhiteSpace(DatHeader.Comment))
{
jtw.WritePropertyName("comment");
jtw.WriteValue(Comment);
jtw.WriteValue(DatHeader.Comment);
}
if (!string.IsNullOrWhiteSpace(Type))
if (!string.IsNullOrWhiteSpace(DatHeader.Type))
{
jtw.WritePropertyName("type");
jtw.WriteValue(Type);
jtw.WriteValue(DatHeader.Type);
}
if (ForceMerging != ForceMerging.None)
if (DatHeader.ForceMerging != ForceMerging.None)
{
jtw.WritePropertyName("forcemerging");
switch (ForceMerging)
switch (DatHeader.ForceMerging)
{
case ForceMerging.Full:
jtw.WriteValue("full");
@@ -939,10 +927,10 @@ namespace SabreTools.Library.DatFiles
break;
}
}
if (ForcePacking != ForcePacking.None)
if (DatHeader.ForcePacking != ForcePacking.None)
{
jtw.WritePropertyName("forcepacking");
switch (ForcePacking)
switch (DatHeader.ForcePacking)
{
case ForcePacking.Unzip:
jtw.WriteValue("unzip");
@@ -952,10 +940,10 @@ namespace SabreTools.Library.DatFiles
break;
}
}
if (ForceNodump != ForceNodump.None)
if (DatHeader.ForceNodump != ForceNodump.None)
{
jtw.WritePropertyName("forcenodump");
switch (ForceNodump)
switch (DatHeader.ForceNodump)
{
case ForceNodump.Ignore:
jtw.WriteValue("ignore");
@@ -968,10 +956,10 @@ namespace SabreTools.Library.DatFiles
break;
}
}
if (!string.IsNullOrWhiteSpace(Header))
if (!string.IsNullOrWhiteSpace(DatHeader.Header))
{
jtw.WritePropertyName("header");
jtw.WriteValue(Header);
jtw.WriteValue(DatHeader.Header);
}
// End header
@@ -1008,49 +996,49 @@ namespace SabreTools.Library.DatFiles
jtw.WriteStartObject();
jtw.WritePropertyName("name");
jtw.WriteValue(datItem.GetField(Field.MachineName, ExcludeFields));
jtw.WriteValue(datItem.GetField(Field.MachineName, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Comment, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Comment, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("comment");
jtw.WriteValue(datItem.Comment);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("description");
jtw.WriteValue(datItem.MachineDescription);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("year");
jtw.WriteValue(datItem.Year);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Manufacturer, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Manufacturer, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("manufacturer");
jtw.WriteValue(datItem.Manufacturer);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Publisher, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Publisher, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("publisher");
jtw.WriteValue(datItem.Publisher);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RomOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.RomOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RomOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.RomOf, StringComparison.OrdinalIgnoreCase))
{
jtw.WritePropertyName("romof");
jtw.WriteValue(datItem.RomOf);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.CloneOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.CloneOf, StringComparison.OrdinalIgnoreCase))
{
jtw.WritePropertyName("cloneof");
jtw.WriteValue(datItem.CloneOf);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.SampleOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.SampleOf, StringComparison.OrdinalIgnoreCase))
{
jtw.WritePropertyName("sampleof");
jtw.WriteValue(datItem.SampleOf);
}
if (!ExcludeFields[(int)Field.Supported] && datItem.Supported != null)
if (!DatHeader.ExcludeFields[(int)Field.Supported] && datItem.Supported != null)
{
if (datItem.Supported == true)
{
@@ -1063,12 +1051,12 @@ namespace SabreTools.Library.DatFiles
jtw.WriteValue("no");
}
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SourceFile, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SourceFile, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sourcefile");
jtw.WriteValue(datItem.SourceFile);
}
if (!ExcludeFields[(int)Field.Runnable] && datItem.Runnable != null)
if (!DatHeader.ExcludeFields[(int)Field.Runnable] && datItem.Runnable != null)
{
if (datItem.Runnable == true)
{
@@ -1081,17 +1069,17 @@ namespace SabreTools.Library.DatFiles
jtw.WriteValue("no");
}
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Board, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Board, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("board");
jtw.WriteValue(datItem.Board);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RebuildTo, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RebuildTo, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("rebuildto");
jtw.WriteValue(datItem.RebuildTo);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Devices, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Devices, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("devices");
jtw.WriteStartArray();
@@ -1102,7 +1090,7 @@ namespace SabreTools.Library.DatFiles
jtw.WriteEndArray();
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SlotOptions, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SlotOptions, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("slotoptions");
jtw.WriteStartArray();
@@ -1113,7 +1101,7 @@ namespace SabreTools.Library.DatFiles
jtw.WriteEndArray();
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Infos, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Infos, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("infos");
jtw.WriteStartArray();
@@ -1127,19 +1115,19 @@ namespace SabreTools.Library.DatFiles
jtw.WriteEndArray();
}
if (!ExcludeFields[(int)Field.MachineType])
if (!DatHeader.ExcludeFields[(int)Field.MachineType])
{
if ((datItem.MachineType & MachineType.Bios) != 0)
if (datItem.MachineType.HasFlag(MachineType.Bios))
{
jtw.WritePropertyName("isbios");
jtw.WriteValue("yes");
}
if ((datItem.MachineType & MachineType.Device) != 0)
if (datItem.MachineType.HasFlag(MachineType.Device))
{
jtw.WritePropertyName("isdevice");
jtw.WriteValue("yes");
}
if ((datItem.MachineType & MachineType.Mechanical) != 0)
if (datItem.MachineType.HasFlag(MachineType.Mechanical))
{
jtw.WritePropertyName("ismechanical");
jtw.WriteValue("yes");
@@ -1217,20 +1205,20 @@ namespace SabreTools.Library.DatFiles
case ItemType.Archive:
jtw.WriteValue("archive");
jtw.WritePropertyName("name");
jtw.WriteValue(datItem.GetField(Field.Name, ExcludeFields));
jtw.WriteValue(datItem.GetField(Field.Name, DatHeader.ExcludeFields));
break;
case ItemType.BiosSet:
var biosSet = datItem as BiosSet;
jtw.WriteValue("biosset");
jtw.WritePropertyName("name");
jtw.WriteValue(biosSet.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.BiosDescription, ExcludeFields)))
jtw.WriteValue(biosSet.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.BiosDescription, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("description");
jtw.WriteValue(biosSet.Description);
}
if (!ExcludeFields[(int)Field.Default] && biosSet.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && biosSet.Default != null)
{
jtw.WritePropertyName("default");
jtw.WriteValue(biosSet.Default);
@@ -1241,63 +1229,65 @@ namespace SabreTools.Library.DatFiles
var disk = datItem as Disk;
jtw.WriteValue("disk");
jtw.WritePropertyName("name");
jtw.WriteValue(disk.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.MD5, ExcludeFields)))
jtw.WriteValue(disk.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.MD5, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("md5");
jtw.WriteValue(disk.MD5.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.MD5, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("ripemd160");
jtw.WriteValue(disk.RIPEMD160.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.SHA1, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sha1");
jtw.WriteValue(disk.SHA1.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.SHA256, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sha256");
jtw.WriteValue(disk.SHA256.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.SHA384, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sha384");
jtw.WriteValue(disk.SHA384.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.SHA512, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sha512");
jtw.WriteValue(disk.SHA512.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Merge, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Merge, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("merge");
jtw.WriteValue(disk.MergeTag);
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Region, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Region, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("region");
jtw.WriteValue(disk.Region);
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Index, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Index, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("index");
jtw.WriteValue(disk.Index);
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Writable, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Writable, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("writable");
jtw.WriteValue(disk.Writable);
}
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
{
jtw.WritePropertyName("status");
jtw.WriteValue(disk.ItemStatus.ToString().ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Optional, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(disk.GetField(Field.Optional, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("optional");
jtw.WriteValue(disk.Optional);
@@ -1308,23 +1298,23 @@ namespace SabreTools.Library.DatFiles
var release = datItem as Release;
jtw.WriteValue("release");
jtw.WritePropertyName("name");
jtw.WriteValue(release.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(release.GetField(Field.Region, ExcludeFields)))
jtw.WriteValue(release.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(release.GetField(Field.Region, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("region");
jtw.WriteValue(release.Region);
}
if (!string.IsNullOrWhiteSpace(release.GetField(Field.Language, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(release.GetField(Field.Language, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("language");
jtw.WriteValue(release.Language);
}
if (!string.IsNullOrWhiteSpace(release.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(release.GetField(Field.Date, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("date");
jtw.WriteValue(release.Date);
}
if (!ExcludeFields[(int)Field.Default] && release.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && release.Default != null)
{
jtw.WritePropertyName("default");
jtw.WriteValue(release.Default);
@@ -1335,78 +1325,80 @@ namespace SabreTools.Library.DatFiles
var rom = datItem as Rom;
jtw.WriteValue("rom");
jtw.WritePropertyName("name");
jtw.WriteValue(rom.GetField(Field.Name, ExcludeFields));
if (!ExcludeFields[(int)Field.Size] && rom.Size != -1)
jtw.WriteValue(rom.GetField(Field.Name, DatHeader.ExcludeFields));
if (!DatHeader.ExcludeFields[(int)Field.Size] && rom.Size != -1)
{
jtw.WritePropertyName("size");
jtw.WriteValue(rom.Size);
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Offset, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Offset, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("offset");
jtw.WriteValue(rom.Offset);
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.CRC, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("crc");
jtw.WriteValue(rom.CRC.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.MD5, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.MD5, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("md5");
jtw.WriteValue(rom.MD5.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.MD5, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("ripemd160");
jtw.WriteValue(rom.RIPEMD160.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.SHA1, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sha1");
jtw.WriteValue(rom.SHA1.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.SHA256, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sha256");
jtw.WriteValue(rom.SHA256.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.SHA384, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sha384");
jtw.WriteValue(rom.SHA384.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.SHA512, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("sha512");
jtw.WriteValue(rom.SHA512.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Bios, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Bios, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("bios");
jtw.WriteValue(rom.Bios);
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Merge, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Merge, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("merge");
jtw.WriteValue(rom.MergeTag);
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Region, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Region, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("region");
jtw.WriteValue(rom.Region);
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Date, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("date");
jtw.WriteValue(rom.Date);
}
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
{
jtw.WritePropertyName("status");
jtw.WriteValue(rom.ItemStatus.ToString().ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Optional, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(rom.GetField(Field.Optional, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("optional");
jtw.WriteValue(rom.Optional);
@@ -1416,21 +1408,21 @@ namespace SabreTools.Library.DatFiles
case ItemType.Sample:
jtw.WriteValue("sample");
jtw.WritePropertyName("name");
jtw.WriteValue(datItem.GetField(Field.Name, ExcludeFields));
jtw.WriteValue(datItem.GetField(Field.Name, DatHeader.ExcludeFields));
break;
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.PartName, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.PartName, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("partname");
jtw.WriteValue(datItem.PartName);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.PartInterface, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.PartInterface, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("partinterface");
jtw.WriteValue(datItem.PartInterface);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Features, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Features, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("features");
jtw.WriteStartArray();
@@ -1444,12 +1436,12 @@ namespace SabreTools.Library.DatFiles
jtw.WriteEndArray();
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.AreaName, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.AreaName, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("areaname");
jtw.WriteValue(datItem.AreaName);
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.AreaSize, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.AreaSize, DatHeader.ExcludeFields)))
{
jtw.WritePropertyName("areasize");
jtw.WriteValue(datItem.AreaSize);

View File

@@ -21,7 +21,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Listrom(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -29,11 +29,8 @@ namespace SabreTools.Library.DatFiles
/// Parse a MAME Listrom 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <remarks>
/// In a new style MAME listrom DAT, each game has the following format:
///
@@ -43,20 +40,17 @@ namespace SabreTools.Library.DatFiles
/// 6331.sound-u8 32 BAD CRC(1d298cb0) SHA1(bb0bb62365402543e3154b9a77be9c75010e6abc) BAD_DUMP
/// 16v8h-blue.u24 279 NO GOOD DUMP KNOWN
/// </remarks>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Open a file reader
Encoding enc = Utilities.GetEncoding(filename);
StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc);
Encoding enc = FileExtensions.GetEncoding(filename);
StreamReader sr = new StreamReader(FileExtensions.TryOpenRead(filename), enc);
string gamename = string.Empty;
while (!sr.EndOfStream)
@@ -91,7 +85,6 @@ namespace SabreTools.Library.DatFiles
else
{
// First, we preprocess the line so that the rom name is consistently correct
string romname = string.Empty;
string[] split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
// If the line doesn't have the 4 spaces of padding, check for 3
@@ -102,7 +95,7 @@ namespace SabreTools.Library.DatFiles
if (split.Length == 1)
Globals.Logger.Warning($"Possibly malformed line: '{line}'");
romname = split[0];
string romname = split[0];
line = line.Substring(romname.Length);
// Next we separate the ROM into pieces
@@ -114,12 +107,15 @@ namespace SabreTools.Library.DatFiles
Disk disk = new Disk()
{
Name = romname,
SHA1 = Utilities.CleanListromHashData(split[0]),
SHA1 = Sanitizer.CleanListromHashData(split[0]),
MachineName = gamename,
IndexId = indexId,
IndexSource = filename,
};
ParseAddHelper(disk, clean, remUnicode);
ParseAddHelper(disk);
}
// Baddump Disks have 4 pieces (name, BAD, sha1, BAD_DUMP)
@@ -128,13 +124,16 @@ namespace SabreTools.Library.DatFiles
Disk disk = new Disk()
{
Name = romname,
SHA1 = Utilities.CleanListromHashData(split[1]),
SHA1 = Sanitizer.CleanListromHashData(split[1]),
ItemStatus = ItemStatus.BadDump,
MachineName = gamename,
IndexId = indexId,
IndexSource = filename,
};
ParseAddHelper(disk, clean, remUnicode);
ParseAddHelper(disk);
}
// Standard ROMs have 4 pieces (name, size, crc, sha1)
@@ -147,13 +146,16 @@ namespace SabreTools.Library.DatFiles
{
Name = romname,
Size = size,
CRC = Utilities.CleanListromHashData(split[1]),
SHA1 = Utilities.CleanListromHashData(split[2]),
CRC = Sanitizer.CleanListromHashData(split[1]),
SHA1 = Sanitizer.CleanListromHashData(split[2]),
MachineName = gamename,
IndexId = indexId,
IndexSource = filename,
};
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
}
// Nodump Disks have 5 pieces (name, NO, GOOD, DUMP, KNOWN)
@@ -165,9 +167,12 @@ namespace SabreTools.Library.DatFiles
ItemStatus = ItemStatus.Nodump,
MachineName = gamename,
IndexId = indexId,
IndexSource = filename,
};
ParseAddHelper(disk, clean, remUnicode);
ParseAddHelper(disk);
}
// Baddump ROMs have 6 pieces (name, size, BAD, crc, sha1, BAD_DUMP)
@@ -180,14 +185,17 @@ namespace SabreTools.Library.DatFiles
{
Name = romname,
Size = size,
CRC = Utilities.CleanListromHashData(split[2]),
SHA1 = Utilities.CleanListromHashData(split[3]),
CRC = Sanitizer.CleanListromHashData(split[2]),
SHA1 = Sanitizer.CleanListromHashData(split[3]),
ItemStatus = ItemStatus.BadDump,
MachineName = gamename,
IndexId = indexId,
IndexSource = filename,
};
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
}
// Nodump ROMs have 6 pieces (name, size, NO, GOOD, DUMP, KNOWN)
@@ -203,9 +211,12 @@ namespace SabreTools.Library.DatFiles
ItemStatus = ItemStatus.Nodump,
MachineName = gamename,
IndexId = indexId,
IndexSource = filename,
};
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
}
// If we have something else, it's invalid
@@ -217,7 +228,6 @@ namespace SabreTools.Library.DatFiles
}
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
@@ -229,7 +239,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -325,7 +335,7 @@ namespace SabreTools.Library.DatFiles
rom.MachineName = rom.MachineName.TrimStart(Path.DirectorySeparatorChar);
// Build the state based on excluded fields
sw.Write($"ROMs required for driver \"{rom.GetField(Field.MachineName, ExcludeFields)}\".\n");
sw.Write($"ROMs required for driver \"{rom.GetField(Field.MachineName, DatHeader.ExcludeFields)}\".\n");
sw.Write("Name Size Checksum\n");
sw.Flush();
@@ -393,19 +403,19 @@ namespace SabreTools.Library.DatFiles
sw.Write($"{disk.Name} ");
// If we have a baddump, put the first indicator
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus == ItemStatus.BadDump)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus == ItemStatus.BadDump)
sw.Write(" BAD");
// If we have a nodump, write out the indicator
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus == ItemStatus.Nodump)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus == ItemStatus.Nodump)
sw.Write(" NO GOOD DUMP KNOWN");
// Otherwise, write out the SHA-1 hash
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
sw.Write($" SHA1({disk.SHA1})");
// If we have a baddump, put the second indicator
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus == ItemStatus.BadDump)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus == ItemStatus.BadDump)
sw.Write(" BAD_DUMP");
sw.Write("\n");
@@ -425,25 +435,25 @@ namespace SabreTools.Library.DatFiles
sw.Write(rom.Size);
// If we have a baddump, put the first indicator
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus == ItemStatus.BadDump)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus == ItemStatus.BadDump)
sw.Write(" BAD");
// If we have a nodump, write out the indicator
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus == ItemStatus.Nodump)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus == ItemStatus.Nodump)
{
sw.Write(" NO GOOD DUMP KNOWN");
}
// Otherwise, write out the CRC and SHA-1 hashes
else
{
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, DatHeader.ExcludeFields)))
sw.Write($" CRC({rom.CRC})");
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
sw.Write($" SHA1({rom.SHA1})");
}
// If we have a baddump, put the second indicator
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus == ItemStatus.BadDump)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus == ItemStatus.BadDump)
sw.Write(" BAD_DUMP");
sw.Write("\n");

View File

@@ -14,7 +14,6 @@ namespace SabreTools.Library.DatFiles
/// <summary>
/// Represents parsing and writing of a MAME XML DAT
/// </summary>
/// TODO: Verify that all write for this DatFile type is correct
internal class Listxml : DatFile
{
/// <summary>
@@ -22,7 +21,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Listxml(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -30,27 +29,20 @@ namespace SabreTools.Library.DatFiles
/// Parse a MAME XML 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <remarks>
/// </remarks>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Prepare all internal variables
Encoding enc = Utilities.GetEncoding(filename);
XmlReader xtr = Utilities.GetXmlTextReader(filename);
XmlReader xtr = filename.GetXmlTextReader();
// If we got a null reader, just return
if (xtr == null)
@@ -72,8 +64,8 @@ namespace SabreTools.Library.DatFiles
switch (xtr.Name)
{
case "mame":
Name = (string.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("build") : Name);
Description = (string.IsNullOrWhiteSpace(Description) ? Name : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? xtr.GetAttribute("build") : DatHeader.Name);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? DatHeader.Name : DatHeader.Description);
// string mame_debug = xtr.GetAttribute("debug"); // (yes|no) "no"
// string mame_mameconfig = xtr.GetAttribute("mameconfig"); CDATA
xtr.Read();
@@ -81,16 +73,16 @@ namespace SabreTools.Library.DatFiles
// Handle M1 DATs since they're 99% the same as a SL DAT
case "m1":
Name = (string.IsNullOrWhiteSpace(Name) ? "M1" : Name);
Description = (string.IsNullOrWhiteSpace(Description) ? "M1" : Description);
Version = (string.IsNullOrWhiteSpace(Version) ? xtr.GetAttribute("version") ?? string.Empty : Version);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? "M1" : DatHeader.Name);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? "M1" : DatHeader.Description);
DatHeader.Version = (string.IsNullOrWhiteSpace(DatHeader.Version) ? xtr.GetAttribute("version") ?? string.Empty : DatHeader.Version);
xtr.Read();
break;
// We want to process the entire subtree of the machine
case "game": // Some older DATs still use "game"
case "machine":
ReadMachine(xtr.ReadSubtree(), filename, sysid, srcid, clean, remUnicode);
ReadMachine(xtr.ReadSubtree(), filename, indexId);
// Skip the machine now that we've processed it
xtr.Skip();
@@ -118,21 +110,13 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="reader">XmlReader representing a machine block</param>
/// <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="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadMachine(
XmlReader reader,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool clean,
bool remUnicode)
int indexId)
{
// If we have an empty machine, skip it
if (reader == null)
@@ -147,13 +131,13 @@ namespace SabreTools.Library.DatFiles
// Create a new machine
MachineType machineType = MachineType.NULL;
if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true)
if (reader.GetAttribute("isbios").AsYesNo() == true)
machineType |= MachineType.Bios;
if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true)
if (reader.GetAttribute("isdevice").AsYesNo() == true)
machineType |= MachineType.Device;
if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true)
if (reader.GetAttribute("ismechanical").AsYesNo() == true)
machineType |= MachineType.Mechanical;
Machine machine = new Machine
@@ -161,7 +145,7 @@ namespace SabreTools.Library.DatFiles
Name = reader.GetAttribute("name"),
Description = reader.GetAttribute("name"),
SourceFile = reader.GetAttribute("sourcefile"),
Runnable = Utilities.GetYesNo(reader.GetAttribute("runnable")),
Runnable = reader.GetAttribute("runnable").AsYesNo(),
Comment = string.Empty,
@@ -205,17 +189,16 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
Description = reader.GetAttribute("description"),
Default = Utilities.GetYesNo(reader.GetAttribute("default")),
Default = reader.GetAttribute("default").AsYesNo(),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
biosset.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(biosset, clean, remUnicode);
key = ParseAddHelper(biosset);
reader.Read();
break;
@@ -227,29 +210,30 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
Bios = reader.GetAttribute("bios"),
Size = Utilities.GetSize(reader.GetAttribute("size")),
CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength),
MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length),
RIPEMD160 = Utilities.CleanHashData(reader.GetAttribute("ripemd160"), Constants.SHA1Length),
SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length),
SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length),
SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length),
Size = Sanitizer.CleanSize(reader.GetAttribute("size")),
CRC = reader.GetAttribute("crc"),
MD5 = reader.GetAttribute("md5"),
#if NET_FRAMEWORK
RIPEMD160 = reader.GetAttribute("ripemd160"),
#endif
SHA1 = reader.GetAttribute("sha1"),
SHA256 = reader.GetAttribute("sha256"),
SHA384 = reader.GetAttribute("sha384"),
SHA512 = reader.GetAttribute("sha512"),
MergeTag = reader.GetAttribute("merge"),
Region = reader.GetAttribute("region"),
Offset = reader.GetAttribute("offset"),
ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")),
Optional = Utilities.GetYesNo(reader.GetAttribute("optional")),
ItemStatus = reader.GetAttribute("status").AsItemStatus(),
Optional = reader.GetAttribute("optional").AsYesNo(),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
rom.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(rom, clean, remUnicode);
key = ParseAddHelper(rom);
reader.Read();
break;
@@ -260,28 +244,29 @@ namespace SabreTools.Library.DatFiles
DatItem disk = new Disk
{
Name = reader.GetAttribute("name"),
MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length),
RIPEMD160 = Utilities.CleanHashData(reader.GetAttribute("ripemd160"), Constants.SHA1Length),
SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length),
SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length),
SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length),
MD5 = reader.GetAttribute("md5"),
#if NET_FRAMEWORK
RIPEMD160 = reader.GetAttribute("ripemd160"),
#endif
SHA1 = reader.GetAttribute("sha1"),
SHA256 = reader.GetAttribute("sha256"),
SHA384 = reader.GetAttribute("sha384"),
SHA512 = reader.GetAttribute("sha512"),
MergeTag = reader.GetAttribute("merge"),
Region = reader.GetAttribute("region"),
Index = reader.GetAttribute("index"),
Writable = Utilities.GetYesNo(reader.GetAttribute("writable")),
ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")),
Optional = Utilities.GetYesNo(reader.GetAttribute("optional")),
Writable = reader.GetAttribute("writable").AsYesNo(),
ItemStatus = reader.GetAttribute("status").AsItemStatus(),
Optional = reader.GetAttribute("optional").AsYesNo(),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
disk.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(disk, clean, remUnicode);
key = ParseAddHelper(disk);
reader.Read();
break;
@@ -301,15 +286,14 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
samplerom.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(samplerom, clean, remUnicode);
key = ParseAddHelper(samplerom);
reader.Read();
break;
@@ -503,14 +487,13 @@ namespace SabreTools.Library.DatFiles
{
Blank blank = new Blank()
{
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
blank.CopyMachineInformation(machine);
// Now process and add the rom
ParseAddHelper(blank, clean, remUnicode);
ParseAddHelper(blank);
}
}
@@ -519,7 +502,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="reader">XmlReader representing a machine block</param>
/// <param name="machine">Machine information to pass to contained items</param>
private void ReadSlot(XmlReader reader, Machine machine)
private void ReadSlot(XmlReader reader, Machine machine)
{
// If we have an empty machine, skip it
if (reader == null)
@@ -569,7 +552,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -578,10 +561,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false));
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false))
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(xtw);
@@ -666,7 +651,7 @@ namespace SabreTools.Library.DatFiles
xtw.WriteStartDocument();
xtw.WriteStartElement("mame");
xtw.WriteAttributeString("build", Name);
xtw.WriteAttributeString("build", DatHeader.Name);
//xtw.WriteAttributeString("debug", Debug);
//xtw.WriteAttributeString("mameconfig", MameConfig);
@@ -696,21 +681,21 @@ namespace SabreTools.Library.DatFiles
// Build the state based on excluded fields
xtw.WriteStartElement("machine");
xtw.WriteAttributeString("name", datItem.GetField(Field.MachineName, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SourceFile, ExcludeFields)))
xtw.WriteAttributeString("name", datItem.GetField(Field.MachineName, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SourceFile, DatHeader.ExcludeFields)))
xtw.WriteElementString("sourcefile", datItem.SourceFile);
if (!ExcludeFields[(int)Field.MachineType])
if (!DatHeader.ExcludeFields[(int)Field.MachineType])
{
if ((datItem.MachineType & MachineType.Bios) != 0)
if (datItem.MachineType.HasFlag(MachineType.Bios))
xtw.WriteAttributeString("isbios", "yes");
if ((datItem.MachineType & MachineType.Device) != 0)
if (datItem.MachineType.HasFlag(MachineType.Device))
xtw.WriteAttributeString("isdevice", "yes");
if ((datItem.MachineType & MachineType.Mechanical) != 0)
if (datItem.MachineType.HasFlag(MachineType.Mechanical))
xtw.WriteAttributeString("ismechanical", "yes");
}
if (!ExcludeFields[(int)Field.Runnable])
if (!DatHeader.ExcludeFields[(int)Field.Runnable])
{
if (datItem.Runnable == true)
xtw.WriteAttributeString("runnable", "yes");
@@ -718,21 +703,21 @@ namespace SabreTools.Library.DatFiles
xtw.WriteAttributeString("runnable", "no");
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.CloneOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.CloneOf, StringComparison.OrdinalIgnoreCase))
xtw.WriteAttributeString("cloneof", datItem.CloneOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RomOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.RomOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RomOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.RomOf, StringComparison.OrdinalIgnoreCase))
xtw.WriteAttributeString("romof", datItem.RomOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.SampleOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.SampleOf, StringComparison.OrdinalIgnoreCase))
xtw.WriteAttributeString("sampleof", datItem.SampleOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, DatHeader.ExcludeFields)))
xtw.WriteElementString("description", datItem.MachineDescription);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, DatHeader.ExcludeFields)))
xtw.WriteElementString("year", datItem.Year);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Publisher, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Publisher, DatHeader.ExcludeFields)))
xtw.WriteElementString("publisher", datItem.Publisher);
if (!ExcludeFields[(int)Field.Infos] && datItem.Infos != null && datItem.Infos.Count > 0)
if (!DatHeader.ExcludeFields[(int)Field.Infos] && datItem.Infos != null && datItem.Infos.Count > 0)
{
foreach (KeyValuePair<string, string> kvp in datItem.Infos)
{
@@ -801,10 +786,10 @@ namespace SabreTools.Library.DatFiles
case ItemType.BiosSet:
var biosSet = datItem as BiosSet;
xtw.WriteStartElement("biosset");
xtw.WriteAttributeString("name", biosSet.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.BiosDescription, ExcludeFields)))
xtw.WriteAttributeString("name", biosSet.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.BiosDescription, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("description", biosSet.Description);
if (!ExcludeFields[(int)Field.Default] && biosSet.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && biosSet.Default != null)
xtw.WriteAttributeString("default", biosSet.Default.ToString().ToLowerInvariant());
xtw.WriteEndElement();
break;
@@ -812,30 +797,32 @@ namespace SabreTools.Library.DatFiles
case ItemType.Disk:
var disk = datItem as Disk;
xtw.WriteStartElement("disk");
xtw.WriteAttributeString("name", disk.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
xtw.WriteAttributeString("name", disk.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("md5", disk.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("ripemd160", disk.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha1", disk.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha256", disk.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha384", disk.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha512", disk.SHA512.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Merge, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Merge, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("merge", disk.MergeTag);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("region", disk.Region);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Index, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Index, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("index", disk.Index);
if (!ExcludeFields[(int)Field.Writable] && disk.Writable != null)
if (!DatHeader.ExcludeFields[(int)Field.Writable] && disk.Writable != null)
xtw.WriteAttributeString("writable", disk.Writable == true ? "yes" : "no");
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
xtw.WriteAttributeString("status", disk.ItemStatus.ToString());
if (!ExcludeFields[(int)Field.Optional] && disk.Optional != null)
if (!DatHeader.ExcludeFields[(int)Field.Optional] && disk.Optional != null)
xtw.WriteAttributeString("optional", disk.Optional == true ? "yes" : "no");
xtw.WriteEndElement();
break;
@@ -843,41 +830,43 @@ namespace SabreTools.Library.DatFiles
case ItemType.Rom:
var rom = datItem as Rom;
xtw.WriteStartElement("rom");
xtw.WriteAttributeString("name", rom.GetField(Field.Name, ExcludeFields));
if (!ExcludeFields[(int)Field.Size] && rom.Size != -1)
xtw.WriteAttributeString("name", rom.GetField(Field.Name, DatHeader.ExcludeFields));
if (!DatHeader.ExcludeFields[(int)Field.Size] && rom.Size != -1)
xtw.WriteAttributeString("size", rom.Size.ToString());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("crc", rom.CRC.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("md5", rom.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("ripemd160", rom.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha1", rom.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha256", rom.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha384", rom.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha512", rom.SHA512.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Bios, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Bios, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("bios", rom.Bios);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Merge, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Merge, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("merge", rom.MergeTag);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("region", rom.Region);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Offset, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Offset, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("offset", rom.Offset);
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
xtw.WriteAttributeString("status", rom.ItemStatus.ToString().ToLowerInvariant());
if (!ExcludeFields[(int)Field.Optional] && rom.Optional != null)
if (!DatHeader.ExcludeFields[(int)Field.Optional] && rom.Optional != null)
xtw.WriteAttributeString("optional", rom.Optional == true ? "yes" : "no");
xtw.WriteEndElement();
break;
case ItemType.Sample:
xtw.WriteStartElement("sample");
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, ExcludeFields));
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
xtw.WriteEndElement();
break;
}

View File

@@ -17,7 +17,6 @@ namespace SabreTools.Library.DatFiles
/// Represents parsing and writing of a Logiqx-derived DAT
/// </summary>
/// TODO: Add XSD validation for all XML DAT types (maybe?)
/// TODO: Verify that all write for this DatFile type is correct
internal class Logiqx : DatFile
{
// Private instance variables specific to Logiqx DATs
@@ -29,7 +28,7 @@ namespace SabreTools.Library.DatFiles
/// <param name="datFile">Parent DatFile to copy from</param>
/// <param name="deprecated">True if the output uses "game", false if the output uses "machine"</param>
public Logiqx(DatFile datFile, bool deprecated)
: base(datFile, cloneHeader: false)
: base(datFile)
{
_deprecated = deprecated;
}
@@ -38,25 +37,18 @@ namespace SabreTools.Library.DatFiles
/// Parse a Logiqx XML 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Prepare all internal variables
Encoding enc = Utilities.GetEncoding(filename);
XmlReader xtr = Utilities.GetXmlTextReader(filename);
XmlReader xtr = filename.GetXmlTextReader();
List<string> dirs = new List<string>();
// If we got a null reader, just return
@@ -99,7 +91,7 @@ namespace SabreTools.Library.DatFiles
// Unique to RomVault-created DATs
case "dir":
Type = "SuperDAT";
DatHeader.Type = "SuperDAT";
dirs.Add(xtr.GetAttribute("name") ?? string.Empty);
xtr.Read();
break;
@@ -107,7 +99,7 @@ namespace SabreTools.Library.DatFiles
// We want to process the entire subtree of the game
case "machine": // New-style Logiqx
case "game": // Old-style Logiqx
ReadMachine(xtr.ReadSubtree(), dirs, filename, sysid, srcid, keep, clean, remUnicode);
ReadMachine(xtr.ReadSubtree(), dirs, filename, indexId, keep);
// Skip the machine now that we've processed it
xtr.Skip();
@@ -156,87 +148,87 @@ namespace SabreTools.Library.DatFiles
}
// Get all header items (ONLY OVERWRITE IF THERE'S NO DATA)
string content = string.Empty;
string content;
switch (reader.Name)
{
case "name":
content = reader.ReadElementContentAsString(); ;
Name = (string.IsNullOrWhiteSpace(Name) ? content : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? content : DatHeader.Name);
superdat = superdat || content.Contains(" - SuperDAT");
if (keep && superdat)
{
Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? "SuperDAT" : DatHeader.Type);
}
break;
case "description":
content = reader.ReadElementContentAsString();
Description = (string.IsNullOrWhiteSpace(Description) ? content : Description);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? content : DatHeader.Description);
break;
case "rootdir": // This is exclusive to TruRip XML
content = reader.ReadElementContentAsString();
RootDir = (string.IsNullOrWhiteSpace(RootDir) ? content : RootDir);
DatHeader.RootDir = (string.IsNullOrWhiteSpace(DatHeader.RootDir) ? content : DatHeader.RootDir);
break;
case "category":
content = reader.ReadElementContentAsString();
Category = (string.IsNullOrWhiteSpace(Category) ? content : Category);
DatHeader.Category = (string.IsNullOrWhiteSpace(DatHeader.Category) ? content : DatHeader.Category);
break;
case "version":
content = reader.ReadElementContentAsString();
Version = (string.IsNullOrWhiteSpace(Version) ? content : Version);
DatHeader.Version = (string.IsNullOrWhiteSpace(DatHeader.Version) ? content : DatHeader.Version);
break;
case "date":
content = reader.ReadElementContentAsString();
Date = (string.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date);
DatHeader.Date = (string.IsNullOrWhiteSpace(DatHeader.Date) ? content.Replace(".", "/") : DatHeader.Date);
break;
case "author":
content = reader.ReadElementContentAsString();
Author = (string.IsNullOrWhiteSpace(Author) ? content : Author);
DatHeader.Author = (string.IsNullOrWhiteSpace(DatHeader.Author) ? content : DatHeader.Author);
break;
case "email":
content = reader.ReadElementContentAsString();
Email = (string.IsNullOrWhiteSpace(Email) ? content : Email);
DatHeader.Email = (string.IsNullOrWhiteSpace(DatHeader.Email) ? content : DatHeader.Email);
break;
case "homepage":
content = reader.ReadElementContentAsString();
Homepage = (string.IsNullOrWhiteSpace(Homepage) ? content : Homepage);
DatHeader.Homepage = (string.IsNullOrWhiteSpace(DatHeader.Homepage) ? content : DatHeader.Homepage);
break;
case "url":
content = reader.ReadElementContentAsString();
Url = (string.IsNullOrWhiteSpace(Url) ? content : Url);
DatHeader.Url = (string.IsNullOrWhiteSpace(DatHeader.Url) ? content : DatHeader.Url);
break;
case "comment":
content = reader.ReadElementContentAsString();
Comment = (string.IsNullOrWhiteSpace(Comment) ? content : Comment);
DatHeader.Comment = (string.IsNullOrWhiteSpace(DatHeader.Comment) ? content : DatHeader.Comment);
break;
case "type": // This is exclusive to TruRip XML
content = reader.ReadElementContentAsString();
Type = (string.IsNullOrWhiteSpace(Type) ? content : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? content : DatHeader.Type);
superdat = superdat || content.Contains("SuperDAT");
break;
case "clrmamepro":
if (string.IsNullOrWhiteSpace(Header))
Header = reader.GetAttribute("header");
if (string.IsNullOrWhiteSpace(DatHeader.Header))
DatHeader.Header = reader.GetAttribute("header");
if (ForceMerging == ForceMerging.None)
ForceMerging = Utilities.GetForceMerging(reader.GetAttribute("forcemerging"));
if (DatHeader.ForceMerging == ForceMerging.None)
DatHeader.ForceMerging = reader.GetAttribute("forcemerging").AsForceMerging();
if (ForceNodump == ForceNodump.None)
ForceNodump = Utilities.GetForceNodump(reader.GetAttribute("forcenodump"));
if (DatHeader.ForceNodump == ForceNodump.None)
DatHeader.ForceNodump = reader.GetAttribute("forcenodump").AsForceNodump();
if (ForcePacking == ForcePacking.None)
ForcePacking = Utilities.GetForcePacking(reader.GetAttribute("forcepacking"));
if (DatHeader.ForcePacking == ForcePacking.None)
DatHeader.ForcePacking = reader.GetAttribute("forcepacking").AsForcePacking();
reader.Read();
break;
@@ -292,24 +284,18 @@ namespace SabreTools.Library.DatFiles
/// <param name="reader">XmlReader to use to parse the machine</param>
/// <param name="dirs">List of dirs to prepend to the game name</param>
/// <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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private void ReadMachine(
XmlReader reader,
List<string> dirs,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// If we have an empty machine, skip it
if (reader == null)
@@ -324,13 +310,13 @@ namespace SabreTools.Library.DatFiles
// Create a new machine
MachineType machineType = MachineType.NULL;
if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true)
if (reader.GetAttribute("isbios").AsYesNo() == true)
machineType |= MachineType.Bios;
if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) // Listxml-specific, used by older DATs
if (reader.GetAttribute("isdevice").AsYesNo() == true) // Listxml-specific, used by older DATs
machineType |= MachineType.Device;
if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) // Listxml-specific, used by older DATs
if (reader.GetAttribute("ismechanical").AsYesNo() == true) // Listxml-specific, used by older DATs
machineType |= MachineType.Mechanical;
string dirsString = (dirs != null && dirs.Count() > 0 ? string.Join("/", dirs) + "/" : string.Empty);
@@ -341,7 +327,7 @@ namespace SabreTools.Library.DatFiles
SourceFile = reader.GetAttribute("sourcefile"),
Board = reader.GetAttribute("board"),
RebuildTo = reader.GetAttribute("rebuildto"),
Runnable = Utilities.GetYesNo(reader.GetAttribute("runnable")), // Listxml-specific, used by older DATs
Runnable = reader.GetAttribute("runnable").AsYesNo(), // Listxml-specific, used by older DATs
Comment = string.Empty,
@@ -352,7 +338,7 @@ namespace SabreTools.Library.DatFiles
MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType),
};
if (Type == "SuperDAT" && !keep)
if (DatHeader.Type == "SuperDAT" && !keep)
{
string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value;
if (!string.IsNullOrWhiteSpace(tempout))
@@ -407,13 +393,13 @@ namespace SabreTools.Library.DatFiles
Region = reader.GetAttribute("region"),
Language = reader.GetAttribute("language"),
Date = reader.GetAttribute("date"),
Default = Utilities.GetYesNo(reader.GetAttribute("default")),
Default = reader.GetAttribute("default").AsYesNo(),
};
release.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(release, clean, remUnicode);
key = ParseAddHelper(release);
reader.Read();
break;
@@ -425,17 +411,16 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
Description = reader.GetAttribute("description"),
Default = Utilities.GetYesNo(reader.GetAttribute("default")),
Default = reader.GetAttribute("default").AsYesNo(),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
biosset.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(biosset, clean, remUnicode);
key = ParseAddHelper(biosset);
reader.Read();
break;
@@ -446,27 +431,28 @@ namespace SabreTools.Library.DatFiles
DatItem rom = new Rom
{
Name = reader.GetAttribute("name"),
Size = Utilities.GetSize(reader.GetAttribute("size")),
CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength),
MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length),
RIPEMD160 = Utilities.CleanHashData(reader.GetAttribute("ripemd160"), Constants.RIPEMD160Length),
SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length),
SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length),
SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length),
Size = Sanitizer.CleanSize(reader.GetAttribute("size")),
CRC = reader.GetAttribute("crc"),
MD5 = reader.GetAttribute("md5"),
#if NET_FRAMEWORK
RIPEMD160 = reader.GetAttribute("ripemd160"),
#endif
SHA1 = reader.GetAttribute("sha1"),
SHA256 = reader.GetAttribute("sha256"),
SHA384 = reader.GetAttribute("sha384"),
SHA512 = reader.GetAttribute("sha512"),
MergeTag = reader.GetAttribute("merge"),
ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")),
Date = Utilities.GetDate(reader.GetAttribute("date")),
ItemStatus = reader.GetAttribute("status").AsItemStatus(),
Date = Sanitizer.CleanDate(reader.GetAttribute("date")),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
rom.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(rom, clean, remUnicode);
key = ParseAddHelper(rom);
reader.Read();
break;
@@ -477,24 +463,25 @@ namespace SabreTools.Library.DatFiles
DatItem disk = new Disk
{
Name = reader.GetAttribute("name"),
MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length),
RIPEMD160 = Utilities.CleanHashData(reader.GetAttribute("ripemd160"), Constants.RIPEMD160Length),
SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length),
SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length),
SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length),
MD5 = reader.GetAttribute("md5"),
#if NET_FRAMEWORK
RIPEMD160 = reader.GetAttribute("ripemd160"),
#endif
SHA1 = reader.GetAttribute("sha1"),
SHA256 = reader.GetAttribute("sha256"),
SHA384 = reader.GetAttribute("sha384"),
SHA512 = reader.GetAttribute("sha512"),
MergeTag = reader.GetAttribute("merge"),
ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")),
ItemStatus = reader.GetAttribute("status").AsItemStatus(),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
disk.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(disk, clean, remUnicode);
key = ParseAddHelper(disk);
reader.Read();
break;
@@ -506,15 +493,14 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
samplerom.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(samplerom, clean, remUnicode);
key = ParseAddHelper(samplerom);
reader.Read();
break;
@@ -526,15 +512,14 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
archiverom.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(archiverom, clean, remUnicode);
key = ParseAddHelper(archiverom);
reader.Read();
break;
@@ -550,14 +535,13 @@ namespace SabreTools.Library.DatFiles
{
Blank blank = new Blank()
{
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
blank.CopyMachineInformation(machine);
// Now process and add the rom
ParseAddHelper(blank, clean, remUnicode);
ParseAddHelper(blank);
}
}
@@ -585,12 +569,10 @@ namespace SabreTools.Library.DatFiles
}
// Get the information from the trurip
string content = string.Empty;
switch (reader.Name)
{
case "titleid":
content = reader.ReadElementContentAsString();
// string titleid = content;
reader.ReadElementContentAsString();
break;
case "publisher":
@@ -606,38 +588,31 @@ namespace SabreTools.Library.DatFiles
break;
case "genre":
content = reader.ReadElementContentAsString();
// string genre = content;
reader.ReadElementContentAsString();
break;
case "subgenre":
content = reader.ReadElementContentAsString();
// string subgenre = content;
reader.ReadElementContentAsString();
break;
case "ratings":
content = reader.ReadElementContentAsString();
// string ratings = content;
reader.ReadElementContentAsString();
break;
case "score":
content = reader.ReadElementContentAsString();
// string score = content;
reader.ReadElementContentAsString();
break;
case "players":
content = reader.ReadElementContentAsString();
// string players = content;
reader.ReadElementContentAsString();
break;
case "enabled":
content = reader.ReadElementContentAsString();
// string enabled = content;
reader.ReadElementContentAsString();
break;
case "crc":
content = reader.ReadElementContentAsString();
// string crc = Utilities.GetYesNo(content);
reader.ReadElementContentAsString().AsYesNo();
break;
case "source":
@@ -649,8 +624,7 @@ namespace SabreTools.Library.DatFiles
break;
case "relatedto":
content = reader.ReadElementContentAsString();
// string relatedto = content;
reader.ReadElementContentAsString();
break;
default:
@@ -671,7 +645,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -680,10 +654,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false));
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false))
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(xtw);
@@ -732,7 +708,9 @@ namespace SabreTools.Library.DatFiles
((Rom)rom).Size = Constants.SizeZero;
((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null;
((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null;
#if NET_FRAMEWORK
((Rom)rom).RIPEMD160 = ((Rom)rom).RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null;
#endif
((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null;
((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null;
((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null;
@@ -774,38 +752,38 @@ namespace SabreTools.Library.DatFiles
{
xtw.WriteStartDocument();
xtw.WriteDocType("datafile", "-//Logiqx//DTD ROM Management Datafile//EN", "http://www.logiqx.com/Dats/datafile.dtd", null);
xtw.WriteStartElement("datafile");
xtw.WriteStartElement("header");
xtw.WriteElementString("name", Name);
xtw.WriteElementString("description", Description);
if (!string.IsNullOrWhiteSpace(RootDir))
xtw.WriteElementString("rootdir", RootDir);
if (!string.IsNullOrWhiteSpace(Category))
xtw.WriteElementString("category", Category);
xtw.WriteElementString("version", Version);
if (!string.IsNullOrWhiteSpace(Date))
xtw.WriteElementString("date", Date);
xtw.WriteElementString("author", Author);
if (!string.IsNullOrWhiteSpace(Email))
xtw.WriteElementString("email", Email);
if (!string.IsNullOrWhiteSpace(Homepage))
xtw.WriteElementString("homepage", Homepage);
if (!string.IsNullOrWhiteSpace(Url))
xtw.WriteElementString("url", Url);
if (!string.IsNullOrWhiteSpace(Comment))
xtw.WriteElementString("comment", Comment);
if (!string.IsNullOrWhiteSpace(Type))
xtw.WriteElementString("type", Type);
if (ForcePacking != ForcePacking.None
|| ForceMerging != ForceMerging.None
|| ForceNodump != ForceNodump.None
|| !string.IsNullOrWhiteSpace(Header))
xtw.WriteStartElement("datafile");
xtw.WriteStartElement("header");
xtw.WriteElementString("name", DatHeader.Name);
xtw.WriteElementString("description", DatHeader.Description);
if (!string.IsNullOrWhiteSpace(DatHeader.RootDir))
xtw.WriteElementString("rootdir", DatHeader.RootDir);
if (!string.IsNullOrWhiteSpace(DatHeader.Category))
xtw.WriteElementString("category", DatHeader.Category);
xtw.WriteElementString("version", DatHeader.Version);
if (!string.IsNullOrWhiteSpace(DatHeader.Date))
xtw.WriteElementString("date", DatHeader.Date);
xtw.WriteElementString("author", DatHeader.Author);
if (!string.IsNullOrWhiteSpace(DatHeader.Email))
xtw.WriteElementString("email", DatHeader.Email);
if (!string.IsNullOrWhiteSpace(DatHeader.Homepage))
xtw.WriteElementString("homepage", DatHeader.Homepage);
if (!string.IsNullOrWhiteSpace(DatHeader.Url))
xtw.WriteElementString("url", DatHeader.Url);
if (!string.IsNullOrWhiteSpace(DatHeader.Comment))
xtw.WriteElementString("comment", DatHeader.Comment);
if (!string.IsNullOrWhiteSpace(DatHeader.Type))
xtw.WriteElementString("type", DatHeader.Type);
if (DatHeader.ForcePacking != ForcePacking.None
|| DatHeader.ForceMerging != ForceMerging.None
|| DatHeader.ForceNodump != ForceNodump.None
|| !string.IsNullOrWhiteSpace(DatHeader.Header))
{
xtw.WriteStartElement("clrmamepro");
switch (ForcePacking)
switch (DatHeader.ForcePacking)
{
case ForcePacking.Unzip:
xtw.WriteAttributeString("forcepacking", "unzip");
@@ -815,7 +793,7 @@ namespace SabreTools.Library.DatFiles
break;
}
switch (ForceMerging)
switch (DatHeader.ForceMerging)
{
case ForceMerging.Full:
xtw.WriteAttributeString("forcemerging", "full");
@@ -831,7 +809,7 @@ namespace SabreTools.Library.DatFiles
break;
}
switch (ForceNodump)
switch (DatHeader.ForceNodump)
{
case ForceNodump.Ignore:
xtw.WriteAttributeString("forcenodump", "ignore");
@@ -844,8 +822,8 @@ namespace SabreTools.Library.DatFiles
break;
}
if (!string.IsNullOrWhiteSpace(Header))
xtw.WriteAttributeString("header", Header);
if (!string.IsNullOrWhiteSpace(DatHeader.Header))
xtw.WriteAttributeString("header", DatHeader.Header);
// End clrmamepro
xtw.WriteEndElement();
@@ -880,18 +858,18 @@ namespace SabreTools.Library.DatFiles
// Build the state based on excluded fields
xtw.WriteStartElement(_deprecated ? "game" : "machine");
xtw.WriteAttributeString("name", datItem.GetField(Field.MachineName, ExcludeFields));
if (!ExcludeFields[(int)Field.MachineType])
xtw.WriteAttributeString("name", datItem.GetField(Field.MachineName, DatHeader.ExcludeFields));
if (!DatHeader.ExcludeFields[(int)Field.MachineType])
{
if ((datItem.MachineType & MachineType.Bios) != 0)
if (datItem.MachineType.HasFlag(MachineType.Bios))
xtw.WriteAttributeString("isbios", "yes");
if ((datItem.MachineType & MachineType.Device) != 0)
if (datItem.MachineType.HasFlag(MachineType.Device))
xtw.WriteAttributeString("isdevice", "yes");
if ((datItem.MachineType & MachineType.Mechanical) != 0)
if (datItem.MachineType.HasFlag(MachineType.Mechanical))
xtw.WriteAttributeString("ismechanical", "yes");
}
if (!ExcludeFields[(int)Field.Runnable] && datItem.Runnable != null)
if (!DatHeader.ExcludeFields[(int)Field.Runnable] && datItem.Runnable != null)
{
if (datItem.Runnable == true)
xtw.WriteAttributeString("runnable", "yes");
@@ -899,22 +877,22 @@ namespace SabreTools.Library.DatFiles
xtw.WriteAttributeString("runnable", "no");
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.CloneOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.CloneOf, StringComparison.OrdinalIgnoreCase))
xtw.WriteAttributeString("cloneof", datItem.CloneOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RomOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.RomOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RomOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.RomOf, StringComparison.OrdinalIgnoreCase))
xtw.WriteAttributeString("romof", datItem.RomOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.SampleOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SampleOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.SampleOf, StringComparison.OrdinalIgnoreCase))
xtw.WriteAttributeString("sampleof", datItem.SampleOf);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Comment, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Comment, DatHeader.ExcludeFields)))
xtw.WriteElementString("comment", datItem.Comment);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, DatHeader.ExcludeFields)))
xtw.WriteElementString("description", datItem.MachineDescription);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, DatHeader.ExcludeFields)))
xtw.WriteElementString("year", datItem.Year);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Publisher, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Publisher, DatHeader.ExcludeFields)))
xtw.WriteElementString("publisher", datItem.Publisher);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Manufacturer, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Manufacturer, DatHeader.ExcludeFields)))
xtw.WriteElementString("manufacturer", datItem.Manufacturer);
xtw.Flush();
@@ -974,17 +952,17 @@ namespace SabreTools.Library.DatFiles
{
case ItemType.Archive:
xtw.WriteStartElement("archive");
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, ExcludeFields));
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
xtw.WriteEndElement();
break;
case ItemType.BiosSet:
var biosSet = datItem as BiosSet;
xtw.WriteStartElement("biosset");
xtw.WriteAttributeString("name", biosSet.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.BiosDescription, ExcludeFields)))
xtw.WriteAttributeString("name", biosSet.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.BiosDescription, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("description", biosSet.Description);
if (!ExcludeFields[(int)Field.Default] && biosSet.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && biosSet.Default != null)
xtw.WriteAttributeString("default", biosSet.Default.ToString().ToLowerInvariant());
xtw.WriteEndElement();
break;
@@ -992,20 +970,22 @@ namespace SabreTools.Library.DatFiles
case ItemType.Disk:
var disk = datItem as Disk;
xtw.WriteStartElement("disk");
xtw.WriteAttributeString("name", disk.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
xtw.WriteAttributeString("name", disk.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("md5", disk.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("ripemd160", disk.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha1", disk.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha256", disk.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha384", disk.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha512", disk.SHA512.ToLowerInvariant());
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
xtw.WriteAttributeString("status", disk.ItemStatus.ToString().ToLowerInvariant());
xtw.WriteEndElement();
break;
@@ -1013,14 +993,14 @@ namespace SabreTools.Library.DatFiles
case ItemType.Release:
var release = datItem as Release;
xtw.WriteStartElement("release");
xtw.WriteAttributeString("name", release.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, ExcludeFields)))
xtw.WriteAttributeString("name", release.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("region", release.Region);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Language, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Language, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("language", release.Language);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("date", release.Date);
if (!ExcludeFields[(int)Field.Default] && release.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && release.Default != null)
xtw.WriteAttributeString("default", release.Default.ToString().ToLowerInvariant());
xtw.WriteEndElement();
break;
@@ -1028,33 +1008,35 @@ namespace SabreTools.Library.DatFiles
case ItemType.Rom:
var rom = datItem as Rom;
xtw.WriteStartElement("rom");
xtw.WriteAttributeString("name", rom.GetField(Field.Name, ExcludeFields));
if (!ExcludeFields[(int)Field.Size] && rom.Size != -1)
xtw.WriteAttributeString("name", rom.GetField(Field.Name, DatHeader.ExcludeFields));
if (!DatHeader.ExcludeFields[(int)Field.Size] && rom.Size != -1)
xtw.WriteAttributeString("size", rom.Size.ToString());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("crc", rom.CRC.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("md5", rom.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("ripemd160", rom.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha1", rom.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha256", rom.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha384", rom.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha512", rom.SHA512.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("date", rom.Date);
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
xtw.WriteAttributeString("status", rom.ItemStatus.ToString().ToLowerInvariant());
xtw.WriteEndElement();
break;
case ItemType.Sample:
xtw.WriteStartElement("sample");
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, ExcludeFields));
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
xtw.WriteEndElement();
break;
}

View File

@@ -20,7 +20,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Missfile(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -28,21 +28,15 @@ namespace SabreTools.Library.DatFiles
/// Parse a Missfile 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// There is no consistent way to parse a missfile...
throw new NotImplementedException();
@@ -59,7 +53,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -146,21 +140,21 @@ namespace SabreTools.Library.DatFiles
ProcessItemName(datItem, false, forceRomName: false);
// If we're in Romba mode, the state is consistent
if (Romba)
if (DatHeader.Romba)
{
sw.Write($"{datItem.GetField(Field.SHA1, ExcludeFields)}\n");
sw.Write($"{datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)}\n");
}
// Otherwise, use any flags
else
{
if (!UseRomName && datItem.MachineName != lastgame)
if (!DatHeader.UseRomName && datItem.MachineName != lastgame)
{
sw.Write($"{datItem.GetField(Field.MachineName, ExcludeFields)}\n");
sw.Write($"{datItem.GetField(Field.MachineName, DatHeader.ExcludeFields)}\n");
lastgame = datItem.MachineName;
}
else if (UseRomName)
else if (DatHeader.UseRomName)
{
sw.Write($"{datItem.GetField(Field.Name, ExcludeFields)}\n");
sw.Write($"{datItem.GetField(Field.Name, DatHeader.ExcludeFields)}\n");
}
}

View File

@@ -14,7 +14,6 @@ namespace SabreTools.Library.DatFiles
/// <summary>
/// Represents parsing and writing of an OfflineList XML DAT
/// </summary>
/// TODO: Verify that all write for this DatFile type is correct
internal class OfflineList : DatFile
{
/// <summary>
@@ -22,7 +21,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public OfflineList(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -30,26 +29,19 @@ namespace SabreTools.Library.DatFiles
/// Parse an OfflineList XML 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <remarks>
/// </remarks>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
Encoding enc = Utilities.GetEncoding(filename);
XmlReader xtr = Utilities.GetXmlTextReader(filename);
XmlReader xtr = filename.GetXmlTextReader();
// If we got a null reader, just return
if (xtr == null)
@@ -78,7 +70,7 @@ namespace SabreTools.Library.DatFiles
break;
case "games":
ReadGames(xtr.ReadSubtree(), keep, clean, remUnicode);
ReadGames(xtr.ReadSubtree(), filename, indexId);
// Skip the games node now that we've processed it
xtr.Skip();
@@ -128,37 +120,34 @@ namespace SabreTools.Library.DatFiles
}
// Get all configuration items (ONLY OVERWRITE IF THERE'S NO DATA)
string content = string.Empty;
string content;
switch (reader.Name.ToLowerInvariant())
{
case "datname":
content = reader.ReadElementContentAsString();
Name = (string.IsNullOrWhiteSpace(Name) ? content : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? content : DatHeader.Name);
superdat = superdat || content.Contains(" - SuperDAT");
if (keep && superdat)
{
Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? "SuperDAT" : DatHeader.Type);
}
break;
case "datversion":
content = reader.ReadElementContentAsString();
Version = (string.IsNullOrWhiteSpace(Version) ? content : Version);
DatHeader.Version = (string.IsNullOrWhiteSpace(DatHeader.Version) ? content : DatHeader.Version);
break;
case "system":
content = reader.ReadElementContentAsString();
// string system = content;
reader.ReadElementContentAsString();
break;
case "screenshotswidth":
content = reader.ReadElementContentAsString();
// string screenshotsWidth = content; // Int32?
reader.ReadElementContentAsString(); // Int32?
break;
case "screenshotsheight":
content = reader.ReadElementContentAsString();
// string screenshotsHeight = content; // Int32?
reader.ReadElementContentAsString(); // Int32?
break;
case "infos":
@@ -190,9 +179,7 @@ namespace SabreTools.Library.DatFiles
break;
case "romtitle":
content = reader.ReadElementContentAsString();
// string romtitle = content;
reader.ReadElementContentAsString();
break;
default:
@@ -229,93 +216,93 @@ namespace SabreTools.Library.DatFiles
switch (reader.Name.ToLowerInvariant())
{
case "title":
// string title_visible = reader.GetAttribute("visible"); // (true|false)
// string title_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string title_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "location":
// string location_visible = reader.GetAttribute("visible"); // (true|false)
// string location_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string location_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "publisher":
// string publisher_visible = reader.GetAttribute("visible"); // (true|false)
// string publisher_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string publisher_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "sourcerom":
// string sourceRom_visible = reader.GetAttribute("visible"); // (true|false)
// string sourceRom_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string sourceRom_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "savetype":
// string saveType_visible = reader.GetAttribute("visible"); // (true|false)
// string saveType_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string saveType_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "romsize":
// string romSize_visible = reader.GetAttribute("visible"); // (true|false)
// string romSize_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string romSize_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "releasenumber":
// string releaseNumber_visible = reader.GetAttribute("visible"); // (true|false)
// string releaseNumber_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string releaseNumber_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "languagenumber":
// string languageNumber_visible = reader.GetAttribute("visible"); // (true|false)
// string languageNumber_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string languageNumber_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "comment":
// string comment_visible = reader.GetAttribute("visible"); // (true|false)
// string comment_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string comment_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "romcrc":
// string romCRC_visible = reader.GetAttribute("visible"); // (true|false)
// string romCRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string romCRC_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "im1crc":
// string im1CRC_visible = reader.GetAttribute("visible"); // (true|false)
// string im1CRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string im1CRC_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "im2crc":
// string im2CRC_visible = reader.GetAttribute("visible"); // (true|false)
// string im2CRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string im2CRC_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
case "languages":
// string languages_visible = reader.GetAttribute("visible"); // (true|false)
// string languages_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false)
// string languages_default = reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("visible"); // (true|false)
reader.GetAttribute("inNamingOption"); // (true|false)
reader.GetAttribute("default"); // (true|false)
reader.Read();
break;
@@ -390,23 +377,21 @@ namespace SabreTools.Library.DatFiles
}
// Get all newdat items
string content = string.Empty;
string content;
switch (reader.Name.ToLowerInvariant())
{
case "datversionurl":
content = reader.ReadElementContentAsString();
Url = (string.IsNullOrWhiteSpace(Name) ? content : Url);
DatHeader.Url = (string.IsNullOrWhiteSpace(DatHeader.Url) ? content : DatHeader.Url);
break;
case "daturl":
// string fileName = reader.GetAttribute("fileName");
content = reader.ReadElementContentAsString();
// string url = content;
reader.GetAttribute("fileName");
reader.ReadElementContentAsString();
break;
case "imurl":
content = reader.ReadElementContentAsString();
// string url = content;
reader.ReadElementContentAsString();
break;
default:
@@ -440,13 +425,12 @@ namespace SabreTools.Library.DatFiles
}
// Get all search items
string content = string.Empty;
switch (reader.Name.ToLowerInvariant())
{
case "to":
// string value = reader.GetAttribute("value");
// string default = reader.GetAttribute("default"); (true|false)
// string auto = reader.GetAttribute("auto"); (true|false)
reader.GetAttribute("value");
reader.GetAttribute("default"); // (true|false)
reader.GetAttribute("auto"); // (true|false)
ReadTo(reader.ReadSubtree());
@@ -485,14 +469,12 @@ namespace SabreTools.Library.DatFiles
}
// Get all search items
string content = string.Empty;
switch (reader.Name.ToLowerInvariant())
{
case "find":
// string operation = reader.GetAttribute("operation");
// string value = reader.GetAttribute("value"); // Int32?
content = reader.ReadElementContentAsString();
// string findValue = content;
reader.GetAttribute("operation");
reader.GetAttribute("value"); // Int32?
reader.ReadElementContentAsString();
break;
default:
@@ -506,15 +488,14 @@ namespace SabreTools.Library.DatFiles
/// Read games information
/// </summary>
/// <param name="reader">XmlReader to use to parse the header</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private void ReadGames(XmlReader reader,
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadGames(
XmlReader reader,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
// Standard Dat parsing
string filename,
int indexId)
{
// If there's no subtree to the configuration, skip it
if (reader == null)
@@ -537,7 +518,7 @@ namespace SabreTools.Library.DatFiles
switch (reader.Name.ToLowerInvariant())
{
case "game":
ReadGame(reader.ReadSubtree(), keep, clean, remUnicode);
ReadGame(reader.ReadSubtree(), filename, indexId);
// Skip the game node now that we've processed it
reader.Skip();
@@ -554,18 +535,17 @@ namespace SabreTools.Library.DatFiles
/// Read game information
/// </summary>
/// <param name="reader">XmlReader to use to parse the header</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private void ReadGame(XmlReader reader,
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadGame(
XmlReader reader,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
// Standard Dat parsing
string filename,
int indexId)
{
// Prepare all internal variables
string releaseNumber = string.Empty, key = string.Empty, publisher = string.Empty, duplicateid = string.Empty;
string releaseNumber = string.Empty, publisher = string.Empty, duplicateid;
long size = -1;
List<Rom> roms = new List<Rom>();
Machine machine = new Machine();
@@ -588,12 +568,10 @@ namespace SabreTools.Library.DatFiles
}
// Get all games items
string content = string.Empty;
switch (reader.Name.ToLowerInvariant())
{
case "imagenumber":
content = reader.ReadElementContentAsString();
// string imageNumber = content;
reader.ReadElementContentAsString();
break;
case "releasenumber":
@@ -601,13 +579,11 @@ namespace SabreTools.Library.DatFiles
break;
case "title":
content = reader.ReadElementContentAsString();
machine.Name = content;
machine.Name = reader.ReadElementContentAsString();
break;
case "savetype":
content = reader.ReadElementContentAsString();
// string saveType = content;
reader.ReadElementContentAsString();
break;
case "romsize":
@@ -621,34 +597,30 @@ namespace SabreTools.Library.DatFiles
break;
case "location":
content = reader.ReadElementContentAsString();
// string location = content;
reader.ReadElementContentAsString();
break;
case "sourcerom":
content = reader.ReadElementContentAsString();
// string sourceRom = content;
reader.ReadElementContentAsString();
break;
case "language":
content = reader.ReadElementContentAsString();
// string language = content;
reader.ReadElementContentAsString();
break;
case "files":
roms = ReadFiles(reader.ReadSubtree(), releaseNumber, machine.Name, keep, clean, remUnicode);
roms = ReadFiles(reader.ReadSubtree(), releaseNumber, machine.Name, filename, indexId);
// Skip the files node now that we've processed it
reader.Skip();
break;
case "im1crc":
content = reader.ReadElementContentAsString();
// string im1crc = content;
reader.ReadElementContentAsString();
break;
case "im2crc":
content = reader.ReadElementContentAsString();
// string im2crc = content;
reader.ReadElementContentAsString();
break;
case "comment":
@@ -676,7 +648,7 @@ namespace SabreTools.Library.DatFiles
roms[i].CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(roms[i], clean, remUnicode);
ParseAddHelper(roms[i]);
}
}
@@ -686,17 +658,16 @@ namespace SabreTools.Library.DatFiles
/// <param name="reader">XmlReader to use to parse the header</param>
/// <param name="releaseNumber">Release number from the parent game</param>
/// <param name="machineName">Name of the parent game to use</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private List<Rom> ReadFiles(XmlReader reader,
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
private List<Rom> ReadFiles(
XmlReader reader,
string releaseNumber,
string machineName,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
// Standard Dat parsing
string filename,
int indexId)
{
// Prepare all internal variables
var extensionToCrc = new List<KeyValuePair<string, string>>();
@@ -741,9 +712,12 @@ namespace SabreTools.Library.DatFiles
roms.Add(new Rom()
{
Name = (releaseNumber != "0" ? releaseNumber + " - " : string.Empty) + machineName + pair.Key,
CRC = Utilities.CleanHashData(pair.Value, Constants.CRCLength),
CRC = pair.Value,
ItemStatus = ItemStatus.None,
IndexId = indexId,
IndexSource = filename,
});
}
@@ -761,7 +735,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -770,10 +744,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false));
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false))
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(xtw);
@@ -814,7 +790,9 @@ namespace SabreTools.Library.DatFiles
((Rom)rom).Size = Constants.SizeZero;
((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null;
((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null;
#if NET_FRAMEWORK
((Rom)rom).RIPEMD160 = ((Rom)rom).RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null;
#endif
((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null;
((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null;
((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null;
@@ -861,8 +839,8 @@ namespace SabreTools.Library.DatFiles
xtw.WriteAttributeString("noNamespaceSchemaLocation", "xsi", "datas.xsd");
xtw.WriteStartElement("configuration");
xtw.WriteElementString("datName", Name);
xtw.WriteElementString("datVersion", Count.ToString());
xtw.WriteElementString("datName", DatHeader.Name);
xtw.WriteElementString("datVersion", DatStats.Count.ToString());
xtw.WriteElementString("system", "none");
xtw.WriteElementString("screenshotsWidth", "240");
xtw.WriteElementString("screenshotsHeight", "160");
@@ -955,14 +933,14 @@ namespace SabreTools.Library.DatFiles
xtw.WriteEndElement();
xtw.WriteStartElement("newDat");
xtw.WriteElementString("datVersionURL", Url);
xtw.WriteElementString("datVersionURL", DatHeader.Url);
xtw.WriteStartElement("datUrl");
xtw.WriteAttributeString("fileName", $"{FileName}.zip");
xtw.WriteString(Url);
xtw.WriteAttributeString("fileName", $"{DatHeader.FileName}.zip");
xtw.WriteString(DatHeader.Url);
xtw.WriteEndElement();
xtw.WriteElementString("imURL", Url);
xtw.WriteElementString("imURL", DatHeader.Url);
// End newDat
xtw.WriteEndElement();
@@ -1048,13 +1026,13 @@ namespace SabreTools.Library.DatFiles
xtw.WriteStartElement("game");
xtw.WriteElementString("imageNumber", "1");
xtw.WriteElementString("releaseNumber", "1");
xtw.WriteElementString("title", datItem.GetField(Field.Name, ExcludeFields));
xtw.WriteElementString("title", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
xtw.WriteElementString("saveType", "None");
if (datItem.ItemType == ItemType.Rom)
{
var rom = datItem as Rom;
xtw.WriteElementString("romSize", datItem.GetField(Field.Size, ExcludeFields));
xtw.WriteElementString("romSize", datItem.GetField(Field.Size, DatHeader.ExcludeFields));
}
xtw.WriteElementString("publisher", "None");
@@ -1066,14 +1044,14 @@ namespace SabreTools.Library.DatFiles
{
var disk = datItem as Disk;
xtw.WriteStartElement("files");
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
{
xtw.WriteStartElement("romMD5");
xtw.WriteAttributeString("extension", ".chd");
xtw.WriteString(disk.MD5.ToUpperInvariant());
xtw.WriteEndElement();
}
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
{
xtw.WriteStartElement("romSHA1");
xtw.WriteAttributeString("extension", ".chd");
@@ -1087,24 +1065,24 @@ namespace SabreTools.Library.DatFiles
else if (datItem.ItemType == ItemType.Rom)
{
var rom = datItem as Rom;
string tempext = "." + Utilities.GetExtension(rom.Name);
string tempext = "." + PathExtensions.GetNormalizedExtension(rom.Name);
xtw.WriteStartElement("files");
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, DatHeader.ExcludeFields)))
{
xtw.WriteStartElement("romCRC");
xtw.WriteAttributeString("extension", tempext);
xtw.WriteString(rom.CRC.ToUpperInvariant());
xtw.WriteEndElement();
}
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
{
xtw.WriteStartElement("romMD5");
xtw.WriteAttributeString("extension", tempext);
xtw.WriteString(rom.MD5.ToUpperInvariant());
xtw.WriteEndElement();
}
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
else if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
{
xtw.WriteStartElement("romSHA1");
xtw.WriteAttributeString("extension", tempext);
@@ -1120,7 +1098,7 @@ namespace SabreTools.Library.DatFiles
xtw.WriteElementString("im2CRC", "00000000");
xtw.WriteElementString("comment", "");
xtw.WriteElementString("duplicateID", "0");
// End game
xtw.WriteEndElement();

View File

@@ -14,7 +14,6 @@ namespace SabreTools.Library.DatFiles
/// <summary>
/// Represents parsing and writing of a openMSX softawre list XML DAT
/// </summary>
/// TODO: Verify that all write for this DatFile type is correct
internal class OpenMSX : DatFile
{
/// <summary>
@@ -22,7 +21,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public OpenMSX(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -30,27 +29,18 @@ namespace SabreTools.Library.DatFiles
/// Parse a openMSX softawre list XML 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <remarks>
/// </remarks>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Prepare all internal variables
Encoding enc = Utilities.GetEncoding(filename);
XmlReader xtr = Utilities.GetXmlTextReader(filename);
XmlReader xtr = filename.GetXmlTextReader();
// If we got a null reader, just return
if (xtr == null)
@@ -72,15 +62,15 @@ namespace SabreTools.Library.DatFiles
switch (xtr.Name)
{
case "softwaredb":
Name = (string.IsNullOrWhiteSpace(Name) ? "openMSX Software List" : Name);
Description = (string.IsNullOrWhiteSpace(Description) ? Name : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? "openMSX Software List" : DatHeader.Name);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? DatHeader.Name : DatHeader.Description);
// string timestamp = xtr.GetAttribute("timestamp"); // CDATA
xtr.Read();
break;
// We want to process the entire subtree of the software
case "software":
ReadSoftware(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode);
ReadSoftware(xtr.ReadSubtree(), filename, indexId);
// Skip the software now that we've processed it
xtr.Skip();
@@ -108,23 +98,13 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="reader">XmlReader representing a machine block</param>
/// <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="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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadSoftware(
XmlReader reader,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
int indexId)
{
// If we have an empty machine, skip it
if (reader == null)
@@ -179,7 +159,7 @@ namespace SabreTools.Library.DatFiles
break;
case "dump":
containsItems = ReadDump(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode);
containsItems = ReadDump(reader.ReadSubtree(), machine, diskno, filename, indexId);
diskno++;
// Skip the dump now that we've processed it
@@ -197,14 +177,13 @@ namespace SabreTools.Library.DatFiles
{
Blank blank = new Blank()
{
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
blank.CopyMachineInformation(machine);
// Now process and add the rom
ParseAddHelper(blank, clean, remUnicode);
ParseAddHelper(blank);
}
}
@@ -215,11 +194,7 @@ namespace SabreTools.Library.DatFiles
/// <param name="machine">Machine information to pass to contained items</param>
/// <param name="diskno">Disk number to use when outputting to other DAT formats</param>
/// <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="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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="indexId">Index ID for the DAT</param>
private bool ReadDump(
XmlReader reader,
Machine machine,
@@ -227,13 +202,7 @@ namespace SabreTools.Library.DatFiles
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
int indexId)
{
bool containsItems = false;
@@ -250,21 +219,21 @@ namespace SabreTools.Library.DatFiles
switch (reader.Name)
{
case "rom":
containsItems = ReadRom(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode);
containsItems = ReadRom(reader.ReadSubtree(), machine, diskno, filename, indexId);
// Skip the rom now that we've processed it
reader.Skip();
break;
case "megarom":
containsItems = ReadMegaRom(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode);
containsItems = ReadMegaRom(reader.ReadSubtree(), machine, diskno, filename, indexId);
// Skip the megarom now that we've processed it
reader.Skip();
break;
case "sccpluscart":
containsItems = ReadSccPlusCart(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode);
containsItems = ReadSccPlusCart(reader.ReadSubtree(), machine, diskno, filename, indexId);
// Skip the sccpluscart now that we've processed it
reader.Skip();
@@ -292,11 +261,7 @@ namespace SabreTools.Library.DatFiles
/// <param name="machine">Machine information to pass to contained items</param>
/// <param name="diskno">Disk number to use when outputting to other DAT formats</param>
/// <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="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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="indexId">Index ID for the DAT</param>
private bool ReadRom(
XmlReader reader,
Machine machine,
@@ -304,15 +269,9 @@ namespace SabreTools.Library.DatFiles
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
int indexId)
{
string hash = string.Empty, offset = string.Empty, type = string.Empty, remark = string.Empty;
string hash = string.Empty, offset = string.Empty, remark = string.Empty;
bool containsItems = false;
while (!reader.EOF)
@@ -337,7 +296,7 @@ namespace SabreTools.Library.DatFiles
break;
case "type":
type = reader.ReadElementContentAsString();
reader.ReadElementContentAsString();
break;
case "remark":
@@ -356,11 +315,14 @@ namespace SabreTools.Library.DatFiles
Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
Offset = offset,
Size = -1,
SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length),
SHA1 = hash,
IndexId = indexId,
IndexSource = filename,
};
rom.CopyMachineInformation(machine);
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
return containsItems;
}
@@ -372,11 +334,7 @@ namespace SabreTools.Library.DatFiles
/// <param name="machine">Machine information to pass to contained items</param>
/// <param name="diskno">Disk number to use when outputting to other DAT formats</param>
/// <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="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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="indexId">Index ID for the DAT</param>
private bool ReadMegaRom(
XmlReader reader,
Machine machine,
@@ -384,15 +342,9 @@ namespace SabreTools.Library.DatFiles
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
int indexId)
{
string hash = string.Empty, offset = string.Empty, type = string.Empty, remark = string.Empty;
string hash = string.Empty, offset = string.Empty, remark = string.Empty;
bool containsItems = false;
while (!reader.EOF)
@@ -417,7 +369,7 @@ namespace SabreTools.Library.DatFiles
break;
case "type":
type = reader.ReadElementContentAsString();
reader.ReadElementContentAsString();
break;
case "remark":
@@ -436,11 +388,14 @@ namespace SabreTools.Library.DatFiles
Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
Offset = offset,
Size = -1,
SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length),
SHA1 = hash,
IndexId = indexId,
IndexSource = filename,
};
rom.CopyMachineInformation(machine);
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
return containsItems;
}
@@ -452,11 +407,7 @@ namespace SabreTools.Library.DatFiles
/// <param name="machine">Machine information to pass to contained items</param>
/// <param name="diskno">Disk number to use when outputting to other DAT formats</param>
/// <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="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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <param name="indexId">Index ID for the DAT</param>
private bool ReadSccPlusCart(
XmlReader reader,
Machine machine,
@@ -464,15 +415,9 @@ namespace SabreTools.Library.DatFiles
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
int indexId)
{
string hash = string.Empty, boot = string.Empty, remark = string.Empty;
string hash = string.Empty, remark = string.Empty;
bool containsItems = false;
while (!reader.EOF)
@@ -488,7 +433,7 @@ namespace SabreTools.Library.DatFiles
switch (reader.Name)
{
case "boot":
boot = reader.ReadElementContentAsString();
reader.ReadElementContentAsString();
break;
case "hash":
@@ -511,11 +456,14 @@ namespace SabreTools.Library.DatFiles
{
Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
Size = -1,
SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length),
SHA1 = hash,
IndexId = indexId,
IndexSource = filename,
};
rom.CopyMachineInformation(machine);
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
return containsItems;
}
@@ -531,7 +479,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -540,10 +488,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false));
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false))
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(xtw);
@@ -671,11 +621,11 @@ namespace SabreTools.Library.DatFiles
// Build the state based on excluded fields
xtw.WriteStartElement("software");
xtw.WriteElementString("title", datItem.GetField(Field.MachineName, ExcludeFields));
xtw.WriteElementString("title", datItem.GetField(Field.MachineName, DatHeader.ExcludeFields));
//xtw.WriteElementString("genmsxid", msxid);
//xtw.WriteElementString("system", system));
xtw.WriteElementString("company", datItem.GetField(Field.Manufacturer, ExcludeFields));
xtw.WriteElementString("year", datItem.GetField(Field.Year, ExcludeFields));
xtw.WriteElementString("company", datItem.GetField(Field.Manufacturer, DatHeader.ExcludeFields));
xtw.WriteElementString("year", datItem.GetField(Field.Year, DatHeader.ExcludeFields));
//xtw.WriteElementString("country", country);
xtw.Flush();
@@ -743,10 +693,10 @@ namespace SabreTools.Library.DatFiles
//xtw.WriteEndElement();
xtw.WriteStartElement("rom");
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Offset, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Offset, DatHeader.ExcludeFields)))
xtw.WriteElementString("start", rom.Offset);
//xtw.WriteElementString("type", "Normal");
xtw.WriteElementString("hash", rom.GetField(Field.SHA1, ExcludeFields).ToLowerInvariant());
xtw.WriteElementString("hash", rom.GetField(Field.SHA1, DatHeader.ExcludeFields).ToLowerInvariant());
//xtw.WriteElementString("remark", "");
// End rom

View File

@@ -22,7 +22,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public RomCenter(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -30,25 +30,18 @@ namespace SabreTools.Library.DatFiles
/// 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Prepare all intenral variables
Encoding enc = Utilities.GetEncoding(filename);
IniReader ir = Utilities.GetIniReader(filename, false);
IniReader ir = filename.GetIniReader(false);
// If we got a null reader, just return
if (ir == null)
@@ -85,7 +78,7 @@ namespace SabreTools.Library.DatFiles
break;
case "games":
ReadGamesSection(ir, sysid, srcid, clean, remUnicode);
ReadGamesSection(ir, filename, indexId);
break;
// Unknown section so we ignore it
@@ -137,32 +130,37 @@ namespace SabreTools.Library.DatFiles
switch (kvp?.Key.ToLowerInvariant())
{
case "author":
Author = string.IsNullOrWhiteSpace(Author) ? kvp?.Value : Author;
DatHeader.Author = string.IsNullOrWhiteSpace(DatHeader.Author) ? kvp?.Value : DatHeader.Author;
reader.ReadNextLine();
break;
case "version":
Version = string.IsNullOrWhiteSpace(Version) ? kvp?.Value : Version;
DatHeader.Version = string.IsNullOrWhiteSpace(DatHeader.Version) ? kvp?.Value : DatHeader.Version;
reader.ReadNextLine();
break;
case "email":
Email = string.IsNullOrWhiteSpace(Email) ? kvp?.Value : Email;
DatHeader.Email = string.IsNullOrWhiteSpace(DatHeader.Email) ? kvp?.Value : DatHeader.Email;
reader.ReadNextLine();
break;
case "homepage":
Homepage = string.IsNullOrWhiteSpace(Homepage) ? kvp?.Value : Homepage;
DatHeader.Homepage = string.IsNullOrWhiteSpace(DatHeader.Homepage) ? kvp?.Value : DatHeader.Homepage;
reader.ReadNextLine();
break;
case "url":
Url = string.IsNullOrWhiteSpace(Url) ? kvp?.Value : Url;
DatHeader.Url = string.IsNullOrWhiteSpace(DatHeader.Url) ? kvp?.Value : DatHeader.Url;
reader.ReadNextLine();
break;
case "date":
Date = string.IsNullOrWhiteSpace(Date) ? kvp?.Value : Date;
DatHeader.Date = string.IsNullOrWhiteSpace(DatHeader.Date) ? kvp?.Value : DatHeader.Date;
reader.ReadNextLine();
break;
case "comment":
DatHeader.Comment = string.IsNullOrWhiteSpace(DatHeader.Comment) ? kvp?.Value : DatHeader.Comment;
reader.ReadNextLine();
break;
@@ -207,25 +205,23 @@ namespace SabreTools.Library.DatFiles
switch (kvp?.Key.ToLowerInvariant())
{
case "version":
string rcVersion = kvp?.Value;
reader.ReadNextLine();
break;
case "plugin":
string plugin = kvp?.Value;
reader.ReadNextLine();
break;
case "split":
if (ForceMerging == ForceMerging.None && kvp?.Value == "1")
ForceMerging = ForceMerging.Split;
if (DatHeader.ForceMerging == ForceMerging.None && kvp?.Value == "1")
DatHeader.ForceMerging = ForceMerging.Split;
reader.ReadNextLine();
break;
case "merge":
if (ForceMerging == ForceMerging.None && kvp?.Value == "1")
ForceMerging = ForceMerging.Merged;
if (DatHeader.ForceMerging == ForceMerging.None && kvp?.Value == "1")
DatHeader.ForceMerging = ForceMerging.Merged;
reader.ReadNextLine();
break;
@@ -271,12 +267,12 @@ namespace SabreTools.Library.DatFiles
switch (kvp?.Key.ToLowerInvariant())
{
case "refname":
Name = string.IsNullOrWhiteSpace(Name) ? kvp?.Value : Name;
DatHeader.Name = string.IsNullOrWhiteSpace(DatHeader.Name) ? kvp?.Value : DatHeader.Name;
reader.ReadNextLine();
break;
case "version":
Description = string.IsNullOrWhiteSpace(Description) ? kvp?.Value : Description;
DatHeader.Description = string.IsNullOrWhiteSpace(DatHeader.Description) ? kvp?.Value : DatHeader.Description;
reader.ReadNextLine();
break;
@@ -292,11 +288,9 @@ namespace SabreTools.Library.DatFiles
/// Read games information
/// </summary>
/// <param name="reader">IniReader to use to parse the credits</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="clean">True if game names are sanitized, false otherwise (default)</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private void ReadGamesSection(IniReader reader, int sysid, int srcid, bool clean, bool remUnicode)
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
private void ReadGamesSection(IniReader reader, string filename, int indexId)
{
// If the reader is somehow null, skip it
if (reader == null)
@@ -349,7 +343,7 @@ namespace SabreTools.Library.DatFiles
{
Name = rominfo[5],
Size = size,
CRC = Utilities.CleanHashData(rominfo[6], Constants.CRCLength),
CRC = rominfo[6],
ItemStatus = ItemStatus.None,
MachineName = rominfo[3],
@@ -358,12 +352,12 @@ namespace SabreTools.Library.DatFiles
RomOf = rominfo[8],
MergeTag = rominfo[9],
SystemID = sysid,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
// Now process and add the rom
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
reader.ReadNextLine();
}
@@ -380,7 +374,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -431,7 +425,9 @@ namespace SabreTools.Library.DatFiles
((Rom)rom).Size = Constants.SizeZero;
((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null;
((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null;
#if NET_FRAMEWORK
((Rom)rom).RIPEMD160 = ((Rom)rom).RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null;
#endif
((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null;
((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null;
((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null;
@@ -469,18 +465,18 @@ namespace SabreTools.Library.DatFiles
try
{
iw.WriteSection("CREDITS");
iw.WriteKeyValuePair("author", Author);
iw.WriteKeyValuePair("version", Version);
iw.WriteKeyValuePair("comment", Comment);
iw.WriteKeyValuePair("author", DatHeader.Author);
iw.WriteKeyValuePair("version", DatHeader.Version);
iw.WriteKeyValuePair("comment", DatHeader.Comment);
iw.WriteSection("DAT");
iw.WriteKeyValuePair("version", "2.50");
iw.WriteKeyValuePair("split", ForceMerging == ForceMerging.Split ? "1" : "0");
iw.WriteKeyValuePair("merge", ForceMerging == ForceMerging.Full || ForceMerging == ForceMerging.Merged ? "1" : "0");
iw.WriteKeyValuePair("split", DatHeader.ForceMerging == ForceMerging.Split ? "1" : "0");
iw.WriteKeyValuePair("merge", DatHeader.ForceMerging == ForceMerging.Full || DatHeader.ForceMerging == ForceMerging.Merged ? "1" : "0");
iw.WriteSection("EMULATOR");
iw.WriteKeyValuePair("refname", Name);
iw.WriteKeyValuePair("version", Description);
iw.WriteKeyValuePair("refname", DatHeader.Name);
iw.WriteKeyValuePair("version", DatHeader.Description);
iw.WriteSection("GAMES");
@@ -527,18 +523,18 @@ namespace SabreTools.Library.DatFiles
ProcessItemName(datItem, true);
// Build the state based on excluded fields
iw.WriteString($"¬{datItem.GetField(Field.CloneOf, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.CloneOf, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.MachineName, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.CloneOf, DatHeader.ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.CloneOf, DatHeader.ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.MachineName, DatHeader.ExcludeFields)}");
if (string.IsNullOrWhiteSpace(datItem.MachineDescription))
iw.WriteString($"¬{datItem.GetField(Field.MachineName, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.MachineName, DatHeader.ExcludeFields)}");
else
iw.WriteString($"¬{datItem.GetField(Field.Description, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.Name, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.CRC, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.Size, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.RomOf, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.Merge, ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.Description, DatHeader.ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.Name, DatHeader.ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.CRC, DatHeader.ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.Size, DatHeader.ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.RomOf, DatHeader.ExcludeFields)}");
iw.WriteString($"¬{datItem.GetField(Field.Merge, DatHeader.ExcludeFields)}");
iw.WriteString("¬");
iw.WriteLine();

View File

@@ -15,7 +15,6 @@ namespace SabreTools.Library.DatFiles
/// <summary>
/// Represents parsing and writing of an SabreDat XML DAT
/// </summary>
/// TODO: Verify that all write for this DatFile type is correct
internal class SabreDat : DatFile
{
/// <summary>
@@ -23,7 +22,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SabreDat(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -31,29 +30,22 @@ namespace SabreTools.Library.DatFiles
/// Parse an SabreDat XML DAT and return all found directories and files 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Prepare all internal variables
bool empty = true;
string key = string.Empty;
string key;
List<string> parent = new List<string>();
Encoding enc = Utilities.GetEncoding(filename);
XmlReader xtr = Utilities.GetXmlTextReader(filename);
XmlReader xtr = filename.GetXmlTextReader();
// If we got a null reader, just return
if (xtr == null)
@@ -72,10 +64,10 @@ namespace SabreTools.Library.DatFiles
if (empty)
{
string tempgame = string.Join("\\", parent);
Rom rom = new Rom("null", tempgame, omitFromScan: Hash.DeepHashes); // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
Rom rom = new Rom("null", tempgame);
// Now process and add the rom
key = ParseAddHelper(rom, clean, remUnicode);
key = ParseAddHelper(rom);
}
// Regardless, end the current folder
@@ -91,7 +83,7 @@ namespace SabreTools.Library.DatFiles
{
parent.RemoveAt(parent.Count - 1);
if (keep && parentcount > 1)
Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? "SuperDAT" : DatHeader.Type);
}
}
@@ -114,7 +106,7 @@ namespace SabreTools.Library.DatFiles
case "dir":
case "directory":
empty = ReadDirectory(xtr.ReadSubtree(), parent, filename, sysid, srcid, keep, clean, remUnicode);
empty = ReadDirectory(xtr.ReadSubtree(), parent, filename, indexId, keep);
// Skip the directory node now that we've processed it
xtr.Read();
@@ -160,55 +152,55 @@ namespace SabreTools.Library.DatFiles
}
// Get all header items (ONLY OVERWRITE IF THERE'S NO DATA)
string content = string.Empty;
string content;
switch (reader.Name)
{
case "name":
content = reader.ReadElementContentAsString(); ;
Name = (string.IsNullOrWhiteSpace(Name) ? content : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? content : DatHeader.Name);
superdat = superdat || content.Contains(" - SuperDAT");
if (keep && superdat)
{
Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? "SuperDAT" : DatHeader.Type);
}
break;
case "description":
content = reader.ReadElementContentAsString();
Description = (string.IsNullOrWhiteSpace(Description) ? content : Description);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? content : DatHeader.Description);
break;
case "rootdir":
content = reader.ReadElementContentAsString();
RootDir = (string.IsNullOrWhiteSpace(RootDir) ? content : RootDir);
DatHeader.RootDir = (string.IsNullOrWhiteSpace(DatHeader.RootDir) ? content : DatHeader.RootDir);
break;
case "category":
content = reader.ReadElementContentAsString();
Category = (string.IsNullOrWhiteSpace(Category) ? content : Category);
DatHeader.Category = (string.IsNullOrWhiteSpace(DatHeader.Category) ? content : DatHeader.Category);
break;
case "version":
content = reader.ReadElementContentAsString();
Version = (string.IsNullOrWhiteSpace(Version) ? content : Version);
DatHeader.Version = (string.IsNullOrWhiteSpace(DatHeader.Version) ? content : DatHeader.Version);
break;
case "date":
content = reader.ReadElementContentAsString();
Date = (string.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date);
DatHeader.Date = (string.IsNullOrWhiteSpace(DatHeader.Date) ? content.Replace(".", "/") : DatHeader.Date);
break;
case "author":
content = reader.ReadElementContentAsString();
Author = (string.IsNullOrWhiteSpace(Author) ? content : Author);
Email = (string.IsNullOrWhiteSpace(Email) ? reader.GetAttribute("email") : Email);
Homepage = (string.IsNullOrWhiteSpace(Homepage) ? reader.GetAttribute("homepage") : Homepage);
Url = (string.IsNullOrWhiteSpace(Url) ? reader.GetAttribute("url") : Url);
DatHeader.Author = (string.IsNullOrWhiteSpace(DatHeader.Author) ? content : DatHeader.Author);
DatHeader.Email = (string.IsNullOrWhiteSpace(DatHeader.Email) ? reader.GetAttribute("email") : DatHeader.Email);
DatHeader.Homepage = (string.IsNullOrWhiteSpace(DatHeader.Homepage) ? reader.GetAttribute("homepage") : DatHeader.Homepage);
DatHeader.Url = (string.IsNullOrWhiteSpace(DatHeader.Url) ? reader.GetAttribute("url") : DatHeader.Url);
break;
case "comment":
content = reader.ReadElementContentAsString();
Comment = (string.IsNullOrWhiteSpace(Comment) ? content : Comment);
DatHeader.Comment = (string.IsNullOrWhiteSpace(DatHeader.Comment) ? content : DatHeader.Comment);
break;
case "flags":
@@ -230,23 +222,17 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="reader">XmlReader to use to parse the header</param>
/// <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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private bool ReadDirectory(XmlReader reader,
List<string> parent,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Prepare all internal variables
XmlReader flagreader;
@@ -273,10 +259,10 @@ namespace SabreTools.Library.DatFiles
if (empty)
{
string tempgame = string.Join("\\", parent);
Rom rom = new Rom("null", tempgame, omitFromScan: Hash.DeepHashes); // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
Rom rom = new Rom("null", tempgame);
// Now process and add the rom
key = ParseAddHelper(rom, clean, remUnicode);
key = ParseAddHelper(rom);
}
// Regardless, end the current folder
@@ -292,7 +278,7 @@ namespace SabreTools.Library.DatFiles
{
parent.RemoveAt(parent.Count - 1);
if (keep && parentcount > 1)
Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? "SuperDAT" : DatHeader.Type);
}
}
@@ -310,7 +296,7 @@ namespace SabreTools.Library.DatFiles
// Directories can contain directories
case "dir":
case "directory":
ReadDirectory(reader.ReadSubtree(), parent, filename, sysid, srcid, keep, clean, remUnicode);
ReadDirectory(reader.ReadSubtree(), parent, filename, indexId, keep);
// Skip the directory node now that we've processed it
reader.Read();
@@ -345,7 +331,7 @@ namespace SabreTools.Library.DatFiles
if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null)
{
content = flagreader.GetAttribute("value");
its = Utilities.GetItemStatus(flagreader.GetAttribute("name"));
its = flagreader.GetAttribute("name").AsItemStatus();
}
break;
}
@@ -354,16 +340,17 @@ namespace SabreTools.Library.DatFiles
}
// If the rom has a Date attached, read it in and then sanitize it
date = Utilities.GetDate(reader.GetAttribute("date"));
date = Sanitizer.CleanDate(reader.GetAttribute("date"));
// Take care of hex-sized files
size = Utilities.GetSize(reader.GetAttribute("size"));
size = Sanitizer.CleanSize(reader.GetAttribute("size"));
Machine dir = new Machine();
// Get the name of the game from the parent
dir.Name = string.Join("\\", parent);
dir.Description = dir.Name;
Machine dir = new Machine
{
// Get the name of the game from the parent
Name = string.Join("\\", parent),
Description = string.Join("\\", parent),
};
DatItem datItem;
switch (reader.GetAttribute("type").ToLowerInvariant())
@@ -373,9 +360,8 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
break;
@@ -384,11 +370,10 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
Description = reader.GetAttribute("description"),
Default = Utilities.GetYesNo(reader.GetAttribute("default")),
Default = reader.GetAttribute("default").AsYesNo(),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
break;
@@ -396,17 +381,18 @@ namespace SabreTools.Library.DatFiles
datItem = new Disk
{
Name = reader.GetAttribute("name"),
MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length),
RIPEMD160 = Utilities.CleanHashData(reader.GetAttribute("ripemd160"), Constants.RIPEMD160Length),
SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length),
SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length),
SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length),
MD5 = reader.GetAttribute("md5"),
#if NET_FRAMEWORK
RIPEMD160 = reader.GetAttribute("ripemd160"),
#endif
SHA1 = reader.GetAttribute("sha1"),
SHA256 = reader.GetAttribute("sha256"),
SHA384 =reader.GetAttribute("sha384"),
SHA512 = reader.GetAttribute("sha512"),
ItemStatus = its,
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
break;
@@ -417,11 +403,10 @@ namespace SabreTools.Library.DatFiles
Region = reader.GetAttribute("region"),
Language = reader.GetAttribute("language"),
Date = reader.GetAttribute("date"),
Default = Utilities.GetYesNo(reader.GetAttribute("default")),
Default = reader.GetAttribute("default").AsYesNo(),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
break;
@@ -430,19 +415,20 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
Size = size,
CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength),
MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length),
RIPEMD160 = Utilities.CleanHashData(reader.GetAttribute("ripemd160"), Constants.RIPEMD160Length),
SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length),
SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length),
SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length),
CRC = reader.GetAttribute("crc"),
MD5 = reader.GetAttribute("md5"),
#if NET_FRAMEWORK
RIPEMD160 = reader.GetAttribute("ripemd160"),
#endif
SHA1 = reader.GetAttribute("sha1"),
SHA256 = reader.GetAttribute("sha256"),
SHA384 = reader.GetAttribute("sha384"),
SHA512 = reader.GetAttribute("sha512"),
ItemStatus = its,
Date = date,
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
break;
@@ -451,9 +437,8 @@ namespace SabreTools.Library.DatFiles
{
Name = reader.GetAttribute("name"),
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
break;
@@ -466,7 +451,7 @@ namespace SabreTools.Library.DatFiles
datItem?.CopyMachineInformation(dir);
// Now process and add the rom
key = ParseAddHelper(datItem, clean, remUnicode);
key = ParseAddHelper(datItem);
reader.Read();
break;
@@ -484,7 +469,7 @@ namespace SabreTools.Library.DatFiles
private void ReadFlags(XmlReader reader, bool superdat)
{
// Prepare all internal variables
string content = string.Empty;
string content;
// If we somehow have a null flag section, skip it
if (reader == null)
@@ -508,29 +493,26 @@ namespace SabreTools.Library.DatFiles
switch (reader.GetAttribute("name").ToLowerInvariant())
{
case "type":
Type = (string.IsNullOrWhiteSpace(Type) ? content : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? content : DatHeader.Type);
superdat = superdat || content.Contains("SuperDAT");
break;
case "forcemerging":
if (ForceMerging == ForceMerging.None)
{
ForceMerging = Utilities.GetForceMerging(content);
}
if (DatHeader.ForceMerging == ForceMerging.None)
DatHeader.ForceMerging = content.AsForceMerging();
break;
case "forcenodump":
if (ForceNodump == ForceNodump.None)
{
ForceNodump = Utilities.GetForceNodump(content);
}
if (DatHeader.ForceNodump == ForceNodump.None)
DatHeader.ForceNodump = content.AsForceNodump();
break;
case "forcepacking":
if (ForcePacking == ForcePacking.None)
{
ForcePacking = Utilities.GetForcePacking(content);
}
if (DatHeader.ForcePacking == ForcePacking.None)
DatHeader.ForcePacking = content.AsForcePacking();
break;
}
}
@@ -557,7 +539,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -566,10 +548,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false));
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false))
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(xtw);
@@ -609,7 +593,7 @@ namespace SabreTools.Library.DatFiles
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
depth = WriteStartGame(xtw, rom, newsplit, lastgame, depth, last);
depth = WriteStartGame(xtw, rom, newsplit, depth, last);
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.ItemType == ItemType.Rom
@@ -664,31 +648,34 @@ namespace SabreTools.Library.DatFiles
xtw.WriteStartElement("header");
xtw.WriteElementString("name", Name);
xtw.WriteElementString("description", Description);
if (!string.IsNullOrWhiteSpace(RootDir))
xtw.WriteElementString("rootdir", RootDir);
if (!string.IsNullOrWhiteSpace(Category))
xtw.WriteElementString("category", Category);
xtw.WriteElementString("version", Version);
if (!string.IsNullOrWhiteSpace(Date))
xtw.WriteElementString("date", Date);
xtw.WriteElementString("author", Author);
if (!string.IsNullOrWhiteSpace(Comment))
xtw.WriteElementString("comment", Comment);
if (!string.IsNullOrWhiteSpace(Type) || ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None)
xtw.WriteElementString("name", DatHeader.Name);
xtw.WriteElementString("description", DatHeader.Description);
if (!string.IsNullOrWhiteSpace(DatHeader.RootDir))
xtw.WriteElementString("rootdir", DatHeader.RootDir);
if (!string.IsNullOrWhiteSpace(DatHeader.Category))
xtw.WriteElementString("category", DatHeader.Category);
xtw.WriteElementString("version", DatHeader.Version);
if (!string.IsNullOrWhiteSpace(DatHeader.Date))
xtw.WriteElementString("date", DatHeader.Date);
xtw.WriteElementString("author", DatHeader.Author);
if (!string.IsNullOrWhiteSpace(DatHeader.Comment))
xtw.WriteElementString("comment", DatHeader.Comment);
if (!string.IsNullOrWhiteSpace(DatHeader.Type)
|| DatHeader.ForcePacking != ForcePacking.None
|| DatHeader.ForceMerging != ForceMerging.None
|| DatHeader.ForceNodump != ForceNodump.None)
{
xtw.WriteStartElement("flags");
if (!string.IsNullOrWhiteSpace(Type))
if (!string.IsNullOrWhiteSpace(DatHeader.Type))
{
xtw.WriteStartElement("flag");
xtw.WriteAttributeString("name", "type");
xtw.WriteAttributeString("value", Type);
xtw.WriteAttributeString("value", DatHeader.Type);
xtw.WriteEndElement();
}
switch (ForcePacking)
switch (DatHeader.ForcePacking)
{
case ForcePacking.Unzip:
xtw.WriteStartElement("flag");
@@ -703,8 +690,8 @@ namespace SabreTools.Library.DatFiles
xtw.WriteEndElement();
break;
}
switch (ForceMerging)
switch (DatHeader.ForceMerging)
{
case ForceMerging.Full:
xtw.WriteStartElement("flag");
@@ -731,8 +718,8 @@ namespace SabreTools.Library.DatFiles
xtw.WriteEndElement();
break;
}
switch (ForceNodump)
switch (DatHeader.ForceNodump)
{
case ForceNodump.Ignore:
xtw.WriteStartElement("flag");
@@ -780,11 +767,10 @@ namespace SabreTools.Library.DatFiles
/// <param name="xtw">XmlTextWriter to output to</param>
/// <param name="datItem">DatItem 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>
/// <returns>The new depth of the tag</returns>
private int WriteStartGame(XmlTextWriter xtw, DatItem datItem, List<string> newsplit, string lastgame, int depth, int last)
private int WriteStartGame(XmlTextWriter xtw, DatItem datItem, List<string> newsplit, int depth, int last)
{
try
{
@@ -795,8 +781,8 @@ namespace SabreTools.Library.DatFiles
for (int i = (last == -1 ? 0 : last); i < newsplit.Count; i++)
{
xtw.WriteStartElement("directory");
xtw.WriteAttributeString("name", !ExcludeFields[(int)Field.MachineName] ? newsplit[i] : string.Empty);
xtw.WriteAttributeString("description", !ExcludeFields[(int)Field.MachineName] ? newsplit[i] : string.Empty);
xtw.WriteAttributeString("name", !DatHeader.ExcludeFields[(int)Field.MachineName] ? newsplit[i] : string.Empty);
xtw.WriteAttributeString("description", !DatHeader.ExcludeFields[(int)Field.MachineName] ? newsplit[i] : string.Empty);
}
depth = depth - (last == -1 ? 0 : last) + newsplit.Count;
@@ -886,7 +872,7 @@ namespace SabreTools.Library.DatFiles
case ItemType.Archive:
xtw.WriteStartElement("file");
xtw.WriteAttributeString("type", "archive");
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, ExcludeFields));
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
xtw.WriteEndElement();
break;
@@ -894,10 +880,10 @@ namespace SabreTools.Library.DatFiles
var biosSet = datItem as BiosSet;
xtw.WriteStartElement("file");
xtw.WriteAttributeString("type", "biosset");
xtw.WriteAttributeString("name", biosSet.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.BiosDescription, ExcludeFields)))
xtw.WriteAttributeString("name", biosSet.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.BiosDescription, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("description", biosSet.Description);
if (!ExcludeFields[(int)Field.Default] && biosSet.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && biosSet.Default != null)
xtw.WriteAttributeString("default", biosSet.Default.ToString().ToLowerInvariant());
xtw.WriteEndElement();
break;
@@ -906,20 +892,22 @@ namespace SabreTools.Library.DatFiles
var disk = datItem as Disk;
xtw.WriteStartElement("file");
xtw.WriteAttributeString("type", "disk");
xtw.WriteAttributeString("name", disk.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
xtw.WriteAttributeString("name", disk.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("md5", disk.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("ripemd160", disk.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha1", disk.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha256", disk.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha384", disk.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha512", disk.SHA512.ToLowerInvariant());
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
{
xtw.WriteStartElement("flags");
@@ -938,14 +926,14 @@ namespace SabreTools.Library.DatFiles
var release = datItem as Release;
xtw.WriteStartElement("file");
xtw.WriteAttributeString("type", "release");
xtw.WriteAttributeString("name", release.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, ExcludeFields)))
xtw.WriteAttributeString("name", release.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Region, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("region", release.Region);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Language, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Language, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("language", release.Language);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("date", release.Date);
if (!ExcludeFields[(int)Field.Default] && release.Default != null)
if (!DatHeader.ExcludeFields[(int)Field.Default] && release.Default != null)
xtw.WriteAttributeString("default", release.Default.ToString().ToLowerInvariant());
xtw.WriteEndElement();
break;
@@ -954,26 +942,28 @@ namespace SabreTools.Library.DatFiles
var rom = datItem as Rom;
xtw.WriteStartElement("file");
xtw.WriteAttributeString("type", "rom");
xtw.WriteAttributeString("name", rom.GetField(Field.Name, ExcludeFields));
if (!ExcludeFields[(int)Field.Size] && rom.Size != -1)
xtw.WriteAttributeString("name", rom.GetField(Field.Name, DatHeader.ExcludeFields));
if (!DatHeader.ExcludeFields[(int)Field.Size] && rom.Size != -1)
xtw.WriteAttributeString("size", rom.Size.ToString());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("crc", rom.CRC.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("md5", rom.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("ripemd160", rom.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha1", rom.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha256", rom.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha384", rom.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha512", rom.SHA512.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Date, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("date", rom.Date);
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
{
xtw.WriteStartElement("flags");
@@ -991,7 +981,7 @@ namespace SabreTools.Library.DatFiles
case ItemType.Sample:
xtw.WriteStartElement("file");
xtw.WriteAttributeString("type", "sample");
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, ExcludeFields));
xtw.WriteAttributeString("name", datItem.GetField(Field.Name, DatHeader.ExcludeFields));
xtw.WriteEndElement();
break;
}

View File

@@ -5,10 +5,10 @@ using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
using SabreTools.Library.Readers;
using SabreTools.Library.Tools;
using SabreTools.Library.Writers;
using NaturalSort;
using SabreTools.Library.Readers;
namespace SabreTools.Library.DatFiles
{
@@ -26,7 +26,7 @@ namespace SabreTools.Library.DatFiles
/// <param name="datFile">Parent DatFile to copy from</param>
/// <param name="delim">Delimiter for parsing individual lines</param>
public SeparatedValue(DatFile datFile, char delim)
: base(datFile, cloneHeader: false)
: base(datFile)
{
_delim = delim;
}
@@ -35,29 +35,25 @@ namespace SabreTools.Library.DatFiles
/// Parse a character-separated value 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Open a file reader
Encoding enc = Utilities.GetEncoding(filename);
SeparatedValueReader svr = new SeparatedValueReader(Utilities.TryOpenRead(filename), enc);
svr.Header = true;
svr.Quotes = true;
svr.Separator = _delim;
svr.VerifyFieldCount = true;
Encoding enc = FileExtensions.GetEncoding(filename);
SeparatedValueReader svr = new SeparatedValueReader(FileExtensions.TryOpenRead(filename), enc)
{
Header = true,
Quotes = true,
Separator = _delim,
VerifyFieldCount = true
};
// If we're somehow at the end of the stream already, we can't do anything
if (svr.EndOfStream)
@@ -93,7 +89,9 @@ namespace SabreTools.Library.DatFiles
biosDescription = null,
crc = null,
md5 = null,
#if NET_FRAMEWORK
ripemd160 = null,
#endif
sha1 = null,
sha256 = null,
sha384 = null,
@@ -120,71 +118,71 @@ namespace SabreTools.Library.DatFiles
#region DatFile
case "DatFile.FileName":
FileName = (string.IsNullOrWhiteSpace(FileName) ? value : FileName);
DatHeader.FileName = (string.IsNullOrWhiteSpace(DatHeader.FileName) ? value : DatHeader.FileName);
break;
case "DatFile.Name":
Name = (string.IsNullOrWhiteSpace(Name) ? value : Name);
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? value : DatHeader.Name);
break;
case "DatFile.Description":
Description = (string.IsNullOrWhiteSpace(Description) ? value : Description);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? value : DatHeader.Description);
break;
case "DatFile.RootDir":
RootDir = (string.IsNullOrWhiteSpace(RootDir) ? value : RootDir);
DatHeader.RootDir = (string.IsNullOrWhiteSpace(DatHeader.RootDir) ? value : DatHeader.RootDir);
break;
case "DatFile.Category":
Category = (string.IsNullOrWhiteSpace(Category) ? value : Category);
DatHeader.Category = (string.IsNullOrWhiteSpace(DatHeader.Category) ? value : DatHeader.Category);
break;
case "DatFile.Version":
Version = (string.IsNullOrWhiteSpace(Version) ? value : Version);
DatHeader.Version = (string.IsNullOrWhiteSpace(DatHeader.Version) ? value : DatHeader.Version);
break;
case "DatFile.Date":
Date = (string.IsNullOrWhiteSpace(Date) ? value : Date);
DatHeader.Date = (string.IsNullOrWhiteSpace(DatHeader.Date) ? value : DatHeader.Date);
break;
case "DatFile.Author":
Author = (string.IsNullOrWhiteSpace(Author) ? value : Author);
DatHeader.Author = (string.IsNullOrWhiteSpace(DatHeader.Author) ? value : DatHeader.Author);
break;
case "DatFile.Email":
Email = (string.IsNullOrWhiteSpace(Email) ? value : Email);
DatHeader.Email = (string.IsNullOrWhiteSpace(DatHeader.Email) ? value : DatHeader.Email);
break;
case "DatFile.Homepage":
Homepage = (string.IsNullOrWhiteSpace(Homepage) ? value : Homepage);
DatHeader.Homepage = (string.IsNullOrWhiteSpace(DatHeader.Homepage) ? value : DatHeader.Homepage);
break;
case "DatFile.Url":
Url = (string.IsNullOrWhiteSpace(Url) ? value : Url);
DatHeader.Url = (string.IsNullOrWhiteSpace(DatHeader.Url) ? value : DatHeader.Url);
break;
case "DatFile.Comment":
Comment = (string.IsNullOrWhiteSpace(Comment) ? value : Comment);
DatHeader.Comment = (string.IsNullOrWhiteSpace(DatHeader.Comment) ? value : DatHeader.Comment);
break;
case "DatFile.Header":
Header = (string.IsNullOrWhiteSpace(Header) ? value : Header);
DatHeader.Header = (string.IsNullOrWhiteSpace(DatHeader.Header) ? value : DatHeader.Header);
break;
case "DatFile.Type":
Type = (string.IsNullOrWhiteSpace(Type) ? value : Type);
DatHeader.Type = (string.IsNullOrWhiteSpace(DatHeader.Type) ? value : DatHeader.Type);
break;
case "DatFile.ForceMerging":
ForceMerging = (ForceMerging == ForceMerging.None ? Utilities.GetForceMerging(value) : ForceMerging);
DatHeader.ForceMerging = (DatHeader.ForceMerging == ForceMerging.None ? value.AsForceMerging() : DatHeader.ForceMerging);
break;
case "DatFile.ForceNodump":
ForceNodump = (ForceNodump == ForceNodump.None ? Utilities.GetForceNodump(value) : ForceNodump);
DatHeader.ForceNodump = (DatHeader.ForceNodump == ForceNodump.None ? value.AsForceNodump() : DatHeader.ForceNodump);
break;
case "DatFile.ForcePacking":
ForcePacking = (ForcePacking == ForcePacking.None ? Utilities.GetForcePacking(value) : ForcePacking);
DatHeader.ForcePacking = (DatHeader.ForcePacking == ForcePacking.None ? value.AsForcePacking() : DatHeader.ForcePacking);
break;
#endregion
@@ -228,20 +226,7 @@ namespace SabreTools.Library.DatFiles
break;
case "Machine.Supported":
switch (value.ToLowerInvariant())
{
case "yes":
machine.Supported = true;
break;
case "no":
machine.Supported = false;
break;
case "partial":
default:
machine.Supported = null;
break;
}
machine.Supported = value.AsYesNo();
break;
case "Machine.SourceFile":
@@ -249,19 +234,7 @@ namespace SabreTools.Library.DatFiles
break;
case "Machine.Runnable":
switch (value.ToLowerInvariant())
{
case "yes":
machine.Runnable = true;
break;
case "no":
machine.Runnable = false;
break;
default:
machine.Runnable = null;
break;
}
machine.Runnable = value.AsYesNo();
break;
case "Machine.Board":
@@ -304,7 +277,7 @@ namespace SabreTools.Library.DatFiles
break;
case "Machine.MachineType":
machine.MachineType = Utilities.GetMachineType(value);
machine.MachineType = value.AsMachineType();
break;
#endregion
@@ -312,7 +285,7 @@ namespace SabreTools.Library.DatFiles
#region DatItem
case "DatItem.Type":
itemType = Utilities.GetItemType(value) ?? ItemType.Rom;
itemType = value.AsItemType() ?? ItemType.Rom;
break;
case "DatItem.Name":
@@ -351,19 +324,7 @@ namespace SabreTools.Library.DatFiles
break;
case "DatItem.Default":
switch (value.ToLowerInvariant())
{
case "yes":
def = true;
break;
case "no":
def = false;
break;
default:
def = null;
break;
}
def = value.AsYesNo();
break;
case "DatItem.Description":
@@ -377,31 +338,33 @@ namespace SabreTools.Library.DatFiles
break;
case "DatItem.CRC":
crc = Utilities.CleanHashData(value, Constants.CRCLength);
crc = value;
break;
case "DatItem.MD5":
md5 = Utilities.CleanHashData(value, Constants.MD5Length);
md5 = value;
break;
#if NET_FRAMEWORK
case "DatItem.RIPEMD160":
ripemd160 = Utilities.CleanHashData(value, Constants.RIPEMD160Length);
ripemd160 = value;
break;
#endif
case "DatItem.SHA1":
sha1 = Utilities.CleanHashData(value, Constants.SHA1Length);
sha1 = value;
break;
case "DatItem.SHA256":
sha256 = Utilities.CleanHashData(value, Constants.SHA256Length);
sha256 = value;
break;
case "DatItem.SHA384":
sha384 = Utilities.CleanHashData(value, Constants.SHA384Length);
sha384 = value;
break;
case "DatItem.SHA512":
sha512 = Utilities.CleanHashData(value, Constants.SHA512Length);
sha512 = value;
break;
case "DatItem.Merge":
@@ -417,39 +380,15 @@ namespace SabreTools.Library.DatFiles
break;
case "DatItem.Writable":
switch (value.ToLowerInvariant())
{
case "yes":
writable = true;
break;
case "no":
writable = false;
break;
default:
writable = null;
break;
}
writable = value.AsYesNo();
break;
case "DatItem.Optional":
switch (value.ToLowerInvariant())
{
case "yes":
optional = true;
break;
case "no":
optional = false;
break;
default:
optional = null;
break;
}
optional = value.AsYesNo();
break;
case "DatItem.Status":
status = Utilities.GetItemStatus(value);
status = value.AsItemStatus();
break;
case "DatItem.Language":
@@ -489,10 +428,13 @@ namespace SabreTools.Library.DatFiles
Features = features,
AreaName = areaName,
AreaSize = areaSize,
IndexId = indexId,
IndexSource = filename,
};
archive.CopyMachineInformation(machine);
ParseAddHelper(archive, clean, remUnicode);
ParseAddHelper(archive);
break;
case ItemType.BiosSet:
@@ -507,10 +449,13 @@ namespace SabreTools.Library.DatFiles
Description = biosDescription,
Default = def,
IndexId = indexId,
IndexSource = filename,
};
biosset.CopyMachineInformation(machine);
ParseAddHelper(biosset, clean, remUnicode);
ParseAddHelper(biosset);
break;
case ItemType.Disk:
@@ -524,7 +469,9 @@ namespace SabreTools.Library.DatFiles
AreaSize = areaSize,
MD5 = md5,
#if NET_FRAMEWORK
RIPEMD160 = ripemd160,
#endif
SHA1 = sha1,
SHA256 = sha256,
SHA384 = sha384,
@@ -535,10 +482,13 @@ namespace SabreTools.Library.DatFiles
Writable = writable,
ItemStatus = status,
Optional = optional,
IndexId = indexId,
IndexSource = filename,
};
disk.CopyMachineInformation(machine);
ParseAddHelper(disk, clean, remUnicode);
ParseAddHelper(disk);
break;
case ItemType.Release:
@@ -555,10 +505,13 @@ namespace SabreTools.Library.DatFiles
Language = language,
Date = date,
Default = default,
IndexId = indexId,
IndexSource = filename,
};
release.CopyMachineInformation(machine);
ParseAddHelper(release, clean, remUnicode);
ParseAddHelper(release);
break;
case ItemType.Rom:
@@ -575,7 +528,9 @@ namespace SabreTools.Library.DatFiles
Size = size,
CRC = crc,
MD5 = md5,
#if NET_FRAMEWORK
RIPEMD160 = ripemd160,
#endif
SHA1 = sha1,
SHA256 = sha256,
SHA384 = sha384,
@@ -586,10 +541,13 @@ namespace SabreTools.Library.DatFiles
Date = date,
ItemStatus = status,
Optional = optional,
IndexId = indexId,
IndexSource = filename,
};
rom.CopyMachineInformation(machine);
ParseAddHelper(rom, clean, remUnicode);
ParseAddHelper(rom);
break;
case ItemType.Sample:
@@ -601,10 +559,13 @@ namespace SabreTools.Library.DatFiles
Features = features,
AreaName = areaName,
AreaSize = areaSize,
IndexId = indexId,
IndexSource = filename,
};
sample.CopyMachineInformation(machine);
ParseAddHelper(sample, clean, remUnicode);
ParseAddHelper(sample);
break;
}
}
@@ -898,7 +859,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -907,10 +868,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false));
svw.Quotes = true;
svw.Separator = this._delim;
svw.VerifyFieldCount = true;
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false))
{
Quotes = true,
Separator = this._delim,
VerifyFieldCount = true
};
// Write out the header
WriteHeader(svw);
@@ -1028,11 +991,11 @@ namespace SabreTools.Library.DatFiles
// Build the state based on excluded fields
// TODO: Can we have some way of saying what fields to write out? Support for read extends to all fields now
string[] fields = new string[14]; // 17;
fields[0] = FileName;
fields[1] = Name;
fields[2] = Description;
fields[3] = datItem.GetField(Field.MachineName, ExcludeFields);
fields[4] = datItem.GetField(Field.Description, ExcludeFields);
fields[0] = DatHeader.FileName;
fields[1] = DatHeader.Name;
fields[2] = DatHeader.Description;
fields[3] = datItem.GetField(Field.MachineName, DatHeader.ExcludeFields);
fields[4] = datItem.GetField(Field.Description, DatHeader.ExcludeFields);
switch (datItem.ItemType)
{
@@ -1040,32 +1003,32 @@ namespace SabreTools.Library.DatFiles
var disk = datItem as Disk;
fields[5] = "disk";
fields[6] = string.Empty;
fields[7] = disk.GetField(Field.Name, ExcludeFields);
fields[7] = disk.GetField(Field.Name, DatHeader.ExcludeFields);
fields[8] = string.Empty;
fields[9] = string.Empty;
fields[10] = disk.GetField(Field.MD5, ExcludeFields).ToLowerInvariant();
//fields[11] = disk.GetField(Field.RIPEMD160, ExcludeFields).ToLowerInvariant();
fields[11] = disk.GetField(Field.SHA1, ExcludeFields).ToLowerInvariant();
fields[12] = disk.GetField(Field.SHA256, ExcludeFields).ToLowerInvariant();
//fields[13] = disk.GetField(Field.SHA384, ExcludeFields).ToLowerInvariant();
//fields[14] = disk.GetField(Field.SHA512, ExcludeFields).ToLowerInvariant();
fields[13] = disk.GetField(Field.Status, ExcludeFields);
fields[10] = disk.GetField(Field.MD5, DatHeader.ExcludeFields).ToLowerInvariant();
//fields[11] = disk.GetField(Field.RIPEMD160, DatHeader.ExcludeFields).ToLowerInvariant();
fields[11] = disk.GetField(Field.SHA1, DatHeader.ExcludeFields).ToLowerInvariant();
fields[12] = disk.GetField(Field.SHA256, DatHeader.ExcludeFields).ToLowerInvariant();
//fields[13] = disk.GetField(Field.SHA384, DatHeader.ExcludeFields).ToLowerInvariant();
//fields[14] = disk.GetField(Field.SHA512, DatHeader.ExcludeFields).ToLowerInvariant();
fields[13] = disk.GetField(Field.Status, DatHeader.ExcludeFields);
break;
case ItemType.Rom:
var rom = datItem as Rom;
fields[5] = "rom";
fields[6] = rom.GetField(Field.Name, ExcludeFields);
fields[6] = rom.GetField(Field.Name, DatHeader.ExcludeFields);
fields[7] = string.Empty;
fields[8] = rom.GetField(Field.Size, ExcludeFields);
fields[9] = rom.GetField(Field.CRC, ExcludeFields).ToLowerInvariant();
fields[10] = rom.GetField(Field.MD5, ExcludeFields).ToLowerInvariant();
//fields[11] = rom.GetField(Field.RIPEMD160, ExcludeFields).ToLowerInvariant();
fields[11] = rom.GetField(Field.SHA1, ExcludeFields).ToLowerInvariant();
fields[12] = rom.GetField(Field.SHA256, ExcludeFields).ToLowerInvariant();
//fields[13] = rom.GetField(Field.SHA384, ExcludeFields).ToLowerInvariant();
//fields[14] = rom.GetField(Field.SHA512, ExcludeFields).ToLowerInvariant();
fields[13] = rom.GetField(Field.Status, ExcludeFields);
fields[8] = rom.GetField(Field.Size, DatHeader.ExcludeFields);
fields[9] = rom.GetField(Field.CRC, DatHeader.ExcludeFields).ToLowerInvariant();
fields[10] = rom.GetField(Field.MD5, DatHeader.ExcludeFields).ToLowerInvariant();
//fields[11] = rom.GetField(Field.RIPEMD160, DatHeader.ExcludeFields).ToLowerInvariant();
fields[11] = rom.GetField(Field.SHA1, DatHeader.ExcludeFields).ToLowerInvariant();
fields[12] = rom.GetField(Field.SHA256, DatHeader.ExcludeFields).ToLowerInvariant();
//fields[13] = rom.GetField(Field.SHA384, DatHeader.ExcludeFields).ToLowerInvariant();
//fields[14] = rom.GetField(Field.SHA512, DatHeader.ExcludeFields).ToLowerInvariant();
fields[13] = rom.GetField(Field.Status, DatHeader.ExcludeFields);
break;
}

View File

@@ -14,7 +14,6 @@ namespace SabreTools.Library.DatFiles
/// <summary>
/// Represents parsing and writing of a SofwareList, M1, or MAME XML DAT
/// </summary>
/// TODO: Verify that all write for this DatFile type is correct
internal class SoftwareList : DatFile
{
/// <summary>
@@ -22,7 +21,7 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SoftwareList(DatFile datFile)
: base(datFile, cloneHeader: false)
: base(datFile)
{
}
@@ -30,25 +29,18 @@ namespace SabreTools.Library.DatFiles
/// Parse an SofwareList XML 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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
public override void ParseFile(
protected override void ParseFile(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// Prepare all internal variables
Encoding enc = Utilities.GetEncoding(filename);
XmlReader xtr = Utilities.GetXmlTextReader(filename);
XmlReader xtr = filename.GetXmlTextReader();
// If we got a null reader, just return
if (xtr == null)
@@ -70,23 +62,23 @@ namespace SabreTools.Library.DatFiles
switch (xtr.Name)
{
case "softwarelist":
Name = (string.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("name") ?? string.Empty : Name);
Description = (string.IsNullOrWhiteSpace(Description) ? xtr.GetAttribute("description") ?? string.Empty : Description);
if (ForceMerging == ForceMerging.None)
ForceMerging = Utilities.GetForceMerging(xtr.GetAttribute("forcemerging"));
DatHeader.Name = (string.IsNullOrWhiteSpace(DatHeader.Name) ? xtr.GetAttribute("name") ?? string.Empty : DatHeader.Name);
DatHeader.Description = (string.IsNullOrWhiteSpace(DatHeader.Description) ? xtr.GetAttribute("description") ?? string.Empty : DatHeader.Description);
if (DatHeader.ForceMerging == ForceMerging.None)
DatHeader.ForceMerging = xtr.GetAttribute("forcemerging").AsForceMerging();
if (ForceNodump == ForceNodump.None)
ForceNodump = Utilities.GetForceNodump(xtr.GetAttribute("forcenodump"));
if (DatHeader.ForceNodump == ForceNodump.None)
DatHeader.ForceNodump = xtr.GetAttribute("forcenodump").AsForceNodump();
if (ForcePacking == ForcePacking.None)
ForcePacking = Utilities.GetForcePacking(xtr.GetAttribute("forcepacking"));
if (DatHeader.ForcePacking == ForcePacking.None)
DatHeader.ForcePacking = xtr.GetAttribute("forcepacking").AsForcePacking();
xtr.Read();
break;
// We want to process the entire subtree of the machine
case "software":
ReadSoftware(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode);
ReadSoftware(xtr.ReadSubtree(), filename, indexId, keep);
// Skip the software now that we've processed it
xtr.Skip();
@@ -114,23 +106,17 @@ namespace SabreTools.Library.DatFiles
/// </summary>
/// <param name="reader">XmlReader representing a software block</param>
/// <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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private void ReadSoftware(
XmlReader reader,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
// If we have an empty software, skip it
if (reader == null)
@@ -139,26 +125,24 @@ namespace SabreTools.Library.DatFiles
// Otherwise, add what is possible
reader.MoveToContent();
string key = string.Empty;
string temptype = reader.Name;
bool containsItems = false;
// Create a new machine
MachineType machineType = MachineType.NULL;
if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true)
if (reader.GetAttribute("isbios").AsYesNo() == true)
machineType |= MachineType.Bios;
if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true)
if (reader.GetAttribute("isdevice").AsYesNo() == true)
machineType |= MachineType.Device;
if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true)
if (reader.GetAttribute("ismechanical").AsYesNo() == true)
machineType |= MachineType.Mechanical;
Machine machine = new Machine
{
Name = reader.GetAttribute("name"),
Description = reader.GetAttribute("name"),
Supported = Utilities.GetYesNo(reader.GetAttribute("supported")), // (yes|partial|no) "yes"
Supported = reader.GetAttribute("supported").AsYesNo(), // (yes|partial|no) "yes"
CloneOf = reader.GetAttribute("cloneof") ?? string.Empty,
Infos = new List<KeyValuePair<string, string>>(),
@@ -202,7 +186,7 @@ namespace SabreTools.Library.DatFiles
break;
case "part": // Contains all rom and disk information
containsItems = ReadPart(reader.ReadSubtree(), machine, filename, sysid, srcid, keep, clean, remUnicode);
containsItems = ReadPart(reader.ReadSubtree(), machine, filename, indexId, keep);
// Skip the part now that we've processed it
reader.Skip();
@@ -219,14 +203,13 @@ namespace SabreTools.Library.DatFiles
{
Blank blank = new Blank()
{
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
blank.CopyMachineInformation(machine);
// Now process and add the rom
ParseAddHelper(blank, clean, remUnicode);
ParseAddHelper(blank);
}
}
@@ -236,27 +219,20 @@ namespace SabreTools.Library.DatFiles
/// <param name="reader">XmlReader representing a part block</param>
/// <param name="machine">Machine information to pass to contained items</param>
/// <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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private bool ReadPart(
XmlReader reader,
Machine machine,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
string key = string.Empty, areaname = string.Empty, partname = string.Empty, partinterface = string.Empty;
string temptype = reader.Name;
string key, areaname, partname = string.Empty, partinterface = string.Empty;
long? areasize = null;
var features = new List<KeyValuePair<string, string>>();
bool containsItems = false;
@@ -274,10 +250,7 @@ namespace SabreTools.Library.DatFiles
}
if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "dataarea" || reader.Name == "diskarea"))
{
areaname = string.Empty;
areasize = null;
}
reader.Read();
continue;
@@ -308,8 +281,8 @@ namespace SabreTools.Library.DatFiles
// string dataarea_width = reader.GetAttribute("width"); // (8|16|32|64) "8"
// string dataarea_endianness = reader.GetAttribute("endianness"); // endianness (big|little) "little"
containsItems = ReadDataArea(reader.ReadSubtree(), machine, features, areaname, areasize,
partname, partinterface, filename, sysid, srcid, keep, clean, remUnicode);
containsItems = ReadDataArea(reader.ReadSubtree(), machine, features, areaname, areasize,
partname, partinterface, filename, indexId, keep);
// Skip the dataarea now that we've processed it
reader.Skip();
@@ -319,7 +292,7 @@ namespace SabreTools.Library.DatFiles
areaname = reader.GetAttribute("name");
containsItems = ReadDiskArea(reader.ReadSubtree(), machine, features, areaname, areasize,
partname, partinterface, filename, sysid, srcid, keep, clean, remUnicode);
partname, partinterface, filename, indexId, keep);
// Skip the diskarea now that we've processed it
reader.Skip();
@@ -358,11 +331,8 @@ namespace SabreTools.Library.DatFiles
/// <param name="partname">Name of the containing part</param>
/// <param name="partinterface">Interface of the containing part</param>
/// <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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private bool ReadDataArea(
XmlReader reader,
Machine machine,
@@ -374,13 +344,10 @@ namespace SabreTools.Library.DatFiles
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
string key = string.Empty;
string temptype = reader.Name;
@@ -408,7 +375,7 @@ namespace SabreTools.Library.DatFiles
DatItem lastrom = this[key][index];
if (lastrom.ItemType == ItemType.Rom)
{
((Rom)lastrom).Size += Utilities.GetSize(reader.GetAttribute("size"));
((Rom)lastrom).Size += Sanitizer.CleanSize(reader.GetAttribute("size"));
}
this[key].RemoveAt(index);
this[key].Add(lastrom);
@@ -419,17 +386,19 @@ namespace SabreTools.Library.DatFiles
DatItem rom = new Rom
{
Name = reader.GetAttribute("name"),
Size = Utilities.GetSize(reader.GetAttribute("size")),
CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength),
MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length),
RIPEMD160 = Utilities.CleanHashData(reader.GetAttribute("ripemd160"), Constants.RIPEMD160Length),
SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length),
SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length),
SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length),
Size = Sanitizer.CleanSize(reader.GetAttribute("size")),
CRC = reader.GetAttribute("crc"),
MD5 = reader.GetAttribute("md5"),
#if NET_FRAMEWORK
RIPEMD160 = reader.GetAttribute("ripemd160"),
#endif
SHA1 = reader.GetAttribute("sha1"),
SHA256 = reader.GetAttribute("sha256"),
SHA384 = reader.GetAttribute("sha384"),
SHA512 = reader.GetAttribute("sha512"),
Offset = reader.GetAttribute("offset"),
// Value = reader.GetAttribute("value");
ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")),
ItemStatus = reader.GetAttribute("status").AsItemStatus(),
// LoadFlag = reader.GetAttribute("loadflag"), // (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain|ignore)
AreaName = areaname,
@@ -438,15 +407,14 @@ namespace SabreTools.Library.DatFiles
PartName = partname,
PartInterface = partinterface,
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
rom.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(rom, clean, remUnicode);
key = ParseAddHelper(rom);
reader.Read();
break;
@@ -471,11 +439,8 @@ namespace SabreTools.Library.DatFiles
/// <param name="partname">Name of the containing part</param>
/// <param name="partinterface">Interface of the containing part</param>
/// <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="indexId">Index ID for the DAT</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="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
private bool ReadDiskArea(
XmlReader reader,
Machine machine,
@@ -487,13 +452,10 @@ namespace SabreTools.Library.DatFiles
// Standard Dat parsing
string filename,
int sysid,
int srcid,
int indexId,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
bool keep)
{
string key = string.Empty;
string temptype = reader.Name;
@@ -517,14 +479,16 @@ namespace SabreTools.Library.DatFiles
DatItem disk = new Disk
{
Name = reader.GetAttribute("name"),
MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length),
RIPEMD160 = Utilities.CleanHashData(reader.GetAttribute("ripemd160"), Constants.RIPEMD160Length),
SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length),
SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length),
SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length),
SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length),
ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")),
Writable = Utilities.GetYesNo(reader.GetAttribute("writable")),
MD5 = reader.GetAttribute("md5"),
#if NET_FRAMEWORK
RIPEMD160 = reader.GetAttribute("ripemd160"),
#endif
SHA1 = reader.GetAttribute("sha1"),
SHA256 = reader.GetAttribute("sha256"),
SHA384 = reader.GetAttribute("sha384"),
SHA512 = reader.GetAttribute("sha512"),
ItemStatus = reader.GetAttribute("status").AsItemStatus(),
Writable = reader.GetAttribute("writable").AsYesNo(),
AreaName = areaname,
AreaSize = areasize,
@@ -532,15 +496,14 @@ namespace SabreTools.Library.DatFiles
PartName = partname,
PartInterface = partinterface,
SystemID = sysid,
System = filename,
SourceID = srcid,
IndexId = indexId,
IndexSource = filename,
};
disk.CopyMachineInformation(machine);
// Now process and add the rom
key = ParseAddHelper(disk, clean, remUnicode);
key = ParseAddHelper(disk);
reader.Read();
break;
@@ -565,7 +528,7 @@ namespace SabreTools.Library.DatFiles
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = Utilities.TryCreate(outfile);
FileStream fs = FileExtensions.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
@@ -574,10 +537,12 @@ namespace SabreTools.Library.DatFiles
return false;
}
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false));
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;
XmlTextWriter xtw = new XmlTextWriter(fs, new UTF8Encoding(false))
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(xtw);
@@ -663,10 +628,10 @@ namespace SabreTools.Library.DatFiles
xtw.WriteDocType("softwarelist", null, "softwarelist.dtd", null);
xtw.WriteStartElement("softwarelist");
xtw.WriteAttributeString("name", Name);
xtw.WriteAttributeString("description", Description);
xtw.WriteAttributeString("name", DatHeader.Name);
xtw.WriteAttributeString("description", DatHeader.Description);
switch (ForcePacking)
switch (DatHeader.ForcePacking)
{
case ForcePacking.Unzip:
xtw.WriteAttributeString("forcepacking", "unzip");
@@ -676,7 +641,7 @@ namespace SabreTools.Library.DatFiles
break;
}
switch (ForceMerging)
switch (DatHeader.ForceMerging)
{
case ForceMerging.Full:
xtw.WriteAttributeString("forcemerging", "full");
@@ -692,7 +657,7 @@ namespace SabreTools.Library.DatFiles
break;
}
switch (ForceNodump)
switch (DatHeader.ForceNodump)
{
case ForceNodump.Ignore:
xtw.WriteAttributeString("forcenodump", "ignore");
@@ -731,12 +696,12 @@ namespace SabreTools.Library.DatFiles
// Build the state based on excluded fields
xtw.WriteStartElement("software");
xtw.WriteAttributeString("name", datItem.GetField(Field.MachineName, ExcludeFields));
xtw.WriteAttributeString("name", datItem.GetField(Field.MachineName, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.CloneOf, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, DatHeader.ExcludeFields)) && !string.Equals(datItem.MachineName, datItem.CloneOf, StringComparison.OrdinalIgnoreCase))
xtw.WriteAttributeString("cloneof", datItem.CloneOf);
if (!ExcludeFields[(int)Field.Supported])
if (!DatHeader.ExcludeFields[(int)Field.Supported])
{
if (datItem.Supported == true)
xtw.WriteAttributeString("supported", "yes");
@@ -746,14 +711,14 @@ namespace SabreTools.Library.DatFiles
xtw.WriteAttributeString("supported", "partial");
}
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Description, DatHeader.ExcludeFields)))
xtw.WriteElementString("description", datItem.MachineDescription);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Year, DatHeader.ExcludeFields)))
xtw.WriteElementString("year", datItem.Year);
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Publisher, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Publisher, DatHeader.ExcludeFields)))
xtw.WriteElementString("publisher", datItem.Publisher);
if (!ExcludeFields[(int)Field.Infos] && datItem.Infos != null && datItem.Infos.Count > 0)
if (!DatHeader.ExcludeFields[(int)Field.Infos] && datItem.Infos != null && datItem.Infos.Count > 0)
{
foreach (KeyValuePair<string, string> kvp in datItem.Infos)
{
@@ -818,10 +783,10 @@ namespace SabreTools.Library.DatFiles
// Build the state based on excluded fields
xtw.WriteStartElement("part");
xtw.WriteAttributeString("name", datItem.GetField(Field.PartName, ExcludeFields));
xtw.WriteAttributeString("interface", datItem.GetField(Field.PartInterface, ExcludeFields));
xtw.WriteAttributeString("name", datItem.GetField(Field.PartName, DatHeader.ExcludeFields));
xtw.WriteAttributeString("interface", datItem.GetField(Field.PartInterface, DatHeader.ExcludeFields));
if (!ExcludeFields[(int)Field.Features] && datItem.Features != null && datItem.Features.Count > 0)
if (!DatHeader.ExcludeFields[(int)Field.Features] && datItem.Features != null && datItem.Features.Count > 0)
{
foreach (KeyValuePair<string, string> kvp in datItem.Features)
{
@@ -832,36 +797,38 @@ namespace SabreTools.Library.DatFiles
}
}
string areaName = datItem.GetField(Field.AreaName, ExcludeFields);
string areaName = datItem.GetField(Field.AreaName, DatHeader.ExcludeFields);
switch (datItem.ItemType)
{
case ItemType.Disk:
var disk = datItem as Disk;
if (!ExcludeFields[(int)Field.AreaName] && string.IsNullOrWhiteSpace(areaName))
if (!DatHeader.ExcludeFields[(int)Field.AreaName] && string.IsNullOrWhiteSpace(areaName))
areaName = "cdrom";
xtw.WriteStartElement("diskarea");
xtw.WriteAttributeString("name", areaName);
if (!ExcludeFields[(int)Field.AreaSize] && disk.AreaSize != null)
if (!DatHeader.ExcludeFields[(int)Field.AreaSize] && disk.AreaSize != null)
xtw.WriteAttributeString("size", disk.AreaSize.ToString());
xtw.WriteStartElement("disk");
xtw.WriteAttributeString("name", disk.GetField(Field.Name, ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
xtw.WriteAttributeString("name", disk.GetField(Field.Name, DatHeader.ExcludeFields));
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("md5", disk.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("ripemd160", disk.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha1", disk.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha256", disk.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha384", disk.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha512", disk.SHA512.ToLowerInvariant());
if (!ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && disk.ItemStatus != ItemStatus.None)
xtw.WriteAttributeString("status", disk.ItemStatus.ToString().ToLowerInvariant());
if (!ExcludeFields[(int)Field.Writable] && disk.Writable != null)
if (!DatHeader.ExcludeFields[(int)Field.Writable] && disk.Writable != null)
xtw.WriteAttributeString("writable", disk.Writable == true ? "yes" : "no");
xtw.WriteEndElement();
@@ -871,39 +838,41 @@ namespace SabreTools.Library.DatFiles
case ItemType.Rom:
var rom = datItem as Rom;
if (!ExcludeFields[(int)Field.AreaName] && string.IsNullOrWhiteSpace(areaName))
if (!DatHeader.ExcludeFields[(int)Field.AreaName] && string.IsNullOrWhiteSpace(areaName))
areaName = "rom";
xtw.WriteStartElement("dataarea");
xtw.WriteAttributeString("name", areaName);
if (!ExcludeFields[(int)Field.AreaSize] && rom.AreaSize != null)
if (!DatHeader.ExcludeFields[(int)Field.AreaSize] && rom.AreaSize != null)
xtw.WriteAttributeString("size", rom.AreaSize.ToString());
xtw.WriteStartElement("rom");
xtw.WriteAttributeString("name", rom.GetField(Field.Name, ExcludeFields));
if (!ExcludeFields[(int)Field.Size] && rom.Size != -1)
xtw.WriteAttributeString("name", rom.GetField(Field.Name, DatHeader.ExcludeFields));
if (!DatHeader.ExcludeFields[(int)Field.Size] && rom.Size != -1)
xtw.WriteAttributeString("size", rom.Size.ToString());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CRC, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("crc", rom.CRC.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.MD5, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("md5", rom.MD5.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, ExcludeFields)))
#if NET_FRAMEWORK
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.RIPEMD160, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("ripemd160", rom.RIPEMD160.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, ExcludeFields)))
#endif
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA1, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha1", rom.SHA1.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA256, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha256", rom.SHA256.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA384, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha384", rom.SHA384.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.SHA512, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("sha512", rom.SHA512.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Offset, ExcludeFields)))
if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Offset, DatHeader.ExcludeFields)))
xtw.WriteAttributeString("offset", rom.Offset);
//if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Value, ExcludeFields)))
//if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Value, DatHeader.ExcludeFields)))
// xtw.WriteAttributeString("value", rom.Value);
if (!ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
if (!DatHeader.ExcludeFields[(int)Field.Status] && rom.ItemStatus != ItemStatus.None)
xtw.WriteAttributeString("status", rom.ItemStatus.ToString().ToLowerInvariant());
//if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Loadflag, ExcludeFields)))
//if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.Loadflag, DatHeader.ExcludeFields)))
// xtw.WriteAttributeString("loadflag", rom.Loadflag);
xtw.WriteEndElement();

View File

@@ -54,10 +54,8 @@ namespace SabreTools.Library.DatItems
Devices = this.Devices,
MachineType = this.MachineType,
SystemID = this.SystemID,
System = this.System,
SourceID = this.SourceID,
Source = this.Source,
IndexId = this.IndexId,
IndexSource = this.IndexSource,
};
}

View File

@@ -71,10 +71,8 @@ namespace SabreTools.Library.DatItems
Devices = this.Devices,
MachineType = this.MachineType,
SystemID = this.SystemID,
System = this.System,
SourceID = this.SourceID,
Source = this.Source,
IndexId = this.IndexId,
IndexSource = this.IndexSource,
Description = this.Description,
Default = this.Default,

View File

@@ -54,10 +54,8 @@ namespace SabreTools.Library.DatItems
Devices = this.Devices,
MachineType = this.MachineType,
SystemID = this.SystemID,
System = this.System,
SourceID = this.SourceID,
Source = this.Source,
IndexId = this.IndexId,
IndexSource = this.IndexSource,
};
}

View File

@@ -2,9 +2,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using SabreTools.Library.Data;
using SabreTools.Library.DatFiles;
using SabreTools.Library.FileTypes;
using SabreTools.Library.Tools;
using NaturalSort;
using Newtonsoft.Json;
@@ -558,28 +559,16 @@ namespace SabreTools.Library.DatItems
#region Source metadata information
/// <summary>
/// Internal system ID for organization
/// Internal DatFile index for organization
/// </summary>
[JsonIgnore]
public int SystemID { get; set; }
public int IndexId { get; set; }
/// <summary>
/// Internal system name for organization
/// Internal DatFile name for organization
/// </summary>
[JsonIgnore]
public string System { get; set; }
/// <summary>
/// Internal source ID for organization
/// </summary>
[JsonIgnore]
public int SourceID { get; set; }
/// <summary>
/// Internal source name for organization
/// </summary>
[JsonIgnore]
public string Source { get; set; }
public string IndexSource { get; set; }
/// <summary>
/// Flag if item should be removed
@@ -698,12 +687,14 @@ namespace SabreTools.Library.DatItems
else if (ItemType == ItemType.Rom)
fieldValue = (this as Rom).MD5;
break;
#if NET_FRAMEWORK
case Field.RIPEMD160:
if (ItemType == ItemType.Disk)
fieldValue = (this as Disk).RIPEMD160;
else if (ItemType == ItemType.Rom)
fieldValue = (this as Rom).RIPEMD160;
break;
#endif
case Field.SHA1:
if (ItemType == ItemType.Disk)
fieldValue = (this as Disk).SHA1;
@@ -805,6 +796,90 @@ namespace SabreTools.Library.DatItems
#endregion
#region Constructors
/// <summary>
/// Create a specific type of DatItem to be used based on an ItemType
/// </summary>
/// <param name="itemType">Type of the DatItem to be created</param>
/// <returns>DatItem of the specific internal type that corresponds to the inputs</returns>
public static DatItem Create(ItemType itemType)
{
#if NET_FRAMEWORK
switch (itemType)
{
case ItemType.Archive:
return new Archive();
case ItemType.BiosSet:
return new BiosSet();
case ItemType.Blank:
return new Blank();
case ItemType.Disk:
return new Disk();
case ItemType.Release:
return new Release();
case ItemType.Rom:
return new Rom();
case ItemType.Sample:
return new Sample();
default:
return new Rom();
}
#else
return itemType switch
{
ItemType.Archive => new Archive(),
ItemType.BiosSet => new BiosSet(),
ItemType.Blank => new Blank(),
ItemType.Disk => new Disk(),
ItemType.Release => new Release(),
ItemType.Rom => new Rom(),
ItemType.Sample => new Sample(),
_ => new Rom(),
};
#endif
}
/// <summary>
/// Create a specific type of DatItem to be used based on a BaseFile
/// </summary>
/// <param name="baseFile">BaseFile containing information to be created</param>
/// <returns>DatItem of the specific internal type that corresponds to the inputs</returns>
public static DatItem Create(BaseFile baseFile)
{
switch (baseFile.Type)
{
case FileType.CHD:
return new Disk(baseFile);
case FileType.GZipArchive:
case FileType.LRZipArchive:
case FileType.LZ4Archive:
case FileType.None:
case FileType.RarArchive:
case FileType.SevenZipArchive:
case FileType.TapeArchive:
case FileType.XZArchive:
case FileType.ZipArchive:
case FileType.ZPAQArchive:
case FileType.ZstdArchive:
return new Rom(baseFile);
case FileType.Folder:
default:
return null;
}
}
#endregion
#region Cloning Methods
/// <summary>
@@ -837,21 +912,17 @@ namespace SabreTools.Library.DatItems
public int CompareTo(DatItem other)
{
int ret = 0;
try
{
if (this.Name == other.Name)
ret = (this.Equals(other) ? 0 : 1);
return this.Equals(other) ? 0 : 1;
ret = String.Compare(this.Name, other.Name);
return String.Compare(this.Name, other.Name);
}
catch
{
ret = 1;
return 1;
}
return ret;
}
/// <summary>
@@ -875,7 +946,7 @@ namespace SabreTools.Library.DatItems
return output;
// If the duplicate is external already or should be, set it
if ((lastItem.DupeType & DupeType.External) != 0 || lastItem.SystemID != this.SystemID || lastItem.SourceID != this.SourceID)
if (lastItem.DupeType.HasFlag(DupeType.External) || lastItem.IndexId != this.IndexId)
{
if (lastItem.MachineName == this.MachineName && lastItem.Name == this.Name)
output = DupeType.External | DupeType.All;
@@ -900,94 +971,96 @@ namespace SabreTools.Library.DatItems
#region Sorting and Merging
/// <summary>
/// Check if a DAT contains the given rom
/// Get the dictionary key that should be used for a given item and sorting type
/// </summary>
/// <param name="datdata">Dat to match against</param>
/// <param name="sorted">True if the DAT is already sorted accordingly, false otherwise (default)</param>
/// <returns>True if it contains the rom, false otherwise</returns>
public bool HasDuplicates(DatFile datdata, bool sorted = false)
/// <param name="sortedBy">BucketedBy enum representing what key to get</param>
/// <param name="lower">True if the key should be lowercased (default), 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>
/// <returns>String representing the key to be used for the DatItem</returns>
public string GetKey(BucketedBy sortedBy, bool lower = true, bool norename = true)
{
// Check for an empty rom list first
if (datdata.Count == 0)
return false;
// Set the output key as the default blank string
string key = string.Empty;
// We want to get the proper key for the DatItem
string key = SortAndGetKey(datdata, sorted);
// If the key doesn't exist, return the empty list
if (!datdata.Contains(key))
return false;
// Try to find duplicates
List<DatItem> roms = datdata[key];
return roms.Any(r => this.Equals(r));
}
/// <summary>
/// List all duplicates found in a DAT based on a rom
/// </summary>
/// <param name="datdata">Dat to match against</param>
/// <param name="remove">True to mark matched roms for removal from the input, false otherwise (default)</param>
/// <param name="sorted">True if the DAT is already sorted accordingly, false otherwise (default)</param>
/// <returns>List of matched DatItem objects</returns>
public List<DatItem> GetDuplicates(DatFile datdata, bool remove = false, bool sorted = false)
{
List<DatItem> output = new List<DatItem>();
// Check for an empty rom list first
if (datdata.Count == 0)
return output;
// We want to get the proper key for the DatItem
string key = SortAndGetKey(datdata, sorted);
// If the key doesn't exist, return the empty list
if (!datdata.Contains(key))
return output;
// Try to find duplicates
List<DatItem> roms = datdata[key];
List<DatItem> left = new List<DatItem>();
for (int i = 0; i < roms.Count; i++)
// Now determine what the key should be based on the sortedBy value
switch (sortedBy)
{
DatItem datItem = roms[i];
case BucketedBy.CRC:
key = (this.ItemType == ItemType.Rom ? ((Rom)this).CRC : Constants.CRCZero);
break;
if (this.Equals(datItem))
{
datItem.Remove = true;
output.Add(datItem);
}
else
{
left.Add(datItem);
}
case BucketedBy.Game:
key = (norename ? string.Empty
: this.IndexId.ToString().PadLeft(10, '0')
+ "-")
+ (string.IsNullOrWhiteSpace(this.MachineName)
? "Default"
: this.MachineName);
if (lower)
key = key.ToLowerInvariant();
if (key == null)
key = "null";
key = WebUtility.HtmlEncode(key);
break;
case BucketedBy.MD5:
key = (this.ItemType == ItemType.Rom
? ((Rom)this).MD5
: (this.ItemType == ItemType.Disk
? ((Disk)this).MD5
: Constants.MD5Zero));
break;
#if NET_FRAMEWORK
case BucketedBy.RIPEMD160:
key = (this.ItemType == ItemType.Rom
? ((Rom)this).RIPEMD160
: (this.ItemType == ItemType.Disk
? ((Disk)this).RIPEMD160
: Constants.RIPEMD160Zero));
break;
#endif
case BucketedBy.SHA1:
key = (this.ItemType == ItemType.Rom
? ((Rom)this).SHA1
: (this.ItemType == ItemType.Disk
? ((Disk)this).SHA1
: Constants.SHA1Zero));
break;
case BucketedBy.SHA256:
key = (this.ItemType == ItemType.Rom
? ((Rom)this).SHA256
: (this.ItemType == ItemType.Disk
? ((Disk)this).SHA256
: Constants.SHA256Zero));
break;
case BucketedBy.SHA384:
key = (this.ItemType == ItemType.Rom
? ((Rom)this).SHA384
: (this.ItemType == ItemType.Disk
? ((Disk)this).SHA384
: Constants.SHA384Zero));
break;
case BucketedBy.SHA512:
key = (this.ItemType == ItemType.Rom
? ((Rom)this).SHA512
: (this.ItemType == ItemType.Disk
? ((Disk)this).SHA512
: Constants.SHA512Zero));
break;
}
// If we're in removal mode, add back all roms with the proper flags
if (remove)
{
datdata.Remove(key);
datdata.AddRange(key, output);
datdata.AddRange(key, left);
}
// Double and triple check the key for corner cases
if (key == null)
key = string.Empty;
return output;
}
/// <summary>
/// Sort the input DAT and get the key to be used by the item
/// </summary>
/// <param name="datdata">Dat to match against</param>
/// <param name="sorted">True if the DAT is already sorted accordingly, false otherwise (default)</param>
/// <returns>Key to try to use</returns>
private string SortAndGetKey(DatFile datdata, bool sorted = false)
{
// If we're not already sorted, take care of it
if (!sorted)
datdata.BucketByBestAvailable();
// Now that we have the sorted type, we get the proper key
return Utilities.GetKeyFromDatItem(this, datdata.SortedBy);
return key;
}
#endregion
@@ -1069,9 +1142,11 @@ namespace SabreTools.Library.DatItems
((Rom)saveditem).MD5 = (string.IsNullOrWhiteSpace(((Rom)saveditem).MD5) && !string.IsNullOrWhiteSpace(((Rom)file).MD5)
? ((Rom)file).MD5
: ((Rom)saveditem).MD5);
#if NET_FRAMEWORK
((Rom)saveditem).RIPEMD160 = (string.IsNullOrWhiteSpace(((Rom)saveditem).RIPEMD160) && !string.IsNullOrWhiteSpace(((Rom)file).RIPEMD160)
? ((Rom)file).RIPEMD160
: ((Rom)saveditem).RIPEMD160);
#endif
((Rom)saveditem).SHA1 = (string.IsNullOrWhiteSpace(((Rom)saveditem).SHA1) && !string.IsNullOrWhiteSpace(((Rom)file).SHA1)
? ((Rom)file).SHA1
: ((Rom)saveditem).SHA1);
@@ -1090,9 +1165,11 @@ namespace SabreTools.Library.DatItems
((Disk)saveditem).MD5 = (string.IsNullOrWhiteSpace(((Disk)saveditem).MD5) && !string.IsNullOrWhiteSpace(((Disk)file).MD5)
? ((Disk)file).MD5
: ((Disk)saveditem).MD5);
#if NET_FRAMEWORK
((Disk)saveditem).RIPEMD160 = (string.IsNullOrWhiteSpace(((Disk)saveditem).RIPEMD160) && !string.IsNullOrWhiteSpace(((Disk)file).RIPEMD160)
? ((Disk)file).RIPEMD160
: ((Disk)saveditem).RIPEMD160);
#endif
((Disk)saveditem).SHA1 = (string.IsNullOrWhiteSpace(((Disk)saveditem).SHA1) && !string.IsNullOrWhiteSpace(((Disk)file).SHA1)
? ((Disk)file).SHA1
: ((Disk)saveditem).SHA1);
@@ -1110,19 +1187,10 @@ namespace SabreTools.Library.DatItems
saveditem.DupeType = dupetype;
// If the current system has a lower ID than the previous, set the system accordingly
if (file.SystemID < saveditem.SystemID)
if (file.IndexId < saveditem.IndexId)
{
saveditem.SystemID = file.SystemID;
saveditem.System = file.System;
saveditem.CopyMachineInformation(file);
saveditem.Name = file.Name;
}
// If the current source has a lower ID than the previous, set the source accordingly
if (file.SourceID < saveditem.SourceID)
{
saveditem.SourceID = file.SourceID;
saveditem.Source = file.Source;
saveditem.IndexId = file.IndexId;
saveditem.IndexSource = file.IndexSource;
saveditem.CopyMachineInformation(file);
saveditem.Name = file.Name;
}
@@ -1185,7 +1253,7 @@ namespace SabreTools.Library.DatItems
}
// If the current item exactly matches the last item, then we don't add it
if ((datItem.GetDuplicateStatus(lastItem) & DupeType.All) != 0)
if (datItem.GetDuplicateStatus(lastItem).HasFlag(DupeType.All))
{
Globals.Logger.Verbose($"Exact duplicate found for '{datItem.Name}'");
continue;
@@ -1199,7 +1267,11 @@ namespace SabreTools.Library.DatItems
if (datItem.ItemType == ItemType.Disk || datItem.ItemType == ItemType.Rom)
{
datItem.Name += GetDuplicateSuffix(datItem);
#if NET_FRAMEWORK
lastrenamed = lastrenamed ?? datItem.Name;
#else
lastrenamed ??= datItem.Name;
#endif
}
// If we have a conflict with the last renamed item, do the right thing
@@ -1281,47 +1353,42 @@ namespace SabreTools.Library.DatItems
try
{
NaturalComparer nc = new NaturalComparer();
if (x.SystemID == y.SystemID)
if (x.IndexId == y.IndexId)
{
if (x.SourceID == y.SourceID)
if (x.MachineName == y.MachineName)
{
if (x.MachineName == y.MachineName)
if ((x.ItemType == ItemType.Rom || x.ItemType == ItemType.Disk) && (y.ItemType == ItemType.Rom || y.ItemType == ItemType.Disk))
{
if ((x.ItemType == ItemType.Rom || x.ItemType == ItemType.Disk) && (y.ItemType == ItemType.Rom || y.ItemType == ItemType.Disk))
if (Path.GetDirectoryName(Sanitizer.RemovePathUnsafeCharacters(x.Name)) == Path.GetDirectoryName(Sanitizer.RemovePathUnsafeCharacters(y.Name)))
{
if (Path.GetDirectoryName(Utilities.RemovePathUnsafeCharacters(x.Name)) == Path.GetDirectoryName(Utilities.RemovePathUnsafeCharacters(y.Name)))
{
return nc.Compare(Path.GetFileName(Utilities.RemovePathUnsafeCharacters(x.Name)), Path.GetFileName(Utilities.RemovePathUnsafeCharacters(y.Name)));
}
return nc.Compare(Path.GetFileName(Sanitizer.RemovePathUnsafeCharacters(x.Name)), Path.GetFileName(Sanitizer.RemovePathUnsafeCharacters(y.Name)));
}
return nc.Compare(Path.GetDirectoryName(Utilities.RemovePathUnsafeCharacters(x.Name)), Path.GetDirectoryName(Utilities.RemovePathUnsafeCharacters(y.Name)));
}
else if ((x.ItemType == ItemType.Rom || x.ItemType == ItemType.Disk) && (y.ItemType != ItemType.Rom && y.ItemType != ItemType.Disk))
{
return -1;
}
else if ((x.ItemType != ItemType.Rom && x.ItemType != ItemType.Disk) && (y.ItemType == ItemType.Rom || y.ItemType == ItemType.Disk))
{
return 1;
}
else
{
if (Path.GetDirectoryName(x.Name) == Path.GetDirectoryName(y.Name))
{
return nc.Compare(Path.GetFileName(x.Name), Path.GetFileName(y.Name));
}
return nc.Compare(Path.GetDirectoryName(x.Name), Path.GetDirectoryName(y.Name));
}
return nc.Compare(Path.GetDirectoryName(Sanitizer.RemovePathUnsafeCharacters(x.Name)), Path.GetDirectoryName(Sanitizer.RemovePathUnsafeCharacters(y.Name)));
}
else if ((x.ItemType == ItemType.Rom || x.ItemType == ItemType.Disk) && (y.ItemType != ItemType.Rom && y.ItemType != ItemType.Disk))
{
return -1;
}
else if ((x.ItemType != ItemType.Rom && x.ItemType != ItemType.Disk) && (y.ItemType == ItemType.Rom || y.ItemType == ItemType.Disk))
{
return 1;
}
else
{
if (Path.GetDirectoryName(x.Name) == Path.GetDirectoryName(y.Name))
{
return nc.Compare(Path.GetFileName(x.Name), Path.GetFileName(y.Name));
}
return nc.Compare(x.MachineName, y.MachineName);
return nc.Compare(Path.GetDirectoryName(x.Name), Path.GetDirectoryName(y.Name));
}
}
return (norename ? nc.Compare(x.MachineName, y.MachineName) : x.SourceID - y.SourceID);
return nc.Compare(x.MachineName, y.MachineName);
}
return (norename ? nc.Compare(x.MachineName, y.MachineName) : x.SystemID - y.SystemID);
return (norename ? nc.Compare(x.MachineName, y.MachineName) : x.IndexId - y.IndexId);
}
catch (Exception)
{

View File

@@ -31,19 +31,21 @@ namespace SabreTools.Library.DatItems
[JsonProperty("md5")]
public string MD5
{
get { return _md5.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_md5); }
set { _md5 = Utilities.StringToByteArray(value); }
get { return _md5.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_md5); }
set { _md5 = Utilities.StringToByteArray(Sanitizer.CleanMD5(value)); }
}
#if NET_FRAMEWORK
/// <summary>
/// Data RIPEMD160 hash
/// </summary>
[JsonProperty("ripemd160")]
public string RIPEMD160
{
get { return _ripemd160.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_ripemd160); }
set { _ripemd160 = Utilities.StringToByteArray(value); }
get { return _ripemd160.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_ripemd160); }
set { _ripemd160 = Utilities.StringToByteArray(Sanitizer.CleanRIPEMD160(value)); }
}
#endif
/// <summary>
/// Data SHA-1 hash
@@ -51,8 +53,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("sha1")]
public string SHA1
{
get { return _sha1.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha1); }
set { _sha1 = Utilities.StringToByteArray(value); }
get { return _sha1.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_sha1); }
set { _sha1 = Utilities.StringToByteArray(Sanitizer.CleanSHA1(value)); }
}
/// <summary>
@@ -61,8 +63,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("sha256")]
public string SHA256
{
get { return _sha256.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha256); }
set { _sha256 = Utilities.StringToByteArray(value); }
get { return _sha256.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_sha256); }
set { _sha256 = Utilities.StringToByteArray(Sanitizer.CleanSHA256(value)); }
}
/// <summary>
@@ -71,8 +73,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("sha384")]
public string SHA384
{
get { return _sha384.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha384); }
set { _sha384 = Utilities.StringToByteArray(value); }
get { return _sha384.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_sha384); }
set { _sha384 = Utilities.StringToByteArray(Sanitizer.CleanSHA384(value)); }
}
/// <summary>
@@ -81,8 +83,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("sha512")]
public string SHA512
{
get { return _sha512.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha512); }
set { _sha512 = Utilities.StringToByteArray(value); }
get { return _sha512.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_sha512); }
set { _sha512 = Utilities.StringToByteArray(Sanitizer.CleanSHA512(value)); }
}
/// <summary>
@@ -144,7 +146,9 @@ namespace SabreTools.Library.DatItems
{
this.Name = baseFile.Filename;
_md5 = baseFile.MD5;
#if NET_FRAMEWORK
_ripemd160 = baseFile.RIPEMD160;
#endif
_sha1 = baseFile.SHA1;
_sha256 = baseFile.SHA256;
_sha384 = baseFile.SHA384;
@@ -191,10 +195,8 @@ namespace SabreTools.Library.DatItems
Devices = this.Devices,
MachineType = this.MachineType,
SystemID = this.SystemID,
System = this.System,
SourceID = this.SourceID,
Source = this.Source,
IndexId = this.IndexId,
IndexSource = this.IndexSource,
_md5 = this._md5,
_ripemd160 = this._ripemd160,
@@ -220,7 +222,9 @@ namespace SabreTools.Library.DatItems
CRC = null,
MD5 = this.MD5,
#if NET_FRAMEWORK
RIPEMD160 = this.RIPEMD160,
#endif
SHA1 = this.SHA1,
SHA256 = this.SHA256,
SHA384 = this.SHA384,
@@ -256,10 +260,8 @@ namespace SabreTools.Library.DatItems
AreaName = this.AreaName,
AreaSize = this.AreaSize,
SystemID = this.SystemID,
System = this.System,
SourceID = this.SourceID,
Source = this.Source,
IndexId = this.IndexId,
IndexSource = this.IndexSource,
Remove = this.Remove,
};
@@ -284,34 +286,34 @@ namespace SabreTools.Library.DatItems
// If all hashes are empty but they're both nodump and the names match, then they're dupes
if ((this.ItemStatus == ItemStatus.Nodump && newOther.ItemStatus == ItemStatus.Nodump)
&& (this.Name == newOther.Name)
&& (this._md5.IsNullOrWhiteSpace() && newOther._md5.IsNullOrWhiteSpace())
&& (this._ripemd160.IsNullOrWhiteSpace() && newOther._ripemd160.IsNullOrWhiteSpace())
&& (this._sha1.IsNullOrWhiteSpace() && newOther._sha1.IsNullOrWhiteSpace())
&& (this._sha256.IsNullOrWhiteSpace() && newOther._sha256.IsNullOrWhiteSpace())
&& (this._sha384.IsNullOrWhiteSpace() && newOther._sha384.IsNullOrWhiteSpace())
&& (this._sha512.IsNullOrWhiteSpace() && newOther._sha512.IsNullOrWhiteSpace()))
&& (this._md5.IsNullOrEmpty() && newOther._md5.IsNullOrEmpty())
&& (this._ripemd160.IsNullOrEmpty() && newOther._ripemd160.IsNullOrEmpty())
&& (this._sha1.IsNullOrEmpty() && newOther._sha1.IsNullOrEmpty())
&& (this._sha256.IsNullOrEmpty() && newOther._sha256.IsNullOrEmpty())
&& (this._sha384.IsNullOrEmpty() && newOther._sha384.IsNullOrEmpty())
&& (this._sha512.IsNullOrEmpty() && newOther._sha512.IsNullOrEmpty()))
{
dupefound = true;
}
// If we can determine that the disks have no non-empty hashes in common, we return false
else if ((this._md5.IsNullOrWhiteSpace() || newOther._md5.IsNullOrWhiteSpace())
&& (this._ripemd160.IsNullOrWhiteSpace() || newOther._ripemd160.IsNullOrWhiteSpace())
&& (this._sha1.IsNullOrWhiteSpace() || newOther._sha1.IsNullOrWhiteSpace())
&& (this._sha256.IsNullOrWhiteSpace() || newOther._sha256.IsNullOrWhiteSpace())
&& (this._sha384.IsNullOrWhiteSpace() || newOther._sha384.IsNullOrWhiteSpace())
&& (this._sha512.IsNullOrWhiteSpace() || newOther._sha512.IsNullOrWhiteSpace()))
else if ((this._md5.IsNullOrEmpty() || newOther._md5.IsNullOrEmpty())
&& (this._ripemd160.IsNullOrEmpty() || newOther._ripemd160.IsNullOrEmpty())
&& (this._sha1.IsNullOrEmpty() || newOther._sha1.IsNullOrEmpty())
&& (this._sha256.IsNullOrEmpty() || newOther._sha256.IsNullOrEmpty())
&& (this._sha384.IsNullOrEmpty() || newOther._sha384.IsNullOrEmpty())
&& (this._sha512.IsNullOrEmpty() || newOther._sha512.IsNullOrEmpty()))
{
dupefound = false;
}
// Otherwise if we get a partial match
else if (((this._md5.IsNullOrWhiteSpace() || newOther._md5.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._md5, newOther._md5))
&& ((this._ripemd160.IsNullOrWhiteSpace() || newOther._ripemd160.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._ripemd160, newOther._ripemd160))
&& ((this._sha1.IsNullOrWhiteSpace() || newOther._sha1.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha1, newOther._sha1))
&& ((this._sha256.IsNullOrWhiteSpace() || newOther._sha256.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha256, newOther._sha256))
&& ((this._sha384.IsNullOrWhiteSpace() || newOther._sha384.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha384, newOther._sha384))
&& ((this._sha512.IsNullOrWhiteSpace() || newOther._sha512.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha512, newOther._sha512)))
else if (((this._md5.IsNullOrEmpty() || newOther._md5.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._md5, newOther._md5))
&& ((this._ripemd160.IsNullOrEmpty() || newOther._ripemd160.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._ripemd160, newOther._ripemd160))
&& ((this._sha1.IsNullOrEmpty() || newOther._sha1.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha1, newOther._sha1))
&& ((this._sha256.IsNullOrEmpty() || newOther._sha256.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha256, newOther._sha256))
&& ((this._sha384.IsNullOrEmpty() || newOther._sha384.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha384, newOther._sha384))
&& ((this._sha512.IsNullOrEmpty() || newOther._sha512.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha512, newOther._sha512)))
{
dupefound = true;
}

View File

@@ -87,10 +87,8 @@ namespace SabreTools.Library.DatItems
Devices = this.Devices,
MachineType = this.MachineType,
SystemID = this.SystemID,
System = this.System,
SourceID = this.SourceID,
Source = this.Source,
IndexId = this.IndexId,
IndexSource = this.IndexSource,
Region = this.Region,
Language = this.Language,

View File

@@ -44,8 +44,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("crc")]
public string CRC
{
get { return _crc.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_crc); }
set { _crc = (value == "null" ? Constants.CRCZeroBytes : Utilities.StringToByteArray(value)); }
get { return _crc.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_crc); }
set { _crc = (value == "null" ? Constants.CRCZeroBytes : Utilities.StringToByteArray(Sanitizer.CleanCRC32(value))); }
}
/// <summary>
@@ -54,19 +54,21 @@ namespace SabreTools.Library.DatItems
[JsonProperty("md5")]
public string MD5
{
get { return _md5.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_md5); }
set { _md5 = Utilities.StringToByteArray(value); }
get { return _md5.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_md5); }
set { _md5 = Utilities.StringToByteArray(Sanitizer.CleanMD5(value)); }
}
#if NET_FRAMEWORK
/// <summary>
/// File RIPEMD160 hash
/// </summary>
[JsonProperty("ripemd160")]
public string RIPEMD160
{
get { return _ripemd160.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_ripemd160); }
set { _ripemd160 = Utilities.StringToByteArray(value); }
get { return _ripemd160.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_ripemd160); }
set { _ripemd160 = Utilities.StringToByteArray(Sanitizer.CleanRIPEMD160(value)); }
}
#endif
/// <summary>
/// File SHA-1 hash
@@ -74,8 +76,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("sha1")]
public string SHA1
{
get { return _sha1.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha1); }
set { _sha1 = Utilities.StringToByteArray(value); }
get { return _sha1.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_sha1); }
set { _sha1 = Utilities.StringToByteArray(Sanitizer.CleanSHA1(value)); }
}
/// <summary>
@@ -84,8 +86,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("sha256")]
public string SHA256
{
get { return _sha256.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha256); }
set { _sha256 = Utilities.StringToByteArray(value); }
get { return _sha256.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_sha256); }
set { _sha256 = Utilities.StringToByteArray(Sanitizer.CleanSHA256(value)); }
}
/// <summary>
@@ -94,8 +96,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("sha384")]
public string SHA384
{
get { return _sha384.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha384); }
set { _sha384 = Utilities.StringToByteArray(value); }
get { return _sha384.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_sha384); }
set { _sha384 = Utilities.StringToByteArray(Sanitizer.CleanSHA384(value)); }
}
/// <summary>
@@ -104,8 +106,8 @@ namespace SabreTools.Library.DatItems
[JsonProperty("sha512")]
public string SHA512
{
get { return _sha512.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha512); }
set { _sha512 = Utilities.StringToByteArray(value); }
get { return _sha512.IsNullOrEmpty() ? null : Utilities.ByteArrayToString(_sha512); }
set { _sha512 = Utilities.StringToByteArray(Sanitizer.CleanSHA512(value)); }
}
/// <summary>
@@ -166,34 +168,11 @@ namespace SabreTools.Library.DatItems
/// <param name="name"></param>
/// <param name="machineName"></param>
/// <param name="omitFromScan"></param>
/// <remarks>TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually</remarks>
public Rom(string name, string machineName, Hash omitFromScan = Hash.DeepHashes)
public Rom(string name, string machineName)
{
this.Name = name;
this.ItemType = ItemType.Rom;
this.Size = -1;
if ((omitFromScan & Hash.CRC) == 0)
_crc = null;
if ((omitFromScan & Hash.MD5) == 0)
_md5 = null;
if ((omitFromScan & Hash.RIPEMD160) == 0)
_ripemd160 = null;
if ((omitFromScan & Hash.SHA1) == 0)
_sha1 = null;
if ((omitFromScan & Hash.SHA256) == 0)
_sha256 = null;
if ((omitFromScan & Hash.SHA384) == 0)
_sha384 = null;
if ((omitFromScan & Hash.SHA512) == 0)
_sha512 = null;
this.ItemStatus = ItemStatus.None;
_machine = new Machine
@@ -213,7 +192,9 @@ namespace SabreTools.Library.DatItems
this.Size = baseFile.Size ?? -1;
_crc = baseFile.CRC;
_md5 = baseFile.MD5;
#if NET_FRAMEWORK
_ripemd160 = baseFile.RIPEMD160;
#endif
_sha1 = baseFile.SHA1;
_sha256 = baseFile.SHA256;
_sha384 = baseFile.SHA384;
@@ -261,10 +242,8 @@ namespace SabreTools.Library.DatItems
Devices = this.Devices,
MachineType = this.MachineType,
SystemID = this.SystemID,
System = this.System,
SourceID = this.SourceID,
Source = this.Source,
IndexId = this.IndexId,
IndexSource = this.IndexSource,
Size = this.Size,
_crc = this._crc,
@@ -297,51 +276,51 @@ namespace SabreTools.Library.DatItems
// If all hashes are empty but they're both nodump and the names match, then they're dupes
if ((this.ItemStatus == ItemStatus.Nodump && newOther.ItemStatus == ItemStatus.Nodump)
&& (this.Name == newOther.Name)
&& (this._crc.IsNullOrWhiteSpace() && newOther._crc.IsNullOrWhiteSpace())
&& (this._md5.IsNullOrWhiteSpace() && newOther._md5.IsNullOrWhiteSpace())
&& (this._ripemd160.IsNullOrWhiteSpace() && newOther._ripemd160.IsNullOrWhiteSpace())
&& (this._sha1.IsNullOrWhiteSpace() && newOther._sha1.IsNullOrWhiteSpace())
&& (this._sha256.IsNullOrWhiteSpace() && newOther._sha256.IsNullOrWhiteSpace())
&& (this._sha384.IsNullOrWhiteSpace() && newOther._sha384.IsNullOrWhiteSpace())
&& (this._sha512.IsNullOrWhiteSpace() && newOther._sha512.IsNullOrWhiteSpace()))
&& (this._crc.IsNullOrEmpty() && newOther._crc.IsNullOrEmpty())
&& (this._md5.IsNullOrEmpty() && newOther._md5.IsNullOrEmpty())
&& (this._ripemd160.IsNullOrEmpty() && newOther._ripemd160.IsNullOrEmpty())
&& (this._sha1.IsNullOrEmpty() && newOther._sha1.IsNullOrEmpty())
&& (this._sha256.IsNullOrEmpty() && newOther._sha256.IsNullOrEmpty())
&& (this._sha384.IsNullOrEmpty() && newOther._sha384.IsNullOrEmpty())
&& (this._sha512.IsNullOrEmpty() && newOther._sha512.IsNullOrEmpty()))
{
dupefound = true;
}
// If we can determine that the roms have no non-empty hashes in common, we return false
else if ((this._crc.IsNullOrWhiteSpace() || newOther._crc.IsNullOrWhiteSpace())
&& (this._md5.IsNullOrWhiteSpace() || newOther._md5.IsNullOrWhiteSpace())
&& (this._ripemd160.IsNullOrWhiteSpace() || newOther._ripemd160.IsNullOrWhiteSpace())
&& (this._sha1.IsNullOrWhiteSpace() || newOther._sha1.IsNullOrWhiteSpace())
&& (this._sha256.IsNullOrWhiteSpace() || newOther._sha256.IsNullOrWhiteSpace())
&& (this._sha384.IsNullOrWhiteSpace() || newOther._sha384.IsNullOrWhiteSpace())
&& (this._sha512.IsNullOrWhiteSpace() || newOther._sha512.IsNullOrWhiteSpace()))
else if ((this._crc.IsNullOrEmpty() || newOther._crc.IsNullOrEmpty())
&& (this._md5.IsNullOrEmpty() || newOther._md5.IsNullOrEmpty())
&& (this._ripemd160.IsNullOrEmpty() || newOther._ripemd160.IsNullOrEmpty())
&& (this._sha1.IsNullOrEmpty() || newOther._sha1.IsNullOrEmpty())
&& (this._sha256.IsNullOrEmpty() || newOther._sha256.IsNullOrEmpty())
&& (this._sha384.IsNullOrEmpty() || newOther._sha384.IsNullOrEmpty())
&& (this._sha512.IsNullOrEmpty() || newOther._sha512.IsNullOrEmpty()))
{
dupefound = false;
}
// If we have a file that has no known size, rely on the hashes only
else if ((this.Size == -1)
&& ((this._crc.IsNullOrWhiteSpace() || newOther._crc.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._crc, newOther._crc))
&& ((this._md5.IsNullOrWhiteSpace() || newOther._md5.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._md5, newOther._md5))
&& ((this._ripemd160.IsNullOrWhiteSpace() || newOther._ripemd160.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._ripemd160, newOther._ripemd160))
&& ((this._sha1.IsNullOrWhiteSpace() || newOther._sha1.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha1, newOther._sha1))
&& ((this._sha256.IsNullOrWhiteSpace() || newOther._sha256.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha256, newOther._sha256))
&& ((this._sha384.IsNullOrWhiteSpace() || newOther._sha384.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha384, newOther._sha384))
&& ((this._sha512.IsNullOrWhiteSpace() || newOther._sha512.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha512, newOther._sha512)))
&& ((this._crc.IsNullOrEmpty() || newOther._crc.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._crc, newOther._crc))
&& ((this._md5.IsNullOrEmpty() || newOther._md5.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._md5, newOther._md5))
&& ((this._ripemd160.IsNullOrEmpty() || newOther._ripemd160.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._ripemd160, newOther._ripemd160))
&& ((this._sha1.IsNullOrEmpty() || newOther._sha1.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha1, newOther._sha1))
&& ((this._sha256.IsNullOrEmpty() || newOther._sha256.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha256, newOther._sha256))
&& ((this._sha384.IsNullOrEmpty() || newOther._sha384.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha384, newOther._sha384))
&& ((this._sha512.IsNullOrEmpty() || newOther._sha512.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha512, newOther._sha512)))
{
dupefound = true;
}
// Otherwise if we get a partial match
else if ((this.Size == newOther.Size)
&& ((this._crc.IsNullOrWhiteSpace() || newOther._crc.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._crc, newOther._crc))
&& ((this._md5.IsNullOrWhiteSpace() || newOther._md5.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._md5, newOther._md5))
&& ((this._ripemd160.IsNullOrWhiteSpace() || newOther._ripemd160.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._ripemd160, newOther._ripemd160))
&& ((this._sha1.IsNullOrWhiteSpace() || newOther._sha1.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha1, newOther._sha1))
&& ((this._sha256.IsNullOrWhiteSpace() || newOther._sha256.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha256, newOther._sha256))
&& ((this._sha384.IsNullOrWhiteSpace() || newOther._sha384.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha384, newOther._sha384))
&& ((this._sha512.IsNullOrWhiteSpace() || newOther._sha512.IsNullOrWhiteSpace()) || Enumerable.SequenceEqual(this._sha512, newOther._sha512)))
&& ((this._crc.IsNullOrEmpty() || newOther._crc.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._crc, newOther._crc))
&& ((this._md5.IsNullOrEmpty() || newOther._md5.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._md5, newOther._md5))
&& ((this._ripemd160.IsNullOrEmpty() || newOther._ripemd160.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._ripemd160, newOther._ripemd160))
&& ((this._sha1.IsNullOrEmpty() || newOther._sha1.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha1, newOther._sha1))
&& ((this._sha256.IsNullOrEmpty() || newOther._sha256.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha256, newOther._sha256))
&& ((this._sha384.IsNullOrEmpty() || newOther._sha384.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha384, newOther._sha384))
&& ((this._sha512.IsNullOrEmpty() || newOther._sha512.IsNullOrEmpty()) || Enumerable.SequenceEqual(this._sha512, newOther._sha512)))
{
dupefound = true;
}

View File

@@ -54,10 +54,8 @@ namespace SabreTools.Library.DatItems
Devices = this.Devices,
MachineType = this.MachineType,
SystemID = this.SystemID,
System = this.System,
SourceID = this.SourceID,
Source = this.Source,
IndexId = this.IndexId,
IndexSource = this.IndexSource,
};
}

View File

@@ -12,27 +12,29 @@ namespace SabreTools.Library.Data
/// <summary>
/// The current toolset version to be used by all child applications
/// </summary>
public readonly static string Version = $"v1.0.0-{File.GetCreationTime(Assembly.GetExecutingAssembly().Location).ToString("yyyy-MM-dd HH:mm:ss")}";
public readonly static string Version = $"v1.0.0-{File.GetCreationTime(Assembly.GetExecutingAssembly().Location):yyyy-MM-dd HH:mm:ss}";
public const int HeaderHeight = 3;
#region 0-byte file constants
public const long SizeZero = 0;
public const string CRCZero = "00000000";
public static readonly byte[] CRCZeroBytes = { 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] CRCZeroBytes = { 0x00, 0x00, 0x00, 0x00 };
public const string MD5Zero = "d41d8cd98f00b204e9800998ecf8427e";
public static readonly byte[] MD5ZeroBytes = { 0xd4, 0x1d, 0x8c, 0xd9,
public static readonly byte[] MD5ZeroBytes = { 0xd4, 0x1d, 0x8c, 0xd9,
0x8f, 0x00, 0xb2, 0x04,
0xe9, 0x80, 0x09, 0x98,
0xec, 0xf8, 0x42, 0x7e };
#if NET_FRAMEWORK
public const string RIPEMD160Zero = "9c1185a5c5e9fc54612808977ee8f548b2258d31";
public static readonly byte[] RIPEMD160ZeroBytes = { 0x9c, 0x11, 0x85, 0xa5,
0xc5, 0xe9, 0xfc, 0x54,
0x61, 0x28, 0x08, 0x97,
0x7e, 0xe8, 0xf5, 0x48,
0xb2, 0x25, 0x8d, 0x31 };
#endif
public const string SHA1Zero = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
public static readonly byte[] SHA1ZeroBytes = { 0xda, 0x39, 0xa3, 0xee,
public static readonly byte[] SHA1ZeroBytes = { 0xda, 0x39, 0xa3, 0xee,
0x5e, 0x6b, 0x4b, 0x0d,
0x32, 0x55, 0xbf, 0xef,
0x95, 0x60, 0x18, 0x90,
@@ -105,54 +107,6 @@ namespace SabreTools.Library.Data
#endregion
#region CHD header values
// Header versions and sizes
public const int CHD_HEADER_VERSION = 5;
public const int CHD_V1_HEADER_SIZE = 76;
public const int CHD_V2_HEADER_SIZE = 80;
public const int CHD_V3_HEADER_SIZE = 120;
public const int CHD_V4_HEADER_SIZE = 108;
public const int CHD_V5_HEADER_SIZE = 124;
public const int CHD_MAX_HEADER_SIZE = CHD_V5_HEADER_SIZE;
// Key offsets within the header (V1)
public const long CHDv1MapOffsetOffset = 0;
public const long CHDv1MetaOffsetOffset = 0;
public const long CHDv1MD5Offset = 44;
public const long CHDv1RawMD5Offset = 0;
public const long CHDv1ParentMD5Offset = 60;
// Key offsets within the header (V2)
public const long CHDv2MapOffsetOffset = 0;
public const long CHDv2MetaOffsetOffset = 0;
public const long CHDv2MD5Offset = 44;
public const long CHDv2RawMD5Offset = 0;
public const long CHDv2ParentMD5Offset = 60;
// Key offsets within the header (V3)
public const long CHDv3MapOffsetOffset = 0; // offset of map offset field
public const long CHDv3MetaOffsetOffset = 36; // offset of metaoffset field
public const long CHDv3SHA1Offset = 80; // offset of SHA1 field
public const long CHDv3RawSHA1Offset = 0; // offset of raw SHA1 field
public const long CHDv3ParentSHA1Offset = 100; // offset of parent SHA1 field
// Key offsets within the header (V4)
public const long CHDv4MapOffsetOffset = 0; // offset of map offset field
public const long CHDv4MetaOffsetOffset = 36; // offset of metaoffset field
public const long CHDv4SHA1Offset = 48; // offset of SHA1 field
public const long CHDv4RawSHA1Offset = 88; // offset of raw SHA1 field
public const long CHDv4ParentSHA1Offset = 68; // offset of parent SHA1 field
// Key offsets within the header (V5)
public const long CHDv5MapOffsetOffset = 40; // offset of map offset field
public const long CHDv5MetaOffsetOffset = 48; // offset of metaoffset field
public const long CHDv5SHA1Offset = 84; // offset of SHA1 field
public const long CHDv5RawSHA1Offset = 64; // offset of raw SHA1 field
public const long CHDv5ParentSHA1Offset = 104; // offset of parent SHA1 field
#endregion
#region Database schema
public const string HeadererDbSchema = "Headerer";
@@ -507,7 +461,9 @@ namespace SabreTools.Library.Data
public const int CRCLength = 8;
public const int MD5Length = 32;
#if NET_FRAMEWORK
public const int RIPEMD160Length = 40;
#endif
public const int SHA1Length = 40;
public const int SHA256Length = 64;
public const int SHA384Length = 96;
@@ -517,48 +473,48 @@ namespace SabreTools.Library.Data
#region Magic numbers
public static readonly byte[] SevenZipSignature = { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c };
public static readonly byte[] A7800SignatureV1 = { 0x41, 0x54, 0x41, 0x52, 0x49, 0x37, 0x38, 0x30, 0x30 }; // Offset 0x01
public static readonly byte[] A7800SignatureV2 = { 0x41, 0x43, 0x54, 0x55, 0x41, 0x4c, 0x20, 0x43, 0x41, 0x52, 0x54, 0x20, 0x44, 0x41,
public static readonly byte[] SevenZipSignature = { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c };
public static readonly byte[] A7800SignatureV1 = { 0x41, 0x54, 0x41, 0x52, 0x49, 0x37, 0x38, 0x30, 0x30 }; // Offset 0x01
public static readonly byte[] A7800SignatureV2 = { 0x41, 0x43, 0x54, 0x55, 0x41, 0x4c, 0x20, 0x43, 0x41, 0x52, 0x54, 0x20, 0x44, 0x41,
0x54, 0x41, 0x20, 0x53, 0x54, 0x41, 0x52, 0x54, 0x53, 0x20, 0x48, 0x45, 0x52, 0x45 }; // Offset 0x64
public static readonly byte[] BZ2Signature = { 0x42, 0x5a, 0x68 };
public static readonly byte[] CabinetSignature = { 0x4d, 0x53, 0x43, 0x46 };
public static readonly byte[] CHDSignature = { 0x4d, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x48, 0x44 };
public static readonly byte[] ELFSignature = { 0x7f, 0x45, 0x4c, 0x46 };
public static readonly byte[] FDSSignatureV1 = { 0x46, 0x44, 0x53, 0x1a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] FDSSignatureV2 = { 0x46, 0x44, 0x53, 0x1a, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] FDSSignatureV3 = { 0x46, 0x44, 0x53, 0x1a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] FDSSignatureV4 = { 0x46, 0x44, 0x53, 0x1a, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] FreeArcSignature = { 0x41, 0x72, 0x43, 0x01 };
public static readonly byte[] GzSignature = { 0x1f, 0x8b, 0x08 };
public static readonly byte[] LRZipSignature = { 0x4c, 0x52, 0x5a, 0x49 };
public static readonly byte[] LynxSignatureV1 = { 0x4c, 0x59, 0x4f, 0x58 };
public static readonly byte[] LynxSignatureV2 = { 0x42, 0x53, 0x39 }; // Offset 0x06
public static readonly byte[] LZ4Signature = { 0x18, 0x4d, 0x22, 0x04 };
public static readonly byte[] LZ4SkippableMinSignature = { 0x18, 0x4d, 0x22, 0x04 };
public static readonly byte[] LZ4SkippableMaxSignature = { 0x18, 0x4d, 0x2a, 0x5f };
public static readonly byte[] N64Signature = { 0x40, 0x12, 0x37, 0x80 };
public static readonly byte[] NESSignature = { 0x4e, 0x45, 0x53, 0x1a };
public static readonly byte[] PCESignature = { 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xbb, 0x02 };
public static readonly byte[] PESignature = { 0x4d, 0x5a };
public static readonly byte[] PSIDSignatureV1 = { 0x50, 0x53, 0x49, 0x44, 0x00, 0x01, 0x00, 0x76 };
public static readonly byte[] PSIDSignatureV2 = { 0x50, 0x53, 0x49, 0x44, 0x00, 0x02, 0x00, 0x7c };
public static readonly byte[] PSIDSignatureV3 = { 0x50, 0x53, 0x49, 0x44, 0x00, 0x03, 0x00, 0x7c };
public static readonly byte[] RarSignature = { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 };
public static readonly byte[] RarFiveSignature = { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 };
public static readonly byte[] SMCSignature = { 0xaa, 0xbb, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Offset 0x16
public static readonly byte[] SPCSignature = { 0x53, 0x4e, 0x45, 0x53, 0x2d, 0x53, 0x50, 0x43 };
public static readonly byte[] TarSignature = { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 };
public static readonly byte[] TarZeroSignature = { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 };
public static readonly byte[] UFOSignature = { 0x53, 0x55, 0x50, 0x45, 0x52, 0x55, 0x46, 0x4f }; // Offset 0x16
public static readonly byte[] V64Signature = { 0x80, 0x37, 0x12, 0x40 };
public static readonly byte[] XZSignature = { 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00 };
public static readonly byte[] Z64Signature = { 0x37, 0x80, 0x40, 0x12 };
public static readonly byte[] ZipSignature = { 0x50, 0x4b, 0x03, 0x04 };
public static readonly byte[] ZipSignatureEmpty = { 0x50, 0x4b, 0x05, 0x06 };
public static readonly byte[] ZipSignatureSpanned = { 0x50, 0x4b, 0x07, 0x08 };
public static readonly byte[] ZPAQSignature = { 0x7a, 0x50, 0x51 };
public static readonly byte[] ZstdSignature = { 0xfd, 0x2f, 0xb5 };
public static readonly byte[] BZ2Signature = { 0x42, 0x5a, 0x68 };
public static readonly byte[] CabinetSignature = { 0x4d, 0x53, 0x43, 0x46 };
public static readonly byte[] CHDSignature = { 0x4d, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x48, 0x44 };
public static readonly byte[] ELFSignature = { 0x7f, 0x45, 0x4c, 0x46 };
public static readonly byte[] FDSSignatureV1 = { 0x46, 0x44, 0x53, 0x1a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] FDSSignatureV2 = { 0x46, 0x44, 0x53, 0x1a, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] FDSSignatureV3 = { 0x46, 0x44, 0x53, 0x1a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] FDSSignatureV4 = { 0x46, 0x44, 0x53, 0x1a, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] FreeArcSignature = { 0x41, 0x72, 0x43, 0x01 };
public static readonly byte[] GzSignature = { 0x1f, 0x8b, 0x08 };
public static readonly byte[] LRZipSignature = { 0x4c, 0x52, 0x5a, 0x49 };
public static readonly byte[] LynxSignatureV1 = { 0x4c, 0x59, 0x4f, 0x58 };
public static readonly byte[] LynxSignatureV2 = { 0x42, 0x53, 0x39 }; // Offset 0x06
public static readonly byte[] LZ4Signature = { 0x18, 0x4d, 0x22, 0x04 };
public static readonly byte[] LZ4SkippableMinSignature = { 0x18, 0x4d, 0x22, 0x04 };
public static readonly byte[] LZ4SkippableMaxSignature = { 0x18, 0x4d, 0x2a, 0x5f };
public static readonly byte[] N64Signature = { 0x40, 0x12, 0x37, 0x80 };
public static readonly byte[] NESSignature = { 0x4e, 0x45, 0x53, 0x1a };
public static readonly byte[] PCESignature = { 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xbb, 0x02 };
public static readonly byte[] PESignature = { 0x4d, 0x5a };
public static readonly byte[] PSIDSignatureV1 = { 0x50, 0x53, 0x49, 0x44, 0x00, 0x01, 0x00, 0x76 };
public static readonly byte[] PSIDSignatureV2 = { 0x50, 0x53, 0x49, 0x44, 0x00, 0x02, 0x00, 0x7c };
public static readonly byte[] PSIDSignatureV3 = { 0x50, 0x53, 0x49, 0x44, 0x00, 0x03, 0x00, 0x7c };
public static readonly byte[] RarSignature = { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 };
public static readonly byte[] RarFiveSignature = { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 };
public static readonly byte[] SMCSignature = { 0xaa, 0xbb, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Offset 0x16
public static readonly byte[] SPCSignature = { 0x53, 0x4e, 0x45, 0x53, 0x2d, 0x53, 0x50, 0x43 };
public static readonly byte[] TarSignature = { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 };
public static readonly byte[] TarZeroSignature = { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 };
public static readonly byte[] UFOSignature = { 0x53, 0x55, 0x50, 0x45, 0x52, 0x55, 0x46, 0x4f }; // Offset 0x16
public static readonly byte[] V64Signature = { 0x80, 0x37, 0x12, 0x40 };
public static readonly byte[] XZSignature = { 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00 };
public static readonly byte[] Z64Signature = { 0x37, 0x80, 0x40, 0x12 };
public static readonly byte[] ZipSignature = { 0x50, 0x4b, 0x03, 0x04 };
public static readonly byte[] ZipSignatureEmpty = { 0x50, 0x4b, 0x05, 0x06 };
public static readonly byte[] ZipSignatureSpanned = { 0x50, 0x4b, 0x07, 0x08 };
public static readonly byte[] ZPAQSignature = { 0x7a, 0x50, 0x51 };
public static readonly byte[] ZstdSignature = { 0xfd, 0x2f, 0xb5 };
#endregion
@@ -617,6 +573,15 @@ namespace SabreTools.Library.Data
*/
public readonly static byte[] TorrentGZHeader = new byte[] { 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00 };
/* (Torrent)XZ Header Format
https://tukaani.org/xz/xz-file-format.txt
00-05 Identification (0xFD, '7', 'z', 'X', 'Z', 0x00) XzSignature
06 Flags (0x01 - CRC32, 0x04 - CRC64, 0x0A - SHA-256)
07-0A Flags CRC32 (uint, little-endian)
*/
public readonly static byte[] TorrentXZHeader = new byte[] { 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36 };
#endregion
#region ZIP internal signatures

View File

@@ -7,28 +7,39 @@
/// </summary>
public enum ArchiveVersion : ushort
{
MSDOSandOS2 = 0,
Amiga = 1,
OpenVMS = 2,
UNIX = 3,
VMCMS = 4,
AtariST = 5,
OS2HPFS = 6,
Macintosh = 7,
ZSystem = 8,
CPM = 9,
WindowsNTFS = 10,
MVS = 11,
VSE = 12,
AcornRisc = 13,
VFAT = 14,
AlternateMVS = 15,
BeOS = 16,
Tandem = 17,
OS400 = 18,
OSXDarwin = 19,
TorrentZip = 20,
TorrentZip64 = 45,
MSDOSandOS2 = 0,
Amiga = 1,
OpenVMS = 2,
UNIX = 3,
VMCMS = 4,
AtariST = 5,
OS2HPFS = 6,
Macintosh = 7,
ZSystem = 8,
CPM = 9,
WindowsNTFS = 10,
MVS = 11,
VSE = 12,
AcornRisc = 13,
VFAT = 14,
AlternateMVS = 15,
BeOS = 16,
Tandem = 17,
OS400 = 18,
OSXDarwin = 19,
TorrentZip = 20,
TorrentZip64 = 45,
}
/// <summary>
/// Compression being used in CHD
/// </summary>
public enum CHDCompression : uint
{
CHDCOMPRESSION_NONE = 0,
CHDCOMPRESSION_ZLIB = 1,
CHDCOMPRESSION_ZLIB_PLUS = 2,
CHDCOMPRESSION_AV = 3,
}
/// <summary>
@@ -36,36 +47,36 @@
/// </summary>
public enum CHD_CODEC : uint
{
NONE = 0,
NONE = 0,
#region General Codecs
ZLIB = 0x7a6c6962, // zlib
LZMA = 0x6c7a6d61, // lzma
HUFFMAN = 0x68756666, // huff
FLAC = 0x666c6163, // flac
ZLIB = 0x7a6c6962, // zlib
LZMA = 0x6c7a6d61, // lzma
HUFFMAN = 0x68756666, // huff
FLAC = 0x666c6163, // flac
#endregion
#region General Codecs with CD Frontend
CD_ZLIB = 0x63647a6c, // cdzl
CD_LZMA = 0x63646c7a, // cdlz
CD_FLAC = 0x6364666c, // cdfl
CD_ZLIB = 0x63647a6c, // cdzl
CD_LZMA = 0x63646c7a, // cdlz
CD_FLAC = 0x6364666c, // cdfl
#endregion
#region A/V Codecs
AVHUFF = 0x61766875, // avhu
AVHUFF = 0x61766875, // avhu
#endregion
#region Pseudo-Codecs Returned by hunk_info
SELF = 1, // copy of another hunk
PARENT = 2, // copy of a parent's hunk
MINI = 3, // legacy "mini" 8-byte repeat
SELF = 1, // copy of another hunk
PARENT = 2, // copy of a parent's hunk
MINI = 3, // legacy "mini" 8-byte repeat
#endregion
}
@@ -75,28 +86,28 @@
/// </summary>
public enum CompressionMethod : ushort
{
Stored = 0,
Shrunk = 1,
ReducedCompressionFactor1 = 2,
ReducedCompressionFactor2 = 3,
ReducedCompressionFactor3 = 4,
ReducedCompressionFactor4 = 5,
Imploded = 6,
Tokenizing = 7,
Deflated = 8,
Delfate64 = 9,
PKWAREDataCompressionLibrary = 10,
Type11 = 11, // Reserved and unused (SHOULD NOT BE USED)
BZIP2 = 12,
Type13 = 13, // Reserved and unused (SHOULD NOT BE USED)
LZMA = 14,
Type15 = 15, // Reserved and unused (SHOULD NOT BE USED)
Type16 = 16, // Reserved and unused (SHOULD NOT BE USED)
Type17 = 17, // Reserved and unused (SHOULD NOT BE USED)
IBMTERSE = 18,
IBMLZ77 = 19,
WavPak = 97,
PPMdVersionIRev1 = 98,
Stored = 0,
Shrunk = 1,
ReducedCompressionFactor1 = 2,
ReducedCompressionFactor2 = 3,
ReducedCompressionFactor3 = 4,
ReducedCompressionFactor4 = 5,
Imploded = 6,
Tokenizing = 7,
Deflated = 8,
Delfate64 = 9,
PKWAREDataCompressionLibrary = 10,
Type11 = 11, // Reserved and unused (SHOULD NOT BE USED)
BZIP2 = 12,
Type13 = 13, // Reserved and unused (SHOULD NOT BE USED)
LZMA = 14,
Type15 = 15, // Reserved and unused (SHOULD NOT BE USED)
Type16 = 16, // Reserved and unused (SHOULD NOT BE USED)
Type17 = 17, // Reserved and unused (SHOULD NOT BE USED)
IBMTERSE = 18,
IBMLZ77 = 19,
WavPak = 97,
PPMdVersionIRev1 = 98,
}
/// <summary>
@@ -132,12 +143,13 @@
TorrentZip,
TorrentGzip,
TorrentGzipRomba,
TorrentXZ,
TorrentXZRomba,
TapeArchive,
// Currently unimplemented fully
Torrent7Zip,
TorrentRar,
TorrentXZ,
TorrentLRZip,
TorrentLZ4,
TorrentZstd,
@@ -149,13 +161,13 @@
/// </summary>
public enum RarExtraAreaFlag : uint
{
FileEncryption = 0x01,
FileHash = 0x02,
FileTime = 0x03,
FileVersion = 0x04,
Redirection = 0x05,
UnixOwner = 0x06,
ServiceData = 0x07,
FileEncryption = 0x01,
FileHash = 0x02,
FileTime = 0x03,
FileVersion = 0x04,
Redirection = 0x05,
UnixOwner = 0x06,
ServiceData = 0x07,
}
/// <summary>
@@ -163,11 +175,11 @@
/// </summary>
public enum RarHeaderType : uint
{
MainArchiveHeader = 1,
File = 2,
Service = 3,
ArchiveEncryption = 4,
EndOfArchive = 5,
MainArchiveHeader = 1,
File = 2,
Service = 3,
ArchiveEncryption = 4,
EndOfArchive = 5,
}
/// <summary>
@@ -175,11 +187,11 @@
/// </summary>
public enum RarRedirectionType : uint
{
UnixSymlink = 0x0001,
WindowsSymlink = 0x0002,
WindowsJunction = 0x0003,
HardLink = 0x0004,
FileCopy = 0x0005,
UnixSymlink = 0x0001,
WindowsSymlink = 0x0002,
WindowsJunction = 0x0003,
HardLink = 0x0004,
FileCopy = 0x0005,
}
/// <summary>
@@ -187,49 +199,69 @@
/// </summary>
public enum SevenZipProperties : uint
{
kEnd = 0x00,
kEnd = 0x00,
kHeader = 0x01,
kHeader = 0x01,
kArchiveProperties = 0x02,
kArchiveProperties = 0x02,
kAdditionalStreamsInfo = 0x03,
kMainStreamsInfo = 0x04,
kFilesInfo = 0x05,
kAdditionalStreamsInfo = 0x03,
kMainStreamsInfo = 0x04,
kFilesInfo = 0x05,
kPackInfo = 0x06,
kUnPackInfo = 0x07,
kSubStreamsInfo = 0x08,
kPackInfo = 0x06,
kUnPackInfo = 0x07,
kSubStreamsInfo = 0x08,
kSize = 0x09,
kCRC = 0x0A,
kSize = 0x09,
kCRC = 0x0A,
kFolder = 0x0B,
kFolder = 0x0B,
kCodersUnPackSize = 0x0C,
kNumUnPackStream = 0x0D,
kCodersUnPackSize = 0x0C,
kNumUnPackStream = 0x0D,
kEmptyStream = 0x0E,
kEmptyFile = 0x0F,
kAnti = 0x10,
kEmptyStream = 0x0E,
kEmptyFile = 0x0F,
kAnti = 0x10,
kName = 0x11,
kCTime = 0x12,
kATime = 0x13,
kMTime = 0x14,
kWinAttributes = 0x15,
kComment = 0x16,
kName = 0x11,
kCTime = 0x12,
kATime = 0x13,
kMTime = 0x14,
kWinAttributes = 0x15,
kComment = 0x16,
kEncodedHeader = 0x17,
kEncodedHeader = 0x17,
kStartPos = 0x18,
kDummy = 0x19,
kStartPos = 0x18,
kDummy = 0x19,
}
#endregion
#region DatFile related
/// <summary>
/// Determines how the current dictionary is bucketed by
/// </summary>
public enum BucketedBy
{
Default = 0,
Size,
CRC,
MD5,
#if NET_FRAMEWORK
RIPEMD160,
#endif
SHA1,
SHA256,
SHA384,
SHA512,
Game,
}
/// <summary>
/// Determines the DAT deduplication type
/// </summary>
@@ -242,7 +274,9 @@
Game,
CRC,
MD5,
#if NET_FRAMEWORK
RIPEMD160,
#endif
SHA1,
SHA256,
SHA384,
@@ -292,23 +326,6 @@
File,
}
/// <summary>
/// Determines how the current dictionary is sorted by
/// </summary>
public enum SortedBy
{
Default = 0,
Size,
CRC,
MD5,
RIPEMD160,
SHA1,
SHA256,
SHA384,
SHA512,
Game,
}
/// <summary>
/// Determines how a DAT will be split internally
/// </summary>
@@ -367,7 +384,9 @@
// Disk
MD5,
#if NET_FRAMEWORK
RIPEMD160,
#endif
SHA1,
SHA256,
SHA384,
@@ -395,14 +414,14 @@
/// </summary>
public enum ItemType
{
Rom = 0,
Disk = 1,
Sample = 2,
Release = 3,
BiosSet = 4,
Archive = 5,
Rom = 0,
Disk = 1,
Sample = 2,
Release = 3,
BiosSet = 4,
Archive = 5,
Blank = 99, // This is not a real type, only used internally
Blank = 99, // This is not a real type, only used internally
}
#endregion

View File

@@ -11,29 +11,29 @@ namespace SabreTools.Library.Data
public enum ArchiveScanLevel
{
// 7zip
SevenZipExternal = 1 << 0,
SevenZipInternal = 1 << 1,
SevenZipBoth = SevenZipExternal | SevenZipInternal,
SevenZipExternal = 1 << 0,
SevenZipInternal = 1 << 1,
SevenZipBoth = SevenZipExternal | SevenZipInternal,
// GZip
GZipExternal = 1 << 2,
GZipInternal = 1 << 3,
GZipBoth = GZipExternal | GZipInternal,
GZipExternal = 1 << 2,
GZipInternal = 1 << 3,
GZipBoth = GZipExternal | GZipInternal,
// RAR
RarExternal = 1 << 4,
RarInternal = 1 << 5,
RarBoth = RarExternal | RarInternal,
RarExternal = 1 << 4,
RarInternal = 1 << 5,
RarBoth = RarExternal | RarInternal,
// Zip
ZipExternal = 1 << 6,
ZipInternal = 1 << 7,
ZipBoth = ZipExternal | ZipInternal,
ZipExternal = 1 << 6,
ZipInternal = 1 << 7,
ZipBoth = ZipExternal | ZipInternal,
// Tar
TarExternal = 1 << 8,
TarInternal = 1 << 9,
TarBoth = TarExternal | TarInternal,
TarExternal = 1 << 8,
TarInternal = 1 << 9,
TarBoth = TarExternal | TarInternal,
}
/// <summary>
@@ -42,34 +42,34 @@ namespace SabreTools.Library.Data
[Flags]
public enum GeneralPurposeBitFlag : ushort
{
Encrypted = 1 << 0,
ZeroedCRCAndSize = 1 << 3,
CompressedPatchedData = 1 << 5,
StrongEncryption = 1 << 6,
LanguageEncodingFlag = 1 << 11,
EncryptedCentralDirectory = 1 << 13,
Encrypted = 1 << 0,
ZeroedCRCAndSize = 1 << 3,
CompressedPatchedData = 1 << 5,
StrongEncryption = 1 << 6,
LanguageEncodingFlag = 1 << 11,
EncryptedCentralDirectory = 1 << 13,
// For Method 6 - Imploding
Imploding8KSlidingDictionary = 1 << 1,
Imploding3ShannonFanoTrees = 1 << 2,
Imploding8KSlidingDictionary = 1 << 1,
Imploding3ShannonFanoTrees = 1 << 2,
// For Methods 8 and 9 - Deflating
DeflatingMaximumCompression = 1 << 1,
DeflatingFastCompression = 1 << 2,
DeflatingMaximumCompression = 1 << 1,
DeflatingFastCompression = 1 << 2,
DeflatingSuperFastCompression = 1 << 1 | 1 << 2,
EnhancedDeflating = 1 << 4,
EnhancedDeflating = 1 << 4,
// For Method 14 - LZMA
LZMAEOSMarkerUsed = 1 << 1,
LZMAEOSMarkerUsed = 1 << 1,
// Reserved and unused (SHOULD NOT BE USED)
Bit7 = 1 << 7,
Bit8 = 1 << 8,
Bit9 = 1 << 9,
Bit10 = 1 << 10,
Bit12 = 1 << 12, // Reserved by PKWARE for enhanced compression
Bit14 = 1 << 14, // Reserved by PKWARE
Bit15 = 1 << 15, // Reserved by PKWARE
Bit7 = 1 << 7,
Bit8 = 1 << 8,
Bit9 = 1 << 9,
Bit10 = 1 << 10,
Bit12 = 1 << 12, // Reserved by PKWARE for enhanced compression
Bit14 = 1 << 14, // Reserved by PKWARE
Bit15 = 1 << 15, // Reserved by PKWARE
}
/// <summary>
@@ -78,12 +78,12 @@ namespace SabreTools.Library.Data
[Flags]
public enum InternalFileAttributes : ushort
{
ASCIIOrTextFile = 1 << 0,
RecordLengthControl = 1 << 1,
ASCIIOrTextFile = 1 << 0,
RecordLengthControl = 1 << 1,
// Reserved and unused (SHOULD NOT BE USED)
Bit1 = 1 << 1,
Bit2 = 1 << 2,
Bit1 = 1 << 1,
Bit2 = 1 << 2,
}
/// <summary>
@@ -95,17 +95,17 @@ namespace SabreTools.Library.Data
/// <summary>
/// Volume. Archive is a part of multivolume set.
/// </summary>
Volume = 1 << 0,
Volume = 1 << 0,
/// <summary>
/// Volume number field is present. This flag is present in all volumes except first.
/// </summary>
VolumeNumberField = 1 << 1,
VolumeNumberField = 1 << 1,
/// <summary>
/// Solid archive.
/// </summary>
Solid = 1 << 2,
Solid = 1 << 2,
/// <summary>
/// Recovery record is present.
@@ -115,7 +115,7 @@ namespace SabreTools.Library.Data
/// <summary>
/// Locked archive.
/// </summary>
Locked = 1 << 4,
Locked = 1 << 4,
}
/// <summary>
@@ -124,8 +124,8 @@ namespace SabreTools.Library.Data
[Flags]
public enum RarEncryptionFlags : uint
{
PasswordCheckDataPresent = 1 << 0,
UseTweakedChecksums = 1 << 1,
PasswordCheckDataPresent = 1 << 0,
UseTweakedChecksums = 1 << 1,
/*
If flag 0x0002 is present, RAR transforms the checksum preserving file or service data integrity, so it becomes dependent on
@@ -143,22 +143,22 @@ namespace SabreTools.Library.Data
/// <summary>
/// Directory file system object (file header only)
/// </summary>
Directory = 1 << 0,
Directory = 1 << 0,
/// <summary>
/// Time field in Unix format is present
/// </summary>
TimeInUnix = 1 << 1,
TimeInUnix = 1 << 1,
/// <summary>
/// CRC32 field is present
/// </summary>
CRCPresent = 1 << 2,
CRCPresent = 1 << 2,
/// <summary>
/// Unpacked size is unknown
/// </summary>
UnpackedSizeUnknown = 1 << 3,
UnpackedSizeUnknown = 1 << 3,
/*
If flag 0x0008 is set, unpacked size field is still present, but must be ignored and extraction
@@ -177,46 +177,46 @@ namespace SabreTools.Library.Data
/// <summary>
/// Extra area is present in the end of header
/// </summary>
ExtraAreaPresent = 1 << 0,
ExtraAreaPresent = 1 << 0,
/// <summary>
/// Data area is present in the end of header
/// </summary>
DataAreaPresent = 1 << 1,
DataAreaPresent = 1 << 1,
/// <summary>
/// Blocks with unknown type and this flag must be skipped when updating an archive
/// </summary>
BlocksWithUnknownType = 1 << 2,
BlocksWithUnknownType = 1 << 2,
/// <summary>
/// Data area is continuing from previous volume
/// </summary>
DataAreaContinuingFromPrevious = 1 << 3,
DataAreaContinuingFromPrevious = 1 << 3,
/// <summary>
/// Data area is continuing in next volume
/// </summary>
DataAreaContinuingToNext = 1 << 4,
DataAreaContinuingToNext = 1 << 4,
/// <summary>
/// Block depends on preceding file block
/// </summary>
BlockDependsOnPreceding = 1 << 5,
BlockDependsOnPreceding = 1 << 5,
/// <summary>
/// Preserve a child block if host block is modified
/// </summary>
PreserveChildBlock = 1 << 6,
PreserveChildBlock = 1 << 6,
}
[Flags]
public enum RarUnixOwnerRecordFlags : uint
{
UserNameStringIsPresent = 1 << 0,
GroupNameStringIsPresent = 1 << 1,
NumericUserIdIsPresent = 1 << 2,
NumericGroupIdIsPresent = 1 << 3,
UserNameStringIsPresent = 1 << 0,
GroupNameStringIsPresent = 1 << 1,
NumericUserIdIsPresent = 1 << 2,
NumericGroupIdIsPresent = 1 << 3,
}
/// <summary>
@@ -225,10 +225,10 @@ namespace SabreTools.Library.Data
[Flags]
public enum RarTimeFlags : uint
{
TimeInUnixFormat = 1 << 0,
ModificationTimePresent = 1 << 1,
CreationTimePresent = 1 << 2,
LastAccessTimePresent = 1 << 3,
TimeInUnixFormat = 1 << 0,
ModificationTimePresent = 1 << 1,
CreationTimePresent = 1 << 2,
LastAccessTimePresent = 1 << 3,
}
#endregion
@@ -246,37 +246,37 @@ namespace SabreTools.Library.Data
/// <summary>
/// Logiqx XML (using machine)
/// </summary>
Logiqx = 1 << 0,
Logiqx = 1 << 0,
/// <summary>
/// Logiqx XML (using game)
/// </summary>
LogiqxDeprecated = 1 << 1,
LogiqxDeprecated = 1 << 1,
/// <summary>
/// MAME Softare List XML
/// </summary>
SoftwareList = 1 << 2,
SoftwareList = 1 << 2,
/// <summary>
/// MAME Listxml output
/// </summary>
Listxml = 1 << 3,
Listxml = 1 << 3,
/// <summary>
/// OfflineList XML
/// </summary>
OfflineList = 1 << 4,
OfflineList = 1 << 4,
/// <summary>
/// SabreDat XML
/// </summary>
SabreDat = 1 << 5,
SabreDat = 1 << 5,
/// <summary>
/// openMSX Software List XML
/// </summary>
OpenMSX = 1 << 6,
OpenMSX = 1 << 6,
#endregion
@@ -285,22 +285,22 @@ namespace SabreTools.Library.Data
/// <summary>
/// ClrMamePro custom
/// </summary>
ClrMamePro = 1 << 7,
ClrMamePro = 1 << 7,
/// <summary>
/// RomCetner INI-based
/// </summary>
RomCenter = 1 << 8,
RomCenter = 1 << 8,
/// <summary>
/// DOSCenter custom
/// </summary>
DOSCenter = 1 << 9,
DOSCenter = 1 << 9,
/// <summary>
/// AttractMode custom
/// </summary>
AttractMode = 1 << 10,
AttractMode = 1 << 10,
#endregion
@@ -309,37 +309,37 @@ namespace SabreTools.Library.Data
/// <summary>
/// ClrMamePro missfile
/// </summary>
MissFile = 1 << 11,
MissFile = 1 << 11,
/// <summary>
/// Comma-Separated Values (standardized)
/// </summary>
CSV = 1 << 12,
CSV = 1 << 12,
/// <summary>
/// Semicolon-Separated Values (standardized)
/// </summary>
SSV = 1 << 13,
SSV = 1 << 13,
/// <summary>
/// Tab-Separated Values (standardized)
/// </summary>
TSV = 1 << 14,
TSV = 1 << 14,
/// <summary>
/// MAME Listrom output
/// </summary>
Listrom = 1 << 15,
Listrom = 1 << 15,
/// <summary>
/// Everdrive Packs SMDB
/// </summary>
EverdriveSMDB = 1 << 16,
EverdriveSMDB = 1 << 16,
/// <summary>
/// JSON
/// </summary>
Json = 1 << 17,
Json = 1 << 17,
#endregion
@@ -348,37 +348,39 @@ namespace SabreTools.Library.Data
/// <summary>
/// CRC32 hash list
/// </summary>
RedumpSFV = 1 << 18,
RedumpSFV = 1 << 18,
/// <summary>
/// MD5 hash list
/// </summary>
RedumpMD5 = 1 << 19,
RedumpMD5 = 1 << 19,
#if NET_FRAMEWORK
/// <summary>
/// RIPEMD160 hash list
/// </summary>
RedumpRIPEMD160 = 1 << 20,
#endif
/// <summary>
/// SHA-1 hash list
/// </summary>
RedumpSHA1 = 1 << 21,
RedumpSHA1 = 1 << 21,
/// <summary>
/// SHA-256 hash list
/// </summary>
RedumpSHA256 = 1 << 22,
RedumpSHA256 = 1 << 22,
/// <summary>
/// SHA-384 hash list
/// </summary>
RedumpSHA384 = 1 << 23,
RedumpSHA384 = 1 << 23,
/// <summary>
/// SHA-512 hash list
/// </summary>
RedumpSHA512 = 1 << 24,
RedumpSHA512 = 1 << 24,
#endregion
@@ -392,18 +394,25 @@ namespace SabreTools.Library.Data
[Flags]
public enum Hash
{
CRC = 1 << 0,
MD5 = 1 << 1,
CRC = 1 << 0,
MD5 = 1 << 1,
#if NET_FRAMEWORK
RIPEMD160 = 1 << 2,
SHA1 = 1 << 3,
SHA256 = 1 << 4,
SHA384 = 1 << 5,
SHA512 = 1 << 6,
#endif
SHA1 = 1 << 3,
SHA256 = 1 << 4,
SHA384 = 1 << 5,
SHA512 = 1 << 6,
// Special combinations
Standard = CRC | MD5 | SHA1,
DeepHashes = SHA256 | SHA384 | SHA512 | RIPEMD160,
SecureHashes = MD5 | SHA1 | SHA256 | SHA384 | SHA512 | RIPEMD160,
#if NET_FRAMEWORK
DeepHashes = SHA256 | SHA384 | SHA512,
SecureHashes = MD5 | RIPEMD160 | SHA1 | SHA256 | SHA384 | SHA512,
#else
DeepHashes = SHA256 | SHA384 | SHA512,
SecureHashes = MD5 | SHA1 | SHA256 | SHA384 | SHA512,
#endif
}
/// <summary>
@@ -415,32 +424,32 @@ namespace SabreTools.Library.Data
/// <summary>
/// Only output to the console
/// </summary>
None = 0x00,
None = 0x00,
/// <summary>
/// Console-formatted
/// </summary>
Textfile = 1 << 0,
Textfile = 1 << 0,
/// <summary>
/// ClrMamePro HTML
/// </summary>
HTML = 1 << 1,
HTML = 1 << 1,
/// <summary>
/// Comma-Separated Values (Standardized)
/// </summary>
CSV = 1 << 2,
CSV = 1 << 2,
/// <summary>
/// Semicolon-Separated Values (Standardized)
/// </summary>
SSV = 1 << 3,
SSV = 1 << 3,
/// <summary>
/// Tab-Separated Values (Standardized)
/// </summary>
TSV = 1 << 4,
TSV = 1 << 4,
All = Int32.MaxValue,
}
@@ -451,13 +460,13 @@ namespace SabreTools.Library.Data
[Flags]
public enum SplittingMode
{
None = 0x00,
None = 0x00,
Extension = 1 << 0,
Hash = 1 << 2,
Level = 1 << 3,
Type = 1 << 4,
Size = 1 << 5,
Extension = 1 << 0,
Hash = 1 << 2,
Level = 1 << 3,
Type = 1 << 4,
Size = 1 << 5,
}
/// <summary>
@@ -469,21 +478,21 @@ namespace SabreTools.Library.Data
None = 0x00,
// Standard diffs
DiffDupesOnly = 1 << 0,
DiffNoDupesOnly = 1 << 1,
DiffIndividualsOnly = 1 << 2,
DiffDupesOnly = 1 << 0,
DiffNoDupesOnly = 1 << 1,
DiffIndividualsOnly = 1 << 2,
// Cascaded diffs
DiffCascade = 1 << 3,
DiffReverseCascade = 1 << 4,
DiffCascade = 1 << 3,
DiffReverseCascade = 1 << 4,
// Base diffs
DiffAgainst = 1 << 5,
DiffAgainst = 1 << 5,
// Special update modes
Merge = 1 << 6,
BaseReplace = 1 << 7,
ReverseBaseReplace = 1 << 8,
Merge = 1 << 6,
BaseReplace = 1 << 7,
ReverseBaseReplace = 1 << 8,
// Combinations
AllDiffs = DiffDupesOnly | DiffNoDupesOnly | DiffIndividualsOnly,
@@ -500,12 +509,12 @@ namespace SabreTools.Library.Data
public enum DupeType
{
// Type of match
Hash = 1 << 0,
All = 1 << 1,
Hash = 1 << 0,
All = 1 << 1,
// Location of match
Internal = 1 << 2,
External = 1 << 3,
Internal = 1 << 2,
External = 1 << 3,
}
/// <summary>
@@ -517,13 +526,13 @@ namespace SabreTools.Library.Data
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0x00,
NULL = 0x00,
None = 1 << 0,
Good = 1 << 1,
BadDump = 1 << 2,
Nodump = 1 << 3,
Verified = 1 << 4,
None = 1 << 0,
Good = 1 << 1,
BadDump = 1 << 2,
Nodump = 1 << 3,
Verified = 1 << 4,
}
/// <summary>
@@ -535,12 +544,12 @@ namespace SabreTools.Library.Data
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0x00,
NULL = 0x00,
None = 1 << 0,
Bios = 1 << 1,
Device = 1 << 2,
Mechanical = 1 << 3,
None = 1 << 0,
Bios = 1 << 1,
Device = 1 << 2,
Mechanical = 1 << 3,
}
#endregion

View File

@@ -11,11 +11,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SabreTools.Library.Tools;
namespace NaturalSort
{
public class NaturalComparer : Comparer<string>, IDisposable
@@ -77,12 +76,12 @@ namespace NaturalSort
{
if (!long.TryParse(left, out long x))
{
return Utilities.CompareNumeric(left, right);
return NaturalComparerUtil.CompareNumeric(left, right);
}
if (!long.TryParse(right, out long y))
{
return Utilities.CompareNumeric(left, right);
return NaturalComparerUtil.CompareNumeric(left, right);
}
// If we have an equal part, then make sure that "longer" ones are taken into account

View File

@@ -0,0 +1,78 @@
using System.IO;
namespace NaturalSort
{
public static class NaturalComparerUtil
{
public static int CompareNumeric(string s1, string s2)
{
// Save the orginal strings, for later comparison
string s1orig = s1;
string s2orig = s2;
// We want to normalize the strings, so we set both to lower case
s1 = s1.ToLowerInvariant();
s2 = s2.ToLowerInvariant();
// If the strings are the same exactly, return
if (s1 == s2)
return s1orig.CompareTo(s2orig);
// If one is null, then say that's less than
if (s1 == null)
return -1;
if (s2 == null)
return 1;
// Now split into path parts after converting AltDirSeparator to DirSeparator
s1 = s1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
s2 = s2.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
string[] s1parts = s1.Split(Path.DirectorySeparatorChar);
string[] s2parts = s2.Split(Path.DirectorySeparatorChar);
// Then compare each part in turn
for (int j = 0; j < s1parts.Length && j < s2parts.Length; j++)
{
int compared = CompareNumericPart(s1parts[j], s2parts[j]);
if (compared != 0)
return compared;
}
// If we got out here, then it looped through at least one of the strings
if (s1parts.Length > s2parts.Length)
return 1;
if (s1parts.Length < s2parts.Length)
return -1;
return s1orig.CompareTo(s2orig);
}
private static int CompareNumericPart(string s1, string s2)
{
// Otherwise, loop through until we have an answer
for (int i = 0; i < s1.Length && i < s2.Length; i++)
{
int s1c = s1[i];
int s2c = s2[i];
// If the characters are the same, continue
if (s1c == s2c)
continue;
// If they're different, check which one was larger
if (s1c > s2c)
return 1;
if (s1c < s2c)
return -1;
}
// If we got out here, then it looped through at least one of the strings
if (s1.Length > s2.Length)
return 1;
if (s1.Length < s2.Length)
return -1;
return 0;
}
}
}

View File

@@ -14,8 +14,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using SabreTools.Library.Tools;
namespace NaturalSort
{
public class NaturalReversedComparer : Comparer<string>, IDisposable
@@ -77,12 +75,12 @@ namespace NaturalSort
{
if (!long.TryParse(left, out long x))
{
return Utilities.CompareNumeric(right, left);
return NaturalComparerUtil.CompareNumeric(right, left);
}
if (!long.TryParse(right, out long y))
{
return Utilities.CompareNumeric(right, left);
return NaturalComparerUtil.CompareNumeric(right, left);
}
// If we have an equal part, then make sure that "longer" ones are taken into account

View File

@@ -3,6 +3,7 @@ using System.IO;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
using SabreTools.Library.Tools;
namespace SabreTools.Library.FileTypes
{
@@ -34,6 +35,83 @@ namespace SabreTools.Library.FileTypes
{
}
/// <summary>
/// Create an archive object from a filename, if possible
/// </summary>
/// <param name="input">Name of the file to create the archive from</param>
/// <returns>Archive object representing the inputs</returns>
public static BaseArchive Create(string input)
{
BaseArchive archive = null;
// First get the archive type
FileType? at = input.GetFileType();
// If we got back null, then it's not an archive, so we we return
if (at == null)
return archive;
// Create the archive based on the type
Globals.Logger.Verbose($"Found archive of type: {at}");
switch (at)
{
case FileType.GZipArchive:
archive = new GZipArchive(input);
break;
case FileType.RarArchive:
archive = new RarArchive(input);
break;
case FileType.SevenZipArchive:
archive = new SevenZipArchive(input);
break;
case FileType.TapeArchive:
archive = new TapeArchive(input);
break;
case FileType.ZipArchive:
archive = new ZipArchive(input);
break;
default:
// We ignore all other types for now
break;
}
return archive;
}
/// <summary>
/// Create an archive object of the specified type, if possible
/// </summary>
/// <param name="archiveType">SharpCompress.Common.ArchiveType representing the archive to create</param>
/// <returns>Archive object representing the inputs</returns>
public static BaseArchive Create(FileType archiveType)
{
switch (archiveType)
{
case FileType.GZipArchive:
return new GZipArchive();
case FileType.RarArchive:
return new RarArchive();
case FileType.SevenZipArchive:
return new SevenZipArchive();
case FileType.TapeArchive:
return new TapeArchive();
case FileType.ZipArchive:
return new ZipArchive();
default:
return null;
}
}
#endregion
#region Extraction

View File

@@ -17,7 +17,9 @@ namespace SabreTools.Library.FileTypes
public long? Size { get; set; }
public byte[] CRC { get; set; }
public byte[] MD5 { get; set; }
#if NET_FRAMEWORK
public byte[] RIPEMD160 { get; set; }
#endif
public byte[] SHA1 { get; set; }
public byte[] SHA256 { get; set; }
public byte[] SHA384 { get; set; }
@@ -45,14 +47,16 @@ namespace SabreTools.Library.FileTypes
if (getHashes)
{
BaseFile temp = Utilities.GetFileInfo(this.Filename);
BaseFile temp = FileExtensions.GetInfo(this.Filename);
if (temp != null)
{
this.Parent = temp.Parent;
this.Date = temp.Date;
this.CRC = temp.CRC;
this.MD5 = temp.MD5;
#if NET_FRAMEWORK
this.RIPEMD160 = temp.RIPEMD160;
#endif
this.SHA1 = temp.SHA1;
this.SHA256 = temp.SHA256;
this.SHA384 = temp.SHA384;
@@ -73,21 +77,23 @@ namespace SabreTools.Library.FileTypes
if (getHashes)
{
BaseFile temp = Utilities.GetStreamInfo(stream, stream.Length);
if(temp != null)
BaseFile temp = stream.GetInfo();
if (temp != null)
{
this.Parent = temp.Parent;
this.Date = temp.Date;
this.CRC = temp.CRC;
this.MD5 = temp.MD5;
#if NET_FRAMEWORK
this.RIPEMD160 = temp.RIPEMD160;
#endif
this.SHA1 = temp.SHA1;
this.SHA256 = temp.SHA256;
this.SHA384 = temp.SHA384;
this.SHA512 = temp.SHA512;
}
}
}
#endregion

View File

@@ -1,4 +1,6 @@
using System.IO;
using System;
using System.IO;
using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.Tools;
@@ -9,477 +11,137 @@ namespace SabreTools.Library.FileTypes
/// This is code adapted from chd.h and chd.cpp in MAME
/// Additional archival code from https://github.com/rtissera/libchdr/blob/master/src/chd.h
/// </summary>
/// <remarks>
/// ----------------------------------------------
/// Common CHD Header:
/// 0x00-0x07 - CHD signature
/// 0x08-0x0B - Header size
/// 0x0C-0x0F - CHD version
/// ----------------------------------------------
/// CHD v1 header layout:
/// 0x10-0x13 - Flags (1: Has parent MD5, 2: Disallow writes)
/// 0x14-0x17 - Compression
/// 0x18-0x1B - 512-byte sectors per hunk
/// 0x1C-0x1F - Hunk count
/// 0x20-0x23 - Hard disk cylinder count
/// 0x24-0x27 - Hard disk head count
/// 0x28-0x2B - Hard disk sector count
/// 0x2C-0x3B - MD5
/// 0x3C-0x4B - Parent MD5
/// ----------------------------------------------
/// CHD v2 header layout:
/// 0x10-0x13 - Flags (1: Has parent MD5, 2: Disallow writes)
/// 0x14-0x17 - Compression
/// 0x18-0x1B - seclen-byte sectors per hunk
/// 0x1C-0x1F - Hunk count
/// 0x20-0x23 - Hard disk cylinder count
/// 0x24-0x27 - Hard disk head count
/// 0x28-0x2B - Hard disk sector count
/// 0x2C-0x3B - MD5
/// 0x3C-0x4B - Parent MD5
/// 0x4C-0x4F - Number of bytes per sector (seclen)
/// ----------------------------------------------
/// CHD v3 header layout:
/// 0x10-0x13 - Flags (1: Has parent SHA-1, 2: Disallow writes)
/// 0x14-0x17 - Compression
/// 0x18-0x1B - Hunk count
/// 0x1C-0x23 - Logical Bytes
/// 0x24-0x2C - Metadata Offset
/// ...
/// 0x4C-0x4F - Hunk Bytes
/// 0x50-0x63 - SHA-1
/// 0x64-0x77 - Parent SHA-1
/// 0x78-0x87 - Map
/// ----------------------------------------------
/// CHD v4 header layout:
/// 0x10-0x13 - Flags (1: Has parent SHA-1, 2: Disallow writes)
/// 0x14-0x17 - Compression
/// 0x18-0x1B - Hunk count
/// 0x1C-0x23 - Logical Bytes
/// 0x24-0x2C - Metadata Offset
/// ...
/// 0x2C-0x2F - Hunk Bytes
/// 0x30-0x43 - SHA-1
/// 0x44-0x57 - Parent SHA-1
/// 0x58-0x6b - Raw SHA-1
/// 0x6c-0x7b - Map
/// ----------------------------------------------
/// CHD v5 header layout:
/// 0x10-0x13 - Compression format 1
/// 0x14-0x17 - Compression format 2
/// 0x18-0x1B - Compression format 3
/// 0x1C-0x1F - Compression format 4
/// 0x20-0x27 - Logical Bytes
/// 0x28-0x2F - Map Offset
/// 0x30-0x37 - Metadata Offset
/// 0x38-0x3B - Hunk Bytes
/// 0x3C-0x3F - Unit Bytes
/// 0x40-0x53 - Raw SHA-1
/// 0x54-0x67 - SHA-1
/// 0x68-0x7b - Parent SHA-1
/// ----------------------------------------------
/// </remarks>
public class CHDFile : BaseFile
public abstract class CHDFile : BaseFile
{
#region Private instance variables
// Core parameters from the header
private byte[] m_signature; // signature
private uint m_headersize; // size of the header
private uint m_version; // version of the header
private ulong m_logicalbytes; // logical size of the raw CHD data in bytes
private ulong m_mapoffset; // offset of map
private ulong m_metaoffset; // offset to first metadata bit
private uint m_sectorsperhunk; // number of sectors per hunk
private uint m_hunkbytes; // size of each raw hunk in bytes
private ulong m_hunkcount; // number of hunks represented
private uint m_unitbytes; // size of each unit in bytes
private ulong m_unitcount; // number of units represented
private CHD_CODEC[] m_compression = new CHD_CODEC[4]; // array of compression types used
// map information
private uint m_mapentrybytes; // length of each entry in a map
// additional required vars
private uint? _headerVersion;
private BinaryReader m_br; // Binary reader representing the CHD stream
#endregion
#region Pubically facing variables
public uint? Version
{
get
{
if (_headerVersion == null)
{
_headerVersion = ValidateHeaderVersion();
}
return _headerVersion;
}
}
// Common header fields
protected char[] tag = new char[8]; // 'MComprHD'
protected uint length; // length of header (including tag and length fields)
protected uint version; // drive format version
#endregion
#region Constructors
/// <summary>
/// Create a new, blank CHDFile
/// </summary>
public CHDFile()
{
this.Type = FileType.CHD;
}
/// <summary>
/// Create a new CHDFile from an input file
/// </summary>
/// <param name="filename"></param>
public CHDFile(string filename)
: this(Utilities.TryOpenRead(filename))
/// <param name="filename">Filename respresenting the CHD file</param>
public static CHDFile Create(string filename)
{
using (FileStream fs = FileExtensions.TryOpenRead(filename))
{
return Create(fs);
}
}
/// <summary>
/// Create a new CHDFile from an input stream
/// </summary>
/// <param name="chdstream">Stream representing the CHD file</param>
public CHDFile(Stream chdstream)
public static CHDFile Create(Stream chdstream)
{
this.Type = FileType.CHD;
m_br = new BinaryReader(chdstream);
// Read the standard CHD headers
(char[] tag, uint length, uint version) = GetHeaderValues(chdstream);
chdstream.Seek(-16, SeekOrigin.Current); // Seek back to start
_headerVersion = ValidateHeaderVersion();
if (_headerVersion != null)
{
byte[] hash = GetHashFromHeader();
if (hash != null)
{
if (hash.Length == Constants.MD5Length)
this.MD5 = hash;
else if (hash.Length == Constants.SHA1Length)
this.SHA1 = hash;
}
}
// Validate that this is actually a valid CHD
uint validatedVersion = ValidateHeader(tag, length, version);
if (validatedVersion == 0)
return null;
// Read and retrun the current CHD
CHDFile generated = ReadAsVersion(chdstream, version);
if (generated != null)
generated.Type = FileType.CHD;
return generated;
}
#endregion
#region Abstract functionality
/// <summary>
/// Return the best-available hash for a particular CHD version
/// </summary>
public abstract byte[] GetHash();
#endregion
#region Header Parsing
/// <summary>
/// Validate the initial signature, version, and header size
/// Get the generic header values of a CHD, if possible
/// </summary>
/// <returns>Unsigned int containing the version number, null if invalid</returns>
private uint? ValidateHeaderVersion()
/// <param name="stream"></param>
/// <returns></returns>
private static (char[] tag, uint length, uint version) GetHeaderValues(Stream stream)
{
try
char[] parsedTag = new char[8];
uint parsedLength = 0;
uint parsedVersion = 0;
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
// Seek to the beginning to make sure we're reading the correct bytes
m_br.BaseStream.Seek(0, SeekOrigin.Begin);
parsedTag = br.ReadCharsBigEndian(8);
parsedLength = br.ReadUInt32BigEndian();
parsedVersion = br.ReadUInt32BigEndian();
}
// Read and verify the CHD signature
m_signature = m_br.ReadBytes(8);
return (parsedTag, parsedLength, parsedVersion);
}
// If no signature could be read, return null
if (m_signature == null || m_signature.Length == 0)
/// <summary>
/// Validate the header values
/// </summary>
/// <returns>Matching version, 0 if none</returns>
private static uint ValidateHeader(char[] tag, uint length, uint version)
{
if (!string.Equals(new string(tag), "MComprHD", StringComparison.Ordinal))
return 0;
switch (version)
{
case 1:
return length == CHDFileV1.HeaderSize ? version : 0;
case 2:
return length == CHDFileV2.HeaderSize ? version : 0;
case 3:
return length == CHDFileV3.HeaderSize ? version : 0;
case 4:
return length == CHDFileV4.HeaderSize ? version : 0;
case 5:
return length == CHDFileV5.HeaderSize ? version : 0;
default:
return 0;
}
}
/// <summary>
/// Read a stream as a particular CHD version
/// </summary>
/// <param name="stream">CHD file as a stream</param>
/// <param name="version">CHD version to parse</param>
/// <returns>Populated CHD file, null on failure</returns>
private static CHDFile ReadAsVersion(Stream stream, uint version)
{
switch (version)
{
case 1:
return CHDFileV1.Deserialize(stream);
case 2:
return CHDFileV2.Deserialize(stream);
case 3:
return CHDFileV3.Deserialize(stream);
case 4:
return CHDFileV4.Deserialize(stream);
case 5:
return CHDFileV5.Deserialize(stream);
default:
return null;
if (!m_signature.StartsWith(Constants.CHDSignature, exact: true))
{
// throw CHDERR_INVALID_FILE;
return null;
}
// Get the header size and version
m_headersize = m_br.ReadUInt32Reverse();
m_version = m_br.ReadUInt32Reverse();
// If we have an invalid combination of size and version
if ((m_version == 1 && m_headersize != Constants.CHD_V1_HEADER_SIZE)
|| (m_version == 2 && m_headersize != Constants.CHD_V2_HEADER_SIZE)
|| (m_version == 3 && m_headersize != Constants.CHD_V3_HEADER_SIZE)
|| (m_version == 4 && m_headersize != Constants.CHD_V4_HEADER_SIZE)
|| (m_version == 5 && m_headersize != Constants.CHD_V5_HEADER_SIZE)
|| (m_version < 1 || m_version > 5))
{
// throw CHDERR_UNSUPPORTED_VERSION;
return null;
}
return m_version;
}
catch
{
return null;
}
}
/// <summary>
/// Get the internal MD5 (v1, v2) or SHA-1 (v3, v4, v5) from the CHD
/// </summary>
/// <returns>MD5 as a byte array, null on error</returns>
private byte[] GetHashFromHeader()
{
// Validate the header by default just in case
uint? version = ValidateHeaderVersion();
// Now get the hash, if possible
byte[] hash;
// Now parse the rest of the header according to the version
try
{
switch (version)
{
case 1:
hash = ParseCHDv1Header();
break;
case 2:
hash = ParseCHDv2Header();
break;
case 3:
hash = ParseCHDv3Header();
break;
case 4:
hash = ParseCHDv4Header();
break;
case 5:
hash = ParseCHDv5Header();
break;
case null:
default:
// throw CHDERR_INVALID_FILE;
return null;
}
}
catch
{
// throw CHDERR_INVALID_FILE;
return null;
}
return hash;
}
/// <summary>
/// Parse a CHD v1 header
/// </summary>
/// <returns>The extracted MD5 on success, null otherwise</returns>
private byte[] ParseCHDv1Header()
{
// Seek to after the signature to make sure we're reading the correct bytes
m_br.BaseStream.Seek(16, SeekOrigin.Begin);
// Set the blank MD5 hash
byte[] md5 = new byte[16];
// Set offsets and defaults
m_mapoffset = 0;
m_mapentrybytes = 0;
// Read the CHD flags
uint flags = m_br.ReadUInt32Reverse();
// Determine compression
switch (m_br.ReadUInt32())
{
case 0: m_compression[0] = CHD_CODEC.NONE; break;
case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
}
m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
m_sectorsperhunk = m_br.ReadUInt32Reverse();
m_hunkcount = m_br.ReadUInt32Reverse();
m_br.ReadUInt32Reverse(); // Cylinder count
m_br.ReadUInt32Reverse(); // Head count
m_br.ReadUInt32Reverse(); // Sector count
md5 = m_br.ReadBytes(16);
m_br.ReadBytes(16); // Parent MD5
return md5;
}
/// <summary>
/// Parse a CHD v2 header
/// </summary>
/// <returns>The extracted MD5 on success, null otherwise</returns>
private byte[] ParseCHDv2Header()
{
// Seek to after the signature to make sure we're reading the correct bytes
m_br.BaseStream.Seek(16, SeekOrigin.Begin);
// Set the blank MD5 hash
byte[] md5 = new byte[16];
// Set offsets and defaults
m_mapoffset = 0;
m_mapentrybytes = 0;
// Read the CHD flags
uint flags = m_br.ReadUInt32Reverse();
// Determine compression
switch (m_br.ReadUInt32())
{
case 0: m_compression[0] = CHD_CODEC.NONE; break;
case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
}
m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
m_sectorsperhunk = m_br.ReadUInt32Reverse();
m_hunkcount = m_br.ReadUInt32Reverse();
m_br.ReadUInt32Reverse(); // Cylinder count
m_br.ReadUInt32Reverse(); // Head count
m_br.ReadUInt32Reverse(); // Sector count
md5 = m_br.ReadBytes(16);
m_br.ReadBytes(16); // Parent MD5
m_br.ReadUInt32Reverse(); // Sector size
return md5;
}
/// <summary>
/// Parse a CHD v3 header
/// </summary>
/// <returns>The extracted SHA-1 on success, null otherwise</returns>
private byte[] ParseCHDv3Header()
{
// Seek to after the signature to make sure we're reading the correct bytes
m_br.BaseStream.Seek(16, SeekOrigin.Begin);
// Set the blank SHA-1 hash
byte[] sha1 = new byte[20];
// Set offsets and defaults
m_mapoffset = 120;
m_mapentrybytes = 16;
// Read the CHD flags
uint flags = m_br.ReadUInt32Reverse();
// Determine compression
switch (m_br.ReadUInt32())
{
case 0: m_compression[0] = CHD_CODEC.NONE; break;
case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
}
m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
m_hunkcount = m_br.ReadUInt32Reverse();
m_logicalbytes = m_br.ReadUInt64Reverse();
m_metaoffset = m_br.ReadUInt32Reverse();
m_br.BaseStream.Seek(76, SeekOrigin.Begin);
m_hunkbytes = m_br.ReadUInt32Reverse();
m_br.BaseStream.Seek(Constants.CHDv3SHA1Offset, SeekOrigin.Begin);
sha1 = m_br.ReadBytes(20);
// guess at the units based on snooping the metadata
// m_unitbytes = guess_unitbytes();
m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
return sha1;
}
/// <summary>
/// Parse a CHD v4 header
/// </summary>
/// <returns>The extracted SHA-1 on success, null otherwise</returns>
private byte[] ParseCHDv4Header()
{
// Seek to after the signature to make sure we're reading the correct bytes
m_br.BaseStream.Seek(16, SeekOrigin.Begin);
// Set the blank SHA-1 hash
byte[] sha1 = new byte[20];
// Set offsets and defaults
m_mapoffset = 108;
m_mapentrybytes = 16;
// Read the CHD flags
uint flags = m_br.ReadUInt32Reverse();
// Determine compression
switch (m_br.ReadUInt32())
{
case 0: m_compression[0] = CHD_CODEC.NONE; break;
case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
}
m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
m_hunkcount = m_br.ReadUInt32Reverse();
m_logicalbytes = m_br.ReadUInt64Reverse();
m_metaoffset = m_br.ReadUInt32Reverse();
m_br.BaseStream.Seek(44, SeekOrigin.Begin);
m_hunkbytes = m_br.ReadUInt32Reverse();
m_br.BaseStream.Seek(Constants.CHDv4SHA1Offset, SeekOrigin.Begin);
sha1 = m_br.ReadBytes(20);
// guess at the units based on snooping the metadata
// m_unitbytes = guess_unitbytes();
m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
return sha1;
}
/// <summary>
/// Parse a CHD v5 header
/// </summary>
/// <returns>The extracted SHA-1 on success, null otherwise</returns>
private byte[] ParseCHDv5Header()
{
// Seek to after the signature to make sure we're reading the correct bytes
m_br.BaseStream.Seek(16, SeekOrigin.Begin);
// Set the blank SHA-1 hash
byte[] sha1 = new byte[20];
// Determine compression
m_compression[0] = (CHD_CODEC)m_br.ReadUInt32Reverse();
m_compression[1] = (CHD_CODEC)m_br.ReadUInt32Reverse();
m_compression[2] = (CHD_CODEC)m_br.ReadUInt32Reverse();
m_compression[3] = (CHD_CODEC)m_br.ReadUInt32Reverse();
m_logicalbytes = m_br.ReadUInt64Reverse();
m_mapoffset = m_br.ReadUInt64Reverse();
m_metaoffset = m_br.ReadUInt64Reverse();
m_hunkbytes = m_br.ReadUInt32Reverse();
m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes;
m_unitbytes = m_br.ReadUInt32Reverse();
m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
// m_allow_writes = !compressed();
// determine properties of map entries
// m_mapentrybytes = compressed() ? 12 : 4;
m_br.BaseStream.Seek(Constants.CHDv5SHA1Offset, SeekOrigin.Begin);
sha1 = m_br.ReadBytes(20);
return sha1;
}
#endregion

View File

@@ -0,0 +1,90 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Library.Tools;
namespace SabreTools.Library.FileTypes
{
/// <summary>
/// CHD V1 File
/// </summary>
public class CHDFileV1 : CHDFile
{
/// <summary>
/// CHD flags
/// </summary>
[Flags]
public enum Flags : uint
{
DriveHasParent = 0x00000001,
DriveAllowsWrites = 0x00000002,
}
/// <summary>
/// Compression being used in CHD
/// </summary>
public enum Compression : uint
{
CHDCOMPRESSION_NONE = 0,
CHDCOMPRESSION_ZLIB = 1,
}
/// <summary>
/// Map format
/// </summary>
public class Map
{
public ulong offset; // 44; starting offset within the file
public ulong length; // 20; length of data; if == hunksize, data is uncompressed
}
public const int HeaderSize = 76;
public const uint Version = 1;
// V1-specific header values
public Flags flags; // flags (see above)
public Compression compression; // compression type
public uint hunksize; // 512-byte sectors per hunk
public uint totalhunks; // total # of hunks represented
public uint cylinders; // number of cylinders on hard disk
public uint heads; // number of heads on hard disk
public uint sectors; // number of sectors on hard disk
public byte[] md5 = new byte[16]; // MD5 checksum of raw data
public byte[] parentmd5 = new byte[16]; // MD5 checksum of parent file
/// <summary>
/// Parse and validate the header as if it's V1
/// </summary>
public static CHDFileV1 Deserialize(Stream stream)
{
CHDFileV1 chd = new CHDFileV1();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
chd.tag = br.ReadCharsBigEndian(8);
chd.length = br.ReadUInt32BigEndian();
chd.version = br.ReadUInt32BigEndian();
chd.flags = (Flags)br.ReadUInt32BigEndian();
chd.compression = (Compression)br.ReadUInt32BigEndian();
chd.hunksize = br.ReadUInt32BigEndian();
chd.totalhunks = br.ReadUInt32BigEndian();
chd.cylinders = br.ReadUInt32BigEndian();
chd.heads = br.ReadUInt32BigEndian();
chd.sectors = br.ReadUInt32BigEndian();
chd.md5 = br.ReadBytesBigEndian(16);
chd.parentmd5 = br.ReadBytesBigEndian(16);
}
return chd;
}
/// <summary>
/// Return internal MD5 hash
/// </summary>
public override byte[] GetHash()
{
return md5;
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Library.Tools;
namespace SabreTools.Library.FileTypes
{
/// <summary>
/// CHD V2 File
/// </summary>
public class CHDFileV2 : CHDFile
{
/// <summary>
/// CHD flags
/// </summary>
[Flags]
public enum Flags : uint
{
DriveHasParent = 0x00000001,
DriveAllowsWrites = 0x00000002,
}
/// <summary>
/// Compression being used in CHD
/// </summary>
public enum Compression : uint
{
CHDCOMPRESSION_NONE = 0,
CHDCOMPRESSION_ZLIB = 1,
}
/// <summary>
/// Map format
/// </summary>
public class Map
{
public ulong offset; // 44; starting offset within the file
public ulong length; // 20; length of data; if == hunksize, data is uncompressed
}
public const int HeaderSize = 80;
public const uint Version = 2;
// V2-specific header values
public Flags flags; // flags (see above)
public Compression compression; // compression type
public uint hunksize; // 512-byte sectors per hunk
public uint totalhunks; // total # of hunks represented
public uint cylinders; // number of cylinders on hard disk
public uint heads; // number of heads on hard disk
public uint sectors; // number of sectors on hard disk
public byte[] md5 = new byte[16]; // MD5 checksum of raw data
public byte[] parentmd5 = new byte[16]; // MD5 checksum of parent file
public uint seclen; // number of bytes per sector
/// <summary>
/// Parse and validate the header as if it's V2
/// </summary>
public static CHDFileV2 Deserialize(Stream stream)
{
CHDFileV2 chd = new CHDFileV2();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
chd.tag = br.ReadCharsBigEndian(8);
chd.length = br.ReadUInt32BigEndian();
chd.version = br.ReadUInt32BigEndian();
chd.flags = (Flags)br.ReadUInt32BigEndian();
chd.compression = (Compression)br.ReadUInt32BigEndian();
chd.hunksize = br.ReadUInt32BigEndian();
chd.totalhunks = br.ReadUInt32BigEndian();
chd.cylinders = br.ReadUInt32BigEndian();
chd.heads = br.ReadUInt32BigEndian();
chd.sectors = br.ReadUInt32BigEndian();
chd.md5 = br.ReadBytesBigEndian(16);
chd.parentmd5 = br.ReadBytesBigEndian(16);
chd.seclen = br.ReadUInt32BigEndian();
}
return chd;
}
/// <summary>
/// Return internal MD5 hash
/// </summary>
public override byte[] GetHash()
{
return md5;
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Library.Tools;
namespace SabreTools.Library.FileTypes
{
/// <summary>
/// CHD V3 File
/// </summary>
public class CHDFileV3 : CHDFile
{
/// <summary>
/// CHD flags
/// </summary>
[Flags]
public enum Flags : uint
{
DriveHasParent = 0x00000001,
DriveAllowsWrites = 0x00000002,
}
/// <summary>
/// Compression being used in CHD
/// </summary>
public enum Compression : uint
{
CHDCOMPRESSION_NONE = 0,
CHDCOMPRESSION_ZLIB = 1,
CHDCOMPRESSION_ZLIB_PLUS = 2,
}
/// <summary>
/// Map format
/// </summary>
public class Map
{
public ulong offset; // starting offset within the file
public uint crc32; // 32-bit CRC of the uncompressed data
public ushort length_lo; // lower 16 bits of length
public byte length_hi; // upper 8 bits of length
public byte flags; // flags, indicating compression info
}
public const int HeaderSize = 120;
public const uint Version = 3;
// V3-specific header values
public Flags flags; // flags (see above)
public Compression compression; // compression type
public uint totalhunks; // total # of hunks represented
public ulong logicalbytes; // logical size of the data (in bytes)
public ulong metaoffset; // offset to the first blob of metadata
public byte[] md5 = new byte[16]; // MD5 checksum of raw data
public byte[] parentmd5 = new byte[16]; // MD5 checksum of parent file
public uint hunkbytes; // number of bytes per hunk
public byte[] sha1 = new byte[20]; // SHA1 checksum of raw data
public byte[] parentsha1 = new byte[20]; // SHA1 checksum of parent file
/// <summary>
/// Parse and validate the header as if it's V3
/// </summary>
public static CHDFileV3 Deserialize(Stream stream)
{
CHDFileV3 chd = new CHDFileV3();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
chd.tag = br.ReadCharsBigEndian(8);
chd.length = br.ReadUInt32BigEndian();
chd.version = br.ReadUInt32BigEndian();
chd.flags = (Flags)br.ReadUInt32BigEndian();
chd.compression = (Compression)br.ReadUInt32BigEndian();
chd.totalhunks = br.ReadUInt32BigEndian();
chd.logicalbytes = br.ReadUInt64BigEndian();
chd.metaoffset = br.ReadUInt64BigEndian();
chd.md5 = br.ReadBytesBigEndian(16);
chd.parentmd5 = br.ReadBytesBigEndian(16);
chd.hunkbytes = br.ReadUInt32BigEndian();
chd.sha1 = br.ReadBytesBigEndian(20);
chd.parentsha1 = br.ReadBytesBigEndian(20);
}
return chd;
}
/// <summary>
/// Return internal SHA1 hash
/// </summary>
public override byte[] GetHash()
{
return sha1;
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Library.Tools;
namespace SabreTools.Library.FileTypes
{
/// <summary>
/// CHD V4 File
/// </summary>
public class CHDFileV4 : CHDFile
{
/// <summary>
/// CHD flags
/// </summary>
[Flags]
public enum Flags : uint
{
DriveHasParent = 0x00000001,
DriveAllowsWrites = 0x00000002,
}
/// <summary>
/// Compression being used in CHD
/// </summary>
public enum Compression : uint
{
CHDCOMPRESSION_NONE = 0,
CHDCOMPRESSION_ZLIB = 1,
CHDCOMPRESSION_ZLIB_PLUS = 2,
CHDCOMPRESSION_AV = 3,
}
/// <summary>
/// Map format
/// </summary>
public class Map
{
public ulong offset; // starting offset within the file
public uint crc32; // 32-bit CRC of the uncompressed data
public ushort length_lo; // lower 16 bits of length
public byte length_hi; // upper 8 bits of length
public byte flags; // flags, indicating compression info
}
public const int HeaderSize = 108;
public const uint Version = 4;
// V4-specific header values
public Flags flags; // flags (see above)
public Compression compression; // compression type
public uint totalhunks; // total # of hunks represented
public ulong logicalbytes; // logical size of the data (in bytes)
public ulong metaoffset; // offset to the first blob of metadata
public uint hunkbytes; // number of bytes per hunk
public byte[] sha1 = new byte[20]; // combined raw+meta SHA1
public byte[] parentsha1 = new byte[20]; // combined raw+meta SHA1 of parent
public byte[] rawsha1 = new byte[20]; // raw data SHA1
/// <summary>
/// Parse and validate the header as if it's V4
/// </summary>
public static CHDFileV4 Deserialize(Stream stream)
{
CHDFileV4 chd = new CHDFileV4();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
chd.tag = br.ReadCharsBigEndian(8);
chd.length = br.ReadUInt32BigEndian();
chd.version = br.ReadUInt32BigEndian();
chd.flags = (Flags)br.ReadUInt32BigEndian();
chd.compression = (Compression)br.ReadUInt32BigEndian();
chd.totalhunks = br.ReadUInt32BigEndian();
chd.logicalbytes = br.ReadUInt64BigEndian();
chd.metaoffset = br.ReadUInt64BigEndian();
chd.hunkbytes = br.ReadUInt32BigEndian();
chd.sha1 = br.ReadBytesBigEndian(20);
chd.parentsha1 = br.ReadBytesBigEndian(20);
chd.rawsha1 = br.ReadBytesBigEndian(20);
}
return chd;
}
/// <summary>
/// Return internal SHA1 hash
/// </summary>
public override byte[] GetHash()
{
return sha1;
}
}
}

View File

@@ -0,0 +1,98 @@
using System.IO;
using System.Text;
using SabreTools.Library.Tools;
namespace SabreTools.Library.FileTypes
{
/// <summary>
/// CHD V5 File
/// </summary>
public class CHDFileV5 : CHDFile
{
/// <summary>
/// Uncompressed map format
/// </summary>
private class UncompressedMap
{
public uint offset; // starting offset within the file
}
/// <summary>
/// Compressed map header format
/// </summary>
private class CompressedMapHeader
{
public uint length; // length of compressed map
public byte[] datastart = new byte[12]; // UINT48; offset of first block
public ushort crc; // crc-16 of the map
public byte lengthbits; // bits used to encode complength
public byte hunkbits; // bits used to encode self-refs
public byte parentunitbits; // bits used to encode parent unit refs
public byte reserved; // future use
}
/// <summary>
/// Compressed map entry format
/// </summary>
private class CompressedMapEntry
{
public byte compression; // compression type
public byte[] complength = new byte[6]; // UINT24; compressed length
public byte[] offset = new byte[12]; // UINT48; offset
public ushort crc; // crc-16 of the data
}
public const int HeaderSize = 124;
public const uint Version = 5;
// V5-specific header values
public uint[] compressors = new uint[4]; // which custom compressors are used?
public ulong logicalbytes; // logical size of the data (in bytes)
public ulong mapoffset; // offset to the map
public ulong metaoffset; // offset to the first blob of metadata
public uint hunkbytes; // number of bytes per hunk
public uint unitbytes; // number of bytes per unit within each hunk
public byte[] rawsha1 = new byte[20]; // raw data SHA1
public byte[] sha1 = new byte[20]; // combined raw+meta SHA1
public byte[] parentsha1 = new byte[20]; // combined raw+meta SHA1 of parent
/// <summary>
/// Parse and validate the header as if it's V5
/// </summary>
public static CHDFileV5 Deserialize(Stream stream)
{
CHDFileV5 chd = new CHDFileV5();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
chd.tag = br.ReadCharsBigEndian(8);
chd.length = br.ReadUInt32BigEndian();
chd.version = br.ReadUInt32BigEndian();
chd.compressors = new uint[4];
for (int i = 0; i < 4; i++)
{
chd.compressors[i] = br.ReadUInt32BigEndian();
}
chd.logicalbytes = br.ReadUInt64BigEndian();
chd.mapoffset = br.ReadUInt64BigEndian();
chd.metaoffset = br.ReadUInt64BigEndian();
chd.hunkbytes = br.ReadUInt32BigEndian();
chd.unitbytes = br.ReadUInt32BigEndian();
chd.rawsha1 = br.ReadBytesBigEndian(20);
chd.sha1 = br.ReadBytesBigEndian(20);
chd.parentsha1 = br.ReadBytesBigEndian(20);
}
return chd;
}
/// <summary>
/// Return internal SHA1 hash
/// </summary>
public override byte[] GetHash()
{
return sha1;
}
}
}

View File

@@ -1,340 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
/// <summary>
/// This code is based on the header format described at http://www.rarlab.com/technote.htm#srvheaders
/// </summary>
/// <remarks>
/// ---------------------------------------------
/// vint
///
/// Variable length integer. Can include one or more bytes, where lower 7 bits of every byte contain integer data
/// and highest bit in every byte is the continuation flag.If highest bit is 0, this is the last byte in sequence.
/// So first byte contains 7 least significant bits of integer and continuation flag. Second byte, if present,
/// contains next 7 bits and so on.
///
/// Currently RAR format uses vint to store up to 64 bit integers, resulting in 10 bytes maximum. This value may
/// be increased in the future if necessary for some reason.
///
/// Sometimes RAR needs to pre-allocate space for vint before knowing its exact value. In such situation it can
/// allocate more space than really necessary and then fill several leading bytes with 0x80 hexadecimal, which means
/// 0 with continuation flag set.
/// ----------------------------------------------
/// General archive layout:
///
/// Self-extracting module(optional) (RAR assumes the maximum SFX module size to not exceed 1 MB, but this value
/// can be increased in the future.
/// RAR 5.0 signature (RAR 5.0 signature consists of 8 bytes: 0x52 0x61 0x72 0x21 0x1A 0x07 0x01 0x00.
/// You need to search for this signature in supposed archive from beginning and up to maximum SFX
/// module size. Just for comparison this is RAR 4.x 7 byte length signature: 0x52 0x61 0x72 0x21 0x1A 0x07 0x00.)
/// Archive encryption header(optional)
/// Main archive header
/// Archive comment service header(optional)
/// File header 1
/// Service headers(NTFS ACL, streams, etc.) for preceding file(optional).
/// ...
/// File header N
/// Service headers(NTFS ACL, streams, etc.) for preceding file(optional).
/// Recovery record(optional).
/// End of archive header.
/// ----------------------------------------------
/// General archive block format:
///
/// Header CRC32: uint32 (CRC32 of header data starting from Header size field and up to and including the optional extra area.)
/// Header size: vint (Size of header data starting from Header type field and up to and including the optional extra area.
/// This field must not be longer than 3 bytes in current implementation, resulting in 2 MB maximum header size.)
/// Header type: vint (Type of archive header. Possible values are: )
/// 1 Main archive header.
/// 2 File header.
/// 3 Service header.
/// 4 Archive encryption header.
/// 5 End of archive header.
/// Header flags: vint (Flags common for all headers:)
/// 0x0001 Extra area is present in the end of header.
/// 0x0002 Data area is present in the end of header.
/// 0x0004 Blocks with unknown type and this flag must be skipped when updating an archive.
/// 0x0008 Data area is continuing from previous volume.
/// 0x0010 Data area is continuing in next volume.
/// 0x0020 Block depends on preceding file block.
/// 0x0040 Preserve a child block if host block is modified.
/// Extra area size: vint (Size of extra area. Optional field, present only if 0x0001 header flag is set.)
/// Data size: vint (Size of data area. Optional field, present only if 0x0002 header flag is set.)
/// ...: ... (Fields specific for current block type. See concrete block type descriptions for details)
/// Extra data: ... (Optional area containing additional header fields, present only if 0x0001 header flag is set.)
/// Data area: vint (Optional data area, present only if 0x0002 header flag is set. Used to store large data amounts, such as
/// compressed file data. Not counted in Header CRC and Header size fields.
/// ----------------------------------------------
/// General extra area format
///
/// Size: vint (Size of record data starting from Type.)
/// Type: vint (Record type. Different archive blocks have different associated extra area record types. Read the
/// concrete archive block description for details. New record types can be added in the future, so unknown
/// record types need to be skipped without interrupting an operation.)
/// Data: ... (Record dependent data. May be missing if record consists only from size and type.)
/// ----------------------------------------------
/// Archive encryption header:
///
/// Header CRC32: uint32
/// Header size: vint
/// Header type: vint (4)
/// Header flags: vint
/// Encryption version: vint (Version of encryption algorithm. Now only 0 version(AES-256) is supported.)
/// Encryption flags: vint
/// 0x0001 Password check data is present.
/// KDF count: 1 byte (Binary logarithm of iteration number for PBKDF2 function.RAR can refuse to process
/// KDF count exceeding some threshold. Concrete value of threshold is a version dependent.)
/// Salt: 16 bytes (Salt value used globally for all encrypted archive headers.)
/// Check value: 12 bytes (Value used to verify the password validity. Present only if 0x0001 encryption
/// flag is set.First 8 bytes are calculated using additional PBKDF2 rounds, 4 last bytes is the additional
/// checksum. Together with the standard header CRC32 we have 64 bit checksum to reliably verify this field
/// integrity and distinguish invalid password and damaged data. Further details can be found in UnRAR source code.)
/// ----------------------------------------------
/// Main archive header:
///
/// Header CRC32: uint32 (CRC32 of header data starting from Header size field and up to and including the optional extra area.)
/// Header size: vint (Size of header data starting from Header type field and up to and including the optional extra area. This field must not be longer than 3 bytes in current implementation, resulting in 2 MB maximum header size.)
/// Header type: vint (1)
/// Header flags: vint (Flags common for all headers)
/// Extra area size: vint (Size of extra area. Optional field, present only if 0x0001 header flag is set.)
/// Archive flags: vint
/// 0x0001 Volume.Archive is a part of multivolume set.
/// 0x0002 Volume number field is present.This flag is present in all volumes except first.
/// 0x0004 Solid archive.
/// 0x0008 Recovery record is present.
/// 0x0010 Locked archive.
/// Volume number: vint (Optional field, present only if 0x0002 archive flag is set. Not present for first volume,
/// 1 for second volume, 2 for third and so on.)
/// Extra area: ... (Optional area containing additional header fields, present only if 0x0001 header flag is set.)
/// [Extra area of main archive header can contain following record types
/// Type Name Description
/// 0x01 Locator Contains positions of different service blocks, so they can be accessed quickly, without scanning
/// the entire archive.This record is optional.If it is missing, it is still necessary to scan the entire archive
/// to verify presence of service blocks.]
/// ----------------------------------------------
/// </remarks>
namespace SabreTools.Library.FileTypes
{
public class CoreRarArchive : BaseArchive
{
// SFX Module Information
public byte[] SFX;
// Standard Header Information
public uint HeaderCRC32;
public uint HeaderSize; // vint
public RarHeaderFlags HeaderFlags; // vint
public uint ExtraAreaSize; // vint
public RarArchiveFlags ArchiveFlags; // vint
public uint VolumeNumber; // vint
public byte[] ExtraArea;
// Encryption Header Information
public uint EncryptionHeaderCRC32;
public uint EncryptionHeaderSize; // vint
public RarHeaderFlags EncryptionHeaderFlags; // vint
public uint EncryptionVersion; // vint
public uint EncryptionFlags; // vint
public byte KDFCount;
public byte[] Salt = new byte[16];
public byte[] CheckValue = new byte[12];
// Locator Information
public uint LocatorSize; // vint
public uint LocatorFlags; // vint
public uint QuickOpenOffset; // vint
public uint RecoveryRecordOffset; // vint
// Entry Information
public List<CoreRarArchiveEntry> Entries = new List<CoreRarArchiveEntry>();
#region Unimplemented methods
public override bool CopyAll(string outDir)
{
throw new NotImplementedException();
}
public override string CopyToFile(string entryName, string outDir)
{
throw new NotImplementedException();
}
public override (MemoryStream, string) CopyToStream(string entryName)
{
throw new NotImplementedException();
}
public override List<BaseFile> GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
{
throw new NotImplementedException();
}
public override List<string> GetEmptyFolders()
{
throw new NotImplementedException();
}
public override bool IsTorrent()
{
throw new NotImplementedException();
}
public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
{
throw new NotImplementedException();
}
public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
{
throw new NotImplementedException();
}
public override bool Write(List<string> inputFiles, string outDir, List<Rom> roms, bool date = false, bool romba = false)
{
throw new NotImplementedException();
}
#endregion
}
public class CoreRarArchiveEntry
{
// Standard Entry Information
public uint HeaderCRC32;
public uint HeaderSize; // vint
public RarHeaderType HeaderType; // vint
public RarHeaderFlags HeaderFlags; // vint
public uint ExtraAreaSize; // vint
public uint DataAreaSize; // vint
public RarFileFlags FileFlags; // vint
public uint UnpackedSize; // vint
public uint Attributes; // vint
public uint mtime;
public uint DataCRC32;
public uint CompressionInformation; // vint
public uint HostOS; // vint
public uint NameLength; // vint
public byte[] Name;
public byte[] DataArea;
// File Encryption Information
public uint EncryptionSize; // vint
public RarEncryptionFlags EncryptionFlags; // vint
public byte KDFCount;
public byte[] Salt = new byte[16];
public byte[] IV = new byte[16];
public byte[] CheckValue = new byte[12];
// File Hash Information
public uint HashSize; // vint
public uint HashType; // vint
public byte[] HashData = new byte[32];
// File Time Information
public uint TimeSize; // vint
public RarTimeFlags TimeFlags; // vint
public uint TimeMtime;
public ulong TimeMtime64;
public uint TimeCtime;
public ulong TimeCtime64;
public uint TimeLtime;
public ulong TimeLtime64;
// File Version Information
public uint VersionSize; // vint
public const uint VersionFlags = 0; // vint
public uint VersionNumber; // vint
// File System Redirection Record
public uint RedirectionSize; // vint
public RarRedirectionType RedirectionType; // vint
public uint RedirectionFlags; // vint
public uint RedirectionNameLength; // vint
public byte[] RedirectionName;
// Unix Owner Record
public uint UnixOwnerSize; // vint
public RarUnixOwnerRecordFlags UnixOwnerFlags; // vint
public uint UnixOwnerUserNameLength; // vint
public byte[] UnixOwnerUserName;
public uint UnixOwnerGroupNameLength; // vint
public byte[] UnixOwnerGroupName;
public uint UnixOwnerUserId; // vint
public uint UnixOwnerGroupId; // vint
// Service Data Information
public uint ServiceSize; // vint
public byte[] ServiceData;
}
// BELOW ARE CONCRETE IMPLEMENTATIONS OF HEADER DETAILS
/// <summary>
/// General archive block format used by all RAR block types
/// </summary>
public class GeneralArchiveBlockFormat
{
public uint HeaderCRC32;
public uint HeaderSize; // vint
public HeaderType HeaderType;
public HeaderFlags HeaderFlags;
public ulong ExtraAreaSize; // vint
public ulong DataAreaSize; // vint
public byte[] ExtraArea;
public byte[] DataArea;
}
/// <summary>
/// General extra area format used by all RAR extra area records
/// </summary>
public class GeneralExtraAreaFormat
{
public ulong Size; // vint
public ulong Type; // vint
public byte[] Data;
}
/// <summary>
/// Encryption header only present in encrypted archives
///
/// Every proceeding header is started from 16 byte AES-256
/// initialization vectors followed by encrypted header data
/// </summary>
public class ArchiveEncryptionHeader : GeneralArchiveBlockFormat
{
public new HeaderType HeaderType = HeaderType.ArchiveEncryptionHeader;
public ulong EncryptionVersion; // vint
public ulong EncryptionFlags; // vint
}
/// <summary>
/// Types of archive header
/// </summary>
public enum HeaderType : ulong // vint
{
MainArchiveHeader = 1,
FileHeader = 2,
ServiceHeader = 3,
ArchiveEncryptionHeader = 4,
EndOfArchiveHeader = 5,
}
/// <summary>
/// Flags common for all headers
/// </summary>
[Flags]
public enum HeaderFlags : ulong // vint
{
ExtraAreaIsPresentInEndOfHeader = 0x0001,
DataAreaIsPresentInEndOfHeader = 0x0002,
BlocksWithUnknownType = 0x0004, // this flag must be skipped when updating an archive
DataAreaIsContinuingFromPreviousVolume = 0x0008,
DataAreaIsContinuingInNextVolume = 0x0010,
BlockDependsOnPrecedingFileBlock = 0x0020,
PreserveChildBlockIfHostBlockIsModified = 0x0040,
}
}

View File

@@ -43,6 +43,55 @@ namespace SabreTools.Library.FileTypes
this.Type = FileType.Folder;
}
/// <summary>
/// Create an folder object of the specified type, if possible
/// </summary>
/// <param name="outputFormat">SabreTools.Library.Data.OutputFormat representing the archive to create</param>
/// <returns>Archive object representing the inputs</returns>
public static Folder Create(OutputFormat outputFormat)
{
switch (outputFormat)
{
case OutputFormat.Folder:
return new Folder();
case OutputFormat.TapeArchive:
return new TapeArchive();
case OutputFormat.Torrent7Zip:
return new SevenZipArchive();
case OutputFormat.TorrentGzip:
case OutputFormat.TorrentGzipRomba:
return new GZipArchive();
case OutputFormat.TorrentLRZip:
return new LRZipArchive();
case OutputFormat.TorrentLZ4:
return new LZ4Archive();
case OutputFormat.TorrentRar:
return new RarArchive();
case OutputFormat.TorrentXZ:
case OutputFormat.TorrentXZRomba:
return new XZArchive();
case OutputFormat.TorrentZip:
return new ZipArchive();
case OutputFormat.TorrentZPAQ:
return new ZPAQArchive();
case OutputFormat.TorrentZstd:
return new ZstdArchive();
default:
return null;
}
}
#endregion
#region Extraction
@@ -129,7 +178,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(outDir);
// Get all files from the input directory
List<string> files = Utilities.RetrieveFiles(this.Filename, new List<string>());
List<string> files = DirectoryExtensions.GetFilesOrdered(this.Filename);
// Now sort through to find the first file that matches
string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault();
@@ -168,7 +217,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(this.Filename);
// Get all files from the input directory
List<string> files = Utilities.RetrieveFiles(this.Filename, new List<string>());
List<string> files = DirectoryExtensions.GetFilesOrdered(this.Filename);
// Now sort through to find the first file that matches
string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault();
@@ -176,7 +225,7 @@ namespace SabreTools.Library.FileTypes
// If we had a file, copy that over to the new name
if (!string.IsNullOrWhiteSpace(match))
{
Utilities.TryOpenRead(match).CopyTo(ms);
FileExtensions.TryOpenRead(match).CopyTo(ms);
realentry = match;
}
}
@@ -207,7 +256,7 @@ namespace SabreTools.Library.FileTypes
_children = new List<BaseFile>();
foreach (string file in Directory.EnumerateFiles(this.Filename, "*", SearchOption.TopDirectoryOnly))
{
BaseFile nf = Utilities.GetFileInfo(file, omitFromScan: omitFromScan, date: date);
BaseFile nf = FileExtensions.GetInfo(file, omitFromScan: omitFromScan, date: date);
_children.Add(nf);
}
@@ -228,7 +277,7 @@ namespace SabreTools.Library.FileTypes
/// <returns>List of empty folders in the folder</returns>
public virtual List<string> GetEmptyFolders()
{
return Utilities.GetEmptyDirectories(this.Filename).ToList();
return DirectoryExtensions.ListEmpty(this.Filename);
}
#endregion
@@ -247,7 +296,7 @@ namespace SabreTools.Library.FileTypes
/// <remarks>This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.</remarks>
public virtual bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
{
FileStream fs = Utilities.TryOpenRead(inputFile);
FileStream fs = FileExtensions.TryOpenRead(inputFile);
return Write(fs, outDir, rom, date, romba);
}
@@ -281,7 +330,7 @@ namespace SabreTools.Library.FileTypes
FileStream outputStream = null;
// Get the output folder name from the first rebuild rom
string fileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName), Utilities.RemovePathUnsafeCharacters(rom.Name));
string fileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(rom.MachineName), Sanitizer.RemovePathUnsafeCharacters(rom.Name));
try
{
@@ -292,7 +341,7 @@ namespace SabreTools.Library.FileTypes
}
// Overwrite output files by default
outputStream = Utilities.TryCreate(fileName);
outputStream = FileExtensions.TryCreate(fileName);
// If the output stream isn't null
if (outputStream != null)

View File

@@ -60,7 +60,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(outDir);
// Decompress the _filename stream
FileStream outstream = Utilities.TryCreate(Path.Combine(outDir, Path.GetFileNameWithoutExtension(this.Filename)));
FileStream outstream = FileExtensions.TryCreate(Path.Combine(outDir, Path.GetFileNameWithoutExtension(this.Filename)));
var gz = new gZip();
ZipReturn ret = gz.ZipFileOpen(this.Filename);
ret = gz.ZipFileOpenReadStream(0, out Stream gzstream, out ulong streamSize);
@@ -110,7 +110,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
// Now open and write the file if possible
FileStream fs = Utilities.TryCreate(realEntry);
FileStream fs = FileExtensions.TryCreate(realEntry);
if (fs != null)
{
ms.Seek(0, SeekOrigin.Begin);
@@ -145,7 +145,7 @@ namespace SabreTools.Library.FileTypes
public override (MemoryStream, string) CopyToStream(string entryName)
{
MemoryStream ms = new MemoryStream();
string realEntry = null;
string realEntry;
try
{
@@ -215,11 +215,10 @@ namespace SabreTools.Library.FileTypes
{
Filename = gamename,
};
BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
BinaryReader br = new BinaryReader(FileExtensions.TryOpenRead(this.Filename));
br.BaseStream.Seek(-8, SeekOrigin.End);
byte[] headercrc = br.ReadBytesReverse(4);
tempRom.CRC = headercrc;
tempRom.Size = br.ReadInt32Reverse();
tempRom.CRC = br.ReadBytesBigEndian(4);
tempRom.Size = br.ReadInt32BigEndian();
br.Dispose();
_children.Add(tempRom);
@@ -230,7 +229,7 @@ namespace SabreTools.Library.FileTypes
var gz = new gZip();
ZipReturn ret = gz.ZipFileOpen(this.Filename);
ret = gz.ZipFileOpenReadStream(0, out Stream gzstream, out ulong streamSize);
BaseFile gzipEntryRom = Utilities.GetStreamInfo(gzstream, gzstream.Length, omitFromScan);
BaseFile gzipEntryRom = gzstream.GetInfo(omitFromScan: omitFromScan);
gzipEntryRom.Filename = gz.Filename(0);
gzipEntryRom.Parent = gamename;
gzipEntryRom.Date = (date && gz.TimeStamp > 0 ? gz.TimeStamp.ToString() : null);
@@ -267,9 +266,7 @@ namespace SabreTools.Library.FileTypes
{
// Check for the file existing first
if (!File.Exists(this.Filename))
{
return false;
}
string datum = Path.GetFileName(this.Filename).ToLowerInvariant();
long filesize = new FileInfo(this.Filename).Length;
@@ -296,15 +293,11 @@ namespace SabreTools.Library.FileTypes
}
// Get the Romba-specific header data
byte[] header; // Get preamble header for checking
byte[] headermd5; // MD5
byte[] headercrc; // CRC
ulong headersz; // Int64 size
BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
header = br.ReadBytes(12);
headermd5 = br.ReadBytes(16);
headercrc = br.ReadBytes(4);
headersz = br.ReadUInt64();
BinaryReader br = new BinaryReader(FileExtensions.TryOpenRead(this.Filename));
byte[] header = br.ReadBytes(12); // Get preamble header for checking
br.ReadBytes(16); // headermd5
br.ReadBytes(4); // headercrc
br.ReadUInt64(); // headersz
br.Dispose();
// If the header is not correct, return a blank rom
@@ -313,15 +306,13 @@ namespace SabreTools.Library.FileTypes
{
// This is a temp fix to ignore the modification time and OS until romba can be fixed
if (i == 4 || i == 5 || i == 6 || i == 7 || i == 9)
{
continue;
}
correct &= (header[i] == Constants.TorrentGZHeader[i]);
}
if (!correct)
{
return false;
}
return true;
}
@@ -367,7 +358,7 @@ namespace SabreTools.Library.FileTypes
byte[] headermd5; // MD5
byte[] headercrc; // CRC
ulong headersz; // Int64 size
BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
BinaryReader br = new BinaryReader(FileExtensions.TryOpenRead(this.Filename));
header = br.ReadBytes(12);
headermd5 = br.ReadBytes(16);
headercrc = br.ReadBytes(4);
@@ -429,10 +420,11 @@ namespace SabreTools.Library.FileTypes
Globals.Logger.Warning($"File '{inputFile}' does not exist!");
return false;
}
inputFile = Path.GetFullPath(inputFile);
// Get the file stream for the file and write out
return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date, romba);
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date, romba);
}
/// <summary>
@@ -451,27 +443,24 @@ namespace SabreTools.Library.FileTypes
// If the stream is not readable, return
if (!inputStream.CanRead)
{
return success;
}
// Make sure the output directory exists
if (!Directory.Exists(outDir))
{
Directory.CreateDirectory(outDir);
}
outDir = Path.GetFullPath(outDir);
// Now get the Rom info for the file so we have hashes and size
rom = new Rom(Utilities.GetStreamInfo(inputStream, inputStream.Length, keepReadOpen: true));
rom = new Rom(inputStream.GetInfo(keepReadOpen: true));
// Get the output file name
string outfile = null;
string outfile;
// If we have a romba output, add the romba path
if (romba)
{
outfile = Path.Combine(outDir, Utilities.GetRombaPath(rom.SHA1)); // TODO: When updating to SHA-256, this needs to update to SHA256
outfile = Path.Combine(outDir, PathExtensions.GetRombaPath(rom.SHA1)); // TODO: When updating to SHA-256, this needs to update to SHA256
// Check to see if the folder needs to be created
if (!Directory.Exists(Path.GetDirectoryName(outfile)))
@@ -489,7 +478,7 @@ namespace SabreTools.Library.FileTypes
if (!File.Exists(outfile))
{
// Compress the input stream
FileStream outputStream = Utilities.TryCreate(outfile);
FileStream outputStream = FileExtensions.TryCreate(outfile);
// Open the output file for writing
BinaryWriter sw = new BinaryWriter(outputStream);

View File

@@ -104,7 +104,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
// Now open and write the file if possible
FileStream fs = Utilities.TryCreate(realEntry);
FileStream fs = FileExtensions.TryCreate(realEntry);
if (fs != null)
{
ms.Seek(0, SeekOrigin.Begin);
@@ -183,7 +183,7 @@ namespace SabreTools.Library.FileTypes
try
{
SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(Utilities.TryOpenRead(this.Filename));
SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(FileExtensions.TryOpenRead(this.Filename));
foreach (RarArchiveEntry entry in ra.Entries.Where(e => e != null && !e.IsDirectory))
{
// If secure hashes are disabled, do a quickscan
@@ -203,7 +203,7 @@ namespace SabreTools.Library.FileTypes
else
{
Stream entryStream = entry.OpenEntryStream();
BaseFile rarEntryRom = Utilities.GetStreamInfo(entryStream, entry.Size, omitFromScan);
BaseFile rarEntryRom = entryStream.GetInfo(entry.Size, omitFromScan);
rarEntryRom.Filename = entry.Key;
rarEntryRom.Parent = gamename;
rarEntryRom.Date = entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss");
@@ -224,223 +224,6 @@ namespace SabreTools.Library.FileTypes
return found;
}
/// <summary>
/// (INCOMPLETE) Retrieve file information for a RAR file
/// </summary>
/// TODO: Write the rest of this RAR file handling
public void GetRarFileInfo()
{
if (!File.Exists(this.Filename))
{
return;
}
BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
// Check for the signature first (Skipping the SFX Module)
byte[] signature = br.ReadBytes(8);
int startpos = 0;
while (startpos < Constants.MibiByte && !signature.StartsWith(Constants.RarSignature, exact: true) && !signature.StartsWith(Constants.RarFiveSignature, exact: true))
{
startpos++;
br.BaseStream.Position = startpos;
signature = br.ReadBytes(8);
}
if (!signature.StartsWith(Constants.RarSignature, exact: true) && !signature.StartsWith(Constants.RarFiveSignature, exact: true))
{
return;
}
CoreRarArchive cra = new CoreRarArchive();
if (startpos > 0)
{
br.BaseStream.Position = 0;
cra.SFX = br.ReadBytes(startpos);
}
// Get all archive header information
cra.HeaderCRC32 = br.ReadUInt32();
cra.HeaderSize = br.ReadUInt32();
uint headerType = br.ReadUInt32();
// Special encryption information
bool hasEncryptionHeader = false;
// If it's encrypted
if (headerType == (uint)RarHeaderType.ArchiveEncryption)
{
hasEncryptionHeader = true;
cra.EncryptionHeaderCRC32 = cra.HeaderCRC32;
cra.EncryptionHeaderSize = cra.HeaderSize;
cra.EncryptionHeaderFlags = (RarHeaderFlags)br.ReadUInt32();
cra.EncryptionVersion = br.ReadUInt32();
cra.EncryptionFlags = br.ReadUInt32();
cra.KDFCount = br.ReadByte();
cra.Salt = br.ReadBytes(16);
cra.CheckValue = br.ReadBytes(12);
cra.HeaderCRC32 = br.ReadUInt32();
cra.HeaderSize = br.ReadUInt32();
headerType = br.ReadUInt32();
}
cra.HeaderFlags = (RarHeaderFlags)br.ReadUInt32();
if ((cra.HeaderFlags & RarHeaderFlags.ExtraAreaPresent) != 0)
{
cra.ExtraAreaSize = br.ReadUInt32();
}
cra.ArchiveFlags = (RarArchiveFlags)br.ReadUInt32();
if ((cra.ArchiveFlags & RarArchiveFlags.VolumeNumberField) != 0)
{
cra.VolumeNumber = br.ReadUInt32();
}
if (((cra.HeaderFlags & RarHeaderFlags.ExtraAreaPresent) != 0) && cra.ExtraAreaSize != 0)
{
cra.ExtraArea = br.ReadBytes((int)cra.ExtraAreaSize);
}
// Archive Comment Service Header
// Now for file headers
for (; ; )
{
CoreRarArchiveEntry crae = new CoreRarArchiveEntry();
crae.HeaderCRC32 = br.ReadUInt32();
crae.HeaderSize = br.ReadUInt32();
crae.HeaderType = (RarHeaderType)br.ReadUInt32();
if (crae.HeaderType == RarHeaderType.EndOfArchive)
{
break;
}
crae.HeaderFlags = (RarHeaderFlags)br.ReadUInt32();
if ((crae.HeaderFlags & RarHeaderFlags.ExtraAreaPresent) != 0)
{
crae.ExtraAreaSize = br.ReadUInt32();
}
if ((crae.HeaderFlags & RarHeaderFlags.DataAreaPresent) != 0)
{
crae.DataAreaSize = br.ReadUInt32();
}
crae.FileFlags = (RarFileFlags)br.ReadUInt32();
crae.UnpackedSize = br.ReadUInt32();
if ((crae.FileFlags & RarFileFlags.UnpackedSizeUnknown) != 0)
{
crae.UnpackedSize = 0;
}
crae.Attributes = br.ReadUInt32();
crae.mtime = br.ReadUInt32();
crae.DataCRC32 = br.ReadUInt32();
crae.CompressionInformation = br.ReadUInt32();
crae.HostOS = br.ReadUInt32();
crae.NameLength = br.ReadUInt32();
crae.Name = br.ReadBytes((int)crae.NameLength);
if ((crae.HeaderFlags & RarHeaderFlags.ExtraAreaPresent) != 0)
{
uint extraSize = br.ReadUInt32();
switch (br.ReadUInt32()) // Extra Area Type
{
case 0x01: // File encryption information
crae.EncryptionSize = extraSize;
crae.EncryptionFlags = (RarEncryptionFlags)br.ReadUInt32();
crae.KDFCount = br.ReadByte();
crae.Salt = br.ReadBytes(16);
crae.IV = br.ReadBytes(16);
crae.CheckValue = br.ReadBytes(12);
break;
case 0x02: // File data hash
crae.HashSize = extraSize;
crae.HashType = br.ReadUInt32();
crae.HashData = br.ReadBytes(32);
break;
case 0x03: // High precision file time
crae.TimeSize = extraSize;
crae.TimeFlags = (RarTimeFlags)br.ReadUInt32();
if ((crae.TimeFlags & RarTimeFlags.TimeInUnixFormat) != 0)
{
if ((crae.TimeFlags & RarTimeFlags.ModificationTimePresent) != 0)
{
crae.TimeMtime64 = br.ReadUInt64();
}
if ((crae.TimeFlags & RarTimeFlags.CreationTimePresent) != 0)
{
crae.TimeCtime64 = br.ReadUInt64();
}
if ((crae.TimeFlags & RarTimeFlags.LastAccessTimePresent) != 0)
{
crae.TimeLtime64 = br.ReadUInt64();
}
}
else
{
if ((crae.TimeFlags & RarTimeFlags.ModificationTimePresent) != 0)
{
crae.TimeMtime = br.ReadUInt32();
}
if ((crae.TimeFlags & RarTimeFlags.CreationTimePresent) != 0)
{
crae.TimeCtime = br.ReadUInt32();
}
if ((crae.TimeFlags & RarTimeFlags.LastAccessTimePresent) != 0)
{
crae.TimeLtime = br.ReadUInt32();
}
}
break;
case 0x04: // File version number
crae.VersionSize = extraSize;
/* crae.VersionFlags = */
br.ReadUInt32();
crae.VersionNumber = br.ReadUInt32();
break;
case 0x05: // File system redirection
crae.RedirectionSize = extraSize;
crae.RedirectionType = (RarRedirectionType)br.ReadUInt32();
crae.RedirectionFlags = br.ReadUInt32();
crae.RedirectionNameLength = br.ReadUInt32();
crae.RedirectionName = br.ReadBytes((int)crae.RedirectionNameLength);
break;
case 0x06: // Unix owner and group information
crae.UnixOwnerSize = extraSize;
crae.UnixOwnerFlags = (RarUnixOwnerRecordFlags)br.ReadUInt32();
if ((crae.UnixOwnerFlags & RarUnixOwnerRecordFlags.UserNameStringIsPresent) != 0)
{
crae.UnixOwnerUserNameLength = br.ReadUInt32();
crae.UnixOwnerUserName = br.ReadBytes((int)crae.UnixOwnerUserNameLength);
}
if ((crae.UnixOwnerFlags & RarUnixOwnerRecordFlags.GroupNameStringIsPresent) != 0)
{
crae.UnixOwnerGroupNameLength = br.ReadUInt32();
crae.UnixOwnerGroupName = br.ReadBytes((int)crae.UnixOwnerGroupNameLength);
}
if ((crae.UnixOwnerFlags & RarUnixOwnerRecordFlags.NumericUserIdIsPresent) != 0)
{
crae.UnixOwnerUserId = br.ReadUInt32();
}
if ((crae.UnixOwnerFlags & RarUnixOwnerRecordFlags.NumericGroupIdIsPresent) != 0)
{
crae.UnixOwnerGroupId = br.ReadUInt32();
}
break;
case 0x07: // Service header data array
break;
}
}
if ((crae.HeaderFlags & RarHeaderFlags.DataAreaPresent) != 0)
{
crae.DataArea = br.ReadBytes((int)crae.DataAreaSize);
}
}
}
/// <summary>
/// Generate a list of empty folders in an archive
/// </summary>
@@ -505,7 +288,7 @@ namespace SabreTools.Library.FileTypes
public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
{
// Get the file stream for the file and write out
return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date, romba);
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date, romba);
}
/// <summary>

View File

@@ -85,7 +85,7 @@ namespace SabreTools.Library.FileTypes
continue;
}
FileStream writeStream = Utilities.TryCreate(Path.Combine(outDir, zf.Filename(i)));
FileStream writeStream = FileExtensions.TryCreate(Path.Combine(outDir, zf.Filename(i)));
// If the stream is smaller than the buffer, just run one loop through to avoid issues
if (streamsize < _bufferSize)
@@ -152,7 +152,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
// Now open and write the file if possible
FileStream fs = Utilities.TryCreate(realEntry);
FileStream fs = FileExtensions.TryCreate(realEntry);
if (fs != null)
{
ms.Seek(0, SeekOrigin.Begin);
@@ -314,7 +314,7 @@ namespace SabreTools.Library.FileTypes
// Otherwise, use the stream directly
else
{
BaseFile zipEntryRom = Utilities.GetStreamInfo(readStream, (long)zf.UncompressedSize(i), omitFromScan, true);
BaseFile zipEntryRom = readStream.GetInfo((long)zf.UncompressedSize(i), omitFromScan, true);
zipEntryRom.Filename = zf.Filename(i);
zipEntryRom.Parent = gamename;
found.Add(zipEntryRom);
@@ -417,7 +417,7 @@ namespace SabreTools.Library.FileTypes
public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
{
// Get the file stream for the file and write out
return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date: date);
}
/// <summary>
@@ -450,7 +450,7 @@ namespace SabreTools.Library.FileTypes
inputStream.Seek(0, SeekOrigin.Begin);
// Get the output archive name from the first rebuild rom
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
// Set internal variables
Stream writeStream = null;
@@ -615,7 +615,7 @@ namespace SabreTools.Library.FileTypes
// If the old file exists, delete it and replace
if (File.Exists(archiveFileName))
{
Utilities.TryDeleteFile(archiveFileName);
FileExtensions.TryDelete(archiveFileName);
}
File.Move(tempFile, archiveFileName);
@@ -658,7 +658,7 @@ namespace SabreTools.Library.FileTypes
}
// Get the output archive name from the first rebuild rom
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
// Set internal variables
Stream writeStream = null;
@@ -697,7 +697,7 @@ namespace SabreTools.Library.FileTypes
int index = inputIndexMap[key];
// Open the input file for reading
Stream freadStream = Utilities.TryOpenRead(inputFiles[index]);
Stream freadStream = FileExtensions.TryOpenRead(inputFiles[index]);
ulong istreamSize = (ulong)(new FileInfo(inputFiles[index]).Length);
DateTime dt = DateTime.Now;
@@ -780,7 +780,7 @@ namespace SabreTools.Library.FileTypes
if (index < 0)
{
// Open the input file for reading
Stream freadStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
Stream freadStream = FileExtensions.TryOpenRead(inputFiles[-index - 1]);
ulong istreamSize = (ulong)(new FileInfo(inputFiles[-index - 1]).Length);
DateTime dt = DateTime.Now;
@@ -842,7 +842,7 @@ namespace SabreTools.Library.FileTypes
// If the old file exists, delete it and replace
if (File.Exists(archiveFileName))
{
Utilities.TryDeleteFile(archiveFileName);
FileExtensions.TryDelete(archiveFileName);
}
File.Move(tempFile, archiveFileName);

View File

@@ -108,7 +108,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
// Now open and write the file if possible
FileStream fs = Utilities.TryCreate(realEntry);
FileStream fs = FileExtensions.TryCreate(realEntry);
if (fs != null)
{
ms.Seek(0, SeekOrigin.Begin);
@@ -187,7 +187,7 @@ namespace SabreTools.Library.FileTypes
try
{
TarArchive ta = TarArchive.Open(Utilities.TryOpenRead(this.Filename));
TarArchive ta = TarArchive.Open(FileExtensions.TryOpenRead(this.Filename));
foreach (TarArchiveEntry entry in ta.Entries.Where(e => e != null && !e.IsDirectory))
{
// If secure hashes are disabled, do a quickscan
@@ -207,7 +207,7 @@ namespace SabreTools.Library.FileTypes
else
{
Stream entryStream = entry.OpenEntryStream();
BaseFile tarEntryRom = Utilities.GetStreamInfo(entryStream, entry.Size, omitFromScan);
BaseFile tarEntryRom = entryStream.GetInfo(entry.Size, omitFromScan);
tarEntryRom.Filename = entry.Key;
tarEntryRom.Parent = gamename;
tarEntryRom.Date = entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss");
@@ -292,7 +292,7 @@ namespace SabreTools.Library.FileTypes
public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
{
// Get the file stream for the file and write out
return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date: date);
}
/// <summary>
@@ -322,7 +322,7 @@ namespace SabreTools.Library.FileTypes
}
// Get the output archive name from the first rebuild rom
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".tar") ? string.Empty : ".tar"));
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".tar") ? string.Empty : ".tar"));
// Set internal variables
TarArchive oldTarFile = TarArchive.Create();
@@ -441,7 +441,7 @@ namespace SabreTools.Library.FileTypes
// If the old file exists, delete it and replace
if (File.Exists(archiveFileName))
{
Utilities.TryDeleteFile(archiveFileName);
FileExtensions.TryDelete(archiveFileName);
}
File.Move(tempFile, archiveFileName);
@@ -484,7 +484,7 @@ namespace SabreTools.Library.FileTypes
}
// Get the output archive name from the first rebuild rom
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".tar") ? string.Empty : ".tar"));
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".tar") ? string.Empty : ".tar"));
// Set internal variables
TarArchive oldTarFile = TarArchive.Create();
@@ -526,7 +526,7 @@ namespace SabreTools.Library.FileTypes
}
// Copy the input stream to the output
tarFile.AddEntry(roms[index].Name, Utilities.TryOpenRead(inputFiles[index]), size: roms[index].Size, modified: usableDate);
tarFile.AddEntry(roms[index].Name, FileExtensions.TryOpenRead(inputFiles[index]), size: roms[index].Size, modified: usableDate);
}
}
@@ -586,7 +586,7 @@ namespace SabreTools.Library.FileTypes
}
// Copy the input file to the output
tarFile.AddEntry(roms[-index - 1].Name, Utilities.TryOpenRead(inputFiles[-index - 1]), size: roms[-index - 1].Size, modified: usableDate);
tarFile.AddEntry(roms[-index - 1].Name, FileExtensions.TryOpenRead(inputFiles[-index - 1]), size: roms[-index - 1].Size, modified: usableDate);
}
// Otherwise, copy the file from the old archive
@@ -622,7 +622,7 @@ namespace SabreTools.Library.FileTypes
// If the old file exists, delete it and replace
if (File.Exists(archiveFileName))
{
Utilities.TryDeleteFile(archiveFileName);
FileExtensions.TryDelete(archiveFileName);
}
File.Move(tempFile, archiveFileName);

View File

@@ -1,23 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
using SabreTools.Library.Tools;
using Compress.ZipFile;
using SevenZip;
using SharpCompress.Archives;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Readers;
using SharpCompress.Compressors.Xz;
namespace SabreTools.Library.FileTypes
{
/// <summary>
/// Represents a TorrentXZ archive for reading and writing
/// </summary>
/// TODO: Wait for XZ write to be enabled by SevenZipSharp library
public class XZArchive : BaseArchive
{
#region Constructors
@@ -61,14 +56,16 @@ namespace SabreTools.Library.FileTypes
// Create the temp directory
Directory.CreateDirectory(outDir);
// Extract all files to the temp directory
SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(Utilities.TryOpenRead(this.Filename));
foreach (SevenZipArchiveEntry entry in sza.Entries)
{
entry.WriteToDirectory(outDir, new SharpCompress.Common.ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
}
// Decompress the _filename stream
FileStream outstream = FileExtensions.TryCreate(Path.Combine(outDir, Path.GetFileNameWithoutExtension(this.Filename)));
var xz = new XZStream(File.OpenRead(this.Filename));
xz.CopyTo(outstream);
// Dispose of the streams
outstream.Dispose();
xz.Dispose();
encounteredErrors = false;
sza.Dispose();
}
catch (EndOfStreamException)
{
@@ -107,7 +104,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
// Now open and write the file if possible
FileStream fs = Utilities.TryCreate(realEntry);
FileStream fs = FileExtensions.TryCreate(realEntry);
if (fs != null)
{
ms.Seek(0, SeekOrigin.Begin);
@@ -142,22 +139,26 @@ namespace SabreTools.Library.FileTypes
public override (MemoryStream, string) CopyToStream(string entryName)
{
MemoryStream ms = new MemoryStream();
string realEntry = null;
string realEntry;
try
{
SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false, });
foreach (SevenZipArchiveEntry entry in sza.Entries)
// Decompress the _filename stream
realEntry = Path.GetFileNameWithoutExtension(this.Filename);
var xz = new XZStream(File.OpenRead(this.Filename));
// Write the file out
byte[] xbuffer = new byte[_bufferSize];
int xlen;
while ((xlen = xz.Read(xbuffer, 0, _bufferSize)) > 0)
{
if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
{
// Write the file out
realEntry = entry.Key;
entry.WriteTo(ms);
break;
}
ms.Write(xbuffer, 0, xlen);
ms.Flush();
}
sza.Dispose();
// Dispose of the streams
xz.Dispose();
}
catch (Exception ex)
{
@@ -182,7 +183,58 @@ namespace SabreTools.Library.FileTypes
/// <remarks>TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually</remarks>
public override List<BaseFile> GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
{
throw new NotImplementedException();
if (_children == null || _children.Count == 0)
{
_children = new List<BaseFile>();
string gamename = Path.GetFileNameWithoutExtension(this.Filename);
BaseFile possibleTxz = GetTorrentXZFileInfo();
// If it was, then add it to the outputs and continue
if (possibleTxz != null && possibleTxz.Filename != null)
{
_children.Add(possibleTxz);
}
else
{
try
{
// If secure hashes are disabled, do a quickscan
if (omitFromScan == Hash.SecureHashes)
{
BaseFile tempRom = new BaseFile()
{
Filename = gamename,
};
BinaryReader br = new BinaryReader(FileExtensions.TryOpenRead(this.Filename));
br.BaseStream.Seek(-8, SeekOrigin.End);
tempRom.CRC = br.ReadBytesBigEndian(4);
tempRom.Size = br.ReadInt32BigEndian();
br.Dispose();
_children.Add(tempRom);
}
// Otherwise, use the stream directly
else
{
var xzStream = new XZStream(File.OpenRead(this.Filename));
BaseFile xzEntryRom = xzStream.GetInfo(omitFromScan: omitFromScan);
xzEntryRom.Filename = gamename;
xzEntryRom.Parent = gamename;
_children.Add(xzEntryRom);
xzStream.Dispose();
}
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return null;
}
}
}
return _children;
}
/// <summary>
@@ -192,7 +244,8 @@ namespace SabreTools.Library.FileTypes
/// <returns>List of empty folders in the archive</returns>
public override List<string> GetEmptyFolders()
{
throw new NotImplementedException();
// XZ files don't contain directories
return new List<string>();
}
/// <summary>
@@ -200,7 +253,50 @@ namespace SabreTools.Library.FileTypes
/// </summary>
public override bool IsTorrent()
{
throw new NotImplementedException();
// Check for the file existing first
if (!File.Exists(this.Filename))
return false;
string datum = Path.GetFileName(this.Filename).ToLowerInvariant();
// Check if the name is the right length
if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.xz")) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
{
Globals.Logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(this.Filename)}'");
return false;
}
return true;
}
/// <summary>
/// Retrieve file information for a single torrent XZ file
/// </summary>
/// <returns>Populated DatItem object if success, empty one on error</returns>
public BaseFile GetTorrentXZFileInfo()
{
// Check for the file existing first
if (!File.Exists(this.Filename))
return null;
string datum = Path.GetFileName(this.Filename).ToLowerInvariant();
// Check if the name is the right length
if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.xz")) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
{
Globals.Logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(this.Filename)}'");
return null;
}
BaseFile baseFile = new BaseFile
{
Filename = Path.GetFileNameWithoutExtension(this.Filename).ToLowerInvariant(),
SHA1 = Utilities.StringToByteArray(Path.GetFileNameWithoutExtension(this.Filename)), // TODO: When updating to SHA-256, this needs to update to SHA256
Parent = Path.GetFileNameWithoutExtension(this.Filename).ToLowerInvariant(),
};
return baseFile;
}
#endregion
@@ -219,8 +315,17 @@ namespace SabreTools.Library.FileTypes
/// <remarks>This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.</remarks>
public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
{
// Check that the input file exists
if (!File.Exists(inputFile))
{
Globals.Logger.Warning($"File '{inputFile}' does not exist!");
return false;
}
inputFile = Path.GetFullPath(inputFile);
// Get the file stream for the file and write out
return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date, romba);
}
/// <summary>
@@ -235,185 +340,48 @@ namespace SabreTools.Library.FileTypes
public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
{
bool success = false;
string tempFile = Path.Combine(outDir, $"tmp{Guid.NewGuid()}");
// If either input is null or empty, return
if (inputStream == null || rom == null || rom.Name == null)
{
return success;
}
// If the stream is not readable, return
if (!inputStream.CanRead)
{
return success;
// Make sure the output directory exists
if (!Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
outDir = Path.GetFullPath(outDir);
// Now get the Rom info for the file so we have hashes and size
rom = new Rom(inputStream.GetInfo(keepReadOpen: true));
// Get the output file name
string outfile;
// If we have a romba output, add the romba path
if (romba)
{
outfile = Path.Combine(outDir, PathExtensions.GetRombaPath(rom.SHA1)); // TODO: When updating to SHA-256, this needs to update to SHA256
// Check to see if the folder needs to be created
if (!Directory.Exists(Path.GetDirectoryName(outfile)))
Directory.CreateDirectory(Path.GetDirectoryName(outfile));
}
// Otherwise, we're just rebuilding to the main directory
else
{
outfile = Path.Combine(outDir, rom.SHA1 + ".xz"); // TODO: When updating to SHA-256, this needs to update to SHA256
}
// Seek to the beginning of the stream
inputStream.Seek(0, SeekOrigin.Begin);
// Get the output archive name from the first rebuild rom
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".xz") ? string.Empty : ".xz"));
// Set internal variables
SevenZipBase.SetLibraryPath("7za.dll");
SevenZipExtractor oldZipFile = null;
SevenZipCompressor zipFile;
try
// If the output file exists, don't try to write again
if (!File.Exists(outfile))
{
// If the full output path doesn't exist, create it
if (!Directory.Exists(Path.GetDirectoryName(tempFile)))
{
Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
}
// Compress the input stream
XZStream outputStream = new XZStream(FileExtensions.TryCreate(outfile));
inputStream.CopyTo(outputStream);
// If the archive doesn't exist, create it and put the single file
if (!File.Exists(archiveFileName))
{
zipFile = new SevenZipCompressor()
{
ArchiveFormat = OutArchiveFormat.XZ,
CompressionLevel = CompressionLevel.Normal,
};
// Create the temp directory
string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
if (!Directory.Exists(tempPath))
{
Directory.CreateDirectory(tempPath);
}
// Create a stream dictionary
Dictionary<string, Stream> dict = new Dictionary<string, Stream>();
dict.Add(rom.Name, inputStream);
// Now add the stream
zipFile.CompressStreamDictionary(dict, tempFile);
}
// Otherwise, sort the input files and write out in the correct order
else
{
// Open the old archive for reading
using (oldZipFile = new SevenZipExtractor(archiveFileName))
{
// Map all inputs to index
Dictionary<string, int> inputIndexMap = new Dictionary<string, int>();
// If the old one doesn't contain the new file, then add it
if (!oldZipFile.ArchiveFileNames.Contains(rom.Name.Replace('\\', '/')))
{
inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
}
// Then add all of the old entries to it too
for (int i = 0; i < oldZipFile.FilesCount; i++)
{
inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
}
// If the number of entries is the same as the old archive, skip out
if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
{
success = true;
return success;
}
// Otherwise, process the old zipfile
zipFile = new SevenZipCompressor()
{
ArchiveFormat = OutArchiveFormat.XZ,
CompressionLevel = CompressionLevel.Normal,
};
// Get the order for the entries with the new file
List<string> keys = inputIndexMap.Keys.ToList();
keys.Sort(ZipFile.TrrntZipStringCompare);
// Copy over all files to the new archive
foreach (string key in keys)
{
// Get the index mapped to the key
int index = inputIndexMap[key];
// If we have the input file, add it now
if (index < 0)
{
// Create a stream dictionary
Dictionary<string, Stream> dict = new Dictionary<string, Stream>();
dict.Add(rom.Name, inputStream);
// Now add the stream
zipFile.CompressStreamDictionary(dict, tempFile);
}
// Otherwise, copy the file from the old archive
else
{
Stream oldZipFileEntryStream = new MemoryStream();
oldZipFile.ExtractFile(index, oldZipFileEntryStream);
oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
// Create a stream dictionary
Dictionary<string, Stream> dict = new Dictionary<string, Stream>();
dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
// Now add the stream
zipFile.CompressStreamDictionary(dict, tempFile);
oldZipFileEntryStream.Dispose();
}
// After the first file, make sure we're in append mode
zipFile.CompressionMode = CompressionMode.Append;
}
}
}
success = true;
}
catch (Exception ex)
{
Console.WriteLine(ex);
success = false;
}
finally
{
inputStream?.Dispose();
}
// If the old file exists, delete it and replace
if (File.Exists(archiveFileName))
{
Utilities.TryDeleteFile(archiveFileName);
}
File.Move(tempFile, archiveFileName);
// Now make the file T7Z
// TODO: Add ACTUAL T7Z compatible code
BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
bw.Seek(0, SeekOrigin.Begin);
bw.Write(Constants.Torrent7ZipHeader);
bw.Seek(0, SeekOrigin.End);
using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
{
// Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
byte[] tempsig = Constants.Torrent7ZipSignature;
if (oldZipFile.FilesCount > 1)
{
tempsig[16] = 0x2;
}
else
{
tempsig[16] = 0;
}
bw.Write(tempsig);
bw.Flush();
bw.Dispose();
// Dispose of everything
outputStream.Dispose();
inputStream.Dispose();
}
return true;
@@ -430,216 +398,7 @@ namespace SabreTools.Library.FileTypes
/// <returns>True if the archive was written properly, false otherwise</returns>
public override bool Write(List<string> inputFiles, string outDir, List<Rom> roms, bool date = false, bool romba = false)
{
bool success = false;
string tempFile = Path.Combine(outDir, $"tmp{Guid.NewGuid()}");
// If either list of roms is null or empty, return
if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
{
return success;
}
// If the number of inputs is less than the number of available roms, return
if (inputFiles.Count < roms.Count)
{
return success;
}
// If one of the files doesn't exist, return
foreach (string file in inputFiles)
{
if (!File.Exists(file))
{
return success;
}
}
// Get the output archive name from the first rebuild rom
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".xz") ? string.Empty : ".xz"));
// Set internal variables
SevenZipBase.SetLibraryPath("7za.dll");
SevenZipExtractor oldZipFile;
SevenZipCompressor zipFile;
try
{
// If the full output path doesn't exist, create it
if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
{
Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
}
// If the archive doesn't exist, create it and put the single file
if (!File.Exists(archiveFileName))
{
zipFile = new SevenZipCompressor()
{
ArchiveFormat = OutArchiveFormat.XZ,
CompressionLevel = CompressionLevel.Normal,
};
// Map all inputs to index
Dictionary<string, int> inputIndexMap = new Dictionary<string, int>();
for (int i = 0; i < inputFiles.Count; i++)
{
inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
}
// Sort the keys in TZIP order
List<string> keys = inputIndexMap.Keys.ToList();
keys.Sort(ZipFile.TrrntZipStringCompare);
// Create the temp directory
string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
if (!Directory.Exists(tempPath))
{
Directory.CreateDirectory(tempPath);
}
// Now add all of the files in order
foreach (string key in keys)
{
string newkey = Path.Combine(tempPath, key);
File.Move(inputFiles[inputIndexMap[key]], newkey);
zipFile.CompressFiles(tempFile, newkey);
File.Move(newkey, inputFiles[inputIndexMap[key]]);
// After the first file, make sure we're in append mode
zipFile.CompressionMode = CompressionMode.Append;
}
Utilities.CleanDirectory(tempPath);
Utilities.TryDeleteDirectory(tempPath);
}
// Otherwise, sort the input files and write out in the correct order
else
{
// Open the old archive for reading
using (oldZipFile = new SevenZipExtractor(archiveFileName))
{
// Map all inputs to index
Dictionary<string, int> inputIndexMap = new Dictionary<string, int>();
for (int i = 0; i < inputFiles.Count; i++)
{
// If the old one contains the new file, then just skip out
if (oldZipFile.ArchiveFileNames.Contains(roms[i].Name.Replace('\\', '/')))
{
continue;
}
inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), -(i + 1));
}
// Then add all of the old entries to it too
for (int i = 0; i < oldZipFile.FilesCount; i++)
{
inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
}
// If the number of entries is the same as the old archive, skip out
if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
{
success = true;
return success;
}
// Otherwise, process the old zipfile
zipFile = new SevenZipCompressor()
{
ArchiveFormat = OutArchiveFormat.XZ,
CompressionLevel = CompressionLevel.Normal,
};
// Get the order for the entries with the new file
List<string> keys = inputIndexMap.Keys.ToList();
keys.Sort(ZipFile.TrrntZipStringCompare);
// Copy over all files to the new archive
foreach (string key in keys)
{
// Get the index mapped to the key
int index = inputIndexMap[key];
// If we have the input file, add it now
if (index < 0)
{
FileStream inputStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
// Create a stream dictionary
Dictionary<string, Stream> dict = new Dictionary<string, Stream>();
dict.Add(key, inputStream);
// Now add the stream
zipFile.CompressStreamDictionary(dict, tempFile);
}
// Otherwise, copy the file from the old archive
else
{
Stream oldZipFileEntryStream = new MemoryStream();
oldZipFile.ExtractFile(index, oldZipFileEntryStream);
oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
// Create a stream dictionary
Dictionary<string, Stream> dict = new Dictionary<string, Stream>();
dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
// Now add the stream
zipFile.CompressStreamDictionary(dict, tempFile);
oldZipFileEntryStream.Dispose();
}
// After the first file, make sure we're in append mode
zipFile.CompressionMode = CompressionMode.Append;
}
}
}
success = true;
}
catch (Exception ex)
{
Console.WriteLine(ex);
success = false;
}
// If the old file exists, delete it and replace
if (File.Exists(archiveFileName))
{
Utilities.TryDeleteFile(archiveFileName);
}
File.Move(tempFile, archiveFileName);
// Now make the file T7Z
// TODO: Add ACTUAL T7Z compatible code
BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
bw.Seek(0, SeekOrigin.Begin);
bw.Write(Constants.Torrent7ZipHeader);
bw.Seek(0, SeekOrigin.End);
using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
{
// Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
byte[] tempsig = Constants.Torrent7ZipSignature;
if (oldZipFile.FilesCount > 1)
{
tempsig[16] = 0x2;
}
else
{
tempsig[16] = 0;
}
bw.Write(tempsig);
bw.Flush();
bw.Dispose();
}
return true;
throw new NotImplementedException();
}
#endregion

View File

@@ -86,7 +86,7 @@ namespace SabreTools.Library.FileTypes
continue;
}
FileStream writeStream = Utilities.TryCreate(Path.Combine(outDir, zf.Filename(i)));
FileStream writeStream = FileExtensions.TryCreate(Path.Combine(outDir, zf.Filename(i)));
// If the stream is smaller than the buffer, just run one loop through to avoid issues
if (streamsize < _bufferSize)
@@ -153,7 +153,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
// Now open and write the file if possible
FileStream fs = Utilities.TryCreate(realEntry);
FileStream fs = FileExtensions.TryCreate(realEntry);
if (fs != null)
{
ms.Seek(0, SeekOrigin.Begin);
@@ -317,7 +317,7 @@ namespace SabreTools.Library.FileTypes
// Otherwise, use the stream directly
else
{
BaseFile zipEntryRom = Utilities.GetStreamInfo(readStream, (long)zf.UncompressedSize(i), omitFromScan, true);
BaseFile zipEntryRom = readStream.GetInfo((long)zf.UncompressedSize(i), omitFromScan, true);
zipEntryRom.Filename = zf.Filename(i);
zipEntryRom.Parent = gamename;
string convertedDate = zf.LastModified(i).ToString("yyyy/MM/dd hh:mm:ss");
@@ -422,7 +422,7 @@ namespace SabreTools.Library.FileTypes
public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
{
// Get the file stream for the file and write out
return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date: date);
}
/// <summary>
@@ -455,7 +455,7 @@ namespace SabreTools.Library.FileTypes
inputStream.Seek(0, SeekOrigin.Begin);
// Get the output archive name from the first rebuild rom
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
// Set internal variables
Stream writeStream = null;
@@ -621,7 +621,7 @@ namespace SabreTools.Library.FileTypes
// If the old file exists, delete it and replace
if (File.Exists(archiveFileName))
{
Utilities.TryDeleteFile(archiveFileName);
FileExtensions.TryDelete(archiveFileName);
}
File.Move(tempFile, archiveFileName);
@@ -664,7 +664,7 @@ namespace SabreTools.Library.FileTypes
}
// Get the output archive name from the first rebuild rom
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
// Set internal variables
Stream writeStream = null;
@@ -703,7 +703,7 @@ namespace SabreTools.Library.FileTypes
int index = inputIndexMap[key];
// Open the input file for reading
Stream freadStream = Utilities.TryOpenRead(inputFiles[index]);
Stream freadStream = FileExtensions.TryOpenRead(inputFiles[index]);
ulong istreamSize = (ulong)(new FileInfo(inputFiles[index]).Length);
DateTime dt = DateTime.Now;
@@ -786,7 +786,7 @@ namespace SabreTools.Library.FileTypes
if (index < 0)
{
// Open the input file for reading
Stream freadStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
Stream freadStream = FileExtensions.TryOpenRead(inputFiles[-index - 1]);
ulong istreamSize = (ulong)(new FileInfo(inputFiles[-index - 1]).Length);
DateTime dt = DateTime.Now;
@@ -849,7 +849,7 @@ namespace SabreTools.Library.FileTypes
// If the old file exists, delete it and replace
if (File.Exists(archiveFileName))
{
Utilities.TryDeleteFile(archiveFileName);
FileExtensions.TryDelete(archiveFileName);
}
File.Move(tempFile, archiveFileName);

View File

@@ -1,10 +1,9 @@
using System;
using SabreTools.Library.Data;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SabreTools.Library.Data;
namespace SabreTools.Library.Help
{
public class Feature
@@ -42,8 +41,10 @@ namespace SabreTools.Library.Help
public Feature(string name, string flag, string description, FeatureType featureType, string longDescription = null)
{
this.Name = name;
this.Flags = new List<string>();
this.Flags.Add(flag);
this.Flags = new List<string>
{
flag
};
this.Description = description;
this.LongDescription = longDescription;
this._featureType = featureType;
@@ -91,7 +92,7 @@ namespace SabreTools.Library.Help
if (this.Features == null)
this.Features = new Dictionary<string, Feature>();
lock(this.Features)
lock (this.Features)
{
this.Features[feature.Name] = feature;
}
@@ -239,7 +240,11 @@ namespace SabreTools.Library.Help
output = CreatePadding(pre + 4);
}
#if NET_FRAMEWORK
output += subsplit[subsplit.Length - 1];
#else
output += subsplit[^1];
#endif
continue;
}
@@ -272,13 +277,7 @@ namespace SabreTools.Library.Help
/// <returns>String with requested number of blank spaces</returns>
private string CreatePadding(int spaces)
{
string blank = string.Empty;
for (int i = 0; i < spaces; i++)
{
blank += " ";
}
return blank;
return string.Empty.PadRight(spaces);
}
/// <summary>
@@ -379,7 +378,11 @@ namespace SabreTools.Library.Help
output = CreatePadding(preAdjusted + 4);
}
#if NET_FRAMEWORK
output += subsplit[subsplit.Length - 1];
#else
output += subsplit[^1];
#endif
continue;
}
@@ -527,7 +530,7 @@ namespace SabreTools.Library.Help
if (_featureType != FeatureType.Flag)
throw new ArgumentException("Feature is not a flag");
return (_value as bool?).HasValue ? (_value as bool?).Value : false;
return (_value as bool?) ?? false;
}
/// <summary>
@@ -549,7 +552,7 @@ namespace SabreTools.Library.Help
if (_featureType != FeatureType.Int32)
throw new ArgumentException("Feature is not an int");
return (_value as int?).HasValue ? (_value as int?).Value : int.MinValue;
return (_value as int?) ?? int.MinValue;
}
/// <summary>
@@ -560,7 +563,7 @@ namespace SabreTools.Library.Help
if (_featureType != FeatureType.Int64)
throw new ArgumentException("Feature is not a long");
return (_value as long?).HasValue ? (_value as long?).Value : long.MinValue;
return (_value as long?) ?? long.MinValue;
}
/// <summary>
@@ -580,6 +583,7 @@ namespace SabreTools.Library.Help
/// <returns>True if the feature is enabled, false otherwise</returns>
public bool IsEnabled()
{
#if NET_FRAMEWORK
switch (_featureType)
{
case FeatureType.Flag:
@@ -592,9 +596,20 @@ namespace SabreTools.Library.Help
return (_value as long?).HasValue && (_value as long?).Value != long.MinValue;
case FeatureType.List:
return (_value as List<string>) != null;
default:
return false;
}
return false;
#else
return _featureType switch
{
FeatureType.Flag => (_value as bool?) == true,
FeatureType.String => (_value as string) != null,
FeatureType.Int32 => (_value as int?).HasValue && (_value as int?).Value != int.MinValue,
FeatureType.Int64 => (_value as long?).HasValue && (_value as long?).Value != long.MinValue,
FeatureType.List => (_value as List<string>) != null,
_ => false,
};
#endif
}
#endregion

View File

@@ -179,6 +179,7 @@ Options:
-nr160, --skip-ripemd160 Don't include RIPEMD160 in output
This allows the user to skip calculating the RIPEMD160 for each of
the files which will speed up the creation of the DAT.
.NET Framework 4.8 only.
-ns, --skip-sha1 Don't include SHA-1 in output
This allows the user to skip calculating the SHA-1 for each of the
@@ -227,7 +228,7 @@ Options:
msx, openmsx - openMSX Software List
ol, offlinelist - OfflineList XML
rc, romcenter - RomCenter
ripemd160 - RIPEMD160
ripemd160 - RIPEMD160 (.NET Framework 4.8 only)
sd, sabredat - SabreDat XML
sfv - SFV
sha1 - SHA1
@@ -423,13 +424,13 @@ Options:
Include only items with this RIPEMD160 hash in the output.
Additionally, the user can specify an exact match or full C#-style
regex for pattern matching. Multiple instances of this flag are
allowed.
allowed. .NET Framework 4.8 only.
-nripemd160=, --not-ripemd160= Filter by not RIPEMD160 hash
Include only items without this RIPEMD160 hash in the output.
Additionally, the user can specify an exact match or full C#-style
regex for pattern matching. Multiple instances of this flag are
allowed.
allowed. .NET Framework 4.8 only
-sha1=, --sha1= Filter by SHA-1 hash
Include only items with this SHA-1 hash in the output. Additionally,
@@ -660,12 +661,11 @@ Options:
format but with custom header information. This is currently unused
by any major application.
-txz, --torrent-xz Enable Torrent XZ output [UNSUPPORTED]
-txz, --torrent-xz Enable Torrent XZ output [UNIMPLEMENTED]
Instead of outputting files to folder, files will be rebuilt to
Torrent XZ (TXZ) files. This format is based on the LZMA container
format XZ, but with custom header information. This is currently
unused by any major application. Currently does not produce proper
Torrent-compatible outputs.
format XZ, but with a file name replaced by the SHA-1 of the file
inside. This is currently unused by any major application.
-tzip, --torrent-zip Enable Torrent Zip output
Instead of outputting files to folder, files will be rebuilt to
@@ -694,21 +694,6 @@ Options:
or specific copier headers by name (such as "fds.xml") to determine
if a file matches or not.
-7z=, --7z= Set scanning level for 7zip archives (default 1)
-gz=, --gz= Set scanning level for GZip archives (default 1)
-rar=, --rar= Set scanning level for RAR archives (default 1)
-zip=, --zip= Set scanning level for Zip archives (default 1)
Scan archives in one of the following ways:
0 - Hash both archive and its contents
1 - Only hash contents of the archive
2 - Only hash archive itself (treat like a regular file)
-sa, --scan-all Set scanning levels for all archives to 0
This flag is the short equivalent to -7z=0 -gz=0 -rar=0 -zip=0
wrapped up. Generally this will be helpful in all cases where the
content of the rebuild folder is not entirely known or is known to be
mixed.
-dm, --dat-merged Force creating merged sets
Preprocess the DAT to have parent sets contain all items from the
children based on the cloneof tag. This is incompatible with the
@@ -766,7 +751,7 @@ Options:
msx, openmsx - openMSX Software List
ol, offlinelist - OfflineList XML
rc, romcenter - RomCenter
ripemd160 - RIPEMD160
ripemd160 - RIPEMD160 (.NET Framework 4.8 only)
sd, sabredat - SabreDat XML
sfv - SFV
sha1 - SHA1
@@ -941,7 +926,7 @@ Options:
msx, openmsx - openMSX Software List
ol, offlinelist - OfflineList XML
rc, romcenter - RomCenter
ripemd160 - RIPEMD160
ripemd160 - RIPEMD160 (.NET Framework 4.8 only)
sd, sabredat - SabreDat XML
sfv - SFV
sha1 - SHA1
@@ -967,7 +952,7 @@ Options:
- %publisher% - Replaced with game Publisher
- %crc% - Replaced with the CRC
- %md5% - Replaced with the MD5
- %ripemd160% - Replaced with the RIPEMD160
- %ripemd160% - Replaced with the RIPEMD160 (.NET Framework 4.8 only)
- %sha1% - Replaced with the SHA-1
- %sha256% - Replaced with the SHA-256
- %sha384% - Replaced with the SHA-384
@@ -1381,13 +1366,13 @@ Options:
Include only items with this RIPEMD160 hash in the output.
Additionally, the user can specify an exact match or full C#-style
regex for pattern matching. Multiple instances of this flag are
allowed.
allowed. .NET Framework 4.8 only.
-nripemd160=, --not-ripemd160= Filter by not RIPEMD160 hash
Include only items without this RIPEMD160 hash in the output.
Additionally, the user can specify an exact match or full C#-style
regex for pattern matching. Multiple instances of this flag are
allowed.
allowed. .NET Framework 4.8 only.
-sha1=, --sha1= Filter by SHA-1 hash
Include only items with this SHA-1 hash in the output. Additionally,
@@ -1638,13 +1623,13 @@ Options:
Include only items with this RIPEMD160 hash in the output.
Additionally, the user can specify an exact match or full C#-style
regex for pattern matching. Multiple instances of this flag are
allowed.
allowed. .NET Framework 4.8 only.
-nripemd160=, --not-ripemd160= Filter by not RIPEMD160 hash
Include only items without this RIPEMD160 hash in the output.
Additionally, the user can specify an exact match or full C#-style
regex for pattern matching. Multiple instances of this flag are
allowed.
allowed. .NET Framework 4.8 only.
-sha1=, --sha1= Filter by SHA-1 hash
Include only items with this SHA-1 hash in the output. Additionally,
@@ -1802,9 +1787,9 @@ attributed to the site and/or person(s) that originally wrote the code. All
code written by project members is licensed under GPL v3. See LICENSE for
more details.
** Section 20.0 - TEMPORARY REMAPPINGS
** Section 20.0 - REMAPPINGS
This section contains remappings from old flag names to new ones for the purposes of testing
This section contains remappings from old flag names to new ones.
-ab, --add-blank -> -ab, --add-blank-files
-ae, --add-ext -> -ae, --add-extension

View File

@@ -498,7 +498,23 @@ Below are originally from SabreTools / DATabase -
system= Comma-separated list of system IDs
source= Comma-separated list of source IDs
(-ss, --sort - This feature flag is not removed, just internal flags)
-7z=, --7z= Set scanning level for 7zip archives (default 1)
-gz=, --gz= Set scanning level for GZip archives (default 1)
-rar=, --rar= Set scanning level for RAR archives (default 1)
-zip=, --zip= Set scanning level for Zip archives (default 1)
Scan archives in one of the following ways:
0 - Hash both archive and its contents
1 - Only hash contents of the archive
2 - Only hash archive itself (treat like a regular file)
-sa, --scan-all Set scanning levels for all archives to 0
This flag is the short equivalent to -7z=0 -gz=0 -rar=0 -zip=0
wrapped up. Generally this will be helpful in all cases where the
content of the rebuild folder is not entirely known or is known to be
mixed.
-tm, --trim-merge Consolidate DAT into a single game and trim entries
In the cases where files will have too long a name, this allows for trimming
the name of the files to the NTFS maximum length at most

View File

@@ -128,7 +128,7 @@ namespace SabreTools.Library.Readers
{
GroupCollection gc = Regex.Match(line, Constants.InternalPatternCMP).Groups;
string normalizedValue = gc[1].Value.ToLowerInvariant();
string[] linegc = Utilities.SplitLineAsCMP(gc[2].Value);
string[] linegc = SplitLineAsCMP(gc[2].Value);
Internal = new Dictionary<string, string>();
for (int i = 0; i < linegc.Length; i++)
@@ -225,6 +225,32 @@ namespace SabreTools.Library.Readers
}
}
/// <summary>
/// Split a line as if it were a CMP rom line
/// </summary>
/// <param name="s">Line to split</param>
/// <returns>Line split</returns>
/// <remarks>Uses code from http://stackoverflow.com/questions/554013/regular-expression-to-split-on-spaces-unless-in-quotes</remarks>
private string[] SplitLineAsCMP(string s)
{
// Get the opening and closing brace locations
int openParenLoc = s.IndexOf('(');
int closeParenLoc = s.LastIndexOf(')');
// Now remove anything outside of those braces, including the braces
s = s.Substring(openParenLoc + 1, closeParenLoc - openParenLoc - 1);
s = s.Trim();
// Now we get each string, divided up as cleanly as possible
string[] matches = Regex
.Matches(s, Constants.InternalPatternAttributesCMP)
.Cast<Match>()
.Select(m => m.Groups[0].Value)
.ToArray();
return matches;
}
/// <summary>
/// Dispose of the underlying reader
/// </summary>

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using SabreTools.Library.Data;
using SabreTools.Library.DatFiles;
using SabreTools.Library.Tools;
@@ -12,37 +13,35 @@ namespace SabreTools.Library.Reports
/// TODO: Can this be overhauled to have all types write like DatFiles?
public abstract class BaseReport
{
protected DatFile _datFile;
protected string _name;
protected long _machineCount;
protected DatStats _stats;
protected StreamWriter _writer;
protected bool _baddumpCol;
protected bool _nodumpCol;
/// <summary>
/// Create a new report from the input DatFile and the filename
/// Create a new report from the filename
/// </summary>
/// <param name="datfile">DatFile to write out statistics for</param>
/// <param name="filename">Name of the file to write out to</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>
public BaseReport(DatFile datfile, string filename, bool baddumpCol = false, bool nodumpCol = false)
public BaseReport(string filename, bool baddumpCol = false, bool nodumpCol = false)
{
_datFile = datfile;
_writer = new StreamWriter(Utilities.TryCreate(filename));
_writer = new StreamWriter(FileExtensions.TryCreate(filename));
_baddumpCol = baddumpCol;
_nodumpCol = nodumpCol;
}
/// <summary>
/// Create a new report from the input DatFile and the stream
/// Create a new report from the stream
/// </summary>
/// <param name="datfile">DatFile to write out statistics for</param>
/// <param name="stream">Output stream to write to</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>
public BaseReport(DatFile datfile, Stream stream, bool baddumpCol = false, bool nodumpCol = false)
public BaseReport(Stream stream, bool baddumpCol = false, bool nodumpCol = false)
{
_datFile = datfile;
if (!stream.CanWrite)
throw new ArgumentException(nameof(stream));
@@ -52,19 +51,67 @@ namespace SabreTools.Library.Reports
}
/// <summary>
/// Replace the DatFile that is being output
/// Create a specific type of BaseReport to be used based on a format and user inputs
/// </summary>
/// <param name="datfile"></param>
public void ReplaceDatFile(DatFile datfile)
/// <param name="statReportFormat">Format of the Statistics Report to be created</param>
/// <param name="filename">Name of the file to write out to</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>
/// <returns>BaseReport of the specific internal type that corresponds to the inputs</returns>
public static BaseReport Create(StatReportFormat statReportFormat, string filename, bool baddumpCol, bool nodumpCol)
{
_datFile = datfile;
#if NET_FRAMEWORK
switch (statReportFormat)
{
case StatReportFormat.None:
return new Textfile(Console.OpenStandardOutput(), baddumpCol, nodumpCol);
case StatReportFormat.Textfile:
return new Textfile(filename, baddumpCol, nodumpCol);
case StatReportFormat.CSV:
return new SeparatedValue(filename, ',', baddumpCol, nodumpCol);
case StatReportFormat.HTML:
return new Html(filename, baddumpCol, nodumpCol);
case StatReportFormat.SSV:
return new SeparatedValue(filename, ';', baddumpCol, nodumpCol);
case StatReportFormat.TSV:
return new SeparatedValue(filename, '\t', baddumpCol, nodumpCol);
default:
return null;
}
#else
return statReportFormat switch
{
StatReportFormat.None => new Textfile(Console.OpenStandardOutput(), baddumpCol, nodumpCol),
StatReportFormat.Textfile => new Textfile(filename, baddumpCol, nodumpCol),
StatReportFormat.CSV => new SeparatedValue(filename, ',', baddumpCol, nodumpCol),
StatReportFormat.HTML => new Html(filename, baddumpCol, nodumpCol),
StatReportFormat.SSV => new SeparatedValue(filename, ';', baddumpCol, nodumpCol),
StatReportFormat.TSV => new SeparatedValue(filename, '\t', baddumpCol, nodumpCol),
_ => null,
};
#endif
}
/// <summary>
/// Replace the statistics that is being output
/// </summary>
public void ReplaceStatistics(string datName, long machineCount, DatStats datStats)
{
_name = datName;
_machineCount = machineCount;
_stats = datStats;
}
/// <summary>
/// Write the report to the output stream
/// </summary>
/// <param name="game">Number of games to use, -1 means use the number of keys</param>
public abstract void Write(long game = -1);
public abstract void Write();
/// <summary>
/// Write out the header to the stream, if any exists

View File

@@ -1,9 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using SabreTools.Library.DatFiles;
using SabreTools.Library.Tools;
namespace SabreTools.Library.Reports
@@ -15,49 +13,49 @@ namespace SabreTools.Library.Reports
internal class Html : BaseReport
{
/// <summary>
/// Create a new report from the input DatFile and the filename
/// Create a new report from the filename
/// </summary>
/// <param name="datfile">DatFile to write out statistics for</param>
/// <param name="filename">Name of the file to write out to</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>
public Html(DatFile datfile, string filename, bool baddumpCol = false, bool nodumpCol = false)
: base(datfile, filename, baddumpCol, nodumpCol)
public Html(string filename, bool baddumpCol = false, bool nodumpCol = false)
: base(filename, baddumpCol, nodumpCol)
{
}
/// <summary>
/// Create a new report from the input DatFile and the stream
/// Create a new report from the stream
/// </summary>
/// <param name="datfile">DatFile to write out statistics for</param>
/// <param name="stream">Output stream to write to</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>
public Html(DatFile datfile, Stream stream, bool baddumpCol = false, bool nodumpCol = false)
: base(datfile, stream, baddumpCol, nodumpCol)
public Html(Stream stream, bool baddumpCol = false, bool nodumpCol = false)
: base(stream, baddumpCol, nodumpCol)
{
}
/// <summary>
/// Write the report to file
/// </summary>
/// <param name="game">Number of games to use, -1 means use the number of keys</param>
public override void Write(long game = -1)
public override void Write()
{
string line = "\t\t\t<tr" + (_datFile.FileName.StartsWith("DIR: ")
? $" class=\"dir\"><td>{WebUtility.HtmlEncode(_datFile.FileName.Remove(0, 5))}"
: $"><td>{WebUtility.HtmlEncode(_datFile.FileName)}") + "</td>"
+ $"<td align=\"right\">{Utilities.GetBytesReadable(_datFile.TotalSize)}</td>"
+ $"<td align=\"right\">{(game == -1 ? _datFile.Keys.Count() : game)}</td>"
+ $"<td align=\"right\">{_datFile.RomCount}</td>"
+ $"<td align=\"right\">{_datFile.DiskCount}</td>"
+ $"<td align=\"right\">{_datFile.CRCCount}</td>"
+ $"<td align=\"right\">{_datFile.MD5Count}</td>"
+ $"<td align=\"right\">{_datFile.RIPEMD160Count}</td>"
+ $"<td align=\"right\">{_datFile.SHA1Count}</td>"
+ $"<td align=\"right\">{_datFile.SHA256Count}</td>"
+ (_baddumpCol ? $"<td align=\"right\">{_datFile.BaddumpCount}</td>" : string.Empty)
+ (_nodumpCol ? $"<td align=\"right\">{_datFile.NodumpCount}</td>" : string.Empty)
string line = "\t\t\t<tr" + (_name.StartsWith("DIR: ")
? $" class=\"dir\"><td>{WebUtility.HtmlEncode(_name.Remove(0, 5))}"
: $"><td>{WebUtility.HtmlEncode(_name)}") + "</td>"
+ $"<td align=\"right\">{Utilities.GetBytesReadable(_stats.TotalSize)}</td>"
+ $"<td align=\"right\">{_machineCount}</td>"
+ $"<td align=\"right\">{_stats.RomCount}</td>"
+ $"<td align=\"right\">{_stats.DiskCount}</td>"
+ $"<td align=\"right\">{_stats.CRCCount}</td>"
+ $"<td align=\"right\">{_stats.MD5Count}</td>"
#if NET_FRAMEWORK
+ $"<td align=\"right\">{_stats.RIPEMD160Count}</td>"
#endif
+ $"<td align=\"right\">{_stats.SHA1Count}</td>"
+ $"<td align=\"right\">{_stats.SHA256Count}</td>"
+ (_baddumpCol ? $"<td align=\"right\">{_stats.BaddumpCount}</td>" : string.Empty)
+ (_nodumpCol ? $"<td align=\"right\">{_stats.NodumpCount}</td>" : string.Empty)
+ "</tr>\n";
_writer.Write(line);
_writer.Flush();

View File

@@ -1,7 +1,4 @@
using System.IO;
using System.Linq;
using SabreTools.Library.DatFiles;
namespace SabreTools.Library.Reports
{
@@ -13,15 +10,14 @@ namespace SabreTools.Library.Reports
private char _separator;
/// <summary>
/// Create a new report from the input DatFile and the filename
/// Create a new report from the filename
/// </summary>
/// <param name="datfile">DatFile to write out statistics for</param>
/// <param name="filename">Name of the file to write out to</param>
/// <param name="separator">Separator character to use in output</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>
public SeparatedValue(DatFile datfile, string filename, char separator, bool baddumpCol = false, bool nodumpCol = false)
: base(datfile, filename, baddumpCol, nodumpCol)
public SeparatedValue(string filename, char separator, bool baddumpCol = false, bool nodumpCol = false)
: base(filename, baddumpCol, nodumpCol)
{
_separator = separator;
}
@@ -29,13 +25,12 @@ namespace SabreTools.Library.Reports
/// <summary>
/// Create a new report from the input DatFile and the stream
/// </summary>
/// <param name="datfile">DatFile to write out statistics for</param>
/// <param name="stream">Output stream to write to</param>
/// <param name="separator">Separator character to use in output</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>
public SeparatedValue(DatFile datfile, Stream stream, char separator, bool baddumpCol = false, bool nodumpCol = false)
: base(datfile, stream, baddumpCol, nodumpCol)
public SeparatedValue(Stream stream, char separator, bool baddumpCol = false, bool nodumpCol = false)
: base(stream, baddumpCol, nodumpCol)
{
_separator = separator;
}
@@ -43,25 +38,26 @@ namespace SabreTools.Library.Reports
/// <summary>
/// Write the report to file
/// </summary>
/// <param name="game">Number of games to use, -1 means use the number of keys</param>
public override void Write(long game = -1)
public override void Write()
{
string line = string.Format("\"" + _datFile.FileName + "\"{0}"
+ "\"" + _datFile.TotalSize + "\"{0}"
+ "\"" + (game == -1 ? _datFile.Keys.Count() : game) + "\"{0}"
+ "\"" + _datFile.RomCount + "\"{0}"
+ "\"" + _datFile.DiskCount + "\"{0}"
+ "\"" + _datFile.CRCCount + "\"{0}"
+ "\"" + _datFile.MD5Count + "\"{0}"
+ "\"" + _datFile.RIPEMD160Count + "\"{0}"
+ "\"" + _datFile.SHA1Count + "\"{0}"
+ "\"" + _datFile.SHA256Count + "\"{0}"
+ "\"" + _datFile.SHA384Count + "\"{0}"
+ "\"" + _datFile.SHA512Count + "\""
+ (_baddumpCol ? "{0}\"" + _datFile.BaddumpCount + "\"" : string.Empty)
+ (_nodumpCol ? "{0}\"" + _datFile.BaddumpCount + "\"" : string.Empty)
string line = string.Format("\"" + _name + "\"{0}"
+ "\"" + _stats.TotalSize + "\"{0}"
+ "\"" + _machineCount + "\"{0}"
+ "\"" + _stats.RomCount + "\"{0}"
+ "\"" + _stats.DiskCount + "\"{0}"
+ "\"" + _stats.CRCCount + "\"{0}"
+ "\"" + _stats.MD5Count + "\"{0}"
#if NET_FRAMEWORK
+ "\"" + _stats.RIPEMD160Count + "\"{0}"
#endif
+ "\"" + _stats.SHA1Count + "\"{0}"
+ "\"" + _stats.SHA256Count + "\"{0}"
+ "\"" + _stats.SHA384Count + "\"{0}"
+ "\"" + _stats.SHA512Count + "\""
+ (_baddumpCol ? "{0}\"" + _stats.BaddumpCount + "\"" : string.Empty)
+ (_nodumpCol ? "{0}\"" + _stats.NodumpCount + "\"" : string.Empty)
+ "\n", _separator);
_writer.Write(line);
_writer.Flush();
}

View File

@@ -1,7 +1,5 @@
using System.IO;
using System.Linq;
using SabreTools.Library.DatFiles;
using SabreTools.Library.Tools;
namespace SabreTools.Library.Reports
@@ -12,54 +10,55 @@ namespace SabreTools.Library.Reports
internal class Textfile : BaseReport
{
/// <summary>
/// Create a new report from the input DatFile and the filename
/// Create a new report from the filename
/// </summary>
/// <param name="datfile">DatFile to write out statistics for</param>
/// <param name="filename">Name of the file to write out to</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>
public Textfile(DatFile datfile, string filename, bool baddumpCol = false, bool nodumpCol = false)
: base(datfile, filename, baddumpCol, nodumpCol)
public Textfile(string filename, bool baddumpCol = false, bool nodumpCol = false)
: base(filename, baddumpCol, nodumpCol)
{
}
/// <summary>
/// Create a new report from the input DatFile and the stream
/// Create a new report from the stream
/// </summary>
/// <param name="datfile">DatFile to write out statistics for</param>
/// <param name="stream">Output stream to write to</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>
public Textfile(DatFile datfile, Stream stream, bool baddumpCol = false, bool nodumpCol = false)
: base(datfile, stream, baddumpCol, nodumpCol)
public Textfile(Stream stream, bool baddumpCol = false, bool nodumpCol = false)
: base(stream, baddumpCol, nodumpCol)
{
}
/// <summary>
/// Write the report to file
/// </summary>
/// <param name="game">Number of games to use, -1 means use the number of keys</param>
public override void Write(long game = -1)
public override void Write()
{
string line = @"'" + _datFile.FileName + @"':
string line = @"'" + _name + @"':
--------------------------------------------------
Uncompressed size: " + Utilities.GetBytesReadable(_datFile.TotalSize) + @"
Games found: " + (game == -1 ? _datFile.Keys.Count() : game) + @"
Roms found: " + _datFile.RomCount + @"
Disks found: " + _datFile.DiskCount + @"
Roms with CRC: " + _datFile.CRCCount + @"
Roms with MD5: " + _datFile.MD5Count + @"
Roms with RIPEMD160: " + _datFile.RIPEMD160Count + @"
Roms with SHA-1: " + _datFile.SHA1Count + @"
Roms with SHA-256: " + _datFile.SHA256Count + @"
Roms with SHA-384: " + _datFile.SHA384Count + @"
Roms with SHA-512: " + _datFile.SHA512Count + "\n";
Uncompressed size: " + Utilities.GetBytesReadable(_stats.TotalSize) + @"
Games found: " + _machineCount + @"
Roms found: " + _stats.RomCount + @"
Disks found: " + _stats.DiskCount + @"
Roms with CRC: " + _stats.CRCCount + @"
Roms with MD5: " + _stats.MD5Count
#if NET_FRAMEWORK
+ @"
Roms with RIPEMD160: " + _stats.RIPEMD160Count
#endif
+ @"
Roms with SHA-1: " + _stats.SHA1Count + @"
Roms with SHA-256: " + _stats.SHA256Count + @"
Roms with SHA-384: " + _stats.SHA384Count + @"
Roms with SHA-512: " + _stats.SHA512Count + "\n";
if (_baddumpCol)
line += " Roms with BadDump status: " + _datFile.BaddumpCount + "\n";
line += " Roms with BadDump status: " + _stats.BaddumpCount + "\n";
if (_nodumpCol)
line += " Roms with Nodump status: " + _datFile.NodumpCount + "\n";
line += " Roms with Nodump status: " + _stats.NodumpCount + "\n";
// For spacing between DATs
line += "\n\n";

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net462;net472;net48;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>net48;netcoreapp3.1</TargetFrameworks>
<RuntimeIdentifiers>win10-x64;win7-x86</RuntimeIdentifiers>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU;x64</Platforms>
@@ -21,7 +21,6 @@
<None Remove="Skippers\psid.xml" />
<None Remove="Skippers\snes.xml" />
<None Remove="Skippers\spc.xml" />
<None Remove="sqlite3.dll" />
</ItemGroup>
<ItemGroup>
@@ -52,15 +51,11 @@
<Content Include="Skippers\spc.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="sqlite3.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Mono.Data.Sqlite.Portable" Version="1.0.3.5" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="3.1.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="SevenZipSharp.Net45" Version="1.0.19" />
<PackageReference Include="SharpCompress" Version="0.25.1" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>

View File

@@ -1,12 +1,12 @@
using System;
using SabreTools.Library.Data;
using SabreTools.Library.FileTypes;
using SabreTools.Library.Tools;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;
using SabreTools.Library.Data;
using SabreTools.Library.Tools;
namespace SabreTools.Library.Skippers
{
public class Skipper
@@ -38,22 +38,15 @@ namespace SabreTools.Library.Skippers
/// </summary>
public string SourceFile { get; set; }
// Local paths
/// <summary>
/// Local paths
/// </summary>
public static string LocalPath = Path.Combine(Globals.ExeDir, "Skippers") + Path.DirectorySeparatorChar;
// Header skippers represented by a list of skipper objects
private static List<Skipper> _list;
public static List<Skipper> List
{
get
{
if (_list == null || _list.Count == 0)
{
PopulateSkippers();
}
return _list;
}
}
/// <summary>
/// Header skippers represented by a list of skipper objects
/// </summary>
private static List<Skipper> List;
#endregion
@@ -80,8 +73,7 @@ namespace SabreTools.Library.Skippers
Rules = new List<SkipperRule>();
SourceFile = Path.GetFileNameWithoutExtension(filename);
Logger logger = new Logger();
XmlReader xtr = Utilities.GetXmlTextReader(filename);
XmlReader xtr = filename.GetXmlTextReader();
if (xtr == null)
return;
@@ -218,13 +210,9 @@ namespace SabreTools.Library.Skippers
{
string offset = subreader.GetAttribute("offset");
if (offset.ToLowerInvariant() == "eof")
{
test.Offset = null;
}
else
{
test.Offset = Convert.ToInt64(offset, 16);
}
}
if (subreader.GetAttribute("value") != null)
@@ -243,16 +231,10 @@ namespace SabreTools.Library.Skippers
if (subreader.GetAttribute("result") != null)
{
string result = subreader.GetAttribute("result");
switch (result.ToLowerInvariant())
{
case "false":
test.Result = false;
break;
case "true":
default:
test.Result = true;
break;
}
if (!bool.TryParse(result, out bool resultBool))
resultBool = true;
test.Result = resultBool;
}
if (subreader.GetAttribute("mask") != null)
@@ -272,18 +254,15 @@ namespace SabreTools.Library.Skippers
{
string size = subreader.GetAttribute("size");
if (size.ToLowerInvariant() == "po2")
{
test.Size = null;
}
else
{
test.Size = Convert.ToInt64(size, 16);
}
}
if (subreader.GetAttribute("operator") != null)
{
string oper = subreader.GetAttribute("operator");
#if NET_FRAMEWORK
switch (oper.ToLowerInvariant())
{
case "less":
@@ -297,6 +276,15 @@ namespace SabreTools.Library.Skippers
test.Operator = HeaderSkipTestFileOperator.Equal;
break;
}
#else
test.Operator = oper.ToLowerInvariant() switch
{
"less" => HeaderSkipTestFileOperator.Less,
"greater" => HeaderSkipTestFileOperator.Greater,
"equal" => HeaderSkipTestFileOperator.Equal,
_ => HeaderSkipTestFileOperator.Equal,
};
#endif
}
// Add the created test to the rule
@@ -330,6 +318,14 @@ namespace SabreTools.Library.Skippers
#region Static Methods
/// <summary>
/// Initialize static fields
/// </summary>
public static void Init()
{
PopulateSkippers();
}
/// <summary>
/// Populate the entire list of header Skippers
/// </summary>
@@ -339,15 +335,113 @@ namespace SabreTools.Library.Skippers
/// </remarks>
private static void PopulateSkippers()
{
if (_list == null)
_list = new List<Skipper>();
if (List == null)
List = new List<Skipper>();
foreach (string skipperFile in Directory.EnumerateFiles(LocalPath, "*", SearchOption.AllDirectories))
{
_list.Add(new Skipper(Path.GetFullPath(skipperFile)));
List.Add(new Skipper(Path.GetFullPath(skipperFile)));
}
}
/// <summary>
/// Detect header skipper compliance and create an output file
/// </summary>
/// <param name="file">Name of the file to be parsed</param>
/// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param>
/// <param name="nostore">True if headers should not be stored in the database, false otherwise</param>
/// <returns>True if the output file was created, false otherwise</returns>
public static bool DetectTransformStore(string file, string outDir, bool nostore)
{
// Create the output directory if it doesn't exist
DirectoryExtensions.Ensure(outDir, create: true);
Globals.Logger.User($"\nGetting skipper information for '{file}'");
// Get the skipper rule that matches the file, if any
SkipperRule rule = GetMatchingRule(file, string.Empty);
// If we have an empty rule, return false
if (rule.Tests == null || rule.Tests.Count == 0 || rule.Operation != HeaderSkipOperation.None)
return false;
Globals.Logger.User("File has a valid copier header");
// Get the header bytes from the file first
string hstr;
try
{
// Extract the header as a string for the database
#if NET_FRAMEWORK
using (var fs = FileExtensions.TryOpenRead(file))
{
#else
using var fs = FileExtensions.TryOpenRead(file);
#endif
byte[] hbin = new byte[(int)rule.StartOffset];
fs.Read(hbin, 0, (int)rule.StartOffset);
hstr = Utilities.ByteArrayToString(hbin);
#if NET_FRAMEWORK
}
#endif
}
catch
{
return false;
}
// Apply the rule to the file
string newfile = (string.IsNullOrWhiteSpace(outDir) ? Path.GetFullPath(file) + ".new" : Path.Combine(outDir, Path.GetFileName(file)));
rule.TransformFile(file, newfile);
// If the output file doesn't exist, return false
if (!File.Exists(newfile))
return false;
// Now add the information to the database if it's not already there
if (!nostore)
{
BaseFile baseFile = FileExtensions.GetInfo(newfile, chdsAsFiles: true);
DatabaseTools.AddHeaderToDatabase(hstr, Utilities.ByteArrayToString(baseFile.SHA1), rule.SourceFile);
}
return true;
}
/// <summary>
/// Detect and replace header(s) to the given file
/// </summary>
/// <param name="file">Name of the file to be parsed</param>
/// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param>
/// <returns>True if a header was found and appended, false otherwise</returns>
public static bool RestoreHeader(string file, string outDir)
{
// Create the output directory if it doesn't exist
if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
// First, get the SHA-1 hash of the file
BaseFile baseFile = FileExtensions.GetInfo(file, chdsAsFiles: true);
// Retrieve a list of all related headers from the database
List<string> headers = DatabaseTools.RetrieveHeadersFromDatabase(Utilities.ByteArrayToString(baseFile.SHA1));
// If we have nothing retrieved, we return false
if (headers.Count == 0)
return false;
// Now loop through and create the reheadered files, if possible
for (int i = 0; i < headers.Count; i++)
{
string outputFile = (string.IsNullOrWhiteSpace(outDir) ? $"{Path.GetFullPath(file)}.new" : Path.Combine(outDir, Path.GetFileName(file))) + i;
Globals.Logger.User($"Creating reheadered file: {outputFile}");
FileExtensions.AppendBytes(file, outputFile, Utilities.StringToByteArray(headers[i]), null);
Globals.Logger.User("Reheadered file created!");
}
return true;
}
/// <summary>
/// Get the SkipperRule associated with a given file
/// </summary>
@@ -364,7 +458,7 @@ namespace SabreTools.Library.Skippers
return new SkipperRule();
}
return GetMatchingRule(Utilities.TryOpenRead(input), skipperName);
return GetMatchingRule(FileExtensions.TryOpenRead(input), skipperName);
}
/// <summary>

View File

@@ -46,8 +46,6 @@ namespace SabreTools.Library.Skippers
/// <returns>True if the file was transformed properly, false otherwise</returns>
public bool TransformFile(string input, string output)
{
bool success = true;
// If the input file doesn't exist, fail
if (!File.Exists(input))
{
@@ -56,15 +54,15 @@ namespace SabreTools.Library.Skippers
}
// Create the output directory if it doesn't already
Utilities.EnsureOutputDirectory(Path.GetDirectoryName(output));
DirectoryExtensions.Ensure(Path.GetDirectoryName(output));
Globals.Logger.User($"Attempting to apply rule to '{input}'");
success = TransformStream(Utilities.TryOpenRead(input), Utilities.TryCreate(output));
bool success = TransformStream(FileExtensions.TryOpenRead(input), FileExtensions.TryCreate(output));
// If the output file has size 0, delete it
if (new FileInfo(output).Length == 0)
{
Utilities.TryDeleteFile(output);
FileExtensions.TryDelete(output);
success = false;
}

View File

@@ -0,0 +1,174 @@
using System;
using System.IO;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Big endian reading overloads for BinaryReader
/// </summary>
public static class BinaryReaderExtensions
{
/// <summary>
/// Reads the specified number of bytes from the stream, starting from a specified point in the byte array.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
/// <param name="count">The number of bytes to read.</param>
/// <returns>The number of bytes read into buffer. This might be less than the number of bytes requested if that many bytes are not available, or it might be zero if the end of the stream is reached.</returns>
public static int ReadBigEndian(this BinaryReader reader, byte[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <summary>
/// Reads the specified number of characters from the stream, starting from a specified point in the character array.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
/// <param name="count">The number of characters to read.</param>
/// <returns>The total number of characters read into the buffer. This might be less than the number of characters requested if that many characters are not currently available, or it might be zero if the end of the stream is reached.</returns>
public static int ReadBigEndian(this BinaryReader reader, char[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <summary>
/// Reads the specified number of bytes from the current stream into a byte array and advances the current position by that number of bytes.
/// </summary>
/// <param name="count">The number of bytes to read. This value must be 0 or a non-negative number or an exception will occur.</param>
/// <returns>A byte array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
{
byte[] retval = reader.ReadBytes(count);
Array.Reverse(retval);
return retval;
}
/// <summary>
/// Reads the specified number of characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream.
/// </summary>
/// <param name="count">The number of characters to read. This value must be 0 or a non-negative number or an exception will occur.</param>
/// <returns>A character array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
public static char[] ReadCharsBigEndian(this BinaryReader reader, int count)
{
char[] retval = reader.ReadChars(count);
Array.Reverse(retval);
return retval;
}
/// <summary>
/// Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes.
/// </summary>
/// <returns>A decimal value read from the current stream.</returns>
public static decimal ReadDecimalBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(16);
Array.Reverse(retval);
int i1 = BitConverter.ToInt32(retval, 0);
int i2 = BitConverter.ToInt32(retval, 4);
int i3 = BitConverter.ToInt32(retval, 8);
int i4 = BitConverter.ToInt32(retval, 12);
return new decimal(new int[] { i1, i2, i3, i4 });
}
/// <summary>
/// eads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.
/// </summary>
/// <returns>An 8-byte floating point value read from the current stream.</returns>
public static double ReadDoubleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToDouble(retval, 0);
}
/// <summary>
/// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.
/// </summary>
/// <returns>A 2-byte signed integer read from the current stream.</returns>
public static short ReadInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToInt16(retval, 0);
}
/// <summary>
/// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
/// </summary>
/// <returns>A 4-byte signed integer read from the current stream.</returns>
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToInt32(retval, 0);
}
/// <summary>
/// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.
/// </summary>
/// <returns>An 8-byte signed integer read from the current stream.</returns>
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToInt64(retval, 0);
}
/// <summary>
/// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
/// </summary>
/// <returns>A 4-byte floating point value read from the current stream.</returns>
public static float ReadSingleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToSingle(retval, 0);
}
/// <summary>
/// Reads a 2-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by two bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>A 2-byte unsigned integer read from this stream.</returns>
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToUInt16(retval, 0);
}
/// <summary>
/// Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>A 4-byte unsigned integer read from this stream.</returns>
public static uint ReadUInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToUInt32(retval, 0);
}
/// <summary>
/// Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>An 8-byte unsigned integer read from this stream.</returns>
public static ulong ReadUInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToUInt64(retval, 0);
}
}
}

View File

@@ -0,0 +1,545 @@
using SabreTools.Library.Data;
namespace SabreTools.Library.Tools
{
public static class Converters
{
/// <summary>
/// Get DatFormat value from input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>DatFormat value corresponding to the string</returns>
public static DatFormat AsDatFormat(this string input)
{
switch (input?.Trim().ToLowerInvariant())
{
case "all":
return DatFormat.ALL;
case "am":
case "attractmode":
return DatFormat.AttractMode;
case "cmp":
case "clrmamepro":
return DatFormat.ClrMamePro;
case "csv":
return DatFormat.CSV;
case "dc":
case "doscenter":
return DatFormat.DOSCenter;
case "json":
return DatFormat.Json;
case "lr":
case "listrom":
return DatFormat.Listrom;
case "lx":
case "listxml":
return DatFormat.Listxml;
case "md5":
return DatFormat.RedumpMD5;
case "miss":
case "missfile":
return DatFormat.MissFile;
case "msx":
case "openmsx":
return DatFormat.OpenMSX;
case "ol":
case "offlinelist":
return DatFormat.OfflineList;
case "rc":
case "romcenter":
return DatFormat.RomCenter;
#if NET_FRAMEWORK
case "ripemd160":
return DatFormat.RedumpRIPEMD160;
#endif
case "sd":
case "sabredat":
return DatFormat.SabreDat;
case "sfv":
return DatFormat.RedumpSFV;
case "sha1":
return DatFormat.RedumpSHA1;
case "sha256":
return DatFormat.RedumpSHA256;
case "sha384":
return DatFormat.RedumpSHA384;
case "sha512":
return DatFormat.RedumpSHA512;
case "sl":
case "softwarelist":
return DatFormat.SoftwareList;
case "smdb":
case "everdrive":
return DatFormat.EverdriveSMDB;
case "ssv":
return DatFormat.SSV;
case "tsv":
return DatFormat.TSV;
case "xml":
case "logiqx":
return DatFormat.Logiqx;
default:
return 0x0;
}
}
/// <summary>
/// Get the field associated with each hash type
/// </summary>
public static Field AsField(this Hash hash)
{
switch (hash)
{
case Hash.CRC:
return Field.CRC;
case Hash.MD5:
return Field.MD5;
#if NET_FRAMEWORK
case Hash.RIPEMD160:
return Field.RIPEMD160;
#endif
case Hash.SHA1:
return Field.SHA1;
case Hash.SHA256:
return Field.SHA256;
case Hash.SHA384:
return Field.SHA384;
case Hash.SHA512:
return Field.SHA512;
default:
return Field.NULL;
}
}
/// <summary>
/// Get Field value from input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>Field value corresponding to the string</returns>
public static Field AsField(this string input)
{
switch (input?.ToLowerInvariant())
{
case "areaname":
return Field.AreaName;
case "areasize":
return Field.AreaSize;
case "bios":
return Field.Bios;
case "biosdescription":
case "bios description":
case "biossetdescription":
case "biosset description":
case "bios set description":
return Field.BiosDescription;
case "board":
return Field.Board;
case "cloneof":
return Field.CloneOf;
case "comment":
return Field.Comment;
case "crc":
return Field.CRC;
case "default":
return Field.Default;
case "date":
return Field.Date;
case "description":
return Field.Description;
case "devices":
return Field.Devices;
case "features":
return Field.Features;
case "gamename":
case "machinename":
return Field.MachineName;
case "gametype":
case "machinetype":
return Field.MachineType;
case "index":
return Field.Index;
case "infos":
return Field.Infos;
case "language":
return Field.Language;
case "manufacturer":
return Field.Manufacturer;
case "md5":
return Field.MD5;
case "merge":
return Field.Merge;
case "name":
return Field.Name;
case "offset":
return Field.Offset;
case "optional":
return Field.Optional;
case "partinterface":
return Field.PartInterface;
case "partname":
return Field.PartName;
case "publisher":
return Field.Publisher;
case "rebuildto":
return Field.RebuildTo;
case "region":
return Field.Region;
#if NET_FRAMEWORK
case "ripemd160":
return Field.RIPEMD160;
#endif
case "romof":
return Field.RomOf;
case "runnable":
return Field.Runnable;
case "sampleof":
return Field.SampleOf;
case "sha1":
return Field.SHA1;
case "sha256":
return Field.SHA256;
case "sha384":
return Field.SHA384;
case "sha512":
return Field.SHA512;
case "size":
return Field.Size;
case "slotoptions":
return Field.SlotOptions;
case "sourcefile":
return Field.SourceFile;
case "status":
return Field.Status;
case "supported":
return Field.Supported;
case "writable":
return Field.Writable;
case "year":
return Field.Year;
default:
return Field.NULL;
}
}
/// <summary>
/// Get ForceMerging value from input string
/// </summary>
/// <param name="forcemerge">String to get value from</param>
/// <returns>ForceMerging value corresponding to the string</returns>
public static ForceMerging AsForceMerging(this string forcemerge)
{
#if NET_FRAMEWORK
switch (forcemerge?.ToLowerInvariant())
{
case "split":
return ForceMerging.Split;
case "merged":
return ForceMerging.Merged;
case "nonmerged":
return ForceMerging.NonMerged;
case "full":
return ForceMerging.Full;
case "none":
default:
return ForceMerging.None;
}
#else
return forcemerge?.ToLowerInvariant() switch
{
"split" => ForceMerging.Split,
"merged" => ForceMerging.Merged,
"nonmerged" => ForceMerging.NonMerged,
"full" => ForceMerging.Full,
"none" => ForceMerging.None,
_ => ForceMerging.None,
};
#endif
}
/// <summary>
/// Get ForceNodump value from input string
/// </summary>
/// <param name="forcend">String to get value from</param>
/// <returns>ForceNodump value corresponding to the string</returns>
public static ForceNodump AsForceNodump(this string forcend)
{
#if NET_FRAMEWORK
switch (forcend?.ToLowerInvariant())
{
case "obsolete":
return ForceNodump.Obsolete;
case "required":
return ForceNodump.Required;
case "ignore":
return ForceNodump.Ignore;
case "none":
default:
return ForceNodump.None;
}
#else
return forcend?.ToLowerInvariant() switch
{
"obsolete" => ForceNodump.Obsolete,
"required" => ForceNodump.Required,
"ignore" => ForceNodump.Ignore,
"none" => ForceNodump.None,
_ => ForceNodump.None,
};
#endif
}
/// <summary>
/// Get ForcePacking value from input string
/// </summary>
/// <param name="forcepack">String to get value from</param>
/// <returns>ForcePacking value corresponding to the string</returns>
public static ForcePacking AsForcePacking(this string forcepack)
{
#if NET_FRAMEWORK
switch (forcepack?.ToLowerInvariant())
{
case "yes":
case "zip":
return ForcePacking.Zip;
case "no":
case "unzip":
return ForcePacking.Unzip;
case "none":
default:
return ForcePacking.None;
}
#else
return forcepack?.ToLowerInvariant() switch
{
"yes" => ForcePacking.Zip,
"zip" => ForcePacking.Zip,
"no" => ForcePacking.Unzip,
"unzip" => ForcePacking.Unzip,
"none" => ForcePacking.None,
_ => ForcePacking.None,
};
#endif
}
/// <summary>
/// Get ItemStatus value from input string
/// </summary>
/// <param name="status">String to get value from</param>
/// <returns>ItemStatus value corresponding to the string</returns>
public static ItemStatus AsItemStatus(this string status)
{
#if NET_FRAMEWORK
switch (status?.ToLowerInvariant())
{
case "good":
return ItemStatus.Good;
case "baddump":
return ItemStatus.BadDump;
case "nodump":
case "yes":
return ItemStatus.Nodump;
case "verified":
return ItemStatus.Verified;
case "none":
case "no":
default:
return ItemStatus.None;
}
#else
return status?.ToLowerInvariant() switch
{
"good" => ItemStatus.Good,
"baddump" => ItemStatus.BadDump,
"nodump" => ItemStatus.Nodump,
"yes" => ItemStatus.Nodump,
"verified" => ItemStatus.Verified,
"none" => ItemStatus.None,
"no" => ItemStatus.None,
_ => ItemStatus.None,
};
#endif
}
/// <summary>
/// Get ItemType? value from input string
/// </summary>
/// <param name="itemType">String to get value from</param>
/// <returns>ItemType? value corresponding to the string</returns>
public static ItemType? AsItemType(this string itemType)
{
#if NET_FRAMEWORK
switch (itemType?.ToLowerInvariant())
{
case "archive":
return ItemType.Archive;
case "biosset":
return ItemType.BiosSet;
case "blank":
return ItemType.Blank;
case "disk":
return ItemType.Disk;
case "release":
return ItemType.Release;
case "rom":
return ItemType.Rom;
case "sample":
return ItemType.Sample;
default:
return null;
}
#else
return itemType?.ToLowerInvariant() switch
{
"archive" => ItemType.Archive,
"biosset" => ItemType.BiosSet,
"blank" => ItemType.Blank,
"disk" => ItemType.Disk,
"release" => ItemType.Release,
"rom" => ItemType.Rom,
"sample" => ItemType.Sample,
_ => null,
};
#endif
}
/// <summary>
/// Get MachineType value from input string
/// </summary>
/// <param name="gametype">String to get value from</param>
/// <returns>MachineType value corresponding to the string</returns>
public static MachineType AsMachineType(this string gametype)
{
#if NET_FRAMEWORK
switch (gametype?.ToLowerInvariant())
{
case "bios":
return MachineType.Bios;
case "dev":
case "device":
return MachineType.Device;
case "mech":
case "mechanical":
return MachineType.Mechanical;
case "none":
default:
return MachineType.None;
}
#else
return gametype?.ToLowerInvariant() switch
{
"bios" => MachineType.Bios,
"dev" => MachineType.Device,
"device" => MachineType.Device,
"mech" => MachineType.Mechanical,
"mechanical" => MachineType.Mechanical,
"none" => MachineType.None,
_ => MachineType.None,
};
#endif
}
/// <summary>
/// Get SplitType value from input ForceMerging
/// </summary>
/// <param name="forceMerging">ForceMerging to get value from</param>
/// <returns>SplitType value corresponding to the string</returns>
public static SplitType AsSplitType(this ForceMerging forceMerging)
{
#if NET_FRAMEWORK
switch (forceMerging)
{
case ForceMerging.Split:
return SplitType.Split;
case ForceMerging.Merged:
return SplitType.Merged;
case ForceMerging.NonMerged:
return SplitType.NonMerged;
case ForceMerging.Full:
return SplitType.FullNonMerged;
case ForceMerging.None:
default:
return SplitType.None;
}
#else
return forceMerging switch
{
ForceMerging.Split => SplitType.Split,
ForceMerging.Merged => SplitType.Merged,
ForceMerging.NonMerged => SplitType.NonMerged,
ForceMerging.Full => SplitType.FullNonMerged,
ForceMerging.None => SplitType.None,
_ => SplitType.None,
};
#endif
}
/// <summary>
/// Get StatReportFormat value from input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>StatReportFormat value corresponding to the string</returns>
public static StatReportFormat AsStatReportFormat(this string input)
{
#if NET_FRAMEWORK
switch (input?.Trim().ToLowerInvariant())
{
case "all":
return StatReportFormat.All;
case "csv":
return StatReportFormat.CSV;
case "html":
return StatReportFormat.HTML;
case "ssv":
return StatReportFormat.SSV;
case "text":
return StatReportFormat.Textfile;
case "tsv":
return StatReportFormat.TSV;
default:
return 0x0;
}
#else
return input?.Trim().ToLowerInvariant() switch
{
"all" => StatReportFormat.All,
"csv" => StatReportFormat.CSV,
"html" => StatReportFormat.HTML,
"ssv" => StatReportFormat.SSV,
"text" => StatReportFormat.Textfile,
"tsv" => StatReportFormat.TSV,
_ => 0x0,
};
#endif
}
/// <summary>
/// Get bool? value from input string
/// </summary>
/// <param name="yesno">String to get value from</param>
/// <returns>bool? corresponding to the string</returns>
public static bool? AsYesNo(this string yesno)
{
#if NET_FRAMEWORK
switch (yesno?.ToLowerInvariant())
{
case "yes":
return true;
case "no":
return false;
case "partial":
default:
return null;
}
#else
return yesno?.ToLowerInvariant() switch
{
"yes" => true,
"no" => false,
"partial" => null,
_ => null,
};
#endif
}
}
}

View File

@@ -3,7 +3,7 @@ using System.IO;
using System.Collections.Generic;
using SabreTools.Library.Data;
using Mono.Data.Sqlite;
using Microsoft.Data.Sqlite;
namespace SabreTools.Library.Tools
{
@@ -20,8 +20,6 @@ namespace SabreTools.Library.Tools
/// <param name="type">Name of the source skipper file</param>
public static void AddHeaderToDatabase(string header, string SHA1, string source)
{
bool exists = false;
// Ensure the database exists
EnsureDatabase(Constants.HeadererDbSchema, Constants.HeadererFileName, Constants.HeadererConnectionString);
@@ -32,7 +30,7 @@ namespace SabreTools.Library.Tools
string query = $"SELECT * FROM data WHERE sha1='{SHA1}' AND header='{header}'";
SqliteCommand slc = new SqliteCommand(query, dbc);
SqliteDataReader sldr = slc.ExecuteReader();
exists = sldr.HasRows;
bool exists = sldr.HasRows;
if (!exists)
{
@@ -60,7 +58,7 @@ namespace SabreTools.Library.Tools
// Make sure the file exists
if (!File.Exists(db))
SqliteConnection.CreateFile(db);
File.Create(db);
// Open the database connection
SqliteConnection dbc = new SqliteConnection(connectionString);

View File

@@ -0,0 +1,286 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SabreTools.Library.Data;
using NaturalSort;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Extensions to Directory functionality
/// </summary>
public static class DirectoryExtensions
{
/// <summary>
/// Cleans out the temporary directory
/// </summary>
/// <param name="dir">Name of the directory to clean out</param>
public static void Clean(string dir)
{
foreach (string file in Directory.EnumerateFiles(dir, "*", SearchOption.TopDirectoryOnly))
{
FileExtensions.TryDelete(file);
}
foreach (string subdir in Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly))
{
TryDelete(subdir);
}
}
/// <summary>
/// Ensure the output directory is a proper format and can be created
/// </summary>
/// <param name="dir">Directory to check</param>
/// <param name="create">True if the directory should be created, false otherwise (default)</param>
/// <param name="temp">True if this is a temp directory, false otherwise</param>
/// <returns>Full path to the directory</returns>
public static string Ensure(string dir, bool create = false, bool temp = false)
{
// If the output directory is invalid
if (string.IsNullOrWhiteSpace(dir))
{
if (temp)
dir = Path.GetTempPath();
else
dir = Environment.CurrentDirectory;
}
// Get the full path for the output directory
dir = Path.GetFullPath(dir);
// If we're creating the output folder, do so
if (create)
Directory.CreateDirectory(dir);
return dir;
}
/// <summary>
/// Retrieve a list of just directories from inputs
/// </summary>
/// <param name="inputs">List of strings representing directories and files</param>
/// <param name="appendparent">True if the parent name should be appended after the special character "¬", false otherwise (default)</param>
/// <returns>List of strings representing just directories from the inputs</returns>
public static List<string> GetDirectoriesOnly(List<string> inputs, bool appendparent = false)
{
List<string> outputs = new List<string>();
foreach (string input in inputs)
{
if (Directory.Exists(input))
{
List<string> directories = GetDirectoriesOrdered(input);
foreach (string dir in directories)
{
try
{
outputs.Add(Path.GetFullPath(dir) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty));
}
catch (PathTooLongException)
{
Globals.Logger.Warning($"The path for '{dir}' was too long");
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
}
}
}
}
return outputs;
}
/// <summary>
/// Retrieve a list of directories from a directory recursively in proper order
/// </summary>
/// <param name="dir">Directory to parse</param>
/// <returns>List with all new files</returns>
private static List<string> GetDirectoriesOrdered(string dir)
{
return GetDirectoriesOrderedHelper(dir, new List<string>());
}
/// <summary>
/// Retrieve a list of directories from a directory recursively in proper order
/// </summary>
/// <param name="dir">Directory to parse</param>
/// <param name="infiles">List representing existing files</param>
/// <returns>List with all new files</returns>
private static List<string> GetDirectoriesOrderedHelper(string dir, List<string> infiles)
{
// Take care of the files in the top directory
List<string> toadd = Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly).ToList();
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
// Then recurse through and add from the directories
foreach (string subDir in toadd)
{
infiles = GetDirectoriesOrderedHelper(subDir, infiles);
}
// Return the new list
return infiles;
}
/// <summary>
/// Retrieve a list of just files from inputs
/// </summary>
/// <param name="inputs">List of strings representing directories and files</param>
/// <param name="appendparent">True if the parent name should be appended after the special character "¬", false otherwise (default)</param>
/// <returns>List of strings representing just files from the inputs</returns>
public static List<string> GetFilesOnly(List<string> inputs, bool appendparent = false)
{
List<string> outputs = new List<string>();
foreach (string input in inputs)
{
if (Directory.Exists(input))
{
List<string> files = GetFilesOrdered(input);
foreach (string file in files)
{
try
{
outputs.Add(Path.GetFullPath(file) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty));
}
catch (PathTooLongException)
{
Globals.Logger.Warning($"The path for '{file}' was too long");
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
}
}
}
else if (File.Exists(input))
{
try
{
outputs.Add(Path.GetFullPath(input) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty));
}
catch (PathTooLongException)
{
Globals.Logger.Warning($"The path for '{input}' was too long");
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
}
}
}
return outputs;
}
/// <summary>
/// Retrieve a list of files from a directory recursively in proper order
/// </summary>
/// <param name="dir">Directory to parse</param>
/// <param name="infiles">List representing existing files</param>
/// <returns>List with all new files</returns>
public static List<string> GetFilesOrdered(string dir)
{
return GetFilesOrderedHelper(dir, new List<string>());
}
/// <summary>
/// Retrieve a list of files from a directory recursively in proper order
/// </summary>
/// <param name="dir">Directory to parse</param>
/// <param name="infiles">List representing existing files</param>
/// <returns>List with all new files</returns>
private static List<string> GetFilesOrderedHelper(string dir, List<string> infiles)
{
// Take care of the files in the top directory
List<string> toadd = Directory.EnumerateFiles(dir, "*", SearchOption.TopDirectoryOnly).ToList();
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
// Then recurse through and add from the directories
List<string> subDirs = Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly).ToList();
subDirs.Sort(new NaturalComparer());
foreach (string subdir in subDirs)
{
infiles = GetFilesOrderedHelper(subdir, infiles);
}
// Return the new list
return infiles;
}
/// <summary>
/// Get all empty folders within a root folder
/// </summary>
/// <param name="root">Root directory to parse</param>
/// <returns>IEumerable containing all directories that are empty, an empty enumerable if the root is empty, null otherwise</returns>
public static List<string> ListEmpty(string root)
{
// Check if the root exists first
if (!Directory.Exists(root))
return null;
// If it does and it is empty, return a blank enumerable
if (Directory.EnumerateFileSystemEntries(root, "*", SearchOption.AllDirectories).Count() == 0)
return new List<string>();
// Otherwise, get the complete list
return Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories)
.Where(dir => Directory.EnumerateFileSystemEntries(dir, "*", SearchOption.AllDirectories).Count() == 0)
.ToList();
}
/// <summary>
/// Try to safely delete a directory, optionally throwing the error
/// </summary>
/// <param name="file">Name of the directory to delete</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>True if the file didn't exist or could be deleted, false otherwise</returns>
public static bool TryCreateDirectory(string file, bool throwOnError = false)
{
// Now wrap creating the directory
try
{
Directory.CreateDirectory(file);
return true;
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return false;
}
}
/// <summary>
/// Try to safely delete a directory, optionally throwing the error
/// </summary>
/// <param name="file">Name of the directory to delete</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>True if the file didn't exist or could be deleted, false otherwise</returns>
public static bool TryDelete(string file, bool throwOnError = false)
{
// Check if the directory exists first
if (!Directory.Exists(file))
return true;
// Now wrap deleting the directory
try
{
Directory.Delete(file, true);
return true;
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return false;
}
}
}
}

View File

@@ -0,0 +1,550 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using SabreTools.Library.Data;
using SabreTools.Library.FileTypes;
using SabreTools.Library.Readers;
using SabreTools.Library.Skippers;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Extensions to File functionality
/// </summary>
public static class FileExtensions
{
/// <summary>
/// Add an aribtrary number of bytes to the inputted file
/// </summary>
/// <param name="input">File to be appended to</param>
/// <param name="output">Outputted file</param>
/// <param name="bytesToAddToHead">Bytes to be added to head of file</param>
/// <param name="bytesToAddToTail">Bytes to be added to tail of file</param>
public static void AppendBytes(string input, string output, byte[] bytesToAddToHead, byte[] bytesToAddToTail)
{
// If any of the inputs are invalid, skip
if (!File.Exists(input))
return;
#if NET_FRAMEWORK
using (FileStream fsr = TryOpenRead(input))
using (FileStream fsw = TryOpenWrite(output))
{
#else
using FileStream fsr = TryOpenRead(input);
using FileStream fsw = TryOpenWrite(output);
#endif
StreamExtensions.AppendBytes(fsr, fsw, bytesToAddToHead, bytesToAddToTail);
#if NET_FRAMEWORK
}
#endif
}
/// <summary>
/// Get what type of DAT the input file is
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <returns>The DatFormat corresponding to the DAT</returns>
public static DatFormat GetDatFormat(this string filename)
{
// Limit the output formats based on extension
if (!PathExtensions.HasValidDatExtension(filename))
return 0;
// Get the extension from the filename
string ext = PathExtensions.GetNormalizedExtension(filename);
// Read the input file, if possible
Globals.Logger.Verbose($"Attempting to read file to get format: {filename}");
// Check if file exists
if (!File.Exists(filename))
{
Globals.Logger.Warning($"File '{filename}' could not read from!");
return 0;
}
// Some formats should only require the extension to know
switch (ext)
{
case "csv":
return DatFormat.CSV;
case "json":
return DatFormat.Json;
case "md5":
return DatFormat.RedumpMD5;
#if NET_FRAMEWORK
case "ripemd160":
return DatFormat.RedumpRIPEMD160;
#endif
case "sfv":
return DatFormat.RedumpSFV;
case "sha1":
return DatFormat.RedumpSHA1;
case "sha256":
return DatFormat.RedumpSHA256;
case "sha384":
return DatFormat.RedumpSHA384;
case "sha512":
return DatFormat.RedumpSHA512;
case "ssv":
return DatFormat.SSV;
case "tsv":
return DatFormat.TSV;
}
// For everything else, we need to read it
try
{
// Get the first two non-whitespace, non-comment lines to check
StreamReader sr = File.OpenText(filename);
string first = sr.ReadLine().ToLowerInvariant();
while (string.IsNullOrWhiteSpace(first) || first.StartsWith("<!--"))
first = sr.ReadLine().ToLowerInvariant();
string second = sr.ReadLine().ToLowerInvariant();
while (string.IsNullOrWhiteSpace(second) || second.StartsWith("<!--"))
second = sr.ReadLine().ToLowerInvariant();
sr.Dispose();
// If we have an XML-based DAT
if (first.Contains("<?xml") && first.Contains("?>"))
{
if (second.StartsWith("<!doctype datafile"))
return DatFormat.Logiqx;
else if (second.StartsWith("<!doctype mame")
|| second.StartsWith("<!doctype m1")
|| second.StartsWith("<mame")
|| second.StartsWith("<m1"))
return DatFormat.Listxml;
else if (second.StartsWith("<!doctype softwaredb"))
return DatFormat.OpenMSX;
else if (second.StartsWith("<!doctype softwarelist"))
return DatFormat.SoftwareList;
else if (second.StartsWith("<!doctype sabredat"))
return DatFormat.SabreDat;
else if ((second.StartsWith("<dat") && !second.StartsWith("<datafile"))
|| second.StartsWith("<?xml-stylesheet"))
return DatFormat.OfflineList;
// Older and non-compliant DATs
else
return DatFormat.Logiqx;
}
// If we have an SMDB (SHA-256, Filename, SHA-1, MD5, CRC32)
else if (Regex.IsMatch(first, @"[0-9a-f]{64}\t.*?\t[0-9a-f]{40}\t[0-9a-f]{32}\t[0-9a-f]{8}"))
return DatFormat.EverdriveSMDB;
// If we have an INI-based DAT
else if (first.Contains("[") && first.Contains("]"))
return DatFormat.RomCenter;
// If we have a listroms DAT
else if (first.StartsWith("roms required for driver"))
return DatFormat.Listrom;
// If we have a CMP-based DAT
else if (first.Contains("clrmamepro"))
return DatFormat.ClrMamePro;
else if (first.Contains("romvault"))
return DatFormat.ClrMamePro;
else if (first.Contains("doscenter"))
return DatFormat.DOSCenter;
else if (first.Contains("#Name;Title;Emulator;CloneOf;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra"))
return DatFormat.AttractMode;
else
return DatFormat.ClrMamePro;
}
catch (Exception)
{
return 0;
}
}
/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
/// Defaults to ASCII when detection of the text file's endianness fails.
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding.</returns>
/// <link>http://stackoverflow.com/questions/3825390/effective-way-to-find-any-files-encoding</link>
public static Encoding GetEncoding(string filename)
{
// Read the BOM
var bom = new byte[4];
FileStream file = FileExtensions.TryOpenRead(filename);
file.Read(bom, 0, 4);
file.Dispose();
// Analyze the BOM
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
return Encoding.Default;
}
/// <summary>
/// Returns the file type of an input file
/// </summary>
/// <param name="input">Input file to check</param>
/// <returns>FileType of inputted file (null on error)</returns>
public static FileType? GetFileType(this string input)
{
FileType? outFileType = null;
// If the file is null, then we have no archive type
if (input == null)
return outFileType;
// First line of defense is going to be the extension, for better or worse
if (!PathExtensions.HasValidArchiveExtension(input))
return outFileType;
// Read the first bytes of the file and get the magic number
try
{
byte[] magic = new byte[8];
BinaryReader br = new BinaryReader(TryOpenRead(input));
magic = br.ReadBytes(8);
br.Dispose();
// Now try to match it to a known signature
if (magic.StartsWith(Constants.SevenZipSignature))
{
outFileType = FileType.SevenZipArchive;
}
else if (magic.StartsWith(Constants.CHDSignature))
{
outFileType = FileType.CHD;
}
else if (magic.StartsWith(Constants.GzSignature))
{
outFileType = FileType.GZipArchive;
}
else if (magic.StartsWith(Constants.LRZipSignature))
{
outFileType = FileType.LRZipArchive;
}
else if (magic.StartsWith(Constants.LZ4Signature)
|| magic.StartsWith(Constants.LZ4SkippableMinSignature)
|| magic.StartsWith(Constants.LZ4SkippableMaxSignature))
{
outFileType = FileType.LZ4Archive;
}
else if (magic.StartsWith(Constants.RarSignature)
|| magic.StartsWith(Constants.RarFiveSignature))
{
outFileType = FileType.RarArchive;
}
else if (magic.StartsWith(Constants.TarSignature)
|| magic.StartsWith(Constants.TarZeroSignature))
{
outFileType = FileType.TapeArchive;
}
else if (magic.StartsWith(Constants.XZSignature))
{
outFileType = FileType.XZArchive;
}
else if (magic.StartsWith(Constants.ZipSignature)
|| magic.StartsWith(Constants.ZipSignatureEmpty)
|| magic.StartsWith(Constants.ZipSignatureSpanned))
{
outFileType = FileType.ZipArchive;
}
else if (magic.StartsWith(Constants.ZPAQSignature))
{
outFileType = FileType.ZPAQArchive;
}
else if (magic.StartsWith(Constants.ZstdSignature))
{
outFileType = FileType.ZstdArchive;
}
}
catch (Exception)
{
// Don't log file open errors
}
return outFileType;
}
/// <summary>
/// Returns if the first byte array starts with the second array
/// </summary>
/// <param name="arr1">First byte array to compare</param>
/// <param name="arr2">Second byte array to compare</param>
/// <param name="exact">True if the input arrays should match exactly, false otherwise (default)</param>
/// <returns>True if the first byte array starts with the second, false otherwise</returns>
private static bool StartsWith(this byte[] arr1, byte[] arr2, bool exact = false)
{
// If we have any invalid inputs, we return false
if (arr1 == null || arr2 == null
|| arr1.Length == 0 || arr2.Length == 0
|| arr2.Length > arr1.Length
|| (exact && arr1.Length != arr2.Length))
{
return false;
}
// Otherwise, loop through and see
for (int i = 0; i < arr2.Length; i++)
{
if (arr1[i] != arr2[i])
return false;
}
return true;
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated (defaults to none)</param>
/// <param name="date">True if the file Date should be included, false otherwise (default)</param>
/// <param name="header">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static BaseFile GetInfo(string input, Hash omitFromScan = 0x0, bool date = false, string header = null, bool chdsAsFiles = true)
{
// Add safeguard if file doesn't exist
if (!File.Exists(input))
return null;
// Get the information from the file stream
BaseFile baseFile;
if (header != null)
{
SkipperRule rule = Skipper.GetMatchingRule(input, Path.GetFileNameWithoutExtension(header));
// If there's a match, get the new information from the stream
if (rule.Tests != null && rule.Tests.Count != 0)
{
// Create the input and output streams
MemoryStream outputStream = new MemoryStream();
FileStream inputStream = TryOpenRead(input);
// Transform the stream and get the information from it
rule.TransformStream(inputStream, outputStream, keepReadOpen: false, keepWriteOpen: true);
baseFile = outputStream.GetInfo(omitFromScan: omitFromScan, keepReadOpen: false, chdsAsFiles: chdsAsFiles);
// Dispose of the streams
outputStream.Dispose();
inputStream.Dispose();
}
// Otherwise, just get the info
else
{
baseFile = TryOpenRead(input).GetInfo(omitFromScan: omitFromScan, keepReadOpen: false, chdsAsFiles: chdsAsFiles);
}
}
else
{
baseFile = TryOpenRead(input).GetInfo(omitFromScan: omitFromScan, keepReadOpen: false, chdsAsFiles: chdsAsFiles);
}
// Add unique data from the file
baseFile.Filename = Path.GetFileName(input);
baseFile.Date = (date ? new FileInfo(input).LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss") : string.Empty);
return baseFile;
}
/// <summary>
/// Get the IniReader associated with a file, if possible
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="validateRows">True if rows should be in a proper format, false if invalid is okay</param>
/// <returns>The IniReader representing the (possibly converted) file, null otherwise</returns>
public static IniReader GetIniReader(this string filename, bool validateRows)
{
Globals.Logger.Verbose($"Attempting to read file: {filename}");
// Check if file exists
if (!File.Exists(filename))
{
Globals.Logger.Warning($"File '{filename}' could not read from!");
return null;
}
IniReader ir = new IniReader(filename)
{
ValidateRows = validateRows
};
return ir;
}
/// <summary>
/// Get the XmlTextReader associated with a file, if possible
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <returns>The XmlTextReader representing the (possibly converted) file, null otherwise</returns>
public static XmlReader GetXmlTextReader(this string filename)
{
Globals.Logger.Verbose($"Attempting to read file: {filename}");
// Check if file exists
if (!File.Exists(filename))
{
Globals.Logger.Warning($"File '{filename}' could not read from!");
return null;
}
XmlReader xtr = XmlReader.Create(filename, new XmlReaderSettings
{
CheckCharacters = false,
DtdProcessing = DtdProcessing.Ignore,
IgnoreComments = true,
IgnoreWhitespace = true,
ValidationFlags = XmlSchemaValidationFlags.None,
ValidationType = ValidationType.None,
});
return xtr;
}
/// <summary>
/// Try to create a file for write, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to create</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>An opened stream representing the file on success, null otherwise</returns>
public static FileStream TryCreate(string file, bool throwOnError = false)
{
// Now wrap opening the file
try
{
return File.Open(file, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return null;
}
}
/// <summary>
/// Try to safely delete a file, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to delete</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>True if the file didn't exist or could be deleted, false otherwise</returns>
public static bool TryDelete(string file, bool throwOnError = false)
{
// Check if the file exists first
if (!File.Exists(file))
return true;
// Now wrap deleting the file
try
{
File.Delete(file);
return true;
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return false;
}
}
/// <summary>
/// Try to open a file for read, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to open</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>An opened stream representing the file on success, null otherwise</returns>
public static FileStream TryOpenRead(string file, bool throwOnError = false)
{
// Check if the file exists first
if (!File.Exists(file))
return null;
// Now wrap opening the file
try
{
return File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return null;
}
}
/// <summary>
/// Try to open a file for read/write, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to open</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>An opened stream representing the file on success, null otherwise</returns>
public static FileStream TryOpenReadWrite(string file, bool throwOnError = false)
{
// Check if the file exists first
if (!File.Exists(file))
return null;
// Now wrap opening the file
try
{
return File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return null;
}
}
/// <summary>
/// Try to open an existing file for write, optionally throwing the error
/// </summary>
/// <param name="file">Name of the file to open</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>An opened stream representing the file on success, null otherwise</returns>
public static FileStream TryOpenWrite(string file, bool throwOnError = false)
{
// Check if the file exists first
if (!File.Exists(file))
return null;
// Now wrap opening the file
try
{
return File.Open(file, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
}
catch (Exception ex)
{
if (throwOnError)
throw ex;
else
return null;
}
}
}
}

View File

@@ -54,7 +54,7 @@ namespace SabreTools.Library.Tools
/// </summary>
public void Stop()
{
Globals.Logger.User($"{_subject} completed in {DateTime.Now.Subtract(_startTime).ToString("hh:mm:ss.fffff")}");
Globals.Logger.User($"{_subject} completed in {DateTime.Now.Subtract(_startTime):G}");
}
}
}

View File

@@ -13,17 +13,17 @@ namespace SabreTools.Library.Tools
public class Logger
{
// Private instance variables
private bool _tofile;
private readonly bool _tofile;
private bool _warnings;
private bool _errors;
private string _filename;
private LogLevel _filter;
private readonly string _filename;
private readonly LogLevel _filter;
private DateTime _start;
private StreamWriter _log;
private object _lock = new object(); // This is used during multithreaded logging
private readonly object _lock = new object(); // This is used during multithreaded logging
// Private required variables
private string _basepath = Path.Combine(Globals.ExeDir, "logs") + Path.DirectorySeparatorChar;
private readonly string _basepath = Path.Combine(Globals.ExeDir, "logs") + Path.DirectorySeparatorChar;
/// <summary>
/// Initialize a console-only logger object
@@ -50,7 +50,7 @@ namespace SabreTools.Library.Tools
_tofile = tofile;
_warnings = false;
_errors = false;
_filename = $"{Path.GetFileNameWithoutExtension(filename)} ({DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss")}).{Utilities.GetExtension(filename)}";
_filename = $"{Path.GetFileNameWithoutExtension(filename)} ({DateTime.Now:yyyy-MM-dd HH-mm-ss}).{PathExtensions.GetNormalizedExtension(filename)}";
_filter = filter;
if (!Directory.Exists(_basepath))
@@ -71,11 +71,13 @@ namespace SabreTools.Library.Tools
try
{
FileStream logfile = Utilities.TryCreate(Path.Combine(_basepath, _filename));
_log = new StreamWriter(logfile, Encoding.UTF8, (int)(4 * Constants.KibiByte), true);
_log.AutoFlush = true;
FileStream logfile = FileExtensions.TryCreate(Path.Combine(_basepath, _filename));
_log = new StreamWriter(logfile, Encoding.UTF8, (int)(4 * Constants.KibiByte), true)
{
AutoFlush = true
};
_log.WriteLine($"Logging started {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
_log.WriteLine($"Logging started {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_log.WriteLine($"Command run: {Globals.CommandLineArgs}");
}
catch
@@ -104,7 +106,7 @@ namespace SabreTools.Library.Tools
TimeSpan span = DateTime.Now.Subtract(_start);
// Special case for multi-day runs
string total = string.Empty;
string total;
if (span >= TimeSpan.FromDays(1))
total = span.ToString(@"d\:hh\:mm\:ss");
else
@@ -118,7 +120,7 @@ namespace SabreTools.Library.Tools
try
{
_log.WriteLine($"Logging ended {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
_log.WriteLine($"Logging ended {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_log.WriteLine($"Total runtime: {total}");
Console.WriteLine($"Total runtime: {total}");
_log.Close();

View File

@@ -0,0 +1,223 @@
using System;
using System.IO;
using SabreTools.Library.Data;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Extensions to Path functionality
/// </summary>
public static class PathExtensions
{
/// <summary>
/// Get the extension from the path, if possible
/// </summary>
/// <param name="path">Path to get extension from</param>
/// <returns>Extension, if possible</returns>
public static string GetNormalizedExtension(string path)
{
// Check null or empty first
if (string.IsNullOrWhiteSpace(path))
return null;
// Get the extension from the path, if possible
string ext = Path.GetExtension(path)?.ToLowerInvariant();
// Check if the extension is null or empty
if (string.IsNullOrWhiteSpace(ext))
return null;
// Make sure that extensions are valid
ext = ext.TrimStart('.');
return ext;
}
/// <summary>
/// Get the proper filename (with subpath) from the file and parent combination
/// </summary>
/// <param name="path">Input combined path to use</param>
/// <param name="sanitize">True if path separators should be converted to '-', false otherwise</param>
/// <returns>Subpath for the file</returns>
public static string GetNormalizedFileName(string path, bool sanitize)
{
// Check that we have a combined path first
if (!path.Contains("¬"))
{
string filename = Path.GetFileName(path);
if (sanitize)
filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-');
return filename;
}
// First separate out the parts
string child = path.Split('¬')[0];
string parent = path.Split('¬')[1];
// If the parts are the same, return the filename from the first part
if (string.Equals(child, parent, StringComparison.Ordinal))
{
string filename = Path.GetFileName(child);
if (sanitize)
filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-');
return filename;
}
// Otherwise, remove the parent from the child and return the remainder
else
{
string filename = child.Remove(0, parent.Length + 1);
if (sanitize)
filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-');
return filename;
}
}
/// <summary>
/// Get the proper output path for a given input file and output directory
/// </summary>
/// <param name="outDir">Output directory to use</param>
/// <param name="inputpath">Input path to create output for</param>
/// <param name="inplace">True if the output file should go to the same input folder, false otherwise</param>
/// <returns>Complete output path</returns>
public static string GetOutputPath(string outDir, string inputpath, bool inplace)
{
// First, we need to ensure the output directory
outDir = DirectoryExtensions.Ensure(outDir);
// Check if we have a split path or not
bool splitpath = inputpath.Contains("¬");
// If we have a split path, we need to treat the input separately
if (splitpath)
{
string[] split = inputpath.Split('¬');
// If we have an inplace output, use the directory name from the input path
if (inplace)
{
outDir = Path.GetDirectoryName(split[0]);
}
// TODO: Should this be the default? Always create a subfolder if a folder is found?
// If we are processing a path that is coming from a directory and we are outputting to the current directory, we want to get the subfolder to write to
else if (split[0].Length != split[1].Length && outDir == Environment.CurrentDirectory)
{
outDir = Path.GetDirectoryName(Path.Combine(outDir, split[0].Remove(0, Path.GetDirectoryName(split[1]).Length + 1)));
}
// If we are processing a path that is coming from a directory, we want to get the subfolder to write to
else if (split[0].Length != split[1].Length)
{
outDir = Path.GetDirectoryName(Path.Combine(outDir, split[0].Remove(0, split[1].Length + 1)));
}
// If we are processing a single file from the root of a directory, we just use the output directory
else
{
// No-op
}
}
// Otherwise, assume the input path is just a filename
else
{
// If we have an inplace output, use the directory name from the input path
if (inplace)
{
outDir = Path.GetDirectoryName(inputpath);
}
// Otherwise, just use the supplied output directory
else
{
// No-op
}
}
// Finally, return the output directory
return outDir;
}
/// <summary>
/// Get a proper romba sub path
/// </summary>
/// <param name="hash">SHA-1 hash to get the path for</param>
/// <returns>Subfolder path for the given hash</returns>
public static string GetRombaPath(string hash)
{
// If the hash isn't the right size, then we return null
if (hash.Length != Constants.SHA1Length) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
return null;
return Path.Combine(hash.Substring(0, 2), hash.Substring(2, 2), hash.Substring(4, 2), hash.Substring(6, 2), hash + ".gz");
}
/// <summary>
/// Get if the given path has a valid DAT extension
/// </summary>
/// <param name="path">Path to check</param>
/// <returns>True if the extension is valid, false otherwise</returns>
public static bool HasValidArchiveExtension(string path)
{
// Get the extension from the path, if possible
string ext = PathExtensions.GetNormalizedExtension(path);
// Check against the list of known archive extensions
switch (ext)
{
case "7z":
case "gz":
case "lzma":
case "rar":
case "rev":
case "r00":
case "r01":
case "tar":
case "tgz":
case "tlz":
case "zip":
case "zipx":
return true;
default:
return false;
}
}
/// <summary>
/// Get if the given path has a valid DAT extension
/// </summary>
/// <param name="path">Path to check</param>
/// <returns>True if the extension is valid, false otherwise</returns>
public static bool HasValidDatExtension(string path)
{
// Get the extension from the path, if possible
string ext = GetNormalizedExtension(path);
// Check against the list of known DAT extensions
switch (ext)
{
case "csv":
case "dat":
case "json":
case "md5":
case "ripemd160":
case "sfv":
case "sha1":
case "sha256":
case "sha384":
case "sha512":
case "ssv":
case "tsv":
case "txt":
case "xml":
return true;
default:
return false;
}
}
}
}

View File

@@ -0,0 +1,413 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SabreTools.Library.Data;
namespace SabreTools.Library.Tools
{
public static class Sanitizer
{
/// <summary>
/// Get a sanitized Date from an input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>Date as a string, if possible</returns>
public static string CleanDate(string input)
{
string date = string.Empty;
if (input != null)
{
if (DateTime.TryParse(input, out DateTime dateTime))
date = dateTime.ToString();
else
date = input;
}
return date;
}
/// <summary>
/// Clean a game (or rom) name to the WoD standard
/// </summary>
/// <param name="game">Name of the game to be cleaned</param>
/// <returns>The cleaned name</returns>
public static string CleanGameName(string game)
{
///Run the name through the filters to make sure that it's correct
game = NormalizeChars(game);
game = RussianToLatin(game);
game = SearchPattern(game);
game = new Regex(@"(([[(].*[\)\]] )?([^([]+))").Match(game).Groups[1].Value;
game = game.TrimStart().TrimEnd();
return game;
}
/// <summary>
/// Clean a game (or rom) name to the WoD standard
/// </summary>
/// <param name="game">Array representing the path to be cleaned</param>
/// <returns>The cleaned name</returns>
public static string CleanGameName(string[] game)
{
#if NET_FRAMEWORK
game[game.Length - 1] = CleanGameName(game.Last());
#else
game[^1] = CleanGameName(game[^1]);
#endif
string outgame = string.Join(Path.DirectorySeparatorChar.ToString(), game);
outgame = outgame.TrimStart().TrimEnd();
return outgame;
}
/// <summary>
/// Clean a CRC32 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanCRC32(string hash)
{
return CleanHashData(hash, Constants.CRCLength);
}
/// <summary>
/// Clean a MD5 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanMD5(string hash)
{
return CleanHashData(hash, Constants.MD5Length);
}
#if NET_FRAMEWORK
/// <summary>
/// Clean a RIPEMD160 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanRIPEMD160(string hash)
{
return CleanHashData(hash, Constants.RIPEMD160Length);
}
#endif
/// <summary>
/// Clean a SHA1 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanSHA1(string hash)
{
return CleanHashData(hash, Constants.SHA1Length);
}
/// <summary>
/// Clean a SHA256 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanSHA256(string hash)
{
return CleanHashData(hash, Constants.SHA256Length);
}
/// <summary>
/// Clean a SHA384 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanSHA384(string hash)
{
return CleanHashData(hash, Constants.SHA384Length);
}
/// <summary>
/// Clean a SHA512 string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanSHA512(string hash)
{
return CleanHashData(hash, Constants.SHA512Length);
}
/// <summary>
/// Clean a hash string and pad to the correct size
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <param name="padding">Amount of characters to pad to</param>
/// <returns>Cleaned string</returns>
private static string CleanHashData(string hash, int padding)
{
// If we have a known blank hash, return blank
if (string.IsNullOrWhiteSpace(hash) || hash == "-" || hash == "_")
return string.Empty;
// Check to see if it's a "hex" hash
hash = hash.Trim().Replace("0x", string.Empty);
// If we have a blank hash now, return blank
if (string.IsNullOrWhiteSpace(hash))
return string.Empty;
// If the hash shorter than the required length, pad it
if (hash.Length < padding)
hash = hash.PadLeft(padding, '0');
// If the hash is longer than the required length, it's invalid
else if (hash.Length > padding)
return string.Empty;
// Now normalize the hash
hash = hash.ToLowerInvariant();
// Otherwise, make sure that every character is a proper match
for (int i = 0; i < hash.Length; i++)
{
if ((hash[i] < '0' || hash[i] > '9') && (hash[i] < 'a' || hash[i] > 'f'))
{
hash = string.Empty;
break;
}
}
return hash;
}
/// <summary>
/// Clean a hash string from a Listrom DAT
/// </summary>
/// <param name="hash">Hash string to sanitize</param>
/// <returns>Cleaned string</returns>
public static string CleanListromHashData(string hash)
{
if (hash.StartsWith("CRC"))
return hash.Substring(4, 8).ToLowerInvariant();
else if (hash.StartsWith("SHA1"))
return hash.Substring(5, 40).ToLowerInvariant();
return hash;
}
/// <summary>
/// Get a sanitized size from an input string
/// </summary>
/// <param name="input">String to get value from</param>
/// <returns>Size as a long, if possible</returns>
public static long CleanSize(string input)
{
long size = -1;
if (input != null && input.Contains("0x"))
size = Convert.ToInt64(input, 16);
else if (input != null)
Int64.TryParse(input, out size);
return size;
}
/// <summary>
/// Remove all chars that are considered path unsafe
/// </summary>
/// <param name="s">Input string to clean</param>
/// <returns>Cleaned string</returns>
public static string RemovePathUnsafeCharacters(string s)
{
List<char> invalidPath = Path.GetInvalidPathChars().ToList();
return new string(s.Where(c => !invalidPath.Contains(c)).ToArray());
}
/// <summary>
/// Remove all unicode-specific chars from a string
/// </summary>
/// <param name="s">Input string to clean</param>
/// <returns>Cleaned string</returns>
public static string RemoveUnicodeCharacters(string s)
{
return new string(s.Where(c => c <= 255).ToArray());
}
/// <summary>
/// Replace accented characters
/// </summary>
/// <param name="input">String to be parsed</param>
/// <returns>String with characters replaced</returns>
private 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>
/// Convert Cyrillic lettering to Latin lettering
/// </summary>
/// <param name="input">String to be parsed</param>
/// <returns>String with characters replaced</returns>
private 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" }, { "Ъ", string.Empty }, { "Ы", "y" }, { "Ь", string.Empty },
{ "Э", "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" },
{ "ъ", string.Empty }, { "ы", "y" }, { "ь", string.Empty }, { "э", "e" }, { "ю", "yu" },
{ "я", "ya" },
};
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>
private 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>
/// Get the multiplier to be used with the size given
/// </summary>
/// <param name="sizestring">String with possible size with extension</param>
/// <returns>Tuple of multiplier to use on final size and fixed size string</returns>
public static long ToSize(string sizestring)
{
// If the string is null or empty, we return -1
if (string.IsNullOrWhiteSpace(sizestring))
return -1;
// Make sure the string is in lower case
sizestring = sizestring.ToLowerInvariant();
// Get any trailing size identifiers
long multiplier = 1;
if (sizestring.EndsWith("k") || sizestring.EndsWith("kb"))
multiplier = Constants.KiloByte;
else if (sizestring.EndsWith("ki") || sizestring.EndsWith("kib"))
multiplier = Constants.KibiByte;
else if (sizestring.EndsWith("m") || sizestring.EndsWith("mb"))
multiplier = Constants.MegaByte;
else if (sizestring.EndsWith("mi") || sizestring.EndsWith("mib"))
multiplier = Constants.MibiByte;
else if (sizestring.EndsWith("g") || sizestring.EndsWith("gb"))
multiplier = Constants.GigaByte;
else if (sizestring.EndsWith("gi") || sizestring.EndsWith("gib"))
multiplier = Constants.GibiByte;
else if (sizestring.EndsWith("t") || sizestring.EndsWith("tb"))
multiplier = Constants.TeraByte;
else if (sizestring.EndsWith("ti") || sizestring.EndsWith("tib"))
multiplier = Constants.TibiByte;
else if (sizestring.EndsWith("p") || sizestring.EndsWith("pb"))
multiplier = Constants.PetaByte;
else if (sizestring.EndsWith("pi") || sizestring.EndsWith("pib"))
multiplier = Constants.PibiByte;
// Remove any trailing identifiers
sizestring = sizestring.TrimEnd(new char[] { 'k', 'm', 'g', 't', 'p', 'i', 'b', ' ' });
// Now try to get the size from the string
if (!Int64.TryParse(sizestring, out long size))
size = -1;
else
size *= multiplier;
return size;
}
}
}

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SabreTools.Library.Data;
using SabreTools.Library.FileTypes;
using Compress.ThreadReaders;
namespace SabreTools.Library.Tools
{
/// <summary>
/// Extensions to Stream functionality
/// </summary>
public static class StreamExtensions
{
/// <summary>
/// Add an aribtrary number of bytes to the inputted stream
/// </summary>
/// <param name="input">Stream to be appended to</param>
/// <param name="output">Outputted stream</param>
/// <param name="bytesToAddToHead">Bytes to be added to head of stream</param>
/// <param name="bytesToAddToTail">Bytes to be added to tail of stream</param>
public static void AppendBytes(Stream input, Stream output, byte[] bytesToAddToHead, byte[] bytesToAddToTail)
{
// Write out prepended bytes
if (bytesToAddToHead != null && bytesToAddToHead.Count() > 0)
output.Write(bytesToAddToHead, 0, bytesToAddToHead.Length);
// Now copy the existing file over
input.CopyTo(output);
// Write out appended bytes
if (bytesToAddToTail != null && bytesToAddToTail.Count() > 0)
output.Write(bytesToAddToTail, 0, bytesToAddToTail.Length);
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="size">Size of the input stream</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated (defaults to none)</param>
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static BaseFile GetInfo(this Stream input, long size = -1, Hash omitFromScan = 0x0, bool keepReadOpen = false, bool chdsAsFiles = true)
{
return GetInfoAsync(input, size, omitFromScan, keepReadOpen, chdsAsFiles).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="size">Size of the input stream</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated (defaults to none)</param>
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static async Task<BaseFile> GetInfoAsync(Stream input, long size = -1, Hash omitFromScan = 0x0, bool keepReadOpen = false, bool chdsAsFiles = true)
{
// If we want to automatically set the size
if (size == -1)
size = input.Length;
// We first check to see if it's a CHD if we have to
if (!chdsAsFiles)
{
var chd = CHDFile.Create(input);
input.SeekIfPossible();
// If we found a valid CHD
if (chd != null)
{
if (!keepReadOpen)
input.Dispose();
return chd;
}
}
BaseFile rom = new BaseFile()
{
Size = size,
};
try
{
// Get a list of hashers to run over the buffer
List<Hasher> hashers = new List<Hasher>()
{
new Hasher(Hash.CRC),
new Hasher(Hash.MD5),
#if NET_FRAMEWORK
new Hasher(Hash.RIPEMD160),
#endif
new Hasher(Hash.SHA1),
new Hasher(Hash.SHA256),
new Hasher(Hash.SHA384),
new Hasher(Hash.SHA512),
};
// Initialize the hashing helpers
var loadBuffer = new ThreadLoadBuffer(input);
int buffersize = 3 * 1024 * 1024;
byte[] buffer0 = new byte[buffersize];
byte[] buffer1 = new byte[buffersize];
/*
Please note that some of the following code is adapted from
RomVault. This is a modified version of how RomVault does
threaded hashing. As such, some of the terminology and code
is the same, though variable names and comments may have
been tweaked to better fit this code base.
*/
// Pre load the first buffer
int next = size > buffersize ? buffersize : (int)size;
input.Read(buffer0, 0, next);
int current = next;
size -= next;
bool bufferSelect = true;
while (current > 0)
{
// Trigger the buffer load on the second buffer
next = size > buffersize ? buffersize : (int)size;
if (next > 0)
loadBuffer.Trigger(bufferSelect ? buffer1 : buffer0, next);
byte[] buffer = bufferSelect ? buffer0 : buffer1;
// Run hashes in parallel
await Task.WhenAll(hashers.Select(h => h.Process(buffer, current)));
// Wait for the load buffer worker, if needed
if (next > 0)
loadBuffer.Wait();
// Setup for the next hashing step
current = next;
size -= next;
bufferSelect = !bufferSelect;
}
// Finalize all hashing helpers
loadBuffer.Finish();
await Task.WhenAll(hashers.Select(h => h.Finalize()));
// Get the results
if (!omitFromScan.HasFlag(Hash.CRC))
rom.CRC = hashers.First(h => h.HashType == Hash.CRC).GetHash();
if (!omitFromScan.HasFlag(Hash.MD5))
rom.MD5 = hashers.First(h => h.HashType == Hash.MD5).GetHash();
#if NET_FRAMEWORK
if (!omitFromScan.HasFlag(Hash.RIPEMD160))
rom.RIPEMD160 = hashers.First(h => h.HashType == Hash.RIPEMD160).GetHash();
#endif
if (!omitFromScan.HasFlag(Hash.SHA1))
rom.SHA1 = hashers.First(h => h.HashType == Hash.SHA1).GetHash();
if (!omitFromScan.HasFlag(Hash.SHA256))
rom.SHA256 = hashers.First(h => h.HashType == Hash.SHA256).GetHash();
if (!omitFromScan.HasFlag(Hash.SHA384))
rom.SHA384 = hashers.First(h => h.HashType == Hash.SHA384).GetHash();
if (!omitFromScan.HasFlag(Hash.SHA512))
rom.SHA512 = hashers.First(h => h.HashType == Hash.SHA512).GetHash();
// Dispose of the hashers
loadBuffer.Dispose();
hashers.ForEach(h => h.Dispose());
}
catch (IOException)
{
return new BaseFile();
}
finally
{
if (!keepReadOpen)
input.Dispose();
else
input.SeekIfPossible();
}
return rom;
}
/// <summary>
/// Seek to a specific point in the stream, if possible
/// </summary>
/// <param name="input">Input stream to try seeking on</param>
/// <param name="offset">Optional offset to seek to</param>
private static long SeekIfPossible(this Stream input, long offset = 0)
{
if (input.CanSeek)
{
try
{
if (offset < 0)
return input.Seek(offset, SeekOrigin.End);
else if (offset >= 0)
return input.Seek(offset, SeekOrigin.Begin);
}
catch (NotSupportedException)
{
Globals.Logger.Verbose("Stream does not support seeking to starting offset. Stream position not changed");
}
catch (NotImplementedException)
{
Globals.Logger.Warning("Stream does not support seeking to starting offset. Stream position not changed");
}
}
return input.Position;
}
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.