");
///
/// Regex matching the title field on a disc page
///
private readonly Regex titleRegex = new Regex(@"(.*?) ");
///
/// Regex matching the current nonce token for login
///
private readonly Regex tokenRegex = new Regex(@" ");
///
/// Regex matching a single track on a disc page
///
private readonly Regex trackRegex = new Regex(@"(?.*?) (?.*?) (?.*?) (?.*?) (?.*?) (?.*?) (?.*?) (?.*?) (?.*?) ", RegexOptions.Singleline);
///
/// Regex matching the track count on a disc page
///
private readonly Regex trackCountRegex = new Regex(@"Number of tracks (.*?) ");
///
/// Regex matching the version field on a disc page
///
private readonly Regex versionRegex = new Regex(@"Version (.*?) ");
///
/// Regex matching the write offset field on a disc page
///
private readonly Regex writeOffsetRegex = new Regex(@"Write offset (.*?) ");
#endregion
#region URLs
///
/// Redump disc page URL template
///
private const string discPageUrl = @"http://redump.org/disc/{0}/";
///
/// Redump last modified search URL
///
private const string lastModifiedUrl = @"http://redump.org/discs/sort/modified/dir/desc?page={0}";
///
/// Redump login page URL
///
private const string loginUrl = "http://forum.redump.org/login/";
///
/// Redump CUE pack URL template
///
private const string packCuesUrl = @"http://redump.org/cues/{0}/";
///
/// Redump DAT pack URL template
///
private const string packDatfileUrl = @"http://redump.org/datfile/{0}/";
///
/// Redump DKEYS pack URL template
///
private const string packDkeysUrl = @"http://redump.org/dkeys/{0}/";
///
/// Redump GDI pack URL template
///
private const string packGdiUrl = @"http://redump.org/gdi/{0}/";
///
/// Redump KEYS pack URL template
///
private const string packKeysUrl = @"http://redump.org/keys/{0}/";
///
/// Redump LSD pack URL template
///
private const string packLsdUrl = @"http://redump.org/lsd/{0}/";
///
/// Redump SBI pack URL template
///
private const string packSbiUrl = @"http://redump.org/sbi/{0}/";
///
/// Redump quicksearch URL template
///
private const string quickSearchUrl = @"http://redump.org/discs/quicksearch/{0}/?page={1}";
///
/// Redump user dumps URL template
///
private const string userDumpsUrl = @"http://redump.org/discs/dumper/{0}/?page={1}";
///
/// Redump WIP disc page URL template
///
private const string wipDiscPageUrl = @"http://redump.org/newdisc/{0}/";
///
/// Redump WIP dumps queue URL
///
private const string wipDumpsUrl = @"http://redump.org/discs-wip/";
#endregion
#region URL Extensions
private const string changesExt = "changes/";
private const string cueExt = "cue/";
private const string editExt = "edit/";
private const string gdiExt = "gdi/";
private const string keyExt = "key/";
private const string lsdExt = "lsd/";
private const string md5Ext = "md5/";
private const string sbiExt = "sbi/";
private const string sfvExt = "sfv/";
private const string sha1Ext = "sha1/";
#endregion
private readonly CookieContainer m_container = new CookieContainer();
///
/// Determines if user is logged into Redump
///
public bool LoggedIn { get; set; } = false;
///
/// Determines if the user is a staff member
///
public bool IsStaff { get; set; } = false;
///
/// Get the last downloaded filename, if possible
///
///
public string GetLastFilename()
{
// Try to extract the filename from the Content-Disposition header
if (!String.IsNullOrEmpty(this.ResponseHeaders["Content-Disposition"]))
return this.ResponseHeaders["Content-Disposition"].Substring(this.ResponseHeaders["Content-Disposition"].IndexOf("filename=") + 9).Replace("\"", "");
return null;
}
protected override WebRequest GetWebRequest(Uri address)
{
WebRequest request = base.GetWebRequest(address);
if (request is HttpWebRequest webRequest)
{
webRequest.CookieContainer = m_container;
}
return request;
}
#region Features
///
/// Login to Redump, if possible
///
/// Redump username
/// Redump password
/// True if the user could be logged in, false otherwise, null on error
public bool? Login(string username, string password)
{
// Credentials verification
if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password))
{
Console.WriteLine("Credentials entered, will attempt Redump login...");
}
else if (!string.IsNullOrWhiteSpace(username) && string.IsNullOrWhiteSpace(password))
{
Console.WriteLine("Only a username was specified, will not attempt Redump login...");
return false;
}
else if (string.IsNullOrWhiteSpace(username))
{
Console.WriteLine("No credentials entered, will not attempt Redump login...");
return false;
}
try
{
// Get the current token from the login page
var loginPage = DownloadString(loginUrl);
string token = this.tokenRegex.Match(loginPage).Groups[1].Value;
// Construct the login request
Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
Encoding = Encoding.UTF8;
var response = UploadString(loginUrl, $"form_sent=1&redirect_url=&csrf_token={token}&req_username={username}&req_password={password}&save_pass=0");
if (response.Contains("Incorrect username and/or password."))
{
Console.WriteLine("Invalid credentials entered, continuing without logging in...");
return false;
}
// The user was able to be logged in
Console.WriteLine("Credentials accepted! Logged into Redump...");
LoggedIn = true;
// If the user is a moderator or staff, set accordingly
if (response.Contains("http://forum.redump.org/forum/9/staff/"))
IsStaff = true;
return true;
}
catch (Exception ex)
{
Console.WriteLine($"An exception occurred while trying to log in: {ex}");
return null;
}
}
///
/// Get the latest version of MPF from GitHub and the release URL
///
public (string tag, string url) GetRemoteVersionAndUrl()
{
Headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0";
// TODO: Figure out a better way than having this hardcoded...
string url = "https://api.github.com/repos/SabreTools/MPF/releases/latest";
string latestReleaseJsonString = DownloadString(url);
var latestReleaseJson = JObject.Parse(latestReleaseJsonString);
string latestTag = latestReleaseJson["tag_name"].ToString();
string releaseUrl = latestReleaseJson["html_url"].ToString();
return (latestTag, releaseUrl);
}
///
/// Create a new SubmissionInfo object based on a disc page
///
/// Redump disc ID to retrieve
/// Filled SubmissionInfo object on success, null on error
public SubmissionInfo CreateFromId(int id)
{
string discData = DownloadSingleSiteID(id);
if (string.IsNullOrEmpty(discData))
return null;
// Create the new object
SubmissionInfo info = new SubmissionInfo();
// Added
var match = addedRegex.Match(discData);
if (match.Success)
{
if (DateTime.TryParse(match.Groups[1].Value, out DateTime added))
info.Added = added;
else
info.Added = null;
}
// Barcode
match = barcodeRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.Barcode = WebUtility.HtmlDecode(match.Groups[1].Value);
// BCA
match = bcaRegex.Match(discData);
if (match.Success)
{
info.Extras.BCA = WebUtility.HtmlDecode(match.Groups[1].Value)
.Replace(" ", "\n")
.Replace("", "");
info.Extras.BCA = Regex.Replace(info.Extras.BCA, @"", "");
}
// Category
match = categoryRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.Category = Extensions.ToCategory(match.Groups[1].Value);
else
info.CommonDiscInfo.Category = DiscCategory.Games;
// Comments
match = commentsRegex.Match(discData);
if (match.Success)
{
info.CommonDiscInfo.Comments = WebUtility.HtmlDecode(match.Groups[1].Value)
.Replace(" ", "\n")
.Replace("ISBN ", "[T:ISBN]") + "\n";
}
// Contents
match = contentsRegex.Match(discData);
if (match.Success)
{
info.CommonDiscInfo.Contents = WebUtility.HtmlDecode(match.Groups[1].Value)
.Replace(" ", "\n")
.Replace("
", "");
info.CommonDiscInfo.Contents = Regex.Replace(info.CommonDiscInfo.Contents, @"", "");
}
// Dumpers
var matches = dumpersRegex.Matches(discData);
if (matches.Count > 0)
{
List tempDumpers = new List();
foreach (Match submatch in matches)
{
tempDumpers.Add(WebUtility.HtmlDecode(submatch.Groups[1].Value));
}
info.DumpersAndStatus.Dumpers = tempDumpers.ToArray();
}
// Edition
match = editionRegex.Match(discData);
if (match.Success)
info.VersionAndEditions.OtherEditions = WebUtility.HtmlDecode(match.Groups[1].Value);
// Error Count
match = errorCountRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.ErrorsCount = match.Groups[1].Value;
// Foreign Title
match = foreignTitleRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.ForeignTitleNonLatin = WebUtility.HtmlDecode(match.Groups[1].Value);
else
info.CommonDiscInfo.ForeignTitleNonLatin = null;
// Languages
matches = languagesRegex.Matches(discData);
if (matches.Count > 0)
{
List tempLanguages = new List();
foreach (Match submatch in matches)
{
tempLanguages.Add(Extensions.ToLanguage(submatch.Groups[1].Value));
}
info.CommonDiscInfo.Languages = tempLanguages.Where(l => l != null).ToArray();
}
// Last Modified
match = lastModifiedRegex.Match(discData);
if (match.Success)
{
if (DateTime.TryParse(match.Groups[1].Value, out DateTime lastModified))
info.LastModified = lastModified;
else
info.LastModified = null;
}
// Media
match = mediaRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.Media = Converters.ToMediaType(match.Groups[1].Value);
// PVD
match = pvdRegex.Match(discData);
if (match.Success)
{
info.Extras.PVD = WebUtility.HtmlDecode(match.Groups[1].Value)
.Replace(" ", "\n")
.Replace("
", "");
info.Extras.PVD = Regex.Replace(info.Extras.PVD, @"", "");
}
// Region
match = regionRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.Region = Extensions.ToRegion(match.Groups[1].Value);
// Serial
match = serialRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.Serial = WebUtility.HtmlDecode(match.Groups[1].Value);
// System
match = systemRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.System = Converters.ToKnownSystem(match.Groups[1].Value);
// Title, Disc Number/Letter, Disc Title
match = titleRegex.Match(discData);
if (match.Success)
{
string title = WebUtility.HtmlDecode(match.Groups[1].Value);
// If we have parenthesis, title is everything before the first one
int firstParenLocation = title.IndexOf(" (");
if (firstParenLocation >= 0)
{
info.CommonDiscInfo.Title = title.Substring(0, firstParenLocation);
var subMatches = discNumberLetterRegex.Match(title);
for (int i = 1; i < subMatches.Groups.Count; i++)
{
string subMatch = subMatches.Groups[i].Value;
// Disc number or letter
if (subMatch.StartsWith("Disc"))
info.CommonDiscInfo.DiscNumberLetter = subMatch.Remove(0, "Disc ".Length);
// Disc title
else
info.CommonDiscInfo.DiscTitle = subMatch;
}
}
// Otherwise, leave the title as-is
else
{
info.CommonDiscInfo.Title = title;
}
}
// Tracks
matches = trackRegex.Matches(discData);
if (matches.Count > 0)
{
List
tempTracks = new List();
foreach (Match submatch in matches)
{
tempTracks.Add(submatch.Groups[1].Value);
}
info.TracksAndWriteOffsets.ClrMameProData = string.Join("\n", tempTracks);
}
// Track Count
match = trackCountRegex.Match(discData);
if (match.Success)
info.TracksAndWriteOffsets.Cuesheet = match.Groups[1].Value;
// Version
match = versionRegex.Match(discData);
if (match.Success)
info.VersionAndEditions.Version = WebUtility.HtmlDecode(match.Groups[1].Value);
// Write Offset
match = writeOffsetRegex.Match(discData);
if (match.Success)
info.TracksAndWriteOffsets.OtherWriteOffsets = WebUtility.HtmlDecode(match.Groups[1].Value);
return info;
}
///
/// Download the last modified disc pages, until first failure
///
/// Output directory to save data to
public void DownloadLastModified(string outDir)
{
// Keep getting last modified pages until there are none left
int pageNumber = 1;
while (true)
{
if (!CheckSingleSitePage(string.Format(lastModifiedUrl, pageNumber++), outDir, true))
break;
}
}
///
/// Download the last submitted WIP disc pages
///
/// Output directory to save data to
public void DownloadLastSubmitted(string outDir)
{
CheckSingleWIPPage(wipDumpsUrl, outDir, false);
}
///
/// Download premade packs
///
/// Output directory to save data to
/// True to use named subfolders to store downloads, false to store directly in the output directory
public void DownloadPacks(string outDir, bool useSubfolders)
{
this.DownloadPacks(packCuesUrl, Extensions.HasCues, "CUEs", outDir, useSubfolders ? "cue" : null);
this.DownloadPacks(packDatfileUrl, Extensions.HasDat, "DATs", outDir, useSubfolders ? "dat" : null);
this.DownloadPacks(packDkeysUrl, Extensions.HasDkeys, "Decrypted KEYS", outDir, useSubfolders ? "dkey" : null);
this.DownloadPacks(packGdiUrl, Extensions.HasGdi, "GDIs", outDir, useSubfolders ? "gdi" : null);
this.DownloadPacks(packKeysUrl, Extensions.HasKeys, "KEYS", outDir, useSubfolders ? "keys" : null);
this.DownloadPacks(packLsdUrl, Extensions.HasKeys, "LSD", outDir, useSubfolders ? "lsd" : null);
this.DownloadPacks(packSbiUrl, Extensions.HasSbi, "SBIs", outDir, useSubfolders ? "sbi" : null);
}
///
/// Download premade packs for an individual system
///
/// RedumpSystem to get all possible packs for
/// Output directory to save data to
/// True to use named subfolders to store downloads, false to store directly in the output directory
public void DownloadPacksForSystem(RedumpSystem system, string outDir, bool useSubfolders)
{
RedumpSystem?[] systemAsArray = new RedumpSystem?[] { system };
if (Extensions.HasCues.Contains(system))
this.DownloadPacks(packCuesUrl, systemAsArray, "CUEs", outDir, useSubfolders ? "cue" : null);
if (Extensions.HasDat.Contains(system))
this.DownloadPacks(packCuesUrl, Extensions.HasDat, "DATs", outDir, useSubfolders ? "dat" : null);
if (Extensions.HasDkeys.Contains(system))
this.DownloadPacks(packCuesUrl, Extensions.HasDkeys, "Decrypted KEYS", outDir, useSubfolders ? "dkey" : null);
if (Extensions.HasGdi.Contains(system))
this.DownloadPacks(packCuesUrl, Extensions.HasGdi, "GDIs", outDir, useSubfolders ? "gdi" : null);
if (Extensions.HasKeys.Contains(system))
this.DownloadPacks(packCuesUrl, Extensions.HasKeys, "KEYS", outDir, useSubfolders ? "keys" : null);
if (Extensions.HasLsd.Contains(system))
this.DownloadPacks(packCuesUrl, Extensions.HasKeys, "LSD", outDir, useSubfolders ? "lsd" : null);
if (Extensions.HasSbi.Contains(system))
this.DownloadPacks(packCuesUrl, Extensions.HasSbi, "SBIs", outDir, useSubfolders ? "sbi" : null);
}
///
/// Download the disc pages associated with a given quicksearch query
///
/// Query string to attempt to search for
public Dictionary DownloadSearchResults(string query)
{
Dictionary resultPages = new Dictionary();
// 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
int pageNumber = 1;
while (true)
{
List pageIds = CheckSingleSitePage(string.Format(quickSearchUrl, query, pageNumber++));
foreach (int pageId in pageIds)
{
resultPages[pageId] = DownloadSingleSiteID(pageId);
}
if (pageIds.Count <= 1)
break;
}
return resultPages;
}
///
/// Download the disc pages associated with a given quicksearch query
///
/// Query string to attempt to search for
/// Output directory to save data to
public void DownloadSearchResults(string query, string outDir)
{
// 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
int pageNumber = 1;
while (true)
{
if (!CheckSingleSitePage(string.Format(quickSearchUrl, query, pageNumber++), outDir, false))
break;
}
}
///
/// Download the specified range of site disc pages
///
/// Output directory to save data to
/// Starting ID for the range
/// Ending ID for the range (inclusive)
public void DownloadSiteRange(string outDir, int minId = 0, int maxId = 0)
{
if (!LoggedIn)
{
Console.WriteLine("Site download functionality is only available to Redump members");
return;
}
for (int id = minId; id <= maxId; id++)
{
if (DownloadSingleSiteID(id, outDir, true))
Thread.Sleep(5 * 1000); // Intentional sleep here so we don't flood the server
}
}
///
/// Download the disc pages associated with the given user
///
/// Username to check discs for
/// Output directory to save data to
public void DownloadUser(string username, string outDir)
{
if (!LoggedIn)
{
Console.WriteLine("User download functionality is only available to Redump members");
return;
}
// Keep getting user pages until there are none left
int pageNumber = 1;
while (true)
{
if (!CheckSingleSitePage(string.Format(userDumpsUrl, username, pageNumber++), outDir, false))
break;
}
}
///
/// Download the specified range of WIP disc pages
///
/// RedumpWebClient for all access
/// Output directory to save data to
/// Starting ID for the range
/// Ending ID for the range (inclusive)
public void DownloadWIPRange(string outDir, int minId = 0, int maxId = 0)
{
if (!LoggedIn || !IsStaff)
{
Console.WriteLine("WIP download functionality is only available to Redump moderators");
return;
}
for (int id = minId; id <= maxId; id++)
{
if (DownloadSingleWIPID(id, outDir, true))
Thread.Sleep(5 * 1000); // Intentional sleep here so we don't flood the server
}
}
///
/// Fill out an existing SubmissionInfo object based on a disc page
///
/// Existing SubmissionInfo object to fill
/// Redump disc ID to retrieve
public void FillFromId(SubmissionInfo info, int id)
{
string discData = DownloadSingleSiteID(id);
if (string.IsNullOrEmpty(discData))
return;
// Title, Disc Number/Letter, Disc Title
var match = titleRegex.Match(discData);
if (match.Success)
{
string title = WebUtility.HtmlDecode(match.Groups[1].Value);
// If we have parenthesis, title is everything before the first one
int firstParenLocation = title.IndexOf(" (");
if (firstParenLocation >= 0)
{
info.CommonDiscInfo.Title = title.Substring(0, firstParenLocation);
var subMatches = discNumberLetterRegex.Match(title);
for (int i = 1; i < subMatches.Groups.Count; i++)
{
string subMatch = subMatches.Groups[i].Value;
// Disc number or letter
if (subMatch.StartsWith("Disc"))
info.CommonDiscInfo.DiscNumberLetter = subMatch.Remove(0, "Disc ".Length);
// Disc title
else
info.CommonDiscInfo.DiscTitle = subMatch;
}
}
// Otherwise, leave the title as-is
else
{
info.CommonDiscInfo.Title = title;
}
}
// Foreign Title
match = foreignTitleRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.ForeignTitleNonLatin = WebUtility.HtmlDecode(match.Groups[1].Value);
else
info.CommonDiscInfo.ForeignTitleNonLatin = null;
// Category
match = categoryRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.Category = Extensions.ToCategory(match.Groups[1].Value);
else
info.CommonDiscInfo.Category = DiscCategory.Games;
// Region
match = regionRegex.Match(discData);
if (match.Success)
info.CommonDiscInfo.Region = Extensions.ToRegion(match.Groups[1].Value);
// Languages
var matches = languagesRegex.Matches(discData);
if (matches.Count > 0)
{
List tempLanguages = new List();
foreach (Match submatch in matches)
tempLanguages.Add(Extensions.ToLanguage(submatch.Groups[1].Value));
info.CommonDiscInfo.Languages = tempLanguages.Where(l => l != null).ToArray();
}
// Error count
match = errorCountRegex.Match(discData);
if (match.Success)
{
// If the error count is empty, fill from the page
if (string.IsNullOrEmpty(info.CommonDiscInfo.ErrorsCount))
info.CommonDiscInfo.ErrorsCount = match.Groups[1].Value;
}
// Version
match = versionRegex.Match(discData);
if (match.Success)
info.VersionAndEditions.Version = WebUtility.HtmlDecode(match.Groups[1].Value);
// Dumpers
matches = dumpersRegex.Matches(discData);
if (matches.Count > 0)
{
// Start with any currently listed dumpers
List tempDumpers = new List();
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();
}
// Comments
match = commentsRegex.Match(discData);
if (match.Success)
{
info.CommonDiscInfo.Comments += (string.IsNullOrEmpty(info.CommonDiscInfo.Comments) ? string.Empty : "\n")
+ WebUtility.HtmlDecode(match.Groups[1].Value)
.Replace(" ", "\n")
.Replace("ISBN ", "[T:ISBN]") + "\n";
}
// Contents
match = contentsRegex.Match(discData);
if (match.Success)
{
info.CommonDiscInfo.Contents = WebUtility.HtmlDecode(match.Groups[1].Value)
.Replace(" ", "\n")
.Replace(" ", "");
info.CommonDiscInfo.Contents = Regex.Replace(info.CommonDiscInfo.Contents, @"", "");
}
// Added
match = 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 = lastModifiedRegex.Match(discData);
if (match.Success)
{
if (DateTime.TryParse(match.Groups[1].Value, out DateTime lastModified))
info.LastModified = lastModified;
else
info.LastModified = null;
}
}
///
/// List the disc IDs associated with a given quicksearch query
///
///
Query string to attempt to search for
///
All disc IDs for the given query, null on error
public List
ListSearchResults(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 = CheckSingleSitePage(string.Format(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;
}
///
/// List the disc IDs associated with the given user
///
/// Username to check discs for
/// All disc IDs for the given user, null on error
public List ListUser(string username)
{
List ids = new List();
if (!LoggedIn)
{
Console.WriteLine("User download functionality is only available to Redump members");
return ids;
}
// Keep getting user pages until there are none left
try
{
int pageNumber = 1;
while (true)
{
List pageIds = CheckSingleSitePage(string.Format(userDumpsUrl, username, 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;
}
#endregion
#region Single Page Helpers
///
/// Process a Redump site page as a list of possible IDs or disc page
///
/// Base URL to download using
/// List of IDs from the page, empty on error
private List CheckSingleSitePage(string url)
{
List ids = new List();
var dumpsPage = DownloadString(url);
// If we have no dumps left
if (dumpsPage.Contains("No discs found."))
return ids;
// If we have a single disc page already
if (dumpsPage.Contains("Download: "))
{
var value = Regex.Match(dumpsPage, @"/disc/(\d+)/sfv/").Groups[1].Value;
if (int.TryParse(value, out int id))
ids.Add(id);
return ids;
}
// Otherwise, traverse each dump on the page
var matches = discRegex.Matches(dumpsPage);
foreach (Match match in matches)
{
try
{
if (int.TryParse(match.Groups[1].Value, out int value))
ids.Add(value);
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
continue;
}
}
return ids;
}
///
/// Process a Redump site page as a list of possible IDs or disc page
///
/// Base URL to download using
/// Output directory to save data to
/// True to return on first error, false otherwise
/// True if the page could be downloaded, false otherwise
private bool CheckSingleSitePage(string url, string outDir, bool failOnSingle)
{
var dumpsPage = DownloadString(url);
// If we have no dumps left
if (dumpsPage.Contains("No discs found."))
return false;
// If we have a single disc page already
if (dumpsPage.Contains("Download: "))
{
var value = Regex.Match(dumpsPage, @"/disc/(\d+)/sfv/").Groups[1].Value;
if (int.TryParse(value, out int id))
{
bool downloaded = DownloadSingleSiteID(id, outDir, false);
if (!downloaded && failOnSingle)
return false;
}
return false;
}
// Otherwise, traverse each dump on the page
var matches = discRegex.Matches(dumpsPage);
foreach (Match match in matches)
{
try
{
if (int.TryParse(match.Groups[1].Value, out int value))
{
bool downloaded = DownloadSingleSiteID(value, outDir, false);
if (!downloaded && failOnSingle)
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
continue;
}
}
return true;
}
///
/// Process a Redump WIP page as a list of possible IDs or disc page
///
/// RedumpWebClient to access the packs
/// List of IDs from the page, empty on error
private List CheckSingleWIPPage(string url)
{
List ids = new List();
var dumpsPage = DownloadString(url);
// If we have no dumps left
if (dumpsPage.Contains("No discs found."))
return ids;
// Otherwise, traverse each dump on the page
var matches = newDiscRegex.Matches(dumpsPage);
foreach (Match match in matches)
{
try
{
if (int.TryParse(match.Groups[2].Value, out int value))
ids.Add(value);
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
continue;
}
}
return ids;
}
///
/// Process a Redump WIP page as a list of possible IDs or disc page
///
/// RedumpWebClient to access the packs
/// Output directory to save data to
/// True to return on first error, false otherwise
/// True if the page could be downloaded, false otherwise
private bool CheckSingleWIPPage(string url, string outDir, bool failOnSingle)
{
var dumpsPage = DownloadString(url);
// If we have no dumps left
if (dumpsPage.Contains("No discs found."))
return false;
// Otherwise, traverse each dump on the page
var matches = newDiscRegex.Matches(dumpsPage);
foreach (Match match in matches)
{
try
{
if (int.TryParse(match.Groups[2].Value, out int value))
{
bool downloaded = DownloadSingleWIPID(value, outDir, false);
if (!downloaded && failOnSingle)
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
continue;
}
}
return true;
}
#endregion
#region Download Helpers
///
/// Download a single pack
///
/// Base URL to download using
/// System to download packs for
/// Byte array containing the downloaded pack, null on error
private byte[] DownloadSinglePack(string url, RedumpSystem? system)
{
try
{
return DownloadData(string.Format(url, system.ShortName()));
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
return null;
}
}
///
/// Download a single pack
///
/// Base URL to download using
/// System to download packs for
/// Output directory to save data to
/// Named subfolder for the pack, used optionally
private void DownloadSinglePack(string url, RedumpSystem? system, string outDir, string subfolder)
{
try
{
string tempfile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
DownloadFile(string.Format(url, system.ShortName()), tempfile);
MoveOrDelete(tempfile, GetLastFilename(), outDir, subfolder);
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
}
}
///
/// Download an individual site ID data, if possible
///
/// Redump disc ID to retrieve
/// String containing the page contents if successful, null on error
private string DownloadSingleSiteID(int id)
{
string paddedId = id.ToString().PadLeft(5, '0');
Console.WriteLine($"Processing ID: {paddedId}");
try
{
string discPage = DownloadString(string.Format(discPageUrl, +id));
if (discPage.Contains($"Disc with ID \"{id}\" doesn't exist"))
{
Console.WriteLine($"ID {paddedId} could not be found!");
return null;
}
Console.WriteLine($"ID {paddedId} has been successfully downloaded");
return discPage;
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
return null;
}
}
///
/// Download an individual site ID data, if possible
///
/// Redump disc ID to retrieve
/// Output directory to save data to
/// True to rename deleted entries, false otherwise
/// True if all data was downloaded, false otherwise
private bool DownloadSingleSiteID(int id, string outDir, bool rename)
{
string paddedId = id.ToString().PadLeft(5, '0');
string paddedIdDir = Path.Combine(outDir, paddedId);
Console.WriteLine($"Processing ID: {paddedId}");
try
{
string discPage = DownloadString(string.Format(discPageUrl, +id));
if (discPage.Contains($"Disc with ID \"{id}\" doesn't exist"))
{
try
{
if (rename)
{
if (Directory.Exists(paddedIdDir) && rename)
Directory.Move(paddedIdDir, paddedIdDir + "-deleted");
else
Directory.CreateDirectory(paddedIdDir + "-deleted");
}
}
catch { }
Console.WriteLine($"ID {paddedId} could not be found!");
return false;
}
// Check if the page has been updated since the last time it was downloaded, if possible
if (File.Exists(Path.Combine(paddedIdDir, "disc.html")))
{
// Read in the cached file
var oldDiscPage = File.ReadAllText(Path.Combine(paddedIdDir, "disc.html"));
// Check for the last modified date in both pages
var oldResult = lastModifiedRegex.Match(oldDiscPage);
var newResult = lastModifiedRegex.Match(discPage);
// If both pages contain the same modified date, skip it
if (oldResult.Success && newResult.Success && oldResult.Groups[1].Value == newResult.Groups[1].Value)
{
Console.WriteLine($"ID {paddedId} has not been changed since last download");
return false;
}
// If neither page contains a modified date, skip it
else if (!oldResult.Success && !newResult.Success)
{
Console.WriteLine($"ID {paddedId} has not been changed since last download");
return false;
}
}
// Create ID subdirectory
Directory.CreateDirectory(paddedIdDir);
// View Edit History
if (discPage.Contains($"
/// Download an individual WIP ID data, if possible
///
/// Redump WIP disc ID to retrieve
/// String containing the page contents if successful, null on error
private string DownloadSingleWIPID(int id)
{
string paddedId = id.ToString().PadLeft(5, '0');
Console.WriteLine($"Processing ID: {paddedId}");
try
{
string discPage = DownloadString(string.Format(wipDiscPageUrl, +id));
if (discPage.Contains($"System \"{id}\" doesn't exist"))
{
Console.WriteLine($"ID {paddedId} could not be found!");
return null;
}
Console.WriteLine($"ID {paddedId} has been successfully downloaded");
return discPage;
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
return null;
}
}
///
/// Download an individual WIP ID data, if possible
///
/// Redump WIP disc ID to retrieve
/// Output directory to save data to
/// True to rename deleted entries, false otherwise
/// True if all data was downloaded, false otherwise
private bool DownloadSingleWIPID(int id, string outDir, bool rename)
{
string paddedId = id.ToString().PadLeft(5, '0');
string paddedIdDir = Path.Combine(outDir, paddedId);
Console.WriteLine($"Processing ID: {paddedId}");
try
{
string discPage = DownloadString(string.Format(wipDiscPageUrl, +id));
if (discPage.Contains($"System \"{id}\" doesn't exist"))
{
try
{
if (rename)
{
if (Directory.Exists(paddedIdDir) && rename)
Directory.Move(paddedIdDir, paddedIdDir + "-deleted");
else
Directory.CreateDirectory(paddedIdDir + "-deleted");
}
}
catch { }
Console.WriteLine($"ID {paddedId} could not be found!");
return false;
}
// Check if the page has been updated since the last time it was downloaded, if possible
if (File.Exists(Path.Combine(paddedIdDir, "disc.html")))
{
// Read in the cached file
var oldDiscPage = File.ReadAllText(Path.Combine(paddedIdDir, "disc.html"));
// Check for the full match ID in both pages
var oldResult = fullMatchRegex.Match(oldDiscPage);
var newResult = fullMatchRegex.Match(discPage);
// If both pages contain the same ID, skip it
if (oldResult.Success && newResult.Success && oldResult.Groups[1].Value == newResult.Groups[1].Value)
{
Console.WriteLine($"ID {paddedId} has not been changed since last download");
return false;
}
// If neither page contains an ID, skip it
else if (!oldResult.Success && !newResult.Success)
{
Console.WriteLine($"ID {paddedId} has not been changed since last download");
return false;
}
}
// Create ID subdirectory
Directory.CreateDirectory(paddedIdDir);
// HTML
using (var discStreamWriter = File.CreateText(Path.Combine(paddedIdDir, "disc.html")))
{
discStreamWriter.Write(discPage);
}
Console.WriteLine($"ID {paddedId} has been successfully downloaded");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
return false;
}
}
#endregion
#region Internal Helpers
///
/// Download a set of packs
///
/// Base URL to download using
/// Systems to download packs for
/// Name of the pack that is downloading
private Dictionary DownloadPacks(string url, RedumpSystem?[] systems, string title)
{
var packsDictionary = new Dictionary();
// If we didn't have credentials
if (!LoggedIn)
systems = systems.Where(s => !Extensions.BannedSystems.Contains(s)).ToArray();
Console.WriteLine($"Downloading {title}");
foreach (var system in systems)
{
Console.Write($"\r{system.LongName()}{new string(' ', Console.BufferWidth - system.LongName().Length - 1)}");
byte[] pack = DownloadSinglePack(url, system);
if (pack != null)
packsDictionary.Add(system, pack);
}
Console.Write($"\rComplete!{new string(' ', Console.BufferWidth - 10)}");
Console.WriteLine();
return packsDictionary;
}
///
/// Download a set of packs
///
/// Base URL to download using
/// Systems to download packs for
/// Name of the pack that is downloading
/// Output directory to save data to
/// Named subfolder for the pack, used optionally
private void DownloadPacks(string url, RedumpSystem?[] systems, string title, string outDir, string subfolder)
{
// If we didn't have credentials
if (!LoggedIn)
systems = systems.Where(s => !Extensions.BannedSystems.Contains(s)).ToArray();
Console.WriteLine($"Downloading {title}");
foreach (var system in systems)
{
Console.Write($"\r{system.LongName()}{new string(' ', Console.BufferWidth - system.LongName().Length - 1)}");
DownloadSinglePack(url, system, outDir, subfolder);
}
Console.Write($"\rComplete!{new string(' ', Console.BufferWidth - 10)}");
Console.WriteLine();
}
///
/// Move a tempfile to a new name unless it aleady exists, in which case, delete the tempfile
///
/// Path to existing temporary file
/// Path to new output file
/// Output directory to save data to
/// Optional subfolder to append to the path
private void MoveOrDelete(string tempfile, string newfile, string outDir, string subfolder)
{
if (!string.IsNullOrWhiteSpace(newfile))
{
if (!string.IsNullOrWhiteSpace(subfolder))
{
if (!Directory.Exists(Path.Combine(outDir, subfolder)))
Directory.CreateDirectory(Path.Combine(outDir, subfolder));
newfile = Path.Combine(subfolder, newfile);
}
if (File.Exists(Path.Combine(outDir, newfile)))
File.Delete(tempfile);
else
File.Move(tempfile, Path.Combine(outDir, newfile));
}
else
File.Delete(tempfile);
}
#endregion
}
}