Skip to content

Commit

Permalink
Implement Steam download
Browse files Browse the repository at this point in the history
  • Loading branch information
affederaffe committed Aug 21, 2024
1 parent 7e1d1e8 commit 6190e3c
Show file tree
Hide file tree
Showing 17 changed files with 376 additions and 123 deletions.
4 changes: 2 additions & 2 deletions BeatSaberModManager/BeatSaberModManager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.2.1" />
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
<PackageReference Include="Avalonia" Version="11.1.3" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
Expand All @@ -46,7 +46,7 @@
<PackageReference Include="Avalonia.Labs.Qr" Version="11.1.0" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
<PackageReference Include="DynamicData" Version="9.0.3" />
<PackageReference Include="DynamicData" Version="9.0.4" />
<PackageReference Include="ReactiveUI" Version="20.1.1" />
<PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
Expand Down
16 changes: 10 additions & 6 deletions BeatSaberModManager/Resources/Localization/de.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,16 @@
<sys:String x:Key="ExceptionDialog:Title">Anwendung abgestürzt</sys:String>
<sys:String x:Key="ExceptionDialog:Ok">Ok</sys:String>

<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:Title">Steam Authentifizierung erforderlich</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:ContinueButton">Weiter</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:CancelButton">Abbrechen</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:UsernameLabel">Benutzername</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:PasswordLabel">Passwort</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:RememberPasswordLabel">Passwort merken</sys:String>
<sys:String x:Key="SteamLoginView:Title">Steam Authentifizierung erforderlich</sys:String>
<sys:String x:Key="SteamLoginView:LoginButton">Login</sys:String>
<sys:String x:Key="SteamLoginView:CancelButton">Abbrechen</sys:String>
<sys:String x:Key="SteamLoginView:UsernameLabel">Benutzername</sys:String>
<sys:String x:Key="SteamLoginView:PasswordLabel">Passwort</sys:String>
<sys:String x:Key="SteamLoginView:RememberPasswordLabel">Passwort merken</sys:String>

<sys:String x:Key="SteamGuardCodeView:Title">Gib den Code deiner Steam Mobile App ein</sys:String>
<sys:String x:Key="SteamGuardCodeView:SubmitButton">Absenden</sys:String>
<sys:String x:Key="SteamGuardCodeView:CancelButton">Cancel</sys:String>

<sys:String x:Key="Status:Installing">Installiere:</sys:String>
<sys:String x:Key="Status:Uninstalling">Deinstalliere:</sys:String>
Expand Down
16 changes: 10 additions & 6 deletions BeatSaberModManager/Resources/Localization/en.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,16 @@
<sys:String x:Key="ExceptionDialog:Title">Application Crashed</sys:String>
<sys:String x:Key="ExceptionDialog:Ok">Ok</sys:String>

<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:Title">Steam Authentication Required</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:ContinueButton">Continue</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:CancelButton">Cancel</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:UsernameLabel">Username</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:PasswordLabel">Password</sys:String>
<sys:String x:Key="SteamAuthenticationLoginDetailsDialog:RememberPasswordLabel">Remember Password</sys:String>
<sys:String x:Key="SteamLoginView:Title">Steam Authentication Required</sys:String>
<sys:String x:Key="SteamLoginView:LoginButton">Login</sys:String>
<sys:String x:Key="SteamLoginView:CancelButton">Cancel</sys:String>
<sys:String x:Key="SteamLoginView:UsernameLabel">Username</sys:String>
<sys:String x:Key="SteamLoginView:PasswordLabel">Password</sys:String>
<sys:String x:Key="SteamLoginView:RememberPasswordLabel">Remember Password</sys:String>

<sys:String x:Key="SteamGuardCodeView:Title">Enter the code from your Steam Mobile App</sys:String>
<sys:String x:Key="SteamGuardCodeView:SubmitButton">Submit</sys:String>
<sys:String x:Key="SteamGuardCodeView:CancelButton">Cancel</sys:String>

