From 1168a7b578f65bfa49bd6ba18816e687c0ad14f3 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sat, 10 May 2025 20:00:27 +0200 Subject: [PATCH 01/19] Slow start --- .../CustomDistributionSingletonService.cs | 16 + .../CustomDistributionsExtentions.cs | 10 + .../CustomDistributions/IDistribution.cs | 35 ++ .../CustomDistributions/RetroRewind.cs | 362 ++++++++++++++++++ WheelWizard/SetupExtensions.cs | 2 + WheelWizard/WheelWizard.csproj | 4 + 6 files changed, 429 insertions(+) create mode 100644 WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs create mode 100644 WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs create mode 100644 WheelWizard/Features/CustomDistributions/IDistribution.cs create mode 100644 WheelWizard/Features/CustomDistributions/RetroRewind.cs diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs new file mode 100644 index 00000000..81bbeb70 --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs @@ -0,0 +1,16 @@ +namespace WheelWizard.CustomDistributions; + +public interface ICustomDistributionSingletonService +{ + List GetDistributions(); +} + +public class CustomDistributionSingletonService : ICustomDistributionSingletonService +{ + readonly RetroRewind _retroRewind = new(); + + public List GetDistributions() + { + return [_retroRewind]; + } +} diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs new file mode 100644 index 00000000..861cd638 --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs @@ -0,0 +1,10 @@ +namespace WheelWizard.CustomDistributions; + +public static class CustomDistributionsExtentions +{ + public static IServiceCollection AddCustomDistributionService(this IServiceCollection services) + { + services.AddSingleton(); + return services; + } +} diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs new file mode 100644 index 00000000..06474da3 --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -0,0 +1,35 @@ +using Semver; +using WheelWizard.Models.Enums; + +namespace WheelWizard.CustomDistributions; + +public interface IDistribution +{ + /// + /// The title of the given distribution. + /// + public string Title { get; } + + /// + /// The name of the primary folder where the distribution is installed within the wheelwizard folder. + /// + string FolderName { get; } + + /// + /// Install the distribution. + /// + Task Install(); + + /// + /// Update the distribution. + /// + Task Update(); + + Task Remove(); + + WheelWizardStatus GetCurrentStatus(); + + SemVersion? GetCurrentVersion(); + + +} diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs new file mode 100644 index 00000000..206a3fa7 --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -0,0 +1,362 @@ +using System.IO.Compression; +using System.Text.RegularExpressions; +using Semver; +using WheelWizard.Helpers; +using WheelWizard.Models.Enums; +using WheelWizard.Resources.Languages; +using WheelWizard.Services; +using WheelWizard.Views.Popups.Generic; + +namespace WheelWizard.CustomDistributions; + +public class RetroRewind : IDistribution +{ + public string Title => "Retro Rewind"; + + // Keep in mind, whenever we download update files from the server, they are actually 1 folder higher, so it contains this folder. + public string FolderName => "RetroRewind6"; + + + public Task Install() + { + throw new NotImplementedException(); + } + + private static async Task> IsRRUpToDate(SemVersion currentVersion) + { + var latestVersionResult = await LatestServerVersion(); + if (latestVersionResult.IsFailure) + return "Failed to check for updates"; + var latestVersion = latestVersionResult.Value; + var isUpToDate = currentVersion.ComparePrecedenceTo(latestVersion) >= 0; + return isUpToDate; + } + + private static async Task> LatestServerVersion() + { + var response = await HttpClientHelper.GetAsync(Endpoints.RRVersionUrl); + if (response.Succeeded && response.Content != null) + return SemVersion.Parse(response.Content); + return "Failed to check for updates"; + } + + public Task Update() + { + try + { + var currentVersion = GetCurrentVersion(); + if (currentVersion == null) + return Install(); + + if (await IsRRUpToDate(currentVersion)) + { + return true; + } + + //if current version is below 3.2.6 we need to do a full reinstall + if (currentVersion.ComparePrecedenceTo("3.2.6") < 0) + { + var removeResult = await Remove(); + if (removeResult.IsFailure) + return removeResult; + + return await Install(); + } + return await ApplyUpdates(currentVersion); + } + catch (Exception e) + { + AbortingUpdate($"Reason: {e.Message}"); + return false; + } + } + + private static async Task ApplyUpdates(SemVersion currentVersion) + { + var allVersions = await GetAllVersionData(); + var updatesToApply = GetUpdatesToApply(currentVersion, allVersions); + + // todo: This progressbar should not be here in this context, this makes this untestable + var progressWindow = new ProgressWindow(Phrases.PopupText_UpdateRR); + progressWindow.Show(); + + // Step 1: Get the version we are updating to + var targetVersion = updatesToApply.Any() ? updatesToApply.Last().Version : currentVersion; + + // Step 2: Apply file deletions for versions between current and targetVersion + var deleteSuccess = await ApplyFileDeletionsBetweenVersions(currentVersion, targetVersion); + if (deleteSuccess.IsFailure) + { + progressWindow.Close(); + return (Phrases.PopupText_FailedUpdateDelete); + } + + // Step 3: Download and apply the updates (if any) + for (var i = 0; i < updatesToApply.Count; i++) + { + var update = updatesToApply[i]; + + var success = await DownloadAndApplyUpdate(update, updatesToApply.Count, i + 1, progressWindow); + if (success.IsFailure) + { + progressWindow.Close(); + return(Phrases.PopupText_FailedUpdateApply); + } + + // Update the version file after each successful update + UpdateVersionFile(update.Version); + } + + progressWindow.Close(); + return Ok(); + } + + private static void UpdateVersionFile(SemVersion newVersion) + { + var versionFilePath = Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); + File.WriteAllText(versionFilePath, newVersion.ToString()); + } + + + private static async Task DownloadAndApplyUpdate(UpdateData update, int totalUpdates, int currentUpdateIndex, ProgressWindow popupWindow) + { + var tempZipPath = Path.GetTempFileName(); + try + { + popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); + var finalFile = await DownloadHelper.DownloadToLocationAsync(update.Url, tempZipPath, popupWindow); + + popupWindow.UpdateProgress(100); + popupWindow.SetExtraText(Common.State_Extracting); + var destinationDirectoryPath = PathManager.RiivolutionWhWzFolderPath; + Directory.CreateDirectory(destinationDirectoryPath); + ExtractZipFile(finalFile, destinationDirectoryPath); + if (File.Exists(finalFile)) + File.Delete(finalFile); + } + finally + { + if (File.Exists(tempZipPath)) + File.Delete(tempZipPath); + } + + return Ok(); + } + + private static OperationResult ExtractZipFile(string path, string destinationDirectory) + { + using var archive = ZipFile.OpenRead(path); + + // Absolute path of the destination directory + var absoluteDestinationPath = Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); + + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith("desktop.ini", StringComparison.OrdinalIgnoreCase)) + continue; // Skip the desktop.ini file + + // Get the full path of the file + var destinationPath = Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); + + // Check for directory traversal attacks + if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) + { + return("The file path is outside the destination directory. Please contact the developers."); + } + + // If the entry is a directory, create it + if (entry.FullName.EndsWith(Path.AltDirectorySeparatorChar)) + { + Directory.CreateDirectory(destinationPath); + continue; + } + + // Create directory if it doesn't exist + var directoryName = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(directoryName)) + Directory.CreateDirectory(directoryName); + + // Extract the file + entry.ExtractToFile(destinationPath, overwrite: true); + } + + return Ok(); + } + + private static async Task ApplyFileDeletionsBetweenVersions(SemVersion currentVersion, SemVersion targetVersion) + { + try + { + var deleteListResult = await GetFileDeletionList(); + if (deleteListResult.IsFailure) + { + return "Failed to get file deletion list"; + } + var deleteList = deleteListResult.Value; + var deletionsToApply = GetDeletionsToApply(currentVersion, targetVersion, deleteList); + + foreach (var file in deletionsToApply) + { + var absoluteDestinationPath = Path.GetFullPath(PathManager.RiivolutionWhWzFolderPath + Path.AltDirectorySeparatorChar); + var filePath = Path.GetFullPath(Path.Combine(absoluteDestinationPath, file.Path.TrimStart('/'))); + //because we are actually getting the path from the server, + //we need to make sure we are not getting hacked, so we check if the path is in the riivolution folder + var resolvedPath = Path.GetFullPath(new FileInfo(filePath).FullName); + if ( + !resolvedPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) + || !filePath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) + || file.Path.Contains("..") + ) + { + return "Invalid file path detected. Please contact the developers.\n Server error: " + resolvedPath; + } + + if (File.Exists(filePath)) + File.Delete(filePath); + else if (Directory.Exists(filePath)) + Directory.Delete(filePath, recursive: true); + } + + return Ok(); + } + catch (Exception e) + { + return($"Failed to delete files: {e.Message}"); + } + } + + private struct DeletionData + { + public SemVersion Version; + public string Path; + } + private static async Task>> GetFileDeletionList() + { + var deleteList = new List(); + + using var httpClient = new HttpClient(); + var deleteListText = await httpClient.GetStringAsync(Endpoints.RRVersionDeleteUrl); + var lines = deleteListText.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var parts = line.Split(' ', 2); + if (parts.Length < 2) + continue; + var deletionVersion = parts[0].Trim(); + var path = parts[1].Trim(); + if (string.IsNullOrWhiteSpace(deletionVersion) || string.IsNullOrWhiteSpace(path)) + continue; + if (!SemVersion.TryParse(deletionVersion, out var parsedVersion)) + return "Failed to parse version"; + var deletionData = new DeletionData + { + Version = parsedVersion, + Path = path + }; + deleteList.Add(deletionData); + } + + return deleteList; + } + + private struct UpdateData + { + public SemVersion Version; + public string Url; + public string Path; + public string Description; + } + private static async Task> GetAllVersionData() + { + var versions = new List(); + + using var httpClient = new HttpClient(); + var allVersionsText = await httpClient.GetStringAsync(Endpoints.RRVersionUrl); + var lines = allVersionsText.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var parts = line.Split(' ', 4); + if (parts.Length < 4) + continue; + var version = parts[0].Trim(); + var url = parts[1].Trim(); + var path = parts[2].Trim(); + var description = parts[3].Trim(); + if (string.IsNullOrWhiteSpace(version) || string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(path)) + continue; + if (!SemVersion.TryParse(version, out var _)) + continue; + var parsedVersion = SemVersion.Parse(version); + var updateData = new UpdateData + { + Version = parsedVersion, + Url = url, + Path = path, + Description = description + }; + versions.Add(updateData); + } + return versions; + } + + //todo: see if we can make this generic to the point we dont have to split up deletions and updates + private static List GetUpdatesToApply(SemVersion currentVersion, List allVersions) + { + var updatesToApply = new List(); + foreach (var update in allVersions) + { + if (update.Version.ComparePrecedenceTo(currentVersion) > 0) + { + updatesToApply.Add(update); + } + } + // Sort the updates by version in descending order + updatesToApply.Reverse(); + return updatesToApply; + } + + private static List GetDeletionsToApply(SemVersion currentVersion, SemVersion targetVersion, List allDeletions + ) + { + var deletionsToApply = new List(); + allDeletions = allDeletions.OrderByDescending(d => d.Version).ToList(); + foreach (var deletion in allDeletions) + { + if (deletion.Version.ComparePrecedenceTo(currentVersion) > 0 && + deletion.Version.ComparePrecedenceTo(targetVersion) <= 0) + { + deletionsToApply.Add(deletion); + } + } + + deletionsToApply.Reverse(); + return deletionsToApply; + } + + + public async Task Remove() + { + throw new NotImplementedException(); + } + + public WheelWizardStatus GetCurrentStatus() + { + throw new NotImplementedException(); + } + + public SemVersion? GetCurrentVersion() + { + var versionFilePath = PathManager.RetroRewindVersionFile; + if (!File.Exists(versionFilePath)) + return null; + + var versionText = File.ReadAllText(versionFilePath).Trim(); + var versionPattern = @"^\d+\.\d+\.\d+$"; + if (!Regex.IsMatch(versionText, versionPattern)) + return null; + + return SemVersion.Parse(versionText); + } +} diff --git a/WheelWizard/SetupExtensions.cs b/WheelWizard/SetupExtensions.cs index 77086d9c..d0ad38cd 100644 --- a/WheelWizard/SetupExtensions.cs +++ b/WheelWizard/SetupExtensions.cs @@ -4,6 +4,7 @@ using Testably.Abstractions; using WheelWizard.AutoUpdating; using WheelWizard.Branding; +using WheelWizard.CustomDistributions; using WheelWizard.GameBanana; using WheelWizard.GitHub; using WheelWizard.MiiImages; @@ -30,6 +31,7 @@ public static void AddWheelWizardServices(this IServiceCollection services) services.AddWiiManagement(); services.AddGameBanana(); services.AddMiiImages(); + services.AddCustomDistributionService(); // IO Abstractions services.AddSingleton(); diff --git a/WheelWizard/WheelWizard.csproj b/WheelWizard/WheelWizard.csproj index 8204c081..321d355b 100644 --- a/WheelWizard/WheelWizard.csproj +++ b/WheelWizard/WheelWizard.csproj @@ -144,4 +144,8 @@ + + + + From 10a0f548ed1f76fe045e7669fafcb09ce9201467 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sat, 10 May 2025 20:09:16 +0200 Subject: [PATCH 02/19] Update Logic --- .../CustomDistributions/RetroRewind.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 206a3fa7..2a990523 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -40,25 +40,30 @@ private static async Task> LatestServerVersion() return "Failed to check for updates"; } - public Task Update() + public async Task Update() { try { var currentVersion = GetCurrentVersion(); if (currentVersion == null) - return Install(); + return await Install(); + + var isRRUpToDate = await IsRRUpToDate(currentVersion); + if (isRRUpToDate.IsFailure) + return isRRUpToDate; - if (await IsRRUpToDate(currentVersion)) + if (isRRUpToDate.Value) { - return true; + return Ok(); } //if current version is below 3.2.6 we need to do a full reinstall - if (currentVersion.ComparePrecedenceTo("3.2.6") < 0) + if (currentVersion.ComparePrecedenceTo(new SemVersion(3, 2, 6)) < 0) { - var removeResult = await Remove(); - if (removeResult.IsFailure) - return removeResult; + //todo: look at this logic + var result = await Install(); + if (result.IsFailure) + return result; return await Install(); } @@ -66,8 +71,7 @@ public Task Update() } catch (Exception e) { - AbortingUpdate($"Reason: {e.Message}"); - return false; + return e; } } From c4646b29e663086fb68e3a20a3c55db69da7b6a0 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 11 May 2025 11:10:46 +0200 Subject: [PATCH 03/19] Slowly start replacing --- .../CustomDistributionSingletonService.cs | 9 +- .../CustomDistributions/IDistribution.cs | 2 +- .../CustomDistributions/RetroRewind.cs | 133 +++- .../Installation/RetroRewindInstaller.cs | 350 +++++----- .../Installation/RetroRewindUpdater.cs | 620 +++++++++--------- 5 files changed, 618 insertions(+), 496 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs index 81bbeb70..9a5e9a3d 100644 --- a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs @@ -2,15 +2,16 @@ namespace WheelWizard.CustomDistributions; public interface ICustomDistributionSingletonService { - List GetDistributions(); + List GetAllDistributions(); + RetroRewind RetroRewind { get; } } public class CustomDistributionSingletonService : ICustomDistributionSingletonService { - readonly RetroRewind _retroRewind = new(); + public RetroRewind RetroRewind { get; } = new RetroRewind(); - public List GetDistributions() + public List GetAllDistributions() { - return [_retroRewind]; + return [RetroRewind]; } } diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs index 06474da3..8a88234f 100644 --- a/WheelWizard/Features/CustomDistributions/IDistribution.cs +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -27,7 +27,7 @@ public interface IDistribution Task Remove(); - WheelWizardStatus GetCurrentStatus(); + Task> GetCurrentStatus(); SemVersion? GetCurrentVersion(); diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 2a990523..1925d083 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -5,6 +5,7 @@ using WheelWizard.Models.Enums; using WheelWizard.Resources.Languages; using WheelWizard.Services; +using WheelWizard.Services.Settings; using WheelWizard.Views.Popups.Generic; namespace WheelWizard.CustomDistributions; @@ -17,9 +18,108 @@ public class RetroRewind : IDistribution public string FolderName => "RetroRewind6"; - public Task Install() + public async Task Install() { - throw new NotImplementedException(); + if (GetCurrentVersion() is null) + { + var removeResult = await Remove(); + if (removeResult.IsFailure) + return removeResult; + } + + if (HasOldRksys()) + { + var rksysQuestion = new YesNoWindow() + .SetMainText(Phrases.PopupText_OldRksysFound) + .SetExtraText(Phrases.PopupText_OldRksysFoundExplained); + if (await rksysQuestion.AwaitAnswer()) + await BackupOldrksys(); + } + var serverResponse = await HttpClientHelper.GetAsync(Endpoints.RRUrl); + if (!serverResponse.Succeeded) + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Warning) + .SetTitleText("Could not connect to the server") + .SetInfoText(Phrases.PopupText_CouldNotConnectServer) + .ShowDialog(); + return "Could not connect to the server"; + } + await DownloadAndExtractRetroRewind(PathManager.RetroRewindTempFile); + await Update(); + return Ok(); + } + + private static async Task DownloadAndExtractRetroRewind(string tempZipPath) + { + var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); + progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); + progressWindow.Show(); + + try + { + await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); + progressWindow.SetExtraText(Common.State_Extracting); + var extractionPath = PathManager.RiivolutionWhWzFolderPath; + ZipFile.ExtractToDirectory(tempZipPath, extractionPath, true); + } + finally + { + progressWindow.Close(); + if (File.Exists(tempZipPath)) + File.Delete(tempZipPath); + } + } + + private static async Task BackupOldrksys() + { + var rrWfc = Path.Combine(GetOldRksys()); + if (!Directory.Exists(rrWfc)) + return; + var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); + if (rksysFiles.Length == 0) + return; + var sourceFile = rksysFiles[0]; + var regionFolder = Path.GetDirectoryName(sourceFile); + var regionFolderName = Path.GetFileName(regionFolder); + var datFileData = await File.ReadAllBytesAsync(sourceFile); + if (regionFolderName == null) + return; + var destinationFolder = Path.Combine(PathManager.SaveFolderPath, regionFolderName); + Directory.CreateDirectory(destinationFolder); + var destinationFile = Path.Combine(destinationFolder, "rksys.dat"); + await File.WriteAllBytesAsync(destinationFile, datFileData); + } + + + private static bool HasOldRksys() + { + return !string.IsNullOrWhiteSpace(GetOldRksys()); + } + + private static string GetOldRksys() + { + var rrWfcPaths = new[] + { + Path.Combine(PathManager.SaveFolderPath), + // Also consider the folder with upper-case `Save` + Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), + Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), + Path.Combine(PathManager.LoadFolderPath, "Riivolution", "Save", "RetroWFC"), + Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), + Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), + }; + + foreach (var rrWfc in rrWfcPaths) + { + if (!Directory.Exists(rrWfc)) + continue; + var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); + if (rksysFiles.Length > 0) + return rrWfc; + } + + return string.Empty; } private static async Task> IsRRUpToDate(SemVersion currentVersion) @@ -340,14 +440,35 @@ private static List GetDeletionsToApply(SemVersion currentVersion, } - public async Task Remove() + public Task Remove() { - throw new NotImplementedException(); + var retroRewindPath = PathManager.RetroRewind6FolderPath; + if (Directory.Exists(retroRewindPath)) + Directory.Delete(retroRewindPath, true); + return Task.FromResult(Ok()); } - public WheelWizardStatus GetCurrentStatus() + public async Task> GetCurrentStatus() { - throw new NotImplementedException(); + if (!SettingsHelper.PathsSetupCorrectly()) + return WheelWizardStatus.ConfigNotFinished; + + var serverEnabled = await HttpClientHelper.GetAsync(Endpoints.RRUrl); + var rrInstalled = GetCurrentVersion() != null; + + if (!serverEnabled.Succeeded) + return rrInstalled ? WheelWizardStatus.NoServerButInstalled : WheelWizardStatus.NoServer; + + if (!rrInstalled) + return WheelWizardStatus.NotInstalled; + var currentVersion = GetCurrentVersion(); + if (currentVersion == null) + return WheelWizardStatus.NotInstalled; + var retroRewindUpToDateResult = await IsRRUpToDate(currentVersion); + if (retroRewindUpToDateResult.IsFailure) + return "Failed to check for updates"; + var retroRewindUpToDate = retroRewindUpToDateResult.Value; + return !retroRewindUpToDate ? WheelWizardStatus.OutOfDate : WheelWizardStatus.Ready; } public SemVersion? GetCurrentVersion() diff --git a/WheelWizard/Services/Installation/RetroRewindInstaller.cs b/WheelWizard/Services/Installation/RetroRewindInstaller.cs index 60bd6740..16701e4d 100644 --- a/WheelWizard/Services/Installation/RetroRewindInstaller.cs +++ b/WheelWizard/Services/Installation/RetroRewindInstaller.cs @@ -1,175 +1,175 @@ -using System.IO.Compression; -using System.Text.RegularExpressions; -using WheelWizard.Helpers; -using WheelWizard.Resources.Languages; -using WheelWizard.Views.Popups.Generic; - -namespace WheelWizard.Services.Installation; - -public static class RetroRewindInstaller -{ - private static readonly string NotInstalledVersion = "Not Installed"; - - public static bool IsRetroRewindInstalled() => CurrentRRVersion() != NotInstalledVersion; - - public static string CurrentRRVersion() - { - var versionFilePath = PathManager.RetroRewindVersionFile; - if (!File.Exists(versionFilePath)) - return NotInstalledVersion; - - var versionText = File.ReadAllText(versionFilePath).Trim(); - var versionPattern = @"^\d+\.\d+\.\d+$"; - if (!Regex.IsMatch(versionText, versionPattern)) - return NotInstalledVersion; - - return versionText; - } - - public static async Task HandleNotInstalled() - { - var result = await new YesNoWindow() - .SetMainText(Phrases.PopupText_RRNotDeterment) - .SetExtraText(Phrases.PopupText_DownloadRR) - .AwaitAnswer(); - - if (!result) - return false; - - await InstallRetroRewind(); - return true; - } - - public static async Task HandleOldVersion() - { - var result = await new YesNoWindow() - .SetMainText(Phrases.PopupText_RRToOld) - .SetExtraText(Phrases.PopupText_ReinstallRR) - .AwaitAnswer(); - - if (!result) - return false; - - await InstallRetroRewind(); - return true; - } - - public static async Task InstallRetroRewind() - { - if (IsRetroRewindInstalled()) - DeleteExistingRetroRewind(); - - if (HasOldRksys()) - { - var rksysQuestion = new YesNoWindow() - .SetMainText(Phrases.PopupText_OldRksysFound) - .SetExtraText(Phrases.PopupText_OldRksysFoundExplained); - if (await rksysQuestion.AwaitAnswer()) - await BackupOldrksys(); - } - var serverResponse = await HttpClientHelper.GetAsync(Endpoints.RRUrl); - if (!serverResponse.Succeeded) - { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Could not connect to the server") - .SetInfoText(Phrases.PopupText_CouldNotConnectServer) - .ShowDialog(); - return; - } - await DownloadAndExtractRetroRewind(PathManager.RetroRewindTempFile); - await RetroRewindUpdater.UpdateRR(); - } - - public static async Task ReinstallRR() - { - var result = await new YesNoWindow() - .SetMainText(Phrases.PopupText_ReinstallRR) - .SetExtraText(Phrases.PopupText_ReinstallQuestion) - .AwaitAnswer(); - - if (!result) - return; - - DeleteExistingRetroRewind(); - await InstallRetroRewind(); - } - - private static async Task DownloadAndExtractRetroRewind(string tempZipPath) - { - var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); - progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); - progressWindow.Show(); - - try - { - await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); - progressWindow.SetExtraText(Common.State_Extracting); - var extractionPath = PathManager.RiivolutionWhWzFolderPath; - ZipFile.ExtractToDirectory(tempZipPath, extractionPath, true); - } - finally - { - progressWindow.Close(); - if (File.Exists(tempZipPath)) - File.Delete(tempZipPath); - } - } - - private static bool HasOldRksys() - { - return !string.IsNullOrWhiteSpace(GetOldRksys()); - } - - private static string GetOldRksys() - { - var rrWfcPaths = new[] - { - Path.Combine(PathManager.SaveFolderPath), - // Also consider the folder with upper-case `Save` - Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "Riivolution", "Save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), - }; - - foreach (var rrWfc in rrWfcPaths) - { - if (!Directory.Exists(rrWfc)) - continue; - var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); - if (rksysFiles.Length > 0) - return rrWfc; - } - - return string.Empty; - } - - private static async Task BackupOldrksys() - { - var rrWfc = Path.Combine(GetOldRksys()); - if (!Directory.Exists(rrWfc)) - return; - var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); - if (rksysFiles.Length == 0) - return; - var sourceFile = rksysFiles[0]; - var regionFolder = Path.GetDirectoryName(sourceFile); - var regionFolderName = Path.GetFileName(regionFolder); - var datFileData = await File.ReadAllBytesAsync(sourceFile); - if (regionFolderName == null) - return; - var destinationFolder = Path.Combine(PathManager.SaveFolderPath, regionFolderName); - Directory.CreateDirectory(destinationFolder); - var destinationFile = Path.Combine(destinationFolder, "rksys.dat"); - await File.WriteAllBytesAsync(destinationFile, datFileData); - } - - private static void DeleteExistingRetroRewind() - { - var retroRewindPath = PathManager.RetroRewind6FolderPath; - if (Directory.Exists(retroRewindPath)) - Directory.Delete(retroRewindPath, true); - } -} +// using System.IO.Compression; +// using System.Text.RegularExpressions; +// using WheelWizard.Helpers; +// using WheelWizard.Resources.Languages; +// using WheelWizard.Views.Popups.Generic; +// +// namespace WheelWizard.Services.Installation; +// +// public static class RetroRewindInstaller +// { +// private static readonly string NotInstalledVersion = "Not Installed"; +// +// public static bool IsRetroRewindInstalled() => CurrentRRVersion() != NotInstalledVersion; +// +// public static string CurrentRRVersion() +// { +// var versionFilePath = PathManager.RetroRewindVersionFile; +// if (!File.Exists(versionFilePath)) +// return NotInstalledVersion; +// +// var versionText = File.ReadAllText(versionFilePath).Trim(); +// var versionPattern = @"^\d+\.\d+\.\d+$"; +// if (!Regex.IsMatch(versionText, versionPattern)) +// return NotInstalledVersion; +// +// return versionText; +// } +// +// public static async Task HandleNotInstalled() +// { +// var result = await new YesNoWindow() +// .SetMainText(Phrases.PopupText_RRNotDeterment) +// .SetExtraText(Phrases.PopupText_DownloadRR) +// .AwaitAnswer(); +// +// if (!result) +// return false; +// +// await InstallRetroRewind(); +// return true; +// } +// +// public static async Task HandleOldVersion() +// { +// var result = await new YesNoWindow() +// .SetMainText(Phrases.PopupText_RRToOld) +// .SetExtraText(Phrases.PopupText_ReinstallRR) +// .AwaitAnswer(); +// +// if (!result) +// return false; +// +// await InstallRetroRewind(); +// return true; +// } +// +// public static async Task InstallRetroRewind() +// { +// if (IsRetroRewindInstalled()) +// DeleteExistingRetroRewind(); +// +// if (HasOldRksys()) +// { +// var rksysQuestion = new YesNoWindow() +// .SetMainText(Phrases.PopupText_OldRksysFound) +// .SetExtraText(Phrases.PopupText_OldRksysFoundExplained); +// if (await rksysQuestion.AwaitAnswer()) +// await BackupOldrksys(); +// } +// var serverResponse = await HttpClientHelper.GetAsync(Endpoints.RRUrl); +// if (!serverResponse.Succeeded) +// { +// await new MessageBoxWindow() +// .SetMessageType(MessageBoxWindow.MessageType.Warning) +// .SetTitleText("Could not connect to the server") +// .SetInfoText(Phrases.PopupText_CouldNotConnectServer) +// .ShowDialog(); +// return; +// } +// await DownloadAndExtractRetroRewind(PathManager.RetroRewindTempFile); +// await RetroRewindUpdater.UpdateRR(); +// } +// +// public static async Task ReinstallRR() +// { +// var result = await new YesNoWindow() +// .SetMainText(Phrases.PopupText_ReinstallRR) +// .SetExtraText(Phrases.PopupText_ReinstallQuestion) +// .AwaitAnswer(); +// +// if (!result) +// return; +// +// DeleteExistingRetroRewind(); +// await InstallRetroRewind(); +// } +// +// private static async Task DownloadAndExtractRetroRewind(string tempZipPath) +// { +// var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); +// progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); +// progressWindow.Show(); +// +// try +// { +// await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); +// progressWindow.SetExtraText(Common.State_Extracting); +// var extractionPath = PathManager.RiivolutionWhWzFolderPath; +// ZipFile.ExtractToDirectory(tempZipPath, extractionPath, true); +// } +// finally +// { +// progressWindow.Close(); +// if (File.Exists(tempZipPath)) +// File.Delete(tempZipPath); +// } +// } +// +// private static bool HasOldRksys() +// { +// return !string.IsNullOrWhiteSpace(GetOldRksys()); +// } +// +// private static string GetOldRksys() +// { +// var rrWfcPaths = new[] +// { +// Path.Combine(PathManager.SaveFolderPath), +// // Also consider the folder with upper-case `Save` +// Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), +// Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), +// Path.Combine(PathManager.LoadFolderPath, "Riivolution", "Save", "RetroWFC"), +// Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), +// Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), +// }; +// +// foreach (var rrWfc in rrWfcPaths) +// { +// if (!Directory.Exists(rrWfc)) +// continue; +// var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); +// if (rksysFiles.Length > 0) +// return rrWfc; +// } +// +// return string.Empty; +// } +// +// private static async Task BackupOldrksys() +// { +// var rrWfc = Path.Combine(GetOldRksys()); +// if (!Directory.Exists(rrWfc)) +// return; +// var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); +// if (rksysFiles.Length == 0) +// return; +// var sourceFile = rksysFiles[0]; +// var regionFolder = Path.GetDirectoryName(sourceFile); +// var regionFolderName = Path.GetFileName(regionFolder); +// var datFileData = await File.ReadAllBytesAsync(sourceFile); +// if (regionFolderName == null) +// return; +// var destinationFolder = Path.Combine(PathManager.SaveFolderPath, regionFolderName); +// Directory.CreateDirectory(destinationFolder); +// var destinationFile = Path.Combine(destinationFolder, "rksys.dat"); +// await File.WriteAllBytesAsync(destinationFile, datFileData); +// } +// +// private static void DeleteExistingRetroRewind() +// { +// var retroRewindPath = PathManager.RetroRewind6FolderPath; +// if (Directory.Exists(retroRewindPath)) +// Directory.Delete(retroRewindPath, true); +// } +// } diff --git a/WheelWizard/Services/Installation/RetroRewindUpdater.cs b/WheelWizard/Services/Installation/RetroRewindUpdater.cs index 14dcba41..c771b5b7 100644 --- a/WheelWizard/Services/Installation/RetroRewindUpdater.cs +++ b/WheelWizard/Services/Installation/RetroRewindUpdater.cs @@ -1,310 +1,310 @@ -using System.IO.Compression; -using WheelWizard.Helpers; -using WheelWizard.Resources.Languages; -using WheelWizard.Views.Popups.Generic; - -namespace WheelWizard.Services.Installation; - -public static class RetroRewindUpdater -{ - public static async Task IsRRUpToDate(string currentVersion) - { - var latestVersion = await GetLatestVersionString(); - return currentVersion.Trim() == latestVersion.Trim(); - } - - private static async Task GetLatestVersionString() - { - var response = await HttpClientHelper.GetAsync(Endpoints.RRVersionUrl); - if (response.Succeeded && response.Content != null) - return response.Content.Split('\n').Last().Split(' ')[0]; - new YesNoWindow().SetMainText(Phrases.PopupText_FailCheckUpdates).AwaitAnswer(); - return "Failed to check for updates"; - } - - public static async Task UpdateRR() - { - try - { - if (!RetroRewindInstaller.IsRetroRewindInstalled()) - return await RetroRewindInstaller.HandleNotInstalled(); - - var currentVersion = RetroRewindInstaller.CurrentRRVersion(); - if (await IsRRUpToDate(currentVersion)) - { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Message) - .SetTitleText(Phrases.PopupText_RRUpToDate) - .SetInfoText(Phrases.PopupText_RRUpToDate) - .ShowDialog(); - return true; - } - - //if current version is below 3.2.6 we need to do a full reinstall - if (CompareVersions(currentVersion, "3.2.6") < 0) - return await RetroRewindInstaller.HandleOldVersion(); - return await ApplyUpdates(currentVersion); - } - catch (Exception e) - { - AbortingUpdate($"Reason: {e.Message}"); - return false; - } - } - - private static async Task ApplyUpdates(string currentVersion) - { - var allVersions = await GetAllVersionData(); - var updatesToApply = GetUpdatesToApply(currentVersion, allVersions); - - var progressWindow = new ProgressWindow(Phrases.PopupText_UpdateRR); - progressWindow.Show(); - - // Step 1: Get the version we are updating to - var targetVersion = updatesToApply.Any() ? updatesToApply.Last().Version : currentVersion; - - // Step 2: Apply file deletions for versions between current and targetVersion - var deleteSuccess = await ApplyFileDeletionsBetweenVersions(currentVersion, targetVersion); - if (!deleteSuccess) - { - AbortingUpdate(Phrases.PopupText_FailedUpdateDelete); - progressWindow.Close(); - return false; - } - - // Step 3: Download and apply the updates (if any) - for (var i = 0; i < updatesToApply.Count; i++) - { - var update = updatesToApply[i]; - - var success = await DownloadAndApplyUpdate(update, updatesToApply.Count, i + 1, progressWindow); - if (!success) - { - progressWindow.Close(); - AbortingUpdate(Phrases.PopupText_FailedUpdateApply); - return false; - } - - // Update the version file after each successful update - UpdateVersionFile(update.Version); - } - - progressWindow.Close(); - return true; - } - - private static async Task ApplyFileDeletionsBetweenVersions(string currentVersion, string targetVersion) - { - try - { - var deleteList = await GetFileDeletionList(); - var deletionsToApply = GetDeletionsToApply(currentVersion, targetVersion, deleteList); - - foreach (var file in deletionsToApply) - { - var absoluteDestinationPath = Path.GetFullPath(PathManager.RiivolutionWhWzFolderPath + Path.AltDirectorySeparatorChar); - var filePath = Path.GetFullPath(Path.Combine(absoluteDestinationPath, file.Path.TrimStart('/'))); - //because we are actually getting the path from the server, - //we need to make sure we are not getting hacked, so we check if the path is in the riivolution folder - var resolvedPath = Path.GetFullPath(new FileInfo(filePath).FullName); - if ( - !resolvedPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) - || !filePath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) - || file.Path.Contains("..") - ) - { - AbortingUpdate("Invalid file path detected. Please contact the developers.\n Server error: " + resolvedPath); - return false; - } - - if (File.Exists(filePath)) - File.Delete(filePath); - else if (Directory.Exists(filePath)) - Directory.Delete(filePath, recursive: true); - } - - return true; - } - catch (Exception e) - { - AbortingUpdate($"Failed to delete files: {e.Message}"); - return false; - } - } - - private static List<(string Version, string Path)> GetDeletionsToApply( - string currentVersion, - string targetVersion, - List<(string Version, string Path)> allDeletions - ) - { - var deletionsToApply = new List<(string Version, string Path)>(); - allDeletions.Sort((a, b) => CompareVersions(b.Version, a.Version)); // Sort in descending order - foreach (var deletion in allDeletions) - { - if (CompareVersions(deletion.Version, currentVersion) > 0 && CompareVersions(deletion.Version, targetVersion) <= 0) - { - deletionsToApply.Add(deletion); - } - } - - deletionsToApply.Reverse(); - return deletionsToApply; - } - - private static async Task> GetFileDeletionList() - { - var deleteList = new List<(string Version, string Path)>(); - - using var httpClient = new HttpClient(); - var deleteListText = await httpClient.GetStringAsync(Endpoints.RRVersionDeleteUrl); - var lines = deleteListText.Split('\n', StringSplitOptions.RemoveEmptyEntries); - - foreach (var line in lines) - { - var parts = line.Split(' ', 2); - if (parts.Length < 2) - continue; - deleteList.Add((parts[0].Trim(), parts[1].Trim())); - } - - return deleteList; - } - - private static void UpdateVersionFile(string newVersion) - { - var versionFilePath = Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); - File.WriteAllText(versionFilePath, newVersion); - } - - private static async Task> GetAllVersionData() - { - var versions = new List<(string Version, string Url, string Path, string Description)>(); - - using var httpClient = new HttpClient(); - var allVersionsText = await httpClient.GetStringAsync(Endpoints.RRVersionUrl); - var lines = allVersionsText.Split('\n', StringSplitOptions.RemoveEmptyEntries); - - foreach (var line in lines) - { - var parts = line.Split(' ', 4); - if (parts.Length < 4) - continue; - versions.Add((parts[0].Trim(), parts[1].Trim(), parts[2].Trim(), parts[3].Trim())); - } - - return versions; - } - - private static List<(string Version, string Url, string Path, string Description)> GetUpdatesToApply( - string currentVersion, - List<(string Version, string Url, string Path, string Description)> allVersions - ) - { - var updatesToApply = new List<(string Version, string Url, string Path, string Description)>(); - allVersions.Sort((a, b) => CompareVersions(b.Version, a.Version)); // Sort in descending order - foreach (var version in allVersions) - { - if (CompareVersions(version.Version, currentVersion) > 0) - updatesToApply.Add(version); - else - break; - } - - updatesToApply.Reverse(); - return updatesToApply; - } - - private static int CompareVersions(string v1, string v2) - { - var parts1 = v1.Split('.').Select(int.Parse).ToArray(); - var parts2 = v2.Split('.').Select(int.Parse).ToArray(); - for (var i = 0; i < Math.Max(parts1.Length, parts2.Length); i++) - { - var p1 = i < parts1.Length ? parts1[i] : 0; - var p2 = i < parts2.Length ? parts2[i] : 0; - if (p1 != p2) - return p1.CompareTo(p2); - } - - return 0; - } - - private static async Task DownloadAndApplyUpdate( - (string Version, string Url, string Path, string Description) update, - int totalUpdates, - int currentUpdateIndex, - ProgressWindow popupWindow - ) - { - var tempZipPath = Path.GetTempFileName(); - try - { - popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); - var finalFile = await DownloadHelper.DownloadToLocationAsync(update.Url, tempZipPath, popupWindow); - - popupWindow.UpdateProgress(100); - popupWindow.SetExtraText(Common.State_Extracting); - var destinationDirectoryPath = PathManager.RiivolutionWhWzFolderPath; - Directory.CreateDirectory(destinationDirectoryPath); - ExtractZipFile(finalFile, destinationDirectoryPath); - if (File.Exists(finalFile)) - File.Delete(finalFile); - } - finally - { - if (File.Exists(tempZipPath)) - File.Delete(tempZipPath); - } - - return true; - } - - private static void ExtractZipFile(string path, string destinationDirectory) - { - using var archive = ZipFile.OpenRead(path); - - // Absolute path of the destination directory - var absoluteDestinationPath = Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); - - foreach (var entry in archive.Entries) - { - if (entry.FullName.EndsWith("desktop.ini", StringComparison.OrdinalIgnoreCase)) - continue; // Skip the desktop.ini file - - // Get the full path of the file - var destinationPath = Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); - - // Check for directory traversal attacks - if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) - { - AbortingUpdate("The file path is outside the destination directory. Please contact the developers."); - return; - } - - // If the entry is a directory, create it - if (entry.FullName.EndsWith(Path.AltDirectorySeparatorChar)) - { - Directory.CreateDirectory(destinationPath); - continue; - } - - // Create directory if it doesn't exist - var directoryName = Path.GetDirectoryName(destinationPath); - if (!string.IsNullOrEmpty(directoryName)) - Directory.CreateDirectory(directoryName); - - // Extract the file - entry.ExtractToFile(destinationPath, overwrite: true); - } - } - - public static void AbortingUpdate(string reason) - { - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Error) - .SetTitleText("Aborting RR Update") - .SetInfoText(reason) - .Show(); - } -} +// using System.IO.Compression; +// using WheelWizard.Helpers; +// using WheelWizard.Resources.Languages; +// using WheelWizard.Views.Popups.Generic; +// +// namespace WheelWizard.Services.Installation; +// +// public static class RetroRewindUpdater +// { +// public static async Task IsRRUpToDate(string currentVersion) +// { +// var latestVersion = await GetLatestVersionString(); +// return currentVersion.Trim() == latestVersion.Trim(); +// } +// +// private static async Task GetLatestVersionString() +// { +// var response = await HttpClientHelper.GetAsync(Endpoints.RRVersionUrl); +// if (response.Succeeded && response.Content != null) +// return response.Content.Split('\n').Last().Split(' ')[0]; +// new YesNoWindow().SetMainText(Phrases.PopupText_FailCheckUpdates).AwaitAnswer(); +// return "Failed to check for updates"; +// } +// +// public static async Task UpdateRR() +// { +// try +// { +// if (!RetroRewindInstaller.IsRetroRewindInstalled()) +// return await RetroRewindInstaller.HandleNotInstalled(); +// +// var currentVersion = RetroRewindInstaller.CurrentRRVersion(); +// if (await IsRRUpToDate(currentVersion)) +// { +// await new MessageBoxWindow() +// .SetMessageType(MessageBoxWindow.MessageType.Message) +// .SetTitleText(Phrases.PopupText_RRUpToDate) +// .SetInfoText(Phrases.PopupText_RRUpToDate) +// .ShowDialog(); +// return true; +// } +// +// //if current version is below 3.2.6 we need to do a full reinstall +// if (CompareVersions(currentVersion, "3.2.6") < 0) +// return await RetroRewindInstaller.HandleOldVersion(); +// return await ApplyUpdates(currentVersion); +// } +// catch (Exception e) +// { +// AbortingUpdate($"Reason: {e.Message}"); +// return false; +// } +// } +// +// private static async Task ApplyUpdates(string currentVersion) +// { +// var allVersions = await GetAllVersionData(); +// var updatesToApply = GetUpdatesToApply(currentVersion, allVersions); +// +// var progressWindow = new ProgressWindow(Phrases.PopupText_UpdateRR); +// progressWindow.Show(); +// +// // Step 1: Get the version we are updating to +// var targetVersion = updatesToApply.Any() ? updatesToApply.Last().Version : currentVersion; +// +// // Step 2: Apply file deletions for versions between current and targetVersion +// var deleteSuccess = await ApplyFileDeletionsBetweenVersions(currentVersion, targetVersion); +// if (!deleteSuccess) +// { +// AbortingUpdate(Phrases.PopupText_FailedUpdateDelete); +// progressWindow.Close(); +// return false; +// } +// +// // Step 3: Download and apply the updates (if any) +// for (var i = 0; i < updatesToApply.Count; i++) +// { +// var update = updatesToApply[i]; +// +// var success = await DownloadAndApplyUpdate(update, updatesToApply.Count, i + 1, progressWindow); +// if (!success) +// { +// progressWindow.Close(); +// AbortingUpdate(Phrases.PopupText_FailedUpdateApply); +// return false; +// } +// +// // Update the version file after each successful update +// UpdateVersionFile(update.Version); +// } +// +// progressWindow.Close(); +// return true; +// } +// +// private static async Task ApplyFileDeletionsBetweenVersions(string currentVersion, string targetVersion) +// { +// try +// { +// var deleteList = await GetFileDeletionList(); +// var deletionsToApply = GetDeletionsToApply(currentVersion, targetVersion, deleteList); +// +// foreach (var file in deletionsToApply) +// { +// var absoluteDestinationPath = Path.GetFullPath(PathManager.RiivolutionWhWzFolderPath + Path.AltDirectorySeparatorChar); +// var filePath = Path.GetFullPath(Path.Combine(absoluteDestinationPath, file.Path.TrimStart('/'))); +// //because we are actually getting the path from the server, +// //we need to make sure we are not getting hacked, so we check if the path is in the riivolution folder +// var resolvedPath = Path.GetFullPath(new FileInfo(filePath).FullName); +// if ( +// !resolvedPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) +// || !filePath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) +// || file.Path.Contains("..") +// ) +// { +// AbortingUpdate("Invalid file path detected. Please contact the developers.\n Server error: " + resolvedPath); +// return false; +// } +// +// if (File.Exists(filePath)) +// File.Delete(filePath); +// else if (Directory.Exists(filePath)) +// Directory.Delete(filePath, recursive: true); +// } +// +// return true; +// } +// catch (Exception e) +// { +// AbortingUpdate($"Failed to delete files: {e.Message}"); +// return false; +// } +// } +// +// private static List<(string Version, string Path)> GetDeletionsToApply( +// string currentVersion, +// string targetVersion, +// List<(string Version, string Path)> allDeletions +// ) +// { +// var deletionsToApply = new List<(string Version, string Path)>(); +// allDeletions.Sort((a, b) => CompareVersions(b.Version, a.Version)); // Sort in descending order +// foreach (var deletion in allDeletions) +// { +// if (CompareVersions(deletion.Version, currentVersion) > 0 && CompareVersions(deletion.Version, targetVersion) <= 0) +// { +// deletionsToApply.Add(deletion); +// } +// } +// +// deletionsToApply.Reverse(); +// return deletionsToApply; +// } +// +// private static async Task> GetFileDeletionList() +// { +// var deleteList = new List<(string Version, string Path)>(); +// +// using var httpClient = new HttpClient(); +// var deleteListText = await httpClient.GetStringAsync(Endpoints.RRVersionDeleteUrl); +// var lines = deleteListText.Split('\n', StringSplitOptions.RemoveEmptyEntries); +// +// foreach (var line in lines) +// { +// var parts = line.Split(' ', 2); +// if (parts.Length < 2) +// continue; +// deleteList.Add((parts[0].Trim(), parts[1].Trim())); +// } +// +// return deleteList; +// } +// +// private static void UpdateVersionFile(string newVersion) +// { +// var versionFilePath = Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); +// File.WriteAllText(versionFilePath, newVersion); +// } +// +// private static async Task> GetAllVersionData() +// { +// var versions = new List<(string Version, string Url, string Path, string Description)>(); +// +// using var httpClient = new HttpClient(); +// var allVersionsText = await httpClient.GetStringAsync(Endpoints.RRVersionUrl); +// var lines = allVersionsText.Split('\n', StringSplitOptions.RemoveEmptyEntries); +// +// foreach (var line in lines) +// { +// var parts = line.Split(' ', 4); +// if (parts.Length < 4) +// continue; +// versions.Add((parts[0].Trim(), parts[1].Trim(), parts[2].Trim(), parts[3].Trim())); +// } +// +// return versions; +// } +// +// private static List<(string Version, string Url, string Path, string Description)> GetUpdatesToApply( +// string currentVersion, +// List<(string Version, string Url, string Path, string Description)> allVersions +// ) +// { +// var updatesToApply = new List<(string Version, string Url, string Path, string Description)>(); +// allVersions.Sort((a, b) => CompareVersions(b.Version, a.Version)); // Sort in descending order +// foreach (var version in allVersions) +// { +// if (CompareVersions(version.Version, currentVersion) > 0) +// updatesToApply.Add(version); +// else +// break; +// } +// +// updatesToApply.Reverse(); +// return updatesToApply; +// } +// +// private static int CompareVersions(string v1, string v2) +// { +// var parts1 = v1.Split('.').Select(int.Parse).ToArray(); +// var parts2 = v2.Split('.').Select(int.Parse).ToArray(); +// for (var i = 0; i < Math.Max(parts1.Length, parts2.Length); i++) +// { +// var p1 = i < parts1.Length ? parts1[i] : 0; +// var p2 = i < parts2.Length ? parts2[i] : 0; +// if (p1 != p2) +// return p1.CompareTo(p2); +// } +// +// return 0; +// } +// +// private static async Task DownloadAndApplyUpdate( +// (string Version, string Url, string Path, string Description) update, +// int totalUpdates, +// int currentUpdateIndex, +// ProgressWindow popupWindow +// ) +// { +// var tempZipPath = Path.GetTempFileName(); +// try +// { +// popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); +// var finalFile = await DownloadHelper.DownloadToLocationAsync(update.Url, tempZipPath, popupWindow); +// +// popupWindow.UpdateProgress(100); +// popupWindow.SetExtraText(Common.State_Extracting); +// var destinationDirectoryPath = PathManager.RiivolutionWhWzFolderPath; +// Directory.CreateDirectory(destinationDirectoryPath); +// ExtractZipFile(finalFile, destinationDirectoryPath); +// if (File.Exists(finalFile)) +// File.Delete(finalFile); +// } +// finally +// { +// if (File.Exists(tempZipPath)) +// File.Delete(tempZipPath); +// } +// +// return true; +// } +// +// private static void ExtractZipFile(string path, string destinationDirectory) +// { +// using var archive = ZipFile.OpenRead(path); +// +// // Absolute path of the destination directory +// var absoluteDestinationPath = Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); +// +// foreach (var entry in archive.Entries) +// { +// if (entry.FullName.EndsWith("desktop.ini", StringComparison.OrdinalIgnoreCase)) +// continue; // Skip the desktop.ini file +// +// // Get the full path of the file +// var destinationPath = Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); +// +// // Check for directory traversal attacks +// if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) +// { +// AbortingUpdate("The file path is outside the destination directory. Please contact the developers."); +// return; +// } +// +// // If the entry is a directory, create it +// if (entry.FullName.EndsWith(Path.AltDirectorySeparatorChar)) +// { +// Directory.CreateDirectory(destinationPath); +// continue; +// } +// +// // Create directory if it doesn't exist +// var directoryName = Path.GetDirectoryName(destinationPath); +// if (!string.IsNullOrEmpty(directoryName)) +// Directory.CreateDirectory(directoryName); +// +// // Extract the file +// entry.ExtractToFile(destinationPath, overwrite: true); +// } +// } +// +// public static void AbortingUpdate(string reason) +// { +// new MessageBoxWindow() +// .SetMessageType(MessageBoxWindow.MessageType.Error) +// .SetTitleText("Aborting RR Update") +// .SetInfoText(reason) +// .Show(); +// } +// } From cb4348ee5ee381dd288f1a5047461688f1c3b890 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 11 May 2025 11:22:19 +0200 Subject: [PATCH 04/19] It crashes, but compiles --- .../CustomDistributionSingletonService.cs | 2 +- .../CustomDistributions/IDistribution.cs | 16 ++-- .../CustomDistributions/RetroRewind.cs | 80 +++++++++++-------- WheelWizard/Services/Launcher/RrLauncher.cs | 25 +++--- .../Pages/Settings/OtherSettings.axaml.cs | 10 ++- .../Pages/Settings/SettingsPage.axaml.cs | 7 +- 6 files changed, 78 insertions(+), 62 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs index 9a5e9a3d..3751e59a 100644 --- a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs @@ -9,7 +9,7 @@ public interface ICustomDistributionSingletonService public class CustomDistributionSingletonService : ICustomDistributionSingletonService { public RetroRewind RetroRewind { get; } = new RetroRewind(); - + public List GetAllDistributions() { return [RetroRewind]; diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs index 8a88234f..3ad69f25 100644 --- a/WheelWizard/Features/CustomDistributions/IDistribution.cs +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -9,27 +9,27 @@ public interface IDistribution /// The title of the given distribution. /// public string Title { get; } - + /// /// The name of the primary folder where the distribution is installed within the wheelwizard folder. /// string FolderName { get; } - + /// /// Install the distribution. /// Task Install(); - + /// /// Update the distribution. /// Task Update(); - + Task Remove(); - + + Task Reinstall(); + Task> GetCurrentStatus(); - + SemVersion? GetCurrentVersion(); - - } diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 1925d083..2ca369b5 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -13,11 +13,10 @@ namespace WheelWizard.CustomDistributions; public class RetroRewind : IDistribution { public string Title => "Retro Rewind"; - + // Keep in mind, whenever we download update files from the server, they are actually 1 folder higher, so it contains this folder. public string FolderName => "RetroRewind6"; - - + public async Task Install() { if (GetCurrentVersion() is null) @@ -49,7 +48,7 @@ public async Task Install() await Update(); return Ok(); } - + private static async Task DownloadAndExtractRetroRewind(string tempZipPath) { var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); @@ -70,7 +69,7 @@ private static async Task DownloadAndExtractRetroRewind(string tempZipPath) File.Delete(tempZipPath); } } - + private static async Task BackupOldrksys() { var rrWfc = Path.Combine(GetOldRksys()); @@ -90,8 +89,7 @@ private static async Task BackupOldrksys() var destinationFile = Path.Combine(destinationFolder, "rksys.dat"); await File.WriteAllBytesAsync(destinationFile, datFileData); } - - + private static bool HasOldRksys() { return !string.IsNullOrWhiteSpace(GetOldRksys()); @@ -121,7 +119,7 @@ private static string GetOldRksys() return string.Empty; } - + private static async Task> IsRRUpToDate(SemVersion currentVersion) { var latestVersionResult = await LatestServerVersion(); @@ -131,7 +129,7 @@ private static async Task> IsRRUpToDate(SemVersion current var isUpToDate = currentVersion.ComparePrecedenceTo(latestVersion) >= 0; return isUpToDate; } - + private static async Task> LatestServerVersion() { var response = await HttpClientHelper.GetAsync(Endpoints.RRVersionUrl); @@ -151,7 +149,7 @@ public async Task Update() var isRRUpToDate = await IsRRUpToDate(currentVersion); if (isRRUpToDate.IsFailure) return isRRUpToDate; - + if (isRRUpToDate.Value) { return Ok(); @@ -164,7 +162,7 @@ public async Task Update() var result = await Install(); if (result.IsFailure) return result; - + return await Install(); } return await ApplyUpdates(currentVersion); @@ -174,7 +172,7 @@ public async Task Update() return e; } } - + private static async Task ApplyUpdates(SemVersion currentVersion) { var allVersions = await GetAllVersionData(); @@ -204,7 +202,7 @@ private static async Task ApplyUpdates(SemVersion currentVersio if (success.IsFailure) { progressWindow.Close(); - return(Phrases.PopupText_FailedUpdateApply); + return (Phrases.PopupText_FailedUpdateApply); } // Update the version file after each successful update @@ -214,15 +212,19 @@ private static async Task ApplyUpdates(SemVersion currentVersio progressWindow.Close(); return Ok(); } - + private static void UpdateVersionFile(SemVersion newVersion) { var versionFilePath = Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); File.WriteAllText(versionFilePath, newVersion.ToString()); } - - - private static async Task DownloadAndApplyUpdate(UpdateData update, int totalUpdates, int currentUpdateIndex, ProgressWindow popupWindow) + + private static async Task DownloadAndApplyUpdate( + UpdateData update, + int totalUpdates, + int currentUpdateIndex, + ProgressWindow popupWindow + ) { var tempZipPath = Path.GetTempFileName(); try @@ -246,7 +248,7 @@ private static async Task DownloadAndApplyUpdate(UpdateData upd return Ok(); } - + private static OperationResult ExtractZipFile(string path, string destinationDirectory) { using var archive = ZipFile.OpenRead(path); @@ -265,7 +267,7 @@ private static OperationResult ExtractZipFile(string path, string destinationDir // Check for directory traversal attacks if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) { - return("The file path is outside the destination directory. Please contact the developers."); + return ("The file path is outside the destination directory. Please contact the developers."); } // If the entry is a directory, create it @@ -286,7 +288,7 @@ private static OperationResult ExtractZipFile(string path, string destinationDir return Ok(); } - + private static async Task ApplyFileDeletionsBetweenVersions(SemVersion currentVersion, SemVersion targetVersion) { try @@ -325,15 +327,16 @@ private static async Task ApplyFileDeletionsBetweenVersions(Sem } catch (Exception e) { - return($"Failed to delete files: {e.Message}"); + return ($"Failed to delete files: {e.Message}"); } } - + private struct DeletionData { public SemVersion Version; public string Path; } + private static async Task>> GetFileDeletionList() { var deleteList = new List(); @@ -353,17 +356,13 @@ private static async Task>> GetFileDeletionLi continue; if (!SemVersion.TryParse(deletionVersion, out var parsedVersion)) return "Failed to parse version"; - var deletionData = new DeletionData - { - Version = parsedVersion, - Path = path - }; + var deletionData = new DeletionData { Version = parsedVersion, Path = path }; deleteList.Add(deletionData); } return deleteList; } - + private struct UpdateData { public SemVersion Version; @@ -371,6 +370,7 @@ private struct UpdateData public string Path; public string Description; } + private static async Task> GetAllVersionData() { var versions = new List(); @@ -398,13 +398,13 @@ private static async Task> GetAllVersionData() Version = parsedVersion, Url = url, Path = path, - Description = description + Description = description, }; versions.Add(updateData); } return versions; } - + //todo: see if we can make this generic to the point we dont have to split up deletions and updates private static List GetUpdatesToApply(SemVersion currentVersion, List allVersions) { @@ -420,16 +420,18 @@ private static List GetUpdatesToApply(SemVersion currentVersion, Lis updatesToApply.Reverse(); return updatesToApply; } - - private static List GetDeletionsToApply(SemVersion currentVersion, SemVersion targetVersion, List allDeletions + + private static List GetDeletionsToApply( + SemVersion currentVersion, + SemVersion targetVersion, + List allDeletions ) { var deletionsToApply = new List(); allDeletions = allDeletions.OrderByDescending(d => d.Version).ToList(); foreach (var deletion in allDeletions) { - if (deletion.Version.ComparePrecedenceTo(currentVersion) > 0 && - deletion.Version.ComparePrecedenceTo(targetVersion) <= 0) + if (deletion.Version.ComparePrecedenceTo(currentVersion) > 0 && deletion.Version.ComparePrecedenceTo(targetVersion) <= 0) { deletionsToApply.Add(deletion); } @@ -439,7 +441,6 @@ private static List GetDeletionsToApply(SemVersion currentVersion, return deletionsToApply; } - public Task Remove() { var retroRewindPath = PathManager.RetroRewind6FolderPath; @@ -448,6 +449,15 @@ public Task Remove() return Task.FromResult(Ok()); } + public async Task Reinstall() + { + //Remove and install + var removeResult = await Remove(); + if (removeResult.IsFailure) + return removeResult; + return await Install(); + } + public async Task> GetCurrentStatus() { if (!SettingsHelper.PathsSetupCorrectly()) diff --git a/WheelWizard/Services/Launcher/RrLauncher.cs b/WheelWizard/Services/Launcher/RrLauncher.cs index 26ceb75e..666d489e 100644 --- a/WheelWizard/Services/Launcher/RrLauncher.cs +++ b/WheelWizard/Services/Launcher/RrLauncher.cs @@ -1,4 +1,5 @@ using Avalonia.Threading; +using WheelWizard.CustomDistributions; using WheelWizard.Helpers; using WheelWizard.Models.Enums; using WheelWizard.Resources.Languages; @@ -6,6 +7,7 @@ using WheelWizard.Services.Launcher.Helpers; using WheelWizard.Services.Settings; using WheelWizard.Services.WiiManagement; +using WheelWizard.Shared.DependencyInjection; using WheelWizard.Views.Popups.Generic; namespace WheelWizard.Services.Launcher; @@ -15,6 +17,9 @@ public class RrLauncher : ILauncher public string GameTitle { get; } = "Retro Rewind"; private static string RrLaunchJsonFilePath => PathManager.RrLaunchJsonFilePath; + [Inject] + private ICustomDistributionSingletonService CustomDistributionSingletonService { get; set; } = null!; + public async Task Launch() { try @@ -55,25 +60,15 @@ public async Task Launch() } } - public Task Install() => RetroRewindInstaller.InstallRetroRewind(); + public Task Install() => CustomDistributionSingletonService.RetroRewind.Install(); - public Task Update() => RetroRewindUpdater.UpdateRR(); + public Task Update() => CustomDistributionSingletonService.RetroRewind.Update(); public async Task GetCurrentStatus() { - if (!SettingsHelper.PathsSetupCorrectly()) - return WheelWizardStatus.ConfigNotFinished; - - var serverEnabled = await HttpClientHelper.GetAsync(Endpoints.RRUrl); - var rrInstalled = RetroRewindInstaller.IsRetroRewindInstalled(); - - if (!serverEnabled.Succeeded) - return rrInstalled ? WheelWizardStatus.NoServerButInstalled : WheelWizardStatus.NoServer; - - if (!rrInstalled) + var statusResult = await CustomDistributionSingletonService.RetroRewind.GetCurrentStatus(); + if (statusResult.IsFailure) return WheelWizardStatus.NotInstalled; - - var retroRewindUpToDate = await RetroRewindUpdater.IsRRUpToDate(RetroRewindInstaller.CurrentRRVersion()); - return !retroRewindUpToDate ? WheelWizardStatus.OutOfDate : WheelWizardStatus.Ready; + return statusResult.Value; } } diff --git a/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs index 2f2297ee..eb19dd6b 100644 --- a/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs @@ -1,19 +1,24 @@ using Avalonia.Controls; using Avalonia.Interactivity; +using WheelWizard.CustomDistributions; using WheelWizard.Helpers; using WheelWizard.Models.Settings; using WheelWizard.Resources.Languages; using WheelWizard.Services; using WheelWizard.Services.Installation; using WheelWizard.Services.Settings; +using WheelWizard.Shared.DependencyInjection; using WheelWizard.Views.Popups.Generic; namespace WheelWizard.Views.Pages.Settings; -public partial class OtherSettings : UserControl +public partial class OtherSettings : UserControlBase { private readonly bool _settingsAreDisabled; + [Inject] + private ICustomDistributionSingletonService CustomDistributionSingletonService { get; set; } = null!; + public OtherSettings() { InitializeComponent(); @@ -126,7 +131,8 @@ private async void WhWzLanguageDropdown_OnSelectionChanged(object? sender, Selec ViewUtils.RefreshWindow(); } - private async void Reinstall_RetroRewind(object sender, RoutedEventArgs e) => await RetroRewindInstaller.ReinstallRR(); + private async void Reinstall_RetroRewind(object sender, RoutedEventArgs e) => + await CustomDistributionSingletonService.RetroRewind.Reinstall(); private void OpenSaveFolder_OnClick(object? sender, RoutedEventArgs e) { diff --git a/WheelWizard/Views/Pages/Settings/SettingsPage.axaml.cs b/WheelWizard/Views/Pages/Settings/SettingsPage.axaml.cs index 41196466..f856327f 100644 --- a/WheelWizard/Views/Pages/Settings/SettingsPage.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/SettingsPage.axaml.cs @@ -1,7 +1,9 @@ using Avalonia.Controls; using Avalonia.Interactivity; using WheelWizard.Branding; +using WheelWizard.CustomDistributions; using WheelWizard.Services.Installation; +using WheelWizard.Shared.DependencyInjection; using WheelWizard.Views.Popups; namespace WheelWizard.Views.Pages.Settings; @@ -11,11 +13,14 @@ public partial class SettingsPage : UserControlBase public SettingsPage() : this(new WhWzSettings()) { } + [Inject] + private ICustomDistributionSingletonService CustomDistributionSingletonService { get; set; } = null!; + public SettingsPage(UserControl initialSettingsPage) { InitializeComponent(); - RrVersionText.Text = "RR: " + RetroRewindInstaller.CurrentRRVersion(); + RrVersionText.Text = "RR: " + CustomDistributionSingletonService.RetroRewind.GetCurrentVersion(); var part1 = "Release"; var part2 = "Unknown OS"; From 7631c83ef73861210f90d2438694518393e52f7b Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 11 May 2025 14:25:29 +0200 Subject: [PATCH 05/19] Works with new systrem now --- .../Features/CustomDistributions/RetroRewind.cs | 16 +++++++++------- WheelWizard/Services/Launcher/RrLauncher.cs | 8 +++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 2ca369b5..4aeb6ff2 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -72,7 +72,7 @@ private static async Task DownloadAndExtractRetroRewind(string tempZipPath) private static async Task BackupOldrksys() { - var rrWfc = Path.Combine(GetOldRksys()); + var rrWfc = GetOldRksys(); if (!Directory.Exists(rrWfc)) return; var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); @@ -134,7 +134,11 @@ private static async Task> LatestServerVersion() { var response = await HttpClientHelper.GetAsync(Endpoints.RRVersionUrl); if (response.Succeeded && response.Content != null) - return SemVersion.Parse(response.Content); + { + var result = response.Content.Split('\n').Last().Split(' ')[0]; + return SemVersion.Parse(result); + } + return "Failed to check for updates"; } @@ -162,8 +166,6 @@ public async Task Update() var result = await Install(); if (result.IsFailure) return result; - - return await Install(); } return await ApplyUpdates(currentVersion); } @@ -416,8 +418,6 @@ private static List GetUpdatesToApply(SemVersion currentVersion, Lis updatesToApply.Add(update); } } - // Sort the updates by version in descending order - updatesToApply.Reverse(); return updatesToApply; } @@ -428,7 +428,9 @@ List allDeletions ) { var deletionsToApply = new List(); - allDeletions = allDeletions.OrderByDescending(d => d.Version).ToList(); + allDeletions = allDeletions + .OrderByDescending(d => d.Version, Comparer.Create((a, b) => a.ComparePrecedenceTo(b))) + .ToList(); foreach (var deletion in allDeletions) { if (deletion.Version.ComparePrecedenceTo(currentVersion) > 0 && deletion.Version.ComparePrecedenceTo(targetVersion) <= 0) diff --git a/WheelWizard/Services/Launcher/RrLauncher.cs b/WheelWizard/Services/Launcher/RrLauncher.cs index 666d489e..96ce1654 100644 --- a/WheelWizard/Services/Launcher/RrLauncher.cs +++ b/WheelWizard/Services/Launcher/RrLauncher.cs @@ -8,6 +8,7 @@ using WheelWizard.Services.Settings; using WheelWizard.Services.WiiManagement; using WheelWizard.Shared.DependencyInjection; +using WheelWizard.Views; using WheelWizard.Views.Popups.Generic; namespace WheelWizard.Services.Launcher; @@ -18,7 +19,8 @@ public class RrLauncher : ILauncher private static string RrLaunchJsonFilePath => PathManager.RrLaunchJsonFilePath; [Inject] - private ICustomDistributionSingletonService CustomDistributionSingletonService { get; set; } = null!; + private ICustomDistributionSingletonService CustomDistributionSingletonService { get; set; } = + App.Services.GetRequiredService(); public async Task Launch() { @@ -66,6 +68,10 @@ public async Task Launch() public async Task GetCurrentStatus() { + if (CustomDistributionSingletonService == null) + { + return WheelWizardStatus.NotInstalled; + } var statusResult = await CustomDistributionSingletonService.RetroRewind.GetCurrentStatus(); if (statusResult.IsFailure) return WheelWizardStatus.NotInstalled; From 06656ba1c173ba0819bbc476826ba7f5963797ce Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 11 May 2025 19:58:16 +0200 Subject: [PATCH 06/19] Better download logic --- .../CustomDistributions/RetroRewind.cs | 54 +++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 4aeb6ff2..f37a3eb9 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -44,29 +44,66 @@ public async Task Install() .ShowDialog(); return "Could not connect to the server"; } - await DownloadAndExtractRetroRewind(PathManager.RetroRewindTempFile); + await DownloadAndExtractRetroRewind(); await Update(); return Ok(); } - private static async Task DownloadAndExtractRetroRewind(string tempZipPath) + private async Task DownloadAndExtractRetroRewind() { var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); progressWindow.Show(); + // path to the downloaded .zip + var tempZipPath = PathManager.RetroRewindTempFile; + // where we'll do the extraction + var tempExtractionPath = PathManager.TempModsFolderPath; + // where the final RR folder should live + var finalDestination = Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName); + try { + // 1) Download + if (Directory.Exists(tempExtractionPath)) + Directory.Delete(tempExtractionPath, recursive: true); + Directory.CreateDirectory(tempExtractionPath); + await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); + + // 2) Extract progressWindow.SetExtraText(Common.State_Extracting); - var extractionPath = PathManager.RiivolutionWhWzFolderPath; - ZipFile.ExtractToDirectory(tempZipPath, extractionPath, true); + + ZipFile.ExtractToDirectory(tempZipPath, tempExtractionPath, overwriteFiles: true); + + // 3) Locate the extracted sub-folder + var sourceFolder = Path.Combine(tempExtractionPath, FolderName); + if (!Directory.Exists(sourceFolder)) + { + var directories = Directory.GetDirectories(tempExtractionPath); + if (directories.Length == 1) + sourceFolder = directories[0]; + else + throw new DirectoryNotFoundException($"Could not find a '{FolderName}' folder inside {tempExtractionPath}"); + } + + // 4) Replace existing install, if any + if (Directory.Exists(finalDestination)) + Directory.Delete(finalDestination, recursive: true); + + // 5) Move into place + Directory.Move(sourceFolder, finalDestination); } finally { + // always clean up UI and temp files progressWindow.Close(); + if (File.Exists(tempZipPath)) File.Delete(tempZipPath); + + if (Directory.Exists(tempExtractionPath)) + Directory.Delete(tempExtractionPath, recursive: true); } } @@ -97,6 +134,7 @@ private static bool HasOldRksys() private static string GetOldRksys() { + // todo, maybe we should check for the existence of the file instead of the folder? and also find the oldest one? var rrWfcPaths = new[] { Path.Combine(PathManager.SaveFolderPath), @@ -107,7 +145,7 @@ private static string GetOldRksys() Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), }; - + foreach (var rrWfc in rrWfcPaths) { if (!Directory.Exists(rrWfc)) @@ -162,10 +200,8 @@ public async Task Update() //if current version is below 3.2.6 we need to do a full reinstall if (currentVersion.ComparePrecedenceTo(new SemVersion(3, 2, 6)) < 0) { - //todo: look at this logic - var result = await Install(); - if (result.IsFailure) - return result; + var result = await Reinstall(); + return result.IsSuccess ? Ok() : result; } return await ApplyUpdates(currentVersion); } From 793cea6a528349c2d03e8e84108a8e8a9cf1a8c4 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 11 May 2025 21:22:53 +0200 Subject: [PATCH 07/19] di --- .../CustomDistributionSingletonService.cs | 10 +++++++++- .../Features/CustomDistributions/RetroRewind.cs | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs index 3751e59a..bca89b95 100644 --- a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs @@ -1,3 +1,5 @@ +using System.IO.Abstractions; + namespace WheelWizard.CustomDistributions; public interface ICustomDistributionSingletonService @@ -8,7 +10,13 @@ public interface ICustomDistributionSingletonService public class CustomDistributionSingletonService : ICustomDistributionSingletonService { - public RetroRewind RetroRewind { get; } = new RetroRewind(); + public IFileSystem FileSystem { get; } + public RetroRewind RetroRewind { get; } + public CustomDistributionSingletonService(IFileSystem fileSystem) + { + FileSystem = fileSystem; + RetroRewind = new RetroRewind(fileSystem); + } public List GetAllDistributions() { diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index f37a3eb9..a6e5d266 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using System.IO.Compression; using System.Text.RegularExpressions; using Semver; @@ -12,6 +13,13 @@ namespace WheelWizard.CustomDistributions; public class RetroRewind : IDistribution { + + private readonly IFileSystem _fileSystem; + public RetroRewind(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + public string Title => "Retro Rewind"; // Keep in mind, whenever we download update files from the server, they are actually 1 folder higher, so it contains this folder. From ccbbdba6510132b5ebab810b3aa12dcd9b1cc6eb Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 15 May 2025 09:10:48 +0200 Subject: [PATCH 08/19] Inject and remove --- .../CustomDistributions/RetroRewind.cs | 135 ++++---- .../Installation/RetroRewindInstaller.cs | 175 ---------- .../Installation/RetroRewindUpdater.cs | 310 ------------------ 3 files changed, 65 insertions(+), 555 deletions(-) delete mode 100644 WheelWizard/Services/Installation/RetroRewindInstaller.cs delete mode 100644 WheelWizard/Services/Installation/RetroRewindUpdater.cs diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index a6e5d266..22b1a085 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -27,7 +27,7 @@ public RetroRewind(IFileSystem fileSystem) public async Task Install() { - if (GetCurrentVersion() is null) + if (GetCurrentVersion() is not null) { var removeResult = await Remove(); if (removeResult.IsFailure) @@ -45,11 +45,6 @@ public async Task Install() var serverResponse = await HttpClientHelper.GetAsync(Endpoints.RRUrl); if (!serverResponse.Succeeded) { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Could not connect to the server") - .SetInfoText(Phrases.PopupText_CouldNotConnectServer) - .ShowDialog(); return "Could not connect to the server"; } await DownloadAndExtractRetroRewind(); @@ -68,14 +63,14 @@ private async Task DownloadAndExtractRetroRewind() // where we'll do the extraction var tempExtractionPath = PathManager.TempModsFolderPath; // where the final RR folder should live - var finalDestination = Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName); + var finalDestination = _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName); try { // 1) Download - if (Directory.Exists(tempExtractionPath)) - Directory.Delete(tempExtractionPath, recursive: true); - Directory.CreateDirectory(tempExtractionPath); + if (_fileSystem.Directory.Exists(tempExtractionPath)) + _fileSystem.Directory.Delete(tempExtractionPath, recursive: true); + _fileSystem.Directory.CreateDirectory(tempExtractionPath); await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); @@ -85,10 +80,10 @@ private async Task DownloadAndExtractRetroRewind() ZipFile.ExtractToDirectory(tempZipPath, tempExtractionPath, overwriteFiles: true); // 3) Locate the extracted sub-folder - var sourceFolder = Path.Combine(tempExtractionPath, FolderName); - if (!Directory.Exists(sourceFolder)) + var sourceFolder = _fileSystem.Path.Combine(tempExtractionPath, FolderName); + if (!_fileSystem.Directory.Exists(sourceFolder)) { - var directories = Directory.GetDirectories(tempExtractionPath); + var directories = _fileSystem.Directory.GetDirectories(tempExtractionPath); if (directories.Length == 1) sourceFolder = directories[0]; else @@ -96,69 +91,69 @@ private async Task DownloadAndExtractRetroRewind() } // 4) Replace existing install, if any - if (Directory.Exists(finalDestination)) - Directory.Delete(finalDestination, recursive: true); + if (_fileSystem.Directory.Exists(finalDestination)) + _fileSystem.Directory.Delete(finalDestination, recursive: true); // 5) Move into place - Directory.Move(sourceFolder, finalDestination); + _fileSystem.Directory.Move(sourceFolder, finalDestination); } finally { // always clean up UI and temp files progressWindow.Close(); - if (File.Exists(tempZipPath)) - File.Delete(tempZipPath); + if (_fileSystem.File.Exists(tempZipPath)) + _fileSystem.File.Delete(tempZipPath); - if (Directory.Exists(tempExtractionPath)) - Directory.Delete(tempExtractionPath, recursive: true); + if (_fileSystem.Directory.Exists(tempExtractionPath)) + _fileSystem.Directory.Delete(tempExtractionPath, recursive: true); } } - private static async Task BackupOldrksys() + private async Task BackupOldrksys() { var rrWfc = GetOldRksys(); - if (!Directory.Exists(rrWfc)) + if (!_fileSystem.Directory.Exists(rrWfc)) return; - var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); + var rksysFiles = _fileSystem.Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); if (rksysFiles.Length == 0) return; var sourceFile = rksysFiles[0]; - var regionFolder = Path.GetDirectoryName(sourceFile); - var regionFolderName = Path.GetFileName(regionFolder); - var datFileData = await File.ReadAllBytesAsync(sourceFile); + var regionFolder = _fileSystem.Path.GetDirectoryName(sourceFile); + var regionFolderName = _fileSystem.Path.GetFileName(regionFolder); + var datFileData = await _fileSystem.File.ReadAllBytesAsync(sourceFile); if (regionFolderName == null) return; - var destinationFolder = Path.Combine(PathManager.SaveFolderPath, regionFolderName); - Directory.CreateDirectory(destinationFolder); - var destinationFile = Path.Combine(destinationFolder, "rksys.dat"); - await File.WriteAllBytesAsync(destinationFile, datFileData); + var destinationFolder = _fileSystem.Path.Combine(PathManager.SaveFolderPath, regionFolderName); + _fileSystem.Directory.CreateDirectory(destinationFolder); + var destinationFile = _fileSystem.Path.Combine(destinationFolder, "rksys.dat"); + await _fileSystem.File.WriteAllBytesAsync(destinationFile, datFileData); } - private static bool HasOldRksys() + private bool HasOldRksys() { return !string.IsNullOrWhiteSpace(GetOldRksys()); } - private static string GetOldRksys() + private string GetOldRksys() { // todo, maybe we should check for the existence of the file instead of the folder? and also find the oldest one? var rrWfcPaths = new[] { - Path.Combine(PathManager.SaveFolderPath), + _fileSystem.Path.Combine(PathManager.SaveFolderPath), // Also consider the folder with upper-case `Save` - Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "Riivolution", "Save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.LoadFolderPath, "Riivolution", "Save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), }; foreach (var rrWfc in rrWfcPaths) { - if (!Directory.Exists(rrWfc)) + if (!_fileSystem.Directory.Exists(rrWfc)) continue; - var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); + var rksysFiles = _fileSystem.Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); if (rksysFiles.Length > 0) return rrWfc; } @@ -219,7 +214,7 @@ public async Task Update() } } - private static async Task ApplyUpdates(SemVersion currentVersion) + private async Task ApplyUpdates(SemVersion currentVersion) { var allVersions = await GetAllVersionData(); var updatesToApply = GetUpdatesToApply(currentVersion, allVersions); @@ -259,20 +254,20 @@ private static async Task ApplyUpdates(SemVersion currentVersio return Ok(); } - private static void UpdateVersionFile(SemVersion newVersion) + private void UpdateVersionFile(SemVersion newVersion) { - var versionFilePath = Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); - File.WriteAllText(versionFilePath, newVersion.ToString()); + var versionFilePath = _fileSystem.Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); + _fileSystem.File.WriteAllText(versionFilePath, newVersion.ToString()); } - private static async Task DownloadAndApplyUpdate( + private async Task DownloadAndApplyUpdate( UpdateData update, int totalUpdates, int currentUpdateIndex, ProgressWindow popupWindow ) { - var tempZipPath = Path.GetTempFileName(); + var tempZipPath = _fileSystem.Path.GetTempFileName(); try { popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); @@ -281,26 +276,26 @@ ProgressWindow popupWindow popupWindow.UpdateProgress(100); popupWindow.SetExtraText(Common.State_Extracting); var destinationDirectoryPath = PathManager.RiivolutionWhWzFolderPath; - Directory.CreateDirectory(destinationDirectoryPath); + _fileSystem.Directory.CreateDirectory(destinationDirectoryPath); ExtractZipFile(finalFile, destinationDirectoryPath); - if (File.Exists(finalFile)) - File.Delete(finalFile); + if (_fileSystem.File.Exists(finalFile)) + _fileSystem.File.Delete(finalFile); } finally { - if (File.Exists(tempZipPath)) - File.Delete(tempZipPath); + if (_fileSystem.File.Exists(tempZipPath)) + _fileSystem.File.Delete(tempZipPath); } return Ok(); } - private static OperationResult ExtractZipFile(string path, string destinationDirectory) + private OperationResult ExtractZipFile(string path, string destinationDirectory) { using var archive = ZipFile.OpenRead(path); // Absolute path of the destination directory - var absoluteDestinationPath = Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); + var absoluteDestinationPath = _fileSystem.Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); foreach (var entry in archive.Entries) { @@ -308,7 +303,7 @@ private static OperationResult ExtractZipFile(string path, string destinationDir continue; // Skip the desktop.ini file // Get the full path of the file - var destinationPath = Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); + var destinationPath = _fileSystem.Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); // Check for directory traversal attacks if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) @@ -317,16 +312,16 @@ private static OperationResult ExtractZipFile(string path, string destinationDir } // If the entry is a directory, create it - if (entry.FullName.EndsWith(Path.AltDirectorySeparatorChar)) + if (entry.FullName.EndsWith(_fileSystem.Path.AltDirectorySeparatorChar)) { - Directory.CreateDirectory(destinationPath); + _fileSystem.Directory.CreateDirectory(destinationPath); continue; } // Create directory if it doesn't exist - var directoryName = Path.GetDirectoryName(destinationPath); + var directoryName = _fileSystem.Path.GetDirectoryName(destinationPath); if (!string.IsNullOrEmpty(directoryName)) - Directory.CreateDirectory(directoryName); + _fileSystem.Directory.CreateDirectory(directoryName); // Extract the file entry.ExtractToFile(destinationPath, overwrite: true); @@ -335,7 +330,7 @@ private static OperationResult ExtractZipFile(string path, string destinationDir return Ok(); } - private static async Task ApplyFileDeletionsBetweenVersions(SemVersion currentVersion, SemVersion targetVersion) + private async Task ApplyFileDeletionsBetweenVersions(SemVersion currentVersion, SemVersion targetVersion) { try { @@ -349,11 +344,11 @@ private static async Task ApplyFileDeletionsBetweenVersions(Sem foreach (var file in deletionsToApply) { - var absoluteDestinationPath = Path.GetFullPath(PathManager.RiivolutionWhWzFolderPath + Path.AltDirectorySeparatorChar); - var filePath = Path.GetFullPath(Path.Combine(absoluteDestinationPath, file.Path.TrimStart('/'))); + var absoluteDestinationPath = _fileSystem.Path.GetFullPath(PathManager.RiivolutionWhWzFolderPath + _fileSystem.Path.AltDirectorySeparatorChar); + var filePath = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(absoluteDestinationPath, file.Path.TrimStart('/'))); //because we are actually getting the path from the server, //we need to make sure we are not getting hacked, so we check if the path is in the riivolution folder - var resolvedPath = Path.GetFullPath(new FileInfo(filePath).FullName); + var resolvedPath = _fileSystem.Path.GetFullPath(new FileInfo(filePath).FullName); if ( !resolvedPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) || !filePath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) @@ -363,10 +358,10 @@ private static async Task ApplyFileDeletionsBetweenVersions(Sem return "Invalid file path detected. Please contact the developers.\n Server error: " + resolvedPath; } - if (File.Exists(filePath)) - File.Delete(filePath); - else if (Directory.Exists(filePath)) - Directory.Delete(filePath, recursive: true); + if (_fileSystem.File.Exists(filePath)) + _fileSystem.File.Delete(filePath); + else if (_fileSystem.Directory.Exists(filePath)) + _fileSystem.Directory.Delete(filePath, recursive: true); } return Ok(); @@ -490,8 +485,8 @@ List allDeletions public Task Remove() { var retroRewindPath = PathManager.RetroRewind6FolderPath; - if (Directory.Exists(retroRewindPath)) - Directory.Delete(retroRewindPath, true); + if (_fileSystem.Directory.Exists(retroRewindPath)) + _fileSystem.Directory.Delete(retroRewindPath, true); return Task.FromResult(Ok()); } @@ -530,10 +525,10 @@ public async Task> GetCurrentStatus() public SemVersion? GetCurrentVersion() { var versionFilePath = PathManager.RetroRewindVersionFile; - if (!File.Exists(versionFilePath)) + if (!_fileSystem.File.Exists(versionFilePath)) return null; - var versionText = File.ReadAllText(versionFilePath).Trim(); + var versionText = _fileSystem.File.ReadAllText(versionFilePath).Trim(); var versionPattern = @"^\d+\.\d+\.\d+$"; if (!Regex.IsMatch(versionText, versionPattern)) return null; diff --git a/WheelWizard/Services/Installation/RetroRewindInstaller.cs b/WheelWizard/Services/Installation/RetroRewindInstaller.cs deleted file mode 100644 index 16701e4d..00000000 --- a/WheelWizard/Services/Installation/RetroRewindInstaller.cs +++ /dev/null @@ -1,175 +0,0 @@ -// using System.IO.Compression; -// using System.Text.RegularExpressions; -// using WheelWizard.Helpers; -// using WheelWizard.Resources.Languages; -// using WheelWizard.Views.Popups.Generic; -// -// namespace WheelWizard.Services.Installation; -// -// public static class RetroRewindInstaller -// { -// private static readonly string NotInstalledVersion = "Not Installed"; -// -// public static bool IsRetroRewindInstalled() => CurrentRRVersion() != NotInstalledVersion; -// -// public static string CurrentRRVersion() -// { -// var versionFilePath = PathManager.RetroRewindVersionFile; -// if (!File.Exists(versionFilePath)) -// return NotInstalledVersion; -// -// var versionText = File.ReadAllText(versionFilePath).Trim(); -// var versionPattern = @"^\d+\.\d+\.\d+$"; -// if (!Regex.IsMatch(versionText, versionPattern)) -// return NotInstalledVersion; -// -// return versionText; -// } -// -// public static async Task HandleNotInstalled() -// { -// var result = await new YesNoWindow() -// .SetMainText(Phrases.PopupText_RRNotDeterment) -// .SetExtraText(Phrases.PopupText_DownloadRR) -// .AwaitAnswer(); -// -// if (!result) -// return false; -// -// await InstallRetroRewind(); -// return true; -// } -// -// public static async Task HandleOldVersion() -// { -// var result = await new YesNoWindow() -// .SetMainText(Phrases.PopupText_RRToOld) -// .SetExtraText(Phrases.PopupText_ReinstallRR) -// .AwaitAnswer(); -// -// if (!result) -// return false; -// -// await InstallRetroRewind(); -// return true; -// } -// -// public static async Task InstallRetroRewind() -// { -// if (IsRetroRewindInstalled()) -// DeleteExistingRetroRewind(); -// -// if (HasOldRksys()) -// { -// var rksysQuestion = new YesNoWindow() -// .SetMainText(Phrases.PopupText_OldRksysFound) -// .SetExtraText(Phrases.PopupText_OldRksysFoundExplained); -// if (await rksysQuestion.AwaitAnswer()) -// await BackupOldrksys(); -// } -// var serverResponse = await HttpClientHelper.GetAsync(Endpoints.RRUrl); -// if (!serverResponse.Succeeded) -// { -// await new MessageBoxWindow() -// .SetMessageType(MessageBoxWindow.MessageType.Warning) -// .SetTitleText("Could not connect to the server") -// .SetInfoText(Phrases.PopupText_CouldNotConnectServer) -// .ShowDialog(); -// return; -// } -// await DownloadAndExtractRetroRewind(PathManager.RetroRewindTempFile); -// await RetroRewindUpdater.UpdateRR(); -// } -// -// public static async Task ReinstallRR() -// { -// var result = await new YesNoWindow() -// .SetMainText(Phrases.PopupText_ReinstallRR) -// .SetExtraText(Phrases.PopupText_ReinstallQuestion) -// .AwaitAnswer(); -// -// if (!result) -// return; -// -// DeleteExistingRetroRewind(); -// await InstallRetroRewind(); -// } -// -// private static async Task DownloadAndExtractRetroRewind(string tempZipPath) -// { -// var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); -// progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); -// progressWindow.Show(); -// -// try -// { -// await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); -// progressWindow.SetExtraText(Common.State_Extracting); -// var extractionPath = PathManager.RiivolutionWhWzFolderPath; -// ZipFile.ExtractToDirectory(tempZipPath, extractionPath, true); -// } -// finally -// { -// progressWindow.Close(); -// if (File.Exists(tempZipPath)) -// File.Delete(tempZipPath); -// } -// } -// -// private static bool HasOldRksys() -// { -// return !string.IsNullOrWhiteSpace(GetOldRksys()); -// } -// -// private static string GetOldRksys() -// { -// var rrWfcPaths = new[] -// { -// Path.Combine(PathManager.SaveFolderPath), -// // Also consider the folder with upper-case `Save` -// Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), -// Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), -// Path.Combine(PathManager.LoadFolderPath, "Riivolution", "Save", "RetroWFC"), -// Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), -// Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), -// }; -// -// foreach (var rrWfc in rrWfcPaths) -// { -// if (!Directory.Exists(rrWfc)) -// continue; -// var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); -// if (rksysFiles.Length > 0) -// return rrWfc; -// } -// -// return string.Empty; -// } -// -// private static async Task BackupOldrksys() -// { -// var rrWfc = Path.Combine(GetOldRksys()); -// if (!Directory.Exists(rrWfc)) -// return; -// var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); -// if (rksysFiles.Length == 0) -// return; -// var sourceFile = rksysFiles[0]; -// var regionFolder = Path.GetDirectoryName(sourceFile); -// var regionFolderName = Path.GetFileName(regionFolder); -// var datFileData = await File.ReadAllBytesAsync(sourceFile); -// if (regionFolderName == null) -// return; -// var destinationFolder = Path.Combine(PathManager.SaveFolderPath, regionFolderName); -// Directory.CreateDirectory(destinationFolder); -// var destinationFile = Path.Combine(destinationFolder, "rksys.dat"); -// await File.WriteAllBytesAsync(destinationFile, datFileData); -// } -// -// private static void DeleteExistingRetroRewind() -// { -// var retroRewindPath = PathManager.RetroRewind6FolderPath; -// if (Directory.Exists(retroRewindPath)) -// Directory.Delete(retroRewindPath, true); -// } -// } diff --git a/WheelWizard/Services/Installation/RetroRewindUpdater.cs b/WheelWizard/Services/Installation/RetroRewindUpdater.cs deleted file mode 100644 index c771b5b7..00000000 --- a/WheelWizard/Services/Installation/RetroRewindUpdater.cs +++ /dev/null @@ -1,310 +0,0 @@ -// using System.IO.Compression; -// using WheelWizard.Helpers; -// using WheelWizard.Resources.Languages; -// using WheelWizard.Views.Popups.Generic; -// -// namespace WheelWizard.Services.Installation; -// -// public static class RetroRewindUpdater -// { -// public static async Task IsRRUpToDate(string currentVersion) -// { -// var latestVersion = await GetLatestVersionString(); -// return currentVersion.Trim() == latestVersion.Trim(); -// } -// -// private static async Task GetLatestVersionString() -// { -// var response = await HttpClientHelper.GetAsync(Endpoints.RRVersionUrl); -// if (response.Succeeded && response.Content != null) -// return response.Content.Split('\n').Last().Split(' ')[0]; -// new YesNoWindow().SetMainText(Phrases.PopupText_FailCheckUpdates).AwaitAnswer(); -// return "Failed to check for updates"; -// } -// -// public static async Task UpdateRR() -// { -// try -// { -// if (!RetroRewindInstaller.IsRetroRewindInstalled()) -// return await RetroRewindInstaller.HandleNotInstalled(); -// -// var currentVersion = RetroRewindInstaller.CurrentRRVersion(); -// if (await IsRRUpToDate(currentVersion)) -// { -// await new MessageBoxWindow() -// .SetMessageType(MessageBoxWindow.MessageType.Message) -// .SetTitleText(Phrases.PopupText_RRUpToDate) -// .SetInfoText(Phrases.PopupText_RRUpToDate) -// .ShowDialog(); -// return true; -// } -// -// //if current version is below 3.2.6 we need to do a full reinstall -// if (CompareVersions(currentVersion, "3.2.6") < 0) -// return await RetroRewindInstaller.HandleOldVersion(); -// return await ApplyUpdates(currentVersion); -// } -// catch (Exception e) -// { -// AbortingUpdate($"Reason: {e.Message}"); -// return false; -// } -// } -// -// private static async Task ApplyUpdates(string currentVersion) -// { -// var allVersions = await GetAllVersionData(); -// var updatesToApply = GetUpdatesToApply(currentVersion, allVersions); -// -// var progressWindow = new ProgressWindow(Phrases.PopupText_UpdateRR); -// progressWindow.Show(); -// -// // Step 1: Get the version we are updating to -// var targetVersion = updatesToApply.Any() ? updatesToApply.Last().Version : currentVersion; -// -// // Step 2: Apply file deletions for versions between current and targetVersion -// var deleteSuccess = await ApplyFileDeletionsBetweenVersions(currentVersion, targetVersion); -// if (!deleteSuccess) -// { -// AbortingUpdate(Phrases.PopupText_FailedUpdateDelete); -// progressWindow.Close(); -// return false; -// } -// -// // Step 3: Download and apply the updates (if any) -// for (var i = 0; i < updatesToApply.Count; i++) -// { -// var update = updatesToApply[i]; -// -// var success = await DownloadAndApplyUpdate(update, updatesToApply.Count, i + 1, progressWindow); -// if (!success) -// { -// progressWindow.Close(); -// AbortingUpdate(Phrases.PopupText_FailedUpdateApply); -// return false; -// } -// -// // Update the version file after each successful update -// UpdateVersionFile(update.Version); -// } -// -// progressWindow.Close(); -// return true; -// } -// -// private static async Task ApplyFileDeletionsBetweenVersions(string currentVersion, string targetVersion) -// { -// try -// { -// var deleteList = await GetFileDeletionList(); -// var deletionsToApply = GetDeletionsToApply(currentVersion, targetVersion, deleteList); -// -// foreach (var file in deletionsToApply) -// { -// var absoluteDestinationPath = Path.GetFullPath(PathManager.RiivolutionWhWzFolderPath + Path.AltDirectorySeparatorChar); -// var filePath = Path.GetFullPath(Path.Combine(absoluteDestinationPath, file.Path.TrimStart('/'))); -// //because we are actually getting the path from the server, -// //we need to make sure we are not getting hacked, so we check if the path is in the riivolution folder -// var resolvedPath = Path.GetFullPath(new FileInfo(filePath).FullName); -// if ( -// !resolvedPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) -// || !filePath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) -// || file.Path.Contains("..") -// ) -// { -// AbortingUpdate("Invalid file path detected. Please contact the developers.\n Server error: " + resolvedPath); -// return false; -// } -// -// if (File.Exists(filePath)) -// File.Delete(filePath); -// else if (Directory.Exists(filePath)) -// Directory.Delete(filePath, recursive: true); -// } -// -// return true; -// } -// catch (Exception e) -// { -// AbortingUpdate($"Failed to delete files: {e.Message}"); -// return false; -// } -// } -// -// private static List<(string Version, string Path)> GetDeletionsToApply( -// string currentVersion, -// string targetVersion, -// List<(string Version, string Path)> allDeletions -// ) -// { -// var deletionsToApply = new List<(string Version, string Path)>(); -// allDeletions.Sort((a, b) => CompareVersions(b.Version, a.Version)); // Sort in descending order -// foreach (var deletion in allDeletions) -// { -// if (CompareVersions(deletion.Version, currentVersion) > 0 && CompareVersions(deletion.Version, targetVersion) <= 0) -// { -// deletionsToApply.Add(deletion); -// } -// } -// -// deletionsToApply.Reverse(); -// return deletionsToApply; -// } -// -// private static async Task> GetFileDeletionList() -// { -// var deleteList = new List<(string Version, string Path)>(); -// -// using var httpClient = new HttpClient(); -// var deleteListText = await httpClient.GetStringAsync(Endpoints.RRVersionDeleteUrl); -// var lines = deleteListText.Split('\n', StringSplitOptions.RemoveEmptyEntries); -// -// foreach (var line in lines) -// { -// var parts = line.Split(' ', 2); -// if (parts.Length < 2) -// continue; -// deleteList.Add((parts[0].Trim(), parts[1].Trim())); -// } -// -// return deleteList; -// } -// -// private static void UpdateVersionFile(string newVersion) -// { -// var versionFilePath = Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); -// File.WriteAllText(versionFilePath, newVersion); -// } -// -// private static async Task> GetAllVersionData() -// { -// var versions = new List<(string Version, string Url, string Path, string Description)>(); -// -// using var httpClient = new HttpClient(); -// var allVersionsText = await httpClient.GetStringAsync(Endpoints.RRVersionUrl); -// var lines = allVersionsText.Split('\n', StringSplitOptions.RemoveEmptyEntries); -// -// foreach (var line in lines) -// { -// var parts = line.Split(' ', 4); -// if (parts.Length < 4) -// continue; -// versions.Add((parts[0].Trim(), parts[1].Trim(), parts[2].Trim(), parts[3].Trim())); -// } -// -// return versions; -// } -// -// private static List<(string Version, string Url, string Path, string Description)> GetUpdatesToApply( -// string currentVersion, -// List<(string Version, string Url, string Path, string Description)> allVersions -// ) -// { -// var updatesToApply = new List<(string Version, string Url, string Path, string Description)>(); -// allVersions.Sort((a, b) => CompareVersions(b.Version, a.Version)); // Sort in descending order -// foreach (var version in allVersions) -// { -// if (CompareVersions(version.Version, currentVersion) > 0) -// updatesToApply.Add(version); -// else -// break; -// } -// -// updatesToApply.Reverse(); -// return updatesToApply; -// } -// -// private static int CompareVersions(string v1, string v2) -// { -// var parts1 = v1.Split('.').Select(int.Parse).ToArray(); -// var parts2 = v2.Split('.').Select(int.Parse).ToArray(); -// for (var i = 0; i < Math.Max(parts1.Length, parts2.Length); i++) -// { -// var p1 = i < parts1.Length ? parts1[i] : 0; -// var p2 = i < parts2.Length ? parts2[i] : 0; -// if (p1 != p2) -// return p1.CompareTo(p2); -// } -// -// return 0; -// } -// -// private static async Task DownloadAndApplyUpdate( -// (string Version, string Url, string Path, string Description) update, -// int totalUpdates, -// int currentUpdateIndex, -// ProgressWindow popupWindow -// ) -// { -// var tempZipPath = Path.GetTempFileName(); -// try -// { -// popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); -// var finalFile = await DownloadHelper.DownloadToLocationAsync(update.Url, tempZipPath, popupWindow); -// -// popupWindow.UpdateProgress(100); -// popupWindow.SetExtraText(Common.State_Extracting); -// var destinationDirectoryPath = PathManager.RiivolutionWhWzFolderPath; -// Directory.CreateDirectory(destinationDirectoryPath); -// ExtractZipFile(finalFile, destinationDirectoryPath); -// if (File.Exists(finalFile)) -// File.Delete(finalFile); -// } -// finally -// { -// if (File.Exists(tempZipPath)) -// File.Delete(tempZipPath); -// } -// -// return true; -// } -// -// private static void ExtractZipFile(string path, string destinationDirectory) -// { -// using var archive = ZipFile.OpenRead(path); -// -// // Absolute path of the destination directory -// var absoluteDestinationPath = Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); -// -// foreach (var entry in archive.Entries) -// { -// if (entry.FullName.EndsWith("desktop.ini", StringComparison.OrdinalIgnoreCase)) -// continue; // Skip the desktop.ini file -// -// // Get the full path of the file -// var destinationPath = Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); -// -// // Check for directory traversal attacks -// if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) -// { -// AbortingUpdate("The file path is outside the destination directory. Please contact the developers."); -// return; -// } -// -// // If the entry is a directory, create it -// if (entry.FullName.EndsWith(Path.AltDirectorySeparatorChar)) -// { -// Directory.CreateDirectory(destinationPath); -// continue; -// } -// -// // Create directory if it doesn't exist -// var directoryName = Path.GetDirectoryName(destinationPath); -// if (!string.IsNullOrEmpty(directoryName)) -// Directory.CreateDirectory(directoryName); -// -// // Extract the file -// entry.ExtractToFile(destinationPath, overwrite: true); -// } -// } -// -// public static void AbortingUpdate(string reason) -// { -// new MessageBoxWindow() -// .SetMessageType(MessageBoxWindow.MessageType.Error) -// .SetTitleText("Aborting RR Update") -// .SetInfoText(reason) -// .Show(); -// } -// } From 53c3d6633090b786609ef913383d81fdde6d32fd Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 15 May 2025 09:30:25 +0200 Subject: [PATCH 09/19] Refit --- .../CustomDistributionSingletonService.cs | 7 +- .../CustomDistributionsExtentions.cs | 6 ++ .../Domain/RetroRewindApi.cs | 18 +++++ .../CustomDistributions/RetroRewind.cs | 77 +++++++++++-------- WheelWizard/WheelWizard.csproj | 4 - 5 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs index bca89b95..6232ff7f 100644 --- a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs @@ -1,4 +1,6 @@ using System.IO.Abstractions; +using WheelWizard.CustomDistributions.Domain; +using WheelWizard.Shared.Services; namespace WheelWizard.CustomDistributions; @@ -12,10 +14,11 @@ public class CustomDistributionSingletonService : ICustomDistributionSingletonSe { public IFileSystem FileSystem { get; } public RetroRewind RetroRewind { get; } - public CustomDistributionSingletonService(IFileSystem fileSystem) + + public CustomDistributionSingletonService(IFileSystem fileSystem, IApiCaller api) { FileSystem = fileSystem; - RetroRewind = new RetroRewind(fileSystem); + RetroRewind = new RetroRewind(fileSystem, api); } public List GetAllDistributions() diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs index 861cd638..3174beff 100644 --- a/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs @@ -1,9 +1,15 @@ +using Refit; +using WheelWizard.CustomDistributions.Domain; +using WheelWizard.Services; + namespace WheelWizard.CustomDistributions; public static class CustomDistributionsExtentions { public static IServiceCollection AddCustomDistributionService(this IServiceCollection services) { + services.AddWhWzRefitApi(Endpoints.RRUrl); + services.AddSingleton(); return services; } diff --git a/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs b/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs new file mode 100644 index 00000000..5711db77 --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs @@ -0,0 +1,18 @@ +using Refit; + +namespace WheelWizard.CustomDistributions.Domain; + +public interface IRetroRewindApi +{ + [Get("/RetroRewind/RetroRewind.zip")] + Task DownloadRetroRewindZip(); + + [Get("/RetroRewind/RetroRewindVersion.txt")] + Task GetVersionFile(); + + [Get("/RetroRewind/RetroRewindDelete.txt")] + Task GetDeletionFile(); + + [Get("/")] + Task Ping(); // use to test server reachability +} diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 22b1a085..bad91336 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -2,24 +2,28 @@ using System.IO.Compression; using System.Text.RegularExpressions; using Semver; +using WheelWizard.CustomDistributions.Domain; using WheelWizard.Helpers; using WheelWizard.Models.Enums; using WheelWizard.Resources.Languages; using WheelWizard.Services; using WheelWizard.Services.Settings; +using WheelWizard.Shared.Services; using WheelWizard.Views.Popups.Generic; namespace WheelWizard.CustomDistributions; public class RetroRewind : IDistribution { - private readonly IFileSystem _fileSystem; - public RetroRewind(IFileSystem fileSystem) + private readonly IApiCaller _api; + + public RetroRewind(IFileSystem fileSystem, IApiCaller api) { + _api = api; _fileSystem = fileSystem; } - + public string Title => "Retro Rewind"; // Keep in mind, whenever we download update files from the server, they are actually 1 folder higher, so it contains this folder. @@ -42,17 +46,21 @@ public async Task Install() if (await rksysQuestion.AwaitAnswer()) await BackupOldrksys(); } - var serverResponse = await HttpClientHelper.GetAsync(Endpoints.RRUrl); - if (!serverResponse.Succeeded) + var serverResponse = await _api.CallApiAsync(api => api.Ping()); // actual response doesnt matter + if (serverResponse.IsFailure) { return "Could not connect to the server"; } - await DownloadAndExtractRetroRewind(); - await Update(); + var downloadResult = await DownloadAndExtractRetroRewind(); + if (downloadResult.IsFailure) + return downloadResult; + var updateResult = await Update(); + if (updateResult.IsFailure) + return updateResult; return Ok(); } - private async Task DownloadAndExtractRetroRewind() + private async Task DownloadAndExtractRetroRewind() { var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); @@ -87,7 +95,7 @@ private async Task DownloadAndExtractRetroRewind() if (directories.Length == 1) sourceFolder = directories[0]; else - throw new DirectoryNotFoundException($"Could not find a '{FolderName}' folder inside {tempExtractionPath}"); + return new DirectoryNotFoundException($"Could not find a '{FolderName}' folder inside {tempExtractionPath}"); } // 4) Replace existing install, if any @@ -108,6 +116,7 @@ private async Task DownloadAndExtractRetroRewind() if (_fileSystem.Directory.Exists(tempExtractionPath)) _fileSystem.Directory.Delete(tempExtractionPath, recursive: true); } + return Ok(); } private async Task BackupOldrksys() @@ -148,7 +157,7 @@ private string GetOldRksys() _fileSystem.Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), _fileSystem.Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), }; - + foreach (var rrWfc in rrWfcPaths) { if (!_fileSystem.Directory.Exists(rrWfc)) @@ -161,7 +170,7 @@ private string GetOldRksys() return string.Empty; } - private static async Task> IsRRUpToDate(SemVersion currentVersion) + private async Task> IsRRUpToDate(SemVersion currentVersion) { var latestVersionResult = await LatestServerVersion(); if (latestVersionResult.IsFailure) @@ -171,16 +180,14 @@ private static async Task> IsRRUpToDate(SemVersion current return isUpToDate; } - private static async Task> LatestServerVersion() + private async Task> LatestServerVersion() { - var response = await HttpClientHelper.GetAsync(Endpoints.RRVersionUrl); - if (response.Succeeded && response.Content != null) - { - var result = response.Content.Split('\n').Last().Split(' ')[0]; - return SemVersion.Parse(result); - } - - return "Failed to check for updates"; + var response = await _api.CallApiAsync(api => api.GetVersionFile()); + if (!response.IsSuccess || String.IsNullOrWhiteSpace(response.Value)) + return "Failed to check for updates"; + + var result = response.Value.Split('\n').Last().Split(' ')[0]; + return SemVersion.Parse(result); } public async Task Update() @@ -344,7 +351,9 @@ private async Task ApplyFileDeletionsBetweenVersions(SemVersion foreach (var file in deletionsToApply) { - var absoluteDestinationPath = _fileSystem.Path.GetFullPath(PathManager.RiivolutionWhWzFolderPath + _fileSystem.Path.AltDirectorySeparatorChar); + var absoluteDestinationPath = _fileSystem.Path.GetFullPath( + PathManager.RiivolutionWhWzFolderPath + _fileSystem.Path.AltDirectorySeparatorChar + ); var filePath = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(absoluteDestinationPath, file.Path.TrimStart('/'))); //because we are actually getting the path from the server, //we need to make sure we are not getting hacked, so we check if the path is in the riivolution folder @@ -378,13 +387,14 @@ private struct DeletionData public string Path; } - private static async Task>> GetFileDeletionList() + private async Task>> GetFileDeletionList() { var deleteList = new List(); - - using var httpClient = new HttpClient(); - var deleteListText = await httpClient.GetStringAsync(Endpoints.RRVersionDeleteUrl); - var lines = deleteListText.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + var deleteListOperation = await _api.CallApiAsync(api => api.GetDeletionFile()); + if (deleteListOperation.IsFailure) + return "Failed to get file deletion list"; + var lines = deleteListOperation.Value.Split('\n', StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { @@ -412,13 +422,14 @@ private struct UpdateData public string Description; } - private static async Task> GetAllVersionData() + private async Task> GetAllVersionData() { var versions = new List(); - - using var httpClient = new HttpClient(); - var allVersionsText = await httpClient.GetStringAsync(Endpoints.RRVersionUrl); - var lines = allVersionsText.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + var allVersionsResult = await _api.CallApiAsync(api => api.GetVersionFile()); + if (allVersionsResult.IsFailure) + return new(); + var lines = allVersionsResult.Value.Split('\n', StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { @@ -504,10 +515,10 @@ public async Task> GetCurrentStatus() if (!SettingsHelper.PathsSetupCorrectly()) return WheelWizardStatus.ConfigNotFinished; - var serverEnabled = await HttpClientHelper.GetAsync(Endpoints.RRUrl); + var serverEnabled = await _api.CallApiAsync(api => api.Ping()); var rrInstalled = GetCurrentVersion() != null; - if (!serverEnabled.Succeeded) + if (serverEnabled.IsFailure) return rrInstalled ? WheelWizardStatus.NoServerButInstalled : WheelWizardStatus.NoServer; if (!rrInstalled) diff --git a/WheelWizard/WheelWizard.csproj b/WheelWizard/WheelWizard.csproj index 321d355b..8204c081 100644 --- a/WheelWizard/WheelWizard.csproj +++ b/WheelWizard/WheelWizard.csproj @@ -144,8 +144,4 @@ - - - - From 3154e376cb820d6c739d5c83f8603f27f57cac07 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 15 May 2025 10:31:23 +0200 Subject: [PATCH 10/19] Working on getting rid of the progressbar needed --- .../CustomDistributions/IDistribution.cs | 9 ++--- .../CustomDistributions/RetroRewind.cs | 33 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs index 3ad69f25..27bd9b0b 100644 --- a/WheelWizard/Features/CustomDistributions/IDistribution.cs +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -1,5 +1,6 @@ using Semver; using WheelWizard.Models.Enums; +using WheelWizard.Views.Popups.Generic; namespace WheelWizard.CustomDistributions; @@ -18,16 +19,16 @@ public interface IDistribution /// /// Install the distribution. /// - Task Install(); + Task Install(ProgressWindow? progressWindow = null); /// /// Update the distribution. /// - Task Update(); + Task Update(ProgressWindow? progressWindow = null); - Task Remove(); + Task Remove(ProgressWindow? progressWindow = null); - Task Reinstall(); + Task Reinstall(ProgressWindow? progressWindow = null); Task> GetCurrentStatus(); diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index bad91336..83a2287f 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -29,7 +29,7 @@ public RetroRewind(IFileSystem fileSystem, IApiCaller api) // Keep in mind, whenever we download update files from the server, they are actually 1 folder higher, so it contains this folder. public string FolderName => "RetroRewind6"; - public async Task Install() + public async Task Install(ProgressWindow? progressWindow = null) { if (GetCurrentVersion() is not null) { @@ -60,11 +60,13 @@ public async Task Install() return Ok(); } - private async Task DownloadAndExtractRetroRewind() + private async Task DownloadAndExtractRetroRewind(ProgressWindow? progressWindow = null) { - var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); - progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); - progressWindow.Show(); + if (progressWindow is not null) + { + progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); + progressWindow.Show(); + } // path to the downloaded .zip var tempZipPath = PathManager.RetroRewindTempFile; @@ -83,8 +85,11 @@ private async Task DownloadAndExtractRetroRewind() await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); // 2) Extract - progressWindow.SetExtraText(Common.State_Extracting); - + if (progressWindow is not null) + { + progressWindow.SetExtraText(Common.State_Extracting); + } + ZipFile.ExtractToDirectory(tempZipPath, tempExtractionPath, overwriteFiles: true); // 3) Locate the extracted sub-folder @@ -108,7 +113,7 @@ private async Task DownloadAndExtractRetroRewind() finally { // always clean up UI and temp files - progressWindow.Close(); + progressWindow?.Close(); if (_fileSystem.File.Exists(tempZipPath)) _fileSystem.File.Delete(tempZipPath); @@ -185,12 +190,12 @@ private async Task> LatestServerVersion() var response = await _api.CallApiAsync(api => api.GetVersionFile()); if (!response.IsSuccess || String.IsNullOrWhiteSpace(response.Value)) return "Failed to check for updates"; - + var result = response.Value.Split('\n').Last().Split(' ')[0]; return SemVersion.Parse(result); } - public async Task Update() + public async Task Update(ProgressWindow? progressWindow = null) { try { @@ -390,7 +395,7 @@ private struct DeletionData private async Task>> GetFileDeletionList() { var deleteList = new List(); - + var deleteListOperation = await _api.CallApiAsync(api => api.GetDeletionFile()); if (deleteListOperation.IsFailure) return "Failed to get file deletion list"; @@ -425,7 +430,7 @@ private struct UpdateData private async Task> GetAllVersionData() { var versions = new List(); - + var allVersionsResult = await _api.CallApiAsync(api => api.GetVersionFile()); if (allVersionsResult.IsFailure) return new(); @@ -493,7 +498,7 @@ List allDeletions return deletionsToApply; } - public Task Remove() + public Task Remove(ProgressWindow? progressWindow = null) { var retroRewindPath = PathManager.RetroRewind6FolderPath; if (_fileSystem.Directory.Exists(retroRewindPath)) @@ -501,7 +506,7 @@ public Task Remove() return Task.FromResult(Ok()); } - public async Task Reinstall() + public async Task Reinstall(ProgressWindow? progressWindow = null) { //Remove and install var removeResult = await Remove(); From bb6879506feaee0a7973b7aff193c9183b555331 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 15 May 2025 12:00:50 +0200 Subject: [PATCH 11/19] . --- .../Features/CustomDistributions/Domain/RetroRewindApi.cs | 2 ++ WheelWizard/Helpers/DownloadHelper.cs | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs b/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs index 5711db77..291bb604 100644 --- a/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs +++ b/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs @@ -15,4 +15,6 @@ public interface IRetroRewindApi [Get("/")] Task Ping(); // use to test server reachability + + } diff --git a/WheelWizard/Helpers/DownloadHelper.cs b/WheelWizard/Helpers/DownloadHelper.cs index 34c003d7..e82290d7 100644 --- a/WheelWizard/Helpers/DownloadHelper.cs +++ b/WheelWizard/Helpers/DownloadHelper.cs @@ -22,7 +22,7 @@ public static async Task DownloadToLocationAsync( return toLocationAsync; } - public static async Task DownloadToLocationAsync( + public static async Task DownloadToLocationAsync( string url, string tempFile, ProgressWindow progressPopupWindow, @@ -47,8 +47,6 @@ public static async Task DownloadToLocationAsync( response.EnsureSuccessStatusCode(); if (response.RequestMessage == null || response.RequestMessage.RequestUri == null) { - // Do we want this error? - // new MessageBoxWindow().SetTitleText("Failed to resolve final URL.").Show(); return null; } From 52e692870c80febb046b47e2c26774ded47b5f4f Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 16 May 2025 17:12:25 +0200 Subject: [PATCH 12/19] progresswindow --- .../CustomDistributions/IDistribution.cs | 8 ++-- .../CustomDistributions/RetroRewind.cs | 46 +++++++------------ WheelWizard/Helpers/DownloadHelper.cs | 1 + 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs index 27bd9b0b..bdb9aeec 100644 --- a/WheelWizard/Features/CustomDistributions/IDistribution.cs +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -19,16 +19,16 @@ public interface IDistribution /// /// Install the distribution. /// - Task Install(ProgressWindow? progressWindow = null); + Task Install(ProgressWindow progressWindow); /// /// Update the distribution. /// - Task Update(ProgressWindow? progressWindow = null); + Task Update(ProgressWindow progressWindow ); - Task Remove(ProgressWindow? progressWindow = null); + Task Remove(ProgressWindow progressWindow); - Task Reinstall(ProgressWindow? progressWindow = null); + Task Reinstall(ProgressWindow progressWindow); Task> GetCurrentStatus(); diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 83a2287f..4a52ee08 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -29,11 +29,11 @@ public RetroRewind(IFileSystem fileSystem, IApiCaller api) // Keep in mind, whenever we download update files from the server, they are actually 1 folder higher, so it contains this folder. public string FolderName => "RetroRewind6"; - public async Task Install(ProgressWindow? progressWindow = null) + public async Task Install(ProgressWindow progressWindow) { if (GetCurrentVersion() is not null) { - var removeResult = await Remove(); + var removeResult = await Remove(progressWindow); if (removeResult.IsFailure) return removeResult; } @@ -51,23 +51,18 @@ public async Task Install(ProgressWindow? progressWindow = null { return "Could not connect to the server"; } - var downloadResult = await DownloadAndExtractRetroRewind(); + var downloadResult = await DownloadAndExtractRetroRewind(progressWindow); if (downloadResult.IsFailure) return downloadResult; - var updateResult = await Update(); + var updateResult = await Update(progressWindow); if (updateResult.IsFailure) return updateResult; return Ok(); } - private async Task DownloadAndExtractRetroRewind(ProgressWindow? progressWindow = null) + private async Task DownloadAndExtractRetroRewind(ProgressWindow progressWindow) { - if (progressWindow is not null) - { - progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); - progressWindow.Show(); - } - + progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); // path to the downloaded .zip var tempZipPath = PathManager.RetroRewindTempFile; // where we'll do the extraction @@ -85,10 +80,8 @@ private async Task DownloadAndExtractRetroRewind(ProgressWindow await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); // 2) Extract - if (progressWindow is not null) - { - progressWindow.SetExtraText(Common.State_Extracting); - } + progressWindow.SetExtraText(Common.State_Extracting); + ZipFile.ExtractToDirectory(tempZipPath, tempExtractionPath, overwriteFiles: true); @@ -195,13 +188,13 @@ private async Task> LatestServerVersion() return SemVersion.Parse(result); } - public async Task Update(ProgressWindow? progressWindow = null) + public async Task Update(ProgressWindow progressWindow) { try { var currentVersion = GetCurrentVersion(); if (currentVersion == null) - return await Install(); + return await Install(progressWindow); var isRRUpToDate = await IsRRUpToDate(currentVersion); if (isRRUpToDate.IsFailure) @@ -215,10 +208,10 @@ public async Task Update(ProgressWindow? progressWindow = null) //if current version is below 3.2.6 we need to do a full reinstall if (currentVersion.ComparePrecedenceTo(new SemVersion(3, 2, 6)) < 0) { - var result = await Reinstall(); + var result = await Reinstall(progressWindow); return result.IsSuccess ? Ok() : result; } - return await ApplyUpdates(currentVersion); + return await ApplyUpdates(currentVersion, progressWindow); } catch (Exception e) { @@ -226,15 +219,10 @@ public async Task Update(ProgressWindow? progressWindow = null) } } - private async Task ApplyUpdates(SemVersion currentVersion) + private async Task ApplyUpdates(SemVersion currentVersion, ProgressWindow progressWindow) { var allVersions = await GetAllVersionData(); var updatesToApply = GetUpdatesToApply(currentVersion, allVersions); - - // todo: This progressbar should not be here in this context, this makes this untestable - var progressWindow = new ProgressWindow(Phrases.PopupText_UpdateRR); - progressWindow.Show(); - // Step 1: Get the version we are updating to var targetVersion = updatesToApply.Any() ? updatesToApply.Last().Version : currentVersion; @@ -498,7 +486,7 @@ List allDeletions return deletionsToApply; } - public Task Remove(ProgressWindow? progressWindow = null) + public Task Remove(ProgressWindow progressWindow) { var retroRewindPath = PathManager.RetroRewind6FolderPath; if (_fileSystem.Directory.Exists(retroRewindPath)) @@ -506,13 +494,13 @@ public Task Remove(ProgressWindow? progressWindow = null) return Task.FromResult(Ok()); } - public async Task Reinstall(ProgressWindow? progressWindow = null) + public async Task Reinstall(ProgressWindow progressWindow) { //Remove and install - var removeResult = await Remove(); + var removeResult = await Remove(progressWindow); if (removeResult.IsFailure) return removeResult; - return await Install(); + return await Install(progressWindow); } public async Task> GetCurrentStatus() diff --git a/WheelWizard/Helpers/DownloadHelper.cs b/WheelWizard/Helpers/DownloadHelper.cs index e82290d7..ef1c9864 100644 --- a/WheelWizard/Helpers/DownloadHelper.cs +++ b/WheelWizard/Helpers/DownloadHelper.cs @@ -2,6 +2,7 @@ namespace WheelWizard.Helpers; +// todo: Delete this static class and write a service for it. public static class DownloadHelper { private const int MaxRetries = 5; From e0634cc82691f415ed6c3c207f236621f0c29d52 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 16 May 2025 17:49:05 +0200 Subject: [PATCH 13/19] async --- .../CustomDistributions/IDistribution.cs | 10 ++++----- .../CustomDistributions/RetroRewind.cs | 22 +++++++++---------- WheelWizard/Services/Launcher/RrLauncher.cs | 20 ++++++++++++++--- .../Pages/Settings/OtherSettings.axaml.cs | 2 +- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs index bdb9aeec..f40c3e6c 100644 --- a/WheelWizard/Features/CustomDistributions/IDistribution.cs +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -19,18 +19,18 @@ public interface IDistribution /// /// Install the distribution. /// - Task Install(ProgressWindow progressWindow); + Task InstallAsync(ProgressWindow progressWindow); /// /// Update the distribution. /// - Task Update(ProgressWindow progressWindow ); + Task UpdateAsync(ProgressWindow progressWindow ); - Task Remove(ProgressWindow progressWindow); + Task RemoveAsync(ProgressWindow progressWindow); - Task Reinstall(ProgressWindow progressWindow); + Task ReinstallAsync(ProgressWindow progressWindow); - Task> GetCurrentStatus(); + Task> GetCurrentStatusAsync(); SemVersion? GetCurrentVersion(); } diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 4a52ee08..9bc34e14 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -29,11 +29,11 @@ public RetroRewind(IFileSystem fileSystem, IApiCaller api) // Keep in mind, whenever we download update files from the server, they are actually 1 folder higher, so it contains this folder. public string FolderName => "RetroRewind6"; - public async Task Install(ProgressWindow progressWindow) + public async Task InstallAsync(ProgressWindow progressWindow) { if (GetCurrentVersion() is not null) { - var removeResult = await Remove(progressWindow); + var removeResult = await RemoveAsync(progressWindow); if (removeResult.IsFailure) return removeResult; } @@ -54,7 +54,7 @@ public async Task Install(ProgressWindow progressWindow) var downloadResult = await DownloadAndExtractRetroRewind(progressWindow); if (downloadResult.IsFailure) return downloadResult; - var updateResult = await Update(progressWindow); + var updateResult = await UpdateAsync(progressWindow); if (updateResult.IsFailure) return updateResult; return Ok(); @@ -188,13 +188,13 @@ private async Task> LatestServerVersion() return SemVersion.Parse(result); } - public async Task Update(ProgressWindow progressWindow) + public async Task UpdateAsync(ProgressWindow progressWindow) { try { var currentVersion = GetCurrentVersion(); if (currentVersion == null) - return await Install(progressWindow); + return await InstallAsync(progressWindow); var isRRUpToDate = await IsRRUpToDate(currentVersion); if (isRRUpToDate.IsFailure) @@ -208,7 +208,7 @@ public async Task Update(ProgressWindow progressWindow) //if current version is below 3.2.6 we need to do a full reinstall if (currentVersion.ComparePrecedenceTo(new SemVersion(3, 2, 6)) < 0) { - var result = await Reinstall(progressWindow); + var result = await ReinstallAsync(progressWindow); return result.IsSuccess ? Ok() : result; } return await ApplyUpdates(currentVersion, progressWindow); @@ -486,7 +486,7 @@ List allDeletions return deletionsToApply; } - public Task Remove(ProgressWindow progressWindow) + public Task RemoveAsync(ProgressWindow progressWindow) { var retroRewindPath = PathManager.RetroRewind6FolderPath; if (_fileSystem.Directory.Exists(retroRewindPath)) @@ -494,16 +494,16 @@ public Task Remove(ProgressWindow progressWindow) return Task.FromResult(Ok()); } - public async Task Reinstall(ProgressWindow progressWindow) + public async Task ReinstallAsync(ProgressWindow progressWindow) { //Remove and install - var removeResult = await Remove(progressWindow); + var removeResult = await RemoveAsync(progressWindow); if (removeResult.IsFailure) return removeResult; - return await Install(progressWindow); + return await InstallAsync(progressWindow); } - public async Task> GetCurrentStatus() + public async Task> GetCurrentStatusAsync() { if (!SettingsHelper.PathsSetupCorrectly()) return WheelWizardStatus.ConfigNotFinished; diff --git a/WheelWizard/Services/Launcher/RrLauncher.cs b/WheelWizard/Services/Launcher/RrLauncher.cs index 96ce1654..5ca1d0dd 100644 --- a/WheelWizard/Services/Launcher/RrLauncher.cs +++ b/WheelWizard/Services/Launcher/RrLauncher.cs @@ -62,9 +62,23 @@ public async Task Launch() } } - public Task Install() => CustomDistributionSingletonService.RetroRewind.Install(); + public async Task Install() + { + var progressWindow = new ProgressWindow(); + progressWindow.Show(); + await CustomDistributionSingletonService.RetroRewind.InstallAsync(progressWindow); + progressWindow.Close(); + } + - public Task Update() => CustomDistributionSingletonService.RetroRewind.Update(); + public async Task Update() + { + var progressWindow = new ProgressWindow(); + progressWindow.Show(); + await CustomDistributionSingletonService.RetroRewind.UpdateAsync(progressWindow); + progressWindow.Close(); + } + public async Task GetCurrentStatus() { @@ -72,7 +86,7 @@ public async Task GetCurrentStatus() { return WheelWizardStatus.NotInstalled; } - var statusResult = await CustomDistributionSingletonService.RetroRewind.GetCurrentStatus(); + var statusResult = await CustomDistributionSingletonService.RetroRewind.GetCurrentStatusAsync(); if (statusResult.IsFailure) return WheelWizardStatus.NotInstalled; return statusResult.Value; diff --git a/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs index eb19dd6b..8caa85de 100644 --- a/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs @@ -132,7 +132,7 @@ private async void WhWzLanguageDropdown_OnSelectionChanged(object? sender, Selec } private async void Reinstall_RetroRewind(object sender, RoutedEventArgs e) => - await CustomDistributionSingletonService.RetroRewind.Reinstall(); + await CustomDistributionSingletonService.RetroRewind.ReinstallAsync(); private void OpenSaveFolder_OnClick(object? sender, RoutedEventArgs e) { From 29716db4b8b3933426b0a47403f3e75d712e5381 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sat, 17 May 2025 14:46:45 +0200 Subject: [PATCH 14/19] Extracting rr --- .../Domain/RetroRewindApi.cs | 2 - .../CustomDistributions/IDistribution.cs | 4 +- .../CustomDistributions/RetroRewind.cs | 77 ++++++++++++------- WheelWizard/Services/Launcher/RrLauncher.cs | 2 - WheelWizard/Services/PathManager.cs | 8 +- .../Pages/Settings/OtherSettings.axaml.cs | 9 ++- 6 files changed, 66 insertions(+), 36 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs b/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs index 291bb604..5711db77 100644 --- a/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs +++ b/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs @@ -15,6 +15,4 @@ public interface IRetroRewindApi [Get("/")] Task Ping(); // use to test server reachability - - } diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs index f40c3e6c..fef021b6 100644 --- a/WheelWizard/Features/CustomDistributions/IDistribution.cs +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -4,6 +4,8 @@ namespace WheelWizard.CustomDistributions; +//todo: we cannot make more distributions before we also write a mystuff service + public interface IDistribution { /// @@ -24,7 +26,7 @@ public interface IDistribution /// /// Update the distribution. /// - Task UpdateAsync(ProgressWindow progressWindow ); + Task UpdateAsync(ProgressWindow progressWindow); Task RemoveAsync(ProgressWindow progressWindow); diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 9bc34e14..a4eff957 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -1,6 +1,7 @@ using System.IO.Abstractions; using System.IO.Compression; using System.Text.RegularExpressions; +using Avalonia.Threading; using Semver; using WheelWizard.CustomDistributions.Domain; using WheelWizard.Helpers; @@ -81,9 +82,11 @@ private async Task DownloadAndExtractRetroRewind(ProgressWindow // 2) Extract progressWindow.SetExtraText(Common.State_Extracting); - - - ZipFile.ExtractToDirectory(tempZipPath, tempExtractionPath, overwriteFiles: true); + + var extractResult = await Task.Run(() => ExtractZipFile(tempZipPath, tempExtractionPath, progressWindow)); + + if (extractResult.IsFailure) + return extractResult; // 3) Locate the extracted sub-folder var sourceFolder = _fileSystem.Path.Combine(tempExtractionPath, FolderName); @@ -106,7 +109,7 @@ private async Task DownloadAndExtractRetroRewind(ProgressWindow finally { // always clean up UI and temp files - progressWindow?.Close(); + progressWindow.Close(); if (_fileSystem.File.Exists(tempZipPath)) _fileSystem.File.Delete(tempZipPath); @@ -256,7 +259,7 @@ private async Task ApplyUpdates(SemVersion currentVersion, Prog private void UpdateVersionFile(SemVersion newVersion) { - var versionFilePath = _fileSystem.Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); + var versionFilePath = _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName, "version.txt"); _fileSystem.File.WriteAllText(versionFilePath, newVersion.ToString()); } @@ -277,7 +280,13 @@ ProgressWindow popupWindow popupWindow.SetExtraText(Common.State_Extracting); var destinationDirectoryPath = PathManager.RiivolutionWhWzFolderPath; _fileSystem.Directory.CreateDirectory(destinationDirectoryPath); - ExtractZipFile(finalFile, destinationDirectoryPath); + + if (finalFile == null) + return "Failed to download update file"; + var extractResult = ExtractZipFile(finalFile, destinationDirectoryPath, popupWindow); + if (extractResult.IsFailure) + return extractResult; + if (_fileSystem.File.Exists(finalFile)) _fileSystem.File.Delete(finalFile); } @@ -290,41 +299,57 @@ ProgressWindow popupWindow return Ok(); } - private OperationResult ExtractZipFile(string path, string destinationDirectory) + private OperationResult ExtractZipFile(string path, string destinationDirectory, ProgressWindow progressWindow) { using var archive = ZipFile.OpenRead(path); + // 1) Compute total work units (we’ll treat each entry as one “unit”) + var entries = archive.Entries.Where(e => !e.FullName.EndsWith("desktop.ini", StringComparison.OrdinalIgnoreCase)).ToList(); + var total = entries.Count; + if (total == 0) + return Ok(); + + // Tell the UI what we’re doing, and set a “goal” so it can estimate MB or items + + Dispatcher.UIThread.Post(() => + { + progressWindow.SetExtraText(Common.State_Extracting).SetGoal($"Extracting {total} files"); + }); + // Absolute path of the destination directory var absoluteDestinationPath = _fileSystem.Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); - foreach (var entry in archive.Entries) + for (var i = 0; i < total; i++) { - if (entry.FullName.EndsWith("desktop.ini", StringComparison.OrdinalIgnoreCase)) - continue; // Skip the desktop.ini file - - // Get the full path of the file + var entry = entries[i]; var destinationPath = _fileSystem.Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); - // Check for directory traversal attacks + // Directory traversal check if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) - { return ("The file path is outside the destination directory. Please contact the developers."); - } - // If the entry is a directory, create it - if (entry.FullName.EndsWith(_fileSystem.Path.AltDirectorySeparatorChar)) + // If it’s a directory, create it + if (entry.FullName.EndsWith(Path.AltDirectorySeparatorChar)) { _fileSystem.Directory.CreateDirectory(destinationPath); - continue; } + else + { + // Ensure folder exists + var dir = _fileSystem.Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(dir)) + _fileSystem.Directory.CreateDirectory(dir); - // Create directory if it doesn't exist - var directoryName = _fileSystem.Path.GetDirectoryName(destinationPath); - if (!string.IsNullOrEmpty(directoryName)) - _fileSystem.Directory.CreateDirectory(directoryName); + // Extract the file + entry.ExtractToFile(destinationPath, overwrite: true); + } - // Extract the file - entry.ExtractToFile(destinationPath, overwrite: true); + // Report incremental progress (0–100) + var percent = (int)(((i + 1) / (double)total) * 100); + Dispatcher.UIThread.Post(() => + { + progressWindow.UpdateProgress(percent); + }); } return Ok(); @@ -488,7 +513,7 @@ List allDeletions public Task RemoveAsync(ProgressWindow progressWindow) { - var retroRewindPath = PathManager.RetroRewind6FolderPath; + var retroRewindPath = _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName); if (_fileSystem.Directory.Exists(retroRewindPath)) _fileSystem.Directory.Delete(retroRewindPath, true); return Task.FromResult(Ok()); @@ -528,7 +553,7 @@ public async Task> GetCurrentStatusAsync() public SemVersion? GetCurrentVersion() { - var versionFilePath = PathManager.RetroRewindVersionFile; + var versionFilePath = _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName, "version.txt"); if (!_fileSystem.File.Exists(versionFilePath)) return null; diff --git a/WheelWizard/Services/Launcher/RrLauncher.cs b/WheelWizard/Services/Launcher/RrLauncher.cs index 5ca1d0dd..25a6c9e3 100644 --- a/WheelWizard/Services/Launcher/RrLauncher.cs +++ b/WheelWizard/Services/Launcher/RrLauncher.cs @@ -70,7 +70,6 @@ public async Task Install() progressWindow.Close(); } - public async Task Update() { var progressWindow = new ProgressWindow(); @@ -78,7 +77,6 @@ public async Task Update() await CustomDistributionSingletonService.RetroRewind.UpdateAsync(progressWindow); progressWindow.Close(); } - public async Task GetCurrentStatus() { diff --git a/WheelWizard/Services/PathManager.cs b/WheelWizard/Services/PathManager.cs index fa791018..251952c9 100644 --- a/WheelWizard/Services/PathManager.cs +++ b/WheelWizard/Services/PathManager.cs @@ -33,7 +33,6 @@ public static class PathManager public static readonly string ModConfigFilePath = Path.Combine(ModsFolderPath, "modconfig.json"); public static readonly string TempModsFolderPath = Path.Combine(ModsFolderPath, "Temp"); public static readonly string RetroRewindTempFile = Path.Combine(TempModsFolderPath, "RetroRewind.zip"); - public static string RetroRewindVersionFile => Path.Combine(RetroRewind6FolderPath, "version.txt"); public static string WiiDbFolder => Path.Combine(WiiFolderPath, "shared2", "menu", "FaceLib"); public static string MiiDbFile => Path.Combine(WiiDbFolder, "RFL_DB.dat"); @@ -42,12 +41,15 @@ public static class PathManager //Also remember that mods may not be in a subfolder, all mod files must be located in /MyStuff directly // Helper paths for folders used across multiple files - public static string MyStuffFolderPath => Path.Combine(RetroRewind6FolderPath, "MyStuff"); + + //todo: before we can actually add more distributions, we will have to rewrite the MyStuff as a service aswell + public static string MyStuffFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RetroRewind6", "MyStuff"); public static string GetModDirectoryPath(string modName) => Path.Combine(ModsFolderPath, modName); public static string RiivolutionWhWzFolderPath => Path.Combine(LoadFolderPath, "Riivolution", "WheelWizard"); - public static string RetroRewind6FolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RetroRewind6"); + + // public static string RetroRewind6FolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RetroRewind6"); // This is not the folder your save file is located in, but its the folder where every Region folder is, so the save file is in SaveFolderPath/Region public static string SaveFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "riivolution", "save", "RetroWFC"); diff --git a/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs index 8caa85de..4efb88ba 100644 --- a/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs @@ -131,8 +131,13 @@ private async void WhWzLanguageDropdown_OnSelectionChanged(object? sender, Selec ViewUtils.RefreshWindow(); } - private async void Reinstall_RetroRewind(object sender, RoutedEventArgs e) => - await CustomDistributionSingletonService.RetroRewind.ReinstallAsync(); + private async void Reinstall_RetroRewind(object sender, RoutedEventArgs e) + { + var progressWindow = new ProgressWindow(); + progressWindow.Show(); + await CustomDistributionSingletonService.RetroRewind.ReinstallAsync(progressWindow); + progressWindow.Close(); + } private void OpenSaveFolder_OnClick(object? sender, RoutedEventArgs e) { From f53ac847022fda7ecaf205913dfa5214ca438696 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Wed, 21 May 2025 20:21:12 +0200 Subject: [PATCH 15/19] Path Removation --- WheelWizard/Features/CustomDistributions/RetroRewind.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index a4eff957..6fe77091 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -270,7 +270,9 @@ private async Task DownloadAndApplyUpdate( ProgressWindow popupWindow ) { - var tempZipPath = _fileSystem.Path.GetTempFileName(); + var tempZipPath = _fileSystem.Path.Combine( + _fileSystem.Path.GetTempPath(), + _fileSystem.Path.GetRandomFileName()); try { popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); @@ -436,7 +438,6 @@ private struct UpdateData { public SemVersion Version; public string Url; - public string Path; public string Description; } @@ -456,7 +457,7 @@ private async Task> GetAllVersionData() continue; var version = parts[0].Trim(); var url = parts[1].Trim(); - var path = parts[2].Trim(); + var path = parts[2].Trim(); // Path unused in our program since on pc we manually decide where to extract var description = parts[3].Trim(); if (string.IsNullOrWhiteSpace(version) || string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(path)) continue; @@ -467,7 +468,6 @@ private async Task> GetAllVersionData() { Version = parsedVersion, Url = url, - Path = path, Description = description, }; versions.Add(updateData); From fb5dfdfc95be5a09bd9f5241b1079cb528d6c1c6 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 22 May 2025 11:01:09 +0200 Subject: [PATCH 16/19] Update RetroRewind.cs --- WheelWizard/Features/CustomDistributions/RetroRewind.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 6fe77091..2e0980f5 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -78,6 +78,8 @@ private async Task DownloadAndExtractRetroRewind(ProgressWindow _fileSystem.Directory.Delete(tempExtractionPath, recursive: true); _fileSystem.Directory.CreateDirectory(tempExtractionPath); + + //todo, service await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); // 2) Extract From e6d74e9178ce7621c253fbf683509a42037e2a77 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sat, 24 May 2025 14:48:28 +0200 Subject: [PATCH 17/19] Close should not be handled by api --- .../CustomDistributions/IDistribution.cs | 2 +- .../CustomDistributions/RetroRewind.cs | 20 ++++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs index fef021b6..51f6e2fd 100644 --- a/WheelWizard/Features/CustomDistributions/IDistribution.cs +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -4,7 +4,7 @@ namespace WheelWizard.CustomDistributions; -//todo: we cannot make more distributions before we also write a mystuff service +//todo: we cannot make more distributions before we also write a mystuff service and a service to download using UI public interface IDistribution { diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 2e0980f5..02c6f6e7 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -78,7 +78,6 @@ private async Task DownloadAndExtractRetroRewind(ProgressWindow _fileSystem.Directory.Delete(tempExtractionPath, recursive: true); _fileSystem.Directory.CreateDirectory(tempExtractionPath); - //todo, service await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); @@ -110,9 +109,6 @@ private async Task DownloadAndExtractRetroRewind(ProgressWindow } finally { - // always clean up UI and temp files - progressWindow.Close(); - if (_fileSystem.File.Exists(tempZipPath)) _fileSystem.File.Delete(tempZipPath); @@ -152,7 +148,7 @@ private string GetOldRksys() // todo, maybe we should check for the existence of the file instead of the folder? and also find the oldest one? var rrWfcPaths = new[] { - _fileSystem.Path.Combine(PathManager.SaveFolderPath), + PathManager.SaveFolderPath, // Also consider the folder with upper-case `Save` _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), _fileSystem.Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), @@ -189,8 +185,10 @@ private async Task> LatestServerVersion() if (!response.IsSuccess || String.IsNullOrWhiteSpace(response.Value)) return "Failed to check for updates"; - var result = response.Value.Split('\n').Last().Split(' ')[0]; - return SemVersion.Parse(result); + var lines = response.Value + .Split('\n', StringSplitOptions.RemoveEmptyEntries); + var last = lines.Last(); + return SemVersion.Parse(last); } public async Task UpdateAsync(ProgressWindow progressWindow) @@ -235,7 +233,6 @@ private async Task ApplyUpdates(SemVersion currentVersion, Prog var deleteSuccess = await ApplyFileDeletionsBetweenVersions(currentVersion, targetVersion); if (deleteSuccess.IsFailure) { - progressWindow.Close(); return (Phrases.PopupText_FailedUpdateDelete); } @@ -247,15 +244,12 @@ private async Task ApplyUpdates(SemVersion currentVersion, Prog var success = await DownloadAndApplyUpdate(update, updatesToApply.Count, i + 1, progressWindow); if (success.IsFailure) { - progressWindow.Close(); return (Phrases.PopupText_FailedUpdateApply); } // Update the version file after each successful update UpdateVersionFile(update.Version); } - - progressWindow.Close(); return Ok(); } @@ -272,9 +266,7 @@ private async Task DownloadAndApplyUpdate( ProgressWindow popupWindow ) { - var tempZipPath = _fileSystem.Path.Combine( - _fileSystem.Path.GetTempPath(), - _fileSystem.Path.GetRandomFileName()); + var tempZipPath = _fileSystem.Path.Combine(_fileSystem.Path.GetTempPath(), _fileSystem.Path.GetRandomFileName()); try { popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); From 149a9905ac9980dbdd0818d9d62407409c0aa77f Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sat, 24 May 2025 14:50:37 +0200 Subject: [PATCH 18/19] Fix vers --- WheelWizard/Features/CustomDistributions/RetroRewind.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs index 02c6f6e7..1850a1b3 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewind.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -185,10 +185,8 @@ private async Task> LatestServerVersion() if (!response.IsSuccess || String.IsNullOrWhiteSpace(response.Value)) return "Failed to check for updates"; - var lines = response.Value - .Split('\n', StringSplitOptions.RemoveEmptyEntries); - var last = lines.Last(); - return SemVersion.Parse(last); + var result = response.Value.Split('\n', StringSplitOptions.RemoveEmptyEntries).Last().Split(' ')[0]; + return SemVersion.Parse(result); } public async Task UpdateAsync(ProgressWindow progressWindow) From 57dd09e9e7c24de098d001837a714cc5049ac2fb Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Tue, 17 Jun 2025 22:42:41 +0200 Subject: [PATCH 19/19] Update SetupExtensions.cs --- WheelWizard/SetupExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WheelWizard/SetupExtensions.cs b/WheelWizard/SetupExtensions.cs index 2b77bd93..d76d1b55 100644 --- a/WheelWizard/SetupExtensions.cs +++ b/WheelWizard/SetupExtensions.cs @@ -4,6 +4,8 @@ using Testably.Abstractions; using WheelWizard.AutoUpdating; using WheelWizard.Branding; +using WheelWizard.CustomCharacters; +using WheelWizard.CustomDistributions; using WheelWizard.GameBanana; using WheelWizard.GitHub; using WheelWizard.MiiImages;