Files
OmniNX-Installer-Payload/desktop/OmniNX.DesktopInstaller/Services/InstallEngine.cs
T
niklascfw d573f6cca4 Add a Windows desktop installer for PC-based OmniNX installs.
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.
2026-09-13 21:10:58 +02:00

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));
}