<sys:String x:Key="Status:Installing">Installing:</sys:String>
<sys:String x:Key="Status:Uninstalling">Uninstalling:</sys:String>
Expand Down
1 change: 0 additions & 1 deletion BeatSaberModManager/Resources/Styles/Controls.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<MergeResourceInclude Source="/Resources/Styles/IconHeader.axaml" />
<MergeResourceInclude Source="/Resources/Styles/ProgressRing.axaml" />
<MergeResourceInclude Source="/Resources/Styles/CardControl.axaml" />
<MergeResourceInclude Source="/Resources/Styles/AsyncImage.axaml" />
<MergeResourceInclude Source="/Resources/Styles/StatusBar.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,21 @@ public class SteamLegacyGameVersionInstaller(Steam3Session steam3Session, ISteam
return null;
string appDataDirPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string legacyGameVersionsDirPath = Path.Join(appDataDirPath, ThisAssembly.Info.Product, "LegacyGameVersions", gameVersion.GameVersion);
DownloadConfig downloadConfig = new() { InstallDirectory = legacyGameVersionsDirPath };
DownloadConfig downloadConfig = new() { InstallDirectory = legacyGameVersionsDirPath, MaxDownloads = 3 };
await steam3Session.LoginAsync(steamAuthenticator, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
return null;
ContentDownloader contentDownloader = new(downloadConfig, steam3Session);
List<(uint DepotId, ulong ManifestId)> depotManifestIds = [(620981, steamLegacyGameVersion.ManifestId)];
try
{
string[]? installDirs = await contentDownloader.DownloadAppAsync(620980, depotManifestIds, SteamConstants.DefaultBranch, null, null, null, false, false, cancellationToken, progress).ConfigureAwait(false);
return installDirs is { Length: 1 } ? installDirs[0] : null;
}
catch (TaskCanceledException)
{
return null;
}
catch (InvalidOperationException e)
{
logger.Error(e, "Failed to download depot");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public bool IsProtocolHandlerRegistered(string protocol)
using StreamReader streamReader = new(fileStream);
while (streamReader.ReadLine() is { } line)
{
if (line.StartsWith($"Exec={Program.Product}", StringComparison.Ordinal))
if (line.StartsWith($"Exec={ThisAssembly.Info.Product}", StringComparison.Ordinal))
return true;
}

Expand Down Expand Up @@ -65,18 +65,20 @@ public void UnregisterProtocolHandler(string protocol)

private string GetHandlerPathForProtocol(string protocol) => Path.Join(_localAppDataPath, GetHandlerNameForProtocol(protocol));

private static string GetHandlerNameForProtocol(string protocol) => $"{Program.Product}-url-{protocol}.desktop";
private static string GetHandlerNameForProtocol(string protocol) => $"{ThisAssembly.Info.Product}-url-{protocol}.desktop";

private static string GetDesktopFileContent(string protocol) =>
@$"[Desktop Entry]
Name={Program.Product}
Comment=URL:{protocol} Protocol
Type=Application
Categories=Utility
Exec='{Program.Product}' --install %u
Terminal=false
NoDisplay=true
MimeType=x-scheme-handler/{protocol}
";
$"""
[Desktop Entry]
Name={ThisAssembly.Info.Product}
Comment=URL:{protocol} Protocol
Type=Application
Categories=Utility
Exec='{ThisAssembly.Info.Product}' --install %u
Terminal=false
NoDisplay=true
MimeType=x-scheme-handler/{protocol}
""";
}
}
26 changes: 17 additions & 9 deletions BeatSaberModManager/ViewModels/GameVersionViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;

using BeatSaberModManager.Models.Interfaces;
using BeatSaberModManager.Services.Interfaces;

using ReactiveUI;

