mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-03-21 22:40:18 +00:00
renamed commands to exporter and importer, changed button location to saveManager and reimplemented exporter using libhac instead of c# file api
This commit is contained in:
parent
11231d8271
commit
94a2c9de75
9 changed files with 305 additions and 123 deletions
|
@ -421,5 +421,10 @@ namespace Ryujinx.Ava.Common
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string subSrcPath, string subDstPath)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,83 +0,0 @@
|
||||||
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
|
||||||
using Ryujinx.Ava.Ui.Windows;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Logger = Ryujinx.Common.Logging.Logger;
|
|
||||||
using Path = System.IO.Path;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
|
||||||
{
|
|
||||||
internal class BackupSavedataCommand : ICommand
|
|
||||||
{
|
|
||||||
public event EventHandler CanExecuteChanged;
|
|
||||||
|
|
||||||
private readonly IControl parentControl;
|
|
||||||
|
|
||||||
public BackupSavedataCommand(IControl parentControl)
|
|
||||||
{
|
|
||||||
this.parentControl = parentControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanExecute(object parameter)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute(object parameter)
|
|
||||||
{
|
|
||||||
SaveUserSaveDirectoryAsZip();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void SaveUserSaveDirectoryAsZip()
|
|
||||||
{
|
|
||||||
CreateBackupZip(await GetAndPrepareBackupPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> GetAndPrepareBackupPath()
|
|
||||||
{
|
|
||||||
SaveFileDialog saveFileDialog = new SaveFileDialog()
|
|
||||||
{
|
|
||||||
Title = LocaleManager.Instance["CreateZipFileDialogTitle"],
|
|
||||||
InitialFileName = "Ryujinx_backup.zip",
|
|
||||||
Filters = new System.Collections.Generic.List<FileDialogFilter>(new[] { new FileDialogFilter() { Extensions = new System.Collections.Generic.List<string>() { "zip" } } })
|
|
||||||
};
|
|
||||||
|
|
||||||
string zipPath = await saveFileDialog.ShowAsync(parentControl.VisualRoot as MainWindow);
|
|
||||||
|
|
||||||
if (File.Exists(zipPath))
|
|
||||||
{
|
|
||||||
File.Delete(zipPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return zipPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateBackupZip(string userBackupPath)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(userBackupPath) && Directory.Exists(Directory.GetParent(userBackupPath).FullName))
|
|
||||||
{
|
|
||||||
string saveDir = Path.Combine(AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", "save");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Logger.Info.Value.Print(LogClass.Application, $"Start creating backup...", nameof(BackupSavedataCommand));
|
|
||||||
|
|
||||||
ZipFile.CreateFromDirectory(saveDir, userBackupPath);
|
|
||||||
|
|
||||||
Logger.Info.Value.Print(LogClass.Application, $"Backup done. Zip is locate under {userBackupPath}", nameof(BackupSavedataCommand));
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
Logger.Error.Value.Print(LogClass.Application, $"Could not create backup zip file.", nameof(BackupSavedataCommand));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,11 +2,14 @@ using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
using LibHac.FsSystem;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Ui.ViewModels;
|
using Ryujinx.Ava.Ui.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
@ -17,20 +20,24 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
public ContentManager ContentManager { get; }
|
public ContentManager ContentManager { get; }
|
||||||
public VirtualFileSystem VirtualFileSystem { get; }
|
public VirtualFileSystem VirtualFileSystem { get; }
|
||||||
public HorizonClient HorizonClient { get; }
|
public HorizonClient HorizonClient { get; }
|
||||||
|
public List<ApplicationData> Applications { get; }
|
||||||
public UserProfileViewModel ViewModel { get; set; }
|
public UserProfileViewModel ViewModel { get; set; }
|
||||||
|
|
||||||
public NavigationDialogHost()
|
public NavigationDialogHost()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
||||||
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
|
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient, List<ApplicationData> applications)
|
||||||
{
|
{
|
||||||
AccountManager = accountManager;
|
AccountManager = accountManager;
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
HorizonClient = horizonClient;
|
HorizonClient = horizonClient;
|
||||||
|
Applications = applications;
|
||||||
ViewModel = new UserProfileViewModel(this);
|
ViewModel = new UserProfileViewModel(this);
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,9 +67,9 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
|
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
|
||||||
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
|
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient, List<ApplicationData> applications)
|
||||||
{
|
{
|
||||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient, applications);
|
||||||
ContentDialog contentDialog = new ContentDialog
|
ContentDialog contentDialog = new ContentDialog
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
||||||
|
|
242
Ryujinx.Ava/Ui/Controls/SaveDataExporter.cs
Normal file
242
Ryujinx.Ava/Ui/Controls/SaveDataExporter.cs
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using LibHac;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.Fs.Shim;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Ui.Models;
|
||||||
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logger = Ryujinx.Common.Logging.Logger;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
internal class SaveDataExporter
|
||||||
|
{
|
||||||
|
private readonly UserProfile _userProfile;
|
||||||
|
private readonly HorizonClient _horizonClient;
|
||||||
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
|
private readonly UserId _userId;
|
||||||
|
|
||||||
|
public SaveDataExporter(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||||
|
{
|
||||||
|
_userProfile = userProfile;
|
||||||
|
_horizonClient = horizonClient;
|
||||||
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
_userId = new UserId(
|
||||||
|
ulong.Parse(_userProfile.UserId.High.ToString(), System.Globalization.NumberStyles.HexNumber),
|
||||||
|
ulong.Parse(_userProfile.UserId.Low.ToString(), System.Globalization.NumberStyles.HexNumber)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void SaveUserSaveDirectoryAsZip(MainWindow mainWindow, List<SaveModel> saves, List<ApplicationData> applications)
|
||||||
|
{
|
||||||
|
string backupFolder = await GetAndPrepareBackupPath(mainWindow);
|
||||||
|
CreateBackup(backupFolder, saves, applications);
|
||||||
|
|
||||||
|
ZipFile.CreateFromDirectory(backupFolder, backupFolder + ".zip");
|
||||||
|
Directory.Delete(backupFolder, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateBackup(string backupPath, List<SaveModel> saves, List<ApplicationData> applications)
|
||||||
|
{
|
||||||
|
string mountName = "save";
|
||||||
|
string outputMountName = "output";
|
||||||
|
|
||||||
|
foreach (ApplicationData application in applications)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Register destination folder as output and mount output
|
||||||
|
Result registerOutpDirResult = RegisterOutputDirectory(Path.Combine(backupPath, application.TitleId), outputMountName);
|
||||||
|
if (registerOutpDirResult.IsFailure())
|
||||||
|
{
|
||||||
|
Logger.Error.Value.Print(LogClass.Application, $"Could not register and mount output directory.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Mount SaveData as save, opens the saveDataIterators and starts reading saveDataInfo
|
||||||
|
Result openAndReadSaveDataResult = OpenSaveDataIteratorAndReadSaveData(mountName, application, out SaveDataInfo saveDataInfo);
|
||||||
|
|
||||||
|
if(openAndReadSaveDataResult.IsFailure())
|
||||||
|
{
|
||||||
|
Logger.Error.Value.Print(LogClass.Application, $"Could not open save Iterator and start reading for application: {application.TitleName}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Copies the whole directory from save mount to output mount
|
||||||
|
Result copyDirResult = CopySaveDataDirectory(mountName, outputMountName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Unmount save and output
|
||||||
|
UnmountDirectory(saveDataInfo.ProgramId.Value, mountName);
|
||||||
|
UnmountDirectory(saveDataInfo.ProgramId.Value, outputMountName);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result RegisterOutputDirectory(string backupPath, string mountName)
|
||||||
|
{
|
||||||
|
using UniqueRef<IFileSystem> outputFileSystem = new UniqueRef<IFileSystem>(new LibHac.FsSystem.LocalFileSystem(backupPath));
|
||||||
|
|
||||||
|
Result registerResult = _horizonClient.Fs.Register(mountName.ToU8Span(), ref outputFileSystem.Ref());
|
||||||
|
if (registerResult.IsFailure()) return registerResult.Miss();
|
||||||
|
|
||||||
|
return registerResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result GetSaveDataIterator(out SaveDataInfo saveDataInfo, ApplicationData application)
|
||||||
|
{
|
||||||
|
return _horizonClient.Fs.FindSaveDataWithFilter(out saveDataInfo,
|
||||||
|
SaveDataSpaceId.User,
|
||||||
|
SaveDataFilter.Make(ulong.Parse(application.TitleId, NumberStyles.HexNumber),
|
||||||
|
saveType: default,
|
||||||
|
_userId,
|
||||||
|
saveDataId: default,
|
||||||
|
index: default));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result MountSaveDataDirectory(ulong programId, string mountName)
|
||||||
|
{
|
||||||
|
U8Span mountNameu8 = mountName.ToU8Span();
|
||||||
|
|
||||||
|
if (!_horizonClient.Fs.IsMounted(mountNameu8))
|
||||||
|
{
|
||||||
|
return _horizonClient.Fs.MountSaveData(mountNameu8, ConvertProgramIdToApplicationId(programId), _userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result UnmountDirectory(ulong programId, string mountName)
|
||||||
|
{
|
||||||
|
U8Span mountNameu8 = mountName.ToU8Span();
|
||||||
|
|
||||||
|
if (_horizonClient.Fs.IsMounted(mountNameu8))
|
||||||
|
{
|
||||||
|
_horizonClient.Fs.Unmount(mountNameu8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result OpenSaveDataIteratorAndReadSaveData(string mountName, ApplicationData application, out SaveDataInfo saveDataInfo)
|
||||||
|
{
|
||||||
|
Result getSvDataIteratorResult = GetSaveDataIterator(out saveDataInfo, application);
|
||||||
|
if (getSvDataIteratorResult.IsFailure()) return getSvDataIteratorResult;
|
||||||
|
|
||||||
|
Result mountSvDataResult = MountSaveDataDirectory(saveDataInfo.ProgramId.Value, mountName);
|
||||||
|
if (mountSvDataResult.IsFailure()) return mountSvDataResult;
|
||||||
|
|
||||||
|
UniqueRef<SaveDataIterator> saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||||
|
Result openSvDataIteratorResult = _horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator, SaveDataSpaceId.User);
|
||||||
|
if (openSvDataIteratorResult.IsFailure()) return openSvDataIteratorResult;
|
||||||
|
|
||||||
|
Result readSvDataInfoResult = saveDataIterator.Get.ReadSaveDataInfo(out long readCount, new Span<SaveDataInfo>(ref saveDataInfo));
|
||||||
|
if (readSvDataInfoResult.IsFailure()) return readSvDataInfoResult;
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result CopySaveDataDirectory(string sourcePath, string destPath)
|
||||||
|
{
|
||||||
|
Result openDir = _horizonClient.Fs.OpenDirectory(out DirectoryHandle dirHandle, $"{sourcePath}:/".ToU8Span(), OpenDirectoryMode.All);
|
||||||
|
if (openDir.IsFailure()) return openDir;
|
||||||
|
|
||||||
|
using (dirHandle)
|
||||||
|
{
|
||||||
|
sourcePath = $"{sourcePath}:/";
|
||||||
|
destPath = $"{destPath}:/";
|
||||||
|
foreach (DirectoryEntryEx entry in _horizonClient.Fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default))
|
||||||
|
{
|
||||||
|
string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name));
|
||||||
|
string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name));
|
||||||
|
|
||||||
|
if (entry.Type == DirectoryEntryType.Directory)
|
||||||
|
{
|
||||||
|
Result copyDirResult = CopyDirectory(subSrcPath, subDstPath);
|
||||||
|
}
|
||||||
|
if (entry.Type == DirectoryEntryType.File)
|
||||||
|
{
|
||||||
|
Result copyFinalDir = CopyFile(subSrcPath, subDstPath, entry.Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_horizonClient.Fs.CloseDirectory(dirHandle);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result CopyDirectory(string subSrcPath, string subDstPath)
|
||||||
|
{
|
||||||
|
_horizonClient.Fs.EnsureDirectoryExists(subDstPath);
|
||||||
|
|
||||||
|
Result copyDirResult = _horizonClient.Fs.CopyDirectory(subSrcPath, subDstPath);
|
||||||
|
|
||||||
|
if (copyDirResult.IsFailure())
|
||||||
|
{
|
||||||
|
Logger.Error.Value.Print(LogClass.Application, $"Could not copy directory: \n{subSrcPath} to destination {subDstPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info.Value.Print(LogClass.Application, $"Successfully copied directory: \n{subSrcPath} to destination {subDstPath}");
|
||||||
|
|
||||||
|
return copyDirResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result CopyFile(string subSrcPath, string subDstPath, long entrySize)
|
||||||
|
{
|
||||||
|
_horizonClient.Fs.CreateOrOverwriteFile(subDstPath, entrySize);
|
||||||
|
|
||||||
|
Result copyFileResult = _horizonClient.Fs.CopyFile(subSrcPath, subDstPath);
|
||||||
|
if (copyFileResult.IsFailure())
|
||||||
|
{
|
||||||
|
Logger.Error.Value.Print(LogClass.Application, $"Could not copy file: \n{subSrcPath} to destination '{subDstPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info.Value.Print(LogClass.Application, $"Successfully copied file: \n{subSrcPath} to destination {subDstPath}");
|
||||||
|
|
||||||
|
return copyFileResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LibHac.Ncm.ApplicationId ConvertProgramIdToApplicationId(ulong programId)
|
||||||
|
{
|
||||||
|
return new LibHac.Ncm.ApplicationId(programId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetAndPrepareBackupPath(MainWindow mainWindow)
|
||||||
|
{
|
||||||
|
SaveFileDialog saveFileDialog = new SaveFileDialog()
|
||||||
|
{
|
||||||
|
Title = LocaleManager.Instance["CreateZipFileDialogTitle"],
|
||||||
|
InitialFileName = "ryujinx_savedata_backup"
|
||||||
|
};
|
||||||
|
|
||||||
|
string zipPath = await saveFileDialog.ShowAsync(mainWindow);
|
||||||
|
|
||||||
|
if (File.Exists(zipPath))
|
||||||
|
{
|
||||||
|
File.Delete(zipPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return zipPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using LibHac;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Ui.Models;
|
||||||
using Ryujinx.Ava.Ui.Windows;
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -10,21 +13,16 @@ using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
internal class RestoreSavedataCommand : ICommand
|
internal class SaveDataImporter
|
||||||
{
|
{
|
||||||
public event EventHandler CanExecuteChanged;
|
|
||||||
|
|
||||||
private readonly IControl parentControl;
|
private readonly UserProfile _userProfile;
|
||||||
|
private readonly HorizonClient _horizonClient;
|
||||||
public RestoreSavedataCommand(IControl parentControl)
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
{
|
|
||||||
this.parentControl = parentControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> ShowConditionMessage()
|
private async Task<bool> ShowConditionMessage()
|
||||||
{
|
{
|
||||||
|
@ -33,28 +31,18 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
"Do you want to continue?");
|
"Do you want to continue?");
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanExecute(object parameter)
|
public async void RestoreSavedataBackup(MainWindow mainWindow)
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute(object parameter)
|
|
||||||
{
|
|
||||||
RestoreSavedataBackup();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void RestoreSavedataBackup()
|
|
||||||
{
|
{
|
||||||
if (!(await ShowConditionMessage())) return;
|
if (!(await ShowConditionMessage())) return;
|
||||||
|
|
||||||
string[] backupZipFiles = await ShowFolderDialog();
|
string[] backupZipFiles = await ShowFolderDialog(mainWindow);
|
||||||
|
|
||||||
ExtractBackupToSaveDirectory(backupZipFiles);
|
ExtractBackupToSaveDirectory(backupZipFiles);
|
||||||
|
|
||||||
Logger.Info.Value.Print(LogClass.Application, $"Done extracting savedata backup!", nameof(RestoreSavedataCommand));
|
Logger.Info.Value.Print(LogClass.Application, $"Done extracting savedata backup!", nameof(SaveDataImporter));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string[]> ShowFolderDialog()
|
private async Task<string[]> ShowFolderDialog(MainWindow mainWindow)
|
||||||
{
|
{
|
||||||
OpenFileDialog dialog = new()
|
OpenFileDialog dialog = new()
|
||||||
{
|
{
|
||||||
|
@ -63,7 +51,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
Filters = new List<FileDialogFilter>(new[] { new FileDialogFilter() { Extensions = new List<string>() { "zip" } } })
|
Filters = new List<FileDialogFilter>(new[] { new FileDialogFilter() { Extensions = new List<string>() { "zip" } } })
|
||||||
};
|
};
|
||||||
|
|
||||||
return await dialog.ShowAsync(parentControl.VisualRoot as MainWindow);
|
return await dialog.ShowAsync(mainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, string> GetTitleIdWithSavedataPath(string saveDirectoryPath)
|
private Dictionary<string, string> GetTitleIdWithSavedataPath(string saveDirectoryPath)
|
||||||
|
@ -84,7 +72,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Logger.Error.Value.Print(LogClass.Application, $"Could not extract hex from savedata file: {saveDataExtra0file}", nameof(RestoreSavedataCommand));
|
Logger.Error.Value.Print(LogClass.Application, $"Could not extract hex from savedata file: {saveDataExtra0file}", nameof(SaveDataImporter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +98,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
string tempZipExtractionPath = Path.GetTempPath();
|
string tempZipExtractionPath = Path.GetTempPath();
|
||||||
ZipFile.ExtractToDirectory(backupZipFiles.First(), tempZipExtractionPath, true);
|
ZipFile.ExtractToDirectory(backupZipFiles.First(), tempZipExtractionPath, true);
|
||||||
|
|
||||||
Logger.Info.Value.Print(LogClass.Application, $"Extracted Backup zip to temp path: {tempZipExtractionPath}", nameof(RestoreSavedataCommand));
|
Logger.Info.Value.Print(LogClass.Application, $"Extracted Backup zip to temp path: {tempZipExtractionPath}", nameof(SaveDataImporter));
|
||||||
|
|
||||||
string saveDir = Path.Combine(AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", "save");
|
string saveDir = Path.Combine(AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", "save");
|
||||||
|
|
||||||
|
@ -130,11 +118,11 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.Move(Directory.GetParent(titleIdAndBackupPath.Value).FullName, Directory.GetParent(titleIdsWithSavePaths[titleIdAndBackupPath.Key]).FullName);
|
Directory.Move(Directory.GetParent(titleIdAndBackupPath.Value).FullName, Directory.GetParent(titleIdsWithSavePaths[titleIdAndBackupPath.Key]).FullName);
|
||||||
Logger.Info.Value.Print(LogClass.Application, $"Copied Savedata {titleIdAndBackupPath.Value} to {titleIdsWithSavePaths[titleIdAndBackupPath.Key]}", nameof(RestoreSavedataCommand));
|
Logger.Info.Value.Print(LogClass.Application, $"Copied Savedata {titleIdAndBackupPath.Value} to {titleIdsWithSavePaths[titleIdAndBackupPath.Key]}", nameof(SaveDataImporter));
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Logger.Error.Value.Print(LogClass.Application, $"Could not copy Savedata {titleIdAndBackupPath.Value} to {titleIdsWithSavePaths[titleIdAndBackupPath.Key]}", nameof(RestoreSavedataCommand));
|
Logger.Error.Value.Print(LogClass.Application, $"Could not copy Savedata {titleIdAndBackupPath.Value} to {titleIdsWithSavePaths[titleIdAndBackupPath.Key]}", nameof(SaveDataImporter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -99,5 +99,10 @@
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Left" Grid.Row="1">
|
||||||
|
<Button Command="{Binding ImportBackup}">Import Backup</Button>
|
||||||
|
<Button Command="{Binding ExportBackup}">Export Backup</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
|
@ -4,15 +4,19 @@ using DynamicData.Binding;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.Fs.Impl;
|
||||||
using LibHac.Fs.Shim;
|
using LibHac.Fs.Shim;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Ui.Models;
|
using Ryujinx.Ava.Ui.Models;
|
||||||
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||||
|
|
||||||
|
@ -26,6 +30,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
private int _sortIndex;
|
private int _sortIndex;
|
||||||
private int _orderIndex;
|
private int _orderIndex;
|
||||||
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
|
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
|
||||||
|
private readonly List<ApplicationData> _applications;
|
||||||
private string _search;
|
private string _search;
|
||||||
|
|
||||||
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
|
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
|
||||||
|
@ -71,11 +76,12 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem, List<ApplicationData> applications)
|
||||||
{
|
{
|
||||||
_userProfile = userProfile;
|
_userProfile = userProfile;
|
||||||
_horizonClient = horizonClient;
|
_horizonClient = horizonClient;
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
_applications = applications;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
@ -83,8 +89,22 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
Task.Run(LoadSaves);
|
Task.Run(LoadSaves);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ImportBackup()
|
||||||
|
{
|
||||||
|
SaveDataImporter restoreSavedataCommand = new SaveDataImporter();
|
||||||
|
restoreSavedataCommand.RestoreSavedataBackup(Parent.VisualRoot as MainWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExportBackup()
|
||||||
|
{
|
||||||
|
SaveDataExporter backupSavedataCommand = new SaveDataExporter(_userProfile, _horizonClient, _virtualFileSystem);
|
||||||
|
backupSavedataCommand.SaveUserSaveDirectoryAsZip(Parent.VisualRoot as MainWindow, Saves.ToList(), _applications);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void LoadSaves()
|
public void LoadSaves()
|
||||||
{
|
{
|
||||||
|
|
||||||
Saves.Clear();
|
Saves.Clear();
|
||||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||||
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
|
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
|
||||||
|
@ -92,7 +112,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||||
|
|
||||||
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||||
|
|
||||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
|
|
|
@ -1062,7 +1062,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
|
|
||||||
public async void ManageProfiles()
|
public async void ManageProfiles()
|
||||||
{
|
{
|
||||||
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem, _owner.LibHacHorizonManager.RyujinxClient);
|
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem, _owner.LibHacHorizonManager.RyujinxClient, Applications.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void OpenAboutWindow()
|
public async void OpenAboutWindow()
|
||||||
|
|
|
@ -143,15 +143,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
{
|
{
|
||||||
UserProfile userProfile = _highlightedProfile ?? SelectedProfile;
|
UserProfile userProfile = _highlightedProfile ?? SelectedProfile;
|
||||||
|
|
||||||
SaveManager manager = new SaveManager(userProfile, _owner.HorizonClient, _owner.VirtualFileSystem);
|
SaveManager manager = new SaveManager(userProfile, _owner.HorizonClient, _owner.VirtualFileSystem, _owner.Applications);
|
||||||
|
|
||||||
ContentDialog contentDialog = new ContentDialog
|
ContentDialog contentDialog = new ContentDialog
|
||||||
{
|
{
|
||||||
Title = string.Format(LocaleManager.Instance["SaveManagerHeading"], userProfile.Name),
|
Title = string.Format(LocaleManager.Instance["SaveManagerHeading"], userProfile.Name),
|
||||||
PrimaryButtonText = "Backup Savedata",
|
PrimaryButtonText = "",
|
||||||
PrimaryButtonCommand = new BackupSavedataCommand(_owner),
|
SecondaryButtonText = "",
|
||||||
SecondaryButtonText = "Restore Savedata",
|
|
||||||
SecondaryButtonCommand = new RestoreSavedataCommand(_owner),
|
|
||||||
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
|
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
|
||||||
Content = manager,
|
Content = manager,
|
||||||
Padding = new Thickness(0)
|
Padding = new Thickness(0)
|
||||||
|
|
Loading…
Reference in a new issue