- Channel radio (Release / Master (Instabil)); master installs the rolling 'master' release from OmniNX/OmniNX-Master. Preselected from release_channel in the card's manifest.ini. - Variant, RAM (8gb) and Hekate-Pro choice are prefilled from the card (manifest.ini / config.ini) like the payload does, instead of silently switching users back to 4 GB / Standard. - Clear read-only attributes before deleting folders. - Remove leftover "OmniNX <Variant>" pack folders after installing. - Create empty folders from the pack. - Save every run's log to %LocalAppData%\OmniNX-Desktop-Installer\logs, plus a "Save log" button.
213 lines
8.1 KiB
C#
213 lines
8.1 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);
|
|
}
|
|
|
|
await Task.Run(() => RemovePackFoldersOnDrive(driveRoot, 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);
|
|
CopyFolderIfExists(staging, driveRoot, "hwtest", 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", "hwtest", "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}");
|
|
}
|
|
}
|
|
|
|
// Same as the payload: pack folders copied to the card for the console installer
|
|
// ("OmniNX Light/Standard/OC") are not needed after a desktop install.
|
|
private static void RemovePackFoldersOnDrive(string driveRoot, IProgress<InstallProgressReport> progress)
|
|
{
|
|
foreach (var variant in new[] { OmniNxVariant.Light, OmniNxVariant.Standard, OmniNxVariant.Oc })
|
|
{
|
|
var folder = VariantHelper.GetStagingFolderName(variant);
|
|
var path = Path.Combine(PathUtils.NormalizeDriveRoot(driveRoot), folder);
|
|
if (!Directory.Exists(path))
|
|
continue;
|
|
|
|
Report(progress, $"Removing leftover pack folder: {folder}");
|
|
try
|
|
{
|
|
PathUtils.DeletePath(path);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Report(progress, $" Could not remove {folder}: {ex.Message}", isError: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|