Expand All @@ -17,13 +19,14 @@ public sealed class GameVersionViewModel : ViewModelBase
/// <summary>
///
/// </summary>
/// <param name="gameVersion"></param>
public GameVersionViewModel(IGameVersion gameVersion)
public GameVersionViewModel(IGameVersion gameVersion, IInstallDirValidator installDirValidator)
{
ArgumentNullException.ThrowIfNull(installDirValidator);
GameVersion = gameVersion;
this.WhenAnyValue(static x => x.GameVersion)
.Select(static x => x is { InstallDir: not null })
.ToProperty(this, nameof(IsInstalled), out _isInstalled);
this.WhenAnyValue(static x => x.InstallDir)
.Select(installDirValidator.ValidateInstallDir)
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, static x => x.IsInstalled, out _isInstalled);
}

/// <summary>
Expand All @@ -36,12 +39,17 @@ public GameVersionViewModel(IGameVersion gameVersion)
/// </summary>
public string? InstallDir
{
get => _installDir ??= GameVersion.InstallDir;
set => GameVersion.InstallDir = this.RaiseAndSetIfChanged(ref _installDir, value);
get => GameVersion.InstallDir;
set
{
if (GameVersion.InstallDir == value)
return;
this.RaisePropertyChanging();
GameVersion.InstallDir = value;
this.RaisePropertyChanged();
}
}

private string? _installDir;

/// <summary>
///
/// </summary>
Expand Down
38 changes: 38 additions & 0 deletions BeatSaberModManager/ViewModels/LegacyGameVersionsTab.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;


namespace BeatSaberModManager.ViewModels
{
/// <inheritdoc />
public class LegacyGameVersionsTab : ViewModelBase
{
/// <summary>
///
/// </summary>
public LegacyGameVersionsTab(IGrouping<int, GameVersionViewModel> group, LegacyGameVersionsViewModel legacyGameVersionsViewModel)
{
ArgumentNullException.ThrowIfNull(group);
Year = group.Key;
Versions = group.ToArray();
LegacyGameVersionsViewModel = legacyGameVersionsViewModel;
}

/// <summary>
///
/// </summary>
public int Year { get; }

/// <summary>
/// TODO
/// </summary>
public IList<GameVersionViewModel> Versions { get; }

/// <summary>
///
/// </summary>
public LegacyGameVersionsViewModel LegacyGameVersionsViewModel { get; }

}
}
74 changes: 44 additions & 30 deletions BeatSaberModManager/ViewModels/LegacyGameVersionsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

using ReactiveUI;

using SteamKit2.Authentication;


