added language support

new method for backup and restoring save data
user can choose filename now
This commit is contained in:
Akisuke 2022-12-13 23:33:19 +01:00
parent fb3c282544
commit 34718e7aa7
10 changed files with 105 additions and 63 deletions

View file

@ -477,7 +477,7 @@
"GridSizeTooltip": "Ändert die Größe der Rasterelemente", "GridSizeTooltip": "Ändert die Größe der Rasterelemente",
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brasilianisches Portugiesisch", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brasilianisches Portugiesisch",
"AboutRyujinxContributorsButtonHeader": "Alle Mitwirkenden anzeigen", "AboutRyujinxContributorsButtonHeader": "Alle Mitwirkenden anzeigen",
"SettingsTabSystemAudioVolume" : "Lautstärke: ", "SettingsTabSystemAudioVolume": "Lautstärke: ",
"AudioVolumeTooltip": "Ändert die Lautstärke", "AudioVolumeTooltip": "Ändert die Lautstärke",
"SettingsTabSystemEnableInternetAccess": "Gast-Internet-Zugang/LAN Modus", "SettingsTabSystemEnableInternetAccess": "Gast-Internet-Zugang/LAN Modus",
"EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen", "EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen",
@ -542,6 +542,7 @@
"CompilingShaders": "Shader werden kompiliert", "CompilingShaders": "Shader werden kompiliert",
"AllKeyboards": "Alle Tastaturen", "AllKeyboards": "Alle Tastaturen",
"OpenFileDialogTitle": "Wähle eine unterstützte Datei", "OpenFileDialogTitle": "Wähle eine unterstützte Datei",
"CreateZipFileDialogTitle": "Wählen Sie ein Verzeichnis und einen Dateinamen aus",
"OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel", "OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel",
"AllSupportedFormats": "Alle unterstützten Formate", "AllSupportedFormats": "Alle unterstützten Formate",
"RyujinxUpdater": "Ryujinx - Updater", "RyujinxUpdater": "Ryujinx - Updater",
@ -554,8 +555,8 @@
"SettingsTabHotkeysToggleMuteHotkey": "Stummschalten:", "SettingsTabHotkeysToggleMuteHotkey": "Stummschalten:",
"ControllerMotionTitle": "Bewegungssteuerung - Einstellungen", "ControllerMotionTitle": "Bewegungssteuerung - Einstellungen",
"ControllerRumbleTitle": "Vibration - Einstellungen", "ControllerRumbleTitle": "Vibration - Einstellungen",
"SettingsSelectThemeFileDialogTitle" : "Wähle ein Design für die Emulator-Benutzeroberfläche", "SettingsSelectThemeFileDialogTitle": "Wähle ein Design für die Emulator-Benutzeroberfläche",
"SettingsXamlThemeFile" : "Xaml Design-Datei", "SettingsXamlThemeFile": "Xaml Design-Datei",
"AvatarWindowTitle": "Profile verwalten - Avatar", "AvatarWindowTitle": "Profile verwalten - Avatar",
"Amiibo": "Amiibo", "Amiibo": "Amiibo",
"Unknown": "Unbekannt", "Unknown": "Unbekannt",
@ -575,12 +576,12 @@
"Discard": "Verwerfen", "Discard": "Verwerfen",
"UserProfilesSetProfileImage": "Profilbild einrichten", "UserProfilesSetProfileImage": "Profilbild einrichten",
"UserProfileEmptyNameError": "Name ist erforderlich", "UserProfileEmptyNameError": "Name ist erforderlich",
"UserProfileNoImageError": "Bitte ein Profilbild auswählen", "UserProfileNoImageError": "Bitte ein Profilbild auswählen",
"GameUpdateWindowHeading": "Update verfügbar für {0} [{1}]", "GameUpdateWindowHeading": "Update verfügbar für {0} [{1}]",
"SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:", "SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:",
"SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:", "SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:",
"UserProfilesName": "Name:", "UserProfilesName": "Name:",
"UserProfilesUserId" : "Benutzer Id:", "UserProfilesUserId": "Benutzer Id:",
"SettingsTabGraphicsBackend": "Grafik-Backend:", "SettingsTabGraphicsBackend": "Grafik-Backend:",
"SettingsTabGraphicsBackendTooltip": "Verwendendetes Grafik-Backend", "SettingsTabGraphicsBackendTooltip": "Verwendendetes Grafik-Backend",
"SettingsEnableTextureRecompression": "Textur-Rekompression", "SettingsEnableTextureRecompression": "Textur-Rekompression",

