mirror of
https://github.com/SabreTools/MPF.git
synced 2026-07-02 17:24:48 +00:00
Migrate to Nuget package for PIC
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
- Remove dd for Windows
|
||||
- Migrate to Nuget package for XMID
|
||||
- Migrate to Nuget package for cuesheets
|
||||
- Migrate to Nuget package for PIC
|
||||
|
||||
### 2.6.3 (2023-08-15)
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
namespace MPF.Core.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Disc Information and Emergency Brake data shall be read from the PIC zone. DI units that
|
||||
/// contain physical information shall be returned.Emergency Brake data shall be returned.The
|
||||
/// information shall be collected from the layer specified in the Layer field of the CDB. If any data
|
||||
/// can be returned, 4 100 bytes shall be returned.
|
||||
/// </summary>
|
||||
/// <see href="https://www.t10.org/ftp/t10/document.05/05-206r0.pdf"/>
|
||||
/// <see href="https://github.com/aaru-dps/Aaru.Decoders/blob/devel/Bluray/DI.cs"/>
|
||||
public class PICDiscInformation
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// 2048 bytes for BD-ROM, 3584 bytes for BD-R/RE
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian format</remarks>
|
||||
public ushort DataStructureLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be 0x00
|
||||
/// </summary>
|
||||
public byte Reserved0 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be 0x00
|
||||
/// </summary>
|
||||
public byte Reserved1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disc information and emergency brake units
|
||||
/// </summary>
|
||||
public PICDiscInformationUnit[] Units { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
namespace MPF.Core.Data
|
||||
{
|
||||
/// <see href="https://www.t10.org/ftp/t10/document.05/05-206r0.pdf"/>
|
||||
/// <see href="https://github.com/aaru-dps/Aaru.Decoders/blob/devel/Bluray/DI.cs"/>
|
||||
public class PICDiscInformationUnit
|
||||
{
|
||||
#region Fields
|
||||
|
||||
#region Header
|
||||
|
||||
/// <summary>
|
||||
/// Disc Information Identifier "DI"
|
||||
/// Emergency Brake Identifier "EB"
|
||||
/// </summary>
|
||||
public string DiscInformationIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disc Information Format
|
||||
/// </summary>
|
||||
public byte DiscInformationFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of DI units in each DI block
|
||||
/// </summary>
|
||||
public byte NumberOfUnitsInBlock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be 0x00
|
||||
/// </summary>
|
||||
public byte Reserved0 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DI unit Sequence Number
|
||||
/// </summary>
|
||||
public byte SequenceNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes in use in this DI unit
|
||||
/// </summary>
|
||||
public byte BytesInUse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be 0x00
|
||||
/// </summary>
|
||||
public byte Reserved1 { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Write models for the dependent contents, if possible
|
||||
#region Body
|
||||
|
||||
/// <summary>
|
||||
/// Disc Type Identifier
|
||||
/// = "BDO" for BD-ROM
|
||||
/// = "BDU" for BD-ROM Ultra
|
||||
/// = "BDW" for BD-RE
|
||||
/// = "BDR" for BD-R
|
||||
/// </summary>
|
||||
public string DiscTypeIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disc Size/Class/Version
|
||||
/// </summary>
|
||||
public byte DiscSizeClassVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DI Unit Format dependent contents
|
||||
/// </summary>
|
||||
/// <remarks>52 bytes for BD-ROM, 100 bytes for BD-R/RE</remarks>
|
||||
public byte[] FormatDependentContents { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Trailer (BD-R/RE only)
|
||||
|
||||
/// <summary>
|
||||
/// Disc Manufacturer ID
|
||||
/// </summary>
|
||||
/// <remarks>6 bytes</remarks>
|
||||
public byte[] DiscManufacturerID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Media Type ID
|
||||
/// </summary>
|
||||
/// <remarks>3 bytes</remarks>
|
||||
public byte[] MediaTypeID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time Stamp
|
||||
/// </summary>
|
||||
public ushort TimeStamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Product Revision Number
|
||||
/// </summary>
|
||||
public byte ProductRevisionNumber { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
public const string DiscTypeIdentifierROM = "BDO";
|
||||
|
||||
public const string DiscTypeIdentifierROMUltra = "BDU";
|
||||
|
||||
public const string DiscTypeIdentifierReWritable = "BDW";
|
||||
|
||||
public const string DiscTypeIdentifierRecordable = "BDR";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
@@ -14,6 +13,7 @@ using MPF.Core.Data;
|
||||
using MPF.Core.Utilities;
|
||||
using MPF.Modules;
|
||||
using Newtonsoft.Json;
|
||||
using SabreTools.Models.PIC;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
@@ -920,13 +920,13 @@ namespace MPF.Library
|
||||
return $"{mediaType.LongName()}-128";
|
||||
else if (layerbreak2 != default)
|
||||
return $"{mediaType.LongName()}-100";
|
||||
else if (layerbreak != default && picIdentifier == PICDiscInformationUnit.DiscTypeIdentifierROMUltra)
|
||||
else if (layerbreak != default && picIdentifier == SabreTools.Models.PIC.Constants.DiscTypeIdentifierROMUltra)
|
||||
return $"{mediaType.LongName()}-66";
|
||||
else if (layerbreak != default && size > 53_687_063_712)
|
||||
return $"{mediaType.LongName()}-66";
|
||||
else if (layerbreak != default)
|
||||
return $"{mediaType.LongName()}-50";
|
||||
else if (picIdentifier == PICDiscInformationUnit.DiscTypeIdentifierROMUltra)
|
||||
else if (picIdentifier == SabreTools.Models.PIC.Constants.DiscTypeIdentifierROMUltra)
|
||||
return $"{mediaType.LongName()}-33";
|
||||
else if (size > 26_843_531_856)
|
||||
return $"{mediaType.LongName()}-33";
|
||||
@@ -1634,13 +1634,13 @@ namespace MPF.Library
|
||||
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 == PICDiscInformationUnit.DiscTypeIdentifierROMUltra)
|
||||
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 == PICDiscInformationUnit.DiscTypeIdentifierROMUltra)
|
||||
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;
|
||||
@@ -1912,7 +1912,7 @@ namespace MPF.Library
|
||||
#endif
|
||||
|
||||
// Title, Disc Number/Letter, Disc Title
|
||||
var match = Constants.TitleRegex.Match(discData);
|
||||
var match = SabreTools.RedumpLib.Data.Constants.TitleRegex.Match(discData);
|
||||
if (match.Success)
|
||||
{
|
||||
string title = WebUtility.HtmlDecode(match.Groups[1].Value);
|
||||
@@ -1922,7 +1922,7 @@ namespace MPF.Library
|
||||
if (firstParenLocation >= 0)
|
||||
{
|
||||
info.CommonDiscInfo.Title = title.Substring(0, firstParenLocation);
|
||||
var subMatches = Constants.DiscNumberLetterRegex.Matches(title);
|
||||
var subMatches = SabreTools.RedumpLib.Data.Constants.DiscNumberLetterRegex.Matches(title);
|
||||
foreach (Match subMatch in subMatches)
|
||||
{
|
||||
var subMatchValue = subMatch.Groups[1].Value;
|
||||
@@ -1944,14 +1944,14 @@ namespace MPF.Library
|
||||
}
|
||||
|
||||
// Foreign Title
|
||||
match = Constants.ForeignTitleRegex.Match(discData);
|
||||
match = SabreTools.RedumpLib.Data.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);
|
||||
match = SabreTools.RedumpLib.Data.Constants.CategoryRegex.Match(discData);
|
||||
if (match.Success)
|
||||
info.CommonDiscInfo.Category = Extensions.ToDiscCategory(match.Groups[1].Value);
|
||||
else
|
||||
@@ -1960,13 +1960,13 @@ namespace MPF.Library
|
||||
// Region
|
||||
if (info.CommonDiscInfo.Region == null)
|
||||
{
|
||||
match = Constants.RegionRegex.Match(discData);
|
||||
match = SabreTools.RedumpLib.Data.Constants.RegionRegex.Match(discData);
|
||||
if (match.Success)
|
||||
info.CommonDiscInfo.Region = Extensions.ToRegion(match.Groups[1].Value);
|
||||
}
|
||||
|
||||
// Languages
|
||||
var matches = Constants.LanguagesRegex.Matches(discData);
|
||||
var matches = SabreTools.RedumpLib.Data.Constants.LanguagesRegex.Matches(discData);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
List<Language?> tempLanguages = new List<Language?>();
|
||||
@@ -1985,7 +1985,7 @@ namespace MPF.Library
|
||||
// Error count
|
||||
if (string.IsNullOrEmpty(info.CommonDiscInfo.ErrorsCount))
|
||||
{
|
||||
match = Constants.ErrorCountRegex.Match(discData);
|
||||
match = SabreTools.RedumpLib.Data.Constants.ErrorCountRegex.Match(discData);
|
||||
if (match.Success)
|
||||
info.CommonDiscInfo.ErrorsCount = match.Groups[1].Value;
|
||||
}
|
||||
@@ -1993,13 +1993,13 @@ namespace MPF.Library
|
||||
// Version
|
||||
if (info.VersionAndEditions.Version == null)
|
||||
{
|
||||
match = Constants.VersionRegex.Match(discData);
|
||||
match = SabreTools.RedumpLib.Data.Constants.VersionRegex.Match(discData);
|
||||
if (match.Success)
|
||||
info.VersionAndEditions.Version = $"(VERIFY THIS) {WebUtility.HtmlDecode(match.Groups[1].Value)}";
|
||||
}
|
||||
|
||||
// Dumpers
|
||||
matches = Constants.DumpersRegex.Matches(discData);
|
||||
matches = SabreTools.RedumpLib.Data.Constants.DumpersRegex.Matches(discData);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
// Start with any currently listed dumpers
|
||||
@@ -2019,7 +2019,7 @@ namespace MPF.Library
|
||||
// TODO: Unify handling of fields that can include site codes (Comments/Contents)
|
||||
|
||||
// Comments
|
||||
match = Constants.CommentsRegex.Match(discData);
|
||||
match = SabreTools.RedumpLib.Data.Constants.CommentsRegex.Match(discData);
|
||||
if (match.Success)
|
||||
{
|
||||
// Process the old comments block
|
||||
@@ -2140,7 +2140,7 @@ namespace MPF.Library
|
||||
}
|
||||
|
||||
// Contents
|
||||
match = Constants.ContentsRegex.Match(discData);
|
||||
match = SabreTools.RedumpLib.Data.Constants.ContentsRegex.Match(discData);
|
||||
if (match.Success)
|
||||
{
|
||||
// Process the old contents block
|
||||
@@ -2224,7 +2224,7 @@ namespace MPF.Library
|
||||
}
|
||||
|
||||
// Added
|
||||
match = Constants.AddedRegex.Match(discData);
|
||||
match = SabreTools.RedumpLib.Data.Constants.AddedRegex.Match(discData);
|
||||
if (match.Success)
|
||||
{
|
||||
if (DateTime.TryParse(match.Groups[1].Value, out DateTime added))
|
||||
@@ -2234,7 +2234,7 @@ namespace MPF.Library
|
||||
}
|
||||
|
||||
// Last Modified
|
||||
match = Constants.LastModifiedRegex.Match(discData);
|
||||
match = SabreTools.RedumpLib.Data.Constants.LastModifiedRegex.Match(discData);
|
||||
if (match.Success)
|
||||
{
|
||||
if (DateTime.TryParse(match.Groups[1].Value, out DateTime lastModified))
|
||||
@@ -2483,9 +2483,9 @@ namespace MPF.Library
|
||||
while (true)
|
||||
{
|
||||
#if NET48 || NETSTANDARD2_1
|
||||
List<int> pageIds = wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++));
|
||||
List<int> pageIds = wc.CheckSingleSitePage(string.Format(SabreTools.RedumpLib.Data.Constants.QuickSearchUrl, query, pageNumber++));
|
||||
#else
|
||||
List<int> pageIds = await wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++));
|
||||
List<int> pageIds = await wc.CheckSingleSitePage(string.Format(SabreTools.RedumpLib.Data.Constants.QuickSearchUrl, query, pageNumber++));
|
||||
#endif
|
||||
ids.AddRange(pageIds);
|
||||
if (pageIds.Count <= 1)
|
||||
@@ -2623,7 +2623,7 @@ namespace MPF.Library
|
||||
return false;
|
||||
|
||||
// Discs with only 1 track don't have a track count listed
|
||||
var match = Constants.TrackCountRegex.Match(discData);
|
||||
var match = SabreTools.RedumpLib.Data.Constants.TrackCountRegex.Match(discData);
|
||||
if (!match.Success && localCount == 1)
|
||||
return true;
|
||||
else if (!match.Success)
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Xml.Serialization;
|
||||
using MPF.Core.Data;
|
||||
using MPF.Core.Hashing;
|
||||
using MPF.Core.Utilities;
|
||||
using SabreTools.Models.PIC;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Modules
|
||||
@@ -1164,66 +1165,13 @@ namespace MPF.Modules
|
||||
/// Gets disc information from a PIC file
|
||||
/// </summary>
|
||||
/// <param name="pic">Path to a PIC.bin file</param>
|
||||
/// <returns>Filled PICDiscInformation on success, null on error</returns>
|
||||
/// <returns>Filled DiscInformation on success, null on error</returns>
|
||||
/// <remarks>This omits the emergency brake information, if it exists</remarks>
|
||||
protected static PICDiscInformation GetDiscInformation(string pic)
|
||||
protected static DiscInformation GetDiscInformation(string pic)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (BinaryReader br = new BinaryReader(File.OpenRead(pic)))
|
||||
{
|
||||
var di = new PICDiscInformation();
|
||||
|
||||
// Read the initial disc information
|
||||
di.DataStructureLength = br.ReadUInt16BigEndian();
|
||||
di.Reserved0 = br.ReadByte();
|
||||
di.Reserved1 = br.ReadByte();
|
||||
|
||||
// Create a list for the units
|
||||
var diUnits = new List<PICDiscInformationUnit>();
|
||||
|
||||
// Loop and read all available units
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
var unit = new PICDiscInformationUnit();
|
||||
|
||||
// We only accept Disc Information units, not Emergency Brake or other
|
||||
unit.DiscInformationIdentifier = Encoding.ASCII.GetString(br.ReadBytes(2));
|
||||
if (unit.DiscInformationIdentifier != "DI")
|
||||
break;
|
||||
|
||||
unit.DiscInformationFormat = br.ReadByte();
|
||||
unit.NumberOfUnitsInBlock = br.ReadByte();
|
||||
unit.Reserved0 = br.ReadByte();
|
||||
unit.SequenceNumber = br.ReadByte();
|
||||
unit.BytesInUse = br.ReadByte();
|
||||
unit.Reserved1 = br.ReadByte();
|
||||
|
||||
unit.DiscTypeIdentifier = Encoding.ASCII.GetString(br.ReadBytes(3));
|
||||
unit.DiscSizeClassVersion = br.ReadByte();
|
||||
switch (unit.DiscTypeIdentifier)
|
||||
{
|
||||
case PICDiscInformationUnit.DiscTypeIdentifierROM:
|
||||
case PICDiscInformationUnit.DiscTypeIdentifierROMUltra:
|
||||
unit.FormatDependentContents = br.ReadBytes(52);
|
||||
break;
|
||||
case PICDiscInformationUnit.DiscTypeIdentifierReWritable:
|
||||
case PICDiscInformationUnit.DiscTypeIdentifierRecordable:
|
||||
unit.FormatDependentContents = br.ReadBytes(100);
|
||||
unit.DiscManufacturerID = br.ReadBytes(6);
|
||||
unit.MediaTypeID = br.ReadBytes(3);
|
||||
unit.TimeStamp = br.ReadUInt16();
|
||||
unit.ProductRevisionNumber = br.ReadByte();
|
||||
break;
|
||||
}
|
||||
|
||||
diUnits.Add(unit);
|
||||
}
|
||||
|
||||
// Assign the units and return
|
||||
di.Units = diUnits.ToArray();
|
||||
return di;
|
||||
}
|
||||
return new SabreTools.Serialization.Files.PIC().Deserialize(pic);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -1412,7 +1360,7 @@ namespace MPF.Modules
|
||||
/// </summary>
|
||||
/// <param name="di">Disc information containing unformatted data</param>
|
||||
/// <returns>True if layerbreak info was set, false otherwise</returns>
|
||||
protected static bool GetLayerbreaks(PICDiscInformation di, out long? layerbreak1, out long? layerbreak2, out long? layerbreak3)
|
||||
protected static bool GetLayerbreaks(DiscInformation di, out long? layerbreak1, out long? layerbreak2, out long? layerbreak3)
|
||||
{
|
||||
// Set the default values
|
||||
layerbreak1 = null; layerbreak2 = null; layerbreak3 = null;
|
||||
@@ -1432,24 +1380,24 @@ namespace MPF.Modules
|
||||
// Layerbreak 1 (2+ layers)
|
||||
if (di.Units.Length >= 2)
|
||||
{
|
||||
long offset = ReadFromArrayBigEndian(di.Units[0].FormatDependentContents, 0x0C);
|
||||
long value = ReadFromArrayBigEndian(di.Units[0].FormatDependentContents, 0x10);
|
||||
long offset = ReadFromArrayBigEndian(di.Units[0].Body.FormatDependentContents, 0x0C);
|
||||
long value = ReadFromArrayBigEndian(di.Units[0].Body.FormatDependentContents, 0x10);
|
||||
layerbreak1 = value - offset + 2;
|
||||
}
|
||||
|
||||
// Layerbreak 2 (3+ layers)
|
||||
if (di.Units.Length >= 3)
|
||||
{
|
||||
long offset = ReadFromArrayBigEndian(di.Units[1].FormatDependentContents, 0x0C);
|
||||
long value = ReadFromArrayBigEndian(di.Units[1].FormatDependentContents, 0x10);
|
||||
long offset = ReadFromArrayBigEndian(di.Units[1].Body.FormatDependentContents, 0x0C);
|
||||
long value = ReadFromArrayBigEndian(di.Units[1].Body.FormatDependentContents, 0x10);
|
||||
layerbreak2 = layerbreak1 + value - offset + 2;
|
||||
}
|
||||
|
||||
// Layerbreak 3 (4 layers)
|
||||
if (di.Units.Length >= 4)
|
||||
{
|
||||
long offset = ReadFromArrayBigEndian(di.Units[2].FormatDependentContents, 0x0C);
|
||||
long value = ReadFromArrayBigEndian(di.Units[2].FormatDependentContents, 0x10);
|
||||
long offset = ReadFromArrayBigEndian(di.Units[2].Body.FormatDependentContents, 0x0C);
|
||||
long value = ReadFromArrayBigEndian(di.Units[2].Body.FormatDependentContents, 0x10);
|
||||
layerbreak3 = layerbreak2 + value - offset + 2;
|
||||
}
|
||||
|
||||
@@ -1461,14 +1409,14 @@ namespace MPF.Modules
|
||||
/// </summary>
|
||||
/// <param name="di">Disc information containing the data</param>
|
||||
/// <returns>String representing the PIC identifier, null on error</returns>
|
||||
protected static string GetPICIdentifier(PICDiscInformation di)
|
||||
protected static string GetPICIdentifier(DiscInformation di)
|
||||
{
|
||||
// If we don't have valid disc information, we can't do anything
|
||||
if (di?.Units == null || di.Units.Length <= 1)
|
||||
return null;
|
||||
|
||||
// We assume the identifier is consistent across all units
|
||||
return di.Units[0].DiscTypeIdentifier;
|
||||
return di.Units[0].Body.DiscTypeIdentifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
|
||||
using MPF.Core.Converters;
|
||||
using MPF.Core.Data;
|
||||
using MPF.Core.Utilities;
|
||||
using SabreTools.Models.PIC;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Modules.DiscImageCreator
|
||||
|
||||
Reference in New Issue
Block a user