From 61a62aaad6ac579ccae785e3ce4c2b4f769c0dbc Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 28 May 2026 08:45:27 +0200 Subject: [PATCH 1/5] Update RetroRewindBeta.cs --- WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs b/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs index 9e734b58..86ad4148 100644 --- a/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs +++ b/WheelWizard/Features/CustomDistributions/RetroRewindBeta.cs @@ -193,7 +193,8 @@ out bool badPassword badPassword = false; try { - using var archive = ArchiveFactory.OpenArchive(zipPath, new ReaderOptions { Password = password }); + using var archiveStream = _fileSystem.File.OpenRead(zipPath); + using var archive = ArchiveFactory.OpenArchive(archiveStream, new ReaderOptions { Password = password }); var entries = archive.Entries.Where(entry => !entry.IsDirectory).ToList(); if (entries.Count == 0) return Ok(); From 98d0e706c7f606d47478df98024d21416ab14384 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 31 May 2026 11:28:09 +0200 Subject: [PATCH 2/5] Cap window scale to available screen --- .../Features/Settings/SettingsTests.cs | 26 +++++++++++++++++++ .../Features/Settings/SettingsManager.cs | 8 +++--- .../Settings/Types/SettingConstants.cs | 7 +++++ WheelWizard/Views/Layout.axaml.cs | 14 +++++++++- .../Pages/Settings/WhWzSettings.axaml.cs | 12 ++++++++- .../Views/Popups/Base/PopupWindow.axaml.cs | 3 ++- WheelWizard/Views/ViewUtils.cs | 17 ++++++++++++ 7 files changed, 81 insertions(+), 6 deletions(-) diff --git a/WheelWizard.Test/Features/Settings/SettingsTests.cs b/WheelWizard.Test/Features/Settings/SettingsTests.cs index 3b974b37..368cee61 100644 --- a/WheelWizard.Test/Features/Settings/SettingsTests.cs +++ b/WheelWizard.Test/Features/Settings/SettingsTests.cs @@ -43,6 +43,32 @@ public void Set_ReturnsFalse_WhenValidationFails() Assert.Equal(0, manager.Get(manager.FOCUSED_USER)); } + [Theory] + [InlineData(0.49)] + [InlineData(2.01)] + public void SavedWindowScale_RejectsValuesOutsideBounds(double scale) + { + var manager = CreateManager(new MockFileSystem(), out _, out _, out _); + + var result = manager.Set(manager.SAVED_WINDOW_SCALE, scale, skipSave: true); + + Assert.False(result); + Assert.Equal(1.0, manager.Get(manager.SAVED_WINDOW_SCALE)); + } + + [Theory] + [InlineData(0.49)] + [InlineData(2.01)] + public void WindowScalePreview_RejectsValuesOutsideBounds(double scale) + { + var manager = CreateManager(new MockFileSystem(), out _, out _, out _); + + var result = manager.Set(manager.WINDOW_SCALE, scale, skipSave: true); + + Assert.False(result); + Assert.Equal(1.0, manager.Get(manager.WINDOW_SCALE)); + } + [Fact] public void ValidateCorePathSettings_ReturnsAllExpectedIssues_WhenDefaultsAreInvalid() { diff --git a/WheelWizard/Features/Settings/SettingsManager.cs b/WheelWizard/Features/Settings/SettingsManager.cs index d5c7a768..02c131fa 100644 --- a/WheelWizard/Features/Settings/SettingsManager.cs +++ b/WheelWizard/Features/Settings/SettingsManager.cs @@ -116,7 +116,7 @@ IFileSystem fileSystem ENABLE_ANIMATIONS = RegisterWhWz("EnableAnimations", true); TESTING_MODE_ENABLED = RegisterWhWz("TestingModeEnabled", false); - SAVED_WINDOW_SCALE = RegisterWhWz("WindowScale", 1.0, value => (double)(value ?? -1) >= 0.5 && (double)(value ?? -1) <= 2.0); + SAVED_WINDOW_SCALE = RegisterWhWz("WindowScale", 1.0, SettingValues.IsValidWindowScale); REMOVE_BLUR = RegisterWhWz("REMOVE_BLUR", true); RR_REGION = RegisterWhWz("RR_Region", MarioKartWiiEnums.Regions.None); WW_LANGUAGE = RegisterWhWz("WW_Language", "en", value => SettingValues.WhWzLanguages.ContainsKey((string)value!)); @@ -155,11 +155,13 @@ IFileSystem fileSystem #endregion #region Virtual settings - WINDOW_SCALE = new VirtualSetting( + var windowScale = new VirtualSetting( typeof(double), value => _internalScale = (double)value!, () => _internalScale == -1.0 ? SAVED_WINDOW_SCALE.Get() : _internalScale - ).SetDependencies(SAVED_WINDOW_SCALE); + ); + windowScale.SetValidation(SettingValues.IsValidWindowScale); + WINDOW_SCALE = windowScale.SetDependencies(SAVED_WINDOW_SCALE); RECOMMENDED_SETTINGS = new VirtualSetting( typeof(bool), diff --git a/WheelWizard/Features/Settings/Types/SettingConstants.cs b/WheelWizard/Features/Settings/Types/SettingConstants.cs index 73508520..6da30264 100644 --- a/WheelWizard/Features/Settings/Types/SettingConstants.cs +++ b/WheelWizard/Features/Settings/Types/SettingConstants.cs @@ -14,9 +14,16 @@ public static class SettingValues // you check for this value and replace it with its corresponding value in the language file public const string NoName = "no name"; public const string NoLicense = "no license"; + public const double MinWindowScale = 0.5; + public const double MaxWindowScale = 2.0; public static readonly double[] WindowScales = [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.8, 2]; + public static bool IsValidWindowScale(object? value) + { + return value is double scale && scale >= MinWindowScale && scale <= MaxWindowScale; + } + public static readonly Dictionary GFXRenderers = new() //Display name, value { #if WINDOWS diff --git a/WheelWizard/Views/Layout.axaml.cs b/WheelWizard/Views/Layout.axaml.cs index 2f5213c7..124f4b58 100644 --- a/WheelWizard/Views/Layout.axaml.cs +++ b/WheelWizard/Views/Layout.axaml.cs @@ -77,6 +77,7 @@ public Layout() InitializeComponent(); AddLayer(); + ClampSavedWindowScaleToCurrentScreen(); OnSettingChanged(SettingsService.SAVED_WINDOW_SCALE); _settingsSignalSubscription = SettingsSignalBus.Subscribe(OnSettingSignal); UpdateTestingButtonVisibility(); @@ -148,7 +149,7 @@ private void OnSettingChanged(Setting setting) // Note that this method will also be called whenever the setting changes if (setting == SettingsService.WINDOW_SCALE || setting == SettingsService.SAVED_WINDOW_SCALE) { - var scaleFactor = (double)setting.Get(); + var scaleFactor = GetUsableWindowScale((double)setting.Get()); Height = WindowHeight * scaleFactor; Width = WindowWidth * scaleFactor; CompleteGrid.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor); @@ -163,6 +164,17 @@ private void OnSettingChanged(Setting setting) UpdateTestingButtonVisibility(); } + private void ClampSavedWindowScaleToCurrentScreen() + { + var savedScale = SettingsService.Get(SettingsService.SAVED_WINDOW_SCALE); + var usableScale = GetUsableWindowScale(savedScale); + if (!savedScale.Equals(usableScale)) + SettingsService.Set(SettingsService.SAVED_WINDOW_SCALE, usableScale); + } + + private double GetUsableWindowScale(double requestedScale) => + ViewUtils.GetUsableWindowScale(requestedScale, new Size(WindowWidth, WindowHeight), this); + private void UpdateModsButtonText() { ModsButton.Text = Common.PageTitle_Patches; diff --git a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs index 5011d11a..73522bc8 100644 --- a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs @@ -647,8 +647,18 @@ private async void WindowScaleDropdown_OnSelectionChanged(object sender, Selecti _editingScale = true; var selectedScale = WindowScaleDropdown.SelectedItem?.ToString() ?? "1"; var scale = double.Parse(selectedScale.Split(" ").Last().Replace("%", "")) / 100; + scale = ViewUtils.GetUsableWindowScale(scale, new Avalonia.Size(Layout.WindowWidth, Layout.WindowHeight), ViewUtils.GetLayout()); + var selectedItemText = ScaleToString(scale); + if (!WindowScaleDropdown.Items.Contains(selectedItemText)) + WindowScaleDropdown.Items.Add(selectedItemText); + WindowScaleDropdown.SelectedItem = selectedItemText; - SettingsService.WINDOW_SCALE.Set(scale); + if (!SettingsService.WINDOW_SCALE.Set(scale)) + { + WindowScaleDropdown.SelectedItem = ScaleToString((double)SettingsService.WINDOW_SCALE.Get()); + _editingScale = false; + return; + } var seconds = 10; string ExtraScaleText() => diff --git a/WheelWizard/Views/Popups/Base/PopupWindow.axaml.cs b/WheelWizard/Views/Popups/Base/PopupWindow.axaml.cs index 62fe1eb5..99ec48bd 100644 --- a/WheelWizard/Views/Popups/Base/PopupWindow.axaml.cs +++ b/WheelWizard/Views/Popups/Base/PopupWindow.axaml.cs @@ -7,6 +7,7 @@ using Avalonia.Media; using WheelWizard.Settings; using WheelWizard.Shared.DependencyInjection; +using WheelWizard.Views; namespace WheelWizard.Views.Popups.Base; @@ -147,7 +148,7 @@ protected override void OnResized(WindowResizedEventArgs e) public void SetWindowSize(Size size) { - var scaleFactor = SettingsService.Get(SettingsService.WINDOW_SCALE); + var scaleFactor = ViewUtils.GetUsableWindowScale(SettingsService.Get(SettingsService.WINDOW_SCALE), size, this); Width = size.Width * scaleFactor; Height = size.Height * scaleFactor; CompleteGrid.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor); diff --git a/WheelWizard/Views/ViewUtils.cs b/WheelWizard/Views/ViewUtils.cs index b6dd65c2..7c344fbb 100644 --- a/WheelWizard/Views/ViewUtils.cs +++ b/WheelWizard/Views/ViewUtils.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Media; using WheelWizard.Services.LiveData; +using WheelWizard.Settings.Types; using WheelWizard.Utilities.RepeatedTasks; namespace WheelWizard.Views; @@ -26,6 +27,22 @@ public static void OpenLink(string link) public static Layout GetLayout() => Layout.Instance; + public static double GetUsableWindowScale(double requestedScale, Size unscaledSize, Window window) + { + var maxScale = SettingValues.MaxWindowScale; + var screen = window.Screens.ScreenFromWindow(window) ?? window.Screens.Primary; + if (screen != null) + { + var screenScale = screen.Scaling <= 0 ? 1 : screen.Scaling; + var availableWidth = screen.WorkingArea.Width / screenScale; + var availableHeight = screen.WorkingArea.Height / screenScale; + maxScale = Math.Min(maxScale, Math.Min(availableWidth / unscaledSize.Width, availableHeight / unscaledSize.Height)); + } + + maxScale = Math.Max(SettingValues.MinWindowScale, maxScale); + return Math.Clamp(requestedScale, SettingValues.MinWindowScale, maxScale); + } + public static void RefreshWindow() { // Refresh window opens in the start page again, that is nessesairy From ea17c7beba78a9bfde35062dcdc80176545453a4 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 31 May 2026 11:30:16 +0200 Subject: [PATCH 3/5] Handle malformed Mii data without crashing --- .../Features/MiiSerializerTests.cs | 23 +++++++++++++++ .../MiiManagement/MiiSerializer.cs | 29 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/WheelWizard.Test/Features/MiiSerializerTests.cs b/WheelWizard.Test/Features/MiiSerializerTests.cs index f41fce5c..9e00bae0 100644 --- a/WheelWizard.Test/Features/MiiSerializerTests.cs +++ b/WheelWizard.Test/Features/MiiSerializerTests.cs @@ -125,4 +125,27 @@ public void Deserialize_InvalidLengthData_ShouldFail() Assert.True(result.IsFailure); Assert.Equal("Invalid Mii data length.", result.Error.Message); } + + [Fact] + public void Deserialize_InvalidCalendarDate_ShouldFailInsteadOfThrowing() + { + var data = Convert.FromBase64String(dataList[0]); + ushort header = (ushort)((data[0] << 8) | data[1]); + header = (ushort)((header & ~(0x0F << 10)) | (2 << 10)); + header = (ushort)((header & ~(0x1F << 5)) | (31 << 5)); + data[0] = (byte)(header >> 8); + data[1] = (byte)header; + + var result = MiiSerializer.Deserialize(data); + + Assert.True(result.IsFailure); + } + + [Fact] + public void Deserialize_InvalidBase64_ShouldFailInsteadOfThrowing() + { + var result = MiiSerializer.Deserialize("not valid base64"); + + Assert.True(result.IsFailure); + } } diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/MiiSerializer.cs b/WheelWizard/Features/WiiManagement/MiiManagement/MiiSerializer.cs index 47e460bd..f5757fe7 100644 --- a/WheelWizard/Features/WiiManagement/MiiManagement/MiiSerializer.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/MiiSerializer.cs @@ -143,9 +143,31 @@ public static OperationResult Serialize(Mii? mii) return data; } - public static OperationResult Deserialize(string data) => Deserialize(Convert.FromBase64String(data)); + public static OperationResult Deserialize(string data) + { + try + { + return Deserialize(Convert.FromBase64String(data)); + } + catch (Exception ex) + { + return InvalidDataExc(ex); + } + } public static OperationResult Deserialize(byte[]? data) + { + try + { + return DeserializeCore(data); + } + catch (Exception ex) + { + return InvalidDataExc(ex); + } + } + + private static OperationResult DeserializeCore(byte[]? data) { if (data == null || data.Length != 74) return Fail("Invalid Mii data length.", MessageTranslation.Error_MiiSerializer_MiiDataLength); @@ -342,4 +364,9 @@ private static OperationResult InvalidDataExc(string data) { return Fail(new InvalidDataException($"Invalid {data}"), MessageTranslation.Error_MiiSerializer_InvalidMiiData, null, [data]); } + + private static OperationResult InvalidDataExc(Exception exception) + { + return Fail(exception, MessageTranslation.Error_MiiSerializer_InvalidMiiData, null, [exception.Message]); + } } From 3e23e7f460a26bc5da5693de7238519681f3c9ea Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 31 May 2026 11:32:23 +0200 Subject: [PATCH 4/5] Fix Mii color selection guard --- .../Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs | 2 +- .../Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs index 561aa25f..38310110 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs @@ -86,7 +86,7 @@ private void SetEyebrowColor(int index) return; var current = Editor.Mii.MiiEyebrows; - if (index == current.Type) + if (index == (int)current.Color) return; var result = MiiEyebrow.Create( diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs index 0bf29744..46255ebc 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs @@ -83,7 +83,7 @@ private void SetEyeType(int index) private void SetEyeColor(int index) { var current = Editor.Mii.MiiEyes; - if (index == current.Type) + if (index == (int)current.Color) return; var result = MiiEye.Create(current.Type, current.Rotation, current.Vertical, (MiiEyeColor)index, current.Size, current.Spacing); From 7717c7081271ce012c0589d30a94cea3375167c7 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Mon, 1 Jun 2026 02:16:35 +0200 Subject: [PATCH 5/5] Release 2.4.8 --- Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml | 1 + WheelWizard/WheelWizard.csproj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml b/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml index 71cd0ac0..5ac6b982 100644 --- a/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml +++ b/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml @@ -55,6 +55,7 @@ + diff --git a/WheelWizard/WheelWizard.csproj b/WheelWizard/WheelWizard.csproj index 762c440c..742c2544 100644 --- a/WheelWizard/WheelWizard.csproj +++ b/WheelWizard/WheelWizard.csproj @@ -12,7 +12,7 @@ $(NoWarn);AVLN3001 - 2.4.7 + 2.4.8 This program will manage RetroRewind and mods :) GNU v3.0 https://github.com/patchzyy/WheelWizard