mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 17:16:47 -04:00
c0ed2bfbeb
the idea behind this is C# code that is OS agnostic can go in WiiCompiled.Setup.Common to be shared by any OS specific code (eg: WiiCompiled.Setup.Windows and WiiCompiled.Setup.Linux).
192 lines
8.9 KiB
C#
192 lines
8.9 KiB
C#
namespace WiiCompiled.Setup.Windows;
|
|
|
|
internal enum AppMode
|
|
{
|
|
Help,
|
|
SilentInstall,
|
|
VerifyInputs,
|
|
Uninstall,
|
|
SilentUninstall,
|
|
UninstallWorker,
|
|
SelfTest,
|
|
LaunchBase,
|
|
LaunchRetro,
|
|
CheckProducts,
|
|
RepairProducts,
|
|
Version,
|
|
EmitPayloadIdentities
|
|
}
|
|
|
|
/// <summary>
|
|
/// What each mode accepts. Every cross-flag rule below is expressed here once instead of as a
|
|
/// per-mode exception list, so adding a mode cannot silently inherit another mode's inputs.
|
|
/// </summary>
|
|
internal sealed record ModeRules
|
|
{
|
|
public string Flag { get; init; } = "this command";
|
|
public bool AcceptsProgressJson { get; init; }
|
|
public bool AcceptsGame { get; init; }
|
|
public bool RequiresGame { get; init; }
|
|
public bool AcceptsRetroDirectory { get; init; }
|
|
public bool RequiresRetroDirectory { get; init; }
|
|
public bool AcceptsPayloadMode { get; init; }
|
|
public bool RequiresInstallDirectory { get; init; }
|
|
public bool RequiresPayloadRoot { get; init; }
|
|
public bool AcceptsPortable { get; init; }
|
|
}
|
|
|
|
internal sealed class CommandLine
|
|
{
|
|
public AppMode Mode { get; private set; } = AppMode.Help;
|
|
public string? GamePath { get; private set; }
|
|
public string? RetroDirectoryPath { get; private set; }
|
|
public RetroWfcPayloadMode RetroWfcPayloadMode { get; private set; }
|
|
public string? InstallDirectory { get; private set; }
|
|
public bool Quiet { get; private set; }
|
|
public bool ProgressJson { get; private set; }
|
|
public string? PayloadRootPath { get; private set; }
|
|
public bool Portable { get; private set; }
|
|
|
|
public static bool WantsProgressJson(string[] args) =>
|
|
args.Any(argument => argument.Equals("--progress-json", StringComparison.OrdinalIgnoreCase));
|
|
|
|
public static CommandLine Parse(string[] args)
|
|
{
|
|
var result = new CommandLine();
|
|
for (var i = 0; i < args.Length; i++)
|
|
{
|
|
switch (args[i].ToLowerInvariant())
|
|
{
|
|
case "--version": result.Mode = AppMode.Version; break;
|
|
case "--silent": result.Mode = AppMode.SilentInstall; break;
|
|
case "--verify-inputs": result.Mode = AppMode.VerifyInputs; break;
|
|
case "--uninstall": result.Mode = AppMode.Uninstall; break;
|
|
case "--silent-uninstall": result.Mode = AppMode.SilentUninstall; result.Quiet = true; break;
|
|
case "--uninstall-worker": result.Mode = AppMode.UninstallWorker; break;
|
|
case "--self-test": result.Mode = AppMode.SelfTest; break;
|
|
case "--launch-base": result.Mode = AppMode.LaunchBase; break;
|
|
case "--launch-retro": result.Mode = AppMode.LaunchRetro; break;
|
|
case "--check-products": result.Mode = AppMode.CheckProducts; break;
|
|
case "--repair-products": result.Mode = AppMode.RepairProducts; break;
|
|
case "--portable": result.Portable = true; break;
|
|
case "--emit-payload-identities": result.Mode = AppMode.EmitPayloadIdentities; break;
|
|
case "--payload-root": result.PayloadRootPath = RequireValue(args, ref i); break;
|
|
case "--quiet": result.Quiet = true; break;
|
|
case "--progress-json": result.ProgressJson = true; break;
|
|
case "--game": result.GamePath = RequireValue(args, ref i); break;
|
|
case "--retro-dir": result.RetroDirectoryPath = RequireValue(args, ref i); break;
|
|
case "--download-retro-wfc-payload":
|
|
if (result.RetroWfcPayloadMode != RetroWfcPayloadMode.NotApplicable)
|
|
throw new ArgumentException("Choose only one Retro-WFC payload mode.");
|
|
result.RetroWfcPayloadMode = RetroWfcPayloadMode.Online;
|
|
break;
|
|
case "--skip-retro-wfc-payload":
|
|
if (result.RetroWfcPayloadMode != RetroWfcPayloadMode.NotApplicable)
|
|
throw new ArgumentException("Choose only one Retro-WFC payload mode.");
|
|
result.RetroWfcPayloadMode = RetroWfcPayloadMode.Skipped;
|
|
break;
|
|
case "--install-dir": result.InstallDirectory = RequireValue(args, ref i); break;
|
|
default:
|
|
throw new ArgumentException($"Unknown CLI option: {args[i]}");
|
|
}
|
|
}
|
|
|
|
result.Validate();
|
|
return result;
|
|
}
|
|
|
|
private static readonly Dictionary<AppMode, ModeRules> Rules = new()
|
|
{
|
|
[AppMode.SilentInstall] = new ModeRules
|
|
{
|
|
Flag = "--silent",
|
|
AcceptsProgressJson = true, AcceptsGame = true, RequiresGame = true,
|
|
AcceptsRetroDirectory = true, AcceptsPayloadMode = true,
|
|
AcceptsPortable = true
|
|
},
|
|
[AppMode.VerifyInputs] = new ModeRules
|
|
{
|
|
Flag = "--verify-inputs",
|
|
AcceptsProgressJson = true, AcceptsGame = true, RequiresGame = true,
|
|
AcceptsRetroDirectory = true
|
|
},
|
|
[AppMode.CheckProducts] = new ModeRules
|
|
{
|
|
Flag = "--check-products", AcceptsProgressJson = true, AcceptsRetroDirectory = true
|
|
},
|
|
[AppMode.RepairProducts] = new ModeRules
|
|
{
|
|
Flag = "--repair-products",
|
|
AcceptsProgressJson = true, AcceptsRetroDirectory = true, RequiresRetroDirectory = true,
|
|
AcceptsPayloadMode = true, RequiresInstallDirectory = true
|
|
},
|
|
[AppMode.LaunchBase] = new ModeRules { Flag = "--launch-base" },
|
|
[AppMode.LaunchRetro] = new ModeRules { Flag = "--launch-retro" },
|
|
[AppMode.Uninstall] = new ModeRules { Flag = "--uninstall", RequiresInstallDirectory = true },
|
|
[AppMode.SilentUninstall] = new ModeRules
|
|
{
|
|
Flag = "--silent-uninstall", RequiresInstallDirectory = true
|
|
},
|
|
[AppMode.UninstallWorker] = new ModeRules
|
|
{
|
|
Flag = "--uninstall-worker", RequiresInstallDirectory = true
|
|
},
|
|
[AppMode.EmitPayloadIdentities] = new ModeRules
|
|
{
|
|
Flag = "--emit-payload-identities", RequiresPayloadRoot = true
|
|
}
|
|
};
|
|
|
|
private void Validate()
|
|
{
|
|
var rules = Rules.GetValueOrDefault(Mode) ?? new ModeRules();
|
|
|
|
void Reject(bool present, string option)
|
|
{
|
|
if (present) throw new ArgumentException($"{option} is not valid with {rules.Flag}.");
|
|
}
|
|
|
|
Reject(GamePath is not null && !rules.AcceptsGame, "--game");
|
|
Reject(RetroDirectoryPath is not null && !rules.AcceptsRetroDirectory, "--retro-dir");
|
|
Reject(ProgressJson && !rules.AcceptsProgressJson, "--progress-json");
|
|
Reject(PayloadRootPath is not null && !rules.RequiresPayloadRoot, "--payload-root");
|
|
Reject(Portable && !rules.AcceptsPortable, "--portable");
|
|
Reject(RetroWfcPayloadMode != RetroWfcPayloadMode.NotApplicable && !rules.AcceptsPayloadMode,
|
|
"A Retro-WFC payload option");
|
|
|
|
if (rules.RequiresGame && string.IsNullOrWhiteSpace(GamePath))
|
|
throw new ArgumentException("--game is required.");
|
|
if (rules.RequiresRetroDirectory && string.IsNullOrWhiteSpace(RetroDirectoryPath))
|
|
throw new ArgumentException(
|
|
$"--retro-dir is required with {rules.Flag}. Supply Wheel Wizard's canonical Retro Rewind folder.");
|
|
if (rules.RequiresPayloadRoot && string.IsNullOrWhiteSpace(PayloadRootPath))
|
|
throw new ArgumentException($"--payload-root is required with {rules.Flag}.");
|
|
|
|
// A portable installation has no default location so the caller must state where that root is.
|
|
if (Portable && string.IsNullOrWhiteSpace(InstallDirectory))
|
|
throw new ArgumentException(
|
|
"--install-dir is required with --portable. Its parent folder becomes the portable root.");
|
|
if (Mode == AppMode.SilentInstall && string.IsNullOrWhiteSpace(InstallDirectory))
|
|
InstallDirectory = ProductInfo.DefaultInstallDirectory;
|
|
if (rules.RequiresInstallDirectory && string.IsNullOrWhiteSpace(InstallDirectory))
|
|
throw new ArgumentException($"--install-dir is required with {rules.Flag}.");
|
|
|
|
// The Retro Rewind source and a payload decision are a pair. a base-only operation
|
|
// has no payload to decide.
|
|
if (rules.AcceptsPayloadMode && RetroDirectoryPath is not null &&
|
|
RetroWfcPayloadMode == RetroWfcPayloadMode.NotApplicable)
|
|
throw new ArgumentException(
|
|
"Retro Rewind requires --download-retro-wfc-payload or --skip-retro-wfc-payload.");
|
|
if (RetroDirectoryPath is null && RetroWfcPayloadMode != RetroWfcPayloadMode.NotApplicable)
|
|
throw new ArgumentException(
|
|
"--retro-dir is required when selecting a Retro-WFC payload option.");
|
|
}
|
|
|
|
private static string RequireValue(string[] args, ref int index)
|
|
{
|
|
if (++index >= args.Length || string.IsNullOrWhiteSpace(args[index]))
|
|
throw new ArgumentException($"A value is required after {args[index - 1]}.");
|
|
return args[index];
|
|
}
|
|
}
|