View file

@ -545,6 +545,7 @@
"CompilingShaders": "Compiling Shaders", "CompilingShaders": "Compiling Shaders",
"AllKeyboards": "All keyboards", "AllKeyboards": "All keyboards",
"OpenFileDialogTitle": "Select a supported file to open", "OpenFileDialogTitle": "Select a supported file to open",
"CreateZipFileDialogTitle": "Select a directory and filename",
"OpenFolderDialogTitle": "Select a folder with an unpacked game", "OpenFolderDialogTitle": "Select a folder with an unpacked game",
"AllSupportedFormats": "All Supported Formats", "AllSupportedFormats": "All Supported Formats",
"RyujinxUpdater": "Ryujinx Updater", "RyujinxUpdater": "Ryujinx Updater",

View file

@ -476,7 +476,7 @@
"GridSize": "Tamaño de cuadrícula", "GridSize": "Tamaño de cuadrícula",
"GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula", "GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula",
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño",
"AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores", "AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores",
"SettingsTabSystemAudioVolume": "Volumen: ", "SettingsTabSystemAudioVolume": "Volumen: ",
"AudioVolumeTooltip": "Ajusta el nivel de volumen", "AudioVolumeTooltip": "Ajusta el nivel de volumen",
"SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN", "SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN",
@ -542,6 +542,7 @@
"CompilingShaders": "Compilando sombreadores", "CompilingShaders": "Compilando sombreadores",
"AllKeyboards": "Todos los teclados", "AllKeyboards": "Todos los teclados",
"OpenFileDialogTitle": "Selecciona un archivo soportado para cargar", "OpenFileDialogTitle": "Selecciona un archivo soportado para cargar",
"CreateZipFileDialogTitle": "Seleccione un directorio y un nombre de archivo",
"OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado", "OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado",
"AllSupportedFormats": "Todos los formatos soportados", "AllSupportedFormats": "Todos los formatos soportados",
"RyujinxUpdater": "Actualizador de Ryujinx", "RyujinxUpdater": "Actualizador de Ryujinx",

View file

@ -542,6 +542,7 @@
"CompilingShaders": "シェーダをコンパイル中", "CompilingShaders": "シェーダをコンパイル中",
"AllKeyboards": "すべてのキーボード", "AllKeyboards": "すべてのキーボード",
"OpenFileDialogTitle": "開くファイルを選択", "OpenFileDialogTitle": "開くファイルを選択",
"CreateZipFileDialogTitle": "ディレクトリとファイル名を選択",
"OpenFolderDialogTitle": "展開されたゲームフォルダを選択", "OpenFolderDialogTitle": "展開されたゲームフォルダを選択",
"AllSupportedFormats": "すべての対応フォーマット", "AllSupportedFormats": "すべての対応フォーマット",
"RyujinxUpdater": "Ryujinx アップデータ", "RyujinxUpdater": "Ryujinx アップデータ",
@ -575,12 +576,12 @@
"Discard": "破棄", "Discard": "破棄",
"UserProfilesSetProfileImage": "プロファイル画像を設定", "UserProfilesSetProfileImage": "プロファイル画像を設定",
"UserProfileEmptyNameError": "名称が必要です", "UserProfileEmptyNameError": "名称が必要です",
"UserProfileNoImageError": "プロファイル画像が必要です", "UserProfileNoImageError": "プロファイル画像が必要です",
"GameUpdateWindowHeading": "利用可能なアップデート {0} [{1}]", "GameUpdateWindowHeading": "利用可能なアップデート {0} [{1}]",
"SettingsTabHotkeysResScaleUpHotkey": "解像度を上げる:", "SettingsTabHotkeysResScaleUpHotkey": "解像度を上げる:",
"SettingsTabHotkeysResScaleDownHotkey": "解像度を下げる:", "SettingsTabHotkeysResScaleDownHotkey": "解像度を下げる:",
"UserProfilesName": "名称:", "UserProfilesName": "名称:",
"UserProfilesUserId" : "ユーザID:", "UserProfilesUserId": "ユーザID:",
"SettingsTabGraphicsBackend": "グラフィックスバックエンド", "SettingsTabGraphicsBackend": "グラフィックスバックエンド",
"SettingsTabGraphicsBackendTooltip": "使用するグラフィックスバックエンドです", "SettingsTabGraphicsBackendTooltip": "使用するグラフィックスバックエンドです",
"SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効", "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効",

