diff --git a/references/Steam4NET b/references/Steam4NET
index 19674e89d..8dcff45ab 160000
--- a/references/Steam4NET
+++ b/references/Steam4NET
@@ -1 +1 @@
-Subproject commit 19674e89da6385330f83771b0d88a2e1a67578a8
+Subproject commit 8dcff45ab4eb569173c48313ca241799a3b3aed5
diff --git a/references/SteamAchievementManager b/references/SteamAchievementManager
index 197e1df42..19b585c58 160000
--- a/references/SteamAchievementManager
+++ b/references/SteamAchievementManager
@@ -1 +1 @@
-Subproject commit 197e1df429851c177451da4f075eb6531a2ef795
+Subproject commit 19b585c58e933f282675c4d5d20ae275a2621aad
diff --git a/release-template.md b/release-template.md
index cf4e304eb..d5d079c05 100644
--- a/release-template.md
+++ b/release-template.md
@@ -1,5 +1,8 @@
### 版本亮点
1. ASF 升级至 V5.2.5.5
+2. 新增 Steam 云存档管理功能,可自行上传或删除 Steam 云存档
+3. 库存游戏支持筛选支持 Steam 云存档的游戏
+4. 更新 Avalonia 至 0.10.15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Controls/CustomFilePicker.xaml.cs b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Controls/CustomFilePicker.xaml.cs
index eaf11329c..2ea7e2bcb 100644
--- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Controls/CustomFilePicker.xaml.cs
+++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Controls/CustomFilePicker.xaml.cs
@@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Metadata;
+using System.Application.UI.Resx;
using System.Collections;
using System.IO;
using static System.Application.FilePicker2;
@@ -75,8 +76,6 @@ namespace System.Application.UI.Views.Controls
private async void CustomFilePicker_Tapped(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
- FilePickerFileType fileTypes = new ValueTuple[] { ("Image Files", FileEx.ImageFileExtensions) };
-
//if (string.IsNullOrEmpty(FileExtensions) || FileExtensions == "*")
//{
// fileTypes = new ValueTuple[] { ("All Files", new[] { "*" }) };
@@ -89,10 +88,15 @@ namespace System.Application.UI.Views.Controls
// };
//}
- await PickAsync((file) =>
+ var result = await PickAsync(PickOptions.Images);
+ if (result != null)
{
- FileName = file;
- }, fileTypes);
+ FileName = result.FullPath;
+ }
+ else
+ {
+ Toast.Show(AppResources.PleaseSelect);
+ }
}
}
}
diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/GameListPage.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/GameListPage.axaml
index 8cf0b421d..8e68032f8 100644
--- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/GameListPage.axaml
+++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/GameListPage.axaml
@@ -169,24 +169,31 @@
-->
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/GameListPage/CloudArchiveWindow.axaml.cs b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/GameListPage/CloudArchiveWindow.axaml.cs
new file mode 100644
index 000000000..7d42a40fb
--- /dev/null
+++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/GameListPage/CloudArchiveWindow.axaml.cs
@@ -0,0 +1,23 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using System.Application.UI.ViewModels;
+
+namespace System.Application.UI.Views.Windows
+{
+ public class CloudArchiveWindow : FluentWindow
+ {
+ public CloudArchiveWindow() : base()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/NewVersionWindow.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/NewVersionWindow.axaml
index cb813959b..3c099bc50 100644
--- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/NewVersionWindow.axaml
+++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/NewVersionWindow.axaml
@@ -44,6 +44,7 @@
Margin="10,0">
+
+
+ CloudArchiveWindow.axaml
+
+
+
\ No newline at end of file
diff --git a/src/ST.Client.Desktop.Windows/Services/Implementation/SteamworksLocalApiServiceImpl.cs b/src/ST.Client.Desktop.Windows/Services/Implementation/SteamworksLocalApiServiceImpl.cs
index 976eb091e..acbfa24ab 100644
--- a/src/ST.Client.Desktop.Windows/Services/Implementation/SteamworksLocalApiServiceImpl.cs
+++ b/src/ST.Client.Desktop.Windows/Services/Implementation/SteamworksLocalApiServiceImpl.cs
@@ -131,6 +131,7 @@ namespace System.Application.Services.Implementation
return SteamClient.SteamApps008.GetAvailableGameLanguages();
}
+ #region SteamUserStats
public bool GetStatValue(string name, out int value)
{
return SteamClient.SteamUserStats.GetStatValue(name, out value);
@@ -214,10 +215,59 @@ namespace System.Application.Services.Implementation
{
SteamClient.SteamUserStats.RequestGlobalAchievementPercentages();
}
+ #endregion
public void RunCallbacks(bool server)
{
SteamClient.RunCallbacks(server);
}
+
+ #region SteamRemoteStorage
+
+ public bool GetCloudArchiveQuota(out ulong totalBytes, out ulong availableBytes)
+ {
+ return SteamClient.SteamRemoteStorage.GetQuota(out totalBytes, out availableBytes);
+ }
+
+ public List? GetCloudArchiveFiles()
+ {
+ List files = new List();
+
+ int fileCount = SteamClient.SteamRemoteStorage.GetFileCount();
+ for (int i = 0; i < fileCount; ++i)
+ {
+ string name = SteamClient.SteamRemoteStorage.GetFileNameAndSize(i, out int length);
+ var file = new SteamRemoteFile(name, length, SteamClient.SteamRemoteStorage.FileExists(name)
+ , SteamClient.SteamRemoteStorage.FilePersisted(name), SteamClient.SteamRemoteStorage.GetFileTimestamp(name))
+ {
+ SyncPlatforms = (SteamKit2.ERemoteStoragePlatform)SteamClient.SteamRemoteStorage.GetSyncPlatforms(name),
+ };
+ files.Add(file);
+ }
+
+ return files;
+ }
+
+ public int FileRead(string name, byte[] buffer)
+ {
+ return SteamClient.SteamRemoteStorage.FileRead(name, buffer);
+ }
+
+ public bool FileWrite(string name, byte[] buffer)
+ {
+ return SteamClient.SteamRemoteStorage.FileWrite(name, buffer);
+ }
+
+ public bool FileForget(string name)
+ {
+ return SteamClient.SteamRemoteStorage.FileForget(name);
+ }
+
+ public bool FileDelete(string name)
+ {
+ return SteamClient.SteamRemoteStorage.FileDelete(name);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/ST.Client/Models/Enums/SteamAppRunType.cs b/src/ST.Client/Models/Enums/SteamAppRunType.cs
new file mode 100644
index 000000000..0b3f137df
--- /dev/null
+++ b/src/ST.Client/Models/Enums/SteamAppRunType.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace System.Application.Models
+{
+ public enum SteamAppRunType
+ {
+ Idle,
+ UnlockAchievement,
+ CloudManager,
+ }
+}
diff --git a/src/ST.Client/Models/Steam/SteamApp.cs b/src/ST.Client/Models/Steam/SteamApp.cs
index 339554abd..6d308f80c 100644
--- a/src/ST.Client/Models/Steam/SteamApp.cs
+++ b/src/ST.Client/Models/Steam/SteamApp.cs
@@ -535,21 +535,15 @@ namespace System.Application.Models
public SteamAppPropertyTable? ChangesData => _properties;
- //public event EventHandler? Modified;
-
- //private void OnEntryModified(object sender, EventArgs e)
- //{
- // var modified = Modified;
- // if (modified == null)
- // {
- // return;
- // }
- // modified(this, new EventArgs());
- //}
-
- public Process? StartSteamAppProcess(bool show = false)
+ public Process? StartSteamAppProcess(SteamAppRunType runType = SteamAppRunType.Idle)
{
- string arguments = $"-clt app {(show ? "" : "-silence")} -id {AppId}";
+ var arg = runType switch
+ {
+ SteamAppRunType.UnlockAchievement => "-achievement",
+ SteamAppRunType.CloudManager => "-cloudmanager",
+ _ => "-silence",
+ };
+ string arguments = $"-clt app {arg} -id {AppId}";
if (OperatingSystem2.IsWindows())
{
return Process = Process2.Start(IApplication.ProgramPath, arguments);
diff --git a/src/ST.Client/Models/Steam/SteamRemoteFile.cs b/src/ST.Client/Models/Steam/SteamRemoteFile.cs
new file mode 100644
index 000000000..3c7dab4fd
--- /dev/null
+++ b/src/ST.Client/Models/Steam/SteamRemoteFile.cs
@@ -0,0 +1,70 @@
+using ReactiveUI;
+using SteamKit2;
+using System;
+using System.Application.Services;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using FilePath = System.IO.Path;
+
+namespace System.Application.Models
+{
+ public class SteamRemoteFile : ReactiveObject
+ {
+ static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+
+ public string Name { get; private set; }
+
+ public bool Exists { get; }
+
+ public bool IsPersisted { get; }
+
+ public long Size { get; }
+
+ public DateTimeOffset Timestamp { get; }
+
+ public ERemoteStoragePlatform SyncPlatforms { get; set; }
+
+ public SteamRemoteFile(string name)
+ {
+ Name = name;
+ }
+
+ public SteamRemoteFile(string name, long length, bool exists, bool isPersisted, long timestamp)
+ {
+ Name = name;
+ Size = length;
+ Exists = exists;
+ IsPersisted = isPersisted;
+ Timestamp = timestamp.ToDateTimeOffsetS();
+ }
+
+ public int Read(byte[] buffer)
+ {
+ return ISteamworksLocalApiService.Instance.FileRead(Name, buffer);
+ }
+
+ public byte[] ReadAllBytes()
+ {
+ byte[] buffer = new byte[Size];
+ int read = Read(buffer);
+ if (read != buffer.Length) throw new IOException("Could not read entire file.");
+ return buffer;
+ }
+
+ public bool WriteAllBytes(byte[] buffer)
+ {
+ return ISteamworksLocalApiService.Instance.FileWrite(Name, buffer);
+ }
+
+ public bool Forget()
+ {
+ return ISteamworksLocalApiService.Instance.FileForget(Name);
+ }
+
+ public bool Delete()
+ {
+ return ISteamworksLocalApiService.Instance.FileDelete(Name);
+ }
+ }
+}
diff --git a/src/ST.Client/Services/ISteamworksLocalApiService.cs b/src/ST.Client/Services/ISteamworksLocalApiService.cs
index a33ab9e9b..b58033ba7 100644
--- a/src/ST.Client/Services/ISteamworksLocalApiService.cs
+++ b/src/ST.Client/Services/ISteamworksLocalApiService.cs
@@ -77,5 +77,17 @@ namespace System.Application.Services
int Result { get; }
}
+
+ bool GetCloudArchiveQuota(out ulong totalBytes, out ulong availableBytes);
+
+ List? GetCloudArchiveFiles();
+
+ int FileRead(string name, byte[] buffer);
+
+ bool FileWrite(string name, byte[] buffer);
+
+ bool FileForget(string name);
+
+ bool FileDelete(string name);
}
}
\ No newline at end of file
diff --git a/src/ST.Client/Services/IViewModelManager.cs b/src/ST.Client/Services/IViewModelManager.cs
index 3c6c35231..7297b59a6 100644
--- a/src/ST.Client/Services/IViewModelManager.cs
+++ b/src/ST.Client/Services/IViewModelManager.cs
@@ -25,6 +25,8 @@ namespace System.Application.Services
void InitUnlockAchievement(int appid);
+ void InitCloudManageMain(int appid);
+
///
/// 打开托盘菜单窗口
///
diff --git a/src/ST.Client/Services/Implementation/SteamworksLocalApiServiceImpl.cs b/src/ST.Client/Services/Implementation/SteamworksLocalApiServiceImpl.cs
index 566bb20fa..a6e466f72 100644
--- a/src/ST.Client/Services/Implementation/SteamworksLocalApiServiceImpl.cs
+++ b/src/ST.Client/Services/Implementation/SteamworksLocalApiServiceImpl.cs
@@ -88,5 +88,41 @@ namespace System.Application.Services.Implementation
public void RunCallbacks(bool server)
{
}
+
+ #region SteamRemoteStorage
+
+ public bool GetCloudArchiveQuota(out ulong totalBytes, out ulong availableBytes)
+ {
+ totalBytes = 0;
+ availableBytes = 0;
+ return false;
+ }
+
+ public List? GetCloudArchiveFiles()
+ {
+ return null;
+ }
+
+ public int FileRead(string name, byte[] buffer)
+ {
+ return 0;
+ }
+
+ public bool FileWrite(string name, byte[] buffer)
+ {
+ return false;
+ }
+
+ public bool FileForget(string name)
+ {
+ return false;
+ }
+
+ public bool FileDelete(string name)
+ {
+ return false;
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/ST.Client/Services/Implementation/ViewModelManager.cs b/src/ST.Client/Services/Implementation/ViewModelManager.cs
index 5a66640b3..66235e29f 100644
--- a/src/ST.Client/Services/Implementation/ViewModelManager.cs
+++ b/src/ST.Client/Services/Implementation/ViewModelManager.cs
@@ -11,8 +11,7 @@ namespace System.Application.Services.Implementation
{
internal sealed class ViewModelManager : ReactiveObject, IViewModelManager
{
- MainWindowViewModel? mainWindow;
- AchievementWindowViewModel? achievementWindow;
+ //MainWindowViewModel? mainWindow;
TaskBarWindowViewModel? taskbarWindow;
readonly CompositeDisposable compositeDisposable = new();
@@ -29,15 +28,19 @@ namespace System.Application.Services.Implementation
{
try
{
- if (appidUnlockAchievementHasValue)
+ if (isCloudManageMain)
{
- achievementWindow = new AchievementWindowViewModel(appidUnlockAchievement);
- mMainWindow = achievementWindow;
+ mMainWindow = new CloudArchiveWindowViewModel(steamaAppid);
+ }
+ else if (isUnlockAchievementMain)
+ {
+ mMainWindow = new AchievementWindowViewModel(steamaAppid);
}
else
{
- mainWindow = new MainWindowViewModel();
- mMainWindow = mainWindow;
+ //mainWindow = new MainWindowViewModel();
+ //mMainWindow = mainWindow;
+ mMainWindow = new MainWindowViewModel();
}
}
catch (Exception ex)
@@ -51,7 +54,7 @@ namespace System.Application.Services.Implementation
{
if (OperatingSystem2.IsWindows() && StartupOptions.Value.HasNotifyIcon)
{
- taskbarWindow = new TaskBarWindowViewModel(mainWindow);
+ taskbarWindow = new TaskBarWindowViewModel(mMainWindow);
}
}
catch (Exception ex)
@@ -62,13 +65,20 @@ namespace System.Application.Services.Implementation
}
}
- int appidUnlockAchievement;
- bool appidUnlockAchievementHasValue;
+ int steamaAppid;
+ bool isUnlockAchievementMain;
+ bool isCloudManageMain;
public void InitUnlockAchievement(int appid)
{
- appidUnlockAchievement = appid;
- appidUnlockAchievementHasValue = true;
+ steamaAppid = appid;
+ isUnlockAchievementMain = true;
+ }
+
+ public void InitCloudManageMain(int appid)
+ {
+ steamaAppid = appid;
+ isCloudManageMain = true;
}
//public Window GetMainWindow()
diff --git a/src/ST.Client/Settings/GameLibrarySettings.Avalonia.cs b/src/ST.Client/Settings/GameLibrarySettings.Avalonia.cs
index 05b9813c9..a4e349775 100644
--- a/src/ST.Client/Settings/GameLibrarySettings.Avalonia.cs
+++ b/src/ST.Client/Settings/GameLibrarySettings.Avalonia.cs
@@ -13,13 +13,23 @@ namespace System.Application.Settings
static readonly SerializableProperty _GameIsInstalledFilter = GetProperty(defaultValue: false, autoSave: true);
///
- /// 游戏类型筛选状态列表
+ /// 已安装游戏筛选
///
[SupportedOSPlatform("Windows7.0")]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Linux")]
public static SerializableProperty GameIsInstalledFilter => _GameIsInstalledFilter;
+ static readonly SerializableProperty _GameIsCloudArchiveFilter = GetProperty(defaultValue: false, autoSave: true);
+
+ ///
+ /// 支持云存档游戏筛选状态
+ ///
+ [SupportedOSPlatform("Windows7.0")]
+ [SupportedOSPlatform("macOS")]
+ [SupportedOSPlatform("Linux")]
+ public static SerializableProperty GameIsCloudArchiveFilter => _GameIsCloudArchiveFilter;
+
static readonly SerializableProperty>? _GameTypeFiltres = IApplication.IsDesktopPlatform ? GetProperty(defaultValue: new List { SteamAppType.Game, SteamAppType.Application, SteamAppType.Demo, SteamAppType.Beta }, autoSave: true) : null;
///
diff --git a/src/ST.Client/SteamApiUrls.cs b/src/ST.Client/SteamApiUrls.cs
index b69d4cd1b..7387b86ec 100644
--- a/src/ST.Client/SteamApiUrls.cs
+++ b/src/ST.Client/SteamApiUrls.cs
@@ -29,12 +29,13 @@ namespace System.Application
public const string STEAM_VIDEO_URL = "https://store.steampowered.com/video/watch/{0}/";
public const string STEAM_REGISTERKEY_URL = "https://store.steampowered.com/account/registerkey?key={0}";
public const string STEAM_FRIENDMESSAGESLOG_URL = "https://help.steampowered.com/zh-cn/accountdata/GetFriendMessagesLog";
+ public const string STEAM_REMOTESTORAGE_URL = "https://store.steampowered.com/account/remotestorage";
+ public const string STEAM_KEY_URL = "https://store.steampowered.com/account/registerkey?key={0}";
public const string STEAM_INSTALL_URL = "steam://install/{0}";
public const string STEAM_RUNGAME_URL = "steam://rungameid/{0}";
public const string STEAM_NAVGAME_URL = "steam://nav/games/details/{0}";
public const string STEAM_OPENURL = "steam://openurl/{0}";
- public const string STEAM_RUN_KEYURL = "steam://openurl/https://store.steampowered.com/account/registerkey?key={0}";
public const string STEAMDB_USERINFO_URL = "https://api.steamdb.ml/v1/users/{0}";
public const string STEAMDB_APPINFO_URL = "https://api.steamdb.ml/v1/apps/{0}";
diff --git a/src/ST.Client/UI/Resx/AppResources.Designer.cs b/src/ST.Client/UI/Resx/AppResources.Designer.cs
index 7b4d1ed07..4a48b7ba7 100644
--- a/src/ST.Client/UI/Resx/AppResources.Designer.cs
+++ b/src/ST.Client/UI/Resx/AppResources.Designer.cs
@@ -1607,6 +1607,15 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 创建时间 的本地化字符串。
+ ///
+ public static string CreateTime {
+ get {
+ return ResourceManager.GetString("CreateTime", resourceCulture);
+ }
+ }
+
///
/// 查找类似 选择文件 的本地化字符串。
///
@@ -1787,6 +1796,15 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 下载 {0} 成功 的本地化字符串。
+ ///
+ public static string Download_Success_FileNameTip {
+ get {
+ return ResourceManager.GetString("Download_Success_FileNameTip", resourceCulture);
+ }
+ }
+
///
/// 查找类似 已下载 的本地化字符串。
///
@@ -1931,6 +1949,15 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 {0}个文件 的本地化字符串。
+ ///
+ public static string FileNumber {
+ get {
+ return ResourceManager.GetString("FileNumber", resourceCulture);
+ }
+ }
+
///
/// 查找类似 文件路径 的本地化字符串。
///
@@ -1940,6 +1967,15 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 文件大小 的本地化字符串。
+ ///
+ public static string FileSize {
+ get {
+ return ResourceManager.GetString("FileSize", resourceCulture);
+ }
+ }
+
///
/// 查找类似 文件大小过大 的本地化字符串。
///
@@ -2039,6 +2075,24 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 确定要删除当前选中的存档数据吗? 的本地化字符串。
+ ///
+ public static string GameList_CloudArchiveDeleteTip {
+ get {
+ return ResourceManager.GetString("GameList_CloudArchiveDeleteTip", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 Steam云存档管理 的本地化字符串。
+ ///
+ public static string GameList_CloudArchiveManager {
+ get {
+ return ResourceManager.GetString("GameList_CloudArchiveManager", resourceCulture);
+ }
+ }
+
///
/// 查找类似 是否移除全部挂时长的游戏? 的本地化字符串。
///
@@ -2183,6 +2237,15 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 仅显示支持Steam云存档的游戏 的本地化字符串。
+ ///
+ public static string GameList_OnlyCloudArchive {
+ get {
+ return ResourceManager.GetString("GameList_OnlyCloudArchive", resourceCulture);
+ }
+ }
+
///
/// 查找类似 仅显示已安装的游戏 的本地化字符串。
///
@@ -2436,6 +2499,15 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 手动上传存档文件 的本地化字符串。
+ ///
+ public static string GameList_UploadCloudArchiveFile {
+ get {
+ return ResourceManager.GetString("GameList_UploadCloudArchiveFile", resourceCulture);
+ }
+ }
+
///
/// 查找类似 游戏工具 的本地化字符串。
///
@@ -5763,6 +5835,24 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 上传失败 的本地化字符串。
+ ///
+ public static string UploadFailed {
+ get {
+ return ResourceManager.GetString("UploadFailed", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 上传成功 的本地化字符串。
+ ///
+ public static string UploadSuccess {
+ get {
+ return ResourceManager.GetString("UploadSuccess", resourceCulture);
+ }
+ }
+
///
/// 查找类似 上传您的头像 的本地化字符串。
///
diff --git a/src/ST.Client/UI/Resx/AppResources.resx b/src/ST.Client/UI/Resx/AppResources.resx
index d1b2546cc..acb461599 100644
--- a/src/ST.Client/UI/Resx/AppResources.resx
+++ b/src/ST.Client/UI/Resx/AppResources.resx
@@ -591,6 +591,9 @@ AAAAA-BBBBB-CCCC3
仅显示已安装的游戏
+
+ 仅显示支持Steam云存档的游戏
+
浏览安装文件夹
@@ -2267,4 +2270,34 @@ AAAAA-BBBBB-CCCC3
原名
+
+ 文件大小
+
+
+ 创建时间
+
+
+ Steam云存档管理
+
+
+ 确定要删除当前选中的存档数据吗?
+
+
+ 手动上传存档文件
+
+
+ 刷新列表
+
+
+ {0}个文件
+
+
+ 上传成功
+
+
+ 上传失败
+
+
+ 下载 {0} 成功
+
\ No newline at end of file
diff --git a/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs b/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs
index 1b53ec0ed..63e070221 100644
--- a/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs
@@ -97,7 +97,7 @@ namespace System.Application.UI.ViewModels
#region InitTabItems
- AddTabItem();
+ //AddTabItem();
AddTabItem();
if (IApplication.IsDesktopPlatform)
{
diff --git a/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs b/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs
index d5bf606e1..98616c3b1 100644
--- a/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs
+++ b/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs
@@ -70,6 +70,16 @@ namespace System.Application.UI.ViewModels
};
}
+ Func PredicateCloudArchive(bool isCloudArchiveFilter)
+ {
+ return s =>
+ {
+ if (isCloudArchiveFilter)
+ return s.IsCloudArchive;
+ return true;
+ };
+ }
+
public GameListPageViewModel()
{
_IconKey = nameof(GameListPageViewModel);
@@ -91,6 +101,8 @@ namespace System.Application.UI.ViewModels
IsInstalledFilter = GameLibrarySettings.GameIsInstalledFilter.Value;
+ //IsCloudArchiveFilter = GameLibrarySettings.GameIsInstalledFilter.Value;
+
this.WhenValueChanged(x => x.IsInstalledFilter, false)
.Subscribe(s => GameLibrarySettings.GameIsInstalledFilter.Value = s);
@@ -98,6 +110,8 @@ namespace System.Application.UI.ViewModels
var installFilter = this.WhenAnyValue(x => x.IsInstalledFilter).Select(PredicateInstalled);
+ var isCloudArchiveFilter = this.WhenAnyValue(x => x.IsCloudArchiveFilter).Select(PredicateCloudArchive);
+
var typeFilter = this.WhenAnyValue(x => x.EnableAppTypeFiltres).Select(PredicateType);
this.WhenAnyValue(x => x.AppTypeFiltres)
@@ -119,6 +133,7 @@ namespace System.Application.UI.ViewModels
.Filter(nameFilter)
.Filter(typeFilter)
.Filter(installFilter)
+ .Filter(isCloudArchiveFilter)
.ObserveOn(RxApp.MainThreadScheduler)
.Sort(SortExpressionComparer.Ascending(x => x.DisplayName).ThenByDescending(s => s.SizeOnDisk))
.Bind(out _SteamApps)
@@ -195,6 +210,14 @@ namespace System.Application.UI.ViewModels
set => this.RaiseAndSetIfChanged(ref _IsInstalledFilter, value);
}
+ private bool _IsCloudArchiveFilter;
+
+ public bool IsCloudArchiveFilter
+ {
+ get => _IsCloudArchiveFilter;
+ set => this.RaiseAndSetIfChanged(ref _IsCloudArchiveFilter, value);
+ }
+
private bool _IsAppInfoOpen;
public bool IsAppInfoOpen
@@ -388,7 +411,7 @@ namespace System.Application.UI.ViewModels
if (result.IsOK())
{
Toast.Show(AppResources.GameList_RuningWait);
- app.StartSteamAppProcess(true);
+ app.StartSteamAppProcess(SteamAppRunType.UnlockAchievement);
SteamConnectService.Current.RuningSteamApps.TryAdd(app.AppId, app);
}
break;
@@ -398,6 +421,21 @@ namespace System.Application.UI.ViewModels
}
}
+ public static async void ManageCloudArchive_Click(SteamApp app)
+ {
+ if (!ISteamService.Instance.IsRunningSteamProcess)
+ {
+ Toast.Show(AppResources.GameList_SteamNotRuning);
+ return;
+ }
+ if (app.IsCloudArchive)
+ {
+ Toast.Show(AppResources.GameList_RuningWait);
+ app.StartSteamAppProcess(SteamAppRunType.CloudManager);
+ SteamConnectService.Current.RuningSteamApps.TryAdd(app.AppId, app);
+ }
+ }
+
public static void AppGridReSize()
{
UISettings.AppGridSize.Value = UISettings.AppGridSize.Value == 200 ? 150 : 200;
diff --git a/src/ST.Client/UI/ViewModels/Windows/GameListPage/CloudArchiveWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/GameListPage/CloudArchiveWindowViewModel.cs
new file mode 100644
index 000000000..a7162b50b
--- /dev/null
+++ b/src/ST.Client/UI/ViewModels/Windows/GameListPage/CloudArchiveWindowViewModel.cs
@@ -0,0 +1,188 @@
+using DynamicData;
+using DynamicData.Binding;
+using ReactiveUI;
+using System.Application.Models;
+using System.Application.Services;
+using System.Application.UI.Resx;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Properties;
+using System.Reactive.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Application.UI.ViewModels
+{
+ public class CloudArchiveWindowViewModel : WindowViewModel
+ {
+ public static string DisplayName => AppResources.GameList_CloudArchiveManager;
+
+ private int AppId { get; }
+
+ #region 云存档列表
+ private readonly ReadOnlyObservableCollection? _CloudArchivews;
+ private readonly SourceList _CloudArchivewSourceList;
+
+ public ReadOnlyObservableCollection? CloudArchivews => _CloudArchivews;
+
+ public string CloudArchivewCountStr => string.Format(AppResources.FileNumber, CloudArchivews?.Count ?? 0);
+ #endregion
+
+ //private bool? _IsCheckAll;
+
+ //public bool? IsCheckAll
+ //{
+ // get => _IsCheckAll;
+ // set => this.RaiseAndSetIfChanged(ref _IsCheckAll, value);
+ //}
+
+ public bool IsCloudEnabledForAccount { get; }
+
+ private bool _IsCloudEnabledForApp;
+
+ public bool IsCloudEnabledForApp
+ {
+ get => _IsCloudEnabledForApp;
+ set => this.RaiseAndSetIfChanged(ref _IsCloudEnabledForApp, value);
+ }
+
+ private int _UsedQutoa;
+
+ public int UsedQutoa
+ {
+ get => _UsedQutoa;
+ set => this.RaiseAndSetIfChanged(ref _UsedQutoa, value);
+ }
+
+ private int _TotalQutoa;
+
+ public int TotalQutoa
+ {
+ get => _TotalQutoa;
+ set => this.RaiseAndSetIfChanged(ref _TotalQutoa, value);
+ }
+
+ public CloudArchiveWindowViewModel(int appid)
+ {
+ Title = GetTitleByDisplayName(DisplayName);
+
+ SteamConnectService.Current.Initialize(appid);
+
+ if (SteamConnectService.Current.IsConnectToSteam == false)
+ {
+ MessageBox.ShowAsync(AppResources.Achievement_Warning_1, Title, MessageBox.Button.OK).ContinueWith(s =>
+ {
+ EnforceClose();
+ }).Wait();
+ }
+
+ _CloudArchivewSourceList = new SourceList();
+
+ _CloudArchivewSourceList
+ .Connect()
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Sort(SortExpressionComparer.Descending(x => x.Timestamp).ThenByAscending(x => x.Name))
+ .Bind(out _CloudArchivews)
+ .Subscribe(_ => this.RaisePropertyChanged(nameof(CloudArchivewCountStr)));
+
+ AppId = appid;
+ string name = ISteamworksLocalApiService.Instance.GetAppData((uint)appid, "name");
+ name ??= appid.ToString();
+ Title = Constants.HARDCODED_APP_NAME + " | " + name;
+ ToastService.Current.Set(AppResources.Achievement_LoadData);
+
+ RefreshList();
+ }
+
+ public void RefreshList()
+ {
+ var results = ISteamworksLocalApiService.Instance.GetCloudArchiveFiles();
+ _CloudArchivewSourceList.Clear();
+ if (results.Any_Nullable())
+ _CloudArchivewSourceList.AddRange(results);
+
+ ISteamworksLocalApiService.Instance.GetCloudArchiveQuota(out var totalBytes, out var availBytes);
+ TotalQutoa = (int)(totalBytes / 1024 / 1024);
+ UsedQutoa = (int)((totalBytes - availBytes) / 1024 / 1024);
+ }
+
+ public async void ClearAllFiles()
+ {
+ var result = await MessageBox.ShowAsync(AppResources.Achievement_ResetWaring_1, Title, MessageBox.Button.OKCancel);
+ if (result.IsOK())
+ {
+ RefreshList();
+ }
+ }
+
+ public async void UploadFile()
+ {
+ var result = await FilePicker2.PickAsync(new PickOptions
+ {
+ PickerTitle = "",
+ Multiselect = true,
+ });
+
+ if (result != null)
+ {
+ var file = new SteamRemoteFile(result.FileName.ToLowerInvariant());
+
+ try
+ {
+ byte[] data = File.ReadAllBytes(result.FullPath);
+ if (!file.WriteAllBytes(data))
+ {
+ throw new IOException("Upload File Write Failed");
+ }
+ Toast.Show(AppResources.UploadSuccess);
+ }
+ catch (IOException)
+ {
+ Toast.Show(AppResources.UploadFailed);
+ }
+ }
+
+ RefreshList();
+ }
+
+ public async void DownloadFile(SteamRemoteFile remoteFile)
+ {
+ var result = await FilePicker2.SaveAsync(new SaveOptions
+ {
+ PickerTitle = "Download " + remoteFile.Name,
+ InitialFileName = remoteFile.Name,
+ });
+
+ if (result != null)
+ {
+ using var dest = result.OpenWrite();
+ var b = remoteFile.ReadAllBytes();
+ await dest.WriteAsync(b, 0, b.Length);
+ dest.Close();
+
+ Toast.Show(string.Format(AppResources.Download_Success_FileNameTip, remoteFile.Name));
+ }
+ }
+
+ public async void DeleteFile(SteamRemoteFile remoteFile)
+ {
+ var result = await MessageBox.ShowAsync(AppResources.GameList_CloudArchiveDeleteTip, Title, MessageBox.Button.OKCancel);
+ if (result.IsOK())
+ {
+ if (remoteFile.Delete())
+ {
+ RefreshList();
+ }
+ }
+ }
+
+ void EnforceClose()
+ {
+ Process.GetCurrentProcess().Kill();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.cs
index 393d564f9..fc62e9dbf 100644
--- a/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.cs
+++ b/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.cs
@@ -43,7 +43,7 @@ namespace System.Application.UI.ViewModels
}
}
- TaskBarWindowViewModel(WindowViewModel? mainwindow) : this(mainwindow is MainWindowViewModel mainwindow2 ? mainwindow2 : null)
+ public TaskBarWindowViewModel(WindowViewModel? mainwindow) : this(mainwindow is MainWindowViewModel mainwindow2 ? mainwindow2 : null)
{
}