mirror of
https://github.com/SabreTools/MPF.git
synced 2026-02-05 05:37:36 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b1a303fea | ||
|
|
80a0f6da35 | ||
|
|
0c30eb7a60 | ||
|
|
7a8125bb71 | ||
|
|
c4beffd845 | ||
|
|
f97c112a53 | ||
|
|
5ef43ab6be | ||
|
|
2c399f99bf | ||
|
|
42e9eb0b96 | ||
|
|
e67d65f908 | ||
|
|
4ec8954b14 | ||
|
|
1a6abb039c | ||
|
|
bb5d1e5ac8 | ||
|
|
03c4c475eb | ||
|
|
04d7817d28 | ||
|
|
7cd100bc53 | ||
|
|
019924232a | ||
|
|
b5fc9f0275 | ||
|
|
44509b72ed | ||
|
|
d532a63dbd | ||
|
|
227785b079 | ||
|
|
0e364be998 | ||
|
|
7ae1f64ee3 | ||
|
|
92463a103d | ||
|
|
101cdb7b34 | ||
|
|
e924299f85 | ||
|
|
b983f7eb4a |
@@ -1,3 +1,30 @@
|
||||
### 2.7.4 (2023-10-31)
|
||||
|
||||
- Move Hash enum and simplify
|
||||
- Add other non-cryptographic hashes
|
||||
- Avoid unncessary allocation in hashing
|
||||
- Make Hasher more consistent
|
||||
- Move all hashing to Hasher
|
||||
- Separate out static hashing
|
||||
- Remove unncessary summary
|
||||
- Version-gate new switch statement
|
||||
- Ignore empty protection results
|
||||
- Remove and Sort Usings
|
||||
- Update Redumper to build 244
|
||||
- Enable Bluray dumping with Redumper
|
||||
- Add Bluray layerbreak support for Redumper
|
||||
- Remove now-obsolete notes
|
||||
- Compile most regex statements
|
||||
- Handle a couple of messages
|
||||
- Remove .manufacturer for Bluray
|
||||
- Fix typo that disables DIC during media check (Deterous)
|
||||
- Fix build
|
||||
- Remove duplicate check
|
||||
- Add PIC output for Redumper
|
||||
- Update Redumper to build 246
|
||||
- Enable HD-DVD for Redumper in UI
|
||||
- Attempt to fix window owner issue
|
||||
|
||||
### 2.7.3 (2023-10-26)
|
||||
|
||||
- Split InfoTool into 2 classes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<Description>Validator for various dumping programs</Description>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Copyright>Copyright (c)2019-2023</Copyright>
|
||||
<VersionPrefix>2.7.3</VersionPrefix>
|
||||
<VersionPrefix>2.7.4</VersionPrefix>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace MPF.Core.Converters
|
||||
case InternalProgram.DiscImageCreator:
|
||||
return "DiscImageCreator";
|
||||
case InternalProgram.Redumper:
|
||||
return "redumper";
|
||||
return "Redumper";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Linq;
|
||||
using Microsoft.Management.Infrastructure;
|
||||
using Microsoft.Management.Infrastructure.Generic;
|
||||
using MPF.Core.Converters;
|
||||
using MPF.Core.Utilities;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Core.Data
|
||||
@@ -14,7 +13,6 @@ namespace MPF.Core.Data
|
||||
/// Represents information for a single drive
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: This needs to be less Windows-centric. Devices do not always have a single letter that can be used.
|
||||
/// TODO: Can the Aaru models be used instead of the ones I've created here?
|
||||
/// </remarks>
|
||||
public class Drive
|
||||
@@ -287,7 +285,6 @@ namespace MPF.Core.Data
|
||||
return defaultValue;
|
||||
|
||||
// We're going to assume for floppies, HDDs, and removable drives
|
||||
// TODO: Try to be smarter about this
|
||||
if (this.InternalDriveType != Data.InternalDriveType.Optical)
|
||||
return RedumpSystem.IBMPCcompatible;
|
||||
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
namespace MPF.Core.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Available hashing types
|
||||
/// </summary>
|
||||
public enum Hash
|
||||
{
|
||||
CRC32,
|
||||
CRC64,
|
||||
MD5,
|
||||
SHA1,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512,
|
||||
XxHash32,
|
||||
XxHash64,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drive type for dumping
|
||||
/// </summary>
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Core.Data;
|
||||
using MPF.Core.Utilities;
|
||||
using MPF.Core.Modules;
|
||||
using MPF.Core.Utilities;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Core
|
||||
@@ -460,14 +461,17 @@ namespace MPF.Core
|
||||
resultProgress?.Report(Result.Failure(txtResult));
|
||||
|
||||
// Write the copy protection output
|
||||
if (Options.ScanForProtection && Options.OutputSeparateProtectionFile)
|
||||
if (submissionInfo?.CopyProtection?.FullProtections != null && submissionInfo.CopyProtection.FullProtections.Any())
|
||||
{
|
||||
resultProgress?.Report(Result.Success("Writing protection to !protectionInfo.txt..."));
|
||||
bool scanSuccess = InfoTool.WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo);
|
||||
if (scanSuccess)
|
||||
resultProgress?.Report(Result.Success("Writing complete!"));
|
||||
else
|
||||
resultProgress?.Report(Result.Failure("Writing could not complete!"));
|
||||
if (Options.ScanForProtection && Options.OutputSeparateProtectionFile)
|
||||
{
|
||||
resultProgress?.Report(Result.Success("Writing protection to !protectionInfo.txt..."));
|
||||
bool scanSuccess = InfoTool.WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo);
|
||||
if (scanSuccess)
|
||||
resultProgress?.Report(Result.Success("Writing complete!"));
|
||||
else
|
||||
resultProgress?.Report(Result.Failure("Writing could not complete!"));
|
||||
}
|
||||
}
|
||||
|
||||
// Write the JSON output, if required
|
||||
|
||||
@@ -1,41 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Hashing;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using MPF.Core.Data;
|
||||
|
||||
namespace MPF.Core.Hashing
|
||||
{
|
||||
/// <summary>
|
||||
/// Available hashing types
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Hash
|
||||
public sealed class Hasher : IDisposable
|
||||
{
|
||||
CRC32 = 1 << 0,
|
||||
MD5 = 1 << 1,
|
||||
SHA1 = 1 << 2,
|
||||
SHA256 = 1 << 3,
|
||||
SHA384 = 1 << 4,
|
||||
SHA512 = 1 << 5,
|
||||
#region Properties
|
||||
|
||||
// Special combinations
|
||||
Standard = CRC32 | MD5 | SHA1,
|
||||
All = CRC32 | MD5 | SHA1 | SHA256 | SHA384 | SHA512,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Async hashing class wraper
|
||||
/// </summary>
|
||||
public class Hasher
|
||||
{
|
||||
/// <summary>
|
||||
/// Hash type associated with the current state
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public Hash HashType { get; private set; }
|
||||
#else
|
||||
public Hash HashType { get; init; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Current hash in bytes
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public byte[] CurrentHashBytes
|
||||
#else
|
||||
public byte[]? CurrentHashBytes
|
||||
#endif
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET48
|
||||
switch (_hasher)
|
||||
{
|
||||
case HashAlgorithm ha:
|
||||
return ha.Hash;
|
||||
case NonCryptographicHashAlgorithm ncha:
|
||||
return ncha.GetCurrentHash().Reverse().ToArray();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
return (_hasher) switch
|
||||
{
|
||||
HashAlgorithm ha => ha.Hash,
|
||||
NonCryptographicHashAlgorithm ncha => ncha.GetCurrentHash().Reverse().ToArray(),
|
||||
_ => null,
|
||||
};
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current hash as a string
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public string CurrentHashString => ByteArrayToString(CurrentHashBytes);
|
||||
#else
|
||||
public string? CurrentHashString => ByteArrayToString(CurrentHashBytes);
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
/// <summary>
|
||||
/// Internal hasher being used for processing
|
||||
/// </summary>
|
||||
/// <remarks>May be either a HashAlgorithm or NonCryptographicHashAlgorithm</remarks>
|
||||
#if NET48
|
||||
private object _hasher;
|
||||
#else
|
||||
private object? _hasher;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="hashType">Hash type to instantiate</param>
|
||||
public Hasher(Hash hashType)
|
||||
{
|
||||
this.HashType = hashType;
|
||||
@@ -47,58 +96,256 @@ namespace MPF.Core.Hashing
|
||||
/// </summary>
|
||||
private void GetHasher()
|
||||
{
|
||||
#if NET48
|
||||
switch (HashType)
|
||||
{
|
||||
case Hash.CRC32:
|
||||
_hasher = new Crc32();
|
||||
break;
|
||||
|
||||
case Hash.CRC64:
|
||||
_hasher = new Crc64();
|
||||
break;
|
||||
case Hash.MD5:
|
||||
_hasher = MD5.Create();
|
||||
break;
|
||||
|
||||
case Hash.SHA1:
|
||||
_hasher = SHA1.Create();
|
||||
break;
|
||||
|
||||
case Hash.SHA256:
|
||||
_hasher = SHA256.Create();
|
||||
break;
|
||||
|
||||
case Hash.SHA384:
|
||||
_hasher = SHA384.Create();
|
||||
break;
|
||||
|
||||
case Hash.SHA512:
|
||||
_hasher = SHA512.Create();
|
||||
break;
|
||||
case Hash.XxHash32:
|
||||
_hasher = new XxHash32();
|
||||
break;
|
||||
case Hash.XxHash64:
|
||||
_hasher = new XxHash64();
|
||||
break;
|
||||
}
|
||||
#else
|
||||
_hasher = HashType switch
|
||||
{
|
||||
Hash.CRC32 => new Crc32(),
|
||||
Hash.CRC64 => new Crc64(),
|
||||
Hash.MD5 => MD5.Create(),
|
||||
Hash.SHA1 => SHA1.Create(),
|
||||
Hash.SHA256 => SHA256.Create(),
|
||||
Hash.SHA384 => SHA384.Create(),
|
||||
Hash.SHA512 => SHA512.Create(),
|
||||
Hash.XxHash32 => new XxHash32(),
|
||||
Hash.XxHash64 => new XxHash64(),
|
||||
_ => null,
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_hasher is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Hashing
|
||||
|
||||
/// <summary>
|
||||
/// Get hashes from an input file path
|
||||
/// </summary>
|
||||
/// <param name="filename">Path to the input file</param>
|
||||
/// <returns>True if hashing was successful, false otherwise</returns>
|
||||
#if NET48
|
||||
public static bool GetFileHashes(string filename, out long size, out string crc32, out string md5, out string sha1)
|
||||
#else
|
||||
public static bool GetFileHashes(string filename, out long size, out string? crc32, out string? md5, out string? sha1)
|
||||
#endif
|
||||
{
|
||||
// Set all initial values
|
||||
crc32 = null; md5 = null; sha1 = null;
|
||||
|
||||
// Get all file hashes
|
||||
var fileHashes = GetFileHashes(filename, out size);
|
||||
if (fileHashes == null)
|
||||
return false;
|
||||
|
||||
// Assign the file hashes and return
|
||||
crc32 = fileHashes[Hash.CRC32];
|
||||
md5 = fileHashes[Hash.MD5];
|
||||
sha1 = fileHashes[Hash.SHA1];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get hashes from an input file path
|
||||
/// </summary>
|
||||
/// <param name="filename">Path to the input file</param>
|
||||
/// <returns>Dictionary containing hashes on success, null on error</returns>
|
||||
#if NET48
|
||||
public static Dictionary<Hash, string> GetFileHashes(string filename, out long size)
|
||||
#else
|
||||
public static Dictionary<Hash, string?>? GetFileHashes(string filename, out long size)
|
||||
#endif
|
||||
{
|
||||
// If the file doesn't exist, we can't do anything
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
size = -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set the file size
|
||||
size = new FileInfo(filename).Length;
|
||||
|
||||
// Create the output dictionary
|
||||
#if NET48
|
||||
var hashDict = new Dictionary<Hash, string>();
|
||||
#else
|
||||
var hashDict = new Dictionary<Hash, string?>();
|
||||
#endif
|
||||
|
||||
// Open the input file
|
||||
var input = File.OpenRead(filename);
|
||||
|
||||
// Return the hashes from the stream
|
||||
return GetStreamHashes(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get hashes from an input Stream
|
||||
/// </summary>
|
||||
/// <param name="input">Stream to hash</param>
|
||||
/// <returns>Dictionary containing hashes on success, null on error</returns>
|
||||
#if NET48
|
||||
public static Dictionary<Hash, string> GetStreamHashes(Stream input)
|
||||
#else
|
||||
public static Dictionary<Hash, string?>? GetStreamHashes(Stream input)
|
||||
#endif
|
||||
{
|
||||
// Create the output dictionary
|
||||
#if NET48
|
||||
var hashDict = new Dictionary<Hash, string>();
|
||||
#else
|
||||
var hashDict = new Dictionary<Hash, string?>();
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
// Get a list of hashers to run over the buffer
|
||||
var hashers = new Dictionary<Hash, Hasher>
|
||||
{
|
||||
{ Hash.CRC32, new Hasher(Hash.CRC32) },
|
||||
{ Hash.CRC64, new Hasher(Hash.CRC64) },
|
||||
{ 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) },
|
||||
{ Hash.XxHash32, new Hasher(Hash.XxHash32) },
|
||||
{ Hash.XxHash64, new Hasher(Hash.XxHash64) },
|
||||
};
|
||||
|
||||
// Initialize the hashing helpers
|
||||
var loadBuffer = new ThreadLoadBuffer(input);
|
||||
int buffersize = 3 * 1024 * 1024;
|
||||
byte[] buffer0 = new byte[buffersize];
|
||||
byte[] buffer1 = new byte[buffersize];
|
||||
|
||||
/*
|
||||
Please note that some of the following code is adapted from
|
||||
RomVault. This is a modified version of how RomVault does
|
||||
threaded hashing. As such, some of the terminology and code
|
||||
is the same, though variable names and comments may have
|
||||
been tweaked to better fit this code base.
|
||||
*/
|
||||
|
||||
// Pre load the first buffer
|
||||
long refsize = input.Length;
|
||||
int next = refsize > buffersize ? buffersize : (int)refsize;
|
||||
input.Read(buffer0, 0, next);
|
||||
int current = next;
|
||||
refsize -= next;
|
||||
bool bufferSelect = true;
|
||||
|
||||
while (current > 0)
|
||||
{
|
||||
// Trigger the buffer load on the second buffer
|
||||
next = refsize > buffersize ? buffersize : (int)refsize;
|
||||
if (next > 0)
|
||||
loadBuffer.Trigger(bufferSelect ? buffer1 : buffer0, next);
|
||||
|
||||
byte[] buffer = bufferSelect ? buffer0 : buffer1;
|
||||
|
||||
// Run hashes in parallel
|
||||
Parallel.ForEach(hashers, h => h.Value.Process(buffer, current));
|
||||
|
||||
// Wait for the load buffer worker, if needed
|
||||
if (next > 0)
|
||||
loadBuffer.Wait();
|
||||
|
||||
// Setup for the next hashing step
|
||||
current = next;
|
||||
refsize -= next;
|
||||
bufferSelect = !bufferSelect;
|
||||
}
|
||||
|
||||
// Finalize all hashing helpers
|
||||
loadBuffer.Finish();
|
||||
Parallel.ForEach(hashers, h => h.Value.Terminate());
|
||||
|
||||
// Get the results
|
||||
hashDict[Hash.CRC32] = hashers[Hash.CRC32].CurrentHashString;
|
||||
hashDict[Hash.CRC64] = hashers[Hash.CRC64].CurrentHashString;
|
||||
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;
|
||||
hashDict[Hash.XxHash32] = hashers[Hash.XxHash32].CurrentHashString;
|
||||
hashDict[Hash.XxHash64] = hashers[Hash.XxHash64].CurrentHashString;
|
||||
hashDict[Hash.CRC64] = hashers[Hash.CRC64].CurrentHashString;
|
||||
|
||||
// Dispose of the hashers
|
||||
loadBuffer.Dispose();
|
||||
foreach (var hasher in hashers.Values)
|
||||
{
|
||||
hasher.Dispose();
|
||||
}
|
||||
|
||||
return hashDict;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
input.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hashing
|
||||
|
||||
/// <summary>
|
||||
/// Process a buffer of some length with the internal hash algorithm
|
||||
/// </summary>
|
||||
public void Process(byte[] buffer, int size)
|
||||
{
|
||||
switch (HashType)
|
||||
switch (_hasher)
|
||||
{
|
||||
case Hash.CRC32:
|
||||
var bufferSpan = new ReadOnlySpan<byte>(buffer, 0, size);
|
||||
(_hasher as NonCryptographicHashAlgorithm)?.Append(bufferSpan);
|
||||
case HashAlgorithm ha:
|
||||
ha.TransformBlock(buffer, 0, size, null, 0);
|
||||
break;
|
||||
|
||||
case Hash.MD5:
|
||||
case Hash.SHA1:
|
||||
case Hash.SHA256:
|
||||
case Hash.SHA384:
|
||||
case Hash.SHA512:
|
||||
(_hasher as HashAlgorithm)?.TransformBlock(buffer, 0, size, null, 0);
|
||||
case NonCryptographicHashAlgorithm ncha:
|
||||
var bufferSpan = new ReadOnlySpan<byte>(buffer, 0, size);
|
||||
ncha.Append(bufferSpan);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -106,68 +353,21 @@ namespace MPF.Core.Hashing
|
||||
/// <summary>
|
||||
/// Finalize the internal hash algorigthm
|
||||
/// </summary>
|
||||
/// <remarks>NonCryptographicHashAlgorithm implementations do not need finalization</remarks>
|
||||
public void Terminate()
|
||||
{
|
||||
byte[] emptyBuffer = Array.Empty<byte>();
|
||||
switch (HashType)
|
||||
switch (_hasher)
|
||||
{
|
||||
case Hash.CRC32:
|
||||
// No finalization is needed
|
||||
break;
|
||||
|
||||
case Hash.MD5:
|
||||
case Hash.SHA1:
|
||||
case Hash.SHA256:
|
||||
case Hash.SHA384:
|
||||
case Hash.SHA512:
|
||||
(_hasher as HashAlgorithm)?.TransformFinalBlock(emptyBuffer, 0, 0);
|
||||
case HashAlgorithm ha:
|
||||
ha.TransformFinalBlock(emptyBuffer, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get internal hash as a byte array
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public byte[] GetHash()
|
||||
#else
|
||||
public byte[]? GetHash()
|
||||
#endif
|
||||
{
|
||||
if (_hasher == null)
|
||||
return null;
|
||||
#endregion
|
||||
|
||||
switch (HashType)
|
||||
{
|
||||
case Hash.CRC32:
|
||||
return (_hasher as NonCryptographicHashAlgorithm)?.GetCurrentHash()?.Reverse().ToArray();
|
||||
|
||||
case Hash.MD5:
|
||||
case Hash.SHA1:
|
||||
case Hash.SHA256:
|
||||
case Hash.SHA384:
|
||||
case Hash.SHA512:
|
||||
return (_hasher as HashAlgorithm)?.Hash;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get internal hash as a string
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public string GetHashString()
|
||||
#else
|
||||
public string? GetHashString()
|
||||
#endif
|
||||
{
|
||||
var hash = GetHash();
|
||||
if (hash == null)
|
||||
return null;
|
||||
|
||||
return ByteArrayToString(hash);
|
||||
}
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Convert a byte array to a hex string
|
||||
@@ -195,5 +395,7 @@ namespace MPF.Core.Hashing
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Xml.Schema;
|
||||
using System.Xml.Serialization;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Core.Data;
|
||||
using MPF.Core.Hashing;
|
||||
using MPF.Core.Modules;
|
||||
using MPF.Core.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
@@ -195,115 +194,6 @@ namespace MPF.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get hashes from an input file path
|
||||
/// </summary>
|
||||
/// <param name="filename">Path to the input file</param>
|
||||
/// <returns>True if hashing was successful, false otherwise</returns>
|
||||
#if NET48
|
||||
internal static bool GetFileHashes(string filename, out long size, out string crc32, out string md5, out string sha1)
|
||||
#else
|
||||
internal static bool GetFileHashes(string filename, out long size, out string? crc32, out string? md5, out string? sha1)
|
||||
#endif
|
||||
{
|
||||
// Set all initial values
|
||||
size = -1; crc32 = null; md5 = null; sha1 = null;
|
||||
|
||||
// If the file doesn't exist, we can't do anything
|
||||
if (!File.Exists(filename))
|
||||
return false;
|
||||
|
||||
// Set the file size
|
||||
size = new FileInfo(filename).Length;
|
||||
|
||||
// Open the input file
|
||||
var input = File.OpenRead(filename);
|
||||
|
||||
try
|
||||
{
|
||||
// Get a list of hashers to run over the buffer
|
||||
var hashers = new List<Hasher>
|
||||
{
|
||||
new Hasher(Hash.CRC32),
|
||||
new Hasher(Hash.MD5),
|
||||
new Hasher(Hash.SHA1),
|
||||
new Hasher(Hash.SHA256),
|
||||
new Hasher(Hash.SHA384),
|
||||
new Hasher(Hash.SHA512),
|
||||
};
|
||||
|
||||
// Initialize the hashing helpers
|
||||
var loadBuffer = new ThreadLoadBuffer(input);
|
||||
int buffersize = 3 * 1024 * 1024;
|
||||
byte[] buffer0 = new byte[buffersize];
|
||||
byte[] buffer1 = new byte[buffersize];
|
||||
|
||||
/*
|
||||
Please note that some of the following code is adapted from
|
||||
RomVault. This is a modified version of how RomVault does
|
||||
threaded hashing. As such, some of the terminology and code
|
||||
is the same, though variable names and comments may have
|
||||
been tweaked to better fit this code base.
|
||||
*/
|
||||
|
||||
// Pre load the first buffer
|
||||
long refsize = size;
|
||||
int next = refsize > buffersize ? buffersize : (int)refsize;
|
||||
input.Read(buffer0, 0, next);
|
||||
int current = next;
|
||||
refsize -= next;
|
||||
bool bufferSelect = true;
|
||||
|
||||
while (current > 0)
|
||||
{
|
||||
// Trigger the buffer load on the second buffer
|
||||
next = refsize > buffersize ? buffersize : (int)refsize;
|
||||
if (next > 0)
|
||||
loadBuffer.Trigger(bufferSelect ? buffer1 : buffer0, next);
|
||||
|
||||
byte[] buffer = bufferSelect ? buffer0 : buffer1;
|
||||
|
||||
// Run hashes in parallel
|
||||
Parallel.ForEach(hashers, h => h.Process(buffer, current));
|
||||
|
||||
// Wait for the load buffer worker, if needed
|
||||
if (next > 0)
|
||||
loadBuffer.Wait();
|
||||
|
||||
// Setup for the next hashing step
|
||||
current = next;
|
||||
refsize -= next;
|
||||
bufferSelect = !bufferSelect;
|
||||
}
|
||||
|
||||
// Finalize all hashing helpers
|
||||
loadBuffer.Finish();
|
||||
Parallel.ForEach(hashers, h => h.Terminate());
|
||||
|
||||
// Get the results
|
||||
crc32 = hashers.First(h => h.HashType == Hash.CRC32).GetHashString();
|
||||
md5 = hashers.First(h => h.HashType == Hash.MD5).GetHashString();
|
||||
sha1 = hashers.First(h => h.HashType == Hash.SHA1).GetHashString();
|
||||
//sha256 = hashers.First(h => h.HashType == Hash.SHA256).GetHashString();
|
||||
//sha384 = hashers.First(h => h.HashType == Hash.SHA384).GetHashString();
|
||||
//sha512 = hashers.First(h => h.HashType == Hash.SHA512).GetHashString();
|
||||
|
||||
// Dispose of the hashers
|
||||
loadBuffer.Dispose();
|
||||
hashers.ForEach(h => h.Dispose());
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
input.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the last modified date from a file path, if possible
|
||||
/// </summary>
|
||||
@@ -396,7 +286,7 @@ namespace MPF.Core
|
||||
if (string.IsNullOrWhiteSpace(hashData))
|
||||
return false;
|
||||
|
||||
var hashreg = new Regex(@"<rom name="".*?"" size=""(.*?)"" crc=""(.*?)"" md5=""(.*?)"" sha1=""(.*?)""");
|
||||
var hashreg = new Regex(@"<rom name="".*?"" size=""(.*?)"" crc=""(.*?)"" md5=""(.*?)"" sha1=""(.*?)""", RegexOptions.Compiled);
|
||||
Match m = hashreg.Match(hashData);
|
||||
if (m.Success)
|
||||
{
|
||||
@@ -613,7 +503,7 @@ namespace MPF.Core
|
||||
// If we had any boot value, parse it and get the executable name
|
||||
if (!string.IsNullOrEmpty(bootValue))
|
||||
{
|
||||
var match = Regex.Match(bootValue, @"cdrom.?:\\?(.*)");
|
||||
var match = Regex.Match(bootValue, @"cdrom.?:\\?(.*)", RegexOptions.Compiled);
|
||||
if (match.Groups.Count > 1)
|
||||
{
|
||||
// EXE name may have a trailing `;` after
|
||||
@@ -1999,7 +1889,7 @@ namespace MPF.Core
|
||||
value = value.Replace(" ", "\t");
|
||||
|
||||
// Sanitize whitespace around tabs
|
||||
value = Regex.Replace(value, @"\s*\t\s*", "\t");
|
||||
value = Regex.Replace(value, @"\s*\t\s*", "\t", RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
// If the value contains a newline
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Copyright>Copyright (c)2019-2023</Copyright>
|
||||
<VersionPrefix>2.7.3</VersionPrefix>
|
||||
<VersionPrefix>2.7.4</VersionPrefix>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
|
||||
|
||||
@@ -1878,7 +1878,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();
|
||||
List<string> parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+")
|
||||
List<string> parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+", RegexOptions.Compiled)
|
||||
.Cast<Match>()
|
||||
.Select(m => m.Value)
|
||||
.ToList();
|
||||
|
||||
@@ -1196,6 +1196,51 @@ namespace MPF.Core.Modules
|
||||
return (value, factor);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods to Move
|
||||
|
||||
/// <summary>
|
||||
/// Get the hex contents of the PIC file
|
||||
/// </summary>
|
||||
/// <param name="picPath">Path to the PIC.bin file associated with the dump</param>
|
||||
/// <param name="trimLength">Number of characters to trim the PIC to, if -1, ignored</param>
|
||||
/// <returns>PIC data as a hex string if possible, null on error</returns>
|
||||
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
|
||||
#if NET48
|
||||
protected static string GetPIC(string picPath, int trimLength = -1)
|
||||
#else
|
||||
protected static string? GetPIC(string picPath, int trimLength = -1)
|
||||
#endif
|
||||
{
|
||||
// If the file doesn't exist, we can't get the info
|
||||
if (!File.Exists(picPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var hex = GetFullFile(picPath, true);
|
||||
if (hex == null)
|
||||
return null;
|
||||
|
||||
if (trimLength > -1)
|
||||
#if NET48
|
||||
hex = hex.Substring(0, trimLength);
|
||||
#else
|
||||
hex = hex[..trimLength];
|
||||
#endif
|
||||
|
||||
return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1101,7 +1101,7 @@ namespace MPF.Core.Modules.DiscImageCreator
|
||||
//if (File.Exists($"{basePath}_PFI.bin"))
|
||||
// info.Artifacts["pfi"] = Convert.ToBase64String(File.ReadAllBytes($"{basePath}_PFI.bin")) ?? string.Empty;
|
||||
//if (File.Exists($"{basePath}_PIC.bin"))
|
||||
// info.Artifacts["pfi"] = Convert.ToBase64String(File.ReadAllBytes($"{basePath}_PFI.bin")) ?? string.Empty;
|
||||
// info.Artifacts["pic"] = Convert.ToBase64String(File.ReadAllBytes($"{basePath}_PIC.bin")) ?? string.Empty;
|
||||
//if (File.Exists($"{basePath}_SS.bin"))
|
||||
// info.Artifacts["ss"] = Convert.ToBase64String(File.ReadAllBytes($"{basePath}_SS.bin")) ?? string.Empty;
|
||||
if (File.Exists($"{basePath}.sub"))
|
||||
@@ -2258,7 +2258,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();
|
||||
List<string> parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+")
|
||||
List<string> parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+", RegexOptions.Compiled)
|
||||
.Cast<Match>()
|
||||
.Select(m => m.Value)
|
||||
.ToList();
|
||||
@@ -3174,7 +3174,7 @@ namespace MPF.Core.Modules.DiscImageCreator
|
||||
// No keys
|
||||
if (line.Contains("No TitleKey"))
|
||||
{
|
||||
var match = Regex.Match(line, @"^LBA:\s*[0-9]+, Filename: (.*?), No TitleKey$");
|
||||
var match = Regex.Match(line, @"^LBA:\s*[0-9]+, Filename: (.*?), No TitleKey$", RegexOptions.Compiled);
|
||||
string matchedFilename = match.Groups[1].Value;
|
||||
if (matchedFilename.EndsWith(";1"))
|
||||
#if NET48
|
||||
@@ -3187,7 +3187,7 @@ namespace MPF.Core.Modules.DiscImageCreator
|
||||
}
|
||||
else
|
||||
{
|
||||
var match = Regex.Match(line, @"^LBA:\s*[0-9]+, Filename: (.*?), EncryptedTitleKey: .*?, DecryptedTitleKey: (.*?)$");
|
||||
var match = Regex.Match(line, @"^LBA:\s*[0-9]+, Filename: (.*?), EncryptedTitleKey: .*?, DecryptedTitleKey: (.*?)$", RegexOptions.Compiled);
|
||||
string matchedFilename = match.Groups[1].Value;
|
||||
if (matchedFilename.EndsWith(";1"))
|
||||
#if NET48
|
||||
@@ -3504,7 +3504,7 @@ namespace MPF.Core.Modules.DiscImageCreator
|
||||
return null;
|
||||
|
||||
// Create the required regex
|
||||
var trackLengthRegex = new Regex(@"^\s*.*?Track\s*([0-9]{1,2}), LBA\s*[0-9]{1,8} - \s*[0-9]{1,8}, Length\s*([0-9]{1,8})$");
|
||||
var trackLengthRegex = new Regex(@"^\s*.*?Track\s*([0-9]{1,2}), LBA\s*[0-9]{1,8} - \s*[0-9]{1,8}, Length\s*([0-9]{1,8})$", RegexOptions.Compiled);
|
||||
|
||||
// Read in the track length data
|
||||
var trackLengthMapping = new Dictionary<string, string>();
|
||||
@@ -3528,7 +3528,7 @@ namespace MPF.Core.Modules.DiscImageCreator
|
||||
return null;
|
||||
|
||||
// Create the required regex
|
||||
var trackSessionRegex = new Regex(@"^\s*Session\s*([0-9]{1,2}),.*?,\s*Track\s*([0-9]{1,2}).*?$");
|
||||
var trackSessionRegex = new Regex(@"^\s*Session\s*([0-9]{1,2}),.*?,\s*Track\s*([0-9]{1,2}).*?$", RegexOptions.Compiled);
|
||||
|
||||
// Read in the track session data
|
||||
var trackSessionMapping = new Dictionary<string, string>();
|
||||
@@ -3632,45 +3632,6 @@ namespace MPF.Core.Modules.DiscImageCreator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the hex contents of the PIC file
|
||||
/// </summary>
|
||||
/// <param name="picPath">Path to the PIC.bin file associated with the dump</param>
|
||||
/// <param name="trimLength">Number of characters to trim the PIC to, if -1, ignored</param>
|
||||
/// <returns>PIC data as a hex string if possible, null on error</returns>
|
||||
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
|
||||
#if NET48
|
||||
private static string GetPIC(string picPath, int trimLength = -1)
|
||||
#else
|
||||
private static string? GetPIC(string picPath, int trimLength = -1)
|
||||
#endif
|
||||
{
|
||||
// If the file doesn't exist, we can't get the info
|
||||
if (!File.Exists(picPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var hex = GetFullFile(picPath, true);
|
||||
if (hex == null)
|
||||
return null;
|
||||
|
||||
if (trimLength > -1)
|
||||
#if NET48
|
||||
hex = hex.Substring(0, trimLength);
|
||||
#else
|
||||
hex = hex[..trimLength];
|
||||
#endif
|
||||
|
||||
return Regex.Replace(hex, ".{32}", "$0\n");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the existence of an anti-modchip string from the input file, if possible
|
||||
/// </summary>
|
||||
@@ -4198,7 +4159,7 @@ namespace MPF.Core.Modules.DiscImageCreator
|
||||
// Set the flag so we don't read duplicate data
|
||||
foundSecuritySectors = true;
|
||||
|
||||
var layerRegex = new Regex(@"Layer [01].*, startLBA-endLBA:\s*(\d+)-\s*(\d+)");
|
||||
var layerRegex = new Regex(@"Layer [01].*, startLBA-endLBA:\s*(\d+)-\s*(\d+)", RegexOptions.Compiled);
|
||||
|
||||
line = sr.ReadLine()?.Trim();
|
||||
if (line == null)
|
||||
@@ -4292,7 +4253,7 @@ namespace MPF.Core.Modules.DiscImageCreator
|
||||
// Set the flag so we don't read duplicate data
|
||||
foundSecuritySectors = true;
|
||||
|
||||
var layerRegex = new Regex(@"Layer [01].*, startLBA-endLBA:\s*(\d+)-\s*(\d+)");
|
||||
var layerRegex = new Regex(@"Layer [01].*, startLBA-endLBA:\s*(\d+)-\s*(\d+)", RegexOptions.Compiled);
|
||||
|
||||
line = sr.ReadLine()?.Trim();
|
||||
if (line == null)
|
||||
|
||||
@@ -15,14 +15,6 @@ namespace MPF.Core.Modules.Redumper
|
||||
/// <summary>
|
||||
/// Represents a generic set of Redumper parameters
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: Redumper natively supports multiple, chained commands but MPF
|
||||
/// doesn't have an easy way of dealing with that right now. Maybe have
|
||||
/// BaseCommand be a space-deliminated list of the commands?
|
||||
/// TODO: With chained commands, how do we tell what parameters are
|
||||
/// actually supported? Do we go one by one and if it doesn't match
|
||||
/// any, then it's incorrect?
|
||||
/// </remarks>
|
||||
public class Parameters : BaseParameters
|
||||
{
|
||||
#region Generic Dumping Information
|
||||
@@ -275,7 +267,23 @@ namespace MPF.Core.Modules.Redumper
|
||||
missingFiles.Add($"{basePath}.dat");
|
||||
if (!File.Exists($"{basePath}.manufacturer") && !File.Exists($"{basePath}.1.manufacturer") && !File.Exists($"{basePath}.2.manufacturer"))
|
||||
missingFiles.Add($"{basePath}.manufacturer");
|
||||
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
|
||||
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
|
||||
missingFiles.Add($"{basePath}.physical");
|
||||
if (!File.Exists($"{basePath}.state"))
|
||||
missingFiles.Add($"{basePath}.state");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MediaType.HDDVD: // TODO: Verify that this is output
|
||||
case MediaType.BluRay:
|
||||
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
|
||||
{
|
||||
if (!File.Exists($"{basePath}.log"))
|
||||
missingFiles.Add($"{basePath}.log");
|
||||
else if (GetDatfile($"{basePath}.log") == null)
|
||||
missingFiles.Add($"{basePath}.dat");
|
||||
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
|
||||
missingFiles.Add($"{basePath}.physical");
|
||||
if (!File.Exists($"{basePath}.state"))
|
||||
missingFiles.Add($"{basePath}.state");
|
||||
@@ -376,6 +384,8 @@ namespace MPF.Core.Modules.Redumper
|
||||
break;
|
||||
|
||||
case MediaType.DVD:
|
||||
case MediaType.HDDVD:
|
||||
case MediaType.BluRay:
|
||||
#if NET48
|
||||
info.Extras.PVD = GetPVD($"{basePath}.log") ?? "Disc has no PVD";
|
||||
info.TracksAndWriteOffsets.ClrMameProData = GetDatfile($"{basePath}.log");
|
||||
@@ -397,12 +407,57 @@ namespace MPF.Core.Modules.Redumper
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
}
|
||||
|
||||
string layerbreak = GetLayerbreak($"{basePath}.log") ?? string.Empty;
|
||||
// Deal with the layerbreaks
|
||||
if (this.Type == MediaType.DVD)
|
||||
{
|
||||
string layerbreak = GetLayerbreak($"{basePath}.log") ?? string.Empty;
|
||||
#if NET48
|
||||
info.SizeAndChecksums.Layerbreak = !string.IsNullOrEmpty(layerbreak) ? Int64.Parse(layerbreak) : default;
|
||||
info.SizeAndChecksums.Layerbreak = !string.IsNullOrEmpty(layerbreak) ? Int64.Parse(layerbreak) : default;
|
||||
#else
|
||||
info.SizeAndChecksums!.Layerbreak = !string.IsNullOrEmpty(layerbreak) ? Int64.Parse(layerbreak) : default;
|
||||
info.SizeAndChecksums!.Layerbreak = !string.IsNullOrEmpty(layerbreak) ? Int64.Parse(layerbreak) : default;
|
||||
#endif
|
||||
}
|
||||
else if (this.Type == MediaType.BluRay)
|
||||
{
|
||||
var di = InfoTool.GetDiscInformation($"{basePath}.physical");
|
||||
#if NET48
|
||||
info.SizeAndChecksums.PICIdentifier = InfoTool.GetPICIdentifier(di);
|
||||
#else
|
||||
info.SizeAndChecksums!.PICIdentifier = InfoTool.GetPICIdentifier(di);
|
||||
#endif
|
||||
if (InfoTool.GetLayerbreaks(di, out long? layerbreak1, out long? layerbreak2, out long? layerbreak3))
|
||||
{
|
||||
if (layerbreak1 != null && layerbreak1 * 2048 < info.SizeAndChecksums.Size)
|
||||
info.SizeAndChecksums.Layerbreak = layerbreak1.Value;
|
||||
|
||||
if (layerbreak2 != null && layerbreak2 * 2048 < info.SizeAndChecksums.Size)
|
||||
info.SizeAndChecksums.Layerbreak2 = layerbreak2.Value;
|
||||
|
||||
if (layerbreak3 != null && layerbreak3 * 2048 < info.SizeAndChecksums.Size)
|
||||
info.SizeAndChecksums.Layerbreak3 = layerbreak3.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Bluray-specific options
|
||||
if (this.Type == MediaType.BluRay)
|
||||
{
|
||||
int trimLength = -1;
|
||||
switch (this.System)
|
||||
{
|
||||
case RedumpSystem.SonyPlayStation3:
|
||||
case RedumpSystem.SonyPlayStation4:
|
||||
case RedumpSystem.SonyPlayStation5:
|
||||
trimLength = 264;
|
||||
break;
|
||||
}
|
||||
|
||||
#if NET48
|
||||
info.Extras.PIC = GetPIC($"{basePath}.physical", trimLength) ?? string.Empty;
|
||||
#else
|
||||
info.Extras!.PIC = GetPIC($"{basePath}.physical", trimLength) ?? string.Empty;
|
||||
#endif
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -970,12 +1025,20 @@ namespace MPF.Core.Modules.Redumper
|
||||
case MediaType.DVD:
|
||||
if (File.Exists($"{basePath}.log"))
|
||||
logFiles.Add($"{basePath}.log");
|
||||
if (File.Exists($"{basePath}.manufacturer"))
|
||||
logFiles.Add($"{basePath}.manufacturer");
|
||||
if (File.Exists($"{basePath}.1.manufacturer"))
|
||||
logFiles.Add($"{basePath}.1.manufacturer");
|
||||
if (File.Exists($"{basePath}.2.manufacturer"))
|
||||
logFiles.Add($"{basePath}.2.manufacturer");
|
||||
if (File.Exists($"{basePath}.physical"))
|
||||
logFiles.Add($"{basePath}.physical");
|
||||
if (File.Exists($"{basePath}.1.physical"))
|
||||
logFiles.Add($"{basePath}.1.physical");
|
||||
if (File.Exists($"{basePath}.2.physical"))
|
||||
logFiles.Add($"{basePath}.2.physical");
|
||||
if (File.Exists($"{basePath}.state"))
|
||||
logFiles.Add($"{basePath}.state");
|
||||
break;
|
||||
|
||||
case MediaType.HDDVD: // TODO: Confirm that this information outputs
|
||||
case MediaType.BluRay:
|
||||
if (File.Exists($"{basePath}.log"))
|
||||
logFiles.Add($"{basePath}.log");
|
||||
if (File.Exists($"{basePath}.physical"))
|
||||
logFiles.Add($"{basePath}.physical");
|
||||
if (File.Exists($"{basePath}.1.physical"))
|
||||
@@ -1044,9 +1107,14 @@ namespace MPF.Core.Modules.Redumper
|
||||
protected override void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Options options)
|
||||
#endif
|
||||
{
|
||||
// If we don't have a CD or DVD, we can't dump using redumper
|
||||
if (this.Type != MediaType.CDROM && this.Type != MediaType.DVD)
|
||||
// If we don't have a CD, DVD, HD-DVD, or BD, we can't dump using redumper
|
||||
if (this.Type != MediaType.CDROM
|
||||
&& this.Type != MediaType.DVD
|
||||
&& this.Type != MediaType.HDDVD
|
||||
&& this.Type != MediaType.BluRay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BaseCommand = CommandStrings.NONE;
|
||||
switch (this.Type)
|
||||
@@ -1073,6 +1141,9 @@ namespace MPF.Core.Modules.Redumper
|
||||
case MediaType.DVD:
|
||||
ModeValues = new List<string> { CommandStrings.DVD };
|
||||
break;
|
||||
case MediaType.HDDVD: // TODO: Keep in sync if another command string shows up
|
||||
ModeValues = new List<string> { CommandStrings.DVD };
|
||||
break;
|
||||
case MediaType.BluRay:
|
||||
ModeValues = new List<string> { CommandStrings.BluRay };
|
||||
break;
|
||||
@@ -1141,7 +1212,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();
|
||||
List<string> parts = Regex.Matches(parameters, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+")
|
||||
List<string> parts = Regex.Matches(parameters, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+", RegexOptions.Compiled)
|
||||
.Cast<Match>()
|
||||
.Select(m => m.Value)
|
||||
.ToList();
|
||||
@@ -1534,7 +1605,7 @@ namespace MPF.Core.Modules.Redumper
|
||||
line = sr.ReadLine()?.Trim();
|
||||
while (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
var match = Regex.Match(line, @"^(.*?): (.*?)$");
|
||||
var match = Regex.Match(line, @"^(.*?): (.*?)$", RegexOptions.Compiled);
|
||||
if (match.Success)
|
||||
{
|
||||
string normalizedKey = match.Groups[2].Value.Replace(':', ' ');
|
||||
@@ -1632,6 +1703,7 @@ namespace MPF.Core.Modules.Redumper
|
||||
/// </summary>
|
||||
/// <param name="log">Log file location</param>
|
||||
/// <returns>Layerbreak if possible, null on error</returns>
|
||||
/// <remarks>TODO: Support multiple layerbreaks when added</remarks>
|
||||
#if NET48
|
||||
private static string GetLayerbreak(string log)
|
||||
#else
|
||||
@@ -2404,9 +2476,9 @@ namespace MPF.Core.Modules.Redumper
|
||||
|
||||
// Generate regex
|
||||
// Permissive
|
||||
var regex = new Regex(@"^redumper (v.+) \[.+\]");
|
||||
var regex = new Regex(@"^redumper (v.+) \[.+\]", RegexOptions.Compiled);
|
||||
// Strict
|
||||
//var regex = new Regex(@"^redumper (v\d{4}\.\d{2}\.\d{2}(| build_\d+)) \[.+\]");
|
||||
//var regex = new Regex(@"^redumper (v\d{4}\.\d{2}\.\d{2}(| build_\d+)) \[.+\]", RegexOptions.Compiled);
|
||||
|
||||
// Extract the version string
|
||||
var match = regex.Match(sr.ReadLine()?.Trim() ?? string.Empty);
|
||||
@@ -2448,7 +2520,7 @@ namespace MPF.Core.Modules.Redumper
|
||||
|
||||
// If we find the hardware info line, return each value
|
||||
// drive: <vendor_id> - <product_id> (revision level: <product_revision_level>, vendor specific: <vendor_specific>)
|
||||
var regex = new Regex(@"drive: (.+) - (.+) \(revision level: (.+), vendor specific: (.+)\)");
|
||||
var regex = new Regex(@"drive: (.+) - (.+) \(revision level: (.+), vendor specific: (.+)\)", RegexOptions.Compiled);
|
||||
|
||||
#if NET48
|
||||
string line;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using MPF.Core.Converters;
|
||||
using MPF.Core.Data;
|
||||
using MPF.Core.Hashing;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Core.Modules.UmdImageCreator
|
||||
@@ -96,7 +97,7 @@ namespace MPF.Core.Modules.UmdImageCreator
|
||||
info.Extras!.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
|
||||
#endif
|
||||
|
||||
if (InfoTool.GetFileHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
|
||||
if (Hasher.GetFileHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
|
||||
{
|
||||
#if NET48
|
||||
info.SizeAndChecksums.Size = filesize;
|
||||
|
||||
@@ -4,8 +4,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner.Protection;
|
||||
using BinaryObjectScanner;
|
||||
using BinaryObjectScanner.Protection;
|
||||
using psxt001z;
|
||||
|
||||
namespace MPF.Core
|
||||
@@ -162,7 +162,7 @@ namespace MPF.Core
|
||||
foundProtections = foundProtections.Where(p => p != "ActiveMARK");
|
||||
|
||||
// Cactus Data Shield
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"Cactus Data Shield [0-9]{3} .+")) && foundProtections.Any(p => p == "Cactus Data Shield 200"))
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"Cactus Data Shield [0-9]{3} .+", RegexOptions.Compiled)) && foundProtections.Any(p => p == "Cactus Data Shield 200"))
|
||||
foundProtections = foundProtections.Where(p => p != "Cactus Data Shield 200");
|
||||
|
||||
// CD-Check
|
||||
@@ -192,7 +192,7 @@ namespace MPF.Core
|
||||
// JoWood X-Prot
|
||||
if (foundProtections.Any(p => p.StartsWith("JoWood X-Prot")))
|
||||
{
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"JoWood X-Prot [0-9]\.[0-9]\.[0-9]\.[0-9]{2}")))
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"JoWood X-Prot [0-9]\.[0-9]\.[0-9]\.[0-9]{2}", RegexOptions.Compiled)))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "JoWood X-Prot")
|
||||
.Where(p => p != "JoWood X-Prot v1.0-v1.3")
|
||||
@@ -241,28 +241,28 @@ namespace MPF.Core
|
||||
// SafeDisc
|
||||
if (foundProtections.Any(p => p.StartsWith("SafeDisc")))
|
||||
{
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}")))
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}")))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+")))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+");
|
||||
}
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}")))
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+")))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+");
|
||||
}
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+")))
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
@@ -302,7 +302,7 @@ namespace MPF.Core
|
||||
// StarForce
|
||||
if (foundProtections.Any(p => p.StartsWith("StarForce")))
|
||||
{
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"StarForce [0-9]+\..+")))
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"StarForce [0-9]+\..+", RegexOptions.Compiled)))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "StarForce")
|
||||
.Where(p => p != "StarForce 3-5")
|
||||
|
||||
@@ -14,6 +14,8 @@ using Newtonsoft.Json;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
#pragma warning disable IDE0051 // Remove unused private members
|
||||
|
||||
namespace MPF.Core
|
||||
{
|
||||
/// <summary>
|
||||
@@ -1134,7 +1136,7 @@ namespace MPF.Core
|
||||
.Replace("</div>", string.Empty)
|
||||
.Replace("[+]", string.Empty)
|
||||
.ReplaceHtmlWithSiteCodes();
|
||||
oldComments = Regex.Replace(oldComments, @"<div .*?>", string.Empty);
|
||||
oldComments = Regex.Replace(oldComments, @"<div .*?>", string.Empty, RegexOptions.Compiled);
|
||||
|
||||
// Create state variables
|
||||
bool addToLast = false;
|
||||
@@ -1267,7 +1269,7 @@ namespace MPF.Core
|
||||
.Replace("</div>", string.Empty)
|
||||
.Replace("[+]", string.Empty)
|
||||
.ReplaceHtmlWithSiteCodes();
|
||||
oldContents = Regex.Replace(oldContents, @"<div .*?>", string.Empty);
|
||||
oldContents = Regex.Replace(oldContents, @"<div .*?>", string.Empty, RegexOptions.Compiled);
|
||||
|
||||
// Create state variables
|
||||
bool addToLast = false;
|
||||
|
||||
@@ -7,8 +7,8 @@ using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Core.Converters;
|
||||
using MPF.Core.Data;
|
||||
using MPF.Core.Utilities;
|
||||
using MPF.Core.UI.ComboBoxItems;
|
||||
using MPF.Core.Utilities;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Core.UI.ViewModels
|
||||
@@ -1563,28 +1563,51 @@ namespace MPF.Core.UI.ViewModels
|
||||
VerboseLogLn($"Supported media speeds: {string.Join(", ", this.DriveSpeeds)}");
|
||||
|
||||
// Set the selected speed
|
||||
#if NET48
|
||||
int speed;
|
||||
switch (this.CurrentMediaType)
|
||||
switch (CurrentMediaType)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
case MediaType.GDROM:
|
||||
speed = this.Options.PreferredDumpSpeedCD;
|
||||
speed = Options.PreferredDumpSpeedCD;
|
||||
break;
|
||||
case MediaType.DVD:
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
speed = this.Options.PreferredDumpSpeedDVD;
|
||||
speed = Options.PreferredDumpSpeedDVD;
|
||||
break;
|
||||
case MediaType.HDDVD:
|
||||
speed = this.Options.PreferredDumpSpeedHDDVD;
|
||||
speed = Options.PreferredDumpSpeedHDDVD;
|
||||
break;
|
||||
case MediaType.BluRay:
|
||||
speed = this.Options.PreferredDumpSpeedBD;
|
||||
speed = Options.PreferredDumpSpeedBD;
|
||||
break;
|
||||
default:
|
||||
speed = this.Options.PreferredDumpSpeedCD;
|
||||
speed = Options.PreferredDumpSpeedCD;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
int speed = (CurrentMediaType) switch
|
||||
{
|
||||
// CD dump speed
|
||||
MediaType.CDROM => Options.PreferredDumpSpeedCD,
|
||||
MediaType.GDROM => Options.PreferredDumpSpeedCD,
|
||||
|
||||
// DVD dump speed
|
||||
MediaType.DVD => Options.PreferredDumpSpeedDVD,
|
||||
MediaType.NintendoGameCubeGameDisc => Options.PreferredDumpSpeedDVD,
|
||||
MediaType.NintendoWiiOpticalDisc => Options.PreferredDumpSpeedDVD,
|
||||
|
||||
// HD-DVD dump speed
|
||||
MediaType.HDDVD => Options.PreferredDumpSpeedHDDVD,
|
||||
|
||||
// BD dump speed
|
||||
MediaType.BluRay => Options.PreferredDumpSpeedBD,
|
||||
|
||||
// Default
|
||||
_ => Options.PreferredDumpSpeedCD,
|
||||
};
|
||||
#endif
|
||||
|
||||
VerboseLogLn($"Setting drive speed to: {speed}");
|
||||
this.DriveSpeed = speed;
|
||||
|
||||
@@ -164,9 +164,11 @@ namespace MPF.Core.Utilities
|
||||
switch (type)
|
||||
{
|
||||
// Formats considered at least partially dumpable by Redumper
|
||||
case MediaType.BluRay:
|
||||
case MediaType.CDROM:
|
||||
case MediaType.DVD:
|
||||
case MediaType.GDROM:
|
||||
case MediaType.HDDVD:
|
||||
return true;
|
||||
|
||||
// All other formats considered unsupported
|
||||
@@ -200,44 +202,46 @@ namespace MPF.Core.Utilities
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
switch (program)
|
||||
return (program) switch
|
||||
{
|
||||
case InternalProgram.Redumper:
|
||||
return type switch
|
||||
{
|
||||
// Formats considered at least partially supported by Redumper
|
||||
MediaType.CDROM
|
||||
or MediaType.DVD
|
||||
or MediaType.GDROM => true,
|
||||
// Aaru
|
||||
InternalProgram.Aaru when type == MediaType.BluRay => true,
|
||||
InternalProgram.Aaru when type == MediaType.CDROM => true,
|
||||
InternalProgram.Aaru when type == MediaType.CompactFlash => true,
|
||||
InternalProgram.Aaru when type == MediaType.DVD => true,
|
||||
InternalProgram.Aaru when type == MediaType.GDROM => true,
|
||||
InternalProgram.Aaru when type == MediaType.FlashDrive => true,
|
||||
InternalProgram.Aaru when type == MediaType.FloppyDisk => true,
|
||||
InternalProgram.Aaru when type == MediaType.HardDisk => true,
|
||||
InternalProgram.Aaru when type == MediaType.HDDVD => true,
|
||||
InternalProgram.Aaru when type == MediaType.NintendoGameCubeGameDisc => true,
|
||||
InternalProgram.Aaru when type == MediaType.NintendoWiiOpticalDisc => true,
|
||||
InternalProgram.Aaru when type == MediaType.SDCard => true,
|
||||
|
||||
// All other formats considered unsupported
|
||||
_ => false,
|
||||
};
|
||||
case InternalProgram.Aaru:
|
||||
case InternalProgram.DiscImageCreator:
|
||||
return type switch
|
||||
{
|
||||
// Formats considered at least partially supported by MPF
|
||||
MediaType.BluRay
|
||||
or MediaType.CDROM
|
||||
or MediaType.DVD
|
||||
or MediaType.GDROM
|
||||
or MediaType.FloppyDisk
|
||||
or MediaType.CompactFlash
|
||||
or MediaType.SDCard
|
||||
or MediaType.FlashDrive
|
||||
or MediaType.HardDisk
|
||||
or MediaType.HDDVD
|
||||
or MediaType.NintendoGameCubeGameDisc
|
||||
or MediaType.NintendoWiiOpticalDisc => true,
|
||||
// DiscImageCreator
|
||||
InternalProgram.DiscImageCreator when type == MediaType.BluRay => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.CDROM => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.CompactFlash => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.DVD => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.GDROM => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.FlashDrive => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.FloppyDisk => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.HardDisk => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.HDDVD => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.NintendoGameCubeGameDisc => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.NintendoWiiOpticalDisc => true,
|
||||
InternalProgram.DiscImageCreator when type == MediaType.SDCard => true,
|
||||
|
||||
// All other formats considered unsupported
|
||||
_ => false,
|
||||
};
|
||||
// All other InternalPrograms are not supported for dumping
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
// Redumper
|
||||
InternalProgram.Redumper when type == MediaType.BluRay => true,
|
||||
InternalProgram.Redumper when type == MediaType.CDROM => true,
|
||||
InternalProgram.Redumper when type == MediaType.DVD => true,
|
||||
InternalProgram.Redumper when type == MediaType.GDROM => true,
|
||||
InternalProgram.Redumper when type == MediaType.HDDVD => true,
|
||||
|
||||
// Default
|
||||
_ => false,
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using psxt001z;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace WPFCustomMessageBox
|
||||
ShowActivated = true;
|
||||
ShowInTaskbar = true;
|
||||
|
||||
if (owner != null)
|
||||
if (owner != null && owner.IsLoaded)
|
||||
{
|
||||
Owner = owner;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<UseWPF>true</UseWPF>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Copyright>Copyright (c)2019-2023</Copyright>
|
||||
<VersionPrefix>2.7.3</VersionPrefix>
|
||||
<VersionPrefix>2.7.4</VersionPrefix>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<Description>Frontend for various dumping programs</Description>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Copyright>Copyright (c)2019-2023</Copyright>
|
||||
<VersionPrefix>2.7.3</VersionPrefix>
|
||||
<VersionPrefix>2.7.4</VersionPrefix>
|
||||
<InternalsVisibleTo>MPF.Test</InternalsVisibleTo>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# version format
|
||||
version: 2.7.3-{build}
|
||||
version: 2.7.4-{build}
|
||||
|
||||
# pull request template
|
||||
pull_requests:
|
||||
@@ -56,8 +56,8 @@ after_build:
|
||||
- 7z e DiscImageCreator_20230606.zip -oMPF\bin\Debug\net6.0-windows\win-x64\publish\Programs\Creator Release_ANSI\*
|
||||
|
||||
# Redumper
|
||||
- ps: appveyor DownloadFile https://github.com/superg/redumper/releases/download/build_230/redumper-2023.10.14_build230-win64.zip
|
||||
- 7z e redumper-2023.10.14_build230-win64.zip -oMPF\bin\Debug\net6.0-windows\win-x64\publish\Programs\Redumper redumper-2023.10.14_build230-win64\bin\*
|
||||
- ps: appveyor DownloadFile https://github.com/superg/redumper/releases/download/build_246/redumper-2023.10.31_build246-win64.zip
|
||||
- 7z e redumper-2023.10.31_build246-win64.zip -oMPF\bin\Debug\net6.0-windows\win-x64\publish\Programs\Redumper redumper-2023.10.31_build246-win64\bin\*
|
||||
|
||||
# Create MPF Debug archives
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\MPF\bin\Debug\net6.0-windows\win-x64\publish\
|
||||
|
||||
Reference in New Issue
Block a user