View file

@ -542,6 +542,7 @@
"CompilingShaders": "Kompilowanie Shaderów", "CompilingShaders": "Kompilowanie Shaderów",
"AllKeyboards": "Wszystkie klawiatury", "AllKeyboards": "Wszystkie klawiatury",
"OpenFileDialogTitle": "Wybierz obsługiwany plik do otwarcia", "OpenFileDialogTitle": "Wybierz obsługiwany plik do otwarcia",
"CreateZipFileDialogTitle": "Wybierz katalog i nazwę pliku",
"OpenFolderDialogTitle": "Wybierz folder z rozpakowaną grą", "OpenFolderDialogTitle": "Wybierz folder z rozpakowaną grą",
"AllSupportedFormats": "Wszystkie Obsługiwane Formaty", "AllSupportedFormats": "Wszystkie Obsługiwane Formaty",
"RyujinxUpdater": "Aktualizator Ryujinx", "RyujinxUpdater": "Aktualizator Ryujinx",
@ -575,12 +576,12 @@
"Discard": "Odrzuć", "Discard": "Odrzuć",
"UserProfilesSetProfileImage": "Ustaw Obraz Profilu", "UserProfilesSetProfileImage": "Ustaw Obraz Profilu",
"UserProfileEmptyNameError": "Nazwa jest wymagana", "UserProfileEmptyNameError": "Nazwa jest wymagana",
"UserProfileNoImageError": "Należy ustawić obraz profilowy", "UserProfileNoImageError": "Należy ustawić obraz profilowy",
"GameUpdateWindowHeading": "Aktualizacje Dostępne dla {0} [{1}]", "GameUpdateWindowHeading": "Aktualizacje Dostępne dla {0} [{1}]",
"SettingsTabHotkeysResScaleUpHotkey": "Zwiększ Rozdzielczość:", "SettingsTabHotkeysResScaleUpHotkey": "Zwiększ Rozdzielczość:",
"SettingsTabHotkeysResScaleDownHotkey": "Zmniejsz Rozdzielczość:", "SettingsTabHotkeysResScaleDownHotkey": "Zmniejsz Rozdzielczość:",
"UserProfilesName": "Nazwa:", "UserProfilesName": "Nazwa:",
"UserProfilesUserId" : "ID Użytkownika:", "UserProfilesUserId": "ID Użytkownika:",
"SettingsTabGraphicsBackend": "Backend Graficzny", "SettingsTabGraphicsBackend": "Backend Graficzny",
"SettingsTabGraphicsBackendTooltip": "Używalne Backendy Graficzne", "SettingsTabGraphicsBackendTooltip": "Używalne Backendy Graficzne",
"SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur", "SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur",

View file

