Overhaul XeMID handling (fixes #334)

This commit is contained in:
Matt Nadareski
2021-12-21 10:54:57 -08:00
parent 151402dc50
commit 8e843647bf
6 changed files with 588 additions and 107 deletions

View File

@@ -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

View File

@@ -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

423
MPF.Core/Data/XgdInfo.cs Normal file
View File

@@ -0,0 +1,423 @@
using RedumpLib.Data;
namespace MPF.Core.Data
{
/// <summary>
/// Contains information specific to an XGD disc
/// </summary>
/// <remarks>
/// 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
/// </remarks>
public class XgdInfo
{
#region Fields
/// <summary>
/// Indicates whether the information in this object is fully instantiated or not
/// </summary>
public bool Initialized { get; private set; }
/// <summary>
/// Raw XeMID string that all other information is derived from
/// </summary>
public string XeMID { get; private set; }
/// <summary>
/// 2-character publisher identifier
/// </summary>
public string PublisherIdentifier { get; private set; }
/// <summary>
/// Platform disc is made for, 2 indicates Xbox 360
/// </summary>
public char? PlatformIdentifier { get; private set; }
/// <summary>
/// Game ID
/// </summary>
public string GameID { get; private set; }
/// <summary>
/// For XGD1: Internal version number
/// For XGD2/3: Title-specific SKU
/// </summary>
public string SKU { get; private set; }
/// <summary>
/// Region identifier character
/// </summary>
public char RegionIdentifier { get; private set; }
/// <summary>
/// Base version of executables, usually starts at 01
/// </summary>
/// <remarks>
/// TODO: Check if this is always 2 characters for XGD2/3
/// </remarks>
public string BaseVersion { get; private set; }
/// <summary>
/// Media subtype identifier
/// </summary>
public char MediaSubtypeIdentifier { get; private set; }
/// <summary>
/// Disc number stored in [disc number][total discs] format
/// </summary>
public string DiscNumberIdentifier { get; private set; }
/// <summary>
/// 8-hex-digit certification submission identifier; usually on test discs only
/// </summary>
public string CertificationSubmissionIdentifier { get; private set; }
#endregion
#region Auto-Generated Information
/// <summary>
/// Human-readable name derived from the publisher identifier
/// </summary>
public string PublisherName => GetPublisher(this.PublisherIdentifier);
/// <summary>
/// Internally represented region
/// </summary>
public Region? InternalRegion => GetRegion(this.RegionIdentifier);
/// <summary>
/// Human-readable subtype derived from the media identifier
/// </summary>
public string MediaSubtype => GetMediaSubtype(this.MediaSubtypeIdentifier);
#endregion
/// <summary>
/// Populate a set of XGD information from a Master ID (XeMID) string
/// </summary>
/// <param name="xemid">XeMID string representing the DMI information</param>
/// <param name="validate">True if value validation should be performed, false otherwise</param>
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);
}
/// <summary>
/// Get the human-readable serial string
/// </summary>
/// <returns>Formatted serial string, null on error</returns>
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;
}
}
/// <summary>
/// Get the human-readable version string
/// </summary>
/// <returns>Formatted version string, null on error</returns>
/// <remarks>This may differ for XGD2/3 in the future</remarks>
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;
}
}
/// <summary>
/// Parse an XGD1 XeMID string
/// </summary>
/// <param name="xemid">XeMID string to attempt to parse</param>
/// <param name="validate">True if value validation should be performed, false otherwise</param>
/// <returns>True if the XeMID could be parsed, false otherwise</returns>
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;
}
/// <summary>
/// Parse an XGD2/3 XeMID string
/// </summary>
/// <param name="xemid">XeMID string to attempt to parse</param>
/// <param name="validate">True if value validation should be performed, false otherwise</param>
/// <returns>True if the XeMID could be parsed, false otherwise</returns>
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
/// <summary>
/// Determine the XGD type based on the XGD2/3 media type identifier character
/// </summary>
/// <param name="mediaTypeIdentifier">Character denoting the media type</param>
/// <returns>Media subtype as a string, if possible</returns>
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;
}
}
/// <summary>
/// Get the full name of the publisher from the 2-character identifier
/// </summary>
/// <param name="publisherIdentifier">Case-sensitive 2-character identifier</param>
/// <returns>Publisher name, if possible</returns>
/// <see cref="https://xboxdevwiki.net/Xbe#Title_ID"/>
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;
}
}
/// <summary>
/// Determine the region based on the XGD serial character
/// </summary>
/// <param name="region">Character denoting the region</param>
/// <returns>Region, if possible</returns>
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
}
}

View File

@@ -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;
}
/// <summary>
/// Determine the region based on the XGD serial character
/// </summary>
/// <param name="region">Character denoting the region</param>
/// <returns>Region, if possible</returns>
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
}
}

View File

@@ -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
/// </summary>
/// <param name="disc">_disc.txt file location</param>
/// <param name="dmihash">Extracted DMI.bin CRC32 hash (upper-cased)</param>
/// <param name="pfihash">Extracted PFI.bin CRC32 hash (upper-cased)</param>
/// <param name="sshash">Extracted SS.bin CRC32 hash (upper-cased)</param>
/// <param name="ss">Extracted security sector data</param>
/// <param name="ssver">Extracted security sector version</param>
/// <returns>True on successful extraction of info, false otherwise</returns>
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
}
/// <summary>
/// Get the Xbox serial info from the DMI.bin file, if possible
/// Get the XGD1 Master ID (XeMID) information
/// </summary>
/// <param name="dmi">DMI.bin file location</param>
/// <returns>True on successful extraction of info, false otherwise</returns>
private static bool GetXboxDMIInfo(string dmi, out string serial, out string version, out Region? region)
/// <returns>String representation of the XGD1 DMI information, empty string on error</returns>
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;
}
}
}
/// <summary>
/// Get the Xbox 360 serial info from the DMI.bin file, if possible
/// Get the XGD2/3 Master ID (XeMID) information
/// </summary>
/// <param name="dmi">DMI.bin file location</param>
/// <returns>True on successful extraction of info, false otherwise</returns>
private static bool GetXbox360DMIInfo(string dmi, out string serial, out string version, out Region? region)
/// <returns>String representation of the XGD2/3 DMI information, empty string on error</returns>
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;
}
}
}

View File

@@ -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);
}
}
}