diff --git a/Ryujinx/Ui/ApplicationLibrary.cs b/Ryujinx/Ui/ApplicationLibrary.cs index 2c0806f7e5..90d333a252 100644 --- a/Ryujinx/Ui/ApplicationLibrary.cs +++ b/Ryujinx/Ui/ApplicationLibrary.cs @@ -343,7 +343,7 @@ namespace Ryujinx.Ui if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNum)) { - var filter = new SaveDataFilter(); + SaveDataFilter filter = new SaveDataFilter(); filter.SetUserId(new UserId(1, 0)); filter.SetTitleId(new TitleId(titleIdNum)); diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs index f8d1d6815f..e74d182854 100644 --- a/Ryujinx/Ui/GameTableContextMenu.cs +++ b/Ryujinx/Ui/GameTableContextMenu.cs @@ -1,7 +1,12 @@ using Gtk; +using LibHac; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.Ncm; using Ryujinx.HLE.FileSystem; using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Reflection; @@ -13,6 +18,7 @@ namespace Ryujinx.Ui { private static ListStore _gameTableStore; private static TreeIter _rowIter; + private FileSystemClient _fsClient; #pragma warning disable CS0649 #pragma warning disable IDE0044 @@ -20,9 +26,10 @@ namespace Ryujinx.Ui #pragma warning restore CS0649 #pragma warning restore IDE0044 - public GameTableContextMenu(ListStore gameTableStore, TreeIter rowIter) : this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, rowIter) { } + public GameTableContextMenu(ListStore gameTableStore, TreeIter rowIter, FileSystemClient fsClient) + : this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, rowIter, fsClient) { } - private GameTableContextMenu(Builder builder, ListStore gameTableStore, TreeIter rowIter) : base(builder.GetObject("_contextMenu").Handle) + private GameTableContextMenu(Builder builder, ListStore gameTableStore, TreeIter rowIter, FileSystemClient fsClient) : base(builder.GetObject("_contextMenu").Handle) { builder.Autoconnect(this); @@ -30,6 +37,7 @@ namespace Ryujinx.Ui _gameTableStore = gameTableStore; _rowIter = rowIter; + _fsClient = fsClient; } //Events @@ -37,33 +45,14 @@ namespace Ryujinx.Ui { string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); - string saveDir = System.IO.Path.Combine(new VirtualFileSystem().GetNandPath(), "user", "save", "0000000000000000", "00000000000000000000000000000001", titleId, "0"); - if (!Directory.Exists(saveDir)) + if (!TryFindSaveData(titleName, titleId, out ulong saveDataId)) { - MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null) - { - Title = "Ryujinx", - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), - Text = $"Could not find save directory for {titleName} [{titleId}]", - SecondaryText = "Would you like to create the directory?", - WindowPosition = WindowPosition.Center - }; - - if (messageDialog.Run() == (int)ResponseType.Yes) - { - Directory.CreateDirectory(saveDir); - } - else - { - messageDialog.Dispose(); - - return; - } - - messageDialog.Dispose(); + return; } + string saveDir = GetSaveDataDirectory(saveDataId); + Process.Start(new ProcessStartInfo() { FileName = saveDir, @@ -71,5 +60,93 @@ namespace Ryujinx.Ui Verb = "open" }); } + + private bool TryFindSaveData(string titleName, string titleIdText, out ulong saveDataId) + { + saveDataId = default; + + if (!ulong.TryParse(titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleId)) + { + GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID"); + + return false; + } + + SaveDataFilter filter = new SaveDataFilter(); + filter.SetUserId(new UserId(1, 0)); + filter.SetTitleId(new TitleId(titleId)); + + Result result = _fsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); + + if (result == ResultFs.TargetNotFound) + { + // Savedata was not found. Ask the user if they want to create it + using MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null) + { + Title = "Ryujinx", + Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), + Text = $"There is no savedata for {titleName} [{titleId:x16}]", + SecondaryText = "Would you like to create savedata for this game?", + WindowPosition = WindowPosition.Center + }; + + if (messageDialog.Run() != (int)ResponseType.Yes) + { + return false; + } + + result = _fsClient.CreateSaveData(new TitleId(titleId), new UserId(1, 0), new TitleId(titleId), 0, 0, 0); + + if (result.IsFailure()) + { + GtkDialog.CreateErrorDialog($"There was an error creating the specified savedata: {result.ToStringWithName()}"); + + return false; + } + + // Try to find the savedata again after creating it + result = _fsClient.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, ref filter); + } + + if (result.IsSuccess()) + { + saveDataId = saveDataInfo.SaveDataId; + + return true; + } + + GtkDialog.CreateErrorDialog($"There was an error finding the specified savedata: {result.ToStringWithName()}"); + + return false; + } + + private string GetSaveDataDirectory(ulong saveDataId) + { + string saveRootPath = System.IO.Path.Combine(new VirtualFileSystem().GetNandPath(), $"user/save/{saveDataId:x16}"); + + if (!Directory.Exists(saveRootPath)) + { + // Inconsistent state. Create the directory + Directory.CreateDirectory(saveRootPath); + } + + string committedPath = System.IO.Path.Combine(saveRootPath, "0"); + string workingPath = System.IO.Path.Combine(saveRootPath, "1"); + + // If the committed directory exists, that path will be loaded the next time the savedata is mounted + if (Directory.Exists(committedPath)) + { + return committedPath; + } + + // If the working directory exists and the committed directory doesn't, + // the working directory will be loaded the next time the savedata is mounted + if (!Directory.Exists(workingPath)) + { + Directory.CreateDirectory(workingPath); + } + + return workingPath; + } } } diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index e5dbb3266f..c29074bfe9 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -491,7 +491,7 @@ namespace Ryujinx.Ui if (treeIter.UserData == IntPtr.Zero) return; - GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter); + GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter, _device.System.FsClient); contextMenu.ShowAll(); contextMenu.PopupAtPointer(null); } diff --git a/Ryujinx/Ui/Migration.cs b/Ryujinx/Ui/Migration.cs index a51397e5a1..c5418cf200 100644 --- a/Ryujinx/Ui/Migration.cs +++ b/Ryujinx/Ui/Migration.cs @@ -20,8 +20,6 @@ namespace Ryujinx.Ui public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded) { - const int responseYes = -8; - if (!IsMigrationNeeded()) { isMigrationNeeded = false; @@ -46,7 +44,7 @@ namespace Ryujinx.Ui dialogResponse = dialog.Run(); } - return dialogResponse == responseYes; + return dialogResponse == (int)ResponseType.Yes; } public static bool DoMigrationForStartup(Window parentWindow, Switch device)