@ -542,6 +542,7 @@
"CompilingShaders": "Shaderlar Derleniyor", "CompilingShaders": "Shaderlar Derleniyor",
"AllKeyboards": "Tüm Klavyeler", "AllKeyboards": "Tüm Klavyeler",
"OpenFileDialogTitle": "Açmak için desteklenen bir dosya seçin", "OpenFileDialogTitle": "Açmak için desteklenen bir dosya seçin",
"CreateZipFileDialogTitle": "Bir dizin ve dosya adı seçin",
"OpenFolderDialogTitle": "Ayrıştırılmamış oyun içeren bir klasör seçin", "OpenFolderDialogTitle": "Ayrıştırılmamış oyun içeren bir klasör seçin",
"AllSupportedFormats": "Tüm Desteklenen Formatlar", "AllSupportedFormats": "Tüm Desteklenen Formatlar",
"RyujinxUpdater": "Ryujinx Güncelleyicisi", "RyujinxUpdater": "Ryujinx Güncelleyicisi",
@ -575,7 +576,7 @@
"Discard": "Iskarta", "Discard": "Iskarta",
"UserProfilesSetProfileImage": "Profil Resmi Ayarla", "UserProfilesSetProfileImage": "Profil Resmi Ayarla",
"UserProfileEmptyNameError": "İsim gerekli", "UserProfileEmptyNameError": "İsim gerekli",
"UserProfileNoImageError": "Profil resmi ayarlanmalıdır", "UserProfileNoImageError": "Profil resmi ayarlanmalıdır",
"GameUpdateWindowHeading": "{0} için güncellemeler mevcut [{1}]", "GameUpdateWindowHeading": "{0} için güncellemeler mevcut [{1}]",
"SettingsTabHotkeysResScaleUpHotkey": "Çözünürlüğü artır:", "SettingsTabHotkeysResScaleUpHotkey": "Çözünürlüğü artır:",
"SettingsTabHotkeysResScaleDownHotkey": "Çözünürlüğü azalt:" "SettingsTabHotkeysResScaleDownHotkey": "Çözünürlüğü azalt:"

View file

@ -542,6 +542,7 @@
"CompilingShaders": "编译着色器中", "CompilingShaders": "编译着色器中",
"AllKeyboards": "所有键盘", "AllKeyboards": "所有键盘",
"OpenFileDialogTitle": "选择支持的文件格式", "OpenFileDialogTitle": "选择支持的文件格式",
"CreateZipFileDialogTitle": "选择目录和文件名",
"OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹", "OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹",
"AllSupportedFormats": "所有支持的格式", "AllSupportedFormats": "所有支持的格式",
"RyujinxUpdater": "Ryujinx 更新程序", "RyujinxUpdater": "Ryujinx 更新程序",
@ -575,12 +576,12 @@
"Discard": "返回", "Discard": "返回",
"UserProfilesSetProfileImage": "选择头像", "UserProfilesSetProfileImage": "选择头像",
"UserProfileEmptyNameError": "必须输入名称", "UserProfileEmptyNameError": "必须输入名称",
"UserProfileNoImageError": "请选择您的头像", "UserProfileNoImageError": "请选择您的头像",
"GameUpdateWindowHeading": "适用于 {0} [{1}] 的更新", "GameUpdateWindowHeading": "适用于 {0} [{1}] 的更新",
"SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:", "SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:",
"SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:", "SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:",
"UserProfilesName": "名称:", "UserProfilesName": "名称:",
"UserProfilesUserId" : "用户 ID:", "UserProfilesUserId": "用户 ID:",
"SettingsTabGraphicsBackend": "图形后端", "SettingsTabGraphicsBackend": "图形后端",
"SettingsTabGraphicsBackendTooltip": "显卡使用的图形后端", "SettingsTabGraphicsBackendTooltip": "显卡使用的图形后端",
"SettingsEnableTextureRecompression": "启用纹理重压缩", "SettingsEnableTextureRecompression": "启用纹理重压缩",

View file

@ -542,6 +542,7 @@
"CompilingShaders": "編譯渲染器中", "CompilingShaders": "編譯渲染器中",
"AllKeyboards": "所有鍵盤", "AllKeyboards": "所有鍵盤",
"OpenFileDialogTitle": "選擇支援的檔案格式", "OpenFileDialogTitle": "選擇支援的檔案格式",
"CreateZipFileDialogTitle": "選擇目錄和文件名",
"OpenFolderDialogTitle": "選擇一個包含解包遊戲的資料夾", "OpenFolderDialogTitle": "選擇一個包含解包遊戲的資料夾",
"AllSupportedFormats": "全部支援的格式", "AllSupportedFormats": "全部支援的格式",
"RyujinxUpdater": "Ryujinx 更新程式", "RyujinxUpdater": "Ryujinx 更新程式",

