From 952828ddddbbbd151d430d182766e3dfae08f138 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 14 Nov 2023 23:09:55 -0500 Subject: [PATCH] Support ancient .NET in Core This does not include .NET Framework 4.0 due to some issues with libraries. --- CHANGELIST.md | 1 + MPF.Core/Data/Constants.cs | 14 +- MPF.Core/Data/Drive.cs | 14 +- MPF.Core/Data/Enumerations.cs | 4 + MPF.Core/Data/IniFile.cs | 2 +- MPF.Core/DumpEnvironment.cs | 15 +- MPF.Core/Hashing/Hasher.cs | 23 ++- MPF.Core/Hashing/OptimizedCRC.cs | 184 ++++++++++++++++++ MPF.Core/InfoTool.cs | 28 +-- MPF.Core/MPF.Core.csproj | 35 +++- MPF.Core/Modules/Aaru/Parameters.cs | 24 ++- MPF.Core/Modules/BaseParameters.cs | 22 ++- MPF.Core/Modules/CleanRIp/Parameters.cs | 24 +-- .../Modules/DiscImageCreator/Parameters.cs | 66 +++---- MPF.Core/Modules/Redumper/Parameters.cs | 52 ++--- .../Modules/UmdImageCreator/Parameters.cs | 2 +- MPF.Core/Protection.cs | 23 ++- MPF.Core/SubmissionInfoTool.cs | 80 ++++++-- MPF.Core/UI/ViewModels/MainViewModel.cs | 6 +- MPF.Core/UI/ViewModels/OptionsViewModel.cs | 4 + MPF.Core/Utilities/EnumExtensions.cs | 2 +- MPF.Core/Utilities/Logging.cs | 30 ++- MPF.Core/Utilities/Tools.cs | 3 +- 23 files changed, 522 insertions(+), 136 deletions(-) create mode 100644 MPF.Core/Hashing/OptimizedCRC.cs diff --git a/CHANGELIST.md b/CHANGELIST.md index 017c37db..6c14d835 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -3,6 +3,7 @@ - Add Bandai Pippin detection - Zip manufacturer files for Redumper - Fix BE flag logic bug in DIC +- Support ancient .NET in Core ### 3.0.0 (2023-11-14) diff --git a/MPF.Core/Data/Constants.cs b/MPF.Core/Data/Constants.cs index 1a0bc49c..c0543552 100644 --- a/MPF.Core/Data/Constants.cs +++ b/MPF.Core/Data/Constants.cs @@ -14,21 +14,33 @@ namespace MPF.Core.Data public const string StopDumping = "Stop Dumping"; // Byte arrays for signatures - public static readonly byte[] SaturnSectorZeroStart = new byte[] { 0x53, 0x45, 0x47, 0x41, 0x20, 0x53, 0x45, 0x47, 0x41, 0x53, 0x41, 0x54, 0x55, 0x52, 0x4E, 0x20 }; + public static readonly byte[] SaturnSectorZeroStart = [0x53, 0x45, 0x47, 0x41, 0x20, 0x53, 0x45, 0x47, 0x41, 0x53, 0x41, 0x54, 0x55, 0x52, 0x4E, 0x20]; // Lists of known drive speed ranges +#if NET40 + public static IList CD { get; } = new List { 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72 }; + public static IList DVD { get; } = CD.Where(s => s <= 24).ToList(); + public static IList HDDVD { get; } = CD.Where(s => s <= 24).ToList(); + public static IList BD { get; } = CD.Where(s => s <= 16).ToList(); + public static IList Unknown { get; } = new List { 1 }; +#else public static IReadOnlyList CD { get; } = new List { 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72 }; public static IReadOnlyList DVD { get; } = CD.Where(s => s <= 24).ToList(); public static IReadOnlyList HDDVD { get; } = CD.Where(s => s <= 24).ToList(); public static IReadOnlyList BD { get; } = CD.Where(s => s <= 16).ToList(); public static IReadOnlyList Unknown { get; } = new List { 1 }; +#endif /// /// Get list of all drive speeds for a given MediaType /// /// MediaType? that represents the current item /// Read-only list of drive speeds +#if NET40 + public static IList GetSpeedsForMediaType(MediaType? type) +#else public static IReadOnlyList GetSpeedsForMediaType(MediaType? type) +#endif { return type switch { diff --git a/MPF.Core/Data/Drive.cs b/MPF.Core/Data/Drive.cs index a032cb11..58f73671 100644 --- a/MPF.Core/Data/Drive.cs +++ b/MPF.Core/Data/Drive.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; +#if NET462_OR_GREATER || NETCOREAPP using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Generic; +#endif using MPF.Core.Converters; using SabreTools.RedumpLib.Data; @@ -61,7 +63,7 @@ namespace MPF.Core.Data { get { - string volumeLabel = Template.DiscNotDetected; + string? volumeLabel = Template.DiscNotDetected; if (this.MarkedActive) { if (string.IsNullOrWhiteSpace(this.VolumeLabel)) @@ -71,7 +73,7 @@ namespace MPF.Core.Data } foreach (char c in Path.GetInvalidFileNameChars()) - volumeLabel = volumeLabel.Replace(c, '_'); + volumeLabel = volumeLabel?.Replace(c, '_'); return volumeLabel; } @@ -109,7 +111,7 @@ namespace MPF.Core.Data // Sanitize a Windows-formatted long device path if (devicePath.StartsWith("\\\\.\\")) - devicePath = devicePath["\\\\.\\".Length..]; + devicePath = devicePath.Substring("\\\\.\\".Length); // Create and validate the drive info object var driveInfo = new DriveInfo(devicePath); @@ -249,7 +251,7 @@ namespace MPF.Core.Data public RedumpSystem? GetRedumpSystem(RedumpSystem? defaultValue) { // If we can't read the media in that drive, we can't do anything - if (!Directory.Exists(this.Name)) + if (string.IsNullOrWhiteSpace(this.Name) || !Directory.Exists(this.Name)) return defaultValue; // We're going to assume for floppies, HDDs, and removable drives @@ -456,7 +458,7 @@ namespace MPF.Core.Data return null; // Audio CD - if (this.VolumeLabel.Equals("Audio CD", StringComparison.OrdinalIgnoreCase)) + if (this.VolumeLabel!.Equals("Audio CD", StringComparison.OrdinalIgnoreCase)) return RedumpSystem.AudioCD; // Microsoft Xbox @@ -547,6 +549,7 @@ namespace MPF.Core.Data } // Find and update all floppy drives +#if NET462_OR_GREATER try { CimSession session = CimSession.Create(null); @@ -567,6 +570,7 @@ namespace MPF.Core.Data { // No-op } +#endif return drives; } diff --git a/MPF.Core/Data/Enumerations.cs b/MPF.Core/Data/Enumerations.cs index 8e2ec1ca..e8d2c0d3 100644 --- a/MPF.Core/Data/Enumerations.cs +++ b/MPF.Core/Data/Enumerations.cs @@ -6,14 +6,18 @@ public enum Hash { CRC32, +#if NET6_0_OR_GREATER CRC64, +#endif MD5, SHA1, SHA256, SHA384, SHA512, +#if NET6_0_OR_GREATER XxHash32, XxHash64, +#endif } /// diff --git a/MPF.Core/Data/IniFile.cs b/MPF.Core/Data/IniFile.cs index 06df6f0d..94f72438 100644 --- a/MPF.Core/Data/IniFile.cs +++ b/MPF.Core/Data/IniFile.cs @@ -108,7 +108,7 @@ namespace MPF.Core.Data } // Comments start with ';' - else if (line.StartsWith(";")) + else if (line!.StartsWith(";")) { // No-op, we don't process comments } diff --git a/MPF.Core/DumpEnvironment.cs b/MPF.Core/DumpEnvironment.cs index 261f0205..55a64eb5 100644 --- a/MPF.Core/DumpEnvironment.cs +++ b/MPF.Core/DumpEnvironment.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; -using BinaryObjectScanner; using MPF.Core.Data; using MPF.Core.Modules; using MPF.Core.Utilities; @@ -75,14 +74,18 @@ namespace MPF.Core /// /// Event handler for data returned from a process /// +#if NET40 + private void OutputToLog(object? proc, BaseParameters.StringEventArgs args) => outputQueue?.Enqueue(args.Value); +#else private void OutputToLog(object? proc, string args) => outputQueue?.Enqueue(args); +#endif /// /// Process the outputs in the queue /// private void ProcessOutputs(string nextOutput) => ReportStatus?.Invoke(this, nextOutput); - #endregion +#endregion /// /// Constructor for a full DumpEnvironment object from user information @@ -256,7 +259,7 @@ namespace MPF.Core /// Result instance with the outcome public async Task VerifyAndSaveDumpOutput( IProgress? resultProgress = null, - IProgress? protectionProgress = null, + IProgress? protectionProgress = null, Func? processUserInfo = null, SubmissionInfo? seedInfo = null) { @@ -432,8 +435,8 @@ namespace MPF.Core { StartInfo = new ProcessStartInfo() { - FileName = parameters.ExecutablePath, - Arguments = parameters.GenerateParameters(), + FileName = parameters.ExecutablePath!, + Arguments = parameters.GenerateParameters()!, CreateNoWindow = true, UseShellExecute = false, RedirectStandardInput = true, @@ -477,7 +480,7 @@ namespace MPF.Core return Result.Failure($"Error! {Parameters.ExecutablePath} does not exist!"); // Validate that the dumping drive doesn't contain the executable - string fullExecutablePath = Path.GetFullPath(Parameters.ExecutablePath); + string fullExecutablePath = Path.GetFullPath(Parameters.ExecutablePath!); if (Drive?.Name != null && fullExecutablePath.StartsWith(Drive.Name)) return Result.Failure("Error! Cannot dump same drive that executable resides on!"); diff --git a/MPF.Core/Hashing/Hasher.cs b/MPF.Core/Hashing/Hasher.cs index 64800038..a4730f0d 100644 --- a/MPF.Core/Hashing/Hasher.cs +++ b/MPF.Core/Hashing/Hasher.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.IO; +#if NET6_0_OR_GREATER using System.IO.Hashing; +#endif using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; @@ -16,7 +18,11 @@ namespace MPF.Core.Hashing /// /// Hash type associated with the current state /// +#if NETFRAMEWORK || NETCOREAPP3_1 + public Hash HashType { get; private set; } +#else public Hash HashType { get; init; } +#endif /// /// Current hash in bytes @@ -71,14 +77,18 @@ namespace MPF.Core.Hashing _hasher = HashType switch { Hash.CRC32 => new Crc32(), +#if NET6_0_OR_GREATER Hash.CRC64 => new Crc64(), +#endif Hash.MD5 => MD5.Create(), Hash.SHA1 => SHA1.Create(), Hash.SHA256 => SHA256.Create(), Hash.SHA384 => SHA384.Create(), Hash.SHA512 => SHA512.Create(), +#if NET6_0_OR_GREATER Hash.XxHash32 => new XxHash32(), Hash.XxHash64 => new XxHash64(), +#endif _ => null, }; } @@ -156,14 +166,18 @@ namespace MPF.Core.Hashing var hashers = new Dictionary { { Hash.CRC32, new Hasher(Hash.CRC32) }, +#if NET6_0_OR_GREATER { Hash.CRC64, new Hasher(Hash.CRC64) }, +#endif { Hash.MD5, new Hasher(Hash.MD5) }, { Hash.SHA1, new Hasher(Hash.SHA1) }, { Hash.SHA256, new Hasher(Hash.SHA256) }, { Hash.SHA384, new Hasher(Hash.SHA384) }, { Hash.SHA512, new Hasher(Hash.SHA512) }, +#if NET6_0_OR_GREATER { Hash.XxHash32, new Hasher(Hash.XxHash32) }, { Hash.XxHash64, new Hasher(Hash.XxHash64) }, +#endif }; // Initialize the hashing helpers @@ -216,15 +230,18 @@ namespace MPF.Core.Hashing // Get the results hashDict[Hash.CRC32] = hashers[Hash.CRC32].CurrentHashString; +#if NET6_0_OR_GREATER hashDict[Hash.CRC64] = hashers[Hash.CRC64].CurrentHashString; +#endif hashDict[Hash.MD5] = hashers[Hash.MD5].CurrentHashString; hashDict[Hash.SHA1] = hashers[Hash.SHA1].CurrentHashString; hashDict[Hash.SHA256] = hashers[Hash.SHA256].CurrentHashString; hashDict[Hash.SHA384] = hashers[Hash.SHA384].CurrentHashString; hashDict[Hash.SHA512] = hashers[Hash.SHA512].CurrentHashString; +#if NET6_0_OR_GREATER hashDict[Hash.XxHash32] = hashers[Hash.XxHash32].CurrentHashString; hashDict[Hash.XxHash64] = hashers[Hash.XxHash64].CurrentHashString; - hashDict[Hash.CRC64] = hashers[Hash.CRC64].CurrentHashString; +#endif // Dispose of the hashers loadBuffer.Dispose(); @@ -272,7 +289,11 @@ namespace MPF.Core.Hashing /// NonCryptographicHashAlgorithm implementations do not need finalization public void Terminate() { +#if NET40 || NET452 + byte[] emptyBuffer = []; +#else byte[] emptyBuffer = Array.Empty(); +#endif switch (_hasher) { case HashAlgorithm ha: diff --git a/MPF.Core/Hashing/OptimizedCRC.cs b/MPF.Core/Hashing/OptimizedCRC.cs new file mode 100644 index 00000000..11b30340 --- /dev/null +++ b/MPF.Core/Hashing/OptimizedCRC.cs @@ -0,0 +1,184 @@ +#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0 + +/* + + Copyright (c) 2012-2015 Eugene Larchenko (spct@mail.ru) + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +using System; + +//namespace OptimizedCRC +namespace MPF.Core.Hashing +{ + /// + /// Shell class to trick older versions into using CRC-32 properly + /// + internal abstract class NonCryptographicHashAlgorithm + { +#if NET40 + /// + /// When overridden in a derived class, appends the contents of source to + /// the data already processed for the current hash computation. + /// + /// The data to process. + public abstract void Append(byte[] source); +#else + /// + /// When overridden in a derived class, appends the contents of source to + /// the data already processed for the current hash computation. + /// + /// The data to process. + public abstract void Append(ReadOnlySpan source); +#endif + + /// + /// Gets the current computed hash value without modifying accumulated state. + /// + /// The hash value for the data already provided. + public abstract byte[] GetCurrentHash(); + } + + /// + /// Some changes have been made to this code to make it more similar to the System.IO.Hashing implementations + /// + internal class Crc32 : NonCryptographicHashAlgorithm, IDisposable + { + private const uint kCrcPoly = 0xEDB88320; + private const uint kInitial = 0xFFFFFFFF; + private const int CRC_NUM_TABLES = 8; + private static readonly uint[] Table; + + static Crc32() + { + unchecked + { + Table = new uint[256 * CRC_NUM_TABLES]; + int i; + for (i = 0; i < 256; i++) + { + uint r = (uint)i; + for (int j = 0; j < 8; j++) + { + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + } + Table[i] = r; + } + for (; i < 256 * CRC_NUM_TABLES; i++) + { + uint r = Table[i - 256]; + Table[i] = Table[r & 0xFF] ^ (r >> 8); + } + } + } + + public uint UnsignedValue; + + public Crc32() + { + Init(); + } + + /// + /// Reset CRC + /// + public void Init() + { + UnsignedValue = kInitial; + } + + /// + public override byte[] GetCurrentHash() + { + return BitConverter.GetBytes(~UnsignedValue); + } + + /// +#if NET40 + public override void Append(byte[] source) + { + Update(source, 0, source.Length); + } +#else + public override void Append(ReadOnlySpan source) + { + byte[] sourceBytes = source.ToArray(); + Update(sourceBytes, 0, sourceBytes.Length); + } +#endif + + private void Update(byte[] data, int offset, int count) + { + _ = new ArraySegment(data, offset, count); // check arguments + if (count == 0) + { + return; + } + + var table = Table; + + uint crc = UnsignedValue; + + for (; (offset & 7) != 0 && count != 0; count--) + { + crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]]; + } + + if (count >= 8) + { + /* + * Idea from 7-zip project sources (http://7-zip.org/sdk.html) + */ + + int end = (count - 8) & ~7; + count -= end; + end += offset; + + while (offset != end) + { + crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24)); + uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24)); + offset += 8; + + crc = table[(byte)crc + 0x700] + ^ table[(byte)(crc >>= 8) + 0x600] + ^ table[(byte)(crc >>= 8) + 0x500] + ^ table[/*(byte)*/(crc >> 8) + 0x400] + ^ table[(byte)(high) + 0x300] + ^ table[(byte)(high >>= 8) + 0x200] + ^ table[(byte)(high >>= 8) + 0x100] + ^ table[/*(byte)*/(high >> 8) + 0x000]; + } + } + + while (count-- != 0) + { + crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]]; + } + + UnsignedValue = crc; + } + + public void Dispose() + { + UnsignedValue = 0; + } + } +} + +#endif diff --git a/MPF.Core/InfoTool.cs b/MPF.Core/InfoTool.cs index 0a7edc7b..2e0a6e1e 100644 --- a/MPF.Core/InfoTool.cs +++ b/MPF.Core/InfoTool.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; -using BinaryObjectScanner; using MPF.Core.Data; using MPF.Core.Modules; using MPF.Core.Utilities; @@ -104,7 +103,7 @@ namespace MPF.Core /// Options object that determines what to scan /// Optional progress callback /// Detected copy protection(s) if possible, null on error - internal static async Task<(string?, Dictionary>?)> GetCopyProtection(Drive? drive, Data.Options options, IProgress? progress = null) + internal static async Task<(string?, Dictionary>?)> GetCopyProtection(Drive? drive, Data.Options options, IProgress? progress = null) { if (options.ScanForProtection && drive?.Name != null) { @@ -980,7 +979,7 @@ namespace MPF.Core return null; // Standardized "S" serials - if (serial.StartsWith("S")) + if (serial!.StartsWith("S")) { // string publisher = serial[0] + serial[1]; // char secondRegion = serial[3]; @@ -1135,8 +1134,13 @@ namespace MPF.Core } else { - string entryName = file[outputDirectory.Length..].TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + string entryName = file.Substring(outputDirectory!.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + +#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0 + zf.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal); +#else zf.CreateEntryFromFile(file, entryName, CompressionLevel.SmallestSize); +#endif } // If the file is MPF-specific, don't delete @@ -1256,8 +1260,8 @@ namespace MPF.Core 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 ?? new Language?[] { null }).Select(l => l.LongName() ?? "SILENCE! (CHANGE THIS)").ToArray(), 1); - AddIfExists(output, Template.PlaystationLanguageSelectionViaField, (info.CommonDiscInfo?.LanguageSelection ?? Array.Empty()).Select(l => l.LongName()).ToArray(), 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 @@ -1383,12 +1387,12 @@ namespace MPF.Core output.Add(""); output.Add("Copy Protection:"); if (info.CommonDiscInfo?.System == RedumpSystem.SonyPlayStation) { - AddIfExists(output, Template.PlayStationAntiModchipField, info.CopyProtection.AntiModchip.LongName(), 1); + 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.CopyProtectionField, info.CopyProtection!.Protection, 1); AddIfExists(output, Template.SubIntentionField, info.CopyProtection.SecuROMData, 1); } @@ -1401,7 +1405,7 @@ namespace MPF.Core if (!string.IsNullOrWhiteSpace(info.TracksAndWriteOffsets?.ClrMameProData)) { output.Add(""); output.Add("Tracks and Write Offsets:"); - AddIfExists(output, Template.DATField, info.TracksAndWriteOffsets.ClrMameProData + "\n", 1); + AddIfExists(output, Template.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)) @@ -1880,7 +1884,7 @@ namespace MPF.Core return files; } - #endregion +#endregion #region Normalization @@ -1894,7 +1898,7 @@ namespace MPF.Core { // If we have no set languages, then assume English if (languages == null || languages.Length == 0) - languages = new Language[] { Language.English }; + languages = [Language.English]; // Loop through all of the given languages foreach (var language in languages) @@ -2358,7 +2362,7 @@ namespace MPF.Core return string.Empty; // Remove quotes from path - path = path.Replace("\"", string.Empty); + path = path!.Replace("\"", string.Empty); // Try getting the combined path and returning that directly string fullPath = getFullPath ? Path.GetFullPath(path) : path; diff --git a/MPF.Core/MPF.Core.csproj b/MPF.Core/MPF.Core.csproj index b208e895..261e7e52 100644 --- a/MPF.Core/MPF.Core.csproj +++ b/MPF.Core/MPF.Core.csproj @@ -1,12 +1,21 @@  - net6.0;net8.0 - win-x64;linux-x64;osx-x64 + + net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64 + latest enable - Matt Nadareski;ReignStumble;Jakz - Copyright (c)2019-2023 + true 3.0.0 + + + Matt Nadareski;ReignStumble;Jakz + Common code for all MPF implementations + Copyright (c) Matt Nadareski 2019-2023 + https://github.com/SabreTools/ + https://github.com/SabreTools/MPF + git @@ -17,16 +26,30 @@ runtime; compile; build; native; analyzers; buildtransitive - + + + + + + + + + + + - + + + + + diff --git a/MPF.Core/Modules/Aaru/Parameters.cs b/MPF.Core/Modules/Aaru/Parameters.cs index b6bfe262..11793773 100644 --- a/MPF.Core/Modules/Aaru/Parameters.cs +++ b/MPF.Core/Modules/Aaru/Parameters.cs @@ -226,9 +226,9 @@ namespace MPF.Core.Modules.Aaru if (!string.IsNullOrWhiteSpace(discType) && !string.IsNullOrWhiteSpace(discSubType)) fullDiscType = $"{discType} ({discSubType})"; else if (!string.IsNullOrWhiteSpace(discType) && string.IsNullOrWhiteSpace(discSubType)) - fullDiscType = discType; + fullDiscType = discType!; else if (string.IsNullOrWhiteSpace(discType) && !string.IsNullOrWhiteSpace(discSubType)) - fullDiscType = discSubType; + fullDiscType = discSubType!; info.DumpingInfo.ReportedDiscType = fullDiscType; } @@ -1630,7 +1630,7 @@ namespace MPF.Core.Modules.Aaru // Now split the string into parts for easier validation // https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes - parameters = parameters.Trim(); + parameters = parameters!.Trim(); List parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+", RegexOptions.Compiled) .Cast() .Select(m => m.Value) @@ -2384,7 +2384,11 @@ namespace MPF.Core.Modules.Aaru var cueFiles = new List(); var cueSheet = new CueSheet { +#if NET40 || NET452 + Performer = string.Join(", ", cicmSidecar.Performer ?? []), +#else Performer = string.Join(", ", cicmSidecar.Performer ?? Array.Empty()), +#endif }; // Only care about OpticalDisc types @@ -2931,16 +2935,24 @@ namespace MPF.Core.Modules.Aaru /// Generate a single 16-byte sector line from a byte array /// /// Row ID for outputting - /// Byte span representing the data to write + /// Bytes representing the data to write /// Formatted string representing the sector line +#if NET40 + private static string? GenerateSectorOutputLine(string row, byte[] bytes) +#else private static string? GenerateSectorOutputLine(string row, ReadOnlySpan bytes) +#endif { // If the data isn't correct, return null if (bytes == null || bytes.Length != 16) return null; string pvdLine = $"{row} : "; +#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0 + pvdLine += BitConverter.ToString(bytes.Slice(0, 8).ToArray()).Replace("-", " "); +#else pvdLine += BitConverter.ToString(bytes[..8].ToArray()).Replace("-", " "); +#endif pvdLine += " "; pvdLine += BitConverter.ToString(bytes.Slice(8, 8).ToArray().ToArray()).Replace("-", " "); pvdLine += " "; @@ -3086,7 +3098,7 @@ namespace MPF.Core.Modules.Aaru // Initialize on seeing the open tag if (string.IsNullOrWhiteSpace(line)) continue; - else if (line.StartsWith("")) + else if (line!.StartsWith("")) totalErrors = 0; else if (line.StartsWith("")) return totalErrors ?? -1; @@ -3400,6 +3412,6 @@ namespace MPF.Core.Modules.Aaru return false; } - #endregion +#endregion } } diff --git a/MPF.Core/Modules/BaseParameters.cs b/MPF.Core/Modules/BaseParameters.cs index ac55738d..1c946a46 100644 --- a/MPF.Core/Modules/BaseParameters.cs +++ b/MPF.Core/Modules/BaseParameters.cs @@ -14,11 +14,27 @@ namespace MPF.Core.Modules { #region Event Handlers +#if NET40 + /// + /// Wrapper event args class for old .NET + /// + public class StringEventArgs : EventArgs + { + public string Value { get; set; } + } + + /// + /// Geneeic way of reporting a message + /// + /// String value to report + public EventHandler? ReportStatus; +#else /// /// Geneeic way of reporting a message /// /// String value to report public EventHandler? ReportStatus; +#endif #endregion @@ -261,7 +277,7 @@ namespace MPF.Core.Modules // Create the start info var startInfo = new ProcessStartInfo() { - FileName = ExecutablePath, + FileName = ExecutablePath!, Arguments = GenerateParameters() ?? "", CreateNoWindow = !separateWindow, UseShellExecute = separateWindow, @@ -1104,7 +1120,7 @@ namespace MPF.Core.Modules return null; if (trimLength > -1) - hex = hex[..trimLength]; + hex = hex.Substring(0, trimLength); return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled); } @@ -1117,6 +1133,6 @@ namespace MPF.Core.Modules - #endregion +#endregion } } diff --git a/MPF.Core/Modules/CleanRIp/Parameters.cs b/MPF.Core/Modules/CleanRIp/Parameters.cs index c7ef8afa..d3e730b2 100644 --- a/MPF.Core/Modules/CleanRIp/Parameters.cs +++ b/MPF.Core/Modules/CleanRIp/Parameters.cs @@ -170,12 +170,12 @@ namespace MPF.Core.Modules.CleanRip var line = sr.ReadLine()?.Trim(); if (string.IsNullOrWhiteSpace(line)) continue; - else if (line.StartsWith("CRC32")) - crc = line[7..].ToLowerInvariant(); + else if (line!.StartsWith("CRC32")) + crc = line.Substring(7).ToLowerInvariant(); else if (line.StartsWith("MD5")) - md5 = line[5..]; + md5 = line.Substring(5); else if (line.StartsWith("SHA-1")) - sha1 = line[7..]; + sha1 = line.Substring(7); } return new Datafile @@ -256,12 +256,12 @@ namespace MPF.Core.Modules.CleanRip var line = sr.ReadLine()?.Trim(); if (string.IsNullOrWhiteSpace(line)) continue; - else if (line.StartsWith("CRC32")) - crc = line[7..].ToLowerInvariant(); + else if (line!.StartsWith("CRC32")) + crc = line.Substring(7).ToLowerInvariant(); else if (line.StartsWith("MD5")) - md5 = line[5..]; + md5 = line.Substring(5); else if (line.StartsWith("SHA-1")) - sha1 = line[7..]; + sha1 = line.Substring(7); } return $""; @@ -304,17 +304,17 @@ namespace MPF.Core.Modules.CleanRip { continue; } - else if (line.StartsWith("Version")) + else if (line!.StartsWith("Version")) { - version = line["Version: ".Length..]; + version = line.Substring("Version: ".Length); } else if (line.StartsWith("Internal Name")) { - name = line["Internal Name: ".Length..]; + name = line.Substring("Internal Name: ".Length); } else if (line.StartsWith("Filename")) { - string serial = line["Filename: ".Length..]; + string serial = line.Substring("Filename: ".Length); // char gameType = serial[0]; // string gameid = serial[1] + serial[2]; diff --git a/MPF.Core/Modules/DiscImageCreator/Parameters.cs b/MPF.Core/Modules/DiscImageCreator/Parameters.cs index c94dc046..69619d7d 100644 --- a/MPF.Core/Modules/DiscImageCreator/Parameters.cs +++ b/MPF.Core/Modules/DiscImageCreator/Parameters.cs @@ -1940,7 +1940,7 @@ namespace MPF.Core.Modules.DiscImageCreator // Now split the string into parts for easier validation // https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes - parameters = parameters.Trim(); + parameters = parameters!.Trim(); List parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+", RegexOptions.Compiled) .Cast() .Select(m => m.Value) @@ -2697,25 +2697,25 @@ namespace MPF.Core.Modules.DiscImageCreator if (line.StartsWith("DiscType:")) { // DiscType: - string identifier = line["DiscType: ".Length..]; + string identifier = line.Substring("DiscType: ".Length); discTypeOrBookTypeSet.Add(identifier); } else if (line.StartsWith("DiscTypeIdentifier:")) { // DiscTypeIdentifier: - string identifier = line["DiscTypeIdentifier: ".Length..]; + string identifier = line.Substring("DiscTypeIdentifier: ".Length); discTypeOrBookTypeSet.Add(identifier); } else if (line.StartsWith("DiscTypeSpecific:")) { // DiscTypeSpecific: - string identifier = line["DiscTypeSpecific: ".Length..]; + string identifier = line.Substring("DiscTypeSpecific: ".Length); discTypeOrBookTypeSet.Add(identifier); } else if (line.StartsWith("BookType:")) { // BookType: - string identifier = line["BookType: ".Length..]; + string identifier = line.Substring("BookType: ".Length); discTypeOrBookTypeSet.Add(identifier); } @@ -2767,9 +2767,9 @@ namespace MPF.Core.Modules.DiscImageCreator break; if (line.StartsWith("CopyrightProtectionType")) - copyrightProtectionSystemType = line["CopyrightProtectionType: ".Length..]; + copyrightProtectionSystemType = line.Substring("CopyrightProtectionType: ".Length); else if (line.StartsWith("RegionManagementInformation")) - region = line["RegionManagementInformation: ".Length..]; + region = line.Substring("RegionManagementInformation: ".Length); line = sr.ReadLine()?.Trim(); } @@ -2792,7 +2792,7 @@ namespace MPF.Core.Modules.DiscImageCreator if (line.StartsWith("DecryptedDiscKey")) { - decryptedDiscKey = line["DecryptedDiscKey[020]: ".Length..]; + decryptedDiscKey = line.Substring("DecryptedDiscKey[020]: ".Length); } else if (line.StartsWith("LBA:")) { @@ -2805,7 +2805,7 @@ namespace MPF.Core.Modules.DiscImageCreator var match = Regex.Match(line, @"^LBA:\s*[0-9]+, Filename: (.*?), No TitleKey$", RegexOptions.Compiled); string matchedFilename = match.Groups[1].Value; if (matchedFilename.EndsWith(";1")) - matchedFilename = matchedFilename[..^2]; + matchedFilename = matchedFilename.Substring(0, matchedFilename.Length - 2); vobKeys += $"{matchedFilename} Title Key: No Title Key\n"; } @@ -2814,7 +2814,7 @@ namespace MPF.Core.Modules.DiscImageCreator var match = Regex.Match(line, @"^LBA:\s*[0-9]+, Filename: (.*?), EncryptedTitleKey: .*?, DecryptedTitleKey: (.*?)$", RegexOptions.Compiled); string matchedFilename = match.Groups[1].Value; if (matchedFilename.EndsWith(";1")) - matchedFilename = matchedFilename[..^2]; + matchedFilename = matchedFilename.Substring(0, matchedFilename.Length - 2); vobKeys += $"{matchedFilename} Title Key: {match.Groups[2].Value}\n"; } @@ -2876,14 +2876,14 @@ namespace MPF.Core.Modules.DiscImageCreator { totalErrors ??= 0; - if (Int64.TryParse(line["Total errors: ".Length..].Trim(), out long te)) + if (Int64.TryParse(line.Substring("Total errors: ".Length).Trim(), out long te)) totalErrors += te; } else if (line.StartsWith("Total warnings")) { totalErrors ??= 0; - if (Int64.TryParse(line["Total warnings: ".Length..].Trim(), out long tw)) + if (Int64.TryParse(line.Substring("Total warnings: ".Length).Trim(), out long tw)) totalErrors += tw; } } @@ -2914,12 +2914,12 @@ namespace MPF.Core.Modules.DiscImageCreator // Now read it in cutting it into lines for easier parsing try { - string[] header = segaHeader.Split('\n'); - string versionLine = header[4][58..]; - string dateLine = header[5][58..]; - serial = versionLine[..10].TrimEnd(); + string[] header = segaHeader!.Split('\n'); + string versionLine = header[4].Substring(58); + string dateLine = header[5].Substring(58); + serial = versionLine.Substring(0, 10).TrimEnd(); version = versionLine.Substring(10, 6).TrimStart('V', 'v'); - date = dateLine[..8]; + date = dateLine.Substring(0, 8); return true; } catch @@ -2956,17 +2956,17 @@ namespace MPF.Core.Modules.DiscImageCreator if (string.IsNullOrEmpty(manufacturer) && line.StartsWith("VendorId")) { // VendorId: - manufacturer = line["VendorId: ".Length..]; + manufacturer = line.Substring("VendorId: ".Length); } else if (string.IsNullOrEmpty(model) && line.StartsWith("ProductId")) { // ProductId: - model = line["ProductId: ".Length..]; + model = line.Substring("ProductId: ".Length); } else if (string.IsNullOrEmpty(firmware) && line.StartsWith("ProductRevisionLevel")) { // ProductRevisionLevel: - firmware = line["ProductRevisionLevel: ".Length..]; + firmware = line.Substring("ProductRevisionLevel: ".Length); } line = sr.ReadLine(); @@ -3117,7 +3117,7 @@ namespace MPF.Core.Modules.DiscImageCreator // TODO: Are there any examples of 3+ session discs? // Read the first session lead-out - var firstSessionLeadOutLengthString = line?["Lead-out length of 1st session: ".Length..]; + var firstSessionLeadOutLengthString = line?.Substring("Lead-out length of 1st session: ".Length); line = sr.ReadLine()?.Trim(); if (line == null) return null; @@ -3126,12 +3126,12 @@ namespace MPF.Core.Modules.DiscImageCreator string? secondSessionLeadInLengthString = null; while (line?.StartsWith("Lead-in length") == false) { - secondSessionLeadInLengthString = line?["Lead-in length of 2nd session: ".Length..]; + secondSessionLeadInLengthString = line?.Substring("Lead-in length of 2nd session: ".Length); line = sr.ReadLine()?.Trim(); } // Read the second session pregap - var secondSessionPregapLengthString = line?["Pregap length of 1st track of 2nd session: ".Length..]; + var secondSessionPregapLengthString = line?.Substring("Pregap length of 1st track of 2nd session: ".Length); // Calculate the session gap total if (!int.TryParse(firstSessionLeadOutLengthString, out int firstSessionLeadOutLength)) @@ -3354,12 +3354,12 @@ namespace MPF.Core.Modules.DiscImageCreator // Now read it in cutting it into lines for easier parsing try { - string[] header = segaHeader.Split('\n'); - string serialVersionLine = header[2][58..]; - string dateLine = header[3][58..]; - serial = serialVersionLine[..10].Trim(); + string[] header = segaHeader!.Split('\n'); + string serialVersionLine = header[2].Substring(58); + string dateLine = header[3].Substring(58); + serial = serialVersionLine.Substring(0, 10).Trim(); version = serialVersionLine.Substring(10, 6).TrimStart('V', 'v'); - date = dateLine[..8]; + date = dateLine.Substring(0, 8); date = $"{date[0]}{date[1]}{date[2]}{date[3]}-{date[4]}{date[5]}-{date[6]}{date[7]}"; return true; } @@ -3387,17 +3387,17 @@ namespace MPF.Core.Modules.DiscImageCreator // Now read it in cutting it into lines for easier parsing try { - string[] header = segaHeader.Split('\n'); - string serialVersionLine = header[8][58..]; - string dateLine = header[1][58..]; + string[] header = segaHeader!.Split('\n'); + string serialVersionLine = header[8].Substring(58); + string dateLine = header[1].Substring(58); serial = serialVersionLine.Substring(3, 8).TrimEnd('-', ' '); - date = dateLine[8..].Trim(); + date = dateLine.Substring(8).Trim(); // Properly format the date string, if possible string[] dateSplit = date.Split('.'); if (dateSplit.Length == 1) - dateSplit = new string[] { date[..4], date[4..] }; + dateSplit = [date.Substring(0, 4), date.Substring(4)]; string month = dateSplit[1]; dateSplit[1] = month switch diff --git a/MPF.Core/Modules/Redumper/Parameters.cs b/MPF.Core/Modules/Redumper/Parameters.cs index a6d631e0..7de1f2f1 100644 --- a/MPF.Core/Modules/Redumper/Parameters.cs +++ b/MPF.Core/Modules/Redumper/Parameters.cs @@ -1016,7 +1016,7 @@ namespace MPF.Core.Modules.Redumper // Now split the string into parts for easier validation // https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes - parameters = parameters.Trim(); + parameters = parameters!.Trim(); List parts = Regex.Matches(parameters, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+", RegexOptions.Compiled) .Cast() .Select(m => m.Value) @@ -1105,12 +1105,12 @@ namespace MPF.Core.Modules.Redumper // Image Path stringValue = ProcessStringParameter(parts, FlagStrings.ImagePath, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) - ImagePathValue = $"\"{stringValue.Trim('"')}\""; + ImagePathValue = $"\"{stringValue!.Trim('"')}\""; // Image Name stringValue = ProcessStringParameter(parts, FlagStrings.ImageName, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) - ImageNameValue = $"\"{stringValue.Trim('"')}\""; + ImageNameValue = $"\"{stringValue!.Trim('"')}\""; // Overwrite ProcessFlagParameter(parts, FlagStrings.Overwrite, ref i); @@ -1352,7 +1352,7 @@ namespace MPF.Core.Modules.Redumper if (line.StartsWith("current profile:")) { // current profile: - discTypeOrBookType = line["current profile: ".Length..]; + discTypeOrBookType = line.Substring("current profile: ".Length); } line = sr.ReadLine(); @@ -1394,17 +1394,17 @@ namespace MPF.Core.Modules.Redumper { if (line.StartsWith("protection system type")) { - copyrightProtectionSystemType = line["protection system type: ".Length..]; + copyrightProtectionSystemType = line.Substring("protection system type: ".Length); if (copyrightProtectionSystemType == "none" || copyrightProtectionSystemType == "") copyrightProtectionSystemType = "No"; } else if (line.StartsWith("region management information:")) { - region = line["region management information: ".Length..]; + region = line.Substring("region management information: ".Length); } else if (line.StartsWith("disc key")) { - decryptedDiscKey = line["disc key: ".Length..].Replace(':', ' '); + decryptedDiscKey = line.Substring("disc key: ".Length).Replace(':', ' '); } else if (line.StartsWith("title keys")) { @@ -1488,7 +1488,7 @@ namespace MPF.Core.Modules.Redumper break; // REDUMP.ORG errors: - string[] parts = line.Split(' '); + string[] parts = line!.Split(' '); if (long.TryParse(parts[2], out long redump)) return redump; else @@ -1596,24 +1596,24 @@ namespace MPF.Core.Modules.Redumper else if (line.StartsWith("layer break:")) { // layer break: - layerbreak1 = line["layer break: ".Length..].Trim(); + layerbreak1 = line.Substring("layer break: ".Length).Trim(); } // Multi-layer discs have the layer in the name else if (line.StartsWith("layer break (layer: 0):")) { // layer break (layer: 0): - layerbreak1 = line["layer break (layer: 0): ".Length..].Trim(); + layerbreak1 = line.Substring("layer break (layer: 0): ".Length).Trim(); } else if (line.StartsWith("layer break (layer: 1):")) { // layer break (layer: 1): - layerbreak2 = line["layer break (layer: 1): ".Length..].Trim(); + layerbreak2 = line.Substring("layer break (layer: 1): ".Length).Trim(); } else if (line.StartsWith("layer break (layer: 2):")) { // layer break (layer: 2): - layerbreak3 = line["layer break (layer: 2): ".Length..].Trim(); + layerbreak3 = line.Substring("layer break (layer: 2): ".Length).Trim(); } } @@ -1658,11 +1658,11 @@ namespace MPF.Core.Modules.Redumper // Store the first session range if (line.Contains("session 1:")) - firstSession = line["session 1: ".Length..].Trim(); + firstSession = line.Substring("session 1: ".Length).Trim(); // Store the secomd session range else if (line.Contains("session 2:")) - secondSession = line["session 2: ".Length..].Trim(); + secondSession = line.Substring("session 2: ".Length).Trim(); } // If either is blank, we don't have multisession @@ -1884,7 +1884,7 @@ namespace MPF.Core.Modules.Redumper { string? line = sr.ReadLine()?.TrimStart(); if (line?.StartsWith("non-zero data sample range") == true) - return line["non-zero data sample range: [".Length..].Trim().Split(' ')[0]; + return line.Substring("non-zero data sample range: [".Length).Trim().Split(' ')[0]; } // We couldn't detect it then @@ -1914,12 +1914,12 @@ namespace MPF.Core.Modules.Redumper // Now read it in cutting it into lines for easier parsing try { - string[] header = segaHeader.Split('\n'); - string serialVersionLine = header[2][58..]; - string dateLine = header[3][58..]; - serial = serialVersionLine[..10].Trim(); + string[] header = segaHeader!.Split('\n'); + string serialVersionLine = header[2].Substring(58); + string dateLine = header[3].Substring(58); + serial = serialVersionLine.Substring(0, 10).Trim(); version = serialVersionLine.Substring(10, 6).TrimStart('V', 'v'); - date = dateLine[..8]; + date = dateLine.Substring(0, 8); date = $"{date[0]}{date[1]}{date[2]}{date[3]}-{date[4]}{date[5]}-{date[6]}{date[7]}"; return true; } @@ -2052,19 +2052,19 @@ namespace MPF.Core.Modules.Redumper if (line.StartsWith("build date:")) { - buildDate = line["build date: ".Length..].Trim(); + buildDate = line.Substring("build date: ".Length).Trim(); } else if (line.StartsWith("serial:")) { - serial = line["serial: ".Length..].Trim(); + serial = line.Substring("serial: ".Length).Trim(); } else if (line.StartsWith("region:")) { - region = line["region: ".Length..].Trim(); + region = line.Substring("region: ".Length).Trim(); } else if (line.StartsWith("regions:")) { - region = line["regions: ".Length..].Trim(); + region = line.Substring("regions: ".Length).Trim(); } else if (line.StartsWith("header:")) { @@ -2109,7 +2109,7 @@ namespace MPF.Core.Modules.Redumper { string? line = sr.ReadLine()?.TrimStart(); if (line?.StartsWith("Universal Hash") == true) - return line["Universal Hash (SHA-1): ".Length..].Trim(); + return line.Substring("Universal Hash (SHA-1): ".Length).Trim(); } // We couldn't detect it then @@ -2141,7 +2141,7 @@ namespace MPF.Core.Modules.Redumper { string? line = sr.ReadLine()?.TrimStart(); if (line?.StartsWith("disc write offset") == true) - return line["disc write offset: ".Length..].Trim(); + return line.Substring("disc write offset: ".Length).Trim(); } // We couldn't detect it then diff --git a/MPF.Core/Modules/UmdImageCreator/Parameters.cs b/MPF.Core/Modules/UmdImageCreator/Parameters.cs index 033f1ceb..a578c40b 100644 --- a/MPF.Core/Modules/UmdImageCreator/Parameters.cs +++ b/MPF.Core/Modules/UmdImageCreator/Parameters.cs @@ -203,7 +203,7 @@ namespace MPF.Core.Modules.UmdImageCreator break; if (line.StartsWith("TITLE") && title == null) - title = line["TITLE: ".Length..]; + title = line.Substring("TITLE: ".Length); else if (line.StartsWith("DISC_VERSION") && umdversion == null) umdversion = line.Split(' ')[1]; else if (line.StartsWith("pspUmdTypes")) diff --git a/MPF.Core/Protection.cs b/MPF.Core/Protection.cs index 96e49773..b0cf965f 100644 --- a/MPF.Core/Protection.cs +++ b/MPF.Core/Protection.cs @@ -4,8 +4,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; -using BinaryObjectScanner; -using BinaryObjectScanner.Protection; using psxt001z; #pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. @@ -21,13 +19,13 @@ namespace MPF.Core /// Options object that determines what to scan /// Optional progress callback /// Set of all detected copy protections with an optional error string - public static async Task<(Dictionary>?, string?)> RunProtectionScanOnPath(string path, Data.Options options, IProgress? progress = null) + public static async Task<(Dictionary>?, string?)> RunProtectionScanOnPath(string path, Data.Options options, IProgress? progress = null) { try { var found = await Task.Run(() => { - var scanner = new Scanner( + var scanner = new BinaryObjectScanner.Scanner( options.ScanArchivesForProtection, scanContents: true, // Hardcoded value to avoid issues scanGameEngines: false, // Hardcoded value to avoid issues @@ -99,7 +97,7 @@ namespace MPF.Core { try { - var antiModchip = new PSXAntiModchip(); + var antiModchip = new BinaryObjectScanner.Protection.PSXAntiModchip(); foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) { try @@ -142,9 +140,15 @@ namespace MPF.Core if (foundProtections.Any(p => p.StartsWith("[Exception opening file"))) { foundProtections = foundProtections.Where(p => !p.StartsWith("[Exception opening file")); +#if NET40 || NET452 || NET462 + var tempList = new List { "Exception occurred while scanning [RESCAN NEEDED]" }; + tempList.AddRange(foundProtections); + foundProtections = tempList.OrderBy(p => p); +#else foundProtections = foundProtections .Prepend("Exception occurred while scanning [RESCAN NEEDED]") .OrderBy(p => p); +#endif } // ActiveMARK @@ -225,7 +229,16 @@ namespace MPF.Core .Where(p => p != "Cactus Data Shield 300 (Confirm presence of other CDS-300 files)"); if (foundProtections.Any(p => !p.StartsWith("SafeDisc"))) + { +#if NET40 || NET452 || NET462 + var tempList = new List(); + tempList.AddRange(foundProtections); + tempList.Add("Cactus Data Shield 300"); + foundProtections = tempList; +#else foundProtections = foundProtections.Append("Cactus Data Shield 300"); +#endif + } } // SafeDisc diff --git a/MPF.Core/SubmissionInfoTool.cs b/MPF.Core/SubmissionInfoTool.cs index e9ffdca2..8cfdc97a 100644 --- a/MPF.Core/SubmissionInfoTool.cs +++ b/MPF.Core/SubmissionInfoTool.cs @@ -7,7 +7,6 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; -using BinaryObjectScanner; using MPF.Core.Data; using MPF.Core.Modules; using Newtonsoft.Json; @@ -90,8 +89,12 @@ namespace MPF.Core // Loop through and get the main node, if possible XmlNode? mainNode = null; - foreach (XmlNode tempNode in bodyNode.ChildNodes) + 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; @@ -113,8 +116,12 @@ namespace MPF.Core return null; // Try to find elements as we're going - foreach (XmlNode childNode in mainNode.ChildNodes) + 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; @@ -132,8 +139,12 @@ namespace MPF.Core continue; // The game node contains multiple other elements - foreach (XmlNode gameNode in childNode.ChildNodes) + 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)) { @@ -150,8 +161,12 @@ namespace MPF.Core continue; // Loop through each of the rows - foreach (XmlNode gameInfoNode in gameNode.ChildNodes) + 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; @@ -288,7 +303,7 @@ namespace MPF.Core Data.Options options, BaseParameters? parameters, IProgress? resultProgress = null, - IProgress? protectionProgress = null) + IProgress? protectionProgress = null) { // Ensure the current disc combination should exist if (!system.MediaTypes().Contains(mediaType)) @@ -692,12 +707,20 @@ namespace MPF.Core /// Existing SubmissionInfo object to fill /// Redump disc ID to retrieve /// True to include all pullable information, false to do bare minimum +#if NETFRAMEWORK + public async static Task FillFromId(RedumpWebClient wc, SubmissionInfo info, int id, bool includeAllData) +#else public async static Task FillFromId(RedumpHttpClient wc, SubmissionInfo info, int id, bool includeAllData) +#endif { // Ensure that required sections exist info = EnsureAllSections(info); +#if NETFRAMEWORK + var discData = await Task.Run(() => wc.DownloadSingleSiteID(id)); +#else var discData = await wc.DownloadSingleSiteID(id); +#endif if (string.IsNullOrEmpty(discData)) return false; @@ -711,7 +734,7 @@ namespace MPF.Core int firstParenLocation = title.IndexOf(" ("); if (firstParenLocation >= 0) { - info.CommonDiscInfo!.Title = title[..firstParenLocation]; + info.CommonDiscInfo!.Title = title.Substring(0, firstParenLocation); var subMatches = Constants.DiscNumberLetterRegex.Matches(title); foreach (Match subMatch in subMatches.Cast()) { @@ -1069,12 +1092,17 @@ namespace MPF.Core // Set the current dumper based on username info.DumpersAndStatus ??= new DumpersAndStatusSection(); - info.DumpersAndStatus.Dumpers = new string[] { options.RedumpUsername }; + info.DumpersAndStatus.Dumpers = [options.RedumpUsername!]; info.PartiallyMatchedIDs = new List(); // Login to Redump +#if NETFRAMEWORK + using var wc = new RedumpWebClient(); + bool? loggedIn = wc.Login(options.RedumpUsername!, options.RedumpPassword!); +#else using var wc = new RedumpHttpClient(); bool? loggedIn = await wc.Login(options.RedumpUsername, options.RedumpPassword); +#endif if (loggedIn == null) { resultProgress?.Report(Result.Failure("There was an unknown error connecting to Redump")); @@ -1094,7 +1122,11 @@ namespace MPF.Core resultProgress?.Report(Result.Success("Finding disc matches on Redump...")); var splitData = info.TracksAndWriteOffsets?.ClrMameProData?.TrimEnd('\n')?.Split('\n'); int trackCount = splitData?.Length ?? 0; +#if NET40 || NET452 + foreach (string hashData in splitData ?? []) +#else foreach (string hashData in splitData ?? Array.Empty()) +#endif { // Catch any errant blank lines if (string.IsNullOrWhiteSpace(hashData)) @@ -1270,7 +1302,7 @@ namespace MPF.Core } } - #endregion +#endregion #region Helpers @@ -1342,7 +1374,11 @@ namespace MPF.Core /// Query string to attempt to search for /// True to filter forward slashes, false otherwise /// All disc IDs for the given query, null on error +#if NETFRAMEWORK + private async static Task?> ListSearchResults(RedumpWebClient wc, string? query, bool filterForwardSlashes = true) +#else private async static Task?> ListSearchResults(RedumpHttpClient wc, string? query, bool filterForwardSlashes = true) +#endif { // If there is an invalid query if (string.IsNullOrWhiteSpace(query)) @@ -1351,7 +1387,7 @@ namespace MPF.Core var ids = new List(); // Strip quotes - query = query.Trim('"', '\''); + query = query!.Trim('"', '\''); // Special characters become dashes query = query.Replace(' ', '-'); @@ -1368,7 +1404,11 @@ namespace MPF.Core int pageNumber = 1; while (true) { +#if NETFRAMEWORK + List pageIds = await Task.Run(() => wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++))); +#else List pageIds = await wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++)); +#endif ids.AddRange(pageIds); if (pageIds.Count <= 1) break; @@ -1391,7 +1431,11 @@ namespace MPF.Core /// DAT-formatted hash data to parse out /// Optional result progress callback /// True if the track was found, false otherwise; List of found values, if possible +#if NETFRAMEWORK + private async static Task<(bool, List?)> ValidateSingleTrack(RedumpWebClient wc, SubmissionInfo info, string hashData, IProgress? resultProgress = null) +#else private async static Task<(bool, List?)> ValidateSingleTrack(RedumpHttpClient wc, SubmissionInfo info, string hashData, IProgress? resultProgress = null) +#endif { // If the line isn't parseable, we can't validate if (!InfoTool.GetISOHashValues(hashData, out long _, out var _, out var _, out var sha1)) @@ -1430,7 +1474,11 @@ namespace MPF.Core /// Existing SubmissionInfo object to fill /// Optional result progress callback /// True if the track was found, false otherwise; List of found values, if possible +#if NETFRAMEWORK + private async static Task<(bool, List?)> ValidateUniversalHash(RedumpWebClient wc, SubmissionInfo info, IProgress? resultProgress = null) +#else private async static Task<(bool, List?)> ValidateUniversalHash(RedumpHttpClient wc, SubmissionInfo info, IProgress? resultProgress = null) +#endif { // If we don't have special fields if (info.CommonDiscInfo?.CommentsSpecialFields == null) @@ -1448,7 +1496,7 @@ namespace MPF.Core } // Format the universal hash for finding within the comments - universalHash = $"{universalHash[..^1]}/comments/only"; + universalHash = $"{universalHash.Substring(0, universalHash.Length - 1)}/comments/only"; // Get all matching IDs for the hash var newIds = await ListSearchResults(wc, universalHash, filterForwardSlashes: false); @@ -1480,10 +1528,18 @@ namespace MPF.Core /// Redump disc ID to retrieve /// Local count of tracks for the current disc /// True if the track count matches, false otherwise +#if NETFRAMEWORK + private async static Task ValidateTrackCount(RedumpWebClient wc, int id, int localCount) +#else private async static Task ValidateTrackCount(RedumpHttpClient wc, int id, int localCount) +#endif { // If we can't pull the remote data, we can't match +#if NETFRAMEWORK + string? discData = await Task.Run(() => wc.DownloadSingleSiteID(id)); +#else string? discData = await wc.DownloadSingleSiteID(id); +#endif if (string.IsNullOrEmpty(discData)) return false; @@ -1502,6 +1558,6 @@ namespace MPF.Core return localCount == remoteCount; } - #endregion +#endregion } } diff --git a/MPF.Core/UI/ViewModels/MainViewModel.cs b/MPF.Core/UI/ViewModels/MainViewModel.cs index 707a1a10..4425434f 100644 --- a/MPF.Core/UI/ViewModels/MainViewModel.cs +++ b/MPF.Core/UI/ViewModels/MainViewModel.cs @@ -1677,10 +1677,10 @@ namespace MPF.Core.UI.ViewModels } // Validate that the user explicitly wants an inactive drive to be considered for dumping - if (_environment.Drive?.MarkedActive != true && _displayUserMessage != null) + if (_environment?.Drive?.MarkedActive != true && _displayUserMessage != null) { string message = "The currently selected drive does not appear to contain a disc! " - + (!_environment.System.DetectedByWindows() ? $"This is normal for {_environment.System.LongName()} as the discs may not be readable on Windows. " : string.Empty) + + (!_environment!.System.DetectedByWindows() ? $"This is normal for {_environment.System.LongName()} as the discs may not be readable on Windows. " : string.Empty) + "Do you want to continue?"; bool? mbresult = _displayUserMessage("No Disc Detected", message, 2, false); @@ -1692,7 +1692,7 @@ namespace MPF.Core.UI.ViewModels } // Pre-split the output path - var outputDirectory = Path.GetDirectoryName(_environment.OutputPath); + var outputDirectory = Path.GetDirectoryName(_environment!.OutputPath); string outputFilename = Path.GetFileName(_environment.OutputPath); // If a complete dump already exists diff --git a/MPF.Core/UI/ViewModels/OptionsViewModel.cs b/MPF.Core/UI/ViewModels/OptionsViewModel.cs index 4c22fbd3..3b192037 100644 --- a/MPF.Core/UI/ViewModels/OptionsViewModel.cs +++ b/MPF.Core/UI/ViewModels/OptionsViewModel.cs @@ -83,7 +83,11 @@ namespace MPF.Core.UI.ViewModels /// public static async Task<(bool?, string?)> TestRedumpLogin(string username, string password) { +#if NETFRAMEWORK + return await Task.Run(() => RedumpWebClient.ValidateCredentials(username, password)); +#else return await RedumpHttpClient.ValidateCredentials(username, password); +#endif } #endregion diff --git a/MPF.Core/Utilities/EnumExtensions.cs b/MPF.Core/Utilities/EnumExtensions.cs index 36fb36ac..9d6eea18 100644 --- a/MPF.Core/Utilities/EnumExtensions.cs +++ b/MPF.Core/Utilities/EnumExtensions.cs @@ -127,7 +127,7 @@ namespace MPF.Core.Utilities foreach (var val in Enum.GetValues(typeof(InternalProgram))) { - if (((InternalProgram)val) == InternalProgram.NONE) + if (((InternalProgram)val!) == InternalProgram.NONE) continue; programs.Add($"{((InternalProgram?)val).LongName()}"); diff --git a/MPF.Core/Utilities/Logging.cs b/MPF.Core/Utilities/Logging.cs index 338ff08d..f27d6b5e 100644 --- a/MPF.Core/Utilities/Logging.cs +++ b/MPF.Core/Utilities/Logging.cs @@ -14,7 +14,11 @@ namespace MPF.Core.Utilities /// TextReader representing the input /// Invoking class, passed on to the event handler /// Event handler to be invoked to write to log +#if NET40 + public static async Task OutputToLog(TextReader reader, object baseClass, EventHandler? handler) +#else public static async Task OutputToLog(TextReader reader, object baseClass, EventHandler? handler) +#endif { // Initialize the required variables char[] buffer = new char[256]; @@ -37,15 +41,27 @@ namespace MPF.Core.Utilities string line = new(buffer, 0, read); // If we have no newline characters, store in the string builder +#if NETFRAMEWORK + if (!line.Contains("\r") && !line.Contains("\n")) +#else if (!line.Contains('\r') && !line.Contains('\n')) +#endif sb.Append(line); // If we have a newline, append and log +#if NETFRAMEWORK + else if (line.Contains("\n") || line.Contains("\r\n")) +#else else if (line.Contains('\n') || line.Contains("\r\n")) +#endif ProcessNewLines(sb, line, baseClass, handler); // If we have a carriage return only, append and log first and last instances +#if NETFRAMEWORK + else if (line.Contains("\r")) +#else else if (line.Contains('\r')) +#endif ProcessCarriageReturns(sb, line, baseClass, handler); } } @@ -63,14 +79,22 @@ namespace MPF.Core.Utilities /// Current line to process /// Invoking class, passed on to the event handler /// Event handler to be invoked to write to log +#if NET40 + private static void ProcessNewLines(StringBuilder sb, string line, object baseClass, EventHandler? handler) +#else private static void ProcessNewLines(StringBuilder sb, string line, object baseClass, EventHandler? handler) +#endif { line = line.Replace("\r\n", "\n"); var split = line.Split('\n'); for (int i = 0; i < split.Length; i++) { // If the chunk contains a carriage return, handle it like a separate line +#if NETFRAMEWORK + if (split[i].Contains("\r")) +#else if (split[i].Contains('\r')) +#endif { ProcessCarriageReturns(sb, split[i], baseClass, handler); continue; @@ -105,7 +129,11 @@ namespace MPF.Core.Utilities /// Current line to process /// Invoking class, passed on to the event handler /// Event handler to be invoked to write to log +#if NET40 + private static void ProcessCarriageReturns(StringBuilder sb, string line, object baseClass, EventHandler? handler) +#else private static void ProcessCarriageReturns(StringBuilder sb, string line, object baseClass, EventHandler? handler) +#endif { var split = line.Split('\r'); @@ -115,7 +143,7 @@ namespace MPF.Core.Utilities // Append the last sb.Clear(); - sb.Append($"\r{split[^1]}"); + sb.Append($"\r{split[split.Length - 1]}"); } } } diff --git a/MPF.Core/Utilities/Tools.cs b/MPF.Core/Utilities/Tools.cs index 352f8166..98cdc5e1 100644 --- a/MPF.Core/Utilities/Tools.cs +++ b/MPF.Core/Utilities/Tools.cs @@ -236,7 +236,8 @@ namespace MPF.Core.Utilities string url = "https://api.github.com/repos/SabreTools/MPF/releases/latest"; var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url); message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0"); - var latestReleaseJsonString = hc.Send(message)?.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + var latestReleaseJsonString = hc.SendAsync(message)?.ConfigureAwait(false).GetAwaiter().GetResult() + .Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (latestReleaseJsonString == null) return (null, null);