2021-09-29 16:16:54 -07:00
using System ;
using System.Collections.Generic ;
2023-07-23 16:41:40 -04:00
using System.Drawing.Drawing2D ;
2021-09-29 16:16:54 -07:00
using System.IO ;
using System.IO.Compression ;
using System.Linq ;
using System.Net ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
2021-12-30 11:09:37 -08:00
using System.Xml ;
2021-09-29 16:16:54 -07:00
using BurnOutSharp ;
using MPF.Core.Data ;
2021-11-25 22:38:59 -08:00
using MPF.Core.Utilities ;
2021-09-29 16:16:54 -07:00
using MPF.Modules ;
using Newtonsoft.Json ;
2023-09-05 00:08:09 -04:00
using SabreTools.RedumpLib.Data ;
using SabreTools.RedumpLib.Web ;
2021-12-30 11:09:37 -08:00
using Formatting = Newtonsoft . Json . Formatting ;
2021-09-29 16:16:54 -07:00
namespace MPF.Library
{
public static class InfoTool
{
#region Information Extraction
/// <summary>
/// Extract all of the possible information from a given input combination
/// </summary>
2022-12-13 11:48:26 -08:00
/// <param name="outputPath">Output path to write to</param>
2021-09-29 16:16:54 -07:00
/// <param name="drive">Drive object representing the current drive</param>
/// <param name="system">Currently selected system</param>
/// <param name="mediaType">Currently selected media type</param>
/// <param name="options">Options object representing user-defined options</param>
/// <param name="parameters">Parameters object representing what to send to the internal program</param>
/// <param name="resultProgress">Optional result progress callback</param>
/// <param name="protectionProgress">Optional protection progress callback</param>
/// <returns>SubmissionInfo populated based on outputs, null on error</returns>
public static async Task < SubmissionInfo > ExtractOutputInformation (
2022-12-13 11:48:26 -08:00
string outputPath ,
2021-09-29 16:16:54 -07:00
Drive drive ,
RedumpSystem ? system ,
MediaType ? mediaType ,
2023-07-14 12:07:44 -04:00
Core . Data . Options options ,
2021-09-29 16:16:54 -07:00
BaseParameters parameters ,
IProgress < Result > resultProgress = null ,
IProgress < ProtectionProgress > protectionProgress = null )
{
// Ensure the current disc combination should exist
if ( ! system . MediaTypes ( ) . Contains ( mediaType ) )
return null ;
2022-12-13 11:48:26 -08:00
// Split the output path for easier use
string outputDirectory = Path . GetDirectoryName ( outputPath ) ;
string outputFilename = Path . GetFileName ( outputPath ) ;
2021-09-29 16:16:54 -07:00
// Check that all of the relevant files are there
( bool foundFiles , List < string > missingFiles ) = FoundAllFiles ( outputDirectory , outputFilename , parameters , false ) ;
if ( ! foundFiles )
{
resultProgress . Report ( Result . Failure ( $"There were files missing from the output:\n{string.Join(" \ n ", missingFiles)}" ) ) ;
2023-02-23 14:32:46 -05:00
resultProgress . Report ( Result . Failure ( $"This may indicate an issue with the hardware or media, including unsupported devices.\nPlease see dumping program documentation for more details." ) ) ;
2021-09-29 16:16:54 -07:00
return null ;
}
2022-01-01 14:16:58 -08:00
// Sanitize the output filename to strip off any potential extension
outputFilename = Path . GetFileNameWithoutExtension ( outputFilename ) ;
2021-09-29 16:16:54 -07:00
// Create the SubmissionInfo object with all user-inputted values by default
string combinedBase = Path . Combine ( outputDirectory , outputFilename ) ;
SubmissionInfo info = new SubmissionInfo ( )
{
CommonDiscInfo = new CommonDiscInfoSection ( )
{
System = system ,
Media = mediaType . ToDiscType ( ) ,
2022-02-01 13:04:28 -08:00
Title = options . AddPlaceholders ? Template . RequiredValue : string . Empty ,
ForeignTitleNonLatin = options . AddPlaceholders ? Template . OptionalValue : string . Empty ,
DiscNumberLetter = options . AddPlaceholders ? Template . OptionalValue : string . Empty ,
DiscTitle = options . AddPlaceholders ? Template . OptionalValue : string . Empty ,
2021-09-29 16:16:54 -07:00
Category = null ,
Region = null ,
Languages = null ,
2022-02-01 13:04:28 -08:00
Serial = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ,
Barcode = options . AddPlaceholders ? Template . OptionalValue : string . Empty ,
2021-12-30 15:23:17 -08:00
Contents = string . Empty ,
2023-09-05 00:08:09 -04:00
#if NET48
2021-12-30 13:00:55 -08:00
ContentsSpecialFields = new Dictionary < SiteCode ? , string > ( ) ,
2023-09-05 00:08:09 -04:00
#else
ContentsSpecialFields = new Dictionary < SiteCode , string > ( ) ,
#endif
2021-12-24 15:37:10 -08:00
Comments = string . Empty ,
2023-09-05 00:08:09 -04:00
#if NET48
2021-12-30 13:00:55 -08:00
CommentsSpecialFields = new Dictionary < SiteCode ? , string > ( ) ,
2023-09-05 00:08:09 -04:00
#else
CommentsSpecialFields = new Dictionary < SiteCode , string > ( ) ,
#endif
2021-09-29 16:16:54 -07:00
} ,
VersionAndEditions = new VersionAndEditionsSection ( )
{
2022-02-01 13:04:28 -08:00
Version = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ,
OtherEditions = options . AddPlaceholders ? "(VERIFY THIS) Original" : string . Empty ,
2021-09-29 16:16:54 -07:00
} ,
TracksAndWriteOffsets = new TracksAndWriteOffsetsSection ( ) ,
} ;
// Get specific tool output handling
2022-10-18 17:03:21 -07:00
parameters . GenerateSubmissionInfo ( info , options , combinedBase , drive , options . IncludeArtifacts ) ;
2021-09-29 16:16:54 -07:00
// Get a list of matching IDs for each line in the DAT
if ( ! string . IsNullOrEmpty ( info . TracksAndWriteOffsets . ClrMameProData ) & & options . HasRedumpLogin )
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2021-12-25 21:37:19 -08:00
FillFromRedump ( options , info , resultProgress ) ;
2022-07-26 13:47:19 -07:00
#else
_ = await FillFromRedump ( options , info , resultProgress ) ;
#endif
2021-09-29 16:16:54 -07:00
// If we have both ClrMamePro and Size and Checksums data, remove the ClrMamePro
if ( ! string . IsNullOrWhiteSpace ( info . SizeAndChecksums . CRC32 ) )
info . TracksAndWriteOffsets . ClrMameProData = null ;
2022-01-20 13:15:53 -08:00
// Add the volume label to comments, if possible or necessary
2022-03-09 14:25:01 -08:00
if ( drive ! = null & & drive . GetRedumpSystemFromVolumeLabel ( ) = = null )
2021-12-30 13:00:55 -08:00
info . CommonDiscInfo . CommentsSpecialFields [ SiteCode . VolumeLabel ] = drive . VolumeLabel ;
2021-12-24 15:13:06 -08:00
2021-09-29 16:16:54 -07:00
// Extract info based generically on MediaType
switch ( mediaType )
{
case MediaType . CDROM :
2022-08-29 19:49:31 -07:00
case MediaType . GDROM :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . Layer0MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0AdditionalMould = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case MediaType . DVD :
case MediaType . HDDVD :
case MediaType . BluRay :
// If we have a single-layer disc
if ( info . SizeAndChecksums . Layerbreak = = default )
{
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . Layer0MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0AdditionalMould = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2021-09-29 16:16:54 -07:00
}
// If we have a dual-layer disc
else
{
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . Layer0MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0AdditionalMould = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2021-09-29 16:16:54 -07:00
}
break ;
case MediaType . NintendoGameCubeGameDisc :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . Layer0MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0AdditionalMould = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . Extras . BCA = info . Extras . BCA ? ? ( options . AddPlaceholders ? Template . RequiredValue : string . Empty ) ;
2021-09-29 16:16:54 -07:00
break ;
case MediaType . NintendoWiiOpticalDisc :
// If we have a single-layer disc
if ( info . SizeAndChecksums . Layerbreak = = default )
{
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . Layer0MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0AdditionalMould = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2021-09-29 16:16:54 -07:00
}
// If we have a dual-layer disc
else
{
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . Layer0MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0AdditionalMould = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2021-09-29 16:16:54 -07:00
}
2022-02-01 13:04:28 -08:00
info . Extras . DiscKey = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
info . Extras . BCA = info . Extras . BCA ? ? ( options . AddPlaceholders ? Template . RequiredValue : string . Empty ) ;
2021-09-29 16:16:54 -07:00
break ;
case MediaType . UMD :
2023-04-10 10:21:47 -04:00
// Both single- and dual-layer discs have two "layers" for the ring
info . CommonDiscInfo . Layer0MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer0MouldSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2022-02-01 13:04:28 -08:00
2023-04-10 10:21:47 -04:00
info . CommonDiscInfo . Layer1MasteringRing = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1MasteringSID = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
info . CommonDiscInfo . Layer1ToolstampMasteringCode = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2021-09-29 16:16:54 -07:00
2022-02-01 13:04:28 -08:00
info . SizeAndChecksums . CRC32 = info . SizeAndChecksums . CRC32 ? ? ( options . AddPlaceholders ? Template . RequiredValue + " [Not automatically generated for UMD]" : string . Empty ) ;
info . SizeAndChecksums . MD5 = info . SizeAndChecksums . MD5 ? ? ( options . AddPlaceholders ? Template . RequiredValue + " [Not automatically generated for UMD]" : string . Empty ) ;
info . SizeAndChecksums . SHA1 = info . SizeAndChecksums . SHA1 ? ? ( options . AddPlaceholders ? Template . RequiredValue + " [Not automatically generated for UMD]" : string . Empty ) ;
2021-09-29 16:16:54 -07:00
info . TracksAndWriteOffsets . ClrMameProData = null ;
break ;
}
// Extract info based specifically on RedumpSystem
switch ( system )
{
case RedumpSystem . AcornArchimedes :
2022-01-26 23:29:06 -08:00
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . UnitedKingdom ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . AppleMacintosh :
case RedumpSystem . EnhancedCD :
case RedumpSystem . IBMPCcompatible :
case RedumpSystem . PalmOS :
case RedumpSystem . PocketPC :
case RedumpSystem . RainbowDisc :
2022-08-30 10:42:04 -07:00
case RedumpSystem . SonyElectronicBook :
2021-09-29 16:16:54 -07:00
resultProgress ? . Report ( Result . Success ( "Running copy protection scan... this might take a while!" ) ) ;
2022-04-10 22:12:20 -07:00
( string protectionString , Dictionary < string , List < string > > fullProtections ) = await GetCopyProtection ( drive , options , protectionProgress ) ;
2022-04-04 22:44:15 -07:00
info . CopyProtection . Protection = protectionString ;
2022-04-10 22:12:20 -07:00
info . CopyProtection . FullProtections = fullProtections ;
2021-09-29 16:16:54 -07:00
resultProgress ? . Report ( Result . Success ( "Copy protection scan complete!" ) ) ;
break ;
case RedumpSystem . AudioCD :
case RedumpSystem . DVDAudio :
case RedumpSystem . SuperAudioCD :
info . CommonDiscInfo . Category = info . CommonDiscInfo . Category ? ? DiscCategory . Audio ;
break ;
case RedumpSystem . BandaiPlaydiaQuickInteractiveSystem :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . BDVideo :
info . CommonDiscInfo . Category = info . CommonDiscInfo . Category ? ? DiscCategory . BonusDiscs ;
2022-02-01 13:04:28 -08:00
info . CopyProtection . Protection = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . CommodoreAmigaCD :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . CommodoreAmigaCD32 :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Europe ;
break ;
case RedumpSystem . CommodoreAmigaCDTV :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Europe ;
break ;
case RedumpSystem . DVDVideo :
info . CommonDiscInfo . Category = info . CommonDiscInfo . Category ? ? DiscCategory . BonusDiscs ;
break ;
case RedumpSystem . FujitsuFMTownsseries :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . FujitsuFMTownsMarty :
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . IncredibleTechnologiesEagle :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . KonamieAmusement :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . KonamiFireBeat :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . KonamiSystemGV :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . KonamiSystem573 :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . KonamiTwinkle :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . MattelHyperScan :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
2022-07-05 22:43:28 -07:00
case RedumpSystem . MicrosoftXboxOne :
string xboxOneMsxcPath = Path . Combine ( $"{drive.Letter}:\\" , "MSXC" ) ;
if ( drive ! = null & & Directory . Exists ( xboxOneMsxcPath ) )
2022-07-25 09:38:02 -07:00
{
info . CommonDiscInfo . CommentsSpecialFields [ SiteCode . Filename ] = string . Join ( "\n" ,
Directory . GetFiles ( xboxOneMsxcPath , "*" , SearchOption . TopDirectoryOnly ) . Select ( Path . GetFileName ) ) ;
}
2022-07-05 22:43:28 -07:00
break ;
2023-05-10 09:26:50 -04:00
case RedumpSystem . MicrosoftXboxSeriesXS :
string xboxSeriesXMsxcPath = Path . Combine ( $"{drive.Letter}:\\" , "MSXC" ) ;
if ( drive ! = null & & Directory . Exists ( xboxSeriesXMsxcPath ) )
{
info . CommonDiscInfo . CommentsSpecialFields [ SiteCode . Filename ] = string . Join ( "\n" ,
Directory . GetFiles ( xboxSeriesXMsxcPath , "*" , SearchOption . TopDirectoryOnly ) . Select ( Path . GetFileName ) ) ;
}
break ;
2021-09-29 16:16:54 -07:00
case RedumpSystem . NamcoSegaNintendoTriforce :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . NavisoftNaviken21 :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . NECPC88series :
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . NECPC98series :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . NECPCFXPCFXGA :
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . SegaChihiro :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . SegaDreamcast :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . SegaNaomi :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . SegaNaomi2 :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . SegaTitanVideo :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . SharpX68000 :
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . SNKNeoGeoCD :
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . EXEDateBuildDate = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . SonyPlayStation :
2022-01-01 20:42:52 -08:00
// Only check the disc if the dumping program couldn't detect
if ( drive ! = null & & info . CopyProtection . AntiModchip = = YesNo . NULL )
2021-10-03 13:53:23 -07:00
{
resultProgress ? . Report ( Result . Success ( "Checking for anti-modchip strings... this might take a while!" ) ) ;
info . CopyProtection . AntiModchip = await GetAntiModchipDetected ( drive ) ? YesNo . Yes : YesNo . No ;
resultProgress ? . Report ( Result . Success ( "Anti-modchip string scan complete!" ) ) ;
}
2021-09-29 16:16:54 -07:00
// Special case for DIC only
if ( parameters . InternalProgram = = InternalProgram . DiscImageCreator )
{
resultProgress ? . Report ( Result . Success ( "Checking for LibCrypt status... this might take a while!" ) ) ;
GetLibCryptDetected ( info , combinedBase ) ;
resultProgress ? . Report ( Result . Success ( "LibCrypt status checking complete!" ) ) ;
}
break ;
case RedumpSystem . SonyPlayStation2 :
info . CommonDiscInfo . LanguageSelection = new LanguageSelection ? [ ] { LanguageSelection . BiosSettings , LanguageSelection . LanguageSelector , LanguageSelection . OptionsMenu } ;
break ;
case RedumpSystem . SonyPlayStation3 :
2022-02-01 13:04:28 -08:00
info . Extras . DiscKey = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
info . Extras . DiscID = options . AddPlaceholders ? Template . RequiredValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
case RedumpSystem . TomyKissSite :
info . CommonDiscInfo . Region = info . CommonDiscInfo . Region ? ? Region . Japan ;
break ;
case RedumpSystem . ZAPiTGamesGameWaveFamilyEntertainmentSystem :
2022-02-01 13:04:28 -08:00
info . CopyProtection . Protection = options . AddPlaceholders ? Template . RequiredIfExistsValue : string . Empty ;
2021-09-29 16:16:54 -07:00
break ;
}
// Set the category if it's not overriden
info . CommonDiscInfo . Category = info . CommonDiscInfo . Category ? ? DiscCategory . Games ;
2021-12-30 15:23:17 -08:00
// Comments and contents have odd handling
2021-09-29 16:16:54 -07:00
if ( string . IsNullOrEmpty ( info . CommonDiscInfo . Comments ) )
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . Comments = options . AddPlaceholders ? Template . OptionalValue : string . Empty ;
2021-12-30 15:23:17 -08:00
if ( string . IsNullOrEmpty ( info . CommonDiscInfo . Contents ) )
2022-02-01 13:04:28 -08:00
info . CommonDiscInfo . Contents = options . AddPlaceholders ? Template . OptionalValue : string . Empty ;
2021-09-29 16:16:54 -07:00
2022-04-04 21:46:13 -07:00
// Normalize the disc type with all current information
NormalizeDiscType ( info ) ;
2021-09-29 16:16:54 -07:00
return info ;
}
/// <summary>
/// Ensures that all required output files have been created
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="outputFilename">Output filename to use as the base path</param>
/// <param name="parameters">Parameters object representing what to send to the internal program</param>
/// <param name="preCheck">True if this is a check done before a dump, false if done after</param>
/// <returns>Tuple of true if all required files exist, false otherwise and a list representing missing files</returns>
public static ( bool , List < string > ) FoundAllFiles ( string outputDirectory , string outputFilename , BaseParameters parameters , bool preCheck )
{
// First, sanitized the output filename to strip off any potential extension
outputFilename = Path . GetFileNameWithoutExtension ( outputFilename ) ;
// Then get the base path for all checking
string basePath = Path . Combine ( outputDirectory , outputFilename ) ;
// Finally, let the parameters say if all files exist
return parameters . CheckAllOutputFilesExist ( basePath , preCheck ) ;
}
/// <summary>
2021-11-26 14:06:57 -08:00
/// Get the existence of an anti-modchip string from a PlayStation disc, if possible
2021-09-29 16:16:54 -07:00
/// </summary>
/// <param name="drive">Drive object representing the current drive</param>
/// <returns>Anti-modchip existence if possible, false on error</returns>
private static async Task < bool > GetAntiModchipDetected ( Drive drive )
= > await Protection . GetPlayStationAntiModchipDetected ( $"{drive.Letter}:\\" ) ;
/// <summary>
2022-04-04 22:44:15 -07:00
/// Get the current detected copy protection(s), if possible
2021-09-29 16:16:54 -07:00
/// </summary>
/// <param name="drive">Drive object representing the current drive</param>
/// <param name="options">Options object that determines what to scan</param>
/// <param name="progress">Optional progress callback</param>
2022-04-04 22:44:15 -07:00
/// <returns>Detected copy protection(s) if possible, null on error</returns>
2023-07-14 12:07:44 -04:00
private static async Task < ( string , Dictionary < string , List < string > > ) > GetCopyProtection ( Drive drive , Core . Data . Options options , IProgress < ProtectionProgress > progress = null )
2021-09-29 16:16:54 -07:00
{
2021-10-03 13:53:23 -07:00
if ( options . ScanForProtection & & drive ! = null )
2021-09-29 16:16:54 -07:00
{
2022-04-04 22:06:56 -07:00
( var protection , string _ ) = await Protection . RunProtectionScanOnPath ( $"{drive.Letter}:\\" , options , progress ) ;
2022-04-04 22:44:15 -07:00
return ( Protection . FormatProtections ( protection ) , protection ) ;
2021-09-29 16:16:54 -07:00
}
2022-04-04 22:44:15 -07:00
return ( "(CHECK WITH PROTECTIONID)" , null ) ;
2021-09-29 16:16:54 -07:00
}
/// <summary>
/// Get the full lines from the input file, if possible
/// </summary>
/// <param name="filename">file location</param>
/// <param name="binary">True if should read as binary, false otherwise (default)</param>
/// <returns>Full text of the file, null on error</returns>
private static string GetFullFile ( string filename , bool binary = false )
{
// If the file doesn't exist, we can't get info from it
if ( ! File . Exists ( filename ) )
return null ;
// If we're reading as binary
if ( binary )
{
byte [ ] bytes = File . ReadAllBytes ( filename ) ;
return BitConverter . ToString ( bytes ) . Replace ( "-" , string . Empty ) ;
}
2023-01-20 21:41:05 -08:00
return File . ReadAllText ( filename ) ;
2021-09-29 16:16:54 -07:00
}
/// <summary>
/// Get the split values for ISO-based media
/// </summary>
/// <param name="hashData">String representing the combined hash data</param>
/// <returns>True if extraction was successful, false otherwise</returns>
private static bool GetISOHashValues ( string hashData , out long size , out string crc32 , out string md5 , out string sha1 )
{
size = - 1 ; crc32 = null ; md5 = null ; sha1 = null ;
if ( string . IsNullOrWhiteSpace ( hashData ) )
return false ;
Regex hashreg = new Regex ( @"<rom name="".*?"" size=""(.*?)"" crc=""(.*?)"" md5=""(.*?)"" sha1=""(.*?)""" ) ;
Match m = hashreg . Match ( hashData ) ;
if ( m . Success )
{
Int64 . TryParse ( m . Groups [ 1 ] . Value , out size ) ;
crc32 = m . Groups [ 2 ] . Value ;
md5 = m . Groups [ 3 ] . Value ;
sha1 = m . Groups [ 4 ] . Value ;
return true ;
}
else
{
return false ;
}
}
/// <summary>
/// Get if LibCrypt data is detected in the subchannel file, if possible
/// </summary>
/// <param name="info">Base submission info to fill in specifics for</param>
/// <param name="basePath">Base filename and path to use for checking</param>
/// <returns>Status of the LibCrypt data, if possible</returns>
private static void GetLibCryptDetected ( SubmissionInfo info , string basePath )
{
bool? psLibCryptStatus = Protection . GetLibCryptDetected ( basePath + ".sub" ) ;
if ( psLibCryptStatus = = true )
{
// Guard against false positives
if ( File . Exists ( basePath + "_subIntention.txt" ) )
{
string libCryptData = GetFullFile ( basePath + "_subIntention.txt" ) ? ? "" ;
if ( string . IsNullOrEmpty ( libCryptData ) )
{
info . CopyProtection . LibCrypt = YesNo . No ;
}
else
{
info . CopyProtection . LibCrypt = YesNo . Yes ;
info . CopyProtection . LibCryptData = libCryptData ;
}
}
else
{
info . CopyProtection . LibCrypt = YesNo . No ;
}
}
else if ( psLibCryptStatus = = false )
{
info . CopyProtection . LibCrypt = YesNo . No ;
}
else
{
info . CopyProtection . LibCrypt = YesNo . NULL ;
info . CopyProtection . LibCryptData = "LibCrypt could not be detected because subchannel file is missing" ;
}
}
2023-09-05 00:08:09 -04:00
#endregion
2021-09-29 16:16:54 -07:00
2022-10-04 13:36:50 -07:00
#region Information Output
2021-09-29 16:16:54 -07:00
/// <summary>
/// Compress log files to save space
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="outputFilename">Output filename to use as the base path</param>
/// <param name="parameters">Parameters object to use to derive log file paths</param>
/// <returns>True if the process succeeded, false otherwise</returns>
2022-10-17 15:41:00 -07:00
public static ( bool , string ) CompressLogFiles ( string outputDirectory , string outputFilename , BaseParameters parameters )
2021-09-29 16:16:54 -07:00
{
// Prepare the necessary paths
outputFilename = Path . GetFileNameWithoutExtension ( outputFilename ) ;
string combinedBase = Path . Combine ( outputDirectory , outputFilename ) ;
string archiveName = combinedBase + "_logs.zip" ;
// Get the list of log files from the parameters object
var files = parameters . GetLogFilePaths ( combinedBase ) ;
2022-10-11 12:54:11 -07:00
// Add on generated log files if they exist
2022-10-11 13:19:06 -07:00
var mpfFiles = GetGeneratedFilePaths ( outputDirectory ) ;
files . AddRange ( mpfFiles ) ;
2022-10-11 12:54:11 -07:00
2021-09-29 16:16:54 -07:00
if ( ! files . Any ( ) )
2022-10-17 15:41:00 -07:00
return ( true , "No files to compress!" ) ;
2021-09-29 16:16:54 -07:00
2022-01-26 09:51:37 -08:00
// If the file already exists, we want to delete the old one
try
{
if ( File . Exists ( archiveName ) )
File . Delete ( archiveName ) ;
}
catch
{
2022-10-17 15:41:00 -07:00
return ( false , "Could not delete old archive!" ) ;
2022-01-26 09:51:37 -08:00
}
2021-09-29 16:16:54 -07:00
// Add the log files to the archive and delete the uncompressed file after
ZipArchive zf = null ;
try
{
zf = ZipFile . Open ( archiveName , ZipArchiveMode . Create ) ;
foreach ( string file in files )
{
string entryName = file . Substring ( outputDirectory . Length ) . TrimStart ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ;
2023-07-17 13:13:16 -04:00
#if NET48 | | NETSTANDARD2_1
zf . CreateEntryFromFile ( file , entryName , CompressionLevel . Optimal ) ;
#else
zf . CreateEntryFromFile ( file , entryName , CompressionLevel . SmallestSize ) ;
#endif
2021-09-29 16:16:54 -07:00
2022-10-11 13:19:06 -07:00
// If the file is MPF-specific, don't delete
if ( mpfFiles . Contains ( file ) )
continue ;
2021-09-29 16:16:54 -07:00
try
{
File . Delete ( file ) ;
}
catch { }
}
2022-10-17 15:41:00 -07:00
return ( true , "Compression complete!" ) ;
2021-09-29 16:16:54 -07:00
}
2022-10-17 15:41:00 -07:00
catch ( Exception ex )
2021-09-29 16:16:54 -07:00
{
2022-10-17 15:41:00 -07:00
return ( false , $"Compression could not complete: {ex}" ) ;
2021-09-29 16:16:54 -07:00
}
finally
{
zf ? . Dispose ( ) ;
}
}
/// <summary>
/// Format the output data in a human readable way, separating each printed line into a new item in the list
/// </summary>
/// <param name="info">Information object that should contain normalized values</param>
2022-10-21 11:06:27 -07:00
/// <param name="options">Options object representing user-defined options</param>
2021-09-29 16:16:54 -07:00
/// <returns>List of strings representing each line of an output file, null on error</returns>
2023-07-14 12:07:44 -04:00
public static ( List < string > , string ) FormatOutputData ( SubmissionInfo info , Core . Data . Options options )
2021-09-29 16:16:54 -07:00
{
// Check to see if the inputs are valid
if ( info = = null )
2022-10-20 12:45:01 -07:00
return ( null , "Submission information was missing" ) ;
2021-09-29 16:16:54 -07:00
try
{
// Sony-printed discs have layers in the opposite order
var system = info . CommonDiscInfo . System ;
2021-11-25 22:38:59 -08:00
bool reverseOrder = system . HasReversedRingcodes ( ) ;
2021-09-29 16:16:54 -07:00
// Common Disc Info section
List < string > output = new List < string > { "Common Disc Info:" } ;
AddIfExists ( output , Template . TitleField , info . CommonDiscInfo . Title , 1 ) ;
AddIfExists ( output , Template . ForeignTitleField , info . CommonDiscInfo . ForeignTitleNonLatin , 1 ) ;
AddIfExists ( output , Template . DiscNumberField , info . CommonDiscInfo . DiscNumberLetter , 1 ) ;
AddIfExists ( output , Template . DiscTitleField , info . CommonDiscInfo . DiscTitle , 1 ) ;
AddIfExists ( output , Template . SystemField , info . CommonDiscInfo . System . LongName ( ) , 1 ) ;
AddIfExists ( output , Template . MediaTypeField , GetFixedMediaType (
info . CommonDiscInfo . Media . ToMediaType ( ) ,
2023-04-26 16:00:20 -04:00
info . SizeAndChecksums . PICIdentifier ,
2021-09-29 16:16:54 -07:00
info . SizeAndChecksums . Size ,
info . SizeAndChecksums . Layerbreak ,
info . SizeAndChecksums . Layerbreak2 ,
info . SizeAndChecksums . Layerbreak3 ) ,
1 ) ;
AddIfExists ( output , Template . CategoryField , info . CommonDiscInfo . Category . LongName ( ) , 1 ) ;
2022-03-12 21:27:23 -08:00
AddIfExists ( output , Template . FullyMatchingIDField , info . FullyMatchedID ? . ToString ( ) , 1 ) ;
AddIfExists ( output , Template . PartiallyMatchingIDsField , info . PartiallyMatchedIDs , 1 ) ;
2021-09-29 16:16:54 -07:00
AddIfExists ( output , Template . RegionField , info . CommonDiscInfo . Region . LongName ( ) ? ? "SPACE! (CHANGE THIS)" , 1 ) ;
2022-01-26 21:38:11 -08:00
AddIfExists ( output , Template . LanguagesField , ( info . CommonDiscInfo . Languages ? ? new Language ? [ ] { null } ) . Select ( l = > l . LongName ( ) ? ? "SILENCE! (CHANGE THIS)" ) . ToArray ( ) , 1 ) ;
2021-09-29 16:16:54 -07:00
AddIfExists ( output , Template . PlaystationLanguageSelectionViaField , ( info . CommonDiscInfo . LanguageSelection ? ? new LanguageSelection ? [ ] { } ) . Select ( l = > l . LongName ( ) ) . ToArray ( ) , 1 ) ;
AddIfExists ( output , Template . DiscSerialField , info . CommonDiscInfo . Serial , 1 ) ;
// All ringcode information goes in an indented area
2023-04-24 14:42:13 -04:00
output . Add ( "" ) ; output . Add ( "\tRingcode Information:" ) ; output . Add ( "" ) ;
2021-09-29 16:16:54 -07:00
// If we have a triple-layer disc
if ( info . SizeAndChecksums . Layerbreak3 ! = default )
{
2023-04-24 14:42:13 -04:00
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . MasteringRingField , info . CommonDiscInfo . Layer0MasteringRing , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . MasteringSIDField , info . CommonDiscInfo . Layer0MasteringSID , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . ToolstampField , info . CommonDiscInfo . Layer0ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , "Data Side " + Template . MouldSIDField , info . CommonDiscInfo . Layer0MouldSID , 0 ) ;
AddIfExists ( output , "Data Side " + Template . AdditionalMouldField , info . CommonDiscInfo . Layer0AdditionalMould , 0 ) ;
AddIfExists ( output , "Layer 1 " + Template . MasteringRingField , info . CommonDiscInfo . Layer1MasteringRing , 0 ) ;
AddIfExists ( output , "Layer 1 " + Template . MasteringSIDField , info . CommonDiscInfo . Layer1MasteringSID , 0 ) ;
AddIfExists ( output , "Layer 1 " + Template . ToolstampField , info . CommonDiscInfo . Layer1ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , "Label Side " + Template . MouldSIDField , info . CommonDiscInfo . Layer1MouldSID , 0 ) ;
AddIfExists ( output , "Label Side " + Template . AdditionalMouldField , info . CommonDiscInfo . Layer1AdditionalMould , 0 ) ;
AddIfExists ( output , "Layer 2 " + Template . MasteringRingField , info . CommonDiscInfo . Layer2MasteringRing , 0 ) ;
AddIfExists ( output , "Layer 2 " + Template . MasteringSIDField , info . CommonDiscInfo . Layer2MasteringSID , 0 ) ;
AddIfExists ( output , "Layer 2 " + Template . ToolstampField , info . CommonDiscInfo . Layer2ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) " ) + Template . MasteringRingField , info . CommonDiscInfo . Layer3MasteringRing , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) " ) + Template . MasteringSIDField , info . CommonDiscInfo . Layer3MasteringSID , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) " ) + Template . ToolstampField , info . CommonDiscInfo . Layer3ToolstampMasteringCode , 0 ) ;
2021-09-29 16:16:54 -07:00
}
// If we have a triple-layer disc
else if ( info . SizeAndChecksums . Layerbreak2 ! = default )
{
2023-04-24 14:42:13 -04:00
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . MasteringRingField , info . CommonDiscInfo . Layer0MasteringRing , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . MasteringSIDField , info . CommonDiscInfo . Layer0MasteringSID , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . ToolstampField , info . CommonDiscInfo . Layer0ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , "Data Side " + Template . MouldSIDField , info . CommonDiscInfo . Layer0MouldSID , 0 ) ;
AddIfExists ( output , "Data Side " + Template . AdditionalMouldField , info . CommonDiscInfo . Layer0AdditionalMould , 0 ) ;
AddIfExists ( output , "Layer 1 " + Template . MasteringRingField , info . CommonDiscInfo . Layer1MasteringRing , 0 ) ;
AddIfExists ( output , "Layer 1 " + Template . MasteringSIDField , info . CommonDiscInfo . Layer1MasteringSID , 0 ) ;
AddIfExists ( output , "Layer 1 " + Template . ToolstampField , info . CommonDiscInfo . Layer1ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , "Label Side " + Template . MouldSIDField , info . CommonDiscInfo . Layer1MouldSID , 0 ) ;
AddIfExists ( output , "Label Side " + Template . AdditionalMouldField , info . CommonDiscInfo . Layer1AdditionalMould , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) " ) + Template . MasteringRingField , info . CommonDiscInfo . Layer2MasteringRing , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) " ) + Template . MasteringSIDField , info . CommonDiscInfo . Layer2MasteringSID , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) " ) + Template . ToolstampField , info . CommonDiscInfo . Layer2ToolstampMasteringCode , 0 ) ;
2021-09-29 16:16:54 -07:00
}
// If we have a dual-layer disc
else if ( info . SizeAndChecksums . Layerbreak ! = default )
{
2023-04-24 14:42:13 -04:00
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . MasteringRingField , info . CommonDiscInfo . Layer0MasteringRing , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . MasteringSIDField , info . CommonDiscInfo . Layer0MasteringSID , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) " ) + Template . ToolstampField , info . CommonDiscInfo . Layer0ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , "Data Side " + Template . MouldSIDField , info . CommonDiscInfo . Layer0MouldSID , 0 ) ;
AddIfExists ( output , "Data Side " + Template . AdditionalMouldField , info . CommonDiscInfo . Layer0AdditionalMould , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) " ) + Template . MasteringRingField , info . CommonDiscInfo . Layer1MasteringRing , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) " ) + Template . MasteringSIDField , info . CommonDiscInfo . Layer1MasteringSID , 0 ) ;
AddIfExists ( output , ( reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) " ) + Template . ToolstampField , info . CommonDiscInfo . Layer1ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , "Label Side " + Template . MouldSIDField , info . CommonDiscInfo . Layer1MouldSID , 0 ) ;
AddIfExists ( output , "Label Side " + Template . AdditionalMouldField , info . CommonDiscInfo . Layer1AdditionalMould , 0 ) ;
2021-09-29 16:16:54 -07:00
}
// If we have a single-layer disc
else
{
2023-04-24 14:42:13 -04:00
AddIfExists ( output , "Data Side " + Template . MasteringRingField , info . CommonDiscInfo . Layer0MasteringRing , 0 ) ;
AddIfExists ( output , "Data Side " + Template . MasteringSIDField , info . CommonDiscInfo . Layer0MasteringSID , 0 ) ;
AddIfExists ( output , "Data Side " + Template . ToolstampField , info . CommonDiscInfo . Layer0ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , "Data Side " + Template . MouldSIDField , info . CommonDiscInfo . Layer0MouldSID , 0 ) ;
AddIfExists ( output , "Data Side " + Template . AdditionalMouldField , info . CommonDiscInfo . Layer0AdditionalMould , 0 ) ;
AddIfExists ( output , "Label Side " + Template . MasteringRingField , info . CommonDiscInfo . Layer1MasteringRing , 0 ) ;
AddIfExists ( output , "Label Side " + Template . MasteringSIDField , info . CommonDiscInfo . Layer1MasteringSID , 0 ) ;
AddIfExists ( output , "Label Side " + Template . ToolstampField , info . CommonDiscInfo . Layer1ToolstampMasteringCode , 0 ) ;
AddIfExists ( output , "Label Side " + Template . MouldSIDField , info . CommonDiscInfo . Layer1MouldSID , 0 ) ;
AddIfExists ( output , "Label Side " + Template . AdditionalMouldField , info . CommonDiscInfo . Layer1AdditionalMould , 0 ) ;
2021-09-29 16:16:54 -07:00
}
2023-04-24 14:42:13 -04:00
output . Add ( "" ) ;
2021-09-29 16:16:54 -07:00
AddIfExists ( output , Template . BarcodeField , info . CommonDiscInfo . Barcode , 1 ) ;
AddIfExists ( output , Template . EXEDateBuildDate , info . CommonDiscInfo . EXEDateBuildDate , 1 ) ;
AddIfExists ( output , Template . ErrorCountField , info . CommonDiscInfo . ErrorsCount , 1 ) ;
AddIfExists ( output , Template . CommentsField , info . CommonDiscInfo . Comments . Trim ( ) , 1 ) ;
AddIfExists ( output , Template . ContentsField , info . CommonDiscInfo . Contents . Trim ( ) , 1 ) ;
// Version and Editions section
output . Add ( "" ) ; output . Add ( "Version and Editions:" ) ;
AddIfExists ( output , Template . VersionField , info . VersionAndEditions . Version , 1 ) ;
AddIfExists ( output , Template . EditionField , info . VersionAndEditions . OtherEditions , 1 ) ;
// EDC section
if ( info . CommonDiscInfo . System = = RedumpSystem . SonyPlayStation )
{
output . Add ( "" ) ; output . Add ( "EDC:" ) ;
AddIfExists ( output , Template . PlayStationEDCField , info . EDC . EDC . LongName ( ) , 1 ) ;
}
// Parent/Clone Relationship section
// output.Add(""); output.Add("Parent/Clone Relationship:");
// AddIfExists(output, Template.ParentIDField, info.ParentID);
// AddIfExists(output, Template.RegionalParentField, info.RegionalParent.ToString());
// Extras section
2022-12-15 22:14:31 -08:00
if ( info . Extras . PVD ! = null | | info . Extras . PIC ! = null | | info . Extras . BCA ! = null | | info . Extras . SecuritySectorRanges ! = null )
2021-09-29 16:16:54 -07:00
{
output . Add ( "" ) ; output . Add ( "Extras:" ) ;
AddIfExists ( output , Template . PVDField , info . Extras . PVD ? . Trim ( ) , 1 ) ;
AddIfExists ( output , Template . PlayStation3WiiDiscKeyField , info . Extras . DiscKey , 1 ) ;
AddIfExists ( output , Template . PlayStation3DiscIDField , info . Extras . DiscID , 1 ) ;
AddIfExists ( output , Template . PICField , info . Extras . PIC , 1 ) ;
AddIfExists ( output , Template . HeaderField , info . Extras . Header , 1 ) ;
AddIfExists ( output , Template . GameCubeWiiBCAField , info . Extras . BCA , 1 ) ;
AddIfExists ( output , Template . XBOXSSRanges , info . Extras . SecuritySectorRanges , 1 ) ;
}
// Copy Protection section
2022-04-04 22:25:07 -07:00
if ( ! string . IsNullOrWhiteSpace ( info . CopyProtection . Protection )
2022-04-07 20:56:28 -07:00
| | ( info . CopyProtection . AntiModchip ! = null & & info . CopyProtection . AntiModchip ! = YesNo . NULL )
| | ( info . CopyProtection . LibCrypt ! = null & & info . CopyProtection . LibCrypt ! = YesNo . NULL )
2022-04-04 22:25:07 -07:00
| | ! string . IsNullOrWhiteSpace ( info . CopyProtection . LibCryptData )
| | ! string . IsNullOrWhiteSpace ( info . CopyProtection . SecuROMData ) )
2021-09-29 16:16:54 -07:00
{
output . Add ( "" ) ; output . Add ( "Copy Protection:" ) ;
if ( info . CommonDiscInfo . System = = RedumpSystem . SonyPlayStation )
{
AddIfExists ( output , Template . PlayStationAntiModchipField , info . CopyProtection . AntiModchip . LongName ( ) , 1 ) ;
AddIfExists ( output , Template . PlayStationLibCryptField , info . CopyProtection . LibCrypt . LongName ( ) , 1 ) ;
AddIfExists ( output , Template . SubIntentionField , info . CopyProtection . LibCryptData , 1 ) ;
}
AddIfExists ( output , Template . CopyProtectionField , info . CopyProtection . Protection , 1 ) ;
AddIfExists ( output , Template . SubIntentionField , info . CopyProtection . SecuROMData , 1 ) ;
}
// Dumpers and Status section
// output.Add(""); output.Add("Dumpers and Status");
// AddIfExists(output, Template.StatusField, info.Status.Name());
// AddIfExists(output, Template.OtherDumpersField, info.OtherDumpers);
// Tracks and Write Offsets section
if ( ! string . IsNullOrWhiteSpace ( info . TracksAndWriteOffsets . ClrMameProData ) )
{
output . Add ( "" ) ; output . Add ( "Tracks and Write Offsets:" ) ;
AddIfExists ( output , Template . DATField , info . TracksAndWriteOffsets . ClrMameProData + "\n" , 1 ) ;
AddIfExists ( output , Template . CuesheetField , info . TracksAndWriteOffsets . Cuesheet , 1 ) ;
2022-10-18 11:49:57 -07:00
string offset = info . TracksAndWriteOffsets . OtherWriteOffsets ;
2022-10-26 13:07:51 +09:00
if ( Int32 . TryParse ( offset , out int i ) )
offset = i . ToString ( "+#;-#;0" ) ;
2022-10-18 11:49:57 -07:00
AddIfExists ( output , Template . WriteOffsetField , offset , 1 ) ;
2021-09-29 16:16:54 -07:00
}
// Size & Checksum section
else
{
output . Add ( "" ) ; output . Add ( "Size & Checksum:" ) ;
2022-10-21 11:06:27 -07:00
// Gross hack because of automatic layerbreaks in Redump
2022-12-19 12:38:18 -08:00
if ( ! options . EnableRedumpCompatibility
| | ( info . CommonDiscInfo . Media . ToMediaType ( ) ! = MediaType . BluRay
& & ! info . CommonDiscInfo . System . IsXGD ( ) ) )
2022-10-21 11:06:27 -07:00
{
AddIfExists ( output , Template . LayerbreakField , ( info . SizeAndChecksums . Layerbreak = = default ? null : info . SizeAndChecksums . Layerbreak . ToString ( ) ) , 1 ) ;
}
2021-09-29 16:16:54 -07:00
AddIfExists ( output , Template . SizeField , info . SizeAndChecksums . Size . ToString ( ) , 1 ) ;
AddIfExists ( output , Template . CRC32Field , info . SizeAndChecksums . CRC32 , 1 ) ;
AddIfExists ( output , Template . MD5Field , info . SizeAndChecksums . MD5 , 1 ) ;
AddIfExists ( output , Template . SHA1Field , info . SizeAndChecksums . SHA1 , 1 ) ;
}
2022-10-17 20:56:28 -07:00
// Dumping Info section
output . Add ( "" ) ; output . Add ( "Dumping Info:" ) ;
AddIfExists ( output , Template . DumpingProgramField , info . DumpingInfo . DumpingProgram , 1 ) ;
2023-08-15 00:47:32 -04:00
AddIfExists ( output , Template . DumpingDateField , info . DumpingInfo . DumpingDate , 1 ) ;
2022-10-17 20:56:28 -07:00
AddIfExists ( output , Template . DumpingDriveManufacturer , info . DumpingInfo . Manufacturer , 1 ) ;
AddIfExists ( output , Template . DumpingDriveModel , info . DumpingInfo . Model , 1 ) ;
AddIfExists ( output , Template . DumpingDriveFirmware , info . DumpingInfo . Firmware , 1 ) ;
2022-10-19 12:34:40 -07:00
AddIfExists ( output , Template . ReportedDiscType , info . DumpingInfo . ReportedDiscType , 1 ) ;
2022-10-17 20:56:28 -07:00
2021-09-29 16:16:54 -07:00
// Make sure there aren't any instances of two blank lines in a row
string last = null ;
for ( int i = 0 ; i < output . Count ; )
{
if ( output [ i ] = = last & & string . IsNullOrWhiteSpace ( last ) )
{
output . RemoveAt ( i ) ;
}
else
{
last = output [ i ] ;
i + + ;
}
}
2022-10-20 12:45:01 -07:00
return ( output , "Formatting complete!" ) ;
2021-09-29 16:16:54 -07:00
}
2022-10-20 12:45:01 -07:00
catch ( Exception ex )
2021-09-29 16:16:54 -07:00
{
2022-10-20 12:45:01 -07:00
return ( null , $"Error formatting submission info: {ex}" ) ;
2021-09-29 16:16:54 -07:00
}
}
2021-11-26 14:06:57 -08:00
/// <summary>
/// Get the adjusted name of the media based on layers, if applicable
/// </summary>
/// <param name="mediaType">MediaType to get the proper name for</param>
2023-04-26 16:00:20 -04:00
/// <param name="picIdentifier">PIC identifier string (BD only)</param>
2021-11-26 14:06:57 -08:00
/// <param name="size">Size of the current media</param>
/// <param name="layerbreak">First layerbreak value, as applicable</param>
/// <param name="layerbreak2">Second layerbreak value, as applicable</param>
2022-04-04 21:46:13 -07:00
/// <param name="layerbreak3">Third layerbreak value, as applicable</param>
2021-11-26 14:06:57 -08:00
/// <returns>String representation of the media, including layer specification</returns>
2023-04-26 16:00:20 -04:00
/// TODO: Figure out why we have this and NormalizeDiscType as well
public static string GetFixedMediaType ( MediaType ? mediaType , string picIdentifier , long size , long layerbreak , long layerbreak2 , long layerbreak3 )
2021-11-26 14:06:57 -08:00
{
switch ( mediaType )
{
case MediaType . DVD :
if ( layerbreak ! = default )
return $"{mediaType.LongName()}-9" ;
else
return $"{mediaType.LongName()}-5" ;
case MediaType . BluRay :
if ( layerbreak3 ! = default )
return $"{mediaType.LongName()}-128" ;
else if ( layerbreak2 ! = default )
return $"{mediaType.LongName()}-100" ;
2023-04-26 16:00:20 -04:00
else if ( layerbreak ! = default & & picIdentifier = = PICDiscInformationUnit . DiscTypeIdentifierROMUltra )
return $"{mediaType.LongName()}-66" ;
else if ( layerbreak ! = default & & size > 53_687_063_712 )
return $"{mediaType.LongName()}-66" ;
2021-11-26 14:06:57 -08:00
else if ( layerbreak ! = default )
return $"{mediaType.LongName()}-50" ;
2023-04-26 16:00:20 -04:00
else if ( picIdentifier = = PICDiscInformationUnit . DiscTypeIdentifierROMUltra )
return $"{mediaType.LongName()}-33" ;
else if ( size > 26_843_531_856 )
return $"{mediaType.LongName()}-33" ;
2021-11-26 14:06:57 -08:00
else
return $"{mediaType.LongName()}-25" ;
case MediaType . UMD :
if ( layerbreak ! = default )
return $"{mediaType.LongName()}-DL" ;
else
return $"{mediaType.LongName()}-SL" ;
default :
return mediaType . LongName ( ) ;
}
}
2021-12-30 13:00:55 -08:00
/// <summary>
/// Process any fields that have to be combined
/// </summary>
/// <param name="info">Information object to normalize</param>
public static void ProcessSpecialFields ( SubmissionInfo info )
{
// Process the comments field
if ( info . CommonDiscInfo ? . CommentsSpecialFields ! = null & & info . CommonDiscInfo . CommentsSpecialFields ? . Any ( ) = = true )
{
// If the field is missing, add an empty one to fill in
if ( info . CommonDiscInfo . Comments = = null )
info . CommonDiscInfo . Comments = string . Empty ;
// Add all special fields before any comments
info . CommonDiscInfo . Comments = string . Join (
2022-01-27 13:35:47 -08:00
"\n" , OrderCommentTags ( info . CommonDiscInfo . CommentsSpecialFields )
2021-12-30 14:52:49 -08:00
. Where ( kvp = > ! string . IsNullOrWhiteSpace ( kvp . Value ) )
2022-01-27 10:51:27 -08:00
. Select ( FormatSiteTag )
2022-01-07 12:49:10 -08:00
. Where ( s = > ! string . IsNullOrEmpty ( s ) )
2021-12-30 13:00:55 -08:00
) + "\n" + info . CommonDiscInfo . Comments ;
2022-04-04 21:12:50 -07:00
// Normalize newlines
info . CommonDiscInfo . Comments = info . CommonDiscInfo . Comments . Replace ( "\r\n" , "\n" ) ;
2021-12-30 13:00:55 -08:00
// Trim the comments field
info . CommonDiscInfo . Comments = info . CommonDiscInfo . Comments . Trim ( ) ;
// Wipe out the special fields dictionary
info . CommonDiscInfo . CommentsSpecialFields = null ;
}
// Process the contents field
if ( info . CommonDiscInfo ? . ContentsSpecialFields ! = null & & info . CommonDiscInfo . ContentsSpecialFields ? . Any ( ) = = true )
{
// If the field is missing, add an empty one to fill in
if ( info . CommonDiscInfo . Contents = = null )
info . CommonDiscInfo . Contents = string . Empty ;
// Add all special fields before any contents
info . CommonDiscInfo . Contents = string . Join (
2022-01-27 12:13:17 -08:00
"\n" , OrderContentTags ( info . CommonDiscInfo . ContentsSpecialFields )
2021-12-30 14:52:49 -08:00
. Where ( kvp = > ! string . IsNullOrWhiteSpace ( kvp . Value ) )
2022-01-27 10:51:27 -08:00
. Select ( FormatSiteTag )
2022-01-07 12:49:10 -08:00
. Where ( s = > ! string . IsNullOrEmpty ( s ) )
2021-12-30 13:00:55 -08:00
) + "\n" + info . CommonDiscInfo . Contents ;
2022-04-04 21:12:50 -07:00
// Normalize newlines
info . CommonDiscInfo . Contents = info . CommonDiscInfo . Contents . Replace ( "\r\n" , "\n" ) ;
2021-12-30 13:00:55 -08:00
// Trim the contents field
info . CommonDiscInfo . Contents = info . CommonDiscInfo . Contents . Trim ( ) ;
// Wipe out the special fields dictionary
info . CommonDiscInfo . ContentsSpecialFields = null ;
}
}
2021-09-29 16:16:54 -07:00
/// <summary>
/// Write the data to the output folder
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="lines">Preformatted list of lines to write out to the file</param>
/// <returns>True on success, false on error</returns>
2022-10-20 12:23:35 -07:00
public static ( bool , string ) WriteOutputData ( string outputDirectory , List < string > lines )
2021-09-29 16:16:54 -07:00
{
// Check to see if the inputs are valid
if ( lines = = null )
2022-10-20 12:23:35 -07:00
return ( false , "No formatted data found to write!" ) ;
2021-09-29 16:16:54 -07:00
// Now write out to a generic file
try
{
using ( StreamWriter sw = new StreamWriter ( File . Open ( Path . Combine ( outputDirectory , "!submissionInfo.txt" ) , FileMode . Create , FileAccess . Write ) ) )
{
foreach ( string line in lines )
2022-10-20 12:23:35 -07:00
{
2021-09-29 16:16:54 -07:00
sw . WriteLine ( line ) ;
2022-10-20 12:23:35 -07:00
}
2021-09-29 16:16:54 -07:00
}
}
2021-11-01 12:02:07 -07:00
catch ( Exception ex )
2021-09-29 16:16:54 -07:00
{
2022-10-20 12:23:35 -07:00
return ( false , $"Writing could not complete: {ex}" ) ;
2021-09-29 16:16:54 -07:00
}
2022-10-20 12:23:35 -07:00
return ( true , "Writing complete!" ) ;
2021-09-29 16:16:54 -07:00
}
/// <summary>
/// Write the data to the output folder
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="info">SubmissionInfo object representing the JSON to write out to the file</param>
2022-04-04 22:15:50 -07:00
/// <param name="includedArtifacts">True if artifacts were included, false otherwise</param>
2021-09-29 16:16:54 -07:00
/// <returns>True on success, false on error</returns>
2022-04-04 22:15:50 -07:00
public static bool WriteOutputData ( string outputDirectory , SubmissionInfo info , bool includedArtifacts )
2021-09-29 16:16:54 -07:00
{
// Check to see if the input is valid
if ( info = = null )
return false ;
try
{
2022-04-04 22:15:50 -07:00
// Serialize the JSON and get it writable
string json = JsonConvert . SerializeObject ( info , Formatting . Indented ) ;
byte [ ] jsonBytes = Encoding . UTF8 . GetBytes ( json ) ;
// If we included artifacts, write to a GZip-compressed file
if ( includedArtifacts )
2021-09-29 16:16:54 -07:00
{
2022-04-04 22:15:50 -07:00
using ( var fs = File . Create ( Path . Combine ( outputDirectory , "!submissionInfo.json.gz" ) ) )
using ( var gs = new GZipStream ( fs , CompressionMode . Compress ) )
{
gs . Write ( jsonBytes , 0 , jsonBytes . Length ) ;
}
}
// Otherwise, write out to a normal JSON
else
{
using ( var fs = File . Create ( Path . Combine ( outputDirectory , "!submissionInfo.json" ) ) )
{
fs . Write ( jsonBytes , 0 , jsonBytes . Length ) ;
}
2021-09-29 16:16:54 -07:00
}
}
2021-11-01 12:02:07 -07:00
catch ( Exception ex )
2021-09-29 16:16:54 -07:00
{
// We don't care what the error is right now
return false ;
}
return true ;
}
2022-04-10 22:12:20 -07:00
/// <summary>
/// Write the protection data to the output folder
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="info">SubmissionInfo object containing the protection information</param>
/// <returns>True on success, false on error</returns>
public static bool WriteProtectionData ( string outputDirectory , SubmissionInfo info )
{
// Check to see if the inputs are valid
if ( info ? . CopyProtection ? . FullProtections = = null | | ! info . CopyProtection . FullProtections . Any ( ) )
return true ;
// Now write out to a generic file
try
{
using ( StreamWriter sw = new StreamWriter ( File . Open ( Path . Combine ( outputDirectory , "!protectionInfo.txt" ) , FileMode . Create , FileAccess . Write ) ) )
{
foreach ( var kvp in info . CopyProtection . FullProtections )
{
sw . WriteLine ( $"{kvp.Key}: {string.Join(" , ", kvp.Value)}" ) ;
}
}
}
catch ( Exception ex )
{
// We don't care what the error is right now
return false ;
}
return true ;
}
2021-09-29 16:16:54 -07:00
/// <summary>
/// Add the properly formatted key and value, if possible
/// </summary>
/// <param name="output">Output list</param>
/// <param name="key">Name of the output key to write</param>
/// <param name="value">Name of the output value to write</param>
/// <param name="indent">Number of tabs to indent the line</param>
private static void AddIfExists ( List < string > output , string key , string value , int indent )
{
// If there's no valid value to write
if ( value = = null )
return ;
string prefix = "" ;
for ( int i = 0 ; i < indent ; i + + )
prefix + = "\t" ;
2022-04-18 21:20:12 -07:00
// Skip fields that need to keep internal whitespace intact
if ( key ! = "Primary Volume Descriptor (PVD)"
& & key ! = "Header"
& & key ! = "Cuesheet" )
{
// Convert to tabs
value = value . Replace ( "<tab>" , "\t" ) ;
value = value . Replace ( "<TAB>" , "\t" ) ;
value = value . Replace ( " " , "\t" ) ;
2022-01-04 21:28:51 -08:00
2022-04-18 21:20:12 -07:00
// Sanitize whitespace around tabs
2022-04-19 22:40:08 -07:00
value = Regex . Replace ( value , @"\s*\t\s*" , "\t" ) ;
2022-04-18 21:20:12 -07:00
}
2022-04-16 12:15:23 -07:00
2021-09-29 16:16:54 -07:00
// If the value contains a newline
value = value . Replace ( "\r\n" , "\n" ) ;
if ( value . Contains ( "\n" ) )
{
output . Add ( prefix + key + ":" ) ; output . Add ( "" ) ;
string [ ] values = value . Split ( '\n' ) ;
foreach ( string val in values )
output . Add ( val ) ;
output . Add ( "" ) ;
}
// For all regular values
else
{
output . Add ( prefix + key + ": " + value ) ;
}
}
/// <summary>
/// Add the properly formatted key and value, if possible
/// </summary>
/// <param name="output">Output list</param>
/// <param name="key">Name of the output key to write</param>
/// <param name="value">Name of the output value to write</param>
/// <param name="indent">Number of tabs to indent the line</param>
private static void AddIfExists ( List < string > output , string key , string [ ] value , int indent )
{
// If there's no valid value to write
if ( value = = null | | value . Length = = 0 )
return ;
AddIfExists ( output , key , string . Join ( ", " , value ) , indent ) ;
}
/// <summary>
/// Add the properly formatted key and value, if possible
/// </summary>
/// <param name="output">Output list</param>
/// <param name="key">Name of the output key to write</param>
/// <param name="value">Name of the output value to write</param>
/// <param name="indent">Number of tabs to indent the line</param>
private static void AddIfExists ( List < string > output , string key , List < int > value , int indent )
{
// If there's no valid value to write
if ( value = = null | | value . Count ( ) = = 0 )
return ;
AddIfExists ( output , key , string . Join ( ", " , value . Select ( o = > o . ToString ( ) ) ) , indent ) ;
}
2022-10-11 13:19:06 -07:00
/// <summary>
/// Generate a list of all MPF-specific log files generated
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <returns>List of all log file paths, empty otherwise</returns>
private static List < string > GetGeneratedFilePaths ( string outputDirectory )
{
List < string > files = new List < string > ( ) ;
if ( File . Exists ( Path . Combine ( outputDirectory , "!submissionInfo.txt" ) ) )
files . Add ( Path . Combine ( outputDirectory , "!submissionInfo.txt" ) ) ;
if ( File . Exists ( Path . Combine ( outputDirectory , "!submissionInfo.json" ) ) )
files . Add ( Path . Combine ( outputDirectory , "!submissionInfo.json" ) ) ;
if ( File . Exists ( Path . Combine ( outputDirectory , "!submissionInfo.json.gz" ) ) )
files . Add ( Path . Combine ( outputDirectory , "!submissionInfo.json.gz" ) ) ;
if ( File . Exists ( Path . Combine ( outputDirectory , "!protectionInfo.txt" ) ) )
files . Add ( Path . Combine ( outputDirectory , "!protectionInfo.txt" ) ) ;
return files ;
}
2023-07-22 21:31:20 -04:00
#endregion
2021-09-29 16:16:54 -07:00
2022-10-04 13:36:50 -07:00
#region Normalization
2021-09-29 16:16:54 -07:00
2022-10-16 23:15:09 -07:00
/// <summary>
/// Adjust a disc title so that it will be processed correctly by Redump
/// </summary>
/// <param name="title">Existing title to potentially reformat</param>
/// <param name="languages">Array of languages to use for assuming articles</param>
/// <returns>The reformatted title</returns>
public static string NormalizeDiscTitle ( string title , Language [ ] languages )
{
// If we have no set languages, then assume English
if ( languages = = null | | languages . Length = = 0 )
languages = new Language [ ] { Language . English } ;
// Loop through all of the given languages
foreach ( var language in languages )
{
// If the new title is different, assume it was normalized and return it
string newTitle = NormalizeDiscTitle ( title , language ) ;
if ( newTitle = = title )
return newTitle ;
}
// If we didn't already try English, try it now
if ( ! languages . Contains ( Language . English ) )
return NormalizeDiscTitle ( title , Language . English ) ;
// If all fails, then the title didn't need normalization
return title ;
}
2022-10-16 21:21:56 -07:00
/// <summary>
/// Adjust a disc title so that it will be processed correctly by Redump
/// </summary>
/// <param name="title">Existing title to potentially reformat</param>
2022-10-16 22:59:54 -07:00
/// <param name="language">Language to use for assuming articles</param>
2022-10-16 21:21:56 -07:00
/// <returns>The reformatted title</returns>
2022-10-16 22:59:54 -07:00
/// <remarks>
/// If the language of the title is unknown or if it's multilingual,
/// pass in Language.English for standardized coverage.
/// </remarks>
public static string NormalizeDiscTitle ( string title , Language language )
2022-10-16 21:21:56 -07:00
{
// If we have an invalid title, just return it as-is
if ( string . IsNullOrWhiteSpace ( title ) )
return title ;
// Get the title split into parts
string [ ] splitTitle = title . Split ( ' ' ) . Where ( s = > ! string . IsNullOrWhiteSpace ( s ) ) . ToArray ( ) ;
// If we only have one part, we can't do anything
if ( splitTitle . Length < = 1 )
return title ;
// Determine if we have a definite or indefinite article as the first item
2022-10-16 22:59:54 -07:00
string firstItem = splitTitle [ 0 ] ;
switch ( firstItem . ToLowerInvariant ( ) )
2022-10-16 21:21:56 -07:00
{
// Latin script articles
2022-10-16 22:59:54 -07:00
case "'n"
when language is Language . Manx :
case "a"
when language is Language . English
| | language is Language . Hungarian
| | language is Language . Portuguese
| | language is Language . Scots :
case "a'"
when language is Language . English
| | language is Language . Hungarian
| | language is Language . Irish
| | language is Language . Gaelic : // Scottish Gaelic
case "al"
when language is Language . Breton :
case "am"
when language is Language . Gaelic : // Scottish Gaelic
case "an"
when language is Language . Breton
| | language is Language . Cornish
| | language is Language . English
| | language is Language . Irish
| | language is Language . Gaelic : // Scottish Gaelic
case "anek"
when language is Language . Nepali :
case "ar"
when language is Language . Breton :
case "az"
when language is Language . Hungarian :
case "ān"
when language is Language . Persian :
case "as"
when language is Language . Portuguese :
case "d'"
when language is Language . Luxembourgish :
case "das"
when language is Language . German :
case "dat"
when language is Language . Luxembourgish :
case "de"
when language is Language . Dutch :
case "déi"
when language is Language . Luxembourgish :
case "dem"
when language is Language . German
| | language is Language . Luxembourgish :
case "den"
when language is Language . Dutch
| | language is Language . German
| | language is Language . Luxembourgish :
case "der"
when language is Language . Dutch
| | language is Language . German
| | language is Language . Luxembourgish :
case "des"
when language is Language . Dutch
| | language is Language . French
| | language is Language . German :
case "die"
when language is Language . Afrikaans
| | language is Language . German :
case "e"
when language is Language . Papiamento :
case "een"
when language is Language . Dutch :
case "egy"
when language is Language . Hungarian :
case "ei"
when language is Language . Norwegian :
case "ein"
when language is Language . German
| | language is Language . Norwegian :
case "eine"
when language is Language . German :
case "einem"
when language is Language . German :
case "einen"
when language is Language . German :
case "einer"
when language is Language . German :
case "eines"
when language is Language . German :
case "eit"
when language is Language . Norwegian :
case "ek"
when language is Language . Nepali :
case "el"
when language is Language . Arabic
| | language is Language . Catalan
| | language is Language . Spanish :
case "els"
when language is Language . Catalan :
case "en"
when language is Language . Danish
| | language is Language . Luxembourgish
| | language is Language . Norwegian
| | language is Language . Swedish :
case "eng"
when language is Language . Luxembourgish :
case "engem"
when language is Language . Luxembourgish :
case "enger"
when language is Language . Luxembourgish :
case "es"
when language is Language . Catalan :
case "et"
when language is Language . Danish
| | language is Language . Norwegian :
case "ett"
when language is Language . Swedish :
case "euta"
when language is Language . Nepali :
case "euti"
when language is Language . Nepali :
case "gli"
when language is Language . Italian :
case "he"
when language is Language . Hawaiian
| | language is Language . Maori :
case "het"
when language is Language . Dutch :
case "i"
when language is Language . Italian
| | language is Language . Khasi :
case "il"
when language is Language . Italian :
case "in"
when language is Language . Persian :
case "ka"
when language is Language . Hawaiian
| | language is Language . Khasi :
case "ke"
when language is Language . Hawaiian :
case "ki"
when language is Language . Khasi :
case "kunai"
when language is Language . Nepali :
case "l'"
when language is Language . Catalan
| | language is Language . French
| | language is Language . Italian :
case "la"
when language is Language . Catalan
| | language is Language . Esperanto
| | language is Language . French
| | language is Language . Italian
| | language is Language . Spanish :
case "las"
when language is Language . Spanish :
case "le"
when language is Language . French
| | language is Language . Interlingua
| | language is Language . Italian :
case "les"
when language is Language . Catalan
| | language is Language . French :
case "lo"
when language is Language . Catalan
| | language is Language . Italian
| | language is Language . Spanish :
case "los"
when language is Language . Catalan
| | language is Language . Spanish :
case "na"
when language is Language . Irish
| | language is Language . Gaelic : // Scottish Gaelic
case "nam"
when language is Language . Gaelic : // Scottish Gaelic
case "nan"
when language is Language . Gaelic : // Scottish Gaelic
case "nā"
when language is Language . Hawaiian :
case "ngā"
when language is Language . Maori :
case "niște"
when language is Language . Romanian :
case "ny"
when language is Language . Manx :
case "o"
when language is Language . Portuguese
| | language is Language . Romanian :
case "os"
when language is Language . Portuguese :
case "sa"
when language is Language . Catalan :
case "sang"
when language is Language . Malay :
case "se"
when language is Language . Finnish :
case "ses"
when language is Language . Catalan :
case "si"
when language is Language . Malay :
case "te"
when language is Language . Maori :
case "the"
when language is Language . English
| | language is Language . Scots :
case "u"
when language is Language . Khasi :
case "ul"
when language is Language . Breton :
case "um"
when language is Language . Portuguese :
case "uma"
when language is Language . Portuguese :
case "umas"
when language is Language . Portuguese :
case "un"
when language is Language . Breton
| | language is Language . Catalan
| | language is Language . French
| | language is Language . Interlingua
| | language is Language . Italian
| | language is Language . Papiamento
| | language is Language . Romanian
| | language is Language . Spanish :
case "un'"
when language is Language . Italian :
case "una"
when language is Language . Catalan
| | language is Language . Italian :
case "unas"
when language is Language . Spanish :
case "une"
when language is Language . French :
case "uno"
when language is Language . Italian :
case "unos"
when language is Language . Spanish :
case "uns"
when language is Language . Catalan
| | language is Language . Portuguese :
case "unei"
when language is Language . Romanian :
case "unes"
when language is Language . Catalan :
case "unor"
when language is Language . Romanian :
case "unui"
when language is Language . Romanian :
case "ur"
when language is Language . Breton :
case "y"
when language is Language . Manx
| | language is Language . Welsh :
case "ye"
when language is Language . Persian :
case "yek"
when language is Language . Persian :
case "yn"
when language is Language . Manx :
case "yr"
when language is Language . Welsh :
2022-10-16 21:21:56 -07:00
// Non-latin script articles
2022-10-16 22:59:54 -07:00
case "ο "
when language is Language . Greek :
case "η"
when language is Language . Greek :
case "το"
when language is Language . Greek :
case "ο ι "
when language is Language . Greek :
case "τα"
when language is Language . Greek :
case "ένας"
when language is Language . Greek :
case "μια"
when language is Language . Greek :
case "ένα"
when language is Language . Greek :
case "еден"
when language is Language . Macedonian :
case "една"
when language is Language . Macedonian :
case "едно"
when language is Language . Macedonian :
case "едни"
when language is Language . Macedonian :
case "एउटा"
when language is Language . Nepali :
case "एउटी"
when language is Language . Nepali :
case "एक"
when language is Language . Nepali :
case "अनेक"
when language is Language . Nepali :
case "कुनै"
when language is Language . Nepali :
case "דער"
when language is Language . Yiddish :
case "די"
when language is Language . Yiddish :
case "דאָס"
when language is Language . Yiddish :
case "דעם"
when language is Language . Yiddish :
case "אַ"
when language is Language . Yiddish :
case "אַן"
when language is Language . Yiddish :
2022-10-16 21:21:56 -07:00
// Seen by Redump, unknown origin
case "du" :
break ;
// Otherwise, just return it as-is
default :
return title ;
}
2022-10-16 22:59:54 -07:00
// Insert the first item if we have a `:` or `-`
bool itemInserted = false ;
StringBuilder newTitleBuilder = new StringBuilder ( ) ;
2022-10-16 21:21:56 -07:00
for ( int i = 1 ; i < splitTitle . Length ; i + + )
{
string segment = splitTitle [ i ] ;
if ( segment . EndsWith ( ":" ) | | segment . EndsWith ( "-" ) )
2022-10-16 22:59:54 -07:00
{
itemInserted = true ;
newTitleBuilder . Append ( $"{segment}, {firstItem}" ) ;
}
2022-10-16 21:21:56 -07:00
else
2022-10-16 22:59:54 -07:00
{
newTitleBuilder . Append ( $"{segment} " ) ;
}
2022-10-16 21:21:56 -07:00
}
2022-10-16 22:59:54 -07:00
// If we didn't insert the item yet, add it to the end
string newTitle = newTitleBuilder . ToString ( ) . Trim ( ) ;
if ( ! itemInserted )
newTitle = $"{newTitle}, {firstItem}" ;
return newTitle ;
2022-10-16 21:21:56 -07:00
}
2022-04-04 21:46:13 -07:00
/// <summary>
/// Adjust the disc type based on size and layerbreak information
/// </summary>
/// <param name="info">Existing SubmissionInfo object to fill</param>
/// <returns>Corrected disc type, if possible</returns>
public static void NormalizeDiscType ( SubmissionInfo info )
{
// If we have nothing valid, do nothing
if ( info ? . CommonDiscInfo ? . Media = = null )
return ;
switch ( info . CommonDiscInfo . Media )
{
case DiscType . BD25 :
2023-04-10 08:57:51 -04:00
case DiscType . BD33 :
2022-04-04 21:46:13 -07:00
case DiscType . BD50 :
2023-04-10 08:57:51 -04:00
case DiscType . BD66 :
2022-04-04 21:46:13 -07:00
case DiscType . BD100 :
case DiscType . BD128 :
if ( info . SizeAndChecksums . Layerbreak3 ! = default )
info . CommonDiscInfo . Media = DiscType . BD128 ;
else if ( info . SizeAndChecksums . Layerbreak2 ! = default )
info . CommonDiscInfo . Media = DiscType . BD100 ;
2023-04-26 10:24:30 -04:00
else if ( info . SizeAndChecksums . Layerbreak ! = default & & info . SizeAndChecksums . PICIdentifier = = PICDiscInformationUnit . DiscTypeIdentifierROMUltra )
info . CommonDiscInfo . Media = DiscType . BD66 ;
2023-04-10 08:57:51 -04:00
else if ( info . SizeAndChecksums . Layerbreak ! = default & & info . SizeAndChecksums . Size > 50_050_629_632 )
info . CommonDiscInfo . Media = DiscType . BD66 ;
2022-04-04 21:46:13 -07:00
else if ( info . SizeAndChecksums . Layerbreak ! = default )
info . CommonDiscInfo . Media = DiscType . BD50 ;
2023-04-26 10:24:30 -04:00
else if ( info . SizeAndChecksums . PICIdentifier = = PICDiscInformationUnit . DiscTypeIdentifierROMUltra )
info . CommonDiscInfo . Media = DiscType . BD33 ;
2023-04-10 08:57:51 -04:00
else if ( info . SizeAndChecksums . Size > 25_025_314_816 )
info . CommonDiscInfo . Media = DiscType . BD33 ;
2022-04-04 21:46:13 -07:00
else
info . CommonDiscInfo . Media = DiscType . BD25 ;
break ;
case DiscType . DVD5 :
case DiscType . DVD9 :
if ( info . SizeAndChecksums . Layerbreak ! = default )
info . CommonDiscInfo . Media = DiscType . DVD9 ;
else
info . CommonDiscInfo . Media = DiscType . DVD5 ;
break ;
case DiscType . HDDVDSL :
case DiscType . HDDVDDL :
if ( info . SizeAndChecksums . Layerbreak ! = default )
info . CommonDiscInfo . Media = DiscType . HDDVDDL ;
else
info . CommonDiscInfo . Media = DiscType . HDDVDSL ;
break ;
case DiscType . UMDSL :
case DiscType . UMDDL :
if ( info . SizeAndChecksums . Layerbreak ! = default )
info . CommonDiscInfo . Media = DiscType . UMDDL ;
else
info . CommonDiscInfo . Media = DiscType . UMDSL ;
break ;
// All other disc types are not processed
default :
break ;
}
}
2021-09-29 16:16:54 -07:00
/// <summary>
/// Normalize a split set of paths
/// </summary>
2022-12-13 11:48:26 -08:00
/// <param name="path">Path value to normalize</param>
public static string NormalizeOutputPaths ( string path )
2021-09-29 16:16:54 -07:00
{
2022-12-13 11:48:26 -08:00
// The easy way
2021-09-29 16:16:54 -07:00
try
{
2022-12-13 11:48:26 -08:00
// Trim quotes from the path
path = path . Trim ( '"' ) ;
2021-09-29 16:16:54 -07:00
2022-12-13 11:48:26 -08:00
// Try getting the combined path and returning that directly
string fullPath = Path . GetFullPath ( path ) ;
string fullDirectory = Path . GetDirectoryName ( fullPath ) ;
string fullFile = Path . GetFileName ( fullPath ) ;
2021-09-29 16:16:54 -07:00
2022-12-13 11:48:26 -08:00
// Remove invalid path characters
2021-09-29 16:16:54 -07:00
foreach ( char c in Path . GetInvalidPathChars ( ) )
2022-12-13 11:48:26 -08:00
fullDirectory = fullDirectory . Replace ( c , '_' ) ;
2022-04-09 13:21:31 -07:00
2022-12-13 11:48:26 -08:00
// Remove invalid filename characters
2021-09-29 16:16:54 -07:00
foreach ( char c in Path . GetInvalidFileNameChars ( ) )
2022-12-13 11:48:26 -08:00
fullFile = fullFile . Replace ( c , '_' ) ;
2023-03-11 22:26:45 -05:00
return Path . Combine ( fullDirectory , fullFile ) ;
2021-09-29 16:16:54 -07:00
}
catch { }
2022-12-13 11:48:26 -08:00
return path ;
2021-09-29 16:16:54 -07:00
}
2022-10-04 13:36:50 -07:00
#endregion
2021-09-29 16:16:54 -07:00
2022-10-04 13:36:50 -07:00
#region Web Calls
2021-09-29 16:16:54 -07:00
2021-12-30 11:09:37 -08:00
/// <summary>
/// Create a new SubmissionInfo object from a disc page
/// </summary>
/// <param name="discData">String containing the HTML disc data</param>
2021-12-30 13:00:55 -08:00
/// <returns>Filled SubmissionInfo object on success, null on error</returns>
2021-12-30 11:09:37 -08:00
/// <remarks>Not currently working</remarks>
2021-12-30 13:00:55 -08:00
private static SubmissionInfo CreateFromID ( string discData )
2021-12-30 11:09:37 -08:00
{
SubmissionInfo info = new SubmissionInfo ( )
{
CommonDiscInfo = new CommonDiscInfoSection ( ) ,
VersionAndEditions = new VersionAndEditionsSection ( ) ,
} ;
// No disc data means we can't parse it
if ( string . IsNullOrWhiteSpace ( discData ) )
2021-12-30 13:00:55 -08:00
return null ;
2021-12-30 11:09:37 -08:00
try
{
// Load the current disc page into an XML document
2022-10-04 13:36:50 -07:00
XmlDocument redumpPage = new XmlDocument ( ) { PreserveWhitespace = true } ;
2021-12-30 11:09:37 -08:00
redumpPage . LoadXml ( discData ) ;
// If the current page isn't valid, we can't parse it
if ( ! redumpPage . HasChildNodes )
2021-12-30 13:00:55 -08:00
return null ;
2021-12-30 11:09:37 -08:00
// Get the body node, if possible
XmlNode bodyNode = redumpPage [ "html" ] ? [ "body" ] ;
if ( bodyNode = = null | | ! bodyNode . HasChildNodes )
2021-12-30 13:00:55 -08:00
return null ;
2021-12-30 11:09:37 -08:00
// Loop through and get the main node, if possible
XmlNode mainNode = null ;
foreach ( XmlNode tempNode in bodyNode . ChildNodes )
{
// We only care about div elements
if ( ! string . Equals ( tempNode . Name , "div" , StringComparison . OrdinalIgnoreCase ) )
continue ;
// We only care if it has attributes
if ( tempNode . Attributes = = null )
continue ;
// The main node has a class of "main"
if ( string . Equals ( tempNode . Attributes [ "class" ] ? . Value , "main" , StringComparison . OrdinalIgnoreCase ) )
{
mainNode = tempNode ;
break ;
}
}
// If the main node is invalid, we can't do anything
if ( mainNode = = null | | ! mainNode . HasChildNodes )
2021-12-30 13:00:55 -08:00
return null ;
2021-12-30 11:09:37 -08:00
// Try to find elements as we're going
foreach ( XmlNode childNode in mainNode . ChildNodes )
{
// The title is the only thing in h1 tags
if ( string . Equals ( childNode . Name , "h1" , StringComparison . OrdinalIgnoreCase ) )
info . CommonDiscInfo . Title = childNode . InnerText ;
// Most things are div elements but can be hard to parse out
else if ( ! string . Equals ( childNode . Name , "div" , StringComparison . OrdinalIgnoreCase ) )
continue ;
// Only 2 of the internal divs have classes attached and one is not used here
2021-12-30 13:00:55 -08:00
if ( childNode . Attributes ! = null & & string . Equals ( childNode . Attributes [ "class" ] ? . Value , "game" ,
StringComparison . OrdinalIgnoreCase ) )
2021-12-30 11:09:37 -08:00
{
// If we don't have children nodes, skip this one over
if ( ! childNode . HasChildNodes )
continue ;
// The game node contains multiple other elements
foreach ( XmlNode gameNode in childNode . ChildNodes )
{
// Table elements contain multiple other parts of information
if ( string . Equals ( gameNode . Name , "table" , StringComparison . OrdinalIgnoreCase ) )
{
// All tables have some attribute we can use
if ( gameNode . Attributes = = null )
continue ;
// The gameinfo node contains most of the major information
2021-12-30 13:00:55 -08:00
if ( string . Equals ( gameNode . Attributes [ "class" ] ? . Value , "gameinfo" ,
StringComparison . OrdinalIgnoreCase ) )
2021-12-30 11:09:37 -08:00
{
// If we don't have children nodes, skip this one over
if ( ! gameNode . HasChildNodes )
continue ;
// Loop through each of the rows
foreach ( XmlNode gameInfoNode in gameNode . ChildNodes )
{
// If we run into anything not a row, ignore it
if ( ! string . Equals ( gameInfoNode . Name , "tr" , StringComparison . OrdinalIgnoreCase ) )
continue ;
// If we don't have the required nodes, ignore it
if ( gameInfoNode [ "th" ] = = null | | gameInfoNode [ "td" ] = = null )
continue ;
XmlNode gameInfoNodeHeader = gameInfoNode [ "th" ] ;
XmlNode gameInfoNodeData = gameInfoNode [ "td" ] ;
if ( string . Equals ( gameInfoNodeHeader . InnerText , "System" , StringComparison . OrdinalIgnoreCase ) )
{
2021-12-30 13:00:55 -08:00
info . CommonDiscInfo . System = Extensions . ToRedumpSystem ( gameInfoNodeData [ "a" ] ? . InnerText ) ;
2021-12-30 11:09:37 -08:00
}
else if ( string . Equals ( gameInfoNodeHeader . InnerText , "Media" , StringComparison . OrdinalIgnoreCase ) )
{
2021-12-30 13:00:55 -08:00
info . CommonDiscInfo . Media = Extensions . ToDiscType ( gameInfoNodeData . InnerText ) ;
2021-12-30 11:09:37 -08:00
}
else if ( string . Equals ( gameInfoNodeHeader . InnerText , "Category" , StringComparison . OrdinalIgnoreCase ) )
{
2021-12-30 13:00:55 -08:00
info . CommonDiscInfo . Category = Extensions . ToDiscCategory ( gameInfoNodeData . InnerText ) ;
2021-12-30 11:09:37 -08:00
}
else if ( string . Equals ( gameInfoNodeHeader . InnerText , "Region" , StringComparison . OrdinalIgnoreCase ) )
{
// TODO: COMPLETE
}
else if ( string . Equals ( gameInfoNodeHeader . InnerText , "Languages" , StringComparison . OrdinalIgnoreCase ) )
{
// TODO: COMPLETE
}
else if ( string . Equals ( gameInfoNodeHeader . InnerText , "Edition" , StringComparison . OrdinalIgnoreCase ) )
{
info . VersionAndEditions . OtherEditions = gameInfoNodeData . InnerText ;
}
else if ( string . Equals ( gameInfoNodeHeader . InnerText , "Added" , StringComparison . OrdinalIgnoreCase ) )
{
if ( DateTime . TryParse ( gameInfoNodeData . InnerText , out DateTime added ) )
info . Added = added ;
}
else if ( string . Equals ( gameInfoNodeHeader . InnerText , "Last modified" , StringComparison . OrdinalIgnoreCase ) )
{
if ( DateTime . TryParse ( gameInfoNodeData . InnerText , out DateTime lastModified ) )
info . LastModified = lastModified ;
}
}
}
// The gamecomments node contains way more than it implies
if ( string . Equals ( gameNode . Attributes [ "class" ] ? . Value , "gamecomments" , StringComparison . OrdinalIgnoreCase ) )
{
// TODO: COMPLETE
}
// TODO: COMPLETE
}
// The only other supported elements are divs
else if ( ! string . Equals ( gameNode . Name , "div" , StringComparison . OrdinalIgnoreCase ) )
{
continue ;
}
// Check the div for dumper info
// TODO: COMPLETE
}
}
// Figure out what the div contains, if possible
// TODO: COMPLETE
}
}
2021-12-30 13:00:55 -08:00
catch
{
return null ;
}
return info ;
2021-12-30 11:09:37 -08:00
}
2021-09-29 16:16:54 -07:00
/// <summary>
/// Fill out an existing SubmissionInfo object based on a disc page
/// </summary>
/// <param name="wc">RedumpWebClient for making the connection</param>
/// <param name="info">Existing SubmissionInfo object to fill</param>
/// <param name="id">Redump disc ID to retrieve</param>
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
private static bool FillFromId ( RedumpWebClient wc , SubmissionInfo info , int id )
2021-09-29 16:16:54 -07:00
{
string discData = wc . DownloadSingleSiteID ( id ) ;
if ( string . IsNullOrEmpty ( discData ) )
2022-07-26 13:47:19 -07:00
return false ;
#else
private async static Task < bool > FillFromId ( RedumpHttpClient wc , SubmissionInfo info , int id )
{
string discData = await wc . DownloadSingleSiteID ( id ) ;
if ( string . IsNullOrEmpty ( discData ) )
return false ;
#endif
2021-09-29 16:16:54 -07:00
// Title, Disc Number/Letter, Disc Title
var match = Constants . 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 ) ;
2022-02-10 11:37:36 -08:00
var subMatches = Constants . DiscNumberLetterRegex . Matches ( title ) ;
foreach ( Match subMatch in subMatches )
2021-09-29 16:16:54 -07:00
{
2022-02-10 11:37:36 -08:00
var subMatchValue = subMatch . Groups [ 1 ] . Value ;
2021-09-29 16:16:54 -07:00
// Disc number or letter
2022-02-10 11:37:36 -08:00
if ( subMatchValue . StartsWith ( "Disc" ) )
info . CommonDiscInfo . DiscNumberLetter = subMatchValue . Remove ( 0 , "Disc " . Length ) ;
2021-09-29 16:16:54 -07:00
// Disc title
else
2022-02-10 11:37:36 -08:00
info . CommonDiscInfo . DiscTitle = subMatchValue ;
2021-09-29 16:16:54 -07:00
}
}
// Otherwise, leave the title as-is
else
{
info . CommonDiscInfo . Title = title ;
}
}
// Foreign Title
match = Constants . ForeignTitleRegex . Match ( discData ) ;
if ( match . Success )
info . CommonDiscInfo . ForeignTitleNonLatin = WebUtility . HtmlDecode ( match . Groups [ 1 ] . Value ) ;
else
info . CommonDiscInfo . ForeignTitleNonLatin = null ;
// Category
match = Constants . CategoryRegex . Match ( discData ) ;
if ( match . Success )
info . CommonDiscInfo . Category = Extensions . ToDiscCategory ( match . Groups [ 1 ] . Value ) ;
else
info . CommonDiscInfo . Category = DiscCategory . Games ;
// Region
2022-01-14 16:30:20 -08:00
if ( info . CommonDiscInfo . Region = = null )
{
match = Constants . RegionRegex . Match ( discData ) ;
if ( match . Success )
info . CommonDiscInfo . Region = Extensions . ToRegion ( match . Groups [ 1 ] . Value ) ;
}
2021-09-29 16:16:54 -07:00
// Languages
var matches = Constants . LanguagesRegex . Matches ( discData ) ;
if ( matches . Count > 0 )
{
List < Language ? > tempLanguages = new List < Language ? > ( ) ;
foreach ( Match submatch in matches )
tempLanguages . Add ( Extensions . ToLanguage ( submatch . Groups [ 1 ] . Value ) ) ;
info . CommonDiscInfo . Languages = tempLanguages . Where ( l = > l ! = null ) . ToArray ( ) ;
}
2021-12-25 21:23:24 -08:00
// Serial
2021-12-27 13:10:48 -08:00
// TODO: Re-enable if there's a way of verifying against a disc
2021-12-25 21:23:24 -08:00
//match = Constants.SerialRegex.Match(discData);
//if (match.Success)
2022-02-01 13:04:28 -08:00
// info.CommonDiscInfo.Serial = $"(VERIFY THIS) {WebUtility.HtmlDecode(match.Groups[1].Value)}";
2021-12-25 21:23:24 -08:00
2021-09-29 16:16:54 -07:00
// Error count
2022-01-14 16:30:20 -08:00
if ( string . IsNullOrEmpty ( info . CommonDiscInfo . ErrorsCount ) )
2021-09-29 16:16:54 -07:00
{
2022-01-14 16:30:20 -08:00
match = Constants . ErrorCountRegex . Match ( discData ) ;
if ( match . Success )
2021-09-29 16:16:54 -07:00
info . CommonDiscInfo . ErrorsCount = match . Groups [ 1 ] . Value ;
}
// Version
2022-02-01 20:54:26 -08:00
if ( info . VersionAndEditions . Version = = null )
{
match = Constants . VersionRegex . Match ( discData ) ;
if ( match . Success )
info . VersionAndEditions . Version = $"(VERIFY THIS) {WebUtility.HtmlDecode(match.Groups[1].Value)}" ;
}
2021-09-29 16:16:54 -07:00
// Dumpers
matches = Constants . DumpersRegex . Matches ( discData ) ;
if ( matches . Count > 0 )
{
// Start with any currently listed dumpers
List < string > tempDumpers = new List < string > ( ) ;
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 ( ) ;
}
2021-12-30 13:00:55 -08:00
// TODO: Unify handling of fields that can include site codes (Comments/Contents)
2021-09-29 16:16:54 -07:00
// Comments
2021-12-30 13:00:55 -08:00
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 )
2022-01-01 14:18:14 -08:00
. Replace ( "\r\n" , "\n" )
2022-01-01 20:46:44 -08:00
. Replace ( "<br />\n" , "\n" )
2022-01-01 14:18:14 -08:00
. Replace ( "<br />" , string . Empty )
. Replace ( "</div>" , string . Empty )
2021-12-30 15:23:17 -08:00
. Replace ( "[+]" , string . Empty )
2021-12-30 13:00:55 -08:00
. ReplaceHtmlWithSiteCodes ( ) ;
2022-01-01 14:18:14 -08:00
oldComments = Regex . Replace ( oldComments , @"<div .*?>" , string . Empty ) ;
2021-12-30 15:36:58 -08:00
// Create state variables
bool addToLast = false ;
SiteCode ? lastSiteCode = null ;
2021-12-30 13:00:55 -08:00
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 ) )
{
2021-12-30 15:36:58 -08:00
addToLast = false ;
lastSiteCode = null ;
2021-12-30 13:00:55 -08:00
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 ) ) )
{
2023-09-05 00:08:09 -04:00
// If we have a null site code, just skip
if ( siteCode = = null )
continue ;
2021-12-30 13:00:55 -08:00
// If the line doesn't contain this tag, just skip
if ( ! commentLine . Contains ( siteCode . ShortName ( ) ) )
continue ;
2023-07-19 00:05:33 -04:00
// 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 ) ;
2023-07-10 11:15:32 -04:00
// Skip certain site codes because of data issues
switch ( siteCode )
{
2023-07-17 10:28:37 -04:00
// Multiple
case SiteCode . InternalSerialName :
case SiteCode . Multisession :
case SiteCode . VolumeLabel :
2023-07-18 22:40:08 -04:00
continue ;
2023-07-17 10:28:37 -04:00
2023-07-12 17:29:35 -04:00
// Audio CD
2023-07-17 10:28:37 -04:00
case SiteCode . RingNonZeroDataStart :
2023-07-12 17:29:35 -04:00
case SiteCode . UniversalHash :
continue ;
2023-07-17 10:28:37 -04:00
// Microsoft Xbox and Xbox 360
2023-07-10 11:15:32 -04:00
case SiteCode . DMIHash :
case SiteCode . PFIHash :
case SiteCode . SSHash :
case SiteCode . SSVersion :
case SiteCode . XMID :
case SiteCode . XeMID :
continue ;
2023-07-17 10:28:37 -04:00
// Microsoft Xbox One and Series X/S
case SiteCode . Filename :
2023-07-18 22:40:08 -04:00
continue ;
2023-07-17 10:28:37 -04:00
// Nintendo Gamecube
case SiteCode . InternalName :
2023-07-18 22:40:08 -04:00
continue ;
2023-07-10 11:15:32 -04:00
}
2021-12-30 13:00:55 -08:00
// If we don't already have this site code, add it to the dictionary
2023-09-05 00:08:09 -04:00
if ( ! info . CommonDiscInfo . CommentsSpecialFields . ContainsKey ( siteCode . Value ) )
info . CommonDiscInfo . CommentsSpecialFields [ siteCode . Value ] = $"(VERIFY THIS) {commentLine.Replace(siteCode.ShortName(), string.Empty).Trim()}" ;
2023-07-22 21:31:20 -04:00
2023-06-19 21:21:20 -04:00
// Otherwise, append the value to the existing key
else
2023-09-05 00:08:09 -04:00
info . CommonDiscInfo . CommentsSpecialFields [ siteCode . Value ] + = $", {commentLine.Replace(siteCode.ShortName(), string.Empty).Trim()}" ;
2022-01-01 22:24:03 -08:00
2021-12-30 13:00:55 -08:00
break ;
}
// If we didn't find a known tag, just add the line, just in case
if ( ! foundTag )
2021-12-30 15:36:58 -08:00
{
if ( addToLast & & lastSiteCode ! = null )
2022-01-27 17:17:42 -08:00
{
2023-09-05 00:08:09 -04:00
if ( ! string . IsNullOrWhiteSpace ( info . CommonDiscInfo . CommentsSpecialFields [ lastSiteCode . Value ] ) )
info . CommonDiscInfo . CommentsSpecialFields [ lastSiteCode . Value ] + = "\n" ;
2022-01-27 17:17:42 -08:00
2023-09-05 00:08:09 -04:00
info . CommonDiscInfo . CommentsSpecialFields [ lastSiteCode . Value ] + = commentLine ;
2022-01-27 17:17:42 -08:00
}
2021-12-30 15:36:58 -08:00
else
2022-01-27 17:17:42 -08:00
{
2021-12-30 15:36:58 -08:00
newComments + = $"{commentLine}\n" ;
2022-01-27 17:17:42 -08:00
}
2021-12-30 15:36:58 -08:00
}
2021-12-30 13:00:55 -08:00
}
// Set the new comments field
info . CommonDiscInfo . Comments = newComments ;
}
2021-09-29 16:16:54 -07:00
// Contents
match = Constants . ContentsRegex . Match ( discData ) ;
if ( match . Success )
{
2021-12-30 13:00:55 -08:00
// Process the old contents block
string oldContents = info . CommonDiscInfo . Contents
+ ( string . IsNullOrEmpty ( info . CommonDiscInfo . Contents ) ? string . Empty : "\n" )
+ WebUtility . HtmlDecode ( match . Groups [ 1 ] . Value )
2022-01-01 14:18:14 -08:00
. Replace ( "\r\n" , "\n" )
2022-01-01 20:46:44 -08:00
. Replace ( "<br />\n" , "\n" )
2022-01-01 14:18:14 -08:00
. Replace ( "<br />" , string . Empty )
2021-12-30 13:00:55 -08:00
. Replace ( "</div>" , string . Empty )
2021-12-30 15:23:17 -08:00
. Replace ( "[+]" , string . Empty )
2021-12-30 13:00:55 -08:00
. ReplaceHtmlWithSiteCodes ( ) ;
oldContents = Regex . Replace ( oldContents , @"<div .*?>" , string . Empty ) ;
2021-12-30 15:36:58 -08:00
// Create state variables
bool addToLast = false ;
SiteCode ? lastSiteCode = null ;
2021-12-30 13:00:55 -08:00
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 ) )
{
2021-12-30 15:36:58 -08:00
addToLast = false ;
lastSiteCode = null ;
2021-12-30 13:00:55 -08:00
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 ) ) )
{
2023-09-05 00:08:09 -04:00
// If we have a null site code, just skip
if ( siteCode = = null )
continue ;
2021-12-30 13:00:55 -08:00
// If the line doesn't contain this tag, just skip
if ( ! contentLine . Contains ( siteCode . ShortName ( ) ) )
continue ;
2021-12-30 15:36:58 -08:00
// Cache the current site code
lastSiteCode = siteCode ;
2021-12-30 13:00:55 -08:00
// If we don't already have this site code, add it to the dictionary
2023-09-05 00:08:09 -04:00
if ( ! info . CommonDiscInfo . ContentsSpecialFields . ContainsKey ( siteCode . Value ) )
info . CommonDiscInfo . ContentsSpecialFields [ siteCode . Value ] = $"(VERIFY THIS) {contentLine.Replace(siteCode.ShortName(), string.Empty).Trim()}" ;
2021-12-30 13:00:55 -08:00
2021-12-30 15:36:58 -08:00
// A subset of tags can be multiline
2022-01-02 21:54:49 -08:00
addToLast = IsMultiLine ( siteCode ) ;
2021-12-30 15:36:58 -08:00
2021-12-30 13:00:55 -08:00
// 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 )
2021-12-30 15:36:58 -08:00
{
if ( addToLast & & lastSiteCode ! = null )
2022-01-27 17:17:42 -08:00
{
2023-09-05 00:08:09 -04:00
if ( ! string . IsNullOrWhiteSpace ( info . CommonDiscInfo . ContentsSpecialFields [ lastSiteCode . Value ] ) )
info . CommonDiscInfo . ContentsSpecialFields [ lastSiteCode . Value ] + = "\n" ;
2022-01-27 17:17:42 -08:00
2023-09-05 00:08:09 -04:00
info . CommonDiscInfo . ContentsSpecialFields [ lastSiteCode . Value ] + = contentLine ;
2022-01-27 17:17:42 -08:00
}
2021-12-30 15:36:58 -08:00
else
2022-01-27 17:17:42 -08:00
{
2021-12-30 15:36:58 -08:00
newContents + = $"{contentLine}\n" ;
2022-01-27 17:17:42 -08:00
}
2021-12-30 15:36:58 -08:00
}
2021-12-30 13:00:55 -08:00
}
// Set the new contents field
info . CommonDiscInfo . Contents = newContents ;
2021-09-29 16:16:54 -07:00
}
// 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 ;
}
2022-07-26 13:47:19 -07:00
return true ;
2021-09-29 16:16:54 -07:00
}
2021-12-25 21:37:19 -08:00
/// <summary>
/// Fill in a SubmissionInfo object from Redump, if possible
/// </summary>
/// <param name="options">Options object representing user-defined options</param>
/// <param name="info">Existing SubmissionInfo object to fill</param>
/// <param name="resultProgress">Optional result progress callback</param>
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2023-07-14 12:07:44 -04:00
private static bool FillFromRedump ( Core . Data . Options options , SubmissionInfo info , IProgress < Result > resultProgress = null )
2022-07-26 13:47:19 -07:00
#else
2023-07-14 12:07:44 -04:00
private async static Task < bool > FillFromRedump ( Core . Data . Options options , SubmissionInfo info , IProgress < Result > resultProgress = null )
2022-07-26 13:47:19 -07:00
#endif
2021-12-25 21:37:19 -08:00
{
// Set the current dumper based on username
info . DumpersAndStatus . Dumpers = new string [ ] { options . RedumpUsername } ;
2022-03-12 21:09:51 -08:00
info . PartiallyMatchedIDs = new List < int > ( ) ;
2021-12-25 21:37:19 -08:00
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2021-12-25 21:37:19 -08:00
using ( RedumpWebClient wc = new RedumpWebClient ( ) )
2022-07-26 13:47:19 -07:00
#else
using ( RedumpHttpClient wc = new RedumpHttpClient ( ) )
#endif
2021-12-25 21:37:19 -08:00
{
// Login to Redump
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2021-12-25 21:37:19 -08:00
bool? loggedIn = wc . Login ( options . RedumpUsername , options . RedumpPassword ) ;
2022-07-26 13:47:19 -07:00
#else
bool? loggedIn = await wc . Login ( options . RedumpUsername , options . RedumpPassword ) ;
#endif
2021-12-25 21:37:19 -08:00
if ( loggedIn = = null )
{
resultProgress ? . Report ( Result . Failure ( "There was an unknown error connecting to Redump" ) ) ;
2022-07-26 13:47:19 -07:00
return false ;
2021-12-25 21:37:19 -08:00
}
else if ( loggedIn = = false )
{
// Don't log the as a failure or error
2022-07-26 13:47:19 -07:00
return false ;
2021-12-25 21:37:19 -08:00
}
2022-03-12 21:09:51 -08:00
// Setup the full-track checks
2022-02-03 15:43:17 -08:00
bool allFound = true ;
2022-03-12 21:09:51 -08:00
List < int > fullyMatchedIDs = null ;
// Loop through all of the hashdata to find matching IDs
2021-12-25 21:37:19 -08:00
resultProgress ? . Report ( Result . Success ( "Finding disc matches on Redump..." ) ) ;
2023-04-24 00:01:07 -04:00
string [ ] splitData = info . TracksAndWriteOffsets . ClrMameProData . TrimEnd ( '\n' ) . Split ( '\n' ) ;
2023-07-23 15:09:46 -04:00
int trackCount = splitData . Length ;
2021-12-25 21:37:19 -08:00
foreach ( string hashData in splitData )
{
2023-04-24 00:01:07 -04:00
// Catch any errant blank lines
if ( string . IsNullOrWhiteSpace ( hashData ) )
2023-07-23 15:09:46 -04:00
{
trackCount - - ;
resultProgress ? . Report ( Result . Success ( "Blank line found, skipping!" ) ) ;
2023-04-24 00:01:07 -04:00
continue ;
2023-07-23 15:09:46 -04:00
}
2023-04-24 00:01:07 -04:00
2023-07-22 21:31:20 -04:00
// 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" ) )
{
2023-07-23 15:09:46 -04:00
trackCount - - ;
2023-07-22 21:31:20 -04:00
resultProgress ? . Report ( Result . Success ( "Extra track found, skipping!" ) ) ;
continue ;
}
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2022-03-12 21:09:51 -08:00
( bool singleFound , List < int > foundIds ) = ValidateSingleTrack ( wc , info , hashData , resultProgress ) ;
2022-07-26 13:47:19 -07:00
#else
( bool singleFound , List < int > foundIds ) = await ValidateSingleTrack ( wc , info , hashData , resultProgress ) ;
#endif
2022-03-12 21:09:51 -08:00
// 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 ( ) ;
}
2022-03-12 21:14:26 -08:00
// If no tracks were found, remove all fully matched IDs found so far
else
{
fullyMatchedIDs = new List < int > ( ) ;
}
2021-12-25 21:37:19 -08:00
}
2023-07-23 16:41:40 -04:00
// If we don't have any matches but we have a universal hash
if ( ! info . PartiallyMatchedIDs . Any ( ) & & info . CommonDiscInfo . CommentsSpecialFields . ContainsKey ( SiteCode . UniversalHash ) )
{
2023-07-23 16:48:44 -04:00
#if NET48 | | NETSTANDARD2_1
( bool singleFound , List < int > foundIds ) = ValidateUniversalHash ( wc , info , resultProgress ) ;
#else
2023-07-23 16:41:40 -04:00
( bool singleFound , List < int > foundIds ) = await ValidateUniversalHash ( wc , info , resultProgress ) ;
2023-07-23 16:48:44 -04:00
#endif
2023-07-23 16:41:40 -04:00
// Ensure that the hash is found
allFound = singleFound ;
// If we found a track, only keep track of distinct found tracks
if ( singleFound & & foundIds ! = null )
{
2023-07-23 19:16:57 -04:00
fullyMatchedIDs = foundIds ;
2023-07-23 16:41:40 -04:00
}
// If no tracks were found, remove all fully matched IDs found so far
else
{
fullyMatchedIDs = new List < int > ( ) ;
}
}
2022-03-12 21:09:51 -08:00
// 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 )
2021-12-25 21:37:19 -08:00
: "No matches found" ) ) ) ;
2022-02-03 15:43:17 -08:00
// Exit early if one failed or there are no matched IDs
2022-03-12 21:09:51 -08:00
if ( ! allFound | | fullyMatchedIDs . Count = = 0 )
2022-07-26 13:47:19 -07:00
return false ;
2021-12-25 21:37:19 -08:00
// Find the first matched ID where the track count matches, we can grab a bunch of info from it
2022-03-12 21:09:51 -08:00
int totalMatchedIDsCount = fullyMatchedIDs . Count ;
2021-12-25 21:37:19 -08:00
for ( int i = 0 ; i < totalMatchedIDsCount ; i + + )
{
// Skip if the track count doesn't match
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2023-07-23 15:09:46 -04:00
if ( ! ValidateTrackCount ( wc , fullyMatchedIDs [ i ] , trackCount ) )
2021-12-25 21:37:19 -08:00
continue ;
2022-07-26 13:47:19 -07:00
#else
2023-07-23 15:09:46 -04:00
if ( ! await ValidateTrackCount ( wc , fullyMatchedIDs [ i ] , trackCount ) )
2022-07-26 13:47:19 -07:00
continue ;
#endif
2021-12-25 21:37:19 -08:00
// Fill in the fields from the existing ID
2022-03-12 21:09:51 -08:00
resultProgress ? . Report ( Result . Success ( $"Filling fields from existing ID {fullyMatchedIDs[i]}..." ) ) ;
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2022-03-12 21:09:51 -08:00
FillFromId ( wc , info , fullyMatchedIDs [ i ] ) ;
2022-07-26 13:47:19 -07:00
#else
_ = await FillFromId ( wc , info , fullyMatchedIDs [ i ] ) ;
#endif
2021-12-25 21:37:19 -08:00
resultProgress ? . Report ( Result . Success ( "Information filling complete!" ) ) ;
2022-03-12 21:09:51 -08:00
// Set the fully matched ID to the current
info . FullyMatchedID = fullyMatchedIDs [ i ] ;
2021-12-25 21:37:19 -08:00
break ;
}
2022-03-12 22:19:03 -08:00
// 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 ) ;
}
2021-12-25 21:37:19 -08:00
}
2022-07-26 13:47:19 -07:00
return true ;
2021-12-25 21:37:19 -08:00
}
2021-12-24 14:30:50 -08:00
/// <summary>
2021-12-30 11:09:37 -08:00
/// Process a text block and replace with internal identifiers
2021-12-24 14:30:50 -08:00
/// </summary>
2021-12-30 11:09:37 -08:00
/// <param name="text">Text block to process</param>
/// <returns>Processed text block, if possible</returns>
private static string ReplaceHtmlWithSiteCodes ( this string text )
2021-12-24 14:30:50 -08:00
{
2021-12-30 11:09:37 -08:00
if ( string . IsNullOrWhiteSpace ( text ) )
return text ;
foreach ( SiteCode ? siteCode in Enum . GetValues ( typeof ( SiteCode ) ) )
{
text = text . Replace ( siteCode . LongName ( ) , siteCode . ShortName ( ) ) ;
}
2022-01-27 13:35:47 -08:00
// For some outdated tags, we need to use alternate names
2022-02-02 12:54:08 -08:00
text = text . Replace ( "<b>Demos</b>:" , ( ( SiteCode ? ) SiteCode . PlayableDemos ) . ShortName ( ) ) ;
2022-01-27 13:35:47 -08:00
text = text . Replace ( "DMI:" , ( ( SiteCode ? ) SiteCode . DMIHash ) . ShortName ( ) ) ;
text = text . Replace ( "<b>LucasArts ID</b>:" , ( ( SiteCode ? ) SiteCode . LucasArtsID ) . ShortName ( ) ) ;
text = text . Replace ( "PFI:" , ( ( SiteCode ? ) SiteCode . PFIHash ) . ShortName ( ) ) ;
text = text . Replace ( "SS:" , ( ( SiteCode ? ) SiteCode . SSHash ) . ShortName ( ) ) ;
2022-01-28 23:12:25 -08:00
text = text . Replace ( "SSv1:" , ( ( SiteCode ? ) SiteCode . SSHash ) . ShortName ( ) ) ;
2023-07-10 11:15:32 -04:00
text = text . Replace ( "<b>SSv1</b>:" , ( ( SiteCode ? ) SiteCode . SSHash ) . ShortName ( ) ) ;
2022-01-31 10:50:53 -08:00
text = text . Replace ( "SSv2:" , ( ( SiteCode ? ) SiteCode . SSHash ) . ShortName ( ) ) ;
2023-07-10 11:15:32 -04:00
text = text . Replace ( "<b>SSv2</b>:" , ( ( SiteCode ? ) SiteCode . SSHash ) . ShortName ( ) ) ;
2022-01-27 13:35:47 -08:00
text = text . Replace ( "SS version:" , ( ( SiteCode ? ) SiteCode . SSVersion ) . ShortName ( ) ) ;
2023-07-20 20:26:57 -04:00
text = text . Replace ( "Universal Hash (SHA-1):" , ( ( SiteCode ? ) SiteCode . UniversalHash ) . ShortName ( ) ) ;
2022-01-27 13:35:47 -08:00
text = text . Replace ( "XeMID:" , ( ( SiteCode ? ) SiteCode . XeMID ) . ShortName ( ) ) ;
text = text . Replace ( "XMID:" , ( ( SiteCode ? ) SiteCode . XMID ) . ShortName ( ) ) ;
2021-12-30 11:09:37 -08:00
return text ;
2021-12-24 14:30:50 -08:00
}
2021-09-29 16:16:54 -07:00
/// <summary>
/// List the disc IDs associated with a given quicksearch query
/// </summary>
/// <param name="wc">RedumpWebClient for making the connection</param>
/// <param name="query">Query string to attempt to search for</param>
2023-07-23 19:16:57 -04:00
/// <param name="filterForwardSlashes">True to filter forward slashes, false otherwise</param>
2021-09-29 16:16:54 -07:00
/// <returns>All disc IDs for the given query, null on error</returns>
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2023-07-23 19:16:57 -04:00
private static List < int > ListSearchResults ( RedumpWebClient wc , string query , bool filterForwardSlashes = true )
2022-07-26 13:47:19 -07:00
#else
2023-07-23 19:16:57 -04:00
private async static Task < List < int > > ListSearchResults ( RedumpHttpClient wc , string query , bool filterForwardSlashes = true )
2022-07-26 13:47:19 -07:00
#endif
2021-09-29 16:16:54 -07:00
{
List < int > ids = new List < int > ( ) ;
// Strip quotes
query = query . Trim ( '"' , '\'' ) ;
// Special characters become dashes
query = query . Replace ( ' ' , '-' ) ;
2023-07-23 19:16:57 -04:00
if ( filterForwardSlashes )
query = query . Replace ( '/' , '-' ) ;
2021-09-29 16:16:54 -07:00
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 )
{
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2021-09-29 16:16:54 -07:00
List < int > pageIds = wc . CheckSingleSitePage ( string . Format ( Constants . QuickSearchUrl , query , pageNumber + + ) ) ;
2022-07-26 13:47:19 -07:00
#else
List < int > pageIds = await wc . CheckSingleSitePage ( string . Format ( Constants . QuickSearchUrl , query , pageNumber + + ) ) ;
#endif
2021-09-29 16:16:54 -07:00
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 ;
}
2021-12-25 21:23:24 -08:00
/// <summary>
/// Validate a single track against Redump, if possible
/// </summary>
/// <param name="wc">RedumpWebClient for making the connection</param>
/// <param name="info">Existing SubmissionInfo object to fill</param>
/// <param name="hashData">DAT-formatted hash data to parse out</param>
/// <param name="resultProgress">Optional result progress callback</param>
2022-03-12 21:09:51 -08:00
/// <returns>True if the track was found, false otherwise; List of found values, if possible</returns>
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2022-03-12 21:09:51 -08:00
private static ( bool , List < int > ) ValidateSingleTrack ( RedumpWebClient wc , SubmissionInfo info , string hashData , IProgress < Result > resultProgress = null )
2022-07-26 13:47:19 -07:00
#else
private async static Task < ( bool , List < int > ) > ValidateSingleTrack ( RedumpHttpClient wc , SubmissionInfo info , string hashData , IProgress < Result > resultProgress = null )
#endif
2021-12-25 21:23:24 -08:00
{
// If the line isn't parseable, we can't validate
if ( ! GetISOHashValues ( hashData , out long _ , out string _ , out string _ , out string sha1 ) )
2022-02-03 15:43:17 -08:00
{
resultProgress ? . Report ( Result . Failure ( "Line could not be parsed for hash data" ) ) ;
2022-03-12 21:09:51 -08:00
return ( false , null ) ;
2022-02-03 15:43:17 -08:00
}
2021-12-25 21:23:24 -08:00
// Get all matching IDs for the track
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2021-12-25 21:23:24 -08:00
List < int > newIds = ListSearchResults ( wc , sha1 ) ;
2022-07-26 13:47:19 -07:00
#else
List < int > newIds = await ListSearchResults ( wc , sha1 ) ;
#endif
2021-12-25 21:23:24 -08:00
// 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" ) ) ;
2022-03-12 21:09:51 -08:00
return ( false , null ) ;
2021-12-25 21:23:24 -08:00
}
2022-03-12 21:09:51 -08:00
// If no IDs match any track, just return
2021-12-25 21:23:24 -08:00
if ( ! newIds . Any ( ) )
2022-03-12 21:09:51 -08:00
return ( false , null ) ;
2021-12-25 21:23:24 -08:00
2022-03-12 21:09:51 -08:00
// Join the list of found IDs to the existing list, if possible
if ( info . PartiallyMatchedIDs . Any ( ) )
info . PartiallyMatchedIDs . AddRange ( newIds ) ;
2021-12-25 21:23:24 -08:00
else
2022-03-12 21:09:51 -08:00
info . PartiallyMatchedIDs = newIds ;
2022-02-03 15:43:17 -08:00
2022-03-12 21:09:51 -08:00
return ( true , newIds ) ;
2021-12-25 21:23:24 -08:00
}
/// <summary>
2023-07-23 16:41:40 -04:00
/// Validate a universal hash against Redump, if possible
/// </summary>
/// <param name="wc">RedumpWebClient for making the connection</param>
/// <param name="info">Existing SubmissionInfo object to fill</param>
/// <param name="resultProgress">Optional result progress callback</param>
/// <returns>True if the track was found, false otherwise; List of found values, if possible</returns>
#if NET48 | | NETSTANDARD2_1
private static ( bool , List < int > ) ValidateUniversalHash ( RedumpWebClient wc , SubmissionInfo info , IProgress < Result > resultProgress = null )
#else
private async static Task < ( bool , List < int > ) > ValidateUniversalHash ( RedumpHttpClient wc , SubmissionInfo info , IProgress < Result > resultProgress = null )
#endif
{
// If we don't have a universal hash
string universalHash = info . CommonDiscInfo . CommentsSpecialFields [ SiteCode . UniversalHash ] ;
if ( string . IsNullOrEmpty ( universalHash ) )
{
2023-07-23 19:16:57 -04:00
resultProgress ? . Report ( Result . Failure ( "Universal hash was missing" ) ) ;
2023-07-23 16:41:40 -04:00
return ( false , null ) ;
}
// Format the universal hash for finding within the comments
2023-07-23 19:16:57 -04:00
universalHash = $"{universalHash.Substring(0, universalHash.Length - 1)}/comments/only" ;
2023-07-23 16:41:40 -04:00
// Get all matching IDs for the hash
#if NET48 | | NETSTANDARD2_1
2023-07-23 19:16:57 -04:00
List < int > newIds = ListSearchResults ( wc , universalHash , filterForwardSlashes : false ) ;
2023-07-23 16:41:40 -04:00
#else
2023-07-23 19:16:57 -04:00
List < int > newIds = await ListSearchResults ( wc , universalHash , filterForwardSlashes : false ) ;
2023-07-23 16:41:40 -04:00
#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 ) ;
}
/// <summary>
2021-12-25 21:23:24 -08:00
/// Validate that the current track count and remote track count match
/// </summary>
/// <param name="wc">RedumpWebClient for making the connection</param>
/// <param name="id">Redump disc ID to retrieve</param>
/// <param name="localCount">Local count of tracks for the current disc</param>
/// <returns>True if the track count matches, false otherwise</returns>
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2021-12-25 21:23:24 -08:00
private static bool ValidateTrackCount ( RedumpWebClient wc , int id , int localCount )
2022-07-26 13:47:19 -07:00
#else
private async static Task < bool > ValidateTrackCount ( RedumpHttpClient wc , int id , int localCount )
#endif
2021-12-25 21:23:24 -08:00
{
// If we can't pull the remote data, we can't match
2022-07-26 13:47:19 -07:00
#if NET48 | | NETSTANDARD2_1
2021-12-25 21:23:24 -08:00
string discData = wc . DownloadSingleSiteID ( id ) ;
2022-07-26 13:47:19 -07:00
#else
string discData = await wc . DownloadSingleSiteID ( id ) ;
#endif
2021-12-25 21:23:24 -08:00
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 ;
}
2022-10-04 13:36:50 -07:00
#endregion
2022-01-27 10:58:40 -08:00
2022-10-04 13:36:50 -07:00
#region Helpers
2022-01-27 10:58:40 -08:00
/// <summary>
/// Format a single site tag to string
/// </summary>
/// <param name="kvp">KeyValuePair representing the site tag and value</param>
/// <returns>String-formatted tag and value</returns>
private static string FormatSiteTag ( KeyValuePair < SiteCode ? , string > 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)}" ;
}
/// <summary>
/// Check if a site code is boolean or not
/// </summary>
/// <param name="siteCode">SiteCode to check</param>
/// <returns>True if the code field is a flag with no value, false otherwise</returns>
/// <remarks>TODO: This should move to Extensions at some point</remarks>
private static bool IsBoolean ( SiteCode ? siteCode )
{
switch ( siteCode )
{
case SiteCode . PostgapType :
case SiteCode . VCD :
return true ;
default :
return false ;
}
}
/// <summary>
/// Check if a site code is multi-line or not
/// </summary>
/// <param name="siteCode">SiteCode to check</param>
/// <returns>True if the code field is multiline by default, false otherwise</returns>
/// <remarks>TODO: This should move to Extensions at some point</remarks>
private static bool IsMultiLine ( SiteCode ? siteCode )
{
switch ( siteCode )
{
case SiteCode . Extras :
2022-07-05 22:43:28 -07:00
case SiteCode . Filename :
2022-02-02 12:50:32 -08:00
case SiteCode . Games :
2022-01-27 10:58:40 -08:00
case SiteCode . GameFootage :
2022-04-19 12:49:18 -07:00
case SiteCode . Multisession :
2022-01-27 10:58:40 -08:00
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 ;
}
}
2022-01-27 12:13:17 -08:00
/// <summary>
/// Order comment code tags according to Redump requirements
/// </summary>
/// <returns>Ordered list of KeyValuePairs representing the tags and values</returns>
2023-09-05 00:08:09 -04:00
#if NET48
2022-01-27 12:13:17 -08:00
private static List < KeyValuePair < SiteCode ? , string > > OrderCommentTags ( Dictionary < SiteCode ? , string > tags )
2023-09-05 00:08:09 -04:00
#else
private static List < KeyValuePair < SiteCode ? , string > > OrderCommentTags ( Dictionary < SiteCode , string > tags )
#endif
2022-01-27 12:13:17 -08:00
{
var sorted = new List < KeyValuePair < SiteCode ? , string > > ( ) ;
// If the input is invalid, just return an empty set
if ( tags = = null | | tags . Count = = 0 )
return sorted ;
2022-01-27 13:35:47 -08:00
// Identifying Info
if ( tags . ContainsKey ( SiteCode . AlternativeTitle ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . AlternativeTitle , tags [ SiteCode . AlternativeTitle ] ) ) ;
if ( tags . ContainsKey ( SiteCode . AlternativeForeignTitle ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . AlternativeForeignTitle , tags [ SiteCode . AlternativeForeignTitle ] ) ) ;
2022-08-25 10:38:20 -07:00
if ( tags . ContainsKey ( SiteCode . InternalName ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . InternalName , tags [ SiteCode . InternalName ] ) ) ;
2022-01-27 13:35:47 -08:00
if ( tags . ContainsKey ( SiteCode . InternalSerialName ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . InternalSerialName , tags [ SiteCode . InternalSerialName ] ) ) ;
2022-01-30 15:57:12 -08:00
if ( tags . ContainsKey ( SiteCode . VolumeLabel ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . VolumeLabel , tags [ SiteCode . VolumeLabel ] ) ) ;
2022-04-12 10:10:07 -07:00
if ( tags . ContainsKey ( SiteCode . Multisession ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . Multisession , tags [ SiteCode . Multisession ] ) ) ;
2023-02-24 13:00:38 -05:00
if ( tags . ContainsKey ( SiteCode . UniversalHash ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . UniversalHash , tags [ SiteCode . UniversalHash ] ) ) ;
2023-02-24 13:40:14 -05:00
if ( tags . ContainsKey ( SiteCode . RingNonZeroDataStart ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . RingNonZeroDataStart , tags [ SiteCode . RingNonZeroDataStart ] ) ) ;
2022-04-12 10:10:07 -07:00
2022-01-27 13:35:47 -08:00
if ( tags . ContainsKey ( SiteCode . XMID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . XMID , tags [ SiteCode . XMID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . XeMID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . XeMID , tags [ SiteCode . XeMID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . DMIHash ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . DMIHash , tags [ SiteCode . DMIHash ] ) ) ;
if ( tags . ContainsKey ( SiteCode . PFIHash ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . PFIHash , tags [ SiteCode . PFIHash ] ) ) ;
if ( tags . ContainsKey ( SiteCode . SSHash ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . SSHash , tags [ SiteCode . SSHash ] ) ) ;
if ( tags . ContainsKey ( SiteCode . SSVersion ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . SSVersion , tags [ SiteCode . SSVersion ] ) ) ;
2022-07-05 22:11:21 -07:00
if ( tags . ContainsKey ( SiteCode . Filename ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . Filename , tags [ SiteCode . Filename ] ) ) ;
2022-01-27 13:35:47 -08:00
if ( tags . ContainsKey ( SiteCode . BBFCRegistrationNumber ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . BBFCRegistrationNumber , tags [ SiteCode . BBFCRegistrationNumber ] ) ) ;
2023-08-26 22:57:44 -04:00
if ( tags . ContainsKey ( SiteCode . CDProjektID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . CDProjektID , tags [ SiteCode . CDProjektID ] ) ) ;
2022-01-27 13:35:47 -08:00
if ( tags . ContainsKey ( SiteCode . DiscHologramID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . DiscHologramID , tags [ SiteCode . DiscHologramID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . DNASDiscID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . DNASDiscID , tags [ SiteCode . DNASDiscID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . ISBN ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . ISBN , tags [ SiteCode . ISBN ] ) ) ;
if ( tags . ContainsKey ( SiteCode . ISSN ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . ISSN , tags [ SiteCode . ISSN ] ) ) ;
if ( tags . ContainsKey ( SiteCode . PPN ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . PPN , tags [ SiteCode . PPN ] ) ) ;
if ( tags . ContainsKey ( SiteCode . VFCCode ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . VFCCode , tags [ SiteCode . VFCCode ] ) ) ;
if ( tags . ContainsKey ( SiteCode . Genre ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . Genre , tags [ SiteCode . Genre ] ) ) ;
if ( tags . ContainsKey ( SiteCode . Series ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . Series , tags [ SiteCode . Series ] ) ) ;
if ( tags . ContainsKey ( SiteCode . PostgapType ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . PostgapType , tags [ SiteCode . PostgapType ] ) ) ;
if ( tags . ContainsKey ( SiteCode . VCD ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . VCD , tags [ SiteCode . VCD ] ) ) ;
// Publisher / Company IDs
if ( tags . ContainsKey ( SiteCode . AcclaimID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . AcclaimID , tags [ SiteCode . AcclaimID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . ActivisionID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . ActivisionID , tags [ SiteCode . ActivisionID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . BandaiID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . BandaiID , tags [ SiteCode . BandaiID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . ElectronicArtsID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . ElectronicArtsID , tags [ SiteCode . ElectronicArtsID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . FoxInteractiveID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . FoxInteractiveID , tags [ SiteCode . FoxInteractiveID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . GTInteractiveID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . GTInteractiveID , tags [ SiteCode . GTInteractiveID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . JASRACID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . JASRACID , tags [ SiteCode . JASRACID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . KingRecordsID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . KingRecordsID , tags [ SiteCode . KingRecordsID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . KoeiID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . KoeiID , tags [ SiteCode . KoeiID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . KonamiID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . KonamiID , tags [ SiteCode . KonamiID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . LucasArtsID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . LucasArtsID , tags [ SiteCode . LucasArtsID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . MicrosoftID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . MicrosoftID , tags [ SiteCode . MicrosoftID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . NaganoID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . NaganoID , tags [ SiteCode . NaganoID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . NamcoID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . NamcoID , tags [ SiteCode . NamcoID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . NipponIchiSoftwareID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . NipponIchiSoftwareID , tags [ SiteCode . NipponIchiSoftwareID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . OriginID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . OriginID , tags [ SiteCode . OriginID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . PonyCanyonID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . PonyCanyonID , tags [ SiteCode . PonyCanyonID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . SegaID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . SegaID , tags [ SiteCode . SegaID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . SelenID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . SelenID , tags [ SiteCode . SelenID ] ) ) ;
2022-01-28 09:24:37 -08:00
if ( tags . ContainsKey ( SiteCode . SierraID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . SierraID , tags [ SiteCode . SierraID ] ) ) ;
2022-01-27 13:35:47 -08:00
if ( tags . ContainsKey ( SiteCode . TaitoID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . TaitoID , tags [ SiteCode . TaitoID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . UbisoftID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . UbisoftID , tags [ SiteCode . UbisoftID ] ) ) ;
if ( tags . ContainsKey ( SiteCode . ValveID ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . ValveID , tags [ SiteCode . ValveID ] ) ) ;
2022-01-27 12:13:17 -08:00
return sorted ;
}
/// <summary>
/// Order content code tags according to Redump requirements
/// </summary>
/// <returns>Ordered list of KeyValuePairs representing the tags and values</returns>
2023-09-05 00:08:09 -04:00
#if NET48
2022-01-27 12:13:17 -08:00
private static List < KeyValuePair < SiteCode ? , string > > OrderContentTags ( Dictionary < SiteCode ? , string > tags )
2023-09-05 00:08:09 -04:00
#else
private static List < KeyValuePair < SiteCode ? , string > > OrderContentTags ( Dictionary < SiteCode , string > tags )
#endif
2022-01-27 12:13:17 -08:00
{
var sorted = new List < KeyValuePair < SiteCode ? , string > > ( ) ;
// 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 ? , string > ( SiteCode . Games , tags [ SiteCode . Games ] ) ) ;
if ( tags . ContainsKey ( SiteCode . NetYarozeGames ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . NetYarozeGames , tags [ SiteCode . NetYarozeGames ] ) ) ;
2022-10-04 13:36:50 -07:00
2022-01-27 12:13:17 -08:00
// Demos
if ( tags . ContainsKey ( SiteCode . PlayableDemos ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . PlayableDemos , tags [ SiteCode . PlayableDemos ] ) ) ;
if ( tags . ContainsKey ( SiteCode . RollingDemos ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . RollingDemos , tags [ SiteCode . RollingDemos ] ) ) ;
if ( tags . ContainsKey ( SiteCode . TechDemos ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . TechDemos , tags [ SiteCode . TechDemos ] ) ) ;
// Video
if ( tags . ContainsKey ( SiteCode . GameFootage ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . GameFootage , tags [ SiteCode . GameFootage ] ) ) ;
if ( tags . ContainsKey ( SiteCode . Videos ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . Videos , tags [ SiteCode . Videos ] ) ) ;
// Miscellaneous
if ( tags . ContainsKey ( SiteCode . Patches ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . Patches , tags [ SiteCode . Patches ] ) ) ;
if ( tags . ContainsKey ( SiteCode . Savegames ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . Savegames , tags [ SiteCode . Savegames ] ) ) ;
if ( tags . ContainsKey ( SiteCode . Extras ) )
sorted . Add ( new KeyValuePair < SiteCode ? , string > ( SiteCode . Extras , tags [ SiteCode . Extras ] ) ) ;
return sorted ;
}
2023-09-05 00:08:09 -04:00
#endregion
2021-09-29 16:16:54 -07:00
}
}