[DatFiles/] Create separate classes for each type of DAT

This DOES group some kinds of DAT due to their inherently similar nature. TSV and CSV are under the same "SeparatedValue" umbrella, and all of the SFV, MD5, SHA1, etc are under Hashfile because they're nearly identical. This is just the first stage change, making everything static and making them reference the DatFile separately.
This commit is contained in:
Matt Nadareski
2017-10-09 18:04:49 -07:00
parent 4eb36aa39f
commit 2209d0a13b
16 changed files with 6878 additions and 4594 deletions

View File

@@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamReader = System.IO.StreamReader;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of an AttractMode DAT
/// </summary>
public class AttractMode
{
/// <summary>
/// Parse an AttractMode DAT and return all found games within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc);
sr.ReadLine(); // Skip the first line since it's the header
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
/*
The gameinfo order is as follows
0 - game name
1 - game description
2 - emulator name (filename)
3 - cloneof
4 - year
5 - manufacturer
6 - category
7 - players
8 - rotation
9 - control
10 - status
11 - displaycount
12 - displaytype
13 - alt romname
14 - alt title
15 - extra
16 - buttons
*/
string[] gameinfo = line.Split(';');
Rom rom = new Rom
{
Name = "-",
Size = Constants.SizeZero,
CRC = Constants.CRCZero,
MD5 = Constants.MD5Zero,
SHA1 = Constants.SHA1Zero,
ItemStatus = ItemStatus.None,
MachineName = gameinfo[0],
MachineDescription = gameinfo[1],
CloneOf = gameinfo[3],
Year = gameinfo[4],
Manufacturer = gameinfo[5],
Comment = gameinfo[15],
};
// Now process and add the rom
datFile.ParseAddHelper(rom, clean, remUnicode);
}
sr.Dispose();
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
WriteHeader(sw);
// Write out each of the machines and roms
string lastgame = null;
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem item = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (item.Name == null || item.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != item.MachineName.ToLowerInvariant())
{
WriteStartGame(datFile, sw, item);
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (item.Type == ItemType.Rom
&& ((Rom)item).Size == -1
&& ((Rom)item).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", item.MachineName);
item.Name = (item.Name == "null" ? "-" : item.Name);
((Rom)item).Size = Constants.SizeZero;
((Rom)item).CRC = ((Rom)item).CRC == "null" ? Constants.CRCZero : null;
((Rom)item).MD5 = ((Rom)item).MD5 == "null" ? Constants.MD5Zero : null;
((Rom)item).SHA1 = ((Rom)item).SHA1 == "null" ? Constants.SHA1Zero : null;
((Rom)item).SHA256 = ((Rom)item).SHA256 == "null" ? Constants.SHA256Zero : null;
((Rom)item).SHA384 = ((Rom)item).SHA384 == "null" ? Constants.SHA384Zero : null;
((Rom)item).SHA512 = ((Rom)item).SHA512 == "null" ? Constants.SHA512Zero : null;
}
// Set the new data to compare against
lastgame = item.MachineName;
}
}
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteHeader(StreamWriter sw)
{
try
{
string header = "#Title;Name;Emulator;CloneOf;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra;Buttons\n";
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteStartGame(DatFile datFile, StreamWriter sw, DatItem rom)
{
try
{
// No game should start with a path separator
if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
rom.MachineName = rom.MachineName.Substring(1);
}
string state = rom.MachineName + ";"
+ rom.MachineDescription + ";"
+ datFile.FileName + ";"
+ rom.CloneOf + ";"
+ rom.Year + ";"
+ rom.Manufacturer + ";"
/* + rom.Category */ + ";"
/* + rom.Players */ + ";"
/* + rom.Rotation */ + ";"
/* + rom.Control */ + ";"
/* + rom.Status */ + ";"
/* + rom.DisplayCount */ + ";"
/* + rom.DisplayType */ + ";"
/* + rom.AltRomname */ + ";"
/* + rom.AltTitle */ + ";"
+ rom.Comment + ";"
/* + rom.Buttons */ + "\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,939 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamReader = System.IO.StreamReader;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of a ClrMamePro DAT
/// </summary>
public class ClrMamePro
{
/// <summary>
/// Parse a ClrMamePro DAT and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc);
bool block = false, superdat = false;
string blockname = "", tempgamename = "", gamedesc = "", cloneof = "",
romof = "", sampleof = "", year = "", manufacturer = "";
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
// Comments in CMP DATs start with a #
if (line.Trim().StartsWith("#"))
{
continue;
}
// If the line is the header or a game
if (Regex.IsMatch(line, Constants.HeaderPatternCMP))
{
GroupCollection gc = Regex.Match(line, Constants.HeaderPatternCMP).Groups;
if (gc[1].Value == "clrmamepro" || gc[1].Value == "romvault" || gc[1].Value.ToLowerInvariant() == "doscenter")
{
blockname = "header";
}
block = true;
}
// If the line is a rom-like item and we're in a block
else if ((line.Trim().StartsWith("rom (")
|| line.Trim().StartsWith("disk (")
|| line.Trim().StartsWith("file (")
|| (line.Trim().StartsWith("sample") && !line.Trim().StartsWith("sampleof"))
) && block)
{
ItemType temptype = ItemType.Rom;
if (line.Trim().StartsWith("rom ("))
{
temptype = ItemType.Rom;
}
else if (line.Trim().StartsWith("disk ("))
{
temptype = ItemType.Disk;
}
else if (line.Trim().StartsWith("file ("))
{
temptype = ItemType.Rom;
}
else if (line.Trim().StartsWith("sample"))
{
temptype = ItemType.Sample;
}
// Create the proper DatItem based on the type
DatItem item;
switch (temptype)
{
case ItemType.Archive:
item = new Archive();
break;
case ItemType.BiosSet:
item = new BiosSet();
break;
case ItemType.Disk:
item = new Disk();
break;
case ItemType.Release:
item = new Release();
break;
case ItemType.Sample:
item = new Sample();
break;
case ItemType.Rom:
default:
item = new Rom();
break;
}
// Then populate it with information
item.MachineName = tempgamename;
item.MachineDescription = gamedesc;
item.CloneOf = cloneof;
item.RomOf = romof;
item.SampleOf = sampleof;
item.Manufacturer = manufacturer;
item.Year = year;
item.SystemID = sysid;
item.SourceID = srcid;
// If we have a sample, treat it special
if (temptype == ItemType.Sample)
{
line = line.Trim().Remove(0, 6).Trim().Replace("\"", ""); // Remove "sample" from the input string
item.Name = line;
// Now process and add the sample
datFile.ParseAddHelper(item, clean, remUnicode);
continue;
}
// Get the line split by spaces and quotes
string[] gc = Style.SplitLineAsCMP(line);
// Special cases for DOSCenter DATs only because of how the lines are arranged
if (line.Trim().StartsWith("file ("))
{
// Loop over the specifics
for (int i = 0; i < gc.Length; i++)
{
// Names are not quoted, for some stupid reason
if (gc[i] == "name")
{
// Get the name in order until we find the next flag
while (++i < gc.Length && gc[i] != "size" && gc[i] != "date" && gc[i] != "crc" && gc[i] != "md5"
&& gc[i] != "sha1" && gc[i] != "sha256" && gc[i] != "sha384" && gc[i] != "sha512")
{
item.Name += " " + gc[i];
}
// Perform correction
item.Name = item.Name.TrimStart();
i--;
}
// Get the size from the next part
else if (gc[i] == "size")
{
long tempsize = -1;
if (!Int64.TryParse(gc[++i], out tempsize))
{
tempsize = 0;
}
((Rom)item).Size = tempsize;
}
// Get the date from the next part
else if (gc[i] == "date")
{
((Rom)item).Date = gc[++i].Replace("\"", "") + " " + gc[++i].Replace("\"", "");
}
// Get the CRC from the next part
else if (gc[i] == "crc")
{
((Rom)item).CRC = gc[++i].Replace("\"", "").ToLowerInvariant();
}
// Get the MD5 from the next part
else if (gc[i] == "md5")
{
((Rom)item).MD5 = gc[++i].Replace("\"", "").ToLowerInvariant();
}
// Get the SHA1 from the next part
else if (gc[i] == "sha1")
{
((Rom)item).SHA1 = gc[++i].Replace("\"", "").ToLowerInvariant();
}
// Get the SHA256 from the next part
else if (gc[i] == "sha256")
{
((Rom)item).SHA256 = gc[++i].Replace("\"", "").ToLowerInvariant();
}
// Get the SHA384 from the next part
else if (gc[i] == "sha384")
{
((Rom)item).SHA384 = gc[++i].Replace("\"", "").ToLowerInvariant();
}
// Get the SHA512 from the next part
else if (gc[i] == "sha512")
{
((Rom)item).SHA512 = gc[++i].Replace("\"", "").ToLowerInvariant();
}
}
// Now process and add the rom
datFile.ParseAddHelper(item, clean, remUnicode);
continue;
}
// Loop over all attributes normally and add them if possible
for (int i = 0; i < gc.Length; i++)
{
// Look at the current item and use it if possible
string quoteless = gc[i].Replace("\"", "");
switch (quoteless)
{
//If the item is empty, we automatically skip it because it's a fluke
case "":
continue;
// Special cases for standalone item statuses
case "baddump":
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.BadDump;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.BadDump;
}
break;
case "good":
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Good;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Good;
}
break;
case "nodump":
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Nodump;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Nodump;
}
break;
case "verified":
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Verified;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Verified;
}
break;
// Regular attributes
case "name":
quoteless = gc[++i].Replace("\"", "");
item.Name = quoteless;
break;
case "size":
if (item.Type == ItemType.Rom)
{
quoteless = gc[++i].Replace("\"", "");
if (Int64.TryParse(quoteless, out long size))
{
((Rom)item).Size = size;
}
else
{
((Rom)item).Size = -1;
}
}
break;
case "crc":
if (item.Type == ItemType.Rom)
{
quoteless = gc[++i].Replace("\"", "");
((Rom)item).CRC = quoteless.ToLowerInvariant();
}
break;
case "md5":
if (item.Type == ItemType.Rom)
{
quoteless = gc[++i].Replace("\"", "");
((Rom)item).MD5 = quoteless.ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
i++;
quoteless = gc[i].Replace("\"", "");
((Disk)item).MD5 = quoteless.ToLowerInvariant();
}
break;
case "sha1":
if (item.Type == ItemType.Rom)
{
quoteless = gc[++i].Replace("\"", "");
((Rom)item).SHA1 = quoteless.ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
quoteless = gc[++i].Replace("\"", "");
((Disk)item).SHA1 = quoteless.ToLowerInvariant();
}
break;
case "sha256":
if (item.Type == ItemType.Rom)
{
quoteless = gc[++i].Replace("\"", "");
((Rom)item).SHA256 = quoteless.ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
quoteless = gc[++i].Replace("\"", "");
((Disk)item).SHA256 = quoteless.ToLowerInvariant();
}
break;
case "sha384":
if (item.Type == ItemType.Rom)
{
quoteless = gc[++i].Replace("\"", "");
((Rom)item).SHA384 = quoteless.ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
quoteless = gc[++i].Replace("\"", "");
((Disk)item).SHA384 = quoteless.ToLowerInvariant();
}
break;
case "sha512":
if (item.Type == ItemType.Rom)
{
quoteless = gc[++i].Replace("\"", "");
((Rom)item).SHA512 = quoteless.ToLowerInvariant();
}
else if (item.Type == ItemType.Disk)
{
quoteless = gc[++i].Replace("\"", "");
((Disk)item).SHA512 = quoteless.ToLowerInvariant();
}
break;
case "status":
case "flags":
quoteless = gc[++i].Replace("\"", "");
if (quoteless.ToLowerInvariant() == "good")
{
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Good;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Good;
}
}
else if (quoteless.ToLowerInvariant() == "baddump")
{
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.BadDump;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.BadDump;
}
}
else if (quoteless.ToLowerInvariant() == "nodump")
{
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Nodump;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Nodump;
}
}
else if (quoteless.ToLowerInvariant() == "verified")
{
if (item.Type == ItemType.Rom)
{
((Rom)item).ItemStatus = ItemStatus.Verified;
}
else if (item.Type == ItemType.Disk)
{
((Disk)item).ItemStatus = ItemStatus.Verified;
}
}
break;
case "date":
if (item.Type == ItemType.Rom)
{
// If we have quotes in the next item, assume only one item
if (gc[i + 1].Contains("\""))
{
quoteless = gc[++i].Replace("\"", "");
}
// Otherwise, we assume we need to read the next two items
else
{
quoteless = gc[++i].Replace("\"", "") + " " + gc[++i].Replace("\"", "");
}
((Rom)item).Date = quoteless;
}
break;
}
}
// Now process and add the rom
datFile.ParseAddHelper(item, clean, remUnicode);
}
// If the line is anything but a rom or disk and we're in a block
else if (Regex.IsMatch(line, Constants.ItemPatternCMP) && block)
{
GroupCollection gc = Regex.Match(line, Constants.ItemPatternCMP).Groups;
if (blockname != "header")
{
string itemval = gc[2].Value.Replace("\"", "");
switch (gc[1].Value)
{
case "name":
tempgamename = (itemval.ToLowerInvariant().EndsWith(".zip") ? itemval.Remove(itemval.Length - 4) : itemval);
break;
case "description":
gamedesc = itemval;
break;
case "romof":
romof = itemval;
break;
case "cloneof":
cloneof = itemval;
break;
case "year":
year = itemval;
break;
case "manufacturer":
manufacturer = itemval;
break;
case "sampleof":
sampleof = itemval;
break;
}
}
else
{
string itemval = gc[2].Value.Replace("\"", "");
if (line.Trim().StartsWith("Name:"))
{
datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? line.Substring(6) : datFile.Name);
superdat = superdat || itemval.Contains(" - SuperDAT");
if (keep && superdat)
{
datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? "SuperDAT" : datFile.Type);
}
continue;
}
switch (gc[1].Value)
{
case "name":
case "Name:":
datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? itemval : datFile.Name);
superdat = superdat || itemval.Contains(" - SuperDAT");
if (keep && superdat)
{
datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? "SuperDAT" : datFile.Type);
}
break;
case "description":
case "Description:":
datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? itemval : datFile.Description);
break;
case "rootdir":
datFile.RootDir = (String.IsNullOrEmpty(datFile.RootDir) ? itemval : datFile.RootDir);
break;
case "category":
datFile.Category = (String.IsNullOrEmpty(datFile.Category) ? itemval : datFile.Category);
break;
case "version":
case "Version:":
datFile.Version = (String.IsNullOrEmpty(datFile.Version) ? itemval : datFile.Version);
break;
case "date":
case "Date:":
datFile.Date = (String.IsNullOrEmpty(datFile.Date) ? itemval : datFile.Date);
break;
case "author":
case "Author:":
datFile.Author = (String.IsNullOrEmpty(datFile.Author) ? itemval : datFile.Author);
break;
case "email":
datFile.Email = (String.IsNullOrEmpty(datFile.Email) ? itemval : datFile.Email);
break;
case "homepage":
case "Homepage:":
datFile.Homepage = (String.IsNullOrEmpty(datFile.Homepage) ? itemval : datFile.Homepage);
break;
case "url":
datFile.Url = (String.IsNullOrEmpty(datFile.Url) ? itemval : datFile.Url);
break;
case "comment":
case "Comment:":
datFile.Comment = (String.IsNullOrEmpty(datFile.Comment) ? itemval : datFile.Comment);
break;
case "header":
datFile.Header = (String.IsNullOrEmpty(datFile.Header) ? itemval : datFile.Header);
break;
case "type":
datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? itemval : datFile.Type);
superdat = superdat || itemval.Contains("SuperDAT");
break;
case "forcemerging":
if (datFile.ForceMerging == ForceMerging.None)
{
switch (itemval)
{
case "none":
datFile.ForceMerging = ForceMerging.None;
break;
case "split":
datFile.ForceMerging = ForceMerging.Split;
break;
case "merged":
datFile.ForceMerging = ForceMerging.Merged;
break;
case "nonmerged":
datFile.ForceMerging = ForceMerging.NonMerged;
break;
case "full":
datFile.ForceMerging = ForceMerging.Full;
break;
}
}
break;
case "forcezipping":
if (datFile.ForcePacking == ForcePacking.None)
{
switch (itemval)
{
case "yes":
datFile.ForcePacking = ForcePacking.Zip;
break;
case "no":
datFile.ForcePacking = ForcePacking.Unzip;
break;
}
}
break;
case "forcepacking":
if (datFile.ForcePacking == ForcePacking.None)
{
switch (itemval)
{
case "zip":
datFile.ForcePacking = ForcePacking.Zip;
break;
case "unzip":
datFile.ForcePacking = ForcePacking.Unzip;
break;
}
}
break;
}
}
}
// If we find an end bracket that's not associated with anything else, the block is done
else if (Regex.IsMatch(line, Constants.EndPatternCMP) && block)
{
block = false;
blockname = ""; tempgamename = ""; gamedesc = ""; cloneof = "";
romof = ""; sampleof = ""; year = ""; manufacturer = "";
}
}
sr.Dispose();
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
WriteHeader(datFile, sw);
// Write out each of the machines and roms
string lastgame = null;
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// 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(sw, rom);
}
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
{
WriteStartGame(datFile, sw, rom);
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
// If we're in a mode that doesn't allow for actual empty folders, add the blank info
rom.Name = (rom.Name == "null" ? "-" : rom.Name);
((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;
((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;
((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null;
}
// Now, output the rom data
WriteRomData(sw, rom, ignoreblanks);
// Set the new data to compare against
lastgame = rom.MachineName;
}
}
// Write the file footer out
WriteFooter(sw);
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteHeader(DatFile datFile, StreamWriter sw)
{
try
{
string header = "clrmamepro (\n" +
"\tname \"" + datFile.Name + "\"\n" +
"\tdescription \"" + datFile.Description + "\"\n" +
(!String.IsNullOrEmpty(datFile.Category) ? "\tcategory \"" + datFile.Category + "\"\n" : "") +
"\tversion \"" + datFile.Version + "\"\n" +
(!String.IsNullOrEmpty(datFile.Date) ? "\tdate \"" + datFile.Date + "\"\n" : "") +
"\tauthor \"" + datFile.Author + "\"\n" +
(!String.IsNullOrEmpty(datFile.Email) ? "\temail \"" + datFile.Email + "\"\n" : "") +
(!String.IsNullOrEmpty(datFile.Homepage) ? "\thomepage \"" + datFile.Homepage + "\"\n" : "") +
(!String.IsNullOrEmpty(datFile.Url) ? "\turl \"" + datFile.Url + "\"\n" : "") +
(!String.IsNullOrEmpty(datFile.Comment) ? "\tcomment \"" + datFile.Comment + "\"\n" : "") +
(datFile.ForcePacking == ForcePacking.Unzip ? "\tforcezipping no\n" : "") +
(datFile.ForcePacking == ForcePacking.Zip ? "\tforcezipping yes\n" : "") +
(datFile.ForceMerging == ForceMerging.Full ? "\tforcemerging full\n" : "") +
(datFile.ForceMerging == ForceMerging.Split ? "\tforcemerging split\n" : "") +
(datFile.ForceMerging == ForceMerging.Merged ? "\tforcemerging merged\n" : "") +
(datFile.ForceMerging == ForceMerging.NonMerged ? "\tforcemerging nonmerged\n" : "") +
")\n";
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteStartGame(DatFile datFile, StreamWriter sw, DatItem rom)
{
try
{
// No game should start with a path separator
if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
rom.MachineName = rom.MachineName.Substring(1);
}
string state = "game (\n\tname \"" + rom.MachineName + "\"\n" +
(datFile.ExcludeOf ? "" :
(String.IsNullOrEmpty(rom.RomOf) ? "" : "\tromof \"" + rom.RomOf + "\"\n") +
(String.IsNullOrEmpty(rom.CloneOf) ? "" : "\tcloneof \"" + rom.CloneOf + "\"\n") +
(String.IsNullOrEmpty(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n")
) +
"\tdescription \"" + (String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription) + "\"\n" +
(String.IsNullOrEmpty(rom.Year) ? "" : "\tyear " + rom.Year + "\n") +
(String.IsNullOrEmpty(rom.Manufacturer) ? "" : "\tmanufacturer \"" + rom.Manufacturer + "\"\n");
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game end using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteEndGame(StreamWriter sw, DatItem rom)
{
try
{
string state = (String.IsNullOrEmpty(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + ")\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(StreamWriter sw, DatItem rom, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "";
switch (rom.Type)
{
case ItemType.Archive:
state += "\tarchive ( name\"" + rom.Name + "\""
+ " )\n";
break;
case ItemType.BiosSet:
state += "\tbiosset ( name\"" + rom.Name + "\""
+ (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description \"" + ((BiosSet)rom).Description + "\"" : "")
+ (((BiosSet)rom).Default != null
? "default " + ((BiosSet)rom).Default.ToString().ToLowerInvariant()
: "")
+ " )\n";
break;
case ItemType.Disk:
state += "\tdisk ( name \"" + rom.Name + "\""
+ (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5 " + ((Disk)rom).MD5.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1 " + ((Disk)rom).SHA1.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA256) ? " sha256 " + ((Disk)rom).SHA256.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA384) ? " sha384 " + ((Disk)rom).SHA384.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA512) ? " sha512 " + ((Disk)rom).SHA512.ToLowerInvariant() : "")
+ (((Disk)rom).ItemStatus != ItemStatus.None ? " flags " + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() : "")
+ " )\n";
break;
case ItemType.Release:
state += "\trelease ( name\"" + rom.Name + "\""
+ (!String.IsNullOrEmpty(((Release)rom).Region) ? " region \"" + ((Release)rom).Region + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Language) ? " language \"" + ((Release)rom).Language + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Date) ? " date \"" + ((Release)rom).Date + "\"" : "")
+ (((Release)rom).Default != null
? "default " + ((Release)rom).Default.ToString().ToLowerInvariant()
: "")
+ " )\n";
break;
case ItemType.Rom:
state += "\trom ( name \"" + rom.Name + "\""
+ (((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5 " + ((Rom)rom).MD5.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1 " + ((Rom)rom).SHA1.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA256) ? " sha256 " + ((Rom)rom).SHA256.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA384) ? " sha384 " + ((Rom)rom).SHA384.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA512) ? " sha512 " + ((Rom)rom).SHA512.ToLowerInvariant() : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date \"" + ((Rom)rom).Date + "\"" : "")
+ (((Rom)rom).ItemStatus != ItemStatus.None ? " flags " + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() : "")
+ " )\n";
break;
case ItemType.Sample:
state += "\tsample ( name\"" + rom.Name + "\""
+ " )\n";
break;
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteFooter(StreamWriter sw)
{
try
{
string footer = footer = ")\n";
// Write the footer out
sw.Write(footer);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,20 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamWriter = System.IO.StreamWriter;
#endif
namespace SabreTools.Library.DatFiles
{
@@ -1441,6 +1453,538 @@ namespace SabreTools.Library.DatFiles
#endregion
#region Parsing
/// <summary>
/// Parse a DAT and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>
/// <param name="datdata">The DatData object representing found roms to this point</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="descAsName">True if descriptions should be used as names, false otherwise (default)</param>
/// <param name="keepext">True if original extension should be kept, false otherwise (default)</param>
/// <param name="useTags">True if tags from the DAT should be used to merge the output, false otherwise (default)</param>
public void Parse(string filename, int sysid, int srcid, bool keep = false, bool clean = false,
bool remUnicode = false, bool descAsName = false, bool keepext = false, bool useTags = false)
{
Parse(filename, sysid, srcid, SplitType.None, keep: keep, clean: clean,
remUnicode: remUnicode, descAsName: descAsName, keepext: keepext, useTags: useTags);
}
/// <summary>
/// Parse a DAT and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="sysid">System ID for the DAT</param>
/// <param name="srcid">Source ID for the DAT</param>>
/// <param name="splitType">Type of the split that should be performed (split, merged, fully merged)</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="descAsName">True if descriptions should be used as names, false otherwise (default)</param>
/// <param name="keepext">True if original extension should be kept, false otherwise (default)</param>
/// <param name="useTags">True if tags from the DAT should be used to merge the output, false otherwise (default)</param>
public void Parse(
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Rom renaming
SplitType splitType,
// Miscellaneous
bool keep = false,
bool clean = false,
bool remUnicode = false,
bool descAsName = false,
bool keepext = false,
bool useTags = false)
{
// Check the file extension first as a safeguard
string ext = Path.GetExtension(filename).ToLowerInvariant();
if (ext.StartsWith("."))
{
ext = ext.Substring(1);
}
if (ext != "dat" && ext != "csv" && ext != "md5" && ext != "sfv" && ext != "sha1" && ext != "sha256"
&& ext != "sha384" && ext != "sha512" && ext != "tsv" && ext != "txt" && ext != "xml")
{
return;
}
// If the output filename isn't set already, get the internal filename
FileName = (String.IsNullOrEmpty(FileName) ? (keepext ? Path.GetFileName(filename) : Path.GetFileNameWithoutExtension(filename)) : FileName);
// If the output type isn't set already, get the internal output type
DatFormat = (DatFormat == 0 ? FileTools.GetDatFormat(filename) : DatFormat);
// Now parse the correct type of DAT
try
{
switch (FileTools.GetDatFormat(filename))
{
case DatFormat.AttractMode:
AttractMode.Parse(this, filename, sysid, srcid, keep, clean, remUnicode);
break;
case DatFormat.ClrMamePro:
ClrMamePro.Parse(this, filename, sysid, srcid, keep, clean, remUnicode);
break;
case DatFormat.CSV:
SeparatedValue.Parse(this, filename, sysid, srcid, ',', keep, clean, remUnicode);
break;
case DatFormat.DOSCenter:
DosCenter.Parse(this, filename, sysid, srcid, keep, clean, remUnicode);
break;
case DatFormat.Listroms:
Listroms.Parse(this, filename, sysid, srcid, keep, clean, remUnicode);
break;
case DatFormat.Logiqx:
Logiqx.Parse(this, filename, sysid, srcid, keep, clean, remUnicode);
break;
case DatFormat.OfflineList:
OfflineList.Parse(this, filename, sysid, srcid, keep, clean, remUnicode);
break;
case DatFormat.RedumpMD5:
Hashfile.Parse(this, filename, sysid, srcid, Hash.MD5, clean, remUnicode);
break;
case DatFormat.RedumpSFV:
Hashfile.Parse(this, filename, sysid, srcid, Hash.CRC, clean, remUnicode);
break;
case DatFormat.RedumpSHA1:
Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA1, clean, remUnicode);
break;
case DatFormat.RedumpSHA256:
Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA256, clean, remUnicode);
break;
case DatFormat.RedumpSHA384:
Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA384, clean, remUnicode);
break;
case DatFormat.RedumpSHA512:
Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA512, clean, remUnicode);
break;
case DatFormat.RomCenter:
RomCenter.Parse(this, filename, sysid, srcid, clean, remUnicode);
break;
case DatFormat.SabreDat:
SabreDat.Parse(this, filename, sysid, srcid, keep, clean, remUnicode);
break;
case DatFormat.SoftwareList:
SoftwareList.Parse(this, filename, sysid, srcid, keep, clean, remUnicode);
break;
case DatFormat.TSV:
SeparatedValue.Parse(this, filename, sysid, srcid, '\t', keep, clean, remUnicode);
break;
default:
return;
}
}
catch (Exception ex)
{
Globals.Logger.Error("Error with file '{0}': {1}", filename, ex);
}
// If we want to use descriptions as names, update everything
if (descAsName)
{
MachineDescriptionToName();
}
// If we are using tags from the DAT, set the proper input for split type unless overridden
if (useTags && splitType == SplitType.None)
{
switch (ForceMerging)
{
case ForceMerging.None:
// No-op
break;
case ForceMerging.Split:
splitType = SplitType.Split;
break;
case ForceMerging.Merged:
splitType = SplitType.Merged;
break;
case ForceMerging.NonMerged:
splitType = SplitType.NonMerged;
break;
case ForceMerging.Full:
splitType = SplitType.FullNonMerged;
break;
}
}
// Now we pre-process the DAT with the splitting/merging mode
switch (splitType)
{
case SplitType.None:
// No-op
break;
case SplitType.DeviceNonMerged:
CreateDeviceNonMergedSets(DedupeType.None);
break;
case SplitType.FullNonMerged:
CreateFullyNonMergedSets(DedupeType.None);
break;
case SplitType.NonMerged:
CreateNonMergedSets(DedupeType.None);
break;
case SplitType.Merged:
CreateMergedSets(DedupeType.None);
break;
case SplitType.Split:
CreateSplitSets(DedupeType.None);
break;
}
}
/// <summary>
/// Add a rom to the Dat after checking
/// </summary>
/// <param name="item">Item data to check against</param>
/// <param name="clean">True if the names should be cleaned to WoD standards, false otherwise</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <returns>The key for the item</returns>
public string ParseAddHelper(DatItem item, bool clean, bool remUnicode)
{
string key = "";
// If there's no name in the rom, we log and skip it
if (item.Name == null)
{
Globals.Logger.Warning("{0}: Rom with no name found! Skipping...", FileName);
return key;
}
// If the name ends with a directory separator, we log and skip it (DOSCenter only?)
if (item.Name.EndsWith("/") || item.Name.EndsWith("\\"))
{
Globals.Logger.Warning("{0}: Rom ending with directory separator found: '{1}'. Skipping...", FileName, item.Name);
return key;
}
// If we're in cleaning mode, sanitize the game name
item.MachineName = (clean ? Style.CleanGameName(item.MachineName) : item.MachineName);
// If we're stripping unicode characters, do so from all relevant things
if (remUnicode)
{
item.Name = Style.RemoveUnicodeCharacters(item.Name);
item.MachineName = Style.RemoveUnicodeCharacters(item.MachineName);
item.MachineDescription = Style.RemoveUnicodeCharacters(item.MachineDescription);
}
// If we have a Rom or a Disk, clean the hash data
if (item.Type == ItemType.Rom)
{
Rom itemRom = (Rom)item;
// Sanitize the hashes from null, hex sizes, and "true blank" strings
itemRom.CRC = Style.CleanHashData(itemRom.CRC, Constants.CRCLength);
itemRom.MD5 = Style.CleanHashData(itemRom.MD5, Constants.MD5Length);
itemRom.SHA1 = Style.CleanHashData(itemRom.SHA1, Constants.SHA1Length);
itemRom.SHA256 = Style.CleanHashData(itemRom.SHA256, Constants.SHA256Length);
itemRom.SHA384 = Style.CleanHashData(itemRom.SHA384, Constants.SHA384Length);
itemRom.SHA512 = Style.CleanHashData(itemRom.SHA512, Constants.SHA512Length);
// If we have a rom and it's missing size AND the hashes match a 0-byte file, fill in the rest of the info
if ((itemRom.Size == 0 || itemRom.Size == -1)
&& ((itemRom.CRC == Constants.CRCZero || String.IsNullOrEmpty(itemRom.CRC))
|| itemRom.MD5 == Constants.MD5Zero
|| itemRom.SHA1 == Constants.SHA1Zero
|| itemRom.SHA256 == Constants.SHA256Zero
|| itemRom.SHA384 == Constants.SHA384Zero
|| itemRom.SHA512 == Constants.SHA512Zero))
{
// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
itemRom.Size = Constants.SizeZero;
itemRom.CRC = Constants.CRCZero;
itemRom.MD5 = Constants.MD5Zero;
itemRom.SHA1 = Constants.SHA1Zero;
itemRom.SHA256 = null;
itemRom.SHA384 = null;
itemRom.SHA512 = null;
//itemRom.SHA256 = Constants.SHA256Zero;
//itemRom.SHA384 = Constants.SHA384Zero;
//itemRom.SHA512 = Constants.SHA512Zero;
}
// If the file has no size and it's not the above case, skip and log
else if (itemRom.ItemStatus != ItemStatus.Nodump && (itemRom.Size == 0 || itemRom.Size == -1))
{
Globals.Logger.Verbose("{0}: Incomplete entry for '{1}' will be output as nodump", FileName, itemRom.Name);
itemRom.ItemStatus = ItemStatus.Nodump;
}
// If the file has a size but aboslutely no hashes, skip and log
else if (itemRom.ItemStatus != ItemStatus.Nodump
&& itemRom.Size > 0
&& String.IsNullOrEmpty(itemRom.CRC)
&& String.IsNullOrEmpty(itemRom.MD5)
&& String.IsNullOrEmpty(itemRom.SHA1)
&& String.IsNullOrEmpty(itemRom.SHA256)
&& String.IsNullOrEmpty(itemRom.SHA384)
&& String.IsNullOrEmpty(itemRom.SHA512))
{
Globals.Logger.Verbose("{0}: Incomplete entry for '{1}' will be output as nodump", FileName, itemRom.Name);
itemRom.ItemStatus = ItemStatus.Nodump;
}
item = itemRom;
}
else if (item.Type == ItemType.Disk)
{
Disk itemDisk = (Disk)item;
// Sanitize the hashes from null, hex sizes, and "true blank" strings
itemDisk.MD5 = Style.CleanHashData(itemDisk.MD5, Constants.MD5Length);
itemDisk.SHA1 = Style.CleanHashData(itemDisk.SHA1, Constants.SHA1Length);
itemDisk.SHA256 = Style.CleanHashData(itemDisk.SHA256, Constants.SHA256Length);
itemDisk.SHA384 = Style.CleanHashData(itemDisk.SHA384, Constants.SHA384Length);
itemDisk.SHA512 = Style.CleanHashData(itemDisk.SHA512, Constants.SHA512Length);
// If the file has aboslutely no hashes, skip and log
if (itemDisk.ItemStatus != ItemStatus.Nodump
&& String.IsNullOrEmpty(itemDisk.MD5)
&& String.IsNullOrEmpty(itemDisk.SHA1)
&& String.IsNullOrEmpty(itemDisk.SHA256)
&& String.IsNullOrEmpty(itemDisk.SHA384)
&& String.IsNullOrEmpty(itemDisk.SHA512))
{
Globals.Logger.Verbose("Incomplete entry for '{0}' will be output as nodump", itemDisk.Name);
itemDisk.ItemStatus = ItemStatus.Nodump;
}
item = itemDisk;
}
// Get the key and add statistical data
switch (item.Type)
{
case ItemType.Archive:
case ItemType.BiosSet:
case ItemType.Release:
case ItemType.Sample:
key = item.Type.ToString();
break;
case ItemType.Disk:
key = ((Disk)item).MD5;
break;
case ItemType.Rom:
key = ((Rom)item).Size + "-" + ((Rom)item).CRC;
break;
default:
key = "default";
break;
}
// Add the item to the DAT
Add(key, item);
return key;
}
/// <summary>
/// Add a rom to the Dat after checking
/// </summary>
/// <param name="item">Item data to check against</param>
/// <param name="clean">True if the names should be cleaned to WoD standards, false otherwise</param>
/// <param name="remUnicode">True if we should remove non-ASCII characters from output, false otherwise (default)</param>
/// <returns>The key for the item</returns>
public async Task<string> ParseAddHelperAsync(DatItem item, bool clean, bool remUnicode)
{
return await Task.Run(() => ParseAddHelper(item, clean, remUnicode));
}
#endregion
#region Writing
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datdata">All information for creating the datfile header</param>
/// <param name="outDir">Set the output directory</param>
/// <param name="norename">True if games should only be compared on game and file name (default), false if system and source are counted</param>
/// <param name="stats">True if DAT statistics should be output on write, false otherwise (default)</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <param name="overwrite">True if files should be overwritten (default), false if they should be renamed instead</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public bool WriteToFile(string outDir, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true)
{
// If there's nothing there, abort
if (Count == 0)
{
Globals.Logger.User("There were no items to write out!");
return false;
}
// If output directory is empty, use the current folder
if (outDir == null || outDir.Trim() == "")
{
Globals.Logger.Verbose("No output directory defined, defaulting to curent folder");
outDir = Environment.CurrentDirectory;
}
// Create the output directory if it doesn't already exist
if (!Directory.Exists(outDir))
{
Directory.CreateDirectory(outDir);
}
// If the DAT has no output format, default to XML
if (DatFormat == 0)
{
Globals.Logger.Verbose("No DAT format defined, defaulting to XML");
DatFormat = DatFormat.Logiqx;
}
// Make sure that the three essential fields are filled in
if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
FileName = Name = Description = "Default";
}
else if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
FileName = Name = Description;
}
else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
FileName = Description = Name;
}
else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
FileName = Description;
}
else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
Name = Description = FileName;
}
else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
Name = Description;
}
else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description))
{
Description = Name;
}
else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description))
{
// Nothing is needed
}
// Output initial statistics, for kicks
if (stats)
{
OutputStats(new Dictionary<StatDatFormat, StreamWriter>(), StatDatFormat.None,
recalculate: (RomCount + DiskCount == 0), baddumpCol: true, nodumpCol: true);
}
// Bucket and dedupe according to the flag
if (DedupeRoms == DedupeType.Full)
{
BucketBy(SortedBy.CRC, DedupeRoms, norename: norename);
}
else if (DedupeRoms == DedupeType.Game)
{
BucketBy(SortedBy.Game, DedupeRoms, norename: norename);
}
// Bucket roms by game name, if not already
BucketBy(SortedBy.Game, DedupeType.None, norename: norename);
// Output the number of items we're going to be writing
Globals.Logger.User("A total of {0} items will be written out to '{1}'", Count, FileName);
// Filter the DAT by 1G1R rules, if we're supposed to
// TODO: Create 1G1R logic before write
// If we are removing hashes, do that now
if (StripHash != 0x0)
{
StripHashesFromItems();
}
// Get the outfile names
Dictionary<DatFormat, string> outfiles = Style.CreateOutfileNames(outDir, this, overwrite);
try
{
// Write out all required formats
Parallel.ForEach(outfiles.Keys, Globals.ParallelOptions, datFormat =>
{
string outfile = outfiles[datFormat];
switch (datFormat)
{
case DatFormat.AttractMode:
AttractMode.WriteToFile(this, outfile);
break;
case DatFormat.ClrMamePro:
ClrMamePro.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.CSV:
SeparatedValue.WriteToFile(this, outfile, ',', ignoreblanks);
break;
case DatFormat.DOSCenter:
DosCenter.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.Listroms:
Listroms.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.Logiqx:
Logiqx.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.MissFile:
Missfile.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.OfflineList:
OfflineList.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.RedumpMD5:
Hashfile.WriteToFile(this, outfile, Hash.MD5, ignoreblanks);
break;
case DatFormat.RedumpSFV:
Hashfile.WriteToFile(this, outfile, Hash.CRC, ignoreblanks);
break;
case DatFormat.RedumpSHA1:
Hashfile.WriteToFile(this, outfile, Hash.SHA1, ignoreblanks);
break;
case DatFormat.RedumpSHA256:
Hashfile.WriteToFile(this, outfile, Hash.SHA256, ignoreblanks);
break;
case DatFormat.RedumpSHA384:
Hashfile.WriteToFile(this, outfile, Hash.SHA384, ignoreblanks);
break;
case DatFormat.RedumpSHA512:
Hashfile.WriteToFile(this, outfile, Hash.SHA512, ignoreblanks);
break;
case DatFormat.RomCenter:
RomCenter.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.SabreDat:
SabreDat.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.SoftwareList:
SoftwareList.WriteToFile(this, outfile, ignoreblanks);
break;
case DatFormat.TSV:
SeparatedValue.WriteToFile(this, outfile, '\t', ignoreblanks);
break;
}
});
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
#endregion
#endregion // Instance Methods
}
}

