mirror of
https://github.com/SabreTools/SabreTools.RedumpLib.git
synced 2026-02-04 05:36:11 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66bb3b75b2 | ||
|
|
9d7d46673a | ||
|
|
16d196c902 | ||
|
|
c93da92f19 | ||
|
|
a219d0c5de | ||
|
|
02e6f0e85f | ||
|
|
9dfec64e4e | ||
|
|
4c12693a33 | ||
|
|
acea06c05f | ||
|
|
067c5cfbbc | ||
|
|
ff04b8ec6f | ||
|
|
e09c895cf1 | ||
|
|
1b68253089 | ||
|
|
5b9a2d6b74 | ||
|
|
d5c7ef74d4 | ||
|
|
f60cd2985d | ||
|
|
7fcb6aa949 | ||
|
|
f22b1b036b | ||
|
|
ec045448c5 | ||
|
|
93873ea204 | ||
|
|
341edc56bd | ||
|
|
da4bdac6e2 | ||
|
|
7fe595ee0a | ||
|
|
8a9f62f5a4 | ||
|
|
dbb7cf7ef9 | ||
|
|
d591ee1550 | ||
|
|
9153c931a5 | ||
|
|
99ebd1f3ac | ||
|
|
844f5506f5 | ||
|
|
4be01b25ab | ||
|
|
22e2e73f65 | ||
|
|
831ea86d4f | ||
|
|
4475dba94c | ||
|
|
63a758c005 | ||
|
|
7e81f723ca | ||
|
|
f69c7e6bb2 | ||
|
|
9d2803a6df | ||
|
|
23f1ceac99 | ||
|
|
55c621b615 | ||
|
|
29cce4d4b9 | ||
|
|
1f37ece28d | ||
|
|
6acac60376 | ||
|
|
3f9b09b943 | ||
|
|
2b1ee393d4 | ||
|
|
887c443a17 | ||
|
|
5ad9bebf88 | ||
|
|
8c8e624ac2 | ||
|
|
d7b2d13d8b | ||
|
|
8c8ae49a3b | ||
|
|
b77eec5063 | ||
|
|
5d992566b5 | ||
|
|
46bde960f3 | ||
|
|
ba2c3a592f | ||
|
|
f0bca60d63 | ||
|
|
a81dc6d680 | ||
|
|
1a6ebfdbf0 | ||
|
|
1391d90768 | ||
|
|
dfe0c2ffaa | ||
|
|
4027470107 | ||
|
|
5bccdeaf2c | ||
|
|
27534d2539 | ||
|
|
9dea759f8a | ||
|
|
55ea639caf | ||
|
|
cb8ac845d5 | ||
|
|
bf551e614b | ||
|
|
1b97aca46e | ||
|
|
b0b9f2099b |
43
.github/workflows/build_nupkg.yml
vendored
Normal file
43
.github/workflows/build_nupkg.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Nuget Pack
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'Nuget Package'
|
||||
path: 'SabreTools.RedumpLib/bin/Release/*.nupkg'
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: 'SabreTools.RedumpLib/bin/Release/*.nupkg'
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
17
.github/workflows/check_pr.yml
vendored
Normal file
17
.github/workflows/check_pr.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Build PR
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
@@ -1,3 +1,5 @@
|
||||
# SabreTools.RedumpLib
|
||||
|
||||
This library comprises interaction logic for [Redump](http://redump.org/). Because there is no formal API for the site, this library interacts with the site through normal HTTP methods. It includes a fairly comprehensive reference of supported parts of the site, including URLs, page information, and packs.
|
||||
|
||||
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.RedumpLib).
|
||||
|
||||
28
SabreTools.RedumpLib.Test/BuilderTests.cs
Normal file
28
SabreTools.RedumpLib.Test/BuilderTests.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.RedumpLib.Test
|
||||
{
|
||||
public class BuilderTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("success_complete.json", false)]
|
||||
[InlineData("success_invalid.json", false)] // Fully in valid returns a default object
|
||||
[InlineData("success_partial.json", false)]
|
||||
[InlineData("fail_invalid.json", true)]
|
||||
public void CreateFromFileTest(string filename, bool expectNull)
|
||||
{
|
||||
// Get the full path to the test file
|
||||
string path = Path.Combine(Environment.CurrentDirectory, "TestData", filename);
|
||||
|
||||
// Try to create the submission info from file
|
||||
var si = Builder.CreateFromFile(path);
|
||||
|
||||
// Check for an expected result
|
||||
Assert.Equal(expectNull, si == null);
|
||||
}
|
||||
}
|
||||
}
|
||||
201
SabreTools.RedumpLib.Test/EnumExtensionsTests.cs
Normal file
201
SabreTools.RedumpLib.Test/EnumExtensionsTests.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.RedumpLib.Test
|
||||
{
|
||||
public class EnumExtensionsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// MediaType values that support drive speeds
|
||||
/// </summary>
|
||||
private static readonly MediaType?[] _supportDriveSpeeds =
|
||||
[
|
||||
MediaType.CDROM,
|
||||
MediaType.DVD,
|
||||
MediaType.GDROM,
|
||||
MediaType.HDDVD,
|
||||
MediaType.BluRay,
|
||||
MediaType.NintendoGameCubeGameDisc,
|
||||
MediaType.NintendoWiiOpticalDisc,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// RedumpSystem values that are considered Audio
|
||||
/// </summary>
|
||||
private static readonly RedumpSystem?[] _audioSystems =
|
||||
[
|
||||
RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem,
|
||||
RedumpSystem.AudioCD,
|
||||
RedumpSystem.DVDAudio,
|
||||
RedumpSystem.HasbroiONEducationalGamingSystem,
|
||||
RedumpSystem.HasbroVideoNow,
|
||||
RedumpSystem.HasbroVideoNowColor,
|
||||
RedumpSystem.HasbroVideoNowJr,
|
||||
RedumpSystem.HasbroVideoNowXP,
|
||||
RedumpSystem.PlayStationGameSharkUpdates,
|
||||
RedumpSystem.PhilipsCDi,
|
||||
RedumpSystem.SuperAudioCD,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// RedumpSystem values that are considered markers
|
||||
/// </summary>
|
||||
private static readonly RedumpSystem?[] _markerSystems =
|
||||
[
|
||||
RedumpSystem.MarkerArcadeEnd,
|
||||
RedumpSystem.MarkerComputerEnd,
|
||||
RedumpSystem.MarkerDiscBasedConsoleEnd,
|
||||
RedumpSystem.MarkerOtherEnd,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// RedumpSystem values that are have reversed ringcodes
|
||||
/// </summary>
|
||||
private static readonly RedumpSystem?[] _reverseRingcodeSystems =
|
||||
[
|
||||
RedumpSystem.SonyPlayStation2,
|
||||
RedumpSystem.SonyPlayStation3,
|
||||
RedumpSystem.SonyPlayStation4,
|
||||
RedumpSystem.SonyPlayStation5,
|
||||
RedumpSystem.SonyPlayStationPortable,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// RedumpSystem values that are considered XGD
|
||||
/// </summary>
|
||||
private static readonly RedumpSystem?[] _xgdSystems =
|
||||
[
|
||||
RedumpSystem.MicrosoftXbox,
|
||||
RedumpSystem.MicrosoftXbox360,
|
||||
RedumpSystem.MicrosoftXboxOne,
|
||||
RedumpSystem.MicrosoftXboxSeriesXS,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Check that all systems with reversed ringcodes are marked properly
|
||||
/// </summary>
|
||||
/// <param name="redumpSystem">RedumpSystem value to check</param>
|
||||
/// <param name="expected">The expected value to come from the check</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateReversedRingcodeSystemsTestData))]
|
||||
public void HasReversedRingcodesTest(RedumpSystem? redumpSystem, bool expected)
|
||||
{
|
||||
bool actual = redumpSystem.HasReversedRingcodes();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that all audio systems are marked properly
|
||||
/// </summary>
|
||||
/// <param name="redumpSystem">RedumpSystem value to check</param>
|
||||
/// <param name="expected">The expected value to come from the check</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateAudioSystemsTestData))]
|
||||
public void IsAudioTest(RedumpSystem? redumpSystem, bool expected)
|
||||
{
|
||||
bool actual = redumpSystem.IsAudio();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that all marker systems are marked properly
|
||||
/// </summary>
|
||||
/// <param name="redumpSystem">RedumpSystem value to check</param>
|
||||
/// <param name="expected">The expected value to come from the check</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateMarkerSystemsTestData))]
|
||||
public void IsMarkerTest(RedumpSystem? redumpSystem, bool expected)
|
||||
{
|
||||
bool actual = redumpSystem.IsMarker();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that all XGD systems are marked properly
|
||||
/// </summary>
|
||||
/// <param name="redumpSystem">RedumpSystem value to check</param>
|
||||
/// <param name="expected">The expected value to come from the check</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateXGDSystemsTestData))]
|
||||
public void IsXGDTest(RedumpSystem? redumpSystem, bool expected)
|
||||
{
|
||||
bool actual = redumpSystem.IsXGD();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of RedumpSystem values that are considered Audio
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
|
||||
public static List<object?[]> GenerateAudioSystemsTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, false } };
|
||||
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
|
||||
{
|
||||
if (_audioSystems.Contains(redumpSystem))
|
||||
testData.Add([redumpSystem, true]);
|
||||
else
|
||||
testData.Add([redumpSystem, false]);
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of RedumpSystem values that are considered markers
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
|
||||
public static List<object?[]> GenerateMarkerSystemsTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, false } };
|
||||
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
|
||||
{
|
||||
if (_markerSystems.Contains(redumpSystem))
|
||||
testData.Add([redumpSystem, true]);
|
||||
else
|
||||
testData.Add([redumpSystem, false]);
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of RedumpSystem values that are considered markers
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
|
||||
public static List<object?[]> GenerateReversedRingcodeSystemsTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, false } };
|
||||
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
|
||||
{
|
||||
if (_reverseRingcodeSystems.Contains(redumpSystem))
|
||||
testData.Add([redumpSystem, true]);
|
||||
else
|
||||
testData.Add([redumpSystem, false]);
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of RedumpSystem values that are considered XGD
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
|
||||
public static List<object?[]> GenerateXGDSystemsTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, false } };
|
||||
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
|
||||
{
|
||||
if (_xgdSystems.Contains(redumpSystem))
|
||||
testData.Add([redumpSystem, true]);
|
||||
else
|
||||
testData.Add([redumpSystem, false]);
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
}
|
||||
}
|
||||
717
SabreTools.RedumpLib.Test/ExtensionsTests.cs
Normal file
717
SabreTools.RedumpLib.Test/ExtensionsTests.cs
Normal file
@@ -0,0 +1,717 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.RedumpLib.Test
|
||||
{
|
||||
// TODO: Add tests for string-to-enum conversion
|
||||
public class ExtensionsTests
|
||||
{
|
||||
#region Cross-Enumeration
|
||||
|
||||
/// <summary>
|
||||
/// DiscType values that map to MediaType
|
||||
/// </summary>
|
||||
private static readonly DiscType?[] _mappableDiscTypes = new DiscType?[]
|
||||
{
|
||||
DiscType.BD25,
|
||||
DiscType.BD33,
|
||||
DiscType.BD50,
|
||||
DiscType.BD66,
|
||||
DiscType.BD100,
|
||||
DiscType.BD128,
|
||||
DiscType.CD,
|
||||
DiscType.DVD5,
|
||||
DiscType.DVD9,
|
||||
DiscType.GDROM,
|
||||
DiscType.HDDVDSL,
|
||||
DiscType.HDDVDDL,
|
||||
DiscType.NintendoGameCubeGameDisc,
|
||||
DiscType.NintendoWiiOpticalDiscSL,
|
||||
DiscType.NintendoWiiOpticalDiscDL,
|
||||
DiscType.NintendoWiiUOpticalDiscSL,
|
||||
DiscType.UMDSL,
|
||||
DiscType.UMDDL,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// MediaType values that map to DiscType
|
||||
/// </summary>
|
||||
private static readonly MediaType?[] _mappableMediaTypes = new MediaType?[]
|
||||
{
|
||||
MediaType.BluRay,
|
||||
MediaType.CDROM,
|
||||
MediaType.DVD,
|
||||
MediaType.GDROM,
|
||||
MediaType.HDDVD,
|
||||
MediaType.NintendoGameCubeGameDisc,
|
||||
MediaType.NintendoWiiOpticalDisc,
|
||||
MediaType.NintendoWiiUOpticalDisc,
|
||||
MediaType.UMD,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Check that every supported system has some set of MediaTypes supported
|
||||
/// </summary>
|
||||
/// <param name="redumpSystem">RedumpSystem value to check</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateRedumpSystemMappingTestData))]
|
||||
public void MediaTypesTest(RedumpSystem? redumpSystem)
|
||||
{
|
||||
var actual = redumpSystem.MediaTypes();
|
||||
Assert.NotEmpty(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that both mappable and unmappable media types output correctly
|
||||
/// </summary>
|
||||
/// <param name="mediaType">MediaType value to check</param>
|
||||
/// <param name="expectNull">True to expect a null mapping, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateMediaTypeMappingTestData))]
|
||||
public void ToDiscTypeTest(MediaType? mediaType, bool expectNull)
|
||||
{
|
||||
DiscType? actual = mediaType.ToDiscType();
|
||||
Assert.Equal(expectNull, actual == null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that DiscType values all map to something appropriate
|
||||
/// </summary>
|
||||
/// <param name="discType">DiscType value to check</param>
|
||||
/// <param name="expectNull">True to expect a null mapping, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateDiscTypeMappingTestData))]
|
||||
public void ToMediaTypeTest(DiscType? discType, bool expectNull)
|
||||
{
|
||||
MediaType? actual = discType.ToMediaType();
|
||||
Assert.Equal(expectNull, actual == null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of DiscType values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of DiscType values</returns>
|
||||
public static List<object?[]> GenerateDiscTypeMappingTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (DiscType? discType in Enum.GetValues(typeof(DiscType)))
|
||||
{
|
||||
if (_mappableDiscTypes.Contains(discType))
|
||||
testData.Add(new object?[] { discType, false });
|
||||
else
|
||||
testData.Add(new object?[] { discType, true });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of RedumpSystem values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
|
||||
public static List<object?[]> GenerateRedumpSystemMappingTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null } };
|
||||
foreach (RedumpSystem? redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
|
||||
{
|
||||
testData.Add(new object?[] { redumpSystem });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of mappable media types
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of MediaTypes</returns>
|
||||
public static List<object?[]> GenerateMediaTypeMappingTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
|
||||
foreach (MediaType? mediaType in Enum.GetValues(typeof(MediaType)))
|
||||
{
|
||||
if (_mappableMediaTypes.Contains(mediaType))
|
||||
testData.Add(new object?[] { mediaType, false });
|
||||
else
|
||||
testData.Add(new object?[] { mediaType, true });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disc Category
|
||||
|
||||
/// <summary>
|
||||
/// Check that every DiscCategory has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="discCategory">DiscCategory value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateDiscCategoryTestData))]
|
||||
public void DiscCategoryLongNameTest(DiscCategory? discCategory, bool expectNull)
|
||||
{
|
||||
var actual = discCategory.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of DiscCategory values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of DiscCategory values</returns>
|
||||
public static List<object?[]> GenerateDiscCategoryTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (DiscCategory? discCategory in Enum.GetValues(typeof(DiscCategory)))
|
||||
{
|
||||
testData.Add(new object?[] { discCategory, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disc Type
|
||||
|
||||
/// <summary>
|
||||
/// Check that every DiscType has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="discType">DiscType value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateDiscTypeTestData))]
|
||||
public void DiscTypeLongNameTest(DiscType? discType, bool expectNull)
|
||||
{
|
||||
var actual = discType.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of DiscType values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of DiscType values</returns>
|
||||
public static List<object?[]> GenerateDiscTypeTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (DiscType? discType in Enum.GetValues(typeof(DiscType)))
|
||||
{
|
||||
if (discType == DiscType.NONE)
|
||||
testData.Add(new object?[] { discType, true });
|
||||
else
|
||||
testData.Add(new object?[] { discType, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Language
|
||||
|
||||
/// <summary>
|
||||
/// Check that every Language has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="language">Language value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateLanguageTestData))]
|
||||
public void LanguageLongNameTest(Language? language, bool expectNull)
|
||||
{
|
||||
var actual = language.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that every Language has a short name provided
|
||||
/// </summary>
|
||||
/// <param name="language">Language value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateLanguageTestData))]
|
||||
public void LanguageShortNameTest(Language? language, bool expectNull)
|
||||
{
|
||||
var actual = language.ShortName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that every Language that has an ISO 639-1 code is unique
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LanguageNoDuplicateTwoLetterCodeTest()
|
||||
{
|
||||
var fullLanguages = Enum.GetValues(typeof(Language)).Cast<Language?>().ToList();
|
||||
var filteredLanguages = new Dictionary<string, Language?>();
|
||||
|
||||
int totalCount = 0;
|
||||
foreach (Language? language in fullLanguages)
|
||||
{
|
||||
var code = language.TwoLetterCode();
|
||||
if (string.IsNullOrEmpty(code))
|
||||
continue;
|
||||
|
||||
// Throw if the code already exists
|
||||
if (filteredLanguages.ContainsKey(code))
|
||||
throw new DuplicateNameException($"Code {code} already in dictionary");
|
||||
|
||||
filteredLanguages[code] = language;
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
Assert.Equal(totalCount, filteredLanguages.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that every Language that has a standard/bibliographic ISO 639-2 code is unique
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LanguageNoDuplicateThreeLetterCodeTest()
|
||||
{
|
||||
var fullLanguages = Enum.GetValues(typeof(Language)).Cast<Language?>().ToList();
|
||||
var filteredLanguages = new Dictionary<string, Language?>();
|
||||
|
||||
int totalCount = 0;
|
||||
foreach (Language? language in fullLanguages)
|
||||
{
|
||||
var code = language.ThreeLetterCode();
|
||||
if (string.IsNullOrEmpty(code))
|
||||
continue;
|
||||
|
||||
// Throw if the code already exists
|
||||
if (filteredLanguages.ContainsKey(code))
|
||||
throw new DuplicateNameException($"Code {code} already in dictionary");
|
||||
|
||||
filteredLanguages[code] = language;
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
Assert.Equal(totalCount, filteredLanguages.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that every Language that has a terminology ISO 639-2 code is unique
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LanguageNoDuplicateThreeLetterCodeAltTest()
|
||||
{
|
||||
var fullLanguages = Enum.GetValues(typeof(Language)).Cast<Language?>().ToList();
|
||||
var filteredLanguages = new Dictionary<string, Language?>();
|
||||
|
||||
int totalCount = 0;
|
||||
foreach (Language? language in fullLanguages)
|
||||
{
|
||||
var code = language.ThreeLetterCodeAlt();
|
||||
if (string.IsNullOrEmpty(code))
|
||||
continue;
|
||||
|
||||
// Throw if the code already exists
|
||||
if (filteredLanguages.ContainsKey(code))
|
||||
throw new DuplicateNameException($"Code {code} already in dictionary");
|
||||
|
||||
filteredLanguages[code] = language;
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
Assert.Equal(totalCount, filteredLanguages.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of Language values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of Language values</returns>
|
||||
public static List<object?[]> GenerateLanguageTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (Language? language in Enum.GetValues(typeof(Language)))
|
||||
{
|
||||
testData.Add(new object?[] { language, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Language Selection
|
||||
|
||||
/// <summary>
|
||||
/// Check that every LanguageSelection has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="languageSelection">LanguageSelection value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateLanguageSelectionTestData))]
|
||||
public void LanguageSelectionLongNameTest(LanguageSelection? languageSelection, bool expectNull)
|
||||
{
|
||||
var actual = languageSelection.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of LanguageSelection values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of LanguageSelection values</returns>
|
||||
public static List<object?[]> GenerateLanguageSelectionTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (LanguageSelection? languageSelection in Enum.GetValues(typeof(LanguageSelection)))
|
||||
{
|
||||
testData.Add(new object?[] { languageSelection, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Media Type
|
||||
|
||||
/// <summary>
|
||||
/// Check that every MediaType has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="mediaType">MediaType value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateMediaTypeTestData))]
|
||||
public void MediaTypeLongNameTest(MediaType? mediaType, bool expectNull)
|
||||
{
|
||||
var actual = mediaType.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that every MediaType has a short name provided
|
||||
/// </summary>
|
||||
/// <param name="mediaType">MediaType value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateMediaTypeTestData))]
|
||||
public void MediaTypeShortNameTest(MediaType? mediaType, bool expectNull)
|
||||
{
|
||||
var actual = mediaType.ShortName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of MediaType values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of MediaType values</returns>
|
||||
public static List<object?[]> GenerateMediaTypeTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (MediaType? mediaType in Enum.GetValues(typeof(MediaType)))
|
||||
{
|
||||
testData.Add(new object?[] { mediaType, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Region
|
||||
|
||||
/// <summary>
|
||||
/// Check that every Region has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="region">Region value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateRegionTestData))]
|
||||
public void RegionLongNameTest(Region? region, bool expectNull)
|
||||
{
|
||||
var actual = region.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that every Region has a short name provided
|
||||
/// </summary>
|
||||
/// <param name="region">Region value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateRegionTestData))]
|
||||
public void RegionShortNameTest(Region? region, bool expectNull)
|
||||
{
|
||||
var actual = region.ShortName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that every Language that has an ISO 639-1 code is unique
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RegionNoDuplicateShortNameTest()
|
||||
{
|
||||
var fullRegions = Enum.GetValues(typeof(Region)).Cast<Region?>().ToList();
|
||||
var filteredRegions = new Dictionary<string, Region?>();
|
||||
|
||||
int totalCount = 0;
|
||||
foreach (Region? region in fullRegions)
|
||||
{
|
||||
var code = region.ShortName();
|
||||
if (string.IsNullOrEmpty(code))
|
||||
continue;
|
||||
|
||||
// Throw if the code already exists
|
||||
if (filteredRegions.ContainsKey(code))
|
||||
throw new DuplicateNameException($"Code {code} already in dictionary");
|
||||
|
||||
filteredRegions[code] = region;
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
Assert.Equal(totalCount, filteredRegions.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of Region values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of Region values</returns>
|
||||
public static List<object?[]> GenerateRegionTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (Region? region in Enum.GetValues(typeof(Region)))
|
||||
{
|
||||
testData.Add(new object?[] { region, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Site Code
|
||||
|
||||
/// <summary>
|
||||
/// Check that every SiteCode has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="siteCode">SiteCode value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateSiteCodeTestData))]
|
||||
public void SiteCodeLongNameTest(SiteCode? siteCode, bool expectNull)
|
||||
{
|
||||
var actual = siteCode.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that every SiteCode has a short name provided
|
||||
/// </summary>
|
||||
/// <param name="siteCode">SiteCode value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateSiteCodeTestData))]
|
||||
public void SiteCodeShortNameTest(SiteCode? siteCode, bool expectNull)
|
||||
{
|
||||
var actual = siteCode.ShortName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of SiteCode values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of SiteCode values</returns>
|
||||
public static List<object?[]> GenerateSiteCodeTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
|
||||
{
|
||||
testData.Add(new object?[] { siteCode, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region System
|
||||
|
||||
/// <summary>
|
||||
/// Check that every RedumpSystem has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="redumpSystem">RedumpSystem value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateRedumpSystemTestData))]
|
||||
public void RedumpSystemLongNameTest(RedumpSystem? redumpSystem, bool expectNull)
|
||||
{
|
||||
var actual = redumpSystem.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
// TODO: Re-enable the following test once non-Redump systems are accounted for
|
||||
|
||||
/// <summary>
|
||||
/// Check that every RedumpSystem has a short name provided
|
||||
/// </summary>
|
||||
/// <param name="redumpSystem">RedumpSystem value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
//[Theory]
|
||||
//[MemberData(nameof(GenerateRedumpSystemTestData))]
|
||||
//public void RedumpSystemShortNameTest(RedumpSystem? redumpSystem, bool expectNull)
|
||||
//{
|
||||
// string actual = redumpSystem.ShortName();
|
||||
|
||||
// if (expectNull)
|
||||
// Assert.Null(actual);
|
||||
// else
|
||||
// Assert.NotNull(actual);
|
||||
//}
|
||||
|
||||
// TODO: Test the other attributes as well
|
||||
// Most are bool checks so they're not as interesting to have unit tests around
|
||||
// SystemCategory always returns something as well, so is it worth testing?
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of RedumpSystem values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
|
||||
public static List<object?[]> GenerateRedumpSystemTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (RedumpSystem? redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
|
||||
{
|
||||
// We want to skip all markers for this
|
||||
if (redumpSystem.IsMarker())
|
||||
continue;
|
||||
|
||||
testData.Add(new object?[] { redumpSystem, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region System Category
|
||||
|
||||
/// <summary>
|
||||
/// Check that every SystemCategory has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="systemCategory">SystemCategory value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateSystemCategoryTestData))]
|
||||
public void SystemCategoryLongNameTest(SystemCategory? systemCategory, bool expectNull)
|
||||
{
|
||||
var actual = systemCategory.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of SystemCategory values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of SystemCategory values</returns>
|
||||
public static List<object?[]> GenerateSystemCategoryTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (SystemCategory? systemCategory in Enum.GetValues(typeof(SystemCategory)))
|
||||
{
|
||||
if (systemCategory == SystemCategory.NONE)
|
||||
testData.Add(new object?[] { systemCategory, true });
|
||||
else
|
||||
testData.Add(new object?[] { systemCategory, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Yes/No
|
||||
|
||||
/// <summary>
|
||||
/// Check that every YesNo has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="yesNo">YesNo value to check</param>
|
||||
/// <param name="expectNull">True to expect a null value, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateYesNoTestData))]
|
||||
public void YesNoLongNameTest(YesNo? yesNo, bool expectNull)
|
||||
{
|
||||
string actual = yesNo.LongName();
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of YesNo values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of YesNo values</returns>
|
||||
public static List<object?[]> GenerateYesNoTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, false } };
|
||||
foreach (YesNo? yesNo in Enum.GetValues(typeof(YesNo)))
|
||||
{
|
||||
testData.Add(new object?[] { yesNo, false });
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
47
SabreTools.RedumpLib.Test/SabreTools.RedumpLib.Test.csproj
Normal file
47
SabreTools.RedumpLib.Test/SabreTools.RedumpLib.Test.csproj
Normal file
@@ -0,0 +1,47 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.RedumpLib\SabreTools.RedumpLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="TestData\*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="TestData\*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeCoverage" Version="17.10.0-release-24177-07" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-release-24177-07" />
|
||||
<PackageReference Include="xunit" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageReference Include="xunit.analyzers" Version="1.13.0" />
|
||||
<PackageReference Include="xunit.assert" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.core" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.extensibility.core" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.extensibility.execution" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.runner.console" Version="2.8.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
180
SabreTools.RedumpLib.Test/SubmissionInfoTests.cs
Normal file
180
SabreTools.RedumpLib.Test/SubmissionInfoTests.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.RedumpLib.Test
|
||||
{
|
||||
public class SubmissionInfoTests
|
||||
{
|
||||
[Fact]
|
||||
public void EmptySerializationTest()
|
||||
{
|
||||
var submissionInfo = new SubmissionInfo();
|
||||
string json = JsonConvert.SerializeObject(submissionInfo, Formatting.Indented);
|
||||
Assert.NotNull(json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PartialSerializationTest()
|
||||
{
|
||||
var submissionInfo = new SubmissionInfo()
|
||||
{
|
||||
CommonDiscInfo = new CommonDiscInfoSection(),
|
||||
VersionAndEditions = new VersionAndEditionsSection(),
|
||||
EDC = new EDCSection(),
|
||||
ParentCloneRelationship = new ParentCloneRelationshipSection(),
|
||||
Extras = new ExtrasSection(),
|
||||
CopyProtection = new CopyProtectionSection(),
|
||||
DumpersAndStatus = new DumpersAndStatusSection(),
|
||||
TracksAndWriteOffsets = new TracksAndWriteOffsetsSection(),
|
||||
SizeAndChecksums = new SizeAndChecksumsSection(),
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(submissionInfo, Formatting.Indented);
|
||||
Assert.NotNull(json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullSerializationTest()
|
||||
{
|
||||
var submissionInfo = new SubmissionInfo()
|
||||
{
|
||||
SchemaVersion = 1,
|
||||
FullyMatchedID = 3,
|
||||
PartiallyMatchedIDs = new List<int> { 0, 1, 2, 3 },
|
||||
Added = DateTime.UtcNow,
|
||||
LastModified = DateTime.UtcNow,
|
||||
|
||||
CommonDiscInfo = new CommonDiscInfoSection()
|
||||
{
|
||||
System = RedumpSystem.IBMPCcompatible,
|
||||
Media = DiscType.CD,
|
||||
Title = "Game Title",
|
||||
ForeignTitleNonLatin = "Foreign Game Title",
|
||||
DiscNumberLetter = "1",
|
||||
DiscTitle = "Install Disc",
|
||||
Category = DiscCategory.Games,
|
||||
Region = Region.World,
|
||||
Languages = new Language?[] { Language.English, Language.Spanish, Language.French },
|
||||
LanguageSelection = new LanguageSelection?[] { LanguageSelection.BiosSettings },
|
||||
Serial = "Disc Serial",
|
||||
Layer0MasteringRing = "L0 Mastering Ring",
|
||||
Layer0MasteringSID = "L0 Mastering SID",
|
||||
Layer0ToolstampMasteringCode = "L0 Toolstamp",
|
||||
Layer0MouldSID = "L0 Mould SID",
|
||||
Layer0AdditionalMould = "L0 Additional Mould",
|
||||
Layer1MasteringRing = "L1 Mastering Ring",
|
||||
Layer1MasteringSID = "L1 Mastering SID",
|
||||
Layer1ToolstampMasteringCode = "L1 Toolstamp",
|
||||
Layer1MouldSID = "L1 Mould SID",
|
||||
Layer1AdditionalMould = "L1 Additional Mould",
|
||||
Layer2MasteringRing = "L2 Mastering Ring",
|
||||
Layer2MasteringSID = "L2 Mastering SID",
|
||||
Layer2ToolstampMasteringCode = "L2 Toolstamp",
|
||||
Layer3MasteringRing = "L3 Mastering Ring",
|
||||
Layer3MasteringSID = "L3 Mastering SID",
|
||||
Layer3ToolstampMasteringCode = "L3 Toolstamp",
|
||||
RingWriteOffset = "+12",
|
||||
Barcode = "UPC Barcode",
|
||||
EXEDateBuildDate = "19xx-xx-xx",
|
||||
ErrorsCount = "0",
|
||||
Comments = "Comment data line 1\r\nComment data line 2",
|
||||
CommentsSpecialFields = new Dictionary<SiteCode, string>()
|
||||
{
|
||||
[SiteCode.ISBN] = "ISBN",
|
||||
},
|
||||
Contents = "Special contents 1\r\nSpecial contents 2",
|
||||
ContentsSpecialFields = new Dictionary<SiteCode, string>()
|
||||
{
|
||||
[SiteCode.PlayableDemos] = "Game Demo 1",
|
||||
},
|
||||
},
|
||||
|
||||
VersionAndEditions = new VersionAndEditionsSection()
|
||||
{
|
||||
Version = "Original",
|
||||
VersionDatfile = "Alt",
|
||||
CommonEditions = new string[] { "Taikenban" },
|
||||
OtherEditions = "Rerelease",
|
||||
},
|
||||
|
||||
EDC = new EDCSection()
|
||||
{
|
||||
EDC = YesNo.Yes,
|
||||
},
|
||||
|
||||
ParentCloneRelationship = new ParentCloneRelationshipSection()
|
||||
{
|
||||
ParentID = "12345",
|
||||
RegionalParent = false,
|
||||
},
|
||||
|
||||
Extras = new ExtrasSection()
|
||||
{
|
||||
PVD = "PVD",
|
||||
DiscKey = "Disc key",
|
||||
DiscID = "Disc ID",
|
||||
PIC = "PIC",
|
||||
Header = "Header",
|
||||
BCA = "BCA",
|
||||
SecuritySectorRanges = "SSv1 Ranges",
|
||||
},
|
||||
|
||||
CopyProtection = new CopyProtectionSection()
|
||||
{
|
||||
AntiModchip = YesNo.Yes,
|
||||
LibCrypt = YesNo.No,
|
||||
LibCryptData = "LibCrypt data",
|
||||
Protection = "List of protections",
|
||||
SecuROMData = "SecuROM data",
|
||||
},
|
||||
|
||||
DumpersAndStatus = new DumpersAndStatusSection()
|
||||
{
|
||||
Status = DumpStatus.TwoOrMoreGreen,
|
||||
Dumpers = new string[] { "Dumper1", "Dumper2" },
|
||||
OtherDumpers = "Dumper3",
|
||||
},
|
||||
|
||||
TracksAndWriteOffsets = new TracksAndWriteOffsetsSection()
|
||||
{
|
||||
ClrMameProData = "Datfile",
|
||||
Cuesheet = "Cuesheet",
|
||||
CommonWriteOffsets = new int[] { 0, 12, -12 },
|
||||
OtherWriteOffsets = "-2",
|
||||
},
|
||||
|
||||
SizeAndChecksums = new SizeAndChecksumsSection()
|
||||
{
|
||||
Layerbreak = 0,
|
||||
Layerbreak2 = 1,
|
||||
Layerbreak3 = 2,
|
||||
Size = 12345,
|
||||
CRC32 = "CRC32",
|
||||
MD5 = "MD5",
|
||||
SHA1 = "SHA1",
|
||||
},
|
||||
|
||||
DumpingInfo = new DumpingInfoSection()
|
||||
{
|
||||
DumpingProgram = "DiscImageCreator 20500101",
|
||||
DumpingDate = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
Manufacturer = "ATAPI",
|
||||
Model = "Optical Drive",
|
||||
Firmware = "1.23",
|
||||
ReportedDiscType = "CD-R",
|
||||
},
|
||||
|
||||
Artifacts = new Dictionary<string, string>()
|
||||
{
|
||||
["Sample Artifact"] = "Sample Data",
|
||||
},
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(submissionInfo, Formatting.Indented);
|
||||
Assert.NotNull(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
SabreTools.RedumpLib.Test/TestData/fail_invalid.json
Normal file
1
SabreTools.RedumpLib.Test/TestData/fail_invalid.json
Normal file
@@ -0,0 +1 @@
|
||||
This isn't even JSON, I lied.
|
||||
96
SabreTools.RedumpLib.Test/TestData/success_complete.json
Normal file
96
SabreTools.RedumpLib.Test/TestData/success_complete.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"schema_version": 3,
|
||||
"common_disc_info":
|
||||
{
|
||||
"d_system": "ajcd",
|
||||
"d_media": "cd",
|
||||
"d_title": "Test Title",
|
||||
"d_title_foreign": "Foreign Title",
|
||||
"d_number": "1",
|
||||
"d_label": "Install",
|
||||
"d_category": "Games",
|
||||
"d_region": "U",
|
||||
"d_languages":
|
||||
[
|
||||
"en",
|
||||
"fr",
|
||||
"es"
|
||||
],
|
||||
"d_languages_selection": [],
|
||||
"d_serial": "Serial",
|
||||
"d_ring_0_ma1": "Ringcode 0 Layer 0",
|
||||
"d_ring_0_ma1_sid": "SID 0 Layer 0",
|
||||
"d_ring_0_ts1": "Toolstamp 0 Layer 0",
|
||||
"d_ring_0_mo1_sid": "Mould SID 0 Layer 0",
|
||||
"d_ring_0_mo1": "Additional Mould 0 Layer 0",
|
||||
"d_ring_0_ma2": "Ringcode 0 Layer 1",
|
||||
"d_ring_0_ma2_sid": "SID 0 Layer 1",
|
||||
"d_ring_0_ts2": "Toolstamp 0 Layer 1",
|
||||
"d_ring_0_mo2_sid": "Mould SID 0 Layer 1",
|
||||
"d_ring_0_mo2": "Additional Mould 0 Layer 1",
|
||||
"d_ring_0_ma3": "Ringcode 0 Layer 2",
|
||||
"d_ring_0_ma3_sid": "SID 0 Layer 2",
|
||||
"d_ring_0_ts3": "Toolstamp 0 Layer 2",
|
||||
"d_ring_0_ma4": "Ringcode 0 Layer 3",
|
||||
"d_ring_0_ma4_sid": "SID 0 Layer 2",
|
||||
"d_ring_0_ts4": "Toolstamp 0 Layer 2",
|
||||
"d_ring_0_offsets": "-22",
|
||||
"d_ring_0_0_value": "-21",
|
||||
"d_barcode": "0 12345 67890 1",
|
||||
"d_date": "1980-01-01",
|
||||
"d_errors": "0",
|
||||
"d_comments": "This is a comment\nwith a newline",
|
||||
"d_contents": "These are contents, sorry"
|
||||
},
|
||||
"versions_and_editions":
|
||||
{
|
||||
"d_version": "1.0.0.0",
|
||||
"d_version_datfile": "1.00",
|
||||
"d_editions_text": "Demo"
|
||||
|
||||
},
|
||||
"edc":
|
||||
{
|
||||
"d_edc": false
|
||||
},
|
||||
"parent_clone_relationship":
|
||||
{
|
||||
"d_parent_id": "12345",
|
||||
"d_is_regional_parent": false
|
||||
},
|
||||
"extras":
|
||||
{
|
||||
"d_pvd": "Pretend\nthis\nis\na\nPVD",
|
||||
"d_d1_key": "Disc key",
|
||||
"d_d2_key": "Disc ID",
|
||||
"d_pic_data": "Pretend\nthis\nis\na\nPIC",
|
||||
"d_header": "Pretend\nthis\nis\na\nHeader",
|
||||
"d_bca": "Pretend\nthis\nis\na\nBCA",
|
||||
"d_ssranges": "Pretend\nthis\nis\na\nsecurity_range"
|
||||
},
|
||||
"copy_protection":
|
||||
{
|
||||
"d_protection_a": false,
|
||||
"d_protection_1": false,
|
||||
"d_libcrypt": "Definitely\nLibCrypt\nData",
|
||||
"d_protection": "Super easy to find protection",
|
||||
"d_securom": "Definitely\nSecuROM\nData"
|
||||
},
|
||||
"tracks_and_write_offsets":
|
||||
{
|
||||
"d_tracks": "Hash data",
|
||||
"d_cue": "Real cuesheet",
|
||||
"d_offset_text": "-22"
|
||||
},
|
||||
"size_and_checksums":
|
||||
{
|
||||
"d_layerbreak": 1,
|
||||
"d_layerbreak_2": 2,
|
||||
"d_layerbreak_3": 3,
|
||||
"d_pic_identifier": "Pretend\nthis\nis\na\nPIC",
|
||||
"d_size": 123456,
|
||||
"d_crc32": "cbf43926",
|
||||
"d_md5": "d41d8cd98f00b204e9800998ecf8427e",
|
||||
"d_sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
||||
}
|
||||
}
|
||||
4
SabreTools.RedumpLib.Test/TestData/success_invalid.json
Normal file
4
SabreTools.RedumpLib.Test/TestData/success_invalid.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"invalid_key": "invalid_value",
|
||||
"invalid_x": 12345
|
||||
}
|
||||
7
SabreTools.RedumpLib.Test/TestData/success_partial.json
Normal file
7
SabreTools.RedumpLib.Test/TestData/success_partial.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"schema_version": 3,
|
||||
"common_disc_info":
|
||||
{
|
||||
"d_title": "Test Title"
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net48;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<Version>1.1.0</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Code to interact with redump.org</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2020-2023</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.RedumpLib</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>web client redump</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.RedumpLib", "SabreTools.RedumpLib.csproj", "{235D3A36-CA69-4348-9EC4-649B27ACFBB8}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.RedumpLib", "SabreTools.RedumpLib\SabreTools.RedumpLib.csproj", "{235D3A36-CA69-4348-9EC4-649B27ACFBB8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.RedumpLib.Test", "SabreTools.RedumpLib.Test\SabreTools.RedumpLib.Test.csproj", "{63519DEA-0C3D-4F0E-95EB-E9B6E1D55378}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -18,5 +20,9 @@ Global
|
||||
{235D3A36-CA69-4348-9EC4-649B27ACFBB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{235D3A36-CA69-4348-9EC4-649B27ACFBB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{235D3A36-CA69-4348-9EC4-649B27ACFBB8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{63519DEA-0C3D-4F0E-95EB-E9B6E1D55378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{63519DEA-0C3D-4F0E-95EB-E9B6E1D55378}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{63519DEA-0C3D-4F0E-95EB-E9B6E1D55378}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{63519DEA-0C3D-4F0E-95EB-E9B6E1D55378}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -10,11 +10,7 @@ namespace SabreTools.RedumpLib.Attributes
|
||||
/// </summary>
|
||||
/// <param name="value">Value to use</param>
|
||||
/// <returns>HumanReadableAttribute attached to the value</returns>
|
||||
#if NET48
|
||||
public static HumanReadableAttribute GetAttribute(T value)
|
||||
#else
|
||||
public static HumanReadableAttribute? GetAttribute(T value)
|
||||
#endif
|
||||
{
|
||||
// Null value in, null value out
|
||||
if (value == null)
|
||||
@@ -26,12 +22,8 @@ namespace SabreTools.RedumpLib.Attributes
|
||||
enumType = Nullable.GetUnderlyingType(enumType);
|
||||
|
||||
// If the value returns a null on ToString, just return null
|
||||
#if NET48
|
||||
string valueStr = value?.ToString();
|
||||
#else
|
||||
string? valueStr = value?.ToString();
|
||||
#endif
|
||||
if (string.IsNullOrWhiteSpace(valueStr))
|
||||
if (string.IsNullOrEmpty(valueStr))
|
||||
return null;
|
||||
|
||||
// Get the member info array
|
||||
@@ -15,19 +15,11 @@ namespace SabreTools.RedumpLib.Attributes
|
||||
/// <summary>
|
||||
/// Human-readable name of the item
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public string LongName { get; set; }
|
||||
#else
|
||||
public string? LongName { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Internally used name of the item
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public string ShortName { get; set; }
|
||||
#else
|
||||
public string? ShortName { get; set; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -11,28 +11,16 @@ namespace SabreTools.RedumpLib.Attributes
|
||||
/// <summary>
|
||||
/// ISO 639-1 Code
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public string TwoLetterCode { get; set; } = null;
|
||||
#else
|
||||
public string? TwoLetterCode { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// ISO 639-2 Code (Standard or Bibliographic)
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public string ThreeLetterCode { get; set; } = null;
|
||||
#else
|
||||
public string? ThreeLetterCode { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// ISO 639-2 Code (Terminology)
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public string ThreeLetterCodeAlt { get; set; } = null;
|
||||
#else
|
||||
public string? ThreeLetterCodeAlt { get; set; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
764
SabreTools.RedumpLib/Builder.cs
Normal file
764
SabreTools.RedumpLib/Builder.cs
Normal file
@@ -0,0 +1,764 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
namespace SabreTools.RedumpLib
|
||||
{
|
||||
public static class Builder
|
||||
{
|
||||
#region Creation
|
||||
|
||||
/// <summary>
|
||||
/// Create a SubmissionInfo from a JSON file path
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the SubmissionInfo JSON</param>
|
||||
/// <returns>Filled SubmissionInfo on success, null on error</returns>
|
||||
public static SubmissionInfo? CreateFromFile(string? path)
|
||||
{
|
||||
// If the path is invalid
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
||||
// If the file doesn't exist
|
||||
if (!File.Exists(path))
|
||||
return null;
|
||||
|
||||
// Try to open and deserialize the file
|
||||
try
|
||||
{
|
||||
byte[] data = File.ReadAllBytes(path);
|
||||
string dataString = Encoding.UTF8.GetString(data);
|
||||
return JsonConvert.DeserializeObject<SubmissionInfo>(dataString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new SubmissionInfo object from a disc page
|
||||
/// </summary>
|
||||
/// <param name="discData">String containing the HTML disc data</param>
|
||||
/// <returns>Filled SubmissionInfo object on success, null on error</returns>
|
||||
/// <remarks>Not currently working</remarks>
|
||||
private static SubmissionInfo? CreateFromID(string discData)
|
||||
{
|
||||
var info = new SubmissionInfo()
|
||||
{
|
||||
CommonDiscInfo = new CommonDiscInfoSection(),
|
||||
VersionAndEditions = new VersionAndEditionsSection(),
|
||||
};
|
||||
|
||||
// No disc data means we can't parse it
|
||||
if (string.IsNullOrEmpty(discData))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Load the current disc page into an XML document
|
||||
var redumpPage = new XmlDocument() { PreserveWhitespace = true };
|
||||
redumpPage.LoadXml(discData);
|
||||
|
||||
// If the current page isn't valid, we can't parse it
|
||||
if (!redumpPage.HasChildNodes)
|
||||
return null;
|
||||
|
||||
// Get the body node, if possible
|
||||
var bodyNode = redumpPage["html"]?["body"];
|
||||
if (bodyNode == null || !bodyNode.HasChildNodes)
|
||||
return null;
|
||||
|
||||
// Loop through and get the main node, if possible
|
||||
XmlNode? mainNode = null;
|
||||
foreach (XmlNode? tempNode in bodyNode.ChildNodes)
|
||||
{
|
||||
// Invalid nodes are skipped
|
||||
if (tempNode == null)
|
||||
continue;
|
||||
|
||||
// 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)
|
||||
return null;
|
||||
|
||||
// Try to find elements as we're going
|
||||
foreach (XmlNode? childNode in mainNode.ChildNodes)
|
||||
{
|
||||
// Invalid nodes are skipped
|
||||
if (childNode == null)
|
||||
continue;
|
||||
|
||||
// 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
|
||||
if (childNode.Attributes != null && string.Equals(childNode.Attributes["class"]?.Value, "game",
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// Invalid nodes are skipped
|
||||
if (gameNode == null)
|
||||
continue;
|
||||
|
||||
// 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
|
||||
if (string.Equals(gameNode.Attributes["class"]?.Value, "gameinfo",
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// Invalid nodes are skipped
|
||||
if (gameInfoNode == null)
|
||||
continue;
|
||||
|
||||
// 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;
|
||||
|
||||
var gameInfoNodeHeader = gameInfoNode["th"];
|
||||
var gameInfoNodeData = gameInfoNode["td"];
|
||||
|
||||
if (gameInfoNodeHeader == null || gameInfoNodeData == null)
|
||||
{
|
||||
// No-op for invalid data
|
||||
}
|
||||
else if (string.Equals(gameInfoNodeHeader.InnerText, "System", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
info.CommonDiscInfo.System = Extensions.ToRedumpSystem(gameInfoNodeData["a"]?.InnerText ?? string.Empty);
|
||||
}
|
||||
else if (string.Equals(gameInfoNodeHeader.InnerText, "Media", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
info.CommonDiscInfo.Media = Extensions.ToDiscType(gameInfoNodeData.InnerText);
|
||||
}
|
||||
else if (string.Equals(gameInfoNodeHeader.InnerText, "Category", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
info.CommonDiscInfo.Category = Extensions.ToDiscCategory(gameInfoNodeData.InnerText);
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill out an existing SubmissionInfo object based on a disc page
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for making the connection</param>
|
||||
/// <param name="info">Existing SubmissionInfo object to fill</param>
|
||||
/// <param name="id">Redump disc ID to retrieve</param>
|
||||
/// <param name="includeAllData">True to include all pullable information, false to do bare minimum</param>
|
||||
public async static Task<bool> FillFromId(RedumpClient rc, SubmissionInfo info, int id, bool includeAllData)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = EnsureAllSections(info);
|
||||
var discData = await rc.DownloadSingleSiteID(id);
|
||||
if (string.IsNullOrEmpty(discData))
|
||||
return false;
|
||||
|
||||
// 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(" (") ?? -1;
|
||||
if (title != null && firstParenLocation >= 0)
|
||||
{
|
||||
info.CommonDiscInfo!.Title = title.Substring(0, firstParenLocation);
|
||||
var subMatches = Constants.DiscNumberLetterRegex.Matches(title);
|
||||
foreach (Match subMatch in subMatches.Cast<Match>())
|
||||
{
|
||||
var subMatchValue = subMatch.Groups[1].Value;
|
||||
|
||||
// Disc number or letter
|
||||
if (subMatchValue.StartsWith("Disc"))
|
||||
info.CommonDiscInfo.DiscNumberLetter = subMatchValue.Remove(0, "Disc ".Length);
|
||||
|
||||
// Issue number
|
||||
else if (subMatchValue.All(c => char.IsNumber(c)))
|
||||
info.CommonDiscInfo.Title += $" ({subMatchValue})";
|
||||
|
||||
// Disc title
|
||||
else
|
||||
info.CommonDiscInfo.DiscTitle = subMatchValue;
|
||||
}
|
||||
}
|
||||
// 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
|
||||
if (info.CommonDiscInfo.Region == null)
|
||||
{
|
||||
match = Constants.RegionRegex.Match(discData);
|
||||
if (match.Success)
|
||||
info.CommonDiscInfo.Region = Extensions.ToRegion(match.Groups[1].Value);
|
||||
}
|
||||
|
||||
// Languages
|
||||
var matches = Constants.LanguagesRegex.Matches(discData);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
var tempLanguages = new List<Language?>();
|
||||
foreach (Match submatch in matches.Cast<Match>())
|
||||
{
|
||||
tempLanguages.Add(Extensions.ToLanguage(submatch.Groups[1].Value));
|
||||
}
|
||||
|
||||
info.CommonDiscInfo.Languages = tempLanguages.Where(l => l != null).ToArray();
|
||||
}
|
||||
|
||||
// Serial
|
||||
if (includeAllData)
|
||||
{
|
||||
// TODO: Re-enable if there's a way of verifying against a disc
|
||||
//match = Constants.SerialRegex.Match(discData);
|
||||
//if (match.Success)
|
||||
// info.CommonDiscInfo.Serial = $"(VERIFY THIS) {WebUtility.HtmlDecode(match.Groups[1].Value)}";
|
||||
}
|
||||
|
||||
// Error count
|
||||
if (string.IsNullOrEmpty(info.CommonDiscInfo.ErrorsCount))
|
||||
{
|
||||
match = Constants.ErrorCountRegex.Match(discData);
|
||||
if (match.Success)
|
||||
info.CommonDiscInfo.ErrorsCount = match.Groups[1].Value;
|
||||
}
|
||||
|
||||
// Version
|
||||
if (info.VersionAndEditions!.Version == null)
|
||||
{
|
||||
match = Constants.VersionRegex.Match(discData);
|
||||
if (match.Success)
|
||||
info.VersionAndEditions.Version = $"(VERIFY THIS) {WebUtility.HtmlDecode(match.Groups[1].Value)}";
|
||||
}
|
||||
|
||||
// Dumpers
|
||||
matches = Constants.DumpersRegex.Matches(discData);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
// Start with any currently listed dumpers
|
||||
var tempDumpers = new List<string>();
|
||||
if (info.DumpersAndStatus!.Dumpers != null && info.DumpersAndStatus.Dumpers.Length > 0)
|
||||
{
|
||||
foreach (string dumper in info.DumpersAndStatus.Dumpers)
|
||||
tempDumpers.Add(dumper);
|
||||
}
|
||||
|
||||
foreach (Match submatch in matches.Cast<Match>())
|
||||
{
|
||||
string? dumper = WebUtility.HtmlDecode(submatch.Groups[1].Value);
|
||||
if (dumper != null)
|
||||
tempDumpers.Add(dumper);
|
||||
}
|
||||
|
||||
info.DumpersAndStatus.Dumpers = [.. tempDumpers];
|
||||
}
|
||||
|
||||
// PS3 DiscKey
|
||||
if (string.IsNullOrEmpty(info.Extras!.DiscKey))
|
||||
{
|
||||
// Validate key is not NULL
|
||||
match = Constants.PS3DiscKey.Match(discData);
|
||||
if (match.Success && match.Groups[1].Value != "<span class=\"null\">NULL</span>")
|
||||
info.Extras.DiscKey = match.Groups[1].Value;
|
||||
}
|
||||
|
||||
// TODO: Unify handling of fields that can include site codes (Comments/Contents)
|
||||
|
||||
// Comments
|
||||
if (includeAllData)
|
||||
{
|
||||
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) ?? string.Empty)
|
||||
.Replace("\r\n", "\n")
|
||||
.Replace("<br />\n", "\n")
|
||||
.Replace("<br />", string.Empty)
|
||||
.Replace("</div>", string.Empty)
|
||||
.Replace("[+]", string.Empty)
|
||||
.ReplaceHtmlWithSiteCodes();
|
||||
oldComments = Regex.Replace(oldComments, @"<div .*?>", string.Empty, RegexOptions.Compiled);
|
||||
|
||||
// Create state variables
|
||||
bool addToLast = false;
|
||||
SiteCode? lastSiteCode = null;
|
||||
string newComments = string.Empty;
|
||||
|
||||
// Process the comments block line-by-line
|
||||
string[] commentsSeparated = oldComments.Split('\n');
|
||||
for (int i = 0; i < commentsSeparated.Length; i++)
|
||||
{
|
||||
string commentLine = commentsSeparated[i].Trim();
|
||||
|
||||
// If we have an empty line, we want to treat this as intentional
|
||||
if (string.IsNullOrEmpty(commentLine))
|
||||
{
|
||||
addToLast = false;
|
||||
lastSiteCode = null;
|
||||
newComments += $"{commentLine}\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, we need to find what tag is in use
|
||||
bool foundTag = false;
|
||||
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
|
||||
{
|
||||
// If we have a null site code, just skip
|
||||
if (siteCode == null)
|
||||
continue;
|
||||
|
||||
// If the line doesn't contain this tag, just skip
|
||||
var shortName = siteCode.ShortName();
|
||||
if (shortName == null || !commentLine.Contains(shortName))
|
||||
continue;
|
||||
|
||||
// Mark as having found a tag
|
||||
foundTag = true;
|
||||
|
||||
// Cache the current site code
|
||||
lastSiteCode = siteCode;
|
||||
|
||||
// A subset of tags can be multiline
|
||||
addToLast = siteCode.IsMultiLine();
|
||||
|
||||
// Skip certain site codes because of data issues
|
||||
switch (siteCode)
|
||||
{
|
||||
// Multiple
|
||||
case SiteCode.InternalSerialName:
|
||||
case SiteCode.Multisession:
|
||||
case SiteCode.VolumeLabel:
|
||||
continue;
|
||||
|
||||
// Audio CD
|
||||
case SiteCode.RingNonZeroDataStart:
|
||||
case SiteCode.UniversalHash:
|
||||
continue;
|
||||
|
||||
// Microsoft Xbox and Xbox 360
|
||||
case SiteCode.DMIHash:
|
||||
case SiteCode.PFIHash:
|
||||
case SiteCode.SSHash:
|
||||
case SiteCode.SSVersion:
|
||||
case SiteCode.XMID:
|
||||
case SiteCode.XeMID:
|
||||
continue;
|
||||
|
||||
// Microsoft Xbox One and Series X/S
|
||||
case SiteCode.Filename:
|
||||
continue;
|
||||
|
||||
// Nintendo Gamecube
|
||||
case SiteCode.InternalName:
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we don't already have this site code, add it to the dictionary
|
||||
if (!info.CommonDiscInfo.CommentsSpecialFields!.ContainsKey(siteCode.Value))
|
||||
info.CommonDiscInfo.CommentsSpecialFields[siteCode.Value] = $"(VERIFY THIS) {commentLine.Replace(shortName, string.Empty).Trim()}";
|
||||
|
||||
// Otherwise, append the value to the existing key
|
||||
else
|
||||
info.CommonDiscInfo.CommentsSpecialFields[siteCode.Value] += $", {commentLine.Replace(shortName, string.Empty).Trim()}";
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// If we didn't find a known tag, just add the line, just in case
|
||||
if (!foundTag)
|
||||
{
|
||||
if (addToLast && lastSiteCode != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(info.CommonDiscInfo.CommentsSpecialFields![lastSiteCode.Value]))
|
||||
info.CommonDiscInfo.CommentsSpecialFields[lastSiteCode.Value] += "\n";
|
||||
|
||||
info.CommonDiscInfo.CommentsSpecialFields[lastSiteCode.Value] += commentLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
newComments += $"{commentLine}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the new comments field
|
||||
info.CommonDiscInfo.Comments = newComments;
|
||||
}
|
||||
}
|
||||
|
||||
// Contents
|
||||
if (includeAllData)
|
||||
{
|
||||
match = Constants.ContentsRegex.Match(discData);
|
||||
if (match.Success)
|
||||
{
|
||||
// Process the old contents block
|
||||
string oldContents = info.CommonDiscInfo.Contents
|
||||
+ (string.IsNullOrEmpty(info.CommonDiscInfo.Contents) ? string.Empty : "\n")
|
||||
+ (WebUtility.HtmlDecode(match.Groups[1].Value) ?? string.Empty)
|
||||
.Replace("\r\n", "\n")
|
||||
.Replace("<br />\n", "\n")
|
||||
.Replace("<br />", string.Empty)
|
||||
.Replace("</div>", string.Empty)
|
||||
.Replace("[+]", string.Empty)
|
||||
.ReplaceHtmlWithSiteCodes();
|
||||
oldContents = Regex.Replace(oldContents, @"<div .*?>", string.Empty, RegexOptions.Compiled);
|
||||
|
||||
// Create state variables
|
||||
bool addToLast = false;
|
||||
SiteCode? lastSiteCode = null;
|
||||
string newContents = string.Empty;
|
||||
|
||||
// Process the contents block line-by-line
|
||||
string[] contentsSeparated = oldContents.Split('\n');
|
||||
for (int i = 0; i < contentsSeparated.Length; i++)
|
||||
{
|
||||
string contentLine = contentsSeparated[i].Trim();
|
||||
|
||||
// If we have an empty line, we want to treat this as intentional
|
||||
if (string.IsNullOrEmpty(contentLine))
|
||||
{
|
||||
addToLast = false;
|
||||
lastSiteCode = null;
|
||||
newContents += $"{contentLine}\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, we need to find what tag is in use
|
||||
bool foundTag = false;
|
||||
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
|
||||
{
|
||||
// If we have a null site code, just skip
|
||||
if (siteCode == null)
|
||||
continue;
|
||||
|
||||
// If the line doesn't contain this tag, just skip
|
||||
var shortName = siteCode.ShortName();
|
||||
if (shortName == null || !contentLine.Contains(shortName))
|
||||
continue;
|
||||
|
||||
// Cache the current site code
|
||||
lastSiteCode = siteCode;
|
||||
|
||||
// If we don't already have this site code, add it to the dictionary
|
||||
if (!info.CommonDiscInfo.ContentsSpecialFields!.ContainsKey(siteCode.Value))
|
||||
info.CommonDiscInfo.ContentsSpecialFields[siteCode.Value] = $"(VERIFY THIS) {contentLine.Replace(shortName, string.Empty).Trim()}";
|
||||
|
||||
// A subset of tags can be multiline
|
||||
addToLast = siteCode.IsMultiLine();
|
||||
|
||||
// Mark as having found a tag
|
||||
foundTag = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we didn't find a known tag, just add the line, just in case
|
||||
if (!foundTag)
|
||||
{
|
||||
if (addToLast && lastSiteCode != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(info.CommonDiscInfo.ContentsSpecialFields![lastSiteCode.Value]))
|
||||
info.CommonDiscInfo.ContentsSpecialFields[lastSiteCode.Value] += "\n";
|
||||
|
||||
info.CommonDiscInfo.ContentsSpecialFields[lastSiteCode.Value] += contentLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
newContents += $"{contentLine}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the new contents field
|
||||
info.CommonDiscInfo.Contents = newContents;
|
||||
}
|
||||
}
|
||||
|
||||
// Added
|
||||
match = Constants.AddedRegex.Match(discData);
|
||||
if (match.Success)
|
||||
{
|
||||
if (DateTime.TryParse(match.Groups[1].Value, out DateTime added))
|
||||
info.Added = added;
|
||||
else
|
||||
info.Added = null;
|
||||
}
|
||||
|
||||
// Last Modified
|
||||
match = Constants.LastModifiedRegex.Match(discData);
|
||||
if (match.Success)
|
||||
{
|
||||
if (DateTime.TryParse(match.Groups[1].Value, out DateTime lastModified))
|
||||
info.LastModified = lastModified;
|
||||
else
|
||||
info.LastModified = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure all required sections in a submission info exist
|
||||
/// </summary>
|
||||
/// <param name="info">SubmissionInfo object to verify</param>
|
||||
public static SubmissionInfo EnsureAllSections(SubmissionInfo? info)
|
||||
{
|
||||
// If there's no info, create one
|
||||
info ??= new SubmissionInfo();
|
||||
|
||||
// Ensure all sections
|
||||
info.CommonDiscInfo ??= new CommonDiscInfoSection();
|
||||
info.VersionAndEditions ??= new VersionAndEditionsSection();
|
||||
info.EDC ??= new EDCSection();
|
||||
info.ParentCloneRelationship ??= new ParentCloneRelationshipSection();
|
||||
info.Extras ??= new ExtrasSection();
|
||||
info.CopyProtection ??= new CopyProtectionSection();
|
||||
info.DumpersAndStatus ??= new DumpersAndStatusSection();
|
||||
info.TracksAndWriteOffsets ??= new TracksAndWriteOffsetsSection();
|
||||
info.SizeAndChecksums ??= new SizeAndChecksumsSection();
|
||||
info.DumpingInfo ??= new DumpingInfoSection();
|
||||
|
||||
// Ensure special dictionaries
|
||||
info.CommonDiscInfo.CommentsSpecialFields ??= [];
|
||||
info.CommonDiscInfo.ContentsSpecialFields ??= [];
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inject information from a seed SubmissionInfo into the existing one
|
||||
/// </summary>
|
||||
/// <param name="info">Existing submission information</param>
|
||||
/// <param name="seed">User-supplied submission information</param>
|
||||
public static void InjectSubmissionInformation(SubmissionInfo? info, SubmissionInfo? seed)
|
||||
{
|
||||
// If we have any invalid info
|
||||
if (seed == null)
|
||||
return;
|
||||
|
||||
// Ensure that required sections exist
|
||||
info = EnsureAllSections(info);
|
||||
|
||||
// Otherwise, inject information as necessary
|
||||
if (info.CommonDiscInfo != null && seed.CommonDiscInfo != null)
|
||||
{
|
||||
// Info that only overwrites if supplied
|
||||
if (!string.IsNullOrEmpty(seed.CommonDiscInfo.Title)) info.CommonDiscInfo.Title = seed.CommonDiscInfo.Title;
|
||||
if (!string.IsNullOrEmpty(seed.CommonDiscInfo.ForeignTitleNonLatin)) info.CommonDiscInfo.ForeignTitleNonLatin = seed.CommonDiscInfo.ForeignTitleNonLatin;
|
||||
if (!string.IsNullOrEmpty(seed.CommonDiscInfo.DiscNumberLetter)) info.CommonDiscInfo.DiscNumberLetter = seed.CommonDiscInfo.DiscNumberLetter;
|
||||
if (!string.IsNullOrEmpty(seed.CommonDiscInfo.DiscTitle)) info.CommonDiscInfo.DiscTitle = seed.CommonDiscInfo.DiscTitle;
|
||||
if (seed.CommonDiscInfo.Category != null) info.CommonDiscInfo.Category = seed.CommonDiscInfo.Category;
|
||||
if (seed.CommonDiscInfo.Region != null) info.CommonDiscInfo.Region = seed.CommonDiscInfo.Region;
|
||||
if (seed.CommonDiscInfo.Languages != null) info.CommonDiscInfo.Languages = seed.CommonDiscInfo.Languages;
|
||||
if (seed.CommonDiscInfo.LanguageSelection != null) info.CommonDiscInfo.LanguageSelection = seed.CommonDiscInfo.LanguageSelection;
|
||||
if (!string.IsNullOrEmpty(seed.CommonDiscInfo.Serial)) info.CommonDiscInfo.Serial = seed.CommonDiscInfo.Serial;
|
||||
if (!string.IsNullOrEmpty(seed.CommonDiscInfo.Barcode)) info.CommonDiscInfo.Barcode = seed.CommonDiscInfo.Barcode;
|
||||
if (!string.IsNullOrEmpty(seed.CommonDiscInfo.Comments)) info.CommonDiscInfo.Comments = seed.CommonDiscInfo.Comments;
|
||||
if (seed.CommonDiscInfo.CommentsSpecialFields != null) info.CommonDiscInfo.CommentsSpecialFields = seed.CommonDiscInfo.CommentsSpecialFields;
|
||||
if (!string.IsNullOrEmpty(seed.CommonDiscInfo.Contents)) info.CommonDiscInfo.Contents = seed.CommonDiscInfo.Contents;
|
||||
if (seed.CommonDiscInfo.ContentsSpecialFields != null) info.CommonDiscInfo.ContentsSpecialFields = seed.CommonDiscInfo.ContentsSpecialFields;
|
||||
|
||||
// Info that always overwrites
|
||||
info.CommonDiscInfo.Layer0MasteringRing = seed.CommonDiscInfo.Layer0MasteringRing;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = seed.CommonDiscInfo.Layer0MasteringSID;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = seed.CommonDiscInfo.Layer0ToolstampMasteringCode;
|
||||
info.CommonDiscInfo.Layer0MouldSID = seed.CommonDiscInfo.Layer0MouldSID;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = seed.CommonDiscInfo.Layer0AdditionalMould;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = seed.CommonDiscInfo.Layer1MasteringRing;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = seed.CommonDiscInfo.Layer1MasteringSID;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = seed.CommonDiscInfo.Layer1ToolstampMasteringCode;
|
||||
info.CommonDiscInfo.Layer1MouldSID = seed.CommonDiscInfo.Layer1MouldSID;
|
||||
info.CommonDiscInfo.Layer1AdditionalMould = seed.CommonDiscInfo.Layer1AdditionalMould;
|
||||
|
||||
info.CommonDiscInfo.Layer2MasteringRing = seed.CommonDiscInfo.Layer2MasteringRing;
|
||||
info.CommonDiscInfo.Layer2MasteringSID = seed.CommonDiscInfo.Layer2MasteringSID;
|
||||
info.CommonDiscInfo.Layer2ToolstampMasteringCode = seed.CommonDiscInfo.Layer2ToolstampMasteringCode;
|
||||
|
||||
info.CommonDiscInfo.Layer3MasteringRing = seed.CommonDiscInfo.Layer3MasteringRing;
|
||||
info.CommonDiscInfo.Layer3MasteringSID = seed.CommonDiscInfo.Layer3MasteringSID;
|
||||
info.CommonDiscInfo.Layer3ToolstampMasteringCode = seed.CommonDiscInfo.Layer3ToolstampMasteringCode;
|
||||
}
|
||||
|
||||
if (info.VersionAndEditions != null && seed.VersionAndEditions != null)
|
||||
{
|
||||
// Info that only overwrites if supplied
|
||||
if (!string.IsNullOrEmpty(seed.VersionAndEditions.Version)) info.VersionAndEditions.Version = seed.VersionAndEditions.Version;
|
||||
if (!string.IsNullOrEmpty(seed.VersionAndEditions.OtherEditions)) info.VersionAndEditions.OtherEditions = seed.VersionAndEditions.OtherEditions;
|
||||
}
|
||||
|
||||
if (info.CopyProtection != null && seed.CopyProtection != null)
|
||||
{
|
||||
// Info that only overwrites if supplied
|
||||
if (!string.IsNullOrEmpty(seed.CopyProtection.Protection)) info.CopyProtection.Protection = seed.CopyProtection.Protection;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Process a text block and replace with internal identifiers
|
||||
/// </summary>
|
||||
/// <param name="text">Text block to process</param>
|
||||
/// <returns>Processed text block, if possible</returns>
|
||||
private static string ReplaceHtmlWithSiteCodes(this string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return text;
|
||||
|
||||
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
|
||||
{
|
||||
var longname = siteCode.LongName();
|
||||
if (!string.IsNullOrEmpty(longname))
|
||||
text = text.Replace(longname, siteCode.ShortName());
|
||||
}
|
||||
|
||||
// For some outdated tags, we need to use alternate names
|
||||
text = text.Replace("<b>Demos</b>:", ((SiteCode?)SiteCode.PlayableDemos).ShortName());
|
||||
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());
|
||||
text = text.Replace("SSv1:", ((SiteCode?)SiteCode.SSHash).ShortName());
|
||||
text = text.Replace("<b>SSv1</b>:", ((SiteCode?)SiteCode.SSHash).ShortName());
|
||||
text = text.Replace("SSv2:", ((SiteCode?)SiteCode.SSHash).ShortName());
|
||||
text = text.Replace("<b>SSv2</b>:", ((SiteCode?)SiteCode.SSHash).ShortName());
|
||||
text = text.Replace("SS version:", ((SiteCode?)SiteCode.SSVersion).ShortName());
|
||||
text = text.Replace("Universal Hash (SHA-1):", ((SiteCode?)SiteCode.UniversalHash).ShortName());
|
||||
text = text.Replace("XeMID:", ((SiteCode?)SiteCode.XeMID).ShortName());
|
||||
text = text.Replace("XMID:", ((SiteCode?)SiteCode.XMID).ShortName());
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,21 @@ namespace SabreTools.RedumpLib.Converters
|
||||
/// </summary>
|
||||
public class DiscCategoryConverter : JsonConverter<DiscCategory?>
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
public override DiscCategory? ReadJson(JsonReader reader, Type objectType, DiscCategory? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// If we have a value already, don't overwrite it
|
||||
if (hasExistingValue)
|
||||
return existingValue;
|
||||
|
||||
// Read the value
|
||||
string? value = reader.Value as string;
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
// Try to parse the value
|
||||
return Data.Extensions.ToDiscCategory(value);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, DiscCategory? value, JsonSerializer serializer)
|
||||
@@ -10,11 +10,21 @@ namespace SabreTools.RedumpLib.Converters
|
||||
/// </summary>
|
||||
public class DiscTypeConverter : JsonConverter<DiscType?>
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
public override DiscType? ReadJson(JsonReader reader, Type objectType, DiscType? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// If we have a value already, don't overwrite it
|
||||
if (hasExistingValue)
|
||||
return existingValue;
|
||||
|
||||
// Read the value
|
||||
string? value = reader.Value as string;
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
// Try to parse the value
|
||||
return Data.Extensions.ToDiscType(value);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, DiscType? value, JsonSerializer serializer)
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
@@ -10,22 +11,34 @@ namespace SabreTools.RedumpLib.Converters
|
||||
/// </summary>
|
||||
public class LanguageConverter : JsonConverter<Language?[]>
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
#if NET48
|
||||
public override Language?[] ReadJson(JsonReader reader, Type objectType, Language?[] existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
#else
|
||||
public override Language?[] ReadJson(JsonReader reader, Type objectType, Language?[]? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
#endif
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// If we have a value already, don't overwrite it
|
||||
if (hasExistingValue)
|
||||
return existingValue ?? [];
|
||||
|
||||
// Get the current depth for checking
|
||||
int currentDepth = reader.Depth;
|
||||
|
||||
// Read the array while it exists
|
||||
List<Language> languages = [];
|
||||
while (reader.Read() && reader.Depth > currentDepth)
|
||||
{
|
||||
string? value = reader.Value as string;
|
||||
if (value == null)
|
||||
continue;
|
||||
|
||||
Language? lang = Data.Extensions.ToLanguage(value);
|
||||
if (lang != null)
|
||||
languages.Add(lang.Value);
|
||||
}
|
||||
|
||||
return [.. languages];
|
||||
}
|
||||
|
||||
#if NET48
|
||||
public override void WriteJson(JsonWriter writer, Language?[] value, JsonSerializer serializer)
|
||||
#else
|
||||
public override void WriteJson(JsonWriter writer, Language?[]? value, JsonSerializer serializer)
|
||||
#endif
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
@@ -10,22 +11,34 @@ namespace SabreTools.RedumpLib.Converters
|
||||
/// </summary>
|
||||
public class LanguageSelectionConverter : JsonConverter<LanguageSelection?[]>
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
#if NET48
|
||||
public override LanguageSelection?[] ReadJson(JsonReader reader, Type objectType, LanguageSelection?[] existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
#else
|
||||
public override LanguageSelection?[] ReadJson(JsonReader reader, Type objectType, LanguageSelection?[]? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
#endif
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// If we have a value already, don't overwrite it
|
||||
if (hasExistingValue)
|
||||
return existingValue ?? [];
|
||||
|
||||
// Get the current depth for checking
|
||||
int currentDepth = reader.Depth;
|
||||
|
||||
// Read the array while it exists
|
||||
List<LanguageSelection> selections = [];
|
||||
while (reader.Read() && reader.Depth > currentDepth)
|
||||
{
|
||||
string? value = reader.Value as string;
|
||||
if (value == null)
|
||||
continue;
|
||||
|
||||
LanguageSelection? sel = Data.Extensions.ToLanguageSelection(value);
|
||||
if (sel != null)
|
||||
selections.Add(sel.Value);
|
||||
}
|
||||
|
||||
return [.. selections];
|
||||
}
|
||||
|
||||
#if NET48
|
||||
public override void WriteJson(JsonWriter writer, LanguageSelection?[] value, JsonSerializer serializer)
|
||||
#else
|
||||
public override void WriteJson(JsonWriter writer, LanguageSelection?[]? value, JsonSerializer serializer)
|
||||
#endif
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
@@ -10,11 +10,21 @@ namespace SabreTools.RedumpLib.Converters
|
||||
/// </summary>
|
||||
public class RegionConverter : JsonConverter<Region?>
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
public override Region? ReadJson(JsonReader reader, Type objectType, Region? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// If we have a value already, don't overwrite it
|
||||
if (hasExistingValue)
|
||||
return existingValue;
|
||||
|
||||
// Read the value
|
||||
string? value = reader.Value as string;
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
// Try to parse the value
|
||||
return Data.Extensions.ToRegion(value);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Region? value, JsonSerializer serializer)
|
||||
@@ -10,11 +10,21 @@ namespace SabreTools.RedumpLib.Converters
|
||||
/// </summary>
|
||||
public class SystemConverter : JsonConverter<RedumpSystem?>
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
public override RedumpSystem? ReadJson(JsonReader reader, Type objectType, RedumpSystem? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// If we have a value already, don't overwrite it
|
||||
if (hasExistingValue)
|
||||
return existingValue;
|
||||
|
||||
// Read the value
|
||||
string? value = reader.Value as string;
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
// Try to parse the value
|
||||
return Data.Extensions.ToRedumpSystem(value);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, RedumpSystem? value, JsonSerializer serializer)
|
||||
@@ -10,11 +10,21 @@ namespace SabreTools.RedumpLib.Converters
|
||||
/// </summary>
|
||||
public class YesNoConverter : JsonConverter<YesNo?>
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
public override YesNo? ReadJson(JsonReader reader, Type objectType, YesNo? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// If we have a value already, don't overwrite it
|
||||
if (hasExistingValue)
|
||||
return existingValue;
|
||||
|
||||
// Read the value
|
||||
if (reader.Value is bool bVal)
|
||||
return Data.Extensions.ToYesNo(bVal);
|
||||
else if (reader.Value is string sVal)
|
||||
return Data.Extensions.ToYesNo(sVal);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, YesNo? value, JsonSerializer serializer)
|
||||
@@ -10,17 +10,17 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <summary>
|
||||
/// Regex matching the added field on a disc page
|
||||
/// </summary>
|
||||
public static Regex AddedRegex = new Regex(@"<tr><th>Added</th><td>(.*?)</td></tr>");
|
||||
public static Regex AddedRegex = new(@"<tr><th>Added</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the barcode field on a disc page
|
||||
/// </summary>
|
||||
public static Regex BarcodeRegex = new Regex(@"<tr><th>Barcode</th></tr><tr><td>(.*?)</td></tr>");
|
||||
public static Regex BarcodeRegex = new(@"<tr><th>Barcode</th></tr><tr><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the BCA field on a disc page
|
||||
/// </summary>
|
||||
public static Regex BcaRegex = new Regex(@"<h3>BCA .*?/></h3></td><td .*?></td></tr>"
|
||||
public static Regex BcaRegex = new(@"<h3>BCA .*?/></h3></td><td .*?></td></tr>"
|
||||
+ "<tr><th>Row</th><th>Contents</th><th>ASCII</th></tr>"
|
||||
+ "<tr><td>(?<row1number>.*?)</td><td>(?<row1contents>.*?)</td><td>(?<row1ascii>.*?)</td></tr>"
|
||||
+ "<tr><td>(?<row2number>.*?)</td><td>(?<row2contents>.*?)</td><td>(?<row2ascii>.*?)</td></tr>"
|
||||
@@ -30,82 +30,87 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <summary>
|
||||
/// Regex matching the category field on a disc page
|
||||
/// </summary>
|
||||
public static Regex CategoryRegex = new Regex(@"<tr><th>Category</th><td>(.*?)</td></tr>");
|
||||
public static Regex CategoryRegex = new(@"<tr><th>Category</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the comments field on a disc page
|
||||
/// </summary>
|
||||
public static Regex CommentsRegex = new Regex(@"<tr><th>Comments</th></tr><tr><td>(.*?)</td></tr>", RegexOptions.Singleline);
|
||||
public static Regex CommentsRegex = new(@"<tr><th>Comments</th></tr><tr><td>(.*?)</td></tr>", RegexOptions.Singleline);
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the contents field on a disc page
|
||||
/// </summary>
|
||||
public static Regex ContentsRegex = new Regex(@"<tr><th>Contents</th></tr><tr .*?><td>(.*?)</td></tr>", RegexOptions.Singleline);
|
||||
public static Regex ContentsRegex = new(@"<tr><th>Contents</th></tr><tr .*?><td>(.*?)</td></tr>", RegexOptions.Singleline);
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching individual disc links on a results page
|
||||
/// </summary>
|
||||
public static Regex DiscRegex = new Regex(@"<a href=""/disc/(\d+)/"">");
|
||||
public static Regex DiscRegex = new(@"<a href=""/disc/(\d+)/"">");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the disc number or letter field on a disc page
|
||||
/// </summary>
|
||||
public static Regex DiscNumberLetterRegex = new Regex(@"\((.*?)\)");
|
||||
public static Regex DiscNumberLetterRegex = new(@"\((.*?)\)");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the dumpers on a disc page
|
||||
/// </summary>
|
||||
public static Regex DumpersRegex = new Regex(@"<a href=""/discs/dumper/(.*?)/"">");
|
||||
public static Regex DumpersRegex = new(@"<a href=""/discs/dumper/(.*?)/"">");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the edition field on a disc page
|
||||
/// </summary>
|
||||
public static Regex EditionRegex = new Regex(@"<tr><th>Edition</th><td>(.*?)</td></tr>");
|
||||
public static Regex EditionRegex = new(@"<tr><th>Edition</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the error count field on a disc page
|
||||
/// </summary>
|
||||
public static Regex ErrorCountRegex = new Regex(@"<tr><th>Errors count</th><td>(.*?)</td></tr>");
|
||||
public static Regex ErrorCountRegex = new(@"<tr><th>Errors count</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the foreign title field on a disc page
|
||||
/// </summary>
|
||||
public static Regex ForeignTitleRegex = new Regex(@"<h2>(.*?)</h2>");
|
||||
public static Regex ForeignTitleRegex = new(@"<h2>(.*?)</h2>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the "full match" ID list from a WIP disc page
|
||||
/// </summary>
|
||||
public static Regex FullMatchRegex = new Regex(@"<td class=""static"">full match ids: (.*?)</td>");
|
||||
public static Regex FullMatchRegex = new(@"<td class=""static"">full match ids: (.*?)</td>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the languages field on a disc page
|
||||
/// </summary>
|
||||
public static Regex LanguagesRegex = new Regex(@"<img src=""/images/languages/(.*?)\.png"" alt="".*?"" title="".*?"" />\s*");
|
||||
public static Regex LanguagesRegex = new(@"<img src=""/images/languages/(.*?)\.png"" alt="".*?"" title="".*?"" />\s*");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the last modified field on a disc page
|
||||
/// </summary>
|
||||
public static Regex LastModifiedRegex = new Regex(@"<tr><th>Last modified</th><td>(.*?)</td></tr>");
|
||||
public static Regex LastModifiedRegex = new(@"<tr><th>Last modified</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the media field on a disc page
|
||||
/// </summary>
|
||||
public static Regex MediaRegex = new Regex(@"<tr><th>Media</th><td>(.*?)</td></tr>");
|
||||
public static Regex MediaRegex = new(@"<tr><th>Media</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching individual WIP disc links on a results page
|
||||
/// </summary>
|
||||
public static Regex NewDiscRegex = new Regex(@"<a (style=.*)?href=""/newdisc/(\d+)/"">");
|
||||
public static Regex NewDiscRegex = new(@"<a (style=.*)?href=""/newdisc/(\d+)/"">");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the "partial match" ID list from a WIP disc page
|
||||
/// </summary>
|
||||
public static Regex PartialMatchRegex = new Regex(@"<td class=""static"">partial match ids: (.*?)</td>");
|
||||
public static Regex PartialMatchRegex = new(@"<td class=""static"">partial match ids: (.*?)</td>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the disc key on a PS3 disc page
|
||||
/// </summary>
|
||||
public static Regex PS3DiscKey = new(@"<th>Disc Key</th><th>Disc ID</th><th>Permanent Information & Control \(PIC\)</th></tr><tr><td>(.*?)</td><td>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the PVD field on a disc page
|
||||
/// </summary>
|
||||
public static Regex PvdRegex = new Regex(@"<h3>Primary Volume Descriptor (PVD) <img .*?/></h3></td><td .*?></td></tr>"
|
||||
public static Regex PvdRegex = new(@"<h3>Primary Volume Descriptor (PVD) <img .*?/></h3></td><td .*?></td></tr>"
|
||||
+ @"<tr><th>Record / Entry</th><th>Contents</th><th>Date</th><th>Time</th><th>GMT</th></tr>"
|
||||
+ @"<tr><td>Creation</td><td>(?<creationbytes>.*?)</td><td>(?<creationdate>.*?)</td><td>(?<creationtime>.*?)</td><td>(?<creationtimezone>.*?)</td></tr>"
|
||||
+ @"<tr><td>Modification</td><td>(?<modificationbytes>.*?)</td><td>(?<modificationdate>.*?)</td><td>(?<modificationtime>.*?)</td><td>(?<modificationtimezone>.*?)</td></tr>"
|
||||
@@ -115,57 +120,57 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <summary>
|
||||
/// Regex matching the region field on a disc page
|
||||
/// </summary>
|
||||
public static Regex RegionRegex = new Regex(@"<tr><th>Region</th><td><a href=""/discs/region/(.*?)/"">");
|
||||
public static Regex RegionRegex = new(@"<tr><th>Region</th><td><a href=""/discs/region/(.*?)/"">");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching a double-layer disc ringcode information
|
||||
/// </summary>
|
||||
public static Regex RingCodeDoubleRegex = new Regex(@"", RegexOptions.Singleline); // Varies based on available fields, like Addtional Mould
|
||||
public static Regex RingCodeDoubleRegex = new(@"", RegexOptions.Singleline); // Varies based on available fields, like Addtional Mould
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching a single-layer disc ringcode information
|
||||
/// </summary>
|
||||
public static Regex RingCodeSingleRegex = new Regex(@"", RegexOptions.Singleline); // Varies based on available fields, like Addtional Mould
|
||||
public static Regex RingCodeSingleRegex = new(@"", RegexOptions.Singleline); // Varies based on available fields, like Addtional Mould
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the serial field on a disc page
|
||||
/// </summary>
|
||||
public static Regex SerialRegex = new Regex(@"<tr><th>Serial</th><td>(.*?)</td></tr>");
|
||||
public static Regex SerialRegex = new(@"<tr><th>Serial</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the system field on a disc page
|
||||
/// </summary>
|
||||
public static Regex SystemRegex = new Regex(@"<tr><th>System</th><td><a href=""/discs/system/(.*?)/"">");
|
||||
public static Regex SystemRegex = new(@"<tr><th>System</th><td><a href=""/discs/system/(.*?)/"">");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the title field on a disc page
|
||||
/// </summary>
|
||||
public static Regex TitleRegex = new Regex(@"<h1>(.*?)</h1>");
|
||||
public static Regex TitleRegex = new(@"<h1>(.*?)</h1>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the current nonce token for login
|
||||
/// </summary>
|
||||
public static Regex TokenRegex = new Regex(@"<input type=""hidden"" name=""csrf_token"" value=""(.*?)"" />");
|
||||
public static Regex TokenRegex = new(@"<input type=""hidden"" name=""csrf_token"" value=""(.*?)"" />");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching a single track on a disc page
|
||||
/// </summary>
|
||||
public static Regex TrackRegex = new Regex(@"<tr><td>(?<number>.*?)</td><td>(?<type>.*?)</td><td>(?<pregap>.*?)</td><td>(?<length>.*?)</td><td>(?<sectors>.*?)</td><td>(?<size>.*?)</td><td>(?<crc32>.*?)</td><td>(?<md5>.*?)</td><td>(?<sha1>.*?)</td></tr>", RegexOptions.Singleline);
|
||||
public static Regex TrackRegex = new(@"<tr><td>(?<number>.*?)</td><td>(?<type>.*?)</td><td>(?<pregap>.*?)</td><td>(?<length>.*?)</td><td>(?<sectors>.*?)</td><td>(?<size>.*?)</td><td>(?<crc32>.*?)</td><td>(?<md5>.*?)</td><td>(?<sha1>.*?)</td></tr>", RegexOptions.Singleline);
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the track count on a disc page
|
||||
/// </summary>
|
||||
public static Regex TrackCountRegex = new Regex(@"<tr><th>Number of tracks</th><td>(.*?)</td></tr>");
|
||||
public static Regex TrackCountRegex = new(@"<tr><th>Number of tracks</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the version field on a disc page
|
||||
/// </summary>
|
||||
public static Regex VersionRegex = new Regex(@"<tr><th>Version</th><td>(.*?)</td></tr>");
|
||||
public static Regex VersionRegex = new(@"<tr><th>Version</th><td>(.*?)</td></tr>");
|
||||
|
||||
/// <summary>
|
||||
/// Regex matching the write offset field on a disc page
|
||||
/// </summary>
|
||||
public static Regex WriteOffsetRegex = new Regex(@"<tr><th>Write offset</th><td>(.*?)</td></tr>");
|
||||
public static Regex WriteOffsetRegex = new(@"<tr><th>Write offset</th><td>(.*?)</td></tr>");
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -301,6 +306,5 @@ namespace SabreTools.RedumpLib.Data
|
||||
public const string Sha1Ext = "sha1/";
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2234,7 +2234,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
[System(Category = SystemCategory.Computer, LongName = "NEC PC-98 series", ShortName = "pc-98", HasCues = true, HasDat = true)]
|
||||
NECPC98series,
|
||||
|
||||
[System(Category = SystemCategory.Computer, LongName = "Sharp X68000", ShortName = "x86kcd", HasCues = true, HasDat = true)]
|
||||
[System(Category = SystemCategory.Computer, LongName = "Sharp X68000", ShortName = "x68k", HasCues = true, HasDat = true)]
|
||||
SharpX68000,
|
||||
|
||||
// End of computer section delimiter
|
||||
@@ -2331,7 +2331,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
[System(Category = SystemCategory.Arcade, Available = false, LongName = "Merit Industries MegaTouch XL")]
|
||||
MeritIndustriesMegaTouchXL,
|
||||
|
||||
[System(Category = SystemCategory.Arcade, LongName = "Namco · Sega · Nintendo Triforce", ShortName = "triforce", HasCues = true, HasDat = true, HasGdi = true)]
|
||||
[System(Category = SystemCategory.Arcade, LongName = "Namco · Sega · Nintendo Triforce", ShortName = "trf", HasCues = true, HasDat = true, HasGdi = true)]
|
||||
NamcoSegaNintendoTriforce,
|
||||
|
||||
[System(Category = SystemCategory.Arcade, LongName = "Namco System 12", ShortName = "ns12")]
|
||||
@@ -3526,16 +3526,28 @@ namespace SabreTools.RedumpLib.Data
|
||||
[HumanReadable(ShortName = "[T:ALTF]", LongName = "<b>Alternative Foreign Title</b>:")]
|
||||
AlternativeForeignTitle,
|
||||
|
||||
// TODO: This doesn't have a site tag yet
|
||||
[HumanReadable(ShortName = "<b>Applications</b>:", LongName = "<b>Applications</b>:")]
|
||||
Applications,
|
||||
|
||||
[HumanReadable(ShortName = "[T:BID]", LongName = "<b>Bandai ID</b>:")]
|
||||
BandaiID,
|
||||
|
||||
[HumanReadable(ShortName = "[T:BBFC]", LongName = "<b>BBFC Reg. No.</b>:")]
|
||||
BBFCRegistrationNumber,
|
||||
|
||||
// TODO: This doesn't have a site tag yet
|
||||
[HumanReadable(ShortName = "<b>Bethesda ID</b>:", LongName = "<b>Bethesda ID</b>:")]
|
||||
BethesdaID,
|
||||
|
||||
// TODO: This doesn't have a site tag yet
|
||||
[HumanReadable(ShortName = "<b>CD Projekt ID</b>:", LongName = "<b>CD Projekt ID</b>:")]
|
||||
CDProjektID,
|
||||
|
||||
// TODO: This doesn't have a site tag yet
|
||||
[HumanReadable(ShortName = "<b>Compatible OS</b>:", LongName = "<b>Compatible OS</b>:")]
|
||||
CompatibleOS,
|
||||
|
||||
// TODO: This doesn't have a site tag yet
|
||||
[HumanReadable(ShortName = "<b>Disc Hologram ID</b>:", LongName = "<b>Disc Hologram ID</b>:")]
|
||||
DiscHologramID,
|
||||
@@ -3547,6 +3559,10 @@ namespace SabreTools.RedumpLib.Data
|
||||
[HumanReadable(ShortName = "[T:DNAS]", LongName = "<b>DNAS Disc ID</b>:")]
|
||||
DNASDiscID,
|
||||
|
||||
// TODO: This doesn't have a site tag yet
|
||||
[HumanReadable(ShortName = "<b>Eidos ID</b>:", LongName = "<b>Eidos ID</b>:")]
|
||||
EidosID,
|
||||
|
||||
[HumanReadable(ShortName = "[T:EAID]", LongName = "<b>Electronic Arts ID</b>:")]
|
||||
ElectronicArtsID,
|
||||
|
||||
@@ -239,6 +239,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
|
||||
// https://en.wikipedia.org/wiki/Game_Wave_Family_Entertainment_System
|
||||
case RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem:
|
||||
types.Add(MediaType.CDROM); // Firmware discs only(?)
|
||||
types.Add(MediaType.DVD);
|
||||
break;
|
||||
|
||||
@@ -733,31 +734,20 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <returns>DiscType if possible, null on error</returns>
|
||||
public static DiscType? ToDiscType(this MediaType? mediaType)
|
||||
{
|
||||
switch (mediaType)
|
||||
return mediaType switch
|
||||
{
|
||||
case MediaType.BluRay:
|
||||
return DiscType.BD50;
|
||||
case MediaType.CDROM:
|
||||
return DiscType.CD;
|
||||
case MediaType.DVD:
|
||||
return DiscType.DVD9;
|
||||
case MediaType.GDROM:
|
||||
return DiscType.GDROM;
|
||||
case MediaType.HDDVD:
|
||||
return DiscType.HDDVDSL;
|
||||
// case MediaType.MILCD: // TODO: Support this?
|
||||
// return DiscType.MILCD;
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
return DiscType.NintendoGameCubeGameDisc;
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
return DiscType.NintendoWiiOpticalDiscDL;
|
||||
case MediaType.NintendoWiiUOpticalDisc:
|
||||
return DiscType.NintendoWiiUOpticalDiscSL;
|
||||
case MediaType.UMD:
|
||||
return DiscType.UMDDL;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
MediaType.BluRay => DiscType.BD50,
|
||||
MediaType.CDROM => DiscType.CD,
|
||||
MediaType.DVD => DiscType.DVD9,
|
||||
MediaType.GDROM => DiscType.GDROM,
|
||||
MediaType.HDDVD => DiscType.HDDVDSL,
|
||||
// MediaType.MILCD => DiscType.MILCD, // TODO: Support this?
|
||||
MediaType.NintendoGameCubeGameDisc => DiscType.NintendoGameCubeGameDisc,
|
||||
MediaType.NintendoWiiOpticalDisc => DiscType.NintendoWiiOpticalDiscDL,
|
||||
MediaType.NintendoWiiUOpticalDisc => DiscType.NintendoWiiUOpticalDiscSL,
|
||||
MediaType.UMD => DiscType.UMDDL,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -767,40 +757,29 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <returns>MediaType if possible, null on error</returns>
|
||||
public static MediaType? ToMediaType(this DiscType? discType)
|
||||
{
|
||||
switch (discType)
|
||||
return discType switch
|
||||
{
|
||||
case DiscType.BD25:
|
||||
case DiscType.BD33:
|
||||
case DiscType.BD50:
|
||||
case DiscType.BD66:
|
||||
case DiscType.BD100:
|
||||
case DiscType.BD128:
|
||||
return MediaType.BluRay;
|
||||
case DiscType.CD:
|
||||
return MediaType.CDROM;
|
||||
case DiscType.DVD5:
|
||||
case DiscType.DVD9:
|
||||
return MediaType.DVD;
|
||||
case DiscType.GDROM:
|
||||
return MediaType.GDROM;
|
||||
case DiscType.HDDVDSL:
|
||||
case DiscType.HDDVDDL:
|
||||
return MediaType.HDDVD;
|
||||
// case DiscType.MILCD: // TODO: Support this?
|
||||
// return MediaType.MILCD;
|
||||
case DiscType.NintendoGameCubeGameDisc:
|
||||
return MediaType.NintendoGameCubeGameDisc;
|
||||
case DiscType.NintendoWiiOpticalDiscSL:
|
||||
case DiscType.NintendoWiiOpticalDiscDL:
|
||||
return MediaType.NintendoWiiOpticalDisc;
|
||||
case DiscType.NintendoWiiUOpticalDiscSL:
|
||||
return MediaType.NintendoWiiUOpticalDisc;
|
||||
case DiscType.UMDSL:
|
||||
case DiscType.UMDDL:
|
||||
return MediaType.UMD;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
DiscType.BD25
|
||||
or DiscType.BD33
|
||||
or DiscType.BD50
|
||||
or DiscType.BD66
|
||||
or DiscType.BD100
|
||||
or DiscType.BD128 => MediaType.BluRay,
|
||||
DiscType.CD => MediaType.CDROM,
|
||||
DiscType.DVD5
|
||||
or DiscType.DVD9 => MediaType.DVD,
|
||||
DiscType.GDROM => MediaType.GDROM,
|
||||
DiscType.HDDVDSL
|
||||
or DiscType.HDDVDDL => MediaType.HDDVD,
|
||||
// DiscType.MILCD => MediaType.MILCD, // TODO: Support this?
|
||||
DiscType.NintendoGameCubeGameDisc => MediaType.NintendoGameCubeGameDisc,
|
||||
DiscType.NintendoWiiOpticalDiscSL
|
||||
or DiscType.NintendoWiiOpticalDiscDL => MediaType.NintendoWiiOpticalDisc,
|
||||
DiscType.NintendoWiiUOpticalDiscSL => MediaType.NintendoWiiUOpticalDisc,
|
||||
DiscType.UMDSL
|
||||
or DiscType.UMDDL => MediaType.UMD,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -812,11 +791,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="category"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string LongName(this DiscCategory? category) => AttributeHelper<DiscCategory?>.GetAttribute(category)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this DiscCategory? category) => AttributeHelper<DiscCategory?>.GetAttribute(category)?.LongName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the Category enum value for a given string
|
||||
@@ -825,35 +800,23 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <returns>Category represented by the string, if possible</returns>
|
||||
public static DiscCategory? ToDiscCategory(string category)
|
||||
{
|
||||
switch (category?.ToLowerInvariant())
|
||||
return (category?.ToLowerInvariant()) switch
|
||||
{
|
||||
case "games":
|
||||
return DiscCategory.Games;
|
||||
case "demos":
|
||||
return DiscCategory.Demos;
|
||||
case "video":
|
||||
return DiscCategory.Video;
|
||||
case "audio":
|
||||
return DiscCategory.Audio;
|
||||
case "multimedia":
|
||||
return DiscCategory.Multimedia;
|
||||
case "applications":
|
||||
return DiscCategory.Applications;
|
||||
case "coverdiscs":
|
||||
return DiscCategory.Coverdiscs;
|
||||
case "educational":
|
||||
return DiscCategory.Educational;
|
||||
case "bonusdiscs":
|
||||
case "bonus discs":
|
||||
return DiscCategory.BonusDiscs;
|
||||
case "preproduction":
|
||||
return DiscCategory.Preproduction;
|
||||
case "addons":
|
||||
case "add-ons":
|
||||
return DiscCategory.AddOns;
|
||||
default:
|
||||
return DiscCategory.Games;
|
||||
}
|
||||
"games" => (DiscCategory?)DiscCategory.Games,
|
||||
"demos" => (DiscCategory?)DiscCategory.Demos,
|
||||
"video" => (DiscCategory?)DiscCategory.Video,
|
||||
"audio" => (DiscCategory?)DiscCategory.Audio,
|
||||
"multimedia" => (DiscCategory?)DiscCategory.Multimedia,
|
||||
"applications" => (DiscCategory?)DiscCategory.Applications,
|
||||
"coverdiscs" => (DiscCategory?)DiscCategory.Coverdiscs,
|
||||
"educational" => (DiscCategory?)DiscCategory.Educational,
|
||||
"bonusdiscs"
|
||||
or "bonus discs" => (DiscCategory?)DiscCategory.BonusDiscs,
|
||||
"preproduction" => (DiscCategory?)DiscCategory.Preproduction,
|
||||
"addons"
|
||||
or "add-ons" => (DiscCategory?)DiscCategory.AddOns,
|
||||
_ => (DiscCategory?)DiscCategory.Games,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -865,11 +828,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="discType"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string LongName(this DiscType? discType) => AttributeHelper<DiscType?>.GetAttribute(discType)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this DiscType? discType) => AttributeHelper<DiscType?>.GetAttribute(discType)?.LongName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the DiscType enum value for a given string
|
||||
@@ -878,72 +837,52 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <returns>DiscType represented by the string, if possible</returns>
|
||||
public static DiscType? ToDiscType(string discType)
|
||||
{
|
||||
switch (discType?.ToLowerInvariant())
|
||||
return (discType?.ToLowerInvariant()) switch
|
||||
{
|
||||
case "bd25":
|
||||
case "bd-25":
|
||||
return DiscType.BD25;
|
||||
case "bd33":
|
||||
case "bd-33":
|
||||
return DiscType.BD33;
|
||||
case "bd50":
|
||||
case "bd-50":
|
||||
return DiscType.BD50;
|
||||
case "bd66":
|
||||
case "bd-66":
|
||||
return DiscType.BD66;
|
||||
case "bd100":
|
||||
case "bd-100":
|
||||
return DiscType.BD100;
|
||||
case "bd128":
|
||||
case "bd-128":
|
||||
return DiscType.BD128;
|
||||
case "cd":
|
||||
case "cdrom":
|
||||
case "cd-rom":
|
||||
return DiscType.CD;
|
||||
case "dvd5":
|
||||
case "dvd-5":
|
||||
return DiscType.DVD5;
|
||||
case "dvd9":
|
||||
case "dvd-9":
|
||||
return DiscType.DVD9;
|
||||
case "gd":
|
||||
case "gdrom":
|
||||
case "gd-rom":
|
||||
return DiscType.GDROM;
|
||||
case "hddvd":
|
||||
case "hddvdsl":
|
||||
case "hd-dvd sl":
|
||||
return DiscType.HDDVDSL;
|
||||
case "hddvddl":
|
||||
case "hd-dvd dl":
|
||||
return DiscType.HDDVDDL;
|
||||
case "milcd":
|
||||
case "mil-cd":
|
||||
return DiscType.MILCD;
|
||||
case "nintendogamecubegamedisc":
|
||||
case "nintendo game cube game disc":
|
||||
return DiscType.NintendoGameCubeGameDisc;
|
||||
case "nintendowiiopticaldiscsl":
|
||||
case "nintendo wii optical disc sl":
|
||||
return DiscType.NintendoWiiOpticalDiscSL;
|
||||
case "nintendowiiopticaldiscdl":
|
||||
case "nintendo wii optical disc dl":
|
||||
return DiscType.NintendoWiiOpticalDiscDL;
|
||||
case "nintendowiiuopticaldiscsl":
|
||||
case "nintendo wii u optical disc sl":
|
||||
return DiscType.NintendoWiiUOpticalDiscSL;
|
||||
case "umd":
|
||||
case "umdsl":
|
||||
case "umd sl":
|
||||
return DiscType.UMDSL;
|
||||
case "umddl":
|
||||
case "umd dl":
|
||||
return DiscType.UMDDL;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
"bd25"
|
||||
or "bd-25" => (DiscType?)DiscType.BD25,
|
||||
"bd33"
|
||||
or "bd-33" => (DiscType?)DiscType.BD33,
|
||||
"bd50"
|
||||
or "bd-50" => (DiscType?)DiscType.BD50,
|
||||
"bd66"
|
||||
or "bd-66" => (DiscType?)DiscType.BD66,
|
||||
"bd100"
|
||||
or "bd-100" => (DiscType?)DiscType.BD100,
|
||||
"bd128"
|
||||
or "bd-128" => (DiscType?)DiscType.BD128,
|
||||
"cd"
|
||||
or "cdrom"
|
||||
or "cd-rom" => (DiscType?)DiscType.CD,
|
||||
"dvd5"
|
||||
or "dvd-5" => (DiscType?)DiscType.DVD5,
|
||||
"dvd9"
|
||||
or "dvd-9" => (DiscType?)DiscType.DVD9,
|
||||
"gd"
|
||||
or "gdrom"
|
||||
or "gd-rom" => (DiscType?)DiscType.GDROM,
|
||||
"hddvd"
|
||||
or "hddvdsl"
|
||||
or "hd-dvd sl" => (DiscType?)DiscType.HDDVDSL,
|
||||
"hddvddl"
|
||||
or "hd-dvd dl" => (DiscType?)DiscType.HDDVDDL,
|
||||
"milcd"
|
||||
or "mil-cd" => (DiscType?)DiscType.MILCD,
|
||||
"nintendogamecubegamedisc"
|
||||
or "nintendo game cube game disc" => (DiscType?)DiscType.NintendoGameCubeGameDisc,
|
||||
"nintendowiiopticaldiscsl"
|
||||
or "nintendo wii optical disc sl" => (DiscType?)DiscType.NintendoWiiOpticalDiscSL,
|
||||
"nintendowiiopticaldiscdl"
|
||||
or "nintendo wii optical disc dl" => (DiscType?)DiscType.NintendoWiiOpticalDiscDL,
|
||||
"nintendowiiuopticaldiscsl"
|
||||
or "nintendo wii u optical disc sl" => (DiscType?)DiscType.NintendoWiiUOpticalDiscSL,
|
||||
"umd"
|
||||
or "umdsl"
|
||||
or "umd sl" => (DiscType?)DiscType.UMDSL,
|
||||
"umddl"
|
||||
or "umd dl" => (DiscType?)DiscType.UMDDL,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -955,37 +894,26 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="language"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string LongName(this Language? language) => AttributeHelper<Language?>.GetAttribute(language)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this Language? language) => AttributeHelper<Language?>.GetAttribute(language)?.LongName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the Redump shortnames for each known language
|
||||
/// </summary>
|
||||
/// <param name="language"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string ShortName(this Language? language)
|
||||
#else
|
||||
public static string? ShortName(this Language? language)
|
||||
#endif
|
||||
{
|
||||
// Some languages need to use the alternate code instead
|
||||
switch (language)
|
||||
return language switch
|
||||
{
|
||||
case Language.Albanian:
|
||||
case Language.Armenian:
|
||||
case Language.Icelandic:
|
||||
case Language.Macedonian:
|
||||
case Language.Romanian:
|
||||
case Language.Slovak:
|
||||
return language.ThreeLetterCodeAlt();
|
||||
|
||||
default:
|
||||
return language.ThreeLetterCode();
|
||||
}
|
||||
Language.Albanian
|
||||
or Language.Armenian
|
||||
or Language.Icelandic
|
||||
or Language.Macedonian
|
||||
or Language.Romanian
|
||||
or Language.Slovak => language.ThreeLetterCodeAlt(),
|
||||
_ => language.ThreeLetterCode(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1029,33 +957,21 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="language"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string ThreeLetterCode(this Language? language) => ((LanguageAttribute)AttributeHelper<Language?>.GetAttribute(language))?.ThreeLetterCode;
|
||||
#else
|
||||
public static string? ThreeLetterCode(this Language? language) => (AttributeHelper<Language?>.GetAttribute(language) as LanguageAttribute)?.ThreeLetterCode;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the ISO 639-2 alternate code for each known language
|
||||
/// </summary>
|
||||
/// <param name="language"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string ThreeLetterCodeAlt(this Language? language) => ((LanguageAttribute)AttributeHelper<Language?>.GetAttribute(language))?.ThreeLetterCodeAlt;
|
||||
#else
|
||||
public static string? ThreeLetterCodeAlt(this Language? language) => (AttributeHelper<Language?>.GetAttribute(language) as LanguageAttribute)?.ThreeLetterCodeAlt;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the ISO 639-1 code for each known language
|
||||
/// </summary>
|
||||
/// <param name="language"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string TwoLetterCode(this Language? language) => ((LanguageAttribute)AttributeHelper<Language?>.GetAttribute(language))?.TwoLetterCode;
|
||||
#else
|
||||
public static string? TwoLetterCode(this Language? language) => (AttributeHelper<Language?>.GetAttribute(language) as LanguageAttribute)?.TwoLetterCode;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1066,11 +982,30 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="langSelect">LanguageSelection value to convert</param>
|
||||
/// <returns>String representing the value, if possible</returns>
|
||||
#if NET48
|
||||
public static string LongName(this LanguageSelection? langSelect) => AttributeHelper<LanguageSelection?>.GetAttribute(langSelect)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this LanguageSelection? langSelect) => AttributeHelper<LanguageSelection?>.GetAttribute(langSelect)?.LongName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the LanguageSelection enum value for a given string
|
||||
/// </summary>
|
||||
/// <param name="langSelect">String value to convert</param>
|
||||
/// <returns>LanguageSelection represented by the string, if possible</returns>
|
||||
public static LanguageSelection? ToLanguageSelection(string langSelect)
|
||||
{
|
||||
return (langSelect?.ToLowerInvariant()) switch
|
||||
{
|
||||
"bios"
|
||||
or "biossettings"
|
||||
or "bios settings" => (LanguageSelection?)LanguageSelection.BiosSettings,
|
||||
"selector"
|
||||
or "langselector"
|
||||
or "lang selector"
|
||||
or "langauge selector" => (LanguageSelection?)LanguageSelection.LanguageSelector,
|
||||
"options"
|
||||
or "optionsmenu"
|
||||
or "options menu" => (LanguageSelection?)LanguageSelection.OptionsMenu,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1085,7 +1020,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
|
||||
foreach (var val in Enum.GetValues(typeof(MediaType)))
|
||||
{
|
||||
if (((MediaType)val) == MediaType.NONE)
|
||||
if (val == null || ((MediaType)val) == MediaType.NONE)
|
||||
continue;
|
||||
|
||||
mediaTypes.Add($"{((MediaType?)val).ShortName()} - {((MediaType?)val).LongName()}");
|
||||
@@ -1099,22 +1034,14 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="mediaType"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string LongName(this MediaType? mediaType) => AttributeHelper<MediaType?>.GetAttribute(mediaType)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this MediaType? mediaType) => AttributeHelper<MediaType?>.GetAttribute(mediaType)?.LongName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the Redump shortnames for each known media type
|
||||
/// </summary>
|
||||
/// <param name="mediaType"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string ShortName(this MediaType? mediaType) => AttributeHelper<MediaType?>.GetAttribute(mediaType)?.ShortName;
|
||||
#else
|
||||
public static string? ShortName(this MediaType? mediaType) => AttributeHelper<MediaType?>.GetAttribute(mediaType)?.ShortName;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1125,22 +1052,14 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="region"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string LongName(this Region? region) => AttributeHelper<Region?>.GetAttribute(region)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this Region? region) => AttributeHelper<Region?>.GetAttribute(region)?.LongName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the Redump shortnames for each known region
|
||||
/// </summary>
|
||||
/// <param name="region"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string ShortName(this Region? region) => AttributeHelper<Region?>.GetAttribute(region)?.ShortName;
|
||||
#else
|
||||
public static string? ShortName(this Region? region) => AttributeHelper<Region?>.GetAttribute(region)?.ShortName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the Region enum value for a given string
|
||||
@@ -1167,32 +1086,264 @@ namespace SabreTools.RedumpLib.Data
|
||||
|
||||
#region Site Code
|
||||
|
||||
/// <summary>
|
||||
/// List all site codes with their short usable names
|
||||
/// </summary>
|
||||
public static List<string> ListSiteCodes()
|
||||
{
|
||||
var siteCodes = new List<string>();
|
||||
|
||||
foreach (var val in Enum.GetValues(typeof(SiteCode)))
|
||||
{
|
||||
string? shortName = ((SiteCode?)val).ShortName()?.TrimEnd(':');
|
||||
string? longName = ((SiteCode?)val).LongName()?.TrimEnd(':');
|
||||
|
||||
bool isCommentCode = ((SiteCode?)val).IsCommentCode();
|
||||
bool isContentCode = ((SiteCode?)val).IsContentCode();
|
||||
bool isMultiline = ((SiteCode?)val).IsMultiLine();
|
||||
|
||||
// Invalid codes should be skipped
|
||||
if (shortName == null || longName == null)
|
||||
continue;
|
||||
|
||||
// Handle site tags
|
||||
string siteCode;
|
||||
if (shortName == longName)
|
||||
siteCode = "***".PadRight(16, ' ');
|
||||
else
|
||||
siteCode = shortName.PadRight(16, ' ');
|
||||
|
||||
// Handle expanded tags
|
||||
siteCode += longName.PadRight(32, ' ');
|
||||
|
||||
// Include special indicators, if necessary
|
||||
var additionalInfo = new List<string>();
|
||||
if (isCommentCode)
|
||||
additionalInfo.Add("Comment Field");
|
||||
if (isContentCode)
|
||||
additionalInfo.Add("Content Field");
|
||||
if (isMultiline)
|
||||
additionalInfo.Add("Multiline");
|
||||
if (additionalInfo.Count > 0)
|
||||
siteCode += $"[{string.Join(", ", [.. additionalInfo])}]";
|
||||
|
||||
// Add the formatted site code
|
||||
siteCodes.Add(siteCode);
|
||||
}
|
||||
|
||||
return siteCodes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a site code should live in the comments section
|
||||
/// </summary>
|
||||
/// <param name="siteCode">SiteCode to check</param>
|
||||
/// <returns>True if the code field is in comments by default, false otherwise</returns>
|
||||
public static bool IsCommentCode(this SiteCode? siteCode)
|
||||
{
|
||||
return siteCode switch
|
||||
{
|
||||
// Identifying Info
|
||||
SiteCode.AlternativeTitle => true,
|
||||
SiteCode.AlternativeForeignTitle => true,
|
||||
SiteCode.BBFCRegistrationNumber => true,
|
||||
SiteCode.CompatibleOS => true,
|
||||
SiteCode.DiscHologramID => true,
|
||||
SiteCode.DMIHash => true,
|
||||
SiteCode.DNASDiscID => true,
|
||||
SiteCode.Filename => true,
|
||||
SiteCode.Genre => true,
|
||||
SiteCode.InternalName => true,
|
||||
SiteCode.InternalSerialName => true,
|
||||
SiteCode.ISBN => true,
|
||||
SiteCode.ISSN => true,
|
||||
SiteCode.Multisession => true,
|
||||
SiteCode.PFIHash => true,
|
||||
SiteCode.PostgapType => true,
|
||||
SiteCode.PPN => true,
|
||||
SiteCode.RingNonZeroDataStart => true,
|
||||
SiteCode.Series => true,
|
||||
SiteCode.SSHash => true,
|
||||
SiteCode.SSVersion => true,
|
||||
SiteCode.UniversalHash => true,
|
||||
SiteCode.VCD => true,
|
||||
SiteCode.VFCCode => true,
|
||||
SiteCode.VolumeLabel => true,
|
||||
SiteCode.XeMID => true,
|
||||
SiteCode.XMID => true,
|
||||
|
||||
// Publisher / Company IDs
|
||||
SiteCode.AcclaimID => true,
|
||||
SiteCode.ActivisionID => true,
|
||||
SiteCode.BandaiID => true,
|
||||
SiteCode.BethesdaID => true,
|
||||
SiteCode.CDProjektID => true,
|
||||
SiteCode.EidosID => true,
|
||||
SiteCode.ElectronicArtsID => true,
|
||||
SiteCode.FoxInteractiveID => true,
|
||||
SiteCode.GTInteractiveID => true,
|
||||
SiteCode.JASRACID => true,
|
||||
SiteCode.KingRecordsID => true,
|
||||
SiteCode.KoeiID => true,
|
||||
SiteCode.KonamiID => true,
|
||||
SiteCode.LucasArtsID => true,
|
||||
SiteCode.MicrosoftID => true,
|
||||
SiteCode.NaganoID => true,
|
||||
SiteCode.NamcoID => true,
|
||||
SiteCode.NipponIchiSoftwareID => true,
|
||||
SiteCode.OriginID => true,
|
||||
SiteCode.PonyCanyonID => true,
|
||||
SiteCode.SegaID => true,
|
||||
SiteCode.SelenID => true,
|
||||
SiteCode.SierraID => true,
|
||||
SiteCode.TaitoID => true,
|
||||
SiteCode.UbisoftID => true,
|
||||
SiteCode.ValveID => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a site code should live in the contents section
|
||||
/// </summary>
|
||||
/// <param name="siteCode">SiteCode to check</param>
|
||||
/// <returns>True if the code field is in contents by default, false otherwise</returns>
|
||||
public static bool IsContentCode(this SiteCode? siteCode)
|
||||
{
|
||||
return siteCode switch
|
||||
{
|
||||
SiteCode.Applications => true,
|
||||
SiteCode.Extras => true,
|
||||
SiteCode.GameFootage => true,
|
||||
SiteCode.Games => true,
|
||||
SiteCode.NetYarozeGames => true,
|
||||
SiteCode.Patches => true,
|
||||
SiteCode.PlayableDemos => true,
|
||||
SiteCode.RollingDemos => true,
|
||||
SiteCode.Savegames => true,
|
||||
SiteCode.TechDemos => true,
|
||||
SiteCode.Videos => true,
|
||||
_ => 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>
|
||||
public static bool IsMultiLine(this SiteCode? siteCode)
|
||||
{
|
||||
return siteCode switch
|
||||
{
|
||||
SiteCode.Extras => true,
|
||||
SiteCode.Filename => true,
|
||||
SiteCode.Games => true,
|
||||
SiteCode.GameFootage => true,
|
||||
SiteCode.Multisession => true,
|
||||
SiteCode.NetYarozeGames => true,
|
||||
SiteCode.Patches => true,
|
||||
SiteCode.PlayableDemos => true,
|
||||
SiteCode.RollingDemos => true,
|
||||
SiteCode.Savegames => true,
|
||||
SiteCode.TechDemos => true,
|
||||
SiteCode.Videos => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the HTML version for each known site code
|
||||
/// </summary>
|
||||
/// <param name="siteCode"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string LongName(this SiteCode? siteCode) => AttributeHelper<SiteCode?>.GetAttribute(siteCode)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this SiteCode? siteCode) => AttributeHelper<SiteCode?>.GetAttribute(siteCode)?.LongName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the short tag for each known site code
|
||||
/// </summary>
|
||||
/// <param name="siteCode"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string ShortName(this SiteCode? siteCode) => AttributeHelper<SiteCode?>.GetAttribute(siteCode)?.ShortName;
|
||||
#else
|
||||
public static string? ShortName(this SiteCode? siteCode) => AttributeHelper<SiteCode?>.GetAttribute(siteCode)?.ShortName;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region System
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a system is okay if it's not detected by Windows
|
||||
/// </summary>
|
||||
/// <param name="system">RedumpSystem value to check</param>
|
||||
/// <returns>True if Windows show see a disc when dumping, false otherwise</returns>
|
||||
public static bool DetectedByWindows(this RedumpSystem? system)
|
||||
{
|
||||
return system switch
|
||||
{
|
||||
RedumpSystem.AmericanLaserGames3DO
|
||||
or RedumpSystem.AppleMacintosh
|
||||
or RedumpSystem.Atari3DO
|
||||
or RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem
|
||||
or RedumpSystem.NewJatreCDi
|
||||
or RedumpSystem.NintendoGameCube
|
||||
or RedumpSystem.NintendoWii
|
||||
or RedumpSystem.NintendoWiiU
|
||||
or RedumpSystem.PhilipsCDi
|
||||
or RedumpSystem.PhilipsCDiDigitalVideo
|
||||
or RedumpSystem.Panasonic3DOInteractiveMultiplayer
|
||||
or RedumpSystem.PanasonicM2
|
||||
or RedumpSystem.PioneerLaserActive
|
||||
or RedumpSystem.SuperAudioCD => false,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a system has reversed ringcodes
|
||||
/// </summary>
|
||||
/// <param name="system">RedumpSystem value to check</param>
|
||||
/// <returns>True if the system has reversed ringcodes, false otherwise</returns>
|
||||
public static bool HasReversedRingcodes(this RedumpSystem? system)
|
||||
{
|
||||
return system switch
|
||||
{
|
||||
RedumpSystem.SonyPlayStation2
|
||||
or RedumpSystem.SonyPlayStation3
|
||||
or RedumpSystem.SonyPlayStation4
|
||||
or RedumpSystem.SonyPlayStation5
|
||||
or RedumpSystem.SonyPlayStationPortable => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a system is considered audio-only
|
||||
/// </summary>
|
||||
/// <param name="system">RedumpSystem value to check</param>
|
||||
/// <returns>True if the system is audio-only, false otherwise</returns>
|
||||
/// <remarks>
|
||||
/// Philips CD-i should NOT be in this list. It's being included until there's a
|
||||
/// reasonable distinction between CD-i and CD-i ready on the database side.
|
||||
/// </remarks>
|
||||
public static bool IsAudio(this RedumpSystem? system)
|
||||
{
|
||||
return system switch
|
||||
{
|
||||
RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem
|
||||
or RedumpSystem.AudioCD
|
||||
or RedumpSystem.DVDAudio
|
||||
or RedumpSystem.HasbroiONEducationalGamingSystem
|
||||
or RedumpSystem.HasbroVideoNow
|
||||
or RedumpSystem.HasbroVideoNowColor
|
||||
or RedumpSystem.HasbroVideoNowJr
|
||||
or RedumpSystem.HasbroVideoNowXP
|
||||
or RedumpSystem.PhilipsCDi
|
||||
or RedumpSystem.PlayStationGameSharkUpdates
|
||||
or RedumpSystem.SuperAudioCD => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a system is a marker value
|
||||
/// </summary>
|
||||
@@ -1200,17 +1351,31 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <returns>True if the system is a marker value, false otherwise</returns>
|
||||
public static bool IsMarker(this RedumpSystem? system)
|
||||
{
|
||||
switch (system)
|
||||
return system switch
|
||||
{
|
||||
case RedumpSystem.MarkerArcadeEnd:
|
||||
case RedumpSystem.MarkerComputerEnd:
|
||||
case RedumpSystem.MarkerDiscBasedConsoleEnd:
|
||||
// case RedumpSystem.MarkerOtherConsoleEnd:
|
||||
case RedumpSystem.MarkerOtherEnd:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
RedumpSystem.MarkerArcadeEnd
|
||||
or RedumpSystem.MarkerComputerEnd
|
||||
or RedumpSystem.MarkerDiscBasedConsoleEnd
|
||||
or RedumpSystem.MarkerOtherEnd => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a system is considered XGD
|
||||
/// </summary>
|
||||
/// <param name="system">RedumpSystem value to check</param>
|
||||
/// <returns>True if the system is XGD, false otherwise</returns>
|
||||
public static bool IsXGD(this RedumpSystem? system)
|
||||
{
|
||||
return system switch
|
||||
{
|
||||
RedumpSystem.MicrosoftXbox
|
||||
or RedumpSystem.MicrosoftXbox360
|
||||
or RedumpSystem.MicrosoftXboxOne
|
||||
or RedumpSystem.MicrosoftXboxSeriesXS => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1238,22 +1403,14 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="system"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string LongName(this RedumpSystem? system) => AttributeHelper<RedumpSystem?>.GetAttribute(system)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this RedumpSystem? system) => AttributeHelper<RedumpSystem?>.GetAttribute(system)?.LongName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the Redump shortnames for each known system
|
||||
/// </summary>
|
||||
/// <param name="system"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string ShortName(this RedumpSystem? system) => AttributeHelper<RedumpSystem?>.GetAttribute(system)?.ShortName;
|
||||
#else
|
||||
public static string? ShortName(this RedumpSystem? system) => AttributeHelper<RedumpSystem?>.GetAttribute(system)?.ShortName;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Determine the category of a system
|
||||
@@ -1376,6 +1533,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "atarijagcd":
|
||||
case "atarijaguarcd":
|
||||
case "atari jaguar cd":
|
||||
case "atari jaguar cd interactive multimedia system":
|
||||
return RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem;
|
||||
case "qis":
|
||||
case "playdia":
|
||||
@@ -1443,6 +1601,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "videonow jr":
|
||||
case "hasbrovideonowjr":
|
||||
case "hasbro videonow jr":
|
||||
case "hasbro videonow jr.":
|
||||
return RedumpSystem.HasbroVideoNowJr;
|
||||
case "xvnxp":
|
||||
case "videonowxp":
|
||||
@@ -1516,6 +1675,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "nec pc-engine cd":
|
||||
case "nec turbografx cd":
|
||||
case "nec pc-engine / turbografx cd":
|
||||
case "nec pc-engine cd & turbografx cd":
|
||||
return RedumpSystem.NECPCEngineCDTurboGrafxCD;
|
||||
case "pcfx":
|
||||
case "pc-fx":
|
||||
@@ -1526,6 +1686,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "nec pc-fx":
|
||||
case "nec pc-fxga":
|
||||
case "nec pc-fx / pc-fxga":
|
||||
case "nec pc-fx & pc-fxga":
|
||||
return RedumpSystem.NECPCFXPCFXGA;
|
||||
case "gc":
|
||||
case "gamecube":
|
||||
@@ -1560,6 +1721,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "sonysupernintendocd":
|
||||
case "sony super nintendo cd":
|
||||
case "sony super nintendo-cd":
|
||||
case "nintendo-sony super nes cd-rom system":
|
||||
return RedumpSystem.NintendoSonySuperNESCDROMSystem;
|
||||
case "wii":
|
||||
case "nintendowii":
|
||||
@@ -1595,6 +1757,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "sega cd":
|
||||
case "mega cd":
|
||||
case "sega cd / mega cd":
|
||||
case "sega mega cd & sega cd":
|
||||
return RedumpSystem.SegaMegaCDSegaCD;
|
||||
case "dc":
|
||||
case "sdc":
|
||||
@@ -1698,6 +1861,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "vtech vsmile pro":
|
||||
case "vtech v.smile pro":
|
||||
case "vtech v.flash - v.smile pro":
|
||||
case "vtech v.flash & v.smile pro":
|
||||
return RedumpSystem.VTechVFlashVSmilePro;
|
||||
case "gamewave":
|
||||
case "game wave":
|
||||
@@ -1729,6 +1893,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "amiga":
|
||||
case "commodoreamiga":
|
||||
case "commodore amiga":
|
||||
case "commodore amiga cd":
|
||||
return RedumpSystem.CommodoreAmigaCD;
|
||||
case "fmtowns":
|
||||
case "fmt":
|
||||
@@ -1748,12 +1913,14 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "necpc88":
|
||||
case "nec pc88":
|
||||
case "nec pc-88":
|
||||
case "nec pc-88 series":
|
||||
return RedumpSystem.NECPC88series;
|
||||
case "pc98":
|
||||
case "pc-98":
|
||||
case "necpc98":
|
||||
case "nec pc98":
|
||||
case "nec pc-98":
|
||||
case "nec pc-98 series":
|
||||
return RedumpSystem.NECPC98series;
|
||||
case "x68k":
|
||||
case "x68kcd":
|
||||
@@ -1937,6 +2104,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "capcomsupersystem256":
|
||||
case "capcom super system 256":
|
||||
case "namco / capcom system 256/super system 256":
|
||||
case "namco system 246 / system 256":
|
||||
return RedumpSystem.NamcoSystem246256;
|
||||
case "triforce":
|
||||
case "namcotriforce":
|
||||
@@ -1946,6 +2114,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "nintendotriforce":
|
||||
case "nintendo triforce":
|
||||
case "namco / sega / nintendo triforce":
|
||||
case "Namco · Sega · Nintendo Triforce":
|
||||
return RedumpSystem.NamcoSegaNintendoTriforce;
|
||||
case "ns12":
|
||||
case "system12":
|
||||
@@ -2104,6 +2273,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "hddvdvideo":
|
||||
case "hddvd-video":
|
||||
case "hd-dvd-video":
|
||||
case "hd dvd-video":
|
||||
return RedumpSystem.HDDVDVideo;
|
||||
case "navi21":
|
||||
case "naviken":
|
||||
@@ -2116,6 +2286,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
return RedumpSystem.NavisoftNaviken21;
|
||||
case "palm":
|
||||
case "palmos":
|
||||
case "palm os":
|
||||
return RedumpSystem.PalmOS;
|
||||
case "photo":
|
||||
case "photocd":
|
||||
@@ -2145,6 +2316,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
case "segaprologue21":
|
||||
case "sega prologue21":
|
||||
case "sega prologue 21":
|
||||
case "sega prologue 21 multimedia karaoke system":
|
||||
return RedumpSystem.SegaPrologue21MultimediaKaraokeSystem;
|
||||
case "electronicbook":
|
||||
case "sonyelectronicbook":
|
||||
@@ -2186,11 +2358,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
/// <param name="category"></param>
|
||||
/// <returns></returns>
|
||||
#if NET48
|
||||
public static string LongName(this SystemCategory? category) => AttributeHelper<SystemCategory?>.GetAttribute(category)?.LongName;
|
||||
#else
|
||||
public static string? LongName(this SystemCategory? category) => AttributeHelper<SystemCategory?>.GetAttribute(category)?.LongName;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -2210,15 +2378,12 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <returns>YesNo represented by the nullable boolean, if possible</returns>
|
||||
public static YesNo? ToYesNo(this bool? yesno)
|
||||
{
|
||||
switch (yesno)
|
||||
return yesno switch
|
||||
{
|
||||
case false:
|
||||
return YesNo.No;
|
||||
case true:
|
||||
return YesNo.Yes;
|
||||
default:
|
||||
return YesNo.NULL;
|
||||
}
|
||||
false => YesNo.No,
|
||||
true => YesNo.Yes,
|
||||
_ => YesNo.NULL,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2228,15 +2393,12 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// <returns>YesNo represented by the string, if possible</returns>
|
||||
public static YesNo? ToYesNo(string yesno)
|
||||
{
|
||||
switch (yesno?.ToLowerInvariant())
|
||||
return (yesno?.ToLowerInvariant()) switch
|
||||
{
|
||||
case "no":
|
||||
return YesNo.No;
|
||||
case "yes":
|
||||
return YesNo.Yes;
|
||||
default:
|
||||
return YesNo.NULL;
|
||||
}
|
||||
"no" => YesNo.No,
|
||||
"yes" => YesNo.Yes,
|
||||
_ => YesNo.NULL,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -24,11 +24,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// List of partially matched Redump IDs
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
#if NET48
|
||||
public List<int> PartiallyMatchedIDs { get; set; }
|
||||
#else
|
||||
public List<int>? PartiallyMatchedIDs { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// DateTime of when the disc was added
|
||||
@@ -43,81 +39,37 @@ namespace SabreTools.RedumpLib.Data
|
||||
public DateTime? LastModified { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "common_disc_info", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public CommonDiscInfoSection CommonDiscInfo { get; set; } = new CommonDiscInfoSection();
|
||||
#else
|
||||
public CommonDiscInfoSection? CommonDiscInfo { get; set; } = new CommonDiscInfoSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "versions_and_editions", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public VersionAndEditionsSection VersionAndEditions { get; set; } = new VersionAndEditionsSection();
|
||||
#else
|
||||
public VersionAndEditionsSection? VersionAndEditions { get; set; } = new VersionAndEditionsSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "edc", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public EDCSection EDC { get; set; } = new EDCSection();
|
||||
#else
|
||||
public EDCSection? EDC { get; set; } = new EDCSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "parent_clone_relationship", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public ParentCloneRelationshipSection ParentCloneRelationship { get; set; } = new ParentCloneRelationshipSection();
|
||||
#else
|
||||
public ParentCloneRelationshipSection? ParentCloneRelationship { get; set; } = new ParentCloneRelationshipSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "extras", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public ExtrasSection Extras { get; set; } = new ExtrasSection();
|
||||
#else
|
||||
public ExtrasSection? Extras { get; set; } = new ExtrasSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "copy_protection", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public CopyProtectionSection CopyProtection { get; set; } = new CopyProtectionSection();
|
||||
#else
|
||||
public CopyProtectionSection? CopyProtection { get; set; } = new CopyProtectionSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "dumpers_and_status", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public DumpersAndStatusSection DumpersAndStatus { get; set; } = new DumpersAndStatusSection();
|
||||
#else
|
||||
public DumpersAndStatusSection? DumpersAndStatus { get; set; } = new DumpersAndStatusSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "tracks_and_write_offsets", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public TracksAndWriteOffsetsSection TracksAndWriteOffsets { get; set; } = new TracksAndWriteOffsetsSection();
|
||||
#else
|
||||
public TracksAndWriteOffsetsSection? TracksAndWriteOffsets { get; set; } = new TracksAndWriteOffsetsSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "size_and_checksums", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public SizeAndChecksumsSection SizeAndChecksums { get; set; } = new SizeAndChecksumsSection();
|
||||
#else
|
||||
public SizeAndChecksumsSection? SizeAndChecksums { get; set; } = new SizeAndChecksumsSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "dumping_info", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public DumpingInfoSection DumpingInfo { get; set; } = new DumpingInfoSection();
|
||||
#else
|
||||
public DumpingInfoSection? DumpingInfo { get; set; } = new DumpingInfoSection();
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "artifacts", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public Dictionary<string, string> Artifacts { get; set; } = new Dictionary<string, string>();
|
||||
#else
|
||||
public Dictionary<string, string>? Artifacts { get; set; } = new Dictionary<string, string>();
|
||||
#endif
|
||||
public Dictionary<string, string>? Artifacts { get; set; } = [];
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -149,275 +101,135 @@ namespace SabreTools.RedumpLib.Data
|
||||
public class CommonDiscInfoSection : ICloneable
|
||||
{
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_system", Required = Required.AllowNull)]
|
||||
[JsonProperty(PropertyName = "d_system", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
[JsonConverter(typeof(SystemConverter))]
|
||||
public RedumpSystem? System { get; set; }
|
||||
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_media", Required = Required.AllowNull)]
|
||||
[JsonProperty(PropertyName = "d_media", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
[JsonConverter(typeof(DiscTypeConverter))]
|
||||
public DiscType? Media { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "d_title", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string Title { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_title", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? Title { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_title_foreign", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string ForeignTitleNonLatin { get; set; }
|
||||
#else
|
||||
public string? ForeignTitleNonLatin { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_number", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string DiscNumberLetter { get; set; }
|
||||
#else
|
||||
public string? DiscNumberLetter { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_label", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string DiscTitle { get; set; }
|
||||
#else
|
||||
public string? DiscTitle { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_category", Required = Required.AllowNull)]
|
||||
[JsonProperty(PropertyName = "d_category", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
[JsonConverter(typeof(DiscCategoryConverter))]
|
||||
public DiscCategory? Category { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "d_region", Required = Required.AllowNull)]
|
||||
[JsonProperty(PropertyName = "d_region", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
[JsonConverter(typeof(RegionConverter))]
|
||||
public Region? Region { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "d_languages", Required = Required.AllowNull)]
|
||||
[JsonProperty(PropertyName = "d_languages", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
[JsonConverter(typeof(LanguageConverter))]
|
||||
#if NET48
|
||||
public Language?[] Languages { get; set; }
|
||||
#else
|
||||
public Language?[]? Languages { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_languages_selection", NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
[JsonConverter(typeof(LanguageSelectionConverter))]
|
||||
#if NET48
|
||||
public LanguageSelection?[] LanguageSelection { get; set; }
|
||||
#else
|
||||
public LanguageSelection?[]? LanguageSelection { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_serial", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Serial { get; set; }
|
||||
#else
|
||||
public string? Serial { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Ring { get; private set; }
|
||||
#else
|
||||
public string? Ring { get; private set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_id", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string RingId { get; private set; }
|
||||
#else
|
||||
public string? RingId { get; private set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma1", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string Layer0MasteringRing { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma1", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? Layer0MasteringRing { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma1_sid", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer0MasteringSID { get; set; }
|
||||
#else
|
||||
public string? Layer0MasteringSID { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ts1", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer0ToolstampMasteringCode { get; set; }
|
||||
#else
|
||||
public string? Layer0ToolstampMasteringCode { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_mo1_sid", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer0MouldSID { get; set; }
|
||||
#else
|
||||
public string? Layer0MouldSID { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_mo1", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer0AdditionalMould { get; set; }
|
||||
#else
|
||||
public string? Layer0AdditionalMould { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma2", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string Layer1MasteringRing { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma2", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? Layer1MasteringRing { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma2_sid", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer1MasteringSID { get; set; }
|
||||
#else
|
||||
public string? Layer1MasteringSID { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ts2", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer1ToolstampMasteringCode { get; set; }
|
||||
#else
|
||||
public string? Layer1ToolstampMasteringCode { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_mo2_sid", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer1MouldSID { get; set; }
|
||||
#else
|
||||
public string? Layer1MouldSID { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_mo2", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer1AdditionalMould { get; set; }
|
||||
#else
|
||||
public string? Layer1AdditionalMould { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma3", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string Layer2MasteringRing { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma3", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? Layer2MasteringRing { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma3_sid", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer2MasteringSID { get; set; }
|
||||
#else
|
||||
public string? Layer2MasteringSID { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ts3", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer2ToolstampMasteringCode { get; set; }
|
||||
#else
|
||||
public string? Layer2ToolstampMasteringCode { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma4", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string Layer3MasteringRing { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma4", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? Layer3MasteringRing { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ma4_sid", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer3MasteringSID { get; set; }
|
||||
#else
|
||||
public string? Layer3MasteringSID { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_ts4", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Layer3ToolstampMasteringCode { get; set; }
|
||||
#else
|
||||
public string? Layer3ToolstampMasteringCode { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_offsets", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string RingOffsetsHidden { get { return "1"; } }
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_0_id", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string RingZeroId { get; private set; }
|
||||
#else
|
||||
public string? RingZeroId { get; private set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_0_density", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string RingZeroDensity { get; private set; }
|
||||
#else
|
||||
public string? RingZeroDensity { get; private set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_0_0_value", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string RingWriteOffset { get; set; }
|
||||
#else
|
||||
public string? RingWriteOffset { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ring_count", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string RingCount { get { return "1"; } }
|
||||
|
||||
[JsonProperty(PropertyName = "d_barcode", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Barcode { get; set; }
|
||||
#else
|
||||
public string? Barcode { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_date", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string EXEDateBuildDate { get; set; }
|
||||
#else
|
||||
public string? EXEDateBuildDate { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_errors", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string ErrorsCount { get; set; }
|
||||
#else
|
||||
public string? ErrorsCount { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_comments", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Comments { get; set; }
|
||||
#else
|
||||
public string? Comments { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
#if NET48
|
||||
public Dictionary<SiteCode?, string> CommentsSpecialFields { get; set; }
|
||||
#else
|
||||
public Dictionary<SiteCode, string>? CommentsSpecialFields { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_contents", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Contents { get; set; }
|
||||
#else
|
||||
public string? Contents { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
#if NET48
|
||||
public Dictionary<SiteCode?, string> ContentsSpecialFields { get; set; }
|
||||
#else
|
||||
public Dictionary<SiteCode, string>? ContentsSpecialFields { get; set; }
|
||||
#endif
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -472,32 +284,16 @@ namespace SabreTools.RedumpLib.Data
|
||||
public class VersionAndEditionsSection : ICloneable
|
||||
{
|
||||
[JsonProperty(PropertyName = "d_version", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Version { get; set; }
|
||||
#else
|
||||
public string? Version { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_version_datfile", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string VersionDatfile { get; set; }
|
||||
#else
|
||||
public string? VersionDatfile { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_editions", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string[] CommonEditions { get; set; }
|
||||
#else
|
||||
public string[]? CommonEditions { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_editions_text", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string OtherEditions { get; set; }
|
||||
#else
|
||||
public string? OtherEditions { get; set; }
|
||||
#endif
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -535,11 +331,7 @@ namespace SabreTools.RedumpLib.Data
|
||||
public class ParentCloneRelationshipSection : ICloneable
|
||||
{
|
||||
[JsonProperty(PropertyName = "d_parent_id", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string ParentID { get; set; }
|
||||
#else
|
||||
public string? ParentID { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_is_regional_parent", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool RegionalParent { get; set; }
|
||||
@@ -560,53 +352,25 @@ namespace SabreTools.RedumpLib.Data
|
||||
public class ExtrasSection : ICloneable
|
||||
{
|
||||
[JsonProperty(PropertyName = "d_pvd", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string PVD { get; set; }
|
||||
#else
|
||||
public string? PVD { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_d1_key", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string DiscKey { get; set; }
|
||||
#else
|
||||
public string? DiscKey { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_d2_key", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string DiscID { get; set; }
|
||||
#else
|
||||
public string? DiscID { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_pic_data", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string PIC { get; set; }
|
||||
#else
|
||||
public string? PIC { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_header", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Header { get; set; }
|
||||
#else
|
||||
public string? Header { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_bca", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string BCA { get; set; }
|
||||
#else
|
||||
public string? BCA { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_ssranges", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string SecuritySectorRanges { get; set; }
|
||||
#else
|
||||
public string? SecuritySectorRanges { get; set; }
|
||||
#endif
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -637,32 +401,16 @@ namespace SabreTools.RedumpLib.Data
|
||||
public YesNo? LibCrypt { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "d_libcrypt", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string LibCryptData { get; set; }
|
||||
#else
|
||||
public string? LibCryptData { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_protection", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Protection { get; set; }
|
||||
#else
|
||||
public string? Protection { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
#if NET48
|
||||
public Dictionary<string, List<string>> FullProtections { get; set; }
|
||||
#else
|
||||
public Dictionary<string, List<string>?>? FullProtections { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_securom", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string SecuROMData { get; set; }
|
||||
#else
|
||||
public string? SecuROMData { get; set; }
|
||||
#endif
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -687,18 +435,10 @@ namespace SabreTools.RedumpLib.Data
|
||||
public DumpStatus Status { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "d_dumpers", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string[] Dumpers { get; set; }
|
||||
#else
|
||||
public string[]? Dumpers { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_dumpers_text", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string OtherDumpers { get; set; }
|
||||
#else
|
||||
public string? OtherDumpers { get; set; }
|
||||
#endif
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -717,32 +457,16 @@ namespace SabreTools.RedumpLib.Data
|
||||
public class TracksAndWriteOffsetsSection : ICloneable
|
||||
{
|
||||
[JsonProperty(PropertyName = "d_tracks", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string ClrMameProData { get; set; }
|
||||
#else
|
||||
public string? ClrMameProData { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_cue", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string Cuesheet { get; set; }
|
||||
#else
|
||||
public string? Cuesheet { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_offset", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public int[] CommonWriteOffsets { get; set; }
|
||||
#else
|
||||
public int[]? CommonWriteOffsets { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_offset_text", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string OtherWriteOffsets { get; set; }
|
||||
#else
|
||||
public string? OtherWriteOffsets { get; set; }
|
||||
#endif
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -771,35 +495,19 @@ namespace SabreTools.RedumpLib.Data
|
||||
public long Layerbreak3 { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "d_pic_identifier", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string PICIdentifier { get; set; }
|
||||
#else
|
||||
public string? PICIdentifier { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_size", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "d_crc32", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string CRC32 { get; set; }
|
||||
#else
|
||||
public string? CRC32 { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_md5", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string MD5 { get; set; }
|
||||
#else
|
||||
public string? MD5 { get; set; }
|
||||
#endif
|
||||
|
||||
[JsonProperty(PropertyName = "d_sha1", NullValueHandling = NullValueHandling.Ignore)]
|
||||
#if NET48
|
||||
public string SHA1 { get; set; }
|
||||
#else
|
||||
public string? SHA1 { get; set; }
|
||||
#endif
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -822,64 +530,55 @@ namespace SabreTools.RedumpLib.Data
|
||||
/// </summary>
|
||||
public class DumpingInfoSection : ICloneable
|
||||
{
|
||||
// Name not defined by Redump -- Only used with MPF
|
||||
[JsonProperty(PropertyName = "d_frontend_version", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? FrontendVersion { get; set; }
|
||||
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_dumping_program", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string DumpingProgram { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_dumping_program", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? DumpingProgram { get; set; }
|
||||
#endif
|
||||
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_dumping_date", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string DumpingDate { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_dumping_date", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? DumpingDate { get; set; }
|
||||
#endif
|
||||
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_drive_manufacturer", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string Manufacturer { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_dumping_params", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? DumpingParameters { get; set; }
|
||||
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_drive_manufacturer", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? Manufacturer { get; set; }
|
||||
#endif
|
||||
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_drive_model", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string Model { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_drive_model", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? Model { get; set; }
|
||||
#endif
|
||||
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_drive_firmware", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string Firmware { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_drive_firmware", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? Firmware { get; set; }
|
||||
#endif
|
||||
|
||||
// Name not defined by Redump
|
||||
[JsonProperty(PropertyName = "d_reported_disc_type", Required = Required.AllowNull)]
|
||||
#if NET48
|
||||
public string ReportedDiscType { get; set; }
|
||||
#else
|
||||
[JsonProperty(PropertyName = "d_reported_disc_type", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public string? ReportedDiscType { get; set; }
|
||||
#endif
|
||||
|
||||
// Name not defined by Redump -- Only used with Redumper
|
||||
[JsonProperty(PropertyName = "d_errors_c2", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string? C2ErrorsCount { get; set; }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new DumpingInfoSection
|
||||
{
|
||||
FrontendVersion = this.FrontendVersion,
|
||||
DumpingProgram = this.DumpingProgram,
|
||||
DumpingDate = this.DumpingDate,
|
||||
DumpingParameters = this.DumpingParameters,
|
||||
Manufacturer = this.Manufacturer,
|
||||
Model = this.Model,
|
||||
Firmware = this.Firmware,
|
||||
ReportedDiscType = this.ReportedDiscType,
|
||||
C2ErrorsCount = this.C2ErrorsCount,
|
||||
};
|
||||
}
|
||||
}
|
||||
75
SabreTools.RedumpLib/Data/Template.cs
Normal file
75
SabreTools.RedumpLib/Data/Template.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace SabreTools.RedumpLib.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Template field values for submission info
|
||||
/// </summary>
|
||||
internal static class Template
|
||||
{
|
||||
// Manual information
|
||||
|
||||
public const string TitleField = "Title";
|
||||
public const string ForeignTitleField = "Foreign Title (Non-latin)";
|
||||
public const string DiscNumberField = "Disc Number / Letter";
|
||||
public const string DiscTitleField = "Disc Title";
|
||||
public const string SystemField = "System";
|
||||
public const string MediaTypeField = "Media Type";
|
||||
public const string CategoryField = "Category";
|
||||
public const string RegionField = "Region";
|
||||
public const string LanguagesField = "Languages";
|
||||
public const string PlaystationLanguageSelectionViaField = "Language Selection Via";
|
||||
public const string DiscSerialField = "Disc Serial";
|
||||
public const string BarcodeField = "Barcode";
|
||||
public const string CommentsField = "Comments";
|
||||
public const string ContentsField = "Contents";
|
||||
public const string VersionField = "Version";
|
||||
public const string EditionField = "Edition/Release";
|
||||
public const string PlayStation3WiiDiscKeyField = "Disc Key";
|
||||
public const string PlayStation3DiscIDField = "Disc ID";
|
||||
public const string GameCubeWiiBCAField = "BCA";
|
||||
public const string CopyProtectionField = "Copy Protection";
|
||||
public const string MasteringRingField = "Mastering Code (laser branded/etched)";
|
||||
public const string MasteringSIDField = "Mastering SID Code";
|
||||
public const string MouldSIDField = "Mould SID Code";
|
||||
public const string AdditionalMouldField = "Additional Mould";
|
||||
public const string ToolstampField = "Toolstamp or Mastering Code (engraved/stamped)";
|
||||
|
||||
// Automatic Information
|
||||
|
||||
public const string FrontendVersionField = "Frontend Version";
|
||||
public const string DumpingProgramField = "Dumping Program";
|
||||
public const string DumpingDateField = "Date";
|
||||
public const string DumpingParametersField = "Parameters";
|
||||
public const string DumpingDriveManufacturer = "Manufacturer";
|
||||
public const string DumpingDriveModel = "Model";
|
||||
public const string DumpingDriveFirmware = "Firmware";
|
||||
public const string ReportedDiscType = "Reported Disc Type";
|
||||
public const string C2ErrorCountField = "C2 Error Count";
|
||||
public const string PVDField = "Primary Volume Descriptor (PVD)";
|
||||
public const string DATField = "DAT";
|
||||
public const string SizeField = "Size";
|
||||
public const string CRC32Field = "CRC32";
|
||||
public const string MD5Field = "MD5";
|
||||
public const string SHA1Field = "SHA1";
|
||||
public const string FullyMatchingIDField = "Fully Matching ID";
|
||||
public const string PartiallyMatchingIDsField = "Partially Matching IDs";
|
||||
public const string ErrorCountField = "Error Count";
|
||||
public const string CuesheetField = "Cuesheet";
|
||||
public const string SubIntentionField = "SubIntention Data (SecuROM/LibCrypt)";
|
||||
public const string WriteOffsetField = "Write Offset";
|
||||
public const string LayerbreakField = "Layerbreak";
|
||||
public const string EXEDateBuildDate = "EXE/Build Date";
|
||||
public const string HeaderField = "Header";
|
||||
public const string PICField = "Permanent Information & Control (PIC)";
|
||||
public const string PlayStationEDCField = "EDC";
|
||||
public const string PlayStationAntiModchipField = "Anti-modchip";
|
||||
public const string PlayStationLibCryptField = "LibCrypt";
|
||||
public const string XBOXSSRanges = "Security Sector Ranges";
|
||||
|
||||
// Default values
|
||||
|
||||
public const string RequiredValue = "(REQUIRED)";
|
||||
public const string RequiredIfExistsValue = "(REQUIRED, IF EXISTS)";
|
||||
public const string OptionalValue = "(OPTIONAL)";
|
||||
public const string DiscNotDetected = "Disc Not Detected";
|
||||
}
|
||||
}
|
||||
145
SabreTools.RedumpLib/Downloader.cs
Normal file
145
SabreTools.RedumpLib/Downloader.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System.Threading.Tasks;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
namespace SabreTools.RedumpLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains logic for dealing with downloads
|
||||
/// </summary>
|
||||
public class Downloader
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Which Redump feature is being used
|
||||
/// </summary>
|
||||
public Feature Feature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum ID for downloading page information (Feature.Site, Feature.WIP only)
|
||||
/// </summary>
|
||||
public int MinimumId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum ID for downloading page information (Feature.Site, Feature.WIP only)
|
||||
/// </summary>
|
||||
public int MaximumId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Quicksearch text for downloading
|
||||
/// </summary>
|
||||
public string? QueryString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Directory to save all outputted files to
|
||||
/// </summary>
|
||||
public string? OutDir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Use named subfolders for discrete download sets (Feature.Packs only)
|
||||
/// </summary>
|
||||
public bool UseSubfolders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Use the last modified page to try to grab all new discs (Feature.Site, Feature.WIP only)
|
||||
/// </summary>
|
||||
public bool OnlyNew { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only list the page IDs but don't download
|
||||
/// </summary>
|
||||
public bool OnlyList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Force continuing downloads until user cancels or pages run out
|
||||
/// </summary>
|
||||
public bool Force { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Redump username
|
||||
/// </summary>
|
||||
public string? Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Redump password
|
||||
/// </summary>
|
||||
public string? Password { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Vars
|
||||
|
||||
/// <summary>
|
||||
/// Current HTTP rc to use
|
||||
/// </summary>
|
||||
private readonly RedumpClient _client;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public Downloader()
|
||||
{
|
||||
_client = new RedumpClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="client">Preconfigured client</param>
|
||||
public Downloader(RedumpClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the downloads that should go
|
||||
/// </summary>
|
||||
/// <returns>True if there was a valid download type, false otherwise</returns>
|
||||
public async Task<bool> Download()
|
||||
{
|
||||
// Login to Redump, if possible
|
||||
if (!_client.LoggedIn)
|
||||
await _client.Login(Username ?? string.Empty, Password ?? string.Empty);
|
||||
|
||||
switch (Feature)
|
||||
{
|
||||
case Feature.Site:
|
||||
if (OnlyNew)
|
||||
await Discs.DownloadLastModified(_client, OutDir, Force);
|
||||
else
|
||||
await Discs.DownloadSiteRange(_client, OutDir, MinimumId, MaximumId);
|
||||
break;
|
||||
case Feature.WIP:
|
||||
if (OnlyNew)
|
||||
await WIP.DownloadLastSubmitted(_client, OutDir);
|
||||
else
|
||||
await WIP.DownloadWIPRange(_client, OutDir, MinimumId, MaximumId);
|
||||
break;
|
||||
case Feature.Packs:
|
||||
await Packs.DownloadPacks(_client, OutDir, UseSubfolders);
|
||||
break;
|
||||
case Feature.User:
|
||||
if (OnlyList)
|
||||
await User.ListUser(_client, Username);
|
||||
else if (OnlyNew)
|
||||
await User.DownloadUserLastModified(_client, Username, OutDir);
|
||||
else
|
||||
await User.DownloadUser(_client, Username, OutDir);
|
||||
break;
|
||||
case Feature.Quicksearch:
|
||||
if (OnlyList)
|
||||
await Search.ListSearchResults(_client, QueryString);
|
||||
else
|
||||
await Search.DownloadSearchResults(_client, QueryString, OutDir);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
698
SabreTools.RedumpLib/Formatter.cs
Normal file
698
SabreTools.RedumpLib/Formatter.cs
Normal file
@@ -0,0 +1,698 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace SabreTools.RedumpLib
|
||||
{
|
||||
public static class Formatter
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="enableRedumpCompatibility">True to enable Redump compatiblity, false otherwise</param>
|
||||
/// <returns>List of strings representing each line of an output file, null on error</returns>
|
||||
public static (List<string>?, string?) FormatOutputData(SubmissionInfo? info, bool enableRedumpCompatibility)
|
||||
{
|
||||
// Check to see if the inputs are valid
|
||||
if (info == null)
|
||||
return (null, "Submission information was missing");
|
||||
|
||||
try
|
||||
{
|
||||
// Sony-printed discs have layers in the opposite order
|
||||
var system = info.CommonDiscInfo?.System;
|
||||
bool reverseOrder = system.HasReversedRingcodes();
|
||||
|
||||
// Preamble for submission
|
||||
#pragma warning disable IDE0028
|
||||
var output = new List<string>
|
||||
{
|
||||
"Users who wish to submit this information to Redump must ensure that all of the fields below are accurate for the exact media they have.",
|
||||
"Please double-check to ensure that there are no fields that need verification, such as the version or copy protection.",
|
||||
"If there are no fields in need of verification or all fields are accurate, this preamble can be removed before submission.",
|
||||
"",
|
||||
};
|
||||
|
||||
// Common Disc Info section
|
||||
output.Add("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(),
|
||||
info.SizeAndChecksums?.PICIdentifier,
|
||||
info.SizeAndChecksums?.Size,
|
||||
info.SizeAndChecksums?.Layerbreak,
|
||||
info.SizeAndChecksums?.Layerbreak2,
|
||||
info.SizeAndChecksums?.Layerbreak3),
|
||||
1);
|
||||
AddIfExists(output, Template.CategoryField, info.CommonDiscInfo?.Category.LongName(), 1);
|
||||
AddIfExists(output, Template.FullyMatchingIDField, info.FullyMatchedID?.ToString(), 1);
|
||||
AddIfExists(output, Template.PartiallyMatchingIDsField, info.PartiallyMatchedIDs, 1);
|
||||
AddIfExists(output, Template.RegionField, info.CommonDiscInfo?.Region.LongName() ?? "SPACE! (CHANGE THIS)", 1);
|
||||
AddIfExists(output, Template.LanguagesField, (info.CommonDiscInfo?.Languages ?? [null]).Select(l => l.LongName() ?? "SILENCE! (CHANGE THIS)").ToArray(), 1);
|
||||
AddIfExists(output, Template.PlaystationLanguageSelectionViaField, (info.CommonDiscInfo?.LanguageSelection ?? []).Select(l => l.LongName()).ToArray(), 1);
|
||||
AddIfExists(output, Template.DiscSerialField, info.CommonDiscInfo?.Serial, 1);
|
||||
|
||||
// All ringcode information goes in an indented area
|
||||
output.Add(""); output.Add("\tRingcode Information:"); output.Add("");
|
||||
|
||||
// If we have a triple-layer disc
|
||||
if (info.SizeAndChecksums?.Layerbreak3 != default && info.SizeAndChecksums?.Layerbreak3 != default(long))
|
||||
{
|
||||
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);
|
||||
}
|
||||
// If we have a triple-layer disc
|
||||
else if (info.SizeAndChecksums?.Layerbreak2 != default && info.SizeAndChecksums?.Layerbreak2 != default(long))
|
||||
{
|
||||
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);
|
||||
}
|
||||
// If we have a dual-layer disc
|
||||
else if (info.SizeAndChecksums?.Layerbreak != default && info.SizeAndChecksums?.Layerbreak != default(long))
|
||||
{
|
||||
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);
|
||||
}
|
||||
// If we have a single-layer disc
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
output.Add("");
|
||||
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
|
||||
if (info.Extras?.PVD != null || info.Extras?.PIC != null || info.Extras?.BCA != null || info.Extras?.SecuritySectorRanges != null)
|
||||
{
|
||||
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
|
||||
if (!string.IsNullOrEmpty(info.CopyProtection?.Protection)
|
||||
|| (info.CopyProtection?.AntiModchip != null && info.CopyProtection.AntiModchip != YesNo.NULL)
|
||||
|| (info.CopyProtection?.LibCrypt != null && info.CopyProtection.LibCrypt != YesNo.NULL)
|
||||
|| !string.IsNullOrEmpty(info.CopyProtection?.LibCryptData)
|
||||
|| !string.IsNullOrEmpty(info.CopyProtection?.SecuROMData))
|
||||
{
|
||||
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.IsNullOrEmpty(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);
|
||||
var offset = info.TracksAndWriteOffsets.OtherWriteOffsets;
|
||||
if (Int32.TryParse(offset, out int i))
|
||||
offset = i.ToString("+#;-#;0");
|
||||
|
||||
AddIfExists(output, Template.WriteOffsetField, offset, 1);
|
||||
}
|
||||
// Size & Checksum section
|
||||
else
|
||||
{
|
||||
output.Add(""); output.Add("Size & Checksum:");
|
||||
|
||||
// Gross hack because of automatic layerbreaks in Redump
|
||||
if (!enableRedumpCompatibility
|
||||
|| (info.CommonDiscInfo?.Media.ToMediaType() != MediaType.BluRay
|
||||
&& info.CommonDiscInfo?.System.IsXGD() == false))
|
||||
{
|
||||
AddIfExists(output, Template.LayerbreakField, info.SizeAndChecksums?.Layerbreak, 1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Dumping Info section
|
||||
output.Add(""); output.Add("Dumping Info:");
|
||||
AddIfExists(output, Template.FrontendVersionField, info.DumpingInfo?.FrontendVersion, 1);
|
||||
AddIfExists(output, Template.DumpingProgramField, info.DumpingInfo?.DumpingProgram, 1);
|
||||
AddIfExists(output, Template.DumpingDateField, info.DumpingInfo?.DumpingDate, 1);
|
||||
AddIfExists(output, Template.DumpingParametersField, info.DumpingInfo?.DumpingParameters, 1);
|
||||
AddIfExists(output, Template.DumpingDriveManufacturer, info.DumpingInfo?.Manufacturer, 1);
|
||||
AddIfExists(output, Template.DumpingDriveModel, info.DumpingInfo?.Model, 1);
|
||||
AddIfExists(output, Template.DumpingDriveFirmware, info.DumpingInfo?.Firmware, 1);
|
||||
AddIfExists(output, Template.ReportedDiscType, info.DumpingInfo?.ReportedDiscType, 1);
|
||||
AddIfExists(output, Template.C2ErrorCountField, info.DumpingInfo?.C2ErrorsCount, 1);
|
||||
|
||||
// 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.IsNullOrEmpty(last))
|
||||
{
|
||||
output.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
last = output[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return (output, "Formatting complete!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (null, $"Error formatting submission info: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process any fields that have to be combined
|
||||
/// </summary>
|
||||
/// <param name="info">Information object to normalize</param>
|
||||
public static void ProcessSpecialFields(SubmissionInfo? info)
|
||||
{
|
||||
// If there is no submission info
|
||||
if (info == null)
|
||||
return;
|
||||
|
||||
// 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(
|
||||
"\n", OrderCommentTags(info.CommonDiscInfo.CommentsSpecialFields)
|
||||
.Where(kvp => !string.IsNullOrEmpty(kvp.Value))
|
||||
.Select(FormatSiteTag)
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray()
|
||||
) + "\n" + info.CommonDiscInfo.Comments;
|
||||
|
||||
// Normalize newlines
|
||||
info.CommonDiscInfo.Comments = info.CommonDiscInfo.Comments.Replace("\r\n", "\n");
|
||||
|
||||
// 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(
|
||||
"\n", OrderContentTags(info.CommonDiscInfo.ContentsSpecialFields)
|
||||
.Where(kvp => !string.IsNullOrEmpty(kvp.Value))
|
||||
.Select(FormatSiteTag)
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray()
|
||||
) + "\n" + info.CommonDiscInfo.Contents;
|
||||
|
||||
// Normalize newlines
|
||||
info.CommonDiscInfo.Contents = info.CommonDiscInfo.Contents.Replace("\r\n", "\n");
|
||||
|
||||
// Trim the contents field
|
||||
info.CommonDiscInfo.Contents = info.CommonDiscInfo.Contents.Trim();
|
||||
|
||||
// Wipe out the special fields dictionary
|
||||
info.CommonDiscInfo.ContentsSpecialFields = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <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 = string.Empty;
|
||||
for (int i = 0; i < indent; i++)
|
||||
prefix += "\t";
|
||||
|
||||
// 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");
|
||||
|
||||
// Sanitize whitespace around tabs
|
||||
value = Regex.Replace(value, @"\s*\t\s*", "\t", RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
// 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, long? value, int indent)
|
||||
{
|
||||
// If there's no valid value to write
|
||||
if (value == null || value == default(long))
|
||||
return;
|
||||
|
||||
string prefix = string.Empty;
|
||||
for (int i = 0; i < indent; i++)
|
||||
prefix += "\t";
|
||||
|
||||
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, 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()).ToArray()), indent);
|
||||
}
|
||||
|
||||
/// <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 = kvp.Key.IsMultiLine();
|
||||
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>
|
||||
/// Get the adjusted name of the media based on layers, if applicable
|
||||
/// </summary>
|
||||
/// <param name="mediaType">MediaType to get the proper name for</param>
|
||||
/// <param name="picIdentifier">PIC identifier string (BD only)</param>
|
||||
/// <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>
|
||||
/// <param name="layerbreak3">Third layerbreak value, as applicable</param>
|
||||
/// <returns>String representation of the media, including layer specification</returns>
|
||||
/// TODO: Figure out why we have this and NormalizeDiscType as well
|
||||
private static string? GetFixedMediaType(MediaType? mediaType, string? picIdentifier, long? size, long? layerbreak, long? layerbreak2, long? layerbreak3)
|
||||
{
|
||||
switch (mediaType)
|
||||
{
|
||||
case MediaType.DVD:
|
||||
if (layerbreak != default && layerbreak != default(long))
|
||||
return $"{mediaType.LongName()}-9";
|
||||
else
|
||||
return $"{mediaType.LongName()}-5";
|
||||
|
||||
case MediaType.BluRay:
|
||||
if (layerbreak3 != default && layerbreak3 != default(long))
|
||||
return $"{mediaType.LongName()}-128";
|
||||
else if (layerbreak2 != default && layerbreak2 != default(long))
|
||||
return $"{mediaType.LongName()}-100";
|
||||
else if (layerbreak != default && layerbreak != default(long) && picIdentifier == SabreTools.Models.PIC.Constants.DiscTypeIdentifierROMUltra)
|
||||
return $"{mediaType.LongName()}-66";
|
||||
else if (layerbreak != default && layerbreak != default(long) && size > 53_687_063_712)
|
||||
return $"{mediaType.LongName()}-66";
|
||||
else if (layerbreak != default && layerbreak != default(long))
|
||||
return $"{mediaType.LongName()}-50";
|
||||
else if (picIdentifier == SabreTools.Models.PIC.Constants.DiscTypeIdentifierROMUltra)
|
||||
return $"{mediaType.LongName()}-33";
|
||||
else if (size > 26_843_531_856)
|
||||
return $"{mediaType.LongName()}-33";
|
||||
else
|
||||
return $"{mediaType.LongName()}-25";
|
||||
|
||||
case MediaType.UMD:
|
||||
if (layerbreak != default && layerbreak != default(long))
|
||||
return $"{mediaType.LongName()}-DL";
|
||||
else
|
||||
return $"{mediaType.LongName()}-SL";
|
||||
|
||||
default:
|
||||
return mediaType.LongName();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return siteCode switch
|
||||
{
|
||||
SiteCode.PostgapType => true,
|
||||
SiteCode.VCD => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Order comment code tags according to Redump requirements
|
||||
/// </summary>
|
||||
/// <returns>Ordered list of KeyValuePairs representing the tags and values</returns>
|
||||
private static List<KeyValuePair<SiteCode?, string>> OrderCommentTags(Dictionary<SiteCode, string> tags)
|
||||
{
|
||||
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;
|
||||
|
||||
// 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]));
|
||||
if (tags.ContainsKey(SiteCode.InternalName))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.InternalName, tags[SiteCode.InternalName]));
|
||||
if (tags.ContainsKey(SiteCode.InternalSerialName))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.InternalSerialName, tags[SiteCode.InternalSerialName]));
|
||||
if (tags.ContainsKey(SiteCode.VolumeLabel))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.VolumeLabel, tags[SiteCode.VolumeLabel]));
|
||||
if (tags.ContainsKey(SiteCode.Multisession))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.Multisession, tags[SiteCode.Multisession]));
|
||||
if (tags.ContainsKey(SiteCode.UniversalHash))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.UniversalHash, tags[SiteCode.UniversalHash]));
|
||||
if (tags.ContainsKey(SiteCode.RingNonZeroDataStart))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.RingNonZeroDataStart, tags[SiteCode.RingNonZeroDataStart]));
|
||||
|
||||
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]));
|
||||
|
||||
if (tags.ContainsKey(SiteCode.Filename))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.Filename, tags[SiteCode.Filename]));
|
||||
|
||||
if (tags.ContainsKey(SiteCode.BBFCRegistrationNumber))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.BBFCRegistrationNumber, tags[SiteCode.BBFCRegistrationNumber]));
|
||||
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.CompatibleOS))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.CompatibleOS, tags[SiteCode.CompatibleOS]));
|
||||
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.BethesdaID))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.BethesdaID, tags[SiteCode.BethesdaID]));
|
||||
if (tags.ContainsKey(SiteCode.CDProjektID))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.CDProjektID, tags[SiteCode.CDProjektID]));
|
||||
if (tags.ContainsKey(SiteCode.EidosID))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.EidosID, tags[SiteCode.EidosID]));
|
||||
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]));
|
||||
if (tags.ContainsKey(SiteCode.SierraID))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.SierraID, tags[SiteCode.SierraID]));
|
||||
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]));
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Order content code tags according to Redump requirements
|
||||
/// </summary>
|
||||
/// <returns>Ordered list of KeyValuePairs representing the tags and values</returns>
|
||||
private static List<KeyValuePair<SiteCode?, string>> OrderContentTags(Dictionary<SiteCode, string> tags)
|
||||
{
|
||||
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;
|
||||
|
||||
// Applications
|
||||
if (tags.ContainsKey(SiteCode.Applications))
|
||||
sorted.Add(new KeyValuePair<SiteCode?, string>(SiteCode.Applications, tags[SiteCode.Applications]));
|
||||
|
||||
// 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]));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
461
SabreTools.RedumpLib/OldDotNet.cs
Normal file
461
SabreTools.RedumpLib/OldDotNet.cs
Normal file
@@ -0,0 +1,461 @@
|
||||
#if NET20 || NET35
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.RedumpLib
|
||||
{
|
||||
/// <see href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs"/>
|
||||
internal static class WebUtility
|
||||
{
|
||||
// some consts copied from Char / CharUnicodeInfo since we don't have friend access to those types
|
||||
private const char HIGH_SURROGATE_START = '\uD800';
|
||||
private const char LOW_SURROGATE_START = '\uDC00';
|
||||
private const char LOW_SURROGATE_END = '\uDFFF';
|
||||
private const int UNICODE_PLANE00_END = 0x00FFFF;
|
||||
private const int UNICODE_PLANE01_START = 0x10000;
|
||||
private const int UNICODE_PLANE16_END = 0x10FFFF;
|
||||
|
||||
public static string? HtmlDecode(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
char[] valueSpan = value!.ToCharArray();
|
||||
|
||||
int index = Array.IndexOf(valueSpan, '&');
|
||||
if (index < 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
// In the worst case the decoded string has the same length.
|
||||
// For small inputs we use stack allocation.
|
||||
StringBuilder sb = value.Length <= 256 ?
|
||||
new StringBuilder(256) :
|
||||
new StringBuilder(value.Length);
|
||||
|
||||
sb.Append(valueSpan.Take(index).ToArray());
|
||||
HtmlDecode(valueSpan.Skip(index).ToArray(), ref sb);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void HtmlDecode(char[] input, ref StringBuilder output)
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
char ch = input[i];
|
||||
|
||||
if (ch == '&')
|
||||
{
|
||||
// We found a '&'. Now look for the next ';' or '&'. The idea is that
|
||||
// if we find another '&' before finding a ';', then this is not an entity,
|
||||
// and the next '&' might start a real entity (VSWhidbey 275184)
|
||||
char[] inputSlice = input.Skip(i + 1).ToArray();
|
||||
|
||||
int semicolonPos = Array.IndexOf(inputSlice, ';');
|
||||
int ampersandPos = Array.IndexOf(inputSlice, '&');
|
||||
int entityLength;
|
||||
if (semicolonPos > -1 && ampersandPos > -1)
|
||||
entityLength = Math.Min(semicolonPos, ampersandPos);
|
||||
else if (semicolonPos <= -1 && ampersandPos > -1)
|
||||
entityLength = ampersandPos;
|
||||
else if (semicolonPos > -1 && ampersandPos <= -1)
|
||||
entityLength = semicolonPos;
|
||||
else
|
||||
entityLength = -1;
|
||||
|
||||
if (entityLength >= 0 && inputSlice[entityLength] == ';')
|
||||
{
|
||||
int entityEndPosition = (i + 1) + entityLength;
|
||||
if (entityLength > 1 && inputSlice[0] == '#')
|
||||
{
|
||||
// The # syntax can be in decimal or hex, e.g.
|
||||
// å --> decimal
|
||||
// å --> same char in hex
|
||||
// See http://www.w3.org/TR/REC-html40/charset.html#entities
|
||||
|
||||
bool parsedSuccessfully = inputSlice[1] == 'x' || inputSlice[1] == 'X'
|
||||
? uint.TryParse(new string(inputSlice.Skip(2).Take(entityLength - 2).ToArray()), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out uint parsedValue)
|
||||
: uint.TryParse(new string(inputSlice.Skip(1).Take(entityLength - 1).ToArray()), NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedValue);
|
||||
|
||||
if (parsedSuccessfully)
|
||||
{
|
||||
// decoded character must be U+0000 .. U+10FFFF, excluding surrogates
|
||||
parsedSuccessfully = ((parsedValue < HIGH_SURROGATE_START) || (LOW_SURROGATE_END < parsedValue && parsedValue <= UNICODE_PLANE16_END));
|
||||
}
|
||||
|
||||
if (parsedSuccessfully)
|
||||
{
|
||||
if (parsedValue <= UNICODE_PLANE00_END)
|
||||
{
|
||||
// single character
|
||||
output.Append((char)parsedValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// multi-character
|
||||
ConvertSmpToUtf16(parsedValue, out char leadingSurrogate, out char trailingSurrogate);
|
||||
output.Append(leadingSurrogate);
|
||||
output.Append(trailingSurrogate);
|
||||
}
|
||||
|
||||
i = entityEndPosition; // already looked at everything until semicolon
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char[] entity = inputSlice.Take(entityLength).ToArray();
|
||||
i = entityEndPosition; // already looked at everything until semicolon
|
||||
char entityChar = HtmlEntities.Lookup(entity);
|
||||
|
||||
if (entityChar != (char)0)
|
||||
{
|
||||
ch = entityChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Append('&');
|
||||
output.Append(entity);
|
||||
output.Append(';');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.Append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
// similar to Char.ConvertFromUtf32, but doesn't check arguments or generate strings
|
||||
// input is assumed to be an SMP character
|
||||
private static void ConvertSmpToUtf16(uint smpChar, out char leadingSurrogate, out char trailingSurrogate)
|
||||
{
|
||||
int utf32 = (int)(smpChar - UNICODE_PLANE01_START);
|
||||
leadingSurrogate = (char)((utf32 / 0x400) + HIGH_SURROGATE_START);
|
||||
trailingSurrogate = (char)((utf32 % 0x400) + LOW_SURROGATE_START);
|
||||
}
|
||||
|
||||
// helper class for lookup of HTML encoding entities
|
||||
private static class HtmlEntities
|
||||
{
|
||||
// The list is from http://www.w3.org/TR/REC-html40/sgml/entities.html, except for ', which
|
||||
// is defined in http://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent.
|
||||
private static Dictionary<ulong, char> InitializeLookupTable()
|
||||
{
|
||||
byte[] tableData =
|
||||
[
|
||||
0x74, 0x6F, 0x75, 0x71, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("quot")*/ 0x22, 0x00, /*'\x0022'*/
|
||||
0x70, 0x6D, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("amp")*/ 0x26, 0x00, /*'\x0026'*/
|
||||
0x73, 0x6F, 0x70, 0x61, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("apos")*/ 0x27, 0x00, /*'\x0027'*/
|
||||
0x74, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("lt")*/ 0x3C, 0x00, /*'\x003c'*/
|
||||
0x74, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("gt")*/ 0x3E, 0x00, /*'\x003e'*/
|
||||
0x70, 0x73, 0x62, 0x6E, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("nbsp")*/ 0xA0, 0x00, /*'\x00a0'*/
|
||||
0x6C, 0x63, 0x78, 0x65, 0x69, 0x00, 0x00, 0x00, /*ToUInt64Key("iexcl")*/ 0xA1, 0x00, /*'\x00a1'*/
|
||||
0x74, 0x6E, 0x65, 0x63, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("cent")*/ 0xA2, 0x00, /*'\x00a2'*/
|
||||
0x64, 0x6E, 0x75, 0x6F, 0x70, 0x00, 0x00, 0x00, /*ToUInt64Key("pound")*/ 0xA3, 0x00, /*'\x00a3'*/
|
||||
0x6E, 0x65, 0x72, 0x72, 0x75, 0x63, 0x00, 0x00, /*ToUInt64Key("curren")*/ 0xA4, 0x00, /*'\x00a4'*/
|
||||
0x6E, 0x65, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("yen")*/ 0xA5, 0x00, /*'\x00a5'*/
|
||||
0x72, 0x61, 0x62, 0x76, 0x72, 0x62, 0x00, 0x00, /*ToUInt64Key("brvbar")*/ 0xA6, 0x00, /*'\x00a6'*/
|
||||
0x74, 0x63, 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sect")*/ 0xA7, 0x00, /*'\x00a7'*/
|
||||
0x6C, 0x6D, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("uml")*/ 0xA8, 0x00, /*'\x00a8'*/
|
||||
0x79, 0x70, 0x6F, 0x63, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("copy")*/ 0xA9, 0x00, /*'\x00a9'*/
|
||||
0x66, 0x64, 0x72, 0x6F, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ordf")*/ 0xAA, 0x00, /*'\x00aa'*/
|
||||
0x6F, 0x75, 0x71, 0x61, 0x6C, 0x00, 0x00, 0x00, /*ToUInt64Key("laquo")*/ 0xAB, 0x00, /*'\x00ab'*/
|
||||
0x74, 0x6F, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("not")*/ 0xAC, 0x00, /*'\x00ac'*/
|
||||
0x79, 0x68, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("shy")*/ 0xAD, 0x00, /*'\x00ad'*/
|
||||
0x67, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("reg")*/ 0xAE, 0x00, /*'\x00ae'*/
|
||||
0x72, 0x63, 0x61, 0x6D, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("macr")*/ 0xAF, 0x00, /*'\x00af'*/
|
||||
0x67, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("deg")*/ 0xB0, 0x00, /*'\x00b0'*/
|
||||
0x6E, 0x6D, 0x73, 0x75, 0x6C, 0x70, 0x00, 0x00, /*ToUInt64Key("plusmn")*/ 0xB1, 0x00, /*'\x00b1'*/
|
||||
0x32, 0x70, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sup2")*/ 0xB2, 0x00, /*'\x00b2'*/
|
||||
0x33, 0x70, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sup3")*/ 0xB3, 0x00, /*'\x00b3'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x00, 0x00, 0x00, /*ToUInt64Key("acute")*/ 0xB4, 0x00, /*'\x00b4'*/
|
||||
0x6F, 0x72, 0x63, 0x69, 0x6D, 0x00, 0x00, 0x00, /*ToUInt64Key("micro")*/ 0xB5, 0x00, /*'\x00b5'*/
|
||||
0x61, 0x72, 0x61, 0x70, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("para")*/ 0xB6, 0x00, /*'\x00b6'*/
|
||||
0x74, 0x6F, 0x64, 0x64, 0x69, 0x6D, 0x00, 0x00, /*ToUInt64Key("middot")*/ 0xB7, 0x00, /*'\x00b7'*/
|
||||
0x6C, 0x69, 0x64, 0x65, 0x63, 0x00, 0x00, 0x00, /*ToUInt64Key("cedil")*/ 0xB8, 0x00, /*'\x00b8'*/
|
||||
0x31, 0x70, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sup1")*/ 0xB9, 0x00, /*'\x00b9'*/
|
||||
0x6D, 0x64, 0x72, 0x6F, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ordm")*/ 0xBA, 0x00, /*'\x00ba'*/
|
||||
0x6F, 0x75, 0x71, 0x61, 0x72, 0x00, 0x00, 0x00, /*ToUInt64Key("raquo")*/ 0xBB, 0x00, /*'\x00bb'*/
|
||||
0x34, 0x31, 0x63, 0x61, 0x72, 0x66, 0x00, 0x00, /*ToUInt64Key("frac14")*/ 0xBC, 0x00, /*'\x00bc'*/
|
||||
0x32, 0x31, 0x63, 0x61, 0x72, 0x66, 0x00, 0x00, /*ToUInt64Key("frac12")*/ 0xBD, 0x00, /*'\x00bd'*/
|
||||
0x34, 0x33, 0x63, 0x61, 0x72, 0x66, 0x00, 0x00, /*ToUInt64Key("frac34")*/ 0xBE, 0x00, /*'\x00be'*/
|
||||
0x74, 0x73, 0x65, 0x75, 0x71, 0x69, 0x00, 0x00, /*ToUInt64Key("iquest")*/ 0xBF, 0x00, /*'\x00bf'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x41, 0x00, 0x00, /*ToUInt64Key("Agrave")*/ 0xC0, 0x00, /*'\x00c0'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x41, 0x00, 0x00, /*ToUInt64Key("Aacute")*/ 0xC1, 0x00, /*'\x00c1'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x41, 0x00, 0x00, 0x00, /*ToUInt64Key("Acirc")*/ 0xC2, 0x00, /*'\x00c2'*/
|
||||
0x65, 0x64, 0x6C, 0x69, 0x74, 0x41, 0x00, 0x00, /*ToUInt64Key("Atilde")*/ 0xC3, 0x00, /*'\x00c3'*/
|
||||
0x6C, 0x6D, 0x75, 0x41, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Auml")*/ 0xC4, 0x00, /*'\x00c4'*/
|
||||
0x67, 0x6E, 0x69, 0x72, 0x41, 0x00, 0x00, 0x00, /*ToUInt64Key("Aring")*/ 0xC5, 0x00, /*'\x00c5'*/
|
||||
0x67, 0x69, 0x6C, 0x45, 0x41, 0x00, 0x00, 0x00, /*ToUInt64Key("AElig")*/ 0xC6, 0x00, /*'\x00c6'*/
|
||||
0x6C, 0x69, 0x64, 0x65, 0x63, 0x43, 0x00, 0x00, /*ToUInt64Key("Ccedil")*/ 0xC7, 0x00, /*'\x00c7'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x45, 0x00, 0x00, /*ToUInt64Key("Egrave")*/ 0xC8, 0x00, /*'\x00c8'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x45, 0x00, 0x00, /*ToUInt64Key("Eacute")*/ 0xC9, 0x00, /*'\x00c9'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x45, 0x00, 0x00, 0x00, /*ToUInt64Key("Ecirc")*/ 0xCA, 0x00, /*'\x00ca'*/
|
||||
0x6C, 0x6D, 0x75, 0x45, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Euml")*/ 0xCB, 0x00, /*'\x00cb'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x49, 0x00, 0x00, /*ToUInt64Key("Igrave")*/ 0xCC, 0x00, /*'\x00cc'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x49, 0x00, 0x00, /*ToUInt64Key("Iacute")*/ 0xCD, 0x00, /*'\x00cd'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x49, 0x00, 0x00, 0x00, /*ToUInt64Key("Icirc")*/ 0xCE, 0x00, /*'\x00ce'*/
|
||||
0x6C, 0x6D, 0x75, 0x49, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Iuml")*/ 0xCF, 0x00, /*'\x00cf'*/
|
||||
0x48, 0x54, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ETH")*/ 0xD0, 0x00, /*'\x00d0'*/
|
||||
0x65, 0x64, 0x6C, 0x69, 0x74, 0x4E, 0x00, 0x00, /*ToUInt64Key("Ntilde")*/ 0xD1, 0x00, /*'\x00d1'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x4F, 0x00, 0x00, /*ToUInt64Key("Ograve")*/ 0xD2, 0x00, /*'\x00d2'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x4F, 0x00, 0x00, /*ToUInt64Key("Oacute")*/ 0xD3, 0x00, /*'\x00d3'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x4F, 0x00, 0x00, 0x00, /*ToUInt64Key("Ocirc")*/ 0xD4, 0x00, /*'\x00d4'*/
|
||||
0x65, 0x64, 0x6C, 0x69, 0x74, 0x4F, 0x00, 0x00, /*ToUInt64Key("Otilde")*/ 0xD5, 0x00, /*'\x00d5'*/
|
||||
0x6C, 0x6D, 0x75, 0x4F, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Ouml")*/ 0xD6, 0x00, /*'\x00d6'*/
|
||||
0x73, 0x65, 0x6D, 0x69, 0x74, 0x00, 0x00, 0x00, /*ToUInt64Key("times")*/ 0xD7, 0x00, /*'\x00d7'*/
|
||||
0x68, 0x73, 0x61, 0x6C, 0x73, 0x4F, 0x00, 0x00, /*ToUInt64Key("Oslash")*/ 0xD8, 0x00, /*'\x00d8'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x55, 0x00, 0x00, /*ToUInt64Key("Ugrave")*/ 0xD9, 0x00, /*'\x00d9'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x55, 0x00, 0x00, /*ToUInt64Key("Uacute")*/ 0xDA, 0x00, /*'\x00da'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x55, 0x00, 0x00, 0x00, /*ToUInt64Key("Ucirc")*/ 0xDB, 0x00, /*'\x00db'*/
|
||||
0x6C, 0x6D, 0x75, 0x55, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Uuml")*/ 0xDC, 0x00, /*'\x00dc'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x59, 0x00, 0x00, /*ToUInt64Key("Yacute")*/ 0xDD, 0x00, /*'\x00dd'*/
|
||||
0x4E, 0x52, 0x4F, 0x48, 0x54, 0x00, 0x00, 0x00, /*ToUInt64Key("THORN")*/ 0xDE, 0x00, /*'\x00de'*/
|
||||
0x67, 0x69, 0x6C, 0x7A, 0x73, 0x00, 0x00, 0x00, /*ToUInt64Key("szlig")*/ 0xDF, 0x00, /*'\x00df'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x61, 0x00, 0x00, /*ToUInt64Key("agrave")*/ 0xE0, 0x00, /*'\x00e0'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x61, 0x00, 0x00, /*ToUInt64Key("aacute")*/ 0xE1, 0x00, /*'\x00e1'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x61, 0x00, 0x00, 0x00, /*ToUInt64Key("acirc")*/ 0xE2, 0x00, /*'\x00e2'*/
|
||||
0x65, 0x64, 0x6C, 0x69, 0x74, 0x61, 0x00, 0x00, /*ToUInt64Key("atilde")*/ 0xE3, 0x00, /*'\x00e3'*/
|
||||
0x6C, 0x6D, 0x75, 0x61, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("auml")*/ 0xE4, 0x00, /*'\x00e4'*/
|
||||
0x67, 0x6E, 0x69, 0x72, 0x61, 0x00, 0x00, 0x00, /*ToUInt64Key("aring")*/ 0xE5, 0x00, /*'\x00e5'*/
|
||||
0x67, 0x69, 0x6C, 0x65, 0x61, 0x00, 0x00, 0x00, /*ToUInt64Key("aelig")*/ 0xE6, 0x00, /*'\x00e6'*/
|
||||
0x6C, 0x69, 0x64, 0x65, 0x63, 0x63, 0x00, 0x00, /*ToUInt64Key("ccedil")*/ 0xE7, 0x00, /*'\x00e7'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x65, 0x00, 0x00, /*ToUInt64Key("egrave")*/ 0xE8, 0x00, /*'\x00e8'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x65, 0x00, 0x00, /*ToUInt64Key("eacute")*/ 0xE9, 0x00, /*'\x00e9'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, /*ToUInt64Key("ecirc")*/ 0xEA, 0x00, /*'\x00ea'*/
|
||||
0x6C, 0x6D, 0x75, 0x65, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("euml")*/ 0xEB, 0x00, /*'\x00eb'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x69, 0x00, 0x00, /*ToUInt64Key("igrave")*/ 0xEC, 0x00, /*'\x00ec'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x69, 0x00, 0x00, /*ToUInt64Key("iacute")*/ 0xED, 0x00, /*'\x00ed'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x69, 0x00, 0x00, 0x00, /*ToUInt64Key("icirc")*/ 0xEE, 0x00, /*'\x00ee'*/
|
||||
0x6C, 0x6D, 0x75, 0x69, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("iuml")*/ 0xEF, 0x00, /*'\x00ef'*/
|
||||
0x68, 0x74, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("eth")*/ 0xF0, 0x00, /*'\x00f0'*/
|
||||
0x65, 0x64, 0x6C, 0x69, 0x74, 0x6E, 0x00, 0x00, /*ToUInt64Key("ntilde")*/ 0xF1, 0x00, /*'\x00f1'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x6F, 0x00, 0x00, /*ToUInt64Key("ograve")*/ 0xF2, 0x00, /*'\x00f2'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x6F, 0x00, 0x00, /*ToUInt64Key("oacute")*/ 0xF3, 0x00, /*'\x00f3'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x6F, 0x00, 0x00, 0x00, /*ToUInt64Key("ocirc")*/ 0xF4, 0x00, /*'\x00f4'*/
|
||||
0x65, 0x64, 0x6C, 0x69, 0x74, 0x6F, 0x00, 0x00, /*ToUInt64Key("otilde")*/ 0xF5, 0x00, /*'\x00f5'*/
|
||||
0x6C, 0x6D, 0x75, 0x6F, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ouml")*/ 0xF6, 0x00, /*'\x00f6'*/
|
||||
0x65, 0x64, 0x69, 0x76, 0x69, 0x64, 0x00, 0x00, /*ToUInt64Key("divide")*/ 0xF7, 0x00, /*'\x00f7'*/
|
||||
0x68, 0x73, 0x61, 0x6C, 0x73, 0x6F, 0x00, 0x00, /*ToUInt64Key("oslash")*/ 0xF8, 0x00, /*'\x00f8'*/
|
||||
0x65, 0x76, 0x61, 0x72, 0x67, 0x75, 0x00, 0x00, /*ToUInt64Key("ugrave")*/ 0xF9, 0x00, /*'\x00f9'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x75, 0x00, 0x00, /*ToUInt64Key("uacute")*/ 0xFA, 0x00, /*'\x00fa'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x75, 0x00, 0x00, 0x00, /*ToUInt64Key("ucirc")*/ 0xFB, 0x00, /*'\x00fb'*/
|
||||
0x6C, 0x6D, 0x75, 0x75, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("uuml")*/ 0xFC, 0x00, /*'\x00fc'*/
|
||||
0x65, 0x74, 0x75, 0x63, 0x61, 0x79, 0x00, 0x00, /*ToUInt64Key("yacute")*/ 0xFD, 0x00, /*'\x00fd'*/
|
||||
0x6E, 0x72, 0x6F, 0x68, 0x74, 0x00, 0x00, 0x00, /*ToUInt64Key("thorn")*/ 0xFE, 0x00, /*'\x00fe'*/
|
||||
0x6C, 0x6D, 0x75, 0x79, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("yuml")*/ 0xFF, 0x00, /*'\x00ff'*/
|
||||
0x67, 0x69, 0x6C, 0x45, 0x4F, 0x00, 0x00, 0x00, /*ToUInt64Key("OElig")*/ 0x52, 0x01, /*'\x0152'*/
|
||||
0x67, 0x69, 0x6C, 0x65, 0x6F, 0x00, 0x00, 0x00, /*ToUInt64Key("oelig")*/ 0x53, 0x01, /*'\x0153'*/
|
||||
0x6E, 0x6F, 0x72, 0x61, 0x63, 0x53, 0x00, 0x00, /*ToUInt64Key("Scaron")*/ 0x60, 0x01, /*'\x0160'*/
|
||||
0x6E, 0x6F, 0x72, 0x61, 0x63, 0x73, 0x00, 0x00, /*ToUInt64Key("scaron")*/ 0x61, 0x01, /*'\x0161'*/
|
||||
0x6C, 0x6D, 0x75, 0x59, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Yuml")*/ 0x78, 0x01, /*'\x0178'*/
|
||||
0x66, 0x6F, 0x6E, 0x66, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("fnof")*/ 0x92, 0x01, /*'\x0192'*/
|
||||
0x63, 0x72, 0x69, 0x63, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("circ")*/ 0xC6, 0x02, /*'\x02c6'*/
|
||||
0x65, 0x64, 0x6C, 0x69, 0x74, 0x00, 0x00, 0x00, /*ToUInt64Key("tilde")*/ 0xDC, 0x02, /*'\x02dc'*/
|
||||
0x61, 0x68, 0x70, 0x6C, 0x41, 0x00, 0x00, 0x00, /*ToUInt64Key("Alpha")*/ 0x91, 0x03, /*'\x0391'*/
|
||||
0x61, 0x74, 0x65, 0x42, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Beta")*/ 0x92, 0x03, /*'\x0392'*/
|
||||
0x61, 0x6D, 0x6D, 0x61, 0x47, 0x00, 0x00, 0x00, /*ToUInt64Key("Gamma")*/ 0x93, 0x03, /*'\x0393'*/
|
||||
0x61, 0x74, 0x6C, 0x65, 0x44, 0x00, 0x00, 0x00, /*ToUInt64Key("Delta")*/ 0x94, 0x03, /*'\x0394'*/
|
||||
0x6E, 0x6F, 0x6C, 0x69, 0x73, 0x70, 0x45, 0x00, /*ToUInt64Key("Epsilon")*/ 0x95, 0x03, /*'\x0395'*/
|
||||
0x61, 0x74, 0x65, 0x5A, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Zeta")*/ 0x96, 0x03, /*'\x0396'*/
|
||||
0x61, 0x74, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Eta")*/ 0x97, 0x03, /*'\x0397'*/
|
||||
0x61, 0x74, 0x65, 0x68, 0x54, 0x00, 0x00, 0x00, /*ToUInt64Key("Theta")*/ 0x98, 0x03, /*'\x0398'*/
|
||||
0x61, 0x74, 0x6F, 0x49, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Iota")*/ 0x99, 0x03, /*'\x0399'*/
|
||||
0x61, 0x70, 0x70, 0x61, 0x4B, 0x00, 0x00, 0x00, /*ToUInt64Key("Kappa")*/ 0x9A, 0x03, /*'\x039a'*/
|
||||
0x61, 0x64, 0x62, 0x6D, 0x61, 0x4C, 0x00, 0x00, /*ToUInt64Key("Lambda")*/ 0x9B, 0x03, /*'\x039b'*/
|
||||
0x75, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Mu")*/ 0x9C, 0x03, /*'\x039c'*/
|
||||
0x75, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Nu")*/ 0x9D, 0x03, /*'\x039d'*/
|
||||
0x69, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Xi")*/ 0x9E, 0x03, /*'\x039e'*/
|
||||
0x6E, 0x6F, 0x72, 0x63, 0x69, 0x6D, 0x4F, 0x00, /*ToUInt64Key("Omicron")*/ 0x9F, 0x03, /*'\x039f'*/
|
||||
0x69, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Pi")*/ 0xA0, 0x03, /*'\x03a0'*/
|
||||
0x6F, 0x68, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Rho")*/ 0xA1, 0x03, /*'\x03a1'*/
|
||||
0x61, 0x6D, 0x67, 0x69, 0x53, 0x00, 0x00, 0x00, /*ToUInt64Key("Sigma")*/ 0xA3, 0x03, /*'\x03a3'*/
|
||||
0x75, 0x61, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Tau")*/ 0xA4, 0x03, /*'\x03a4'*/
|
||||
0x6E, 0x6F, 0x6C, 0x69, 0x73, 0x70, 0x55, 0x00, /*ToUInt64Key("Upsilon")*/ 0xA5, 0x03, /*'\x03a5'*/
|
||||
0x69, 0x68, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Phi")*/ 0xA6, 0x03, /*'\x03a6'*/
|
||||
0x69, 0x68, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Chi")*/ 0xA7, 0x03, /*'\x03a7'*/
|
||||
0x69, 0x73, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("Psi")*/ 0xA8, 0x03, /*'\x03a8'*/
|
||||
0x61, 0x67, 0x65, 0x6D, 0x4F, 0x00, 0x00, 0x00, /*ToUInt64Key("Omega")*/ 0xA9, 0x03, /*'\x03a9'*/
|
||||
0x61, 0x68, 0x70, 0x6C, 0x61, 0x00, 0x00, 0x00, /*ToUInt64Key("alpha")*/ 0xB1, 0x03, /*'\x03b1'*/
|
||||
0x61, 0x74, 0x65, 0x62, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("beta")*/ 0xB2, 0x03, /*'\x03b2'*/
|
||||
0x61, 0x6D, 0x6D, 0x61, 0x67, 0x00, 0x00, 0x00, /*ToUInt64Key("gamma")*/ 0xB3, 0x03, /*'\x03b3'*/
|
||||
0x61, 0x74, 0x6C, 0x65, 0x64, 0x00, 0x00, 0x00, /*ToUInt64Key("delta")*/ 0xB4, 0x03, /*'\x03b4'*/
|
||||
0x6E, 0x6F, 0x6C, 0x69, 0x73, 0x70, 0x65, 0x00, /*ToUInt64Key("epsilon")*/ 0xB5, 0x03, /*'\x03b5'*/
|
||||
0x61, 0x74, 0x65, 0x7A, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("zeta")*/ 0xB6, 0x03, /*'\x03b6'*/
|
||||
0x61, 0x74, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("eta")*/ 0xB7, 0x03, /*'\x03b7'*/
|
||||
0x61, 0x74, 0x65, 0x68, 0x74, 0x00, 0x00, 0x00, /*ToUInt64Key("theta")*/ 0xB8, 0x03, /*'\x03b8'*/
|
||||
0x61, 0x74, 0x6F, 0x69, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("iota")*/ 0xB9, 0x03, /*'\x03b9'*/
|
||||
0x61, 0x70, 0x70, 0x61, 0x6B, 0x00, 0x00, 0x00, /*ToUInt64Key("kappa")*/ 0xBA, 0x03, /*'\x03ba'*/
|
||||
0x61, 0x64, 0x62, 0x6D, 0x61, 0x6C, 0x00, 0x00, /*ToUInt64Key("lambda")*/ 0xBB, 0x03, /*'\x03bb'*/
|
||||
0x75, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("mu")*/ 0xBC, 0x03, /*'\x03bc'*/
|
||||
0x75, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("nu")*/ 0xBD, 0x03, /*'\x03bd'*/
|
||||
0x69, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("xi")*/ 0xBE, 0x03, /*'\x03be'*/
|
||||
0x6E, 0x6F, 0x72, 0x63, 0x69, 0x6D, 0x6F, 0x00, /*ToUInt64Key("omicron")*/ 0xBF, 0x03, /*'\x03bf'*/
|
||||
0x69, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("pi")*/ 0xC0, 0x03, /*'\x03c0'*/
|
||||
0x6F, 0x68, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("rho")*/ 0xC1, 0x03, /*'\x03c1'*/
|
||||
0x66, 0x61, 0x6D, 0x67, 0x69, 0x73, 0x00, 0x00, /*ToUInt64Key("sigmaf")*/ 0xC2, 0x03, /*'\x03c2'*/
|
||||
0x61, 0x6D, 0x67, 0x69, 0x73, 0x00, 0x00, 0x00, /*ToUInt64Key("sigma")*/ 0xC3, 0x03, /*'\x03c3'*/
|
||||
0x75, 0x61, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("tau")*/ 0xC4, 0x03, /*'\x03c4'*/
|
||||
0x6E, 0x6F, 0x6C, 0x69, 0x73, 0x70, 0x75, 0x00, /*ToUInt64Key("upsilon")*/ 0xC5, 0x03, /*'\x03c5'*/
|
||||
0x69, 0x68, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("phi")*/ 0xC6, 0x03, /*'\x03c6'*/
|
||||
0x69, 0x68, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("chi")*/ 0xC7, 0x03, /*'\x03c7'*/
|
||||
0x69, 0x73, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("psi")*/ 0xC8, 0x03, /*'\x03c8'*/
|
||||
0x61, 0x67, 0x65, 0x6D, 0x6F, 0x00, 0x00, 0x00, /*ToUInt64Key("omega")*/ 0xC9, 0x03, /*'\x03c9'*/
|
||||
0x6D, 0x79, 0x73, 0x61, 0x74, 0x65, 0x68, 0x74, /*ToUInt64Key("thetasym")*/0xD1, 0x03, /*'\x03d1'*/
|
||||
0x68, 0x69, 0x73, 0x70, 0x75, 0x00, 0x00, 0x00, /*ToUInt64Key("upsih")*/ 0xD2, 0x03, /*'\x03d2'*/
|
||||
0x76, 0x69, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("piv")*/ 0xD6, 0x03, /*'\x03d6'*/
|
||||
0x70, 0x73, 0x6E, 0x65, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ensp")*/ 0x02, 0x20, /*'\x2002'*/
|
||||
0x70, 0x73, 0x6D, 0x65, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("emsp")*/ 0x03, 0x20, /*'\x2003'*/
|
||||
0x70, 0x73, 0x6E, 0x69, 0x68, 0x74, 0x00, 0x00, /*ToUInt64Key("thinsp")*/ 0x09, 0x20, /*'\x2009'*/
|
||||
0x6A, 0x6E, 0x77, 0x7A, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("zwnj")*/ 0x0C, 0x20, /*'\x200c'*/
|
||||
0x6A, 0x77, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("zwj")*/ 0x0D, 0x20, /*'\x200d'*/
|
||||
0x6D, 0x72, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("lrm")*/ 0x0E, 0x20, /*'\x200e'*/
|
||||
0x6D, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("rlm")*/ 0x0F, 0x20, /*'\x200f'*/
|
||||
0x68, 0x73, 0x61, 0x64, 0x6E, 0x00, 0x00, 0x00, /*ToUInt64Key("ndash")*/ 0x13, 0x20, /*'\x2013'*/
|
||||
0x68, 0x73, 0x61, 0x64, 0x6D, 0x00, 0x00, 0x00, /*ToUInt64Key("mdash")*/ 0x14, 0x20, /*'\x2014'*/
|
||||
0x6F, 0x75, 0x71, 0x73, 0x6C, 0x00, 0x00, 0x00, /*ToUInt64Key("lsquo")*/ 0x18, 0x20, /*'\x2018'*/
|
||||
0x6F, 0x75, 0x71, 0x73, 0x72, 0x00, 0x00, 0x00, /*ToUInt64Key("rsquo")*/ 0x19, 0x20, /*'\x2019'*/
|
||||
0x6F, 0x75, 0x71, 0x62, 0x73, 0x00, 0x00, 0x00, /*ToUInt64Key("sbquo")*/ 0x1A, 0x20, /*'\x201a'*/
|
||||
0x6F, 0x75, 0x71, 0x64, 0x6C, 0x00, 0x00, 0x00, /*ToUInt64Key("ldquo")*/ 0x1C, 0x20, /*'\x201c'*/
|
||||
0x6F, 0x75, 0x71, 0x64, 0x72, 0x00, 0x00, 0x00, /*ToUInt64Key("rdquo")*/ 0x1D, 0x20, /*'\x201d'*/
|
||||
0x6F, 0x75, 0x71, 0x64, 0x62, 0x00, 0x00, 0x00, /*ToUInt64Key("bdquo")*/ 0x1E, 0x20, /*'\x201e'*/
|
||||
0x72, 0x65, 0x67, 0x67, 0x61, 0x64, 0x00, 0x00, /*ToUInt64Key("dagger")*/ 0x20, 0x20, /*'\x2020'*/
|
||||
0x72, 0x65, 0x67, 0x67, 0x61, 0x44, 0x00, 0x00, /*ToUInt64Key("Dagger")*/ 0x21, 0x20, /*'\x2021'*/
|
||||
0x6C, 0x6C, 0x75, 0x62, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("bull")*/ 0x22, 0x20, /*'\x2022'*/
|
||||
0x70, 0x69, 0x6C, 0x6C, 0x65, 0x68, 0x00, 0x00, /*ToUInt64Key("hellip")*/ 0x26, 0x20, /*'\x2026'*/
|
||||
0x6C, 0x69, 0x6D, 0x72, 0x65, 0x70, 0x00, 0x00, /*ToUInt64Key("permil")*/ 0x30, 0x20, /*'\x2030'*/
|
||||
0x65, 0x6D, 0x69, 0x72, 0x70, 0x00, 0x00, 0x00, /*ToUInt64Key("prime")*/ 0x32, 0x20, /*'\x2032'*/
|
||||
0x65, 0x6D, 0x69, 0x72, 0x50, 0x00, 0x00, 0x00, /*ToUInt64Key("Prime")*/ 0x33, 0x20, /*'\x2033'*/
|
||||
0x6F, 0x75, 0x71, 0x61, 0x73, 0x6C, 0x00, 0x00, /*ToUInt64Key("lsaquo")*/ 0x39, 0x20, /*'\x2039'*/
|
||||
0x6F, 0x75, 0x71, 0x61, 0x73, 0x72, 0x00, 0x00, /*ToUInt64Key("rsaquo")*/ 0x3A, 0x20, /*'\x203a'*/
|
||||
0x65, 0x6E, 0x69, 0x6C, 0x6F, 0x00, 0x00, 0x00, /*ToUInt64Key("oline")*/ 0x3E, 0x20, /*'\x203e'*/
|
||||
0x6C, 0x73, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, /*ToUInt64Key("frasl")*/ 0x44, 0x20, /*'\x2044'*/
|
||||
0x6F, 0x72, 0x75, 0x65, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("euro")*/ 0xAC, 0x20, /*'\x20ac'*/
|
||||
0x65, 0x67, 0x61, 0x6D, 0x69, 0x00, 0x00, 0x00, /*ToUInt64Key("image")*/ 0x11, 0x21, /*'\x2111'*/
|
||||
0x70, 0x72, 0x65, 0x69, 0x65, 0x77, 0x00, 0x00, /*ToUInt64Key("weierp")*/ 0x18, 0x21, /*'\x2118'*/
|
||||
0x6C, 0x61, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("real")*/ 0x1C, 0x21, /*'\x211c'*/
|
||||
0x65, 0x64, 0x61, 0x72, 0x74, 0x00, 0x00, 0x00, /*ToUInt64Key("trade")*/ 0x22, 0x21, /*'\x2122'*/
|
||||
0x6D, 0x79, 0x73, 0x66, 0x65, 0x6C, 0x61, 0x00, /*ToUInt64Key("alefsym")*/ 0x35, 0x21, /*'\x2135'*/
|
||||
0x72, 0x72, 0x61, 0x6C, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("larr")*/ 0x90, 0x21, /*'\x2190'*/
|
||||
0x72, 0x72, 0x61, 0x75, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("uarr")*/ 0x91, 0x21, /*'\x2191'*/
|
||||
0x72, 0x72, 0x61, 0x72, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("rarr")*/ 0x92, 0x21, /*'\x2192'*/
|
||||
0x72, 0x72, 0x61, 0x64, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("darr")*/ 0x93, 0x21, /*'\x2193'*/
|
||||
0x72, 0x72, 0x61, 0x68, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("harr")*/ 0x94, 0x21, /*'\x2194'*/
|
||||
0x72, 0x72, 0x61, 0x72, 0x63, 0x00, 0x00, 0x00, /*ToUInt64Key("crarr")*/ 0xB5, 0x21, /*'\x21b5'*/
|
||||
0x72, 0x72, 0x41, 0x6C, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("lArr")*/ 0xD0, 0x21, /*'\x21d0'*/
|
||||
0x72, 0x72, 0x41, 0x75, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("uArr")*/ 0xD1, 0x21, /*'\x21d1'*/
|
||||
0x72, 0x72, 0x41, 0x72, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("rArr")*/ 0xD2, 0x21, /*'\x21d2'*/
|
||||
0x72, 0x72, 0x41, 0x64, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("dArr")*/ 0xD3, 0x21, /*'\x21d3'*/
|
||||
0x72, 0x72, 0x41, 0x68, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("hArr")*/ 0xD4, 0x21, /*'\x21d4'*/
|
||||
0x6C, 0x6C, 0x61, 0x72, 0x6F, 0x66, 0x00, 0x00, /*ToUInt64Key("forall")*/ 0x00, 0x22, /*'\x2200'*/
|
||||
0x74, 0x72, 0x61, 0x70, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("part")*/ 0x02, 0x22, /*'\x2202'*/
|
||||
0x74, 0x73, 0x69, 0x78, 0x65, 0x00, 0x00, 0x00, /*ToUInt64Key("exist")*/ 0x03, 0x22, /*'\x2203'*/
|
||||
0x79, 0x74, 0x70, 0x6D, 0x65, 0x00, 0x00, 0x00, /*ToUInt64Key("empty")*/ 0x05, 0x22, /*'\x2205'*/
|
||||
0x61, 0x6C, 0x62, 0x61, 0x6E, 0x00, 0x00, 0x00, /*ToUInt64Key("nabla")*/ 0x07, 0x22, /*'\x2207'*/
|
||||
0x6E, 0x69, 0x73, 0x69, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("isin")*/ 0x08, 0x22, /*'\x2208'*/
|
||||
0x6E, 0x69, 0x74, 0x6F, 0x6E, 0x00, 0x00, 0x00, /*ToUInt64Key("notin")*/ 0x09, 0x22, /*'\x2209'*/
|
||||
0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ni")*/ 0x0B, 0x22, /*'\x220b'*/
|
||||
0x64, 0x6F, 0x72, 0x70, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("prod")*/ 0x0F, 0x22, /*'\x220f'*/
|
||||
0x6D, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sum")*/ 0x11, 0x22, /*'\x2211'*/
|
||||
0x73, 0x75, 0x6E, 0x69, 0x6D, 0x00, 0x00, 0x00, /*ToUInt64Key("minus")*/ 0x12, 0x22, /*'\x2212'*/
|
||||
0x74, 0x73, 0x61, 0x77, 0x6F, 0x6C, 0x00, 0x00, /*ToUInt64Key("lowast")*/ 0x17, 0x22, /*'\x2217'*/
|
||||
0x63, 0x69, 0x64, 0x61, 0x72, 0x00, 0x00, 0x00, /*ToUInt64Key("radic")*/ 0x1A, 0x22, /*'\x221a'*/
|
||||
0x70, 0x6F, 0x72, 0x70, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("prop")*/ 0x1D, 0x22, /*'\x221d'*/
|
||||
0x6E, 0x69, 0x66, 0x6E, 0x69, 0x00, 0x00, 0x00, /*ToUInt64Key("infin")*/ 0x1E, 0x22, /*'\x221e'*/
|
||||
0x67, 0x6E, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ang")*/ 0x20, 0x22, /*'\x2220'*/
|
||||
0x64, 0x6E, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("and")*/ 0x27, 0x22, /*'\x2227'*/
|
||||
0x72, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("or")*/ 0x28, 0x22, /*'\x2228'*/
|
||||
0x70, 0x61, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("cap")*/ 0x29, 0x22, /*'\x2229'*/
|
||||
0x70, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("cup")*/ 0x2A, 0x22, /*'\x222a'*/
|
||||
0x74, 0x6E, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("int")*/ 0x2B, 0x22, /*'\x222b'*/
|
||||
0x34, 0x65, 0x72, 0x65, 0x68, 0x74, 0x00, 0x00, /*ToUInt64Key("there4")*/ 0x34, 0x22, /*'\x2234'*/
|
||||
0x6D, 0x69, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sim")*/ 0x3C, 0x22, /*'\x223c'*/
|
||||
0x67, 0x6E, 0x6F, 0x63, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("cong")*/ 0x45, 0x22, /*'\x2245'*/
|
||||
0x70, 0x6D, 0x79, 0x73, 0x61, 0x00, 0x00, 0x00, /*ToUInt64Key("asymp")*/ 0x48, 0x22, /*'\x2248'*/
|
||||
0x65, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ne")*/ 0x60, 0x22, /*'\x2260'*/
|
||||
0x76, 0x69, 0x75, 0x71, 0x65, 0x00, 0x00, 0x00, /*ToUInt64Key("equiv")*/ 0x61, 0x22, /*'\x2261'*/
|
||||
0x65, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("le")*/ 0x64, 0x22, /*'\x2264'*/
|
||||
0x65, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("ge")*/ 0x65, 0x22, /*'\x2265'*/
|
||||
0x62, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sub")*/ 0x82, 0x22, /*'\x2282'*/
|
||||
0x70, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sup")*/ 0x83, 0x22, /*'\x2283'*/
|
||||
0x62, 0x75, 0x73, 0x6E, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("nsub")*/ 0x84, 0x22, /*'\x2284'*/
|
||||
0x65, 0x62, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sube")*/ 0x86, 0x22, /*'\x2286'*/
|
||||
0x65, 0x70, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("supe")*/ 0x87, 0x22, /*'\x2287'*/
|
||||
0x73, 0x75, 0x6C, 0x70, 0x6F, 0x00, 0x00, 0x00, /*ToUInt64Key("oplus")*/ 0x95, 0x22, /*'\x2295'*/
|
||||
0x73, 0x65, 0x6D, 0x69, 0x74, 0x6F, 0x00, 0x00, /*ToUInt64Key("otimes")*/ 0x97, 0x22, /*'\x2297'*/
|
||||
0x70, 0x72, 0x65, 0x70, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("perp")*/ 0xA5, 0x22, /*'\x22a5'*/
|
||||
0x74, 0x6F, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("sdot")*/ 0xC5, 0x22, /*'\x22c5'*/
|
||||
0x6C, 0x69, 0x65, 0x63, 0x6C, 0x00, 0x00, 0x00, /*ToUInt64Key("lceil")*/ 0x08, 0x23, /*'\x2308'*/
|
||||
0x6C, 0x69, 0x65, 0x63, 0x72, 0x00, 0x00, 0x00, /*ToUInt64Key("rceil")*/ 0x09, 0x23, /*'\x2309'*/
|
||||
0x72, 0x6F, 0x6F, 0x6C, 0x66, 0x6C, 0x00, 0x00, /*ToUInt64Key("lfloor")*/ 0x0A, 0x23, /*'\x230a'*/
|
||||
0x72, 0x6F, 0x6F, 0x6C, 0x66, 0x72, 0x00, 0x00, /*ToUInt64Key("rfloor")*/ 0x0B, 0x23, /*'\x230b'*/
|
||||
0x67, 0x6E, 0x61, 0x6C, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("lang")*/ 0x29, 0x23, /*'\x2329'*/
|
||||
0x67, 0x6E, 0x61, 0x72, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("rang")*/ 0x2A, 0x23, /*'\x232a'*/
|
||||
0x7A, 0x6F, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, /*ToUInt64Key("loz")*/ 0xCA, 0x25, /*'\x25ca'*/
|
||||
0x73, 0x65, 0x64, 0x61, 0x70, 0x73, 0x00, 0x00, /*ToUInt64Key("spades")*/ 0x60, 0x26, /*'\x2660'*/
|
||||
0x73, 0x62, 0x75, 0x6C, 0x63, 0x00, 0x00, 0x00, /*ToUInt64Key("clubs")*/ 0x63, 0x26, /*'\x2663'*/
|
||||
0x73, 0x74, 0x72, 0x61, 0x65, 0x68, 0x00, 0x00, /*ToUInt64Key("hearts")*/ 0x65, 0x26, /*'\x2665'*/
|
||||
0x73, 0x6D, 0x61, 0x69, 0x64, 0x00, 0x00, 0x00, /*ToUInt64Key("diams")*/ 0x66, 0x26, /*'\x2666'*/
|
||||
];
|
||||
|
||||
var dictionary = new Dictionary<ulong, char>(tableData.Length / (sizeof(ulong) + sizeof(char)));
|
||||
while (tableData.Length > 0)
|
||||
{
|
||||
ulong key = BitConverter.ToUInt64(tableData, 0);
|
||||
char value = (char)BitConverter.ToUInt16(tableData, sizeof(ulong));
|
||||
dictionary[key] = value;
|
||||
tableData = tableData.Skip((sizeof(ulong) + sizeof(char))).ToArray();
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
// maps entity strings => unicode chars
|
||||
private static readonly Dictionary<ulong, char> s_lookupTable = InitializeLookupTable();
|
||||
|
||||
public static char Lookup(char[] entity)
|
||||
{
|
||||
// To avoid an allocation, keys of type "ulong" are used in the lookup table.
|
||||
// Since all entity strings comprise 8 characters or less and are ASCII-only, they "fit" into an ulong (8 bytes).
|
||||
if (entity.Length <= 8)
|
||||
{
|
||||
s_lookupTable.TryGetValue(ToUInt64Key(entity), out char result);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Currently, there are no entities that are longer than 8 characters.
|
||||
return (char)0;
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong ToUInt64Key(char[] entity)
|
||||
{
|
||||
// The ulong key is the reversed single-byte character representation of the actual entity string.
|
||||
ulong key = 0;
|
||||
for (int i = 0; i < entity.Length; i++)
|
||||
{
|
||||
if (entity[i] > 0xFF)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
key = (key << 8) | entity[i];
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
46
SabreTools.RedumpLib/SabreTools.RedumpLib.csproj
Normal file
46
SabreTools.RedumpLib/SabreTools.RedumpLib.csproj
Normal file
@@ -0,0 +1,46 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.4.1</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Code to interact with redump.org</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2020-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.RedumpLib</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>web client redump</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))">
|
||||
<PackageReference Include="MinValueTupleBridge" Version="0.2.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
<PackageReference Include="Net30.LinqBridge" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`))">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.4.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
226
SabreTools.RedumpLib/Validator.cs
Normal file
226
SabreTools.RedumpLib/Validator.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
namespace SabreTools.RedumpLib
|
||||
{
|
||||
public static class Validator
|
||||
{
|
||||
/// <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 || info?.SizeAndChecksums == null)
|
||||
return;
|
||||
|
||||
switch (info.CommonDiscInfo.Media)
|
||||
{
|
||||
case DiscType.BD25:
|
||||
case DiscType.BD33:
|
||||
case DiscType.BD50:
|
||||
case DiscType.BD66:
|
||||
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;
|
||||
else if (info.SizeAndChecksums.Layerbreak != default && info.SizeAndChecksums.PICIdentifier == SabreTools.Models.PIC.Constants.DiscTypeIdentifierROMUltra)
|
||||
info.CommonDiscInfo.Media = DiscType.BD66;
|
||||
else if (info.SizeAndChecksums.Layerbreak != default && info.SizeAndChecksums.Size > 50_050_629_632)
|
||||
info.CommonDiscInfo.Media = DiscType.BD66;
|
||||
else if (info.SizeAndChecksums.Layerbreak != default)
|
||||
info.CommonDiscInfo.Media = DiscType.BD50;
|
||||
else if (info.SizeAndChecksums.PICIdentifier == SabreTools.Models.PIC.Constants.DiscTypeIdentifierROMUltra)
|
||||
info.CommonDiscInfo.Media = DiscType.BD33;
|
||||
else if (info.SizeAndChecksums.Size > 25_025_314_816)
|
||||
info.CommonDiscInfo.Media = DiscType.BD33;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List the disc IDs associated with a given quicksearch query
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for making the connection</param>
|
||||
/// <param name="query">Query string to attempt to search for</param>
|
||||
/// <param name="filterForwardSlashes">True to filter forward slashes, false otherwise</param>
|
||||
/// <returns>All disc IDs for the given query, null on error</returns>
|
||||
public async static Task<List<int>?> ListSearchResults(RedumpClient rc, string? query, bool filterForwardSlashes = true)
|
||||
{
|
||||
// If there is an invalid query
|
||||
if (string.IsNullOrEmpty(query))
|
||||
return null;
|
||||
|
||||
var ids = new List<int>();
|
||||
|
||||
// Strip quotes
|
||||
query = query!.Trim('"', '\'');
|
||||
|
||||
// Special characters become dashes
|
||||
query = query.Replace(' ', '-');
|
||||
if (filterForwardSlashes)
|
||||
query = query.Replace('/', '-');
|
||||
query = query.Replace('\\', '/');
|
||||
|
||||
// Lowercase is defined per language
|
||||
query = query.ToLowerInvariant();
|
||||
|
||||
// Keep getting quicksearch pages until there are none left
|
||||
try
|
||||
{
|
||||
int pageNumber = 1;
|
||||
while (true)
|
||||
{
|
||||
List<int> pageIds = await rc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++));
|
||||
ids.AddRange(pageIds);
|
||||
if (pageIds.Count <= 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception occurred while trying to log in: {ex}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate a single track against Redump, if possible
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for making the connection</param>
|
||||
/// <param name="info">Existing SubmissionInfo object to fill</param>
|
||||
/// <param name="sha1">SHA-1 hash to check against</param>
|
||||
/// <returns>True if the track was found, false otherwise; List of found values, if possible</returns>
|
||||
public async static Task<(bool, List<int>?, string?)> ValidateSingleTrack(RedumpClient rc, SubmissionInfo info, string? sha1)
|
||||
{
|
||||
// Get all matching IDs for the track
|
||||
var newIds = await ListSearchResults(rc, sha1);
|
||||
|
||||
// If we got null back, there was an error
|
||||
if (newIds == null)
|
||||
return (false, null, "There was an unknown error retrieving information from Redump");
|
||||
|
||||
// If no IDs match, just return
|
||||
if (!newIds.Any())
|
||||
return (false, null, $"There were no matching IDs for track with SHA-1 of '{sha1}'");
|
||||
|
||||
// Join the list of found IDs to the existing list, if possible
|
||||
if (info.PartiallyMatchedIDs != null && info.PartiallyMatchedIDs.Any())
|
||||
info.PartiallyMatchedIDs.AddRange(newIds);
|
||||
else
|
||||
info.PartiallyMatchedIDs = newIds;
|
||||
|
||||
return (true, newIds, $"There were matching ID(s) found for track with SHA-1 of '{sha1}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate a universal hash against Redump, if possible
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient 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>
|
||||
public async static Task<(bool, List<int>?, string?)> ValidateUniversalHash(RedumpClient rc, SubmissionInfo info)
|
||||
{
|
||||
// If we don't have special fields
|
||||
if (info.CommonDiscInfo?.CommentsSpecialFields == null)
|
||||
return (false, null, "Universal hash was missing");
|
||||
|
||||
// If we don't have a universal hash
|
||||
string? universalHash = info.CommonDiscInfo.CommentsSpecialFields[SiteCode.UniversalHash];
|
||||
if (string.IsNullOrEmpty(universalHash))
|
||||
return (false, null, "Universal hash was missing");
|
||||
|
||||
// Format the universal hash for finding within the comments
|
||||
string universalHashQuery = $"{universalHash.Substring(0, universalHash.Length - 1)}/comments/only";
|
||||
|
||||
// Get all matching IDs for the hash
|
||||
var newIds = await ListSearchResults(rc, universalHashQuery, filterForwardSlashes: false);
|
||||
|
||||
// If we got null back, there was an error
|
||||
if (newIds == null)
|
||||
return (false, null, "There was an unknown error retrieving information from Redump");
|
||||
|
||||
// If no IDs match, just return
|
||||
if (!newIds.Any())
|
||||
return (false, null, $"There were no matching IDs for universal hash of '{universalHash}'");
|
||||
|
||||
// Join the list of found IDs to the existing list, if possible
|
||||
if (info.PartiallyMatchedIDs != null && info.PartiallyMatchedIDs.Any())
|
||||
info.PartiallyMatchedIDs.AddRange(newIds);
|
||||
else
|
||||
info.PartiallyMatchedIDs = newIds;
|
||||
|
||||
return (true, newIds, $"There were matching ID(s) found for universal hash of '{universalHash}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the current track count and remote track count match
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient 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>
|
||||
public async static Task<bool> ValidateTrackCount(RedumpClient rc, int id, int localCount)
|
||||
{
|
||||
// If we can't pull the remote data, we can't match
|
||||
string? discData = await rc.DownloadSingleSiteID(id);
|
||||
if (string.IsNullOrEmpty(discData))
|
||||
return false;
|
||||
|
||||
// Discs with only 1 track don't have a track count listed
|
||||
var match = Constants.TrackCountRegex.Match(discData);
|
||||
if (!match.Success && localCount == 1)
|
||||
return true;
|
||||
else if (!match.Success)
|
||||
return false;
|
||||
|
||||
// If the count isn't parseable, we're not taking chances
|
||||
if (!int.TryParse(match.Groups[1].Value, out int remoteCount))
|
||||
return false;
|
||||
|
||||
// Finally check to see if the counts match
|
||||
return localCount == remoteCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
SabreTools.RedumpLib/Web/CookieWebClient.cs
Normal file
40
SabreTools.RedumpLib/Web/CookieWebClient.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
#pragma warning disable SYSLIB0014 // 'WebClient.WebClient()' is obsolete
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
internal class CookieWebClient : WebClient
|
||||
{
|
||||
// https://stackoverflow.com/questions/1777221/using-cookiecontainer-with-webclient-class
|
||||
private readonly CookieContainer _container = new();
|
||||
|
||||
/// <summary>
|
||||
/// Get the last downloaded filename, if possible
|
||||
/// </summary>
|
||||
public string? GetLastFilename()
|
||||
{
|
||||
// If the response headers are null or empty
|
||||
if (ResponseHeaders == null || ResponseHeaders.Count == 0)
|
||||
return null;
|
||||
|
||||
// If we don't have the response header we care about
|
||||
string? headerValue = ResponseHeaders.Get("Content-Disposition");
|
||||
if (string.IsNullOrEmpty(headerValue))
|
||||
return null;
|
||||
|
||||
// Extract the filename from the value
|
||||
return headerValue.Substring(headerValue.IndexOf("filename=") + 9).Replace("\"", "");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override WebRequest GetWebRequest(Uri address)
|
||||
{
|
||||
WebRequest request = base.GetWebRequest(address);
|
||||
if (request is HttpWebRequest webRequest)
|
||||
webRequest.CookieContainer = _container;
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
SabreTools.RedumpLib/Web/DelayHelper.cs
Normal file
21
SabreTools.RedumpLib/Web/DelayHelper.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for delaying
|
||||
/// </summary>
|
||||
internal static class DelayHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Delay a random amount of time up to 5 seconds
|
||||
/// </summary>
|
||||
public static void DelayRandom()
|
||||
{
|
||||
var r = new Random();
|
||||
int delay = r.Next(0, 50);
|
||||
Thread.Sleep(delay * 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
SabreTools.RedumpLib/Web/Discs.cs
Normal file
55
SabreTools.RedumpLib/Web/Discs.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains logic for dealing with disc pages
|
||||
/// </summary>
|
||||
public static class Discs
|
||||
{
|
||||
/// <summary>
|
||||
/// Download the last modified disc pages, until first failure
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="force">Force continuation of download</param>
|
||||
public static async Task<bool> DownloadLastModified(RedumpClient rc, string? outDir, bool force)
|
||||
{
|
||||
// Keep getting last modified pages until there are none left
|
||||
int pageNumber = 1;
|
||||
while (true)
|
||||
{
|
||||
if (!await rc.CheckSingleSitePage(string.Format(Constants.LastModifiedUrl, pageNumber++), outDir, !force))
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download the specified range of site disc pages
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="minId">Starting ID for the range</param>
|
||||
/// <param name="maxId">Ending ID for the range (inclusive)</param>
|
||||
public static async Task<bool> DownloadSiteRange(RedumpClient rc, string? outDir, int minId = 0, int maxId = 0)
|
||||
{
|
||||
if (!rc.LoggedIn)
|
||||
{
|
||||
Console.WriteLine("Site download functionality is only available to Redump members");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int id = minId; id <= maxId; id++)
|
||||
{
|
||||
if (await rc.DownloadSingleSiteID(id, outDir, true))
|
||||
DelayHelper.DelayRandom(); // Intentional sleep here so we don't flood the server
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
SabreTools.RedumpLib/Web/Packs.cs
Normal file
73
SabreTools.RedumpLib/Web/Packs.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains logic for dealing with packs
|
||||
/// </summary>
|
||||
internal static class Packs
|
||||
{
|
||||
/// <summary>
|
||||
/// Download premade packs
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="useSubfolders">True to use named subfolders to store downloads, false to store directly in the output directory</param>
|
||||
public static async Task<bool> DownloadPacks(RedumpClient rc, string? outDir, bool useSubfolders)
|
||||
{
|
||||
#if NETFRAMEWORK || NETCOREAPP3_1
|
||||
var systems = Enum.GetValues(typeof(RedumpSystem)).OfType<RedumpSystem>().Select(s => new Nullable<RedumpSystem>(s));
|
||||
#else
|
||||
var systems = Enum.GetValues<RedumpSystem>().Select(s => new RedumpSystem?(s));
|
||||
#endif
|
||||
|
||||
await rc.DownloadPacks(Constants.PackCuesUrl, systems.Where(s => s.HasCues()).ToArray(), "CUEs", outDir, useSubfolders ? "cue" : null);
|
||||
await rc.DownloadPacks(Constants.PackDatfileUrl, systems.Where(s => s.HasDat()).ToArray(), "DATs", outDir, useSubfolders ? "dat" : null);
|
||||
await rc.DownloadPacks(Constants.PackDkeysUrl, systems.Where(s => s.HasDkeys()).ToArray(), "Decrypted KEYS", outDir, useSubfolders ? "dkey" : null);
|
||||
await rc.DownloadPacks(Constants.PackGdiUrl, systems.Where(s => s.HasGdi()).ToArray(), "GDIs", outDir, useSubfolders ? "gdi" : null);
|
||||
await rc.DownloadPacks(Constants.PackKeysUrl, systems.Where(s => s.HasKeys()).ToArray(), "KEYS", outDir, useSubfolders ? "keys" : null);
|
||||
await rc.DownloadPacks(Constants.PackLsdUrl, systems.Where(s => s.HasLsd()).ToArray(), "LSD", outDir, useSubfolders ? "lsd" : null);
|
||||
await rc.DownloadPacks(Constants.PackSbiUrl, systems.Where(s => s.HasSbi()).ToArray(), "SBIs", outDir, useSubfolders ? "sbi" : null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download premade packs for an individual system
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="system">RedumpSystem to get all possible packs for</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="useSubfolders">True to use named subfolders to store downloads, false to store directly in the output directory</param>
|
||||
public static async Task<bool> DownloadPacksForSystem(RedumpClient rc, RedumpSystem? system, string? outDir, bool useSubfolders)
|
||||
{
|
||||
var systemAsArray = new RedumpSystem?[] { system };
|
||||
|
||||
if (system.HasCues())
|
||||
await rc.DownloadPacks(Constants.PackCuesUrl, systemAsArray, "CUEs", outDir, useSubfolders ? "cue" : null);
|
||||
|
||||
if (system.HasDat())
|
||||
await rc.DownloadPacks(Constants.PackDatfileUrl, systemAsArray, "DATs", outDir, useSubfolders ? "dat" : null);
|
||||
|
||||
if (system.HasDkeys())
|
||||
await rc.DownloadPacks(Constants.PackDkeysUrl, systemAsArray, "Decrypted KEYS", outDir, useSubfolders ? "dkey" : null);
|
||||
|
||||
if (system.HasGdi())
|
||||
await rc.DownloadPacks(Constants.PackGdiUrl, systemAsArray, "GDIs", outDir, useSubfolders ? "gdi" : null);
|
||||
|
||||
if (system.HasKeys())
|
||||
await rc.DownloadPacks(Constants.PackKeysUrl, systemAsArray, "KEYS", outDir, useSubfolders ? "keys" : null);
|
||||
|
||||
if (system.HasLsd())
|
||||
await rc.DownloadPacks(Constants.PackLsdUrl, systemAsArray, "LSD", outDir, useSubfolders ? "lsd" : null);
|
||||
|
||||
if (system.HasSbi())
|
||||
await rc.DownloadPacks(Constants.PackSbiUrl, systemAsArray, "SBIs", outDir, useSubfolders ? "sbi" : null);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
#if NETCOREAPP
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
#endif
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
public class RedumpHttpClient : HttpClient
|
||||
public class RedumpClient
|
||||
{
|
||||
#region Properties
|
||||
|
||||
@@ -27,14 +29,44 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// </summary>
|
||||
public bool IsStaff { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum retry count for any operation
|
||||
/// </summary>
|
||||
public int RetryCount { get; private set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Internal client for interaction
|
||||
/// </summary>
|
||||
#if NETFRAMEWORK
|
||||
private CookieWebClient _internalClient;
|
||||
#else
|
||||
private HttpClient _internalClient;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public RedumpHttpClient()
|
||||
: base(new HttpClientHandler { UseCookies = true })
|
||||
public RedumpClient()
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
_internalClient = new CookieWebClient();
|
||||
#else
|
||||
_internalClient = new HttpClient(new HttpClientHandler { UseCookies = true });
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public RedumpClient(int retryCount) : this()
|
||||
{
|
||||
// Ensure there are a positive number of retries
|
||||
if (retryCount <= 0)
|
||||
retryCount = 3;
|
||||
|
||||
RetryCount = retryCount;
|
||||
}
|
||||
|
||||
#region Credentials
|
||||
@@ -45,13 +77,13 @@ namespace SabreTools.RedumpLib.Web
|
||||
public async static Task<(bool?, string?)> ValidateCredentials(string username, string password)
|
||||
{
|
||||
// If options are invalid or we're missing something key, just return
|
||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||
return (false, null);
|
||||
|
||||
// Try logging in with the supplied credentials otherwise
|
||||
using RedumpHttpClient httpClient = new();
|
||||
var redumpClient = new RedumpClient();
|
||||
|
||||
bool? loggedIn = await httpClient.Login(username, password);
|
||||
bool? loggedIn = await redumpClient.Login(username, password);
|
||||
if (loggedIn == true)
|
||||
return (true, "Redump username and password accepted!");
|
||||
else if (loggedIn == false)
|
||||
@@ -69,23 +101,27 @@ namespace SabreTools.RedumpLib.Web
|
||||
public async Task<bool?> Login(string username, string password)
|
||||
{
|
||||
// Credentials verification
|
||||
if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password))
|
||||
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
|
||||
{
|
||||
Console.WriteLine("Credentials entered, will attempt Redump login...");
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(username) && string.IsNullOrWhiteSpace(password))
|
||||
else if (!string.IsNullOrEmpty(username) && string.IsNullOrEmpty(password))
|
||||
{
|
||||
Console.WriteLine("Only a username was specified, will not attempt Redump login...");
|
||||
return false;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(username))
|
||||
else if (string.IsNullOrEmpty(username))
|
||||
{
|
||||
Console.WriteLine("No credentials entered, will not attempt Redump login...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// HTTP encode the password
|
||||
#if NET20 || NET35 || NET40
|
||||
password = Uri.EscapeUriString(password);
|
||||
#else
|
||||
password = WebUtility.UrlEncode(password);
|
||||
#endif
|
||||
|
||||
// Attempt to login up to 3 times
|
||||
for (int i = 0; i < 3; i++)
|
||||
@@ -93,25 +129,36 @@ namespace SabreTools.RedumpLib.Web
|
||||
try
|
||||
{
|
||||
// Get the current token from the login page
|
||||
var loginPage = await GetStringAsync(Constants.LoginUrl);
|
||||
string token = Constants.TokenRegex.Match(loginPage).Groups[1].Value;
|
||||
var loginPage = await DownloadStringWithRetries(Constants.LoginUrl);
|
||||
string token = Constants.TokenRegex.Match(loginPage ?? string.Empty).Groups[1].Value;
|
||||
|
||||
#if NETFRAMEWORK
|
||||
// Construct the login request
|
||||
_internalClient.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
|
||||
_internalClient.Encoding = Encoding.UTF8;
|
||||
|
||||
// Send the login request and get the result
|
||||
string? responseContent = _internalClient.UploadString(Constants.LoginUrl, $"form_sent=1&redirect_url=&csrf_token={token}&req_username={username}&req_password={password}&save_pass=0");
|
||||
#else
|
||||
// Construct the login request
|
||||
var postContent = new StringContent($"form_sent=1&redirect_url=&csrf_token={token}&req_username={username}&req_password={password}&save_pass=0", Encoding.UTF8);
|
||||
postContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
|
||||
|
||||
// Send the login request and get the result
|
||||
var response = await PostAsync(Constants.LoginUrl, postContent);
|
||||
var response = await _internalClient.PostAsync(Constants.LoginUrl, postContent);
|
||||
string? responseContent = null;
|
||||
if (response?.Content != null)
|
||||
responseContent = await response.Content.ReadAsStringAsync();
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrWhiteSpace(responseContent))
|
||||
// An empty response indicates an error
|
||||
if (string.IsNullOrEmpty(responseContent))
|
||||
{
|
||||
Console.WriteLine($"An error occurred while trying to log in on attempt {i}: No response");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Explcit confirmation the login was wrong
|
||||
if (responseContent.Contains("Incorrect username and/or password."))
|
||||
{
|
||||
Console.WriteLine("Invalid credentials entered, continuing without logging in...");
|
||||
@@ -149,10 +196,10 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <returns>List of IDs from the page, empty on error</returns>
|
||||
public async Task<List<int>> CheckSingleSitePage(string url)
|
||||
{
|
||||
List<int> ids = new();
|
||||
List<int> ids = [];
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
string? dumpsPage = await DownloadString(url, retries: 3);
|
||||
// Try to retrieve the data
|
||||
string? dumpsPage = await DownloadStringWithRetries(url);
|
||||
|
||||
// If we have no dumps left
|
||||
if (dumpsPage == null || dumpsPage.Contains("No discs found."))
|
||||
@@ -170,8 +217,11 @@ namespace SabreTools.RedumpLib.Web
|
||||
|
||||
// Otherwise, traverse each dump on the page
|
||||
var matches = Constants.DiscRegex.Matches(dumpsPage);
|
||||
foreach (Match match in matches)
|
||||
foreach (Match? match in matches.Cast<Match?>())
|
||||
{
|
||||
if (match == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
if (int.TryParse(match.Groups[1].Value, out int value))
|
||||
@@ -194,10 +244,10 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="failOnSingle">True to return on first error, false otherwise</param>
|
||||
/// <returns>True if the page could be downloaded, false otherwise</returns>
|
||||
public async Task<bool> CheckSingleSitePage(string url, string outDir, bool failOnSingle)
|
||||
public async Task<bool> CheckSingleSitePage(string url, string? outDir, bool failOnSingle)
|
||||
{
|
||||
// Try up to 3 times to retrieve the data
|
||||
string? dumpsPage = await DownloadString(url, retries: 3);
|
||||
// Try to retrieve the data
|
||||
string? dumpsPage = await DownloadStringWithRetries(url);
|
||||
|
||||
// If we have no dumps left
|
||||
if (dumpsPage == null || dumpsPage.Contains("No discs found."))
|
||||
@@ -219,8 +269,11 @@ namespace SabreTools.RedumpLib.Web
|
||||
|
||||
// Otherwise, traverse each dump on the page
|
||||
var matches = Constants.DiscRegex.Matches(dumpsPage);
|
||||
foreach (Match match in matches)
|
||||
foreach (Match? match in matches.Cast<Match?>())
|
||||
{
|
||||
if (match == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
if (int.TryParse(match.Groups[1].Value, out int value))
|
||||
@@ -247,10 +300,10 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <returns>List of IDs from the page, empty on error</returns>
|
||||
public async Task<List<int>> CheckSingleWIPPage(string url)
|
||||
{
|
||||
List<int> ids = new();
|
||||
List<int> ids = [];
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
string? dumpsPage = await DownloadString(url, retries: 3);
|
||||
// Try to retrieve the data
|
||||
string? dumpsPage = await DownloadStringWithRetries(url);
|
||||
|
||||
// If we have no dumps left
|
||||
if (dumpsPage == null || dumpsPage.Contains("No discs found."))
|
||||
@@ -258,8 +311,11 @@ namespace SabreTools.RedumpLib.Web
|
||||
|
||||
// Otherwise, traverse each dump on the page
|
||||
var matches = Constants.NewDiscRegex.Matches(dumpsPage);
|
||||
foreach (Match match in matches)
|
||||
foreach (Match? match in matches.Cast<Match?>())
|
||||
{
|
||||
if (match == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
if (int.TryParse(match.Groups[2].Value, out int value))
|
||||
@@ -282,10 +338,10 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="failOnSingle">True to return on first error, false otherwise</param>
|
||||
/// <returns>True if the page could be downloaded, false otherwise</returns>
|
||||
public async Task<bool> CheckSingleWIPPage(string url, string outDir, bool failOnSingle)
|
||||
public async Task<bool> CheckSingleWIPPage(string url, string? outDir, bool failOnSingle)
|
||||
{
|
||||
// Try up to 3 times to retrieve the data
|
||||
string? dumpsPage = await DownloadString(url, retries: 3);
|
||||
// Try to retrieve the data
|
||||
string? dumpsPage = await DownloadStringWithRetries(url);
|
||||
|
||||
// If we have no dumps left
|
||||
if (dumpsPage == null || dumpsPage.Contains("No discs found."))
|
||||
@@ -293,8 +349,11 @@ namespace SabreTools.RedumpLib.Web
|
||||
|
||||
// Otherwise, traverse each dump on the page
|
||||
var matches = Constants.NewDiscRegex.Matches(dumpsPage);
|
||||
foreach (Match match in matches)
|
||||
foreach (Match? match in matches.Cast<Match?>())
|
||||
{
|
||||
if (match == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
if (int.TryParse(match.Groups[2].Value, out int value))
|
||||
@@ -328,7 +387,13 @@ namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetByteArrayAsync(string.Format(url, system.ShortName()));
|
||||
#if NET40
|
||||
return await Task.Factory.StartNew(() => _internalClient.DownloadData(string.Format(url, system.ShortName())));
|
||||
#elif NETFRAMEWORK
|
||||
return await Task.Run(() => _internalClient.DownloadData(string.Format(url, system.ShortName())));
|
||||
#else
|
||||
return await _internalClient.GetByteArrayAsync(string.Format(url, system.ShortName()));
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -344,12 +409,12 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <param name="system">System to download packs for</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="subfolder">Named subfolder for the pack, used optionally</param>
|
||||
public async Task<bool> DownloadSinglePack(string url, RedumpSystem? system, string outDir, string subfolder)
|
||||
public async Task<bool> DownloadSinglePack(string url, RedumpSystem? system, string? outDir, string? subfolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If no output directory is defined, use the current directory instead
|
||||
if (string.IsNullOrWhiteSpace(outDir))
|
||||
if (string.IsNullOrEmpty(outDir))
|
||||
outDir = Environment.CurrentDirectory;
|
||||
|
||||
string tempfile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
|
||||
@@ -357,7 +422,7 @@ namespace SabreTools.RedumpLib.Web
|
||||
|
||||
// Make the call to get the pack
|
||||
string? remoteFileName = await DownloadFile(packUri, tempfile);
|
||||
MoveOrDelete(tempfile, remoteFileName, outDir, subfolder);
|
||||
MoveOrDelete(tempfile, remoteFileName, outDir!, subfolder);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -374,13 +439,13 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <returns>String containing the page contents if successful, null on error</returns>
|
||||
public async Task<string?> DownloadSingleSiteID(int id)
|
||||
{
|
||||
string paddedId = id.ToString().PadLeft(5, '0');
|
||||
string paddedId = id.ToString().PadLeft(6, '0');
|
||||
Console.WriteLine($"Processing ID: {paddedId}");
|
||||
try
|
||||
{
|
||||
// Try up to 3 times to retrieve the data
|
||||
// Try to retrieve the data
|
||||
string discPageUri = string.Format(Constants.DiscPageUrl, +id);
|
||||
string? discPage = await DownloadString(discPageUri, retries: 3);
|
||||
string? discPage = await DownloadStringWithRetries(discPageUri);
|
||||
|
||||
if (discPage == null || discPage.Contains($"Disc with ID \"{id}\" doesn't exist"))
|
||||
{
|
||||
@@ -405,20 +470,20 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="rename">True to rename deleted entries, false otherwise</param>
|
||||
/// <returns>True if all data was downloaded, false otherwise</returns>
|
||||
public async Task<bool> DownloadSingleSiteID(int id, string outDir, bool rename)
|
||||
public async Task<bool> DownloadSingleSiteID(int id, string? outDir, bool rename)
|
||||
{
|
||||
// If no output directory is defined, use the current directory instead
|
||||
if (string.IsNullOrWhiteSpace(outDir))
|
||||
if (string.IsNullOrEmpty(outDir))
|
||||
outDir = Environment.CurrentDirectory;
|
||||
|
||||
string paddedId = id.ToString().PadLeft(5, '0');
|
||||
string paddedId = id.ToString().PadLeft(6, '0');
|
||||
string paddedIdDir = Path.Combine(outDir, paddedId);
|
||||
Console.WriteLine($"Processing ID: {paddedId}");
|
||||
try
|
||||
{
|
||||
// Try up to 3 times to retrieve the data
|
||||
// Try to retrieve the data
|
||||
string discPageUri = string.Format(Constants.DiscPageUrl, +id);
|
||||
string? discPage = await DownloadString(discPageUri, retries: 3);
|
||||
string? discPage = await DownloadStringWithRetries(discPageUri);
|
||||
|
||||
if (discPage == null || discPage.Contains($"Disc with ID \"{id}\" doesn't exist"))
|
||||
{
|
||||
@@ -536,13 +601,13 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <returns>String containing the page contents if successful, null on error</returns>
|
||||
public async Task<string?> DownloadSingleWIPID(int id)
|
||||
{
|
||||
string paddedId = id.ToString().PadLeft(5, '0');
|
||||
string paddedId = id.ToString().PadLeft(6, '0');
|
||||
Console.WriteLine($"Processing ID: {paddedId}");
|
||||
try
|
||||
{
|
||||
// Try up to 3 times to retrieve the data
|
||||
// Try to retrieve the data
|
||||
string discPageUri = string.Format(Constants.WipDiscPageUrl, +id);
|
||||
string? discPage = await DownloadString(discPageUri, retries: 3);
|
||||
string? discPage = await DownloadStringWithRetries(discPageUri);
|
||||
|
||||
if (discPage == null || discPage.Contains($"WIP disc with ID \"{id}\" doesn't exist"))
|
||||
{
|
||||
@@ -567,20 +632,20 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="rename">True to rename deleted entries, false otherwise</param>
|
||||
/// <returns>True if all data was downloaded, false otherwise</returns>
|
||||
public async Task<bool> DownloadSingleWIPID(int id, string outDir, bool rename)
|
||||
public async Task<bool> DownloadSingleWIPID(int id, string? outDir, bool rename)
|
||||
{
|
||||
// If no output directory is defined, use the current directory instead
|
||||
if (string.IsNullOrWhiteSpace(outDir))
|
||||
if (string.IsNullOrEmpty(outDir))
|
||||
outDir = Environment.CurrentDirectory;
|
||||
|
||||
string paddedId = id.ToString().PadLeft(5, '0');
|
||||
string paddedId = id.ToString().PadLeft(6, '0');
|
||||
string paddedIdDir = Path.Combine(outDir, paddedId);
|
||||
Console.WriteLine($"Processing ID: {paddedId}");
|
||||
try
|
||||
{
|
||||
// Try up to 3 times to retrieve the data
|
||||
// Try to retrieve the data
|
||||
string discPageUri = string.Format(Constants.WipDiscPageUrl, +id);
|
||||
string? discPage = await DownloadString(discPageUri, retries: 3);
|
||||
string? discPage = await DownloadStringWithRetries(discPageUri);
|
||||
|
||||
if (discPage == null || discPage.Contains($"WIP disc with ID \"{id}\" doesn't exist"))
|
||||
{
|
||||
@@ -671,10 +736,10 @@ namespace SabreTools.RedumpLib.Web
|
||||
|
||||
// If the system is unknown, we can't do anything
|
||||
string? longName = system.LongName();
|
||||
if (string.IsNullOrWhiteSpace(longName))
|
||||
if (string.IsNullOrEmpty(longName))
|
||||
continue;
|
||||
|
||||
Console.Write($"\r{longName}{new string(' ', Console.BufferWidth - longName.Length - 1)}");
|
||||
Console.Write($"\r{longName}{new string(' ', Console.BufferWidth - longName!.Length - 1)}");
|
||||
byte[]? pack = await DownloadSinglePack(url, system);
|
||||
if (pack != null)
|
||||
packsDictionary.Add(system.Value, pack);
|
||||
@@ -694,7 +759,7 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <param name="title">Name of the pack that is downloading</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="subfolder">Named subfolder for the pack, used optionally</param>
|
||||
public async Task<bool> DownloadPacks(string url, RedumpSystem?[] systems, string title, string outDir, string subfolder)
|
||||
public async Task<bool> DownloadPacks(string url, RedumpSystem?[] systems, string title, string? outDir, string? subfolder)
|
||||
{
|
||||
Console.WriteLine($"Downloading {title}");
|
||||
foreach (var system in systems)
|
||||
@@ -709,10 +774,10 @@ namespace SabreTools.RedumpLib.Web
|
||||
|
||||
// If the system is unknown, we can't do anything
|
||||
string? longName = system.LongName();
|
||||
if (string.IsNullOrWhiteSpace(longName))
|
||||
if (string.IsNullOrEmpty(longName))
|
||||
continue;
|
||||
|
||||
Console.Write($"\r{longName}{new string(' ', Console.BufferWidth - longName.Length - 1)}");
|
||||
Console.Write($"\r{longName}{new string(' ', Console.BufferWidth - longName!.Length - 1)}");
|
||||
await DownloadSinglePack(url, system, outDir, subfolder);
|
||||
}
|
||||
|
||||
@@ -729,8 +794,15 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <returns>The remote filename from the URI, null on error</returns>
|
||||
private async Task<string?> DownloadFile(string uri, string fileName)
|
||||
{
|
||||
#if NET40
|
||||
await Task.Factory.StartNew(() => { _internalClient.DownloadFile(uri, fileName); return true; });
|
||||
return _internalClient.GetLastFilename();
|
||||
#elif NETFRAMEWORK
|
||||
await Task.Run(() => _internalClient.DownloadFile(uri, fileName));
|
||||
return _internalClient.GetLastFilename();
|
||||
#else
|
||||
// Make the call to get the file
|
||||
var response = await GetAsync(uri);
|
||||
var response = await _internalClient.GetAsync(uri);
|
||||
if (response?.Content?.Headers == null || !response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($"Could not download {uri}");
|
||||
@@ -745,27 +817,36 @@ namespace SabreTools.RedumpLib.Web
|
||||
}
|
||||
|
||||
return response.Content.Headers.ContentDisposition?.FileName?.Replace("\"", "");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download from a URI to a string
|
||||
/// </summary>
|
||||
/// <param name="uri">Remote URI to retrieve</param>
|
||||
/// <param name="retries">Number of times to retry on error</param>
|
||||
/// <returns>String from the URI, null on error</returns>
|
||||
private async Task<string?> DownloadString(string uri, int retries = 3)
|
||||
private async Task<string?> DownloadStringWithRetries(string uri)
|
||||
{
|
||||
// Only retry a positive number of times
|
||||
if (retries <= 0)
|
||||
if (RetryCount <= 0)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < retries; i++)
|
||||
for (int i = 0; i < RetryCount; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetStringAsync(uri);
|
||||
#if NET40
|
||||
return await Task.Factory.StartNew(() => _internalClient.DownloadString(uri));
|
||||
#elif NETFRAMEWORK
|
||||
return await Task.Run(() => _internalClient.DownloadString(uri));
|
||||
#else
|
||||
return await _internalClient.GetStringAsync(uri);
|
||||
#endif
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Sleep for 100ms if the last attempt failed
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -778,17 +859,17 @@ namespace SabreTools.RedumpLib.Web
|
||||
/// <param name="newfile">Path to new output file</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="subfolder">Optional subfolder to append to the path</param>
|
||||
private static void MoveOrDelete(string tempfile, string? newfile, string outDir, string subfolder)
|
||||
private static void MoveOrDelete(string tempfile, string? newfile, string outDir, string? subfolder)
|
||||
{
|
||||
// If we don't have a file to move to, just delete the temp file
|
||||
if (string.IsNullOrWhiteSpace(newfile))
|
||||
if (string.IsNullOrEmpty(newfile))
|
||||
{
|
||||
File.Delete(tempfile);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a subfolder, create it and update the newfile name
|
||||
if (!string.IsNullOrWhiteSpace(subfolder))
|
||||
if (!string.IsNullOrEmpty(subfolder))
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(outDir, subfolder)))
|
||||
Directory.CreateDirectory(Path.Combine(outDir, subfolder));
|
||||
@@ -805,6 +886,4 @@ namespace SabreTools.RedumpLib.Web
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
93
SabreTools.RedumpLib/Web/Search.cs
Normal file
93
SabreTools.RedumpLib/Web/Search.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains logic for dealing with searches
|
||||
/// </summary>
|
||||
internal static class Search
|
||||
{
|
||||
/// <summary>
|
||||
/// List the disc IDs associated with a given quicksearch query
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="query">Query string to attempt to search for</param>
|
||||
/// <returns>All disc IDs for the given query, null on error</returns>
|
||||
public static async Task<List<int>?> ListSearchResults(RedumpClient rc, string? query)
|
||||
{
|
||||
// If the query is invalid
|
||||
if (string.IsNullOrEmpty(query))
|
||||
return null;
|
||||
|
||||
List<int> ids = [];
|
||||
|
||||
// Strip quotes
|
||||
query = query!.Trim('"', '\'');
|
||||
|
||||
// Special characters become dashes
|
||||
query = query.Replace(' ', '-');
|
||||
query = query.Replace('/', '-');
|
||||
query = query.Replace('\\', '/');
|
||||
|
||||
// Lowercase is defined per language
|
||||
query = query.ToLowerInvariant();
|
||||
|
||||
// Keep getting quicksearch pages until there are none left
|
||||
try
|
||||
{
|
||||
int pageNumber = 1;
|
||||
while (true)
|
||||
{
|
||||
List<int> pageIds = await rc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++));
|
||||
ids.AddRange(pageIds);
|
||||
if (pageIds.Count <= 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception occurred while trying to log in: {ex}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download the disc pages associated with a given quicksearch query
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="query">Query string to attempt to search for</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
public static async Task<bool> DownloadSearchResults(RedumpClient rc, string? query, string? outDir)
|
||||
{
|
||||
// If the query is invalid
|
||||
if (string.IsNullOrEmpty(query))
|
||||
return false;
|
||||
|
||||
// Strip quotes
|
||||
query = query!.Trim('"', '\'');
|
||||
|
||||
// Special characters become dashes
|
||||
query = query.Replace(' ', '-');
|
||||
query = query.Replace('/', '-');
|
||||
query = query.Replace('\\', '/');
|
||||
|
||||
// Lowercase is defined per language
|
||||
query = query.ToLowerInvariant();
|
||||
|
||||
// Keep getting quicksearch pages until there are none left
|
||||
int pageNumber = 1;
|
||||
while (true)
|
||||
{
|
||||
if (!await rc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++), outDir, false))
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
SabreTools.RedumpLib/Web/User.cs
Normal file
100
SabreTools.RedumpLib/Web/User.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains logic for dealing with users
|
||||
/// </summary>
|
||||
public static class User
|
||||
{
|
||||
/// <summary>
|
||||
/// Download the disc pages associated with the given user
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="username">Username to check discs for</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
public static async Task<bool> DownloadUser(RedumpClient rc, string? username, string? outDir)
|
||||
{
|
||||
if (!rc.LoggedIn || string.IsNullOrEmpty(username))
|
||||
{
|
||||
Console.WriteLine("User download functionality is only available to Redump members");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep getting user pages until there are none left
|
||||
int pageNumber = 1;
|
||||
while (true)
|
||||
{
|
||||
if (!await rc.CheckSingleSitePage(string.Format(Constants.UserDumpsUrl, username, pageNumber++), outDir, false))
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download the last modified disc pages associated with the given user, until first failure
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="username">Username to check discs for</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
public static async Task<bool> DownloadUserLastModified(RedumpClient rc, string? username, string? outDir)
|
||||
{
|
||||
if (!rc.LoggedIn || string.IsNullOrEmpty(username))
|
||||
{
|
||||
Console.WriteLine("User download functionality is only available to Redump members");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep getting last modified user pages until there are none left
|
||||
int pageNumber = 1;
|
||||
while (true)
|
||||
{
|
||||
if (!await rc.CheckSingleSitePage(string.Format(Constants.UserDumpsLastModifiedUrl, username, pageNumber++), outDir, true))
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List the disc IDs associated with the given user
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="username">Username to check discs for</param>
|
||||
/// <returns>All disc IDs for the given user, null on error</returns>
|
||||
public static async Task<List<int>?> ListUser(RedumpClient rc, string? username)
|
||||
{
|
||||
List<int> ids = [];
|
||||
|
||||
if (!rc.LoggedIn || string.IsNullOrEmpty(username))
|
||||
{
|
||||
Console.WriteLine("User download functionality is only available to Redump members");
|
||||
return ids;
|
||||
}
|
||||
|
||||
// Keep getting user pages until there are none left
|
||||
try
|
||||
{
|
||||
int pageNumber = 1;
|
||||
while (true)
|
||||
{
|
||||
List<int> pageIds = await rc.CheckSingleSitePage(string.Format(Constants.UserDumpsUrl, username, pageNumber++));
|
||||
ids.AddRange(pageIds);
|
||||
if (pageIds.Count <= 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception occurred while trying to log in: {ex}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
SabreTools.RedumpLib/Web/WIP.cs
Normal file
46
SabreTools.RedumpLib/Web/WIP.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains logic for dealing with WIP queue
|
||||
/// </summary>
|
||||
public static class WIP
|
||||
{
|
||||
/// <summary>
|
||||
/// Download the last submitted WIP disc pages
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
public static async Task<bool> DownloadLastSubmitted(RedumpClient rc, string? outDir)
|
||||
{
|
||||
return await rc.CheckSingleWIPPage(Constants.WipDumpsUrl, outDir, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download the specified range of WIP disc pages
|
||||
/// </summary>
|
||||
/// <param name="rc">RedumpClient for connectivity</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="minId">Starting ID for the range</param>
|
||||
/// <param name="maxId">Ending ID for the range (inclusive)</param>
|
||||
public static async Task<bool> DownloadWIPRange(RedumpClient rc, string? outDir, int minId = 0, int maxId = 0)
|
||||
{
|
||||
if (!rc.LoggedIn || !rc.IsStaff)
|
||||
{
|
||||
Console.WriteLine("WIP download functionality is only available to Redump moderators");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int id = minId; id <= maxId; id++)
|
||||
{
|
||||
if (await rc.DownloadSingleWIPID(id, outDir, true))
|
||||
DelayHelper.DelayRandom(); // Intentional sleep here so we don't flood the server
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,837 +0,0 @@
|
||||
#if NET48
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace SabreTools.RedumpLib.Web
|
||||
{
|
||||
// https://stackoverflow.com/questions/1777221/using-cookiecontainer-with-webclient-class
|
||||
public class RedumpWebClient : WebClient
|
||||
{
|
||||
private readonly CookieContainer m_container = new CookieContainer();
|
||||
|
||||
/// <summary>
|
||||
/// Determines if user is logged into Redump
|
||||
/// </summary>
|
||||
public bool LoggedIn { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the user is a staff member
|
||||
/// </summary>
|
||||
public bool IsStaff { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Get the last downloaded filename, if possible
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetLastFilename()
|
||||
{
|
||||
// If the response headers are null or empty
|
||||
if (ResponseHeaders == null || ResponseHeaders.Count == 0)
|
||||
return null;
|
||||
|
||||
// If we don't have the response header we care about
|
||||
string headerValue = ResponseHeaders.Get("Content-Disposition");
|
||||
if (string.IsNullOrWhiteSpace(headerValue))
|
||||
return null;
|
||||
|
||||
// Extract the filename from the value
|
||||
#if NETSTANDARD2_1
|
||||
return headerValue[(headerValue.IndexOf("filename=") + 9)..].Replace("\"", "");
|
||||
#else
|
||||
return headerValue.Substring(headerValue.IndexOf("filename=") + 9).Replace("\"", "");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override WebRequest GetWebRequest(Uri address)
|
||||
{
|
||||
WebRequest request = base.GetWebRequest(address);
|
||||
if (request is HttpWebRequest webRequest)
|
||||
webRequest.CookieContainer = m_container;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate supplied credentials
|
||||
/// </summary>
|
||||
public static (bool?, string) ValidateCredentials(string username, string password)
|
||||
{
|
||||
// If options are invalid or we're missing something key, just return
|
||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||
return (false, null);
|
||||
|
||||
// Try logging in with the supplied credentials otherwise
|
||||
#if NETSTANDARD2_1
|
||||
using RedumpWebClient wc = new RedumpWebClient();
|
||||
#else
|
||||
using (RedumpWebClient wc = new RedumpWebClient())
|
||||
{
|
||||
#endif
|
||||
bool? loggedIn = wc.Login(username, password);
|
||||
if (loggedIn == true)
|
||||
return (true, "Redump username and password accepted!");
|
||||
else if (loggedIn == false)
|
||||
return (false, "Redump username and password denied!");
|
||||
else
|
||||
return (null, "An error occurred validating your credentials!");
|
||||
#if NET48
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Login to Redump, if possible
|
||||
/// </summary>
|
||||
/// <param name="username">Redump username</param>
|
||||
/// <param name="password">Redump password</param>
|
||||
/// <returns>True if the user could be logged in, false otherwise, null on error</returns>
|
||||
public bool? Login(string username, string password)
|
||||
{
|
||||
// Credentials verification
|
||||
if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
Console.WriteLine("Credentials entered, will attempt Redump login...");
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(username) && string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
Console.WriteLine("Only a username was specified, will not attempt Redump login...");
|
||||
return false;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
Console.WriteLine("No credentials entered, will not attempt Redump login...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// HTTP encode the password
|
||||
password = WebUtility.UrlEncode(password);
|
||||
|
||||
// Attempt to login up to 3 times
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the current token from the login page
|
||||
var loginPage = DownloadString(Constants.LoginUrl);
|
||||
string token = Constants.TokenRegex.Match(loginPage).Groups[1].Value;
|
||||
|
||||
// Construct the login request
|
||||
Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
|
||||
Encoding = Encoding.UTF8;
|
||||
var response = UploadString(Constants.LoginUrl, $"form_sent=1&redirect_url=&csrf_token={token}&req_username={username}&req_password={password}&save_pass=0");
|
||||
|
||||
if (response.Contains("Incorrect username and/or password."))
|
||||
{
|
||||
Console.WriteLine("Invalid credentials entered, continuing without logging in...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The user was able to be logged in
|
||||
Console.WriteLine("Credentials accepted! Logged into Redump...");
|
||||
LoggedIn = true;
|
||||
|
||||
// If the user is a moderator or staff, set accordingly
|
||||
if (response.Contains("http://forum.redump.org/forum/9/staff/"))
|
||||
IsStaff = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception occurred while trying to log in on attempt {i}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Could not login to Redump in 3 attempts, continuing without logging in...");
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Single Page Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Process a Redump site page as a list of possible IDs or disc page
|
||||
/// </summary>
|
||||
/// <param name="url">Base URL to download using</param>
|
||||
/// <returns>List of IDs from the page, empty on error</returns>
|
||||
public List<int> CheckSingleSitePage(string url)
|
||||
{
|
||||
List<int> ids = new List<int>();
|
||||
string dumpsPage = string.Empty;
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
dumpsPage = DownloadString(url);
|
||||
break;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// If we have no dumps left
|
||||
if (dumpsPage.Contains("No discs found."))
|
||||
return ids;
|
||||
|
||||
// If we have a single disc page already
|
||||
if (dumpsPage.Contains("<b>Download:</b>"))
|
||||
{
|
||||
var value = Regex.Match(dumpsPage, @"/disc/(\d+)/sfv/").Groups[1].Value;
|
||||
if (int.TryParse(value, out int id))
|
||||
ids.Add(id);
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
// Otherwise, traverse each dump on the page
|
||||
var matches = Constants.DiscRegex.Matches(dumpsPage);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (int.TryParse(match.Groups[1].Value, out int value))
|
||||
ids.Add(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a Redump site page as a list of possible IDs or disc page
|
||||
/// </summary>
|
||||
/// <param name="url">Base URL to download using</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="failOnSingle">True to return on first error, false otherwise</param>
|
||||
/// <returns>True if the page could be downloaded, false otherwise</returns>
|
||||
public bool CheckSingleSitePage(string url, string outDir, bool failOnSingle)
|
||||
{
|
||||
string dumpsPage = string.Empty;
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
dumpsPage = DownloadString(url);
|
||||
break;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// If we have no dumps left
|
||||
if (dumpsPage.Contains("No discs found."))
|
||||
return false;
|
||||
|
||||
// If we have a single disc page already
|
||||
if (dumpsPage.Contains("<b>Download:</b>"))
|
||||
{
|
||||
var value = Regex.Match(dumpsPage, @"/disc/(\d+)/sfv/").Groups[1].Value;
|
||||
if (int.TryParse(value, out int id))
|
||||
{
|
||||
bool downloaded = DownloadSingleSiteID(id, outDir, false);
|
||||
if (!downloaded && failOnSingle)
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, traverse each dump on the page
|
||||
var matches = Constants.DiscRegex.Matches(dumpsPage);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (int.TryParse(match.Groups[1].Value, out int value))
|
||||
{
|
||||
bool downloaded = DownloadSingleSiteID(value, outDir, false);
|
||||
if (!downloaded && failOnSingle)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a Redump WIP page as a list of possible IDs or disc page
|
||||
/// </summary>
|
||||
/// <param name="wc">RedumpWebClient to access the packs</param>
|
||||
/// <returns>List of IDs from the page, empty on error</returns>
|
||||
public List<int> CheckSingleWIPPage(string url)
|
||||
{
|
||||
List<int> ids = new List<int>();
|
||||
string dumpsPage = string.Empty;
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
dumpsPage = DownloadString(url);
|
||||
break;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// If we have no dumps left
|
||||
if (dumpsPage.Contains("No discs found."))
|
||||
return ids;
|
||||
|
||||
// Otherwise, traverse each dump on the page
|
||||
var matches = Constants.NewDiscRegex.Matches(dumpsPage);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (int.TryParse(match.Groups[2].Value, out int value))
|
||||
ids.Add(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a Redump WIP page as a list of possible IDs or disc page
|
||||
/// </summary>
|
||||
/// <param name="wc">RedumpWebClient to access the packs</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="failOnSingle">True to return on first error, false otherwise</param>
|
||||
/// <returns>True if the page could be downloaded, false otherwise</returns>
|
||||
public bool CheckSingleWIPPage(string url, string outDir, bool failOnSingle)
|
||||
{
|
||||
string dumpsPage = string.Empty;
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
dumpsPage = DownloadString(url);
|
||||
break;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// If we have no dumps left
|
||||
if (dumpsPage.Contains("No discs found."))
|
||||
return false;
|
||||
|
||||
// Otherwise, traverse each dump on the page
|
||||
var matches = Constants.NewDiscRegex.Matches(dumpsPage);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (int.TryParse(match.Groups[2].Value, out int value))
|
||||
{
|
||||
bool downloaded = DownloadSingleWIPID(value, outDir, false);
|
||||
if (!downloaded && failOnSingle)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Download Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Download a single pack
|
||||
/// </summary>
|
||||
/// <param name="url">Base URL to download using</param>
|
||||
/// <param name="system">System to download packs for</param>
|
||||
/// <returns>Byte array containing the downloaded pack, null on error</returns>
|
||||
public byte[] DownloadSinglePack(string url, RedumpSystem? system)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadData(string.Format(url, system.ShortName()));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a single pack
|
||||
/// </summary>
|
||||
/// <param name="url">Base URL to download using</param>
|
||||
/// <param name="system">System to download packs for</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="subfolder">Named subfolder for the pack, used optionally</param>
|
||||
public void DownloadSinglePack(string url, RedumpSystem? system, string outDir, string subfolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If no output directory is defined, use the current directory instead
|
||||
if (string.IsNullOrWhiteSpace(outDir))
|
||||
outDir = Environment.CurrentDirectory;
|
||||
|
||||
string tempfile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
|
||||
DownloadFile(string.Format(url, system.ShortName()), tempfile);
|
||||
MoveOrDelete(tempfile, GetLastFilename(), outDir, subfolder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download an individual site ID data, if possible
|
||||
/// </summary>
|
||||
/// <param name="id">Redump disc ID to retrieve</param>
|
||||
/// <returns>String containing the page contents if successful, null on error</returns>
|
||||
public string DownloadSingleSiteID(int id)
|
||||
{
|
||||
string paddedId = id.ToString().PadLeft(5, '0');
|
||||
Console.WriteLine($"Processing ID: {paddedId}");
|
||||
try
|
||||
{
|
||||
string discPage = string.Empty;
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
discPage = DownloadString(string.Format(Constants.DiscPageUrl, +id));
|
||||
break;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (discPage.Contains($"Disc with ID \"{id}\" doesn't exist"))
|
||||
{
|
||||
Console.WriteLine($"ID {paddedId} could not be found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine($"ID {paddedId} has been successfully downloaded");
|
||||
return discPage;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download an individual site ID data, if possible
|
||||
/// </summary>
|
||||
/// <param name="id">Redump disc ID to retrieve</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="rename">True to rename deleted entries, false otherwise</param>
|
||||
/// <returns>True if all data was downloaded, false otherwise</returns>
|
||||
public bool DownloadSingleSiteID(int id, string outDir, bool rename)
|
||||
{
|
||||
// If no output directory is defined, use the current directory instead
|
||||
if (string.IsNullOrWhiteSpace(outDir))
|
||||
outDir = Environment.CurrentDirectory;
|
||||
|
||||
string paddedId = id.ToString().PadLeft(5, '0');
|
||||
string paddedIdDir = Path.Combine(outDir, paddedId);
|
||||
Console.WriteLine($"Processing ID: {paddedId}");
|
||||
try
|
||||
{
|
||||
string discPage = string.Empty;
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
discPage = DownloadString(string.Format(Constants.DiscPageUrl, +id));
|
||||
break;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (discPage.Contains($"Disc with ID \"{id}\" doesn't exist"))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (rename)
|
||||
{
|
||||
if (Directory.Exists(paddedIdDir) && rename)
|
||||
Directory.Move(paddedIdDir, paddedIdDir + "-deleted");
|
||||
else
|
||||
Directory.CreateDirectory(paddedIdDir + "-deleted");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
Console.WriteLine($"ID {paddedId} could not be found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the page has been updated since the last time it was downloaded, if possible
|
||||
if (File.Exists(Path.Combine(paddedIdDir, "disc.html")))
|
||||
{
|
||||
// Read in the cached file
|
||||
var oldDiscPage = File.ReadAllText(Path.Combine(paddedIdDir, "disc.html"));
|
||||
|
||||
// Check for the last modified date in both pages
|
||||
var oldResult = Constants.LastModifiedRegex.Match(oldDiscPage);
|
||||
var newResult = Constants.LastModifiedRegex.Match(discPage);
|
||||
|
||||
// If both pages contain the same modified date, skip it
|
||||
if (oldResult.Success && newResult.Success && oldResult.Groups[1].Value == newResult.Groups[1].Value)
|
||||
{
|
||||
Console.WriteLine($"ID {paddedId} has not been changed since last download");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If neither page contains a modified date, skip it
|
||||
else if (!oldResult.Success && !newResult.Success)
|
||||
{
|
||||
Console.WriteLine($"ID {paddedId} has not been changed since last download");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create ID subdirectory
|
||||
Directory.CreateDirectory(paddedIdDir);
|
||||
|
||||
// View Edit History
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/changes/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.ChangesExt, Path.Combine(paddedIdDir, "changes.html"));
|
||||
|
||||
// CUE
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/cue/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.CueExt, Path.Combine(paddedIdDir, paddedId + ".cue"));
|
||||
|
||||
// Edit disc
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/edit/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.EditExt, Path.Combine(paddedIdDir, "edit.html"));
|
||||
|
||||
// GDI
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/gdi/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.GdiExt, Path.Combine(paddedIdDir, paddedId + ".gdi"));
|
||||
|
||||
// KEYS
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/key/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.KeyExt, Path.Combine(paddedIdDir, paddedId + ".key"));
|
||||
|
||||
// LSD
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/lsd/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.LsdExt, Path.Combine(paddedIdDir, paddedId + ".lsd"));
|
||||
|
||||
// MD5
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/md5/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.Md5Ext, Path.Combine(paddedIdDir, paddedId + ".md5"));
|
||||
|
||||
// Review WIP entry
|
||||
if (Constants.NewDiscRegex.IsMatch(discPage))
|
||||
{
|
||||
var match = Constants.NewDiscRegex.Match(discPage);
|
||||
DownloadFile(string.Format(Constants.WipDiscPageUrl, match.Groups[2].Value), Path.Combine(paddedIdDir, "newdisc.html"));
|
||||
}
|
||||
|
||||
// SBI
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/sbi/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.SbiExt, Path.Combine(paddedIdDir, paddedId + ".sbi"));
|
||||
|
||||
// SFV
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/sfv/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.SfvExt, Path.Combine(paddedIdDir, paddedId + ".sfv"));
|
||||
|
||||
// SHA1
|
||||
if (discPage.Contains($"<a href=\"/disc/{id}/sha1/\""))
|
||||
DownloadFile(string.Format(Constants.DiscPageUrl, +id) + Constants.Sha1Ext, Path.Combine(paddedIdDir, paddedId + ".sha1"));
|
||||
|
||||
// HTML (Last in case of errors)
|
||||
using (var discStreamWriter = File.CreateText(Path.Combine(paddedIdDir, "disc.html")))
|
||||
{
|
||||
discStreamWriter.Write(discPage);
|
||||
}
|
||||
|
||||
Console.WriteLine($"ID {paddedId} has been successfully downloaded");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download an individual WIP ID data, if possible
|
||||
/// </summary>
|
||||
/// <param name="id">Redump WIP disc ID to retrieve</param>
|
||||
/// <returns>String containing the page contents if successful, null on error</returns>
|
||||
public string DownloadSingleWIPID(int id)
|
||||
{
|
||||
string paddedId = id.ToString().PadLeft(5, '0');
|
||||
Console.WriteLine($"Processing ID: {paddedId}");
|
||||
try
|
||||
{
|
||||
string discPage = string.Empty;
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
discPage = DownloadString(string.Format(Constants.WipDiscPageUrl, +id));
|
||||
break;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (discPage.Contains($"WIP disc with ID \"{id}\" doesn't exist"))
|
||||
{
|
||||
Console.WriteLine($"ID {paddedId} could not be found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine($"ID {paddedId} has been successfully downloaded");
|
||||
return discPage;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download an individual WIP ID data, if possible
|
||||
/// </summary>
|
||||
/// <param name="id">Redump WIP disc ID to retrieve</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="rename">True to rename deleted entries, false otherwise</param>
|
||||
/// <returns>True if all data was downloaded, false otherwise</returns>
|
||||
public bool DownloadSingleWIPID(int id, string outDir, bool rename)
|
||||
{
|
||||
// If no output directory is defined, use the current directory instead
|
||||
if (string.IsNullOrWhiteSpace(outDir))
|
||||
outDir = Environment.CurrentDirectory;
|
||||
|
||||
string paddedId = id.ToString().PadLeft(5, '0');
|
||||
string paddedIdDir = Path.Combine(outDir, paddedId);
|
||||
Console.WriteLine($"Processing ID: {paddedId}");
|
||||
try
|
||||
{
|
||||
string discPage = string.Empty;
|
||||
|
||||
// Try up to 3 times to retrieve the data
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
discPage = DownloadString(string.Format(Constants.WipDiscPageUrl, +id));
|
||||
break;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (discPage.Contains($"WIP disc with ID \"{id}\" doesn't exist"))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (rename)
|
||||
{
|
||||
if (Directory.Exists(paddedIdDir) && rename)
|
||||
Directory.Move(paddedIdDir, paddedIdDir + "-deleted");
|
||||
else
|
||||
Directory.CreateDirectory(paddedIdDir + "-deleted");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
Console.WriteLine($"ID {paddedId} could not be found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the page has been updated since the last time it was downloaded, if possible
|
||||
if (File.Exists(Path.Combine(paddedIdDir, "disc.html")))
|
||||
{
|
||||
// Read in the cached file
|
||||
var oldDiscPage = File.ReadAllText(Path.Combine(paddedIdDir, "disc.html"));
|
||||
|
||||
// Check for the full match ID in both pages
|
||||
var oldResult = Constants.FullMatchRegex.Match(oldDiscPage);
|
||||
var newResult = Constants.FullMatchRegex.Match(discPage);
|
||||
|
||||
// If both pages contain the same ID, skip it
|
||||
if (oldResult.Success && newResult.Success && oldResult.Groups[1].Value == newResult.Groups[1].Value)
|
||||
{
|
||||
Console.WriteLine($"ID {paddedId} has not been changed since last download");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If neither page contains an ID, skip it
|
||||
else if (!oldResult.Success && !newResult.Success)
|
||||
{
|
||||
Console.WriteLine($"ID {paddedId} has not been changed since last download");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create ID subdirectory
|
||||
Directory.CreateDirectory(paddedIdDir);
|
||||
|
||||
// HTML
|
||||
using (var discStreamWriter = File.CreateText(Path.Combine(paddedIdDir, "disc.html")))
|
||||
{
|
||||
discStreamWriter.Write(discPage);
|
||||
}
|
||||
|
||||
Console.WriteLine($"ID {paddedId} has been successfully downloaded");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An exception has occurred: {ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Download a set of packs
|
||||
/// </summary>
|
||||
/// <param name="url">Base URL to download using</param>
|
||||
/// <param name="system">Systems to download packs for</param>
|
||||
/// <param name="title">Name of the pack that is downloading</param>
|
||||
public Dictionary<RedumpSystem, byte[]> DownloadPacks(string url, RedumpSystem?[] systems, string title)
|
||||
{
|
||||
var packsDictionary = new Dictionary<RedumpSystem, byte[]>();
|
||||
|
||||
Console.WriteLine($"Downloading {title}");
|
||||
foreach (var system in systems)
|
||||
{
|
||||
// If the system is invalid, we can't do anything
|
||||
if (system == null || !system.IsAvailable())
|
||||
continue;
|
||||
|
||||
// If we didn't have credentials
|
||||
if (!LoggedIn && system.IsBanned())
|
||||
continue;
|
||||
|
||||
// If the system is unknown, we can't do anything
|
||||
string longName = system.LongName();
|
||||
if (string.IsNullOrWhiteSpace(longName))
|
||||
continue;
|
||||
|
||||
Console.Write($"\r{longName}{new string(' ', Console.BufferWidth - longName.Length - 1)}");
|
||||
byte[] pack = DownloadSinglePack(url, system);
|
||||
if (pack != null)
|
||||
packsDictionary.Add(system.Value, pack);
|
||||
}
|
||||
|
||||
Console.Write($"\rComplete!{new string(' ', Console.BufferWidth - 10)}");
|
||||
Console.WriteLine();
|
||||
|
||||
return packsDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a set of packs
|
||||
/// </summary>
|
||||
/// <param name="url">Base URL to download using</param>
|
||||
/// <param name="system">Systems to download packs for</param>
|
||||
/// <param name="title">Name of the pack that is downloading</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="subfolder">Named subfolder for the pack, used optionally</param>
|
||||
public void DownloadPacks(string url, RedumpSystem?[] systems, string title, string outDir, string subfolder)
|
||||
{
|
||||
Console.WriteLine($"Downloading {title}");
|
||||
foreach (var system in systems)
|
||||
{
|
||||
// If the system is invalid, we can't do anything
|
||||
if (system == null || !system.IsAvailable())
|
||||
continue;
|
||||
|
||||
// If we didn't have credentials
|
||||
if (!LoggedIn && system.IsBanned())
|
||||
continue;
|
||||
|
||||
// If the system is unknown, we can't do anything
|
||||
string longName = system.LongName();
|
||||
if (string.IsNullOrWhiteSpace(longName))
|
||||
continue;
|
||||
|
||||
Console.Write($"\r{longName}{new string(' ', Console.BufferWidth - longName.Length - 1)}");
|
||||
DownloadSinglePack(url, system, outDir, subfolder);
|
||||
}
|
||||
|
||||
Console.Write($"\rComplete!{new string(' ', Console.BufferWidth - 10)}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a tempfile to a new name unless it aleady exists, in which case, delete the tempfile
|
||||
/// </summary>
|
||||
/// <param name="tempfile">Path to existing temporary file</param>
|
||||
/// <param name="newfile">Path to new output file</param>
|
||||
/// <param name="outDir">Output directory to save data to</param>
|
||||
/// <param name="subfolder">Optional subfolder to append to the path</param>
|
||||
private static void MoveOrDelete(string tempfile, string newfile, string outDir, string subfolder)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(newfile))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(subfolder))
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(outDir, subfolder)))
|
||||
Directory.CreateDirectory(Path.Combine(outDir, subfolder));
|
||||
|
||||
newfile = Path.Combine(subfolder, newfile);
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(outDir, newfile)))
|
||||
File.Delete(tempfile);
|
||||
else
|
||||
File.Move(tempfile, Path.Combine(outDir, newfile));
|
||||
}
|
||||
else
|
||||
File.Delete(tempfile);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user