This lets mass deployments and modchip setups apply the same install/update flow from a PC, with generated deletion lists kept in sync with the payload headers.
187 lines
7.0 KiB
C#
187 lines
7.0 KiB
C#
using OmniNX.DesktopInstaller.Models;
|
|
|
|
namespace OmniNX.DesktopInstaller.Services;
|
|
|
|
public sealed class InstallEngine
|
|
{
|
|
private readonly DeletionService _deletion = new();
|
|
private readonly BackupService _backup = new();
|
|
private readonly ExtrasService _extras = new();
|
|
|
|
public async Task RunAsync(
|
|
InstallOptions options,
|
|
IProgress<InstallProgressReport> progress,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var driveRoot = PathUtils.NormalizeDriveRoot(options.DriveRoot);
|
|
var staging = options.StagingPath
|
|
?? throw new InvalidOperationException("Staging path is required.");
|
|
|
|
if (!Directory.Exists(staging))
|
|
throw new DirectoryNotFoundException($"Staging path not found: {staging}");
|
|
|
|
Report(progress, $"Install mode: {options.Mode}");
|
|
Report(progress, $"Pack variant: {VariantHelper.GetDisplayName(options.Variant)}");
|
|
|
|
if (options.Mode == InstallMode.Update)
|
|
{
|
|
await RunUpdateAsync(driveRoot, staging, options, progress, cancellationToken);
|
|
}
|
|
else
|
|
{
|
|
await RunCleanAsync(driveRoot, staging, options, progress, cancellationToken);
|
|
}
|
|
|
|
Report(progress, options.Mode == InstallMode.Update
|
|
? "Update completed successfully."
|
|
: "Installation completed successfully.");
|
|
}
|
|
|
|
private async Task RunUpdateAsync(
|
|
string driveRoot,
|
|
string staging,
|
|
InstallOptions options,
|
|
IProgress<InstallProgressReport> progress,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (options.Variant == OmniNxVariant.Oc)
|
|
{
|
|
Report(progress, "HorizonOC: backing up config.ini...");
|
|
_backup.BackupHorizonOcConfig(driveRoot, StringProgress(progress));
|
|
}
|
|
|
|
Report(progress, "Step 1: Cleanup...");
|
|
await Task.Run(() => _deletion.RunUpdateDeletion(driveRoot, StringProgress(progress)), cancellationToken);
|
|
|
|
Report(progress, "Step 2: Copying files...");
|
|
await Task.Run(() => CopyPackFiles(staging, driveRoot, options.Variant, progress), cancellationToken);
|
|
|
|
Report(progress, "Applying RAM / Hekate-AMS extras...");
|
|
await Task.Run(() => _extras.ApplyExtras(driveRoot, options.Use8GbRam, options.UseHekateAmsPro,
|
|
StringProgress(progress)), cancellationToken);
|
|
|
|
if (options.Variant == OmniNxVariant.Oc)
|
|
{
|
|
Report(progress, "HorizonOC: restoring config.ini...");
|
|
await Task.Run(() =>
|
|
{
|
|
_backup.RestoreHorizonOcConfig(driveRoot, StringProgress(progress));
|
|
_backup.CleanupHorizonOcBackup(driveRoot);
|
|
}, cancellationToken);
|
|
}
|
|
}
|
|
|
|
private async Task RunCleanAsync(
|
|
string driveRoot,
|
|
string staging,
|
|
InstallOptions options,
|
|
IProgress<InstallProgressReport> progress,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
Report(progress, "Step 1: Backing up user data...");
|
|
await Task.Run(() => _backup.BackupUserData(driveRoot, StringProgress(progress)), cancellationToken);
|
|
|
|
Report(progress, "Step 2: Cleaning old installation...");
|
|
await Task.Run(() => _deletion.RunCleanDeletion(driveRoot, StringProgress(progress)), cancellationToken);
|
|
|
|
Report(progress, "Step 3: Restoring user data...");
|
|
await Task.Run(() =>
|
|
{
|
|
_backup.RestoreUserData(driveRoot, StringProgress(progress));
|
|
_deletion.RunPostRestoreCleanup(driveRoot);
|
|
_backup.CleanupBackup(driveRoot);
|
|
}, cancellationToken);
|
|
|
|
Report(progress, "Step 4: Copying files...");
|
|
await Task.Run(() => CopyPackFiles(staging, driveRoot, options.Variant, progress), cancellationToken);
|
|
|
|
Report(progress, "Applying RAM / Hekate-AMS extras...");
|
|
await Task.Run(() => _extras.ApplyExtras(driveRoot, options.Use8GbRam, options.UseHekateAmsPro,
|
|
StringProgress(progress)), cancellationToken);
|
|
}
|
|
|
|
private static void CopyPackFiles(
|
|
string staging,
|
|
string driveRoot,
|
|
OmniNxVariant variant,
|
|
IProgress<InstallProgressReport> progress)
|
|
{
|
|
CopyFolderIfExists(staging, driveRoot, "atmosphere", progress);
|
|
CopyFolderIfExists(staging, driveRoot, "bootloader", progress);
|
|
CopyFolderIfExists(staging, driveRoot, "config", progress);
|
|
CopyFolderIfExists(staging, driveRoot, "switch", progress);
|
|
CopyFolderIfExists(staging, driveRoot, "themes", progress);
|
|
CopyFolderIfExists(staging, driveRoot, "warmboot_mariko", progress);
|
|
|
|
if (variant == OmniNxVariant.Oc)
|
|
CopyFolderIfExists(staging, driveRoot, "SaltySD", progress);
|
|
|
|
CopyFolderIfExists(staging, driveRoot, "extras", progress);
|
|
CopyRootFiles(staging, driveRoot, progress);
|
|
}
|
|
|
|
private static void CopyFolderIfExists(
|
|
string staging,
|
|
string driveRoot,
|
|
string folder,
|
|
IProgress<InstallProgressReport> progress)
|
|
{
|
|
var src = Path.Combine(staging, folder);
|
|
if (!Directory.Exists(src))
|
|
return;
|
|
|
|
var dst = Path.Combine(PathUtils.NormalizeDriveRoot(driveRoot), folder);
|
|
Report(progress, $"Copying {folder}/...");
|
|
PathUtils.CopyDirectory(src, dst, new Progress<(int current, int total, string file)>(copyProgress =>
|
|
{
|
|
var (current, total, file) = copyProgress;
|
|
var pct = total > 0 ? current * 100 / total : 0;
|
|
progress.Report(new InstallProgressReport
|
|
{
|
|
Message = $" {folder}/ {file}",
|
|
Percent = pct,
|
|
});
|
|
}));
|
|
}
|
|
|
|
private static void CopyRootFiles(
|
|
string staging,
|
|
string driveRoot,
|
|
IProgress<InstallProgressReport> progress)
|
|
{
|
|
Report(progress, "Copying root files...");
|
|
var skipDirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"atmosphere", "bootloader", "config", "switch", "themes",
|
|
"warmboot_mariko", "SaltySD", "extras",
|
|
};
|
|
|
|
foreach (var file in Directory.GetFiles(staging))
|
|
{
|
|
var name = Path.GetFileName(file);
|
|
if (name.StartsWith('.'))
|
|
continue;
|
|
|
|
var dst = Path.Combine(PathUtils.NormalizeDriveRoot(driveRoot), name);
|
|
PathUtils.CopyFile(file, dst);
|
|
Report(progress, $" {name}");
|
|
}
|
|
|
|
foreach (var dir in Directory.GetDirectories(staging))
|
|
{
|
|
var name = Path.GetFileName(dir);
|
|
if (skipDirs.Contains(name))
|
|
continue;
|
|
Report(progress, $"Skipping unexpected directory at pack root: {name}");
|
|
}
|
|
}
|
|
|
|
private static void Report(IProgress<InstallProgressReport> progress, string message, bool isError = false)
|
|
{
|
|
progress.Report(new InstallProgressReport { Message = message, IsError = isError });
|
|
}
|
|
|
|
private static IProgress<string> StringProgress(IProgress<InstallProgressReport> progress) =>
|
|
new Progress<string>(msg => Report(progress, msg));
|
|
}
|