Skip to content

Commit 4137cd3

Browse files
authored
Merge pull request #299 from TeamWheelWizard/dev
Release 2.4.11
2 parents 8df6d60 + 1a03b9d commit 4137cd3

34 files changed

Lines changed: 664 additions & 326 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[Desktop Entry]
2+
Version=1.0
3+
Icon=io.github.TeamWheelWizard.WheelWizard
4+
Exec=WheelWizard %U
5+
Terminal=false
6+
NoDisplay=true
7+
StartupNotify=true
8+
Type=Application
9+
Categories=Game;
10+
StartupWMClass=WheelWizard
11+
MimeType=x-scheme-handler/wheelwizard;
12+
Name=Wheel Wizard – URL Handler
13+
GenericName=Mario Kart Wii Mod Manager
14+
Comment=Mario Kart Wii Mod Manager & Retro Rewind Auto Updater

Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
</provides>
5656

5757
<releases>
58+
<release version="2.4.11" date="2026-07-09"/>
5859
<release version="2.4.10" date="2026-06-05"/>
5960
<release version="2.4.9" date="2026-06-01"/>
6061
<release version="2.4.8" date="2026-06-01"/>

WheelWizard.Test/Features/GameBananaTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ private GameBananaModPreview CreateFakeModPreview(int id)
212212
},
213213
ModelName = "Mod",
214214
PreviewMedia = new(),
215+
HasContentRatings = false,
215216
};
216217
}
217218

WheelWizard.Test/Features/LinuxDolphinInstallerTests.cs

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,29 @@ public LinuxDolphinInstallerTests()
1717
}
1818

1919
[Fact]
20-
public void IsDolphinInstalledInFlatpak_ReturnsTrue_WhenFlatpakInfoExitCodeIsZero()
20+
public void IsDolphinInstalledInFlatpak_ReturnsTrue_WhenFlatpakListHasDolphin()
2121
{
22-
_processService.Run("flatpak", "info org.DolphinEmu.dolphin-emu").Returns(Ok(0));
22+
_processService.Run("flatpak", "list --app --columns=application", out var stdOut, out _).Returns(Ok(0)).AndDoes(callInfo => callInfo[2] = "Application ID\norg.DolphinEmu.dolphin-emu\n");
2323

2424
var result = _installer.IsDolphinInstalledInFlatpak();
2525

2626
Assert.True(result);
2727
}
2828

2929
[Fact]
30-
public void IsDolphinInstalledInFlatpak_ReturnsFalse_WhenFlatpakInfoExitCodeIsNonZero()
30+
public void IsDolphinInstalledInFlatpak_ReturnsFalse_WhenFlatpakListHasNoDolphin()
3131
{
32-
_processService.Run("flatpak", "info org.DolphinEmu.dolphin-emu").Returns(Ok(1));
32+
_processService.Run("flatpak", "list --app --columns=application", out var stdOut, out _).Returns(Ok(0)).AndDoes(callInfo => callInfo[2] = "Application ID\n");
33+
34+
var result = _installer.IsDolphinInstalledInFlatpak();
35+
36+
Assert.False(result);
37+
}
38+
39+
[Fact]
40+
public void IsDolphinInstalledInFlatpak_ReturnsFalse_WhenFlatpakListFails()
41+
{
42+
_processService.Run("flatpak", "list --app --columns=application", out _, out _).Returns(Ok(1));
3343

3444
var result = _installer.IsDolphinInstalledInFlatpak();
3545

@@ -77,13 +87,53 @@ public async Task InstallFlatpak_ReturnsSuccess_WhenInstallCompletesAndCommandBe
7787
Assert.True(result.IsSuccess);
7888
}
7989