View file

@ -1,5 +1,5 @@
using Avalonia.Controls; 
using Avalonia.Logging; using Avalonia.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Windows; using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@ -17,6 +17,7 @@ namespace Ryujinx.Ava.Ui.Controls
internal class BackupSavedataCommand : ICommand internal class BackupSavedataCommand : ICommand
{ {
public event EventHandler CanExecuteChanged; public event EventHandler CanExecuteChanged;
private readonly IControl parentControl; private readonly IControl parentControl;
public BackupSavedataCommand(IControl parentControl) public BackupSavedataCommand(IControl parentControl)
@ -36,37 +37,46 @@ namespace Ryujinx.Ava.Ui.Controls
public async void SaveUserSaveDirectoryAsZip() public async void SaveUserSaveDirectoryAsZip()
{ {
CreateBackupZip(await OpenFolderDialog()); CreateBackupZip(await GetAndPrepareBackupPath());
} }
private async Task<string> OpenFolderDialog() private async Task<string> GetAndPrepareBackupPath()
{ {
OpenFolderDialog dialog = new() SaveFileDialog saveFileDialog = new SaveFileDialog()
{ {
Title = LocaleManager.Instance["OpenFolderDialogTitle"] 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" } } })
}; };
return await dialog.ShowAsync(parentControl.VisualRoot as MainWindow); string zipPath = await saveFileDialog.ShowAsync(parentControl.VisualRoot as MainWindow);
if (File.Exists(zipPath))
{
File.Delete(zipPath);
}
return zipPath;
} }
private void CreateBackupZip(string directoryPath) private void CreateBackupZip(string userBackupPath)
{ {
if (!string.IsNullOrWhiteSpace(directoryPath) && Directory.Exists(directoryPath)) if (!string.IsNullOrWhiteSpace(userBackupPath) && Directory.Exists(Directory.GetParent(userBackupPath).FullName))
{ {
string saveDir = Path.Combine(AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", "save"); string saveDir = Path.Combine(AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", "save");
string zipFolderPath = Path.Combine(directoryPath, "Ryujinx_backup.zip"); try
Logger.Info.Value.Print(LogClass.Application, $"Start creating backup...", nameof(BackupSavedataCommand));
if (File.Exists(zipFolderPath))
{ {
File.Delete(zipFolderPath); Logger.Info.Value.Print(LogClass.Application, $"Start creating backup...", nameof(BackupSavedataCommand));
}
ZipFile.CreateFromDirectory(saveDir, zipFolderPath);
Logger.Info.Value.Print(LogClass.Application, $"Backup done. Zip is locate under {directoryPath}", 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));
}
} }
} }
} }

View file

