();
if (info.DumpersAndStatus.Dumpers.Length > 0)
{
foreach (string dumper in info.DumpersAndStatus.Dumpers)
tempDumpers.Add(dumper);
}
foreach (Match submatch in matches)
tempDumpers.Add(WebUtility.HtmlDecode(submatch.Groups[1].Value));
info.DumpersAndStatus.Dumpers = tempDumpers.ToArray();
}
// TODO: Unify handling of fields that can include site codes (Comments/Contents)
// Comments
match = Constants.CommentsRegex.Match(discData);
if (match.Success)
{
// Process the old comments block
string oldComments = info.CommonDiscInfo.Comments
+ (string.IsNullOrEmpty(info.CommonDiscInfo.Comments) ? string.Empty : "\n")
+ WebUtility.HtmlDecode(match.Groups[1].Value)
.Replace("\r\n", "\n")
.Replace("
\n", "\n")
.Replace("
", string.Empty)
.Replace("", string.Empty)
.Replace("[+]", string.Empty)
.ReplaceHtmlWithSiteCodes();
oldComments = Regex.Replace(oldComments, @"", string.Empty);
// Create state variables
bool addToLast = false;
SiteCode? lastSiteCode = null;
string newComments = string.Empty;
// Process the comments block line-by-line
string[] commentsSeparated = oldComments.Split('\n');
for (int i = 0; i < commentsSeparated.Length; i++)
{
string commentLine = commentsSeparated[i].Trim();
// If we have an empty line, we want to treat this as intentional
if (string.IsNullOrWhiteSpace(commentLine))
{
addToLast = false;
lastSiteCode = null;
newComments += $"{commentLine}\n";
continue;
}
// Otherwise, we need to find what tag is in use
bool foundTag = false;
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
{
// If we have a null site code, just skip
if (siteCode == null)
continue;
// If the line doesn't contain this tag, just skip
if (!commentLine.Contains(siteCode.ShortName()))
continue;
// Mark as having found a tag
foundTag = true;
// Cache the current site code
lastSiteCode = siteCode;
// A subset of tags can be multiline
addToLast = IsMultiLine(siteCode);
// Skip certain site codes because of data issues
switch (siteCode)
{
// Multiple
case SiteCode.InternalSerialName:
case SiteCode.Multisession:
case SiteCode.VolumeLabel:
continue;
// Audio CD
case SiteCode.RingNonZeroDataStart:
case SiteCode.UniversalHash:
continue;
// Microsoft Xbox and Xbox 360
case SiteCode.DMIHash:
case SiteCode.PFIHash:
case SiteCode.SSHash:
case SiteCode.SSVersion:
case SiteCode.XMID:
case SiteCode.XeMID:
continue;
// Microsoft Xbox One and Series X/S
case SiteCode.Filename:
continue;
// Nintendo Gamecube
case SiteCode.InternalName:
continue;
}
// If we don't already have this site code, add it to the dictionary
if (!info.CommonDiscInfo.CommentsSpecialFields.ContainsKey(siteCode.Value))
info.CommonDiscInfo.CommentsSpecialFields[siteCode.Value] = $"(VERIFY THIS) {commentLine.Replace(siteCode.ShortName(), string.Empty).Trim()}";
// Otherwise, append the value to the existing key
else
info.CommonDiscInfo.CommentsSpecialFields[siteCode.Value] += $", {commentLine.Replace(siteCode.ShortName(), string.Empty).Trim()}";
break;
}
// If we didn't find a known tag, just add the line, just in case
if (!foundTag)
{
if (addToLast && lastSiteCode != null)
{
if (!string.IsNullOrWhiteSpace(info.CommonDiscInfo.CommentsSpecialFields[lastSiteCode.Value]))
info.CommonDiscInfo.CommentsSpecialFields[lastSiteCode.Value] += "\n";
info.CommonDiscInfo.CommentsSpecialFields[lastSiteCode.Value] += commentLine;
}
else
{
newComments += $"{commentLine}\n";
}
}
}
// Set the new comments field
info.CommonDiscInfo.Comments = newComments;
}
// Contents
match = Constants.ContentsRegex.Match(discData);
if (match.Success)
{
// Process the old contents block
string oldContents = info.CommonDiscInfo.Contents
+ (string.IsNullOrEmpty(info.CommonDiscInfo.Contents) ? string.Empty : "\n")
+ WebUtility.HtmlDecode(match.Groups[1].Value)
.Replace("\r\n", "\n")
.Replace("
\n", "\n")
.Replace("
", string.Empty)
.Replace("
", string.Empty)
.Replace("[+]", string.Empty)
.ReplaceHtmlWithSiteCodes();
oldContents = Regex.Replace(oldContents, @"", string.Empty);
// Create state variables
bool addToLast = false;
SiteCode? lastSiteCode = null;
string newContents = string.Empty;
// Process the contents block line-by-line
string[] contentsSeparated = oldContents.Split('\n');
for (int i = 0; i < contentsSeparated.Length; i++)
{
string contentLine = contentsSeparated[i].Trim();
// If we have an empty line, we want to treat this as intentional
if (string.IsNullOrWhiteSpace(contentLine))
{
addToLast = false;
lastSiteCode = null;
newContents += $"{contentLine}\n";
continue;
}
// Otherwise, we need to find what tag is in use
bool foundTag = false;
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
{
// If we have a null site code, just skip
if (siteCode == null)
continue;
// If the line doesn't contain this tag, just skip
if (!contentLine.Contains(siteCode.ShortName()))
continue;
// Cache the current site code
lastSiteCode = siteCode;
// If we don't already have this site code, add it to the dictionary
if (!info.CommonDiscInfo.ContentsSpecialFields.ContainsKey(siteCode.Value))
info.CommonDiscInfo.ContentsSpecialFields[siteCode.Value] = $"(VERIFY THIS) {contentLine.Replace(siteCode.ShortName(), string.Empty).Trim()}";
// A subset of tags can be multiline
addToLast = IsMultiLine(siteCode);
// Mark as having found a tag
foundTag = true;
break;
}
// If we didn't find a known tag, just add the line, just in case
if (!foundTag)
{
if (addToLast && lastSiteCode != null)
{
if (!string.IsNullOrWhiteSpace(info.CommonDiscInfo.ContentsSpecialFields[lastSiteCode.Value]))
info.CommonDiscInfo.ContentsSpecialFields[lastSiteCode.Value] += "\n";
info.CommonDiscInfo.ContentsSpecialFields[lastSiteCode.Value] += contentLine;
}
else
{
newContents += $"{contentLine}\n";
}
}
}
// Set the new contents field
info.CommonDiscInfo.Contents = newContents;
}
// Added
match = Constants.AddedRegex.Match(discData);
if (match.Success)
{
if (DateTime.TryParse(match.Groups[1].Value, out DateTime added))
info.Added = added;
else
info.Added = null;
}
// Last Modified
match = Constants.LastModifiedRegex.Match(discData);
if (match.Success)
{
if (DateTime.TryParse(match.Groups[1].Value, out DateTime lastModified))
info.LastModified = lastModified;
else
info.LastModified = null;
}
return true;
}
///
/// Fill in a SubmissionInfo object from Redump, if possible
///
///
Options object representing user-defined options
///
Existing SubmissionInfo object to fill
///
Optional result progress callback
#if NET48 || NETSTANDARD2_1
private static bool FillFromRedump(Core.Data.Options options, SubmissionInfo info, IProgress
resultProgress = null)
#else
private async static Task FillFromRedump(Core.Data.Options options, SubmissionInfo info, IProgress resultProgress = null)
#endif
{
// Set the current dumper based on username
info.DumpersAndStatus.Dumpers = new string[] { options.RedumpUsername };
info.PartiallyMatchedIDs = new List();
#if NET48 || NETSTANDARD2_1
using (RedumpWebClient wc = new RedumpWebClient())
#else
using (RedumpHttpClient wc = new RedumpHttpClient())
#endif
{
// Login to Redump
#if NET48 || NETSTANDARD2_1
bool? loggedIn = wc.Login(options.RedumpUsername, options.RedumpPassword);
#else
bool? loggedIn = await wc.Login(options.RedumpUsername, options.RedumpPassword);
#endif
if (loggedIn == null)
{
resultProgress?.Report(Result.Failure("There was an unknown error connecting to Redump"));
return false;
}
else if (loggedIn == false)
{
// Don't log the as a failure or error
return false;
}
// Setup the full-track checks
bool allFound = true;
List fullyMatchedIDs = null;
// Loop through all of the hashdata to find matching IDs
resultProgress?.Report(Result.Success("Finding disc matches on Redump..."));
string[] splitData = info.TracksAndWriteOffsets.ClrMameProData.TrimEnd('\n').Split('\n');
int trackCount = splitData.Length;
foreach (string hashData in splitData)
{
// Catch any errant blank lines
if (string.IsNullOrWhiteSpace(hashData))
{
trackCount--;
resultProgress?.Report(Result.Success("Blank line found, skipping!"));
continue;
}
// If the line ends in a known extra track names, skip them for checking
if (hashData.Contains("(Track 0).bin")
|| hashData.Contains("(Track 00).bin")
|| hashData.Contains("(Track A).bin")
|| hashData.Contains("(Track AA).bin"))
{
trackCount--;
resultProgress?.Report(Result.Success("Extra track found, skipping!"));
continue;
}
#if NET48 || NETSTANDARD2_1
(bool singleFound, List foundIds) = ValidateSingleTrack(wc, info, hashData, resultProgress);
#else
(bool singleFound, List foundIds) = await ValidateSingleTrack(wc, info, hashData, resultProgress);
#endif
// Ensure that all tracks are found
allFound &= singleFound;
// If we found a track, only keep track of distinct found tracks
if (singleFound && foundIds != null)
{
if (fullyMatchedIDs == null)
fullyMatchedIDs = foundIds;
else
fullyMatchedIDs = fullyMatchedIDs.Intersect(foundIds).ToList();
}
// If no tracks were found, remove all fully matched IDs found so far
else
{
fullyMatchedIDs = new List();
}
}
// If we don't have any matches but we have a universal hash
if (!info.PartiallyMatchedIDs.Any() && info.CommonDiscInfo.CommentsSpecialFields.ContainsKey(SiteCode.UniversalHash))
{
#if NET48 || NETSTANDARD2_1
(bool singleFound, List foundIds) = ValidateUniversalHash(wc, info, resultProgress);
#else
(bool singleFound, List foundIds) = await ValidateUniversalHash(wc, info, resultProgress);
#endif
// Ensure that the hash is found
allFound = singleFound;
// If we found a track, only keep track of distinct found tracks
if (singleFound && foundIds != null)
{
fullyMatchedIDs = foundIds;
}
// If no tracks were found, remove all fully matched IDs found so far
else
{
fullyMatchedIDs = new List();
}
}
// Make sure we only have unique IDs
info.PartiallyMatchedIDs = info.PartiallyMatchedIDs
.Distinct()
.OrderBy(id => id)
.ToList();
resultProgress?.Report(Result.Success("Match finding complete! " + (fullyMatchedIDs.Count > 0
? "Fully Matched IDs: " + string.Join(",", fullyMatchedIDs)
: "No matches found")));
// Exit early if one failed or there are no matched IDs
if (!allFound || fullyMatchedIDs.Count == 0)
return false;
// Find the first matched ID where the track count matches, we can grab a bunch of info from it
int totalMatchedIDsCount = fullyMatchedIDs.Count;
for (int i = 0; i < totalMatchedIDsCount; i++)
{
// Skip if the track count doesn't match
#if NET48 || NETSTANDARD2_1
if (!ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount))
continue;
#else
if (!await ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount))
continue;
#endif
// Fill in the fields from the existing ID
resultProgress?.Report(Result.Success($"Filling fields from existing ID {fullyMatchedIDs[i]}..."));
#if NET48 || NETSTANDARD2_1
FillFromId(wc, info, fullyMatchedIDs[i]);
#else
_ = await FillFromId(wc, info, fullyMatchedIDs[i]);
#endif
resultProgress?.Report(Result.Success("Information filling complete!"));
// Set the fully matched ID to the current
info.FullyMatchedID = fullyMatchedIDs[i];
break;
}
// Clear out fully matched IDs from the partial list
if (info.FullyMatchedID.HasValue)
{
if (info.PartiallyMatchedIDs.Count() == 1)
info.PartiallyMatchedIDs = null;
else
info.PartiallyMatchedIDs.Remove(info.FullyMatchedID.Value);
}
}
return true;
}
///
/// Process a text block and replace with internal identifiers
///
/// Text block to process
/// Processed text block, if possible
private static string ReplaceHtmlWithSiteCodes(this string text)
{
if (string.IsNullOrWhiteSpace(text))
return text;
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
{
text = text.Replace(siteCode.LongName(), siteCode.ShortName());
}
// For some outdated tags, we need to use alternate names
text = text.Replace("Demos:", ((SiteCode?)SiteCode.PlayableDemos).ShortName());
text = text.Replace("DMI:", ((SiteCode?)SiteCode.DMIHash).ShortName());
text = text.Replace("LucasArts ID:", ((SiteCode?)SiteCode.LucasArtsID).ShortName());
text = text.Replace("PFI:", ((SiteCode?)SiteCode.PFIHash).ShortName());
text = text.Replace("SS:", ((SiteCode?)SiteCode.SSHash).ShortName());
text = text.Replace("SSv1:", ((SiteCode?)SiteCode.SSHash).ShortName());
text = text.Replace("SSv1:", ((SiteCode?)SiteCode.SSHash).ShortName());
text = text.Replace("SSv2:", ((SiteCode?)SiteCode.SSHash).ShortName());
text = text.Replace("SSv2:", ((SiteCode?)SiteCode.SSHash).ShortName());
text = text.Replace("SS version:", ((SiteCode?)SiteCode.SSVersion).ShortName());
text = text.Replace("Universal Hash (SHA-1):", ((SiteCode?)SiteCode.UniversalHash).ShortName());
text = text.Replace("XeMID:", ((SiteCode?)SiteCode.XeMID).ShortName());
text = text.Replace("XMID:", ((SiteCode?)SiteCode.XMID).ShortName());
return text;
}
///
/// List the disc IDs associated with a given quicksearch query
///
/// RedumpWebClient for making the connection
/// Query string to attempt to search for
/// True to filter forward slashes, false otherwise
/// All disc IDs for the given query, null on error
#if NET48 || NETSTANDARD2_1
private static List ListSearchResults(RedumpWebClient wc, string query, bool filterForwardSlashes = true)
#else
private async static Task> ListSearchResults(RedumpHttpClient wc, string query, bool filterForwardSlashes = true)
#endif
{
List ids = new List();
// Strip quotes
query = query.Trim('"', '\'');
// Special characters become dashes
query = query.Replace(' ', '-');
if (filterForwardSlashes)
query = query.Replace('/', '-');
query = query.Replace('\\', '/');
// Lowercase is defined per language
query = query.ToLowerInvariant();
// Keep getting quicksearch pages until there are none left
try
{
int pageNumber = 1;
while (true)
{
#if NET48 || NETSTANDARD2_1
List pageIds = wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++));
#else
List pageIds = await wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++));
#endif
ids.AddRange(pageIds);
if (pageIds.Count <= 1)
break;
}
}
catch (Exception ex)
{
Console.WriteLine($"An exception occurred while trying to log in: {ex}");
return null;
}
return ids;
}
///
/// Validate a single track against Redump, if possible
///
/// RedumpWebClient for making the connection
/// Existing SubmissionInfo object to fill
/// DAT-formatted hash data to parse out
/// Optional result progress callback
/// True if the track was found, false otherwise; List of found values, if possible
#if NET48 || NETSTANDARD2_1
private static (bool, List) ValidateSingleTrack(RedumpWebClient wc, SubmissionInfo info, string hashData, IProgress resultProgress = null)
#else
private async static Task<(bool, List)> ValidateSingleTrack(RedumpHttpClient wc, SubmissionInfo info, string hashData, IProgress resultProgress = null)
#endif
{
// If the line isn't parseable, we can't validate
if (!GetISOHashValues(hashData, out long _, out string _, out string _, out string sha1))
{
resultProgress?.Report(Result.Failure("Line could not be parsed for hash data"));
return (false, null);
}
// Get all matching IDs for the track
#if NET48 || NETSTANDARD2_1
List newIds = ListSearchResults(wc, sha1);
#else
List newIds = await ListSearchResults(wc, sha1);
#endif
// If we got null back, there was an error
if (newIds == null)
{
resultProgress?.Report(Result.Failure("There was an unknown error retrieving information from Redump"));
return (false, null);
}
// If no IDs match any track, just return
if (!newIds.Any())
return (false, null);
// Join the list of found IDs to the existing list, if possible
if (info.PartiallyMatchedIDs.Any())
info.PartiallyMatchedIDs.AddRange(newIds);
else
info.PartiallyMatchedIDs = newIds;
return (true, newIds);
}
///
/// Validate a universal hash against Redump, if possible
///
/// RedumpWebClient for making the connection
/// Existing SubmissionInfo object to fill
/// Optional result progress callback
/// True if the track was found, false otherwise; List of found values, if possible
#if NET48 || NETSTANDARD2_1
private static (bool, List) ValidateUniversalHash(RedumpWebClient wc, SubmissionInfo info, IProgress resultProgress = null)
#else
private async static Task<(bool, List)> ValidateUniversalHash(RedumpHttpClient wc, SubmissionInfo info, IProgress resultProgress = null)
#endif
{
// If we don't have a universal hash
string universalHash = info.CommonDiscInfo.CommentsSpecialFields[SiteCode.UniversalHash];
if (string.IsNullOrEmpty(universalHash))
{
resultProgress?.Report(Result.Failure("Universal hash was missing"));
return (false, null);
}
// Format the universal hash for finding within the comments
universalHash = $"{universalHash.Substring(0, universalHash.Length - 1)}/comments/only";
// Get all matching IDs for the hash
#if NET48 || NETSTANDARD2_1
List newIds = ListSearchResults(wc, universalHash, filterForwardSlashes: false);
#else
List newIds = await ListSearchResults(wc, universalHash, filterForwardSlashes: false);
#endif
// If we got null back, there was an error
if (newIds == null)
{
resultProgress?.Report(Result.Failure("There was an unknown error retrieving information from Redump"));
return (false, null);
}
// If no IDs match any track, just return
if (!newIds.Any())
return (false, null);
// Join the list of found IDs to the existing list, if possible
if (info.PartiallyMatchedIDs.Any())
info.PartiallyMatchedIDs.AddRange(newIds);
else
info.PartiallyMatchedIDs = newIds;
return (true, newIds);
}
///
/// Validate that the current track count and remote track count match
///
/// RedumpWebClient for making the connection
/// Redump disc ID to retrieve
/// Local count of tracks for the current disc
/// True if the track count matches, false otherwise
#if NET48 || NETSTANDARD2_1
private static bool ValidateTrackCount(RedumpWebClient wc, int id, int localCount)
#else
private async static Task ValidateTrackCount(RedumpHttpClient wc, int id, int localCount)
#endif
{
// If we can't pull the remote data, we can't match
#if NET48 || NETSTANDARD2_1
string discData = wc.DownloadSingleSiteID(id);
#else
string discData = await wc.DownloadSingleSiteID(id);
#endif
if (string.IsNullOrEmpty(discData))
return false;
// Discs with only 1 track don't have a track count listed
var match = Constants.TrackCountRegex.Match(discData);
if (!match.Success && localCount == 1)
return true;
else if (!match.Success)
return false;
// If the count isn't parseable, we're not taking chances
if (!Int32.TryParse(match.Groups[1].Value, out int remoteCount))
return false;
// Finally check to see if the counts match
return localCount == remoteCount;
}
#endregion
#region Helpers
///
/// Format a single site tag to string
///
/// KeyValuePair representing the site tag and value
/// String-formatted tag and value
private static string FormatSiteTag(KeyValuePair kvp)
{
bool isMultiLine = IsMultiLine(kvp.Key);
string line = $"{kvp.Key.ShortName()}{(isMultiLine ? "\n" : " ")}";
// Special case for boolean fields
if (IsBoolean(kvp.Key))
{
if (kvp.Value != true.ToString())
return string.Empty;
return line.Trim();
}
return $"{line}{kvp.Value}{(isMultiLine ? "\n" : string.Empty)}";
}
///
/// Check if a site code is boolean or not
///
/// SiteCode to check
/// True if the code field is a flag with no value, false otherwise
/// TODO: This should move to Extensions at some point
private static bool IsBoolean(SiteCode? siteCode)
{
switch (siteCode)
{
case SiteCode.PostgapType:
case SiteCode.VCD:
return true;
default:
return false;
}
}
///
/// Check if a site code is multi-line or not
///
/// SiteCode to check
/// True if the code field is multiline by default, false otherwise
/// TODO: This should move to Extensions at some point
private static bool IsMultiLine(SiteCode? siteCode)
{
switch (siteCode)
{
case SiteCode.Extras:
case SiteCode.Filename:
case SiteCode.Games:
case SiteCode.GameFootage:
case SiteCode.Multisession:
case SiteCode.NetYarozeGames:
case SiteCode.Patches:
case SiteCode.PlayableDemos:
case SiteCode.RollingDemos:
case SiteCode.Savegames:
case SiteCode.TechDemos:
case SiteCode.Videos:
return true;
default:
return false;
}
}
///
/// Order comment code tags according to Redump requirements
///
/// Ordered list of KeyValuePairs representing the tags and values
#if NET48
private static List> OrderCommentTags(Dictionary tags)
#else
private static List> OrderCommentTags(Dictionary tags)
#endif
{
var sorted = new List>();
// If the input is invalid, just return an empty set
if (tags == null || tags.Count == 0)
return sorted;
// Identifying Info
if (tags.ContainsKey(SiteCode.AlternativeTitle))
sorted.Add(new KeyValuePair(SiteCode.AlternativeTitle, tags[SiteCode.AlternativeTitle]));
if (tags.ContainsKey(SiteCode.AlternativeForeignTitle))
sorted.Add(new KeyValuePair(SiteCode.AlternativeForeignTitle, tags[SiteCode.AlternativeForeignTitle]));
if (tags.ContainsKey(SiteCode.InternalName))
sorted.Add(new KeyValuePair(SiteCode.InternalName, tags[SiteCode.InternalName]));
if (tags.ContainsKey(SiteCode.InternalSerialName))
sorted.Add(new KeyValuePair(SiteCode.InternalSerialName, tags[SiteCode.InternalSerialName]));
if (tags.ContainsKey(SiteCode.VolumeLabel))
sorted.Add(new KeyValuePair(SiteCode.VolumeLabel, tags[SiteCode.VolumeLabel]));
if (tags.ContainsKey(SiteCode.Multisession))
sorted.Add(new KeyValuePair(SiteCode.Multisession, tags[SiteCode.Multisession]));
if (tags.ContainsKey(SiteCode.UniversalHash))
sorted.Add(new KeyValuePair(SiteCode.UniversalHash, tags[SiteCode.UniversalHash]));
if (tags.ContainsKey(SiteCode.RingNonZeroDataStart))
sorted.Add(new KeyValuePair(SiteCode.RingNonZeroDataStart, tags[SiteCode.RingNonZeroDataStart]));
if (tags.ContainsKey(SiteCode.XMID))
sorted.Add(new KeyValuePair(SiteCode.XMID, tags[SiteCode.XMID]));
if (tags.ContainsKey(SiteCode.XeMID))
sorted.Add(new KeyValuePair(SiteCode.XeMID, tags[SiteCode.XeMID]));
if (tags.ContainsKey(SiteCode.DMIHash))
sorted.Add(new KeyValuePair(SiteCode.DMIHash, tags[SiteCode.DMIHash]));
if (tags.ContainsKey(SiteCode.PFIHash))
sorted.Add(new KeyValuePair(SiteCode.PFIHash, tags[SiteCode.PFIHash]));
if (tags.ContainsKey(SiteCode.SSHash))
sorted.Add(new KeyValuePair(SiteCode.SSHash, tags[SiteCode.SSHash]));
if (tags.ContainsKey(SiteCode.SSVersion))
sorted.Add(new KeyValuePair(SiteCode.SSVersion, tags[SiteCode.SSVersion]));
if (tags.ContainsKey(SiteCode.Filename))
sorted.Add(new KeyValuePair(SiteCode.Filename, tags[SiteCode.Filename]));
if (tags.ContainsKey(SiteCode.BBFCRegistrationNumber))
sorted.Add(new KeyValuePair(SiteCode.BBFCRegistrationNumber, tags[SiteCode.BBFCRegistrationNumber]));
if (tags.ContainsKey(SiteCode.CDProjektID))
sorted.Add(new KeyValuePair(SiteCode.CDProjektID, tags[SiteCode.CDProjektID]));
if (tags.ContainsKey(SiteCode.DiscHologramID))
sorted.Add(new KeyValuePair(SiteCode.DiscHologramID, tags[SiteCode.DiscHologramID]));
if (tags.ContainsKey(SiteCode.DNASDiscID))
sorted.Add(new KeyValuePair(SiteCode.DNASDiscID, tags[SiteCode.DNASDiscID]));
if (tags.ContainsKey(SiteCode.ISBN))
sorted.Add(new KeyValuePair(SiteCode.ISBN, tags[SiteCode.ISBN]));
if (tags.ContainsKey(SiteCode.ISSN))
sorted.Add(new KeyValuePair(SiteCode.ISSN, tags[SiteCode.ISSN]));
if (tags.ContainsKey(SiteCode.PPN))
sorted.Add(new KeyValuePair(SiteCode.PPN, tags[SiteCode.PPN]));
if (tags.ContainsKey(SiteCode.VFCCode))
sorted.Add(new KeyValuePair(SiteCode.VFCCode, tags[SiteCode.VFCCode]));
if (tags.ContainsKey(SiteCode.Genre))
sorted.Add(new KeyValuePair(SiteCode.Genre, tags[SiteCode.Genre]));
if (tags.ContainsKey(SiteCode.Series))
sorted.Add(new KeyValuePair(SiteCode.Series, tags[SiteCode.Series]));
if (tags.ContainsKey(SiteCode.PostgapType))
sorted.Add(new KeyValuePair(SiteCode.PostgapType, tags[SiteCode.PostgapType]));
if (tags.ContainsKey(SiteCode.VCD))
sorted.Add(new KeyValuePair(SiteCode.VCD, tags[SiteCode.VCD]));
// Publisher / Company IDs
if (tags.ContainsKey(SiteCode.AcclaimID))
sorted.Add(new KeyValuePair(SiteCode.AcclaimID, tags[SiteCode.AcclaimID]));
if (tags.ContainsKey(SiteCode.ActivisionID))
sorted.Add(new KeyValuePair(SiteCode.ActivisionID, tags[SiteCode.ActivisionID]));
if (tags.ContainsKey(SiteCode.BandaiID))
sorted.Add(new KeyValuePair(SiteCode.BandaiID, tags[SiteCode.BandaiID]));
if (tags.ContainsKey(SiteCode.ElectronicArtsID))
sorted.Add(new KeyValuePair(SiteCode.ElectronicArtsID, tags[SiteCode.ElectronicArtsID]));
if (tags.ContainsKey(SiteCode.FoxInteractiveID))
sorted.Add(new KeyValuePair(SiteCode.FoxInteractiveID, tags[SiteCode.FoxInteractiveID]));
if (tags.ContainsKey(SiteCode.GTInteractiveID))
sorted.Add(new KeyValuePair(SiteCode.GTInteractiveID, tags[SiteCode.GTInteractiveID]));
if (tags.ContainsKey(SiteCode.JASRACID))
sorted.Add(new KeyValuePair(SiteCode.JASRACID, tags[SiteCode.JASRACID]));
if (tags.ContainsKey(SiteCode.KingRecordsID))
sorted.Add(new KeyValuePair(SiteCode.KingRecordsID, tags[SiteCode.KingRecordsID]));
if (tags.ContainsKey(SiteCode.KoeiID))
sorted.Add(new KeyValuePair(SiteCode.KoeiID, tags[SiteCode.KoeiID]));
if (tags.ContainsKey(SiteCode.KonamiID))
sorted.Add(new KeyValuePair(SiteCode.KonamiID, tags[SiteCode.KonamiID]));
if (tags.ContainsKey(SiteCode.LucasArtsID))
sorted.Add(new KeyValuePair(SiteCode.LucasArtsID, tags[SiteCode.LucasArtsID]));
if (tags.ContainsKey(SiteCode.MicrosoftID))
sorted.Add(new KeyValuePair(SiteCode.MicrosoftID, tags[SiteCode.MicrosoftID]));
if (tags.ContainsKey(SiteCode.NaganoID))
sorted.Add(new KeyValuePair(SiteCode.NaganoID, tags[SiteCode.NaganoID]));
if (tags.ContainsKey(SiteCode.NamcoID))
sorted.Add(new KeyValuePair(SiteCode.NamcoID, tags[SiteCode.NamcoID]));
if (tags.ContainsKey(SiteCode.NipponIchiSoftwareID))
sorted.Add(new KeyValuePair(SiteCode.NipponIchiSoftwareID, tags[SiteCode.NipponIchiSoftwareID]));
if (tags.ContainsKey(SiteCode.OriginID))
sorted.Add(new KeyValuePair(SiteCode.OriginID, tags[SiteCode.OriginID]));
if (tags.ContainsKey(SiteCode.PonyCanyonID))
sorted.Add(new KeyValuePair(SiteCode.PonyCanyonID, tags[SiteCode.PonyCanyonID]));
if (tags.ContainsKey(SiteCode.SegaID))
sorted.Add(new KeyValuePair(SiteCode.SegaID, tags[SiteCode.SegaID]));
if (tags.ContainsKey(SiteCode.SelenID))
sorted.Add(new KeyValuePair(SiteCode.SelenID, tags[SiteCode.SelenID]));
if (tags.ContainsKey(SiteCode.SierraID))
sorted.Add(new KeyValuePair(SiteCode.SierraID, tags[SiteCode.SierraID]));
if (tags.ContainsKey(SiteCode.TaitoID))
sorted.Add(new KeyValuePair(SiteCode.TaitoID, tags[SiteCode.TaitoID]));
if (tags.ContainsKey(SiteCode.UbisoftID))
sorted.Add(new KeyValuePair(SiteCode.UbisoftID, tags[SiteCode.UbisoftID]));
if (tags.ContainsKey(SiteCode.ValveID))
sorted.Add(new KeyValuePair(SiteCode.ValveID, tags[SiteCode.ValveID]));
return sorted;
}
///
/// Order content code tags according to Redump requirements
///
/// Ordered list of KeyValuePairs representing the tags and values
#if NET48
private static List> OrderContentTags(Dictionary tags)
#else
private static List> OrderContentTags(Dictionary tags)
#endif
{
var sorted = new List>();
// If the input is invalid, just return an empty set
if (tags == null || tags.Count == 0)
return sorted;
// Games
if (tags.ContainsKey(SiteCode.Games))
sorted.Add(new KeyValuePair(SiteCode.Games, tags[SiteCode.Games]));
if (tags.ContainsKey(SiteCode.NetYarozeGames))
sorted.Add(new KeyValuePair(SiteCode.NetYarozeGames, tags[SiteCode.NetYarozeGames]));
// Demos
if (tags.ContainsKey(SiteCode.PlayableDemos))
sorted.Add(new KeyValuePair(SiteCode.PlayableDemos, tags[SiteCode.PlayableDemos]));
if (tags.ContainsKey(SiteCode.RollingDemos))
sorted.Add(new KeyValuePair(SiteCode.RollingDemos, tags[SiteCode.RollingDemos]));
if (tags.ContainsKey(SiteCode.TechDemos))
sorted.Add(new KeyValuePair(SiteCode.TechDemos, tags[SiteCode.TechDemos]));
// Video
if (tags.ContainsKey(SiteCode.GameFootage))
sorted.Add(new KeyValuePair(SiteCode.GameFootage, tags[SiteCode.GameFootage]));
if (tags.ContainsKey(SiteCode.Videos))
sorted.Add(new KeyValuePair(SiteCode.Videos, tags[SiteCode.Videos]));
// Miscellaneous
if (tags.ContainsKey(SiteCode.Patches))
sorted.Add(new KeyValuePair(SiteCode.Patches, tags[SiteCode.Patches]));
if (tags.ContainsKey(SiteCode.Savegames))
sorted.Add(new KeyValuePair(SiteCode.Savegames, tags[SiteCode.Savegames]));
if (tags.ContainsKey(SiteCode.Extras))
sorted.Add(new KeyValuePair(SiteCode.Extras, tags[SiteCode.Extras]));
return sorted;
}
#endregion
}
}