using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; using System.Xml; using System.Windows.Forms; namespace BizHawk.Client.DBMan { public class TOSECParser : DATParser { /// /// Required to generate a GameDB file /// public override SystemType SysType { get; set; } private List xmls = new List(); public TOSECParser(SystemType type) { SysType = type; } /// /// Parses multiple DAT files and returns a single GamesDB format csv string /// public override string ParseDAT(string[] filePath) { foreach (var s in filePath) { try { xmls.Add(XDocument.Load(s)); } catch { var res = MessageBox.Show("Could not parse document as valid XML:\n\n" + s + "\n\nDo you wish to continue any other processing?", "Parsing Error", MessageBoxButtons.YesNo); if (res != DialogResult.Yes) return ""; } } int startIndex = 0; // actual tosec parsing foreach (var obj in xmls) { startIndex = Data.Count > 0 ? Data.Count - 1 : 0; // get header info var header = obj.Root.Descendants("header").First(); var category = header.Element("category").Value; var name = header.Element("name").Value; var version = header.Element("version").Value; var description = header.Element("description").Value; // start comment block List comments = new List { $"Type:\t{category}", $"Source:\t{description}", $"FileGen:\t{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} (UTC)", }; AddCommentBlock(comments.ToArray()); // process each entry var query = obj.Root.Descendants("game"); foreach (var g in query) { GameDB item = new GameDB(); item.Name = g.Value; item.SHA1 = g.Elements("rom").First().Attribute("sha1").Value.ToUpper(); item.MD5 = g.Elements("rom").First().Attribute("md5").Value.ToUpper(); item.System = GameDB.GetSystemCode(SysType); ParseTOSECFlags(item); Data.Add(item); } // add this file's data to the stringbuilder // first we will sort into various ROMSTATUS groups var working = Data.Skip(startIndex).ToList(); var baddump = working.Where(st => st.Status == "B").OrderBy(na => na.Name).ToList(); AddCommentBlock("Bad Dumps"); AppendCSVData(baddump); var hack = working.Where(st => st.Status == "H").OrderBy(na => na.Name).ToList(); AddCommentBlock("Hacks"); AppendCSVData(hack); var over = working.Where(st => st.Status == "O").OrderBy(na => na.Name).ToList(); AddCommentBlock("Over Dumps"); AppendCSVData(over); var trans = working.Where(st => st.Status == "T").OrderBy(na => na.Name).ToList(); AddCommentBlock("Translated"); AppendCSVData(trans); var pd = working.Where(st => st.Status == "D").OrderBy(na => na.Name).ToList(); AddCommentBlock("Home Brew"); AppendCSVData(pd); var good = working.Where(st => st.Status == "" || st.Status == null).OrderBy(na => na.Name).ToList(); AddCommentBlock("Believed Good"); AppendCSVData(good); } string result = sb.ToString(); return sb.ToString(); } /// /// Parses all the weird TOSEC flags within the game field /// Detailed info here: https://www.tosecdev.org/tosec-naming-convention /// Guts of this has been reused from here: https://github.com/Asnivor/MedLaunch/blob/master/MedLaunch/_Debug/DATDB/Platforms/TOSEC/StringConverterToSec.cs /// private void ParseTOSECFlags(GameDB g) { string nameString = g.Name; // remove uninteresting options string a = RemoveUnneededOptions(nameString); // process data contained in () string[] d = a.ToString().Split('(', ')'); if (d.Length > 0) { // name field } if (d.Length > 1) { if (d[1].Length > 3) { // year field } } if (d.Length > 3) { // publisher field } // public domain if (nameString.Contains("(PD)")) { g.Status = "D"; } if (d.Length > 4) { // parse all other () fields // because these are not mandatory this can be a confusing process for (int i = 4; i < d.Length; i++) { string f = d[i]; // system field if (f == "Aladdin Deck Enhancer" || f == "PlayChoice-10" || f == "VS DualSystem" || f == "VS UniSystem") { // ignore for now (not currently implemented) continue; } // country flag(s) if (IsCountryFlag(f) == true) { g.Region = f; continue; } // language - if present add to notes if (IsLanguageFlag(f) == true) { g.Notes = f; continue; } // check copyright status (not currently implemented) if (IsCopyrightStatus(f) == true) { continue; } // check development status (not currently implemented) if (IsDevelopmenttStatus(f) == true) { continue; } // Media Type - ignore for now // Media Label - ignore for now } // process dump info flags and other info contained in [] if (nameString.Contains("[") && nameString.Contains("]")) { List e = nameString.ToString().Split('[', ']').ToList(); // remove first entry (this is the bit before the [] entries start e.RemoveAt(0); // remove empty entries e = e.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList(); if (e.Count > 0) { // bizhawk currently only has a few different RomStatus values (not as many as TOSEC anyway) // Parsing priority will be: // RomStatus.BadDump // RomStatus.Hack // RomStatus.Overdump // RomStatus.GoodDump // RomStatus.TranslatedRom // everything else // all tosec cr, h, t etc.. will fall under RomStatus.Hack if (e.Where(str => // bad dump str == "b" || str.StartsWith("b ") || // virus str == "v" || str.StartsWith("v ") || // under dump str == "u" || str.StartsWith("u ")).ToList().Count > 0) { // RomStatus.BadDump g.Status = "B"; } else if (e.Where(str => // cracked str == "cr" || str.StartsWith("cr ") || // fixed str == "f" || str.StartsWith("f ") || // hack str == "h" || str.StartsWith("h ") || // modified str == "m" || str.StartsWith("m ") || // pirated str == "p" || str.StartsWith("p ") || // trained str == "t" || str.StartsWith("t ") ).ToList().Count > 0) { // RomStatus.Hack g.Status = "H"; } else if (e.Where(str => // over dump str == "o" || str.StartsWith("o ")).ToList().Count > 0) { // RomStatus.Overdump g.Status = "O"; } else if (e.Where(str => // known verified dump str == "!").ToList().Count > 0) { // RomStatus.GoodDump g.Status = ""; } else if (e.Where(str => // translated str == "tr" || str.StartsWith("tr ")).ToList().Count > 0) { // RomStatus.TranslatedRom g.Status = "T"; } } } } } public static bool IsDevelopmenttStatus(string s) { List DS = new List { "alpha", "beta", "preview", "pre-release", "proto" }; bool b = DS.Any(s.Contains); return b; } public static bool IsCopyrightStatus(string s) { List CS = new List { "CW", "CW-R", "FW", "GW", "GW-R", "LW", "PD", "SW", "SW-R" }; bool b = CS.Any(s.Contains); return b; } public static bool IsLanguageFlag(string s) { List LC = new List { "ar", "bg", "bs", "cs", "cy", "da", "de", "el", "en", "eo", "es", "et", "fa", "fi", "fr", "ga", "gu", "he", "hi", "hr", "hu", "is", "it", "ja", "ko", "lt", "lv", "ms", "nl", "no", "pl", "pt", "ro", "ru", "sk", "sl", "sq", "sr", "sv", "th", "tr", "ur", "vi", "yi", "zh", "M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9" }; bool b = false; if (!s.Contains("[") && !s.Contains("]")) { foreach (var x in LC) { if (s == x || s.StartsWith(x) || s.EndsWith(x)) { b = true; break; } } //b = LC.Any(s.Contains); } return b; } public static bool IsCountryFlag(string s) { List CC = new List { "AE", "AL", "AS", "AT", "AU", "BA", "BE", "BG", "BR", "CA", "CH", "CL", "CN", "CS", "CY", "CZ", "DE", "DK", "EE", "EG", "EU", "ES", "FI", "FR", "GB", "GR", "HK", "HR", "HU", "ID", "IE", "IL", "IN", "IR", "IS", "IT", "JO", "JP", "KR", "LT", "LU", "LV", "MN", "MX", "MY", "NL", "NO", "NP", "NZ", "OM", "PE", "PH", "PL", "PT", "QA", "RO", "RU", "SE", "SG", "SI", "SK", "TH", "TR", "TW", "US", "VN", "YU", "ZA" }; bool b = false; if (!s.Contains("[") && !s.Contains("]")) { foreach (var x in CC) { if (s == x || s.StartsWith(x) || s.EndsWith(x)) { b = true; break; } } //b = CC.Any(s.Contains); } return b; } public static string RemoveUnneededOptions(string nameString) { // Remove unneeded entries string n = nameString .Replace(" (demo) ", " ") .Replace(" (demo-kiosk) ", " ") .Replace(" (demo-playable) ", " ") .Replace(" (demo-rolling) ", " ") .Replace(" (demo-slideshow) ", " "); return n; } } }