ryujinx/Ryujinx/Ui/GameTableContextMenu.cs
Alex Barney 63b24b4af2 Rename "RyuFs" directory to "Ryujinx" and use the same savedata system the Switch uses (#801)
* Use savedata FS commands from LibHac

* Add EnsureSaveData. Use ApplicationControlProperty struct

* Add a function to migrate to the new directory layout

* LibHac update

* Change backup structure

* Don't create UI files in the save path

* Update RyuFs paths

* Add GetProgramIndexForAccessLog

Ryujinx only runs one program at a time, so always return values reflecting that

* Load control NCA when loading from an NSP

* Skip over UI stats when exiting

* Set TitleName and TitleId in more cases. Fix TitleID naming style

* Completely comment out GUI play stats code

* rebase

* Update LibHac

* Update LibHac

* Revert UI changes

* Do migration automatically at startup

* Rename RyuFs directory to Ryujinx

* Update RyuFs text

* Store savedata paths in the GUI

* Make "Open Save Directory" work

* Use a dummy NACP in EnsureSaveData if one is not loaded

* Remove manual migration button

* Respond to feedback

* Don't read the installer config to get a version string

* Delete nuget.config

* Exclude 'sdcard' and 'bis' during migration

Co-authored-by: Thog <thog@protonmail.com>
2020-01-05 12:49:44 +01:00

153 lines
5.5 KiB
C#

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;
using GUI = Gtk.Builder.ObjectAttribute;
namespace Ryujinx.Ui
{
public class GameTableContextMenu : Menu
{
private static ListStore _gameTableStore;
private static TreeIter _rowIter;
private FileSystemClient _fsClient;
#pragma warning disable CS0649
#pragma warning disable IDE0044
[GUI] MenuItem _openSaveDir;
#pragma warning restore CS0649
#pragma warning restore IDE0044
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, FileSystemClient fsClient) : base(builder.GetObject("_contextMenu").Handle)
{
builder.Autoconnect(this);
_openSaveDir.Activated += OpenSaveDir_Clicked;
_gameTableStore = gameTableStore;
_rowIter = rowIter;
_fsClient = fsClient;
}
//Events
private void OpenSaveDir_Clicked(object sender, EventArgs args)
{
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
if (!TryFindSaveData(titleName, titleId, out ulong saveDataId))
{
return;
}
string saveDir = GetSaveDataDirectory(saveDataId);
Process.Start(new ProcessStartInfo()
{
FileName = saveDir,
UseShellExecute = true,
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;
}
}
}