From c4154b01fdf131881b580261c3423765a5164615 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:14:19 +0100 Subject: [PATCH 1/6] Add Retro Rewind Beta distribution & testing UI --- .../CustomDistributionSingletonService.cs | 5 +- .../CustomDistributions/RetroRewindBeta.cs | 382 ++++++++++++++++++ .../GameLicense/GameLicenseService.cs | 7 +- WheelWizard/Services/Endpoints.cs | 1 + .../Launcher/Helpers/ModsLaunchHelper.cs | 19 +- .../Helpers/RetroRewindLaunchHelper.cs | 92 +++-- .../Services/Launcher/RrBetaLauncher.cs | 99 +++++ WheelWizard/Services/PathManager.cs | 7 +- .../Services/Settings/SettingsManager.cs | 1 + WheelWizard/Views/Layout.axaml | 10 +- WheelWizard/Views/Layout.axaml.cs | 82 +++- WheelWizard/Views/Pages/TestingPage.axaml | 83 ++++ WheelWizard/Views/Pages/TestingPage.axaml.cs | 110 +++++ 13 files changed, 850 insertions(+), 48 deletions(-) create mode 100644 WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs create mode 100644 WheelWizard/Services/Launcher/RrBetaLauncher.cs create mode 100644 WheelWizard/Views/Pages/TestingPage.axaml create mode 100644 WheelWizard/Views/Pages/TestingPage.axaml.cs diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs index 1c296671..a65a8acf 100644 --- a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs @@ -13,19 +13,22 @@ public interface ICustomDistributionSingletonService // Instead you would want something like DistService.GetCurrentDistro() // The rest of the application should not have to know what distribution is currently active. RetroRewind RetroRewind { get; } + RetroRewindBeta RetroRewindBeta { get; } } public class CustomDistributionSingletonService : ICustomDistributionSingletonService { public RetroRewind RetroRewind { get; } + public RetroRewindBeta RetroRewindBeta { get; } public CustomDistributionSingletonService(IFileSystem fileSystem, IApiCaller api, ILogger logger) { RetroRewind = new RetroRewind(fileSystem, api, logger); + RetroRewindBeta = new RetroRewindBeta(fileSystem, logger); } public List GetAllDistributions() { - return [RetroRewind]; + return [RetroRewind, RetroRewindBeta]; } } diff --git a/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs b/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs new file mode 100644 index 00000000..8417093b --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs @@ -0,0 +1,382 @@ +using System.IO.Abstractions; +using System.Security.Cryptography; +using System.Text.Json; +using Avalonia.Threading; +using Microsoft.Extensions.Logging; +using Semver; +using SharpCompress.Archives; +using SharpCompress.Readers; +using WheelWizard.Helpers; +using WheelWizard.Models.Enums; +using WheelWizard.Resources.Languages; +using WheelWizard.Services; +using WheelWizard.Services.Settings; +using WheelWizard.Views.Popups.Generic; + +namespace WheelWizard.CustomDistributions; + +public class RetroRewindBeta : IDistribution +{ + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + + public RetroRewindBeta(IFileSystem fileSystem, ILogger logger) + { + _fileSystem = fileSystem; + _logger = logger; + } + + public string Title => "Retro Rewind Beta"; + public string FolderName => "RRBeta"; + public string XMLFolderName => "riivolution"; + public string XMLFileName => "RRBeta"; + + public async Task InstallAsync(ProgressWindow progressWindow) + { + var tempRootPath = PathManager.RrBetaTempFolderPath; + var tempZipPath = PathManager.RrBetaTempFilePath; + var tempExtractionPath = _fileSystem.Path.Combine(tempRootPath, "Extracted"); + OperationResult? result = null; + + try + { + var removeResult = await RemoveAsync(progressWindow); + if (removeResult.IsFailure) + return removeResult; + + progressWindow.SetExtraText("Downloading test build"); + if (_fileSystem.Directory.Exists(tempRootPath)) + _fileSystem.Directory.Delete(tempRootPath, recursive: true); + _fileSystem.Directory.CreateDirectory(tempRootPath); + + var downloadedFile = await DownloadHelper.DownloadToLocationAsync( + Endpoints.RRTestersZipUrl, + tempZipPath, + progressWindow, + ForceGivenFilePath: true + ); + + if (string.IsNullOrWhiteSpace(downloadedFile) || !_fileSystem.File.Exists(downloadedFile)) + return Fail("Failed to download the testing build"); + + while (true) + { + var password = await RequestPasswordAsync(); + if (string.IsNullOrWhiteSpace(password)) + return Fail("Password was not provided."); + + if (_fileSystem.Directory.Exists(tempExtractionPath)) + _fileSystem.Directory.Delete(tempExtractionPath, recursive: true); + _fileSystem.Directory.CreateDirectory(tempExtractionPath); + + progressWindow.SetExtraText(Common.State_Extracting); + var badPassword = false; + var extractResult = await Task.Run( + () => ExtractZipFile(downloadedFile, tempExtractionPath, progressWindow, password, out badPassword) + ); + if (extractResult.IsSuccess) + break; + + if (badPassword) + { + var retry = await new YesNoWindow() + .SetMainText("Incorrect password") + .SetExtraText("Do you want to try again?") + .SetButtonText("Retry", "Cancel") + .AwaitAnswer(); + if (retry) + continue; + return Fail("Incorrect password."); + } + + return extractResult; + } + + var betaFolderSource = _fileSystem.Path.Combine(tempExtractionPath, FolderName); + var xmlFolderSource = _fileSystem.Path.Combine(tempExtractionPath, XMLFolderName); + if (!_fileSystem.Directory.Exists(betaFolderSource)) + return Fail($"Could not find a '{FolderName}' folder inside {tempExtractionPath}"); + if (!_fileSystem.Directory.Exists(xmlFolderSource)) + return Fail($"Could not find a '{XMLFolderName}' folder inside {tempExtractionPath}"); + + var moveResult = MoveExtractedFiles(tempExtractionPath); + if (moveResult.IsFailure) + return moveResult; + + SaveManifest(moveResult.Value); + result = Ok(); + } + catch (Exception ex) + { + result ??= Fail(ex); + _logger.LogError(ex, ex.Message); + } + finally + { + if (_fileSystem.Directory.Exists(tempRootPath)) + _fileSystem.Directory.Delete(tempRootPath, recursive: true); + } + + return result ?? Ok(); + } + + public Task UpdateAsync(ProgressWindow progressWindow) => InstallAsync(progressWindow); + + public Task RemoveAsync(ProgressWindow progressWindow) + { + var rootPath = PathManager.RiivolutionWhWzFolderPath; + var rootFullPath = _fileSystem.Path.GetFullPath(rootPath + Path.AltDirectorySeparatorChar); + + foreach (var entry in LoadManifest()) + { + var fullPath = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(rootPath, entry)); + if (!fullPath.StartsWith(rootFullPath, StringComparison.Ordinal)) + continue; + + if (_fileSystem.File.Exists(fullPath)) + _fileSystem.File.Delete(fullPath); + else if (_fileSystem.Directory.Exists(fullPath)) + _fileSystem.Directory.Delete(fullPath, recursive: true); + } + + if (_fileSystem.Directory.Exists(PathManager.RrBetaFolderPath)) + _fileSystem.Directory.Delete(PathManager.RrBetaFolderPath, recursive: true); + if (_fileSystem.File.Exists(PathManager.RrBetaXmlFilePath)) + _fileSystem.File.Delete(PathManager.RrBetaXmlFilePath); + if (_fileSystem.File.Exists(PathManager.RrBetaManifestFilePath)) + _fileSystem.File.Delete(PathManager.RrBetaManifestFilePath); + + return Task.FromResult(Ok()); + } + + public async Task ReinstallAsync(ProgressWindow progressWindow) + { + var removeResult = await RemoveAsync(progressWindow); + if (removeResult.IsFailure) + return removeResult; + + return await InstallAsync(progressWindow); + } + + public Task> GetCurrentStatusAsync() + { + if (!SettingsHelper.PathsSetupCorrectly()) + return Task.FromResult(Ok(WheelWizardStatus.ConfigNotFinished)); + + var isInstalled = + _fileSystem.Directory.Exists(PathManager.RrBetaFolderPath) && _fileSystem.File.Exists(PathManager.RrBetaXmlFilePath); + + return Task.FromResult(Ok(isInstalled ? WheelWizardStatus.Ready : WheelWizardStatus.NotInstalled)); + } + + public SemVersion? GetCurrentVersion() => null; + + private async Task RequestPasswordAsync() + { + return await new TextInputWindow() + .SetMainText("Please enter Password") + .SetPlaceholderText("Password") + .SetButtonText("Cancel", "Submit") + .ShowDialog(); + } + + private OperationResult ExtractZipFile( + string zipPath, + string destinationDirectory, + ProgressWindow progressWindow, + string password, + out bool badPassword + ) + { + badPassword = false; + try + { + using var archive = ArchiveFactory.Open(zipPath, new ReaderOptions { Password = password }); + var entries = archive.Entries.Where(entry => !entry.IsDirectory).ToList(); + if (entries.Count == 0) + return Ok(); + + Dispatcher.UIThread.Post(() => + { + progressWindow.SetExtraText(Common.State_Extracting).SetGoal($"Extracting {entries.Count} files"); + }); + + var absoluteDestinationPath = _fileSystem.Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); + + for (var i = 0; i < entries.Count; i++) + { + var entry = entries[i]; + var normalized = NormalizeEntryPath(entry.Key ?? string.Empty); + if (string.IsNullOrWhiteSpace(normalized)) + continue; + + if (!TryGetRelativeExtractionPath(normalized, out var relativePath)) + return Fail("Unexpected file in the test archive. Please contact the developers."); + + var destinationPath = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(destinationDirectory, relativePath)); + if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) + return Fail("The file path is outside the destination directory. Please contact the developers."); + + var destinationDir = _fileSystem.Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(destinationDir)) + _fileSystem.Directory.CreateDirectory(destinationDir); + + using var entryStream = entry.OpenEntryStream(); + using var outputStream = File.Create(destinationPath); + entryStream.CopyTo(outputStream); + + var percent = (int)(((i + 1) / (double)entries.Count) * 100); + Dispatcher.UIThread.Post(() => + { + progressWindow.UpdateProgress(percent); + }); + } + + return Ok(); + } + catch (Exception ex) + { + badPassword = IsBadPasswordException(ex); + return badPassword ? Fail("Incorrect password.") : Fail(ex); + } + } + + private static bool IsBadPasswordException(Exception ex) + { + if (ex is CryptographicException) + return true; + + if (!string.IsNullOrWhiteSpace(ex.Message) && ex.Message.Contains("password", StringComparison.OrdinalIgnoreCase)) + return true; + + return ex.InnerException != null && IsBadPasswordException(ex.InnerException); + } + + private static string NormalizeEntryPath(string path) => path.Replace('\\', '/').TrimStart('/'); + + private bool TryGetRelativeExtractionPath(string normalizedPath, out string relativePath) + { + relativePath = string.Empty; + + if (normalizedPath.Equals(FolderName, StringComparison.OrdinalIgnoreCase)) + { + relativePath = FolderName; + return true; + } + + if (normalizedPath.StartsWith($"{FolderName}/", StringComparison.OrdinalIgnoreCase)) + { + relativePath = Path.Combine( + FolderName, + normalizedPath.Substring(FolderName.Length + 1).Replace('/', Path.DirectorySeparatorChar) + ); + return true; + } + + if (normalizedPath.Equals(XMLFolderName, StringComparison.OrdinalIgnoreCase)) + { + relativePath = XMLFolderName; + return true; + } + + if (normalizedPath.StartsWith($"{XMLFolderName}/", StringComparison.OrdinalIgnoreCase)) + { + relativePath = Path.Combine( + XMLFolderName, + normalizedPath.Substring(XMLFolderName.Length + 1).Replace('/', Path.DirectorySeparatorChar) + ); + return true; + } + + return false; + } + + private OperationResult> MoveExtractedFiles(string tempExtractionPath) + { + var destinationRoot = PathManager.RiivolutionWhWzFolderPath; + _fileSystem.Directory.CreateDirectory(destinationRoot); + + var betaFolderSource = _fileSystem.Path.Combine(tempExtractionPath, FolderName); + var xmlFolderSource = _fileSystem.Path.Combine(tempExtractionPath, XMLFolderName); + + var manifestEntries = new List(); + var sourceFiles = _fileSystem + .Directory.EnumerateFiles(betaFolderSource, "*", SearchOption.AllDirectories) + .Concat(_fileSystem.Directory.EnumerateFiles(xmlFolderSource, "*", SearchOption.AllDirectories)); + + var absoluteDestinationRoot = _fileSystem.Path.GetFullPath(destinationRoot + Path.AltDirectorySeparatorChar); + + foreach (var file in sourceFiles) + { + var relativePath = _fileSystem.Path.GetRelativePath(tempExtractionPath, file); + if (IsRiivolutionPath(relativePath) && !IsBetaRiivolutionFile(relativePath)) + { + _logger.LogWarning("Skipping non-beta riivolution file: {RelativePath}", relativePath); + continue; + } + + var destinationPath = _fileSystem.Path.Combine(destinationRoot, relativePath); + var fullDestinationPath = _fileSystem.Path.GetFullPath(destinationPath); + if (!fullDestinationPath.StartsWith(absoluteDestinationRoot, StringComparison.Ordinal)) + return Fail("The file path is outside the destination directory. Please contact the developers."); + + var destinationDirectory = _fileSystem.Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(destinationDirectory)) + _fileSystem.Directory.CreateDirectory(destinationDirectory); + + _fileSystem.File.Move(file, destinationPath, overwrite: true); + + var manifestRelativePath = _fileSystem.Path.GetRelativePath(destinationRoot, destinationPath); + manifestEntries.Add(manifestRelativePath); + } + + return Ok(manifestEntries); + } + + private static bool IsRiivolutionPath(string relativePath) + { + var normalized = relativePath.Replace(Path.DirectorySeparatorChar, '/').Replace(Path.AltDirectorySeparatorChar, '/'); + return normalized.StartsWith("riivolution/", StringComparison.OrdinalIgnoreCase) + || normalized.Equals("riivolution", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsBetaRiivolutionFile(string relativePath) + { + var fileName = Path.GetFileName(relativePath); + return fileName.StartsWith("RRBeta", StringComparison.OrdinalIgnoreCase); + } + + private void SaveManifest(List entries) + { + try + { + var manifestDirectory = _fileSystem.Path.GetDirectoryName(PathManager.RrBetaManifestFilePath); + if (!string.IsNullOrEmpty(manifestDirectory)) + _fileSystem.Directory.CreateDirectory(manifestDirectory); + + var json = JsonSerializer.Serialize(entries, new JsonSerializerOptions { WriteIndented = true }); + _fileSystem.File.WriteAllText(PathManager.RrBetaManifestFilePath, json); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to write beta manifest"); + } + } + + private List LoadManifest() + { + try + { + if (!_fileSystem.File.Exists(PathManager.RrBetaManifestFilePath)) + return []; + + var json = _fileSystem.File.ReadAllText(PathManager.RrBetaManifestFilePath); + return JsonSerializer.Deserialize>(json) ?? []; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to read beta manifest"); + return []; + } + } +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs b/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs index 5961fa30..5c863856 100644 --- a/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs +++ b/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs @@ -82,7 +82,12 @@ public class GameLicenseSingletonService : RepeatedTaskManager, IGameLicenseSing private LicenseCollection Licenses { get; } private byte[]? _rksysData; - public GameLicenseSingletonService(IMiiDbService miiService, IFileSystem fileSystem, IWhWzDataSingletonService whWzDataSingletonService, IRRratingReader rrratingReader) + public GameLicenseSingletonService( + IMiiDbService miiService, + IFileSystem fileSystem, + IWhWzDataSingletonService whWzDataSingletonService, + IRRratingReader rrratingReader + ) : base(40) { _miiService = miiService; diff --git a/WheelWizard/Services/Endpoints.cs b/WheelWizard/Services/Endpoints.cs index 789d262c..7a4bff68 100644 --- a/WheelWizard/Services/Endpoints.cs +++ b/WheelWizard/Services/Endpoints.cs @@ -33,6 +33,7 @@ public static class Endpoints public const string OldRRUrl = "http://update.rwfc.net:8000/"; public const string RRUrl = "https://rwfc.net/updates/"; public const string RRZipUrl = RRUrl + "RetroRewind/zip/RetroRewind.zip"; + public const string RRTestersZipUrl = RRUrl + "RetroRewind/zip/Testers.zip"; public const string RRVersionUrl = RRUrl + "RetroRewind/RetroRewindVersion.txt"; public const string RRVersionDeleteUrl = RRUrl + "RetroRewind/RetroRewindDelete.txt"; public const string RRDiscordUrl = "https://discord.gg/yH3ReN8EhQ"; diff --git a/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs b/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs index f023d190..7cfd0ad5 100644 --- a/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs +++ b/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs @@ -11,19 +11,20 @@ public static class ModsLaunchHelper public static readonly string ModsFolderPath = PathManager.ModsFolderPath; public static readonly string[] AcceptedModExtensions = ["*.szs", "*.arc", "*.brstm", "*.brsar", "*.thp"]; - public static async Task PrepareModsForLaunch() + public static async Task PrepareModsForLaunch(string? myStuffFolderPath = null) { + var resolvedMyStuffFolderPath = myStuffFolderPath ?? MyStuffFolderPath; var mods = ModManager.Instance.Mods.Where(mod => mod.IsEnabled).ToArray(); if (mods.Length == 0) { - if (Directory.Exists(MyStuffFolderPath) && Directory.EnumerateFiles(MyStuffFolderPath).Any()) + if (Directory.Exists(resolvedMyStuffFolderPath) && Directory.EnumerateFiles(resolvedMyStuffFolderPath).Any()) { var modsFoundQuestion = new YesNoWindow() .SetButtonText(Common.Action_Delete, Common.Action_Keep) .SetMainText(Phrases.Question_LaunchClearModsFound_Title) .SetExtraText(Phrases.Question_LaunchClearModsFound_Extra); if (await modsFoundQuestion.AwaitAnswer()) - Directory.Delete(MyStuffFolderPath, true); + Directory.Delete(resolvedMyStuffFolderPath, true); return; } @@ -52,9 +53,9 @@ public static async Task PrepareModsForLaunch() // Get existing files in MyStuff var existingFiles = new HashSet(StringComparer.OrdinalIgnoreCase); - if (Directory.Exists(MyStuffFolderPath)) + if (Directory.Exists(resolvedMyStuffFolderPath)) { - var files = Directory.GetFiles(MyStuffFolderPath, "*.*", SearchOption.TopDirectoryOnly); + var files = Directory.GetFiles(resolvedMyStuffFolderPath, "*.*", SearchOption.TopDirectoryOnly); foreach (var file in files) { var relativePath = Path.GetFileName(file); @@ -63,7 +64,7 @@ public static async Task PrepareModsForLaunch() } else { - Directory.CreateDirectory(MyStuffFolderPath); + Directory.CreateDirectory(resolvedMyStuffFolderPath); } var totalFiles = finalFiles.Count; @@ -76,9 +77,9 @@ await Task.Run(() => { var processedFiles = 0; // Delete files in MyStuff that are not in finalFiles - if (Directory.Exists(MyStuffFolderPath)) + if (Directory.Exists(resolvedMyStuffFolderPath)) { - var files = Directory.GetFiles(MyStuffFolderPath, "*.*", SearchOption.TopDirectoryOnly); + var files = Directory.GetFiles(resolvedMyStuffFolderPath, "*.*", SearchOption.TopDirectoryOnly); foreach (var file in files) { var relativePath = Path.GetFileName(file); @@ -93,7 +94,7 @@ await Task.Run(() => { var relativePath = kvp.Key; var sourceFile = kvp.Value; - var destinationFile = Path.Combine(MyStuffFolderPath, relativePath); + var destinationFile = Path.Combine(resolvedMyStuffFolderPath, relativePath); processedFiles++; var progress = (int)((processedFiles) / (double)totalFiles * 100); diff --git a/WheelWizard/Services/Launcher/Helpers/RetroRewindLaunchHelper.cs b/WheelWizard/Services/Launcher/Helpers/RetroRewindLaunchHelper.cs index 148a76fe..6a2836cb 100644 --- a/WheelWizard/Services/Launcher/Helpers/RetroRewindLaunchHelper.cs +++ b/WheelWizard/Services/Launcher/Helpers/RetroRewindLaunchHelper.cs @@ -1,7 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using WheelWizard.Models.RRLaunchModels; -using WheelWizard.Services.Settings; namespace WheelWizard.Services.Launcher.Helpers; @@ -12,8 +11,29 @@ public static class RetroRewindLaunchHelper public static void GenerateLaunchJson() { - var removeBlur = (bool)SettingsManager.REMOVE_BLUR.Get(); + GenerateLaunchJson(XmlFilePath); + } + + public static void GenerateLaunchJson(string xmlFilePath) + { + var launchInfo = GetLaunchInfo(xmlFilePath); + GenerateLaunchJson( + xmlFilePath, + PathManager.RiivolutionWhWzFolderPath, + launchInfo.SectionName, + launchInfo.MyStuffChoice, + launchInfo.EnableSeparateSave + ); + } + private static void GenerateLaunchJson( + string xmlFilePath, + string rootFolderPath, + string sectionName, + int myStuffChoice, + bool enableSeparateSave + ) + { var launchConfig = new LaunchConfig { BaseFile = Path.GetFullPath(PathManager.GameFilePath), @@ -24,29 +44,9 @@ public static void GenerateLaunchJson() [ new() { - Options = - [ - new() - { - Choice = 1, - OptionName = "Pack", - SectionName = "Retro Rewind", - }, - new() - { - Choice = 2, - OptionName = "My Stuff", - SectionName = "Retro Rewind", - }, - new() - { - Choice = removeBlur ? 1 : 0, - OptionName = "Remove Blur", - SectionName = "Retro Rewind", - }, - ], - Root = Path.GetFullPath(PathManager.RiivolutionWhWzFolderPath), - Xml = Path.GetFullPath(XmlFilePath), + Options = BuildOptions(sectionName, myStuffChoice, enableSeparateSave).ToArray(), + Root = Path.GetFullPath(rootFolderPath), + Xml = Path.GetFullPath(xmlFilePath), }, ], }, @@ -66,4 +66,46 @@ public static void GenerateLaunchJson() File.WriteAllText(JsonFilePath, jsonString); } + + private static List BuildOptions(string sectionName, int myStuffChoice, bool enableSeparateSave) + { + var options = new List + { + new() + { + Choice = 1, + OptionName = "Pack", + SectionName = sectionName, + }, + new() + { + Choice = myStuffChoice, + OptionName = "My Stuff", + SectionName = sectionName, + }, + }; + + if (enableSeparateSave) + { + options.Add( + new() + { + Choice = 1, + OptionName = "Seperate Savegame", + SectionName = sectionName, + } + ); + } + + return options; + } + + private static (string SectionName, int MyStuffChoice, bool EnableSeparateSave) GetLaunchInfo(string xmlFilePath) + { + var fileName = Path.GetFileName(xmlFilePath); + if (fileName.Equals("RRBeta.xml", StringComparison.OrdinalIgnoreCase)) + return ("Retro Rewind Beta", 2, true); + + return ("Retro Rewind", 2, false); + } } diff --git a/WheelWizard/Services/Launcher/RrBetaLauncher.cs b/WheelWizard/Services/Launcher/RrBetaLauncher.cs new file mode 100644 index 00000000..1bcba6b9 --- /dev/null +++ b/WheelWizard/Services/Launcher/RrBetaLauncher.cs @@ -0,0 +1,99 @@ +using Avalonia.Threading; +using WheelWizard.CustomDistributions; +using WheelWizard.Helpers; +using WheelWizard.Models.Enums; +using WheelWizard.Resources.Languages; +using WheelWizard.Services.Launcher.Helpers; +using WheelWizard.Services.Settings; +using WheelWizard.Services.WiiManagement; +using WheelWizard.Shared.DependencyInjection; +using WheelWizard.Views; +using WheelWizard.Views.Popups.Generic; + +namespace WheelWizard.Services.Launcher; + +public class RrBetaLauncher : ILauncher +{ + public string GameTitle { get; } = "Retro Rewind Beta"; + private static string RrLaunchJsonFilePath => PathManager.RrLaunchJsonFilePath; + + [Inject] + private ICustomDistributionSingletonService CustomDistributionSingletonService { get; set; } = + App.Services.GetRequiredService(); + + public async Task Launch() + { + try + { + DolphinLaunchHelper.KillDolphin(); + if (WiiMoteSettings.IsForceSettingsEnabled()) + WiiMoteSettings.DisableVirtualWiiMote(); + await ModsLaunchHelper.PrepareModsForLaunch(PathManager.RrBetaMyStuffFolderPath); + if (!File.Exists(PathManager.GameFilePath)) + { + Dispatcher.UIThread.Post(() => + { + new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Warning) + .SetTitleText("Invalid game path") + .SetInfoText(Phrases.MessageWarning_NotFindGame_Extra) + .Show(); + }); + return; + } + + RetroRewindLaunchHelper.GenerateLaunchJson(PathManager.RrBetaXmlFilePath); + var dolphinLaunchType = (bool)SettingsManager.LAUNCH_WITH_DOLPHIN.Get() ? "" : "-b"; + DolphinLaunchHelper.LaunchDolphin( + $"{dolphinLaunchType} -e {EnvHelper.QuotePath(Path.GetFullPath(RrLaunchJsonFilePath))} --config=Dolphin.Core.EnableCheats=False --config=Achievements.Achievements.Enabled=False" + ); + } + catch (Exception ex) + { + Dispatcher.UIThread.Post(() => + { + new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText("Failed to launch Retro Rewind Beta") + .SetInfoText($"Reason: {ex.Message}") + .Show(); + }); + } + } + + public async Task Install() + { + var progressWindow = new ProgressWindow("Installing test build"); + progressWindow.Show(); + var installResult = await CustomDistributionSingletonService.RetroRewindBeta.InstallAsync(progressWindow); + progressWindow.Close(); + if (installResult.IsFailure) + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText("Unable to install test build") + .SetInfoText(installResult.Error.Message) + .ShowDialog(); + } + } + + public async Task Update() + { + var progressWindow = new ProgressWindow("Updating test build"); + progressWindow.Show(); + await CustomDistributionSingletonService.RetroRewindBeta.UpdateAsync(progressWindow); + progressWindow.Close(); + } + + public async Task GetCurrentStatus() + { + if (CustomDistributionSingletonService == null) + { + return WheelWizardStatus.NotInstalled; + } + var statusResult = await CustomDistributionSingletonService.RetroRewindBeta.GetCurrentStatusAsync(); + if (statusResult.IsFailure) + return WheelWizardStatus.NotInstalled; + return statusResult.Value; + } +} diff --git a/WheelWizard/Services/PathManager.cs b/WheelWizard/Services/PathManager.cs index 39f28cef..4417cd1f 100644 --- a/WheelWizard/Services/PathManager.cs +++ b/WheelWizard/Services/PathManager.cs @@ -73,6 +73,9 @@ public static bool IsUsingCustomWheelWizardAppdataPath public static string ModConfigFilePath => Path.Combine(ModsFolderPath, "modconfig.json"); public static string TempModsFolderPath => Path.Combine(ModsFolderPath, "Temp"); public static string RetroRewindTempFile => Path.Combine(TempModsFolderPath, "RetroRewind.zip"); + public static string RrBetaTempFolderPath => Path.Combine(TempModsFolderPath, "RRBetaTemp"); + public static string RrBetaTempFilePath => Path.Combine(RrBetaTempFolderPath, "Testers.zip"); + public static string RrBetaManifestFilePath => Path.Combine(WheelWizardAppdataPath, "RRBeta.manifest.json"); public static string WiiDbFolder => Path.Combine(WiiFolderPath, "shared2", "menu", "FaceLib"); public static string MiiDbFile => Path.Combine(WiiDbFolder, "RFL_DB.dat"); public static string RRratingFilePath => Path.Combine(WiiFolderPath, "shared2", "Pulsar", "RetroRewind6", "RRRating.pul"); @@ -499,8 +502,9 @@ private static DirectoryMoveContentsResult MoveWheelWizardAppdataContents(string // Helper paths for folders used across multiple files - //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 RrBetaFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RRBeta"); + public static string RrBetaMyStuffFolderPath => Path.Combine(RrBetaFolderPath, "MyStuff"); public static string GetModDirectoryPath(string modName) => Path.Combine(ModsFolderPath, modName); @@ -512,6 +516,7 @@ private static DirectoryMoveContentsResult MoveWheelWizardAppdataContents(string public static string SaveFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "riivolution", "save", "RetroWFC"); public static string RiivolutionXmlFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "riivolution"); public static string XmlFilePath => Path.Combine(RiivolutionXmlFolderPath, "RetroRewind6.xml"); + public static string RrBetaXmlFilePath => Path.Combine(RiivolutionXmlFolderPath, "RRBeta.xml"); private static string PortableUserFolderPath => Path.Combine(GetDolphinExeDirectory(), RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "user" : "User"); diff --git a/WheelWizard/Services/Settings/SettingsManager.cs b/WheelWizard/Services/Settings/SettingsManager.cs index ff5ab985..bbdc61e6 100644 --- a/WheelWizard/Services/Settings/SettingsManager.cs +++ b/WheelWizard/Services/Settings/SettingsManager.cs @@ -85,6 +85,7 @@ public class SettingsManager ); public static Setting ENABLE_ANIMATIONS = new WhWzSetting(typeof(bool), "EnableAnimations", true); + public static Setting TESTING_MODE_ENABLED = new WhWzSetting(typeof(bool), "TestingModeEnabled", false); public static Setting SAVED_WINDOW_SCALE = new WhWzSetting(typeof(double), "WindowScale", 1.0).SetValidation(value => (double)(value ?? -1) >= 0.5 && (double)(value ?? -1) <= 2.0 ); diff --git a/WheelWizard/Views/Layout.axaml b/WheelWizard/Views/Layout.axaml index db31cd3a..e0512497 100644 --- a/WheelWizard/Views/Layout.axaml +++ b/WheelWizard/Views/Layout.axaml @@ -41,8 +41,9 @@ + FontSize="20" IconSize="31" + Margin="10,10,0,18" + PointerPressed="TitleLabel_OnPointerPressed" /> @@ -143,6 +144,9 @@ + @@ -275,4 +279,4 @@ - \ No newline at end of file + diff --git a/WheelWizard/Views/Layout.axaml.cs b/WheelWizard/Views/Layout.axaml.cs index 4eb62c19..c64dc547 100644 --- a/WheelWizard/Views/Layout.axaml.cs +++ b/WheelWizard/Views/Layout.axaml.cs @@ -15,6 +15,7 @@ using WheelWizard.Utilities.RepeatedTasks; using WheelWizard.Views.Components; using WheelWizard.Views.Pages; +using WheelWizard.Views.Popups.Generic; using WheelWizard.WheelWizardData.Domain; using WheelWizard.WiiManagement; using WheelWizard.WiiManagement.GameLicense; @@ -29,6 +30,10 @@ public partial class Layout : BaseWindow, IRepeatedTaskListener, ISettingListene public const double WindowHeight = 876; public const double WindowWidth = 656; public static Layout Instance { get; private set; } = null!; + private const int TesterClicksRequired = 10; + private const string TesterSecretPhrase = "WhenSonicInRR?"; + private int _testerClickCount; + private bool _testerPromptOpen; [Inject] private IBrandingSingletonService BrandingService { get; set; } = null!; @@ -44,6 +49,8 @@ public Layout() OnSettingChanged(SettingsManager.SAVED_WINDOW_SCALE); SettingsManager.WINDOW_SCALE.Subscribe(this); + SettingsManager.TESTING_MODE_ENABLED.Subscribe(this); + UpdateTestingButtonVisibility(); var completeString = Humanizer.ReplaceDynamic(Phrases.Text_MadeByString, "Patchzy", "WantToBeeMe"); if (completeString != null && completeString.Contains("\\n")) @@ -82,14 +89,21 @@ protected override void OnLoaded(RoutedEventArgs e) public void OnSettingChanged(Setting setting) { // Note that this method will also be called whenever the setting changes - var scaleFactor = (double)setting.Get(); - Height = WindowHeight * scaleFactor; - Width = WindowWidth * scaleFactor; - CompleteGrid.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor); - var marginXCorrection = ((scaleFactor * WindowWidth) - WindowWidth) / 2f; - var marginYCorrection = ((scaleFactor * WindowHeight) - WindowHeight) / 2f; - CompleteGrid.Margin = new(marginXCorrection, marginYCorrection); - //ExtendClientAreaToDecorationsHint = scaleFactor <= 1.2f; + if (setting == SettingsManager.WINDOW_SCALE) + { + var scaleFactor = (double)setting.Get(); + Height = WindowHeight * scaleFactor; + Width = WindowWidth * scaleFactor; + CompleteGrid.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor); + var marginXCorrection = ((scaleFactor * WindowWidth) - WindowWidth) / 2f; + var marginYCorrection = ((scaleFactor * WindowHeight) - WindowHeight) / 2f; + CompleteGrid.Margin = new(marginXCorrection, marginYCorrection); + //ExtendClientAreaToDecorationsHint = scaleFactor <= 1.2f; + return; + } + + if (setting == SettingsManager.TESTING_MODE_ENABLED) + UpdateTestingButtonVisibility(); } public void NavigateToPage(UserControl page) @@ -177,6 +191,58 @@ private void TopBar_PointerPressed(object? sender, PointerPressedEventArgs e) BeginMoveDrag(e); } + private async void TitleLabel_OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + return; + + e.Handled = true; + + if ((bool)SettingsManager.TESTING_MODE_ENABLED.Get()) + return; + + if (_testerPromptOpen) + return; + + _testerClickCount++; + if (_testerClickCount < TesterClicksRequired) + return; + + _testerClickCount = 0; + _testerPromptOpen = true; + + try + { + var result = await new TextInputWindow() + .SetMainText("Welcome tester, write your secret phrase") + .SetPlaceholderText("Secret phrase") + .SetButtonText("Cancel", "Submit") + .ShowDialog(); + + if (string.IsNullOrWhiteSpace(result)) + return; + + if (result == TesterSecretPhrase) + { + SettingsManager.TESTING_MODE_ENABLED.Set(true); + ShowSnackbar("Testing mode enabled", ViewUtils.SnackbarType.Success); + } + else + { + ShowSnackbar("Incorrect secret phrase", ViewUtils.SnackbarType.Danger); + } + } + finally + { + _testerPromptOpen = false; + } + } + + private void UpdateTestingButtonVisibility() + { + TestingButton.IsVisible = (bool)SettingsManager.TESTING_MODE_ENABLED.Get(); + } + private void CloseButton_Click(object? sender, RoutedEventArgs e) => Close(); private void MinimizeButton_Click(object? sender, RoutedEventArgs e) => WindowState = WindowState.Minimized; diff --git a/WheelWizard/Views/Pages/TestingPage.axaml b/WheelWizard/Views/Pages/TestingPage.axaml new file mode 100644 index 00000000..39865049 --- /dev/null +++ b/WheelWizard/Views/Pages/TestingPage.axaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WheelWizard/Views/Pages/TestingPage.axaml.cs b/WheelWizard/Views/Pages/TestingPage.axaml.cs new file mode 100644 index 00000000..48793a6c --- /dev/null +++ b/WheelWizard/Views/Pages/TestingPage.axaml.cs @@ -0,0 +1,110 @@ +using Avalonia.Interactivity; +using WheelWizard.CustomDistributions; +using WheelWizard.Models.Enums; +using WheelWizard.Services.Launcher; +using WheelWizard.Services.Launcher.Helpers; +using WheelWizard.Services.Settings; +using WheelWizard.Shared.DependencyInjection; +using WheelWizard.Views.Popups.Generic; + +namespace WheelWizard.Views.Pages; + +public partial class TestingPage : UserControlBase +{ + private readonly ILauncher _launcher; + private WheelWizardStatus _status = WheelWizardStatus.Loading; + private bool _isBusy; + + [Inject] + private ICustomDistributionSingletonService CustomDistributionSingletonService { get; set; } = null!; + + public TestingPage() + { + InitializeComponent(); + _launcher = new RrBetaLauncher(); + UpdateStatusAsync(); + } + + private async void UpdateStatusAsync() + { + _status = WheelWizardStatus.Loading; + UpdateUi(); + + _status = await _launcher.GetCurrentStatus(); + UpdateUi(); + } + + private void UpdateUi() + { + var pathsReady = SettingsHelper.PathsSetupCorrectly(); + + InstallButton.IsEnabled = pathsReady && !_isBusy; + DeleteButton.IsEnabled = !_isBusy && _status == WheelWizardStatus.Ready; + LaunchButton.IsEnabled = !_isBusy && _status == WheelWizardStatus.Ready; + DolphinButton.IsEnabled = !_isBusy && pathsReady; + + StatusText.Text = _status switch + { + WheelWizardStatus.ConfigNotFinished => "Setup required. Configure paths in Settings.", + WheelWizardStatus.NotInstalled => "Not installed", + WheelWizardStatus.Ready => "Installed", + WheelWizardStatus.NoServer or WheelWizardStatus.NoServerButInstalled => "Server offline", + WheelWizardStatus.OutOfDate => "Update available", + _ => "Checking status...", + }; + } + + private async void InstallButton_OnClick(object? sender, RoutedEventArgs e) + { + if (_isBusy) + return; + + _isBusy = true; + UpdateUi(); + + await _launcher.Install(); + + _isBusy = false; + UpdateStatusAsync(); + } + + private async void DeleteButton_OnClick(object? sender, RoutedEventArgs e) + { + if (_isBusy) + return; + + _isBusy = true; + UpdateUi(); + + var progressWindow = new ProgressWindow("Removing test build"); + progressWindow.Show(); + var removeResult = await CustomDistributionSingletonService.RetroRewindBeta.RemoveAsync(progressWindow); + progressWindow.Close(); + + if (removeResult.IsFailure) + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText("Unable to delete test build") + .SetInfoText(removeResult.Error.Message) + .ShowDialog(); + } + + _isBusy = false; + UpdateStatusAsync(); + } + + private async void LaunchButton_OnClick(object? sender, RoutedEventArgs e) + { + if (_isBusy) + return; + + _isBusy = true; + UpdateUi(); + await _launcher.Launch(); + _isBusy = false; + UpdateUi(); + } + + private void DolphinButton_OnClick(object? sender, RoutedEventArgs e) => DolphinLaunchHelper.LaunchDolphin(); +} From 1a7e395aed4f38dbca2ed90059fa8cf1cfdd98f0 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:59:13 +0100 Subject: [PATCH 2/6] Remove dolphin buton --- WheelWizard/Views/Pages/TestingPage.axaml | 173 ++++++++++++------- WheelWizard/Views/Pages/TestingPage.axaml.cs | 3 - 2 files changed, 109 insertions(+), 67 deletions(-) diff --git a/WheelWizard/Views/Pages/TestingPage.axaml b/WheelWizard/Views/Pages/TestingPage.axaml index 39865049..cbdc2c98 100644 --- a/WheelWizard/Views/Pages/TestingPage.axaml +++ b/WheelWizard/Views/Pages/TestingPage.axaml @@ -4,80 +4,125 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:components="clr-namespace:WheelWizard.Views.Components" xmlns:lang="clr-namespace:WheelWizard.Resources.Languages" - mc:Ignorable="d" d:DesignWidth="656" d:DesignHeight="876" + mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="700" x:Class="WheelWizard.Views.Pages.TestingPage"> + + + + + + - + - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + - - - + + + + + + + diff --git a/WheelWizard/Views/Pages/TestingPage.axaml.cs b/WheelWizard/Views/Pages/TestingPage.axaml.cs index 48793a6c..8bc7baf9 100644 --- a/WheelWizard/Views/Pages/TestingPage.axaml.cs +++ b/WheelWizard/Views/Pages/TestingPage.axaml.cs @@ -41,7 +41,6 @@ private void UpdateUi() InstallButton.IsEnabled = pathsReady && !_isBusy; DeleteButton.IsEnabled = !_isBusy && _status == WheelWizardStatus.Ready; LaunchButton.IsEnabled = !_isBusy && _status == WheelWizardStatus.Ready; - DolphinButton.IsEnabled = !_isBusy && pathsReady; StatusText.Text = _status switch { @@ -105,6 +104,4 @@ private async void LaunchButton_OnClick(object? sender, RoutedEventArgs e) _isBusy = false; UpdateUi(); } - - private void DolphinButton_OnClick(object? sender, RoutedEventArgs e) => DolphinLaunchHelper.LaunchDolphin(); } From fc51e1b7bb7647bde26ab43ae9c59d6a0e45b8a0 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:06:54 +0100 Subject: [PATCH 3/6] clarify tester secret --- WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs | 4 +++- WheelWizard/Views/Layout.axaml.cs | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs b/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs index 8417093b..2f5efdcf 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs @@ -94,8 +94,10 @@ public async Task InstallAsync(ProgressWindow progressWindow) var betaFolderSource = _fileSystem.Path.Combine(tempExtractionPath, FolderName); var xmlFolderSource = _fileSystem.Path.Combine(tempExtractionPath, XMLFolderName); + if (!_fileSystem.Directory.Exists(betaFolderSource)) return Fail($"Could not find a '{FolderName}' folder inside {tempExtractionPath}"); + if (!_fileSystem.Directory.Exists(xmlFolderSource)) return Fail($"Could not find a '{XMLFolderName}' folder inside {tempExtractionPath}"); @@ -117,7 +119,7 @@ public async Task InstallAsync(ProgressWindow progressWindow) _fileSystem.Directory.Delete(tempRootPath, recursive: true); } - return result ?? Ok(); + return result; } public Task UpdateAsync(ProgressWindow progressWindow) => InstallAsync(progressWindow); diff --git a/WheelWizard/Views/Layout.axaml.cs b/WheelWizard/Views/Layout.axaml.cs index c64dc547..b3d6d634 100644 --- a/WheelWizard/Views/Layout.axaml.cs +++ b/WheelWizard/Views/Layout.axaml.cs @@ -31,6 +31,10 @@ public partial class Layout : BaseWindow, IRepeatedTaskListener, ISettingListene public const double WindowWidth = 656; public static Layout Instance { get; private set; } = null!; private const int TesterClicksRequired = 10; + + // so this is not really "Secret" its just ment to hold out people who are not meant to be testers + // if you came here to find it, it will be useless to you, you can not actually download or play + // testing builds since they are behind authentication walls. private const string TesterSecretPhrase = "WhenSonicInRR?"; private int _testerClickCount; private bool _testerPromptOpen; From abead5997b52226feaab3516c6a446094f4b2445 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:09:09 +0100 Subject: [PATCH 4/6] . --- WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs | 4 ++-- WheelWizard/Views/Layout.axaml.cs | 2 +- WheelWizard/Views/Pages/TestingPage.axaml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs b/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs index 2f5efdcf..d6f9f5a9 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs @@ -94,10 +94,10 @@ public async Task InstallAsync(ProgressWindow progressWindow) var betaFolderSource = _fileSystem.Path.Combine(tempExtractionPath, FolderName); var xmlFolderSource = _fileSystem.Path.Combine(tempExtractionPath, XMLFolderName); - + if (!_fileSystem.Directory.Exists(betaFolderSource)) return Fail($"Could not find a '{FolderName}' folder inside {tempExtractionPath}"); - + if (!_fileSystem.Directory.Exists(xmlFolderSource)) return Fail($"Could not find a '{XMLFolderName}' folder inside {tempExtractionPath}"); diff --git a/WheelWizard/Views/Layout.axaml.cs b/WheelWizard/Views/Layout.axaml.cs index b3d6d634..c1e5a56d 100644 --- a/WheelWizard/Views/Layout.axaml.cs +++ b/WheelWizard/Views/Layout.axaml.cs @@ -31,7 +31,7 @@ public partial class Layout : BaseWindow, IRepeatedTaskListener, ISettingListene public const double WindowWidth = 656; public static Layout Instance { get; private set; } = null!; private const int TesterClicksRequired = 10; - + // so this is not really "Secret" its just ment to hold out people who are not meant to be testers // if you came here to find it, it will be useless to you, you can not actually download or play // testing builds since they are behind authentication walls. diff --git a/WheelWizard/Views/Pages/TestingPage.axaml b/WheelWizard/Views/Pages/TestingPage.axaml index cbdc2c98..98f71190 100644 --- a/WheelWizard/Views/Pages/TestingPage.axaml +++ b/WheelWizard/Views/Pages/TestingPage.axaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:components="clr-namespace:WheelWizard.Views.Components" xmlns:lang="clr-namespace:WheelWizard.Resources.Languages" - mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="700" + mc:Ignorable="d" d:DesignWidth="656" d:DesignHeight="876" x:Class="WheelWizard.Views.Pages.TestingPage"> @@ -54,7 +54,7 @@ - + From 64dd53dc21a0ab2347ad485d676085ebeb8fb39d Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:18:30 +0100 Subject: [PATCH 5/6] ui changes --- WheelWizard/Views/Pages/TestingPage.axaml | 170 +++++++++---------- WheelWizard/Views/Pages/TestingPage.axaml.cs | 12 +- 2 files changed, 85 insertions(+), 97 deletions(-) diff --git a/WheelWizard/Views/Pages/TestingPage.axaml b/WheelWizard/Views/Pages/TestingPage.axaml index 98f71190..3714f752 100644 --- a/WheelWizard/Views/Pages/TestingPage.axaml +++ b/WheelWizard/Views/Pages/TestingPage.axaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:components="clr-namespace:WheelWizard.Views.Components" xmlns:lang="clr-namespace:WheelWizard.Resources.Languages" - mc:Ignorable="d" d:DesignWidth="656" d:DesignHeight="876" + mc:Ignorable="d" d:DesignWidth="370" d:DesignHeight="520" x:Class="WheelWizard.Views.Pages.TestingPage"> @@ -13,116 +13,98 @@ + - - - - - - + + - - - - - - - + + + + + + + + + - - - + + + + + + + - - - - - - - - + + + + - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + + + + diff --git a/WheelWizard/Views/Pages/TestingPage.axaml.cs b/WheelWizard/Views/Pages/TestingPage.axaml.cs index 8bc7baf9..63bbb300 100644 --- a/WheelWizard/Views/Pages/TestingPage.axaml.cs +++ b/WheelWizard/Views/Pages/TestingPage.axaml.cs @@ -37,16 +37,22 @@ private async void UpdateStatusAsync() private void UpdateUi() { var pathsReady = SettingsHelper.PathsSetupCorrectly(); + var isInstalled = _status == WheelWizardStatus.Ready; InstallButton.IsEnabled = pathsReady && !_isBusy; - DeleteButton.IsEnabled = !_isBusy && _status == WheelWizardStatus.Ready; - LaunchButton.IsEnabled = !_isBusy && _status == WheelWizardStatus.Ready; + InstallButton.IsVisible = !isInstalled; + + DeleteButton.IsEnabled = !_isBusy && isInstalled; + DeleteButton.IsVisible = isInstalled; + + LaunchButton.IsEnabled = !_isBusy && isInstalled; + LaunchButton.IsVisible = isInstalled; StatusText.Text = _status switch { WheelWizardStatus.ConfigNotFinished => "Setup required. Configure paths in Settings.", WheelWizardStatus.NotInstalled => "Not installed", - WheelWizardStatus.Ready => "Installed", + WheelWizardStatus.Ready => "Installed - Ready to play", WheelWizardStatus.NoServer or WheelWizardStatus.NoServerButInstalled => "Server offline", WheelWizardStatus.OutOfDate => "Update available", _ => "Checking status...", From a0d29d66e38267f149c3ee314a10596309eb0955 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:59:13 +0100 Subject: [PATCH 6/6] Update Layout.axaml.cs --- WheelWizard/Views/Layout.axaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/WheelWizard/Views/Layout.axaml.cs b/WheelWizard/Views/Layout.axaml.cs index c1e5a56d..8cd59767 100644 --- a/WheelWizard/Views/Layout.axaml.cs +++ b/WheelWizard/Views/Layout.axaml.cs @@ -35,6 +35,7 @@ public partial class Layout : BaseWindow, IRepeatedTaskListener, ISettingListene // so this is not really "Secret" its just ment to hold out people who are not meant to be testers // if you came here to find it, it will be useless to you, you can not actually download or play // testing builds since they are behind authentication walls. + // but have fun with the beta button :) private const string TesterSecretPhrase = "WhenSonicInRR?"; private int _testerClickCount; private bool _testerPromptOpen;