View File

@@ -0,0 +1,322 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of a DosCenter DAT
/// </summary>
public class DosCenter
{
/// <summary>
/// Parse a DosCenter DAT and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// ClrMamePro and DosCenter parsing are identical so it just calls one implementation
ClrMamePro.Parse(datFile, filename, sysid, srcid, keep, clean, remUnicode);
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
WriteHeader(datFile, sw);
// Write out each of the machines and roms
string lastgame = null;
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
List<string> newsplit = rom.MachineName.Split('\\').ToList();
// If we have a different game and we're not at the start of the list, output the end of last item
if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
{
WriteEndGame(sw, rom);
}
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
{
WriteStartGame(sw, rom);
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
rom.Name = (rom.Name == "null" ? "-" : rom.Name);
((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;
((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;
((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null;
}
// Now, output the rom data
WriteRomData(sw, rom, ignoreblanks);
// Set the new data to compare against
lastgame = rom.MachineName;
}
}
// Write the file footer out
WriteFooter(sw);
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteHeader(DatFile datFile, StreamWriter sw)
{
try
{
string header = "DOSCenter (\n" +
"\tName: " + datFile.Name + "\n" +
"\tDescription: " + datFile.Description + "\n" +
"\tVersion: " + datFile.Version + "\n" +
"\tDate: " + datFile.Date + "\n" +
"\tAuthor: " + datFile.Author + "\n" +
"\tHomepage: " + datFile.Homepage + "\n" +
"\tComment: " + datFile.Comment + "\n" +
")\n";
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteStartGame(StreamWriter sw, DatItem rom)
{
try
{
// No game should start with a path separator
if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
rom.MachineName = rom.MachineName.Substring(1);
}
string state = "game (\n\tname \"" + rom.MachineName + ".zip\"\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game end using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteEndGame(StreamWriter sw, DatItem rom)
{
try
{
string state = (String.IsNullOrEmpty(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + ")\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(StreamWriter sw, DatItem rom, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "";
switch (rom.Type)
{
case ItemType.Archive:
case ItemType.BiosSet:
case ItemType.Disk:
case ItemType.Release:
case ItemType.Sample:
// We don't output these at all for DosCenter
break;
case ItemType.Rom:
state += "\tfile ( name " + ((Rom)rom).Name
+ (((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date " + ((Rom)rom).Date : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "")
+ " )\n";
break;
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteFooter(StreamWriter sw)
{
try
{
string footer = ")\n";
// Write the footer out
sw.Write(footer);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,270 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamReader = System.IO.StreamReader;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of a hashfile such as an SFV, MD5, or SHA-1 file
/// </summary>
public class Hashfile
{
/// <summary>
/// Parse a hashfile or SFV and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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="hashtype">Hash type that should be assumed</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 static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Specific to hash files
Hash hashtype,
// Miscellaneous
bool clean,
bool remUnicode)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc);
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
// Split the line and get the name and hash
string[] split = line.Split(' ');
string name = "";
string hash = "";
// If we have CRC, then it's an SFV file and the name is first are
if ((hashtype & Hash.CRC) != 0)
{
name = split[0].Replace("*", String.Empty);
hash = split[1];
}
// Otherwise, the name is second
else
{
name = split[1].Replace("*", String.Empty);
hash = split[0];
}
Rom rom = new Rom
{
Name = name,
Size = -1,
CRC = ((hashtype & Hash.CRC) != 0 ? hash : null),
MD5 = ((hashtype & Hash.MD5) != 0 ? hash : null),
SHA1 = ((hashtype & Hash.SHA1) != 0 ? hash : null),
SHA256 = ((hashtype & Hash.SHA256) != 0 ? hash : null),
SHA384 = ((hashtype & Hash.SHA384) != 0 ? hash : null),
SHA512 = ((hashtype & Hash.SHA512) != 0 ? hash : null),
ItemStatus = ItemStatus.None,
MachineName = Path.GetFileNameWithoutExtension(filename),
SystemID = sysid,
SourceID = srcid,
};
// Now process and add the rom
datFile.ParseAddHelper(rom, clean, remUnicode);
}
sr.Dispose();
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="hash">Hash that should be written out</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, Hash hash, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
}
// Now, output the rom data
WriteRomData(datFile, sw, hash, rom, ignoreblanks);
}
}
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="hash">Hash that should be written out</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(DatFile datFile, StreamWriter sw, Hash hash, DatItem rom, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "";
switch (hash)
{
case Hash.MD5:
if (rom.Type == ItemType.Rom)
{
state += ((Rom)rom).MD5 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
else if (rom.Type == ItemType.Disk)
{
state += ((Disk)rom).MD5 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
break;
case Hash.CRC:
if (rom.Type == ItemType.Rom)
{
state += (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + " " + ((Rom)rom).CRC + "\n";
}
break;
case Hash.SHA1:
if (rom.Type == ItemType.Rom)
{
state += ((Rom)rom).SHA1 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
else if (rom.Type == ItemType.Disk)
{
state += ((Disk)rom).SHA1 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
break;
case Hash.SHA256:
if (rom.Type == ItemType.Rom)
{
state += ((Rom)rom).SHA256 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
else if (rom.Type == ItemType.Disk)
{
state += ((Disk)rom).SHA256 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
break;
case Hash.SHA384:
if (rom.Type == ItemType.Rom)
{
state += ((Rom)rom).SHA384 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
else if (rom.Type == ItemType.Disk)
{
state += ((Disk)rom).SHA384 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
break;
case Hash.SHA512:
if (rom.Type == ItemType.Rom)
{
state += ((Rom)rom).SHA512 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
else if (rom.Type == ItemType.Disk)
{
state += ((Disk)rom).SHA512 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n";
}
break;
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,508 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamReader = System.IO.StreamReader;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of a MAME Listroms DAT
/// </summary>
public class Listroms
{
/// <summary>
/// Parse a MAME Listroms DAT and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
/// <remarks>
/// In a new style MAME listroms DAT, each game has the following format:
///
/// ROMs required for driver "005".
/// Name Size Checksum
/// 1346b.cpu-u25 2048 CRC(8e68533e) SHA1(a257c556d31691068ed5c991f1fb2b51da4826db)
/// 6331.sound-u8 32 BAD CRC(1d298cb0) SHA1(bb0bb62365402543e3154b9a77be9c75010e6abc) BAD_DUMP
/// 16v8h-blue.u24 279 NO GOOD DUMP KNOWN
/// </remarks>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc);
string gamename = "";
while (!sr.EndOfStream)
{
string line = sr.ReadLine().Trim();
// If we have a blank line, we just skip it
if (String.IsNullOrEmpty(line))
{
continue;
}
// If we have the descriptor line, ignore it
else if (line == "Name Size Checksum")
{
continue;
}
// If we have the beginning of a game, set the name of the game
else if (line.StartsWith("ROMs required for"))
{
gamename = Regex.Match(line, @"^ROMs required for \S*? ""(.*?)""\.").Groups[1].Value;
}
// If we have a machine with no required roms (usually internal devices), skip it
else if (line.StartsWith("No ROMs required for"))
{
continue;
}
// Otherwise, we assume we have a rom that we need to add
else
{
// First, we preprocess the line so that the rom name is consistently correct
string romname = "";
string[] split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
// If the line doesn't have the 4 spaces of padding, check for 3
if (split.Length == 1)
{
split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
}
// If the split is still unsuccessful, log it and skip
if (split.Length == 1)
{
Globals.Logger.Warning("Possibly malformed line: '{0}'", line);
}
romname = split[0];
line = line.Substring(romname.Length);
// Next we separate the ROM into pieces
split = line.Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
// Standard Disks have 2 pieces (name, sha1)
if (split.Length == 1)
{
Disk disk = new Disk()
{
Name = romname,
SHA1 = Style.CleanListromHashData(split[0]),
MachineName = gamename,
};
datFile.ParseAddHelper(disk, clean, remUnicode);
}
// Baddump Disks have 4 pieces (name, BAD, sha1, BAD_DUMP)
else if (split.Length == 3 && line.EndsWith("BAD_DUMP"))
{
Disk disk = new Disk()
{
Name = romname,
SHA1 = Style.CleanListromHashData(split[1]),
ItemStatus = ItemStatus.BadDump,
MachineName = gamename,
};
datFile.ParseAddHelper(disk, clean, remUnicode);
}
// Standard ROMs have 4 pieces (name, size, crc, sha1)
else if (split.Length == 3)
{
if (!Int64.TryParse(split[0], out long size))
{
size = 0;
}
Rom rom = new Rom()
{
Name = romname,
Size = size,
CRC = Style.CleanListromHashData(split[1]),
SHA1 = Style.CleanListromHashData(split[2]),
MachineName = gamename,
};
datFile.ParseAddHelper(rom, clean, remUnicode);
}
// Nodump Disks have 5 pieces (name, NO, GOOD, DUMP, KNOWN)
else if (split.Length == 4 && line.EndsWith("NO GOOD DUMP KNOWN"))
{
Disk disk = new Disk()
{
Name = romname,
ItemStatus = ItemStatus.Nodump,
MachineName = gamename,
};
datFile.ParseAddHelper(disk, clean, remUnicode);
}
// Baddump ROMs have 6 pieces (name, size, BAD, crc, sha1, BAD_DUMP)
else if (split.Length == 5 && line.EndsWith("BAD_DUMP"))
{
if (!Int64.TryParse(split[0], out long size))
{
size = 0;
}
Rom rom = new Rom()
{
Name = romname,
Size = size,
CRC = Style.CleanListromHashData(split[2]),
SHA1 = Style.CleanListromHashData(split[3]),
ItemStatus = ItemStatus.BadDump,
MachineName = gamename,
};
datFile.ParseAddHelper(rom, clean, remUnicode);
}
// Nodump ROMs have 6 pieces (name, size, NO, GOOD, DUMP, KNOWN)
else if (split.Length == 5 && line.EndsWith("NO GOOD DUMP KNOWN"))
{
if (!Int64.TryParse(split[0], out long size))
{
size = 0;
}
Rom rom = new Rom()
{
Name = romname,
Size = size,
ItemStatus = ItemStatus.Nodump,
MachineName = gamename,
};
datFile.ParseAddHelper(rom, clean, remUnicode);
}
// If we have something else, it's invalid
else
{
Globals.Logger.Warning("Invalid line detected: '{0} {1}'", romname, line);
}
}
}
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out each of the machines and roms
string lastgame = null;
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// 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(sw);
}
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
{
WriteStartGame(sw, rom);
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
rom.Name = (rom.Name == "null" ? "-" : rom.Name);
((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;
((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;
((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null;
}
// Now, output the rom data
WriteRomData(sw, rom, ignoreblanks);
// Set the new data to compare against
lastgame = rom.MachineName;
}
}
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteStartGame(StreamWriter sw, DatItem rom)
{
try
{
// No game should start with a path separator
if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
rom.MachineName = rom.MachineName.Substring(1);
}
string state = "ROMs required for driver \"" + rom.MachineName + "\".\n" +
"Name Size Checksum\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game end using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteEndGame(StreamWriter sw)
{
try
{
string state = "\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(StreamWriter sw, DatItem rom, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "";
switch (rom.Type)
{
case ItemType.Archive:
case ItemType.BiosSet:
case ItemType.Release:
case ItemType.Sample:
// We don't output these at all
break;
case ItemType.Disk:
// The name is padded out to a particular length
if (rom.Name.Length < 43)
{
state += rom.Name.PadRight(43, ' ');
}
else
{
state += rom.Name + " ";
}
// If we have a baddump, put the first indicator
if (((Disk)rom).ItemStatus == ItemStatus.BadDump)
{
state += " BAD";
}
// If we have a nodump, write out the indicator
if (((Disk)rom).ItemStatus == ItemStatus.Nodump)
{
state += " NO GOOD DUMP KNOWN";
}
// Otherwise, write out the SHA-1 hash
else
{
state += " SHA1(" + ((Disk)rom).SHA1 + ")";
}
// If we have a baddump, put the second indicator
if (((Disk)rom).ItemStatus == ItemStatus.BadDump)
{
state += " BAD_DUMP";
}
state += "\n";
break;
case ItemType.Rom:
// The name is padded out to a particular length
if (rom.Name.Length < 40)
{
state += rom.Name.PadRight(43 - (((Rom)rom).Size.ToString().Length), ' ');
}
else
{
state += rom.Name + " ";
}
// If we don't have a nodump, write out the size
if (((Rom)rom).ItemStatus != ItemStatus.Nodump)
{
state += ((Rom)rom).Size;
}
// If we have a baddump, put the first indicator
if (((Rom)rom).ItemStatus == ItemStatus.BadDump)
{
state += " BAD";
}
// If we have a nodump, write out the indicator
if (((Rom)rom).ItemStatus == ItemStatus.Nodump)
{
state += " NO GOOD DUMP KNOWN";
}
// Otherwise, write out the CRC and SHA-1 hashes
else
{
state += " CRC(" + ((Rom)rom).CRC + ")";
state += " SHA1(" + ((Rom)rom).SHA1 + ")";
}
// If we have a baddump, put the second indicator
if (((Rom)rom).ItemStatus == ItemStatus.BadDump)
{
state += " BAD_DUMP";
}
state += "\n";
break;
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,311 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of a Missfile
/// </summary>
public class Missfile
{
/// <summary>
/// Parse a Missfileand return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
/// <remarks>
/// </remarks>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// There is no consistent way to parse a missfile...
throw new NotImplementedException();
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out each of the machines and roms
string lastgame = null;
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
lastgame = rom.MachineName;
continue;
}
// Now, output the rom data
WriteRomData(datFile, sw, rom, lastgame, ignoreblanks);
// Set the new data to compare against
lastgame = rom.MachineName;
}
}
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="lastgame">The name of the last game to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(DatFile datFile, StreamWriter sw, DatItem rom, string lastgame, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "", name = "", pre = "", post = "";
pre = datFile.Prefix + (datFile.Quotes ? "\"" : "");
post = (datFile.Quotes ? "\"" : "") + datFile.Postfix;
if (rom.Type == ItemType.Rom)
{
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%sha256%", ((Rom)rom).SHA256)
.Replace("%sha384%", ((Rom)rom).SHA384)
.Replace("%sha512%", ((Rom)rom).SHA512)
.Replace("%size%", ((Rom)rom).Size.ToString());
post = post
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%sha256%", ((Rom)rom).SHA256)
.Replace("%sha384%", ((Rom)rom).SHA384)
.Replace("%sha512%", ((Rom)rom).SHA512)
.Replace("%size%", ((Rom)rom).Size.ToString());
}
else if (rom.Type == ItemType.Disk)
{
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", string.Empty)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1)
.Replace("%sha256%", ((Disk)rom).SHA256)
.Replace("%sha384%", ((Disk)rom).SHA384)
.Replace("%sha512%", ((Disk)rom).SHA512)
.Replace("%size%", string.Empty);
post = post
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", string.Empty)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1)
.Replace("%sha256%", ((Disk)rom).SHA256)
.Replace("%sha384%", ((Disk)rom).SHA384)
.Replace("%sha512%", ((Disk)rom).SHA512)
.Replace("%size%", string.Empty);
}
else
{
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", string.Empty)
.Replace("%md5%", string.Empty)
.Replace("%sha1%", string.Empty)
.Replace("%sha256%", string.Empty)
.Replace("%sha384%", string.Empty)
.Replace("%sha512%", string.Empty)
.Replace("%size%", string.Empty);
post = post
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", string.Empty)
.Replace("%md5%", string.Empty)
.Replace("%sha1%", string.Empty)
.Replace("%sha256%", string.Empty)
.Replace("%sha384%", string.Empty)
.Replace("%sha512%", string.Empty)
.Replace("%size%", string.Empty);
}
// If we're in Romba mode, the state is consistent
if (datFile.Romba)
{
if (rom.Type == ItemType.Rom)
{
// We can only write out if there's a SHA-1
if (((Rom)rom).SHA1 != "")
{
name = ((Rom)rom).SHA1.Substring(0, 2)
+ "/" + ((Rom)rom).SHA1.Substring(2, 2)
+ "/" + ((Rom)rom).SHA1.Substring(4, 2)
+ "/" + ((Rom)rom).SHA1.Substring(6, 2)
+ "/" + ((Rom)rom).SHA1 + ".gz";
state += pre + name + post + "\n";
}
}
else if (rom.Type == ItemType.Disk)
{
// We can only write out if there's a SHA-1
if (((Disk)rom).SHA1 != "")
{
name = ((Disk)rom).SHA1.Substring(0, 2)
+ "/" + ((Disk)rom).SHA1.Substring(2, 2)
+ "/" + ((Disk)rom).SHA1.Substring(4, 2)
+ "/" + ((Disk)rom).SHA1.Substring(6, 2)
+ "/" + ((Disk)rom).SHA1 + ".gz";
state += pre + name + post + "\n";
}
}
}
// Otherwise, use any flags
else
{
name = (datFile.UseGame ? rom.MachineName : rom.Name);
if (datFile.RepExt != "" || datFile.RemExt)
{
if (datFile.RemExt)
{
datFile.RepExt = "";
}
string dir = Path.GetDirectoryName(name);
dir = (dir.StartsWith(Path.DirectorySeparatorChar.ToString()) ? dir.Remove(0, 1) : dir);
name = Path.Combine(dir, Path.GetFileNameWithoutExtension(name) + datFile.RepExt);
}
if (datFile.AddExt != "")
{
name += datFile.AddExt;
}
if (!datFile.UseGame && datFile.GameName)
{
name = Path.Combine(rom.MachineName, name);
}
if (datFile.UseGame && rom.MachineName != lastgame)
{
state += pre + name + post + "\n";
lastgame = rom.MachineName;
}
else if (!datFile.UseGame)
{
state += pre + name + post + "\n";
}
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,357 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of an OfflineList XML DAT
/// </summary>
public class OfflineList
{
/// <summary>
/// Parse an OfflineList XML DAT and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
/// <remarks>
/// </remarks>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// All XML-derived DATs share a lot in common so it just calls one implementation
Logiqx.Parse(datFile, filename, sysid, srcid, keep, clean, remUnicode);
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
WriteHeader(datFile, sw);
// Write out each of the machines and roms
string lastgame = null;
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// 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(sw);
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
rom.Name = (rom.Name == "null" ? "-" : rom.Name);
((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;
((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;
((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null;
}
// Now, output the rom data
WriteRomData(sw, rom, ignoreblanks);
// Set the new data to compare against
lastgame = rom.MachineName;
}
}
// Write the file footer out
WriteFooter(sw);
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteHeader(DatFile datFile, StreamWriter sw)
{
try
{
string header = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+ "<dat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"datas.xsd\">\n"
+ "\t<configuration>\n"
+ "\t\t<datName>" + HttpUtility.HtmlEncode(datFile.Name) + "</datName>\n"
+ "\t\t<datVersion>" + datFile.Count + "</datVersion>\n"
+ "\t\t<system>none</system>\n"
+ "\t\t<screenshotsWidth>240</screenshotsWidth>\n"
+ "\t\t<screenshotsHeight>160</screenshotsHeight>\n"
+ "\t\t<infos>\n"
+ "\t\t\t<title visible=\"false\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<location visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<publisher visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<sourceRom visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<saveType visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<romSize visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t\t<releaseNumber visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<languageNumber visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<comment visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<romCRC visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n"
+ "\t\t\t<im1CRC visible=\"false\" inNamingOption=\"false\" default=\"false\"/>\n"
+ "\t\t\t<im2CRC visible=\"false\" inNamingOption=\"false\" default=\"false\"/>\n"
+ "\t\t\t<languages visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n"
+ "\t\t</infos>\n"
+ "\t\t<canOpen>\n"
+ "\t\t\t<extension>.bin</extension>\n"
+ "\t\t</canOpen>\n"
+ "\t\t<newDat>\n"
+ "\t\t\t<datVersionURL>" + HttpUtility.HtmlEncode(datFile.Url) + "</datVersionURL>\n"
+ "\t\t\t<datURL fileName=\"" + HttpUtility.HtmlEncode(datFile.FileName) + ".zip\">" + HttpUtility.HtmlEncode(datFile.Url) + "</datURL>\n"
+ "\t\t\t<imURL>" + HttpUtility.HtmlEncode(datFile.Url) + "</imURL>\n"
+ "\t\t</newDat>\n"
+ "\t\t<search>\n"
+ "\t\t\t<to value=\"location\" default=\"true\" auto=\"true\"/>\n"
+ "\t\t\t<to value=\"romSize\" default=\"true\" auto=\"false\"/>\n"
+ "\t\t\t<to value=\"languages\" default=\"true\" auto=\"true\"/>\n"
+ "\t\t\t<to value=\"saveType\" default=\"false\" auto=\"false\"/>\n"
+ "\t\t\t<to value=\"publisher\" default=\"false\" auto=\"true\"/>\n"
+ "\t\t\t<to value=\"sourceRom\" default=\"false\" auto=\"true\"/>\n"
+ "\t\t</search>\n"
+ "\t\t<romTitle >%u - %n</romTitle>\n"
+ "\t</configuration>\n"
+ "\t<games>\n";
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteEndGame(StreamWriter sw)
{
try
{
string state = "\t\t</game>\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(StreamWriter sw, DatItem rom, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "";
state += "\t\t<game>\n"
+ "\t\t\t<imageNumber>1</imageNumber>\n"
+ "\t\t\t<releaseNumber>1</releaseNumber>\n"
+ "\t\t\t<title>" + HttpUtility.HtmlEncode(rom.Name) + "</title>\n"
+ "\t\t\t<saveType>None</saveType>\n";
if (rom.Type == ItemType.Rom)
{
state += "\t\t\t<romSize>" + ((Rom)rom).Size + "</romSize>\n";
}
state += "\t\t\t<publisher>None</publisher>\n"
+ "\t\t\t<location>0</location>\n"
+ "\t\t\t<sourceRom>None</sourceRom>\n"
+ "\t\t\t<language>0</language>\n";
if (rom.Type == ItemType.Disk)
{
state += "\t\t\t<files>\n"
+ (((Disk)rom).MD5 != null
? "\t\t\t\t<romMD5 extension=\".chd\">" + ((Disk)rom).MD5.ToUpperInvariant() + "</romMD5>\n"
: "\t\t\t\t<romSHA1 extension=\".chd\">" + ((Disk)rom).SHA1.ToUpperInvariant() + "</romSHA1>\n")
+ "\t\t\t</files>\n";
}
else if (rom.Type == ItemType.Rom)
{
string tempext = Path.GetExtension(((Rom)rom).Name);
if (!tempext.StartsWith("."))
{
tempext = "." + tempext;
}
state += "\t\t\t<files>\n"
+ (((Rom)rom).CRC != null
? "\t\t\t\t<romCRC extension=\"" + tempext + "\">" + ((Rom)rom).CRC.ToUpperInvariant() + "</romMD5>\n"
: ((Rom)rom).MD5 != null
? "\t\t\t\t<romMD5 extension=\"" + tempext + "\">" + ((Rom)rom).MD5.ToUpperInvariant() + "</romMD5>\n"
: "\t\t\t\t<romSHA1 extension=\"" + tempext + "\">" + ((Rom)rom).SHA1.ToUpperInvariant() + "</romSHA1>\n")
+ "\t\t\t</files>\n";
}
state += "\t\t\t<im1CRC>00000000</im1CRC>\n"
+ "\t\t\t<im2CRC>00000000</im2CRC>\n"
+ "\t\t\t<comment></comment>\n"
+ "\t\t\t<duplicateID>0</duplicateID>\n"
+ "\t\t</game>\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteFooter(StreamWriter sw)
{
try
{
string footer = "\t\t</game>"
+ "\t</games>\n"
+ "\t<gui>\n"
+ "\t\t<images width=\"487\" height=\"162\">\n"
+ "\t\t\t<image x=\"0\" y=\"0\" width=\"240\" height=\"160\"/>\n"
+ "\t\t\t<image x=\"245\" y=\"0\" width=\"240\" height=\"160\"/>\n"
+ "\t\t</images>\n"
+ "\t</gui>\n"
+ "</dat>";
// Write the footer out
sw.Write(footer);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,371 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamReader = System.IO.StreamReader;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of a RomCenter DAT
/// </summary>
public class RomCenter
{
/// <summary>
/// Parse a RomCenter DAT and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool clean,
bool remUnicode)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc);
string blocktype = "";
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
// If the line is the start of the credits section
if (line.ToLowerInvariant().StartsWith("[credits]"))
{
blocktype = "credits";
}
// If the line is the start of the dat section
else if (line.ToLowerInvariant().StartsWith("[dat]"))
{
blocktype = "dat";
}
// If the line is the start of the emulator section
else if (line.ToLowerInvariant().StartsWith("[emulator]"))
{
blocktype = "emulator";
}
// If the line is the start of the game section
else if (line.ToLowerInvariant().StartsWith("[games]"))
{
blocktype = "games";
}
// Otherwise, it's not a section and it's data, so get out all data
else
{
// If we have an author
if (line.ToLowerInvariant().StartsWith("author="))
{
datFile.Author = (String.IsNullOrEmpty(datFile.Author) ? line.Split('=')[1] : datFile.Author);
}
// If we have one of the three version tags
else if (line.ToLowerInvariant().StartsWith("version="))
{
switch (blocktype)
{
case "credits":
datFile.Version = (String.IsNullOrEmpty(datFile.Version) ? line.Split('=')[1] : datFile.Version);
break;
case "emulator":
datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? line.Split('=')[1] : datFile.Description);
break;
}
}
// If we have a URL
else if (line.ToLowerInvariant().StartsWith("url="))
{
datFile.Url = (String.IsNullOrEmpty(datFile.Url) ? line.Split('=')[1] : datFile.Url);
}
// If we have a comment
else if (line.ToLowerInvariant().StartsWith("comment="))
{
datFile.Comment = (String.IsNullOrEmpty(datFile.Comment) ? line.Split('=')[1] : datFile.Comment);
}
// If we have the split flag
else if (line.ToLowerInvariant().StartsWith("split="))
{
if (Int32.TryParse(line.Split('=')[1], out int split))
{
if (split == 1 && datFile.ForceMerging == ForceMerging.None)
{
datFile.ForceMerging = ForceMerging.Split;
}
}
}
// If we have the merge tag
else if (line.ToLowerInvariant().StartsWith("merge="))
{
if (Int32.TryParse(line.Split('=')[1], out int merge))
{
if (merge == 1 && datFile.ForceMerging == ForceMerging.None)
{
datFile.ForceMerging = ForceMerging.Full;
}
}
}
// If we have the refname tag
else if (line.ToLowerInvariant().StartsWith("refname="))
{
datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? line.Split('=')[1] : datFile.Name);
}
// If we have a rom
else if (line.StartsWith("¬"))
{
// Some old RC DATs have this behavior
if (line.Contains("¬N¬O"))
{
line = line.Replace("¬N¬O", "") + "¬¬";
}
/*
The rominfo order is as follows:
1 - parent name
2 - parent description
3 - game name
4 - game description
5 - rom name
6 - rom crc
7 - rom size
8 - romof name
9 - merge name
*/
string[] rominfo = line.Split('¬');
// Try getting the size separately
if (!Int64.TryParse(rominfo[7], out long size))
{
size = 0;
}
Rom rom = new Rom
{
Name = rominfo[5],
Size = size,
CRC = rominfo[6],
ItemStatus = ItemStatus.None,
MachineName = rominfo[3],
MachineDescription = rominfo[4],
CloneOf = rominfo[1],
RomOf = rominfo[8],
SystemID = sysid,
SourceID = srcid,
};
// Now process and add the rom
datFile.ParseAddHelper(rom, clean, remUnicode);
}
}
}
sr.Dispose();
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
WriteHeader(datFile, sw);
// Write out each of the machines and roms
string lastgame = null;
List<string> splitpath = new List<string>();
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
rom.Name = (rom.Name == "null" ? "-" : rom.Name);
((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;
((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;
((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null;
}
// Now, output the rom data
WriteRomData(sw, rom, ignoreblanks);
// Set the new data to compare against
lastgame = rom.MachineName;
}
}
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteHeader(DatFile datFile, StreamWriter sw)
{
try
{
string header = header = "[CREDITS]\n" +
"author=" + datFile.Author + "\n" +
"version=" + datFile.Version + "\n" +
"comment=" + datFile.Comment + "\n" +
"[DAT]\n" +
"version=2.50\n" +
"split=" + (datFile.ForceMerging == ForceMerging.Split ? "1" : "0") + "\n" +
"merge=" + (datFile.ForceMerging == ForceMerging.Full || datFile.ForceMerging == ForceMerging.Merged ? "1" : "0") + "\n" +
"[EMULATOR]\n" +
"refname=" + datFile.Name + "\n" +
"version=" + datFile.Description + "\n" +
"[GAMES]\n";
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(StreamWriter sw, DatItem rom, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "";
if (rom.Type == ItemType.Rom)
{
state += "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) +
"¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) +
"¬" + HttpUtility.HtmlEncode(rom.MachineName) +
"¬" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) +
"¬" + HttpUtility.HtmlEncode(rom.Name) +
"¬" + ((Rom)rom).CRC.ToLowerInvariant() +
"¬" + (((Rom)rom).Size != -1 ? ((Rom)rom).Size.ToString() : "") + "¬¬¬\n";
}
else if (rom.Type == ItemType.Disk)
{
state += "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) +
"¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) +
"¬" + HttpUtility.HtmlEncode(rom.MachineName) +
"¬" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) +
"¬" + HttpUtility.HtmlEncode(rom.Name) +
"¬¬¬¬¬\n";
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,451 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamReader = System.IO.StreamReader;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of an SabreDat XML DAT
/// </summary>
public class SabreDat
{
/// <summary>
/// Parse an SabreDat XML DAT and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
/// <remarks>
/// </remarks>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// All XML-derived DATs share a lot in common so it just calls one implementation
Logiqx.Parse(datFile, filename, sysid, srcid, keep, clean, remUnicode);
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="norename">True if games should only be compared on game and file name (default), false if system and source are counted</param>
/// <param name="stats">True if DAT statistics should be output on write, false otherwise (default)</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <param name="overwrite">True if files should be overwritten (default), false if they should be renamed instead</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
WriteHeader(datFile, sw);
// Write out each of the machines and roms
int depth = 2, last = -1;
string lastgame = null;
List<string> splitpath = new List<string>();
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
List<string> newsplit = rom.MachineName.Split('\\').ToList();
// If we have a different game and we're not at the start of the list, output the end of last item
if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
{
depth = WriteEndGame(sw, splitpath, newsplit, depth, out last);
}
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
{
depth = WriteStartGame(sw, rom, newsplit, lastgame, depth, last);
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
splitpath = newsplit;
lastgame = rom.MachineName;
continue;
}
// Now, output the rom data
WriteRomData(sw, rom, depth, ignoreblanks);
// Set the new data to compare against
splitpath = newsplit;
lastgame = rom.MachineName;
}
}
// Write the file footer out
WriteFooter(sw, depth);
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteHeader(DatFile datFile, StreamWriter sw)
{
try
{
string header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE sabredat SYSTEM \"newdat.xsd\">\n\n" +
"<datafile>\n" +
"\t<header>\n" +
"\t\t<name>" + HttpUtility.HtmlEncode(datFile.Name) + "</name>\n" +
"\t\t<description>" + HttpUtility.HtmlEncode(datFile.Description) + "</description>\n" +
(!String.IsNullOrEmpty(datFile.RootDir) ? "\t\t<rootdir>" + HttpUtility.HtmlEncode(datFile.RootDir) + "</rootdir>\n" : "") +
(!String.IsNullOrEmpty(datFile.Category) ? "\t\t<category>" + HttpUtility.HtmlEncode(datFile.Category) + "</category>\n" : "") +
"\t\t<version>" + HttpUtility.HtmlEncode(datFile.Version) + "</version>\n" +
(!String.IsNullOrEmpty(datFile.Date) ? "\t\t<date>" + HttpUtility.HtmlEncode(datFile.Date) + "</date>\n" : "") +
"\t\t<author>" + HttpUtility.HtmlEncode(datFile.Author) + "</author>\n" +
(!String.IsNullOrEmpty(datFile.Comment) ? "\t\t<comment>" + HttpUtility.HtmlEncode(datFile.Comment) + "</comment>\n" : "") +
(!String.IsNullOrEmpty(datFile.Type) || datFile.ForcePacking != ForcePacking.None || datFile.ForceMerging != ForceMerging.None || datFile.ForceNodump != ForceNodump.None ?
"\t\t<flags>\n" +
(!String.IsNullOrEmpty(datFile.Type) ? "\t\t\t<flag name=\"type\" value=\"" + HttpUtility.HtmlEncode(datFile.Type) + "\"/>\n" : "") +
(datFile.ForcePacking == ForcePacking.Unzip ? "\t\t\t<flag name=\"forcepacking\" value=\"unzip\"/>\n" : "") +
(datFile.ForcePacking == ForcePacking.Zip ? "\t\t\t<flag name=\"forcepacking\" value=\"zip\"/>\n" : "") +
(datFile.ForceMerging == ForceMerging.Full ? "\t\t\t<flag name=\"forcemerging\" value=\"full\"/>\n" : "") +
(datFile.ForceMerging == ForceMerging.Split ? "\t\t\t<flag name=\"forcemerging\" value=\"split\"/>\n" : "") +
(datFile.ForceMerging == ForceMerging.Merged ? "\t\t\t<flag name=\"forcemerging\" value=\"merged\"/>\n" : "") +
(datFile.ForceMerging == ForceMerging.NonMerged ? "\t\t\t<flag name=\"forcemerging\" value=\"nonmerged\"/>\n" : "") +
(datFile.ForceNodump == ForceNodump.Ignore ? "\t\t\t<flag name=\"forcenodump\" value=\"ignore\"/>\n" : "") +
(datFile.ForceNodump == ForceNodump.Obsolete ? "\t\t\t<flag name=\"forcenodump\" value=\"obsolete\"/>\n" : "") +
(datFile.ForceNodump == ForceNodump.Required ? "\t\t\t<flag name=\"forcenodump\" value=\"required\"/>\n" : "") +
"\t\t</flags>\n"
: "") +
"\t</header>\n" +
"\t<data>\n";
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="newsplit">Split path representing the parent game (SabreDAT only)</param>
/// <param name="lastgame">The name of the last game to be output</param>
/// <param name="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 static int WriteStartGame(StreamWriter sw, DatItem rom, List<string> newsplit, string lastgame, int depth, int last)
{
try
{
// No game should start with a path separator
if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
rom.MachineName = rom.MachineName.Substring(1);
}
string state = "";
for (int i = (last == -1 ? 0 : last); i < newsplit.Count; i++)
{
for (int j = 0; j < depth - last + i - (lastgame == null ? 1 : 0); j++)
{
state += "\t";
}
state += "<directory name=\"" + HttpUtility.HtmlEncode(newsplit[i]) + "\" description=\"" +
HttpUtility.HtmlEncode(newsplit[i]) + "\">\n";
}
depth = depth - (last == -1 ? 0 : last) + newsplit.Count;
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return depth;
}
return depth;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="splitpath">Split path representing last kwown parent game (SabreDAT only)</param>
/// <param name="newsplit">Split path representing the parent game (SabreDAT only)</param>
/// <param name="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 static int WriteEndGame(StreamWriter sw, List<string> splitpath, List<string> newsplit, int depth, out int last)
{
last = 0;
try
{
string state = "";
if (splitpath != null)
{
for (int i = 0; i < newsplit.Count && i < splitpath.Count; i++)
{
// Always keep track of the last seen item
last = i;
// If we find a difference, break
if (newsplit[i] != splitpath[i])
{
break;
}
}
// Now that we have the last known position, take down all open folders
for (int i = depth - 1; i > last + 1; i--)
{
// Print out the number of tabs and the end folder
for (int j = 0; j < i; j++)
{
state += "\t";
}
state += "</directory>\n";
}
// Reset the current depth
depth = 2 + last;
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return depth;
}
return depth;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="depth">Current depth to output file at (SabreDAT only)</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(StreamWriter sw, DatItem rom, int depth, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "", prefix = "";
for (int i = 0; i < depth; i++)
{
prefix += "\t";
}
state += prefix;
switch (rom.Type)
{
case ItemType.Archive:
state += "<file type=\"archive\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n";
break;
case ItemType.BiosSet:
state += "<file type=\"biosset\" name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description=\"" + HttpUtility.HtmlEncode(((BiosSet)rom).Description) + "\"" : "")
+ (((BiosSet)rom).Default != null
? ((BiosSet)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n";
break;
case ItemType.Disk:
state += "<file type=\"disk\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5=\"" + ((Disk)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1=\"" + ((Disk)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA256) ? " sha256=\"" + ((Disk)rom).SHA256.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA384) ? " sha384=\"" + ((Disk)rom).SHA384.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA512) ? " sha512=\"" + ((Disk)rom).SHA512.ToLowerInvariant() + "\"" : "")
+ (((Disk)rom).ItemStatus != ItemStatus.None ? prefix + "/>\n" + prefix + "\t<flags>\n" +
prefix + "\t\t<flag name=\"status\" value=\"" + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() + "\"/>\n" +
prefix + "\t</flags>\n" +
prefix + "</file>\n" : "/>\n");
break;
case ItemType.Release:
state += "<file type=\"release\" name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Release)rom).Region) ? " region=\"" + HttpUtility.HtmlEncode(((Release)rom).Region) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Language) ? " language=\"" + HttpUtility.HtmlEncode(((Release)rom).Language) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Date) ? " date=\"" + HttpUtility.HtmlEncode(((Release)rom).Date) + "\"" : "")
+ (((Release)rom).Default != null
? ((Release)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n";
break;
case ItemType.Rom:
state += "<file type=\"rom\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (((Rom)rom).Size != -1 ? " size=\"" + ((Rom)rom).Size + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc=\"" + ((Rom)rom).CRC.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5=\"" + ((Rom)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1=\"" + ((Rom)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA256) ? " sha256=\"" + ((Rom)rom).SHA256.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA384) ? " sha384=\"" + ((Rom)rom).SHA384.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA512) ? " sha512=\"" + ((Rom)rom).SHA512.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date=\"" + ((Rom)rom).Date + "\"" : "")
+ (((Rom)rom).ItemStatus != ItemStatus.None ? prefix + "/>\n" + prefix + "\t<flags>\n" +
prefix + "\t\t<flag name=\"status\" value=\"" + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() + "\"/>\n" +
prefix + "\t</flags>\n" +
prefix + "</file>\n" : "/>\n");
break;
case ItemType.Sample:
state += "<file type=\"sample\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n";
break;
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="depth">Current depth to output file at (SabreDAT only)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteFooter(StreamWriter sw, int depth)
{
try
{
string footer = "";
for (int i = depth - 1; i >= 2; i--)
{
// Print out the number of tabs and the end folder
for (int j = 0; j < i; j++)
{
footer += "\t";
}
footer += "</directory>\n";
}
footer += "\t</data>\n</datafile>\n";
// Write the footer out
sw.Write(footer);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,612 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using FileStream = System.IO.FileStream;
using StreamReader = System.IO.StreamReader;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of a value-separated DAT
/// </summary>
public class SeparatedValue
{
/// <summary>
/// Parse a character-separated value DAT and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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="delim">Delimiter for parsing individual lines</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 static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
char delim,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// Open a file reader
Encoding enc = Style.GetEncoding(filename);
StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc);
// Create an empty list of columns to parse though
List<string> columns = new List<string>();
long linenum = -1;
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
linenum++;
// Parse the first line, getting types from the column names
if (linenum == 0)
{
string[] parsedColumns = line.Split(delim);
foreach (string parsed in parsedColumns)
{
switch (parsed.ToLowerInvariant().Trim('"'))
{
case "file":
case "filename":
case "file name":
columns.Add("DatFile.FileName");
break;
case "internal name":
columns.Add("DatFile.Name");
break;
case "description":
case "dat description":
columns.Add("DatFile.Description");
break;
case "game name":
case "game":
case "machine":
columns.Add("Machine.Name");
break;
case "game description":
columns.Add("Description");
break;
case "type":
columns.Add("DatItem.Type");
break;
case "rom":
case "romname":
case "rom name":
case "name":
columns.Add("Rom.Name");
break;
case "disk":
case "diskname":
case "disk name":
columns.Add("Disk.Name");
break;
case "size":
columns.Add("DatItem.Size");
break;
case "crc":
case "crc hash":
columns.Add("DatItem.CRC");
break;
case "md5":
case "md5 hash":
columns.Add("DatItem.MD5");
break;
case "sha1":
case "sha-1":
case "sha1 hash":
case "sha-1 hash":
columns.Add("DatItem.SHA1");
break;
case "sha256":
case "sha-256":
case "sha256 hash":
case "sha-256 hash":
columns.Add("DatItem.SHA256");
break;
case "sha384":
case "sha-384":
case "sha384 hash":
case "sha-384 hash":
columns.Add("DatItem.SHA384");
break;
case "sha512":
case "sha-512":
case "sha512 hash":
case "sha-512 hash":
columns.Add("DatItem.SHA512");
break;
case "nodump":
case "no dump":
case "status":
case "item status":
columns.Add("DatItem.Nodump");
break;
default:
columns.Add("INVALID");
break;
}
}
continue;
}
// Otherwise, we want to split the line and parse
string[] parsedLine = line.Split(delim);
// If the line doesn't have the correct number of columns, we log and skip
if (parsedLine.Length != columns.Count)
{
Globals.Logger.Warning("Malformed line found in '{0}' at line {1}", filename, linenum);
continue;
}
// Set the output item information
string machineName = null, machineDesc = null, name = null, crc = null, md5 = null, sha1 = null,
sha256 = null, sha384 = null, sha512 = null;
long size = -1;
ItemType itemType = ItemType.Rom;
ItemStatus status = ItemStatus.None;
// Now we loop through and get values for everything
for (int i = 0; i < columns.Count; i++)
{
string value = parsedLine[i].Trim('"');
switch (columns[i])
{
case "DatFile.FileName":
datFile.FileName = (String.IsNullOrEmpty(datFile.FileName) ? value : datFile.FileName);
break;
case "DatFile.Name":
datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? value : datFile.Name);
break;
case "DatFile.Description":
datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? value : datFile.Description);
break;
case "Machine.Name":
machineName = value;
break;
case "Description":
machineDesc = value;
break;
case "DatItem.Type":
switch (value.ToLowerInvariant())
{
case "archive":
itemType = ItemType.Archive;
break;
case "biosset":
itemType = ItemType.BiosSet;
break;
case "disk":
itemType = ItemType.Disk;
break;
case "release":
itemType = ItemType.Release;
break;
case "rom":
itemType = ItemType.Rom;
break;
case "sample":
itemType = ItemType.Sample;
break;
}
break;
case "Rom.Name":
case "Disk.Name":
name = value == "" ? name : value;
break;
case "DatItem.Size":
if (!Int64.TryParse(value, out size))
{
size = -1;
}
break;
case "DatItem.CRC":
crc = value;
break;
case "DatItem.MD5":
md5 = value;
break;
case "DatItem.SHA1":
sha1 = value;
break;
case "DatItem.SHA256":
sha256 = value;
break;
case "DatItem.SHA384":
sha384 = value;
break;
case "DatItem.SHA512":
sha512 = value;
break;
case "DatItem.Nodump":
switch (value.ToLowerInvariant())
{
case "baddump":
status = ItemStatus.BadDump;
break;
case "good":
status = ItemStatus.Good;
break;
case "no":
case "none":
status = ItemStatus.None;
break;
case "nodump":
case "yes":
status = ItemStatus.Nodump;
break;
case "verified":
status = ItemStatus.Verified;
break;
}
break;
}
}
// And now we populate and add the new item
switch (itemType)
{
case ItemType.Archive:
Archive archive = new Archive()
{
Name = name,
MachineName = machineName,
MachineDescription = machineDesc,
};
datFile.ParseAddHelper(archive, clean, remUnicode);
break;
case ItemType.BiosSet:
BiosSet biosset = new BiosSet()
{
Name = name,
MachineName = machineName,
Description = machineDesc,
};
datFile.ParseAddHelper(biosset, clean, remUnicode);
break;
case ItemType.Disk:
Disk disk = new Disk()
{
Name = name,
MD5 = md5,
SHA1 = sha1,
SHA256 = sha256,
SHA384 = sha384,
SHA512 = sha512,
MachineName = machineName,
MachineDescription = machineDesc,
ItemStatus = status,
};
datFile.ParseAddHelper(disk, clean, remUnicode);
break;
case ItemType.Release:
Release release = new Release()
{
Name = name,
MachineName = machineName,
MachineDescription = machineDesc,
};
datFile.ParseAddHelper(release, clean, remUnicode);
break;
case ItemType.Rom:
Rom rom = new Rom()
{
Name = name,
Size = size,
CRC = crc,
MD5 = md5,
SHA1 = sha1,
SHA256 = sha256,
SHA384 = sha384,
SHA512 = sha512,
MachineName = machineName,
MachineDescription = machineDesc,
ItemStatus = status,
};
datFile.ParseAddHelper(rom, clean, remUnicode);
break;
case ItemType.Sample:
Sample sample = new Sample()
{
Name = name,
MachineName = machineName,
MachineDescription = machineDesc,
};
datFile.ParseAddHelper(sample, clean, remUnicode);
break;
}
}
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="delim">Delimiter for parsing individual lines</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, char delim, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
WriteHeader(sw, delim);
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
}
// Now, output the rom data
WriteRomData(datFile, sw, delim, rom, ignoreblanks);
}
}
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="delim">Delimiter for parsing individual lines</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteHeader(StreamWriter sw, char delim)
{
try
{
string header = string.Format("\"File Name\"{0}\"Internal Name\"{0}\"Description\"{0}\"Game Name\"{0}\"Game Description\"{0}\"Type\"{0}\"" +
"Rom Name\"{0}\"Disk Name\"{0}\"Size\"{0}\"CRC\"{0}\"MD5\"{0}\"SHA1\"{0}\"SHA256\"{0}\"Nodump\"\n", delim);
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="delim">Delimiter for parsing individual lines</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(DatFile datFile, StreamWriter sw, char delim, DatItem rom, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "", name = "", pre = "", post = "";
// Separated values should only output Rom and Disk
if (rom.Type != ItemType.Disk && rom.Type != ItemType.Rom)
{
return true;
}
pre = datFile.Prefix + (datFile.Quotes ? "\"" : "");
post = (datFile.Quotes ? "\"" : "") + datFile.Postfix;
if (rom.Type == ItemType.Rom)
{
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%sha256%", ((Rom)rom).SHA256)
.Replace("%sha384%", ((Rom)rom).SHA384)
.Replace("%sha512%", ((Rom)rom).SHA512)
.Replace("%size%", ((Rom)rom).Size.ToString());
post = post
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", ((Rom)rom).CRC)
.Replace("%md5%", ((Rom)rom).MD5)
.Replace("%sha1%", ((Rom)rom).SHA1)
.Replace("%sha256%", ((Rom)rom).SHA256)
.Replace("%sha384%", ((Rom)rom).SHA384)
.Replace("%sha512%", ((Rom)rom).SHA512)
.Replace("%size%", ((Rom)rom).Size.ToString());
}
else if (rom.Type == ItemType.Disk)
{
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", string.Empty)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1)
.Replace("%sha256%", ((Disk)rom).SHA256)
.Replace("%sha384%", ((Disk)rom).SHA384)
.Replace("%sha512%", ((Disk)rom).SHA512)
.Replace("%size%", string.Empty); ;
post = post
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", string.Empty)
.Replace("%md5%", ((Disk)rom).MD5)
.Replace("%sha1%", ((Disk)rom).SHA1)
.Replace("%sha256%", ((Disk)rom).SHA256)
.Replace("%sha384%", ((Disk)rom).SHA384)
.Replace("%sha512%", ((Disk)rom).SHA512)
.Replace("%size%", string.Empty); ;
}
else
{
// Check for special strings in prefix and postfix
pre = pre
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", string.Empty)
.Replace("%md5%", string.Empty)
.Replace("%sha1%", string.Empty)
.Replace("%size%", string.Empty);
post = post
.Replace("%game%", rom.MachineName)
.Replace("%name%", rom.Name)
.Replace("%crc%", string.Empty)
.Replace("%md5%", string.Empty)
.Replace("%sha1%", string.Empty)
.Replace("%size%", string.Empty);
}
if (rom.Type == ItemType.Rom)
{
string inline = string.Format("\"" + datFile.FileName + "\""
+ "{0}\"" + datFile.Name + "\""
+ "{0}\"" + datFile.Description + "\""
+ "{0}\"" + rom.MachineName + "\""
+ "{0}\"" + rom.MachineDescription + "\""
+ "{0}" + "\"rom\""
+ "{0}\"" + rom.Name + "\""
+ "{0}" + "\"\""
+ "{0}\"" + ((Rom)rom).Size + "\""
+ "{0}\"" + ((Rom)rom).CRC + "\""
+ "{0}\"" + ((Rom)rom).MD5 + "\""
+ "{0}\"" + ((Rom)rom).SHA1 + "\""
+ "{0}\"" + ((Rom)rom).SHA256 + "\""
// + "{0}\"" + ((Rom)rom).SHA384 + "\""
// + "{0}\"" + ((Rom)rom).SHA512 + "\""
+ "{0}" + (((Rom)rom).ItemStatus != ItemStatus.None ? "\"" + ((Rom)rom).ItemStatus.ToString() + "\"" : "\"\""), delim);
state += pre + inline + post + "\n";
}
else if (rom.Type == ItemType.Disk)
{
string inline = string.Format("\"" + datFile.FileName + "\""
+ "{0}\"" + datFile.Name + "\""
+ "{0}\"" + datFile.Description + "\""
+ "{0}\"" + rom.MachineName + "\""
+ "{0}\"" + rom.MachineDescription + "\""
+ "{0}" + "\"disk\""
+ "{0}" + "\"\""
+ "{0}\"" + rom.Name + "\""
+ "{0}" + "\"\""
+ "{0}" + "\"\""
+ "{0}\"" + ((Disk)rom).MD5 + "\""
+ "{0}\"" + ((Disk)rom).SHA1 + "\""
+ "{0}\"" + ((Disk)rom).SHA256 + "\""
// + "{0}\"" + ((Rom)rom).SHA384 + "\""
// + "{0}\"" + ((Rom)rom).SHA512 + "\""
+ "{0}" + (((Disk)rom).ItemStatus != ItemStatus.None ? "\"" + ((Disk)rom).ItemStatus.ToString() + "\"" : "\"\""), delim);
state += pre + inline + post + "\n";
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,403 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using SabreTools.Library.Data;
using SabreTools.Library.Items;
using SabreTools.Library.Tools;
#if MONO
using System.IO;
#else
using Alphaleonis.Win32.Filesystem;
using FileStream = System.IO.FileStream;
using StreamWriter = System.IO.StreamWriter;
#endif
using NaturalSort;
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of an SabreDat XML DAT
/// </summary>
public class SoftwareList
{
/// <summary>
/// Parse an SabreDat XML DAT and return all found games and roms within
/// </summary>
/// <param name="datFile">DatFile to populate with the read information</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>
/// <remarks>
/// </remarks>
public static void Parse(
DatFile datFile,
// Standard Dat parsing
string filename,
int sysid,
int srcid,
// Miscellaneous
bool keep,
bool clean,
bool remUnicode)
{
// All XML-derived DATs share a lot in common so it just calls one implementation
Logiqx.Parse(datFile, filename, sysid, srcid, keep, clean, remUnicode);
}
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false)
{
try
{
Globals.Logger.User("Opening file for writing: {0}", outfile);
FileStream fs = FileTools.TryCreate(outfile);
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile);
return false;
}
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true));
// Write out the header
WriteHeader(datFile, sw);
// Write out each of the machines and roms
string lastgame = null;
// Get a properly sorted set of keys
List<string> keys = datFile.Keys.ToList();
keys.Sort(new NaturalComparer());
foreach (string key in keys)
{
List<DatItem> roms = datFile[key];
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
if (rom.Name == null || rom.MachineName == null)
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// 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(sw);
}
// If we have a new game, output the beginning of the new item
if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant())
{
WriteStartGame(datFile, sw, rom);
}
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.Type == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName);
lastgame = rom.MachineName;
continue;
}
// Now, output the rom data
WriteRomData(sw, rom, ignoreblanks);
// Set the new data to compare against
lastgame = rom.MachineName;
}
}
// Write the file footer out
WriteFooter(sw);
Globals.Logger.Verbose("File written!" + Environment.NewLine);
sw.Dispose();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteHeader(DatFile datFile, StreamWriter sw)
{
try
{
string header = "<?xml version=\"1.0\"?>\n" +
"<!DOCTYPE softwarelist SYSTEM \"softwarelist.dtd\">\n\n" +
"<softwarelist name=\"" + HttpUtility.HtmlEncode(datFile.Name) + "\"" +
" description=\"" + HttpUtility.HtmlEncode(datFile.Description) + "\"" +
(datFile.ForcePacking == ForcePacking.Unzip ? " forcepacking=\"unzip\"" : "") +
(datFile.ForcePacking == ForcePacking.Zip ? " forcepacking=\"zip\"" : "") +
(datFile.ForceMerging == ForceMerging.Full ? " forcemerging=\"full\"" : "") +
(datFile.ForceMerging == ForceMerging.Split ? " forcemerging=\"split\"" : "") +
(datFile.ForceMerging == ForceMerging.Merged ? " forcemerging=\"merged\"" : "") +
(datFile.ForceMerging == ForceMerging.NonMerged ? " forcemerging=\"nonmerged\"" : "") +
(datFile.ForceNodump == ForceNodump.Ignore ? " forcenodump=\"ignore\"" : "") +
(datFile.ForceNodump == ForceNodump.Obsolete ? " forcenodump=\"obsolete\"" : "") +
(datFile.ForceNodump == ForceNodump.Required ? " forcenodump=\"required\"" : "") +
">\n\n";
// Write the header out
sw.Write(header);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="datFile">DatFile to write out from</param>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteStartGame(DatFile datFile, StreamWriter sw, DatItem rom)
{
try
{
// No game should start with a path separator
if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
rom.MachineName = rom.MachineName.Substring(1);
}
string state = "\t<software name=\"" + HttpUtility.HtmlEncode(rom.MachineName) + "\""
+ (rom.Supported != null ? " supported=\"" + (rom.Supported == true ? "yes" : "no") + "\"" : "") +
(datFile.ExcludeOf ? "" :
(String.IsNullOrEmpty(rom.CloneOf) || (rom.MachineName.ToLowerInvariant() == rom.CloneOf.ToLowerInvariant())
? ""
: " cloneof=\"" + HttpUtility.HtmlEncode(rom.CloneOf) + "\"") +
(String.IsNullOrEmpty(rom.RomOf) || (rom.MachineName.ToLowerInvariant() == rom.RomOf.ToLowerInvariant())
? ""
: " romof=\"" + HttpUtility.HtmlEncode(rom.RomOf) + "\"") +
(String.IsNullOrEmpty(rom.SampleOf) || (rom.MachineName.ToLowerInvariant() == rom.SampleOf.ToLowerInvariant())
? ""
: " sampleof=\"" + HttpUtility.HtmlEncode(rom.SampleOf) + "\"")
) + ">\n"
+ "\t\t<description>" + HttpUtility.HtmlEncode(rom.MachineDescription) + "</description>\n"
+ (rom.Year != null ? "\t\t<year>" + HttpUtility.HtmlEncode(rom.Year) + "</year>\n" : "")
+ (rom.Publisher != null ? "\t\t<publisher>" + HttpUtility.HtmlEncode(rom.Publisher) + "</publisher>\n" : "");
foreach (Tuple<string, string> kvp in rom.Infos)
{
state += "\t\t<info name=\"" + HttpUtility.HtmlEncode(kvp.Item1) + "\" value=\"" + HttpUtility.HtmlEncode(kvp.Item2) + "\" />\n";
}
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteEndGame(StreamWriter sw)
{
try
{
string state = "\t</software>\n\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out RomData using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="rom">RomData object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteRomData(StreamWriter sw, DatItem rom, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks
&& (rom.Type == ItemType.Rom
&& (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1)))
{
return true;
}
try
{
string state = "";
state += "\t\t<part name=\"" + rom.PartName + "\" interface=\"" + rom.PartInterface + "\">\n";
foreach (Tuple<string, string> kvp in rom.Features)
{
state += "\t\t\t<feature name=\"" + HttpUtility.HtmlEncode(kvp.Item1) + "\" value=\"" + HttpUtility.HtmlEncode(kvp.Item2) + "\"/>\n";
}
switch (rom.Type)
{
case ItemType.Archive:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "archive" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<archive name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
case ItemType.BiosSet:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "biosset" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<biosset name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description=\"" + HttpUtility.HtmlEncode(((BiosSet)rom).Description) + "\"" : "")
+ (((BiosSet)rom).Default != null
? ((BiosSet)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
case ItemType.Disk:
state += "\t\t\t<diskarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "cdrom" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<disk name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5=\"" + ((Disk)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1=\"" + ((Disk)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA256) ? " sha256=\"" + ((Disk)rom).SHA256.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA384) ? " sha384=\"" + ((Disk)rom).SHA384.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Disk)rom).SHA512) ? " sha512=\"" + ((Disk)rom).SHA512.ToLowerInvariant() + "\"" : "")
+ (((Disk)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "")
+ "/>\n"
+ "\t\t\t</diskarea>\n";
break;
case ItemType.Release:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "release" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<release name\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (!String.IsNullOrEmpty(((Release)rom).Region) ? " region=\"" + HttpUtility.HtmlEncode(((Release)rom).Region) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Language) ? " language=\"" + HttpUtility.HtmlEncode(((Release)rom).Language) + "\"" : "")
+ (!String.IsNullOrEmpty(((Release)rom).Date) ? " date=\"" + HttpUtility.HtmlEncode(((Release)rom).Date) + "\"" : "")
+ (((Release)rom).Default != null
? ((Release)rom).Default.ToString().ToLowerInvariant()
: "")
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
case ItemType.Rom:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "rom" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<rom name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ (((Rom)rom).Size != -1 ? " size=\"" + ((Rom)rom).Size + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc=\"" + ((Rom)rom).CRC.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5=\"" + ((Rom)rom).MD5.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1=\"" + ((Rom)rom).SHA1.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA256) ? " sha256=\"" + ((Rom)rom).SHA256.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA384) ? " sha384=\"" + ((Rom)rom).SHA384.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).SHA512) ? " sha512=\"" + ((Rom)rom).SHA512.ToLowerInvariant() + "\"" : "")
+ (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date=\"" + ((Rom)rom).Date + "\"" : "")
+ (((Rom)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "")
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
case ItemType.Sample:
state += "\t\t\t<dataarea name=\"" + (String.IsNullOrEmpty(rom.AreaName) ? "sample" : rom.AreaName) + "\""
+ (rom.AreaSize != null ? " size=\"" + rom.AreaSize + "\"" : "") + ">\n"
+ "\t\t\t\t<sample type=\"sample\" name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\""
+ "/>\n"
+ "\t\t\t</dataarea>\n";
break;
}
state += "\t\t</part>\n";
sw.Write(state);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private static bool WriteFooter(StreamWriter sw)
{
try
{
string footer = "\t</software>\n\n</softwarelist>\n";
// Write the footer out
sw.Write(footer);
sw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -115,17 +115,27 @@
<Compile Include="Data\Constants.cs" />
<Compile Include="Data\Flags.cs" />
<Compile Include="Data\Globals.cs" />
<Compile Include="DatFiles\AttractMode.cs" />
<Compile Include="DatFiles\ClrMamePro.cs" />
<Compile Include="DatFiles\DatHeader.cs" />
<Compile Include="DatFiles\DatStats.cs" />
<Compile Include="DatFiles\DatFile.Manipulate.cs" />
<Compile Include="DatFiles\DatFile.ConvertUpdate.cs" />
<Compile Include="DatFiles\DatFile.DFD.cs" />
<Compile Include="DatFiles\DatFile.Parsers.cs" />
<Compile Include="DatFiles\DosCenter.cs" />
<Compile Include="DatFiles\Filter.cs" />
<Compile Include="DatFiles\DatFile.Rebuild.cs" />
<Compile Include="DatFiles\DatFile.Splitters.cs" />
<Compile Include="DatFiles\DatFile.Statistics.cs" />
<Compile Include="DatFiles\DatFile.Writers.cs" />
<Compile Include="DatFiles\Hashfile.cs" />
<Compile Include="DatFiles\Listroms.cs" />
<Compile Include="DatFiles\Logiqx.cs" />
<Compile Include="DatFiles\Missfile.cs" />
<Compile Include="DatFiles\OfflineList.cs" />
<Compile Include="DatFiles\RomCenter.cs" />
<Compile Include="DatFiles\SabreDat.cs" />
<Compile Include="DatFiles\SeparatedValue.cs" />
<Compile Include="DatFiles\SoftwareList.cs" />
<Compile Include="External\CoreRarArchive.cs" />
<Compile Include="External\NaturalSort\NaturalComparer.cs" />
<Compile Include="External\NaturalSort\NaturalReversedComparer.cs" />