diff --git a/CHANGELIST.md b/CHANGELIST.md
index be179fc8..cb2879c7 100644
--- a/CHANGELIST.md
+++ b/CHANGELIST.md
@@ -34,6 +34,7 @@
- Trim filenames for DVD protection from DIC
- Fill out internal tests around Redump library
- Refine the "missing disc" text
+- Overhaul XeMID handling
### 2.1 (2021-07-22)
- Enum, no more
diff --git a/MPF.Core/Data/Constants.cs b/MPF.Core/Data/Constants.cs
index d9fe3be6..50fb9caa 100644
--- a/MPF.Core/Data/Constants.cs
+++ b/MPF.Core/Data/Constants.cs
@@ -105,6 +105,7 @@ namespace MPF.Core.Data
public const string XBOXSSHash = "SS";
public const string XBOXSSRanges = "Security Sector Ranges";
public const string XBOXSSVersion = "Security Sector Version";
+ public const string XBOXXeMID = "XeMID";
// Default values
diff --git a/MPF.Core/Data/XgdInfo.cs b/MPF.Core/Data/XgdInfo.cs
new file mode 100644
index 00000000..32b545be
--- /dev/null
+++ b/MPF.Core/Data/XgdInfo.cs
@@ -0,0 +1,423 @@
+using RedumpLib.Data;
+
+namespace MPF.Core.Data
+{
+ ///
+ /// Contains information specific to an XGD disc
+ ///
+ ///
+ /// XGD1 XeMID Format Information:
+ ///
+ /// AABBBCCD
+ /// - AA => The two-ASCII-character publisher identifier (see GetPublisher for details)
+ /// - BBB => Game ID
+ /// - CC => Version number
+ /// - D => Region identifier (see GetRegion for details)
+ ///
+ /// XGD2/3 XeMID Format Information:
+ ///
+ /// AABCCCDDEFFGHH(IIIIIIII)
+ /// - AA => The two-ASCII-character publisher identifier (see GetPublisher for details)
+ /// - B => Platform identifier; 2 indicates Xbox 360.
+ /// - CCC => Game ID
+ /// - DD => SKU number (unique per SKU of a title)
+ /// - E => Region identifier (see GetRegion for details)
+ /// - FF => Base version; usually starts at 01 (can be 1 or 2 characters)
+ /// - G => Media type identifier (see GetMediaSubtype for details)
+ /// - HH => Disc number stored in [disc number][total discs] format
+ /// - IIIIIIII => 8-hex-digit certification submission identifier; usually on test discs only
+ ///
+ public class XgdInfo
+ {
+ #region Fields
+
+ ///
+ /// Indicates whether the information in this object is fully instantiated or not
+ ///
+ public bool Initialized { get; private set; }
+
+ ///
+ /// Raw XeMID string that all other information is derived from
+ ///
+ public string XeMID { get; private set; }
+
+ ///
+ /// 2-character publisher identifier
+ ///
+ public string PublisherIdentifier { get; private set; }
+
+ ///
+ /// Platform disc is made for, 2 indicates Xbox 360
+ ///
+ public char? PlatformIdentifier { get; private set; }
+
+ ///
+ /// Game ID
+ ///
+ public string GameID { get; private set; }
+
+ ///
+ /// For XGD1: Internal version number
+ /// For XGD2/3: Title-specific SKU
+ ///
+ public string SKU { get; private set; }
+
+ ///
+ /// Region identifier character
+ ///
+ public char RegionIdentifier { get; private set; }
+
+ ///
+ /// Base version of executables, usually starts at 01
+ ///
+ ///
+ /// TODO: Check if this is always 2 characters for XGD2/3
+ ///
+ public string BaseVersion { get; private set; }
+
+ ///
+ /// Media subtype identifier
+ ///
+ public char MediaSubtypeIdentifier { get; private set; }
+
+ ///
+ /// Disc number stored in [disc number][total discs] format
+ ///
+ public string DiscNumberIdentifier { get; private set; }
+
+ ///
+ /// 8-hex-digit certification submission identifier; usually on test discs only
+ ///
+ public string CertificationSubmissionIdentifier { get; private set; }
+
+ #endregion
+
+ #region Auto-Generated Information
+
+ ///
+ /// Human-readable name derived from the publisher identifier
+ ///
+ public string PublisherName => GetPublisher(this.PublisherIdentifier);
+
+ ///
+ /// Internally represented region
+ ///
+ public Region? InternalRegion => GetRegion(this.RegionIdentifier);
+
+ ///
+ /// Human-readable subtype derived from the media identifier
+ ///
+ public string MediaSubtype => GetMediaSubtype(this.MediaSubtypeIdentifier);
+
+ #endregion
+
+ ///
+ /// Populate a set of XGD information from a Master ID (XeMID) string
+ ///
+ /// XeMID string representing the DMI information
+ /// True if value validation should be performed, false otherwise
+ public XgdInfo(string xemid, bool validate = false)
+ {
+ this.Initialized = false;
+ if (string.IsNullOrWhiteSpace(xemid))
+ return;
+
+ this.XeMID = xemid.TrimEnd('\0');
+ if (string.IsNullOrWhiteSpace(this.XeMID))
+ return;
+
+ // XGD1 information is 8 characters
+ if (this.XeMID.Length == 8)
+ this.Initialized = ParseXGD1XeMID(this.XeMID, validate);
+
+ // XGD2/3 information is semi-variable length
+ else if (this.XeMID.Length == 13 || this.XeMID.Length == 14 || this.XeMID.Length == 21 || this.XeMID.Length == 22)
+ this.Initialized = ParseXGD23XeMID(this.XeMID, validate);
+ }
+
+ ///
+ /// Get the human-readable serial string
+ ///
+ /// Formatted serial string, null on error
+ public string GetSerial()
+ {
+ if (!this.Initialized)
+ return null;
+
+ try
+ {
+ // XGD1 doesn't use PlatformIdentifier
+ if (this.PlatformIdentifier == null)
+ return $"{this.PublisherIdentifier}-{this.GameID}";
+
+ // XGD2/3 uses a specific identifier
+ else if (this.PlatformIdentifier == '2')
+ return $"{this.PublisherIdentifier}-{this.PlatformIdentifier}{this.GameID}";
+
+ return null;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Get the human-readable version string
+ ///
+ /// Formatted version string, null on error
+ /// This may differ for XGD2/3 in the future
+ public string GetVersion()
+ {
+ if (!this.Initialized)
+ return null;
+
+ try
+ {
+ // XGD1 doesn't use PlatformIdentifier
+ if (this.PlatformIdentifier == null)
+ return $"1.{this.SKU}";
+
+ // XGD2/3 uses a specific identifier
+ else if (this.PlatformIdentifier == '2')
+ return $"1.{this.SKU}";
+
+ return null;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Parse an XGD1 XeMID string
+ ///
+ /// XeMID string to attempt to parse
+ /// True if value validation should be performed, false otherwise
+ /// True if the XeMID could be parsed, false otherwise
+ private bool ParseXGD1XeMID(string xemid, bool validate)
+ {
+ if (xemid == null || xemid.Length != 8)
+ return false;
+
+ this.PublisherIdentifier = xemid.Substring(0, 2);
+ if (validate && string.IsNullOrEmpty(this.PublisherName))
+ return false;
+
+ this.GameID = xemid.Substring(2, 3);
+ this.SKU = xemid.Substring(5, 2);
+ this.RegionIdentifier = xemid[7];
+ if (validate && this.InternalRegion == null)
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Parse an XGD2/3 XeMID string
+ ///
+ /// XeMID string to attempt to parse
+ /// True if value validation should be performed, false otherwise
+ /// True if the XeMID could be parsed, false otherwise
+ private bool ParseXGD23XeMID(string xemid, bool validate)
+ {
+ if (xemid == null
+ || (xemid.Length != 13 && xemid.Length != 14
+ && xemid.Length != 21 && xemid.Length != 22))
+ return false;
+
+ this.PublisherIdentifier = xemid.Substring(0, 2);
+ if (validate && string.IsNullOrEmpty(this.PublisherName))
+ return false;
+
+ this.PlatformIdentifier = xemid[2];
+ if (validate && this.PlatformIdentifier != '2')
+ return false;
+
+ this.GameID = xemid.Substring(3, 3);
+ this.SKU = xemid.Substring(6, 2);
+ this.RegionIdentifier = xemid[8];
+ if (validate && this.InternalRegion == null)
+ return false;
+
+ if (xemid.Length == 13 || xemid.Length == 21)
+ {
+ this.BaseVersion = xemid.Substring(9, 1);
+ this.MediaSubtypeIdentifier = xemid[10];
+ if (validate && string.IsNullOrEmpty(this.MediaSubtype))
+ return false;
+
+ this.DiscNumberIdentifier = xemid.Substring(11, 2);
+ }
+ else if (xemid.Length == 14 || xemid.Length == 22)
+ {
+ this.BaseVersion = xemid.Substring(9, 2);
+ this.MediaSubtypeIdentifier = xemid[11];
+ if (validate && string.IsNullOrEmpty(this.MediaSubtype))
+ return false;
+
+ this.DiscNumberIdentifier = xemid.Substring(12, 2);
+ }
+
+ if (xemid.Length == 21)
+ this.CertificationSubmissionIdentifier = xemid.Substring(13);
+ else if (xemid.Length == 22)
+ this.CertificationSubmissionIdentifier = xemid.Substring(14);
+
+ return true;
+ }
+
+ #region Helpers
+
+ ///
+ /// Determine the XGD type based on the XGD2/3 media type identifier character
+ ///
+ /// Character denoting the media type
+ /// Media subtype as a string, if possible
+ private static string GetMediaSubtype(char mediaTypeIdentifier)
+ {
+ switch (mediaTypeIdentifier)
+ {
+ case 'F': return "XGD3";
+ case 'X': return "XGD2";
+ case 'Z': return "Games on Demand / Marketplace Demo";
+ default: return null;
+ }
+ }
+
+ ///
+ /// Get the full name of the publisher from the 2-character identifier
+ ///
+ /// Case-sensitive 2-character identifier
+ /// Publisher name, if possible
+ ///
+ private static string GetPublisher(string publisherIdentifier)
+ {
+ switch (publisherIdentifier)
+ {
+ case "AC": return "Acclaim Entertainment";
+ case "AH": return "ARUSH Entertainment";
+ case "AQ": return "Aqua System";
+ case "AS": return "ASK";
+ case "AT": return "Atlus";
+ case "AV": return "Activision";
+ case "AY": return "Aspyr Media";
+ case "BA": return "Bandai";
+ case "BL": return "Black Box";
+ case "BM": return "BAM! Entertainment";
+ case "BR": return "Broccoli Co.";
+ case "BS": return "Bethesda Softworks";
+ case "BU": return "Bunkasha Co.";
+ case "BV": return "Buena Vista Games";
+ case "BW": return "BBC Multimedia";
+ case "BZ": return "Blizzard";
+ case "CC": return "Capcom";
+ case "CK": return "Kemco Corporation"; // TODO: Confirm
+ case "CM": return "Codemasters";
+ case "CV": return "Crave Entertainment";
+ case "DC": return "DreamCatcher Interactive";
+ case "DX": return "Davilex";
+ case "EA": return "Electronic Arts (EA)";
+ case "EC": return "Encore inc";
+ case "EL": return "Enlight Software";
+ case "EM": return "Empire Interactive";
+ case "ES": return "Eidos Interactive";
+ case "FI": return "Fox Interactive";
+ case "FS": return "From Software";
+ case "GE": return "Genki Co.";
+ case "GV": return "Groove Games";
+ case "HE": return "Tru Blu (Entertainment division of Home Entertainment Suppliers)";
+ case "HP": return "Hip games";
+ case "HU": return "Hudson Soft";
+ case "HW": return "Highwaystar";
+ case "IA": return "Mad Catz Interactive";
+ case "IF": return "Idea Factory";
+ case "IG": return "Infogrames";
+ case "IL": return "Interlex Corporation";
+ case "IM": return "Imagine Media";
+ case "IO": return "Ignition Entertainment";
+ case "IP": return "Interplay Entertainment";
+ case "IX": return "InXile Entertainment"; // TODO: Confirm
+ case "JA": return "Jaleco";
+ case "JW": return "JoWooD";
+ case "KB": return "Kemco"; // TODO: Confirm
+ case "KI": return "Kids Station Inc."; // TODO: Confirm
+ case "KN": return "Konami";
+ case "KO": return "KOEI";
+ case "KU": return "Kobi and / or GAE (formerly Global A Entertainment)"; // TODO: Confirm
+ case "LA": return "LucasArts";
+ case "LS": return "Black Bean Games (publishing arm of Leader S.p.A.)";
+ case "MD": return "Metro3D";
+ case "ME": return "Medix";
+ case "MI": return "Microïds";
+ case "MJ": return "Majesco Entertainment";
+ case "MM": return "Myelin Media";
+ case "MP": return "MediaQuest"; // TODO: Confirm
+ case "MS": return "Microsoft Game Studios";
+ case "MW": return "Midway Games";
+ case "MX": return "Empire Interactive"; // TODO: Confirm
+ case "NK": return "NewKidCo";
+ case "NL": return "NovaLogic";
+ case "NM": return "Namco";
+ case "OX": return "Oxygen Interactive";
+ case "PC": return "Playlogic Entertainment";
+ case "PL": return "Phantagram Co., Ltd.";
+ case "RA": return "Rage";
+ case "SA": return "Sammy";
+ case "SC": return "SCi Games";
+ case "SE": return "SEGA";
+ case "SN": return "SNK";
+ case "SS": return "Simon & Schuster";
+ case "SU": return "Success Corporation";
+ case "SW": return "Swing! Deutschland";
+ case "TA": return "Takara";
+ case "TC": return "Tecmo";
+ case "TD": return "The 3DO Company (or just 3DO)";
+ case "TK": return "Takuyo";
+ case "TM": return "TDK Mediactive";
+ case "TQ": return "THQ";
+ case "TS": return "Titus Interactive";
+ case "TT": return "Take-Two Interactive Software";
+ case "US": return "Ubisoft";
+ case "VC": return "Victor Interactive Software";
+ case "VN": return "Vivendi Universal (just took Interplays publishing rights)"; // TODO: Confirm
+ case "VU": return "Vivendi Universal Games";
+ case "VV": return "Vivendi Universal Games"; // TODO: Confirm
+ case "WE": return "Wanadoo Edition";
+ case "WR": return "Warner Bros. Interactive Entertainment"; // TODO: Confirm
+ case "XI": return "XPEC Entertainment and Idea Factory";
+ case "XK": return "Xbox kiosk disk?"; // TODO: Confirm
+ case "XL": return "Xbox special bundled or live demo disk?"; // TODO: Confirm
+ case "XM": return "Evolved Games"; // TODO: Confirm
+ case "XP": return "XPEC Entertainment";
+ case "XR": return "Panorama";
+ case "YB": return "YBM Sisa (South-Korea)";
+ case "ZD": return "Zushi Games (formerly Zoo Digital Publishing)";
+ default: return null;
+ }
+ }
+
+ ///
+ /// Determine the region based on the XGD serial character
+ ///
+ /// Character denoting the region
+ /// Region, if possible
+ private static Region? GetRegion(char region)
+ {
+ switch (region)
+ {
+ case 'W': return Region.World;
+ case 'A': return Region.USA;
+ case 'J': return Region.JapanAsia;
+ case 'E': return Region.Europe;
+ case 'K': return Region.USAJapan;
+ case 'L': return Region.USAEurope;
+ case 'H': return Region.JapanEurope;
+ default: return null;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/MPF.Modules/BaseParameters.cs b/MPF.Modules/BaseParameters.cs
index 1e135c81..30218917 100644
--- a/MPF.Modules/BaseParameters.cs
+++ b/MPF.Modules/BaseParameters.cs
@@ -1324,14 +1324,10 @@ namespace MPF.Modules
{
switch (category)
{
- case "GAME":
- return DiscCategory.Games;
- case "VIDEO":
- return DiscCategory.Video;
- case "AUDIO":
- return DiscCategory.Audio;
- default:
- return null;
+ case "GAME": return DiscCategory.Games;
+ case "VIDEO": return DiscCategory.Video;
+ case "AUDIO": return DiscCategory.Audio;
+ default: return null;
}
}
@@ -1353,20 +1349,13 @@ namespace MPF.Modules
// char secondRegion = serial[3];
switch (serial[2])
{
- case 'A':
- return Region.Asia;
- case 'C':
- return Region.China;
- case 'E':
- return Region.Europe;
- case 'J':
- return Region.JapanKorea;
- case 'K':
- return Region.Korea;
- case 'P':
- return Region.Japan;
- case 'U':
- return Region.USA;
+ case 'A': return Region.Asia;
+ case 'C': return Region.China;
+ case 'E': return Region.Europe;
+ case 'J': return Region.JapanKorea;
+ case 'K': return Region.Korea;
+ case 'P': return Region.Japan;
+ case 'U': return Region.USA;
}
}
@@ -1393,34 +1382,6 @@ namespace MPF.Modules
return null;
}
- ///
- /// Determine the region based on the XGD serial character
- ///
- /// Character denoting the region
- /// Region, if possible
- protected static Region? GetXgdRegion(char region)
- {
- switch (region)
- {
- case 'W':
- return Region.World;
- case 'A':
- return Region.USA;
- case 'J':
- return Region.JapanAsia;
- case 'E':
- return Region.Europe;
- case 'K':
- return Region.USAJapan;
- case 'L':
- return Region.USAEurope;
- case 'H':
- return Region.JapanEurope;
- default:
- return null;
- }
- }
-
#endregion
}
}
\ No newline at end of file
diff --git a/MPF.Modules/DiscImageCreator/Parameters.cs b/MPF.Modules/DiscImageCreator/Parameters.cs
index 29a0342b..3928b342 100644
--- a/MPF.Modules/DiscImageCreator/Parameters.cs
+++ b/MPF.Modules/DiscImageCreator/Parameters.cs
@@ -472,40 +472,31 @@ namespace MPF.Modules.DiscImageCreator
break;
case RedumpSystem.MicrosoftXbox:
- if (GetXgdAuxInfo(basePath + "_disc.txt", out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver))
- {
- info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {dmihash ?? ""}\n" +
- $"{Template.XBOXPFIHash}: {pfihash ?? ""}\n" +
- $"{Template.XBOXSSHash}: {sshash ?? ""}\n" +
- $"{Template.XBOXSSVersion}: {ssver ?? ""}\n";
- info.Extras.SecuritySectorRanges = ss ?? "";
- }
-
- if (GetXboxDMIInfo(Path.Combine(outputDirectory, "DMI.bin"), out string serial, out string version, out Region? region))
- {
- info.CommonDiscInfo.Serial = serial ?? "";
- info.VersionAndEditions.Version = version ?? "";
- info.CommonDiscInfo.Region = region;
- }
-
- break;
-
case RedumpSystem.MicrosoftXbox360:
- if (GetXgdAuxInfo(basePath + "_disc.txt", out string dmi360hash, out string pfi360hash, out string ss360hash, out string ss360, out string ssver360))
+ string xgdXeMID = string.Empty;
+ if (this.System == RedumpSystem.MicrosoftXbox)
+ xgdXeMID = GetXGD1XeMID(Path.Combine(outputDirectory, "DMI.bin"));
+ else if (this.System == RedumpSystem.MicrosoftXbox360)
+ xgdXeMID = GetXGD23XeMID(Path.Combine(outputDirectory, "DMI.bin"));
+
+ XgdInfo xgdInfo = new XgdInfo(xgdXeMID);
+ if (xgdInfo?.Initialized == true)
{
- info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {dmi360hash ?? ""}\n" +
- $"{Template.XBOXPFIHash}: {pfi360hash ?? ""}\n" +
- $"{Template.XBOXSSHash}: {ss360hash ?? ""}\n" +
- $"{Template.XBOXSSVersion}: {ssver360 ?? ""}\n";
- info.Extras.SecuritySectorRanges = ss360 ?? "";
+ info.CommonDiscInfo.Comments += $"{Template.XBOXXeMID}: {xgdInfo.XeMID ?? ""}\n";
+ info.CommonDiscInfo.Serial = xgdInfo.GetSerial() ?? "";
+ info.VersionAndEditions.Version = xgdInfo.GetVersion() ?? "";
+ info.CommonDiscInfo.Region = xgdInfo.InternalRegion;
}
- if (GetXbox360DMIInfo(Path.Combine(outputDirectory, "DMI.bin"), out string serial360, out string version360, out Region? region360))
+ if (GetXGDAuxInfo(basePath + "_disc.txt", out string xgdDMIHash, out string xgdPFIHash, out string xgdSSHash, out string xgdSS, out string xgdSSVer))
{
- info.CommonDiscInfo.Serial = serial360 ?? "";
- info.VersionAndEditions.Version = version360 ?? "";
- info.CommonDiscInfo.Region = region360;
+ info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {xgdDMIHash ?? ""}\n" +
+ $"{Template.XBOXPFIHash}: {xgdPFIHash ?? ""}\n" +
+ $"{Template.XBOXSSHash}: {xgdSSHash ?? ""}\n" +
+ $"{Template.XBOXSSVersion}: {xgdSSVer ?? ""}\n";
+ info.Extras.SecuritySectorRanges = xgdSS ?? "";
}
+
break;
case RedumpSystem.NamcoSegaNintendoTriforce:
@@ -3094,8 +3085,13 @@ namespace MPF.Modules.DiscImageCreator
/// Get the XGD auxiliary info from the outputted files, if possible
///
/// _disc.txt file location
+ /// Extracted DMI.bin CRC32 hash (upper-cased)
+ /// Extracted PFI.bin CRC32 hash (upper-cased)
+ /// Extracted SS.bin CRC32 hash (upper-cased)
+ /// Extracted security sector data
+ /// Extracted security sector version
/// True on successful extraction of info, false otherwise
- private static bool GetXgdAuxInfo(string disc, out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver)
+ private static bool GetXGDAuxInfo(string disc, out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver)
{
dmihash = null; pfihash = null; sshash = null; ss = null; ssver = null;
@@ -3169,65 +3165,49 @@ namespace MPF.Modules.DiscImageCreator
}
///
- /// Get the Xbox serial info from the DMI.bin file, if possible
+ /// Get the XGD1 Master ID (XeMID) information
///
/// DMI.bin file location
- /// True on successful extraction of info, false otherwise
- private static bool GetXboxDMIInfo(string dmi, out string serial, out string version, out Region? region)
+ /// String representation of the XGD1 DMI information, empty string on error
+ private static string GetXGD1XeMID(string dmi)
{
- serial = null; version = null; region = Region.World;
-
if (!File.Exists(dmi))
- return false;
+ return string.Empty;
using (BinaryReader br = new BinaryReader(File.OpenRead(dmi)))
{
try
{
br.BaseStream.Seek(8, SeekOrigin.Begin);
- char[] str = br.ReadChars(8);
-
- serial = $"{str[0]}{str[1]}-{str[2]}{str[3]}{str[4]}";
- version = $"1.{str[5]}{str[6]}";
- region = GetXgdRegion(str[7]);
- return true;
+ return new string(br.ReadChars(8));
}
catch
{
- return false;
+ return string.Empty;
}
}
}
///
- /// Get the Xbox 360 serial info from the DMI.bin file, if possible
+ /// Get the XGD2/3 Master ID (XeMID) information
///
/// DMI.bin file location
- /// True on successful extraction of info, false otherwise
- private static bool GetXbox360DMIInfo(string dmi, out string serial, out string version, out Region? region)
+ /// String representation of the XGD2/3 DMI information, empty string on error
+ private static string GetXGD23XeMID(string dmi)
{
- serial = null; version = null; region = Region.World;
-
if (!File.Exists(dmi))
- return false;
+ return string.Empty;
using (BinaryReader br = new BinaryReader(File.OpenRead(dmi)))
{
try
{
br.BaseStream.Seek(64, SeekOrigin.Begin);
- char[] str = br.ReadChars(14);
-
- serial = $"{str[0]}{str[1]}-{str[2]}{str[3]}{str[4]}{str[5]}";
- version = $"1.{str[6]}{str[7]}";
- region = GetXgdRegion(str[8]);
- // str[9], str[10], str[11] - unknown purpose
- // str[12], str[13] - disc <12> of <13>
- return true;
+ return new string(br.ReadChars(14));
}
catch
{
- return false;
+ return string.Empty;
}
}
}
diff --git a/MPF.Test/Core/Data/XgdInfoTests.cs b/MPF.Test/Core/Data/XgdInfoTests.cs
new file mode 100644
index 00000000..335407ab
--- /dev/null
+++ b/MPF.Test/Core/Data/XgdInfoTests.cs
@@ -0,0 +1,115 @@
+using MPF.Core.Data;
+using Xunit;
+
+namespace MPF.Test.Core.Data
+{
+ public class XgdInfoTests
+ {
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("1234567")]
+ [InlineData("1234567\0")]
+ [InlineData("123456789")]
+ public void UnmatchedStringTests(string invalidString)
+ {
+ XgdInfo xgdInfo = new XgdInfo(invalidString);
+ Assert.False(xgdInfo.Initialized);
+ }
+
+ [Theory]
+ [InlineData("AV00100W", "AV", "001", "00", 'W')]
+ [InlineData("AV00100W\0", "AV", "001", "00", 'W')]
+ public void XGD1ValidTests(string validString, string publisher, string gameId, string version, char regionIdentifier)
+ {
+ XgdInfo xgdInfo = new XgdInfo(validString, validate: true);
+
+ Assert.True(xgdInfo.Initialized);
+ Assert.Equal(publisher, xgdInfo.PublisherIdentifier);
+ Assert.Equal(gameId, xgdInfo.GameID);
+ Assert.Equal(version, xgdInfo.SKU);
+ Assert.Equal(regionIdentifier, xgdInfo.RegionIdentifier);
+ }
+
+ [Theory]
+ // Invalid publisher identifier
+ [InlineData("ZZ000000")]
+ [InlineData("ZZ000000\0")]
+ // Invalid region identifier
+ [InlineData("AV00000Z")]
+ [InlineData("AV00000Z\0")]
+ public void XGD1InvalidTests(string invalidString)
+ {
+ XgdInfo xgdInfo = new XgdInfo(invalidString, validate: true);
+ Assert.False(xgdInfo.Initialized);
+ }
+
+ [Theory]
+ [InlineData("AV200100W0F11", "AV", "001", "00", 'W', "0", 'F', "11", null)]
+ [InlineData("AV200100W0F11\0", "AV", "001", "00", 'W', "0", 'F', "11", null)]
+ [InlineData("AV200100W01F11", "AV", "001", "00", 'W', "01", 'F', "11", null)]
+ [InlineData("AV200100W01F11\0", "AV", "001", "00", 'W', "01", 'F', "11", null)]
+ [InlineData("AV200100W0F11DEADBEEF", "AV", "001", "00", 'W', "0", 'F', "11", "DEADBEEF")]
+ [InlineData("AV200100W0F11DEADBEEF\0", "AV", "001", "00", 'W', "0", 'F', "11", "DEADBEEF")]
+ [InlineData("AV200100W01F11DEADBEEF", "AV", "001", "00", 'W', "01", 'F', "11", "DEADBEEF")]
+ [InlineData("AV200100W01F11DEADBEEF\0", "AV", "001", "00", 'W', "01", 'F', "11", "DEADBEEF")]
+ public void XGD23ValidTests(string validString, string publisher, string gameId, string sku, char regionIdentifier, string baseVersion, char mediaSubtype, string discNumber, string cert)
+ {
+ XgdInfo xgdInfo = new XgdInfo(validString, validate: true);
+
+ Assert.True(xgdInfo.Initialized);
+ Assert.Equal(publisher, xgdInfo.PublisherIdentifier);
+ Assert.Equal('2', xgdInfo.PlatformIdentifier);
+ Assert.Equal(gameId, xgdInfo.GameID);
+ Assert.Equal(sku, xgdInfo.SKU);
+ Assert.Equal(regionIdentifier, xgdInfo.RegionIdentifier);
+ Assert.Equal(baseVersion, xgdInfo.BaseVersion);
+ Assert.Equal(mediaSubtype, xgdInfo.MediaSubtypeIdentifier);
+ Assert.Equal(discNumber, xgdInfo.DiscNumberIdentifier);
+ Assert.Equal(cert, xgdInfo.CertificationSubmissionIdentifier);
+ }
+
+ [Theory]
+ // Invalid publisher identifier
+ [InlineData("ZZ00000000000")]
+ [InlineData("ZZ00000000000\0")]
+ [InlineData("ZZ000000000000")]
+ [InlineData("ZZ000000000000\0")]
+ [InlineData("ZZ0000000000000000000")]
+ [InlineData("ZZ0000000000000000000\0")]
+ [InlineData("ZZ00000000000000000000")]
+ [InlineData("ZZ00000000000000000000\0")]
+ // Invalid platform identifier
+ [InlineData("AV90000000000")]
+ [InlineData("AV90000000000\0")]
+ [InlineData("AV900000000000")]
+ [InlineData("AV900000000000\0")]
+ [InlineData("AV9000000000000000000")]
+ [InlineData("AV9000000000000000000\0")]
+ [InlineData("AV90000000000000000000")]
+ [InlineData("AV90000000000000000000\0")]
+ // Invalid region identifier
+ [InlineData("AV200000Z0000")]
+ [InlineData("AV200000Z0000\0")]
+ [InlineData("AV200000Z00000")]
+ [InlineData("AV200000Z00000\0")]
+ [InlineData("AV200000Z000000000000")]
+ [InlineData("AV200000Z000000000000\0")]
+ [InlineData("AV200000Z0000000000000")]
+ [InlineData("AV200000Z0000000000000\0")]
+ // Invalid media subtype identifier
+ [InlineData("AV200000W0A00")]
+ [InlineData("AV200000W0A00\0")]
+ [InlineData("AV200000W00A00")]
+ [InlineData("AV200000W00A00\0")]
+ [InlineData("AV200000W00A000000000")]
+ [InlineData("AV200000W00A000000000\0")]
+ [InlineData("AV200000W00A0000000000")]
+ [InlineData("AV200000W00A0000000000\0")]
+ public void XGD23InvalidTests(string invalidString)
+ {
+ XgdInfo xgdInfo = new XgdInfo(invalidString, validate: true);
+ Assert.False(xgdInfo.Initialized);
+ }
+ }
+}