", "");
}
// 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;
}
}
///
/// Fill in a SubmissionInfo object from Redump, if possible
///
///
Options object representing user-defined options
///
Existing SubmissionInfo object to fill
///
Optional result progress callback
private static void FillFromRedump(Options options, SubmissionInfo info, IProgress
resultProgress = null)
{
// Set the current dumper based on username
info.DumpersAndStatus.Dumpers = new string[] { options.RedumpUsername };
info.MatchedIDs = new List();
using (RedumpWebClient wc = new RedumpWebClient())
{
// Login to Redump
bool? loggedIn = wc.Login(options.RedumpUsername, options.RedumpPassword);
if (loggedIn == null)
{
resultProgress?.Report(Result.Failure("There was an unknown error connecting to Redump"));
return;
}
else if (loggedIn == false)
{
// Don't log the as a failure or error
return;
}
// Loop through all of the hashdata to find matching IDs
resultProgress?.Report(Result.Success("Finding disc matches on Redump..."));
string[] splitData = info.TracksAndWriteOffsets.ClrMameProData.Split('\n');
foreach (string hashData in splitData)
{
ValidateSingleTrack(wc, info, hashData, resultProgress);
}
resultProgress?.Report(Result.Success("Match finding complete! " + (info.MatchedIDs.Count > 0
? "Matched IDs: " + string.Join(",", info.MatchedIDs)
: "No matches found")));
// Exit early if there are no matched IDs
if (info.MatchedIDs.Count == 0)
return;
// Find the first matched ID where the track count matches, we can grab a bunch of info from it
int totalMatchedIDsCount = info.MatchedIDs.Count;
for (int i = 0; i < totalMatchedIDsCount; i++)
{
// Skip if the track count doesn't match
if (!ValidateTrackCount(wc, info.MatchedIDs[i], splitData.Length))
continue;
// Fill in the fields from the existing ID
resultProgress?.Report(Result.Success($"Filling fields from existing ID {info.MatchedIDs[i]}..."));
FillFromId(wc, info, info.MatchedIDs[0]);
resultProgress?.Report(Result.Success("Information filling complete!"));
// Set the matched IDs to just the current
info.MatchedIDs = new List { info.MatchedIDs[i] };
break;
}
}
}
///
/// Process a comment block and replace with internal identifiers
///
/// Comments block to process
/// Processed comments block, if possible
private static string ReplaceCommentFieldsWithTags(this string comments)
{
if (string.IsNullOrWhiteSpace(comments))
return comments;
return comments
.Replace(Constants.AcclaimIDCommentString, Constants.AcclaimIDCommentField)
.Replace(Constants.ActivisionIDCommentString, Constants.ActivisionIDCommentField)
.Replace(Constants.AlternativeTitleCommentString, Constants.AlternativeTitleCommentField)
.Replace(Constants.AlternativeForeignTitleCommentString, Constants.AlternativeForeignTitleCommentField)
.Replace(Constants.BandaiIDCommentString, Constants.BandaiIDCommentField)
.Replace(Constants.BBFCRegistrationNumberCommentString, Constants.BBFCRegistrationNumberCommentField)
.Replace(Constants.DNASDiscIDCommentString, Constants.DNASDiscIDCommentField)
.Replace(Constants.ElectronicArtsIDCommentString, Constants.ElectronicArtsIDCommentField)
.Replace(Constants.ExtrasCommentString, Constants.ExtrasCommentField)
.Replace(Constants.FoxInteractiveIDCommentString, Constants.FoxInteractiveIDCommentField)
.Replace(Constants.GameFootageCommentString, Constants.GameFootageCommentField)
.Replace(Constants.GenreCommentString, Constants.GenreCommentField)
.Replace(Constants.GTInteractiveIDCommentString, Constants.GTInteractiveIDCommentField)
.Replace(Constants.InternalSerialNameCommentString, Constants.InternalSerialNameCommentField)
.Replace(Constants.ISBNCommentString, Constants.ISBNCommentField)
.Replace(Constants.ISSNCommentString, Constants.ISSNCommentField)
.Replace(Constants.JASRACIDCommentString, Constants.JASRACIDCommentField)
.Replace(Constants.KingRecordsIDCommentString, Constants.KingRecordsIDCommentField)
.Replace(Constants.KoeiIDCommentString, Constants.KoeiIDCommentField)
.Replace(Constants.KonamiIDCommentString, Constants.KonamiIDCommentField)
.Replace(Constants.LucasArtsIDCommentString, Constants.LucasArtsIDCommentField)
.Replace(Constants.NaganoIDCommentString, Constants.NaganoIDCommentField)
.Replace(Constants.NamcoIDCommentString, Constants.NamcoIDCommentField)
.Replace(Constants.NetYarozeGamesCommentSring, Constants.NetYarozeGamesCommentField)
.Replace(Constants.NipponIchiSoftwareIDCommentString, Constants.NipponIchiSoftwareIDCommentField)
.Replace(Constants.OriginIDCommentString, Constants.OriginIDCommentField)
.Replace(Constants.PatchesCommentString, Constants.PatchesCommentField)
.Replace(Constants.PlayableDemosCommentString, Constants.PlayableDemosCommentField)
.Replace(Constants.PonyCanyonIDCommentString, Constants.PonyCanyonIDCommentField)
.Replace(Constants.PostgapTypeCommentString, Constants.PostgapTypeCommentField)
.Replace(Constants.PPNCommentString, Constants.PPNCommentField)
.Replace(Constants.RollingDemosCommentString, Constants.RollingDemosCommentField)
.Replace(Constants.SavegamesCommentString, Constants.SavegamesCommentField)
.Replace(Constants.SegaIDCommentString, Constants.SegaIDCommentField)
.Replace(Constants.SelenIDCommentString, Constants.SelenIDCommentField)
.Replace(Constants.SeriesCommentString, Constants.SeriesCommentField)
.Replace(Constants.TaitoIDCommentString, Constants.TaitoIDCommentField)
.Replace(Constants.TechDemosCommentString, Constants.TechDemosCommentField)
.Replace(Constants.UbisoftIDCommentString, Constants.UbisoftIDCommentField)
.Replace(Constants.ValveIDCommentString, Constants.ValveIDCommentField)
.Replace(Constants.VGCCodeCommentString, Constants.VFCCodeCommentField)
.Replace(Constants.VideosCommentString, Constants.VideosCommentField)
.Replace(Constants.VolumeLabelCommentString, Constants.VolumeLabelCommentField)
.Replace(Constants.VCDCommentString, Constants.VCDCommentField);
}
///
/// List the disc IDs associated with a given quicksearch query
///
/// RedumpWebClient for making the connection
/// Query string to attempt to search for
/// All disc IDs for the given query, null on error
private static List ListSearchResults(RedumpWebClient wc, string query)
{
List ids = new List();
// Strip quotes
query = query.Trim('"', '\'');
// Special characters become dashes
query = query.Replace(' ', '-');
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)
{
List pageIds = wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++));
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
private static void ValidateSingleTrack(RedumpWebClient wc, SubmissionInfo info, string hashData, IProgress resultProgress = null)
{
// If the line isn't parseable, we can't validate
if (!GetISOHashValues(hashData, out long _, out string _, out string _, out string sha1))
return;
// Get all matching IDs for the track
List newIds = ListSearchResults(wc, sha1);
// 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;
}
// If no IDs match any track, then we don't match a disc at all
if (!newIds.Any())
{
info.MatchedIDs = new List();
return;
}
// If we have multiple tracks, only take IDs that are in common
if (info.MatchedIDs.Any())
info.MatchedIDs = info.MatchedIDs.Intersect(newIds).ToList();
// If we're on the first track, all IDs are added
else
info.MatchedIDs = 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
private static bool ValidateTrackCount(RedumpWebClient wc, int id, int localCount)
{
// If we can't pull the remote data, we can't match
string discData = wc.DownloadSingleSiteID(id);
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
}
}