@ -4,9 +4,11 @@ using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Path = System.IO.Path; using Path = System.IO.Path;
@ -58,11 +60,49 @@ namespace Ryujinx.Ava.Ui.Controls
{ {
Title = LocaleManager.Instance["OpenFileDialogTitle"], Title = LocaleManager.Instance["OpenFileDialogTitle"],
AllowMultiple = false, AllowMultiple = false,
Filters = new List<FileDialogFilter>(new[] { new FileDialogFilter() { Extensions = new List<string>() { "zip" } } })
}; };
return await dialog.ShowAsync(parentControl.VisualRoot as MainWindow); return await dialog.ShowAsync(parentControl.VisualRoot as MainWindow);
} }
private Dictionary<string, string> GetTitleIdWithSavedataPath(string saveDirectoryPath)
{
Dictionary<string, string> titleIdWithSavePath = new Dictionary<string, string>();
//Loop through all ExtraData0 files in the savedata directory and read the first 8 bytes to determine which game this belongs to
foreach (var saveDataExtra0file in Directory.GetFiles(saveDirectoryPath, "ExtraData0*", SearchOption.AllDirectories))
{
try
{
string hexValues = FlipHexBytes(new string(Convert.ToHexString(File.ReadAllBytes(saveDataExtra0file)).Substring(0, 16).Reverse().ToArray()));
if (!titleIdWithSavePath.ContainsKey(hexValues))
{
titleIdWithSavePath.Add(hexValues, saveDataExtra0file);
}
}
catch (Exception)
{
Logger.Error.Value.Print(LogClass.Application, $"Could not extract hex from savedata file: {saveDataExtra0file}", nameof(RestoreSavedataCommand));
}
}
return titleIdWithSavePath;
}
private string FlipHexBytes(string hexString)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i <= hexString.Length - 2; i = i + 2)
{
result.Append(new StringBuilder(new string(hexString.Substring(i, 2).Reverse().ToArray())));
}
return result.ToString();
}
private void ExtractBackupToSaveDirectory(string[] backupZipFiles) private void ExtractBackupToSaveDirectory(string[] backupZipFiles)
{ {
if (!string.IsNullOrWhiteSpace(backupZipFiles.First()) && File.Exists(backupZipFiles.First())) if (!string.IsNullOrWhiteSpace(backupZipFiles.First()) && File.Exists(backupZipFiles.First()))
@ -73,47 +113,31 @@ namespace Ryujinx.Ava.Ui.Controls
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(RestoreSavedataCommand));
string saveDir = Path.Combine(AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", "save"); string saveDir = Path.Combine(AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", "save");
ReplaceSavedataFilesWithBackupSaveFiles(Directory.GetDirectories(tempZipExtractionPath), saveDir);
Dictionary<string, string> titleIdsAndSavePaths = GetTitleIdWithSavedataPath(saveDir);
Dictionary<string, string> titleIdsAndBackupPaths = GetTitleIdWithSavedataPath(tempZipExtractionPath);
ReplaceSavedataFiles(titleIdsAndSavePaths, titleIdsAndBackupPaths);
} }
} }
private void ReplaceSavedataFilesWithBackupSaveFiles(string[] backupSavedataPath, string saveDirectory) private void ReplaceSavedataFiles(Dictionary<string, string> titleIdsWithSavePaths, Dictionary<string, string> titleIdsAndBackupPaths)
{ {
//All current save files for later replacement foreach (var titleIdAndBackupPath in titleIdsAndBackupPaths)
string[] userSaveFiles = GetSaveFilesInAllSubDirectories(saveDirectory);
//Loops through every Title save folder and replaces all user save data files with the savedata files inside the extracted backup folder
//Logic to decide wich file is replaces is based on der filename and parent directory
foreach (string[] backupSaveFiles in backupSavedataPath.Select(GetSaveFilesInAllSubDirectories))
{ {
foreach (string backupSaveFile in backupSaveFiles) if (titleIdsWithSavePaths.ContainsKey(titleIdAndBackupPath.Key))
{ {
foreach (string userSaveFile in GetSaveFilesWithSameNameAndParentDir(userSaveFiles, backupSaveFile)) try
{ {
try 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));
File.Copy(backupSaveFile, userSaveFile, true); }
Logger.Info.Value.Print(LogClass.Application, $"Copied Savedata {backupSaveFile} to {userSaveFile}", nameof(RestoreSavedataCommand)); 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 {backupSaveFile} to {userSaveFile}", nameof(RestoreSavedataCommand));
}
} }
} }
} }
} }
private string[] GetSaveFilesWithSameNameAndParentDir(string[] userSaveFiles, string backupSaveFile)
{
return userSaveFiles.Where(sf => Path.GetFileName(sf) == Path.GetFileName(backupSaveFile) && Directory.GetParent(sf).Name == Directory.GetParent(backupSaveFile).Name).ToArray();
}
private string[] GetSaveFilesInAllSubDirectories(string rootDirectory)
{
string[] unnecessarySaveFiles = { ".lock", "ExtraData0", "ExtraData1" };
return Directory.GetFiles(rootDirectory, "*", SearchOption.AllDirectories).Where(file => !unnecessarySaveFiles.Any(usf => file.EndsWith(usf))).ToArray();
}
} }
} }