diff --git a/Files/XMID.Deserializer.cs b/Files/XMID.Deserializer.cs new file mode 100644 index 00000000..7f2bd0cc --- /dev/null +++ b/Files/XMID.Deserializer.cs @@ -0,0 +1,214 @@ +namespace SabreTools.Serialization.Files +{ + public partial class XMID : IFileSerializer + { + /// + /// This treats the input path like a parseable string +#if NET48 + public Models.Xbox.XMID Deserialize(string path) +#else + public Models.Xbox.XMID? Deserialize(string? path) +#endif + { + if (string.IsNullOrWhiteSpace(path)) + return null; + + string xmid = path.TrimEnd('\0'); + if (string.IsNullOrWhiteSpace(xmid)) + return null; + + return ParseXMID(xmid); + } + + /// + /// Parse an XGD2/3 XMID string + /// + /// XMID string to attempt to parse + /// Filled XMID on success, null on error +#if NET48 + private static Models.Xbox.XMID ParseXMID(string xmidString) +#else + private static Models.Xbox.XMID? ParseXMID(string? xmidString) +#endif + { + if (xmidString == null || xmidString.Length != 8) + return null; + + var xmid = new Models.Xbox.XMID(); + + xmid.PublisherIdentifier = xmidString.Substring(0, 2); + if (string.IsNullOrEmpty(PublisherName(xmid))) + return null; + + xmid.GameID = xmidString.Substring(2, 3); + xmid.VersionNumber = xmidString.Substring(5, 2); + xmid.RegionIdentifier = xmidString[7]; + if (InternalRegion(xmid) == null) + return null; + + return xmid; + } + + #region Helpers + + /// + /// Human-readable name derived from the publisher identifier + /// +#if NET48 + public static string PublisherName(Models.Xbox.XMID xmid) => GetPublisher(xmid.PublisherIdentifier); +#else + public static string? PublisherName(Models.Xbox.XMID xmid) => GetPublisher(xmid.PublisherIdentifier); +#endif + + /// + /// Internally represented region + /// +#if NET48 + public static string InternalRegion(Models.Xbox.XMID xmid) => GetRegion(xmid.RegionIdentifier); +#else + public static string? InternalRegion(Models.Xbox.XMID xmid) => GetRegion(xmid.RegionIdentifier); +#endif + + /// + /// Get the full name of the publisher from the 2-character identifier + /// + /// Case-sensitive 2-character identifier + /// Publisher name, if possible + /// +#if NET48 + private static string GetPublisher(string publisherIdentifier) +#else + private static string? GetPublisher(string? publisherIdentifier) +#endif + { + 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 +#if NET48 + private static string GetRegion(char region) +#else + private static string? GetRegion(char region) +#endif + { + switch (region) + { + case 'W': return "World"; + case 'A': return "USA"; + case 'J': return "Japan / Asia"; + case 'E': return "Europe"; + case 'K': return "USA / Japan"; + case 'L': return "USA / Europe"; + case 'H': return "Japan / Europe"; + default: return null; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Files/XMID.Serializer.cs b/Files/XMID.Serializer.cs new file mode 100644 index 00000000..1f747078 --- /dev/null +++ b/Files/XMID.Serializer.cs @@ -0,0 +1,14 @@ +using System; + +namespace SabreTools.Serialization.Files +{ + public partial class XMID : IFileSerializer + { + /// +#if NET48 + public bool Serialize(Models.Xbox.XMID obj, string path) => throw new NotImplementedException(); +#else + public bool Serialize(Models.Xbox.XMID? obj, string? path) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file diff --git a/Files/XeMID.Deserializer.cs b/Files/XeMID.Deserializer.cs new file mode 100644 index 00000000..d80b52d6 --- /dev/null +++ b/Files/XeMID.Deserializer.cs @@ -0,0 +1,273 @@ +namespace SabreTools.Serialization.Files +{ + public partial class XeMID : IFileSerializer + { + /// + /// This treats the input path like a parseable string +#if NET48 + public Models.Xbox.XeMID Deserialize(string path) +#else + public Models.Xbox.XeMID? Deserialize(string? path) +#endif + { + if (string.IsNullOrWhiteSpace(path)) + return null; + + string xemid = path.TrimEnd('\0'); + if (string.IsNullOrWhiteSpace(xemid)) + return null; + + return ParseXeMID(xemid); + } + + /// + /// Parse an XGD2/3 XeMID string + /// + /// XeMID string to attempt to parse + /// Filled XeMID on success, null on error +#if NET48 + private static Models.Xbox.XeMID ParseXeMID(string xemidString) +#else + private static Models.Xbox.XeMID? ParseXeMID(string? xemidString) +#endif + { + if (xemidString == null + || (xemidString.Length != 13 && xemidString.Length != 14 + && xemidString.Length != 21 && xemidString.Length != 22)) + return null; + + var xemid = new Models.Xbox.XeMID(); + + xemid.PublisherIdentifier = xemidString.Substring(0, 2); + if (string.IsNullOrEmpty(PublisherName(xemid))) + return null; + + xemid.PlatformIdentifier = xemidString[2]; + if (xemid.PlatformIdentifier != '2') + return null; + + xemid.GameID = xemidString.Substring(3, 3); + xemid.SKU = xemidString.Substring(6, 2); + xemid.RegionIdentifier = xemidString[8]; + if (InternalRegion(xemid) == null) + return null; + + if (xemidString.Length == 13 || xemidString.Length == 21) + { + xemid.BaseVersion = xemidString.Substring(9, 1); + xemid.MediaSubtypeIdentifier = xemidString[10]; + if (string.IsNullOrEmpty(MediaSubtype(xemid))) + return null; + + xemid.DiscNumberIdentifier = xemidString.Substring(11, 2); + } + else if (xemidString.Length == 14 || xemidString.Length == 22) + { + xemid.BaseVersion = xemidString.Substring(9, 2); + xemid.MediaSubtypeIdentifier = xemidString[11]; + if (string.IsNullOrEmpty(MediaSubtype(xemid))) + return null; + + xemid.DiscNumberIdentifier = xemidString.Substring(12, 2); + } + + if (xemidString.Length == 21) + xemid.CertificationSubmissionIdentifier = xemidString.Substring(13); + else if (xemidString.Length == 22) + xemid.CertificationSubmissionIdentifier = xemidString.Substring(14); + + return xemid; + } + + #region Helpers + + /// + /// Human-readable name derived from the publisher identifier + /// +#if NET48 + public static string PublisherName(Models.Xbox.XeMID xemid) => GetPublisher(xemid.PublisherIdentifier); +#else + public static string? PublisherName(Models.Xbox.XeMID xemid) => GetPublisher(xemid.PublisherIdentifier); +#endif + + /// + /// Internally represented region + /// +#if NET48 + public static string InternalRegion(Models.Xbox.XeMID xemid) => GetRegion(xemid.RegionIdentifier); +#else + public static string? InternalRegion(Models.Xbox.XeMID xemid) => GetRegion(xemid.RegionIdentifier); +#endif + + /// + /// Human-readable subtype derived from the media identifier + /// +#if NET48 + public static string MediaSubtype(Models.Xbox.XeMID xemid) => GetMediaSubtype(xemid.MediaSubtypeIdentifier); +#else + public static string? MediaSubtype(Models.Xbox.XeMID xemid) => GetMediaSubtype(xemid.MediaSubtypeIdentifier); +#endif + + /// + /// 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 +#if NET48 + private static string GetMediaSubtype(char mediaTypeIdentifier) +#else + private static string? GetMediaSubtype(char mediaTypeIdentifier) +#endif + { + 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 + /// +#if NET48 + private static string GetPublisher(string publisherIdentifier) +#else + private static string? GetPublisher(string? publisherIdentifier) +#endif + { + 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 +#if NET48 + private static string GetRegion(char region) +#else + private static string? GetRegion(char region) +#endif + { + switch (region) + { + case 'W': return "World"; + case 'A': return "USA"; + case 'J': return "Japan / Asia"; + case 'E': return "Europe"; + case 'K': return "USA / Japan"; + case 'L': return "USA / Europe"; + case 'H': return "Japan / Europe"; + default: return null; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Files/XeMID.Serializer.cs b/Files/XeMID.Serializer.cs new file mode 100644 index 00000000..068038dc --- /dev/null +++ b/Files/XeMID.Serializer.cs @@ -0,0 +1,14 @@ +using System; + +namespace SabreTools.Serialization.Files +{ + public partial class XeMID : IFileSerializer + { + /// +#if NET48 + public bool Serialize(Models.Xbox.XeMID obj, string path) => throw new NotImplementedException(); +#else + public bool Serialize(Models.Xbox.XeMID? obj, string? path) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file