namespace BeatSaberModManager.ViewModels
{
Expand All @@ -25,21 +23,23 @@ namespace BeatSaberModManager.ViewModels
public sealed class LegacyGameVersionsViewModel : ViewModelBase
{
private readonly IGameInstallLocator _gameInstallLocator;
private readonly IInstallDirValidator _installDirValidator;
private readonly ILegacyGameVersionProvider _legacyGameVersionProvider;
private readonly ILegacyGameVersionInstaller _legacyGameVersionInstaller;
private readonly ObservableAsPropertyHelper<bool> _isExecuting;
private readonly ObservableAsPropertyHelper<bool> _isSuccess;
private readonly ObservableAsPropertyHelper<bool> _canInstall;
private readonly ObservableAsPropertyHelper<bool> _canUninstall;
private readonly ObservableAsPropertyHelper<IReadOnlyList<IGrouping<int, GameVersionViewModel>>> _legacyGameVersions;
private readonly ObservableAsPropertyHelper<LegacyGameVersionsTab[]> _legacyGameVersions;

/// <summary>
/// TODO
/// </summary>
public LegacyGameVersionsViewModel(SettingsViewModel settingsViewModel, SteamAuthenticationViewModel steamAuthenticationViewModel, IGameInstallLocator gameInstallLocator, ILegacyGameVersionProvider legacyGameVersionProvider, ILegacyGameVersionInstaller legacyGameVersionInstaller, StatusProgress statusProgress)
public LegacyGameVersionsViewModel(SettingsViewModel settingsViewModel, SteamAuthenticationViewModel steamAuthenticationViewModel, IGameInstallLocator gameInstallLocator, IInstallDirValidator installDirValidator, ILegacyGameVersionProvider legacyGameVersionProvider, ILegacyGameVersionInstaller legacyGameVersionInstaller, StatusProgress statusProgress)
{
ArgumentNullException.ThrowIfNull(settingsViewModel);
_gameInstallLocator = gameInstallLocator;
_installDirValidator = installDirValidator;
_legacyGameVersionProvider = legacyGameVersionProvider;
_legacyGameVersionInstaller = legacyGameVersionInstaller;
SteamAuthenticationViewModel = steamAuthenticationViewModel;
Expand All @@ -49,22 +49,17 @@ public LegacyGameVersionsViewModel(SettingsViewModel settingsViewModel, SteamAut
InitializeCommand.CombineLatest(InitializeCommand.IsExecuting)
.Select(static x => x is (not null, false))
.ToProperty(this, nameof(IsSuccess), out _isSuccess);
InitializeCommand.WhereNotNull()
.Select(static gameVersions => gameVersions
.OrderByDescending(static x => x.GameVersion.ReleaseDate)
IObservable<LegacyGameVersionsTab[]> legacyGameVersionTabs = InitializeCommand.WhereNotNull()
.Select(gameVersions => gameVersions
.OrderByDescending(static version => version.GameVersion.ReleaseDate)
.GroupBy(static version => version.GameVersion.ReleaseDate.Year)
.ToArray())
.ToProperty(this, nameof(LegacyGameVersions), out _legacyGameVersions);
settingsViewModel.IsGameVersionValidObservable.FirstAsync()
.Where(static isValid => !isValid)
.CombineLatest(InitializeCommand)
.Select(static x => x.Second?.LastOrDefault(static gameVersion => gameVersion.IsInstalled))
.WhereNotNull()
.Subscribe(installedVersion => settingsViewModel.GameVersion = installedVersion.GameVersion);
settingsViewModel.ValidatedGameVersionObservable.FirstAsync()
.CombineLatest(InitializeCommand.WhereNotNull())
.Select(static x => x.Second.FirstOrDefault(gameVersion => gameVersion.GameVersion.GameVersion == x.First.GameVersion))
.Subscribe(gameVersion => SelectedGameVersion = gameVersion);
.Select(group => new LegacyGameVersionsTab(group, this))
.ToArray());
legacyGameVersionTabs.ToProperty(this, nameof(LegacyGameVersions), out _legacyGameVersions);
settingsViewModel.ValidatedGameVersionObservable
.FirstAsync()
.CombineLatest(legacyGameVersionTabs)
.Subscribe(x => MarkStoreVersionAsInstalled(x.First, x.Second));
IObservable<(GameVersionViewModel? GameVersion, bool IsInstalled)> whenAnySelectedGameVersion = this.WhenAnyValue(static x => x.SelectedGameVersion, static x => x.SelectedGameVersion!.IsInstalled, static (gameVersion, _) => (gameVersion, gameVersion?.IsInstalled ?? false));
IObservable<bool> canInstallVersion = whenAnySelectedGameVersion.Select(static x => x.GameVersion is not null && !x.IsInstalled);
canInstallVersion.ToProperty(this, nameof(CanInstall), out _canInstall);
Expand All @@ -83,11 +78,6 @@ public LegacyGameVersionsViewModel(SettingsViewModel settingsViewModel, SteamAut
/// </summary>
public SteamAuthenticationViewModel SteamAuthenticationViewModel { get; }

/// <summary>
/// TODO
/// </summary>
public Interaction<Unit, AuthPollResult?> AuthenticateSteamInteraction { get; } = new();

/// <summary>
/// TODO
/// </summary>
Expand Down Expand Up @@ -136,7 +126,7 @@ public LegacyGameVersionsViewModel(SettingsViewModel settingsViewModel, SteamAut
/// <summary>
/// TODO
/// </summary>
public IReadOnlyList<IGrouping<int, GameVersionViewModel>> LegacyGameVersions => _legacyGameVersions.Value;
public IReadOnlyList<LegacyGameVersionsTab> LegacyGameVersions => _legacyGameVersions.Value;

/// <summary>
/// TODO
Expand All @@ -163,24 +153,33 @@ public GameVersionViewModel? SelectedGameVersion
allInstalledGameVersions.Insert(0, installedStoreVersion);

if (availableGameVersions is not null && allInstalledGameVersions.Count == 0)
return availableGameVersions.Select(static gameVersion => new GameVersionViewModel(gameVersion)).ToArray();
return availableGameVersions.Select(gameVersion => new GameVersionViewModel(gameVersion, _installDirValidator)).ToArray();

if (availableGameVersions is null)
return allInstalledGameVersions.Select(static gameVersion => new GameVersionViewModel(gameVersion)).ToArray();
return allInstalledGameVersions.Select(gameVersion => new GameVersionViewModel(gameVersion, _installDirValidator)).ToArray();

foreach (IGameVersion gameVersion in availableGameVersions)
gameVersion.InstallDir = allInstalledGameVersions.FirstOrDefault(x => x.GameVersion == gameVersion.GameVersion)?.InstallDir;

return availableGameVersions.Select(static gameVersion => new GameVersionViewModel(gameVersion)).ToArray();
return availableGameVersions.Select(gameVersion => new GameVersionViewModel(gameVersion, _installDirValidator)).ToArray();
}

private async Task<bool> InstallSelectedLegacyGameVersionAsync()
{
GameVersionViewModel selectedGameVersion = SelectedGameVersion!;
StatusProgress.Report(new ProgressInfo(StatusType.Installing, selectedGameVersion.GameVersion.GameVersion));
string? installDir = await _legacyGameVersionInstaller.InstallLegacyGameVersionAsync(selectedGameVersion.GameVersion, CancellationToken.None, StatusProgress).ConfigureAwait(false);
using CancellationTokenSource cts = new();
using IDisposable subscription = SteamAuthenticationViewModel.CancelCommand.Subscribe(_ => cts.Cancel());
string? installDir = await _legacyGameVersionInstaller.InstallLegacyGameVersionAsync(selectedGameVersion.GameVersion, cts.Token, StatusProgress).ConfigureAwait(false);
if (installDir is null)
{
StatusProgress.Report(new ProgressInfo(StatusType.Failed, null));
return false;
}

selectedGameVersion.InstallDir = installDir;
return installDir is not null;
StatusProgress.Report(new ProgressInfo(StatusType.Completed, null));
return true;
}

private async Task<bool> UninstallSelectedLegacyGameVersionAsync()
Expand All @@ -191,5 +190,20 @@ private async Task<bool> UninstallSelectedLegacyGameVersionAsync()
selectedGameVersion.InstallDir = null;
return success;
}

private void MarkStoreVersionAsInstalled(IGameVersion storeVersion, IEnumerable<LegacyGameVersionsTab> tabs)
{
foreach (LegacyGameVersionsTab tab in tabs)
{
GameVersionViewModel? storeInstalledVersion = tab.Versions.FirstOrDefault(version => version.GameVersion.GameVersion == storeVersion.GameVersion);
if (storeInstalledVersion is null)
continue;

storeInstalledVersion.InstallDir = storeVersion.InstallDir;
storeInstalledVersion.GameVersion.InstallDir = storeVersion.InstallDir;
SelectedGameVersion = storeInstalledVersion;
break;
}
}
}
}
Loading

0 comments on commit 6190e3c

Please sign in to comment.