90+
[Fact]
91+
public async Task InstallFlatpakDolphin_ReturnsFailure_WhenDolphinRemoteCommandFails()
92+
{
93+
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
94+
_processService
95+
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
96+
.Returns(Ok(1));
97+
_processService
98+
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
99+
.Returns(Ok(0));
100+
101+
var result = await _installer.InstallFlatpakDolphin();
102+
103+
Assert.True(result.IsFailure);
104+
Assert.Contains("exit code 1", result.Error.Message);
105+
}
106+
107+
[Fact]
108+
public async Task InstallFlatpakDolphin_ReturnsFailure_WhenFlathubRemoteCommandFails()
109+
{
110+
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
111+
_processService
112+
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
113+
.Returns(Ok(0));
114+
_processService
115+
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
116+
.Returns(Ok(1));
117+
118+
var result = await _installer.InstallFlatpakDolphin();
119+
120+
Assert.True(result.IsFailure);
121+
Assert.Contains("exit code 1", result.Error.Message);
122+
}
123+
80124
[Fact]
81125
public async Task InstallFlatpakDolphin_ReturnsFailure_WhenDolphinInstallCommandFails()
82126
{
83127
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
84128
_processService
85-
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
86-
.Returns(Task.FromResult<OperationResult<int>>(Ok(1)));
129+
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
130+
.Returns(Ok(0));
131+
_processService
132+
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
133+
.Returns(Ok(0));
134+
_processService
135+
.RunWithProgressAsync("flatpak", "install --user -y dolphin org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
136+
.Returns(Task.FromResult(Ok(1)));
87137

88138
var result = await _installer.InstallFlatpakDolphin();
89139

@@ -96,8 +146,14 @@ public async Task InstallFlatpakDolphin_ReturnsFailure_WhenWarmupLaunchFails()
96146
{
97147
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
98148
_processService
99-
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
100-
.Returns(Task.FromResult<OperationResult<int>>(Ok(0)));
149+
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
150+
.Returns(Ok(0));
151+
_processService
152+
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
153+
.Returns(Ok(0));
154+
_processService
155+
.RunWithProgressAsync("flatpak", "install --user -y dolphin org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
156+
.Returns(Task.FromResult(Ok(0)));
101157
_processService
102158
.LaunchAndStopAsync("flatpak", "run org.DolphinEmu.dolphin-emu", TimeSpan.FromSeconds(4))
103159
.Returns(Task.FromResult<OperationResult>(Fail("Launch failed")));
@@ -113,11 +169,17 @@ public async Task InstallFlatpakDolphin_ReturnsSuccess_WhenInstallAndWarmupSucce
113169
{
114170
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
115171
_processService
116-
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
117-
.Returns(Task.FromResult<OperationResult<int>>(Ok(0)));
172+
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
173+
.Returns(Ok(0));
174+
_processService
175+
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
176+
.Returns(Ok(0));
177+
_processService
178+
.RunWithProgressAsync("flatpak", "install --user -y dolphin org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
179+
.Returns(Task.FromResult(Ok(0)));
118180
_processService
119181
.LaunchAndStopAsync("flatpak", "run org.DolphinEmu.dolphin-emu", TimeSpan.FromSeconds(4))
120-
.Returns(Task.FromResult<OperationResult>(Ok()));
182+
.Returns(Task.FromResult(Ok()));
121183

122184
var result = await _installer.InstallFlatpakDolphin();
123185

WheelWizard.Test/Features/Settings/SettingsTests.cs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,23 @@ public class SettingsManagerTests
2020
[Fact]
2121
public void Get_Throws_WhenRequestedTypeDoesNotMatchSettingType()
2222
{
23-
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
23+
var manager = CreateManager(new MockFileSystem(), out _, out _);
2424

2525
Assert.Throws<InvalidOperationException>(() => manager.Get<int>(manager.WW_LANGUAGE));
2626
}
2727

2828
[Fact]
2929
public void Set_Throws_WhenProvidedValueIsNull()
3030
{
31-
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
31+
var manager = CreateManager(new MockFileSystem(), out _, out _);
3232

3333
Assert.Throws<ArgumentNullException>(() => manager.Set<string>(manager.WW_LANGUAGE, null!));
3434
}
3535

3636
[Fact]
3737
public void Set_ReturnsFalse_WhenValidationFails()
3838
{
39-
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
39+
var manager = CreateManager(new MockFileSystem(), out _, out _);
4040

4141
var result = manager.Set(manager.FOCUSED_USER, 99, skipSave: true);
4242

@@ -49,7 +49,7 @@ public void Set_ReturnsFalse_WhenValidationFails()
4949
[InlineData(2.01)]
5050
public void SavedWindowScale_RejectsValuesOutsideBounds(double scale)
5151
{
52-
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
52+
var manager = CreateManager(new MockFileSystem(), out _, out _);
5353

5454
var result = manager.Set(manager.SAVED_WINDOW_SCALE, scale, skipSave: true);
5555

@@ -62,7 +62,7 @@ public void SavedWindowScale_RejectsValuesOutsideBounds(double scale)
6262
[InlineData(2.01)]
6363
public void WindowScalePreview_RejectsValuesOutsideBounds(double scale)
6464
{
65-
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
65+
var manager = CreateManager(new MockFileSystem(), out _, out _);
6666

6767
var result = manager.Set(manager.WINDOW_SCALE, scale, skipSave: true);
6868

@@ -73,7 +73,7 @@ public void WindowScalePreview_RejectsValuesOutsideBounds(double scale)
7373
[Fact]
7474
public void ValidateCorePathSettings_ReturnsAllExpectedIssues_WhenDefaultsAreInvalid()
7575
{
76-
var manager = CreateManager(new RealFileSystem(), out _, out _, out _);
76+
var manager = CreateManager(new RealFileSystem(), out _, out _);
7777
#pragma warning disable CS0618
7878
SettingsRuntime.Initialize(manager);
7979
#pragma warning restore CS0618
@@ -91,7 +91,7 @@ public void ValidateCorePathSettings_ReturnsAllExpectedIssues_WhenDefaultsAreInv
9191
public void PathsSetupCorrectly_ReturnsTrue_WhenCorePathsAreValid()
9292
{
9393
var fileSystem = new MockFileSystem();
94-
var manager = CreateManager(fileSystem, out _, out _, out _);
94+
var manager = CreateManager(fileSystem, out _, out _);
9595
var userFolderPath = $"/wheelwizard-user-{Guid.NewGuid():N}";
9696
var gameFilePath = Path.Combine(userFolderPath, "game.iso");
9797
var dolphinLocation = SettingsTestUtils.GetValidDolphinLocation(fileSystem);
@@ -110,7 +110,7 @@ public void PathsSetupCorrectly_ReturnsTrue_WhenCorePathsAreValid()
110110
[Fact]
111111
public void LoadSettings_CallsUnderlyingManagersOnlyOnce()
112112
{
113-
var manager = CreateManager(new MockFileSystem(), out var whWzManager, out var dolphinManager, out _);
113+
var manager = CreateManager(new MockFileSystem(), out var whWzManager, out var dolphinManager);
114114

115115
manager.LoadSettings();
116116
manager.LoadSettings();
@@ -122,16 +122,13 @@ public void LoadSettings_CallsUnderlyingManagersOnlyOnce()
122122
private static SettingsManager CreateManager(
123123
IFileSystem fileSystem,
124124
out IWhWzSettingManager whWzSettingManager,
125-
out IDolphinSettingManager dolphinSettingManager,
126-
out ILinuxDolphinInstaller linuxDolphinInstaller
125+
out IDolphinSettingManager dolphinSettingManager
127126
)
128127
{
129128
whWzSettingManager = Substitute.For<IWhWzSettingManager>();
130129
dolphinSettingManager = Substitute.For<IDolphinSettingManager>();
131-
linuxDolphinInstaller = Substitute.For<ILinuxDolphinInstaller>();
132-
linuxDolphinInstaller.IsDolphinInstalledInFlatpak().Returns(true);
133130

134-
return new SettingsManager(whWzSettingManager, dolphinSettingManager, linuxDolphinInstaller, fileSystem);
131+
return new SettingsManager(whWzSettingManager, dolphinSettingManager, fileSystem);
135132
}
136133
}
137134

WheelWizard/Features/DolphinInstaller/LinuxDolphinInstaller.cs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace WheelWizard.DolphinInstaller;
33
public interface ILinuxDolphinInstaller
44
{
55
bool IsDolphinInstalledInFlatpak();
6+
bool IsDolphinInstalledNative();
67
bool IsFlatpakInstalled();
78
Task<OperationResult> InstallFlatpak(IProgress<int>? progress = null);
89
Task<OperationResult> InstallFlatpakDolphin(IProgress<int>? progress = null);
@@ -13,7 +14,21 @@ public sealed class LinuxDolphinInstaller(ILinuxCommandEnvironment commandEnviro
1314
{
1415
public bool IsDolphinInstalledInFlatpak()
1516
{
16-
var processResult = processService.Run("flatpak", "info org.DolphinEmu.dolphin-emu");
17+
const string dolphinAppId = "org.DolphinEmu.dolphin-emu";
18+
var processResult = processService.Run("flatpak", "list --app --columns=application", out var stdOut, out _);
19+
20+
return processResult.IsSuccess && processResult.Value == 0 && stdOut
21+
.Split('\n')
22+
.Any(line => line == dolphinAppId);
23+
}
24+
25+
public bool IsDolphinInstalledNative()
26+
{
27+
if (!commandEnvironment.IsCommandAvailable("dolphin-emu"))
28+
{
29+
return false;
30+
}
31+
var processResult = processService.Run("dolphin-emu", "--version");
1732
return processResult.IsSuccess && processResult.Value == 0;
1833
}
1934

@@ -56,17 +71,34 @@ public async Task<OperationResult> InstallFlatpakDolphin(IProgress<int>? progres
5671
return installFlatpakResult;
5772
}
5873

74+
var addRemoteResult = processService.Run(
75+
"flatpak",
76+
"remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo"
77+
);
78+
if (addRemoteResult.IsFailure)
79+
return addRemoteResult.Error;
80+
81+
if (addRemoteResult.Value != 0)
82+
return Fail($"Adding the Dolphin Flatpak remote failed with exit code {addRemoteResult.Value}.");
83+
84+
addRemoteResult = processService.Run(
85+
"flatpak",
86+
"remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo"
87+
);
88+
if (addRemoteResult.IsFailure)
89+
return addRemoteResult.Error;
90+
91+
if (addRemoteResult.Value != 0)
92+
return Fail($"Adding the Flathub Flatpak remote failed with exit code {addRemoteResult.Value}.");
93+
5994
var installDolphinResult = await processService.RunWithProgressAsync(
60-
"pkexec",
61-
"flatpak --system install -y org.DolphinEmu.dolphin-emu",
95+
"flatpak",
96+
"install --user -y dolphin org.DolphinEmu.dolphin-emu",
6297
progress
6398
);
6499
if (installDolphinResult.IsFailure)
65100
return installDolphinResult.Error;
66101

67-
if (installDolphinResult.Value is 126 or 127)
68-
return Fail("You need to be an administrator to install Dolphin via Flatpak.");
69-
70102
if (installDolphinResult.Value != 0)
71103
return Fail($"Dolphin installation failed with exit code {installDolphinResult.Value}.");
72104

WheelWizard/Features/DolphinInstaller/LinuxProcessService.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ namespace WheelWizard.DolphinInstaller;
55

66
public interface ILinuxProcessService
77
{
8+
OperationResult<int> Run(string fileName, string arguments, out string stdOut, out string stdErr);
89
OperationResult<int> Run(string fileName, string arguments);
910
Task<OperationResult<int>> RunWithProgressAsync(string fileName, string arguments, IProgress<int>? progress = null);
1011
Task<OperationResult> LaunchAndStopAsync(string fileName, string arguments, TimeSpan duration);
1112
}
1213

1314
public sealed class LinuxProcessService : ILinuxProcessService
1415
{
15-
public OperationResult<int> Run(string fileName, string arguments)
16+
public OperationResult<int> Run(string fileName, string arguments, out string stdOut, out string stdErr)
1617
{
17-
return TryCatch(
18+
var localStdOut = "";
19+
var localStdErr = "";
20+
var result = TryCatch(
1821
() =>
1922
{
2023
var processInfo = new ProcessStartInfo
@@ -31,11 +34,22 @@ public OperationResult<int> Run(string fileName, string arguments)
3134
if (process == null)
3235
return -1;
3336

37+
localStdOut = process.StandardOutput.ReadToEnd();
38+
localStdErr = process.StandardError.ReadToEnd();
3439
process.WaitForExit();
3540
return process.ExitCode;
3641
},
3742
$"Failed to run process: {fileName} {arguments}"
3843
);
44+
45+
stdOut = localStdOut;
46+
stdErr = localStdErr;
47+
return result;
48+
}
49+
50+
public OperationResult<int> Run(string fileName, string arguments)
51+
{
52+
return Run(fileName, arguments, out _, out _);
3953
}
4054

4155
public async Task<OperationResult<int>> RunWithProgressAsync(string fileName, string arguments, IProgress<int>? progress = null)

WheelWizard/Features/GameBanana/Domain/GameBananaModDetails.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,8 @@ public class GameBananaModDetails
6363
public required int DownloadCount { get; set; }
6464

6565
[JsonPropertyName("_aFiles")]
66-
public required List<GameBananaModFiles> Files { get; set; }
66+
public List<GameBananaModFiles>? Files { get; set; }
67+
68+
[JsonPropertyName("_aArchivedFiles")]
69+
public List<GameBananaModFiles>? ArchivedFiles { get; set; }
6770
}

WheelWizard/Features/GameBanana/Domain/GameBananaModPreview.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public class GameBananaModPreview
2525
[JsonPropertyName("_aPreviewMedia")]
2626
public required GameBananaPreviewMedia PreviewMedia { get; set; }
2727

28+
[JsonPropertyName("_bHasContentRatings")]
29+
public required bool HasContentRatings { get; set; }
30+
2831
[JsonPropertyName("_nLikeCount")]
2932
public int LikeCount { get; set; }
3033

WheelWizard/Features/GameBanana/GameBananaSingletonService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public GameBananaModPreview GetLoadingPreview()
7070
AvatarUrl = "",
7171
},
7272
PreviewMedia = new(),
73+
HasContentRatings = false,
7374
};
7475
}
7576
}

0 commit comments

Comments
 (0)