diff --git a/.github/workflows/build_cli.yml b/.github/workflows/build_cli.yml
new file mode 100644
index 00000000..7dc74639
--- /dev/null
+++ b/.github/workflows/build_cli.yml
@@ -0,0 +1,50 @@
+name: MPF CLI
+
+on:
+ push:
+ branches: [ "master" ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ project: [MPF.CLI]
+ runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64]
+ framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c Debug --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
+
+ - name: Archive build
+ run: zip -r ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip ${{ matrix.project }}/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
+
+ - name: Upload build
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug
+ path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
+
+ - name: Upload to rolling
+ uses: ncipollo/release-action@v1.14.0
+ with:
+ allowUpdates: True
+ artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
+ body: 'Last built commit: ${{ github.sha }}'
+ name: 'Rolling Release'
+ prerelease: True
+ replacesArtifacts: True
+ tag: "rolling"
+ updateOnlyUnreleased: True
diff --git a/CHANGELIST.md b/CHANGELIST.md
index e529bb6f..3bc78695 100644
--- a/CHANGELIST.md
+++ b/CHANGELIST.md
@@ -15,6 +15,7 @@
- Purple
- Separate themes into own namespace and files
- Seal all theme classes
+- Add preliminary MPF.CLI
### 3.2.0 (2024-06-20)
diff --git a/MPF.CLI/ConsoleLogger.cs b/MPF.CLI/ConsoleLogger.cs
new file mode 100644
index 00000000..0b070c1c
--- /dev/null
+++ b/MPF.CLI/ConsoleLogger.cs
@@ -0,0 +1,25 @@
+using System;
+using BinaryObjectScanner;
+using MPF.Frontend;
+
+namespace MPF.CLI
+{
+ public static class ConsoleLogger
+ {
+ ///
+ /// Simple process counter to write to console
+ ///
+ public static void ProgressUpdated(object? sender, ResultEventArgs value)
+ {
+ Console.WriteLine(value.Message);
+ }
+
+ ///
+ /// Simple process counter to write to console
+ ///
+ public static void ProgressUpdated(object? sender, ProtectionProgress value)
+ {
+ Console.WriteLine($"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}");
+ }
+ }
+}
diff --git a/MPF.CLI/MPF.CLI.csproj b/MPF.CLI/MPF.CLI.csproj
new file mode 100644
index 00000000..85eefe5d
--- /dev/null
+++ b/MPF.CLI/MPF.CLI.csproj
@@ -0,0 +1,54 @@
+
+
+
+
+ net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0
+ Exe
+ false
+ false
+ latest
+ enable
+ true
+ true
+ 3.2.0
+
+
+ MPF CLI
+ Matt Nadareski;ReignStumble;Jakz
+ CLI frontend for various dumping programs
+ Copyright (c) Matt Nadareski 2019-2024
+ https://github.com/SabreTools/
+ https://github.com/SabreTools/MPF
+ git
+
+
+
+
+ win-x86;win-x64
+
+
+ win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64
+
+
+ win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64
+
+
+ net6.0;net7.0;net8.0
+
+
+
+
+
+
+
+
+ runtime; compile; build; native; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MPF.CLI/Program.cs b/MPF.CLI/Program.cs
new file mode 100644
index 00000000..b0d6ba45
--- /dev/null
+++ b/MPF.CLI/Program.cs
@@ -0,0 +1,167 @@
+using System;
+using System.IO;
+using BinaryObjectScanner;
+using MPF.Frontend;
+using MPF.Frontend.Tools;
+using SabreTools.RedumpLib.Data;
+using SabreTools.RedumpLib.Web;
+
+namespace MPF.CLI
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ // Try processing the standalone arguments
+ bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
+ if (standaloneProcessed != false)
+ {
+ if (standaloneProcessed == null)
+ DisplayHelp();
+ return;
+ }
+
+ // Check for the minimum number of arguments
+ if (args.Length < 5)
+ {
+ DisplayHelp("Not enough arguments have been provided, exiting...");
+ return;
+ }
+
+ // Try processing the common arguments
+ (bool success, MediaType mediaType, RedumpSystem? knownSystem, var error) = OptionsLoader.ProcessCommonArguments(args);
+ if (!success)
+ {
+ DisplayHelp(error);
+ return;
+ }
+
+ // Load options from the config file
+ var options = OptionsLoader.LoadFromConfig();
+ if (options.FirstRun)
+ {
+ // Application paths
+ options.AaruPath = "FILL ME IN";
+ options.DiscImageCreatorPath = "FILL ME IN";
+ options.RedumperPath = "FILL ME IN";
+ options.InternalProgram = InternalProgram.NONE;
+
+ // Reset first run
+ options.FirstRun = false;
+ OptionsLoader.SaveToConfig(options);
+ }
+
+ // Validate the internal program
+ switch (options.InternalProgram)
+ {
+ case InternalProgram.Aaru:
+ if (!File.Exists(options.AaruPath))
+ {
+ DisplayHelp("A path needs to be supplied for Aaru, exiting...");
+ return;
+ }
+ break;
+
+ case InternalProgram.DiscImageCreator:
+ if (!File.Exists(options.DiscImageCreatorPath))
+ {
+ DisplayHelp("A path needs to be supplied for DIC, exiting...");
+ return;
+ }
+ break;
+
+ case InternalProgram.Redumper:
+ if (!File.Exists(options.RedumperPath))
+ {
+ DisplayHelp("A path needs to be supplied for Redumper, exiting...");
+ return;
+ }
+ break;
+
+ default:
+ DisplayHelp($"{options.InternalProgram} is not a supported dumping program, exiting...");
+ break;
+ }
+
+ // Make new Progress objects
+ var resultProgress = new Progress();
+ resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
+ var protectionProgress = new Progress();
+ protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
+
+ // Validate the supplied credentials
+#if NETFRAMEWORK
+ (bool? _, string? message) = RedumpWebClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty);
+#else
+ (bool? _, string? message) = RedumpHttpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).ConfigureAwait(false).GetAwaiter().GetResult();
+#endif
+ if (!string.IsNullOrEmpty(message))
+ Console.WriteLine(message);
+
+ // Get the explicit output options
+ string path = args[2].Trim('"');
+ string speedStr = args[3].Trim('"');
+ if (!int.TryParse(speedStr, out int speed))
+ speed = 8; // Reasonable default for most media types
+ string filepath = args[4].Trim('"');
+
+ // TODO: Additional arguments get passed to dumping program?
+ // - Should it be an explicit option, like --params?
+ // - Should it be that everything after is treated like an addition to default?
+ // - Should it be that everything after is a replacement of default?
+ // - Two separate options for replace and add params?
+
+ // Now populate an environment
+ var drive = Drive.Create(null, path);
+ var env = new DumpEnvironment(options, filepath, drive, knownSystem, mediaType, options.InternalProgram, parameters: null);
+ string? paramStr = env.GetFullParameters(speed);
+ if (string.IsNullOrEmpty(paramStr))
+ {
+ DisplayHelp("No valid environment could be created, exiting...");
+ return;
+ }
+ env.SetExecutionContext(paramStr);
+
+ // Invoke the dumping program
+#if NET40
+ var dumpResult = env.Run(resultProgress);
+#else
+ var dumpResult = env.Run(resultProgress).GetAwaiter().GetResult();
+#endif
+ Console.WriteLine(dumpResult.Message);
+ if (!dumpResult)
+ return;
+
+ // Finally, attempt to do the output dance
+#if NET40
+ var verifyTask = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress);
+ verifyTask.Wait();
+ var verifyResult = verifyTask.Result;
+#else
+ var verifyResult = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult();
+#endif
+ Console.WriteLine(verifyResult.Message);
+ }
+
+ ///
+ /// Display help for MPF.CLI
+ ///
+ /// Error string to prefix the help text with
+ private static void DisplayHelp(string? error = null)
+ {
+ if (error != null)
+ Console.WriteLine(error);
+
+ Console.WriteLine("Usage:");
+ Console.WriteLine("MPF.CLI ");
+ Console.WriteLine();
+ Console.WriteLine("Standalone Options:");
+ Console.WriteLine("-h, -? Show this help text");
+ Console.WriteLine("-lc, --listcodes List supported comment/content site codes");
+ Console.WriteLine("-lm, --listmedia List supported media types");
+ Console.WriteLine("-ls, --listsystems List supported system types");
+ Console.WriteLine("-lp, --listprograms List supported dumping program outputs");
+ Console.WriteLine();
+ }
+ }
+}
diff --git a/MPF.Check/Program.cs b/MPF.Check/Program.cs
index f79a4852..68ffecad 100644
--- a/MPF.Check/Program.cs
+++ b/MPF.Check/Program.cs
@@ -94,7 +94,7 @@ namespace MPF.Check
Console.WriteLine(error);
Console.WriteLine("Usage:");
- Console.WriteLine("MPF.Check.exe [options] ...");
+ Console.WriteLine("MPF.Check [options] ...");
Console.WriteLine();
Console.WriteLine("Standalone Options:");
Console.WriteLine("-h, -? Show this help text");
diff --git a/MPF.sln b/MPF.sln
index 604fdb98..35cdd371 100644
--- a/MPF.sln
+++ b/MPF.sln
@@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPF.Processors", "MPF.Proce
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPF.ExecutionContexts", "MPF.ExecutionContexts\MPF.ExecutionContexts.csproj", "{75609610-CE85-486E-8F42-9525A52B80BF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MPF.CLI", "MPF.CLI\MPF.CLI.csproj", "{A825771B-98AC-44A5-8076-C9AB3D469751}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +70,10 @@ Global
{75609610-CE85-486E-8F42-9525A52B80BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75609610-CE85-486E-8F42-9525A52B80BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75609610-CE85-486E-8F42-9525A52B80BF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A825771B-98AC-44A5-8076-C9AB3D469751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A825771B-98AC-44A5-8076-C9AB3D469751}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A825771B-98AC-44A5-8076-C9AB3D469751}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A825771B-98AC-44A5-8076-C9AB3D469751}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/publish-nix.sh b/publish-nix.sh
index 17ee9eeb..a7db4d93 100755
--- a/publish-nix.sh
+++ b/publish-nix.sh
@@ -117,6 +117,45 @@ if [ $NO_BUILD = false ]; then
done
done
+ # Build CLI
+ for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"; do
+ for RUNTIME in "${CHECK_RUNTIMES[@]}"; do
+ # Output the current build
+ echo "===== Build CLI - $FRAMEWORK, $RUNTIME ====="
+
+ # If we have an invalid combination of framework and runtime
+ if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
+ if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
+ echo "Skipped due to invalid combination"
+ continue
+ fi
+ fi
+
+ # If we have Apple silicon but an unsupported framework
+ if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
+ if [ $RUNTIME = "osx-arm64" ]; then
+ echo "Skipped due to no Apple Silicon support"
+ continue
+ fi
+ fi
+
+ # Only .NET 5 and above can publish to a single file
+ if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]; then
+ # Only include Debug if building all
+ if [ $USE_ALL = true ]; then
+ dotnet publish MPF.CLI/MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
+ fi
+ dotnet publish MPF.CLI/MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
+ else
+ # Only include Debug if building all
+ if [ $USE_ALL = true ]; then
+ dotnet publish MPF.CLI/MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
+ fi
+ dotnet publish MPF.CLI/MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
+ fi
+ done
+ done
+
# Build Check
for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"; do
for RUNTIME in "${CHECK_RUNTIMES[@]}"; do
@@ -199,6 +238,38 @@ if [ $NO_ARCHIVE = false ]; then
done
done
+ # Create CLI archives
+ for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"; do
+ for RUNTIME in "${CHECK_RUNTIMES[@]}"; do
+ # Output the current build
+ echo "===== Archive CLI - $FRAMEWORK, $RUNTIME ====="
+
+ # If we have an invalid combination of framework and runtime
+ if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
+ if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
+ echo "Skipped due to invalid combination"
+ continue
+ fi
+ fi
+
+ # If we have Apple silicon but an unsupported framework
+ if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
+ if [ $RUNTIME = "osx-arm64" ]; then
+ echo "Skipped due to no Apple Silicon support"
+ continue
+ fi
+ fi
+
+ # Only include Debug if building all
+ if [ $USE_ALL = true ]; then
+ cd $BUILD_FOLDER/MPF.CLI/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/
+ zip -r $BUILD_FOLDER/MPF.CLI_${FRAMEWORK}_${RUNTIME}_debug.zip .
+ fi
+ cd $BUILD_FOLDER/MPF.CLI/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/
+ zip -r $BUILD_FOLDER/MPF.CLI_${FRAMEWORK}_${RUNTIME}_release.zip .
+ done
+ done
+
# Create Check archives
for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"; do
for RUNTIME in "${CHECK_RUNTIMES[@]}"; do
diff --git a/publish-win.ps1 b/publish-win.ps1
index e42192aa..bf5b2f5e 100644
--- a/publish-win.ps1
+++ b/publish-win.ps1
@@ -105,6 +105,42 @@ if (!$NO_BUILD.IsPresent) {
}
}
+ # Build CLI
+ foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) {
+ foreach ($RUNTIME in $CHECK_RUNTIMES) {
+ # Output the current build
+ Write-Host "===== Build CLI - $FRAMEWORK, $RUNTIME ====="
+
+ # If we have an invalid combination of framework and runtime
+ if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
+ Write-Host "Skipped due to invalid combination"
+ continue
+ }
+
+ # If we have Apple silicon but an unsupported framework
+ if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
+ Write-Host "Skipped due to no Apple Silicon support"
+ continue
+ }
+
+ # Only .NET 5 and above can publish to a single file
+ if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) {
+ # Only include Debug if building all
+ if ($USE_ALL.IsPresent) {
+ dotnet publish MPF.CLI\MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
+ }
+ dotnet publish MPF.CLI\MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
+ }
+ else {
+ # Only include Debug if building all
+ if ($USE_ALL.IsPresent) {
+ dotnet publish MPF.CLI\MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
+ }
+ dotnet publish MPF.CLI\MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
+ }
+ }
+ }
+
# Build Check
foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) {
foreach ($RUNTIME in $CHECK_RUNTIMES) {
@@ -183,6 +219,34 @@ if (!$NO_ARCHIVE.IsPresent) {
}
}
+ # Create CLI archives
+ foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) {
+ foreach ($RUNTIME in $CHECK_RUNTIMES) {
+ # Output the current build
+ Write-Host "===== Archive CLI - $FRAMEWORK, $RUNTIME ====="
+
+ # If we have an invalid combination of framework and runtime
+ if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
+ Write-Host "Skipped due to invalid combination"
+ continue
+ }
+
+ # If we have Apple silicon but an unsupported framework
+ if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
+ Write-Host "Skipped due to no Apple Silicon support"
+ continue
+ }
+
+ # Only include Debug if building all
+ if ($USE_ALL.IsPresent) {
+ Set-Location -Path $BUILD_FOLDER\MPF.CLI\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
+ 7z a -tzip $BUILD_FOLDER\MPF.CLI_${FRAMEWORK}_${RUNTIME}_debug.zip *
+ }
+ Set-Location -Path $BUILD_FOLDER\MPF.CLI\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
+ 7z a -tzip $BUILD_FOLDER\MPF.CLI_${FRAMEWORK}_${RUNTIME}_release.zip *
+ }
+ }
+
# Create Check archives
foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) {
foreach ($RUNTIME in $CHECK_RUNTIMES) {